300 lines
12 KiB
Java
300 lines
12 KiB
Java
package net.minecraft.world.level.block.entity.trialspawner;
|
|
|
|
import java.util.List;
|
|
import java.util.Objects;
|
|
import java.util.Optional;
|
|
import java.util.Set;
|
|
import java.util.UUID;
|
|
import java.util.stream.Stream;
|
|
import net.minecraft.Util;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.Direction;
|
|
import net.minecraft.core.particles.ParticleTypes;
|
|
import net.minecraft.core.particles.SimpleParticleType;
|
|
import net.minecraft.server.level.ServerLevel;
|
|
import net.minecraft.sounds.SoundEvents;
|
|
import net.minecraft.sounds.SoundSource;
|
|
import net.minecraft.util.Mth;
|
|
import net.minecraft.util.RandomSource;
|
|
import net.minecraft.util.StringRepresentable;
|
|
import net.minecraft.world.entity.Entity;
|
|
import net.minecraft.world.entity.OminousItemSpawner;
|
|
import net.minecraft.world.entity.player.Player;
|
|
import net.minecraft.world.item.ItemStack;
|
|
import net.minecraft.world.level.ClipContext;
|
|
import net.minecraft.world.level.Level;
|
|
import net.minecraft.world.level.ClipContext.Block;
|
|
import net.minecraft.world.level.ClipContext.Fluid;
|
|
import net.minecraft.world.phys.BlockHitResult;
|
|
import net.minecraft.world.phys.Vec3;
|
|
import net.minecraft.world.phys.shapes.CollisionContext;
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
public enum TrialSpawnerState implements StringRepresentable {
|
|
INACTIVE("inactive", 0, TrialSpawnerState.ParticleEmission.NONE, -1.0, false),
|
|
WAITING_FOR_PLAYERS("waiting_for_players", 4, TrialSpawnerState.ParticleEmission.SMALL_FLAMES, 200.0, true),
|
|
ACTIVE("active", 8, TrialSpawnerState.ParticleEmission.FLAMES_AND_SMOKE, 1000.0, true),
|
|
WAITING_FOR_REWARD_EJECTION("waiting_for_reward_ejection", 8, TrialSpawnerState.ParticleEmission.SMALL_FLAMES, -1.0, false),
|
|
EJECTING_REWARD("ejecting_reward", 8, TrialSpawnerState.ParticleEmission.SMALL_FLAMES, -1.0, false),
|
|
COOLDOWN("cooldown", 0, TrialSpawnerState.ParticleEmission.SMOKE_INSIDE_AND_TOP_FACE, -1.0, false);
|
|
|
|
private static final float DELAY_BEFORE_EJECT_AFTER_KILLING_LAST_MOB = 40.0F;
|
|
private static final int TIME_BETWEEN_EACH_EJECTION = Mth.floor(30.0F);
|
|
private final String name;
|
|
private final int lightLevel;
|
|
private final double spinningMobSpeed;
|
|
private final TrialSpawnerState.ParticleEmission particleEmission;
|
|
private final boolean isCapableOfSpawning;
|
|
|
|
private TrialSpawnerState(
|
|
final String name,
|
|
final int lightLevel,
|
|
final TrialSpawnerState.ParticleEmission particleEmission,
|
|
final double spinningMobSpeed,
|
|
final boolean isCapableOfSpawning
|
|
) {
|
|
this.name = name;
|
|
this.lightLevel = lightLevel;
|
|
this.particleEmission = particleEmission;
|
|
this.spinningMobSpeed = spinningMobSpeed;
|
|
this.isCapableOfSpawning = isCapableOfSpawning;
|
|
}
|
|
|
|
TrialSpawnerState tickAndGetNext(BlockPos pos, TrialSpawner spawner, ServerLevel level) {
|
|
TrialSpawnerData trialSpawnerData = spawner.getData();
|
|
TrialSpawnerConfig trialSpawnerConfig = spawner.getConfig();
|
|
|
|
return switch (this) {
|
|
case INACTIVE -> trialSpawnerData.getOrCreateDisplayEntity(spawner, level, WAITING_FOR_PLAYERS) == null ? this : WAITING_FOR_PLAYERS;
|
|
case WAITING_FOR_PLAYERS -> {
|
|
if (!spawner.canSpawnInLevel(level)) {
|
|
trialSpawnerData.resetStatistics();
|
|
yield this;
|
|
} else if (!trialSpawnerData.hasMobToSpawn(spawner, level.random)) {
|
|
yield INACTIVE;
|
|
} else {
|
|
trialSpawnerData.tryDetectPlayers(level, pos, spawner);
|
|
yield trialSpawnerData.detectedPlayers.isEmpty() ? this : ACTIVE;
|
|
}
|
|
}
|
|
case ACTIVE -> {
|
|
if (!spawner.canSpawnInLevel(level)) {
|
|
trialSpawnerData.resetStatistics();
|
|
yield WAITING_FOR_PLAYERS;
|
|
} else if (!trialSpawnerData.hasMobToSpawn(spawner, level.random)) {
|
|
yield INACTIVE;
|
|
} else {
|
|
int i = trialSpawnerData.countAdditionalPlayers(pos);
|
|
trialSpawnerData.tryDetectPlayers(level, pos, spawner);
|
|
if (spawner.isOminous()) {
|
|
this.spawnOminousOminousItemSpawner(level, pos, spawner);
|
|
}
|
|
|
|
if (trialSpawnerData.hasFinishedSpawningAllMobs(trialSpawnerConfig, i)) {
|
|
if (trialSpawnerData.haveAllCurrentMobsDied()) {
|
|
trialSpawnerData.cooldownEndsAt = level.getGameTime() + spawner.getTargetCooldownLength();
|
|
trialSpawnerData.totalMobsSpawned = 0;
|
|
trialSpawnerData.nextMobSpawnsAt = 0L;
|
|
yield WAITING_FOR_REWARD_EJECTION;
|
|
}
|
|
} else if (trialSpawnerData.isReadyToSpawnNextMob(level, trialSpawnerConfig, i)) {
|
|
spawner.spawnMob(level, pos).ifPresent(uUID -> {
|
|
trialSpawnerData.currentMobs.add(uUID);
|
|
trialSpawnerData.totalMobsSpawned++;
|
|
trialSpawnerData.nextMobSpawnsAt = level.getGameTime() + trialSpawnerConfig.ticksBetweenSpawn();
|
|
trialSpawnerConfig.spawnPotentialsDefinition().getRandom(level.getRandom()).ifPresent(spawnData -> {
|
|
trialSpawnerData.nextSpawnData = Optional.of(spawnData);
|
|
spawner.markUpdated();
|
|
});
|
|
});
|
|
}
|
|
|
|
yield this;
|
|
}
|
|
}
|
|
case WAITING_FOR_REWARD_EJECTION -> {
|
|
if (trialSpawnerData.isReadyToOpenShutter(level, 40.0F, spawner.getTargetCooldownLength())) {
|
|
level.playSound(null, pos, SoundEvents.TRIAL_SPAWNER_OPEN_SHUTTER, SoundSource.BLOCKS);
|
|
yield EJECTING_REWARD;
|
|
} else {
|
|
yield this;
|
|
}
|
|
}
|
|
case EJECTING_REWARD -> {
|
|
if (!trialSpawnerData.isReadyToEjectItems(level, TIME_BETWEEN_EACH_EJECTION, spawner.getTargetCooldownLength())) {
|
|
yield this;
|
|
} else if (trialSpawnerData.detectedPlayers.isEmpty()) {
|
|
level.playSound(null, pos, SoundEvents.TRIAL_SPAWNER_CLOSE_SHUTTER, SoundSource.BLOCKS);
|
|
trialSpawnerData.ejectingLootTable = Optional.empty();
|
|
yield COOLDOWN;
|
|
} else {
|
|
if (trialSpawnerData.ejectingLootTable.isEmpty()) {
|
|
trialSpawnerData.ejectingLootTable = trialSpawnerConfig.lootTablesToEject().getRandom(level.getRandom());
|
|
}
|
|
|
|
trialSpawnerData.ejectingLootTable.ifPresent(resourceKey -> spawner.ejectReward(level, pos, resourceKey));
|
|
trialSpawnerData.detectedPlayers.remove(trialSpawnerData.detectedPlayers.iterator().next());
|
|
yield this;
|
|
}
|
|
}
|
|
case COOLDOWN -> {
|
|
trialSpawnerData.tryDetectPlayers(level, pos, spawner);
|
|
if (!trialSpawnerData.detectedPlayers.isEmpty()) {
|
|
trialSpawnerData.totalMobsSpawned = 0;
|
|
trialSpawnerData.nextMobSpawnsAt = 0L;
|
|
yield ACTIVE;
|
|
} else if (trialSpawnerData.isCooldownFinished(level)) {
|
|
spawner.removeOminous(level, pos);
|
|
trialSpawnerData.reset();
|
|
yield WAITING_FOR_PLAYERS;
|
|
} else {
|
|
yield this;
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
private void spawnOminousOminousItemSpawner(ServerLevel level, BlockPos pos, TrialSpawner spawner) {
|
|
TrialSpawnerData trialSpawnerData = spawner.getData();
|
|
TrialSpawnerConfig trialSpawnerConfig = spawner.getConfig();
|
|
ItemStack itemStack = (ItemStack)trialSpawnerData.getDispensingItems(level, trialSpawnerConfig, pos).getRandom(level.random).orElse(ItemStack.EMPTY);
|
|
if (!itemStack.isEmpty()) {
|
|
if (this.timeToSpawnItemSpawner(level, trialSpawnerData)) {
|
|
calculatePositionToSpawnSpawner(level, pos, spawner, trialSpawnerData).ifPresent(vec3 -> {
|
|
OminousItemSpawner ominousItemSpawner = OminousItemSpawner.create(level, itemStack);
|
|
ominousItemSpawner.snapTo(vec3);
|
|
level.addFreshEntity(ominousItemSpawner);
|
|
float f = (level.getRandom().nextFloat() - level.getRandom().nextFloat()) * 0.2F + 1.0F;
|
|
level.playSound(null, BlockPos.containing(vec3), SoundEvents.TRIAL_SPAWNER_SPAWN_ITEM_BEGIN, SoundSource.BLOCKS, 1.0F, f);
|
|
trialSpawnerData.cooldownEndsAt = level.getGameTime() + spawner.getOminousConfig().ticksBetweenItemSpawners();
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
private static Optional<Vec3> calculatePositionToSpawnSpawner(ServerLevel level, BlockPos pos, TrialSpawner spawner, TrialSpawnerData spawnerData) {
|
|
List<Player> list = spawnerData.detectedPlayers
|
|
.stream()
|
|
.map(level::getPlayerByUUID)
|
|
.filter(Objects::nonNull)
|
|
.filter(
|
|
player -> !player.isCreative()
|
|
&& !player.isSpectator()
|
|
&& player.isAlive()
|
|
&& player.distanceToSqr(pos.getCenter()) <= Mth.square(spawner.getRequiredPlayerRange())
|
|
)
|
|
.toList();
|
|
if (list.isEmpty()) {
|
|
return Optional.empty();
|
|
} else {
|
|
Entity entity = selectEntityToSpawnItemAbove(list, spawnerData.currentMobs, spawner, pos, level);
|
|
return entity == null ? Optional.empty() : calculatePositionAbove(entity, level);
|
|
}
|
|
}
|
|
|
|
private static Optional<Vec3> calculatePositionAbove(Entity entity, ServerLevel level) {
|
|
Vec3 vec3 = entity.position();
|
|
Vec3 vec32 = vec3.relative(Direction.UP, entity.getBbHeight() + 2.0F + level.random.nextInt(4));
|
|
BlockHitResult blockHitResult = level.clip(new ClipContext(vec3, vec32, Block.VISUAL, Fluid.NONE, CollisionContext.empty()));
|
|
Vec3 vec33 = blockHitResult.getBlockPos().getCenter().relative(Direction.DOWN, 1.0);
|
|
BlockPos blockPos = BlockPos.containing(vec33);
|
|
return !level.getBlockState(blockPos).getCollisionShape(level, blockPos).isEmpty() ? Optional.empty() : Optional.of(vec33);
|
|
}
|
|
|
|
@Nullable
|
|
private static Entity selectEntityToSpawnItemAbove(List<Player> player, Set<UUID> currentMobs, TrialSpawner spawner, BlockPos pos, ServerLevel level) {
|
|
Stream<Entity> stream = currentMobs.stream()
|
|
.map(level::getEntity)
|
|
.filter(Objects::nonNull)
|
|
.filter(entity -> entity.isAlive() && entity.distanceToSqr(pos.getCenter()) <= Mth.square(spawner.getRequiredPlayerRange()));
|
|
List<? extends Entity> list = level.random.nextBoolean() ? stream.toList() : player;
|
|
if (list.isEmpty()) {
|
|
return null;
|
|
} else {
|
|
return list.size() == 1 ? (Entity)list.getFirst() : Util.getRandom(list, level.random);
|
|
}
|
|
}
|
|
|
|
private boolean timeToSpawnItemSpawner(ServerLevel level, TrialSpawnerData spawnerData) {
|
|
return level.getGameTime() >= spawnerData.cooldownEndsAt;
|
|
}
|
|
|
|
public int lightLevel() {
|
|
return this.lightLevel;
|
|
}
|
|
|
|
public double spinningMobSpeed() {
|
|
return this.spinningMobSpeed;
|
|
}
|
|
|
|
public boolean hasSpinningMob() {
|
|
return this.spinningMobSpeed >= 0.0;
|
|
}
|
|
|
|
public boolean isCapableOfSpawning() {
|
|
return this.isCapableOfSpawning;
|
|
}
|
|
|
|
public void emitParticles(Level level, BlockPos pos, boolean isOminous) {
|
|
this.particleEmission.emit(level, level.getRandom(), pos, isOminous);
|
|
}
|
|
|
|
@Override
|
|
public String getSerializedName() {
|
|
return this.name;
|
|
}
|
|
|
|
static class LightLevel {
|
|
private static final int UNLIT = 0;
|
|
private static final int HALF_LIT = 4;
|
|
private static final int LIT = 8;
|
|
|
|
private LightLevel() {
|
|
}
|
|
}
|
|
|
|
interface ParticleEmission {
|
|
TrialSpawnerState.ParticleEmission NONE = (level, randomSource, blockPos, bl) -> {};
|
|
TrialSpawnerState.ParticleEmission SMALL_FLAMES = (level, randomSource, blockPos, bl) -> {
|
|
if (randomSource.nextInt(2) == 0) {
|
|
Vec3 vec3 = blockPos.getCenter().offsetRandom(randomSource, 0.9F);
|
|
addParticle(bl ? ParticleTypes.SOUL_FIRE_FLAME : ParticleTypes.SMALL_FLAME, vec3, level);
|
|
}
|
|
};
|
|
TrialSpawnerState.ParticleEmission FLAMES_AND_SMOKE = (level, randomSource, blockPos, bl) -> {
|
|
Vec3 vec3 = blockPos.getCenter().offsetRandom(randomSource, 1.0F);
|
|
addParticle(ParticleTypes.SMOKE, vec3, level);
|
|
addParticle(bl ? ParticleTypes.SOUL_FIRE_FLAME : ParticleTypes.FLAME, vec3, level);
|
|
};
|
|
TrialSpawnerState.ParticleEmission SMOKE_INSIDE_AND_TOP_FACE = (level, randomSource, blockPos, bl) -> {
|
|
Vec3 vec3 = blockPos.getCenter().offsetRandom(randomSource, 0.9F);
|
|
if (randomSource.nextInt(3) == 0) {
|
|
addParticle(ParticleTypes.SMOKE, vec3, level);
|
|
}
|
|
|
|
if (level.getGameTime() % 20L == 0L) {
|
|
Vec3 vec32 = blockPos.getCenter().add(0.0, 0.5, 0.0);
|
|
int i = level.getRandom().nextInt(4) + 20;
|
|
|
|
for (int j = 0; j < i; j++) {
|
|
addParticle(ParticleTypes.SMOKE, vec32, level);
|
|
}
|
|
}
|
|
};
|
|
|
|
private static void addParticle(SimpleParticleType particleType, Vec3 pos, Level level) {
|
|
level.addParticle(particleType, pos.x(), pos.y(), pos.z(), 0.0, 0.0, 0.0);
|
|
}
|
|
|
|
void emit(Level level, RandomSource randomSource, BlockPos blockPos, boolean bl);
|
|
}
|
|
|
|
static class SpinningMob {
|
|
private static final double NONE = -1.0;
|
|
private static final double SLOW = 200.0;
|
|
private static final double FAST = 1000.0;
|
|
|
|
private SpinningMob() {
|
|
}
|
|
}
|
|
}
|