Skip to content

Commit

Permalink
Feat/machine components (#15)
Browse files Browse the repository at this point in the history
* Add ComponentTank

* Add ComponentBlockEntity

* fx patched components

* Clean up merge

* resolve some comments

* add helpers

* split up utils, switch to abstract val for initial components
  • Loading branch information
ThatGravyBoat authored Jul 13, 2024
1 parent 80b3a9e commit 1b3d43a
Show file tree
Hide file tree
Showing 12 changed files with 324 additions and 13 deletions.
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ resourcefulgradle = "0.0.+"

# Dependencies
resourcefulLib = "3.0.9"
resourcefulLibKt = "2.0.1"
resourcefulLibKt = "2.0.2"
resourcefulConfig = "3.0.2"
resourcefulConfigKt = "3.0.2"
bytecodecs = "1.1.2"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package earth.terrarium.techarium.mixin.common;

import com.llamalad7.mixinextras.sugar.Local;
import earth.terrarium.techarium.common.blocks.entities.ComponentBlockEntity;
import net.minecraft.core.component.DataComponentMap;
import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.world.level.block.entity.BlockEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

@Mixin(BlockEntity.class)
public abstract class BlockEntityMixin {

@Inject(method = "lambda$loadWithComponents$1", at = @At("HEAD"), cancellable = true)
private void loadWithComponents(DataComponentMap components, CallbackInfo ci) {
Object thisObject = this;
//noinspection ConstantValue
if (!(thisObject instanceof ComponentBlockEntity be)) return;
ci.cancel();
be.setComponents(components);
}

@Inject(
method = "applyComponents",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/core/component/DataComponentPatch;split()Lnet/minecraft/core/component/DataComponentPatch$SplitResult;"
),
cancellable = true
)
private void applyComponents(CallbackInfo ci, @Local(ordinal = 1) DataComponentPatch components) {
Object thisObject = this;
//noinspection ConstantValue
if (!(thisObject instanceof ComponentBlockEntity be)) return;
ci.cancel();
be.applyComponents(components);
}
}
7 changes: 2 additions & 5 deletions src/main/kotlin/earth/terrarium/techarium/common/Techarium.kt
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
package earth.terrarium.techarium.common

import earth.terrarium.techarium.common.registries.ModBlockEntityTypes
import earth.terrarium.techarium.common.registries.ModBlocks
import earth.terrarium.techarium.common.registries.initializeRegistries
import net.neoforged.bus.api.IEventBus
import net.neoforged.fml.ModContainer
import net.neoforged.fml.common.Mod
import software.bernie.geckolib.GeckoLib

