minecraft-src/net/minecraft/world/entity/npc/WanderingTraderSpawner.java
2025-07-04 01:41:11 +03:00

159 lines
5.7 KiB
Java

package net.minecraft.world.entity.npc;
import java.util.Optional;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.tags.BiomeTags;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.MobSpawnType;
import net.minecraft.world.entity.SpawnPlacementType;
import net.minecraft.world.entity.SpawnPlacements;
import net.minecraft.world.entity.ai.village.poi.PoiManager;
import net.minecraft.world.entity.ai.village.poi.PoiTypes;
import net.minecraft.world.entity.animal.horse.TraderLlama;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.CustomSpawner;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.storage.ServerLevelData;
import org.jetbrains.annotations.Nullable;
public class WanderingTraderSpawner implements CustomSpawner {
private static final int DEFAULT_TICK_DELAY = 1200;
public static final int DEFAULT_SPAWN_DELAY = 24000;
private static final int MIN_SPAWN_CHANCE = 25;
private static final int MAX_SPAWN_CHANCE = 75;
private static final int SPAWN_CHANCE_INCREASE = 25;
private static final int SPAWN_ONE_IN_X_CHANCE = 10;
private static final int NUMBER_OF_SPAWN_ATTEMPTS = 10;
private final RandomSource random = RandomSource.create();
private final ServerLevelData serverLevelData;
private int tickDelay;
private int spawnDelay;
private int spawnChance;
public WanderingTraderSpawner(ServerLevelData serverLevelData) {
this.serverLevelData = serverLevelData;
this.tickDelay = 1200;
this.spawnDelay = serverLevelData.getWanderingTraderSpawnDelay();
this.spawnChance = serverLevelData.getWanderingTraderSpawnChance();
if (this.spawnDelay == 0 && this.spawnChance == 0) {
this.spawnDelay = 24000;
serverLevelData.setWanderingTraderSpawnDelay(this.spawnDelay);
this.spawnChance = 25;
serverLevelData.setWanderingTraderSpawnChance(this.spawnChance);
}
}
@Override
public int tick(ServerLevel level, boolean spawnEnemies, boolean spawnFriendlies) {
if (!level.getGameRules().getBoolean(GameRules.RULE_DO_TRADER_SPAWNING)) {
return 0;
} else if (--this.tickDelay > 0) {
return 0;
} else {
this.tickDelay = 1200;
this.spawnDelay -= 1200;
this.serverLevelData.setWanderingTraderSpawnDelay(this.spawnDelay);
if (this.spawnDelay > 0) {
return 0;
} else {
this.spawnDelay = 24000;
if (!level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) {
return 0;
} else {
int i = this.spawnChance;
this.spawnChance = Mth.clamp(this.spawnChance + 25, 25, 75);
this.serverLevelData.setWanderingTraderSpawnChance(this.spawnChance);
if (this.random.nextInt(100) > i) {
return 0;
} else if (this.spawn(level)) {
this.spawnChance = 25;
return 1;
} else {
return 0;
}
}
}
}
}
private boolean spawn(ServerLevel serverLevel) {
Player player = serverLevel.getRandomPlayer();
if (player == null) {
return true;
} else if (this.random.nextInt(10) != 0) {
return false;
} else {
BlockPos blockPos = player.blockPosition();
int i = 48;
PoiManager poiManager = serverLevel.getPoiManager();
Optional<BlockPos> optional = poiManager.find(holder -> holder.is(PoiTypes.MEETING), blockPosx -> true, blockPos, 48, PoiManager.Occupancy.ANY);
BlockPos blockPos2 = (BlockPos)optional.orElse(blockPos);
BlockPos blockPos3 = this.findSpawnPositionNear(serverLevel, blockPos2, 48);
if (blockPos3 != null && this.hasEnoughSpace(serverLevel, blockPos3)) {
if (serverLevel.getBiome(blockPos3).is(BiomeTags.WITHOUT_WANDERING_TRADER_SPAWNS)) {
return false;
}
WanderingTrader wanderingTrader = EntityType.WANDERING_TRADER.spawn(serverLevel, blockPos3, MobSpawnType.EVENT);
if (wanderingTrader != null) {
for (int j = 0; j < 2; j++) {
this.tryToSpawnLlamaFor(serverLevel, wanderingTrader, 4);
}
this.serverLevelData.setWanderingTraderId(wanderingTrader.getUUID());
wanderingTrader.setDespawnDelay(48000);
wanderingTrader.setWanderTarget(blockPos2);
wanderingTrader.restrictTo(blockPos2, 16);
return true;
}
}
return false;
}
}
private void tryToSpawnLlamaFor(ServerLevel serverLevel, WanderingTrader trader, int maxDistance) {
BlockPos blockPos = this.findSpawnPositionNear(serverLevel, trader.blockPosition(), maxDistance);
if (blockPos != null) {
TraderLlama traderLlama = EntityType.TRADER_LLAMA.spawn(serverLevel, blockPos, MobSpawnType.EVENT);
if (traderLlama != null) {
traderLlama.setLeashedTo(trader, true);
}
}
}
@Nullable
private BlockPos findSpawnPositionNear(LevelReader level, BlockPos pos, int maxDistance) {
BlockPos blockPos = null;
SpawnPlacementType spawnPlacementType = SpawnPlacements.getPlacementType(EntityType.WANDERING_TRADER);
for (int i = 0; i < 10; i++) {
int j = pos.getX() + this.random.nextInt(maxDistance * 2) - maxDistance;
int k = pos.getZ() + this.random.nextInt(maxDistance * 2) - maxDistance;
int l = level.getHeight(Heightmap.Types.WORLD_SURFACE, j, k);
BlockPos blockPos2 = new BlockPos(j, l, k);
if (spawnPlacementType.isSpawnPositionOk(level, blockPos2, EntityType.WANDERING_TRADER)) {
blockPos = blockPos2;
break;
}
}
return blockPos;
}
private boolean hasEnoughSpace(BlockGetter level, BlockPos pos) {
for (BlockPos blockPos : BlockPos.betweenClosed(pos, pos.offset(1, 2, 1))) {
if (!level.getBlockState(blockPos).getCollisionShape(level, blockPos).isEmpty()) {
return false;
}
}
return true;
}
}