367 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			367 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| 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.DifficultyInstance;
 | |
| 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.SlotAccess;
 | |
| import net.minecraft.world.entity.SpawnGroupData;
 | |
| import net.minecraft.world.entity.ai.gossip.GossipContainer;
 | |
| import net.minecraft.world.entity.ai.village.ReputationEventType;
 | |
| 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.VillagerProfession;
 | |
| 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.ServerLevelAccessor;
 | |
| 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.jetbrains.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;
 | |
| 	@Nullable
 | |
| 	private UUID conversionStarter;
 | |
| 	@Nullable
 | |
| 	private GossipContainer gossips;
 | |
| 	@Nullable
 | |
| 	private MerchantOffers tradeOffers;
 | |
| 	private int villagerXp = 0;
 | |
| 
 | |
| 	public ZombieVillager(EntityType<? extends ZombieVillager> entityType, Level level) {
 | |
| 		super(entityType, level);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected void defineSynchedData(SynchedEntityData.Builder builder) {
 | |
| 		super.defineSynchedData(builder);
 | |
| 		builder.define(DATA_CONVERTING_ID, false);
 | |
| 		builder.define(DATA_VILLAGER_DATA, Villager.createDefaultVillagerData());
 | |
| 	}
 | |
| 
 | |
| 	@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, (VillagerData)input.read("VillagerData", VillagerData.CODEC).orElseGet(Villager::createDefaultVillagerData));
 | |
| 		this.tradeOffers = (MerchantOffers)input.read("Offers", MerchantOffers.CODEC).orElse(null);
 | |
| 		this.gossips = (GossipContainer)input.read("Gossips", GossipContainer.CODEC).orElse(null);
 | |
| 		int i = input.getIntOr("ConversionTime", -1);
 | |
| 		if (i != -1) {
 | |
| 			UUID uUID = (UUID)input.read("ConversionPlayer", UUIDUtil.CODEC).orElse(null);
 | |
| 			this.startConverting(uUID, i);
 | |
| 		} else {
 | |
| 			this.getEntityData().set(DATA_CONVERTING_ID, false);
 | |
| 			this.villagerConversionTime = -1;
 | |
| 		}
 | |
| 
 | |
