Shulker Box Boats
All checks were successful
Twine/pipeline/head This commit looks good

This commit is contained in:
TheBrokenRail 2020-06-16 12:21:17 -04:00
parent 8164f8f8b9
commit 42b73413bf
18 changed files with 396 additions and 212 deletions

View File

@ -30,7 +30,7 @@ Glowing Obsidian heals monsters and hurts everything else. It naturally generate
</table>
## Chest Boats
Right-Click a Boat with a Chest to place it in the Boat, you can also use an Ender Chest or Trapped Chest. Shift-Right-Click the Boat to open the Chest. You can also open the Chest inside the Boat by opening your inventory.
Right-Click a Boat with a Chest to place it in the Boat, you can also use an Ender Chest, Trapped Chest, or Shulker Box. Shift-Right-Click the Boat to open the Chest. You can also open the Chest inside the Boat by opening your inventory.
## Difficulty Stages
Each player has a "personal" stage of an area, the highest online player's difficulty stage in an area will be the "effective" difficulty stage of that area.

View File

@ -2,6 +2,7 @@ package com.thebrokenrail.twine;
import com.thebrokenrail.twine.advancement.ChestBoatCriterion;
import com.thebrokenrail.twine.advancement.EnderChestBoatCriterion;
import com.thebrokenrail.twine.advancement.ShulkerBoxBoatCriterion;
import com.thebrokenrail.twine.block.CreativeItemSpawnerBlock;
import com.thebrokenrail.twine.block.GlowingObsidianBlock;
import com.thebrokenrail.twine.item.BackpackItem;
@ -47,6 +48,7 @@ public class Twine implements ModInitializer {
public static ChestBoatCriterion CHEST_BOAT_CRITERION = CriteriaHook.callRegister(new ChestBoatCriterion());
public static EnderChestBoatCriterion ENDER_CHEST_BOAT_CRITERION = CriteriaHook.callRegister(new EnderChestBoatCriterion());
public static ShulkerBoxBoatCriterion SHULKER_BOX_BOAT_CRITERION = CriteriaHook.callRegister(new ShulkerBoxBoatCriterion());
@Override
public void onInitialize() {

View File

@ -0,0 +1,29 @@
package com.thebrokenrail.twine.advancement;
import com.google.gson.JsonObject;
import com.thebrokenrail.twine.Twine;
import net.minecraft.advancement.criterion.AbstractCriterion;
import net.minecraft.advancement.criterion.AbstractCriterionConditions;
import net.minecraft.predicate.entity.AdvancementEntityPredicateDeserializer;
import net.minecraft.predicate.entity.EntityPredicate;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.Identifier;
public class ShulkerBoxBoatCriterion extends AbstractCriterion<AbstractCriterionConditions> {
private static final Identifier ID = new Identifier(Twine.NAMESPACE, "shulker_box_boat");
public void trigger(ServerPlayerEntity player) {
this.test(player, conditions -> true);
}
@Override
protected AbstractCriterionConditions conditionsFromJson(JsonObject obj, EntityPredicate.Extended playerPredicate, AdvancementEntityPredicateDeserializer predicateDeserializer) {
return new AbstractCriterionConditions(ID, playerPredicate) {
};
}
@Override
public Identifier getId() {
return ID;
}
}

View File

@ -5,8 +5,6 @@ import com.thebrokenrail.twine.util.BackpackScreenHandler;
import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.inventory.Inventories;
import net.minecraft.inventory.Inventory;
import net.minecraft.item.DyeableItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
@ -17,87 +15,9 @@ import net.minecraft.text.Text;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.TypedActionResult;
import net.minecraft.util.collection.DefaultedList;
import net.minecraft.world.World;
public class BackpackItem extends Item implements DyeableItem {
public static class ItemInventory implements Inventory {
private final int size;
private final ItemStack stack;
private final Hand hand;
private final DefaultedList<ItemStack> inv;
public ItemInventory(int size, PlayerEntity player, Hand hand) {
this.size = size;
this.stack = player.getStackInHand(hand);
this.hand = hand;
inv = getInv();
}
private DefaultedList<ItemStack> getInv() {
DefaultedList<ItemStack> list = DefaultedList.ofSize(size, ItemStack.EMPTY);
Inventories.fromTag(stack.getOrCreateTag(), list);
return list;
}
private void setInv(DefaultedList<ItemStack> list) {
Inventories.toTag(stack.getOrCreateTag(), list);
}
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
for (ItemStack item : inv) {
if (item != ItemStack.EMPTY) {
return false;
}
}
return true;
}
@Override
public ItemStack getStack(int slot) {
return inv.get(slot);
}
@Override
public ItemStack removeStack(int slot, int amount) {
return inv.get(slot).split(amount);
}
@Override
public ItemStack removeStack(int slot) {
ItemStack item = inv.get(slot);
return item.split(item.getCount());
}
@Override
public void setStack(int slot, ItemStack stack) {
inv.set(slot, stack);
}
@Override
public void markDirty() {
setInv(inv);
}
@Override
public boolean canPlayerUse(PlayerEntity player) {
return player.getStackInHand(hand) == stack;
}
@Override
public void clear() {
for (int i = 0; i < size; i++) {
inv.set(i, ItemStack.EMPTY);
}
}
}
private final boolean large;
public BackpackItem(boolean large) {

View File

@ -1,21 +1,21 @@
package com.thebrokenrail.twine.mixin;
import com.thebrokenrail.twine.Twine;
import com.thebrokenrail.twine.util.BoatChestMode;
import com.thebrokenrail.twine.util.BoatInventory;
import com.thebrokenrail.twine.util.BoatUtil;
import com.thebrokenrail.twine.util.EnderChestInventoryWrapper;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.EnderChestBlock;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.entity.Entity;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.entity.data.DataTracker;
import net.minecraft.entity.data.TrackedData;
import net.minecraft.entity.data.TrackedDataHandlerRegistry;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.vehicle.BoatEntity;
import net.minecraft.inventory.Inventory;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.screen.GenericContainerScreenHandler;
import net.minecraft.screen.SimpleNamedScreenHandlerFactory;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.ActionResult;
@ -34,21 +34,23 @@ import java.util.List;
@Mixin(BoatEntity.class)
public class MixinBoatEntity implements BoatUtil {
private static final TrackedData<Integer> CHEST_MODE = DataTracker.registerData(BoatEntity.class, TrackedDataHandlerRegistry.INTEGER);
@Inject(at = @At("RETURN"), method = "initDataTracker")
public void initDataTracker(CallbackInfo info) {
((BoatEntity) (Object) this).getDataTracker().startTracking(BoatUtil.HAS_CHEST, ChestMode.NONE.name());
((BoatEntity) (Object) this).getDataTracker().startTracking(CHEST_MODE, BoatChestMode.NONE.getID());
}
@Inject(at = @At("HEAD"), method = "canAddPassenger", cancellable = true)
public void canAddPassenger(Entity passenger, CallbackInfoReturnable<Boolean> info) {
if (((BoatEntity) (Object) this).getPassengerList().size() > 0 && getChestMode() != ChestMode.NONE) {
if (((BoatEntity) (Object) this).getPassengerList().size() > 0 && getChestMode() != BoatChestMode.NONE) {
info.setReturnValue(false);
}
}
@Redirect(at = @At(value = "INVOKE", target = "Ljava/util/List;size()I"), method = "updatePassengerPosition", allow = 2, require = 2)
public int updatePassengerPosition(List<Entity> list) {
if (getChestMode() != ChestMode.NONE) {
if (getChestMode() != BoatChestMode.NONE) {
return list.size() + 1;
} else {
return list.size();
@ -57,25 +59,28 @@ public class MixinBoatEntity implements BoatUtil {
@Inject(at = @At("HEAD"), method = "interact", cancellable = true)
public void interact(PlayerEntity player, Hand hand, CallbackInfoReturnable<ActionResult> info) {
ChestMode hasChest = getChestMode();
ItemStack stack = player.getStackInHand(hand);
ChestMode newMode = ChestMode.valueOF(stack.getItem());
if (newMode != ChestMode.NONE && hasChest == ChestMode.NONE) {
BoatChestMode mode = getChestMode();
ItemStack itemStack = player.getStackInHand(hand);
BoatChestMode newMode = BoatChestMode.valueOf(Block.getBlockFromItem(itemStack.getItem()));
if (newMode != BoatChestMode.NONE && mode == BoatChestMode.NONE) {
List<Entity> passengers = ((BoatEntity) (Object) this).getPassengerList();
for (int i = 1; i < passengers.size(); i++) {
passengers.get(i).stopRiding();
}
if (!player.getEntityWorld().isClient()) {
if (newMode.hasItems()) {
Twine.CHEST_BOAT_CRITERION.trigger((ServerPlayerEntity) player);
} else if (newMode == ChestMode.ENDER_CHEST) {
Twine.ENDER_CHEST_BOAT_CRITERION.trigger((ServerPlayerEntity) player);
}
setChestMode(newMode);
updateInventory();
}
ItemStack newStack;
if (!player.isCreative()) {
stack.decrement(1);
newStack = itemStack.split(1);
} else {
newStack = itemStack.copy();
newStack.setCount(1);
}
setChestItem(newStack);
newMode.interact((ServerPlayerEntity) player);
}
info.setReturnValue(ActionResult.SUCCESS);
} else if (player.isSneaking()) {
@ -87,19 +92,26 @@ public class MixinBoatEntity implements BoatUtil {
}
@Unique
private void setChestMode(ChestMode mode) {
((BoatEntity) (Object) this).getDataTracker().set(BoatUtil.HAS_CHEST, mode.name());
private void updateChestMode() {
BoatChestMode mode = BoatChestMode.valueOf(Block.getBlockFromItem(stack.getItem()));
((BoatEntity) (Object) this).getDataTracker().set(CHEST_MODE, mode.getID());
}
@Unique
private void dropItemsOnDestroy(boolean isCreative) {
if (((BoatEntity) (Object) this).getEntityWorld().getGameRules().getBoolean(GameRules.DO_ENTITY_DROPS)) {
BoatChestMode mode = getChestMode();
if (mode.containsItems()) {
((BoatEntity) (Object) this).dropStack(stack.copy());
} else {
if (!isCreative) {
((BoatEntity) (Object) this).dropStack(new ItemStack(getChestMode().getItem()));
((BoatEntity) (Object) this).dropStack(getStackWithoutItems());
}
dropItems();
}
}
}
@Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/vehicle/BoatEntity;remove()V"), method = "damage")
public void damage(DamageSource source, float amount, CallbackInfoReturnable<Boolean> info) {
@ -116,14 +128,15 @@ public class MixinBoatEntity implements BoatUtil {
@Unique
private void updateInventory() {
items = getChestMode().hasItems() ? new BoatInventory((BoatEntity) (Object) this, 27) : null;
BoatChestMode mode = getChestMode();
items = mode.hasItems() ? new BoatInventory((BoatEntity) (Object) this, mode.getSize(), stack, mode.getOpenSound(), mode.getCloseSound()) : null;
}
@Unique
private void dropItems() {
if (items != null) {
for (int i = 0; i < items.size(); ++i) {
ItemStack itemStack = items.getStack(i);
ItemStack itemStack = items.getStack(i).copy();
if (!itemStack.isEmpty() && !EnchantmentHelper.hasVanishingCurse(itemStack)) {
((BoatEntity) (Object) this).dropStack(itemStack);
}
@ -131,57 +144,46 @@ public class MixinBoatEntity implements BoatUtil {
}
}
private void setChestItem(ItemStack newStack) {
stack = newStack;
updateChestMode();
updateInventory();
}
@Unique
private ItemStack stack;
@Unique
private ItemStack getStackWithoutItems() {
ItemStack newStack = stack.copy();
CompoundTag tag = newStack.getOrCreateTag();
CompoundTag entityTag = tag.getCompound("BlockEntityTag");
entityTag.remove("Items");
if (entityTag.getSize() < 1) {
tag.remove("BlockEntityTag");
}
return newStack;
}
@Inject(at = @At("RETURN"), method = "writeCustomDataToTag")
public void writeCustomDataToTag(CompoundTag tag, CallbackInfo info) {
ChestMode hasChest = getChestMode();
tag.putString("HasChest", hasChest.name());
if (hasChest.hasItems()) {
ListTag listTag = new ListTag();
for (int i = 2; i < this.items.size(); ++i) {
ItemStack itemStack = this.items.getStack(i);
if (!itemStack.isEmpty()) {
CompoundTag compoundTag = new CompoundTag();
compoundTag.putByte("Slot", (byte) i);
itemStack.toTag(compoundTag);
listTag.add(compoundTag);
}
}
tag.put("Items", listTag);
}
tag.put("ChestItem", stack.toTag(new CompoundTag()));
}
@Inject(at = @At("RETURN"), method = "readCustomDataFromTag")
public void readCustomDataFromTag(CompoundTag tag, CallbackInfo info) {
ChestMode hasChest;
try {
hasChest = ChestMode.valueOf(tag.getString("HasChest"));
} catch (IllegalArgumentException e) {
hasChest = ChestMode.NONE;
}
setChestMode(hasChest);
updateInventory();
if (hasChest.hasItems()) {
ListTag listTag = tag.getList("Items", 10);
CompoundTag itemTag = tag.getCompound("ChestItem");
ItemStack newStack = ItemStack.fromTag(itemTag);
for (int i = 0; i < listTag.size(); ++i) {
CompoundTag compoundTag = listTag.getCompound(i);
int j = compoundTag.getByte("Slot") & 255;
if (j >= 2 && j < items.size()) {
items.setStack(j, ItemStack.fromTag(compoundTag));
}
}
}
setChestItem(newStack);
}
@Override
public ChestMode getChestMode() {
try {
return ChestMode.valueOf(((BoatEntity) (Object) this).getDataTracker().get(BoatUtil.HAS_CHEST));
} catch (IllegalArgumentException e) {
return ChestMode.NONE;
}
public BoatChestMode getChestMode() {
return BoatChestMode.valueOf(((BoatEntity) (Object) this).getDataTracker().get(CHEST_MODE));
}
@Override
@ -191,10 +193,7 @@ public class MixinBoatEntity implements BoatUtil {
@Override
public void openInventory(PlayerEntity player) {
if (getChestMode().hasItems()) {
player.openHandledScreen(new SimpleNamedScreenHandlerFactory((i, playerInventory, playerEntity) -> GenericContainerScreenHandler.createGeneric9x3(i, playerInventory, getChestInventory()), ((BoatEntity) (Object) this).getDisplayName()));
} else if (getChestMode() == ChestMode.ENDER_CHEST) {
player.openHandledScreen(new SimpleNamedScreenHandlerFactory((i, playerInventory, playerEntity) -> GenericContainerScreenHandler.createGeneric9x3(i, playerInventory, new EnderChestInventoryWrapper((BoatEntity) (Object) this, player.getEnderChestInventory())), EnderChestBlock.CONTAINER_NAME));
}
BoatChestMode mode = getChestMode();
player.openHandledScreen(new SimpleNamedScreenHandlerFactory((i, playerInventory, playerEntity) -> mode.getScreenHandlerFactory().createMenu(i, playerInventory, playerEntity, MixinBoatEntity.this), mode.getScreenHandlerName(stack)));
}
}

View File

@ -3,7 +3,6 @@ package com.thebrokenrail.twine.mixin;
import com.thebrokenrail.twine.util.BoatUtil;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.block.Block;
import net.minecraft.block.BlockRenderType;
import net.minecraft.block.BlockState;
import net.minecraft.client.MinecraftClient;
@ -23,7 +22,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
public class MixinBoatEntityRenderer {
@Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/client/util/math/MatrixStack;pop()V"), method = "render", allow = 1)
public void render(BoatEntity boatEntity, float f, float g, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i, CallbackInfo info) {
BlockState blockState = Block.getBlockFromItem(((BoatUtil) boatEntity).getChestMode().getItem()).getDefaultState();
BlockState blockState = ((BoatUtil) boatEntity).getChestMode().getBlock().getDefaultState();
if (blockState.getRenderType() != BlockRenderType.INVISIBLE) {
matrixStack.push();
matrixStack.multiply(Vector3f.POSITIVE_X.getDegreesQuaternion(180.0f));

View File

@ -1,5 +1,6 @@
package com.thebrokenrail.twine.mixin;
import com.thebrokenrail.twine.util.BoatChestMode;
import com.thebrokenrail.twine.util.BoatUtil;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
@ -23,7 +24,7 @@ public class MixinClientPlayerInteractionManager {
@Inject(at = @At("HEAD"), method = "hasRidingInventory", cancellable = true)
public void hasRidingInventory(CallbackInfoReturnable<Boolean> info) {
assert client.player != null;
if (client.player.hasVehicle() && client.player.getVehicle() instanceof BoatEntity && ((BoatUtil) client.player.getVehicle()).getChestMode() != BoatUtil.ChestMode.NONE) {
if (client.player.hasVehicle() && client.player.getVehicle() instanceof BoatEntity && ((BoatUtil) client.player.getVehicle()).getChestMode() != BoatChestMode.NONE) {
info.setReturnValue(true);
}
}

View File

@ -1,5 +1,6 @@
package com.thebrokenrail.twine.mixin;
import com.thebrokenrail.twine.util.BoatChestMode;
import com.thebrokenrail.twine.util.BoatUtil;
import net.minecraft.entity.vehicle.BoatEntity;
import net.minecraft.network.packet.c2s.play.ClientCommandC2SPacket;
@ -18,7 +19,7 @@ public class MixinServerPlayNetworkHandler {
@Inject(at = @At("TAIL"), method = "onClientCommand")
public void onClientCommand(ClientCommandC2SPacket packet, CallbackInfo info) {
if (packet.getMode() == ClientCommandC2SPacket.Mode.OPEN_INVENTORY && player.hasVehicle() && player.getVehicle() instanceof BoatEntity && ((BoatUtil) player.getVehicle()).getChestMode() != BoatUtil.ChestMode.NONE) {
if (packet.getMode() == ClientCommandC2SPacket.Mode.OPEN_INVENTORY && player.hasVehicle() && player.getVehicle() instanceof BoatEntity && ((BoatUtil) player.getVehicle()).getChestMode() != BoatChestMode.NONE) {
((BoatUtil) player.getVehicle()).openInventory(player);
}
}

View File

@ -1,6 +1,7 @@
package com.thebrokenrail.twine.mixin;
import com.thebrokenrail.twine.item.BackpackItem;
import com.thebrokenrail.twine.util.BackpackInventory;
import net.minecraft.inventory.Inventory;
import net.minecraft.item.ItemStack;
import net.minecraft.screen.slot.Slot;
@ -19,7 +20,7 @@ public class MixinSlot {
@Inject(at = @At("HEAD"), method = "canInsert", cancellable = true)
public void canInsert(ItemStack stack, CallbackInfoReturnable<Boolean> info) {
if (inventory instanceof BackpackItem.ItemInventory) {
if (inventory instanceof BackpackInventory) {
if (stack.getItem() instanceof BackpackItem) {
info.setReturnValue(false);
return;

View File

@ -0,0 +1,18 @@
package com.thebrokenrail.twine.util;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.Hand;
public class BackpackInventory extends ItemInventory {
private final Hand hand;
public BackpackInventory(int size, PlayerEntity player, Hand hand) {
super(size, player.getStackInHand(hand));
this.hand = hand;
}
@Override
public boolean canPlayerUse(PlayerEntity player) {
return player.getStackInHand(hand) == getHolder();
}
}

View File

@ -1,7 +1,6 @@
package com.thebrokenrail.twine.util;
import com.thebrokenrail.twine.Twine;
import com.thebrokenrail.twine.item.BackpackItem;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.inventory.Inventory;
import net.minecraft.network.PacketByteBuf;
@ -15,7 +14,7 @@ public class BackpackScreenHandler extends GenericContainerScreenHandler {
}
public BackpackScreenHandler(int i, PlayerInventory playerInventory, boolean large, Hand hand) {
this(Twine.BACKPACK_SCREEN_TYPE, i, playerInventory, new BackpackItem.ItemInventory(9 * (large ? 6 : 3), playerInventory.player, hand));
this(Twine.BACKPACK_SCREEN_TYPE, i, playerInventory, new BackpackInventory(9 * (large ? 6 : 3), playerInventory.player, hand));
}
public BackpackScreenHandler(ScreenHandlerType<?> type, int syncId, PlayerInventory playerInventory, Inventory inventory) {

View File

@ -0,0 +1,143 @@
package com.thebrokenrail.twine.util;
import com.google.common.collect.Lists;
import com.thebrokenrail.twine.Twine;
import net.minecraft.block.Block;
import net.minecraft.block.Blocks;
import net.minecraft.block.EnderChestBlock;
import net.minecraft.block.ShulkerBoxBlock;
import net.minecraft.block.entity.ChestBlockEntity;
import net.minecraft.block.entity.ShulkerBoxBlockEntity;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.screen.GenericContainerScreenHandler;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.screen.ShulkerBoxScreenHandler;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.sound.SoundEvent;
import net.minecraft.sound.SoundEvents;
import net.minecraft.text.Text;
import net.minecraft.util.DyeColor;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
public class BoatChestMode {
public interface BoatScreenHandlerFactory {
ScreenHandler createMenu(int syncId, PlayerInventory inv, PlayerEntity player, BoatUtil boat);
}
private static final ArrayList<BoatChestMode> values = new ArrayList<>();
public static final BoatChestMode NONE = new BoatChestMode(Blocks.AIR, null, null, null);
static {
BoatScreenHandlerFactory chestScreenHandler = (i, playerInventory, playerEntity, boat) -> GenericContainerScreenHandler.createGeneric9x3(i, playerInventory, boat.getChestInventory());
ChestBlockEntity chest = new ChestBlockEntity();
Function<ItemStack, Text> chestScreenHandlerName = stack -> stack.hasCustomName() ? stack.getName() : chest.getName();
new BoatChestMode(Blocks.ENDER_CHEST, (i, playerInventory, playerEntity, boat) -> GenericContainerScreenHandler.createGeneric9x3(i, playerInventory, new EnderChestInventoryWrapper((Entity) boat, playerEntity.getEnderChestInventory())), stack -> EnderChestBlock.CONTAINER_NAME, player -> Twine.ENDER_CHEST_BOAT_CRITERION.trigger(player));
new BoatChestMode(Blocks.CHEST, true, false, 27, chestScreenHandler, chestScreenHandlerName, player -> Twine.CHEST_BOAT_CRITERION.trigger(player), SoundEvents.BLOCK_CHEST_OPEN, SoundEvents.BLOCK_CHEST_CLOSE);
new BoatChestMode(Blocks.TRAPPED_CHEST, true, false, 27, chestScreenHandler, chestScreenHandlerName, player -> Twine.CHEST_BOAT_CRITERION.trigger(player), SoundEvents.BLOCK_CHEST_OPEN, SoundEvents.BLOCK_CHEST_CLOSE);
ShulkerBoxBlockEntity shulker = new ShulkerBoxBlockEntity();
List<DyeColor> colors = Lists.asList(null, DyeColor.values());
for (DyeColor value : colors) {
new BoatChestMode(ShulkerBoxBlock.get(value), true, true, 27, (i, playerInventory, playerEntity, boat) -> new ShulkerBoxScreenHandler(i, playerInventory, boat.getChestInventory()), stack -> stack.hasCustomName() ? stack.getName() : shulker.getName(), player -> Twine.SHULKER_BOX_BOAT_CRITERION.trigger(player), SoundEvents.BLOCK_SHULKER_BOX_OPEN, SoundEvents.BLOCK_SHULKER_BOX_CLOSE);
}
}
private final Block block;
private final boolean hasItems;
private final boolean containsItems;
private final int size;
private final BoatScreenHandlerFactory screenHandlerFactory;
private final Function<ItemStack, Text> screenHandlerNameFactory;
private final Consumer<ServerPlayerEntity> onInteract;
private final int id;
private final SoundEvent openSound;
private final SoundEvent closeSound;
private BoatChestMode(Block block, boolean hasItems, boolean containsItems, int size, BoatScreenHandlerFactory screenHandlerFactory, Function<ItemStack, Text> screenHandlerNameFactory, Consumer<ServerPlayerEntity> onInteract, SoundEvent openSound, SoundEvent closeSound) {
this.screenHandlerFactory = screenHandlerFactory;
this.size = size;
this.screenHandlerNameFactory = screenHandlerNameFactory;
this.onInteract = onInteract;
this.openSound = openSound;
this.closeSound = closeSound;
this.id = values.size();
this.block = block;
this.hasItems = hasItems;
this.containsItems = containsItems;
values.add(this);
}
private BoatChestMode(Block block, BoatScreenHandlerFactory screenHandlerFactory, Function<ItemStack, Text> screenHandlerNameFactory, Consumer<ServerPlayerEntity> onInteract) {
this(block,false, false, 0, screenHandlerFactory, screenHandlerNameFactory, onInteract, null, null);
}
public int getSize() {
return size;
}
public BoatScreenHandlerFactory getScreenHandlerFactory() {
return screenHandlerFactory;
}
public Text getScreenHandlerName(ItemStack stack) {
return screenHandlerNameFactory.apply(stack);
}
public void interact(ServerPlayerEntity player) {
if (onInteract != null) {
onInteract.accept(player);
}
}
public SoundEvent getOpenSound() {
return openSound;
}
public SoundEvent getCloseSound() {
return closeSound;
}
public Block getBlock() {
return block;
}
public boolean hasItems() {
return hasItems;
}
public boolean containsItems() {
return containsItems;
}
public int getID() {
return id;
}
public static BoatChestMode valueOf(Block block) {
for (BoatChestMode mode : values) {
if (mode.getBlock() == block) {
return mode;
}
}
return NONE;
}
public static BoatChestMode valueOf(int index) {
BoatChestMode mode = values.get(index);
return mode != null ? mode : NONE;
}
}

View File

@ -2,29 +2,34 @@ package com.thebrokenrail.twine.util;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.inventory.SimpleInventory;
import net.minecraft.sound.SoundEvents;
import net.minecraft.item.ItemStack;
import net.minecraft.sound.SoundEvent;
public class BoatInventory extends SimpleInventory {
public class BoatInventory extends ItemInventory {
private final Entity entity;
public BoatInventory(Entity entity, int size) {
super(size);
private final SoundEvent openSound;
private final SoundEvent closeSound;
public BoatInventory(Entity entity, int size, ItemStack stack, SoundEvent openSound, SoundEvent closeSound) {
super(size, stack);
this.entity = entity;
this.openSound = openSound;
this.closeSound = closeSound;
}
@Override
public boolean canPlayerUse(PlayerEntity player) {
return BoatUtil.canReachEntity(player, entity) && entity.isAlive() && ((BoatUtil) entity).getChestMode() == BoatUtil.ChestMode.CHEST && super.canPlayerUse(player);
return BoatUtil.canReachEntity(player, entity) && entity.isAlive() && ((BoatUtil) entity).getChestMode().hasItems();
}
@Override
public void onOpen(PlayerEntity player) {
BoatUtil.playSound(entity, SoundEvents.BLOCK_CHEST_OPEN);
BoatUtil.playSound(entity, openSound);
}
@Override
public void onClose(PlayerEntity player) {
BoatUtil.playSound(entity, SoundEvents.BLOCK_CHEST_CLOSE);
BoatUtil.playSound(entity, closeSound);
}
}

View File

@ -1,22 +1,14 @@
package com.thebrokenrail.twine.util;
import net.minecraft.entity.Entity;
import net.minecraft.entity.data.DataTracker;
import net.minecraft.entity.data.TrackedData;
import net.minecraft.entity.data.TrackedDataHandlerRegistry;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.vehicle.BoatEntity;
import net.minecraft.inventory.Inventory;
import net.minecraft.item.Item;
import net.minecraft.item.Items;
import net.minecraft.sound.SoundCategory;
import net.minecraft.sound.SoundEvent;
import net.minecraft.util.math.Vec3d;
public interface BoatUtil {
TrackedData<String> HAS_CHEST = DataTracker.registerData(BoatEntity.class, TrackedDataHandlerRegistry.STRING);
ChestMode getChestMode();
BoatChestMode getChestMode();
Inventory getChestInventory();
@ -26,38 +18,6 @@ public interface BoatUtil {
return player.squaredDistanceTo(entity.getPos().getX(), entity.getPos().getY(), entity.getPos().getZ()) <= 64d;
}
enum ChestMode {
ENDER_CHEST(Items.ENDER_CHEST, false),
CHEST(Items.CHEST, true),
TRAPPED_CHEST(Items.TRAPPED_CHEST, true),
NONE(Items.AIR, false);
private final Item item;
private final boolean hasItems;
ChestMode(Item item, boolean hasItems) {
this.item = item;
this.hasItems = hasItems;
}
public Item getItem() {
return item;
}
public boolean hasItems() {
return hasItems;
}
public static ChestMode valueOF(Item item) {
for (ChestMode mode : values()) {
if (mode.getItem() == item) {
return mode;
}
}
return NONE;
}
}
static void playSound(Entity vehicle, SoundEvent sound) {
Vec3d pos = vehicle.getPos();
vehicle.getEntityWorld().playSound(null, pos.getX(), pos.getY(), pos.getZ(), sound, SoundCategory.BLOCKS, 0.5F, vehicle.getEntityWorld().random.nextFloat() * 0.1F + 0.9F);

View File

@ -1,5 +1,6 @@
package com.thebrokenrail.twine.util;
import net.minecraft.block.Blocks;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.inventory.EnderChestInventory;
@ -53,7 +54,7 @@ public class EnderChestInventoryWrapper implements Inventory {
@Override
public boolean canPlayerUse(PlayerEntity player) {
return BoatUtil.canReachEntity(player, entity) && entity.isAlive() && ((BoatUtil) entity).getChestMode() == BoatUtil.ChestMode.ENDER_CHEST;
return BoatUtil.canReachEntity(player, entity) && entity.isAlive() && ((BoatUtil) entity).getChestMode().getBlock() == Blocks.ENDER_CHEST;
}
@Override

View File

@ -0,0 +1,84 @@
package com.thebrokenrail.twine.util;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.inventory.Inventories;
import net.minecraft.inventory.Inventory;
import net.minecraft.item.ItemStack;
import net.minecraft.util.collection.DefaultedList;
public abstract class ItemInventory implements Inventory {
private final int size;
private final ItemStack stack;
private final DefaultedList<ItemStack> inv;
public ItemInventory(int size, ItemStack stack) {
this.size = size;
this.stack = stack;
inv = load();
}
protected ItemStack getHolder() {
return stack;
}
private DefaultedList<ItemStack> load() {
DefaultedList<ItemStack> list = DefaultedList.ofSize(size, ItemStack.EMPTY);
Inventories.fromTag(stack.getOrCreateTag().getCompound("BlockEntityTag"), list);
return list;
}
private void save(DefaultedList<ItemStack> list) {
Inventories.toTag(stack.getOrCreateTag().getCompound("BlockEntityTag"), list);
}
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
for (ItemStack item : inv) {
if (item != ItemStack.EMPTY) {
return false;
}
}
return true;
}
@Override
public ItemStack getStack(int slot) {
return inv.get(slot);
}
@Override
public ItemStack removeStack(int slot, int amount) {
return inv.get(slot).split(amount);
}
@Override
public ItemStack removeStack(int slot) {
ItemStack item = inv.get(slot);
return item.split(item.getCount());
}
@Override
public void setStack(int slot, ItemStack stack) {
inv.set(slot, stack);
}
@Override
public void markDirty() {
save(inv);
}
@Override
public abstract boolean canPlayerUse(PlayerEntity player);
@Override
public void clear() {
for (int i = 0; i < size; i++) {
inv.set(i, ItemStack.EMPTY);
}
}
}

View File

@ -17,6 +17,9 @@
"advancements.twine.ender_chest_boat.title": "Why not?",
"advancements.twine.ender_chest_boat.description": "Put an Ender Chest in a Boat",
"advancements.twine.shulker_box_boat.title": "Why, just why?",
"advancements.twine.shulker_box_boat.description": "Put a Shulker Box in a Boat",
"advancements.twine.small_backpack.title": "On the go!",
"advancements.twine.small_backpack.description": "Construct a Small Backpack with a Crafting Table",

View File

@ -0,0 +1,19 @@
{
"display": {
"icon": {
"item": "minecraft:oak_boat"
},
"title": {
"translate": "advancements.twine.shulker_box_boat.title"
},
"description": {
"translate": "advancements.twine.shulker_box_boat.description"
}
},
"parent": "twine:chest_boat",
"criteria": {
"chest_boat": {
"trigger": "twine:shulker_box_boat"
}
}
}