From 408c9d11c0c7806a90cdef8da641029db5b5639d Mon Sep 17 00:00:00 2001 From: Sollace Date: Thu, 11 Apr 2024 19:37:25 +0100 Subject: [PATCH] Crystal doors can now be locked by providing them a signed bangle of comradery. When locked they will only let the owner or players wearing a signed bangle through. Also you can't cheat them --- .../unicopia/block/CrystalDoorBlock.java | 144 ++++++++++++++++-- .../unicopia/block/UBlockEntities.java | 1 + .../providers/UBlockStateModelGenerator.java | 50 ++++-- .../unicopia/item/FriendshipBraceletItem.java | 4 + .../block/crystal_door_bottom_locked.png | Bin 0 -> 6962 bytes .../block/crystal_door_top_locked.png | Bin 0 -> 10942 bytes .../block/crystal_door_top_locked.png.mcmeta | 11 ++ 7 files changed, 182 insertions(+), 28 deletions(-) create mode 100644 src/main/resources/assets/unicopia/textures/block/crystal_door_bottom_locked.png create mode 100644 src/main/resources/assets/unicopia/textures/block/crystal_door_top_locked.png create mode 100644 src/main/resources/assets/unicopia/textures/block/crystal_door_top_locked.png.mcmeta diff --git a/src/main/java/com/minelittlepony/unicopia/block/CrystalDoorBlock.java b/src/main/java/com/minelittlepony/unicopia/block/CrystalDoorBlock.java index c7690aa1..5e8cbf25 100644 --- a/src/main/java/com/minelittlepony/unicopia/block/CrystalDoorBlock.java +++ b/src/main/java/com/minelittlepony/unicopia/block/CrystalDoorBlock.java @@ -1,64 +1,182 @@ package com.minelittlepony.unicopia.block; +import java.util.UUID; + import org.jetbrains.annotations.Nullable; import com.minelittlepony.unicopia.EquineContext; import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.USounds; +import com.minelittlepony.unicopia.item.FriendshipBraceletItem; import com.minelittlepony.unicopia.item.UItems; import net.minecraft.block.Block; +import net.minecraft.block.BlockEntityProvider; import net.minecraft.block.BlockSetType; import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; import net.minecraft.block.DoorBlock; +import net.minecraft.block.entity.BlockEntity; import net.minecraft.block.enums.DoubleBlockHalf; import net.minecraft.entity.Entity; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +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.Properties; import net.minecraft.util.ActionResult; import net.minecraft.util.Hand; import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; +import net.minecraft.util.math.random.Random; import net.minecraft.world.World; import net.minecraft.world.event.GameEvent; -public class CrystalDoorBlock extends DoorBlock { +public class CrystalDoorBlock extends DoorBlock implements BlockEntityProvider { + public static final BooleanProperty LOCKED = Properties.LOCKED; public CrystalDoorBlock(Settings settings, BlockSetType blockSet) { super(settings, blockSet); + setDefaultState(getDefaultState().with(LOCKED, false)); + } + + @Override + protected void appendProperties(StateManager.Builder builder) { + super.appendProperties(builder); + builder.add(LOCKED); + } + + @Override + public boolean hasRandomTicks(BlockState state) { + return state.get(LOCKED); + } + + @Deprecated + @Override + public void randomTick(BlockState state, ServerWorld world, BlockPos pos, Random random) { + super.randomTick(state, world, pos, random); + if (!isLocked(world, pos)) { + setOnGuard(state, world, pos, false); + } } @Override public void neighborUpdate(BlockState state, World world, BlockPos pos, Block sourceBlock, BlockPos sourcePos, boolean notify) { - boolean powered = world.isReceivingRedstonePower(pos) || world.isReceivingRedstonePower(pos.offset(state.get(HALF) == DoubleBlockHalf.LOWER ? Direction.UP : Direction.DOWN)); - if (!getDefaultState().isOf(sourceBlock) && powered != state.get(POWERED)) { - if (powered) { - state = state.cycle(OPEN); - playOpenCloseSound(null, world, pos, state.get(OPEN)); - world.emitGameEvent(null, state.get(OPEN) ? GameEvent.BLOCK_OPEN : GameEvent.BLOCK_CLOSE, pos); - } + if (!state.get(LOCKED)) { + boolean powered = world.isReceivingRedstonePower(pos) || world.isReceivingRedstonePower(pos.offset(state.get(HALF) == DoubleBlockHalf.LOWER ? Direction.UP : Direction.DOWN)); + if (!getDefaultState().isOf(sourceBlock) && powered != state.get(POWERED)) { + if (powered) { + state = state.cycle(OPEN); + playOpenCloseSound(null, world, pos, state.get(OPEN)); + world.emitGameEvent(null, state.get(OPEN) ? GameEvent.BLOCK_OPEN : GameEvent.BLOCK_CLOSE, pos); + } - world.setBlockState(pos, state.with(POWERED, powered), Block.NOTIFY_LISTENERS); + world.setBlockState(pos, state.with(POWERED, powered), Block.NOTIFY_LISTENERS); + } + } + + if (state.get(HALF) == DoubleBlockHalf.LOWER && sourcePos.getY() == pos.getY() - 1) { + if (!canPlaceAt(state, world, pos) && world.isAir(sourcePos)) { + world.setBlockState(sourcePos, Blocks.DIRT.getDefaultState()); + } } } @Override public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) { - if (!EquineContext.of(player).getCompositeRace().any(Race::canCast)) { - - if (!player.getStackInHand(hand).isOf(UItems.MEADOWBROOKS_STAFF)) { + if (!shouldProvideAccess(world, pos, player)) { + if (isLocked(world, pos) || !player.getStackInHand(hand).isOf(UItems.MEADOWBROOKS_STAFF)) { playOpenCloseSound(player, world, pos, false); - return ActionResult.FAIL; + setOnGuard(state, world, pos, true); + return ActionResult.CONSUME; } else { world.playSound(player, pos, USounds.ENTITY_CRYSTAL_SHARDS_AMBIENT, SoundCategory.BLOCKS, 1, world.getRandom().nextFloat() * 0.1F + 0.9F); } + } else if (!isLocked(world, pos)) { + ItemStack heldStack = player.getStackInHand(hand); + if (heldStack.isOf(UItems.FRIENDSHIP_BRACELET)) { + @Nullable + UUID signator = FriendshipBraceletItem.getSignatorId(heldStack); + if (signator != null) { + BlockEntityUtil.getOrCreateBlockEntity(world, state.get(HALF) == DoubleBlockHalf.LOWER ? pos.up() : pos, UBlockEntities.CRYSTAL_DOOR).ifPresent(data -> { + data.setSignator(signator); + setOnGuard(state, world, pos, true); + world.playSound(player, pos, USounds.ENTITY_CRYSTAL_SHARDS_AMBIENT, SoundCategory.BLOCKS, 1, world.getRandom().nextFloat() * 0.1F + 0.9F); + }); + return ActionResult.SUCCESS; + } + } else { + setOnGuard(state, world, pos, false); + } } return super.onUse(state, world, pos, player, hand, hit); } + private void setOnGuard(BlockState state, World world, BlockPos pos, boolean locked) { + world.setBlockState(pos, state.with(LOCKED, locked)); + pos = pos.offset(state.get(HALF) == DoubleBlockHalf.LOWER ? Direction.UP : Direction.DOWN); + state = world.getBlockState(pos); + if (state.isOf(this)) { + world.setBlockState(pos, state.with(LOCKED, locked)); + } + } + + private boolean shouldProvideAccess(World world, BlockPos pos, PlayerEntity player) { + UUID signator = getSignator(world, pos); + if (signator != null) { + return signator.equals(player.getUuid()) || FriendshipBraceletItem.isComrade(signator, player); + } + return EquineContext.of(player).getCompositeRace().any(Race::canCast); + } + + private boolean isLocked(World world, BlockPos pos) { + return getSignator(world, pos) != null; + } + + @Nullable + private UUID getSignator(World world, BlockPos pos) { + pos = world.getBlockState(pos).get(HALF) == DoubleBlockHalf.LOWER ? pos.up() : pos; + var d = world.getBlockEntity(pos, UBlockEntities.CRYSTAL_DOOR); + return d.map(data -> data.signator).orElse(null); + } + private void playOpenCloseSound(@Nullable Entity entity, World world, BlockPos pos, boolean open) { world.playSound(entity, pos, open ? getBlockSetType().doorOpen() : getBlockSetType().doorClose(), SoundCategory.BLOCKS, 1, world.getRandom().nextFloat() * 0.1f + 0.9f); } + @Override + public BlockEntity createBlockEntity(BlockPos pos, BlockState state) { + return new TileData(pos, state); + } + + public static class TileData extends BlockEntity { + @Nullable + private UUID signator; + + public TileData(BlockPos pos, BlockState state) { + super(UBlockEntities.CRYSTAL_DOOR, pos, state); + } + + public void setSignator(UUID signator) { + this.signator = signator; + markDirty(); + } + + @Override + public void readNbt(NbtCompound nbt) { + signator = nbt.containsUuid("signator") ? nbt.getUuid("signator") : null; + } + + @Override + protected void writeNbt(NbtCompound nbt) { + if (signator != null) { + nbt.putUuid("signator", signator); + } + } + + } } diff --git a/src/main/java/com/minelittlepony/unicopia/block/UBlockEntities.java b/src/main/java/com/minelittlepony/unicopia/block/UBlockEntities.java index 87b25155..969a1e9e 100644 --- a/src/main/java/com/minelittlepony/unicopia/block/UBlockEntities.java +++ b/src/main/java/com/minelittlepony/unicopia/block/UBlockEntities.java @@ -16,6 +16,7 @@ public interface UBlockEntities { BlockEntityType CLOUD_CHEST = create("cloud_chest", BlockEntityType.Builder.create(CloudChestBlock.TileData::new, UBlocks.CLOUD_CHEST)); BlockEntityType HIVE_STORAGE = create("hive_storage", BlockEntityType.Builder.create(HiveBlock.TileData::new, UBlocks.HIVE)); BlockEntityType ITEM_JAR = create("item_jar", BlockEntityType.Builder.create(ItemJarBlock.TileData::new, UBlocks.JAR)); + BlockEntityType CRYSTAL_DOOR = create("crystal_door", BlockEntityType.Builder.create(CrystalDoorBlock.TileData::new, UBlocks.CRYSTAL_DOOR)); static BlockEntityType create(String id, Builder builder) { return Registry.register(Registries.BLOCK_ENTITY_TYPE, id, builder.build(null)); diff --git a/src/main/java/com/minelittlepony/unicopia/datagen/providers/UBlockStateModelGenerator.java b/src/main/java/com/minelittlepony/unicopia/datagen/providers/UBlockStateModelGenerator.java index 40fe766c..9f581503 100644 --- a/src/main/java/com/minelittlepony/unicopia/datagen/providers/UBlockStateModelGenerator.java +++ b/src/main/java/com/minelittlepony/unicopia/datagen/providers/UBlockStateModelGenerator.java @@ -34,6 +34,7 @@ import net.minecraft.data.client.Model; import net.minecraft.data.client.ModelIds; import net.minecraft.data.client.Models; import net.minecraft.data.client.MultipartBlockStateSupplier; +import net.minecraft.data.client.TextureKey; import net.minecraft.data.client.TextureMap; import net.minecraft.data.client.TexturedModel; import net.minecraft.data.client.VariantSettings; @@ -89,7 +90,8 @@ public class UBlockStateModelGenerator extends BlockStateModelGenerator { registerAll((g, block) -> g.registerParentedItemModel(block, ModelIds.getBlockModelId(block)), UBlocks.SHAPING_BENCH, UBlocks.SURFACE_CHITIN); registerAll(UBlockStateModelGenerator::registerSimpleState, UBlocks.SHAPING_BENCH, UBlocks.BANANAS); // doors - registerAll(UBlockStateModelGenerator::registerStableDoor, UBlocks.STABLE_DOOR, UBlocks.DARK_OAK_DOOR, UBlocks.CRYSTAL_DOOR, UBlocks.CLOUD_DOOR); + registerAll(UBlockStateModelGenerator::registerStableDoor, UBlocks.STABLE_DOOR, UBlocks.DARK_OAK_DOOR, UBlocks.CLOUD_DOOR); + registerLockingDoor(UBlocks.CRYSTAL_DOOR); // cloud blocks createCustomTexturePool(UBlocks.CLOUD, TexturedModel.CUBE_ALL).same(UBlocks.UNSTABLE_CLOUD).slab(UBlocks.CLOUD_SLAB).stairs(UBlocks.CLOUD_STAIRS); @@ -347,23 +349,37 @@ public class UBlockStateModelGenerator extends BlockStateModelGenerator { } public void registerStableDoor(Block door) { - TextureMap topTextures = TextureMap.topBottom(door); - TextureMap bottomTextures = topTextures.copyAndAdd(TOP, topTextures.getTexture(BOTTOM)); - registerItemModel(door.asItem()); var variants = BlockStateVariantMap.create(Properties.HORIZONTAL_FACING, Properties.DOUBLE_BLOCK_HALF, Properties.DOOR_HINGE, Properties.OPEN); - fillStableDoorVariantMap(variants, DoubleBlockHalf.LOWER, - BlockModels.DOOR_LEFT.upload(door, "_bottom_left", bottomTextures, modelCollector), - BlockModels.DOOR_RIGHT.upload(door, "_bottom_right", bottomTextures, modelCollector) - ); - fillStableDoorVariantMap(variants, DoubleBlockHalf.UPPER, - BlockModels.DOOR_LEFT.upload(door, "_top_left", topTextures, modelCollector), - BlockModels.DOOR_RIGHT.upload(door, "_top_right", topTextures, modelCollector) - ); + registerItemModel(door.asItem()); + buildDoorStateModels(door, "", variants::register); blockStateCollector.accept(VariantsBlockStateSupplier.create(door).coordinate(variants)); } - public static void fillStableDoorVariantMap( - BlockStateVariantMap.QuadrupleProperty variantMap, + public void registerLockingDoor(Block door) { + var variants = BlockStateVariantMap.create(Properties.HORIZONTAL_FACING, Properties.DOUBLE_BLOCK_HALF, Properties.DOOR_HINGE, Properties.OPEN, Properties.LOCKED); + registerItemModel(door.asItem()); + buildDoorStateModels(door, "", (facing, half, hinge, open, map) -> variants.register(facing, half, hinge, open, false, map)); + buildDoorStateModels(door, "_locked", (facing, half, hinge, open, map) -> variants.register(facing, half, hinge, open, true, map)); + blockStateCollector.accept(VariantsBlockStateSupplier.create(door).coordinate(variants)); + } + + private void buildDoorStateModels(Block door, String suffex, DoorStateConsumer variants) { + TextureMap topTextures = new TextureMap() + .put(TextureKey.TOP, TextureMap.getSubId(door, "_top" + suffex)) + .put(TextureKey.BOTTOM, TextureMap.getSubId(door, "_bottom" + suffex)); + TextureMap bottomTextures = topTextures.copyAndAdd(TOP, topTextures.getTexture(BOTTOM)); + fillStableDoorVariantMap(variants, DoubleBlockHalf.LOWER, + BlockModels.DOOR_LEFT.upload(door, "_bottom_left" + suffex, bottomTextures, modelCollector), + BlockModels.DOOR_RIGHT.upload(door, "_bottom_right" + suffex, bottomTextures, modelCollector) + ); + fillStableDoorVariantMap(variants, DoubleBlockHalf.UPPER, + BlockModels.DOOR_LEFT.upload(door, "_top_left" + suffex, topTextures, modelCollector), + BlockModels.DOOR_RIGHT.upload(door, "_top_right" + suffex, topTextures, modelCollector) + ); + } + + private static void fillStableDoorVariantMap( + DoorStateConsumer variantMap, DoubleBlockHalf targetHalf, Identifier leftModelId, Identifier rightModelId) { fillStableDoorVariantMap(variantMap, targetHalf, DoorHinge.LEFT, false, R0, leftModelId); fillStableDoorVariantMap(variantMap, targetHalf, DoorHinge.RIGHT, false, R0, rightModelId); @@ -373,7 +389,7 @@ public class UBlockStateModelGenerator extends BlockStateModelGenerator { } public static void fillStableDoorVariantMap( - BlockStateVariantMap.QuadrupleProperty variantMap, + DoorStateConsumer variantMap, DoubleBlockHalf targetHalf, DoorHinge hinge, boolean open, Rotation rotation, Identifier modelId) { @@ -386,6 +402,10 @@ public class UBlockStateModelGenerator extends BlockStateModelGenerator { } } + interface DoorStateConsumer { + void register(Direction direction, DoubleBlockHalf half, DoorHinge hinge, boolean open, BlockStateVariant variant); + } + public void registerPillar(Block pillar) { TextureMap textures = new TextureMap() .put(SIDE, ModelIds.getBlockSubModelId(pillar, "_side")) diff --git a/src/main/java/com/minelittlepony/unicopia/item/FriendshipBraceletItem.java b/src/main/java/com/minelittlepony/unicopia/item/FriendshipBraceletItem.java index 40e10dc4..1828f66d 100644 --- a/src/main/java/com/minelittlepony/unicopia/item/FriendshipBraceletItem.java +++ b/src/main/java/com/minelittlepony/unicopia/item/FriendshipBraceletItem.java @@ -124,6 +124,10 @@ public class FriendshipBraceletItem extends WearableItem implements DyeableItem, .isPresent(); } + public static boolean isComrade(UUID signator, Entity entity) { + return entity instanceof LivingEntity l && getWornBangles(l).anyMatch(stack -> isSignedBy(stack, signator)); + } + public static Stream getPartyMembers(Caster caster, double radius) { return Pony.stream(caster.findAllEntitiesInRange(radius, entity -> isComrade(caster, entity))); } diff --git a/src/main/resources/assets/unicopia/textures/block/crystal_door_bottom_locked.png b/src/main/resources/assets/unicopia/textures/block/crystal_door_bottom_locked.png new file mode 100644 index 0000000000000000000000000000000000000000..8836a4a5e65634e25c6cb4a4588e8680758ae9bd GIT binary patch literal 6962 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 j$Q?Xpp!;O>jVXbOJKb*-a6