diff --git a/README.md b/README.md
index 8bc503b13f..7c7d96d7a8 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,25 @@
[![Banner](https://i.imgur.com/HiDAPmf.png)](https://hangar.papermc.io/xenondevs/Nova)
-
-
+
+
-
-
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
# Nova
@@ -19,8 +28,6 @@ With Nova, developers don't have to deal with resource pack tricks, data seriali
just focus in adding content to the game.
As a server administrator, you can choose from a set of Nova addons, which will add content to the game.
-You can find Nova on: [SpigotMC](https://www.spigotmc.org/resources/93648/) | [Hangar](https://hangar.papermc.io/xenondevs/Nova) | [Modrinth](https://modrinth.com/plugin/nova-framework)
-
## Features
* Custom items
diff --git a/gradle.properties b/gradle.properties
index 8eebc75d06..e4e75fa0b3 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,3 +1,3 @@
-version = 0.14.2
+version = 0.14.3
kotlin.daemon.jvmargs=-Xmx2g
org.gradle.jvmargs=-Xmx2g
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index c7b9e7f488..7ad8d9f9b9 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -2,7 +2,7 @@
format.version = "1.1"
[versions]
-cbf = "0.5"
+cbf = "0.7"
invui = "1.12"
kotlin = "1.8.22"
ktor = "2.3.1"
diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/addon/registry/ItemRegistry.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/addon/registry/ItemRegistry.kt
index cd6b61d1e8..df838ee5fa 100644
--- a/nova/src/main/kotlin/xyz/xenondevs/nova/addon/registry/ItemRegistry.kt
+++ b/nova/src/main/kotlin/xyz/xenondevs/nova/addon/registry/ItemRegistry.kt
@@ -21,7 +21,7 @@ interface ItemRegistry : AddonGetter {
fun registerItem(
name: String,
- vararg behaviors: ItemBehaviorHolder<*>,
+ vararg behaviors: ItemBehaviorHolder,
localizedName: String = "item.${addon.description.id}.$name",
isHidden: Boolean = false
): NovaItem {
@@ -37,7 +37,7 @@ interface ItemRegistry : AddonGetter {
fun registerItem(
block: NovaBlock,
- vararg behaviors: ItemBehaviorHolder<*>,
+ vararg behaviors: ItemBehaviorHolder,
localizedName: String = block.localizedName,
isHidden: Boolean = false
): NovaItem {
diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/data/config/ConfigAccess.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/data/config/ConfigAccess.kt
index 9d75e73d4e..bac258b386 100644
--- a/nova/src/main/kotlin/xyz/xenondevs/nova/data/config/ConfigAccess.kt
+++ b/nova/src/main/kotlin/xyz/xenondevs/nova/data/config/ConfigAccess.kt
@@ -11,7 +11,7 @@ import xyz.xenondevs.nova.item.NovaItem
import kotlin.reflect.KType
import kotlin.reflect.typeOf
-abstract class ConfigAccess(private val configReceiver: () -> YamlConfiguration) {
+open class ConfigAccess(private val configReceiver: () -> YamlConfiguration) {
val cfg: YamlConfiguration
get() = configReceiver()
@@ -20,11 +20,11 @@ abstract class ConfigAccess(private val configReceiver: () -> YamlConfiguration)
constructor(item: NovaItem) : this({ NovaConfig[item] })
- protected inline fun getEntry(key: String): Provider {
+ inline fun getEntry(key: String): Provider {
return RequiredConfigEntryAccessor(key, typeOf()).also(ConfigEntryAccessor<*>::reload)
}
- protected inline fun getEntry(key: String, vararg fallbackKeys: String): Provider {
+ inline fun getEntry(key: String, vararg fallbackKeys: String): Provider {
val type = typeOf()
var provider: Provider = NullableConfigEntryAccessor(key, type)
fallbackKeys.forEach { provider = provider.orElse(NullableConfigEntryAccessor(it, type)) }
@@ -33,32 +33,35 @@ abstract class ConfigAccess(private val configReceiver: () -> YamlConfiguration)
.also(Provider<*>::update)
}
- protected inline fun getOptionalEntry(key: String): Provider {
+ inline fun getOptionalEntry(key: String): Provider {
return NullableConfigEntryAccessor(key, typeOf()).also(Provider<*>::update)
}
- protected inline fun getOptionalEntry(key: String, vararg fallbackKeys: String): Provider {
+ inline fun getOptionalEntry(key: String, vararg fallbackKeys: String): Provider {
val type = typeOf()
var provider: Provider = NullableConfigEntryAccessor(key, type)
fallbackKeys.forEach { provider = provider.orElse(NullableConfigEntryAccessor(it, type)) }
return provider.also(Provider<*>::update)
}
- protected inner class RequiredConfigEntryAccessor(key: String, type: KType) : ConfigEntryAccessor(key, type) {
+ @PublishedApi
+ internal inner class RequiredConfigEntryAccessor(key: String, type: KType) : ConfigEntryAccessor(key, type) {
override fun loadValue(): T {
check(key in cfg) { "No such config entry: $key" }
return cfg.getDeserialized(key, type)!!
}
}
- protected inner class NullableConfigEntryAccessor(key: String, type: KType) : ConfigEntryAccessor(key, type) {
+ @PublishedApi
+ internal inner class NullableConfigEntryAccessor(key: String, type: KType) : ConfigEntryAccessor(key, type) {
override fun loadValue(): T? {
return cfg.getDeserialized(key, type)
}
}
@Suppress("LeakingThis")
- protected abstract class ConfigEntryAccessor(protected val key: String, protected val type: KType) : Provider(), Reloadable {
+ @PublishedApi
+ internal abstract class ConfigEntryAccessor(protected val key: String, protected val type: KType) : Provider(), Reloadable {
init {
NovaConfig.reloadables += this
diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/data/recipe/impl/RepairItemRecipe.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/data/recipe/impl/RepairItemRecipe.kt
index acafb5ffe8..ffe8a267f4 100644
--- a/nova/src/main/kotlin/xyz/xenondevs/nova/data/recipe/impl/RepairItemRecipe.kt
+++ b/nova/src/main/kotlin/xyz/xenondevs/nova/data/recipe/impl/RepairItemRecipe.kt
@@ -10,7 +10,6 @@ import net.minecraft.world.item.enchantment.Enchantment
import net.minecraft.world.item.enchantment.EnchantmentHelper
import net.minecraft.world.level.Level
import xyz.xenondevs.nova.item.behavior.Damageable
-import xyz.xenondevs.nova.util.item.DamageableUtils
import xyz.xenondevs.nova.util.item.novaItem
import xyz.xenondevs.nova.util.nmsCopy
import kotlin.math.max
@@ -26,7 +25,7 @@ internal class RepairItemRecipe(id: ResourceLocation) : MojangRepairItemRecipe(i
if (itemStack.isEmpty)
continue
- if (!secondStackFound && DamageableUtils.isDamageable(itemStack) && itemStack.count == 1) {
+ if (!secondStackFound && Damageable.isDamageable(itemStack) && itemStack.count == 1) {
if (firstStack == null) {
firstStack = itemStack
} else if (isSameItem(firstStack, itemStack)) {
@@ -49,8 +48,8 @@ internal class RepairItemRecipe(id: ResourceLocation) : MojangRepairItemRecipe(i
require(items.size == 2) { "Item size is not 2" }
val novaItem = items[0].novaItem
if (novaItem != null) {
- val damageable = novaItem.getBehavior(Damageable::class)!!
- val maxDurability = damageable.options.durability
+ val damageable = novaItem.getBehavior(Damageable::class)
+ val maxDurability = damageable.maxDurability
val itemStackA = items[0]
val itemStackB = items[1]
diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/data/serialization/cbf/NamespacedCompound.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/data/serialization/cbf/NamespacedCompound.kt
index b9c256b22e..1e1c9b6746 100644
--- a/nova/src/main/kotlin/xyz/xenondevs/nova/data/serialization/cbf/NamespacedCompound.kt
+++ b/nova/src/main/kotlin/xyz/xenondevs/nova/data/serialization/cbf/NamespacedCompound.kt
@@ -9,11 +9,12 @@ import xyz.xenondevs.cbf.io.ByteReader
import xyz.xenondevs.cbf.io.ByteWriter
import xyz.xenondevs.nova.addon.Addon
import xyz.xenondevs.nova.util.name
+import java.util.*
import kotlin.reflect.KType
import kotlin.reflect.typeOf
class NamespacedCompound internal constructor(
- private val map: HashMap
+ private val map: MutableMap
) {
val keys: Set
@@ -152,6 +153,13 @@ class NamespacedCompound internal constructor(
}
+ fun putAll(other: NamespacedCompound) {
+ for ((namespace, otherCompound) in other.map) {
+ val compound = map.getOrPut(namespace, ::Compound)
+ compound.putAll(otherCompound)
+ }
+ }
+
fun copy(): NamespacedCompound = NamespacedCompound(map.mapValuesTo(HashMap()) { (_, value) -> value.copy() })
fun isEmpty(): Boolean = map.isNotEmpty()
@@ -169,6 +177,12 @@ class NamespacedCompound internal constructor(
return builder.toString().replace("\n", "\n ") + "\n}"
}
+ companion object {
+
+ val EMPTY = NamespacedCompound(Collections.unmodifiableMap(HashMap()))
+
+ }
+
internal object NamespacedCompoundBinaryAdapter : BinaryAdapter {
override fun write(obj: NamespacedCompound, type: KType, writer: ByteWriter) {
diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/item/DefaultItems.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/item/DefaultItems.kt
index ec5ea0979e..97cd70163e 100644
--- a/nova/src/main/kotlin/xyz/xenondevs/nova/item/DefaultItems.kt
+++ b/nova/src/main/kotlin/xyz/xenondevs/nova/item/DefaultItems.kt
@@ -3,8 +3,8 @@
package xyz.xenondevs.nova.item
import net.minecraft.resources.ResourceLocation
-import xyz.xenondevs.nova.initialize.InternalInitStage
import xyz.xenondevs.nova.initialize.InternalInit
+import xyz.xenondevs.nova.initialize.InternalInitStage
import xyz.xenondevs.nova.item.behavior.ItemBehaviorHolder
import xyz.xenondevs.nova.item.behavior.impl.WrenchBehavior
import xyz.xenondevs.nova.item.logic.ItemLogic
@@ -137,7 +137,7 @@ object DefaultBlockOverlays {
private fun registerCoreItem(
name: String,
- vararg itemBehaviors: ItemBehaviorHolder<*>,
+ vararg itemBehaviors: ItemBehaviorHolder,
localizedName: String = "item.nova.$name",
isHidden: Boolean = false
): NovaItem = register(NovaItem(
@@ -150,7 +150,7 @@ private fun registerCoreItem(
private fun registerUnnamedHiddenCoreItem(
name: String,
localizedName: String = "",
- vararg itemBehaviors: ItemBehaviorHolder<*>
+ vararg itemBehaviors: ItemBehaviorHolder
): NovaItem = register(NovaItem(
ResourceLocation("nova", name),
localizedName,
diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/item/NovaItem.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/item/NovaItem.kt
index 5d074c2c6d..0c8b19263a 100644
--- a/nova/src/main/kotlin/xyz/xenondevs/nova/item/NovaItem.kt
+++ b/nova/src/main/kotlin/xyz/xenondevs/nova/item/NovaItem.kt
@@ -109,26 +109,38 @@ class NovaItem internal constructor(
/**
* Checks whether this [NovaItem] has an [ItemBehavior] of the reified type [T], or a subclass of it.
*/
- inline fun hasBehavior(): Boolean =
+ inline fun hasBehavior(): Boolean =
hasBehavior(T::class)
/**
* Checks whether this [NovaItem] has an [ItemBehavior] of the specified class [behavior], or a subclass of it.
*/
- fun hasBehavior(behavior: KClass): Boolean =
+ fun hasBehavior(behavior: KClass): Boolean =
logic.hasBehavior(behavior)
/**
- * Gets the [ItemBehavior] instance of the reified type [T], or a subclass of it.
+ * Gets the first [ItemBehavior] that is an instance of [T], or null if there is none.
*/
- inline fun getBehavior(): T? =
+ inline fun getBehaviorOrNull(): T? =
+ getBehaviorOrNull(T::class)
+
+ /**
+ * Gets the first [ItemBehavior] that is an instance of [behavior], or null if there is none.
+ */
+ fun getBehaviorOrNull(behavior: KClass): T? =
+ logic.getBehaviorOrNull(behavior)
+
+ /**
+ * Gets the first [ItemBehavior] that is an instance of [T], or throws an [IllegalStateException] if there is none.
+ */
+ inline fun getBehavior(): T =
getBehavior(T::class)
/**
- * Gets the [ItemBehavior] instance of the specified class [behavior], or a subclass of it.
+ * Gets the first [ItemBehavior] that is an instance of [behavior], or throws an [IllegalStateException] if there is none.
*/
- fun getBehavior(behavior: KClass): T? =
- logic.getBehavior(behavior)
+ fun getBehavior(behavior: KClass): T =
+ getBehaviorOrNull(behavior) ?: throw IllegalStateException("Item $id does not have a behavior of type ${behavior.simpleName}")
override fun toString() = id.toString()
diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/item/NovaItemBuilder.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/item/NovaItemBuilder.kt
index dc17e2f61b..6267565089 100644
--- a/nova/src/main/kotlin/xyz/xenondevs/nova/item/NovaItemBuilder.kt
+++ b/nova/src/main/kotlin/xyz/xenondevs/nova/item/NovaItemBuilder.kt
@@ -16,7 +16,7 @@ import xyz.xenondevs.nova.world.block.NovaTileEntityBlock
class NovaItemBuilder internal constructor(id: ResourceLocation): RegistryElementBuilder(NovaRegistries.ITEM, id) {
- private var logic: MutableList> = ArrayList()
+ private var logic: MutableList = ArrayList()
private var localizedName = "item.${id.namespace}.${id.name}"
private var maxStackSize = 64
private var craftingRemainingItem: ItemBuilder? = null
@@ -35,12 +35,12 @@ class NovaItemBuilder internal constructor(id: ResourceLocation): RegistryElemen
return this
}
- fun behaviors(vararg itemBehaviors: ItemBehaviorHolder<*>): NovaItemBuilder {
+ fun behaviors(vararg itemBehaviors: ItemBehaviorHolder): NovaItemBuilder {
this.logic = itemBehaviors.toMutableList()
return this
}
- fun addBehavior(vararg itemBehaviors: ItemBehaviorHolder<*>): NovaItemBuilder {
+ fun addBehavior(vararg itemBehaviors: ItemBehaviorHolder): NovaItemBuilder {
this.logic += itemBehaviors
return this
}
@@ -86,7 +86,7 @@ class NovaItemBuilder internal constructor(id: ResourceLocation): RegistryElemen
this.block = block
localizedName(block.localizedName)
if (block is NovaTileEntityBlock)
- behaviors(TileEntityItemBehavior())
+ behaviors(TileEntityItemBehavior)
}
}
diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Chargeable.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Chargeable.kt
index 2850c1a7b6..07375868c4 100644
--- a/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Chargeable.kt
+++ b/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Chargeable.kt
@@ -2,103 +2,125 @@ package xyz.xenondevs.nova.item.behavior
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.format.NamedTextColor
-import org.bukkit.inventory.ItemStack
-import xyz.xenondevs.invui.item.builder.ItemBuilder
+import xyz.xenondevs.commons.provider.Provider
+import xyz.xenondevs.nova.data.config.ConfigAccess
import xyz.xenondevs.nova.data.serialization.cbf.NamespacedCompound
import xyz.xenondevs.nova.item.NovaItem
import xyz.xenondevs.nova.item.logic.PacketItemData
-import xyz.xenondevs.nova.item.options.ChargeableOptions
import xyz.xenondevs.nova.item.vanilla.VanillaMaterialProperty
import xyz.xenondevs.nova.util.NumberFormatUtils
import xyz.xenondevs.nova.util.item.novaCompound
import net.minecraft.world.item.ItemStack as MojangStack
+import org.bukkit.inventory.ItemStack as BukkitStack
@Suppress("FunctionName")
-fun Chargeable(affectsItemDurability: Boolean): ItemBehaviorFactory =
- object : ItemBehaviorFactory() {
- override fun create(item: NovaItem): Chargeable =
- Chargeable(ChargeableOptions.configurable(item), affectsItemDurability)
+fun Chargeable(affectsItemDurability: Boolean): ItemBehaviorFactory =
+ object : ItemBehaviorFactory {
+ override fun create(item: NovaItem): Chargeable.Default {
+ val cfg = ConfigAccess(item)
+ return Chargeable.Default(cfg.getEntry("max_energy"), affectsItemDurability)
+ }
}
-class Chargeable(
- val options: ChargeableOptions,
- private val affectsItemDurability: Boolean = true
-) : ItemBehavior() {
+/**
+ * Allows items to store energy and be charged.
+ */
+interface Chargeable {
- @Deprecated("Replaced by ChargeableOptions", ReplaceWith("options.maxEnergy"))
+ /**
+ * The maximum amount of energy this item can store.
+ */
val maxEnergy: Long
- get() = options.maxEnergy
- //
- fun getEnergy(itemStack: ItemStack): Long {
- return getEnergy(itemStack.novaCompound)
- }
+ /**
+ * Gets the current amount of energy stored in the given [itemStack].
+ */
+ fun getEnergy(itemStack: BukkitStack): Long
- fun setEnergy(itemStack: ItemStack, energy: Long) {
- return setEnergy(itemStack.novaCompound, energy)
- }
+ /**
+ * Sets the current amount of energy stored in the given [itemStack] to [energy].
+ */
+ fun setEnergy(itemStack: BukkitStack, energy: Long)
- fun addEnergy(itemStack: ItemStack, energy: Long) {
- return addEnergy(itemStack.novaCompound, energy)
- }
- //
+ /**
+ * Adds the given [energy] to the current amount of energy stored in the given [itemStack], capped at [maxEnergy].
+ */
+ fun addEnergy(itemStack: BukkitStack, energy: Long)
- //
- fun getEnergy(itemStack: MojangStack): Long {
- return getEnergy(itemStack.novaCompound)
- }
+ /**
+ * Gets the current amount of energy stored in the given [itemStack].
+ */
+ fun getEnergy(itemStack: MojangStack): Long
- fun setEnergy(itemStack: MojangStack, energy: Long) {
- return setEnergy(itemStack.novaCompound, energy)
- }
+ /**
+ * Sets the current amount of energy stored in the given [itemStack] to [energy].
+ */
+ fun setEnergy(itemStack: MojangStack, energy: Long)
- fun addEnergy(itemStack: MojangStack, energy: Long) {
- return addEnergy(itemStack.novaCompound, energy)
- }
- //
+ /**
+ * Adds the given [energy] to the current amount of energy stored in the given [itemStack], capped at [maxEnergy].
+ */
+ fun addEnergy(itemStack: MojangStack, energy: Long)
- //
- fun getEnergy(data: NamespacedCompound): Long {
- val currentEnergy = data["nova", "energy"] ?: 0L
- if (currentEnergy > options.maxEnergy) {
- setEnergy(data, options.maxEnergy)
- return options.maxEnergy
+ companion object : ItemBehaviorFactory {
+
+ override fun create(item: NovaItem): Default {
+ val cfg = ConfigAccess(item)
+ return Default(cfg.getEntry("max_energy"), true)
}
- return currentEnergy
- }
-
- fun setEnergy(data: NamespacedCompound, energy: Long) {
- data["nova", "energy"] = energy.coerceIn(0, options.maxEnergy)
- }
-
- fun addEnergy(data: NamespacedCompound, energy: Long) {
- setEnergy(data, getEnergy(data) + energy)
- }
- //
-
- override fun modifyItemBuilder(itemBuilder: ItemBuilder): ItemBuilder {
- itemBuilder.addModifier { setEnergy(it.novaCompound, 0); it }
- return itemBuilder
+
}
- override fun updatePacketItemData(data: NamespacedCompound, itemData: PacketItemData) {
- val energy = getEnergy(data)
+ class Default(
+ maxEnergy: Provider,
+ private val affectsItemDurability: Boolean
+ ) : ItemBehavior, Chargeable {
- itemData.addLore(Component.text(NumberFormatUtils.getEnergyString(energy, options.maxEnergy), NamedTextColor.GRAY))
+ override val maxEnergy by maxEnergy
+
+ override fun getVanillaMaterialProperties(): List {
+ return if (affectsItemDurability)
+ listOf(VanillaMaterialProperty.DAMAGEABLE)
+ else emptyList()
+ }
+
+ override fun getDefaultCompound(): NamespacedCompound {
+ val compound = NamespacedCompound()
+ compound["nova", "energy"] = 0L
+ return compound
+ }
+
+ override fun updatePacketItemData(data: NamespacedCompound, itemData: PacketItemData) {
+ val energy = getEnergy(data)
+ itemData.addLore(Component.text(NumberFormatUtils.getEnergyString(energy, maxEnergy), NamedTextColor.GRAY))
+ if (affectsItemDurability)
+ itemData.durabilityBar = energy.toDouble() / maxEnergy.toDouble()
+ }
+
+ override fun getEnergy(itemStack: BukkitStack) = getEnergy(itemStack.novaCompound)
+ override fun setEnergy(itemStack: BukkitStack, energy: Long) = setEnergy(itemStack.novaCompound, energy)
+ override fun addEnergy(itemStack: BukkitStack, energy: Long) = addEnergy(itemStack.novaCompound, energy)
+ override fun getEnergy(itemStack: MojangStack) = getEnergy(itemStack.novaCompound)
+ override fun setEnergy(itemStack: MojangStack, energy: Long) = setEnergy(itemStack.novaCompound, energy)
+ override fun addEnergy(itemStack: MojangStack, energy: Long) = addEnergy(itemStack.novaCompound, energy)
+
+ private fun getEnergy(data: NamespacedCompound): Long {
+ val currentEnergy = data["nova", "energy"] ?: 0L
+ if (currentEnergy > maxEnergy) {
+ setEnergy(data, maxEnergy)
+ return maxEnergy
+ }
+ return currentEnergy
+ }
+
+ private fun setEnergy(data: NamespacedCompound, energy: Long) {
+ data["nova", "energy"] = energy.coerceIn(0, maxEnergy)
+ }
+
+ private fun addEnergy(data: NamespacedCompound, energy: Long) {
+ setEnergy(data, getEnergy(data) + energy)
+ }
- if (affectsItemDurability)
- itemData.durabilityBar = energy.toDouble() / options.maxEnergy.toDouble()
- }
-
- override fun getVanillaMaterialProperties(): List {
- return if (affectsItemDurability)
- listOf(VanillaMaterialProperty.DAMAGEABLE)
- else emptyList()
- }
-
- companion object : ItemBehaviorFactory() {
- override fun create(item: NovaItem): Chargeable =
- Chargeable(ChargeableOptions.configurable(item), true)
}
}
\ No newline at end of file
diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Consumable.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Consumable.kt
index e3f00de5d7..ad28b7a46b 100644
--- a/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Consumable.kt
+++ b/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Consumable.kt
@@ -11,13 +11,17 @@ import org.bukkit.event.block.Action
import org.bukkit.event.player.PlayerInteractEvent
import org.bukkit.inventory.EquipmentSlot
import org.bukkit.inventory.ItemStack
+import org.bukkit.potion.PotionEffect
+import xyz.xenondevs.commons.provider.Provider
+import xyz.xenondevs.commons.provider.immutable.map
+import xyz.xenondevs.commons.provider.immutable.orElse
+import xyz.xenondevs.commons.provider.immutable.provider
import xyz.xenondevs.nmsutils.network.PacketIdRegistry
import xyz.xenondevs.nmsutils.network.event.serverbound.ServerboundPlayerActionPacketEvent
import xyz.xenondevs.nmsutils.network.send
import xyz.xenondevs.nmsutils.util.removeIf
+import xyz.xenondevs.nova.data.config.ConfigAccess
import xyz.xenondevs.nova.item.NovaItem
-import xyz.xenondevs.nova.item.options.FoodOptions
-import xyz.xenondevs.nova.item.options.FoodOptions.FoodType
import xyz.xenondevs.nova.item.vanilla.VanillaMaterialProperty
import xyz.xenondevs.nova.util.getPlayersNearby
import xyz.xenondevs.nova.util.intValue
@@ -34,125 +38,234 @@ import kotlin.random.Random
private const val PACKET_DISTANCE = 500.0
-data class Eater(val itemStack: ItemStack, val hand: EquipmentSlot, val startTime: Int)
+private data class Eater(val itemStack: ItemStack, val hand: EquipmentSlot, val startTime: Int)
-class Consumable(private val options: FoodOptions) : ItemBehavior() {
+fun FoodOptions(
+ type: Consumable.FoodType,
+ consumeTime: Int,
+ nutrition: Int,
+ saturationModifier: Float,
+ instantHealth: Double = 0.0,
+ effects: List? = null
+) = Consumable.Default(
+ provider(type),
+ provider(consumeTime),
+ provider(nutrition),
+ provider(saturationModifier),
+ provider(instantHealth),
+ provider(effects)
+)
+
+/**
+ * Allows items to be consumed.
+ */
+sealed interface Consumable { // TODO: remove sealed & make more customizable (make logic a patch?)
- private val eaters = HashMap()
+ /**
+ * The type of food.
+ */
+ val type: FoodType
- init {
- runTaskTimer(0, 1) {
- eaters.removeIf { (player, eater) ->
- if (serverTick >= eater.startTime + options.consumeTime) {
- finishEating(player, eater)
- return@removeIf true
- } else handleEating(player, eater)
-
- return@removeIf false
- }
- }
- }
+ /**
+ * The time it takes for the food to be consumed, in ticks.
+ */
+ val consumeTime: Int
- override fun getVanillaMaterialProperties(): List {
- return listOf(options.type.vanillaMaterialProperty)
- }
+ /**
+ * The nutrition value of the food.
+ */
+ val nutrition: Int
+
+ /**
+ * The saturation modifier this food provides. The saturation is calculated like this:
+ * ```
+ * saturation = min(saturation + nutrition * saturationModifier * 2.0f, foodLevel)
+ * ```
+ */
+ val saturationModifier: Float
- override fun handleInteract(player: Player, itemStack: ItemStack, action: Action, event: PlayerInteractEvent) {
- // only right-clicks in the air or on non-interactive blocks will cause an eating process
- if (!action.isRightClick() || (action == Action.RIGHT_CLICK_BLOCK && event.clickedBlock!!.type.isInteractable))
- return
+ /**
+ * The amount of health to be restored immediately.
+ */
+ val instantHealth: Double
+
+ /**
+ * A list of effects to apply to the player when this food is consumed.
+ */
+ val effects: List?
+
+ enum class FoodType(internal val property: VanillaMaterialProperty) {
+
+ /**
+ * Behaves like normal food.
+ *
+ * Has a small delay before the eating animation starts.
+ *
+ * Can only be eaten when hungry.
+ */
+ NORMAL(VanillaMaterialProperty.CONSUMABLE_NORMAL),
- // food which is not always consumable cannot be eaten in survival with a full hunger bar
- if (options.type != FoodType.ALWAYS_EATABLE && player.gameMode != GameMode.CREATIVE && event.player.foodLevel == 20)
- return
+ /**
+ * The eating animation starts immediately.
+ *
+ * Can only be eaten when hungry.
+ */
+ FAST(VanillaMaterialProperty.CONSUMABLE_FAST),
- event.isCancelled = true
- beginEating(player, itemStack, event.hand!!)
+ /**
+ * The food can always be eaten, no hunger is required.
+ *
+ * Has a small delay before the eating animation starts.
+ */
+ ALWAYS_EATABLE(VanillaMaterialProperty.CONSUMABLE_ALWAYS)
}
- override fun handleRelease(player: Player, itemStack: ItemStack, event: ServerboundPlayerActionPacketEvent) {
- runTask {
- val eater = eaters.remove(player)
- if (eater != null)
- stopEatingAnimation(player, eater)
+ class Default(
+ type: Provider,
+ consumeTime: Provider,
+ nutrition: Provider,
+ saturationModifier: Provider,
+ instantHealth: Provider,
+ effects: Provider?>
+ ) : ItemBehavior, Consumable {
+
+ override val type by type
+ override val consumeTime by consumeTime
+ override val nutrition by nutrition
+ override val saturationModifier by saturationModifier
+ override val instantHealth by instantHealth
+ override val effects by effects
+
+ private val eaters = HashMap()
+
+ init {
+ runTaskTimer(0, 1) {
+ eaters.removeIf { (player, eater) ->
+ if (serverTick >= eater.startTime + this.consumeTime) {
+ finishEating(player, eater)
+ return@removeIf true
+ } else handleEating(player, eater)
+
+ return@removeIf false
+ }
+ }
}
- }
-
- private fun handleEating(player: Player, eater: Eater) {
- if (options.consumeTime < 20) {
- // if the consumeTime is smaller than 20 ticks, the initial wait of 7 ticks will be skipped
- playEatSound(player)
- } else {
- val eatTimePassed = serverTick - eater.startTime
- if ((options.type == FoodType.FAST || eatTimePassed > 7) && eatTimePassed % 4 == 0)
- playEatSound(player)
+
+ override fun getVanillaMaterialProperties(): List {
+ return listOf(type.property)
}
- }
-
- private fun playEatSound(player: Player) {
- player.location.playSoundNearby(Sound.ENTITY_GENERIC_EAT, SoundCategory.PLAYERS, 1f, Random.nextDouble(0.8, 1.2).toFloat(), player)
- }
-
- private fun beginEating(player: Player, itemStack: ItemStack, hand: EquipmentSlot) {
- // add to eaters map
- eaters[player] = Eater(itemStack, hand, serverTick)
- // send packet to begin eating particles for players nearby
- val packet = createEatingAnimationPacket(player.entityId, true, hand)
- player.location.getPlayersNearby(PACKET_DISTANCE, player).forEach { it.send(packet) }
- }
-
- private fun stopEatingAnimation(player: Player, eater: Eater) {
- // send packet to stop eating animation to other players
- val stopEatingAnimationPacket = createEatingAnimationPacket(player.entityId, false, eater.hand)
- player.location.getPlayersNearby(PACKET_DISTANCE, player).forEach { it.send(stopEatingAnimationPacket) }
- }
-
- private fun finishEating(player: Player, eater: Eater) {
- if (!player.isOnline)
- return
+ override fun handleInteract(player: Player, itemStack: ItemStack, action: Action, event: PlayerInteractEvent) {
+ // only right-clicks in the air or on non-interactive blocks will cause an eating process
+ if (!action.isRightClick() || (action == Action.RIGHT_CLICK_BLOCK && event.clickedBlock!!.type.isInteractable))
+ return
+
+ // food which is not always consumable cannot be eaten in survival with a full hunger bar
+ if (type != FoodType.ALWAYS_EATABLE && player.gameMode != GameMode.CREATIVE && event.player.foodLevel == 20)
+ return
+
+ event.isCancelled = true
+ beginEating(player, itemStack, event.hand!!)
+ }
- // stop eating animation for other players
- stopEatingAnimation(player, eater)
+ override fun handleRelease(player: Player, itemStack: ItemStack, event: ServerboundPlayerActionPacketEvent) {
+ runTask {
+ val eater = eaters.remove(player)
+ if (eater != null)
+ stopEatingAnimation(player, eater)
+ }
+ }
- // send packet to stop eating
- val stopEatingPacket = ClientboundEntityEventPacket(player.serverPlayer, 9)
- player.send(stopEatingPacket)
+ private fun handleEating(player: Player, eater: Eater) {
+ if (consumeTime < 20) {
+ // if the consumeTime is smaller than 20 ticks, the initial wait of 7 ticks will be skipped
+ playEatSound(player)
+ } else {
+ val eatTimePassed = serverTick - eater.startTime
+ if ((type == FoodType.FAST || eatTimePassed > 7) && eatTimePassed % 4 == 0)
+ playEatSound(player)
+ }
+ }
- // food level / saturation / health
- player.foodLevel = min(player.foodLevel + options.nutrition, 20)
- player.saturation = min(player.saturation + options.nutrition * options.saturationModifier * 2.0f, player.foodLevel.toFloat())
- player.health = min(player.health + options.instantHealth, player.genericMaxHealth)
+ private fun playEatSound(player: Player) {
+ player.location.playSoundNearby(Sound.ENTITY_GENERIC_EAT, SoundCategory.PLAYERS, 1f, Random.nextDouble(0.8, 1.2).toFloat(), player)
+ }
- // effects
- options.effects?.forEach { player.addPotionEffect(it) }
+ private fun beginEating(player: Player, itemStack: ItemStack, hand: EquipmentSlot) {
+ // add to eaters map
+ eaters[player] = Eater(itemStack, hand, serverTick)
+
+ // send packet to begin eating particles for players nearby
+ val packet = createEatingAnimationPacket(player.entityId, true, hand)
+ player.location.getPlayersNearby(PACKET_DISTANCE, player).forEach { it.send(packet) }
+ }
- // sounds
- player.location.playSoundNearby(Sound.ENTITY_PLAYER_BURP, SoundCategory.PLAYERS, 0.5f, Random.nextDouble(0.9, 1.0).toFloat())
- player.location.playSoundNearby(Sound.ENTITY_GENERIC_EAT, SoundCategory.PLAYERS, 1.0f, Random.nextDouble(0.6, 1.4).toFloat())
+ private fun stopEatingAnimation(player: Player, eater: Eater) {
+ // send packet to stop eating animation to other players
+ val stopEatingAnimationPacket = createEatingAnimationPacket(player.entityId, false, eater.hand)
+ player.location.getPlayersNearby(PACKET_DISTANCE, player).forEach { it.send(stopEatingAnimationPacket) }
+ }
+
+ private fun finishEating(player: Player, eater: Eater) {
+ if (!player.isOnline)
+ return
+
+ // stop eating animation for other players
+ stopEatingAnimation(player, eater)
+
+ // send packet to stop eating
+ val stopEatingPacket = ClientboundEntityEventPacket(player.serverPlayer, 9)
+ player.send(stopEatingPacket)
+
+ // food level / saturation / health
+ player.foodLevel = min(player.foodLevel + nutrition, 20)
+ player.saturation = min(player.saturation + nutrition * saturationModifier * 2.0f, player.foodLevel.toFloat())
+ player.health = min(player.health + instantHealth, player.genericMaxHealth)
+
+ // effects
+ effects?.forEach { player.addPotionEffect(it) }
+
+ // sounds
+ player.location.playSoundNearby(Sound.ENTITY_PLAYER_BURP, SoundCategory.PLAYERS, 0.5f, Random.nextDouble(0.9, 1.0).toFloat())
+ player.location.playSoundNearby(Sound.ENTITY_GENERIC_EAT, SoundCategory.PLAYERS, 1.0f, Random.nextDouble(0.6, 1.4).toFloat())
+
+ // take item
+ if (player.gameMode != GameMode.CREATIVE)
+ eater.itemStack.amount -= 1
+ }
+
+ private fun createEatingAnimationPacket(entityId: Int, active: Boolean, hand: EquipmentSlot): FriendlyByteBuf {
+ val isOffHand = hand == EquipmentSlot.OFF_HAND
+
+ val buf = FriendlyByteBuf(Unpooled.buffer())
+ buf.writeVarInt(PacketIdRegistry.CLIENTBOUND_SET_ENTITY_DATA_PACKET)
+ buf.writeVarInt(entityId) // entity id
+ buf.writeByte(8) // type for eating animation
+ buf.writeVarInt(0) // following is byte
+ buf.writeByte(active.intValue or (isOffHand.intValue shl 1))
+ buf.writeByte(0xff) // no more data
+
+ return buf
+ }
- // take item
- if (player.gameMode != GameMode.CREATIVE)
- eater.itemStack.amount -= 1
- }
-
- private fun createEatingAnimationPacket(entityId: Int, active: Boolean, hand: EquipmentSlot): FriendlyByteBuf {
- val isOffHand = hand == EquipmentSlot.OFF_HAND
-
- val buf = FriendlyByteBuf(Unpooled.buffer())
- buf.writeVarInt(PacketIdRegistry.CLIENTBOUND_SET_ENTITY_DATA_PACKET)
- buf.writeVarInt(entityId) // entity id
- buf.writeByte(8) // type for eating animation
- buf.writeVarInt(0) // following is byte
- buf.writeByte(active.intValue or (isOffHand.intValue shl 1))
- buf.writeByte(0xff) // no more data
-
- return buf
}
- companion object : ItemBehaviorFactory() {
- override fun create(item: NovaItem): Consumable =
- Consumable(FoodOptions.configurable(item))
+ companion object : ItemBehaviorFactory {
+
+ override fun create(item: NovaItem): Default {
+ val cfg = ConfigAccess(item)
+ return Default(
+ cfg.getOptionalEntry("food_type")
+ .map { FoodType.valueOf(it.uppercase()) }
+ .orElse(FoodType.NORMAL),
+ cfg.getEntry("consume_time"),
+ cfg.getEntry("nutrition"),
+ cfg.getEntry("saturation_modifier"),
+ cfg.getOptionalEntry("instant_health").orElse(0.0),
+ cfg.getOptionalEntry>("effects")
+ )
+ }
+
}
}
\ No newline at end of file
diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Damageable.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Damageable.kt
index 2da13d5240..33875106a6 100644
--- a/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Damageable.kt
+++ b/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Damageable.kt
@@ -1,106 +1,517 @@
+@file:Suppress("UNCHECKED_CAST")
+
package xyz.xenondevs.nova.item.behavior
-import net.kyori.adventure.text.Component
+import net.minecraft.advancements.CriteriaTriggers
+import net.minecraft.server.level.ServerPlayer
+import net.minecraft.stats.Stats
+import net.minecraft.world.item.enchantment.EnchantmentHelper
+import net.minecraft.world.item.enchantment.Enchantments
+import org.bukkit.GameMode
+import org.bukkit.Statistic
+import org.bukkit.craftbukkit.v1_20_R1.event.CraftEventFactory
+import org.bukkit.craftbukkit.v1_20_R1.util.CraftMagicNumbers
+import org.bukkit.enchantments.Enchantment
+import org.bukkit.event.player.PlayerItemBreakEvent
+import org.bukkit.event.player.PlayerItemDamageEvent
import org.bukkit.inventory.ItemStack
+import org.bukkit.inventory.RecipeChoice
+import xyz.xenondevs.commons.provider.Provider
+import xyz.xenondevs.commons.provider.immutable.map
+import xyz.xenondevs.commons.provider.immutable.orElse
+import xyz.xenondevs.commons.provider.immutable.provider
+import xyz.xenondevs.nova.data.config.ConfigAccess
import xyz.xenondevs.nova.data.serialization.cbf.NamespacedCompound
+import xyz.xenondevs.nova.data.serialization.json.serializer.RecipeDeserializer
import xyz.xenondevs.nova.item.NovaItem
import xyz.xenondevs.nova.item.logic.PacketItemData
-import xyz.xenondevs.nova.item.options.DamageableOptions
import xyz.xenondevs.nova.item.vanilla.VanillaMaterialProperty
+import xyz.xenondevs.nova.util.bukkitMirror
+import xyz.xenondevs.nova.util.callEvent
+import xyz.xenondevs.nova.util.item.isEmpty
import xyz.xenondevs.nova.util.item.novaCompound
-import kotlin.math.min
+import xyz.xenondevs.nova.util.item.novaCompoundOrNull
+import xyz.xenondevs.nova.util.item.novaItem
+import xyz.xenondevs.nova.util.nmsCopy
+import xyz.xenondevs.nova.util.serverPlayer
+import kotlin.random.Random
+import net.minecraft.world.entity.LivingEntity as MojangLivingEntity
+import net.minecraft.world.entity.player.Player as MojangPlayer
import net.minecraft.world.item.ItemStack as MojangStack
+import org.bukkit.entity.LivingEntity as BukkitLivingEntity
+import org.bukkit.entity.Player as BukkitPlayer
+import org.bukkit.inventory.ItemStack as BukkitStack
+import org.bukkit.inventory.meta.Damageable as BukkitDamageable
+
+fun Damageable(
+ maxDurability: Int,
+ itemDamageOnAttackEntity: Int,
+ itemDamageOnBreakBlock: Int,
+ repairIngredient: RecipeChoice? = null
+) = Damageable.Default(
+ provider(maxDurability),
+ provider(itemDamageOnAttackEntity),
+ provider(itemDamageOnBreakBlock),
+ provider(repairIngredient),
+ true
+)
-class Damageable(val options: DamageableOptions) : ItemBehavior() {
+/**
+ * Allows items to store and receive damage.
+ */
+interface Damageable {
- @Deprecated("Replaced by DamageableOptions", ReplaceWith("options.durability"))
- val durability: Int by options.durabilityProvider
+ val maxDurability: Int
+ val itemDamageOnAttackEntity: Int
+ val itemDamageOnBreakBlock: Int
+ val repairIngredient: RecipeChoice?
- //
- fun getDamage(itemStack: ItemStack): Int {
- return getDamage(itemStack.novaCompound)
- }
+ /**
+ * Returns the current damage of the [itemStack].
+ */
+ fun getDamage(itemStack: BukkitStack): Int
- fun setDamage(itemStack: ItemStack, damage: Int) {
- setDamage(itemStack.novaCompound, damage)
- }
+ /**
+ * Sets the damage of the [itemStack] to [damage].
+ */
+ fun setDamage(itemStack: BukkitStack, damage: Int)
- fun addDamage(itemStack: ItemStack, damage: Int) {
- addDamage(itemStack.novaCompound, damage)
- }
-
- fun getDurability(itemStack: ItemStack): Int {
- return getDurability(itemStack.novaCompound)
- }
+ /**
+ * Returns the current durability of the [itemStack].
+ */
+ fun getDurability(itemStack: BukkitStack): Int = maxDurability - getDamage(itemStack)
- fun setDurability(itemStack: ItemStack, durability: Int) {
- return setDurability(itemStack.novaCompound, durability)
- }
- //
+ /**
+ * Sets the durability of the [itemStack] to [durability].
+ */
+ fun setDurability(itemStack: BukkitStack, durability: Int) = setDamage(itemStack, maxDurability - durability)
- //
- fun getDamage(itemStack: MojangStack): Int {
- return getDamage(itemStack.novaCompound)
- }
+ /**
+ * Adds [damage] to the current damage of the [itemStack] and returns whether the item broke.
+ */
+ fun damageAndBreak(itemStack: BukkitStack, damage: Int): Boolean
- fun setDamage(itemStack: MojangStack, damage: Int) {
- setDamage(itemStack.novaCompound, damage)
- }
+ /**
+ * Returns the current damage of the [itemStack].
+ */
+ fun getDamage(itemStack: MojangStack): Int
- fun addDamage(itemStack: MojangStack, damage: Int) {
- addDamage(itemStack.novaCompound, damage)
- }
+ /**
+ * Sets the damage of the [itemStack] to [damage].
+ */
+ fun setDamage(itemStack: MojangStack, damage: Int)
- fun getDurability(itemStack: MojangStack): Int {
- return getDurability(itemStack.novaCompound)
- }
+ /**
+ * Returns the current durability of the [itemStack].
+ */
+ fun getDurability(itemStack: MojangStack): Int = maxDurability - getDamage(itemStack)
- fun setDurability(itemStack: MojangStack, durability: Int) {
- return setDurability(itemStack.novaCompound, durability)
- }
- //
+ /**
+ * Sets the durability of the [itemStack] to [durability].
+ */
+ fun setDurability(itemStack: MojangStack, durability: Int) = setDamage(itemStack, maxDurability - durability)
- //
- fun getDamage(data: NamespacedCompound): Int {
- return min(options.durability, data["nova", "damage"] ?: 0)
- }
+ /**
+ * Adds [damage] to the current damage of the [itemStack] and returns whether the item broke.
+ */
+ fun damageAndBreak(itemStack: MojangStack, damage: Int): Boolean
- fun setDamage(data: NamespacedCompound, damage: Int) {
- val coercedDamage = damage.coerceIn(0..options.durability)
- data["nova", "damage"] = coercedDamage
- }
-
- fun addDamage(data: NamespacedCompound, damage: Int) {
- setDamage(data, getDamage(data) + damage)
- }
-
- fun getDurability(data: NamespacedCompound): Int {
- return options.durability - getDamage(data)
- }
-
- fun setDurability(data: NamespacedCompound, durability: Int) {
- setDamage(data, options.durability - durability)
- }
- //
-
- override fun updatePacketItemData(data: NamespacedCompound, itemData: PacketItemData) {
- val damage = getDamage(data)
- val durability = options.durability - damage
+ class Default(
+ maxDurability: Provider,
+ damageOnAttackEntity: Provider,
+ damageOnBreakBlock: Provider,
+ repairIngredient: Provider,
+ private val affectsItemDurability: Boolean
+ ) : ItemBehavior, Damageable {
- itemData.durabilityBar = durability / options.durability.toDouble()
+ override val maxDurability by maxDurability
+ override val itemDamageOnAttackEntity by damageOnAttackEntity
+ override val itemDamageOnBreakBlock by damageOnBreakBlock
+ override val repairIngredient by repairIngredient
+
+ override fun getDamage(itemStack: BukkitStack) = getDamage(itemStack.novaCompoundOrNull)
+ override fun setDamage(itemStack: BukkitStack, damage: Int) = setDamage(itemStack.novaCompound, damage)
+ override fun getDamage(itemStack: MojangStack) = getDamage(itemStack.novaCompoundOrNull)
+ override fun setDamage(itemStack: MojangStack, damage: Int) = setDamage(itemStack.novaCompound, damage)
+
+ override fun damageAndBreak(itemStack: BukkitStack, damage: Int): Boolean {
+ val compound = itemStack.novaCompound
+ val newDamage = getDamage(compound) + damage
+ setDamage(compound, newDamage)
+ return newDamage > maxDurability
+ }
+
+ override fun damageAndBreak(itemStack: MojangStack, damage: Int): Boolean {
+ val compound = itemStack.novaCompound
+ val newDamage = getDamage(compound) + damage
+ setDamage(compound, newDamage)
+ return newDamage >= maxDurability
+ }
+
+ private fun getDamage(compound: NamespacedCompound?): Int {
+ return compound?.get("nova", "damage")?.coerceIn(0..maxDurability) ?: 0
+ }
+
+ private fun setDamage(compound: NamespacedCompound, damage: Int) {
+ compound["nova", "damage"] = damage
+ }
+
+ private fun addDamage(compound: NamespacedCompound, damage: Int) {
+ setDamage(compound, getDamage(compound) + damage)
+ }
+
+ override fun getVanillaMaterialProperties(): List {
+ return if (affectsItemDurability)
+ listOf(VanillaMaterialProperty.DAMAGEABLE)
+ else emptyList()
+ }
+
+ override fun getDefaultCompound(): NamespacedCompound {
+ val compound = NamespacedCompound()
+ compound["nova", "damage"] = 0
+ return compound
+ }
+
+ override fun updatePacketItemData(data: NamespacedCompound, itemData: PacketItemData) {
+ if (affectsItemDurability)
+ itemData.durabilityBar = (maxDurability - getDamage(data)) / maxDurability.toDouble()
+ }
- itemData.addAdvancedTooltipsLore(
- Component.translatable("item.durability", Component.text(durability), Component.text(options.durability))
- )
- }
-
- override fun getVanillaMaterialProperties(): List {
- return listOf(VanillaMaterialProperty.DAMAGEABLE)
}
- companion object : ItemBehaviorFactory() {
- override fun create(item: NovaItem) =
- Damageable(DamageableOptions.configurable(item))
+ companion object : ItemBehaviorFactory {
+
+ override fun create(item: NovaItem): Default {
+ val cfg = ConfigAccess(item)
+ return Default(
+ cfg.getEntry("durability", "max_durability"),
+ cfg.getOptionalEntry("item_damage_on_attack_entity").orElse(0),
+ cfg.getOptionalEntry("item_damage_on_break_block").orElse(0),
+ cfg.getOptionalEntry("repair_ingredient").map {
+ val list = when (it) {
+ is String -> listOf(it)
+ else -> it as List
+ }
+ RecipeDeserializer.parseRecipeChoice(list)
+ },
+ true
+ )
+ }
+
+ // -- Bukkit ItemStack --
+
+ /**
+ * Damages the given [itemStack] while respecting the unbreaking enchantment.
+ *
+ * @param itemStack The [ItemStack] to damage
+ * @param damage The amount of damage to add
+ * @param breakCallback A callback that is called if the item breaks
+ */
+ fun damageAndBreak(itemStack: BukkitStack, damage: Int, breakCallback: () -> Unit) =
+ damageAndBreak(itemStack, damage, null) { breakCallback() }
+
+ /**
+ * Damages the given [itemStack] while respecting the unbreaking enchantment, calling events and criteria triggers and incrementing stats.
+ *
+ * @param itemStack The [ItemStack] to damage
+ * @param damage The amount of damage to add
+ * @param damager The entity that damaged the item, used for events, criteria triggers and stats
+ * @param breakCallback A callback that is called if the item breaks
+ */
+ fun damageAndBreak(itemStack: BukkitStack, damage: Int, damager: T, breakCallback: (T) -> Unit) {
+ // check for creative mode
+ if (damager is BukkitPlayer && damager.gameMode == GameMode.CREATIVE)
+ return
+ // check if item is empty
+ if (itemStack.isEmpty())
+ return
+
+ val item = itemStack.type
+ val novaItem = itemStack.novaItem
+ val damageable = novaItem?.getBehaviorOrNull()
+
+ // check if the item is damageable
+ if (novaItem != null && damageable == null || item.maxDurability <= 0 || itemStack.itemMeta?.isUnbreakable == true)
+ return
+
+ // build damage based on enchantments and events
+ var actualDamage = damage
+ val unbreakingLevel = itemStack.getEnchantmentLevel(Enchantment.DURABILITY)
+ if (unbreakingLevel > 0)
+ actualDamage = calculateActualDamage(actualDamage, unbreakingLevel, Wearable.isWearable(itemStack))
+
+ if (damager is BukkitPlayer) {
+ val event = PlayerItemDamageEvent(damager, itemStack, actualDamage).also(::callEvent) // TODO: On Paper: Include original damage
+
+ if (actualDamage != event.damage || event.isCancelled)
+ damager.updateInventory()
+ if (event.isCancelled)
+ return
+ } // TODO: On Paper: Call EntityDamageItemEvent
+
+ if (actualDamage <= 0)
+ return
+
+ // damage item
+ val broken: Boolean
+ if (damageable != null) {
+ broken = damageable.damageAndBreak(itemStack, actualDamage)
+ } else {
+ val itemMeta = itemStack.itemMeta as BukkitDamageable
+ val damageValue = itemMeta.damage + actualDamage
+ broken = damageValue >= item.maxDurability
+ if (damager is BukkitPlayer) {
+ CriteriaTriggers.ITEM_DURABILITY_CHANGED.trigger(damager.serverPlayer, itemStack.nmsCopy, damageValue)
+ if (broken) damager.incrementStatistic(Statistic.BREAK_ITEM, item)
+ }
+ itemMeta.damage = damageValue
+ itemStack.itemMeta = itemMeta
+ }
+
+ if (broken) {
+ breakCallback(damager)
+
+ if (itemStack.amount == 1 && damager is BukkitPlayer)
+ callEvent(PlayerItemBreakEvent(damager, itemStack))
+
+ itemStack.amount = itemStack.amount - 1
+
+ // reset damage value
+ if (damageable != null) {
+ damageable.setDamage(itemStack, 0)
+ } else {
+ val itemMeta = itemStack.itemMeta as BukkitDamageable
+ itemMeta.damage = 0
+ itemStack.itemMeta = itemMeta
+ }
+ }
+ }
+
+ /**
+ * Checks whether this [itemStack] is of a damageable type and does not have the unbreakable tag.
+ */
+ fun isDamageable(itemStack: BukkitStack): Boolean {
+ val novaItem = itemStack.novaItem
+ if (novaItem != null)
+ return novaItem.hasBehavior() && itemStack.itemMeta?.isUnbreakable != true
+
+ return itemStack.type.maxDurability > 0 && itemStack.itemMeta?.isUnbreakable != true
+ }
+
+ /**
+ * Gets the maximum durability of this [itemStack] or 0 if it is not of a damageable type.
+ *
+ * Note that a maximum durability larger than 0 does not necessarily mean that the item is damageable,
+ * as the unbreakable tag is not checked.
+ */
+ fun getMaxDurability(itemStack: BukkitStack): Int {
+ val novaItem = itemStack.novaItem
+ if (novaItem != null)
+ return novaItem.getBehaviorOrNull()?.maxDurability ?: 0
+
+ return itemStack.type.maxDurability.toInt()
+ }
+
+ /**
+ * Gets the current damage of this [itemStack] or 0 if it is not of a damageable type.
+ */
+ fun getDamage(itemStack: BukkitStack): Int {
+ val novaItem = itemStack.novaItem
+ if (novaItem != null)
+ return novaItem.getBehaviorOrNull()?.getDamage(itemStack) ?: 0
+
+ return (itemStack.itemMeta as? BukkitDamageable)?.damage ?: 0
+ }
+
+ /**
+ * Sets the current damage of this [itemStack] if it is of a damageable type.
+ */
+ fun setDamage(itemStack: BukkitStack, damage: Int) {
+ val novaItem = itemStack.novaItem
+ if (novaItem != null) {
+ novaItem.getBehaviorOrNull()?.setDamage(itemStack, damage)
+ } else {
+ val damageable = itemStack.itemMeta as? BukkitDamageable ?: return
+ damageable.damage = damage
+ itemStack.itemMeta = damageable
+ }
+ }
+
+ /**
+ * Checks whether [repairItem] is a valid repair ingredient for [item].
+ */
+ fun isValidRepairItem(item: BukkitStack, repairItem: BukkitStack): Boolean {
+ val novaItem = item.novaItem
+ if (novaItem != null)
+ return novaItem.getBehaviorOrNull()?.repairIngredient?.test(repairItem) ?: false
+
+ return CraftMagicNumbers.getItem(item.type).isValidRepairItem(item.nmsCopy, repairItem.nmsCopy)
+ }
+
+ // -- Mojang ItemStack --
+
+ /**
+ * Damages the given [itemStack] while respecting the unbreaking enchantment.
+ *
+ * @param itemStack The [ItemStack] to damage
+ * @param damage The amount of damage to add
+ * @param breakCallback A callback that is called if the item breaks
+ */
+ fun damageAndBreak(itemStack: MojangStack, damage: Int, breakCallback: () -> Unit) =
+ damageAndBreak(itemStack, damage, null) { breakCallback() }
+
+ /**
+ * Damages the given [itemStack] while respecting the unbreaking enchantment, calling events and criteria triggers and incrementing stats.
+ *
+ * @param itemStack The [ItemStack] to damage
+ * @param damage The amount of damage to add
+ * @param damager The entity that damaged the item, used for events, criteria triggers and stats
+ * @param breakCallback A callback that is called if the item breaks
+ */
+ fun damageAndBreak(itemStack: MojangStack, damage: Int, damager: T, breakCallback: (T) -> Unit) {
+ // check for creative mode
+ if (damager is MojangPlayer && damager.abilities.instabuild)
+ return
+
+ // check if item is empty
+ if (itemStack.isEmpty)
+ return
+
+ val item = itemStack.item
+ val novaItem = itemStack.novaItem
+ val damageable = novaItem?.getBehaviorOrNull()
+
+ // check if the item is damageable
+ if (novaItem != null && damageable == null || item.maxDamage <= 0 || itemStack.tag?.getBoolean("Unbreakable") == true)
+ return
+
+ // build actual damage value based on enchantments and events
+ var actualDamage = damage
+ val unbreakingLevel = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.UNBREAKING, itemStack)
+ if (unbreakingLevel > 0)
+ actualDamage = calculateActualDamage(actualDamage, unbreakingLevel, Wearable.isWearable(itemStack))
+
+ if (damager is ServerPlayer) {
+ val event = PlayerItemDamageEvent(damager.bukkitEntity, itemStack.bukkitMirror, actualDamage).also(::callEvent) // TODO: On Paper: Include original damage
+
+ if (actualDamage != event.damage || event.isCancelled)
+ event.player.updateInventory()
+ if (event.isCancelled)
+ return
+
+ actualDamage = event.damage
+ } // TODO: On Paper: Call EntityDamageItemEvent
+
+ if (actualDamage <= 0)
+ return
+
+ // damage item
+ val broken: Boolean
+ if (damageable != null) {
+ broken = damageable.damageAndBreak(itemStack, actualDamage)
+ } else {
+ val damageValue = itemStack.damageValue + actualDamage
+ broken = damageValue >= itemStack.maxDamage
+ if (damager is ServerPlayer) {
+ CriteriaTriggers.ITEM_DURABILITY_CHANGED.trigger(damager, itemStack, damageValue)
+ if (broken) damager.awardStat(Stats.ITEM_BROKEN.get(item))
+ }
+ itemStack.damageValue = damageValue
+ }
+
+ if (broken) {
+ breakCallback(damager)
+
+ if (itemStack.count == 1 && damager is MojangPlayer)
+ CraftEventFactory.callPlayerItemBreakEvent(damager, itemStack)
+
+ itemStack.shrink(1)
+
+ // reset damage value
+ if (damageable != null) {
+ damageable.setDamage(itemStack, 0)
+ } else {
+ itemStack.damageValue = 0
+ }
+ }
+ }
+
+ /**
+ * Checks whether this [itemStack] is of a damageable type and does not have the unbreakable tag.
+ */
+ fun isDamageable(itemStack: MojangStack): Boolean {
+ val novaItem = itemStack.novaItem
+ if (novaItem != null)
+ return novaItem.hasBehavior() && itemStack.tag?.getBoolean("Unbreakable") != true
+
+ return itemStack.item.canBeDepleted()
+ }
+
+ /**
+ * Gets the maximum durability of this [itemStack] or 0 if it is not of a damageable type.
+ *
+ * Note that a maximum durability larger than 0 does not necessarily mean that the item is damageable,
+ * as the unbreakable tag is not checked.
+ */
+ fun getMaxDurability(itemStack: MojangStack): Int {
+ val novaItem = itemStack.novaItem
+ if (novaItem != null)
+ return novaItem.getBehaviorOrNull()?.maxDurability ?: 0
+
+ return itemStack.maxDamage
+ }
+
+ /**
+ * Gets the current damage of this [itemStack] or 0 if it is not of a damageable type.
+ */
+ fun getDamage(itemStack: MojangStack): Int {
+ val novaItem = itemStack.novaItem
+ if (novaItem != null)
+ return novaItem.getBehaviorOrNull()?.getDamage(itemStack) ?: 0
+
+ return itemStack.damageValue
+ }
+
+ /**
+ * Sets the current damage of this [itemStack] if it is of a damageable type.
+ */
+ fun setDamage(itemStack: MojangStack, damage: Int) {
+ val novaItem = itemStack.novaItem
+ if (novaItem != null) {
+ novaItem.getBehaviorOrNull()?.setDamage(itemStack, damage)
+ } else {
+ itemStack.damageValue = damage
+ }
+ }
+
+ /**
+ * Checks whether [repairItem] is a valid repair ingredient for [item].
+ */
+ fun isValidRepairItem(item: MojangStack, repairItem: MojangStack): Boolean {
+ val novaItem = item.novaItem
+ if (novaItem != null)
+ return novaItem.getBehaviorOrNull()?.repairIngredient?.test(repairItem.bukkitMirror) ?: false
+
+ return item.item.isValidRepairItem(item, repairItem)
+ }
+
+ // -- Misc --
+
+ private fun calculateActualDamage(damage: Int, unbreakingLevel: Int, isArmor: Boolean): Int {
+ var actualDamage = 0
+ repeat(damage) {
+ if (isArmor) {
+ if (Random.nextFloat() > 0.6f)
+ actualDamage++
+ } else {
+ if (Random.nextInt(unbreakingLevel + 1) == 0)
+ actualDamage++
+ }
+ }
+
+ return actualDamage
+ }
+
}
}
\ No newline at end of file
diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Enchantable.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Enchantable.kt
index b0495988d7..9763ae0c2a 100644
--- a/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Enchantable.kt
+++ b/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Enchantable.kt
@@ -3,35 +3,185 @@ package xyz.xenondevs.nova.item.behavior
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.ListTag
import net.minecraft.nbt.StringTag
+import org.bukkit.inventory.meta.EnchantmentStorageMeta
import xyz.xenondevs.commons.collections.isNotNullOrEmpty
+import xyz.xenondevs.commons.provider.Provider
+import xyz.xenondevs.commons.provider.immutable.map
+import xyz.xenondevs.commons.provider.immutable.provider
+import xyz.xenondevs.nova.data.config.ConfigAccess
import xyz.xenondevs.nova.data.serialization.cbf.NamespacedCompound
import xyz.xenondevs.nova.item.NovaItem
import xyz.xenondevs.nova.item.enchantment.Enchantment
+import xyz.xenondevs.nova.item.enchantment.EnchantmentCategory
import xyz.xenondevs.nova.item.enchantment.NovaEnchantment
import xyz.xenondevs.nova.item.enchantment.VanillaEnchantment
-import xyz.xenondevs.nova.item.options.EnchantableOptions
import xyz.xenondevs.nova.registry.NovaRegistries
import xyz.xenondevs.nova.util.data.getOrNull
import xyz.xenondevs.nova.util.data.getOrPut
import xyz.xenondevs.nova.util.get
+import xyz.xenondevs.nova.util.getOrThrow
import xyz.xenondevs.nova.util.item.novaCompound
import xyz.xenondevs.nova.util.item.novaCompoundOrNull
import xyz.xenondevs.nova.util.item.novaItem
+import xyz.xenondevs.nova.util.nmsCopy
import net.minecraft.world.item.ItemStack as MojangStack
+import org.bukkit.enchantments.Enchantment as BukkitEnchantment
+import org.bukkit.inventory.ItemStack as BukkitStack
private const val ENCHANTMENTS_CBF = "enchantments"
private const val ENCHANTMENTS_NBT = "Enchantments"
private const val STORED_ENCHANTMENTS_CBF = "stored_enchantments"
private const val STORED_ENCHANTMENTS_NBT = "StoredEnchantments"
-class Enchantable(val options: EnchantableOptions) : ItemBehavior() {
+fun Enchantable(
+ enchantmentValue: Int,
+ enchantmentCategories: List
+) = Enchantable.Default(provider(enchantmentValue), provider(enchantmentCategories))
+
+/**
+ * Allows items to be enchanted.
+ */
+interface Enchantable {
+
+ /**
+ * The enchantment value of this item.
+ * Items with a higher enchantment value have a higher chance of getting more secondary enchantments
+ * when enchanted in the enchanting table.
+ *
+ * As an example, these are the vanilla enchantment values depending on the material:
+ *
+ * * Wood: 15
+ * * Stone: 5
+ * * Iron: 14
+ * * Diamond: 10
+ * * Gold: 22
+ * * Netherite: 15
+ */
+ val enchantmentValue: Int
+
+ /**
+ * A list of enchantment categories that can be applied to this item.
+ */
+ val enchantmentCategories: List
+
+ class Default(
+ enchantmentValue: Provider,
+ enchantmentCategories: Provider>,
+ ) : ItemBehavior, Enchantable {
+
+ override val enchantmentValue by enchantmentValue
+ override val enchantmentCategories by enchantmentCategories
+
+ }
- companion object : ItemBehaviorFactory() {
+ companion object : ItemBehaviorFactory {
+
+ override fun create(item: NovaItem): Default {
+ val cfg = ConfigAccess(item)
+ return Default(
+ cfg.getEntry("enchantment_value"),
+ cfg.getEntry>("enchantment_categories")
+ .map { list -> list.map { NovaRegistries.ENCHANTMENT_CATEGORY.getOrThrow(it) } }
+ )
+ }
+
+ // -- Bukkit ItemStack --
+
+ fun isEnchantable(itemStack: BukkitStack): Boolean =
+ isEnchantable(itemStack.nmsCopy)
+
+ fun isEnchanted(itemStack: BukkitStack): Boolean =
+ isEnchanted(itemStack.nmsCopy)
+
+ fun hasStoredEnchantments(itemStack: BukkitStack): Boolean =
+ hasStoredEnchantments(itemStack.nmsCopy)
+
+ fun getEnchantments(itemStack: BukkitStack): Map =
+ getEnchantments(itemStack.nmsCopy)
+
+ fun getStoredEnchantments(itemStack: BukkitStack): Map =
+ getStoredEnchantments(itemStack.nmsCopy)
+
+ fun setEnchantments(itemStack: BukkitStack, enchantments: Map) {
+ // clear vanilla enchants
+ for ((enchantment, _) in itemStack.enchantments)
+ itemStack.removeEnchantment(enchantment)
+
+ setEnchantments(ENCHANTMENTS_CBF, itemStack::addUnsafeEnchantment, itemStack, enchantments)
+ }
+
+ fun setStoredEnchantments(itemStack: BukkitStack, enchantments: Map) {
+ val meta = itemStack.itemMeta as? EnchantmentStorageMeta ?: return
+ // clear vanilla enchants
+ for ((enchantment, _) in meta.storedEnchants)
+ meta.removeStoredEnchant(enchantment)
+
+ setEnchantments(STORED_ENCHANTMENTS_CBF, { ench, lvl -> meta.addStoredEnchant(ench, lvl, true) }, itemStack, enchantments)
+ }
+
+ private fun setEnchantments(cbfName: String, addVanillaEnchantment: (BukkitEnchantment, Int) -> Unit, itemStack: BukkitStack, enchantments: Map) {
+ val novaEnchantmentsMap = HashMap()
+ for ((enchantment, level) in enchantments) {
+ if (enchantment is VanillaEnchantment) {
+ addVanillaEnchantment(Enchantment.asBukkitEnchantment(enchantment), level)
+ } else {
+ novaEnchantmentsMap[enchantment] = level
+ }
+ }
+
+ if (novaEnchantmentsMap.isNotEmpty()) {
+ itemStack.novaCompound["nova", cbfName] = novaEnchantmentsMap
+ } else {
+ itemStack.novaCompoundOrNull?.remove("nova", cbfName)
+ }
+ }
+
+ fun addEnchantment(itemStack: BukkitStack, enchantment: Enchantment, level: Int) =
+ addEnchantment(ENCHANTMENTS_CBF, itemStack::addUnsafeEnchantment, itemStack, enchantment, level)
+
+ fun addStoredEnchantment(itemStack: BukkitStack, enchantment: Enchantment, level: Int) {
+ val meta = itemStack.itemMeta as? EnchantmentStorageMeta ?: return
+ addEnchantment(STORED_ENCHANTMENTS_CBF, { ench, lvl -> meta.addStoredEnchant(ench, lvl, true) }, itemStack, enchantment, level)
+ }
+
+ private fun addEnchantment(cbfName: String, addVanillaEnchantment: (BukkitEnchantment, Int) -> Unit, itemStack: BukkitStack, enchantment: Enchantment, level: Int) {
+ if (enchantment is VanillaEnchantment) {
+ addVanillaEnchantment(Enchantment.asBukkitEnchantment(enchantment), level)
+ } else {
+ itemStack.novaCompound.getEnchantments(cbfName)[enchantment] = level
+ }
+ }
+
+ fun removeEnchantment(itemStack: BukkitStack, enchantment: Enchantment) =
+ removeEnchantment(ENCHANTMENTS_CBF, itemStack::removeEnchantment, itemStack, enchantment)
+
+ fun removeStoredEnchantment(itemStack: BukkitStack, enchantment: Enchantment) {
+ val meta = itemStack.itemMeta as? EnchantmentStorageMeta ?: return
+ removeEnchantment(STORED_ENCHANTMENTS_CBF, meta::removeStoredEnchant, itemStack, enchantment)
+ }
+
+ private fun removeEnchantment(cbfName: String, removeVanillaEnchantment: (BukkitEnchantment) -> Unit, itemStack: BukkitStack, enchantment: Enchantment) {
+ if (enchantment is VanillaEnchantment) {
+ removeVanillaEnchantment(Enchantment.asBukkitEnchantment(enchantment))
+ } else {
+ itemStack.novaCompoundOrNull?.getEnchantmentsOrNull(cbfName)?.remove(enchantment)
+ }
+ }
+
+ fun removeAllEnchantments(itemStack: BukkitStack) {
+ for ((enchantment, _) in itemStack.enchantments)
+ itemStack.removeEnchantment(enchantment)
+ itemStack.novaCompoundOrNull?.remove("nova", ENCHANTMENTS_CBF)
+ }
- override fun create(item: NovaItem) =
- Enchantable(EnchantableOptions.configurable(item))
+ fun removeAllStoredEnchantments(itemStack: BukkitStack) {
+ val meta = itemStack.itemMeta as? EnchantmentStorageMeta ?: return
+ for ((enchantment, _) in meta.enchants)
+ meta.removeStoredEnchant(enchantment)
+ itemStack.novaCompoundOrNull?.remove("nova", STORED_ENCHANTMENTS_CBF)
+ }
- // TODO: Bukkit methods
+ // -- Mojang ItemStack --
@JvmStatic
fun isEnchantable(itemStack: MojangStack): Boolean {
@@ -53,13 +203,13 @@ class Enchantable(val options: EnchantableOptions) : ItemBehavior() {
return itemStack.tag?.getOrNull(nbtName).isNotNullOrEmpty()
}
- fun getEnchantments(itemStack: MojangStack): MutableMap =
+ fun getEnchantments(itemStack: MojangStack): Map =
getEnchantments(ENCHANTMENTS_CBF, ENCHANTMENTS_NBT, itemStack)
- fun getStoredEnchantments(itemStack: MojangStack): MutableMap =
+ fun getStoredEnchantments(itemStack: MojangStack): Map =
getEnchantments(STORED_ENCHANTMENTS_CBF, STORED_ENCHANTMENTS_NBT, itemStack)
- private fun getEnchantments(cbfName: String, nbtName: String, itemStack: MojangStack): MutableMap {
+ private fun getEnchantments(cbfName: String, nbtName: String, itemStack: MojangStack): Map {
val enchantments = HashMap()
itemStack.tag?.getOrNull(nbtName)?.asSequence()
?.filterIsInstance()
@@ -73,13 +223,13 @@ class Enchantable(val options: EnchantableOptions) : ItemBehavior() {
return enchantments
}
- fun setEnchantments(itemStack: MojangStack, enchantments: MutableMap) =
+ fun setEnchantments(itemStack: MojangStack, enchantments: Map) =
setEnchantments(ENCHANTMENTS_CBF, ENCHANTMENTS_NBT, itemStack, enchantments)
- fun setStoredEnchantments(itemStack: MojangStack, enchantments: MutableMap) =
+ fun setStoredEnchantments(itemStack: MojangStack, enchantments: Map) =
setEnchantments(STORED_ENCHANTMENTS_CBF, STORED_ENCHANTMENTS_NBT, itemStack, enchantments)
- private fun setEnchantments(cbfName: String, nbtName: String, itemStack: MojangStack, enchantments: MutableMap) {
+ private fun setEnchantments(cbfName: String, nbtName: String, itemStack: MojangStack, enchantments: Map) {
val vanillaEnchantmentsTag = ListTag()
val novaEnchantmentsMap = HashMap()
@@ -95,11 +245,17 @@ class Enchantable(val options: EnchantableOptions) : ItemBehavior() {
}
}
- if (vanillaEnchantmentsTag.isNotEmpty())
+ if (vanillaEnchantmentsTag.isNotEmpty()) {
itemStack.orCreateTag.put(nbtName, vanillaEnchantmentsTag)
+ } else {
+ itemStack.tag?.remove(nbtName)
+ }
- if (novaEnchantmentsMap.isNotEmpty())
+ if (novaEnchantmentsMap.isNotEmpty()) {
itemStack.novaCompound["nova", cbfName] = novaEnchantmentsMap
+ } else {
+ itemStack.novaCompoundOrNull?.remove("nova", cbfName)
+ }
}
fun addEnchantment(itemStack: MojangStack, enchantment: Enchantment, level: Int) =
@@ -149,18 +305,14 @@ class Enchantable(val options: EnchantableOptions) : ItemBehavior() {
itemStack.novaCompoundOrNull?.remove("nova", cbfName)
}
+ // -- Misc --
+
private fun NamespacedCompound.getEnchantmentsOrNull(name: String): MutableMap? =
get("nova", name)
private fun NamespacedCompound.getEnchantments(name: String): MutableMap =
getOrPut("nova", name, ::HashMap)
- private fun CompoundTag.getEnchantmentsOrNull(name: String): ListTag? =
- getOrNull(name)
-
- private fun CompoundTag.getEnchantments(name: String): ListTag =
- getOrPut(name, ::ListTag)
-
}
}
\ No newline at end of file
diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Extinguishing.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Extinguishing.kt
index 9ff50da008..c2fa94eeb6 100644
--- a/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Extinguishing.kt
+++ b/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Extinguishing.kt
@@ -12,7 +12,7 @@ import org.bukkit.event.block.Action
import org.bukkit.event.player.PlayerInteractEvent
import org.bukkit.inventory.ItemStack
import xyz.xenondevs.nmsutils.particle.particle
-import xyz.xenondevs.nova.util.item.damageItemInHand
+import xyz.xenondevs.nova.util.damageItemInHand
import xyz.xenondevs.nova.util.nmsState
import xyz.xenondevs.nova.util.runTaskLater
import xyz.xenondevs.nova.util.sendTo
@@ -22,9 +22,12 @@ import xyz.xenondevs.nova.util.swingHand
import xyz.xenondevs.nova.world.pos
import kotlin.random.Random
-private const val EXTINGuiSH_CAMPFIRE_LEVEL_EVENT = 1009
+private const val EXTINGUISH_CAMPFIRE_LEVEL_EVENT = 1009
-object Extinguishing : ItemBehavior() {
+/**
+ * Allows items to extinguish campfires.
+ */
+object Extinguishing : ItemBehavior {
override fun handleInteract(player: Player, itemStack: ItemStack, action: Action, event: PlayerInteractEvent) {
if (action == Action.RIGHT_CLICK_BLOCK) {
@@ -45,7 +48,7 @@ object Extinguishing : ItemBehavior() {
val newState = state.setValue(CampfireBlock.LIT, false)
level.setBlock(pos, newState, 11)
- level.levelEvent(null, EXTINGuiSH_CAMPFIRE_LEVEL_EVENT, pos, 0)
+ level.levelEvent(null, EXTINGUISH_CAMPFIRE_LEVEL_EVENT, pos, 0)
level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(serverPlayer, newState))
val hand = event.hand!!
diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/FireResistant.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/FireResistant.kt
index 91266e93ec..291541a3b1 100644
--- a/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/FireResistant.kt
+++ b/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/FireResistant.kt
@@ -1,16 +1,14 @@
package xyz.xenondevs.nova.item.behavior
-import xyz.xenondevs.nova.item.NovaItem
import xyz.xenondevs.nova.item.vanilla.VanillaMaterialProperty
-class FireResistant : ItemBehavior() {
+/**
+ * Makes items fire-resistant.
+ */
+object FireResistant : ItemBehavior {
override fun getVanillaMaterialProperties(): List {
return listOf(VanillaMaterialProperty.FIRE_RESISTANT)
}
- companion object : ItemBehaviorFactory() {
- override fun create(item: NovaItem) = FireResistant()
- }
-
}
\ No newline at end of file
diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Flattening.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Flattening.kt
index f4b3649643..f2806e6501 100644
--- a/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Flattening.kt
+++ b/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Flattening.kt
@@ -7,7 +7,7 @@ import org.bukkit.entity.Player
import org.bukkit.event.block.Action
import org.bukkit.event.player.PlayerInteractEvent
import org.bukkit.inventory.ItemStack
-import xyz.xenondevs.nova.util.item.damageItemInMainHand
+import xyz.xenondevs.nova.util.damageItemInMainHand
import xyz.xenondevs.nova.util.playSoundNearby
import xyz.xenondevs.nova.util.runTaskLater
import xyz.xenondevs.nova.util.swingHand
@@ -21,7 +21,10 @@ private val FLATTENABLES: Set = hashSetOf(
Material.ROOTED_DIRT
)
-object Flattening : ItemBehavior() {
+/**
+ * Allows items to flatten the ground.
+ */
+object Flattening : ItemBehavior {
override fun handleInteract(player: Player, itemStack: ItemStack, action: Action, event: PlayerInteractEvent) {
if (action == Action.RIGHT_CLICK_BLOCK) {
diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Fuel.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Fuel.kt
index 444830b1fa..1a573f8e68 100644
--- a/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Fuel.kt
+++ b/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Fuel.kt
@@ -1,16 +1,84 @@
package xyz.xenondevs.nova.item.behavior
+import net.minecraft.world.item.Item
+import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity
+import org.bukkit.Material
+import org.bukkit.craftbukkit.v1_20_R1.util.CraftMagicNumbers
+import xyz.xenondevs.commons.collections.enumMap
+import xyz.xenondevs.commons.provider.Provider
+import xyz.xenondevs.commons.provider.immutable.provider
+import xyz.xenondevs.nova.data.config.ConfigAccess
import xyz.xenondevs.nova.item.NovaItem
-import xyz.xenondevs.nova.item.options.FuelOptions
+import xyz.xenondevs.nova.util.item.novaItem
+import net.minecraft.world.item.ItemStack as MojangStack
+import org.bukkit.inventory.ItemStack as BukkitStack
-class Fuel(val options: FuelOptions) : ItemBehavior() {
+fun Fuel(burnTime: Int) = Fuel.Default(provider(burnTime))
+
+/**
+ * Allows items to be used as fuel in furnaces.
+ */
+interface Fuel {
+
+ /**
+ * The burn time of this fuel, in ticks.
+ */
+ val burnTime: Int
+
+ class Default(
+ burnTime: Provider
+ ): ItemBehavior, Fuel {
+ override val burnTime by burnTime
+ }
- companion object : ItemBehaviorFactory() {
+ companion object : ItemBehaviorFactory {
- override fun create(item: NovaItem): Fuel {
- return Fuel(FuelOptions.configurable(item))
+ private val NMS_VANILLA_FUELS: Map- = AbstractFurnaceBlockEntity.getFuel()
+ private val VANILLA_FUELS: Map = NMS_VANILLA_FUELS
+ .mapKeysTo(enumMap()) { (item, _) -> CraftMagicNumbers.getMaterial(item) }
+
+ override fun create(item: NovaItem): Default {
+ val cfg = ConfigAccess(item)
+ return Default(cfg.getEntry("burn_time"))
}
-
+
+ fun isFuel(material: Material): Boolean = material in VANILLA_FUELS
+ fun getBurnTime(material: Material): Int? = VANILLA_FUELS[material]
+
+ fun isFuel(itemStack: BukkitStack): Boolean {
+ val novaItem = itemStack.novaItem
+ if (novaItem != null) {
+ return novaItem.hasBehavior(Fuel::class)
+ }
+
+ return itemStack.type in VANILLA_FUELS
+ }
+
+ fun getBurnTime(itemStack: BukkitStack): Int? {
+ val novaItem = itemStack.novaItem
+ if (novaItem != null)
+ return novaItem.getBehaviorOrNull(Fuel::class)?.burnTime
+
+ return getBurnTime(itemStack.type)
+ }
+
+ fun isFuel(itemStack: MojangStack): Boolean {
+ val novaItem = itemStack.novaItem
+ if (novaItem != null)
+ return novaItem.hasBehavior()
+
+ return itemStack.item in NMS_VANILLA_FUELS
+ }
+
+ fun getBurnTime(itemStack: MojangStack): Int? {
+ val novaItem = itemStack.novaItem
+ if (novaItem != null) {
+ return novaItem.getBehaviorOrNull(Fuel::class)?.burnTime
+ }
+
+ return NMS_VANILLA_FUELS[itemStack.item]
+ }
+
}
}
\ No newline at end of file
diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/ItemBehavior.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/ItemBehavior.kt
index f15f0b6e58..e11bd6115c 100644
--- a/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/ItemBehavior.kt
+++ b/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/ItemBehavior.kt
@@ -21,49 +21,34 @@ import xyz.xenondevs.nova.item.vanilla.VanillaMaterialProperty
import xyz.xenondevs.nova.player.equipment.ArmorEquipEvent
import xyz.xenondevs.nova.world.block.event.BlockBreakActionEvent
-abstract class ItemBehavior : ItemBehaviorHolder() {
-
- lateinit var item: NovaItem
- internal set
-
- open fun getVanillaMaterialProperties(): List = emptyList()
- open fun getAttributeModifiers(): List = emptyList()
-
- open fun handleInteract(player: Player, itemStack: ItemStack, action: Action, event: PlayerInteractEvent) = Unit
- open fun handleEntityInteract(player: Player, itemStack: ItemStack, clicked: Entity, event: PlayerInteractAtEntityEvent) = Unit
- open fun handleAttackEntity(player: Player, itemStack: ItemStack, attacked: Entity, event: EntityDamageByEntityEvent) = Unit
- open fun handleBreakBlock(player: Player, itemStack: ItemStack, event: BlockBreakEvent) = Unit
- open fun handleDamage(player: Player, itemStack: ItemStack, event: PlayerItemDamageEvent) = Unit
- open fun handleBreak(player: Player, itemStack: ItemStack, event: PlayerItemBreakEvent) = Unit
- open fun handleEquip(player: Player, itemStack: ItemStack, equipped: Boolean, event: ArmorEquipEvent) = Unit
- open fun handleInventoryClick(player: Player, itemStack: ItemStack, event: InventoryClickEvent) = Unit
- open fun handleInventoryClickOnCursor(player: Player, itemStack: ItemStack, event: InventoryClickEvent) = Unit
- open fun handleInventoryHotbarSwap(player: Player, itemStack: ItemStack, event: InventoryClickEvent) = Unit
- open fun handleBlockBreakAction(player: Player, itemStack: ItemStack, event: BlockBreakActionEvent) = Unit
- open fun handleRelease(player: Player, itemStack: ItemStack, event: ServerboundPlayerActionPacketEvent) = Unit
+sealed interface ItemBehaviorHolder
+
+interface ItemBehavior : ItemBehaviorHolder {
- open fun modifyItemBuilder(itemBuilder: ItemBuilder): ItemBuilder = itemBuilder
- open fun updatePacketItemData(data: NamespacedCompound, itemData: PacketItemData) = Unit
+ fun getVanillaMaterialProperties(): List = emptyList()
+ fun getAttributeModifiers(): List = emptyList()
+ fun getDefaultCompound(): NamespacedCompound = NamespacedCompound()
- final override fun get(item: NovaItem): ItemBehavior {
- setMaterial(item)
- return this
- }
+ fun handleInteract(player: Player, itemStack: ItemStack, action: Action, event: PlayerInteractEvent) = Unit
+ fun handleEntityInteract(player: Player, itemStack: ItemStack, clicked: Entity, event: PlayerInteractAtEntityEvent) = Unit
+ fun handleAttackEntity(player: Player, itemStack: ItemStack, attacked: Entity, event: EntityDamageByEntityEvent) = Unit
+ fun handleBreakBlock(player: Player, itemStack: ItemStack, event: BlockBreakEvent) = Unit
+ fun handleDamage(player: Player, itemStack: ItemStack, event: PlayerItemDamageEvent) = Unit
+ fun handleBreak(player: Player, itemStack: ItemStack, event: PlayerItemBreakEvent) = Unit
+ fun handleEquip(player: Player, itemStack: ItemStack, equipped: Boolean, event: ArmorEquipEvent) = Unit
+ fun handleInventoryClick(player: Player, itemStack: ItemStack, event: InventoryClickEvent) = Unit
+ fun handleInventoryClickOnCursor(player: Player, itemStack: ItemStack, event: InventoryClickEvent) = Unit
+ fun handleInventoryHotbarSwap(player: Player, itemStack: ItemStack, event: InventoryClickEvent) = Unit
+ fun handleBlockBreakAction(player: Player, itemStack: ItemStack, event: BlockBreakActionEvent) = Unit
+ fun handleRelease(player: Player, itemStack: ItemStack, event: ServerboundPlayerActionPacketEvent) = Unit
- internal fun setMaterial(item: NovaItem) {
- if (this::item.isInitialized)
- throw IllegalStateException("The same item behavior instance cannot be used for multiple materials")
-
- this.item = item
- }
+ @Suppress("DeprecatedCallableAddReplaceWith")
+ @Deprecated("Use getDefaultCompound or updatePacketItemData instead")
+ fun modifyItemBuilder(itemBuilder: ItemBuilder): ItemBuilder = itemBuilder
+ fun updatePacketItemData(data: NamespacedCompound, itemData: PacketItemData) = Unit
}
-abstract class ItemBehaviorFactory : ItemBehaviorHolder() {
- abstract fun create(item: NovaItem): T
- final override fun get(item: NovaItem) = create(item).apply { setMaterial(item) }
-}
-
-abstract class ItemBehaviorHolder internal constructor() {
- internal abstract fun get(item: NovaItem): T
+interface ItemBehaviorFactory : ItemBehaviorHolder {
+ fun create(item: NovaItem): T
}
\ No newline at end of file
diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Stripping.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Stripping.kt
index 25b767aff3..5040f16798 100644
--- a/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Stripping.kt
+++ b/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Stripping.kt
@@ -16,7 +16,6 @@ import org.bukkit.event.block.Action
import org.bukkit.event.player.PlayerInteractEvent
import org.bukkit.inventory.ItemStack
import xyz.xenondevs.nova.util.interactionHand
-import xyz.xenondevs.nova.util.item.DamageableUtils
import xyz.xenondevs.nova.util.nmsState
import xyz.xenondevs.nova.util.runTaskLater
import xyz.xenondevs.nova.util.serverLevel
@@ -49,7 +48,10 @@ private val STRIPPABLES: Map = mapOf(
Blocks.CHERRY_LOG to Blocks.STRIPPED_CHERRY_LOG
)
-object Stripping : ItemBehavior() {
+/**
+ * Allows items to strip blocks.
+ */
+object Stripping : ItemBehavior {
override fun handleInteract(player: Player, itemStack: ItemStack, action: Action, event: PlayerInteractEvent) {
if (action == Action.RIGHT_CLICK_BLOCK) {
@@ -71,7 +73,7 @@ object Stripping : ItemBehavior() {
fun setNewState(newState: BlockState) {
runTaskLater(1) { player.swing(hand, true) }
level.setBlock(pos, newState, 11)
- DamageableUtils.damageAndBreakItem(itemStack, 1, player)
+ Damageable.damageAndBreak(itemStack, 1, player) { player.broadcastBreakEvent(hand) }
}
val stripped = STRIPPABLES[block]?.defaultBlockState()?.apply { setValue(RotatedPillarBlock.AXIS, state.getValue(RotatedPillarBlock.AXIS)) }
diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Tilling.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Tilling.kt
index ec02ddac76..76c4aaf2c3 100644
--- a/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Tilling.kt
+++ b/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Tilling.kt
@@ -15,7 +15,6 @@ import org.bukkit.event.player.PlayerInteractEvent
import org.bukkit.inventory.ItemStack
import xyz.xenondevs.nova.util.above
import xyz.xenondevs.nova.util.interactionHand
-import xyz.xenondevs.nova.util.item.DamageableUtils
import xyz.xenondevs.nova.util.nmsDirection
import xyz.xenondevs.nova.util.nmsState
import xyz.xenondevs.nova.util.runTaskLater
@@ -36,7 +35,10 @@ private fun onlyIfAirAbove(event: PlayerInteractEvent): Boolean {
return event.blockFace != BlockFace.DOWN && event.clickedBlock!!.above.type.isAir
}
-object Tilling : ItemBehavior() {
+/**
+ * Allows items to till the ground.
+ */
+object Tilling : ItemBehavior {
override fun handleInteract(player: Player, itemStack: ItemStack, action: Action, event: PlayerInteractEvent) {
if (action == Action.RIGHT_CLICK_BLOCK) {
@@ -58,7 +60,7 @@ object Tilling : ItemBehavior() {
// drop items
drops.forEach { Block.popResourceFromFace(level, pos, event.blockFace.nmsDirection, MojangStack(it)) }
// damage item
- DamageableUtils.damageAndBreakItem(serverPlayer.getItemInHand(interactionHand), 1, serverPlayer)
+ Damageable.damageAndBreak(serverPlayer.getItemInHand(interactionHand), 1, serverPlayer) { serverPlayer.broadcastBreakEvent(interactionHand) }
// swing hand
runTaskLater(1) { serverPlayer.swing(interactionHand, true) }
}
diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Tool.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Tool.kt
index e38c37a3a1..290f22aa7b 100644
--- a/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Tool.kt
+++ b/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Tool.kt
@@ -3,64 +3,170 @@ package xyz.xenondevs.nova.item.behavior
import net.minecraft.world.entity.EquipmentSlot
import net.minecraft.world.entity.ai.attributes.AttributeModifier.Operation
import net.minecraft.world.entity.ai.attributes.Attributes
+import xyz.xenondevs.commons.provider.Provider
+import xyz.xenondevs.commons.provider.immutable.map
+import xyz.xenondevs.commons.provider.immutable.orElse
+import xyz.xenondevs.commons.provider.immutable.provider
+import xyz.xenondevs.nova.data.config.ConfigAccess
import xyz.xenondevs.nova.item.NovaItem
-import xyz.xenondevs.nova.item.options.ToolOptions
+import xyz.xenondevs.nova.item.tool.ToolCategory
+import xyz.xenondevs.nova.item.tool.ToolTier
+import xyz.xenondevs.nova.item.tool.VanillaToolCategories
import xyz.xenondevs.nova.item.vanilla.AttributeModifier
import xyz.xenondevs.nova.item.vanilla.VanillaMaterialProperty
+import xyz.xenondevs.nova.registry.NovaRegistries
+import xyz.xenondevs.nova.util.get
import java.util.*
private const val PLAYER_ATTACK_SPEED = 4.0
private const val PLAYER_ATTACK_DAMAGE = 1.0
-class Tool(val options: ToolOptions) : ItemBehavior() {
+fun Tool(
+ tier: ToolTier,
+ category: ToolCategory,
+ breakSpeed: Double,
+ attackDamage: Double?,
+ attackSpeed: Double?,
+ knockbackBonus: Int,
+ canSweepAttack: Boolean = false,
+ canBreakBlocksInCreative: Boolean = category != VanillaToolCategories.SWORD
+) = Tool.Default(
+ provider(tier),
+ provider(category),
+ provider(breakSpeed),
+ provider(attackDamage),
+ provider(attackSpeed),
+ provider(knockbackBonus),
+ provider(canSweepAttack),
+ provider(canBreakBlocksInCreative)
+)
+
+/**
+ * Allows items to be used as tools, by specifying break and attack properties.
+ */
+sealed interface Tool {
- override fun getVanillaMaterialProperties(): List {
- val properties = ArrayList()
- properties += VanillaMaterialProperty.DAMAGEABLE
- if (!options.canBreakBlocksInCreative)
- properties += VanillaMaterialProperty.CREATIVE_NON_BLOCK_BREAKING
- return properties
- }
+ /**
+ * The [ToolTier] of this tool.
+ */
+ val tier: ToolTier
+
+ /**
+ * The [ToolCategory] of this tool.
+ */
+ val category: ToolCategory
+
+ /**
+ * The break speed of this tool.
+ */
+ val breakSpeed: Double
+
+ /**
+ * The attack damage of this tool.
+ */
+ val attackDamage: Double?
+
+ /**
+ * The attack speed of this tool.
+ */
+ val attackSpeed: Double?
+
+ /**
+ * The knockback bonus of this tool when attacking.
+ */
+ val knockbackBonus: Int
+
+ /**
+ * Whether this tool can perform a sweep attack.
+ */
+ val canSweepAttack: Boolean
- override fun getAttributeModifiers(): List {
- val modifiers = ArrayList()
+ /**
+ * Whether this tool can break blocks in creative mode.
+ */
+ val canBreakBlocksInCreative: Boolean
+
+ class Default(
+ tier: Provider,
+ category: Provider,
+ breakSpeed: Provider,
+ attackDamage: Provider,
+ attackSpeed: Provider,
+ knockbackBonus: Provider,
+ canSweepAttack: Provider,
+ canBreakBlocksInCreative: Provider
+ ) : ItemBehavior, Tool {
- val attackDamage = options.attackDamage
- if (attackDamage != null) {
- modifiers += AttributeModifier(
- BASE_ATTACK_DAMAGE_UUID,
- "Nova Attack Damage",
- Attributes.ATTACK_DAMAGE,
- Operation.ADDITION,
- options.attackDamage!! - PLAYER_ATTACK_DAMAGE,
- true,
- EquipmentSlot.MAINHAND
- )
+ override val tier by tier
+ override val category by category
+ override val breakSpeed by breakSpeed
+ override val attackDamage by attackDamage
+ override val attackSpeed by attackSpeed
+ override val knockbackBonus by knockbackBonus
+ override val canSweepAttack by canSweepAttack
+ override val canBreakBlocksInCreative by canBreakBlocksInCreative
+
+ override fun getVanillaMaterialProperties(): List {
+ val properties = ArrayList()
+ properties += VanillaMaterialProperty.DAMAGEABLE
+ if (!canBreakBlocksInCreative)
+ properties += VanillaMaterialProperty.CREATIVE_NON_BLOCK_BREAKING
+ return properties
}
- val attackSpeed = options.attackSpeed
- if (attackSpeed != null) {
- modifiers += AttributeModifier(
- BASE_ATTACK_SPEED_UUID,
- "Nova Attack Speed",
- Attributes.ATTACK_SPEED,
- Operation.ADDITION,
- options.attackSpeed!! - PLAYER_ATTACK_SPEED,
- true,
- EquipmentSlot.MAINHAND
- )
+ override fun getAttributeModifiers(): List {
+ val modifiers = ArrayList()
+
+ val attackDamage = attackDamage
+ if (attackDamage != null) {
+ modifiers += AttributeModifier(
+ BASE_ATTACK_DAMAGE_UUID,
+ "Nova Attack Damage",
+ Attributes.ATTACK_DAMAGE,
+ Operation.ADDITION,
+ attackDamage - PLAYER_ATTACK_DAMAGE,
+ true,
+ EquipmentSlot.MAINHAND
+ )
+ }
+
+ val attackSpeed = attackSpeed
+ if (attackSpeed != null) {
+ modifiers += AttributeModifier(
+ BASE_ATTACK_SPEED_UUID,
+ "Nova Attack Speed",
+ Attributes.ATTACK_SPEED,
+ Operation.ADDITION,
+ attackSpeed - PLAYER_ATTACK_SPEED,
+ true,
+ EquipmentSlot.MAINHAND
+ )
+ }
+
+ return modifiers
}
- return modifiers
}
- companion object : ItemBehaviorFactory() {
+ companion object : ItemBehaviorFactory {
val BASE_ATTACK_DAMAGE_UUID: UUID = UUID.fromString("CB3F55D3-645C-4F38-A497-9C13A33DB5CF")
val BASE_ATTACK_SPEED_UUID: UUID = UUID.fromString("FA233E1C-4180-4865-B01B-BCCE9785ACA3")
- override fun create(item: NovaItem) =
- Tool(ToolOptions.configurable(item))
+ override fun create(item: NovaItem): Default {
+ val cfg = ConfigAccess(item)
+ return Default(
+ cfg.getEntry("tool_tier", "tool_level").map { NovaRegistries.TOOL_TIER[it]!! },
+ cfg.getEntry("tool_category").map { NovaRegistries.TOOL_CATEGORY[it]!! },
+ cfg.getEntry("break_speed"),
+ cfg.getOptionalEntry("attack_damage"),
+ cfg.getOptionalEntry("attack_speed"),
+ cfg.getOptionalEntry("knockback_bonus").orElse(0),
+ cfg.getOptionalEntry("can_sweep_attack").orElse(false),
+ cfg.getOptionalEntry("can_break_blocks_in_creative").orElse(true)
+ )
+ }
+
}
}
\ No newline at end of file
diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Wearable.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Wearable.kt
index 49ebc6cb17..a66a82c93e 100644
--- a/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Wearable.kt
+++ b/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/Wearable.kt
@@ -6,123 +6,218 @@ import net.minecraft.nbt.CompoundTag
import net.minecraft.sounds.SoundEvent
import net.minecraft.world.entity.ai.attributes.AttributeModifier.Operation
import net.minecraft.world.entity.ai.attributes.Attributes
+import net.minecraft.world.item.BlockItem
+import net.minecraft.world.item.Equipable
import org.bukkit.GameMode
import org.bukkit.Sound
import org.bukkit.entity.Player
import org.bukkit.event.block.Action
import org.bukkit.event.player.PlayerInteractEvent
-import org.bukkit.inventory.ItemStack
+import xyz.xenondevs.commons.collections.enumMap
+import xyz.xenondevs.commons.provider.Provider
+import xyz.xenondevs.commons.provider.immutable.orElse
+import xyz.xenondevs.commons.provider.immutable.provider
+import xyz.xenondevs.nova.data.config.ConfigAccess
import xyz.xenondevs.nova.data.resources.lookup.ResourceLookups
import xyz.xenondevs.nova.data.serialization.cbf.NamespacedCompound
import xyz.xenondevs.nova.item.NovaItem
import xyz.xenondevs.nova.item.logic.PacketItemData
-import xyz.xenondevs.nova.item.options.WearableOptions
import xyz.xenondevs.nova.item.vanilla.AttributeModifier
import xyz.xenondevs.nova.item.vanilla.HideableFlag
import xyz.xenondevs.nova.item.vanilla.VanillaMaterialProperty
-import xyz.xenondevs.nova.player.equipment.ArmorType
+import xyz.xenondevs.nova.util.bukkitEquipmentSlot
import xyz.xenondevs.nova.util.data.getOrPut
import xyz.xenondevs.nova.util.item.isActuallyInteractable
+import xyz.xenondevs.nova.util.item.novaItem
import xyz.xenondevs.nova.util.item.takeUnlessEmpty
import xyz.xenondevs.nova.util.nmsCopy
import xyz.xenondevs.nova.util.nmsEquipmentSlot
import xyz.xenondevs.nova.util.serverPlayer
import xyz.xenondevs.nova.util.swingHand
+import java.util.*
+import net.minecraft.world.entity.EquipmentSlot as MojangEquipmentSlot
+import net.minecraft.world.item.ItemStack as MojangStack
+import org.bukkit.inventory.EquipmentSlot as BukkitEquipmentSlot
+import org.bukkit.inventory.ItemStack as BukkitStack
-fun Wearable(type: ArmorType, equipSound: Sound): ItemBehaviorFactory =
- Wearable(type, equipSound.key.toString())
+fun Wearable(slot: BukkitEquipmentSlot, equipSound: Sound): ItemBehaviorFactory =
+ Wearable(slot, equipSound.key.toString())
-fun Wearable(type: ArmorType, equipSound: SoundEvent): ItemBehaviorFactory =
- Wearable(type, equipSound.location.toString())
+fun Wearable(slot: BukkitEquipmentSlot, equipSound: SoundEvent): ItemBehaviorFactory =
+ Wearable(slot, equipSound.location.toString())
-fun Wearable(type: ArmorType, equipSound: String? = null): ItemBehaviorFactory =
- object : ItemBehaviorFactory() {
- override fun create(item: NovaItem): Wearable =
- Wearable(WearableOptions.configurable(type, equipSound, item))
+fun Wearable(slot: BukkitEquipmentSlot, equipSound: String? = null): ItemBehaviorFactory {
+ return object : ItemBehaviorFactory {
+ override fun create(item: NovaItem): Wearable.Default {
+ val texture = ResourceLookups.MODEL_DATA_LOOKUP[item.id]?.armor
+ ?.let { ResourceLookups.ARMOR_DATA_LOOKUP[it] }?.color
+ val cfg = ConfigAccess(item)
+ return Wearable.Default(
+ provider(texture),
+ provider(slot),
+ cfg.getOptionalEntry("armor").orElse(0.0),
+ cfg.getOptionalEntry("armor_toughness").orElse(0.0),
+ cfg.getOptionalEntry("knockback_resistance").orElse(0.0),
+ provider(equipSound)
+ )
+ }
}
+}
-class Wearable(val options: WearableOptions) : ItemBehavior() {
+/**
+ * Allows items to be worn in armor slots.
+ */
+sealed interface Wearable {
- private val textureColor: Int? by lazy {
- ResourceLookups.MODEL_DATA_LOOKUP[item.id]
- ?.armor
- ?.let { ResourceLookups.ARMOR_DATA_LOOKUP[it] }
- ?.color
- }
+ val texture: Int?
+ val slot: BukkitEquipmentSlot
+ val armor: Double
+ val armorToughness: Double
+ val knockbackResistance: Double
+ val equipSound: String?
- override fun getVanillaMaterialProperties(): List {
- if (textureColor == null)
- return emptyList()
+ class Default(
+ texture: Provider,
+ slot: Provider,
+ armor: Provider,
+ armorToughness: Provider,
+ knockbackResistance: Provider,
+ equipSound: Provider
+ ) : ItemBehavior, Wearable {
- return listOf(
- when (options.armorType) {
- ArmorType.HELMET -> VanillaMaterialProperty.HELMET
- ArmorType.CHESTPLATE -> VanillaMaterialProperty.CHESTPLATE
- ArmorType.LEGGINGS -> VanillaMaterialProperty.LEGGINGS
- ArmorType.BOOTS -> VanillaMaterialProperty.BOOTS
- }
- )
- }
-
- override fun getAttributeModifiers(): List {
- val equipmentSlot = options.armorType.equipmentSlot.nmsEquipmentSlot
- return listOf(
- AttributeModifier(
- "Nova Armor (${item.id}})",
- Attributes.ARMOR,
- Operation.ADDITION,
- options.armor,
- true,
- equipmentSlot
- ),
- AttributeModifier(
- "Nova Armor Toughness (${item.id}})",
- Attributes.ARMOR_TOUGHNESS,
- Operation.ADDITION,
- options.armorToughness,
- true,
- equipmentSlot
- ),
- AttributeModifier(
- "Nova Knockback Resistance (${item.id}})",
- Attributes.KNOCKBACK_RESISTANCE,
- Operation.ADDITION,
- options.knockbackResistance,
- true,
- equipmentSlot
- )
- )
- }
-
- override fun handleInteract(player: Player, itemStack: ItemStack, action: Action, event: PlayerInteractEvent) {
- if (action == Action.RIGHT_CLICK_AIR || (action == Action.RIGHT_CLICK_BLOCK && !event.clickedBlock!!.type.isActuallyInteractable())) {
- event.isCancelled = true
+ override val texture by texture
+ override val slot by slot
+ override val armor by armor
+ override val armorToughness by armorToughness
+ override val knockbackResistance by knockbackResistance
+ override val equipSound by equipSound
+
+ override fun getVanillaMaterialProperties(): List {
+ if (texture == null)
+ return emptyList()
- val hand = event.hand!!
- val equipmentSlot = options.armorType.equipmentSlot
- val previous = player.inventory.getItem(equipmentSlot)?.takeUnlessEmpty()
- if (previous != null) {
- // swap armor
- player.inventory.setItem(equipmentSlot, itemStack)
- player.inventory.setItem(hand, previous)
- } else {
- // equip armor
- player.inventory.setItem(equipmentSlot, itemStack)
- if (player.gameMode != GameMode.CREATIVE) player.inventory.setItem(hand, null)
+ return listOf(
+ when (slot) {
+ BukkitEquipmentSlot.HEAD -> VanillaMaterialProperty.HELMET
+ BukkitEquipmentSlot.CHEST -> VanillaMaterialProperty.CHESTPLATE
+ BukkitEquipmentSlot.LEGS -> VanillaMaterialProperty.LEGGINGS
+ BukkitEquipmentSlot.FEET -> VanillaMaterialProperty.BOOTS
+ else -> throw IllegalArgumentException("Invalid wearable slot: $slot")
+ }
+ )
+ }
+
+ override fun getAttributeModifiers(): List {
+ val equipmentSlot = slot.nmsEquipmentSlot
+ return listOf(
+ AttributeModifier(
+ ARMOR_MODIFIER_UUIDS[slot]!!,
+ "Nova Wearable Armor",
+ Attributes.ARMOR,
+ Operation.ADDITION,
+ armor,
+ true,
+ equipmentSlot
+ ),
+ AttributeModifier(
+ ARMOR_TOUGHNESS_MODIFIER_UUIDS[slot]!!,
+ "Nova Wearable Armor Toughness",
+ Attributes.ARMOR_TOUGHNESS,
+ Operation.ADDITION,
+ armorToughness,
+ true,
+ equipmentSlot
+ ),
+ AttributeModifier(
+ KNOCKBACK_RESISTANCE_MODIFIER_UUIDS[slot]!!,
+ "Nova Wearable Knockback Resistance",
+ Attributes.KNOCKBACK_RESISTANCE,
+ Operation.ADDITION,
+ knockbackResistance,
+ true,
+ equipmentSlot
+ )
+ )
+ }
+
+ override fun handleInteract(player: Player, itemStack: BukkitStack, action: Action, event: PlayerInteractEvent) {
+ if (action == Action.RIGHT_CLICK_AIR || (action == Action.RIGHT_CLICK_BLOCK && !event.clickedBlock!!.type.isActuallyInteractable())) {
+ event.isCancelled = true
+
+ val hand = event.hand!!
+ val previous = player.inventory.getItem(slot)?.takeUnlessEmpty()
+ if (previous != null) {
+ // swap armor
+ player.inventory.setItem(slot, itemStack)
+ player.inventory.setItem(hand, previous)
+ } else {
+ // equip armor
+ player.inventory.setItem(slot, itemStack)
+ if (player.gameMode != GameMode.CREATIVE) player.inventory.setItem(hand, null)
+ }
+
+ player.swingHand(hand)
+ player.serverPlayer.onEquipItem(slot.nmsEquipmentSlot, previous.nmsCopy, itemStack.nmsCopy)
+ }
+ }
+
+ override fun updatePacketItemData(data: NamespacedCompound, itemData: PacketItemData) {
+ val texture = texture
+ if (texture != null) {
+ itemData.nbt.getOrPut("display", ::CompoundTag).putInt("color", texture)
+ itemData.hide(HideableFlag.DYE)
}
-
- player.swingHand(hand)
- player.serverPlayer.onEquipItem(options.armorType.equipmentSlot.nmsEquipmentSlot, previous.nmsCopy, itemStack.nmsCopy)
}
+
}
- override fun updatePacketItemData(data: NamespacedCompound, itemData: PacketItemData) {
- val textureColor = textureColor
- if (textureColor != null) {
- itemData.nbt.getOrPut("display", ::CompoundTag).putInt("color", textureColor)
- itemData.hide(HideableFlag.DYE)
+ companion object {
+
+ val ARMOR_MODIFIER_UUIDS: Map = BukkitEquipmentSlot.values().associateWithTo(enumMap()) { UUID.randomUUID() }
+ val ARMOR_TOUGHNESS_MODIFIER_UUIDS: Map = BukkitEquipmentSlot.values().associateWithTo(enumMap()) { UUID.randomUUID() }
+ val KNOCKBACK_RESISTANCE_MODIFIER_UUIDS: Map = BukkitEquipmentSlot.values().associateWithTo(enumMap()) { UUID.randomUUID() }
+
+ /**
+ * Checks whether the specified [itemStack] is wearable.
+ */
+ fun isWearable(itemStack: BukkitStack): Boolean =
+ isWearable(itemStack.nmsCopy)
+
+ /**
+ * Checks whether the specified [itemStack] is wearable.
+ */
+ fun isWearable(itemStack: MojangStack): Boolean {
+ val novaItem = itemStack.novaItem
+ if (novaItem != null)
+ return novaItem.hasBehavior()
+
+ val item = itemStack.item
+ return item is Equipable || item is BlockItem && item.block is Equipable
}
- textureColor?.let { itemData.nbt.getOrPut("display", ::CompoundTag).putInt("color", it) }
+
+ /**
+ * Gets the [BukkitEquipmentSlot] of the specified [itemStack], or null if it is not wearable.
+ */
+ fun getSlot(itemStack: BukkitStack): BukkitEquipmentSlot? =
+ getSlot(itemStack.nmsCopy)?.bukkitEquipmentSlot
+
+ /**
+ * Gets the [MojangEquipmentSlot] of the specified [itemStack], or null if it is not wearable.
+ */
+ fun getSlot(itemStack: MojangStack): MojangEquipmentSlot? {
+ val novaItem = itemStack.novaItem
+ if (novaItem != null)
+ return novaItem.getBehaviorOrNull()?.slot?.nmsEquipmentSlot
+
+ val equipable = when (val item = itemStack.item) {
+ is Equipable -> item
+ is BlockItem -> item.block as? Equipable
+ else -> null
+ } ?: return null
+
+ return equipable.equipmentSlot
+ }
+
}
}
\ No newline at end of file
diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/impl/TileEntityItemBehavior.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/impl/TileEntityItemBehavior.kt
index be0f06ab11..ed135c242b 100644
--- a/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/impl/TileEntityItemBehavior.kt
+++ b/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/impl/TileEntityItemBehavior.kt
@@ -10,7 +10,7 @@ import xyz.xenondevs.nova.tileentity.TileEntity
import xyz.xenondevs.nova.tileentity.network.fluid.FluidType
import xyz.xenondevs.nova.util.NumberFormatUtils
-internal class TileEntityItemBehavior : ItemBehavior() {
+object TileEntityItemBehavior : ItemBehavior {
override fun updatePacketItemData(data: NamespacedCompound, itemData: PacketItemData) {
val tileEntityData: Compound? = data[TileEntity.TILE_ENTITY_DATA_KEY]
diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/impl/WrenchBehavior.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/impl/WrenchBehavior.kt
index fd654fdff9..91b4f96caf 100644
--- a/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/impl/WrenchBehavior.kt
+++ b/nova/src/main/kotlin/xyz/xenondevs/nova/item/behavior/impl/WrenchBehavior.kt
@@ -29,7 +29,7 @@ import xyz.xenondevs.nova.util.swingHand
import xyz.xenondevs.nova.util.toString
import xyz.xenondevs.nova.world.pos
-internal object WrenchBehavior : ItemBehavior() {
+internal object WrenchBehavior : ItemBehavior {
private val WRENCH_MODE_KEY = NamespacedKey(NOVA, "wrench_mode")
private val NETWORK_TYPES = arrayOf(DefaultNetworkTypes.ENERGY, DefaultNetworkTypes.ITEMS, DefaultNetworkTypes.FLUID)
diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/item/enchantment/EnchantmentCategory.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/item/enchantment/EnchantmentCategory.kt
index d83916c5de..c500985108 100644
--- a/nova/src/main/kotlin/xyz/xenondevs/nova/item/enchantment/EnchantmentCategory.kt
+++ b/nova/src/main/kotlin/xyz/xenondevs/nova/item/enchantment/EnchantmentCategory.kt
@@ -58,8 +58,8 @@ abstract class EnchantmentCategory {
* Checks if this [EnchantmentCategory] can be applied to the specified [novaItem].
*/
fun canEnchant(novaItem: NovaItem): Boolean {
- val enchantable = novaItem.getBehavior()
- return enchantable?.options?.enchantmentCategories?.contains(this) ?: false
+ val enchantable = novaItem.getBehaviorOrNull()
+ return enchantable?.enchantmentCategories?.contains(this) ?: false
}
/**
diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/item/logic/ItemLogic.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/item/logic/ItemLogic.kt
index e8a378451d..1f44d5c321 100644
--- a/nova/src/main/kotlin/xyz/xenondevs/nova/item/logic/ItemLogic.kt
+++ b/nova/src/main/kotlin/xyz/xenondevs/nova/item/logic/ItemLogic.kt
@@ -31,6 +31,7 @@ import xyz.xenondevs.nova.data.resources.builder.task.material.info.VanillaMater
import xyz.xenondevs.nova.data.serialization.cbf.NamespacedCompound
import xyz.xenondevs.nova.item.NovaItem
import xyz.xenondevs.nova.item.behavior.ItemBehavior
+import xyz.xenondevs.nova.item.behavior.ItemBehaviorFactory
import xyz.xenondevs.nova.item.behavior.ItemBehaviorHolder
import xyz.xenondevs.nova.item.vanilla.AttributeModifier
import xyz.xenondevs.nova.player.equipment.ArmorEquipEvent
@@ -38,25 +39,50 @@ import xyz.xenondevs.nova.util.data.getConfigurationSectionList
import xyz.xenondevs.nova.util.data.getDoubleOrNull
import xyz.xenondevs.nova.util.data.logExceptionMessages
import xyz.xenondevs.nova.util.item.novaCompound
+import xyz.xenondevs.nova.util.item.novaCompoundOrNull
import xyz.xenondevs.nova.world.block.event.BlockBreakActionEvent
import java.util.logging.Level
import kotlin.reflect.KClass
import kotlin.reflect.full.isSuperclassOf
import net.minecraft.world.item.ItemStack as MojangStack
+private fun loadBehaviors(item: NovaItem, holders: List): List =
+ holders.map { holder ->
+ when (holder) {
+ is ItemBehavior -> holder
+ is ItemBehaviorFactory<*> -> holder.create(item)
+ }
+ }
+
+// TODO: merge with NovaItem?
/**
- * Handles actions performed on [ItemStack]s of a [NovaItem]
+ * Handles actions performed on [ItemStacks][ItemStack] of a [NovaItem].
*/
-internal class ItemLogic internal constructor(holders: List>) : Reloadable {
+internal class ItemLogic internal constructor(holders: List) : Reloadable {
- private val behaviors: List by lazy { holders.map { it.get(item) } }
+ private val behaviors: List by lazy { loadBehaviors(item, holders) }
private lateinit var item: NovaItem
private lateinit var name: Component
-
lateinit var vanillaMaterial: Material private set
lateinit var attributeModifiers: Map> private set
+ private var defaultCompound: NamespacedCompound? = null
- internal constructor(vararg holders: ItemBehaviorHolder<*>) : this(holders.toList())
+ internal constructor(vararg holders: ItemBehaviorHolder) : this(holders.asList())
+
+ fun getBehaviorOrNull(type: KClass): T? =
+ behaviors.firstOrNull { type.isSuperclassOf(it::class) } as T?
+
+ fun hasBehavior(type: KClass): Boolean =
+ behaviors.any { it::class == type }
+
+ fun setMaterial(item: NovaItem) {
+ if (this::item.isInitialized)
+ throw IllegalStateException("NovaItems cannot be used for multiple materials")
+
+ this.item = item
+ this.name = Component.translatable(item.localizedName)
+ reload()
+ }
override fun reload() {
vanillaMaterial = VanillaMaterialTypes.getMaterial(behaviors.flatMap { it.getVanillaMaterialProperties() }.toHashSet())
@@ -69,25 +95,25 @@ internal class ItemLogic internal constructor(holders: List getBehavior(type: KClass): T? =
- behaviors.firstOrNull { type.isSuperclassOf(it::class) } as T?
-
- fun hasBehavior(type: KClass): Boolean =
- behaviors.any { it::class == type }
-
- fun setMaterial(item: NovaItem) {
- if (this::item.isInitialized)
- throw IllegalStateException("NovaItems cannot be used for multiple materials")
- this.item = item
- this.name = Component.translatable(item.localizedName)
- reload()
+ var defaultCompound: NamespacedCompound? = null
+ for (behavior in behaviors) {
+ val behaviorCompound = behavior.getDefaultCompound()
+ if (behaviorCompound.isNotEmpty()) {
+ if (defaultCompound == null)
+ defaultCompound = NamespacedCompound()
+
+ defaultCompound.putAll(behaviorCompound)
+ }
+ }
+ this.defaultCompound = defaultCompound
}
+ @Suppress("DEPRECATION")
fun modifyItemBuilder(itemBuilder: ItemBuilder): ItemBuilder {
var builder = itemBuilder
+ if (defaultCompound != null)
+ builder.addModifier { it.novaCompound.putAll(defaultCompound!!.copy()); it }
behaviors.forEach { builder = it.modifyItemBuilder(builder) }
return builder
}
@@ -95,7 +121,7 @@ internal class ItemLogic internal constructor(holders: List("max_energy")
-
- constructor(path: String) : super(path)
- constructor(item: NovaItem) : super(item)
-
-}
\ No newline at end of file
diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/item/options/DamageableOptions.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/item/options/DamageableOptions.kt
deleted file mode 100644
index e1b62ae282..0000000000
--- a/nova/src/main/kotlin/xyz/xenondevs/nova/item/options/DamageableOptions.kt
+++ /dev/null
@@ -1,77 +0,0 @@
-package xyz.xenondevs.nova.item.options
-
-import org.bukkit.inventory.RecipeChoice
-import xyz.xenondevs.commons.provider.Provider
-import xyz.xenondevs.commons.provider.immutable.map
-import xyz.xenondevs.commons.provider.immutable.orElse
-import xyz.xenondevs.commons.provider.immutable.provider
-import xyz.xenondevs.nova.data.config.ConfigAccess
-import xyz.xenondevs.nova.data.serialization.json.serializer.RecipeDeserializer
-import xyz.xenondevs.nova.item.NovaItem
-
-@HardcodedMaterialOptions
-fun DamageableOptions(
- maxDurability: Int,
- itemDamageOnAttackEntity: Int,
- itemDamageOnBreakBlock: Int,
- repairIngredient: RecipeChoice? = null
-): DamageableOptions = HardcodedDamageableOptions(maxDurability, itemDamageOnAttackEntity, itemDamageOnBreakBlock, repairIngredient)
-
-sealed interface DamageableOptions {
-
- val durabilityProvider: Provider
- val itemDamageOnAttackEntityProvider: Provider
- val itemDamageOnBreakBlockProvider: Provider
- val repairIngredientProvider: Provider
-
- val durability: Int
- get() = durabilityProvider.value
- val itemDamageOnAttackEntity: Int
- get() = itemDamageOnAttackEntityProvider.value
- val itemDamageOnBreakBlock: Int
- get() = itemDamageOnBreakBlockProvider.value
- val repairIngredient: RecipeChoice?
- get() = repairIngredientProvider.value
-
- companion object {
-
- fun configurable(item: NovaItem): DamageableOptions =
- ConfigurableDamageableOptions(item)
-
- fun configurable(path: String): DamageableOptions =
- ConfigurableDamageableOptions(path)
-
- }
-
-}
-
-private class HardcodedDamageableOptions(
- maxDurability: Int,
- itemDamageOnAttackEntity: Int,
- itemDamageOnBreakBlock: Int,
- repairIngredient: RecipeChoice?
-) : DamageableOptions {
- override val durabilityProvider = provider(maxDurability)
- override val itemDamageOnAttackEntityProvider = provider(itemDamageOnAttackEntity)
- override val itemDamageOnBreakBlockProvider = provider(itemDamageOnBreakBlock)
- override val repairIngredientProvider = provider(repairIngredient)
-}
-
-@Suppress("UNCHECKED_CAST")
-private class ConfigurableDamageableOptions : ConfigAccess, DamageableOptions {
-
- override val durabilityProvider = getEntry("durability", "max_durability")
- override val itemDamageOnAttackEntityProvider = getOptionalEntry("item_damage_on_attack_entity").orElse(0)
- override val itemDamageOnBreakBlockProvider = getOptionalEntry("item_damage_on_break_block").orElse(0)
- override val repairIngredientProvider = getOptionalEntry("repair_ingredient").map {
- val list = when (it) {
- is String -> listOf(it)
- else -> it as List
- }
- RecipeDeserializer.parseRecipeChoice(list)
- }
-
- constructor(path: String) : super(path)
- constructor(item: NovaItem) : super(item)
-
-}
\ No newline at end of file
diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/item/options/EnchantableOptions.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/item/options/EnchantableOptions.kt
deleted file mode 100644
index ccf23fc51c..0000000000
--- a/nova/src/main/kotlin/xyz/xenondevs/nova/item/options/EnchantableOptions.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-package xyz.xenondevs.nova.item.options
-
-import xyz.xenondevs.commons.provider.immutable.map
-import xyz.xenondevs.nova.data.config.ConfigAccess
-import xyz.xenondevs.nova.item.NovaItem
-import xyz.xenondevs.nova.item.enchantment.EnchantmentCategory
-import xyz.xenondevs.nova.registry.NovaRegistries
-import xyz.xenondevs.nova.util.getOrThrow
-
-@HardcodedMaterialOptions
-fun EnchantableOptions(
- enchantmentValue: Int,
- enchantmentCategories: List
-): EnchantableOptions = HardcodedEnchantableOptions(enchantmentValue, enchantmentCategories)
-
-sealed interface EnchantableOptions {
-
- val enchantmentValue: Int
- val enchantmentCategories: List
-
- companion object {
-
- fun configurable(item: NovaItem): EnchantableOptions =
- ConfigurableEnchantableOptions(item)
-
- fun configurable(path: String): EnchantableOptions =
- ConfigurableEnchantableOptions(path)
-
- }
-
-}
-
-private class HardcodedEnchantableOptions(
- override val enchantmentValue: Int,
- override val enchantmentCategories: List
-) : EnchantableOptions
-
-private class ConfigurableEnchantableOptions : ConfigAccess, EnchantableOptions {
-
- override val enchantmentValue by getEntry("enchantment_value")
- override val enchantmentCategories by getEntry
>("enchantment_categories")
- .map { list -> list.map { NovaRegistries.ENCHANTMENT_CATEGORY.getOrThrow(it) } }
-
- constructor(path: String) : super(path)
- constructor(item: NovaItem) : super(item)
-
-}
\ No newline at end of file
diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/item/options/FoodOptions.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/item/options/FoodOptions.kt
deleted file mode 100644
index a5bfd099f6..0000000000
--- a/nova/src/main/kotlin/xyz/xenondevs/nova/item/options/FoodOptions.kt
+++ /dev/null
@@ -1,124 +0,0 @@
-package xyz.xenondevs.nova.item.options
-
-import org.bukkit.potion.PotionEffect
-import xyz.xenondevs.commons.provider.Provider
-import xyz.xenondevs.commons.provider.immutable.map
-import xyz.xenondevs.commons.provider.immutable.orElse
-import xyz.xenondevs.commons.provider.immutable.provider
-import xyz.xenondevs.nova.data.config.ConfigAccess
-import xyz.xenondevs.nova.item.NovaItem
-import xyz.xenondevs.nova.item.options.FoodOptions.FoodType
-import xyz.xenondevs.nova.item.vanilla.VanillaMaterialProperty
-
-/**
- * @param type The type of food
- * @param consumeTime The time it takes for the food to be consumed, in ticks.
- * @param nutrition The nutrition value this food provides.
- * @param saturationModifier The saturation modifier this food provides. The saturation is calculated like this:
- * ```
- * saturation = min(saturation + nutrition * saturationModifier * 2.0f, foodLevel)
- * ```
- * @param instantHealth The amount of health to be restored immediately.
- * @param effects A list of effects to apply to the player when this food is consumed.
- */
-@HardcodedMaterialOptions
-fun FoodOptions(
- type: FoodType,
- consumeTime: Int,
- nutrition: Int,
- saturationModifier: Float,
- instantHealth: Double = 0.0,
- effects: List? = null
-): FoodOptions = HardcodedFoodOptions(type, consumeTime, nutrition, saturationModifier, instantHealth, effects)
-
-sealed interface FoodOptions {
-
- val typeProvider: Provider
- val consumeTimeProvider: Provider
- val nutritionProvider: Provider
- val saturationModifierProvider: Provider