添加基础载具类

This commit is contained in:
Light_Quanta 2025-03-28 20:39:58 +08:00
parent b2c48cde0e
commit 250d021cd9
No known key found for this signature in database
GPG key ID: 11A39A1B8C890959
24 changed files with 2733 additions and 7 deletions

View file

@ -28,6 +28,7 @@ public class ModCapabilities {
@SubscribeEvent
public static void registerCapabilities(RegisterCapabilitiesEvent event) {
// 玩家变量和激光
event.registerEntity(ModCapabilities.LASER_CAPABILITY, EntityType.PLAYER, new LaserCapabilityProvider());
event.registerEntity(ModCapabilities.PLAYER_VARIABLE, EntityType.PLAYER, new PlayerVariablesProvider());
@ -47,5 +48,9 @@ public class ModCapabilities {
}
}
// 载具
// TODO 载具能量
// event.registerEntity(Capabilities.EnergyStorage.ENTITY, ModEntities., );
}
}

View file

@ -6,6 +6,7 @@ import com.atsuishio.superbwarfare.config.server.MiscConfig;
import com.atsuishio.superbwarfare.config.server.ProjectileConfig;
import com.atsuishio.superbwarfare.entity.ICustomKnockback;
import com.atsuishio.superbwarfare.entity.TargetEntity;
import com.atsuishio.superbwarfare.entity.vehicle.base.VehicleEntity;
import com.atsuishio.superbwarfare.init.*;
import com.atsuishio.superbwarfare.network.message.ClientIndicatorMessage;
import com.atsuishio.superbwarfare.network.message.ClientMotionSyncMessage;
@ -739,10 +740,9 @@ public class ProjectileEntity extends Projectile implements IEntityWithComplexSp
entity.hurt(ModDamageTypes.causeGunFireHeadshotAbsoluteDamage(this.level().registryAccess(), this, this.shooter), absoluteDamage * headShotModifier);
entity.invulnerableTime = 0;
// TODO 大于1的穿甲对载具造成额外伤害
// if (entity instanceof VehicleEntity vehicle && this.bypassArmorRate > 1) {
// vehicle.hurt(ModDamageTypes.causeGunFireAbsoluteDamage(this.level().registryAccess(), this, this.shooter), absoluteDamage * (this.bypassArmorRate - 1) * 0.5f);
// }
if (entity instanceof VehicleEntity vehicle && this.bypassArmorRate > 1) {
vehicle.hurt(ModDamageTypes.causeGunFireAbsoluteDamage(this.level().registryAccess(), this, this.shooter), absoluteDamage * (this.bypassArmorRate - 1) * 0.5f);
}
}
}

View file

@ -0,0 +1,77 @@
package com.atsuishio.superbwarfare.entity.vehicle.base;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
public interface ArmedVehicleEntity {
/**
* 载具开火
*
* @param player 玩家
*/
void vehicleShoot(Player player, int type);
/**
* 判断指定玩家是否是载具驾驶员
*
* @param player 玩家
* @return 是否是驾驶员
*/
default boolean isDriver(Player player) {
if (this instanceof Entity entity) {
return player == entity.getFirstPassenger();
}
return false;
}
/**
* 主武器射速
*
* @return 射速
*/
int mainGunRpm(Player player);
/**
* 当前情况载具是否可以开火
*
* @param player 玩家
* @return 是否可以开火
*/
boolean canShoot(Player player);
/**
* 获取当前选择的主武器的备弹数量
*
* @param player 玩家
* @return 备弹数量
*/
int getAmmoCount(Player player);
/**
* 是否禁用玩家手臂
*
* @param player 玩家
*/
default boolean banHand(Player player) {
// 若玩家所在位置有可用武器则默认禁用手臂
if (this instanceof VehicleEntity vehicle && this instanceof WeaponVehicleEntity weaponVehicle) {
return weaponVehicle.hasWeapon(vehicle.getSeatIndex(player));
}
return false;
}
/**
* 是否隐藏载具上的玩家
*
* @return 是否隐藏
*/
boolean hidePassenger(Entity entity);
/**
* 瞄准时的放大倍率
*
* @return 放大倍率
*/
int zoomFov();
}

View file

@ -0,0 +1,4 @@
package com.atsuishio.superbwarfare.entity.vehicle.base;
public interface CannonEntity extends ArmedVehicleEntity, WeaponVehicleEntity {
}

View file

@ -0,0 +1,259 @@
package com.atsuishio.superbwarfare.entity.vehicle.base;
import com.atsuishio.superbwarfare.init.ModItems;
import com.atsuishio.superbwarfare.menu.VehicleMenu;
import com.atsuishio.superbwarfare.tools.InventoryTool;
import net.minecraft.core.NonNullList;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.ContainerHelper;
import net.minecraft.world.Containers;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.HasCustomInventoryScreen;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.vehicle.ContainerEntity;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.gameevent.GameEvent;
import net.minecraft.world.level.storage.loot.LootTable;
import net.neoforged.neoforge.capabilities.Capabilities;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.joml.Math;
public abstract class ContainerMobileVehicleEntity extends MobileVehicleEntity implements HasCustomInventoryScreen, ContainerEntity {
public static final int CONTAINER_SIZE = 102;
private final NonNullList<ItemStack> items = NonNullList.withSize(CONTAINER_SIZE, ItemStack.EMPTY);
// private Supplier<?> itemHandler = () -> new InvWrapper(this);
public ContainerMobileVehicleEntity(EntityType<?> pEntityType, Level pLevel) {
super(pEntityType, pLevel);
}
@Override
public void addAdditionalSaveData(CompoundTag compound) {
super.addAdditionalSaveData(compound);
ContainerHelper.saveAllItems(compound, this.getItemStacks(), level().registryAccess());
}
@Override
public void readAdditionalSaveData(CompoundTag compound) {
super.readAdditionalSaveData(compound);
ContainerHelper.loadAllItems(compound, this.getItemStacks(), level().registryAccess());
}
@Override
public @NotNull InteractionResult interact(Player player, @NotNull InteractionHand hand) {
if (player.getVehicle() == this) return InteractionResult.PASS;
ItemStack stack = player.getMainHandItem();
if (player.isShiftKeyDown() && !stack.is(ModItems.CROWBAR.get())) {
player.openMenu(this);
return !player.level().isClientSide ? InteractionResult.CONSUME : InteractionResult.SUCCESS;
}
return super.interact(player, hand);
}
@Override
public void remove(@NotNull RemovalReason pReason) {
if (!this.level().isClientSide && pReason != RemovalReason.DISCARDED) {
Containers.dropContents(this.level(), this, this);
}
super.remove(pReason);
}
@Override
public void baseTick() {
super.baseTick();
for (var stack : this.getItemStacks()) {
int neededEnergy = this.getMaxEnergy() - this.getEnergy();
if (neededEnergy <= 0) break;
var energyCap = stack.getCapability(Capabilities.EnergyStorage.ITEM);
if (energyCap == null) continue;
var stored = energyCap.getEnergyStored();
if (stored <= 0) continue;
int energyToExtract = Math.min(stored, neededEnergy);
energyCap.extractEnergy(energyToExtract, false);
this.setEnergy(this.getEnergy() + energyToExtract);
}
this.refreshDimensions();
}
@Override
public void openCustomInventoryScreen(Player pPlayer) {
pPlayer.openMenu(this);
if (!pPlayer.level().isClientSide) {
this.gameEvent(GameEvent.CONTAINER_OPEN, pPlayer);
}
}
@Override
public ResourceKey<LootTable> getLootTable() {
return null;
}
@Override
public void setLootTable(@Nullable ResourceKey<LootTable> lootTable) {
}
@Override
public long getLootTableSeed() {
return 0;
}
@Override
public void setLootTableSeed(long pLootTableSeed) {
}
@Override
public @NotNull NonNullList<ItemStack> getItemStacks() {
return this.items;
}
/**
* 计算当前载具内指定物品的数量
*
* @param item 物品类型
* @return 物品数量
*/
public int countItem(@NotNull Item item) {
return InventoryTool.countItem(this.getItemStacks(), item);
}
/**
* 判断载具内是否包含指定物品
*
* @param item 物品类型
*/
public boolean hasItem(Item item) {
return countItem(item) > 0;
}
/**
* 消耗载具内指定物品
*
* @param item 物品类型
* @param count 要消耗的数量
* @return 成功消耗的物品数量
*/
public int consumeItem(Item item, int count) {
return InventoryTool.consumeItem(this.getItemStacks(), item, count);
}
/**
* 尝试插入指定物品指定数量如果载具内已满则生成掉落物
*
* @param item 物品类型
* @param count 要插入的数量
*/
public void insertItem(Item item, int count) {
var rest = InventoryTool.insertItem(this.getItemStacks(), item, count);
if (rest > 0) {
var stackToDrop = new ItemStack(item, rest);
this.level().addFreshEntity(new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), stackToDrop));
}
}
@Override
public void clearItemStacks() {
this.items.clear();
}
@Override
public int getContainerSize() {
return CONTAINER_SIZE;
}
@Override
public @NotNull ItemStack getItem(int pSlot) {
return this.items.get(pSlot);
}
@Override
public @NotNull ItemStack removeItem(int pSlot, int pAmount) {
return ContainerHelper.removeItem(this.items, pSlot, pAmount);
}
@Override
public @NotNull ItemStack removeItemNoUpdate(int pSlot) {
ItemStack itemstack = this.getItemStacks().get(pSlot);
if (itemstack.isEmpty()) {
return ItemStack.EMPTY;
} else {
this.getItemStacks().set(pSlot, ItemStack.EMPTY);
return itemstack;
}
}
@Override
public void setItem(int pSlot, @NotNull ItemStack pStack) {
this.getItemStacks().set(pSlot, pStack);
if (!pStack.isEmpty() && pStack.getCount() > this.getMaxStackSize()) {
pStack.setCount(this.getMaxStackSize());
}
}
@Override
public void setChanged() {
}
@Override
public boolean stillValid(@NotNull Player pPlayer) {
return !this.isRemoved() && this.position().closerThan(pPlayer.position(), 8.0D);
}
@Override
public void clearContent() {
this.getItemStacks().clear();
}
@Nullable
@Override
public AbstractContainerMenu createMenu(int pContainerId, Inventory pPlayerInventory, Player pPlayer) {
if (pPlayer.isSpectator()) {
return null;
} else {
return new VehicleMenu(pContainerId, pPlayerInventory, this);
}
}
// TODO Capability
// @Override
// public <T> @NotNull LazyOptional<T> getCapability(@NotNull Capability<T> capability, @Nullable Direction facing) {
// if (this.isAlive() && capability == ForgeCapabilities.ITEM_HANDLER) {
// return itemHandler.cast();
// }
// return super.getCapability(capability, facing);
// }
//
// @Override
// public void invalidateCaps() {
// super.invalidateCaps();
// itemHandler.invalidate();
// }
//
// @Override
// public void reviveCaps() {
// super.reviveCaps();
// itemHandler = LazyOptional.of(() -> new InvWrapper(this));
// }
@Override
public void stopOpen(@NotNull Player pPlayer) {
this.level().gameEvent(GameEvent.CONTAINER_CLOSE, this.position(), GameEvent.Context.of(pPlayer));
}
}

