From f01ca0e4b8f7141212f711b763fa1a53e5708f4e Mon Sep 17 00:00:00 2001 From: Nikita Klyshko Date: Mon, 11 Sep 2023 12:54:11 +0300 Subject: [PATCH] Shield behaviour implementation --- .../material/info/VanillaMaterialTypes.kt | 2 + .../xenondevs/nova/item/behavior/Blocking.kt | 11 ++ .../item/vanilla/VanillaMaterialProperty.kt | 6 +- .../xyz/xenondevs/nova/transformer/Patcher.kt | 3 +- .../transformer/patch/item/BlockingPatches.kt | 134 ++++++++++++++++++ .../util/reflection/ReflectionRegistry.kt | 5 + 6 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Blocking.kt create mode 100644 nova/src/main/kotlin/xyz/xenondevs/nova/transformer/patch/item/BlockingPatches.kt diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/data/resources/builder/task/material/info/VanillaMaterialTypes.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/data/resources/builder/task/material/info/VanillaMaterialTypes.kt index 473e5fb76b..ebe7c5cd78 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/data/resources/builder/task/material/info/VanillaMaterialTypes.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/data/resources/builder/task/material/info/VanillaMaterialTypes.kt @@ -8,6 +8,8 @@ import xyz.xenondevs.nova.item.vanilla.VanillaMaterialProperty internal object VanillaMaterialTypes { private val MATERIAL_TYPES = enumMapOf( + Material.SHIELD to setOf(VanillaMaterialProperty.SHIELD), + Material.WOODEN_PICKAXE to setOf(VanillaMaterialProperty.DAMAGEABLE), Material.WOODEN_SWORD to setOf(VanillaMaterialProperty.DAMAGEABLE, VanillaMaterialProperty.CREATIVE_NON_BLOCK_BREAKING), diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Blocking.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Blocking.kt new file mode 100644 index 0000000000..3f821ee1e1 --- /dev/null +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Blocking.kt @@ -0,0 +1,11 @@ +package xyz.xenondevs.nova.item.behavior + +import xyz.xenondevs.nova.item.vanilla.VanillaMaterialProperty + +object Blocking : ItemBehavior { + + override fun getVanillaMaterialProperties(): List { + return listOf(VanillaMaterialProperty.SHIELD) + } + +} \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/item/vanilla/VanillaMaterialProperty.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/item/vanilla/VanillaMaterialProperty.kt index 18e5c2033d..d1f1408547 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/item/vanilla/VanillaMaterialProperty.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/item/vanilla/VanillaMaterialProperty.kt @@ -50,6 +50,10 @@ enum class VanillaMaterialProperty(internal val importance: Int) { /** * The item will not catch on fire. */ - FIRE_RESISTANT(0) + FIRE_RESISTANT(0), + /** + * The item that player can use to block damage. + */ + SHIELD(3) } \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/transformer/Patcher.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/transformer/Patcher.kt index d1de796ed0..1c1d3a2c42 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/transformer/Patcher.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/transformer/Patcher.kt @@ -22,6 +22,7 @@ import xyz.xenondevs.nova.transformer.patch.block.NoteBlockPatch import xyz.xenondevs.nova.transformer.patch.bossbar.BossBarOriginPatch import xyz.xenondevs.nova.transformer.patch.item.AnvilResultPatch import xyz.xenondevs.nova.transformer.patch.item.AttributePatch +import xyz.xenondevs.nova.transformer.patch.item.BlockingPatches import xyz.xenondevs.nova.transformer.patch.item.DamageablePatches import xyz.xenondevs.nova.transformer.patch.item.EnchantmentPatches import xyz.xenondevs.nova.transformer.patch.item.FireResistancePatches @@ -63,7 +64,7 @@ internal object Patcher { StackSizePatch, FeatureSorterPatch, LevelChunkSectionPatch, ChunkAccessSectionsPatch, RegistryCodecPatch, WrapperBlockPatch, MappedRegistryPatch, FuelPatches, RemainingItemPatches, FireResistancePatches, SoundPatches, BroadcastPacketPatch, CBFCompoundTagPatch, EventPreventionPatch, LegacyConversionPatch, WearablePatch, - NovaRuleTestPatch, BossBarOriginPatch, ThreadingDetectorPatch, FakePlayerLastHurtPatch + NovaRuleTestPatch, BossBarOriginPatch, ThreadingDetectorPatch, FakePlayerLastHurtPatch, BlockingPatches ).filter(Transformer::shouldTransform).toSet() } diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/transformer/patch/item/BlockingPatches.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/transformer/patch/item/BlockingPatches.kt new file mode 100644 index 0000000000..0ec17eab86 --- /dev/null +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/transformer/patch/item/BlockingPatches.kt @@ -0,0 +1,134 @@ +package xyz.xenondevs.nova.transformer.patch.item + +import net.minecraft.world.InteractionHand +import net.minecraft.world.InteractionResultHolder +import net.minecraft.world.entity.player.Player +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items +import net.minecraft.world.item.UseAnim +import net.minecraft.world.level.Level +import org.objectweb.asm.Opcodes +import org.objectweb.asm.tree.MethodInsnNode +import xyz.xenondevs.bytebase.asm.buildInsnList +import xyz.xenondevs.bytebase.jvm.VirtualClassPath +import xyz.xenondevs.bytebase.util.calls +import xyz.xenondevs.bytebase.util.replaceFirst +import xyz.xenondevs.nova.item.behavior.Blocking +import xyz.xenondevs.nova.transformer.MultiTransformer +import xyz.xenondevs.nova.util.item.novaItem +import xyz.xenondevs.nova.util.reflection.ReflectionRegistry + +@Suppress("unused") +internal object BlockingPatches : MultiTransformer(Item::class, Player::class) { + + override fun transform() { + patchUse() + patchUseDuration() + patchUseAnimation() + patchHurtCurrentlyUsedShield() + } + + private fun patchUse() { + VirtualClassPath[Item::use] + .instructions = buildInsnList { + aLoad(0) + aLoad(1) + aLoad(2) + aLoad(3) + invokeStatic(::use) + areturn() + } + } + + @JvmStatic + fun use(item: Item, world: Level, user: Player, hand: InteractionHand): InteractionResultHolder { + val itemStack: ItemStack = user.getItemInHand(hand) + + val novaItem = itemStack.novaItem + if (novaItem != null) { + if (novaItem.hasBehavior(Blocking::class)) { + user.startUsingItem(hand) + return InteractionResultHolder.consume(itemStack) + } + } + + // -- nms logic -- + return if (item.isEdible) { + if (user.canEat(item.foodProperties!!.canAlwaysEat())) { + user.startUsingItem(hand) + InteractionResultHolder.consume(itemStack) + } else { + InteractionResultHolder.fail(itemStack) + } + } else { + InteractionResultHolder.pass(user.getItemInHand(hand)) + } + // ---- + } + + private fun patchUseDuration() { + VirtualClassPath[Item::getUseDuration] + .instructions = buildInsnList { + aLoad(1) + aLoad(0) + invokeStatic(::getUseDuration) + ireturn() + } + } + + @JvmStatic + fun getUseDuration(stack: ItemStack, item: Item): Int { + val novaItem = stack.novaItem + if (novaItem != null) { + if (novaItem.hasBehavior(Blocking::class)) { + return 72000 + } + } + + // -- nms logic -- + return if (stack.item.isEdible) { + if (item.foodProperties!!.isFastFood) 16 else 32 + } else { + 0 + } + // ---- + } + + private fun patchUseAnimation() { + VirtualClassPath[Item::getUseAnimation] + .instructions = buildInsnList { + aLoad(1) + aLoad(0) + invokeStatic(::getUseAnimation) + areturn() + } + } + + @JvmStatic + fun getUseAnimation(stack: ItemStack, item: Item): UseAnim { + val novaItem = stack.novaItem + if (novaItem != null) { + if (novaItem.hasBehavior(Blocking::class)) { + return UseAnim.BLOCK + } + } + + return if (stack.item.isEdible) UseAnim.EAT else UseAnim.NONE // nms logic + } + + private fun patchHurtCurrentlyUsedShield() { + VirtualClassPath[ReflectionRegistry.PLAYER_HURT_CURRENTLY_USED_SHIELD_METHOD].replaceFirst( + 2, 0, + buildInsnList { + invokeStatic(BlockingPatches::isShieldItem) + } + ) { it.opcode == Opcodes.INVOKEVIRTUAL && (it as MethodInsnNode).calls(ReflectionRegistry.ITEM_STACK_IS_METHOD) } + } + + @JvmStatic + fun isShieldItem(player: Player): Boolean { + return player.useItem.`is`(Items.SHIELD) || (player.useItem.novaItem?.hasBehavior(Blocking::class) ?: false) + } + +} \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/util/reflection/ReflectionRegistry.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/util/reflection/ReflectionRegistry.kt index 108c36627e..53f19999ec 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/util/reflection/ReflectionRegistry.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/util/reflection/ReflectionRegistry.kt @@ -21,7 +21,10 @@ import net.minecraft.util.RandomSource import net.minecraft.util.ThreadingDetector import net.minecraft.world.Container import net.minecraft.world.entity.ExperienceOrb +import net.minecraft.world.entity.player.Player import net.minecraft.world.inventory.ItemCombinerMenu +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack import net.minecraft.world.item.crafting.Recipe import net.minecraft.world.level.BlockGetter import net.minecraft.world.level.ChunkPos @@ -152,6 +155,8 @@ internal object ReflectionRegistry { val NOISE_ROUTER_DATA_OVERWORLD_METHOD = getMethod(NoiseRouterData::class, true, "SRM(net.minecraft.world.level.levelgen.NoiseRouterData overworld)", HolderGetter::class, HolderGetter::class, Boolean::class, Boolean::class) val SEMAPHORE_ACQUIRE_METHOD = getMethod(Semaphore::class, false, "acquire") val CRAFT_META_ITEM_CLONE_METHOD = getMethod(CB_CRAFT_META_ITEM_CLASS, true, "clone") + val PLAYER_HURT_CURRENTLY_USED_SHIELD_METHOD = getMethod(Player::class, true, "SRM(net.minecraft.world.entity.player.Player hurtCurrentlyUsedShield)", Float::class) + val ITEM_STACK_IS_METHOD = getMethod(ItemStack::class, false, "SRM(net.minecraft.world.item.ItemStack is)", Item::class) // Fields val CRAFT_META_ITEM_UNHANDLED_TAGS_FIELD = getField(CB_CRAFT_META_ITEM_CLASS, true, "unhandledTags")