Initial tickable multipart entity implementation

This commit is contained in:
Shadowfacts 2018-12-25 12:08:48 -05:00
parent 17a5a8d6ee
commit 5759262d07
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
13 changed files with 326 additions and 198 deletions

View File

@ -1,6 +1,7 @@
package net.shadowfacts.simplemultipart;
import net.fabricmc.api.ModInitializer;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.block.entity.BlockEntityType;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.IdRegistry;
@ -9,14 +10,13 @@ import net.minecraft.world.loot.context.LootContextType;
import net.minecraft.world.loot.context.LootContextTypes;
import net.minecraft.world.loot.context.Parameter;
import net.minecraft.world.loot.context.Parameters;
import net.shadowfacts.simplemultipart.container.ContainerBlockEntity;
import net.shadowfacts.simplemultipart.container.ContainerEventHandler;
import net.shadowfacts.simplemultipart.container.ContainerBlock;
import net.shadowfacts.simplemultipart.container.*;
import net.shadowfacts.simplemultipart.multipart.Multipart;
import net.shadowfacts.simplemultipart.multipart.MultipartState;
import java.lang.reflect.Method;
import java.util.function.Consumer;
import java.util.function.Supplier;
/**
* @author shadowfacts
@ -31,7 +31,8 @@ public class SimpleMultipart implements ModInitializer {
public static final LootContextType MULTIPART_LOOT_CONTEXT = createMultipartLootContextType();
public static final ContainerBlock containerBlock = new ContainerBlock();
public static final BlockEntityType<ContainerBlockEntity> containerBlockEntity = createBlockEntityType();
public static final BlockEntityType<ContainerBlockEntity> containerBlockEntity = createBlockEntityType("container", ContainerBlockEntity::new);
public static final BlockEntityType<TickableContainerBlockEntity> tickableContainerBlockEntity = createBlockEntityType("tickable_container", TickableContainerBlockEntity::new);
@Override
public void onInitialize() {
@ -46,9 +47,9 @@ public class SimpleMultipart implements ModInitializer {
return registry;
}
private static BlockEntityType<ContainerBlockEntity> createBlockEntityType() {
BlockEntityType.Builder<ContainerBlockEntity> builder = BlockEntityType.Builder.create(ContainerBlockEntity::new);
return Registry.register(Registry.BLOCK_ENTITY, new Identifier(MODID, "container"), builder.method_11034(null));
private static <T extends BlockEntity> BlockEntityType<T> createBlockEntityType(String name, Supplier<T> supplier) {
BlockEntityType.Builder<T> builder = BlockEntityType.Builder.create(supplier);
return Registry.register(Registry.BLOCK_ENTITY, new Identifier(MODID, name), builder.method_11034(null));
}
private static LootContextType createMultipartLootContextType() {

View File

@ -0,0 +1,218 @@
package net.shadowfacts.simplemultipart.container;
import com.google.common.collect.ImmutableSet;
import net.fabricmc.fabric.api.util.NbtType;
import net.fabricmc.fabric.block.entity.ClientSerializable;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.block.entity.BlockEntityType;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.Tag;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Tickable;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.loot.context.LootContext;
import net.minecraft.world.loot.context.Parameters;
import net.shadowfacts.simplemultipart.SimpleMultipart;
import net.shadowfacts.simplemultipart.api.MultipartContainer;
import net.shadowfacts.simplemultipart.multipart.MultipartState;
import net.shadowfacts.simplemultipart.multipart.entity.MultipartEntity;
import net.shadowfacts.simplemultipart.multipart.entity.MultipartEntityProvider;
import net.shadowfacts.simplemultipart.util.MultipartHelper;
import net.shadowfacts.simplemultipart.api.MultipartView;
import net.shadowfacts.simplemultipart.util.ShapeUtils;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author shadowfacts
*/
public abstract class AbstractContainerBlockEntity extends BlockEntity implements MultipartContainer, ClientSerializable {
protected Set<Entry> parts = new HashSet<>();
public AbstractContainerBlockEntity(BlockEntityType<?> type) {
super(type);
}
@Override
public Set<MultipartView> getParts() {
return ImmutableSet.copyOf(parts);
}
@Override
public boolean canInsert(MultipartState partState) {
VoxelShape newShape = partState.getBoundingShape(null);
for (Entry e : parts) {
VoxelShape existingShape = e.state.getBoundingShape(e);
if (ShapeUtils.intersect(newShape, existingShape)) {
return false;
}
}
return true;
}
@Override
public void insert(MultipartState partState) {
if (!canInsert(partState)) {
return;
}
MultipartEntity entity = null;
if (partState.getMultipart() instanceof MultipartEntityProvider) {
entity = ((MultipartEntityProvider)partState.getMultipart()).createMultipartEntity(partState, this);
}
parts.add(new Entry(this, partState, entity));
updateWorld();
}
@Override
public void remove(MultipartState partState) {
parts.removeIf(e -> e.state == partState);
if (parts.isEmpty()) {
world.setBlockState(pos, Blocks.AIR.getDefaultState());
} else {
updateWorld();
}
}
@Override
public boolean breakPart(MultipartState partState) {
Optional<Entry> entry = parts.stream().filter(e -> e.state == partState).findFirst();
if (!entry.isPresent()) {
return false;
}
if (world instanceof ServerWorld) {
List<ItemStack> drops = getDroppedStacks(entry.get(), (ServerWorld)world, pos);
drops.forEach(stack -> Block.dropStack(world, pos, stack));
// TODO: don't drop if player is creative
}
remove(partState);
updateWorld();
return true;
}
@Override
public void schedulePartSave() {
markDirty(); // see yarn #360
}
private void updateWorld() {
boolean hasTickableParts = parts.stream().anyMatch(e -> e.getEntity() != null && e.getEntity() instanceof Tickable);
boolean currentlyTickable = this instanceof Tickable;
if (hasTickableParts != currentlyTickable) {
AbstractContainerBlockEntity newContainer = hasTickableParts ? new TickableContainerBlockEntity() : new ContainerBlockEntity();
world.setBlockEntity(pos, newContainer);
newContainer.parts = parts.stream()
.map(e -> new Entry(newContainer, e.state, e.entity))
.collect(Collectors.toSet());
}
world.markDirty(pos, world.getBlockEntity(pos));
world.scheduleBlockRender(pos);
BlockState blockState = world.getBlockState(pos);
world.updateListeners(pos, blockState, blockState, 3);
}
private List<ItemStack> getDroppedStacks(Entry e, ServerWorld world, BlockPos pos) {
LootContext.Builder builder = new LootContext.Builder(world);
builder.setRandom(world.random);
builder.put(SimpleMultipart.MULTIPART_STATE_PARAMETER, e.state);
builder.put(Parameters.POSITION, pos);
return e.state.getDroppedStacks(e, builder);
}
private ListTag partsToTag() {
ListTag list = new ListTag();
for (Entry e : parts) {
CompoundTag tag = new CompoundTag();
tag.put("part", MultipartHelper.serializeMultipartState(e.state));
if (e.entity != null) {
tag.put("entity", e.entity.toTag(new CompoundTag()));
}
list.add(tag);
}
return list;
}
private void partsFromTag(ListTag list) {
parts.clear();
for (Tag tag : list) {
CompoundTag compound = (CompoundTag)tag;
MultipartState state = MultipartHelper.deserializeMultipartState(compound.getCompound("part"));
MultipartEntity entity = null;
if (state.getMultipart() instanceof MultipartEntityProvider && compound.containsKey("entity", NbtType.COMPOUND)) {
entity = ((MultipartEntityProvider)state.getMultipart()).createMultipartEntity(state, this);
entity.fromTag(compound.getCompound("entity"));
}
parts.add(new Entry(this, state, entity));
}
}
@Override
public CompoundTag toTag(CompoundTag tag) {
tag.put("parts", partsToTag());
return super.toTag(tag);
}
@Override
public void fromTag(CompoundTag tag) {
super.fromTag(tag);
ListTag list = tag.getList("parts", NbtType.COMPOUND);
partsFromTag(list);
}
@Override
public CompoundTag toClientTag(CompoundTag tag) {
tag.put("parts", partsToTag());
return tag;
}
@Override
public void fromClientTag(CompoundTag tag) {
ListTag list = tag.getList("parts", NbtType.COMPOUND);
partsFromTag(list);
updateWorld();
}
public static class Entry implements MultipartView {
public final MultipartContainer container;
public final MultipartState state;
public final MultipartEntity entity;
private Entry(MultipartContainer container, MultipartState state, MultipartEntity entity) {
this.container = container;
this.state = state;
this.entity = entity;
}
@Override
public MultipartContainer getContainer() {
return container;
}
@Override
public MultipartState getState() {
return state;
}
@Override
public MultipartEntity getEntity() {
return entity;
}
}
}