View file

@ -0,0 +1,80 @@
package com.atsuishio.superbwarfare.entity.vehicle.base;
import com.atsuishio.superbwarfare.capability.energy.SyncedEntityEnergyStorage;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.IntTag;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.Level;
import net.neoforged.neoforge.energy.IEnergyStorage;
import java.util.function.Supplier;
public abstract class EnergyVehicleEntity extends VehicleEntity {
public static final EntityDataAccessor<Integer> ENERGY = SynchedEntityData.defineId(EnergyVehicleEntity.class, EntityDataSerializers.INT);
protected final SyncedEntityEnergyStorage energyStorage = new SyncedEntityEnergyStorage(this.getMaxEnergy(), this.entityData, ENERGY);
protected final Supplier<IEnergyStorage> energy = () -> energyStorage;
public EnergyVehicleEntity(EntityType<?> pEntityType, Level pLevel) {
super(pEntityType, pLevel);
this.setEnergy(0);
}
@Override
protected void defineSynchedData(SynchedEntityData.Builder builder) {
super.defineSynchedData(builder);
builder.define(ENERGY, 0);
}
@Override
protected void readAdditionalSaveData(CompoundTag compound) {
super.readAdditionalSaveData(compound);
if (compound.get("Energy") instanceof IntTag energyNBT) {
energyStorage.deserializeNBT(level().registryAccess(), energyNBT);
}
}
@Override
public void addAdditionalSaveData(CompoundTag compound) {
super.addAdditionalSaveData(compound);
compound.put("Energy", energyStorage.serializeNBT(level().registryAccess()));
}
/**
* 消耗指定电量
*
* @param amount 要消耗的电量
*/
public void consumeEnergy(int amount) {
this.energyStorage.extractEnergy(amount, false);
}
public boolean canConsume(int amount) {
return this.getEnergy() >= amount;
}
public int getEnergy() {
return this.energyStorage.getEnergyStored();
}
public void setEnergy(int pEnergy) {
int targetEnergy = Mth.clamp(pEnergy, 0, this.getMaxEnergy());
if (targetEnergy > energyStorage.getEnergyStored()) {
energyStorage.receiveEnergy(targetEnergy - energyStorage.getEnergyStored(), false);
} else {
energyStorage.extractEnergy(energyStorage.getEnergyStored() - targetEnergy, false);
}
}
public int getMaxEnergy() {
return 100000;
}
}

View file

@ -0,0 +1,14 @@
package com.atsuishio.superbwarfare.entity.vehicle.base;
public interface HelicopterEntity extends ArmedVehicleEntity {
float getRotX(float tickDelta);
float getRotY(float tickDelta);
float getRotZ(float tickDelta);
float getPower();
int getDecoy();
}

View file

@ -0,0 +1,18 @@
package com.atsuishio.superbwarfare.entity.vehicle.base;
import net.minecraft.world.phys.Vec3;
public interface LandArmorEntity extends ArmedVehicleEntity {
float turretYRotO();
float turretYRot();
float turretXRotO();
float turretXRot();
Vec3 getBarrelVec(float ticks);
Vec3 getGunVec(float ticks);
}

View file

