minecraft-src/net/minecraft/world/entity/monster/piglin/Piglin.java
2025-07-04 02:00:41 +03:00

478 lines
17 KiB
Java

package net.minecraft.world.entity.monster.piglin;
import com.google.common.collect.ImmutableList;
import com.mojang.serialization.Dynamic;
import java.util.List;
import net.minecraft.core.BlockPos;
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.network.syncher.SynchedEntityData.Builder;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.util.RandomSource;
import net.minecraft.util.VisibleForDebug;
import net.minecraft.util.profiling.Profiler;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.SimpleContainer;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityDimensions;
import net.minecraft.world.entity.EntitySpawnReason;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Pose;
import net.minecraft.world.entity.SpawnGroupData;
import net.minecraft.world.entity.ai.Brain;
import net.minecraft.world.entity.ai.Brain.Provider;
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.sensing.Sensor;
import net.minecraft.world.entity.ai.sensing.SensorType;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.monster.Creeper;
import net.minecraft.world.entity.monster.CrossbowAttackMob;
import net.minecraft.world.entity.monster.Monster;
import net.minecraft.world.entity.npc.InventoryCarrier;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.CrossbowItem;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.ProjectileWeaponItem;
import net.minecraft.world.item.enchantment.EnchantmentEffectComponents;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.ServerLevelAccessor;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import org.jetbrains.annotations.Nullable;
public class Piglin extends AbstractPiglin implements CrossbowAttackMob, InventoryCarrier {
private static final EntityDataAccessor<Boolean> DATA_BABY_ID = SynchedEntityData.defineId(Piglin.class, EntityDataSerializers.BOOLEAN);
private static final EntityDataAccessor<Boolean> DATA_IS_CHARGING_CROSSBOW = SynchedEntityData.defineId(Piglin.class, EntityDataSerializers.BOOLEAN);
private static final EntityDataAccessor<Boolean> DATA_IS_DANCING = SynchedEntityData.defineId(Piglin.class, EntityDataSerializers.BOOLEAN);
private static final ResourceLocation SPEED_MODIFIER_BABY_ID = ResourceLocation.withDefaultNamespace("baby");
private static final AttributeModifier SPEED_MODIFIER_BABY = new AttributeModifier(
SPEED_MODIFIER_BABY_ID, 0.2F, AttributeModifier.Operation.ADD_MULTIPLIED_BASE
);
private static final int MAX_HEALTH = 16;
private static final float MOVEMENT_SPEED_WHEN_FIGHTING = 0.35F;
private static final int ATTACK_DAMAGE = 5;
private static final float CHANCE_OF_WEARING_EACH_ARMOUR_ITEM = 0.1F;
private static final int MAX_PASSENGERS_ON_ONE_HOGLIN = 3;
private static final float PROBABILITY_OF_SPAWNING_AS_BABY = 0.2F;
private static final EntityDimensions BABY_DIMENSIONS = EntityType.PIGLIN.getDimensions().scale(0.5F).withEyeHeight(0.97F);
private static final double PROBABILITY_OF_SPAWNING_WITH_CROSSBOW_INSTEAD_OF_SWORD = 0.5;
private final SimpleContainer inventory = new SimpleContainer(8);
private boolean cannotHunt;
protected static final ImmutableList<SensorType<? extends Sensor<? super Piglin>>> SENSOR_TYPES = ImmutableList.of(
SensorType.NEAREST_LIVING_ENTITIES, SensorType.NEAREST_PLAYERS, SensorType.NEAREST_ITEMS, SensorType.HURT_BY, SensorType.PIGLIN_SPECIFIC_SENSOR
);
protected static final ImmutableList<MemoryModuleType<?>> MEMORY_TYPES = ImmutableList.of(
MemoryModuleType.LOOK_TARGET,
MemoryModuleType.DOORS_TO_CLOSE,
MemoryModuleType.NEAREST_LIVING_ENTITIES,
MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES,
MemoryModuleType.NEAREST_VISIBLE_PLAYER,
MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER,
MemoryModuleType.NEAREST_VISIBLE_ADULT_PIGLINS,
MemoryModuleType.NEARBY_ADULT_PIGLINS,
MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM,
MemoryModuleType.ITEM_PICKUP_COOLDOWN_TICKS,
MemoryModuleType.HURT_BY,
MemoryModuleType.HURT_BY_ENTITY,
MemoryModuleType.WALK_TARGET,
MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE,
MemoryModuleType.ATTACK_TARGET,
MemoryModuleType.ATTACK_COOLING_DOWN,
MemoryModuleType.INTERACTION_TARGET,
MemoryModuleType.PATH,
MemoryModuleType.ANGRY_AT,
MemoryModuleType.UNIVERSAL_ANGER,
MemoryModuleType.AVOID_TARGET,
MemoryModuleType.ADMIRING_ITEM,
MemoryModuleType.TIME_TRYING_TO_REACH_ADMIRE_ITEM,
MemoryModuleType.ADMIRING_DISABLED,
MemoryModuleType.DISABLE_WALK_TO_ADMIRE_ITEM,
MemoryModuleType.CELEBRATE_LOCATION,
MemoryModuleType.DANCING,
MemoryModuleType.HUNTED_RECENTLY,
MemoryModuleType.NEAREST_VISIBLE_BABY_HOGLIN,
MemoryModuleType.NEAREST_VISIBLE_NEMESIS,
MemoryModuleType.NEAREST_VISIBLE_ZOMBIFIED,
MemoryModuleType.RIDE_TARGET,
MemoryModuleType.VISIBLE_ADULT_PIGLIN_COUNT,
MemoryModuleType.VISIBLE_ADULT_HOGLIN_COUNT,
MemoryModuleType.NEAREST_VISIBLE_HUNTABLE_HOGLIN,
MemoryModuleType.NEAREST_TARGETABLE_PLAYER_NOT_WEARING_GOLD,
MemoryModuleType.NEAREST_PLAYER_HOLDING_WANTED_ITEM,
MemoryModuleType.ATE_RECENTLY,
MemoryModuleType.NEAREST_REPELLENT
);
public Piglin(EntityType<? extends AbstractPiglin> entityType, Level level) {
super(entityType, level);
this.xpReward = 5;
}
@Override
public void addAdditionalSaveData(CompoundTag compound) {
super.addAdditionalSaveData(compound);
if (this.isBaby()) {
compound.putBoolean("IsBaby", true);
}
if (this.cannotHunt) {
compound.putBoolean("CannotHunt", true);
}
this.writeInventoryToTag(compound, this.registryAccess());
}
@Override
public void readAdditionalSaveData(CompoundTag compound) {
super.readAdditionalSaveData(compound);
this.setBaby(compound.getBoolean("IsBaby"));
this.setCannotHunt(compound.getBoolean("CannotHunt"));
this.readInventoryFromTag(compound, this.registryAccess());
}
@VisibleForDebug
@Override
public SimpleContainer getInventory() {
return this.inventory;
}
@Override
protected void dropCustomDeathLoot(ServerLevel level, DamageSource damageSource, boolean recentlyHit) {
super.dropCustomDeathLoot(level, damageSource, recentlyHit);
if (damageSource.getEntity() instanceof Creeper creeper && creeper.canDropMobsSkull()) {
ItemStack itemStack = new ItemStack(Items.PIGLIN_HEAD);
creeper.increaseDroppedSkulls();
this.spawnAtLocation(level, itemStack);
}
this.inventory.removeAllItems().forEach(itemStackx -> this.spawnAtLocation(level, itemStackx));
}
protected ItemStack addToInventory(ItemStack stack) {
return this.inventory.addItem(stack);
}
protected boolean canAddToInventory(ItemStack stack) {
return this.inventory.canAddItem(stack);
}
@Override
protected void defineSynchedData(Builder builder) {
super.defineSynchedData(builder);
builder.define(DATA_BABY_ID, false);
builder.define(DATA_IS_CHARGING_CROSSBOW, false);
builder.define(DATA_IS_DANCING, false);
}
@Override
public void onSyncedDataUpdated(EntityDataAccessor<?> dataAccessor) {
super.onSyncedDataUpdated(dataAccessor);
if (DATA_BABY_ID.equals(dataAccessor)) {
this.refreshDimensions();
}
}
public static net.minecraft.world.entity.ai.attributes.AttributeSupplier.Builder createAttributes() {
return Monster.createMonsterAttributes().add(Attributes.MAX_HEALTH, 16.0).add(Attributes.MOVEMENT_SPEED, 0.35F).add(Attributes.ATTACK_DAMAGE, 5.0);
}
public static boolean checkPiglinSpawnRules(
EntityType<Piglin> entityType, LevelAccessor levelAccessor, EntitySpawnReason entitySpawnReason, BlockPos blockPos, RandomSource randomSource
) {
return !levelAccessor.getBlockState(blockPos.below()).is(Blocks.NETHER_WART_BLOCK);
}
@Nullable
@Override
public SpawnGroupData finalizeSpawn(
ServerLevelAccessor serverLevelAccessor, DifficultyInstance difficultyInstance, EntitySpawnReason entitySpawnReason, @Nullable SpawnGroupData spawnGroupData
) {
RandomSource randomSource = serverLevelAccessor.getRandom();
if (entitySpawnReason != EntitySpawnReason.STRUCTURE) {
if (randomSource.nextFloat() < 0.2F) {
this.setBaby(true);
} else if (this.isAdult()) {
this.setItemSlot(EquipmentSlot.MAINHAND, this.createSpawnWeapon());
}
}
PiglinAi.initMemories(this, serverLevelAccessor.getRandom());
this.populateDefaultEquipmentSlots(randomSource, difficultyInstance);
this.populateDefaultEquipmentEnchantments(serverLevelAccessor, randomSource, difficultyInstance);
return super.finalizeSpawn(serverLevelAccessor, difficultyInstance, entitySpawnReason, spawnGroupData);
}
@Override
protected boolean shouldDespawnInPeaceful() {
return false;
}
@Override
public boolean removeWhenFarAway(double distanceToClosestPlayer) {
return !this.isPersistenceRequired();
}
@Override
protected void populateDefaultEquipmentSlots(RandomSource random, DifficultyInstance difficulty) {
if (this.isAdult()) {
this.maybeWearArmor(EquipmentSlot.HEAD, new ItemStack(Items.GOLDEN_HELMET), random);
this.maybeWearArmor(EquipmentSlot.CHEST, new ItemStack(Items.GOLDEN_CHESTPLATE), random);
this.maybeWearArmor(EquipmentSlot.LEGS, new ItemStack(Items.GOLDEN_LEGGINGS), random);
this.maybeWearArmor(EquipmentSlot.FEET, new ItemStack(Items.GOLDEN_BOOTS), random);
}
}
private void maybeWearArmor(EquipmentSlot slot, ItemStack stack, RandomSource random) {
if (random.nextFloat() < 0.1F) {
this.setItemSlot(slot, stack);
}
}
@Override
protected Provider<Piglin> brainProvider() {
return Brain.provider(MEMORY_TYPES, SENSOR_TYPES);
}
@Override
protected Brain<?> makeBrain(Dynamic<?> dynamic) {
return PiglinAi.makeBrain(this, this.brainProvider().makeBrain(dynamic));
}
@Override
public Brain<Piglin> getBrain() {
return (Brain<Piglin>)super.getBrain();
}
@Override
public InteractionResult mobInteract(Player player, InteractionHand hand) {
InteractionResult interactionResult = super.mobInteract(player, hand);
if (interactionResult.consumesAction()) {
return interactionResult;
} else if (this.level() instanceof ServerLevel serverLevel) {
return PiglinAi.mobInteract(serverLevel, this, player, hand);
} else {
boolean bl = PiglinAi.canAdmire(this, player.getItemInHand(hand)) && this.getArmPose() != PiglinArmPose.ADMIRING_ITEM;
return (InteractionResult)(bl ? InteractionResult.SUCCESS : InteractionResult.PASS);
}
}
@Override
public EntityDimensions getDefaultDimensions(Pose pose) {
return this.isBaby() ? BABY_DIMENSIONS : super.getDefaultDimensions(pose);
}
@Override
public void setBaby(boolean baby) {
this.getEntityData().set(DATA_BABY_ID, baby);
if (!this.level().isClientSide) {
AttributeInstance attributeInstance = this.getAttribute(Attributes.MOVEMENT_SPEED);
attributeInstance.removeModifier(SPEED_MODIFIER_BABY.id());
if (baby) {
attributeInstance.addTransientModifier(SPEED_MODIFIER_BABY);
}
}
}
@Override
public boolean isBaby() {
return this.getEntityData().get(DATA_BABY_ID);
}
private void setCannotHunt(boolean cannotHunt) {
this.cannotHunt = cannotHunt;
}
@Override
protected boolean canHunt() {
return !this.cannotHunt;
}
@Override
protected void customServerAiStep(ServerLevel serverLevel) {
ProfilerFiller profilerFiller = Profiler.get();
profilerFiller.push("piglinBrain");
this.getBrain().tick(serverLevel, this);
profilerFiller.pop();
PiglinAi.updateActivity(this);
super.customServerAiStep(serverLevel);
}
@Override
protected int getBaseExperienceReward(ServerLevel serverLevel) {
return this.xpReward;
}
@Override
protected void finishConversion(ServerLevel serverLevel) {
PiglinAi.cancelAdmiring(serverLevel, this);
this.inventory.removeAllItems().forEach(itemStack -> this.spawnAtLocation(serverLevel, itemStack));
super.finishConversion(serverLevel);
}
private ItemStack createSpawnWeapon() {
return this.random.nextFloat() < 0.5 ? new ItemStack(Items.CROSSBOW) : new ItemStack(Items.GOLDEN_SWORD);
}
private boolean isChargingCrossbow() {
return this.entityData.get(DATA_IS_CHARGING_CROSSBOW);
}
@Override
public void setChargingCrossbow(boolean chargingCrossbow) {
this.entityData.set(DATA_IS_CHARGING_CROSSBOW, chargingCrossbow);
}
@Override
public void onCrossbowAttackPerformed() {
this.noActionTime = 0;
}
@Override
public PiglinArmPose getArmPose() {
if (this.isDancing()) {
return PiglinArmPose.DANCING;
} else if (PiglinAi.isLovedItem(this.getOffhandItem())) {
return PiglinArmPose.ADMIRING_ITEM;
} else if (this.isAggressive() && this.isHoldingMeleeWeapon()) {
return PiglinArmPose.ATTACKING_WITH_MELEE_WEAPON;
} else if (this.isChargingCrossbow()) {
return PiglinArmPose.CROSSBOW_CHARGE;
} else {
return this.isHolding(Items.CROSSBOW) && CrossbowItem.isCharged(this.getWeaponItem()) ? PiglinArmPose.CROSSBOW_HOLD : PiglinArmPose.DEFAULT;
}
}
public boolean isDancing() {
return this.entityData.get(DATA_IS_DANCING);
}
public void setDancing(boolean dancing) {
this.entityData.set(DATA_IS_DANCING, dancing);
}
@Override
public boolean hurtServer(ServerLevel serverLevel, DamageSource damageSource, float f) {
boolean bl = super.hurtServer(serverLevel, damageSource, f);
if (bl && damageSource.getEntity() instanceof LivingEntity livingEntity) {
PiglinAi.wasHurtBy(serverLevel, this, livingEntity);
}
return bl;
}
@Override
public void performRangedAttack(LivingEntity target, float velocity) {
this.performCrossbowAttack(this, 1.6F);
}
@Override
public boolean canFireProjectileWeapon(ProjectileWeaponItem projectileWeapon) {
return projectileWeapon == Items.CROSSBOW;
}
protected void holdInMainHand(ItemStack stack) {
this.setItemSlotAndDropWhenKilled(EquipmentSlot.MAINHAND, stack);
}
protected void holdInOffHand(ItemStack stack) {
if (stack.is(PiglinAi.BARTERING_ITEM)) {
this.setItemSlot(EquipmentSlot.OFFHAND, stack);
this.setGuaranteedDrop(EquipmentSlot.OFFHAND);
} else {
this.setItemSlotAndDropWhenKilled(EquipmentSlot.OFFHAND, stack);
}
}
@Override
public boolean wantsToPickUp(ServerLevel serverLevel, ItemStack itemStack) {
return serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && this.canPickUpLoot() && PiglinAi.wantsToPickup(this, itemStack);
}
protected boolean canReplaceCurrentItem(ItemStack candidate) {
EquipmentSlot equipmentSlot = this.getEquipmentSlotForItem(candidate);
ItemStack itemStack = this.getItemBySlot(equipmentSlot);
return this.canReplaceCurrentItem(candidate, itemStack, equipmentSlot);
}
@Override
protected boolean canReplaceCurrentItem(ItemStack itemStack, ItemStack itemStack2, EquipmentSlot equipmentSlot) {
if (EnchantmentHelper.has(itemStack2, EnchantmentEffectComponents.PREVENT_ARMOR_CHANGE)) {
return false;
} else {
boolean bl = PiglinAi.isLovedItem(itemStack) || itemStack.is(Items.CROSSBOW);
boolean bl2 = PiglinAi.isLovedItem(itemStack2) || itemStack2.is(Items.CROSSBOW);
if (bl && !bl2) {
return true;
} else if (!bl && bl2) {
return false;
} else {
return this.isAdult() && !itemStack.is(Items.CROSSBOW) && itemStack2.is(Items.CROSSBOW)
? false
: super.canReplaceCurrentItem(itemStack, itemStack2, equipmentSlot);
}
}
}
@Override
protected void pickUpItem(ServerLevel serverLevel, ItemEntity itemEntity) {
this.onItemPickup(itemEntity);
PiglinAi.pickUpItem(serverLevel, this, itemEntity);
}
@Override
public boolean startRiding(Entity vehicle, boolean force) {
if (this.isBaby() && vehicle.getType() == EntityType.HOGLIN) {
vehicle = this.getTopPassenger(vehicle, 3);
}
return super.startRiding(vehicle, force);
}
private Entity getTopPassenger(Entity vehicle, int maxPosition) {
List<Entity> list = vehicle.getPassengers();
return maxPosition != 1 && !list.isEmpty() ? this.getTopPassenger((Entity)list.get(0), maxPosition - 1) : vehicle;
}
@Override
protected SoundEvent getAmbientSound() {
return this.level().isClientSide ? null : (SoundEvent)PiglinAi.getSoundForCurrentActivity(this).orElse(null);
}
@Override
protected SoundEvent getHurtSound(DamageSource damageSource) {
return SoundEvents.PIGLIN_HURT;
}
@Override
protected SoundEvent getDeathSound() {
return SoundEvents.PIGLIN_DEATH;
}
@Override
protected void playStepSound(BlockPos pos, BlockState state) {
this.playSound(SoundEvents.PIGLIN_STEP, 0.15F, 1.0F);
}
@Override
protected void playConvertedSound() {
this.makeSound(SoundEvents.PIGLIN_CONVERTED_TO_ZOMBIFIED);
}
}