2023-10-15 17:51:57 +02:00
|
|
|
package com.minelittlepony.unicopia.block;
|
|
|
|
|
2024-02-16 17:42:48 +01:00
|
|
|
import java.util.ArrayList;
|
2023-10-15 17:51:57 +02:00
|
|
|
import java.util.Collection;
|
2024-02-16 17:42:48 +01:00
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.HashSet;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Map;
|
|
|
|
import java.util.Set;
|
|
|
|
|
|
|
|
import org.jetbrains.annotations.Nullable;
|
2023-10-15 17:51:57 +02:00
|
|
|
|
|
|
|
import com.minelittlepony.unicopia.EquineContext;
|
2024-02-16 17:42:48 +01:00
|
|
|
import com.minelittlepony.unicopia.EquinePredicates;
|
2023-10-15 17:51:57 +02:00
|
|
|
import com.minelittlepony.unicopia.Race;
|
|
|
|
import com.minelittlepony.unicopia.USounds;
|
|
|
|
import com.minelittlepony.unicopia.entity.player.Pony;
|
|
|
|
import com.minelittlepony.unicopia.particle.ParticleUtils;
|
2023-12-30 11:55:26 +01:00
|
|
|
import com.mojang.serialization.MapCodec;
|
2024-02-16 17:42:48 +01:00
|
|
|
import com.minelittlepony.unicopia.util.NbtSerialisable;
|
|
|
|
import com.minelittlepony.unicopia.util.NbtSerialisable.Serializer;
|
|
|
|
import com.minelittlepony.unicopia.util.PosHelper;
|
2023-10-15 17:51:57 +02:00
|
|
|
|
|
|
|
import net.minecraft.block.Block;
|
2024-02-16 17:42:48 +01:00
|
|
|
import net.minecraft.block.BlockEntityProvider;
|
2023-10-15 17:51:57 +02:00
|
|
|
import net.minecraft.block.BlockState;
|
2024-02-16 17:42:48 +01:00
|
|
|
import net.minecraft.block.Blocks;
|
2023-10-15 17:51:57 +02:00
|
|
|
import net.minecraft.block.ConnectingBlock;
|
|
|
|
import net.minecraft.block.ShapeContext;
|
2024-02-16 17:42:48 +01:00
|
|
|
import net.minecraft.block.entity.BlockEntity;
|
|
|
|
import net.minecraft.block.entity.BlockEntityTicker;
|
|
|
|
import net.minecraft.block.entity.BlockEntityType;
|
2023-10-15 17:51:57 +02:00
|
|
|
import net.minecraft.entity.player.PlayerEntity;
|
2024-02-16 17:42:48 +01:00
|
|
|
import net.minecraft.nbt.NbtCompound;
|
|
|
|
import net.minecraft.nbt.NbtElement;
|
2023-10-15 17:51:57 +02:00
|
|
|
import net.minecraft.particle.ParticleTypes;
|
|
|
|
import net.minecraft.server.world.ServerWorld;
|
|
|
|
import net.minecraft.sound.SoundCategory;
|
|
|
|
import net.minecraft.state.StateManager;
|
|
|
|
import net.minecraft.state.property.BooleanProperty;
|
|
|
|
import net.minecraft.state.property.Property;
|
2024-02-16 17:42:48 +01:00
|
|
|
import net.minecraft.util.ActionResult;
|
|
|
|
import net.minecraft.util.Hand;
|
|
|
|
import net.minecraft.util.hit.BlockHitResult;
|
2023-10-15 17:51:57 +02:00
|
|
|
import net.minecraft.util.math.BlockPos;
|
|
|
|
import net.minecraft.util.math.Direction;
|
2024-02-16 17:42:48 +01:00
|
|
|
import net.minecraft.util.math.Vec3d;
|
2023-10-15 17:51:57 +02:00
|
|
|
import net.minecraft.util.math.random.Random;
|
|
|
|
import net.minecraft.util.shape.VoxelShape;
|
|
|
|
import net.minecraft.util.shape.VoxelShapes;
|
|
|
|
import net.minecraft.world.BlockView;
|
|
|
|
import net.minecraft.world.World;
|
|
|
|
import net.minecraft.world.WorldAccess;
|
2024-02-16 17:42:48 +01:00
|
|
|
import net.minecraft.world.event.BlockPositionSource;
|
|
|
|
import net.minecraft.world.event.GameEvent;
|
|
|
|
import net.minecraft.world.event.GameEvent.Emitter;
|
|
|
|
import net.minecraft.world.event.PositionSource;
|
|
|
|
import net.minecraft.world.event.listener.GameEventListener;
|
2023-10-15 17:51:57 +02:00
|
|
|
|
2024-02-16 17:42:48 +01:00
|
|
|
public class HiveBlock extends ConnectingBlock implements BlockEntityProvider {
|
2023-12-30 11:55:26 +01:00
|
|
|
public static final MapCodec<HiveBlock> CODEC = createCodec(HiveBlock::new);
|
2023-10-15 17:51:57 +02:00
|
|
|
static final BooleanProperty AWAKE = BooleanProperty.of("awake");
|
2024-02-16 17:42:48 +01:00
|
|
|
static final BooleanProperty CONSUMING = BooleanProperty.of("consuming");
|
2023-10-15 17:51:57 +02:00
|
|
|
static final Collection<BooleanProperty> PROPERTIES = FACING_PROPERTIES.values();
|
|
|
|
|
|
|
|
public HiveBlock(Settings settings) {
|
|
|
|
super(0.5F, settings);
|
2024-02-16 17:42:48 +01:00
|
|
|
setDefaultState(getDefaultState().with(AWAKE, false).with(CONSUMING, false));
|
2023-10-15 17:51:57 +02:00
|
|
|
PROPERTIES.forEach(property -> {
|
|
|
|
setDefaultState(getDefaultState().with(property, true));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-12-30 11:55:26 +01:00
|
|
|
@Override
|
|
|
|
protected MapCodec<? extends HiveBlock> getCodec() {
|
|
|
|
return CODEC;
|
|
|
|
}
|
|
|
|
|
2023-10-15 17:51:57 +02:00
|
|
|
@Override
|
|
|
|
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
|
|
|
|
builder.add(PROPERTIES.toArray(Property[]::new));
|
2024-02-16 17:42:48 +01:00
|
|
|
builder.add(AWAKE, CONSUMING);
|
2023-10-15 17:51:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void randomDisplayTick(BlockState state, World world, BlockPos pos, Random random) {
|
|
|
|
super.randomDisplayTick(state, world, pos, random);
|
|
|
|
|
|
|
|
if (random.nextInt(250) == 0) {
|
|
|
|
world.playSoundAtBlockCenter(pos, USounds.BLOCK_CHITIN_AMBIENCE, SoundCategory.BLOCKS, 0.13F, 0.2F, true);
|
|
|
|
|
|
|
|
for (int i = 0; i < 9; i++) {
|
|
|
|
world.addParticle(random.nextInt(2) == 0 ? ParticleTypes.SPORE_BLOSSOM_AIR : ParticleTypes.CRIMSON_SPORE,
|
|
|
|
pos.getX() + random.nextDouble(),
|
|
|
|
pos.getY() + 1.1, pos.getZ() + random.nextDouble(),
|
|
|
|
random.nextDouble() - 0.5, 0, random.nextDouble() - 0.5
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Deprecated
|
|
|
|
@Override
|
|
|
|
public BlockState getStateForNeighborUpdate(BlockState state, Direction direction, BlockState neighborState, WorldAccess world, BlockPos pos, BlockPos neighborPos) {
|
2024-02-16 17:42:48 +01:00
|
|
|
if (state.get(CONSUMING)) {
|
|
|
|
return state;
|
|
|
|
}
|
2023-10-15 17:51:57 +02:00
|
|
|
boolean connected = !neighborState.isAir();
|
|
|
|
state = state.with(FACING_PROPERTIES.get(direction), connected);
|
|
|
|
|
|
|
|
if (!connected) {
|
|
|
|
return state.with(AWAKE, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void scheduledTick(BlockState state, ServerWorld world, BlockPos pos, Random random) {
|
2024-02-16 17:42:48 +01:00
|
|
|
if (state.get(CONSUMING) || !state.get(AWAKE)) {
|
2023-10-15 17:51:57 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (var property : FACING_PROPERTIES.entrySet()) {
|
|
|
|
if (!state.get(property.getValue())) {
|
|
|
|
BlockPos neighborPos = pos.offset(property.getKey());
|
|
|
|
//world.setBlockState(neighborPos, getDefaultState());
|
|
|
|
world.setBlockState(neighborPos, UBlocks.CHITIN.getDefaultState());
|
|
|
|
|
|
|
|
world.playSound(null, neighborPos, USounds.Vanilla.BLOCK_SLIME_BLOCK_PLACE, SoundCategory.BLOCKS);
|
|
|
|
world.playSound(null, neighborPos, USounds.BLOCK_CHITIN_AMBIENCE, SoundCategory.BLOCKS, 0.13F, 0.2F);
|
|
|
|
|
|
|
|
for (int i = 0; i < 9; i++) {
|
|
|
|
ParticleUtils.spawnParticle(world, random.nextInt(2) == 0 ? ParticleTypes.SPORE_BLOSSOM_AIR : ParticleTypes.CRIMSON_SPORE,
|
|
|
|
neighborPos.getX() + random.nextDouble(),
|
|
|
|
neighborPos.getY() + 1.1, pos.getZ() + random.nextDouble(),
|
|
|
|
random.nextDouble() - 0.5, 0, random.nextDouble() - 0.5
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-16 17:42:48 +01:00
|
|
|
@Override
|
|
|
|
public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
|
|
|
|
if (EquineContext.of(player).getCompositeRace().includes(Race.CHANGELING)) {
|
|
|
|
world.setBlockState(pos, state.with(CONSUMING, true));
|
|
|
|
if (!world.isClient) {
|
|
|
|
BlockEntityUtil.getOrCreateBlockEntity(world, pos, UBlockEntities.HIVE_STORAGE).ifPresent(data -> {
|
|
|
|
|
|
|
|
if (data.opening) {
|
|
|
|
data.opening = false;
|
|
|
|
data.closing = true;
|
|
|
|
} else {
|
|
|
|
data.opening = true;
|
|
|
|
data.closing = false;
|
|
|
|
}
|
|
|
|
data.tickNow = true;
|
|
|
|
data.markDirty();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return ActionResult.SUCCESS;
|
|
|
|
}
|
|
|
|
return ActionResult.PASS;
|
|
|
|
}
|
|
|
|
|
2023-10-15 17:51:57 +02:00
|
|
|
@Deprecated
|
|
|
|
@Override
|
|
|
|
public void neighborUpdate(BlockState state, World world, BlockPos pos, Block sourceBlock, BlockPos sourcePos, boolean notify) {
|
|
|
|
world.scheduleBlockTick(pos, this, 15);
|
|
|
|
super.neighborUpdate(state, world, pos, sourceBlock, sourcePos, notify);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Deprecated
|
|
|
|
@Override
|
|
|
|
public VoxelShape getCollisionShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
|
|
|
|
return EquineContext.of(context).getSpecies() == Race.CHANGELING ? VoxelShapes.empty() : super.getCollisionShape(state, world, pos, context);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Deprecated
|
|
|
|
@Override
|
|
|
|
public float calcBlockBreakingDelta(BlockState state, PlayerEntity player, BlockView world, BlockPos pos) {
|
|
|
|
float delta = super.calcBlockBreakingDelta(state, player, world, pos);
|
|
|
|
delta *= Pony.of(player).getSpecies() == Race.CHANGELING ? 2 : 1;
|
|
|
|
return delta;
|
|
|
|
}
|
2024-02-16 17:42:48 +01:00
|
|
|
|
|
|
|
@Nullable
|
|
|
|
@Override
|
|
|
|
public BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
|
|
|
|
return state.get(CONSUMING) ? new TileData(pos, state) : null;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(World world, BlockState state, BlockEntityType<T> type) {
|
|
|
|
if (!state.get(CONSUMING)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return BlockEntityUtil.checkType(type, UBlockEntities.HIVE_STORAGE, TileData::tick);
|
|
|
|
}
|
|
|
|
|
|
|
|
static class TileData extends BlockEntity implements GameEventListener.Holder<TileData.Listener> {
|
|
|
|
private final Map<BlockPos, Entry> storedBlocks = new HashMap<>();
|
|
|
|
private final List<Set<BlockPos>> lastConsumed = new ArrayList<>();
|
|
|
|
private boolean opening;
|
|
|
|
private boolean closing;
|
|
|
|
private boolean tickNow;
|
|
|
|
private long lastTick;
|
|
|
|
private final Listener listener;
|
|
|
|
|
|
|
|
public TileData(BlockPos pos, BlockState state) {
|
|
|
|
super(UBlockEntities.HIVE_STORAGE, pos, state);
|
|
|
|
listener = new Listener(pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void readNbt(NbtCompound nbt) {
|
|
|
|
opening = nbt.getBoolean("opening");
|
|
|
|
closing = nbt.getBoolean("closing");
|
|
|
|
storedBlocks.clear();
|
|
|
|
if (nbt.contains("storedBlocks", NbtElement.LIST_TYPE)) {
|
|
|
|
Entry.SERIALIZER.readAll(nbt.getList("storedBlocks", NbtElement.COMPOUND_TYPE)).forEach(entry -> {
|
|
|
|
storedBlocks.put(entry.pos(), entry);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void writeNbt(NbtCompound nbt) {
|
|
|
|
nbt.putBoolean("opening", opening);
|
|
|
|
nbt.putBoolean("closing", closing);
|
|
|
|
nbt.put("storedBlocks", Entry.SERIALIZER.writeAll(storedBlocks.values()));
|
|
|
|
}
|
|
|
|
|
|
|
|
static void tick(World world, BlockPos pos, BlockState state, TileData data) {
|
|
|
|
if (data.tickNow || (world.getTime() > data.lastTick + 2)) {
|
|
|
|
data.tickNow = false;
|
|
|
|
data.lastTick = world.getTime();
|
|
|
|
if (data.closing) {
|
|
|
|
data.stepClosing(world, pos);
|
|
|
|
|
|
|
|
if (data.lastConsumed.isEmpty() && state.get(CONSUMING)) {
|
|
|
|
world.setBlockState(pos, state.with(CONSUMING, false));
|
|
|
|
}
|
|
|
|
} else if (data.opening) {
|
|
|
|
data.stepOpening(world, pos);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void stepOpening(World world, BlockPos pos) {
|
|
|
|
if (lastConsumed.size() >= 4) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Set<BlockPos> consumed = new HashSet<>();
|
|
|
|
for (BlockPos neighbor : lastConsumed.isEmpty() ? Set.of(pos) : lastConsumed.get(lastConsumed.size() - 1)) {
|
|
|
|
if (!(neighbor.equals(pos) || (storedBlocks.containsKey(neighbor) && storedBlocks.get(neighbor).state().isOf(UBlocks.CHITIN)))) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
PosHelper.adjacentNeighbours(neighbor).forEach(adjacent -> {
|
|
|
|
BlockState s = world.getBlockState(adjacent);
|
|
|
|
|
|
|
|
if (consumed.add(adjacent.toImmutable()) && !storedBlocks.containsKey(adjacent)) {
|
|
|
|
BlockEntity data = world.getBlockEntity(adjacent);
|
|
|
|
storedBlocks.put(adjacent.toImmutable(), new Entry(adjacent.toImmutable(), s, data instanceof TileData ? null : data));
|
|
|
|
|
|
|
|
if (s.isOf(UBlocks.CHITIN)) {
|
|
|
|
world.breakBlock(adjacent, false);
|
|
|
|
} else if (s.isOf(UBlocks.HIVE)) {
|
|
|
|
world.setBlockState(adjacent, s.with(CONSUMING, true));
|
|
|
|
TileData next = BlockEntityUtil.getOrCreateBlockEntity(world, adjacent.toImmutable(), UBlockEntities.HIVE_STORAGE).orElse(null);
|
|
|
|
if (next != null) {
|
|
|
|
next.opening = opening;
|
|
|
|
next.closing = closing;
|
|
|
|
next.tickNow = true;
|
|
|
|
next.markDirty();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
lastConsumed.add(consumed);
|
|
|
|
markDirty();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void stepClosing(World world, BlockPos pos) {
|
|
|
|
if (lastConsumed.isEmpty()) {
|
|
|
|
closing = false;
|
|
|
|
markDirty();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (BlockPos neighbor : lastConsumed.remove(lastConsumed.size() - 1)) {
|
|
|
|
@Nullable
|
|
|
|
Entry entry = storedBlocks.remove(neighbor);
|
|
|
|
if (entry != null && !entry.state().isAir()) {
|
|
|
|
if (world.getBlockState(entry.pos()).isOf(UBlocks.HIVE)) {
|
|
|
|
BlockEntityUtil.getOrCreateBlockEntity(world, entry.pos(), UBlockEntities.HIVE_STORAGE).ifPresent(data -> {
|
|
|
|
data.closing = closing;
|
|
|
|
data.opening = opening;
|
|
|
|
data.tickNow = true;
|
|
|
|
data.markDirty();
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
entry.restore(world);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
markDirty();
|
|
|
|
}
|
|
|
|
|
|
|
|
record Entry (BlockPos pos, BlockState state, @Nullable BlockEntity data) {
|
|
|
|
public static final Serializer<Entry> SERIALIZER = Serializer.of(compound -> new Entry(
|
|
|
|
NbtSerialisable.BLOCK_POS.read(compound.getCompound("pos")),
|
|
|
|
NbtSerialisable.decode(BlockState.CODEC, compound.get("state")).orElse(Blocks.AIR.getDefaultState()),
|
|
|
|
compound.getCompound("data")
|
|
|
|
), entry -> {
|
|
|
|
NbtCompound compound = new NbtCompound();
|
|
|
|
compound.put("pos", NbtSerialisable.BLOCK_POS.write(entry.pos()));
|
|
|
|
compound.put("state", NbtSerialisable.encode(BlockState.CODEC, entry.state()));
|
|
|
|
if (entry.data() != null) {
|
|
|
|
compound.put("data", entry.data().createNbtWithId());
|
|
|
|
}
|
|
|
|
return compound;
|
|
|
|
});
|
|
|
|
|
|
|
|
Entry(BlockPos pos, BlockState state, NbtCompound nbt) {
|
|
|
|
this(pos, state, BlockEntity.createFromNbt(pos, state, nbt));
|
|
|
|
}
|
|
|
|
|
|
|
|
@SuppressWarnings("deprecation")
|
|
|
|
public void restore(World world) {
|
|
|
|
if (world.isAir(pos)) {
|
|
|
|
world.setBlockState(pos, state);
|
|
|
|
if (data != null) {
|
|
|
|
data.setCachedState(state);
|
|
|
|
world.addBlockEntity(data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Listener getEventListener() {
|
|
|
|
return listener;
|
|
|
|
}
|
|
|
|
|
|
|
|
class Listener implements GameEventListener {
|
|
|
|
private final PositionSource position;
|
|
|
|
|
|
|
|
Listener(BlockPos pos) {
|
|
|
|
this.position = new BlockPositionSource(pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public PositionSource getPositionSource() {
|
|
|
|
return position;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int getRange() {
|
|
|
|
return 15;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean listen(ServerWorld world, GameEvent event, Emitter emitter, Vec3d emitterPos) {
|
|
|
|
if (isImportant(event) || (EquinePredicates.IS_PLAYER.test(emitter.sourceEntity()) && !EquinePredicates.CHANGELING.test(emitter.sourceEntity()))) {
|
|
|
|
closing = true;
|
|
|
|
markDirty();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
private boolean isImportant(GameEvent event) {
|
|
|
|
return event == GameEvent.EXPLODE
|
2024-02-16 17:44:13 +01:00
|
|
|
|| event == GameEvent.PRIME_FUSE
|
2024-02-16 17:42:48 +01:00
|
|
|
|| event == GameEvent.SHRIEK;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-10-15 17:51:57 +02:00
|
|
|
}
|