425 lines
14 KiB
Java
425 lines
14 KiB
Java
package net.minecraft.world.level.block.entity;
|
|
|
|
import com.google.common.collect.Lists;
|
|
import com.mojang.logging.LogUtils;
|
|
import com.mojang.serialization.Codec;
|
|
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
|
import io.netty.buffer.ByteBuf;
|
|
import java.util.Arrays;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.Direction;
|
|
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.nbt.NbtOps;
|
|
import net.minecraft.nbt.NbtUtils;
|
|
import net.minecraft.network.codec.ByteBufCodecs;
|
|
import net.minecraft.network.codec.StreamCodec;
|
|
import net.minecraft.network.protocol.game.DebugPackets;
|
|
import net.minecraft.sounds.SoundEvents;
|
|
import net.minecraft.sounds.SoundSource;
|
|
import net.minecraft.tags.BlockTags;
|
|
import net.minecraft.tags.EntityTypeTags;
|
|
import net.minecraft.util.VisibleForDebug;
|
|
import net.minecraft.world.entity.Entity;
|
|
import net.minecraft.world.entity.EntitySpawnReason;
|
|
import net.minecraft.world.entity.EntityType;
|
|
import net.minecraft.world.entity.animal.Bee;
|
|
import net.minecraft.world.entity.player.Player;
|
|
import net.minecraft.world.item.component.CustomData;
|
|
import net.minecraft.world.level.Level;
|
|
import net.minecraft.world.level.block.BeehiveBlock;
|
|
import net.minecraft.world.level.block.CampfireBlock;
|
|
import net.minecraft.world.level.block.FireBlock;
|
|
import net.minecraft.world.level.block.state.BlockState;
|
|
import net.minecraft.world.level.gameevent.GameEvent;
|
|
import net.minecraft.world.level.gameevent.GameEvent.Context;
|
|
import org.jetbrains.annotations.Nullable;
|
|
import org.slf4j.Logger;
|
|
|
|
public class BeehiveBlockEntity extends BlockEntity {
|
|
private static final Logger LOGGER = LogUtils.getLogger();
|
|
private static final String TAG_FLOWER_POS = "flower_pos";
|
|
private static final String BEES = "bees";
|
|
static final List<String> IGNORED_BEE_TAGS = Arrays.asList(
|
|
"Air",
|
|
"ArmorDropChances",
|
|
"ArmorItems",
|
|
"Brain",
|
|
"CanPickUpLoot",
|
|
"DeathTime",
|
|
"FallDistance",
|
|
"FallFlying",
|
|
"Fire",
|
|
"HandDropChances",
|
|
"HandItems",
|
|
"HurtByTimestamp",
|
|
"HurtTime",
|
|
"LeftHanded",
|
|
"Motion",
|
|
"NoGravity",
|
|
"OnGround",
|
|
"PortalCooldown",
|
|
"Pos",
|
|
"Rotation",
|
|
"SleepingX",
|
|
"SleepingY",
|
|
"SleepingZ",
|
|
"CannotEnterHiveTicks",
|
|
"TicksSincePollination",
|
|
"CropsGrownSincePollination",
|
|
"hive_pos",
|
|
"Passengers",
|
|
"leash",
|
|
"UUID"
|
|
);
|
|
public static final int MAX_OCCUPANTS = 3;
|
|
private static final int MIN_TICKS_BEFORE_REENTERING_HIVE = 400;
|
|
private static final int MIN_OCCUPATION_TICKS_NECTAR = 2400;
|
|
public static final int MIN_OCCUPATION_TICKS_NECTARLESS = 600;
|
|
private final List<BeehiveBlockEntity.BeeData> stored = Lists.<BeehiveBlockEntity.BeeData>newArrayList();
|
|
@Nullable
|
|
private BlockPos savedFlowerPos;
|
|
|
|
public BeehiveBlockEntity(BlockPos pos, BlockState blockState) {
|
|
super(BlockEntityType.BEEHIVE, pos, blockState);
|
|
}
|
|
|
|
@Override
|
|
public void setChanged() {
|
|
if (this.isFireNearby()) {
|
|
this.emptyAllLivingFromHive(null, this.level.getBlockState(this.getBlockPos()), BeehiveBlockEntity.BeeReleaseStatus.EMERGENCY);
|
|
}
|
|
|
|
super.setChanged();
|
|
}
|
|
|
|
public boolean isFireNearby() {
|
|
if (this.level == null) {
|
|
return false;
|
|
} else {
|
|
for (BlockPos blockPos : BlockPos.betweenClosed(this.worldPosition.offset(-1, -1, -1), this.worldPosition.offset(1, 1, 1))) {
|
|
if (this.level.getBlockState(blockPos).getBlock() instanceof FireBlock) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public boolean isEmpty() {
|
|
return this.stored.isEmpty();
|
|
}
|
|
|
|
public boolean isFull() {
|
|
return this.stored.size() == 3;
|
|
}
|
|
|
|
public void emptyAllLivingFromHive(@Nullable Player player, BlockState state, BeehiveBlockEntity.BeeReleaseStatus releaseStatus) {
|
|
List<Entity> list = this.releaseAllOccupants(state, releaseStatus);
|
|
if (player != null) {
|
|
for (Entity entity : list) {
|
|
if (entity instanceof Bee bee && player.position().distanceToSqr(entity.position()) <= 16.0) {
|
|
if (!this.isSedated()) {
|
|
bee.setTarget(player);
|
|
} else {
|
|
bee.setStayOutOfHiveCountdown(400);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private List<Entity> releaseAllOccupants(BlockState state, BeehiveBlockEntity.BeeReleaseStatus releaseStatus) {
|
|
List<Entity> list = Lists.<Entity>newArrayList();
|
|
this.stored.removeIf(beeData -> releaseOccupant(this.level, this.worldPosition, state, beeData.toOccupant(), list, releaseStatus, this.savedFlowerPos));
|
|
if (!list.isEmpty()) {
|
|
super.setChanged();
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
@VisibleForDebug
|
|
public int getOccupantCount() {
|
|
return this.stored.size();
|
|
}
|
|
|
|
public static int getHoneyLevel(BlockState state) {
|
|
return (Integer)state.getValue(BeehiveBlock.HONEY_LEVEL);
|
|
}
|
|
|
|
@VisibleForDebug
|
|
public boolean isSedated() {
|
|
return CampfireBlock.isSmokeyPos(this.level, this.getBlockPos());
|
|
}
|
|
|
|
public void addOccupant(Entity occupant) {
|
|
if (this.stored.size() < 3) {
|
|
occupant.stopRiding();
|
|
occupant.ejectPassengers();
|
|
this.storeBee(BeehiveBlockEntity.Occupant.of(occupant));
|
|
if (this.level != null) {
|
|
if (occupant instanceof Bee bee && bee.hasSavedFlowerPos() && (!this.hasSavedFlowerPos() || this.level.random.nextBoolean())) {
|
|
this.savedFlowerPos = bee.getSavedFlowerPos();
|
|
}
|
|
|
|
BlockPos blockPos = this.getBlockPos();
|
|
this.level
|
|
.playSound(null, (double)blockPos.getX(), (double)blockPos.getY(), (double)blockPos.getZ(), SoundEvents.BEEHIVE_ENTER, SoundSource.BLOCKS, 1.0F, 1.0F);
|
|
this.level.gameEvent(GameEvent.BLOCK_CHANGE, blockPos, Context.of(occupant, this.getBlockState()));
|
|
}
|
|
|
|
occupant.discard();
|
|
super.setChanged();
|
|
}
|
|
}
|
|
|
|
public void storeBee(BeehiveBlockEntity.Occupant occupant) {
|
|
this.stored.add(new BeehiveBlockEntity.BeeData(occupant));
|
|
}
|
|
|
|
private static boolean releaseOccupant(
|
|
Level level,
|
|
BlockPos pos,
|
|
BlockState state,
|
|
BeehiveBlockEntity.Occupant occupant,
|
|
@Nullable List<Entity> storedInHives,
|
|
BeehiveBlockEntity.BeeReleaseStatus releaseStatus,
|
|
@Nullable BlockPos storedFlowerPos
|
|
) {
|
|
if (Bee.isNightOrRaining(level) && releaseStatus != BeehiveBlockEntity.BeeReleaseStatus.EMERGENCY) {
|
|
return false;
|
|
} else {
|
|
Direction direction = state.getValue(BeehiveBlock.FACING);
|
|
BlockPos blockPos = pos.relative(direction);
|
|
boolean bl = !level.getBlockState(blockPos).getCollisionShape(level, blockPos).isEmpty();
|
|
if (bl && releaseStatus != BeehiveBlockEntity.BeeReleaseStatus.EMERGENCY) {
|
|
return false;
|
|
} else {
|
|
Entity entity = occupant.createEntity(level, pos);
|
|
if (entity != null) {
|
|
if (entity instanceof Bee bee) {
|
|
if (storedFlowerPos != null && !bee.hasSavedFlowerPos() && level.random.nextFloat() < 0.9F) {
|
|
bee.setSavedFlowerPos(storedFlowerPos);
|
|
}
|
|
|
|
if (releaseStatus == BeehiveBlockEntity.BeeReleaseStatus.HONEY_DELIVERED) {
|
|
bee.dropOffNectar();
|
|
if (state.is(BlockTags.BEEHIVES, blockStateBase -> blockStateBase.hasProperty(BeehiveBlock.HONEY_LEVEL))) {
|
|
int i = getHoneyLevel(state);
|
|
if (i < 5) {
|
|
int j = level.random.nextInt(100) == 0 ? 2 : 1;
|
|
if (i + j > 5) {
|
|
j--;
|
|
}
|
|
|
|
level.setBlockAndUpdate(pos, state.setValue(BeehiveBlock.HONEY_LEVEL, i + j));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (storedInHives != null) {
|
|
storedInHives.add(bee);
|
|
}
|
|
|
|
float f = entity.getBbWidth();
|
|
double d = bl ? 0.0 : 0.55 + f / 2.0F;
|
|
double e = pos.getX() + 0.5 + d * direction.getStepX();
|
|
double g = pos.getY() + 0.5 - entity.getBbHeight() / 2.0F;
|
|
double h = pos.getZ() + 0.5 + d * direction.getStepZ();
|
|
entity.moveTo(e, g, h, entity.getYRot(), entity.getXRot());
|
|
}
|
|
|
|
level.playSound(null, pos, SoundEvents.BEEHIVE_EXIT, SoundSource.BLOCKS, 1.0F, 1.0F);
|
|
level.gameEvent(GameEvent.BLOCK_CHANGE, pos, Context.of(entity, level.getBlockState(pos)));
|
|
return level.addFreshEntity(entity);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private boolean hasSavedFlowerPos() {
|
|
return this.savedFlowerPos != null;
|
|
}
|
|
|
|
private static void tickOccupants(Level level, BlockPos pos, BlockState state, List<BeehiveBlockEntity.BeeData> data, @Nullable BlockPos savedFlowerPos) {
|
|
boolean bl = false;
|
|
Iterator<BeehiveBlockEntity.BeeData> iterator = data.iterator();
|
|
|
|
while (iterator.hasNext()) {
|
|
BeehiveBlockEntity.BeeData beeData = (BeehiveBlockEntity.BeeData)iterator.next();
|
|
if (beeData.tick()) {
|
|
BeehiveBlockEntity.BeeReleaseStatus beeReleaseStatus = beeData.hasNectar()
|
|
? BeehiveBlockEntity.BeeReleaseStatus.HONEY_DELIVERED
|
|
: BeehiveBlockEntity.BeeReleaseStatus.BEE_RELEASED;
|
|
if (releaseOccupant(level, pos, state, beeData.toOccupant(), null, beeReleaseStatus, savedFlowerPos)) {
|
|
bl = true;
|
|
iterator.remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bl) {
|
|
setChanged(level, pos, state);
|
|
}
|
|
}
|
|
|
|
public static void serverTick(Level level, BlockPos pos, BlockState state, BeehiveBlockEntity beehive) {
|
|
tickOccupants(level, pos, state, beehive.stored, beehive.savedFlowerPos);
|
|
if (!beehive.stored.isEmpty() && level.getRandom().nextDouble() < 0.005) {
|
|
double d = pos.getX() + 0.5;
|
|
double e = pos.getY();
|
|
double f = pos.getZ() + 0.5;
|
|
level.playSound(null, d, e, f, SoundEvents.BEEHIVE_WORK, SoundSource.BLOCKS, 1.0F, 1.0F);
|
|
}
|
|
|
|
DebugPackets.sendHiveInfo(level, pos, state, beehive);
|
|
}
|
|
|
|
@Override
|
|
protected void loadAdditional(CompoundTag tag, HolderLookup.Provider registries) {
|
|
super.loadAdditional(tag, registries);
|
|
this.stored.clear();
|
|
if (tag.contains("bees")) {
|
|
BeehiveBlockEntity.Occupant.LIST_CODEC
|
|
.parse(NbtOps.INSTANCE, tag.get("bees"))
|
|
.resultOrPartial(string -> LOGGER.error("Failed to parse bees: '{}'", string))
|
|
.ifPresent(list -> list.forEach(this::storeBee));
|
|
}
|
|
|
|
this.savedFlowerPos = (BlockPos)NbtUtils.readBlockPos(tag, "flower_pos").orElse(null);
|
|
}
|
|
|
|
@Override
|
|
protected void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) {
|
|
super.saveAdditional(tag, registries);
|
|
tag.put("bees", BeehiveBlockEntity.Occupant.LIST_CODEC.encodeStart(NbtOps.INSTANCE, this.getBees()).getOrThrow());
|
|
if (this.hasSavedFlowerPos()) {
|
|
tag.put("flower_pos", NbtUtils.writeBlockPos(this.savedFlowerPos));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void applyImplicitComponents(BlockEntity.DataComponentInput componentInput) {
|
|
super.applyImplicitComponents(componentInput);
|
|
this.stored.clear();
|
|
List<BeehiveBlockEntity.Occupant> list = componentInput.getOrDefault(DataComponents.BEES, List.of());
|
|
list.forEach(this::storeBee);
|
|
}
|
|
|
|
@Override
|
|
protected void collectImplicitComponents(Builder components) {
|
|
super.collectImplicitComponents(components);
|
|
components.set(DataComponents.BEES, this.getBees());
|
|
}
|
|
|
|
@Override
|
|
public void removeComponentsFromTag(CompoundTag tag) {
|
|
super.removeComponentsFromTag(tag);
|
|
tag.remove("bees");
|
|
}
|
|
|
|
private List<BeehiveBlockEntity.Occupant> getBees() {
|
|
return this.stored.stream().map(BeehiveBlockEntity.BeeData::toOccupant).toList();
|
|
}
|
|
|
|
static class BeeData {
|
|
private final BeehiveBlockEntity.Occupant occupant;
|
|
private int ticksInHive;
|
|
|
|
BeeData(BeehiveBlockEntity.Occupant occupant) {
|
|
this.occupant = occupant;
|
|
this.ticksInHive = occupant.ticksInHive();
|
|
}
|
|
|
|
public boolean tick() {
|
|
return this.ticksInHive++ > this.occupant.minTicksInHive;
|
|
}
|
|
|
|
public BeehiveBlockEntity.Occupant toOccupant() {
|
|
return new BeehiveBlockEntity.Occupant(this.occupant.entityData, this.ticksInHive, this.occupant.minTicksInHive);
|
|
}
|
|
|
|
public boolean hasNectar() {
|
|
return this.occupant.entityData.getUnsafe().getBoolean("HasNectar");
|
|
}
|
|
}
|
|
|
|
public static enum BeeReleaseStatus {
|
|
HONEY_DELIVERED,
|
|
BEE_RELEASED,
|
|
EMERGENCY;
|
|
}
|
|
|
|
public record Occupant(CustomData entityData, int ticksInHive, int minTicksInHive) {
|
|
public static final Codec<BeehiveBlockEntity.Occupant> CODEC = RecordCodecBuilder.create(
|
|
instance -> instance.group(
|
|
CustomData.CODEC.optionalFieldOf("entity_data", CustomData.EMPTY).forGetter(BeehiveBlockEntity.Occupant::entityData),
|
|
Codec.INT.fieldOf("ticks_in_hive").forGetter(BeehiveBlockEntity.Occupant::ticksInHive),
|
|
Codec.INT.fieldOf("min_ticks_in_hive").forGetter(BeehiveBlockEntity.Occupant::minTicksInHive)
|
|
)
|
|
.apply(instance, BeehiveBlockEntity.Occupant::new)
|
|
);
|
|
public static final Codec<List<BeehiveBlockEntity.Occupant>> LIST_CODEC = CODEC.listOf();
|
|
public static final StreamCodec<ByteBuf, BeehiveBlockEntity.Occupant> STREAM_CODEC = StreamCodec.composite(
|
|
CustomData.STREAM_CODEC,
|
|
BeehiveBlockEntity.Occupant::entityData,
|
|
ByteBufCodecs.VAR_INT,
|
|
BeehiveBlockEntity.Occupant::ticksInHive,
|
|
ByteBufCodecs.VAR_INT,
|
|
BeehiveBlockEntity.Occupant::minTicksInHive,
|
|
BeehiveBlockEntity.Occupant::new
|
|
);
|
|
|
|
public static BeehiveBlockEntity.Occupant of(Entity entity) {
|
|
CompoundTag compoundTag = new CompoundTag();
|
|
entity.save(compoundTag);
|
|
BeehiveBlockEntity.IGNORED_BEE_TAGS.forEach(compoundTag::remove);
|
|
boolean bl = compoundTag.getBoolean("HasNectar");
|
|
return new BeehiveBlockEntity.Occupant(CustomData.of(compoundTag), 0, bl ? 2400 : 600);
|
|
}
|
|
|
|
public static BeehiveBlockEntity.Occupant create(int ticksInHive) {
|
|
CompoundTag compoundTag = new CompoundTag();
|
|
compoundTag.putString("id", BuiltInRegistries.ENTITY_TYPE.getKey(EntityType.BEE).toString());
|
|
return new BeehiveBlockEntity.Occupant(CustomData.of(compoundTag), ticksInHive, 600);
|
|
}
|
|
|
|
@Nullable
|
|
public Entity createEntity(Level level, BlockPos pos) {
|
|
CompoundTag compoundTag = this.entityData.copyTag();
|
|
BeehiveBlockEntity.IGNORED_BEE_TAGS.forEach(compoundTag::remove);
|
|
Entity entity = EntityType.loadEntityRecursive(compoundTag, level, EntitySpawnReason.LOAD, entityx -> entityx);
|
|
if (entity != null && entity.getType().is(EntityTypeTags.BEEHIVE_INHABITORS)) {
|
|
entity.setNoGravity(true);
|
|
if (entity instanceof Bee bee) {
|
|
bee.setHivePos(pos);
|
|
setBeeReleaseData(this.ticksInHive, bee);
|
|
}
|
|
|
|
return entity;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private static void setBeeReleaseData(int ticksInHive, Bee bee) {
|
|
int i = bee.getAge();
|
|
if (i < 0) {
|
|
bee.setAge(Math.min(0, i + ticksInHive));
|
|
} else if (i > 0) {
|
|
bee.setAge(Math.max(0, i - ticksInHive));
|
|
}
|
|
|
|
bee.setInLoveTime(Math.max(0, bee.getInLoveTime() - ticksInHive));
|
|
}
|
|
}
|
|
}
|