Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions rebar/src/main/kotlin/io/github/pylonmc/rebar/Rebar.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import io.github.pylonmc.rebar.recipe.RecipeType
import io.github.pylonmc.rebar.registry.RebarRegistry
import io.github.pylonmc.rebar.resourcepack.armor.ArmorTextureEngine
import io.github.pylonmc.rebar.util.delayTicks
import io.github.pylonmc.rebar.item.base.VanillaAnvilItem
import io.github.pylonmc.rebar.util.mergeGlobalConfig
import io.github.pylonmc.rebar.waila.Waila
import io.github.retrooper.packetevents.factory.spigot.SpigotPacketEventsBuilder
Expand Down Expand Up @@ -222,6 +223,7 @@ object Rebar : JavaPlugin(), RebarAddon {
RebarSplashPotion.register(this, pm)
RebarTool.register(this, pm)
RebarWeapon.register(this, pm)
VanillaAnvilItem.register(this, pm)
VanillaCookingFuel.register(this, pm)

// Rebar Entities
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ open class RebarItem(val stack: ItemStack) : Keyed {
@Suppress("UNCHECKED_CAST")
fun <T> fromStack(stack: ItemStack?, clazz: Class<T>): T? {
val schema = RebarItemSchema.fromStack(stack) ?: return null
if (!clazz.isAssignableFrom(schema.itemClass)) return null
if (!schema.isType(clazz)) return null
return schema.itemClass.cast(schema.loadConstructor.invoke(stack)) as T?
}

Expand All @@ -176,7 +176,7 @@ open class RebarItem(val stack: ItemStack) : Keyed {
@Contract("null, _ -> false")
fun isRebarItem(stack: ItemStack?, clazz: Class<*>): Boolean {
val schema = RebarItemSchema.fromStack(stack) ?: return false
return clazz.isAssignableFrom(schema.itemClass)
return schema.isType(clazz)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ class RebarItemSchema @JvmOverloads internal constructor(
*/
fun getRebarItem(): RebarItem = itemClass.cast(loadConstructor.invoke(getItemStack()))

fun <T> isType(clazz: Class<T>): Boolean {
return clazz.isAssignableFrom(itemClass)
}

val research: Research?
get() = RebarRegistry.RESEARCHES.find { key in it.unlocks }

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.github.pylonmc.rebar.item.base

import io.github.pylonmc.rebar.item.RebarItemSchema
import org.bukkit.NamespacedKey
import org.bukkit.inventory.ItemStack

interface RebarRepairable {
fun getRepairItems(): List<NamespacedKey>

fun isValidRepairItem(item: ItemStack): Boolean {
val key = RebarItemSchema.fromStack(item)?.key ?: item.type.key
return key in getRepairItems()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package io.github.pylonmc.rebar.item.base

import io.github.pylonmc.rebar.event.api.MultiListener
import io.github.pylonmc.rebar.event.api.annotation.MultiHandlers
import io.github.pylonmc.rebar.event.api.annotation.UniversalHandler
import io.github.pylonmc.rebar.item.RebarItem
import io.github.pylonmc.rebar.item.RebarItemListener
import org.bukkit.event.EventPriority
import org.bukkit.event.inventory.PrepareAnvilEvent

/**
* Enables the use of a RebarItem as if it's its vanilla counterpart in an anvil
* For example: If you have a rebar item that's an enchantment book, and you want it to be usable in an anvil
*/
interface VanillaAnvilItem {
fun onPrepareAnvilCraft(event: PrepareAnvilEvent) {}

companion object : MultiListener {
@UniversalHandler
private fun onPrepareAnvil(event: PrepareAnvilEvent, priority: EventPriority) {
val rebarItem1 = RebarItem.fromStack(event.inventory.firstItem)
val rebarItem2 = RebarItem.fromStack(event.inventory.secondItem)
if (rebarItem1 is VanillaAnvilItem) {
try {
MultiHandlers.handleEvent(rebarItem1, "onPrepareAnvilCraft", event, priority)
} catch (e: Exception) {
RebarItemListener.logEventHandleErr(event, e, rebarItem1)
}
}
if (rebarItem2 is VanillaAnvilItem) {
try {
MultiHandlers.handleEvent(rebarItem2, "onPrepareAnvilCraft", event, priority)
} catch (e: Exception) {
RebarItemListener.logEventHandleErr(event, e, rebarItem2)
}
}
}
}
}
142 changes: 131 additions & 11 deletions rebar/src/main/kotlin/io/github/pylonmc/rebar/recipe/RecipeListener.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ import io.github.pylonmc.rebar.recipe.vanilla.CookingRecipeWrapper
import io.github.pylonmc.rebar.recipe.vanilla.ShapedRecipeType
import io.github.pylonmc.rebar.recipe.vanilla.VanillaRecipeType
import io.github.pylonmc.rebar.util.isRebarAndIsNot
import io.github.pylonmc.rebar.util.plainText
import io.papermc.paper.datacomponent.DataComponentTypes
import io.papermc.paper.event.player.CartographyItemEvent
import net.kyori.adventure.text.Component
import org.bukkit.GameMode
import org.bukkit.Keyed
import org.bukkit.block.Crafter
import org.bukkit.block.Furnace
Expand All @@ -25,6 +28,7 @@ import org.bukkit.inventory.ShapedRecipe
import org.bukkit.inventory.ShapelessRecipe
import org.bukkit.inventory.StonecutterInventory
import kotlin.math.max
import kotlin.math.min

internal object RebarRecipeListener : Listener {

Expand All @@ -49,7 +53,7 @@ internal object RebarRecipeListener : Listener {
var firstItem: ItemStack? = null
var secondItem: ItemStack? = null
for (item in e.inventory.matrix) {
if (item != null && !item.isEmpty) {
if (item != null && !item.isEmpty) {
if (firstItem == null) {
firstItem = item
} else if (secondItem == null) {
Expand All @@ -66,8 +70,9 @@ internal object RebarRecipeListener : Listener {
val firstSchema = RebarItemSchema.fromStack(firstItem)
val secondSchema = RebarItemSchema.fromStack(secondItem)
if (firstSchema == null || secondSchema == null || firstSchema != secondSchema
|| RebarUnmergeable::class.java.isAssignableFrom(firstSchema.itemClass)
|| firstSchema.isType(RebarUnmergeable::class.java)
|| firstItem.amount != 1 || secondItem.amount != 1
|| firstItem.hasData(DataComponentTypes.UNBREAKABLE) || secondItem.hasData(DataComponentTypes.UNBREAKABLE)
|| !firstItem.hasData(DataComponentTypes.MAX_DAMAGE) || !secondItem.hasData(DataComponentTypes.MAX_DAMAGE)
|| !firstItem.hasData(DataComponentTypes.DAMAGE) || !secondItem.hasData(DataComponentTypes.DAMAGE)) {
inventory.result = null
Expand Down Expand Up @@ -245,21 +250,136 @@ internal object RebarRecipeListener : Listener {
}
}

@Suppress("UnstableApiUsage")
@EventHandler(priority = EventPriority.LOWEST)
private fun onAnvilSlotChanged(e: PrepareAnvilEvent) {
val player = e.viewers.first() as? Player ?: return
val inventory = e.inventory
val firstItem = inventory.firstItem
val secondItem = inventory.secondItem
val firstRebarSchema = RebarItemSchema.fromStack(firstItem)
val secondRebarSchema = RebarItemSchema.fromStack(secondItem)

// Disallow using Rebar items but allow renaming them
// Have tried to support this. It's really hard because you end up effectively
// having to do the repair manually for items that can't usually be repaired.
// Gave up after 2 hours or so
if ((firstRebarSchema != null && (secondItem != null && !secondItem.isEmpty)) || secondRebarSchema != null) {
e.result = null
val firstSchema = RebarItemSchema.fromStack(firstItem)
val secondSchema = RebarItemSchema.fromStack(secondItem)

if (firstSchema == null && secondSchema == null) {
// If it's not a rebar item interaction, we don't care about it
return
} else if (firstSchema == null) {
// If it's a vanilla item being manipulated by a rebar item, prevent it unless it's a VanillaAnvilItem
if (secondSchema != null && !secondSchema.isType(VanillaAnvilItem::class.java)) {
e.result = null
}
return
} else if (secondItem == null || secondItem.isEmpty) {
// If it's renaming a rebar item, allow it, otherwise cancel
val resultItem = inventory.result
if (resultItem != null && !firstItem!!.matchesWithoutData(resultItem, setOf(DataComponentTypes.CUSTOM_NAME))) {
e.result = null
}
return
} else if (firstItem == null) {
// Dummy check
return
}

var resultItem: ItemStack? = null

// Allow repairing with rebar items
if (firstSchema.isType(RebarRepairable::class.java) && firstItem.isRepairable()) {
val repairable = RebarItem.fromStack(firstItem, RebarRepairable::class.java)!!
if (repairable.isValidRepairItem(secondItem)) {
var price = 0
var namingPrice = 0
val tax = firstItem.getDataOrDefault(DataComponentTypes.REPAIR_COST, 0)!! + secondItem.getDataOrDefault(DataComponentTypes.REPAIR_COST, 0)!!
var repairItemCountCost = 0

resultItem = firstItem.clone()
var damage = resultItem.getDataOrDefault(DataComponentTypes.DAMAGE, 0)!!
val maxDamage = resultItem.getDataOrDefault(DataComponentTypes.MAX_DAMAGE, 0)!!
var repairAmount = min(damage, maxDamage / 4)
if (repairAmount <= 0) {
e.result = null
return
}

for (i in 0 until secondItem.amount) {
if (repairAmount <= 0) break
damage -= repairAmount
repairAmount = min(damage, maxDamage / 4)
repairItemCountCost++
price++
}

val renameText = e.view.renameText
if (!renameText.isNullOrBlank()) {
if (renameText != resultItem.effectiveName().plainText) {
namingPrice = 1
price += namingPrice
resultItem.setData(DataComponentTypes.CUSTOM_NAME, Component.text(renameText))
}
} else if (resultItem.hasData(DataComponentTypes.CUSTOM_NAME)) {
namingPrice = 1
price += namingPrice
resultItem.unsetData(DataComponentTypes.CUSTOM_NAME)
}

if (price <= 0) {
e.result = null
return
}

var cost = Math.clamp((tax + price).toLong(), 0, Int.MAX_VALUE)
if (namingPrice == price) {
if (cost >= 40) {
cost = 39
}
}

e.result = resultItem
e.view.repairCost = cost
e.view.repairItemCountCost = repairItemCountCost

if (cost >= 40 && player.gameMode != GameMode.CREATIVE) {
e.result = null
return
}

var resultCost = resultItem.getDataOrDefault(DataComponentTypes.REPAIR_COST, 0)!!
val secondCost = secondItem.getDataOrDefault(DataComponentTypes.REPAIR_COST, 0)!!
if (resultCost < secondCost) {
resultCost = secondCost
}
resultCost = min(resultCost * 2L + 1L, Int.MAX_VALUE.toLong()).toInt()
resultItem.setData(DataComponentTypes.DAMAGE, damage)
resultItem.setData(DataComponentTypes.REPAIR_COST, resultCost)
}
}

// If it hasn't been repaired, check for enchantment application or item merging by piggy backing off of vanilla logic
if (resultItem == null) {
resultItem = e.result
if (resultItem == null) {
// Something else has already canceled it
return
}

val usingBook = secondItem.hasData(DataComponentTypes.STORED_ENCHANTMENTS)
if (!usingBook && (firstSchema != secondSchema || firstSchema.isType(RebarUnmergeable::class.java))) {
e.result = null
} else if (!firstItem.matchesWithoutData(resultItem, setOf(
DataComponentTypes.ENCHANTMENTS, DataComponentTypes.STORED_ENCHANTMENTS,
DataComponentTypes.MAX_DAMAGE, DataComponentTypes.DAMAGE,
DataComponentTypes.CUSTOM_NAME, DataComponentTypes.REPAIR_COST
))) {
e.result = null
}
}
}

@Suppress("UnstableApiUsage")
private fun ItemStack.isRepairable(): Boolean {
return !hasData(DataComponentTypes.UNBREAKABLE)
&& hasData(DataComponentTypes.MAX_DAMAGE)
&& hasData(DataComponentTypes.DAMAGE)
&& getData(DataComponentTypes.DAMAGE)!! > 0
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ fun findRebarItemInInventory(inventory: Inventory, targetItem: RebarItem): Int?
@JvmSynthetic
inline fun <reified T> ItemStack?.isRebarAndIsNot(): Boolean {
val schema = RebarItemSchema.fromStack(this)
return schema != null && !T::class.java.isAssignableFrom(schema.itemClass)
return schema != null && !schema.isType(T::class.java)
}

@JvmSynthetic
Expand Down
Loading