From 2373878d092bbbb109d9887a421def914233e553 Mon Sep 17 00:00:00 2001 From: ManApart Date: Wed, 17 Jan 2024 08:17:51 -0500 Subject: [PATCH 01/45] ideas on simplier ai --- .idea/vcs.xml | 6 ---- .../kotlin/core/ai/composableExp/AIPackage.kt | 24 ++++++++++++++ .../composableExp/AIPackageTemplateBuilder.kt | 33 +++++++++++++++++++ .../AIPackageTemplatesBuilder.kt | 18 ++++++++++ .../core/ai/composableExp/IdeaBuilder.kt | 26 +++++++++++++++ .../kotlin/core/ai/composableExp/TestSheet.kt | 17 ++++++++++ 6 files changed, 118 insertions(+), 6 deletions(-) delete mode 100644 .idea/vcs.xml create mode 100644 src/commonMain/kotlin/core/ai/composableExp/AIPackage.kt create mode 100644 src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplateBuilder.kt create mode 100644 src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplatesBuilder.kt create mode 100644 src/commonMain/kotlin/core/ai/composableExp/IdeaBuilder.kt create mode 100644 src/commonMain/kotlin/core/ai/composableExp/TestSheet.kt diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1ddfb..000000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/commonMain/kotlin/core/ai/composableExp/AIPackage.kt b/src/commonMain/kotlin/core/ai/composableExp/AIPackage.kt new file mode 100644 index 000000000..6da1bad88 --- /dev/null +++ b/src/commonMain/kotlin/core/ai/composableExp/AIPackage.kt @@ -0,0 +1,24 @@ +package core.ai.composableExp + +import core.events.Event +import core.thing.Thing + +//How does priority work? Is it just based on order definition? Then what about sub modules +//If everything is order based, then all top level ideas happen before any children, and children are priority per order +//Could also provide priority override map of name to priority (string,Int) +data class Idea(val name: String, val criteria: suspend (Thing) -> Boolean, val action: suspend (Thing) -> List) + +//TODO - we can validate string references before flattening +data class AIPackageTemplate(val name: String, val subPackages: List, val ideas: List) { + fun flatten(reference: Map, flattenedReference: MutableMap): AIPackage { + return flattenedReference[name] ?: let { + val subIdeas = subPackages.mapNotNull { reference[it] }.flatMap { subPackage -> + flattenedReference[subPackage.name]?.ideas + ?: subPackage.flatten(reference, flattenedReference).also { flattenedReference[subPackage.name] = it }.ideas + } + AIPackage(name, ideas + subIdeas) + } + } +} + +data class AIPackage(val name: String, val ideas: List) diff --git a/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplateBuilder.kt b/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplateBuilder.kt new file mode 100644 index 000000000..95920558b --- /dev/null +++ b/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplateBuilder.kt @@ -0,0 +1,33 @@ +package core.ai.composableExp + +import core.events.Event +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 + + +class AIPackageTemplateBuilder(val name: String) { + private val templates = mutableListOf() + private val ideas = mutableListOf() + + + fun build(): AIPackageTemplate { + return AIPackageTemplate(name, templates, ideas) + } + + fun template(vararg names: String) = this.templates.addAll(names) + + fun idea(name: String, initializer: IdeaBuilder.() -> Unit = {}) { + ideas.add(IdeaBuilder(name).apply(initializer).build()) + } + +} + +//DO list of +fun aiPackage(name: String, initializer: AIPackageTemplateBuilder.() -> Unit): AIPackageTemplate { + return AIPackageTemplateBuilder(name).apply(initializer).build() +} \ No newline at end of file diff --git a/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplatesBuilder.kt b/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplatesBuilder.kt new file mode 100644 index 000000000..5583cf9d8 --- /dev/null +++ b/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplatesBuilder.kt @@ -0,0 +1,18 @@ +package core.ai.composableExp + + +class AIPackageTemplatesBuilder { + internal val children = mutableListOf() + fun aiPackage(item: AIPackageTemplateBuilder){ + children.add(item) + } + + fun aiPackage(name: String, initializer: AIPackageTemplateBuilder.() -> Unit){ + children.add(AIPackageTemplateBuilder(name).apply(initializer)) + } + +} + +fun aiPackages(initializer: AIPackageTemplatesBuilder.() -> Unit): List { + return AIPackageTemplatesBuilder().apply(initializer).children.map { it.build() } +} \ No newline at end of file diff --git a/src/commonMain/kotlin/core/ai/composableExp/IdeaBuilder.kt b/src/commonMain/kotlin/core/ai/composableExp/IdeaBuilder.kt new file mode 100644 index 000000000..42b0bd226 --- /dev/null +++ b/src/commonMain/kotlin/core/ai/composableExp/IdeaBuilder.kt @@ -0,0 +1,26 @@ +package core.ai.composableExp + +import core.events.Event +import core.thing.Thing + +class IdeaBuilder(val name: String) { + private var criteria: suspend (Thing) -> Boolean = { true } + private var action: suspend (Thing) -> List = { listOf() } + + + fun build() = Idea(name, criteria, action) + + fun criteria(criteria: suspend (Thing) -> Boolean) { + this.criteria = criteria + } + + fun actions(action: suspend (Thing) -> List) { + this.action = action + } + + fun action(action: suspend (Thing) -> Event) { + this.action = { listOf(action(it)) } + } + +} + diff --git a/src/commonMain/kotlin/core/ai/composableExp/TestSheet.kt b/src/commonMain/kotlin/core/ai/composableExp/TestSheet.kt new file mode 100644 index 000000000..5d591924e --- /dev/null +++ b/src/commonMain/kotlin/core/ai/composableExp/TestSheet.kt @@ -0,0 +1,17 @@ +package core.ai.composableExp + +import use.eat.EatFoodEvent + +fun testAiPackages() { + aiPackages { + aiPackage("animal") { + idea("eat") { + criteria { !it.isSafe() } + action { EatFoodEvent(it, it) } + } + } + aiPackage("predator") { + template("animal") + } + } +} \ No newline at end of file From b006ad90ce1843e83cd0529d97d611f530adb78b Mon Sep 17 00:00:00 2001 From: ManApart Date: Thu, 18 Jan 2024 07:47:29 -0500 Subject: [PATCH 02/45] nested criteria for ideas --- .../kotlin/core/ai/composableExp/AIPackage.kt | 4 +++ .../composableExp/AIPackageTemplateBuilder.kt | 21 ++++++++++++ .../AIPackageTemplatesBuilder.kt | 18 ---------- .../core/ai/composableExp/IdeaBuilder.kt | 34 +++++++++++++++++-- .../kotlin/core/ai/composableExp/TestSheet.kt | 19 +++++++++-- 5 files changed, 73 insertions(+), 23 deletions(-) delete mode 100644 src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplatesBuilder.kt diff --git a/src/commonMain/kotlin/core/ai/composableExp/AIPackage.kt b/src/commonMain/kotlin/core/ai/composableExp/AIPackage.kt index 6da1bad88..d5ba75655 100644 --- a/src/commonMain/kotlin/core/ai/composableExp/AIPackage.kt +++ b/src/commonMain/kotlin/core/ai/composableExp/AIPackage.kt @@ -6,6 +6,10 @@ import core.thing.Thing //How does priority work? Is it just based on order definition? Then what about sub modules //If everything is order based, then all top level ideas happen before any children, and children are priority per order //Could also provide priority override map of name to priority (string,Int) +//give a base priority, and then packages can override that priority, with the top package overriding the override of a child package +//When evaluating which idea to go with, map all ideas by priority, and grab the first idea that matches at a given priority list (or random at that level?) + +//TODO - make name unique by prefixing it with parant ai package? How do we prevent name collisions? data class Idea(val name: String, val criteria: suspend (Thing) -> Boolean, val action: suspend (Thing) -> List) //TODO - we can validate string references before flattening diff --git a/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplateBuilder.kt b/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplateBuilder.kt index 95920558b..d215ca94d 100644 --- a/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplateBuilder.kt +++ b/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplateBuilder.kt @@ -13,6 +13,7 @@ import core.thing.Thing class AIPackageTemplateBuilder(val name: String) { private val templates = mutableListOf() private val ideas = mutableListOf() + private val criteriaChildren = mutableMapOf Boolean, IdeasBuilder>() fun build(): AIPackageTemplate { @@ -25,9 +26,29 @@ class AIPackageTemplateBuilder(val name: String) { ideas.add(IdeaBuilder(name).apply(initializer).build()) } + fun criteria(criteria: suspend (Thing) -> Boolean, initializer: IdeasBuilder.() -> Unit){ + criteriaChildren[criteria] = IdeasBuilder().apply(initializer) + } + } //DO list of fun aiPackage(name: String, initializer: AIPackageTemplateBuilder.() -> Unit): AIPackageTemplate { return AIPackageTemplateBuilder(name).apply(initializer).build() +} + +class AIPackageTemplatesBuilder { + internal val children = mutableListOf() + fun aiPackage(item: AIPackageTemplateBuilder){ + children.add(item) + } + + fun aiPackage(name: String, initializer: AIPackageTemplateBuilder.() -> Unit){ + children.add(AIPackageTemplateBuilder(name).apply(initializer)) + } + +} + +fun aiPackages(initializer: AIPackageTemplatesBuilder.() -> Unit): List { + return AIPackageTemplatesBuilder().apply(initializer).children.map { it.build() } } \ No newline at end of file diff --git a/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplatesBuilder.kt b/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplatesBuilder.kt deleted file mode 100644 index 5583cf9d8..000000000 --- a/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplatesBuilder.kt +++ /dev/null @@ -1,18 +0,0 @@ -package core.ai.composableExp - - -class AIPackageTemplatesBuilder { - internal val children = mutableListOf() - fun aiPackage(item: AIPackageTemplateBuilder){ - children.add(item) - } - - fun aiPackage(name: String, initializer: AIPackageTemplateBuilder.() -> Unit){ - children.add(AIPackageTemplateBuilder(name).apply(initializer)) - } - -} - -fun aiPackages(initializer: AIPackageTemplatesBuilder.() -> Unit): List { - return AIPackageTemplatesBuilder().apply(initializer).children.map { it.build() } -} \ No newline at end of file diff --git a/src/commonMain/kotlin/core/ai/composableExp/IdeaBuilder.kt b/src/commonMain/kotlin/core/ai/composableExp/IdeaBuilder.kt index 42b0bd226..0aea46c81 100644 --- a/src/commonMain/kotlin/core/ai/composableExp/IdeaBuilder.kt +++ b/src/commonMain/kotlin/core/ai/composableExp/IdeaBuilder.kt @@ -2,13 +2,14 @@ package core.ai.composableExp import core.events.Event import core.thing.Thing +import kotlinx.coroutines.NonCancellable.children class IdeaBuilder(val name: String) { - private var criteria: suspend (Thing) -> Boolean = { true } + internal var criteria: suspend (Thing) -> Boolean = { true } private var action: suspend (Thing) -> List = { listOf() } - fun build() = Idea(name, criteria, action) + fun build(packageName: String) = Idea("$packageName-$name", criteria, action) fun criteria(criteria: suspend (Thing) -> Boolean) { this.criteria = criteria @@ -24,3 +25,32 @@ class IdeaBuilder(val name: String) { } +class IdeasBuilder { + private val children = mutableListOf() + private val criteriaChildren = mutableMapOf Boolean, IdeasBuilder>() + + fun getChildren(parentCriteria: suspend (Thing) -> Boolean = { true }): List { + children.forEach { it.criteria = { thing -> parentCriteria(thing) && it.criteria(thing) } } + + return children + criteriaChildren.flatMap { (criteria, child) -> + child.getChildren { thing -> parentCriteria(thing) && criteria(thing) } + } + } + + fun idea(item: IdeaBuilder) { + children.add(item) + } + + fun idea(name: String, initializer: IdeaBuilder.() -> Unit) { + children.add(IdeaBuilder(name).apply(initializer)) + } + + fun criteria(criteria: suspend (Thing) -> Boolean, initializer: IdeasBuilder.() -> Unit) { + criteriaChildren[criteria] = IdeasBuilder().apply(initializer) + } + +} + +fun ideas(initializer: IdeasBuilder.() -> Unit): List { + return IdeasBuilder().apply(initializer).getChildren() +} \ No newline at end of file diff --git a/src/commonMain/kotlin/core/ai/composableExp/TestSheet.kt b/src/commonMain/kotlin/core/ai/composableExp/TestSheet.kt index 5d591924e..8d1019591 100644 --- a/src/commonMain/kotlin/core/ai/composableExp/TestSheet.kt +++ b/src/commonMain/kotlin/core/ai/composableExp/TestSheet.kt @@ -1,17 +1,30 @@ package core.ai.composableExp +import status.rest.RestEvent import use.eat.EatFoodEvent fun testAiPackages() { aiPackages { aiPackage("animal") { - idea("eat") { - criteria { !it.isSafe() } - action { EatFoodEvent(it, it) } + criteria({ !it.isSafe() }) { + criteria({ !it.isSafe() }) { + idea("eat") { + criteria { !it.isSafe() } + action { EatFoodEvent(it, it) } + } + idea("wait") { + criteria { !it.isSafe() } + action { EatFoodEvent(it, it) } + } + } } } aiPackage("predator") { template("animal") + idea("hunt") { + criteria { it.soul.getConditions().isEmpty() } + action { RestEvent(it, 1) } + } } } } \ No newline at end of file From 199e80bb9e951a5df235e4c457f4b5800f1a04d2 Mon Sep 17 00:00:00 2001 From: ManApart Date: Thu, 18 Jan 2024 08:03:14 -0500 Subject: [PATCH 03/45] idea priority --- .../kotlin/core/ai/composableExp/AIPackage.kt | 15 ++++++++------- .../composableExp/AIPackageTemplateBuilder.kt | 18 ++++++++++++------ .../core/ai/composableExp/IdeaBuilder.kt | 9 ++++----- .../kotlin/core/ai/composableExp/TestSheet.kt | 3 ++- 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/commonMain/kotlin/core/ai/composableExp/AIPackage.kt b/src/commonMain/kotlin/core/ai/composableExp/AIPackage.kt index d5ba75655..33934a56f 100644 --- a/src/commonMain/kotlin/core/ai/composableExp/AIPackage.kt +++ b/src/commonMain/kotlin/core/ai/composableExp/AIPackage.kt @@ -3,22 +3,23 @@ package core.ai.composableExp import core.events.Event import core.thing.Thing -//How does priority work? Is it just based on order definition? Then what about sub modules -//If everything is order based, then all top level ideas happen before any children, and children are priority per order -//Could also provide priority override map of name to priority (string,Int) -//give a base priority, and then packages can override that priority, with the top package overriding the override of a child package //When evaluating which idea to go with, map all ideas by priority, and grab the first idea that matches at a given priority list (or random at that level?) -//TODO - make name unique by prefixing it with parant ai package? How do we prevent name collisions? -data class Idea(val name: String, val criteria: suspend (Thing) -> Boolean, val action: suspend (Thing) -> List) +data class Idea(val name: String, val priority: Int, val criteria: suspend (Thing) -> Boolean, val action: suspend (Thing) -> List) //TODO - we can validate string references before flattening -data class AIPackageTemplate(val name: String, val subPackages: List, val ideas: List) { +//Validate +// - package name unique +// - idea name unique +// - string references exist +data class AIPackageTemplate(val name: String, val subPackages: List, val priorityOverride: Map, val ideas: List) { fun flatten(reference: Map, flattenedReference: MutableMap): AIPackage { return flattenedReference[name] ?: let { val subIdeas = subPackages.mapNotNull { reference[it] }.flatMap { subPackage -> flattenedReference[subPackage.name]?.ideas ?: subPackage.flatten(reference, flattenedReference).also { flattenedReference[subPackage.name] = it }.ideas + }.map { idea -> + priorityOverride[it.name]?.let { idea.copy(priority = it) } ?: idea } AIPackage(name, ideas + subIdeas) } diff --git a/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplateBuilder.kt b/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplateBuilder.kt index d215ca94d..39c2eab25 100644 --- a/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplateBuilder.kt +++ b/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplateBuilder.kt @@ -1,6 +1,5 @@ package core.ai.composableExp -import core.events.Event import core.thing.Thing //Build all the templates, but don't flatten them @@ -12,18 +11,25 @@ import core.thing.Thing class AIPackageTemplateBuilder(val name: String) { private val templates = mutableListOf() - private val ideas = mutableListOf() + private val ideas = mutableListOf() private val criteriaChildren = mutableMapOf Boolean, IdeasBuilder>() - + private val priorityOverride = mutableMapOf() fun build(): AIPackageTemplate { - return AIPackageTemplate(name, templates, ideas) + return AIPackageTemplate(name, templates, priorityOverride, ideas.map { it.build(name) }) } fun template(vararg names: String) = this.templates.addAll(names) - fun idea(name: String, initializer: IdeaBuilder.() -> Unit = {}) { - ideas.add(IdeaBuilder(name).apply(initializer).build()) + /** + Override the priority of an idea inherited from a template. + */ + fun priority(ideaName: String, newPriority: Int){ + + } + + fun idea(name: String, priority: Int = 20, initializer: IdeaBuilder.() -> Unit = {}) { + ideas.add(IdeaBuilder(name, priority).apply(initializer)) } fun criteria(criteria: suspend (Thing) -> Boolean, initializer: IdeasBuilder.() -> Unit){ diff --git a/src/commonMain/kotlin/core/ai/composableExp/IdeaBuilder.kt b/src/commonMain/kotlin/core/ai/composableExp/IdeaBuilder.kt index 0aea46c81..441fb31a7 100644 --- a/src/commonMain/kotlin/core/ai/composableExp/IdeaBuilder.kt +++ b/src/commonMain/kotlin/core/ai/composableExp/IdeaBuilder.kt @@ -4,12 +4,11 @@ import core.events.Event import core.thing.Thing import kotlinx.coroutines.NonCancellable.children -class IdeaBuilder(val name: String) { +class IdeaBuilder(val name: String, val priority: Int) { internal var criteria: suspend (Thing) -> Boolean = { true } private var action: suspend (Thing) -> List = { listOf() } - - fun build(packageName: String) = Idea("$packageName-$name", criteria, action) + fun build(packageName: String) = Idea("$packageName-$name", priority, criteria, action) fun criteria(criteria: suspend (Thing) -> Boolean) { this.criteria = criteria @@ -41,8 +40,8 @@ class IdeasBuilder { children.add(item) } - fun idea(name: String, initializer: IdeaBuilder.() -> Unit) { - children.add(IdeaBuilder(name).apply(initializer)) + fun idea(name: String, priority: Int = 20, initializer: IdeaBuilder.() -> Unit) { + children.add(IdeaBuilder(name, priority).apply(initializer)) } fun criteria(criteria: suspend (Thing) -> Boolean, initializer: IdeasBuilder.() -> Unit) { diff --git a/src/commonMain/kotlin/core/ai/composableExp/TestSheet.kt b/src/commonMain/kotlin/core/ai/composableExp/TestSheet.kt index 8d1019591..0834c1f56 100644 --- a/src/commonMain/kotlin/core/ai/composableExp/TestSheet.kt +++ b/src/commonMain/kotlin/core/ai/composableExp/TestSheet.kt @@ -8,7 +8,7 @@ fun testAiPackages() { aiPackage("animal") { criteria({ !it.isSafe() }) { criteria({ !it.isSafe() }) { - idea("eat") { + idea("eat", 10) { criteria { !it.isSafe() } action { EatFoodEvent(it, it) } } @@ -20,6 +20,7 @@ fun testAiPackages() { } } aiPackage("predator") { + priority("eat", 15) template("animal") idea("hunt") { criteria { it.soul.getConditions().isEmpty() } From 823dea83213f2e698ecd29561e9962f2ec61c48e Mon Sep 17 00:00:00 2001 From: ManApart Date: Thu, 18 Jan 2024 08:23:55 -0500 Subject: [PATCH 04/45] start injecting into DI --- src/commonMain/kotlin/building/ModManager.kt | 4 +++ .../kotlin/core/DependencyInjector.kt | 3 +++ src/commonMain/kotlin/core/ai/AIManager2.kt | 25 +++++++++++++++++++ .../AIPackageTemplateResource.kt | 5 ++++ .../AIPackageTemplatesCollection.kt | 6 +++++ .../AIPackageTemplatesGenerated.kt | 5 ++++ .../core/events/EventListenerMapGenerated.kt | 2 +- .../composableExp/AIPackageTemplatesMock.kt | 4 +++ .../core/ai/composableExp/AIPackagesMock.kt | 4 +++ .../kotlin/building/ReflectionTools.kt | 4 +++ 10 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 src/commonMain/kotlin/core/ai/AIManager2.kt create mode 100644 src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplateResource.kt create mode 100644 src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplatesCollection.kt create mode 100644 src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplatesGenerated.kt create mode 100644 src/jvmTest/kotlin/core/ai/composableExp/AIPackageTemplatesMock.kt create mode 100644 src/jvmTest/kotlin/core/ai/composableExp/AIPackagesMock.kt diff --git a/src/commonMain/kotlin/building/ModManager.kt b/src/commonMain/kotlin/building/ModManager.kt index 9d9f50d8b..e4ce126d4 100644 --- a/src/commonMain/kotlin/building/ModManager.kt +++ b/src/commonMain/kotlin/building/ModManager.kt @@ -3,6 +3,8 @@ package building import conversation.dsl.DialogueTree import core.ai.agenda.Agenda import core.ai.behavior.Behavior +import core.ai.composableExp.AIPackage +import core.ai.composableExp.AIPackageTemplate import core.ai.desire.DesireTree import core.events.EventListener import core.thing.ThingBuilder @@ -19,6 +21,7 @@ object ModManager { val eventListeners = mutableMapOf>>() val activators = mutableListOf() val ai = mutableListOf() + val ai2 = mutableListOf() val agendas = mutableListOf() val behaviors = mutableListOf>() val bodies = mutableListOf() @@ -38,6 +41,7 @@ object ModManager { fun reset(){ activators.clear() ai.clear() + ai2.clear() agendas.clear() behaviors.clear() bodies.clear() diff --git a/src/commonMain/kotlin/core/DependencyInjector.kt b/src/commonMain/kotlin/core/DependencyInjector.kt index 0aa53f1ed..c6078c973 100644 --- a/src/commonMain/kotlin/core/DependencyInjector.kt +++ b/src/commonMain/kotlin/core/DependencyInjector.kt @@ -6,6 +6,8 @@ import core.ai.behavior.BehaviorsCollection import core.ai.behavior.BehaviorsGenerated import core.ai.agenda.AgendasCollection import core.ai.agenda.AgendasGenerated +import core.ai.composableExp.AIPackageTemplatesCollection +import core.ai.composableExp.AIPackageTemplatesGenerated import core.ai.desire.DesiresCollection import core.ai.desire.DesiresGenerated import core.body.BodyPartsCollection @@ -78,6 +80,7 @@ object DependencyInjector { return mapOf( ActivatorsCollection::class to ActivatorsGenerated(), AgendasCollection::class to AgendasGenerated(), + AIPackageTemplatesCollection::class to AIPackageTemplatesGenerated(), BehaviorsCollection::class to BehaviorsGenerated(), BodysCollection::class to BodysGenerated(), BodyPartsCollection::class to BodyPartsGenerated(), diff --git a/src/commonMain/kotlin/core/ai/AIManager2.kt b/src/commonMain/kotlin/core/ai/AIManager2.kt new file mode 100644 index 000000000..e801c266e --- /dev/null +++ b/src/commonMain/kotlin/core/ai/AIManager2.kt @@ -0,0 +1,25 @@ +package core.ai + +import building.ModManager +import core.DependencyInjector +import core.ai.composableExp.AIPackage +import core.ai.composableExp.AIPackageTemplatesCollection +import core.startupLog +import core.utility.lazyM + +object AIManager2 { + var aiPackages by lazyM { loadAIPackages() } + private set + + private fun loadAIPackages(): Map { + startupLog("Loading AI Packages.") + val templates = (DependencyInjector.getImplementation(AIPackageTemplatesCollection::class).values + ModManager.ai2).associateBy { it.name } + val flattenedReference = mutableMapOf() + return templates.values.map { it.flatten(templates, flattenedReference) }.associateBy { it.name } + } + + fun reset() { + aiPackages = loadAIPackages() + } + +} \ No newline at end of file diff --git a/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplateResource.kt b/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplateResource.kt new file mode 100644 index 000000000..700a97d07 --- /dev/null +++ b/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplateResource.kt @@ -0,0 +1,5 @@ +package core.ai.composableExp + +interface AIPackageTemplateResource { + val values: List +} \ No newline at end of file diff --git a/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplatesCollection.kt b/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplatesCollection.kt new file mode 100644 index 000000000..727f3d0ef --- /dev/null +++ b/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplatesCollection.kt @@ -0,0 +1,6 @@ +package core.ai.composableExp +import core.ai.composableExp.AIPackageTemplate + +interface AIPackageTemplatesCollection { + val values: List +} \ No newline at end of file diff --git a/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplatesGenerated.kt b/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplatesGenerated.kt new file mode 100644 index 000000000..baa1a6b06 --- /dev/null +++ b/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplatesGenerated.kt @@ -0,0 +1,5 @@ +package core.ai.composableExp + +class AIPackageTemplatesGenerated : AIPackageTemplatesCollection { + override val values by lazy { listOf().flatMap { it.values }} +} \ No newline at end of file diff --git a/src/commonMain/kotlin/core/events/EventListenerMapGenerated.kt b/src/commonMain/kotlin/core/events/EventListenerMapGenerated.kt index 5529210a8..580bdc96a 100644 --- a/src/commonMain/kotlin/core/events/EventListenerMapGenerated.kt +++ b/src/commonMain/kotlin/core/events/EventListenerMapGenerated.kt @@ -3,5 +3,5 @@ package core.events class EventListenerMapGenerated : EventListenerMapCollection { private val listenerMap: Map> = mapOf("combat.attack.Attack" to combat.attack.Attack(), "combat.block.Block" to combat.block.Block(), "combat.takeDamage.TakeDamage" to combat.takeDamage.TakeDamage(), "conversation.dialogue.DialogueListener" to conversation.dialogue.DialogueListener(), "conversation.end.EndConversation" to conversation.end.EndConversation(), "conversation.start.StartConversation" to conversation.start.StartConversation(), "core.MessageHandler" to core.MessageHandler(), "core.ai.knowledge.DiscoverFact" to core.ai.knowledge.DiscoverFact(), "core.commands.commandEvent.CommandEventListener" to core.commands.commandEvent.CommandEventListener(), "core.history.SessionListener" to core.history.SessionListener(), "core.properties.SetProperties" to core.properties.SetProperties(), "core.properties.propValChanged.PropertyStatChanged" to core.properties.propValChanged.PropertyStatChanged(), "core.properties.propValChanged.PropertyStatMinned" to core.properties.propValChanged.PropertyStatMinned(), "core.thing.item.ItemSpawner" to core.thing.item.ItemSpawner(), "crafting.DiscoverRecipe" to crafting.DiscoverRecipe(), "crafting.checkRecipe.CheckRecipes" to crafting.checkRecipe.CheckRecipes(), "crafting.craft.Craft" to crafting.craft.Craft(), "explore.examine.Examine" to explore.examine.Examine(), "explore.listen.Listen" to explore.listen.Listen(), "explore.look.Look" to explore.look.Look(), "explore.map.ReadMap" to explore.map.ReadMap(), "explore.map.compass.SetCompassGoal" to explore.map.compass.SetCompassGoal(), "explore.map.compass.ViewCompass" to explore.map.compass.ViewCompass(), "inventory.ViewEquipped" to inventory.ViewEquipped(), "inventory.ViewInventory" to inventory.ViewInventory(), "inventory.dropItem.ItemDropped" to inventory.dropItem.ItemDropped(), "inventory.dropItem.PlaceItem" to inventory.dropItem.PlaceItem(), "inventory.equipItem.EquipItem" to inventory.equipItem.EquipItem(), "inventory.equipItem.ItemEquipped" to inventory.equipItem.ItemEquipped(), "inventory.pickupItem.ItemPickedUp" to inventory.pickupItem.ItemPickedUp(), "inventory.pickupItem.TakeItem" to inventory.pickupItem.TakeItem(), "inventory.putItem.TransferItem" to inventory.putItem.TransferItem(), "inventory.unEquipItem.ItemUnEquipped" to inventory.unEquipItem.ItemUnEquipped(), "inventory.unEquipItem.UnEquipItem" to inventory.unEquipItem.UnEquipItem(), "magic.ViewWordHelp" to magic.ViewWordHelp(), "magic.castSpell.CastSpell" to magic.castSpell.CastSpell(), "quests.CompleteQuest" to quests.CompleteQuest(), "quests.QuestListener" to quests.QuestListener(), "quests.journal.ViewQuestJournal" to quests.journal.ViewQuestJournal(), "quests.journal.ViewQuestList" to quests.journal.ViewQuestList(), "status.ExpGained" to status.ExpGained(), "status.LevelUp" to status.LevelUp(), "status.conditions.AddCondition" to status.conditions.AddCondition(), "status.conditions.ConditionRemover" to status.conditions.ConditionRemover(), "status.conditions.RemoveCondition" to status.conditions.RemoveCondition(), "status.effects.ApplyEffects" to status.effects.ApplyEffects(), "status.rest.Rest" to status.rest.Rest(), "status.statChanged.CreatureDied" to status.statChanged.CreatureDied(), "status.statChanged.PlayerStatMaxed" to status.statChanged.PlayerStatMaxed(), "status.statChanged.PlayerStatMinned" to status.statChanged.PlayerStatMinned(), "status.statChanged.StatBoosted" to status.statChanged.StatBoosted(), "status.statChanged.StatChanged" to status.statChanged.StatChanged(), "status.statChanged.StatMinned" to status.statChanged.StatMinned(), "status.status.Status" to status.status.Status(), "system.alias.CreateAlias" to system.alias.CreateAlias(), "system.alias.DeleteAlias" to system.alias.DeleteAlias(), "system.alias.ListAlias" to system.alias.ListAlias(), "system.connection.Connect" to system.connection.Connect(), "system.connection.ConnectInfo" to system.connection.ConnectInfo(), "system.connection.Disconnect" to system.connection.Disconnect(), "system.debug.DebugListListener" to system.debug.DebugListListener(), "system.debug.DebugStatListener" to system.debug.DebugStatListener(), "system.debug.DebugTagListener" to system.debug.DebugTagListener(), "system.debug.DebugToggleListener" to system.debug.DebugToggleListener(), "system.debug.DebugWeatherListener" to system.debug.DebugWeatherListener(), "system.help.ViewHelp" to system.help.ViewHelp(), "system.history.ViewGameLog" to system.history.ViewGameLog(), "system.message.DisplayMessage" to system.message.DisplayMessage(), "system.persistance.changePlayer.ListCharacters" to system.persistance.changePlayer.ListCharacters(), "system.persistance.changePlayer.PlayAs" to system.persistance.changePlayer.PlayAs(), "system.persistance.createPlayer.CreateCharacter" to system.persistance.createPlayer.CreateCharacter(), "system.persistance.loading.ListSaves" to system.persistance.loading.ListSaves(), "system.persistance.loading.Load" to system.persistance.loading.Load(), "system.persistance.newGame.CreateNewGame" to system.persistance.newGame.CreateNewGame(), "system.persistance.saving.Save" to system.persistance.saving.Save(), "time.ViewTime" to time.ViewTime(), "time.gameTick.TimeListener" to time.gameTick.TimeListener(), "traveling.RestrictLocation" to traveling.RestrictLocation(), "traveling.arrive.ArrivalHandler" to traveling.arrive.ArrivalHandler(), "traveling.arrive.Arrive" to traveling.arrive.Arrive(), "traveling.climb.AttemptClimb" to traveling.climb.AttemptClimb(), "traveling.climb.ClimbComplete" to traveling.climb.ClimbComplete(), "traveling.jump.PlayerFall" to traveling.jump.PlayerFall(), "traveling.jump.PlayerJump" to traveling.jump.PlayerJump(), "traveling.location.weather.WeatherListener" to traveling.location.weather.WeatherListener(), "traveling.move.Move" to traveling.move.Move(), "traveling.routes.FindRoute" to traveling.routes.FindRoute(), "traveling.routes.ViewRoute" to traveling.routes.ViewRoute(), "traveling.scope.remove.RemoveItem" to traveling.scope.remove.RemoveItem(), "traveling.scope.remove.RemoveScope" to traveling.scope.remove.RemoveScope(), "traveling.scope.spawn.ActivatorSpawner" to traveling.scope.spawn.ActivatorSpawner(), "traveling.scope.spawn.SpawnItem" to traveling.scope.spawn.SpawnItem(), "traveling.travel.TravelStart" to traveling.travel.TravelStart(), "use.actions.ChopWood" to use.actions.ChopWood(), "use.actions.DamageCreature" to use.actions.DamageCreature(), "use.actions.NoUseFound" to use.actions.NoUseFound(), "use.actions.ScratchSurface" to use.actions.ScratchSurface(), "use.actions.StartFire" to use.actions.StartFire(), "use.actions.UseFoodItem" to use.actions.UseFoodItem(), "use.actions.UseIngredientOnActivatorRecipe" to use.actions.UseIngredientOnActivatorRecipe(), "use.actions.UseItemOnIngredientRecipe" to use.actions.UseItemOnIngredientRecipe(), "use.actions.UseOnFire" to use.actions.UseOnFire(), "use.eat.EatFood" to use.eat.EatFood(), "use.interaction.Interact" to use.interaction.Interact(), "use.interaction.NoInteractionFound" to use.interaction.NoInteractionFound(), "use.interaction.nothing.DoNothing" to use.interaction.nothing.DoNothing()) - override val values: Map>> = mapOf("AttackEvent" to listOf(listenerMap["combat.attack.Attack"]!!), "BlockEvent" to listOf(listenerMap["combat.block.Block"]!!), "TakeDamageEvent" to listOf(listenerMap["combat.takeDamage.TakeDamage"]!!), "DialogueEvent" to listOf(listenerMap["conversation.dialogue.DialogueListener"]!!), "EndConversationEvent" to listOf(listenerMap["conversation.end.EndConversation"]!!), "StartConversationEvent" to listOf(listenerMap["conversation.start.StartConversation"]!!), "MessageEvent" to listOf(listenerMap["core.MessageHandler"]!!), "DiscoverFactEvent" to listOf(listenerMap["core.ai.knowledge.DiscoverFact"]!!), "CommandEvent" to listOf(listenerMap["core.commands.commandEvent.CommandEventListener"]!!), "GameTickEvent" to listOf(listenerMap["status.conditions.ConditionRemover"]!!,listenerMap["status.effects.ApplyEffects"]!!,listenerMap["time.gameTick.TimeListener"]!!,listenerMap["traveling.location.weather.WeatherListener"]!!), "Event" to listOf(listenerMap["core.history.SessionListener"]!!,listenerMap["quests.QuestListener"]!!), "SetPropertiesEvent" to listOf(listenerMap["core.properties.SetProperties"]!!), "PropertyStatChangeEvent" to listOf(listenerMap["core.properties.propValChanged.PropertyStatChanged"]!!), "PropertyStatMinnedEvent" to listOf(listenerMap["core.properties.propValChanged.PropertyStatMinned"]!!), "SpawnItemEvent" to listOf(listenerMap["core.thing.item.ItemSpawner"]!!), "DiscoverRecipeEvent" to listOf(listenerMap["crafting.DiscoverRecipe"]!!), "CheckRecipeEvent" to listOf(listenerMap["crafting.checkRecipe.CheckRecipes"]!!), "CraftRecipeEvent" to listOf(listenerMap["crafting.craft.Craft"]!!), "ExamineEvent" to listOf(listenerMap["explore.examine.Examine"]!!), "ListenEvent" to listOf(listenerMap["explore.listen.Listen"]!!), "LookEvent" to listOf(listenerMap["explore.look.Look"]!!), "ReadMapEvent" to listOf(listenerMap["explore.map.ReadMap"]!!), "SetCompassEvent" to listOf(listenerMap["explore.map.compass.SetCompassGoal"]!!), "ViewCompassEvent" to listOf(listenerMap["explore.map.compass.ViewCompass"]!!), "ViewEquippedEvent" to listOf(listenerMap["inventory.ViewEquipped"]!!), "ViewInventoryEvent" to listOf(listenerMap["inventory.ViewInventory"]!!), "ItemDroppedEvent" to listOf(listenerMap["inventory.dropItem.ItemDropped"]!!), "PlaceItemEvent" to listOf(listenerMap["inventory.dropItem.PlaceItem"]!!), "EquipItemEvent" to listOf(listenerMap["inventory.equipItem.EquipItem"]!!), "ItemEquippedEvent" to listOf(listenerMap["inventory.equipItem.ItemEquipped"]!!), "ItemPickedUpEvent" to listOf(listenerMap["inventory.pickupItem.ItemPickedUp"]!!), "TakeItemEvent" to listOf(listenerMap["inventory.pickupItem.TakeItem"]!!), "TransferItemEvent" to listOf(listenerMap["inventory.putItem.TransferItem"]!!), "ItemUnEquippedEvent" to listOf(listenerMap["inventory.unEquipItem.ItemUnEquipped"]!!), "UnEquipItemEvent" to listOf(listenerMap["inventory.unEquipItem.UnEquipItem"]!!), "ViewWordHelpEvent" to listOf(listenerMap["magic.ViewWordHelp"]!!), "CastSpellEvent" to listOf(listenerMap["magic.castSpell.CastSpell"]!!), "CompleteQuestEvent" to listOf(listenerMap["quests.CompleteQuest"]!!), "ViewQuestJournalEvent" to listOf(listenerMap["quests.journal.ViewQuestJournal"]!!), "ViewQuestListEvent" to listOf(listenerMap["quests.journal.ViewQuestList"]!!), "ExpGainedEvent" to listOf(listenerMap["status.ExpGained"]!!), "LevelUpEvent" to listOf(listenerMap["status.LevelUp"]!!), "AddConditionEvent" to listOf(listenerMap["status.conditions.AddCondition"]!!), "RemoveConditionEvent" to listOf(listenerMap["status.conditions.RemoveCondition"]!!), "RestEvent" to listOf(listenerMap["status.rest.Rest"]!!), "StatMinnedEvent" to listOf(listenerMap["status.statChanged.CreatureDied"]!!,listenerMap["status.statChanged.PlayerStatMinned"]!!,listenerMap["status.statChanged.StatMinned"]!!), "StatMaxedEvent" to listOf(listenerMap["status.statChanged.PlayerStatMaxed"]!!), "StatBoostEvent" to listOf(listenerMap["status.statChanged.StatBoosted"]!!), "StatChangeEvent" to listOf(listenerMap["status.statChanged.StatChanged"]!!), "StatusEvent" to listOf(listenerMap["status.status.Status"]!!), "CreateAliasEvent" to listOf(listenerMap["system.alias.CreateAlias"]!!), "DeleteAliasEvent" to listOf(listenerMap["system.alias.DeleteAlias"]!!), "ListAliasesEvent" to listOf(listenerMap["system.alias.ListAlias"]!!), "ConnectEvent" to listOf(listenerMap["system.connection.Connect"]!!), "ConnectInfoEvent" to listOf(listenerMap["system.connection.ConnectInfo"]!!), "DisconnectEvent" to listOf(listenerMap["system.connection.Disconnect"]!!), "DebugListEvent" to listOf(listenerMap["system.debug.DebugListListener"]!!), "DebugStatEvent" to listOf(listenerMap["system.debug.DebugStatListener"]!!), "DebugTagEvent" to listOf(listenerMap["system.debug.DebugTagListener"]!!), "DebugToggleEvent" to listOf(listenerMap["system.debug.DebugToggleListener"]!!), "DebugWeatherEvent" to listOf(listenerMap["system.debug.DebugWeatherListener"]!!), "ViewHelpEvent" to listOf(listenerMap["system.help.ViewHelp"]!!), "ViewGameLogEvent" to listOf(listenerMap["system.history.ViewGameLog"]!!), "DisplayMessageEvent" to listOf(listenerMap["system.message.DisplayMessage"]!!), "ListCharactersEvent" to listOf(listenerMap["system.persistance.changePlayer.ListCharacters"]!!), "PlayAsEvent" to listOf(listenerMap["system.persistance.changePlayer.PlayAs"]!!), "CreateCharacterEvent" to listOf(listenerMap["system.persistance.createPlayer.CreateCharacter"]!!), "ListSavesEvent" to listOf(listenerMap["system.persistance.loading.ListSaves"]!!), "LoadEvent" to listOf(listenerMap["system.persistance.loading.Load"]!!), "CreateNewGameEvent" to listOf(listenerMap["system.persistance.newGame.CreateNewGame"]!!), "SaveEvent" to listOf(listenerMap["system.persistance.saving.Save"]!!), "ViewTimeEvent" to listOf(listenerMap["time.ViewTime"]!!), "RestrictLocationEvent" to listOf(listenerMap["traveling.RestrictLocation"]!!), "ArriveEvent" to listOf(listenerMap["traveling.arrive.ArrivalHandler"]!!,listenerMap["traveling.arrive.Arrive"]!!), "AttemptClimbEvent" to listOf(listenerMap["traveling.climb.AttemptClimb"]!!), "ClimbCompleteEvent" to listOf(listenerMap["traveling.climb.ClimbComplete"]!!), "FallEvent" to listOf(listenerMap["traveling.jump.PlayerFall"]!!), "JumpEvent" to listOf(listenerMap["traveling.jump.PlayerJump"]!!), "MoveEvent" to listOf(listenerMap["traveling.move.Move"]!!), "FindRouteEvent" to listOf(listenerMap["traveling.routes.FindRoute"]!!), "ViewRouteEvent" to listOf(listenerMap["traveling.routes.ViewRoute"]!!), "RemoveItemEvent" to listOf(listenerMap["traveling.scope.remove.RemoveItem"]!!), "RemoveScopeEvent" to listOf(listenerMap["traveling.scope.remove.RemoveScope"]!!), "SpawnActivatorEvent" to listOf(listenerMap["traveling.scope.spawn.ActivatorSpawner"]!!), "ItemSpawnedEvent" to listOf(listenerMap["traveling.scope.spawn.SpawnItem"]!!), "TravelStartEvent" to listOf(listenerMap["traveling.travel.TravelStart"]!!), "UseEvent" to listOf(listenerMap["use.actions.ChopWood"]!!,listenerMap["use.actions.DamageCreature"]!!,listenerMap["use.actions.NoUseFound"]!!,listenerMap["use.actions.ScratchSurface"]!!,listenerMap["use.actions.StartFire"]!!,listenerMap["use.actions.UseFoodItem"]!!,listenerMap["use.actions.UseIngredientOnActivatorRecipe"]!!,listenerMap["use.actions.UseItemOnIngredientRecipe"]!!,listenerMap["use.actions.UseOnFire"]!!), "EatFoodEvent" to listOf(listenerMap["use.eat.EatFood"]!!), "InteractEvent" to listOf(listenerMap["use.interaction.Interact"]!!,listenerMap["use.interaction.NoInteractionFound"]!!), "NothingEvent" to listOf(listenerMap["use.interaction.nothing.DoNothing"]!!)) + override val values: Map>> = mapOf("AttackEvent" to listOf(listenerMap["combat.attack.Attack"]!!), "BlockEvent" to listOf(listenerMap["combat.block.Block"]!!), "TakeDamageEvent" to listOf(listenerMap["combat.takeDamage.TakeDamage"]!!), "DialogueEvent" to listOf(listenerMap["conversation.dialogue.DialogueListener"]!!), "EndConversationEvent" to listOf(listenerMap["conversation.end.EndConversation"]!!), "StartConversationEvent" to listOf(listenerMap["conversation.start.StartConversation"]!!), "MessageEvent" to listOf(listenerMap["core.MessageHandler"]!!), "DiscoverFactEvent" to listOf(listenerMap["core.ai.knowledge.DiscoverFact"]!!), "CommandEvent" to listOf(listenerMap["core.commands.commandEvent.CommandEventListener"]!!), "Event" to listOf(listenerMap["core.history.SessionListener"]!!,listenerMap["quests.QuestListener"]!!), "SetPropertiesEvent" to listOf(listenerMap["core.properties.SetProperties"]!!), "PropertyStatChangeEvent" to listOf(listenerMap["core.properties.propValChanged.PropertyStatChanged"]!!), "PropertyStatMinnedEvent" to listOf(listenerMap["core.properties.propValChanged.PropertyStatMinned"]!!), "SpawnItemEvent" to listOf(listenerMap["core.thing.item.ItemSpawner"]!!), "DiscoverRecipeEvent" to listOf(listenerMap["crafting.DiscoverRecipe"]!!), "CheckRecipeEvent" to listOf(listenerMap["crafting.checkRecipe.CheckRecipes"]!!), "CraftRecipeEvent" to listOf(listenerMap["crafting.craft.Craft"]!!), "ExamineEvent" to listOf(listenerMap["explore.examine.Examine"]!!), "ListenEvent" to listOf(listenerMap["explore.listen.Listen"]!!), "LookEvent" to listOf(listenerMap["explore.look.Look"]!!), "ReadMapEvent" to listOf(listenerMap["explore.map.ReadMap"]!!), "SetCompassEvent" to listOf(listenerMap["explore.map.compass.SetCompassGoal"]!!), "ViewCompassEvent" to listOf(listenerMap["explore.map.compass.ViewCompass"]!!), "ViewEquippedEvent" to listOf(listenerMap["inventory.ViewEquipped"]!!), "ViewInventoryEvent" to listOf(listenerMap["inventory.ViewInventory"]!!), "ItemDroppedEvent" to listOf(listenerMap["inventory.dropItem.ItemDropped"]!!), "PlaceItemEvent" to listOf(listenerMap["inventory.dropItem.PlaceItem"]!!), "EquipItemEvent" to listOf(listenerMap["inventory.equipItem.EquipItem"]!!), "ItemEquippedEvent" to listOf(listenerMap["inventory.equipItem.ItemEquipped"]!!), "ItemPickedUpEvent" to listOf(listenerMap["inventory.pickupItem.ItemPickedUp"]!!), "TakeItemEvent" to listOf(listenerMap["inventory.pickupItem.TakeItem"]!!), "TransferItemEvent" to listOf(listenerMap["inventory.putItem.TransferItem"]!!), "ItemUnEquippedEvent" to listOf(listenerMap["inventory.unEquipItem.ItemUnEquipped"]!!), "UnEquipItemEvent" to listOf(listenerMap["inventory.unEquipItem.UnEquipItem"]!!), "ViewWordHelpEvent" to listOf(listenerMap["magic.ViewWordHelp"]!!), "CastSpellEvent" to listOf(listenerMap["magic.castSpell.CastSpell"]!!), "CompleteQuestEvent" to listOf(listenerMap["quests.CompleteQuest"]!!), "ViewQuestJournalEvent" to listOf(listenerMap["quests.journal.ViewQuestJournal"]!!), "ViewQuestListEvent" to listOf(listenerMap["quests.journal.ViewQuestList"]!!), "ExpGainedEvent" to listOf(listenerMap["status.ExpGained"]!!), "LevelUpEvent" to listOf(listenerMap["status.LevelUp"]!!), "AddConditionEvent" to listOf(listenerMap["status.conditions.AddCondition"]!!), "GameTickEvent" to listOf(listenerMap["status.conditions.ConditionRemover"]!!,listenerMap["status.effects.ApplyEffects"]!!,listenerMap["time.gameTick.TimeListener"]!!,listenerMap["traveling.location.weather.WeatherListener"]!!), "RemoveConditionEvent" to listOf(listenerMap["status.conditions.RemoveCondition"]!!), "RestEvent" to listOf(listenerMap["status.rest.Rest"]!!), "StatMinnedEvent" to listOf(listenerMap["status.statChanged.CreatureDied"]!!,listenerMap["status.statChanged.PlayerStatMinned"]!!,listenerMap["status.statChanged.StatMinned"]!!), "StatMaxedEvent" to listOf(listenerMap["status.statChanged.PlayerStatMaxed"]!!), "StatBoostEvent" to listOf(listenerMap["status.statChanged.StatBoosted"]!!), "StatChangeEvent" to listOf(listenerMap["status.statChanged.StatChanged"]!!), "StatusEvent" to listOf(listenerMap["status.status.Status"]!!), "CreateAliasEvent" to listOf(listenerMap["system.alias.CreateAlias"]!!), "DeleteAliasEvent" to listOf(listenerMap["system.alias.DeleteAlias"]!!), "ListAliasesEvent" to listOf(listenerMap["system.alias.ListAlias"]!!), "ConnectEvent" to listOf(listenerMap["system.connection.Connect"]!!), "ConnectInfoEvent" to listOf(listenerMap["system.connection.ConnectInfo"]!!), "DisconnectEvent" to listOf(listenerMap["system.connection.Disconnect"]!!), "DebugListEvent" to listOf(listenerMap["system.debug.DebugListListener"]!!), "DebugStatEvent" to listOf(listenerMap["system.debug.DebugStatListener"]!!), "DebugTagEvent" to listOf(listenerMap["system.debug.DebugTagListener"]!!), "DebugToggleEvent" to listOf(listenerMap["system.debug.DebugToggleListener"]!!), "DebugWeatherEvent" to listOf(listenerMap["system.debug.DebugWeatherListener"]!!), "ViewHelpEvent" to listOf(listenerMap["system.help.ViewHelp"]!!), "ViewGameLogEvent" to listOf(listenerMap["system.history.ViewGameLog"]!!), "DisplayMessageEvent" to listOf(listenerMap["system.message.DisplayMessage"]!!), "ListCharactersEvent" to listOf(listenerMap["system.persistance.changePlayer.ListCharacters"]!!), "PlayAsEvent" to listOf(listenerMap["system.persistance.changePlayer.PlayAs"]!!), "CreateCharacterEvent" to listOf(listenerMap["system.persistance.createPlayer.CreateCharacter"]!!), "ListSavesEvent" to listOf(listenerMap["system.persistance.loading.ListSaves"]!!), "LoadEvent" to listOf(listenerMap["system.persistance.loading.Load"]!!), "CreateNewGameEvent" to listOf(listenerMap["system.persistance.newGame.CreateNewGame"]!!), "SaveEvent" to listOf(listenerMap["system.persistance.saving.Save"]!!), "ViewTimeEvent" to listOf(listenerMap["time.ViewTime"]!!), "RestrictLocationEvent" to listOf(listenerMap["traveling.RestrictLocation"]!!), "ArriveEvent" to listOf(listenerMap["traveling.arrive.ArrivalHandler"]!!,listenerMap["traveling.arrive.Arrive"]!!), "AttemptClimbEvent" to listOf(listenerMap["traveling.climb.AttemptClimb"]!!), "ClimbCompleteEvent" to listOf(listenerMap["traveling.climb.ClimbComplete"]!!), "FallEvent" to listOf(listenerMap["traveling.jump.PlayerFall"]!!), "JumpEvent" to listOf(listenerMap["traveling.jump.PlayerJump"]!!), "MoveEvent" to listOf(listenerMap["traveling.move.Move"]!!), "FindRouteEvent" to listOf(listenerMap["traveling.routes.FindRoute"]!!), "ViewRouteEvent" to listOf(listenerMap["traveling.routes.ViewRoute"]!!), "RemoveItemEvent" to listOf(listenerMap["traveling.scope.remove.RemoveItem"]!!), "RemoveScopeEvent" to listOf(listenerMap["traveling.scope.remove.RemoveScope"]!!), "SpawnActivatorEvent" to listOf(listenerMap["traveling.scope.spawn.ActivatorSpawner"]!!), "ItemSpawnedEvent" to listOf(listenerMap["traveling.scope.spawn.SpawnItem"]!!), "TravelStartEvent" to listOf(listenerMap["traveling.travel.TravelStart"]!!), "UseEvent" to listOf(listenerMap["use.actions.ChopWood"]!!,listenerMap["use.actions.DamageCreature"]!!,listenerMap["use.actions.NoUseFound"]!!,listenerMap["use.actions.ScratchSurface"]!!,listenerMap["use.actions.StartFire"]!!,listenerMap["use.actions.UseFoodItem"]!!,listenerMap["use.actions.UseIngredientOnActivatorRecipe"]!!,listenerMap["use.actions.UseItemOnIngredientRecipe"]!!,listenerMap["use.actions.UseOnFire"]!!), "EatFoodEvent" to listOf(listenerMap["use.eat.EatFood"]!!), "InteractEvent" to listOf(listenerMap["use.interaction.Interact"]!!,listenerMap["use.interaction.NoInteractionFound"]!!), "NothingEvent" to listOf(listenerMap["use.interaction.nothing.DoNothing"]!!)) } \ No newline at end of file diff --git a/src/jvmTest/kotlin/core/ai/composableExp/AIPackageTemplatesMock.kt b/src/jvmTest/kotlin/core/ai/composableExp/AIPackageTemplatesMock.kt new file mode 100644 index 000000000..8a73e8603 --- /dev/null +++ b/src/jvmTest/kotlin/core/ai/composableExp/AIPackageTemplatesMock.kt @@ -0,0 +1,4 @@ +package core.ai.composableExp +import AIPackage + +class AIPackageTemplatesMock(override val values: List = listOf()) : AIPackageTemplatesCollection \ No newline at end of file diff --git a/src/jvmTest/kotlin/core/ai/composableExp/AIPackagesMock.kt b/src/jvmTest/kotlin/core/ai/composableExp/AIPackagesMock.kt new file mode 100644 index 000000000..a8ca2d395 --- /dev/null +++ b/src/jvmTest/kotlin/core/ai/composableExp/AIPackagesMock.kt @@ -0,0 +1,4 @@ +package core.ai.composableExp +import AIPackage + +class AIPackagesMock(override val values: List = listOf()) : AIPackagesCollection \ No newline at end of file diff --git a/src/jvmTools/kotlin/building/ReflectionTools.kt b/src/jvmTools/kotlin/building/ReflectionTools.kt index 481892a06..e5b8b6425 100644 --- a/src/jvmTools/kotlin/building/ReflectionTools.kt +++ b/src/jvmTools/kotlin/building/ReflectionTools.kt @@ -6,6 +6,9 @@ import core.ai.agenda.Agenda import core.ai.agenda.AgendaResource import core.ai.behavior.Behavior import core.ai.behavior.BehaviorResource +import core.ai.composableExp.AIPackage +import core.ai.composableExp.AIPackageTemplate +import core.ai.composableExp.AIPackageTemplateResource import core.ai.desire.DesireResource import core.ai.desire.DesireTree import core.body.BodyPartResource @@ -61,6 +64,7 @@ object ReflectionTools { generateResourcesFile(ActivatorResource::class, ThingBuilder::class) generateResourcesFile(AgendaResource::class, Agenda::class) + generateResourcesFile(AIPackageTemplateResource::class, AIPackageTemplate::class) generateResourcesFile(ConditionResource::class, ConditionRecipe::class) generateResourcesFile(BodyResource::class, NetworkBuilder::class) generateResourcesFile(BodyPartResource::class, LocationRecipeBuilder::class) From dc8458e05844d8f5496401953396f0c522cc9c2f Mon Sep 17 00:00:00 2001 From: ManApart Date: Fri, 19 Jan 2024 07:39:04 -0500 Subject: [PATCH 05/45] evaluate actions to take --- .../kotlin/core/ai/composableExp/AIPackage.kt | 28 +++++-------------- .../ai/composableExp/AIPackageTemplate.kt | 21 ++++++++++++++ .../kotlin/core/ai/composableExp/Idea.kt | 10 +++++++ 3 files changed, 38 insertions(+), 21 deletions(-) create mode 100644 src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplate.kt create mode 100644 src/commonMain/kotlin/core/ai/composableExp/Idea.kt diff --git a/src/commonMain/kotlin/core/ai/composableExp/AIPackage.kt b/src/commonMain/kotlin/core/ai/composableExp/AIPackage.kt index 33934a56f..9d957399b 100644 --- a/src/commonMain/kotlin/core/ai/composableExp/AIPackage.kt +++ b/src/commonMain/kotlin/core/ai/composableExp/AIPackage.kt @@ -1,29 +1,15 @@ package core.ai.composableExp -import core.events.Event import core.thing.Thing -//When evaluating which idea to go with, map all ideas by priority, and grab the first idea that matches at a given priority list (or random at that level?) +//Can we memoize criteria per call to pickIdea? Would that be premature optimization? -data class Idea(val name: String, val priority: Int, val criteria: suspend (Thing) -> Boolean, val action: suspend (Thing) -> List) +data class AIPackage(val name: String, val ideas: Map>) { + constructor(name: String, ideas: List) : this(name, ideas.groupBy { it.priority }) -//TODO - we can validate string references before flattening -//Validate -// - package name unique -// - idea name unique -// - string references exist -data class AIPackageTemplate(val name: String, val subPackages: List, val priorityOverride: Map, val ideas: List) { - fun flatten(reference: Map, flattenedReference: MutableMap): AIPackage { - return flattenedReference[name] ?: let { - val subIdeas = subPackages.mapNotNull { reference[it] }.flatMap { subPackage -> - flattenedReference[subPackage.name]?.ideas - ?: subPackage.flatten(reference, flattenedReference).also { flattenedReference[subPackage.name] = it }.ideas - }.map { idea -> - priorityOverride[it.name]?.let { idea.copy(priority = it) } ?: idea - } - AIPackage(name, ideas + subIdeas) - } + suspend fun pickIdea(source: Thing): Idea { + return ideas.entries.sortedByDescending { it.key }.firstNotNullOfOrNull { (_, ideas) -> + ideas.firstOrNull { it.criteria(source) } + } ?: DO_NOTHING_IDEA } } - -data class AIPackage(val name: String, val ideas: List) diff --git a/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplate.kt b/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplate.kt new file mode 100644 index 000000000..9d0c7cf3e --- /dev/null +++ b/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplate.kt @@ -0,0 +1,21 @@ +package core.ai.composableExp + + +//TODO - we can validate string references before flattening +//Validate +// - package name unique +// - idea name unique +// - string references exist +data class AIPackageTemplate(val name: String, val subPackages: List, val priorityOverride: Map, val ideas: List) { + fun flatten(reference: Map, flattenedReference: MutableMap): AIPackage { + return flattenedReference[name] ?: let { + val subIdeas = subPackages.mapNotNull { reference[it] }.flatMap { subPackage -> + flattenedReference[subPackage.name]?.ideas?.values?.flatten() + ?: subPackage.flatten(reference, flattenedReference).also { flattenedReference[subPackage.name] = it }.ideas.values.flatten() + }.map { idea -> + priorityOverride[it.name]?.let { idea.copy(priority = it) } ?: idea + } + AIPackage(name, ideas + subIdeas) + } + } +} \ No newline at end of file diff --git a/src/commonMain/kotlin/core/ai/composableExp/Idea.kt b/src/commonMain/kotlin/core/ai/composableExp/Idea.kt new file mode 100644 index 000000000..0254b70d9 --- /dev/null +++ b/src/commonMain/kotlin/core/ai/composableExp/Idea.kt @@ -0,0 +1,10 @@ +package core.ai.composableExp + +import core.events.Event +import core.thing.Thing +import use.interaction.nothing.NothingEvent + + +val DO_NOTHING_IDEA = Idea("Do Nothing", 0,{true}, { listOf(NothingEvent(it))}) + +data class Idea(val name: String, val priority: Int, val criteria: suspend (Thing) -> Boolean, val action: suspend (Thing) -> List) From b3a82c28f32974978c633bce2e487d5f588e60bf Mon Sep 17 00:00:00 2001 From: ManApart Date: Fri, 19 Jan 2024 07:58:42 -0500 Subject: [PATCH 06/45] basic package validation --- src/commonMain/kotlin/building/ModManager.kt | 3 +- .../kotlin/core/DependencyInjector.kt | 4 +- src/commonMain/kotlin/core/ai/AIManager2.kt | 4 +- .../{composableExp => packages}/AIPackage.kt | 2 +- .../AIPackageTemplate.kt | 8 +-- .../AIPackageTemplateBuilder.kt | 2 +- .../AIPackageTemplateResource.kt | 2 +- .../AIPackageTemplatesCollection.kt | 4 +- .../AIPackageTemplatesGenerated.kt | 4 +- .../ai/{composableExp => packages}/Idea.kt | 2 +- .../IdeaBuilder.kt | 3 +- .../ai/packages/CommonPackages.kt} | 13 +++- .../AIPackageTemplatesMock.kt | 3 +- .../AIPackagesMock.kt | 3 +- .../kotlin/validation/AIPackageValidator.kt | 68 +++++++++++++++++++ .../kotlin/validation/ValidationTest.kt | 1 + .../kotlin/building/ReflectionTools.kt | 5 +- 17 files changed, 99 insertions(+), 32 deletions(-) rename src/commonMain/kotlin/core/ai/{composableExp => packages}/AIPackage.kt (94%) rename src/commonMain/kotlin/core/ai/{composableExp => packages}/AIPackageTemplate.kt (82%) rename src/commonMain/kotlin/core/ai/{composableExp => packages}/AIPackageTemplateBuilder.kt (98%) rename src/commonMain/kotlin/core/ai/{composableExp => packages}/AIPackageTemplateResource.kt (72%) rename src/commonMain/kotlin/core/ai/{composableExp => packages}/AIPackageTemplatesCollection.kt (51%) rename src/commonMain/kotlin/core/ai/{composableExp => packages}/AIPackageTemplatesGenerated.kt (59%) rename src/commonMain/kotlin/core/ai/{composableExp => packages}/Idea.kt (91%) rename src/commonMain/kotlin/core/ai/{composableExp => packages}/IdeaBuilder.kt (95%) rename src/commonMain/kotlin/{core/ai/composableExp/TestSheet.kt => resources/ai/packages/CommonPackages.kt} (68%) rename src/jvmTest/kotlin/core/ai/{composableExp => packages}/AIPackageTemplatesMock.kt (69%) rename src/jvmTest/kotlin/core/ai/{composableExp => packages}/AIPackagesMock.kt (66%) create mode 100644 src/jvmTestIntegration/kotlin/validation/AIPackageValidator.kt diff --git a/src/commonMain/kotlin/building/ModManager.kt b/src/commonMain/kotlin/building/ModManager.kt index e4ce126d4..2ed3a63dc 100644 --- a/src/commonMain/kotlin/building/ModManager.kt +++ b/src/commonMain/kotlin/building/ModManager.kt @@ -3,8 +3,7 @@ package building import conversation.dsl.DialogueTree import core.ai.agenda.Agenda import core.ai.behavior.Behavior -import core.ai.composableExp.AIPackage -import core.ai.composableExp.AIPackageTemplate +import core.ai.packages.AIPackageTemplate import core.ai.desire.DesireTree import core.events.EventListener import core.thing.ThingBuilder diff --git a/src/commonMain/kotlin/core/DependencyInjector.kt b/src/commonMain/kotlin/core/DependencyInjector.kt index c6078c973..89f8756a3 100644 --- a/src/commonMain/kotlin/core/DependencyInjector.kt +++ b/src/commonMain/kotlin/core/DependencyInjector.kt @@ -6,8 +6,8 @@ import core.ai.behavior.BehaviorsCollection import core.ai.behavior.BehaviorsGenerated import core.ai.agenda.AgendasCollection import core.ai.agenda.AgendasGenerated -import core.ai.composableExp.AIPackageTemplatesCollection -import core.ai.composableExp.AIPackageTemplatesGenerated +import core.ai.packages.AIPackageTemplatesCollection +import core.ai.packages.AIPackageTemplatesGenerated import core.ai.desire.DesiresCollection import core.ai.desire.DesiresGenerated import core.body.BodyPartsCollection diff --git a/src/commonMain/kotlin/core/ai/AIManager2.kt b/src/commonMain/kotlin/core/ai/AIManager2.kt index e801c266e..40317b5d6 100644 --- a/src/commonMain/kotlin/core/ai/AIManager2.kt +++ b/src/commonMain/kotlin/core/ai/AIManager2.kt @@ -2,8 +2,8 @@ package core.ai import building.ModManager import core.DependencyInjector -import core.ai.composableExp.AIPackage -import core.ai.composableExp.AIPackageTemplatesCollection +import core.ai.packages.AIPackage +import core.ai.packages.AIPackageTemplatesCollection import core.startupLog import core.utility.lazyM diff --git a/src/commonMain/kotlin/core/ai/composableExp/AIPackage.kt b/src/commonMain/kotlin/core/ai/packages/AIPackage.kt similarity index 94% rename from src/commonMain/kotlin/core/ai/composableExp/AIPackage.kt rename to src/commonMain/kotlin/core/ai/packages/AIPackage.kt index 9d957399b..984246faa 100644 --- a/src/commonMain/kotlin/core/ai/composableExp/AIPackage.kt +++ b/src/commonMain/kotlin/core/ai/packages/AIPackage.kt @@ -1,4 +1,4 @@ -package core.ai.composableExp +package core.ai.packages import core.thing.Thing diff --git a/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplate.kt b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplate.kt similarity index 82% rename from src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplate.kt rename to src/commonMain/kotlin/core/ai/packages/AIPackageTemplate.kt index 9d0c7cf3e..812a219aa 100644 --- a/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplate.kt +++ b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplate.kt @@ -1,12 +1,8 @@ -package core.ai.composableExp +package core.ai.packages -//TODO - we can validate string references before flattening -//Validate -// - package name unique -// - idea name unique -// - string references exist data class AIPackageTemplate(val name: String, val subPackages: List, val priorityOverride: Map, val ideas: List) { + fun flatten(reference: Map, flattenedReference: MutableMap): AIPackage { return flattenedReference[name] ?: let { val subIdeas = subPackages.mapNotNull { reference[it] }.flatMap { subPackage -> diff --git a/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplateBuilder.kt b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplateBuilder.kt similarity index 98% rename from src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplateBuilder.kt rename to src/commonMain/kotlin/core/ai/packages/AIPackageTemplateBuilder.kt index 39c2eab25..3e6bf30c8 100644 --- a/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplateBuilder.kt +++ b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplateBuilder.kt @@ -1,4 +1,4 @@ -package core.ai.composableExp +package core.ai.packages import core.thing.Thing diff --git a/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplateResource.kt b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplateResource.kt similarity index 72% rename from src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplateResource.kt rename to src/commonMain/kotlin/core/ai/packages/AIPackageTemplateResource.kt index 700a97d07..9d0350dad 100644 --- a/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplateResource.kt +++ b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplateResource.kt @@ -1,4 +1,4 @@ -package core.ai.composableExp +package core.ai.packages interface AIPackageTemplateResource { val values: List diff --git a/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplatesCollection.kt b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplatesCollection.kt similarity index 51% rename from src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplatesCollection.kt rename to src/commonMain/kotlin/core/ai/packages/AIPackageTemplatesCollection.kt index 727f3d0ef..da15a595d 100644 --- a/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplatesCollection.kt +++ b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplatesCollection.kt @@ -1,5 +1,5 @@ -package core.ai.composableExp -import core.ai.composableExp.AIPackageTemplate +package core.ai.packages +import core.ai.packages.AIPackageTemplate interface AIPackageTemplatesCollection { val values: List diff --git a/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplatesGenerated.kt b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplatesGenerated.kt similarity index 59% rename from src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplatesGenerated.kt rename to src/commonMain/kotlin/core/ai/packages/AIPackageTemplatesGenerated.kt index baa1a6b06..af3ca26a8 100644 --- a/src/commonMain/kotlin/core/ai/composableExp/AIPackageTemplatesGenerated.kt +++ b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplatesGenerated.kt @@ -1,5 +1,5 @@ -package core.ai.composableExp +package core.ai.packages class AIPackageTemplatesGenerated : AIPackageTemplatesCollection { - override val values by lazy { listOf().flatMap { it.values }} + override val values by lazy { listOf(resources.ai.packages.CommonPackages()).flatMap { it.values }} } \ No newline at end of file diff --git a/src/commonMain/kotlin/core/ai/composableExp/Idea.kt b/src/commonMain/kotlin/core/ai/packages/Idea.kt similarity index 91% rename from src/commonMain/kotlin/core/ai/composableExp/Idea.kt rename to src/commonMain/kotlin/core/ai/packages/Idea.kt index 0254b70d9..22e2b2f44 100644 --- a/src/commonMain/kotlin/core/ai/composableExp/Idea.kt +++ b/src/commonMain/kotlin/core/ai/packages/Idea.kt @@ -1,4 +1,4 @@ -package core.ai.composableExp +package core.ai.packages import core.events.Event import core.thing.Thing diff --git a/src/commonMain/kotlin/core/ai/composableExp/IdeaBuilder.kt b/src/commonMain/kotlin/core/ai/packages/IdeaBuilder.kt similarity index 95% rename from src/commonMain/kotlin/core/ai/composableExp/IdeaBuilder.kt rename to src/commonMain/kotlin/core/ai/packages/IdeaBuilder.kt index 441fb31a7..56cfe404d 100644 --- a/src/commonMain/kotlin/core/ai/composableExp/IdeaBuilder.kt +++ b/src/commonMain/kotlin/core/ai/packages/IdeaBuilder.kt @@ -1,8 +1,7 @@ -package core.ai.composableExp +package core.ai.packages import core.events.Event import core.thing.Thing -import kotlinx.coroutines.NonCancellable.children class IdeaBuilder(val name: String, val priority: Int) { internal var criteria: suspend (Thing) -> Boolean = { true } diff --git a/src/commonMain/kotlin/core/ai/composableExp/TestSheet.kt b/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt similarity index 68% rename from src/commonMain/kotlin/core/ai/composableExp/TestSheet.kt rename to src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt index 0834c1f56..d74044744 100644 --- a/src/commonMain/kotlin/core/ai/composableExp/TestSheet.kt +++ b/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt @@ -1,10 +1,12 @@ -package core.ai.composableExp +package resources.ai.packages +import core.ai.packages.AIPackageTemplateResource +import core.ai.packages.aiPackages import status.rest.RestEvent import use.eat.EatFoodEvent -fun testAiPackages() { - aiPackages { +class CommonPackages: AIPackageTemplateResource { + override val values = aiPackages { aiPackage("animal") { criteria({ !it.isSafe() }) { criteria({ !it.isSafe() }) { @@ -16,6 +18,10 @@ fun testAiPackages() { criteria { !it.isSafe() } action { EatFoodEvent(it, it) } } + idea("wait") { + criteria { !it.isSafe() } + action { EatFoodEvent(it, it) } + } } } } @@ -28,4 +34,5 @@ fun testAiPackages() { } } } + } \ No newline at end of file diff --git a/src/jvmTest/kotlin/core/ai/composableExp/AIPackageTemplatesMock.kt b/src/jvmTest/kotlin/core/ai/packages/AIPackageTemplatesMock.kt similarity index 69% rename from src/jvmTest/kotlin/core/ai/composableExp/AIPackageTemplatesMock.kt rename to src/jvmTest/kotlin/core/ai/packages/AIPackageTemplatesMock.kt index 8a73e8603..56423f840 100644 --- a/src/jvmTest/kotlin/core/ai/composableExp/AIPackageTemplatesMock.kt +++ b/src/jvmTest/kotlin/core/ai/packages/AIPackageTemplatesMock.kt @@ -1,4 +1,3 @@ -package core.ai.composableExp -import AIPackage +package core.ai.packages class AIPackageTemplatesMock(override val values: List = listOf()) : AIPackageTemplatesCollection \ No newline at end of file diff --git a/src/jvmTest/kotlin/core/ai/composableExp/AIPackagesMock.kt b/src/jvmTest/kotlin/core/ai/packages/AIPackagesMock.kt similarity index 66% rename from src/jvmTest/kotlin/core/ai/composableExp/AIPackagesMock.kt rename to src/jvmTest/kotlin/core/ai/packages/AIPackagesMock.kt index a8ca2d395..4e688fd76 100644 --- a/src/jvmTest/kotlin/core/ai/composableExp/AIPackagesMock.kt +++ b/src/jvmTest/kotlin/core/ai/packages/AIPackagesMock.kt @@ -1,4 +1,3 @@ -package core.ai.composableExp -import AIPackage +package core.ai.packages class AIPackagesMock(override val values: List = listOf()) : AIPackagesCollection \ No newline at end of file diff --git a/src/jvmTestIntegration/kotlin/validation/AIPackageValidator.kt b/src/jvmTestIntegration/kotlin/validation/AIPackageValidator.kt new file mode 100644 index 000000000..1f10caa48 --- /dev/null +++ b/src/jvmTestIntegration/kotlin/validation/AIPackageValidator.kt @@ -0,0 +1,68 @@ +package validation + +import building.ModManager +import core.DependencyInjector +import core.ai.AIManager2 +import core.ai.packages.AIPackageTemplatesCollection +import kotlinx.coroutines.runBlocking + +//TODO validate all things/minds reference valid package name + +class AIPackageValidator { + + private val packages = runBlocking { AIManager2.aiPackages } + private val templates = (DependencyInjector.getImplementation(AIPackageTemplatesCollection::class).values + ModManager.ai2) + + fun validate(): Int { + return noDuplicatePackageNames() + + noDuplicateIdeaNames() + + subPackageStringReferenceExists() + } + + private fun noDuplicatePackageNames(): Int { + var warnings = 0 + val names = mutableListOf() + + templates.forEach { template -> + if (names.contains(template.name)) { + println("WARN: AI Package Template '${template.name}' has a duplicate name.") + warnings++ + } else { + names.add(template.name) + } + } + return warnings + } + + private fun noDuplicateIdeaNames(): Int { + var warnings = 0 + val names = mutableListOf() + + packages.values.flatMap { it.ideas.values.flatten() }.forEach { idea -> + if (names.contains(idea.name)) { + println("WARN: Idea '${idea.name}' has a duplicate name.") + warnings++ + } else { + names.add(idea.name) + } + } + return warnings + } + + private fun subPackageStringReferenceExists(): Int { + var warnings = 0 + val packageRef = templates.map { it.name }.toSet() + + templates.forEach { template -> + template.subPackages.forEach { reference -> + if (!packageRef.contains(reference)) { + println("WARN: AI Package Template ${template.name} references non existent package template $reference.") + warnings++ + } + } + } + return warnings + } + + +} \ No newline at end of file diff --git a/src/jvmTestIntegration/kotlin/validation/ValidationTest.kt b/src/jvmTestIntegration/kotlin/validation/ValidationTest.kt index b59329271..df3283616 100644 --- a/src/jvmTestIntegration/kotlin/validation/ValidationTest.kt +++ b/src/jvmTestIntegration/kotlin/validation/ValidationTest.kt @@ -14,6 +14,7 @@ class ValidationTest { runBlocking { val warnings = ActivatorValidator().validate() + + AIPackageValidator().validate() + CommandValidator().validate() + QuestValidator().validate() + DesireValidator().validate() diff --git a/src/jvmTools/kotlin/building/ReflectionTools.kt b/src/jvmTools/kotlin/building/ReflectionTools.kt index e5b8b6425..59bbaa5f3 100644 --- a/src/jvmTools/kotlin/building/ReflectionTools.kt +++ b/src/jvmTools/kotlin/building/ReflectionTools.kt @@ -6,9 +6,8 @@ import core.ai.agenda.Agenda import core.ai.agenda.AgendaResource import core.ai.behavior.Behavior import core.ai.behavior.BehaviorResource -import core.ai.composableExp.AIPackage -import core.ai.composableExp.AIPackageTemplate -import core.ai.composableExp.AIPackageTemplateResource +import core.ai.packages.AIPackageTemplate +import core.ai.packages.AIPackageTemplateResource import core.ai.desire.DesireResource import core.ai.desire.DesireTree import core.body.BodyPartResource From f060e1bbc5b17764755bbf39579e7d59eb8f423c Mon Sep 17 00:00:00 2001 From: ManApart Date: Fri, 19 Jan 2024 08:11:40 -0500 Subject: [PATCH 07/45] basic package validation passes --- src/commonMain/kotlin/core/ai/packages/AIPackageTemplate.kt | 4 ++-- .../kotlin/core/ai/packages/AIPackageTemplateBuilder.kt | 6 ++++-- .../kotlin/resources/ai/packages/CommonPackages.kt | 4 ---- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/commonMain/kotlin/core/ai/packages/AIPackageTemplate.kt b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplate.kt index 812a219aa..1180eb46a 100644 --- a/src/commonMain/kotlin/core/ai/packages/AIPackageTemplate.kt +++ b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplate.kt @@ -10,8 +10,8 @@ data class AIPackageTemplate(val name: String, val subPackages: List, va ?: subPackage.flatten(reference, flattenedReference).also { flattenedReference[subPackage.name] = it }.ideas.values.flatten() }.map { idea -> priorityOverride[it.name]?.let { idea.copy(priority = it) } ?: idea - } - AIPackage(name, ideas + subIdeas) + }.map { it.copy(name = "$name-${it.name}")} + AIPackage(name, ideas + subIdeas).also { flattenedReference[name] = it } } } } \ No newline at end of file diff --git a/src/commonMain/kotlin/core/ai/packages/AIPackageTemplateBuilder.kt b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplateBuilder.kt index 3e6bf30c8..747d8d9f5 100644 --- a/src/commonMain/kotlin/core/ai/packages/AIPackageTemplateBuilder.kt +++ b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplateBuilder.kt @@ -1,6 +1,7 @@ package core.ai.packages import core.thing.Thing +import core.utility.map //Build all the templates, but don't flatten them //Using the dependency injection, we could have many things producing the templates @@ -16,7 +17,8 @@ class AIPackageTemplateBuilder(val name: String) { private val priorityOverride = mutableMapOf() fun build(): AIPackageTemplate { - return AIPackageTemplate(name, templates, priorityOverride, ideas.map { it.build(name) }) + val childIdeas = criteriaChildren.flatMap { (parentCriteria, builder) -> builder.getChildren(parentCriteria) } + return AIPackageTemplate(name, templates, priorityOverride, (ideas + childIdeas).map { it.build(name) }) } fun template(vararg names: String) = this.templates.addAll(names) @@ -25,7 +27,7 @@ class AIPackageTemplateBuilder(val name: String) { Override the priority of an idea inherited from a template. */ fun priority(ideaName: String, newPriority: Int){ - + priorityOverride[ideaName] = newPriority } fun idea(name: String, priority: Int = 20, initializer: IdeaBuilder.() -> Unit = {}) { diff --git a/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt b/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt index d74044744..01818a8a9 100644 --- a/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt +++ b/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt @@ -18,10 +18,6 @@ class CommonPackages: AIPackageTemplateResource { criteria { !it.isSafe() } action { EatFoodEvent(it, it) } } - idea("wait") { - criteria { !it.isSafe() } - action { EatFoodEvent(it, it) } - } } } } From 167d638384281f6dd45b3475b7f616ecdc6e7849 Mon Sep 17 00:00:00 2001 From: ManApart Date: Wed, 24 Jan 2024 07:51:02 -0500 Subject: [PATCH 08/45] allow dropping of sub ideas --- .../kotlin/core/ai/packages/AIPackageTemplate.kt | 7 ++++--- .../kotlin/core/ai/packages/AIPackageTemplateBuilder.kt | 8 ++++++-- .../kotlin/resources/ai/packages/CommonPackages.kt | 4 +++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/commonMain/kotlin/core/ai/packages/AIPackageTemplate.kt b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplate.kt index 1180eb46a..e295d5ca8 100644 --- a/src/commonMain/kotlin/core/ai/packages/AIPackageTemplate.kt +++ b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplate.kt @@ -1,15 +1,16 @@ package core.ai.packages -data class AIPackageTemplate(val name: String, val subPackages: List, val priorityOverride: Map, val ideas: List) { +data class AIPackageTemplate(val name: String, val ideas: List, val subPackages: List, val priorityOverride: Map, val dropped: List) { fun flatten(reference: Map, flattenedReference: MutableMap): AIPackage { return flattenedReference[name] ?: let { val subIdeas = subPackages.mapNotNull { reference[it] }.flatMap { subPackage -> flattenedReference[subPackage.name]?.ideas?.values?.flatten() ?: subPackage.flatten(reference, flattenedReference).also { flattenedReference[subPackage.name] = it }.ideas.values.flatten() - }.map { idea -> - priorityOverride[it.name]?.let { idea.copy(priority = it) } ?: idea + }.filter { !dropped.contains(it.name) } + .map { idea -> + priorityOverride[idea.name]?.let { idea.copy(priority = it) } ?: idea }.map { it.copy(name = "$name-${it.name}")} AIPackage(name, ideas + subIdeas).also { flattenedReference[name] = it } } diff --git a/src/commonMain/kotlin/core/ai/packages/AIPackageTemplateBuilder.kt b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplateBuilder.kt index 747d8d9f5..3db441814 100644 --- a/src/commonMain/kotlin/core/ai/packages/AIPackageTemplateBuilder.kt +++ b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplateBuilder.kt @@ -1,7 +1,6 @@ package core.ai.packages import core.thing.Thing -import core.utility.map //Build all the templates, but don't flatten them //Using the dependency injection, we could have many things producing the templates @@ -15,10 +14,11 @@ class AIPackageTemplateBuilder(val name: String) { private val ideas = mutableListOf() private val criteriaChildren = mutableMapOf Boolean, IdeasBuilder>() private val priorityOverride = mutableMapOf() + private val dropped = mutableListOf() fun build(): AIPackageTemplate { val childIdeas = criteriaChildren.flatMap { (parentCriteria, builder) -> builder.getChildren(parentCriteria) } - return AIPackageTemplate(name, templates, priorityOverride, (ideas + childIdeas).map { it.build(name) }) + return AIPackageTemplate(name, (ideas + childIdeas).map { it.build(name) }, templates, priorityOverride, dropped) } fun template(vararg names: String) = this.templates.addAll(names) @@ -30,6 +30,10 @@ class AIPackageTemplateBuilder(val name: String) { priorityOverride[ideaName] = newPriority } + fun drop(vararg ideaName: String){ + dropped.addAll(ideaName) + } + fun idea(name: String, priority: Int = 20, initializer: IdeaBuilder.() -> Unit = {}) { ideas.add(IdeaBuilder(name, priority).apply(initializer)) } diff --git a/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt b/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt index 01818a8a9..207e025c1 100644 --- a/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt +++ b/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt @@ -22,8 +22,10 @@ class CommonPackages: AIPackageTemplateResource { } } aiPackage("predator") { - priority("eat", 15) template("animal") + priority("animal-eat", 15) + drop("animal-wait") + idea("hunt") { criteria { it.soul.getConditions().isEmpty() } action { RestEvent(it, 1) } From 70d652628719f6eec784fd6d67d05d171fd808f5 Mon Sep 17 00:00:00 2001 From: ManApart Date: Wed, 24 Jan 2024 08:24:53 -0500 Subject: [PATCH 09/45] progress replicating packages --- .../ai/packages/AIPackageTemplateBuilder.kt | 22 ++-- .../kotlin/core/ai/packages/IdeaBuilder.kt | 6 +- .../resources/ai/packages/CommonPackages.kt | 102 ++++++++++++++---- 3 files changed, 96 insertions(+), 34 deletions(-) diff --git a/src/commonMain/kotlin/core/ai/packages/AIPackageTemplateBuilder.kt b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplateBuilder.kt index 3db441814..210afd35e 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 56cfe404d..622131562 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 207e025c1..59ab80f54 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 From 6541b83c11a2f8d79baff8e94edaaf8c66fed74a Mon Sep 17 00:00:00 2001 From: ManApart Date: Thu, 25 Jan 2024 07:27:35 -0500 Subject: [PATCH 10/45] use target goal --- .../core/ai/knowledge/CreatureMemory.kt | 9 +++ .../kotlin/core/ai/knowledge/ForgetFact.kt | 17 ++++++ .../core/ai/knowledge/ForgetFactEvent.kt | 10 ++++ .../kotlin/core/ai/knowledge/Mind.kt | 12 +--- .../core/ai/packages/AIPackageHelpers.kt | 46 ++++++++++++++++ .../core/events/EventListenerMapGenerated.kt | 4 +- .../resources/ai/agenda/CommonAgendas.kt | 10 ++-- .../resources/ai/packages/CommonPackages.kt | 55 +++---------------- 8 files changed, 100 insertions(+), 63 deletions(-) create mode 100644 src/commonMain/kotlin/core/ai/knowledge/ForgetFact.kt create mode 100644 src/commonMain/kotlin/core/ai/knowledge/ForgetFactEvent.kt create mode 100644 src/commonMain/kotlin/core/ai/packages/AIPackageHelpers.kt diff --git a/src/commonMain/kotlin/core/ai/knowledge/CreatureMemory.kt b/src/commonMain/kotlin/core/ai/knowledge/CreatureMemory.kt index 7d7567d57..7b23abcf8 100644 --- a/src/commonMain/kotlin/core/ai/knowledge/CreatureMemory.kt +++ b/src/commonMain/kotlin/core/ai/knowledge/CreatureMemory.kt @@ -8,6 +8,10 @@ data class CreatureMemory( ) { constructor(facts: List, listFacts: List) : this(facts.parsedFacts(), listFacts.parsedListFacts()) + fun getFirstFact(kind: String): Fact? { + return facts[kind]?.values?.firstOrNull() + } + fun getFact(source: Subject, kind: String): Fact? { return facts[kind]?.get(source) } @@ -49,6 +53,11 @@ data class CreatureMemory( listFacts.remove(fact.kind) } + fun forget(kind: String){ + facts.remove(kind) + listFacts.remove(kind) + } + fun forget() { facts.clear() } diff --git a/src/commonMain/kotlin/core/ai/knowledge/ForgetFact.kt b/src/commonMain/kotlin/core/ai/knowledge/ForgetFact.kt new file mode 100644 index 000000000..947f4801b --- /dev/null +++ b/src/commonMain/kotlin/core/ai/knowledge/ForgetFact.kt @@ -0,0 +1,17 @@ +package core.ai.knowledge + +import core.events.EventListener + +class ForgetFact : EventListener() { + + override suspend fun complete(event: ForgetFactEvent) { + with(event.source.mind.memory) { + when { + event.fact != null -> forget(event.fact) + event.listFact != null -> forget(event.listFact) + event.kind != null -> forget(event.kind) + } + } + + } +} \ No newline at end of file diff --git a/src/commonMain/kotlin/core/ai/knowledge/ForgetFactEvent.kt b/src/commonMain/kotlin/core/ai/knowledge/ForgetFactEvent.kt new file mode 100644 index 000000000..09b47670b --- /dev/null +++ b/src/commonMain/kotlin/core/ai/knowledge/ForgetFactEvent.kt @@ -0,0 +1,10 @@ +package core.ai.knowledge + +import core.events.Event +import core.thing.Thing + +data class ForgetFactEvent(val source: Thing, val fact: Fact? = null, val listFact: ListFact? = null, val kind: String? = null) : Event { + init { + require(fact != null || listFact != null || kind != null) + } +} diff --git a/src/commonMain/kotlin/core/ai/knowledge/Mind.kt b/src/commonMain/kotlin/core/ai/knowledge/Mind.kt index 327a402aa..5642d5de6 100644 --- a/src/commonMain/kotlin/core/ai/knowledge/Mind.kt +++ b/src/commonMain/kotlin/core/ai/knowledge/Mind.kt @@ -102,18 +102,12 @@ data class Mind( } } - fun setUseTarget(enemy: Thing) { - learn(Fact(Subject(enemy), "useTarget")) - } - - suspend fun getUseTarget(): Thing? { + suspend fun getUseTargetThing(): Thing? { return knowsThingByKind("useTarget") } - suspend fun clearUseTarget() { - getAggroTarget()?.let { - memory.forget(Fact(Subject(it), "useTarget")) - } + fun getUseTarget(): Fact? { + return memory.getFirstFact("useTarget") } } \ No newline at end of file diff --git a/src/commonMain/kotlin/core/ai/packages/AIPackageHelpers.kt b/src/commonMain/kotlin/core/ai/packages/AIPackageHelpers.kt new file mode 100644 index 000000000..4e159b17a --- /dev/null +++ b/src/commonMain/kotlin/core/ai/packages/AIPackageHelpers.kt @@ -0,0 +1,46 @@ +package core.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.ForgetFactEvent +import core.ai.knowledge.Subject +import core.properties.Properties +import core.properties.Values +import core.thing.Thing +import core.utility.RandomManager +import traveling.position.ThingAim + + +suspend fun Thing.hasUseTarget() = mind.getUseTargetThing() != null + +fun Thing.setUseGoal(target: Thing, howToUse: String): DiscoverFactEvent { + return DiscoverFactEvent(this, Fact(Subject(target), "useTarget", Properties(Values(mutableMapOf("goal" to howToUse))))) +} + +fun Thing.clearUseGoal(): ForgetFactEvent { + return ForgetFactEvent(this, kind ="useTarget") +} + +suspend fun Thing.canReachGoal(howToUse: String ): Boolean { + val useTarget = mind.getUseTarget() + return useTarget != null && useTarget.props.values.getString("goal") == howToUse && useTarget.source.getThing()?.position?.let { pos -> canReach(pos) } ?: false +} + +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 diff --git a/src/commonMain/kotlin/core/events/EventListenerMapGenerated.kt b/src/commonMain/kotlin/core/events/EventListenerMapGenerated.kt index 580bdc96a..0b7443389 100644 --- a/src/commonMain/kotlin/core/events/EventListenerMapGenerated.kt +++ b/src/commonMain/kotlin/core/events/EventListenerMapGenerated.kt @@ -1,7 +1,7 @@ package core.events class EventListenerMapGenerated : EventListenerMapCollection { - private val listenerMap: Map> = mapOf("combat.attack.Attack" to combat.attack.Attack(), "combat.block.Block" to combat.block.Block(), "combat.takeDamage.TakeDamage" to combat.takeDamage.TakeDamage(), "conversation.dialogue.DialogueListener" to conversation.dialogue.DialogueListener(), "conversation.end.EndConversation" to conversation.end.EndConversation(), "conversation.start.StartConversation" to conversation.start.StartConversation(), "core.MessageHandler" to core.MessageHandler(), "core.ai.knowledge.DiscoverFact" to core.ai.knowledge.DiscoverFact(), "core.commands.commandEvent.CommandEventListener" to core.commands.commandEvent.CommandEventListener(), "core.history.SessionListener" to core.history.SessionListener(), "core.properties.SetProperties" to core.properties.SetProperties(), "core.properties.propValChanged.PropertyStatChanged" to core.properties.propValChanged.PropertyStatChanged(), "core.properties.propValChanged.PropertyStatMinned" to core.properties.propValChanged.PropertyStatMinned(), "core.thing.item.ItemSpawner" to core.thing.item.ItemSpawner(), "crafting.DiscoverRecipe" to crafting.DiscoverRecipe(), "crafting.checkRecipe.CheckRecipes" to crafting.checkRecipe.CheckRecipes(), "crafting.craft.Craft" to crafting.craft.Craft(), "explore.examine.Examine" to explore.examine.Examine(), "explore.listen.Listen" to explore.listen.Listen(), "explore.look.Look" to explore.look.Look(), "explore.map.ReadMap" to explore.map.ReadMap(), "explore.map.compass.SetCompassGoal" to explore.map.compass.SetCompassGoal(), "explore.map.compass.ViewCompass" to explore.map.compass.ViewCompass(), "inventory.ViewEquipped" to inventory.ViewEquipped(), "inventory.ViewInventory" to inventory.ViewInventory(), "inventory.dropItem.ItemDropped" to inventory.dropItem.ItemDropped(), "inventory.dropItem.PlaceItem" to inventory.dropItem.PlaceItem(), "inventory.equipItem.EquipItem" to inventory.equipItem.EquipItem(), "inventory.equipItem.ItemEquipped" to inventory.equipItem.ItemEquipped(), "inventory.pickupItem.ItemPickedUp" to inventory.pickupItem.ItemPickedUp(), "inventory.pickupItem.TakeItem" to inventory.pickupItem.TakeItem(), "inventory.putItem.TransferItem" to inventory.putItem.TransferItem(), "inventory.unEquipItem.ItemUnEquipped" to inventory.unEquipItem.ItemUnEquipped(), "inventory.unEquipItem.UnEquipItem" to inventory.unEquipItem.UnEquipItem(), "magic.ViewWordHelp" to magic.ViewWordHelp(), "magic.castSpell.CastSpell" to magic.castSpell.CastSpell(), "quests.CompleteQuest" to quests.CompleteQuest(), "quests.QuestListener" to quests.QuestListener(), "quests.journal.ViewQuestJournal" to quests.journal.ViewQuestJournal(), "quests.journal.ViewQuestList" to quests.journal.ViewQuestList(), "status.ExpGained" to status.ExpGained(), "status.LevelUp" to status.LevelUp(), "status.conditions.AddCondition" to status.conditions.AddCondition(), "status.conditions.ConditionRemover" to status.conditions.ConditionRemover(), "status.conditions.RemoveCondition" to status.conditions.RemoveCondition(), "status.effects.ApplyEffects" to status.effects.ApplyEffects(), "status.rest.Rest" to status.rest.Rest(), "status.statChanged.CreatureDied" to status.statChanged.CreatureDied(), "status.statChanged.PlayerStatMaxed" to status.statChanged.PlayerStatMaxed(), "status.statChanged.PlayerStatMinned" to status.statChanged.PlayerStatMinned(), "status.statChanged.StatBoosted" to status.statChanged.StatBoosted(), "status.statChanged.StatChanged" to status.statChanged.StatChanged(), "status.statChanged.StatMinned" to status.statChanged.StatMinned(), "status.status.Status" to status.status.Status(), "system.alias.CreateAlias" to system.alias.CreateAlias(), "system.alias.DeleteAlias" to system.alias.DeleteAlias(), "system.alias.ListAlias" to system.alias.ListAlias(), "system.connection.Connect" to system.connection.Connect(), "system.connection.ConnectInfo" to system.connection.ConnectInfo(), "system.connection.Disconnect" to system.connection.Disconnect(), "system.debug.DebugListListener" to system.debug.DebugListListener(), "system.debug.DebugStatListener" to system.debug.DebugStatListener(), "system.debug.DebugTagListener" to system.debug.DebugTagListener(), "system.debug.DebugToggleListener" to system.debug.DebugToggleListener(), "system.debug.DebugWeatherListener" to system.debug.DebugWeatherListener(), "system.help.ViewHelp" to system.help.ViewHelp(), "system.history.ViewGameLog" to system.history.ViewGameLog(), "system.message.DisplayMessage" to system.message.DisplayMessage(), "system.persistance.changePlayer.ListCharacters" to system.persistance.changePlayer.ListCharacters(), "system.persistance.changePlayer.PlayAs" to system.persistance.changePlayer.PlayAs(), "system.persistance.createPlayer.CreateCharacter" to system.persistance.createPlayer.CreateCharacter(), "system.persistance.loading.ListSaves" to system.persistance.loading.ListSaves(), "system.persistance.loading.Load" to system.persistance.loading.Load(), "system.persistance.newGame.CreateNewGame" to system.persistance.newGame.CreateNewGame(), "system.persistance.saving.Save" to system.persistance.saving.Save(), "time.ViewTime" to time.ViewTime(), "time.gameTick.TimeListener" to time.gameTick.TimeListener(), "traveling.RestrictLocation" to traveling.RestrictLocation(), "traveling.arrive.ArrivalHandler" to traveling.arrive.ArrivalHandler(), "traveling.arrive.Arrive" to traveling.arrive.Arrive(), "traveling.climb.AttemptClimb" to traveling.climb.AttemptClimb(), "traveling.climb.ClimbComplete" to traveling.climb.ClimbComplete(), "traveling.jump.PlayerFall" to traveling.jump.PlayerFall(), "traveling.jump.PlayerJump" to traveling.jump.PlayerJump(), "traveling.location.weather.WeatherListener" to traveling.location.weather.WeatherListener(), "traveling.move.Move" to traveling.move.Move(), "traveling.routes.FindRoute" to traveling.routes.FindRoute(), "traveling.routes.ViewRoute" to traveling.routes.ViewRoute(), "traveling.scope.remove.RemoveItem" to traveling.scope.remove.RemoveItem(), "traveling.scope.remove.RemoveScope" to traveling.scope.remove.RemoveScope(), "traveling.scope.spawn.ActivatorSpawner" to traveling.scope.spawn.ActivatorSpawner(), "traveling.scope.spawn.SpawnItem" to traveling.scope.spawn.SpawnItem(), "traveling.travel.TravelStart" to traveling.travel.TravelStart(), "use.actions.ChopWood" to use.actions.ChopWood(), "use.actions.DamageCreature" to use.actions.DamageCreature(), "use.actions.NoUseFound" to use.actions.NoUseFound(), "use.actions.ScratchSurface" to use.actions.ScratchSurface(), "use.actions.StartFire" to use.actions.StartFire(), "use.actions.UseFoodItem" to use.actions.UseFoodItem(), "use.actions.UseIngredientOnActivatorRecipe" to use.actions.UseIngredientOnActivatorRecipe(), "use.actions.UseItemOnIngredientRecipe" to use.actions.UseItemOnIngredientRecipe(), "use.actions.UseOnFire" to use.actions.UseOnFire(), "use.eat.EatFood" to use.eat.EatFood(), "use.interaction.Interact" to use.interaction.Interact(), "use.interaction.NoInteractionFound" to use.interaction.NoInteractionFound(), "use.interaction.nothing.DoNothing" to use.interaction.nothing.DoNothing()) + private val listenerMap: Map> = mapOf("combat.attack.Attack" to combat.attack.Attack(), "combat.block.Block" to combat.block.Block(), "combat.takeDamage.TakeDamage" to combat.takeDamage.TakeDamage(), "conversation.dialogue.DialogueListener" to conversation.dialogue.DialogueListener(), "conversation.end.EndConversation" to conversation.end.EndConversation(), "conversation.start.StartConversation" to conversation.start.StartConversation(), "core.MessageHandler" to core.MessageHandler(), "core.ai.knowledge.DiscoverFact" to core.ai.knowledge.DiscoverFact(), "core.ai.knowledge.ForgetFact" to core.ai.knowledge.ForgetFact(), "core.commands.commandEvent.CommandEventListener" to core.commands.commandEvent.CommandEventListener(), "core.history.SessionListener" to core.history.SessionListener(), "core.properties.SetProperties" to core.properties.SetProperties(), "core.properties.propValChanged.PropertyStatChanged" to core.properties.propValChanged.PropertyStatChanged(), "core.properties.propValChanged.PropertyStatMinned" to core.properties.propValChanged.PropertyStatMinned(), "core.thing.item.ItemSpawner" to core.thing.item.ItemSpawner(), "crafting.DiscoverRecipe" to crafting.DiscoverRecipe(), "crafting.checkRecipe.CheckRecipes" to crafting.checkRecipe.CheckRecipes(), "crafting.craft.Craft" to crafting.craft.Craft(), "explore.examine.Examine" to explore.examine.Examine(), "explore.listen.Listen" to explore.listen.Listen(), "explore.look.Look" to explore.look.Look(), "explore.map.ReadMap" to explore.map.ReadMap(), "explore.map.compass.SetCompassGoal" to explore.map.compass.SetCompassGoal(), "explore.map.compass.ViewCompass" to explore.map.compass.ViewCompass(), "inventory.ViewEquipped" to inventory.ViewEquipped(), "inventory.ViewInventory" to inventory.ViewInventory(), "inventory.dropItem.ItemDropped" to inventory.dropItem.ItemDropped(), "inventory.dropItem.PlaceItem" to inventory.dropItem.PlaceItem(), "inventory.equipItem.EquipItem" to inventory.equipItem.EquipItem(), "inventory.equipItem.ItemEquipped" to inventory.equipItem.ItemEquipped(), "inventory.pickupItem.ItemPickedUp" to inventory.pickupItem.ItemPickedUp(), "inventory.pickupItem.TakeItem" to inventory.pickupItem.TakeItem(), "inventory.putItem.TransferItem" to inventory.putItem.TransferItem(), "inventory.unEquipItem.ItemUnEquipped" to inventory.unEquipItem.ItemUnEquipped(), "inventory.unEquipItem.UnEquipItem" to inventory.unEquipItem.UnEquipItem(), "magic.ViewWordHelp" to magic.ViewWordHelp(), "magic.castSpell.CastSpell" to magic.castSpell.CastSpell(), "quests.CompleteQuest" to quests.CompleteQuest(), "quests.QuestListener" to quests.QuestListener(), "quests.journal.ViewQuestJournal" to quests.journal.ViewQuestJournal(), "quests.journal.ViewQuestList" to quests.journal.ViewQuestList(), "status.ExpGained" to status.ExpGained(), "status.LevelUp" to status.LevelUp(), "status.conditions.AddCondition" to status.conditions.AddCondition(), "status.conditions.ConditionRemover" to status.conditions.ConditionRemover(), "status.conditions.RemoveCondition" to status.conditions.RemoveCondition(), "status.effects.ApplyEffects" to status.effects.ApplyEffects(), "status.rest.Rest" to status.rest.Rest(), "status.statChanged.CreatureDied" to status.statChanged.CreatureDied(), "status.statChanged.PlayerStatMaxed" to status.statChanged.PlayerStatMaxed(), "status.statChanged.PlayerStatMinned" to status.statChanged.PlayerStatMinned(), "status.statChanged.StatBoosted" to status.statChanged.StatBoosted(), "status.statChanged.StatChanged" to status.statChanged.StatChanged(), "status.statChanged.StatMinned" to status.statChanged.StatMinned(), "status.status.Status" to status.status.Status(), "system.alias.CreateAlias" to system.alias.CreateAlias(), "system.alias.DeleteAlias" to system.alias.DeleteAlias(), "system.alias.ListAlias" to system.alias.ListAlias(), "system.connection.Connect" to system.connection.Connect(), "system.connection.ConnectInfo" to system.connection.ConnectInfo(), "system.connection.Disconnect" to system.connection.Disconnect(), "system.debug.DebugListListener" to system.debug.DebugListListener(), "system.debug.DebugStatListener" to system.debug.DebugStatListener(), "system.debug.DebugTagListener" to system.debug.DebugTagListener(), "system.debug.DebugToggleListener" to system.debug.DebugToggleListener(), "system.debug.DebugWeatherListener" to system.debug.DebugWeatherListener(), "system.help.ViewHelp" to system.help.ViewHelp(), "system.history.ViewGameLog" to system.history.ViewGameLog(), "system.message.DisplayMessage" to system.message.DisplayMessage(), "system.persistance.changePlayer.ListCharacters" to system.persistance.changePlayer.ListCharacters(), "system.persistance.changePlayer.PlayAs" to system.persistance.changePlayer.PlayAs(), "system.persistance.createPlayer.CreateCharacter" to system.persistance.createPlayer.CreateCharacter(), "system.persistance.loading.ListSaves" to system.persistance.loading.ListSaves(), "system.persistance.loading.Load" to system.persistance.loading.Load(), "system.persistance.newGame.CreateNewGame" to system.persistance.newGame.CreateNewGame(), "system.persistance.saving.Save" to system.persistance.saving.Save(), "time.ViewTime" to time.ViewTime(), "time.gameTick.TimeListener" to time.gameTick.TimeListener(), "traveling.RestrictLocation" to traveling.RestrictLocation(), "traveling.arrive.ArrivalHandler" to traveling.arrive.ArrivalHandler(), "traveling.arrive.Arrive" to traveling.arrive.Arrive(), "traveling.climb.AttemptClimb" to traveling.climb.AttemptClimb(), "traveling.climb.ClimbComplete" to traveling.climb.ClimbComplete(), "traveling.jump.PlayerFall" to traveling.jump.PlayerFall(), "traveling.jump.PlayerJump" to traveling.jump.PlayerJump(), "traveling.location.weather.WeatherListener" to traveling.location.weather.WeatherListener(), "traveling.move.Move" to traveling.move.Move(), "traveling.routes.FindRoute" to traveling.routes.FindRoute(), "traveling.routes.ViewRoute" to traveling.routes.ViewRoute(), "traveling.scope.remove.RemoveItem" to traveling.scope.remove.RemoveItem(), "traveling.scope.remove.RemoveScope" to traveling.scope.remove.RemoveScope(), "traveling.scope.spawn.ActivatorSpawner" to traveling.scope.spawn.ActivatorSpawner(), "traveling.scope.spawn.SpawnItem" to traveling.scope.spawn.SpawnItem(), "traveling.travel.TravelStart" to traveling.travel.TravelStart(), "use.actions.ChopWood" to use.actions.ChopWood(), "use.actions.DamageCreature" to use.actions.DamageCreature(), "use.actions.NoUseFound" to use.actions.NoUseFound(), "use.actions.ScratchSurface" to use.actions.ScratchSurface(), "use.actions.StartFire" to use.actions.StartFire(), "use.actions.UseFoodItem" to use.actions.UseFoodItem(), "use.actions.UseIngredientOnActivatorRecipe" to use.actions.UseIngredientOnActivatorRecipe(), "use.actions.UseItemOnIngredientRecipe" to use.actions.UseItemOnIngredientRecipe(), "use.actions.UseOnFire" to use.actions.UseOnFire(), "use.eat.EatFood" to use.eat.EatFood(), "use.interaction.Interact" to use.interaction.Interact(), "use.interaction.NoInteractionFound" to use.interaction.NoInteractionFound(), "use.interaction.nothing.DoNothing" to use.interaction.nothing.DoNothing()) - override val values: Map>> = mapOf("AttackEvent" to listOf(listenerMap["combat.attack.Attack"]!!), "BlockEvent" to listOf(listenerMap["combat.block.Block"]!!), "TakeDamageEvent" to listOf(listenerMap["combat.takeDamage.TakeDamage"]!!), "DialogueEvent" to listOf(listenerMap["conversation.dialogue.DialogueListener"]!!), "EndConversationEvent" to listOf(listenerMap["conversation.end.EndConversation"]!!), "StartConversationEvent" to listOf(listenerMap["conversation.start.StartConversation"]!!), "MessageEvent" to listOf(listenerMap["core.MessageHandler"]!!), "DiscoverFactEvent" to listOf(listenerMap["core.ai.knowledge.DiscoverFact"]!!), "CommandEvent" to listOf(listenerMap["core.commands.commandEvent.CommandEventListener"]!!), "Event" to listOf(listenerMap["core.history.SessionListener"]!!,listenerMap["quests.QuestListener"]!!), "SetPropertiesEvent" to listOf(listenerMap["core.properties.SetProperties"]!!), "PropertyStatChangeEvent" to listOf(listenerMap["core.properties.propValChanged.PropertyStatChanged"]!!), "PropertyStatMinnedEvent" to listOf(listenerMap["core.properties.propValChanged.PropertyStatMinned"]!!), "SpawnItemEvent" to listOf(listenerMap["core.thing.item.ItemSpawner"]!!), "DiscoverRecipeEvent" to listOf(listenerMap["crafting.DiscoverRecipe"]!!), "CheckRecipeEvent" to listOf(listenerMap["crafting.checkRecipe.CheckRecipes"]!!), "CraftRecipeEvent" to listOf(listenerMap["crafting.craft.Craft"]!!), "ExamineEvent" to listOf(listenerMap["explore.examine.Examine"]!!), "ListenEvent" to listOf(listenerMap["explore.listen.Listen"]!!), "LookEvent" to listOf(listenerMap["explore.look.Look"]!!), "ReadMapEvent" to listOf(listenerMap["explore.map.ReadMap"]!!), "SetCompassEvent" to listOf(listenerMap["explore.map.compass.SetCompassGoal"]!!), "ViewCompassEvent" to listOf(listenerMap["explore.map.compass.ViewCompass"]!!), "ViewEquippedEvent" to listOf(listenerMap["inventory.ViewEquipped"]!!), "ViewInventoryEvent" to listOf(listenerMap["inventory.ViewInventory"]!!), "ItemDroppedEvent" to listOf(listenerMap["inventory.dropItem.ItemDropped"]!!), "PlaceItemEvent" to listOf(listenerMap["inventory.dropItem.PlaceItem"]!!), "EquipItemEvent" to listOf(listenerMap["inventory.equipItem.EquipItem"]!!), "ItemEquippedEvent" to listOf(listenerMap["inventory.equipItem.ItemEquipped"]!!), "ItemPickedUpEvent" to listOf(listenerMap["inventory.pickupItem.ItemPickedUp"]!!), "TakeItemEvent" to listOf(listenerMap["inventory.pickupItem.TakeItem"]!!), "TransferItemEvent" to listOf(listenerMap["inventory.putItem.TransferItem"]!!), "ItemUnEquippedEvent" to listOf(listenerMap["inventory.unEquipItem.ItemUnEquipped"]!!), "UnEquipItemEvent" to listOf(listenerMap["inventory.unEquipItem.UnEquipItem"]!!), "ViewWordHelpEvent" to listOf(listenerMap["magic.ViewWordHelp"]!!), "CastSpellEvent" to listOf(listenerMap["magic.castSpell.CastSpell"]!!), "CompleteQuestEvent" to listOf(listenerMap["quests.CompleteQuest"]!!), "ViewQuestJournalEvent" to listOf(listenerMap["quests.journal.ViewQuestJournal"]!!), "ViewQuestListEvent" to listOf(listenerMap["quests.journal.ViewQuestList"]!!), "ExpGainedEvent" to listOf(listenerMap["status.ExpGained"]!!), "LevelUpEvent" to listOf(listenerMap["status.LevelUp"]!!), "AddConditionEvent" to listOf(listenerMap["status.conditions.AddCondition"]!!), "GameTickEvent" to listOf(listenerMap["status.conditions.ConditionRemover"]!!,listenerMap["status.effects.ApplyEffects"]!!,listenerMap["time.gameTick.TimeListener"]!!,listenerMap["traveling.location.weather.WeatherListener"]!!), "RemoveConditionEvent" to listOf(listenerMap["status.conditions.RemoveCondition"]!!), "RestEvent" to listOf(listenerMap["status.rest.Rest"]!!), "StatMinnedEvent" to listOf(listenerMap["status.statChanged.CreatureDied"]!!,listenerMap["status.statChanged.PlayerStatMinned"]!!,listenerMap["status.statChanged.StatMinned"]!!), "StatMaxedEvent" to listOf(listenerMap["status.statChanged.PlayerStatMaxed"]!!), "StatBoostEvent" to listOf(listenerMap["status.statChanged.StatBoosted"]!!), "StatChangeEvent" to listOf(listenerMap["status.statChanged.StatChanged"]!!), "StatusEvent" to listOf(listenerMap["status.status.Status"]!!), "CreateAliasEvent" to listOf(listenerMap["system.alias.CreateAlias"]!!), "DeleteAliasEvent" to listOf(listenerMap["system.alias.DeleteAlias"]!!), "ListAliasesEvent" to listOf(listenerMap["system.alias.ListAlias"]!!), "ConnectEvent" to listOf(listenerMap["system.connection.Connect"]!!), "ConnectInfoEvent" to listOf(listenerMap["system.connection.ConnectInfo"]!!), "DisconnectEvent" to listOf(listenerMap["system.connection.Disconnect"]!!), "DebugListEvent" to listOf(listenerMap["system.debug.DebugListListener"]!!), "DebugStatEvent" to listOf(listenerMap["system.debug.DebugStatListener"]!!), "DebugTagEvent" to listOf(listenerMap["system.debug.DebugTagListener"]!!), "DebugToggleEvent" to listOf(listenerMap["system.debug.DebugToggleListener"]!!), "DebugWeatherEvent" to listOf(listenerMap["system.debug.DebugWeatherListener"]!!), "ViewHelpEvent" to listOf(listenerMap["system.help.ViewHelp"]!!), "ViewGameLogEvent" to listOf(listenerMap["system.history.ViewGameLog"]!!), "DisplayMessageEvent" to listOf(listenerMap["system.message.DisplayMessage"]!!), "ListCharactersEvent" to listOf(listenerMap["system.persistance.changePlayer.ListCharacters"]!!), "PlayAsEvent" to listOf(listenerMap["system.persistance.changePlayer.PlayAs"]!!), "CreateCharacterEvent" to listOf(listenerMap["system.persistance.createPlayer.CreateCharacter"]!!), "ListSavesEvent" to listOf(listenerMap["system.persistance.loading.ListSaves"]!!), "LoadEvent" to listOf(listenerMap["system.persistance.loading.Load"]!!), "CreateNewGameEvent" to listOf(listenerMap["system.persistance.newGame.CreateNewGame"]!!), "SaveEvent" to listOf(listenerMap["system.persistance.saving.Save"]!!), "ViewTimeEvent" to listOf(listenerMap["time.ViewTime"]!!), "RestrictLocationEvent" to listOf(listenerMap["traveling.RestrictLocation"]!!), "ArriveEvent" to listOf(listenerMap["traveling.arrive.ArrivalHandler"]!!,listenerMap["traveling.arrive.Arrive"]!!), "AttemptClimbEvent" to listOf(listenerMap["traveling.climb.AttemptClimb"]!!), "ClimbCompleteEvent" to listOf(listenerMap["traveling.climb.ClimbComplete"]!!), "FallEvent" to listOf(listenerMap["traveling.jump.PlayerFall"]!!), "JumpEvent" to listOf(listenerMap["traveling.jump.PlayerJump"]!!), "MoveEvent" to listOf(listenerMap["traveling.move.Move"]!!), "FindRouteEvent" to listOf(listenerMap["traveling.routes.FindRoute"]!!), "ViewRouteEvent" to listOf(listenerMap["traveling.routes.ViewRoute"]!!), "RemoveItemEvent" to listOf(listenerMap["traveling.scope.remove.RemoveItem"]!!), "RemoveScopeEvent" to listOf(listenerMap["traveling.scope.remove.RemoveScope"]!!), "SpawnActivatorEvent" to listOf(listenerMap["traveling.scope.spawn.ActivatorSpawner"]!!), "ItemSpawnedEvent" to listOf(listenerMap["traveling.scope.spawn.SpawnItem"]!!), "TravelStartEvent" to listOf(listenerMap["traveling.travel.TravelStart"]!!), "UseEvent" to listOf(listenerMap["use.actions.ChopWood"]!!,listenerMap["use.actions.DamageCreature"]!!,listenerMap["use.actions.NoUseFound"]!!,listenerMap["use.actions.ScratchSurface"]!!,listenerMap["use.actions.StartFire"]!!,listenerMap["use.actions.UseFoodItem"]!!,listenerMap["use.actions.UseIngredientOnActivatorRecipe"]!!,listenerMap["use.actions.UseItemOnIngredientRecipe"]!!,listenerMap["use.actions.UseOnFire"]!!), "EatFoodEvent" to listOf(listenerMap["use.eat.EatFood"]!!), "InteractEvent" to listOf(listenerMap["use.interaction.Interact"]!!,listenerMap["use.interaction.NoInteractionFound"]!!), "NothingEvent" to listOf(listenerMap["use.interaction.nothing.DoNothing"]!!)) + override val values: Map>> = mapOf("AttackEvent" to listOf(listenerMap["combat.attack.Attack"]!!), "BlockEvent" to listOf(listenerMap["combat.block.Block"]!!), "TakeDamageEvent" to listOf(listenerMap["combat.takeDamage.TakeDamage"]!!), "DialogueEvent" to listOf(listenerMap["conversation.dialogue.DialogueListener"]!!), "EndConversationEvent" to listOf(listenerMap["conversation.end.EndConversation"]!!), "StartConversationEvent" to listOf(listenerMap["conversation.start.StartConversation"]!!), "MessageEvent" to listOf(listenerMap["core.MessageHandler"]!!), "DiscoverFactEvent" to listOf(listenerMap["core.ai.knowledge.DiscoverFact"]!!), "ForgetFactEvent" to listOf(listenerMap["core.ai.knowledge.ForgetFact"]!!), "CommandEvent" to listOf(listenerMap["core.commands.commandEvent.CommandEventListener"]!!), "Event" to listOf(listenerMap["core.history.SessionListener"]!!,listenerMap["quests.QuestListener"]!!), "SetPropertiesEvent" to listOf(listenerMap["core.properties.SetProperties"]!!), "PropertyStatChangeEvent" to listOf(listenerMap["core.properties.propValChanged.PropertyStatChanged"]!!), "PropertyStatMinnedEvent" to listOf(listenerMap["core.properties.propValChanged.PropertyStatMinned"]!!), "SpawnItemEvent" to listOf(listenerMap["core.thing.item.ItemSpawner"]!!), "DiscoverRecipeEvent" to listOf(listenerMap["crafting.DiscoverRecipe"]!!), "CheckRecipeEvent" to listOf(listenerMap["crafting.checkRecipe.CheckRecipes"]!!), "CraftRecipeEvent" to listOf(listenerMap["crafting.craft.Craft"]!!), "ExamineEvent" to listOf(listenerMap["explore.examine.Examine"]!!), "ListenEvent" to listOf(listenerMap["explore.listen.Listen"]!!), "LookEvent" to listOf(listenerMap["explore.look.Look"]!!), "ReadMapEvent" to listOf(listenerMap["explore.map.ReadMap"]!!), "SetCompassEvent" to listOf(listenerMap["explore.map.compass.SetCompassGoal"]!!), "ViewCompassEvent" to listOf(listenerMap["explore.map.compass.ViewCompass"]!!), "ViewEquippedEvent" to listOf(listenerMap["inventory.ViewEquipped"]!!), "ViewInventoryEvent" to listOf(listenerMap["inventory.ViewInventory"]!!), "ItemDroppedEvent" to listOf(listenerMap["inventory.dropItem.ItemDropped"]!!), "PlaceItemEvent" to listOf(listenerMap["inventory.dropItem.PlaceItem"]!!), "EquipItemEvent" to listOf(listenerMap["inventory.equipItem.EquipItem"]!!), "ItemEquippedEvent" to listOf(listenerMap["inventory.equipItem.ItemEquipped"]!!), "ItemPickedUpEvent" to listOf(listenerMap["inventory.pickupItem.ItemPickedUp"]!!), "TakeItemEvent" to listOf(listenerMap["inventory.pickupItem.TakeItem"]!!), "TransferItemEvent" to listOf(listenerMap["inventory.putItem.TransferItem"]!!), "ItemUnEquippedEvent" to listOf(listenerMap["inventory.unEquipItem.ItemUnEquipped"]!!), "UnEquipItemEvent" to listOf(listenerMap["inventory.unEquipItem.UnEquipItem"]!!), "ViewWordHelpEvent" to listOf(listenerMap["magic.ViewWordHelp"]!!), "CastSpellEvent" to listOf(listenerMap["magic.castSpell.CastSpell"]!!), "CompleteQuestEvent" to listOf(listenerMap["quests.CompleteQuest"]!!), "ViewQuestJournalEvent" to listOf(listenerMap["quests.journal.ViewQuestJournal"]!!), "ViewQuestListEvent" to listOf(listenerMap["quests.journal.ViewQuestList"]!!), "ExpGainedEvent" to listOf(listenerMap["status.ExpGained"]!!), "LevelUpEvent" to listOf(listenerMap["status.LevelUp"]!!), "AddConditionEvent" to listOf(listenerMap["status.conditions.AddCondition"]!!), "GameTickEvent" to listOf(listenerMap["status.conditions.ConditionRemover"]!!,listenerMap["status.effects.ApplyEffects"]!!,listenerMap["time.gameTick.TimeListener"]!!,listenerMap["traveling.location.weather.WeatherListener"]!!), "RemoveConditionEvent" to listOf(listenerMap["status.conditions.RemoveCondition"]!!), "RestEvent" to listOf(listenerMap["status.rest.Rest"]!!), "StatMinnedEvent" to listOf(listenerMap["status.statChanged.CreatureDied"]!!,listenerMap["status.statChanged.PlayerStatMinned"]!!,listenerMap["status.statChanged.StatMinned"]!!), "StatMaxedEvent" to listOf(listenerMap["status.statChanged.PlayerStatMaxed"]!!), "StatBoostEvent" to listOf(listenerMap["status.statChanged.StatBoosted"]!!), "StatChangeEvent" to listOf(listenerMap["status.statChanged.StatChanged"]!!), "StatusEvent" to listOf(listenerMap["status.status.Status"]!!), "CreateAliasEvent" to listOf(listenerMap["system.alias.CreateAlias"]!!), "DeleteAliasEvent" to listOf(listenerMap["system.alias.DeleteAlias"]!!), "ListAliasesEvent" to listOf(listenerMap["system.alias.ListAlias"]!!), "ConnectEvent" to listOf(listenerMap["system.connection.Connect"]!!), "ConnectInfoEvent" to listOf(listenerMap["system.connection.ConnectInfo"]!!), "DisconnectEvent" to listOf(listenerMap["system.connection.Disconnect"]!!), "DebugListEvent" to listOf(listenerMap["system.debug.DebugListListener"]!!), "DebugStatEvent" to listOf(listenerMap["system.debug.DebugStatListener"]!!), "DebugTagEvent" to listOf(listenerMap["system.debug.DebugTagListener"]!!), "DebugToggleEvent" to listOf(listenerMap["system.debug.DebugToggleListener"]!!), "DebugWeatherEvent" to listOf(listenerMap["system.debug.DebugWeatherListener"]!!), "ViewHelpEvent" to listOf(listenerMap["system.help.ViewHelp"]!!), "ViewGameLogEvent" to listOf(listenerMap["system.history.ViewGameLog"]!!), "DisplayMessageEvent" to listOf(listenerMap["system.message.DisplayMessage"]!!), "ListCharactersEvent" to listOf(listenerMap["system.persistance.changePlayer.ListCharacters"]!!), "PlayAsEvent" to listOf(listenerMap["system.persistance.changePlayer.PlayAs"]!!), "CreateCharacterEvent" to listOf(listenerMap["system.persistance.createPlayer.CreateCharacter"]!!), "ListSavesEvent" to listOf(listenerMap["system.persistance.loading.ListSaves"]!!), "LoadEvent" to listOf(listenerMap["system.persistance.loading.Load"]!!), "CreateNewGameEvent" to listOf(listenerMap["system.persistance.newGame.CreateNewGame"]!!), "SaveEvent" to listOf(listenerMap["system.persistance.saving.Save"]!!), "ViewTimeEvent" to listOf(listenerMap["time.ViewTime"]!!), "RestrictLocationEvent" to listOf(listenerMap["traveling.RestrictLocation"]!!), "ArriveEvent" to listOf(listenerMap["traveling.arrive.ArrivalHandler"]!!,listenerMap["traveling.arrive.Arrive"]!!), "AttemptClimbEvent" to listOf(listenerMap["traveling.climb.AttemptClimb"]!!), "ClimbCompleteEvent" to listOf(listenerMap["traveling.climb.ClimbComplete"]!!), "FallEvent" to listOf(listenerMap["traveling.jump.PlayerFall"]!!), "JumpEvent" to listOf(listenerMap["traveling.jump.PlayerJump"]!!), "MoveEvent" to listOf(listenerMap["traveling.move.Move"]!!), "FindRouteEvent" to listOf(listenerMap["traveling.routes.FindRoute"]!!), "ViewRouteEvent" to listOf(listenerMap["traveling.routes.ViewRoute"]!!), "RemoveItemEvent" to listOf(listenerMap["traveling.scope.remove.RemoveItem"]!!), "RemoveScopeEvent" to listOf(listenerMap["traveling.scope.remove.RemoveScope"]!!), "SpawnActivatorEvent" to listOf(listenerMap["traveling.scope.spawn.ActivatorSpawner"]!!), "ItemSpawnedEvent" to listOf(listenerMap["traveling.scope.spawn.SpawnItem"]!!), "TravelStartEvent" to listOf(listenerMap["traveling.travel.TravelStart"]!!), "UseEvent" to listOf(listenerMap["use.actions.ChopWood"]!!,listenerMap["use.actions.DamageCreature"]!!,listenerMap["use.actions.NoUseFound"]!!,listenerMap["use.actions.ScratchSurface"]!!,listenerMap["use.actions.StartFire"]!!,listenerMap["use.actions.UseFoodItem"]!!,listenerMap["use.actions.UseIngredientOnActivatorRecipe"]!!,listenerMap["use.actions.UseItemOnIngredientRecipe"]!!,listenerMap["use.actions.UseOnFire"]!!), "EatFoodEvent" to listOf(listenerMap["use.eat.EatFood"]!!), "InteractEvent" to listOf(listenerMap["use.interaction.Interact"]!!,listenerMap["use.interaction.NoInteractionFound"]!!), "NothingEvent" to listOf(listenerMap["use.interaction.nothing.DoNothing"]!!)) } \ No newline at end of file diff --git a/src/commonMain/kotlin/resources/ai/agenda/CommonAgendas.kt b/src/commonMain/kotlin/resources/ai/agenda/CommonAgendas.kt index 955e57a2d..33b418b72 100644 --- a/src/commonMain/kotlin/resources/ai/agenda/CommonAgendas.kt +++ b/src/commonMain/kotlin/resources/ai/agenda/CommonAgendas.kt @@ -37,7 +37,7 @@ class CommonAgendas : AgendaResource { } agendaAction("Eat Targeted Food") { creature -> - creature.mind.getUseTarget()?.let { target -> + creature.mind.getUseTargetThing()?.let { target -> EatFoodEvent(creature, target) } } @@ -90,12 +90,12 @@ class CommonAgendas : AgendaResource { agenda("Move to Use Target") { actionDetailed("Move to Use Target") { shouldSkip { creature -> - creature.mind.getUseTarget()?.position?.let { + creature.mind.getUseTargetThing()?.position?.let { creature.canReach(it) } } result { creature -> - creature.mind.getUseTarget()?.position?.let { + creature.mind.getUseTargetThing()?.position?.let { startMoveEvent(creature, destination = it) } } @@ -121,7 +121,7 @@ class CommonAgendas : AgendaResource { agenda("Move to Use Target") action("Scratch Tree") { creature -> - creature.mind.getUseTarget()?.let { target -> + creature.mind.getUseTargetThing()?.let { target -> clawAttack(target, creature) } } @@ -186,7 +186,7 @@ class CommonAgendas : AgendaResource { } agendaAction("Interact Target") { creature -> - creature.mind.getUseTarget()?.let { target -> + creature.mind.getUseTargetThing()?.let { target -> InteractEvent(creature, target) } } diff --git a/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt b/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt index 59ab80f54..58018d047 100644 --- a/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt +++ b/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt @@ -1,23 +1,10 @@ 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 core.ai.packages.* 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 @@ -39,17 +26,16 @@ class CommonPackages : AIPackageTemplateResource { //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) } + cond { it.hasUseTarget() && !it.canReach(it.mind.getUseTargetThing()!!.position) } + act { startMoveEvent(it, destination = it.mind.getUseTargetThing()!!.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) + cond { it.canReachGoal("eat") } + act { + EatFoodEvent(it, it.mind.getUseTargetThing()!!) + it.clearUseGoal() } - act { EatFoodEvent(it, it.mind.getUseTarget()!!) } } } @@ -60,37 +46,12 @@ class CommonPackages : AIPackageTemplateResource { 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") + owner.setUseGoal(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 From d6a43279a796d7b928eba638927d0caf79b51f7f Mon Sep 17 00:00:00 2001 From: ManApart Date: Thu, 7 Mar 2024 07:47:09 -0500 Subject: [PATCH 11/45] comment --- src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt b/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt index 58018d047..84019b846 100644 --- a/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt +++ b/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt @@ -25,6 +25,8 @@ class CommonPackages : AIPackageTemplateResource { } //TODO - need move to location as well + //If usetarget in different location, create map to get there + // map available, move to location idea("Move to Use Target", 50) { cond { it.hasUseTarget() && !it.canReach(it.mind.getUseTargetThing()!!.position) } act { startMoveEvent(it, destination = it.mind.getUseTargetThing()!!.position) } From aa1dd75d67283417126ca043d1f5b1368a5f32fc Mon Sep 17 00:00:00 2001 From: ManApart Date: Tue, 30 Dec 2025 08:43:07 -0500 Subject: [PATCH 12/45] bump kotlin --- .idea/.name | 1 - build.gradle.kts | 9 ++++----- 2 files changed, 4 insertions(+), 6 deletions(-) delete mode 100644 .idea/.name diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index 4d93e2a6c..000000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -quest-command \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 65be1f68a..69a418adf 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,8 +2,8 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { - kotlin("multiplatform") version "2.1.0" - kotlin("plugin.serialization") version "2.1.0" + kotlin("multiplatform") version "2.3.0" + kotlin("plugin.serialization") version "2.3.0" `maven-publish` } @@ -18,9 +18,8 @@ kotlin { freeCompilerArgs.add("-Xexpect-actual-classes") } jvm { - withJava() compilerOptions { - jvmTarget.set(JvmTarget.JVM_21) + jvmTarget.set(JvmTarget.JVM_25) } testRuns["test"].executionTask.configure { useJUnitPlatform() @@ -99,7 +98,7 @@ kotlin { } } runTask { - devServer = devServer.copy(port = 3000) + devServerProperty.set(devServerProperty.get().copy(port = 3000)) } } } From 7ae99ee13fb291cc46b9d8d6ee5590d0183c0fab Mon Sep 17 00:00:00 2001 From: ManApart Date: Tue, 30 Dec 2025 09:16:57 -0500 Subject: [PATCH 13/45] tests passing, general cleanup --- build.gradle.kts | 1 + src/commonMain/kotlin/crafting/RecipeResultBuilder.kt | 5 ++--- src/commonMain/kotlin/resources/thing/SharedThings.kt | 4 ++-- src/jvmMain/kotlin/building/ModLoader.kt | 5 +++-- src/jvmMain/kotlin/system/connection/WebClient.kt | 4 +++- .../kotlin/core/ai/packages/AIPackageTemplatesMock.kt | 2 +- src/jvmTest/kotlin/core/ai/packages/AIPackagesMock.kt | 3 --- 7 files changed, 12 insertions(+), 12 deletions(-) delete mode 100644 src/jvmTest/kotlin/core/ai/packages/AIPackagesMock.kt diff --git a/build.gradle.kts b/build.gradle.kts index 69a418adf..7b4915004 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -122,6 +122,7 @@ kotlin { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1") implementation("org.jetbrains.kotlin:kotlin-reflect:1.6.10") implementation("io.ktor:ktor-client-cio:2.0.1") + implementation("org.slf4j:slf4j-nop:2.0.17") } } val jvmTest by getting diff --git a/src/commonMain/kotlin/crafting/RecipeResultBuilder.kt b/src/commonMain/kotlin/crafting/RecipeResultBuilder.kt index 2dc407b5f..7b10f8a3b 100644 --- a/src/commonMain/kotlin/crafting/RecipeResultBuilder.kt +++ b/src/commonMain/kotlin/crafting/RecipeResultBuilder.kt @@ -46,13 +46,13 @@ class RecipeResultBuilder { this.getItem = getItem } - suspend fun build(): RecipeResult { + fun build(): RecipeResult { if (description.isBlank()) buildDescription() if (getItem != null) return RecipeResult(description, getItem!!) val baseItemGetter: suspend (Map>) -> Thing = when { - (ingredientReference != null) -> { usedIngredients -> (usedIngredients[ingredientReference!!.toString()])!!.second } + (ingredientReference != null) -> { usedIngredients -> (usedIngredients[ingredientReference!!])!!.second } (itemName != null) -> { _ -> ItemManager.getItem(itemName!!) } else -> throw IllegalStateException("Recipe must have an item name or item reference") } @@ -79,4 +79,3 @@ class RecipeResultBuilder { fun result(initializer: RecipeResultBuilder.() -> Unit): RecipeResultBuilder { return RecipeResultBuilder().apply(initializer) } - diff --git a/src/commonMain/kotlin/resources/thing/SharedThings.kt b/src/commonMain/kotlin/resources/thing/SharedThings.kt index 51726845a..a103a6d0f 100644 --- a/src/commonMain/kotlin/resources/thing/SharedThings.kt +++ b/src/commonMain/kotlin/resources/thing/SharedThings.kt @@ -7,7 +7,7 @@ import core.thing.ThingBuilder import core.thing.thing import core.utility.Backer -val burnToAsh = BehaviorRecipe("Burn to Ash", mapOf("name" to "\$itemName")) +val burnToAsh = BehaviorRecipe("Burn to Ash", mapOf("name" to $$"$itemName")) val burnable: Backer = Backer(::burnable) @@ -20,4 +20,4 @@ private suspend fun burnable(): ThingBuilder { } behavior(burnToAsh) } -} \ No newline at end of file +} diff --git a/src/jvmMain/kotlin/building/ModLoader.kt b/src/jvmMain/kotlin/building/ModLoader.kt index 0ffb42ae0..eabe6066b 100644 --- a/src/jvmMain/kotlin/building/ModLoader.kt +++ b/src/jvmMain/kotlin/building/ModLoader.kt @@ -21,6 +21,7 @@ import traveling.location.network.NetworkResource import traveling.location.weather.WeatherResource import java.io.File import java.lang.reflect.ParameterizedType +import java.net.URI import java.net.URL import java.net.URLClassLoader import java.util.jar.JarEntry @@ -63,7 +64,7 @@ private suspend fun loadJar(jarFile: File) { val jar = JarFile(jarFile) val e = jar.entries() - val urls = arrayOf(URL("jar:file:" + jarFile.absolutePath + "!/")) + val urls = arrayOf(URI.create("jar:file:" + jarFile.absolutePath + "!/").toURL()) val cl = URLClassLoader.newInstance(urls) while (e.hasMoreElements()) { @@ -116,4 +117,4 @@ private fun processNetwork(c: Class) = ModManager.networks.addA private fun processMaterial(c: Class) = ModManager.materials.addAll(c.getDeclaredConstructor().newInstance().values) private suspend fun processRecipe(c: Class) = ModManager.recipes.addAll(c.getDeclaredConstructor().newInstance().values()) private fun processQuest(c: Class) = ModManager.quests.addAll(c.getDeclaredConstructor().newInstance().values) -private fun processWeather(c: Class) = ModManager.weather.addAll(c.getDeclaredConstructor().newInstance().values) \ No newline at end of file +private fun processWeather(c: Class) = ModManager.weather.addAll(c.getDeclaredConstructor().newInstance().values) diff --git a/src/jvmMain/kotlin/system/connection/WebClient.kt b/src/jvmMain/kotlin/system/connection/WebClient.kt index a69e46a20..06bc741a4 100644 --- a/src/jvmMain/kotlin/system/connection/WebClient.kt +++ b/src/jvmMain/kotlin/system/connection/WebClient.kt @@ -9,6 +9,7 @@ import io.ktor.client.engine.cio.* import io.ktor.client.plugins.contentnegotiation.* import io.ktor.client.request.* import io.ktor.serialization.kotlinx.json.* +import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -89,6 +90,7 @@ object WebClient { } } + @OptIn(DelicateCoroutinesApi::class) fun pollForUpdates() { doPolling = true GlobalScope.launch { @@ -129,4 +131,4 @@ object WebClient { } } -} \ No newline at end of file +} diff --git a/src/jvmTest/kotlin/core/ai/packages/AIPackageTemplatesMock.kt b/src/jvmTest/kotlin/core/ai/packages/AIPackageTemplatesMock.kt index 56423f840..a64ceb006 100644 --- a/src/jvmTest/kotlin/core/ai/packages/AIPackageTemplatesMock.kt +++ b/src/jvmTest/kotlin/core/ai/packages/AIPackageTemplatesMock.kt @@ -1,3 +1,3 @@ package core.ai.packages -class AIPackageTemplatesMock(override val values: List = listOf()) : AIPackageTemplatesCollection \ No newline at end of file +class AIPackageTemplatesMock(override val values: List = listOf()) : AIPackageTemplatesCollection diff --git a/src/jvmTest/kotlin/core/ai/packages/AIPackagesMock.kt b/src/jvmTest/kotlin/core/ai/packages/AIPackagesMock.kt deleted file mode 100644 index 4e688fd76..000000000 --- a/src/jvmTest/kotlin/core/ai/packages/AIPackagesMock.kt +++ /dev/null @@ -1,3 +0,0 @@ -package core.ai.packages - -class AIPackagesMock(override val values: List = listOf()) : AIPackagesCollection \ No newline at end of file From 984d10f00717888302374c63f218c9ad36618618 Mon Sep 17 00:00:00 2001 From: ManApart Date: Tue, 30 Dec 2025 09:56:47 -0500 Subject: [PATCH 14/45] upgrade gradle --- .github/workflows/publishLatest.yml | 4 +- .github/workflows/publishRelease.yml | 4 +- .github/workflows/runTests.yml | 4 +- gradle/wrapper/gradle-wrapper.jar | Bin 59203 -> 45457 bytes gradle/wrapper/gradle-wrapper.properties | 4 +- gradlew | 283 ++++++++++++++--------- gradlew.bat | 40 ++-- 7 files changed, 204 insertions(+), 135 deletions(-) diff --git a/.github/workflows/publishLatest.yml b/.github/workflows/publishLatest.yml index f6f554720..d1c63086f 100644 --- a/.github/workflows/publishLatest.yml +++ b/.github/workflows/publishLatest.yml @@ -13,10 +13,10 @@ jobs: packages: write steps: - uses: actions/checkout@v4 - - name: Set up JDK 17 + - name: Set up JDK uses: actions/setup-java@v4 with: - java-version: '21' + java-version: '25' distribution: 'adopt' - name: Cache Gradle packages uses: actions/cache@v4 diff --git a/.github/workflows/publishRelease.yml b/.github/workflows/publishRelease.yml index 89c05ab17..ef6b37ff5 100644 --- a/.github/workflows/publishRelease.yml +++ b/.github/workflows/publishRelease.yml @@ -13,10 +13,10 @@ jobs: packages: write steps: - uses: actions/checkout@v4 - - name: Set up JDK 17 + - name: Set up JDK uses: actions/setup-java@v4 with: - java-version: '21' + java-version: '25' distribution: 'adopt' - name: Cache Gradle packages uses: actions/cache@v4 diff --git a/.github/workflows/runTests.yml b/.github/workflows/runTests.yml index 7be710274..06d9beaba 100644 --- a/.github/workflows/runTests.yml +++ b/.github/workflows/runTests.yml @@ -6,10 +6,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up JDK 17 + - name: Set up JDK uses: actions/setup-java@v4 with: - java-version: '21' + java-version: '25' distribution: 'adopt' - name: Cache Gradle packages uses: actions/cache@v4 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c023ec8b20f512888fe07c5bd3ff77bb8f..8bdaf60c75ab801e22807dde59e12a8735a34077 100644 GIT binary patch literal 45457 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 c82=K%QXLKFx%@O{wJjEi6Y56o#$)Bpeg literal 59203 zcma&O1CT9Y(k9%tZQHhO+qUh#ZQHhO+qmuS+qP|E@9xZO?0h@l{(r>DQ>P;GjjD{w zH}lENr;dU&FbEU?00aa80D$0M0RRB{U*7-#kbjS|qAG&4l5%47zyJ#WrfA#1$1Ctx zf&Z_d{GW=lf^w2#qRJ|CvSJUi(^E3iv~=^Z(zH}F)3Z%V3`@+rNB7gTVU{Bb~90p|f+0(v;nz01EG7yDMX9@S~__vVgv%rS$+?IH+oZ03D5zYrv|^ zC1J)SruYHmCki$jLBlTaE5&dFG9-kq3!^i>^UQL`%gn6)jz54$WDmeYdsBE9;PqZ_ zoGd=P4+|(-u4U1dbAVQrFWoNgNd;0nrghPFbQrJctO>nwDdI`Q^i0XJDUYm|T|RWc zZ3^Qgo_Qk$%Fvjj-G}1NB#ZJqIkh;kX%V{THPqOyiq)d)0+(r9o(qKlSp*hmK#iIY zA^)Vr$-Hz<#SF=0@tL@;dCQsm`V9s1vYNq}K1B)!XSK?=I1)tX+bUV52$YQu*0%fnWEukW>mxkz+%3-S!oguE8u#MGzST8_Dy^#U?fA@S#K$S@9msUiX!gd_ow>08w5)nX{-KxqMOo7d?k2&?Vf z&diGDtZr(0cwPe9z9FAUSD9KC)7(n^lMWuayCfxzy8EZsns%OEblHFSzP=cL6}?J| z0U$H!4S_TVjj<`6dy^2j`V`)mC;cB%* z8{>_%E1^FH!*{>4a7*C1v>~1*@TMcLK{7nEQ!_igZC}ikJ$*<$yHy>7)oy79A~#xE zWavoJOIOC$5b6*q*F_qN1>2#MY)AXVyr$6x4b=$x^*aqF*L?vmj>Mgv+|ITnw_BoW zO?jwHvNy^prH{9$rrik1#fhyU^MpFqF2fYEt(;4`Q&XWOGDH8k6M=%@fics4ajI;st# zCU^r1CK&|jzUhRMv;+W~6N;u<;#DI6cCw-otsc@IsN3MoSD^O`eNflIoR~l4*&-%RBYk@gb^|-JXs&~KuSEmMxB}xSb z@K76cXD=Y|=I&SNC2E+>Zg?R6E%DGCH5J1nU!A|@eX9oS(WPaMm==k2s_ueCqdZw| z&hqHp)47`c{BgwgvY2{xz%OIkY1xDwkw!<0veB#yF4ZKJyabhyyVS`gZepcFIk%e2 zTcrmt2@-8`7i-@5Nz>oQWFuMC_KlroCl(PLSodswHqJ3fn<;gxg9=}~3x_L3P`9Sn zChIf}8vCHvTriz~T2~FamRi?rh?>3bX1j}%bLH+uFX+p&+^aXbOK7clZxdU~6Uxgy z8R=obwO4dL%pmVo*Ktf=lH6hnlz_5k3cG;m8lgaPp~?eD!Yn2kf)tU6PF{kLyn|oI@eQ`F z3IF7~Blqg8-uwUuWZScRKn%c2_}dXB6Dx_&xR*n9M9LXasJhtZdr$vBY!rP{c@=)& z#!?L$2UrkvClwQO>U*fSMs67oSj2mxiJ$t;E|>q%Kh_GzzWWO&3;ufU%2z%ucBU8H z3WIwr$n)cfCXR&>tyB7BcSInK>=ByZA%;cVEJhcg<#6N{aZC4>K41XF>ZgjG`z_u& zGY?;Ad?-sgiOnI`oppF1o1Gurqbi*;#x2>+SSV6|1^G@ooVy@fg?wyf@0Y!UZ4!}nGuLeC^l)6pwkh|oRY`s1Pm$>zZ3u-83T|9 zGaKJIV3_x+u1>cRibsaJpJqhcm%?0-L;2 zitBrdRxNmb0OO2J%Y&Ym(6*`_P3&&5Bw157{o7LFguvxC$4&zTy#U=W*l&(Q2MNO} zfaUwYm{XtILD$3864IA_nn34oVa_g^FRuHL5wdUd)+W-p-iWCKe8m_cMHk+=? zeKX)M?Dt(|{r5t7IenkAXo%&EXIb-i^w+0CX0D=xApC=|Xy(`xy+QG^UyFe z+#J6h_&T5i#sV)hj3D4WN%z;2+jJcZxcI3*CHXGmOF3^)JD5j&wfX)e?-|V0GPuA+ zQFot%aEqGNJJHn$!_}#PaAvQ^{3-Ye7b}rWwrUmX53(|~i0v{}G_sI9uDch_brX&6 zWl5Ndj-AYg(W9CGfQf<6!YmY>Ey)+uYd_JNXH=>|`OH-CDCmcH(0%iD_aLlNHKH z7bcW-^5+QV$jK?R*)wZ>r9t}loM@XN&M-Pw=F#xn(;u3!(3SXXY^@=aoj70;_=QE9 zGghsG3ekq#N||u{4We_25U=y#T*S{4I{++Ku)> zQ!DZW;pVcn>b;&g2;YE#+V`v*Bl&Y-i@X6D*OpNA{G@JAXho&aOk(_j^weW{#3X5Y z%$q_wpb07EYPdmyH(1^09i$ca{O<}7) zRWncXdSPgBE%BM#by!E>tdnc$8RwUJg1*x($6$}ae$e9Knj8gvVZe#bLi!<+&BkFj zg@nOpDneyc+hU9P-;jmOSMN|*H#>^Ez#?;%C3hg_65leSUm;iz)UkW)jX#p)e&S&M z1|a?wDzV5NVnlhRBCd_;F87wp>6c<&nkgvC+!@KGiIqWY4l}=&1w7|r6{oBN8xyzh zG$b#2=RJp_iq6)#t5%yLkKx(0@D=C3w+oiXtSuaQ%I1WIb-eiE$d~!)b@|4XLy!CZ z9p=t=%3ad@Ep+<9003D2KZ5VyP~_n$=;~r&YUg5UZ0KVD&tR1DHy9x)qWtKJp#Kq# zP*8p#W(8JJ_*h_3W}FlvRam?<4Z+-H77^$Lvi+#vmhL9J zJ<1SV45xi;SrO2f=-OB(7#iNA5)x1uNC-yNxUw|!00vcW2PufRm>e~toH;M0Q85MQLWd?3O{i8H+5VkR@l9Dg-ma ze2fZ%>G(u5(k9EHj2L6!;(KZ8%8|*-1V|B#EagbF(rc+5iL_5;Eu)L4Z-V;0HfK4d z*{utLse_rvHZeQ>V5H=f78M3Ntg1BPxFCVD{HbNA6?9*^YIq;B-DJd{Ca2L#)qWP? zvX^NhFmX?CTWw&Ns}lgs;r3i+Bq@y}Ul+U%pzOS0Fcv9~aB(0!>GT0)NO?p=25LjN z2bh>6RhgqD7bQj#k-KOm@JLgMa6>%-ok1WpOe)FS^XOU{c?d5shG(lIn3GiVBxmg`u%-j=)^v&pX1JecJics3&jvPI)mDut52? z3jEA)DM%}BYbxxKrizVYwq?(P&19EXlwD9^-6J+4!}9{ywR9Gk42jjAURAF&EO|~N z)?s>$Da@ikI4|^z0e{r`J8zIs>SpM~Vn^{3fArRu;?+43>lD+^XtUcY1HidJwnR6+ z!;oG2=B6Z_=M%*{z-RaHc(n|1RTKQdNjjV!Pn9lFt^4w|AeN06*j}ZyhqZ^!-=cyGP_ShV1rGxkx8t zB;8`h!S{LD%ot``700d0@Grql(DTt4Awgmi+Yr0@#jbe=2#UkK%rv=OLqF)9D7D1j z!~McAwMYkeaL$~kI~90)5vBhBzWYc3Cj1WI0RS`z000R8-@ET0dA~*r(gSiCJmQMN&4%1D zyVNf0?}sBH8zNbBLn>~(W{d3%@kL_eQ6jEcR{l>C|JK z(R-fA!z|TTRG40|zv}7E@PqCAXP3n`;%|SCQ|ZS%ym$I{`}t3KPL&^l5`3>yah4*6 zifO#{VNz3)?ZL$be;NEaAk9b#{tV?V7 zP|wf5YA*1;s<)9A4~l3BHzG&HH`1xNr#%){4xZ!jq%o=7nN*wMuXlFV{HaiQLJ`5G zBhDi#D(m`Q1pLh@Tq+L;OwuC52RdW7b8}~60WCOK5iYMUad9}7aWBuILb({5=z~YF zt?*Jr5NG+WadM{mDL>GyiByCuR)hd zA=HM?J6l1Xv0Dl+LW@w$OTcEoOda^nFCw*Sy^I@$sSuneMl{4ys)|RY#9&NxW4S)9 zq|%83IpslTLoz~&vTo!Ga@?rj_kw{|k{nv+w&Ku?fyk4Ki4I?);M|5Axm)t+BaE)D zm(`AQ#k^DWrjbuXoJf2{Aj^KT zFb1zMSqxq|vceV+Mf-)$oPflsO$@*A0n0Z!R{&(xh8s}=;t(lIy zv$S8x>m;vQNHuRzoaOo?eiWFe{0;$s`Bc+Osz~}Van${u;g(su`3lJ^TEfo~nERfP z)?aFzpDgnLYiERsKPu|0tq4l2wT)Atr6Qb%m-AUn6HnCue*yWICp7TjW$@sO zm5rm4aTcPQ(rfi7a`xP7cKCFrJD}*&_~xgLyr^-bmsL}y;A5P|al8J3WUoBSjqu%v zxC;mK!g(7r6RRJ852Z~feoC&sD3(6}^5-uLK8o)9{8L_%%rItZK9C){UxB|;G>JbP zsRRtS4-3B*5c+K2kvmgZK8472%l>3cntWUOVHxB|{Ay~aOg5RN;{PJgeVD*H%ac+y!h#wi%o2bF2Ca8IyMyH{>4#{E_8u^@+l-+n=V}Sq?$O z{091@v%Bd*3pk0^2UtiF9Z+(a@wy6 zUdw8J*ze$K#=$48IBi1U%;hmhO>lu!uU;+RS}p&6@rQila7WftH->*A4=5W|Fmtze z)7E}jh@cbmr9iup^i%*(uF%LG&!+Fyl@LFA-}Ca#bxRfDJAiR2dt6644TaYw1Ma79 zt8&DYj31j^5WPNf5P&{)J?WlCe@<3u^78wnd(Ja4^a>{^Tw}W>|Cjt^If|7l^l)^Q zbz|7~CF(k_9~n|h;ysZ+jHzkXf(*O*@5m zLzUmbHp=x!Q|!9NVXyipZ3)^GuIG$k;D)EK!a5=8MFLI_lpf`HPKl=-Ww%z8H_0$j ztJ||IfFG1lE9nmQ0+jPQy zCBdKkjArH@K7jVcMNz);Q(Q^R{d5G?-kk;Uu_IXSyWB)~KGIizZL(^&qF;|1PI7!E zTP`%l)gpX|OFn&)M%txpQ2F!hdA~hX1Cm5)IrdljqzRg!f{mN%G~H1&oqe`5eJCIF zHdD7O;AX-{XEV(a`gBFJ9ews#CVS2y!&>Cm_dm3C8*n3MA*e67(WC?uP@8TXuMroq z{#w$%z@CBIkRM7?}Xib+>hRjy?%G!fiw8! z8(gB+8J~KOU}yO7UGm&1g_MDJ$IXS!`+*b*QW2x)9>K~Y*E&bYMnjl6h!{17_8d!%&9D`a7r&LKZjC<&XOvTRaKJ1 zUY@hl5^R&kZl3lU3njk`3dPzxj$2foOL26r(9zsVF3n_F#v)s5vv3@dgs|lP#eylq62{<-vczqP!RpVBTgI>@O6&sU>W|do17+#OzQ7o5A$ICH z?GqwqnK^n2%LR;$^oZM;)+>$X3s2n}2jZ7CdWIW0lnGK-b#EG01)P@aU`pg}th&J-TrU`tIpb5t((0eu|!u zQz+3ZiOQ^?RxxK4;zs=l8q!-n7X{@jSwK(iqNFiRColuEOg}!7cyZi`iBX4g1pNBj zAPzL?P^Ljhn;1$r8?bc=#n|Ed7wB&oHcw()&*k#SS#h}jO?ZB246EGItsz*;^&tzp zu^YJ0=lwsi`eP_pU8}6JA7MS;9pfD;DsSsLo~ogzMNP70@@;Fm8f0^;>$Z>~}GWRw!W5J3tNX*^2+1f3hz{~rIzJo z6W%J(H!g-eI_J1>0juX$X4Cl6i+3wbc~k146UIX&G22}WE>0ga#WLsn9tY(&29zBvH1$`iWtTe zG2jYl@P!P)eb<5DsR72BdI7-zP&cZNI{7q3e@?N8IKc4DE#UVr->|-ryuJXk^u^>4 z$3wE~=q390;XuOQP~TNoDR?#|NSPJ%sTMInA6*rJ%go|=YjGe!B>z6u$IhgQSwoV* zjy3F2#I>uK{42{&IqP59)Y(1*Z>>#W8rCf4_eVsH)`v!P#^;BgzKDR`ARGEZzkNX+ zJUQu=*-ol=Xqqt5=`=pA@BIn@6a9G8C{c&`i^(i+BxQO9?YZ3iu%$$da&Kb?2kCCo zo7t$UpSFWqmydXf@l3bVJ=%K?SSw)|?srhJ-1ZdFu*5QhL$~-IQS!K1s@XzAtv6*Y zl8@(5BlWYLt1yAWy?rMD&bwze8bC3-GfNH=p zynNFCdxyX?K&G(ZZ)afguQ2|r;XoV^=^(;Cku#qYn4Lus`UeKt6rAlFo_rU`|Rq z&G?~iWMBio<78of-2X(ZYHx~=U0Vz4btyXkctMKdc9UM!vYr~B-(>)(Hc|D zMzkN4!PBg%tZoh+=Gba!0++d193gbMk2&krfDgcbx0jI92cq?FFESVg0D$>F+bil} zY~$)|>1HZsX=5sAZ2WgPB5P=8X#TI+NQ(M~GqyVB53c6IdX=k>Wu@A0Svf5#?uHaF zsYn|koIi3$(%GZ2+G+7Fv^lHTb#5b8sAHSTnL^qWZLM<(1|9|QFw9pnRU{svj}_Al zL)b9>fN{QiA($8peNEJyy`(a{&uh-T4_kdZFIVsKKVM(?05}76EEz?#W za^fiZOAd14IJ4zLX-n7Lq0qlQ^lW8Cvz4UKkV9~P}>sq0?xD3vg+$4vLm~C(+ zM{-3Z#qnZ09bJ>}j?6ry^h+@PfaD7*jZxBEY4)UG&daWb??6)TP+|3#Z&?GL?1i+280CFsE|vIXQbm| zM}Pk!U`U5NsNbyKzkrul-DzwB{X?n3E6?TUHr{M&+R*2%yOiXdW-_2Yd6?38M9Vy^ z*lE%gA{wwoSR~vN0=no}tP2Ul5Gk5M(Xq`$nw#ndFk`tcpd5A=Idue`XZ!FS>Q zG^0w#>P4pPG+*NC9gLP4x2m=cKP}YuS!l^?sHSFftZy{4CoQrb_ z^20(NnG`wAhMI=eq)SsIE~&Gp9Ne0nD4%Xiu|0Fj1UFk?6avDqjdXz{O1nKao*46y zT8~iA%Exu=G#{x=KD;_C&M+Zx4+n`sHT>^>=-1YM;H<72k>$py1?F3#T1*ef9mLZw z5naLQr?n7K;2l+{_uIw*_1nsTn~I|kkCgrn;|G~##hM;9l7Jy$yJfmk+&}W@JeKcF zx@@Woiz8qdi|D%aH3XTx5*wDlbs?dC1_nrFpm^QbG@wM=i2?Zg;$VK!c^Dp8<}BTI zyRhAq@#%2pGV49*Y5_mV4+OICP|%I(dQ7x=6Ob}>EjnB_-_18*xrY?b%-yEDT(wrO z9RY2QT0`_OpGfMObKHV;QLVnrK%mc?$WAdIT`kJQT^n%GuzE7|9@k3ci5fYOh(287 zuIbg!GB3xLg$YN=n)^pHGB0jH+_iIiC=nUcD;G6LuJsjn2VI1cyZx=a?ShCsF==QK z;q~*m&}L<-cb+mDDXzvvrRsybcgQ;Vg21P(uLv5I+eGc7o7tc6`;OA9{soHFOz zT~2?>Ts}gprIX$wRBb4yE>ot<8+*Bv`qbSDv*VtRi|cyWS>)Fjs>fkNOH-+PX&4(~ z&)T8Zam2L6puQl?;5zg9h<}k4#|yH9czHw;1jw-pwBM*O2hUR6yvHATrI%^mvs9q_ z&ccT0>f#eDG<^WG^q@oVqlJrhxH)dcq2cty@l3~|5#UDdExyXUmLQ}f4#;6fI{f^t zDCsgIJ~0`af%YR%Ma5VQq-p21k`vaBu6WE?66+5=XUd%Ay%D$irN>5LhluRWt7 zov-=f>QbMk*G##&DTQyou$s7UqjjW@k6=!I@!k+S{pP8R(2=e@io;N8E`EOB;OGoI zw6Q+{X1_I{OO0HPpBz!X!@`5YQ2)t{+!?M_iH25X(d~-Zx~cXnS9z>u?+If|iNJbx zyFU2d1!ITX64D|lE0Z{dLRqL1Ajj=CCMfC4lD3&mYR_R_VZ>_7_~|<^o*%_&jevU+ zQ4|qzci=0}Jydw|LXLCrOl1_P6Xf@c0$ieK2^7@A9UbF{@V_0p%lqW|L?5k>bVM8|p5v&2g;~r>B8uo<4N+`B zH{J)h;SYiIVx@#jI&p-v3dwL5QNV1oxPr8J%ooezTnLW>i*3Isb49%5i!&ac_dEXv zvXmVUck^QHmyrF8>CGXijC_R-y(Qr{3Zt~EmW)-nC!tiH`wlw5D*W7Pip;T?&j%kX z6DkZX4&}iw>hE(boLyjOoupf6JpvBG8}jIh!!VhnD0>}KSMMo{1#uU6kiFcA04~|7 zVO8eI&x1`g4CZ<2cYUI(n#wz2MtVFHx47yE5eL~8bot~>EHbevSt}LLMQX?odD{Ux zJMnam{d)W4da{l7&y-JrgiU~qY3$~}_F#G7|MxT)e;G{U`In&?`j<5D->}cb{}{T(4DF0BOk-=1195KB-E*o@c?`>y#4=dMtYtSY=&L{!TAjFVcq0y@AH`vH! z$41+u!Ld&}F^COPgL(EE{0X7LY&%D7-(?!kjFF7=qw<;`V{nwWBq<)1QiGJgUc^Vz ztMUlq1bZqKn17|6x6iAHbWc~l1HcmAxr%$Puv!znW)!JiukwIrqQ00|H$Z)OmGG@= zv%A8*4cq}(?qn4rN6o`$Y))(MyXr8R<2S^J+v(wmFmtac!%VOfN?&(8Nr!T@kV`N; z*Q33V3t`^rN&aBiHet)18wy{*wi1=W!B%B-Q6}SCrUl$~Hl{@!95ydml@FK8P=u4s z4e*7gV2s=YxEvskw2Ju!2%{8h01rx-3`NCPc(O zH&J0VH5etNB2KY6k4R@2Wvl^Ck$MoR3=)|SEclT2ccJ!RI9Nuter7u9@;sWf-%um;GfI!=eEIQ2l2p_YWUd{|6EG ze{yO6;lMc>;2tPrsNdi@&1K6(1;|$xe8vLgiouj%QD%gYk`4p{Ktv9|j+!OF-P?@p z;}SV|oIK)iwlBs+`ROXkhd&NK zzo__r!B>tOXpBJMDcv!Mq54P+n4(@dijL^EpO1wdg~q+!DT3lB<>9AANSe!T1XgC=J^)IP0XEZ()_vpu!!3HQyJhwh?r`Ae%Yr~b% zO*NY9t9#qWa@GCPYOF9aron7thfWT`eujS4`t2uG6)~JRTI;f(ZuoRQwjZjp5Pg34 z)rp$)Kr?R+KdJ;IO;pM{$6|2y=k_siqvp%)2||cHTe|b5Ht8&A{wazGNca zX$Ol?H)E_R@SDi~4{d-|8nGFhZPW;Cts1;08TwUvLLv&_2$O6Vt=M)X;g%HUr$&06 zISZb(6)Q3%?;3r~*3~USIg=HcJhFtHhIV(siOwV&QkQe#J%H9&E21!C*d@ln3E@J* zVqRO^<)V^ky-R|%{(9`l-(JXq9J)1r$`uQ8a}$vr9E^nNiI*thK8=&UZ0dsFN_eSl z(q~lnD?EymWLsNa3|1{CRPW60>DSkY9YQ;$4o3W7Ms&@&lv9eH!tk~N&dhqX&>K@} zi1g~GqglxkZ5pEFkllJ)Ta1I^c&Bt6#r(QLQ02yHTaJB~- zCcE=5tmi`UA>@P=1LBfBiqk)HB4t8D?02;9eXj~kVPwv?m{5&!&TFYhu>3=_ zsGmYZ^mo*-j69-42y&Jj0cBLLEulNRZ9vXE)8~mt9C#;tZs;=#M=1*hebkS;7(aGf zcs7zH(I8Eui9UU4L--))yy`&d&$In&VA2?DAEss4LAPCLd>-$i?lpXvn!gu^JJ$(DoUlc6wE98VLZ*z`QGQov5l4Fm_h?V-;mHLYDVOwKz7>e4+%AzeO>P6v}ndPW| zM>m#6Tnp7K?0mbK=>gV}=@k*0Mr_PVAgGMu$j+pWxzq4MAa&jpCDU&-5eH27Iz>m^ zax1?*HhG%pJ((tkR(V(O(L%7v7L%!_X->IjS3H5kuXQT2!ow(;%FDE>16&3r){!ex zhf==oJ!}YU89C9@mfDq!P3S4yx$aGB?rbtVH?sHpg?J5C->!_FHM%Hl3#D4eplxzQ zRA+<@LD%LKSkTk2NyWCg7u=$%F#;SIL44~S_OGR}JqX}X+=bc@swpiClB`Zbz|f!4 z7Ysah7OkR8liXfI`}IIwtEoL}(URrGe;IM8%{>b1SsqXh)~w}P>yiFRaE>}rEnNkT z!HXZUtxUp1NmFm)Dm@-{FI^aRQqpSkz}ZSyKR%Y}YHNzBk)ZIp} zMtS=aMvkgWKm9&oTcU0?S|L~CDqA+sHpOxwnswF-fEG)cXCzUR?ps@tZa$=O)=L+5 zf%m58cq8g_o}3?Bhh+c!w4(7AjxwQ3>WnVi<{{38g7yFboo>q|+7qs<$8CPXUFAN< zG&}BHbbyQ5n|qqSr?U~GY{@GJ{(Jny{bMaOG{|IkUj7tj^9pa9|FB_<+KHLxSxR;@ zHpS$4V)PP+tx}22fWx(Ku9y+}Ap;VZqD0AZW4gCDTPCG=zgJmF{|x;(rvdM|2|9a}cex6xrMkERnkE;}jvU-kmzd%_J50$M`lIPCKf+^*zL=@LW`1SaEc%=m zQ+lT06Gw+wVwvQ9fZ~#qd430v2HndFsBa9WjD0P}K(rZYdAt^5WQIvb%D^Q|pkVE^ zte$&#~zmULFACGfS#g=2OLOnIf2Of-k!(BIHjs77nr!5Q1*I9 z1%?=~#Oss!rV~?-6Gm~BWJiA4mJ5TY&iPm_$)H1_rTltuU1F3I(qTQ^U$S>%$l z)Wx1}R?ij0idp@8w-p!Oz{&*W;v*IA;JFHA9%nUvVDy7Q8woheC#|8QuDZb-L_5@R zOqHwrh|mVL9b=+$nJxM`3eE{O$sCt$UK^2@L$R(r^-_+z?lOo+me-VW=Zw z-Bn>$4ovfWd%SPY`ab-u9{INc*k2h+yH%toDHIyqQ zO68=u`N}RIIs7lsn1D){)~%>ByF<>i@qFb<-axvu(Z+6t7v<^z&gm9McRB~BIaDn$ z#xSGT!rzgad8o>~kyj#h1?7g96tOcCJniQ+*#=b7wPio>|6a1Z?_(TS{)KrPe}(8j z!#&A=k(&Pj^F;r)CI=Z{LVu>uj!_W1q4b`N1}E(i%;BWjbEcnD=mv$FL$l?zS6bW!{$7j1GR5ocn94P2u{ z70tAAcpqtQo<@cXw~@i-@6B23;317|l~S>CB?hR5qJ%J3EFgyBdJd^fHZu7AzHF(BQ!tyAz^L0`X z23S4Fe{2X$W0$zu9gm%rg~A>ijaE#GlYlrF9$ds^QtaszE#4M(OLVP2O-;XdT(XIC zatwzF*)1c+t~c{L=fMG8Z=k5lv>U0;C{caN1NItnuSMp)6G3mbahu>E#sj&oy94KC zpH}8oEw{G@N3pvHhp{^-YaZeH;K+T_1AUv;IKD<=mv^&Ueegrb!yf`4VlRl$M?wsl zZyFol(2|_QM`e_2lYSABpKR{{NlxlDSYQNkS;J66aT#MSiTx~;tUmvs-b*CrR4w=f z8+0;*th6kfZ3|5!Icx3RV11sp=?`0Jy3Fs0N4GZQMN=8HmT6%x9@{Dza)k}UwL6JT zHRDh;%!XwXr6yuuy`4;Xsn0zlR$k%r%9abS1;_v?`HX_hI|+EibVnlyE@3aL5vhQq zlIG?tN^w@0(v9M*&L+{_+RQZw=o|&BRPGB>e5=ys7H`nc8nx)|-g;s7mRc7hg{GJC zAe^vCIJhajmm7C6g! zL&!WAQ~5d_5)00?w_*|*H>3$loHrvFbitw#WvLB!JASO?#5Ig5$Ys10n>e4|3d;tS zELJ0|R4n3Az(Fl3-r^QiV_C;)lQ1_CW{5bKS15U|E9?ZgLec@%kXr84>5jV2a5v=w z?pB1GPdxD$IQL4)G||B_lI+A=08MUFFR4MxfGOu07vfIm+j=z9tp~5i_6jb`tR>qV z$#`=BQ*jpCjm$F0+F)L%xRlnS%#&gro6PiRfu^l!EVan|r3y}AHJQOORGx4~ z&<)3=K-tx518DZyp%|!EqpU!+X3Et7n2AaC5(AtrkW>_57i}$eqs$rupubg0a1+WO zGHZKLN2L0D;ab%{_S1Plm|hx8R?O14*w*f&2&bB050n!R2by zw!@XOQx$SqZ5I<(Qu$V6g>o#A!JVwErWv#(Pjx=KeS0@hxr4?13zj#oWwPS(7Ro|v z>Mp@Kmxo79q|}!5qtX2-O@U&&@6s~!I&)1WQIl?lTnh6UdKT_1R640S4~f=_xoN3- zI+O)$R@RjV$F=>Ti7BlnG1-cFKCC(t|Qjm{SalS~V-tX#+2ekRhwmN zZr`8{QF6y~Z!D|{=1*2D-JUa<(1Z=;!Ei!KiRNH?o{p5o3crFF=_pX9O-YyJchr$~ zRC`+G+8kx~fD2k*ZIiiIGR<8r&M@3H?%JVOfE>)})7ScOd&?OjgAGT@WVNSCZ8N(p zuQG~76GE3%(%h1*vUXg$vH{ua0b`sQ4f0*y=u~lgyb^!#CcPJa2mkSEHGLsnO^kb$ zru5_l#nu=Y{rSMWiYx?nO{8I!gH+?wEj~UM?IrG}E|bRIBUM>UlY<`T1EHpRr36vv zBi&dG8oxS|J$!zoaq{+JpJy+O^W(nt*|#g32bd&K^w-t>!Vu9N!k9eA8r!Xc{utY> zg9aZ(D2E0gL#W0MdjwES-7~Wa8iubPrd?8-$C4BP?*wok&O8+ykOx{P=Izx+G~hM8 z*9?BYz!T8~dzcZr#ux8kS7u7r@A#DogBH8km8Ry4slyie^n|GrTbO|cLhpqgMdsjX zJ_LdmM#I&4LqqsOUIXK8gW;V0B(7^$y#h3h>J0k^WJfAMeYek%Y-Dcb_+0zPJez!GM zAmJ1u;*rK=FNM0Nf}Y!!P9c4)HIkMnq^b;JFd!S3?_Qi2G#LIQ)TF|iHl~WKK6JmK zbv7rPE6VkYr_%_BT}CK8h=?%pk@3cz(UrZ{@h40%XgThP*-Oeo`T0eq9 zA8BnWZKzCy5e&&_GEsU4*;_k}(8l_&al5K-V*BFM=O~;MgRkYsOs%9eOY6s6AtE*<7GQAR2ulC3RAJrG_P1iQK5Z~&B z&f8X<>yJV6)oDGIlS$Y*D^Rj(cszTy5c81a5IwBr`BtnC6_e`ArI8CaTX_%rx7;cn zR-0?J_LFg*?(#n~G8cXut(1nVF0Oka$A$1FGcERU<^ggx;p@CZc?3UB41RY+wLS`LWFNSs~YP zuw1@DNN3lTd|jDL7gjBsd9}wIw}4xT2+8dBQzI00m<@?c2L%>}QLfK5%r!a-iII`p zX@`VEUH)uj^$;7jVUYdADQ2k*!1O3WdfgF?OMtUXNpQ1}QINamBTKDuv19^{$`8A1 zeq%q*O0mi@(%sZU>Xdb0Ru96CFqk9-L3pzLVsMQ`Xpa~N6CR{9Rm2)A|CI21L(%GW zh&)Y$BNHa=FD+=mBw3{qTgw)j0b!Eahs!rZnpu)z!!E$*eXE~##yaXz`KE5(nQM`s zD!$vW9XH)iMxu9R>r$VlLk9oIR%HxpUiW=BK@4U)|1WNQ=mz9a z^!KkO=>GaJ!GBXm{KJj^;kh-MkUlEQ%lza`-G&}C5y1>La1sR6hT=d*NeCnuK%_LV zOXt$}iP6(YJKc9j-Fxq~*ItVUqljQ8?oaysB-EYtFQp9oxZ|5m0^Hq(qV!S+hq#g( z?|i*H2MIr^Kxgz+3vIljQ*Feejy6S4v~jKEPTF~Qhq!(ms5>NGtRgO5vfPPc4Z^AM zTj!`5xEreIN)vaNxa|q6qWdg>+T`Ol0Uz)ckXBXEGvPNEL3R8hB3=C5`@=SYgAju1 z!)UBr{2~=~xa{b8>x2@C7weRAEuatC)3pkRhT#pMPTpSbA|tan%U7NGMvzmF?c!V8 z=pEWxbdXbTAGtWTyI?Fml%lEr-^AE}w#l(<7OIw;ctw}imYax&vR4UYNJZK6P7ZOd zP87XfhnUHxCUHhM@b*NbTi#(-8|wcv%3BGNs#zRCVV(W?1Qj6^PPQa<{yaBwZ`+<`w|;rqUY_C z&AeyKwwf*q#OW-F()lir=T^<^wjK65Lif$puuU5+tk$;e_EJ;Lu+pH>=-8=PDhkBg z8cWt%@$Sc#C6F$Vd+0507;{OOyT7Hs%nKS88q-W!$f~9*WGBpHGgNp}=C*7!RiZ5s zn1L_DbKF@B8kwhDiLKRB@lsXVVLK|ph=w%_`#owlf@s@V(pa`GY$8h%;-#h@TsO|Y8V=n@*!Rog7<7Cid%apR|x zOjhHCyfbIt%+*PCveTEcuiDi%Wx;O;+K=W?OFUV%)%~6;gl?<0%)?snDDqIvkHF{ zyI02)+lI9ov42^hL>ZRrh*HhjF9B$A@=H94iaBESBF=eC_KT$8A@uB^6$~o?3Wm5t1OIaqF^~><2?4e3c&)@wKn9bD? zoeCs;H>b8DL^F&>Xw-xjZEUFFTv>JD^O#1E#)CMBaG4DX9bD(Wtc8Rzq}9soQ8`jf zeSnHOL}<+WVSKp4kkq&?SbETjq6yr@4%SAqOG=9E(3YeLG9dtV+8vmzq+6PFPk{L; z(&d++iu=^F%b+ea$i2UeTC{R*0Isk;vFK!no<;L+(`y`3&H-~VTdKROkdyowo1iqR zbVW(3`+(PQ2>TKY>N!jGmGo7oeoB8O|P_!Ic@ zZ^;3dnuXo;WJ?S+)%P>{Hcg!Jz#2SI(s&dY4QAy_vRlmOh)QHvs_7c&zkJCmJGVvV zX;Mtb>QE+xp`KyciG$Cn*0?AK%-a|=o!+7x&&yzHQOS>8=B*R=niSnta^Pxp1`=md z#;$pS$4WCT?mbiCYU?FcHGZ#)kHVJTTBt^%XE(Q};aaO=Zik0UgLcc0I(tUpt(>|& zcxB_|fxCF7>&~5eJ=Dpn&5Aj{A^cV^^}(7w#p;HG&Q)EaN~~EqrE1qKrMAc&WXIE;>@<&)5;gD2?={Xf@Mvn@OJKw=8Mgn z!JUFMwD+s==JpjhroT&d{$kQAy%+d`a*XxDEVxy3`NHzmITrE`o!;5ClXNPb4t*8P zzAivdr{j_v!=9!^?T3y?gzmqDWX6mkzhIzJ-3S{T5bcCFMr&RPDryMcdwbBuZbsgN zGrp@^i?rcfN7v0NKGzDPGE#4yszxu=I_`MI%Z|10nFjU-UjQXXA?k8Pk|OE<(?ae) zE%vG#eZAlj*E7_3dx#Zz4kMLj>H^;}33UAankJiDy5ZvEhrjr`!9eMD8COp}U*hP+ zF}KIYx@pkccIgyxFm#LNw~G&`;o&5)2`5aogs`1~7cMZQ7zj!%L4E`2yzlQN6REX20&O<9 zKV6fyr)TScJPPzNTC2gL+0x#=u>(({{D7j)c-%tvqls3#Y?Z1m zV5WUE)zdJ{$p>yX;^P!UcXP?UD~YM;IRa#Rs5~l+*$&nO(;Ers`G=0D!twR(0GF@c zHl9E5DQI}Oz74n zfKP>&$q0($T4y$6w(p=ERAFh+>n%iaeRA%!T%<^+pg?M)@ucY<&59$x9M#n+V&>}=nO9wCV{O~lg&v#+jcUj(tQ z`0u1YH)-`U$15a{pBkGyPL0THv1P|4e@pf@3IBZS4dVJPo#H>pWq%Lr0YS-SeWash z8R7=jb28KPMI|_lo#GEO|5B?N_e``H*23{~a!AmUJ+fb4HX-%QI@lSEUxKlGV7z7Q zSKw@-TR>@1RL%w{x}dW#k1NgW+q4yt2Xf1J62Bx*O^WG8OJ|FqI4&@d3_o8Id@*)4 zYrk=>@!wv~mh7YWv*bZhxqSmFh2Xq)o=m;%n$I?GSz49l1$xRpPu_^N(vZ>*>Z<04 z2+rP70oM=NDysd!@fQdM2OcyT?3T^Eb@lIC-UG=Bw{BjQ&P`KCv$AcJ;?`vdZ4){d z&gkoUK{$!$$K`3*O-jyM1~p-7T*qb)Ys>Myt^;#1&a%O@x8A+E>! zY8=eD`ZG)LVagDLBeHg>=atOG?Kr%h4B%E6m@J^C+U|y)XX@f z8oyJDW|9g=<#f<{JRr{y#~euMnv)`7j=%cHWLc}ngjq~7k**6%4u>Px&W%4D94(r* z+akunK}O0DC2A%Xo9jyF;DobX?!1I(7%}@7F>i%&nk*LMO)bMGg2N+1iqtg+r(70q zF5{Msgsm5GS7DT`kBsjMvOrkx&|EU!{{~gL4d2MWrAT=KBQ-^zQCUq{5PD1orxlIL zq;CvlWx#f1NWvh`hg011I%?T_s!e38l*lWVt|~z-PO4~~1g)SrJ|>*tXh=QfXT)%( z+ex+inPvD&O4Ur;JGz>$sUOnWdpSLcm1X%aQDw4{dB!cnj`^muI$CJ2%p&-kULVCE z>$eMR36kN$wCPR+OFDM3-U(VOrp9k3)lI&YVFqd;Kpz~K)@Fa&FRw}L(SoD z9B4a+hQzZT-BnVltst&=kq6Y(f^S4hIGNKYBgMxGJ^;2yrO}P3;r)(-I-CZ)26Y6? z&rzHI_1GCvGkgy-t1E;r^3Le30|%$ebDRu2+gdLG)r=A~Qz`}~&L@aGJ{}vVs_GE* zVUjFnzHiXfKQbpv&bR&}l2bzIjAooB)=-XNcYmrGmBh(&iu@o!^hn0^#}m2yZZUK8 zufVm7Gq0y`Mj;9b>`c?&PZkU0j4>IL=UL&-Lp3j&47B5pAW4JceG{!XCA)kT<%2nqCxj<)uy6XR_uws~>_MEKPOpAQ!H zkn>FKh)<9DwwS*|Y(q?$^N!6(51O0 z^JM~Ax{AI1Oj$fs-S5d4T7Z_i1?{%0SsIuQ&r8#(JA=2iLcTN+?>wOL532%&dMYkT z*T5xepC+V6zxhS@vNbMoi|i)=rpli@R9~P!39tWbSSb904ekv7D#quKbgFEMTb48P zuq(VJ+&L8aWU(_FCD$3^uD!YM%O^K(dvy~Wm2hUuh6bD|#(I39Xt>N1Y{ZqXL`Fg6 zKQ?T2htHN!(Bx;tV2bfTtIj7e)liN-29s1kew>v(D^@)#v;}C4-G=7x#;-dM4yRWm zyY`cS21ulzMK{PoaQ6xChEZ}o_#}X-o}<&0)$1#3we?+QeLt;aVCjeA)hn!}UaKt< zat1fHEx13y-rXNMvpUUmCVzocPmN~-Y4(YJvQ#db)4|%B!rBsgAe+*yor~}FrNH08 z3V!97S}D7d$zbSD{$z;@IYMxM6aHdypIuS*pr_U6;#Y!_?0i|&yU*@16l z*dcMqDQgfNBf}?quiu4e>H)yTVfsp#f+Du0@=Kc41QockXkCkvu>FBd6Q+@FL!(Yx z2`YuX#eMEiLEDhp+9uFqME_E^faV&~9qjBHJkIp~%$x^bN=N)K@kvSVEMdDuzA0sn z88CBG?`RX1@#hQNd`o^V{37)!w|nA)QfiYBE^m=yQKv-fQF+UCMcuEe1d4BH7$?>b zJl-r9@0^Ie=)guO1vOd=i$_4sz>y3x^R7n4ED!5oXL3@5**h(xr%Hv)_gILarO46q+MaDOF%ChaymKoI6JU5Pg;7#2n9-18|S1;AK+ zgsn6;k6-%!QD>D?cFy}8F;r@z8H9xN1jsOBw2vQONVqBVEbkiNUqgw~*!^##ht>w0 zUOykwH=$LwX2j&nLy=@{hr)2O&-wm-NyjW7n~Zs9UlH;P7iP3 zI}S(r0YFVYacnKH(+{*)Tbw)@;6>%=&Th=+Z6NHo_tR|JCI8TJiXv2N7ei7M^Q+RM z?9o`meH$5Yi;@9XaNR#jIK^&{N|DYNNbtdb)XW1Lv2k{E>;?F`#Pq|&_;gm~&~Zc9 zf+6ZE%{x4|{YdtE?a^gKyzr}dA>OxQv+pq|@IXL%WS0CiX!V zm$fCePA%lU{%pTKD7|5NJHeXg=I0jL@$tOF@K*MI$)f?om)D63K*M|r`gb9edD1~Y zc|w7N)Y%do7=0{RC|AziW7#am$)9jciRJ?IWl9PE{G3U+$%FcyKs_0Cgq`=K3@ttV z9g;M!3z~f_?P%y3-ph%vBMeS@p7P&Ea8M@97+%XEj*(1E6vHj==d zjsoviB>j^$_^OI_DEPvFkVo(BGRo%cJeD){6Uckei=~1}>sp299|IRjhXe)%?uP0I zF5+>?0#Ye}T^Y$u_rc4=lPcq4K^D(TZG-w30-YiEM=dcK+4#o*>lJ8&JLi+3UcpZk z!^?95S^C0ja^jwP`|{<+3cBVog$(mRdQmadS+Vh~z zS@|P}=|z3P6uS+&@QsMp0no9Od&27O&14zHXGAOEy zh~OKpymK5C%;LLb467@KgIiVwYbYd6wFxI{0-~MOGfTq$nBTB!{SrWmL9Hs}C&l&l#m?s*{tA?BHS4mVKHAVMqm63H<|c5n0~k)-kbg zXidai&9ZUy0~WFYYKT;oe~rytRk?)r8bptITsWj(@HLI;@=v5|XUnSls7$uaxFRL+ zRVMGuL3w}NbV1`^=Pw*0?>bm8+xfeY(1PikW*PB>>Tq(FR`91N0c2&>lL2sZo5=VD zQY{>7dh_TX98L2)n{2OV=T10~*YzX27i2Q7W86M4$?gZIXZaBq#sA*{PH8){|GUi;oM>e?ua7eF4WFuFYZSG| zze?srg|5Ti8Og{O zeFxuw9!U+zhyk?@w zjsA6(oKD=Ka;A>Ca)oPORxK+kxH#O@zhC!!XS4@=swnuMk>t+JmLmFiE^1aX3f<)D@`%K0FGK^gg1a1j>zi z2KhV>sjU7AX3F$SEqrXSC}fRx64GDoc%!u2Yag68Lw@w9v;xOONf@o)Lc|Uh3<21ctTYu-mFZuHk*+R{GjXHIGq3p)tFtQp%TYqD=j1&y)>@zxoxUJ!G@ zgI0XKmP6MNzw>nRxK$-Gbzs}dyfFzt>#5;f6oR27ql!%+{tr+(`(>%51|k`ML} zY4eE)Lxq|JMas(;JibNQds1bUB&r}ydMQXBY4x(^&fY_&LlQC)3hylc$~8&~|06-D z#T+%66rYbHX%^KuqJED_wuGB+=h`nWA!>1n0)3wZrBG3%`b^Ozv6__dNa@%V14|!D zQ?o$z5u0^8`giv%qE!BzZ!3j;BlDlJDk)h@9{nSQeEk!z9RGW) z${RSF3phEM*ce*>Xdp}585vj$|40=&S{S-GTiE?Op*vY&Lvr9}BO$XWy80IF+6@%n z5*2ueT_g@ofP#u5pxb7n*fv^Xtt7&?SRc{*2Ka-*!BuOpf}neHGCiHy$@Ka1^Dint z;DkmIL$-e)rj4o2WQV%Gy;Xg(_Bh#qeOsTM2f@KEe~4kJ8kNLQ+;(!j^bgJMcNhvklP5Z6I+9Fq@c&D~8Fb-4rmDT!MB5QC{Dsb;BharP*O;SF4& zc$wj-7Oep7#$WZN!1nznc@Vb<_Dn%ga-O#J(l=OGB`dy=Sy&$(5-n3zzu%d7E#^8`T@}V+5B;PP8J14#4cCPw-SQTdGa2gWL0*zKM z#DfSXs_iWOMt)0*+Y>Lkd=LlyoHjublNLefhKBv@JoC>P7N1_#> zv=mLWe96%EY;!ZGSQDbZWb#;tzqAGgx~uk+-$+2_8U`!ypbwXl z^2E-FkM1?lY@yt8=J3%QK+xaZ6ok=-y%=KXCD^0r!5vUneW>95PzCkOPO*t}p$;-> ze5j-BLT_;)cZQzR2CEsm@rU7GZfFtdp*a|g4wDr%8?2QkIGasRfDWT-Dvy*U{?IHT z*}wGnzdlSptl#ZF^sf)KT|BJs&kLG91^A6ls{CzFprZ6-Y!V0Xysh%9p%iMd7HLsS zN+^Un$tDV)T@i!v?3o0Fsx2qI(AX_$dDkBzQ@fRM%n zRXk6hb9Py#JXUs+7)w@eo;g%QQ95Yq!K_d=z{0dGS+pToEI6=Bo8+{k$7&Z zo4>PH(`ce8E-Ps&uv`NQ;U$%t;w~|@E3WVOCi~R4oj5wP?%<*1C%}Jq%a^q~T7u>K zML5AKfQDv6>PuT`{SrKHRAF+^&edg6+5R_#H?Lz3iGoWo#PCEd0DS;)2U({{X#zU^ zw_xv{4x7|t!S)>44J;KfA|DC?;uQ($l+5Vp7oeqf7{GBF9356nx|&B~gs+@N^gSdd zvb*>&W)|u#F{Z_b`f#GVtQ`pYv3#||N{xj1NgB<#=Odt6{eB%#9RLt5v zIi|0u70`#ai}9fJjKv7dE!9ZrOIX!3{$z_K5FBd-Kp-&e4(J$LD-)NMTp^_pB`RT; zftVVlK2g@+1Ahv2$D){@Y#cL#dUj9*&%#6 zd2m9{1NYp>)6=oAvqdCn5#cx{AJ%S8skUgMglu2*IAtd+z1>B&`MuEAS(D(<6X#Lj z?f4CFx$)M&$=7*>9v1ER4b6!SIz-m0e{o0BfkySREchp?WdVPpQCh!q$t>?rL!&Jg zd#heM;&~A}VEm8Dvy&P|J*eAV&w!&Nx6HFV&B8jJFVTmgLaswn!cx$&%JbTsloz!3 zMEz1d`k==`Ueub_JAy_&`!ogbwx27^ZXgFNAbx=g_I~5nO^r)}&myw~+yY*cJl4$I znNJ32M&K=0(2Dj_>@39`3=FX!v3nZHno_@q^!y}%(yw0PqOo=);6Y@&ylVe>nMOZ~ zd>j#QQSBn3oaWd;qy$&5(5H$Ayi)0haAYO6TH>FR?rhqHmNOO+(})NB zLI@B@v0)eq!ug`>G<@htRlp3n!EpU|n+G+AvXFrWSUsLMBfL*ZB`CRsIVHNTR&b?K zxBgsN0BjfB>UVcJ|x%=-zb%OV7lmZc& zxiupadZVF7)6QuhoY;;FK2b*qL0J-Rn-8!X4ZY$-ZSUXV5DFd7`T41c(#lAeLMoeT z4%g655v@7AqT!i@)Edt5JMbN(=Q-6{=L4iG8RA%}w;&pKmtWvI4?G9pVRp|RTw`g0 zD5c12B&A2&P6Ng~8WM2eIW=wxd?r7A*N+&!Be7PX3s|7~z=APxm=A?5 zt>xB4WG|*Td@VX{Rs)PV0|yK`oI3^xn(4c_j&vgxk_Y3o(-`_5o`V zRTghg6%l@(qodXN;dB#+OKJEEvhfcnc#BeO2|E(5df-!fKDZ!%9!^BJ_4)9P+9Dq5 zK1=(v?KmIp34r?z{NEWnLB3Px{XYwy-akun4F7xTRr2^zeYW{gcK9)>aJDdU5;w5@ zak=<+-PLH-|04pelTb%ULpuuuJC7DgyT@D|p{!V!0v3KpDnRjANN12q6SUR3mb9<- z>2r~IApQGhstZ!3*?5V z8#)hJ0TdZg0M-BK#nGFP>$i=qk82DO z7h;Ft!D5E15OgW)&%lej*?^1~2=*Z5$2VX>V{x8SC+{i10BbtUk9@I#Vi&hX)q

Q!LwySI{Bnv%Sm)yh{^sSVJ8&h_D-BJ_YZe5eCaAWU9b$O2c z$T|{vWVRtOL!xC0DTc(Qbe`ItNtt5hr<)VijD0{U;T#bUEp381_y`%ZIav?kuYG{iyYdEBPW=*xNSc;Rlt6~F4M`5G+VtOjc z*0qGzCb@gME5udTjJA-9O<&TWd~}ysBd(eVT1-H82-doyH9RST)|+Pb{o*;$j9Tjs zhU!IlsPsj8=(x3bAKJTopW3^6AKROHR^7wZ185wJGVhA~hEc|LP;k7NEz-@4p5o}F z`AD6naG3(n=NF9HTH81=F+Q|JOz$7wm9I<+#BSmB@o_cLt2GkW9|?7mM;r!JZp89l zbo!Hp8=n!XH1{GwaDU+k)pGp`C|cXkCU5%vcH)+v@0eK>%7gWxmuMu9YLlChA|_D@ zi#5zovN_!a-0?~pUV-Rj*1P)KwdU-LguR>YM&*Nen+ln8Q$?WFCJg%DY%K}2!!1FE zDv-A%Cbwo^p(lzac&_TZ-l#9kq`mhLcY3h9ZTUVCM(Ad&=EriQY5{jJv<5K&g|*Lk zgV%ILnf1%8V2B0E&;Sp4sYbYOvvMebLwYwzkRQ#F8GpTQq#uv=J`uaSJ34OWITeSGo6+-8Xw znCk*n{kdDEi)Hi&u^)~cs@iyCkFWB2SWZU|Uc%^43ZIZQ-vWNExCCtDWjqHs;;tWf$v{}0{p0Rvxkq``)*>+Akq%|Na zA`@~-Vfe|+(AIlqru+7Ceh4nsVmO9p9jc8}HX^W&ViBDXT+uXbT#R#idPn&L>+#b6 zflC-4C5-X;kUnR~L>PSLh*gvL68}RBsu#2l`s_9KjUWRhiqF`j)`y`2`YU(>3bdBj z?>iyjEhe-~$^I5!nn%B6Wh+I`FvLNvauve~eX<+Ipl&04 zT}};W&1a3%W?dJ2=N#0t?e+aK+%t}5q%jSLvp3jZ%?&F}nOOWr>+{GFIa%wO_2`et z=JzoRR~}iKuuR+azPI8;Gf9)z3kyA4EIOSl!sRR$DlW}0>&?GbgPojmjmnln;cTqCt=ADbE zZ8GAnoM+S1(5$i8^O4t`ue;vO4i}z0wz-QEIVe5_u03;}-!G1NyY8;h^}y;tzY}i5 zqQr#Ur3Fy8sSa$Q0ys+f`!`+>9WbvU_I`Sj;$4{S>O3?#inLHCrtLy~!s#WXV=oVP zeE93*Nc`PBi4q@%Ao$x4lw9vLHM!6mn3-b_cebF|n-2vt-zYVF_&sDE--J-P;2WHo z+@n2areE0o$LjvjlV2X7ZU@j+`{*8zq`JR3gKF#EW|#+{nMyo-a>nFFTg&vhyT=b} zDa8+v0(Dgx0yRL@ZXOYIlVSZ0|MFizy0VPW8;AfA5|pe!#j zX}Py^8fl5SyS4g1WSKKtnyP+_PoOwMMwu`(i@Z)diJp~U54*-miOchy7Z35eL>^M z4p<-aIxH4VUZgS783@H%M7P9hX>t{|RU7$n4T(brCG#h9e9p! z+o`i;EGGq3&pF;~5V~eBD}lC)>if$w%Vf}AFxGqO88|ApfHf&Bvu+xdG)@vuF}Yvk z)o;~k-%+0K0g+L`Wala!$=ZV|z$e%>f0%XoLib%)!R^RoS+{!#X?h-6uu zF&&KxORdZU&EwQFITIRLo(7TA3W}y6X{?Y%y2j0It!ekU#<)$qghZtpcS>L3uh`Uj z7GY;6f$9qKynP#oS3$$a{p^{D+0oJQ71`1?OAn_m8)UGZmj3l*ZI)`V-a>MKGGFG< z&^jg#Ok%(hhm>hSrZ5;Qga4u(?^i>GiW_j9%_7M>j(^|Om$#{k+^*ULnEgzW_1gCICtAD^WpC`A z{9&DXkG#01Xo)U$OC(L5Y$DQ|Q4C6CjUKk1UkPj$nXH##J{c8e#K|&{mA*;b$r0E4 zUNo0jthwA(c&N1l=PEe8Rw_8cEl|-eya9z&H3#n`B$t#+aJ03RFMzrV@gowbe8v(c zIFM60^0&lCFO10NU4w@|61xiZ4CVXeaKjd;d?sv52XM*lS8XiVjgWpRB;&U_C0g+`6B5V&w|O6B*_q zsATxL!M}+$He)1eOWECce#eS@2n^xhlB4<_Nn?yCVEQWDs(r`|@2GqLe<#(|&P0U? z$7V5IgpWf09uIf_RazRwC?qEqRaHyL?iiS05UiGesJy%^>-C{{ypTBI&B0-iUYhk> zIk<5xpsuV@g|z(AZD+C-;A!fTG=df1=<%nxy(a(IS+U{ME4ZbDEBtcD_3V=icT6*_ z)>|J?>&6%nvHhZERBtjK+s4xnut*@>GAmA5m*OTp$!^CHTr}vM4n(X1Q*;{e-Rd2BCF-u@1ZGm z!S8hJ6L=Gl4T_SDa7Xx|-{4mxveJg=ctf`BJ*fy!yF6Dz&?w(Q_6B}WQVtNI!BVBC zKfX<>7vd6C96}XAQmF-Jd?1Q4eTfRB3q7hCh0f!(JkdWT5<{iAE#dKy*Jxq&3a1@~ z8C||Dn2mFNyrUV|<-)C^_y7@8c2Fz+2jrae9deBDu;U}tJ{^xAdxCD248(k;dCJ%o z`y3sADe>U%suxwwv~8A1+R$VB=Q?%U?4joI$um;aH+eCrBqpn- z%79D_7rb;R-;-9RTrwi9dPlg8&@tfWhhZ(Vx&1PQ+6(huX`;M9x~LrW~~#3{j0Bh2kDU$}@!fFQej4VGkJv?M4rU^x!RU zEwhu$!CA_iDjFjrJa`aocySDX16?~;+wgav;}Zut6Mg%C4>}8FL?8)Kgwc(Qlj{@#2Pt0?G`$h7P#M+qoXtlV@d}%c&OzO+QYKK`kyXaK{U(O^2DyIXCZlNQjt0^8~8JzNGrIxhj}}M z&~QZlbx%t;MJ(Vux;2tgNKGlAqphLq%pd}JG9uoVHUo?|hN{pLQ6Em%r*+7t^<);X zm~6=qChlNAVXNN*Sow->*4;}T;l;D1I-5T{Bif@4_}=>l`tK;qqDdt5zvisCKhMAH z#r}`)7VW?LZqfdmXQ%zo5bJ00{Xb9^YKrk0Nf|oIW*K@(=`o2Vndz}ZDyk{!u}PVx zzd--+_WC*U{~DH3{?GI64IB+@On&@9X>EUAo&L+G{L^dozaI4C3G#2wr~hseW@K&g zKWs{uHu-9Je!3;4pE>eBltKUXb^*hG8I&413)$J&{D4N%7PcloU6bn%jPxJyQL?g* z9g+YFFEDiE`8rW^laCNzQmi7CTnPfwyg3VDHRAl>h=In6jeaVOP@!-CP60j3+#vpL zEYmh_oP0{-gTe7Or`L6x)6w?77QVi~jD8lWN@3RHcm80iV%M1A!+Y6iHM)05iC64tb$X2lV_%Txk@0l^hZqi^%Z?#- zE;LE0uFx)R08_S-#(wC=dS&}vj6P4>5ZWjhthP=*Hht&TdLtKDR;rXEX4*z0h74FA zMCINqrh3Vq;s%3MC1YL`{WjIAPkVL#3rj^9Pj9Ss7>7duy!9H0vYF%>1jh)EPqvlr6h%R%CxDsk| z!BACz7E%j?bm=pH6Eaw{+suniuY7C9Ut~1cWfOX9KW9=H><&kQlinPV3h9R>3nJvK z4L9(DRM=x;R&d#a@oFY7mB|m8h4692U5eYfcw|QKwqRsshN(q^v$4$)HgPpAJDJ`I zkqjq(8Cd!K!+wCd=d@w%~e$=gdUgD&wj$LQ1r>-E=O@c ze+Z$x{>6(JA-fNVr)X;*)40Eym1TtUZI1Pwwx1hUi+G1Jlk~vCYeXMNYtr)1?qwyg zsX_e*$h?380O00ou?0R@7-Fc59o$UvyVs4cUbujHUA>sH!}L54>`e` zHUx#Q+Hn&Og#YVOuo*niy*GU3rH;%f``nk#NN5-xrZ34NeH$l`4@t);4(+0|Z#I>Y z)~Kzs#exIAaf--65L0UHT_SvV8O2WYeD>Mq^Y6L!Xu8%vnpofG@w!}R7M28?i1*T&zp3X4^OMCY6(Dg<-! zXmcGQrRgHXGYre7GfTJ)rhl|rs%abKT_Nt24_Q``XH{88NVPW+`x4ZdrMuO0iZ0g` z%p}y};~T5gbb9SeL8BSc`SO#ixC$@QhXxZ=B}L`tP}&k?1oSPS=4%{UOHe0<_XWln zwbl5cn(j-qK`)vGHY5B5C|QZd5)W7c@{bNVXqJ!!n$^ufc?N9C-BF2QK1(kv++h!>$QbAjq)_b$$PcJdV+F7hz0Hu@ zqj+}m0qn{t^tD3DfBb~0B36|Q`bs*xs|$i^G4uNUEBl4g;op-;Wl~iThgga?+dL7s zUP(8lMO?g{GcYpDS{NM!UA8Hco?#}eNEioRBHy4`mq!Pd-9@-97|k$hpEX>xoX+dY zDr$wfm^P&}Wu{!%?)U_(%Mn79$(ywvu*kJ9r4u|MyYLI_67U7%6Gd_vb##Nerf@>& z8W11z$$~xEZt$dPG}+*IZky+os5Ju2eRi;1=rUEeIn>t-AzC_IGM-IXWK3^6QNU+2pe=MBn4I*R@A%-iLDCOHTE-O^wo$sL_h{dcPl=^muAQb`_BRm};=cy{qSkui;`WSsj9%c^+bIDQ z0`_?KX0<-=o!t{u(Ln)v>%VGL z0pC=GB7*AQ?N7N{ut*a%MH-tdtNmNC+Yf$|KS)BW(gQJ*z$d{+{j?(e&hgTy^2|AR9vx1Xre2fagGv0YXWqtNkg*v%40v?BJBt|f9wX5 z{QTlCM}b-0{mV?IG>TW_BdviUKhtosrBqdfq&Frdz>cF~yK{P@(w{Vr7z2qKFwLhc zQuogKO@~YwyS9%+d-zD7mJG~@?EFJLSn!a&mhE5$_4xBl&6QHMzL?CdzEnC~C3$X@ zvY!{_GR06ep5;<#cKCSJ%srxX=+pn?ywDwtJ2{TV;0DKBO2t++B(tIO4)Wh`rD13P z4fE$#%zkd=UzOB74gi=-*CuID&Z3zI^-`4U^S?dHxK8fP*;fE|a(KYMgMUo`THIS1f!*6dOI2 zFjC3O=-AL`6=9pp;`CYPTdVX z8(*?V&%QoipuH0>WKlL8A*zTKckD!paN@~hh zmXzm~qZhMGVdQGd=AG8&20HW0RGV8X{$9LldFZYm zE?}`Q3i?xJRz43S?VFMmqRyvWaS#(~Lempg9nTM$EFDP(Gzx#$r)W&lpFKqcAoJh-AxEw$-bjW>`_+gEi z2w`99#UbFZGiQjS8kj~@PGqpsPX`T{YOj`CaEqTFag;$jY z8_{Wzz>HXx&G*Dx<5skhpETxIdhKH?DtY@b9l8$l?UkM#J-Snmts7bd7xayKTFJ(u zyAT&@6cAYcs{PBfpqZa%sxhJ5nSZBPji?Zlf&}#L?t)vC4X5VLp%~fz2Sx<*oN<7` z?ge=k<=X7r<~F7Tvp9#HB{!mA!QWBOf%EiSJ6KIF8QZNjg&x~-%e*tflL(ji_S^sO ztmib1rp09uon}RcsFi#k)oLs@$?vs(i>5k3YN%$T(5Or(TZ5JW9mA6mIMD08=749$ z!d+l*iu{Il7^Yu}H;lgw=En1sJpCKPSqTCHy4(f&NPelr31^*l%KHq^QE>z>Ks_bH zjbD?({~8Din7IvZeJ>8Ey=e;I?thpzD=zE5UHeO|neioJwG;IyLk?xOz(yO&0DTU~ z^#)xcs|s>Flgmp;SmYJ4g(|HMu3v7#;c*Aa8iF#UZo7CvDq4>8#qLJ|YdZ!AsH%^_7N1IQjCro

K7UpUK$>l@ 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 E2O(~oYXATM diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d706aba60..23449a2b5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 4f906e0c8..adff685a0 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,81 +15,114 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,88 +131,118 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index ac1b06f93..e509b2dd8 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,8 +13,10 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +27,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -56,32 +59,33 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal From 5716671f127d91f78099c73e17ae089f740ed52c Mon Sep 17 00:00:00 2001 From: ManApart Date: Tue, 30 Dec 2025 10:52:31 -0500 Subject: [PATCH 15/45] fix flaky speaking tests --- src/commonMain/kotlin/core/ai/AI.kt | 5 +++-- .../kotlin/resources/ai/desire/CommonDesires.kt | 2 +- src/commonMain/kotlin/traveling/travel/TravelStart.kt | 3 +-- .../kotlin/commandCombos/ConversationsTest.kt | 9 ++++++++- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/commonMain/kotlin/core/ai/AI.kt b/src/commonMain/kotlin/core/ai/AI.kt index aac6fd96b..bc6d5310c 100644 --- a/src/commonMain/kotlin/core/ai/AI.kt +++ b/src/commonMain/kotlin/core/ai/AI.kt @@ -6,16 +6,17 @@ import core.thing.Thing abstract class AI { lateinit var creature: Thing + var enabled: Boolean = true abstract suspend fun hear(event: DialogueEvent) abstract suspend fun takeAction() val actions = mutableListOf() suspend fun chooseAction() { - if (!creature.isPlayer()) { + if (enabled && !creature.isPlayer()) { takeAction() } } -} \ No newline at end of file +} diff --git a/src/commonMain/kotlin/resources/ai/desire/CommonDesires.kt b/src/commonMain/kotlin/resources/ai/desire/CommonDesires.kt index 96e60d646..c2f4e4d05 100644 --- a/src/commonMain/kotlin/resources/ai/desire/CommonDesires.kt +++ b/src/commonMain/kotlin/resources/ai/desire/CommonDesires.kt @@ -78,4 +78,4 @@ private suspend fun Thing.activators(): List { private suspend fun Thing.items(): List { return location.getLocation().getItems(perceivedBy = this).filter { it != this } -} \ No newline at end of file +} diff --git a/src/commonMain/kotlin/traveling/travel/TravelStart.kt b/src/commonMain/kotlin/traveling/travel/TravelStart.kt index 6d221667c..9f0292652 100644 --- a/src/commonMain/kotlin/traveling/travel/TravelStart.kt +++ b/src/commonMain/kotlin/traveling/travel/TravelStart.kt @@ -1,6 +1,5 @@ package traveling.travel -import combat.attack.AttackEvent import core.events.EventListener import core.events.EventManager import core.history.displayToMe @@ -50,4 +49,4 @@ suspend fun postArriveEvent(source: Thing, destination: LocationPoint, requiredS source.addSoundEffect("Moving", "the sound of footfalls", soundLevel) EventManager.postEvent(ArriveEvent(source, destination = destination, method = "travel", quiet = quiet)) EventManager.postEvent(StatChangeEvent(source, "The journey", STAMINA, -requiredStamina, silent = quiet)) -} \ No newline at end of file +} diff --git a/src/jvmTestIntegration/kotlin/commandCombos/ConversationsTest.kt b/src/jvmTestIntegration/kotlin/commandCombos/ConversationsTest.kt index 83b92e8c4..fb4b8d068 100644 --- a/src/jvmTestIntegration/kotlin/commandCombos/ConversationsTest.kt +++ b/src/jvmTestIntegration/kotlin/commandCombos/ConversationsTest.kt @@ -8,6 +8,7 @@ import core.events.EventManager import core.history.GameLogger import kotlinx.coroutines.runBlocking import system.debug.DebugType +import traveling.location.location.LocationManager import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals @@ -20,6 +21,7 @@ class ConversationsTest { EventManager.clear() GameManager.newGame(testing = true) runBlocking { EventManager.processEvents() } + disableFarmerAI() } } @@ -54,7 +56,6 @@ class ConversationsTest { assertEquals("Farmer: I be with you.", GameLogger.getMainHistory().getLastOutput()) } - //TODO -this is flaky! @Test fun whereBeMe() { runBlocking { @@ -81,4 +82,10 @@ class ConversationsTest { } assertEquals("Farmer: What you mean? You mean Kanbara Gate or Kanbara City?", GameLogger.getMainHistory().getLastOutput()) } + + private suspend fun disableFarmerAI() { + LocationManager.findLocationInAnyNetwork(GameState.player.thing, "Farmer's Hut")!!.getLocation() + .getThings("Farmer").first() + .mind.ai.enabled = false + } } From dd522d972870f212678a848bc49562b283bff17d Mon Sep 17 00:00:00 2001 From: ManApart Date: Tue, 30 Dec 2025 12:30:04 -0500 Subject: [PATCH 16/45] make validate a test --- src/commonMain/kotlin/core/ai/DumbAI.kt | 14 ++++---------- .../ai/packages/AIPackageTemplateBuilder.kt | 2 +- .../packages/AIPackageTemplatesCollection.kt | 3 +-- .../kotlin/core/ai/packages/IdeaBuilder.kt | 2 +- .../resources/ai/packages/CommonPackages.kt | 19 +++++++++++++++++-- .../kotlin/validation/AIPackageValidator.kt | 15 ++++++++++----- 6 files changed, 34 insertions(+), 21 deletions(-) diff --git a/src/commonMain/kotlin/core/ai/DumbAI.kt b/src/commonMain/kotlin/core/ai/DumbAI.kt index 9474ee740..3ef8ccdf7 100644 --- a/src/commonMain/kotlin/core/ai/DumbAI.kt +++ b/src/commonMain/kotlin/core/ai/DumbAI.kt @@ -3,15 +3,9 @@ package core.ai import conversation.dialogue.DialogueEvent class DumbAI : AI() { - override fun toString(): String { - return "Dumb AI for ${creature.name}" - } + override fun toString() = "Dumb AI for ${creature.name}" override suspend fun hear(event: DialogueEvent) {} override suspend fun takeAction() {} - override fun equals(other: Any?): Boolean { - return other is DumbAI - } - override fun hashCode(): Int { - return this::class.hashCode() - } -} \ No newline at end of file + override fun equals(other: Any?) = other is DumbAI + override fun hashCode() = this::class.hashCode() +} diff --git a/src/commonMain/kotlin/core/ai/packages/AIPackageTemplateBuilder.kt b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplateBuilder.kt index 210afd35e..db87e17df 100644 --- a/src/commonMain/kotlin/core/ai/packages/AIPackageTemplateBuilder.kt +++ b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplateBuilder.kt @@ -65,4 +65,4 @@ class AIPackageTemplatesBuilder { fun aiPackages(initializer: AIPackageTemplatesBuilder.() -> Unit): List { return AIPackageTemplatesBuilder().apply(initializer).children.map { it.build() } -} \ No newline at end of file +} diff --git a/src/commonMain/kotlin/core/ai/packages/AIPackageTemplatesCollection.kt b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplatesCollection.kt index da15a595d..626227397 100644 --- a/src/commonMain/kotlin/core/ai/packages/AIPackageTemplatesCollection.kt +++ b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplatesCollection.kt @@ -1,6 +1,5 @@ package core.ai.packages -import core.ai.packages.AIPackageTemplate interface AIPackageTemplatesCollection { val values: List -} \ No newline at end of file +} diff --git a/src/commonMain/kotlin/core/ai/packages/IdeaBuilder.kt b/src/commonMain/kotlin/core/ai/packages/IdeaBuilder.kt index 622131562..99fce2995 100644 --- a/src/commonMain/kotlin/core/ai/packages/IdeaBuilder.kt +++ b/src/commonMain/kotlin/core/ai/packages/IdeaBuilder.kt @@ -51,4 +51,4 @@ class IdeasBuilder { fun ideas(initializer: IdeasBuilder.() -> Unit): List { return IdeasBuilder().apply(initializer).getChildren() -} \ No newline at end of file +} diff --git a/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt b/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt index 84019b846..30640126c 100644 --- a/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt +++ b/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt @@ -10,7 +10,7 @@ import use.interaction.nothing.NothingEvent class CommonPackages : AIPackageTemplateResource { override val values = aiPackages { - aiPackage("creature") { + aiPackage("Creature") { idea("Rest") { cond { s -> s.soul.getCurrent(STAMINA) < s.soul.getTotal(STAMINA) / 10 } @@ -39,10 +39,16 @@ class CommonPackages : AIPackageTemplateResource { it.clearUseGoal() } } + idea("Wander") { + act { + val target = it.location.getLocation().getThings(it).random() + startMoveEvent(it, destination = target.position) + } + } } aiPackage("Commoner") { - template("creature") + template("Creature") idea("Want Food") { cond { GameState.timeManager.getPercentDayComplete() in listOf(25, 50, 75) } act { owner -> @@ -54,6 +60,15 @@ class CommonPackages : AIPackageTemplateResource { } } + aiPackage("Predator") { + template("Creature") + idea("Rest") { + cond { !GameState.timeManager.isNight() } + act { RestEvent(it, 2) } + } + + } + } } diff --git a/src/jvmTestIntegration/kotlin/validation/AIPackageValidator.kt b/src/jvmTestIntegration/kotlin/validation/AIPackageValidator.kt index 1f10caa48..048e499e5 100644 --- a/src/jvmTestIntegration/kotlin/validation/AIPackageValidator.kt +++ b/src/jvmTestIntegration/kotlin/validation/AIPackageValidator.kt @@ -5,6 +5,8 @@ import core.DependencyInjector import core.ai.AIManager2 import core.ai.packages.AIPackageTemplatesCollection import kotlinx.coroutines.runBlocking +import kotlin.test.Test +import kotlin.test.assertEquals //TODO validate all things/minds reference valid package name @@ -13,10 +15,13 @@ class AIPackageValidator { private val packages = runBlocking { AIManager2.aiPackages } private val templates = (DependencyInjector.getImplementation(AIPackageTemplatesCollection::class).values + ModManager.ai2) - fun validate(): Int { - return noDuplicatePackageNames() + - noDuplicateIdeaNames() + - subPackageStringReferenceExists() + @Test + fun validate() { + assertEquals( + 0, noDuplicatePackageNames() + + noDuplicateIdeaNames() + + subPackageStringReferenceExists() + ) } private fun noDuplicatePackageNames(): Int { @@ -65,4 +70,4 @@ class AIPackageValidator { } -} \ No newline at end of file +} From 5033b4794a51f2d79abece1addde75856556dc71 Mon Sep 17 00:00:00 2001 From: ManApart Date: Tue, 30 Dec 2025 12:35:46 -0500 Subject: [PATCH 17/45] extract out packages to own files --- .../packages/AIPackageTemplatesCollection.kt | 3 +- .../packages/AIPackageTemplatesGenerated.kt | 2 +- .../resources/ai/packages/CommonPackages.kt | 53 ------------------- .../resources/ai/packages/CommonerPackage.kt | 24 +++++++++ .../resources/ai/packages/CreaturePackage.kt | 53 +++++++++++++++++++ 5 files changed, 80 insertions(+), 55 deletions(-) create mode 100644 src/commonMain/kotlin/resources/ai/packages/CommonerPackage.kt create mode 100644 src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt diff --git a/src/commonMain/kotlin/core/ai/packages/AIPackageTemplatesCollection.kt b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplatesCollection.kt index 626227397..da15a595d 100644 --- a/src/commonMain/kotlin/core/ai/packages/AIPackageTemplatesCollection.kt +++ b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplatesCollection.kt @@ -1,5 +1,6 @@ package core.ai.packages +import core.ai.packages.AIPackageTemplate interface AIPackageTemplatesCollection { val values: List -} +} \ No newline at end of file diff --git a/src/commonMain/kotlin/core/ai/packages/AIPackageTemplatesGenerated.kt b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplatesGenerated.kt index af3ca26a8..f29fdd1e4 100644 --- a/src/commonMain/kotlin/core/ai/packages/AIPackageTemplatesGenerated.kt +++ b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplatesGenerated.kt @@ -1,5 +1,5 @@ package core.ai.packages class AIPackageTemplatesGenerated : AIPackageTemplatesCollection { - override val values by lazy { listOf(resources.ai.packages.CommonPackages()).flatMap { it.values }} + override val values by lazy { listOf(resources.ai.packages.CommonPackages(), resources.ai.packages.CommonerPackage(), resources.ai.packages.CreaturePackage()).flatMap { it.values }} } \ No newline at end of file diff --git a/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt b/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt index 30640126c..67957ebf6 100644 --- a/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt +++ b/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt @@ -10,65 +10,12 @@ import use.interaction.nothing.NothingEvent 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 - //If usetarget in different location, create map to get there - // map available, move to location - idea("Move to Use Target", 50) { - cond { it.hasUseTarget() && !it.canReach(it.mind.getUseTargetThing()!!.position) } - act { startMoveEvent(it, destination = it.mind.getUseTargetThing()!!.position) } - } - - idea("Eat Targeted Food", 50) { - cond { it.canReachGoal("eat") } - act { - EatFoodEvent(it, it.mind.getUseTargetThing()!!) - it.clearUseGoal() - } - } - idea("Wander") { - act { - val target = it.location.getLocation().getThings(it).random() - startMoveEvent(it, destination = target.position) - } - } - } - - 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.setUseGoal(target, "eat") - } ?: NothingEvent(owner) - } - } - } - aiPackage("Predator") { template("Creature") idea("Rest") { cond { !GameState.timeManager.isNight() } act { RestEvent(it, 2) } } - } - } - } diff --git a/src/commonMain/kotlin/resources/ai/packages/CommonerPackage.kt b/src/commonMain/kotlin/resources/ai/packages/CommonerPackage.kt new file mode 100644 index 000000000..01ae1a3a8 --- /dev/null +++ b/src/commonMain/kotlin/resources/ai/packages/CommonerPackage.kt @@ -0,0 +1,24 @@ +package resources.ai.packages + +import core.GameState +import core.ai.packages.AIPackageTemplateResource +import core.ai.packages.aiPackages +import core.ai.packages.setUseGoal +import use.interaction.nothing.NothingEvent + +class CommonerPackage : AIPackageTemplateResource { + override val values = aiPackages { + 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.setUseGoal(target, "eat") + } ?: NothingEvent(owner) + } + } + } + } +} diff --git a/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt b/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt new file mode 100644 index 000000000..d70b68702 --- /dev/null +++ b/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt @@ -0,0 +1,53 @@ +package resources.ai.packages + +import core.ai.packages.AIPackageTemplateResource +import core.ai.packages.aiPackages +import core.ai.packages.canReachGoal +import core.ai.packages.clawAttack +import core.ai.packages.clearUseGoal +import core.ai.packages.hasUseTarget +import status.rest.RestEvent +import status.stat.STAMINA +import traveling.move.startMoveEvent +import use.eat.EatFoodEvent + +class CreaturePackage : 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 + //If usetarget in different location, create map to get there + // map available, move to location + idea("Move to Use Target", 50) { + cond { it.hasUseTarget() && !it.canReach(it.mind.getUseTargetThing()!!.position) } + act { startMoveEvent(it, destination = it.mind.getUseTargetThing()!!.position) } + } + + idea("Eat Targeted Food", 50) { + cond { it.canReachGoal("eat") } + act { + EatFoodEvent(it, it.mind.getUseTargetThing()!!) + it.clearUseGoal() + } + } + idea("Wander") { + act { + val target = it.location.getLocation().getThings(it).random() + startMoveEvent(it, destination = target.position) + } + } + } + } +} From e060a247fbc565fed14e0f4faae39077b37f5589 Mon Sep 17 00:00:00 2001 From: ManApart Date: Tue, 30 Dec 2025 13:49:14 -0500 Subject: [PATCH 18/45] setup to use enums for properties --- .../conversation/dsl/DialogueBuilder.kt | 5 ++- .../core/ai/knowledge/DiscoverFactEvent.kt | 17 ++++++++++ .../kotlin/core/ai/knowledge/Fact.kt | 12 +++++-- .../core/ai/knowledge/ForgetFactEvent.kt | 4 +++ .../core/ai/packages/AIPackageHelpers.kt | 31 +++++++++++++------ .../kotlin/core/ai/packages/IdeaBuilder.kt | 2 +- .../kotlin/core/properties/Properties.kt | 8 ++++- .../kotlin/core/properties/PropsBuilder.kt | 6 ++-- .../kotlin/core/properties/TagKey.kt | 5 +++ src/commonMain/kotlin/core/properties/Tags.kt | 3 +- .../kotlin/core/properties/ValueKey.kt | 5 +++ .../kotlin/core/properties/Values.kt | 3 +- .../resources/ai/agenda/CommonAgendas.kt | 9 ++---- .../resources/ai/desire/CommonDesires.kt | 23 ++++---------- .../resources/ai/packages/CommonPackages.kt | 11 ++++--- .../resources/ai/packages/CommonerPackage.kt | 28 ++++++++++------- .../resources/ai/packages/CreaturePackage.kt | 30 +++++++++++++----- 17 files changed, 137 insertions(+), 65 deletions(-) create mode 100644 src/commonMain/kotlin/core/properties/TagKey.kt create mode 100644 src/commonMain/kotlin/core/properties/ValueKey.kt diff --git a/src/commonMain/kotlin/conversation/dsl/DialogueBuilder.kt b/src/commonMain/kotlin/conversation/dsl/DialogueBuilder.kt index 666dd037d..5dc3acb47 100644 --- a/src/commonMain/kotlin/conversation/dsl/DialogueBuilder.kt +++ b/src/commonMain/kotlin/conversation/dsl/DialogueBuilder.kt @@ -5,6 +5,7 @@ import conversation.dialogue.DialogueEvent import conversation.parsing.QuestionType import conversation.parsing.Verb import core.events.Event +import core.properties.TagKey import core.thing.Thing import core.utility.Named import core.utility.applySuspending @@ -81,6 +82,8 @@ suspend fun Conversation.subjects(): List? { return history.last().parsed()?.subjects } +//TODO - move this to thing and location node +suspend fun Named?.hasTag(tag: TagKey) = hasTag(tag.name) suspend fun Named?.hasTag(tag: String): Boolean { return if (this != null) { (this is LocationNode && getLocation().properties.tags.has(tag)) || @@ -88,4 +91,4 @@ suspend fun Named?.hasTag(tag: String): Boolean { } else { false } -} \ No newline at end of file +} diff --git a/src/commonMain/kotlin/core/ai/knowledge/DiscoverFactEvent.kt b/src/commonMain/kotlin/core/ai/knowledge/DiscoverFactEvent.kt index 2fd523f9d..b69776bd7 100644 --- a/src/commonMain/kotlin/core/ai/knowledge/DiscoverFactEvent.kt +++ b/src/commonMain/kotlin/core/ai/knowledge/DiscoverFactEvent.kt @@ -1,6 +1,23 @@ package core.ai.knowledge import core.events.Event +import core.properties.ValueKey +import core.properties.props import core.thing.Thing +import traveling.location.network.LocationNode data class DiscoverFactEvent(val source: Thing, val fact: Fact) : Event + +fun Thing.discover(target: Thing, kind: FactKind) = discover(target, kind.name) +fun Thing.discover(target: Thing, kind: String): DiscoverFactEvent { + return DiscoverFactEvent(this, Fact(Subject(target), kind)) +} + +fun Thing.setUseTarget(target: Thing, howToUse: HowToUse): DiscoverFactEvent { + return DiscoverFactEvent(this, Fact(Subject(target), FactKind.USE_TARGET.name, props(ValueKey.GOAL to howToUse.name))) +} + +fun Thing.discover(target: LocationNode, kind: FactKind) = discover(target, kind.name) +fun Thing.discover(target: LocationNode, kind: String): DiscoverFactEvent { + return DiscoverFactEvent(this, Fact(Subject(target), kind)) +} diff --git a/src/commonMain/kotlin/core/ai/knowledge/Fact.kt b/src/commonMain/kotlin/core/ai/knowledge/Fact.kt index c20806a1d..af15e1f9e 100644 --- a/src/commonMain/kotlin/core/ai/knowledge/Fact.kt +++ b/src/commonMain/kotlin/core/ai/knowledge/Fact.kt @@ -5,5 +5,13 @@ import core.properties.Properties data class Fact(val source: Subject, val kind: String, val props: Properties = Properties()) data class ListFact(val kind: String, val sources: List, val props: Properties = Properties()) { - constructor(kind: String, source: Subject, props: Properties = Properties()): this(kind, listOf(source), props) -} \ No newline at end of file + constructor(kind: String, source: Subject, props: Properties = Properties()) : this(kind, listOf(source), props) +} + +enum class FactKind { + USE_TARGET +} + +enum class HowToUse { + EAT +} diff --git a/src/commonMain/kotlin/core/ai/knowledge/ForgetFactEvent.kt b/src/commonMain/kotlin/core/ai/knowledge/ForgetFactEvent.kt index 09b47670b..b5897d660 100644 --- a/src/commonMain/kotlin/core/ai/knowledge/ForgetFactEvent.kt +++ b/src/commonMain/kotlin/core/ai/knowledge/ForgetFactEvent.kt @@ -8,3 +8,7 @@ data class ForgetFactEvent(val source: Thing, val fact: Fact? = null, val listFa require(fact != null || listFact != null || kind != null) } } + +fun Thing.clearUseGoal(): ForgetFactEvent { + return ForgetFactEvent(this, kind = FactKind.USE_TARGET.name) +} diff --git a/src/commonMain/kotlin/core/ai/packages/AIPackageHelpers.kt b/src/commonMain/kotlin/core/ai/packages/AIPackageHelpers.kt index 4e159b17a..8fb96e975 100644 --- a/src/commonMain/kotlin/core/ai/packages/AIPackageHelpers.kt +++ b/src/commonMain/kotlin/core/ai/packages/AIPackageHelpers.kt @@ -7,8 +7,10 @@ import core.GameState import core.ai.knowledge.DiscoverFactEvent import core.ai.knowledge.Fact import core.ai.knowledge.ForgetFactEvent +import core.ai.knowledge.HowToUse import core.ai.knowledge.Subject import core.properties.Properties +import core.properties.ValueKey import core.properties.Values import core.thing.Thing import core.utility.RandomManager @@ -17,17 +19,10 @@ import traveling.position.ThingAim suspend fun Thing.hasUseTarget() = mind.getUseTargetThing() != null -fun Thing.setUseGoal(target: Thing, howToUse: String): DiscoverFactEvent { - return DiscoverFactEvent(this, Fact(Subject(target), "useTarget", Properties(Values(mutableMapOf("goal" to howToUse))))) -} - -fun Thing.clearUseGoal(): ForgetFactEvent { - return ForgetFactEvent(this, kind ="useTarget") -} - +suspend fun Thing.canReachGoal(howToUse: HowToUse ) = canReachGoal(howToUse.name) suspend fun Thing.canReachGoal(howToUse: String ): Boolean { val useTarget = mind.getUseTarget() - return useTarget != null && useTarget.props.values.getString("goal") == howToUse && useTarget.source.getThing()?.position?.let { pos -> canReach(pos) } ?: false + return useTarget != null && useTarget.props.values.getString(ValueKey.GOAL) == howToUse && useTarget.source.getThing()?.position?.let { pos -> canReach(pos) } ?: false } suspend fun clawAttack(target: Thing, creature: Thing): AttackEvent { @@ -43,4 +38,20 @@ suspend fun clawAttack(target: Thing, creature: Thing): AttackEvent { creature.body.getRootPart() } return startAttack(creature, partToAttackWith, ThingAim(GameState.player.thing, thingPart), DamageType.SLASH) -} \ No newline at end of file +} + +suspend fun Thing.perceivedCreatures(): List { + return location.getLocation().getCreatures(perceivedBy = this).filter { it != this } +} + +suspend fun Thing.perceivedActivators(): List { + return location.getLocation().getActivators(perceivedBy = this).filter { it != this } +} + +suspend fun Thing.perceivedItems(): List { + return location.getLocation().getItems(perceivedBy = this).filter { it != this } +} + +suspend fun Thing.perceivedItemsAndInventory(): List { + return (inventory.getItems() + location.getLocation().getItems(perceivedBy = this)).filter { it != this } +} diff --git a/src/commonMain/kotlin/core/ai/packages/IdeaBuilder.kt b/src/commonMain/kotlin/core/ai/packages/IdeaBuilder.kt index 99fce2995..aa6910262 100644 --- a/src/commonMain/kotlin/core/ai/packages/IdeaBuilder.kt +++ b/src/commonMain/kotlin/core/ai/packages/IdeaBuilder.kt @@ -28,7 +28,7 @@ class IdeasBuilder { private val criteriaChildren = mutableMapOf Boolean, IdeasBuilder>() fun getChildren(parentCriteria: suspend (Thing) -> Boolean = { true }): List { - children.forEach { it.criteria = { thing -> parentCriteria(thing) && it.criteria(thing) } } + children.forEach { it.criteria = { thing -> parentCriteria(thing) && it.criteria(thing) } } return children + criteriaChildren.flatMap { (criteria, child) -> child.getChildren { thing -> parentCriteria(thing) && criteria(thing) } diff --git a/src/commonMain/kotlin/core/properties/Properties.kt b/src/commonMain/kotlin/core/properties/Properties.kt index 1e843f4be..b97015adb 100644 --- a/src/commonMain/kotlin/core/properties/Properties.kt +++ b/src/commonMain/kotlin/core/properties/Properties.kt @@ -10,8 +10,14 @@ import traveling.position.Distances.MIN_RANGE import traveling.position.Distances.SPEAR_RANGE import traveling.position.Distances.SWORD_RANGE + +fun props(vararg values: Pair) = Properties(*values.map { (k,v) -> k.name to v }.toTypedArray()) + data class Properties(val values: Values = Values(), val tags: Tags = Tags()) { constructor(tags: Tags) : this(Values(), tags) + constructor(vararg tags: String) : this(Values(), Tags(*tags)) + constructor(vararg tags: TagKey) : this(Values(), Tags(*tags)) + constructor(vararg values: Pair) : this(Values(*values), Tags()) constructor(base: Properties, params: Map = mapOf()) : this( Values(base.values, params), Tags(base.tags, params) @@ -102,4 +108,4 @@ data class Properties(val values: Values = Values(), val tags: Tags = Tags()) { } -} \ No newline at end of file +} diff --git a/src/commonMain/kotlin/core/properties/PropsBuilder.kt b/src/commonMain/kotlin/core/properties/PropsBuilder.kt index 837a4ba15..34d95718d 100644 --- a/src/commonMain/kotlin/core/properties/PropsBuilder.kt +++ b/src/commonMain/kotlin/core/properties/PropsBuilder.kt @@ -16,7 +16,7 @@ class PropsBuilder { fun value(key: String, value: String) = values.entry(key, value) fun value(key: String, value: Int) = values.entry(key, value) - fun props(properties: Properties){ + fun props(properties: Properties) { tags.addAll(properties.tags.getAll()) value(properties.values.getAll()) } @@ -36,10 +36,10 @@ class PropsBuilder { } } -fun props(params: Map = mapOf(), initializer: PropsBuilder.() -> Unit): Properties { +fun props(params: Map = mapOf(), initializer: PropsBuilder.() -> Unit): Properties { return PropsBuilder().apply(initializer).build(params) } fun propsUnbuilt(initializer: PropsBuilder.() -> Unit): PropsBuilder { return PropsBuilder().apply(initializer) -} \ No newline at end of file +} diff --git a/src/commonMain/kotlin/core/properties/TagKey.kt b/src/commonMain/kotlin/core/properties/TagKey.kt new file mode 100644 index 000000000..68bb35bc8 --- /dev/null +++ b/src/commonMain/kotlin/core/properties/TagKey.kt @@ -0,0 +1,5 @@ +package core.properties + +enum class TagKey { + FOOD +} diff --git a/src/commonMain/kotlin/core/properties/Tags.kt b/src/commonMain/kotlin/core/properties/Tags.kt index 78f6eab75..76cc5f5d2 100644 --- a/src/commonMain/kotlin/core/properties/Tags.kt +++ b/src/commonMain/kotlin/core/properties/Tags.kt @@ -5,6 +5,7 @@ import core.utility.apply @kotlinx.serialization.Serializable data class Tags(private val tags: MutableList = mutableListOf()) { constructor(vararg tags: String) : this(tags.toMutableList()) + constructor(vararg tags: TagKey) : this(tags.map { it.name }.toMutableList()) constructor(base: Tags, params: Map = mapOf()) : this(base.tags.apply(params).toMutableList()) override fun toString(): String { @@ -93,4 +94,4 @@ data class Tags(private val tags: MutableList = mutableListOf()) { return tags.asSequence().map { it.lowercase() }.toList() } -} \ No newline at end of file +} diff --git a/src/commonMain/kotlin/core/properties/ValueKey.kt b/src/commonMain/kotlin/core/properties/ValueKey.kt new file mode 100644 index 000000000..1b5327a0e --- /dev/null +++ b/src/commonMain/kotlin/core/properties/ValueKey.kt @@ -0,0 +1,5 @@ +package core.properties + +enum class ValueKey() { + GOAL +} diff --git a/src/commonMain/kotlin/core/properties/Values.kt b/src/commonMain/kotlin/core/properties/Values.kt index 2a1fa40ed..8709b7921 100644 --- a/src/commonMain/kotlin/core/properties/Values.kt +++ b/src/commonMain/kotlin/core/properties/Values.kt @@ -54,6 +54,7 @@ data class Values(private val properties: MutableMap = mutableMa return default } + fun getString(key: ValueKey, default: String = "") = getString(key.name, default) fun getString(key: String, default: String = ""): String { return properties[key.lowercase()] ?: default } @@ -120,4 +121,4 @@ data class Values(private val properties: MutableMap = mutableMa put(key.lowercase(), (getInt(key, 0) + amount).toString()) } -} \ No newline at end of file +} diff --git a/src/commonMain/kotlin/resources/ai/agenda/CommonAgendas.kt b/src/commonMain/kotlin/resources/ai/agenda/CommonAgendas.kt index 33b418b72..557b7f90c 100644 --- a/src/commonMain/kotlin/resources/ai/agenda/CommonAgendas.kt +++ b/src/commonMain/kotlin/resources/ai/agenda/CommonAgendas.kt @@ -8,6 +8,7 @@ import core.ai.agenda.AgendaResource import core.ai.agenda.agendas import core.ai.knowledge.DiscoverFactEvent import core.ai.knowledge.Fact +import core.ai.knowledge.FactKind import core.ai.knowledge.Subject import core.thing.Thing import core.utility.RandomManager @@ -137,7 +138,7 @@ class CommonAgendas : AgendaResource { agendaAction("Search For Food") { owner -> val target = (owner.inventory.getItems() + owner.location.getLocation().getItems(perceivedBy = owner)).firstOrNull { it.properties.tags.has("Food") } target?.let { - owner.discover(target, "useTarget") + owner.discover(target, FactKind.USE_TARGET.name) } } @@ -149,7 +150,7 @@ class CommonAgendas : AgendaResource { actions("Find Bed") { owner -> owner.mind.knowsThingByKind("MyBed")?.let { target -> listOf( - owner.discover(target, "useTarget"), + owner.discover(target, FactKind.USE_TARGET.name), owner.discover(target.location, "LocationGoal") ) } @@ -190,10 +191,7 @@ class CommonAgendas : AgendaResource { InteractEvent(creature, target) } } - - } - } private fun Thing.discover(target: Thing, kind: String): DiscoverFactEvent { @@ -218,4 +216,3 @@ private suspend fun clawAttack(target: Thing, creature: Thing): AttackEvent { } return startAttack(creature, partToAttackWith, ThingAim(GameState.player.thing, thingPart), DamageType.SLASH) } - diff --git a/src/commonMain/kotlin/resources/ai/desire/CommonDesires.kt b/src/commonMain/kotlin/resources/ai/desire/CommonDesires.kt index c2f4e4d05..badbb4813 100644 --- a/src/commonMain/kotlin/resources/ai/desire/CommonDesires.kt +++ b/src/commonMain/kotlin/resources/ai/desire/CommonDesires.kt @@ -3,10 +3,11 @@ package resources.ai.desire import core.GameState import core.ai.desire.DesireResource import core.ai.desire.desires +import core.ai.packages.perceivedActivators +import core.ai.packages.perceivedCreatures +import core.ai.packages.perceivedItems import core.commands.CommandParsers -import core.thing.Thing import status.stat.STAMINA -import time.TimeManager class CommonDesires : DesireResource { override suspend fun values() = desires { @@ -52,30 +53,18 @@ class CommonDesires : DesireResource { agenda("Rest") } - cond({ s -> s.items().firstOrNull { it.properties.tags.has("Food") } != null }) { + cond({ s -> s.perceivedItems().firstOrNull { it.properties.tags.has("Food") } != null }) { agenda("Eat Food") } - cond(20, { s -> s.activators().firstOrNull { it.name.contains("Tree") } != null }) { + cond(20, { s -> s.perceivedActivators().firstOrNull { it.name.contains("Tree") } != null }) { agenda("Scratch Tree") } //Eventually use factions + actions to create how much something likes something else - cond({ s -> s.creatures().firstOrNull { !it.properties.tags.has("Predator") } != null }) { + cond({ s -> s.perceivedCreatures().firstOrNull { !it.properties.tags.has("Predator") } != null }) { additionalPriority = 10 agenda("Hunt") } } } } - -private suspend fun Thing.creatures(): List { - return location.getLocation().getCreatures(perceivedBy = this).filter { it != this } -} - -private suspend fun Thing.activators(): List { - return location.getLocation().getActivators(perceivedBy = this).filter { it != this } -} - -private suspend fun Thing.items(): List { - return location.getLocation().getItems(perceivedBy = this).filter { it != this } -} diff --git a/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt b/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt index 67957ebf6..f91842c7e 100644 --- a/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt +++ b/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt @@ -3,10 +3,6 @@ package resources.ai.packages import core.GameState import core.ai.packages.* import status.rest.RestEvent -import status.stat.STAMINA -import traveling.move.startMoveEvent -import use.eat.EatFoodEvent -import use.interaction.nothing.NothingEvent class CommonPackages : AIPackageTemplateResource { override val values = aiPackages { @@ -16,6 +12,13 @@ class CommonPackages : AIPackageTemplateResource { cond { !GameState.timeManager.isNight() } act { RestEvent(it, 2) } } + + idea("Scratch Tree") { + //TODO + } + idea("Hunt") { + //TODO + } } } } diff --git a/src/commonMain/kotlin/resources/ai/packages/CommonerPackage.kt b/src/commonMain/kotlin/resources/ai/packages/CommonerPackage.kt index 01ae1a3a8..5762b7af0 100644 --- a/src/commonMain/kotlin/resources/ai/packages/CommonerPackage.kt +++ b/src/commonMain/kotlin/resources/ai/packages/CommonerPackage.kt @@ -1,23 +1,29 @@ package resources.ai.packages -import core.GameState import core.ai.packages.AIPackageTemplateResource import core.ai.packages.aiPackages -import core.ai.packages.setUseGoal -import use.interaction.nothing.NothingEvent class CommonerPackage : AIPackageTemplateResource { override val values = aiPackages { 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.setUseGoal(target, "eat") - } ?: NothingEvent(owner) - } + idea("Eat Food"){ + //TODO + } + idea("Converse"){ + //TODO + } + idea("Travel to Job Site"){ + //TODO + } + idea("Work"){ + //TODO + } + idea("Sleep in Bed"){ + //TODO + } + idea("Rest"){ + //TODO } } } diff --git a/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt b/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt index d70b68702..9689faf24 100644 --- a/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt +++ b/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt @@ -1,15 +1,17 @@ package resources.ai.packages -import core.ai.packages.AIPackageTemplateResource -import core.ai.packages.aiPackages -import core.ai.packages.canReachGoal -import core.ai.packages.clawAttack -import core.ai.packages.clearUseGoal -import core.ai.packages.hasUseTarget +import conversation.dsl.hasTag +import core.GameState +import core.ai.knowledge.HowToUse +import core.ai.knowledge.clearUseGoal +import core.ai.knowledge.setUseTarget +import core.ai.packages.* +import core.properties.TagKey import status.rest.RestEvent import status.stat.STAMINA import traveling.move.startMoveEvent import use.eat.EatFoodEvent +import use.interaction.nothing.NothingEvent class CreaturePackage : AIPackageTemplateResource { override val values = aiPackages { @@ -35,13 +37,27 @@ class CreaturePackage : AIPackageTemplateResource { act { startMoveEvent(it, destination = it.mind.getUseTargetThing()!!.position) } } + //TODO - Maybe last eaten + amount of time + idea("Want Food") { + cond { s -> + GameState.timeManager.getPercentDayComplete() in listOf(25, 50, 75) && + s.perceivedItemsAndInventory().firstOrNull { it.hasTag(TagKey.FOOD) } != null + } + act { s -> + s.perceivedItemsAndInventory().firstOrNull { it.hasTag(TagKey.FOOD.name) } + ?.let { s.setUseTarget(it, HowToUse.EAT) } + ?: NothingEvent(s) + } + } + idea("Eat Targeted Food", 50) { - cond { it.canReachGoal("eat") } + cond { it.canReachGoal(HowToUse.EAT) } act { EatFoodEvent(it, it.mind.getUseTargetThing()!!) it.clearUseGoal() } } + idea("Wander") { act { val target = it.location.getLocation().getThings(it).random() From 474bcb4a009d14c93dd9b10436e474bf4afb8a02 Mon Sep 17 00:00:00 2001 From: ManApart Date: Tue, 30 Dec 2025 13:57:28 -0500 Subject: [PATCH 19/45] use fact kind instead of string --- .../kotlin/core/ai/knowledge/Fact.kt | 5 +++- .../kotlin/core/ai/knowledge/Mind.kt | 28 ++++++++++++------- .../resources/ai/agenda/CommonAgendas.kt | 2 +- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/commonMain/kotlin/core/ai/knowledge/Fact.kt b/src/commonMain/kotlin/core/ai/knowledge/Fact.kt index af15e1f9e..5ebf4639c 100644 --- a/src/commonMain/kotlin/core/ai/knowledge/Fact.kt +++ b/src/commonMain/kotlin/core/ai/knowledge/Fact.kt @@ -9,7 +9,10 @@ data class ListFact(val kind: String, val sources: List, val props: Pro } enum class FactKind { - USE_TARGET + AGGRO_TARGET, + LOCATION, + RECIPE, + USE_TARGET, } enum class HowToUse { diff --git a/src/commonMain/kotlin/core/ai/knowledge/Mind.kt b/src/commonMain/kotlin/core/ai/knowledge/Mind.kt index 5642d5de6..05809c462 100644 --- a/src/commonMain/kotlin/core/ai/knowledge/Mind.kt +++ b/src/commonMain/kotlin/core/ai/knowledge/Mind.kt @@ -20,14 +20,17 @@ data class Mind( ai.creature = creature } + fun knows(kind: FactKind) = knows(kind.name) fun knows(kind: String): ListFact? { return memory.getListFact(kind) } + fun knows(source: Subject, kind: FactKind) = knows(source, kind.name) fun knows(source: Subject, kind: String): Fact? { return memory.getFact(source, kind) } + suspend fun knowsThingByKind(kind: FactKind) = knowsThingByKind(kind.name) suspend fun knowsThingByKind(kind: String): Thing? { return memory.getSubjects(kind).firstNotNullOfOrNull { it.getThing() } } @@ -44,14 +47,19 @@ data class Mind( return knowsLocationByKind(kind) != null } + fun learn(source: Subject, kind: FactKind) = learn(source, kind.name) fun learn(source: Subject, kind: String) { val fact = memory.getFact(source, kind) ?: Fact(source, kind) learn(fact) } + fun learn(kind: FactKind, addition: Subject) = learn(kind.name, listOf(addition)) fun learn(kind: String, addition: Subject) = learn(kind, listOf(addition)) + @JvmName("learnTopicsEnum") + fun learn(kind: FactKind, additions: List) = learn(kind.name, additions) @JvmName("learnTopics") fun learn(kind: String, additions: List) = learn(kind, additions.map { Subject(topic = it) }) + fun learn(kind: FactKind, additions: List) = learn(kind.name, additions) fun learn(kind: String, additions: List) { val existing = memory.getListFact(kind) val fact = ListFact(kind, additions.toList() + (existing?.sources ?: listOf())) @@ -71,43 +79,43 @@ data class Mind( } fun knows(location: LocationNode): Boolean { - val fact = knows("Location") ?: return false + val fact = knows(FactKind.LOCATION) ?: return false return fact.sources.contains(Subject(location)) } fun knows(recipe: Recipe): Boolean { - val fact = knows("Recipe") ?: return false + val fact = knows(FactKind.RECIPE) ?: return false return fact.sources.contains(Subject(recipe.name)) } fun discover(location: LocationNode) { - learn("Location", Subject(location)) + learn(FactKind.LOCATION, Subject(location)) } fun discover(recipe: Recipe) { - learn("Recipe", Subject(recipe.name)) + learn(FactKind.RECIPE, Subject(recipe.name)) } fun setAggroTarget(enemy: Thing) { - learn(Fact(Subject(enemy), "aggroTarget")) + learn(Fact(Subject(enemy), FactKind.AGGRO_TARGET.name)) } suspend fun getAggroTarget(): Thing? { - return knowsThingByKind("aggroTarget") + return knowsThingByKind(FactKind.AGGRO_TARGET) } suspend fun clearAggroTarget() { getAggroTarget()?.let { - memory.forget(Fact(Subject(it), "aggroTarget")) + memory.forget(Fact(Subject(it), FactKind.AGGRO_TARGET.name)) } } suspend fun getUseTargetThing(): Thing? { - return knowsThingByKind("useTarget") + return knowsThingByKind(FactKind.USE_TARGET) } fun getUseTarget(): Fact? { - return memory.getFirstFact("useTarget") + return memory.getFirstFact(FactKind.USE_TARGET.name) } -} \ No newline at end of file +} diff --git a/src/commonMain/kotlin/resources/ai/agenda/CommonAgendas.kt b/src/commonMain/kotlin/resources/ai/agenda/CommonAgendas.kt index 557b7f90c..f60ea4c28 100644 --- a/src/commonMain/kotlin/resources/ai/agenda/CommonAgendas.kt +++ b/src/commonMain/kotlin/resources/ai/agenda/CommonAgendas.kt @@ -131,7 +131,7 @@ class CommonAgendas : AgendaResource { agendaAction("Search For Enemy") { owner -> val target = owner.location.getLocation().getCreatures(perceivedBy = owner).firstOrNull { !it.properties.tags.has("Predator") } target?.let { - owner.discover(target, "aggroTarget") + owner.discover(target, FactKind.AGGRO_TARGET.name) } } From 770267fdf9d215d89d4b36bdd169ebf914e0d4f2 Mon Sep 17 00:00:00 2001 From: ManApart Date: Tue, 30 Dec 2025 14:12:25 -0500 Subject: [PATCH 20/45] find tree --- src/commonMain/kotlin/core/ai/knowledge/Fact.kt | 1 + src/commonMain/kotlin/core/ai/packages/IdeaBuilder.kt | 5 +++++ .../kotlin/resources/ai/packages/CommonPackages.kt | 9 +++++++++ .../kotlin/resources/ai/packages/CreaturePackage.kt | 6 ++---- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/commonMain/kotlin/core/ai/knowledge/Fact.kt b/src/commonMain/kotlin/core/ai/knowledge/Fact.kt index 5ebf4639c..3784ef711 100644 --- a/src/commonMain/kotlin/core/ai/knowledge/Fact.kt +++ b/src/commonMain/kotlin/core/ai/knowledge/Fact.kt @@ -11,6 +11,7 @@ data class ListFact(val kind: String, val sources: List, val props: Pro enum class FactKind { AGGRO_TARGET, LOCATION, + LOCATION_GOAL, RECIPE, USE_TARGET, } diff --git a/src/commonMain/kotlin/core/ai/packages/IdeaBuilder.kt b/src/commonMain/kotlin/core/ai/packages/IdeaBuilder.kt index aa6910262..c1bee3fc1 100644 --- a/src/commonMain/kotlin/core/ai/packages/IdeaBuilder.kt +++ b/src/commonMain/kotlin/core/ai/packages/IdeaBuilder.kt @@ -2,6 +2,7 @@ package core.ai.packages import core.events.Event import core.thing.Thing +import use.interaction.nothing.NothingEvent class IdeaBuilder(val name: String, val priority: Int) { internal var criteria: suspend (Thing) -> Boolean = { true } @@ -21,6 +22,10 @@ class IdeaBuilder(val name: String, val priority: Int) { this.action = { listOf(action(it)) } } + fun actOrNot(action: suspend (Thing) -> Event?) { + this.action = { listOf(action(it) ?: NothingEvent(it)) } + } + } class IdeasBuilder { diff --git a/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt b/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt index f91842c7e..86beea374 100644 --- a/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt +++ b/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt @@ -1,6 +1,8 @@ package resources.ai.packages import core.GameState +import core.ai.knowledge.FactKind +import core.ai.knowledge.discover import core.ai.packages.* import status.rest.RestEvent @@ -13,6 +15,13 @@ class CommonPackages : AIPackageTemplateResource { act { RestEvent(it, 2) } } + idea("Find Tree to Scratch") { + cond { s -> s.perceivedActivators().any { it.name.contains("Tree") } } + actOrNot { s -> + s.perceivedActivators().firstOrNull { it.name.contains("Tree") }?.let { s.discover(it, FactKind.USE_TARGET) } + } + } + idea("Scratch Tree") { //TODO } diff --git a/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt b/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt index 9689faf24..b4a88578e 100644 --- a/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt +++ b/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt @@ -11,7 +11,6 @@ import status.rest.RestEvent import status.stat.STAMINA import traveling.move.startMoveEvent import use.eat.EatFoodEvent -import use.interaction.nothing.NothingEvent class CreaturePackage : AIPackageTemplateResource { override val values = aiPackages { @@ -43,14 +42,13 @@ class CreaturePackage : AIPackageTemplateResource { GameState.timeManager.getPercentDayComplete() in listOf(25, 50, 75) && s.perceivedItemsAndInventory().firstOrNull { it.hasTag(TagKey.FOOD) } != null } - act { s -> + actOrNot { s -> s.perceivedItemsAndInventory().firstOrNull { it.hasTag(TagKey.FOOD.name) } ?.let { s.setUseTarget(it, HowToUse.EAT) } - ?: NothingEvent(s) } } - idea("Eat Targeted Food", 50) { + idea("Eat Targeted Food", 45) { cond { it.canReachGoal(HowToUse.EAT) } act { EatFoodEvent(it, it.mind.getUseTargetThing()!!) From 1efc3c73e83d78abffef542bc0a0772d06676f89 Mon Sep 17 00:00:00 2001 From: ManApart Date: Tue, 30 Dec 2025 14:18:24 -0500 Subject: [PATCH 21/45] attack tree --- .../kotlin/core/ai/knowledge/Fact.kt | 3 ++- .../resources/ai/packages/CommonPackages.kt | 23 ++++++++++++++----- .../resources/ai/packages/CreaturePackage.kt | 6 ++--- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/commonMain/kotlin/core/ai/knowledge/Fact.kt b/src/commonMain/kotlin/core/ai/knowledge/Fact.kt index 3784ef711..a063e10f4 100644 --- a/src/commonMain/kotlin/core/ai/knowledge/Fact.kt +++ b/src/commonMain/kotlin/core/ai/knowledge/Fact.kt @@ -17,5 +17,6 @@ enum class FactKind { } enum class HowToUse { - EAT + EAT, + ATTACK } diff --git a/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt b/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt index 86beea374..575c0fdb0 100644 --- a/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt +++ b/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt @@ -1,9 +1,14 @@ package resources.ai.packages import core.GameState -import core.ai.knowledge.FactKind -import core.ai.knowledge.discover -import core.ai.packages.* +import core.ai.knowledge.HowToUse +import core.ai.knowledge.clearUseGoal +import core.ai.knowledge.setUseTarget +import core.ai.packages.AIPackageTemplateResource +import core.ai.packages.aiPackages +import core.ai.packages.canReachGoal +import core.ai.packages.clawAttack +import core.ai.packages.perceivedActivators import status.rest.RestEvent class CommonPackages : AIPackageTemplateResource { @@ -18,12 +23,18 @@ class CommonPackages : AIPackageTemplateResource { idea("Find Tree to Scratch") { cond { s -> s.perceivedActivators().any { it.name.contains("Tree") } } actOrNot { s -> - s.perceivedActivators().firstOrNull { it.name.contains("Tree") }?.let { s.discover(it, FactKind.USE_TARGET) } + s.perceivedActivators().firstOrNull { it.name.contains("Tree") }?.let { s.setUseTarget(it, HowToUse.ATTACK) } } } - idea("Scratch Tree") { - //TODO + idea("Attack Goal") { + cond { it.canReachGoal(HowToUse.ATTACK) } + actions { s -> + listOfNotNull( + s.mind.getUseTargetThing()?.let { clawAttack(it, s) }, + s.clearUseGoal() + ) + } } idea("Hunt") { //TODO diff --git a/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt b/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt index b4a88578e..5f6aa93be 100644 --- a/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt +++ b/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt @@ -50,9 +50,9 @@ class CreaturePackage : AIPackageTemplateResource { idea("Eat Targeted Food", 45) { cond { it.canReachGoal(HowToUse.EAT) } - act { - EatFoodEvent(it, it.mind.getUseTargetThing()!!) - it.clearUseGoal() + actions { s -> + listOfNotNull(s.mind.getUseTargetThing()?.let { EatFoodEvent(s, it) }, + s.clearUseGoal()) } } From af2a3edfbcbf7a77915b13d8256f40ee1ee10b1f Mon Sep 17 00:00:00 2001 From: ManApart Date: Tue, 30 Dec 2025 14:34:28 -0500 Subject: [PATCH 22/45] predator packages --- .../kotlin/core/ai/packages/AIPackageHelpers.kt | 1 + src/commonMain/kotlin/core/ai/packages/Idea.kt | 2 +- .../kotlin/core/ai/packages/IdeaBuilder.kt | 9 ++------- .../kotlin/resources/ai/packages/CommonPackages.kt | 12 ++++++++++-- .../kotlin/resources/ai/packages/CommonerPackage.kt | 12 ++++++------ .../kotlin/resources/ai/packages/CreaturePackage.kt | 10 +++++++--- 6 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/commonMain/kotlin/core/ai/packages/AIPackageHelpers.kt b/src/commonMain/kotlin/core/ai/packages/AIPackageHelpers.kt index 8fb96e975..c914786c4 100644 --- a/src/commonMain/kotlin/core/ai/packages/AIPackageHelpers.kt +++ b/src/commonMain/kotlin/core/ai/packages/AIPackageHelpers.kt @@ -17,6 +17,7 @@ import core.utility.RandomManager import traveling.position.ThingAim +suspend fun Thing.hasAggroTarget() = mind.getAggroTarget() != null suspend fun Thing.hasUseTarget() = mind.getUseTargetThing() != null suspend fun Thing.canReachGoal(howToUse: HowToUse ) = canReachGoal(howToUse.name) diff --git a/src/commonMain/kotlin/core/ai/packages/Idea.kt b/src/commonMain/kotlin/core/ai/packages/Idea.kt index 22e2b2f44..52aa58167 100644 --- a/src/commonMain/kotlin/core/ai/packages/Idea.kt +++ b/src/commonMain/kotlin/core/ai/packages/Idea.kt @@ -5,6 +5,6 @@ import core.thing.Thing import use.interaction.nothing.NothingEvent -val DO_NOTHING_IDEA = Idea("Do Nothing", 0,{true}, { listOf(NothingEvent(it))}) +val DO_NOTHING_IDEA = Idea("Do Nothing", 0, { true }, { listOf(NothingEvent(it)) }) data class Idea(val name: String, val priority: Int, val criteria: suspend (Thing) -> Boolean, val action: suspend (Thing) -> List) diff --git a/src/commonMain/kotlin/core/ai/packages/IdeaBuilder.kt b/src/commonMain/kotlin/core/ai/packages/IdeaBuilder.kt index c1bee3fc1..614ed829e 100644 --- a/src/commonMain/kotlin/core/ai/packages/IdeaBuilder.kt +++ b/src/commonMain/kotlin/core/ai/packages/IdeaBuilder.kt @@ -18,14 +18,9 @@ class IdeaBuilder(val name: String, val priority: Int) { this.action = action } - fun act(action: suspend (Thing) -> Event) { - this.action = { listOf(action(it)) } + fun act(action: suspend (Thing) -> Event?) { + this.action = { listOfNotNull(action(it)) } } - - fun actOrNot(action: suspend (Thing) -> Event?) { - this.action = { listOf(action(it) ?: NothingEvent(it)) } - } - } class IdeasBuilder { diff --git a/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt b/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt index 575c0fdb0..7bfa01372 100644 --- a/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt +++ b/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt @@ -1,14 +1,18 @@ package resources.ai.packages +import conversation.dsl.hasTag import core.GameState +import core.ai.knowledge.FactKind import core.ai.knowledge.HowToUse import core.ai.knowledge.clearUseGoal +import core.ai.knowledge.discover import core.ai.knowledge.setUseTarget import core.ai.packages.AIPackageTemplateResource import core.ai.packages.aiPackages import core.ai.packages.canReachGoal import core.ai.packages.clawAttack import core.ai.packages.perceivedActivators +import core.ai.packages.perceivedCreatures import status.rest.RestEvent class CommonPackages : AIPackageTemplateResource { @@ -22,7 +26,7 @@ class CommonPackages : AIPackageTemplateResource { idea("Find Tree to Scratch") { cond { s -> s.perceivedActivators().any { it.name.contains("Tree") } } - actOrNot { s -> + act { s -> s.perceivedActivators().firstOrNull { it.name.contains("Tree") }?.let { s.setUseTarget(it, HowToUse.ATTACK) } } } @@ -37,7 +41,11 @@ class CommonPackages : AIPackageTemplateResource { } } idea("Hunt") { - //TODO + cond { s -> s.perceivedCreatures().any { !it.hasTag("Predator") } } + act { s -> + s.perceivedCreatures().firstOrNull { !it.hasTag("Predator") } + ?.let { s.discover(it, FactKind.AGGRO_TARGET) } + } } } } diff --git a/src/commonMain/kotlin/resources/ai/packages/CommonerPackage.kt b/src/commonMain/kotlin/resources/ai/packages/CommonerPackage.kt index 5762b7af0..4f9d5dc8d 100644 --- a/src/commonMain/kotlin/resources/ai/packages/CommonerPackage.kt +++ b/src/commonMain/kotlin/resources/ai/packages/CommonerPackage.kt @@ -7,22 +7,22 @@ class CommonerPackage : AIPackageTemplateResource { override val values = aiPackages { aiPackage("Commoner") { template("Creature") - idea("Eat Food"){ + idea("Eat Food") { //TODO } - idea("Converse"){ + idea("Converse") { //TODO } - idea("Travel to Job Site"){ + idea("Travel to Job Site") { //TODO } - idea("Work"){ + idea("Work") { //TODO } - idea("Sleep in Bed"){ + idea("Sleep in Bed") { //TODO } - idea("Rest"){ + idea("Rest") { //TODO } } diff --git a/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt b/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt index 5f6aa93be..5cbbef560 100644 --- a/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt +++ b/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt @@ -22,7 +22,7 @@ class CreaturePackage : AIPackageTemplateResource { } idea("Attack", 70) { - cond { it.mind.getAggroTarget() != null } + cond { it.mind.getAggroTarget() != null && it.canReach(it.mind.getAggroTarget()!!.position) } act { clawAttack(it.mind.getAggroTarget()!!, it) } @@ -35,6 +35,10 @@ class CreaturePackage : AIPackageTemplateResource { cond { it.hasUseTarget() && !it.canReach(it.mind.getUseTargetThing()!!.position) } act { startMoveEvent(it, destination = it.mind.getUseTargetThing()!!.position) } } + idea("Move to Aggro Target", 70) { + cond { it.hasAggroTarget() && !it.canReach(it.mind.getAggroTarget()!!.position) } + act { startMoveEvent(it, destination = it.mind.getAggroTarget()!!.position) } + } //TODO - Maybe last eaten + amount of time idea("Want Food") { @@ -42,8 +46,8 @@ class CreaturePackage : AIPackageTemplateResource { GameState.timeManager.getPercentDayComplete() in listOf(25, 50, 75) && s.perceivedItemsAndInventory().firstOrNull { it.hasTag(TagKey.FOOD) } != null } - actOrNot { s -> - s.perceivedItemsAndInventory().firstOrNull { it.hasTag(TagKey.FOOD.name) } + act { s -> + s.perceivedItemsAndInventory().firstOrNull { it.hasTag(TagKey.FOOD) } ?.let { s.setUseTarget(it, HowToUse.EAT) } } } From 9400e25423d419218cd601a38f3aea47c3c2f48d Mon Sep 17 00:00:00 2001 From: ManApart Date: Tue, 30 Dec 2025 15:12:15 -0500 Subject: [PATCH 23/45] travel package --- .../kotlin/crafting/RecipeManager.kt | 5 +-- .../resources/ai/packages/CreaturePackage.kt | 34 ++++++++++++++++--- .../kotlin/traveling/arrive/Arrive.kt | 7 +++- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/commonMain/kotlin/crafting/RecipeManager.kt b/src/commonMain/kotlin/crafting/RecipeManager.kt index 9ff1631a2..4d1704df6 100644 --- a/src/commonMain/kotlin/crafting/RecipeManager.kt +++ b/src/commonMain/kotlin/crafting/RecipeManager.kt @@ -5,6 +5,7 @@ import core.DependencyInjector import core.GameState import core.Player import core.ai.AIManager +import core.ai.knowledge.FactKind import core.startupLog import core.thing.Thing import core.utility.Backer @@ -44,7 +45,7 @@ object RecipeManager { suspend fun getKnownRecipes(source: Player): NameSearchableList { return if (GameState.getDebugBoolean(DebugType.RECIPE_SHOW_ALL)) getAllRecipes() else { - source.mind.memory.getListFact("Recipe")?.sources + source.mind.memory.getListFact(FactKind.RECIPE.name)?.sources ?.mapNotNull { it.topic }?.let { getRecipes(it) }?.toNameSearchableList() ?: NameSearchableList() } @@ -61,4 +62,4 @@ object RecipeManager { suspend fun findRecipes(crafter: Thing, ingredients: List, tool: Thing?): List { return getRecipes().filter { it.matches(crafter, ingredients, tool) } } -} \ No newline at end of file +} diff --git a/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt b/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt index 5cbbef560..69688df11 100644 --- a/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt +++ b/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt @@ -9,7 +9,10 @@ import core.ai.packages.* import core.properties.TagKey import status.rest.RestEvent import status.stat.STAMINA +import traveling.location.RouteFinder import traveling.move.startMoveEvent +import traveling.routes.FindRouteEvent +import traveling.travel.TravelStartEvent import use.eat.EatFoodEvent class CreaturePackage : AIPackageTemplateResource { @@ -28,13 +31,32 @@ class CreaturePackage : AIPackageTemplateResource { } } - //TODO - need move to location as well - //If usetarget in different location, create map to get there - // map available, move to location + idea("Start Travel") { + cond { s -> + (s.mind.getAggroTarget() ?: s.mind.getUseTargetThing()) + ?.let { s.location != it.location } ?: false + } + act { s -> + val goal = (s.mind.getAggroTarget() ?: s.mind.getUseTargetThing())?.location ?: return@act null + val dest = RouteFinder(s.location, goal).getRoute().getNextStep(s.location).destination.location + + FindRouteEvent(s, s.location, dest, startImmediately = true) + } + } + + idea("Travel", 60) { + cond { s -> s.mind.route != null } + act { s -> + s.mind.route?.getNextStep(s.location)?.destination?.location + ?.let { TravelStartEvent(s, destination = it) } + } + } + idea("Move to Use Target", 50) { cond { it.hasUseTarget() && !it.canReach(it.mind.getUseTargetThing()!!.position) } act { startMoveEvent(it, destination = it.mind.getUseTargetThing()!!.position) } } + idea("Move to Aggro Target", 70) { cond { it.hasAggroTarget() && !it.canReach(it.mind.getAggroTarget()!!.position) } act { startMoveEvent(it, destination = it.mind.getAggroTarget()!!.position) } @@ -55,8 +77,10 @@ class CreaturePackage : AIPackageTemplateResource { idea("Eat Targeted Food", 45) { cond { it.canReachGoal(HowToUse.EAT) } actions { s -> - listOfNotNull(s.mind.getUseTargetThing()?.let { EatFoodEvent(s, it) }, - s.clearUseGoal()) + listOfNotNull( + s.mind.getUseTargetThing()?.let { EatFoodEvent(s, it) }, + s.clearUseGoal() + ) } } diff --git a/src/commonMain/kotlin/traveling/arrive/Arrive.kt b/src/commonMain/kotlin/traveling/arrive/Arrive.kt index 8839c5c68..0a9ace3fe 100644 --- a/src/commonMain/kotlin/traveling/arrive/Arrive.kt +++ b/src/commonMain/kotlin/traveling/arrive/Arrive.kt @@ -33,8 +33,13 @@ class Arrive : EventListener() { } } } + creature.mind.route?.let { route -> + if (route.destination == player.location){ + creature.mind.route = null + } + } } } } -} \ No newline at end of file +} From 88ec205cf3c938601f5b6cd9dfb8ff7f387fdec1 Mon Sep 17 00:00:00 2001 From: ManApart Date: Tue, 30 Dec 2025 15:53:41 -0500 Subject: [PATCH 24/45] all actions copied over --- .../kotlin/core/ai/knowledge/Fact.kt | 9 ++- .../kotlin/core/ai/knowledge/Mind.kt | 17 ++--- .../core/ai/packages/AIPackageHelpers.kt | 13 +++- .../packages/AIPackageTemplatesGenerated.kt | 4 +- .../resources/ai/agenda/CommonAgendas.kt | 10 +-- .../resources/ai/desire/CommonDesires.kt | 2 +- .../resources/ai/packages/CommonerPackage.kt | 30 --------- .../resources/ai/packages/CreaturePackage.kt | 20 ++++-- .../resources/ai/packages/PeasantPackage.kt | 63 +++++++++++++++++++ 9 files changed, 113 insertions(+), 55 deletions(-) delete mode 100644 src/commonMain/kotlin/resources/ai/packages/CommonerPackage.kt create mode 100644 src/commonMain/kotlin/resources/ai/packages/PeasantPackage.kt diff --git a/src/commonMain/kotlin/core/ai/knowledge/Fact.kt b/src/commonMain/kotlin/core/ai/knowledge/Fact.kt index a063e10f4..a171cd1c5 100644 --- a/src/commonMain/kotlin/core/ai/knowledge/Fact.kt +++ b/src/commonMain/kotlin/core/ai/knowledge/Fact.kt @@ -10,13 +10,20 @@ data class ListFact(val kind: String, val sources: List, val props: Pro enum class FactKind { AGGRO_TARGET, + HOME, LOCATION, LOCATION_GOAL, + MY_BED, + MY_HOME, + MY_WORKPLACE, RECIPE, USE_TARGET, + WORK_TAGS, } enum class HowToUse { EAT, - ATTACK + ATTACK, + SLEEP, + INTERACT, } diff --git a/src/commonMain/kotlin/core/ai/knowledge/Mind.kt b/src/commonMain/kotlin/core/ai/knowledge/Mind.kt index 05809c462..b3c94a672 100644 --- a/src/commonMain/kotlin/core/ai/knowledge/Mind.kt +++ b/src/commonMain/kotlin/core/ai/knowledge/Mind.kt @@ -30,21 +30,22 @@ data class Mind( return memory.getFact(source, kind) } - suspend fun knowsThingByKind(kind: FactKind) = knowsThingByKind(kind.name) - suspend fun knowsThingByKind(kind: String): Thing? { + suspend fun knownThingByKind(kind: FactKind) = knownThingByKind(kind.name) + suspend fun knownThingByKind(kind: String): Thing? { return memory.getSubjects(kind).firstNotNullOfOrNull { it.getThing() } } suspend fun thingByKindExists(kind: String): Boolean { - return knowsThingByKind(kind) != null + return knownThingByKind(kind) != null } - - fun knowsLocationByKind(kind: String): LocationNode? { + fun knownLocationByKind(kind: FactKind) = knownLocationByKind(kind.name) + fun knownLocationByKind(kind: String): LocationNode? { return memory.getSubjects(kind).firstNotNullOfOrNull { it.getLocation() } } + fun locationByKindExists(kind: FactKind) = locationByKindExists(kind.name) fun locationByKindExists(kind: String): Boolean { - return knowsLocationByKind(kind) != null + return knownLocationByKind(kind) != null } fun learn(source: Subject, kind: FactKind) = learn(source, kind.name) @@ -101,7 +102,7 @@ data class Mind( } suspend fun getAggroTarget(): Thing? { - return knowsThingByKind(FactKind.AGGRO_TARGET) + return knownThingByKind(FactKind.AGGRO_TARGET) } suspend fun clearAggroTarget() { @@ -111,7 +112,7 @@ data class Mind( } suspend fun getUseTargetThing(): Thing? { - return knowsThingByKind(FactKind.USE_TARGET) + return knownThingByKind(FactKind.USE_TARGET) } fun getUseTarget(): Fact? { diff --git a/src/commonMain/kotlin/core/ai/packages/AIPackageHelpers.kt b/src/commonMain/kotlin/core/ai/packages/AIPackageHelpers.kt index c914786c4..dff41cc87 100644 --- a/src/commonMain/kotlin/core/ai/packages/AIPackageHelpers.kt +++ b/src/commonMain/kotlin/core/ai/packages/AIPackageHelpers.kt @@ -14,14 +14,17 @@ import core.properties.ValueKey import core.properties.Values import core.thing.Thing import core.utility.RandomManager +import traveling.location.RouteFinder +import traveling.location.network.LocationNode import traveling.position.ThingAim +import traveling.routes.FindRouteEvent suspend fun Thing.hasAggroTarget() = mind.getAggroTarget() != null suspend fun Thing.hasUseTarget() = mind.getUseTargetThing() != null -suspend fun Thing.canReachGoal(howToUse: HowToUse ) = canReachGoal(howToUse.name) -suspend fun Thing.canReachGoal(howToUse: String ): Boolean { +suspend fun Thing.canReachGoal(howToUse: HowToUse) = canReachGoal(howToUse.name) +suspend fun Thing.canReachGoal(howToUse: String): Boolean { val useTarget = mind.getUseTarget() return useTarget != null && useTarget.props.values.getString(ValueKey.GOAL) == howToUse && useTarget.source.getThing()?.position?.let { pos -> canReach(pos) } ?: false } @@ -56,3 +59,9 @@ suspend fun Thing.perceivedItems(): List { suspend fun Thing.perceivedItemsAndInventory(): List { return (inventory.getItems() + location.getLocation().getItems(perceivedBy = this)).filter { it != this } } + +fun plotRouteAndStartTravel(s: Thing, goal: LocationNode): FindRouteEvent { + val dest = RouteFinder(s.location, goal).getRoute().getNextStep(s.location).destination.location + + return FindRouteEvent(s, s.location, dest, startImmediately = true) +} diff --git a/src/commonMain/kotlin/core/ai/packages/AIPackageTemplatesGenerated.kt b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplatesGenerated.kt index f29fdd1e4..0003ccf27 100644 --- a/src/commonMain/kotlin/core/ai/packages/AIPackageTemplatesGenerated.kt +++ b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplatesGenerated.kt @@ -1,5 +1,5 @@ package core.ai.packages class AIPackageTemplatesGenerated : AIPackageTemplatesCollection { - override val values by lazy { listOf(resources.ai.packages.CommonPackages(), resources.ai.packages.CommonerPackage(), resources.ai.packages.CreaturePackage()).flatMap { it.values }} -} \ No newline at end of file + override val values by lazy { listOf(resources.ai.packages.CommonPackages(), resources.ai.packages.PeasantPackage(), resources.ai.packages.CreaturePackage()).flatMap { it.values }} +} diff --git a/src/commonMain/kotlin/resources/ai/agenda/CommonAgendas.kt b/src/commonMain/kotlin/resources/ai/agenda/CommonAgendas.kt index f60ea4c28..95d058dec 100644 --- a/src/commonMain/kotlin/resources/ai/agenda/CommonAgendas.kt +++ b/src/commonMain/kotlin/resources/ai/agenda/CommonAgendas.kt @@ -67,18 +67,18 @@ class CommonAgendas : AgendaResource { agenda("Travel to Location") { actionDetailed("Identify Route") { shouldSkip { s -> - val goal = s.mind.knowsLocationByKind("LocationGoal") + val goal = s.mind.knownLocationByKind("LocationGoal") goal == null || s.location == goal || s.mind.route?.destination == goal } result { s -> - s.mind.knowsLocationByKind("LocationGoal")?.let { + s.mind.knownLocationByKind("LocationGoal")?.let { FindRouteEvent(s, s.location, it) } } } actionDetailed("Travel to Location") { shouldSkip { s -> - s.location == s.mind.knowsLocationByKind("LocationGoal") + s.location == s.mind.knownLocationByKind("LocationGoal") } result { s -> s.mind.route?.let { @@ -148,7 +148,7 @@ class CommonAgendas : AgendaResource { agenda("Sleep In Bed") { actions("Find Bed") { owner -> - owner.mind.knowsThingByKind("MyBed")?.let { target -> + owner.mind.knownThingByKind("MyBed")?.let { target -> listOf( owner.discover(target, FactKind.USE_TARGET.name), owner.discover(target.location, "LocationGoal") @@ -167,7 +167,7 @@ class CommonAgendas : AgendaResource { agenda("Travel to Job Site") { action("Find Work Site") { owner -> - owner.mind.knowsLocationByKind("MyWorkplace")?.let { target -> + owner.mind.knownLocationByKind("MyWorkplace")?.let { target -> owner.discover(target, "LocationGoal") } } diff --git a/src/commonMain/kotlin/resources/ai/desire/CommonDesires.kt b/src/commonMain/kotlin/resources/ai/desire/CommonDesires.kt index badbb4813..474d2b71b 100644 --- a/src/commonMain/kotlin/resources/ai/desire/CommonDesires.kt +++ b/src/commonMain/kotlin/resources/ai/desire/CommonDesires.kt @@ -34,7 +34,7 @@ class CommonDesires : DesireResource { } cond({ s -> GameState.timeManager.isWorkHours() && s.mind.locationByKindExists("MyWorkplace") }) { - cond({ s -> s.location != s.mind.knowsLocationByKind("MyWorkplace") }) { + cond({ s -> s.location != s.mind.knownLocationByKind("MyWorkplace") }) { agenda("Travel to Job Site") } agenda("Work At Job Site") diff --git a/src/commonMain/kotlin/resources/ai/packages/CommonerPackage.kt b/src/commonMain/kotlin/resources/ai/packages/CommonerPackage.kt deleted file mode 100644 index 4f9d5dc8d..000000000 --- a/src/commonMain/kotlin/resources/ai/packages/CommonerPackage.kt +++ /dev/null @@ -1,30 +0,0 @@ -package resources.ai.packages - -import core.ai.packages.AIPackageTemplateResource -import core.ai.packages.aiPackages - -class CommonerPackage : AIPackageTemplateResource { - override val values = aiPackages { - aiPackage("Commoner") { - template("Creature") - idea("Eat Food") { - //TODO - } - idea("Converse") { - //TODO - } - idea("Travel to Job Site") { - //TODO - } - idea("Work") { - //TODO - } - idea("Sleep in Bed") { - //TODO - } - idea("Rest") { - //TODO - } - } - } -} diff --git a/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt b/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt index 69688df11..950672489 100644 --- a/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt +++ b/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt @@ -9,11 +9,10 @@ import core.ai.packages.* import core.properties.TagKey import status.rest.RestEvent import status.stat.STAMINA -import traveling.location.RouteFinder import traveling.move.startMoveEvent -import traveling.routes.FindRouteEvent import traveling.travel.TravelStartEvent import use.eat.EatFoodEvent +import use.interaction.InteractEvent class CreaturePackage : AIPackageTemplateResource { override val values = aiPackages { @@ -38,9 +37,7 @@ class CreaturePackage : AIPackageTemplateResource { } act { s -> val goal = (s.mind.getAggroTarget() ?: s.mind.getUseTargetThing())?.location ?: return@act null - val dest = RouteFinder(s.location, goal).getRoute().getNextStep(s.location).destination.location - - FindRouteEvent(s, s.location, dest, startImmediately = true) + plotRouteAndStartTravel(s, goal) } } @@ -63,6 +60,7 @@ class CreaturePackage : AIPackageTemplateResource { } //TODO - Maybe last eaten + amount of time + //TODO - second version to go look for food idea("Want Food") { cond { s -> GameState.timeManager.getPercentDayComplete() in listOf(25, 50, 75) && @@ -74,7 +72,7 @@ class CreaturePackage : AIPackageTemplateResource { } } - idea("Eat Targeted Food", 45) { + idea("Eat Food", 45) { cond { it.canReachGoal(HowToUse.EAT) } actions { s -> listOfNotNull( @@ -84,6 +82,16 @@ class CreaturePackage : AIPackageTemplateResource { } } + idea("Interact", 40) { + cond { it.canReachGoal(HowToUse.INTERACT) } + actions { s -> + listOfNotNull( + s.mind.getUseTargetThing()?.let { InteractEvent(s, it) }, + s.clearUseGoal() + ) + } + } + idea("Wander") { act { val target = it.location.getLocation().getThings(it).random() diff --git a/src/commonMain/kotlin/resources/ai/packages/PeasantPackage.kt b/src/commonMain/kotlin/resources/ai/packages/PeasantPackage.kt new file mode 100644 index 000000000..e82fc6807 --- /dev/null +++ b/src/commonMain/kotlin/resources/ai/packages/PeasantPackage.kt @@ -0,0 +1,63 @@ +package resources.ai.packages + +import core.GameState +import core.ai.knowledge.FactKind +import core.ai.knowledge.HowToUse +import core.ai.knowledge.setUseTarget +import core.ai.packages.AIPackageTemplateResource +import core.ai.packages.aiPackages +import core.ai.packages.canReachGoal +import core.ai.packages.perceivedActivators +import core.ai.packages.plotRouteAndStartTravel +import core.commands.CommandParsers + +class PeasantPackage : AIPackageTemplateResource { + override val values = aiPackages { + aiPackage("Commoner") { + template("Creature") + idea("Converse") { + cond { s -> CommandParsers.getConversations().any { it.containsParticipant(s) }} + } + idea("Go to Work") { + cond { s -> + val place = s.mind.knownLocationByKind(FactKind.MY_WORKPLACE) + GameState.timeManager.isWorkHours() && place != null && s.location != place + } + act { s -> + s.mind.knownLocationByKind(FactKind.MY_WORKPLACE)?.let { plotRouteAndStartTravel(s, it) } + } + } + idea("Go Home") { + cond { s -> + val place = s.mind.knownLocationByKind(FactKind.MY_HOME) + !GameState.timeManager.isWorkHours() && place != null && s.location != place + } + act { s -> + s.mind.knownLocationByKind(FactKind.MY_HOME)?.let { plotRouteAndStartTravel(s, it) } + } + } + idea("Work") { + cond { s -> + GameState.timeManager.isWorkHours() + && s.location == s.mind.knownLocationByKind(FactKind.MY_WORKPLACE) + && (s.mind.knows(FactKind.WORK_TAGS)?.sources?.mapNotNull { it.topic }?.isNotEmpty() ?: false) + } + act { s -> + s.mind.knows(FactKind.WORK_TAGS)?.sources?.mapNotNull { it.topic }?.let { tags -> + s.perceivedActivators().firstOrNull { it.properties.tags.hasAll(tags) }?.let { s.setUseTarget(it, HowToUse.INTERACT) } + } + } + } + idea("Want to Sleep in Bed") { + cond { s -> GameState.timeManager.isNight() && s.mind.knownThingByKind(FactKind.MY_BED) != null } + act { s -> s.mind.knownThingByKind(FactKind.MY_BED)?.let { s.setUseTarget(it, HowToUse.SLEEP) } } + } + idea("Sleep in Bed") { + cond { s -> + GameState.timeManager.isNight() && s.canReachGoal(HowToUse.SLEEP) + } + + } + } + } +} From 0353cf3ca880f8752c15c8c4ea9b2ffc3338eb30 Mon Sep 17 00:00:00 2001 From: ManApart Date: Tue, 30 Dec 2025 19:41:57 -0500 Subject: [PATCH 25/45] prep to swap in new AI --- .../ai/{AIManager2.kt => AIPackageManager.kt} | 9 ++++-- .../kotlin/core/ai/PackageBasedAI.kt | 30 +++++++++++++++++++ .../kotlin/core/properties/TagKey.kt | 3 +- .../kotlin/core/thing/ThingBuilder.kt | 16 ++++++++-- .../kotlin/explore/look/DescribeMind.kt | 12 +++++++- .../kotlin/validation/AIPackageValidator.kt | 5 ++-- 6 files changed, 66 insertions(+), 9 deletions(-) rename src/commonMain/kotlin/core/ai/{AIManager2.kt => AIPackageManager.kt} (89%) create mode 100644 src/commonMain/kotlin/core/ai/PackageBasedAI.kt diff --git a/src/commonMain/kotlin/core/ai/AIManager2.kt b/src/commonMain/kotlin/core/ai/AIPackageManager.kt similarity index 89% rename from src/commonMain/kotlin/core/ai/AIManager2.kt rename to src/commonMain/kotlin/core/ai/AIPackageManager.kt index 40317b5d6..93150580c 100644 --- a/src/commonMain/kotlin/core/ai/AIManager2.kt +++ b/src/commonMain/kotlin/core/ai/AIPackageManager.kt @@ -7,7 +7,11 @@ import core.ai.packages.AIPackageTemplatesCollection import core.startupLog import core.utility.lazyM -object AIManager2 { +object AIPackageKeys { + const val CREATURE = "Creature" +} + +object AIPackageManager { var aiPackages by lazyM { loadAIPackages() } private set @@ -21,5 +25,4 @@ object AIManager2 { fun reset() { aiPackages = loadAIPackages() } - -} \ No newline at end of file +} diff --git a/src/commonMain/kotlin/core/ai/PackageBasedAI.kt b/src/commonMain/kotlin/core/ai/PackageBasedAI.kt new file mode 100644 index 000000000..e0f194bee --- /dev/null +++ b/src/commonMain/kotlin/core/ai/PackageBasedAI.kt @@ -0,0 +1,30 @@ +package core.ai + +import conversation.ConversationManager +import conversation.dialogue.DialogueEvent +import core.ai.packages.AIPackage +import core.events.EventManager +import core.history.display +import core.utility.RandomManager + +class PackageBasedAI(val aiPackage: AIPackage) : AI() { + val previousIdeas = mutableListOf() + override fun toString(): String { + return "AI for ${creature.name} using ${aiPackage.name}" + } + + override suspend fun takeAction() { + aiPackage.pickIdea(creature) + .also { previousIdeas.add(it.name) } + .action(creature).forEach { EventManager.postEvent(it) } + } + + override suspend fun hear(event: DialogueEvent) { + event.speaker.display(event.line) + val matches = ConversationManager.getMatchingDialogue(event.conversation) + val priority = matches.maxOf { it.priority } + val topMatches = matches.filter { it.priority == priority } + val response = RandomManager.getRandom(topMatches) + response.result(event.conversation).forEach { EventManager.postEvent(it) } + } +} diff --git a/src/commonMain/kotlin/core/properties/TagKey.kt b/src/commonMain/kotlin/core/properties/TagKey.kt index 68bb35bc8..dfec55c9c 100644 --- a/src/commonMain/kotlin/core/properties/TagKey.kt +++ b/src/commonMain/kotlin/core/properties/TagKey.kt @@ -1,5 +1,6 @@ package core.properties enum class TagKey { - FOOD + FOOD, + CREATURE, } diff --git a/src/commonMain/kotlin/core/thing/ThingBuilder.kt b/src/commonMain/kotlin/core/thing/ThingBuilder.kt index d456914a3..f91797af8 100644 --- a/src/commonMain/kotlin/core/thing/ThingBuilder.kt +++ b/src/commonMain/kotlin/core/thing/ThingBuilder.kt @@ -68,7 +68,7 @@ class ThingBuilder(internal val name: String) { val allItems = itemNames + bases.flatMap { it.itemNames } val inventory = Inventory(name, body) inventory.addAllByName(allItems) - val ai = ai ?: basesR.firstNotNullOfOrNull { it.ai } ?: if (tagsToApply.contains(CREATURE_TAG)) ConditionalAI() else DumbAI() + val ai = ai ?: basesR.firstNotNullOfOrNull { it.ai } ?: discernAI(tagsToApply) val mindParsed = mindP?.let { Mind(ai, CreatureMemory(mindP!!.facts.map { it.parsed() }, mindP!!.listFacts.map { it.parsed() })) } val mind = this.mind ?: mindParsed ?: basesR.firstNotNullOfOrNull { it.mind } ?: Mind(ai) mind.mindInitializer() @@ -154,6 +154,10 @@ class ThingBuilder(internal val name: String) { this.ai = ConditionalAI() } + fun packageAI(packageName: String) { + this.ai = PackageBasedAI(AIPackageManager.aiPackages[packageName]!!) + } + fun dumbAI() { this.ai = DumbAI() } @@ -252,8 +256,16 @@ class ThingBuilder(internal val name: String) { }.also { bodyCustomizer.apply(it) } } + private fun discernAI(tags: List) : AI { + return when { +// tags.contains(CREATURE_TAG) -> PackageBasedAI(AIPackageManager.aiPackages[AIPackageKeys.CREATURE]!!) + tags.contains(CREATURE_TAG) -> ConditionalAI() + else -> DumbAI() + } + } + } suspend fun thing(name: String, initializer: suspend ThingBuilder.() -> Unit): ThingBuilder { return ThingBuilder(name).applySuspending(initializer) -} \ No newline at end of file +} diff --git a/src/commonMain/kotlin/explore/look/DescribeMind.kt b/src/commonMain/kotlin/explore/look/DescribeMind.kt index bc6ddf371..494b0f4f3 100644 --- a/src/commonMain/kotlin/explore/look/DescribeMind.kt +++ b/src/commonMain/kotlin/explore/look/DescribeMind.kt @@ -4,6 +4,7 @@ import core.GameState import core.Player import core.ai.ConditionalAI import core.ai.DumbAI +import core.ai.PackageBasedAI import core.ai.PlayerControlledAI import core.ai.knowledge.Mind import core.history.displayToMe @@ -16,6 +17,7 @@ fun describeMind(source: Player, thing: Thing, mind: Mind) { mind.ai is DumbAI -> source.displayToMe("${thing.getDisplayName()} is without thought.") !GameState.getDebugBoolean(DebugType.CLARITY) -> source.displayToMe("You are unable to perceive the thought of ${thing.getDisplayName()}.") mind.ai is ConditionalAI -> ponderThing(source, thing, mind.ai) + mind.ai is PackageBasedAI -> ponderThing(source, thing, mind.ai) else -> source.displayToMe("You are unable to perceive the thought of ${thing.getDisplayName()}.") } } @@ -31,4 +33,12 @@ private fun ponderThing(source: Player, thing: Thing, ai: ConditionalAI) { } source.displayToMe(message) } -} \ No newline at end of file +} + +private fun ponderThing(source: Player, thing: Thing, ai: PackageBasedAI) { + var message = thing.getDisplayName() + " has take the following actions (top first)" + with(ai) { + message += "\n\t" + ai.previousIdeas.reversed().joinToString("\n\t") + source.displayToMe(message) + } +} diff --git a/src/jvmTestIntegration/kotlin/validation/AIPackageValidator.kt b/src/jvmTestIntegration/kotlin/validation/AIPackageValidator.kt index 048e499e5..b6b68ae8a 100644 --- a/src/jvmTestIntegration/kotlin/validation/AIPackageValidator.kt +++ b/src/jvmTestIntegration/kotlin/validation/AIPackageValidator.kt @@ -2,17 +2,18 @@ package validation import building.ModManager import core.DependencyInjector -import core.ai.AIManager2 +import core.ai.AIPackageManager import core.ai.packages.AIPackageTemplatesCollection import kotlinx.coroutines.runBlocking import kotlin.test.Test import kotlin.test.assertEquals //TODO validate all things/minds reference valid package name +// - Check things builder class AIPackageValidator { - private val packages = runBlocking { AIManager2.aiPackages } + private val packages = runBlocking { AIPackageManager.aiPackages } private val templates = (DependencyInjector.getImplementation(AIPackageTemplatesCollection::class).values + ModManager.ai2) @Test From 2034a548fa90af6cdea62a5414c2eb217f2c879a Mon Sep 17 00:00:00 2001 From: ManApart Date: Tue, 30 Dec 2025 20:09:55 -0500 Subject: [PATCH 26/45] centralize magic strings --- .../conversation/dsl/DialogueBuilder.kt | 2 - src/commonMain/kotlin/core/GameManager.kt | 6 +-- src/commonMain/kotlin/core/MagicStrings.kt | 41 +++++++++++++++++++ .../kotlin/core/ai/AIPackageManager.kt | 4 -- .../core/ai/knowledge/DiscoverFactEvent.kt | 11 +++-- .../kotlin/core/ai/knowledge/Fact.kt | 20 --------- .../core/ai/knowledge/ForgetFactEvent.kt | 3 +- .../kotlin/core/ai/knowledge/Mind.kt | 17 ++------ .../core/ai/packages/AIPackageHelpers.kt | 10 +---- .../kotlin/core/properties/Properties.kt | 7 +--- .../kotlin/core/properties/TagKey.kt | 6 --- src/commonMain/kotlin/core/properties/Tags.kt | 1 - .../kotlin/core/properties/ValueKey.kt | 5 --- .../kotlin/core/properties/Values.kt | 1 - src/commonMain/kotlin/core/thing/Thing.kt | 25 ++++------- .../kotlin/core/thing/ThingBuilder.kt | 8 ++-- .../core/thing/creature/CreatureManager.kt | 7 ++-- .../kotlin/crafting/RecipeManager.kt | 6 +-- .../kotlin/explore/listen/Listen.kt | 6 +-- .../kotlin/explore/listen/SoundTools.kt | 3 +- .../resources/ai/agenda/CommonAgendas.kt | 8 ++-- .../resources/ai/packages/CommonPackages.kt | 11 ++--- .../resources/ai/packages/CreaturePackage.kt | 4 +- .../resources/ai/packages/PeasantPackage.kt | 4 +- .../status/statChanged/PlayerStatMinned.kt | 6 +-- .../location/LocationRecipeBuilder.kt | 6 +-- .../location/weather/WeatherBuilder.kt | 6 +-- src/jvmTest/kotlin/TestConstructors.kt | 5 +-- .../kotlin/system/ActivatorManagerTest.kt | 6 +-- .../location/network/NetworksMock.kt | 6 +-- 30 files changed, 106 insertions(+), 145 deletions(-) create mode 100644 src/commonMain/kotlin/core/MagicStrings.kt delete mode 100644 src/commonMain/kotlin/core/properties/TagKey.kt delete mode 100644 src/commonMain/kotlin/core/properties/ValueKey.kt diff --git a/src/commonMain/kotlin/conversation/dsl/DialogueBuilder.kt b/src/commonMain/kotlin/conversation/dsl/DialogueBuilder.kt index 5dc3acb47..bfa75f628 100644 --- a/src/commonMain/kotlin/conversation/dsl/DialogueBuilder.kt +++ b/src/commonMain/kotlin/conversation/dsl/DialogueBuilder.kt @@ -5,7 +5,6 @@ import conversation.dialogue.DialogueEvent import conversation.parsing.QuestionType import conversation.parsing.Verb import core.events.Event -import core.properties.TagKey import core.thing.Thing import core.utility.Named import core.utility.applySuspending @@ -83,7 +82,6 @@ suspend fun Conversation.subjects(): List? { } //TODO - move this to thing and location node -suspend fun Named?.hasTag(tag: TagKey) = hasTag(tag.name) suspend fun Named?.hasTag(tag: String): Boolean { return if (this != null) { (this is LocationNode && getLocation().properties.tags.has(tag)) || diff --git a/src/commonMain/kotlin/core/GameManager.kt b/src/commonMain/kotlin/core/GameManager.kt index a0488cc62..a04276bba 100644 --- a/src/commonMain/kotlin/core/GameManager.kt +++ b/src/commonMain/kotlin/core/GameManager.kt @@ -1,5 +1,7 @@ package core +import core.NetworkKeys.PLAYER_START_LOCATION +import core.NetworkKeys.PLAYER_START_NETWORK import core.commands.CommandParsers import core.events.EventManager import core.history.GameLogger @@ -18,10 +20,6 @@ import traveling.location.location.LocationManager import traveling.location.location.LocationPoint import traveling.location.network.LocationNode -const val PLAYER_START_NETWORK = "Kanbara Countryside" -const val PLAYER_START_LOCATION = "An Open Field" - - object GameManager { var playing = false diff --git a/src/commonMain/kotlin/core/MagicStrings.kt b/src/commonMain/kotlin/core/MagicStrings.kt new file mode 100644 index 000000000..c70a05888 --- /dev/null +++ b/src/commonMain/kotlin/core/MagicStrings.kt @@ -0,0 +1,41 @@ +package core + +object AIPackageKeys { + const val CREATURE = "Creature" +} + +object TagKey { + const val CREATURE = "Creature" + const val FOOD = "Food" + const val SOUND_DESCRIPTION = "Sound Description" + const val SOUND_LEVEL = "Sound Level" +} + +object ValueKey { + const val GOAL = "Goal" +} + +object HowToUse { + const val EAT = "Eat" + const val ATTACK = "Attack" + const val SLEEP = "Sleep" + const val INTERACT = "Interact" +} + +object FactKind { + const val AGGRO_TARGET = "AggroTarget" + const val HOME = "Home" + const val LOCATION = "Location" + const val LOCATION_GOAL = "LocationGoal" + const val MY_BED = "MyBed" + const val MY_HOME = "MyHome" + const val MY_WORKPLACE = "MyWorkplace" + const val RECIPE = "Recipe" + const val USE_TARGET = "UseTarget" + const val WORK_TAGS = "WorkTags" +} + +object NetworkKeys { + const val PLAYER_START_NETWORK = "Kanbara Countryside" + const val PLAYER_START_LOCATION = "An Open Field" +} diff --git a/src/commonMain/kotlin/core/ai/AIPackageManager.kt b/src/commonMain/kotlin/core/ai/AIPackageManager.kt index 93150580c..3b43ee347 100644 --- a/src/commonMain/kotlin/core/ai/AIPackageManager.kt +++ b/src/commonMain/kotlin/core/ai/AIPackageManager.kt @@ -7,10 +7,6 @@ import core.ai.packages.AIPackageTemplatesCollection import core.startupLog import core.utility.lazyM -object AIPackageKeys { - const val CREATURE = "Creature" -} - object AIPackageManager { var aiPackages by lazyM { loadAIPackages() } private set diff --git a/src/commonMain/kotlin/core/ai/knowledge/DiscoverFactEvent.kt b/src/commonMain/kotlin/core/ai/knowledge/DiscoverFactEvent.kt index b69776bd7..ca2ca540f 100644 --- a/src/commonMain/kotlin/core/ai/knowledge/DiscoverFactEvent.kt +++ b/src/commonMain/kotlin/core/ai/knowledge/DiscoverFactEvent.kt @@ -1,23 +1,22 @@ package core.ai.knowledge +import core.FactKind +import core.ValueKey import core.events.Event -import core.properties.ValueKey -import core.properties.props +import core.properties.Properties import core.thing.Thing import traveling.location.network.LocationNode data class DiscoverFactEvent(val source: Thing, val fact: Fact) : Event -fun Thing.discover(target: Thing, kind: FactKind) = discover(target, kind.name) fun Thing.discover(target: Thing, kind: String): DiscoverFactEvent { return DiscoverFactEvent(this, Fact(Subject(target), kind)) } -fun Thing.setUseTarget(target: Thing, howToUse: HowToUse): DiscoverFactEvent { - return DiscoverFactEvent(this, Fact(Subject(target), FactKind.USE_TARGET.name, props(ValueKey.GOAL to howToUse.name))) +fun Thing.setUseTarget(target: Thing, howToUse: String): DiscoverFactEvent { + return DiscoverFactEvent(this, Fact(Subject(target), FactKind.USE_TARGET, Properties(ValueKey.GOAL to howToUse))) } -fun Thing.discover(target: LocationNode, kind: FactKind) = discover(target, kind.name) fun Thing.discover(target: LocationNode, kind: String): DiscoverFactEvent { return DiscoverFactEvent(this, Fact(Subject(target), kind)) } diff --git a/src/commonMain/kotlin/core/ai/knowledge/Fact.kt b/src/commonMain/kotlin/core/ai/knowledge/Fact.kt index a171cd1c5..e25686f4f 100644 --- a/src/commonMain/kotlin/core/ai/knowledge/Fact.kt +++ b/src/commonMain/kotlin/core/ai/knowledge/Fact.kt @@ -7,23 +7,3 @@ data class Fact(val source: Subject, val kind: String, val props: Properties = P data class ListFact(val kind: String, val sources: List, val props: Properties = Properties()) { constructor(kind: String, source: Subject, props: Properties = Properties()) : this(kind, listOf(source), props) } - -enum class FactKind { - AGGRO_TARGET, - HOME, - LOCATION, - LOCATION_GOAL, - MY_BED, - MY_HOME, - MY_WORKPLACE, - RECIPE, - USE_TARGET, - WORK_TAGS, -} - -enum class HowToUse { - EAT, - ATTACK, - SLEEP, - INTERACT, -} diff --git a/src/commonMain/kotlin/core/ai/knowledge/ForgetFactEvent.kt b/src/commonMain/kotlin/core/ai/knowledge/ForgetFactEvent.kt index b5897d660..8b82432d3 100644 --- a/src/commonMain/kotlin/core/ai/knowledge/ForgetFactEvent.kt +++ b/src/commonMain/kotlin/core/ai/knowledge/ForgetFactEvent.kt @@ -1,5 +1,6 @@ package core.ai.knowledge +import core.FactKind import core.events.Event import core.thing.Thing @@ -10,5 +11,5 @@ data class ForgetFactEvent(val source: Thing, val fact: Fact? = null, val listFa } fun Thing.clearUseGoal(): ForgetFactEvent { - return ForgetFactEvent(this, kind = FactKind.USE_TARGET.name) + return ForgetFactEvent(this, kind = FactKind.USE_TARGET) } diff --git a/src/commonMain/kotlin/core/ai/knowledge/Mind.kt b/src/commonMain/kotlin/core/ai/knowledge/Mind.kt index b3c94a672..c17cb0495 100644 --- a/src/commonMain/kotlin/core/ai/knowledge/Mind.kt +++ b/src/commonMain/kotlin/core/ai/knowledge/Mind.kt @@ -1,5 +1,6 @@ package core.ai.knowledge +import core.FactKind import core.ai.AI import core.ai.DumbAI import core.thing.Thing @@ -20,17 +21,14 @@ data class Mind( ai.creature = creature } - fun knows(kind: FactKind) = knows(kind.name) fun knows(kind: String): ListFact? { return memory.getListFact(kind) } - fun knows(source: Subject, kind: FactKind) = knows(source, kind.name) fun knows(source: Subject, kind: String): Fact? { return memory.getFact(source, kind) } - suspend fun knownThingByKind(kind: FactKind) = knownThingByKind(kind.name) suspend fun knownThingByKind(kind: String): Thing? { return memory.getSubjects(kind).firstNotNullOfOrNull { it.getThing() } } @@ -38,29 +36,22 @@ data class Mind( suspend fun thingByKindExists(kind: String): Boolean { return knownThingByKind(kind) != null } - fun knownLocationByKind(kind: FactKind) = knownLocationByKind(kind.name) fun knownLocationByKind(kind: String): LocationNode? { return memory.getSubjects(kind).firstNotNullOfOrNull { it.getLocation() } } - fun locationByKindExists(kind: FactKind) = locationByKindExists(kind.name) fun locationByKindExists(kind: String): Boolean { return knownLocationByKind(kind) != null } - fun learn(source: Subject, kind: FactKind) = learn(source, kind.name) fun learn(source: Subject, kind: String) { val fact = memory.getFact(source, kind) ?: Fact(source, kind) learn(fact) } - fun learn(kind: FactKind, addition: Subject) = learn(kind.name, listOf(addition)) fun learn(kind: String, addition: Subject) = learn(kind, listOf(addition)) - @JvmName("learnTopicsEnum") - fun learn(kind: FactKind, additions: List) = learn(kind.name, additions) @JvmName("learnTopics") fun learn(kind: String, additions: List) = learn(kind, additions.map { Subject(topic = it) }) - fun learn(kind: FactKind, additions: List) = learn(kind.name, additions) fun learn(kind: String, additions: List) { val existing = memory.getListFact(kind) val fact = ListFact(kind, additions.toList() + (existing?.sources ?: listOf())) @@ -98,7 +89,7 @@ data class Mind( } fun setAggroTarget(enemy: Thing) { - learn(Fact(Subject(enemy), FactKind.AGGRO_TARGET.name)) + learn(Fact(Subject(enemy), FactKind.AGGRO_TARGET)) } suspend fun getAggroTarget(): Thing? { @@ -107,7 +98,7 @@ data class Mind( suspend fun clearAggroTarget() { getAggroTarget()?.let { - memory.forget(Fact(Subject(it), FactKind.AGGRO_TARGET.name)) + memory.forget(Fact(Subject(it), FactKind.AGGRO_TARGET)) } } @@ -116,7 +107,7 @@ data class Mind( } fun getUseTarget(): Fact? { - return memory.getFirstFact(FactKind.USE_TARGET.name) + return memory.getFirstFact(FactKind.USE_TARGET) } } diff --git a/src/commonMain/kotlin/core/ai/packages/AIPackageHelpers.kt b/src/commonMain/kotlin/core/ai/packages/AIPackageHelpers.kt index dff41cc87..b57363f22 100644 --- a/src/commonMain/kotlin/core/ai/packages/AIPackageHelpers.kt +++ b/src/commonMain/kotlin/core/ai/packages/AIPackageHelpers.kt @@ -4,14 +4,7 @@ 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.ForgetFactEvent -import core.ai.knowledge.HowToUse -import core.ai.knowledge.Subject -import core.properties.Properties -import core.properties.ValueKey -import core.properties.Values +import core.ValueKey import core.thing.Thing import core.utility.RandomManager import traveling.location.RouteFinder @@ -23,7 +16,6 @@ import traveling.routes.FindRouteEvent suspend fun Thing.hasAggroTarget() = mind.getAggroTarget() != null suspend fun Thing.hasUseTarget() = mind.getUseTargetThing() != null -suspend fun Thing.canReachGoal(howToUse: HowToUse) = canReachGoal(howToUse.name) suspend fun Thing.canReachGoal(howToUse: String): Boolean { val useTarget = mind.getUseTarget() return useTarget != null && useTarget.props.values.getString(ValueKey.GOAL) == howToUse && useTarget.source.getThing()?.position?.let { pos -> canReach(pos) } ?: false diff --git a/src/commonMain/kotlin/core/properties/Properties.kt b/src/commonMain/kotlin/core/properties/Properties.kt index b97015adb..c44e4cbe8 100644 --- a/src/commonMain/kotlin/core/properties/Properties.kt +++ b/src/commonMain/kotlin/core/properties/Properties.kt @@ -1,7 +1,7 @@ package core.properties +import core.TagKey import core.thing.activator.ACTIVATOR_TAG -import core.thing.creature.CREATURE_TAG import core.thing.item.ITEM_TAG import core.utility.wrapNonEmpty import traveling.position.Distances.BOW_RANGE @@ -11,12 +11,9 @@ import traveling.position.Distances.SPEAR_RANGE import traveling.position.Distances.SWORD_RANGE -fun props(vararg values: Pair) = Properties(*values.map { (k,v) -> k.name to v }.toTypedArray()) - data class Properties(val values: Values = Values(), val tags: Tags = Tags()) { constructor(tags: Tags) : this(Values(), tags) constructor(vararg tags: String) : this(Values(), Tags(*tags)) - constructor(vararg tags: TagKey) : this(Values(), Tags(*tags)) constructor(vararg values: Pair) : this(Values(*values), Tags()) constructor(base: Properties, params: Map = mapOf()) : this( Values(base.values, params), @@ -77,7 +74,7 @@ data class Properties(val values: Values = Values(), val tags: Tags = Tags()) { } fun isCreature(): Boolean { - return tags.has(CREATURE_TAG) + return tags.has(TagKey.CREATURE) } fun canBeHeldByContainerWithProperties(containerProperties: Properties): Boolean { diff --git a/src/commonMain/kotlin/core/properties/TagKey.kt b/src/commonMain/kotlin/core/properties/TagKey.kt deleted file mode 100644 index dfec55c9c..000000000 --- a/src/commonMain/kotlin/core/properties/TagKey.kt +++ /dev/null @@ -1,6 +0,0 @@ -package core.properties - -enum class TagKey { - FOOD, - CREATURE, -} diff --git a/src/commonMain/kotlin/core/properties/Tags.kt b/src/commonMain/kotlin/core/properties/Tags.kt index 76cc5f5d2..6118bd5e1 100644 --- a/src/commonMain/kotlin/core/properties/Tags.kt +++ b/src/commonMain/kotlin/core/properties/Tags.kt @@ -5,7 +5,6 @@ import core.utility.apply @kotlinx.serialization.Serializable data class Tags(private val tags: MutableList = mutableListOf()) { constructor(vararg tags: String) : this(tags.toMutableList()) - constructor(vararg tags: TagKey) : this(tags.map { it.name }.toMutableList()) constructor(base: Tags, params: Map = mapOf()) : this(base.tags.apply(params).toMutableList()) override fun toString(): String { diff --git a/src/commonMain/kotlin/core/properties/ValueKey.kt b/src/commonMain/kotlin/core/properties/ValueKey.kt deleted file mode 100644 index 1b5327a0e..000000000 --- a/src/commonMain/kotlin/core/properties/ValueKey.kt +++ /dev/null @@ -1,5 +0,0 @@ -package core.properties - -enum class ValueKey() { - GOAL -} diff --git a/src/commonMain/kotlin/core/properties/Values.kt b/src/commonMain/kotlin/core/properties/Values.kt index 8709b7921..07e83e6f4 100644 --- a/src/commonMain/kotlin/core/properties/Values.kt +++ b/src/commonMain/kotlin/core/properties/Values.kt @@ -54,7 +54,6 @@ data class Values(private val properties: MutableMap = mutableMa return default } - fun getString(key: ValueKey, default: String = "") = getString(key.name, default) fun getString(key: String, default: String = ""): String { return properties[key.lowercase()] ?: default } diff --git a/src/commonMain/kotlin/core/thing/Thing.kt b/src/commonMain/kotlin/core/thing/Thing.kt index b1f3fbf6c..e2e055132 100644 --- a/src/commonMain/kotlin/core/thing/Thing.kt +++ b/src/commonMain/kotlin/core/thing/Thing.kt @@ -1,6 +1,7 @@ package core.thing import core.GameState +import core.GameState.properties import core.ai.AI import core.ai.DumbAI import core.ai.behavior.Behavior @@ -14,12 +15,14 @@ import core.utility.clamp import core.utility.max import explore.listen.getSound import inventory.Inventory +import kotlinx.coroutines.NonDisposableHandle.parent import status.Soul import status.stat.PERCEPTION import status.stat.SNEAK import system.debug.DebugType import traveling.location.Route import traveling.location.location.Location +import traveling.location.location.location import traveling.location.network.LocationNode import traveling.location.network.NOWHERE_NODE import traveling.position.NO_VECTOR @@ -59,23 +62,9 @@ data class Thing( fun getDisplayName(): String { val locationDescription = properties.values.getString("locationDescription") - val description = if (locationDescription.isBlank()) { - "" - } else { - " $locationDescription" - } - - val location = if (position == NO_VECTOR) { - "" - } else { - " $position" - } - - val count = if (properties.getCount() != 1) { - "" + properties.getCount() + "x " - } else { - "" - } + val description = if (locationDescription.isBlank()) "" else " $locationDescription" + val location = if (position == NO_VECTOR) "" else " $position" + val count = if (properties.getCount() != 1) "" + properties.getCount() + "x " else "" return count + name + description + location } @@ -260,4 +249,4 @@ suspend fun List.perceivedBy(source: Thing): List { fun List.toThingString(): String { return joinToString(", ") { it.getDisplayName() } -} \ No newline at end of file +} diff --git a/src/commonMain/kotlin/core/thing/ThingBuilder.kt b/src/commonMain/kotlin/core/thing/ThingBuilder.kt index f91797af8..7bae41ed8 100644 --- a/src/commonMain/kotlin/core/thing/ThingBuilder.kt +++ b/src/commonMain/kotlin/core/thing/ThingBuilder.kt @@ -1,5 +1,8 @@ package core.thing +import core.TagKey +import core.TagKey.SOUND_DESCRIPTION +import core.TagKey.SOUND_LEVEL import core.ai.* import core.ai.behavior.BehaviorManager import core.ai.behavior.BehaviorRecipe @@ -12,7 +15,6 @@ import core.body.BodyManager import core.body.Slot import core.properties.Properties import core.properties.PropsBuilder -import core.thing.creature.CREATURE_TAG import core.utility.MapBuilder import core.utility.apply import core.utility.applyNested @@ -20,8 +22,6 @@ import core.utility.applySuspending import crafting.material.DEFAULT_MATERIAL import crafting.material.Material import crafting.material.MaterialManager -import explore.listen.SOUND_DESCRIPTION -import explore.listen.SOUND_LEVEL import explore.listen.SOUND_LEVEL_DEFAULT import inventory.Inventory import status.Soul @@ -259,7 +259,7 @@ class ThingBuilder(internal val name: String) { private fun discernAI(tags: List) : AI { return when { // tags.contains(CREATURE_TAG) -> PackageBasedAI(AIPackageManager.aiPackages[AIPackageKeys.CREATURE]!!) - tags.contains(CREATURE_TAG) -> ConditionalAI() + tags.contains(TagKey.CREATURE) -> ConditionalAI() else -> DumbAI() } } diff --git a/src/commonMain/kotlin/core/thing/creature/CreatureManager.kt b/src/commonMain/kotlin/core/thing/creature/CreatureManager.kt index b431d05e7..acb552de9 100644 --- a/src/commonMain/kotlin/core/thing/creature/CreatureManager.kt +++ b/src/commonMain/kotlin/core/thing/creature/CreatureManager.kt @@ -2,6 +2,7 @@ package core.thing.creature import building.ModManager import core.DependencyInjector +import core.TagKey import core.ai.AIManager import core.startupLog import core.thing.Thing @@ -14,8 +15,6 @@ import core.utility.toNameSearchableList import status.stat.* import traveling.location.location.LocationThing -const val CREATURE_TAG = "Creature" - object CreatureManager { private val creatures = Backer(::loadCreatures) suspend fun getCreatures() = creatures.get() @@ -27,7 +26,7 @@ object CreatureManager { private suspend fun loadCreatures(): NameSearchableList { startupLog("Loading Creatures.") val collection = DependencyInjector.getImplementation(CreaturesCollection::class) - return (collection.values() + ModManager.creatures).build(CREATURE_TAG).toNameSearchableList() + return (collection.values() + ModManager.creatures).build(TagKey.CREATURE).toNameSearchableList() } private suspend fun getCreature(name: String): Thing { @@ -72,4 +71,4 @@ object CreatureManager { } } -} \ No newline at end of file +} diff --git a/src/commonMain/kotlin/crafting/RecipeManager.kt b/src/commonMain/kotlin/crafting/RecipeManager.kt index 4d1704df6..ea8b250e9 100644 --- a/src/commonMain/kotlin/crafting/RecipeManager.kt +++ b/src/commonMain/kotlin/crafting/RecipeManager.kt @@ -2,15 +2,13 @@ package crafting import building.ModManager import core.DependencyInjector +import core.FactKind import core.GameState import core.Player -import core.ai.AIManager -import core.ai.knowledge.FactKind import core.startupLog import core.thing.Thing import core.utility.Backer import core.utility.NameSearchableList -import core.utility.lazyM import core.utility.toNameSearchableList import system.debug.DebugType @@ -45,7 +43,7 @@ object RecipeManager { suspend fun getKnownRecipes(source: Player): NameSearchableList { return if (GameState.getDebugBoolean(DebugType.RECIPE_SHOW_ALL)) getAllRecipes() else { - source.mind.memory.getListFact(FactKind.RECIPE.name)?.sources + source.mind.memory.getListFact(FactKind.RECIPE)?.sources ?.mapNotNull { it.topic }?.let { getRecipes(it) }?.toNameSearchableList() ?: NameSearchableList() } diff --git a/src/commonMain/kotlin/explore/listen/Listen.kt b/src/commonMain/kotlin/explore/listen/Listen.kt index 7f957b832..12efe5922 100644 --- a/src/commonMain/kotlin/explore/listen/Listen.kt +++ b/src/commonMain/kotlin/explore/listen/Listen.kt @@ -1,5 +1,7 @@ package explore.listen +import core.TagKey.SOUND_DESCRIPTION +import core.TagKey.SOUND_LEVEL import core.events.EventListener import core.history.displayToMe import core.thing.Thing @@ -11,8 +13,6 @@ import traveling.position.NO_VECTOR import traveling.position.Vector import kotlin.math.max -const val SOUND_DESCRIPTION = "Sound Description" -const val SOUND_LEVEL = "Sound Level" const val SOUND_LEVEL_DEFAULT = 5 data class Sound(val description: String, val level: Int, val distance: Vector, val strength: Int) @@ -79,4 +79,4 @@ fun Thing.getSound(source: Thing): Sound? { val strength = max(0, (level * 10) - distance) return Sound(description, level, vector, strength) -} \ No newline at end of file +} diff --git a/src/commonMain/kotlin/explore/listen/SoundTools.kt b/src/commonMain/kotlin/explore/listen/SoundTools.kt index de19e2386..616e63f9c 100644 --- a/src/commonMain/kotlin/explore/listen/SoundTools.kt +++ b/src/commonMain/kotlin/explore/listen/SoundTools.kt @@ -1,6 +1,7 @@ package explore.listen import combat.DamageType +import core.TagKey.SOUND_LEVEL import core.thing.Thing import magic.Element import status.conditions.Condition @@ -21,4 +22,4 @@ fun soundCondition(name: String, description: String, amount: Int, duration: Int fun soundEffect(name: String, description: String, amount: Int, duration: Int): Effect { val base = EffectBase(name, description, SOUND_LEVEL, StatKind.PROP_VAL, StatEffect.BOOST, AmountType.FLAT_NUMBER, DamageType.NONE) return Effect(base, amount, duration) -} \ No newline at end of file +} diff --git a/src/commonMain/kotlin/resources/ai/agenda/CommonAgendas.kt b/src/commonMain/kotlin/resources/ai/agenda/CommonAgendas.kt index 95d058dec..903681ce6 100644 --- a/src/commonMain/kotlin/resources/ai/agenda/CommonAgendas.kt +++ b/src/commonMain/kotlin/resources/ai/agenda/CommonAgendas.kt @@ -3,12 +3,12 @@ package resources.ai.agenda import combat.DamageType import combat.attack.AttackEvent import combat.attack.startAttack +import core.FactKind import core.GameState import core.ai.agenda.AgendaResource import core.ai.agenda.agendas import core.ai.knowledge.DiscoverFactEvent import core.ai.knowledge.Fact -import core.ai.knowledge.FactKind import core.ai.knowledge.Subject import core.thing.Thing import core.utility.RandomManager @@ -131,14 +131,14 @@ class CommonAgendas : AgendaResource { agendaAction("Search For Enemy") { owner -> val target = owner.location.getLocation().getCreatures(perceivedBy = owner).firstOrNull { !it.properties.tags.has("Predator") } target?.let { - owner.discover(target, FactKind.AGGRO_TARGET.name) + owner.discover(target, FactKind.AGGRO_TARGET) } } agendaAction("Search For Food") { owner -> val target = (owner.inventory.getItems() + owner.location.getLocation().getItems(perceivedBy = owner)).firstOrNull { it.properties.tags.has("Food") } target?.let { - owner.discover(target, FactKind.USE_TARGET.name) + owner.discover(target, FactKind.USE_TARGET) } } @@ -150,7 +150,7 @@ class CommonAgendas : AgendaResource { actions("Find Bed") { owner -> owner.mind.knownThingByKind("MyBed")?.let { target -> listOf( - owner.discover(target, FactKind.USE_TARGET.name), + owner.discover(target, FactKind.USE_TARGET), owner.discover(target.location, "LocationGoal") ) } diff --git a/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt b/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt index 7bfa01372..d3d9ebfd6 100644 --- a/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt +++ b/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt @@ -1,18 +1,13 @@ package resources.ai.packages import conversation.dsl.hasTag +import core.FactKind import core.GameState -import core.ai.knowledge.FactKind -import core.ai.knowledge.HowToUse +import core.HowToUse import core.ai.knowledge.clearUseGoal import core.ai.knowledge.discover import core.ai.knowledge.setUseTarget -import core.ai.packages.AIPackageTemplateResource -import core.ai.packages.aiPackages -import core.ai.packages.canReachGoal -import core.ai.packages.clawAttack -import core.ai.packages.perceivedActivators -import core.ai.packages.perceivedCreatures +import core.ai.packages.* import status.rest.RestEvent class CommonPackages : AIPackageTemplateResource { diff --git a/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt b/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt index 950672489..620da03c6 100644 --- a/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt +++ b/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt @@ -2,11 +2,11 @@ package resources.ai.packages import conversation.dsl.hasTag import core.GameState -import core.ai.knowledge.HowToUse +import core.HowToUse +import core.TagKey import core.ai.knowledge.clearUseGoal import core.ai.knowledge.setUseTarget import core.ai.packages.* -import core.properties.TagKey import status.rest.RestEvent import status.stat.STAMINA import traveling.move.startMoveEvent diff --git a/src/commonMain/kotlin/resources/ai/packages/PeasantPackage.kt b/src/commonMain/kotlin/resources/ai/packages/PeasantPackage.kt index e82fc6807..a345d67d3 100644 --- a/src/commonMain/kotlin/resources/ai/packages/PeasantPackage.kt +++ b/src/commonMain/kotlin/resources/ai/packages/PeasantPackage.kt @@ -1,8 +1,8 @@ package resources.ai.packages +import core.FactKind import core.GameState -import core.ai.knowledge.FactKind -import core.ai.knowledge.HowToUse +import core.HowToUse import core.ai.knowledge.setUseTarget import core.ai.packages.AIPackageTemplateResource import core.ai.packages.aiPackages diff --git a/src/commonMain/kotlin/status/statChanged/PlayerStatMinned.kt b/src/commonMain/kotlin/status/statChanged/PlayerStatMinned.kt index 644ef7d9e..56206a495 100644 --- a/src/commonMain/kotlin/status/statChanged/PlayerStatMinned.kt +++ b/src/commonMain/kotlin/status/statChanged/PlayerStatMinned.kt @@ -1,8 +1,8 @@ package status.statChanged import core.GameState -import core.PLAYER_START_LOCATION -import core.PLAYER_START_NETWORK +import core.NetworkKeys.PLAYER_START_LOCATION +import core.NetworkKeys.PLAYER_START_NETWORK import core.Player import core.events.EventListener import core.events.EventManager @@ -50,4 +50,4 @@ class PlayerStatMinned : EventListener() { } } -} \ No newline at end of file +} diff --git a/src/commonMain/kotlin/traveling/location/location/LocationRecipeBuilder.kt b/src/commonMain/kotlin/traveling/location/location/LocationRecipeBuilder.kt index c93769182..8a3b307b0 100644 --- a/src/commonMain/kotlin/traveling/location/location/LocationRecipeBuilder.kt +++ b/src/commonMain/kotlin/traveling/location/location/LocationRecipeBuilder.kt @@ -1,12 +1,12 @@ package traveling.location.location +import core.TagKey.SOUND_DESCRIPTION +import core.TagKey.SOUND_LEVEL import core.conditional.ConditionalString import core.conditional.ConditionalStringBuilder import core.conditional.unBuild import core.properties.Properties import core.properties.PropsBuilder -import explore.listen.SOUND_DESCRIPTION -import explore.listen.SOUND_LEVEL import explore.listen.SOUND_LEVEL_DEFAULT import traveling.scope.LIGHT @@ -184,4 +184,4 @@ fun LocationRecipe.unBuild(): LocationRecipeBuilder { props(properties) slots.forEach { slot(it) } } -} \ No newline at end of file +} diff --git a/src/commonMain/kotlin/traveling/location/weather/WeatherBuilder.kt b/src/commonMain/kotlin/traveling/location/weather/WeatherBuilder.kt index aed628263..164bd31cc 100644 --- a/src/commonMain/kotlin/traveling/location/weather/WeatherBuilder.kt +++ b/src/commonMain/kotlin/traveling/location/weather/WeatherBuilder.kt @@ -1,9 +1,9 @@ package traveling.location.weather +import core.TagKey.SOUND_DESCRIPTION +import core.TagKey.SOUND_LEVEL import core.properties.Properties import core.properties.PropsBuilder -import explore.listen.SOUND_DESCRIPTION -import explore.listen.SOUND_LEVEL import explore.listen.SOUND_LEVEL_DEFAULT import traveling.scope.LIGHT @@ -50,4 +50,4 @@ class WeatherBuilder(private val name: String) { fun weather(name: String, description: String, initializer: WeatherBuilder.() -> Unit): WeatherBuilder { return WeatherBuilder(name).apply { description(description) }.apply(initializer) -} \ No newline at end of file +} diff --git a/src/jvmTest/kotlin/TestConstructors.kt b/src/jvmTest/kotlin/TestConstructors.kt index e572ba0ca..94932b1cf 100644 --- a/src/jvmTest/kotlin/TestConstructors.kt +++ b/src/jvmTest/kotlin/TestConstructors.kt @@ -2,6 +2,7 @@ import conversation.dsl.DialoguesCollection import conversation.dsl.DialoguesMock import core.DependencyInjector import core.GameState +import core.TagKey import core.ai.AIManager import core.ai.agenda.AgendasCollection import core.ai.agenda.AgendasMock @@ -25,7 +26,6 @@ import core.thing.activator.ACTIVATOR_TAG import core.thing.activator.ActivatorManager import core.thing.activator.dsl.ActivatorsCollection import core.thing.activator.dsl.ActivatorsMock -import core.thing.creature.CREATURE_TAG import core.thing.item.ITEM_TAG import core.thing.item.ItemManager import core.thing.item.ItemsCollection @@ -117,7 +117,7 @@ fun createPackMule(strength: Int = 1): Thing { "Pack Mule", body = createInventoryBodyBlocking("Pack Mule"), properties = Properties( Values(STRENGTH to strength.toString()), - Tags(CONTAINER, OPEN, CREATURE_TAG) + Tags(CONTAINER, OPEN, TagKey.CREATURE) ) ) } @@ -189,4 +189,3 @@ fun createMockedGame() { GameLogger.reset() } } - diff --git a/src/jvmTest/kotlin/system/ActivatorManagerTest.kt b/src/jvmTest/kotlin/system/ActivatorManagerTest.kt index ca70db525..d51eb104b 100644 --- a/src/jvmTest/kotlin/system/ActivatorManagerTest.kt +++ b/src/jvmTest/kotlin/system/ActivatorManagerTest.kt @@ -1,8 +1,8 @@ package system import core.DependencyInjector -import core.PLAYER_START_LOCATION -import core.PLAYER_START_NETWORK +import core.NetworkKeys.PLAYER_START_LOCATION +import core.NetworkKeys.PLAYER_START_NETWORK import core.body.* import core.thing.activator.ActivatorManager import core.thing.activator.dsl.ActivatorsCollection @@ -80,4 +80,4 @@ class ActivatorManagerTest { } } -} \ No newline at end of file +} diff --git a/src/jvmTest/kotlin/traveling/location/network/NetworksMock.kt b/src/jvmTest/kotlin/traveling/location/network/NetworksMock.kt index db17e63f7..de513e6c9 100644 --- a/src/jvmTest/kotlin/traveling/location/network/NetworksMock.kt +++ b/src/jvmTest/kotlin/traveling/location/network/NetworksMock.kt @@ -1,11 +1,11 @@ package traveling.location.network -import core.PLAYER_START_LOCATION -import core.PLAYER_START_NETWORK +import core.NetworkKeys.PLAYER_START_LOCATION +import core.NetworkKeys.PLAYER_START_NETWORK class NetworksMock(override val values: List = networks { network(PLAYER_START_NETWORK){ locationNode(PLAYER_START_LOCATION) } } -) : NetworksCollection \ No newline at end of file +) : NetworksCollection From 783a076dbc9dd8f6a2776e1ae4455cba984ba0f8 Mon Sep 17 00:00:00 2001 From: ManApart Date: Wed, 31 Dec 2025 09:46:21 -0500 Subject: [PATCH 27/45] tests passing using new ai --- src/commonMain/kotlin/core/GameManager.kt | 5 +++- src/commonMain/kotlin/core/GameState.kt | 4 +-- src/commonMain/kotlin/core/MagicStrings.kt | 8 ++++-- .../kotlin/core/ai/PackageBasedAI.kt | 2 +- .../kotlin/core/ai/packages/AIPackage.kt | 22 +++++++++++++-- .../kotlin/core/properties/Values.kt | 9 +++--- src/commonMain/kotlin/core/thing/Thing.kt | 6 ---- .../kotlin/core/thing/ThingBuilder.kt | 10 ++++--- .../kotlin/explore/look/DescribeThing.kt | 19 +++++++++---- .../resources/ai/packages/CommonPackages.kt | 28 ++++++++++--------- .../resources/ai/packages/CreaturePackage.kt | 8 ++++-- .../resources/ai/packages/PeasantPackage.kt | 3 +- .../thing/creature/CommonCreatures.kt | 5 ++-- .../kotlin/traveling/move/MoveEvent.kt | 3 +- 14 files changed, 82 insertions(+), 50 deletions(-) diff --git a/src/commonMain/kotlin/core/GameManager.kt b/src/commonMain/kotlin/core/GameManager.kt index a04276bba..d1756f153 100644 --- a/src/commonMain/kotlin/core/GameManager.kt +++ b/src/commonMain/kotlin/core/GameManager.kt @@ -59,7 +59,10 @@ object GameManager { private fun setDefaultProperties(testing: Boolean) { // GameState.properties.values.put(AUTO_SAVE, true) // GameState.putDebug(DebugType.VERBOSE_ACTIONS, testing) -// GameState.putDebug(DebugType.VERBOSE_AI, testing) +// GameState.putDebug(DebugType.VERBOSE_AI, true) +// GameState.putDebug(DebugType.CLARITY, true) +// GameState.properties.values.put(DEBUG_PACKAGE, AIPackageKeys.PREDATOR) + GameState.properties.values.put(AUTO_LOAD, !testing) GameState.putDebug(DebugType.POLL_CONNECTION, !testing) GameState.properties.values.put(TEST_SAVE_FOLDER, testing) diff --git a/src/commonMain/kotlin/core/GameState.kt b/src/commonMain/kotlin/core/GameState.kt index ea300618d..dd73be86e 100644 --- a/src/commonMain/kotlin/core/GameState.kt +++ b/src/commonMain/kotlin/core/GameState.kt @@ -5,7 +5,6 @@ import core.events.Event import core.history.GameLogger import core.properties.Properties import core.thing.Thing -import core.utility.lazyM import system.debug.DebugType import time.TimeManager @@ -64,8 +63,9 @@ fun startupLog(message: String) { const val AUTO_SAVE = "autosave" const val AUTO_LOAD = "autoload" +const val DEBUG_PACKAGE = "debug package" const val VERBOSE_STARTUP = "verbose startup" const val TEST_SAVE_FOLDER = "use test save folder" const val SKIP_SAVE_STATS = "skip save stats" const val LAST_SAVE_GAME_NAME = "last save character name" -const val PRINT_WITHOUT_FLUSH = "print without needing to flush histories" \ No newline at end of file +const val PRINT_WITHOUT_FLUSH = "print without needing to flush histories" diff --git a/src/commonMain/kotlin/core/MagicStrings.kt b/src/commonMain/kotlin/core/MagicStrings.kt index c70a05888..2584b236d 100644 --- a/src/commonMain/kotlin/core/MagicStrings.kt +++ b/src/commonMain/kotlin/core/MagicStrings.kt @@ -2,11 +2,14 @@ package core object AIPackageKeys { const val CREATURE = "Creature" + const val PREDATOR = "Predator" + const val PEASANT = "Peasant" } object TagKey { const val CREATURE = "Creature" const val FOOD = "Food" + const val PREDATOR = "Predator" const val SOUND_DESCRIPTION = "Sound Description" const val SOUND_LEVEL = "Sound Level" } @@ -16,17 +19,16 @@ object ValueKey { } object HowToUse { - const val EAT = "Eat" const val ATTACK = "Attack" - const val SLEEP = "Sleep" + const val EAT = "Eat" const val INTERACT = "Interact" + const val SLEEP = "Sleep" } object FactKind { const val AGGRO_TARGET = "AggroTarget" const val HOME = "Home" const val LOCATION = "Location" - const val LOCATION_GOAL = "LocationGoal" const val MY_BED = "MyBed" const val MY_HOME = "MyHome" const val MY_WORKPLACE = "MyWorkplace" diff --git a/src/commonMain/kotlin/core/ai/PackageBasedAI.kt b/src/commonMain/kotlin/core/ai/PackageBasedAI.kt index e0f194bee..e087ef213 100644 --- a/src/commonMain/kotlin/core/ai/PackageBasedAI.kt +++ b/src/commonMain/kotlin/core/ai/PackageBasedAI.kt @@ -10,7 +10,7 @@ import core.utility.RandomManager class PackageBasedAI(val aiPackage: AIPackage) : AI() { val previousIdeas = mutableListOf() override fun toString(): String { - return "AI for ${creature.name} using ${aiPackage.name}" + return "AI for ${creature.name} using ${aiPackage.name} package" } override suspend fun takeAction() { diff --git a/src/commonMain/kotlin/core/ai/packages/AIPackage.kt b/src/commonMain/kotlin/core/ai/packages/AIPackage.kt index 984246faa..d36b85ab2 100644 --- a/src/commonMain/kotlin/core/ai/packages/AIPackage.kt +++ b/src/commonMain/kotlin/core/ai/packages/AIPackage.kt @@ -1,15 +1,31 @@ package core.ai.packages +import core.DEBUG_PACKAGE +import core.GameState import core.thing.Thing +import system.debug.DebugType //Can we memoize criteria per call to pickIdea? Would that be premature optimization? -data class AIPackage(val name: String, val ideas: Map>) { +data class AIPackage(val name: String, val ideas: Map>) { constructor(name: String, ideas: List) : this(name, ideas.groupBy { it.priority }) suspend fun pickIdea(source: Thing): Idea { - return ideas.entries.sortedByDescending { it.key }.firstNotNullOfOrNull { (_, ideas) -> + val verbose = GameState.getDebugBoolean(DebugType.VERBOSE_AI) && GameState.properties.values[DEBUG_PACKAGE] == name + if (verbose) debugWhyPicked(source) + return (ideas.entries.sortedByDescending { it.key }.firstNotNullOfOrNull { (_, ideas) -> ideas.firstOrNull { it.criteria(source) } - } ?: DO_NOTHING_IDEA + } ?: DO_NOTHING_IDEA) + .also { if (verbose) println("\tPicked ${it.name}")} + } + + private suspend fun debugWhyPicked(source: Thing) { + println("AI Package $name for ${source.name}") + ideas.entries + .flatMap { (p, ideas) -> ideas.map { p to it } } + .filter { (_, idea) -> idea.criteria(source) } + .forEach { (priority, idea) -> + println("\t$priority ${idea.name}") + } } } diff --git a/src/commonMain/kotlin/core/properties/Values.kt b/src/commonMain/kotlin/core/properties/Values.kt index 07e83e6f4..6d176d75c 100644 --- a/src/commonMain/kotlin/core/properties/Values.kt +++ b/src/commonMain/kotlin/core/properties/Values.kt @@ -5,7 +5,7 @@ import core.utility.* @kotlinx.serialization.Serializable data class Values(private val properties: MutableMap = mutableMapOf()) { - constructor(vararg props: Pair): this(props.toMap().toMutableMap()) + constructor(vararg props: Pair) : this(props.toMap().toMutableMap()) constructor(base: Values, params: Map = mapOf()) : this(base.properties.apply(params).toMutableMap()) init { @@ -13,9 +13,7 @@ data class Values(private val properties: MutableMap = mutableMa } override fun toString(): String { - return if (properties.isEmpty()) { - "" - } else { + return if (properties.isEmpty()) "" else { properties.entries.joinToString(", ") { "${it.key} ${it.value}" } } } @@ -28,7 +26,7 @@ data class Values(private val properties: MutableMap = mutableMa return properties.hashCode() } - private fun parseProperties(){ + private fun parseProperties() { val base = properties.toMap() properties.clear() base.entries.forEach { @@ -54,6 +52,7 @@ data class Values(private val properties: MutableMap = mutableMa return default } + operator fun get(key: String) = getString(key) fun getString(key: String, default: String = ""): String { return properties[key.lowercase()] ?: default } diff --git a/src/commonMain/kotlin/core/thing/Thing.kt b/src/commonMain/kotlin/core/thing/Thing.kt index e2e055132..189946c33 100644 --- a/src/commonMain/kotlin/core/thing/Thing.kt +++ b/src/commonMain/kotlin/core/thing/Thing.kt @@ -1,9 +1,6 @@ package core.thing import core.GameState -import core.GameState.properties -import core.ai.AI -import core.ai.DumbAI import core.ai.behavior.Behavior import core.ai.knowledge.Mind import core.body.Body @@ -15,14 +12,11 @@ import core.utility.clamp import core.utility.max import explore.listen.getSound import inventory.Inventory -import kotlinx.coroutines.NonDisposableHandle.parent import status.Soul import status.stat.PERCEPTION import status.stat.SNEAK import system.debug.DebugType -import traveling.location.Route import traveling.location.location.Location -import traveling.location.location.location import traveling.location.network.LocationNode import traveling.location.network.NOWHERE_NODE import traveling.position.NO_VECTOR diff --git a/src/commonMain/kotlin/core/thing/ThingBuilder.kt b/src/commonMain/kotlin/core/thing/ThingBuilder.kt index 7bae41ed8..2f53f7313 100644 --- a/src/commonMain/kotlin/core/thing/ThingBuilder.kt +++ b/src/commonMain/kotlin/core/thing/ThingBuilder.kt @@ -1,5 +1,6 @@ package core.thing +import core.AIPackageKeys import core.TagKey import core.TagKey.SOUND_DESCRIPTION import core.TagKey.SOUND_LEVEL @@ -68,7 +69,7 @@ class ThingBuilder(internal val name: String) { val allItems = itemNames + bases.flatMap { it.itemNames } val inventory = Inventory(name, body) inventory.addAllByName(allItems) - val ai = ai ?: basesR.firstNotNullOfOrNull { it.ai } ?: discernAI(tagsToApply) + val ai = ai ?: basesR.firstNotNullOfOrNull { it.ai } ?: discernAI(props) val mindParsed = mindP?.let { Mind(ai, CreatureMemory(mindP!!.facts.map { it.parsed() }, mindP!!.listFacts.map { it.parsed() })) } val mind = this.mind ?: mindParsed ?: basesR.firstNotNullOfOrNull { it.mind } ?: Mind(ai) mind.mindInitializer() @@ -92,6 +93,7 @@ class ThingBuilder(internal val name: String) { } private fun calcHeldSlots(props: Properties): List { + if(props.tags.has(TagKey.CREATURE)) return emptyList() return when { props.tags.has("Small") || props.values.getInt("weight", 100) < 3 -> listOf(Slot(listOf("Right Hand Grip")), Slot(listOf("Left Hand Grip"))) props.tags.has("Medium") || props.values.getInt("weight", 100) < 6 -> listOf(Slot(listOf("Right Hand Grip", "Left Hand Grip"))) @@ -256,10 +258,10 @@ class ThingBuilder(internal val name: String) { }.also { bodyCustomizer.apply(it) } } - private fun discernAI(tags: List) : AI { + private fun discernAI(props: Properties) : AI { return when { -// tags.contains(CREATURE_TAG) -> PackageBasedAI(AIPackageManager.aiPackages[AIPackageKeys.CREATURE]!!) - tags.contains(TagKey.CREATURE) -> ConditionalAI() + props.tags.has(TagKey.PREDATOR) -> PackageBasedAI(AIPackageManager.aiPackages[AIPackageKeys.PREDATOR]!!) + props.tags.has(TagKey.CREATURE) -> PackageBasedAI(AIPackageManager.aiPackages[AIPackageKeys.CREATURE]!!) else -> DumbAI() } } diff --git a/src/commonMain/kotlin/explore/look/DescribeThing.kt b/src/commonMain/kotlin/explore/look/DescribeThing.kt index dc7f72f27..d617880bb 100644 --- a/src/commonMain/kotlin/explore/look/DescribeThing.kt +++ b/src/commonMain/kotlin/explore/look/DescribeThing.kt @@ -16,6 +16,7 @@ suspend fun describeThing(source: Player, thing: Thing) { suspend fun describeThingDetailed(source: Player, thing: Thing) { var message = thing.getDisplayName() message += "\n\t${thing.description}" + message += describeCurrentAction(thing) message += describeStatusEffects(thing) message += describeEquipSlots(thing) message += describeProperties(thing) @@ -23,6 +24,15 @@ suspend fun describeThingDetailed(source: Player, thing: Thing) { source.displayToOthers("${source.name} examines ${thing.name}.") } +//TODO - ideally events have a "description instead of just two-stringing +private fun describeCurrentAction(thing: Thing): String { + if (thing.mind.ai.actions.isNotEmpty()) { + val actions = thing.mind.ai.actions.joinToString { it.toString() } + return "\n\t${thing.name} is currently doing: $actions" + } + return "" +} + private fun describeStatusEffects(thing: Thing): String { if (thing.soul.getConditions().isNotEmpty()) { val effects = thing.soul.getConditions().joinToString(", ") { it.name } @@ -32,11 +42,8 @@ private fun describeStatusEffects(thing: Thing): String { } private fun describeProperties(thing: Thing): String { - if (!thing.properties.isEmpty()) { - return "\n\tTags: ${thing.properties.tags}" + - "\n\tValues: ${thing.properties.values}" - } - return "" + return (thing.properties.tags.takeIf { !it.isEmpty() }?.let { "\n\tTags: $it" } ?: "") + + (thing.properties.values.takeIf { !it.isEmpty() }?.let { "\n\tValues: $it" } ?: "") } private fun describeEquipSlots(thing: Thing): String { @@ -45,4 +52,4 @@ private fun describeEquipSlots(thing: Thing): String { return "\n\tIt can be equipped to $slots." } return "" -} \ No newline at end of file +} diff --git a/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt b/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt index d3d9ebfd6..afac4dbc1 100644 --- a/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt +++ b/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt @@ -14,19 +14,8 @@ class CommonPackages : AIPackageTemplateResource { override val values = aiPackages { aiPackage("Predator") { template("Creature") - idea("Rest") { - cond { !GameState.timeManager.isNight() } - act { RestEvent(it, 2) } - } - - idea("Find Tree to Scratch") { - cond { s -> s.perceivedActivators().any { it.name.contains("Tree") } } - act { s -> - s.perceivedActivators().firstOrNull { it.name.contains("Tree") }?.let { s.setUseTarget(it, HowToUse.ATTACK) } - } - } - idea("Attack Goal") { + idea("Attack Goal", 50) { cond { it.canReachGoal(HowToUse.ATTACK) } actions { s -> listOfNotNull( @@ -35,13 +24,26 @@ class CommonPackages : AIPackageTemplateResource { ) } } - idea("Hunt") { + idea("Hunt", 30) { cond { s -> s.perceivedCreatures().any { !it.hasTag("Predator") } } act { s -> s.perceivedCreatures().firstOrNull { !it.hasTag("Predator") } ?.let { s.discover(it, FactKind.AGGRO_TARGET) } } } + + idea("Rest") { + cond { !GameState.timeManager.isNight() } + act { RestEvent(it, 2) } + } + + idea("Find Tree to Scratch") { + cond { s -> s.perceivedActivators().any { it.name.contains("Tree") } } + act { s -> + s.perceivedActivators().firstOrNull { it.name.contains("Tree") }?.let { s.setUseTarget(it, HowToUse.ATTACK) } + } + } + } } } diff --git a/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt b/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt index 620da03c6..bc123025d 100644 --- a/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt +++ b/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt @@ -7,6 +7,8 @@ import core.TagKey import core.ai.knowledge.clearUseGoal import core.ai.knowledge.setUseTarget import core.ai.packages.* +import core.utility.getRandomRange +import core.utility.random import status.rest.RestEvent import status.stat.STAMINA import traveling.move.startMoveEvent @@ -93,9 +95,11 @@ class CreaturePackage : AIPackageTemplateResource { } idea("Wander") { + cond { s -> s.location.getLocation().getThings(s).filter { it != s }.isNotEmpty() } act { - val target = it.location.getLocation().getThings(it).random() - startMoveEvent(it, destination = target.position) + it.location.getLocation().getThings(it).random()?.position?.let { pos -> + startMoveEvent(it, destination = pos) + } } } } diff --git a/src/commonMain/kotlin/resources/ai/packages/PeasantPackage.kt b/src/commonMain/kotlin/resources/ai/packages/PeasantPackage.kt index a345d67d3..e605f210b 100644 --- a/src/commonMain/kotlin/resources/ai/packages/PeasantPackage.kt +++ b/src/commonMain/kotlin/resources/ai/packages/PeasantPackage.kt @@ -1,5 +1,6 @@ package resources.ai.packages +import core.AIPackageKeys.PEASANT import core.FactKind import core.GameState import core.HowToUse @@ -13,7 +14,7 @@ import core.commands.CommandParsers class PeasantPackage : AIPackageTemplateResource { override val values = aiPackages { - aiPackage("Commoner") { + aiPackage(PEASANT) { template("Creature") idea("Converse") { cond { s -> CommandParsers.getConversations().any { it.containsParticipant(s) }} diff --git a/src/commonMain/kotlin/resources/thing/creature/CommonCreatures.kt b/src/commonMain/kotlin/resources/thing/creature/CommonCreatures.kt index 3c7e6b646..97b97be9f 100644 --- a/src/commonMain/kotlin/resources/thing/creature/CommonCreatures.kt +++ b/src/commonMain/kotlin/resources/thing/creature/CommonCreatures.kt @@ -1,5 +1,6 @@ package resources.thing.creature +import core.TagKey.PREDATOR import core.thing.creature.CreatureResource import core.thing.things @@ -13,7 +14,7 @@ class CommonCreatures : CreatureResource { soul("Strength", 1) soul("Bare Handed", 2) props { - tag("Small", "Predator") + tag("Small", PREDATOR) } //TODO - make this a 'death item' that's spawned on death item("Poor Quality Meat") @@ -48,4 +49,4 @@ class CommonCreatures : CreatureResource { } -} \ No newline at end of file +} diff --git a/src/commonMain/kotlin/traveling/move/MoveEvent.kt b/src/commonMain/kotlin/traveling/move/MoveEvent.kt index 571c8a65c..7b99f725f 100644 --- a/src/commonMain/kotlin/traveling/move/MoveEvent.kt +++ b/src/commonMain/kotlin/traveling/move/MoveEvent.kt @@ -22,6 +22,7 @@ suspend fun startMoveEvent( private suspend fun calcTimeLeft(source: Thing, moveThing: Vector, speedScalar: Float): Int { val agility = getUnencumberedAgility(source) val distance = source.position.getDistance(moveThing) + if (distance == 0) return 1 val speed = max(agility - distance, 1) return max(1, 100 / (speedScalar * speed).toInt()) @@ -31,4 +32,4 @@ private suspend fun getUnencumberedAgility(thing: Thing): Int { val agility = thing.soul.getCurrent(AGILITY) val encumbrance = thing.getEncumbranceInverted() return (agility * encumbrance).toInt() -} \ No newline at end of file +} From 7336e82735783d87651b8604dd62d07551425ce6 Mon Sep 17 00:00:00 2001 From: ManApart Date: Wed, 31 Dec 2025 10:14:06 -0500 Subject: [PATCH 28/45] rip out old ai system --- src/commonMain/kotlin/building/ModManager.kt | 12 +- .../kotlin/core/DependencyInjector.kt | 9 +- src/commonMain/kotlin/core/ai/AIManager.kt | 35 --- .../kotlin/core/ai/AIPackageManager.kt | 2 +- .../kotlin/core/ai/ConditionalAI.kt | 52 ----- src/commonMain/kotlin/core/ai/Goal.kt | 46 ---- .../kotlin/core/ai/action/AIAction.kt | 11 - .../kotlin/core/ai/agenda/ActionBuilder.kt | 32 --- .../kotlin/core/ai/agenda/Agenda.kt | 23 -- .../kotlin/core/ai/agenda/AgendaBuilder.kt | 33 --- .../kotlin/core/ai/agenda/AgendaResource.kt | 5 - .../kotlin/core/ai/agenda/AgendasBuilder.kt | 33 --- .../core/ai/agenda/AgendasCollection.kt | 6 - .../kotlin/core/ai/agenda/AgendasGenerated.kt | 5 - .../kotlin/core/ai/desire/DesireBuilder.kt | 50 ---- .../kotlin/core/ai/desire/DesireResource.kt | 5 - .../kotlin/core/ai/desire/DesireTree.kt | 22 -- .../core/ai/desire/DesiresCollection.kt | 6 - .../kotlin/core/ai/desire/DesiresGenerated.kt | 5 - .../kotlin/core/thing/ThingBuilder.kt | 4 - .../core/thing/creature/CreatureManager.kt | 2 - .../kotlin/explore/look/DescribeMind.kt | 21 +- .../resources/ai/agenda/CommonAgendas.kt | 218 ------------------ .../resources/ai/desire/CommonDesires.kt | 70 ------ src/jvmMain/kotlin/building/ModLoader.kt | 8 +- src/jvmTest/kotlin/TestConstructors.kt | 10 +- .../kotlin/core/ai/agenda/AgendasMock.kt | 3 - .../kotlin/core/ai/desire/DesiresMock.kt | 5 - .../kotlin/validation/AIPackageValidator.kt | 2 +- .../kotlin/validation/DesireValidator.kt | 46 ---- .../kotlin/building/ReflectionTools.kt | 8 +- 31 files changed, 16 insertions(+), 773 deletions(-) delete mode 100644 src/commonMain/kotlin/core/ai/AIManager.kt delete mode 100644 src/commonMain/kotlin/core/ai/ConditionalAI.kt delete mode 100644 src/commonMain/kotlin/core/ai/Goal.kt delete mode 100644 src/commonMain/kotlin/core/ai/action/AIAction.kt delete mode 100644 src/commonMain/kotlin/core/ai/agenda/ActionBuilder.kt delete mode 100644 src/commonMain/kotlin/core/ai/agenda/Agenda.kt delete mode 100644 src/commonMain/kotlin/core/ai/agenda/AgendaBuilder.kt delete mode 100644 src/commonMain/kotlin/core/ai/agenda/AgendaResource.kt delete mode 100644 src/commonMain/kotlin/core/ai/agenda/AgendasBuilder.kt delete mode 100644 src/commonMain/kotlin/core/ai/agenda/AgendasCollection.kt delete mode 100644 src/commonMain/kotlin/core/ai/agenda/AgendasGenerated.kt delete mode 100644 src/commonMain/kotlin/core/ai/desire/DesireBuilder.kt delete mode 100644 src/commonMain/kotlin/core/ai/desire/DesireResource.kt delete mode 100644 src/commonMain/kotlin/core/ai/desire/DesireTree.kt delete mode 100644 src/commonMain/kotlin/core/ai/desire/DesiresCollection.kt delete mode 100644 src/commonMain/kotlin/core/ai/desire/DesiresGenerated.kt delete mode 100644 src/commonMain/kotlin/resources/ai/agenda/CommonAgendas.kt delete mode 100644 src/commonMain/kotlin/resources/ai/desire/CommonDesires.kt delete mode 100644 src/jvmTest/kotlin/core/ai/agenda/AgendasMock.kt delete mode 100644 src/jvmTest/kotlin/core/ai/desire/DesiresMock.kt delete mode 100644 src/jvmTestIntegration/kotlin/validation/DesireValidator.kt diff --git a/src/commonMain/kotlin/building/ModManager.kt b/src/commonMain/kotlin/building/ModManager.kt index 2ed3a63dc..03c72107f 100644 --- a/src/commonMain/kotlin/building/ModManager.kt +++ b/src/commonMain/kotlin/building/ModManager.kt @@ -1,10 +1,8 @@ package building import conversation.dsl.DialogueTree -import core.ai.agenda.Agenda import core.ai.behavior.Behavior import core.ai.packages.AIPackageTemplate -import core.ai.desire.DesireTree import core.events.EventListener import core.thing.ThingBuilder import crafting.RecipeBuilder @@ -19,9 +17,7 @@ import traveling.location.weather.Weather object ModManager { val eventListeners = mutableMapOf>>() val activators = mutableListOf() - val ai = mutableListOf() - val ai2 = mutableListOf() - val agendas = mutableListOf() + val aiPackages = mutableListOf() val behaviors = mutableListOf>() val bodies = mutableListOf() val bodyParts = mutableListOf() @@ -39,9 +35,7 @@ object ModManager { fun reset(){ activators.clear() - ai.clear() - ai2.clear() - agendas.clear() + aiPackages.clear() behaviors.clear() bodies.clear() bodyParts.clear() @@ -57,4 +51,4 @@ object ModManager { recipes.clear() weather.clear() } -} \ No newline at end of file +} diff --git a/src/commonMain/kotlin/core/DependencyInjector.kt b/src/commonMain/kotlin/core/DependencyInjector.kt index 89f8756a3..a7d683b25 100644 --- a/src/commonMain/kotlin/core/DependencyInjector.kt +++ b/src/commonMain/kotlin/core/DependencyInjector.kt @@ -4,19 +4,16 @@ import conversation.dsl.DialoguesCollection import conversation.dsl.DialoguesGenerated import core.ai.behavior.BehaviorsCollection import core.ai.behavior.BehaviorsGenerated -import core.ai.agenda.AgendasCollection -import core.ai.agenda.AgendasGenerated import core.ai.packages.AIPackageTemplatesCollection import core.ai.packages.AIPackageTemplatesGenerated -import core.ai.desire.DesiresCollection -import core.ai.desire.DesiresGenerated import core.body.BodyPartsCollection import core.body.BodyPartsGenerated import core.body.BodysCollection import core.body.BodysGenerated import core.commands.CommandsCollection import core.commands.CommandsGenerated -import core.events.* +import core.events.EventListenerMapCollection +import core.events.EventListenerMapGenerated import core.thing.activator.dsl.ActivatorsCollection import core.thing.activator.dsl.ActivatorsGenerated import core.thing.creature.CreaturesCollection @@ -79,7 +76,6 @@ object DependencyInjector { private fun createDefaultImplementations(): Map, Any> { return mapOf( ActivatorsCollection::class to ActivatorsGenerated(), - AgendasCollection::class to AgendasGenerated(), AIPackageTemplatesCollection::class to AIPackageTemplatesGenerated(), BehaviorsCollection::class to BehaviorsGenerated(), BodysCollection::class to BodysGenerated(), @@ -91,7 +87,6 @@ object DependencyInjector { EffectsCollection::class to EffectsGenerated(), EventListenerMapCollection::class to EventListenerMapGenerated(), ItemsCollection::class to ItemsGenerated(), - DesiresCollection::class to DesiresGenerated(), LocationsCollection::class to LocationsGenerated(), MaterialsCollection::class to MaterialsGenerated(), NetworksCollection::class to NetworksGenerated(), diff --git a/src/commonMain/kotlin/core/ai/AIManager.kt b/src/commonMain/kotlin/core/ai/AIManager.kt deleted file mode 100644 index 0e4a47e0d..000000000 --- a/src/commonMain/kotlin/core/ai/AIManager.kt +++ /dev/null @@ -1,35 +0,0 @@ -package core.ai - -import building.ModManager -import core.DependencyInjector -import core.ai.agenda.Agenda -import core.ai.agenda.AgendasCollection -import core.ai.desire.DesireTree -import core.ai.desire.DesiresCollection -import core.startupLog -import core.utility.Backer -import core.utility.lazyM - -object AIManager { - private val desires = Backer(::loadDesires) - suspend fun getDesires() = desires.get() - - var agendas by lazyM { loadAgendas() } - private set - - private suspend fun loadDesires(): List { - startupLog("Loading AI Desires.") - return DependencyInjector.getImplementation(DesiresCollection::class).values() + ModManager.ai - } - - private fun loadAgendas(): Map { - startupLog("Loading AI Agendas.") - return (DependencyInjector.getImplementation(AgendasCollection::class).values + ModManager.agendas).associateBy { it.name } - } - - suspend fun reset() { - desires.reset() - agendas = loadAgendas() - } - -} \ No newline at end of file diff --git a/src/commonMain/kotlin/core/ai/AIPackageManager.kt b/src/commonMain/kotlin/core/ai/AIPackageManager.kt index 3b43ee347..28e78a630 100644 --- a/src/commonMain/kotlin/core/ai/AIPackageManager.kt +++ b/src/commonMain/kotlin/core/ai/AIPackageManager.kt @@ -13,7 +13,7 @@ object AIPackageManager { private fun loadAIPackages(): Map { startupLog("Loading AI Packages.") - val templates = (DependencyInjector.getImplementation(AIPackageTemplatesCollection::class).values + ModManager.ai2).associateBy { it.name } + val templates = (DependencyInjector.getImplementation(AIPackageTemplatesCollection::class).values + ModManager.aiPackages).associateBy { it.name } val flattenedReference = mutableMapOf() return templates.values.map { it.flatten(templates, flattenedReference) }.associateBy { it.name } } diff --git a/src/commonMain/kotlin/core/ai/ConditionalAI.kt b/src/commonMain/kotlin/core/ai/ConditionalAI.kt deleted file mode 100644 index d03d0e5da..000000000 --- a/src/commonMain/kotlin/core/ai/ConditionalAI.kt +++ /dev/null @@ -1,52 +0,0 @@ -package core.ai - -import conversation.ConversationManager -import conversation.dialogue.DialogueEvent -import core.GameState -import core.events.EventManager -import core.history.display -import core.history.displayGlobal -import core.utility.RandomManager -import system.debug.DebugType - -private val defaultAgenda = Pair("Nothing", 0) - -class ConditionalAI : AI() { - var goal: Goal? = null - private set - - override fun toString(): String { - return "Conditional AI for ${creature.name}" - } - - override suspend fun takeAction() { - if (goal == null) { - goal = determineGoal() - } - goal?.step(creature) - if (goal?.canContinue() == false) { - goal = null - } - } - - private suspend fun determineGoal(): Goal { - val matches = AIManager.getDesires().flatMap { it.getDesires(creature) } - val priority = matches.maxOfOrNull { it.second } ?: 0 - val topMatches = matches.filter { it.second == priority } - val desire = (RandomManager.getRandomOrNull(topMatches) ?: defaultAgenda).first - val agenda = AIManager.agendas[desire] ?: AIManager.agendas[defaultAgenda.first]!!.also { displayGlobal("Couldn't find agenda for ${desire}!") } - - if (GameState.getDebugBoolean(DebugType.VERBOSE_AI)) displayGlobal("${creature.name} picks ${agenda.name}.") - return Goal(agenda, priority) - } - - override suspend fun hear(event: DialogueEvent) { - event.speaker.display(event.line) - val matches = ConversationManager.getMatchingDialogue(event.conversation) - val priority = matches.maxOf { it.priority } - val topMatches = matches.filter { it.priority == priority } - val response = RandomManager.getRandom(topMatches) - response.result(event.conversation).forEach { EventManager.postEvent(it) } - } - -} \ No newline at end of file diff --git a/src/commonMain/kotlin/core/ai/Goal.kt b/src/commonMain/kotlin/core/ai/Goal.kt deleted file mode 100644 index ff3768195..000000000 --- a/src/commonMain/kotlin/core/ai/Goal.kt +++ /dev/null @@ -1,46 +0,0 @@ -package core.ai - -import core.GameState -import core.ai.action.AIAction -import core.ai.agenda.Agenda -import core.events.EventManager -import core.history.displayGlobal -import core.thing.Thing -import core.utility.Named -import system.debug.DebugType - -data class Goal( - override val name: String, - val priority: Int, - var progress: Int, - val steps: List, -) : Named { - constructor(agenda: Agenda, priority: Int) : this(agenda.name, priority, 0, agenda.steps.flatMap { it.getActions() }) - - private var aborted = false - - suspend fun step(owner: Thing) { - val step = steps[progress] - if (step.shouldSkip(owner) != true) { - val events = try { - step.createEvents(owner) - } catch (e: Exception) { - println("Failed to create event actions for ${owner.name}. This shouldn't happen!") - println(e.message) - e.printStackTrace() - null - } - if (events == null) { - aborted = true - } - if (GameState.getDebugBoolean(DebugType.VERBOSE_AI)) displayGlobal("${owner.name} does ${step.name}, producing events ${events?.joinToString { it.toString() }}.") - events?.forEach { EventManager.postEvent(it) } - } - progress++ - } - - fun canContinue(): Boolean { - return !aborted && progress < steps.size - } -} - diff --git a/src/commonMain/kotlin/core/ai/action/AIAction.kt b/src/commonMain/kotlin/core/ai/action/AIAction.kt deleted file mode 100644 index cc9616366..000000000 --- a/src/commonMain/kotlin/core/ai/action/AIAction.kt +++ /dev/null @@ -1,11 +0,0 @@ -package core.ai.action - -import core.events.Event -import core.thing.Thing -import core.utility.Named - -data class AIAction( - override val name: String, - val createEvents: suspend (Thing) -> List? = { _ -> listOf() }, - val shouldSkip: suspend (Thing) -> Boolean? = { false } -) : Named \ No newline at end of file diff --git a/src/commonMain/kotlin/core/ai/agenda/ActionBuilder.kt b/src/commonMain/kotlin/core/ai/agenda/ActionBuilder.kt deleted file mode 100644 index b461d38e7..000000000 --- a/src/commonMain/kotlin/core/ai/agenda/ActionBuilder.kt +++ /dev/null @@ -1,32 +0,0 @@ -package core.ai.agenda - -import core.ai.action.AIAction -import core.events.Event -import core.thing.Thing - -class ActionBuilder(private val name: String) { - private var result: suspend (Thing) -> Event? = { null } - private var resultList: (suspend (Thing) -> List?)? = null - private var shouldSkip: suspend (Thing) -> Boolean? = { true } - - fun result(result: suspend (Thing) -> Event?) { - this.result = result - } - - fun results(result: suspend (Thing) -> List?) { - this.resultList = result - } - - fun shouldSkip(shouldSkip: suspend (Thing) -> Boolean?) { - this.shouldSkip = shouldSkip - } - - internal fun build(): AIAction { - val res = resultList ?: { thing -> - result(thing)?.let { listOf(it) } - } - return AIAction(name, res, shouldSkip) - } - -} - diff --git a/src/commonMain/kotlin/core/ai/agenda/Agenda.kt b/src/commonMain/kotlin/core/ai/agenda/Agenda.kt deleted file mode 100644 index 26e7e48ab..000000000 --- a/src/commonMain/kotlin/core/ai/agenda/Agenda.kt +++ /dev/null @@ -1,23 +0,0 @@ -package core.ai.agenda - -import core.ai.AIManager -import core.ai.action.AIAction -import core.utility.Named - -data class GoalStep(val agendaName: String?, val step: AIAction?) { - constructor(agendaName: String) : this(agendaName, null) - constructor(step: AIAction) : this(null, step) - - fun getActions(): List { - return step?.let { listOf(it) } ?: AIManager.agendas[agendaName]!!.getActions() - } -} - -data class Agenda( - override val name: String, - val steps: List -) : Named { - fun getActions(): List { - return steps.flatMap { it.getActions() } - } -} diff --git a/src/commonMain/kotlin/core/ai/agenda/AgendaBuilder.kt b/src/commonMain/kotlin/core/ai/agenda/AgendaBuilder.kt deleted file mode 100644 index c98e9f95c..000000000 --- a/src/commonMain/kotlin/core/ai/agenda/AgendaBuilder.kt +++ /dev/null @@ -1,33 +0,0 @@ -package core.ai.agenda - -import core.ai.action.AIAction -import core.events.Event -import core.thing.Thing - -class AgendaBuilder(private val name: String) { - private val steps: MutableList = mutableListOf() - - fun action(name: String, result: suspend (Thing) -> Event?) { - this.steps.add(GoalStep(AIAction(name, { thing -> - result(thing)?.let { listOf(it) } - }))) - } - - fun actionDetailed(name: String, initializer: ActionBuilder.() -> Unit) { - this.steps.add(GoalStep(ActionBuilder(name).apply(initializer).build())) - } - - fun actions(name: String, result: suspend (Thing) -> List?) { - this.steps.add(GoalStep(AIAction(name, result))) - } - - fun agenda(name: String) { - this.steps.add(GoalStep(name)) - } - - internal fun build(): Agenda { - return Agenda(name, steps) - } - -} - diff --git a/src/commonMain/kotlin/core/ai/agenda/AgendaResource.kt b/src/commonMain/kotlin/core/ai/agenda/AgendaResource.kt deleted file mode 100644 index 270fa5d01..000000000 --- a/src/commonMain/kotlin/core/ai/agenda/AgendaResource.kt +++ /dev/null @@ -1,5 +0,0 @@ -package core.ai.agenda - -interface AgendaResource { - val values: List -} \ No newline at end of file diff --git a/src/commonMain/kotlin/core/ai/agenda/AgendasBuilder.kt b/src/commonMain/kotlin/core/ai/agenda/AgendasBuilder.kt deleted file mode 100644 index f8a0c9f6a..000000000 --- a/src/commonMain/kotlin/core/ai/agenda/AgendasBuilder.kt +++ /dev/null @@ -1,33 +0,0 @@ -package core.ai.agenda - -import core.events.Event -import core.thing.Thing - -class AgendasBuilder { - internal val children = mutableListOf() - - fun agenda(item: AgendaBuilder) { - children.add(item) - } - - fun agenda(name: String, initializer: AgendaBuilder.() -> Unit) { - children.add(AgendaBuilder(name).apply(initializer)) - } - - //Creates an Agenda with an action of the same name and a single result event - fun agendaAction(name: String, result: suspend (Thing) -> Event?) { - agenda(name){ - action(name, result) - } - } - - fun build(): List { - return children.map { it.build() } - } -} - -fun agendas( - initializer: AgendasBuilder.() -> Unit -): List { - return AgendasBuilder().apply(initializer).build() -} diff --git a/src/commonMain/kotlin/core/ai/agenda/AgendasCollection.kt b/src/commonMain/kotlin/core/ai/agenda/AgendasCollection.kt deleted file mode 100644 index b7b68a66e..000000000 --- a/src/commonMain/kotlin/core/ai/agenda/AgendasCollection.kt +++ /dev/null @@ -1,6 +0,0 @@ -package core.ai.agenda -import core.ai.agenda.Agenda - -interface AgendasCollection { - val values: List -} \ No newline at end of file diff --git a/src/commonMain/kotlin/core/ai/agenda/AgendasGenerated.kt b/src/commonMain/kotlin/core/ai/agenda/AgendasGenerated.kt deleted file mode 100644 index fdeb6446f..000000000 --- a/src/commonMain/kotlin/core/ai/agenda/AgendasGenerated.kt +++ /dev/null @@ -1,5 +0,0 @@ -package core.ai.agenda - -class AgendasGenerated : AgendasCollection { - override val values by lazy { listOf(resources.ai.agenda.CommonAgendas()).flatMap { it.values }} -} \ No newline at end of file diff --git a/src/commonMain/kotlin/core/ai/desire/DesireBuilder.kt b/src/commonMain/kotlin/core/ai/desire/DesireBuilder.kt deleted file mode 100644 index 842dc4332..000000000 --- a/src/commonMain/kotlin/core/ai/desire/DesireBuilder.kt +++ /dev/null @@ -1,50 +0,0 @@ -package core.ai.desire - -import core.thing.Thing -import core.utility.RandomManager -import core.utility.applySuspending - -class DesireBuilder(val condition: suspend (Thing) -> Boolean?) { - //Set priority to an exact amount - var priority: Int? = null - //Give some additional priority to actions at the top level of this branch. Not recursive - var additionalPriority: Int = 0 - private val depthScale: Int = 2 - private val children: MutableList = mutableListOf() - private val agendas: MutableList = mutableListOf() - - - suspend fun cond(condition: suspend (Thing) -> Boolean? = { _ -> true }, initializer: suspend DesireBuilder.() -> Unit) { - children.add(DesireBuilder(condition).applySuspending(initializer)) - } - - suspend fun cond(randomChance: Int, condition: suspend (Thing) -> Boolean? = { _ -> true }, initializer: DesireBuilder.() -> Unit) { - val newCondition: suspend (Thing) -> Boolean? = { thing -> if (RandomManager.isSuccess(randomChance)) condition(thing) else null } - children.add(DesireBuilder(newCondition).apply(initializer)) - } - - suspend fun tag(tag: String, initializer: suspend DesireBuilder.() -> Unit) { - val b = DesireBuilder { source -> source.properties.tags.has(tag) } - b.initializer() - children.add(b) - } - - fun agenda(agenda: String) { - agendas.add(agenda) - } - - internal fun build(depth: Int = 0): DesireTree { - val usedPriority = priority ?: (10 + depthScale * depth + additionalPriority) - val usedAgendas = agendas.map { Pair(it, usedPriority) } - val usedChildren = children.map { it.build(depth + 1) } - - return DesireTree(condition, usedAgendas, usedChildren) - } -} - -suspend fun desires( - condition: (Thing) -> Boolean? = { _ -> true }, - initializer: suspend DesireBuilder.() -> Unit -): List { - return listOf(DesireBuilder(condition).applySuspending(initializer).build()) -} diff --git a/src/commonMain/kotlin/core/ai/desire/DesireResource.kt b/src/commonMain/kotlin/core/ai/desire/DesireResource.kt deleted file mode 100644 index c10e420e7..000000000 --- a/src/commonMain/kotlin/core/ai/desire/DesireResource.kt +++ /dev/null @@ -1,5 +0,0 @@ -package core.ai.desire - -interface DesireResource { - suspend fun values(): List -} \ No newline at end of file diff --git a/src/commonMain/kotlin/core/ai/desire/DesireTree.kt b/src/commonMain/kotlin/core/ai/desire/DesireTree.kt deleted file mode 100644 index 90f7e8952..000000000 --- a/src/commonMain/kotlin/core/ai/desire/DesireTree.kt +++ /dev/null @@ -1,22 +0,0 @@ -package core.ai.desire - -import core.thing.Thing - -typealias PrioritizedAgendaName = Pair - -data class DesireTree( - private val condition: suspend (Thing) -> Boolean?, - private val agendas: List = listOf(), - private val children: List = listOf() -) { - - suspend fun getDesires(source: Thing): List { - return if (condition(source) == true) { - agendas + children.flatMap { it.getDesires(source) } - } else listOf() - } - - fun getAllDesires(): List { - return agendas + children.flatMap { it.getAllDesires() } - } -} \ No newline at end of file diff --git a/src/commonMain/kotlin/core/ai/desire/DesiresCollection.kt b/src/commonMain/kotlin/core/ai/desire/DesiresCollection.kt deleted file mode 100644 index 15e7837ff..000000000 --- a/src/commonMain/kotlin/core/ai/desire/DesiresCollection.kt +++ /dev/null @@ -1,6 +0,0 @@ -package core.ai.desire -import core.ai.desire.DesireTree - -interface DesiresCollection { - suspend fun values(): List -} \ No newline at end of file diff --git a/src/commonMain/kotlin/core/ai/desire/DesiresGenerated.kt b/src/commonMain/kotlin/core/ai/desire/DesiresGenerated.kt deleted file mode 100644 index a24420775..000000000 --- a/src/commonMain/kotlin/core/ai/desire/DesiresGenerated.kt +++ /dev/null @@ -1,5 +0,0 @@ -package core.ai.desire - -class DesiresGenerated : DesiresCollection { - override suspend fun values() = listOf(resources.ai.desire.CommonDesires()).flatMap { it.values() } -} \ No newline at end of file diff --git a/src/commonMain/kotlin/core/thing/ThingBuilder.kt b/src/commonMain/kotlin/core/thing/ThingBuilder.kt index 2f53f7313..249503ff1 100644 --- a/src/commonMain/kotlin/core/thing/ThingBuilder.kt +++ b/src/commonMain/kotlin/core/thing/ThingBuilder.kt @@ -152,10 +152,6 @@ class ThingBuilder(internal val name: String) { this.ai = PlayerControlledAI() } - fun conditionalAI() { - this.ai = ConditionalAI() - } - fun packageAI(packageName: String) { this.ai = PackageBasedAI(AIPackageManager.aiPackages[packageName]!!) } diff --git a/src/commonMain/kotlin/core/thing/creature/CreatureManager.kt b/src/commonMain/kotlin/core/thing/creature/CreatureManager.kt index acb552de9..a8f19e9a3 100644 --- a/src/commonMain/kotlin/core/thing/creature/CreatureManager.kt +++ b/src/commonMain/kotlin/core/thing/creature/CreatureManager.kt @@ -3,14 +3,12 @@ package core.thing.creature import building.ModManager import core.DependencyInjector import core.TagKey -import core.ai.AIManager import core.startupLog import core.thing.Thing import core.thing.build import core.thing.thing import core.utility.Backer import core.utility.NameSearchableList -import core.utility.lazyM import core.utility.toNameSearchableList import status.stat.* import traveling.location.location.LocationThing diff --git a/src/commonMain/kotlin/explore/look/DescribeMind.kt b/src/commonMain/kotlin/explore/look/DescribeMind.kt index 494b0f4f3..b40706ec3 100644 --- a/src/commonMain/kotlin/explore/look/DescribeMind.kt +++ b/src/commonMain/kotlin/explore/look/DescribeMind.kt @@ -2,7 +2,6 @@ package explore.look import core.GameState import core.Player -import core.ai.ConditionalAI import core.ai.DumbAI import core.ai.PackageBasedAI import core.ai.PlayerControlledAI @@ -16,29 +15,13 @@ fun describeMind(source: Player, thing: Thing, mind: Mind) { mind.ai is PlayerControlledAI || thing.isPlayer() -> source.displayToMe("Who can fathom the thought of ${thing.getDisplayName()}?") mind.ai is DumbAI -> source.displayToMe("${thing.getDisplayName()} is without thought.") !GameState.getDebugBoolean(DebugType.CLARITY) -> source.displayToMe("You are unable to perceive the thought of ${thing.getDisplayName()}.") - mind.ai is ConditionalAI -> ponderThing(source, thing, mind.ai) mind.ai is PackageBasedAI -> ponderThing(source, thing, mind.ai) else -> source.displayToMe("You are unable to perceive the thought of ${thing.getDisplayName()}.") } } -private fun ponderThing(source: Player, thing: Thing, ai: ConditionalAI) { - var message = thing.getDisplayName() - with(ai) { - if (goal == null) { - message += "\n\thas no goal" - } else { - message += "\n\tis working towards ${goal?.name ?: ""}" - message += "\n\tis on step ${goal?.progress}: ${goal?.steps?.get(goal?.progress ?: 0)?.name ?: ""}" - } - source.displayToMe(message) - } -} - private fun ponderThing(source: Player, thing: Thing, ai: PackageBasedAI) { var message = thing.getDisplayName() + " has take the following actions (top first)" - with(ai) { - message += "\n\t" + ai.previousIdeas.reversed().joinToString("\n\t") - source.displayToMe(message) - } + message += "\n\t" + ai.previousIdeas.reversed().joinToString("\n\t") + source.displayToMe(message) } diff --git a/src/commonMain/kotlin/resources/ai/agenda/CommonAgendas.kt b/src/commonMain/kotlin/resources/ai/agenda/CommonAgendas.kt deleted file mode 100644 index 903681ce6..000000000 --- a/src/commonMain/kotlin/resources/ai/agenda/CommonAgendas.kt +++ /dev/null @@ -1,218 +0,0 @@ -package resources.ai.agenda - -import combat.DamageType -import combat.attack.AttackEvent -import combat.attack.startAttack -import core.FactKind -import core.GameState -import core.ai.agenda.AgendaResource -import core.ai.agenda.agendas -import core.ai.knowledge.DiscoverFactEvent -import core.ai.knowledge.Fact -import core.ai.knowledge.Subject -import core.thing.Thing -import core.utility.RandomManager -import status.rest.RestEvent -import traveling.location.network.LocationNode -import traveling.move.startMoveEvent -import traveling.position.ThingAim -import traveling.routes.FindRouteEvent -import traveling.travel.TravelStartEvent -import use.eat.EatFoodEvent -import use.interaction.InteractEvent -import use.interaction.nothing.NothingEvent - -class CommonAgendas : AgendaResource { - override val values = agendas { - - agendaAction("Attack") { creature -> - creature.mind.getAggroTarget()?.let { target -> - clawAttack(target, creature) - } - } - - agenda("Eat Food") { - agenda("Search For Food") - agenda("Move to Use Target") - agenda("Eat Targeted Food") - } - - agendaAction("Eat Targeted Food") { creature -> - creature.mind.getUseTargetThing()?.let { target -> - EatFoodEvent(creature, target) - } - } - - agenda("Hunt") { - agenda("Search For Enemy") - agenda("Move to Aggro Target") - agenda("Attack") - } - - agenda("Move to Aggro Target") { - actionDetailed("Move to Aggro Target") { - shouldSkip { creature -> - creature.mind.getAggroTarget()?.position?.let { - creature.canReach(it) - } - } - result { creature -> - creature.mind.getAggroTarget()?.position?.let { - startMoveEvent(creature, destination = it) - } - } - } - } - - agenda("Travel to Location") { - actionDetailed("Identify Route") { - shouldSkip { s -> - val goal = s.mind.knownLocationByKind("LocationGoal") - goal == null || s.location == goal || s.mind.route?.destination == goal - } - result { s -> - s.mind.knownLocationByKind("LocationGoal")?.let { - FindRouteEvent(s, s.location, it) - } - } - } - actionDetailed("Travel to Location") { - shouldSkip { s -> - s.location == s.mind.knownLocationByKind("LocationGoal") - } - result { s -> - s.mind.route?.let { - TravelStartEvent(s, destination = it.getNextStep(s.location).destination.location) - } - } - } - } - - agenda("Move to Use Target") { - actionDetailed("Move to Use Target") { - shouldSkip { creature -> - creature.mind.getUseTargetThing()?.position?.let { - creature.canReach(it) - } - } - result { creature -> - creature.mind.getUseTargetThing()?.position?.let { - startMoveEvent(creature, destination = it) - } - } - } - } - - agenda("Nothing") { - action("Nothing") { creature -> NothingEvent(creature) } - } - - agenda("Converse") { - action("Converse") { creature -> NothingEvent(creature) } - } - - agenda("Scratch Tree") { - action("Find Tree") { owner -> - val target = owner.location.getLocation().getActivators(perceivedBy = owner).firstOrNull { it.name.contains("Tree") } - target?.let { - owner.discover(target, "useTarget") - } - } - - agenda("Move to Use Target") - - action("Scratch Tree") { creature -> - creature.mind.getUseTargetThing()?.let { target -> - clawAttack(target, creature) - } - } - } - - agendaAction("Search For Enemy") { owner -> - val target = owner.location.getLocation().getCreatures(perceivedBy = owner).firstOrNull { !it.properties.tags.has("Predator") } - target?.let { - owner.discover(target, FactKind.AGGRO_TARGET) - } - } - - agendaAction("Search For Food") { owner -> - val target = (owner.inventory.getItems() + owner.location.getLocation().getItems(perceivedBy = owner)).firstOrNull { it.properties.tags.has("Food") } - target?.let { - owner.discover(target, FactKind.USE_TARGET) - } - } - - agenda("Rest") { - action("Rest") { creature -> RestEvent(creature, 2) } - } - - agenda("Sleep In Bed") { - actions("Find Bed") { owner -> - owner.mind.knownThingByKind("MyBed")?.let { target -> - listOf( - owner.discover(target, FactKind.USE_TARGET), - owner.discover(target.location, "LocationGoal") - ) - } - } - agenda("Travel to Location") - agenda("Move to Use Target") - agenda("Interact Target") - } - - agendaAction("Wander") { creature -> - val target = creature.location.getLocation().getThings(creature).random() - startMoveEvent(creature, destination = target.position) - } - - agenda("Travel to Job Site") { - action("Find Work Site") { owner -> - owner.mind.knownLocationByKind("MyWorkplace")?.let { target -> - owner.discover(target, "LocationGoal") - } - } - agenda("Travel to Location") - } - - agenda("Work At Job Site") { - action("Find Workable Activator") { s -> - s.mind.knows("WorkTags")?.sources?.mapNotNull { it.topic }?.let { tags -> - s.location.getLocation().getActivators(s).firstOrNull { it.properties.tags.hasAll(tags) }?.let { target -> - s.discover(target, "useTarget") - } - } - } - agenda("Move to Use Target") - agenda("Interact Target") - } - - agendaAction("Interact Target") { creature -> - creature.mind.getUseTargetThing()?.let { target -> - InteractEvent(creature, target) - } - } - } -} - -private fun Thing.discover(target: Thing, kind: String): DiscoverFactEvent { - return DiscoverFactEvent(this, Fact(Subject(target), kind)) -} - -private fun Thing.discover(target: LocationNode, kind: String): DiscoverFactEvent { - return DiscoverFactEvent(this, Fact(Subject(target), kind)) -} - -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) -} diff --git a/src/commonMain/kotlin/resources/ai/desire/CommonDesires.kt b/src/commonMain/kotlin/resources/ai/desire/CommonDesires.kt deleted file mode 100644 index 474d2b71b..000000000 --- a/src/commonMain/kotlin/resources/ai/desire/CommonDesires.kt +++ /dev/null @@ -1,70 +0,0 @@ -package resources.ai.desire - -import core.GameState -import core.ai.desire.DesireResource -import core.ai.desire.desires -import core.ai.packages.perceivedActivators -import core.ai.packages.perceivedCreatures -import core.ai.packages.perceivedItems -import core.commands.CommandParsers -import status.stat.STAMINA - -class CommonDesires : DesireResource { - override suspend fun values() = desires { - agenda("Nothing") - agenda("Wander") - - cond({ s -> s.soul.getCurrent(STAMINA) < s.soul.getTotal(STAMINA) / 10 }) { - agenda("Rest") - } - - cond({ source -> source.mind.getAggroTarget() != null }) { - priority = 70 - agenda("Attack") - } - - tag("Commoner") { - cond({ _ -> GameState.timeManager.getPercentDayComplete() in listOf(25, 50, 75) }) { - additionalPriority = 2 - agenda("Eat Food") - } - cond({ s -> CommandParsers.getConversations().any { it.containsParticipant(s) } }) { - priority = 65 - agenda("Converse") - } - - cond({ s -> GameState.timeManager.isWorkHours() && s.mind.locationByKindExists("MyWorkplace") }) { - cond({ s -> s.location != s.mind.knownLocationByKind("MyWorkplace") }) { - agenda("Travel to Job Site") - } - agenda("Work At Job Site") - } - - cond({ _ -> !GameState.timeManager.isNight() }) { - cond({ s -> s.mind.thingByKindExists("MyBed") }) { - agenda("Sleep In Bed") - } - agenda("Rest") - } - } - - tag("Predator") { - cond({ _ -> !GameState.timeManager.isNight() }) { - agenda("Rest") - } - - cond({ s -> s.perceivedItems().firstOrNull { it.properties.tags.has("Food") } != null }) { - agenda("Eat Food") - } - - cond(20, { s -> s.perceivedActivators().firstOrNull { it.name.contains("Tree") } != null }) { - agenda("Scratch Tree") - } - //Eventually use factions + actions to create how much something likes something else - cond({ s -> s.perceivedCreatures().firstOrNull { !it.properties.tags.has("Predator") } != null }) { - additionalPriority = 10 - agenda("Hunt") - } - } - } -} diff --git a/src/jvmMain/kotlin/building/ModLoader.kt b/src/jvmMain/kotlin/building/ModLoader.kt index eabe6066b..e4333e75c 100644 --- a/src/jvmMain/kotlin/building/ModLoader.kt +++ b/src/jvmMain/kotlin/building/ModLoader.kt @@ -1,8 +1,8 @@ package building import conversation.dsl.DialogueTreeResource -import core.ai.agenda.AgendaResource import core.ai.behavior.BehaviorResource +import core.ai.packages.AIPackageTemplateResource import core.body.BodyPartResource import core.body.BodyResource import core.events.EventListener @@ -22,10 +22,10 @@ import traveling.location.weather.WeatherResource import java.io.File import java.lang.reflect.ParameterizedType import java.net.URI -import java.net.URL import java.net.URLClassLoader import java.util.jar.JarEntry import java.util.jar.JarFile +import kotlin.jvm.java fun loadMods() { ModManager.reset() @@ -77,7 +77,7 @@ private suspend fun loadJar(jarFile: File) { when { c.superclass == EventListener::class.java -> processListeners(c as Class>) c.interfaces.contains(ActivatorResource::class.java) -> processActivator(c as Class) - c.interfaces.contains(AgendaResource::class.java) -> processAgenda(c as Class) + c.interfaces.contains(AIPackageTemplateResource::class.java) -> processAIPackageTemplateResource(c as Class) c.interfaces.contains(BehaviorResource::class.java) -> processBehavior(c as Class) c.interfaces.contains(BodyResource::class.java) -> processBody(c as Class) c.interfaces.contains(BodyPartResource::class.java) -> processBodyPart(c as Class) @@ -103,7 +103,7 @@ private fun processListeners(c: Class>) { } private suspend fun processActivator(c: Class) = ModManager.activators.addAll(c.getDeclaredConstructor().newInstance().values()) -private fun processAgenda(c: Class) = ModManager.agendas.addAll(c.getDeclaredConstructor().newInstance().values) +private fun processAIPackageTemplateResource(c: Class) = ModManager.aiPackages.addAll(c.getDeclaredConstructor().newInstance().values) private fun processBehavior(c: Class) = ModManager.behaviors.addAll(c.getDeclaredConstructor().newInstance().values) private fun processBody(c: Class) = ModManager.bodies.addAll(c.getDeclaredConstructor().newInstance().values) private fun processBodyPart(c: Class) = ModManager.bodyParts.addAll(c.getDeclaredConstructor().newInstance().values) diff --git a/src/jvmTest/kotlin/TestConstructors.kt b/src/jvmTest/kotlin/TestConstructors.kt index 94932b1cf..9fbceab81 100644 --- a/src/jvmTest/kotlin/TestConstructors.kt +++ b/src/jvmTest/kotlin/TestConstructors.kt @@ -3,14 +3,10 @@ import conversation.dsl.DialoguesMock import core.DependencyInjector import core.GameState import core.TagKey -import core.ai.AIManager -import core.ai.agenda.AgendasCollection -import core.ai.agenda.AgendasMock +import core.ai.AIPackageManager import core.ai.behavior.BehaviorManager import core.ai.behavior.BehaviorsCollection import core.ai.behavior.BehaviorsMock -import core.ai.desire.DesiresCollection -import core.ai.desire.DesiresMock import core.body.* import core.commands.CommandParsers import core.commands.CommandsCollection @@ -138,9 +134,7 @@ fun createMockedGame() { DependencyInjector.setImplementation(ActivatorsCollection::class, ActivatorsMock()) ActivatorManager.reset() - DependencyInjector.setImplementation(DesiresCollection::class, DesiresMock()) - DependencyInjector.setImplementation(AgendasCollection::class, AgendasMock()) - AIManager.reset() + AIPackageManager.reset() DependencyInjector.setImplementation(BehaviorsCollection::class, BehaviorsMock()) BehaviorManager.reset() diff --git a/src/jvmTest/kotlin/core/ai/agenda/AgendasMock.kt b/src/jvmTest/kotlin/core/ai/agenda/AgendasMock.kt deleted file mode 100644 index def307e4a..000000000 --- a/src/jvmTest/kotlin/core/ai/agenda/AgendasMock.kt +++ /dev/null @@ -1,3 +0,0 @@ -package core.ai.agenda - -class AgendasMock(override val values: List = listOf()) : AgendasCollection \ No newline at end of file diff --git a/src/jvmTest/kotlin/core/ai/desire/DesiresMock.kt b/src/jvmTest/kotlin/core/ai/desire/DesiresMock.kt deleted file mode 100644 index 85610dc35..000000000 --- a/src/jvmTest/kotlin/core/ai/desire/DesiresMock.kt +++ /dev/null @@ -1,5 +0,0 @@ -package core.ai.desire - -class DesiresMock(val values: List = listOf()) : DesiresCollection { - override suspend fun values() = values -} \ No newline at end of file diff --git a/src/jvmTestIntegration/kotlin/validation/AIPackageValidator.kt b/src/jvmTestIntegration/kotlin/validation/AIPackageValidator.kt index b6b68ae8a..2ac235722 100644 --- a/src/jvmTestIntegration/kotlin/validation/AIPackageValidator.kt +++ b/src/jvmTestIntegration/kotlin/validation/AIPackageValidator.kt @@ -14,7 +14,7 @@ import kotlin.test.assertEquals class AIPackageValidator { private val packages = runBlocking { AIPackageManager.aiPackages } - private val templates = (DependencyInjector.getImplementation(AIPackageTemplatesCollection::class).values + ModManager.ai2) + private val templates = (DependencyInjector.getImplementation(AIPackageTemplatesCollection::class).values + ModManager.aiPackages) @Test fun validate() { diff --git a/src/jvmTestIntegration/kotlin/validation/DesireValidator.kt b/src/jvmTestIntegration/kotlin/validation/DesireValidator.kt deleted file mode 100644 index 598ee72cd..000000000 --- a/src/jvmTestIntegration/kotlin/validation/DesireValidator.kt +++ /dev/null @@ -1,46 +0,0 @@ -package validation - -import core.ai.AIManager -import core.ai.desire.DesireTree -import kotlinx.coroutines.runBlocking -import kotlin.test.Test -import kotlin.test.assertEquals - - -class DesireValidator { - private val agendas = AIManager.agendas - - @Test - fun validate() { - val desires = runBlocking { AIManager.getDesires() } - assertEquals(0, noGoalsWithoutAgendas(desires) + noAgendaOrphans()) - } - - private fun noGoalsWithoutAgendas(desires: List): Int { - var warnings = 0 - desires.forEach { desireTree -> - desireTree.getAllDesires().map { it.first }.forEach { desire -> - if (agendas[desire] == null) { - println("WARN: Could not find agenda '${desire}' for desire '${desire}'.") - warnings++ - } - } - } - return warnings - } - - private fun noAgendaOrphans(): Int { - var warnings = 0 - agendas.values.forEach { agenda -> - agenda.steps.mapNotNull { it.agendaName }.forEach { reference -> - if (agendas[reference] == null) { - println("WARN: Could not find agenda '${reference}' referenced by agenda '${agenda}'.") - warnings++ - } - } - } - return warnings - } - - -} diff --git a/src/jvmTools/kotlin/building/ReflectionTools.kt b/src/jvmTools/kotlin/building/ReflectionTools.kt index 59bbaa5f3..e1649af27 100644 --- a/src/jvmTools/kotlin/building/ReflectionTools.kt +++ b/src/jvmTools/kotlin/building/ReflectionTools.kt @@ -2,14 +2,10 @@ package building import conversation.dsl.DialogueTree import conversation.dsl.DialogueTreeResource -import core.ai.agenda.Agenda -import core.ai.agenda.AgendaResource import core.ai.behavior.Behavior import core.ai.behavior.BehaviorResource import core.ai.packages.AIPackageTemplate import core.ai.packages.AIPackageTemplateResource -import core.ai.desire.DesireResource -import core.ai.desire.DesireTree import core.body.BodyPartResource import core.body.BodyResource import core.commands.Command @@ -62,13 +58,11 @@ object ReflectionTools { generateListenerMapFile() generateResourcesFile(ActivatorResource::class, ThingBuilder::class) - generateResourcesFile(AgendaResource::class, Agenda::class) generateResourcesFile(AIPackageTemplateResource::class, AIPackageTemplate::class) generateResourcesFile(ConditionResource::class, ConditionRecipe::class) generateResourcesFile(BodyResource::class, NetworkBuilder::class) generateResourcesFile(BodyPartResource::class, LocationRecipeBuilder::class) generateResourcesFile(CreatureResource::class, ThingBuilder::class) - generateResourcesFile(DesireResource::class, DesireTree::class) generateResourcesFile(ItemResource::class, ThingBuilder::class) generateResourcesFile(BehaviorResource::class, Behavior::class) generateResourcesFile(DialogueTreeResource::class, DialogueTree::class) @@ -282,4 +276,4 @@ object ReflectionTools { } -} \ No newline at end of file +} From c4b32b15178c26cc5e4fa7e0513e0444c3839ddd Mon Sep 17 00:00:00 2001 From: ManApart Date: Wed, 31 Dec 2025 10:53:10 -0500 Subject: [PATCH 29/45] tend wheat --- src/commonMain/kotlin/core/MagicStrings.kt | 2 ++ .../ai/packages/AIPackageTemplatesGenerated.kt | 2 +- src/commonMain/kotlin/core/thing/ThingBuilder.kt | 1 + .../kotlin/quests/ConditionalEventsBuilder.kt | 16 ++++++++++------ .../{CommonPackages.kt => CommonAIPackages.kt} | 2 +- .../{CreaturePackage.kt => CreatureAIPackage.kt} | 3 +-- .../{PeasantPackage.kt => PeasantAIPackage.kt} | 2 +- .../resources/behaviors/CommonBehaviors.kt | 11 ++++++++++- .../thing/activators/CommonActivators.kt | 6 ++++-- .../resources/thing/creature/CommonCreatures.kt | 7 +++++-- .../kotlin/validation/AIPackageValidator.kt | 7 ++----- 11 files changed, 38 insertions(+), 21 deletions(-) rename src/commonMain/kotlin/resources/ai/packages/{CommonPackages.kt => CommonAIPackages.kt} (96%) rename src/commonMain/kotlin/resources/ai/packages/{CreaturePackage.kt => CreatureAIPackage.kt} (97%) rename src/commonMain/kotlin/resources/ai/packages/{PeasantPackage.kt => PeasantAIPackage.kt} (98%) diff --git a/src/commonMain/kotlin/core/MagicStrings.kt b/src/commonMain/kotlin/core/MagicStrings.kt index 2584b236d..c9fd4fac0 100644 --- a/src/commonMain/kotlin/core/MagicStrings.kt +++ b/src/commonMain/kotlin/core/MagicStrings.kt @@ -8,7 +8,9 @@ object AIPackageKeys { object TagKey { const val CREATURE = "Creature" + const val COMMONER = "Commoner" const val FOOD = "Food" + const val FARMABLE = "Farmable" const val PREDATOR = "Predator" const val SOUND_DESCRIPTION = "Sound Description" const val SOUND_LEVEL = "Sound Level" diff --git a/src/commonMain/kotlin/core/ai/packages/AIPackageTemplatesGenerated.kt b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplatesGenerated.kt index 0003ccf27..2e4a08c40 100644 --- a/src/commonMain/kotlin/core/ai/packages/AIPackageTemplatesGenerated.kt +++ b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplatesGenerated.kt @@ -1,5 +1,5 @@ package core.ai.packages class AIPackageTemplatesGenerated : AIPackageTemplatesCollection { - override val values by lazy { listOf(resources.ai.packages.CommonPackages(), resources.ai.packages.PeasantPackage(), resources.ai.packages.CreaturePackage()).flatMap { it.values }} + override val values by lazy { listOf(resources.ai.packages.CommonAIPackages(), resources.ai.packages.PeasantAIPackage(), resources.ai.packages.CreatureAIPackage()).flatMap { it.values }} } diff --git a/src/commonMain/kotlin/core/thing/ThingBuilder.kt b/src/commonMain/kotlin/core/thing/ThingBuilder.kt index 249503ff1..ad115ae94 100644 --- a/src/commonMain/kotlin/core/thing/ThingBuilder.kt +++ b/src/commonMain/kotlin/core/thing/ThingBuilder.kt @@ -258,6 +258,7 @@ class ThingBuilder(internal val name: String) { return when { props.tags.has(TagKey.PREDATOR) -> PackageBasedAI(AIPackageManager.aiPackages[AIPackageKeys.PREDATOR]!!) props.tags.has(TagKey.CREATURE) -> PackageBasedAI(AIPackageManager.aiPackages[AIPackageKeys.CREATURE]!!) + props.tags.has(TagKey.COMMONER) -> PackageBasedAI(AIPackageManager.aiPackages[AIPackageKeys.PEASANT]!!) else -> DumbAI() } } diff --git a/src/commonMain/kotlin/quests/ConditionalEventsBuilder.kt b/src/commonMain/kotlin/quests/ConditionalEventsBuilder.kt index fd7d17ed3..74c557ad2 100644 --- a/src/commonMain/kotlin/quests/ConditionalEventsBuilder.kt +++ b/src/commonMain/kotlin/quests/ConditionalEventsBuilder.kt @@ -5,7 +5,7 @@ import core.utility.MapBuilder import core.utility.applySuspending import kotlin.reflect.KClass -class ConditionalEventsBuilder(private val trigger: KClass) { +class ConditionalEventsBuilder(private val trigger: KClass) { private val paramsBuilder = MapBuilder() private var condition: (E, Map) -> Boolean = { _, _ -> true } private var createEvents: suspend (E, Map) -> List = { _, _ -> listOf() } @@ -14,20 +14,24 @@ class ConditionalEventsBuilder(private val trigger: KClass) { return ConditionalEvents(trigger, condition, createEvents, paramsBuilder.build()) } - fun condition(cond: (E, Map) -> Boolean){ + fun condition(cond: (E, Map) -> Boolean) { this.condition = cond } - fun events(createEvents: suspend (E, Map) -> List){ + fun events(createEvents: suspend (E, Map) -> List) { this.createEvents = createEvents } + fun event(createEvent: suspend (E, Map) -> Event) { + this.createEvents = { e, params -> listOf(createEvent(e, params)) } + } + fun param(vararg values: Pair) = this.paramsBuilder.entry(values.toList()) fun param(key: String, value: String) = paramsBuilder.entry(key, value) fun param(key: String, value: Int) = paramsBuilder.entry(key, value) - + } -fun condition(trigger: KClass, initializer: ConditionalEventsBuilder.() -> Unit): ConditionalEvents<*> { +fun condition(trigger: KClass, initializer: ConditionalEventsBuilder.() -> Unit): ConditionalEvents<*> { return ConditionalEventsBuilder(trigger).apply(initializer).build() -} \ No newline at end of file +} diff --git a/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt b/src/commonMain/kotlin/resources/ai/packages/CommonAIPackages.kt similarity index 96% rename from src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt rename to src/commonMain/kotlin/resources/ai/packages/CommonAIPackages.kt index afac4dbc1..99ab9bd0d 100644 --- a/src/commonMain/kotlin/resources/ai/packages/CommonPackages.kt +++ b/src/commonMain/kotlin/resources/ai/packages/CommonAIPackages.kt @@ -10,7 +10,7 @@ import core.ai.knowledge.setUseTarget import core.ai.packages.* import status.rest.RestEvent -class CommonPackages : AIPackageTemplateResource { +class CommonAIPackages : AIPackageTemplateResource { override val values = aiPackages { aiPackage("Predator") { template("Creature") diff --git a/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt b/src/commonMain/kotlin/resources/ai/packages/CreatureAIPackage.kt similarity index 97% rename from src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt rename to src/commonMain/kotlin/resources/ai/packages/CreatureAIPackage.kt index bc123025d..53e144b0d 100644 --- a/src/commonMain/kotlin/resources/ai/packages/CreaturePackage.kt +++ b/src/commonMain/kotlin/resources/ai/packages/CreatureAIPackage.kt @@ -7,7 +7,6 @@ import core.TagKey import core.ai.knowledge.clearUseGoal import core.ai.knowledge.setUseTarget import core.ai.packages.* -import core.utility.getRandomRange import core.utility.random import status.rest.RestEvent import status.stat.STAMINA @@ -16,7 +15,7 @@ import traveling.travel.TravelStartEvent import use.eat.EatFoodEvent import use.interaction.InteractEvent -class CreaturePackage : AIPackageTemplateResource { +class CreatureAIPackage : AIPackageTemplateResource { override val values = aiPackages { aiPackage("Creature") { diff --git a/src/commonMain/kotlin/resources/ai/packages/PeasantPackage.kt b/src/commonMain/kotlin/resources/ai/packages/PeasantAIPackage.kt similarity index 98% rename from src/commonMain/kotlin/resources/ai/packages/PeasantPackage.kt rename to src/commonMain/kotlin/resources/ai/packages/PeasantAIPackage.kt index e605f210b..e12da30d4 100644 --- a/src/commonMain/kotlin/resources/ai/packages/PeasantPackage.kt +++ b/src/commonMain/kotlin/resources/ai/packages/PeasantAIPackage.kt @@ -12,7 +12,7 @@ import core.ai.packages.perceivedActivators import core.ai.packages.plotRouteAndStartTravel import core.commands.CommandParsers -class PeasantPackage : AIPackageTemplateResource { +class PeasantAIPackage : AIPackageTemplateResource { override val values = aiPackages { aiPackage(PEASANT) { template("Creature") diff --git a/src/commonMain/kotlin/resources/behaviors/CommonBehaviors.kt b/src/commonMain/kotlin/resources/behaviors/CommonBehaviors.kt index 303bdd5f0..ef021217c 100644 --- a/src/commonMain/kotlin/resources/behaviors/CommonBehaviors.kt +++ b/src/commonMain/kotlin/resources/behaviors/CommonBehaviors.kt @@ -91,6 +91,15 @@ class CommonBehaviors : BehaviorResource { } } + //TODO - make message to all who perceive it + behavior("Tend Crop", InteractEvent::class) { + events { event, params -> + listOfNotNull( + eventWithPlayer(event.creature) { MessageEvent(it, params["message"] ?: "You tend the ${event.interactionTarget.name}.") }, + ) + } + } + behavior("Restrict Destination", InteractEvent::class) { events { event, params -> val source = event.creature @@ -155,4 +164,4 @@ class CommonBehaviors : BehaviorResource { } } } -} \ No newline at end of file +} diff --git a/src/commonMain/kotlin/resources/thing/activators/CommonActivators.kt b/src/commonMain/kotlin/resources/thing/activators/CommonActivators.kt index 5f693360a..e0a664597 100644 --- a/src/commonMain/kotlin/resources/thing/activators/CommonActivators.kt +++ b/src/commonMain/kotlin/resources/thing/activators/CommonActivators.kt @@ -1,5 +1,6 @@ package resources.thing.activators +import core.TagKey import core.properties.CONTAINER import core.properties.OPEN import core.properties.SIZE @@ -43,6 +44,7 @@ class CommonActivators : ActivatorResource { description("The golden shafts of wheat whisper as they brush against each other.") sound(5, "a faint rustling sound") param("fireHealth" to 2, "itemName" to "Wheat Field") + behavior("Tend Crop") behavior( "Slash Harvest", "itemName" to "Wheat Bundle", @@ -50,7 +52,7 @@ class CommonActivators : ActivatorResource { "count" to 3 ) props { - tag("Farmable") + tag(TagKey.FARMABLE) } } @@ -210,4 +212,4 @@ class CommonActivators : ActivatorResource { } -} \ No newline at end of file +} diff --git a/src/commonMain/kotlin/resources/thing/creature/CommonCreatures.kt b/src/commonMain/kotlin/resources/thing/creature/CommonCreatures.kt index 97b97be9f..1d2ed3081 100644 --- a/src/commonMain/kotlin/resources/thing/creature/CommonCreatures.kt +++ b/src/commonMain/kotlin/resources/thing/creature/CommonCreatures.kt @@ -1,5 +1,8 @@ package resources.thing.creature +import core.FactKind.WORK_TAGS +import core.TagKey.COMMONER +import core.TagKey.FARMABLE import core.TagKey.PREDATOR import core.thing.creature.CreatureResource import core.thing.things @@ -27,10 +30,10 @@ class CommonCreatures : CreatureResource { soul("Strength", 3) soul("Bare Handed", 2) mind{ - learn("WorkTags", listOf("Farmable")) + learn(WORK_TAGS, listOf(FARMABLE)) } props { - tag("Commoner") + tag(COMMONER) value("Race", "Human") } item("Brown Pants", "Old Shirt") diff --git a/src/jvmTestIntegration/kotlin/validation/AIPackageValidator.kt b/src/jvmTestIntegration/kotlin/validation/AIPackageValidator.kt index 2ac235722..9a56d57e9 100644 --- a/src/jvmTestIntegration/kotlin/validation/AIPackageValidator.kt +++ b/src/jvmTestIntegration/kotlin/validation/AIPackageValidator.kt @@ -8,9 +8,6 @@ import kotlinx.coroutines.runBlocking import kotlin.test.Test import kotlin.test.assertEquals -//TODO validate all things/minds reference valid package name -// - Check things builder - class AIPackageValidator { private val packages = runBlocking { AIPackageManager.aiPackages } @@ -21,7 +18,7 @@ class AIPackageValidator { assertEquals( 0, noDuplicatePackageNames() + noDuplicateIdeaNames() + - subPackageStringReferenceExists() + packageTemplateStringReferenceExists() ) } @@ -55,7 +52,7 @@ class AIPackageValidator { return warnings } - private fun subPackageStringReferenceExists(): Int { + private fun packageTemplateStringReferenceExists(): Int { var warnings = 0 val packageRef = templates.map { it.name }.toSet() From 9b3add65b8ed92ff2b5dcf1c650a44549d71986b Mon Sep 17 00:00:00 2001 From: ManApart Date: Wed, 31 Dec 2025 11:22:21 -0500 Subject: [PATCH 30/45] tending crops shows up to others; message can be public --- src/commonMain/kotlin/core/MagicStrings.kt | 7 +++ src/commonMain/kotlin/core/MessageHandler.kt | 11 +++-- src/commonMain/kotlin/core/history/Display.kt | 3 +- .../resources/behaviors/CommonBehaviors.kt | 47 ++++++++++--------- .../kotlin/system/message/MessageEvent.kt | 5 +- 5 files changed, 45 insertions(+), 28 deletions(-) diff --git a/src/commonMain/kotlin/core/MagicStrings.kt b/src/commonMain/kotlin/core/MagicStrings.kt index c9fd4fac0..f130bcae4 100644 --- a/src/commonMain/kotlin/core/MagicStrings.kt +++ b/src/commonMain/kotlin/core/MagicStrings.kt @@ -43,3 +43,10 @@ object NetworkKeys { const val PLAYER_START_NETWORK = "Kanbara Countryside" const val PLAYER_START_LOCATION = "An Open Field" } + +object ParameterKeys { + const val COUNT = "count" + const val MESSAGE = "message" + const val MESSAGE_TO_OTHERS = "messageToOthers" + const val ITEM_NAME = "itemName" +} diff --git a/src/commonMain/kotlin/core/MessageHandler.kt b/src/commonMain/kotlin/core/MessageHandler.kt index aba4d9a40..a8d7abd42 100644 --- a/src/commonMain/kotlin/core/MessageHandler.kt +++ b/src/commonMain/kotlin/core/MessageHandler.kt @@ -1,12 +1,15 @@ package core import core.events.EventListener -import core.history.display +import core.history.displayToMe +import core.history.displayToOthers import system.message.MessageEvent class MessageHandler : EventListener() { override suspend fun complete(event: MessageEvent) { - event.source.display(event.message) + event.source.displayToMe(event.messageToYou) + if (!event.private) { + event.source.displayToOthers(event.messageToOthers) + } } - -} \ No newline at end of file +} diff --git a/src/commonMain/kotlin/core/history/Display.kt b/src/commonMain/kotlin/core/history/Display.kt index 85b5a1055..8073aecc8 100644 --- a/src/commonMain/kotlin/core/history/Display.kt +++ b/src/commonMain/kotlin/core/history/Display.kt @@ -10,7 +10,6 @@ import system.debug.DebugType * Only displayed to this thing (you) */ fun Player.displayToMe(message: String) = GameLogger.getHistory(this).print(message) - fun Thing.displayToMe(message: String) { GameState.getPlayer(this)?.let { GameLogger.getHistory(it).print(message) @@ -69,4 +68,4 @@ suspend fun Thing.displayToOthers(message: (Player) -> String) { val messageText = message(history.listener) history.print(messageText) } -} \ No newline at end of file +} diff --git a/src/commonMain/kotlin/resources/behaviors/CommonBehaviors.kt b/src/commonMain/kotlin/resources/behaviors/CommonBehaviors.kt index ef021217c..09edee8c8 100644 --- a/src/commonMain/kotlin/resources/behaviors/CommonBehaviors.kt +++ b/src/commonMain/kotlin/resources/behaviors/CommonBehaviors.kt @@ -1,11 +1,15 @@ package resources.behaviors +import core.ParameterKeys +import core.ParameterKeys.ITEM_NAME import core.ai.behavior.BehaviorResource import core.ai.behavior.behaviors import core.commands.commandEvent.CommandEvent import core.eventWithPlayer +import core.properties.COUNT import core.properties.propValChanged.PropertyStatMinnedEvent import core.thing.activator.ActivatorManager +import core.thing.item.ITEM_TAG import core.utility.parseLocation import crafting.DiscoverRecipeEvent import crafting.RecipeManager @@ -37,8 +41,8 @@ class CommonBehaviors : BehaviorResource { } events { event, params -> val treeName = params["treeName"] ?: "tree" - listOfNotNull( - eventWithPlayer(event.thing) { MessageEvent(it, "The $treeName cracks and falls to the ground.") }, + listOf( + MessageEvent(event.thing, "The $treeName cracks and falls to the ground.", private = false), RemoveScopeEvent(event.thing), SpawnActivatorEvent(ActivatorManager.getActivator("Logs"), thingLocation = event.thing.location), SpawnItemEvent(params["resultItemName"] ?: "Apple", params["count"]?.toInt() ?: 1, thingLocation = event.thing.location) @@ -58,8 +62,8 @@ class CommonBehaviors : BehaviorResource { } events { event, params -> val name = params["name"] ?: "object" - listOfNotNull( - eventWithPlayer(event.thing) { MessageEvent(it, "The $name smolders until it is nothing more than ash.") }, + listOf( + MessageEvent(event.thing, "The $name smolders until it is nothing more than ash.", private = false), RemoveScopeEvent(event.thing), SpawnItemEvent("Ash", params["count"]?.toInt() ?: 1, thingLocation = event.thing.location, positionParent = event.thing) ) @@ -71,8 +75,8 @@ class CommonBehaviors : BehaviorResource { event.stat == "fireHealth" && event.thing.soul.hasEffect("On Fire") } events { event, params -> - listOfNotNull( - eventWithPlayer(event.thing) { MessageEvent(it, "The ${event.thing} smolders out and needs to be relit.") }, + listOf( + MessageEvent(event.thing, "The ${event.thing} smolders out and needs to be relit.", private = false), RemoveConditionEvent(event.thing, event.thing.soul.getConditionWithEffect("On Fire")), StatChangeEvent(event.thing, "lighting", "fireHealth", params["fireHealth"]?.toInt() ?: 1) ) @@ -84,19 +88,20 @@ class CommonBehaviors : BehaviorResource { event.used.properties.tags.has("Sharp") } events { event, params -> - listOfNotNull( - eventWithPlayer(event.creature) { MessageEvent(it, params["message"] ?: "You harvest ${event.usedOn} with ${event.used}.") }, - SpawnItemEvent(params["itemName"] ?: "Apple", params["count"]?.toInt() ?: 1, thingLocation = event.usedOn.location, positionParent = event.usedOn) + val message = params[ParameterKeys.MESSAGE] ?: "You harvest ${event.usedOn} with ${event.used}." + val messageToOthers = params[ParameterKeys.MESSAGE_TO_OTHERS] ?: "${event.creature} harvests ${event.usedOn} with ${event.used}." + listOf( + MessageEvent(event.creature, message, messageToOthers, false), + SpawnItemEvent(params[ITEM_NAME] ?: "Apple", params[COUNT]?.toInt() ?: 1, thingLocation = event.usedOn.location, positionParent = event.usedOn) ) } } - //TODO - make message to all who perceive it behavior("Tend Crop", InteractEvent::class) { - events { event, params -> - listOfNotNull( - eventWithPlayer(event.creature) { MessageEvent(it, params["message"] ?: "You tend the ${event.interactionTarget.name}.") }, - ) + event { event, params -> + val message = params[ParameterKeys.MESSAGE] ?: "You tend the ${event.interactionTarget.name}." + val messageToOthers = params[ParameterKeys.MESSAGE_TO_OTHERS] ?: "${event.creature} tends the ${event.interactionTarget.name}." + MessageEvent(event.creature, message, messageToOthers, false) } } @@ -107,8 +112,8 @@ class CommonBehaviors : BehaviorResource { val destinationLocation = parseLocation(params, source, "destinationNetwork", "destinationLocation") val makeRestricted = false val replacement = ActivatorManager.getActivator(params["replacementActivator"] ?: "Logs") - listOfNotNull( - eventWithPlayer(source) { MessageEvent(it, params["message"] ?: "") }, + listOf( + MessageEvent(event.creature, params["message"] ?: ""), RestrictLocationEvent(event.interactionTarget, sourceLocation, destinationLocation, makeRestricted), RemoveScopeEvent(event.interactionTarget), SpawnActivatorEvent(replacement, true, event.interactionTarget.location) @@ -118,8 +123,8 @@ class CommonBehaviors : BehaviorResource { behavior("Rest", InteractEvent::class) { events { event, params -> val hoursRested = params["hoursRested"]?.toIntOrNull() ?: 10 - listOfNotNull( - eventWithPlayer(event.creature) { MessageEvent(it, "You rest for $hoursRested hours.") }, + listOf( + MessageEvent(event.creature, "You rest for $hoursRested hours.", "${event.creature} rests for $hoursRested hours."), RestEvent(event.creature, hoursRested) ) } @@ -136,10 +141,10 @@ class CommonBehaviors : BehaviorResource { val depositLocation = parseLocation(params, event.source, "resultItemNetwork", "resultItemLocation") val depositThing = depositLocation.getLocation().getThings(params["resultContainer"] ?: "Grain Bin").firstOrNull() if (sourceItem == null || depositThing == null) { - listOfNotNull(eventWithPlayer(event.source) { MessageEvent(it, "Unable to Mill.") }) + listOf(MessageEvent(event.source, "Unable to Mill.", "${event.source} fails to mill the ${event.item.name}.")) } else { - listOfNotNull( - eventWithPlayer(event.source) { MessageEvent(it, "The ${event.item.name} slides down the chute and is milled into $resultItem as it collects in the ${depositThing.name}.") }, + listOf( + MessageEvent(event.source, "The ${event.item.name} slides down the chute and is milled into $resultItem as it collects in the ${depositThing.name}.", private = false), RemoveItemEvent(event.source, sourceItem), SpawnItemEvent(resultItem, 1, depositThing) ) diff --git a/src/commonMain/kotlin/system/message/MessageEvent.kt b/src/commonMain/kotlin/system/message/MessageEvent.kt index 575a75d6a..4e6420710 100644 --- a/src/commonMain/kotlin/system/message/MessageEvent.kt +++ b/src/commonMain/kotlin/system/message/MessageEvent.kt @@ -2,5 +2,8 @@ package system.message import core.Player import core.events.Event +import core.thing.Thing -data class MessageEvent(val source: Player, val message: String) : Event \ No newline at end of file +data class MessageEvent(val source: Thing, val messageToYou: String, val messageToOthers: String = messageToYou, val private: Boolean = (messageToYou == messageToOthers)) : Event{ + constructor(source: Player, messageToYou: String, messageToOthers: String = messageToYou, private: Boolean = true) : this(source.thing, messageToYou, messageToOthers, private) +} From 5b7c15ebcd0627c55fccfb34ef72b5581d19a0b7 Mon Sep 17 00:00:00 2001 From: ManApart Date: Wed, 31 Dec 2025 13:58:08 -0500 Subject: [PATCH 31/45] force ai to only evaluate once per set of event processing loops --- src/commonMain/kotlin/core/GameManager.kt | 2 +- src/commonMain/kotlin/core/ai/AI.kt | 2 ++ src/commonMain/kotlin/core/ai/AITurnDirector.kt | 15 +++++++++++---- src/commonMain/kotlin/core/ai/PackageBasedAI.kt | 1 + src/commonMain/kotlin/core/events/EventManager.kt | 12 ++++++++++-- src/commonMain/kotlin/core/thing/ThingBuilder.kt | 2 +- src/commonMain/kotlin/system/debug/DebugType.kt | 2 +- .../kotlin/system/help/ViewHelpEvent.kt | 2 +- .../kotlin/commandCombos/CommandComboTest.kt | 4 ++-- 9 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/commonMain/kotlin/core/GameManager.kt b/src/commonMain/kotlin/core/GameManager.kt index d1756f153..7ab069b5f 100644 --- a/src/commonMain/kotlin/core/GameManager.kt +++ b/src/commonMain/kotlin/core/GameManager.kt @@ -60,8 +60,8 @@ object GameManager { // GameState.properties.values.put(AUTO_SAVE, true) // GameState.putDebug(DebugType.VERBOSE_ACTIONS, testing) // GameState.putDebug(DebugType.VERBOSE_AI, true) +// GameState.properties.values.put(DEBUG_PACKAGE, AIPackageKeys.PEASANT) // GameState.putDebug(DebugType.CLARITY, true) -// GameState.properties.values.put(DEBUG_PACKAGE, AIPackageKeys.PREDATOR) GameState.properties.values.put(AUTO_LOAD, !testing) GameState.putDebug(DebugType.POLL_CONNECTION, !testing) diff --git a/src/commonMain/kotlin/core/ai/AI.kt b/src/commonMain/kotlin/core/ai/AI.kt index bc6d5310c..e4e8546e5 100644 --- a/src/commonMain/kotlin/core/ai/AI.kt +++ b/src/commonMain/kotlin/core/ai/AI.kt @@ -7,6 +7,7 @@ import core.thing.Thing abstract class AI { lateinit var creature: Thing var enabled: Boolean = true + var takenTurn: Boolean = false abstract suspend fun hear(event: DialogueEvent) abstract suspend fun takeAction() @@ -15,6 +16,7 @@ abstract class AI { suspend fun chooseAction() { if (enabled && !creature.isPlayer()) { takeAction() + takenTurn = true } } diff --git a/src/commonMain/kotlin/core/ai/AITurnDirector.kt b/src/commonMain/kotlin/core/ai/AITurnDirector.kt index 7e38d67eb..2fe51bc08 100644 --- a/src/commonMain/kotlin/core/ai/AITurnDirector.kt +++ b/src/commonMain/kotlin/core/ai/AITurnDirector.kt @@ -2,21 +2,28 @@ package core.ai import core.GameState +suspend fun replenishAITurns() { + getCreatureAIs().forEach { it.takenTurn = false } +} + /** Make sure everyone with an AI is doing something Keep the game ticking so long as all creatures have stuff to do If a player doesn't have an action, pause the loop - Returns true if it's a players turn and we need to pause +Returns true if it's a players turn and we need to pause */ suspend fun directAI(): Boolean { - val creatureAIs = GameState.players.values.flatMap { it.thing.location.getLocation().getCreatures(it.thing) }.toSet().map { it.mind.ai } + val creatureAIs = getCreatureAIs() - creatureAIs.filter { it.actions.isEmpty() }.forEach { + creatureAIs.filter { !it.takenTurn && it.actions.isEmpty() }.forEach { it.creature.body.blockHelper.resetStance() it.chooseAction() } //In multiplayer we don't want to force all players to wait on a slow player //Give player a chance to take an action - return creatureAIs.filter { it.creature.isPlayer() }.all { it.actions.isEmpty() } + return creatureAIs.filter { it.creature.isPlayer() }.all { it.actions.isEmpty() } } + + +private suspend fun getCreatureAIs() = GameState.players.values.flatMap { it.thing.location.getLocation().getCreatures(it.thing) }.toSet().map { it.mind.ai } diff --git a/src/commonMain/kotlin/core/ai/PackageBasedAI.kt b/src/commonMain/kotlin/core/ai/PackageBasedAI.kt index e087ef213..3f4883b56 100644 --- a/src/commonMain/kotlin/core/ai/PackageBasedAI.kt +++ b/src/commonMain/kotlin/core/ai/PackageBasedAI.kt @@ -4,6 +4,7 @@ import conversation.ConversationManager import conversation.dialogue.DialogueEvent import core.ai.packages.AIPackage import core.events.EventManager +import core.events.TemporalEvent import core.history.display import core.utility.RandomManager diff --git a/src/commonMain/kotlin/core/events/EventManager.kt b/src/commonMain/kotlin/core/events/EventManager.kt index 3b535b215..782a607e6 100644 --- a/src/commonMain/kotlin/core/events/EventManager.kt +++ b/src/commonMain/kotlin/core/events/EventManager.kt @@ -4,6 +4,7 @@ import building.ModManager import core.DependencyInjector import core.GameState import core.ai.directAI +import core.ai.replenishAITurns import core.history.displayGlobal import core.thing.Thing import system.debug.DebugType @@ -38,6 +39,7 @@ object EventManager { */ suspend fun processEvents() { var loop = 0 + replenishAITurns() while (eventQueue.isNotEmpty() && loop < MAX_EVENT_LOOPS) { startEvents() loop++ @@ -46,8 +48,15 @@ object EventManager { } private suspend fun startEvents() { - if (GameState.getDebugBoolean(DebugType.VERBOSE_ACTIONS)) displayGlobal(eventQueue.joinToString { it.toString() }) val eventCopy = eventQueue.toList() + if (GameState.getDebugBoolean(DebugType.VERBOSE_EVENT_QUEUE)) { + if (eventQueue.isNotEmpty()) { + displayGlobal("Queue:\n" + eventQueue.joinToString("\n") { "\t $it" }) + } + if (eventsInProgress.isNotEmpty()) { + displayGlobal("In Progress:\n" + eventsInProgress.joinToString("\n") { "\t $it" }) + } + } eventQueue.clear() eventCopy.forEach { startEvent(it) } val playerTurn = directAI() @@ -81,7 +90,6 @@ object EventManager { if (event !is TemporalEvent || event.timeLeft == 0) { completeEvent(event) } - } private suspend fun tick() { diff --git a/src/commonMain/kotlin/core/thing/ThingBuilder.kt b/src/commonMain/kotlin/core/thing/ThingBuilder.kt index ad115ae94..0e1dfba17 100644 --- a/src/commonMain/kotlin/core/thing/ThingBuilder.kt +++ b/src/commonMain/kotlin/core/thing/ThingBuilder.kt @@ -257,8 +257,8 @@ class ThingBuilder(internal val name: String) { private fun discernAI(props: Properties) : AI { return when { props.tags.has(TagKey.PREDATOR) -> PackageBasedAI(AIPackageManager.aiPackages[AIPackageKeys.PREDATOR]!!) - props.tags.has(TagKey.CREATURE) -> PackageBasedAI(AIPackageManager.aiPackages[AIPackageKeys.CREATURE]!!) props.tags.has(TagKey.COMMONER) -> PackageBasedAI(AIPackageManager.aiPackages[AIPackageKeys.PEASANT]!!) + props.tags.has(TagKey.CREATURE) -> PackageBasedAI(AIPackageManager.aiPackages[AIPackageKeys.CREATURE]!!) else -> DumbAI() } } diff --git a/src/commonMain/kotlin/system/debug/DebugType.kt b/src/commonMain/kotlin/system/debug/DebugType.kt index 1f8b6fc93..56bb87680 100644 --- a/src/commonMain/kotlin/system/debug/DebugType.kt +++ b/src/commonMain/kotlin/system/debug/DebugType.kt @@ -11,7 +11,7 @@ enum class DebugType(val propertyName: String) { MAP_SHOW_ALL_LOCATIONS("Show All Map Locations"), RECIPE_SHOW_ALL("Show All Recipes"), VERBOSE_AI("Verbose AI"), - VERBOSE_ACTIONS("Verbose Actions"), + VERBOSE_EVENT_QUEUE("Verbose Event Queue"), VERBOSE_TIME("Verbose Time"), VERBOSE_WEATHER("Verbose Weather"), POLL_CONNECTION("Poll server on cadence for updates"), diff --git a/src/commonMain/kotlin/system/help/ViewHelpEvent.kt b/src/commonMain/kotlin/system/help/ViewHelpEvent.kt index e6abf0cf4..4f1218f23 100644 --- a/src/commonMain/kotlin/system/help/ViewHelpEvent.kt +++ b/src/commonMain/kotlin/system/help/ViewHelpEvent.kt @@ -4,4 +4,4 @@ import core.Player import core.commands.Command import core.events.Event -class ViewHelpEvent(val source: Player, val commandGroups: Boolean = false, val commandManual: Command? = null, val args: List = listOf()) : Event \ No newline at end of file +data class ViewHelpEvent(val source: Player, val commandGroups: Boolean = false, val commandManual: Command? = null, val args: List = listOf()) : Event diff --git a/src/jvmTestIntegration/kotlin/commandCombos/CommandComboTest.kt b/src/jvmTestIntegration/kotlin/commandCombos/CommandComboTest.kt index 8b179a40e..b3ab4d230 100644 --- a/src/jvmTestIntegration/kotlin/commandCombos/CommandComboTest.kt +++ b/src/jvmTestIntegration/kotlin/commandCombos/CommandComboTest.kt @@ -124,7 +124,7 @@ class CommandComboTest { GameState.putDebug(DebugType.RANDOM_SUCCEED, true) GameState.putDebug(DebugType.RANDOM_RESPONSE, 0) runBlocking { - CommandParsers.parseCommand(GameState.player, "s && nothing && nothing && nothing && nothing && nothing && nothing") + CommandParsers.parseCommand(GameState.player, "s && nothing && nothing && nothing && nothing && nothing && nothing && r 1") } assertTrue(GameLogger.getMainHistory().contains("Oh dear, you have died!")) } @@ -287,4 +287,4 @@ class CommandComboTest { assertTrue(tags.hasAll(listOf("Bronze", "Weapon"))) } } -} \ No newline at end of file +} From a62e8b37517a4c4a0415bb3234f540745e9ac132 Mon Sep 17 00:00:00 2001 From: ManApart Date: Wed, 31 Dec 2025 14:06:42 -0500 Subject: [PATCH 32/45] replenish turns if just waitin on player --- src/commonMain/kotlin/core/events/EventManager.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/commonMain/kotlin/core/events/EventManager.kt b/src/commonMain/kotlin/core/events/EventManager.kt index 782a607e6..848dc4998 100644 --- a/src/commonMain/kotlin/core/events/EventManager.kt +++ b/src/commonMain/kotlin/core/events/EventManager.kt @@ -58,6 +58,8 @@ object EventManager { } } eventQueue.clear() + //If we're just waiting on the player, allow AI to keep moving + if (eventCopy.size == 1 && eventCopy.first() is GameTickEvent) replenishAITurns() eventCopy.forEach { startEvent(it) } val playerTurn = directAI() if (!playerTurn) { From 0b98878c3981ed1a3d0940b067ad229ad6e2c01f Mon Sep 17 00:00:00 2001 From: ManApart Date: Wed, 31 Dec 2025 14:47:26 -0500 Subject: [PATCH 33/45] farmer interacts with wheat if no distance --- src/commonMain/kotlin/core/GameManager.kt | 1 - src/commonMain/kotlin/core/ai/AI.kt | 3 --- src/commonMain/kotlin/core/ai/packages/AIPackageHelpers.kt | 2 +- .../kotlin/resources/ai/packages/CreatureAIPackage.kt | 5 +++-- src/commonMain/kotlin/resources/behaviors/CommonBehaviors.kt | 2 +- src/commonMain/kotlin/use/interaction/Interact.kt | 5 ++--- src/commonMain/kotlin/use/interaction/InteractEvent.kt | 2 +- .../kotlin/commandCombos/CommandComboTest.kt | 1 - 8 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/commonMain/kotlin/core/GameManager.kt b/src/commonMain/kotlin/core/GameManager.kt index 7ab069b5f..362b796b2 100644 --- a/src/commonMain/kotlin/core/GameManager.kt +++ b/src/commonMain/kotlin/core/GameManager.kt @@ -58,7 +58,6 @@ object GameManager { private fun setDefaultProperties(testing: Boolean) { // GameState.properties.values.put(AUTO_SAVE, true) -// GameState.putDebug(DebugType.VERBOSE_ACTIONS, testing) // GameState.putDebug(DebugType.VERBOSE_AI, true) // GameState.properties.values.put(DEBUG_PACKAGE, AIPackageKeys.PEASANT) // GameState.putDebug(DebugType.CLARITY, true) diff --git a/src/commonMain/kotlin/core/ai/AI.kt b/src/commonMain/kotlin/core/ai/AI.kt index e4e8546e5..486d88eeb 100644 --- a/src/commonMain/kotlin/core/ai/AI.kt +++ b/src/commonMain/kotlin/core/ai/AI.kt @@ -10,7 +10,6 @@ abstract class AI { var takenTurn: Boolean = false abstract suspend fun hear(event: DialogueEvent) abstract suspend fun takeAction() - val actions = mutableListOf() suspend fun chooseAction() { @@ -19,6 +18,4 @@ abstract class AI { takenTurn = true } } - - } diff --git a/src/commonMain/kotlin/core/ai/packages/AIPackageHelpers.kt b/src/commonMain/kotlin/core/ai/packages/AIPackageHelpers.kt index b57363f22..6145d11d1 100644 --- a/src/commonMain/kotlin/core/ai/packages/AIPackageHelpers.kt +++ b/src/commonMain/kotlin/core/ai/packages/AIPackageHelpers.kt @@ -18,7 +18,7 @@ suspend fun Thing.hasUseTarget() = mind.getUseTargetThing() != null suspend fun Thing.canReachGoal(howToUse: String): Boolean { val useTarget = mind.getUseTarget() - return useTarget != null && useTarget.props.values.getString(ValueKey.GOAL) == howToUse && useTarget.source.getThing()?.position?.let { pos -> canReach(pos) } ?: false + return useTarget != null && useTarget.props.values.getString(ValueKey.GOAL) == howToUse.lowercase() && useTarget.source.getThing()?.position?.let { pos -> canReach(pos) } ?: false } suspend fun clawAttack(target: Thing, creature: Thing): AttackEvent { diff --git a/src/commonMain/kotlin/resources/ai/packages/CreatureAIPackage.kt b/src/commonMain/kotlin/resources/ai/packages/CreatureAIPackage.kt index 53e144b0d..c15cb70b3 100644 --- a/src/commonMain/kotlin/resources/ai/packages/CreatureAIPackage.kt +++ b/src/commonMain/kotlin/resources/ai/packages/CreatureAIPackage.kt @@ -86,14 +86,15 @@ class CreatureAIPackage : AIPackageTemplateResource { idea("Interact", 40) { cond { it.canReachGoal(HowToUse.INTERACT) } actions { s -> + //TODO - ignoreDistance shouldn't be needed. Move to field first listOfNotNull( - s.mind.getUseTargetThing()?.let { InteractEvent(s, it) }, + s.mind.getUseTargetThing()?.let { InteractEvent(s, it, ignoreDistance = true) }, s.clearUseGoal() ) } } - idea("Wander") { + idea("Wander", 10) { cond { s -> s.location.getLocation().getThings(s).filter { it != s }.isNotEmpty() } act { it.location.getLocation().getThings(it).random()?.position?.let { pos -> diff --git a/src/commonMain/kotlin/resources/behaviors/CommonBehaviors.kt b/src/commonMain/kotlin/resources/behaviors/CommonBehaviors.kt index 09edee8c8..50f7378e4 100644 --- a/src/commonMain/kotlin/resources/behaviors/CommonBehaviors.kt +++ b/src/commonMain/kotlin/resources/behaviors/CommonBehaviors.kt @@ -100,7 +100,7 @@ class CommonBehaviors : BehaviorResource { behavior("Tend Crop", InteractEvent::class) { event { event, params -> val message = params[ParameterKeys.MESSAGE] ?: "You tend the ${event.interactionTarget.name}." - val messageToOthers = params[ParameterKeys.MESSAGE_TO_OTHERS] ?: "${event.creature} tends the ${event.interactionTarget.name}." + val messageToOthers = params[ParameterKeys.MESSAGE_TO_OTHERS] ?: "${event.creature.name} tends the ${event.interactionTarget.name}." MessageEvent(event.creature, message, messageToOthers, false) } } diff --git a/src/commonMain/kotlin/use/interaction/Interact.kt b/src/commonMain/kotlin/use/interaction/Interact.kt index 04970d137..e9d29d32b 100644 --- a/src/commonMain/kotlin/use/interaction/Interact.kt +++ b/src/commonMain/kotlin/use/interaction/Interact.kt @@ -9,9 +9,8 @@ import core.utility.isAre class Interact : EventListener() { override suspend fun complete(event: InteractEvent) { - when { - !event.interactionTarget.isWithinRangeOf(event.creature) -> event.creature.display { event.creature.asSubject(it) + " " + event.creature.isAre(it) + " too far away to interact with ${event.interactionTarget}." } + !event.ignoreDistance && !event.interactionTarget.isWithinRangeOf(event.creature) -> event.creature.display { event.creature.asSubject(it) + " " + event.creature.isAre(it) + " too far away to interact with ${event.interactionTarget}." } !event.creature.canInteract() -> event.creature.displayToMe("You can't interact with ${event.interactionTarget.name} right now.") !event.interactionTarget.canConsume(event) -> event.creature.displayToMe("${event.interactionTarget.name} doesn't seem to do anything interesting.") else -> { @@ -19,4 +18,4 @@ class Interact : EventListener() { } } } -} \ No newline at end of file +} diff --git a/src/commonMain/kotlin/use/interaction/InteractEvent.kt b/src/commonMain/kotlin/use/interaction/InteractEvent.kt index 75a032f0e..193910443 100644 --- a/src/commonMain/kotlin/use/interaction/InteractEvent.kt +++ b/src/commonMain/kotlin/use/interaction/InteractEvent.kt @@ -6,4 +6,4 @@ import core.thing.Thing /** * A thing is interacting with another thing. Different from UseEvent in that nothing is being used ON/WITH the thing. */ -data class InteractEvent(override val creature: Thing, val interactionTarget: Thing, override var timeLeft: Int = 1) : TemporalEvent \ No newline at end of file +data class InteractEvent(override val creature: Thing, val interactionTarget: Thing, override var timeLeft: Int = 1, val ignoreDistance: Boolean = false) : TemporalEvent diff --git a/src/jvmTestIntegration/kotlin/commandCombos/CommandComboTest.kt b/src/jvmTestIntegration/kotlin/commandCombos/CommandComboTest.kt index b3ab4d230..32150a82d 100644 --- a/src/jvmTestIntegration/kotlin/commandCombos/CommandComboTest.kt +++ b/src/jvmTestIntegration/kotlin/commandCombos/CommandComboTest.kt @@ -129,7 +129,6 @@ class CommandComboTest { assertTrue(GameLogger.getMainHistory().contains("Oh dear, you have died!")) } - @Test fun doNotAttackDeadThing() { val input = "s && slash torso of rat && sl rat && sl && slash rat" From bd4f9cac9b2b4f298e671213c7c06cec391299c6 Mon Sep 17 00:00:00 2001 From: ManApart Date: Wed, 31 Dec 2025 21:14:42 -0500 Subject: [PATCH 34/45] minor stuff --- .../kotlin/resources/ai/packages/CreatureAIPackage.kt | 4 +++- src/jvmMain/kotlin/QuestCommand.kt | 3 --- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/commonMain/kotlin/resources/ai/packages/CreatureAIPackage.kt b/src/commonMain/kotlin/resources/ai/packages/CreatureAIPackage.kt index c15cb70b3..772b59602 100644 --- a/src/commonMain/kotlin/resources/ai/packages/CreatureAIPackage.kt +++ b/src/commonMain/kotlin/resources/ai/packages/CreatureAIPackage.kt @@ -51,7 +51,9 @@ class CreatureAIPackage : AIPackageTemplateResource { } idea("Move to Use Target", 50) { - cond { it.hasUseTarget() && !it.canReach(it.mind.getUseTargetThing()!!.position) } + cond { s -> + s.mind.getUseTargetThing()?.position?.let { !s.canReach(it) } ?: false + } act { startMoveEvent(it, destination = it.mind.getUseTargetThing()!!.position) } } diff --git a/src/jvmMain/kotlin/QuestCommand.kt b/src/jvmMain/kotlin/QuestCommand.kt index 579a6fe06..7fd702d58 100644 --- a/src/jvmMain/kotlin/QuestCommand.kt +++ b/src/jvmMain/kotlin/QuestCommand.kt @@ -23,11 +23,8 @@ fun main(args: Array) { } private suspend fun runInTerminal() { - GameState.player.displayToMe("Stuff") CommandParsers.parseInitialCommand(GameState.player) - GameState.player.displayToMe("Stuff2") TerminalPrinter.print() - GameState.player.displayToMe("Stuff3") while (GameManager.playing) { CommandParsers.parseCommand(GameState.player, readlnOrNull() ?: "") TerminalPrinter.print() From ff998ca3c7e9a21478bd29c06ba28e89ff8020cf Mon Sep 17 00:00:00 2001 From: ManApart Date: Wed, 31 Dec 2025 23:16:02 -0500 Subject: [PATCH 35/45] farmer moves to wheat --- src/commonMain/kotlin/core/ai/packages/AIPackageHelpers.kt | 1 - src/commonMain/kotlin/core/thing/Thing.kt | 6 +++--- .../kotlin/resources/ai/packages/CreatureAIPackage.kt | 7 ++----- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/commonMain/kotlin/core/ai/packages/AIPackageHelpers.kt b/src/commonMain/kotlin/core/ai/packages/AIPackageHelpers.kt index 6145d11d1..559efc496 100644 --- a/src/commonMain/kotlin/core/ai/packages/AIPackageHelpers.kt +++ b/src/commonMain/kotlin/core/ai/packages/AIPackageHelpers.kt @@ -14,7 +14,6 @@ import traveling.routes.FindRouteEvent suspend fun Thing.hasAggroTarget() = mind.getAggroTarget() != null -suspend fun Thing.hasUseTarget() = mind.getUseTargetThing() != null suspend fun Thing.canReachGoal(howToUse: String): Boolean { val useTarget = mind.getUseTarget() diff --git a/src/commonMain/kotlin/core/thing/Thing.kt b/src/commonMain/kotlin/core/thing/Thing.kt index 189946c33..f547ed112 100644 --- a/src/commonMain/kotlin/core/thing/Thing.kt +++ b/src/commonMain/kotlin/core/thing/Thing.kt @@ -177,10 +177,10 @@ data class Thing( return getTopParent().location == creature.getTopParent().location && range >= distance } - fun canReach(position: Vector): Boolean { + fun canReach(targetPos: Vector): Boolean { val range = body.getRange() - val centerOfCreature = position + Vector(z = body.getSize().z / 2) - val distance = centerOfCreature.getDistance(position) + val centerOfCreature = this.position + Vector(z = body.getSize().z / 2) + val distance = centerOfCreature.getDistance(targetPos) return range >= distance } diff --git a/src/commonMain/kotlin/resources/ai/packages/CreatureAIPackage.kt b/src/commonMain/kotlin/resources/ai/packages/CreatureAIPackage.kt index 772b59602..fc0894682 100644 --- a/src/commonMain/kotlin/resources/ai/packages/CreatureAIPackage.kt +++ b/src/commonMain/kotlin/resources/ai/packages/CreatureAIPackage.kt @@ -51,9 +51,7 @@ class CreatureAIPackage : AIPackageTemplateResource { } idea("Move to Use Target", 50) { - cond { s -> - s.mind.getUseTargetThing()?.position?.let { !s.canReach(it) } ?: false - } + cond { s -> s.mind.getUseTargetThing()?.let { !s.canReach(it.position) } ?: false } act { startMoveEvent(it, destination = it.mind.getUseTargetThing()!!.position) } } @@ -88,9 +86,8 @@ class CreatureAIPackage : AIPackageTemplateResource { idea("Interact", 40) { cond { it.canReachGoal(HowToUse.INTERACT) } actions { s -> - //TODO - ignoreDistance shouldn't be needed. Move to field first listOfNotNull( - s.mind.getUseTargetThing()?.let { InteractEvent(s, it, ignoreDistance = true) }, + s.mind.getUseTargetThing()?.let { InteractEvent(s, it) }, s.clearUseGoal() ) } From 6deb815c4e8ac0b2fe78de2de7b0cb9d5da55985 Mon Sep 17 00:00:00 2001 From: ManApart Date: Thu, 1 Jan 2026 19:32:43 -0500 Subject: [PATCH 36/45] account for proper reach --- src/commonMain/kotlin/core/body/Body.kt | 2 +- .../kotlin/commandCombos/CommandComboTest.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/commonMain/kotlin/core/body/Body.kt b/src/commonMain/kotlin/core/body/Body.kt index fee22f62d..b0af41802 100644 --- a/src/commonMain/kotlin/core/body/Body.kt +++ b/src/commonMain/kotlin/core/body/Body.kt @@ -202,7 +202,7 @@ data class Body( } fun getRange(): Int { - val size = getSize() + val size = getSize() * 2 return max(size.x, size.y, size.z) / 2 } diff --git a/src/jvmTestIntegration/kotlin/commandCombos/CommandComboTest.kt b/src/jvmTestIntegration/kotlin/commandCombos/CommandComboTest.kt index 32150a82d..d0dd5b8a9 100644 --- a/src/jvmTestIntegration/kotlin/commandCombos/CommandComboTest.kt +++ b/src/jvmTestIntegration/kotlin/commandCombos/CommandComboTest.kt @@ -263,7 +263,7 @@ class CommandComboTest { runBlocking { CommandParsers.parseCommand( GameState.player, - "w && s && take tinder && n && rest 10 && e && n && w && take lantern && t mouth && hold lantern f && ls" + "w && s && mv to range && take tinder && rest 10 && n && rest 10 && e && n && w && take lantern && t mouth && hold lantern f && ls" ) assertEquals("It's too dark to see anything.", GameLogger.getMainHistory().getLastOutput()) CommandParsers.parseCommand(GameState.player, "use tinder on lantern && ls") @@ -276,7 +276,7 @@ class CommandComboTest { runBlocking { CommandParsers.parseCommand( GameState.player, - "w && n && w && debug recipe && mv to chest && take tinder && take all from chest && mv to forge && debug stat smithing 2 && use tinder on forge && craft dagger" + "w && n && w && debug recipe && mv to chest && take all from chest && mv to forge && take tinder && debug stat smithing 2 && use tinder on forge && craft dagger" ) val dagger = GameState.player.inventory.getItem("Bronze Dagger") From 086b977fa38f7f531947fcd8371ce9407b2ee0a2 Mon Sep 17 00:00:00 2001 From: ManApart Date: Thu, 1 Jan 2026 19:49:00 -0500 Subject: [PATCH 37/45] most ideas take an ai turn --- src/commonMain/kotlin/core/ai/AI.kt | 5 ++--- src/commonMain/kotlin/core/ai/DumbAI.kt | 2 +- src/commonMain/kotlin/core/ai/PackageBasedAI.kt | 9 +++++---- src/commonMain/kotlin/core/ai/PlayerControlledAI.kt | 3 ++- .../kotlin/core/ai/packages/AIPackageTemplateBuilder.kt | 4 ++-- src/commonMain/kotlin/core/ai/packages/Idea.kt | 4 ++-- src/commonMain/kotlin/core/ai/packages/IdeaBuilder.kt | 8 ++++---- .../kotlin/resources/ai/packages/CreatureAIPackage.kt | 2 +- .../kotlin/resources/ai/packages/PeasantAIPackage.kt | 4 ++-- 9 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/commonMain/kotlin/core/ai/AI.kt b/src/commonMain/kotlin/core/ai/AI.kt index 486d88eeb..6fd44339a 100644 --- a/src/commonMain/kotlin/core/ai/AI.kt +++ b/src/commonMain/kotlin/core/ai/AI.kt @@ -9,13 +9,12 @@ abstract class AI { var enabled: Boolean = true var takenTurn: Boolean = false abstract suspend fun hear(event: DialogueEvent) - abstract suspend fun takeAction() + abstract suspend fun takeAction(): Boolean val actions = mutableListOf() suspend fun chooseAction() { if (enabled && !creature.isPlayer()) { - takeAction() - takenTurn = true + takenTurn = takeAction() } } } diff --git a/src/commonMain/kotlin/core/ai/DumbAI.kt b/src/commonMain/kotlin/core/ai/DumbAI.kt index 3ef8ccdf7..0185bc6b0 100644 --- a/src/commonMain/kotlin/core/ai/DumbAI.kt +++ b/src/commonMain/kotlin/core/ai/DumbAI.kt @@ -5,7 +5,7 @@ import conversation.dialogue.DialogueEvent class DumbAI : AI() { override fun toString() = "Dumb AI for ${creature.name}" override suspend fun hear(event: DialogueEvent) {} - override suspend fun takeAction() {} + override suspend fun takeAction() = true override fun equals(other: Any?) = other is DumbAI override fun hashCode() = this::class.hashCode() } diff --git a/src/commonMain/kotlin/core/ai/PackageBasedAI.kt b/src/commonMain/kotlin/core/ai/PackageBasedAI.kt index 3f4883b56..8dfdf37b8 100644 --- a/src/commonMain/kotlin/core/ai/PackageBasedAI.kt +++ b/src/commonMain/kotlin/core/ai/PackageBasedAI.kt @@ -14,10 +14,11 @@ class PackageBasedAI(val aiPackage: AIPackage) : AI() { return "AI for ${creature.name} using ${aiPackage.name} package" } - override suspend fun takeAction() { - aiPackage.pickIdea(creature) - .also { previousIdeas.add(it.name) } - .action(creature).forEach { EventManager.postEvent(it) } + override suspend fun takeAction(): Boolean { + val idea = aiPackage.pickIdea(creature) + previousIdeas.add(idea.name) + idea.action(creature).forEach { EventManager.postEvent(it) } + return idea.takesTurn } override suspend fun hear(event: DialogueEvent) { diff --git a/src/commonMain/kotlin/core/ai/PlayerControlledAI.kt b/src/commonMain/kotlin/core/ai/PlayerControlledAI.kt index 976dc7f27..be6aa2420 100644 --- a/src/commonMain/kotlin/core/ai/PlayerControlledAI.kt +++ b/src/commonMain/kotlin/core/ai/PlayerControlledAI.kt @@ -13,9 +13,10 @@ class PlayerControlledAI : AI() { event.speaker.display("" + event.speaker.name + ": " + event.line) } - override suspend fun takeAction() { + override suspend fun takeAction(): Boolean { creature.displayToMe("What do you do, ${creature.name}?") creature.displayToOthersGlobal("${creature.name} does what?") + return true } } \ No newline at end of file diff --git a/src/commonMain/kotlin/core/ai/packages/AIPackageTemplateBuilder.kt b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplateBuilder.kt index db87e17df..123a22806 100644 --- a/src/commonMain/kotlin/core/ai/packages/AIPackageTemplateBuilder.kt +++ b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplateBuilder.kt @@ -31,8 +31,8 @@ class AIPackageTemplateBuilder(val name: String) { dropped.addAll(ideaName) } - fun idea(name: String, priority: Int = 20, initializer: IdeaBuilder.() -> Unit = {}) { - ideas.add(IdeaBuilder(name, priority).apply(initializer)) + fun idea(name: String, priority: Int = 20, takesTurn: Boolean = true, initializer: IdeaBuilder.() -> Unit = {}) { + ideas.add(IdeaBuilder(name, priority, takesTurn).apply(initializer)) } fun tagged(tag: String, initializer: IdeasBuilder.() -> Unit) { diff --git a/src/commonMain/kotlin/core/ai/packages/Idea.kt b/src/commonMain/kotlin/core/ai/packages/Idea.kt index 52aa58167..d94563421 100644 --- a/src/commonMain/kotlin/core/ai/packages/Idea.kt +++ b/src/commonMain/kotlin/core/ai/packages/Idea.kt @@ -5,6 +5,6 @@ import core.thing.Thing import use.interaction.nothing.NothingEvent -val DO_NOTHING_IDEA = Idea("Do Nothing", 0, { true }, { listOf(NothingEvent(it)) }) +val DO_NOTHING_IDEA = Idea("Do Nothing", 0, true,{ true }, { listOf(NothingEvent(it)) }) -data class Idea(val name: String, val priority: Int, val criteria: suspend (Thing) -> Boolean, val action: suspend (Thing) -> List) +data class Idea(val name: String, val priority: Int, val takesTurn: Boolean = true, val criteria: suspend (Thing) -> Boolean, val action: suspend (Thing) -> List) diff --git a/src/commonMain/kotlin/core/ai/packages/IdeaBuilder.kt b/src/commonMain/kotlin/core/ai/packages/IdeaBuilder.kt index 614ed829e..923419043 100644 --- a/src/commonMain/kotlin/core/ai/packages/IdeaBuilder.kt +++ b/src/commonMain/kotlin/core/ai/packages/IdeaBuilder.kt @@ -4,11 +4,11 @@ import core.events.Event import core.thing.Thing import use.interaction.nothing.NothingEvent -class IdeaBuilder(val name: String, val priority: Int) { +class IdeaBuilder(val name: String, val priority: Int, val takesTurn: Boolean) { internal var criteria: suspend (Thing) -> Boolean = { true } private var action: suspend (Thing) -> List = { listOf() } - fun build(packageName: String) = Idea("$packageName-$name", priority, criteria, action) + fun build(packageName: String) = Idea("$packageName-$name", priority, takesTurn, criteria, action) fun cond(condition: suspend (Thing) -> Boolean) { this.criteria = condition @@ -39,8 +39,8 @@ class IdeasBuilder { children.add(item) } - fun idea(name: String, priority: Int = 20, initializer: IdeaBuilder.() -> Unit) { - children.add(IdeaBuilder(name, priority).apply(initializer)) + fun idea(name: String, priority: Int = 20, takesTurn: Boolean = true, initializer: IdeaBuilder.() -> Unit) { + children.add(IdeaBuilder(name, priority, takesTurn).apply(initializer)) } fun criteria(criteria: suspend (Thing) -> Boolean, initializer: IdeasBuilder.() -> Unit) { diff --git a/src/commonMain/kotlin/resources/ai/packages/CreatureAIPackage.kt b/src/commonMain/kotlin/resources/ai/packages/CreatureAIPackage.kt index fc0894682..d7b0bec8d 100644 --- a/src/commonMain/kotlin/resources/ai/packages/CreatureAIPackage.kt +++ b/src/commonMain/kotlin/resources/ai/packages/CreatureAIPackage.kt @@ -62,7 +62,7 @@ class CreatureAIPackage : AIPackageTemplateResource { //TODO - Maybe last eaten + amount of time //TODO - second version to go look for food - idea("Want Food") { + idea("Want Food", takesTurn = false) { cond { s -> GameState.timeManager.getPercentDayComplete() in listOf(25, 50, 75) && s.perceivedItemsAndInventory().firstOrNull { it.hasTag(TagKey.FOOD) } != null diff --git a/src/commonMain/kotlin/resources/ai/packages/PeasantAIPackage.kt b/src/commonMain/kotlin/resources/ai/packages/PeasantAIPackage.kt index e12da30d4..d8389f866 100644 --- a/src/commonMain/kotlin/resources/ai/packages/PeasantAIPackage.kt +++ b/src/commonMain/kotlin/resources/ai/packages/PeasantAIPackage.kt @@ -37,7 +37,7 @@ class PeasantAIPackage : AIPackageTemplateResource { s.mind.knownLocationByKind(FactKind.MY_HOME)?.let { plotRouteAndStartTravel(s, it) } } } - idea("Work") { + idea("Work", takesTurn = false) { cond { s -> GameState.timeManager.isWorkHours() && s.location == s.mind.knownLocationByKind(FactKind.MY_WORKPLACE) @@ -49,7 +49,7 @@ class PeasantAIPackage : AIPackageTemplateResource { } } } - idea("Want to Sleep in Bed") { + idea("Want to Sleep in Bed", takesTurn = false) { cond { s -> GameState.timeManager.isNight() && s.mind.knownThingByKind(FactKind.MY_BED) != null } act { s -> s.mind.knownThingByKind(FactKind.MY_BED)?.let { s.setUseTarget(it, HowToUse.SLEEP) } } } From c23a13984b3226af677d193da551c39329225b19 Mon Sep 17 00:00:00 2001 From: ManApart Date: Fri, 2 Jan 2026 14:34:47 -0500 Subject: [PATCH 38/45] test for tending wheat --- .../resources/thing/creature/CommonCreatures.kt | 2 ++ .../kotlin/commandCombos/CommandComboTest.kt | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/commonMain/kotlin/resources/thing/creature/CommonCreatures.kt b/src/commonMain/kotlin/resources/thing/creature/CommonCreatures.kt index 1d2ed3081..930ef95b8 100644 --- a/src/commonMain/kotlin/resources/thing/creature/CommonCreatures.kt +++ b/src/commonMain/kotlin/resources/thing/creature/CommonCreatures.kt @@ -6,6 +6,7 @@ import core.TagKey.FARMABLE import core.TagKey.PREDATOR import core.thing.creature.CreatureResource import core.thing.things +import status.stat.AGILITY class CommonCreatures : CreatureResource { @@ -29,6 +30,7 @@ class CommonCreatures : CreatureResource { soul("Health", 10) soul("Strength", 3) soul("Bare Handed", 2) + soul(AGILITY, 1) mind{ learn(WORK_TAGS, listOf(FARMABLE)) } diff --git a/src/jvmTestIntegration/kotlin/commandCombos/CommandComboTest.kt b/src/jvmTestIntegration/kotlin/commandCombos/CommandComboTest.kt index d0dd5b8a9..3f7980ed9 100644 --- a/src/jvmTestIntegration/kotlin/commandCombos/CommandComboTest.kt +++ b/src/jvmTestIntegration/kotlin/commandCombos/CommandComboTest.kt @@ -286,4 +286,16 @@ class CommandComboTest { assertTrue(tags.hasAll(listOf("Bronze", "Weapon"))) } } + + @Test + fun farmerTendsWheat() { + runBlocking { + CommandParsers.parseCommand( + GameState.player, + "w && e && rs 1 && mv wheat && rs 1" + ) + + assertTrue(GameLogger.getMainHistory().contains("Farmer tends the Wheat Field.")) + } + } } From de4c686ede9787cecb393297444ec14474c8531d Mon Sep 17 00:00:00 2001 From: ManApart Date: Fri, 2 Jan 2026 14:48:25 -0500 Subject: [PATCH 39/45] work toward sleep in bed --- src/commonMain/kotlin/core/ai/knowledge/DiscoverFactEvent.kt | 4 ++++ .../kotlin/resources/ai/packages/PeasantAIPackage.kt | 4 +++- src/commonMain/kotlin/time/TimeManager.kt | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/commonMain/kotlin/core/ai/knowledge/DiscoverFactEvent.kt b/src/commonMain/kotlin/core/ai/knowledge/DiscoverFactEvent.kt index ca2ca540f..7f3486491 100644 --- a/src/commonMain/kotlin/core/ai/knowledge/DiscoverFactEvent.kt +++ b/src/commonMain/kotlin/core/ai/knowledge/DiscoverFactEvent.kt @@ -17,6 +17,10 @@ fun Thing.setUseTarget(target: Thing, howToUse: String): DiscoverFactEvent { return DiscoverFactEvent(this, Fact(Subject(target), FactKind.USE_TARGET, Properties(ValueKey.GOAL to howToUse))) } +fun Thing.useTargetGoal(): String? { + return mind.getUseTarget()?.props?.values?.get(ValueKey.GOAL) +} + fun Thing.discover(target: LocationNode, kind: String): DiscoverFactEvent { return DiscoverFactEvent(this, Fact(Subject(target), kind)) } diff --git a/src/commonMain/kotlin/resources/ai/packages/PeasantAIPackage.kt b/src/commonMain/kotlin/resources/ai/packages/PeasantAIPackage.kt index d8389f866..b3c44a6ab 100644 --- a/src/commonMain/kotlin/resources/ai/packages/PeasantAIPackage.kt +++ b/src/commonMain/kotlin/resources/ai/packages/PeasantAIPackage.kt @@ -4,7 +4,9 @@ import core.AIPackageKeys.PEASANT import core.FactKind import core.GameState import core.HowToUse +import core.ValueKey import core.ai.knowledge.setUseTarget +import core.ai.knowledge.useTargetGoal import core.ai.packages.AIPackageTemplateResource import core.ai.packages.aiPackages import core.ai.packages.canReachGoal @@ -50,7 +52,7 @@ class PeasantAIPackage : AIPackageTemplateResource { } } idea("Want to Sleep in Bed", takesTurn = false) { - cond { s -> GameState.timeManager.isNight() && s.mind.knownThingByKind(FactKind.MY_BED) != null } + cond { s -> GameState.timeManager.isNight() && s.mind.knownThingByKind(FactKind.MY_BED) != null && s.useTargetGoal() != HowToUse.SLEEP } act { s -> s.mind.knownThingByKind(FactKind.MY_BED)?.let { s.setUseTarget(it, HowToUse.SLEEP) } } } idea("Sleep in Bed") { diff --git a/src/commonMain/kotlin/time/TimeManager.kt b/src/commonMain/kotlin/time/TimeManager.kt index 398a4700e..944148065 100644 --- a/src/commonMain/kotlin/time/TimeManager.kt +++ b/src/commonMain/kotlin/time/TimeManager.kt @@ -80,7 +80,7 @@ class TimeManager(private var ticks: Long = 0) { fun isWorkHours(): Boolean { val percent = getPercentDayComplete() - return percent > 25 || percent < 50 + return percent > 25 && percent < 50 } private fun debugTimeUpdate() { From 1935d24bacd02fe4b8a4de9945c6f24695fa5066 Mon Sep 17 00:00:00 2001 From: ManApart Date: Fri, 2 Jan 2026 17:42:40 -0500 Subject: [PATCH 40/45] fix tend wheat --- .../kotlin/resources/thing/creature/CommonCreatures.kt | 3 ++- .../kotlin/commandCombos/CommandComboTest.kt | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/commonMain/kotlin/resources/thing/creature/CommonCreatures.kt b/src/commonMain/kotlin/resources/thing/creature/CommonCreatures.kt index 930ef95b8..2a69a6b03 100644 --- a/src/commonMain/kotlin/resources/thing/creature/CommonCreatures.kt +++ b/src/commonMain/kotlin/resources/thing/creature/CommonCreatures.kt @@ -17,6 +17,7 @@ class CommonCreatures : CreatureResource { soul("Health", 3) soul("Strength", 1) soul("Bare Handed", 2) + soul(AGILITY, 1) props { tag("Small", PREDATOR) } @@ -31,7 +32,7 @@ class CommonCreatures : CreatureResource { soul("Strength", 3) soul("Bare Handed", 2) soul(AGILITY, 1) - mind{ + mind { learn(WORK_TAGS, listOf(FARMABLE)) } props { diff --git a/src/jvmTestIntegration/kotlin/commandCombos/CommandComboTest.kt b/src/jvmTestIntegration/kotlin/commandCombos/CommandComboTest.kt index 3f7980ed9..d81a9277a 100644 --- a/src/jvmTestIntegration/kotlin/commandCombos/CommandComboTest.kt +++ b/src/jvmTestIntegration/kotlin/commandCombos/CommandComboTest.kt @@ -290,9 +290,10 @@ class CommandComboTest { @Test fun farmerTendsWheat() { runBlocking { + GameState.putDebug(DebugType.CLARITY, true) CommandParsers.parseCommand( GameState.player, - "w && e && rs 1 && mv wheat && rs 1" + "rs 8 && w && e && rs 1 && mv wheat && rs 1" ) assertTrue(GameLogger.getMainHistory().contains("Farmer tends the Wheat Field.")) From 24b1ab058f9b02de1912387748527898b7923647 Mon Sep 17 00:00:00 2001 From: ManApart Date: Sat, 3 Jan 2026 09:22:51 -0500 Subject: [PATCH 41/45] more prep for farmer sleeping --- .../resources/ai/packages/PeasantAIPackage.kt | 8 +------- .../kotlin/commandCombos/CommandComboTest.kt | 13 +++++++++++++ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/commonMain/kotlin/resources/ai/packages/PeasantAIPackage.kt b/src/commonMain/kotlin/resources/ai/packages/PeasantAIPackage.kt index b3c44a6ab..80dd2da73 100644 --- a/src/commonMain/kotlin/resources/ai/packages/PeasantAIPackage.kt +++ b/src/commonMain/kotlin/resources/ai/packages/PeasantAIPackage.kt @@ -19,7 +19,7 @@ class PeasantAIPackage : AIPackageTemplateResource { aiPackage(PEASANT) { template("Creature") idea("Converse") { - cond { s -> CommandParsers.getConversations().any { it.containsParticipant(s) }} + cond { s -> CommandParsers.getConversations().any { it.containsParticipant(s) } } } idea("Go to Work") { cond { s -> @@ -55,12 +55,6 @@ class PeasantAIPackage : AIPackageTemplateResource { cond { s -> GameState.timeManager.isNight() && s.mind.knownThingByKind(FactKind.MY_BED) != null && s.useTargetGoal() != HowToUse.SLEEP } act { s -> s.mind.knownThingByKind(FactKind.MY_BED)?.let { s.setUseTarget(it, HowToUse.SLEEP) } } } - idea("Sleep in Bed") { - cond { s -> - GameState.timeManager.isNight() && s.canReachGoal(HowToUse.SLEEP) - } - - } } } } diff --git a/src/jvmTestIntegration/kotlin/commandCombos/CommandComboTest.kt b/src/jvmTestIntegration/kotlin/commandCombos/CommandComboTest.kt index d81a9277a..57779c261 100644 --- a/src/jvmTestIntegration/kotlin/commandCombos/CommandComboTest.kt +++ b/src/jvmTestIntegration/kotlin/commandCombos/CommandComboTest.kt @@ -299,4 +299,17 @@ class CommandComboTest { assertTrue(GameLogger.getMainHistory().contains("Farmer tends the Wheat Field.")) } } + + @Test + fun farmerSleeps() { + runBlocking { + GameState.putDebug(DebugType.CLARITY, true) + CommandParsers.parseCommand( + GameState.player, + "rs 3 && w && s && mv bed" + ) + + assertTrue(GameLogger.getMainHistory().contains("")) + } + } } From 66bf530c1abdf1ed7e278a90fda6892fe3d1d036 Mon Sep 17 00:00:00 2001 From: ManApart Date: Wed, 7 Jan 2026 07:53:45 -0500 Subject: [PATCH 42/45] farmer rests test --- src/commonMain/kotlin/core/GameManager.kt | 1 + src/commonMain/kotlin/core/GameState.kt | 1 + .../core/ai/packages/AIPackageHelpers.kt | 2 -- .../kotlin/core/events/EventManager.kt | 6 +++++- .../resources/ai/packages/CreatureAIPackage.kt | 18 ++++++++++++++---- .../resources/ai/packages/PeasantAIPackage.kt | 4 +++- .../resources/behaviors/CommonBehaviors.kt | 2 +- .../kotlin/traveling/position/Vector.kt | 4 +++- .../kotlin/commandCombos/CommandComboTest.kt | 4 ++-- 9 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/commonMain/kotlin/core/GameManager.kt b/src/commonMain/kotlin/core/GameManager.kt index 362b796b2..0be43d9f4 100644 --- a/src/commonMain/kotlin/core/GameManager.kt +++ b/src/commonMain/kotlin/core/GameManager.kt @@ -65,6 +65,7 @@ object GameManager { GameState.properties.values.put(AUTO_LOAD, !testing) GameState.putDebug(DebugType.POLL_CONNECTION, !testing) GameState.properties.values.put(TEST_SAVE_FOLDER, testing) + GameState.properties.values.put(TEST_MODE, testing) GameState.properties.values.put(SKIP_SAVE_STATS, testing) GameState.properties.values.put(PRINT_WITHOUT_FLUSH, testing) GameState.putDebug(DebugType.VERBOSE_TIME, testing) diff --git a/src/commonMain/kotlin/core/GameState.kt b/src/commonMain/kotlin/core/GameState.kt index dd73be86e..c1afa3175 100644 --- a/src/commonMain/kotlin/core/GameState.kt +++ b/src/commonMain/kotlin/core/GameState.kt @@ -65,6 +65,7 @@ const val AUTO_SAVE = "autosave" const val AUTO_LOAD = "autoload" const val DEBUG_PACKAGE = "debug package" const val VERBOSE_STARTUP = "verbose startup" +const val TEST_MODE = "in testing mode" const val TEST_SAVE_FOLDER = "use test save folder" const val SKIP_SAVE_STATS = "skip save stats" const val LAST_SAVE_GAME_NAME = "last save character name" diff --git a/src/commonMain/kotlin/core/ai/packages/AIPackageHelpers.kt b/src/commonMain/kotlin/core/ai/packages/AIPackageHelpers.kt index 559efc496..f669443fd 100644 --- a/src/commonMain/kotlin/core/ai/packages/AIPackageHelpers.kt +++ b/src/commonMain/kotlin/core/ai/packages/AIPackageHelpers.kt @@ -13,8 +13,6 @@ import traveling.position.ThingAim import traveling.routes.FindRouteEvent -suspend fun Thing.hasAggroTarget() = mind.getAggroTarget() != null - suspend fun Thing.canReachGoal(howToUse: String): Boolean { val useTarget = mind.getUseTarget() return useTarget != null && useTarget.props.values.getString(ValueKey.GOAL) == howToUse.lowercase() && useTarget.source.getThing()?.position?.let { pos -> canReach(pos) } ?: false diff --git a/src/commonMain/kotlin/core/events/EventManager.kt b/src/commonMain/kotlin/core/events/EventManager.kt index 848dc4998..b55388199 100644 --- a/src/commonMain/kotlin/core/events/EventManager.kt +++ b/src/commonMain/kotlin/core/events/EventManager.kt @@ -3,6 +3,7 @@ package core.events import building.ModManager import core.DependencyInjector import core.GameState +import core.TEST_MODE import core.ai.directAI import core.ai.replenishAITurns import core.history.displayGlobal @@ -44,7 +45,10 @@ object EventManager { startEvents() loop++ } - if (loop == MAX_EVENT_LOOPS) println("Reached max loops, this should not happen!") + if (loop == MAX_EVENT_LOOPS) { + if (GameState.properties.values.getBoolean(TEST_MODE)) throw IllegalStateException("Reached max loops, this should not happen!") + println("Reached max loops, this should not happen!") + } } private suspend fun startEvents() { diff --git a/src/commonMain/kotlin/resources/ai/packages/CreatureAIPackage.kt b/src/commonMain/kotlin/resources/ai/packages/CreatureAIPackage.kt index d7b0bec8d..de2400f31 100644 --- a/src/commonMain/kotlin/resources/ai/packages/CreatureAIPackage.kt +++ b/src/commonMain/kotlin/resources/ai/packages/CreatureAIPackage.kt @@ -31,7 +31,7 @@ class CreatureAIPackage : AIPackageTemplateResource { } } - idea("Start Travel") { + idea("Start Travel", 50) { cond { s -> (s.mind.getAggroTarget() ?: s.mind.getUseTargetThing()) ?.let { s.location != it.location } ?: false @@ -51,12 +51,12 @@ class CreatureAIPackage : AIPackageTemplateResource { } idea("Move to Use Target", 50) { - cond { s -> s.mind.getUseTargetThing()?.let { !s.canReach(it.position) } ?: false } + cond { s -> s.mind.getUseTargetThing()?.let { s.location == it.location && !s.canReach(it.position) } ?: false } act { startMoveEvent(it, destination = it.mind.getUseTargetThing()!!.position) } } idea("Move to Aggro Target", 70) { - cond { it.hasAggroTarget() && !it.canReach(it.mind.getAggroTarget()!!.position) } + cond { s -> s.mind.getAggroTarget()?.let { s.location == it.location && !s.canReach(it.position) } ?: false } act { startMoveEvent(it, destination = it.mind.getAggroTarget()!!.position) } } @@ -93,11 +93,21 @@ class CreatureAIPackage : AIPackageTemplateResource { } } + idea("Rest", 40) { + cond { it.canReachGoal(HowToUse.SLEEP) } + actions { s -> + listOfNotNull( + s.mind.getUseTargetThing()?.let { InteractEvent(s, it) }, + s.clearUseGoal() + ) + } + } + idea("Wander", 10) { cond { s -> s.location.getLocation().getThings(s).filter { it != s }.isNotEmpty() } act { it.location.getLocation().getThings(it).random()?.position?.let { pos -> - startMoveEvent(it, destination = pos) + startMoveEvent(it, destination = pos.floor()) } } } diff --git a/src/commonMain/kotlin/resources/ai/packages/PeasantAIPackage.kt b/src/commonMain/kotlin/resources/ai/packages/PeasantAIPackage.kt index 80dd2da73..b93d02e02 100644 --- a/src/commonMain/kotlin/resources/ai/packages/PeasantAIPackage.kt +++ b/src/commonMain/kotlin/resources/ai/packages/PeasantAIPackage.kt @@ -52,7 +52,9 @@ class PeasantAIPackage : AIPackageTemplateResource { } } idea("Want to Sleep in Bed", takesTurn = false) { - cond { s -> GameState.timeManager.isNight() && s.mind.knownThingByKind(FactKind.MY_BED) != null && s.useTargetGoal() != HowToUse.SLEEP } + cond { s -> + GameState.timeManager.isNight() && s.mind.knownThingByKind(FactKind.MY_BED) != null && s.useTargetGoal() != HowToUse.SLEEP.lowercase() + } act { s -> s.mind.knownThingByKind(FactKind.MY_BED)?.let { s.setUseTarget(it, HowToUse.SLEEP) } } } } diff --git a/src/commonMain/kotlin/resources/behaviors/CommonBehaviors.kt b/src/commonMain/kotlin/resources/behaviors/CommonBehaviors.kt index 50f7378e4..4cba742f3 100644 --- a/src/commonMain/kotlin/resources/behaviors/CommonBehaviors.kt +++ b/src/commonMain/kotlin/resources/behaviors/CommonBehaviors.kt @@ -124,7 +124,7 @@ class CommonBehaviors : BehaviorResource { events { event, params -> val hoursRested = params["hoursRested"]?.toIntOrNull() ?: 10 listOf( - MessageEvent(event.creature, "You rest for $hoursRested hours.", "${event.creature} rests for $hoursRested hours."), + MessageEvent(event.creature, "You rest for $hoursRested hours.", "${event.creature.name} rests for $hoursRested hours."), RestEvent(event.creature, hoursRested) ) } diff --git a/src/commonMain/kotlin/traveling/position/Vector.kt b/src/commonMain/kotlin/traveling/position/Vector.kt index e86d955f4..9f59d3d51 100644 --- a/src/commonMain/kotlin/traveling/position/Vector.kt +++ b/src/commonMain/kotlin/traveling/position/Vector.kt @@ -79,6 +79,8 @@ class Vector(val x: Int = 0, val y: Int = 0, val z: Int = 0) { return pointAlongPath + getVectorInDirection(pointAlongPath, amount) } + fun floor() = Vector(x, y, 0) + fun isFurtherAlongSameDirectionThan(other: Vector): Boolean { val xSign = (x >= 0 && other.x >= 0) || (x <= 0 && other.x <= 0) val ySign = (y >= 0 && other.y >= 0) || (y <= 0 && other.y <= 0) @@ -226,4 +228,4 @@ fun Sequence.sum(): Vector { sum += element } return sum -} \ No newline at end of file +} diff --git a/src/jvmTestIntegration/kotlin/commandCombos/CommandComboTest.kt b/src/jvmTestIntegration/kotlin/commandCombos/CommandComboTest.kt index 57779c261..9d01f6e16 100644 --- a/src/jvmTestIntegration/kotlin/commandCombos/CommandComboTest.kt +++ b/src/jvmTestIntegration/kotlin/commandCombos/CommandComboTest.kt @@ -306,10 +306,10 @@ class CommandComboTest { GameState.putDebug(DebugType.CLARITY, true) CommandParsers.parseCommand( GameState.player, - "rs 3 && w && s && mv bed" + "rs 3 && w && s && rs 1 && rs 10 && mv bed" ) - assertTrue(GameLogger.getMainHistory().contains("")) + assertTrue(GameLogger.getMainHistory().contains("Farmer rests for 10 hours.")) } } } From 86a5600ef5d58fc7ee44f16e3cc25e92ce529f43 Mon Sep 17 00:00:00 2001 From: ManApart Date: Wed, 7 Jan 2026 20:27:52 -0500 Subject: [PATCH 43/45] AI not shared between creature instances Shared actions list was causing test pollution --- src/commonMain/kotlin/core/GameManager.kt | 1 - src/commonMain/kotlin/core/ai/AI.kt | 1 + src/commonMain/kotlin/core/ai/DumbAI.kt | 1 + .../kotlin/core/ai/PackageBasedAI.kt | 6 ++--- .../kotlin/core/ai/PlayerControlledAI.kt | 9 ++++---- .../kotlin/core/thing/ThingBuilder.kt | 2 +- src/commonMain/kotlin/time/TimeManager.kt | 4 ++-- .../kotlin/commandCombos/CommandComboTest.kt | 22 +++++++++---------- 8 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/commonMain/kotlin/core/GameManager.kt b/src/commonMain/kotlin/core/GameManager.kt index 0be43d9f4..0fa61c596 100644 --- a/src/commonMain/kotlin/core/GameManager.kt +++ b/src/commonMain/kotlin/core/GameManager.kt @@ -57,7 +57,6 @@ object GameManager { } private fun setDefaultProperties(testing: Boolean) { - // GameState.properties.values.put(AUTO_SAVE, true) // GameState.putDebug(DebugType.VERBOSE_AI, true) // GameState.properties.values.put(DEBUG_PACKAGE, AIPackageKeys.PEASANT) // GameState.putDebug(DebugType.CLARITY, true) diff --git a/src/commonMain/kotlin/core/ai/AI.kt b/src/commonMain/kotlin/core/ai/AI.kt index 6fd44339a..6756a0be6 100644 --- a/src/commonMain/kotlin/core/ai/AI.kt +++ b/src/commonMain/kotlin/core/ai/AI.kt @@ -8,6 +8,7 @@ abstract class AI { lateinit var creature: Thing var enabled: Boolean = true var takenTurn: Boolean = false + abstract fun copy(): AI abstract suspend fun hear(event: DialogueEvent) abstract suspend fun takeAction(): Boolean val actions = mutableListOf() diff --git a/src/commonMain/kotlin/core/ai/DumbAI.kt b/src/commonMain/kotlin/core/ai/DumbAI.kt index 0185bc6b0..a52252182 100644 --- a/src/commonMain/kotlin/core/ai/DumbAI.kt +++ b/src/commonMain/kotlin/core/ai/DumbAI.kt @@ -4,6 +4,7 @@ import conversation.dialogue.DialogueEvent class DumbAI : AI() { override fun toString() = "Dumb AI for ${creature.name}" + override fun copy() = DumbAI() override suspend fun hear(event: DialogueEvent) {} override suspend fun takeAction() = true override fun equals(other: Any?) = other is DumbAI diff --git a/src/commonMain/kotlin/core/ai/PackageBasedAI.kt b/src/commonMain/kotlin/core/ai/PackageBasedAI.kt index 8dfdf37b8..109aa4357 100644 --- a/src/commonMain/kotlin/core/ai/PackageBasedAI.kt +++ b/src/commonMain/kotlin/core/ai/PackageBasedAI.kt @@ -3,6 +3,7 @@ package core.ai import conversation.ConversationManager import conversation.dialogue.DialogueEvent import core.ai.packages.AIPackage +import core.ai.packages.aiPackage import core.events.EventManager import core.events.TemporalEvent import core.history.display @@ -10,9 +11,8 @@ import core.utility.RandomManager class PackageBasedAI(val aiPackage: AIPackage) : AI() { val previousIdeas = mutableListOf() - override fun toString(): String { - return "AI for ${creature.name} using ${aiPackage.name} package" - } + override fun toString() = "AI for ${creature.name} using ${aiPackage.name} package" + override fun copy() = PackageBasedAI(aiPackage) override suspend fun takeAction(): Boolean { val idea = aiPackage.pickIdea(creature) diff --git a/src/commonMain/kotlin/core/ai/PlayerControlledAI.kt b/src/commonMain/kotlin/core/ai/PlayerControlledAI.kt index be6aa2420..cd41568bc 100644 --- a/src/commonMain/kotlin/core/ai/PlayerControlledAI.kt +++ b/src/commonMain/kotlin/core/ai/PlayerControlledAI.kt @@ -6,9 +6,9 @@ import core.history.displayToMe import core.history.displayToOthersGlobal class PlayerControlledAI : AI() { - override fun toString(): String { - return "Player Controlled AI for ${creature.name}" - } + override fun toString() = "Player Controlled AI for ${creature.name}" + override fun copy() = PlayerControlledAI() + override suspend fun hear(event: DialogueEvent) { event.speaker.display("" + event.speaker.name + ": " + event.line) } @@ -18,5 +18,4 @@ class PlayerControlledAI : AI() { creature.displayToOthersGlobal("${creature.name} does what?") return true } - -} \ No newline at end of file +} diff --git a/src/commonMain/kotlin/core/thing/ThingBuilder.kt b/src/commonMain/kotlin/core/thing/ThingBuilder.kt index 0e1dfba17..9d12d716b 100644 --- a/src/commonMain/kotlin/core/thing/ThingBuilder.kt +++ b/src/commonMain/kotlin/core/thing/ThingBuilder.kt @@ -234,7 +234,7 @@ class ThingBuilder(internal val name: String) { description(t.description) location(t.location) t.parent?.let { parent(t.parent) } - mind(Mind(t.mind.ai, CreatureMemory(t.mind.memory.getAllFacts(), t.mind.memory.getAllListFacts()))) + mind(Mind(t.mind.ai.copy(), CreatureMemory(t.mind.memory.getAllFacts(), t.mind.memory.getAllListFacts()))) body(Body(t.body)) equipSlotOptions(t.equipSlots) item(t.inventory.getAllItems().map { it.name }) diff --git a/src/commonMain/kotlin/time/TimeManager.kt b/src/commonMain/kotlin/time/TimeManager.kt index 944148065..4a82b257d 100644 --- a/src/commonMain/kotlin/time/TimeManager.kt +++ b/src/commonMain/kotlin/time/TimeManager.kt @@ -85,11 +85,11 @@ class TimeManager(private var ticks: Long = 0) { private fun debugTimeUpdate() { if (GameState.getDebugBoolean(DebugType.VERBOSE_TIME)) { - if (getHoursPassed() != debugTimeHour){ + if (getHoursPassed() != debugTimeHour) { debugTimeHour = getHoursPassed() println(getTimeString()) } } } -} \ No newline at end of file +} diff --git a/src/jvmTestIntegration/kotlin/commandCombos/CommandComboTest.kt b/src/jvmTestIntegration/kotlin/commandCombos/CommandComboTest.kt index 9d01f6e16..15f1a7c06 100644 --- a/src/jvmTestIntegration/kotlin/commandCombos/CommandComboTest.kt +++ b/src/jvmTestIntegration/kotlin/commandCombos/CommandComboTest.kt @@ -225,16 +225,16 @@ class CommandComboTest { runBlocking { CommandParsers.parseCommand(GameState.player, input) } val expected = """ - An Open Field is a part of Kanbara Countryside. It is neighbored by: - Name Distance Direction Path - Apple Tree 100 N - Barren Patch 100 S - Training Circle 100 E - Farmer's Hut 100 W - Windmill 180 NE - """.trimIndent().trim() - - assertEquals(expected, GameLogger.getMainHistory().getLastOutput().trimIndent().trim()) + An Open Field is a part of Kanbara Countryside. It is neighbored by: + Name Distance Direction Path + Apple Tree 100 N + Barren Patch 100 S + Training Circle 100 E + Farmer's Hut 100 W + Windmill 180 NE + """.trimIndent().trim() + + assertEquals(expected, GameLogger.getMainHistory().getLastOutput().split("\n").joinToString("\n") { it.trim() }.trimIndent().trim()) } @Test @@ -293,7 +293,7 @@ class CommandComboTest { GameState.putDebug(DebugType.CLARITY, true) CommandParsers.parseCommand( GameState.player, - "rs 8 && w && e && rs 1 && mv wheat && rs 1" + "rs 8 && w && rs 1 && e && rs 1 && rs 10 && mv wheat" ) assertTrue(GameLogger.getMainHistory().contains("Farmer tends the Wheat Field.")) From a3efdd8f6f3b1ad86003d0a23f737802eb63feb7 Mon Sep 17 00:00:00 2001 From: ManApart Date: Wed, 7 Jan 2026 20:46:49 -0500 Subject: [PATCH 44/45] consolidate rest events --- .../resources/ai/packages/CreatureAIPackage.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/commonMain/kotlin/resources/ai/packages/CreatureAIPackage.kt b/src/commonMain/kotlin/resources/ai/packages/CreatureAIPackage.kt index de2400f31..01d626b80 100644 --- a/src/commonMain/kotlin/resources/ai/packages/CreatureAIPackage.kt +++ b/src/commonMain/kotlin/resources/ai/packages/CreatureAIPackage.kt @@ -19,11 +19,6 @@ class CreatureAIPackage : 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 && it.canReach(it.mind.getAggroTarget()!!.position) } act { @@ -93,7 +88,12 @@ class CreatureAIPackage : AIPackageTemplateResource { } } - idea("Rest", 40) { + idea("Rest") { + cond { s -> s.soul.getCurrent(STAMINA) < s.soul.getTotal(STAMINA) / 10 } + act { RestEvent(it, 2) } + } + + idea("Sleep", 40) { cond { it.canReachGoal(HowToUse.SLEEP) } actions { s -> listOfNotNull( From f13d75c97518265830ad906d258d2330487eed9a Mon Sep 17 00:00:00 2001 From: ManApart Date: Wed, 7 Jan 2026 21:27:44 -0500 Subject: [PATCH 45/45] remove redundant packages --- .../core/ai/packages/AIPackageTemplate.kt | 16 +++++++--------- .../ai/packages/AIPackageTemplateBuilder.kt | 15 +-------------- .../kotlin/core/ai/packages/IdeaBuilder.kt | 2 +- .../resources/ai/packages/CommonAIPackages.kt | 2 +- .../kotlin/validation/AIPackageValidator.kt | 18 ++++++++++-------- 5 files changed, 20 insertions(+), 33 deletions(-) diff --git a/src/commonMain/kotlin/core/ai/packages/AIPackageTemplate.kt b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplate.kt index e295d5ca8..30ae259ca 100644 --- a/src/commonMain/kotlin/core/ai/packages/AIPackageTemplate.kt +++ b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplate.kt @@ -1,18 +1,16 @@ package core.ai.packages -data class AIPackageTemplate(val name: String, val ideas: List, val subPackages: List, val priorityOverride: Map, val dropped: List) { +data class AIPackageTemplate(val name: String, val ideas: List, val subPackages: List, val dropped: List) { fun flatten(reference: Map, flattenedReference: MutableMap): AIPackage { return flattenedReference[name] ?: let { - val subIdeas = subPackages.mapNotNull { reference[it] }.flatMap { subPackage -> - flattenedReference[subPackage.name]?.ideas?.values?.flatten() - ?: subPackage.flatten(reference, flattenedReference).also { flattenedReference[subPackage.name] = it }.ideas.values.flatten() - }.filter { !dropped.contains(it.name) } - .map { idea -> - priorityOverride[idea.name]?.let { idea.copy(priority = it) } ?: idea - }.map { it.copy(name = "$name-${it.name}")} + val subIdeas = subPackages.asSequence().mapNotNull { reference[it] } + .flatMap { subPackage -> + flattenedReference[subPackage.name]?.ideas?.values?.flatten() + ?: subPackage.flatten(reference, flattenedReference).also { flattenedReference[subPackage.name] = it }.ideas.values.flatten() + }.filter { !dropped.contains(it.name) }.toList() AIPackage(name, ideas + subIdeas).also { flattenedReference[name] = it } } } -} \ No newline at end of file +} diff --git a/src/commonMain/kotlin/core/ai/packages/AIPackageTemplateBuilder.kt b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplateBuilder.kt index 123a22806..92ddf5c94 100644 --- a/src/commonMain/kotlin/core/ai/packages/AIPackageTemplateBuilder.kt +++ b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplateBuilder.kt @@ -2,31 +2,19 @@ package core.ai.packages import core.thing.Thing -//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() private val ideas = mutableListOf() private val criteriaChildren = mutableMapOf Boolean, IdeasBuilder>() - private val priorityOverride = mutableMapOf() private val dropped = mutableListOf() fun build(): AIPackageTemplate { val childIdeas = criteriaChildren.flatMap { (parentCriteria, builder) -> builder.getChildren(parentCriteria) } - return AIPackageTemplate(name, (ideas + childIdeas).map { it.build(name) }, templates, priorityOverride, dropped) + return AIPackageTemplate(name, (ideas + childIdeas).map { it.build() }, templates, dropped) } fun template(vararg names: String) = this.templates.addAll(names) - /** - Override the priority of an idea inherited from a template. - */ - fun priority(ideaName: String, newPriority: Int) { - priorityOverride[ideaName] = newPriority - } - fun drop(vararg ideaName: String) { dropped.addAll(ideaName) } @@ -60,7 +48,6 @@ class AIPackageTemplatesBuilder { fun aiPackage(name: String, initializer: AIPackageTemplateBuilder.() -> Unit) { children.add(AIPackageTemplateBuilder(name).apply(initializer)) } - } fun aiPackages(initializer: AIPackageTemplatesBuilder.() -> Unit): List { diff --git a/src/commonMain/kotlin/core/ai/packages/IdeaBuilder.kt b/src/commonMain/kotlin/core/ai/packages/IdeaBuilder.kt index 923419043..ceea8a95d 100644 --- a/src/commonMain/kotlin/core/ai/packages/IdeaBuilder.kt +++ b/src/commonMain/kotlin/core/ai/packages/IdeaBuilder.kt @@ -8,7 +8,7 @@ class IdeaBuilder(val name: String, val priority: Int, val takesTurn: Boolean) { internal var criteria: suspend (Thing) -> Boolean = { true } private var action: suspend (Thing) -> List = { listOf() } - fun build(packageName: String) = Idea("$packageName-$name", priority, takesTurn, criteria, action) + fun build() = Idea(name, priority, takesTurn, criteria, action) fun cond(condition: suspend (Thing) -> Boolean) { this.criteria = condition diff --git a/src/commonMain/kotlin/resources/ai/packages/CommonAIPackages.kt b/src/commonMain/kotlin/resources/ai/packages/CommonAIPackages.kt index 99ab9bd0d..6b47dad3c 100644 --- a/src/commonMain/kotlin/resources/ai/packages/CommonAIPackages.kt +++ b/src/commonMain/kotlin/resources/ai/packages/CommonAIPackages.kt @@ -32,7 +32,7 @@ class CommonAIPackages : AIPackageTemplateResource { } } - idea("Rest") { + idea("Sleep Outside") { cond { !GameState.timeManager.isNight() } act { RestEvent(it, 2) } } diff --git a/src/jvmTestIntegration/kotlin/validation/AIPackageValidator.kt b/src/jvmTestIntegration/kotlin/validation/AIPackageValidator.kt index 9a56d57e9..6ad653aff 100644 --- a/src/jvmTestIntegration/kotlin/validation/AIPackageValidator.kt +++ b/src/jvmTestIntegration/kotlin/validation/AIPackageValidator.kt @@ -4,6 +4,7 @@ import building.ModManager import core.DependencyInjector import core.ai.AIPackageManager import core.ai.packages.AIPackageTemplatesCollection +import core.ai.packages.ideas import kotlinx.coroutines.runBlocking import kotlin.test.Test import kotlin.test.assertEquals @@ -39,14 +40,15 @@ class AIPackageValidator { private fun noDuplicateIdeaNames(): Int { var warnings = 0 - val names = mutableListOf() - - packages.values.flatMap { it.ideas.values.flatten() }.forEach { idea -> - if (names.contains(idea.name)) { - println("WARN: Idea '${idea.name}' has a duplicate name.") - warnings++ - } else { - names.add(idea.name) + packages.values.forEach { pack -> + val names = mutableListOf() + pack.ideas.values.flatten().forEach { idea -> + if (names.contains(idea.name)) { + println("WARN: Idea '${idea.name}' inside package '${pack.name}' has a duplicate name.") + warnings++ + } else { + names.add(idea.name) + } } } return warnings