From 017847f982c1940d380a7023568d320bb4d9f563 Mon Sep 17 00:00:00 2001 From: Joshua Anderson Date: Wed, 22 May 2024 17:59:44 -0600 Subject: [PATCH] Improved keycard reader and unique keycard system --- .../blockstates/keycard_cloner.json | 19 + .../blockstates/keycard_writer.json | 19 + .../models/item/keycard_cloner.json | 3 + .../models/item/keycard_writer.json | 3 + .../andersmmg/lockandblock/LockAndBlock.java | 16 +- .../lockandblock/block/ModBlocks.java | 6 + .../block/custom/KeycardClonerBlock.java | 130 +++++ .../block/custom/KeycardReaderBlock.java | 69 ++- .../block/custom/KeycardWriterBlock.java | 99 ++++ .../entity/KeycardClonerBlockEntity.java | 43 ++ .../entity/KeycardReaderBlockEntity.java | 28 + .../block/entity/ModBlockEntities.java | 4 + .../datagen/ModModelProvider.java | 8 + .../andersmmg/lockandblock/item/ModItems.java | 2 +- .../lockandblock/item/custom/KeycardItem.java | 31 ++ .../lockandblock/sounds/ModSounds.java | 21 + .../assets/lockandblock/lang/en_us.json | 15 +- .../models/block/keycard_cloner.json | 365 ++++++++++++ .../models/block/keycard_writer.json | 526 ++++++++++++++++++ .../resources/assets/lockandblock/sounds.json | 14 + .../assets/lockandblock/sounds/beep_error.ogg | Bin 0 -> 11641 bytes .../lockandblock/sounds/beep_success.ogg | Bin 0 -> 4707 bytes .../textures/block/keycard_cloner.png | Bin 0 -> 374 bytes .../textures/block/keycard_writer.png | Bin 0 -> 439 bytes 24 files changed, 1407 insertions(+), 14 deletions(-) create mode 100644 src/main/generated/assets/lockandblock/blockstates/keycard_cloner.json create mode 100644 src/main/generated/assets/lockandblock/blockstates/keycard_writer.json create mode 100644 src/main/generated/assets/lockandblock/models/item/keycard_cloner.json create mode 100644 src/main/generated/assets/lockandblock/models/item/keycard_writer.json create mode 100644 src/main/java/com/andersmmg/lockandblock/block/custom/KeycardClonerBlock.java create mode 100644 src/main/java/com/andersmmg/lockandblock/block/custom/KeycardWriterBlock.java create mode 100644 src/main/java/com/andersmmg/lockandblock/block/entity/KeycardClonerBlockEntity.java create mode 100644 src/main/java/com/andersmmg/lockandblock/sounds/ModSounds.java create mode 100644 src/main/resources/assets/lockandblock/models/block/keycard_cloner.json create mode 100644 src/main/resources/assets/lockandblock/models/block/keycard_writer.json create mode 100644 src/main/resources/assets/lockandblock/sounds.json create mode 100644 src/main/resources/assets/lockandblock/sounds/beep_error.ogg create mode 100644 src/main/resources/assets/lockandblock/sounds/beep_success.ogg create mode 100644 src/main/resources/assets/lockandblock/textures/block/keycard_cloner.png create mode 100644 src/main/resources/assets/lockandblock/textures/block/keycard_writer.png diff --git a/src/main/generated/assets/lockandblock/blockstates/keycard_cloner.json b/src/main/generated/assets/lockandblock/blockstates/keycard_cloner.json new file mode 100644 index 0000000..ac1f9a2 --- /dev/null +++ b/src/main/generated/assets/lockandblock/blockstates/keycard_cloner.json @@ -0,0 +1,19 @@ +{ + "variants": { + "facing=east": { + "model": "lockandblock:block/keycard_cloner", + "y": 90 + }, + "facing=north": { + "model": "lockandblock:block/keycard_cloner" + }, + "facing=south": { + "model": "lockandblock:block/keycard_cloner", + "y": 180 + }, + "facing=west": { + "model": "lockandblock:block/keycard_cloner", + "y": 270 + } + } +} \ No newline at end of file diff --git a/src/main/generated/assets/lockandblock/blockstates/keycard_writer.json b/src/main/generated/assets/lockandblock/blockstates/keycard_writer.json new file mode 100644 index 0000000..c17c214 --- /dev/null +++ b/src/main/generated/assets/lockandblock/blockstates/keycard_writer.json @@ -0,0 +1,19 @@ +{ + "variants": { + "facing=east": { + "model": "lockandblock:block/keycard_writer", + "y": 90 + }, + "facing=north": { + "model": "lockandblock:block/keycard_writer" + }, + "facing=south": { + "model": "lockandblock:block/keycard_writer", + "y": 180 + }, + "facing=west": { + "model": "lockandblock:block/keycard_writer", + "y": 270 + } + } +} \ No newline at end of file diff --git a/src/main/generated/assets/lockandblock/models/item/keycard_cloner.json b/src/main/generated/assets/lockandblock/models/item/keycard_cloner.json new file mode 100644 index 0000000..0583961 --- /dev/null +++ b/src/main/generated/assets/lockandblock/models/item/keycard_cloner.json @@ -0,0 +1,3 @@ +{ + "parent": "lockandblock:block/keycard_cloner" +} \ No newline at end of file diff --git a/src/main/generated/assets/lockandblock/models/item/keycard_writer.json b/src/main/generated/assets/lockandblock/models/item/keycard_writer.json new file mode 100644 index 0000000..8c22369 --- /dev/null +++ b/src/main/generated/assets/lockandblock/models/item/keycard_writer.json @@ -0,0 +1,3 @@ +{ + "parent": "lockandblock:block/keycard_writer" +} \ No newline at end of file diff --git a/src/main/java/com/andersmmg/lockandblock/LockAndBlock.java b/src/main/java/com/andersmmg/lockandblock/LockAndBlock.java index f8d4f4d..c8dd0f5 100644 --- a/src/main/java/com/andersmmg/lockandblock/LockAndBlock.java +++ b/src/main/java/com/andersmmg/lockandblock/LockAndBlock.java @@ -4,7 +4,10 @@ import com.andersmmg.lockandblock.block.entity.ModBlockEntities; import com.andersmmg.lockandblock.item.ModItemGroups; import com.andersmmg.lockandblock.item.ModItems; +import com.andersmmg.lockandblock.sounds.ModSounds; import net.fabricmc.api.ModInitializer; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; import net.minecraft.util.Identifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -12,16 +15,27 @@ public class LockAndBlock implements ModInitializer { public static final String MOD_ID = "lockandblock"; public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); -// public static final ModConfig CONFIG = ModConfig.createAndLoad(); + // public static final ModConfig CONFIG = ModConfig.createAndLoad(); + public static final String CARD_UUID_KEY = "card_uuid"; public static Identifier id(String path) { return new Identifier(MOD_ID, path); } + + public static MutableText langText(String key) { + return langText(key, "text"); + } + + public static MutableText langText(String key, String type) { + return Text.translatable(type + "." + LockAndBlock.MOD_ID + "." + key); + } + @Override public void onInitialize() { ModItems.registerModItems(); ModItemGroups.registerItemGroups(); ModBlocks.registerModBlocks(); ModBlockEntities.registerBlockEntities(); + ModSounds.registerSounds(); } } diff --git a/src/main/java/com/andersmmg/lockandblock/block/ModBlocks.java b/src/main/java/com/andersmmg/lockandblock/block/ModBlocks.java index 5900f82..388c7c3 100644 --- a/src/main/java/com/andersmmg/lockandblock/block/ModBlocks.java +++ b/src/main/java/com/andersmmg/lockandblock/block/ModBlocks.java @@ -1,7 +1,9 @@ package com.andersmmg.lockandblock.block; import com.andersmmg.lockandblock.LockAndBlock; +import com.andersmmg.lockandblock.block.custom.KeycardClonerBlock; import com.andersmmg.lockandblock.block.custom.KeycardReaderBlock; +import com.andersmmg.lockandblock.block.custom.KeycardWriterBlock; import com.andersmmg.lockandblock.item.ModItemGroups; import io.wispforest.owo.itemgroup.OwoItemSettings; import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; @@ -15,6 +17,10 @@ public class ModBlocks { public static final Block KEYCARD_READER = registerBlock("keycard_reader", new KeycardReaderBlock(FabricBlockSettings.copyOf(Blocks.WHITE_CONCRETE).nonOpaque())); + public static final Block KEYCARD_WRITER = registerBlock("keycard_writer", + new KeycardWriterBlock(FabricBlockSettings.copyOf(Blocks.WHITE_CONCRETE).nonOpaque())); + public static final Block KEYCARD_CLONER = registerBlock("keycard_cloner", + new KeycardClonerBlock(FabricBlockSettings.copyOf(Blocks.WHITE_CONCRETE).nonOpaque())); private static Block registerBlock(String name, Block block) { registerBlockItem(name, block); diff --git a/src/main/java/com/andersmmg/lockandblock/block/custom/KeycardClonerBlock.java b/src/main/java/com/andersmmg/lockandblock/block/custom/KeycardClonerBlock.java new file mode 100644 index 0000000..05034ac --- /dev/null +++ b/src/main/java/com/andersmmg/lockandblock/block/custom/KeycardClonerBlock.java @@ -0,0 +1,130 @@ +package com.andersmmg.lockandblock.block.custom; + +import com.andersmmg.lockandblock.LockAndBlock; +import com.andersmmg.lockandblock.block.entity.KeycardClonerBlockEntity; +import com.andersmmg.lockandblock.item.ModItems; +import com.andersmmg.lockandblock.item.custom.KeycardItem; +import com.andersmmg.lockandblock.util.VoxelUtils; +import net.minecraft.block.*; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemPlacementContext; +import net.minecraft.item.ItemStack; +import net.minecraft.state.StateManager; +import net.minecraft.state.property.DirectionProperty; +import net.minecraft.state.property.Properties; +import net.minecraft.util.ActionResult; +import net.minecraft.util.BlockMirror; +import net.minecraft.util.BlockRotation; +import net.minecraft.util.Hand; +import net.minecraft.util.function.BooleanBiFunction; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.util.shape.VoxelShapes; +import net.minecraft.world.BlockView; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; + +import java.util.stream.Stream; + +public class KeycardClonerBlock extends BlockWithEntity { + public static final DirectionProperty FACING = Properties.HORIZONTAL_FACING; + private static final VoxelShape VOXEL_SHAPE = Stream.of( + Block.createCuboidShape(3, 0, 3, 13, 2, 13), + Block.createCuboidShape(3, 2, 3, 4, 4, 13), + Block.createCuboidShape(5, 2, 3, 6, 4, 13) + ).reduce((v1, v2) -> VoxelShapes.combineAndSimplify(v1, v2, BooleanBiFunction.OR)).get(); + ; + + public KeycardClonerBlock(Settings settings) { + super(settings); + this.setDefaultState(this.stateManager.getDefaultState().with(FACING, Direction.NORTH)); + } + + @Override + public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) { + ItemStack stack = player.getStackInHand(hand); + if (stack.isOf(ModItems.KEYCARD)) { + BlockEntity blockEntity = world.getBlockEntity(pos); + if (blockEntity instanceof KeycardClonerBlockEntity keycardClonerBlockEntity) { + if (keycardClonerBlockEntity.hasUuid()) { + if (KeycardItem.hasUuid(stack)) { + if (!world.isClient) + player.sendMessage(LockAndBlock.langText("card_not_blank"), true); + return ActionResult.FAIL; + } else { + if (!world.isClient) { + KeycardItem.setUuid(keycardClonerBlockEntity.getUuid(), stack); + player.sendMessage(LockAndBlock.langText("card_copied"), true); + keycardClonerBlockEntity.clearUuid(); + } + } + return ActionResult.SUCCESS; + } else { + if (KeycardItem.hasUuid(stack)) { + if (!world.isClient) { + keycardClonerBlockEntity.setUuid(KeycardItem.getUuid(stack)); + player.sendMessage(LockAndBlock.langText("card_saved"), true); + } + } else { + if (!world.isClient) + player.sendMessage(LockAndBlock.langText("card_blank"), true); + } + } + } + return ActionResult.SUCCESS; + } else { + BlockEntity blockEntity = world.getBlockEntity(pos); + if (blockEntity instanceof KeycardClonerBlockEntity keycardClonerBlockEntity && keycardClonerBlockEntity.hasUuid()) { + if (!world.isClient) { + keycardClonerBlockEntity.clearUuid(); + player.sendMessage(LockAndBlock.langText("card_cleared"), true); + } + return ActionResult.SUCCESS; + } + } + return ActionResult.FAIL; + } + + protected static Direction getDirection(BlockState state) { + return state.get(FACING); + } + + @Override + public BlockRenderType getRenderType(BlockState state) { + return BlockRenderType.MODEL; + } + + @Override + public BlockState rotate(BlockState state, BlockRotation rotation) { + return state.with(FACING, rotation.rotate(state.get(FACING))); + } + + @Override + public BlockState mirror(BlockState state, BlockMirror mirror) { + return state.rotate(mirror.getRotation(state.get(FACING))); + } + + @Override + public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) { + return VoxelUtils.rotateShape(getDirection(state), VOXEL_SHAPE); + } + + @Override + protected void appendProperties(StateManager.Builder builder) { + builder.add(FACING); + } + + @Override + public BlockState getPlacementState(ItemPlacementContext ctx) { + return this.getDefaultState().with(FACING, ctx.getHorizontalPlayerFacing().getOpposite()); + } + + @Nullable + @Override + public BlockEntity createBlockEntity(BlockPos pos, BlockState state) { + return new KeycardClonerBlockEntity(pos, state); + } +} diff --git a/src/main/java/com/andersmmg/lockandblock/block/custom/KeycardReaderBlock.java b/src/main/java/com/andersmmg/lockandblock/block/custom/KeycardReaderBlock.java index 96f1f6e..4ac00ed 100644 --- a/src/main/java/com/andersmmg/lockandblock/block/custom/KeycardReaderBlock.java +++ b/src/main/java/com/andersmmg/lockandblock/block/custom/KeycardReaderBlock.java @@ -1,13 +1,18 @@ package com.andersmmg.lockandblock.block.custom; +import com.andersmmg.lockandblock.LockAndBlock; import com.andersmmg.lockandblock.block.entity.KeycardReaderBlockEntity; import com.andersmmg.lockandblock.item.ModItems; +import com.andersmmg.lockandblock.item.custom.KeycardItem; +import com.andersmmg.lockandblock.sounds.ModSounds; import com.andersmmg.lockandblock.util.VoxelUtils; import net.minecraft.block.*; import net.minecraft.block.entity.BlockEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemPlacementContext; +import net.minecraft.item.ItemStack; import net.minecraft.server.world.ServerWorld; +import net.minecraft.sound.SoundCategory; import net.minecraft.state.StateManager; import net.minecraft.state.property.BooleanProperty; import net.minecraft.state.property.DirectionProperty; @@ -36,22 +41,68 @@ public KeycardReaderBlock(Settings settings) { this.setDefaultState(this.stateManager.getDefaultState().with(FACING, Direction.NORTH).with(POWERED, false)); } + protected static Direction getDirection(BlockState state) { + return state.get(FACING); + } + @Override public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) { - if (player.getStackInHand(hand).isOf(ModItems.KEYCARD)) { - if (!state.get(POWERED)) { - if (!world.isClient()) { - world.setBlockState(pos, state.with(POWERED, true), 3); - world.scheduleBlockTick(pos, this, 20, TickPriority.NORMAL); - this.updateNeighbors(state, (ServerWorld) world, pos); + ItemStack stack = player.getStackInHand(hand); + if (state.get(POWERED)) { + return ActionResult.FAIL; + } + if (stack.isOf(ModItems.KEYCARD)) { + BlockEntity blockEntity = world.getBlockEntity(pos); + if (blockEntity instanceof KeycardReaderBlockEntity keycardReaderBlockEntity) { + if (keycardReaderBlockEntity.hasUuid()) { + if (KeycardItem.hasUuid(stack)) { + if (keycardReaderBlockEntity.getUuid().equals(KeycardItem.getUuid(stack))) { + return this.activate(state, world, pos); + } else { + if (!world.isClient) { + world.playSound(null, pos, ModSounds.BEEP_ERROR, SoundCategory.BLOCKS, 1.0F, 1.0F); + player.sendMessage(LockAndBlock.langText("wrong_keycard"), true); + } + } + } else { + if (!world.isClient) { + world.playSound(null, pos, ModSounds.BEEP_ERROR, SoundCategory.BLOCKS, 1.0F, 1.0F); + player.sendMessage(LockAndBlock.langText("blank_keycard"), true); + } + } + } else { + if (KeycardItem.hasUuid(stack)) { + if (!world.isClient) { + keycardReaderBlockEntity.setUuid(KeycardItem.getUuid(stack)); + player.sendMessage(LockAndBlock.langText("reader_programmed"), true); + return this.activate(state, world, pos); + } + } else { + if (!world.isClient) { + world.playSound(null, pos, ModSounds.BEEP_ERROR, SoundCategory.BLOCKS, 1.0F, 1.0F); + player.sendMessage(LockAndBlock.langText("blank_keycard"), true); + } + } } - return ActionResult.SUCCESS; } return ActionResult.SUCCESS; } return ActionResult.FAIL; } + private ActionResult activate(BlockState state, World world, BlockPos pos) { + if (!state.get(POWERED)) { + if (!world.isClient()) { + world.playSound(null, pos, ModSounds.BEEP_SUCCESS, SoundCategory.BLOCKS, 1.0F, 1.0F); + world.setBlockState(pos, state.with(POWERED, true), 3); + world.scheduleBlockTick(pos, this, 20, TickPriority.NORMAL); + this.updateNeighbors(state, (ServerWorld) world, pos); + } + return ActionResult.SUCCESS; + } + return ActionResult.CONSUME; + } + private void updateNeighbors(BlockState state, ServerWorld world, BlockPos pos) { world.updateNeighborsAlways(pos, this); world.updateNeighborsAlways(pos.offset(getDirection(state).getOpposite()), this); @@ -69,10 +120,6 @@ public boolean emitsRedstonePower(BlockState state) { return true; } - protected static Direction getDirection(BlockState state) { - return state.get(FACING); - } - @Override public BlockRenderType getRenderType(BlockState state) { return BlockRenderType.MODEL; diff --git a/src/main/java/com/andersmmg/lockandblock/block/custom/KeycardWriterBlock.java b/src/main/java/com/andersmmg/lockandblock/block/custom/KeycardWriterBlock.java new file mode 100644 index 0000000..e81bd7c --- /dev/null +++ b/src/main/java/com/andersmmg/lockandblock/block/custom/KeycardWriterBlock.java @@ -0,0 +1,99 @@ +package com.andersmmg.lockandblock.block.custom; + +import com.andersmmg.lockandblock.LockAndBlock; +import com.andersmmg.lockandblock.item.ModItems; +import com.andersmmg.lockandblock.item.custom.KeycardItem; +import com.andersmmg.lockandblock.util.VoxelUtils; +import net.minecraft.block.Block; +import net.minecraft.block.BlockRenderType; +import net.minecraft.block.BlockState; +import net.minecraft.block.ShapeContext; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemPlacementContext; +import net.minecraft.item.ItemStack; +import net.minecraft.state.StateManager; +import net.minecraft.state.property.DirectionProperty; +import net.minecraft.state.property.Properties; +import net.minecraft.util.ActionResult; +import net.minecraft.util.BlockMirror; +import net.minecraft.util.BlockRotation; +import net.minecraft.util.Hand; +import net.minecraft.util.function.BooleanBiFunction; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.util.shape.VoxelShapes; +import net.minecraft.world.BlockView; +import net.minecraft.world.World; + +import java.util.stream.Stream; + +public class KeycardWriterBlock extends Block { + public static final DirectionProperty FACING = Properties.HORIZONTAL_FACING; + private static final VoxelShape VOXEL_SHAPE = Stream.of( + Block.createCuboidShape(3, 0, 3, 13, 2, 13), + Block.createCuboidShape(4, 6, 11, 12, 10, 12), + Block.createCuboidShape(7, 2, 11, 9, 6, 12), + Block.createCuboidShape(3, 2, 8, 13, 4, 9), + Block.createCuboidShape(3, 2, 6, 13, 4, 7) + ).reduce((v1, v2) -> VoxelShapes.combineAndSimplify(v1, v2, BooleanBiFunction.OR)).get(); + ; + + public KeycardWriterBlock(Settings settings) { + super(settings); + this.setDefaultState(this.stateManager.getDefaultState().with(FACING, Direction.NORTH)); + } + + @Override + public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) { + ItemStack stack = player.getStackInHand(hand); + if (stack.isOf(ModItems.KEYCARD)) { + if (!world.isClient) { + if (KeycardItem.hasUuid(stack)) { + player.sendMessage(LockAndBlock.langText("card_not_blank"), true); + } else { + String new_uuid = java.util.UUID.randomUUID().toString(); + KeycardItem.setUuid(new_uuid, stack); + player.sendMessage(LockAndBlock.langText("card_written"), true); + } + } + return ActionResult.SUCCESS; + } + return ActionResult.FAIL; + } + + protected static Direction getDirection(BlockState state) { + return state.get(FACING); + } + + @Override + public BlockRenderType getRenderType(BlockState state) { + return BlockRenderType.MODEL; + } + + @Override + public BlockState rotate(BlockState state, BlockRotation rotation) { + return state.with(FACING, rotation.rotate(state.get(FACING))); + } + + @Override + public BlockState mirror(BlockState state, BlockMirror mirror) { + return state.rotate(mirror.getRotation(state.get(FACING))); + } + + @Override + public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) { + return VoxelUtils.rotateShape(getDirection(state), VOXEL_SHAPE); + } + + @Override + protected void appendProperties(StateManager.Builder builder) { + builder.add(FACING); + } + + @Override + public BlockState getPlacementState(ItemPlacementContext ctx) { + return this.getDefaultState().with(FACING, ctx.getHorizontalPlayerFacing().getOpposite()); + } +} diff --git a/src/main/java/com/andersmmg/lockandblock/block/entity/KeycardClonerBlockEntity.java b/src/main/java/com/andersmmg/lockandblock/block/entity/KeycardClonerBlockEntity.java new file mode 100644 index 0000000..40494f6 --- /dev/null +++ b/src/main/java/com/andersmmg/lockandblock/block/entity/KeycardClonerBlockEntity.java @@ -0,0 +1,43 @@ +package com.andersmmg.lockandblock.block.entity; + +import com.andersmmg.lockandblock.LockAndBlock; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.util.math.BlockPos; + +public class KeycardClonerBlockEntity extends BlockEntity { + private String uuid = ""; + + public KeycardClonerBlockEntity(BlockPos pos, BlockState state) { + super(ModBlockEntities.KEYCARD_CLONER_BLOCK_ENTITY, pos, state); + } + + @Override + public void writeNbt(NbtCompound nbt) { + super.writeNbt(nbt); + nbt.putString(LockAndBlock.CARD_UUID_KEY, uuid); + } + + @Override + public void readNbt(NbtCompound nbt) { + super.readNbt(nbt); + uuid = nbt.getString(LockAndBlock.CARD_UUID_KEY); + } + + public boolean hasUuid() { + return !uuid.isEmpty(); + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public void clearUuid() { + this.uuid = ""; + } +} diff --git a/src/main/java/com/andersmmg/lockandblock/block/entity/KeycardReaderBlockEntity.java b/src/main/java/com/andersmmg/lockandblock/block/entity/KeycardReaderBlockEntity.java index 40f8fb5..d4ccc38 100644 --- a/src/main/java/com/andersmmg/lockandblock/block/entity/KeycardReaderBlockEntity.java +++ b/src/main/java/com/andersmmg/lockandblock/block/entity/KeycardReaderBlockEntity.java @@ -1,11 +1,39 @@ package com.andersmmg.lockandblock.block.entity; +import com.andersmmg.lockandblock.LockAndBlock; import net.minecraft.block.BlockState; import net.minecraft.block.entity.BlockEntity; +import net.minecraft.nbt.NbtCompound; import net.minecraft.util.math.BlockPos; public class KeycardReaderBlockEntity extends BlockEntity { + private String uuid = ""; + public KeycardReaderBlockEntity(BlockPos pos, BlockState state) { super(ModBlockEntities.KEYCARD_READER_BLOCK_ENTITY, pos, state); } + + @Override + public void writeNbt(NbtCompound nbt) { + super.writeNbt(nbt); + nbt.putString(LockAndBlock.CARD_UUID_KEY, uuid); + } + + @Override + public void readNbt(NbtCompound nbt) { + super.readNbt(nbt); + uuid = nbt.getString(LockAndBlock.CARD_UUID_KEY); + } + + public boolean hasUuid() { + return !uuid.isEmpty(); + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } } diff --git a/src/main/java/com/andersmmg/lockandblock/block/entity/ModBlockEntities.java b/src/main/java/com/andersmmg/lockandblock/block/entity/ModBlockEntities.java index e80a02e..0fadd89 100644 --- a/src/main/java/com/andersmmg/lockandblock/block/entity/ModBlockEntities.java +++ b/src/main/java/com/andersmmg/lockandblock/block/entity/ModBlockEntities.java @@ -12,6 +12,10 @@ public class ModBlockEntities { Registry.register(Registries.BLOCK_ENTITY_TYPE, LockAndBlock.id("keycard_reader_be"), FabricBlockEntityTypeBuilder.create(KeycardReaderBlockEntity::new, ModBlocks.KEYCARD_READER).build()); + public static final BlockEntityType KEYCARD_CLONER_BLOCK_ENTITY = + Registry.register(Registries.BLOCK_ENTITY_TYPE, LockAndBlock.id("keycard_cloner_be"), + FabricBlockEntityTypeBuilder.create(KeycardClonerBlockEntity::new, + ModBlocks.KEYCARD_CLONER).build()); public static void registerBlockEntities() { LockAndBlock.LOGGER.info("Registering Block Entities for " + LockAndBlock.MOD_ID); diff --git a/src/main/java/com/andersmmg/lockandblock/datagen/ModModelProvider.java b/src/main/java/com/andersmmg/lockandblock/datagen/ModModelProvider.java index 403cbca..91335cf 100644 --- a/src/main/java/com/andersmmg/lockandblock/datagen/ModModelProvider.java +++ b/src/main/java/com/andersmmg/lockandblock/datagen/ModModelProvider.java @@ -17,6 +17,8 @@ public ModModelProvider(FabricDataOutput output) { @Override public void generateBlockStateModels(BlockStateModelGenerator blockStateModelGenerator) { registerActivatable(blockStateModelGenerator, ModBlocks.KEYCARD_READER); + registerRotatable(blockStateModelGenerator, ModBlocks.KEYCARD_WRITER); + registerRotatable(blockStateModelGenerator, ModBlocks.KEYCARD_CLONER); } @Override @@ -24,6 +26,12 @@ public void generateItemModels(ItemModelGenerator itemModelGenerator) { itemModelGenerator.register(ModItems.KEYCARD, Models.GENERATED); } + private void registerRotatable(BlockStateModelGenerator blockStateModelGenerator, Block block) { + Identifier model_base = ModelIds.getBlockModelId(block); + blockStateModelGenerator.blockStateCollector.accept(VariantsBlockStateSupplier.create(block, BlockStateVariant.create().put(VariantSettings.MODEL, model_base)) + .coordinate(BlockStateModelGenerator.createNorthDefaultHorizontalRotationStates())); + } + private void registerActivatable(BlockStateModelGenerator blockStateModelGenerator, Block block) { Identifier model_base = ModelIds.getBlockModelId(block); Identifier model_open = ModelIds.getBlockSubModelId(block, "_active"); diff --git a/src/main/java/com/andersmmg/lockandblock/item/ModItems.java b/src/main/java/com/andersmmg/lockandblock/item/ModItems.java index 8d74cb7..31148cc 100644 --- a/src/main/java/com/andersmmg/lockandblock/item/ModItems.java +++ b/src/main/java/com/andersmmg/lockandblock/item/ModItems.java @@ -8,7 +8,7 @@ import net.minecraft.registry.Registry; public class ModItems { - public static final Item KEYCARD = registerItem("keycard", new KeycardItem(new OwoItemSettings().group(ModItemGroups.LOCKBLOCK_GROUP))); + public static final Item KEYCARD = registerItem("keycard", new KeycardItem(new OwoItemSettings().maxCount(1).group(ModItemGroups.LOCKBLOCK_GROUP))); private static Item registerItem(String name, Item item) { return Registry.register(Registries.ITEM, LockAndBlock.id(name), item); diff --git a/src/main/java/com/andersmmg/lockandblock/item/custom/KeycardItem.java b/src/main/java/com/andersmmg/lockandblock/item/custom/KeycardItem.java index c27bc89..8126bb2 100644 --- a/src/main/java/com/andersmmg/lockandblock/item/custom/KeycardItem.java +++ b/src/main/java/com/andersmmg/lockandblock/item/custom/KeycardItem.java @@ -1,9 +1,40 @@ package com.andersmmg.lockandblock.item.custom; +import com.andersmmg.lockandblock.LockAndBlock; +import net.minecraft.client.item.TooltipContext; import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; + +import java.util.List; public class KeycardItem extends Item { public KeycardItem(Settings settings) { super(settings); } + + public static boolean hasUuid(ItemStack stack) { + return stack.hasNbt() && stack.getNbt().contains(LockAndBlock.CARD_UUID_KEY); + } + + public static String getUuid(ItemStack stack) { + return stack.getNbt().getString(LockAndBlock.CARD_UUID_KEY); + } + + public static void setUuid(String uuid, ItemStack stack) { + stack.getOrCreateNbt().putString(LockAndBlock.CARD_UUID_KEY, uuid); + } + + @Override + public void appendTooltip(ItemStack stack, @Nullable World world, List tooltip, TooltipContext context) { + super.appendTooltip(stack, world, tooltip, context); + if (hasUuid(stack)) { + tooltip.add(LockAndBlock.langText("keycard.written").formatted(Formatting.GOLD)); + return; + } + tooltip.add(LockAndBlock.langText("keycard.blank").formatted(Formatting.ITALIC, Formatting.GRAY)); + } } diff --git a/src/main/java/com/andersmmg/lockandblock/sounds/ModSounds.java b/src/main/java/com/andersmmg/lockandblock/sounds/ModSounds.java new file mode 100644 index 0000000..a480a48 --- /dev/null +++ b/src/main/java/com/andersmmg/lockandblock/sounds/ModSounds.java @@ -0,0 +1,21 @@ +package com.andersmmg.lockandblock.sounds; + +import com.andersmmg.lockandblock.LockAndBlock; +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; +import net.minecraft.sound.SoundEvent; +import net.minecraft.util.Identifier; + +public class ModSounds { + public static final SoundEvent BEEP_SUCCESS = registerSoundEvent("beep_success"); + public static final SoundEvent BEEP_ERROR = registerSoundEvent("beep_error"); + + private static SoundEvent registerSoundEvent(String name) { + Identifier id = LockAndBlock.id(name); + return Registry.register(Registries.SOUND_EVENT, id, SoundEvent.of(id)); + } + + public static void registerSounds() { + LockAndBlock.LOGGER.info("Registering Mod Sounds for " + LockAndBlock.MOD_ID); + } +} \ No newline at end of file diff --git a/src/main/resources/assets/lockandblock/lang/en_us.json b/src/main/resources/assets/lockandblock/lang/en_us.json index c1f7088..d9c5ce2 100644 --- a/src/main/resources/assets/lockandblock/lang/en_us.json +++ b/src/main/resources/assets/lockandblock/lang/en_us.json @@ -1,5 +1,18 @@ { "item.lockandblock.keycard": "Keycard", "block.lockandblock.keycard_reader": "Keycard Reader", - "itemGroup.lockandblock.item_group": "Lock & Block" + "block.lockandblock.keycard_writer": "Keycard Writer", + "itemGroup.lockandblock.item_group": "Lock & Block", + "text.lockandblock.wrong_keycard": "Access denied.", + "text.lockandblock.blank_keycard": "Blank keycard detected.", + "text.lockandblock.reader_programmed": "Keycard reader programmed", + "text.lockandblock.card_not_blank": "Not a blank keycard.", + "text.lockandblock.card_written": "Keycard programmed.", + "text.lockandblock.card_copied": "Keycard copied.", + "text.lockandblock.keycard.blank": "Blank", + "text.lockandblock.keycard.written": "Programmed", + "text.lockandblock.card_saved": "Card saved, ready to clone.", + "text.lockandblock.card_cleared": "Card cleared.", + "sounds.lockandblock.beep_success": "Successful beep", + "sounds.lockandblock.beep_error": "Error beep" } \ No newline at end of file diff --git a/src/main/resources/assets/lockandblock/models/block/keycard_cloner.json b/src/main/resources/assets/lockandblock/models/block/keycard_cloner.json new file mode 100644 index 0000000..227b421 --- /dev/null +++ b/src/main/resources/assets/lockandblock/models/block/keycard_cloner.json @@ -0,0 +1,365 @@ +{ + "texture_size": [ + 32, + 32 + ], + "textures": { + "0": "lockandblock:block/keycard_cloner", + "particle": "lockandblock:block/keycard_cloner" + }, + "elements": [ + { + "from": [ + 3, + 0, + 3 + ], + "to": [ + 13, + 2, + 13 + ], + "rotation": { + "angle": 0, + "axis": "y", + "origin": [ + 7, + 0, + 7 + ] + }, + "faces": { + "north": { + "uv": [ + 5, + 0, + 10, + 1 + ], + "texture": "#0" + }, + "east": { + "uv": [ + 5, + 1, + 10, + 2 + ], + "texture": "#0" + }, + "south": { + "uv": [ + 5, + 2, + 10, + 3 + ], + "texture": "#0" + }, + "west": { + "uv": [ + 5, + 3, + 10, + 4 + ], + "texture": "#0" + }, + "up": { + "uv": [ + 5, + 5, + 0, + 0 + ], + "texture": "#0" + }, + "down": { + "uv": [ + 5, + 5, + 0, + 10 + ], + "texture": "#0" + } + } + }, + { + "from": [ + 3, + 2, + 3 + ], + "to": [ + 4, + 4, + 13 + ], + "rotation": { + "angle": 0, + "axis": "y", + "origin": [ + 7, + 2, + 7 + ] + }, + "faces": { + "north": { + "uv": [ + 7, + 8, + 7.5, + 9 + ], + "texture": "#0" + }, + "east": { + "uv": [ + 5, + 4, + 10, + 5 + ], + "texture": "#0" + }, + "south": { + "uv": [ + 7.5, + 8, + 8, + 9 + ], + "texture": "#0" + }, + "west": { + "uv": [ + 5, + 5, + 10, + 6 + ], + "texture": "#0" + }, + "up": { + "uv": [ + 5.5, + 13, + 5, + 8 + ], + "texture": "#0" + }, + "down": { + "uv": [ + 6, + 8, + 5.5, + 13 + ], + "texture": "#0" + } + } + }, + { + "from": [ + 5, + 2, + 3 + ], + "to": [ + 6, + 4, + 13 + ], + "rotation": { + "angle": 0, + "axis": "y", + "origin": [ + 9, + 2, + 7 + ] + }, + "faces": { + "north": { + "uv": [ + 8, + 8, + 8.5, + 9 + ], + "texture": "#0" + }, + "east": { + "uv": [ + 5, + 6, + 10, + 7 + ], + "texture": "#0" + }, + "south": { + "uv": [ + 8.5, + 8, + 9, + 9 + ], + "texture": "#0" + }, + "west": { + "uv": [ + 5, + 7, + 10, + 8 + ], + "texture": "#0" + }, + "up": { + "uv": [ + 6.5, + 13, + 6, + 8 + ], + "texture": "#0" + }, + "down": { + "uv": [ + 7, + 8, + 6.5, + 13 + ], + "texture": "#0" + } + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [ + 75, + 45, + 0 + ], + "translation": [ + 0, + 2.5, + 0 + ], + "scale": [ + 0.375, + 0.375, + 0.375 + ] + }, + "thirdperson_lefthand": { + "rotation": [ + 75, + 45, + 0 + ], + "translation": [ + 0, + 2.5, + 0 + ], + "scale": [ + 0.375, + 0.375, + 0.375 + ] + }, + "firstperson_righthand": { + "rotation": [ + 0, + 45, + 0 + ], + "translation": [ + 2.25, + 3.5, + 0 + ], + "scale": [ + 0.4, + 0.4, + 0.4 + ] + }, + "firstperson_lefthand": { + "rotation": [ + 0, + 45, + 0 + ], + "translation": [ + 2.25, + 3.5, + 0 + ], + "scale": [ + 0.4, + 0.4, + 0.4 + ] + }, + "ground": { + "translation": [ + 0, + 3, + 0 + ], + "scale": [ + 0.25, + 0.25, + 0.25 + ] + }, + "gui": { + "rotation": [ + 30, + 225, + 0 + ], + "translation": [ + 0, + 5, + 0 + ], + "scale": [ + 0.91, + 0.91, + 0.91 + ] + }, + "fixed": { + "scale": [ + 0.5, + 0.5, + 0.5 + ] + } + }, + "groups": [ + { + "name": "VoxelShapes", + "origin": [ + 9, + 2, + 7 + ], + "color": 0, + "children": [ + 0, + 1, + 2 + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/lockandblock/models/block/keycard_writer.json b/src/main/resources/assets/lockandblock/models/block/keycard_writer.json new file mode 100644 index 0000000..f44f6b6 --- /dev/null +++ b/src/main/resources/assets/lockandblock/models/block/keycard_writer.json @@ -0,0 +1,526 @@ +{ + "texture_size": [ + 32, + 32 + ], + "textures": { + "0": "lockandblock:block/keycard_writer", + "particle": "lockandblock:block/keycard_writer" + }, + "elements": [ + { + "from": [ + 3, + 0, + 3 + ], + "to": [ + 13, + 2, + 13 + ], + "rotation": { + "angle": 0, + "axis": "x", + "origin": [ + 7, + 0, + 7 + ] + }, + "faces": { + "north": { + "uv": [ + 5, + 4, + 10, + 5 + ], + "texture": "#0" + }, + "east": { + "uv": [ + 5, + 5, + 10, + 6 + ], + "texture": "#0" + }, + "south": { + "uv": [ + 5, + 6, + 10, + 7 + ], + "texture": "#0" + }, + "west": { + "uv": [ + 5, + 7, + 10, + 8 + ], + "texture": "#0" + }, + "up": { + "uv": [ + 5, + 5, + 0, + 0 + ], + "texture": "#0" + }, + "down": { + "uv": [ + 5, + 5, + 0, + 10 + ], + "texture": "#0" + } + } + }, + { + "from": [ + 4, + 6, + 11 + ], + "to": [ + 12, + 10, + 12 + ], + "rotation": { + "angle": 0, + "axis": "x", + "origin": [ + 7, + 4, + 7 + ] + }, + "faces": { + "north": { + "uv": [ + 5, + 0, + 9, + 2 + ], + "texture": "#0" + }, + "east": { + "uv": [ + 8, + 10, + 8.5, + 12 + ], + "texture": "#0" + }, + "south": { + "uv": [ + 5, + 2, + 9, + 4 + ], + "texture": "#0" + }, + "west": { + "uv": [ + 10, + 8, + 10.5, + 10 + ], + "texture": "#0" + }, + "up": { + "uv": [ + 4, + 10.5, + 0, + 10 + ], + "texture": "#0" + }, + "down": { + "uv": [ + 8, + 10, + 4, + 10.5 + ], + "texture": "#0" + } + } + }, + { + "from": [ + 7, + 2, + 11 + ], + "to": [ + 9, + 6, + 12 + ], + "rotation": { + "angle": 0, + "axis": "x", + "origin": [ + 7, + 2, + 7 + ] + }, + "faces": { + "north": { + "uv": [ + 10, + 4, + 11, + 6 + ], + "texture": "#0" + }, + "east": { + "uv": [ + 8.5, + 10, + 9, + 12 + ], + "texture": "#0" + }, + "south": { + "uv": [ + 10, + 6, + 11, + 8 + ], + "texture": "#0" + }, + "west": { + "uv": [ + 9, + 10, + 9.5, + 12 + ], + "texture": "#0" + }, + "up": { + "uv": [ + 10.5, + 10.5, + 9.5, + 10 + ], + "texture": "#0" + }, + "down": { + "uv": [ + 1, + 10.5, + 0, + 11 + ], + "texture": "#0" + } + } + }, + { + "from": [ + 3, + 2, + 8 + ], + "to": [ + 13, + 4, + 9 + ], + "rotation": { + "angle": 0, + "axis": "y", + "origin": [ + 7, + 2, + 5 + ] + }, + "faces": { + "north": { + "uv": [ + 5, + 8, + 10, + 9 + ], + "texture": "#0" + }, + "east": { + "uv": [ + 1, + 10.5, + 1.5, + 11.5 + ], + "texture": "#0" + }, + "south": { + "uv": [ + 9, + 0, + 14, + 1 + ], + "texture": "#0" + }, + "west": { + "uv": [ + 1.5, + 10.5, + 2, + 11.5 + ], + "texture": "#0" + }, + "up": { + "uv": [ + 14, + 3.5, + 9, + 3 + ], + "texture": "#0" + }, + "down": { + "uv": [ + 14, + 3.5, + 9, + 4 + ], + "texture": "#0" + } + } + }, + { + "from": [ + 3, + 2, + 6 + ], + "to": [ + 13, + 4, + 7 + ], + "rotation": { + "angle": 0, + "axis": "y", + "origin": [ + 7, + 2, + 3 + ] + }, + "faces": { + "north": { + "uv": [ + 9, + 1, + 14, + 2 + ], + "texture": "#0" + }, + "east": { + "uv": [ + 2, + 10.5, + 2.5, + 11.5 + ], + "texture": "#0" + }, + "south": { + "uv": [ + 9, + 2, + 14, + 3 + ], + "texture": "#0" + }, + "west": { + "uv": [ + 2.5, + 10.5, + 3, + 11.5 + ], + "texture": "#0" + }, + "up": { + "uv": [ + 10, + 9.5, + 5, + 9 + ], + "texture": "#0" + }, + "down": { + "uv": [ + 10, + 9.5, + 5, + 10 + ], + "texture": "#0" + } + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [ + 75, + 45, + 0 + ], + "translation": [ + 0, + 2.5, + 0 + ], + "scale": [ + 0.375, + 0.375, + 0.375 + ] + }, + "thirdperson_lefthand": { + "rotation": [ + 75, + 45, + 0 + ], + "translation": [ + 0, + 2.5, + 0 + ], + "scale": [ + 0.375, + 0.375, + 0.375 + ] + }, + "firstperson_righthand": { + "rotation": [ + 0, + 45, + 0 + ], + "translation": [ + 2.25, + 3.5, + 0 + ], + "scale": [ + 0.4, + 0.4, + 0.4 + ] + }, + "firstperson_lefthand": { + "rotation": [ + 0, + 45, + 0 + ], + "translation": [ + 2.25, + 3.5, + 0 + ], + "scale": [ + 0.4, + 0.4, + 0.4 + ] + }, + "ground": { + "translation": [ + 0, + 3, + 0 + ], + "scale": [ + 0.25, + 0.25, + 0.25 + ] + }, + "gui": { + "rotation": [ + 30, + 225, + 0 + ], + "translation": [ + 0, + 2.75, + 0 + ], + "scale": [ + 0.91, + 0.91, + 0.91 + ] + }, + "fixed": { + "translation": [ + 0, + 2, + -1 + ], + "scale": [ + 0.5, + 0.5, + 0.5 + ] + } + }, + "groups": [ + { + "name": "VoxelShapes", + "origin": [ + 9, + 2, + 7 + ], + "color": 0, + "children": [ + 0, + 1, + 2, + 3, + 4 + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/lockandblock/sounds.json b/src/main/resources/assets/lockandblock/sounds.json new file mode 100644 index 0000000..89d692e --- /dev/null +++ b/src/main/resources/assets/lockandblock/sounds.json @@ -0,0 +1,14 @@ +{ + "beep_success": { + "subtitle": "sounds.lockandblock.beep_success", + "sounds": [ + "lockandblock:beep_success" + ] + }, + "beep_error": { + "subtitle": "sounds.lockandblock.beep_error", + "sounds": [ + "lockandblock:beep_error" + ] + } +} \ No newline at end of file diff --git a/src/main/resources/assets/lockandblock/sounds/beep_error.ogg b/src/main/resources/assets/lockandblock/sounds/beep_error.ogg new file mode 100644 index 0000000000000000000000000000000000000000..1cf0a5c95d729851e228474693635f8bffb4d26c GIT binary patch literal 11641 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%0Dc^4!$X185QU(lfR54h% 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 MO26;H8QEC&FX~xjJpcdz literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/lockandblock/textures/block/keycard_cloner.png b/src/main/resources/assets/lockandblock/textures/block/keycard_cloner.png new file mode 100644 index 0000000000000000000000000000000000000000..9ade3d3690854405bc4007077da2df666fd8a30b GIT binary patch literal 374 zcmV-+0g3*JP)3`-c@zsBM7t;sogAEci_l--N^M?*iY&%JIP;2LMJykn`74k%(%u zsV1_0PgfxAUG7#p0Dkrl+f4*UsW1Te7>-NDv*}2CkSPoRHfjAHuqu$Pvf9Sg%fz0* zb#iZIxRSvBKU{{>n_H~Y1VRXh4pqzjZ z0`HwZ&cBx1XOSQXwC(}g0#%yY6UG8z7#gj(O8a|2^$sVc%xws?o`4f@0uG1c7ontH UV_NmN=Kufz07*qoM6N<$f{10LD*ylh literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/lockandblock/textures/block/keycard_writer.png b/src/main/resources/assets/lockandblock/textures/block/keycard_writer.png new file mode 100644 index 0000000000000000000000000000000000000000..fc83ebbf4662a9ae7cc0c4a68cab7756c1cc9dbb GIT binary patch literal 439 zcmV;o0Z9IdP)C0>0KQAvyt@A5CL5{^j0RR}|c{7GzGjLwCWhkX8`*eKY zxAN~y51s|iw*i224u5=9I;b~&fKrMiK&K#pFquZ5I@Mce? zzHMg@0IYPTB}geDWpxIkC~7C*?e3Q3XxnzH0JQ>kk56fyA1}sk$8ku*gke||P*npu zbqh6(lNyh5$Tct@2xGrBNS`C?8vqalfo1$46F`dIfTr7#y}v$_9EW13r6e<38yU3$ zYhQw$ba3|H!fi hsMlvtPfyRf`2leOZWdF~H0=NY002ovPDHLkV1j%ayd3}l literal 0 HcmV?d00001