978 lines
		
	
	
	
		
			34 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			978 lines
		
	
	
	
		
			34 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| package net.minecraft.world.entity.npc;
 | |
| 
 | |
| import com.google.common.annotations.VisibleForTesting;
 | |
| import com.google.common.collect.ImmutableList;
 | |
| import com.google.common.collect.ImmutableMap;
 | |
| import com.google.common.collect.ImmutableSet;
 | |
| import com.mojang.datafixers.util.Pair;
 | |
| import com.mojang.logging.LogUtils;
 | |
| import com.mojang.serialization.Dynamic;
 | |
| import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
 | |
| import java.util.List;
 | |
| import java.util.Map;
 | |
| import java.util.Optional;
 | |
| import java.util.function.BiPredicate;
 | |
| import net.minecraft.core.BlockPos;
 | |
| import net.minecraft.core.GlobalPos;
 | |
| import net.minecraft.core.Holder;
 | |
| import net.minecraft.core.component.DataComponentGetter;
 | |
| import net.minecraft.core.component.DataComponentType;
 | |
| import net.minecraft.core.component.DataComponents;
 | |
| import net.minecraft.core.particles.ParticleTypes;
 | |
| import net.minecraft.core.registries.BuiltInRegistries;
 | |
| import net.minecraft.network.chat.Component;
 | |
| import net.minecraft.network.protocol.game.DebugPackets;
 | |
| import net.minecraft.network.syncher.EntityDataAccessor;
 | |
| import net.minecraft.network.syncher.EntityDataSerializers;
 | |
| import net.minecraft.network.syncher.SynchedEntityData;
 | |
| import net.minecraft.resources.ResourceKey;
 | |
| import net.minecraft.server.MinecraftServer;
 | |
| import net.minecraft.server.level.ServerLevel;
 | |
| import net.minecraft.sounds.SoundEvent;
 | |
| import net.minecraft.sounds.SoundEvents;
 | |
| import net.minecraft.stats.Stats;
 | |
| import net.minecraft.tags.ItemTags;
 | |
| import net.minecraft.util.Mth;
 | |
| import net.minecraft.util.SpawnUtil;
 | |
| import net.minecraft.util.profiling.Profiler;
 | |
| import net.minecraft.util.profiling.ProfilerFiller;
 | |
| import net.minecraft.world.Difficulty;
 | |
| import net.minecraft.world.DifficultyInstance;
 | |
| import net.minecraft.world.InteractionHand;
 | |
| import net.minecraft.world.InteractionResult;
 | |
| import net.minecraft.world.SimpleContainer;
 | |
| import net.minecraft.world.damagesource.DamageSource;
 | |
| import net.minecraft.world.effect.MobEffectInstance;
 | |
| import net.minecraft.world.effect.MobEffects;
 | |
| import net.minecraft.world.entity.AgeableMob;
 | |
| import net.minecraft.world.entity.ConversionParams;
 | |
| import net.minecraft.world.entity.Entity;
 | |
| import net.minecraft.world.entity.EntitySpawnReason;
 | |
| import net.minecraft.world.entity.EntityType;
 | |
| import net.minecraft.world.entity.ExperienceOrb;
 | |
| import net.minecraft.world.entity.LightningBolt;
 | |
| import net.minecraft.world.entity.LivingEntity;
 | |
| import net.minecraft.world.entity.Mob;
 | |
| import net.minecraft.world.entity.ReputationEventHandler;
 | |
| import net.minecraft.world.entity.SpawnGroupData;
 | |
| import net.minecraft.world.entity.ai.Brain;
 | |
| import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
 | |
| import net.minecraft.world.entity.ai.attributes.Attributes;
 | |
| import net.minecraft.world.entity.ai.behavior.VillagerGoalPackages;
 | |
| import net.minecraft.world.entity.ai.gossip.GossipContainer;
 | |
| import net.minecraft.world.entity.ai.gossip.GossipType;
 | |
| import net.minecraft.world.entity.ai.memory.MemoryModuleType;
 | |
| import net.minecraft.world.entity.ai.memory.MemoryStatus;
 | |
| import net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities;
 | |
| import net.minecraft.world.entity.ai.navigation.GroundPathNavigation;
 | |
| import net.minecraft.world.entity.ai.sensing.GolemSensor;
 | |
| import net.minecraft.world.entity.ai.sensing.Sensor;
 | |
| import net.minecraft.world.entity.ai.sensing.SensorType;
 | |
| import net.minecraft.world.entity.ai.village.ReputationEventType;
 | |
| import net.minecraft.world.entity.ai.village.poi.PoiManager;
 | |
| import net.minecraft.world.entity.ai.village.poi.PoiType;
 | |
| import net.minecraft.world.entity.ai.village.poi.PoiTypes;
 | |
| import net.minecraft.world.entity.item.ItemEntity;
 | |
| import net.minecraft.world.entity.monster.Witch;
 | |
| import net.minecraft.world.entity.player.Player;
 | |
| import net.minecraft.world.entity.raid.Raid;
 | |
| import net.minecraft.world.entity.schedule.Activity;
 | |
| import net.minecraft.world.entity.schedule.Schedule;
 | |
| import net.minecraft.world.flag.FeatureFlags;
 | |
| import net.minecraft.world.item.Item;
 | |
| import net.minecraft.world.item.ItemStack;
 | |
| import net.minecraft.world.item.Items;
 | |
| import net.minecraft.world.item.trading.MerchantOffer;
 | |
| import net.minecraft.world.item.trading.MerchantOffers;
 | |
| import net.minecraft.world.level.Level;
 | |
| import net.minecraft.world.level.ServerLevelAccessor;
 | |
| import net.minecraft.world.level.storage.ValueInput;
 | |
| import net.minecraft.world.level.storage.ValueOutput;
 | |
| import net.minecraft.world.phys.AABB;
 | |
| import org.jetbrains.annotations.Nullable;
 | |
| import org.slf4j.Logger;
 | |
| 
 | |
