2025-11-24 22:52:51 +03:00

326 lines
13 KiB
Java

/*
* 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<Boolean> DATA_CONVERTING_ID = SynchedEntityData.defineId(ZombieVillager.class, EntityDataSerializers.BOOLEAN);
private static final EntityDataAccessor<VillagerData> 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<? extends ZombieVillager> type, Level level) {
super((EntityType<? extends Zombie>)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 <T> @Nullable T get(DataComponentType<? extends T> 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 <T> boolean applyImplicitComponent(DataComponentType<T> type, T value) {
if (type == DataComponents.VILLAGER_VARIANT) {
Holder<VillagerType> variant = ZombieVillager.castComponentValue(DataComponents.VILLAGER_VARIANT, value);
this.setVillagerData(this.getVillagerData().withType(variant));
return true;
}
return super.applyImplicitComponent(type, value);
}
}