| 		this.villagerXp = input.getIntOr("Xp", 0);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void tick() {
 | |
| 		if (!this.level().isClientSide && this.isAlive() && this.isConverting()) {
 | |
| 			int i = this.getConversionProgress();
 | |
| 			this.villagerConversionTime -= i;
 | |
| 			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;
 | |
| 			} else {
 | |
| 				return InteractionResult.CONSUME;
 | |
| 			}
 | |
| 		} else {
 | |
| 			return super.mobInteract(player, hand);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected boolean convertsInWater() {
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public boolean removeWhenFarAway(double distanceToClosestPlayer) {
 | |
| 		return !this.isConverting() && this.villagerXp == 0;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Returns whether this zombie is in the process of converting to a villager
 | |
| 	 */
 | |
| 	public boolean isConverting() {
 | |
| 		return this.getEntityData().get(DATA_CONVERTING_ID);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Starts conversion of this zombie villager to a villager
 | |
| 	 */
 | |
| 	private void startConverting(@Nullable UUID conversionStarter, int villagerConversionTime) {
 | |
| 		this.conversionStarter = conversionStarter;
 | |
| 		this.villagerConversionTime = villagerConversionTime;
 | |
| 		this.getEntityData().set(DATA_CONVERTING_ID, true);
 | |
| 		this.removeEffect(MobEffects.WEAKNESS);
 | |
| 		this.addEffect(new MobEffectInstance(MobEffects.STRENGTH, villagerConversionTime, 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
 | |
| 					);
 | |
| 			}
 | |
| 		} else {
 | |
| 			super.handleEntityEvent(id);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private void finishConversion(ServerLevel level) {
 | |
| 		this.convertTo(
 | |
| 			EntityType.VILLAGER,
 | |
| 			ConversionParams.single(this, false, false),
 | |
| 			villager -> {
 | |
| 				for (EquipmentSlot equipmentSlot : this.dropPreservedEquipment(
 | |
| 					level, itemStack -> !EnchantmentHelper.has(itemStack, EnchantmentEffectComponents.PREVENT_ARMOR_CHANGE)
 | |
| 				)) {
 | |
| 					SlotAccess slotAccess = villager.getSlot(equipmentSlot.getIndex() + 300);
 | |
| 					slotAccess.set(this.getItemBySlot(equipmentSlot));
 | |
| 				}
 | |
| 
 | |
| 				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 player = level.getPlayerByUUID(this.conversionStarter);
 | |
| 					if (player instanceof ServerPlayer) {
 | |
| 						CriteriaTriggers.CURED_ZOMBIE_VILLAGER.trigger((ServerPlayer)player, this, villager);
 | |
| 						level.onReputationEvent(ReputationEventType.ZOMBIE_VILLAGER_CURED, player, villager);
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				villager.addEffect(new MobEffectInstance(MobEffects.NAUSEA, 200, 0));
 | |
| 				if (!this.isSilent()) {
 | |
| 					level.levelEvent(null, 1027, this.blockPosition(), 0);
 | |
| 				}
 | |
| 			}
 | |
| 		);
 | |
| 	}
 | |
| 
 | |
| 	@VisibleForTesting
 | |
| 	public void setVillagerConversionTime(int villagerConversionTime) {
 | |
| 		this.villagerConversionTime = villagerConversionTime;
 | |
| 	}
 | |
| 
 | |
| 	private int getConversionProgress() {
 | |
| 		int i = 1;
 | |
| 		if (this.random.nextFloat() < 0.01F) {
 | |
| 			int j = 0;
 | |
| 			BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
 | |
| 
 | |
| 			for (int k = (int)this.getX() - 4; k < (int)this.getX() + 4 && j < 14; k++) {
 | |
| 				for (int l = (int)this.getY() - 4; l < (int)this.getY() + 4 && j < 14; l++) {
 | |
| 					for (int m = (int)this.getZ() - 4; m < (int)this.getZ() + 4 && j < 14; m++) {
 | |
| 						BlockState blockState = this.level().getBlockState(mutableBlockPos.set(k, l, m));
 | |
| 						if (blockState.is(Blocks.IRON_BARS) || blockState.getBlock() instanceof BedBlock) {
 | |
| 							if (this.random.nextFloat() < 0.3F) {
 | |
| 								i++;
 | |
| 							}
 | |
| 
 | |
| 							j++;
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return i;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public float getVoicePitch() {
 | |
| 		return this.isBaby() ? (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 2.0F : (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public SoundEvent getAmbientSound() {
 | |
| 		return SoundEvents.ZOMBIE_VILLAGER_AMBIENT;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public SoundEvent getHurtSound(DamageSource damageSource) {
 | |
| 		return SoundEvents.ZOMBIE_VILLAGER_HURT;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public SoundEvent getDeathSound() {
 | |
| 		return SoundEvents.ZOMBIE_VILLAGER_DEATH;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public SoundEvent getStepSound() {
 | |
| 		return SoundEvents.ZOMBIE_VILLAGER_STEP;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected ItemStack getSkull() {
 | |
| 		return ItemStack.EMPTY;
 | |
| 	}
 | |
| 
 | |
| 	public void setTradeOffers(MerchantOffers tradeOffers) {
 | |
| 		this.tradeOffers = tradeOffers;
 | |
| 	}
 | |
| 
 | |
| 	public void setGossips(GossipContainer gossips) {
 | |
| 		this.gossips = gossips;
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	@Override
 | |
| 	public SpawnGroupData finalizeSpawn(
 | |
| 		ServerLevelAccessor level, DifficultyInstance difficulty, EntitySpawnReason spawnReason, @Nullable SpawnGroupData spawnGroupData
 | |
| 	) {
 | |
| 		VillagerData villagerData = this.getVillagerData().withType(level.registryAccess(), VillagerType.byBiome(level.getBiome(this.blockPosition())));
 | |
| 		Optional<Holder.Reference<VillagerProfession>> optional = BuiltInRegistries.VILLAGER_PROFESSION.getRandom(this.random);
 | |
| 		if (optional.isPresent()) {
 | |
| 			villagerData = villagerData.withProfession((Holder<VillagerProfession>)optional.get());
 | |
| 		}
 | |
| 
 | |
| 		this.setVillagerData(villagerData);
 | |
| 		return super.finalizeSpawn(level, difficulty, spawnReason, spawnGroupData);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void setVillagerData(VillagerData data) {
 | |
| 		VillagerData villagerData = this.getVillagerData();
 | |
| 		if (!villagerData.profession().equals(data.profession())) {
 | |
| 			this.tradeOffers = null;
 | |
| 		}
 | |
| 
 | |
| 		this.entityData.set(DATA_VILLAGER_DATA, data);
 | |
| 	}
 | |
| 
 | |
| 	@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;
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	@Override
 | |
| 	public <T> T get(DataComponentType<? extends T> component) {
 | |
| 		return component == DataComponents.VILLAGER_VARIANT
 | |
| 			? castComponentValue((DataComponentType<T>)component, this.getVillagerData().type())
 | |
| 			: super.get(component);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected void applyImplicitComponents(DataComponentGetter componentGetter) {
 | |
| 		this.applyImplicitComponentIfPresent(componentGetter, DataComponents.VILLAGER_VARIANT);
 | |
| 		super.applyImplicitComponents(componentGetter);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected <T> boolean applyImplicitComponent(DataComponentType<T> component, T value) {
 | |
| 		if (component == DataComponents.VILLAGER_VARIANT) {
 | |
| 			Holder<VillagerType> holder = castComponentValue(DataComponents.VILLAGER_VARIANT, value);
 | |
| 			this.setVillagerData(this.getVillagerData().withType(holder));
 | |
| 			return true;
 | |
| 		} else {
 | |
| 			return super.applyImplicitComponent(component, value);
 | |
| 		}
 | |
| 	}
 | |
| }
 |