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 +}