/* * Decompiled with CFR 0.152. * * Could not load the following classes: * org.apache.commons.lang3.tuple.Pair * org.jspecify.annotations.Nullable */ package net.minecraft.world.entity.npc; import java.util.EnumSet; import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerLevel; import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundEvents; import net.minecraft.stats.Stats; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.AgeableMob; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.ExperienceOrb; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.ai.goal.AvoidEntityGoal; import net.minecraft.world.entity.ai.goal.FloatGoal; import net.minecraft.world.entity.ai.goal.Goal; import net.minecraft.world.entity.ai.goal.InteractGoal; import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal; import net.minecraft.world.entity.ai.goal.LookAtTradingPlayerGoal; import net.minecraft.world.entity.ai.goal.MoveTowardsRestrictionGoal; import net.minecraft.world.entity.ai.goal.PanicGoal; import net.minecraft.world.entity.ai.goal.TradeWithPlayerGoal; import net.minecraft.world.entity.ai.goal.UseItemGoal; import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal; import net.minecraft.world.entity.monster.Evoker; import net.minecraft.world.entity.monster.Illusioner; import net.minecraft.world.entity.monster.Pillager; import net.minecraft.world.entity.monster.Vex; import net.minecraft.world.entity.monster.Vindicator; import net.minecraft.world.entity.monster.Zoglin; import net.minecraft.world.entity.monster.Zombie; import net.minecraft.world.entity.npc.AbstractVillager; import net.minecraft.world.entity.npc.VillagerTrades; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.item.alchemy.PotionContents; import net.minecraft.world.item.alchemy.Potions; import net.minecraft.world.item.component.Consumable; 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.storage.ValueInput; import net.minecraft.world.level.storage.ValueOutput; import net.minecraft.world.phys.Vec3; import org.apache.commons.lang3.tuple.Pair; import org.jspecify.annotations.Nullable; public class WanderingTrader extends AbstractVillager implements Consumable.OverrideConsumeSound { private static final int DEFAULT_DESPAWN_DELAY = 0; private @Nullable BlockPos wanderTarget; private int despawnDelay = 0; public WanderingTrader(EntityType type, Level level) { super((EntityType)type, level); } @Override protected void registerGoals() { this.goalSelector.addGoal(0, new FloatGoal(this)); this.goalSelector.addGoal(0, new UseItemGoal(this, PotionContents.createItemStack(Items.POTION, Potions.INVISIBILITY), SoundEvents.WANDERING_TRADER_DISAPPEARED, e -> this.level().isDarkOutside() && !e.isInvisible())); this.goalSelector.addGoal(0, new UseItemGoal(this, new ItemStack(Items.MILK_BUCKET), SoundEvents.WANDERING_TRADER_REAPPEARED, e -> this.level().isBrightOutside() && e.isInvisible())); this.goalSelector.addGoal(1, new TradeWithPlayerGoal(this)); this.goalSelector.addGoal(1, new AvoidEntityGoal(this, Zombie.class, 8.0f, 0.5, 0.5)); this.goalSelector.addGoal(1, new AvoidEntityGoal(this, Evoker.class, 12.0f, 0.5, 0.5)); this.goalSelector.addGoal(1, new AvoidEntityGoal(this, Vindicator.class, 8.0f, 0.5, 0.5)); this.goalSelector.addGoal(1, new AvoidEntityGoal(this, Vex.class, 8.0f, 0.5, 0.5)); this.goalSelector.addGoal(1, new AvoidEntityGoal(this, Pillager.class, 15.0f, 0.5, 0.5)); this.goalSelector.addGoal(1, new AvoidEntityGoal(this, Illusioner.class, 12.0f, 0.5, 0.5)); this.goalSelector.addGoal(1, new AvoidEntityGoal(this, Zoglin.class, 10.0f, 0.5, 0.5)); this.goalSelector.addGoal(1, new PanicGoal(this, 0.5)); this.goalSelector.addGoal(1, new LookAtTradingPlayerGoal(this)); this.goalSelector.addGoal(2, new WanderToPositionGoal(this, 2.0, 0.35)); this.goalSelector.addGoal(4, new MoveTowardsRestrictionGoal(this, 0.35)); this.goalSelector.addGoal(8, new WaterAvoidingRandomStrollGoal(this, 0.35)); this.goalSelector.addGoal(9, new InteractGoal(this, Player.class, 3.0f, 1.0f)); this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 8.0f)); } @Override public @Nullable AgeableMob getBreedOffspring(ServerLevel level, AgeableMob partner) { return null; } @Override public boolean showProgressBar() { return false; } @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.isBaby()) { if (hand == InteractionHand.MAIN_HAND) { player.awardStat(Stats.TALKED_TO_VILLAGER); } if (!this.level().isClientSide()) { if (this.getOffers().isEmpty()) { return InteractionResult.CONSUME; } this.setTradingPlayer(player); this.openTradingScreen(player, this.getDisplayName(), 1); } return InteractionResult.SUCCESS; } return super.mobInteract(player, hand); } @Override protected void updateTrades(ServerLevel level) { MerchantOffers offers = this.getOffers(); for (Pair tradesAndAmount : VillagerTrades.WANDERING_TRADER_TRADES) { VillagerTrades.ItemListing[] itemListings = (VillagerTrades.ItemListing[])tradesAndAmount.getLeft(); this.addOffersFromItemListings(level, offers, itemListings, (Integer)tradesAndAmount.getRight()); } } @Override protected void addAdditionalSaveData(ValueOutput output) { super.addAdditionalSaveData(output); output.putInt("DespawnDelay", this.despawnDelay); output.storeNullable("wander_target", BlockPos.CODEC, this.wanderTarget); } @Override protected void readAdditionalSaveData(ValueInput input) { super.readAdditionalSaveData(input); this.despawnDelay = input.getIntOr("DespawnDelay", 0); this.wanderTarget = input.read("wander_target", BlockPos.CODEC).orElse(null); this.setAge(Math.max(0, this.getAge())); } @Override public boolean removeWhenFarAway(double distSqr) { return false; } @Override protected void rewardTradeXp(MerchantOffer offer) { if (offer.shouldRewardExp()) { int popXp = 3 + this.random.nextInt(4); this.level().addFreshEntity(new ExperienceOrb(this.level(), this.getX(), this.getY() + 0.5, this.getZ(), popXp)); } } @Override protected SoundEvent getAmbientSound() { if (this.isTrading()) { return SoundEvents.WANDERING_TRADER_TRADE; } return SoundEvents.WANDERING_TRADER_AMBIENT; } @Override protected SoundEvent getHurtSound(DamageSource source) { return SoundEvents.WANDERING_TRADER_HURT; } @Override protected SoundEvent getDeathSound() { return SoundEvents.WANDERING_TRADER_DEATH; } @Override public SoundEvent getConsumeSound(ItemStack itemStack) { if (itemStack.is(Items.MILK_BUCKET)) { return SoundEvents.WANDERING_TRADER_DRINK_MILK; } return SoundEvents.WANDERING_TRADER_DRINK_POTION; } @Override protected SoundEvent getTradeUpdatedSound(boolean validTrade) { return validTrade ? SoundEvents.WANDERING_TRADER_YES : SoundEvents.WANDERING_TRADER_NO; } @Override public SoundEvent getNotifyTradeSound() { return SoundEvents.WANDERING_TRADER_YES; } public void setDespawnDelay(int despawnDelay) { this.despawnDelay = despawnDelay; } public int getDespawnDelay() { return this.despawnDelay; } @Override public void aiStep() { super.aiStep(); if (!this.level().isClientSide()) { this.maybeDespawn(); } } private void maybeDespawn() { if (this.despawnDelay > 0 && !this.isTrading() && --this.despawnDelay == 0) { this.discard(); } } public void setWanderTarget(@Nullable BlockPos pos) { this.wanderTarget = pos; } private @Nullable BlockPos getWanderTarget() { return this.wanderTarget; } private class WanderToPositionGoal extends Goal { final WanderingTrader trader; final double stopDistance; final double speedModifier; WanderToPositionGoal(WanderingTrader trader, double stopDistance, double speedModifier) { this.trader = trader; this.stopDistance = stopDistance; this.speedModifier = speedModifier; this.setFlags(EnumSet.of(Goal.Flag.MOVE)); } @Override public void stop() { this.trader.setWanderTarget(null); WanderingTrader.this.navigation.stop(); } @Override public boolean canUse() { BlockPos wanderPosition = this.trader.getWanderTarget(); return wanderPosition != null && this.isTooFarAway(wanderPosition, this.stopDistance); } @Override public void tick() { BlockPos wanderPosition = this.trader.getWanderTarget(); if (wanderPosition != null && WanderingTrader.this.navigation.isDone()) { if (this.isTooFarAway(wanderPosition, 10.0)) { Vec3 dir = new Vec3((double)wanderPosition.getX() - this.trader.getX(), (double)wanderPosition.getY() - this.trader.getY(), (double)wanderPosition.getZ() - this.trader.getZ()).normalize(); Vec3 targetPos = dir.scale(10.0).add(this.trader.getX(), this.trader.getY(), this.trader.getZ()); WanderingTrader.this.navigation.moveTo(targetPos.x, targetPos.y, targetPos.z, this.speedModifier); } else { WanderingTrader.this.navigation.moveTo(wanderPosition.getX(), wanderPosition.getY(), wanderPosition.getZ(), this.speedModifier); } } } private boolean isTooFarAway(BlockPos pos, double distance) { return !pos.closerToCenterThan(this.trader.position(), distance); } } }