minecraft-src/net/minecraft/world/level/block/entity/BeaconBlockEntity.java
2025-07-04 02:00:41 +03:00

418 lines
13 KiB
Java

package net.minecraft.world.level.block.entity;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import net.minecraft.advancements.CriteriaTriggers;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.component.DataComponentMap.Builder;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.tags.BlockTags;
import net.minecraft.util.ARGB;
import net.minecraft.world.LockCode;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.Nameable;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.BeaconMenu;
import net.minecraft.world.inventory.ContainerData;
import net.minecraft.world.inventory.ContainerLevelAccess;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BeaconBeamBlock;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.phys.AABB;
import org.jetbrains.annotations.Nullable;
public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Nameable {
private static final int MAX_LEVELS = 4;
/**
* A list of effects that beacons can apply.
*/
public static final List<List<Holder<MobEffect>>> BEACON_EFFECTS = List.of(
List.of(MobEffects.MOVEMENT_SPEED, MobEffects.DIG_SPEED),
List.of(MobEffects.DAMAGE_RESISTANCE, MobEffects.JUMP),
List.of(MobEffects.DAMAGE_BOOST),
List.of(MobEffects.REGENERATION)
);
private static final Set<Holder<MobEffect>> VALID_EFFECTS = (Set<Holder<MobEffect>>)BEACON_EFFECTS.stream()
.flatMap(Collection::stream)
.collect(Collectors.toSet());
public static final int DATA_LEVELS = 0;
public static final int DATA_PRIMARY = 1;
public static final int DATA_SECONDARY = 2;
public static final int NUM_DATA_VALUES = 3;
private static final int BLOCKS_CHECK_PER_TICK = 10;
private static final Component DEFAULT_NAME = Component.translatable("container.beacon");
private static final String TAG_PRIMARY = "primary_effect";
private static final String TAG_SECONDARY = "secondary_effect";
/**
* A list of beam segments for this beacon.
*/
List<BeaconBlockEntity.BeaconBeamSection> beamSections = Lists.<BeaconBlockEntity.BeaconBeamSection>newArrayList();
private List<BeaconBlockEntity.BeaconBeamSection> checkingBeamSections = Lists.<BeaconBlockEntity.BeaconBeamSection>newArrayList();
/**
* The number of levels of this beacon's pyramid.
*/
int levels;
private int lastCheckY;
/**
* The primary effect given by this beacon.
*/
@Nullable
Holder<MobEffect> primaryPower;
/**
* The secondary effect given by this beacon.
*/
@Nullable
Holder<MobEffect> secondaryPower;
/**
* The custom name for this beacon.
*/
@Nullable
private Component name;
private LockCode lockKey = LockCode.NO_LOCK;
private final ContainerData dataAccess = new ContainerData() {
@Override
public int get(int index) {
return switch (index) {
case 0 -> BeaconBlockEntity.this.levels;
case 1 -> BeaconMenu.encodeEffect(BeaconBlockEntity.this.primaryPower);
case 2 -> BeaconMenu.encodeEffect(BeaconBlockEntity.this.secondaryPower);
default -> 0;
};
}
@Override
public void set(int index, int value) {
switch (index) {
case 0:
BeaconBlockEntity.this.levels = value;
break;
case 1:
if (!BeaconBlockEntity.this.level.isClientSide && !BeaconBlockEntity.this.beamSections.isEmpty()) {
BeaconBlockEntity.playSound(BeaconBlockEntity.this.level, BeaconBlockEntity.this.worldPosition, SoundEvents.BEACON_POWER_SELECT);
}
BeaconBlockEntity.this.primaryPower = BeaconBlockEntity.filterEffect(BeaconMenu.decodeEffect(value));
break;
case 2:
BeaconBlockEntity.this.secondaryPower = BeaconBlockEntity.filterEffect(BeaconMenu.decodeEffect(value));
}
}
@Override
public int getCount() {
return 3;
}
};
@Nullable
static Holder<MobEffect> filterEffect(@Nullable Holder<MobEffect> effect) {
return VALID_EFFECTS.contains(effect) ? effect : null;
}
public BeaconBlockEntity(BlockPos pos, BlockState blockState) {
super(BlockEntityType.BEACON, pos, blockState);
}
public static void tick(Level level, BlockPos pos, BlockState state, BeaconBlockEntity blockEntity) {
int i = pos.getX();
int j = pos.getY();
int k = pos.getZ();
BlockPos blockPos;
if (blockEntity.lastCheckY < j) {
blockPos = pos;
blockEntity.checkingBeamSections = Lists.<BeaconBlockEntity.BeaconBeamSection>newArrayList();
blockEntity.lastCheckY = pos.getY() - 1;
} else {
blockPos = new BlockPos(i, blockEntity.lastCheckY + 1, k);
}
BeaconBlockEntity.BeaconBeamSection beaconBeamSection = blockEntity.checkingBeamSections.isEmpty()
? null
: (BeaconBlockEntity.BeaconBeamSection)blockEntity.checkingBeamSections.get(blockEntity.checkingBeamSections.size() - 1);
int l = level.getHeight(Heightmap.Types.WORLD_SURFACE, i, k);
for (int m = 0; m < 10 && blockPos.getY() <= l; m++) {
BlockState blockState = level.getBlockState(blockPos);
if (blockState.getBlock() instanceof BeaconBeamBlock beaconBeamBlock) {
int n = beaconBeamBlock.getColor().getTextureDiffuseColor();
if (blockEntity.checkingBeamSections.size() <= 1) {
beaconBeamSection = new BeaconBlockEntity.BeaconBeamSection(n);
blockEntity.checkingBeamSections.add(beaconBeamSection);
} else if (beaconBeamSection != null) {
if (n == beaconBeamSection.color) {
beaconBeamSection.increaseHeight();
} else {
beaconBeamSection = new BeaconBlockEntity.BeaconBeamSection(ARGB.average(beaconBeamSection.color, n));
blockEntity.checkingBeamSections.add(beaconBeamSection);
}
}
} else {
if (beaconBeamSection == null || blockState.getLightBlock() >= 15 && !blockState.is(Blocks.BEDROCK)) {
blockEntity.checkingBeamSections.clear();
blockEntity.lastCheckY = l;
break;
}
beaconBeamSection.increaseHeight();
}
blockPos = blockPos.above();
blockEntity.lastCheckY++;
}
int m = blockEntity.levels;
if (level.getGameTime() % 80L == 0L) {
if (!blockEntity.beamSections.isEmpty()) {
blockEntity.levels = updateBase(level, i, j, k);
}
if (blockEntity.levels > 0 && !blockEntity.beamSections.isEmpty()) {
applyEffects(level, pos, blockEntity.levels, blockEntity.primaryPower, blockEntity.secondaryPower);
playSound(level, pos, SoundEvents.BEACON_AMBIENT);
}
}
if (blockEntity.lastCheckY >= l) {
blockEntity.lastCheckY = level.getMinY() - 1;
boolean bl = m > 0;
blockEntity.beamSections = blockEntity.checkingBeamSections;
if (!level.isClientSide) {
boolean bl2 = blockEntity.levels > 0;
if (!bl && bl2) {
playSound(level, pos, SoundEvents.BEACON_ACTIVATE);
for (ServerPlayer serverPlayer : level.getEntitiesOfClass(ServerPlayer.class, new AABB(i, j, k, i, j - 4, k).inflate(10.0, 5.0, 10.0))) {
CriteriaTriggers.CONSTRUCT_BEACON.trigger(serverPlayer, blockEntity.levels);
}
} else if (bl && !bl2) {
playSound(level, pos, SoundEvents.BEACON_DEACTIVATE);
}
}
}
}
private static int updateBase(Level level, int x, int y, int z) {
int i = 0;
for (int j = 1; j <= 4; i = j++) {
int k = y - j;
if (k < level.getMinY()) {
break;
}
boolean bl = true;
for (int l = x - j; l <= x + j && bl; l++) {
for (int m = z - j; m <= z + j; m++) {
if (!level.getBlockState(new BlockPos(l, k, m)).is(BlockTags.BEACON_BASE_BLOCKS)) {
bl = false;
break;
}
}
}
if (!bl) {
break;
}
}
return i;
}
@Override
public void setRemoved() {
playSound(this.level, this.worldPosition, SoundEvents.BEACON_DEACTIVATE);
super.setRemoved();
}
private static void applyEffects(
Level level, BlockPos pos, int beaconLevel, @Nullable Holder<MobEffect> primaryEffect, @Nullable Holder<MobEffect> secondaryEffect
) {
if (!level.isClientSide && primaryEffect != null) {
double d = beaconLevel * 10 + 10;
int i = 0;
if (beaconLevel >= 4 && Objects.equals(primaryEffect, secondaryEffect)) {
i = 1;
}
int j = (9 + beaconLevel * 2) * 20;
AABB aABB = new AABB(pos).inflate(d).expandTowards(0.0, level.getHeight(), 0.0);
List<Player> list = level.getEntitiesOfClass(Player.class, aABB);
for (Player player : list) {
player.addEffect(new MobEffectInstance(primaryEffect, j, i, true, true));
}
if (beaconLevel >= 4 && !Objects.equals(primaryEffect, secondaryEffect) && secondaryEffect != null) {
for (Player player : list) {
player.addEffect(new MobEffectInstance(secondaryEffect, j, 0, true, true));
}
}
}
}
public static void playSound(Level level, BlockPos pos, SoundEvent sound) {
level.playSound(null, pos, sound, SoundSource.BLOCKS, 1.0F, 1.0F);
}
public List<BeaconBlockEntity.BeaconBeamSection> getBeamSections() {
return (List<BeaconBlockEntity.BeaconBeamSection>)(this.levels == 0 ? ImmutableList.of() : this.beamSections);
}
public ClientboundBlockEntityDataPacket getUpdatePacket() {
return ClientboundBlockEntityDataPacket.create(this);
}
@Override
public CompoundTag getUpdateTag(HolderLookup.Provider registries) {
return this.saveCustomOnly(registries);
}
private static void storeEffect(CompoundTag tag, String key, @Nullable Holder<MobEffect> effect) {
if (effect != null) {
effect.unwrapKey().ifPresent(resourceKey -> tag.putString(key, resourceKey.location().toString()));
}
}
@Nullable
private static Holder<MobEffect> loadEffect(CompoundTag tag, String key) {
if (tag.contains(key, 8)) {
ResourceLocation resourceLocation = ResourceLocation.tryParse(tag.getString(key));
return resourceLocation == null ? null : (Holder)BuiltInRegistries.MOB_EFFECT.get(resourceLocation).map(BeaconBlockEntity::filterEffect).orElse(null);
} else {
return null;
}
}
@Override
protected void loadAdditional(CompoundTag tag, HolderLookup.Provider registries) {
super.loadAdditional(tag, registries);
this.primaryPower = loadEffect(tag, "primary_effect");
this.secondaryPower = loadEffect(tag, "secondary_effect");
if (tag.contains("CustomName", 8)) {
this.name = parseCustomNameSafe(tag.getString("CustomName"), registries);
}
this.lockKey = LockCode.fromTag(tag, registries);
}
@Override
protected void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) {
super.saveAdditional(tag, registries);
storeEffect(tag, "primary_effect", this.primaryPower);
storeEffect(tag, "secondary_effect", this.secondaryPower);
tag.putInt("Levels", this.levels);
if (this.name != null) {
tag.putString("CustomName", Component.Serializer.toJson(this.name, registries));
}
this.lockKey.addToTag(tag, registries);
}
/**
* Sets the custom name for this beacon.
*/
public void setCustomName(@Nullable Component name) {
this.name = name;
}
@Nullable
@Override
public Component getCustomName() {
return this.name;
}
@Nullable
@Override
public AbstractContainerMenu createMenu(int i, Inventory inventory, Player player) {
return BaseContainerBlockEntity.canUnlock(player, this.lockKey, this.getDisplayName())
? new BeaconMenu(i, inventory, this.dataAccess, ContainerLevelAccess.create(this.level, this.getBlockPos()))
: null;
}
@Override
public Component getDisplayName() {
return this.getName();
}
@Override
public Component getName() {
return this.name != null ? this.name : DEFAULT_NAME;
}
@Override
protected void applyImplicitComponents(BlockEntity.DataComponentInput componentInput) {
super.applyImplicitComponents(componentInput);
this.name = componentInput.get(DataComponents.CUSTOM_NAME);
this.lockKey = componentInput.getOrDefault(DataComponents.LOCK, LockCode.NO_LOCK);
}
@Override
protected void collectImplicitComponents(Builder components) {
super.collectImplicitComponents(components);
components.set(DataComponents.CUSTOM_NAME, this.name);
if (!this.lockKey.equals(LockCode.NO_LOCK)) {
components.set(DataComponents.LOCK, this.lockKey);
}
}
@Override
public void removeComponentsFromTag(CompoundTag tag) {
tag.remove("CustomName");
tag.remove("lock");
}
@Override
public void setLevel(Level level) {
super.setLevel(level);
this.lastCheckY = level.getMinY() - 1;
}
public static class BeaconBeamSection {
/**
* The colors of this section of a beacon beam, in RGB float format.
*/
final int color;
private int height;
public BeaconBeamSection(int color) {
this.color = color;
this.height = 1;
}
protected void increaseHeight() {
this.height++;
}
public int getColor() {
return this.color;
}
public int getHeight() {
return this.height;
}
}
}