From 250d021cd976f64cd8f4a7543996372c9244c9f1 Mon Sep 17 00:00:00 2001 From: Light_Quanta Date: Fri, 28 Mar 2025 20:39:58 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=9F=BA=E7=A1=80=E8=BD=BD?= =?UTF-8?q?=E5=85=B7=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../capability/ModCapabilities.java | 5 + .../entity/projectile/ProjectileEntity.java | 8 +- .../vehicle/base/ArmedVehicleEntity.java | 77 ++ .../entity/vehicle/base/CannonEntity.java | 4 + .../base/ContainerMobileVehicleEntity.java | 259 +++++ .../vehicle/base/EnergyVehicleEntity.java | 80 ++ .../entity/vehicle/base/HelicopterEntity.java | 14 + .../entity/vehicle/base/LandArmorEntity.java | 18 + .../vehicle/base/MobileVehicleEntity.java | 630 ++++++++++++ .../base/ThirdPersonCameraPosition.java | 4 + .../entity/vehicle/base/VehicleEntity.java | 930 ++++++++++++++++++ .../vehicle/base/WeaponVehicleEntity.java | 146 +++ .../entity/vehicle/damage/DamageModifier.java | 186 ++++ .../entity/vehicle/damage/DamageModify.java | 78 ++ .../vehicle/weapon/CannonShellWeapon.java | 53 + .../entity/vehicle/weapon/EmptyWeapon.java | 4 + .../vehicle/weapon/HeliRocketWeapon.java | 32 + .../entity/vehicle/weapon/LaserWeapon.java | 11 + .../vehicle/weapon/ProjectileWeapon.java | 52 + .../weapon/SmallCannonShellWeapon.java | 26 + .../entity/vehicle/weapon/VehicleWeapon.java | 79 ++ .../vehicle/weapon/WgMissileWeapon.java | 32 + .../superbwarfare/init/ModSerializers.java | 10 +- .../resources/META-INF/accesstransformer.cfg | 2 + 24 files changed, 2733 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/ArmedVehicleEntity.java create mode 100644 src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/CannonEntity.java create mode 100644 src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/ContainerMobileVehicleEntity.java create mode 100644 src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/EnergyVehicleEntity.java create mode 100644 src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/HelicopterEntity.java create mode 100644 src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/LandArmorEntity.java create mode 100644 src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/MobileVehicleEntity.java create mode 100644 src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/ThirdPersonCameraPosition.java create mode 100644 src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/VehicleEntity.java create mode 100644 src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/WeaponVehicleEntity.java create mode 100644 src/main/java/com/atsuishio/superbwarfare/entity/vehicle/damage/DamageModifier.java create mode 100644 src/main/java/com/atsuishio/superbwarfare/entity/vehicle/damage/DamageModify.java create mode 100644 src/main/java/com/atsuishio/superbwarfare/entity/vehicle/weapon/CannonShellWeapon.java create mode 100644 src/main/java/com/atsuishio/superbwarfare/entity/vehicle/weapon/EmptyWeapon.java create mode 100644 src/main/java/com/atsuishio/superbwarfare/entity/vehicle/weapon/HeliRocketWeapon.java create mode 100644 src/main/java/com/atsuishio/superbwarfare/entity/vehicle/weapon/LaserWeapon.java create mode 100644 src/main/java/com/atsuishio/superbwarfare/entity/vehicle/weapon/ProjectileWeapon.java create mode 100644 src/main/java/com/atsuishio/superbwarfare/entity/vehicle/weapon/SmallCannonShellWeapon.java create mode 100644 src/main/java/com/atsuishio/superbwarfare/entity/vehicle/weapon/VehicleWeapon.java create mode 100644 src/main/java/com/atsuishio/superbwarfare/entity/vehicle/weapon/WgMissileWeapon.java diff --git a/src/main/java/com/atsuishio/superbwarfare/capability/ModCapabilities.java b/src/main/java/com/atsuishio/superbwarfare/capability/ModCapabilities.java index ee2c5e436..b67b42b39 100644 --- a/src/main/java/com/atsuishio/superbwarfare/capability/ModCapabilities.java +++ b/src/main/java/com/atsuishio/superbwarfare/capability/ModCapabilities.java @@ -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., ); + } } diff --git a/src/main/java/com/atsuishio/superbwarfare/entity/projectile/ProjectileEntity.java b/src/main/java/com/atsuishio/superbwarfare/entity/projectile/ProjectileEntity.java index db6e7e4a6..20ab60f39 100644 --- a/src/main/java/com/atsuishio/superbwarfare/entity/projectile/ProjectileEntity.java +++ b/src/main/java/com/atsuishio/superbwarfare/entity/projectile/ProjectileEntity.java @@ -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); + } } } diff --git a/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/ArmedVehicleEntity.java b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/ArmedVehicleEntity.java new file mode 100644 index 000000000..d7fa016e5 --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/ArmedVehicleEntity.java @@ -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(); +} diff --git a/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/CannonEntity.java b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/CannonEntity.java new file mode 100644 index 000000000..50c4fe3b2 --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/CannonEntity.java @@ -0,0 +1,4 @@ +package com.atsuishio.superbwarfare.entity.vehicle.base; + +public interface CannonEntity extends ArmedVehicleEntity, WeaponVehicleEntity { +} diff --git a/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/ContainerMobileVehicleEntity.java b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/ContainerMobileVehicleEntity.java new file mode 100644 index 000000000..eca65b486 --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/ContainerMobileVehicleEntity.java @@ -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 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 getLootTable() { + return null; + } + + @Override + public void setLootTable(@Nullable ResourceKey lootTable) { + } + + @Override + public long getLootTableSeed() { + return 0; + } + + @Override + public void setLootTableSeed(long pLootTableSeed) { + } + + @Override + public @NotNull NonNullList 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 @NotNull LazyOptional getCapability(@NotNull Capability 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)); + } +} diff --git a/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/EnergyVehicleEntity.java b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/EnergyVehicleEntity.java new file mode 100644 index 000000000..ce72a7825 --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/EnergyVehicleEntity.java @@ -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 ENERGY = SynchedEntityData.defineId(EnergyVehicleEntity.class, EntityDataSerializers.INT); + + protected final SyncedEntityEnergyStorage energyStorage = new SyncedEntityEnergyStorage(this.getMaxEnergy(), this.entityData, ENERGY); + protected final Supplier 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; + } + +} diff --git a/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/HelicopterEntity.java b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/HelicopterEntity.java new file mode 100644 index 000000000..7ca4c4ebe --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/HelicopterEntity.java @@ -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(); +} diff --git a/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/LandArmorEntity.java b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/LandArmorEntity.java new file mode 100644 index 000000000..471c55a7c --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/LandArmorEntity.java @@ -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); +} diff --git a/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/MobileVehicleEntity.java b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/MobileVehicleEntity.java new file mode 100644 index 000000000..75e0f7090 --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/MobileVehicleEntity.java @@ -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 CANNON_RECOIL_TIME = SynchedEntityData.defineId(MobileVehicleEntity.class, EntityDataSerializers.INT); + + public static final EntityDataAccessor POWER = SynchedEntityData.defineId(MobileVehicleEntity.class, EntityDataSerializers.FLOAT); + public static final EntityDataAccessor YAW = SynchedEntityData.defineId(MobileVehicleEntity.class, EntityDataSerializers.FLOAT); + + public static final EntityDataAccessor FIRE_ANIM = SynchedEntityData.defineId(MobileVehicleEntity.class, EntityDataSerializers.INT); + public static final EntityDataAccessor HEAT = SynchedEntityData.defineId(MobileVehicleEntity.class, EntityDataSerializers.INT); + public static final EntityDataAccessor COAX_HEAT = SynchedEntityData.defineId(MobileVehicleEntity.class, EntityDataSerializers.INT); + + public static final EntityDataAccessor 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; + } +} diff --git a/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/ThirdPersonCameraPosition.java b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/ThirdPersonCameraPosition.java new file mode 100644 index 000000000..8b645fa09 --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/ThirdPersonCameraPosition.java @@ -0,0 +1,4 @@ +package com.atsuishio.superbwarfare.entity.vehicle.base; + +public record ThirdPersonCameraPosition(double distance, double y, double z) { +} diff --git a/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/VehicleEntity.java b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/VehicleEntity.java new file mode 100644 index 000000000..af06bb22d --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/VehicleEntity.java @@ -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 HEALTH = SynchedEntityData.defineId(VehicleEntity.class, EntityDataSerializers.FLOAT); + public static final EntityDataAccessor LAST_ATTACKER_UUID = SynchedEntityData.defineId(VehicleEntity.class, EntityDataSerializers.STRING); + public static final EntityDataAccessor LAST_DRIVER_UUID = SynchedEntityData.defineId(VehicleEntity.class, EntityDataSerializers.STRING); + public static final EntityDataAccessor DELTA_ROT = SynchedEntityData.defineId(VehicleEntity.class, EntityDataSerializers.FLOAT); + public static final EntityDataAccessor> 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 orderedPassengers = generatePassengersList(); + + private ArrayList generatePassengersList() { + var list = new ArrayList(this.getMaxPassengers()); + for (int i = 0; i < this.getMaxPassengers(); i++) { + list.add(null); + } + return list; + } + + /** + * 获取按顺序排列的成员列表 + * + * @return 按顺序排列的成员列表 + */ + public List getOrderedPassengers() { + return orderedPassengers; + } + + // 仅在客户端存在的实体顺序获取,用于在客户端正确同步实体座位顺序 + public Function 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 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 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 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; + } + +} diff --git a/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/WeaponVehicleEntity.java b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/WeaponVehicleEntity.java new file mode 100644 index 000000000..fb9a5dabc --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/base/WeaponVehicleEntity.java @@ -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 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); + } +} diff --git a/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/damage/DamageModifier.java b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/damage/DamageModifier.java new file mode 100644 index 000000000..f92973c0b --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/damage/DamageModifier.java @@ -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 immuneList = new ArrayList<>(); + private final List reduceList = new ArrayList<>(); + private final List multiplyList = new ArrayList<>(); + private final List> customList = new ArrayList<>(); + + /** + * 免疫所有伤害 + */ + public DamageModifier immuneTo() { + immuneList.add(new DamageModify(DamageModify.ModifyType.IMMUNITY, 0)); + return this; + } + + /** + * 免疫指定类型的伤害 + * + * @param sourceTagKey 伤害类型 + */ + public DamageModifier immuneTo(TagKey sourceTagKey) { + immuneList.add(new DamageModify(DamageModify.ModifyType.IMMUNITY, 0, sourceTagKey)); + return this; + } + + /** + * 免疫指定类型的伤害 + * + * @param sourceKey 伤害类型 + */ + public DamageModifier immuneTo(ResourceKey sourceKey) { + immuneList.add(new DamageModify(DamageModify.ModifyType.IMMUNITY, 0, sourceKey)); + return this; + } + + /** + * 免疫指定类型的伤害 + * + * @param condition 伤害来源判定条件 + */ + public DamageModifier immuneTo(Function 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 sourceTagKey) { + reduceList.add(new DamageModify(DamageModify.ModifyType.REDUCE, value, sourceTagKey)); + return this; + } + + /** + * 固定减少指定类型的伤害一定数值 + * + * @param value 要减少的数值 + * @param sourceKey 伤害类型 + */ + public DamageModifier reduce(float value, ResourceKey sourceKey) { + reduceList.add(new DamageModify(DamageModify.ModifyType.REDUCE, value, sourceKey)); + return this; + } + + /** + * 固定减少指定类型的伤害一定数值 + * + * @param value 要减少的数值 + * @param condition 伤害来源判定条件 + */ + public DamageModifier reduce(float value, Function 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 sourceTagKey) { + multiplyList.add(new DamageModify(DamageModify.ModifyType.MULTIPLY, value, sourceTagKey)); + return this; + } + + /** + * 将指定类型的伤害值乘以指定数值 + * + * @param value 要乘以的数值 + * @param sourceKey 伤害类型 + */ + public DamageModifier multiply(float value, ResourceKey sourceKey) { + multiplyList.add(new DamageModify(DamageModify.ModifyType.MULTIPLY, value, sourceKey)); + return this; + } + + /** + * 将指定类型的伤害值乘以指定数值 + * + * @param value 要乘以的数值 + * @param condition 伤害来源判定条件 + */ + public DamageModifier multiply(float value, Function condition) { + multiplyList.add(new DamageModify(DamageModify.ModifyType.MULTIPLY, value, condition)); + return this; + } + + /** + * 自定义伤害值计算 + * + * @param damageModifyFunction 自定义伤害值计算函数 + */ + public DamageModifier custom(BiFunction damageModifyFunction) { + customList.add(damageModifyFunction); + return this; + } + + private final List 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; + } +} diff --git a/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/damage/DamageModify.java b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/damage/DamageModify.java new file mode 100644 index 000000000..76538d051 --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/damage/DamageModify.java @@ -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 sourceTagKey = null; + private ResourceKey sourceKey = null; + private Function condition = null; + + public DamageModify(ModifyType type, float value) { + this.type = type; + this.value = value; + } + + + public DamageModify(ModifyType type, float value, TagKey sourceTagKey) { + this.type = type; + this.value = value; + this.sourceTagKey = sourceTagKey; + } + + public DamageModify(ModifyType type, float value, ResourceKey sourceKey) { + this.type = type; + this.value = value; + this.sourceKey = sourceKey; + } + + public DamageModify(ModifyType type, float value, Function 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; + }; + } +} diff --git a/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/weapon/CannonShellWeapon.java b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/weapon/CannonShellWeapon.java new file mode 100644 index 000000000..f5d854dac --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/weapon/CannonShellWeapon.java @@ -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); +// } +} diff --git a/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/weapon/EmptyWeapon.java b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/weapon/EmptyWeapon.java new file mode 100644 index 000000000..8a2e6c54f --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/weapon/EmptyWeapon.java @@ -0,0 +1,4 @@ +package com.atsuishio.superbwarfare.entity.vehicle.weapon; + +public class EmptyWeapon extends VehicleWeapon { +} diff --git a/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/weapon/HeliRocketWeapon.java b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/weapon/HeliRocketWeapon.java new file mode 100644 index 000000000..c61581b7f --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/weapon/HeliRocketWeapon.java @@ -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); +// } +} diff --git a/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/weapon/LaserWeapon.java b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/weapon/LaserWeapon.java new file mode 100644 index 000000000..e85a6f3a9 --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/weapon/LaserWeapon.java @@ -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"); + } + +} diff --git a/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/weapon/ProjectileWeapon.java b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/weapon/ProjectileWeapon.java new file mode 100644 index 000000000..1954ab7d3 --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/weapon/ProjectileWeapon.java @@ -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); + } +} diff --git a/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/weapon/SmallCannonShellWeapon.java b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/weapon/SmallCannonShellWeapon.java new file mode 100644 index 000000000..85e56569a --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/weapon/SmallCannonShellWeapon.java @@ -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); +// } +} diff --git a/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/weapon/VehicleWeapon.java b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/weapon/VehicleWeapon.java new file mode 100644 index 000000000..1186eab7c --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/weapon/VehicleWeapon.java @@ -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; + } +} diff --git a/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/weapon/WgMissileWeapon.java b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/weapon/WgMissileWeapon.java new file mode 100644 index 000000000..bedc12ada --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/weapon/WgMissileWeapon.java @@ -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); +// } +} diff --git a/src/main/java/com/atsuishio/superbwarfare/init/ModSerializers.java b/src/main/java/com/atsuishio/superbwarfare/init/ModSerializers.java index 29cbaf84a..b6a4b3e44 100644 --- a/src/main/java/com/atsuishio/superbwarfare/init/ModSerializers.java +++ b/src/main/java/com/atsuishio/superbwarfare/init/ModSerializers.java @@ -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> REGISTRY = DeferredRegister.create(NeoForgeRegistries.Keys.ENTITY_DATA_SERIALIZERS, ModUtils.MODID); -// public static final DeferredHolder, EntityDataSerializer> INT_LIST_SERIALIZER = REGISTRY.register("int_list_serializer", -// () -> EntityDataSerializer.simple(FriendlyByteBuf::writeIntIdList, FriendlyByteBuf::readIntIdList)); + public static final DeferredHolder, EntityDataSerializer>> INT_LIST_SERIALIZER = REGISTRY.register("int_list_serializer", + () -> EntityDataSerializer.forValueType(ByteBufCodecs.VAR_INT.apply(ByteBufCodecs.list())) + ); } diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index 4ed160a4b..2820100d2 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -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 \ No newline at end of file