Add the digitizer block #56

Merged
BlakeRain merged 2 commits from BlakeRain/utamacraft:main into main 2024-01-29 14:54:40 +00:00
28 changed files with 1396 additions and 73 deletions

1
assets/digitizer.bbmodel Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 711 B

View File

@ -3,9 +3,11 @@ package net.banutama.utamacraft;
import dan200.computercraft.api.ForgeComputerCraftAPI;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import net.banutama.utamacraft.block.entity.AwarenessBlockEntity;
import net.banutama.utamacraft.block.entity.DigitizerBlockEntity;
import net.banutama.utamacraft.block.entity.InsolatorBlockEntity;
import net.banutama.utamacraft.integrations.computercraft.PeripheralProvider;
import net.banutama.utamacraft.integrations.computercraft.peripheral.AwarenessBlockPeripheral;
import net.banutama.utamacraft.integrations.computercraft.peripheral.DigitizerPeripheral;
import net.banutama.utamacraft.integrations.computercraft.peripheral.InsolatorPeripheral;
import net.banutama.utamacraft.integrations.computercraft.turtles.TurtlePlayerUpgrade;
import net.minecraft.resources.ResourceLocation;
@ -17,7 +19,6 @@ import net.minecraftforge.registries.RegistryObject;
* CC:Tweaked registration
*/
public class CCRegistration {
public static final DeferredRegister<TurtleUpgradeSerialiser<?>> TURTLE_SERIALIZERS = DeferredRegister
.create(TurtleUpgradeSerialiser.REGISTRY_ID, Utamacraft.MOD_ID);
@ -33,6 +34,7 @@ public class CCRegistration {
peripheralProvider.registerBlockPeripheral(InsolatorPeripheral::new, InsolatorBlockEntity.class::isInstance);
peripheralProvider.registerBlockPeripheral(AwarenessBlockPeripheral::new,
AwarenessBlockEntity.class::isInstance);
peripheralProvider.registerBlockPeripheral(DigitizerPeripheral::new, DigitizerBlockEntity.class::isInstance);
ForgeComputerCraftAPI.registerPeripheralProvider(peripheralProvider);
}

View File

@ -10,6 +10,7 @@ import net.banutama.utamacraft.item.ModItems;
import net.banutama.utamacraft.networking.ModMessages;
import net.banutama.utamacraft.recipe.ModRecipes;
import net.banutama.utamacraft.screen.DigitizerScreen;
import net.banutama.utamacraft.screen.InsolatorScreen;
import net.banutama.utamacraft.screen.ModMenuTypes;
import net.banutama.utamacraft.sound.ModSounds;
@ -69,6 +70,7 @@ public class Utamacraft {
@SubscribeEvent
public static void onClientSetup(FMLClientSetupEvent event) {
MenuScreens.register(ModMenuTypes.INSOLATOR_MENU.get(), InsolatorScreen::new);
MenuScreens.register(ModMenuTypes.DIGITIZER_MENU.get(), DigitizerScreen::new);
CuriosRenderers.register();
}

View File

@ -0,0 +1,140 @@
package net.banutama.utamacraft.block.custom;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import net.banutama.utamacraft.block.entity.DigitizerBlockEntity;
import net.banutama.utamacraft.block.entity.ModBlockEntities;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BaseEntityBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.RenderShape;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.DirectionProperty;
import net.minecraft.world.level.material.Material;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraftforge.network.NetworkHooks;
public class DigitizerBlock extends BaseEntityBlock {
public static final DirectionProperty FACING = BlockStateProperties.HORIZONTAL_FACING;
public static final VoxelShape SHAPE = Block.box(0, 0, 0, 16, 16, 16);
public DigitizerBlock() {
super(getProperties());
registerDefaultState(this.getStateDefinition().any().setValue(FACING, Direction.NORTH));
}
private static Block.Properties getProperties() {
return Block.Properties.of(Material.STONE).strength(3.0f).requiresCorrectToolForDrops().noOcclusion();
}
@Override
public @NotNull VoxelShape getShape(@NotNull BlockState state, @NotNull BlockGetter getter, @NotNull BlockPos pos,
@NotNull CollisionContext context) {
return SHAPE;
}
@Nullable
@Override
public BlockState getStateForPlacement(BlockPlaceContext context) {
return this.defaultBlockState().setValue(FACING, context.getHorizontalDirection().getOpposite());
}
@Override
public @NotNull BlockState rotate(BlockState state, Rotation rotation) {
return state.setValue(FACING, rotation.rotate(state.getValue(FACING)));
}
@SuppressWarnings("deprecation")
@Override
public @NotNull BlockState mirror(BlockState state, Mirror mirror) {
return state.rotate(mirror.getRotation(state.getValue(FACING)));
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(FACING);
}
@Override
public @NotNull RenderShape getRenderShape(@NotNull BlockState state) {
return RenderShape.MODEL;
}
@Override
public boolean hasAnalogOutputSignal(BlockState pState) {
return true;
}
@Override
public int getAnalogOutputSignal(BlockState state, Level level, BlockPos pos) {
if (!(level.getBlockEntity(pos) instanceof DigitizerBlockEntity entity)) {
return 0;
}
var stack = entity.getInventory().getStackInSlot(0);
return (int) (1 + (float) stack.getCount() / stack.getMaxStackSize() * 14);
}
@SuppressWarnings("deprecation")
@Override
public void onRemove(BlockState state, @NotNull Level level, @NotNull BlockPos pos, BlockState newState,
boolean isMoving) {
if (!state.is(newState.getBlock())) {
if (level.getBlockEntity(pos) instanceof DigitizerBlockEntity digitizer) {
if (level instanceof ServerLevel) {
digitizer.getInventoryOptional().ifPresent(handler -> {
for (int slot = 0; slot < handler.getSlots(); ++slot) {
Block.popResource(level, pos, handler.getStackInSlot(slot));
}
});
}
level.updateNeighbourForOutputSignal(pos, this);
}
}
super.onRemove(state, level, pos, newState, isMoving);
}
@Override
public @NotNull InteractionResult use(@NotNull BlockState state, @NotNull Level level, @NotNull BlockPos pos,
@NotNull Player player, @NotNull InteractionHand hand, @NotNull BlockHitResult hit) {
BlockEntity blockEntity = level.getBlockEntity(pos);
if (!(blockEntity instanceof DigitizerBlockEntity digitizerEntity)) {
return InteractionResult.PASS;
}
if (level.isClientSide()) {
return InteractionResult.SUCCESS;
}
if (player instanceof ServerPlayer serverPlayer) {
NetworkHooks.openScreen(serverPlayer, digitizerEntity, pos);
}
return InteractionResult.CONSUME;
}
@Nullable
@Override
public BlockEntity newBlockEntity(@NotNull BlockPos pos, @NotNull BlockState state) {
return ModBlockEntities.DIGITIZER.get().create(pos, state);
}
}

View File

@ -40,6 +40,7 @@ public class ModBlocks {
.requiresCorrectToolForDrops()));
public static final RegistryObject<Block> INSOLATOR = registerBlock("insolator", InsolatorBlock::new);
public static final RegistryObject<Block> AWARENESS_BLOCK = registerBlock("awareness_block", AwarenessBlock::new);
public static final RegistryObject<Block> DIGITIZER = registerBlock("digitizer", DigitizerBlock::new);
private static <T extends Block> RegistryObject<T> registerBlock(String name, Supplier<T> block) {
RegistryObject<T> registered_block = BLOCKS.register(name, block);

View File

@ -0,0 +1,144 @@
package net.banutama.utamacraft.block.entity;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import net.banutama.utamacraft.screen.DigitizerMenu;
import net.banutama.utamacraft.util.ModEnergyStorage;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientGamePacketListener;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.ForgeCapabilities;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.items.ItemStackHandler;
public class DigitizerBlockEntity extends BlockEntity implements MenuProvider {
public static final int MATERIALZE_ENERGY_REQUIRED = 1024;
public static final int REFRESH_ENERGY_REQUIRED = 64;
public static final int ENERGY_MAX = MATERIALZE_ENERGY_REQUIRED * 64;
public static final int ENERGY_DRAW = ENERGY_MAX / (20 * 10);
private final ItemStackHandler inventory = new ItemStackHandler(1) {
@Override
protected void onContentsChanged(int slot) {
super.onContentsChanged(slot);
DigitizerBlockEntity.this.sendUpdate();
}
@Override
public boolean isItemValid(int slot, @NotNull ItemStack stack) {
return switch (slot) {
case 0 -> true;
default -> super.isItemValid(slot, stack);
};
}
};
private final ModEnergyStorage energy = new ModEnergyStorage(ENERGY_MAX, ENERGY_DRAW) {
@Override
public void onEnergyChanged() {
setChanged();
DigitizerBlockEntity.this.sendUpdate();
}
};
private final LazyOptional<ItemStackHandler> inventoryOptional = LazyOptional.of(() -> inventory);
private final LazyOptional<ModEnergyStorage> energyOptional = LazyOptional.of(() -> energy);
public DigitizerBlockEntity(BlockPos pos, BlockState state) {
super(ModBlockEntities.DIGITIZER.get(), pos, state);
}
@Override
public @NotNull Component getDisplayName() {
return Component.translatable("block_entity.utamacraft.digitizer");
}
@Override
@Nullable
public AbstractContainerMenu createMenu(int containerId, @NotNull Inventory playerInventory,
@NotNull Player player) {
return new DigitizerMenu(containerId, playerInventory, this);
}
@Nullable
@Override
public @NotNull <T> LazyOptional<T> getCapability(@NotNull Capability<T> cap, @Nullable Direction side) {
if (cap == ForgeCapabilities.ITEM_HANDLER) {
return inventoryOptional.cast();
}
if (cap == ForgeCapabilities.ENERGY) {
return energyOptional.cast();
}
return super.getCapability(cap, side);
}
@Override
public void invalidateCaps() {
super.invalidateCaps();
inventoryOptional.invalidate();
energyOptional.invalidate();
}
@Override
protected void saveAdditional(@NotNull CompoundTag nbt) {
super.saveAdditional(nbt);
nbt.put("inventory", inventory.serializeNBT());
nbt.put("energy", energy.serializeNBT());
}
@Override
public void load(@NotNull CompoundTag nbt) {
super.load(nbt);
inventory.deserializeNBT(nbt.getCompound("inventory"));
energy.deserializeNBT(nbt.get("energy"));
}
@Override
public @NotNull CompoundTag getUpdateTag() {
CompoundTag nbt = super.getUpdateTag();
saveAdditional(nbt);
return nbt;
}
@Nullable
@Override
public Packet<ClientGamePacketListener> getUpdatePacket() {
return ClientboundBlockEntityDataPacket.create(this);
}
protected void sendUpdate() {
setChanged();
if (this.level != null) {
this.level.sendBlockUpdated(this.worldPosition, getBlockState(), getBlockState(), Block.UPDATE_ALL);
}
}
public ItemStackHandler getInventory() {
return inventory;
}
public ModEnergyStorage getEnergy() {
return energy;
}
public LazyOptional<ItemStackHandler> getInventoryOptional() {
return inventoryOptional;
}
}

View File

@ -20,6 +20,10 @@ public class ModBlockEntities {
"awareness_block",
() -> BlockEntityType.Builder.of(AwarenessBlockEntity::new, ModBlocks.AWARENESS_BLOCK.get()).build(null));
public static final RegistryObject<BlockEntityType<DigitizerBlockEntity>> DIGITIZER = BLOCK_ENTITIES.register(
"digitizer_block",
() -> BlockEntityType.Builder.of(DigitizerBlockEntity::new, ModBlocks.DIGITIZER.get()).build(null));
public static void register(IEventBus bus) {
BLOCK_ENTITIES.register(bus);
}

View File

@ -0,0 +1,323 @@
package net.banutama.utamacraft.integrations.computercraft.peripheral;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.jetbrains.annotations.NotNull;
import dan200.computercraft.api.lua.IArguments;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.lua.MethodResult;
import net.banutama.utamacraft.block.entity.DigitizerBlockEntity;
import net.banutama.utamacraft.integrations.computercraft.peripheral.digitizer.DigitizedCache;
import net.banutama.utamacraft.integrations.computercraft.peripheral.digitizer.DigitizedItem;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraftforge.items.ItemHandlerHelper;
import net.minecraftforge.registries.ForgeRegistries;
public class DigitizerPeripheral extends BasePeripheral {
public static final String PERIPHERAL_TYPE = "digitizier";
protected DigitizerPeripheral(BasePeripheralOwner owner) {
super(PERIPHERAL_TYPE, owner);
}
public DigitizerPeripheral(BlockEntity blockEntity) {
this(new BlockEntityPeripheralOwner(blockEntity));
}
private DigitizerBlockEntity getDigitizer() throws LuaException {
if (!(owner instanceof BlockEntityPeripheralOwner blockOwner)) {
throw new LuaException("Owner of this DigitizerPeripheral is not a BlockEntityPeripheralOwner");
}
if (!(blockOwner.getBlockEntity() instanceof DigitizerBlockEntity digitizer)) {
throw new LuaException("Owner of this DigitizerPeripheral is not a DigitizerBlockEntity");
}
return digitizer;
}
@LuaFunction(mainThread = true)
public final int getEnergy() throws LuaException {
return getDigitizer().getEnergy().getEnergyStored();
}
@LuaFunction(mainThread = true)
public final int getEnergyCapacity() throws LuaException {
return getDigitizer().getEnergy().getMaxEnergyStored();
}
@LuaFunction(mainThread = true)
public final int size() throws LuaException {
return getDigitizer().getInventory().getSlots();
}
@LuaFunction(mainThread = true)
public final int getDigitizationCost() {
return DigitizerBlockEntity.MATERIALZE_ENERGY_REQUIRED;
}
@LuaFunction(mainThread = true)
public final int getRefreshCost() {
return DigitizerBlockEntity.REFRESH_ENERGY_REQUIRED;
}
@LuaFunction(mainThread = true)
public final int getItemLimit(int slot) throws LuaException {
var inventory = getDigitizer().getInventory();
if (slot < 1 || slot > inventory.getSlots()) {
throw new LuaException(
String.format("Slot %d is out of range (%d slots available)", slot, inventory.getSlots()));
}
return inventory.getSlotLimit(slot - 1);
}
@LuaFunction(mainThread = true)
public final @NotNull MethodResult getItemDetail(int slot) throws LuaException {
var inventory = getDigitizer().getInventory();
if (slot < 1 || slot > inventory.getSlots()) {
throw new LuaException(
String.format("Slot %d is out of range (%d slots available)", slot, inventory.getSlots()));
}
var stack = inventory.getStackInSlot(slot - 1);
if (stack.getCount() == 0) {
return MethodResult.of();
}
ResourceLocation itemName = ForgeRegistries.ITEMS.getKey(stack.getItem());
var dict = new HashMap<String, Object>();
dict.put("displayName", stack.getDisplayName().getString());
dict.put("name", itemName == null ? "unknown" : itemName.toString());
dict.put("count", stack.getCount());
dict.put("maxCount", stack.getMaxStackSize());
List<Map<String, Object>> items = new ArrayList<>();
items.add(dict);
return MethodResult.of(items);
}
@LuaFunction(mainThread = true)
public final @NotNull MethodResult digitize(@NotNull IArguments arguments) throws LuaException {
var digitizer = getDigitizer();
var gameTime = digitizer.getLevel().getGameTime();
var inventory = digitizer.getInventory();
var requested = arguments.optInt(0, inventory.getStackInSlot(0).getCount());
var simulate = arguments.optBoolean(1, false);
// Figure out how many items we're going to digitize (based on the requested
// number of items and the number of items available in the inventory slot of
// the digitizer block). We can use this to compute the cost of the
// digitization.
var available = inventory.getStackInSlot(0).getCount();
var amount = Math.min(requested, available);
var cost = DigitizerBlockEntity.MATERIALZE_ENERGY_REQUIRED * amount;
DigitizedItem digitized = null;
if (!simulate && amount > 0) {
// As we're not simulating, and there are actually some items that we can
// digitize, we can deduct the required energy from the digitizer blocks' energy
// store.
var energy = digitizer.getEnergy();
if (!energy.subtractEnergy(cost)) {
return MethodResult.of(null,
String.format("Not enough energy to digitize %d items (require %d, available %d)",
amount, cost, energy.getEnergyStored()));
}
// Get the extracted item stack from the digitizer and store it as a new
// digitized item in the cache. We then keep the item for the result structure.
var extracted = digitizer.getInventory().extractItem(0, amount, false);
digitized = new DigitizedItem(extracted, gameTime);
DigitizedCache.getInstance(digitizer.getLevel()).put(digitized);
}
Map<String, Object> result = new HashMap<>();
result.put("simulation", simulate);
result.put("requested", requested);
result.put("available", available);
result.put("count", amount);
result.put("cost", cost);
if (digitized != null) {
result.put("item", digitized.describeItem(gameTime));
}
return MethodResult.of(result);
}
@LuaFunction(mainThread = true)
public final @NotNull MethodResult materialize(@NotNull IArguments arguments) throws LuaException {
var id = UUID.fromString(arguments.getString(0));
var simulate = arguments.optBoolean(2, false);
var digitizer = getDigitizer();
var inventory = digitizer.getInventory();
var level = digitizer.getLevel();
var cache = DigitizedCache.getInstance(level);
var gameTime = level.getGameTime();
// See if we can find the digitised item, and make sure that it has not expired.
var digitizedItem = cache.take(id);
if (digitizedItem == null || digitizedItem.isExpired(gameTime)) {
return MethodResult.of(null, "No digitized item with ID " + id.toString());
}
// This is the number of items available in the digitized item stack.
var available = digitizedItem.itemStack.getCount();
// The requested number of items to materialize defaults to the number
// available.
var requested = arguments.optInt(1, available);
requested = Math.min(requested, available);
// Build an ItemStack that contains the number of items that we request to
// materialize. We're just going to use a copy for now. When we actually do the
// materialization we'll adjust the digitized item stack.
var remaining = available - requested;
var materializing = digitizedItem.itemStack.copy();
materializing.setCount(requested);
// Simulate the insertion of the `materializing` stack into the inventory slot.
// This gives us back an ItemStack that describes the items that were not
// materialized.
var unmaterialized = inventory.insertItem(0, materializing, true);
// Expand the number of items in the unmaterialized stack by those remaining in
// the digitized item stack.
unmaterialized.grow(remaining);
// The actual number of items that would be materialized is the requested amount
// minus the number of items that were unmaterialized.
var actual = requested - unmaterialized.getCount();
// The materialized cost is the number of items that were actually materialized
// times the cost per item.
var materializeCost = DigitizerBlockEntity.MATERIALZE_ENERGY_REQUIRED * actual;
// The refresh cost is the number of items that were not materialized times the
// refresh cost per item.
var refreshCost = DigitizerBlockEntity.REFRESH_ENERGY_REQUIRED * unmaterialized.getCount();
// The final cost is the sum of the materialized cost and the refresh cost.
var cost = materializeCost + refreshCost;
if (!simulate) {
// The user doesn't want to simulate the materialization, so actually deduct the
// energy needed from the digitizer blocks' energy store and then insert the
// items into the digitizer's inventory. Any remaining items remain digitized.
var energy = digitizer.getEnergy();
if (!energy.subtractEnergy(cost)) {
return MethodResult.of(null,
String.format("Not enough energy to materialize %d items (require %d, available %d)",
requested, cost, energy.getEnergyStored()));
}
// Now we do the actual materialization: insert the items in the `materializing`
// stack into the inventory slot. The result is the unmaterialized items.
unmaterialized = inventory.insertItem(0, materializing, false);
// Expand the number of items in the unmaterialized stack by those remaining in
// the digitized item stack.
unmaterialized.grow(remaining);
if (unmaterialized.getCount() > 0) {
// There's still some remainder, so put it back in the cache. Before we do that,
// we need to update the count to reflect the remainder and refresh the expiry
// time.
digitizedItem.itemStack = unmaterialized;
digitizedItem.refresh(gameTime);
cache.put(digitizedItem);
} else {
// The digitized item stack was depleted: no items remain unmaterialized.
digitizedItem.itemStack.setCount(0);
}
} else {
// We're simulating, so put the digitized item back in the cache.
cache.put(digitizedItem);
}
// Build up information for the result.
var result = new HashMap<String, Object>();
result.put("simulation", simulate);
result.put("requested", requested);
result.put("available", available);
result.put("materialized", actual);
result.put("remainder", unmaterialized.getCount());
result.put("cost", cost);
result.put("materializeCost", materializeCost);
result.put("refreshCost", refreshCost);
result.put("item", digitizedItem.describeItem(gameTime));
return MethodResult.of(result);
}
@LuaFunction(mainThread = true)
public final @NotNull MethodResult query(@NotNull IArguments arguments) throws LuaException {
var id = UUID.fromString(arguments.getString(0));
var level = getDigitizer().getLevel();
var cache = DigitizedCache.getInstance(level);
var gameTime = level.getGameTime();
var digitizedItem = cache.get(id);
if (digitizedItem != null && !digitizedItem.isExpired(gameTime)) {
return MethodResult.of(digitizedItem.describeItem(gameTime));
} else {
return MethodResult.of(null, "No digitized item with ID " + id.toString());
}
}
@LuaFunction(mainThread = true)
public final @NotNull MethodResult refresh(@NotNull IArguments arguments) throws LuaException {
var id = UUID.fromString(arguments.getString(0));
var simulate = arguments.optBoolean(1, false);
var level = getDigitizer().getLevel();
var cache = DigitizedCache.getInstance(level);
var gameTime = level.getGameTime();
var digitizedItem = cache.get(id);
if (digitizedItem != null && !digitizedItem.isExpired(gameTime)) {
var cost = DigitizerBlockEntity.REFRESH_ENERGY_REQUIRED * digitizedItem.itemStack.getCount();
if (!simulate) {
var energy = getDigitizer().getEnergy();
if (!energy.subtractEnergy(cost)) {
return MethodResult.of(null,
String.format("Not enough energy to refresh %d items (require %d, available %d)",
digitizedItem.itemStack.getCount(), cost, energy.getEnergyStored()));
}
digitizedItem.refresh(gameTime);
}
var result = new HashMap<String, Object>();
result.put("simulation", simulate);
result.put("cost", cost);
result.put("item", digitizedItem.describeItem(gameTime));
return MethodResult.of(result);
} else {
return MethodResult.of(null, "No digitized item with ID " + id.toString());
}
}
@LuaFunction(mainThread = true)
public final @NotNull MethodResult list() throws LuaException {
var level = getDigitizer().getLevel();
var cache = DigitizedCache.getInstance(level);
var gameTime = level.getGameTime();
var result = new ArrayList<Map<String, Object>>();
cache.forEach(item -> {
result.add(item.describeItem(gameTime));
});
return MethodResult.of(result);
}
}

View File

@ -0,0 +1,117 @@
package net.banutama.utamacraft.integrations.computercraft.peripheral.digitizer;
import java.util.HashMap;
import java.util.UUID;
import java.util.function.Consumer;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import com.mojang.logging.LogUtils;
import net.banutama.utamacraft.Utamacraft;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.saveddata.SavedData;
public class DigitizedCache extends SavedData {
private final HashMap<UUID, DigitizedItem> cache = new HashMap<>();
private static DigitizedCache instance;
private static final Logger LOGGER = LogUtils.getLogger();
@NotNull
public static DigitizedCache getInstance(Level level) {
if (!(level instanceof ServerLevel serverLevel)) {
throw new RuntimeException("Cannot get DigitizedCache for non-server level");
}
if (instance != null) {
return instance;
}
var storage = serverLevel.getServer().overworld().getDataStorage();
instance = storage.computeIfAbsent(DigitizedCache::load, DigitizedCache::create,
String.format("%s_DigitizedCache", Utamacraft.MOD_ID));
instance.collect(serverLevel);
return instance;
}
@NotNull
public static DigitizedCache create() {
return new DigitizedCache();
}
public void put(@NotNull DigitizedItem item) {
cache.put(item.id, item);
setDirty();
}
public DigitizedItem get(UUID id) {
return cache.get(id);
}
public DigitizedItem take(UUID id) {
var item = cache.remove(id);
if (item != null) {
setDirty();
}
return item;
}
public void forEach(Consumer<DigitizedItem> consumer) {
cache.values().forEach(consumer::accept);
}
@NotNull
public static DigitizedCache load(CompoundTag tag) {
DigitizedCache cache = new DigitizedCache();
if (tag.contains("items") && tag.get("items") instanceof ListTag) {
((ListTag) tag.get("items")).forEach(item -> {
var digitizedItem = new DigitizedItem((CompoundTag) item);
cache.cache.put(digitizedItem.id, digitizedItem);
});
}
return cache;
}
@Override
@NotNull
public CompoundTag save(CompoundTag tag) {
ListTag items = new ListTag();
cache.values().forEach(digitizedItem -> {
CompoundTag item = new CompoundTag();
digitizedItem.serialize(item);
items.add(item);
});
tag.put("items", items);
return tag;
}
public void collect(Level level) {
var gameTime = level.getGameTime();
var count = cache.size();
var iterator = cache.entrySet().iterator();
iterator.forEachRemaining(item -> {
if (item.getValue().isExpired(gameTime)) {
iterator.remove();
}
});
var remaining = cache.size();
if (count != remaining) {
LOGGER.info(
String.format("Collected %d expired digitized items, %d remaining", count - remaining, remaining));
}
setDirty();
}
}

View File

@ -0,0 +1,77 @@
package net.banutama.utamacraft.integrations.computercraft.peripheral.digitizer;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.registries.ForgeRegistries;
public class DigitizedItem {
// Lifetime of a digitized item (in ticks): 20 TPS, 60 minutes, 20
// minutes-per-day, 5 days
public final static long LIFETIME = 20 * 60 * 20 * 5;
public UUID id;
public ItemStack itemStack;
public long createdAt;
public long expiresAt;
public DigitizedItem(ItemStack itemStack, long gameTime) {
this.id = UUID.randomUUID();
this.itemStack = itemStack;
this.createdAt = gameTime;
this.expiresAt = gameTime + LIFETIME;
}
public DigitizedItem(CompoundTag tag) {
id = tag.getUUID("id");
itemStack = ItemStack.of((CompoundTag) tag.get("itemStack"));
createdAt = tag.getLong("createdAt");
expiresAt = tag.getLong("expiresAt");
}
public void serialize(CompoundTag tag) {
tag.putUUID("id", id);
tag.put("itemStack", itemStack.serializeNBT());
tag.putLong("createdAt", createdAt);
tag.putLong("expiresAt", expiresAt);
}
public long age(long gameTime) {
return gameTime - createdAt;
}
public long remaining(long gameTime) {
return Math.max(0, expiresAt - gameTime);
}
public boolean isExpired(long gameTime) {
return expiresAt <= gameTime;
}
public void refresh(long gameTime) {
expiresAt = gameTime + LIFETIME;
}
public void describeItem(Map<String, Object> map, long gameTime) {
var itemName = ForgeRegistries.ITEMS.getKey(itemStack.getItem());
map.put("id", id.toString());
map.put("name", itemName == null ? "unknown" : itemName.toString());
map.put("count", itemStack.getCount());
map.put("maxCount", itemStack.getMaxStackSize());
map.put("createdAt", createdAt);
map.put("expiresAt", expiresAt);
map.put("age", age(gameTime));
map.put("isExpired", isExpired(gameTime));
map.put("remainingTime", remaining(gameTime));
}
public Map<String, Object> describeItem(long gameTime) {
var map = new HashMap<String, Object>();
describeItem(map, gameTime);
return map;
}
}

View File

@ -1,10 +1,26 @@
package net.banutama.utamacraft.item;
import net.minecraft.core.NonNullList;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.NotNull;
import dan200.computercraft.shared.Registry;
public abstract class BaseItem extends Item {
public BaseItem(@NotNull Properties properties) {
super(properties.tab(ModCreativeModeTab.TAB));
}
public static void createTurtleStacks(@NotNull NonNullList<ItemStack> stack, @NotNull ResourceLocation peripheral) {
ItemStack turtleStack = new ItemStack(Registry.ModItems.TURTLE_NORMAL.get());
turtleStack.getOrCreateTag().putString("RightUpgrade", peripheral.toString());
stack.add(turtleStack);
ItemStack advancedTurtleStack = new ItemStack(Registry.ModItems.TURTLE_ADVANCED.get());
advancedTurtleStack.getOrCreateTag().putString("RightUpgrade", peripheral.toString());
stack.add(advancedTurtleStack);
}
}

View File

@ -1,6 +1,5 @@
package net.banutama.utamacraft.item;
import dan200.computercraft.shared.Registry;
import net.banutama.utamacraft.CCRegistration;
import net.minecraft.core.NonNullList;
import net.minecraft.world.item.CreativeModeTab;
@ -18,13 +17,7 @@ public class PlayerPeripheralItem extends BaseItem {
super.fillItemCategory(group, items);
if (allowedIn(group)) {
ItemStack turtle_stack = new ItemStack(Registry.ModItems.TURTLE_NORMAL.get());
turtle_stack.getOrCreateTag().putString("RightUpgrade", CCRegistration.ID.PLAYER_TURTLE.toString());
items.add(turtle_stack);
ItemStack advanced_turtle_stack = new ItemStack(Registry.ModItems.TURTLE_ADVANCED.get());
advanced_turtle_stack.getOrCreateTag().putString("RightUpgrade", CCRegistration.ID.PLAYER_TURTLE.toString());
items.add(advanced_turtle_stack);
createTurtleStacks(items, CCRegistration.ID.PLAYER_TURTLE);
}
}
}

View File

@ -0,0 +1,78 @@
package net.banutama.utamacraft.screen;
import org.jetbrains.annotations.NotNull;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
public abstract class BaseAbstractContainerMenu extends AbstractContainerMenu {
private static final int HOTBAR_SLOT_COUNT = 9;
private static final int PLAYER_INVENTORY_ROW_COUNT = 3;
private static final int PLAYER_INVENTORY_COLUMN_COUNT = 9;
private static final int PLAYER_INVENTORY_SLOT_COUNT = PLAYER_INVENTORY_ROW_COUNT * PLAYER_INVENTORY_COLUMN_COUNT;
private static final int VANILLA_SLOT_COUNT = HOTBAR_SLOT_COUNT + PLAYER_INVENTORY_SLOT_COUNT;
private static final int TILE_ENTITY_START_SLOT = VANILLA_SLOT_COUNT;
protected BaseAbstractContainerMenu(MenuType<?> type, int id) {
super(type, id);
}
protected abstract int getTileEntitySlotCount();
@Override
public @NotNull ItemStack quickMoveStack(@NotNull Player player, int index) {
Slot source = slots.get(index);
if (!source.hasItem()) {
return ItemStack.EMPTY;
}
ItemStack sourceStack = source.getItem();
ItemStack sourceCopy = sourceStack.copy();
if (index < VANILLA_SLOT_COUNT) {
// This is a vanilla container slot, so merge the stack into the tile inventory.
if (!moveItemStackTo(sourceStack, TILE_ENTITY_START_SLOT, TILE_ENTITY_START_SLOT + getTileEntitySlotCount(),
false)) {
return ItemStack.EMPTY;
}
} else if (index < TILE_ENTITY_START_SLOT + getTileEntitySlotCount()) {
// This is a tile-entity slot, so merge the stack into the players inventory.
if (!moveItemStackTo(sourceStack, 0, VANILLA_SLOT_COUNT, false)) {
return ItemStack.EMPTY;
}
} else {
return ItemStack.EMPTY;
}
// If the stack size is zero, the entire stack was moved, so set the slot
// contents to null.
if (sourceStack.getCount() == 0) {
source.set(ItemStack.EMPTY);
} else {
source.setChanged();
}
source.onTake(player, sourceStack);
return sourceCopy;
}
protected void addPlayerInventory(Inventory inventory) {
for (int row = 0; row < PLAYER_INVENTORY_ROW_COUNT; ++row) {
for (int col = 0; col < PLAYER_INVENTORY_COLUMN_COUNT; ++col) {
this.addSlot(new Slot(inventory, col + row * 9 + 9, 8 + col * 18, 82 + row * 18));
}
}
}
protected void addPlayerHotbar(Inventory inventory) {
for (int slot = 0; slot < HOTBAR_SLOT_COUNT; ++slot) {
this.addSlot(new Slot(inventory, slot, 8 + slot * 18, 140));
}
}
}

View File

@ -0,0 +1,58 @@
package net.banutama.utamacraft.screen;
import java.util.Objects;
import net.banutama.utamacraft.block.custom.ModBlocks;
import net.banutama.utamacraft.block.entity.DigitizerBlockEntity;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.ContainerLevelAccess;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraftforge.common.capabilities.ForgeCapabilities;
import net.minecraftforge.items.SlotItemHandler;
public class DigitizerMenu extends BaseAbstractContainerMenu {
public final DigitizerBlockEntity blockEntity;
private final ContainerLevelAccess levelAccess;
// Client constructor
public DigitizerMenu(int id, Inventory inventory, FriendlyByteBuf extraData) {
this(id, inventory, inventory.player.level.getBlockEntity(extraData.readBlockPos()));
}
// Server constructor
public DigitizerMenu(int id, Inventory inventory, BlockEntity entity) {
super(ModMenuTypes.DIGITIZER_MENU.get(), id);
if (entity instanceof DigitizerBlockEntity digitizer) {
checkContainerSize(inventory, 1);
this.levelAccess = ContainerLevelAccess.create(Objects.requireNonNull(entity.getLevel()),
entity.getBlockPos());
this.blockEntity = digitizer;
addPlayerHotbar(inventory);
addPlayerInventory(inventory);
this.blockEntity.getCapability(ForgeCapabilities.ITEM_HANDLER).ifPresent(handler -> {
this.addSlot(new SlotItemHandler(handler, 0, 26, 8));
});
} else {
throw new IllegalArgumentException("Block entity must be a DigitizerBlockEntity");
}
}
public DigitizerBlockEntity getBlockEntity() {
return blockEntity;
}
@Override
public boolean stillValid(Player player) {
return stillValid(levelAccess, player, ModBlocks.DIGITIZER.get());
}
@Override
protected int getTileEntitySlotCount() {
return 1;
}
}

View File

@ -0,0 +1,78 @@
package net.banutama.utamacraft.screen;
import java.util.List;
import java.util.Optional;
import org.jetbrains.annotations.NotNull;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.PoseStack;
import net.banutama.utamacraft.Utamacraft;
import net.banutama.utamacraft.screen.utils.MouseUtils;
import net.banutama.utamacraft.screen.utils.TooltipUtils;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.player.Inventory;
import net.minecraftforge.energy.EnergyStorage;
public class DigitizerScreen extends AbstractContainerScreen<DigitizerMenu> {
private static final ResourceLocation TEXTURE = new ResourceLocation(Utamacraft.MOD_ID,
"textures/gui/digitizer_gui.png");
public DigitizerScreen(DigitizerMenu menu, Inventory inventory, Component component) {
super(menu, inventory, component);
inventoryLabelY += 5;
}
@Override
protected void init() {
super.init();
leftPos = (width - imageWidth) / 2;
topPos = (height - imageHeight) / 2;
}
@Override
protected void renderBg(@NotNull PoseStack stack, float partialTick, int mouseX, int mouseY) {
RenderSystem.setShader(GameRenderer::getPositionTexShader);
RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f);
RenderSystem.setShaderTexture(0, TEXTURE);
blit(stack, leftPos, topPos, 0, 0, imageWidth, imageHeight);
renderEnergy(stack, leftPos + 8, topPos + 8);
}
@Override
protected void renderLabels(@NotNull PoseStack pPoseStack, int pMouseX, int pMouseY) {
int x = (width - imageWidth) / 2;
int y = (height - imageHeight) / 2;
if (MouseUtils.isMouseOver(pMouseX, pMouseY, x + 7, y + 7, 11, 62)) {
EnergyStorage energy = menu.getBlockEntity().getEnergy();
List<Component> components = TooltipUtils.getEnergyTooltip(energy.getEnergyStored(),
energy.getMaxEnergyStored());
renderTooltip(pPoseStack, components, Optional.empty(), pMouseX - x, pMouseY - y);
}
}
private void renderEnergy(@NotNull PoseStack stack, int x, int y) {
EnergyStorage energy = menu.getBlockEntity().getEnergy();
if (energy.getEnergyStored() <= 0 || energy.getMaxEnergyStored() <= 0) {
return;
}
final int ENERGY_HEIGHT = 60;
int stored = (int) (ENERGY_HEIGHT * ((float) energy.getEnergyStored() / (float) energy.getMaxEnergyStored()));
fillGradient(stack, x, y + (ENERGY_HEIGHT - stored), x + 9, y + 60, 0xffb51500, 0xff600b00);
}
@Override
public void render(@NotNull PoseStack stack, int mouseX, int mouseY, float delta) {
renderBackground(stack);
super.render(stack, mouseX, mouseY, delta);
renderTooltip(stack, mouseX, mouseY);
}
}

