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 00000000..8836a4a5 Binary files /dev/null and b/src/main/resources/assets/unicopia/textures/block/crystal_door_bottom_locked.png differ diff --git a/src/main/resources/assets/unicopia/textures/block/crystal_door_top_locked.png b/src/main/resources/assets/unicopia/textures/block/crystal_door_top_locked.png new file mode 100644 index 00000000..c89965fb Binary files /dev/null and b/src/main/resources/assets/unicopia/textures/block/crystal_door_top_locked.png differ diff --git a/src/main/resources/assets/unicopia/textures/block/crystal_door_top_locked.png.mcmeta b/src/main/resources/assets/unicopia/textures/block/crystal_door_top_locked.png.mcmeta new file mode 100644 index 00000000..66de2f42 --- /dev/null +++ b/src/main/resources/assets/unicopia/textures/block/crystal_door_top_locked.png.mcmeta @@ -0,0 +1,11 @@ +{ + "animation": { + "frametime": 200, + "frames": [ + 0, 1, 2, 3, + 1, 2, 3, 0, + 2, 3, 0, 1, + 3, 0, 1, 2 + ] + } +}