@ -0,0 +1,630 @@
package com.atsuishio.superbwarfare.entity.vehicle.base;
import com.atsuishio.superbwarfare.config.server.VehicleConfig;
import com.atsuishio.superbwarfare.entity.TargetEntity;
import com.atsuishio.superbwarfare.init.ModDamageTypes;
import com.atsuishio.superbwarfare.init.ModSounds;
import com.atsuishio.superbwarfare.init.ModTags;
import com.atsuishio.superbwarfare.tools.EntityFindUtil;
import com.mojang.math.Axis;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.MoverType;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.vehicle.Boat;
import net.minecraft.world.entity.vehicle.Minecart;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.entity.EntityTypeTest;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
import org.joml.Math;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.joml.Vector4f;
public abstract class MobileVehicleEntity extends EnergyVehicleEntity {
public static final EntityDataAccessor<Integer> CANNON_RECOIL_TIME = SynchedEntityData.defineId(MobileVehicleEntity.class, EntityDataSerializers.INT);
public static final EntityDataAccessor<Float> POWER = SynchedEntityData.defineId(MobileVehicleEntity.class, EntityDataSerializers.FLOAT);
public static final EntityDataAccessor<Float> YAW = SynchedEntityData.defineId(MobileVehicleEntity.class, EntityDataSerializers.FLOAT);
public static final EntityDataAccessor<Integer> FIRE_ANIM = SynchedEntityData.defineId(MobileVehicleEntity.class, EntityDataSerializers.INT);
public static final EntityDataAccessor<Integer> HEAT = SynchedEntityData.defineId(MobileVehicleEntity.class, EntityDataSerializers.INT);
public static final EntityDataAccessor<Integer> COAX_HEAT = SynchedEntityData.defineId(MobileVehicleEntity.class, EntityDataSerializers.INT);
public static final EntityDataAccessor<Integer> AMMO = SynchedEntityData.defineId(MobileVehicleEntity.class, EntityDataSerializers.INT);
public boolean leftInputDown;
public boolean rightInputDown;
public boolean forwardInputDown;
public boolean backInputDown;
public boolean upInputDown;
public boolean downInputDown;
public boolean decoyInputDown;
public double lastTickSpeed;
public double lastTickVerticalSpeed;
public int collisionCoolDown;
public float rudderRot;
public float rudderRotO;
public float leftWheelRot;
public float rightWheelRot;
public float leftWheelRotO;
public float rightWheelRotO;
public float leftTrackO;
public float rightTrackO;
public float leftTrack;
public float rightTrack;
public float rotorRot;
public float rotorRotO;
public float propellerRot;
public float propellerRotO;
public double recoilShake;
public double recoilShakeO;
public boolean cannotFire;
public boolean cannotFireCoax;
public int reloadCoolDown;
public double velocityO;
public double velocity;
public MobileVehicleEntity(EntityType<?> pEntityType, Level pLevel) {
super(pEntityType, pLevel);
}
@Override
public void playerTouch(Player pPlayer) {
if (pPlayer.isCrouching() && !this.level().isClientSide) {
double entitySize = pPlayer.getBbWidth() * pPlayer.getBbHeight();
double thisSize = this.getBbWidth() * this.getBbHeight();
double f = Math.min(entitySize / thisSize, 2);
double f1 = Math.min(thisSize / entitySize, 4);
this.setDeltaMovement(this.getDeltaMovement().add(new Vec3(pPlayer.position().vectorTo(this.position()).toVector3f()).scale(0.15 * f * pPlayer.getDeltaMovement().length())));
pPlayer.setDeltaMovement(pPlayer.getDeltaMovement().add(new Vec3(this.position().vectorTo(pPlayer.position()).toVector3f()).scale(0.1 * f1 * pPlayer.getDeltaMovement().length())));
}
}
@Override
public void baseTick() {
turretYRotO = this.getTurretYRot();
turretXRotO = this.getTurretXRot();
gunYRotO = this.getGunYRot();
gunXRotO = this.getGunXRot();
leftWheelRotO = this.getLeftWheelRot();
rightWheelRotO = this.getRightWheelRot();
leftTrackO = this.getLeftTrack();
rightTrackO = this.getRightTrack();
rotorRotO = this.getRotorRot();
rudderRotO = this.getRudderRot();
propellerRotO = this.getPropellerRot();
recoilShakeO = this.getRecoilShake();
velocityO = this.getVelocity();
lastTickSpeed = new Vec3(this.getDeltaMovement().x, this.getDeltaMovement().y + 0.06, this.getDeltaMovement().z).length();
lastTickVerticalSpeed = this.getDeltaMovement().y + 0.06;
if (collisionCoolDown > 0) {
collisionCoolDown--;
}
super.baseTick();
double direct = (90 - calculateAngle(this.getDeltaMovement(), this.getViewVector(1))) / 90;
setVelocity(Mth.lerp(0.4, getVelocity(), getDeltaMovement().horizontalDistance() * direct * 20));
float deltaT = java.lang.Math.abs(getTurretYRot() - turretYRotO);
while (getTurretYRot() > 180F) {
setTurretYRot(getTurretYRot() - 360F);
turretYRotO = getTurretYRot() - deltaT;
}
while (getTurretYRot() <= -180F) {
setTurretYRot(getTurretYRot() + 360F);
turretYRotO = deltaT + getTurretYRot();
}
if (this.entityData.get(HEAT) > 0) {
this.entityData.set(HEAT, this.entityData.get(HEAT) - 1);
}
if (this.entityData.get(COAX_HEAT) > 0) {
this.entityData.set(COAX_HEAT, this.entityData.get(COAX_HEAT) - 1);
}
if (this.entityData.get(FIRE_ANIM) > 0) {
this.entityData.set(FIRE_ANIM, this.entityData.get(FIRE_ANIM) - 1);
}
if (this.entityData.get(HEAT) < 40) {
cannotFire = false;
}
if (this.entityData.get(COAX_HEAT) < 40) {
cannotFireCoax = false;
}
if (this.entityData.get(HEAT) > 100) {
cannotFire = true;
this.level().playSound(null, this.getOnPos(), ModSounds.MINIGUN_OVERHEAT.get(), SoundSource.PLAYERS, 1, 1);
}
if (this.entityData.get(COAX_HEAT) > 100) {
cannotFireCoax = true;
this.level().playSound(null, this.getOnPos(), ModSounds.MINIGUN_OVERHEAT.get(), SoundSource.PLAYERS, 1, 1);
}
if (this.entityData.get(CANNON_RECOIL_TIME) > 0) {
this.entityData.set(CANNON_RECOIL_TIME, this.entityData.get(CANNON_RECOIL_TIME) - 1);
}
this.setRecoilShake(java.lang.Math.pow(entityData.get(CANNON_RECOIL_TIME), 4) * 0.0000007 * java.lang.Math.sin(0.2 * java.lang.Math.PI * (entityData.get(CANNON_RECOIL_TIME) - 2.5)));
preventStacking();
crushEntities(this.getDeltaMovement());
// TODO drone
// if (!(this instanceof DroneEntity)) {
// this.setDeltaMovement(this.getDeltaMovement().add(0.0, -0.06, 0.0));
// }
this.move(MoverType.SELF, this.getDeltaMovement());
collideLilyPadBlock();
this.refreshDimensions();
}
// 惯性倾斜
public void inertiaRotate(float multiple) {
float angleX = 0;
float diffX = (float) (getAcceleration() * multiple - angleX);
setXRot(getXRot() - 0.5f * diffX);
}
// 地形适应测试
public void terrainCompat(float w, float l) {
Matrix4f transform = this.getWheelsTransform(1);
// 点位
//
Vector4f positionF = transformPosition(transform, 0, 0, l / 2);
//
Vector4f positionB = transformPosition(transform, 0, 0, -l / 2);
//
Vector4f positionL = transformPosition(transform, -w / 2, 0, 0);
//
Vector4f positionR = transformPosition(transform, w / 2, 0, 0);
// 左前
Vector4f positionLF = transformPosition(transform, w / 2, 0, l / 2);
// 右前
Vector4f positionRF = transformPosition(transform, -w / 2, 0, l / 2);
// 左后
Vector4f positionLB = transformPosition(transform, w / 2, 0, -l / 2);
// 右后
Vector4f positionRB = transformPosition(transform, -w / 2, 0, -l / 2);
Vec3 p1 = new Vec3(positionLF.x, positionLF.y, positionLF.z);
Vec3 p2 = new Vec3(positionRF.x, positionRF.y, positionRF.z);
Vec3 p3 = new Vec3(positionLB.x, positionLB.y, positionLB.z);
Vec3 p4 = new Vec3(positionRB.x, positionRB.y, positionRB.z);
Vec3 p5 = new Vec3(positionF.x, positionF.y, positionF.z);
Vec3 p6 = new Vec3(positionB.x, positionB.y, positionB.z);
Vec3 p7 = new Vec3(positionL.x, positionL.y, positionL.z);
Vec3 p8 = new Vec3(positionR.x, positionR.y, positionR.z);
// 确定点位是否在墙里来调整点位高度
float p1y = (float) this.traceBlockY(p1, l);
float p2y = (float) this.traceBlockY(p2, l);
float p3y = (float) this.traceBlockY(p3, l);
float p4y = (float) this.traceBlockY(p4, l);
float p5y = (float) Mth.clamp(this.traceBlockY(p5, l), -l, l);
float p6y = (float) Mth.clamp(this.traceBlockY(p6, l), -l, l);
float p7y = (float) Mth.clamp(this.traceBlockY(p7, l), -l, l);
float p8y = (float) Mth.clamp(this.traceBlockY(p8, l), -l, l);
p1 = new Vec3(positionLF.x, p1y, positionLF.z);
p2 = new Vec3(positionRF.x, p2y, positionRF.z);
p3 = new Vec3(positionLB.x, p3y, positionLB.z);
p4 = new Vec3(positionRB.x, p4y, positionRB.z);
// 测试用粒子效果用于确定点位位置
// var passenger = this.getFirstPassenger();
//
// if (passenger != null) {
// if (passenger.level() instanceof ServerLevel serverLevel) {
// sendParticle(serverLevel, ParticleTypes.END_ROD, p1.x, p1.y, p1.z, 1, 0, 0, 0, 0, true);
// sendParticle(serverLevel, ParticleTypes.END_ROD, p2.x, p2.y, p2.z, 1, 0, 0, 0, 0, true);
// sendParticle(serverLevel, ParticleTypes.END_ROD, p3.x, p3.y, p3.z, 1, 0, 0, 0, 0, true);
// sendParticle(serverLevel, ParticleTypes.END_ROD, p4.x, p4.y, p4.z, 1, 0, 0, 0, 0, true);
// }
// }
// 通过点位位置获取角度
// 左后-左前
Vec3 v0 = p3.vectorTo(p1);
// 右后-右前
Vec3 v1 = p4.vectorTo(p2);
// 左前-右前
Vec3 v2 = p1.vectorTo(p2);
// 左后-右后
Vec3 v3 = p3.vectorTo(p4);
double x1 = getXRotFromVector(v0);
double x2 = getXRotFromVector(v1);
double z1 = getXRotFromVector(v2);
double z2 = getXRotFromVector(v3);
float diffX = Math.clamp(-90f, 90f, Mth.wrapDegrees((float) (-(x1 + x2)) - this.getXRot()));
this.setXRot(Mth.clamp(this.getXRot() + 0.075f * diffX, -90f, 90f));
float diffZ = Math.clamp(-90f, 90f, Mth.wrapDegrees((float) (-(z1 + z2)) - this.getRoll()));
this.setZRot(Mth.clamp(this.getRoll() + 0.15f * diffZ, -90f, 90f));
}
public Matrix4f getWheelsTransform(float ticks) {
Matrix4f transform = new Matrix4f();
transform.translate((float) Mth.lerp(ticks, xo, getX()), (float) Mth.lerp(ticks, yo, getY()), (float) Mth.lerp(ticks, zo, getZ()));
transform.rotate(Axis.YP.rotationDegrees(-Mth.lerp(ticks, yRotO, getYRot())));
return transform;
}
public double traceBlockY(Vec3 pos, double maxLength) {
var res = this.level().clip(new ClipContext(pos, pos.add(0, -maxLength, 0),
ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this));
double targetY = 0;
if (res.getType() == HitResult.Type.BLOCK) {
targetY = res.getLocation().y;
} else if (!this.level().noCollision(new AABB(pos, pos))) {
targetY = pos.y + maxLength / 2;
} else if (res.getType() == HitResult.Type.MISS) {
targetY = pos.y - maxLength / 2;
}
double diffY = targetY - pos.y;
return pos.y + 0.5f * diffY;
}
public void collideLilyPadBlock() {
if (level() instanceof ServerLevel) {
AABB aabb = getBoundingBox().inflate(0.05).move(this.getDeltaMovement().scale(0.6));
BlockPos.betweenClosedStream(aabb).forEach((pos) -> {
BlockState blockstate = this.level().getBlockState(pos);
if (blockstate.is(Blocks.LILY_PAD)) {
this.level().destroyBlock(pos, true);
}
});
}
}
public void collideBlock() {
if (level() instanceof ServerLevel) {
if (!VehicleConfig.COLLISION_DESTROY_BLOCKS.get()) return;
AABB aabb = getBoundingBox().move(this.getDeltaMovement().scale(0.5)).inflate(0.1, -0.05, 0.1);
BlockPos.betweenClosedStream(aabb).forEach((pos) -> {
BlockState blockstate = this.level().getBlockState(pos);
if (blockstate.is(ModTags.Blocks.SOFT_COLLISION)) {
this.level().destroyBlock(pos, true);
}
});
}
}
public void collideHardBlock() {
if (level() instanceof ServerLevel) {
if (!VehicleConfig.COLLISION_DESTROY_HARD_BLOCKS.get()) return;
AABB aabb = getBoundingBox().move(this.getDeltaMovement().scale(0.5)).inflate(0.1, -0.05, 0.1);
BlockPos.betweenClosedStream(aabb).forEach((pos) -> {
BlockState blockstate = this.level().getBlockState(pos);
if (blockstate.is(ModTags.Blocks.HARD_COLLISION)) {
this.level().destroyBlock(pos, true);
this.setDeltaMovement(this.getDeltaMovement().scale(0.95));
}
});
}
}
@Override
public void move(@NotNull MoverType movementType, @NotNull Vec3 movement) {
super.move(movementType, movement);
if (level() instanceof ServerLevel) {
if (lastTickSpeed < 0.3 || collisionCoolDown > 0
// TODO drone
// || this instanceof DroneEntity
) return;
Entity driver = EntityFindUtil.findEntity(this.level(), this.entityData.get(LAST_DRIVER_UUID));
if ((verticalCollision)) {
if (this instanceof HelicopterEntity) {
this.hurt(ModDamageTypes.causeVehicleStrikeDamage(this.level().registryAccess(), this, driver == null ? this : driver), (float) (20 * ((lastTickSpeed - 0.3) * (lastTickSpeed - 0.3))));
this.bounceVertical(Direction.getNearest(this.getDeltaMovement().x(), this.getDeltaMovement().y(), this.getDeltaMovement().z()).getOpposite());
} else if (Mth.abs((float) lastTickVerticalSpeed) > 0.4) {
this.hurt(ModDamageTypes.causeVehicleStrikeDamage(this.level().registryAccess(), this, driver == null ? this : driver), (float) (96 * ((Mth.abs((float) lastTickVerticalSpeed) - 0.4) * (lastTickSpeed - 0.3) * (lastTickSpeed - 0.3))));
if (!this.level().isClientSide) {
this.level().playSound(null, this, ModSounds.VEHICLE_STRIKE.get(), this.getSoundSource(), 1, 1);
}
this.bounceVertical(Direction.getNearest(this.getDeltaMovement().x(), this.getDeltaMovement().y(), this.getDeltaMovement().z()).getOpposite());
}
}
if (this.horizontalCollision) {
this.hurt(ModDamageTypes.causeVehicleStrikeDamage(this.level().registryAccess(), this, driver == null ? this : driver), (float) (126 * ((lastTickSpeed - 0.4) * (lastTickSpeed - 0.4))));
this.bounceHorizontal(Direction.getNearest(this.getDeltaMovement().x(), this.getDeltaMovement().y(), this.getDeltaMovement().z()).getOpposite());
if (!this.level().isClientSide) {
this.level().playSound(null, this, ModSounds.VEHICLE_STRIKE.get(), this.getSoundSource(), 1, 1);
}
collisionCoolDown = 4;
crash = true;
this.entityData.set(POWER, 0.4f * entityData.get(POWER));
}
}
}
public void bounceHorizontal(Direction direction) {
collideBlock();
collideHardBlock();
switch (direction.getAxis()) {
case X:
this.setDeltaMovement(this.getDeltaMovement().multiply(-0.4, 0.99, 0.99));
break;
case Z:
this.setDeltaMovement(this.getDeltaMovement().multiply(0.99, 0.99, -0.4));
break;
}
}
public void bounceVertical(Direction direction) {
if (!this.level().isClientSide) {
this.level().playSound(null, this, ModSounds.VEHICLE_STRIKE.get(), this.getSoundSource(), 1, 1);
}
collisionCoolDown = 4;
crash = true;
if (direction.getAxis() == Direction.Axis.Y) {
this.setDeltaMovement(this.getDeltaMovement().multiply(0.9, -0.8, 0.9));
}
}
/**
* 防止载具堆叠
*/
public void preventStacking() {
var Box = getBoundingBox();
var entities = level().getEntities(EntityTypeTest.forClass(Entity.class), Box, entity -> entity != this && entity != getFirstPassenger() && entity.getVehicle() == null)
.stream().filter(entity -> entity instanceof VehicleEntity)
.toList();
for (var entity : entities) {
Vec3 toVec = this.position().add(new Vec3(1, 1, 1).scale(random.nextFloat() * 0.01f + 1f)).vectorTo(entity.position());
Vec3 velAdd = toVec.normalize().scale(Math.max((this.getBbWidth() + 2) - position().distanceTo(entity.position()), 0) * 0.002);
double entitySize = entity.getBbWidth() * entity.getBbHeight();
double thisSize = this.getBbWidth() * this.getBbHeight();
double f = Math.min(entitySize / thisSize, 2);
double f1 = Math.min(thisSize / entitySize, 2);
this.pushNew(-f * velAdd.x, -f * velAdd.y, -f * velAdd.z);
entity.push(f1 * velAdd.x, f1 * velAdd.y, f1 * velAdd.z);
}
}
public void pushNew(double pX, double pY, double pZ) {
this.setDeltaMovement(this.getDeltaMovement().add(pX, pY, pZ));
}
/**
* 撞击实体并造成伤害
*
* @param velocity 动量
*/
public void crushEntities(Vec3 velocity) {
if (level() instanceof ServerLevel) {
if (!this.canCrushEntities()) return;
if (velocity.horizontalDistance() < 0.25) return;
if (isRemoved()) return;
var frontBox = getBoundingBox().move(velocity.scale(0.6));
var velAdd = velocity.add(0, 0, 0).scale(0.9);
var entities = level().getEntities(EntityTypeTest.forClass(Entity.class), frontBox,
entity -> entity != this && entity != getFirstPassenger() && entity.getVehicle() == null)
.stream().filter(entity -> {
if (entity.isAlive()) {
var type = BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType());
return (entity instanceof VehicleEntity || entity instanceof Boat || entity instanceof Minecart
|| (entity instanceof LivingEntity living && !(living instanceof Player player && player.isSpectator())))
|| VehicleConfig.COLLISION_ENTITY_WHITELIST.get().contains(type.toString());
}
return false;
}
)
.toList();
for (var entity : entities) {
double entitySize = entity.getBbWidth() * entity.getBbHeight();
double thisSize = this.getBbWidth() * this.getBbHeight();
double f = Math.min(entitySize / thisSize, 2);
double f1 = Math.min(thisSize / entitySize, 4);
if (velocity.length() > 0.3 && getBoundingBox().distanceToSqr(entity.getBoundingBox().getCenter()) < 1) {
if (!this.level().isClientSide) {
this.level().playSound(null, this, ModSounds.VEHICLE_STRIKE.get(), this.getSoundSource(), 1, 1);
}
if (!(entity instanceof TargetEntity)) {
this.pushNew(-f * velAdd.x, -f * velAdd.y, -f * velAdd.z);
}
if (entity instanceof MobileVehicleEntity mobileVehicle) {
mobileVehicle.pushNew(f1 * velAdd.x, f1 * velAdd.y, f1 * velAdd.z);
} else {
entity.push(f1 * velAdd.x, f1 * velAdd.y, f1 * velAdd.z);
}
entity.hurt(ModDamageTypes.causeVehicleStrikeDamage(this.level().registryAccess(), this, this.getFirstPassenger() == null ? this : this.getFirstPassenger()), (float) (thisSize * 20 * ((velocity.length() - 0.3) * (velocity.length() - 0.3))));
if (entities instanceof VehicleEntity) {
this.hurt(ModDamageTypes.causeVehicleStrikeDamage(this.level().registryAccess(), entity, entity.getFirstPassenger() == null ? entity : entity.getFirstPassenger()), (float) (entitySize * 10 * ((velocity.length() - 0.3) * (velocity.length() - 0.3))));
}
} else {
entity.push(0.3 * f1 * velAdd.x, 0.3 * f1 * velAdd.y, 0.3 * f1 * velAdd.z);
}
}
}
}
public Vector3f getForwardDirection() {
return new Vector3f(
Mth.sin(-getYRot() * ((float) Math.PI / 180)),
0.0f,
Mth.cos(getYRot() * ((float) Math.PI / 180))
).normalize();
}
public Vector3f getRightDirection() {
return new Vector3f(
Mth.cos(-getYRot() * ((float) Math.PI / 180)),
0.0f,
Mth.sin(getYRot() * ((float) Math.PI / 180))
).normalize();
}
public SoundEvent getEngineSound() {
return SoundEvents.EMPTY;
}
public double getVelocity() {
return this.velocity;
}
public void setVelocity(double pV) {
this.velocity = pV;
}
public double getAcceleration() {
return getVelocity() - velocityO;
}
public float getRudderRot() {
return this.rudderRot;
}
public void setRudderRot(float pRudderRot) {
this.rudderRot = pRudderRot;
}
public float getLeftWheelRot() {
return this.leftWheelRot;
}
public void setLeftWheelRot(float pLeftWheelRot) {
this.leftWheelRot = pLeftWheelRot;
}
public float getRightWheelRot() {
return this.rightWheelRot;
}
public void setRightWheelRot(float pRightWheelRot) {
this.rightWheelRot = pRightWheelRot;
}
public float getLeftTrack() {
return this.leftTrack;
}
public void setLeftTrack(float pLeftTrack) {
this.leftTrack = pLeftTrack;
}
public float getRightTrack() {
return this.rightTrack;
}
public void setRightTrack(float pRightTrack) {
this.rightTrack = pRightTrack;
}
public float getRotorRot() {
return this.rotorRot;
}
public void setRotorRot(float pRotorRot) {
this.rotorRot = pRotorRot;
}
public float getPropellerRot() {
return this.propellerRot;
}
public void setPropellerRot(float pPropellerRot) {
this.propellerRot = pPropellerRot;
}
public double getRecoilShake() {
return this.recoilShake;
}
public void setRecoilShake(double pRecoilShake) {
this.recoilShake = pRecoilShake;
}
@Override
protected void defineSynchedData(SynchedEntityData.Builder builder) {
super.defineSynchedData(builder);
builder.define(CANNON_RECOIL_TIME, 0);
builder.define(POWER, 0f);
builder.define(YAW, 0f);
builder.define(AMMO, 0);
builder.define(FIRE_ANIM, 0);
builder.define(HEAT, 0);
builder.define(COAX_HEAT, 0);
}
@Override
protected void readAdditionalSaveData(CompoundTag compound) {
super.readAdditionalSaveData(compound);
this.entityData.set(POWER, compound.getFloat("Power"));
}
@Override
public void addAdditionalSaveData(CompoundTag compound) {
super.addAdditionalSaveData(compound);
compound.putFloat("Power", this.entityData.get(POWER));
}
public boolean canCrushEntities() {
return true;
}
}

