diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cc22070..c8e80a0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" diff --git a/src/main/java/earth/terrarium/techarium/mixin/common/BlockEntityMixin.java b/src/main/java/earth/terrarium/techarium/mixin/common/BlockEntityMixin.java new file mode 100644 index 0000000..c159f1b --- /dev/null +++ b/src/main/java/earth/terrarium/techarium/mixin/common/BlockEntityMixin.java @@ -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); + } +} diff --git a/src/main/kotlin/earth/terrarium/techarium/common/Techarium.kt b/src/main/kotlin/earth/terrarium/techarium/common/Techarium.kt index d43085e..7f81c6a 100644 --- a/src/main/kotlin/earth/terrarium/techarium/common/Techarium.kt +++ b/src/main/kotlin/earth/terrarium/techarium/common/Techarium.kt @@ -1,11 +1,9 @@ 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) { @@ -13,7 +11,6 @@ class Techarium(modBus: IEventBus, mod: ModContainer) { init { println("Hello, ${mod.modInfo.displayName} v${mod.modInfo.version} (common)") - ModBlocks.registry.init() - ModBlockEntityTypes.registry.init() + initializeRegistries() } } \ No newline at end of file diff --git a/src/main/kotlin/earth/terrarium/techarium/common/blocks/entities/ComponentBlockEntity.kt b/src/main/kotlin/earth/terrarium/techarium/common/blocks/entities/ComponentBlockEntity.kt new file mode 100644 index 0000000..bb5adaa --- /dev/null +++ b/src/main/kotlin/earth/terrarium/techarium/common/blocks/entities/ComponentBlockEntity.kt @@ -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 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 set(componentType: DataComponentType, value: T?): T? = + editComponents { set(componentType, value) } + + override fun remove(type: DataComponentType): 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) } +} \ No newline at end of file diff --git a/src/main/kotlin/earth/terrarium/techarium/common/capabilities/blocks/ComponentFluidHandler.kt b/src/main/kotlin/earth/terrarium/techarium/common/capabilities/blocks/ComponentFluidHandler.kt new file mode 100644 index 0000000..f2747f6 --- /dev/null +++ b/src/main/kotlin/earth/terrarium/techarium/common/capabilities/blocks/ComponentFluidHandler.kt @@ -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 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 create( + slot: ComponentSlot, + type: AttachmentType, + holder: T, + validator: FluidValidator = { true } + ) + where T : MutableDataComponentHolder, T : IAttachmentHolder = + create(slot, { holder.getData(type) }, { holder.setData(type, it) }, holder, validator) + } +} \ No newline at end of file diff --git a/src/main/kotlin/earth/terrarium/techarium/common/registries/ModComponents.kt b/src/main/kotlin/earth/terrarium/techarium/common/registries/ModComponents.kt new file mode 100644 index 0000000..1be8a0e --- /dev/null +++ b/src/main/kotlin/earth/terrarium/techarium/common/registries/ModComponents.kt @@ -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> = + ResourcefulRegistries.create(BuiltInRegistries.DATA_COMPONENT_TYPE, TechariumConstants.MOD_ID) + + val tankCapacity: DataComponentType> by registry.register("tank_capacity") { + component { + persistent = Codec.unboundedMap(ComponentSlot.CODEC, Codec.INT) + synced = ByteCodec.mapOf(ComponentSlot.BYTE_CODEC, ByteCodec.INT) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/earth/terrarium/techarium/common/registries/RegistryHandler.kt b/src/main/kotlin/earth/terrarium/techarium/common/registries/RegistryHandler.kt new file mode 100644 index 0000000..13b1936 --- /dev/null +++ b/src/main/kotlin/earth/terrarium/techarium/common/registries/RegistryHandler.kt @@ -0,0 +1,7 @@ +package earth.terrarium.techarium.common.registries + +internal fun initializeRegistries() { + ModComponents.registry.init() + ModBlocks.registry.init() + ModBlockEntityTypes.registry.init() +} \ No newline at end of file diff --git a/src/main/kotlin/earth/terrarium/techarium/common/utils/AttachmentUtils.kt b/src/main/kotlin/earth/terrarium/techarium/common/utils/AttachmentUtils.kt new file mode 100644 index 0000000..51a4fbf --- /dev/null +++ b/src/main/kotlin/earth/terrarium/techarium/common/utils/AttachmentUtils.kt @@ -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 AttachmentHolder.get(key: AttachmentType): T = this.getData(key) +operator fun AttachmentHolder.set(key: AttachmentType, value: T) = this.setData(key, value) +operator fun AttachmentHolder.minusAssign(key: AttachmentType) { + this.removeData(key) +} + +operator fun AttachmentType.getValue(thisRef: AttachmentHolder, property: KProperty<*>): T = thisRef[this] +operator fun AttachmentType.setValue(thisRef: AttachmentHolder, property: KProperty<*>, value: T) { + thisRef[this] = value +} + +class OptionalAttachmentDelegate(private val key: AttachmentType) : + ReadWriteProperty { + 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 AttachmentType.optional() = OptionalAttachmentDelegate(this) \ No newline at end of file diff --git a/src/main/kotlin/earth/terrarium/techarium/common/utils/ComponentSlot.kt b/src/main/kotlin/earth/terrarium/techarium/common/utils/ComponentSlot.kt new file mode 100644 index 0000000..6cf28f4 --- /dev/null +++ b/src/main/kotlin/earth/terrarium/techarium/common/utils/ComponentSlot.kt @@ -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 = EnumCodec.of(ComponentSlot::class.java) + val BYTE_CODEC: ByteCodec = ByteCodec.ofEnum(ComponentSlot::class.java) + } +} \ No newline at end of file diff --git a/src/main/kotlin/earth/terrarium/techarium/common/utils/ComponentUtils.kt b/src/main/kotlin/earth/terrarium/techarium/common/utils/ComponentUtils.kt new file mode 100644 index 0000000..91311e2 --- /dev/null +++ b/src/main/kotlin/earth/terrarium/techarium/common/utils/ComponentUtils.kt @@ -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(private val key: DataComponentType, 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( + private val holder: MutableDataComponentHolder, + private val key: DataComponentType, + 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 DataComponentType.getValue(thisRef: MutableDataComponentHolder, property: KProperty<*>): T? = + thisRef[this] + +operator fun DataComponentType.setValue( + thisRef: MutableDataComponentHolder, + property: KProperty<*>, + value: T? +) { + thisRef[this] = value +} + +fun DataComponentType.default(default: T) = ComponentDelegate(this, default) +fun DataComponentType.default(default: T, holder: MutableDataComponentHolder) = + ComponentDelegateWithHolder(holder, this, default) + +fun buildComponentMap(builder: DataComponentMap.Builder.() -> Unit): DataComponentMap = + DataComponentMap.builder().apply(builder).build() \ No newline at end of file diff --git a/src/main/resources/META-INF/neoforge.mods.toml b/src/main/resources/META-INF/neoforge.mods.toml index 0f8d99b..985ffc1 100644 --- a/src/main/resources/META-INF/neoforge.mods.toml +++ b/src/main/resources/META-INF/neoforge.mods.toml @@ -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" diff --git a/src/main/resources/techarium.mixins.json b/src/main/resources/techarium.mixins.json new file mode 100644 index 0000000..1e43055 --- /dev/null +++ b/src/main/resources/techarium.mixins.json @@ -0,0 +1,12 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "earth.terrarium.techarium.mixin", + "compatibilityLevel": "JAVA_21", + "client": [ + ], + "mixins": [ "common.BlockEntityMixin" ], + "injectors": { + "defaultRequire": 1 + } +} \ No newline at end of file