package net.minecraft.world.entity.monster.piglin; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.mojang.datafixers.util.Pair; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.function.Predicate; import net.minecraft.server.level.ServerLevel; import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundEvents; import net.minecraft.tags.ItemTags; import net.minecraft.util.RandomSource; import net.minecraft.util.TimeUtil; import net.minecraft.util.valueproviders.UniformInt; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.PathfinderMob; import net.minecraft.world.entity.ai.Brain; import net.minecraft.world.entity.ai.behavior.BackUpIfTooClose; import net.minecraft.world.entity.ai.behavior.BehaviorControl; import net.minecraft.world.entity.ai.behavior.BehaviorUtils; import net.minecraft.world.entity.ai.behavior.CopyMemoryWithExpiry; import net.minecraft.world.entity.ai.behavior.CrossbowAttack; import net.minecraft.world.entity.ai.behavior.DismountOrSkipMounting; import net.minecraft.world.entity.ai.behavior.DoNothing; import net.minecraft.world.entity.ai.behavior.EraseMemoryIf; import net.minecraft.world.entity.ai.behavior.GoToTargetLocation; import net.minecraft.world.entity.ai.behavior.GoToWantedItem; import net.minecraft.world.entity.ai.behavior.InteractWith; import net.minecraft.world.entity.ai.behavior.InteractWithDoor; import net.minecraft.world.entity.ai.behavior.LookAtTargetSink; import net.minecraft.world.entity.ai.behavior.MeleeAttack; import net.minecraft.world.entity.ai.behavior.Mount; import net.minecraft.world.entity.ai.behavior.MoveToTargetSink; import net.minecraft.world.entity.ai.behavior.OneShot; import net.minecraft.world.entity.ai.behavior.RandomStroll; import net.minecraft.world.entity.ai.behavior.RunOne; import net.minecraft.world.entity.ai.behavior.SetEntityLookTarget; import net.minecraft.world.entity.ai.behavior.SetLookAndInteract; import net.minecraft.world.entity.ai.behavior.SetWalkTargetAwayFrom; import net.minecraft.world.entity.ai.behavior.SetWalkTargetFromAttackTargetIfTargetOutOfReach; import net.minecraft.world.entity.ai.behavior.SetWalkTargetFromLookTarget; import net.minecraft.world.entity.ai.behavior.StartAttacking; import net.minecraft.world.entity.ai.behavior.StartCelebratingIfTargetDead; import net.minecraft.world.entity.ai.behavior.StopAttackingIfTargetInvalid; import net.minecraft.world.entity.ai.behavior.StopBeingAngryIfTargetDead; import net.minecraft.world.entity.ai.behavior.TriggerGate; import net.minecraft.world.entity.ai.behavior.SetEntityLookTargetSometimes.Ticker; import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder; import net.minecraft.world.entity.ai.behavior.declarative.Trigger; import net.minecraft.world.entity.ai.memory.MemoryModuleType; import net.minecraft.world.entity.ai.sensing.Sensor; import net.minecraft.world.entity.ai.util.LandRandomPos; import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.entity.monster.hoglin.Hoglin; import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.schedule.Activity; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.level.GameRules; import net.minecraft.world.level.storage.loot.BuiltInLootTables; import net.minecraft.world.level.storage.loot.LootParams; import net.minecraft.world.level.storage.loot.LootTable; import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets; import net.minecraft.world.level.storage.loot.parameters.LootContextParams; import net.minecraft.world.phys.Vec3; public class PiglinAi { public static final int REPELLENT_DETECTION_RANGE_HORIZONTAL = 8; public static final int REPELLENT_DETECTION_RANGE_VERTICAL = 4; public static final Item BARTERING_ITEM = Items.GOLD_INGOT; private static final int PLAYER_ANGER_RANGE = 16; private static final int ANGER_DURATION = 600; private static final int ADMIRE_DURATION = 119; private static final int MAX_DISTANCE_TO_WALK_TO_ITEM = 9; private static final int MAX_TIME_TO_WALK_TO_ITEM = 200; private static final int HOW_LONG_TIME_TO_DISABLE_ADMIRE_WALKING_IF_CANT_REACH_ITEM = 200; private static final int CELEBRATION_TIME = 300; protected static final UniformInt TIME_BETWEEN_HUNTS = TimeUtil.rangeOfSeconds(30, 120); private static final int BABY_FLEE_DURATION_AFTER_GETTING_HIT = 100; private static final int HIT_BY_PLAYER_MEMORY_TIMEOUT = 400; private static final int MAX_WALK_DISTANCE_TO_START_RIDING = 8; private static final UniformInt RIDE_START_INTERVAL = TimeUtil.rangeOfSeconds(10, 40); private static final UniformInt RIDE_DURATION = TimeUtil.rangeOfSeconds(10, 30); private static final UniformInt RETREAT_DURATION = TimeUtil.rangeOfSeconds(5, 20); private static final int MELEE_ATTACK_COOLDOWN = 20; private static final int EAT_COOLDOWN = 200; private static final int DESIRED_DISTANCE_FROM_ENTITY_WHEN_AVOIDING = 12; private static final int MAX_LOOK_DIST = 8; private static final int MAX_LOOK_DIST_FOR_PLAYER_HOLDING_LOVED_ITEM = 14; private static final int INTERACTION_RANGE = 8; private static final int MIN_DESIRED_DIST_FROM_TARGET_WHEN_HOLDING_CROSSBOW = 5; private static final float SPEED_WHEN_STRAFING_BACK_FROM_TARGET = 0.75F; private static final int DESIRED_DISTANCE_FROM_ZOMBIFIED = 6; private static final UniformInt AVOID_ZOMBIFIED_DURATION = TimeUtil.rangeOfSeconds(5, 7); private static final UniformInt BABY_AVOID_NEMESIS_DURATION = TimeUtil.rangeOfSeconds(5, 7); private static final float PROBABILITY_OF_CELEBRATION_DANCE = 0.1F; private static final float SPEED_MULTIPLIER_WHEN_AVOIDING = 1.0F; private static final float SPEED_MULTIPLIER_WHEN_RETREATING = 1.0F; private static final float SPEED_MULTIPLIER_WHEN_MOUNTING = 0.8F; private static final float SPEED_MULTIPLIER_WHEN_GOING_TO_WANTED_ITEM = 1.0F; private static final float SPEED_MULTIPLIER_WHEN_GOING_TO_CELEBRATE_LOCATION = 1.0F; private static final float SPEED_MULTIPLIER_WHEN_DANCING = 0.6F; private static final float SPEED_MULTIPLIER_WHEN_IDLING = 0.6F; protected static Brain makeBrain(Piglin piglin, Brain brain) { initCoreActivity(brain); initIdleActivity(brain); initAdmireItemActivity(brain); initFightActivity(piglin, brain); initCelebrateActivity(brain); initRetreatActivity(brain); initRideHoglinActivity(brain); brain.setCoreActivities(ImmutableSet.of(Activity.CORE)); brain.setDefaultActivity(Activity.IDLE); brain.useDefaultActivity(); return brain; } protected static void initMemories(Piglin piglin, RandomSource random) { int i = TIME_BETWEEN_HUNTS.sample(random); piglin.getBrain().setMemoryWithExpiry(MemoryModuleType.HUNTED_RECENTLY, true, i); } private static void initCoreActivity(Brain brain) { brain.addActivity( Activity.CORE, 0, ImmutableList.of( new LookAtTargetSink(45, 90), new MoveToTargetSink(), InteractWithDoor.create(), babyAvoidNemesis(), avoidZombified(), StopHoldingItemIfNoLongerAdmiring.create(), StartAdmiringItemIfSeen.create(119), StartCelebratingIfTargetDead.create(300, PiglinAi::wantsToDance), StopBeingAngryIfTargetDead.create() ) ); } private static void initIdleActivity(Brain brain) { brain.addActivity( Activity.IDLE, 10, ImmutableList.of( SetEntityLookTarget.create(PiglinAi::isPlayerHoldingLovedItem, 14.0F), StartAttacking.create((serverLevel, piglin) -> piglin.isAdult(), PiglinAi::findNearestValidAttackTarget), BehaviorBuilder.triggerIf(Piglin::canHunt, StartHuntingHoglin.create()), avoidRepellent(), babySometimesRideBabyHoglin(), createIdleLookBehaviors(), createIdleMovementBehaviors(), SetLookAndInteract.create(EntityType.PLAYER, 4) ) ); } private static void initFightActivity(Piglin piglin, Brain brain) { brain.addActivityAndRemoveMemoryWhenStopped( Activity.FIGHT, 10, ImmutableList.of( StopAttackingIfTargetInvalid.create((serverLevel, livingEntity) -> !isNearestValidAttackTarget(serverLevel, piglin, livingEntity)), BehaviorBuilder.triggerIf(PiglinAi::hasCrossbow, BackUpIfTooClose.create(5, 0.75F)), SetWalkTargetFromAttackTargetIfTargetOutOfReach.create(1.0F), MeleeAttack.create(20), new CrossbowAttack(), RememberIfHoglinWasKilled.create(), EraseMemoryIf.create(PiglinAi::isNearZombified, MemoryModuleType.ATTACK_TARGET) ), MemoryModuleType.ATTACK_TARGET ); } private static void initCelebrateActivity(Brain brain) { brain.addActivityAndRemoveMemoryWhenStopped( Activity.CELEBRATE, 10, ImmutableList.of( avoidRepellent(), SetEntityLookTarget.create(PiglinAi::isPlayerHoldingLovedItem, 14.0F), StartAttacking.create((serverLevel, piglin) -> piglin.isAdult(), PiglinAi::findNearestValidAttackTarget), BehaviorBuilder.triggerIf(piglin -> !piglin.isDancing(), GoToTargetLocation.create(MemoryModuleType.CELEBRATE_LOCATION, 2, 1.0F)), BehaviorBuilder.triggerIf(Piglin::isDancing, GoToTargetLocation.create(MemoryModuleType.CELEBRATE_LOCATION, 4, 0.6F)), new RunOne( ImmutableList.of( Pair.of(SetEntityLookTarget.create(EntityType.PIGLIN, 8.0F), 1), Pair.of(RandomStroll.stroll(0.6F, 2, 1), 1), Pair.of(new DoNothing(10, 20), 1) ) ) ), MemoryModuleType.CELEBRATE_LOCATION ); } private static void initAdmireItemActivity(Brain brain) { brain.addActivityAndRemoveMemoryWhenStopped( Activity.ADMIRE_ITEM, 10, ImmutableList.of( GoToWantedItem.create(PiglinAi::isNotHoldingLovedItemInOffHand, 1.0F, true, 9), StopAdmiringIfItemTooFarAway.create(9), StopAdmiringIfTiredOfTryingToReachItem.create(200, 200) ), MemoryModuleType.ADMIRING_ITEM ); } private static void initRetreatActivity(Brain brain) { brain.addActivityAndRemoveMemoryWhenStopped( Activity.AVOID, 10, ImmutableList.of( SetWalkTargetAwayFrom.entity(MemoryModuleType.AVOID_TARGET, 1.0F, 12, true), createIdleLookBehaviors(), createIdleMovementBehaviors(), EraseMemoryIf.create(PiglinAi::wantsToStopFleeing, MemoryModuleType.AVOID_TARGET) ), MemoryModuleType.AVOID_TARGET ); } private static void initRideHoglinActivity(Brain brain) { brain.addActivityAndRemoveMemoryWhenStopped( Activity.RIDE, 10, ImmutableList.of( Mount.create(0.8F), SetEntityLookTarget.create(PiglinAi::isPlayerHoldingLovedItem, 8.0F), BehaviorBuilder.sequence( BehaviorBuilder.triggerIf(Entity::isPassenger), TriggerGate.triggerOneShuffled( ImmutableList., Integer>>builder() .addAll(createLookBehaviors()) .add(Pair.of(BehaviorBuilder.triggerIf((Predicate)(piglin -> true)), 1)) .build() ) ), DismountOrSkipMounting.create(8, PiglinAi::wantsToStopRiding) ), MemoryModuleType.RIDE_TARGET ); } private static ImmutableList, Integer>> createLookBehaviors() { return ImmutableList.of( Pair.of(SetEntityLookTarget.create(EntityType.PLAYER, 8.0F), 1), Pair.of(SetEntityLookTarget.create(EntityType.PIGLIN, 8.0F), 1), Pair.of(SetEntityLookTarget.create(8.0F), 1) ); } private static RunOne createIdleLookBehaviors() { return new RunOne<>( ImmutableList., Integer>>builder() .addAll(createLookBehaviors()) .add(Pair.of(new DoNothing(30, 60), 1)) .build() ); } private static RunOne createIdleMovementBehaviors() { return new RunOne<>( ImmutableList.of( Pair.of(RandomStroll.stroll(0.6F), 2), Pair.of(InteractWith.of(EntityType.PIGLIN, 8, MemoryModuleType.INTERACTION_TARGET, 0.6F, 2), 2), Pair.of(BehaviorBuilder.triggerIf(PiglinAi::doesntSeeAnyPlayerHoldingLovedItem, SetWalkTargetFromLookTarget.create(0.6F, 3)), 2), Pair.of(new DoNothing(30, 60), 1) ) ); } private static BehaviorControl avoidRepellent() { return SetWalkTargetAwayFrom.pos(MemoryModuleType.NEAREST_REPELLENT, 1.0F, 8, false); } private static BehaviorControl babyAvoidNemesis() { return CopyMemoryWithExpiry.create(Piglin::isBaby, MemoryModuleType.NEAREST_VISIBLE_NEMESIS, MemoryModuleType.AVOID_TARGET, BABY_AVOID_NEMESIS_DURATION); } private static BehaviorControl avoidZombified() { return CopyMemoryWithExpiry.create( PiglinAi::isNearZombified, MemoryModuleType.NEAREST_VISIBLE_ZOMBIFIED, MemoryModuleType.AVOID_TARGET, AVOID_ZOMBIFIED_DURATION ); } protected static void updateActivity(Piglin piglin) { Brain brain = piglin.getBrain(); Activity activity = (Activity)brain.getActiveNonCoreActivity().orElse(null); brain.setActiveActivityToFirstValid(ImmutableList.of(Activity.ADMIRE_ITEM, Activity.FIGHT, Activity.AVOID, Activity.CELEBRATE, Activity.RIDE, Activity.IDLE)); Activity activity2 = (Activity)brain.getActiveNonCoreActivity().orElse(null); if (activity != activity2) { getSoundForCurrentActivity(piglin).ifPresent(piglin::makeSound); } piglin.setAggressive(brain.hasMemoryValue(MemoryModuleType.ATTACK_TARGET)); if (!brain.hasMemoryValue(MemoryModuleType.RIDE_TARGET) && isBabyRidingBaby(piglin)) { piglin.stopRiding(); } if (!brain.hasMemoryValue(MemoryModuleType.CELEBRATE_LOCATION)) { brain.eraseMemory(MemoryModuleType.DANCING); } piglin.setDancing(brain.hasMemoryValue(MemoryModuleType.DANCING)); } private static boolean isBabyRidingBaby(Piglin passenger) { if (!passenger.isBaby()) { return false; } else { Entity entity = passenger.getVehicle(); return entity instanceof Piglin && ((Piglin)entity).isBaby() || entity instanceof Hoglin && ((Hoglin)entity).isBaby(); } } protected static void pickUpItem(ServerLevel level, Piglin piglin, ItemEntity itemEntity) { stopWalking(piglin); ItemStack itemStack; if (itemEntity.getItem().is(Items.GOLD_NUGGET)) { piglin.take(itemEntity, itemEntity.getItem().getCount()); itemStack = itemEntity.getItem(); itemEntity.discard(); } else { piglin.take(itemEntity, 1); itemStack = removeOneItemFromItemEntity(itemEntity); } if (isLovedItem(itemStack)) { piglin.getBrain().eraseMemory(MemoryModuleType.TIME_TRYING_TO_REACH_ADMIRE_ITEM); holdInOffhand(level, piglin, itemStack); admireGoldItem(piglin); } else if (isFood(itemStack) && !hasEatenRecently(piglin)) { eat(piglin); } else { boolean bl = !piglin.equipItemIfPossible(level, itemStack).equals(ItemStack.EMPTY); if (!bl) { putInInventory(piglin, itemStack); } } } private static void holdInOffhand(ServerLevel level, Piglin piglin, ItemStack stack) { if (isHoldingItemInOffHand(piglin)) { piglin.spawnAtLocation(level, piglin.getItemInHand(InteractionHand.OFF_HAND)); } piglin.holdInOffHand(stack); } private static ItemStack removeOneItemFromItemEntity(ItemEntity itemEntity) { ItemStack itemStack = itemEntity.getItem(); ItemStack itemStack2 = itemStack.split(1); if (itemStack.isEmpty()) { itemEntity.discard(); } else { itemEntity.setItem(itemStack); } return itemStack2; } protected static void stopHoldingOffHandItem(ServerLevel level, Piglin piglin, boolean barter) { ItemStack itemStack = piglin.getItemInHand(InteractionHand.OFF_HAND); piglin.setItemInHand(InteractionHand.OFF_HAND, ItemStack.EMPTY); if (piglin.isAdult()) { boolean bl = isBarterCurrency(itemStack); if (barter && bl) { throwItems(piglin, getBarterResponseItems(piglin)); } else if (!bl) { boolean bl2 = !piglin.equipItemIfPossible(level, itemStack).isEmpty(); if (!bl2) { putInInventory(piglin, itemStack); } } } else { boolean bl = !piglin.equipItemIfPossible(level, itemStack).isEmpty(); if (!bl) { ItemStack itemStack2 = piglin.getMainHandItem(); if (isLovedItem(itemStack2)) { putInInventory(piglin, itemStack2); } else { throwItems(piglin, Collections.singletonList(itemStack2)); } piglin.holdInMainHand(itemStack); } } } protected static void cancelAdmiring(ServerLevel level, Piglin piglin) { if (isAdmiringItem(piglin) && !piglin.getOffhandItem().isEmpty()) { piglin.spawnAtLocation(level, piglin.getOffhandItem()); piglin.setItemInHand(InteractionHand.OFF_HAND, ItemStack.EMPTY); } } private static void putInInventory(Piglin piglin, ItemStack stack) { ItemStack itemStack = piglin.addToInventory(stack); throwItemsTowardRandomPos(piglin, Collections.singletonList(itemStack)); } private static void throwItems(Piglin pilgin, List stacks) { Optional optional = pilgin.getBrain().getMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER); if (optional.isPresent()) { throwItemsTowardPlayer(pilgin, (Player)optional.get(), stacks); } else { throwItemsTowardRandomPos(pilgin, stacks); } } private static void throwItemsTowardRandomPos(Piglin piglin, List stacks) { throwItemsTowardPos(piglin, stacks, getRandomNearbyPos(piglin)); } private static void throwItemsTowardPlayer(Piglin piglin, Player player, List stacks) { throwItemsTowardPos(piglin, stacks, player.position()); } private static void throwItemsTowardPos(Piglin piglin, List stacks, Vec3 pos) { if (!stacks.isEmpty()) { piglin.swing(InteractionHand.OFF_HAND); for (ItemStack itemStack : stacks) { BehaviorUtils.throwItem(piglin, itemStack, pos.add(0.0, 1.0, 0.0)); } } } private static List getBarterResponseItems(Piglin piglin) { LootTable lootTable = piglin.level().getServer().reloadableRegistries().getLootTable(BuiltInLootTables.PIGLIN_BARTERING); List list = lootTable.getRandomItems( new LootParams.Builder((ServerLevel)piglin.level()).withParameter(LootContextParams.THIS_ENTITY, piglin).create(LootContextParamSets.PIGLIN_BARTER) ); return list; } private static boolean wantsToDance(LivingEntity piglin, LivingEntity target) { return target.getType() != EntityType.HOGLIN ? false : RandomSource.create(piglin.level().getGameTime()).nextFloat() < 0.1F; } protected static boolean wantsToPickup(Piglin piglin, ItemStack stack) { if (piglin.isBaby() && stack.is(ItemTags.IGNORED_BY_PIGLIN_BABIES)) { return false; } else if (stack.is(ItemTags.PIGLIN_REPELLENTS)) { return false; } else if (isAdmiringDisabled(piglin) && piglin.getBrain().hasMemoryValue(MemoryModuleType.ATTACK_TARGET)) { return false; } else if (isBarterCurrency(stack)) { return isNotHoldingLovedItemInOffHand(piglin); } else { boolean bl = piglin.canAddToInventory(stack); if (stack.is(Items.GOLD_NUGGET)) { return bl; } else if (isFood(stack)) { return !hasEatenRecently(piglin) && bl; } else { return !isLovedItem(stack) ? piglin.canReplaceCurrentItem(stack) : isNotHoldingLovedItemInOffHand(piglin) && bl; } } } protected static boolean isLovedItem(ItemStack item) { return item.is(ItemTags.PIGLIN_LOVED); } private static boolean wantsToStopRiding(Piglin piglin, Entity vehicle) { return !(vehicle instanceof Mob mob) ? false : !mob.isBaby() || !mob.isAlive() || wasHurtRecently(piglin) || wasHurtRecently(mob) || mob instanceof Piglin && mob.getVehicle() == null; } private static boolean isNearestValidAttackTarget(ServerLevel level, Piglin piglin, LivingEntity target) { return findNearestValidAttackTarget(level, piglin).filter(livingEntity2 -> livingEntity2 == target).isPresent(); } private static boolean isNearZombified(Piglin piglin) { Brain brain = piglin.getBrain(); if (brain.hasMemoryValue(MemoryModuleType.NEAREST_VISIBLE_ZOMBIFIED)) { LivingEntity livingEntity = (LivingEntity)brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_ZOMBIFIED).get(); return piglin.closerThan(livingEntity, 6.0); } else { return false; } } private static Optional findNearestValidAttackTarget(ServerLevel level, Piglin piglin) { Brain brain = piglin.getBrain(); if (isNearZombified(piglin)) { return Optional.empty(); } else { Optional optional = BehaviorUtils.getLivingEntityFromUUIDMemory(piglin, MemoryModuleType.ANGRY_AT); if (optional.isPresent() && Sensor.isEntityAttackableIgnoringLineOfSight(level, piglin, (LivingEntity)optional.get())) { return optional; } else { if (brain.hasMemoryValue(MemoryModuleType.UNIVERSAL_ANGER)) { Optional optional2 = brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER); if (optional2.isPresent()) { return optional2; } } Optional optional2 = brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_NEMESIS); if (optional2.isPresent()) { return optional2; } else { Optional optional3 = brain.getMemory(MemoryModuleType.NEAREST_TARGETABLE_PLAYER_NOT_WEARING_GOLD); return optional3.isPresent() && Sensor.isEntityAttackable(level, piglin, (LivingEntity)optional3.get()) ? optional3 : Optional.empty(); } } } } public static void angerNearbyPiglins(ServerLevel level, Player player, boolean requireLineOfSight) { List list = player.level().getEntitiesOfClass(Piglin.class, player.getBoundingBox().inflate(16.0)); list.stream().filter(PiglinAi::isIdle).filter(piglin -> !requireLineOfSight || BehaviorUtils.canSee(piglin, player)).forEach(piglin -> { if (level.getGameRules().getBoolean(GameRules.RULE_UNIVERSAL_ANGER)) { setAngerTargetToNearestTargetablePlayerIfFound(level, piglin, player); } else { setAngerTarget(level, piglin, player); } }); } public static InteractionResult mobInteract(ServerLevel level, Piglin piglin, Player player, InteractionHand hand) { ItemStack itemStack = player.getItemInHand(hand); if (canAdmire(piglin, itemStack)) { ItemStack itemStack2 = itemStack.consumeAndReturn(1, player); holdInOffhand(level, piglin, itemStack2); admireGoldItem(piglin); stopWalking(piglin); return InteractionResult.SUCCESS; } else { return InteractionResult.PASS; } } protected static boolean canAdmire(Piglin piglin, ItemStack stack) { return !isAdmiringDisabled(piglin) && !isAdmiringItem(piglin) && piglin.isAdult() && isBarterCurrency(stack); } protected static void wasHurtBy(ServerLevel level, Piglin piglin, LivingEntity entity) { if (!(entity instanceof Piglin)) { if (isHoldingItemInOffHand(piglin)) { stopHoldingOffHandItem(level, piglin, false); } Brain brain = piglin.getBrain(); brain.eraseMemory(MemoryModuleType.CELEBRATE_LOCATION); brain.eraseMemory(MemoryModuleType.DANCING); brain.eraseMemory(MemoryModuleType.ADMIRING_ITEM); if (entity instanceof Player) { brain.setMemoryWithExpiry(MemoryModuleType.ADMIRING_DISABLED, true, 400L); } getAvoidTarget(piglin).ifPresent(livingEntity2 -> { if (livingEntity2.getType() != entity.getType()) { brain.eraseMemory(MemoryModuleType.AVOID_TARGET); } }); if (piglin.isBaby()) { brain.setMemoryWithExpiry(MemoryModuleType.AVOID_TARGET, entity, 100L); if (Sensor.isEntityAttackableIgnoringLineOfSight(level, piglin, entity)) { broadcastAngerTarget(level, piglin, entity); } } else if (entity.getType() == EntityType.HOGLIN && hoglinsOutnumberPiglins(piglin)) { setAvoidTargetAndDontHuntForAWhile(piglin, entity); broadcastRetreat(piglin, entity); } else { maybeRetaliate(level, piglin, entity); } } } protected static void maybeRetaliate(ServerLevel level, AbstractPiglin piglin, LivingEntity entity) { if (!piglin.getBrain().isActive(Activity.AVOID)) { if (Sensor.isEntityAttackableIgnoringLineOfSight(level, piglin, entity)) { if (!BehaviorUtils.isOtherTargetMuchFurtherAwayThanCurrentAttackTarget(piglin, entity, 4.0)) { if (entity.getType() == EntityType.PLAYER && level.getGameRules().getBoolean(GameRules.RULE_UNIVERSAL_ANGER)) { setAngerTargetToNearestTargetablePlayerIfFound(level, piglin, entity); broadcastUniversalAnger(level, piglin); } else { setAngerTarget(level, piglin, entity); broadcastAngerTarget(level, piglin, entity); } } } } } public static Optional getSoundForCurrentActivity(Piglin piglin) { return piglin.getBrain().getActiveNonCoreActivity().map(activity -> getSoundForActivity(piglin, activity)); } private static SoundEvent getSoundForActivity(Piglin piglin, Activity activity) { if (activity == Activity.FIGHT) { return SoundEvents.PIGLIN_ANGRY; } else if (piglin.isConverting()) { return SoundEvents.PIGLIN_RETREAT; } else if (activity == Activity.AVOID && isNearAvoidTarget(piglin)) { return SoundEvents.PIGLIN_RETREAT; } else if (activity == Activity.ADMIRE_ITEM) { return SoundEvents.PIGLIN_ADMIRING_ITEM; } else if (activity == Activity.CELEBRATE) { return SoundEvents.PIGLIN_CELEBRATE; } else if (seesPlayerHoldingLovedItem(piglin)) { return SoundEvents.PIGLIN_JEALOUS; } else { return isNearRepellent(piglin) ? SoundEvents.PIGLIN_RETREAT : SoundEvents.PIGLIN_AMBIENT; } } private static boolean isNearAvoidTarget(Piglin piglin) { Brain brain = piglin.getBrain(); return !brain.hasMemoryValue(MemoryModuleType.AVOID_TARGET) ? false : ((LivingEntity)brain.getMemory(MemoryModuleType.AVOID_TARGET).get()).closerThan(piglin, 12.0); } protected static List getVisibleAdultPiglins(Piglin piglin) { return (List)piglin.getBrain().getMemory(MemoryModuleType.NEAREST_VISIBLE_ADULT_PIGLINS).orElse(ImmutableList.of()); } private static List getAdultPiglins(AbstractPiglin piglin) { return (List)piglin.getBrain().getMemory(MemoryModuleType.NEARBY_ADULT_PIGLINS).orElse(ImmutableList.of()); } public static boolean isWearingSafeArmor(LivingEntity entity) { for (ItemStack itemStack : entity.getArmorAndBodyArmorSlots()) { if (itemStack.is(ItemTags.PIGLIN_SAFE_ARMOR)) { return true; } } return false; } private static void stopWalking(Piglin piglin) { piglin.getBrain().eraseMemory(MemoryModuleType.WALK_TARGET); piglin.getNavigation().stop(); } private static BehaviorControl babySometimesRideBabyHoglin() { Ticker ticker = new Ticker(RIDE_START_INTERVAL); return CopyMemoryWithExpiry.create( livingEntity -> livingEntity.isBaby() && ticker.tickDownAndCheck(livingEntity.level().random), MemoryModuleType.NEAREST_VISIBLE_BABY_HOGLIN, MemoryModuleType.RIDE_TARGET, RIDE_DURATION ); } protected static void broadcastAngerTarget(ServerLevel level, AbstractPiglin piglin, LivingEntity angerTarget) { getAdultPiglins(piglin).forEach(abstractPiglin -> { if (angerTarget.getType() != EntityType.HOGLIN || abstractPiglin.canHunt() && ((Hoglin)angerTarget).canBeHunted()) { setAngerTargetIfCloserThanCurrent(level, abstractPiglin, angerTarget); } }); } protected static void broadcastUniversalAnger(ServerLevel level, AbstractPiglin piglin) { getAdultPiglins(piglin) .forEach(abstractPiglin -> getNearestVisibleTargetablePlayer(abstractPiglin).ifPresent(player -> setAngerTarget(level, abstractPiglin, player))); } protected static void setAngerTarget(ServerLevel level, AbstractPiglin piglin, LivingEntity angerTarget) { if (Sensor.isEntityAttackableIgnoringLineOfSight(level, piglin, angerTarget)) { piglin.getBrain().eraseMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE); piglin.getBrain().setMemoryWithExpiry(MemoryModuleType.ANGRY_AT, angerTarget.getUUID(), 600L); if (angerTarget.getType() == EntityType.HOGLIN && piglin.canHunt()) { dontKillAnyMoreHoglinsForAWhile(piglin); } if (angerTarget.getType() == EntityType.PLAYER && level.getGameRules().getBoolean(GameRules.RULE_UNIVERSAL_ANGER)) { piglin.getBrain().setMemoryWithExpiry(MemoryModuleType.UNIVERSAL_ANGER, true, 600L); } } } private static void setAngerTargetToNearestTargetablePlayerIfFound(ServerLevel level, AbstractPiglin piglin, LivingEntity entity) { Optional optional = getNearestVisibleTargetablePlayer(piglin); if (optional.isPresent()) { setAngerTarget(level, piglin, (LivingEntity)optional.get()); } else { setAngerTarget(level, piglin, entity); } } private static void setAngerTargetIfCloserThanCurrent(ServerLevel level, AbstractPiglin piglin, LivingEntity angerTarget) { Optional optional = getAngerTarget(piglin); LivingEntity livingEntity = BehaviorUtils.getNearestTarget(piglin, optional, angerTarget); if (!optional.isPresent() || optional.get() != livingEntity) { setAngerTarget(level, piglin, livingEntity); } } private static Optional getAngerTarget(AbstractPiglin piglin) { return BehaviorUtils.getLivingEntityFromUUIDMemory(piglin, MemoryModuleType.ANGRY_AT); } public static Optional getAvoidTarget(Piglin piglin) { return piglin.getBrain().hasMemoryValue(MemoryModuleType.AVOID_TARGET) ? piglin.getBrain().getMemory(MemoryModuleType.AVOID_TARGET) : Optional.empty(); } public static Optional getNearestVisibleTargetablePlayer(AbstractPiglin piglin) { return piglin.getBrain().hasMemoryValue(MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER) ? piglin.getBrain().getMemory(MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER) : Optional.empty(); } private static void broadcastRetreat(Piglin piglin, LivingEntity target) { getVisibleAdultPiglins(piglin) .stream() .filter(abstractPiglin -> abstractPiglin instanceof Piglin) .forEach(abstractPiglin -> retreatFromNearestTarget((Piglin)abstractPiglin, target)); } private static void retreatFromNearestTarget(Piglin piglin, LivingEntity target) { Brain brain = piglin.getBrain(); LivingEntity livingEntity = BehaviorUtils.getNearestTarget(piglin, brain.getMemory(MemoryModuleType.AVOID_TARGET), target); livingEntity = BehaviorUtils.getNearestTarget(piglin, brain.getMemory(MemoryModuleType.ATTACK_TARGET), livingEntity); setAvoidTargetAndDontHuntForAWhile(piglin, livingEntity); } private static boolean wantsToStopFleeing(Piglin piglin) { Brain brain = piglin.getBrain(); if (!brain.hasMemoryValue(MemoryModuleType.AVOID_TARGET)) { return true; } else { LivingEntity livingEntity = (LivingEntity)brain.getMemory(MemoryModuleType.AVOID_TARGET).get(); EntityType entityType = livingEntity.getType(); if (entityType == EntityType.HOGLIN) { return piglinsEqualOrOutnumberHoglins(piglin); } else { return isZombified(entityType) ? !brain.isMemoryValue(MemoryModuleType.NEAREST_VISIBLE_ZOMBIFIED, livingEntity) : false; } } } private static boolean piglinsEqualOrOutnumberHoglins(Piglin piglin) { return !hoglinsOutnumberPiglins(piglin); } private static boolean hoglinsOutnumberPiglins(Piglin piglin) { int i = (Integer)piglin.getBrain().getMemory(MemoryModuleType.VISIBLE_ADULT_PIGLIN_COUNT).orElse(0) + 1; int j = (Integer)piglin.getBrain().getMemory(MemoryModuleType.VISIBLE_ADULT_HOGLIN_COUNT).orElse(0); return j > i; } private static void setAvoidTargetAndDontHuntForAWhile(Piglin piglin, LivingEntity target) { piglin.getBrain().eraseMemory(MemoryModuleType.ANGRY_AT); piglin.getBrain().eraseMemory(MemoryModuleType.ATTACK_TARGET); piglin.getBrain().eraseMemory(MemoryModuleType.WALK_TARGET); piglin.getBrain().setMemoryWithExpiry(MemoryModuleType.AVOID_TARGET, target, RETREAT_DURATION.sample(piglin.level().random)); dontKillAnyMoreHoglinsForAWhile(piglin); } protected static void dontKillAnyMoreHoglinsForAWhile(AbstractPiglin piglin) { piglin.getBrain().setMemoryWithExpiry(MemoryModuleType.HUNTED_RECENTLY, true, TIME_BETWEEN_HUNTS.sample(piglin.level().random)); } private static void eat(Piglin piglin) { piglin.getBrain().setMemoryWithExpiry(MemoryModuleType.ATE_RECENTLY, true, 200L); } private static Vec3 getRandomNearbyPos(Piglin piglin) { Vec3 vec3 = LandRandomPos.getPos(piglin, 4, 2); return vec3 == null ? piglin.position() : vec3; } private static boolean hasEatenRecently(Piglin piglin) { return piglin.getBrain().hasMemoryValue(MemoryModuleType.ATE_RECENTLY); } protected static boolean isIdle(AbstractPiglin piglin) { return piglin.getBrain().isActive(Activity.IDLE); } private static boolean hasCrossbow(LivingEntity piglin) { return piglin.isHolding(Items.CROSSBOW); } private static void admireGoldItem(LivingEntity piglin) { piglin.getBrain().setMemoryWithExpiry(MemoryModuleType.ADMIRING_ITEM, true, 119L); } private static boolean isAdmiringItem(Piglin piglin) { return piglin.getBrain().hasMemoryValue(MemoryModuleType.ADMIRING_ITEM); } private static boolean isBarterCurrency(ItemStack stack) { return stack.is(BARTERING_ITEM); } private static boolean isFood(ItemStack stack) { return stack.is(ItemTags.PIGLIN_FOOD); } private static boolean isNearRepellent(Piglin piglin) { return piglin.getBrain().hasMemoryValue(MemoryModuleType.NEAREST_REPELLENT); } private static boolean seesPlayerHoldingLovedItem(LivingEntity piglin) { return piglin.getBrain().hasMemoryValue(MemoryModuleType.NEAREST_PLAYER_HOLDING_WANTED_ITEM); } private static boolean doesntSeeAnyPlayerHoldingLovedItem(LivingEntity piglin) { return !seesPlayerHoldingLovedItem(piglin); } public static boolean isPlayerHoldingLovedItem(LivingEntity player) { return player.getType() == EntityType.PLAYER && player.isHolding(PiglinAi::isLovedItem); } private static boolean isAdmiringDisabled(Piglin piglin) { return piglin.getBrain().hasMemoryValue(MemoryModuleType.ADMIRING_DISABLED); } private static boolean wasHurtRecently(LivingEntity piglin) { return piglin.getBrain().hasMemoryValue(MemoryModuleType.HURT_BY); } private static boolean isHoldingItemInOffHand(Piglin piglin) { return !piglin.getOffhandItem().isEmpty(); } private static boolean isNotHoldingLovedItemInOffHand(Piglin piglin) { return piglin.getOffhandItem().isEmpty() || !isLovedItem(piglin.getOffhandItem()); } public static boolean isZombified(EntityType entityType) { return entityType == EntityType.ZOMBIFIED_PIGLIN || entityType == EntityType.ZOGLIN; } }