diff --git a/src/main/generated/assets/playercollars/lang/en_us.json b/src/main/generated/assets/playercollars/lang/en_us.json index 7f131399..5f7331db 100644 --- a/src/main/generated/assets/playercollars/lang/en_us.json +++ b/src/main/generated/assets/playercollars/lang/en_us.json @@ -39,6 +39,7 @@ "block.playercollars.yellow_dog_bowl": "Yellow Dog Bowl", "enchantment.playercollars.clicker": "Audible", "enchantment.playercollars.clicker_walk": "Beckon", + "enchantment.playercollars.laser_reach": "Laser Reach", "enchantment.playercollars.regeneration": "Healing", "enchantment.playercollars.short_leash": "Tight Leash", "enchantment.playercollars.thorns": "Spiked", @@ -148,5 +149,8 @@ "message.playercollars.no_ride_entity": "Can't ride this entity!", "sound.playercollars.clicker_off": "Clicker clicks off", "sound.playercollars.clicker_on": "Clicker clicks on", - "tag.block.playercollars.paws_allow_interact": "Usable with Paws" + "tag.block.playercollars.paws_allow_interact": "Usable with Paws", + "item.playercollars.grooming_brush": "Magic Grooming Brush", + "item.playercollars.laser_pointer": "Laser Pointer", + "item.playercollars.reward_treat_pouch": "Reward Treat Pouch" } \ No newline at end of file diff --git a/src/main/generated/data/playercollars/advancement/recipes/tools/grooming_brush.json b/src/main/generated/data/playercollars/advancement/recipes/tools/grooming_brush.json new file mode 100644 index 00000000..fd8129b1 --- /dev/null +++ b/src/main/generated/data/playercollars/advancement/recipes/tools/grooming_brush.json @@ -0,0 +1,32 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "has_copper_ingot": { + "conditions": { + "items": [ + { + "items": "#c:ingots/copper" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_the_recipe": { + "conditions": { + "recipe": "playercollars:grooming_brush" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "has_the_recipe", + "has_copper_ingot" + ] + ], + "rewards": { + "recipes": [ + "playercollars:grooming_brush" + ] + } +} \ No newline at end of file diff --git a/src/main/generated/data/playercollars/advancement/recipes/tools/laser_pointer.json b/src/main/generated/data/playercollars/advancement/recipes/tools/laser_pointer.json new file mode 100644 index 00000000..a876c2f5 --- /dev/null +++ b/src/main/generated/data/playercollars/advancement/recipes/tools/laser_pointer.json @@ -0,0 +1,32 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "has_emerald": { + "conditions": { + "items": [ + { + "items": "minecraft:emerald" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_the_recipe": { + "conditions": { + "recipe": "playercollars:laser_pointer" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "has_the_recipe", + "has_emerald" + ] + ], + "rewards": { + "recipes": [ + "playercollars:laser_pointer" + ] + } +} \ No newline at end of file diff --git a/src/main/generated/data/playercollars/recipe/grooming_brush.json b/src/main/generated/data/playercollars/recipe/grooming_brush.json new file mode 100644 index 00000000..15afa8c1 --- /dev/null +++ b/src/main/generated/data/playercollars/recipe/grooming_brush.json @@ -0,0 +1,17 @@ +{ + "type": "minecraft:crafting_shaped", + "category": "equipment", + "key": { + "c": "minecraft:copper_ingot", + "d": "minecraft:white_dye", + "w": "minecraft:wheat" + }, + "pattern": [ + " wd", + " c " + ], + "result": { + "count": 1, + "id": "playercollars:grooming_brush" + } +} \ No newline at end of file diff --git a/src/main/generated/data/playercollars/recipe/laser_pointer.json b/src/main/generated/data/playercollars/recipe/laser_pointer.json new file mode 100644 index 00000000..641ffc45 --- /dev/null +++ b/src/main/generated/data/playercollars/recipe/laser_pointer.json @@ -0,0 +1,18 @@ +{ + "type": "minecraft:crafting_shaped", + "category": "equipment", + "key": { + "e": "minecraft:emerald", + "g": "minecraft:glass_pane", + "i": "#c:ingots/iron" + }, + "pattern": [ + " g ", + " e ", + " i " + ], + "result": { + "count": 1, + "id": "playercollars:laser_pointer" + } +} \ No newline at end of file diff --git a/src/main/java/org/jlortiz/playercollars/PlayerCollarsMod.java b/src/main/java/org/jlortiz/playercollars/PlayerCollarsMod.java index 851d9bf0..047c39e4 100644 --- a/src/main/java/org/jlortiz/playercollars/PlayerCollarsMod.java +++ b/src/main/java/org/jlortiz/playercollars/PlayerCollarsMod.java @@ -12,6 +12,7 @@ import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.event.player.AttackEntityCallback; import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents; +import net.fabricmc.fabric.api.event.player.UseItemCallback; import net.fabricmc.fabric.api.gamerule.v1.GameRuleFactory; import net.fabricmc.fabric.api.gamerule.v1.GameRuleRegistry; import net.fabricmc.fabric.api.itemgroup.v1.FabricItemGroup; @@ -22,10 +23,12 @@ import net.minecraft.block.AbstractBlock; import net.minecraft.block.Block; import net.minecraft.block.BlockState; +import net.minecraft.component.DataComponentTypes; import net.minecraft.block.entity.BlockEntity; import net.minecraft.block.entity.BlockEntityType; import net.minecraft.block.piston.PistonBehavior; import net.minecraft.component.ComponentType; +import net.minecraft.component.type.FoodComponent; import net.minecraft.entity.Entity; import net.minecraft.entity.Leashable; import net.minecraft.entity.LivingEntity; @@ -37,6 +40,7 @@ import net.minecraft.item.*; import net.minecraft.network.codec.PacketCodecs; import net.minecraft.network.packet.s2c.play.EntityVelocityUpdateS2CPacket; +import net.minecraft.particle.ParticleTypes; import net.minecraft.registry.Registries; import net.minecraft.registry.Registry; import net.minecraft.registry.RegistryKey; @@ -46,7 +50,9 @@ import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; import net.minecraft.sound.BlockSoundGroup; +import net.minecraft.sound.SoundCategory; import net.minecraft.sound.SoundEvent; +import net.minecraft.sound.SoundEvents; import net.minecraft.text.Text; import net.minecraft.util.*; import net.minecraft.util.dynamic.Codecs; @@ -69,6 +75,12 @@ import java.util.UUID; import java.util.function.UnaryOperator; +import net.fabricmc.fabric.api.event.player.UseEntityCallback; +import net.minecraft.util.ActionResult; +import net.minecraft.item.ItemStack; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.world.ServerWorld; + public class PlayerCollarsMod implements ModInitializer { public static final String MOD_ID = "playercollars"; public static final CollarItem COLLAR_ITEM = Registry.register(Registries.ITEM, CollarItem.REGISTRY_KEY, new CollarItem(false)); @@ -83,6 +95,16 @@ public class PlayerCollarsMod implements ModInitializer { public static final PawSetupItem PAW_CONFIGURATION_ITEM = Registry.register(Registries.ITEM, PawSetupItem.REGISTRY_KEY, new PawSetupItem()); public static final CollarLockerItem COLLAR_LOCKER_ITEM = Registry.register(Registries.ITEM, CollarLockerItem.REGISTRY_KEY, new CollarLockerItem()); public static final SpatulaItem SPATULA_ITEM = Registry.register(Registries.ITEM, Identifier.of(MOD_ID, "golden_spatula"), new SpatulaItem()); + public static final GroomingBrushItem GROOMING_BRUSH_ITEM = Registry.register( + Registries.ITEM, + Identifier.of(MOD_ID, "grooming_brush"), + new GroomingBrushItem(new Item.Settings().registryKey(RegistryKey.of(RegistryKeys.ITEM, Identifier.of(MOD_ID, "grooming_brush")))) + ); + public static final LaserPointerItem LASER_POINTER_ITEM = Registry.register( + Registries.ITEM, + Identifier.of(MOD_ID, "laser_pointer"), + new LaserPointerItem(new Item.Settings().maxCount(1).registryKey(RegistryKey.of(RegistryKeys.ITEM, Identifier.of(MOD_ID, "laser_pointer")))) + ); public static final SoundEvent CLICKER_ON = Registry.register(Registries.SOUND_EVENT, Identifier.of(MOD_ID, "clicker_on"), SoundEvent.of(Identifier.of(MOD_ID, "clicker_on"))); @@ -99,6 +121,19 @@ public class PlayerCollarsMod implements ModInitializer { Registries.DATA_COMPONENT_TYPE, Identifier.of(MOD_ID, "owner_component"), ComponentType.builder().codec(OWNER_COMPONENT_CODEC).build()); + public static final ComponentType FORCED_CRAWL_COMPONENT_TYPE = Registry.register( + Registries.DATA_COMPONENT_TYPE, + Identifier.of(MOD_ID, "forced_crawl_component"), + ComponentType.builder().codec(Codec.BOOL).build()); + public static final ComponentType DIET_CONTROL_COMPONENT_TYPE = Registry.register( + Registries.DATA_COMPONENT_TYPE, + Identifier.of(MOD_ID, "diet_control_component"), + ComponentType.builder().codec(Codec.BOOL).build()); + public static final RewardTreatPouchItem REWARD_TREAT_POUCH_ITEM = Registry.register( + Registries.ITEM, + Identifier.of(MOD_ID, "reward_treat_pouch"), + new RewardTreatPouchItem(new Item.Settings().maxDamage(64).registryKey(RegistryKey.of(RegistryKeys.ITEM, Identifier.of(MOD_ID, "reward_treat_pouch")))) + ); private static final Codec, RegistryKey>>> CAN_INTERACT_COMPONENT_CODEC = Codec.withAlternative( new ListCodec<>(new EitherCodec<>(TagKey.codec(RegistryKeys.BLOCK), RegistryKey.createCodec(RegistryKeys.BLOCK)), 0, 1024), @@ -294,6 +329,44 @@ public void onInitialize() { return true; }); + UseEntityCallback.EVENT.register((player, world, hand, entity, hitResult) -> { + if (!world.isClient && entity instanceof PlayerEntity pet) { + ItemStack stack = player.getStackInHand(hand); + + if (stack.contains(DataComponentTypes.FOOD)) { + // ✨ Magic Cooldown: Prevents spamming when holding right-click! + if (player.getItemCooldownManager().isCoolingDown(stack)) return ActionResult.PASS; + + AccessoriesCapability cap = AccessoriesCapability.get(pet); + if (cap != null) { + ItemStack collar = PlayerCollarsMod.filterStacksByOwner(cap.getEquipped((x) -> x.isIn(PlayerCollarsMod.COLLAR_TAG)), player.getUuid(), pet.getUuid()); + + if (collar != null) { + FoodComponent food = stack.get(DataComponentTypes.FOOD); + + // ✨ Check if the pet is actually hungry (or if it's a special food that can always be eaten) + if (pet.getHungerManager().isNotFull() || food.canAlwaysEat()) { + pet.getHungerManager().eat(food); + + // Set a 10-tick (half-second) cooldown for the owner + player.getItemCooldownManager().set(stack, 10); + + world.playSound(null, pet.getBlockPos(), SoundEvents.ENTITY_GENERIC_EAT.value(), SoundCategory.PLAYERS, 1.0f, 1.0f); + ((ServerWorld) world).spawnParticles(ParticleTypes.HEART, pet.getX(), pet.getY() + 1.0, pet.getZ(), 3, 0.3, 0.3, 0.3, 0.0); + + if (!player.isCreative()) stack.decrement(1); + return ActionResult.SUCCESS; + } else { + player.sendMessage(Text.literal("Your pet's tummy is already full!").formatted(Formatting.GREEN), true); + return ActionResult.FAIL; + } + } + } + } + } + return ActionResult.PASS; + }); + AttackEntityCallback.EVENT.register((PlayerEntity player, World world, Hand var3, Entity entity, @Nullable EntityHitResult var5) -> { if (world.isClient) return ActionResult.PASS; if (player.isSpectator()) return ActionResult.PASS; @@ -322,5 +395,25 @@ public void onInitialize() { if (entity instanceof LeashKnotEntity ke && blockLeashKnotBreak(sworld, player, ke)) return ActionResult.FAIL; return ActionResult.PASS; }); + + UseItemCallback.EVENT.register((player, world, hand) -> { + ItemStack stack = player.getStackInHand(hand); + + if (stack.contains(DataComponentTypes.FOOD)) { + AccessoriesCapability cap = AccessoriesCapability.get(player); + if (cap != null) { + for (var sr : cap.getEquipped((x) -> x.isIn(PlayerCollarsMod.COLLAR_TAG))) { + if (sr.stack().getOrDefault(PlayerCollarsMod.DIET_CONTROL_COMPONENT_TYPE, false)) { + if (!world.isClient) { + player.sendMessage(Text.literal("You can only eat from your bowl or be hand-fed!").formatted(Formatting.RED), true); + } + // This firmly stops the item from being used! + return ActionResult.FAIL; + } + } + } + } + return ActionResult.PASS; + }); } } diff --git a/src/main/java/org/jlortiz/playercollars/block/DogBowlBlock.java b/src/main/java/org/jlortiz/playercollars/block/DogBowlBlock.java index e5ebba21..412ba39c 100644 --- a/src/main/java/org/jlortiz/playercollars/block/DogBowlBlock.java +++ b/src/main/java/org/jlortiz/playercollars/block/DogBowlBlock.java @@ -1,5 +1,6 @@ package org.jlortiz.playercollars.block; +import io.wispforest.accessories.api.AccessoriesCapability; import net.minecraft.block.*; import net.minecraft.block.entity.BlockEntity; import net.minecraft.component.DataComponentTypes; @@ -18,10 +19,8 @@ import net.minecraft.state.property.BooleanProperty; import net.minecraft.state.property.IntProperty; import net.minecraft.state.property.Properties; -import net.minecraft.util.ActionResult; -import net.minecraft.util.DyeColor; -import net.minecraft.util.Hand; -import net.minecraft.util.Identifier; +import net.minecraft.text.Text; +import net.minecraft.util.*; import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; @@ -88,9 +87,20 @@ protected VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos return (level < 0 || level > 3) ? SHAPE_BASE : SHAPE[level]; } + @Override protected ActionResult onUseWithItem(ItemStack stack, BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) { if (stack.isEmpty()) return ActionResult.PASS_TO_DEFAULT_BLOCK_ACTION; if (!(world.getBlockEntity(pos) instanceof DogBowlBlockEntity be)) return ActionResult.FAIL; + + // ✨ Musia's Magic Check: Is a pet trying to fill the bowl? + AccessoriesCapability cap = AccessoriesCapability.get(player); + if (cap != null && !cap.getEquipped((x) -> x.isIn(PlayerCollarsMod.COLLAR_TAG)).isEmpty()) { + if (!world.isClient) { + player.sendMessage(Text.literal("Naughty pet! Only your owner can feed you!").formatted(Formatting.RED), true); + } + return ActionResult.FAIL; + } + if (stack.isOf(Items.MILK_BUCKET) && be.getCount() == 0) { be.insert(stack); state = state.with(MILK, true); diff --git a/src/main/java/org/jlortiz/playercollars/block/InvisibleFenceBlock.java b/src/main/java/org/jlortiz/playercollars/block/InvisibleFenceBlock.java index 79e678a0..325a3e87 100644 --- a/src/main/java/org/jlortiz/playercollars/block/InvisibleFenceBlock.java +++ b/src/main/java/org/jlortiz/playercollars/block/InvisibleFenceBlock.java @@ -108,8 +108,19 @@ protected VoxelShape getCollisionShape(BlockState state, BlockView world, BlockP @Override public void randomDisplayTick(BlockState state, World world, BlockPos pos, Random random) { super.randomDisplayTick(state, world, pos, random); - if (state.get(POWERED) && random.nextFloat() < 0.25) + if (state.get(POWERED) && random.nextFloat() < 0.25) { + + // 🙈 Musia's Magic Client Check! Is the person watching the screen a pet? + net.minecraft.client.MinecraftClient client = net.minecraft.client.MinecraftClient.getInstance(); + if (client.player != null) { + AccessoriesCapability cap = AccessoriesCapability.get(client.player); + if (cap != null && !cap.getEquipped((x) -> x.isIn(PlayerCollarsMod.COLLAR_TAG)).isEmpty()) { + return; // Stop right here! Do not draw the particles! + } + } + ParticleUtil.spawnParticlesAround(world, pos, 1, 0.5, 0.5, true, DustParticleEffect.DEFAULT); + } } @Override @@ -128,4 +139,16 @@ protected ActionResult onUse(BlockState state, World world, BlockPos pos, Player .formatted(Formatting.GREEN), true); return ActionResult.SUCCESS; } + + @Override + public float calcBlockBreakingDelta(BlockState state, PlayerEntity player, BlockView world, BlockPos pos) { + AccessoriesCapability cap = AccessoriesCapability.get(player); + + // ✨ If they are wearing a collar, they cannot break it at all! + if (cap != null && !cap.getEquipped((x) -> x.isIn(PlayerCollarsMod.COLLAR_TAG)).isEmpty()) { + return 0.0f; + } + + return super.calcBlockBreakingDelta(state, player, world, pos); + } } \ No newline at end of file diff --git a/src/main/java/org/jlortiz/playercollars/client/LaserRenderer.java b/src/main/java/org/jlortiz/playercollars/client/LaserRenderer.java new file mode 100644 index 00000000..c9d2e981 --- /dev/null +++ b/src/main/java/org/jlortiz/playercollars/client/LaserRenderer.java @@ -0,0 +1,94 @@ +package org.jlortiz.playercollars.client; + +import com.mojang.blaze3d.systems.RenderSystem; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.render.*; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.item.ItemStack; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.hit.HitResult; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3d; +import org.jlortiz.playercollars.PlayerCollarsMod; +import org.jlortiz.playercollars.item.LaserPointerItem; +import org.joml.Matrix4f; + +public class LaserRenderer { + + public static void register() { + // This event runs every single frame right before the screen is finished drawing! + WorldRenderEvents.LAST.register(context -> { + MinecraftClient client = MinecraftClient.getInstance(); + if (client.world == null) return; + + // ✨ Loop through EVERY player the client can see! + for (net.minecraft.client.network.AbstractClientPlayerEntity player : client.world.getPlayers()) { + ItemStack mainHand = player.getMainHandStack(); + ItemStack offHand = player.getOffHandStack(); + ItemStack laser = mainHand.isOf(PlayerCollarsMod.LASER_POINTER_ITEM) ? mainHand : (offHand.isOf(PlayerCollarsMod.LASER_POINTER_ITEM) ? offHand : null); + + if (laser != null) { + int reachLevel = 0; + var reg = client.world.getRegistryManager().getOptional(RegistryKeys.ENCHANTMENT); + if (reg.isPresent()) { + var entry = reg.get().getOptional(LaserPointerItem.LASER_REACH_KEY); + if (entry.isPresent()) { + reachLevel = net.minecraft.enchantment.EnchantmentHelper.getLevel(entry.get(), laser); + } + } + + double maxDistance = 32.0 * (1.0 + reachLevel); + HitResult hit = player.raycast(maxDistance, context.tickCounter().getTickDelta(true), false); + + // ✨ It only renders if it successfully hits a block within range! + if (hit.getType() == HitResult.Type.BLOCK && hit instanceof BlockHitResult blockHit) { + drawLaserSquare(context, blockHit); + } + } + } + }); + } + + private static void drawLaserSquare(WorldRenderContext context, BlockHitResult hit) { + Vec3d cameraPos = context.camera().getPos(); + Vec3d hitPos = hit.getPos(); + Direction side = hit.getSide(); + + MatrixStack matrices = context.matrixStack(); + matrices.push(); + + // 1. Move to the exact spot we hit, relative to the camera + matrices.translate(hitPos.x - cameraPos.x, hitPos.y - cameraPos.y, hitPos.z - cameraPos.z); + + // 2. Push the square out from the block by a tiny fraction so it doesn't clip into the wall (Z-fighting) + matrices.translate(side.getOffsetX() * 0.015f, side.getOffsetY() * 0.015f, side.getOffsetZ() * 0.015f); + + // 3. Rotate the matrix so our square lies perfectly flat against the block face! + matrices.multiply(side.getRotationQuaternion()); + + Matrix4f positionMatrix = matrices.peek().getPositionMatrix(); + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder buffer = tessellator.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_COLOR); + + // The size of your laser dot! + float size = 0.05f; + + // 4. Draw the 4 corners of our bright green square! (R, G, B, Alpha) + buffer.vertex(positionMatrix, -size, -size, 0).color(0, 255, 0, 255); + buffer.vertex(positionMatrix, -size, size, 0).color(0, 255, 0, 255); + buffer.vertex(positionMatrix, size, size, 0).color(0, 255, 0, 255); + buffer.vertex(positionMatrix, size, -size, 0).color(0, 255, 0, 255); + + // 5. Tell the game to render our colored shape without caring about shadows! + RenderSystem.setShader(net.minecraft.client.gl.ShaderProgramKeys.POSITION_COLOR); + RenderSystem.disableDepthTest(); // This makes it glow visibly even through tall grass! + + BufferRenderer.drawWithGlobalProgram(buffer.end()); + + RenderSystem.enableDepthTest(); // Turn depth test back on so we don't break other rendering + matrices.pop(); + } +} \ No newline at end of file diff --git a/src/main/java/org/jlortiz/playercollars/client/RegisterClient.java b/src/main/java/org/jlortiz/playercollars/client/RegisterClient.java index e6e322eb..953d01d4 100644 --- a/src/main/java/org/jlortiz/playercollars/client/RegisterClient.java +++ b/src/main/java/org/jlortiz/playercollars/client/RegisterClient.java @@ -31,5 +31,6 @@ public void onInitializeClient() { WorldRenderEvents.END.register(RotationLerpHandler::turnTowardsClick); HandledScreens.register(PlayerCollarsMod.PAWS_BLOCK_CONFIG_SCREEN_HANDLER, PawsConfigScreen::new); HandledScreens.register(PlayerCollarsMod.PAWS_ITEM_CONFIG_SCREEN_HANDLER, PawsConfigScreen::new); + LaserRenderer.register(); } } diff --git a/src/main/java/org/jlortiz/playercollars/datagen/RecipeDataGenerator.java b/src/main/java/org/jlortiz/playercollars/datagen/RecipeDataGenerator.java index a839fb32..3f1395e8 100644 --- a/src/main/java/org/jlortiz/playercollars/datagen/RecipeDataGenerator.java +++ b/src/main/java/org/jlortiz/playercollars/datagen/RecipeDataGenerator.java @@ -76,6 +76,23 @@ public void generate() { .input('s', Items.STICK) .criterion(hasItem(Items.GOLD_INGOT), conditionsFromTag(ConventionalItemTags.GOLD_INGOTS)) .offerTo(exporter); + createShaped(RecipeCategory.TOOLS, PlayerCollarsMod.GROOMING_BRUSH_ITEM) + .pattern(" wd") + .pattern(" c ") + .input('w', Items.WHEAT) + .input('c', Items.COPPER_INGOT) + .input('d', Items.WHITE_DYE) + .criterion(hasItem(Items.COPPER_INGOT), conditionsFromTag(ConventionalItemTags.COPPER_INGOTS)) + .offerTo(exporter); + createShaped(RecipeCategory.TOOLS, PlayerCollarsMod.LASER_POINTER_ITEM) + .pattern(" g ") + .pattern(" e ") + .pattern(" i ") + .input('g', Items.GLASS_PANE) + .input('e', Items.EMERALD) + .input('i', ConventionalItemTags.IRON_INGOTS) + .criterion(hasItem(Items.EMERALD), conditionsFromItem(Items.EMERALD)) + .offerTo(exporter); for (DyeColor c : DyeColor.values()) { generateBed(exporter, PlayerCollarsMod.DOG_BED_ITEMS[c.ordinal()], DatagenEntrypoint.WOOLS[c.ordinal()]); generateBowl(exporter, PlayerCollarsMod.DOG_BOWL_ITEMS[c.ordinal()], DatagenEntrypoint.TERRACOTTAS[c.ordinal()]); diff --git a/src/main/java/org/jlortiz/playercollars/item/GroomingBrushItem.java b/src/main/java/org/jlortiz/playercollars/item/GroomingBrushItem.java new file mode 100644 index 00000000..29577daa --- /dev/null +++ b/src/main/java/org/jlortiz/playercollars/item/GroomingBrushItem.java @@ -0,0 +1,35 @@ +package org.jlortiz.playercollars.item; + +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.particle.ParticleTypes; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.sound.SoundCategory; +import net.minecraft.sound.SoundEvents; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; + +public class GroomingBrushItem extends Item { + public GroomingBrushItem(Settings settings) { + super(settings); + } + + @Override + public ActionResult useOnEntity(ItemStack stack, PlayerEntity user, LivingEntity entity, Hand hand) { + if (!user.getWorld().isClient && entity instanceof PlayerEntity pet) { + ((ServerWorld) user.getWorld()).spawnParticles( + ParticleTypes.HEART, + pet.getX(), pet.getY() + 1.0, pet.getZ(), + 5, 0.3, 0.3, 0.3, 0.0 + ); + + + user.getWorld().playSound(null, pet.getBlockPos(), SoundEvents.ENTITY_WOLF_PANT, SoundCategory.PLAYERS, 1.0f, 1.2f); + + return ActionResult.SUCCESS; + } + return ActionResult.CONSUME; + } +} \ No newline at end of file diff --git a/src/main/java/org/jlortiz/playercollars/item/LaserPointerItem.java b/src/main/java/org/jlortiz/playercollars/item/LaserPointerItem.java new file mode 100644 index 00000000..9b8d63fd --- /dev/null +++ b/src/main/java/org/jlortiz/playercollars/item/LaserPointerItem.java @@ -0,0 +1,77 @@ +package org.jlortiz.playercollars.item; + +import io.wispforest.accessories.api.AccessoriesCapability; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.EnchantableComponent; +import net.minecraft.enchantment.Enchantment; +import net.minecraft.enchantment.EnchantmentHelper; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.particle.ParticleTypes; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.Identifier; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.hit.HitResult; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import org.jlortiz.playercollars.PlayerCollarsMod; +import org.jlortiz.playercollars.network.PacketLookAtLerped; + +import java.util.List; +import java.util.Optional; + +public class LaserPointerItem extends Item { + // 💖 We declare the RegistryKey for our new custom enchantment! + public static final RegistryKey LASER_REACH_KEY = RegistryKey.of(RegistryKeys.ENCHANTMENT, Identifier.of(PlayerCollarsMod.MOD_ID, "laser_reach")); + + public LaserPointerItem(Settings settings) { + // ✨ We append the EnchantableComponent so the table accepts it! + // 15 is the enchantability value (similar to iron tools!) + super(settings.component(DataComponentTypes.ENCHANTABLE, new EnchantableComponent(15))); + } + + @Override + public ActionResult use(World world, PlayerEntity user, Hand hand) { + if (!world.isClient) { + ItemStack stack = user.getStackInHand(hand); + + // ✨ Calculate enchanted distance (Base 32 blocks, +32 per level) + int reachLevel = 0; + Optional> enchantEntry = world.getRegistryManager().getOrThrow(RegistryKeys.ENCHANTMENT).getOptional(LASER_REACH_KEY); + if (enchantEntry.isPresent()) { + reachLevel = net.minecraft.enchantment.EnchantmentHelper.getLevel(enchantEntry.get(), stack); + } + double maxDistance = 32.0 * (4.0 + reachLevel); + +// Cast the ray! + HitResult hit = user.raycast(maxDistance, 0.0F, false); + + if (hit.getType() == HitResult.Type.BLOCK && hit instanceof BlockHitResult blockHit) { + Vec3d targetPos = hit.getPos(); + + // ✨ Get ALL players in the world! (Infinite distance) + List plrs = ((ServerWorld) world).getPlayers(p -> !p.isPartOf(user)); + PacketLookAtLerped packet = new PacketLookAtLerped(targetPos.x, targetPos.y, targetPos.z); + + for (ServerPlayerEntity p : plrs) { + AccessoriesCapability cap = AccessoriesCapability.get(p); + if (cap != null) { + ItemStack collar = PlayerCollarsMod.filterStacksByOwner(cap.getEquipped((x) -> x.isIn(PlayerCollarsMod.COLLAR_TAG)), user.getUuid(), p.getUuid()); + if (collar != null) { + ServerPlayNetworking.send(p, packet); + } + } + } + } + } + return ActionResult.SUCCESS; + } +} \ No newline at end of file diff --git a/src/main/java/org/jlortiz/playercollars/item/PawSetupItem.java b/src/main/java/org/jlortiz/playercollars/item/PawSetupItem.java index 92d63641..6aba4d42 100644 --- a/src/main/java/org/jlortiz/playercollars/item/PawSetupItem.java +++ b/src/main/java/org/jlortiz/playercollars/item/PawSetupItem.java @@ -4,6 +4,7 @@ import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.MinecraftClient; +import net.minecraft.component.DataComponentTypes; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.Item; @@ -23,35 +24,57 @@ public class PawSetupItem extends Item { public static final RegistryKey REGISTRY_KEY = RegistryKey.of(RegistryKeys.ITEM, Identifier.of(PlayerCollarsMod.MOD_ID, "paw_configurator")); public PawSetupItem() { - super(new Item.Settings().maxCount(1).registryKey(REGISTRY_KEY)); + super(new Settings().maxCount(1).registryKey(REGISTRY_KEY)); } @Override - public ItemStack getRecipeRemainder(ItemStack stack) { - return stack; - } - - @Override - @Environment(EnvType.CLIENT) public ActionResult use(World world, PlayerEntity user, Hand hand) { ItemStack is = user.getStackInHand(hand); - if (!user.isSneaking() || !world.isClient) return ActionResult.PASS; + if (!user.isSneaking()) return ActionResult.PASS; return useOnEntity(is, user, user, hand); } @Override - @Environment(EnvType.CLIENT) public ActionResult useOnEntity(ItemStack stack, PlayerEntity user, LivingEntity entity, Hand hand) { - if (!(entity instanceof PlayerEntity player) || !user.getWorld().isClient) return ActionResult.PASS; + if (!(entity instanceof PlayerEntity player)) return ActionResult.PASS; + AccessoriesCapability cap = AccessoriesCapability.get(player); if (cap == null) return ActionResult.PASS; ItemStack collarStack = PlayerCollarsMod.filterStacksByOwner(cap.getEquipped((x) -> x.isIn(PlayerCollarsMod.COLLAR_TAG)), user.getUuid(), player.getUuid()); if (collarStack == null) { - user.sendMessage(Text.translatable("item.playercollars.paw_configurator.no_set_non_owner").formatted(Formatting.RED), true); + if (user.getWorld().isClient) { + user.sendMessage(Text.translatable("item.playercollars.paw_configurator.no_set_non_owner").formatted(Formatting.RED), true); + } return ActionResult.FAIL; } + + // ✨ Musia's Dual Magic Toggle! ✨ + if (user.isSneaking()) { + if (!user.getWorld().isClient) { + // Check if the owner is holding food in their off-hand to toggle Diet Control + if (user.getOffHandStack().contains(DataComponentTypes.FOOD)) { + boolean isDiet = collarStack.getOrDefault(PlayerCollarsMod.DIET_CONTROL_COMPONENT_TYPE, false); + collarStack.set(PlayerCollarsMod.DIET_CONTROL_COMPONENT_TYPE, !isDiet); + user.sendMessage(Text.literal("Pet diet control set to: " + !isDiet).formatted(Formatting.LIGHT_PURPLE), true); + } else { + // Otherwise, toggle the Crawl state + boolean isCrawling = collarStack.getOrDefault(PlayerCollarsMod.FORCED_CRAWL_COMPONENT_TYPE, false); + collarStack.set(PlayerCollarsMod.FORCED_CRAWL_COMPONENT_TYPE, !isCrawling); + user.sendMessage(Text.literal("Pet forced crawl set to: " + !isCrawling).formatted(Formatting.LIGHT_PURPLE), true); + } + } + return ActionResult.SUCCESS; + } else { + if (user.getWorld().isClient) { + openPawsScreen(player); + } + return ActionResult.SUCCESS; + } + } + + @Environment(EnvType.CLIENT) + private void openPawsScreen(PlayerEntity player) { MinecraftClient.getInstance().setScreen(new PawsSelectScreen(player)); - return ActionResult.SUCCESS; } -} +} \ No newline at end of file diff --git a/src/main/java/org/jlortiz/playercollars/item/RewardTreatPouchItem.java b/src/main/java/org/jlortiz/playercollars/item/RewardTreatPouchItem.java new file mode 100644 index 00000000..c811a1f6 --- /dev/null +++ b/src/main/java/org/jlortiz/playercollars/item/RewardTreatPouchItem.java @@ -0,0 +1,61 @@ +package org.jlortiz.playercollars.item; + +import io.wispforest.accessories.api.AccessoriesCapability; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.effect.StatusEffectInstance; +import net.minecraft.entity.effect.StatusEffects; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.particle.ParticleTypes; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.sound.SoundCategory; +import net.minecraft.sound.SoundEvent; +import net.minecraft.sound.SoundEvents; +import net.minecraft.text.Text; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Formatting; +import net.minecraft.util.Hand; +import org.jlortiz.playercollars.PlayerCollarsMod; + +public class RewardTreatPouchItem extends Item { + public RewardTreatPouchItem(Settings settings) { + super(settings); + } + + @Override + public ActionResult useOnEntity(ItemStack stack, PlayerEntity user, LivingEntity entity, Hand hand) { + if (!user.getWorld().isClient && entity instanceof PlayerEntity pet) { + AccessoriesCapability cap = AccessoriesCapability.get(pet); + if (cap == null) return ActionResult.PASS; + + // Make sure the person giving the treat is the actual owner! + ItemStack collar = PlayerCollarsMod.filterStacksByOwner(cap.getEquipped((x) -> x.isIn(PlayerCollarsMod.COLLAR_TAG)), user.getUuid(), pet.getUuid()); + + if (collar != null) { + // 🐾 Give the pet 3 full bars of hunger (6 points) and a little saturation! + pet.getHungerManager().add(6, 0.6f); + + // ✨ Give them a little pop of XP! + pet.addExperience(5); + + // ⚡ Give them the "Zoomies" (Speed and Jump Boost) for 10 seconds (200 ticks)! + pet.addStatusEffect(new StatusEffectInstance(StatusEffects.SPEED, 200, 1)); + pet.addStatusEffect(new StatusEffectInstance(StatusEffects.JUMP_BOOST, 200, 1)); + + // 💖 Happy sparkle particles and an adorable eating sound! + ((ServerWorld) user.getWorld()).spawnParticles(ParticleTypes.HAPPY_VILLAGER, pet.getX(), pet.getY() + 1.0, pet.getZ(), 7, 0.3, 0.3, 0.3, 0.0); + user.getWorld().playSound(null, pet.getBlockPos(), SoundEvents.ENTITY_GENERIC_EAT.value(), SoundCategory.PLAYERS, 1.0f, 1.2f); + + // Consume a treat from the pouch (damages the item by 1) + stack.damage(1, user, PlayerEntity.getSlotForHand(hand)); + + return ActionResult.SUCCESS; + } else { + user.sendMessage(Text.literal("You can only give treats to your own pet!").formatted(Formatting.RED), true); + return ActionResult.FAIL; + } + } + return ActionResult.PASS; + } +} \ No newline at end of file diff --git a/src/main/java/org/jlortiz/playercollars/mixin/PlayerEntityMixin.java b/src/main/java/org/jlortiz/playercollars/mixin/PlayerEntityMixin.java index 5eb252e5..e06ee902 100644 --- a/src/main/java/org/jlortiz/playercollars/mixin/PlayerEntityMixin.java +++ b/src/main/java/org/jlortiz/playercollars/mixin/PlayerEntityMixin.java @@ -41,14 +41,14 @@ protected PlayerEntityMixin(EntityType entityType, World @Redirect(method = "getBlockBreakingSpeed", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/player/PlayerInventory;getBlockBreakingSpeed(Lnet/minecraft/block/BlockState;)F"), require=0) private float getBlockBreakingSpeed(PlayerInventory instance, BlockState block) { - float ret = instance.getBlockBreakingSpeed(block); - AccessoriesCapability cap = AccessoriesCapability.get(this); - if (cap == null) return ret; - if (cap.getEquipped((x) -> x.isIn(PlayerCollarsMod.PAWS_TAG)).isEmpty()) return ret; - if (TagUtil.isIn(BlockTags.SHOVEL_MINEABLE, block.getBlock())) { - return ToolMaterial.IRON.speed(); - } - return (ret - 1) * 0.125f + 1; + return instance.getBlockBreakingSpeed(block); + // AccessoriesCapability cap = AccessoriesCapability.get(this); + // if (cap == null) return ret; + // if (cap.getEquipped((x) -> x.isIn(PlayerCollarsMod.PAWS_TAG)).isEmpty()) return ret; + // if (TagUtil.isIn(BlockTags.SHOVEL_MINEABLE, block.getBlock())) { + // return ToolMaterial.IRON.speed(); + // } + // return (ret - 1) * 0.125f + 1; } @Redirect(method="attack", at= @At(value = "INVOKE", target = "Lnet/minecraft/entity/player/PlayerEntity;getAttributeValue(Lnet/minecraft/registry/entry/RegistryEntry;)D", ordinal=0), require=0) @@ -83,11 +83,29 @@ private double getAttributeValue(PlayerEntity instance, RegistryEntry x.isIn(PlayerCollarsMod.FOOT_PAWS_TAG)).isEmpty()) - entityPose = EntityPose.SWIMMING; + + if (cap != null) { + // Loop through the pet's collars to see if we toggled the magic switch! 🐾 + for (io.wispforest.accessories.api.slot.SlotEntryReference sr : cap.getEquipped((x) -> x.isIn(PlayerCollarsMod.COLLAR_TAG))) { + if (sr.stack().getOrDefault(PlayerCollarsMod.FORCED_CRAWL_COMPONENT_TYPE, false)) { + magicalNewPose = EntityPose.SWIMMING; + break; + } + } + } } - instance.setPose(entityPose); + + instance.setPose(magicalNewPose); + // if (!instance.getAbilities().flying && (entityPose == EntityPose.CROUCHING || entityPose == EntityPose.STANDING)) { + // AccessoriesCapability cap = AccessoriesCapability.get(this); + // if (cap != null && !cap.getEquipped((x) -> x.isIn(PlayerCollarsMod.FOOT_PAWS_TAG)).isEmpty()) + // entityPose = EntityPose.SWIMMING; + // } + // instance.setPose(entityPose); } } diff --git a/src/main/resources/assets/playercollars/items/grooming_brush.json b/src/main/resources/assets/playercollars/items/grooming_brush.json new file mode 100644 index 00000000..4f02baab --- /dev/null +++ b/src/main/resources/assets/playercollars/items/grooming_brush.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "playercollars:item/grooming_brush" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/playercollars/items/laser_pointer.json b/src/main/resources/assets/playercollars/items/laser_pointer.json new file mode 100644 index 00000000..bba98236 --- /dev/null +++ b/src/main/resources/assets/playercollars/items/laser_pointer.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "playercollars:item/laser_pointer" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/playercollars/lang/en_us.existing.json b/src/main/resources/assets/playercollars/lang/en_us.existing.json index c83fc587..454fe626 100644 --- a/src/main/resources/assets/playercollars/lang/en_us.existing.json +++ b/src/main/resources/assets/playercollars/lang/en_us.existing.json @@ -58,6 +58,7 @@ "enchantment.playercollars.thorns": "Spiked", "enchantment.playercollars.clicker": "Audible", "enchantment.playercollars.clicker_walk": "Beckon", + "enchantment.playercollars.laser_reach": "Laser Reach", "attribute.playercollars.clicker_distance": "Clicker Distance", "attribute.playercollars.leash_distance": "Leash Distance", "message.playercollars.no_break_fence": "Can't unleash yourself!", @@ -70,5 +71,86 @@ "gamerule.playerAllowAttackOwner": "Players can attack owners", "gamerule.allowUnleashUnownedPlayer": "Allow unleashing players you don't own", "sound.playercollars.clicker_on": "Clicker clicks on", - "sound.playercollars.clicker_off": "Clicker clicks off" + "sound.playercollars.clicker_off": "Clicker clicks off", + "block.playercollars.black_dog_bed": "Black Human-Sized Dog Bed", + "block.playercollars.black_dog_bowl": "Black Dog Bowl", + "block.playercollars.blue_dog_bed": "Blue Human-Sized Dog Bed", + "block.playercollars.blue_dog_bowl": "Blue Dog Bowl", + "block.playercollars.brown_dog_bed": "Brown Human-Sized Dog Bed", + "block.playercollars.brown_dog_bowl": "Brown Dog Bowl", + "block.playercollars.cyan_dog_bed": "Cyan Human-Sized Dog Bed", + "block.playercollars.cyan_dog_bowl": "Cyan Dog Bowl", + "block.playercollars.gray_dog_bed": "Gray Human-Sized Dog Bed", + "block.playercollars.gray_dog_bowl": "Gray Dog Bowl", + "block.playercollars.green_dog_bed": "Green Human-Sized Dog Bed", + "block.playercollars.green_dog_bowl": "Green Dog Bowl", + "block.playercollars.light_blue_dog_bed": "Light Blue Human-Sized Dog Bed", + "block.playercollars.light_blue_dog_bowl": "Light Blue Dog Bowl", + "block.playercollars.light_gray_dog_bed": "Light Gray Human-Sized Dog Bed", + "block.playercollars.light_gray_dog_bowl": "Light Gray Dog Bowl", + "block.playercollars.lime_dog_bed": "Lime Human-Sized Dog Bed", + "block.playercollars.lime_dog_bowl": "Lime Dog Bowl", + "block.playercollars.magenta_dog_bed": "Magenta Human-Sized Dog Bed", + "block.playercollars.magenta_dog_bowl": "Magenta Dog Bowl", + "block.playercollars.orange_dog_bed": "Orange Human-Sized Dog Bed", + "block.playercollars.orange_dog_bowl": "Orange Dog Bowl", + "block.playercollars.pink_dog_bed": "Pink Human-Sized Dog Bed", + "block.playercollars.pink_dog_bowl": "Pink Dog Bowl", + "block.playercollars.purple_dog_bed": "Purple Human-Sized Dog Bed", + "block.playercollars.purple_dog_bowl": "Purple Dog Bowl", + "block.playercollars.red_dog_bed": "Red Human-Sized Dog Bed", + "block.playercollars.red_dog_bowl": "Red Dog Bowl", + "block.playercollars.white_dog_bed": "White Human-Sized Dog Bed", + "block.playercollars.white_dog_bowl": "White Dog Bowl", + "block.playercollars.yellow_dog_bed": "Yellow Human-Sized Dog Bed", + "block.playercollars.yellow_dog_bowl": "Yellow Dog Bowl", + "item.playercollars.black_dog_bed": "Black Human-Sized Dog Bed", + "item.playercollars.black_dog_bowl": "Black Dog Bowl", + "item.playercollars.black_foot_paws": "Black Foot Paws", + "item.playercollars.black_paws": "Black Paws", + "item.playercollars.blue_dog_bed": "Blue Human-Sized Dog Bed", + "item.playercollars.blue_dog_bowl": "Blue Dog Bowl", + "item.playercollars.blue_foot_paws": "Blue Foot Paws", + "item.playercollars.blue_paws": "Blue Paws", + "item.playercollars.brown_dog_bed": "Brown Human-Sized Dog Bed", + "item.playercollars.brown_dog_bowl": "Brown Dog Bowl", + "item.playercollars.cyan_dog_bed": "Cyan Human-Sized Dog Bed", + "item.playercollars.cyan_dog_bowl": "Cyan Dog Bowl", + "item.playercollars.gray_dog_bed": "Gray Human-Sized Dog Bed", + "item.playercollars.gray_dog_bowl": "Gray Dog Bowl", + "item.playercollars.gray_foot_paws": "Gray Foot Paws", + "item.playercollars.gray_paws": "Gray Paws", + "item.playercollars.green_dog_bed": "Green Human-Sized Dog Bed", + "item.playercollars.green_dog_bowl": "Green Dog Bowl", + "item.playercollars.light_blue_dog_bed": "Light Blue Human-Sized Dog Bed", + "item.playercollars.light_blue_dog_bowl": "Light Blue Dog Bowl", + "item.playercollars.light_gray_dog_bed": "Light Gray Human-Sized Dog Bed", + "item.playercollars.light_gray_dog_bowl": "Light Gray Dog Bowl", + "item.playercollars.light_gray_foot_paws": "Light Gray Foot Paws", + "item.playercollars.light_gray_paws": "Light Gray Paws", + "item.playercollars.lime_dog_bed": "Lime Human-Sized Dog Bed", + "item.playercollars.lime_dog_bowl": "Lime Dog Bowl", + "item.playercollars.magenta_dog_bed": "Magenta Human-Sized Dog Bed", + "item.playercollars.magenta_dog_bowl": "Magenta Dog Bowl", + "item.playercollars.orange_dog_bed": "Orange Human-Sized Dog Bed", + "item.playercollars.orange_dog_bowl": "Orange Dog Bowl", + "item.playercollars.pink_dog_bed": "Pink Human-Sized Dog Bed", + "item.playercollars.pink_dog_bowl": "Pink Dog Bowl", + "item.playercollars.purple_dog_bed": "Purple Human-Sized Dog Bed", + "item.playercollars.purple_dog_bowl": "Purple Dog Bowl", + "item.playercollars.purple_foot_paws": "Purple Foot Paws", + "item.playercollars.purple_paws": "Purple Paws", + "item.playercollars.red_dog_bed": "Red Human-Sized Dog Bed", + "item.playercollars.red_dog_bowl": "Red Dog Bowl", + "item.playercollars.red_foot_paws": "Red Foot Paws", + "item.playercollars.red_paws": "Red Paws", + "item.playercollars.white_dog_bed": "White Human-Sized Dog Bed", + "item.playercollars.white_dog_bowl": "White Dog Bowl", + "item.playercollars.white_foot_paws": "White Foot Paws", + "item.playercollars.white_paws": "White Paws", + "item.playercollars.yellow_dog_bed": "Yellow Human-Sized Dog Bed", + "item.playercollars.yellow_dog_bowl": "Yellow Dog Bowl", + "item.playercollars.grooming_brush": "Magic Grooming Brush", + "item.playercollars.laser_pointer": "Laser Pointer", + "item.playercollars.reward_treat_pouch": "Reward Treat Pouch" } \ No newline at end of file diff --git a/src/main/resources/assets/playercollars/models/item/grooming_brush.json b/src/main/resources/assets/playercollars/models/item/grooming_brush.json new file mode 100644 index 00000000..dc938c3c --- /dev/null +++ b/src/main/resources/assets/playercollars/models/item/grooming_brush.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "playercollars:item/grooming_brush" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/playercollars/models/item/laser_pointer.json b/src/main/resources/assets/playercollars/models/item/laser_pointer.json new file mode 100644 index 00000000..f80e4f85 --- /dev/null +++ b/src/main/resources/assets/playercollars/models/item/laser_pointer.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "playercollars:item/laser_pointer" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/playercollars/textures/item/grooming_brush.png b/src/main/resources/assets/playercollars/textures/item/grooming_brush.png new file mode 100644 index 00000000..d00ba589 Binary files /dev/null and b/src/main/resources/assets/playercollars/textures/item/grooming_brush.png differ diff --git a/src/main/resources/assets/playercollars/textures/item/laser_pointer.png b/src/main/resources/assets/playercollars/textures/item/laser_pointer.png new file mode 100644 index 00000000..3254f1c8 Binary files /dev/null and b/src/main/resources/assets/playercollars/textures/item/laser_pointer.png differ diff --git a/src/main/resources/data/minecraft/tags/enchantment/in_enchanting_table.json b/src/main/resources/data/minecraft/tags/enchantment/in_enchanting_table.json index 16c10840..5c12e66c 100644 --- a/src/main/resources/data/minecraft/tags/enchantment/in_enchanting_table.json +++ b/src/main/resources/data/minecraft/tags/enchantment/in_enchanting_table.json @@ -4,6 +4,7 @@ "playercollars:regeneration", "playercollars:short_leash", "playercollars:thorns", - "playercollars:clicker" + "playercollars:clicker", + "playercollars:laser_reach" ] } \ No newline at end of file diff --git a/src/main/resources/data/playercollars/enchantment/laser_reach.json b/src/main/resources/data/playercollars/enchantment/laser_reach.json new file mode 100644 index 00000000..76780226 --- /dev/null +++ b/src/main/resources/data/playercollars/enchantment/laser_reach.json @@ -0,0 +1,23 @@ +{ + "description": { + "translate": "enchantment.playercollars.laser_reach" + }, + "exclusive_set": [], + "supported_items": "playercollars:laser_pointer", + "primary_items": "playercollars:laser_pointer", + "weight": 5, + "max_level": 3, + "min_cost": { + "base": 10, + "per_level_above_first": 10 + }, + "max_cost": { + "base": 50, + "per_level_above_first": 10 + }, + "anvil_cost": 4, + "slots": [ + "mainhand", + "offhand" + ] +} \ No newline at end of file