mirror of
https://github.com/Sollace/Unicopia.git
synced 2024-11-23 13:37:58 +01:00
Added a new feature to hive blocks
This commit is contained in:
parent
0e1d7e8a51
commit
4edde4d479
3 changed files with 296 additions and 4 deletions
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -1,40 +1,69 @@
|
|||
package com.minelittlepony.unicopia.block;
|
||||
|
||||
import java.util.ArrayList;
|
||||
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.EquinePredicates;
|
||||
import com.minelittlepony.unicopia.Race;
|
||||
import com.minelittlepony.unicopia.USounds;
|
||||
import com.minelittlepony.unicopia.entity.player.Pony;
|
||||
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.BlockEntityProvider;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.block.Blocks;
|
||||
import net.minecraft.block.ConnectingBlock;
|
||||
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.nbt.NbtCompound;
|
||||
import net.minecraft.nbt.NbtElement;
|
||||
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;
|
||||
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.Direction;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
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;
|
||||
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 CONSUMING = BooleanProperty.of("consuming");
|
||||
static final Collection<BooleanProperty> PROPERTIES = FACING_PROPERTIES.values();
|
||||
|
||||
public HiveBlock(Settings settings) {
|
||||
super(0.5F, settings);
|
||||
setDefaultState(getDefaultState().with(AWAKE, false));
|
||||
setDefaultState(getDefaultState().with(AWAKE, false).with(CONSUMING, false));
|
||||
PROPERTIES.forEach(property -> {
|
||||
setDefaultState(getDefaultState().with(property, true));
|
||||
});
|
||||
|
@ -43,7 +72,7 @@ public class HiveBlock extends ConnectingBlock {
|
|||
@Override
|
||||
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
|
||||
builder.add(PROPERTIES.toArray(Property[]::new));
|
||||
builder.add(AWAKE);
|
||||
builder.add(AWAKE, CONSUMING);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -66,6 +95,9 @@ public class HiveBlock extends ConnectingBlock {
|
|||
@Deprecated
|
||||
@Override
|
||||
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();
|
||||
state = state.with(FACING_PROPERTIES.get(direction), connected);
|
||||
|
||||
|
@ -78,7 +110,7 @@ public class HiveBlock extends ConnectingBlock {
|
|||
|
||||
@Override
|
||||
public void scheduledTick(BlockState state, ServerWorld world, BlockPos pos, Random random) {
|
||||
if (!state.get(AWAKE)) {
|
||||
if (state.get(CONSUMING) || !state.get(AWAKE)) {
|
||||
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
|
||||
@Override
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<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<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) {
|
||||
return Registry.register(Registries.BLOCK_ENTITY_TYPE, id, builder.build(null));
|
||||
|
|
Loading…
Reference in a new issue