904 lines
38 KiB
Java
904 lines
38 KiB
Java
package net.minecraft.world.entity.npc;
|
|
|
|
import net.minecraft.world.entity.ai.sensing.NearestLivingEntitySensor;
|
|
import com.google.common.collect.ImmutableMap;
|
|
import net.minecraft.network.syncher.SynchedEntityData;
|
|
import net.minecraft.network.syncher.EntityDataSerializers;
|
|
import net.minecraft.core.SerializableLong;
|
|
import net.minecraft.world.entity.ai.gossip.GossipType;
|
|
import net.minecraft.world.level.LevelReader;
|
|
import net.minecraft.world.entity.animal.IronGolem;
|
|
import net.minecraft.world.phys.AABB;
|
|
import java.util.stream.Collector;
|
|
import java.util.stream.Collectors;
|
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
|
import net.minecraft.world.level.ItemLike;
|
|
import net.minecraft.world.SimpleContainer;
|
|
import net.minecraft.world.entity.item.ItemEntity;
|
|
import net.minecraft.world.entity.monster.Witch;
|
|
import net.minecraft.world.entity.global.LightningBolt;
|
|
import net.minecraft.world.entity.AgableMob;
|
|
import net.minecraft.world.entity.SpawnGroupData;
|
|
import net.minecraft.world.entity.MobSpawnType;
|
|
import net.minecraft.world.DifficultyInstance;
|
|
import net.minecraft.world.level.LevelAccessor;
|
|
import net.minecraft.core.particles.ParticleOptions;
|
|
import net.minecraft.core.particles.ParticleTypes;
|
|
import net.minecraft.network.chat.TranslatableComponent;
|
|
import net.minecraft.core.Registry;
|
|
import net.minecraft.network.chat.Component;
|
|
import net.minecraft.world.entity.ai.village.poi.PoiManager;
|
|
import net.minecraft.server.MinecraftServer;
|
|
import net.minecraft.network.protocol.game.DebugPackets;
|
|
import java.util.Optional;
|
|
import java.util.List;
|
|
import net.minecraft.world.entity.ExperienceOrb;
|
|
import net.minecraft.world.damagesource.DamageSource;
|
|
import net.minecraft.sounds.SoundEvent;
|
|
import net.minecraft.nbt.ListTag;
|
|
import net.minecraft.world.item.trading.MerchantOffers;
|
|
import net.minecraft.nbt.Tag;
|
|
import net.minecraft.util.Mth;
|
|
import java.util.Iterator;
|
|
import net.minecraft.world.item.trading.MerchantOffer;
|
|
import net.minecraft.sounds.SoundEvents;
|
|
import net.minecraft.world.item.ItemStack;
|
|
import net.minecraft.stats.Stats;
|
|
import net.minecraft.world.entity.LivingEntity;
|
|
import net.minecraft.world.item.Items;
|
|
import net.minecraft.world.InteractionHand;
|
|
import net.minecraft.world.entity.raid.Raid;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.world.entity.Entity;
|
|
import net.minecraft.world.entity.ai.village.ReputationEventType;
|
|
import net.minecraft.world.effect.MobEffectInstance;
|
|
import net.minecraft.world.effect.MobEffects;
|
|
import com.google.common.collect.ImmutableSet;
|
|
import com.mojang.datafixers.util.Pair;
|
|
import net.minecraft.world.entity.ai.memory.MemoryStatus;
|
|
import net.minecraft.world.entity.ai.behavior.VillagerGoalPackages;
|
|
import net.minecraft.world.entity.schedule.Activity;
|
|
import net.minecraft.world.entity.schedule.Schedule;
|
|
import net.minecraft.world.entity.monster.SharedMonsterAttributes;
|
|
import net.minecraft.server.level.ServerLevel;
|
|
import java.util.Collection;
|
|
import net.minecraft.world.entity.ai.Brain;
|
|
import com.mojang.datafixers.types.DynamicOps;
|
|
import com.mojang.datafixers.Dynamic;
|
|
import net.minecraft.nbt.CompoundTag;
|
|
import net.minecraft.nbt.NbtOps;
|
|
import net.minecraft.world.entity.ai.navigation.GroundPathNavigation;
|
|
import net.minecraft.world.level.Level;
|
|
import net.minecraft.world.entity.EntityType;
|
|
import net.minecraft.world.entity.ai.village.poi.PoiType;
|
|
import java.util.function.BiPredicate;
|
|
import net.minecraft.core.GlobalPos;
|
|
import net.minecraft.world.entity.ai.sensing.Sensor;
|
|
import net.minecraft.world.entity.ai.sensing.SensorType;
|
|
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
|
|
import com.google.common.collect.ImmutableList;
|
|
import net.minecraft.world.entity.ai.gossip.GossipContainer;
|
|
import javax.annotation.Nullable;
|
|
import net.minecraft.world.entity.player.Player;
|
|
import java.util.Set;
|
|
import net.minecraft.world.item.Item;
|
|
import java.util.Map;
|
|
import net.minecraft.network.syncher.EntityDataAccessor;
|
|
import net.minecraft.world.entity.ReputationEventHandler;
|
|
|
|
public class Villager extends AbstractVillager implements ReputationEventHandler, VillagerDataHolder {
|
|
private static final EntityDataAccessor<VillagerData> DATA_VILLAGER_DATA;
|
|
public static final Map<Item, Integer> FOOD_POINTS;
|
|
private static final Set<Item> WANTED_ITEMS;
|
|
private int updateMerchantTimer;
|
|
private boolean increaseProfessionLevelOnUpdate;
|
|
@Nullable
|
|
private Player lastTradedPlayer;
|
|
private byte foodLevel;
|
|
private final GossipContainer gossips;
|
|
private long lastGossipTime;
|
|
private long lastGossipDecayTime;
|
|
private int villagerXp;
|
|
private long lastRestockGameTime;
|
|
private int numberOfRestocksToday;
|
|
private long lastRestockCheckDayTime;
|
|
private static final ImmutableList<MemoryModuleType<?>> MEMORY_TYPES;
|
|
private static final ImmutableList<SensorType<? extends Sensor<? super Villager>>> SENSOR_TYPES;
|
|
public static final Map<MemoryModuleType<GlobalPos>, BiPredicate<Villager, PoiType>> POI_MEMORIES;
|
|
|
|
public Villager(final EntityType<? extends Villager> akr, final Level bjt) {
|
|
this(akr, bjt, VillagerType.PLAINS);
|
|
}
|
|
|
|
public Villager(final EntityType<? extends Villager> akr, final Level bjt, final VillagerType axy) {
|
|
super(akr, bjt);
|
|
this.gossips = new GossipContainer();
|
|
((GroundPathNavigation)this.getNavigation()).setCanOpenDoors(true);
|
|
this.getNavigation().setCanFloat(true);
|
|
this.setCanPickUpLoot(true);
|
|
this.setVillagerData(this.getVillagerData().setType(axy).setProfession(VillagerProfession.NONE));
|
|
this.brain = this.makeBrain(new Dynamic((DynamicOps)NbtOps.INSTANCE, new CompoundTag()));
|
|
}
|
|
|
|
@Override
|
|
public Brain<Villager> getBrain() {
|
|
return (Brain<Villager>)super.getBrain();
|
|
}
|
|
|
|
@Override
|
|
protected Brain<?> makeBrain(final Dynamic<?> dynamic) {
|
|
final Brain<Villager> alm3 = new Brain<Villager>(Villager.MEMORY_TYPES, Villager.SENSOR_TYPES, dynamic);
|
|
this.registerBrainGoals(alm3);
|
|
return alm3;
|
|
}
|
|
|
|
public void refreshBrain(final ServerLevel xd) {
|
|
final Brain<Villager> alm3 = this.getBrain();
|
|
alm3.stopAll(xd, this);
|
|
this.brain = alm3.copyWithoutGoals();
|
|
this.registerBrainGoals(this.getBrain());
|
|
}
|
|
|
|
private void registerBrainGoals(final Brain<Villager> alm) {
|
|
final VillagerProfession axw3 = this.getVillagerData().getProfession();
|
|
final float float4 = (float)this.getAttribute(SharedMonsterAttributes.MOVEMENT_SPEED).getValue();
|
|
if (this.isBaby()) {
|
|
alm.setSchedule(Schedule.VILLAGER_BABY);
|
|
alm.addActivity(Activity.PLAY, VillagerGoalPackages.getPlayPackage(float4));
|
|
}
|
|
else {
|
|
alm.setSchedule(Schedule.VILLAGER_DEFAULT);
|
|
alm.addActivity(Activity.WORK, VillagerGoalPackages.getWorkPackage(axw3, float4), ImmutableSet.of(Pair.of(MemoryModuleType.JOB_SITE, MemoryStatus.VALUE_PRESENT)));
|
|
}
|
|
alm.addActivity(Activity.CORE, VillagerGoalPackages.getCorePackage(axw3, float4));
|
|
alm.addActivity(Activity.MEET, VillagerGoalPackages.getMeetPackage(axw3, float4), ImmutableSet.of(Pair.of(MemoryModuleType.MEETING_POINT, MemoryStatus.VALUE_PRESENT)));
|
|
alm.addActivity(Activity.REST, VillagerGoalPackages.getRestPackage(axw3, float4));
|
|
alm.addActivity(Activity.IDLE, VillagerGoalPackages.getIdlePackage(axw3, float4));
|
|
alm.addActivity(Activity.PANIC, VillagerGoalPackages.getPanicPackage(axw3, float4));
|
|
alm.addActivity(Activity.PRE_RAID, VillagerGoalPackages.getPreRaidPackage(axw3, float4));
|
|
alm.addActivity(Activity.RAID, VillagerGoalPackages.getRaidPackage(axw3, float4));
|
|
alm.addActivity(Activity.HIDE, VillagerGoalPackages.getHidePackage(axw3, float4));
|
|
alm.setCoreActivities(ImmutableSet.<Activity>of(Activity.CORE));
|
|
alm.setDefaultActivity(Activity.IDLE);
|
|
alm.setActivity(Activity.IDLE);
|
|
alm.updateActivity(this.level.getDayTime(), this.level.getGameTime());
|
|
}
|
|
|
|
@Override
|
|
protected void ageBoundaryReached() {
|
|
super.ageBoundaryReached();
|
|
if (this.level instanceof ServerLevel) {
|
|
this.refreshBrain((ServerLevel)this.level);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void registerAttributes() {
|
|
super.registerAttributes();
|
|
this.getAttribute(SharedMonsterAttributes.MOVEMENT_SPEED).setBaseValue(0.5);
|
|
this.getAttribute(SharedMonsterAttributes.FOLLOW_RANGE).setBaseValue(48.0);
|
|
}
|
|
|
|
@Override
|
|
protected void customServerAiStep() {
|
|
this.level.getProfiler().push("brain");
|
|
this.getBrain().tick((ServerLevel)this.level, this);
|
|
this.level.getProfiler().pop();
|
|
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 && this.level instanceof ServerLevel) {
|
|
((ServerLevel)this.level).onReputationEvent(ReputationEventType.TRADE, this.lastTradedPlayer, this);
|
|
this.level.broadcastEntityEvent(this, (byte)14);
|
|
this.lastTradedPlayer = null;
|
|
}
|
|
if (!this.isNoAi() && this.random.nextInt(100) == 0) {
|
|
final Raid azk2 = ((ServerLevel)this.level).getRaidAt(new BlockPos(this));
|
|
if (azk2 != null && azk2.isActive() && !azk2.isOver()) {
|
|
this.level.broadcastEntityEvent(this, (byte)42);
|
|
}
|
|
}
|
|
if (this.getVillagerData().getProfession() == VillagerProfession.NONE && this.isTrading()) {
|
|
this.stopTrading();
|
|
}
|
|
super.customServerAiStep();
|
|
}
|
|
|
|
@Override
|
|
public void tick() {
|
|
super.tick();
|
|
if (this.getUnhappyCounter() > 0) {
|
|
this.setUnhappyCounter(this.getUnhappyCounter() - 1);
|
|
}
|
|
this.maybeDecayGossip();
|
|
}
|
|
|
|
@Override
|
|
public boolean mobInteract(final Player ayg, final InteractionHand ajh) {
|
|
final ItemStack bek4 = ayg.getItemInHand(ajh);
|
|
final boolean boolean5 = bek4.getItem() == Items.NAME_TAG;
|
|
if (boolean5) {
|
|
bek4.interactEnemy(ayg, this, ajh);
|
|
return true;
|
|
}
|
|
if (bek4.getItem() == Items.VILLAGER_SPAWN_EGG || !this.isAlive() || this.isTrading() || this.isSleeping()) {
|
|
return super.mobInteract(ayg, ajh);
|
|
}
|
|
if (this.isBaby()) {
|
|
this.setUnhappy();
|
|
return super.mobInteract(ayg, ajh);
|
|
}
|
|
final boolean boolean6 = this.getOffers().isEmpty();
|
|
if (ajh == InteractionHand.MAIN_HAND) {
|
|
if (boolean6 && !this.level.isClientSide) {
|
|
this.setUnhappy();
|
|
}
|
|
ayg.awardStat(Stats.TALKED_TO_VILLAGER);
|
|
}
|
|
if (boolean6) {
|
|
return super.mobInteract(ayg, ajh);
|
|
}
|
|
if (!this.level.isClientSide && !this.offers.isEmpty()) {
|
|
this.startTrading(ayg);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private void setUnhappy() {
|
|
this.setUnhappyCounter(40);
|
|
if (!this.level.isClientSide()) {
|
|
this.playSound(SoundEvents.VILLAGER_NO, this.getSoundVolume(), this.getVoicePitch());
|
|
}
|
|
}
|
|
|
|
private void startTrading(final Player ayg) {
|
|
this.updateSpecialPrices(ayg);
|
|
this.setTradingPlayer(ayg);
|
|
this.openTradingScreen(ayg, this.getDisplayName(), this.getVillagerData().getLevel());
|
|
}
|
|
|
|
@Override
|
|
public void setTradingPlayer(@Nullable final Player ayg) {
|
|
final boolean boolean3 = this.getTradingPlayer() != null && ayg == null;
|
|
super.setTradingPlayer(ayg);
|
|
if (boolean3) {
|
|
this.stopTrading();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void stopTrading() {
|
|
super.stopTrading();
|
|
this.resetSpecialPrices();
|
|
}
|
|
|
|
private void resetSpecialPrices() {
|
|
for (final MerchantOffer biw3 : this.getOffers()) {
|
|
biw3.resetSpecialPriceDiff();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean canRestock() {
|
|
return true;
|
|
}
|
|
|
|
public void restock() {
|
|
this.updateDemand();
|
|
for (final MerchantOffer biw3 : this.getOffers()) {
|
|
biw3.resetUses();
|
|
}
|
|
if (this.getVillagerData().getProfession() == VillagerProfession.FARMER) {
|
|
this.makeBread();
|
|
}
|
|
this.lastRestockGameTime = this.level.getGameTime();
|
|
++this.numberOfRestocksToday;
|
|
}
|
|
|
|
private boolean needsToRestock() {
|
|
for (final MerchantOffer biw3 : this.getOffers()) {
|
|
if (biw3.needsRestock()) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private boolean allowedToRestock() {
|
|
return this.numberOfRestocksToday == 0 || (this.numberOfRestocksToday < 2 && this.level.getGameTime() > this.lastRestockGameTime + 2400L);
|
|
}
|
|
|
|
public boolean shouldRestock() {
|
|
final long long2 = this.lastRestockGameTime + 12000L;
|
|
final long long3 = this.level.getGameTime();
|
|
boolean boolean6 = long3 > long2;
|
|
final long long4 = this.level.getDayTime();
|
|
if (this.lastRestockCheckDayTime > 0L) {
|
|
final long long5 = this.lastRestockCheckDayTime / 24000L;
|
|
final long long6 = long4 / 24000L;
|
|
boolean6 |= (long6 > long5);
|
|
}
|
|
this.lastRestockCheckDayTime = long4;
|
|
if (boolean6) {
|
|
this.lastRestockGameTime = long3;
|
|
this.resetNumberOfRestocks();
|
|
}
|
|
return this.allowedToRestock() && this.needsToRestock();
|
|
}
|
|
|
|
private void catchUpDemand() {
|
|
final int integer2 = 2 - this.numberOfRestocksToday;
|
|
if (integer2 > 0) {
|
|
for (final MerchantOffer biw4 : this.getOffers()) {
|
|
biw4.resetUses();
|
|
}
|
|
}
|
|
for (int integer3 = 0; integer3 < integer2; ++integer3) {
|
|
this.updateDemand();
|
|
}
|
|
}
|
|
|
|
private void updateDemand() {
|
|
for (final MerchantOffer biw3 : this.getOffers()) {
|
|
biw3.updateDemand();
|
|
}
|
|
}
|
|
|
|
private void updateSpecialPrices(final Player ayg) {
|
|
final int integer3 = this.getPlayerReputation(ayg);
|
|
if (integer3 != 0) {
|
|
for (final MerchantOffer biw5 : this.getOffers()) {
|
|
biw5.addToSpecialPriceDiff(-Mth.floor(integer3 * biw5.getPriceMultiplier()));
|
|
}
|
|
}
|
|
if (ayg.hasEffect(MobEffects.HERO_OF_THE_VILLAGE)) {
|
|
final MobEffectInstance akh4 = ayg.getEffect(MobEffects.HERO_OF_THE_VILLAGE);
|
|
final int integer4 = akh4.getAmplifier();
|
|
for (final MerchantOffer biw6 : this.getOffers()) {
|
|
final double double8 = 0.3 + 0.0625 * integer4;
|
|
final int integer5 = (int)Math.floor(double8 * biw6.getBaseCostA().getCount());
|
|
biw6.addToSpecialPriceDiff(-Math.max(integer5, 1));
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void defineSynchedData() {
|
|
super.defineSynchedData();
|
|
this.entityData.<VillagerData>define(Villager.DATA_VILLAGER_DATA, new VillagerData(VillagerType.PLAINS, VillagerProfession.NONE, 1));
|
|
}
|
|
|
|
@Override
|
|
public void addAdditionalSaveData(final CompoundTag jt) {
|
|
super.addAdditionalSaveData(jt);
|
|
jt.put("VillagerData", this.getVillagerData().<Tag>serialize((com.mojang.datafixers.types.DynamicOps<Tag>)NbtOps.INSTANCE));
|
|
jt.putByte("FoodLevel", this.foodLevel);
|
|
jt.put("Gossips", (Tag)this.gossips.store((com.mojang.datafixers.types.DynamicOps<Object>)NbtOps.INSTANCE).getValue());
|
|
jt.putInt("Xp", this.villagerXp);
|
|
jt.putLong("LastRestock", this.lastRestockGameTime);
|
|
jt.putLong("LastGossipDecay", this.lastGossipDecayTime);
|
|
jt.putInt("RestocksToday", this.numberOfRestocksToday);
|
|
}
|
|
|
|
@Override
|
|
public void readAdditionalSaveData(final CompoundTag jt) {
|
|
super.readAdditionalSaveData(jt);
|
|
if (jt.contains("VillagerData", 10)) {
|
|
this.setVillagerData(new VillagerData(new Dynamic((DynamicOps)NbtOps.INSTANCE, jt.get("VillagerData"))));
|
|
}
|
|
if (jt.contains("Offers", 10)) {
|
|
this.offers = new MerchantOffers(jt.getCompound("Offers"));
|
|
}
|
|
if (jt.contains("FoodLevel", 1)) {
|
|
this.foodLevel = jt.getByte("FoodLevel");
|
|
}
|
|
final ListTag jz3 = jt.getList("Gossips", 10);
|
|
this.gossips.update(new Dynamic((DynamicOps)NbtOps.INSTANCE, jz3));
|
|
if (jt.contains("Xp", 3)) {
|
|
this.villagerXp = jt.getInt("Xp");
|
|
}
|
|
this.lastRestockGameTime = jt.getLong("LastRestock");
|
|
this.lastGossipDecayTime = jt.getLong("LastGossipDecay");
|
|
this.setCanPickUpLoot(true);
|
|
if (this.level instanceof ServerLevel) {
|
|
this.refreshBrain((ServerLevel)this.level);
|
|
}
|
|
this.numberOfRestocksToday = jt.getInt("RestocksToday");
|
|
}
|
|
|
|
@Override
|
|
public boolean removeWhenFarAway(final double double1) {
|
|
return false;
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
protected SoundEvent getAmbientSound() {
|
|
if (this.isSleeping()) {
|
|
return null;
|
|
}
|
|
if (this.isTrading()) {
|
|
return SoundEvents.VILLAGER_TRADE;
|
|
}
|
|
return SoundEvents.VILLAGER_AMBIENT;
|
|
}
|
|
|
|
@Override
|
|
protected SoundEvent getHurtSound(final DamageSource ajw) {
|
|
return SoundEvents.VILLAGER_HURT;
|
|
}
|
|
|
|
@Override
|
|
protected SoundEvent getDeathSound() {
|
|
return SoundEvents.VILLAGER_DEATH;
|
|
}
|
|
|
|
public void playWorkSound() {
|
|
final SoundEvent aah2 = this.getVillagerData().getProfession().getWorkSound();
|
|
if (aah2 != null) {
|
|
this.playSound(aah2, this.getSoundVolume(), this.getVoicePitch());
|
|
}
|
|
}
|
|
|
|
public void setVillagerData(final VillagerData axu) {
|
|
final VillagerData axu2 = this.getVillagerData();
|
|
if (axu2.getProfession() != axu.getProfession()) {
|
|
this.offers = null;
|
|
}
|
|
this.entityData.<VillagerData>set(Villager.DATA_VILLAGER_DATA, axu);
|
|
}
|
|
|
|
@Override
|
|
public VillagerData getVillagerData() {
|
|
return this.entityData.<VillagerData>get(Villager.DATA_VILLAGER_DATA);
|
|
}
|
|
|
|
@Override
|
|
protected void rewardTradeXp(final MerchantOffer biw) {
|
|
int integer3 = 3 + this.random.nextInt(4);
|
|
this.villagerXp += biw.getXp();
|
|
this.lastTradedPlayer = this.getTradingPlayer();
|
|
if (this.shouldIncreaseLevel()) {
|
|
this.updateMerchantTimer = 40;
|
|
this.increaseProfessionLevelOnUpdate = true;
|
|
integer3 += 5;
|
|
}
|
|
if (biw.shouldRewardExp()) {
|
|
this.level.addFreshEntity(new ExperienceOrb(this.level, this.getX(), this.getY() + 0.5, this.getZ(), integer3));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setLastHurtByMob(@Nullable final LivingEntity akw) {
|
|
if (akw != null && this.level instanceof ServerLevel) {
|
|
((ServerLevel)this.level).onReputationEvent(ReputationEventType.VILLAGER_HURT, akw, this);
|
|
if (this.isAlive() && akw instanceof Player) {
|
|
this.level.broadcastEntityEvent(this, (byte)13);
|
|
}
|
|
}
|
|
super.setLastHurtByMob(akw);
|
|
}
|
|
|
|
@Override
|
|
public void die(final DamageSource ajw) {
|
|
Villager.LOGGER.info("Villager {} died, message: '{}'", this, ajw.getLocalizedDeathMessage(this).getString());
|
|
final Entity akn3 = ajw.getEntity();
|
|
if (akn3 != null) {
|
|
this.tellWitnessesThatIWasMurdered(akn3);
|
|
}
|
|
this.releasePoi(MemoryModuleType.HOME);
|
|
this.releasePoi(MemoryModuleType.JOB_SITE);
|
|
this.releasePoi(MemoryModuleType.MEETING_POINT);
|
|
super.die(ajw);
|
|
}
|
|
|
|
private void tellWitnessesThatIWasMurdered(final Entity akn) {
|
|
if (!(this.level instanceof ServerLevel)) {
|
|
return;
|
|
}
|
|
final Optional<List<LivingEntity>> optional3 = this.brain.<List<LivingEntity>>getMemory(MemoryModuleType.VISIBLE_LIVING_ENTITIES);
|
|
if (!optional3.isPresent()) {
|
|
return;
|
|
}
|
|
final ServerLevel xd4 = (ServerLevel)this.level;
|
|
optional3.get().stream().filter(akw -> akw instanceof ReputationEventHandler).forEach(akw -> xd4.onReputationEvent(ReputationEventType.VILLAGER_KILLED, akn, akw));
|
|
}
|
|
|
|
public void releasePoi(final MemoryModuleType<GlobalPos> ari) {
|
|
if (!(this.level instanceof ServerLevel)) {
|
|
return;
|
|
}
|
|
final MinecraftServer minecraftServer3 = ((ServerLevel)this.level).getServer();
|
|
final ServerLevel xd5;
|
|
final PoiManager aso6;
|
|
final Optional<PoiType> optional7;
|
|
final BiPredicate<Villager, PoiType> biPredicate8;
|
|
this.brain.<GlobalPos>getMemory(ari).ifPresent(fr -> {
|
|
xd5 = minecraftServer3.getLevel(fr.dimension());
|
|
aso6 = xd5.getPoiManager();
|
|
optional7 = aso6.getType(fr.pos());
|
|
biPredicate8 = Villager.POI_MEMORIES.get(ari);
|
|
if (optional7.isPresent() && biPredicate8.test(this, optional7.get())) {
|
|
aso6.release(fr.pos());
|
|
DebugPackets.sendPoiTicketCountPacket(xd5, fr.pos());
|
|
}
|
|
});
|
|
}
|
|
|
|
public boolean canBreed() {
|
|
return this.foodLevel + this.countFoodPointsInInventory() >= 12 && this.getAge() == 0;
|
|
}
|
|
|
|
private boolean hungry() {
|
|
return this.foodLevel < 12;
|
|
}
|
|
|
|
private void eatUntilFull() {
|
|
if (!this.hungry() || this.countFoodPointsInInventory() == 0) {
|
|
return;
|
|
}
|
|
for (int integer2 = 0; integer2 < this.getInventory().getContainerSize(); ++integer2) {
|
|
final ItemStack bek3 = this.getInventory().getItem(integer2);
|
|
if (!bek3.isEmpty()) {
|
|
final Integer integer3 = Villager.FOOD_POINTS.get(bek3.getItem());
|
|
if (integer3 != null) {
|
|
int integer5;
|
|
for (int integer4 = integer5 = bek3.getCount(); integer5 > 0; --integer5) {
|
|
this.foodLevel += (byte)integer3;
|
|
this.getInventory().removeItem(integer2, 1);
|
|
if (!this.hungry()) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public int getPlayerReputation(final Player ayg) {
|
|
return this.gossips.getReputation(ayg.getUUID(), arg -> true);
|
|
}
|
|
|
|
private void digestFood(final int integer) {
|
|
this.foodLevel -= (byte)integer;
|
|
}
|
|
|
|
public void eatAndDigestFood() {
|
|
this.eatUntilFull();
|
|
this.digestFood(12);
|
|
}
|
|
|
|
public void setOffers(final MerchantOffers bix) {
|
|
this.offers = bix;
|
|
}
|
|
|
|
private boolean shouldIncreaseLevel() {
|
|
final int integer2 = this.getVillagerData().getLevel();
|
|
return VillagerData.canLevelUp(integer2) && this.villagerXp >= VillagerData.getMaxXpPerLevel(integer2);
|
|
}
|
|
|
|
private void increaseMerchantCareer() {
|
|
this.setVillagerData(this.getVillagerData().setLevel(this.getVillagerData().getLevel() + 1));
|
|
this.updateTrades();
|
|
}
|
|
|
|
@Override
|
|
protected Component getTypeName() {
|
|
return new TranslatableComponent(this.getType().getDescriptionId() + '.' + Registry.VILLAGER_PROFESSION.getKey(this.getVillagerData().getProfession()).getPath(), new Object[0]);
|
|
}
|
|
|
|
@Override
|
|
public void handleEntityEvent(final byte byte1) {
|
|
if (byte1 == 12) {
|
|
this.addParticlesAroundSelf(ParticleTypes.HEART);
|
|
}
|
|
else if (byte1 == 13) {
|
|
this.addParticlesAroundSelf(ParticleTypes.ANGRY_VILLAGER);
|
|
}
|
|
else if (byte1 == 14) {
|
|
this.addParticlesAroundSelf(ParticleTypes.HAPPY_VILLAGER);
|
|
}
|
|
else if (byte1 == 42) {
|
|
this.addParticlesAroundSelf(ParticleTypes.SPLASH);
|
|
}
|
|
else {
|
|
super.handleEntityEvent(byte1);
|
|
}
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
public SpawnGroupData finalizeSpawn(final LevelAccessor bju, final DifficultyInstance ajg, final MobSpawnType akz, @Nullable final SpawnGroupData alj, @Nullable final CompoundTag jt) {
|
|
if (akz == MobSpawnType.BREEDING) {
|
|
this.setVillagerData(this.getVillagerData().setProfession(VillagerProfession.NONE));
|
|
}
|
|
if (akz == MobSpawnType.COMMAND || akz == MobSpawnType.SPAWN_EGG || akz == MobSpawnType.SPAWNER || akz == MobSpawnType.DISPENSER) {
|
|
this.setVillagerData(this.getVillagerData().setType(VillagerType.byBiome(bju.getBiome(new BlockPos(this)))));
|
|
}
|
|
return super.finalizeSpawn(bju, ajg, akz, alj, jt);
|
|
}
|
|
|
|
@Override
|
|
public Villager getBreedOffspring(final AgableMob akl) {
|
|
final double double4 = this.random.nextDouble();
|
|
VillagerType axy3;
|
|
if (double4 < 0.5) {
|
|
axy3 = VillagerType.byBiome(this.level.getBiome(new BlockPos(this)));
|
|
}
|
|
else if (double4 < 0.75) {
|
|
axy3 = this.getVillagerData().getType();
|
|
}
|
|
else {
|
|
axy3 = ((Villager)akl).getVillagerData().getType();
|
|
}
|
|
final Villager axt6 = new Villager(EntityType.VILLAGER, this.level, axy3);
|
|
axt6.finalizeSpawn(this.level, this.level.getCurrentDifficultyAt(new BlockPos(axt6)), MobSpawnType.BREEDING, null, null);
|
|
return axt6;
|
|
}
|
|
|
|
@Override
|
|
public void thunderHit(final LightningBolt avv) {
|
|
final Witch axk3 = EntityType.WITCH.create(this.level);
|
|
axk3.moveTo(this.getX(), this.getY(), this.getZ(), this.yRot, this.xRot);
|
|
axk3.finalizeSpawn(this.level, this.level.getCurrentDifficultyAt(new BlockPos(axk3)), MobSpawnType.CONVERSION, null, null);
|
|
axk3.setNoAi(this.isNoAi());
|
|
if (this.hasCustomName()) {
|
|
axk3.setCustomName(this.getCustomName());
|
|
axk3.setCustomNameVisible(this.isCustomNameVisible());
|
|
}
|
|
this.level.addFreshEntity(axk3);
|
|
this.remove();
|
|
}
|
|
|
|
@Override
|
|
protected void pickUpItem(final ItemEntity avy) {
|
|
final ItemStack bek3 = avy.getItem();
|
|
final Item bef4 = bek3.getItem();
|
|
if (this.wantToPickUp(bef4)) {
|
|
final SimpleContainer ajn5 = this.getInventory();
|
|
boolean boolean6 = false;
|
|
for (int integer7 = 0; integer7 < ajn5.getContainerSize(); ++integer7) {
|
|
final ItemStack bek4 = ajn5.getItem(integer7);
|
|
if (bek4.isEmpty() || (bek4.getItem() == bef4 && bek4.getCount() < bek4.getMaxStackSize())) {
|
|
boolean6 = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!boolean6) {
|
|
return;
|
|
}
|
|
int integer7 = ajn5.countItem(bef4);
|
|
if (integer7 == 256) {
|
|
return;
|
|
}
|
|
if (integer7 > 256) {
|
|
ajn5.removeItemType(bef4, integer7 - 256);
|
|
return;
|
|
}
|
|
this.take(avy, bek3.getCount());
|
|
final ItemStack bek4 = ajn5.addItem(bek3);
|
|
if (bek4.isEmpty()) {
|
|
avy.remove();
|
|
}
|
|
else {
|
|
bek3.setCount(bek4.getCount());
|
|
}
|
|
}
|
|
}
|
|
|
|
public boolean wantToPickUp(final Item bef) {
|
|
return Villager.WANTED_ITEMS.contains(bef) || this.getVillagerData().getProfession().getRequestedItems().contains(bef);
|
|
}
|
|
|
|
public boolean hasExcessFood() {
|
|
return this.countFoodPointsInInventory() >= 24;
|
|
}
|
|
|
|
public boolean wantsMoreFood() {
|
|
return this.countFoodPointsInInventory() < 12;
|
|
}
|
|
|
|
private int countFoodPointsInInventory() {
|
|
final SimpleContainer ajn2 = this.getInventory();
|
|
return Villager.FOOD_POINTS.entrySet().stream().mapToInt(entry -> ajn2.countItem(entry.getKey()) * (int)entry.getValue()).sum();
|
|
}
|
|
|
|
private void makeBread() {
|
|
final SimpleContainer ajn2 = this.getInventory();
|
|
final int integer3 = ajn2.countItem(Items.WHEAT);
|
|
final int integer4 = integer3 / 3;
|
|
if (integer4 == 0) {
|
|
return;
|
|
}
|
|
final int integer5 = integer4 * 3;
|
|
ajn2.removeItemType(Items.WHEAT, integer5);
|
|
final ItemStack bek6 = ajn2.addItem(new ItemStack(Items.BREAD, integer4));
|
|
if (!bek6.isEmpty()) {
|
|
this.spawnAtLocation(bek6, 0.5f);
|
|
}
|
|
}
|
|
|
|
public boolean hasFarmSeeds() {
|
|
final SimpleContainer ajn2 = this.getInventory();
|
|
return ajn2.hasAnyOf(ImmutableSet.<Item>of(Items.WHEAT_SEEDS, Items.POTATO, Items.CARROT, Items.BEETROOT_SEEDS));
|
|
}
|
|
|
|
@Override
|
|
protected void updateTrades() {
|
|
final VillagerData axu2 = this.getVillagerData();
|
|
final Int2ObjectMap<VillagerTrades.ItemListing[]> int2ObjectMap3 = VillagerTrades.TRADES.get(axu2.getProfession());
|
|
if (int2ObjectMap3 == null || int2ObjectMap3.isEmpty()) {
|
|
return;
|
|
}
|
|
final VillagerTrades.ItemListing[] arr4 = (VillagerTrades.ItemListing[])int2ObjectMap3.get(axu2.getLevel());
|
|
if (arr4 == null) {
|
|
return;
|
|
}
|
|
final MerchantOffers bix5 = this.getOffers();
|
|
this.addOffersFromItemListings(bix5, arr4, 2);
|
|
}
|
|
|
|
public void gossip(final Villager axt, final long long2) {
|
|
if ((long2 >= this.lastGossipTime && long2 < this.lastGossipTime + 1200L) || (long2 >= axt.lastGossipTime && long2 < axt.lastGossipTime + 1200L)) {
|
|
return;
|
|
}
|
|
this.gossips.transferFrom(axt.gossips, this.random, 10);
|
|
this.lastGossipTime = long2;
|
|
this.spawnGolemIfNeeded(axt.lastGossipTime = long2, 5);
|
|
}
|
|
|
|
private void maybeDecayGossip() {
|
|
final long long2 = this.level.getGameTime();
|
|
if (this.lastGossipDecayTime == 0L) {
|
|
this.lastGossipDecayTime = long2;
|
|
return;
|
|
}
|
|
if (long2 < this.lastGossipDecayTime + 24000L) {
|
|
return;
|
|
}
|
|
this.gossips.decay();
|
|
this.lastGossipDecayTime = long2;
|
|
}
|
|
|
|
public void spawnGolemIfNeeded(final long long1, final int integer) {
|
|
if (!this.wantsToSpawnGolem(long1)) {
|
|
return;
|
|
}
|
|
final AABB cvc5 = this.getBoundingBox().inflate(10.0, 10.0, 10.0);
|
|
final List<Villager> list6 = this.level.<Villager>getEntitiesOfClass(Villager.class, cvc5);
|
|
final List<Villager> list7 = list6.stream().filter(axt -> axt.wantsToSpawnGolem(long1)).limit(5L).collect(Collectors.toList());
|
|
if (list7.size() < integer) {
|
|
return;
|
|
}
|
|
final IronGolem ati8 = this.trySpawnGolem();
|
|
if (ati8 == null) {
|
|
return;
|
|
}
|
|
list6.forEach(axt -> axt.sawGolem(long1));
|
|
}
|
|
|
|
private void sawGolem(final long long1) {
|
|
this.brain.<Long>setMemory(MemoryModuleType.GOLEM_LAST_SEEN_TIME, long1);
|
|
}
|
|
|
|
private boolean hasSeenGolemRecently(final long long1) {
|
|
final Optional<Long> optional4 = this.brain.<Long>getMemory(MemoryModuleType.GOLEM_LAST_SEEN_TIME);
|
|
if (!optional4.isPresent()) {
|
|
return false;
|
|
}
|
|
final Long long2 = optional4.get();
|
|
return long1 - long2 <= 600L;
|
|
}
|
|
|
|
public boolean wantsToSpawnGolem(final long long1) {
|
|
final VillagerData axu4 = this.getVillagerData();
|
|
return axu4.getProfession() != VillagerProfession.NONE && axu4.getProfession() != VillagerProfession.NITWIT && this.golemSpawnConditionsMet(this.level.getGameTime()) && !this.hasSeenGolemRecently(long1);
|
|
}
|
|
|
|
@Nullable
|
|
private IronGolem trySpawnGolem() {
|
|
final BlockPos fk2 = new BlockPos(this);
|
|
for (int integer3 = 0; integer3 < 10; ++integer3) {
|
|
final double double4 = this.level.random.nextInt(16) - 8;
|
|
final double double5 = this.level.random.nextInt(16) - 8;
|
|
double double6 = 6.0;
|
|
for (int integer4 = 0; integer4 >= -12; --integer4) {
|
|
final BlockPos fk3 = fk2.offset(double4, double6 + integer4, double5);
|
|
if ((this.level.getBlockState(fk3).isAir() || this.level.getBlockState(fk3).getMaterial().isLiquid()) && this.level.getBlockState(fk3.below()).getMaterial().isSolidBlocking()) {
|
|
double6 += integer4;
|
|
break;
|
|
}
|
|
}
|
|
final BlockPos fk4 = fk2.offset(double4, double6, double5);
|
|
final IronGolem ati11 = EntityType.IRON_GOLEM.create(this.level, null, null, null, fk4, MobSpawnType.MOB_SUMMONED, false, false);
|
|
if (ati11 != null) {
|
|
if (ati11.checkSpawnRules(this.level, MobSpawnType.MOB_SUMMONED) && ati11.checkSpawnObstruction(this.level)) {
|
|
this.level.addFreshEntity(ati11);
|
|
return ati11;
|
|
}
|
|
ati11.remove();
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public void onReputationEventFrom(final ReputationEventType asl, final Entity akn) {
|
|
if (asl == ReputationEventType.ZOMBIE_VILLAGER_CURED) {
|
|
this.gossips.add(akn.getUUID(), GossipType.MAJOR_POSITIVE, 20);
|
|
this.gossips.add(akn.getUUID(), GossipType.MINOR_POSITIVE, 25);
|
|
}
|
|
else if (asl == ReputationEventType.TRADE) {
|
|
this.gossips.add(akn.getUUID(), GossipType.TRADING, 2);
|
|
}
|
|
else if (asl == ReputationEventType.VILLAGER_HURT) {
|
|
this.gossips.add(akn.getUUID(), GossipType.MINOR_NEGATIVE, 25);
|
|
}
|
|
else if (asl == ReputationEventType.VILLAGER_KILLED) {
|
|
this.gossips.add(akn.getUUID(), GossipType.MAJOR_NEGATIVE, 25);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int getVillagerXp() {
|
|
return this.villagerXp;
|
|
}
|
|
|
|
public void setVillagerXp(final int integer) {
|
|
this.villagerXp = integer;
|
|
}
|
|
|
|
private void resetNumberOfRestocks() {
|
|
this.catchUpDemand();
|
|
this.numberOfRestocksToday = 0;
|
|
}
|
|
|
|
public GossipContainer getGossips() {
|
|
return this.gossips;
|
|
}
|
|
|
|
public void setGossips(final Tag kj) {
|
|
this.gossips.update(new Dynamic((DynamicOps)NbtOps.INSTANCE, kj));
|
|
}
|
|
|
|
@Override
|
|
protected void sendDebugPackets() {
|
|
super.sendDebugPackets();
|
|
DebugPackets.sendEntityBrain(this);
|
|
}
|
|
|
|
@Override
|
|
public void startSleeping(final BlockPos fk) {
|
|
super.startSleeping(fk);
|
|
this.brain.<SerializableLong>setMemory(MemoryModuleType.LAST_SLEPT, SerializableLong.of(this.level.getGameTime()));
|
|
}
|
|
|
|
@Override
|
|
public void stopSleeping() {
|
|
super.stopSleeping();
|
|
this.brain.<SerializableLong>setMemory(MemoryModuleType.LAST_WOKEN, SerializableLong.of(this.level.getGameTime()));
|
|
}
|
|
|
|
private boolean golemSpawnConditionsMet(final long long1) {
|
|
final Optional<SerializableLong> optional4 = this.brain.<SerializableLong>getMemory(MemoryModuleType.LAST_SLEPT);
|
|
final Optional<SerializableLong> optional5 = this.brain.<SerializableLong>getMemory(MemoryModuleType.LAST_WORKED_AT_POI);
|
|
return optional4.isPresent() && optional5.isPresent() && long1 - optional4.get().value() < 24000L && long1 - optional5.get().value() < 36000L;
|
|
}
|
|
|
|
static {
|
|
DATA_VILLAGER_DATA = SynchedEntityData.<VillagerData>defineId(Villager.class, EntityDataSerializers.VILLAGER_DATA);
|
|
FOOD_POINTS = ImmutableMap.<Item, Integer>of(Items.BREAD, Integer.valueOf(4), Items.POTATO, Integer.valueOf(1), Items.CARROT, Integer.valueOf(1), Items.BEETROOT, Integer.valueOf(1));
|
|
WANTED_ITEMS = ImmutableSet.<Item>of(Items.BREAD, Items.POTATO, Items.CARROT, Items.WHEAT, Items.WHEAT_SEEDS, Items.BEETROOT, new Item[] { Items.BEETROOT_SEEDS });
|
|
MEMORY_TYPES = ImmutableList.<MemoryModuleType<GlobalPos>>of(MemoryModuleType.HOME, MemoryModuleType.JOB_SITE, MemoryModuleType.MEETING_POINT, (MemoryModuleType<GlobalPos>)MemoryModuleType.LIVING_ENTITIES, (MemoryModuleType<GlobalPos>)MemoryModuleType.VISIBLE_LIVING_ENTITIES, (MemoryModuleType<GlobalPos>)MemoryModuleType.VISIBLE_VILLAGER_BABIES, (MemoryModuleType<GlobalPos>)MemoryModuleType.NEAREST_PLAYERS, (MemoryModuleType<GlobalPos>)MemoryModuleType.NEAREST_VISIBLE_PLAYER, (MemoryModuleType<GlobalPos>)MemoryModuleType.WALK_TARGET, (MemoryModuleType<GlobalPos>)MemoryModuleType.LOOK_TARGET, (MemoryModuleType<GlobalPos>)MemoryModuleType.INTERACTION_TARGET, (MemoryModuleType<GlobalPos>)MemoryModuleType.BREED_TARGET, MemoryModuleType.PATH, MemoryModuleType.INTERACTABLE_DOORS, MemoryModuleType.OPENED_DOORS, 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_LAST_SEEN_TIME);
|
|
SENSOR_TYPES = ImmutableList.<SensorType<NearestLivingEntitySensor>>of(SensorType.NEAREST_LIVING_ENTITIES, (SensorType<NearestLivingEntitySensor>)SensorType.NEAREST_PLAYERS, (SensorType<NearestLivingEntitySensor>)SensorType.INTERACTABLE_DOORS, (SensorType<NearestLivingEntitySensor>)SensorType.NEAREST_BED, (SensorType<NearestLivingEntitySensor>)SensorType.HURT_BY, (SensorType<NearestLivingEntitySensor>)SensorType.VILLAGER_HOSTILES, (SensorType<NearestLivingEntitySensor>)SensorType.VILLAGER_BABIES, (SensorType<NearestLivingEntitySensor>)SensorType.SECONDARY_POIS, (SensorType<NearestLivingEntitySensor>)SensorType.GOLEM_LAST_SEEN);
|
|
POI_MEMORIES = ImmutableMap.<MemoryModuleType<GlobalPos>, Object>of(MemoryModuleType.HOME, (axt, asr) -> asr == PoiType.HOME, MemoryModuleType.JOB_SITE, (axt, asr) -> axt.getVillagerData().getProfession().getJobPoiType() == asr, MemoryModuleType.MEETING_POINT, (axt, asr) -> asr == PoiType.MEETING);
|
|
}
|
|
}
|