Skip to content

Commit

Permalink
Item- & Block Behavior factory functions with config fallback
Browse files Browse the repository at this point in the history
  • Loading branch information
NichtStudioCode committed Nov 15, 2024
1 parent 654b04e commit 7544420
Show file tree
Hide file tree
Showing 13 changed files with 462 additions and 309 deletions.
42 changes: 42 additions & 0 deletions nova/src/main/kotlin/xyz/xenondevs/nova/config/ConfigProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,48 @@ inline fun <reified T : Any> Provider<ConfigurationNode>.strongEntryOrElse(defau
inline fun <reified T : Any> Provider<ConfigurationNode>.entryOrElse(default: T?, vararg paths: Array<String>): Provider<T> =
if (default != null) optionalEntry<T>(*paths).orElse(default) else entry<T>(*paths)

/**
* Gets an entry [Provider] for a value of type [T] under [path], using [default] as fallback, or requiring config presence if null.
*
* @throws NoSuchElementException if the entry does not exist and [default] is null
* @throws IllegalStateException if the entry could not be deserialized to [T]
*/
inline fun <reified T : Any> Provider<ConfigurationNode>.strongEntryOrElse(default: Provider<T>?, vararg path: String): Provider<T> =
if (default != null) strongOptionalEntry<T>(*path).strongOrElse(default) else strongEntry<T>(*path)

/**
* Gets an entry [Provider] for a value of type [T] under [path], using [default] as fallback, or requiring config presence if null.
*
* The returned provider will only be stored in a [WeakReference] in the parent provider.
*
* @throws NoSuchElementException if the entry does not exist and [default] is null
* @throws IllegalStateException if the entry could not be deserialized to [T]
*/
inline fun <reified T : Any> Provider<ConfigurationNode>.entryOrElse(default: Provider<T>?, vararg path: String): Provider<T> =
if (default != null) optionalEntry<T>(*path).orElse(default) else entry<T>(*path)

/**
* Gets an entry [Provider] for a value of type [T] under the first existing path from [paths],
* using [default] as fallback, or requiring config presence if null.
*
* @throws NoSuchElementException if no entry exists and [default] is null
* @throws IllegalStateException if the entry could not be deserialized to [T]
*/
inline fun <reified T : Any> Provider<ConfigurationNode>.strongEntryOrElse(default: Provider<T>?, vararg paths: Array<String>): Provider<T> =
if (default != null) strongOptionalEntry<T>(*paths).strongOrElse(default) else strongEntry<T>(*paths)

/**
* Gets an entry [Provider] for a value of type [T] under the first existing path from [paths],
* using [default] as fallback, or requiring config presence if null.
*
* The returned provider will only be stored in a [WeakReference] in the parent provider.
*
* @throws NoSuchElementException if no entry exists and [default] is null
* @throws IllegalStateException if the entry could not be deserialized to [T]
*/
inline fun <reified T : Any> Provider<ConfigurationNode>.entryOrElse(default: Provider<T>?, vararg paths: Array<String>): Provider<T> =
if (default != null) optionalEntry<T>(*paths).orElse(default) else entry<T>(*paths)

private fun Provider<ConfigurationNode>.fullPath(): String {
val filePath = findFilePath()
val path = get().path()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ internal object DefaultBlocks {
BlockSounds(SoundGroup.WOOD),
Breakable(
hardness = 0.8,
toolCategory = VanillaToolCategories.AXE,
toolCategories = setOf(VanillaToolCategories.AXE),
toolTier = VanillaToolTiers.WOOD,
requiresToolForDrops = false,
breakParticles = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ interface BlockBehavior : BlockBehaviorHolder {
/**
* Factory for creating [BlockBehavior] instances of [T] based on a [NovaBlock].
*/
interface BlockBehaviorFactory<T : BlockBehavior> : BlockBehaviorHolder {
fun interface BlockBehaviorFactory<T : BlockBehavior> : BlockBehaviorHolder {

/**
* Creates a new [BlockBehavior] instance of [T] based on the given [block].
Expand Down
Original file line number Diff line number Diff line change
@@ -1,64 +1,61 @@
package xyz.xenondevs.nova.world.block.behavior

import org.bukkit.Material
import xyz.xenondevs.commons.collections.isNotNullOrEmpty
import xyz.xenondevs.commons.provider.Provider
import xyz.xenondevs.commons.provider.orElse
import xyz.xenondevs.commons.provider.provider
import xyz.xenondevs.nova.config.entry
import xyz.xenondevs.nova.config.entryOrElse
import xyz.xenondevs.nova.config.optionalEntry
import xyz.xenondevs.nova.world.block.NovaBlock
import xyz.xenondevs.nova.world.item.tool.ToolCategory
import xyz.xenondevs.nova.world.item.tool.ToolTier

/**
* Creates a factory for [Breakable] behaviors using the given values, if not specified otherwise in the block's config.
*
* @param hardness The hardness of the block.
* Used when `hardness` is not specified in the config, or `null` to require the presence of a config entry.
*
* @param toolCategories The [ToolCategories][ToolCategory] required to break the block.
* Used when `tool_categories` is not specified in the config.
*
* @param toolTier The [ToolTier] required to break the block. Can be null to not require a specific tool tier.
* Used when `tool_tier` is not specified in the config.
*
* @param requiresToolForDrops Whether the block requires a tool to drop its item.
* Used when `requires_tool_for_drops` is not specified in the config, or `null` to require the presence of a config entry.
*
* @param breakParticles The type break particles to spawn in case the block is backed by barriers. Can be null.
* Used when `break_particles` is not specified in the config.
*
* @param showBreakAnimation Whether the break animation should be shown.
* Used when `show_break_animation` is not specified in the config.
*/
@Suppress("FunctionName")
fun Breakable(
hardness: Double,
toolCategories: Set<ToolCategory>,
toolTier: ToolTier?,
requiresToolForDrops: Boolean,
hardness: Double? = null,
toolCategories: Set<ToolCategory> = emptySet(),
toolTier: ToolTier? = null,
requiresToolForDrops: Boolean? = null,
breakParticles: Material? = null,
showBreakAnimation: Boolean = true
): Breakable.Default {
require(toolCategories.isNotEmpty()) { "Tool categories cannot be empty if a tool tier is specified!" }
return Breakable.Default(
hardness,
toolCategories,
toolTier,
requiresToolForDrops,
breakParticles,
showBreakAnimation
) = BlockBehaviorFactory<Breakable> {
require(toolTier == null || toolCategories.isNotNullOrEmpty()) { "Tool categories cannot be empty if a tool tier is specified!" }

val cfg = it.config
Breakable.Default(
cfg.entryOrElse(hardness, "hardness"),
cfg.entryOrElse(toolCategories, "tool_categories"),
cfg.optionalEntry<ToolTier>("tool_tier").orElse(toolTier),
cfg.entryOrElse(requiresToolForDrops, "requires_tool_for_drops"),
cfg.optionalEntry<Material>("break_particles").orElse(breakParticles),
cfg.optionalEntry<Boolean>("show_break_animation").orElse(showBreakAnimation)
)
}

fun Breakable(
hardness: Double,
toolCategory: ToolCategory,
toolTier: ToolTier,
requiresToolForDrops: Boolean,
breakParticles: Material? = null,
showBreakAnimation: Boolean = true
) = Breakable.Default(
hardness,
setOf(toolCategory),
toolTier,
requiresToolForDrops,
breakParticles,
showBreakAnimation
)

fun Breakable(
hardness: Double,
breakParticles: Material? = null,
showBreakAnimation: Boolean = true
) = Breakable.Default(
hardness,
emptySet(),
null,
false,
breakParticles,
showBreakAnimation
)

interface Breakable {
/**
* Defines values used for block breaking. Makes blocks breakable.
*/
interface Breakable : BlockBehavior {

val hardness: Double
val toolCategories: Set<ToolCategory>
Expand All @@ -74,7 +71,7 @@ interface Breakable {
requiresToolForDrops: Provider<Boolean>,
breakParticles: Provider<Material?>,
showBreakAnimation: Provider<Boolean>
) : BlockBehavior, Breakable {
) : Breakable {

override val hardness by hardness
override val toolCategories by toolCategories
Expand All @@ -83,38 +80,6 @@ interface Breakable {
override val breakParticles by breakParticles
override val showBreakAnimation by showBreakAnimation

constructor(
hardness: Double,
toolCategories: Set<ToolCategory>,
toolTier: ToolTier?,
requiresToolForDrops: Boolean,
breakParticles: Material?,
showBreakAnimation: Boolean
) : this(
provider(hardness),
provider(toolCategories),
provider(toolTier),
provider(requiresToolForDrops),
provider(breakParticles),
provider(showBreakAnimation)
)

}

companion object : BlockBehaviorFactory<Default> {

override fun create(block: NovaBlock): Default {
val cfg = block.config
return Default(
cfg.entry<Double>("hardness"),
cfg.entry<Set<ToolCategory>>("toolCategories"),
cfg.optionalEntry<ToolTier>("toolTier"),
cfg.entry<Boolean>("requiresToolForDrops"),
cfg.optionalEntry<Material>("breakParticles"),
cfg.optionalEntry<Boolean>("showBreakAnimation").orElse(true)
)
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,32 @@ import xyz.xenondevs.nova.world.item.vanilla.VanillaMaterialProperty
import java.awt.Color

/**
* Creates a new [AnimatedDye].
* Creates a factory for [AnimatedDye] behaviors using the given values, if not specified otherwise in the config.
*
* @param defaultTicksPerColor The default value for the amount of ticks between each color,
* to be used when `ticks_per_color` is not present in the config, or null to require config presence.
* @param defaultTicksPerColor The default value for the amount of ticks between each color.
* Used when `ticks_per_color` is not specified in the config, or null to require the presence of a config entry.
*
* @param defaultColors The default value for the list of colors to cycle through,
* to be used when `colors` is not present in the config, or null to require config presence.
* to be used when `colors` is not specified in the config, or null to require the presence of a config entry.
*/
@Suppress("FunctionName")
fun AnimatedDye(
defaultTicksPerColor: Int? = null,
defaultColors: List<Color>? = null
) = ItemBehaviorFactory<AnimatedDye> {
val config = it.config
val cfg = it.config
AnimatedDye(
config.entryOrElse(defaultTicksPerColor, "ticks_per_color"),
config.entryOrElse(defaultColors, "colors")
cfg.entryOrElse(defaultTicksPerColor, "ticks_per_color"),
cfg.entryOrElse(defaultColors, "colors")
)
}

/**
* Animates the `minecraft:dyed_color` component by interpolating between a given set of colors.
*
* @param ticksPerColor The amount of ticks between each color.
* @param colors The list of colors to cycle through.
*/
class AnimatedDye(
ticksPerColor: Provider<Int>,
colors: Provider<List<Color>>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,43 @@ import org.bukkit.entity.Player
import org.bukkit.inventory.ItemStack
import xyz.xenondevs.commons.provider.Provider
import xyz.xenondevs.commons.provider.provider
import xyz.xenondevs.nova.config.entry
import xyz.xenondevs.nova.config.entryOrElse
import xyz.xenondevs.nova.serialization.cbf.NamespacedCompound
import xyz.xenondevs.nova.util.NumberFormatUtils
import xyz.xenondevs.nova.util.component.adventure.withoutPreFormatting
import xyz.xenondevs.nova.util.item.novaCompound
import xyz.xenondevs.nova.util.item.retrieveData
import xyz.xenondevs.nova.util.item.storeData
import xyz.xenondevs.nova.util.unwrap
import xyz.xenondevs.nova.world.item.NovaItem
import org.bukkit.inventory.ItemStack as BukkitStack

private val ENERGY_KEY = ResourceLocation.fromNamespaceAndPath("nova", "energy")

/**
* Creates a factory for [Chargeable] behaviors using the given values, if not specified otherwise in the item's config.
*
* @param maxEnergy The maximum amount of energy the item can store.
* Used when `max_energy` is not specified in the config, or `null` to require the presence of a config entry.
*
* @param affectsItemDurability Whether the item's durability bar should be used to visualize the amount
* of energy stored in the item. Used when `charge_affects_item_durability` is not specified in the config.
*/
@Suppress("FunctionName")
fun Chargeable(affectsItemDurability: Boolean): ItemBehaviorFactory<Chargeable.Default> =
object : ItemBehaviorFactory<Chargeable.Default> {
override fun create(item: NovaItem): Chargeable.Default {
return Chargeable.Default(item.config.entry("max_energy"), affectsItemDurability)
}
}
fun Chargeable(
maxEnergy: Long? = null,
affectsItemDurability: Boolean = true
) = ItemBehaviorFactory<Chargeable> {
val cfg = it.config
Chargeable.Default(
cfg.entryOrElse(maxEnergy, "max_energy"),
cfg.entryOrElse<Boolean>(affectsItemDurability, "charge_affects_item_durability")
)
}

/**
* Allows items to store energy and be charged.
*/
interface Chargeable {
interface Chargeable : ItemBehavior {

/**
* The maximum amount of energy this item can store.
Expand All @@ -54,20 +66,13 @@ interface Chargeable {
*/
fun addEnergy(itemStack: BukkitStack, energy: Long)

companion object : ItemBehaviorFactory<Default> {

override fun create(item: NovaItem): Default {
return Default(item.config.entry<Long>("max_energy"), true)
}

}

class Default(
maxEnergy: Provider<Long>,
private val affectsItemDurability: Boolean
affectsItemDurability: Provider<Boolean>
) : ItemBehavior, Chargeable {

override val maxEnergy by maxEnergy
private val affectsItemDurability by affectsItemDurability

override val defaultCompound = provider {
NamespacedCompound().apply { this[ENERGY_KEY] = 0L }
Expand Down
Loading

0 comments on commit 7544420

Please sign in to comment.