2025-11-24 22:52:51 +03:00

296 lines
13 KiB
Java

/*
* Decompiled with CFR 0.152.
*
* Could not load the following classes:
* com.mojang.datafixers.kinds.App
* com.mojang.datafixers.kinds.Applicative
* com.mojang.serialization.Codec
* com.mojang.serialization.codecs.RecordCodecBuilder
* org.joml.Quaternionf
* org.joml.Quaternionfc
* org.joml.Vector3f
* org.joml.Vector3fc
* org.jspecify.annotations.Nullable
*/
package net.minecraft.world.item;
import com.mojang.datafixers.kinds.App;
import com.mojang.datafixers.kinds.Applicative;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import net.minecraft.advancements.CriteriaTriggers;
import net.minecraft.core.Holder;
import net.minecraft.core.component.DataComponents;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.stats.Stats;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
import net.minecraft.util.StringRepresentable;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.projectile.AbstractArrow;
import net.minecraft.world.entity.projectile.FireworkRocketEntity;
import net.minecraft.world.entity.projectile.Projectile;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.ItemUseAnimation;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.ProjectileWeaponItem;
import net.minecraft.world.item.component.ChargedProjectiles;
import net.minecraft.world.item.enchantment.EnchantmentEffectComponents;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import org.joml.Quaternionf;
import org.joml.Quaternionfc;
import org.joml.Vector3f;
import org.joml.Vector3fc;
import org.jspecify.annotations.Nullable;
public class CrossbowItem
extends ProjectileWeaponItem {
private static final float MAX_CHARGE_DURATION = 1.25f;
public static final int DEFAULT_RANGE = 8;
private boolean startSoundPlayed = false;
private boolean midLoadSoundPlayed = false;
private static final float START_SOUND_PERCENT = 0.2f;
private static final float MID_SOUND_PERCENT = 0.5f;
private static final float ARROW_POWER = 3.15f;
private static final float FIREWORK_POWER = 1.6f;
public static final float MOB_ARROW_POWER = 1.6f;
private static final ChargingSounds DEFAULT_SOUNDS = new ChargingSounds(Optional.of(SoundEvents.CROSSBOW_LOADING_START), Optional.of(SoundEvents.CROSSBOW_LOADING_MIDDLE), Optional.of(SoundEvents.CROSSBOW_LOADING_END));
public CrossbowItem(Item.Properties properties) {
super(properties);
}
@Override
public Predicate<ItemStack> getSupportedHeldProjectiles() {
return ARROW_OR_FIREWORK;
}
@Override
public Predicate<ItemStack> getAllSupportedProjectiles() {
return ARROW_ONLY;
}
@Override
public InteractionResult use(Level level, Player player, InteractionHand hand) {
ItemStack itemStack = player.getItemInHand(hand);
ChargedProjectiles chargedProjectiles = itemStack.get(DataComponents.CHARGED_PROJECTILES);
if (chargedProjectiles != null && !chargedProjectiles.isEmpty()) {
this.performShooting(level, player, hand, itemStack, CrossbowItem.getShootingPower(chargedProjectiles), 1.0f, null);
return InteractionResult.CONSUME;
}
if (!player.getProjectile(itemStack).isEmpty()) {
this.startSoundPlayed = false;
this.midLoadSoundPlayed = false;
player.startUsingItem(hand);
return InteractionResult.CONSUME;
}
return InteractionResult.FAIL;
}
private static float getShootingPower(ChargedProjectiles projectiles) {
if (projectiles.contains(Items.FIREWORK_ROCKET)) {
return 1.6f;
}
return 3.15f;
}
@Override
public boolean releaseUsing(ItemStack itemStack, Level level, LivingEntity entity, int remainingTime) {
int timeHeld = this.getUseDuration(itemStack, entity) - remainingTime;
return CrossbowItem.getPowerForTime(timeHeld, itemStack, entity) >= 1.0f && CrossbowItem.isCharged(itemStack);
}
private static boolean tryLoadProjectiles(LivingEntity shooter, ItemStack heldItem) {
List<ItemStack> drawn = CrossbowItem.draw(heldItem, shooter.getProjectile(heldItem), shooter);
if (!drawn.isEmpty()) {
heldItem.set(DataComponents.CHARGED_PROJECTILES, ChargedProjectiles.of(drawn));
return true;
}
return false;
}
public static boolean isCharged(ItemStack itemStack) {
ChargedProjectiles projectiles = itemStack.getOrDefault(DataComponents.CHARGED_PROJECTILES, ChargedProjectiles.EMPTY);
return !projectiles.isEmpty();
}
@Override
protected void shootProjectile(LivingEntity livingEntity, Projectile projectileEntity, int index, float power, float uncertainty, float angle, @Nullable LivingEntity targetOverride) {
Vector3f shotVector;
if (targetOverride != null) {
double xd = targetOverride.getX() - livingEntity.getX();
double zd = targetOverride.getZ() - livingEntity.getZ();
double distanceToTarget = Math.sqrt(xd * xd + zd * zd);
double yd = targetOverride.getY(0.3333333333333333) - projectileEntity.getY() + distanceToTarget * (double)0.2f;
shotVector = CrossbowItem.getProjectileShotVector(livingEntity, new Vec3(xd, yd, zd), angle);
} else {
Vec3 upVector = livingEntity.getUpVector(1.0f);
Quaternionf upQuaternion = new Quaternionf().setAngleAxis((double)(angle * ((float)Math.PI / 180)), upVector.x, upVector.y, upVector.z);
Vec3 viewVec = livingEntity.getViewVector(1.0f);
shotVector = viewVec.toVector3f().rotate((Quaternionfc)upQuaternion);
}
projectileEntity.shoot(shotVector.x(), shotVector.y(), shotVector.z(), power, uncertainty);
float soundPitch = CrossbowItem.getShotPitch(livingEntity.getRandom(), index);
livingEntity.level().playSound(null, livingEntity.getX(), livingEntity.getY(), livingEntity.getZ(), SoundEvents.CROSSBOW_SHOOT, livingEntity.getSoundSource(), 1.0f, soundPitch);
}
private static Vector3f getProjectileShotVector(LivingEntity body, Vec3 originalVector, float angle) {
Vector3f viewVec = originalVector.toVector3f().normalize();
Vector3f rightVectorPreRot = new Vector3f((Vector3fc)viewVec).cross((Vector3fc)new Vector3f(0.0f, 1.0f, 0.0f));
if ((double)rightVectorPreRot.lengthSquared() <= 1.0E-7) {
Vec3 up = body.getUpVector(1.0f);
rightVectorPreRot = new Vector3f((Vector3fc)viewVec).cross((Vector3fc)up.toVector3f());
}
Vector3f viewVec3f = new Vector3f((Vector3fc)viewVec).rotateAxis(1.5707964f, rightVectorPreRot.x, rightVectorPreRot.y, rightVectorPreRot.z);
return new Vector3f((Vector3fc)viewVec).rotateAxis(angle * ((float)Math.PI / 180), viewVec3f.x, viewVec3f.y, viewVec3f.z);
}
@Override
protected Projectile createProjectile(Level level, LivingEntity shooter, ItemStack heldItem, ItemStack projectile, boolean isCrit) {
if (projectile.is(Items.FIREWORK_ROCKET)) {
return new FireworkRocketEntity(level, projectile, shooter, shooter.getX(), shooter.getEyeY() - (double)0.15f, shooter.getZ(), true);
}
Projectile projectileEntity = super.createProjectile(level, shooter, heldItem, projectile, isCrit);
if (projectileEntity instanceof AbstractArrow) {
AbstractArrow arrow = (AbstractArrow)projectileEntity;
arrow.setSoundEvent(SoundEvents.CROSSBOW_HIT);
}
return projectileEntity;
}
@Override
protected int getDurabilityUse(ItemStack projectile) {
return projectile.is(Items.FIREWORK_ROCKET) ? 3 : 1;
}
public void performShooting(Level level, LivingEntity shooter, InteractionHand hand, ItemStack weapon, float power, float uncertainty, @Nullable LivingEntity targetOverride) {
if (!(level instanceof ServerLevel)) {
return;
}
ServerLevel serverLevel = (ServerLevel)level;
ChargedProjectiles charged = weapon.set(DataComponents.CHARGED_PROJECTILES, ChargedProjectiles.EMPTY);
if (charged == null || charged.isEmpty()) {
return;
}
this.shoot(serverLevel, shooter, hand, weapon, charged.getItems(), power, uncertainty, shooter instanceof Player, targetOverride);
if (shooter instanceof ServerPlayer) {
ServerPlayer player = (ServerPlayer)shooter;
CriteriaTriggers.SHOT_CROSSBOW.trigger(player, weapon);
player.awardStat(Stats.ITEM_USED.get(weapon.getItem()));
}
}
private static float getShotPitch(RandomSource random, int index) {
if (index == 0) {
return 1.0f;
}
return CrossbowItem.getRandomShotPitch((index & 1) == 1, random);
}
private static float getRandomShotPitch(boolean highPitch, RandomSource random) {
float rangeDecider = highPitch ? 0.63f : 0.43f;
return 1.0f / (random.nextFloat() * 0.5f + 1.8f) + rangeDecider;
}
@Override
public void onUseTick(Level level, LivingEntity entity, ItemStack itemStack, int ticksRemaining) {
if (!level.isClientSide()) {
ChargingSounds sounds = this.getChargingSounds(itemStack);
float tickPercent = (float)(itemStack.getUseDuration(entity) - ticksRemaining) / (float)CrossbowItem.getChargeDuration(itemStack, entity);
if (tickPercent < 0.2f) {
this.startSoundPlayed = false;
this.midLoadSoundPlayed = false;
}
if (tickPercent >= 0.2f && !this.startSoundPlayed) {
this.startSoundPlayed = true;
sounds.start().ifPresent(sound -> level.playSound(null, entity.getX(), entity.getY(), entity.getZ(), (SoundEvent)sound.value(), SoundSource.PLAYERS, 0.5f, 1.0f));
}
if (tickPercent >= 0.5f && !this.midLoadSoundPlayed) {
this.midLoadSoundPlayed = true;
sounds.mid().ifPresent(sound -> level.playSound(null, entity.getX(), entity.getY(), entity.getZ(), (SoundEvent)sound.value(), SoundSource.PLAYERS, 0.5f, 1.0f));
}
if (tickPercent >= 1.0f && !CrossbowItem.isCharged(itemStack) && CrossbowItem.tryLoadProjectiles(entity, itemStack)) {
sounds.end().ifPresent(sound -> level.playSound(null, entity.getX(), entity.getY(), entity.getZ(), (SoundEvent)sound.value(), entity.getSoundSource(), 1.0f, 1.0f / (level.getRandom().nextFloat() * 0.5f + 1.0f) + 0.2f));
}
}
}
@Override
public int getUseDuration(ItemStack itemStack, LivingEntity user) {
return 72000;
}
public static int getChargeDuration(ItemStack crossbow, LivingEntity user) {
float duration = EnchantmentHelper.modifyCrossbowChargingTime(crossbow, user, 1.25f);
return Mth.floor(duration * 20.0f);
}
@Override
public ItemUseAnimation getUseAnimation(ItemStack itemStack) {
return ItemUseAnimation.CROSSBOW;
}
ChargingSounds getChargingSounds(ItemStack itemStack) {
return EnchantmentHelper.pickHighestLevel(itemStack, EnchantmentEffectComponents.CROSSBOW_CHARGING_SOUNDS).orElse(DEFAULT_SOUNDS);
}
private static float getPowerForTime(int timeHeld, ItemStack itemStack, LivingEntity holder) {
float pow = (float)timeHeld / (float)CrossbowItem.getChargeDuration(itemStack, holder);
if (pow > 1.0f) {
pow = 1.0f;
}
return pow;
}
@Override
public boolean useOnRelease(ItemStack itemStack) {
return itemStack.is(this);
}
@Override
public int getDefaultProjectileRange() {
return 8;
}
public record ChargingSounds(Optional<Holder<SoundEvent>> start, Optional<Holder<SoundEvent>> mid, Optional<Holder<SoundEvent>> end) {
public static final Codec<ChargingSounds> CODEC = RecordCodecBuilder.create(i -> i.group((App)SoundEvent.CODEC.optionalFieldOf("start").forGetter(ChargingSounds::start), (App)SoundEvent.CODEC.optionalFieldOf("mid").forGetter(ChargingSounds::mid), (App)SoundEvent.CODEC.optionalFieldOf("end").forGetter(ChargingSounds::end)).apply((Applicative)i, ChargingSounds::new));
}
public static enum ChargeType implements StringRepresentable
{
NONE("none"),
ARROW("arrow"),
ROCKET("rocket");
public static final Codec<ChargeType> CODEC;
private final String name;
private ChargeType(String name) {
this.name = name;
}
@Override
public String getSerializedName() {
return this.name;
}
static {
CODEC = StringRepresentable.fromEnum(ChargeType::values);
}
}
}