254 lines
		
	
	
	
		
			7.7 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			254 lines
		
	
	
	
		
			7.7 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.network.syncher.EntityDataAccessor;
 | |
| import net.minecraft.network.syncher.EntityDataSerializers;
 | |
| import net.minecraft.network.syncher.SynchedEntityData;
 | |
| 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.level.storage.ValueInput;
 | |
| import net.minecraft.world.level.storage.ValueOutput;
 | |
| 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(SynchedEntityData.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
 | |
| 	protected void addAdditionalSaveData(ValueOutput output) {
 | |
| 		super.addAdditionalSaveData(output);
 | |
| 		if (!this.level().isClientSide) {
 | |
| 			MerchantOffers merchantOffers = this.getOffers();
 | |
| 			if (!merchantOffers.isEmpty()) {
 | |
| 				output.store("Offers", MerchantOffers.CODEC, merchantOffers);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		this.writeInventoryToTag(output);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected void readAdditionalSaveData(ValueInput input) {
 | |
| 		super.readAdditionalSaveData(input);
 | |
| 		this.offers = (MerchantOffers)input.read("Offers", MerchantOffers.CODEC).orElse(null);
 | |
| 		this.readInventoryFromTag(input);
 | |
| 	}
 | |
| 
 | |
| 	@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);
 | |
| 	}
 | |
| }
 |