Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Brain DSL (draft) #40

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2022 QuiltMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.quiltmc.qkl.wrapper.minecraft.mixin.brain;

import net.minecraft.entity.ai.brain.sensor.Sensor;
import net.minecraft.entity.ai.brain.sensor.SensorType;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;

import java.util.function.Supplier;

@Mixin(SensorType.class)
public interface SensorTypeAccessor {
@Invoker("<init>")
static <U extends Sensor<?>> SensorType<U> create(Supplier<U> supplier) {
throw new UnsupportedOperationException();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* Copyright 2022 QuiltMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.quiltmc.qkl.wrapper.minecraft.brain

import com.google.common.collect.ImmutableList
import net.minecraft.entity.LivingEntity
import net.minecraft.entity.ai.brain.Activity
import net.minecraft.entity.ai.brain.Brain
import net.minecraft.entity.ai.brain.MemoryModuleState
import net.minecraft.entity.ai.brain.MemoryModuleType
import net.minecraft.entity.ai.brain.task.Task
import kotlin.Pair
import com.mojang.datafixers.util.Pair as DFUPair

@BrainDsl
public class ActivityBuilder<E : LivingEntity> {
private val forgottenMemories = mutableSetOf<MemoryModuleType<*>>()
private val requiredMemories =
mutableMapOf<MemoryModuleType<*>, MemoryModuleState>()
private val prioritizedTasks = mutableListOf<Pair<Int, Task<in E>>>()

@BrainDsl
public fun requiresMemories(config: MemoryRequirements.() -> Unit) {
MemoryRequirements().apply(config)
}

@BrainDsl
public fun forgets(vararg memories: MemoryModuleType<*>) {
forgottenMemories += memories
}

@BrainDsl
public fun tasks(config: ActivityTasks.() -> Unit) {
ActivityTasks().apply(config)
}

public fun assignToBrainActivity(brain: Brain<out E>, activity: Activity) {
brain.setTaskList(
activity,
ImmutableList.copyOf(
prioritizedTasks.map { DFUPair(it.first, it.second) }
),
requiredMemories.map { DFUPair(it.key, it.value) }.toSet(),
forgottenMemories
)
}

public inner class MemoryRequirements {
@BrainDsl
public infix fun MemoryModuleType<*>.toHaveState(state: MemoryModuleState) {
requiredMemories += this to state
}

@BrainDsl
public fun MemoryModuleType<*>.toExist() {
this toHaveState MemoryModuleState.REGISTERED
}

@BrainDsl
public fun MemoryModuleType<*>.toBePresent() {
this toHaveState MemoryModuleState.VALUE_PRESENT
}

@BrainDsl
public fun MemoryModuleType<*>.toBeAbsent() {
this toHaveState MemoryModuleState.VALUE_ABSENT
}
}

public inner class ActivityTasks {
@BrainDsl
public fun task(priority: Int, task: Task<in E>) {
prioritizedTasks += priority to task
}

@BrainDsl
public operator fun Int.minus(task: Task<in E>) {
task(this, task)
}

@BrainDsl
public fun priority(
priority: Int,
config: PrioritizedTasks.() -> Unit
) {
PrioritizedTasks(priority).apply(config)
}

@BrainDsl
public operator fun Int.invoke(config: PrioritizedTasks.() -> Unit) {
priority(this, config)
}

public inner class PrioritizedTasks(private val priority: Int) {
@BrainDsl
public fun task(task: Task<in E>) {
task(priority, task)
}

@BrainDsl
public operator fun Task<in E>.unaryMinus() {
task(this)
}
}
}
}

@BrainDsl
public fun <E : LivingEntity> Brain<E>.activity(
activity: Activity,
config: ActivityBuilder<E>.() -> Unit
) {
ActivityBuilder<E>().apply(config).assignToBrainActivity(this, activity)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright 2022 QuiltMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.quiltmc.qkl.wrapper.minecraft.brain

import com.mojang.serialization.Dynamic
import net.minecraft.entity.LivingEntity
import net.minecraft.entity.ai.brain.Activity
import net.minecraft.entity.ai.brain.Brain
import net.minecraft.entity.ai.brain.MemoryModuleType
import net.minecraft.entity.ai.brain.sensor.Sensor
import net.minecraft.entity.ai.brain.sensor.SensorType

@DslMarker
public annotation class BrainDsl

@BrainDsl
public class BrainBuilder<E : LivingEntity> {
private val memories: MutableSet<MemoryModuleType<*>> = mutableSetOf()
private val sensors: MutableList<SensorType<out Sensor<in E>>> = mutableListOf()
private val activities: MutableMap<Activity, ActivityBuilder<E>> = mutableMapOf()
private val coreActivities: MutableSet<Activity> = mutableSetOf()
private var defaultActivity: Activity? = null

@BrainDsl
public fun memories(vararg addedMemories: MemoryModuleType<*>) {
memories += addedMemories
}

@BrainDsl
public fun sensors(vararg types: SensorType<out Sensor<in E>>) {
sensors += types
memories += types.flatMap { it.create().outputMemoryModules }
}

@BrainDsl
public fun activity(
activity: Activity,
config: ActivityBuilder<E>.() -> Unit
) {
activities += activity to ActivityBuilder<E>().apply(config)
}

@BrainDsl
public fun defaultActivity(
activity: Activity,
config: (ActivityBuilder<E>.() -> Unit)? = null
) {
if (config != null) {
activity(activity, config)
}

defaultActivity = activity
}

@BrainDsl
public fun coreActivity(
activity: Activity,
config: (ActivityBuilder<E>.() -> Unit)? = null
) {
if (config != null) {
activity(activity, config)
}

coreActivities += activity
}

public fun deserializeAndConfigure(data: Dynamic<*>): Brain<E> {
val brain = Brain.createProfile(memories, sensors).deserialize(data)

activities.forEach {
it.value.assignToBrainActivity(brain, it.key)
}

if (defaultActivity != null) {
brain.setDefaultActivity(defaultActivity)
}

brain.setCoreActivities(coreActivities)

//generics on sensors appear to break? might be doing something wrong
@Suppress("UNCHECKED_CAST")
return brain as Brain<E>
}
}

@BrainDsl
public fun <E : LivingEntity> buildBrain(
data: Dynamic<*>,
config: BrainBuilder<E>.() -> Unit
): Brain<E> {
return BrainBuilder<E>().apply(config).deserializeAndConfigure(data)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright 2022 QuiltMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.quiltmc.qkl.wrapper.minecraft.brain

import net.minecraft.entity.ai.brain.Activity
import net.minecraft.entity.ai.brain.Schedule
import net.minecraft.entity.ai.brain.ScheduleBuilder

@BrainDsl
public class ScheduleBuilderScope(
private val scheduleBuilder: ScheduleBuilder
) {
@BrainDsl
public operator fun Int.minus(activity: Activity) {
scheduleBuilder.withActivity(this, activity)
}
}

@BrainDsl
public fun schedule(config: ScheduleBuilderScope.() -> Unit): Schedule {
val schedule = Schedule()

val builder = ScheduleBuilder(schedule)
ScheduleBuilderScope(builder).apply(config)

return builder.build()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright 2022 QuiltMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.quiltmc.qkl.wrapper.minecraft.brain

import net.minecraft.entity.LivingEntity
import net.minecraft.entity.ai.brain.MemoryModuleType
import net.minecraft.entity.ai.brain.sensor.Sensor
import net.minecraft.entity.ai.brain.sensor.SensorType
import net.minecraft.server.world.ServerWorld
import org.quiltmc.qkl.wrapper.minecraft.mixin.brain.SensorTypeAccessor


@BrainDsl
public class SensorBuilder<E : LivingEntity> {
private lateinit var sensor: (ServerWorld, E) -> Unit
public val outputMemories: MutableSet<MemoryModuleType<*>> = mutableSetOf()

@BrainDsl
public fun outputMemories(vararg memories: MemoryModuleType<*>) {
outputMemories += memories
}

@BrainDsl
public fun sense(sensor: (world: ServerWorld, entity: E) -> Unit) {
this.sensor = sensor
}

public fun build(): SensorType<Sensor<in E>> {
val memories = outputMemories.toSet()

if (!this::sensor.isInitialized) {
throw IllegalStateException("Sensor has not been provided with a sense block")
}

return SensorTypeAccessor.create {
object : Sensor<E>() {
override fun sense(world: ServerWorld, entity: E) {
sensor(world, entity)
}

override fun getOutputMemoryModules(): Set<MemoryModuleType<*>> {
return memories
}
}
}
}
}

@BrainDsl
public fun <E : LivingEntity> sensor(config: SensorBuilder<E>.() -> Unit): SensorType<Sensor<in E>> {
return SensorBuilder<E>().apply(config).build()
}
Loading