/* * Decompiled with CFR 0.152. * * Could not load the following classes: * com.google.common.annotations.VisibleForTesting * org.jspecify.annotations.Nullable */ package net.minecraft.world.entity.monster; import com.google.common.annotations.VisibleForTesting; import java.util.Optional; import java.util.UUID; import net.minecraft.advancements.CriteriaTriggers; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.UUIDUtil; import net.minecraft.core.component.DataComponentGetter; import net.minecraft.core.component.DataComponentType; import net.minecraft.core.component.DataComponents; import net.minecraft.core.registries.BuiltInRegistries; 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.server.level.ServerPlayer; import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundEvents; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.effect.MobEffectInstance; import net.minecraft.world.effect.MobEffects; import net.minecraft.world.entity.ConversionParams; import net.minecraft.world.entity.EntitySpawnReason; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.ReputationEventHandler; import net.minecraft.world.entity.SlotAccess; import net.minecraft.world.entity.ai.gossip.GossipContainer; import net.minecraft.world.entity.ai.village.ReputationEventType; import net.minecraft.world.entity.monster.Zombie; import net.minecraft.world.entity.npc.Villager; import net.minecraft.world.entity.npc.VillagerData; import net.minecraft.world.entity.npc.VillagerDataHolder; import net.minecraft.world.entity.npc.VillagerType; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.item.enchantment.EnchantmentEffectComponents; import net.minecraft.world.item.enchantment.EnchantmentHelper; import net.minecraft.world.item.trading.MerchantOffers; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.BedBlock; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.storage.ValueInput; import net.minecraft.world.level.storage.ValueOutput; import org.jspecify.annotations.Nullable; public class ZombieVillager extends Zombie implements VillagerDataHolder { private static final EntityDataAccessor DATA_CONVERTING_ID = SynchedEntityData.defineId(ZombieVillager.class, EntityDataSerializers.BOOLEAN); private static final EntityDataAccessor DATA_VILLAGER_DATA = SynchedEntityData.defineId(ZombieVillager.class, EntityDataSerializers.VILLAGER_DATA); private static final int VILLAGER_CONVERSION_WAIT_MIN = 3600; private static final int VILLAGER_CONVERSION_WAIT_MAX = 6000; private static final int MAX_SPECIAL_BLOCKS_COUNT = 14; private static final int SPECIAL_BLOCK_RADIUS = 4; private static final int NOT_CONVERTING = -1; private static final int DEFAULT_XP = 0; private int villagerConversionTime; private @Nullable UUID conversionStarter; private @Nullable GossipContainer gossips; private @Nullable MerchantOffers tradeOffers; private int villagerXp = 0; public ZombieVillager(EntityType type, Level level) { super((EntityType)type, level); } @Override protected void defineSynchedData(SynchedEntityData.Builder entityData) { super.defineSynchedData(entityData); entityData.define(DATA_CONVERTING_ID, false); entityData.define(DATA_VILLAGER_DATA, this.initializeVillagerData()); } @Override protected void addAdditionalSaveData(ValueOutput output) { super.addAdditionalSaveData(output); output.store("VillagerData", VillagerData.CODEC, this.getVillagerData()); output.storeNullable("Offers", MerchantOffers.CODEC, this.tradeOffers); output.storeNullable("Gossips", GossipContainer.CODEC, this.gossips); output.putInt("ConversionTime", this.isConverting() ? this.villagerConversionTime : -1); output.storeNullable("ConversionPlayer", UUIDUtil.CODEC, this.conversionStarter); output.putInt("Xp", this.villagerXp); } @Override protected void readAdditionalSaveData(ValueInput input) { super.readAdditionalSaveData(input); this.entityData.set(DATA_VILLAGER_DATA, input.read("VillagerData", VillagerData.CODEC).orElseGet(this::initializeVillagerData)); this.tradeOffers = input.read("Offers", MerchantOffers.CODEC).orElse(null); this.gossips = input.read("Gossips", GossipContainer.CODEC).orElse(null); int conversionTime = input.getIntOr("ConversionTime", -1); if (conversionTime != -1) { UUID conversionStarter = input.read("ConversionPlayer", UUIDUtil.CODEC).orElse(null); this.startConverting(conversionStarter, conversionTime); } else { this.getEntityData().set(DATA_CONVERTING_ID, false); this.villagerConversionTime = -1; } this.villagerXp = input.getIntOr("Xp", 0); } private VillagerData initializeVillagerData() { Level level = this.level(); Optional profession = BuiltInRegistries.VILLAGER_PROFESSION.getRandom(this.random); VillagerData villagerData = Villager.createDefaultVillagerData().withType(level.registryAccess(), VillagerType.byBiome(level.getBiome(this.blockPosition()))); if (profession.isPresent()) { villagerData = villagerData.withProfession(profession.get()); } return villagerData; } @Override public void tick() { if (!this.level().isClientSide() && this.isAlive() && this.isConverting()) { int amount = this.getConversionProgress(); this.villagerConversionTime -= amount; if (this.villagerConversionTime <= 0) { this.finishConversion((ServerLevel)this.level()); } } super.tick(); } @Override public InteractionResult mobInteract(Player player, InteractionHand hand) { ItemStack itemStack = player.getItemInHand(hand); if (itemStack.is(Items.GOLDEN_APPLE)) { if (this.hasEffect(MobEffects.WEAKNESS)) { itemStack.consume(1, player); if (!this.level().isClientSide()) { this.startConverting(player.getUUID(), this.random.nextInt(2401) + 3600); } return InteractionResult.SUCCESS_SERVER; } return InteractionResult.CONSUME; } return super.mobInteract(player, hand); } @Override protected boolean convertsInWater() { return false; } @Override public boolean removeWhenFarAway(double distSqr) { return !this.isConverting() && this.villagerXp == 0; } public boolean isConverting() { return this.getEntityData().get(DATA_CONVERTING_ID); } private void startConverting(@Nullable UUID player, int time) { this.conversionStarter = player; this.villagerConversionTime = time; this.getEntityData().set(DATA_CONVERTING_ID, true); this.removeEffect(MobEffects.WEAKNESS); this.addEffect(new MobEffectInstance(MobEffects.STRENGTH, time, Math.min(this.level().getDifficulty().getId() - 1, 0))); this.level().broadcastEntityEvent(this, (byte)16); } @Override public void handleEntityEvent(byte id) { if (id == 16) { if (!this.isSilent()) { this.level().playLocalSound(this.getX(), this.getEyeY(), this.getZ(), SoundEvents.ZOMBIE_VILLAGER_CURE, this.getSoundSource(), 1.0f + this.random.nextFloat(), this.random.nextFloat() * 0.7f + 0.3f, false); } return; } super.handleEntityEvent(id); } private void finishConversion(ServerLevel level) { this.convertTo(EntityType.VILLAGER, ConversionParams.single(this, false, false), villager -> { Player player; for (EquipmentSlot undroppedSlot : this.dropPreservedEquipment(level, stack -> !EnchantmentHelper.has(stack, EnchantmentEffectComponents.PREVENT_ARMOR_CHANGE))) { SlotAccess offsetSlot = villager.getSlot(undroppedSlot.getIndex() + 300); if (offsetSlot == null) continue; offsetSlot.set(this.getItemBySlot(undroppedSlot)); } villager.setVillagerData(this.getVillagerData()); if (this.gossips != null) { villager.setGossips(this.gossips); } if (this.tradeOffers != null) { villager.setOffers(this.tradeOffers.copy()); } villager.setVillagerXp(this.villagerXp); villager.finalizeSpawn(level, level.getCurrentDifficultyAt(villager.blockPosition()), EntitySpawnReason.CONVERSION, null); villager.refreshBrain(level); if (this.conversionStarter != null && (player = level.getPlayerByUUID(this.conversionStarter)) instanceof ServerPlayer) { CriteriaTriggers.CURED_ZOMBIE_VILLAGER.trigger((ServerPlayer)player, this, (Villager)villager); level.onReputationEvent(ReputationEventType.ZOMBIE_VILLAGER_CURED, player, (ReputationEventHandler)((Object)villager)); } villager.addEffect(new MobEffectInstance(MobEffects.NAUSEA, 200, 0)); if (!this.isSilent()) { level.levelEvent(null, 1027, this.blockPosition(), 0); } }); } @VisibleForTesting public void setVillagerConversionTime(int conversionTime) { this.villagerConversionTime = conversionTime; } private int getConversionProgress() { int amount = 1; if (this.random.nextFloat() < 0.01f) { int specialBlocksCount = 0; BlockPos.MutableBlockPos blockPos = new BlockPos.MutableBlockPos(); for (int xx = (int)this.getX() - 4; xx < (int)this.getX() + 4 && specialBlocksCount < 14; ++xx) { for (int yy = (int)this.getY() - 4; yy < (int)this.getY() + 4 && specialBlocksCount < 14; ++yy) { for (int zz = (int)this.getZ() - 4; zz < (int)this.getZ() + 4 && specialBlocksCount < 14; ++zz) { BlockState state = this.level().getBlockState(blockPos.set(xx, yy, zz)); if (!state.is(Blocks.IRON_BARS) && !(state.getBlock() instanceof BedBlock)) continue; if (this.random.nextFloat() < 0.3f) { ++amount; } ++specialBlocksCount; } } } } return amount; } @Override public float getVoicePitch() { if (this.isBaby()) { return (this.random.nextFloat() - this.random.nextFloat()) * 0.2f + 2.0f; } return (this.random.nextFloat() - this.random.nextFloat()) * 0.2f + 1.0f; } @Override public SoundEvent getAmbientSound() { return SoundEvents.ZOMBIE_VILLAGER_AMBIENT; } @Override public SoundEvent getHurtSound(DamageSource source) { return SoundEvents.ZOMBIE_VILLAGER_HURT; } @Override public SoundEvent getDeathSound() { return SoundEvents.ZOMBIE_VILLAGER_DEATH; } @Override public SoundEvent getStepSound() { return SoundEvents.ZOMBIE_VILLAGER_STEP; } public void setTradeOffers(MerchantOffers tradeOffers) { this.tradeOffers = tradeOffers; } public void setGossips(GossipContainer gossips) { this.gossips = gossips; } @Override public void setVillagerData(VillagerData villagerData) { VillagerData currentData = this.getVillagerData(); if (!currentData.profession().equals(villagerData.profession())) { this.tradeOffers = null; } this.entityData.set(DATA_VILLAGER_DATA, villagerData); } @Override public VillagerData getVillagerData() { return this.entityData.get(DATA_VILLAGER_DATA); } public int getVillagerXp() { return this.villagerXp; } public void setVillagerXp(int villagerXp) { this.villagerXp = villagerXp; } @Override public @Nullable T get(DataComponentType type) { if (type == DataComponents.VILLAGER_VARIANT) { return ZombieVillager.castComponentValue(type, this.getVillagerData().type()); } return super.get(type); } @Override protected void applyImplicitComponents(DataComponentGetter components) { this.applyImplicitComponentIfPresent(components, DataComponents.VILLAGER_VARIANT); super.applyImplicitComponents(components); } @Override protected boolean applyImplicitComponent(DataComponentType type, T value) { if (type == DataComponents.VILLAGER_VARIANT) { Holder variant = ZombieVillager.castComponentValue(DataComponents.VILLAGER_VARIANT, value); this.setVillagerData(this.getVillagerData().withType(variant)); return true; } return super.applyImplicitComponent(type, value); } }