From 16ee0293388e87a73262584f37db66f4ab98f4ab Mon Sep 17 00:00:00 2001 From: ErrorCraft Date: Tue, 20 Jan 2026 16:53:20 +0100 Subject: [PATCH 1/3] Backport #59 to 1.20.6 (#60) --- .../render/item/HeldItemRendererExtender.java | 119 ++++++-- .../errorcraft/itematic/gametest/Assert.java | 24 +- .../itematic/gametest/TestUtil.java | 56 +++- .../gametest/block/AnvilBlockTestSuite.java | 101 +++++++ .../block/EnchantingTableTestSuite.java | 105 +++++++ .../gametest/block/GrindstoneTestSuite.java | 82 +++++ .../block/WaterCauldronBlockTestSuite.java | 2 +- .../passive/MooshroomEntityTestSuite.java | 2 +- .../gametest/item/BeetrootSoupTestSuite.java | 30 ++ .../itematic/gametest/item/BowTestSuite.java | 38 +++ .../gametest/item/BundleTestSuite.java | 6 +- .../gametest/item/CrossbowTestSuite.java | 38 +++ .../gametest/item/HoneyBottleTestSuite.java | 2 +- .../ConsumableItemComponentTestSuite.java | 2 +- .../component/FoodItemComponentTestSuite.java | 4 +- .../ItemHolderItemComponentTestSuite.java | 8 +- .../MappableItemComponentTestSuite.java | 2 +- .../PotionItemComponentTestSuite.java | 2 +- .../ShooterItemComponentTestSuite.java | 19 +- .../gametest/structures/block.anvil.snbt | 39 +++ .../structures/block.enchanting_table.snbt | 39 +++ .../gametest/structures/block.grindstone.snbt | 39 +++ .../structures/item.bow.platform.snbt | 38 +++ src/gametest/resources/fabric.mod.json | 8 +- .../generated/data/minecraft/item/arrow.json | 1 - .../data/minecraft/item/beetroot_soup.json | 4 +- .../generated/data/minecraft/item/bow.json | 7 + .../data/minecraft/item/crossbow.json | 33 +- .../data/minecraft/item/firework_rocket.json | 2 - .../data/minecraft/item/honey_bottle.json | 4 +- .../data/minecraft/item/milk_bucket.json | 4 +- .../generated/data/minecraft/item/potion.json | 4 +- .../data/minecraft/item/spectral_arrow.json | 1 - .../data/minecraft/item/tipped_arrow.json | 1 - .../data/minecraft/item/trident.json | 1 - .../ChargedProjectilesComponentAccess.java | 3 - .../component/ItematicDataComponentTypes.java | 29 +- .../type/ItemDamageRulesDataComponent.java | 58 ++++ .../errorcraft/itematic/item/ItemUtil.java | 143 ++++++--- .../components/ConsumableItemComponent.java | 82 ++++- .../components/FoodItemComponent.java | 27 +- .../components/ProjectileItemComponent.java | 21 +- .../components/ShooterItemComponent.java | 244 ++++----------- .../overrides/ChargedModelOverride.java | 9 +- .../override/overrides/PullModelOverride.java | 4 +- .../overrides/PullingModelOverride.java | 12 +- .../item/shooter/method/ShooterMethod.java | 32 ++ .../shooter/method/ShooterMethodType.java | 6 + .../shooter/method/ShooterMethodTypeKeys.java | 16 + .../shooter/method/ShooterMethodTypes.java | 20 ++ .../methods/ChargeableShooterMethod.java | 284 ++++++++++++++++++ .../method/methods/DirectShooterMethod.java | 108 +++++++ .../providers/ShooterIntegerProvider.java | 15 +- .../ChargedProjectilesComponentExtender.java | 19 -- .../EnchantmentHelperExtender.java | 38 ++- .../mixin/entity/CrossbowUserExtender.java | 12 +- .../mixin/entity/LivingEntityExtender.java | 64 +++- .../mixin/item/CrossbowItemAccessor.java | 25 +- .../mixin/item/CrossbowItemExtender.java | 78 +++-- .../itematic/mixin/item/ItemExtender.java | 13 +- .../mixin/item/ItemStackExtender.java | 72 ++++- .../mixin/item/RangedWeaponItemAccessor.java | 22 ++ .../mixin/item/RangedWeaponItemExtender.java | 49 +-- .../EnchantmentScreenHandlerExtender.java | 6 +- .../itematic/registry/ItematicRegistries.java | 3 + .../registry/ItematicRegistryKeys.java | 2 + .../itematic/sound/SoundEventKeys.java | 2 + .../world/action/actions/PlaySoundAction.java | 44 ++- .../world/action/context/ActionContext.java | 7 +- src/main/resources/itematic.mixins.json | 1 + 70 files changed, 1879 insertions(+), 558 deletions(-) create mode 100644 src/gametest/java/net/errorcraft/itematic/gametest/block/AnvilBlockTestSuite.java create mode 100644 src/gametest/java/net/errorcraft/itematic/gametest/block/EnchantingTableTestSuite.java create mode 100644 src/gametest/java/net/errorcraft/itematic/gametest/block/GrindstoneTestSuite.java create mode 100644 src/gametest/java/net/errorcraft/itematic/gametest/item/BeetrootSoupTestSuite.java create mode 100644 src/gametest/java/net/errorcraft/itematic/gametest/item/BowTestSuite.java create mode 100644 src/gametest/java/net/errorcraft/itematic/gametest/item/CrossbowTestSuite.java create mode 100644 src/gametest/resources/data/itematic/gametest/structures/block.anvil.snbt create mode 100644 src/gametest/resources/data/itematic/gametest/structures/block.enchanting_table.snbt create mode 100644 src/gametest/resources/data/itematic/gametest/structures/block.grindstone.snbt create mode 100644 src/gametest/resources/data/itematic/gametest/structures/item.bow.platform.snbt create mode 100644 src/main/java/net/errorcraft/itematic/component/type/ItemDamageRulesDataComponent.java create mode 100644 src/main/java/net/errorcraft/itematic/item/shooter/method/ShooterMethod.java create mode 100644 src/main/java/net/errorcraft/itematic/item/shooter/method/ShooterMethodType.java create mode 100644 src/main/java/net/errorcraft/itematic/item/shooter/method/ShooterMethodTypeKeys.java create mode 100644 src/main/java/net/errorcraft/itematic/item/shooter/method/ShooterMethodTypes.java create mode 100644 src/main/java/net/errorcraft/itematic/item/shooter/method/methods/ChargeableShooterMethod.java create mode 100644 src/main/java/net/errorcraft/itematic/item/shooter/method/methods/DirectShooterMethod.java create mode 100644 src/main/java/net/errorcraft/itematic/mixin/item/RangedWeaponItemAccessor.java diff --git a/src/client/java/net/errorcraft/itematic/mixin/client/render/item/HeldItemRendererExtender.java b/src/client/java/net/errorcraft/itematic/mixin/client/render/item/HeldItemRendererExtender.java index ed0b0f5e..42039568 100644 --- a/src/client/java/net/errorcraft/itematic/mixin/client/render/item/HeldItemRendererExtender.java +++ b/src/client/java/net/errorcraft/itematic/mixin/client/render/item/HeldItemRendererExtender.java @@ -1,17 +1,17 @@ package net.errorcraft.itematic.mixin.client.render.item; -import com.llamalad7.mixinextras.sugar.Local; import com.llamalad7.mixinextras.sugar.Share; -import com.llamalad7.mixinextras.sugar.ref.LocalRef; -import net.errorcraft.itematic.item.ItemKeys; +import com.llamalad7.mixinextras.sugar.ref.LocalIntRef; import net.errorcraft.itematic.item.component.ItemComponentTypes; import net.errorcraft.itematic.item.component.components.ShooterItemComponent; +import net.errorcraft.itematic.item.shooter.method.ShooterMethodTypes; import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.AbstractClientPlayerEntity; import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.client.render.item.HeldItemRenderer; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; +import org.objectweb.asm.Opcodes; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -20,6 +20,7 @@ import org.spongepowered.asm.mixin.injection.Slice; import java.util.Optional; +import java.util.OptionalInt; @Mixin(HeldItemRenderer.class) public class HeldItemRendererExtender { @@ -28,7 +29,10 @@ public class HeldItemRendererExtender { private MinecraftClient client; @Redirect( - method = { "getHandRenderType", "getUsingItemHandRenderType" }, + method = { + "getHandRenderType", + "getUsingItemHandRenderType" + }, at = @At( value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;isOf(Lnet/minecraft/item/Item;)Z" @@ -36,16 +40,21 @@ public class HeldItemRendererExtender { slice = @Slice( from = @At( value = "FIELD", - target = "Lnet/minecraft/item/Items;BOW:Lnet/minecraft/item/Item;" + target = "Lnet/minecraft/item/Items;BOW:Lnet/minecraft/item/Item;", + opcode = Opcodes.GETSTATIC ), to = @At( value = "FIELD", - target = "Lnet/minecraft/item/Items;CROSSBOW:Lnet/minecraft/item/Item;" + target = "Lnet/minecraft/item/Items;CROSSBOW:Lnet/minecraft/item/Item;", + opcode = Opcodes.GETSTATIC ) ) ) - private static boolean isOfForBowUseRegistryKeyCheck(ItemStack instance, Item item) { - return instance.itematic$isOf(ItemKeys.BOW); + private static boolean isOfForBowUseItemComponent(ItemStack instance, Item item) { + return instance.itematic$getComponent(ItemComponentTypes.SHOOTER) + .map(ShooterItemComponent::method) + .filter(method -> method.type() == ShooterMethodTypes.DIRECT) + .isPresent(); } @Redirect( @@ -58,15 +67,28 @@ private static boolean isOfForBowUseRegistryKeyCheck(ItemStack instance, Item it slice = @Slice( from = @At( value = "FIELD", - target = "Lnet/minecraft/item/Items;CROSSBOW:Lnet/minecraft/item/Item;" + target = "Lnet/minecraft/item/Items;CROSSBOW:Lnet/minecraft/item/Item;", + opcode = Opcodes.GETSTATIC ) ) ) - private boolean isOfForCrossbowUseItemComponent(ItemStack instance, Item item, @Share("shooterItemComponent") LocalRef shooterItemComponent) { - Optional optionalComponent = instance.itematic$getComponent(ItemComponentTypes.SHOOTER); - optionalComponent.ifPresent(shooterItemComponent::set); - return optionalComponent.map(ShooterItemComponent::isChargeable) - .orElse(false); + private boolean isOfForCrossbowUseItemComponent(ItemStack instance, Item item, AbstractClientPlayerEntity player, @Share("useDuration") LocalIntRef useDuration) { + Optional optionalShooter = instance.itematic$getComponent(ItemComponentTypes.SHOOTER); + if (optionalShooter.isEmpty()) { + return false; + } + + if (optionalShooter.get().method().type() != ShooterMethodTypes.CHARGEABLE) { + return false; + } + + OptionalInt optionalUseDuration = optionalShooter.get().useDuration(instance, player); + if (optionalUseDuration.orElse(0) <= 0) { + return false; + } + + useDuration.set(optionalUseDuration.getAsInt()); + return true; } @Redirect( @@ -77,56 +99,82 @@ private boolean isOfForCrossbowUseItemComponent(ItemStack instance, Item item, @ ordinal = 0 ) ) - private int useDifference(AbstractClientPlayerEntity instance, @Local(argsOnly = true) ItemStack stack, @Share("shooterItemComponent") LocalRef shooterItemComponent) { - return ShooterItemComponent.useDuration(stack) - instance.itematic$itemUsedTicks(); + private int useDifferenceForCrossbow(AbstractClientPlayerEntity instance, @Share("useDuration") LocalIntRef useDuration) { + return useDuration.get() - instance.itematic$itemUsedTicks(); } @Redirect( method = "renderFirstPersonItem", at = @At( value = "INVOKE", - target = "Lnet/minecraft/item/CrossbowItem;isCharged(Lnet/minecraft/item/ItemStack;)Z" + target = "Lnet/minecraft/item/ItemStack;getMaxUseTime()I" ) ) - private boolean isChargedUseItemComponent(ItemStack stack, @Share("shooterItemComponent") LocalRef shooterItemComponent) { - return shooterItemComponent.get().isCharged(stack); + private int getMaxUseTimeReturnZero(ItemStack instance) { + return 0; } @Redirect( method = "renderFirstPersonItem", at = @At( value = "INVOKE", - target = "Lnet/minecraft/item/ItemStack;getMaxUseTime()I" + target = "Lnet/minecraft/client/network/ClientPlayerEntity;getItemUseTimeLeft()I", + ordinal = 0 + ), + slice = @Slice( + from = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/render/item/HeldItemRenderer;applyEquipOffset(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/util/Arm;F)V", + ordinal = 0 + ) ) ) - private int getMaxUseTimeReturnZero(ItemStack stack, AbstractClientPlayerEntity player) { - return 0; + private int getUseTimeLeftForCrossbowUseNegatedUsedTicks(ClientPlayerEntity instance) { + return -instance.itematic$itemUsedTicks(); } @Redirect( method = "renderFirstPersonItem", at = @At( value = "INVOKE", - target = "Lnet/minecraft/client/network/ClientPlayerEntity;getItemUseTimeLeft()I" + target = "Lnet/minecraft/client/network/AbstractClientPlayerEntity;getItemUseTimeLeft()I", + ordinal = 0 + ), + slice = @Slice( + from = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/render/item/HeldItemRenderer;renderItem(Lnet/minecraft/entity/LivingEntity;Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/render/model/json/ModelTransformationMode;ZLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;I)V", + ordinal = 0 + ) ) ) - private int getUseTimeLeftUseUsedTicks(ClientPlayerEntity instance) { - return -instance.itematic$itemUsedTicks(); + private int getUseTimeLeftForUseAnimationCheckUseUsedTicks(AbstractClientPlayerEntity instance) { + return instance.itematic$itemUsedTicks(); } @Redirect( method = "renderFirstPersonItem", at = @At( value = "INVOKE", - target = "Lnet/minecraft/item/CrossbowItem;getPullTime(Lnet/minecraft/item/ItemStack;)I" + target = "Lnet/minecraft/client/network/ClientPlayerEntity;getItemUseTimeLeft()I" + ), + slice = @Slice( + from = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/render/item/HeldItemRenderer;applyEatOrDrinkTransformation(Lnet/minecraft/client/util/math/MatrixStack;FLnet/minecraft/util/Arm;Lnet/minecraft/item/ItemStack;)V" + ) ) ) - private int getPullTimeUseItemComponent(ItemStack stack) { - return ShooterItemComponent.getPullTime(stack); + private int getUseTimeLeftForBowAndSpearUseNegatedUsedTicks(ClientPlayerEntity instance) { + return -instance.itematic$itemUsedTicks(); } @Redirect( - method = { "getHandRenderType", "getUsingItemHandRenderType", "isChargedCrossbow" }, + method = { + "getHandRenderType", + "getUsingItemHandRenderType", + "isChargedCrossbow" + }, at = @At( value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;isOf(Lnet/minecraft/item/Item;)Z" @@ -134,12 +182,16 @@ private int getPullTimeUseItemComponent(ItemStack stack) { slice = @Slice( from = @At( value = "FIELD", - target = "Lnet/minecraft/item/Items;CROSSBOW:Lnet/minecraft/item/Item;" + target = "Lnet/minecraft/item/Items;CROSSBOW:Lnet/minecraft/item/Item;", + opcode = Opcodes.GETSTATIC ) ) ) - private static boolean isOfForCrossbowUseRegistryKeyCheckStatic(ItemStack instance, Item item) { - return instance.itematic$isOf(ItemKeys.CROSSBOW); + private static boolean isOfForCrossbowUseItemComponentStatic(ItemStack instance, Item item) { + return instance.itematic$getComponent(ItemComponentTypes.SHOOTER) + .map(ShooterItemComponent::method) + .filter(method -> method.type() == ShooterMethodTypes.CHARGEABLE) + .isPresent(); } @Redirect( @@ -152,7 +204,8 @@ private static boolean isOfForCrossbowUseRegistryKeyCheckStatic(ItemStack instan slice = @Slice( from = @At( value = "FIELD", - target = "Lnet/minecraft/item/Items;FILLED_MAP:Lnet/minecraft/item/Item;" + target = "Lnet/minecraft/item/Items;FILLED_MAP:Lnet/minecraft/item/Item;", + opcode = Opcodes.GETSTATIC ) ) ) diff --git a/src/gametest/java/net/errorcraft/itematic/gametest/Assert.java b/src/gametest/java/net/errorcraft/itematic/gametest/Assert.java index 8c5ef069..ec96ea1c 100644 --- a/src/gametest/java/net/errorcraft/itematic/gametest/Assert.java +++ b/src/gametest/java/net/errorcraft/itematic/gametest/Assert.java @@ -4,6 +4,8 @@ import net.minecraft.block.entity.BlockEntityType; import net.minecraft.component.DataComponentType; import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.ItemEnchantmentsComponent; +import net.minecraft.enchantment.Enchantment; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.effect.StatusEffect; import net.minecraft.fluid.Fluid; @@ -59,25 +61,25 @@ public static void itemStackIsNotEmpty(ItemStack value) { } } - public static void itemStackHasComponent(ItemStack value, DataComponentType type) { + public static void itemStackHasDataComponent(ItemStack value, DataComponentType type) { if (value.get(type) == null) { throw new GameTestException("Expected item stack to contain the " + type + " component"); } } - public static void itemStackDoesNotHaveComponent(ItemStack value, DataComponentType type) { + public static void itemStackDoesNotHaveDataComponent(ItemStack value, DataComponentType type) { if (value.get(type) != null) { throw new GameTestException("Expected item stack to not contain the " + type + " component"); } } - public static void itemStackHasComponent(ItemStack value, DataComponentType type, Consumer assertion) { - itemStackHasComponent(value, type); - assertion.accept(TestUtil.getComponent(value, type)); + public static void itemStackHasDataComponent(ItemStack value, DataComponentType type, Consumer assertion) { + itemStackHasDataComponent(value, type); + assertion.accept(TestUtil.getDataComponent(value, type)); } public static void itemStackHasPotion(ItemStack value, RegistryEntry expected) { - itemStackHasComponent(value, DataComponentTypes.POTION_CONTENTS, component -> { + itemStackHasDataComponent(value, DataComponentTypes.POTION_CONTENTS, component -> { RegistryEntry potion = component.potion() .orElseThrow(() -> new GameTestException("Expected item stack to have potion " + expected.getKey().orElseThrow())); if (expected != potion) { @@ -86,6 +88,16 @@ public static void itemStackHasPotion(ItemStack value, RegistryEntry exp }); } + public static void dataComponentHasEnchantment(ItemEnchantmentsComponent enchantments, Enchantment expected) { + for (RegistryEntry enchantment : enchantments.getEnchantments()) { + if (enchantment.value() == expected) { + return; + } + } + + throw new GameTestException("Expected data component to have enchantment " + expected); + } + public static void entityDoesNotHaveStatusEffect(LivingEntity entity, RegistryEntry effect) { if (entity.getStatusEffect(effect) != null) { throw new GameTestException("Expected entity to not have the " + effect.getKey().orElseThrow() + " status effect"); diff --git a/src/gametest/java/net/errorcraft/itematic/gametest/TestUtil.java b/src/gametest/java/net/errorcraft/itematic/gametest/TestUtil.java index 3cf678eb..71823d6a 100644 --- a/src/gametest/java/net/errorcraft/itematic/gametest/TestUtil.java +++ b/src/gametest/java/net/errorcraft/itematic/gametest/TestUtil.java @@ -6,13 +6,21 @@ import net.minecraft.block.entity.BlockEntity; import net.minecraft.block.entity.BlockEntityType; import net.minecraft.component.DataComponentType; +import net.minecraft.enchantment.Enchantment; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.item.ItemUsageContext; import net.minecraft.registry.Registries; +import net.minecraft.registry.RegistryKey; +import net.minecraft.screen.NamedScreenHandlerFactory; +import net.minecraft.screen.ScreenHandler; +import net.minecraft.screen.ScreenHandlerType; +import net.minecraft.server.world.ServerWorld; import net.minecraft.test.GameTestException; +import net.minecraft.test.PositionedException; import net.minecraft.test.TestContext; import net.minecraft.util.Hand; import net.minecraft.util.hit.BlockHitResult; @@ -26,12 +34,28 @@ public class TestUtil { private TestUtil() {} + public static ItemStack createItemStackWithSlightDamage(ServerWorld world, RegistryKey item) { + ItemStack stack = world.itematic$createStack(item); + if (!stack.isDamageable()) { + throw new AssertionError("Item " + item.getValue() + " is not damageable"); + } + + stack.setDamage(1); + return stack; + } + + public static ItemStack createItemStackWithEnchantment(ServerWorld world, RegistryKey item, Enchantment enchantment) { + ItemStack stack = world.itematic$createStack(item); + stack.addEnchantment(enchantment, 1); + return stack; + } + public static > T getItemComponent(ItemStack stack, ItemComponentType type) { return stack.itematic$getComponent(type) .orElseThrow(() -> new GameTestException("Item " + stack.itematic$key() + " does not contain the " + ItematicRegistries.ITEM_COMPONENT_TYPE.getKey(type).orElseThrow() + " item component")); } - public static T getComponent(ItemStack stack, DataComponentType type) { + public static T getDataComponent(ItemStack stack, DataComponentType type) { T component = stack.get(type); if (component == null) { throw new GameTestException("Item stack does not contain the " + type + " component"); @@ -84,4 +108,34 @@ public static void useBlock(TestContext context, BlockPos pos, PlayerEntity play BlockPos absolutePos = context.getAbsolutePos(pos); context.useBlock(pos, player, new BlockHitResult(Vec3d.ofCenter(absolutePos), direction, absolutePos, true)); } + + @SuppressWarnings("unchecked") + public static T getMenuFromBlock(TestContext context, BlockPos pos, PlayerEntity player, ScreenHandlerType type) { + BlockPos absolutePos = context.getAbsolutePos(pos); + NamedScreenHandlerFactory factory = context.getBlockState(pos).createScreenHandlerFactory(context.getWorld(), absolutePos); + if (factory == null) { + throw new PositionedException("Block does not provide a menu", absolutePos, pos, context.getTick()); + } + + ScreenHandler menu = factory.createMenu(-1, player.getInventory(), player); + if (menu == null) { + throw new PositionedException("Block does not create a menu", absolutePos, pos, context.getTick()); + } + + try { + ScreenHandlerType actualType = menu.getType(); + if (type == actualType) { + return (T) menu; + } + + throw new PositionedException( + "Block has the incorrect menu type " + Registries.SCREEN_HANDLER.getId(actualType) + ", expected " + Registries.SCREEN_HANDLER.getId(type), + absolutePos, + pos, + context.getTick() + ); + } catch (UnsupportedOperationException ignored) { + throw new PositionedException("Block does not create a menu by type", absolutePos, pos, context.getTick()); + } + } } diff --git a/src/gametest/java/net/errorcraft/itematic/gametest/block/AnvilBlockTestSuite.java b/src/gametest/java/net/errorcraft/itematic/gametest/block/AnvilBlockTestSuite.java new file mode 100644 index 00000000..3f56fb03 --- /dev/null +++ b/src/gametest/java/net/errorcraft/itematic/gametest/block/AnvilBlockTestSuite.java @@ -0,0 +1,101 @@ +package net.errorcraft.itematic.gametest.block; + +import net.errorcraft.itematic.gametest.Assert; +import net.errorcraft.itematic.gametest.TestUtil; +import net.errorcraft.itematic.item.ItemKeys; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.enchantment.Enchantments; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.AnvilScreenHandler; +import net.minecraft.screen.ScreenHandlerType; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.test.GameTest; +import net.minecraft.test.TestContext; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.GameMode; + +public class AnvilBlockTestSuite { + private static final BlockPos BLOCK_POSITION = new BlockPos(1, 2, 1); + + @GameTest(templateName = "itematic:block.anvil") + public void combiningEnchantedItemsWithSameIdInAnvilCombinesEnchantments(TestContext context) { + ServerWorld world = context.getWorld(); + PlayerEntity player = context.createMockPlayer(GameMode.SURVIVAL); + AnvilScreenHandler anvilMenu = TestUtil.getMenuFromBlock(context, BLOCK_POSITION, player, ScreenHandlerType.ANVIL); + anvilMenu.getSlot(0) + .setStack(TestUtil.createItemStackWithEnchantment(world, ItemKeys.IRON_PICKAXE, Enchantments.UNBREAKING)); + anvilMenu.getSlot(1) + .setStack(TestUtil.createItemStackWithEnchantment(world, ItemKeys.IRON_PICKAXE, Enchantments.EFFICIENCY)); + context.addInstantFinalTask(() -> { + ItemStack result = anvilMenu.getSlot(2).getStack(); + Assert.itemStackIsOf(result, ItemKeys.IRON_PICKAXE); + Assert.itemStackHasDataComponent(result, DataComponentTypes.ENCHANTMENTS, enchantments -> { + Assert.dataComponentHasEnchantment(enchantments, Enchantments.UNBREAKING); + Assert.dataComponentHasEnchantment(enchantments, Enchantments.EFFICIENCY); + }); + }); + } + + @GameTest(templateName = "itematic:block.anvil") + public void combiningEnchantedItemsWithDifferentIdsInAnvilRejectsCombination(TestContext context) { + ServerWorld world = context.getWorld(); + PlayerEntity player = context.createMockPlayer(GameMode.SURVIVAL); + AnvilScreenHandler anvilMenu = TestUtil.getMenuFromBlock(context, BLOCK_POSITION, player, ScreenHandlerType.ANVIL); + anvilMenu.getSlot(0) + .setStack(TestUtil.createItemStackWithEnchantment(world, ItemKeys.IRON_PICKAXE, Enchantments.UNBREAKING)); + anvilMenu.getSlot(1) + .setStack(TestUtil.createItemStackWithEnchantment(world, ItemKeys.DIAMOND_PICKAXE, Enchantments.EFFICIENCY)); + context.addInstantFinalTask(() -> Assert.itemStackIsEmpty(anvilMenu.getSlot(2).getStack())); + } + + @GameTest(templateName = "itematic:block.anvil") + public void combiningItemWithEnchantedBookInAnvilAddsEnchantment(TestContext context) { + ServerWorld world = context.getWorld(); + PlayerEntity player = context.createMockPlayer(GameMode.SURVIVAL); + AnvilScreenHandler anvilMenu = TestUtil.getMenuFromBlock(context, BLOCK_POSITION, player, ScreenHandlerType.ANVIL); + anvilMenu.getSlot(0) + .setStack(TestUtil.createItemStackWithEnchantment(world, ItemKeys.IRON_PICKAXE, Enchantments.UNBREAKING)); + anvilMenu.getSlot(1) + .setStack(TestUtil.createItemStackWithEnchantment(world, ItemKeys.ENCHANTED_BOOK, Enchantments.EFFICIENCY)); + context.addInstantFinalTask(() -> { + ItemStack result = anvilMenu.getSlot(2).getStack(); + Assert.itemStackIsOf(result, ItemKeys.IRON_PICKAXE); + Assert.itemStackHasDataComponent(result, DataComponentTypes.ENCHANTMENTS, enchantments -> { + Assert.dataComponentHasEnchantment(enchantments, Enchantments.UNBREAKING); + Assert.dataComponentHasEnchantment(enchantments, Enchantments.EFFICIENCY); + }); + }); + } + + @GameTest(templateName = "itematic:block.anvil") + public void combiningItemWithEnchantedBookWithIncompatibleEnchantmentInAnvilRejectsCombination(TestContext context) { + ServerWorld world = context.getWorld(); + PlayerEntity player = context.createMockPlayer(GameMode.SURVIVAL); + AnvilScreenHandler anvilMenu = TestUtil.getMenuFromBlock(context, BLOCK_POSITION, player, ScreenHandlerType.ANVIL); + anvilMenu.getSlot(0) + .setStack(TestUtil.createItemStackWithEnchantment(world, ItemKeys.IRON_PICKAXE, Enchantments.UNBREAKING)); + anvilMenu.getSlot(1) + .setStack(TestUtil.createItemStackWithEnchantment(world, ItemKeys.ENCHANTED_BOOK, Enchantments.SHARPNESS)); + context.addInstantFinalTask(() -> Assert.itemStackIsEmpty(anvilMenu.getSlot(2).getStack())); + } + + @GameTest(templateName = "itematic:block.anvil") + public void combiningEnchantedBooksInAnvilCombinesEnchantments(TestContext context) { + ServerWorld world = context.getWorld(); + PlayerEntity player = context.createMockPlayer(GameMode.SURVIVAL); + AnvilScreenHandler anvilMenu = TestUtil.getMenuFromBlock(context, BLOCK_POSITION, player, ScreenHandlerType.ANVIL); + anvilMenu.getSlot(0) + .setStack(TestUtil.createItemStackWithEnchantment(world, ItemKeys.ENCHANTED_BOOK, Enchantments.UNBREAKING)); + anvilMenu.getSlot(1) + .setStack(TestUtil.createItemStackWithEnchantment(world, ItemKeys.ENCHANTED_BOOK, Enchantments.EFFICIENCY)); + context.addInstantFinalTask(() -> { + ItemStack result = anvilMenu.getSlot(2).getStack(); + Assert.itemStackIsOf(result, ItemKeys.ENCHANTED_BOOK); + Assert.itemStackHasDataComponent(result, DataComponentTypes.STORED_ENCHANTMENTS, storedEnchantments -> { + Assert.dataComponentHasEnchantment(storedEnchantments, Enchantments.UNBREAKING); + Assert.dataComponentHasEnchantment(storedEnchantments, Enchantments.EFFICIENCY); + }); + }); + } +} diff --git a/src/gametest/java/net/errorcraft/itematic/gametest/block/EnchantingTableTestSuite.java b/src/gametest/java/net/errorcraft/itematic/gametest/block/EnchantingTableTestSuite.java new file mode 100644 index 00000000..efcca339 --- /dev/null +++ b/src/gametest/java/net/errorcraft/itematic/gametest/block/EnchantingTableTestSuite.java @@ -0,0 +1,105 @@ +package net.errorcraft.itematic.gametest.block; + +import net.errorcraft.itematic.gametest.Assert; +import net.errorcraft.itematic.gametest.TestUtil; +import net.errorcraft.itematic.item.ItemKeys; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.enchantment.Enchantments; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.EnchantmentScreenHandler; +import net.minecraft.screen.ScreenHandlerType; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.test.GameTest; +import net.minecraft.test.TestContext; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.GameMode; + +public class EnchantingTableTestSuite { + private static final BlockPos BLOCK_POSITION = new BlockPos(1, 2, 1); + + @GameTest(templateName = "itematic:block.enchanting_table") + public void placingEnchantableItemWithoutEnchantmentsSuggestsEnchantments(TestContext context) { + ServerWorld world = context.getWorld(); + PlayerEntity player = context.createMockPlayer(GameMode.SURVIVAL); + EnchantmentScreenHandler enchantmentMenu = TestUtil.getMenuFromBlock(context, BLOCK_POSITION, player, ScreenHandlerType.ENCHANTMENT); + enchantmentMenu.getSlot(0) + .setStack(world.itematic$createStack(ItemKeys.IRON_PICKAXE)); + enchantmentMenu.getSlot(1) + .setStack(world.itematic$createStack(ItemKeys.LAPIS_LAZULI)); + context.addInstantFinalTask(() -> { + context.assertTrue(enchantmentMenu.enchantmentPower[0] > 0, "Expected enchantments to be suggested"); + }); + } + + @GameTest(templateName = "itematic:block.enchanting_table") + public void placingUnenchantableItemInEnchantingTableDoesNotSuggestEnchantments(TestContext context) { + ServerWorld world = context.getWorld(); + PlayerEntity player = context.createMockPlayer(GameMode.SURVIVAL); + EnchantmentScreenHandler enchantmentMenu = TestUtil.getMenuFromBlock(context, BLOCK_POSITION, player, ScreenHandlerType.ENCHANTMENT); + enchantmentMenu.getSlot(0) + .setStack(world.itematic$createStack(ItemKeys.STICK)); + enchantmentMenu.getSlot(1) + .setStack(world.itematic$createStack(ItemKeys.LAPIS_LAZULI)); + context.addInstantFinalTask(() -> { + context.assertTrue(enchantmentMenu.enchantmentPower[0] == 0, "Expected no enchantments to be suggested"); + }); + } + + @GameTest(templateName = "itematic:block.enchanting_table") + public void placingEnchantableItemWithEnchantmentsInEnchantingTableDoesNotSuggestEnchantments(TestContext context) { + ServerWorld world = context.getWorld(); + PlayerEntity player = context.createMockPlayer(GameMode.SURVIVAL); + EnchantmentScreenHandler enchantmentMenu = TestUtil.getMenuFromBlock(context, BLOCK_POSITION, player, ScreenHandlerType.ENCHANTMENT); + enchantmentMenu.getSlot(0) + .setStack(TestUtil.createItemStackWithEnchantment(world, ItemKeys.IRON_PICKAXE, Enchantments.UNBREAKING)); + enchantmentMenu.getSlot(1) + .setStack(world.itematic$createStack(ItemKeys.LAPIS_LAZULI)); + context.addInstantFinalTask(() -> { + context.assertTrue(enchantmentMenu.enchantmentPower[0] == 0, "Expected no enchantments to be suggested"); + }); + } + + @GameTest(templateName = "itematic:block.enchanting_table") + public void enchantingEnchantableItemInEnchantingTableAddsEnchantments(TestContext context) { + ServerWorld world = context.getWorld(); + PlayerEntity player = context.createMockPlayer(GameMode.SURVIVAL); + player.experienceLevel = 1000; + EnchantmentScreenHandler enchantmentMenu = TestUtil.getMenuFromBlock(context, BLOCK_POSITION, player, ScreenHandlerType.ENCHANTMENT); + enchantmentMenu.getSlot(0) + .setStack(world.itematic$createStack(ItemKeys.IRON_PICKAXE)); + enchantmentMenu.getSlot(1) + .setStack(world.itematic$createStack(ItemKeys.LAPIS_LAZULI)); + enchantmentMenu.onButtonClick(player, 0); + context.addInstantFinalTask(() -> { + ItemStack result = enchantmentMenu.getSlot(0).getStack(); + Assert.itemStackIsOf(result, ItemKeys.IRON_PICKAXE); + Assert.itemStackHasDataComponent(result, DataComponentTypes.ENCHANTMENTS, enchantments -> { + context.assertFalse(enchantments.isEmpty(), "Expected enchantments to be added to " + DataComponentTypes.ENCHANTMENTS); + }); + }); + } + + @GameTest(templateName = "itematic:block.enchanting_table") + public void enchantingBookInEnchantingTableTransformsItemIntoEnchantedBookAndAddsEnchantmentsToStoredEnchantments(TestContext context) { + ServerWorld world = context.getWorld(); + PlayerEntity player = context.createMockPlayer(GameMode.SURVIVAL); + player.experienceLevel = 1000; + EnchantmentScreenHandler enchantmentMenu = TestUtil.getMenuFromBlock(context, BLOCK_POSITION, player, ScreenHandlerType.ENCHANTMENT); + enchantmentMenu.getSlot(0) + .setStack(world.itematic$createStack(ItemKeys.BOOK)); + enchantmentMenu.getSlot(1) + .setStack(world.itematic$createStack(ItemKeys.LAPIS_LAZULI)); + enchantmentMenu.onButtonClick(player, 0); + context.addInstantFinalTask(() -> { + ItemStack result = enchantmentMenu.getSlot(0).getStack(); + Assert.itemStackIsOf(result, ItemKeys.ENCHANTED_BOOK); + Assert.itemStackHasDataComponent(result, DataComponentTypes.ENCHANTMENTS, enchantments -> { + context.assertTrue(enchantments.isEmpty(), "Expected enchantments not to be added to " + DataComponentTypes.ENCHANTMENTS); + }); + Assert.itemStackHasDataComponent(result, DataComponentTypes.STORED_ENCHANTMENTS, storedEnchantments -> { + context.assertFalse(storedEnchantments.isEmpty(), "Expected enchantments to be added to " + DataComponentTypes.STORED_ENCHANTMENTS); + }); + }); + } +} diff --git a/src/gametest/java/net/errorcraft/itematic/gametest/block/GrindstoneTestSuite.java b/src/gametest/java/net/errorcraft/itematic/gametest/block/GrindstoneTestSuite.java new file mode 100644 index 00000000..81569b7f --- /dev/null +++ b/src/gametest/java/net/errorcraft/itematic/gametest/block/GrindstoneTestSuite.java @@ -0,0 +1,82 @@ +package net.errorcraft.itematic.gametest.block; + +import net.errorcraft.itematic.gametest.Assert; +import net.errorcraft.itematic.gametest.TestUtil; +import net.errorcraft.itematic.item.ItemKeys; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.enchantment.Enchantments; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.GrindstoneScreenHandler; +import net.minecraft.screen.ScreenHandlerType; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.test.GameTest; +import net.minecraft.test.TestContext; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.GameMode; + +public class GrindstoneTestSuite { + private static final BlockPos BLOCK_POSITION = new BlockPos(1, 2, 1); + + @GameTest(templateName = "itematic:block.grindstone") + public void placingEnchantedItemInGrindstoneDisenchantsItem(TestContext context) { + ServerWorld world = context.getWorld(); + PlayerEntity player = context.createMockPlayer(GameMode.SURVIVAL); + GrindstoneScreenHandler grindstoneMenu = TestUtil.getMenuFromBlock(context, BLOCK_POSITION, player, ScreenHandlerType.GRINDSTONE); + grindstoneMenu.getSlot(0) + .setStack(TestUtil.createItemStackWithEnchantment(world, ItemKeys.IRON_PICKAXE, Enchantments.UNBREAKING)); + context.addInstantFinalTask(() -> { + ItemStack result = grindstoneMenu.getSlot(2).getStack(); + Assert.itemStackIsOf(result, ItemKeys.IRON_PICKAXE); + Assert.itemStackHasDataComponent(result, DataComponentTypes.ENCHANTMENTS, enchantments -> { + context.assertTrue(enchantments.isEmpty(), "Expected item to not have any enchantments"); + }); + }); + } + + @GameTest(templateName = "itematic:block.grindstone") + public void placingEnchantedBookInGrindstoneTransformsItemIntoBook(TestContext context) { + ServerWorld world = context.getWorld(); + PlayerEntity player = context.createMockPlayer(GameMode.SURVIVAL); + GrindstoneScreenHandler grindstoneMenu = TestUtil.getMenuFromBlock(context, BLOCK_POSITION, player, ScreenHandlerType.GRINDSTONE); + grindstoneMenu.getSlot(0) + .setStack(TestUtil.createItemStackWithEnchantment(world, ItemKeys.ENCHANTED_BOOK, Enchantments.UNBREAKING)); + context.addInstantFinalTask(() -> { + ItemStack result = grindstoneMenu.getSlot(2).getStack(); + Assert.itemStackIsOf(result, ItemKeys.BOOK); + Assert.itemStackHasDataComponent(result, DataComponentTypes.ENCHANTMENTS, enchantments -> { + context.assertTrue(enchantments.isEmpty(), "Expected item to not have any enchantments"); + }); + }); + } + + @GameTest(templateName = "itematic:block.grindstone") + public void placingDamageableItemsWithSameIsInGrindstoneRepairsItem(TestContext context) { + ServerWorld world = context.getWorld(); + PlayerEntity player = context.createMockPlayer(GameMode.SURVIVAL); + GrindstoneScreenHandler grindstoneMenu = TestUtil.getMenuFromBlock(context, BLOCK_POSITION, player, ScreenHandlerType.GRINDSTONE); + grindstoneMenu.getSlot(0) + .setStack(TestUtil.createItemStackWithSlightDamage(world, ItemKeys.IRON_PICKAXE)); + grindstoneMenu.getSlot(1) + .setStack(TestUtil.createItemStackWithSlightDamage(world, ItemKeys.IRON_PICKAXE)); + context.addInstantFinalTask(() -> { + ItemStack result = grindstoneMenu.getSlot(2).getStack(); + Assert.itemStackIsOf(result, ItemKeys.IRON_PICKAXE); + context.assertFalse(result.isDamaged(), "Expected item stack not to be damaged"); + }); + } + + @GameTest(templateName = "itematic:block.grindstone") + public void placingDamageableItemsWithDifferentIdsInGrindstoneRejectsRepair(TestContext context) { + ServerWorld world = context.getWorld(); + PlayerEntity player = context.createMockPlayer(GameMode.SURVIVAL); + GrindstoneScreenHandler grindstoneMenu = TestUtil.getMenuFromBlock(context, BLOCK_POSITION, player, ScreenHandlerType.GRINDSTONE); + grindstoneMenu.getSlot(0) + .setStack(TestUtil.createItemStackWithSlightDamage(world, ItemKeys.IRON_PICKAXE)); + grindstoneMenu.getSlot(1) + .setStack(TestUtil.createItemStackWithSlightDamage(world, ItemKeys.DIAMOND_PICKAXE)); + context.addInstantFinalTask(() -> { + Assert.itemStackIsEmpty(grindstoneMenu.getSlot(2).getStack()); + }); + } +} diff --git a/src/gametest/java/net/errorcraft/itematic/gametest/block/WaterCauldronBlockTestSuite.java b/src/gametest/java/net/errorcraft/itematic/gametest/block/WaterCauldronBlockTestSuite.java index 63476278..0f0c1a5a 100644 --- a/src/gametest/java/net/errorcraft/itematic/gametest/block/WaterCauldronBlockTestSuite.java +++ b/src/gametest/java/net/errorcraft/itematic/gametest/block/WaterCauldronBlockTestSuite.java @@ -36,6 +36,6 @@ public void usingColoredWolfArmorOnWaterCauldronClearsColor(TestContext context) player.setStackInHand(Hand.MAIN_HAND, stack); world.spawnEntity(player); context.useBlock(WATER_CAULDRON_POSITION, player); - context.addFinalTask(() -> Assert.itemStackDoesNotHaveComponent(player.getStackInHand(Hand.MAIN_HAND), DataComponentTypes.DYED_COLOR)); + context.addFinalTask(() -> Assert.itemStackDoesNotHaveDataComponent(player.getStackInHand(Hand.MAIN_HAND), DataComponentTypes.DYED_COLOR)); } } diff --git a/src/gametest/java/net/errorcraft/itematic/gametest/entity/passive/MooshroomEntityTestSuite.java b/src/gametest/java/net/errorcraft/itematic/gametest/entity/passive/MooshroomEntityTestSuite.java index b3dda6e1..bf0faa02 100644 --- a/src/gametest/java/net/errorcraft/itematic/gametest/entity/passive/MooshroomEntityTestSuite.java +++ b/src/gametest/java/net/errorcraft/itematic/gametest/entity/passive/MooshroomEntityTestSuite.java @@ -34,7 +34,7 @@ public void usingFlowerOnBrownMooshroomGivesMooshroomSuspiciousEffects(TestConte context.assertTrue(bowlResult.isAccepted(), "Expected interaction with bowl on brown Mooshroom to be successful"); ItemStack heldStack = player.getStackInHand(Hand.MAIN_HAND); Assert.itemStackIsOf(heldStack, ItemKeys.SUSPICIOUS_STEW); - Assert.itemStackHasComponent(heldStack, DataComponentTypes.SUSPICIOUS_STEW_EFFECTS, + Assert.itemStackHasDataComponent(heldStack, DataComponentTypes.SUSPICIOUS_STEW_EFFECTS, component -> context.assertTrue(!component.effects().isEmpty(), "Expected item stack to have suspicious effects") ); }); diff --git a/src/gametest/java/net/errorcraft/itematic/gametest/item/BeetrootSoupTestSuite.java b/src/gametest/java/net/errorcraft/itematic/gametest/item/BeetrootSoupTestSuite.java new file mode 100644 index 00000000..5d991041 --- /dev/null +++ b/src/gametest/java/net/errorcraft/itematic/gametest/item/BeetrootSoupTestSuite.java @@ -0,0 +1,30 @@ +package net.errorcraft.itematic.gametest.item; + +import net.errorcraft.itematic.item.ItemKeys; +import net.fabricmc.fabric.api.gametest.v1.FabricGameTest; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.test.GameTest; +import net.minecraft.test.TestContext; +import net.minecraft.util.Hand; +import net.minecraft.world.GameMode; + +public class BeetrootSoupTestSuite { + @GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE) + public void eatingBeetrootSoupLeavesBowlAfterConsuming(TestContext context) { + PlayerEntity player = context.createMockPlayer(GameMode.SURVIVAL); + player.getHungerManager().setFoodLevel(0); + ServerWorld world = context.getWorld(); + ItemStack stack = world.itematic$createStack(ItemKeys.BEETROOT_SOUP); + player.setStackInHand(Hand.MAIN_HAND, stack); + world.spawnEntity(player); + context.createTimedTaskRunner() + .createAndAddReported(() -> stack.use(world, player, Hand.MAIN_HAND)) + .expectMinDurationAndRun( + stack.itematic$useDuration(player), + () -> context.assertTrue(player.getInventory().contains(s -> s.itematic$isOf(ItemKeys.BOWL)), "Expected Player to have a Bowl in their inventory") + ) + .completeIfSuccessful(); + } +} diff --git a/src/gametest/java/net/errorcraft/itematic/gametest/item/BowTestSuite.java b/src/gametest/java/net/errorcraft/itematic/gametest/item/BowTestSuite.java new file mode 100644 index 00000000..b99e4c14 --- /dev/null +++ b/src/gametest/java/net/errorcraft/itematic/gametest/item/BowTestSuite.java @@ -0,0 +1,38 @@ +package net.errorcraft.itematic.gametest.item; + +import net.errorcraft.itematic.gametest.TestUtil; +import net.errorcraft.itematic.item.ItemKeys; +import net.minecraft.enchantment.Enchantments; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.test.GameTest; +import net.minecraft.test.TestContext; +import net.minecraft.util.Hand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.GameMode; + +public class BowTestSuite { + private static final BlockPos SPAWN_POSITION = new BlockPos(1, 2, 1); + + @GameTest(templateName = "itematic:item.bow.platform") + public void usingBowWithMultishotSpawnsMultipleArrows(TestContext context) { + ServerWorld world = context.getWorld(); + ItemStack stack = world.itematic$createStack(ItemKeys.BOW); + stack.addEnchantment(Enchantments.MULTISHOT, 1); + ItemStack ammunition = world.itematic$createStack(ItemKeys.ARROW); + PlayerEntity player = context.createMockPlayer(GameMode.SURVIVAL); + TestUtil.setEntityPos(context, player, SPAWN_POSITION); + player.setStackInHand(Hand.MAIN_HAND, stack); + player.getInventory().insertStack(ammunition); + world.spawnEntity(player); + context.createTimedTaskRunner() + .createAndAddReported(() -> stack.use(world, player, Hand.MAIN_HAND)) + .expectMinDurationAndRun(20, () -> { + player.stopUsingItem(); + context.expectEntities(EntityType.ARROW, 3); + }) + .completeIfSuccessful(); + } +} diff --git a/src/gametest/java/net/errorcraft/itematic/gametest/item/BundleTestSuite.java b/src/gametest/java/net/errorcraft/itematic/gametest/item/BundleTestSuite.java index 35c74240..ec70ed68 100644 --- a/src/gametest/java/net/errorcraft/itematic/gametest/item/BundleTestSuite.java +++ b/src/gametest/java/net/errorcraft/itematic/gametest/item/BundleTestSuite.java @@ -32,7 +32,7 @@ public void addingNormalItemToBundleAddsIt(TestContext context) { context.addInstantFinalTask(() -> { context.assertTrue(bundleStack.onStackClicked(slot, ClickType.RIGHT, player), "Expected right-clicking with Bundle to be successful"); Assert.itemStackIsEmpty(slot.getStack()); - Assert.itemStackHasComponent(bundleStack, DataComponentTypes.BUNDLE_CONTENTS, bundleContents -> { + Assert.itemStackHasDataComponent(bundleStack, DataComponentTypes.BUNDLE_CONTENTS, bundleContents -> { Assert.itemStackIsOf(bundleContents.get(0), ItemKeys.STICK); }); }); @@ -50,7 +50,7 @@ public void addingShulkerBoxToBundleRejectsIt(TestContext context) { context.addInstantFinalTask(() -> { context.assertTrue(bundleStack.onStackClicked(slot, ClickType.RIGHT, player), "Expected right-clicking with Bundle to be successful"); Assert.itemStackIsNotEmpty(slot.getStack()); - Assert.itemStackHasComponent(bundleStack, DataComponentTypes.BUNDLE_CONTENTS, bundleContents -> { + Assert.itemStackHasDataComponent(bundleStack, DataComponentTypes.BUNDLE_CONTENTS, bundleContents -> { context.assertTrue(bundleContents.isEmpty(), "Expected Bundle to be empty"); }); }); @@ -68,7 +68,7 @@ public void addingBundleToBundleAddsItWithPenalty(TestContext context) { context.addInstantFinalTask(() -> { context.assertTrue(bundleStack.onStackClicked(slot, ClickType.RIGHT, player), "Expected right-clicking with Bundle to be successful"); Assert.itemStackIsEmpty(slot.getStack()); - Assert.itemStackHasComponent(bundleStack, DataComponentTypes.BUNDLE_CONTENTS, bundleContents -> { + Assert.itemStackHasDataComponent(bundleStack, DataComponentTypes.BUNDLE_CONTENTS, bundleContents -> { Assert.itemStackIsOf(bundleContents.get(0), ItemKeys.BUNDLE); }); context.assertEquals( diff --git a/src/gametest/java/net/errorcraft/itematic/gametest/item/CrossbowTestSuite.java b/src/gametest/java/net/errorcraft/itematic/gametest/item/CrossbowTestSuite.java new file mode 100644 index 00000000..d130d980 --- /dev/null +++ b/src/gametest/java/net/errorcraft/itematic/gametest/item/CrossbowTestSuite.java @@ -0,0 +1,38 @@ +package net.errorcraft.itematic.gametest.item; + +import net.errorcraft.itematic.gametest.Assert; +import net.errorcraft.itematic.item.ItemKeys; +import net.fabricmc.fabric.api.gametest.v1.FabricGameTest; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.enchantment.Enchantments; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.test.GameTest; +import net.minecraft.test.TestContext; +import net.minecraft.util.Hand; +import net.minecraft.world.GameMode; + +public class CrossbowTestSuite { + @GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE) + public void usingCrossbowWithInfinityChargesArrowFromInventoryButDoesNotConsumeTheArrow(TestContext context) { + ServerWorld world = context.getWorld(); + ItemStack stack = world.itematic$createStack(ItemKeys.CROSSBOW); + stack.addEnchantment(Enchantments.INFINITY, 1); + ItemStack ammunition = world.itematic$createStack(ItemKeys.ARROW); + PlayerEntity player = context.createMockPlayer(GameMode.SURVIVAL); + player.setStackInHand(Hand.MAIN_HAND, stack); + player.getInventory().insertStack(ammunition); + world.spawnEntity(player); + context.createTimedTaskRunner() + .createAndAddReported(() -> stack.use(world, player, Hand.MAIN_HAND)) + .expectMinDurationAndRun(stack.itematic$useDuration(player), () -> { + player.stopUsingItem(); + Assert.itemStackHasDataComponent(player.getStackInHand(Hand.MAIN_HAND), DataComponentTypes.CHARGED_PROJECTILES, + component -> context.assertTrue(component.itematic$contains(ItemKeys.ARROW), "Expected item stack to have an Arrow as a charged projectile") + ); + context.assertTrue(player.getInventory().contains(s -> s.itematic$isOf(ItemKeys.ARROW)), "Expected player to have an Arrow in their inventory"); + }) + .completeIfSuccessful(); + } +} diff --git a/src/gametest/java/net/errorcraft/itematic/gametest/item/HoneyBottleTestSuite.java b/src/gametest/java/net/errorcraft/itematic/gametest/item/HoneyBottleTestSuite.java index 258785eb..2b58b687 100644 --- a/src/gametest/java/net/errorcraft/itematic/gametest/item/HoneyBottleTestSuite.java +++ b/src/gametest/java/net/errorcraft/itematic/gametest/item/HoneyBottleTestSuite.java @@ -27,7 +27,7 @@ public void consumingHoneyBottleRemovesPoisonStatusEffect(TestContext context) { world.spawnEntity(player); stack.use(world, player, Hand.MAIN_HAND); context.createTimedTaskRunner().expectMinDurationAndRun( - TestUtil.getComponent(stack, ItematicDataComponentTypes.USE_DURATION).ticks(stack, player), + TestUtil.getDataComponent(stack, ItematicDataComponentTypes.USE_DURATION).ticks(stack, player), () -> Assert.entityDoesNotHaveStatusEffect(player, StatusEffects.POISON) ).completeIfSuccessful(); } diff --git a/src/gametest/java/net/errorcraft/itematic/gametest/item/component/ConsumableItemComponentTestSuite.java b/src/gametest/java/net/errorcraft/itematic/gametest/item/component/ConsumableItemComponentTestSuite.java index 9e04e6fc..97ceb116 100644 --- a/src/gametest/java/net/errorcraft/itematic/gametest/item/component/ConsumableItemComponentTestSuite.java +++ b/src/gametest/java/net/errorcraft/itematic/gametest/item/component/ConsumableItemComponentTestSuite.java @@ -24,7 +24,7 @@ public void consumingHoneyBottleReplacesItemWithGlassBottle(TestContext context) world.spawnEntity(player); stack.use(world, player, Hand.MAIN_HAND); context.createTimedTaskRunner().expectMinDurationAndRun( - TestUtil.getComponent(stack, ItematicDataComponentTypes.USE_DURATION).ticks(stack, player), + TestUtil.getDataComponent(stack, ItematicDataComponentTypes.USE_DURATION).ticks(stack, player), () -> Assert.itemStackIsOf(player.getStackInHand(Hand.MAIN_HAND), ItemKeys.GLASS_BOTTLE) ).completeIfSuccessful(); } diff --git a/src/gametest/java/net/errorcraft/itematic/gametest/item/component/FoodItemComponentTestSuite.java b/src/gametest/java/net/errorcraft/itematic/gametest/item/component/FoodItemComponentTestSuite.java index 8d4aa205..e8e49204 100644 --- a/src/gametest/java/net/errorcraft/itematic/gametest/item/component/FoodItemComponentTestSuite.java +++ b/src/gametest/java/net/errorcraft/itematic/gametest/item/component/FoodItemComponentTestSuite.java @@ -31,7 +31,7 @@ public void eatingFoodItemAddsNutrition(TestContext context) { FoodItemComponent component = TestUtil.getItemComponent(stack, ItemComponentTypes.FOOD); stack.use(world, player, Hand.MAIN_HAND); context.createTimedTaskRunner().expectMinDurationAndRun( - TestUtil.getComponent(stack, ItematicDataComponentTypes.USE_DURATION).ticks(stack, player), + TestUtil.getDataComponent(stack, ItematicDataComponentTypes.USE_DURATION).ticks(stack, player), () -> { Assert.itemStackIsEmpty(player.getStackInHand(Hand.MAIN_HAND)); Assert.areIntsEqual( @@ -55,7 +55,7 @@ public void eatingSuspiciousStewAddsSuspiciousEffects(TestContext context) { world.spawnEntity(player); stack.use(world, player, Hand.MAIN_HAND); context.createTimedTaskRunner().expectMinDurationAndRun( - TestUtil.getComponent(stack, ItematicDataComponentTypes.USE_DURATION).ticks(stack, player), + TestUtil.getDataComponent(stack, ItematicDataComponentTypes.USE_DURATION).ticks(stack, player), () -> { Assert.forAll(effects, effect -> context.expectEntityHasEffect(player, effect.effect(), effect.createStatusEffectInstance().getAmplifier())); Assert.itemStackIsOf(player.getStackInHand(Hand.MAIN_HAND), ItemKeys.BOWL); diff --git a/src/gametest/java/net/errorcraft/itematic/gametest/item/component/ItemHolderItemComponentTestSuite.java b/src/gametest/java/net/errorcraft/itematic/gametest/item/component/ItemHolderItemComponentTestSuite.java index 1e9e4051..ab22e814 100644 --- a/src/gametest/java/net/errorcraft/itematic/gametest/item/component/ItemHolderItemComponentTestSuite.java +++ b/src/gametest/java/net/errorcraft/itematic/gametest/item/component/ItemHolderItemComponentTestSuite.java @@ -37,7 +37,7 @@ public void rightClickingOnStackWithItemHolderAddsStackToItemHolder(TestContext context.addInstantFinalTask(() -> { context.assertTrue(success, "Expected right clicking with item holder to be successful"); Assert.itemStackIsEmpty(inventory.getStack(SLOT)); - Assert.itemStackHasComponent(stack, DataComponentTypes.BUNDLE_CONTENTS, + Assert.itemStackHasDataComponent(stack, DataComponentTypes.BUNDLE_CONTENTS, component -> Assert.itemStackIsOf(component.get(0), ItemKeys.STICK) ); }); @@ -57,7 +57,7 @@ public void rightClickingOnEmptySlotPlacesLastStackFromItemHolderInSlot(TestCont context.addInstantFinalTask(() -> { context.assertTrue(success, "Expected right clicking with item holder to be successful"); Assert.itemStackIsOf(inventory.getStack(SLOT), ItemKeys.STICK); - Assert.itemStackHasComponent(stack, DataComponentTypes.BUNDLE_CONTENTS, + Assert.itemStackHasDataComponent(stack, DataComponentTypes.BUNDLE_CONTENTS, component -> context.assertTrue(component.isEmpty(), "Expected item holder to be empty") ); }); @@ -78,7 +78,7 @@ public void rightClickingOnItemHolderWithStackAddsStackToItemHolder(TestContext context.addInstantFinalTask(() -> { context.assertTrue(success, "Expected right clicking on item holder to be successful"); Assert.itemStackIsEmpty(cursorStack.get()); - Assert.itemStackHasComponent(inventory.getStack(SLOT), DataComponentTypes.BUNDLE_CONTENTS, + Assert.itemStackHasDataComponent(inventory.getStack(SLOT), DataComponentTypes.BUNDLE_CONTENTS, component -> Assert.itemStackIsOf(component.get(0), ItemKeys.STICK) ); }); @@ -101,7 +101,7 @@ public void rightClickingOnItemHolderRemovesStackFromItemHolder(TestContext cont context.addInstantFinalTask(() -> { context.assertTrue(success, "Expected right clicking on item holder to be successful"); Assert.itemStackIsOf(cursorStack.get(), ItemKeys.STICK); - Assert.itemStackHasComponent(inventory.getStack(SLOT), DataComponentTypes.BUNDLE_CONTENTS, + Assert.itemStackHasDataComponent(inventory.getStack(SLOT), DataComponentTypes.BUNDLE_CONTENTS, component -> context.assertTrue(component.isEmpty(), "Expected item holder to be empty") ); }); diff --git a/src/gametest/java/net/errorcraft/itematic/gametest/item/component/MappableItemComponentTestSuite.java b/src/gametest/java/net/errorcraft/itematic/gametest/item/component/MappableItemComponentTestSuite.java index e719f87d..92c83ec4 100644 --- a/src/gametest/java/net/errorcraft/itematic/gametest/item/component/MappableItemComponentTestSuite.java +++ b/src/gametest/java/net/errorcraft/itematic/gametest/item/component/MappableItemComponentTestSuite.java @@ -23,7 +23,7 @@ public void usingMapFillsMap(TestContext context) { ItemStack resultStack = stack.use(world, player, Hand.MAIN_HAND).getValue(); context.addInstantFinalTask(() -> { Assert.itemStackIsOf(resultStack, ItemKeys.FILLED_MAP); - Assert.itemStackHasComponent(resultStack, DataComponentTypes.MAP_ID); + Assert.itemStackHasDataComponent(resultStack, DataComponentTypes.MAP_ID); }); } } diff --git a/src/gametest/java/net/errorcraft/itematic/gametest/item/component/PotionItemComponentTestSuite.java b/src/gametest/java/net/errorcraft/itematic/gametest/item/component/PotionItemComponentTestSuite.java index f4139431..6c5790d1 100644 --- a/src/gametest/java/net/errorcraft/itematic/gametest/item/component/PotionItemComponentTestSuite.java +++ b/src/gametest/java/net/errorcraft/itematic/gametest/item/component/PotionItemComponentTestSuite.java @@ -25,7 +25,7 @@ public void drinkingPotionItemAddsEffects(TestContext context) { world.spawnEntity(player); stack.use(world, player, Hand.MAIN_HAND); context.createTimedTaskRunner().expectMinDurationAndRun( - TestUtil.getComponent(stack, ItematicDataComponentTypes.USE_DURATION).ticks(stack, player), + TestUtil.getDataComponent(stack, ItematicDataComponentTypes.USE_DURATION).ticks(stack, player), () -> Assert.forAll( Potions.LEAPING.value().getEffects(), effectInstance -> context.expectEntityHasEffect(player, effectInstance.getEffectType(), effectInstance.getAmplifier()) diff --git a/src/gametest/java/net/errorcraft/itematic/gametest/item/component/ShooterItemComponentTestSuite.java b/src/gametest/java/net/errorcraft/itematic/gametest/item/component/ShooterItemComponentTestSuite.java index a321e773..057840d2 100644 --- a/src/gametest/java/net/errorcraft/itematic/gametest/item/component/ShooterItemComponentTestSuite.java +++ b/src/gametest/java/net/errorcraft/itematic/gametest/item/component/ShooterItemComponentTestSuite.java @@ -2,7 +2,6 @@ import net.errorcraft.itematic.gametest.Assert; import net.errorcraft.itematic.item.ItemKeys; -import net.errorcraft.itematic.item.component.components.ShooterItemComponent; import net.fabricmc.fabric.api.gametest.v1.FabricGameTest; import net.minecraft.component.DataComponentTypes; import net.minecraft.entity.player.PlayerEntity; @@ -15,7 +14,7 @@ public class ShooterItemComponentTestSuite { @GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE) - public void usingCrossbowLoadsArrowFromInventory(TestContext context) { + public void usingCrossbowChargesArrowFromInventory(TestContext context) { PlayerEntity player = context.createMockPlayer(GameMode.SURVIVAL); ServerWorld world = context.getWorld(); ItemStack stack = world.itematic$createStack(ItemKeys.CROSSBOW); @@ -25,19 +24,19 @@ public void usingCrossbowLoadsArrowFromInventory(TestContext context) { world.spawnEntity(player); stack.use(world, player, Hand.MAIN_HAND); context.createTimedTaskRunner().expectMinDurationAndRun( - ShooterItemComponent.getPullTime(stack), + stack.itematic$useDuration(player), () -> { stack.onStoppedUsing(world, player, player.getItemUseTimeLeft()); - Assert.itemStackHasComponent(player.getStackInHand(Hand.MAIN_HAND), DataComponentTypes.CHARGED_PROJECTILES, - component -> context.assertTrue(component.itematic$contains(ItemKeys.ARROW), "Expected item stack to have an arrow as a charged projectile") + Assert.itemStackHasDataComponent(player.getStackInHand(Hand.MAIN_HAND), DataComponentTypes.CHARGED_PROJECTILES, + component -> context.assertTrue(component.itematic$contains(ItemKeys.ARROW), "Expected item stack to have an Arrow as a charged projectile") ); - context.assertTrue(!player.getInventory().contains(s -> s.itematic$isOf(ItemKeys.ARROW)), "Expected player to have no arrows in their inventory"); + context.assertTrue(!player.getInventory().contains(s -> s.itematic$isOf(ItemKeys.ARROW)), "Expected player to have no Arrows in their inventory"); } ).completeIfSuccessful(); } @GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE) - public void usingCrossbowLoadsFireworkRocketFromOffhand(TestContext context) { + public void usingCrossbowChargesFireworkRocketFromOffhand(TestContext context) { PlayerEntity player = context.createMockPlayer(GameMode.SURVIVAL); ServerWorld world = context.getWorld(); ItemStack stack = world.itematic$createStack(ItemKeys.CROSSBOW); @@ -47,11 +46,11 @@ public void usingCrossbowLoadsFireworkRocketFromOffhand(TestContext context) { world.spawnEntity(player); stack.use(world, player, Hand.MAIN_HAND); context.createTimedTaskRunner().expectMinDurationAndRun( - ShooterItemComponent.getPullTime(stack), + stack.itematic$useDuration(player), () -> { stack.onStoppedUsing(world, player, player.getItemUseTimeLeft()); - Assert.itemStackHasComponent(player.getStackInHand(Hand.MAIN_HAND), DataComponentTypes.CHARGED_PROJECTILES, - component -> context.assertTrue(component.itematic$contains(ItemKeys.FIREWORK_ROCKET), "Expected item stack to have a firework rocket as a charged projectile") + Assert.itemStackHasDataComponent(player.getStackInHand(Hand.MAIN_HAND), DataComponentTypes.CHARGED_PROJECTILES, + component -> context.assertTrue(component.itematic$contains(ItemKeys.FIREWORK_ROCKET), "Expected item stack to have a Firework Rocket as a charged projectile") ); Assert.itemStackIsEmpty(player.getStackInHand(Hand.OFF_HAND)); } diff --git a/src/gametest/resources/data/itematic/gametest/structures/block.anvil.snbt b/src/gametest/resources/data/itematic/gametest/structures/block.anvil.snbt new file mode 100644 index 00000000..97942637 --- /dev/null +++ b/src/gametest/resources/data/itematic/gametest/structures/block.anvil.snbt @@ -0,0 +1,39 @@ +{ + DataVersion: 3839, + size: [3, 3, 3], + data: [ + {pos: [0, 0, 0], state: "minecraft:bedrock"}, + {pos: [0, 0, 1], state: "minecraft:bedrock"}, + {pos: [0, 0, 2], state: "minecraft:bedrock"}, + {pos: [1, 0, 0], state: "minecraft:bedrock"}, + {pos: [1, 0, 1], state: "minecraft:bedrock"}, + {pos: [1, 0, 2], state: "minecraft:bedrock"}, + {pos: [2, 0, 0], state: "minecraft:bedrock"}, + {pos: [2, 0, 1], state: "minecraft:bedrock"}, + {pos: [2, 0, 2], state: "minecraft:bedrock"}, + {pos: [0, 1, 0], state: "minecraft:air"}, + {pos: [0, 1, 1], state: "minecraft:air"}, + {pos: [0, 1, 2], state: "minecraft:air"}, + {pos: [1, 1, 0], state: "minecraft:air"}, + {pos: [1, 1, 1], state: "minecraft:anvil{facing:west}"}, + {pos: [1, 1, 2], state: "minecraft:air"}, + {pos: [2, 1, 0], state: "minecraft:air"}, + {pos: [2, 1, 1], state: "minecraft:air"}, + {pos: [2, 1, 2], state: "minecraft:air"}, + {pos: [0, 2, 0], state: "minecraft:air"}, + {pos: [0, 2, 1], state: "minecraft:air"}, + {pos: [0, 2, 2], state: "minecraft:air"}, + {pos: [1, 2, 0], state: "minecraft:air"}, + {pos: [1, 2, 1], state: "minecraft:air"}, + {pos: [1, 2, 2], state: "minecraft:air"}, + {pos: [2, 2, 0], state: "minecraft:air"}, + {pos: [2, 2, 1], state: "minecraft:air"}, + {pos: [2, 2, 2], state: "minecraft:air"} + ], + entities: [], + palette: [ + "minecraft:bedrock", + "minecraft:air", + "minecraft:anvil{facing:west}" + ] +} diff --git a/src/gametest/resources/data/itematic/gametest/structures/block.enchanting_table.snbt b/src/gametest/resources/data/itematic/gametest/structures/block.enchanting_table.snbt new file mode 100644 index 00000000..27cc4abb --- /dev/null +++ b/src/gametest/resources/data/itematic/gametest/structures/block.enchanting_table.snbt @@ -0,0 +1,39 @@ +{ + DataVersion: 3839, + size: [3, 3, 3], + data: [ + {pos: [0, 0, 0], state: "minecraft:bedrock"}, + {pos: [0, 0, 1], state: "minecraft:bedrock"}, + {pos: [0, 0, 2], state: "minecraft:bedrock"}, + {pos: [1, 0, 0], state: "minecraft:bedrock"}, + {pos: [1, 0, 1], state: "minecraft:bedrock"}, + {pos: [1, 0, 2], state: "minecraft:bedrock"}, + {pos: [2, 0, 0], state: "minecraft:bedrock"}, + {pos: [2, 0, 1], state: "minecraft:bedrock"}, + {pos: [2, 0, 2], state: "minecraft:bedrock"}, + {pos: [0, 1, 0], state: "minecraft:air"}, + {pos: [0, 1, 1], state: "minecraft:air"}, + {pos: [0, 1, 2], state: "minecraft:air"}, + {pos: [1, 1, 0], state: "minecraft:air"}, + {pos: [1, 1, 1], state: "minecraft:enchanting_table", nbt: {id: "minecraft:enchanting_table"}}, + {pos: [1, 1, 2], state: "minecraft:air"}, + {pos: [2, 1, 0], state: "minecraft:air"}, + {pos: [2, 1, 1], state: "minecraft:air"}, + {pos: [2, 1, 2], state: "minecraft:air"}, + {pos: [0, 2, 0], state: "minecraft:air"}, + {pos: [0, 2, 1], state: "minecraft:air"}, + {pos: [0, 2, 2], state: "minecraft:air"}, + {pos: [1, 2, 0], state: "minecraft:air"}, + {pos: [1, 2, 1], state: "minecraft:air"}, + {pos: [1, 2, 2], state: "minecraft:air"}, + {pos: [2, 2, 0], state: "minecraft:air"}, + {pos: [2, 2, 1], state: "minecraft:air"}, + {pos: [2, 2, 2], state: "minecraft:air"} + ], + entities: [], + palette: [ + "minecraft:bedrock", + "minecraft:air", + "minecraft:enchanting_table" + ] +} diff --git a/src/gametest/resources/data/itematic/gametest/structures/block.grindstone.snbt b/src/gametest/resources/data/itematic/gametest/structures/block.grindstone.snbt new file mode 100644 index 00000000..56ac8288 --- /dev/null +++ b/src/gametest/resources/data/itematic/gametest/structures/block.grindstone.snbt @@ -0,0 +1,39 @@ +{ + DataVersion: 3839, + size: [3, 3, 3], + data: [ + {pos: [0, 0, 0], state: "minecraft:bedrock"}, + {pos: [0, 0, 1], state: "minecraft:bedrock"}, + {pos: [0, 0, 2], state: "minecraft:bedrock"}, + {pos: [1, 0, 0], state: "minecraft:bedrock"}, + {pos: [1, 0, 1], state: "minecraft:bedrock"}, + {pos: [1, 0, 2], state: "minecraft:bedrock"}, + {pos: [2, 0, 0], state: "minecraft:bedrock"}, + {pos: [2, 0, 1], state: "minecraft:bedrock"}, + {pos: [2, 0, 2], state: "minecraft:bedrock"}, + {pos: [0, 1, 0], state: "minecraft:air"}, + {pos: [0, 1, 1], state: "minecraft:air"}, + {pos: [0, 1, 2], state: "minecraft:air"}, + {pos: [1, 1, 0], state: "minecraft:air"}, + {pos: [1, 1, 1], state: "minecraft:grindstone{face:floor,facing:south}"}, + {pos: [1, 1, 2], state: "minecraft:air"}, + {pos: [2, 1, 0], state: "minecraft:air"}, + {pos: [2, 1, 1], state: "minecraft:air"}, + {pos: [2, 1, 2], state: "minecraft:air"}, + {pos: [0, 2, 0], state: "minecraft:air"}, + {pos: [0, 2, 1], state: "minecraft:air"}, + {pos: [0, 2, 2], state: "minecraft:air"}, + {pos: [1, 2, 0], state: "minecraft:air"}, + {pos: [1, 2, 1], state: "minecraft:air"}, + {pos: [1, 2, 2], state: "minecraft:air"}, + {pos: [2, 2, 0], state: "minecraft:air"}, + {pos: [2, 2, 1], state: "minecraft:air"}, + {pos: [2, 2, 2], state: "minecraft:air"} + ], + entities: [], + palette: [ + "minecraft:bedrock", + "minecraft:air", + "minecraft:grindstone{face:floor,facing:south}" + ] +} diff --git a/src/gametest/resources/data/itematic/gametest/structures/item.bow.platform.snbt b/src/gametest/resources/data/itematic/gametest/structures/item.bow.platform.snbt new file mode 100644 index 00000000..5693ba04 --- /dev/null +++ b/src/gametest/resources/data/itematic/gametest/structures/item.bow.platform.snbt @@ -0,0 +1,38 @@ +{ + DataVersion: 3839, + size: [3, 3, 3], + data: [ + {pos: [0, 0, 0], state: "minecraft:bedrock"}, + {pos: [0, 0, 1], state: "minecraft:bedrock"}, + {pos: [0, 0, 2], state: "minecraft:bedrock"}, + {pos: [1, 0, 0], state: "minecraft:bedrock"}, + {pos: [1, 0, 1], state: "minecraft:bedrock"}, + {pos: [1, 0, 2], state: "minecraft:bedrock"}, + {pos: [2, 0, 0], state: "minecraft:bedrock"}, + {pos: [2, 0, 1], state: "minecraft:bedrock"}, + {pos: [2, 0, 2], state: "minecraft:bedrock"}, + {pos: [0, 1, 0], state: "minecraft:air"}, + {pos: [0, 1, 1], state: "minecraft:air"}, + {pos: [0, 1, 2], state: "minecraft:air"}, + {pos: [1, 1, 0], state: "minecraft:air"}, + {pos: [1, 1, 1], state: "minecraft:air"}, + {pos: [1, 1, 2], state: "minecraft:air"}, + {pos: [2, 1, 0], state: "minecraft:air"}, + {pos: [2, 1, 1], state: "minecraft:air"}, + {pos: [2, 1, 2], state: "minecraft:air"}, + {pos: [0, 2, 0], state: "minecraft:air"}, + {pos: [0, 2, 1], state: "minecraft:air"}, + {pos: [0, 2, 2], state: "minecraft:air"}, + {pos: [1, 2, 0], state: "minecraft:air"}, + {pos: [1, 2, 1], state: "minecraft:air"}, + {pos: [1, 2, 2], state: "minecraft:air"}, + {pos: [2, 2, 0], state: "minecraft:air"}, + {pos: [2, 2, 1], state: "minecraft:air"}, + {pos: [2, 2, 2], state: "minecraft:air"} + ], + entities: [], + palette: [ + "minecraft:bedrock", + "minecraft:air" + ] +} diff --git a/src/gametest/resources/fabric.mod.json b/src/gametest/resources/fabric.mod.json index a00bf284..af8a5d42 100644 --- a/src/gametest/resources/fabric.mod.json +++ b/src/gametest/resources/fabric.mod.json @@ -7,10 +7,13 @@ "environment": "*", "entrypoints": { "fabric-gametest": [ + "net.errorcraft.itematic.gametest.block.AnvilBlockTestSuite", "net.errorcraft.itematic.gametest.block.BeehiveBlockTestSuite", "net.errorcraft.itematic.gametest.block.CandleBlockTestSuite", "net.errorcraft.itematic.gametest.block.ComposterBlockTestSuite", "net.errorcraft.itematic.gametest.block.DispenserBehaviorTestSuite", + "net.errorcraft.itematic.gametest.block.EnchantingTableTestSuite", + "net.errorcraft.itematic.gametest.block.GrindstoneTestSuite", "net.errorcraft.itematic.gametest.block.PickBlockTestSuite", "net.errorcraft.itematic.gametest.block.ShulkerBoxBlockTestSuite", "net.errorcraft.itematic.gametest.block.WaterCauldronBlockTestSuite", @@ -24,8 +27,11 @@ "net.errorcraft.itematic.gametest.entity.passive.SheepEntityTestSuite", "net.errorcraft.itematic.gametest.entity.passive.SnifferEntityTestSuite", "net.errorcraft.itematic.gametest.entity.passive.WolfEntityTestSuite", - "net.errorcraft.itematic.gametest.item.BundleTestSuite", + "net.errorcraft.itematic.gametest.item.BeetrootSoupTestSuite", + "net.errorcraft.itematic.gametest.item.BowTestSuite", "net.errorcraft.itematic.gametest.item.BrushTestSuite", + "net.errorcraft.itematic.gametest.item.BundleTestSuite", + "net.errorcraft.itematic.gametest.item.CrossbowTestSuite", "net.errorcraft.itematic.gametest.item.FishingRodTestSuite", "net.errorcraft.itematic.gametest.item.FlowerPotItemTestSuite", "net.errorcraft.itematic.gametest.item.HoneyBottleTestSuite", diff --git a/src/main/generated/data/minecraft/item/arrow.json b/src/main/generated/data/minecraft/item/arrow.json index f8be902a..80c9a73b 100644 --- a/src/main/generated/data/minecraft/item/arrow.json +++ b/src/main/generated/data/minecraft/item/arrow.json @@ -7,7 +7,6 @@ "behavior": "minecraft:shoot_projectile" }, "minecraft:projectile": { - "damage": 1, "entity": { "type": "minecraft:arrow" } diff --git a/src/main/generated/data/minecraft/item/beetroot_soup.json b/src/main/generated/data/minecraft/item/beetroot_soup.json index 0c103043..096d3761 100644 --- a/src/main/generated/data/minecraft/item/beetroot_soup.json +++ b/src/main/generated/data/minecraft/item/beetroot_soup.json @@ -3,7 +3,9 @@ "translation_key": "item.minecraft.beetroot_soup" }, "components": { - "minecraft:consumable": {}, + "minecraft:consumable": { + "result_item": "minecraft:bowl" + }, "minecraft:food": { "nutrition": 6, "saturation": 7.2000003 diff --git a/src/main/generated/data/minecraft/item/bow.json b/src/main/generated/data/minecraft/item/bow.json index 51dd5fdc..e4170db7 100644 --- a/src/main/generated/data/minecraft/item/bow.json +++ b/src/main/generated/data/minecraft/item/bow.json @@ -19,6 +19,13 @@ "minecraft:shooter": { "ammunition": "#minecraft:bow_ammunition", "held_ammunition": "#minecraft:bow_ammunition", + "item_damage": { + "default_damage": 1, + "rules": [] + }, + "method": { + "type": "minecraft:direct" + }, "range": 15 }, "minecraft:stackable": 1, diff --git a/src/main/generated/data/minecraft/item/crossbow.json b/src/main/generated/data/minecraft/item/crossbow.json index c8c96632..0e91289d 100644 --- a/src/main/generated/data/minecraft/item/crossbow.json +++ b/src/main/generated/data/minecraft/item/crossbow.json @@ -18,17 +18,34 @@ }, "minecraft:shooter": { "ammunition": "#minecraft:bow_ammunition", - "chargeable": { - "quick_charge_sounds": { - "default": "minecraft:item.crossbow.loading_start", - "levels": [ - "minecraft:item.crossbow.quick_charge_1", - "minecraft:item.crossbow.quick_charge_2", - "minecraft:item.crossbow.quick_charge_3" + "held_ammunition": "#minecraft:crossbow_ammunition", + "item_damage": { + "default_damage": 1, + "rules": [ + { + "damage": 3, + "items": "minecraft:firework_rocket" + } + ] + }, + "method": { + "type": "minecraft:chargeable", + "charged_power_rules": { + "default_power": 3.15, + "rules": [ + { + "items": "minecraft:firework_rocket", + "power": 1.6 + } ] + }, + "default_charge_time": 1.25, + "default_charging_sounds": { + "end": "minecraft:item.crossbow.loading_end", + "mid": "minecraft:item.crossbow.loading_middle", + "start": "minecraft:item.crossbow.loading_start" } }, - "held_ammunition": "#minecraft:crossbow_ammunition", "range": 8 }, "minecraft:stackable": 1, diff --git a/src/main/generated/data/minecraft/item/firework_rocket.json b/src/main/generated/data/minecraft/item/firework_rocket.json index bd72802d..4fb18aa0 100644 --- a/src/main/generated/data/minecraft/item/firework_rocket.json +++ b/src/main/generated/data/minecraft/item/firework_rocket.json @@ -8,8 +8,6 @@ }, "minecraft:firework": {}, "minecraft:projectile": { - "charged_speed": 1.6, - "damage": 3, "entity": { "type": "minecraft:firework_rocket" } diff --git a/src/main/generated/data/minecraft/item/honey_bottle.json b/src/main/generated/data/minecraft/item/honey_bottle.json index 8462e608..93f7a440 100644 --- a/src/main/generated/data/minecraft/item/honey_bottle.json +++ b/src/main/generated/data/minecraft/item/honey_bottle.json @@ -4,7 +4,9 @@ }, "components": { "minecraft:consumable": { - "result_item": "minecraft:glass_bottle" + "has_consume_particles": false, + "result_item": "minecraft:glass_bottle", + "sound": "minecraft:item.honey_bottle.drink" }, "minecraft:food": { "nutrition": 6, diff --git a/src/main/generated/data/minecraft/item/milk_bucket.json b/src/main/generated/data/minecraft/item/milk_bucket.json index 60520504..8c3a57f9 100644 --- a/src/main/generated/data/minecraft/item/milk_bucket.json +++ b/src/main/generated/data/minecraft/item/milk_bucket.json @@ -4,7 +4,9 @@ }, "components": { "minecraft:consumable": { - "result_item": "minecraft:bucket" + "has_consume_particles": false, + "result_item": "minecraft:bucket", + "sound": "minecraft:entity.generic.drink" }, "minecraft:recipe_remainder": { "item": "minecraft:bucket" diff --git a/src/main/generated/data/minecraft/item/potion.json b/src/main/generated/data/minecraft/item/potion.json index 44c3f056..00b2ee63 100644 --- a/src/main/generated/data/minecraft/item/potion.json +++ b/src/main/generated/data/minecraft/item/potion.json @@ -4,7 +4,9 @@ }, "components": { "minecraft:consumable": { - "result_item": "minecraft:glass_bottle" + "has_consume_particles": false, + "result_item": "minecraft:glass_bottle", + "sound": "minecraft:entity.generic.drink" }, "minecraft:dispensable": { "behavior": "minecraft:use_item_on_block_or_dispense_item" diff --git a/src/main/generated/data/minecraft/item/spectral_arrow.json b/src/main/generated/data/minecraft/item/spectral_arrow.json index 51c3f5b3..4c6bd815 100644 --- a/src/main/generated/data/minecraft/item/spectral_arrow.json +++ b/src/main/generated/data/minecraft/item/spectral_arrow.json @@ -7,7 +7,6 @@ "behavior": "minecraft:shoot_projectile" }, "minecraft:projectile": { - "damage": 1, "entity": { "type": "minecraft:spectral_arrow" } diff --git a/src/main/generated/data/minecraft/item/tipped_arrow.json b/src/main/generated/data/minecraft/item/tipped_arrow.json index 019ea66b..3794b4d9 100644 --- a/src/main/generated/data/minecraft/item/tipped_arrow.json +++ b/src/main/generated/data/minecraft/item/tipped_arrow.json @@ -10,7 +10,6 @@ "duration_multiplier": 0.125 }, "minecraft:projectile": { - "damage": 1, "entity": { "type": "minecraft:arrow" } diff --git a/src/main/generated/data/minecraft/item/trident.json b/src/main/generated/data/minecraft/item/trident.json index 070c373e..19f32f1c 100644 --- a/src/main/generated/data/minecraft/item/trident.json +++ b/src/main/generated/data/minecraft/item/trident.json @@ -15,7 +15,6 @@ "enchantments": "minecraft:trident_forging" }, "minecraft:projectile": { - "damage": 1, "entity": { "type": "minecraft:trident", "prevent_spawn_from_riptide": true diff --git a/src/main/java/net/errorcraft/itematic/access/component/type/ChargedProjectilesComponentAccess.java b/src/main/java/net/errorcraft/itematic/access/component/type/ChargedProjectilesComponentAccess.java index 4e6b3150..008ac1e1 100644 --- a/src/main/java/net/errorcraft/itematic/access/component/type/ChargedProjectilesComponentAccess.java +++ b/src/main/java/net/errorcraft/itematic/access/component/type/ChargedProjectilesComponentAccess.java @@ -7,7 +7,4 @@ public interface ChargedProjectilesComponentAccess { default boolean itematic$contains(RegistryKey item) { return false; } - default float itematic$getChargedSpeed() { - return 0.0f; - } } diff --git a/src/main/java/net/errorcraft/itematic/component/ItematicDataComponentTypes.java b/src/main/java/net/errorcraft/itematic/component/ItematicDataComponentTypes.java index 81af6646..4825f316 100644 --- a/src/main/java/net/errorcraft/itematic/component/ItematicDataComponentTypes.java +++ b/src/main/java/net/errorcraft/itematic/component/ItematicDataComponentTypes.java @@ -1,32 +1,39 @@ package net.errorcraft.itematic.component; -import net.errorcraft.itematic.component.type.ImmuneToDamageComponent; -import net.errorcraft.itematic.component.type.ItemListDataComponent; -import net.errorcraft.itematic.component.type.UseDurationDataComponent; -import net.errorcraft.itematic.component.type.WeaponAttackDamageDataComponent; +import net.errorcraft.itematic.component.type.*; import net.errorcraft.itematic.item.component.components.ItemHolderItemComponent; import net.errorcraft.itematic.item.holder.rule.ItemHolderRules; +import net.errorcraft.itematic.item.shooter.method.methods.ChargeableShooterMethod; import net.errorcraft.itematic.mixin.component.DataComponentTypesAccessor; import net.errorcraft.itematic.network.codec.PacketCodecUtil; import net.errorcraft.itematic.serialization.ItematicCodecs; import net.errorcraft.itematic.util.UseActionUtil; import net.minecraft.component.DataComponentType; import net.minecraft.network.codec.PacketCodecs; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.sound.SoundEvent; import net.minecraft.util.Identifier; import net.minecraft.util.UseAction; import org.apache.commons.lang3.math.Fraction; public class ItematicDataComponentTypes { public static final DataComponentType IMMUNE_TO_DAMAGE = DataComponentTypesAccessor.register("immune_to_damage", builder -> builder.codec(ImmuneToDamageComponent.CODEC).packetCodec(ImmuneToDamageComponent.PACKET_CODEC)); - public static final DataComponentType USE_DURATION = DataComponentTypesAccessor.register("use_duration", builder -> builder.codec(UseDurationDataComponent.CODEC).packetCodec(UseDurationDataComponent.PACKET_CODEC)); - public static final DataComponentType USE_ANIMATION = DataComponentTypesAccessor.register("use_animation", builder -> builder.codec(UseActionUtil.CODEC).packetCodec(UseActionUtil.PACKET_CODEC)); - public static final DataComponentType SHOOTER_AMMUNITION = DataComponentTypesAccessor.register("shooter_ammunition", builder -> builder.codec(ItemListDataComponent.CODEC).packetCodec(ItemListDataComponent.PACKET_CODEC)); - public static final DataComponentType SHOOTER_HELD_AMMUNITION = DataComponentTypesAccessor.register("shooter_held_ammunition", builder -> builder.codec(ItemListDataComponent.CODEC).packetCodec(ItemListDataComponent.PACKET_CODEC)); - public static final DataComponentType ATTACK_SPEED_MULTIPLIER = DataComponentTypesAccessor.register("attack_speed_multiplier", builder -> builder.codec(ItematicCodecs.NON_NEGATIVE_DOUBLE).packetCodec(PacketCodecs.DOUBLE)); - public static final DataComponentType WEAPON_ATTACK_DAMAGE = DataComponentTypesAccessor.register("weapon_attack_damage", builder -> builder.codec(WeaponAttackDamageDataComponent.CODEC).packetCodec(WeaponAttackDamageDataComponent.PACKET_CODEC)); + public static final DataComponentType USE_DURATION = DataComponentTypesAccessor.register("use_duration", builder -> builder.codec(UseDurationDataComponent.CODEC).packetCodec(UseDurationDataComponent.PACKET_CODEC).cache()); + public static final DataComponentType USE_ANIMATION = DataComponentTypesAccessor.register("use_animation", builder -> builder.codec(UseActionUtil.CODEC).packetCodec(UseActionUtil.PACKET_CODEC).cache()); + public static final DataComponentType SHOOTER_AMMUNITION = DataComponentTypesAccessor.register("shooter_ammunition", builder -> builder.codec(ItemListDataComponent.CODEC).packetCodec(ItemListDataComponent.PACKET_CODEC).cache()); + public static final DataComponentType SHOOTER_HELD_AMMUNITION = DataComponentTypesAccessor.register("shooter_held_ammunition", builder -> builder.codec(ItemListDataComponent.CODEC).packetCodec(ItemListDataComponent.PACKET_CODEC).cache()); + public static final DataComponentType ATTACK_SPEED_MULTIPLIER = DataComponentTypesAccessor.register("attack_speed_multiplier", builder -> builder.codec(ItematicCodecs.NON_NEGATIVE_DOUBLE).packetCodec(PacketCodecs.DOUBLE).cache()); + public static final DataComponentType WEAPON_ATTACK_DAMAGE = DataComponentTypesAccessor.register("weapon_attack_damage", builder -> builder.codec(WeaponAttackDamageDataComponent.CODEC).packetCodec(WeaponAttackDamageDataComponent.PACKET_CODEC).cache()); public static final DataComponentType ITEM_BAR_STYLE = DataComponentTypesAccessor.register("item_bar_style", builder -> builder.codec(Identifier.CODEC).packetCodec(Identifier.PACKET_CODEC).cache()); - public static final DataComponentType ITEM_HOLDER_CAPACITY = DataComponentTypesAccessor.register("item_holder_capacity", builder -> builder.codec(ItemHolderItemComponent.CAPACITY_CODEC).packetCodec(PacketCodecUtil.FRACTION)); + public static final DataComponentType ITEM_HOLDER_CAPACITY = DataComponentTypesAccessor.register("item_holder_capacity", builder -> builder.codec(ItemHolderItemComponent.CAPACITY_CODEC).packetCodec(PacketCodecUtil.FRACTION).cache()); public static final DataComponentType ITEM_HOLDER_RULES = DataComponentTypesAccessor.register("item_holder_rules", builder -> builder.codec(ItemHolderRules.CODEC).packetCodec(ItemHolderRules.PACKET_CODEC).cache()); + public static final DataComponentType SHOOTER_DAMAGE_RULES = DataComponentTypesAccessor.register("shooter_damage_rules", builder -> builder.codec(ItemDamageRulesDataComponent.CODEC).packetCodec(ItemDamageRulesDataComponent.PACKET_CODEC).cache()); + public static final DataComponentType SHOOTER_DEFAULT_CHARGE_TIME = DataComponentTypesAccessor.register("shooter_default_charge_time", builder -> builder.codec(ItematicCodecs.NON_NEGATIVE_FLOAT).packetCodec(PacketCodecs.FLOAT).cache()); + public static final DataComponentType SHOOTER_DEFAULT_CHARGING_SOUNDS = DataComponentTypesAccessor.register("shooter_default_charging_sounds", builder -> builder.codec(ChargeableShooterMethod.ChargingSounds.CODEC).packetCodec(ChargeableShooterMethod.ChargingSounds.PACKET_CODEC).cache()); + public static final DataComponentType SHOOTER_CHARGED_POWER_RULES = DataComponentTypesAccessor.register("shooter_charged_power_rules", builder -> builder.codec(ChargeableShooterMethod.ChargedPowerRules.CODEC).packetCodec(ChargeableShooterMethod.ChargedPowerRules.PACKET_CODEC)); + public static final DataComponentType> SHOOTER_SHOOT_SOUND = DataComponentTypesAccessor.register("shooter_shoot_sound", builder -> builder.codec(SoundEvent.ENTRY_CODEC).packetCodec(SoundEvent.ENTRY_PACKET_CODEC)); + + private ItematicDataComponentTypes() {} public static void init() {} } diff --git a/src/main/java/net/errorcraft/itematic/component/type/ItemDamageRulesDataComponent.java b/src/main/java/net/errorcraft/itematic/component/type/ItemDamageRulesDataComponent.java new file mode 100644 index 00000000..fddaf5d6 --- /dev/null +++ b/src/main/java/net/errorcraft/itematic/component/type/ItemDamageRulesDataComponent.java @@ -0,0 +1,58 @@ +package net.errorcraft.itematic.component.type; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.network.RegistryByteBuf; +import net.minecraft.network.codec.PacketCodec; +import net.minecraft.network.codec.PacketCodecs; +import net.minecraft.registry.RegistryCodecs; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.registry.entry.RegistryEntryList; +import net.minecraft.util.dynamic.Codecs; + +import java.util.List; +import java.util.Optional; + +public record ItemDamageRulesDataComponent(List rules, int defaultItemDamage) { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + Rule.CODEC.listOf().fieldOf("rules").forGetter(ItemDamageRulesDataComponent::rules), + Codecs.NONNEGATIVE_INT.fieldOf("default_damage").forGetter(ItemDamageRulesDataComponent::defaultItemDamage) + ).apply(instance, ItemDamageRulesDataComponent::new)); + public static final PacketCodec PACKET_CODEC = PacketCodec.tuple( + Rule.PACKET_CODEC.collect(PacketCodecs.toList()), ItemDamageRulesDataComponent::rules, + PacketCodecs.VAR_INT, ItemDamageRulesDataComponent::defaultItemDamage, + ItemDamageRulesDataComponent::new + ); + + public int damage(ItemStack stack) { + for (Rule rule : this.rules) { + if (rule.damage.isPresent() && rule.matches(stack)) { + return rule.damage.get(); + } + } + + return this.defaultItemDamage; + } + + public record Rule(RegistryEntryList items, Optional damage) { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + RegistryCodecs.entryList(RegistryKeys.ITEM).fieldOf("items").forGetter(Rule::items), + Codecs.NONNEGATIVE_INT.optionalFieldOf("damage").forGetter(Rule::damage) + ).apply(instance, Rule::new)); + public static final PacketCodec PACKET_CODEC = PacketCodec.tuple( + PacketCodecs.registryEntryList(RegistryKeys.ITEM), Rule::items, + PacketCodecs.VAR_INT.collect(PacketCodecs::optional), Rule::damage, + Rule::new + ); + + public static Rule of(RegistryEntryList items, int damage) { + return new Rule(items, Optional.of(damage)); + } + + public boolean matches(ItemStack stack) { + return stack.isIn(this.items); + } + } +} diff --git a/src/main/java/net/errorcraft/itematic/item/ItemUtil.java b/src/main/java/net/errorcraft/itematic/item/ItemUtil.java index 66584d0b..2fa2b7bb 100644 --- a/src/main/java/net/errorcraft/itematic/item/ItemUtil.java +++ b/src/main/java/net/errorcraft/itematic/item/ItemUtil.java @@ -5,6 +5,7 @@ import net.errorcraft.itematic.block.BlockKeys; import net.errorcraft.itematic.block.ComposterBlockUtil; import net.errorcraft.itematic.block.entity.FurnaceBlockEntityUtil; +import net.errorcraft.itematic.component.type.ItemDamageRulesDataComponent; import net.errorcraft.itematic.enchantment.EnchantmentTags; import net.errorcraft.itematic.entity.EntityTypeKeys; import net.errorcraft.itematic.entity.ItematicEntityTypeTags; @@ -27,6 +28,8 @@ import net.errorcraft.itematic.item.holder.rule.rules.RejectItemHolderRule; import net.errorcraft.itematic.item.pointer.Pointer; import net.errorcraft.itematic.item.pointer.PointerKeys; +import net.errorcraft.itematic.item.shooter.method.methods.ChargeableShooterMethod; +import net.errorcraft.itematic.item.shooter.method.methods.DirectShooterMethod; import net.errorcraft.itematic.item.smithing.template.SmithingTemplate; import net.errorcraft.itematic.item.smithing.template.SmithingTemplates; import net.errorcraft.itematic.loot.predicate.SideCheckPredicate; @@ -187,7 +190,12 @@ private void bootstrapConsumables() { .ticks(MilkBucketItemAccessor.getMaxUseTime()) .animation(UseAction.DRINK) .build()) - .with(ConsumableItemComponent.of(this.items.getOrThrow(ItemKeys.BUCKET))) + .with(ConsumableItemComponent.builder(MilkBucketItemAccessor.getMaxUseTime()) + .useAnimation(UseAction.DRINK) + .resultItem(this.items.getOrThrow(ItemKeys.BUCKET)) + .noConsumeParticles() + .consumeSound(this.soundEvents.getOrThrow(SoundEventKeys.GENERIC_DRINK)) + .build()) .with(RecipeRemainderItemComponent.of(this.items.getOrThrow(ItemKeys.BUCKET))) .build(), ItemEventMap.builder() @@ -204,7 +212,12 @@ private void bootstrapConsumables() { .build()) .with(PotionItemComponent.INSTANCE) .with(PotionHolderItemComponent.of(1.0f)) - .with(ConsumableItemComponent.of(this.items.getOrThrow(ItemKeys.GLASS_BOTTLE))) + .with(ConsumableItemComponent.builder(PotionItemAccessor.getMaxUseTime()) + .useAnimation(UseAction.DRINK) + .resultItem(this.items.getOrThrow(ItemKeys.GLASS_BOTTLE)) + .noConsumeParticles() + .consumeSound(this.soundEvents.getOrThrow(SoundEventKeys.GENERIC_DRINK)) + .build()) .with(TintedItemComponent.of(PotionItemColor.INSTANCE)) .with(DispensableItemComponent.of(this.dispenseBehaviors.getOrThrow(DispenseBehaviors.USE_ITEM_ON_BLOCK_OR_DISPENSE_ITEM))) .build(), @@ -254,7 +267,7 @@ private void bootstrapFood() { ItemBase.Builder.forItem(ItemKeys.APPLE).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(64)) - .with(FoodItemComponent.from(FoodComponents.APPLE)) + .with(ConsumableItemComponent.builder(FoodComponents.APPLE).build()) .with(CompostableItemComponent.of(ComposterBlockUtil.BIG_CHANCE_TO_COMPOST)) .build() )); @@ -262,7 +275,7 @@ private void bootstrapFood() { ItemBase.Builder.forItem(ItemKeys.MELON_SLICE).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(64)) - .with(FoodItemComponent.from(FoodComponents.MELON_SLICE)) + .with(ConsumableItemComponent.builder(FoodComponents.MELON_SLICE).build()) .with(CompostableItemComponent.of(ComposterBlockUtil.HALF_CHANCE_TO_COMPOST)) .build() )); @@ -270,7 +283,7 @@ private void bootstrapFood() { ItemBase.Builder.forItem(ItemKeys.DRIED_KELP).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(64)) - .with(FoodItemComponent.from(FoodComponents.DRIED_KELP)) + .with(ConsumableItemComponent.builder(FoodComponents.DRIED_KELP).build()) .with(CompostableItemComponent.of(ComposterBlockUtil.SMALL_CHANCE_TO_COMPOST)) .build() )); @@ -278,7 +291,7 @@ private void bootstrapFood() { ItemBase.Builder.forItem(ItemKeys.CARROT).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(64)) - .with(FoodItemComponent.from(FoodComponents.CARROT)) + .with(ConsumableItemComponent.builder(FoodComponents.CARROT).build()) .with(BlockItemComponent.of(this.blocks.getOrThrow(BlockKeys.CARROTS))) .with(CompostableItemComponent.of(ComposterBlockUtil.BIG_CHANCE_TO_COMPOST)) .build() @@ -287,7 +300,7 @@ private void bootstrapFood() { ItemBase.Builder.forItem(ItemKeys.POTATO).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(64)) - .with(FoodItemComponent.from(FoodComponents.POTATO)) + .with(ConsumableItemComponent.builder(FoodComponents.POTATO).build()) .with(BlockItemComponent.of(this.blocks.getOrThrow(BlockKeys.POTATOES))) .with(CompostableItemComponent.of(ComposterBlockUtil.BIG_CHANCE_TO_COMPOST)) .build() @@ -296,7 +309,7 @@ private void bootstrapFood() { ItemBase.Builder.forItem(ItemKeys.BAKED_POTATO).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(64)) - .with(FoodItemComponent.from(FoodComponents.BAKED_POTATO)) + .with(ConsumableItemComponent.builder(FoodComponents.BAKED_POTATO).build()) .with(CompostableItemComponent.of(ComposterBlockUtil.ALMOST_GUARANTEED_TO_COMPOST)) .build() )); @@ -304,7 +317,7 @@ private void bootstrapFood() { ItemBase.Builder.forItem(ItemKeys.CHORUS_FRUIT).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(64)) - .with(FoodItemComponent.from(FoodComponents.CHORUS_FRUIT)) + .with(ConsumableItemComponent.builder(FoodComponents.CHORUS_FRUIT).build()) .build(), ItemEventMap.builder() .add(ItemEvents.CONSUME_ITEM, ActionEntry.of(TeleportAction.of(16, ActionContextParameter.THIS))) @@ -314,7 +327,7 @@ private void bootstrapFood() { ItemBase.Builder.forItem(ItemKeys.BEETROOT).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(64)) - .with(FoodItemComponent.from(FoodComponents.BEETROOT)) + .with(ConsumableItemComponent.builder(FoodComponents.BEETROOT).build()) .with(CompostableItemComponent.of(ComposterBlockUtil.BIG_CHANCE_TO_COMPOST)) .build() )); @@ -322,7 +335,7 @@ private void bootstrapFood() { ItemBase.Builder.forItem(ItemKeys.SWEET_BERRIES).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(64)) - .with(FoodItemComponent.from(FoodComponents.SWEET_BERRIES)) + .with(ConsumableItemComponent.builder(FoodComponents.SWEET_BERRIES).build()) .with(BlockItemComponent.of(this.blocks.getOrThrow(BlockKeys.SWEET_BERRY_BUSH))) .with(CompostableItemComponent.of(ComposterBlockUtil.SMALL_CHANCE_TO_COMPOST)) .build() @@ -331,7 +344,7 @@ private void bootstrapFood() { ItemBase.Builder.forItem(ItemKeys.GLOW_BERRIES).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(64)) - .with(FoodItemComponent.from(FoodComponents.GLOW_BERRIES)) + .with(ConsumableItemComponent.builder(FoodComponents.GLOW_BERRIES).build()) .with(BlockItemComponent.of(this.blocks.getOrThrow(BlockKeys.CAVE_VINES))) .with(CompostableItemComponent.of(ComposterBlockUtil.SMALL_CHANCE_TO_COMPOST)) .build() @@ -340,7 +353,7 @@ private void bootstrapFood() { ItemBase.Builder.forItem(ItemKeys.BREAD).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(64)) - .with(FoodItemComponent.from(FoodComponents.BREAD)) + .with(ConsumableItemComponent.builder(FoodComponents.BREAD).build()) .with(CompostableItemComponent.of(ComposterBlockUtil.ALMOST_GUARANTEED_TO_COMPOST)) .build() )); @@ -348,7 +361,7 @@ private void bootstrapFood() { ItemBase.Builder.forItem(ItemKeys.COOKIE).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(64)) - .with(FoodItemComponent.from(FoodComponents.COOKIE)) + .with(ConsumableItemComponent.builder(FoodComponents.COOKIE).build()) .with(CompostableItemComponent.of(ComposterBlockUtil.ALMOST_GUARANTEED_TO_COMPOST)) .build() )); @@ -356,140 +369,148 @@ private void bootstrapFood() { ItemBase.Builder.forItem(ItemKeys.PORKCHOP).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(64)) - .with(FoodItemComponent.from(FoodComponents.PORKCHOP)) + .with(ConsumableItemComponent.builder(FoodComponents.PORKCHOP).build()) .build() )); this.registerable.register(ItemKeys.COOKED_PORKCHOP, create( ItemBase.Builder.forItem(ItemKeys.COOKED_PORKCHOP).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(64)) - .with(FoodItemComponent.from(FoodComponents.COOKED_PORKCHOP)) + .with(ConsumableItemComponent.builder(FoodComponents.COOKED_PORKCHOP).build()) .build() )); this.registerable.register(ItemKeys.BEEF, create( ItemBase.Builder.forItem(ItemKeys.BEEF).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(64)) - .with(FoodItemComponent.from(FoodComponents.BEEF)) + .with(ConsumableItemComponent.builder(FoodComponents.BEEF).build()) .build() )); this.registerable.register(ItemKeys.COOKED_BEEF, create( ItemBase.Builder.forItem(ItemKeys.COOKED_BEEF).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(64)) - .with(FoodItemComponent.from(FoodComponents.COOKED_BEEF)) + .with(ConsumableItemComponent.builder(FoodComponents.COOKED_BEEF).build()) .build() )); this.registerable.register(ItemKeys.CHICKEN, create( ItemBase.Builder.forItem(ItemKeys.CHICKEN).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(64)) - .with(FoodItemComponent.from(FoodComponents.CHICKEN)) + .with(ConsumableItemComponent.builder(FoodComponents.CHICKEN).build()) .build() )); this.registerable.register(ItemKeys.COOKED_CHICKEN, create( ItemBase.Builder.forItem(ItemKeys.COOKED_CHICKEN).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(64)) - .with(FoodItemComponent.from(FoodComponents.COOKED_CHICKEN)) + .with(ConsumableItemComponent.builder(FoodComponents.COOKED_CHICKEN).build()) .build() )); this.registerable.register(ItemKeys.RABBIT, create( ItemBase.Builder.forItem(ItemKeys.RABBIT).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(64)) - .with(FoodItemComponent.from(FoodComponents.RABBIT)) + .with(ConsumableItemComponent.builder(FoodComponents.RABBIT).build()) .build() )); this.registerable.register(ItemKeys.COOKED_RABBIT, create( ItemBase.Builder.forItem(ItemKeys.COOKED_RABBIT).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(64)) - .with(FoodItemComponent.from(FoodComponents.COOKED_RABBIT)) + .with(ConsumableItemComponent.builder(FoodComponents.COOKED_RABBIT).build()) .build() )); this.registerable.register(ItemKeys.MUTTON, create( ItemBase.Builder.forItem(ItemKeys.MUTTON).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(64)) - .with(FoodItemComponent.from(FoodComponents.MUTTON)) + .with(ConsumableItemComponent.builder(FoodComponents.MUTTON).build()) .build() )); this.registerable.register(ItemKeys.COOKED_MUTTON, create( ItemBase.Builder.forItem(ItemKeys.COOKED_MUTTON).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(64)) - .with(FoodItemComponent.from(FoodComponents.COOKED_MUTTON)) + .with(ConsumableItemComponent.builder(FoodComponents.COOKED_MUTTON).build()) .build() )); this.registerable.register(ItemKeys.COD, create( ItemBase.Builder.forItem(ItemKeys.COD).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(64)) - .with(FoodItemComponent.from(FoodComponents.COD)) + .with(ConsumableItemComponent.builder(FoodComponents.COD).build()) .build() )); this.registerable.register(ItemKeys.SALMON, create( ItemBase.Builder.forItem(ItemKeys.SALMON).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(64)) - .with(FoodItemComponent.from(FoodComponents.SALMON)) + .with(ConsumableItemComponent.builder(FoodComponents.SALMON).build()) .build() )); this.registerable.register(ItemKeys.TROPICAL_FISH, create( ItemBase.Builder.forItem(ItemKeys.TROPICAL_FISH).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(64)) - .with(FoodItemComponent.from(FoodComponents.TROPICAL_FISH)) + .with(ConsumableItemComponent.builder(FoodComponents.TROPICAL_FISH).build()) .build() )); this.registerable.register(ItemKeys.PUFFERFISH, create( ItemBase.Builder.forItem(ItemKeys.PUFFERFISH).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(64)) - .with(FoodItemComponent.from(FoodComponents.PUFFERFISH)) + .with(ConsumableItemComponent.builder(FoodComponents.PUFFERFISH).build()) .build() )); this.registerable.register(ItemKeys.COOKED_COD, create( ItemBase.Builder.forItem(ItemKeys.COOKED_COD).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(64)) - .with(FoodItemComponent.from(FoodComponents.COOKED_COD)) + .with(ConsumableItemComponent.builder(FoodComponents.COOKED_COD).build()) .build() )); this.registerable.register(ItemKeys.COOKED_SALMON, create( ItemBase.Builder.forItem(ItemKeys.COOKED_SALMON).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(64)) - .with(FoodItemComponent.from(FoodComponents.COOKED_SALMON)) + .with(ConsumableItemComponent.builder(FoodComponents.COOKED_SALMON).build()) .build() )); this.registerable.register(ItemKeys.MUSHROOM_STEW, create( ItemBase.Builder.forItem(ItemKeys.MUSHROOM_STEW).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(1)) - .with(FoodItemComponent.from(FoodComponents.MUSHROOM_STEW, this.items.getOrThrow(ItemKeys.BOWL))) + .with(ConsumableItemComponent.builder(FoodComponents.MUSHROOM_STEW) + .resultItem(this.items.getOrThrow(ItemKeys.BOWL)) + .build()) .build() )); this.registerable.register(ItemKeys.RABBIT_STEW, create( ItemBase.Builder.forItem(ItemKeys.RABBIT_STEW).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(1)) - .with(FoodItemComponent.from(FoodComponents.RABBIT_STEW, this.items.getOrThrow(ItemKeys.BOWL))) + .with(ConsumableItemComponent.builder(FoodComponents.RABBIT_STEW) + .resultItem(this.items.getOrThrow(ItemKeys.BOWL)) + .build()) .build() )); this.registerable.register(ItemKeys.BEETROOT_SOUP, create( ItemBase.Builder.forItem(ItemKeys.BEETROOT_SOUP).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(1)) - .with(FoodItemComponent.from(FoodComponents.BEETROOT_SOUP)) + .with(ConsumableItemComponent.builder(FoodComponents.BEETROOT_SOUP) + .resultItem(this.items.getOrThrow(ItemKeys.BOWL)) + .build()) .build() )); this.registerable.register(ItemKeys.SUSPICIOUS_STEW, create( ItemBase.Builder.forItem(ItemKeys.SUSPICIOUS_STEW).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(1)) - .with(FoodItemComponent.from(FoodComponents.SUSPICIOUS_STEW, this.items.getOrThrow(ItemKeys.BOWL))) + .with(ConsumableItemComponent.builder(FoodComponents.SUSPICIOUS_STEW) + .resultItem(this.items.getOrThrow(ItemKeys.BOWL)) + .build()) .build(), ItemEventMap.builder() .add(ItemEvents.CONSUME_ITEM, ActionEntry.of( @@ -503,21 +524,21 @@ private void bootstrapFood() { ItemBase.Builder.forItem(ItemKeys.ROTTEN_FLESH).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(64)) - .with(FoodItemComponent.from(FoodComponents.ROTTEN_FLESH)) + .with(ConsumableItemComponent.builder(FoodComponents.ROTTEN_FLESH).build()) .build() )); this.registerable.register(ItemKeys.SPIDER_EYE, create( ItemBase.Builder.forItem(ItemKeys.SPIDER_EYE).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(64)) - .with(FoodItemComponent.from(FoodComponents.SPIDER_EYE)) + .with(ConsumableItemComponent.builder(FoodComponents.SPIDER_EYE).build()) .build() )); this.registerable.register(ItemKeys.POISONOUS_POTATO, create( ItemBase.Builder.forItem(ItemKeys.POISONOUS_POTATO).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(64)) - .with(FoodItemComponent.from(FoodComponents.POISONOUS_POTATO)) + .with(ConsumableItemComponent.builder(FoodComponents.POISONOUS_POTATO).build()) .build() )); this.registerable.register(ItemKeys.GOLDEN_APPLE, create( @@ -526,7 +547,7 @@ private void bootstrapFood() { .build(), ItemComponentSet.builder() .with(StackableItemComponent.of(64)) - .with(FoodItemComponent.from(FoodComponents.GOLDEN_APPLE)) + .with(ConsumableItemComponent.builder(FoodComponents.GOLDEN_APPLE).build()) .build() )); this.registerable.register(ItemKeys.ENCHANTED_GOLDEN_APPLE, create( @@ -536,21 +557,21 @@ private void bootstrapFood() { .build(), ItemComponentSet.builder() .with(StackableItemComponent.of(64)) - .with(FoodItemComponent.from(FoodComponents.ENCHANTED_GOLDEN_APPLE)) + .with(ConsumableItemComponent.builder(FoodComponents.ENCHANTED_GOLDEN_APPLE).build()) .build() )); this.registerable.register(ItemKeys.GOLDEN_CARROT, create( ItemBase.Builder.forItem(ItemKeys.GOLDEN_CARROT).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(64)) - .with(FoodItemComponent.from(FoodComponents.GOLDEN_CARROT)) + .with(ConsumableItemComponent.builder(FoodComponents.GOLDEN_CARROT).build()) .build() )); this.registerable.register(ItemKeys.PUMPKIN_PIE, create( ItemBase.Builder.forItem(ItemKeys.PUMPKIN_PIE).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(64)) - .with(FoodItemComponent.from(FoodComponents.PUMPKIN_PIE)) + .with(ConsumableItemComponent.builder(FoodComponents.PUMPKIN_PIE).build()) .with(CompostableItemComponent.of(ComposterBlockUtil.GUARANTEED_TO_COMPOST)) .build() )); @@ -558,7 +579,13 @@ private void bootstrapFood() { ItemBase.Builder.forItem(ItemKeys.HONEY_BOTTLE).build(), ItemComponentSet.builder() .with(StackableItemComponent.of(16)) - .with(FoodItemComponent.from(FoodComponents.HONEY_BOTTLE, HoneyBottleItemAccessor.maxUseTime(), UseAction.DRINK, this.items.getOrThrow(ItemKeys.GLASS_BOTTLE))) + .with(ConsumableItemComponent.builder(HoneyBottleItemAccessor.maxUseTime()) + .food(FoodComponents.HONEY_BOTTLE) + .useAnimation(UseAction.DRINK) + .resultItem(this.items.getOrThrow(ItemKeys.GLASS_BOTTLE)) + .noConsumeParticles() + .consumeSound(this.soundEvents.getOrThrow(SoundEventKeys.HONEY_BOTTLE_DRINK)) + .build()) .with(RecipeRemainderItemComponent.of(this.items.getOrThrow(ItemKeys.GLASS_BOTTLE))) .build(), ItemEventMap.builder() @@ -4825,7 +4852,13 @@ private void bootstrapToolsAndWeapons() { ItemComponentSet.builder() .with(StackableItemComponent.of(1)) .with(DamageableItemComponent.of(384)) - .with(ShooterItemComponent.of(this.items.getOrThrow(ItematicItemTags.BOW_AMMUNITION), this.items.getOrThrow(ItematicItemTags.BOW_AMMUNITION), BowItem.RANGE)) + .with(ShooterItemComponent.of( + UseAction.BOW, + this.items.getOrThrow(ItematicItemTags.BOW_AMMUNITION), + this.items.getOrThrow(ItematicItemTags.BOW_AMMUNITION), + BowItem.RANGE, + DirectShooterMethod.of() + )) .with(EnchantableItemComponent.enchants(1, EnchantmentTags.BOW_ENCHANTING)) .with(ForgeableItemComponent.of(EnchantmentTags.BOW_FORGING)) .with(FuelItemComponent.of(FurnaceBlockEntityUtil.WOOD_FUEL_TIME)) @@ -4836,7 +4869,23 @@ private void bootstrapToolsAndWeapons() { ItemComponentSet.builder() .with(StackableItemComponent.of(1)) .with(DamageableItemComponent.of(465)) - .with(ShooterItemComponent.of(this.items.getOrThrow(ItematicItemTags.CROSSBOW_AMMUNITION), this.items.getOrThrow(ItematicItemTags.BOW_AMMUNITION), CrossbowItem.RANGE, this.soundEvents.getOrThrow(SoundEventKeys.CROSSBOW_LOADING_START), this.soundEvents.getOrThrow(SoundEventKeys.CROSSBOW_QUICK_CHARGE_1), this.soundEvents.getOrThrow(SoundEventKeys.CROSSBOW_QUICK_CHARGE_2), this.soundEvents.getOrThrow(SoundEventKeys.CROSSBOW_QUICK_CHARGE_3))) + .with(ShooterItemComponent.of( + UseAction.CROSSBOW, + this.items.getOrThrow(ItematicItemTags.CROSSBOW_AMMUNITION), + this.items.getOrThrow(ItematicItemTags.BOW_AMMUNITION), + CrossbowItem.RANGE, + ChargeableShooterMethod.of( + ChargeableShooterMethod.ChargingSounds.DEFAULT, + ChargeableShooterMethod.ChargedPowerRules.Rule.of( + RegistryEntryList.of(this.items.getOrThrow(ItemKeys.FIREWORK_ROCKET)), + CrossbowItemAccessor.fireworkRocketPower() + ) + ), + ItemDamageRulesDataComponent.Rule.of( + RegistryEntryList.of(this.items.getOrThrow(ItemKeys.FIREWORK_ROCKET)), + 3 + ) + )) .with(EnchantableItemComponent.enchants(1, EnchantmentTags.CROSSBOW_ENCHANTING)) .with(ForgeableItemComponent.of(EnchantmentTags.CROSSBOW_FORGING)) .with(FuelItemComponent.of(FurnaceBlockEntityUtil.WOOD_FUEL_TIME)) @@ -4851,7 +4900,7 @@ private void bootstrapToolsAndWeapons() { .build()) .with(WeaponItemComponent.of(1, TridentItem.ATTACK_DAMAGE + 1, 0.275d)) .with(ThrowableItemComponent.trident(TridentItem.THROW_SPEED, 0.0f, TridentItem.MIN_DRAW_DURATION)) - .with(ProjectileItemComponent.of(TridentEntityInitializer.of(true), 1)) + .with(ProjectileItemComponent.of(TridentEntityInitializer.of(true))) .with(EnchantableItemComponent.enchants(1, EnchantmentTags.TRIDENT_ENCHANTING)) .with(ForgeableItemComponent.of(EnchantmentTags.TRIDENT_FORGING)) .build(), @@ -8613,7 +8662,7 @@ private void bootstrapProjectiles() { ItemComponentSet.builder() .with(StackableItemComponent.of(64)) .with(ThrowableItemComponent.of()) - .with(ProjectileItemComponent.of(EyeOfEnderEntityInitializer.INSTANCE, 0)) + .with(ProjectileItemComponent.of(EyeOfEnderEntityInitializer.INSTANCE)) .with(PreventUseWhenUsedOnTargetItemComponent.forBlock()) .build(), ItemEventMap.builder() @@ -8686,7 +8735,7 @@ private void bootstrapProjectiles() { ItemComponentSet.builder() .with(StackableItemComponent.of(64)) .with(FireworkItemComponent.INSTANCE) - .with(ProjectileItemComponent.of(FireworkRocketEntityInitializer.INSTANCE, 3, CrossbowItemAccessor.fireworkRocketSpeed())) + .with(ProjectileItemComponent.of(FireworkRocketEntityInitializer.INSTANCE)) .with(DispensableItemComponent.of(this.dispenseBehaviors.getOrThrow(DispenseBehaviors.SHOOT_FIREWORK_ROCKET))) .build() )); diff --git a/src/main/java/net/errorcraft/itematic/item/component/components/ConsumableItemComponent.java b/src/main/java/net/errorcraft/itematic/item/component/components/ConsumableItemComponent.java index 19ed1a48..785adc8f 100644 --- a/src/main/java/net/errorcraft/itematic/item/component/components/ConsumableItemComponent.java +++ b/src/main/java/net/errorcraft/itematic/item/component/components/ConsumableItemComponent.java @@ -10,27 +10,52 @@ import net.errorcraft.itematic.world.action.context.ActionContext; import net.errorcraft.itematic.world.action.context.parameter.ActionContextParameter; import net.minecraft.advancement.criterion.Criteria; +import net.minecraft.component.type.FoodComponent; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.item.ItemUsage; +import net.minecraft.registry.Registries; import net.minecraft.registry.RegistryKeys; import net.minecraft.registry.entry.RegistryEntry; import net.minecraft.registry.entry.RegistryFixedCodec; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; +import net.minecraft.sound.SoundEvent; +import net.minecraft.sound.SoundEvents; import net.minecraft.stat.Stats; import net.minecraft.util.Hand; +import net.minecraft.util.UseAction; import net.minecraft.world.World; +import java.util.HashSet; +import java.util.Objects; import java.util.Optional; +import java.util.Set; -public record ConsumableItemComponent(Optional> resultItem) implements ItemComponent { +public record ConsumableItemComponent(Optional> resultItem, boolean hasConsumeParticles, RegistryEntry sound) implements ItemComponent { + private static final RegistryEntry DEFAULT_SOUND = Registries.SOUND_EVENT.getEntry(SoundEvents.ENTITY_GENERIC_EAT); public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( - RegistryFixedCodec.of(RegistryKeys.ITEM).optionalFieldOf("result_item").forGetter(ConsumableItemComponent::resultItem) + RegistryFixedCodec.of(RegistryKeys.ITEM).optionalFieldOf("result_item").forGetter(ConsumableItemComponent::resultItem), + Codec.BOOL.optionalFieldOf("has_consume_particles", true).forGetter(ConsumableItemComponent::hasConsumeParticles), + SoundEvent.ENTRY_CODEC.optionalFieldOf("sound", DEFAULT_SOUND).forGetter(ConsumableItemComponent::sound) ).apply(instance, ConsumableItemComponent::new)); + public static ConsumableItemComponent of(RegistryEntry resultItem, boolean hasConsumeParticles, RegistryEntry sound) { + return new ConsumableItemComponent(Optional.ofNullable(resultItem), hasConsumeParticles, sound); + } + + public static Builder builder(int useDuration) { + return new Builder(useDuration); + } + + public static Builder builder(FoodComponent food) { + return new Builder(food.getEatTicks()) + .food(food) + .useAnimation(UseAction.EAT); + } + @Override public ItemComponentType type() { return ItemComponentTypes.CONSUMABLE; @@ -62,7 +87,56 @@ public void consume(LivingEntity user, ItemStack stack, ItemStackConsumer result player.incrementStat(Stats.USED.itematic$getOrCreateStat(stack.getRegistryEntry())); } - public static ConsumableItemComponent of(RegistryEntry resultItem) { - return new ConsumableItemComponent(Optional.ofNullable(resultItem)); + public static class Builder { + private final int useDuration; + private UseAction useAnimation; + private FoodItemComponent food; + private RegistryEntry resultItem; + private boolean hasConsumeParticles = true; + private RegistryEntry consumeSound = DEFAULT_SOUND; + + private Builder(int useDuration) { + this.useDuration = useDuration; + } + + public ItemComponent[] build() { + Set> behavior = new HashSet<>(); + behavior.add(UseableItemComponent.builder() + .ticks(this.useDuration) + .animation(this.useAnimation) + .build() + ); + behavior.add(ConsumableItemComponent.of(this.resultItem, this.hasConsumeParticles, this.consumeSound)); + if (this.food != null) { + behavior.add(this.food); + } + + return behavior.toArray(ItemComponent[]::new); + } + + public Builder food(FoodComponent food) { + this.food = FoodItemComponent.of(food); + return this; + } + + public Builder useAnimation(UseAction animation) { + this.useAnimation = animation; + return this; + } + + public Builder resultItem(RegistryEntry resultItem) { + this.resultItem = Objects.requireNonNull(resultItem); + return this; + } + + public Builder noConsumeParticles() { + this.hasConsumeParticles = false; + return this; + } + + public Builder consumeSound(RegistryEntry consumeSound) { + this.consumeSound = Objects.requireNonNull(consumeSound); + return this; + } } } diff --git a/src/main/java/net/errorcraft/itematic/item/component/components/FoodItemComponent.java b/src/main/java/net/errorcraft/itematic/item/component/components/FoodItemComponent.java index 405a91ff..8b88d0a4 100644 --- a/src/main/java/net/errorcraft/itematic/item/component/components/FoodItemComponent.java +++ b/src/main/java/net/errorcraft/itematic/item/component/components/FoodItemComponent.java @@ -6,16 +6,12 @@ import net.errorcraft.itematic.item.component.ItemComponent; import net.errorcraft.itematic.item.component.ItemComponentType; import net.errorcraft.itematic.item.component.ItemComponentTypes; -import net.minecraft.SharedConstants; import net.minecraft.component.ComponentMap; import net.minecraft.component.DataComponentTypes; import net.minecraft.component.type.FoodComponent; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.Item; import net.minecraft.item.ItemStack; -import net.minecraft.registry.entry.RegistryEntry; -import net.minecraft.util.UseAction; import net.minecraft.util.dynamic.Codecs; import net.minecraft.world.World; @@ -29,27 +25,8 @@ public record FoodItemComponent(int nutrition, float saturation, boolean alwaysE FoodComponent.StatusEffectEntry.CODEC.listOf().optionalFieldOf("effects", List.of()).forGetter(FoodItemComponent::effects) ).apply(instance, FoodItemComponent::new)); - public static FoodItemComponent of(int nutrition, float saturation, boolean alwaysEdible, List effects) { - return new FoodItemComponent(nutrition, saturation, alwaysEdible, effects); - } - - public static ItemComponent[] from(FoodComponent component) { - return from(component, null); - } - - public static ItemComponent[] from(FoodComponent component, RegistryEntry resultItem) { - return from(component, (int)(component.eatSeconds() * SharedConstants.TICKS_PER_SECOND), UseAction.EAT, resultItem); - } - - public static ItemComponent[] from(FoodComponent component, int eatTicks, UseAction useAction, RegistryEntry resultItem) { - return new ItemComponent[] { - UseableItemComponent.builder() - .ticks(eatTicks) - .animation(useAction) - .build(), - of(component.nutrition(), component.saturation(), component.canAlwaysEat(), component.effects()), - ConsumableItemComponent.of(resultItem) - }; + public static FoodItemComponent of(FoodComponent food) { + return new FoodItemComponent(food.nutrition(), food.saturation(), food.canAlwaysEat(), food.effects()); } @Override diff --git a/src/main/java/net/errorcraft/itematic/item/component/components/ProjectileItemComponent.java b/src/main/java/net/errorcraft/itematic/item/component/components/ProjectileItemComponent.java index ca2ac7ad..16212986 100644 --- a/src/main/java/net/errorcraft/itematic/item/component/components/ProjectileItemComponent.java +++ b/src/main/java/net/errorcraft/itematic/item/component/components/ProjectileItemComponent.java @@ -9,8 +9,6 @@ import net.errorcraft.itematic.item.component.ItemComponent; import net.errorcraft.itematic.item.component.ItemComponentType; import net.errorcraft.itematic.item.component.ItemComponentTypes; -import net.errorcraft.itematic.mixin.item.CrossbowItemAccessor; -import net.errorcraft.itematic.serialization.ItematicCodecs; import net.errorcraft.itematic.world.action.context.ActionContext; import net.errorcraft.itematic.world.action.context.parameter.ActionContextParameter; import net.minecraft.entity.Entity; @@ -23,32 +21,25 @@ import net.minecraft.item.ItemStack; import net.minecraft.registry.entry.RegistryEntry; import net.minecraft.server.world.ServerWorld; -import net.minecraft.util.dynamic.Codecs; import net.minecraft.util.math.Direction; import net.minecraft.util.math.Position; import net.minecraft.world.World; -public record ProjectileItemComponent(EntityInitializer entity, int damage, float chargedSpeed) implements ItemComponent { +public record ProjectileItemComponent(EntityInitializer entity) implements ItemComponent { public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( - EntityInitializer.CODEC.fieldOf("entity").forGetter(ProjectileItemComponent::entity), - Codecs.NONNEGATIVE_INT.optionalFieldOf("damage", 0).forGetter(ProjectileItemComponent::damage), - ItematicCodecs.NON_NEGATIVE_FLOAT.optionalFieldOf("charged_speed", CrossbowItemAccessor.defaultSpeed()).forGetter(ProjectileItemComponent::chargedSpeed) + EntityInitializer.CODEC.fieldOf("entity").forGetter(ProjectileItemComponent::entity) ).apply(instance, ProjectileItemComponent::new)); - public static ProjectileItemComponent of(EntityInitializer entity, int damage, float chargedSpeed) { - return new ProjectileItemComponent(entity, damage, chargedSpeed); - } - - public static ProjectileItemComponent of(EntityInitializer entity, int damage) { - return of(entity, damage, CrossbowItemAccessor.defaultSpeed()); + public static ProjectileItemComponent of(EntityInitializer entity) { + return new ProjectileItemComponent(entity); } public static ProjectileItemComponent of(RegistryEntry> entity) { - return of(SimpleEntityInitializer.of(entity.value()), 0); + return of(SimpleEntityInitializer.of(entity.value())); } public static ProjectileItemComponent persistentProjectile(EntityType entityType, PersistentProjectileEntityInitializer.OwnerCreator ownerCreator, PersistentProjectileEntityInitializer.SimpleCreator simpleCreator) { - return of(new PersistentProjectileEntityInitializer<>(entityType, ownerCreator, simpleCreator), 1); + return of(new PersistentProjectileEntityInitializer<>(entityType, ownerCreator, simpleCreator)); } @Override diff --git a/src/main/java/net/errorcraft/itematic/item/component/components/ShooterItemComponent.java b/src/main/java/net/errorcraft/itematic/item/component/components/ShooterItemComponent.java index f44eee33..a2b014e6 100644 --- a/src/main/java/net/errorcraft/itematic/item/component/components/ShooterItemComponent.java +++ b/src/main/java/net/errorcraft/itematic/item/component/components/ShooterItemComponent.java @@ -3,37 +3,27 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; import net.errorcraft.itematic.component.ItematicDataComponentTypes; +import net.errorcraft.itematic.component.type.ItemDamageRulesDataComponent; import net.errorcraft.itematic.component.type.ItemListDataComponent; -import net.errorcraft.itematic.item.ItemKeys; import net.errorcraft.itematic.item.ItemStackConsumer; import net.errorcraft.itematic.item.component.ItemComponent; import net.errorcraft.itematic.item.component.ItemComponentType; import net.errorcraft.itematic.item.component.ItemComponentTypes; +import net.errorcraft.itematic.item.shooter.method.ShooterMethod; import net.errorcraft.itematic.item.use.provider.providers.ShooterIntegerProvider; -import net.errorcraft.itematic.mixin.item.CrossbowItemAccessor; import net.errorcraft.itematic.world.action.context.ActionContext; import net.errorcraft.itematic.world.action.context.parameter.ActionContextParameter; import net.minecraft.component.ComponentMap; -import net.minecraft.component.DataComponentTypes; -import net.minecraft.component.type.ChargedProjectilesComponent; -import net.minecraft.enchantment.EnchantmentHelper; -import net.minecraft.enchantment.Enchantments; import net.minecraft.entity.Entity; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.entity.projectile.PersistentProjectileEntity; -import net.minecraft.item.BowItem; -import net.minecraft.item.CrossbowItem; +import net.minecraft.entity.projectile.ProjectileEntity; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.registry.RegistryCodecs; import net.minecraft.registry.RegistryKeys; -import net.minecraft.registry.entry.RegistryEntry; import net.minecraft.registry.entry.RegistryEntryList; import net.minecraft.server.world.ServerWorld; -import net.minecraft.sound.SoundCategory; -import net.minecraft.sound.SoundEvent; -import net.minecraft.sound.SoundEvents; import net.minecraft.util.ActionResult; import net.minecraft.util.Hand; import net.minecraft.util.UseAction; @@ -43,39 +33,30 @@ import java.util.List; import java.util.Optional; +import java.util.OptionalInt; -public record ShooterItemComponent(RegistryEntryList heldAmmunition, RegistryEntryList ammunition, int range, Optional chargeable) implements ItemComponent { +public record ShooterItemComponent(RegistryEntryList heldAmmunition, RegistryEntryList ammunition, int range, ShooterMethod method, ItemDamageRulesDataComponent itemDamage) implements ItemComponent { public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( RegistryCodecs.entryList(RegistryKeys.ITEM).fieldOf("held_ammunition").forGetter(ShooterItemComponent::heldAmmunition), RegistryCodecs.entryList(RegistryKeys.ITEM).fieldOf("ammunition").forGetter(ShooterItemComponent::ammunition), Codecs.POSITIVE_INT.fieldOf("range").forGetter(ShooterItemComponent::range), - Chargeable.CODEC.optionalFieldOf("chargeable").forGetter(ShooterItemComponent::chargeable) + ShooterMethod.CODEC.fieldOf("method").forGetter(ShooterItemComponent::method), + ItemDamageRulesDataComponent.CODEC.fieldOf("item_damage").forGetter(ShooterItemComponent::itemDamage) ).apply(instance, ShooterItemComponent::new)); - private static final float CHARGE_PROGRESS = CrossbowItemAccessor.chargeProgress(); - private static final float LOAD_PROGRESS = CrossbowItemAccessor.loadProgress(); - private static final int DEFAULT_CHARGE_TIME = CrossbowItemAccessor.defaultPullTime(); - private static final int EXTRA_USE_TIME = 3; - private static final int CHARGE_TIME_PER_QUICK_CHARGE_LEVEL = 5; - private static final CrossbowItem DUMMY = new CrossbowItem(new Item.Settings()); - public static ItemComponent[] of(RegistryEntryList heldAmmunition, RegistryEntryList ammunition, int range) { + public static ItemComponent[] of(UseAction animation, RegistryEntryList heldAmmunition, RegistryEntryList ammunition, int range, ShooterMethod method, ItemDamageRulesDataComponent.Rule... rules) { return new ItemComponent[] { UseableItemComponent.builder() .ticks(ShooterIntegerProvider.INSTANCE) - .animation(UseAction.BOW) + .animation(animation) .build(), - new ShooterItemComponent(heldAmmunition, ammunition, range, Optional.empty()) - }; - } - - @SafeVarargs - public static ItemComponent[] of(RegistryEntryList heldAmmunition, RegistryEntryList ammunition, int range, RegistryEntry defaultSound, RegistryEntry... levelSounds) { - return new ItemComponent[] { - UseableItemComponent.builder() - .ticks(ShooterIntegerProvider.INSTANCE) - .animation(UseAction.CROSSBOW) - .build(), - new ShooterItemComponent(heldAmmunition, ammunition, range, Optional.of(Chargeable.of(QuickChargeSounds.of(defaultSound, levelSounds)))) + new ShooterItemComponent( + heldAmmunition, + ammunition, + range, + method, + new ItemDamageRulesDataComponent(List.of(rules), 1) + ) }; } @@ -91,189 +72,86 @@ public Codec codec() { @Override public ActionResult use(World world, PlayerEntity user, Hand hand, ItemStack stack, ItemStackConsumer resultStackConsumer) { - if (!this.isCharged(stack)) { - return ActionResult.PASS; + if (this.method.tryShoot(this, stack, world, user, hand)) { + return ActionResult.CONSUME; } - float chargedSpeed = stack.getOrDefault(DataComponentTypes.CHARGED_PROJECTILES, ChargedProjectilesComponent.DEFAULT) - .itematic$getChargedSpeed(); - DUMMY.shootAll(world, user, hand, stack, chargedSpeed, 1.0f, null); - return ActionResult.CONSUME; + + return ActionResult.PASS; } @Override public void using(ItemStack stack, World world, LivingEntity user, int usedTicks, int remainingUseTicks) { - this.chargeable.ifPresent(chargeable -> this.tryLoad(stack, world, user, usedTicks, chargeable)); + this.method.hold(this, stack, world, user, usedTicks); } @Override public void stopUsing(ItemStack stack, World world, LivingEntity user, int usedTicks, int remainingUseTicks, ItemStackConsumer resultStackConsumer) { - float pullProgress = this.getPullProgress(stack, usedTicks); - if (this.isChargeable()) { - this.charge(stack, world, user, pullProgress); - return; - } - if (!(user instanceof PlayerEntity player)) { - return; - } - this.shoot(stack, world, player, pullProgress, resultStackConsumer); + this.method.stop(this, stack, world, user, usedTicks); } @Override public void addComponents(ComponentMap.Builder builder) { builder.add(ItematicDataComponentTypes.SHOOTER_AMMUNITION, new ItemListDataComponent(this.ammunition)); builder.add(ItematicDataComponentTypes.SHOOTER_HELD_AMMUNITION, new ItemListDataComponent(this.heldAmmunition)); - if (this.isChargeable()) { - builder.add(DataComponentTypes.CHARGED_PROJECTILES, ChargedProjectilesComponent.DEFAULT); - } - } - - public static int useDuration(ItemStack stack) { - return getPullTime(stack) + EXTRA_USE_TIME; - } - - public static int getPullTime(ItemStack stack) { - int quickChargeLevel = EnchantmentHelper.getLevel(Enchantments.QUICK_CHARGE, stack); - return DEFAULT_CHARGE_TIME - CHARGE_TIME_PER_QUICK_CHARGE_LEVEL * quickChargeLevel; - } - - public void shootAll(World world, LivingEntity shooter, Hand hand, ItemStack stack, float speed, float divergence, @Nullable LivingEntity target) { - DUMMY.shootAll(world, shooter, hand, stack, speed, divergence, target); - } + builder.add(ItematicDataComponentTypes.SHOOTER_DAMAGE_RULES, this.itemDamage); + this.method.addComponents(builder); + } + + public void shoot(ServerWorld world, LivingEntity shooter, Hand hand, ItemStack shooterStack, List projectiles, float power, float divergence, boolean critical, @Nullable LivingEntity target) { + float maxAngle = 10.0f; + float angleStep = projectiles.size() == 1 ? + 0.0f : + 2.0f * maxAngle / (projectiles.size() - 1); + float angleOffset = ((projectiles.size() - 1) % 2.0f) * angleStep / 2.0f; + float direction = 1.0f; + for (int i = 0; i < projectiles.size(); i++) { + ItemStack projectile = projectiles.get(i); + if (projectile.isEmpty()) { + continue; + } - public float getPullProgress(ItemStack stack, int usedTicks) { - if (this.isChargeable()) { - float progress = (float)usedTicks / getPullTime(stack); - return Math.min(progress, 1.0f); + float angle = angleOffset + direction * ((i + 1) / 2.0f) * angleStep; + direction *= -1; + this.damageItem(shooterStack, world, hand, shooter); + this.createProjectile(projectile, world, shooter, power, divergence, angle, i, critical, target); } - return BowItem.getPullProgress(usedTicks); - } - - public boolean isChargeable() { - return this.chargeable.isPresent(); } - public boolean isCharged(ItemStack stack) { - return this.isChargeable() && !stack.getOrDefault(DataComponentTypes.CHARGED_PROJECTILES, ChargedProjectilesComponent.DEFAULT).isEmpty(); + public OptionalInt useDuration(ItemStack stack, LivingEntity user) { + return this.method.useDuration(stack, user); } - private void tryLoad(ItemStack stack, World world, LivingEntity user, int usedTicks, Chargeable chargeable) { - if (world.isClient()) { + private void damageItem(ItemStack stack, ServerWorld world, Hand hand, LivingEntity shooter) { + ItemDamageRulesDataComponent rules = stack.get(ItematicDataComponentTypes.SHOOTER_DAMAGE_RULES); + if (rules == null) { return; } - int pullTime = getPullTime(stack); - if (usedTicks >= pullTime) { - return; - } - int quickChargeLevel = EnchantmentHelper.getLevel(Enchantments.QUICK_CHARGE, stack); - if (usedTicks == getPullTimeAt(pullTime, CHARGE_PROGRESS)) { - world.playSound(null, user.getX(), user.getY(), user.getZ(), chargeable.quickChargeSounds.get(quickChargeLevel).value(), SoundCategory.PLAYERS, 0.5f, 1.0f); - return; - } - if (usedTicks == getPullTimeAt(pullTime, LOAD_PROGRESS) && quickChargeLevel == 0) { - world.playSound(null, user.getX(), user.getY(), user.getZ(), SoundEvents.ITEM_CROSSBOW_LOADING_MIDDLE, SoundCategory.PLAYERS, 0.5f, 1.0f); - } - } - private void charge(ItemStack stack, World world, LivingEntity user, float pullProgress) { - if (pullProgress == 1.0f && !this.isCharged(stack) && CrossbowItemAccessor.loadProjectiles(user, stack)) { - SoundCategory soundCategory = user instanceof PlayerEntity ? SoundCategory.PLAYERS : SoundCategory.HOSTILE; - world.playSound(null, user.getX(), user.getY(), user.getZ(), SoundEvents.ITEM_CROSSBOW_LOADING_END, soundCategory, 1.0f, 1.0f / (world.getRandom().nextFloat() * 0.5f + 1.0f) + 0.2f); - } - } - - private void shoot(ItemStack stack, World world, PlayerEntity player, float pullProgress, ItemStackConsumer resultStackConsumer) { - if (pullProgress < 0.1f) { - return; - } - ItemStack ammunition = player.itematic$getAmmunition(stack); - if (ammunition.isEmpty()) { - return; - } - boolean disallowPickup = EnchantmentHelper.getLevel(Enchantments.INFINITY, stack) > 0 && ammunition.itematic$isOf(ItemKeys.ARROW); - if (!world.isClient()) { - this.createProjectile(stack, ammunition, (ServerWorld) world, player, pullProgress, disallowPickup, resultStackConsumer); - } - world.playSound(null, player.getX(), player.getY(), player.getZ(), SoundEvents.ENTITY_ARROW_SHOOT, SoundCategory.PLAYERS, 1.0f, 1.0f / (world.getRandom().nextFloat() * 0.4f + 1.2f) + pullProgress * 0.5f); - if (player.getAbilities().creativeMode || disallowPickup) { + int damage = rules.damage(stack); + if (damage == 0) { return; } - ammunition.decrement(1); - if (ammunition.isEmpty()) { - player.getInventory().removeOne(ammunition); - } - } - private void createProjectile(ItemStack stack, ItemStack ammunition, ServerWorld world, PlayerEntity player, float pullProgress, boolean disallowPickup, ItemStackConsumer resultStackConsumer) { - Optional optionalEntity = ammunition.itematic$getComponent(ItemComponentTypes.PROJECTILE) - .map(c -> c.createEntity(world, player, ammunition, 0.0f, pullProgress * 3.0f)); + ActionContext context = ActionContext.builder(world) + .stack(stack) + .hand(hand) + .entityPosition(ActionContextParameter.THIS, shooter) + .build(); + stack.itematic$damage(damage, context); + } + private void createProjectile(ItemStack projectile, ServerWorld world, LivingEntity shooter, float power, float divergence, float angle, int index, boolean critical, @Nullable LivingEntity target) { + Optional optionalEntity = projectile.itematic$getComponent(ItemComponentTypes.PROJECTILE) + .map(projectileComponent -> projectileComponent.createEntity(world, shooter, projectile, 0.0f, power)); if (optionalEntity.isEmpty()) { return; } Entity entity = optionalEntity.get(); - if (entity instanceof PersistentProjectileEntity persistentProjectileEntity) { - this.initProjectile(persistentProjectileEntity, stack, pullProgress); - - ActionContext context = ActionContext.builder(world, stack, resultStackConsumer, player.getActiveHand()) - .entityPosition(ActionContextParameter.THIS, player) - .build(); - stack.itematic$damage(1, context); - if (disallowPickup) { - persistentProjectileEntity.pickupType = PersistentProjectileEntity.PickupPermission.CREATIVE_ONLY; - } + if (entity instanceof ProjectileEntity projectileEntity) { + this.method.initializeProjectile(shooter, projectileEntity, index, power, divergence, angle, critical, target); } world.spawnEntity(entity); } - - private void initProjectile(PersistentProjectileEntity entity, ItemStack stack, float pullProgress) { - if (pullProgress == 1.0f) { - entity.setCritical(true); - } - int powerLevel = EnchantmentHelper.getLevel(Enchantments.POWER, stack); - if (powerLevel > 0) { - entity.setDamage(entity.getDamage() + powerLevel * 0.5d + 0.5d); - } - int punchLevel = EnchantmentHelper.getLevel(Enchantments.PUNCH, stack); - if (punchLevel > 0) { - entity.setPunch(punchLevel); - } - if (EnchantmentHelper.getLevel(Enchantments.FLAME, stack) > 0) { - entity.setOnFireFor(100); - } - } - - private static int getPullTimeAt(int pullTime, float progress) { - return (int)(progress * pullTime); - } - - public record Chargeable(QuickChargeSounds quickChargeSounds) { - public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( - QuickChargeSounds.CODEC.fieldOf("quick_charge_sounds").forGetter(Chargeable::quickChargeSounds) - ).apply(instance, Chargeable::new)); - - public static Chargeable of(QuickChargeSounds quickChargeSounds) { - return new Chargeable(quickChargeSounds); - } - } - - public record QuickChargeSounds(List> levels, RegistryEntry defaultSound) { - public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( - SoundEvent.ENTRY_CODEC.listOf().fieldOf("levels").forGetter(QuickChargeSounds::levels), - SoundEvent.ENTRY_CODEC.fieldOf("default").forGetter(QuickChargeSounds::defaultSound) - ).apply(instance, QuickChargeSounds::new)); - - public RegistryEntry get(int level) { - if (level < 0 || level >= this.levels.size()) { - return this.defaultSound; - } - return this.levels.get(level); - } - - @SafeVarargs - public static QuickChargeSounds of(RegistryEntry defaultSound, RegistryEntry... levelSounds) { - return new QuickChargeSounds(List.of(levelSounds), defaultSound); - } - } } diff --git a/src/main/java/net/errorcraft/itematic/item/model/override/overrides/ChargedModelOverride.java b/src/main/java/net/errorcraft/itematic/item/model/override/overrides/ChargedModelOverride.java index 54a3af48..88d2be5f 100644 --- a/src/main/java/net/errorcraft/itematic/item/model/override/overrides/ChargedModelOverride.java +++ b/src/main/java/net/errorcraft/itematic/item/model/override/overrides/ChargedModelOverride.java @@ -3,6 +3,7 @@ import net.errorcraft.itematic.item.component.ItemComponentTypes; import net.errorcraft.itematic.item.model.override.ModelOverride; import net.minecraft.entity.LivingEntity; +import net.minecraft.item.CrossbowItem; import net.minecraft.item.ItemStack; import net.minecraft.world.World; import org.jetbrains.annotations.Nullable; @@ -10,9 +11,11 @@ public class ChargedModelOverride implements ModelOverride { @Override public float apply(ItemStack stack, @Nullable World world, @Nullable LivingEntity target, int seed) { - return stack.itematic$getComponent(ItemComponentTypes.SHOOTER) - .map(c -> c.isCharged(stack) ? 1.0f : 0.0f) - .orElse(0.0f); + if (CrossbowItem.isCharged(stack)) { + return 1.0f; + } + + return 0.0f; } @Override diff --git a/src/main/java/net/errorcraft/itematic/item/model/override/overrides/PullModelOverride.java b/src/main/java/net/errorcraft/itematic/item/model/override/overrides/PullModelOverride.java index 38d9d82d..6981871e 100644 --- a/src/main/java/net/errorcraft/itematic/item/model/override/overrides/PullModelOverride.java +++ b/src/main/java/net/errorcraft/itematic/item/model/override/overrides/PullModelOverride.java @@ -13,11 +13,13 @@ public float applyUnclamped(ItemStack stack, @Nullable World world, @Nullable Li if (target == null) { return 0.0f; } + if (target.getActiveItem() != stack) { return 0.0f; } + return stack.itematic$getComponent(ItemComponentTypes.SHOOTER) - .map(c -> c.getPullProgress(stack, target.itematic$itemUsedTicks())) + .map(shooter -> shooter.method().pullProgress(stack, target, target.itematic$itemUsedTicks())) .orElse(0.0f); } diff --git a/src/main/java/net/errorcraft/itematic/item/model/override/overrides/PullingModelOverride.java b/src/main/java/net/errorcraft/itematic/item/model/override/overrides/PullingModelOverride.java index 315d2ebd..a26f5369 100644 --- a/src/main/java/net/errorcraft/itematic/item/model/override/overrides/PullingModelOverride.java +++ b/src/main/java/net/errorcraft/itematic/item/model/override/overrides/PullingModelOverride.java @@ -3,6 +3,7 @@ import net.errorcraft.itematic.item.component.ItemComponentTypes; import net.errorcraft.itematic.item.model.override.ModelOverride; import net.minecraft.entity.LivingEntity; +import net.minecraft.item.CrossbowItem; import net.minecraft.item.ItemStack; import net.minecraft.world.World; import org.jetbrains.annotations.Nullable; @@ -13,15 +14,20 @@ public float apply(ItemStack stack, @Nullable World world, @Nullable LivingEntit if (target == null) { return 0.0f; } + if (!target.isUsingItem()) { return 0.0f; } + if (target.getActiveItem() != stack) { return 0.0f; } - return stack.itematic$getComponent(ItemComponentTypes.SHOOTER) - .map(c -> c.isCharged(stack) ? 0.0f : 1.0f) - .orElse(0.0f); + + if (CrossbowItem.isCharged(stack)) { + return 0.0f; + } + + return 1.0f; } @Override diff --git a/src/main/java/net/errorcraft/itematic/item/shooter/method/ShooterMethod.java b/src/main/java/net/errorcraft/itematic/item/shooter/method/ShooterMethod.java new file mode 100644 index 00000000..4e5bed3c --- /dev/null +++ b/src/main/java/net/errorcraft/itematic/item/shooter/method/ShooterMethod.java @@ -0,0 +1,32 @@ +package net.errorcraft.itematic.item.shooter.method; + +import com.mojang.serialization.Codec; +import net.errorcraft.itematic.item.component.components.ShooterItemComponent; +import net.errorcraft.itematic.registry.ItematicRegistries; +import net.minecraft.component.ComponentMap; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.projectile.PersistentProjectileEntity; +import net.minecraft.entity.projectile.ProjectileEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Hand; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; + +import java.util.OptionalInt; + +public interface ShooterMethod { + Codec CODEC = ItematicRegistries.SHOOTER_METHOD_TYPE.getCodec().dispatch(ShooterMethod::type, ShooterMethodType::codec); + + ShooterMethodType type(); + void addComponents(ComponentMap.Builder builder); + boolean tryShoot(ShooterItemComponent component, ItemStack stack, World world, LivingEntity user, Hand hand); + void hold(ShooterItemComponent shooter, ItemStack stack, World world, LivingEntity user, int usedTicks); + void stop(ShooterItemComponent shooter, ItemStack stack, World world, LivingEntity user, int usedTicks); + default void initializeProjectile(LivingEntity user, ProjectileEntity projectile, int index, float power, float uncertainty, float angle, boolean critical, @Nullable LivingEntity target) { + if (critical && projectile instanceof PersistentProjectileEntity persistentProjectile) { + persistentProjectile.setCritical(true); + } + } + OptionalInt useDuration(ItemStack stack, LivingEntity user); + float pullProgress(ItemStack stack, LivingEntity user, int usedTicks); +} diff --git a/src/main/java/net/errorcraft/itematic/item/shooter/method/ShooterMethodType.java b/src/main/java/net/errorcraft/itematic/item/shooter/method/ShooterMethodType.java new file mode 100644 index 00000000..ee69fd41 --- /dev/null +++ b/src/main/java/net/errorcraft/itematic/item/shooter/method/ShooterMethodType.java @@ -0,0 +1,6 @@ +package net.errorcraft.itematic.item.shooter.method; + +import com.mojang.serialization.MapCodec; + +public record ShooterMethodType(MapCodec codec) { +} diff --git a/src/main/java/net/errorcraft/itematic/item/shooter/method/ShooterMethodTypeKeys.java b/src/main/java/net/errorcraft/itematic/item/shooter/method/ShooterMethodTypeKeys.java new file mode 100644 index 00000000..8666761c --- /dev/null +++ b/src/main/java/net/errorcraft/itematic/item/shooter/method/ShooterMethodTypeKeys.java @@ -0,0 +1,16 @@ +package net.errorcraft.itematic.item.shooter.method; + +import net.errorcraft.itematic.registry.ItematicRegistryKeys; +import net.minecraft.registry.RegistryKey; +import net.minecraft.util.Identifier; + +public class ShooterMethodTypeKeys { + public static final RegistryKey> DIRECT = of("direct"); + public static final RegistryKey> CHARGEABLE = of("chargeable"); + + private ShooterMethodTypeKeys() {} + + private static RegistryKey> of(String id) { + return RegistryKey.of(ItematicRegistryKeys.SHOOTER_METHOD_TYPE, new Identifier(id)); + } +} diff --git a/src/main/java/net/errorcraft/itematic/item/shooter/method/ShooterMethodTypes.java b/src/main/java/net/errorcraft/itematic/item/shooter/method/ShooterMethodTypes.java new file mode 100644 index 00000000..bf9868cb --- /dev/null +++ b/src/main/java/net/errorcraft/itematic/item/shooter/method/ShooterMethodTypes.java @@ -0,0 +1,20 @@ +package net.errorcraft.itematic.item.shooter.method; + +import net.errorcraft.itematic.item.shooter.method.methods.ChargeableShooterMethod; +import net.errorcraft.itematic.item.shooter.method.methods.DirectShooterMethod; +import net.errorcraft.itematic.registry.ItematicRegistries; +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; + +public class ShooterMethodTypes { + public static final ShooterMethodType DIRECT = register(ShooterMethodTypeKeys.DIRECT, new ShooterMethodType<>(DirectShooterMethod.CODEC)); + public static final ShooterMethodType CHARGEABLE = register(ShooterMethodTypeKeys.CHARGEABLE, new ShooterMethodType<>(ChargeableShooterMethod.CODEC)); + + private ShooterMethodTypes() {} + + public static void init() {} + + private static ShooterMethodType register(RegistryKey> id, ShooterMethodType type) { + return Registry.register(ItematicRegistries.SHOOTER_METHOD_TYPE, id, type); + } +} diff --git a/src/main/java/net/errorcraft/itematic/item/shooter/method/methods/ChargeableShooterMethod.java b/src/main/java/net/errorcraft/itematic/item/shooter/method/methods/ChargeableShooterMethod.java new file mode 100644 index 00000000..6d64cd5e --- /dev/null +++ b/src/main/java/net/errorcraft/itematic/item/shooter/method/methods/ChargeableShooterMethod.java @@ -0,0 +1,284 @@ +package net.errorcraft.itematic.item.shooter.method.methods; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.errorcraft.itematic.component.ItematicDataComponentTypes; +import net.errorcraft.itematic.item.component.components.ShooterItemComponent; +import net.errorcraft.itematic.item.shooter.method.ShooterMethod; +import net.errorcraft.itematic.item.shooter.method.ShooterMethodType; +import net.errorcraft.itematic.item.shooter.method.ShooterMethodTypes; +import net.errorcraft.itematic.mixin.item.CrossbowItemAccessor; +import net.errorcraft.itematic.mixin.item.RangedWeaponItemAccessor; +import net.errorcraft.itematic.serialization.ItematicCodecs; +import net.minecraft.SharedConstants; +import net.minecraft.advancement.criterion.Criteria; +import net.minecraft.component.ComponentMap; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.ChargedProjectilesComponent; +import net.minecraft.enchantment.EnchantmentHelper; +import net.minecraft.enchantment.Enchantments; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.projectile.PersistentProjectileEntity; +import net.minecraft.entity.projectile.ProjectileEntity; +import net.minecraft.item.CrossbowItem; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.network.RegistryByteBuf; +import net.minecraft.network.codec.PacketCodec; +import net.minecraft.network.codec.PacketCodecs; +import net.minecraft.registry.Registries; +import net.minecraft.registry.RegistryCodecs; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.registry.entry.RegistryEntryList; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.sound.SoundEvent; +import net.minecraft.sound.SoundEvents; +import net.minecraft.stat.Stats; +import net.minecraft.util.Hand; +import net.minecraft.util.math.MathHelper; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Optional; +import java.util.OptionalInt; +import java.util.stream.Stream; + +public record ChargeableShooterMethod(float defaultChargeTime, ChargingSounds defaultChargingSounds, ChargedPowerRules chargedPowerRules) implements ShooterMethod { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group( + ItematicCodecs.NON_NEGATIVE_FLOAT.fieldOf("default_charge_time").forGetter(ChargeableShooterMethod::defaultChargeTime), + ChargingSounds.CODEC.fieldOf("default_charging_sounds").forGetter(ChargeableShooterMethod::defaultChargingSounds), + ChargedPowerRules.CODEC.fieldOf("charged_power_rules").forGetter(ChargeableShooterMethod::chargedPowerRules) + ).apply(instance, ChargeableShooterMethod::new)); + private static final float START_SOUND_PROGRESS = CrossbowItemAccessor.startSoundProgress(); + private static final float MID_SOUND_PROGRESS = CrossbowItemAccessor.midSoundProgress(); + private static final int EXTRA_USE_TIME = 3; + private static final CrossbowItem DUMMY = new CrossbowItem(new Item.Settings()); + + public static ChargeableShooterMethod of(ChargingSounds defaultChargingSounds, ChargedPowerRules.Rule... chargedPowerRules) { + return new ChargeableShooterMethod((float) CrossbowItemAccessor.defaultChargeTime() / SharedConstants.TICKS_PER_SECOND, defaultChargingSounds, new ChargedPowerRules(List.of(chargedPowerRules), CrossbowItemAccessor.defaultPower())); + } + + @Override + public ShooterMethodType type() { + return ShooterMethodTypes.CHARGEABLE; + } + + @Override + public void addComponents(ComponentMap.Builder builder) { + builder.add(DataComponentTypes.CHARGED_PROJECTILES, ChargedProjectilesComponent.DEFAULT); + builder.add(ItematicDataComponentTypes.SHOOTER_DEFAULT_CHARGE_TIME, this.defaultChargeTime); + builder.add(ItematicDataComponentTypes.SHOOTER_DEFAULT_CHARGING_SOUNDS, this.defaultChargingSounds); + builder.add(ItematicDataComponentTypes.SHOOTER_CHARGED_POWER_RULES, this.chargedPowerRules); + } + + @Override + public boolean tryShoot(ShooterItemComponent component, ItemStack stack, World world, LivingEntity user, Hand hand) { + if (!CrossbowItem.isCharged(stack)) { + return false; + } + + ChargedPowerRules chargedPowerRules = stack.get(ItematicDataComponentTypes.SHOOTER_CHARGED_POWER_RULES); + if (chargedPowerRules == null) { + return false; + } + + this.shoot(component, world, user, hand, stack, chargedPowerRules.power(stack), 1.0f, null); + return true; + } + + @Override + public void hold(ShooterItemComponent shooter, ItemStack stack, World world, LivingEntity user, int usedTicks) { + if (world.isClient()) { + return; + } + + int chargeTime = CrossbowItem.getPullTime(stack); + if (usedTicks >= chargeTime) { + return; + } + + ChargingSounds chargingSounds = this.chargingSounds(stack); + if (usedTicks == getChargeTimeAt(chargeTime, START_SOUND_PROGRESS)) { + chargingSounds.start().ifPresent(sound -> world.playSound(null, user.getX(), user.getY(), user.getZ(), sound.value(), user.getSoundCategory(), 0.5f, 1.0f)); + return; + } + + if (usedTicks == getChargeTimeAt(chargeTime, MID_SOUND_PROGRESS)) { + chargingSounds.mid().ifPresent(sound -> world.playSound(null, user.getX(), user.getY(), user.getZ(), sound.value(), user.getSoundCategory(), 0.5f, 1.0f)); + } + } + + @Override + public void stop(ShooterItemComponent shooter, ItemStack stack, World world, LivingEntity user, int usedTicks) { + if (usedTicks < CrossbowItem.getPullTime(stack)) { + return; + } + + if (CrossbowItem.isCharged(stack) || !chargeProjectiles(user, stack)) { + return; + } + + ChargingSounds chargingSounds = this.chargingSounds(stack); + float pitch = MathHelper.lerp(world.getRandom().nextFloat(), 0.87f, 1.2f); + chargingSounds.end().ifPresent(sound -> world.playSound(null, user.getX(), user.getY(), user.getZ(), sound.value(), user.getSoundCategory(), 1.0f, pitch)); + } + + @Override + public void initializeProjectile(LivingEntity user, ProjectileEntity projectile, int index, float power, float uncertainty, float angle, boolean critical, @Nullable LivingEntity target) { + ShooterMethod.super.initializeProjectile(user, projectile, index, power, uncertainty, angle, critical, target); + if (projectile instanceof PersistentProjectileEntity persistentProjectile) { + persistentProjectile.setSound(SoundEvents.ITEM_CROSSBOW_HIT); + } + + ((RangedWeaponItemAccessor) DUMMY).shoot(user, projectile, index, power, uncertainty, angle, target); + } + + @Override + public OptionalInt useDuration(ItemStack stack, LivingEntity user) { + if (CrossbowItem.isCharged(stack)) { + return OptionalInt.empty(); + } + + return OptionalInt.of(CrossbowItem.getPullTime(stack) + EXTRA_USE_TIME); + } + + @Override + public float pullProgress(ItemStack stack, LivingEntity user, int usedTicks) { + return ((float)usedTicks) / CrossbowItem.getPullTime(stack); + } + + public void shoot(ShooterItemComponent shooter, World world, LivingEntity user, Hand hand, ItemStack stack, float power, float divergence, @Nullable LivingEntity livingEntity) { + if (!(world instanceof ServerWorld serverWorld)) { + return; + } + + ChargedProjectilesComponent chargedProjectiles = stack.set(DataComponentTypes.CHARGED_PROJECTILES, ChargedProjectilesComponent.DEFAULT); + if (chargedProjectiles == null || chargedProjectiles.isEmpty()) { + return; + } + + shooter.shoot(serverWorld, user, hand, stack, chargedProjectiles.getProjectiles(), power, divergence, user instanceof PlayerEntity, livingEntity); + if (user instanceof ServerPlayerEntity player) { + Criteria.SHOT_CROSSBOW.trigger(player, stack); + player.incrementStat(Stats.USED.itematic$getOrCreateStat(stack.getRegistryEntry())); + } + } + + private static int getChargeTimeAt(int chargeTime, float progress) { + return MathHelper.floor(progress * chargeTime); + } + + private ChargingSounds chargingSounds(ItemStack stack) { + int quickChargeLevel = EnchantmentHelper.getLevel(Enchantments.QUICK_CHARGE, stack); + if (quickChargeLevel == 0) { + return stack.getOrDefault(ItematicDataComponentTypes.SHOOTER_DEFAULT_CHARGING_SOUNDS, ChargingSounds.EMPTY); + } + + return ChargingSounds.enchanted(quickChargeLevel); + } + + private static boolean chargeProjectiles(LivingEntity user, ItemStack stack) { + List projectiles = RangedWeaponItemAccessor.load(stack, user.itematic$getAmmunition(stack), user); + if (projectiles.isEmpty()) { + return false; + } + + stack.set(DataComponentTypes.CHARGED_PROJECTILES, ChargedProjectilesComponent.of(projectiles)); + return true; + } + + public record ChargingSounds(Optional> start, Optional> mid, Optional> end) { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + SoundEvent.ENTRY_CODEC.optionalFieldOf("start").forGetter(ChargingSounds::start), + SoundEvent.ENTRY_CODEC.optionalFieldOf("mid").forGetter(ChargingSounds::mid), + SoundEvent.ENTRY_CODEC.optionalFieldOf("end").forGetter(ChargingSounds::end) + ).apply(instance, ChargingSounds::new)); + public static final PacketCodec PACKET_CODEC = PacketCodec.tuple( + SoundEvent.ENTRY_PACKET_CODEC.collect(PacketCodecs::optional), ChargingSounds::start, + SoundEvent.ENTRY_PACKET_CODEC.collect(PacketCodecs::optional), ChargingSounds::mid, + SoundEvent.ENTRY_PACKET_CODEC.collect(PacketCodecs::optional), ChargingSounds::end, + ChargingSounds::new + ); + public static final ChargingSounds DEFAULT = new ChargingSounds( + Optional.of(Registries.SOUND_EVENT.getEntry(SoundEvents.ITEM_CROSSBOW_LOADING_START)), + Optional.of(Registries.SOUND_EVENT.getEntry(SoundEvents.ITEM_CROSSBOW_LOADING_MIDDLE)), + Optional.of(Registries.SOUND_EVENT.getEntry(SoundEvents.ITEM_CROSSBOW_LOADING_END)) + ); + private static final ChargingSounds EMPTY = new ChargingSounds( + Optional.empty(), + Optional.empty(), + Optional.empty() + ); + private static final List QUICK_CHARGE = Stream.of( + Registries.SOUND_EVENT.getEntry(SoundEvents.ITEM_CROSSBOW_QUICK_CHARGE_1), + Registries.SOUND_EVENT.getEntry(SoundEvents.ITEM_CROSSBOW_QUICK_CHARGE_2), + Registries.SOUND_EVENT.getEntry(SoundEvents.ITEM_CROSSBOW_QUICK_CHARGE_3) + ) + .map(startSound -> new ChargingSounds( + Optional.of(startSound), + Optional.empty(), + Optional.of(Registries.SOUND_EVENT.getEntry(SoundEvents.ITEM_CROSSBOW_LOADING_END)) + )) + .toList(); + + public static ChargingSounds enchanted(int quickChargeLevel) { + int index = quickChargeLevel - 1; + if (quickChargeLevel < 0) { + return QUICK_CHARGE.getFirst(); + } + + if (quickChargeLevel >= QUICK_CHARGE.size()) { + return QUICK_CHARGE.getLast(); + } + + return QUICK_CHARGE.get(index); + } + } + + public record ChargedPowerRules(List rules, float defaultPower) { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + Rule.CODEC.listOf().fieldOf("rules").forGetter(ChargedPowerRules::rules), + ItematicCodecs.NON_NEGATIVE_FLOAT.fieldOf("default_power").forGetter(ChargedPowerRules::defaultPower) + ).apply(instance, ChargedPowerRules::new)); + public static final PacketCodec PACKET_CODEC = PacketCodec.tuple( + Rule.PACKET_CODEC.collect(PacketCodecs.toList()), ChargedPowerRules::rules, + PacketCodecs.FLOAT, ChargedPowerRules::defaultPower, + ChargedPowerRules::new + ); + + public float power(ItemStack stack) { + for (Rule rule : this.rules) { + if (rule.power.isPresent() && rule.matches(stack)) { + return rule.power.get(); + } + } + + return this.defaultPower; + } + + public record Rule(RegistryEntryList items, Optional power) { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + RegistryCodecs.entryList(RegistryKeys.ITEM).fieldOf("items").forGetter(Rule::items), + ItematicCodecs.NON_NEGATIVE_FLOAT.optionalFieldOf("power").forGetter(Rule::power) + ).apply(instance, Rule::new)); + public static final PacketCodec PACKET_CODEC = PacketCodec.tuple( + PacketCodecs.registryEntryList(RegistryKeys.ITEM), Rule::items, + PacketCodecs.FLOAT.collect(PacketCodecs::optional), Rule::power, + Rule::new + ); + + public static Rule of(RegistryEntryList items, float power) { + return new Rule(items, Optional.of(power)); + } + + public boolean matches(ItemStack stack) { + return stack.isIn(this.items); + } + } + } +} diff --git a/src/main/java/net/errorcraft/itematic/item/shooter/method/methods/DirectShooterMethod.java b/src/main/java/net/errorcraft/itematic/item/shooter/method/methods/DirectShooterMethod.java new file mode 100644 index 00000000..90b98098 --- /dev/null +++ b/src/main/java/net/errorcraft/itematic/item/shooter/method/methods/DirectShooterMethod.java @@ -0,0 +1,108 @@ +package net.errorcraft.itematic.item.shooter.method.methods; + +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.errorcraft.itematic.component.ItematicDataComponentTypes; +import net.errorcraft.itematic.component.type.UseDurationDataComponent; +import net.errorcraft.itematic.item.component.components.ShooterItemComponent; +import net.errorcraft.itematic.item.shooter.method.ShooterMethod; +import net.errorcraft.itematic.item.shooter.method.ShooterMethodType; +import net.errorcraft.itematic.item.shooter.method.ShooterMethodTypes; +import net.errorcraft.itematic.mixin.item.RangedWeaponItemAccessor; +import net.minecraft.component.ComponentMap; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.projectile.ProjectileEntity; +import net.minecraft.item.BowItem; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.registry.Registries; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.sound.SoundCategory; +import net.minecraft.sound.SoundEvent; +import net.minecraft.sound.SoundEvents; +import net.minecraft.stat.Stats; +import net.minecraft.util.Hand; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.OptionalInt; + +public record DirectShooterMethod(RegistryEntry shootSound) implements ShooterMethod { + private static final RegistryEntry DEFAULT_SHOOT_SOUND = Registries.SOUND_EVENT.getEntry(SoundEvents.ENTITY_ARROW_SHOOT); + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group( + SoundEvent.ENTRY_CODEC.optionalFieldOf("shoot_sound", DEFAULT_SHOOT_SOUND).forGetter(DirectShooterMethod::shootSound) + ).apply(instance, DirectShooterMethod::new)); + private static final BowItem DUMMY = new BowItem(new Item.Settings()); + + public static DirectShooterMethod of() { + return new DirectShooterMethod(DEFAULT_SHOOT_SOUND); + } + + @Override + public ShooterMethodType type() { + return ShooterMethodTypes.DIRECT; + } + + @Override + public void addComponents(ComponentMap.Builder builder) { + builder.add(ItematicDataComponentTypes.SHOOTER_SHOOT_SOUND, this.shootSound); + } + + @Override + public boolean tryShoot(ShooterItemComponent component, ItemStack stack, World world, LivingEntity user, Hand hand) { + return false; + } + + @Override + public void hold(ShooterItemComponent shooter, ItemStack stack, World world, LivingEntity user, int usedTicks) {} + + @Override + public void stop(ShooterItemComponent shooter, ItemStack stack, World world, LivingEntity user, int usedTicks) { + ItemStack ammunition = user.itematic$getAmmunition(stack); + if (ammunition.isEmpty()) { + return; + } + + float pullProgress = this.pullProgress(usedTicks); + if (pullProgress < 0.1f) { + return; + } + + List projectiles = RangedWeaponItemAccessor.load(stack, ammunition, user); + if (world instanceof ServerWorld serverWorld && !projectiles.isEmpty()) { + shooter.shoot(serverWorld, user, user.getActiveHand(), stack, projectiles, pullProgress * 3.0f, 1.0f, pullProgress == 1.0f, null); + } + + RegistryEntry shootSound = stack.get(ItematicDataComponentTypes.SHOOTER_SHOOT_SOUND); + if (shootSound != null) { + world.playSound(null, user.getX(), user.getY(), user.getZ(), shootSound.value(), SoundCategory.PLAYERS, 1.0f, 1.0f / (world.getRandom().nextFloat() * 0.4f + 1.2f) + pullProgress * 0.5f); + } + + if (user instanceof PlayerEntity playerEntity) { + playerEntity.incrementStat(Stats.USED.itematic$getOrCreateStat(stack.getRegistryEntry())); + } + } + + @Override + public void initializeProjectile(LivingEntity user, ProjectileEntity projectile, int index, float power, float uncertainty, float angle, boolean critical, @Nullable LivingEntity target) { + ShooterMethod.super.initializeProjectile(user, projectile, index, power, uncertainty, angle, critical, target); + ((RangedWeaponItemAccessor) DUMMY).shoot(user, projectile, index, power, uncertainty, angle, target); + } + + @Override + public OptionalInt useDuration(ItemStack stack, LivingEntity user) { + return OptionalInt.of(UseDurationDataComponent.INDEFINITE_USE_DURATION); + } + + @Override + public float pullProgress(ItemStack stack, LivingEntity user, int usedTicks) { + return this.pullProgress(usedTicks); + } + + private float pullProgress(int usedTicks) { + return BowItem.getPullProgress(usedTicks); + } +} diff --git a/src/main/java/net/errorcraft/itematic/item/use/provider/providers/ShooterIntegerProvider.java b/src/main/java/net/errorcraft/itematic/item/use/provider/providers/ShooterIntegerProvider.java index e5cd1c27..0be2e733 100644 --- a/src/main/java/net/errorcraft/itematic/item/use/provider/providers/ShooterIntegerProvider.java +++ b/src/main/java/net/errorcraft/itematic/item/use/provider/providers/ShooterIntegerProvider.java @@ -2,9 +2,7 @@ import com.mojang.serialization.MapCodec; import io.netty.buffer.ByteBuf; -import net.errorcraft.itematic.component.type.UseDurationDataComponent; import net.errorcraft.itematic.item.component.ItemComponentTypes; -import net.errorcraft.itematic.item.component.components.ShooterItemComponent; import net.errorcraft.itematic.item.use.provider.IntegerProvider; import net.errorcraft.itematic.item.use.provider.IntegerProviderType; import net.errorcraft.itematic.item.use.provider.IntegerProviderTypes; @@ -12,7 +10,6 @@ import net.minecraft.item.ItemStack; import net.minecraft.network.codec.PacketCodec; -import java.util.Optional; import java.util.OptionalInt; public record ShooterIntegerProvider() implements IntegerProvider { @@ -27,16 +24,12 @@ public IntegerProviderType type() { @Override public OptionalInt get(ItemStack stack, LivingEntity user) { - Optional shooter = stack.itematic$getComponent(ItemComponentTypes.SHOOTER); - if (shooter.isEmpty()) { - return OptionalInt.empty(); - } - if (shooter.get().isCharged(stack)) { - return OptionalInt.empty(); - } if (user.itematic$getAmmunition(stack).isEmpty()) { return OptionalInt.empty(); } - return OptionalInt.of(UseDurationDataComponent.INDEFINITE_USE_DURATION); + + return stack.itematic$getComponent(ItemComponentTypes.SHOOTER) + .map(shooter -> shooter.useDuration(stack, user)) + .orElseGet(OptionalInt::empty); } } diff --git a/src/main/java/net/errorcraft/itematic/mixin/component/type/ChargedProjectilesComponentExtender.java b/src/main/java/net/errorcraft/itematic/mixin/component/type/ChargedProjectilesComponentExtender.java index 557dac4e..fc34e9bd 100644 --- a/src/main/java/net/errorcraft/itematic/mixin/component/type/ChargedProjectilesComponentExtender.java +++ b/src/main/java/net/errorcraft/itematic/mixin/component/type/ChargedProjectilesComponentExtender.java @@ -1,9 +1,6 @@ package net.errorcraft.itematic.mixin.component.type; import net.errorcraft.itematic.access.component.type.ChargedProjectilesComponentAccess; -import net.errorcraft.itematic.item.component.ItemComponentTypes; -import net.errorcraft.itematic.item.component.components.ProjectileItemComponent; -import net.errorcraft.itematic.mixin.item.CrossbowItemAccessor; import net.minecraft.component.type.ChargedProjectilesComponent; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; @@ -11,15 +8,11 @@ import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.Unique; import java.util.List; @Mixin(ChargedProjectilesComponent.class) public class ChargedProjectilesComponentExtender implements ChargedProjectilesComponentAccess { - @Unique - private static final float DEFAULT_CHARGED_SPEED = CrossbowItemAccessor.defaultSpeed(); - @Shadow @Final private List projectiles; @@ -33,16 +26,4 @@ public class ChargedProjectilesComponentExtender implements ChargedProjectilesCo } return false; } - - @Override - public float itematic$getChargedSpeed() { - float actualChargedSpeed = DEFAULT_CHARGED_SPEED; - for (ItemStack projectile : this.projectiles) { - float chargedSpeed = projectile.itematic$getComponent(ItemComponentTypes.PROJECTILE) - .map(ProjectileItemComponent::chargedSpeed) - .orElse(DEFAULT_CHARGED_SPEED); - actualChargedSpeed = Math.min(actualChargedSpeed, chargedSpeed); - } - return actualChargedSpeed; - } } diff --git a/src/main/java/net/errorcraft/itematic/mixin/enchantment/EnchantmentHelperExtender.java b/src/main/java/net/errorcraft/itematic/mixin/enchantment/EnchantmentHelperExtender.java index 258f2dee..4a0075d9 100644 --- a/src/main/java/net/errorcraft/itematic/mixin/enchantment/EnchantmentHelperExtender.java +++ b/src/main/java/net/errorcraft/itematic/mixin/enchantment/EnchantmentHelperExtender.java @@ -22,17 +22,14 @@ @Mixin(EnchantmentHelper.class) public class EnchantmentHelperExtender { @Redirect( - method = "getPossibleEntries", + method = "getEnchantmentsComponentType", at = @At( value = "INVOKE", - target = "Lnet/minecraft/registry/Registry;iterator()Ljava/util/Iterator;" + target = "Lnet/minecraft/item/ItemStack;isOf(Lnet/minecraft/item/Item;)Z" ) ) - private static Iterator getPossibleEntriesUseEnchantmentTag(Registry instance, @Local(argsOnly = true) int power, @Local(argsOnly = true) ItemStack stack) { - return stack.itematic$getComponent(ItemComponentTypes.ENCHANTABLE) - .flatMap(EnchantableItemComponent::enchantments) - .map(key -> instance.getOrCreateEntryList(key).stream().map(RegistryEntry::value).iterator()) - .orElse(instance.iterator()); + private static boolean isOfUseItemComponentCheck(ItemStack instance, Item item) { + return instance.itematic$hasComponent(ItemComponentTypes.ENCHANTMENT_HOLDER); } @Redirect( @@ -59,4 +56,31 @@ private static boolean isOfForBookUseItemComponent(ItemStack instance, Item item private static ItemStack newItemStackForEnchantedBookUseItemComponent(ItemConvertible item, @Local(argsOnly = true) ItemStack target, @Share("transformsInto") LocalRef> transformsInto) { return target.itematic$copyWithItem(transformsInto.get()); } + + @Redirect( + method = "getPossibleEntries", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/item/ItemStack;isOf(Lnet/minecraft/item/Item;)Z" + ) + ) + private static boolean isOfUseItemComponent(ItemStack instance, Item item) { + return instance.itematic$getComponent(ItemComponentTypes.ENCHANTABLE) + .map(enchantable -> enchantable.enchantments().isEmpty()) + .orElse(false); + } + + @Redirect( + method = "getPossibleEntries", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/registry/Registry;iterator()Ljava/util/Iterator;" + ) + ) + private static Iterator getPossibleEntriesUseEnchantmentTag(Registry instance, @Local(argsOnly = true) int power, @Local(argsOnly = true) ItemStack stack) { + return stack.itematic$getComponent(ItemComponentTypes.ENCHANTABLE) + .flatMap(EnchantableItemComponent::enchantments) + .map(key -> instance.getOrCreateEntryList(key).stream().map(RegistryEntry::value).iterator()) + .orElse(instance.iterator()); + } } diff --git a/src/main/java/net/errorcraft/itematic/mixin/entity/CrossbowUserExtender.java b/src/main/java/net/errorcraft/itematic/mixin/entity/CrossbowUserExtender.java index 8a7aeae1..5e44c114 100644 --- a/src/main/java/net/errorcraft/itematic/mixin/entity/CrossbowUserExtender.java +++ b/src/main/java/net/errorcraft/itematic/mixin/entity/CrossbowUserExtender.java @@ -7,6 +7,7 @@ import net.errorcraft.itematic.item.ItemKeys; import net.errorcraft.itematic.item.component.ItemComponentTypes; import net.errorcraft.itematic.item.component.components.ShooterItemComponent; +import net.errorcraft.itematic.item.shooter.method.methods.ChargeableShooterMethod; import net.minecraft.entity.CrossbowUser; import net.minecraft.entity.LivingEntity; import net.minecraft.item.CrossbowItem; @@ -41,11 +42,8 @@ private Hand getHandPossiblyHoldingForCrossbowUseRegistryKey(LivingEntity entity ) private boolean instanceOfCrossbowItemUseItemComponent(Object reference, Class clazz, @Local ItemStack heldStack, @Share("shooterItemComponent") LocalRef shooterItemComponent) { Optional optionalComponent = heldStack.itematic$getComponent(ItemComponentTypes.SHOOTER); - if (optionalComponent.map(ShooterItemComponent::isChargeable).orElse(false)) { - shooterItemComponent.set(optionalComponent.get()); - return true; - } - return false; + optionalComponent.ifPresent(shooterItemComponent::set); + return optionalComponent.isPresent(); } @ModifyVariable( @@ -65,6 +63,8 @@ private Item castToCrossbowItemUseNull(Item instance) { ) ) private void shootAllUseItemComponent(CrossbowItem instance, World world, LivingEntity shooter, Hand hand, ItemStack stack, float speed, float divergence, LivingEntity livingEntity, @Share("shooterItemComponent") LocalRef shooterItemComponent) { - shooterItemComponent.get().shootAll(world, shooter, hand, stack, speed, divergence, livingEntity); + if (shooterItemComponent.get().method() instanceof ChargeableShooterMethod chargeableShooterMethod) { + chargeableShooterMethod.shoot(shooterItemComponent.get(), world, shooter, hand, stack, speed, divergence, livingEntity); + } } } diff --git a/src/main/java/net/errorcraft/itematic/mixin/entity/LivingEntityExtender.java b/src/main/java/net/errorcraft/itematic/mixin/entity/LivingEntityExtender.java index d5407ff9..80693ea9 100644 --- a/src/main/java/net/errorcraft/itematic/mixin/entity/LivingEntityExtender.java +++ b/src/main/java/net/errorcraft/itematic/mixin/entity/LivingEntityExtender.java @@ -12,6 +12,7 @@ import net.errorcraft.itematic.item.ItemKeys; import net.errorcraft.itematic.item.ItemStackConsumer; import net.errorcraft.itematic.item.component.ItemComponentTypes; +import net.errorcraft.itematic.item.component.components.ConsumableItemComponent; import net.errorcraft.itematic.item.component.components.LifeSavingItemComponent; import net.errorcraft.itematic.item.event.ItemEvents; import net.errorcraft.itematic.world.action.context.ActionContext; @@ -33,6 +34,7 @@ import net.minecraft.registry.entry.RegistryEntry; import net.minecraft.registry.tag.ItemTags; import net.minecraft.server.world.ServerWorld; +import net.minecraft.sound.SoundEvent; import net.minecraft.stat.Stat; import net.minecraft.stat.StatType; import net.minecraft.util.Hand; @@ -42,10 +44,7 @@ import org.spongepowered.asm.mixin.Overwrite; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Unique; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.Redirect; -import org.spongepowered.asm.mixin.injection.Slice; +import org.spongepowered.asm.mixin.injection.*; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @@ -85,6 +84,9 @@ public abstract class LivingEntityExtender extends Entity implements LivingEntit @Shadow public abstract double getAttributeBaseValue(RegistryEntry attribute); + @Shadow + protected abstract void spawnItemParticles(ItemStack stack, int count); + @Unique private int itemUsedTicks; @@ -354,6 +356,60 @@ private void checkMaxUseTime(CallbackInfoReturnable info) { } } + @Redirect( + method = "shouldSpawnConsumptionEffects", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/item/ItemStack;getMaxUseTime()I" + ) + ) + private int getMaxUseTimeUseCustomImplementation(ItemStack instance) { + return instance.itematic$useDuration((LivingEntity)(Object) this); + } + + @Inject( + method = "spawnConsumptionEffects", + at = @At("HEAD"), + cancellable = true + ) + private void alwaysSpawnItemParticlesAndStoreConsumableSound(ItemStack stack, int particleCount, CallbackInfo info, @Share("consumeSound") LocalRef> consumeSound) { + this.spawnItemParticles(stack, particleCount); + this.activeItemStack.itematic$getComponent(ItemComponentTypes.CONSUMABLE) + .map(ConsumableItemComponent::sound) + .ifPresentOrElse(consumeSound::set, info::cancel); + } + + @Redirect( + method = "spawnConsumptionEffects", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/entity/LivingEntity;spawnItemParticles(Lnet/minecraft/item/ItemStack;I)V" + ) + ) + private void doNotSpawnItemParticlesNormally(LivingEntity instance, ItemStack stack, int count) {} + + @ModifyArg( + method = "spawnConsumptionEffects", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/entity/LivingEntity;playSound(Lnet/minecraft/sound/SoundEvent;FF)V" + ) + ) + private SoundEvent playSoundUseItemComponent(SoundEvent sound, @Share("consumeSound") LocalRef> consumeSound) { + return consumeSound.get().value(); + } + + @Inject( + method = "spawnItemParticles", + at = @At("HEAD"), + cancellable = true + ) + private void shouldSpawnParticles(ItemStack stack, int count, CallbackInfo info) { + if (!this.activeItemStack.itematic$getComponent(ItemComponentTypes.CONSUMABLE).map(ConsumableItemComponent::hasConsumeParticles).orElse(false)) { + info.cancel(); + } + } + @Redirect( method = "tickFallFlying", at = @At( diff --git a/src/main/java/net/errorcraft/itematic/mixin/item/CrossbowItemAccessor.java b/src/main/java/net/errorcraft/itematic/mixin/item/CrossbowItemAccessor.java index a97b8793..b0ff0e22 100644 --- a/src/main/java/net/errorcraft/itematic/mixin/item/CrossbowItemAccessor.java +++ b/src/main/java/net/errorcraft/itematic/mixin/item/CrossbowItemAccessor.java @@ -1,48 +1,33 @@ package net.errorcraft.itematic.mixin.item; -import net.minecraft.entity.LivingEntity; import net.minecraft.item.CrossbowItem; -import net.minecraft.item.ItemStack; -import org.jetbrains.annotations.Contract; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; -import org.spongepowered.asm.mixin.gen.Invoker; @Mixin(CrossbowItem.class) public interface CrossbowItemAccessor { @Accessor("DEFAULT_PULL_TIME") - @Contract - static int defaultPullTime() { + static int defaultChargeTime() { throw new AssertionError(); } @Accessor("CHARGE_PROGRESS") - @Contract - static float chargeProgress() { + static float startSoundProgress() { throw new AssertionError(); } @Accessor("LOAD_PROGRESS") - @Contract - static float loadProgress() { + static float midSoundProgress() { throw new AssertionError(); } @Accessor("DEFAULT_SPEED") - @Contract - static float defaultSpeed() { + static float defaultPower() { throw new AssertionError(); } @Accessor("FIREWORK_ROCKET_SPEED") - @Contract - static float fireworkRocketSpeed() { - throw new AssertionError(); - } - - @Invoker("loadProjectiles") - @Contract - static boolean loadProjectiles(LivingEntity shooter, ItemStack crossbow) { + static float fireworkRocketPower() { throw new AssertionError(); } } diff --git a/src/main/java/net/errorcraft/itematic/mixin/item/CrossbowItemExtender.java b/src/main/java/net/errorcraft/itematic/mixin/item/CrossbowItemExtender.java index 8122646b..c400fd79 100644 --- a/src/main/java/net/errorcraft/itematic/mixin/item/CrossbowItemExtender.java +++ b/src/main/java/net/errorcraft/itematic/mixin/item/CrossbowItemExtender.java @@ -1,32 +1,28 @@ package net.errorcraft.itematic.mixin.item; -import com.llamalad7.mixinextras.sugar.Local; -import net.errorcraft.itematic.entity.initializer.EntityInitializer; +import com.llamalad7.mixinextras.sugar.Share; +import com.llamalad7.mixinextras.sugar.ref.LocalFloatRef; +import net.errorcraft.itematic.component.ItematicDataComponentTypes; import net.errorcraft.itematic.item.component.ItemComponentTypes; -import net.errorcraft.itematic.item.component.components.ProjectileItemComponent; -import net.minecraft.entity.EntityType; +import net.minecraft.SharedConstants; import net.minecraft.entity.LivingEntity; import net.minecraft.item.CrossbowItem; -import net.minecraft.item.Item; import net.minecraft.item.ItemStack; -import net.minecraft.stat.Stat; -import net.minecraft.stat.StatType; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Overwrite; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.*; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(CrossbowItem.class) public abstract class CrossbowItemExtender { - @Redirect( - method = "shootAll", - at = @At( - value = "INVOKE", - target = "Lnet/minecraft/stat/StatType;getOrCreateStat(Ljava/lang/Object;)Lnet/minecraft/stat/Stat;" - ) + @Inject( + method = "loadProjectiles", + at = @At("HEAD"), + cancellable = true ) - private Stat getOrCreateStatUseRegistryEntry(StatType instance, T key, @Local(argsOnly = true) ItemStack stack) { - return instance.itematic$getOrCreateStat(stack.getRegistryEntry()); + private static void getAmmunitionUseItemComponent(LivingEntity shooter, ItemStack crossbow, CallbackInfoReturnable info) { + if (!crossbow.itematic$hasComponent(ItemComponentTypes.SHOOTER)) { + info.setReturnValue(false); + } } @Redirect( @@ -36,36 +32,36 @@ private Stat getOrCreateStatUseRegistryEntry(StatType instance, target = "Lnet/minecraft/entity/LivingEntity;getProjectileType(Lnet/minecraft/item/ItemStack;)Lnet/minecraft/item/ItemStack;" ) ) - private static ItemStack getProjectileTypeUseItemComponent(LivingEntity instance, ItemStack stack) { + private static ItemStack getAmmunitionUseItemComponent(LivingEntity instance, ItemStack stack) { if (stack.itematic$hasComponent(ItemComponentTypes.SHOOTER)) { - return instance.itematic$getAmmunition(stack); + instance.itematic$getAmmunition(stack); } + return ItemStack.EMPTY; } - @Redirect( - method = "createArrowEntity", - at = @At( - value = "INVOKE", - target = "Lnet/minecraft/item/ItemStack;isOf(Lnet/minecraft/item/Item;)Z" - ) + @Inject( + method = "getPullTime", + at = @At("HEAD"), + cancellable = true ) - private boolean isOfForFireworkRocketUseItemComponent(ItemStack instance, Item item) { - return instance.itematic$getComponent(ItemComponentTypes.PROJECTILE) - .map(ProjectileItemComponent::entity) - .map(EntityInitializer::type) - .map(e -> e == EntityType.FIREWORK_ROCKET) - .orElse(false); + private static void checkAndStoreDefaultChargeTime(ItemStack stack, CallbackInfoReturnable info, @Share("defaultChargeTime") LocalFloatRef defaultChargeTime) { + Float possibleDefaultChargeTime = stack.get(ItematicDataComponentTypes.SHOOTER_DEFAULT_CHARGE_TIME); + if (possibleDefaultChargeTime == null) { + info.setReturnValue(0); + return; + } + + defaultChargeTime.set(possibleDefaultChargeTime); } - /** - * @author ErrorCraft - * @reason Uses the ItemComponent implementation for data-driven items. - */ - @Overwrite - public int getWeaponStackDamage(ItemStack projectile) { - return projectile.itematic$getComponent(ItemComponentTypes.PROJECTILE) - .map(ProjectileItemComponent::damage) - .orElse(0); + @ModifyConstant( + method = "getPullTime", + constant = @Constant( + intValue = 25 + ) + ) + private static int defaultChargeTimeUseDataComponent(int constant, @Share("defaultChargeTime") LocalFloatRef defaultChargeTime) { + return (int) (defaultChargeTime.get() * SharedConstants.TICKS_PER_SECOND); } } diff --git a/src/main/java/net/errorcraft/itematic/mixin/item/ItemExtender.java b/src/main/java/net/errorcraft/itematic/mixin/item/ItemExtender.java index 37190d22..7488a912 100644 --- a/src/main/java/net/errorcraft/itematic/mixin/item/ItemExtender.java +++ b/src/main/java/net/errorcraft/itematic/mixin/item/ItemExtender.java @@ -345,6 +345,17 @@ public void appendTooltip(ItemStack stack, Item.TooltipContext context, List type) { + public boolean containsMaxDamageUseItemComponentCheck(ItemStack instance, DataComponentType type) { return instance.itematic$hasComponent(ItemComponentTypes.ENCHANTABLE); } diff --git a/src/main/java/net/errorcraft/itematic/mixin/item/ItemStackExtender.java b/src/main/java/net/errorcraft/itematic/mixin/item/ItemStackExtender.java index aa4b3104..fa8bc409 100644 --- a/src/main/java/net/errorcraft/itematic/mixin/item/ItemStackExtender.java +++ b/src/main/java/net/errorcraft/itematic/mixin/item/ItemStackExtender.java @@ -15,11 +15,15 @@ import net.errorcraft.itematic.item.component.ItemComponent; import net.errorcraft.itematic.item.component.ItemComponentType; import net.errorcraft.itematic.item.component.ItemComponentTypes; +import net.errorcraft.itematic.item.component.components.ShooterItemComponent; import net.errorcraft.itematic.item.event.ItemEvent; import net.errorcraft.itematic.item.event.ItemEvents; +import net.errorcraft.itematic.item.shooter.method.ShooterMethodTypes; import net.errorcraft.itematic.util.Util; import net.errorcraft.itematic.world.action.context.ActionContext; import net.errorcraft.itematic.world.action.context.parameter.ActionContextParameter; +import net.fabricmc.fabric.api.item.v1.EnchantingContext; +import net.fabricmc.fabric.api.item.v1.FabricItemStack; import net.minecraft.advancement.criterion.ItemDurabilityChangedCriterion; import net.minecraft.block.BlockState; import net.minecraft.client.item.TooltipType; @@ -27,6 +31,7 @@ import net.minecraft.component.ComponentHolder; import net.minecraft.component.ComponentMap; import net.minecraft.component.ComponentMapImpl; +import net.minecraft.enchantment.Enchantment; import net.minecraft.entity.Entity; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.damage.DamageSource; @@ -74,7 +79,7 @@ import java.util.stream.Stream; @Mixin(ItemStack.class) -public abstract class ItemStackExtender implements ComponentHolder, ItemStackAccess { +public abstract class ItemStackExtender implements ComponentHolder, ItemStackAccess, FabricItemStack { @Shadow @Final private static Logger LOGGER; @@ -159,7 +164,11 @@ private void componentChangesConstructorSetFields(RegistryEntry item, int } @Redirect( - method = { "(Lnet/minecraft/registry/entry/RegistryEntry;)V", "(Lnet/minecraft/registry/entry/RegistryEntry;I)V", "(Lnet/minecraft/registry/entry/RegistryEntry;ILnet/minecraft/component/ComponentChanges;)V" }, + method = { + "(Lnet/minecraft/registry/entry/RegistryEntry;)V", + "(Lnet/minecraft/registry/entry/RegistryEntry;I)V", + "(Lnet/minecraft/registry/entry/RegistryEntry;ILnet/minecraft/component/ComponentChanges;)V" + }, at = @At( value = "INVOKE", target = "Lnet/minecraft/registry/entry/RegistryEntry;value()Ljava/lang/Object;" @@ -170,7 +179,7 @@ private static T registryEntryValueReturnNullToPreventUnboundRegistryEntryIs } @Redirect( - method = { "(Lnet/minecraft/item/ItemConvertible;I)V" }, + method = "(Lnet/minecraft/item/ItemConvertible;I)V", at = @At( value = "INVOKE", target = "Lnet/minecraft/item/ItemConvertible;asItem()Lnet/minecraft/item/Item;" @@ -192,7 +201,10 @@ private Item asItemReturnNullToPreventNullPointerException(ItemConvertible insta } @Redirect( - method = { "(Lnet/minecraft/item/ItemConvertible;I)V", "(Lnet/minecraft/registry/entry/RegistryEntry;ILnet/minecraft/component/ComponentChanges;)V" }, + method = { + "(Lnet/minecraft/item/ItemConvertible;I)V", + "(Lnet/minecraft/registry/entry/RegistryEntry;ILnet/minecraft/component/ComponentChanges;)V" + }, at = @At( value = "INVOKE", target = "Lnet/minecraft/item/Item;getComponents()Lnet/minecraft/component/ComponentMap;" @@ -426,7 +438,10 @@ public void isEnchantableCheckNullEntry(CallbackInfoReturnable info) { } @Inject( - method = { "areItemsEqual", "areItemsAndComponentsEqual" }, + method = { + "areItemsEqual", + "areItemsAndComponentsEqual" + }, at = @At("HEAD"), cancellable = true ) @@ -437,7 +452,10 @@ private static void checkEmptyStacksPrematurely(ItemStack stack, ItemStack other } @Redirect( - method = { "areItemsEqual", "areItemsAndComponentsEqual" }, + method = { + "areItemsEqual", + "areItemsAndComponentsEqual" + }, at = @At( value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;isOf(Lnet/minecraft/item/Item;)Z" @@ -447,6 +465,18 @@ private static boolean isOfUseRegistryEntryCheck(ItemStack instance, Item item, return instance.itemMatches(right.getRegistryEntry()); } + @Inject( + method = "isUsedOnRelease", + at = @At("HEAD"), + cancellable = true + ) + private void checkForChargeableShooter(CallbackInfoReturnable info) { + this.itematic$getComponent(ItemComponentTypes.SHOOTER) + .map(ShooterItemComponent::method) + .filter(method -> method.type() == ShooterMethodTypes.CHARGEABLE) + .ifPresent(method -> info.setReturnValue(true)); + } + @Inject( method = "hasGlint", at = @At("HEAD"), @@ -595,7 +625,13 @@ private void invokeBreakToolEvent(int amount, Random random, ServerPlayerEntity } @Redirect( - method = { "useOnBlock", "method_56097", "postHit", "postMine", "onCraftByPlayer" }, + method = { + "useOnBlock", + "method_56097", + "postHit", + "postMine", + "onCraftByPlayer" + }, at = @At( value = "INVOKE", target = "Lnet/minecraft/stat/StatType;getOrCreateStat(Ljava/lang/Object;)Lnet/minecraft/stat/Stat;" @@ -614,11 +650,18 @@ public String toString() { return this.count + " " + this.itematic$key().getValue().toString(); } + @Override + public boolean canBeEnchantedWith(Enchantment enchantment, EnchantingContext context) { + // Use the original implementation again + return enchantment.isAcceptableItem((ItemStack)(Object) this); + } + @Override public RegistryKey itematic$key() { if (this.entry == null) { return ItemKeys.AIR; } + return this.entry.getKey().orElse(ItemKeys.AIR); } @@ -632,6 +675,7 @@ public String toString() { if (this.isEmpty()) { return; } + this.count = MathHelper.clamp(this.count + count, 0, this.getMaxCount()); } @@ -652,6 +696,7 @@ public String toString() { if (this.isEmpty()) { return EMPTY; } + return this.itematic$copyComponentsToNewStackIgnoreEmpty(item, count); } @@ -670,6 +715,7 @@ public String toString() { if (context.player(ActionContextParameter.THIS).map(PlayerEntity::isCreative).orElse(false)) { return; } + this.context = context; Entity entity = context.entity(ActionContextParameter.THIS).orElse(null); this.damage(amount, context.world().getRandom(), entity instanceof ServerPlayerEntity player ? player : null, () -> this.onItemBroken(entity, context)); @@ -686,6 +732,7 @@ public String toString() { if (this.entry == null) { return Optional.empty(); } + return this.entry.value().itematic$getComponent(type); } @@ -694,9 +741,11 @@ public String toString() { if (this.entry == null) { return false; } + if (this.activeEvents.contains(event)) { return false; } + this.activeEvents.add(event); boolean result = this.entry.value().itematic$invokeEvent(event, context); this.activeEvents.remove(event); @@ -708,9 +757,11 @@ public String toString() { if (this.entry == null) { return true; } + if (miner.isCreative() && this.isIn(ItematicItemTags.PREVENTS_MINING_IN_CREATIVE)) { return false; } + return this.entry.value().canMine(state, world, pos, miner); } @@ -719,6 +770,7 @@ public String toString() { if (this.entry == null) { return false; } + return this.entry.value().isNetworkSynced(); } @@ -727,6 +779,7 @@ public String toString() { if (this.entry == null) { return false; } + return this.entry.value().itematic$mayStartUsing(world, user, hand, stack); } @@ -735,10 +788,12 @@ public String toString() { if (!this.itematic$hasComponent(ItemComponentTypes.USEABLE)) { return 0; } + UseDurationDataComponent useDuration = this.get(ItematicDataComponentTypes.USE_DURATION); if (useDuration == null) { return 0; } + return useDuration.ticks((ItemStack)(Object) this, user); } @@ -747,6 +802,7 @@ public String toString() { if (!this.itematic$hasComponent(ItemComponentTypes.WEAPON)) { return 1.0d; } + return this.getOrDefault(ItematicDataComponentTypes.ATTACK_SPEED_MULTIPLIER, 1.0d); } @@ -766,11 +822,13 @@ private void onItemBroken(Entity entity, ActionContext context) { if (entity instanceof LivingEntity livingEntity) { context.slot().ifPresent(livingEntity::sendEquipmentBreakStatus); } + this.decrement(1); this.itematic$invokeEvent(ItemEvents.BREAK_ITEM, context); if (entity instanceof PlayerEntity player && this.entry != null) { player.incrementStat(Stats.BROKEN.itematic$getOrCreateStat(this.entry)); } + this.setDamage(0); } } diff --git a/src/main/java/net/errorcraft/itematic/mixin/item/RangedWeaponItemAccessor.java b/src/main/java/net/errorcraft/itematic/mixin/item/RangedWeaponItemAccessor.java new file mode 100644 index 00000000..e4bbf306 --- /dev/null +++ b/src/main/java/net/errorcraft/itematic/mixin/item/RangedWeaponItemAccessor.java @@ -0,0 +1,22 @@ +package net.errorcraft.itematic.mixin.item; + +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.projectile.ProjectileEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.item.RangedWeaponItem; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +import java.util.List; + +@Mixin(RangedWeaponItem.class) +public interface RangedWeaponItemAccessor { + @Invoker("load") + static List load(ItemStack stack, ItemStack projectile, LivingEntity shooter) { + throw new AssertionError(); + } + + @Invoker("shoot") + void shoot(LivingEntity user, ProjectileEntity projectile, int index, float power, float uncertainty, float angle, @Nullable LivingEntity target); +} diff --git a/src/main/java/net/errorcraft/itematic/mixin/item/RangedWeaponItemExtender.java b/src/main/java/net/errorcraft/itematic/mixin/item/RangedWeaponItemExtender.java index 324ac6ac..0c67127f 100644 --- a/src/main/java/net/errorcraft/itematic/mixin/item/RangedWeaponItemExtender.java +++ b/src/main/java/net/errorcraft/itematic/mixin/item/RangedWeaponItemExtender.java @@ -1,60 +1,23 @@ package net.errorcraft.itematic.mixin.item; -import com.llamalad7.mixinextras.sugar.Local; -import net.errorcraft.itematic.item.component.ItemComponentTypes; -import net.errorcraft.itematic.item.component.components.ProjectileItemComponent; -import net.minecraft.entity.Entity; -import net.minecraft.entity.LivingEntity; -import net.minecraft.entity.projectile.PersistentProjectileEntity; -import net.minecraft.entity.projectile.ProjectileEntity; -import net.minecraft.item.ArrowItem; +import net.errorcraft.itematic.item.ItemKeys; +import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.item.RangedWeaponItem; -import net.minecraft.world.World; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Redirect; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -import java.util.Optional; @Mixin(RangedWeaponItem.class) public class RangedWeaponItemExtender { @Redirect( - method = "createArrowEntity", + method = "isInfinity", at = @At( value = "INVOKE", - target = "Lnet/minecraft/item/ArrowItem;createArrow(Lnet/minecraft/world/World;Lnet/minecraft/item/ItemStack;Lnet/minecraft/entity/LivingEntity;)Lnet/minecraft/entity/projectile/PersistentProjectileEntity;" + target = "Lnet/minecraft/item/ItemStack;isOf(Lnet/minecraft/item/Item;)Z" ) ) - private PersistentProjectileEntity createArrowUseItemComponent(ArrowItem instance, World world, ItemStack projectile, LivingEntity shooter) { - Optional component = projectile.itematic$getComponent(ItemComponentTypes.PROJECTILE); - if (component.isEmpty()) { - return null; - } - - Entity entity = component.get().createEntity(world, shooter, projectile, 0.0f, 1.0f); - if (entity instanceof PersistentProjectileEntity persistentProjectileEntity) { - return persistentProjectileEntity; - } - - world.spawnEntity(entity); - return null; - } - - @Inject( - method = "createArrowEntity", - at = @At( - value = "INVOKE_ASSIGN", - target = "Lnet/minecraft/item/ArrowItem;createArrow(Lnet/minecraft/world/World;Lnet/minecraft/item/ItemStack;Lnet/minecraft/entity/LivingEntity;)Lnet/minecraft/entity/projectile/PersistentProjectileEntity;", - shift = At.Shift.AFTER - ), - cancellable = true - ) - private void createArrowCheckNullEntity(World world, LivingEntity shooter, ItemStack weaponStack, ItemStack projectileStack, boolean critical, CallbackInfoReturnable info, @Local PersistentProjectileEntity persistentProjectileEntity) { - if (persistentProjectileEntity == null) { - info.setReturnValue(null); - } + private static boolean isOfForArrowUseRegistryKeyCheck(ItemStack instance, Item item) { + return instance.itematic$isOf(ItemKeys.ARROW); } } diff --git a/src/main/java/net/errorcraft/itematic/mixin/screen/EnchantmentScreenHandlerExtender.java b/src/main/java/net/errorcraft/itematic/mixin/screen/EnchantmentScreenHandlerExtender.java index 7fc82cae..ceb0109e 100644 --- a/src/main/java/net/errorcraft/itematic/mixin/screen/EnchantmentScreenHandlerExtender.java +++ b/src/main/java/net/errorcraft/itematic/mixin/screen/EnchantmentScreenHandlerExtender.java @@ -26,10 +26,10 @@ public class EnchantmentScreenHandlerExtender { ) ) private boolean isOfForBookUseItemComponent(ItemStack instance, Item item, @Share("transformsInto") LocalRef> transformsInto) { - Optional> optionalEntry = instance.itematic$getComponent(ItemComponentTypes.ENCHANTABLE) + Optional> optionalItem = instance.itematic$getComponent(ItemComponentTypes.ENCHANTABLE) .flatMap(EnchantableItemComponent::transformsInto); - optionalEntry.ifPresent(transformsInto::set); - return optionalEntry.isPresent(); + optionalItem.ifPresent(transformsInto::set); + return optionalItem.isPresent(); } @Redirect( diff --git a/src/main/java/net/errorcraft/itematic/registry/ItematicRegistries.java b/src/main/java/net/errorcraft/itematic/registry/ItematicRegistries.java index 15390905..9408b77f 100644 --- a/src/main/java/net/errorcraft/itematic/registry/ItematicRegistries.java +++ b/src/main/java/net/errorcraft/itematic/registry/ItematicRegistries.java @@ -14,6 +14,8 @@ import net.errorcraft.itematic.item.placement.block.picker.BlockPickerTypes; import net.errorcraft.itematic.item.pointer.Pointer; import net.errorcraft.itematic.item.pointer.Pointers; +import net.errorcraft.itematic.item.shooter.method.ShooterMethodType; +import net.errorcraft.itematic.item.shooter.method.ShooterMethodTypes; import net.errorcraft.itematic.item.smithing.template.SmithingTemplateType; import net.errorcraft.itematic.item.smithing.template.SmithingTemplateTypes; import net.errorcraft.itematic.item.use.provider.IntegerProviderType; @@ -40,6 +42,7 @@ public class ItematicRegistries { public static final Registry> TRADE_MODIFIER_TYPE = RegistriesAccessor.create(ItematicRegistryKeys.TRADE_MODIFIER_TYPE, r -> TradeModifierTypes.ENCHANT_WITH_LEVELS); public static final Registry> INTEGER_PROVIDER_TYPE = RegistriesAccessor.create(ItematicRegistryKeys.INTEGER_PROVIDER_TYPE, r -> IntegerProviderTypes.CONSTANT); public static final Registry> ITEM_HOLDER_RULE_TYPE = RegistriesAccessor.create(ItematicRegistryKeys.ITEM_HOLDER_RULE_TYPE, r -> ItemHolderRuleTypes.REJECT); + public static final Registry> SHOOTER_METHOD_TYPE = RegistriesAccessor.create(ItematicRegistryKeys.SHOOTER_METHOD_TYPE, r -> ShooterMethodTypes.DIRECT); private ItematicRegistries() {} } diff --git a/src/main/java/net/errorcraft/itematic/registry/ItematicRegistryKeys.java b/src/main/java/net/errorcraft/itematic/registry/ItematicRegistryKeys.java index 8e8cf228..2e0d4ee8 100644 --- a/src/main/java/net/errorcraft/itematic/registry/ItematicRegistryKeys.java +++ b/src/main/java/net/errorcraft/itematic/registry/ItematicRegistryKeys.java @@ -10,6 +10,7 @@ import net.errorcraft.itematic.item.model.override.ModelOverride; import net.errorcraft.itematic.item.placement.block.picker.BlockPickerType; import net.errorcraft.itematic.item.pointer.Pointer; +import net.errorcraft.itematic.item.shooter.method.ShooterMethodType; import net.errorcraft.itematic.item.smithing.template.SmithingTemplate; import net.errorcraft.itematic.item.smithing.template.SmithingTemplateType; import net.errorcraft.itematic.item.use.provider.IntegerProviderType; @@ -41,6 +42,7 @@ public class ItematicRegistryKeys { public static final RegistryKey>> TRADE_MODIFIER_TYPE = RegistryKey.ofRegistry(new Identifier("trade_modifier_type")); public static final RegistryKey>> INTEGER_PROVIDER_TYPE = RegistryKey.ofRegistry(new Identifier("integer_provider_type")); public static final RegistryKey>> ITEM_HOLDER_RULE_TYPE = RegistryKey.ofRegistry(new Identifier("item_holder_rule_type")); + public static final RegistryKey>> SHOOTER_METHOD_TYPE = RegistryKey.ofRegistry(new Identifier("shooter_method_type")); private ItematicRegistryKeys() {} } diff --git a/src/main/java/net/errorcraft/itematic/sound/SoundEventKeys.java b/src/main/java/net/errorcraft/itematic/sound/SoundEventKeys.java index bb117d8b..e39c781b 100644 --- a/src/main/java/net/errorcraft/itematic/sound/SoundEventKeys.java +++ b/src/main/java/net/errorcraft/itematic/sound/SoundEventKeys.java @@ -36,8 +36,10 @@ public class SoundEventKeys { public static final RegistryKey FIRE_CHARGE_USE = of("item.firecharge.use"); public static final RegistryKey FIRE_EXTINGUISH = of("block.fire.extinguish"); public static final RegistryKey FLINT_AND_STEEL_USE = of("item.flintandsteel.use"); + public static final RegistryKey GENERIC_DRINK = of("entity.generic.drink"); public static final RegistryKey GENERIC_SPLASH = of("entity.generic.splash"); public static final RegistryKey HOE_TILL = of("item.hoe.till"); + public static final RegistryKey HONEY_BOTTLE_DRINK = of("item.honey_bottle.drink"); public static final RegistryKey HORSE_SADDLE = of("entity.horse.saddle"); public static final RegistryKey MUSIC_DISC_5 = of("music_disc.5"); public static final RegistryKey MUSIC_DISC_11 = of("music_disc.11"); diff --git a/src/main/java/net/errorcraft/itematic/world/action/actions/PlaySoundAction.java b/src/main/java/net/errorcraft/itematic/world/action/actions/PlaySoundAction.java index 3d0295b8..681a40bc 100644 --- a/src/main/java/net/errorcraft/itematic/world/action/actions/PlaySoundAction.java +++ b/src/main/java/net/errorcraft/itematic/world/action/actions/PlaySoundAction.java @@ -9,6 +9,7 @@ import net.errorcraft.itematic.world.action.ActionTypes; import net.errorcraft.itematic.world.action.context.ActionContext; import net.errorcraft.itematic.world.action.context.parameter.ActionContextParameter; +import net.minecraft.entity.Entity; import net.minecraft.registry.entry.RegistryEntry; import net.minecraft.server.world.ServerWorld; import net.minecraft.sound.SoundCategory; @@ -17,11 +18,13 @@ import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.random.Random; -public record PlaySoundAction(ActionContextParameter position, RegistryEntry sound, SoundCategory category, Range.FloatRange volume, Range.FloatRange pitch, boolean fromEntity) implements Action { +import java.util.Optional; + +public record PlaySoundAction(ActionContextParameter position, RegistryEntry sound, Optional category, Range.FloatRange volume, Range.FloatRange pitch, boolean fromEntity) implements Action { public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group( ActionContextParameter.CODEC.fieldOf("position").forGetter(PlaySoundAction::position), SoundEvent.ENTRY_CODEC.fieldOf("sound").forGetter(PlaySoundAction::sound), - StringIdentifiable.createCodec(SoundCategory::values).fieldOf("category").forGetter(PlaySoundAction::category), + StringIdentifiable.createCodec(SoundCategory::values).optionalFieldOf("category").forGetter(PlaySoundAction::category), Range.FLOAT_CODEC.fieldOf("volume").forGetter(PlaySoundAction::volume), Range.FLOAT_CODEC.fieldOf("pitch").forGetter(PlaySoundAction::pitch), Codec.BOOL.optionalFieldOf("from_entity", false).forGetter(PlaySoundAction::fromEntity) @@ -32,11 +35,11 @@ public static Builder builder(ActionContextParameter position, RegistryEntry sound, SoundCategory category) { - return new PlaySoundAction(position, sound, category, Range.FloatRange.of(1.0f), Range.FloatRange.of(1.0f), false); + return new PlaySoundAction(position, sound, Optional.of(category), Range.FloatRange.of(1.0f), Range.FloatRange.of(1.0f), false); } public static PlaySoundAction of(ActionContextParameter position, RegistryEntry sound, SoundCategory category, float volume, float pitch) { - return new PlaySoundAction(position, sound, category, Range.FloatRange.of(volume), Range.FloatRange.of(pitch), false); + return new PlaySoundAction(position, sound, Optional.of(category), Range.FloatRange.of(volume), Range.FloatRange.of(pitch), false); } @Override @@ -46,30 +49,44 @@ public ActionType type() { @Override public boolean execute(ActionContext context) { + SoundCategory category = this.category(context.entity(ActionContextParameter.THIS).orElse(null)); + if (category == null) { + return false; + } + ServerWorld world = context.world(); Random random = world.getRandom(); float volume = this.volume.get(random); float pitch = this.pitch.get(random); - long seed = world.getRandom().nextLong(); - context.player(ActionContextParameter.THIS) - .filter(player -> this.fromEntity) + long seed = random.nextLong(); + context.entity(ActionContextParameter.THIS) + .filter(entity -> this.fromEntity) .ifPresentOrElse( - player -> world.playSoundFromEntity(null, player, this.sound, this.category, volume, pitch, seed), + entity -> world.playSoundFromEntity(null, entity, this.sound, category, volume, pitch, seed), () -> { Vec3d pos = context.position(this.position); - world.playSound(null, pos.getX(), pos.getY(), pos.getZ(), this.sound, this.category, volume, pitch, seed); + world.playSound(null, pos.getX(), pos.getY(), pos.getZ(), this.sound, category, volume, pitch, seed); } ); return true; } + private SoundCategory category(Entity entity) { + return this.category.orElseGet(() -> { + if (entity == null) { + return null; + } + + return entity.getSoundCategory(); + }); + } + public static class Builder { private final ActionContextParameter position; private final RegistryEntry sound; private final SoundCategory category; private Range.FloatRange volume = Range.FloatRange.of(1.0f); private Range.FloatRange pitch = Range.FloatRange.of(1.0f); - private boolean fromEntity = false; private Builder(ActionContextParameter position, RegistryEntry sound, SoundCategory category) { this.position = position; @@ -78,7 +95,7 @@ private Builder(ActionContextParameter position, RegistryEntry sound } public PlaySoundAction build() { - return new PlaySoundAction(this.position, this.sound, this.category, this.volume, this.pitch, this.fromEntity); + return new PlaySoundAction(this.position, this.sound, Optional.of(this.category), this.volume, this.pitch, false); } public Builder volume(float volume) { @@ -100,10 +117,5 @@ public Builder pitch(float min, float max) { this.pitch = Range.FloatRange.of(min, max); return this; } - - public Builder fromEntity() { - this.fromEntity = true; - return this; - } } } diff --git a/src/main/java/net/errorcraft/itematic/world/action/context/ActionContext.java b/src/main/java/net/errorcraft/itematic/world/action/context/ActionContext.java index 0658beae..f8305eca 100644 --- a/src/main/java/net/errorcraft/itematic/world/action/context/ActionContext.java +++ b/src/main/java/net/errorcraft/itematic/world/action/context/ActionContext.java @@ -84,7 +84,7 @@ public static Builder builder(ServerWorld world, ItemStack stack, ItemStackConsu if (hand == null) { return builder(world, stack, resultStackConsumer); } - return builder(world, stack, resultStackConsumer, hand == Hand.MAIN_HAND ? EquipmentSlot.MAINHAND : EquipmentSlot.OFFHAND); + return builder(world, stack, resultStackConsumer, LivingEntity.getSlotForHand(hand)); } public Builder builderForCopy() { @@ -275,5 +275,10 @@ public Builder slot(EquipmentSlot slot) { this.slot = slot; return this; } + + public Builder hand(Hand hand) { + this.slot = LivingEntity.getSlotForHand(hand); + return this; + } } } diff --git a/src/main/resources/itematic.mixins.json b/src/main/resources/itematic.mixins.json index 1e9df7d7..5cdd13fb 100644 --- a/src/main/resources/itematic.mixins.json +++ b/src/main/resources/itematic.mixins.json @@ -281,6 +281,7 @@ "item.KnowledgeBookItemExtender", "item.MilkBucketItemAccessor", "item.PotionItemAccessor", + "item.RangedWeaponItemAccessor", "item.RangedWeaponItemExtender", "item.SmithingTemplateItemAccessor", "item.SpawnEggItemAccessor", From aacfe25f373ba9a08a6d61fec216c0a456810503 Mon Sep 17 00:00:00 2001 From: ErrorCraft <51973682+ErrorCraft@users.noreply.github.com> Date: Tue, 20 Jan 2026 17:11:05 +0100 Subject: [PATCH 2/3] Increment version to 0.4.0-preview.1+1.20.6 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index cd14946c..fd474dea 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,7 +8,7 @@ org.gradle.jvmargs=-Xmx1G loader_version=0.17.3 # Mod Properties - mod_version = 0.3.0 + mod_version = 0.4.0-preview.1+1.20.6 maven_group = net.errorcraft archives_base_name = itematic From 2d93cb02252c6a30dff6027c1cfd4cfe9f65da03 Mon Sep 17 00:00:00 2001 From: ErrorCraft <51973682+ErrorCraft@users.noreply.github.com> Date: Sun, 25 Jan 2026 17:36:25 +0100 Subject: [PATCH 3/3] Increment version to 0.4.0+1.20.6 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index fd474dea..3d23577c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,7 +8,7 @@ org.gradle.jvmargs=-Xmx1G loader_version=0.17.3 # Mod Properties - mod_version = 0.4.0-preview.1+1.20.6 + mod_version = 0.4.0+1.20.6 maven_group = net.errorcraft archives_base_name = itematic