Skip to content

Commit

Permalink
Merge branch '1.20.2' into 1.20.4
Browse files Browse the repository at this point in the history
  • Loading branch information
Sollace committed Feb 16, 2024
2 parents fabafd8 + 013a75f commit ccbb0bd
Show file tree
Hide file tree
Showing 6 changed files with 298 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -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);
});
}

}
260 changes: 256 additions & 4 deletions src/main/java/com/minelittlepony/unicopia/block/HiveBlock.java
Original file line number Diff line number Diff line change
@@ -1,42 +1,71 @@
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.mojang.serialization.MapCodec;
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 {
public static final MapCodec<HiveBlock> CODEC = createCodec(HiveBlock::new);
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));
});
Expand All @@ -50,7 +79,7 @@ protected MapCodec<? extends HiveBlock> getCodec() {
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
builder.add(PROPERTIES.toArray(Property[]::new));
builder.add(AWAKE);
builder.add(AWAKE, CONSUMING);
}

@Override
Expand All @@ -73,6 +102,9 @@ public void randomDisplayTick(BlockState state, World world, BlockPos pos, Rando
@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);

Expand All @@ -85,7 +117,7 @@ public BlockState getStateForNeighborUpdate(BlockState state, Direction directio

@Override
public void scheduledTick(BlockState state, ServerWorld world, BlockPos pos, Random random) {
if (!state.get(AWAKE)) {
if (state.get(CONSUMING) || !state.get(AWAKE)) {
return;
}

Expand All @@ -109,6 +141,29 @@ public void scheduledTick(BlockState state, ServerWorld world, BlockPos pos, Ran
}
}

@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) {
Expand All @@ -129,4 +184,201 @@ public float calcBlockBreakingDelta(BlockState state, PlayerEntity player, Block
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.PRIME_FUSE
|| event == GameEvent.SHRIEK;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
},
"elements": [
{
"from": [0, 0, 0],
"to": [16, 16, 2],
"from": [0.001, 0.001, -0.001],
"to": [15.999, 15.999, 2],
"faces": {
"north": {"uv": [0, 0, 16, 16], "texture": "#all"},
"east": {"uv": [14, 0, 16, 16], "texture": "#all"},
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit ccbb0bd

Please sign in to comment.