Update upgrade recipe, and merge energy container recipe onto upgrade recipe

This commit is contained in:
DBotThePony 2024-08-17 21:06:12 +07:00
parent 7f927d5f05
commit 4c0bac35c1
Signed by: DBot
GPG Key ID: DCC23B5715498507
4 changed files with 112 additions and 262 deletions

View File

@ -4,7 +4,6 @@ package ru.dbotthepony.mc.otm.datagen.recipes
import com.google.gson.JsonObject
import net.minecraft.advancements.Advancement
import net.minecraft.advancements.Criterion
import net.minecraft.advancements.CriterionTriggerInstance
import net.minecraft.data.recipes.FinishedRecipe
import net.minecraft.data.recipes.RecipeCategory
import net.minecraft.data.recipes.RecipeOutput
@ -20,9 +19,7 @@ import ru.dbotthepony.mc.otm.core.registryName
import ru.dbotthepony.mc.otm.core.set
import ru.dbotthepony.mc.otm.core.toJsonStrict
import ru.dbotthepony.mc.otm.datagen.modLocation
import ru.dbotthepony.mc.otm.recipe.EnergyContainerRecipe
import ru.dbotthepony.mc.otm.recipe.UpgradeRecipe
import java.util.function.Consumer
private interface RecipeCell {
val value: Ingredient
@ -191,7 +188,7 @@ class MatteryRecipe(val result: ItemLike, val count: Int = 1, val category: Reci
object : FinishedRecipe by it {
override fun serializeRecipeData(pJson: JsonObject) {
pJson["parent"] = it.serializeRecipe()
pJson["copyPaths"] = UpgradeRecipe.COPY_PATHS_CODEC.toJsonStrict(copyPaths)
pJson["copyPaths"] = UpgradeRecipe.OPERATION_CODEC.toJsonStrict(copyPaths)
pJson["source"] = upgradeSource!!.toString()
}

View File

@ -96,7 +96,7 @@ interface IMatteryEnergyStorage : IEnergyStorage {
val canSetBatteryLevel: Boolean get() = true
/**
* How much energy (estimated) is stored in this energy storage. Why estimated? Because some objects can be bottomless.
* How much energy (considered to be estimated, and not ground truth) is stored in this energy storage
*
* Implementations are free to throw [UnsupportedOperationException] if setting battery level is not supported
* due to technical complications (in this case, [canSetBatteryLevel] MUST be false)

View File

@ -1,83 +0,0 @@
package ru.dbotthepony.mc.otm.recipe
import com.mojang.serialization.Codec
import com.mojang.serialization.MapCodec
import net.minecraft.core.HolderLookup
import net.minecraft.core.RegistryAccess
import net.minecraft.core.component.DataComponents
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.network.codec.StreamCodec
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.crafting.CraftingBookCategory
import net.minecraft.world.item.crafting.CraftingInput
import net.minecraft.world.item.crafting.RecipeSerializer
import net.minecraft.world.item.crafting.ShapedRecipe
import net.minecraft.world.item.crafting.ShapedRecipePattern
import net.minecraft.world.item.enchantment.ItemEnchantments
import net.minecraft.world.level.Level
import ru.dbotthepony.mc.otm.capability.matteryEnergy
import ru.dbotthepony.mc.otm.container.util.iterator
import ru.dbotthepony.mc.otm.core.filterNotNull
class EnergyContainerRecipe(group: String, category: CraftingBookCategory, pattern: ShapedRecipePattern, result: ItemStack, showNotification: Boolean = true) : ShapedRecipe(group, category, pattern, result, showNotification) {
constructor(parent: ShapedRecipe) : this(parent.group, parent.category(), parent.pattern, parent.result, parent.showNotification())
override fun assemble(container: CraftingInput, registryAccess: HolderLookup.Provider): ItemStack {
val itemStack = super.assemble(container, registryAccess)
val battery = container.items().stream()
.filter { !it.isEmpty }
.map { it.matteryEnergy }
.filterNotNull()
.findAny().orElse(null)
if (battery != null) {
itemStack.matteryEnergy?.batteryLevel = battery.batteryLevel
}
if (itemStack.isEnchantable) {
for (it in container.items()) {
val enchantments = it[DataComponents.ENCHANTMENTS]
if (enchantments != null) {
itemStack[DataComponents.ENCHANTMENTS] = ItemEnchantments.Mutable(itemStack.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY)).let {
for ((key, level) in enchantments.entrySet()) {
it.upgrade(key, level)
}
it.toImmutable()
}
}
}
}
return itemStack
}
override fun matches(container: CraftingInput, level: Level): Boolean {
return super.matches(container, level) && container.items().none { it.isDamaged }
}
override fun getSerializer(): RecipeSerializer<EnergyContainerRecipe> {
return Companion
}
companion object : RecipeSerializer<EnergyContainerRecipe> {
private val codec by lazy {
RecipeSerializer.SHAPED_RECIPE.codec().xmap(::EnergyContainerRecipe, { it })
}
private val streamCodec by lazy {
RecipeSerializer.SHAPED_RECIPE.streamCodec().map(::EnergyContainerRecipe, { it })
}
override fun codec(): MapCodec<EnergyContainerRecipe> {
return codec
}
override fun streamCodec(): StreamCodec<RegistryFriendlyByteBuf, EnergyContainerRecipe> {
return streamCodec
}
}
}

View File

@ -1,109 +1,59 @@
package ru.dbotthepony.mc.otm.recipe
import com.google.common.collect.ImmutableList
import com.mojang.serialization.Codec
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.NonNullList
import net.minecraft.core.RegistryAccess
import net.minecraft.nbt.CompoundTag
import net.minecraft.core.HolderLookup
import net.minecraft.core.component.DataComponentType
import net.minecraft.core.component.DataComponents
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.network.codec.ByteBufCodecs
import net.minecraft.network.codec.StreamCodec
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.StringRepresentable
import net.minecraft.world.inventory.CraftingContainer
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.crafting.CraftingBookCategory
import net.minecraft.world.item.crafting.CraftingRecipe
import net.minecraft.world.item.crafting.Ingredient
import net.minecraft.world.item.crafting.CraftingInput
import net.minecraft.world.item.crafting.RecipeSerializer
import net.minecraft.world.item.crafting.RecipeType
import net.minecraft.world.item.crafting.ShapedRecipe
import net.minecraft.world.item.crafting.ShapedRecipePattern
import net.minecraft.world.item.enchantment.ItemEnchantments
import net.minecraft.world.level.Level
import net.minecraftforge.common.crafting.IShapedRecipe
import ru.dbotthepony.mc.otm.container.util.stream
import ru.dbotthepony.mc.otm.core.nbt.set
import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.core.registryName
import ru.dbotthepony.mc.otm.core.tagNotNull
import ru.dbotthepony.mc.otm.data.SingletonCodec
import java.util.stream.Stream
class UpgradeRecipe(
val parent: ShapedRecipe,
copyPaths: Stream<Op>,
val source: ResourceLocation,
) : CraftingRecipe, IShapedRecipe<CraftingContainer> by parent {
constructor(parent: ShapedRecipe, copyPaths: Collection<Op>, source: ResourceLocation) : this(parent, copyPaths.stream(), source)
override fun matches(p_44002_: CraftingContainer, p_44003_: Level): Boolean {
return parent.matches(p_44002_, p_44003_)
}
override fun canCraftInDimensions(p_43999_: Int, p_44000_: Int): Boolean {
return parent.canCraftInDimensions(p_43999_, p_44000_)
}
override fun getResultItem(p_267052_: RegistryAccess): ItemStack {
return parent.getResultItem(p_267052_)
}
override fun getRemainingItems(p_44004_: CraftingContainer): NonNullList<ItemStack> {
return parent.getRemainingItems(p_44004_)
}
override fun getIngredients(): NonNullList<Ingredient> {
return parent.ingredients
}
override fun isSpecial(): Boolean {
return parent.isSpecial
}
override fun showNotification(): Boolean {
return parent.showNotification()
}
override fun getGroup(): String {
return parent.group
}
override fun getToastSymbol(): ItemStack {
return parent.toastSymbol
}
override fun isIncomplete(): Boolean {
return parent.isIncomplete
}
override fun getType(): RecipeType<*> {
return parent.type
}
override fun category(): CraftingBookCategory {
return parent.category()
}
group: String,
category: CraftingBookCategory,
pattern: ShapedRecipePattern,
result: ItemStack,
val operations: List<Pair<ResourceLocation, Op>>,
showNotification: Boolean = true,
) : ShapedRecipe(group, category, pattern, result, showNotification) {
constructor(parent: ShapedRecipe, operations: List<Pair<ResourceLocation, Op>>) : this(parent.group, parent.category(), parent.pattern, parent.result, operations, parent.showNotification())
enum class OpType : StringRepresentable {
DIRECT {
override val codec: MapCodec<Direct> = RecordCodecBuilder.mapCodec {
COPY_COMPONENT {
override val codec: MapCodec<CopyComponent<*>> = RecordCodecBuilder.mapCodec {
it.group(
Codec.STRING.fieldOf("path").forGetter(Direct::path)
).apply(it, ::Direct)
DataComponentType.CODEC.fieldOf("component").forGetter(CopyComponent<*>::component)
).apply(it) { CopyComponent(it) }
}
},
INDIRECT {
override val codec: MapCodec<Indirect> = RecordCodecBuilder.mapCodec {
it.group(
Codec.STRING.fieldOf("source").forGetter(Indirect::pathSource),
Codec.STRING.fieldOf("destination").forGetter(Indirect::pathDestination),
).apply(it, ::Indirect)
}
COPY_ALL_COMPONENTS {
override val codec: MapCodec<CopyAllComponents> = MapCodec.unit(CopyAllComponents)
},
ALL {
override val codec: MapCodec<All> by lazy { MapCodec.unit(All) }
COPY_ENCHANTMENTS {
override val codec: MapCodec<CopyEnchantments> = MapCodec.unit(CopyEnchantments)
},
COPY_ENERGY_CHARGE {
override val codec: MapCodec<CopyEnergyCharge> = MapCodec.unit(CopyEnergyCharge)
},
;
private val serName = name.lowercase()
override fun getSerializedName(): String {
return name.lowercase()
return serName
}
abstract val codec: MapCodec<out Op>
@ -111,134 +61,120 @@ class UpgradeRecipe(
sealed class Op {
abstract val type: OpType
abstract fun apply(source: CompoundTag, destination: CompoundTag)
abstract fun apply(source: ItemStack, container: CraftingInput, registry: HolderLookup.Provider, destination: ItemStack)
open fun matches(container: CraftingInput, level: Level): Boolean {
return true
}
}
data class Direct(val path: String) : Op() {
private val split = path.split('.')
data class CopyComponent<T>(val component: DataComponentType<T>) : Op() {
override val type: OpType
get() = OpType.DIRECT
get() = OpType.COPY_COMPONENT
override fun apply(source: CompoundTag, destination: CompoundTag) {
var a = source
var b = destination
override fun apply(source: ItemStack, container: CraftingInput, registry: HolderLookup.Provider, destination: ItemStack) {
val existing = source[component]
for (i in 0 until split.size - 1) {
val value = split[i]
if (value !in a) {
a[value] = CompoundTag()
}
if (value !in b) {
b[value] = CompoundTag()
}
a = a[value] as CompoundTag
b = b[value] as CompoundTag
}
val value = split.last()
if (value in a) {
b[value] = a[value]!!.copy()
if (existing != null) {
destination[component] = existing
}
}
}
data class Indirect(val pathSource: String, val pathDestination: String) : Op() {
private val splitSource = pathSource.split('.')
private val splitDestination = pathDestination.split('.')
object CopyAllComponents : Op() {
override val type: OpType
get() = OpType.INDIRECT
get() = OpType.COPY_ALL_COMPONENTS
override fun apply(source: CompoundTag, destination: CompoundTag) {
var a = source
var b = destination
override fun apply(source: ItemStack, container: CraftingInput, registry: HolderLookup.Provider, destination: ItemStack) {
destination.applyComponentsAndValidate(source.componentsPatch)
}
}
for (i in 0 until splitSource.size - 1) {
val value = splitSource[i]
object CopyEnchantments : Op() {
override val type: OpType
get() = OpType.COPY_ENCHANTMENTS
if (value !in a) {
a[value] = CompoundTag()
override fun apply(source: ItemStack, container: CraftingInput, registry: HolderLookup.Provider, destination: ItemStack) {
val enchantments = source[DataComponents.ENCHANTMENTS]
if (enchantments != null) {
destination[DataComponents.ENCHANTMENTS] = ItemEnchantments.Mutable(destination.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY)).let {
for ((key, level) in enchantments.entrySet()) {
it.upgrade(key, level)
}
it.toImmutable()
}
a = a[value] as CompoundTag
}
for (i in 0 until splitDestination.size - 1) {
val value = splitDestination[i]
if (value !in b) {
b[value] = CompoundTag()
}
b = b[value] as CompoundTag
}
val valueA = splitSource.last()
val valueB = splitDestination.last()
if (valueA in a) {
b[valueB] = a[valueA]!!.copy()
}
}
}
object All : Op() {
object CopyEnergyCharge : Op() {
override val type: OpType
get() = OpType.ALL
get() = OpType.COPY_ENERGY_CHARGE
override fun apply(source: CompoundTag, destination: CompoundTag) {
source.allKeys.forEach {
destination[it] = source[it]!!
}
override fun apply(source: ItemStack, container: CraftingInput, registry: HolderLookup.Provider, destination: ItemStack) {
val batterySource = source.getCapability(MatteryCapability.ITEM_ENERGY) ?: return
val batteryDestination = destination.getCapability(MatteryCapability.ITEM_ENERGY) ?: return
batteryDestination.batteryLevel += batterySource.batteryLevel
}
}
val copyPaths: ImmutableList<Op> = copyPaths.collect(ImmutableList.toImmutableList())
override fun assemble(pInv: CraftingContainer, registryAccess: RegistryAccess): ItemStack {
val result = parent.assemble(pInv, registryAccess)
override fun assemble(input: CraftingInput, registry: HolderLookup.Provider): ItemStack {
val result = super.assemble(input, registry)
if (result.isEmpty) {
return result
}
val sources = pInv.stream().filter { !it.isEmpty && it.item.registryName == source }.toList()
if (sources.size != 1) {
return ItemStack.EMPTY
}
val source = sources.first()
for (op in copyPaths) {
op.apply(source.tagNotNull, result.tagNotNull)
for ((item, op) in operations) {
input.items()
.filter { it.item.registryName == item }
.forEach { op.apply(it, input, registry, result) }
}
return result
}
override fun getSerializer(): RecipeSerializer<UpgradeRecipe> {
return CODEC
override fun matches(p_345040_: CraftingInput, p_44167_: Level): Boolean {
return super.matches(p_345040_, p_44167_) && operations.all { it.second.matches(p_345040_, p_44167_) }
}
companion object {
val COPY_PATHS_CODEC: Codec<List<Op>> = StringRepresentable
.fromEnum(OpType::values)
.dispatch({ it.type }, { it.codec })
.listOf()
override fun getSerializer(): RecipeSerializer<UpgradeRecipe> {
return Companion
}
val CODEC = Codec2RecipeSerializer<UpgradeRecipe> { p ->
RecordCodecBuilder.create {
it.group(
p.wrap(ShapedRecipe.Serializer.SHAPED_RECIPE).fieldOf("parent").forGetter(UpgradeRecipe::parent),
COPY_PATHS_CODEC.fieldOf("copyPaths").forGetter(UpgradeRecipe::copyPaths),
ResourceLocation.CODEC.fieldOf("source").forGetter(UpgradeRecipe::source)
).apply(it, ::UpgradeRecipe)
}
companion object : RecipeSerializer<UpgradeRecipe> {
private val operationCodec: MapCodec<Op> = StringRepresentable
.fromEnum(OpType::values)
.dispatchMap({ it.type }, { it.codec })
private val operationEntryCodec = RecordCodecBuilder.create<Pair<ResourceLocation, Op>> {
it.group(
ResourceLocation.CODEC.fieldOf("item").forGetter(Pair<ResourceLocation, Op>::first),
operationCodec.forGetter(Pair<ResourceLocation, Op>::second),
).apply(it) { a, b -> Pair(a, b) }
}.listOf()
private val codec = RecordCodecBuilder.mapCodec {
it.group(
Serializer.CODEC.forGetter { it },
operationEntryCodec.fieldOf("operations").forGetter(UpgradeRecipe::operations)
).apply(it, ::UpgradeRecipe)
}
private val streamCodec = StreamCodec.composite(
Serializer.STREAM_CODEC, { it },
ByteBufCodecs.fromCodecWithRegistries(operationEntryCodec), { it.operations }, // quite lazy solution
::UpgradeRecipe
)
override fun codec(): MapCodec<UpgradeRecipe> {
return codec
}
override fun streamCodec(): StreamCodec<RegistryFriendlyByteBuf, UpgradeRecipe> {
return streamCodec
}
}
}