/* * Decompiled with CFR 0.152. * * Could not load the following classes: * com.mojang.serialization.Codec * org.jspecify.annotations.Nullable */ package net.minecraft.world.entity.animal; import com.mojang.serialization.Codec; import java.util.EnumSet; import java.util.List; import java.util.function.IntFunction; import net.minecraft.core.BlockPos; import net.minecraft.core.particles.ItemParticleOption; import net.minecraft.core.particles.ParticleTypes; import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.SynchedEntityData; import net.minecraft.server.level.ServerLevel; import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundEvents; import net.minecraft.tags.DamageTypeTags; import net.minecraft.tags.ItemTags; import net.minecraft.util.ByIdMap; import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; import net.minecraft.util.StringRepresentable; import net.minecraft.world.DifficultyInstance; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.AgeableMob; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityAttachment; import net.minecraft.world.entity.EntityAttachments; import net.minecraft.world.entity.EntityDimensions; import net.minecraft.world.entity.EntitySelector; 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.Mob; import net.minecraft.world.entity.PathfinderMob; import net.minecraft.world.entity.Pose; import net.minecraft.world.entity.SpawnGroupData; import net.minecraft.world.entity.ai.attributes.AttributeSupplier; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.ai.control.MoveControl; import net.minecraft.world.entity.ai.goal.AvoidEntityGoal; import net.minecraft.world.entity.ai.goal.BreedGoal; import net.minecraft.world.entity.ai.goal.FloatGoal; import net.minecraft.world.entity.ai.goal.FollowParentGoal; import net.minecraft.world.entity.ai.goal.Goal; import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal; import net.minecraft.world.entity.ai.goal.MeleeAttackGoal; import net.minecraft.world.entity.ai.goal.PanicGoal; import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal; import net.minecraft.world.entity.ai.goal.TemptGoal; import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal; import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal; import net.minecraft.world.entity.ai.targeting.TargetingConditions; import net.minecraft.world.entity.animal.Animal; import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.entity.monster.Monster; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; import net.minecraft.world.level.ServerLevelAccessor; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.gameevent.GameEvent; import net.minecraft.world.level.gamerules.GameRules; import net.minecraft.world.level.storage.ValueInput; import net.minecraft.world.level.storage.ValueOutput; import net.minecraft.world.level.storage.loot.BuiltInLootTables; import net.minecraft.world.phys.Vec3; import org.jspecify.annotations.Nullable; public class Panda extends Animal { private static final EntityDataAccessor UNHAPPY_COUNTER = SynchedEntityData.defineId(Panda.class, EntityDataSerializers.INT); private static final EntityDataAccessor SNEEZE_COUNTER = SynchedEntityData.defineId(Panda.class, EntityDataSerializers.INT); private static final EntityDataAccessor EAT_COUNTER = SynchedEntityData.defineId(Panda.class, EntityDataSerializers.INT); private static final EntityDataAccessor MAIN_GENE_ID = SynchedEntityData.defineId(Panda.class, EntityDataSerializers.BYTE); private static final EntityDataAccessor HIDDEN_GENE_ID = SynchedEntityData.defineId(Panda.class, EntityDataSerializers.BYTE); private static final EntityDataAccessor DATA_ID_FLAGS = SynchedEntityData.defineId(Panda.class, EntityDataSerializers.BYTE); private static final TargetingConditions BREED_TARGETING = TargetingConditions.forNonCombat().range(8.0); private static final EntityDimensions BABY_DIMENSIONS = EntityType.PANDA.getDimensions().scale(0.5f).withAttachments(EntityAttachments.builder().attach(EntityAttachment.PASSENGER, 0.0f, 0.40625f, 0.0f)); private static final int FLAG_SNEEZE = 2; private static final int FLAG_ROLL = 4; private static final int FLAG_SIT = 8; private static final int FLAG_ON_BACK = 16; private static final int EAT_TICK_INTERVAL = 5; public static final int TOTAL_ROLL_STEPS = 32; private static final int TOTAL_UNHAPPY_TIME = 32; private boolean gotBamboo; private boolean didBite; public int rollCounter; private Vec3 rollDelta; private float sitAmount; private float sitAmountO; private float onBackAmount; private float onBackAmountO; private float rollAmount; private float rollAmountO; private PandaLookAtPlayerGoal lookAtPlayerGoal; public Panda(EntityType type, Level level) { super((EntityType)type, level); this.moveControl = new PandaMoveControl(this); if (!this.isBaby()) { this.setCanPickUpLoot(true); } } @Override protected boolean canDispenserEquipIntoSlot(EquipmentSlot slot) { return slot == EquipmentSlot.MAINHAND && this.canPickUpLoot(); } public int getUnhappyCounter() { return this.entityData.get(UNHAPPY_COUNTER); } public void setUnhappyCounter(int value) { this.entityData.set(UNHAPPY_COUNTER, value); } public boolean isSneezing() { return this.getFlag(2); } public boolean isSitting() { return this.getFlag(8); } public void sit(boolean value) { this.setFlag(8, value); } public boolean isOnBack() { return this.getFlag(16); } public void setOnBack(boolean value) { this.setFlag(16, value); } public boolean isEating() { return this.entityData.get(EAT_COUNTER) > 0; } public void eat(boolean value) { this.entityData.set(EAT_COUNTER, value ? 1 : 0); } private int getEatCounter() { return this.entityData.get(EAT_COUNTER); } private void setEatCounter(int value) { this.entityData.set(EAT_COUNTER, value); } public void sneeze(boolean value) { this.setFlag(2, value); if (!value) { this.setSneezeCounter(0); } } public int getSneezeCounter() { return this.entityData.get(SNEEZE_COUNTER); } public void setSneezeCounter(int value) { this.entityData.set(SNEEZE_COUNTER, value); } public Gene getMainGene() { return Gene.byId(this.entityData.get(MAIN_GENE_ID).byteValue()); } public void setMainGene(Gene gene) { if (gene.getId() > 6) { gene = Gene.getRandom(this.random); } this.entityData.set(MAIN_GENE_ID, (byte)gene.getId()); } public Gene getHiddenGene() { return Gene.byId(this.entityData.get(HIDDEN_GENE_ID).byteValue()); } public void setHiddenGene(Gene gene) { if (gene.getId() > 6) { gene = Gene.getRandom(this.random); } this.entityData.set(HIDDEN_GENE_ID, (byte)gene.getId()); } public boolean isRolling() { return this.getFlag(4); } public void roll(boolean value) { this.setFlag(4, value); } @Override protected void defineSynchedData(SynchedEntityData.Builder entityData) { super.defineSynchedData(entityData); entityData.define(UNHAPPY_COUNTER, 0); entityData.define(SNEEZE_COUNTER, 0); entityData.define(MAIN_GENE_ID, (byte)0); entityData.define(HIDDEN_GENE_ID, (byte)0); entityData.define(DATA_ID_FLAGS, (byte)0); entityData.define(EAT_COUNTER, 0); } private boolean getFlag(int flag) { return (this.entityData.get(DATA_ID_FLAGS) & flag) != 0; } private void setFlag(int flag, boolean value) { byte current = this.entityData.get(DATA_ID_FLAGS); if (value) { this.entityData.set(DATA_ID_FLAGS, (byte)(current | flag)); } else { this.entityData.set(DATA_ID_FLAGS, (byte)(current & ~flag)); } } @Override protected void addAdditionalSaveData(ValueOutput output) { super.addAdditionalSaveData(output); output.store("MainGene", Gene.CODEC, this.getMainGene()); output.store("HiddenGene", Gene.CODEC, this.getHiddenGene()); } @Override protected void readAdditionalSaveData(ValueInput input) { super.readAdditionalSaveData(input); this.setMainGene(input.read("MainGene", Gene.CODEC).orElse(Gene.NORMAL)); this.setHiddenGene(input.read("HiddenGene", Gene.CODEC).orElse(Gene.NORMAL)); } @Override public @Nullable AgeableMob getBreedOffspring(ServerLevel level, AgeableMob partner) { Panda baby = EntityType.PANDA.create(level, EntitySpawnReason.BREEDING); if (baby != null) { if (partner instanceof Panda) { Panda partnerPanda = (Panda)partner; baby.setGeneFromParents(this, partnerPanda); } baby.setAttributes(); } return baby; } @Override protected void registerGoals() { this.goalSelector.addGoal(0, new FloatGoal(this)); this.goalSelector.addGoal(2, new PandaPanicGoal(this, 2.0)); this.goalSelector.addGoal(2, new PandaBreedGoal(this, 1.0)); this.goalSelector.addGoal(3, new PandaAttackGoal(this, (double)1.2f, true)); this.goalSelector.addGoal(4, new TemptGoal(this, 1.0, i -> i.is(ItemTags.PANDA_FOOD), false)); this.goalSelector.addGoal(6, new PandaAvoidGoal(this, Player.class, 8.0f, 2.0, 2.0)); this.goalSelector.addGoal(6, new PandaAvoidGoal(this, Monster.class, 4.0f, 2.0, 2.0)); this.goalSelector.addGoal(7, new PandaSitGoal()); this.goalSelector.addGoal(8, new PandaLieOnBackGoal(this)); this.goalSelector.addGoal(8, new PandaSneezeGoal(this)); this.lookAtPlayerGoal = new PandaLookAtPlayerGoal(this, Player.class, 6.0f); this.goalSelector.addGoal(9, this.lookAtPlayerGoal); this.goalSelector.addGoal(10, new RandomLookAroundGoal(this)); this.goalSelector.addGoal(12, new PandaRollGoal(this)); this.goalSelector.addGoal(13, new FollowParentGoal(this, 1.25)); this.goalSelector.addGoal(14, new WaterAvoidingRandomStrollGoal(this, 1.0)); this.targetSelector.addGoal(1, new PandaHurtByTargetGoal(this, new Class[0]).setAlertOthers(new Class[0])); } public static AttributeSupplier.Builder createAttributes() { return Animal.createAnimalAttributes().add(Attributes.MOVEMENT_SPEED, 0.15f).add(Attributes.ATTACK_DAMAGE, 6.0); } public Gene getVariant() { return Gene.getVariantFromGenes(this.getMainGene(), this.getHiddenGene()); } public boolean isLazy() { return this.getVariant() == Gene.LAZY; } public boolean isWorried() { return this.getVariant() == Gene.WORRIED; } public boolean isPlayful() { return this.getVariant() == Gene.PLAYFUL; } public boolean isBrown() { return this.getVariant() == Gene.BROWN; } public boolean isWeak() { return this.getVariant() == Gene.WEAK; } @Override public boolean isAggressive() { return this.getVariant() == Gene.AGGRESSIVE; } @Override public boolean canBeLeashed() { return false; } @Override public boolean doHurtTarget(ServerLevel level, Entity target) { if (!this.isAggressive()) { this.didBite = true; } return super.doHurtTarget(level, target); } @Override public void playAttackSound() { this.playSound(SoundEvents.PANDA_BITE, 1.0f, 1.0f); } @Override public void tick() { LivingEntity target; super.tick(); if (this.isWorried()) { if (this.level().isThundering() && !this.isInWater()) { this.sit(true); this.eat(false); } else if (!this.isEating()) { this.sit(false); } } if ((target = this.getTarget()) == null) { this.gotBamboo = false; this.didBite = false; } if (this.getUnhappyCounter() > 0) { if (target != null) { this.lookAt(target, 90.0f, 90.0f); } if (this.getUnhappyCounter() == 29 || this.getUnhappyCounter() == 14) { this.playSound(SoundEvents.PANDA_CANT_BREED, 1.0f, 1.0f); } this.setUnhappyCounter(this.getUnhappyCounter() - 1); } if (this.isSneezing()) { this.setSneezeCounter(this.getSneezeCounter() + 1); if (this.getSneezeCounter() > 20) { this.sneeze(false); this.afterSneeze(); } else if (this.getSneezeCounter() == 1) { this.playSound(SoundEvents.PANDA_PRE_SNEEZE, 1.0f, 1.0f); } } if (this.isRolling()) { this.handleRoll(); } else { this.rollCounter = 0; } if (this.isSitting()) { this.setXRot(0.0f); } this.updateSitAmount(); this.handleEating(); this.updateOnBackAnimation(); this.updateRollAmount(); } public boolean isScared() { return this.isWorried() && this.level().isThundering(); } private void handleEating() { if (!this.isEating() && this.isSitting() && !this.isScared() && !this.getItemBySlot(EquipmentSlot.MAINHAND).isEmpty() && this.random.nextInt(80) == 1) { this.eat(true); } else if (this.getItemBySlot(EquipmentSlot.MAINHAND).isEmpty() || !this.isSitting()) { this.eat(false); } if (this.isEating()) { this.addEatingParticles(); if (!this.level().isClientSide() && this.getEatCounter() > 80 && this.random.nextInt(20) == 1) { if (this.getEatCounter() > 100 && this.getItemBySlot(EquipmentSlot.MAINHAND).is(ItemTags.PANDA_EATS_FROM_GROUND)) { if (!this.level().isClientSide()) { this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY); this.gameEvent(GameEvent.EAT); } this.sit(false); } this.eat(false); return; } this.setEatCounter(this.getEatCounter() + 1); } } private void addEatingParticles() { if (this.getEatCounter() % 5 == 0) { this.playSound(SoundEvents.PANDA_EAT, 0.5f + 0.5f * (float)this.random.nextInt(2), (this.random.nextFloat() - this.random.nextFloat()) * 0.2f + 1.0f); for (int i = 0; i < 6; ++i) { Vec3 d = new Vec3(((double)this.random.nextFloat() - 0.5) * 0.1, Math.random() * 0.1 + 0.1, ((double)this.random.nextFloat() - 0.5) * 0.1); d = d.xRot(-this.getXRot() * ((float)Math.PI / 180)); d = d.yRot(-this.getYRot() * ((float)Math.PI / 180)); double y1 = (double)(-this.random.nextFloat()) * 0.6 - 0.3; Vec3 p = new Vec3(((double)this.random.nextFloat() - 0.5) * 0.8, y1, 1.0 + ((double)this.random.nextFloat() - 0.5) * 0.4); p = p.yRot(-this.yBodyRot * ((float)Math.PI / 180)); p = p.add(this.getX(), this.getEyeY() + 1.0, this.getZ()); this.level().addParticle(new ItemParticleOption(ParticleTypes.ITEM, this.getItemBySlot(EquipmentSlot.MAINHAND)), p.x, p.y, p.z, d.x, d.y + 0.05, d.z); } } } private void updateSitAmount() { this.sitAmountO = this.sitAmount; this.sitAmount = this.isSitting() ? Math.min(1.0f, this.sitAmount + 0.15f) : Math.max(0.0f, this.sitAmount - 0.19f); } private void updateOnBackAnimation() { this.onBackAmountO = this.onBackAmount; this.onBackAmount = this.isOnBack() ? Math.min(1.0f, this.onBackAmount + 0.15f) : Math.max(0.0f, this.onBackAmount - 0.19f); } private void updateRollAmount() { this.rollAmountO = this.rollAmount; this.rollAmount = this.isRolling() ? Math.min(1.0f, this.rollAmount + 0.15f) : Math.max(0.0f, this.rollAmount - 0.19f); } public float getSitAmount(float a) { return Mth.lerp(a, this.sitAmountO, this.sitAmount); } public float getLieOnBackAmount(float a) { return Mth.lerp(a, this.onBackAmountO, this.onBackAmount); } public float getRollAmount(float a) { return Mth.lerp(a, this.rollAmountO, this.rollAmount); } private void handleRoll() { ++this.rollCounter; if (this.rollCounter > 32) { this.roll(false); return; } if (!this.level().isClientSide()) { Vec3 movement = this.getDeltaMovement(); if (this.rollCounter == 1) { float angle = this.getYRot() * ((float)Math.PI / 180); float multiplier = this.isBaby() ? 0.1f : 0.2f; this.rollDelta = new Vec3(movement.x + (double)(-Mth.sin(angle) * multiplier), 0.0, movement.z + (double)(Mth.cos(angle) * multiplier)); this.setDeltaMovement(this.rollDelta.add(0.0, 0.27, 0.0)); } else if ((float)this.rollCounter == 7.0f || (float)this.rollCounter == 15.0f || (float)this.rollCounter == 23.0f) { this.setDeltaMovement(0.0, this.onGround() ? 0.27 : movement.y, 0.0); } else { this.setDeltaMovement(this.rollDelta.x, movement.y, this.rollDelta.z); } } } private void afterSneeze() { ServerLevel serverLevel; Vec3 movement = this.getDeltaMovement(); Level level = this.level(); level.addParticle(ParticleTypes.SNEEZE, this.getX() - (double)(this.getBbWidth() + 1.0f) * 0.5 * (double)Mth.sin(this.yBodyRot * ((float)Math.PI / 180)), this.getEyeY() - (double)0.1f, this.getZ() + (double)(this.getBbWidth() + 1.0f) * 0.5 * (double)Mth.cos(this.yBodyRot * ((float)Math.PI / 180)), movement.x, 0.0, movement.z); this.playSound(SoundEvents.PANDA_SNEEZE, 1.0f, 1.0f); List pandas = level.getEntitiesOfClass(Panda.class, this.getBoundingBox().inflate(10.0)); for (Panda panda : pandas) { if (panda.isBaby() || !panda.onGround() || panda.isInWater() || !panda.canPerformAction()) continue; panda.jumpFromGround(); } Level level2 = this.level(); if (level2 instanceof ServerLevel && (serverLevel = (ServerLevel)level2).getGameRules().get(GameRules.MOB_DROPS).booleanValue()) { this.dropFromGiftLootTable(serverLevel, BuiltInLootTables.PANDA_SNEEZE, this::spawnAtLocation); } } @Override protected void pickUpItem(ServerLevel level, ItemEntity entity) { if (this.getItemBySlot(EquipmentSlot.MAINHAND).isEmpty() && Panda.canPickUpAndEat(entity)) { this.onItemPickup(entity); ItemStack itemStack = entity.getItem(); this.setItemSlot(EquipmentSlot.MAINHAND, itemStack); this.setGuaranteedDrop(EquipmentSlot.MAINHAND); this.take(entity, itemStack.getCount()); entity.discard(); } } @Override public boolean hurtServer(ServerLevel level, DamageSource source, float damage) { this.sit(false); return super.hurtServer(level, source, damage); } @Override public @Nullable SpawnGroupData finalizeSpawn(ServerLevelAccessor level, DifficultyInstance difficulty, EntitySpawnReason spawnReason, @Nullable SpawnGroupData groupData) { RandomSource random = level.getRandom(); this.setMainGene(Gene.getRandom(random)); this.setHiddenGene(Gene.getRandom(random)); this.setAttributes(); if (groupData == null) { groupData = new AgeableMob.AgeableMobGroupData(0.2f); } return super.finalizeSpawn(level, difficulty, spawnReason, groupData); } public void setGeneFromParents(Panda parent1, @Nullable Panda parent2) { if (parent2 == null) { if (this.random.nextBoolean()) { this.setMainGene(parent1.getOneOfGenesRandomly()); this.setHiddenGene(Gene.getRandom(this.random)); } else { this.setMainGene(Gene.getRandom(this.random)); this.setHiddenGene(parent1.getOneOfGenesRandomly()); } } else if (this.random.nextBoolean()) { this.setMainGene(parent1.getOneOfGenesRandomly()); this.setHiddenGene(parent2.getOneOfGenesRandomly()); } else { this.setMainGene(parent2.getOneOfGenesRandomly()); this.setHiddenGene(parent1.getOneOfGenesRandomly()); } if (this.random.nextInt(32) == 0) { this.setMainGene(Gene.getRandom(this.random)); } if (this.random.nextInt(32) == 0) { this.setHiddenGene(Gene.getRandom(this.random)); } } private Gene getOneOfGenesRandomly() { if (this.random.nextBoolean()) { return this.getMainGene(); } return this.getHiddenGene(); } public void setAttributes() { if (this.isWeak()) { this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(10.0); } if (this.isLazy()) { this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(0.07f); } } private void tryToSit() { if (!this.isInWater()) { this.setZza(0.0f); this.getNavigation().stop(); this.sit(true); } } /* * Enabled force condition propagation * Lifted jumps to return sites */ @Override public InteractionResult mobInteract(Player player, InteractionHand hand) { ItemStack interactionItemStack = player.getItemInHand(hand); if (this.isScared()) { return InteractionResult.PASS; } if (this.isOnBack()) { this.setOnBack(false); return InteractionResult.SUCCESS; } if (!this.isFood(interactionItemStack)) return InteractionResult.PASS; if (this.getTarget() != null) { this.gotBamboo = true; } if (this.isBaby()) { this.usePlayerItem(player, hand, interactionItemStack); this.ageUp((int)((float)(-this.getAge() / 20) * 0.1f), true); return InteractionResult.SUCCESS_SERVER; } else if (!this.level().isClientSide() && this.getAge() == 0 && this.canFallInLove()) { this.usePlayerItem(player, hand, interactionItemStack); this.setInLove(player); return InteractionResult.SUCCESS_SERVER; } else { Level level = this.level(); if (!(level instanceof ServerLevel)) return InteractionResult.PASS; ServerLevel level2 = (ServerLevel)level; if (this.isSitting() || this.isInWater()) return InteractionResult.PASS; this.tryToSit(); this.eat(true); ItemStack pandasCurrentItem = this.getItemBySlot(EquipmentSlot.MAINHAND); if (!pandasCurrentItem.isEmpty() && !player.hasInfiniteMaterials()) { this.spawnAtLocation(level2, pandasCurrentItem); } this.setItemSlot(EquipmentSlot.MAINHAND, new ItemStack(interactionItemStack.getItem(), 1)); this.usePlayerItem(player, hand, interactionItemStack); } return InteractionResult.SUCCESS_SERVER; } @Override protected @Nullable SoundEvent getAmbientSound() { if (this.isAggressive()) { return SoundEvents.PANDA_AGGRESSIVE_AMBIENT; } if (this.isWorried()) { return SoundEvents.PANDA_WORRIED_AMBIENT; } return SoundEvents.PANDA_AMBIENT; } @Override protected void playStepSound(BlockPos pos, BlockState blockState) { this.playSound(SoundEvents.PANDA_STEP, 0.15f, 1.0f); } @Override public boolean isFood(ItemStack itemStack) { return itemStack.is(ItemTags.PANDA_FOOD); } @Override protected @Nullable SoundEvent getDeathSound() { return SoundEvents.PANDA_DEATH; } @Override protected @Nullable SoundEvent getHurtSound(DamageSource source) { return SoundEvents.PANDA_HURT; } public boolean canPerformAction() { return !this.isOnBack() && !this.isScared() && !this.isEating() && !this.isRolling() && !this.isSitting(); } @Override public EntityDimensions getDefaultDimensions(Pose pose) { return this.isBaby() ? BABY_DIMENSIONS : super.getDefaultDimensions(pose); } private static boolean canPickUpAndEat(ItemEntity entity) { return entity.getItem().is(ItemTags.PANDA_EATS_FROM_GROUND) && entity.isAlive() && !entity.hasPickUpDelay(); } private static class PandaMoveControl extends MoveControl { private final Panda panda; public PandaMoveControl(Panda mob) { super(mob); this.panda = mob; } @Override public void tick() { if (!this.panda.canPerformAction()) { return; } super.tick(); } } public static enum Gene implements StringRepresentable { NORMAL(0, "normal", false), LAZY(1, "lazy", false), WORRIED(2, "worried", false), PLAYFUL(3, "playful", false), BROWN(4, "brown", true), WEAK(5, "weak", true), AGGRESSIVE(6, "aggressive", false); public static final Codec CODEC; private static final IntFunction BY_ID; private static final int MAX_GENE = 6; private final int id; private final String name; private final boolean isRecessive; private Gene(int id, String name, boolean isRecessive) { this.id = id; this.name = name; this.isRecessive = isRecessive; } public int getId() { return this.id; } @Override public String getSerializedName() { return this.name; } public boolean isRecessive() { return this.isRecessive; } private static Gene getVariantFromGenes(Gene mainGene, Gene hiddenGene) { if (mainGene.isRecessive()) { if (mainGene == hiddenGene) { return mainGene; } return NORMAL; } return mainGene; } public static Gene byId(int id) { return BY_ID.apply(id); } public static Gene getRandom(RandomSource random) { int nextInt = random.nextInt(16); if (nextInt == 0) { return LAZY; } if (nextInt == 1) { return WORRIED; } if (nextInt == 2) { return PLAYFUL; } if (nextInt == 4) { return AGGRESSIVE; } if (nextInt < 9) { return WEAK; } if (nextInt < 11) { return BROWN; } return NORMAL; } static { CODEC = StringRepresentable.fromEnum(Gene::values); BY_ID = ByIdMap.continuous(Gene::getId, Gene.values(), ByIdMap.OutOfBoundsStrategy.ZERO); } } private static class PandaPanicGoal extends PanicGoal { private final Panda panda; public PandaPanicGoal(Panda mob, double speedModifier) { super((PathfinderMob)mob, speedModifier, DamageTypeTags.PANIC_ENVIRONMENTAL_CAUSES); this.panda = mob; } @Override public boolean canContinueToUse() { if (this.panda.isSitting()) { this.panda.getNavigation().stop(); return false; } return super.canContinueToUse(); } } private static class PandaBreedGoal extends BreedGoal { private final Panda panda; private int unhappyCooldown; public PandaBreedGoal(Panda panda, double speedModifier) { super(panda, speedModifier); this.panda = panda; } @Override public boolean canUse() { if (super.canUse() && this.panda.getUnhappyCounter() == 0) { if (!this.canFindBamboo()) { if (this.unhappyCooldown <= this.panda.tickCount) { this.panda.setUnhappyCounter(32); this.unhappyCooldown = this.panda.tickCount + 600; if (this.panda.isEffectiveAi()) { Player player = this.level.getNearestPlayer(BREED_TARGETING, this.panda); this.panda.lookAtPlayerGoal.setTarget(player); } } return false; } return true; } return false; } private boolean canFindBamboo() { BlockPos pandaPos = this.panda.blockPosition(); BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); for (int yOff = 0; yOff < 3; ++yOff) { for (int r = 0; r < 8; ++r) { int x = 0; while (x <= r) { int z; int n = z = x < r && x > -r ? r : 0; while (z <= r) { pos.setWithOffset(pandaPos, x, yOff, z); if (this.level.getBlockState(pos).is(Blocks.BAMBOO)) { return true; } z = z > 0 ? -z : 1 - z; } x = x > 0 ? -x : 1 - x; } } } return false; } } private static class PandaAttackGoal extends MeleeAttackGoal { private final Panda panda; public PandaAttackGoal(Panda mob, double speedModifier, boolean trackTarget) { super(mob, speedModifier, trackTarget); this.panda = mob; } @Override public boolean canUse() { return this.panda.canPerformAction() && super.canUse(); } } private static class PandaAvoidGoal extends AvoidEntityGoal { private final Panda panda; public PandaAvoidGoal(Panda panda, Class avoidClass, float maxDist, double walkSpeedModifier, double sprintSpeedModifier) { super(panda, avoidClass, maxDist, walkSpeedModifier, sprintSpeedModifier, EntitySelector.NO_SPECTATORS); this.panda = panda; } @Override public boolean canUse() { return this.panda.isWorried() && this.panda.canPerformAction() && super.canUse(); } } private class PandaSitGoal extends Goal { private int cooldown; public PandaSitGoal() { this.setFlags(EnumSet.of(Goal.Flag.MOVE)); } @Override public boolean canUse() { if (this.cooldown > Panda.this.tickCount || Panda.this.isBaby() || Panda.this.isInWater() || !Panda.this.canPerformAction() || Panda.this.getUnhappyCounter() > 0) { return false; } if (!Panda.this.getItemBySlot(EquipmentSlot.MAINHAND).isEmpty()) { return true; } return !Panda.this.level().getEntitiesOfClass(ItemEntity.class, Panda.this.getBoundingBox().inflate(6.0, 6.0, 6.0), Panda::canPickUpAndEat).isEmpty(); } @Override public boolean canContinueToUse() { if (Panda.this.isInWater() || !Panda.this.isLazy() && Panda.this.random.nextInt(PandaSitGoal.reducedTickDelay(600)) == 1) { return false; } return Panda.this.random.nextInt(PandaSitGoal.reducedTickDelay(2000)) != 1; } @Override public void tick() { if (!Panda.this.isSitting() && !Panda.this.getItemBySlot(EquipmentSlot.MAINHAND).isEmpty()) { Panda.this.tryToSit(); } } @Override public void start() { if (Panda.this.getItemBySlot(EquipmentSlot.MAINHAND).isEmpty()) { List items = Panda.this.level().getEntitiesOfClass(ItemEntity.class, Panda.this.getBoundingBox().inflate(8.0, 8.0, 8.0), Panda::canPickUpAndEat); if (!items.isEmpty()) { Panda.this.getNavigation().moveTo(items.getFirst(), (double)1.2f); } } else { Panda.this.tryToSit(); } this.cooldown = 0; } @Override public void stop() { ItemStack itemStack = Panda.this.getItemBySlot(EquipmentSlot.MAINHAND); if (!itemStack.isEmpty()) { Panda.this.spawnAtLocation(PandaSitGoal.getServerLevel(Panda.this.level()), itemStack); Panda.this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY); int waitSeconds = Panda.this.isLazy() ? Panda.this.random.nextInt(50) + 10 : Panda.this.random.nextInt(150) + 10; this.cooldown = Panda.this.tickCount + waitSeconds * 20; } Panda.this.sit(false); } } private static class PandaLieOnBackGoal extends Goal { private final Panda panda; private int cooldown; public PandaLieOnBackGoal(Panda panda) { this.panda = panda; } @Override public boolean canUse() { return this.cooldown < this.panda.tickCount && this.panda.isLazy() && this.panda.canPerformAction() && this.panda.random.nextInt(PandaLieOnBackGoal.reducedTickDelay(400)) == 1; } @Override public boolean canContinueToUse() { if (this.panda.isInWater() || !this.panda.isLazy() && this.panda.random.nextInt(PandaLieOnBackGoal.reducedTickDelay(600)) == 1) { return false; } return this.panda.random.nextInt(PandaLieOnBackGoal.reducedTickDelay(2000)) != 1; } @Override public void start() { this.panda.setOnBack(true); this.cooldown = 0; } @Override public void stop() { this.panda.setOnBack(false); this.cooldown = this.panda.tickCount + 200; } } private static class PandaSneezeGoal extends Goal { private final Panda panda; public PandaSneezeGoal(Panda panda) { this.panda = panda; } @Override public boolean canUse() { if (!this.panda.isBaby() || !this.panda.canPerformAction()) { return false; } if (this.panda.isWeak() && this.panda.random.nextInt(PandaSneezeGoal.reducedTickDelay(500)) == 1) { return true; } return this.panda.random.nextInt(PandaSneezeGoal.reducedTickDelay(6000)) == 1; } @Override public boolean canContinueToUse() { return false; } @Override public void start() { this.panda.sneeze(true); } } private static class PandaLookAtPlayerGoal extends LookAtPlayerGoal { private final Panda panda; public PandaLookAtPlayerGoal(Panda mob, Class lookAtType, float lookDistance) { super(mob, lookAtType, lookDistance); this.panda = mob; } public void setTarget(LivingEntity entity) { this.lookAt = entity; } @Override public boolean canContinueToUse() { return this.lookAt != null && super.canContinueToUse(); } @Override public boolean canUse() { if (this.mob.getRandom().nextFloat() >= this.probability) { return false; } if (this.lookAt == null) { ServerLevel level = PandaLookAtPlayerGoal.getServerLevel(this.mob); this.lookAt = this.lookAtType == Player.class ? level.getNearestPlayer(this.lookAtContext, this.mob, this.mob.getX(), this.mob.getEyeY(), this.mob.getZ()) : level.getNearestEntity(this.mob.level().getEntitiesOfClass(this.lookAtType, this.mob.getBoundingBox().inflate(this.lookDistance, 3.0, this.lookDistance), entity -> true), this.lookAtContext, this.mob, this.mob.getX(), this.mob.getEyeY(), this.mob.getZ()); } return this.panda.canPerformAction() && this.lookAt != null; } @Override public void tick() { if (this.lookAt != null) { super.tick(); } } } private static class PandaRollGoal extends Goal { private final Panda panda; public PandaRollGoal(Panda panda) { this.panda = panda; this.setFlags(EnumSet.of(Goal.Flag.MOVE, Goal.Flag.LOOK, Goal.Flag.JUMP)); } @Override public boolean canUse() { int zStep; if (!this.panda.isBaby() && !this.panda.isPlayful() || !this.panda.onGround()) { return false; } if (!this.panda.canPerformAction()) { return false; } float angle = this.panda.getYRot() * ((float)Math.PI / 180); float xDir = -Mth.sin(angle); float zDir = Mth.cos(angle); int xStep = (double)Math.abs(xDir) > 0.5 ? Mth.sign(xDir) : 0; int n = zStep = (double)Math.abs(zDir) > 0.5 ? Mth.sign(zDir) : 0; if (this.panda.level().getBlockState(this.panda.blockPosition().offset(xStep, -1, zStep)).isAir()) { return true; } if (this.panda.isPlayful() && this.panda.random.nextInt(PandaRollGoal.reducedTickDelay(60)) == 1) { return true; } return this.panda.random.nextInt(PandaRollGoal.reducedTickDelay(500)) == 1; } @Override public boolean canContinueToUse() { return false; } @Override public void start() { this.panda.roll(true); } @Override public boolean isInterruptable() { return false; } } private static class PandaHurtByTargetGoal extends HurtByTargetGoal { private final Panda panda; public PandaHurtByTargetGoal(Panda mob, Class ... ignoreDamageFromTheseTypes) { super(mob, ignoreDamageFromTheseTypes); this.panda = mob; } @Override public boolean canContinueToUse() { if (this.panda.gotBamboo || this.panda.didBite) { this.panda.setTarget(null); return false; } return super.canContinueToUse(); } @Override protected void alertOther(Mob other, LivingEntity hurtByMob) { if (other instanceof Panda && other.isAggressive()) { other.setTarget(hurtByMob); } } } }