418 lines
19 KiB
Java
418 lines
19 KiB
Java
package net.minecraft.world.level.block.entity.trialspawner;
|
|
|
|
import com.google.common.annotations.VisibleForTesting;
|
|
import com.mojang.serialization.Codec;
|
|
import com.mojang.serialization.MapCodec;
|
|
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
|
import com.mojang.serialization.codecs.RecordCodecBuilder.Instance;
|
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
|
import java.util.Optional;
|
|
import java.util.UUID;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.Direction;
|
|
import net.minecraft.core.Holder;
|
|
import net.minecraft.core.dispenser.DefaultDispenseItemBehavior;
|
|
import net.minecraft.core.particles.ParticleOptions;
|
|
import net.minecraft.core.particles.ParticleTypes;
|
|
import net.minecraft.core.particles.SimpleParticleType;
|
|
import net.minecraft.nbt.CompoundTag;
|
|
import net.minecraft.resources.ResourceKey;
|
|
import net.minecraft.server.level.ServerLevel;
|
|
import net.minecraft.sounds.SoundEvent;
|
|
import net.minecraft.sounds.SoundEvents;
|
|
import net.minecraft.sounds.SoundSource;
|
|
import net.minecraft.util.Mth;
|
|
import net.minecraft.util.RandomSource;
|
|
import net.minecraft.world.Difficulty;
|
|
import net.minecraft.world.entity.Entity;
|
|
import net.minecraft.world.entity.EntitySpawnReason;
|
|
import net.minecraft.world.entity.EntityType;
|
|
import net.minecraft.world.entity.Mob;
|
|
import net.minecraft.world.entity.SpawnPlacements;
|
|
import net.minecraft.world.item.ItemStack;
|
|
import net.minecraft.world.level.ClipContext;
|
|
import net.minecraft.world.level.GameRules;
|
|
import net.minecraft.world.level.Level;
|
|
import net.minecraft.world.level.SpawnData;
|
|
import net.minecraft.world.level.block.TrialSpawnerBlock;
|
|
import net.minecraft.world.level.gameevent.GameEvent;
|
|
import net.minecraft.world.level.storage.loot.LootParams;
|
|
import net.minecraft.world.level.storage.loot.LootTable;
|
|
import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets;
|
|
import net.minecraft.world.phys.BlockHitResult;
|
|
import net.minecraft.world.phys.HitResult;
|
|
import net.minecraft.world.phys.Vec3;
|
|
import net.minecraft.world.phys.shapes.CollisionContext;
|
|
|
|
public final class TrialSpawner {
|
|
public static final String NORMAL_CONFIG_TAG_NAME = "normal_config";
|
|
public static final String OMINOUS_CONFIG_TAG_NAME = "ominous_config";
|
|
public static final int DETECT_PLAYER_SPAWN_BUFFER = 40;
|
|
private static final int DEFAULT_TARGET_COOLDOWN_LENGTH = 36000;
|
|
private static final int DEFAULT_PLAYER_SCAN_RANGE = 14;
|
|
private static final int MAX_MOB_TRACKING_DISTANCE = 47;
|
|
private static final int MAX_MOB_TRACKING_DISTANCE_SQR = Mth.square(47);
|
|
private static final float SPAWNING_AMBIENT_SOUND_CHANCE = 0.02F;
|
|
private Holder<TrialSpawnerConfig> normalConfig;
|
|
private Holder<TrialSpawnerConfig> ominousConfig;
|
|
private final TrialSpawnerData data;
|
|
private final int requiredPlayerRange;
|
|
private final int targetCooldownLength;
|
|
private final TrialSpawner.StateAccessor stateAccessor;
|
|
private PlayerDetector playerDetector;
|
|
private final PlayerDetector.EntitySelector entitySelector;
|
|
private boolean overridePeacefulAndMobSpawnRule;
|
|
private boolean isOminous;
|
|
|
|
public MapCodec<TrialSpawner> codec() {
|
|
return RecordCodecBuilder.mapCodec(
|
|
p_360522_ -> p_360522_.group(
|
|
TrialSpawnerConfig.CODEC
|
|
.optionalFieldOf("normal_config", Holder.direct(TrialSpawnerConfig.DEFAULT))
|
|
.forGetter(p_360521_ -> p_360521_.normalConfig),
|
|
TrialSpawnerConfig.CODEC
|
|
.optionalFieldOf("ominous_config", Holder.direct(TrialSpawnerConfig.DEFAULT))
|
|
.forGetter(p_360515_ -> p_360515_.ominousConfig),
|
|
TrialSpawnerData.MAP_CODEC.forGetter(TrialSpawner::getData),
|
|
Codec.intRange(0, Integer.MAX_VALUE).optionalFieldOf("target_cooldown_length", 36000).forGetter(TrialSpawner::getTargetCooldownLength),
|
|
Codec.intRange(1, 128).optionalFieldOf("required_player_range", 14).forGetter(TrialSpawner::getRequiredPlayerRange)
|
|
)
|
|
.apply(
|
|
p_360522_,
|
|
(p_360516_, p_360517_, p_360518_, p_360519_, p_360520_) -> new TrialSpawner(
|
|
p_360516_, p_360517_, p_360518_, p_360519_, p_360520_, this.stateAccessor, this.playerDetector, this.entitySelector
|
|
)
|
|
)
|
|
);
|
|
}
|
|
|
|
public TrialSpawner(TrialSpawner.StateAccessor p_310216_, PlayerDetector p_309626_, PlayerDetector.EntitySelector p_328170_) {
|
|
this(
|
|
Holder.direct(TrialSpawnerConfig.DEFAULT),
|
|
Holder.direct(TrialSpawnerConfig.DEFAULT),
|
|
new TrialSpawnerData(),
|
|
36000,
|
|
14,
|
|
p_310216_,
|
|
p_309626_,
|
|
p_328170_
|
|
);
|
|
}
|
|
|
|
public TrialSpawner(
|
|
Holder<TrialSpawnerConfig> p_362438_,
|
|
Holder<TrialSpawnerConfig> p_365001_,
|
|
TrialSpawnerData p_330822_,
|
|
int p_330441_,
|
|
int p_335693_,
|
|
TrialSpawner.StateAccessor p_310539_,
|
|
PlayerDetector p_312974_,
|
|
PlayerDetector.EntitySelector p_333634_
|
|
) {
|
|
this.normalConfig = p_362438_;
|
|
this.ominousConfig = p_365001_;
|
|
this.data = p_330822_;
|
|
this.targetCooldownLength = p_330441_;
|
|
this.requiredPlayerRange = p_335693_;
|
|
this.stateAccessor = p_310539_;
|
|
this.playerDetector = p_312974_;
|
|
this.entitySelector = p_333634_;
|
|
}
|
|
|
|
public TrialSpawnerConfig getConfig() {
|
|
return this.isOminous ? this.getOminousConfig() : this.getNormalConfig();
|
|
}
|
|
|
|
@VisibleForTesting
|
|
public TrialSpawnerConfig getNormalConfig() {
|
|
return this.normalConfig.value();
|
|
}
|
|
|
|
@VisibleForTesting
|
|
public TrialSpawnerConfig getOminousConfig() {
|
|
return this.ominousConfig.value();
|
|
}
|
|
|
|
public void applyOminous(ServerLevel p_334207_, BlockPos p_327778_) {
|
|
p_334207_.setBlock(p_327778_, p_334207_.getBlockState(p_327778_).setValue(TrialSpawnerBlock.OMINOUS, true), 3);
|
|
p_334207_.levelEvent(3020, p_327778_, 1);
|
|
this.isOminous = true;
|
|
this.data.resetAfterBecomingOminous(this, p_334207_);
|
|
}
|
|
|
|
public void removeOminous(ServerLevel p_336080_, BlockPos p_328593_) {
|
|
p_336080_.setBlock(p_328593_, p_336080_.getBlockState(p_328593_).setValue(TrialSpawnerBlock.OMINOUS, false), 3);
|
|
this.isOminous = false;
|
|
}
|
|
|
|
public boolean isOminous() {
|
|
return this.isOminous;
|
|
}
|
|
|
|
public TrialSpawnerData getData() {
|
|
return this.data;
|
|
}
|
|
|
|
public int getTargetCooldownLength() {
|
|
return this.targetCooldownLength;
|
|
}
|
|
|
|
public int getRequiredPlayerRange() {
|
|
return this.requiredPlayerRange;
|
|
}
|
|
|
|
public TrialSpawnerState getState() {
|
|
return this.stateAccessor.getState();
|
|
}
|
|
|
|
public void setState(Level p_310153_, TrialSpawnerState p_312484_) {
|
|
this.stateAccessor.setState(p_310153_, p_312484_);
|
|
}
|
|
|
|
public void markUpdated() {
|
|
this.stateAccessor.markUpdated();
|
|
}
|
|
|
|
public PlayerDetector getPlayerDetector() {
|
|
return this.playerDetector;
|
|
}
|
|
|
|
public PlayerDetector.EntitySelector getEntitySelector() {
|
|
return this.entitySelector;
|
|
}
|
|
|
|
public boolean canSpawnInLevel(ServerLevel p_361781_) {
|
|
if (this.overridePeacefulAndMobSpawnRule) {
|
|
return true;
|
|
} else {
|
|
return p_361781_.getDifficulty() == Difficulty.PEACEFUL ? false : p_361781_.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING);
|
|
}
|
|
}
|
|
|
|
public Optional<UUID> spawnMob(ServerLevel p_312690_, BlockPos p_313108_) {
|
|
RandomSource randomsource = p_312690_.getRandom();
|
|
SpawnData spawndata = this.data.getOrCreateNextSpawnData(this, p_312690_.getRandom());
|
|
CompoundTag compoundtag = spawndata.entityToSpawn();
|
|
Optional<EntityType<?>> optional = EntityType.by(compoundtag);
|
|
if (optional.isEmpty()) {
|
|
return Optional.empty();
|
|
} else {
|
|
Vec3 vec3 = compoundtag.read("Pos", Vec3.CODEC)
|
|
.orElseGet(
|
|
() -> new Vec3(
|
|
p_313108_.getX() + (randomsource.nextDouble() - randomsource.nextDouble()) * this.getConfig().spawnRange() + 0.5,
|
|
p_313108_.getY() + randomsource.nextInt(3) - 1,
|
|
p_313108_.getZ() + (randomsource.nextDouble() - randomsource.nextDouble()) * this.getConfig().spawnRange() + 0.5
|
|
)
|
|
);
|
|
if (!p_312690_.noCollision(optional.get().getSpawnAABB(vec3.x, vec3.y, vec3.z))) {
|
|
return Optional.empty();
|
|
} else if (!inLineOfSight(p_312690_, p_313108_.getCenter(), vec3)) {
|
|
return Optional.empty();
|
|
} else {
|
|
BlockPos blockpos = BlockPos.containing(vec3);
|
|
if (!SpawnPlacements.checkSpawnRules(optional.get(), p_312690_, EntitySpawnReason.TRIAL_SPAWNER, blockpos, p_312690_.getRandom())) {
|
|
return Optional.empty();
|
|
} else {
|
|
if (spawndata.getCustomSpawnRules().isPresent()) {
|
|
SpawnData.CustomSpawnRules spawndata$customspawnrules = spawndata.getCustomSpawnRules().get();
|
|
if (!spawndata$customspawnrules.isValidPosition(blockpos, p_312690_)) {
|
|
return Optional.empty();
|
|
}
|
|
}
|
|
|
|
Entity entity = EntityType.loadEntityRecursive(compoundtag, p_312690_, EntitySpawnReason.TRIAL_SPAWNER, p_390986_ -> {
|
|
p_390986_.snapTo(vec3.x, vec3.y, vec3.z, randomsource.nextFloat() * 360.0F, 0.0F);
|
|
return p_390986_;
|
|
});
|
|
if (entity == null) {
|
|
return Optional.empty();
|
|
} else {
|
|
if (entity instanceof Mob mob) {
|
|
if (!mob.checkSpawnObstruction(p_312690_)) {
|
|
return Optional.empty();
|
|
}
|
|
|
|
boolean flag = spawndata.getEntityToSpawn().size() == 1 && spawndata.getEntityToSpawn().getString("id").isPresent();
|
|
if (flag) {
|
|
mob.finalizeSpawn(p_312690_, p_312690_.getCurrentDifficultyAt(mob.blockPosition()), EntitySpawnReason.TRIAL_SPAWNER, null);
|
|
}
|
|
|
|
mob.setPersistenceRequired();
|
|
spawndata.getEquipment().ifPresent(mob::equip);
|
|
}
|
|
|
|
if (!p_312690_.tryAddFreshEntityWithPassengers(entity)) {
|
|
return Optional.empty();
|
|
} else {
|
|
TrialSpawner.FlameParticle trialspawner$flameparticle = this.isOminous
|
|
? TrialSpawner.FlameParticle.OMINOUS
|
|
: TrialSpawner.FlameParticle.NORMAL;
|
|
p_312690_.levelEvent(3011, p_313108_, trialspawner$flameparticle.encode());
|
|
p_312690_.levelEvent(3012, blockpos, trialspawner$flameparticle.encode());
|
|
p_312690_.gameEvent(entity, GameEvent.ENTITY_PLACE, blockpos);
|
|
return Optional.of(entity.getUUID());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void ejectReward(ServerLevel p_310080_, BlockPos p_311547_, ResourceKey<LootTable> p_330647_) {
|
|
LootTable loottable = p_310080_.getServer().reloadableRegistries().getLootTable(p_330647_);
|
|
LootParams lootparams = new LootParams.Builder(p_310080_).create(LootContextParamSets.EMPTY);
|
|
ObjectArrayList<ItemStack> objectarraylist = loottable.getRandomItems(lootparams);
|
|
if (!objectarraylist.isEmpty()) {
|
|
for (ItemStack itemstack : objectarraylist) {
|
|
DefaultDispenseItemBehavior.spawnItem(p_310080_, itemstack, 2, Direction.UP, Vec3.atBottomCenterOf(p_311547_).relative(Direction.UP, 1.2));
|
|
}
|
|
|
|
p_310080_.levelEvent(3014, p_311547_, 0);
|
|
}
|
|
}
|
|
|
|
public void tickClient(Level p_309627_, BlockPos p_311485_, boolean p_332221_) {
|
|
TrialSpawnerState trialspawnerstate = this.getState();
|
|
trialspawnerstate.emitParticles(p_309627_, p_311485_, p_332221_);
|
|
if (trialspawnerstate.hasSpinningMob()) {
|
|
double d0 = Math.max(0L, this.data.nextMobSpawnsAt - p_309627_.getGameTime());
|
|
this.data.oSpin = this.data.spin;
|
|
this.data.spin = (this.data.spin + trialspawnerstate.spinningMobSpeed() / (d0 + 200.0)) % 360.0;
|
|
}
|
|
|
|
if (trialspawnerstate.isCapableOfSpawning()) {
|
|
RandomSource randomsource = p_309627_.getRandom();
|
|
if (randomsource.nextFloat() <= 0.02F) {
|
|
SoundEvent soundevent = p_332221_ ? SoundEvents.TRIAL_SPAWNER_AMBIENT_OMINOUS : SoundEvents.TRIAL_SPAWNER_AMBIENT;
|
|
p_309627_.playLocalSound(p_311485_, soundevent, SoundSource.BLOCKS, randomsource.nextFloat() * 0.25F + 0.75F, randomsource.nextFloat() + 0.5F, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void tickServer(ServerLevel p_310996_, BlockPos p_312836_, boolean p_332881_) {
|
|
this.isOminous = p_332881_;
|
|
TrialSpawnerState trialspawnerstate = this.getState();
|
|
if (this.data.currentMobs.removeIf(p_309715_ -> shouldMobBeUntracked(p_310996_, p_312836_, p_309715_))) {
|
|
this.data.nextMobSpawnsAt = p_310996_.getGameTime() + this.getConfig().ticksBetweenSpawn();
|
|
}
|
|
|
|
TrialSpawnerState trialspawnerstate1 = trialspawnerstate.tickAndGetNext(p_312836_, this, p_310996_);
|
|
if (trialspawnerstate1 != trialspawnerstate) {
|
|
this.setState(p_310996_, trialspawnerstate1);
|
|
}
|
|
}
|
|
|
|
private static boolean shouldMobBeUntracked(ServerLevel p_312275_, BlockPos p_310158_, UUID p_312011_) {
|
|
Entity entity = p_312275_.getEntity(p_312011_);
|
|
return entity == null
|
|
|| !entity.isAlive()
|
|
|| !entity.level().dimension().equals(p_312275_.dimension())
|
|
|| entity.blockPosition().distSqr(p_310158_) > MAX_MOB_TRACKING_DISTANCE_SQR;
|
|
}
|
|
|
|
private static boolean inLineOfSight(Level p_311873_, Vec3 p_311845_, Vec3 p_312229_) {
|
|
BlockHitResult blockhitresult = p_311873_.clip(
|
|
new ClipContext(p_312229_, p_311845_, ClipContext.Block.VISUAL, ClipContext.Fluid.NONE, CollisionContext.empty())
|
|
);
|
|
return blockhitresult.getBlockPos().equals(BlockPos.containing(p_311845_)) || blockhitresult.getType() == HitResult.Type.MISS;
|
|
}
|
|
|
|
public static void addSpawnParticles(Level p_333032_, BlockPos p_328008_, RandomSource p_330922_, SimpleParticleType p_331431_) {
|
|
for (int i = 0; i < 20; i++) {
|
|
double d0 = p_328008_.getX() + 0.5 + (p_330922_.nextDouble() - 0.5) * 2.0;
|
|
double d1 = p_328008_.getY() + 0.5 + (p_330922_.nextDouble() - 0.5) * 2.0;
|
|
double d2 = p_328008_.getZ() + 0.5 + (p_330922_.nextDouble() - 0.5) * 2.0;
|
|
p_333032_.addParticle(ParticleTypes.SMOKE, d0, d1, d2, 0.0, 0.0, 0.0);
|
|
p_333032_.addParticle(p_331431_, d0, d1, d2, 0.0, 0.0, 0.0);
|
|
}
|
|
}
|
|
|
|
public static void addBecomeOminousParticles(Level p_312837_, BlockPos p_311261_, RandomSource p_312356_) {
|
|
for (int i = 0; i < 20; i++) {
|
|
double d0 = p_311261_.getX() + 0.5 + (p_312356_.nextDouble() - 0.5) * 2.0;
|
|
double d1 = p_311261_.getY() + 0.5 + (p_312356_.nextDouble() - 0.5) * 2.0;
|
|
double d2 = p_311261_.getZ() + 0.5 + (p_312356_.nextDouble() - 0.5) * 2.0;
|
|
double d3 = p_312356_.nextGaussian() * 0.02;
|
|
double d4 = p_312356_.nextGaussian() * 0.02;
|
|
double d5 = p_312356_.nextGaussian() * 0.02;
|
|
p_312837_.addParticle(ParticleTypes.TRIAL_OMEN, d0, d1, d2, d3, d4, d5);
|
|
p_312837_.addParticle(ParticleTypes.SOUL_FIRE_FLAME, d0, d1, d2, d3, d4, d5);
|
|
}
|
|
}
|
|
|
|
public static void addDetectPlayerParticles(Level p_309415_, BlockPos p_309941_, RandomSource p_310263_, int p_310988_, ParticleOptions p_331085_) {
|
|
for (int i = 0; i < 30 + Math.min(p_310988_, 10) * 5; i++) {
|
|
double d0 = (2.0F * p_310263_.nextFloat() - 1.0F) * 0.65;
|
|
double d1 = (2.0F * p_310263_.nextFloat() - 1.0F) * 0.65;
|
|
double d2 = p_309941_.getX() + 0.5 + d0;
|
|
double d3 = p_309941_.getY() + 0.1 + p_310263_.nextFloat() * 0.8;
|
|
double d4 = p_309941_.getZ() + 0.5 + d1;
|
|
p_309415_.addParticle(p_331085_, d2, d3, d4, 0.0, 0.0, 0.0);
|
|
}
|
|
}
|
|
|
|
public static void addEjectItemParticles(Level p_311170_, BlockPos p_309958_, RandomSource p_309409_) {
|
|
for (int i = 0; i < 20; i++) {
|
|
double d0 = p_309958_.getX() + 0.4 + p_309409_.nextDouble() * 0.2;
|
|
double d1 = p_309958_.getY() + 0.4 + p_309409_.nextDouble() * 0.2;
|
|
double d2 = p_309958_.getZ() + 0.4 + p_309409_.nextDouble() * 0.2;
|
|
double d3 = p_309409_.nextGaussian() * 0.02;
|
|
double d4 = p_309409_.nextGaussian() * 0.02;
|
|
double d5 = p_309409_.nextGaussian() * 0.02;
|
|
p_311170_.addParticle(ParticleTypes.SMALL_FLAME, d0, d1, d2, d3, d4, d5 * 0.25);
|
|
p_311170_.addParticle(ParticleTypes.SMOKE, d0, d1, d2, d3, d4, d5);
|
|
}
|
|
}
|
|
|
|
public void overrideEntityToSpawn(EntityType<?> p_378280_, Level p_376000_) {
|
|
this.data.reset();
|
|
this.normalConfig = Holder.direct(this.normalConfig.value().withSpawning(p_378280_));
|
|
this.ominousConfig = Holder.direct(this.ominousConfig.value().withSpawning(p_378280_));
|
|
this.setState(p_376000_, TrialSpawnerState.INACTIVE);
|
|
}
|
|
|
|
@Deprecated(
|
|
forRemoval = true
|
|
)
|
|
@VisibleForTesting
|
|
public void setPlayerDetector(PlayerDetector p_311472_) {
|
|
this.playerDetector = p_311472_;
|
|
}
|
|
|
|
@Deprecated(
|
|
forRemoval = true
|
|
)
|
|
@VisibleForTesting
|
|
public void overridePeacefulAndMobSpawnRule() {
|
|
this.overridePeacefulAndMobSpawnRule = true;
|
|
}
|
|
|
|
public static enum FlameParticle {
|
|
NORMAL(ParticleTypes.FLAME),
|
|
OMINOUS(ParticleTypes.SOUL_FIRE_FLAME);
|
|
|
|
public final SimpleParticleType particleType;
|
|
|
|
private FlameParticle(final SimpleParticleType p_332977_) {
|
|
this.particleType = p_332977_;
|
|
}
|
|
|
|
public static TrialSpawner.FlameParticle decode(int p_333274_) {
|
|
TrialSpawner.FlameParticle[] atrialspawner$flameparticle = values();
|
|
return p_333274_ <= atrialspawner$flameparticle.length && p_333274_ >= 0 ? atrialspawner$flameparticle[p_333274_] : NORMAL;
|
|
}
|
|
|
|
public int encode() {
|
|
return this.ordinal();
|
|
}
|
|
}
|
|
|
|
public interface StateAccessor {
|
|
void setState(Level p_309383_, TrialSpawnerState p_310563_);
|
|
|
|
TrialSpawnerState getState();
|
|
|
|
void markUpdated();
|
|
}
|
|
} |