package net.minecraft.world.level.block.entity; import java.util.Optional; import net.minecraft.core.BlockPos; import net.minecraft.core.HolderLookup; import net.minecraft.core.particles.TargetColorParticleOption; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; import net.minecraft.server.level.ServerLevel; import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; import net.minecraft.util.RandomSource; import net.minecraft.util.SpawnUtil; import net.minecraft.world.Difficulty; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.EntitySpawnReason; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.monster.creaking.Creaking; import net.minecraft.world.entity.monster.creaking.CreakingTransient; import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.GameRules; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.CreakingHeartBlock; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.gameevent.GameEvent; import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.Nullable; public class CreakingHeartBlockEntity extends BlockEntity { private static final int PLAYER_DETECTION_RANGE = 32; public static final int CREAKING_ROAMING_RADIUS = 32; private static final int DISTANCE_CREAKING_TOO_FAR = 34; private static final int SPAWN_RANGE_XZ = 16; private static final int SPAWN_RANGE_Y = 8; private static final int ATTEMPTS_PER_SPAWN = 5; private static final int UPDATE_TICKS = 20; private static final int HURT_CALL_TOTAL_TICKS = 100; private static final int NUMBER_OF_HURT_CALLS = 10; private static final int HURT_CALL_INTERVAL = 10; private static final int HURT_CALL_PARTICLE_TICKS = 50; @Nullable private CreakingTransient creaking; private int ticker; private int emitter; @Nullable private Vec3 emitterTarget; private int outputSignal; public CreakingHeartBlockEntity(BlockPos pos, BlockState state) { super(BlockEntityType.CREAKING_HEART, pos, state); } public static void serverTick(Level level, BlockPos pos, BlockState state, CreakingHeartBlockEntity creakingHeart) { int i = creakingHeart.computeAnalogOutputSignal(); if (creakingHeart.outputSignal != i) { creakingHeart.outputSignal = i; level.updateNeighbourForOutputSignal(pos, Blocks.CREAKING_HEART); } if (creakingHeart.emitter > 0) { if (creakingHeart.emitter > 50) { creakingHeart.emitParticles((ServerLevel)level, 1, true); creakingHeart.emitParticles((ServerLevel)level, 1, false); } if (creakingHeart.emitter % 10 == 0 && level instanceof ServerLevel serverLevel && creakingHeart.emitterTarget != null) { if (creakingHeart.creaking != null) { creakingHeart.emitterTarget = creakingHeart.creaking.getBoundingBox().getCenter(); } Vec3 vec3 = Vec3.atCenterOf(pos); float f = 0.2F + 0.8F * (100 - creakingHeart.emitter) / 100.0F; Vec3 vec32 = vec3.subtract(creakingHeart.emitterTarget).scale(f).add(creakingHeart.emitterTarget); BlockPos blockPos = BlockPos.containing(vec32); float g = creakingHeart.emitter / 2.0F / 100.0F + 0.5F; serverLevel.playSound(null, blockPos, SoundEvents.CREAKING_HEART_HURT, SoundSource.BLOCKS, g, 1.0F); } creakingHeart.emitter--; } if (creakingHeart.ticker-- < 0) { creakingHeart.ticker = 20; if (creakingHeart.creaking != null) { if (CreakingHeartBlock.canSummonCreaking(level) && !(creakingHeart.distanceToCreaking() > 34.0)) { if (creakingHeart.creaking.isRemoved()) { creakingHeart.creaking = null; } if (!CreakingHeartBlock.hasRequiredLogs(state, level, pos) && creakingHeart.creaking == null) { level.setBlock(pos, state.setValue(CreakingHeartBlock.CREAKING, CreakingHeartBlock.CreakingHeartState.DISABLED), 3); } } else { creakingHeart.removeProtector(null); } } else if (!CreakingHeartBlock.hasRequiredLogs(state, level, pos)) { level.setBlock(pos, state.setValue(CreakingHeartBlock.CREAKING, CreakingHeartBlock.CreakingHeartState.DISABLED), 3); } else { if (!CreakingHeartBlock.canSummonCreaking(level)) { if (state.getValue(CreakingHeartBlock.CREAKING) == CreakingHeartBlock.CreakingHeartState.ACTIVE) { level.setBlock(pos, state.setValue(CreakingHeartBlock.CREAKING, CreakingHeartBlock.CreakingHeartState.DORMANT), 3); return; } } else if (state.getValue(CreakingHeartBlock.CREAKING) == CreakingHeartBlock.CreakingHeartState.DORMANT) { level.setBlock(pos, state.setValue(CreakingHeartBlock.CREAKING, CreakingHeartBlock.CreakingHeartState.ACTIVE), 3); return; } if (state.getValue(CreakingHeartBlock.CREAKING) == CreakingHeartBlock.CreakingHeartState.ACTIVE) { if (level.getDifficulty() != Difficulty.PEACEFUL) { if (!(level instanceof ServerLevel serverLevel && !serverLevel.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING))) { Player player = level.getNearestPlayer(pos.getX(), pos.getY(), pos.getZ(), 32.0, false); if (player != null) { creakingHeart.creaking = spawnProtector((ServerLevel)level, creakingHeart); if (creakingHeart.creaking != null) { creakingHeart.creaking.makeSound(SoundEvents.CREAKING_SPAWN); level.playSound(null, creakingHeart.getBlockPos(), SoundEvents.CREAKING_HEART_SPAWN, SoundSource.BLOCKS, 1.0F, 1.0F); } } } } } } } } private double distanceToCreaking() { return this.creaking == null ? 0.0 : Math.sqrt(this.creaking.distanceToSqr(Vec3.atBottomCenterOf(this.getBlockPos()))); } @Nullable private static CreakingTransient spawnProtector(ServerLevel level, CreakingHeartBlockEntity creakingHeart) { BlockPos blockPos = creakingHeart.getBlockPos(); Optional optional = SpawnUtil.trySpawnMob( EntityType.CREAKING_TRANSIENT, EntitySpawnReason.SPAWNER, level, blockPos, 5, 16, 8, SpawnUtil.Strategy.ON_TOP_OF_COLLIDER_NO_LEAVES ); if (optional.isEmpty()) { return null; } else { CreakingTransient creakingTransient = (CreakingTransient)optional.get(); level.gameEvent(creakingTransient, GameEvent.ENTITY_PLACE, creakingTransient.position()); level.broadcastEntityEvent(creakingTransient, (byte)60); creakingTransient.bindToCreakingHeart(blockPos); return creakingTransient; } } public ClientboundBlockEntityDataPacket getUpdatePacket() { return ClientboundBlockEntityDataPacket.create(this); } @Override public CompoundTag getUpdateTag(HolderLookup.Provider registries) { return this.saveCustomOnly(registries); } public void creakingHurt() { if (this.creaking != null) { if (this.level instanceof ServerLevel serverLevel) { this.emitParticles(serverLevel, 20, false); this.emitter = 100; this.emitterTarget = this.creaking.getBoundingBox().getCenter(); } } } private void emitParticles(ServerLevel level, int count, boolean reverseDirection) { if (this.creaking != null) { int i = reverseDirection ? 16545810 : 6250335; RandomSource randomSource = level.random; for (double d = 0.0; d < count; d++) { Vec3 vec3 = this.creaking .getBoundingBox() .getMinPosition() .add( randomSource.nextDouble() * this.creaking.getBoundingBox().getXsize(), randomSource.nextDouble() * this.creaking.getBoundingBox().getYsize(), randomSource.nextDouble() * this.creaking.getBoundingBox().getZsize() ); Vec3 vec32 = Vec3.atLowerCornerOf(this.getBlockPos()).add(randomSource.nextDouble(), randomSource.nextDouble(), randomSource.nextDouble()); if (reverseDirection) { Vec3 vec33 = vec3; vec3 = vec32; vec32 = vec33; } TargetColorParticleOption targetColorParticleOption = new TargetColorParticleOption(vec32, i); level.sendParticles(targetColorParticleOption, vec3.x, vec3.y, vec3.z, 1, 0.0, 0.0, 0.0, 0.0); } } } public void removeProtector(@Nullable DamageSource damageSource) { if (this.creaking != null) { this.creaking.tearDown(damageSource); this.creaking = null; } } public boolean isProtector(Creaking creaking) { return this.creaking == creaking; } public int getAnalogOutputSignal() { return this.outputSignal; } public int computeAnalogOutputSignal() { if (this.creaking == null) { return 0; } else { double d = this.distanceToCreaking(); double e = Math.clamp(d, 0.0, 32.0) / 32.0; return 15 - (int)Math.floor(e * 15.0); } } }