diff --git a/src/commonMain/kotlin/core/ai/packages/AIPackageTemplateBuilder.kt b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplateBuilder.kt index 3db44181..210afd35 100644 --- a/src/commonMain/kotlin/core/ai/packages/AIPackageTemplateBuilder.kt +++ b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplateBuilder.kt @@ -2,12 +2,9 @@ package core.ai.packages import core.thing.Thing -//Build all the templates, but don't flatten them -//Using the dependency injection, we could have many things producing the templates -//Once all templates generated, validate / transform them into ai packages //A mind references a single ai package by string //Ideas _should_ only have a single instance, so can possibly be optimized - +// How do we specify additional priority? Do we need to? class AIPackageTemplateBuilder(val name: String) { private val templates = mutableListOf() @@ -26,11 +23,11 @@ class AIPackageTemplateBuilder(val name: String) { /** Override the priority of an idea inherited from a template. */ - fun priority(ideaName: String, newPriority: Int){ + fun priority(ideaName: String, newPriority: Int) { priorityOverride[ideaName] = newPriority } - fun drop(vararg ideaName: String){ + fun drop(vararg ideaName: String) { dropped.addAll(ideaName) } @@ -38,8 +35,13 @@ class AIPackageTemplateBuilder(val name: String) { ideas.add(IdeaBuilder(name, priority).apply(initializer)) } - fun criteria(criteria: suspend (Thing) -> Boolean, initializer: IdeasBuilder.() -> Unit){ - criteriaChildren[criteria] = IdeasBuilder().apply(initializer) + fun tagged(tag: String, initializer: IdeasBuilder.() -> Unit) { + val cond: suspend (Thing) -> Boolean = { it.properties.tags.has(tag) } + criteriaChildren[cond] = IdeasBuilder().apply(initializer) + } + + fun cond(condition: suspend (Thing) -> Boolean, initializer: IdeasBuilder.() -> Unit) { + criteriaChildren[condition] = IdeasBuilder().apply(initializer) } } @@ -51,11 +53,11 @@ fun aiPackage(name: String, initializer: AIPackageTemplateBuilder.() -> Unit): A class AIPackageTemplatesBuilder { internal val children = mutableListOf() - fun aiPackage(item: AIPackageTemplateBuilder){ + fun aiPackage(item: AIPackageTemplateBuilder) { children.add(item) } - fun aiPackage(name: String, initializer: AIPackageTemplateBuilder.() -> Unit){ + fun aiPackage(name: String, initializer: AIPackageTemplateBuilder.() -> Unit) { children.add(AIPackageTemplateBuilder(name).apply(initializer)) } diff --git a/src/commonMain/kotlin/core/ai/packages/IdeaBuilder.kt b/src/commonMain/kotlin/core/ai/packages/IdeaBuilder.kt index 56cfe404..62213156 100644 --- a/src/commonMain/kotlin/core/ai/packages/IdeaBuilder.kt +++ b/src/commonMain/kotlin/core/ai/packages/IdeaBuilder.kt @@ -9,15 +9,15 @@ class IdeaBuilder(val name: String, val priority: Int) { fun build(packageName: String) = Idea("$packageName-$name", priority, criteria, action) - fun criteria(criteria: suspend (Thing) -> Boolean) { - this.criteria = criteria + fun cond(condition: suspend (Thing) -> Boolean) { + this.criteria = condition } fun actions(action: suspend (Thing) -> List) { this.action = action } - fun action(action: suspend (Thing) -> Event) { + fun act(action: suspend (Thing) -> Event) { this.action = { listOf(action(it)) } } diff --git a/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt b/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt index 207e025c..59ab80f5 100644 --- a/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt +++ b/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt @@ -1,36 +1,96 @@ package resources.ai.packages +import combat.DamageType +import combat.attack.AttackEvent +import combat.attack.startAttack +import core.GameState +import core.ai.knowledge.DiscoverFactEvent +import core.ai.knowledge.Fact +import core.ai.knowledge.Subject import core.ai.packages.AIPackageTemplateResource import core.ai.packages.aiPackages +import core.events.Event +import core.properties.Properties +import core.properties.Values +import core.thing.Thing +import core.utility.RandomManager import status.rest.RestEvent +import status.stat.STAMINA +import traveling.move.startMoveEvent +import traveling.position.ThingAim import use.eat.EatFoodEvent +import use.interaction.nothing.NothingEvent -class CommonPackages: AIPackageTemplateResource { - override val values = aiPackages { - aiPackage("animal") { - criteria({ !it.isSafe() }) { - criteria({ !it.isSafe() }) { - idea("eat", 10) { - criteria { !it.isSafe() } - action { EatFoodEvent(it, it) } - } - idea("wait") { - criteria { !it.isSafe() } - action { EatFoodEvent(it, it) } - } +class CommonPackages : AIPackageTemplateResource { + override val values = aiPackages { + aiPackage("creature") { + + idea("Rest") { + cond { s -> s.soul.getCurrent(STAMINA) < s.soul.getTotal(STAMINA) / 10 } + act { RestEvent(it, 2) } + } + + idea("Attack", 70) { + cond { it.mind.getAggroTarget() != null } + act { + clawAttack(it.mind.getAggroTarget()!!, it) + } + } + + //TODO - need move to location as well + idea("Move to Use Target", 50) { + cond { it.hasUseTarget() && !it.canReach(it.mind.getUseTarget()!!.position) } + act { startMoveEvent(it, destination = it.mind.getUseTarget()!!.position) } + } + + idea("Eat Targeted Food", 50) { + //TODO get fact itself, check properties to see if goal is eat + cond { + val useTarget = it.mind.getUseTarget() + useTarget != null && it.canReach(useTarget.position) } + act { EatFoodEvent(it, it.mind.getUseTarget()!!) } } } - aiPackage("predator") { - template("animal") - priority("animal-eat", 15) - drop("animal-wait") - - idea("hunt") { - criteria { it.soul.getConditions().isEmpty() } - action { RestEvent(it, 1) } + + aiPackage("Commoner") { + template("creature") + idea("Want Food") { + cond { GameState.timeManager.getPercentDayComplete() in listOf(25, 50, 75) } + act { owner -> + val target = (owner.inventory.getItems() + owner.location.getLocation().getItems(perceivedBy = owner)).firstOrNull { it.properties.tags.has("Food") } + target?.let { + owner.setGoal(target, "eat") + } ?: NothingEvent(owner) + } } } + } +} + +private suspend fun Thing.hasUseTarget() = mind.getUseTarget() != null +private fun Thing.setGoal(target: Thing, howToUse: String): DiscoverFactEvent{ + return DiscoverFactEvent(this, Fact(Subject(target), "useTarget", Properties(Values(mutableMapOf("goal" to howToUse))))) +} + +//TODO - forget goal +private fun Thing.clearGoal(target: Thing, howToUse: String): List{ + return emptyList() +} + +private suspend fun clawAttack(target: Thing, creature: Thing): AttackEvent { + val enemyBody = target.body + val possibleParts = listOf( + enemyBody.getPart("Right Foot"), + enemyBody.getPart("Left Foot") + ) + val thingPart = listOf(RandomManager.getRandom(possibleParts)) + val partToAttackWith = if (creature.body.hasPart("Small Claws")) { + creature.body.getPart("Small Claws") + } else { + creature.body.getRootPart() + } + return startAttack(creature, partToAttackWith, ThingAim(GameState.player.thing, thingPart), DamageType.SLASH) } \ No newline at end of file