diff --git a/.github/actions/init-version-gdext/action.yml b/.github/actions/init-version-gdext/action.yml index ca39fd9..8cd29c5 100644 --- a/.github/actions/init-version-gdext/action.yml +++ b/.github/actions/init-version-gdext/action.yml @@ -15,7 +15,7 @@ runs: echo "GDEXTENSION_VERSION=${GDEXTENSION_VERSION}" >> "$GITHUB_ENV" - cd ../limboai + cd .. echo "LIMBOAI_VERSION=$( (git describe --tags --exact-match HEAD || git rev-parse --short HEAD) | sed 's/\(.*\)-\(.*\)/\1.\2/g' )" >> "$GITHUB_ENV" - name: Set NAME_PREFIX diff --git a/.github/workflows/gdextension.yml b/.github/workflows/gdextension.yml index e27e397..0a83767 100644 --- a/.github/workflows/gdextension.yml +++ b/.github/workflows/gdextension.yml @@ -167,6 +167,12 @@ jobs: BIN: liblimboai.${{matrix.opts.platform}}.${{matrix.opts.target}}.${{matrix.opts.arch}} steps: + - name: Clone LimboAI module + uses: actions/checkout@v4 + with: + fetch-tags: true + ref: ${{ inputs.limboai-ref }} + - name: Clone godot-cpp uses: actions/checkout@v4 with: @@ -175,15 +181,8 @@ jobs: path: godot-cpp ref: ${{ inputs.godot-cpp-ref }} - - name: Clone LimboAI module - uses: actions/checkout@v4 - with: - path: limboai - fetch-tags: true - ref: ${{ inputs.limboai-ref }} - # Inits GDEXTENSION_VERSION, LIMBOAI_VERSION and NAME_PREFIX environment variables. - - uses: ./limboai/.github/actions/init-version-gdext + - uses: ./.github/actions/init-version-gdext - name: Output NAME_PREFIX id: output-name-prefix @@ -191,7 +190,7 @@ jobs: - name: Setup Linux toolchain if: matrix.opts.platform == 'linux' - uses: ./limboai/.github/actions/setup-linux-toolchain + uses: ./.github/actions/setup-linux-toolchain with: arch: ${{matrix.opts.arch}} @@ -257,15 +256,6 @@ jobs: ${{env.BIN}}-${{inputs.godot-cpp-ref}}-${{inputs.limboai-ref}} ${{env.BIN}}-${{inputs.godot-cpp-ref}} - - name: Setup project structure for GDExtension - shell: bash - run: | - bash ./limboai/gdextension/setup_gdextension.sh --copy-all - echo "---" - ls -l - echo "---" - ls -l -R ./demo/ - - name: Compilation shell: bash env: @@ -279,9 +269,9 @@ jobs: run: | mkdir out mv demo/addons/ out/ - cp limboai/{README,LICENSE,LOGO_LICENSE}.md out/addons/limboai/ - cp -R limboai/demo/demo/ out/demo/ - cp limboai/demo/LICENSE_ASSETS.md out/demo/ + cp {README,LICENSE,LOGO_LICENSE}.md out/addons/limboai/ + cp -R demo/demo/ out/demo/ + cp demo/LICENSE_ASSETS.md out/demo/ rm -f out/addons/limboai/bin/*.{exp,lib,pdb} echo "${LIMBOAI_VERSION}" > out/addons/limboai/version.txt echo "---" diff --git a/.gitignore b/.gitignore index 8fcda8a..b8dbf90 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ demo/addons/ demo/script_templates/ icons/*.import +godot-cpp # Godot auto generated files *.gen.* diff --git a/README.md b/README.md index 838eb1c..069edf0 100644 --- a/README.md +++ b/README.md @@ -93,13 +93,19 @@ LimboAI can be used as either a C++ module or as a GDExtension shared library. G ### Compiling from source ->**🛈 For GDExtension:** Refer to comments in [setup_gdextension.sh](./gdextension/setup_gdextension.sh) file. - - Download the Godot Engine source code and put this module source into the `modules/limboai` directory. - Consult the Godot Engine documentation for instructions on [how to build from source code](https://docs.godotengine.org/en/stable/contributing/development/compiling/index.html). - If you plan to export a game utilizing the LimboAI module, you'll also need to build export templates. - To execute unit tests, compile the engine with `tests=yes` and run it with `--test --tc="*[LimboAI]*"`. +#### For GDExtension + +- You'll need SCons build tool and a C++ compiler. See also [Compiling](https://docs.godotengine.org/en/stable/contributing/development/compiling/index.html). +- Run `scons target=editor` to build the plugin library for your current platform. + - SCons will automatically clone the godot-cpp/ repository if it doesn't already exist in the `limboai/godot-cpp` directory. + - By default, built targets are placed in the demo project: `demo/addons/limboai/bin/` +- Check `scons -h` for other options and targets. + ## Using the plugin - [Online Documentation](https://limboai.readthedocs.io/en/latest/index.html) diff --git a/SConstruct b/SConstruct new file mode 100644 index 0000000..b4fa6e7 --- /dev/null +++ b/SConstruct @@ -0,0 +1,160 @@ +#!/usr/bin/env python +""" +This is SConstruct file for building GDExtension variant using SCons build system. +For module variant, see SCsub file. + +Use --project=DIR to customize output path for built targets. + - Built targets are placed into "DIR/addons/limboai/bin". + - For example: scons --project="../my_project" + - built targets will be placed into "../my_project/addons/limboai/bin". + - If not specified, built targets are put into the demo/ project. +""" + +import os +import sys +import subprocess +from limboai_version import generate_module_version_header, godot_cpp_ref + +sys.path.append("gdextension") +from update_icon_entries import update_icon_entries +from fix_icon_imports import fix_icon_imports + +# Check if godot-cpp/ exists +if not os.path.exists("godot-cpp"): + print("Directory godot-cpp/ not found. Cloning repository...") + result = subprocess.run( + ["git", "clone", "-b", godot_cpp_ref, "https://github.com/godotengine/godot-cpp.git"], + check=True, + # capture_output=True + ) + if result.returncode != 0: + print("Error: Cloning godot-cpp repository failed.") + Exit(1) + print("Finished cloning godot-cpp repository.") + +AddOption( + "--project", + dest="project", + type="string", + nargs=1, + action="store", + metavar="DIR", + default="demo", + help="Specify project directory", +) + +help_text = """ +Options: + --project=DIR Specify project directory (default: "demo"); + built targets will be placed in DIR/addons/limboai/bin +""" +Help(help_text) + +project_dir = GetOption("project") +if not os.path.isdir(project_dir): + print("Project directory not found: " + project_dir) + Exit(2) + +# Parse LimboAI-specific variables. +vars = Variables() +vars.AddVariables( + BoolVariable("deploy_manifest", help="Deploy limboai.gdextension into PROJECT/addons/limboai/bin", default=True), + BoolVariable("deploy_icons", help="Deploy icons into PROJECT/addons/limboai/icons", default=True), +) +env = Environment(tools=["default"], PLATFORM="", variables=vars) +Help(vars.GenerateHelpText(env)) + +# Read LimboAI-specific variables. +deploy_manifest = env["deploy_manifest"] +deploy_icons = env["deploy_icons"] + +# Remove processed variables from ARGUMENTS to avoid godot-cpp warnings. +for o in vars.options: + if o.key in ARGUMENTS: + del ARGUMENTS[o.key] + +# For reference: +# - CCFLAGS are compilation flags shared between C and C++ +# - CFLAGS are for C-specific compilation flags +# - CXXFLAGS are for C++-specific compilation flags +# - CPPFLAGS are for pre-processor flags +# - CPPDEFINES are for pre-processor defines +# - LINKFLAGS are for linking flags + +env = SConscript("godot-cpp/SConstruct") + +# Generate version header. +print("Generating LimboAI version header...") +generate_module_version_header() + +# Update icon entries in limboai.gdextension file. +# Note: This will remove everything after [icons] section, and rebuild it with generated icon entries. +print("Updating LimboAI icon entries...") +update_icon_entries(silent=True) + +# Fix icon imports in the PROJECT/addons/limboai/icons/. +# Enables scaling and color conversion in the editor for imported SVG icons. +try: + fix_icon_imports(project_dir) +except FileNotFoundError as e: + print(e) +except Exception as e: + print("Unknown error: " + str(e)) + +# Tweak this if you want to use different folders, or more folders, to store your source code in. +env.Append(CPPDEFINES=["LIMBOAI_GDEXTENSION"]) +sources = Glob("*.cpp") +sources += Glob("blackboard/*.cpp") +sources += Glob("blackboard/bb_param/*.cpp") +sources += Glob("bt/*.cpp") +sources += Glob("bt/tasks/*.cpp") +sources += Glob("bt/tasks/blackboard/*.cpp") +sources += Glob("bt/tasks/composites/*.cpp") +sources += Glob("bt/tasks/decorators/*.cpp") +sources += Glob("bt/tasks/scene/*.cpp") +sources += Glob("bt/tasks/utility/*.cpp") +sources += Glob("gdextension/*.cpp") +sources += Glob("editor/debugger/*.cpp") +sources += Glob("editor/*.cpp") +sources += Glob("hsm/*.cpp") +sources += Glob("util/*.cpp") + +# Generate documentation header. +if env["target"] in ["editor", "template_debug"]: + doc_data = env.GodotCPPDocData("gen/doc_data.gen.cpp", source=Glob("doc_classes/*.xml")) + sources.append(doc_data) + +# Build library. +if env["platform"] == "macos": + library = env.SharedLibrary( + project_dir + + "/addons/limboai/bin/liblimboai.{}.{}.framework/liblimboai.{}.{}".format( + env["platform"], env["target"], env["platform"], env["target"] + ), + source=sources, + ) +else: + library = env.SharedLibrary( + project_dir + "/addons/limboai/bin/liblimboai{}{}".format(env["suffix"], env["SHLIBSUFFIX"]), + source=sources, + ) + +Default(library) + +# Deploy icons into PROJECT/addons/limboai/icons. +if deploy_icons: + cmd_deploy_icons = env.Command( + project_dir + "/addons/limboai/icons/", + "icons/", + Copy("$TARGET", "$SOURCE"), + ) + Default(cmd_deploy_icons) + +# Deploy limboai.gdextension into PROJECT/addons/limboai/bin. +if deploy_manifest: + cmd_deploy_manifest = env.Command( + project_dir + "/addons/limboai/bin/limboai.gdextension", + "gdextension/limboai.gdextension", + Copy("$TARGET", "$SOURCE"), + ) + Default(cmd_deploy_manifest) diff --git a/gdextension/SConstruct b/gdextension/SConstruct deleted file mode 100644 index 122f0be..0000000 --- a/gdextension/SConstruct +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env python -import os -import sys - -env = SConscript("godot-cpp/SConstruct") - -# For reference: -# - CCFLAGS are compilation flags shared between C and C++ -# - CFLAGS are for C-specific compilation flags -# - CXXFLAGS are for C++-specific compilation flags -# - CPPFLAGS are for pre-processor flags -# - CPPDEFINES are for pre-processor defines -# - LINKFLAGS are for linking flags - -# Generate version header. -sys.path.append("./limboai") -import limboai_version - -os.chdir("./limboai") -limboai_version.generate_module_version_header() -os.chdir("..") -sys.path.remove("./limboai") - -# Tweak this if you want to use different folders, or more folders, to store your source code in. -env.Append(CPPPATH=["limboai/"]) -env.Append(CPPDEFINES=["LIMBOAI_GDEXTENSION"]) -sources = Glob("limboai/*.cpp") -sources += Glob("limboai/blackboard/*.cpp") -sources += Glob("limboai/blackboard/bb_param/*.cpp") -sources += Glob("limboai/bt/*.cpp") -sources += Glob("limboai/bt/tasks/*.cpp") -sources += Glob("limboai/bt/tasks/blackboard/*.cpp") -sources += Glob("limboai/bt/tasks/composites/*.cpp") -sources += Glob("limboai/bt/tasks/decorators/*.cpp") -sources += Glob("limboai/bt/tasks/scene/*.cpp") -sources += Glob("limboai/bt/tasks/utility/*.cpp") -sources += Glob("limboai/gdextension/*.cpp") -sources += Glob("limboai/editor/debugger/*.cpp") -sources += Glob("limboai/editor/*.cpp") -sources += Glob("limboai/hsm/*.cpp") -sources += Glob("limboai/util/*.cpp") - -# Generate documentation header. -if env["target"] in ["editor", "template_debug"]: - doc_data = env.GodotCPPDocData("limboai/gen/doc_data.gen.cpp", source=Glob("limboai/doc_classes/*.xml")) - sources.append(doc_data) - -if env["platform"] == "macos": - library = env.SharedLibrary( - "demo/addons/limboai/bin/liblimboai.{}.{}.framework/liblimboai.{}.{}".format( - env["platform"], env["target"], env["platform"], env["target"] - ), - source=sources, - ) -else: - library = env.SharedLibrary( - "demo/addons/limboai/bin/liblimboai{}{}".format(env["suffix"], env["SHLIBSUFFIX"]), - source=sources, - ) - -Default(library) diff --git a/gdextension/fix_icon_imports.py b/gdextension/fix_icon_imports.py new file mode 100755 index 0000000..ecfccb6 --- /dev/null +++ b/gdextension/fix_icon_imports.py @@ -0,0 +1,114 @@ +#!/usr/bin/python +""" +Usage: fix_icon_imports.py [--silent] PROJECT_DIR +Fix icon imports in PROJECT_DIR/addons/limboai/icons/. + +Options: + -s, --silent Don't print anything. + -h, --help Print this message. + +Dependencies: python3. + +Use of this source code is governed by an MIT-style +license that can be found in the LICENSE file or at +https://opensource.org/licenses/MIT. +""" + +import os +import glob +import sys +import getopt + + +def usage(): + print(__doc__.strip()) + + +def get_limboai_icon_import_files(project_path="demo/"): + if not os.path.isdir(project_path): + raise FileNotFoundError("Project directory not found: " + project_path) + + icons_path = os.path.join(project_path, "addons/limboai/icons/") + if not os.path.isdir(icons_path): + raise FileNotFoundError("Icons directory not found: " + icons_path) + + return glob.glob(icons_path + "*.import") + + +def fix_import_file(file_path): + if not os.path.isfile(file_path): + raise FileNotFoundError("File not found: " + file_path) + + old_lines = [] + new_lines = [] + + file = open(file_path, "r") + old_lines = file.readlines() + file.close() + + for line in old_lines: + line = line.replace("editor/scale_with_editor_scale=false", "editor/scale_with_editor_scale=true") + line = line.replace( + "editor/convert_colors_with_editor_theme=false", "editor/convert_colors_with_editor_theme=true" + ) + new_lines.append(line) + + if old_lines != new_lines: + file = open(file_path, "w") + for line in new_lines: + file.write(line) + file.close() + return True + return False + + +def fix_icon_imports(project_path="demo/", silent=False): + if not silent: + print("Checking icon import files...") + + project_import_files = get_limboai_icon_import_files(project_path) + + for import_file in project_import_files: + changed = fix_import_file(import_file) + if changed and not silent: + print("Updated icon import file: " + import_file) + + +if __name__ == "__main__": + silent = False + project_path = "demo/" + + try: + opts, args = getopt.getopt(sys.argv[1:], "s", ["silent"]) + except getopt.GetoptError as e: + print( + "%s: %s!\n" + % ( + os.path.basename(__file__), + e.msg, + ) + ) + usage() + sys.exit(2) + + if len(args) > 1: + usage() + sys.exit(2) + elif len(args) == 1: + project_path = args[0] + + for opt, arg in opts: + if opt in ("-h", "--help"): + usage() + sys.exit(0) + elif opt in ("-s", "--silent"): + silent = True + + try: + fix_icon_imports(project_path, silent) + except FileNotFoundError as e: + print(e) + exit(1) + + if not silent: + print("Done!") diff --git a/gdextension/setup_gdextension.sh b/gdextension/setup_gdextension.sh deleted file mode 100755 index 4329e29..0000000 --- a/gdextension/setup_gdextension.sh +++ /dev/null @@ -1,156 +0,0 @@ -#!/bin/bash - -## This script creates project structure needed for LimboAI development using GDExtension. -## Works only on Unix-likes. You can still use the directory layout below, if you are on Windows. -## -## Instructions: -## 1) Create the project root directory, name doesn't matter. -## 2) Inside the project root directory, clone the limboai repository: -## git clone https://github.com/limbonaut/limboai -## 3) From the project root directory, run: -## bash ./limboai/gdextension/setup_gdextension.sh -## -## Directory layout: -## project/ -- call this script from here, directory name doesn't matter. -## project/limboai/ -- LimboAI repository should be here after you clone it. -## project/godot-cpp/ -- will be created by this script, unless cloned manually. -## project/demo/ -- symbolic link (leads to limboai/demo). -## project/demo/addons/limboai/limboai.gdextension -- symbolid link (leads to limboai/gdextension/limboai.gdextension). -## project/demo/addons/limboai/icons/ -- symbolic link (leads to icons/). -## project/SConstruct -- symbolic link (leads to limboai/gdextension/SContruct). -## -## Note: Symbolic links will be created by this script. -## -## Dependencies: bash, git, python3. - -# Script Settings -GODOT_CPP_VERSION=4.2 -PYTHON=python - -# Colors -HIGHLIGHT_COLOR='\033[1;36m' # Light Cyan -NC='\033[0m' # No Color -ERROR_COLOR="\033[0;31m" # Red - -usage() { echo "Usage: $0 [--copy-demo] [--copy-all] [--trash-old]"; } - -msg () { echo -e "$@"; } -highlight() { echo -e "${HIGHLIGHT_COLOR}$@${NC}"; } -error () { echo -e "${ERROR_COLOR}$@${NC}" >&2; } - -if [ ! -d "${PWD}/limboai/" ]; then - error Aborting: \"limboai\" subdirectory is not found. - msg Tip: Run this script from the project root directory with limboai repository cloned into \"limboai\" subdirectory. - msg Command: bash ./limboai/gdextension/setup_gdextension.sh - exit 1 -fi - -# Interrupt execution and exit on Ctrl-C -trap exit SIGINT - -set -e - -copy_demo=0 -copy_all=0 -trash_old=0 - -# Parsing arguments -for i in "$@" -do - case "${i}" in - --copy-demo) - copy_demo=1 - shift - ;; - --copy-all) - copy_demo=1 - copy_all=1 - shift - ;; - --trash-old) - trash_old=1 - shift - ;; - *) - usage - exit 1 - ;; - esac -done - -highlight Setup started. - -${PYTHON} limboai/gdextension/update_icons.py --silent -highlight -- Icon declarations updated. - -transfer="ln -s" -transfer_word="Linked" -if [ ${copy_all} == 1 ]; then - transfer="cp -R" - transfer_word="Copied" -fi - -if [ ${trash_old} == 1 ]; then - if ! command -v trash &> /dev/null; then - error trash command not available. Aborting. - exit 1 - fi - trash SConstruct limboai/demo/addons demo || /bin/true - highlight -- Trashed old setup. -fi - -if [ ! -d "${PWD}/godot-cpp/" ]; then - highlight -- Cloning godot-cpp... - git clone -b ${GODOT_CPP_VERSION} https://github.com/godotengine/godot-cpp - highlight -- Finished cloning godot-cpp. -else - highlight -- Skipping \"godot-cpp\". Directory already exists! -fi - -if [ ! -f "${PWD}/SConstruct" ]; then - ${transfer} limboai/gdextension/SConstruct SConstruct - highlight -- ${transfer_word} SConstruct. -else - highlight -- Skipping \"SConstruct\". File already exists! -fi - -if [ ! -e "${PWD}/demo" ]; then - if [ ${copy_demo} == 1 ]; then - cp -R limboai/demo demo - highlight -- Copied demo. - else - ln -s limboai/demo demo - highlight -- Linked demo project. - fi -else - highlight -- Skipping \"demo\". File already exists! -fi - -if [ ! -e "${PWD}/demo/addons/limboai/bin/limboai.gdextension" ]; then - mkdir -p ./demo/addons/limboai/bin/ - cd ./demo/addons/limboai/bin/ - if [ -f "../../../../gdextension/limboai.gdextension" ]; then - ${transfer} ../../../../gdextension/limboai.gdextension limboai.gdextension - else - ${transfer} ../../../../limboai/gdextension/limboai.gdextension limboai.gdextension - fi - cd - - highlight -- ${transfer_word} limboai.gdextension. -else - highlight -- Skipping limboai.gdextension. File already exists! -fi - -if [ ! -e "${PWD}/demo/addons/limboai/icons/" ]; then - cd ./demo/addons/limboai/ - if [ -d "../../../icons" ]; then - ${transfer} ../../../icons icons - else - ${transfer} ../../../limboai/icons icons - fi - cd - - highlight -- ${transfer_word} icons. -else - highlight -- Skipping icons. File already exists! -fi - -highlight Setup complete. diff --git a/gdextension/update_icons.py b/gdextension/update_icon_entries.py similarity index 67% rename from gdextension/update_icons.py rename to gdextension/update_icon_entries.py index a8dcf59..850b048 100755 --- a/gdextension/update_icons.py +++ b/gdextension/update_icon_entries.py @@ -1,7 +1,7 @@ #!/usr/bin/python """ -Usage: update_icons.py [--silent] -Update icon declarations in limboai.gdextension file. +Usage: update_icon_entries.py [--silent] +Update icon entries in limboai.gdextension file. Options: -s, --silent Don't print anything. @@ -28,7 +28,44 @@ def get_script_dir(): return os.path.dirname(os.path.realpath(__file__)) -def main(): +def update_icon_entries(silent=False): + config_dir = get_script_dir() + config_path = os.path.join(config_dir, "limboai.gdextension") + content = "" + new_content = "" + + f = open(config_path, "r") + for line in f: + content += line + f.close() + + index = content.find("[icons]") + new_content = content[0:index] + new_content += "[icons]\n\n" + + icon_files = [] + icons_dir = os.path.join(config_dir, "../icons/") + for icon_file in glob.glob(icons_dir + "/*.svg"): + icon_file = os.path.basename(icon_file) + icon_files.append(icon_file) + + icon_files.sort() + for icon_file in icon_files: + new_content += os.path.splitext(icon_file)[0] + ' = "res://addons/limboai/icons/' + icon_file + '"\n' + + if new_content != content: + f = open(config_path, "w") + f.write(new_content) + f.close() + if not silent: + print(new_content) + print("=== Icon entries updated ===") + else: + if not silent: + print("=== No update needed for icon entries ===") + + +if __name__ == "__main__": silent = False try: opts, args = getopt.getopt(sys.argv[1:], "s", ["silent"]) @@ -50,37 +87,4 @@ def main(): elif opt in ("-s", "--silent"): silent = True - config_dir = get_script_dir() - config_path = os.path.join(config_dir, "limboai.gdextension") - content = "" - - f = open(config_path, "r") - for line in f: - if line.startswith("[icons]"): - break - content += line - f.close() - - content += "[icons]\n\n" - - icon_files = [] - icons_dir = os.path.join(config_dir, "../icons/") - for icon_file in glob.glob(icons_dir + "/*.svg"): - icon_file = os.path.basename(icon_file) - icon_files.append(icon_file) - - icon_files.sort() - for icon_file in icon_files: - content += os.path.splitext(icon_file)[0] + ' = "res://addons/limboai/icons/' + icon_file + '"\n' - - f = open(config_path, "w") - f.write(content) - f.close() - - if not silent: - print(content) - print("======= Icon declarations updated =======") - - -if __name__ == "__main__": - main() + update_icon_entries(silent) diff --git a/limboai_version.py b/limboai_version.py index 09a89e8..eb62f16 100644 --- a/limboai_version.py +++ b/limboai_version.py @@ -5,6 +5,7 @@ minor = 2 patch = 0 status = "dev" doc_branch = "latest" +godot_cpp_ref = "master" # Code that generates version header