Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shield behaviour implementation #281

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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),

Expand Down
11 changes: 11 additions & 0 deletions nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Blocking.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package xyz.xenondevs.nova.item.behavior

import xyz.xenondevs.nova.item.vanilla.VanillaMaterialProperty

object Blocking : ItemBehavior {

override fun getVanillaMaterialProperties(): List<VanillaMaterialProperty> {
return listOf(VanillaMaterialProperty.SHIELD)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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<ItemStack> {
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)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down