View File

@ -71,7 +71,7 @@ public class ContainerBlock extends Block implements BlockEntityProvider, Render
}
@Override
public ContainerBlockEntity createBlockEntity(BlockView world) {
public AbstractContainerBlockEntity createBlockEntity(BlockView world) {
return new ContainerBlockEntity();
}
}

View File

@ -1,197 +1,14 @@
package net.shadowfacts.simplemultipart.container;
import com.google.common.collect.ImmutableSet;
import net.fabricmc.fabric.api.util.NbtType;
import net.fabricmc.fabric.block.entity.ClientSerializable;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.Tag;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.loot.context.LootContext;
import net.minecraft.world.loot.context.Parameters;
import net.shadowfacts.simplemultipart.SimpleMultipart;
import net.shadowfacts.simplemultipart.api.MultipartContainer;
import net.shadowfacts.simplemultipart.multipart.MultipartState;
import net.shadowfacts.simplemultipart.multipart.entity.MultipartEntity;
import net.shadowfacts.simplemultipart.multipart.entity.MultipartEntityProvider;
import net.shadowfacts.simplemultipart.util.MultipartHelper;
import net.shadowfacts.simplemultipart.api.MultipartView;
import net.shadowfacts.simplemultipart.util.ShapeUtils;
import java.util.*;
/**
* @author shadowfacts
*/
public class ContainerBlockEntity extends BlockEntity implements MultipartContainer, ClientSerializable {
private Set<Entry> parts = new HashSet<>();
public class ContainerBlockEntity extends AbstractContainerBlockEntity {
public ContainerBlockEntity() {
super(SimpleMultipart.containerBlockEntity);
}
@Override
public Set<MultipartView> getParts() {
return ImmutableSet.copyOf(parts);
}
@Override
public boolean canInsert(MultipartState partState) {
VoxelShape newShape = partState.getBoundingShape(null);
for (Entry e : parts) {
VoxelShape existingShape = e.state.getBoundingShape(e);
if (ShapeUtils.intersect(newShape, existingShape)) {
return false;
}
}
return true;
}
@Override
public void insert(MultipartState partState) {
if (!canInsert(partState)) {
return;
}
MultipartEntity entity = null;
if (partState.getMultipart() instanceof MultipartEntityProvider) {
entity = ((MultipartEntityProvider)partState.getMultipart()).createMultipartEntity(partState, this);
}
parts.add(new Entry(partState, entity));
markDirty();
world.scheduleBlockRender(pos);
}
@Override
public void remove(MultipartState partState) {
parts.removeIf(e -> e.state == partState);
if (parts.isEmpty()) {
world.setBlockState(pos, Blocks.AIR.getDefaultState());
}
}
@Override
public boolean breakPart(MultipartState partState) {
Optional<Entry> entry = parts.stream().filter(e -> e.state == partState).findFirst();
if (!entry.isPresent()) {
return false;
}
if (world instanceof ServerWorld) {
List<ItemStack> drops = getDroppedStacks(entry.get(), (ServerWorld)world, pos);
drops.forEach(stack -> Block.dropStack(world, pos, stack));
// TODO: don't drop if player is creative
}
remove(partState);
world.markDirty(pos, this);
world.scheduleBlockRender(pos);
BlockState blockState = world.getBlockState(pos);
world.updateListeners(pos, blockState, blockState, 3);
return true;
}
@Override
public void schedulePartSave() {
markDirty(); // see yarn #360
}
private List<ItemStack> getDroppedStacks(ContainerBlockEntity.Entry e, ServerWorld world, BlockPos pos) {
LootContext.Builder builder = new LootContext.Builder(world);
builder.setRandom(world.random);
builder.put(SimpleMultipart.MULTIPART_STATE_PARAMETER, e.state);
builder.put(Parameters.POSITION, pos);
return e.state.getDroppedStacks(e, builder);
}
private ListTag partsToTag() {
ListTag list = new ListTag();
for (Entry e : parts) {
CompoundTag tag = new CompoundTag();
tag.put("part", MultipartHelper.serializeMultipartState(e.state));
if (e.entity != null) {
tag.put("entity", e.entity.toTag(new CompoundTag()));
}
list.add(tag);
}
return list;
}
private void partsFromTag(ListTag list) {
parts.clear();
for (Tag tag : list) {
CompoundTag compound = (CompoundTag)tag;
MultipartState state = MultipartHelper.deserializeMultipartState(compound.getCompound("part"));
MultipartEntity entity = null;
if (state.getMultipart() instanceof MultipartEntityProvider && compound.containsKey("entity", NbtType.COMPOUND)) {
entity = ((MultipartEntityProvider)state.getMultipart()).createMultipartEntity(state, this);
entity.fromTag(compound.getCompound("entity"));
}
parts.add(new Entry(state, entity));
}
}
@Override
public CompoundTag toTag(CompoundTag tag) {
tag.put("parts", partsToTag());
return super.toTag(tag);
}
@Override
public void fromTag(CompoundTag tag) {
super.fromTag(tag);
ListTag list = tag.getList("parts", NbtType.COMPOUND);
partsFromTag(list);
}
@Override
public CompoundTag toClientTag(CompoundTag tag) {
tag.put("parts", partsToTag());
return tag;
}
@Override
public void fromClientTag(CompoundTag tag) {
ListTag list = tag.getList("parts", NbtType.COMPOUND);
partsFromTag(list);
world.scheduleBlockRender(pos);
}
public class Entry implements MultipartView {
public final MultipartState state;
public final MultipartEntity entity;
private Entry(MultipartState state, MultipartEntity entity) {
this.state = state;
this.entity = entity;
}
@Override
public ContainerBlockEntity getContainer() {
return ContainerBlockEntity.this; // TODO: is this bad?
}
@Override
public MultipartState getState() {
return state;
}
@Override
public MultipartEntity getEntity() {
return entity;
}
}
}

View File

@ -1,6 +1,7 @@
package net.shadowfacts.simplemultipart.container;
import net.fabricmc.fabric.events.PlayerInteractionEvent;
import net.minecraft.block.Block;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;

View File

@ -0,0 +1,24 @@
package net.shadowfacts.simplemultipart.container;
import net.minecraft.util.Tickable;
import net.shadowfacts.simplemultipart.SimpleMultipart;
/**
* @author shadowfacts
*/
public class TickableContainerBlockEntity extends AbstractContainerBlockEntity implements Tickable {
public TickableContainerBlockEntity() {
super(SimpleMultipart.tickableContainerBlockEntity);
}
@Override
public void tick() {
for (Entry e : parts) {
if (e.getEntity() != null && e.getEntity() instanceof Tickable) {
((Tickable)e.getEntity()).tick();
}
}
}
}

View File

@ -6,7 +6,6 @@ import net.minecraft.item.ItemUsageContext;
import net.minecraft.util.ActionResult;
import net.shadowfacts.simplemultipart.SimpleMultipart;
import net.shadowfacts.simplemultipart.api.MultipartContainer;
import net.shadowfacts.simplemultipart.container.ContainerBlockEntity;
import net.shadowfacts.simplemultipart.multipart.Multipart;
import net.shadowfacts.simplemultipart.multipart.MultipartState;
import net.shadowfacts.simplemultipart.util.MultipartPlacementContext;

View File

@ -15,7 +15,6 @@ import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.World;
import net.shadowfacts.simplemultipart.SimpleMultipart;
import net.shadowfacts.simplemultipart.api.MultipartContainer;
import net.shadowfacts.simplemultipart.container.ContainerBlockEntity;
import net.shadowfacts.simplemultipart.multipart.Multipart;
import net.shadowfacts.simplemultipart.multipart.MultipartState;

View File

@ -6,7 +6,8 @@ import net.minecraft.util.Hand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
import net.shadowfacts.simplemultipart.container.ContainerBlockEntity;
import net.shadowfacts.simplemultipart.api.MultipartContainer;
import net.shadowfacts.simplemultipart.container.AbstractContainerBlockEntity;
import net.shadowfacts.simplemultipart.multipart.Multipart;
import net.shadowfacts.simplemultipart.multipart.MultipartState;
import net.shadowfacts.simplemultipart.multipart.entity.MultipartEntity;
@ -33,17 +34,17 @@ public class EntityTestPart extends Multipart implements MultipartEntityProvider
}
@Override
public MultipartEntity createMultipartEntity(MultipartState state, ContainerBlockEntity container) {
public MultipartEntity createMultipartEntity(MultipartState state, MultipartContainer container) {
return new Entity(container);
}
public static class Entity extends MultipartEntity {
public Entity(ContainerBlockEntity container) {
public Entity(MultipartContainer container) {
super(container);
}
public BlockPos getPos() {
return container.getPos();
return ((AbstractContainerBlockEntity)container).getPos();
}
}

View File

@ -19,11 +19,13 @@ public class MultipartTestMod implements ModInitializer {
public static final SlabMultipart ironSlab = new SlabMultipart();
public static final SlabMultipart goldSlab = new SlabMultipart();
public static final EntityTestPart entityTest = new EntityTestPart();
public static final TickableEntityTestPart tickableEntityTest = new TickableEntityTestPart();
public static final ItemMultipart testItem = new ItemMultipart(testPart);
public static final ItemMultipart ironSlabItem = new ItemMultipart(ironSlab);
public static final ItemMultipart goldSlabItem = new ItemMultipart(goldSlab);
public static final ItemMultipart entityTestItem = new ItemMultipart(entityTest);
public static final ItemMultipart tickableEntityTestItem = new ItemMultipart(tickableEntityTest);
@Override
public void onInitialize() {
@ -31,6 +33,7 @@ public class MultipartTestMod implements ModInitializer {
registerPartAndItem("iron_slab", ironSlab, ironSlabItem);
registerPartAndItem("gold_slab", goldSlab, goldSlabItem);
registerPartAndItem("entity_test", entityTest, entityTestItem);
registerPartAndItem("tickable_entity_test", tickableEntityTest, tickableEntityTestItem);
}
private void registerPartAndItem(String name, Multipart part, Item item) {

View File

@ -0,0 +1,53 @@
package net.shadowfacts.simplemultipart.test;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.text.StringTextComponent;
import net.minecraft.util.Hand;
import net.minecraft.util.Tickable;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
import net.shadowfacts.simplemultipart.api.MultipartContainer;
import net.shadowfacts.simplemultipart.api.MultipartView;
import net.shadowfacts.simplemultipart.multipart.Multipart;
import net.shadowfacts.simplemultipart.multipart.MultipartState;
import net.shadowfacts.simplemultipart.multipart.entity.MultipartEntity;
import net.shadowfacts.simplemultipart.multipart.entity.MultipartEntityProvider;
/**
* @author shadowfacts
*/
public class TickableEntityTestPart extends Multipart implements MultipartEntityProvider {
@Override
@Deprecated
public VoxelShape getBoundingShape(MultipartState state, MultipartView view) {
return VoxelShapes.cube(6/16f, 6/16f, 6/16f, 10/16f, 10/16f, 10/16f);
}
@Override
@Deprecated
public boolean activate(MultipartState state, MultipartView view, PlayerEntity player, Hand hand) {
int timer = ((Entity)view.getEntity()).timer;
player.addChatMessage(new StringTextComponent("Timer: " + timer), false);
return true;
}
@Override
public MultipartEntity createMultipartEntity(MultipartState state, MultipartContainer container) {
return new Entity(container);
}
public static class Entity extends MultipartEntity implements Tickable {
public int timer = 0;
public Entity(MultipartContainer container) {
super(container);
}
@Override
public void tick() {
timer++;
}
}
}

View File

@ -0,0 +1,6 @@
{
"parent": "multipart_test:multipart/center",
"textures": {
"texture": "block/redstone_block"
}
}

View File

@ -0,0 +1,6 @@
{
"variants": {
"": { "model": "multipart_test:multipart/tickable_entity_test" },
"inventory": { "model": "multipart_test:multipart/tickable_entity_test" }
}
}