Added a new feature to hive blocks

This commit is contained in:
Sollace 2024-02-16 16:42:48 +00:00
parent 0e1d7e8a51
commit 4edde4d479
No known key found for this signature in database
GPG key ID: E52FACE7B5C773DB
3 changed files with 296 additions and 4 deletions

View file

@ -0,0 +1,39 @@
package com.minelittlepony.unicopia.block;
import java.util.Optional;
import org.jetbrains.annotations.Nullable;
import net.minecraft.block.BlockEntityProvider;
import net.minecraft.block.BlockState;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.block.entity.BlockEntityTicker;
import net.minecraft.block.entity.BlockEntityType;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
public interface BlockEntityUtil {
@SuppressWarnings("unchecked")
@Nullable
static <E extends BlockEntity, A extends BlockEntity> BlockEntityTicker<A> checkType(BlockEntityType<A> givenType, BlockEntityType<E> expectedType, BlockEntityTicker<? super E> ticker) {
return expectedType == givenType ? (@Nullable BlockEntityTicker<A>) ticker : null;
}
@SuppressWarnings({ "unchecked", "deprecation" })
static <T extends BlockEntity> Optional<T> getOrCreateBlockEntity(World world, BlockPos pos, BlockEntityType<T> type) {
return world.getBlockEntity(pos, type).or(() -> {
BlockState state = world.getBlockState(pos);
if (!(state.hasBlockEntity())) {
return Optional.empty();
}
BlockEntity e = ((BlockEntityProvider)state.getBlock()).createBlockEntity(pos, state);
if (e == null || e.getType() != type) {
return Optional.empty();
}
e.setCachedState(state);
world.addBlockEntity(e);
return Optional.of((T)e);
});
}
}

View file

