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/.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/.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/build.gradle.kts b/build.gradle.kts
index 65be1f68a..7b4915004 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))
}
}
}
@@ -123,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/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index e708b1c02..8bdaf60c7 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
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
diff --git a/src/commonMain/kotlin/building/ModManager.kt b/src/commonMain/kotlin/building/ModManager.kt
index 9d9f50d8b..03c72107f 100644
--- a/src/commonMain/kotlin/building/ModManager.kt
+++ b/src/commonMain/kotlin/building/ModManager.kt
@@ -1,9 +1,8 @@
package building
import conversation.dsl.DialogueTree
-import core.ai.agenda.Agenda
import core.ai.behavior.Behavior
-import core.ai.desire.DesireTree
+import core.ai.packages.AIPackageTemplate
import core.events.EventListener
import core.thing.ThingBuilder
import crafting.RecipeBuilder
@@ -18,8 +17,7 @@ import traveling.location.weather.Weather
object ModManager {
val eventListeners = mutableMapOf>>()
val activators = mutableListOf()
- val ai = mutableListOf()
- val agendas = mutableListOf()
+ val aiPackages = mutableListOf()
val behaviors = mutableListOf>()
val bodies = mutableListOf()
val bodyParts = mutableListOf()
@@ -37,8 +35,7 @@ object ModManager {
fun reset(){
activators.clear()
- ai.clear()
- agendas.clear()
+ aiPackages.clear()
behaviors.clear()
bodies.clear()
bodyParts.clear()
@@ -54,4 +51,4 @@ object ModManager {
recipes.clear()
weather.clear()
}
-}
\ No newline at end of file
+}
diff --git a/src/commonMain/kotlin/conversation/dsl/DialogueBuilder.kt b/src/commonMain/kotlin/conversation/dsl/DialogueBuilder.kt
index 666dd037d..bfa75f628 100644
--- a/src/commonMain/kotlin/conversation/dsl/DialogueBuilder.kt
+++ b/src/commonMain/kotlin/conversation/dsl/DialogueBuilder.kt
@@ -81,6 +81,7 @@ suspend fun Conversation.subjects(): List? {
return history.last().parsed()?.subjects
}
+//TODO - move this to thing and location node
suspend fun Named?.hasTag(tag: String): Boolean {
return if (this != null) {
(this is LocationNode && getLocation().properties.tags.has(tag)) ||
@@ -88,4 +89,4 @@ suspend fun Named?.hasTag(tag: String): Boolean {
} else {
false
}
-}
\ No newline at end of file
+}
diff --git a/src/commonMain/kotlin/core/DependencyInjector.kt b/src/commonMain/kotlin/core/DependencyInjector.kt
index 0aa53f1ed..a7d683b25 100644
--- a/src/commonMain/kotlin/core/DependencyInjector.kt
+++ b/src/commonMain/kotlin/core/DependencyInjector.kt
@@ -4,17 +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.desire.DesiresCollection
-import core.ai.desire.DesiresGenerated
+import core.ai.packages.AIPackageTemplatesCollection
+import core.ai.packages.AIPackageTemplatesGenerated
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
@@ -77,7 +76,7 @@ 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(),
BodyPartsCollection::class to BodyPartsGenerated(),
@@ -88,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/GameManager.kt b/src/commonMain/kotlin/core/GameManager.kt
index a0488cc62..0fa61c596 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
@@ -59,12 +57,14 @@ 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.properties.values.put(DEBUG_PACKAGE, AIPackageKeys.PEASANT)
+// GameState.putDebug(DebugType.CLARITY, true)
+
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 ea300618d..c1afa3175 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,10 @@ 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_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"
-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
new file mode 100644
index 000000000..f130bcae4
--- /dev/null
+++ b/src/commonMain/kotlin/core/MagicStrings.kt
@@ -0,0 +1,52 @@
+package core
+
+object AIPackageKeys {
+ const val CREATURE = "Creature"
+ const val PREDATOR = "Predator"
+ const val PEASANT = "Peasant"
+}
+
+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"
+}
+
+object ValueKey {
+ const val GOAL = "Goal"
+}
+
+object HowToUse {
+ const val ATTACK = "Attack"
+ 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 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"
+}
+
+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/ai/AI.kt b/src/commonMain/kotlin/core/ai/AI.kt
index aac6fd96b..6756a0be6 100644
--- a/src/commonMain/kotlin/core/ai/AI.kt
+++ b/src/commonMain/kotlin/core/ai/AI.kt
@@ -6,16 +6,16 @@ import core.thing.Thing
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()
-
+ abstract suspend fun takeAction(): Boolean
val actions = mutableListOf()
suspend fun chooseAction() {
- if (!creature.isPlayer()) {
- takeAction()
+ if (enabled && !creature.isPlayer()) {
+ takenTurn = takeAction()
}
}
-
-
-}
\ No newline at end of file
+}
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
new file mode 100644
index 000000000..28e78a630
--- /dev/null
+++ b/src/commonMain/kotlin/core/ai/AIPackageManager.kt
@@ -0,0 +1,24 @@
+package core.ai
+
+import building.ModManager
+import core.DependencyInjector
+import core.ai.packages.AIPackage
+import core.ai.packages.AIPackageTemplatesCollection
+import core.startupLog
+import core.utility.lazyM
+
+object AIPackageManager {
+ var aiPackages by lazyM { loadAIPackages() }
+ private set
+
+ private fun loadAIPackages(): Map {
+ startupLog("Loading AI Packages.")
+ 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 }
+ }
+
+ fun reset() {
+ aiPackages = loadAIPackages()
+ }
+}
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/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/DumbAI.kt b/src/commonMain/kotlin/core/ai/DumbAI.kt
index 9474ee740..a52252182 100644
--- a/src/commonMain/kotlin/core/ai/DumbAI.kt
+++ b/src/commonMain/kotlin/core/ai/DumbAI.kt
@@ -3,15 +3,10 @@ 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 fun copy() = DumbAI()
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 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/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/PackageBasedAI.kt b/src/commonMain/kotlin/core/ai/PackageBasedAI.kt
new file mode 100644
index 000000000..109aa4357
--- /dev/null
+++ b/src/commonMain/kotlin/core/ai/PackageBasedAI.kt
@@ -0,0 +1,32 @@
+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
+import core.utility.RandomManager
+
+class PackageBasedAI(val aiPackage: AIPackage) : AI() {
+ val previousIdeas = mutableListOf()
+ 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)
+ previousIdeas.add(idea.name)
+ idea.action(creature).forEach { EventManager.postEvent(it) }
+ return idea.takesTurn
+ }
+
+ 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/ai/PlayerControlledAI.kt b/src/commonMain/kotlin/core/ai/PlayerControlledAI.kt
index 976dc7f27..cd41568bc 100644
--- a/src/commonMain/kotlin/core/ai/PlayerControlledAI.kt
+++ b/src/commonMain/kotlin/core/ai/PlayerControlledAI.kt
@@ -6,16 +6,16 @@ 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)
}
- 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/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/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/DiscoverFactEvent.kt b/src/commonMain/kotlin/core/ai/knowledge/DiscoverFactEvent.kt
index 2fd523f9d..7f3486491 100644
--- a/src/commonMain/kotlin/core/ai/knowledge/DiscoverFactEvent.kt
+++ b/src/commonMain/kotlin/core/ai/knowledge/DiscoverFactEvent.kt
@@ -1,6 +1,26 @@
package core.ai.knowledge
+import core.FactKind
+import core.ValueKey
import core.events.Event
+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: String): DiscoverFactEvent {
+ return DiscoverFactEvent(this, Fact(Subject(target), kind))
+}
+
+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/core/ai/knowledge/Fact.kt b/src/commonMain/kotlin/core/ai/knowledge/Fact.kt
index c20806a1d..e25686f4f 100644
--- a/src/commonMain/kotlin/core/ai/knowledge/Fact.kt
+++ b/src/commonMain/kotlin/core/ai/knowledge/Fact.kt
@@ -5,5 +5,5 @@ 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)
+}
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..8b82432d3
--- /dev/null
+++ b/src/commonMain/kotlin/core/ai/knowledge/ForgetFactEvent.kt
@@ -0,0 +1,15 @@
+package core.ai.knowledge
+
+import core.FactKind
+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)
+ }
+}
+
+fun Thing.clearUseGoal(): ForgetFactEvent {
+ 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 327a402aa..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
@@ -28,20 +29,19 @@ data class Mind(
return memory.getFact(source, kind)
}
- suspend fun knowsThingByKind(kind: String): Thing? {
+ 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: String): LocationNode? {
return memory.getSubjects(kind).firstNotNullOfOrNull { it.getLocation() }
}
fun locationByKindExists(kind: String): Boolean {
- return knowsLocationByKind(kind) != null
+ return knownLocationByKind(kind) != null
}
fun learn(source: Subject, kind: String) {
@@ -71,49 +71,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))
}
suspend fun getAggroTarget(): Thing? {
- return knowsThingByKind("aggroTarget")
+ return knownThingByKind(FactKind.AGGRO_TARGET)
}
suspend fun clearAggroTarget() {
getAggroTarget()?.let {
- memory.forget(Fact(Subject(it), "aggroTarget"))
+ memory.forget(Fact(Subject(it), FactKind.AGGRO_TARGET))
}
}
- fun setUseTarget(enemy: Thing) {
- learn(Fact(Subject(enemy), "useTarget"))
- }
-
- suspend fun getUseTarget(): Thing? {
- return knowsThingByKind("useTarget")
+ suspend fun getUseTargetThing(): Thing? {
+ return knownThingByKind(FactKind.USE_TARGET)
}
- suspend fun clearUseTarget() {
- getAggroTarget()?.let {
- memory.forget(Fact(Subject(it), "useTarget"))
- }
+ fun getUseTarget(): Fact? {
+ return memory.getFirstFact(FactKind.USE_TARGET)
}
-}
\ No newline at end of file
+}
diff --git a/src/commonMain/kotlin/core/ai/packages/AIPackage.kt b/src/commonMain/kotlin/core/ai/packages/AIPackage.kt
new file mode 100644
index 000000000..d36b85ab2
--- /dev/null
+++ b/src/commonMain/kotlin/core/ai/packages/AIPackage.kt
@@ -0,0 +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>) {
+ constructor(name: String, ideas: List) : this(name, ideas.groupBy { it.priority })
+
+ suspend fun pickIdea(source: Thing): Idea {
+ 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)
+ .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/ai/packages/AIPackageHelpers.kt b/src/commonMain/kotlin/core/ai/packages/AIPackageHelpers.kt
new file mode 100644
index 000000000..f669443fd
--- /dev/null
+++ b/src/commonMain/kotlin/core/ai/packages/AIPackageHelpers.kt
@@ -0,0 +1,56 @@
+package core.ai.packages
+
+import combat.DamageType
+import combat.attack.AttackEvent
+import combat.attack.startAttack
+import core.GameState
+import core.ValueKey
+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.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
+}
+
+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)
+}
+
+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 }
+}
+
+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/AIPackageTemplate.kt b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplate.kt
new file mode 100644
index 000000000..30ae259ca
--- /dev/null
+++ b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplate.kt
@@ -0,0 +1,16 @@
+package core.ai.packages
+
+
+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.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 }
+ }
+ }
+}
diff --git a/src/commonMain/kotlin/core/ai/packages/AIPackageTemplateBuilder.kt b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplateBuilder.kt
new file mode 100644
index 000000000..92ddf5c94
--- /dev/null
+++ b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplateBuilder.kt
@@ -0,0 +1,55 @@
+package core.ai.packages
+
+import core.thing.Thing
+
+class AIPackageTemplateBuilder(val name: String) {
+ private val templates = mutableListOf()
+ private val ideas = mutableListOf()
+ private val criteriaChildren = mutableMapOf Boolean, IdeasBuilder>()
+ private val dropped = mutableListOf()
+
+ fun build(): AIPackageTemplate {
+ val childIdeas = criteriaChildren.flatMap { (parentCriteria, builder) -> builder.getChildren(parentCriteria) }
+ return AIPackageTemplate(name, (ideas + childIdeas).map { it.build() }, templates, dropped)
+ }
+
+ fun template(vararg names: String) = this.templates.addAll(names)
+
+ fun drop(vararg ideaName: String) {
+ dropped.addAll(ideaName)
+ }
+
+ 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) {
+ 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)
+ }
+
+}
+
+//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() }
+}
diff --git a/src/commonMain/kotlin/core/ai/packages/AIPackageTemplateResource.kt b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplateResource.kt
new file mode 100644
index 000000000..9d0350dad
--- /dev/null
+++ b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplateResource.kt
@@ -0,0 +1,5 @@
+package core.ai.packages
+
+interface AIPackageTemplateResource {
+ val values: List
+}
\ 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
new file mode 100644
index 000000000..da15a595d
--- /dev/null
+++ b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplatesCollection.kt
@@ -0,0 +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
new file mode 100644
index 000000000..2e4a08c40
--- /dev/null
+++ b/src/commonMain/kotlin/core/ai/packages/AIPackageTemplatesGenerated.kt
@@ -0,0 +1,5 @@
+package core.ai.packages
+
+class AIPackageTemplatesGenerated : AIPackageTemplatesCollection {
+ 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/ai/packages/Idea.kt b/src/commonMain/kotlin/core/ai/packages/Idea.kt
new file mode 100644
index 000000000..d94563421
--- /dev/null
+++ b/src/commonMain/kotlin/core/ai/packages/Idea.kt
@@ -0,0 +1,10 @@
+package core.ai.packages
+
+import core.events.Event
+import core.thing.Thing
+import use.interaction.nothing.NothingEvent
+
+
+val DO_NOTHING_IDEA = Idea("Do Nothing", 0, true,{ true }, { listOf(NothingEvent(it)) })
+
+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
new file mode 100644
index 000000000..ceea8a95d
--- /dev/null
+++ b/src/commonMain/kotlin/core/ai/packages/IdeaBuilder.kt
@@ -0,0 +1,54 @@
+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, val takesTurn: Boolean) {
+ internal var criteria: suspend (Thing) -> Boolean = { true }
+ private var action: suspend (Thing) -> List = { listOf() }
+
+ fun build() = Idea(name, priority, takesTurn, criteria, action)
+
+ fun cond(condition: suspend (Thing) -> Boolean) {
+ this.criteria = condition
+ }
+
+ fun actions(action: suspend (Thing) -> List) {
+ this.action = action
+ }
+
+ fun act(action: suspend (Thing) -> Event?) {
+ this.action = { listOfNotNull(action(it)) }
+ }
+}
+
+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, 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) {
+ criteriaChildren[criteria] = IdeasBuilder().apply(initializer)
+ }
+
+}
+
+fun ideas(initializer: IdeasBuilder.() -> Unit): List {
+ return IdeasBuilder().apply(initializer).getChildren()
+}
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/commonMain/kotlin/core/events/EventListenerMapGenerated.kt b/src/commonMain/kotlin/core/events/EventListenerMapGenerated.kt
index 5529210a8..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"]!!), "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"]!!), "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/core/events/EventManager.kt b/src/commonMain/kotlin/core/events/EventManager.kt
index 3b535b215..b55388199 100644
--- a/src/commonMain/kotlin/core/events/EventManager.kt
+++ b/src/commonMain/kotlin/core/events/EventManager.kt
@@ -3,7 +3,9 @@ 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
import core.thing.Thing
import system.debug.DebugType
@@ -38,17 +40,30 @@ object EventManager {
*/
suspend fun processEvents() {
var loop = 0
+ replenishAITurns()
while (eventQueue.isNotEmpty() && loop < MAX_EVENT_LOOPS) {
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() {
- 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()
+ //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) {
@@ -81,7 +96,6 @@ object EventManager {
if (event !is TemporalEvent || event.timeLeft == 0) {
completeEvent(event)
}
-
}
private suspend fun tick() {
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/core/properties/Properties.kt b/src/commonMain/kotlin/core/properties/Properties.kt
index 1e843f4be..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
@@ -10,8 +10,11 @@ import traveling.position.Distances.MIN_RANGE
import traveling.position.Distances.SPEAR_RANGE
import traveling.position.Distances.SWORD_RANGE
+
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 values: Pair) : this(Values(*values), Tags())
constructor(base: Properties, params: Map = mapOf()) : this(
Values(base.values, params),
Tags(base.tags, params)
@@ -71,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 {
@@ -102,4 +105,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/Tags.kt b/src/commonMain/kotlin/core/properties/Tags.kt
index 78f6eab75..6118bd5e1 100644
--- a/src/commonMain/kotlin/core/properties/Tags.kt
+++ b/src/commonMain/kotlin/core/properties/Tags.kt
@@ -93,4 +93,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/Values.kt b/src/commonMain/kotlin/core/properties/Values.kt
index 2a1fa40ed..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
}
@@ -120,4 +119,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/core/thing/Thing.kt b/src/commonMain/kotlin/core/thing/Thing.kt
index b1f3fbf6c..f547ed112 100644
--- a/src/commonMain/kotlin/core/thing/Thing.kt
+++ b/src/commonMain/kotlin/core/thing/Thing.kt
@@ -1,8 +1,6 @@
package core.thing
import core.GameState
-import core.ai.AI
-import core.ai.DumbAI
import core.ai.behavior.Behavior
import core.ai.knowledge.Mind
import core.body.Body
@@ -18,7 +16,6 @@ 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.network.LocationNode
import traveling.location.network.NOWHERE_NODE
@@ -59,23 +56,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
}
@@ -194,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
}
@@ -260,4 +243,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 d456914a3..9d12d716b 100644
--- a/src/commonMain/kotlin/core/thing/ThingBuilder.kt
+++ b/src/commonMain/kotlin/core/thing/ThingBuilder.kt
@@ -1,5 +1,9 @@
package core.thing
+import core.AIPackageKeys
+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 +16,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 +23,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
@@ -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 } ?: if (tagsToApply.contains(CREATURE_TAG)) ConditionalAI() else DumbAI()
+ 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")))
@@ -150,8 +152,8 @@ class ThingBuilder(internal val name: String) {
this.ai = PlayerControlledAI()
}
- fun conditionalAI() {
- this.ai = ConditionalAI()
+ fun packageAI(packageName: String) {
+ this.ai = PackageBasedAI(AIPackageManager.aiPackages[packageName]!!)
}
fun dumbAI() {
@@ -232,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 })
@@ -252,8 +254,17 @@ class ThingBuilder(internal val name: String) {
}.also { bodyCustomizer.apply(it) }
}
+ private fun discernAI(props: Properties) : AI {
+ return when {
+ props.tags.has(TagKey.PREDATOR) -> PackageBasedAI(AIPackageManager.aiPackages[AIPackageKeys.PREDATOR]!!)
+ props.tags.has(TagKey.COMMONER) -> PackageBasedAI(AIPackageManager.aiPackages[AIPackageKeys.PEASANT]!!)
+ props.tags.has(TagKey.CREATURE) -> PackageBasedAI(AIPackageManager.aiPackages[AIPackageKeys.CREATURE]!!)
+ 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/core/thing/creature/CreatureManager.kt b/src/commonMain/kotlin/core/thing/creature/CreatureManager.kt
index b431d05e7..a8f19e9a3 100644
--- a/src/commonMain/kotlin/core/thing/creature/CreatureManager.kt
+++ b/src/commonMain/kotlin/core/thing/creature/CreatureManager.kt
@@ -2,20 +2,17 @@ package core.thing.creature
import building.ModManager
import core.DependencyInjector
-import core.ai.AIManager
+import core.TagKey
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
-const val CREATURE_TAG = "Creature"
-
object CreatureManager {
private val creatures = Backer(::loadCreatures)
suspend fun getCreatures() = creatures.get()
@@ -27,7 +24,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 +69,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 9ff1631a2..ea8b250e9 100644
--- a/src/commonMain/kotlin/crafting/RecipeManager.kt
+++ b/src/commonMain/kotlin/crafting/RecipeManager.kt
@@ -2,14 +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.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
@@ -44,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("Recipe")?.sources
+ source.mind.memory.getListFact(FactKind.RECIPE)?.sources
?.mapNotNull { it.topic }?.let { getRecipes(it) }?.toNameSearchableList()
?: NameSearchableList()
}
@@ -61,4 +60,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/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/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/explore/look/DescribeMind.kt b/src/commonMain/kotlin/explore/look/DescribeMind.kt
index bc6ddf371..b40706ec3 100644
--- a/src/commonMain/kotlin/explore/look/DescribeMind.kt
+++ b/src/commonMain/kotlin/explore/look/DescribeMind.kt
@@ -2,8 +2,8 @@ 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
import core.ai.knowledge.Mind
import core.history.displayToMe
@@ -15,20 +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)
- }
-}
\ 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)"
+ message += "\n\t" + ai.previousIdeas.reversed().joinToString("\n\t")
+ source.displayToMe(message)
+}
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/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/agenda/CommonAgendas.kt b/src/commonMain/kotlin/resources/ai/agenda/CommonAgendas.kt
deleted file mode 100644
index 955e57a2d..000000000
--- a/src/commonMain/kotlin/resources/ai/agenda/CommonAgendas.kt
+++ /dev/null
@@ -1,221 +0,0 @@
-package resources.ai.agenda
-
-import combat.DamageType
-import combat.attack.AttackEvent
-import combat.attack.startAttack
-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.getUseTarget()?.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.knowsLocationByKind("LocationGoal")
- goal == null || s.location == goal || s.mind.route?.destination == goal
- }
- result { s ->
- s.mind.knowsLocationByKind("LocationGoal")?.let {
- FindRouteEvent(s, s.location, it)
- }
- }
- }
- actionDetailed("Travel to Location") {
- shouldSkip { s ->
- s.location == s.mind.knowsLocationByKind("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.getUseTarget()?.position?.let {
- creature.canReach(it)
- }
- }
- result { creature ->
- creature.mind.getUseTarget()?.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.getUseTarget()?.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, "aggroTarget")
- }
- }
-
- 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")
- }
- }
-
- agenda("Rest") {
- action("Rest") { creature -> RestEvent(creature, 2) }
- }
-
- agenda("Sleep In Bed") {
- actions("Find Bed") { owner ->
- owner.mind.knowsThingByKind("MyBed")?.let { target ->
- listOf(
- owner.discover(target, "useTarget"),
- 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.knowsLocationByKind("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.getUseTarget()?.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 96e60d646..000000000
--- a/src/commonMain/kotlin/resources/ai/desire/CommonDesires.kt
+++ /dev/null
@@ -1,81 +0,0 @@
-package resources.ai.desire
-
-import core.GameState
-import core.ai.desire.DesireResource
-import core.ai.desire.desires
-import core.commands.CommandParsers
-import core.thing.Thing
-import status.stat.STAMINA
-import time.TimeManager
-
-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.knowsLocationByKind("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.items().firstOrNull { it.properties.tags.has("Food") } != null }) {
- agenda("Eat Food")
- }
-
- cond(20, { s -> s.activators().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 }) {
- 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 }
-}
\ No newline at end of file
diff --git a/src/commonMain/kotlin/resources/ai/packages/CommonAIPackages.kt b/src/commonMain/kotlin/resources/ai/packages/CommonAIPackages.kt
new file mode 100644
index 000000000..6b47dad3c
--- /dev/null
+++ b/src/commonMain/kotlin/resources/ai/packages/CommonAIPackages.kt
@@ -0,0 +1,49 @@
+package resources.ai.packages
+
+import conversation.dsl.hasTag
+import core.FactKind
+import core.GameState
+import core.HowToUse
+import core.ai.knowledge.clearUseGoal
+import core.ai.knowledge.discover
+import core.ai.knowledge.setUseTarget
+import core.ai.packages.*
+import status.rest.RestEvent
+
+class CommonAIPackages : AIPackageTemplateResource {
+ override val values = aiPackages {
+ aiPackage("Predator") {
+ template("Creature")
+
+ idea("Attack Goal", 50) {
+ cond { it.canReachGoal(HowToUse.ATTACK) }
+ actions { s ->
+ listOfNotNull(
+ s.mind.getUseTargetThing()?.let { clawAttack(it, s) },
+ s.clearUseGoal()
+ )
+ }
+ }
+ 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("Sleep Outside") {
+ 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/CreatureAIPackage.kt b/src/commonMain/kotlin/resources/ai/packages/CreatureAIPackage.kt
new file mode 100644
index 000000000..01d626b80
--- /dev/null
+++ b/src/commonMain/kotlin/resources/ai/packages/CreatureAIPackage.kt
@@ -0,0 +1,116 @@
+package resources.ai.packages
+
+import conversation.dsl.hasTag
+import core.GameState
+import core.HowToUse
+import core.TagKey
+import core.ai.knowledge.clearUseGoal
+import core.ai.knowledge.setUseTarget
+import core.ai.packages.*
+import core.utility.random
+import status.rest.RestEvent
+import status.stat.STAMINA
+import traveling.move.startMoveEvent
+import traveling.travel.TravelStartEvent
+import use.eat.EatFoodEvent
+import use.interaction.InteractEvent
+
+class CreatureAIPackage : AIPackageTemplateResource {
+ override val values = aiPackages {
+ aiPackage("Creature") {
+
+ idea("Attack", 70) {
+ cond { it.mind.getAggroTarget() != null && it.canReach(it.mind.getAggroTarget()!!.position) }
+ act {
+ clawAttack(it.mind.getAggroTarget()!!, it)
+ }
+ }
+
+ idea("Start Travel", 50) {
+ 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
+ plotRouteAndStartTravel(s, goal)
+ }
+ }
+
+ 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 { 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 { s -> s.mind.getAggroTarget()?.let { s.location == it.location && !s.canReach(it.position) } ?: false }
+ act { startMoveEvent(it, destination = it.mind.getAggroTarget()!!.position) }
+ }
+
+ //TODO - Maybe last eaten + amount of time
+ //TODO - second version to go look for food
+ idea("Want Food", takesTurn = false) {
+ 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) }
+ ?.let { s.setUseTarget(it, HowToUse.EAT) }
+ }
+ }
+
+ idea("Eat Food", 45) {
+ cond { it.canReachGoal(HowToUse.EAT) }
+ actions { s ->
+ listOfNotNull(
+ s.mind.getUseTargetThing()?.let { EatFoodEvent(s, it) },
+ s.clearUseGoal()
+ )
+ }
+ }
+
+ idea("Interact", 40) {
+ cond { it.canReachGoal(HowToUse.INTERACT) }
+ actions { s ->
+ listOfNotNull(
+ s.mind.getUseTargetThing()?.let { InteractEvent(s, it) },
+ s.clearUseGoal()
+ )
+ }
+ }
+
+ 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(
+ 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.floor())
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/commonMain/kotlin/resources/ai/packages/PeasantAIPackage.kt b/src/commonMain/kotlin/resources/ai/packages/PeasantAIPackage.kt
new file mode 100644
index 000000000..b93d02e02
--- /dev/null
+++ b/src/commonMain/kotlin/resources/ai/packages/PeasantAIPackage.kt
@@ -0,0 +1,62 @@
+package resources.ai.packages
+
+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
+import core.ai.packages.perceivedActivators
+import core.ai.packages.plotRouteAndStartTravel
+import core.commands.CommandParsers
+
+class PeasantAIPackage : AIPackageTemplateResource {
+ override val values = aiPackages {
+ aiPackage(PEASANT) {
+ 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", takesTurn = false) {
+ 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", takesTurn = false) {
+ 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 303bdd5f0..4cba742f3 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,13 +88,23 @@ 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)
)
}
}
+ 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.name} tends the ${event.interactionTarget.name}."
+ MessageEvent(event.creature, message, messageToOthers, false)
+ }
+ }
+
behavior("Restrict Destination", InteractEvent::class) {
events { event, params ->
val source = event.creature
@@ -98,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)
@@ -109,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.name} rests for $hoursRested hours."),
RestEvent(event.creature, hoursRested)
)
}
@@ -127,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)
)
@@ -155,4 +169,4 @@ class CommonBehaviors : BehaviorResource {
}
}
}
-}
\ No newline at end of file
+}
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/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 3c7e6b646..2a69a6b03 100644
--- a/src/commonMain/kotlin/resources/thing/creature/CommonCreatures.kt
+++ b/src/commonMain/kotlin/resources/thing/creature/CommonCreatures.kt
@@ -1,7 +1,12 @@
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
+import status.stat.AGILITY
class CommonCreatures : CreatureResource {
@@ -12,8 +17,9 @@ class CommonCreatures : CreatureResource {
soul("Health", 3)
soul("Strength", 1)
soul("Bare Handed", 2)
+ soul(AGILITY, 1)
props {
- tag("Small", "Predator")
+ tag("Small", PREDATOR)
}
//TODO - make this a 'death item' that's spawned on death
item("Poor Quality Meat")
@@ -25,11 +31,12 @@ class CommonCreatures : CreatureResource {
soul("Health", 10)
soul("Strength", 3)
soul("Bare Handed", 2)
- mind{
- learn("WorkTags", listOf("Farmable"))
+ soul(AGILITY, 1)
+ mind {
+ learn(WORK_TAGS, listOf(FARMABLE))
}
props {
- tag("Commoner")
+ tag(COMMONER)
value("Race", "Human")
}
item("Brown Pants", "Old Shirt")
@@ -48,4 +55,4 @@ class CommonCreatures : CreatureResource {
}
-}
\ No newline at end of file
+}
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/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/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)
+}
diff --git a/src/commonMain/kotlin/time/TimeManager.kt b/src/commonMain/kotlin/time/TimeManager.kt
index 398a4700e..4a82b257d 100644
--- a/src/commonMain/kotlin/time/TimeManager.kt
+++ b/src/commonMain/kotlin/time/TimeManager.kt
@@ -80,16 +80,16 @@ 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() {
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/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
+}
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/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
+}
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/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/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/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()
diff --git a/src/jvmMain/kotlin/building/ModLoader.kt b/src/jvmMain/kotlin/building/ModLoader.kt
index 0ffb42ae0..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
@@ -21,10 +21,11 @@ import traveling.location.network.NetworkResource
import traveling.location.weather.WeatherResource
import java.io.File
import java.lang.reflect.ParameterizedType
-import java.net.URL
+import java.net.URI
import java.net.URLClassLoader
import java.util.jar.JarEntry
import java.util.jar.JarFile
+import kotlin.jvm.java
fun loadMods() {
ModManager.reset()
@@ -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()) {
@@ -76,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)
@@ -102,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)
@@ -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/TestConstructors.kt b/src/jvmTest/kotlin/TestConstructors.kt
index e572ba0ca..9fbceab81 100644
--- a/src/jvmTest/kotlin/TestConstructors.kt
+++ b/src/jvmTest/kotlin/TestConstructors.kt
@@ -2,14 +2,11 @@ import conversation.dsl.DialoguesCollection
import conversation.dsl.DialoguesMock
import core.DependencyInjector
import core.GameState
-import core.ai.AIManager
-import core.ai.agenda.AgendasCollection
-import core.ai.agenda.AgendasMock
+import core.TagKey
+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
@@ -25,7 +22,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 +113,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)
)
)
}
@@ -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()
@@ -189,4 +183,3 @@ fun createMockedGame() {
GameLogger.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/jvmTest/kotlin/core/ai/packages/AIPackageTemplatesMock.kt b/src/jvmTest/kotlin/core/ai/packages/AIPackageTemplatesMock.kt
new file mode 100644
index 000000000..a64ceb006
--- /dev/null
+++ b/src/jvmTest/kotlin/core/ai/packages/AIPackageTemplatesMock.kt
@@ -0,0 +1,3 @@
+package core.ai.packages
+
+class AIPackageTemplatesMock(override val values: List = listOf()) : AIPackageTemplatesCollection
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
diff --git a/src/jvmTestIntegration/kotlin/commandCombos/CommandComboTest.kt b/src/jvmTestIntegration/kotlin/commandCombos/CommandComboTest.kt
index 8b179a40e..15f1a7c06 100644
--- a/src/jvmTestIntegration/kotlin/commandCombos/CommandComboTest.kt
+++ b/src/jvmTestIntegration/kotlin/commandCombos/CommandComboTest.kt
@@ -124,12 +124,11 @@ 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!"))
}
-
@Test
fun doNotAttackDeadThing() {
val input = "s && slash torso of rat && sl rat && sl && slash rat"
@@ -226,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
@@ -264,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")
@@ -277,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")
@@ -287,4 +286,30 @@ class CommandComboTest {
assertTrue(tags.hasAll(listOf("Bronze", "Weapon")))
}
}
-}
\ No newline at end of file
+
+ @Test
+ fun farmerTendsWheat() {
+ runBlocking {
+ GameState.putDebug(DebugType.CLARITY, true)
+ CommandParsers.parseCommand(
+ GameState.player,
+ "rs 8 && w && rs 1 && e && rs 1 && rs 10 && mv wheat"
+ )
+
+ 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 && rs 1 && rs 10 && mv bed"
+ )
+
+ assertTrue(GameLogger.getMainHistory().contains("Farmer rests for 10 hours."))
+ }
+ }
+}
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
+ }
}
diff --git a/src/jvmTestIntegration/kotlin/validation/AIPackageValidator.kt b/src/jvmTestIntegration/kotlin/validation/AIPackageValidator.kt
new file mode 100644
index 000000000..6ad653aff
--- /dev/null
+++ b/src/jvmTestIntegration/kotlin/validation/AIPackageValidator.kt
@@ -0,0 +1,73 @@
+package validation
+
+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
+
+class AIPackageValidator {
+
+ private val packages = runBlocking { AIPackageManager.aiPackages }
+ private val templates = (DependencyInjector.getImplementation(AIPackageTemplatesCollection::class).values + ModManager.aiPackages)
+
+ @Test
+ fun validate() {
+ assertEquals(
+ 0, noDuplicatePackageNames() +
+ noDuplicateIdeaNames() +
+ packageTemplateStringReferenceExists()
+ )
+ }
+
+ 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
+ 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
+ }
+
+ private fun packageTemplateStringReferenceExists(): 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
+ }
+
+
+}
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 481892a06..e1649af27 100644
--- a/src/jvmTools/kotlin/building/ReflectionTools.kt
+++ b/src/jvmTools/kotlin/building/ReflectionTools.kt
@@ -2,12 +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.desire.DesireResource
-import core.ai.desire.DesireTree
+import core.ai.packages.AIPackageTemplate
+import core.ai.packages.AIPackageTemplateResource
import core.body.BodyPartResource
import core.body.BodyResource
import core.commands.Command
@@ -60,12 +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)
@@ -279,4 +276,4 @@ object ReflectionTools {
}
-}
\ No newline at end of file
+}