Skip to content

Commit

Permalink
feat: Rudimentary system for ingredient options in crafting recipes (…
Browse files Browse the repository at this point in the history
…ex. keep ingredient, or damage ingredient)
  • Loading branch information
0ffz committed Aug 19, 2024
1 parent 5c3031b commit 359792f
Show file tree
Hide file tree
Showing 11 changed files with 199 additions and 53 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package com.mineinabyss.idofront

import com.mineinabyss.idofront.resourcepacks.MinecraftAssetExtractor
import org.bukkit.Bukkit
import com.mineinabyss.idofront.di.DI
import com.mineinabyss.idofront.plugin.listeners
import com.mineinabyss.idofront.serialization.recipes.options.IngredientOptionsListener
import org.bukkit.plugin.java.JavaPlugin

class IdofrontPlugin : JavaPlugin() {

override fun onEnable() {
val recipeOptionsListener = IngredientOptionsListener(this)
DI.add(recipeOptionsListener)
listeners(recipeOptionsListener)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.mineinabyss.idofront.items.asColorable
import com.mineinabyss.idofront.messaging.idofrontLogger
import com.mineinabyss.idofront.nms.hideAttributeTooltipWithItemFlagSet
import com.mineinabyss.idofront.plugin.Plugins
import com.mineinabyss.idofront.serialization.recipes.options.IngredientOption
import dev.lone.itemsadder.api.CustomStack
import io.lumine.mythiccrucible.MythicCrucible
import io.th0rgal.oraxen.OraxenPlugin
Expand Down Expand Up @@ -67,7 +68,10 @@ data class BaseSerializableItemStack(
@EncodeDefault(NEVER) val enchantmentGlintOverride: Boolean? = null,
@EncodeDefault(NEVER) val maxStackSize: Int? = null,
@EncodeDefault(NEVER) val rarity: ItemRarity? = null,

// Custom recipes
@EncodeDefault(NEVER) val tag: String? = null,
@EncodeDefault(NEVER) val recipeOptions: List<IngredientOption> = listOf(),

// Third-party plugins
@EncodeDefault(NEVER) val crucibleItem: String? = null,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.mineinabyss.idofront.serialization.recipes

import org.bukkit.Bukkit
import org.bukkit.Material
import org.bukkit.NamespacedKey
import org.bukkit.Tag
import org.bukkit.inventory.RecipeChoice

object RecipeUtils {
fun getMaterialChoiceForTag(key: NamespacedKey) = RecipeChoice.MaterialChoice(
Bukkit.getTag(
Tag.REGISTRY_BLOCKS,
key,
Material::class.java
) ?: Bukkit.getTag(
Tag.REGISTRY_ITEMS,
key,
Material::class.java
) ?: Tag.DIRT
)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package com.mineinabyss.idofront.serialization.recipes

import com.mineinabyss.idofront.recipes.register
import com.mineinabyss.idofront.serialization.recipes.options.IngredientOptions
import com.mineinabyss.idofront.serialization.recipes.options.RecipeWithOptions
import com.mineinabyss.idofront.serialization.recipes.options.ingredientOptionsListener
import kotlinx.serialization.Serializable
import org.bukkit.NamespacedKey
import org.bukkit.inventory.ItemStack
Expand All @@ -8,4 +12,25 @@ import org.bukkit.inventory.Recipe
@Serializable
sealed class SerializableRecipeIngredients {
abstract fun toRecipe(key: NamespacedKey, result: ItemStack, group: String = "", category: String = "MISC"): Recipe
}

open fun toRecipeWithOptions(
key: NamespacedKey,
result: ItemStack,
group: String = "",
category: String = "MISC",
): RecipeWithOptions {
val recipe = toRecipe(key, result, group, category)
return RecipeWithOptions(recipe, IngredientOptions())
}

fun registerRecipeWithOptions(
key: NamespacedKey,
result: ItemStack,
group: String = "",
category: String = "MISC",
) {
val (recipe, options) = toRecipeWithOptions(key, result, group, category)
recipe.register()
ingredientOptionsListener.keyToOptions[key.asString()] = options
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package com.mineinabyss.idofront.serialization.recipes

import com.mineinabyss.idofront.serialization.SerializableItemStack
import com.mineinabyss.idofront.serialization.recipes.options.IngredientOptions
import com.mineinabyss.idofront.serialization.recipes.options.RecipeWithOptions
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import org.bukkit.Bukkit
import org.bukkit.Material
import org.bukkit.NamespacedKey
import org.bukkit.Tag
import org.bukkit.inventory.ItemStack
import org.bukkit.inventory.Recipe
import org.bukkit.inventory.RecipeChoice
import org.bukkit.inventory.ShapedRecipe
import org.bukkit.inventory.recipe.CraftingBookCategory
Expand All @@ -19,34 +17,32 @@ class ShapedRecipeIngredients(
val items: Map<String, SerializableItemStack>,
val configuration: String = "",
) : SerializableRecipeIngredients() {
override fun toRecipe(key: NamespacedKey, result: ItemStack, group: String, category: String): Recipe {
override fun toRecipe(key: NamespacedKey, result: ItemStack, group: String, category: String): ShapedRecipe {
val recipe = ShapedRecipe(key, result)

recipe.shape(*configuration.replace("|", "").split("\n").toTypedArray())

recipe.group = group
recipe.category = CraftingBookCategory.entries.find { it.name == category } ?: CraftingBookCategory.MISC

items.forEach { (key, ingredient) ->
if (ingredient.tag?.isNotEmpty() == true) {
val namespacedKey = NamespacedKey.fromString(ingredient.tag) ?: NamespacedKey.minecraft(ingredient.tag)
recipe.setIngredient(
key[0],
RecipeChoice.MaterialChoice(
Bukkit.getTag(
Tag.REGISTRY_BLOCKS,
namespacedKey,
Material::class.java
) ?: Bukkit.getTag(
Tag.REGISTRY_ITEMS,
namespacedKey,
Material::class.java
) ?: Tag.DIRT
)
)
} else recipe.setIngredient(key[0], RecipeChoice.ExactChoice(ingredient.toItemStack()))
}

return recipe
}

override fun toRecipeWithOptions(
key: NamespacedKey,
result: ItemStack,
group: String,
category: String,
): RecipeWithOptions {
val recipe = toRecipe(key, result, group, category)
val options = items.mapNotNull { (key, ingredient) ->
val choice = if (ingredient.tag?.isNotEmpty() == true) {
val namespacedKey = NamespacedKey.fromString(ingredient.tag, null)!!
RecipeUtils.getMaterialChoiceForTag(namespacedKey)
} else RecipeChoice.ExactChoice(ingredient.toItemStack())
recipe.setIngredient(key[0], choice)
choice to (ingredient.recipeOptions.takeIf { it.isNotEmpty() } ?: return@mapNotNull null)
}.toMap()
return RecipeWithOptions(recipe, IngredientOptions(options))
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package com.mineinabyss.idofront.serialization.recipes

import com.mineinabyss.idofront.serialization.SerializableItemStack
import com.mineinabyss.idofront.serialization.recipes.options.IngredientOptions
import com.mineinabyss.idofront.serialization.recipes.options.RecipeWithOptions
import com.mineinabyss.idofront.serialization.recipes.options.ingredientOptionsListener
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import org.bukkit.Bukkit
import org.bukkit.Material
import org.bukkit.NamespacedKey
import org.bukkit.Tag
import org.bukkit.inventory.ItemStack
import org.bukkit.inventory.Recipe
import org.bukkit.inventory.RecipeChoice
import org.bukkit.inventory.ShapelessRecipe
import org.bukkit.inventory.recipe.CraftingBookCategory
Expand All @@ -18,31 +17,31 @@ import org.bukkit.inventory.recipe.CraftingBookCategory
class ShapelessRecipeIngredients(
val items: List<SerializableItemStack>,
) : SerializableRecipeIngredients() {
override fun toRecipe(key: NamespacedKey, result: ItemStack, group: String, category: String): Recipe {
override fun toRecipe(key: NamespacedKey, result: ItemStack, group: String, category: String): ShapelessRecipe {
val recipe = ShapelessRecipe(key, result)

recipe.group = group
recipe.category = CraftingBookCategory.entries.find { it.name == category } ?: CraftingBookCategory.MISC

items.forEach { ingredient ->
if (ingredient.tag?.isNotEmpty() == true) {
val namespacedKey = NamespacedKey.fromString(ingredient.tag) ?: NamespacedKey.minecraft(ingredient.tag)
recipe.addIngredient(
RecipeChoice.MaterialChoice(
Bukkit.getTag(
Tag.REGISTRY_BLOCKS,
namespacedKey,
Material::class.java
) ?: Bukkit.getTag(
Tag.REGISTRY_ITEMS,
namespacedKey,
Material::class.java
) ?: Tag.DIRT
)
)
} else recipe.addIngredient(RecipeChoice.ExactChoice(ingredient.toItemStack()))
}

return recipe
}

override fun toRecipeWithOptions(
key: NamespacedKey,
result: ItemStack,
group: String,
category: String,
): RecipeWithOptions {
val recipe = toRecipe(key, result, group, category)
val options = items.mapNotNull { ingredient ->
val choice = if (ingredient.tag?.isNotEmpty() == true) {
val namespacedKey = NamespacedKey.fromString(ingredient.tag, null)!!
RecipeUtils.getMaterialChoiceForTag(namespacedKey)
} else RecipeChoice.ExactChoice(ingredient.toItemStack())
recipe.addIngredient(choice)
choice to (ingredient.recipeOptions.takeIf { it.isNotEmpty() } ?: return@mapNotNull null)
}.toMap()
ingredientOptionsListener.keyToOptions[key.asString()] = IngredientOptions(options)
return RecipeWithOptions(recipe, IngredientOptions(options))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.mineinabyss.idofront.serialization.recipes.options

import org.bukkit.inventory.ItemStack

internal data class IngredientInfo(
val item: ItemStack,
val slot: Int,
val options: List<IngredientOption>,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.mineinabyss.idofront.serialization.recipes.options

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import org.bukkit.inventory.ItemStack
import org.bukkit.inventory.meta.Damageable

@Serializable
sealed class IngredientOption {
abstract fun onCrafted(item: ItemStack, setItemInSlot: (ItemStack?) -> Unit)

@Serializable
@SerialName("keep")
data object Keep : IngredientOption() {
override fun onCrafted(item: ItemStack, setItemInSlot: (ItemStack?) -> Unit) {
setItemInSlot(item)
}
}

@Serializable
@SerialName("damage")
data class Damage(val amount: Int) : IngredientOption() {
override fun onCrafted(item: ItemStack, setItemInSlot: (ItemStack?) -> Unit) {
item.editMeta {
if (it is Damageable) it.damage += amount
}
setItemInSlot(item)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.mineinabyss.idofront.serialization.recipes.options

import org.bukkit.inventory.ItemStack
import org.bukkit.inventory.RecipeChoice

data class IngredientOptions(
val options: Map<RecipeChoice, List<IngredientOption>> = mapOf(),
) {
fun getOptionsFor(item: ItemStack): List<IngredientOption> {
val choice = options.entries.firstOrNull { it.key.test(item) } ?: return emptyList()
return choice.value
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.mineinabyss.idofront.serialization.recipes.options

import com.mineinabyss.idofront.di.DI
import org.bukkit.Bukkit
import org.bukkit.Keyed
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.inventory.CraftItemEvent
import org.bukkit.plugin.Plugin

val ingredientOptionsListener by DI.observe<IngredientOptionsListener>()

class IngredientOptionsListener(
val plugin: Plugin,
) : Listener {
val keyToOptions = mutableMapOf<String, IngredientOptions>()

@EventHandler
fun CraftItemEvent.onCraft() {
val recipe = recipe as? Keyed ?: return
val options = keyToOptions[recipe.key.asString()] ?: return
val onCrafted = inventory.matrix.mapIndexedNotNull { index, itemStack ->
if (itemStack == null) return@mapIndexedNotNull null
val itemOptions = options.getOptionsFor(itemStack)
IngredientInfo(itemStack.clone(), index, itemOptions)
}

Bukkit.getScheduler().scheduleSyncDelayedTask(plugin) {
val matrix = inventory.matrix
onCrafted.forEach { (item, slot, options) ->
options.forEach { it.onCrafted(item) { matrix[slot] = it } }
}
inventory.matrix = matrix
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.mineinabyss.idofront.serialization.recipes.options

import org.bukkit.inventory.Recipe

data class RecipeWithOptions(
val recipe: Recipe,
val options: IngredientOptions,
)

0 comments on commit 359792f

Please sign in to comment.