diff --git a/assets/digitizer.bbmodel b/assets/digitizer.bbmodel new file mode 100644 index 0000000..e5048f9 --- /dev/null +++ b/assets/digitizer.bbmodel @@ -0,0 +1 @@ +{"meta":{"format_version":"4.9","model_format":"java_block","box_uv":false},"name":"digitizer_block","parent":"","ambientocclusion":true,"front_gui_light":false,"visible_box":[1,1,0],"variable_placeholders":"","variable_placeholder_buttons":[],"unhandled_root_fields":{},"resolution":{"width":64,"height":64},"elements":[{"name":"upper","box_uv":false,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[1,2,1],"to":[15,5,15],"autouv":0,"color":9,"origin":[14,1,1],"faces":{"north":{"uv":[8,28,22,31],"texture":0},"east":{"uv":[28,10,42,13],"texture":0},"south":{"uv":[28,13,42,16],"texture":0},"west":{"uv":[28,16,42,19],"texture":0},"up":{"uv":[14,14,0,0],"texture":0},"down":{"uv":[14,14,0,28],"texture":0}},"type":"cube","uuid":"a12aba1b-b81b-b1f1-5c0c-62f7e4c08f02"},{"name":"left","box_uv":false,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[14,1,1],"to":[15,2,15],"autouv":0,"color":9,"origin":[14,1,1],"faces":{"north":{"uv":[7,38,8,39],"texture":0},"east":{"uv":[25,31,39,32],"texture":0},"south":{"uv":[19,38,20,39],"texture":0},"west":{"uv":[31,27,45,28],"texture":0},"up":{"uv":[21,45,20,31],"texture":0},"down":{"uv":[22,31,21,45],"texture":0}},"type":"cube","uuid":"abb75303-04df-7b2c-1761-4631bb49c874"},{"name":"right","box_uv":false,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[1,1,1],"to":[2,2,15],"autouv":0,"color":9,"origin":[14,1,1],"faces":{"north":{"uv":[19,39,20,40],"texture":0},"east":{"uv":[31,28,45,29],"texture":0},"south":{"uv":[39,35,40,36],"texture":0},"west":{"uv":[31,29,45,30],"texture":0},"up":{"uv":[26,46,25,32],"texture":0},"down":{"uv":[27,32,26,46],"texture":0}},"type":"cube","uuid":"4cafb52c-b44d-cff7-e845-c9e5981f5f2b"},{"name":"back","box_uv":false,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[2,1,14],"to":[14,2,15],"autouv":0,"color":9,"origin":[14,1,1],"faces":{"north":{"uv":[8,33,20,34],"texture":0},"east":{"uv":[37,39,38,40],"texture":0},"south":{"uv":[8,34,20,35],"texture":0},"west":{"uv":[39,37,40,38],"texture":0},"up":{"uv":[20,36,8,35],"texture":0},"down":{"uv":[39,35,27,36],"texture":0}},"type":"cube","uuid":"5bf5b54d-0c79-7dec-fae9-1642bf509458"},{"name":"lower","box_uv":false,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[1,0,1],"to":[15,1,15],"autouv":0,"color":9,"origin":[14,1,1],"faces":{"north":{"uv":[31,30,45,31],"texture":0},"east":{"uv":[27,32,41,33],"texture":0},"south":{"uv":[27,33,41,34],"texture":0},"west":{"uv":[27,34,41,35],"texture":0},"up":{"uv":[28,14,14,0],"texture":0},"down":{"uv":[28,14,14,28],"texture":0}},"type":"cube","uuid":"59f362e7-f1cf-cb8d-c8c2-ca7a8107668a"},{"name":"button_6","box_uv":false,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[2,5,2],"to":[4,6,4],"autouv":0,"color":5,"origin":[0,0,0],"faces":{"north":{"uv":[17,38,19,39],"texture":0},"east":{"uv":[39,2,41,3],"texture":0},"south":{"uv":[39,3,41,4],"texture":0},"west":{"uv":[4,39,6,40],"texture":0},"up":{"uv":[39,4,37,2],"texture":0},"down":{"uv":[39,4,37,6],"texture":0}},"type":"cube","uuid":"a460ffd3-337f-a74c-34b3-6f5c3a7cfcf5"},{"name":"button_5","box_uv":false,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[5,5,2],"to":[7,6,4],"autouv":0,"color":5,"origin":[0,0,0],"faces":{"north":{"uv":[39,4,41,5],"texture":0},"east":{"uv":[39,5,41,6],"texture":0},"south":{"uv":[6,39,8,40],"texture":0},"west":{"uv":[39,6,41,7],"texture":0},"up":{"uv":[39,8,37,6],"texture":0},"down":{"uv":[39,8,37,10],"texture":0}},"type":"cube","uuid":"4f051020-8d89-e756-e079-a54f38276b2c"},{"name":"button_4","box_uv":false,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[8,5,2],"to":[10,6,4],"autouv":0,"color":5,"origin":[0,0,0],"faces":{"north":{"uv":[39,7,41,8],"texture":0},"east":{"uv":[39,8,41,9],"texture":0},"south":{"uv":[39,9,41,10],"texture":0},"west":{"uv":[17,39,19,40],"texture":0},"up":{"uv":[29,39,27,37],"texture":0},"down":{"uv":[31,37,29,39],"texture":0}},"type":"cube","uuid":"250dee2d-1235-794b-d9a6-700119d6a297"},{"name":"receiver","box_uv":false,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[11,5,2],"to":[14,7,14],"autouv":0,"color":5,"origin":[0,0,0],"faces":{"north":{"uv":[25,28,28,30],"texture":0},"east":{"uv":[8,31,20,33],"texture":0},"south":{"uv":[17,36,20,38],"texture":0},"west":{"uv":[31,19,43,21],"texture":0},"up":{"uv":[31,31,28,19],"texture":0},"down":{"uv":[25,28,22,40],"texture":0}},"type":"cube","uuid":"85199572-1ed6-8346-bfef-486883cc2a03"},{"name":"button_3","box_uv":false,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[2,5,5],"to":[4,6,7],"autouv":0,"color":5,"origin":[0,0,0],"faces":{"north":{"uv":[39,21,41,22],"texture":0},"east":{"uv":[39,22,41,23],"texture":0},"south":{"uv":[39,23,41,24],"texture":0},"west":{"uv":[39,24,41,25],"texture":0},"up":{"uv":[33,39,31,37],"texture":0},"down":{"uv":[35,37,33,39],"texture":0}},"type":"cube","uuid":"9e829284-c571-19b6-2c82-535cb16d9128"},{"name":"button_2","box_uv":false,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[5,5,5],"to":[7,6,7],"autouv":0,"color":5,"origin":[0,0,0],"faces":{"north":{"uv":[39,25,41,26],"texture":0},"east":{"uv":[39,26,41,27],"texture":0},"south":{"uv":[27,39,29,40],"texture":0},"west":{"uv":[29,39,31,40],"texture":0},"up":{"uv":[37,39,35,37],"texture":0},"down":{"uv":[39,37,37,39],"texture":0}},"type":"cube","uuid":"fa2dea41-4c24-9ae2-5d91-0fd71e56a4aa"},{"name":"button_1","box_uv":false,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[8,5,5],"to":[10,6,7],"autouv":0,"color":5,"origin":[0,0,0],"faces":{"north":{"uv":[31,39,33,40],"texture":0},"east":{"uv":[39,31,41,32],"texture":0},"south":{"uv":[33,39,35,40],"texture":0},"west":{"uv":[35,39,37,40],"texture":0},"up":{"uv":[2,40,0,38],"texture":0},"down":{"uv":[4,38,2,40],"texture":0}},"type":"cube","uuid":"b3e200ba-b7dc-46e0-974f-dd3d2938bd06"},{"name":"tray_bottom","box_uv":false,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[2,3,13],"to":[10,13,14],"autouv":0,"color":9,"rotation":[22.5,0,0],"origin":[6,8,10],"faces":{"north":{"uv":[0,28,8,38],"texture":0},"east":{"uv":[36,0,37,10],"texture":0},"south":{"uv":[28,0,36,10],"texture":0},"west":{"uv":[8,36,9,46],"texture":0},"up":{"uv":[35,37,27,36],"texture":0},"down":{"uv":[43,36,35,37],"texture":0}},"type":"cube","uuid":"e992aab8-f3ac-c2d6-05f7-be9587a76578"},{"name":"tray_left","box_uv":false,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[9,3,12],"to":[10,13,13],"autouv":0,"color":9,"rotation":[22.5,0,0],"origin":[6,8,10],"faces":{"north":{"uv":[9,36,10,46],"texture":0},"east":{"uv":[10,36,11,46],"texture":0},"south":{"uv":[11,36,12,46],"texture":0},"west":{"uv":[12,36,13,46],"texture":0},"up":{"uv":[39,40,38,39],"texture":0},"down":{"uv":[40,38,39,39],"texture":0}},"type":"cube","uuid":"9759b8df-ad17-536d-8155-602c6aaaafb4"},{"name":"tray_right","box_uv":false,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[2,3,12],"to":[3,13,13],"autouv":0,"color":9,"rotation":[22.5,0,0],"origin":[6,8,10],"faces":{"north":{"uv":[13,36,14,46],"texture":0},"east":{"uv":[14,36,15,46],"texture":0},"south":{"uv":[15,36,16,46],"texture":0},"west":{"uv":[16,36,17,46],"texture":0},"up":{"uv":[40,40,39,39],"texture":0},"down":{"uv":[1,40,0,41],"texture":0}},"type":"cube","uuid":"033b81c8-f4cd-4373-905a-8fcc701d2e49"},{"name":"button_2","box_uv":false,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[2,5,8],"to":[10,6,11],"autouv":0,"color":5,"origin":[0,0,0],"faces":{"north":{"uv":[37,0,45,1],"texture":0},"east":{"uv":[25,30,28,31],"texture":0},"south":{"uv":[37,1,45,2],"texture":0},"west":{"uv":[4,38,7,39],"texture":0},"up":{"uv":[39,24,31,21],"texture":0},"down":{"uv":[39,24,31,27],"texture":0}},"type":"cube","uuid":"a9bb3568-1ad9-8b14-a9a8-35be7c515f72"}],"outliner":[{"name":"paper_tray","origin":[14,1,1],"color":0,"uuid":"0788a580-f11e-83fe-2996-3b0ec9616e03","export":true,"mirror_uv":false,"isOpen":true,"locked":false,"visibility":true,"autouv":0,"children":["033b81c8-f4cd-4373-905a-8fcc701d2e49","e992aab8-f3ac-c2d6-05f7-be9587a76578","9759b8df-ad17-536d-8155-602c6aaaafb4"]},{"name":"base","origin":[14,1,1],"color":0,"uuid":"a7d981a4-5504-4000-5d7d-4bac8ce1f9ad","export":true,"mirror_uv":false,"isOpen":true,"locked":false,"visibility":true,"autouv":0,"children":["59f362e7-f1cf-cb8d-c8c2-ca7a8107668a","abb75303-04df-7b2c-1761-4631bb49c874","a12aba1b-b81b-b1f1-5c0c-62f7e4c08f02","4cafb52c-b44d-cff7-e845-c9e5981f5f2b","5bf5b54d-0c79-7dec-fae9-1642bf509458"]},{"name":"buttons","origin":[0,0,0],"color":0,"uuid":"0b92496b-e561-939a-6111-c1f4ac86ccce","export":true,"mirror_uv":false,"isOpen":true,"locked":false,"visibility":true,"autouv":0,"children":["a460ffd3-337f-a74c-34b3-6f5c3a7cfcf5","4f051020-8d89-e756-e079-a54f38276b2c","250dee2d-1235-794b-d9a6-700119d6a297","b3e200ba-b7dc-46e0-974f-dd3d2938bd06","a9bb3568-1ad9-8b14-a9a8-35be7c515f72","fa2dea41-4c24-9ae2-5d91-0fd71e56a4aa","9e829284-c571-19b6-2c82-535cb16d9128"]},"85199572-1ed6-8346-bfef-486883cc2a03"],"textures":[{"path":"/Users/blakerain/dev/bans-minecraft/utamacraft/assets/textures/digitizer.png","name":"digitizer.png","folder":"","namespace":"assets","id":"0","width":64,"height":64,"uv_width":64,"uv_height":64,"particle":true,"layers_enabled":false,"sync_to_project":"","render_mode":"default","render_sides":"auto","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"internal":true,"saved":true,"uuid":"52308155-7b28-5a49-ae78-07030227a498","relative_path":"../textures/digitizer.png","source":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAArxJREFUeF5j3LhpzX8GMkBf72QydDEwdHR0ENRXXV2NV82+ffsYCRpCpALG0QAYhClgx44dKPHX2NhItRhHTxiDMgWM+ADombUWHFE6ctxgesSlgBEfACM+C4z4ABjxWWBYB0BRcS6RbTRUZf5+ITRpC9C9HTAaAKMpYJBlAXt7e7J6g+QmZbIKAAYGBpqVAaMBMJoCRrPAaBlATsFEbiF47uxlcqyjWbeYkd6F4GgAIKWAL0Z38KYGnnMqNIt5mMUDmgJGA2Ckp4Arj77izQJ0GRMcyEIQFADS4hJYA+Hpyxf0GRQd6ADAlwSGfQo4cOAAWW2CgwcPUm1whPH48eN4W4KvXj8ly5GL1p+A64PFJLpBwz4AQPkbOS+jB4CRsS5ZgUvNrjFNUwAsAN4/PAf3qK2tLZw9JAIAfYyeUJSZhiYxzIdOncMCIC7QgpA2rPKwZjNNp8YIlQHUCIDE4lxwoJAbENRM8hizw4QCoKKigqTYK526CCMFDOkAILUWYFYyHV4B8JZXiqQUAFKMXgbAUgCpBoGyDC2TP8g9BGsBZEcbiq1mOP8qFC4E4yOXE9gKQVgAlKQFkxoGKOotLS2p1gCCd4cJlQHUDABYgwjWCSLEX6gqxMBTVUV1TyP7ibG+vh7cEoQ5CrnOzi/IYhATlcaIcZgAegoA1ev4ygBCHkaXB9mzdn4PfQMAvdnq4eFBdgDANMKyAK4mMXq+OJLJx9By3wosTItkj5ICghNL/uMLZeQsgqsMQHbklisP/4MKQWQzsYlRVBhQUTPB5DUaAEi9xdEUgKMaHM0CSPXziC8DsJVPQ7oQpGKBOyiNIlgLDEpXU9FRowFAxcAckkaNpoAhGW1UdPRoCqBiYA5Jo0ZTwJCMNio6ejQFUDEwh6RRoylgSEYbFR09mgKoGJhD0qjRFDAko42KjgYA45piX6qgDwoAAAAASUVORK5CYII="}],"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]}}} \ No newline at end of file diff --git a/assets/digitizer_peripheral.afdesign b/assets/digitizer_peripheral.afdesign new file mode 100644 index 0000000..baf93b7 Binary files /dev/null and b/assets/digitizer_peripheral.afdesign differ diff --git a/assets/gui/digitizer_gui.afdesign b/assets/gui/digitizer_gui.afdesign new file mode 100644 index 0000000..ecaea63 Binary files /dev/null and b/assets/gui/digitizer_gui.afdesign differ diff --git a/assets/textures/digitizer.png b/assets/textures/digitizer.png new file mode 100644 index 0000000..e7bbff0 Binary files /dev/null and b/assets/textures/digitizer.png differ diff --git a/src/main/java/net/banutama/utamacraft/CCRegistration.java b/src/main/java/net/banutama/utamacraft/CCRegistration.java index ec5c50e..768d7e0 100644 --- a/src/main/java/net/banutama/utamacraft/CCRegistration.java +++ b/src/main/java/net/banutama/utamacraft/CCRegistration.java @@ -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> 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); } diff --git a/src/main/java/net/banutama/utamacraft/Utamacraft.java b/src/main/java/net/banutama/utamacraft/Utamacraft.java index ea2a820..4f4e97c 100644 --- a/src/main/java/net/banutama/utamacraft/Utamacraft.java +++ b/src/main/java/net/banutama/utamacraft/Utamacraft.java @@ -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(); } diff --git a/src/main/java/net/banutama/utamacraft/block/custom/DigitizerBlock.java b/src/main/java/net/banutama/utamacraft/block/custom/DigitizerBlock.java new file mode 100644 index 0000000..ea71a97 --- /dev/null +++ b/src/main/java/net/banutama/utamacraft/block/custom/DigitizerBlock.java @@ -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 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); + } +} diff --git a/src/main/java/net/banutama/utamacraft/block/custom/ModBlocks.java b/src/main/java/net/banutama/utamacraft/block/custom/ModBlocks.java index 08bd37f..22e1a2f 100644 --- a/src/main/java/net/banutama/utamacraft/block/custom/ModBlocks.java +++ b/src/main/java/net/banutama/utamacraft/block/custom/ModBlocks.java @@ -40,6 +40,7 @@ public class ModBlocks { .requiresCorrectToolForDrops())); public static final RegistryObject INSOLATOR = registerBlock("insolator", InsolatorBlock::new); public static final RegistryObject AWARENESS_BLOCK = registerBlock("awareness_block", AwarenessBlock::new); + public static final RegistryObject DIGITIZER = registerBlock("digitizer", DigitizerBlock::new); private static RegistryObject registerBlock(String name, Supplier block) { RegistryObject registered_block = BLOCKS.register(name, block); diff --git a/src/main/java/net/banutama/utamacraft/block/entity/DigitizerBlockEntity.java b/src/main/java/net/banutama/utamacraft/block/entity/DigitizerBlockEntity.java new file mode 100644 index 0000000..cfa2d82 --- /dev/null +++ b/src/main/java/net/banutama/utamacraft/block/entity/DigitizerBlockEntity.java @@ -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 inventoryOptional = LazyOptional.of(() -> inventory); + private final LazyOptional 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 LazyOptional getCapability(@NotNull Capability 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 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 getInventoryOptional() { + return inventoryOptional; + } +} diff --git a/src/main/java/net/banutama/utamacraft/block/entity/ModBlockEntities.java b/src/main/java/net/banutama/utamacraft/block/entity/ModBlockEntities.java index 50e5dda..40ecc52 100644 --- a/src/main/java/net/banutama/utamacraft/block/entity/ModBlockEntities.java +++ b/src/main/java/net/banutama/utamacraft/block/entity/ModBlockEntities.java @@ -20,6 +20,10 @@ public class ModBlockEntities { "awareness_block", () -> BlockEntityType.Builder.of(AwarenessBlockEntity::new, ModBlocks.AWARENESS_BLOCK.get()).build(null)); + public static final RegistryObject> 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); } diff --git a/src/main/java/net/banutama/utamacraft/integrations/computercraft/peripheral/DigitizerPeripheral.java b/src/main/java/net/banutama/utamacraft/integrations/computercraft/peripheral/DigitizerPeripheral.java new file mode 100644 index 0000000..2aabafa --- /dev/null +++ b/src/main/java/net/banutama/utamacraft/integrations/computercraft/peripheral/DigitizerPeripheral.java @@ -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(); + 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> 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 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(); + 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(); + 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>(); + cache.forEach(item -> { + result.add(item.describeItem(gameTime)); + }); + + return MethodResult.of(result); + } +} diff --git a/src/main/java/net/banutama/utamacraft/integrations/computercraft/peripheral/digitizer/DigitizedCache.java b/src/main/java/net/banutama/utamacraft/integrations/computercraft/peripheral/digitizer/DigitizedCache.java new file mode 100644 index 0000000..eb73936 --- /dev/null +++ b/src/main/java/net/banutama/utamacraft/integrations/computercraft/peripheral/digitizer/DigitizedCache.java @@ -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 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 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(); + } +} diff --git a/src/main/java/net/banutama/utamacraft/integrations/computercraft/peripheral/digitizer/DigitizedItem.java b/src/main/java/net/banutama/utamacraft/integrations/computercraft/peripheral/digitizer/DigitizedItem.java new file mode 100644 index 0000000..2684407 --- /dev/null +++ b/src/main/java/net/banutama/utamacraft/integrations/computercraft/peripheral/digitizer/DigitizedItem.java @@ -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 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 describeItem(long gameTime) { + var map = new HashMap(); + describeItem(map, gameTime); + return map; + } +} diff --git a/src/main/java/net/banutama/utamacraft/item/BaseItem.java b/src/main/java/net/banutama/utamacraft/item/BaseItem.java index 118a4a1..d634bfb 100644 --- a/src/main/java/net/banutama/utamacraft/item/BaseItem.java +++ b/src/main/java/net/banutama/utamacraft/item/BaseItem.java @@ -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 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); + } } diff --git a/src/main/java/net/banutama/utamacraft/item/PlayerPeripheralItem.java b/src/main/java/net/banutama/utamacraft/item/PlayerPeripheralItem.java index 5ba14cb..79c8ded 100644 --- a/src/main/java/net/banutama/utamacraft/item/PlayerPeripheralItem.java +++ b/src/main/java/net/banutama/utamacraft/item/PlayerPeripheralItem.java @@ -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); } } } diff --git a/src/main/java/net/banutama/utamacraft/screen/BaseAbstractContainerMenu.java b/src/main/java/net/banutama/utamacraft/screen/BaseAbstractContainerMenu.java new file mode 100644 index 0000000..9c91c04 --- /dev/null +++ b/src/main/java/net/banutama/utamacraft/screen/BaseAbstractContainerMenu.java @@ -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)); + } + } +} diff --git a/src/main/java/net/banutama/utamacraft/screen/DigitizerMenu.java b/src/main/java/net/banutama/utamacraft/screen/DigitizerMenu.java new file mode 100644 index 0000000..858ab8b --- /dev/null +++ b/src/main/java/net/banutama/utamacraft/screen/DigitizerMenu.java @@ -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; + } +} diff --git a/src/main/java/net/banutama/utamacraft/screen/DigitizerScreen.java b/src/main/java/net/banutama/utamacraft/screen/DigitizerScreen.java new file mode 100644 index 0000000..4e5dd6d --- /dev/null +++ b/src/main/java/net/banutama/utamacraft/screen/DigitizerScreen.java @@ -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 { + 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 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); + } +} diff --git a/src/main/java/net/banutama/utamacraft/screen/InsolatorMenu.java b/src/main/java/net/banutama/utamacraft/screen/InsolatorMenu.java index d02c3c6..f4ad2dc 100644 --- a/src/main/java/net/banutama/utamacraft/screen/InsolatorMenu.java +++ b/src/main/java/net/banutama/utamacraft/screen/InsolatorMenu.java @@ -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; } } diff --git a/src/main/java/net/banutama/utamacraft/screen/ModMenuTypes.java b/src/main/java/net/banutama/utamacraft/screen/ModMenuTypes.java index 477eaa6..6f165b9 100644 --- a/src/main/java/net/banutama/utamacraft/screen/ModMenuTypes.java +++ b/src/main/java/net/banutama/utamacraft/screen/ModMenuTypes.java @@ -11,13 +11,16 @@ import net.minecraftforge.registries.ForgeRegistries; import net.minecraftforge.registries.RegistryObject; public class ModMenuTypes { - public static final DeferredRegister> MENUS = - DeferredRegister.create(ForgeRegistries.MENU_TYPES, Utamacraft.MOD_ID); + public static final DeferredRegister> MENUS = DeferredRegister.create(ForgeRegistries.MENU_TYPES, + Utamacraft.MOD_ID); - public static final RegistryObject> INSOLATOR_MENU = - registerMenuType("insolator_menu", InsolatorMenu::new); + public static final RegistryObject> INSOLATOR_MENU = registerMenuType("insolator_menu", + InsolatorMenu::new); + public static final RegistryObject> DIGITIZER_MENU = registerMenuType("digitizer_menu", + DigitizerMenu::new); - private static RegistryObject> registerMenuType(String name, IContainerFactory factory) { + private static RegistryObject> registerMenuType(String name, + IContainerFactory factory) { return MENUS.register(name, () -> IForgeMenuType.create(factory)); } diff --git a/src/main/java/net/banutama/utamacraft/util/ModEnergyStorage.java b/src/main/java/net/banutama/utamacraft/util/ModEnergyStorage.java index eebb670..963e3c8 100644 --- a/src/main/java/net/banutama/utamacraft/util/ModEnergyStorage.java +++ b/src/main/java/net/banutama/utamacraft/util/ModEnergyStorage.java @@ -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(); } diff --git a/src/main/resources/assets/utamacraft/blockstates/digitizer.json b/src/main/resources/assets/utamacraft/blockstates/digitizer.json new file mode 100644 index 0000000..8274078 --- /dev/null +++ b/src/main/resources/assets/utamacraft/blockstates/digitizer.json @@ -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 + } + } +} diff --git a/src/main/resources/assets/utamacraft/lang/en_us.json b/src/main/resources/assets/utamacraft/lang/en_us.json index 3e0008a..5ef3a14 100644 --- a/src/main/resources/assets/utamacraft/lang/en_us.json +++ b/src/main/resources/assets/utamacraft/lang/en_us.json @@ -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", diff --git a/src/main/resources/assets/utamacraft/models/block/digitizer.json b/src/main/resources/assets/utamacraft/models/block/digitizer.json new file mode 100644 index 0000000..d24836e --- /dev/null +++ b/src/main/resources/assets/utamacraft/models/block/digitizer.json @@ -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 + ] +} diff --git a/src/main/resources/assets/utamacraft/models/item/digitizer.json b/src/main/resources/assets/utamacraft/models/item/digitizer.json new file mode 100644 index 0000000..96b9e2f --- /dev/null +++ b/src/main/resources/assets/utamacraft/models/item/digitizer.json @@ -0,0 +1,3 @@ +{ + "parent": "utamacraft:block/digitizer" +} diff --git a/src/main/resources/assets/utamacraft/textures/block/digitizer.png b/src/main/resources/assets/utamacraft/textures/block/digitizer.png new file mode 100644 index 0000000..e7bbff0 Binary files /dev/null and b/src/main/resources/assets/utamacraft/textures/block/digitizer.png differ diff --git a/src/main/resources/assets/utamacraft/textures/gui/digitizer_gui.png b/src/main/resources/assets/utamacraft/textures/gui/digitizer_gui.png new file mode 100644 index 0000000..4aa08f2 Binary files /dev/null and b/src/main/resources/assets/utamacraft/textures/gui/digitizer_gui.png differ diff --git a/src/main/resources/data/utamacraft/recipes/digitizer.json b/src/main/resources/data/utamacraft/recipes/digitizer.json new file mode 100644 index 0000000..664112e --- /dev/null +++ b/src/main/resources/data/utamacraft/recipes/digitizer.json @@ -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 + } +}