View file

@ -0,0 +1,4 @@
package com.atsuishio.superbwarfare.entity.vehicle.base;
public record ThirdPersonCameraPosition(double distance, double y, double z) {
}

View file

@ -0,0 +1,930 @@
package com.atsuishio.superbwarfare.entity.vehicle.base;
import com.atsuishio.superbwarfare.ModUtils;
import com.atsuishio.superbwarfare.config.server.VehicleConfig;
import com.atsuishio.superbwarfare.entity.vehicle.damage.DamageModifier;
import com.atsuishio.superbwarfare.entity.vehicle.weapon.VehicleWeapon;
import com.atsuishio.superbwarfare.init.*;
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.ImmutableList;
import com.google.common.collect.Lists;
import com.mojang.math.Axis;
import it.unimi.dsi.fastutil.ints.IntList;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.protocol.game.ClientboundSetPassengersPacket;
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.AbstractArrow;
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.level.gameevent.GameEvent;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.phys.Vec3;
import net.neoforged.neoforge.common.util.FakePlayer;
import net.neoforged.neoforge.network.PacketDistributor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.joml.Math;
import org.joml.Matrix4f;
import org.joml.Vector4f;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import static com.atsuishio.superbwarfare.tools.ParticleTool.sendParticle;
public abstract class VehicleEntity extends Entity {
public static final EntityDataAccessor<Float> HEALTH = SynchedEntityData.defineId(VehicleEntity.class, EntityDataSerializers.FLOAT);
public static final EntityDataAccessor<String> LAST_ATTACKER_UUID = SynchedEntityData.defineId(VehicleEntity.class, EntityDataSerializers.STRING);
public static final EntityDataAccessor<String> LAST_DRIVER_UUID = SynchedEntityData.defineId(VehicleEntity.class, EntityDataSerializers.STRING);
public static final EntityDataAccessor<Float> DELTA_ROT = SynchedEntityData.defineId(VehicleEntity.class, EntityDataSerializers.FLOAT);
public static final EntityDataAccessor<List<Integer>> SELECTED_WEAPON = SynchedEntityData.defineId(VehicleEntity.class, ModSerializers.INT_LIST_SERIALIZER.get());
public VehicleWeapon[][] availableWeapons;
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 turretYRot;
public float turretXRot;
public float turretYRotO;
public float turretXRotO;
public float turretYRotLock;
public float gunYRot;
public float gunXRot;
public float gunYRotO;
public float gunXRotO;
// 自定义骑乘
private final List<Entity> orderedPassengers = generatePassengersList();
private ArrayList<Entity> generatePassengersList() {
var list = new ArrayList<Entity>(this.getMaxPassengers());
for (int i = 0; i < this.getMaxPassengers(); i++) {
list.add(null);
}
return list;
}
/**
* 获取按顺序排列的成员列表
*
* @return 按顺序排列的成员列表
*/
public List<Entity> getOrderedPassengers() {
return orderedPassengers;
}
// 仅在客户端存在的实体顺序获取用于在客户端正确同步实体座位顺序
public Function<Entity, Integer> entityIndexOverride = null;
@Override
protected void addPassenger(@NotNull Entity newPassenger) {
if (newPassenger.getVehicle() != this) {
throw new IllegalStateException("Use x.startRiding(y), not y.addPassenger(x)");
}
int index;
if (entityIndexOverride != null && entityIndexOverride.apply(newPassenger) != -1) {
index = entityIndexOverride.apply(newPassenger);
} else {
index = 0;
for (Entity passenger : orderedPassengers) {
if (passenger == null) {
break;
}
index++;
}
}
if (index >= getMaxPassengers() || index < 0) return;
orderedPassengers.set(index, newPassenger);
this.passengers = ImmutableList.copyOf(orderedPassengers.stream().filter(Objects::nonNull).toList());
this.gameEvent(GameEvent.ENTITY_MOUNT, newPassenger);
}
@Override
protected void removePassenger(@NotNull Entity pPassenger) {
if (pPassenger.getVehicle() == this) {
throw new IllegalStateException("Use x.stopRiding(y), not y.removePassenger(x)");
}
var index = getSeatIndex(pPassenger);
if (index == -1) return;
orderedPassengers.set(index, null);
this.passengers = ImmutableList.copyOf(orderedPassengers.stream().filter(Objects::nonNull).toList());
pPassenger.boardingCooldown = 60;
this.gameEvent(GameEvent.ENTITY_DISMOUNT, pPassenger);
}
@Nullable
@Override
public Entity getFirstPassenger() {
return orderedPassengers.get(0);
}
/**
* 获取第index个乘客
*
* @param index 目标座位
* @return 目标座位的乘客
*/
public Entity getNthEntity(int index) {
return orderedPassengers.get(index);
}
/**
* 尝试切换座位
*
* @param entity 乘客
* @param index 目标座位
* @return 是否切换成功
*/
public boolean changeSeat(Entity entity, int index) {
if (index < 0 || index >= getMaxPassengers()) return false;
if (orderedPassengers.get(index) != null) return false;
if (!orderedPassengers.contains(entity)) return false;
orderedPassengers.set(orderedPassengers.indexOf(entity), null);
orderedPassengers.set(index, entity);
// 在服务端运行时向所有玩家同步载具座位信息
if (!this.level().isClientSide && this.level() instanceof ServerLevel serverLevel) {
serverLevel.getPlayers(s -> true).forEach(p -> p.connection.send(new ClientboundSetPassengersPacket(this)));
}
return true;
}
/**
* 获取乘客所在座位索引
*
* @param entity 乘客
* @return 座位索引
*/
public int getSeatIndex(Entity entity) {
return orderedPassengers.indexOf(entity);
}
/**
* 第三人称视角相机位置重载返回null表示不进行修改
*
* @param seatIndex 座位索引
*/
@Nullable
public ThirdPersonCameraPosition getThirdPersonCameraPosition(int seatIndex) {
return null;
}
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 void turretTurnSound(float diffX, float diffY, float pitch) {
if (level().isClientSide && (java.lang.Math.abs(diffY) > 0.5 || java.lang.Math.abs(diffX) > 0.5)) {
level().playLocalSound(this.getX(), this.getY() + this.getBbHeight() * 0.5, this.getZ(), ModSounds.TURRET_TURN.get(), this.getSoundSource(), (float) java.lang.Math.min(0.15 * (java.lang.Math.max(Mth.abs(diffX), Mth.abs(diffY))), 0.75), (random.nextFloat() * 0.05f + pitch), false);
}
}
public VehicleEntity(EntityType<?> pEntityType, Level pLevel) {
super(pEntityType, pLevel);
this.setHealth(this.getMaxHealth());
}
@Override
protected void defineSynchedData(SynchedEntityData.Builder builder) {
builder.define(HEALTH, this.getMaxHealth())
.define(LAST_ATTACKER_UUID, "undefined")
.define(LAST_DRIVER_UUID, "undefined")
.define(DELTA_ROT, 0f);
if (this instanceof WeaponVehicleEntity weaponVehicle && weaponVehicle.getAllWeapons().length > 0) {
builder.define(SELECTED_WEAPON, IntList.of(initSelectedWeaponArray(weaponVehicle)));
}
}
private int[] initSelectedWeaponArray(WeaponVehicleEntity weaponVehicle) {
// 初始化武器数组
weaponVehicle.getAllWeapons();
var selected = new int[this.getMaxPassengers()];
for (int i = 0; i < this.getMaxPassengers(); i++) {
selected[i] = weaponVehicle.hasWeapon(i) ? 0 : -1;
}
return selected;
}
@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"));
if (this instanceof WeaponVehicleEntity weaponVehicle && weaponVehicle.getAllWeapons().length > 0) {
var selected = compound.getIntArray("SelectedWeapon");
if (selected.length != this.getMaxPassengers()) {
// 数量不符时可能是更新或遇到损坏数据重新初始化已选择武器
this.entityData.set(SELECTED_WEAPON, IntList.of(initSelectedWeaponArray(weaponVehicle)));
} else {
this.entityData.set(SELECTED_WEAPON, IntList.of(selected));
}
}
}
@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));
if (this instanceof WeaponVehicleEntity weaponVehicle && weaponVehicle.getAllWeapons().length > 0) {
compound.putIntArray("SelectedWeapon", this.entityData.get(SELECTED_WEAPON));
}
}
@Override
public @NotNull InteractionResult interact(Player player, @NotNull InteractionHand hand) {
if (player.getVehicle() == this) return InteractionResult.PASS;
ItemStack stack = player.getMainHandItem();
if (player.isShiftKeyDown() && stack.is(ModItems.CROWBAR.get()) && this.getPassengers().isEmpty()) {
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) {
if (player instanceof FakePlayer) return InteractionResult.PASS;
setDriverAngle(player);
player.setSprinting(false);
return player.startRiding(this) ? InteractionResult.CONSUME : InteractionResult.PASS;
} else if (!(this.getFirstPassenger() instanceof Player)) {
if (player instanceof FakePlayer) return InteractionResult.PASS;
this.getFirstPassenger().stopRiding();
setDriverAngle(player);
player.setSprinting(false);
return player.startRiding(this) ? InteractionResult.CONSUME : InteractionResult.PASS;
}
if (this.canAddPassenger(player)) {
if (player instanceof FakePlayer) return InteractionResult.PASS;
player.setSprinting(false);
return player.startRiding(this) ? InteractionResult.CONSUME : InteractionResult.PASS;
}
}
return InteractionResult.PASS;
}
// 将有炮塔的载具驾驶员设置为炮塔角度
public void setDriverAngle(Player player) {
if (this instanceof LandArmorEntity landArmorEntity) {
player.xRotO = -(float) getXRotFromVector(landArmorEntity.getBarrelVec(1));
player.setXRot(-(float) getXRotFromVector(landArmorEntity.getBarrelVec(1)));
player.yRotO = -(float) getYRotFromVector(landArmorEntity.getBarrelVec(1));
player.setYRot(-(float) getYRotFromVector(landArmorEntity.getBarrelVec(1)));
player.setYHeadRot(-(float) getYRotFromVector(landArmorEntity.getBarrelVec(1)));
} else {
player.xRotO = this.getXRot();
player.setXRot(this.getXRot());
player.yRotO = this.getYRot();
player.setYRot(this.getYRot());
}
}
public static double getYRotFromVector(Vec3 vec3) {
return Mth.atan2(vec3.x, vec3.z) * (180F / Math.PI);
}
public static double getXRotFromVector(Vec3 vec3) {
double d0 = vec3.horizontalDistance();
return Mth.atan2(vec3.y, d0) * (180F / Math.PI);
}
@Override
public boolean hurt(@NotNull DamageSource source, float amount) {
if (source.is(DamageTypes.CACTUS)) return false;
// 计算减伤后的伤害
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.DROWN)
.immuneTo(DamageTypes.DRAGON_BREATH)
.immuneTo(DamageTypes.WITHER)
.immuneTo(DamageTypes.WITHER_SKULL)
.reduce(5, ModDamageTypes.VEHICLE_STRIKE);
}
public float getSourceAngle(DamageSource source, float multiply) {
Entity attacker = source.getDirectEntity();
if (attacker == null) {
attacker = source.getEntity();
}
if (attacker != null) {
float angle = (float) java.lang.Math.abs(VectorTool.calculateAngle(this.position().vectorTo(attacker.position()), this.getViewVector(1)));
return java.lang.Math.max(1f + multiply * ((angle - 90) / 90), 0.5f);
}
return 1;
}
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
// TODO Drone
// && 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()));
PacketDistributor.sendToPlayer(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(@NotNull Entity pPassenger) {
return this.getPassengers().size() < this.getMaxPassengers();
}
public int getMaxPassengers() {
return 1;
}
public double getSubmergedHeight(Entity entity) {
for (Fluid fluid : BuiltInRegistries.FLUID) {
var type = fluid.getFluidType();
if (entity.level().getFluidState(entity.blockPosition()).getFluidType() == type)
return entity.getFluidTypeHeight(type);
}
return 0;
}
/**
* 呼吸回血冷却时长(单位:tick)设为小于0的值以禁用呼吸回血
*/
public int maxRepairCoolDown() {
return VehicleConfig.REPAIR_COOLDOWN.get();
}
/**
* 呼吸回血回血量
*/
public float repairAmount() {
return VehicleConfig.REPAIR_AMOUNT.get().floatValue();
}
@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());
}
clearArrow();
this.refreshDimensions();
}
public void clearArrow() {
List<Entity> list = this.level().getEntities(this, this.getBoundingBox().inflate(0F, 0.5F, 0F));
if (!list.isEmpty()) {
for (Entity entity : list) {
if (entity instanceof AbstractArrow) {
entity.discard();
}
}
}
}
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.level() instanceof ServerLevel serverLevel) {
if (this.getHealth() <= 0.25 * this.getMaxHealth()) {
playLowHealthParticle(serverLevel);
}
if (this.getHealth() <= 0.15 * this.getMaxHealth()) {
playLowHealthParticle(serverLevel);
}
}
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);
}
}
private void playLowHealthParticle(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);
}
public void turretAngle(float ySpeed, float xSpeed) {
Entity driver = this.getFirstPassenger();
if (driver != null) {
float turretAngle = -Mth.wrapDegrees(driver.getYHeadRot() - this.getYRot());
float diffY = Mth.wrapDegrees(turretAngle - getTurretYRot());
float diffX = Mth.wrapDegrees(driver.getXRot() - this.getTurretXRot());
this.turretTurnSound(diffX, diffY, 0.95f);
float min = -ySpeed + (float) (isInWater() && !onGround() ? 2.5 : 6) * entityData.get(DELTA_ROT);
float max = ySpeed + (float) (isInWater() && !onGround() ? 2.5 : 6) * entityData.get(DELTA_ROT);
this.setTurretXRot(this.getTurretXRot() + Mth.clamp(0.95f * diffX, -xSpeed, xSpeed));
this.setTurretYRot(this.getTurretYRot() + Mth.clamp(0.9f * diffY, min, max));
turretYRotLock = Mth.clamp(0.9f * diffY, min, max);
} else {
turretYRotLock = 0;
}
}
public void gunnerAngle(float ySpeed, float xSpeed) {
Entity gunner = this.getNthEntity(1);
float diffY = 0;
float diffX = 0;
float speed = 1;
if (gunner instanceof Player) {
float gunAngle = -Mth.wrapDegrees(gunner.getYHeadRot() - this.getYRot());
diffY = Mth.wrapDegrees(gunAngle - getGunYRot());
diffX = Mth.wrapDegrees(gunner.getXRot() - this.getGunXRot());
turretTurnSound(diffX, diffY, 0.95f);
speed = 0;
}
this.setGunXRot(this.getGunXRot() + Mth.clamp(0.95f * diffX, -xSpeed, xSpeed));
this.setGunYRot(this.getGunYRot() + Mth.clamp(0.9f * diffY, -ySpeed, ySpeed) + speed * turretYRotLock);
}
public void destroy() {
}
protected Entity getAttacker() {
return EntityFindUtil.findEntity(this.level(), this.entityData.get(LAST_ATTACKER_UUID));
}
protected void crashPassengers() {
for (var entity : this.getPassengers()) {
if (entity instanceof LivingEntity living) {
var tempAttacker = living == getAttacker() ? null : getAttacker();
living.hurt(ModDamageTypes.causeAirCrashDamage(this.level().registryAccess(), null, tempAttacker), Integer.MAX_VALUE);
living.invulnerableTime = 0;
living.hurt(ModDamageTypes.causeAirCrashDamage(this.level().registryAccess(), null, tempAttacker), Integer.MAX_VALUE);
living.invulnerableTime = 0;
living.hurt(ModDamageTypes.causeAirCrashDamage(this.level().registryAccess(), null, tempAttacker), Integer.MAX_VALUE);
living.invulnerableTime = 0;
living.hurt(ModDamageTypes.causeAirCrashDamage(this.level().registryAccess(), null, tempAttacker), Integer.MAX_VALUE);
living.invulnerableTime = 0;
living.hurt(ModDamageTypes.causeAirCrashDamage(this.level().registryAccess(), null, tempAttacker), Integer.MAX_VALUE);
}
}
}
protected void explodePassengers() {
for (var entity : this.getPassengers()) {
if (entity instanceof LivingEntity living) {
var tempAttacker = living == getAttacker() ? null : getAttacker();
living.hurt(ModDamageTypes.causeVehicleExplosionDamage(this.level().registryAccess(), null, tempAttacker), Integer.MAX_VALUE);
living.invulnerableTime = 0;
living.hurt(ModDamageTypes.causeVehicleExplosionDamage(this.level().registryAccess(), null, tempAttacker), Integer.MAX_VALUE);
living.invulnerableTime = 0;
living.hurt(ModDamageTypes.causeVehicleExplosionDamage(this.level().registryAccess(), null, tempAttacker), Integer.MAX_VALUE);
living.invulnerableTime = 0;
living.hurt(ModDamageTypes.causeVehicleExplosionDamage(this.level().registryAccess(), null, tempAttacker), Integer.MAX_VALUE);
living.invulnerableTime = 0;
living.hurt(ModDamageTypes.causeVehicleExplosionDamage(this.level().registryAccess(), null, tempAttacker), Integer.MAX_VALUE);
}
}
}
public void travel() {
}
// From Immersive_Aircraft
public Matrix4f getVehicleTransform(float ticks) {
Matrix4f transform = new Matrix4f();
transform.translate((float) Mth.lerp(ticks, xo, getX()), (float) Mth.lerp(ticks, yo, getY()), (float) Mth.lerp(ticks, zo, getZ()));
transform.rotate(Axis.YP.rotationDegrees(-Mth.lerp(ticks, yRotO, getYRot())));
transform.rotate(Axis.XP.rotationDegrees(Mth.lerp(ticks, xRotO, getXRot())));
transform.rotate(Axis.ZP.rotationDegrees(Mth.lerp(ticks, prevRoll, 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 yRot, float xRot, int steps) {
this.x = x;
this.y = y;
this.z = z;
serverYRot = yRot;
serverXRot = xRot;
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) / 1.75;
float yaw = getYRot() + 90.0f;
float x = -Mth.sin(yaw * ((float) Math.PI / 180));
float z = Mth.cos(yaw * ((float) Math.PI / 180));
float n = Math.max(Math.abs(x), Math.abs(z));
return new Vec3((double) x * offset / (double) n, 0.0, (double) z * offset / (double) n);
}
@Override
public @NotNull 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) {
}
public Vec3 getBarrelVector(float pPartialTicks) {
return this.calculateViewVector(this.getBarrelXRot(pPartialTicks), this.getBarrelYRot(pPartialTicks));
}
public float getBarrelXRot(float pPartialTicks) {
return Mth.lerp(pPartialTicks, turretXRotO - this.xRotO, getTurretXRot() - this.getXRot());
}
public float getBarrelYRot(float pPartialTick) {
return -Mth.lerp(pPartialTick, turretYRotO - this.yRotO, getTurretYRot() - this.getYRot());
}
public Vec3 getGunVector(float pPartialTicks) {
return this.calculateViewVector(this.getGunXRot(pPartialTicks), this.getGunYRot(pPartialTicks));
}
public float getGunXRot(float pPartialTicks) {
return Mth.lerp(pPartialTicks, gunXRotO - this.xRotO, getGunXRot() - this.getXRot());
}
public float getGunYRot(float pPartialTick) {
return -Mth.lerp(pPartialTick, gunYRotO - this.yRotO, getGunYRot() - this.getYRot());
}
public float turretYRotO() {
return turretYRotO;
}
public float turretYRot() {
return turretYRot;
}
public float turretXRotO() {
return turretXRotO;
}
public float turretXRot() {
return turretXRot;
}
public Vec3 getBarrelVec(float ticks) {
return getBarrelVector(ticks);
}
public Vec3 getGunVec(float ticks) {
return getGunVector(ticks);
}
public float getTurretYRot() {
return this.turretYRot;
}
public float getTurretYaw(float pPartialTick) {
return Mth.lerp(pPartialTick, turretYRotO, getTurretYRot());
}
public void setTurretYRot(float pTurretYRot) {
this.turretYRot = pTurretYRot;
}
public float getTurretXRot() {
return this.turretXRot;
}
public void setTurretXRot(float pTurretXRot) {
this.turretXRot = pTurretXRot;
}
public float getTurretPitch(float pPartialTick) {
return Mth.lerp(pPartialTick, turretXRotO, getTurretXRot());
}
public float getGunYRot() {
return this.gunYRot;
}
public void setGunYRot(float pGunYRot) {
this.gunYRot = pGunYRot;
}
public float getGunXRot() {
return this.gunXRot;
}
public void setGunXRot(float pGunXRot) {
this.gunXRot = pGunXRot;
}
}

