844 lines
34 KiB
Java
844 lines
34 KiB
Java
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.EquipmentSlot;
|
|
import net.minecraft.world.entity.EquipmentSlotGroup;
|
|
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.LootTable;
|
|
import net.minecraft.world.level.storage.loot.LootParams.Builder;
|
|
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<Piglin> 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<Piglin> 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<Piglin> brain) {
|
|
brain.addActivity(
|
|
Activity.IDLE,
|
|
10,
|
|
ImmutableList.of(
|
|
SetEntityLookTarget.create(PiglinAi::isPlayerHoldingLovedItem, 14.0F),
|
|
StartAttacking.<Piglin>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<Piglin> 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<Piglin> brain) {
|
|
brain.addActivityAndRemoveMemoryWhenStopped(
|
|
Activity.CELEBRATE,
|
|
10,
|
|
ImmutableList.of(
|
|
avoidRepellent(),
|
|
SetEntityLookTarget.create(PiglinAi::isPlayerHoldingLovedItem, 14.0F),
|
|
StartAttacking.<Piglin>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<LivingEntity>(
|
|
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<Piglin> 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<Piglin> brain) {
|
|
brain.addActivityAndRemoveMemoryWhenStopped(
|
|
Activity.AVOID,
|
|
10,
|
|
ImmutableList.of(
|
|
SetWalkTargetAwayFrom.entity(MemoryModuleType.AVOID_TARGET, 1.0F, 12, true),
|
|
createIdleLookBehaviors(),
|
|
createIdleMovementBehaviors(),
|
|
EraseMemoryIf.<PathfinderMob>create(PiglinAi::wantsToStopFleeing, MemoryModuleType.AVOID_TARGET)
|
|
),
|
|
MemoryModuleType.AVOID_TARGET
|
|
);
|
|
}
|
|
|
|
private static void initRideHoglinActivity(Brain<Piglin> 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.<Pair<? extends Trigger<? super LivingEntity>, Integer>>builder()
|
|
.addAll(createLookBehaviors())
|
|
.add(Pair.of(BehaviorBuilder.triggerIf((Predicate<? super LivingEntity>)(piglin -> true)), 1))
|
|
.build()
|
|
)
|
|
),
|
|
DismountOrSkipMounting.<LivingEntity>create(8, PiglinAi::wantsToStopRiding)
|
|
),
|
|
MemoryModuleType.RIDE_TARGET
|
|
);
|
|
}
|
|
|
|
private static ImmutableList<Pair<OneShot<LivingEntity>, 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<LivingEntity> createIdleLookBehaviors() {
|
|
return new RunOne<>(
|
|
ImmutableList.<Pair<? extends BehaviorControl<? super LivingEntity>, Integer>>builder()
|
|
.addAll(createLookBehaviors())
|
|
.add(Pair.of(new DoNothing(30, 60), 1))
|
|
.build()
|
|
);
|
|
}
|
|
|
|
private static RunOne<Piglin> 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<PathfinderMob> avoidRepellent() {
|
|
return SetWalkTargetAwayFrom.pos(MemoryModuleType.NEAREST_REPELLENT, 1.0F, 8, false);
|
|
}
|
|
|
|
private static BehaviorControl<Piglin> babyAvoidNemesis() {
|
|
return CopyMemoryWithExpiry.create(Piglin::isBaby, MemoryModuleType.NEAREST_VISIBLE_NEMESIS, MemoryModuleType.AVOID_TARGET, BABY_AVOID_NEMESIS_DURATION);
|
|
}
|
|
|
|
private static BehaviorControl<Piglin> avoidZombified() {
|
|
return CopyMemoryWithExpiry.create(
|
|
PiglinAi::isNearZombified, MemoryModuleType.NEAREST_VISIBLE_ZOMBIFIED, MemoryModuleType.AVOID_TARGET, AVOID_ZOMBIFIED_DURATION
|
|
);
|
|
}
|
|
|
|
protected static void updateActivity(Piglin piglin) {
|
|
Brain<Piglin> 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<ItemStack> stacks) {
|
|
Optional<Player> 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<ItemStack> stacks) {
|
|
throwItemsTowardPos(piglin, stacks, getRandomNearbyPos(piglin));
|
|
}
|
|
|
|
private static void throwItemsTowardPlayer(Piglin piglin, Player player, List<ItemStack> stacks) {
|
|
throwItemsTowardPos(piglin, stacks, player.position());
|
|
}
|
|
|
|
private static void throwItemsTowardPos(Piglin piglin, List<ItemStack> 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<ItemStack> getBarterResponseItems(Piglin piglin) {
|
|
LootTable lootTable = piglin.level().getServer().reloadableRegistries().getLootTable(BuiltInLootTables.PIGLIN_BARTERING);
|
|
List<ItemStack> list = lootTable.getRandomItems(
|
|
new 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<Piglin> 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<? extends LivingEntity> findNearestValidAttackTarget(ServerLevel level, Piglin piglin) {
|
|
Brain<Piglin> brain = piglin.getBrain();
|
|
if (isNearZombified(piglin)) {
|
|
return Optional.empty();
|
|
} else {
|
|
Optional<LivingEntity> 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<Player> optional2 = brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER);
|
|
if (optional2.isPresent()) {
|
|
return optional2;
|
|
}
|
|
}
|
|
|
|
Optional<Mob> optional2 = brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_NEMESIS);
|
|
if (optional2.isPresent()) {
|
|
return optional2;
|
|
} else {
|
|
Optional<Player> 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<Piglin> 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<Piglin> 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<SoundEvent> 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<Piglin> brain = piglin.getBrain();
|
|
return !brain.hasMemoryValue(MemoryModuleType.AVOID_TARGET)
|
|
? false
|
|
: ((LivingEntity)brain.getMemory(MemoryModuleType.AVOID_TARGET).get()).closerThan(piglin, 12.0);
|
|
}
|
|
|
|
protected static List<AbstractPiglin> getVisibleAdultPiglins(Piglin piglin) {
|
|
return (List<AbstractPiglin>)piglin.getBrain().getMemory(MemoryModuleType.NEAREST_VISIBLE_ADULT_PIGLINS).orElse(ImmutableList.of());
|
|
}
|
|
|
|
private static List<AbstractPiglin> getAdultPiglins(AbstractPiglin piglin) {
|
|
return (List<AbstractPiglin>)piglin.getBrain().getMemory(MemoryModuleType.NEARBY_ADULT_PIGLINS).orElse(ImmutableList.of());
|
|
}
|
|
|
|
public static boolean isWearingSafeArmor(LivingEntity entity) {
|
|
for (EquipmentSlot equipmentSlot : EquipmentSlotGroup.ARMOR) {
|
|
if (entity.getItemBySlot(equipmentSlot).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<LivingEntity> 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<Player> 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<LivingEntity> optional = getAngerTarget(piglin);
|
|
LivingEntity livingEntity = BehaviorUtils.getNearestTarget(piglin, optional, angerTarget);
|
|
if (!optional.isPresent() || optional.get() != livingEntity) {
|
|
setAngerTarget(level, piglin, livingEntity);
|
|
}
|
|
}
|
|
|
|
private static Optional<LivingEntity> getAngerTarget(AbstractPiglin piglin) {
|
|
return BehaviorUtils.getLivingEntityFromUUIDMemory(piglin, MemoryModuleType.ANGRY_AT);
|
|
}
|
|
|
|
public static Optional<LivingEntity> getAvoidTarget(Piglin piglin) {
|
|
return piglin.getBrain().hasMemoryValue(MemoryModuleType.AVOID_TARGET) ? piglin.getBrain().getMemory(MemoryModuleType.AVOID_TARGET) : Optional.empty();
|
|
}
|
|
|
|
public static Optional<Player> 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<Piglin> 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<Piglin> 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;
|
|
}
|
|
}
|