@ -1,40 +1,69 @@
package com.minelittlepony.unicopia.block; package com.minelittlepony.unicopia.block;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
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;
import com.minelittlepony.unicopia.EquineContext; import com.minelittlepony.unicopia.EquineContext;
import com.minelittlepony.unicopia.EquinePredicates;
import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.particle.ParticleUtils; import com.minelittlepony.unicopia.particle.ParticleUtils;
import com.minelittlepony.unicopia.util.NbtSerialisable;
import com.minelittlepony.unicopia.util.NbtSerialisable.Serializer;
import com.minelittlepony.unicopia.util.PosHelper;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.BlockEntityProvider;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.ConnectingBlock; import net.minecraft.block.ConnectingBlock;
import net.minecraft.block.ShapeContext; import net.minecraft.block.ShapeContext;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.block.entity.BlockEntityTicker;
import net.minecraft.block.entity.BlockEntityType;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtElement;
import net.minecraft.particle.ParticleTypes; import net.minecraft.particle.ParticleTypes;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
import net.minecraft.sound.SoundCategory; import net.minecraft.sound.SoundCategory;
import net.minecraft.state.StateManager; import net.minecraft.state.StateManager;
import net.minecraft.state.property.BooleanProperty; import net.minecraft.state.property.BooleanProperty;
import net.minecraft.state.property.Property; import net.minecraft.state.property.Property;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction; import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.random.Random; import net.minecraft.util.math.random.Random;
import net.minecraft.util.shape.VoxelShape; import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes; import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.BlockView; import net.minecraft.world.BlockView;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraft.world.WorldAccess; import net.minecraft.world.WorldAccess;
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;
public class HiveBlock extends ConnectingBlock { public class HiveBlock extends ConnectingBlock implements BlockEntityProvider {
static final BooleanProperty AWAKE = BooleanProperty.of("awake"); static final BooleanProperty AWAKE = BooleanProperty.of("awake");
static final BooleanProperty CONSUMING = BooleanProperty.of("consuming");
static final Collection<BooleanProperty> PROPERTIES = FACING_PROPERTIES.values(); static final Collection<BooleanProperty> PROPERTIES = FACING_PROPERTIES.values();
public HiveBlock(Settings settings) { public HiveBlock(Settings settings) {
super(0.5F, settings); super(0.5F, settings);
setDefaultState(getDefaultState().with(AWAKE, false)); setDefaultState(getDefaultState().with(AWAKE, false).with(CONSUMING, false));
PROPERTIES.forEach(property -> { PROPERTIES.forEach(property -> {
setDefaultState(getDefaultState().with(property, true)); setDefaultState(getDefaultState().with(property, true));
}); });
@ -43,7 +72,7 @@ public class HiveBlock extends ConnectingBlock {
@Override @Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) { protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
builder.add(PROPERTIES.toArray(Property[]::new)); builder.add(PROPERTIES.toArray(Property[]::new));
builder.add(AWAKE); builder.add(AWAKE, CONSUMING);
} }
@Override @Override
@ -66,6 +95,9 @@ public class HiveBlock extends ConnectingBlock {
@Deprecated @Deprecated
@Override @Override
public BlockState getStateForNeighborUpdate(BlockState state, Direction direction, BlockState neighborState, WorldAccess world, BlockPos pos, BlockPos neighborPos) { public BlockState getStateForNeighborUpdate(BlockState state, Direction direction, BlockState neighborState, WorldAccess world, BlockPos pos, BlockPos neighborPos) {
if (state.get(CONSUMING)) {
return state;
}
boolean connected = !neighborState.isAir(); boolean connected = !neighborState.isAir();
state = state.with(FACING_PROPERTIES.get(direction), connected); state = state.with(FACING_PROPERTIES.get(direction), connected);
@ -78,7 +110,7 @@ public class HiveBlock extends ConnectingBlock {
@Override @Override
public void scheduledTick(BlockState state, ServerWorld world, BlockPos pos, Random random) { public void scheduledTick(BlockState state, ServerWorld world, BlockPos pos, Random random) {
if (!state.get(AWAKE)) { if (state.get(CONSUMING) || !state.get(AWAKE)) {
return; return;
} }
@ -102,6 +134,29 @@ public class HiveBlock extends ConnectingBlock {
} }
} }
@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;
}
@Deprecated @Deprecated
@Override @Override
public void neighborUpdate(BlockState state, World world, BlockPos pos, Block sourceBlock, BlockPos sourcePos, boolean notify) { public void neighborUpdate(BlockState state, World world, BlockPos pos, Block sourceBlock, BlockPos sourcePos, boolean notify) {
@ -122,4 +177,201 @@ public class HiveBlock extends ConnectingBlock {
delta *= Pony.of(player).getSpecies() == Race.CHANGELING ? 2 : 1; delta *= Pony.of(player).getSpecies() == Race.CHANGELING ? 2 : 1;
return delta; return delta;
} }
@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
|| event == GameEvent.ENTITY_ROAR
|| event == GameEvent.SHRIEK;
}
}
}
} }

View file

@ -14,6 +14,7 @@ public interface UBlockEntities {
BlockEntityType<WeatherVaneBlock.WeatherVane> WEATHER_VANE = create("weather_vane", BlockEntityType.Builder.create(WeatherVaneBlock.WeatherVane::new, UBlocks.WEATHER_VANE)); BlockEntityType<WeatherVaneBlock.WeatherVane> WEATHER_VANE = create("weather_vane", BlockEntityType.Builder.create(WeatherVaneBlock.WeatherVane::new, UBlocks.WEATHER_VANE));
BlockEntityType<CloudBedBlock.Tile> FANCY_BED = create("fancy_bed", BlockEntityType.Builder.create(CloudBedBlock.Tile::new, UBlocks.CLOTH_BED, UBlocks.CLOUD_BED)); BlockEntityType<CloudBedBlock.Tile> FANCY_BED = create("fancy_bed", BlockEntityType.Builder.create(CloudBedBlock.Tile::new, UBlocks.CLOTH_BED, UBlocks.CLOUD_BED));
BlockEntityType<ChestBlockEntity> CLOUD_CHEST = create("cloud_chest", BlockEntityType.Builder.create(CloudChestBlock.TileData::new, UBlocks.CLOUD_CHEST)); BlockEntityType<ChestBlockEntity> CLOUD_CHEST = create("cloud_chest", BlockEntityType.Builder.create(CloudChestBlock.TileData::new, UBlocks.CLOUD_CHEST));
BlockEntityType<HiveBlock.TileData> HIVE_STORAGE = create("hive_storage", BlockEntityType.Builder.create(HiveBlock.TileData::new, UBlocks.HIVE));
static <T extends BlockEntity> BlockEntityType<T> create(String id, Builder<T> builder) { static <T extends BlockEntity> BlockEntityType<T> create(String id, Builder<T> builder) {
return Registry.register(Registries.BLOCK_ENTITY_TYPE, id, builder.build(null)); return Registry.register(Registries.BLOCK_ENTITY_TYPE, id, builder.build(null));