superb-warfare/src/main/java/com/atsuishio/superbwarfare/item/gun/GunItem.java
2025-05-07 18:05:08 +08:00

673 lines
20 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

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

package com.atsuishio.superbwarfare.item.gun;
import com.atsuishio.superbwarfare.Mod;
import com.atsuishio.superbwarfare.client.PoseTool;
import com.atsuishio.superbwarfare.client.tooltip.component.GunImageComponent;
import com.atsuishio.superbwarfare.entity.projectile.ProjectileEntity;
import com.atsuishio.superbwarfare.init.ModAttachments;
import com.atsuishio.superbwarfare.init.ModItems;
import com.atsuishio.superbwarfare.init.ModPerks;
import com.atsuishio.superbwarfare.init.ModSounds;
import com.atsuishio.superbwarfare.item.CustomRendererItem;
import com.atsuishio.superbwarfare.item.gun.data.GunData;
import com.atsuishio.superbwarfare.item.gun.data.value.AttachmentType;
import com.atsuishio.superbwarfare.perk.AmmoPerk;
import com.atsuishio.superbwarfare.perk.Perk;
import com.atsuishio.superbwarfare.tools.SoundTool;
import net.minecraft.client.model.HumanoidModel;
import net.minecraft.client.renderer.BlockEntityWithoutLevelRenderer;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.util.Mth;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EquipmentSlotGroup;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.tooltip.TooltipComponent;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.component.ItemAttributeModifiers;
import net.minecraft.world.item.enchantment.Enchantment;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.client.extensions.common.IClientItemExtensions;
import net.neoforged.neoforge.client.extensions.common.RegisterClientExtensionsEvent;
import org.jetbrains.annotations.NotNull;
import javax.annotation.ParametersAreNonnullByDefault;
import java.util.*;
import java.util.function.Consumer;
@EventBusSubscriber(modid = Mod.MODID, bus = EventBusSubscriber.Bus.MOD)
public abstract class GunItem extends Item implements CustomRendererItem {
public GunItem(Properties properties) {
super(properties);
addReloadTimeBehavior(this.reloadTimeBehaviors);
}
@Override
public boolean isBarVisible(@NotNull ItemStack stack) {
var data = GunData.from(stack);
return data.heat.get() != 0;
}
@Override
public int getBarWidth(@NotNull ItemStack stack) {
var data = GunData.from(stack);
return Math.round((float) data.heat.get() * 13.0F / 100F);
}
@Override
public int getBarColor(@NotNull ItemStack stack) {
var data = GunData.from(stack);
double f = 1 - data.heat.get() / 100.0F;
return Mth.hsvToRgb((float) f / 3.0F, 1.0F, 1.0F);
}
@Override
@ParametersAreNonnullByDefault
public boolean canAttackBlock(BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer) {
return false;
}
@Override
@ParametersAreNonnullByDefault
public void inventoryTick(ItemStack stack, Level level, Entity entity, int slot, boolean selected) {
if (!(entity instanceof LivingEntity living) || !(stack.getItem() instanceof GunItem gunItem)) return;
var data = GunData.from(stack);
if (!data.initialized()) {
data.initialize();
if (level.getServer() != null && entity instanceof Player player && player.isCreative()) {
data.ammo.set(data.magazine());
}
}
data.draw.set(false);
for (Perk.Type type : Perk.Type.values()) {
var instance = data.perk.getInstance(type);
if (instance != null) {
instance.perk().tick(data, instance, living);
}
}
var hasBulletInBarrel = gunItem.hasBulletInBarrel(stack);
var ammoCount = data.ammo.get();
var magazine = data.magazine();
if ((hasBulletInBarrel && ammoCount > magazine + 1) || (!hasBulletInBarrel && ammoCount > magazine)) {
int count = ammoCount - magazine - (hasBulletInBarrel ? 1 : 0);
var capability = entity.getData(ModAttachments.PLAYER_VARIABLE).watch();
var ammoType = data.ammoTypeInfo().playerAmmoType();
if (ammoType != null) {
ammoType.add(capability, count);
}
entity.setData(ModAttachments.PLAYER_VARIABLE, capability);
capability.sync(entity);
data.ammo.set(magazine + (hasBulletInBarrel ? 1 : 0));
}
// 冷却
double cooldown = 0;
if (entity.wasInPowderSnow) {
cooldown = 0.15;
} else if (entity.isInWaterOrRain()) {
cooldown = 0.04;
} else if (entity.isOnFire() || entity.isInLava()) {
cooldown = -0.1;
}
data.heat.set(Mth.clamp(data.heat.get() - 0.25 - cooldown, 0, 100));
if (data.heat.get() < 80 && data.overHeat.get()) {
data.overHeat.set(false);
}
data.save();
}
@Override
@ParametersAreNonnullByDefault
public boolean shouldCauseReequipAnimation(ItemStack oldStack, ItemStack newStack, boolean slotChanged) {
return false;
}
private static final ResourceLocation SPEED_ID = Mod.loc("gun_movement_speed");
@Override
public @NotNull ItemAttributeModifiers getDefaultAttributeModifiers(@NotNull ItemStack stack) {
var list = new ArrayList<>(super.getDefaultAttributeModifiers(stack).modifiers());
var data = GunData.from(stack);
list.add(new ItemAttributeModifiers.Entry(
Attributes.MOVEMENT_SPEED,
new AttributeModifier(SPEED_ID,
-0.01f - 0.005f * data.weight(),
AttributeModifier.Operation.ADD_MULTIPLIED_BASE
),
EquipmentSlotGroup.MAINHAND
));
return new ItemAttributeModifiers(list, true);
}
@Override
public @NotNull Optional<TooltipComponent> getTooltipImage(@NotNull ItemStack pStack) {
return Optional.of(new GunImageComponent(pStack));
}
public Set<SoundEvent> getReloadSound() {
return Set.of();
}
public ResourceLocation getGunIcon() {
return Mod.loc("textures/gun_icon/default_icon.png");
}
public String getGunDisplayName() {
return "";
}
@Override
public boolean isFoil(@NotNull ItemStack stack) {
return false;
}
@Override
public boolean isEnchantable(@NotNull ItemStack stack) {
return false;
}
@Override
@ParametersAreNonnullByDefault
public boolean supportsEnchantment(ItemStack stack, Holder<Enchantment> enchantment) {
return false;
}
/**
* 是否使用弹匣换弹
*
* @param stack 武器物品
*/
public boolean isMagazineReload(ItemStack stack) {
return false;
}
/**
* 是否使用弹夹换弹
*
* @param stack 武器物品
*/
public boolean isClipReload(ItemStack stack) {
return false;
}
/**
* 是否是单发装填换弹
*
* @param stack 武器物品
*/
public boolean isIterativeReload(ItemStack stack) {
return false;
}
/**
* 开膛待击
*
* @param stack 武器物品
*/
public boolean isOpenBolt(ItemStack stack) {
return false;
}
/**
* 是否允许额外往枪管里塞入一发子弹
*
* @param stack 武器物品
*/
public boolean hasBulletInBarrel(ItemStack stack) {
return false;
}
/**
* 武器是否为全自动武器
*
* @param stack 武器物品
*/
public boolean isAutoWeapon(ItemStack stack) {
return false;
}
/**
* 武器是否能进行改装
*
* @param stack 武器物品
*/
public boolean isCustomizable(ItemStack stack) {
return false;
}
/**
* 武器是否能更换枪管配件
*
* @param stack 武器物品
*/
public boolean hasCustomBarrel(ItemStack stack) {
return false;
}
/**
* 武器是否能更换枪托配件
*
* @param stack 武器物品
*/
public boolean hasCustomGrip(ItemStack stack) {
return false;
}
/**
* 武器是否能更换弹匣配件
*
* @param stack 武器物品
*/
public boolean hasCustomMagazine(ItemStack stack) {
return false;
}
/**
* 武器是否能更换瞄具配件
*
* @param stack 武器物品
*/
public boolean hasCustomScope(ItemStack stack) {
return false;
}
/**
* 武器是否能更换枪托配件
*
* @param stack 武器物品
*/
public boolean hasCustomStock(ItemStack stack) {
return false;
}
/**
* 武器是否有脚架
*
* @param stack 武器物品
*/
public boolean hasBipod(ItemStack stack) {
return false;
}
/**
* 武器是否会抛壳
*
* @param stack 武器物品
*/
public boolean canEjectShell(ItemStack stack) {
return false;
}
/**
* 武器是否能进行近战攻击
*
* @param stack 武器物品
*/
public boolean hasMeleeAttack(ItemStack stack) {
return false;
}
/**
* 获取武器可用的开火模式
*/
public int getAvailableFireModes() {
return 0;
}
/**
* 获取额外伤害加成
*/
public double getCustomDamage(ItemStack stack) {
return 0;
}
/**
* 获取额外爆头伤害加成
*/
public double getCustomHeadshot(ItemStack stack) {
return 0;
}
/**
* 获取额外护甲穿透加成
*/
public double getCustomBypassArmor(ItemStack stack) {
return 0;
}
/**
* 获取额外弹匣容量加成
*/
public int getCustomMagazine(ItemStack stack) {
return 0;
}
/**
* 获取额外缩放倍率加成
*/
public double getCustomZoom(ItemStack stack) {
return 0;
}
/**
* 获取额外RPM加成
*/
public int getCustomRPM(ItemStack stack) {
return 0;
}
/**
* 获取额外总重量加成
*/
public double getCustomWeight(ItemStack stack) {
var attachment = GunData.from(stack).attachment;
double scopeWeight = switch (attachment.get(AttachmentType.SCOPE)) {
case 1 -> 0.5;
case 2 -> 1;
case 3 -> 1.5;
default -> 0;
};
double barrelWeight = switch (attachment.get(AttachmentType.BARREL)) {
case 1 -> 0.5;
case 2 -> 1;
default -> 0;
};
double magazineWeight = switch (attachment.get(AttachmentType.MAGAZINE)) {
case 1 -> 1;
case 2 -> 2;
default -> 0;
};
double stockWeight = switch (attachment.get(AttachmentType.STOCK)) {
case 1 -> -2;
case 2 -> 1.5;
default -> 0;
};
double gripWeight = switch (attachment.get(AttachmentType.GRIP)) {
case 1, 2 -> 0.25;
case 3 -> 1;
default -> 0;
};
return scopeWeight + barrelWeight + magazineWeight + stockWeight + gripWeight;
}
/**
* 获取额外弹速加成
*/
public double getCustomVelocity(ItemStack stack) {
return 0;
}
/**
* 获取额外音效半径加成
*/
public double getCustomSoundRadius(ItemStack stack) {
return GunData.from(stack).attachment.get(AttachmentType.BARREL) == 2 ? 0.6 : 1;
}
public int getCustomBoltActionTime(ItemStack stack) {
return 0;
}
/**
* 是否允许缩放
*/
public boolean canAdjustZoom(ItemStack stack) {
return false;
}
/**
* 是否允许切换瞄具
*/
public boolean canSwitchScope(ItemStack stack) {
return false;
}
/**
* 右下角弹药显示名称
*/
public String getAmmoDisplayName(GunData data) {
var type = data.ammoTypeInfo().playerAmmoType();
if (type != null) {
return type.displayName;
}
return "";
}
public enum FireMode {
SEMI(1),
BURST(2),
AUTO(4);
public final int flag;
FireMode(int i) {
this.flag = i;
}
}
public final Map<Integer, Consumer<GunData>> reloadTimeBehaviors = new HashMap<>();
/**
* 添加达到指定换弹时间时的额外行为
*/
public void addReloadTimeBehavior(Map<Integer, Consumer<GunData>> behaviors) {
}
/**
* 判断武器能否开火
*/
public boolean canShoot(GunData data) {
return data.projectileAmount() > 0;
}
/**
* 服务端在开火前的额外行为
*/
public void beforeShoot(GunData data, Player player, double spread, boolean zoom) {
// 空仓挂机
if (data.ammo.get() == 1) {
data.holdOpen.set(true);
}
// 判断是否为栓动武器BoltActionTime > 0并在开火后给一个需要上膛的状态
if (data.defaultActionTime() > 0 && data.ammo.get() > 1) {
data.bolt.needed.set(true);
}
}
/**
* 服务端在开火后的额外行为
*/
public void afterShoot(GunData data, Player player) {
if (!data.useBackpackAmmo()) {
data.ammo.set(data.ammo.get() - 1);
data.isEmpty.set(true);
} else {
data.consumeBackupAmmo(player, 1);
}
}
/**
* 服务端处理开火
*/
public void onShoot(GunData data, Player player, double spread, boolean zoom) {
if (!data.hasEnoughAmmoToShoot(player)) return;
// 开火前事件
data.item.beforeShoot(data, player, spread, zoom);
int projectileAmount = data.projectileAmount();
var perk = data.perk.get(Perk.Type.AMMO);
// 生成所有子弹
for (int index0 = 0; index0 < (perk instanceof AmmoPerk ammoPerk && ammoPerk.slug ? 1 : projectileAmount); index0++) {
if (!shootBullet(player, data, spread, zoom)) return;
}
// 添加热量
data.heat.set(Mth.clamp(data.heat.get() + data.heatPerShoot(), 0, 100));
// 过热
if (data.heat.get() >= 100 && !data.overHeat.get()) {
data.overHeat.set(true);
if (!player.level().isClientSide() && player instanceof ServerPlayer serverPlayer) {
SoundTool.playLocalSound(serverPlayer, ModSounds.MINIGUN_OVERHEAT.get(), 2f, 1f);
}
}
data.item.afterShoot(data, player);
playFireSounds(data, player, zoom);
}
/**
* 播放开火音效
*/
public void playFireSounds(GunData data, Player player, boolean zoom) {
ItemStack stack = data.stack;
if (!(stack.getItem() instanceof GunItem)) return;
String origin = stack.getItem().getDescriptionId();
String name = origin.substring(origin.lastIndexOf(".") + 1);
float pitch = data.heat.get() <= 75 ? 1 : (float) (1 - 0.02 * Math.abs(75 - data.heat.get()));
var perk = data.perk.get(Perk.Type.AMMO);
if (perk == ModPerks.BEAST_BULLET.get()) {
player.playSound(ModSounds.HENG.get(), 4f, pitch);
}
float soundRadius = (float) data.soundRadius();
int barrelType = data.attachment.get(AttachmentType.BARREL);
SoundEvent sound3p = BuiltInRegistries.SOUND_EVENT.get(Mod.loc(name + (barrelType == 2 ? "_fire_3p_s" : "_fire_3p")));
if (sound3p != null) {
player.playSound(sound3p, soundRadius * 0.4f, pitch);
}
SoundEvent soundFar = BuiltInRegistries.SOUND_EVENT.get(Mod.loc(name + (barrelType == 2 ? "_far_s" : "_far")));
if (soundFar != null) {
player.playSound(soundFar, soundRadius * 0.7f, pitch);
}
SoundEvent soundVeryFar = BuiltInRegistries.SOUND_EVENT.get(Mod.loc(name + (barrelType == 2 ? "_veryfar_s" : "_veryfar")));
if (soundVeryFar != null) {
player.playSound(soundVeryFar, soundRadius, pitch);
}
}
/**
* 服务端处理按下开火按键时的额外行为
*/
public void onFireKeyPress(final GunData data, Player player, boolean zoom) {
if (data.reload.prepareTimer.get() == 0 && data.reloading() && data.hasEnoughAmmoToShoot(player)) {
data.forceStop.set(true);
}
}
/**
* 服务端处理松开开火按键时的额外行为
*/
public void onFireKeyRelease(final GunData data, Player player, double power, boolean zoom) {
}
public static double perkDamage(Perk perk) {
if (perk instanceof AmmoPerk ammoPerk) {
return ammoPerk.damageRate;
}
return 1;
}
/**
* 服务端发射单发子弹
*
* @return 是否发射成功
*/
public boolean shootBullet(Player player, GunData data, double spread, boolean zoom) {
var stack = data.stack;
float headshot = (float) data.headshot();
float damage = (float) data.damage();
float velocity = (float) data.velocity();
float bypassArmorRate = (float) data.bypassArmor();
ProjectileEntity projectile = new ProjectileEntity(player.level())
.shooter(player)
.damage(damage)
.headShot(headshot)
.zoom(zoom)
.bypassArmorRate(bypassArmorRate)
.setGunItemId(stack);
for (Perk.Type type : Perk.Type.values()) {
var instance = data.perk.getInstance(type);
if (instance != null) {
instance.perk().modifyProjectile(data, instance, projectile);
if (instance.perk() instanceof AmmoPerk ammoPerk) {
velocity = (float) ammoPerk.getModifiedVelocity(data, instance);
}
}
}
projectile.setPos(player.getX() - 0.1 * player.getLookAngle().x, player.getEyeY() - 0.1 - 0.1 * player.getLookAngle().y, player.getZ() + -0.1 * player.getLookAngle().z);
projectile.shoot(player, player.getLookAngle().x, player.getLookAngle().y + 0.001f, player.getLookAngle().z, velocity, (float) spread);
player.level().addFreshEntity(projectile);
return true;
}
@SubscribeEvent
private static void registerGunExtensions(RegisterClientExtensionsEvent event) {
for (var item : ModItems.GUNS.getEntries()) {
if (item.get() instanceof GunItem gun) {
event.registerItem(new IClientItemExtensions() {
private final BlockEntityWithoutLevelRenderer renderer = gun.getRenderer().get();
@Override
public @NotNull BlockEntityWithoutLevelRenderer getCustomRenderer() {
return renderer;
}
@Override
@ParametersAreNonnullByDefault
public HumanoidModel.ArmPose getArmPose(LivingEntity entityLiving, InteractionHand hand, ItemStack stack) {
return PoseTool.pose(entityLiving, hand, stack);
}
}, item);
}
}
}
}