minecraft-src/net/minecraft/world/entity/npc/AbstractVillager.java
2025-07-04 03:45:38 +03:00

255 lines
7.8 KiB
Java

package net.minecraft.world.entity.npc;
import com.google.common.collect.Lists;
import java.util.ArrayList;
import net.minecraft.advancements.CriteriaTriggers;
import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtOps;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.network.syncher.SynchedEntityData.Builder;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.util.Mth;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.SimpleContainer;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.AgeableMob;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntitySpawnReason;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.SlotAccess;
import net.minecraft.world.entity.SpawnGroupData;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.trading.Merchant;
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.pathfinder.PathType;
import net.minecraft.world.level.portal.TeleportTransition;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
public abstract class AbstractVillager extends AgeableMob implements InventoryCarrier, Npc, Merchant {
private static final EntityDataAccessor<Integer> DATA_UNHAPPY_COUNTER = SynchedEntityData.defineId(AbstractVillager.class, EntityDataSerializers.INT);
public static final int VILLAGER_SLOT_OFFSET = 300;
private static final int VILLAGER_INVENTORY_SIZE = 8;
@Nullable
private Player tradingPlayer;
@Nullable
protected MerchantOffers offers;
private final SimpleContainer inventory = new SimpleContainer(8);
public AbstractVillager(EntityType<? extends AbstractVillager> entityType, Level level) {
super(entityType, level);
this.setPathfindingMalus(PathType.DANGER_FIRE, 16.0F);
this.setPathfindingMalus(PathType.DAMAGE_FIRE, -1.0F);
}
@Override
public SpawnGroupData finalizeSpawn(
ServerLevelAccessor level, DifficultyInstance difficulty, EntitySpawnReason spawnReason, @Nullable SpawnGroupData spawnGroupData
) {
if (spawnGroupData == null) {
spawnGroupData = new AgeableMob.AgeableMobGroupData(false);
}
return super.finalizeSpawn(level, difficulty, spawnReason, spawnGroupData);
}
public int getUnhappyCounter() {
return this.entityData.get(DATA_UNHAPPY_COUNTER);
}
public void setUnhappyCounter(int unhappyCounter) {
this.entityData.set(DATA_UNHAPPY_COUNTER, unhappyCounter);
}
@Override
public int getVillagerXp() {
return 0;
}
@Override
protected void defineSynchedData(Builder builder) {
super.defineSynchedData(builder);
builder.define(DATA_UNHAPPY_COUNTER, 0);
}
@Override
public void setTradingPlayer(@Nullable Player tradingPlayer) {
this.tradingPlayer = tradingPlayer;
}
@Nullable
@Override
public Player getTradingPlayer() {
return this.tradingPlayer;
}
public boolean isTrading() {
return this.tradingPlayer != null;
}
@Override
public MerchantOffers getOffers() {
if (this.level().isClientSide) {
throw new IllegalStateException("Cannot load Villager offers on the client");
} else {
if (this.offers == null) {
this.offers = new MerchantOffers();
this.updateTrades();
}
return this.offers;
}
}
@Override
public void overrideOffers(@Nullable MerchantOffers offers) {
}
@Override
public void overrideXp(int xp) {
}
@Override
public void notifyTrade(MerchantOffer offer) {
offer.increaseUses();
this.ambientSoundTime = -this.getAmbientSoundInterval();
this.rewardTradeXp(offer);
if (this.tradingPlayer instanceof ServerPlayer) {
CriteriaTriggers.TRADE.trigger((ServerPlayer)this.tradingPlayer, this, offer.getResult());
}
}
protected abstract void rewardTradeXp(MerchantOffer offer);
@Override
public boolean showProgressBar() {
return true;
}
@Override
public void notifyTradeUpdated(ItemStack stack) {
if (!this.level().isClientSide && this.ambientSoundTime > -this.getAmbientSoundInterval() + 20) {
this.ambientSoundTime = -this.getAmbientSoundInterval();
this.makeSound(this.getTradeUpdatedSound(!stack.isEmpty()));
}
}
@Override
public SoundEvent getNotifyTradeSound() {
return SoundEvents.VILLAGER_YES;
}
protected SoundEvent getTradeUpdatedSound(boolean isYesSound) {
return isYesSound ? SoundEvents.VILLAGER_YES : SoundEvents.VILLAGER_NO;
}
public void playCelebrateSound() {
this.makeSound(SoundEvents.VILLAGER_CELEBRATE);
}
@Override
public void addAdditionalSaveData(CompoundTag tag) {
super.addAdditionalSaveData(tag);
if (!this.level().isClientSide) {
MerchantOffers merchantOffers = this.getOffers();
if (!merchantOffers.isEmpty()) {
tag.store("Offers", MerchantOffers.CODEC, this.registryAccess().createSerializationContext(NbtOps.INSTANCE), merchantOffers);
}
}
this.writeInventoryToTag(tag, this.registryAccess());
}
@Override
public void readAdditionalSaveData(CompoundTag tag) {
super.readAdditionalSaveData(tag);
this.offers = (MerchantOffers)tag.read("Offers", MerchantOffers.CODEC, this.registryAccess().createSerializationContext(NbtOps.INSTANCE)).orElse(null);
this.readInventoryFromTag(tag, this.registryAccess());
}
@Nullable
@Override
public Entity teleport(TeleportTransition teleportTransition) {
this.stopTrading();
return super.teleport(teleportTransition);
}
protected void stopTrading() {
this.setTradingPlayer(null);
}
@Override
public void die(DamageSource damageSource) {
super.die(damageSource);
this.stopTrading();
}
protected void addParticlesAroundSelf(ParticleOptions particleOption) {
for (int i = 0; i < 5; i++) {
double d = this.random.nextGaussian() * 0.02;
double e = this.random.nextGaussian() * 0.02;
double f = this.random.nextGaussian() * 0.02;
this.level().addParticle(particleOption, this.getRandomX(1.0), this.getRandomY() + 1.0, this.getRandomZ(1.0), d, e, f);
}
}
@Override
public boolean canBeLeashed() {
return false;
}
@Override
public SimpleContainer getInventory() {
return this.inventory;
}
@Override
public SlotAccess getSlot(int slot) {
int i = slot - 300;
return i >= 0 && i < this.inventory.getContainerSize() ? SlotAccess.forContainer(this.inventory, i) : super.getSlot(slot);
}
protected abstract void updateTrades();
/**
* Adds limited numbers of trades to the given {@link net.minecraft.world.item.trading.MerchantOffers}.
*/
protected void addOffersFromItemListings(MerchantOffers givenMerchantOffers, VillagerTrades.ItemListing[] newTrades, int maxNumbers) {
ArrayList<VillagerTrades.ItemListing> arrayList = Lists.newArrayList(newTrades);
int i = 0;
while (i < maxNumbers && !arrayList.isEmpty()) {
MerchantOffer merchantOffer = ((VillagerTrades.ItemListing)arrayList.remove(this.random.nextInt(arrayList.size()))).getOffer(this, this.random);
if (merchantOffer != null) {
givenMerchantOffers.add(merchantOffer);
i++;
}
}
}
@Override
public Vec3 getRopeHoldPosition(float partialTicks) {
float f = Mth.lerp(partialTicks, this.yBodyRotO, this.yBodyRot) * (float) (Math.PI / 180.0);
Vec3 vec3 = new Vec3(0.0, this.getBoundingBox().getYsize() - 1.0, 0.2);
return this.getPosition(partialTicks).add(vec3.yRot(-f));
}
@Override
public boolean isClientSide() {
return this.level().isClientSide;
}
@Override
public boolean stillValid(Player player) {
return this.getTradingPlayer() == player && this.isAlive() && player.canInteractWithEntity(this, 4.0);
}
}