View file

@ -0,0 +1,146 @@
package com.atsuishio.superbwarfare.entity.vehicle.base;
import com.atsuishio.superbwarfare.entity.vehicle.weapon.VehicleWeapon;
import java.util.List;
/**
* 拥有任意武器的载具
*/
public interface WeaponVehicleEntity extends ArmedVehicleEntity {
/**
* 检测该槽位是否有可用武器
*
* @param index 武器槽位
* @return 武器是否可用
*/
default boolean hasWeapon(int index) {
if (!(this instanceof VehicleEntity vehicle)) return false;
if (index < 0 || index >= vehicle.getMaxPassengers()) return false;
var weapons = getAvailableWeapons(index);
return !weapons.isEmpty();
}
/**
* 切换武器事件
*
* @param index 武器槽位
* @param value 数值可能为-1~1之间的滚动或绝对数值
* @param isScroll 是否是滚动事件
*/
default void changeWeapon(int index, int value, boolean isScroll) {
if (!(this instanceof VehicleEntity vehicle)) return;
if (index < 0 || index >= vehicle.getMaxPassengers()) return;
var weapons = getAvailableWeapons(index);
if (weapons.isEmpty()) return;
var count = weapons.size();
var typeIndex = isScroll ? (value + getWeaponIndex(index) + count) % count : value;
var weapon = weapons.get(typeIndex);
// 修改该槽位选择的武器
setWeaponIndex(index, typeIndex);
// 播放武器切换音效
var sound = weapon.sound;
if (sound != null) {
vehicle.level().playSound(null, vehicle, sound, vehicle.getSoundSource(), 1, 1);
}
}
/**
* 获取所有可用武器列表
*/
default VehicleWeapon[][] getAllWeapons() {
if (!(this instanceof VehicleEntity vehicle)) return new VehicleWeapon[][]{};
if (vehicle.availableWeapons == null) {
vehicle.availableWeapons = new VehicleWeapon[vehicle.getMaxPassengers()][];
var weapons = this.initWeapons();
for (int i = 0; i < weapons.length && i < vehicle.getMaxPassengers(); i++) {
vehicle.availableWeapons[i] = weapons[i];
}
}
return vehicle.availableWeapons;
}
/**
* 初始化所有可用武器列表
*/
VehicleWeapon[][] initWeapons();
/**
* 获取该槽位可用的武器列表
*
* @param index 武器槽位
*/
default List<VehicleWeapon> getAvailableWeapons(int index) {
if (!(this instanceof VehicleEntity vehicle)) return List.of();
if (index < 0 || index >= vehicle.getMaxPassengers()) return List.of();
if (vehicle.availableWeapons[index] != null) {
return List.of(vehicle.availableWeapons[index]);
}
return List.of();
}
default VehicleWeapon[][] initAvailableWeapons() {
if (!(this instanceof VehicleEntity vehicle)) return null;
if (vehicle.availableWeapons == null) {
vehicle.availableWeapons = new VehicleWeapon[vehicle.getMaxPassengers()][];
}
return vehicle.availableWeapons;
}
/**
* 获取该槽位当前的武器
*
* @param index 武器槽位
*/
default VehicleWeapon getWeapon(int index) {
if (!(this instanceof VehicleEntity vehicle)) return null;
if (index < 0 || index >= vehicle.getMaxPassengers()) return null;
var weapons = getAvailableWeapons(index);
if (weapons.isEmpty()) return null;
var type = getWeaponIndex(index);
if (type < 0 || type >= weapons.size()) return null;
return weapons.get(type);
}
/**
* 获取该槽位当前的武器编号返回-1则表示该位置没有可用武器
*
* @param index 武器槽位
* @return 武器类型
*/
default int getWeaponIndex(int index) {
if (!(this instanceof VehicleEntity vehicle)) return -1;
var selectedWeapons = vehicle.getEntityData().get(VehicleEntity.SELECTED_WEAPON);
if (selectedWeapons.size() <= index) return -1;
return selectedWeapons.get(index);
}
/**
* 设置该槽位当前的武器编号
*
* @param index 武器槽位
* @param type 武器类型
*/
default void setWeaponIndex(int index, int type) {
if (!(this instanceof VehicleEntity vehicle)) return;
var selectedWeapons = vehicle.getEntityData().get(VehicleEntity.SELECTED_WEAPON);
selectedWeapons.set(index, type);
vehicle.getEntityData().set(VehicleEntity.SELECTED_WEAPON, selectedWeapons);
}
}