View File

@ -6,7 +6,6 @@ import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.*;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraftforge.common.capabilities.ForgeCapabilities;
import net.minecraftforge.items.SlotItemHandler;
@ -14,7 +13,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.Objects;
public class InsolatorMenu extends AbstractContainerMenu {
public class InsolatorMenu extends BaseAbstractContainerMenu {
public final InsolatorBlockEntity blockEntity;
private final ContainerLevelAccess levelAccess;
@ -29,7 +28,8 @@ public class InsolatorMenu extends AbstractContainerMenu {
if (entity instanceof InsolatorBlockEntity insolator) {
checkContainerSize(inventory, 3);
this.levelAccess = ContainerLevelAccess.create(Objects.requireNonNull(entity.getLevel()), entity.getBlockPos());
this.levelAccess = ContainerLevelAccess.create(Objects.requireNonNull(entity.getLevel()),
entity.getBlockPos());
this.blockEntity = insolator;
addPlayerHotbar(inventory);
@ -54,62 +54,8 @@ public class InsolatorMenu extends AbstractContainerMenu {
return stillValid(levelAccess, player, ModBlocks.INSOLATOR.get());
}
private static final int HOTBAR_SLOT_COUNT = 9;
private static final int PLAYER_INVENTORY_ROW_COUNT = 3;
private static final int PLAYER_INVENTORY_COLUMN_COUNT = 9;
private static final int PLAYER_INVENTORY_SLOT_COUNT = PLAYER_INVENTORY_ROW_COUNT * PLAYER_INVENTORY_COLUMN_COUNT;
private static final int VANILLA_SLOT_COUNT = HOTBAR_SLOT_COUNT + PLAYER_INVENTORY_SLOT_COUNT;
private static final int TILE_ENTITY_START_SLOT = VANILLA_SLOT_COUNT;
private static final int TILE_ENTITY_SLOT_COUNT = 3;
@Override
public @NotNull ItemStack quickMoveStack(@NotNull Player player, int index) {
Slot source = slots.get(index);
if (!source.hasItem()) {
return ItemStack.EMPTY;
}
ItemStack sourceStack = source.getItem();
ItemStack sourceCopy = sourceStack.copy();
if (index < VANILLA_SLOT_COUNT) {
// This is a vanilla container slot, so merge the stack into the tile inventory.
if (!moveItemStackTo(sourceStack, TILE_ENTITY_START_SLOT, TILE_ENTITY_START_SLOT + TILE_ENTITY_SLOT_COUNT, false)) {
return ItemStack.EMPTY;
}
} else if (index < TILE_ENTITY_START_SLOT + TILE_ENTITY_SLOT_COUNT) {
// This is a tile-entity slot, so merge the stack into the players inventory.
if (!moveItemStackTo(sourceStack, 0, VANILLA_SLOT_COUNT, false)) {
return ItemStack.EMPTY;
}
} else {
return ItemStack.EMPTY;
}
// If the stack size is zero, the entire stack was moved, so set the slot contents to null.
if (sourceStack.getCount() == 0) {
source.set(ItemStack.EMPTY);
} else {
source.setChanged();
}
source.onTake(player, sourceStack);
return sourceCopy;
}
private void addPlayerInventory(Inventory inventory) {
for (int row = 0; row < PLAYER_INVENTORY_ROW_COUNT; ++row) {
for (int col = 0; col < PLAYER_INVENTORY_COLUMN_COUNT; ++col) {
this.addSlot(new Slot(inventory, col + row * 9 + 9, 8 + col * 18, 82 + row * 18));
}
}
}
private void addPlayerHotbar(Inventory inventory) {
for (int slot = 0; slot < HOTBAR_SLOT_COUNT; ++slot) {
this.addSlot(new Slot(inventory, slot, 8 + slot * 18, 140));
}
protected int getTileEntitySlotCount() {
return 3;
}
}

View File

@ -11,13 +11,16 @@ import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject;
public class ModMenuTypes {
public static final DeferredRegister<MenuType<?>> MENUS =
DeferredRegister.create(ForgeRegistries.MENU_TYPES, Utamacraft.MOD_ID);
public static final DeferredRegister<MenuType<?>> MENUS = DeferredRegister.create(ForgeRegistries.MENU_TYPES,
Utamacraft.MOD_ID);
public static final RegistryObject<MenuType<InsolatorMenu>> INSOLATOR_MENU =
registerMenuType("insolator_menu", InsolatorMenu::new);
public static final RegistryObject<MenuType<InsolatorMenu>> INSOLATOR_MENU = registerMenuType("insolator_menu",
InsolatorMenu::new);
public static final RegistryObject<MenuType<DigitizerMenu>> DIGITIZER_MENU = registerMenuType("digitizer_menu",
DigitizerMenu::new);
private static <T extends AbstractContainerMenu> RegistryObject<MenuType<T>> registerMenuType(String name, IContainerFactory<T> factory) {
private static <T extends AbstractContainerMenu> RegistryObject<MenuType<T>> registerMenuType(String name,
IContainerFactory<T> factory) {
return MENUS.register(name, () -> IForgeMenuType.create(factory));
}

View File

@ -36,5 +36,15 @@ public abstract class ModEnergyStorage extends EnergyStorage {
return energy;
}
public boolean subtractEnergy(int energy) {
if (this.energy >= energy) {
this.energy -= energy;
this.onEnergyChanged();
return true;
}
return false;
}
public abstract void onEnergyChanged();
}

View File

@ -0,0 +1,19 @@
{
"variants": {
"facing=north": {
"model": "utamacraft:block/digitizer"
},
"facing=east": {
"model": "utamacraft:block/digitizer",
"y": 90
},
"facing=south": {
"model": "utamacraft:block/digitizer",
"y": 180
},
"facing=west": {
"model": "utamacraft:block/digitizer",
"y": 270
}
}
}

View File

@ -1,18 +1,21 @@
{
"block.utamacraft.awareness_block": "Awareness Block",
"block.utamacraft.deepslate_tungsten_ore": "Deepslate Tungsten Ore",
"block.utamacraft.digitizer": "Digitizer",
"block.utamacraft.ethereal_glass": "Ethereal Glass",
"block.utamacraft.ethereal_glass_tinted": "Tinted Ethereal Glass",
"block.utamacraft.insolator": "Insolator",
"block.utamacraft.tungsten_block": "Tungsten Block",
"block.utamacraft.tungsten_ore": "Tungsten Ore",
"block_entity.utamacraft.awareness_block": "Awareness Block",
"block_entity.utamacraft.digitizer": "Digitizer",
"block_entity.utamacraft.insolator": "Insolator",
"gui.utamacraft.insolator.dump": "Dump",
"gui.utamacraft.insolator.dump.tooltip": "Dump the fluid contents of the Insolator",
"gui.utamacraft.insolator.dump.tooltip.empty": "No fluid contents to dump from Insolator",
"item.utamacraft.awareness_block": "Awareness Block",
"item.utamacraft.bulb": "Bulb",
"item.utamacraft.digitizer": "Digitizer",
"item.utamacraft.fiber_glass": "Fiberglass",
"item.utamacraft.fire_ward": "Fire Ward Necklace",
"item.utamacraft.insolator": "Insolator",

View File

@ -0,0 +1,283 @@
{
"credit": "Made with Blockbench",
"texture_size": [64, 64],
"textures": {
"0": "utamacraft:block/digitizer",
"particle": "utamacraft:block/digitizer"
},
"elements": [
{
"name": "tray_right",
"from": [2, 3, 12],
"to": [3, 13, 13],
"rotation": { "angle": 22.5, "axis": "x", "origin": [6, 8, 10] },
"faces": {
"north": { "uv": [3.25, 9, 3.5, 11.5], "texture": "#0" },
"east": { "uv": [3.5, 9, 3.75, 11.5], "texture": "#0" },
"south": { "uv": [3.75, 9, 4, 11.5], "texture": "#0" },
"west": { "uv": [4, 9, 4.25, 11.5], "texture": "#0" },
"up": { "uv": [10, 10, 9.75, 9.75], "texture": "#0" },
"down": { "uv": [0.25, 10, 0, 10.25], "texture": "#0" }
}
},
{
"name": "tray_bottom",
"from": [2, 3, 13],
"to": [10, 13, 14],
"rotation": { "angle": 22.5, "axis": "x", "origin": [6, 8, 10] },
"faces": {
"north": { "uv": [0, 7, 2, 9.5], "texture": "#0" },
"east": { "uv": [9, 0, 9.25, 2.5], "texture": "#0" },
"south": { "uv": [7, 0, 9, 2.5], "texture": "#0" },
"west": { "uv": [2, 9, 2.25, 11.5], "texture": "#0" },
"up": { "uv": [8.75, 9.25, 6.75, 9], "texture": "#0" },
"down": { "uv": [10.75, 9, 8.75, 9.25], "texture": "#0" }
}
},
{
"name": "tray_left",
"from": [9, 3, 12],
"to": [10, 13, 13],
"rotation": { "angle": 22.5, "axis": "x", "origin": [6, 8, 10] },
"faces": {
"north": { "uv": [2.25, 9, 2.5, 11.5], "texture": "#0" },
"east": { "uv": [2.5, 9, 2.75, 11.5], "texture": "#0" },
"south": { "uv": [2.75, 9, 3, 11.5], "texture": "#0" },
"west": { "uv": [3, 9, 3.25, 11.5], "texture": "#0" },
"up": { "uv": [9.75, 10, 9.5, 9.75], "texture": "#0" },
"down": { "uv": [10, 9.5, 9.75, 9.75], "texture": "#0" }
}
},
{
"name": "lower",
"from": [1, 0, 1],
"to": [15, 1, 15],
"rotation": { "angle": 0, "axis": "z", "origin": [14, 1, 1] },
"faces": {
"north": { "uv": [7.75, 7.5, 11.25, 7.75], "texture": "#0" },
"east": { "uv": [6.75, 8, 10.25, 8.25], "texture": "#0" },
"south": { "uv": [6.75, 8.25, 10.25, 8.5], "texture": "#0" },
"west": { "uv": [6.75, 8.5, 10.25, 8.75], "texture": "#0" },
"up": { "uv": [7, 3.5, 3.5, 0], "texture": "#0" },
"down": { "uv": [7, 3.5, 3.5, 7], "texture": "#0" }
}
},
{
"name": "left",
"from": [14, 1, 1],
"to": [15, 2, 15],
"rotation": { "angle": 0, "axis": "z", "origin": [14, 1, 1] },
"faces": {
"north": { "uv": [1.75, 9.5, 2, 9.75], "texture": "#0" },
"east": { "uv": [6.25, 7.75, 9.75, 8], "texture": "#0" },
"south": { "uv": [4.75, 9.5, 5, 9.75], "texture": "#0" },
"west": { "uv": [7.75, 6.75, 11.25, 7], "texture": "#0" },
"up": { "uv": [5.25, 11.25, 5, 7.75], "texture": "#0" },
"down": { "uv": [5.5, 7.75, 5.25, 11.25], "texture": "#0" }
}
},
{
"name": "upper",
"from": [1, 2, 1],
"to": [15, 5, 15],
"rotation": { "angle": 0, "axis": "z", "origin": [14, 1, 1] },
"faces": {
"north": { "uv": [2, 7, 5.5, 7.75], "texture": "#0" },
"east": { "uv": [7, 2.5, 10.5, 3.25], "texture": "#0" },
"south": { "uv": [7, 3.25, 10.5, 4], "texture": "#0" },
"west": { "uv": [7, 4, 10.5, 4.75], "texture": "#0" },
"up": { "uv": [3.5, 3.5, 0, 0], "texture": "#0" },
"down": { "uv": [3.5, 3.5, 0, 7], "texture": "#0" }
}
},
{
"name": "right",
"from": [1, 1, 1],
"to": [2, 2, 15],
"rotation": { "angle": 0, "axis": "z", "origin": [14, 1, 1] },
"faces": {
"north": { "uv": [4.75, 9.75, 5, 10], "texture": "#0" },
"east": { "uv": [7.75, 7, 11.25, 7.25], "texture": "#0" },
"south": { "uv": [9.75, 8.75, 10, 9], "texture": "#0" },
"west": { "uv": [7.75, 7.25, 11.25, 7.5], "texture": "#0" },
"up": { "uv": [6.5, 11.5, 6.25, 8], "texture": "#0" },
"down": { "uv": [6.75, 8, 6.5, 11.5], "texture": "#0" }
}
},
{
"name": "back",
"from": [2, 1, 14],
"to": [14, 2, 15],
"rotation": { "angle": 0, "axis": "z", "origin": [14, 1, 1] },
"faces": {
"north": { "uv": [2, 8.25, 5, 8.5], "texture": "#0" },
"east": { "uv": [9.25, 9.75, 9.5, 10], "texture": "#0" },
"south": { "uv": [2, 8.5, 5, 8.75], "texture": "#0" },
"west": { "uv": [9.75, 9.25, 10, 9.5], "texture": "#0" },
"up": { "uv": [5, 9, 2, 8.75], "texture": "#0" },
"down": { "uv": [9.75, 8.75, 6.75, 9], "texture": "#0" }
}
},
{
"name": "button_6",
"from": [2, 5, 2],
"to": [4, 6, 4],
"faces": {
"north": { "uv": [4.25, 9.5, 4.75, 9.75], "texture": "#0" },
"east": { "uv": [9.75, 0.5, 10.25, 0.75], "texture": "#0" },
"south": { "uv": [9.75, 0.75, 10.25, 1], "texture": "#0" },
"west": { "uv": [1, 9.75, 1.5, 10], "texture": "#0" },
"up": { "uv": [9.75, 1, 9.25, 0.5], "texture": "#0" },
"down": { "uv": [9.75, 1, 9.25, 1.5], "texture": "#0" }
}
},
{
"name": "button_5",
"from": [5, 5, 2],
"to": [7, 6, 4],
"faces": {
"north": { "uv": [9.75, 1, 10.25, 1.25], "texture": "#0" },
"east": { "uv": [9.75, 1.25, 10.25, 1.5], "texture": "#0" },
"south": { "uv": [1.5, 9.75, 2, 10], "texture": "#0" },
"west": { "uv": [9.75, 1.5, 10.25, 1.75], "texture": "#0" },
"up": { "uv": [9.75, 2, 9.25, 1.5], "texture": "#0" },
"down": { "uv": [9.75, 2, 9.25, 2.5], "texture": "#0" }
}
},
{
"name": "button_4",
"from": [8, 5, 2],
"to": [10, 6, 4],
"faces": {
"north": { "uv": [9.75, 1.75, 10.25, 2], "texture": "#0" },
"east": { "uv": [9.75, 2, 10.25, 2.25], "texture": "#0" },
"south": { "uv": [9.75, 2.25, 10.25, 2.5], "texture": "#0" },
"west": { "uv": [4.25, 9.75, 4.75, 10], "texture": "#0" },
"up": { "uv": [7.25, 9.75, 6.75, 9.25], "texture": "#0" },
"down": { "uv": [7.75, 9.25, 7.25, 9.75], "texture": "#0" }
}
},
{
"name": "button_1",
"from": [8, 5, 5],
"to": [10, 6, 7],
"faces": {
"north": { "uv": [7.75, 9.75, 8.25, 10], "texture": "#0" },
"east": { "uv": [9.75, 7.75, 10.25, 8], "texture": "#0" },
"south": { "uv": [8.25, 9.75, 8.75, 10], "texture": "#0" },
"west": { "uv": [8.75, 9.75, 9.25, 10], "texture": "#0" },
"up": { "uv": [0.5, 10, 0, 9.5], "texture": "#0" },
"down": { "uv": [1, 9.5, 0.5, 10], "texture": "#0" }
}
},
{
"name": "button_2",
"from": [2, 5, 8],
"to": [10, 6, 11],
"faces": {
"north": { "uv": [9.25, 0, 11.25, 0.25], "texture": "#0" },
"east": { "uv": [6.25, 7.5, 7, 7.75], "texture": "#0" },
"south": { "uv": [9.25, 0.25, 11.25, 0.5], "texture": "#0" },
"west": { "uv": [1, 9.5, 1.75, 9.75], "texture": "#0" },
"up": { "uv": [9.75, 6, 7.75, 5.25], "texture": "#0" },
"down": { "uv": [9.75, 6, 7.75, 6.75], "texture": "#0" }
}
},
{
"name": "button_2",
"from": [5, 5, 5],
"to": [7, 6, 7],
"faces": {
"north": { "uv": [9.75, 6.25, 10.25, 6.5], "texture": "#0" },
"east": { "uv": [9.75, 6.5, 10.25, 6.75], "texture": "#0" },
"south": { "uv": [6.75, 9.75, 7.25, 10], "texture": "#0" },
"west": { "uv": [7.25, 9.75, 7.75, 10], "texture": "#0" },
"up": { "uv": [9.25, 9.75, 8.75, 9.25], "texture": "#0" },
"down": { "uv": [9.75, 9.25, 9.25, 9.75], "texture": "#0" }
}
},
{
"name": "button_3",
"from": [2, 5, 5],
"to": [4, 6, 7],
"faces": {
"north": { "uv": [9.75, 5.25, 10.25, 5.5], "texture": "#0" },
"east": { "uv": [9.75, 5.5, 10.25, 5.75], "texture": "#0" },
"south": { "uv": [9.75, 5.75, 10.25, 6], "texture": "#0" },
"west": { "uv": [9.75, 6, 10.25, 6.25], "texture": "#0" },
"up": { "uv": [8.25, 9.75, 7.75, 9.25], "texture": "#0" },
"down": { "uv": [8.75, 9.25, 8.25, 9.75], "texture": "#0" }
}
},
{
"name": "receiver",
"from": [11, 5, 2],
"to": [14, 7, 14],
"faces": {
"north": { "uv": [6.25, 7, 7, 7.5], "texture": "#0" },
"east": { "uv": [2, 7.75, 5, 8.25], "texture": "#0" },
"south": { "uv": [4.25, 9, 5, 9.5], "texture": "#0" },
"west": { "uv": [7.75, 4.75, 10.75, 5.25], "texture": "#0" },
"up": { "uv": [7.75, 7.75, 7, 4.75], "texture": "#0" },
"down": { "uv": [6.25, 7, 5.5, 10], "texture": "#0" }
}
}
],
"display": {
"thirdperson_righthand": {
"rotation": [0, -180, 0],
"translation": [0, 2.75, -2],
"scale": [0.65, 0.65, 0.65]
},
"thirdperson_lefthand": {
"translation": [0, 2.75, -2],
"scale": [0.65, 0.65, 0.65]
},
"firstperson_righthand": {
"rotation": [5, -180, 0],
"translation": [0, 3.75, 0],
"scale": [0.65, 0.65, 0.65]
},
"firstperson_lefthand": {
"rotation": [5, 0, 0],
"translation": [0, 3.75, 0],
"scale": [0.65, 0.65, 0.65]
},
"ground": {
"scale": [0.61, 0.61, 0.61]
},
"gui": {
"rotation": [39, -145, 9],
"translation": [0.5, 1.25, 0],
"scale": [0.72, 0.69, 0.71]
},
"head": {
"translation": [0, 11, 0]
},
"fixed": {
"rotation": [-90, 0, 0],
"translation": [0, 0, -4.25]
}
},
"groups": [
{
"name": "paper_tray",
"origin": [14, 1, 1],
"color": 0,
"children": [0, 1, 2]
},
{
"name": "base",
"origin": [14, 1, 1],
"color": 0,
"children": [3, 4, 5, 6, 7]
},
{
"name": "buttons",
"origin": [0, 0, 0],
"color": 0,
"children": [8, 9, 10, 11, 12, 13, 14]
},
15
]
}

View File

@ -0,0 +1,3 @@
{
"parent": "utamacraft:block/digitizer"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 711 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -0,0 +1,22 @@
{
"type": "minecraft:crafting_shaped",
"pattern": [" p ", "TRT", "TPT"],
"key": {
"p": {
"item": "minecraft:paper"
},
"T": {
"item": "utamacraft:tungsten_ingot"
},
"R": {
"item": "minecraft:redstone"
},
"P": {
"item": "utamacraft:pcb"
}
},
"result": {
"item": "utamacraft:digitizer",
"count": 1
}
}