@Mod(TechariumConstants.MOD_ID)
class Techarium(modBus: IEventBus, mod: ModContainer) {

init {
println("Hello, ${mod.modInfo.displayName} v${mod.modInfo.version} (common)")

ModBlocks.registry.init()
ModBlockEntityTypes.registry.init()
initializeRegistries()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package earth.terrarium.techarium.common.blocks.entities

import net.minecraft.core.BlockPos
import net.minecraft.core.component.DataComponentMap
import net.minecraft.core.component.DataComponentPatch
import net.minecraft.core.component.DataComponentType
import net.minecraft.core.component.PatchedDataComponentMap
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import net.neoforged.neoforge.common.MutableDataComponentHolder

abstract class ComponentBlockEntity(
type: BlockEntityType<*>,
pos: BlockPos,
state: BlockState
) : BlockEntity(type, pos, state), MutableDataComponentHolder {

abstract val initialComponents: DataComponentMap

override fun getComponents(): DataComponentMap = this.components()

private fun <R> editComponents(action: PatchedDataComponentMap.() -> R): R {
val patched = components as? PatchedDataComponentMap ?: PatchedDataComponentMap(initialComponents).apply { setAll(components) }
val value = patched.let(action)
super.setComponents(patched)
return value
}

override fun <T> set(componentType: DataComponentType<in T>, value: T?): T? =
editComponents { set(componentType, value) }

override fun <T> remove(type: DataComponentType<out T>): T? =
editComponents { remove(type) }

override fun applyComponents(patch: DataComponentPatch) =
editComponents { applyPatch(patch) }

override fun applyComponents(components: DataComponentMap) =
editComponents { setAll(components) }

override fun setComponents(components: DataComponentMap) =
editComponents { setAll(components) }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package earth.terrarium.techarium.common.capabilities.blocks

import earth.terrarium.techarium.common.registries.ModComponents
import earth.terrarium.techarium.common.utils.ComponentSlot
import earth.terrarium.techarium.common.utils.default
import net.neoforged.neoforge.attachment.AttachmentType
import net.neoforged.neoforge.attachment.IAttachmentHolder
import net.neoforged.neoforge.common.MutableDataComponentHolder
import net.neoforged.neoforge.fluids.FluidStack
import net.neoforged.neoforge.fluids.capability.IFluidHandler

typealias FluidValidator = (FluidStack) -> Boolean

open class ComponentFluidHandler protected constructor(
private val slot: ComponentSlot,
private val getter: () -> FluidStack,
private val setter: (FluidStack) -> Unit,
holder: MutableDataComponentHolder,
private val validator: FluidValidator = { true }
) : IFluidHandler {

private val data: Map<ComponentSlot, Int> by ModComponents.tankCapacity.default(emptyMap(), holder)

private val capacity: Int get() = data[slot] ?: 0
private val amount: Int get() = fluid.amount
private val remaining: Int get() = capacity - amount

private var fluid: FluidStack
get() = getter()
set(value) = setter(value)

override fun getTanks() = 1
override fun getTankCapacity(tank: Int) = capacity
override fun getFluidInTank(tank: Int) = fluid
override fun isFluidValid(tank: Int, stack: FluidStack) = validator(stack)

override fun fill(resource: FluidStack, action: IFluidHandler.FluidAction): Int {
if (resource.isEmpty || !isFluidValid(0, resource)) return 0
val amount = resource.amount.coerceAtMost(remaining)
if (amount == 0) return 0
if (fluid.isEmpty) {
if (action.execute()) {
fluid = resource.copyWithAmount(amount)
}
return amount
}
if (FluidStack.isSameFluidSameComponents(fluid, resource)) {
if (action.execute()) {
fluid.amount += amount
}
return amount
}
return 0
}

override fun drain(resource: FluidStack, action: IFluidHandler.FluidAction): FluidStack {
if (resource.isEmpty || !FluidStack.isSameFluidSameComponents(fluid, resource)) return FluidStack.EMPTY
return drain(resource.amount, action)
}

override fun drain(maxDrain: Int, action: IFluidHandler.FluidAction): FluidStack {
val amount = maxDrain.coerceAtMost(amount)
if (amount == 0) return FluidStack.EMPTY
val fluid = fluid.copyWithAmount(amount)
if (action.execute()) {
fluid.amount -= amount
}
return fluid
}

companion object {

fun create(
slot: ComponentSlot,
getter: () -> FluidStack,
setter: (FluidStack) -> Unit,
holder: MutableDataComponentHolder,
validator: FluidValidator = { true }
) = ComponentFluidHandler(slot, getter, setter, holder, validator)

fun <T> create(
slot: ComponentSlot,
type: AttachmentType<FluidStack>,
holder: T,
validator: FluidValidator = { true }
)
where T : MutableDataComponentHolder, T : IAttachmentHolder =
create(slot, { holder.getData(type) }, { holder.setData(type, it) }, holder, validator)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package earth.terrarium.techarium.common.registries

import com.mojang.serialization.Codec
import com.teamresourceful.bytecodecs.base.ByteCodec
import com.teamresourceful.resourcefullib.common.registry.ResourcefulRegistries
import com.teamresourceful.resourcefullib.common.registry.ResourcefulRegistry
import com.teamresourceful.resourcefullibkt.common.component
import com.teamresourceful.resourcefullibkt.common.getValue
import com.teamresourceful.resourcefullibkt.common.persistent
import com.teamresourceful.resourcefullibkt.common.synced
import earth.terrarium.techarium.common.TechariumConstants
import earth.terrarium.techarium.common.utils.ComponentSlot
import net.minecraft.core.component.DataComponentType
import net.minecraft.core.registries.BuiltInRegistries

object ModComponents {

val registry: ResourcefulRegistry<DataComponentType<*>> =
ResourcefulRegistries.create(BuiltInRegistries.DATA_COMPONENT_TYPE, TechariumConstants.MOD_ID)

val tankCapacity: DataComponentType<Map<ComponentSlot, Int>> by registry.register("tank_capacity") {
component {
persistent = Codec.unboundedMap(ComponentSlot.CODEC, Codec.INT)
synced = ByteCodec.mapOf(ComponentSlot.BYTE_CODEC, ByteCodec.INT)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package earth.terrarium.techarium.common.registries

internal fun initializeRegistries() {
ModComponents.registry.init()
ModBlocks.registry.init()
ModBlockEntityTypes.registry.init()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package earth.terrarium.techarium.common.utils

import net.neoforged.neoforge.attachment.AttachmentHolder
import net.neoforged.neoforge.attachment.AttachmentType
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

operator fun <T : Any> AttachmentHolder.get(key: AttachmentType<T>): T = this.getData(key)
operator fun <T : Any> AttachmentHolder.set(key: AttachmentType<T>, value: T) = this.setData(key, value)
operator fun <T : Any> AttachmentHolder.minusAssign(key: AttachmentType<T>) {
this.removeData(key)
}

operator fun <T : Any> AttachmentType<T>.getValue(thisRef: AttachmentHolder, property: KProperty<*>): T = thisRef[this]
operator fun <T : Any> AttachmentType<T>.setValue(thisRef: AttachmentHolder, property: KProperty<*>, value: T) {
thisRef[this] = value
}

class OptionalAttachmentDelegate<T : Any>(private val key: AttachmentType<T>) :
ReadWriteProperty<AttachmentHolder, T?> {
override operator fun getValue(thisRef: AttachmentHolder, property: KProperty<*>): T? =
thisRef.getExistingData(key).orElse(null)

override operator fun setValue(thisRef: AttachmentHolder, property: KProperty<*>, value: T?) {
if (value == null) {
thisRef -= key
} else {
thisRef[key] = value
}
}
}

fun <T : Any> AttachmentType<T>.optional() = OptionalAttachmentDelegate(this)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package earth.terrarium.techarium.common.utils

import com.mojang.serialization.Codec
import com.teamresourceful.bytecodecs.base.ByteCodec
import com.teamresourceful.resourcefullib.common.codecs.EnumCodec

enum class ComponentSlot {
INPUT,
OUTPUT,
;

companion object {
val CODEC: Codec<ComponentSlot> = EnumCodec.of(ComponentSlot::class.java)
val BYTE_CODEC: ByteCodec<ComponentSlot> = ByteCodec.ofEnum(ComponentSlot::class.java)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package earth.terrarium.techarium.common.utils

import net.minecraft.core.component.DataComponentMap
import net.minecraft.core.component.DataComponentType
import net.neoforged.neoforge.common.MutableDataComponentHolder
import kotlin.reflect.KProperty

class ComponentDelegate<T : Any>(private val key: DataComponentType<T>, private val default: T) {
operator fun getValue(thisRef: MutableDataComponentHolder, property: KProperty<*>): T = thisRef[key] ?: default
operator fun setValue(thisRef: MutableDataComponentHolder, property: KProperty<*>, value: T) {
thisRef[key] = value
}
}

class ComponentDelegateWithHolder<T : Any>(
private val holder: MutableDataComponentHolder,
private val key: DataComponentType<T>,
private val default: T
) {
operator fun getValue(thisRef: Any, property: KProperty<*>): T = holder[key] ?: default
operator fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
holder[key] = value
}
}

operator fun <T : Any> DataComponentType<T>.getValue(thisRef: MutableDataComponentHolder, property: KProperty<*>): T? =
thisRef[this]

operator fun <T : Any> DataComponentType<T>.setValue(
thisRef: MutableDataComponentHolder,
property: KProperty<*>,
value: T?
) {
thisRef[this] = value
}

fun <T : Any> DataComponentType<T>.default(default: T) = ComponentDelegate(this, default)
fun <T : Any> DataComponentType<T>.default(default: T, holder: MutableDataComponentHolder) =
ComponentDelegateWithHolder(holder, this, default)

fun buildComponentMap(builder: DataComponentMap.Builder.() -> Unit): DataComponentMap =
DataComponentMap.builder().apply(builder).build()
17 changes: 10 additions & 7 deletions src/main/resources/META-INF/neoforge.mods.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ license = "ARR"
issueTrackerURL = "https://github.com/terrarium-earth/Techarium/issues"

[[mods]]
modId = "techarium"
version = "${version}"
displayName = "Techarium"
displayURL = "https://modrinth.com/mod/techarium"
authors = ""
credits = ""
description = ""
modId = "techarium"
version = "${version}"
displayName = "Techarium"
displayURL = "https://modrinth.com/mod/techarium"
authors = ""
credits = ""
description = ""

[[mixins]]
config = "techarium.mixins.json"

[[dependencies.techarium]]
modId = "neoforge"
Expand Down
12 changes: 12 additions & 0 deletions src/main/resources/techarium.mixins.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"required": true,
"minVersion": "0.8",
"package": "earth.terrarium.techarium.mixin",
"compatibilityLevel": "JAVA_21",
"client": [
],
"mixins": [ "common.BlockEntityMixin" ],
"injectors": {
"defaultRequire": 1
}
}

0 comments on commit 1b3d43a

Please sign in to comment.