View file

@ -0,0 +1,186 @@
package com.atsuishio.superbwarfare.entity.vehicle.damage;
import net.minecraft.resources.ResourceKey;
import net.minecraft.tags.TagKey;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.damagesource.DamageType;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
public class DamageModifier {
private final List<DamageModify> immuneList = new ArrayList<>();
private final List<DamageModify> reduceList = new ArrayList<>();
private final List<DamageModify> multiplyList = new ArrayList<>();
private final List<BiFunction<DamageSource, Float, Float>> customList = new ArrayList<>();
/**
* 免疫所有伤害
*/
public DamageModifier immuneTo() {
immuneList.add(new DamageModify(DamageModify.ModifyType.IMMUNITY, 0));
return this;
}
/**
* 免疫指定类型的伤害
*
* @param sourceTagKey 伤害类型
*/
public DamageModifier immuneTo(TagKey<DamageType> sourceTagKey) {
immuneList.add(new DamageModify(DamageModify.ModifyType.IMMUNITY, 0, sourceTagKey));
return this;
}
/**
* 免疫指定类型的伤害
*
* @param sourceKey 伤害类型
*/
public DamageModifier immuneTo(ResourceKey<DamageType> sourceKey) {
immuneList.add(new DamageModify(DamageModify.ModifyType.IMMUNITY, 0, sourceKey));
return this;
}
/**
* 免疫指定类型的伤害
*
* @param condition 伤害来源判定条件
*/
public DamageModifier immuneTo(Function<DamageSource, Boolean> condition) {
immuneList.add(new DamageModify(DamageModify.ModifyType.IMMUNITY, 0, condition));
return this;
}
/**
* 固定减少所有伤害一定数值
*
* @param value 要减少的数值
*/
public DamageModifier reduce(float value) {
reduceList.add(new DamageModify(DamageModify.ModifyType.REDUCE, value));
return this;
}
/**
* 固定减少指定类型的伤害一定数值
*
* @param value 要减少的数值
* @param sourceTagKey 伤害类型
*/
public DamageModifier reduce(float value, TagKey<DamageType> sourceTagKey) {
reduceList.add(new DamageModify(DamageModify.ModifyType.REDUCE, value, sourceTagKey));
return this;
}
/**
* 固定减少指定类型的伤害一定数值
*
* @param value 要减少的数值
* @param sourceKey 伤害类型
*/
public DamageModifier reduce(float value, ResourceKey<DamageType> sourceKey) {
reduceList.add(new DamageModify(DamageModify.ModifyType.REDUCE, value, sourceKey));
return this;
}
/**
* 固定减少指定类型的伤害一定数值
*
* @param value 要减少的数值
* @param condition 伤害来源判定条件
*/
public DamageModifier reduce(float value, Function<DamageSource, Boolean> condition) {
reduceList.add(new DamageModify(DamageModify.ModifyType.REDUCE, value, condition));
return this;
}
/**
* 将所有类型的伤害值乘以指定数值
*
* @param value 要乘以的数值
*/
public DamageModifier multiply(float value) {
multiplyList.add(new DamageModify(DamageModify.ModifyType.MULTIPLY, value));
return this;
}
/**
* 将指定类型的伤害值乘以指定数值
*
* @param value 要乘以的数值
* @param sourceTagKey 伤害类型
*/
public DamageModifier multiply(float value, TagKey<DamageType> sourceTagKey) {
multiplyList.add(new DamageModify(DamageModify.ModifyType.MULTIPLY, value, sourceTagKey));
return this;
}
/**
* 将指定类型的伤害值乘以指定数值
*
* @param value 要乘以的数值
* @param sourceKey 伤害类型
*/
public DamageModifier multiply(float value, ResourceKey<DamageType> sourceKey) {
multiplyList.add(new DamageModify(DamageModify.ModifyType.MULTIPLY, value, sourceKey));
return this;
}
/**
* 将指定类型的伤害值乘以指定数值
*
* @param value 要乘以的数值
* @param condition 伤害来源判定条件
*/
public DamageModifier multiply(float value, Function<DamageSource, Boolean> condition) {
multiplyList.add(new DamageModify(DamageModify.ModifyType.MULTIPLY, value, condition));
return this;
}
/**
* 自定义伤害值计算
*
* @param damageModifyFunction 自定义伤害值计算函数
*/
public DamageModifier custom(BiFunction<DamageSource, Float, Float> damageModifyFunction) {
customList.add(damageModifyFunction);
return this;
}
private final List<DamageModify> combinedList = new ArrayList<>();
/**
* 计算减伤后的伤害值
*
* @param source 伤害来源
* @param damage 原伤害值
* @return 减伤后的伤害值
*/
public float compute(DamageSource source, float damage) {
if (combinedList.isEmpty()) {
// 计算优先级 免疫 > 固定减伤 >
combinedList.addAll(immuneList);
combinedList.addAll(reduceList);
combinedList.addAll(multiplyList);
}
for (DamageModify damageModify : combinedList) {
if (damageModify.match(source)) {
damage = damageModify.compute(damage);
if (damage <= 0) return 0;
}
}
// 最后计算自定义伤害
for (var func : customList) {
damage = func.apply(source, damage);
}
return damage;
}
}