| public class Villager extends AbstractVillager implements ReputationEventHandler, VillagerDataHolder {
 | |
| 	private static final Logger LOGGER = LogUtils.getLogger();
 | |
| 	private static final EntityDataAccessor<VillagerData> DATA_VILLAGER_DATA = SynchedEntityData.defineId(Villager.class, EntityDataSerializers.VILLAGER_DATA);
 | |
| 	public static final int BREEDING_FOOD_THRESHOLD = 12;
 | |
| 	/**
 | |
| 	 * Mapping between valid food items and their respective efficiency values.
 | |
| 	 */
 | |
| 	public static final Map<Item, Integer> FOOD_POINTS = ImmutableMap.of(Items.BREAD, 4, Items.POTATO, 1, Items.CARROT, 1, Items.BEETROOT, 1);
 | |
| 	private static final int TRADES_PER_LEVEL = 2;
 | |
| 	private static final int MAX_GOSSIP_TOPICS = 10;
 | |
| 	private static final int GOSSIP_COOLDOWN = 1200;
 | |
| 	private static final int GOSSIP_DECAY_INTERVAL = 24000;
 | |
| 	private static final int HOW_FAR_AWAY_TO_TALK_TO_OTHER_VILLAGERS_ABOUT_GOLEMS = 10;
 | |
| 	private static final int HOW_MANY_VILLAGERS_NEED_TO_AGREE_TO_SPAWN_A_GOLEM = 5;
 | |
| 	private static final long TIME_SINCE_SLEEPING_FOR_GOLEM_SPAWNING = 24000L;
 | |
| 	@VisibleForTesting
 | |
| 	public static final float SPEED_MODIFIER = 0.5F;
 | |
| 	private static final int DEFAULT_XP = 0;
 | |
| 	private static final byte DEFAULT_FOOD_LEVEL = 0;
 | |
| 	private static final int DEFAULT_LAST_RESTOCK = 0;
 | |
| 	private static final int DEFAULT_LAST_GOSSIP_DECAY = 0;
 | |
| 	private static final int DEFAULT_RESTOCKS_TODAY = 0;
 | |
| 	private static final boolean DEFAULT_ASSIGN_PROFESSION_WHEN_SPAWNED = false;
 | |
| 	private int updateMerchantTimer;
 | |
| 	private boolean increaseProfessionLevelOnUpdate;
 | |
| 	@Nullable
 | |
| 	private Player lastTradedPlayer;
 | |
| 	private boolean chasing;
 | |
| 	private int foodLevel = 0;
 | |
| 	private final GossipContainer gossips = new GossipContainer();
 | |
| 	private long lastGossipTime;
 | |
| 	private long lastGossipDecayTime = 0L;
 | |
| 	private int villagerXp = 0;
 | |
| 	private long lastRestockGameTime = 0L;
 | |
| 	private int numberOfRestocksToday = 0;
 | |
| 	private long lastRestockCheckDayTime;
 | |
| 	private boolean assignProfessionWhenSpawned = false;
 | |
| 	private static final ImmutableList<MemoryModuleType<?>> MEMORY_TYPES = ImmutableList.of(
 | |
| 		MemoryModuleType.HOME,
 | |
| 		MemoryModuleType.JOB_SITE,
 | |
| 		MemoryModuleType.POTENTIAL_JOB_SITE,
 | |
| 		MemoryModuleType.MEETING_POINT,
 | |
| 		MemoryModuleType.NEAREST_LIVING_ENTITIES,
 | |
| 		MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES,
 | |
| 		MemoryModuleType.VISIBLE_VILLAGER_BABIES,
 | |
| 		MemoryModuleType.NEAREST_PLAYERS,
 | |
| 		MemoryModuleType.NEAREST_VISIBLE_PLAYER,
 | |
| 		MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER,
 | |
| 		MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM,
 | |
| 		MemoryModuleType.ITEM_PICKUP_COOLDOWN_TICKS,
 | |
| 		MemoryModuleType.WALK_TARGET,
 | |
| 		MemoryModuleType.LOOK_TARGET,
 | |
| 		MemoryModuleType.INTERACTION_TARGET,
 | |
| 		MemoryModuleType.BREED_TARGET,
 | |
| 		MemoryModuleType.PATH,
 | |
| 		MemoryModuleType.DOORS_TO_CLOSE,
 | |
| 		MemoryModuleType.NEAREST_BED,
 | |
| 		MemoryModuleType.HURT_BY,
 | |
| 		MemoryModuleType.HURT_BY_ENTITY,
 | |
| 		MemoryModuleType.NEAREST_HOSTILE,
 | |
| 		MemoryModuleType.SECONDARY_JOB_SITE,
 | |
| 		MemoryModuleType.HIDING_PLACE,
 | |
| 		MemoryModuleType.HEARD_BELL_TIME,
 | |
| 		MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE,
 | |
| 		MemoryModuleType.LAST_SLEPT,
 | |
| 		MemoryModuleType.LAST_WOKEN,
 | |
| 		MemoryModuleType.LAST_WORKED_AT_POI,
 | |
| 		MemoryModuleType.GOLEM_DETECTED_RECENTLY
 | |
| 	);
 | |
| 	private static final ImmutableList<SensorType<? extends Sensor<? super Villager>>> SENSOR_TYPES = ImmutableList.of(
 | |
| 		SensorType.NEAREST_LIVING_ENTITIES,
 | |
| 		SensorType.NEAREST_PLAYERS,
 | |
| 		SensorType.NEAREST_ITEMS,
 | |
| 		SensorType.NEAREST_BED,
 | |
| 		SensorType.HURT_BY,
 | |
| 		SensorType.VILLAGER_HOSTILES,
 | |
| 		SensorType.VILLAGER_BABIES,
 | |
| 		SensorType.SECONDARY_POIS,
 | |
| 		SensorType.GOLEM_DETECTED
 | |
| 	);
 | |
| 	public static final Map<MemoryModuleType<GlobalPos>, BiPredicate<Villager, Holder<PoiType>>> POI_MEMORIES = ImmutableMap.of(
 | |
| 		MemoryModuleType.HOME,
 | |
| 		(villager, holder) -> holder.is(PoiTypes.HOME),
 | |
| 		MemoryModuleType.JOB_SITE,
 | |
| 		(villager, holder) -> villager.getVillagerData().profession().value().heldJobSite().test(holder),
 | |
| 		MemoryModuleType.POTENTIAL_JOB_SITE,
 | |
| 		(villager, holder) -> VillagerProfession.ALL_ACQUIRABLE_JOBS.test(holder),
 | |
| 		MemoryModuleType.MEETING_POINT,
 | |
| 		(villager, holder) -> holder.is(PoiTypes.MEETING)
 | |
| 	);
 | |
| 
 | |
| 	public Villager(EntityType<? extends Villager> entityType, Level level) {
 | |
| 		this(entityType, level, VillagerType.PLAINS);
 | |
| 	}
 | |
| 
 | |
| 	public Villager(EntityType<? extends Villager> entityType, Level level, ResourceKey<VillagerType> villagerType) {
 | |
| 		this(entityType, level, level.registryAccess().getOrThrow(villagerType));
 | |
| 	}
 | |
| 
 | |
| 	public Villager(EntityType<? extends Villager> entityType, Level level, Holder<VillagerType> villagerType) {
 | |
| 		super(entityType, level);
 | |
| 		((GroundPathNavigation)this.getNavigation()).setCanOpenDoors(true);
 | |
| 		this.getNavigation().setCanFloat(true);
 | |
| 		this.getNavigation().setRequiredPathLength(48.0F);
 | |
| 		this.setCanPickUpLoot(true);
 | |
| 		this.setVillagerData(this.getVillagerData().withType(villagerType).withProfession(level.registryAccess(), VillagerProfession.NONE));
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public Brain<Villager> getBrain() {
 | |
| 		return (Brain<Villager>)super.getBrain();
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected Brain.Provider<Villager> brainProvider() {
 | |
| 		return Brain.provider(MEMORY_TYPES, SENSOR_TYPES);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected Brain<?> makeBrain(Dynamic<?> dynamic) {
 | |
| 		Brain<Villager> brain = this.brainProvider().makeBrain(dynamic);
 | |
| 		this.registerBrainGoals(brain);
 | |
| 		return brain;
 | |
| 	}
 | |
| 
 | |
| 	public void refreshBrain(ServerLevel serverLevel) {
 | |
| 		Brain<Villager> brain = this.getBrain();
 | |
| 		brain.stopAll(serverLevel, this);
 | |
| 		this.brain = brain.copyWithoutBehaviors();
 | |
| 		this.registerBrainGoals(this.getBrain());
 | |
| 	}
 | |
| 
 | |
| 	private void registerBrainGoals(Brain<Villager> villagerBrain) {
 | |
| 		Holder<VillagerProfession> holder = this.getVillagerData().profession();
 | |
| 		if (this.isBaby()) {
 | |
| 			villagerBrain.setSchedule(Schedule.VILLAGER_BABY);
 | |
| 			villagerBrain.addActivity(Activity.PLAY, VillagerGoalPackages.getPlayPackage(0.5F));
 | |
| 		} else {
 | |
| 			villagerBrain.setSchedule(Schedule.VILLAGER_DEFAULT);
 | |
| 			villagerBrain.addActivityWithConditions(
 | |
| 				Activity.WORK, VillagerGoalPackages.getWorkPackage(holder, 0.5F), ImmutableSet.of(Pair.of(MemoryModuleType.JOB_SITE, MemoryStatus.VALUE_PRESENT))
 | |
| 			);
 | |
| 		}
 | |
| 
 | |
| 		villagerBrain.addActivity(Activity.CORE, VillagerGoalPackages.getCorePackage(holder, 0.5F));
 | |
| 		villagerBrain.addActivityWithConditions(
 | |
| 			Activity.MEET, VillagerGoalPackages.getMeetPackage(holder, 0.5F), ImmutableSet.of(Pair.of(MemoryModuleType.MEETING_POINT, MemoryStatus.VALUE_PRESENT))
 | |
| 		);
 | |
| 		villagerBrain.addActivity(Activity.REST, VillagerGoalPackages.getRestPackage(holder, 0.5F));
 | |
| 		villagerBrain.addActivity(Activity.IDLE, VillagerGoalPackages.getIdlePackage(holder, 0.5F));
 | |
| 		villagerBrain.addActivity(Activity.PANIC, VillagerGoalPackages.getPanicPackage(holder, 0.5F));
 | |
| 		villagerBrain.addActivity(Activity.PRE_RAID, VillagerGoalPackages.getPreRaidPackage(holder, 0.5F));
 | |
| 		villagerBrain.addActivity(Activity.RAID, VillagerGoalPackages.getRaidPackage(holder, 0.5F));
 | |
| 		villagerBrain.addActivity(Activity.HIDE, VillagerGoalPackages.getHidePackage(holder, 0.5F));
 | |
| 		villagerBrain.setCoreActivities(ImmutableSet.of(Activity.CORE));
 | |
| 		villagerBrain.setDefaultActivity(Activity.IDLE);
 | |
| 		villagerBrain.setActiveActivityIfPossible(Activity.IDLE);
 | |
| 		villagerBrain.updateActivityFromSchedule(this.level().getDayTime(), this.level().getGameTime());
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected void ageBoundaryReached() {
 | |
| 		super.ageBoundaryReached();
 | |
| 		if (this.level() instanceof ServerLevel) {
 | |
| 			this.refreshBrain((ServerLevel)this.level());
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public static AttributeSupplier.Builder createAttributes() {
 | |
| 		return Mob.createMobAttributes().add(Attributes.MOVEMENT_SPEED, 0.5);
 | |
| 	}
 | |
| 
 | |
| 	public boolean assignProfessionWhenSpawned() {
 | |
| 		return this.assignProfessionWhenSpawned;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected void customServerAiStep(ServerLevel level) {
 | |
| 		ProfilerFiller profilerFiller = Profiler.get();
 | |
| 		profilerFiller.push("villagerBrain");
 | |
| 		this.getBrain().tick(level, this);
 | |
| 		profilerFiller.pop();
 | |
| 		if (this.assignProfessionWhenSpawned) {
 | |
| 			this.assignProfessionWhenSpawned = false;
 | |
| 		}
 | |
| 
 | |
| 		if (!this.isTrading() && this.updateMerchantTimer > 0) {
 | |
| 			this.updateMerchantTimer--;
 | |
| 			if (this.updateMerchantTimer <= 0) {
 | |
| 				if (this.increaseProfessionLevelOnUpdate) {
 | |
| 					this.increaseMerchantCareer();
 | |
| 					this.increaseProfessionLevelOnUpdate = false;
 | |
| 				}
 | |
| 
 | |
| 				this.addEffect(new MobEffectInstance(MobEffects.REGENERATION, 200, 0));
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if (this.lastTradedPlayer != null) {
 | |
| 			level.onReputationEvent(ReputationEventType.TRADE, this.lastTradedPlayer, this);
 | |
| 			level.broadcastEntityEvent(this, (byte)14);
 | |
| 			this.lastTradedPlayer = null;
 | |
| 		}
 | |
| 
 | |
| 		if (!this.isNoAi() && this.random.nextInt(100) == 0) {
 | |
| 			Raid raid = level.getRaidAt(this.blockPosition());
 | |
| 			if (raid != null && raid.isActive() && !raid.isOver()) {
 | |
| 				level.broadcastEntityEvent(this, (byte)42);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if (this.getVillagerData().profession().is(VillagerProfession.NONE) && this.isTrading()) {
 | |
| 			this.stopTrading();
 | |
| 		}
 | |
| 
 | |
| 		super.customServerAiStep(level);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void tick() {
 | |
| 		super.tick();
 | |
| 		if (this.getUnhappyCounter() > 0) {
 | |
| 			this.setUnhappyCounter(this.getUnhappyCounter() - 1);
 | |
| 		}
 | |
| 
 | |
| 		this.maybeDecayGossip();
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public InteractionResult mobInteract(Player player, InteractionHand hand) {
 | |
| 		ItemStack itemStack = player.getItemInHand(hand);
 | |
| 		if (itemStack.is(Items.VILLAGER_SPAWN_EGG) || !this.isAlive() || this.isTrading() || this.isSleeping()) {
 | |
| 			return super.mobInteract(player, hand);
 | |
| 		} else if (this.isBaby()) {
 | |
| 			this.setUnhappy();
 | |
| 			return InteractionResult.SUCCESS;
 | |
| 		} else {
 | |
| 			if (!this.level().isClientSide) {
 | |
| 				boolean bl = this.getOffers().isEmpty();
 | |
| 				if (hand == InteractionHand.MAIN_HAND) {
 | |
| 					if (bl) {
 | |
| 						this.setUnhappy();
 | |
| 					}
 | |
| 
 | |
| 					player.awardStat(Stats.TALKED_TO_VILLAGER);
 | |
| 				}
 | |
| 
 | |
| 				if (bl) {
 | |
| 					return InteractionResult.CONSUME;
 | |
| 				}
 | |
| 
 | |
| 				this.startTrading(player);
 | |
| 			}
 | |
| 
 | |
| 			return InteractionResult.SUCCESS;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private void setUnhappy() {
 | |
| 		this.setUnhappyCounter(40);
 | |
| 		if (!this.level().isClientSide()) {
 | |
| 			this.makeSound(SoundEvents.VILLAGER_NO);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private void startTrading(Player player) {
 | |
| 		this.updateSpecialPrices(player);
 | |
| 		this.setTradingPlayer(player);
 | |
| 		this.openTradingScreen(player, this.getDisplayName(), this.getVillagerData().level());
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void setTradingPlayer(@Nullable Player tradingPlayer) {
 | |
| 		boolean bl = this.getTradingPlayer() != null && tradingPlayer == null;
 | |
| 		super.setTradingPlayer(tradingPlayer);
 | |
| 		if (bl) {
 | |
| 			this.stopTrading();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected void stopTrading() {
 | |
| 		super.stopTrading();
 | |
| 		this.resetSpecialPrices();
 | |
| 	}
 | |
| 
 | |
| 	private void resetSpecialPrices() {
 | |
| 		if (!this.level().isClientSide()) {
 | |
| 			for (MerchantOffer merchantOffer : this.getOffers()) {
 | |
| 				merchantOffer.resetSpecialPriceDiff();
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public boolean canRestock() {
 | |
| 		return true;
 | |
| 	}
 | |
| 
 | |
| 	public void restock() {
 | |
| 		this.updateDemand();
 | |
| 
 | |
| 		for (MerchantOffer merchantOffer : this.getOffers()) {
 | |
| 			merchantOffer.resetUses();
 | |
| 		}
 | |
| 
 | |
| 		this.resendOffersToTradingPlayer();
 | |
| 		this.lastRestockGameTime = this.level().getGameTime();
 | |
| 		this.numberOfRestocksToday++;
 | |
| 	}
 | |
| 
 | |
| 	private void resendOffersToTradingPlayer() {
 | |
| 		MerchantOffers merchantOffers = this.getOffers();
 | |
| 		Player player = this.getTradingPlayer();
 | |
| 		if (player != null && !merchantOffers.isEmpty()) {
 | |
| 			player.sendMerchantOffers(
 | |
| 				player.containerMenu.containerId, merchantOffers, this.getVillagerData().level(), this.getVillagerXp(), this.showProgressBar(), this.canRestock()
 | |
| 			);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private boolean needsToRestock() {
 | |
| 		for (MerchantOffer merchantOffer : this.getOffers()) {
 | |
| 			if (merchantOffer.needsRestock()) {
 | |
| 				return true;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	private boolean allowedToRestock() {
 | |
| 		return this.numberOfRestocksToday == 0 || this.numberOfRestocksToday < 2 && this.level().getGameTime() > this.lastRestockGameTime + 2400L;
 | |
| 	}
 | |
| 
 | |
| 	public boolean shouldRestock() {
 | |
| 		long l = this.lastRestockGameTime + 12000L;
 | |
| 		long m = this.level().getGameTime();
 | |
| 		boolean bl = m > l;
 | |
| 		long n = this.level().getDayTime();
 | |
| 		if (this.lastRestockCheckDayTime > 0L) {
 | |
| 			long o = this.lastRestockCheckDayTime / 24000L;
 | |
| 			long p = n / 24000L;
 | |
| 			bl |= p > o;
 | |
| 		}
 | |
| 
 | |
| 		this.lastRestockCheckDayTime = n;
 | |
| 		if (bl) {
 | |
| 			this.lastRestockGameTime = m;
 | |
| 			this.resetNumberOfRestocks();
 | |
| 		}
 | |
| 
 | |
| 		return this.allowedToRestock() && this.needsToRestock();
 | |
| 	}
 | |
| 
 | |
| 	private void catchUpDemand() {
 | |
| 		int i = 2 - this.numberOfRestocksToday;
 | |
| 		if (i > 0) {
 | |
| 			for (MerchantOffer merchantOffer : this.getOffers()) {
 | |
| 				merchantOffer.resetUses();
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		for (int j = 0; j < i; j++) {
 | |
| 			this.updateDemand();
 | |
| 		}
 | |
| 
 | |
| 		this.resendOffersToTradingPlayer();
 | |
| 	}
 | |
| 
 | |
| 	private void updateDemand() {
 | |
| 		for (MerchantOffer merchantOffer : this.getOffers()) {
 | |
| 			merchantOffer.updateDemand();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private void updateSpecialPrices(Player player) {
 | |
| 		int i = this.getPlayerReputation(player);
 | |
| 		if (i != 0) {
 | |
| 			for (MerchantOffer merchantOffer : this.getOffers()) {
 | |
| 				merchantOffer.addToSpecialPriceDiff(-Mth.floor(i * merchantOffer.getPriceMultiplier()));
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if (player.hasEffect(MobEffects.HERO_OF_THE_VILLAGE)) {
 | |
| 			MobEffectInstance mobEffectInstance = player.getEffect(MobEffects.HERO_OF_THE_VILLAGE);
 | |
| 			int j = mobEffectInstance.getAmplifier();
 | |
| 
 | |
| 			for (MerchantOffer merchantOffer2 : this.getOffers()) {
 | |
| 				double d = 0.3 + 0.0625 * j;
 | |
| 				int k = (int)Math.floor(d * merchantOffer2.getBaseCostA().getCount());
 | |
| 				merchantOffer2.addToSpecialPriceDiff(-Math.max(k, 1));
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected void defineSynchedData(SynchedEntityData.Builder builder) {
 | |
| 		super.defineSynchedData(builder);
 | |
| 		builder.define(DATA_VILLAGER_DATA, createDefaultVillagerData());
 | |
| 	}
 | |
| 
 | |
| 	public static VillagerData createDefaultVillagerData() {
 | |
| 		return new VillagerData(
 | |
| 			BuiltInRegistries.VILLAGER_TYPE.getOrThrow(VillagerType.PLAINS), BuiltInRegistries.VILLAGER_PROFESSION.getOrThrow(VillagerProfession.NONE), 1
 | |
| 		);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected void addAdditionalSaveData(ValueOutput output) {
 | |
| 		super.addAdditionalSaveData(output);
 | |
| 		output.store("VillagerData", VillagerData.CODEC, this.getVillagerData());
 | |
| 		output.putByte("FoodLevel", (byte)this.foodLevel);
 | |
| 		output.store("Gossips", GossipContainer.CODEC, this.gossips);
 | |
| 		output.putInt("Xp", this.villagerXp);
 | |
| 		output.putLong("LastRestock", this.lastRestockGameTime);
 | |
| 		output.putLong("LastGossipDecay", this.lastGossipDecayTime);
 | |
| 		output.putInt("RestocksToday", this.numberOfRestocksToday);
 | |
| 		if (this.assignProfessionWhenSpawned) {
 | |
| 			output.putBoolean("AssignProfessionWhenSpawned", true);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@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.foodLevel = input.getByteOr("FoodLevel", (byte)0);
 | |
| 		this.gossips.clear();
 | |
| 		input.read("Gossips", GossipContainer.CODEC).ifPresent(this.gossips::putAll);
 | |
| 		this.villagerXp = input.getIntOr("Xp", 0);
 | |
| 		this.lastRestockGameTime = input.getLongOr("LastRestock", 0L);
 | |
| 		this.lastGossipDecayTime = input.getLongOr("LastGossipDecay", 0L);
 | |
| 		if (this.level() instanceof ServerLevel) {
 | |
| 			this.refreshBrain((ServerLevel)this.level());
 | |
| 		}
 | |
| 
 | |
| 		this.numberOfRestocksToday = input.getIntOr("RestocksToday", 0);
 | |
| 		this.assignProfessionWhenSpawned = input.getBooleanOr("AssignProfessionWhenSpawned", false);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public boolean removeWhenFarAway(double distanceToClosestPlayer) {
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	@Override
 | |
| 	protected SoundEvent getAmbientSound() {
 | |
| 		if (this.isSleeping()) {
 | |
| 			return null;
 | |
| 		} else {
 | |
| 			return this.isTrading() ? SoundEvents.VILLAGER_TRADE : SoundEvents.VILLAGER_AMBIENT;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected SoundEvent getHurtSound(DamageSource damageSource) {
 | |
| 		return SoundEvents.VILLAGER_HURT;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected SoundEvent getDeathSound() {
 | |
| 		return SoundEvents.VILLAGER_DEATH;
 | |
| 	}
 | |
| 
 | |
| 	public void playWorkSound() {
 | |
| 		this.makeSound(this.getVillagerData().profession().value().workSound());
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void setVillagerData(VillagerData data) {
 | |
| 		VillagerData villagerData = this.getVillagerData();
 | |
| 		if (!villagerData.profession().equals(data.profession())) {
 | |
| 			this.offers = null;
 | |
| 		}
 | |
| 
 | |
| 		this.entityData.set(DATA_VILLAGER_DATA, data);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public VillagerData getVillagerData() {
 | |
| 		return this.entityData.get(DATA_VILLAGER_DATA);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected void rewardTradeXp(MerchantOffer offer) {
 | |
| 		int i = 3 + this.random.nextInt(4);
 | |
| 		this.villagerXp = this.villagerXp + offer.getXp();
 | |
| 		this.lastTradedPlayer = this.getTradingPlayer();
 | |
| 		if (this.shouldIncreaseLevel()) {
 | |
| 			this.updateMerchantTimer = 40;
 | |
| 			this.increaseProfessionLevelOnUpdate = true;
 | |
| 			i += 5;
 | |
| 		}
 | |
| 
 | |
| 		if (offer.shouldRewardExp()) {
 | |
| 			this.level().addFreshEntity(new ExperienceOrb(this.level(), this.getX(), this.getY() + 0.5, this.getZ(), i));
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void setLastHurtByMob(@Nullable LivingEntity livingEntity) {
 | |
| 		if (livingEntity != null && this.level() instanceof ServerLevel) {
 | |
| 			((ServerLevel)this.level()).onReputationEvent(ReputationEventType.VILLAGER_HURT, livingEntity, this);
 | |
| 			if (this.isAlive() && livingEntity instanceof Player) {
 | |
| 				this.level().broadcastEntityEvent(this, (byte)13);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		super.setLastHurtByMob(livingEntity);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void die(DamageSource damageSource) {
 | |
| 		LOGGER.info("Villager {} died, message: '{}'", this, damageSource.getLocalizedDeathMessage(this).getString());
 | |
| 		Entity entity = damageSource.getEntity();
 | |
| 		if (entity != null) {
 | |
| 			this.tellWitnessesThatIWasMurdered(entity);
 | |
| 		}
 | |
| 
 | |
| 		this.releaseAllPois();
 | |
| 		super.die(damageSource);
 | |
| 	}
 | |
| 
 | |
| 	private void releaseAllPois() {
 | |
| 		this.releasePoi(MemoryModuleType.HOME);
 | |
| 		this.releasePoi(MemoryModuleType.JOB_SITE);
 | |
| 		this.releasePoi(MemoryModuleType.POTENTIAL_JOB_SITE);
 | |
| 		this.releasePoi(MemoryModuleType.MEETING_POINT);
 | |
| 	}
 | |
| 
 | |
| 	private void tellWitnessesThatIWasMurdered(Entity murderer) {
 | |
| 		if (this.level() instanceof ServerLevel serverLevel) {
 | |
| 			Optional<NearestVisibleLivingEntities> optional = this.brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES);
 | |
| 			if (!optional.isEmpty()) {
 | |
| 				((NearestVisibleLivingEntities)optional.get())
 | |
| 					.findAll(ReputationEventHandler.class::isInstance)
 | |
| 					.forEach(livingEntity -> serverLevel.onReputationEvent(ReputationEventType.VILLAGER_KILLED, murderer, (ReputationEventHandler)livingEntity));
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public void releasePoi(MemoryModuleType<GlobalPos> moduleType) {
 | |
| 		if (this.level() instanceof ServerLevel) {
 | |
| 			MinecraftServer minecraftServer = ((ServerLevel)this.level()).getServer();
 | |
| 			this.brain.getMemory(moduleType).ifPresent(globalPos -> {
 | |
| 				ServerLevel serverLevel = minecraftServer.getLevel(globalPos.dimension());
 | |
| 				if (serverLevel != null) {
 | |
| 					PoiManager poiManager = serverLevel.getPoiManager();
 | |
| 					Optional<Holder<PoiType>> optional = poiManager.getType(globalPos.pos());
 | |
| 					BiPredicate<Villager, Holder<PoiType>> biPredicate = (BiPredicate<Villager, Holder<PoiType>>)POI_MEMORIES.get(moduleType);
 | |
| 					if (optional.isPresent() && biPredicate.test(this, (Holder)optional.get())) {
 | |
| 						poiManager.release(globalPos.pos());
 | |
| 						DebugPackets.sendPoiTicketCountPacket(serverLevel, globalPos.pos());
 | |
| 					}
 | |
| 				}
 | |
| 			});
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public boolean canBreed() {
 | |
| 		return this.foodLevel + this.countFoodPointsInInventory() >= 12 && !this.isSleeping() && this.getAge() == 0;
 | |
| 	}
 | |
| 
 | |
| 	private boolean hungry() {
 | |
| 		return this.foodLevel < 12;
 | |
| 	}
 | |
| 
 | |
| 	private void eatUntilFull() {
 | |
| 		if (this.hungry() && this.countFoodPointsInInventory() != 0) {
 | |
| 			for (int i = 0; i < this.getInventory().getContainerSize(); i++) {
 | |
| 				ItemStack itemStack = this.getInventory().getItem(i);
 | |
| 				if (!itemStack.isEmpty()) {
 | |
| 					Integer integer = (Integer)FOOD_POINTS.get(itemStack.getItem());
 | |
| 					if (integer != null) {
 | |
| 						int j = itemStack.getCount();
 | |
| 
 | |
| 						for (int k = j; k > 0; k--) {
 | |
| 							this.foodLevel = this.foodLevel + integer;
 | |
| 							this.getInventory().removeItem(i, 1);
 | |
| 							if (!this.hungry()) {
 | |
| 								return;
 | |
| 							}
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public int getPlayerReputation(Player player) {
 | |
| 		return this.gossips.getReputation(player.getUUID(), gossipType -> true);
 | |
| 	}
 | |
| 
 | |
| 	private void digestFood(int qty) {
 | |
| 		this.foodLevel -= qty;
 | |
| 	}
 | |
| 
 | |
| 	public void eatAndDigestFood() {
 | |
| 		this.eatUntilFull();
 | |
| 		this.digestFood(12);
 | |
| 	}
 | |
| 
 | |
| 	public void setOffers(MerchantOffers offers) {
 | |
| 		this.offers = offers;
 | |
| 	}
 | |
| 
 | |
| 	private boolean shouldIncreaseLevel() {
 | |
| 		int i = this.getVillagerData().level();
 | |
| 		return VillagerData.canLevelUp(i) && this.villagerXp >= VillagerData.getMaxXpPerLevel(i);
 | |
| 	}
 | |
| 
 | |
| 	private void increaseMerchantCareer() {
 | |
| 		this.setVillagerData(this.getVillagerData().withLevel(this.getVillagerData().level() + 1));
 | |
| 		this.updateTrades();
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected Component getTypeName() {
 | |
| 		return this.getVillagerData().profession().value().name();
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void handleEntityEvent(byte id) {
 | |
| 		if (id == 12) {
 | |
| 			this.addParticlesAroundSelf(ParticleTypes.HEART);
 | |
| 		} else if (id == 13) {
 | |
| 			this.addParticlesAroundSelf(ParticleTypes.ANGRY_VILLAGER);
 | |
| 		} else if (id == 14) {
 | |
| 			this.addParticlesAroundSelf(ParticleTypes.HAPPY_VILLAGER);
 | |
| 		} else if (id == 42) {
 | |
| 			this.addParticlesAroundSelf(ParticleTypes.SPLASH);
 | |
| 		} else {
 | |
| 			super.handleEntityEvent(id);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	@Override
 | |
| 	public SpawnGroupData finalizeSpawn(
 | |
| 		ServerLevelAccessor level, DifficultyInstance difficulty, EntitySpawnReason spawnReason, @Nullable SpawnGroupData spawnGroupData
 | |
| 	) {
 | |
| 		if (spawnReason == EntitySpawnReason.BREEDING) {
 | |
| 			this.setVillagerData(this.getVillagerData().withProfession(level.registryAccess(), VillagerProfession.NONE));
 | |
| 		}
 | |
| 
 | |
| 		if (spawnReason == EntitySpawnReason.COMMAND
 | |
| 			|| spawnReason == EntitySpawnReason.SPAWN_ITEM_USE
 | |
| 			|| EntitySpawnReason.isSpawner(spawnReason)
 | |
| 			|| spawnReason == EntitySpawnReason.DISPENSER) {
 | |
| 			this.setVillagerData(this.getVillagerData().withType(level.registryAccess(), VillagerType.byBiome(level.getBiome(this.blockPosition()))));
 | |
| 		}
 | |
| 
 | |
| 		if (spawnReason == EntitySpawnReason.STRUCTURE) {
 | |
| 			this.assignProfessionWhenSpawned = true;
 | |
| 		}
 | |
| 
 | |
| 		return super.finalizeSpawn(level, difficulty, spawnReason, spawnGroupData);
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	public Villager getBreedOffspring(ServerLevel level, AgeableMob otherParent) {
 | |
| 		double d = this.random.nextDouble();
 | |
| 		Holder<VillagerType> holder;
 | |
| 		if (d < 0.5) {
 | |
| 			holder = level.registryAccess().getOrThrow(VillagerType.byBiome(level.getBiome(this.blockPosition())));
 | |
| 		} else if (d < 0.75) {
 | |
| 			holder = this.getVillagerData().type();
 | |
| 		} else {
 | |
| 			holder = ((Villager)otherParent).getVillagerData().type();
 | |
| 		}
 | |
| 
 | |
| 		Villager villager = new Villager(EntityType.VILLAGER, level, holder);
 | |
| 		villager.finalizeSpawn(level, level.getCurrentDifficultyAt(villager.blockPosition()), EntitySpawnReason.BREEDING, null);
 | |
| 		return villager;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void thunderHit(ServerLevel level, LightningBolt lightning) {
 | |
| 		if (level.getDifficulty() != Difficulty.PEACEFUL) {
 | |
| 			LOGGER.info("Villager {} was struck by lightning {}.", this, lightning);
 | |
| 			Witch witch = this.convertTo(EntityType.WITCH, ConversionParams.single(this, false, false), witchx -> {
 | |
| 				witchx.finalizeSpawn(level, level.getCurrentDifficultyAt(witchx.blockPosition()), EntitySpawnReason.CONVERSION, null);
 | |
| 				witchx.setPersistenceRequired();
 | |
| 				this.releaseAllPois();
 | |
| 			});
 | |
| 			if (witch == null) {
 | |
| 				super.thunderHit(level, lightning);
 | |
| 			}
 | |
| 		} else {
 | |
| 			super.thunderHit(level, lightning);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected void pickUpItem(ServerLevel level, ItemEntity entity) {
 | |
| 		InventoryCarrier.pickUpItem(level, this, this, entity);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public boolean wantsToPickUp(ServerLevel level, ItemStack stack) {
 | |
| 		Item item = stack.getItem();
 | |
| 		return (stack.is(ItemTags.VILLAGER_PICKS_UP) || this.getVillagerData().profession().value().requestedItems().contains(item))
 | |
| 			&& this.getInventory().canAddItem(stack);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Used by {@link net.minecraft.world.entity.ai.behavior.TradeWithVillager} to check if the villager can give some items from an inventory to another villager.
 | |
| 	 */
 | |
| 	public boolean hasExcessFood() {
 | |
| 		return this.countFoodPointsInInventory() >= 24;
 | |
| 	}
 | |
| 
 | |
| 	public boolean wantsMoreFood() {
 | |
| 		return this.countFoodPointsInInventory() < 12;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * @return calculated food value from item stacks in this villager's inventory
 | |
| 	 */
 | |
| 	private int countFoodPointsInInventory() {
 | |
| 		SimpleContainer simpleContainer = this.getInventory();
 | |
| 		return FOOD_POINTS.entrySet().stream().mapToInt(entry -> simpleContainer.countItem((Item)entry.getKey()) * (Integer)entry.getValue()).sum();
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Returns {@code true} if villager has seeds, potatoes or carrots in inventory
 | |
| 	 */
 | |
| 	public boolean hasFarmSeeds() {
 | |
| 		return this.getInventory().hasAnyMatching(itemStack -> itemStack.is(ItemTags.VILLAGER_PLANTABLE_SEEDS));
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected void updateTrades() {
 | |
| 		VillagerData villagerData = this.getVillagerData();
 | |
| 		ResourceKey<VillagerProfession> resourceKey = (ResourceKey<VillagerProfession>)villagerData.profession().unwrapKey().orElse(null);
 | |
| 		if (resourceKey != null) {
 | |
| 			Int2ObjectMap<VillagerTrades.ItemListing[]> int2ObjectMap2;
 | |
| 			if (this.level().enabledFeatures().contains(FeatureFlags.TRADE_REBALANCE)) {
 | |
| 				Int2ObjectMap<VillagerTrades.ItemListing[]> int2ObjectMap = (Int2ObjectMap<VillagerTrades.ItemListing[]>)VillagerTrades.EXPERIMENTAL_TRADES
 | |
| 					.get(resourceKey);
 | |
| 				int2ObjectMap2 = int2ObjectMap != null ? int2ObjectMap : (Int2ObjectMap)VillagerTrades.TRADES.get(resourceKey);
 | |
| 			} else {
 | |
| 				int2ObjectMap2 = (Int2ObjectMap<VillagerTrades.ItemListing[]>)VillagerTrades.TRADES.get(resourceKey);
 | |
| 			}
 | |
| 
 | |
| 			if (int2ObjectMap2 != null && !int2ObjectMap2.isEmpty()) {
 | |
| 				VillagerTrades.ItemListing[] itemListings = int2ObjectMap2.get(villagerData.level());
 | |
| 				if (itemListings != null) {
 | |
| 					MerchantOffers merchantOffers = this.getOffers();
 | |
| 					this.addOffersFromItemListings(merchantOffers, itemListings, 2);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public void gossip(ServerLevel serverLevel, Villager target, long gameTime) {
 | |
| 		if ((gameTime < this.lastGossipTime || gameTime >= this.lastGossipTime + 1200L)
 | |
| 			&& (gameTime < target.lastGossipTime || gameTime >= target.lastGossipTime + 1200L)) {
 | |
| 			this.gossips.transferFrom(target.gossips, this.random, 10);
 | |
| 			this.lastGossipTime = gameTime;
 | |
| 			target.lastGossipTime = gameTime;
 | |
| 			this.spawnGolemIfNeeded(serverLevel, gameTime, 5);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private void maybeDecayGossip() {
 | |
| 		long l = this.level().getGameTime();
 | |
| 		if (this.lastGossipDecayTime == 0L) {
 | |
| 			this.lastGossipDecayTime = l;
 | |
| 		} else if (l >= this.lastGossipDecayTime + 24000L) {
 | |
| 			this.gossips.decay();
 | |
| 			this.lastGossipDecayTime = l;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public void spawnGolemIfNeeded(ServerLevel serverLevel, long gameTime, int minVillagerAmount) {
 | |
| 		if (this.wantsToSpawnGolem(gameTime)) {
 | |
| 			AABB aABB = this.getBoundingBox().inflate(10.0, 10.0, 10.0);
 | |
| 			List<Villager> list = serverLevel.getEntitiesOfClass(Villager.class, aABB);
 | |
| 			List<Villager> list2 = list.stream().filter(villager -> villager.wantsToSpawnGolem(gameTime)).limit(5L).toList();
 | |
| 			if (list2.size() >= minVillagerAmount) {
 | |
| 				if (!SpawnUtil.trySpawnMob(
 | |
| 						EntityType.IRON_GOLEM, EntitySpawnReason.MOB_SUMMONED, serverLevel, this.blockPosition(), 10, 8, 6, SpawnUtil.Strategy.LEGACY_IRON_GOLEM, false
 | |
| 					)
 | |
| 					.isEmpty()) {
 | |
| 					list.forEach(GolemSensor::golemDetected);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public boolean wantsToSpawnGolem(long gameTime) {
 | |
| 		return !this.golemSpawnConditionsMet(this.level().getGameTime()) ? false : !this.brain.hasMemoryValue(MemoryModuleType.GOLEM_DETECTED_RECENTLY);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void onReputationEventFrom(ReputationEventType type, Entity target) {
 | |
| 		if (type == ReputationEventType.ZOMBIE_VILLAGER_CURED) {
 | |
| 			this.gossips.add(target.getUUID(), GossipType.MAJOR_POSITIVE, 20);
 | |
| 			this.gossips.add(target.getUUID(), GossipType.MINOR_POSITIVE, 25);
 | |
| 		} else if (type == ReputationEventType.TRADE) {
 | |
| 			this.gossips.add(target.getUUID(), GossipType.TRADING, 2);
 | |
| 		} else if (type == ReputationEventType.VILLAGER_HURT) {
 | |
| 			this.gossips.add(target.getUUID(), GossipType.MINOR_NEGATIVE, 25);
 | |
| 		} else if (type == ReputationEventType.VILLAGER_KILLED) {
 | |
| 			this.gossips.add(target.getUUID(), GossipType.MAJOR_NEGATIVE, 25);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public int getVillagerXp() {
 | |
| 		return this.villagerXp;
 | |
| 	}
 | |
| 
 | |
| 	public void setVillagerXp(int villagerXp) {
 | |
| 		this.villagerXp = villagerXp;
 | |
| 	}
 | |
| 
 | |
| 	private void resetNumberOfRestocks() {
 | |
| 		this.catchUpDemand();
 | |
| 		this.numberOfRestocksToday = 0;
 | |
| 	}
 | |
| 
 | |
| 	public GossipContainer getGossips() {
 | |
| 		return this.gossips;
 | |
| 	}
 | |
| 
 | |
| 	public void setGossips(GossipContainer gossips) {
 | |
| 		this.gossips.putAll(gossips);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected void sendDebugPackets() {
 | |
| 		super.sendDebugPackets();
 | |
| 		DebugPackets.sendEntityBrain(this);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void startSleeping(BlockPos pos) {
 | |
| 		super.startSleeping(pos);
 | |
| 		this.brain.setMemory(MemoryModuleType.LAST_SLEPT, this.level().getGameTime());
 | |
| 		this.brain.eraseMemory(MemoryModuleType.WALK_TARGET);
 | |
| 		this.brain.eraseMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void stopSleeping() {
 | |
| 		super.stopSleeping();
 | |
| 		this.brain.setMemory(MemoryModuleType.LAST_WOKEN, this.level().getGameTime());
 | |
| 	}
 | |
| 
 | |
| 	private boolean golemSpawnConditionsMet(long gameTime) {
 | |
| 		Optional<Long> optional = this.brain.getMemory(MemoryModuleType.LAST_SLEPT);
 | |
| 		return optional.filter(long_ -> gameTime - long_ < 24000L).isPresent();
 | |
| 	}
 | |
| 
 | |
| 	@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);
 | |
| 		}
 | |
| 	}
 | |
| }
 |