View file

@ -0,0 +1,78 @@
package com.atsuishio.superbwarfare.entity.vehicle.damage;
import net.minecraft.resources.ResourceKey;
import net.minecraft.tags.TagKey;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.damagesource.DamageType;
import java.util.function.Function;
public class DamageModify {
public enum ModifyType {
IMMUNITY, // 完全免疫
REDUCE, // 固定数值减伤
MULTIPLY, // 乘以指定倍数
}
private final float value;
private final ModifyType type;
private TagKey<DamageType> sourceTagKey = null;
private ResourceKey<DamageType> sourceKey = null;
private Function<DamageSource, Boolean> condition = null;
public DamageModify(ModifyType type, float value) {
this.type = type;
this.value = value;
}
public DamageModify(ModifyType type, float value, TagKey<DamageType> sourceTagKey) {
this.type = type;
this.value = value;
this.sourceTagKey = sourceTagKey;
}
public DamageModify(ModifyType type, float value, ResourceKey<DamageType> sourceKey) {
this.type = type;
this.value = value;
this.sourceKey = sourceKey;
}
public DamageModify(ModifyType type, float value, Function<DamageSource, Boolean> condition) {
this.type = type;
this.value = value;
this.condition = condition;
}
/**
* 判断指定伤害来源是否符合指定条件若未指定条件则默认符合
*
* @param source 伤害来源
* @return 伤害来源是否符合条件
*/
public boolean match(DamageSource source) {
if (condition != null) {
return condition.apply(source);
} else if (sourceTagKey != null) {
return source.is(sourceTagKey);
} else if (sourceKey != null) {
return source.is(sourceKey);
}
return true;
}
/**
* 计算减伤后的伤害值
*
* @param damage 原伤害值
* @return 计算后的伤害值
*/
public float compute(float damage) {
return switch (type) {
case IMMUNITY -> 0;
case REDUCE -> Math.max(damage - value, 0);
case MULTIPLY -> damage * value;
};
}
}

View file

@ -0,0 +1,53 @@
package com.atsuishio.superbwarfare.entity.vehicle.weapon;
public class CannonShellWeapon extends VehicleWeapon {
public float hitDamage, explosionRadius, explosionDamage, fireProbability, velocity;
public int fireTime, durability;
public CannonShellWeapon hitDamage(float hitDamage) {
this.hitDamage = hitDamage;
return this;
}
public CannonShellWeapon explosionRadius(float explosionRadius) {
this.explosionRadius = explosionRadius;
return this;
}
public CannonShellWeapon explosionDamage(float explosionDamage) {
this.explosionDamage = explosionDamage;
return this;
}
public CannonShellWeapon fireProbability(float fireProbability) {
this.fireProbability = fireProbability;
return this;
}
public CannonShellWeapon velocity(float velocity) {
this.velocity = velocity;
return this;
}
public CannonShellWeapon fireTime(int fireTime) {
this.fireTime = fireTime;
return this;
}
public CannonShellWeapon durability(int durability) {
this.durability = durability;
return this;
}
// TODO create
// public CannonShellEntity create(Player player) {
// return new CannonShellEntity(player,
// player.level(),
// this.hitDamage,
// this.explosionRadius,
// this.explosionDamage,
// this.fireProbability,
// this.fireTime
// ).durability(this.durability);
// }
}

View file

@ -0,0 +1,4 @@
package com.atsuishio.superbwarfare.entity.vehicle.weapon;
public class EmptyWeapon extends VehicleWeapon {
}

View file

@ -0,0 +1,32 @@
package com.atsuishio.superbwarfare.entity.vehicle.weapon;
import com.atsuishio.superbwarfare.ModUtils;
public class HeliRocketWeapon extends VehicleWeapon {
public float damage = 140, explosionDamage = 60, explosionRadius = 5;
public HeliRocketWeapon() {
this.icon = ModUtils.loc("textures/screens/vehicle_weapon/rocket_70mm.png");
}
public HeliRocketWeapon damage(float damage) {
this.damage = damage;
return this;
}
public HeliRocketWeapon explosionDamage(float explosionDamage) {
this.explosionDamage = explosionDamage;
return this;
}
public HeliRocketWeapon explosionRadius(float explosionRadius) {
this.explosionRadius = explosionRadius;
return this;
}
// TODO create
// public HeliRocketEntity create(LivingEntity entity) {
// return new HeliRocketEntity(entity, entity.level(), damage, explosionDamage, explosionRadius);
// }
}

View file

@ -0,0 +1,11 @@
package com.atsuishio.superbwarfare.entity.vehicle.weapon;
import com.atsuishio.superbwarfare.ModUtils;
public class LaserWeapon extends VehicleWeapon {
public LaserWeapon() {
this.icon = ModUtils.loc("textures/screens/vehicle_weapon/laser.png");
}
}

View file

@ -0,0 +1,52 @@
package com.atsuishio.superbwarfare.entity.vehicle.weapon;
import com.atsuishio.superbwarfare.entity.projectile.ProjectileEntity;
import net.minecraft.world.entity.LivingEntity;
public class ProjectileWeapon extends VehicleWeapon {
public float headShot, damage, bypassArmorRate;
public boolean zoom;
public int jhpLevel, heLevel;
public ProjectileWeapon headShot(float headShot) {
this.headShot = headShot;
return this;
}
public ProjectileWeapon damage(float damage) {
this.damage = damage;
return this;
}
public ProjectileWeapon bypassArmorRate(float bypassArmorRate) {
this.bypassArmorRate = bypassArmorRate;
return this;
}
public ProjectileWeapon zoom(boolean zoom) {
this.zoom = zoom;
return this;
}
public ProjectileWeapon jhpBullet(int jhpLevel) {
this.jhpLevel = jhpLevel;
return this;
}
public ProjectileWeapon heBullet(int heLevel) {
this.heLevel = heLevel;
return this;
}
public ProjectileEntity create(LivingEntity shooter) {
return new ProjectileEntity(shooter.level())
.shooter(shooter)
.headShot(headShot)
.damage(damage)
.bypassArmorRate(bypassArmorRate)
.zoom(zoom)
.jhpBullet(jhpLevel)
.heBullet(heLevel);
}
}

View file

@ -0,0 +1,26 @@
package com.atsuishio.superbwarfare.entity.vehicle.weapon;
public class SmallCannonShellWeapon extends VehicleWeapon {
public float damage = 40, explosionDamage = 80, explosionRadius = 5;
public SmallCannonShellWeapon damage(float damage) {
this.damage = damage;
return this;
}
public SmallCannonShellWeapon explosionDamage(float explosionDamage) {
this.explosionDamage = explosionDamage;
return this;
}
public SmallCannonShellWeapon explosionRadius(float explosionRadius) {
this.explosionRadius = explosionRadius;
return this;
}
// TODO create
// public SmallCannonShellEntity create(LivingEntity entity) {
// return new SmallCannonShellEntity(entity, entity.level(), damage, explosionDamage, explosionRadius);
// }
}

View file

@ -0,0 +1,79 @@
package com.atsuishio.superbwarfare.entity.vehicle.weapon;
import com.atsuishio.superbwarfare.ModUtils;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.world.item.Item;
public abstract class VehicleWeapon {
// 武器的图标
public ResourceLocation icon = ModUtils.loc("textures/screens/vehicle_weapon/empty.png");
// 武器的名称
public Component name;
// 武器使用的弹药类型
public Item ammo;
// 装弹类型
public AmmoType ammoType = AmmoType.INDIRECT;
// 最大装弹量对直接读取备弹的武器无效
public int maxAmmo;
// 当前弹药量对直接读取备弹的武器无效
public int currentAmmo;
// 备弹量
public int backupAmmo;
public SoundEvent sound;
public VehicleWeapon icon(ResourceLocation icon) {
this.icon = icon;
return this;
}
public VehicleWeapon name(Component name) {
this.name = name;
return this;
}
public VehicleWeapon name(String name) {
this.name = Component.literal(name);
return this;
}
public VehicleWeapon ammo(Item ammo) {
this.ammo = ammo;
return this;
}
public VehicleWeapon direct() {
this.ammoType = AmmoType.DIRECT;
this.maxAmmo = 0;
this.currentAmmo = 0;
return this;
}
/**
* 切换到该武器时的音效
*
* @param sound 音效
*/
public VehicleWeapon sound(SoundEvent sound) {
this.sound = sound;
return this;
}
/**
* 载具武器的装弹类型
* INDIRECT - 需要先进行上弹再发射
* DIRECT - 直接读取载具存储的弹药
*/
public enum AmmoType {
INDIRECT,
DIRECT,
}
public VehicleWeapon maxAmmo(int maxAmmo) {
this.maxAmmo = maxAmmo;
return this;
}
}

View file

@ -0,0 +1,32 @@
package com.atsuishio.superbwarfare.entity.vehicle.weapon;
import com.atsuishio.superbwarfare.ModUtils;
public class WgMissileWeapon extends VehicleWeapon {
public float damage = 250, explosionDamage = 200, explosionRadius = 10;
public WgMissileWeapon() {
this.icon = ModUtils.loc("textures/screens/vehicle_weapon/missile_9m113.png");
}
public WgMissileWeapon damage(float damage) {
this.damage = damage;
return this;
}
public WgMissileWeapon explosionDamage(float explosionDamage) {
this.explosionDamage = explosionDamage;
return this;
}
public WgMissileWeapon explosionRadius(float explosionRadius) {
this.explosionRadius = explosionRadius;
return this;
}
// TODO create
// public WgMissileEntity create(LivingEntity entity) {
// return new WgMissileEntity(entity, entity.level(), damage, explosionDamage, explosionRadius);
// }
}

View file

@ -1,15 +1,19 @@
package com.atsuishio.superbwarfare.init;
import com.atsuishio.superbwarfare.ModUtils;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.syncher.EntityDataSerializer;
import net.neoforged.neoforge.registries.DeferredHolder;
import net.neoforged.neoforge.registries.DeferredRegister;
import net.neoforged.neoforge.registries.NeoForgeRegistries;
import java.util.List;
public class ModSerializers {
// TODO serializers
public static final DeferredRegister<EntityDataSerializer<?>> REGISTRY = DeferredRegister.create(NeoForgeRegistries.Keys.ENTITY_DATA_SERIALIZERS, ModUtils.MODID);
// public static final DeferredHolder<EntityDataSerializer<IntList>, EntityDataSerializer<IntList>> INT_LIST_SERIALIZER = REGISTRY.register("int_list_serializer",
// () -> EntityDataSerializer.simple(FriendlyByteBuf::writeIntIdList, FriendlyByteBuf::readIntIdList));
public static final DeferredHolder<EntityDataSerializer<?>, EntityDataSerializer<List<Integer>>> INT_LIST_SERIALIZER = REGISTRY.register("int_list_serializer",
() -> EntityDataSerializer.forValueType(ByteBufCodecs.VAR_INT.apply(ByteBufCodecs.list()))
);
}

View file

@ -1 +1,3 @@
public net.minecraft.client.multiplayer.ClientLevel getEntities()Lnet/minecraft/world/level/entity/LevelEntityGetter; # getEntities
public net.minecraft.world.entity.Entity passengers # passengers
public net.minecraft.world.entity.Entity boardingCooldown # boardingCooldown