diff --git a/.github/PULL_REQUEST_TEMPLATES/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATES/pull_request_template.md index ed3dfa558f..537cbfb04a 100644 --- a/.github/PULL_REQUEST_TEMPLATES/pull_request_template.md +++ b/.github/PULL_REQUEST_TEMPLATES/pull_request_template.md @@ -5,7 +5,7 @@ - [ ] I have read the [contribution guide](doc/contributing.md) - [ ] I have added my info to [copying.md](copying.md) (only first time contributors) -- [ ] I have run `make checkall` and fixed all mentioned problems +- [ ] I have run `make checkmerge` and fixed all mentioned problems ### Description diff --git a/.github/workflows/ubuntu-22.04.yml b/.github/workflows/ubuntu-24.04.yml similarity index 68% rename from .github/workflows/ubuntu-22.04.yml rename to .github/workflows/ubuntu-24.04.yml index 311524f799..2fd07b7030 100644 --- a/.github/workflows/ubuntu-22.04.yml +++ b/.github/workflows/ubuntu-24.04.yml @@ -1,14 +1,14 @@ -name: Ubuntu 22.04 CI +name: Ubuntu 24.04 CI on: [push, workflow_dispatch] jobs: build-devenv: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - name: Build the Docker image - run: sudo DOCKER_BUILDKIT=1 docker build ./packaging/docker/devenv --file ./packaging/docker/devenv/Dockerfile.ubuntu.2204 --tag openage-devenv:latest + run: sudo DOCKER_BUILDKIT=1 docker build ./packaging/docker/devenv --file ./packaging/docker/devenv/Dockerfile.ubuntu.2404 --tag openage-devenv:latest shell: bash - name: Save the Docker image run: | @@ -24,7 +24,7 @@ jobs: retention-days: 30 build: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 needs: build-devenv steps: - uses: actions/checkout@v4 @@ -41,7 +41,7 @@ jobs: - name: Build openage run: | sudo docker run --rm -v "$(pwd)":/mnt/openage -w /mnt/openage openage-devenv:latest \ - bash -c 'mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=$(which gcc-11) -DCMAKE_CXX_COMPILER=$(which g++-11) -DCMAKE_CXX_FLAGS='' -DCMAKE_EXE_LINKER_FLAGS='' -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_MODULE_LINKER_FLAGS='' -DCMAKE_SHARED_LINKER_FLAGS='' -DDOWNLOAD_NYAN=YES -DCXX_OPTIMIZATION_LEVEL=auto -DCXX_SANITIZE_FATAL=False -DCXX_SANITIZE_MODE=none -DWANT_BACKTRACE=if_available -DWANT_GPERFTOOLS_PROFILER=if_available -DWANT_GPERFTOOLS_TCMALLOC=False -DWANT_INOTIFY=if_available -DWANT_NCURSES=if_available -DWANT_OPENGL=if_available -DWANT_VULKAN=if_available -G Ninja .. && cmake --build . --parallel $(nproc) -- -k1' + bash -c 'mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=$(which gcc) -DCMAKE_CXX_COMPILER=$(which g++) -DCMAKE_CXX_FLAGS='' -DCMAKE_EXE_LINKER_FLAGS='' -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_MODULE_LINKER_FLAGS='' -DCMAKE_SHARED_LINKER_FLAGS='' -DDOWNLOAD_NYAN=YES -DCXX_OPTIMIZATION_LEVEL=auto -DCXX_SANITIZE_FATAL=False -DCXX_SANITIZE_MODE=none -DWANT_BACKTRACE=if_available -DWANT_GPERFTOOLS_PROFILER=if_available -DWANT_GPERFTOOLS_TCMALLOC=False -DWANT_INOTIFY=if_available -DWANT_NCURSES=if_available -DWANT_OPENGL=if_available -DWANT_VULKAN=if_available -G Ninja .. && cmake --build . --parallel $(nproc) -- -k1' - name: Compress build artifacts run: | mkdir -p /tmp/openage diff --git a/.github/workflows/windows-server-2019.yml b/.github/workflows/windows-server-2019.yml index df9d96284b..06f9fa0df4 100644 --- a/.github/workflows/windows-server-2019.yml +++ b/.github/workflows/windows-server-2019.yml @@ -50,8 +50,8 @@ jobs: $FLEX_PATH = (Get-ChildItem ./download -Recurse -Force -Filter 'win_flex.exe')[0].FullName mkdir build cd build - cmake -DCMAKE_TOOLCHAIN_FILE="$TOOLCHAIN_FILE" -DCMAKE_BUILD_TYPE=Release -DCMAKE_TRY_COMPILE_CONFIGURATION=Release -DCMAKE_CXX_FLAGS='/Zc:__cplusplus /permissive- /EHsc' -DCMAKE_EXE_LINKER_FLAGS='' -DCMAKE_MODULE_LINKER_FLAGS='' -DCMAKE_SHARED_LINKER_FLAGS='' -DDOWNLOAD_NYAN=YES -DCXX_OPTIMIZATION_LEVEL=auto -DCXX_SANITIZE_FATAL=False -DCXX_SANITIZE_MODE=none -DWANT_BACKTRACE=if_available -DWANT_GPERFTOOLS_PROFILER=if_available -DWANT_GPERFTOOLS_TCMALLOC=False -DWANT_INOTIFY=if_available -DWANT_NCURSES=if_available -DWANT_OPENGL=if_available -DWANT_VULKAN=if_available -DFLEX_EXECUTABLE="$FLEX_PATH" -G "Visual Studio 16 2019" -A x64 ../source - cmake --build . --config RelWithDebInfo -- -nologo -maxCpuCount + cmake -DCMAKE_TOOLCHAIN_FILE="$TOOLCHAIN_FILE" -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TRY_COMPILE_CONFIGURATION=Debug -DCMAKE_CXX_FLAGS='/Zc:__cplusplus /permissive- /EHsc' -DCMAKE_EXE_LINKER_FLAGS='' -DCMAKE_MODULE_LINKER_FLAGS='' -DCMAKE_SHARED_LINKER_FLAGS='' -DDOWNLOAD_NYAN=YES -DCXX_OPTIMIZATION_LEVEL=auto -DCXX_SANITIZE_FATAL=False -DCXX_SANITIZE_MODE=none -DWANT_BACKTRACE=if_available -DWANT_GPERFTOOLS_PROFILER=if_available -DWANT_GPERFTOOLS_TCMALLOC=False -DWANT_INOTIFY=if_available -DWANT_NCURSES=if_available -DWANT_OPENGL=if_available -DWANT_VULKAN=if_available -DFLEX_EXECUTABLE="$FLEX_PATH" -G "Visual Studio 16 2019" -A x64 ../source + cmake --build . --config Debug -- -nologo -maxCpuCount shell: pwsh - name: Package run: | @@ -76,6 +76,7 @@ jobs: $DLL_PATH = Join-Path package dll | Resolve-Path cd package python -m openage --add-dll-search-path $DLL_PATH --version + python -m openage --add-dll-search-path $DLL_PATH test -a shell: pwsh - name: Publish build artifacts uses: actions/upload-artifact@v4 diff --git a/.github/workflows/windows-server-2022.yml b/.github/workflows/windows-server-2022.yml index bbc73b78c2..0ee700e8f3 100644 --- a/.github/workflows/windows-server-2022.yml +++ b/.github/workflows/windows-server-2022.yml @@ -50,8 +50,8 @@ jobs: $FLEX_PATH = (Get-ChildItem ./download -Recurse -Force -Filter 'win_flex.exe')[0].FullName mkdir build cd build - cmake -DCMAKE_TOOLCHAIN_FILE="$TOOLCHAIN_FILE" -DCMAKE_BUILD_TYPE=Release -DCMAKE_TRY_COMPILE_CONFIGURATION=Release -DCMAKE_CXX_FLAGS='/Zc:__cplusplus /permissive- /EHsc' -DCMAKE_EXE_LINKER_FLAGS='' -DCMAKE_MODULE_LINKER_FLAGS='' -DCMAKE_SHARED_LINKER_FLAGS='' -DDOWNLOAD_NYAN=YES -DCXX_OPTIMIZATION_LEVEL=auto -DCXX_SANITIZE_FATAL=False -DCXX_SANITIZE_MODE=none -DWANT_BACKTRACE=if_available -DWANT_GPERFTOOLS_PROFILER=if_available -DWANT_GPERFTOOLS_TCMALLOC=False -DWANT_INOTIFY=if_available -DWANT_NCURSES=if_available -DWANT_OPENGL=if_available -DWANT_VULKAN=if_available -DFLEX_EXECUTABLE="$FLEX_PATH" -G "Visual Studio 17 2022" -A x64 ../source - cmake --build . --config RelWithDebInfo -- -nologo -maxCpuCount + cmake -DCMAKE_TOOLCHAIN_FILE="$TOOLCHAIN_FILE" -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TRY_COMPILE_CONFIGURATION=Debug -DCMAKE_CXX_FLAGS='/Zc:__cplusplus /permissive- /EHsc' -DCMAKE_EXE_LINKER_FLAGS='' -DCMAKE_MODULE_LINKER_FLAGS='' -DCMAKE_SHARED_LINKER_FLAGS='' -DDOWNLOAD_NYAN=YES -DCXX_OPTIMIZATION_LEVEL=auto -DCXX_SANITIZE_FATAL=False -DCXX_SANITIZE_MODE=none -DWANT_BACKTRACE=if_available -DWANT_GPERFTOOLS_PROFILER=if_available -DWANT_GPERFTOOLS_TCMALLOC=False -DWANT_INOTIFY=if_available -DWANT_NCURSES=if_available -DWANT_OPENGL=if_available -DWANT_VULKAN=if_available -DFLEX_EXECUTABLE="$FLEX_PATH" -G "Visual Studio 17 2022" -A x64 ../source + cmake --build . --config Debug -- -nologo -maxCpuCount shell: pwsh - name: Package run: | @@ -76,6 +76,7 @@ jobs: $DLL_PATH = Join-Path package dll | Resolve-Path cd package python -m openage --add-dll-search-path $DLL_PATH --version + python -m openage --add-dll-search-path $DLL_PATH test -a shell: pwsh - name: Publish build artifacts uses: actions/upload-artifact@v4 diff --git a/.gitignore b/.gitignore index 1910b8d6ac..06263991e6 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,9 @@ __pycache__ /.ccls-cache /.cache +# Nix build output +/result + # root dir run script /run /run.cpp diff --git a/.mailmap b/.mailmap index fbf10ad63f..5acbd1b581 100644 --- a/.mailmap +++ b/.mailmap @@ -20,4 +20,6 @@ Tobias Feldballe Tobias Feldballe Jonas Borchelt Derek Frogget <114030121+derekfrogget@users.noreply.github.com> -Nikhil Ghosh \ No newline at end of file +Nikhil Ghosh +David Wever <56411717+dmwever@users.noreply.github.com> +Ngô Xuân Minh diff --git a/CMakeLists.txt b/CMakeLists.txt index 47484acb06..7a6a785bf4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,7 +39,9 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) # Python and Cython requirements set(PYTHON_MIN_VERSION 3.9) -set(CYTHON_MIN_VERSION 0.29.31) +set(CYTHON_MIN_VERSION 3.0.10) +set(CYTHON_MIN_VERSION_FALLBACK 0.29.31) +set(CYTHON_MAX_VERSION_FALLBACK 3.0.7) # CMake policies foreach(pol diff --git a/Makefile b/Makefile index ef481521b2..ad8ad7adf0 100644 --- a/Makefile +++ b/Makefile @@ -45,7 +45,11 @@ libopenage: $(BUILDDIR) .PHONY: codegen codegen: $(BUILDDIR) - $(MAKE) $(MAKEARGS) -C $(BUILDDIR) codegen + $(MAKE) $(MAKEARGS) -C $(BUILDDIR) cppgen + +.PHONY: cppgen +cppgen: $(BUILDDIR) + $(MAKE) $(MAKEARGS) -C $(BUILDDIR) cppgen .PHONY: pxdgen pxdgen: $(BUILDDIR) @@ -128,22 +132,26 @@ mrproperer: mrproper checkfast: python3 -m buildsystem.codecompliance --fast -.PHONY: checkall -checkall: - python3 -m buildsystem.codecompliance --all +.PHONY: checkmerge +checkmerge: + python3 -m buildsystem.codecompliance --merge .PHONY: checkchanged checkchanged: - python3 -m buildsystem.codecompliance --all --only-changed-files=origin/master + python3 -m buildsystem.codecompliance --merge --only-changed-files=origin/master .PHONY: checkuncommited checkuncommited: - python3 -m buildsystem.codecompliance --all --only-changed-files=HEAD + python3 -m buildsystem.codecompliance --merge --only-changed-files=HEAD .PHONY: checkpy checkpy: python3 -m buildsystem.codecompliance --pystyle --pylint +.PHONY: checkall +checkall: + python3 -m buildsystem.codecompliance --all + .PHONY: help help: $(BUILDDIR)/Makefile @echo "openage Makefile" @@ -175,6 +183,7 @@ help: $(BUILDDIR)/Makefile @echo "tests -> run the tests (py + cpp)" @echo "" @echo "checkall -> full code compliance check" + @echo "checkmerge -> code compliance check for merging to master" @echo "checkfast -> fast checks only" @echo "checkchanged -> full check for all files changed since origin/master" @echo "checkuncommited -> full check for all currently uncommited files" diff --git a/README.md b/README.md index 266cdfc5fa..8666083783 100644 --- a/README.md +++ b/README.md @@ -87,8 +87,8 @@ If you're interested, we wrote detailed explanations on our blog: [Part 1](https | Operating System | Build status | | :-----------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| Debian Sid | [Todo: Kevin #11] | -| Ubuntu 22.04 LTS | [![Ubuntu 22.04 build status](https://github.com/SFTTech/openage/actions/workflows/ubuntu-22.04.yml/badge.svg?branch=master)](https://github.com/SFTtech/openage/actions/workflows/ubuntu-22.04.yml) | +| Debian Sid | [![Kevin CI status](https://cidata.sft.lol/openage/branches/master/status.svg)](/kevinfile) | +| Ubuntu 24.04 LTS | [![Ubuntu 24.04 build status](https://github.com/SFTTech/openage/actions/workflows/ubuntu-24.04.yml/badge.svg?branch=master)](https://github.com/SFTtech/openage/actions/workflows/ubuntu-24.04.yml) | | macOS | [![macOS build status](https://github.com/SFTtech/openage/workflows/macOS-CI/badge.svg)](https://github.com/SFTtech/openage/actions?query=workflow%3AmacOS-CI) | | Windows Server 2019 | [![Windows Server 2019 build status](https://github.com/SFTtech/openage/actions/workflows/windows-server-2019.yml/badge.svg?branch=master)](https://github.com/SFTtech/openage/actions/workflows/windows-server-2019.yml) | | Windows Server 2022 | [![Windows Server 2022 build status](https://github.com/SFTtech/openage/actions/workflows/windows-server-2022.yml/badge.svg?branch=master)](https://github.com/SFTtech/openage/actions/workflows/windows-server-2022.yml) | @@ -127,6 +127,9 @@ Quickstart make ``` +**Alternative approach:** +You can build and run the project using Docker. See [Running with docker](/doc/build_instructions/docker.md) for more details. + * **I compiled everything. Now how do I run it?** * Execute `cd bin && ./run main`. * [The convert script](/doc/media_convert.md) will transform original assets into openage formats, which are a lot saner and more moddable. @@ -145,7 +148,6 @@ To turn them off, use `./bin/run --dont-segfault --no-errors --dont-eat-dog`. If this still does not help, try our [troubleshooting guide](/doc/troubleshooting.md), the [contact section](#contact) or the [bug tracker](https://github.com/SFTtech/openage/issues). - Contributing ============ diff --git a/assets/test/shaders/demo_7_shader_command.frag.glsl b/assets/test/shaders/demo_7_shader_command.frag.glsl new file mode 100644 index 0000000000..76e50ffd78 --- /dev/null +++ b/assets/test/shaders/demo_7_shader_command.frag.glsl @@ -0,0 +1,12 @@ +#version 330 + +in vec2 tex_coord; +out vec4 frag_color; + +uniform float time; + +void main() { + // PLACEHOLDER: color + // PLACEHOLDER: alpha + frag_color = vec4(r, g, b, alpha); +} diff --git a/assets/test/shaders/demo_7_shader_command.vert.glsl b/assets/test/shaders/demo_7_shader_command.vert.glsl new file mode 100644 index 0000000000..caefe64535 --- /dev/null +++ b/assets/test/shaders/demo_7_shader_command.vert.glsl @@ -0,0 +1,9 @@ +#version 330 + +in vec2 position; +out vec2 tex_coord; + +void main() { + tex_coord = position.xy * 0.5 + 0.5; + gl_Position = vec4(position.xy, 0.0, 1.0); +} diff --git a/assets/test/shaders/demo_7_snippets/alpha.snippet b/assets/test/shaders/demo_7_snippets/alpha.snippet new file mode 100644 index 0000000000..603c275311 --- /dev/null +++ b/assets/test/shaders/demo_7_snippets/alpha.snippet @@ -0,0 +1 @@ +float alpha = (sin(time) + 1.0) * 0.5; diff --git a/assets/test/shaders/demo_7_snippets/color.snippet b/assets/test/shaders/demo_7_snippets/color.snippet new file mode 100644 index 0000000000..4c0204e773 --- /dev/null +++ b/assets/test/shaders/demo_7_snippets/color.snippet @@ -0,0 +1,3 @@ +float r = 0.5 + 0.5 * sin(time); +float g = 0.5 + 0.5 * sin(time + 2.0); +float b = 0.5 + 0.5 * sin(time + 4.0); diff --git a/buildsystem/HandlePythonOptions.cmake b/buildsystem/HandlePythonOptions.cmake index ef7da71f9b..4c5988efad 100644 --- a/buildsystem/HandlePythonOptions.cmake +++ b/buildsystem/HandlePythonOptions.cmake @@ -1,10 +1,20 @@ -# Copyright 2015-2023 the openage authors. See copying.md for legal info. +# Copyright 2015-2025 the openage authors. See copying.md for legal info. # finds the python interpreter, install destination and extension flags. # the Python version number requirement is in modules/FindPython_test.cpp find_package(Python ${PYTHON_MIN_VERSION} REQUIRED) -find_package(Cython ${CYTHON_MIN_VERSION} REQUIRED) + +find_package(Cython ${CYTHON_MIN_VERSION}) +if(NOT CYTHON_FOUND) + message("Checking for alternative Cython fallback version (>=${CYTHON_MIN_VERSION_FALLBACK} AND <=${CYTHON_MAX_VERSION_FALLBACK})") + find_package(Cython ${CYTHON_MIN_VERSION_FALLBACK} QUIET) + if(CYTHON_VERSION VERSION_LESS ${CYTHON_MIN_VERSION} AND CYTHON_VERSION VERSION_GREATER ${CYTHON_MAX_VERSION_FALLBACK}) + message(FATAL_ERROR "Cython version ${CYTHON_VERSION} is not compatible") + else() + message("Compatible Cython version ${CYTHON_VERSION} found") + endif() +endif() py_get_config_var(EXT_SUFFIX PYEXT_SUFFIX) if(MINGW) @@ -39,6 +49,19 @@ if(PYTHON_VER VERSION_GREATER_EQUAL 3.8 AND PYTHON_VERSION VERSION_LESS 3.9) endif() set(PYEXT_LIBRARY "${PYTHON_LIBRARIES}") +message("PYTHON_LIBRARIES: " "${PYTHON_LIBRARIES}") +#Windows always uses optimized version of Python lib +if(WIN32 AND "${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + #get index of string "optimized" and increment it by 1 so index points at the path of the optimized lib + list(FIND PYEXT_LIBRARY "optimized" _index) + if(${_index} GREATER -1) + MATH(EXPR _index "${_index}+1") + list(GET PYEXT_LIBRARY ${_index} PYEXT_LIBRARY) + endif() + message("force linking to python release lib, instead of debug lib when cythonising") + set(force_optimized_lib_flag "--force_optimized_lib") +endif() + set(PYEXT_INCLUDE_DIRS "${PYTHON_INCLUDE_DIRS};${NUMPY_INCLUDE_DIR}") if(NOT CMAKE_PY_INSTALL_PREFIX) diff --git a/buildsystem/codecompliance/__main__.py b/buildsystem/codecompliance/__main__.py index 2a4f8b4084..4d3237e36a 100644 --- a/buildsystem/codecompliance/__main__.py +++ b/buildsystem/codecompliance/__main__.py @@ -1,4 +1,4 @@ -# Copyright 2014-2024 the openage authors. See copying.md for legal info. +# Copyright 2014-2025 the openage authors. See copying.md for legal info. """ Entry point for the code compliance checker. @@ -18,16 +18,24 @@ def parse_args(): """ Returns the raw argument namespace. """ cli = argparse.ArgumentParser() - cli.add_argument("--fast", action="store_true", - help="do all checks that can be performed quickly") - cli.add_argument("--all", action="store_true", - help="do all checks, even the really slow ones") + check_types = cli.add_mutually_exclusive_group() + check_types.add_argument("--fast", action="store_true", + help="do all checks that can be performed quickly") + check_types.add_argument("--merge", action="store_true", + help="do all checks that are required before merges to master") + check_types.add_argument("--all", action="store_true", + help="do all checks, even the really slow ones") + cli.add_argument("--only-changed-files", metavar='GITREF', help=("slow checks are only done on files that have " "changed since GITREF.")) cli.add_argument("--authors", action="store_true", help=("check whether all git authors are in copying.md. " "repo must be a git repository.")) + cli.add_argument("--clang-tidy", action="store_true", + help=("Check the C++ code with clang-tidy. Make sure you have build the " + "project with ./configure --clang-tidy or have set " + "CMAKE_CXX_CLANG_TIDY for your CMake build.")) cli.add_argument("--cppstyle", action="store_true", help="check the cpp code style") cli.add_argument("--cython", action="store_true", @@ -76,7 +84,7 @@ def process_args(args, error): # set up log level log_setup(args.verbose - args.quiet) - if args.fast or args.all: + if args.fast or args.merge or args.all: # enable "fast" tests args.authors = True args.cppstyle = True @@ -86,16 +94,19 @@ def process_args(args, error): args.filemodes = True args.textfiles = True - if args.all: - # enable tests that take a bit longer - + if args.merge or args.all: + # enable tests that are required before merging to master args.pystyle = True args.pylint = True args.test_git_change_years = True + if args.all: + # enable tests that take a bit longer + args.clang_tidy = True + if not any((args.headerguards, args.legal, args.authors, args.pystyle, args.cppstyle, args.cython, args.test_git_change_years, - args.pylint, args.filemodes, args.textfiles)): + args.pylint, args.filemodes, args.textfiles, args.clang_tidy)): error("no checks were specified") has_git = bool(shutil.which('git')) @@ -128,6 +139,10 @@ def process_args(args, error): if not importlib.util.find_spec('pylint'): error("pylint python module required for linting") + if args.clang_tidy: + if not shutil.which('clang-tidy'): + error("--clang-tidy requires clang-tidy to be installed") + def get_changed_files(gitref): """ @@ -264,6 +279,9 @@ def find_all_issues(args, check_files=None): from .modes import find_issues yield from find_issues(check_files, ('openage', 'buildsystem', 'libopenage', 'etc/gdb_pretty')) + if args.clang_tidy: + from .clangtidy import find_issues + yield from find_issues(check_files, ('libopenage', )) if __name__ == '__main__': diff --git a/buildsystem/codecompliance/clangtidy.py b/buildsystem/codecompliance/clangtidy.py new file mode 100644 index 0000000000..d4e1b5586a --- /dev/null +++ b/buildsystem/codecompliance/clangtidy.py @@ -0,0 +1,69 @@ +# Copyright 2024-2024 the openage authors. See copying.md for legal info. + +""" +Checks clang-tidy errors on cpp files +""" + +import subprocess +from .cppstyle import filter_file_list +from .util import findfiles + + +def find_issues(check_files, dirnames): + """ + Invoke clang-tidy to check C++ files for issues. + Yields issues found by clang-tidy in real-time. + """ + # Specify the checks to include + # 4 checks we focus on + checks_to_include = [ + 'clang-analyzer-*', + 'bugprone-*', + 'concurrency-*', + 'performance-*' + ] + # Create the checks string + checks = ', '.join(checks_to_include) + + # Invocation command + invocation = ['clang-tidy', f'-checks=-*,{checks}'] + + # Use utility functions from util.py and cppstyle.py + if check_files is not None: + filenames = list(filter_file_list(check_files, dirnames)) + else: + filenames = list(filter_file_list(findfiles(dirnames), dirnames)) + + if not filenames: + print("No files to check.") + return # No files to check + + for filename in filenames: + # Run clang-tidy for each file + print(f"Starting clang-tidy check on file: {filename}") + try: + with subprocess.Popen( + invocation + [filename], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) as process: + # Stream output in real-time + while True: + output = process.stdout.readline() + if output: + yield ("clang-tidy output", output.strip(), None) + elif process.poll() is not None: + break + + # Capture remaining errors (if any) + for error_line in process.stderr: + yield ("clang-tidy error", error_line.strip(), None) + + # Handle exception + except subprocess.SubprocessError as exc: + yield ( + "clang-tidy error", + f"An error occurred while running clang-tidy on {filename}: {str(exc)}", + None + ) diff --git a/buildsystem/codegen.cmake b/buildsystem/codegen.cmake index 068078bf6d..685dbf0463 100644 --- a/buildsystem/codegen.cmake +++ b/buildsystem/codegen.cmake @@ -1,4 +1,4 @@ -# Copyright 2014-2019 the openage authors. See copying.md for legal info. +# Copyright 2014-2025 the openage authors. See copying.md for legal info. # set CODEGEN_SCU_FILE to the absolute path to SCU file macro(get_codegen_scu_file) @@ -52,7 +52,7 @@ function(codegen_run) COMMENT "openage.codegen: generating c++ code" ) - add_custom_target(codegen + add_custom_target(cppgen DEPENDS "${CODEGEN_TIMEFILE}" ) diff --git a/buildsystem/cythonize.py b/buildsystem/cythonize.py index 9e8ba8e234..e7ac86ae6f 100755 --- a/buildsystem/cythonize.py +++ b/buildsystem/cythonize.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright 2015-2021 the openage authors. See copying.md for legal info. +# Copyright 2015-2025 the openage authors. See copying.md for legal info. """ Runs Cython on all modules that were listed via add_cython_module. @@ -73,7 +73,7 @@ def remove_if_exists(filename): filename.unlink() -def cythonize_wrapper(modules, **kwargs): +def cythonize_wrapper(modules, force_optimized_lib = False, **kwargs): """ Calls cythonize, filtering useless warnings """ bin_dir, bin_modules = kwargs['build_dir'], [] src_dir, src_modules = Path.cwd(), [] @@ -88,13 +88,59 @@ def cythonize_wrapper(modules, **kwargs): with redirect_stdout(cython_filter): if src_modules: cythonize(src_modules, **kwargs) + if sys.platform == 'win32' and force_optimized_lib: + win_use_optimized_lib_python(src_modules, bin_dir) if bin_modules: os.chdir(bin_dir) cythonize(bin_modules, **kwargs) + if sys.platform == 'win32' and force_optimized_lib: + win_use_optimized_lib_python(bin_modules, bin_dir) os.chdir(src_dir) +def win_use_optimized_lib_python(modules, path): + """ + Add an #ifdef statement in cythonized .cpp files to temporarily undefine _DEBUG before + #include "Python.h" + + This function modifies the generated C++ files from Cython to prevent linking to + the debug version of the Python library on Windows. The debug version of the + Python library cannot import Python libraries that contain extension modules. + see: https://github.com/python/cpython/issues/127619 (To unserstand the problem) + see: https://stackoverflow.com/a/59566420 (To understand the soloution) + """ + + for module in modules: + module = str(module) + if path: + module = path + "\\" + module + module = module.removesuffix(".py").removesuffix(".pyx") + module = module + ".cpp" + with open(module, "r", encoding='utf8') as file: + text = file.read() + if not text.count("OPENAGE: UNDEF_DEBUG_INSERTED"): + text = text.replace( + '#include "Python.h"', + ( + "\n\n// OPENAGE: UNDEF_DEBUG_INSERTED\n" + "// Avoid linking to the debug version of the Python library on Windows\n\n" + "#ifdef _DEBUG\n" + "#define _DEBUG_WAS_DEFINED\n" + "#undef _DEBUG\n#endif\n" + "#include \"Python.h\"\n" + "#ifdef _DEBUG_WAS_DEFINED\n" + "#define _DEBUG\n" + "#undef _DEBUG_WAS_DEFINED\n" + "#endif\n\n" + "// OPENAGE: UNDEF_DEBUG_INSERTED\n\n" + ), + 1 + ) + with open(module, "w", encoding='utf8') as file: + file.write(text) + + def main(): """ CLI entry point """ cli = argparse.ArgumentParser() @@ -125,6 +171,8 @@ def main(): )) cli.add_argument("--threads", type=int, default=cpu_count(), help="number of compilation threads to use") + cli.add_argument("--force_optimized_lib", action="store_true", + help= "edit compiled .cpp files to link to optimized version of python libary") args = cli.parse_args() # cython emits warnings on using absolute paths to modules @@ -159,12 +207,12 @@ def main(): # writing funny lines at the head of each file. cythonize_args['language'] = 'c++' - cythonize_wrapper(modules, **cythonize_args) + cythonize_wrapper(modules, args.force_optimized_lib, **cythonize_args) # build standalone executables that embed the py interpreter Options.embed = "main" - cythonize_wrapper(embedded_modules, **cythonize_args) + cythonize_wrapper(embedded_modules, args.force_optimized_lib, **cythonize_args) # verify depends from Cython.Build.Dependencies import _dep_tree diff --git a/buildsystem/modules/FindPython.cmake b/buildsystem/modules/FindPython.cmake index c2e03011d0..61d5494191 100644 --- a/buildsystem/modules/FindPython.cmake +++ b/buildsystem/modules/FindPython.cmake @@ -1,4 +1,4 @@ -# Copyright 2015-2023 the openage authors. See copying.md for legal info. +# Copyright 2015-2025 the openage authors. See copying.md for legal info. # Find Python # ~~~~~~~~~~~ @@ -78,6 +78,12 @@ set(PYTHON_MIN_VERSION_HEX "${BIT_SHIFT_HEX}") # there's a static_assert that tests the Python version. # that way, we verify the interpreter and the library version. # (the interpreter provided us the library location) + +if(WIN32 AND "${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + set(TEMP_CMAKE_TRY_COMPILE_CONFIGURATION ${CMAKE_TRY_COMPILE_CONFIGURATION}) + set(CMAKE_TRY_COMPILE_CONFIGURATION "Release") +endif() + try_compile(PYTHON_TEST_RESULT "${CMAKE_BINARY_DIR}" SOURCES "${CMAKE_CURRENT_LIST_DIR}/FindPython_test.cpp" @@ -87,6 +93,11 @@ try_compile(PYTHON_TEST_RESULT OUTPUT_VARIABLE PYTHON_TEST_OUTPUT ) +if(WIN32 AND "${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + set(CMAKE_TRY_COMPILE_CONFIGURATION ${TEMP_CMAKE_TRY_COMPILE_CONFIGURATION}) +endif() + + if(NOT PYTHON_TEST_RESULT) message(STATUS "!! No suitable Python interpreter was found !! diff --git a/buildsystem/python.cmake b/buildsystem/python.cmake index f5c4d84de5..90117baadc 100644 --- a/buildsystem/python.cmake +++ b/buildsystem/python.cmake @@ -1,4 +1,4 @@ -# Copyright 2014-2020 the openage authors. See copying.md for legal info. +# Copyright 2014-2025 the openage authors. See copying.md for legal info. # provides macros for defining python extension modules and pxdgen sources. # and a 'finalize' function that must be called in the end. @@ -393,6 +393,7 @@ function(python_finalize) "${CMAKE_BINARY_DIR}/py/cython_modules" "${CMAKE_BINARY_DIR}/py/cython_modules_embed" "${CMAKE_BINARY_DIR}/py/pxd_list" + ${force_optimized_lib_flag} "--build-dir" "${CMAKE_BINARY_DIR}" COMMAND "${CMAKE_COMMAND}" -E touch "${CYTHONIZE_TIMEFILE}" DEPENDS diff --git a/copying.md b/copying.md index 61153fb9cf..8c61b294e2 100644 --- a/copying.md +++ b/copying.md @@ -157,6 +157,16 @@ _the openage authors_ are: | Edvin Lindholm | EdvinLndh | edvinlndh à gmail dawt com | | Jeremiah Morgan | jere8184 | jeremiahmorgan dawt bham à outlook dawt com | | Tobias Alam | alamt22 | tobiasal à umich dawt edu | +| Alex Zhuohao He | ZzzhHe | zhuohao dawt he à outlook dawt com | +| David Wever | dmwever | dmwever à crimson dawt ua dawt edu | +| Michael Lynch | mtlynch | git à mtlynch dawt io | +| Ngô Xuân Minh | | xminh dawt ngo dawt 00 à gmail dawt com | +| Haytham Tang | haytham918 | yunxuant à umich dawt edu | +| Ana Trias-Labellarte | anatriaslabella | ana dawt triaslabella à ufl dawt edu | +| Eelco Empting | Eeelco | me à eelco dawt de | +| Jordan Sutton | jsutCodes | jsutcodes à gmail dawt com | +| Daniel Wieczorek | Danio | danielwieczorek96 à gmail dawt com | +| | bytegrrrl | bytegrrrl à proton dawt me | If you're a first-time committer, add yourself to the above list. This is not just for legal reasons, but also to keep an overview of all those nicknames. diff --git a/doc/build_instructions/docker.md b/doc/build_instructions/docker.md new file mode 100644 index 0000000000..6a431f0c63 --- /dev/null +++ b/doc/build_instructions/docker.md @@ -0,0 +1,70 @@ +### Running with Docker + +Docker simplifies the setup process by providing a consistent development environment. It allows you to build and run the project without manually installing dependencies. Currently provided [Dockerfile.ubuntu.2404](../../packaging/docker/devenv/Dockerfile.ubuntu.2404) supports Ubuntu 24.04 as the base operating system. + +#### Prerequisites + +- **Docker**: Ensure Docker is installed on your machine. Follow the [official documentation](https://docs.docker.com/) for installation instructions. +- **Display Server**: This guide supports both **X11** and **Wayland** display servers for GUI applications. +- **Tested Configuration**: These instructions were tested on Ubuntu 24.04 running on WSL2 on Windows 11. + +#### Steps to Build and Run + +1. **Enable GUI Support** + + Depending on your display server, follow the appropriate steps: + + - **For X11**: + Allow the Docker container to access your X server: + ```bash + xhost +local:root + ``` + + - **For Wayland**: + Allow the Docker container to access your Wayland socket: + ```bash + sudo chmod a+rw /run/user/$(id -u)/wayland-0 + ``` + +2. **Build the Docker Image** + + Build the Docker image using the provided Dockerfile: + ```bash + sudo docker build -t openage -f packaging/docker/devenv/Dockerfile.ubuntu.2404 . + ``` + +3. **Run the Docker Container** + + Start the Docker container with the appropriate configuration for your display server: + + - **For X11**: + ```bash + docker run -it \ + -e DISPLAY=$DISPLAY \ + -v /tmp/.X11-unix:/tmp/.X11-unix \ + -v $HOME/.Xauthority:/root/.Xauthority \ + --network host openage + ``` + + - **For Wayland**: + ```bash + docker run -it \ + -e XDG_RUNTIME_DIR=/tmp \ + -e QT_QPA_PLATFORM=wayland \ + -e WAYLAND_DISPLAY=$WAYLAND_DISPLAY \ + -v $XDG_RUNTIME_DIR/$WAYLAND_DISPLAY:/tmp/$WAYLAND_DISPLAY \ + --user=$(id -u):$(id -g) \ + --network host openage + ``` + +4. **Follow the Regular Setup** + +Once inside the container, follow the regular setup described in the [Development](../building.md#development) chapter. You can skip dependency installation since the Docker image already includes all required dependencies. + +#### Notes + +- **X11 vs. Wayland**: Ensure you know which display server your system is using. Most modern Linux distributions default to Wayland, but X11 is still widely used. +- **Permissions**: For Wayland, you may need to adjust permissions for the Wayland socket (`/run/user/$(id -u)/wayland-0`) to allow Docker access. +- **GUI Applications**: These configurations enable GUI applications to run inside the Docker container. + +By following these steps, you can build and run the `openage` project in a Dockerized environment with support X11 or Wayland display servers. diff --git a/doc/build_instructions/macos.md b/doc/build_instructions/macos.md index c9946f2d39..72458f6269 100644 --- a/doc/build_instructions/macos.md +++ b/doc/build_instructions/macos.md @@ -38,7 +38,7 @@ We advise against using the clang version that comes with macOS (Apple Clang) as ``` # on Intel macOS, llvm is by default in /usr/local/Cellar/llvm/bin/ # on ARM macOS, llvm is by default in /opt/homebrew/Cellar/llvm/bin/ -./configure --compiler="$(brew --prefix llvm)/bin/clang"" --download-nyan +./configure --compiler="$(brew --prefix llvm)/bin/clang++" --download-nyan ``` Afterwards, trigger the build using `make`: diff --git a/doc/build_instructions/ubuntu.md b/doc/build_instructions/ubuntu.md index d46ebd159f..46cd975fb3 100644 --- a/doc/build_instructions/ubuntu.md +++ b/doc/build_instructions/ubuntu.md @@ -2,19 +2,23 @@ Run the following commands: - - `sudo apt-get update` - - `sudo apt-get install g++ cmake cython3 libeigen3-dev libepoxy-dev libfontconfig1-dev libfreetype-dev libharfbuzz-dev libogg-dev libopus-dev libopusfile-dev libpng-dev libtoml11-dev python3-dev python3-mako python3-numpy python3-lz4 python3-pil python3-pip python3-pygments python3-toml qml6-module-qtquick-controls qt6-declarative-dev qt6-multimedia-dev qml6-module-qtquick3d-spatialaudio` +```bash + sudo apt-get update + sudo apt-get install g++ cmake cython3 libeigen3-dev libepoxy-dev libfontconfig1-dev libfreetype-dev libharfbuzz-dev libogg-dev libopus-dev libopusfile-dev libpng-dev libtoml11-dev python3-dev python3-mako python3-numpy python3-lz4 python3-pil python3-pip python3-pygments python3-toml qml6-module-qtquick-controls qt6-declarative-dev qt6-multimedia-dev qml6-module-qtquick3d-spatialaudio + ``` You will also need [nyan](https://github.com/SFTtech/nyan/blob/master/doc/building.md) and its dependencies. -# Additional steps for Ubuntu 22.04 LTS +# Additional steps for Ubuntu 22.04 LTS & 24.04 LTS -The available system version of Cython is too old in Ubuntu 22.04. You have to get the correct version +The available system version of Cython is too old in Ubuntu 22.04 & 24.04. You have to get the correct version from pip: -``` +```bash pip3 install cython --break-system-packages ``` +Please note that project requires at least **Cython 3.0.10**. + # Linux Mint Issue Linux Mint has a [problem with `toml11`](https://github.com/SFTtech/openage/issues/1601), since CMake can't find it. To solve this, download the [toml11.zip](https://github.com/SFTtech/openage/files/13401192/toml11.zip), after, put the files in the `/usr/lib/x86_64-linux-gnu/cmake/toml11` path. (if the `toml11` directory doesn't exist, create it) diff --git a/doc/build_instructions/windows_msvc.md b/doc/build_instructions/windows_msvc.md index af889d8b99..9dc072db5a 100644 --- a/doc/build_instructions/windows_msvc.md +++ b/doc/build_instructions/windows_msvc.md @@ -17,100 +17,112 @@ __NOTE:__ You need to manually make sure and doublecheck if the system you are b --> ## Setting up the build environment - You will need to download and install the following manually. - Those who already have the latest stable versions of these programs can skip this: - - [Visual Studio Buildtools](https://aka.ms/vs/17/release/vs_BuildTools.exe) - - With the "Visual C++ Buildtools" workload. +You will need to download and install the following manually. +Those who already have the latest stable versions of these programs can skip this: - _NOTE:_ If you are searching for an IDE for development you can get an overview [here](https://en.wikipedia.org/wiki/Comparison_of_integrated_development_environments#C/C++). - We've also written some [instructions for developing with different IDEs](/doc/ide/README.md). +- [Visual Studio Buildtools](https://aka.ms/vs/17/release/vs_BuildTools.exe) + - With the "Visual C++ Buildtools" workload. + _NOTE:_ If you are searching for an IDE for development you can get an overview [here](https://en.wikipedia.org/wiki/Comparison_of_integrated_development_environments#C/C++). We've also written some [instructions for developing with different IDEs](/doc/ide/README.md). - - [Python 3](https://www.python.org/downloads/windows/) - - With the "pip" option enabled. We use `pip` to install other dependencies. - - With the "Precompile standard library" option enabled. - - With the "Download debug binaries (...)" option enabled. - - If in doubt, run the installer again and choose "Modify". - - You are going to need the 64-bit version of python if you are planning to build the 64-bit version of openage, and vice versa. +- [Python 3](https://www.python.org/downloads/windows/) + - With the *pip* option enabled. We use `pip` to install other dependencies. + - With the *Precompile standard library* option enabled. + - With the *Download debug binaries (...)* option enabled. + - If in doubt, run the installer again and choose *Modify*. + - You are going to need the 64-bit version of python if you are planning to build the 64-bit version of openage, and vice versa. - [CMake](https://cmake.org/download/) ### Python Modules - Open a command prompt at `/Scripts` - - pip install cython numpy lz4 toml pillow pygments pyreadline3 mako +Open a command prompt at `/Scripts` +```ps +pip install cython numpy lz4 toml pillow pygments pyreadline3 mako +``` _Note:_ Make sure the Python 3 instance you're installing these scripts for is the one you call `python` in CMD + _Note:_ Also ensure that `python` and `python3` both point to the correct and the same version of Python 3 ### vcpkg packages - Set up [vcpkg](https://github.com/Microsoft/vcpkg#quick-start). Open a command prompt at `` +Set up [vcpkg](https://github.com/Microsoft/vcpkg#quick-start). Open a command prompt at `` - vcpkg install dirent eigen3 fontconfig freetype harfbuzz libepoxy libogg libpng opus opusfile qtbase qtdeclarative qtmultimedia toml11 +```ps +vcpkg install dirent eigen3 fontconfig freetype harfbuzz libepoxy libogg libpng opus opusfile qtbase qtdeclarative qtmultimedia toml11 +``` + +If you also want Vulkan graphics support, you should also run this command: + +```ps +vcpkg install vulkan +``` - _Note:_ The `qt6` port in vcpkg has been split into multiple packages, build times are acceptable now. - If you want, you can still use [the prebuilt version](https://www.qt.io/download-open-source/) instead. - If you do so, include `-DCMAKE_PREFIX_PATH=` in the cmake configure command. +_Note:_ The `qt` port in vcpkg has been split into multiple packages, build times are acceptable now. If you want, you can still use [the prebuilt version](https://www.qt.io/download-open-source/) instead. If you do so, include `-DCMAKE_PREFIX_PATH=` in the cmake configure command. - _Note:_ If you are planning to build the 64-bit version of openage, you are going to need 64-bit libraries. - Add command line option `--triplet x64-windows` to the above command or add the environment variable `VCPKG_DEFAULT_TRIPLET=x64-windows` to build x64 libraries. [See here](https://github.com/Microsoft/vcpkg/issues/1254) +_Note:_ If you are planning to build the 64-bit version of openage, you are going to need 64-bit libraries. Add command line option `--triplet x64-windows` to the above command or add the environment variable `VCPKG_DEFAULT_TRIPLET=x64-windows` to build x64 libraries. [See here](https://github.com/Microsoft/vcpkg/issues/1254) ## Building openage - Note that openage doesn't support completely out-of-source-tree builds yet. - We will, however, use a separate `build` directory to build the binaries. +Note that openage doesn't support completely out-of-source-tree builds yet. We will, however, use a separate `build` directory to build the binaries. -_Note:_ You will also need to set up [the dependencies for Nyan](https://github.com/SFTtech/nyan/blob/master/doc/building.md#windows), which is mainly [flex](https://sourceforge.net/projects/winflexbison/) +_Note:_ You will also need to set up [the dependencies for nyan](https://github.com/SFTtech/nyan/blob/master/doc/building.md#windows), which is mainly [flex](https://sourceforge.net/projects/winflexbison/). Open a command prompt at ``: - mkdir build - cd build - cmake -DCMAKE_TOOLCHAIN_FILE=\scripts\buildsystems\vcpkg.cmake .. - cmake --build . --config RelWithDebInfo -- /nologo /m /v:m +```ps +mkdir build +cd build +cmake -DCMAKE_TOOLCHAIN_FILE=\scripts\buildsystems\vcpkg.cmake .. +cmake --build . --config RelWithDebInfo -- /nologo /m /v:m +``` _Note:_ If you want to build the x64 version, please add `-G "Visual Studio 17 2022" -A x64` (for VS2022) to the first cmake command. _Note:_ If you want to download and build Nyan automatically add `-DDOWNLOAD_NYAN=YES -DFLEX_EXECUTABLE=` to the first cmake command. ## Running openage (in devmode) - While this is straightforward on other platforms, there is still stuff to do to run openage on Windows: - - Install the [DejaVu Book Font](https://dejavu-fonts.github.io/Download.html). - - Download and extract the latest `dejavu-fonts-ttf` tarball/zip file. - - Select all `ttf\DejaVuSerif*.ttf` files, right click and click `Install for all users`. - - _Note:_ This will require administrator rights. - - Set the `FONTCONFIG_PATH` environment variable to `\installed\\tools\fontconfig\fonts\`. - - Copy `fontconfig\57-dejavu-serif.conf` to `%FONTCONFIG_PATH%\conf.d`. - - [Optional] Set the `AGE2DIR` environment variable to the AoE 2 installation directory. - - Set `QML2_IMPORT_PATH` to `\installed\\qml` or for prebuilt Qt `\\\qml` - - openage needs these DLL files to run: - - `openage.dll` (Usually in `\build\libopenage\`.) - - `nyan.dll` (The location depends on the procedure chosen to get nyan.) - - DLLs from vcpkg-installed dependencies. Normally, these DLLs should be copied to `\build\libopenage\` during the build process. If they are not, you can find them in `\installed\\bin`. - - If prebuilt QT6 was installed, the original location of QT6 DLLs is `\bin`. - - - Now, to run the openage: - - Open a CMD window in `\build\` and run `python -m openage main` - - Execute`\build\run.exe` every time after that and enjoy! +While this is straightforward on other platforms, there is still stuff to do to run openage on Windows: + +- Install the [DejaVu Book Font](https://dejavu-fonts.github.io/Download.html). + - Download and extract the latest `dejavu-fonts-ttf` tarball/zip file. + - Select all `ttf\DejaVuSerif*.ttf` files, right click and click `Install for all users`. + _Note:_ This will require administrator rights. + + - Set the `FONTCONFIG_PATH` environment variable to `\installed\\tools\fontconfig\fonts\` or `\installed\\etc\fonts\`. + - Copy `fontconfig\57-dejavu-serif.conf` to `%FONTCONFIG_PATH%\conf.d`. +- [Optional] Set the `AGE2DIR` environment variable to the AoE 2 installation directory. +- Set `QML2_IMPORT_PATH` to `\installed\\qml` or for prebuilt Qt `\\\qml` +- openage needs these DLL files to run: + - `openage.dll` (Usually in `\build\libopenage\`.) + - `nyan.dll` (The location depends on the procedure chosen to get nyan.) + - DLLs from vcpkg-installed dependencies. Normally, these DLLs should be copied to `\build\libopenage\` during the build process. If they are not, you can find them in `\installed\\bin`. + - If prebuilt QT6 was installed, the original location of QT6 DLLs is `\bin`. + +Now, to run openage: + +- Open a CMD window in `\build\` and run `python -m openage main` +- Execute`\build\run.exe` every time after that and enjoy! ## Packaging - - Install [NSIS](https://sourceforge.net/projects/nsis/files/latest/download). - - Depending on the way you installed Qt (vcpkg/pre-built) you need to edit the following line in `\buildsystem\templates\ForwardVariables.cmake.in`: +- Install [NSIS](https://sourceforge.net/projects/nsis/files/latest/download). +- Depending on the way you installed Qt (vcpkg/pre-built) you need to edit the following line in `\buildsystem\templates\ForwardVariables.cmake.in`: + ``` - # Use windeploy for packaging qt-prebuilt, standard value '1' for windeploy, '0' for vcpkg - set(use_windeployqt 1) +# Use windeploy for packaging qt-prebuilt, standard value '1' for windeploy, '0' for vcpkg +set(use_windeployqt 1) ``` - Open a command prompt at `\build` (or use the one from the building step): +Open a command prompt at `\build` (or use the one from the building step): +```ps cpack -C RelWithDebInfo +``` - The installer (`openage--.exe`) will be generated in the same directory.
+The installer (`openage--.exe`) will be generated in the same directory.
- _Hint:_ Append `-V` to the `cpack` command for verbose output (it takes time to package all dependencies). +_Note:_ Append `-V` to the `cpack` command for verbose output (it takes time to package all dependencies). - _Hint:_ you can set with the environment variable `TARGET_PLATFORM` (e.g. amd64, x86). +_Note:_ you can set with the environment variable `TARGET_PLATFORM` (e.g. amd64, x86). diff --git a/doc/building.md b/doc/building.md index 619ad7a423..923fde5507 100644 --- a/doc/building.md +++ b/doc/building.md @@ -29,7 +29,7 @@ Dependency list: C gcc >=10 or clang >=10 CRA python >=3.9 - C cython >=0.29.31 + C cython >=3.0.10 OR (>=0.29.31 AND <=3.0.7) C cmake >=3.16 A numpy A lz4 @@ -39,6 +39,7 @@ Dependency list: CR opengl >=3.3 CR libepoxy CR libpng + S clang-tidy R dejavu font CR eigen >=3 CR freetype2 @@ -160,7 +161,6 @@ The reference package is [created for Gentoo](https://github.com/SFTtech/gentoo- - Use `make install DESTDIR=/tmp/your_temporary_packaging_dir`, which will then be packed/installed by your package manager. - ### Troubleshooting - I wanna see compiler invocations diff --git a/doc/buildsystem.md b/doc/buildsystem.md index 57dcdaf1d7..65b9acba5b 100644 --- a/doc/buildsystem.md +++ b/doc/buildsystem.md @@ -42,7 +42,7 @@ Additional recipes: - `doc` (generate docs via Doxygen) - `test` (runs the various tests) - various cleaning recipes: `cleanelf`, `cleancodegen`, `cleancython`, `cleaninsourcebuild`, `cleanpxdgen`, `cleanbuilddirs`, `mrproper`, `mrproperer` - - various compliance checkers: `checkfast`, `checkall`, ... + - various compliance checkers: `checkfast`, `checkmerge`, `checkall`, ... Phases diff --git a/doc/changelogs/nyan_api/v0.6.0.md b/doc/changelogs/nyan_api/v0.6.0.md new file mode 100644 index 0000000000..1372c5ef83 --- /dev/null +++ b/doc/changelogs/nyan_api/v0.6.0.md @@ -0,0 +1,43 @@ +# [0.6.0] - 2025-05-18 +All notable changes for version [v0.6.0] are documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Added +### Ability module +- Add `Ranged(Property)` object; defines range of an ability; replaces various range members in `Ability` objects + +### Utility module +- Add `AbilityUsable(Condition)` object; checks if an ability can be used by an entity +- Add `NextCommand(Condition)` object; checks if a specific command is next in the command queue; replaces individual objects checking for specific commands +- Add `TargetInRange(Condition)` object; checks if the target of an entity is in range of a specific ability +- Add `Task(Node)` object; execute internal task when visiting node +- Add `Task(Object)` object +- Add `ClearCommandQueue(Task)` object; clears the command queue when visiting task node +- Add `PopCommandQueue(Task)` object; pops the front command in the command queue when visiting task node +- Add `MoveToTarget(Task)` object; move to the current target of the entity when visiting task node +- Add `XORSwitchGate(Node)` object; switch branches based on evaluating single value +- Add `SwitchCondition(Object)` object +- Add `NextCommand(SwitchCondition)` object; switch branches based on the next command in the command queue +- Add `Command(Object)` object; references internal commands of the engine +- Add `ApplyEffect(Command)` object +- Add `Idle(Command)` object +- Add `Move(Command)` object + +### Removed +### Ability module +- Remove `RangedContinuousEffect(Ability)` object; functionality superceded by `Ranged(Property)` object +- Remove `RangedDiscreteEffect(Ability)` object; functionality superceded by `Ranged(Property)` object +- Remove `range` member from `DetectCloak(Ability)`; functionality superceded by `Ranged(Property)` object +- Remove `range` member from `Herd(Ability)`; functionality superceded by `Ranged(Property)` object +- Remove `min_range` member from `ShootProjectile(Ability)`; functionality superceded by `Ranged(Property)` object +- Remove `max_range` member from `ShootProjectile(Ability)`; functionality superceded by `Ranged(Property)` object + +### Utility module +- Remove `NextCommandIdle(Condition)` object; functionality superceded by `NextCommand(Condition)` object +- Remove `NextCommandMove(Condition)` object; functionality superceded by `NextCommand(Condition)` object + +## Reference visualization + +* [Gamedata](https://github.com/SFTtech/openage/blob/927f547d4985cba8e172c9492273b34537571c56/doc/nyan/aoe2_nyan_tree.svg) diff --git a/doc/code/curves.md b/doc/code/curves.md index 376c4c938c..e914f8bcfe 100644 --- a/doc/code/curves.md +++ b/doc/code/curves.md @@ -17,6 +17,7 @@ Curves are an integral part of openage's event-based game simulation. 2. [Container](#container) 1. [Queue](#queue) 2. [Unordered Map](#unordered-map) + 3. [Array](#array) ## Motivation @@ -42,7 +43,7 @@ directly invalidating the state, making curves more reliable in async scenarios resolving dependencies for keyframes in the past can still be challenging). The usage of curves has a few downsides though. They are less space efficient due to the -keyframe storage, interpolation are more costly more costly than incremental changes, and +keyframe storage, interpolation are more costly than incremental changes, and their integration is more complex than the usage of simpler data structures. However, in situations where operations are predictable, long-lasting, and easy to calculate - which is the case for most RTS games - the positives may outweigh the downsides. @@ -198,7 +199,7 @@ e.g. angles between 0 and 360 degrees. ### Container Container curves are intended for storing changes to collections and containers. -The currently supported containers are `Queue` and `UnorderedMap`. +The currently supported containers are `Queue`, `UnorderedMap` and `Array`. The most important distinction between regular C++ containers and curve containers is that curve containers track the *lifespan* of each element, i.e. their insertion time, @@ -253,3 +254,41 @@ Unordered map curve containers store key-value pairs while additionally keeping track of element insertion time. Requests for a key `k` at time `t` will return the value of `k` at that time. The unordered map can also be iterated over for a specific time `t` which allows access to all key-value pairs that were in the map at time `t`. + + +#### Array + +Array curve containers store a fixed number of `n` elements where `n` is determined at compile-time. +They are the curve equivalent to the `std::array` C++ containers. In comparison to `std::array` each +element in the array curve container is tracked individually over time. Hence, each index is associated +with its own `KeyframeContainer` whose keyframes can be updated independent from other indices. +When a value is added to the `Array` curve at a given index, a new keyframe is added to the respective +`KeyframeContainer` stored at that index. + +**Read** + +Read operations retrieve values for a specific point in time. + +| Method | Description | +| ------------------ | ------------------------------------------------------------------------ | +| `get(t, i)` | Get value of element at index `i` at time <= `t` | +| `get(t)` | Get array of values at time <= `t` | +| `size()` | Get the number of elements in the array | +| `frame(t, i)` | Get the previous keyframe (time and value) at index `i` before or at `t` | +| `next_frame(t, i)` | Get the next keyframe (time and value) at index `i` after `t` | + +**Modify** + +Modify operations insert values for a specific point in time. + +| Method | Description | +| -------------------------- | ------------------------------------------------------------------------------------------ | +| `set_insert(t, i, value)` | Insert a new keyframe(`t`, `value`) at index `i` | +| `set_last(t, i, value)` | Insert a new keyframe(`t`, `value`) at index `i`; delete all keyframes after time `t` | +| `set_replace(t, i, value)` | Insert a new keyframe(`t`, `value`) at index `i`; remove all other keyframes with time `t` | + +**Copy** + +| Method | Description | +| ---------------- | ------------------------------------------------------------------------------------------------ | +| `sync(Curve, t)` | Replace all keyframes from self after time `t` with keyframes from source `Curve` after time `t` | diff --git a/doc/code/renderer/demos.md b/doc/code/renderer/demos.md index 3bb7b266f3..82de8c4e86 100644 --- a/doc/code/renderer/demos.md +++ b/doc/code/renderer/demos.md @@ -24,7 +24,7 @@ The demo mostly follows the steps described in the [Level 1 Renderer - Basic Usa documentation. ```bash -./bin/run test --demo renderer.tests.renderer_demo 0 +cd bin && ./run test --demo renderer.tests.renderer_demo 0 ``` **Result:** @@ -38,7 +38,7 @@ This demo shows how simple *textured* meshes can be created and rendered. It als how to interact with the window and the renderer using window callbacks. ```bash -./bin/run test --demo renderer.tests.renderer_demo 1 +cd bin && ./run test --demo renderer.tests.renderer_demo 1 ``` **Controls:** @@ -56,7 +56,7 @@ In this demo, we show how animation and texture metadata files are parsed and us load and render the correct textures and animations for a mesh. ```bash -./bin/run test --demo renderer.tests.renderer_demo 2 +cd bin && ./run test --demo renderer.tests.renderer_demo 2 ``` **Controls:** @@ -76,7 +76,7 @@ This demo shows a minimal setup for the [Level 2 Renderer](level2.md) and how to with it. The demo also introduces the camera system and how to interact with it. ```bash -./bin/run test --demo renderer.tests.renderer_demo 3 +cd bin && ./run test --demo renderer.tests.renderer_demo 3 ``` **Controls:** @@ -95,7 +95,7 @@ This demos shows how animation frame timing works and how to control the animati with the engine's internal clock. ```bash -./bin/run test --demo renderer.tests.renderer_demo 4 +cd bin && ./run test --demo renderer.tests.renderer_demo 4 ``` **Controls:** @@ -115,7 +115,7 @@ This demo shows how to create [uniform buffers](level1.md#uniform-buffers) and h Additionally, uniform buffer usage for the camera system is demonstrated. ```bash -./bin/run test --demo renderer.tests.renderer_demo 5 +cd bin && ./run test --demo renderer.tests.renderer_demo 5 ``` **Controls:** @@ -132,7 +132,7 @@ Additionally, uniform buffer usage for the camera system is demonstrated. This demo shows how to use [frustum culling](level2.md#frustum-culling) in the renderer. ```bash -./bin/run test --demo renderer.tests.renderer_demo 6 +cd bin && ./run test --demo renderer.tests.renderer_demo 6 ``` **Controls:** @@ -144,6 +144,19 @@ This demo shows how to use [frustum culling](level2.md#frustum-culling) in the r ![Demo 6](/doc/code/renderer/images/demo_6.png) +### Demo 7 + +This demo shows how to use [shader templating](level1.md#shader-templates) in the renderer. + +```bash +cd bin && ./run test --demo renderer.tests.renderer_demo 7 +``` + +**Result:** + +![Demo 7](/doc/code/renderer/images/demo_7.mp4) + + ## Stresstests ### Stresstest 0 @@ -151,7 +164,7 @@ This demo shows how to use [frustum culling](level2.md#frustum-culling) in the r This stresstest tests the performance when rendering an increasingly larger number of objects. ```bash -./bin/run test --demo renderer.tests.stresstest 0 +cd bin && ./run test --demo renderer.tests.renderer_stresstest 0 ``` **Result:** @@ -164,7 +177,7 @@ This stresstest tests the performance when [frustum culling](level2.md#frustum-c number of objects is rendered on the screen. ```bash -./bin/run test --demo renderer.tests.stresstest 1 +cd bin && ./run test --demo renderer.tests.renderer_stresstest 1 ``` **Result:** diff --git a/doc/code/renderer/images/demo_7.mp4 b/doc/code/renderer/images/demo_7.mp4 new file mode 100644 index 0000000000..fa26db0f86 Binary files /dev/null and b/doc/code/renderer/images/demo_7.mp4 differ diff --git a/doc/code/renderer/level1.md b/doc/code/renderer/level1.md index 659c51036c..b8dbc8fb30 100644 --- a/doc/code/renderer/level1.md +++ b/doc/code/renderer/level1.md @@ -17,6 +17,7 @@ Low-level renderer for communicating with the OpenGL and Vulkan APIs. 3. [Defining Layers in a Render Pass](#defining-layers-in-a-render-pass) 4. [Complex Geometry](#complex-geometry) 5. [Uniform Buffers](#uniform-buffers) + 6. [Shader Templates](#shader-templates) 5. [Thread-safety](#thread-safety) @@ -542,5 +543,80 @@ resources::UniformBufferInfo ubo_info{ std::shared_ptr buffer = renderer->add_uniform_buffer(ubo_info); ``` +### Shader Templates + +Shader templates allow the definition of a shader source code with placeholders that can be replaced at +load- or run-time. Templates are help if only specific parts of the shader code are supposed to +be configurable. This may be used, for example, to alter certain code path of the built-in shaders +without recompiling the engine. + +An example shader template looks like this: + +```glsl +#version 330 + +in vec2 tex_coord; +out vec4 frag_color; + +// PLACEHOLDER: uniform_color + +void main() { + // PLACEHOLDER: alpha + frag_color = vec4(col.xyz, alpha); +} +``` + +Placeholders are inserted as comments into the GLSL source. Every placeholder has an ID for +referencing. For these IDs, *snippets* can be defined to insert code in place of the placeholder +comment. Placeholders can be placed at any position in the template, so they can be used to insert +other statements than the control code, including unforms, input/output variables, functions and more. + +The snippets for the above example may look like this: + +`uniform_color.snippet` + +```glsl +uniform vec4 col; +``` + +`alpha.snippet` + +```glsl +float alpha = 0.5; +``` + +In the renderer, shader templates can be created using the `renderer::resources::ShaderTemplate` class. + +```cpp +util::Path template_path = shaderdir / "example_template.frag.glsl"; +resources::ShaderTemplate template(template_path); +``` + +After loading the template, snippets for the placeholders can be added. These are either loaded +as single files or from a directory. + +```cpp +util::Path snippets_path = shaderdir / "example_snippets"; +template.load_snippets(snippets_path); +``` + +or + +```cpp +util::Path snippets_path = shaderdir / "example_snippets"; +template.add_snippet(snippets_path / "uniform_color.snippet"); +template.add_snippet(snippets_path / "alpha.snippet"); +``` + +If snippets have been loaded for all placeholder IDs, the shader template can generate the final +shader source code as `renderer::resources::ShaderSource`. This can then be used to create the actual +`renderer::ShaderProgram` uploaded to the GPU. + +```cpp +resources::ShaderSource source = template.generate_source(); +ShaderProgram prog = = renderer->add_shader({source}); +``` + + ## Thread-safety This level might or might not be threadsafe depending on the concrete backend. The OpenGL version is, in typical GL fashion, so not-threadsafe it's almost anti-threadsafe. All code must be executed sequentially on a dedicated window thread, the same one on which the window and renderer were initially created. The plan for the Vulkan version is to make it at least independent of thread-local storage and hopefully completely threadsafe. diff --git a/doc/contributing.md b/doc/contributing.md index 6498b10f95..326c1d1e98 100644 --- a/doc/contributing.md +++ b/doc/contributing.md @@ -52,7 +52,7 @@ tl;dr - Add yourself to `copying.md` - `git add libopenage/unit/tentacle_monster.cpp` - `git commit -m "engine: fixed vomiting animation of tentacle monster"` -- `make checkall` +- `make checkmerge` - `make test` - `git push origin tentacle-monster-fix` - Create a pull request and look at the CI output @@ -101,7 +101,7 @@ Before making a pull request, it's good to review these things: - Run `make test` to check whether any functionality has been broken - [Check your whitespaces](https://github.com/SFTtech/openage/blob/master/doc/code_style/tabs_n_spaces.md) - [Read all the codestyle docs]( https://github.com/SFTtech/openage/tree/master/doc/code_style) -- Before pushing, run `make checkall`. If that fails, the automatic buildbot will reject your code. +- Before pushing, run `make checkmerge`. If that fails, the automatic buildbot will reject your code. - If this is your first contribution, add yourself to the authors list in [copying.md](/copying.md). - Commit messages should be meaningful, they should say in a sentence (or very little text) what changes it has without requiring to read the entire diff. [tpope knows this very well!](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) diff --git a/doc/ide/configs/vscode/tasks.json b/doc/ide/configs/vscode/tasks.json index 9ac35659b0..2a43dbf10e 100644 --- a/doc/ide/configs/vscode/tasks.json +++ b/doc/ide/configs/vscode/tasks.json @@ -51,7 +51,7 @@ "type": "shell", "command": "make", "args": [ - "checkall" + "checkmerge" ], "group": "build", "presentation": { diff --git a/doc/nyan/aoe2_nyan_tree.svg b/doc/nyan/aoe2_nyan_tree.svg index 27360da9cf..ba6d22e8d4 100644 --- a/doc/nyan/aoe2_nyan_tree.svg +++ b/doc/nyan/aoe2_nyan_tree.svg @@ -1,7 +1,7 @@ -AbilityUsableability : AbilityMoveToTargetPathTypeClearCommandQueueNextCommandMovePopCommandQueueNextCommandIdleTaskTasknext : Nodetask : TaskTargetInRangeability : AbilityRangedmin_range : floatmax_range : floatApplyEffectMoveIdleCommandNextCommandnext : dict(Command, Node)SwitchConditionXORSwitchGateswitch : SwitchConditiondefault : NodePathTypeNextCommandcommand : CommandCommandInQueueConditionnext : NodeCommandInQueueWaitAbilityWaittime : floatEventUnit behaviour graphActivitygraph : ActivityAbilitynext : Nodeability : abstract(Ability)ability : AbilityXOREventGatenext : dict(Event, Node)XORGatenext : orderedset(Condition)default : NodeEndActivitystart : StartStartnext : NodeNodeConstructablestarting_progress : intconstruction_progress : set(Progress)TransformCarryRestockHarvestConstructAll ingame objectsare game entitiesProjectileBarracksSwordsmanRelicTreeFalseTruePatchPropertyPrioritypriority : intChainedBatchOrderedBatchUnorderedBatchChancechance : floatPrioritypriority : intBatchPropertyEffectBatcheffects : set(DiscreteEffect)properties : dict(BatchProperty, BatchProperty) = {}OwnsGameEntitygame_entity : GameEntityStateChangeActivestate_change : StateChangerResetResetProgressPropertyResistancePropertyAreaEffectrange : floatdropoff : DropoffTypeDiplomaticstances : set(DiplomaticStance)Costcost : CostEffectPropertyflacflacresistanceeffectMultipliermultiplier : floatModifierPropertyLockPoolslots : intLocklock_pools : set(LockPool)Locklock_pool : LockPoolAbilityPropertyMULTIXORXORNOTSUBSETMAXsize : intLogicGateinputs : set(LogicElement)BuySellExchangeModefee_multiplier : floatExchangeResourcesresource_a : Resourceresource_b : Resourceexchange_rate : ExchangeRateexchange_modes : set(ExchangeMode)ExchangeRatebase_price : floatprice_adjust : optional(dict(ExchangeMode, PriceMode)) = Noneprice_pool : optional(PricePool) = NoneAnyTransformPoolInternalDropSiteupdate_time : floatResourceContainerresource : Resourcemax_amount : intcarry_progress : set(Progress)ResourceStoragecontainers : set(ResourceContainer)MiscVariantElevationDifferenceHighmin_elevation_difference : optional(float) = NoneElevationDifferenceHighmin_elevation_difference : optional(float) = NoneAttributeAboveValueattribute : Attributethreshold : floatAttributeBelowPercentageattribute : Attributethreshold : floatAnimationOverlayoverlays : set(Animation)AnyAnyAnyTechTypeReplacegame_entities : set(GameEntity)Guardrange : floatPalettepalette : fileAttributeAbovePercentageattribute : Attributethreshold : floatNyanPatchStackedstack_limit : intSelectionBoxMatchToSpriteRectanglewidth : floatheight : floatMeanDistributionTypeDetectCloak (SWGB)range : floatallowed_types : set(GameEntityType)blacklisted_entities : set(GameEntity)Hitboxradius_x : floatradius_y : floatradius_z : floatProjectileHitTerrainProjectilePassThroughpass_through_range : intAttributeBelowValueattribute : Attributethreshold : floatTimertime : floatResourceSpotsDepletedonly_enabled : boolSelfAnyLiteralScopestances : set(DiplomaticStance)SUBSETMINsize : intORANDLogicElementonly_once : boolResearchablesexclude : set(ResearchableTech)Creatablesexclude : set(CreatableGameEntity)ProductionModeProductionQueuesize : intproduction_modes : set(ProductionMode)OwnStoragecontainer : EntityContainerNoStackLinearshift_x : intshift_y : intscale_factor : floatHyperbolicshift_x : intshift_y : intscale_factor : floatCalculationTypeStackedstack_limit : intcalculation_type : CalculationTypedistribution_type : DistributionTypeTerrainTypeMostHerdingLongestTimeInRangeClosestHerdingHerdableModeShadowTimeRelativeProgressIncreaseTimeRelativeProgressDecreaseTimeRelativeProgressIncreaseTimeRelativeProgressDecreaseAttributeChangeAdaptiveArrearAdvancePaymentModeResearchAttributeCostattributes : set(Attribute)researchables : set(ResearchableTech)CreationAttributeCostattributes : set(Attribute)creatables : set(CreatableGameEntity)AttributeCostamount : set(AttributeAmount)ResourceCostamount : set(ResourceAmount)Costpayment_mode : PaymentModeUnconditionalUnconditionalTimeRelativeProgressChangeTimeRelativeAttributeChangePricePoolDynamicchange_value : floatmin_price : floatmax_price : floatFixedPriceModeDepositResourcesOnProgressprogress_type : ProgressTyperesources : set(Resource)affected_types : set(GameEntityType)blacklisted_entities : set(GameEntity)Attributename : TranslatedStringabbreviation : TranslatedStringOverlayTerrainterrain_overlay : TerrainTerrainRequirementallowed_types : set(TerrainType)blacklisted_terrains : set(Terrain)Terrainterrain : TerrainStateChangestate_change : StateChangerAoE1TradeRouteexchange_resources : set(Resource)trade_amount : intProgressTypeTimeRelativeProgressChangetype : ProgressTypeFlatAttributeIncreaseFlatAttributeDecreaseTimeRelativeAttributeChangetype : AttributeChangeTypeTimeRelativeProgressChangetype : ProgressTypetotal_change_time : floatTimeRelativeAttributeIncreaseTimeRelativeAttributeDecreaseTimeRelativeAttributeChangetype : AttributeChangeTypetotal_change_time : floatignore_protection : set(ProtectingAttribute)ProgressStatusprogress_type : ProgressTypeprogress : floatRefundOnConditioncondition : set(LogicElement)refund_amount : set(ResourceAmount)AnimationOverrideoverrides : set(AnimationOverride)ExecutionSoundsounds : set(Sound)InverseLinearAdjacentTilesVariantnorth : optional(GameEntity)north_east : optional(GameEntity)east : optional(GameEntity)south_east : optional(GameEntity)south : optional(GameEntity)south_west : optional(GameEntity)west : optional(GameEntity)north_west : optional(GameEntity)Placetile_snap_distance : floatclearance_size_x : floatclearance_size_y : floatallow_rotation : boolmax_elevation_difference : intEjectPlacementModeSendToContainerTypeLureTypeDiplomaticLineOfSightdiplomatic_stance : DiplomaticStanceTerrainterrain : TerrainTerrainterrain : TerrainNormalInContainerDiscreteEffectcontainers : set(EntityContainer)ability : ApplyDiscreteEffectInContainerContinuousEffectcontainers : set(EntityContainer)ability : ApplyContinuousEffectStateChangerenable_abilities : set(Ability)disable_abilities : set(Ability)enable_modifiers : set(Modifier)disable_modifiers : set(Modifier)transform_pool : optional(TransformPool) = Nonepriority : intopenage nyan data API v0.5.0openage nyan data API v0.6.0GameEntityScopeaffected_types : set(GameEntityType)blacklisted_entities : set(GameEntity)StandardRegenerateResourceSpotrate : ResourceRateresource_spot : ResourceSpotAoE2ProjectileAmountprovider_abilities : set(ApplyDiscreteEffect)receiver_abilities : set(ApplyDiscreteEffect)change_types : set(AttributeChangeType)Orange elements:Effects/Resistances that canbe applied on other gameentitiesRevealline_of_sight : floataffected_types : set(GameEntityType)blacklisted_entities : set(GameEntity)ResearchTimeresearchables : set(ResearchableTech)StorageElementCapacitystorage_element : StorageElementDefinitionEntityContainerCapacitycontainer : EntityContainerHerdrange : floatstrength : intallowed_types : set(GameEntityType)blacklisted_entities : set(GameEntity)InstantTechResearchtech : Techcondition : set(LogicElement)ResearchResourceCostresources : set(Resource)researchables : set(ResearchableTech)CreationResourceCostresources : set(Resource)creatables : set(CreatableGameEntity)CreationTimecreatables : set(CreatableGameEntity)ReloadTimeGatheringEfficiencyresource_spot : ResourceSpotAbsoluteProjectileAmountamount : floatStrayMoveModeAttackMoveModifierScopeExpectedPositionCurrentPositionTargetModeSendToContainertype : SendToContainerTypesearch_range : floatignore_containers : set(EntityContainer)SendToContainertype : SendToContainerTypestorages : set(EntityContainer)Scopedstances : set(DiplomaticStance)scope : ModifierScopeGameEntityFormationformation : Formationsubformation : SubformationSubformationordering_priority : intFormationsubformations : set(Subformation)FlatAttributeIncreaseFlatAttributeDecreaseFlatAttributeIncreaseFlatAttributeDecreaseFlatAttributeIncreaseFlatAttributeDecreaseFlatAttributeIncreaseFlatAttributeDecreaseAoE2TradeRouteTradeRoutetrade_resource : Resourcestart_trade_post : GameEntityend_trade_post : GameEntityTechtypes : set(TechType)name : TranslatedStringdescription : TranslatedMarkupFilelong_description : TranslatedMarkupFileupdates : orderedset(Patch)FallbackLinearNoDropoffDropoffTypeDiplomaticstances : set(DiplomaticStance)AnimationOverrideability : AnimatedAbilityanimations : set(Animation)priority : intCostcost : CostEntityContainerallowed_types : set(GameEntityType)blacklisted_entities : set(GameEntity)storage_element_defs : set(StorageElementDefinition)slots : intcarry_progress : set(Progress)Visibilityvisible_in_fog : boolTurnturn_speed : floatLanguageietf_string : textLanguageSoundPairlanguage : Languagesound : SoundLanguageMarkupPairlanguage : Languagemarkup_file : fileLanguageTextPairlanguage : Languagestring : textLuretype : LureTypeLuretype : LureTypedestination : set(GameEntity)min_distance_to_destination : floatElevationDifferenceLowmin_elevation_difference : optional(float) = NoneSelfDiplomaticstances : set(DiplomaticStance)DiplomaticStanceExitContainerallowed_containers : set(EntityContainer)EnterContainerallowed_containers : set(EntityContainer)allowed_types : set(GameEntityType)blacklisted_entities : set(GameEntity)RangedDiscreteEffectmin_range : intmax_range : intHuntConvertTypeConvertSelfDestructConvert (Ranged)RangedContinuousEffectmin_range : intmax_range : intSelfDestructMakeHarvestableresource_spot : ResourceSpotresist_condition : set(LogicElement)MakeHarvestableresource_spot : ResourceSpotGatherauto_resume : boolresume_search_range : floattargets : set(ResourceSpot)gather_rate : ResourceRatecontainer : ResourceContainerRepairMonkHealMonkHeal (Ranged)ApplyContinuousEffecteffects : set(ContinuousEffect)application_delay : floatallowed_types : set(GameEntityType)blacklisted_entities : set(GameEntity)ApplyDiscreteEffectbatches : set(EffectBatch)reload_time : floatapplication_delay : floatallowed_types : set(GameEntityType)blacklisted_entities : set(GameEntity)FlatAttributeChangetype : AttributeChangeTypeblock_rate : AttributeRateContinuousResistanceAttributeRatetype : Attributerate : floatResourceRatetype : Resourcerate : floatDiscreteResistanceDiscreteEffectFlatAttributeChangeignore_protection : set(ProtectingAttribute)ContinuousEffectAoE2Convertprotection_round_recharge_time : floatAoE2Convertskip_guaranteed_rounds : intskip_protected_rounds : intConvertchance_resist : floatConvertcost_fail : optional(Cost) = NoneAttributeChangeTypeFlatAttributeChangetype : AttributeChangeTypeblock_value : AttributeAmountFlatAttributeChangetype : AttributeChangeTypemin_change_value : optional(AttributeAmount) = Nonemax_change_value : optional(AttributeAmount) = Nonechange_value : AttributeAmountignore_protection : set(ProtectingAttribute)Effectors' sideResistors' sideEffectproperties : dict(EffectProperty, EffectProperty) = {}Resistanceproperties : dict(ResistanceProperty, ResistanceProperty) = {}HealthGameEntityTypeAttributeAmounttype : Attributeamount : intAccuracyaccuracy : floataccuracy_dispersion : floatdispersion_dropoff : DropOffTypetarget_types : set(GameEntityType)blacklisted_entities : set(GameEntity)Cloak (SWGB)interrupted_by : set(Ability)interrupt_cooldown : floatLineOfSightrange : floatFaithShield (SWGB)ProtectingAttributeprotects : AttributeAttributeSettingstarting_value : intTerrainOverlayterrain_overlay : TerrainAnimatedoverrides : set(AnimationOverride)Terrainsprite : fileCreatableGameEntityplacement_modes : set(PlacementMode)UseContingentamount : set(ResourceAmount)ProvideContingentamount : set(ResourceAmount)ResourceContingentmin_amount : intmax_amount : intLiteralscope : LiteralScopeProjectileunignore_entities : set(GameEntity)AttributeChangeTrackerattribute : Attributechange_progress : set(Progress)Collisionhitbox : HitboxNamedlong_description : TranslatedMarkupFileDropResourcescontainers : set(ResourceContainer)search_range : floatallowed_types : set(GameEntityType)blacklisted_entities : set(GameEntity)Herdableadjacent_discover_range : floatmode : HerdableModeStopPathablehitbox : Hitboxpath_costs : dict(PathType, int)Foundationfoundation_terrain : TerrainPerspectiveVariantangle : intRestockamount : intPassiveStandGroundDefensiveAggressiveTradePosttrade_routes : set(TradeRoute)Followrange : floatPatrolGameEntityStancetype_preference : orderedset(GameEntityType)GameEntityStancestances: set(GameEntityStance)SendBackToTaskblacklisted_entities : set(GameEntity)TransferStoragestorage_element : GameEntitysource_container : EntityContainertarget_container : EntityContainerGameEntityProgressstatus : ProgressStatusTechResearchedtech : TechVariantchanges : orderedset(Patch)priority : intActiveTransformTotransform_progress : set(Progress)Selectableselection_box : SelectionBoxRallyPointAoE2 specific objectsimplementation)implementation)Basic nyan API objectsElevationDifferenceLowmin_elevation_difference : optional(float) = NoneFlyoverrelative_angle : floatflyover_types : set(GameEntityType)blacklisted_entities : set(GameEntity)Animationsprite : fileAnimationOverridesanimation.If min_projectiles is greater thanthe number of Projectiles inprojectiles, the last projectilein the orderedset should be usedbut AoM had more.Stores what happens aftera percentage ofconstruction, damage,transformation, etc. isreachedProgressright_boundary : floatgame_setup patches the uniquefeatures into the objects(graphics, techs, boni, abilities,etc.)RemoveStoragestorage_elements : set(GameEntity)CollectStoragestorage_elements : set(GameEntity)StorageElementDefinitionstorage_element : GameEntityelements_per_slot : intconflicts : set(StorageElementDefinition)state_change : optional(StateChanger) = NoneStoragecontainer : EntityContainerempty_condition : set(LogicElement)RandomVariantchance_share : floatResearchresearchables : set(ResearchableTech)Tradetrade_routes : set(TradeRoute)container : ResourceContainerCreatecreatables : set(CreatableGameEntity)RelicBonusGatheringRateresource_spot : ResourceSpotMoveSpeedAttributeSettingsValueattribute : AttributeFeitoriaBonusFoodAmountWoodAmountStoneAmountGoldAmountResourceAmounttype : Resourceamount : intContinuousResourcerates : set(ResourceRate)patched.IdlePlayerSetupgame_setup : orderedset(Patch)Despawnstate_change : optional(StateChanger) = NoneTauntactivation_message : textdisplay_message : TranslatedStringsound : SoundLiveattributes : set(AttributeSetting)Harvestableharvestable_by_default : boolPassiveTransformTotransform_progress : set(Progress)Cheatactivation_message : textchanges : orderedset(Patch)Flyheight : floatResistanceresistances : set(Resistance)ShootProjectilemax_projectiles : intmin_range : intmax_range : intreload_time : floatspawn_delay : floatprojectile_delay : floatrequire_turning : boolmanual_aiming_allowed : boolspawning_area_offset_x : floatspawning_area_offset_y : floatspawning_area_offset_z : floatspawning_area_width : floatspawning_area_height : floatspawning_area_randomness : floatallowed_types : set(GameEntityType)blacklisted_entities : set(GameEntity)RegenerateAttributerate : AttributeRateFormationformations : set(GameEntityFormation)Movepath_type : PathTypeCommandSoundsounds : set(Sound)Animatedanimations : set(Animation)Abilityproperties : dict(AbilityProperty, AbilityProperty) = {}Modpriority : intpatches : orderedset(Patch)Patchproperties : dict(PatchProperty, PatchProperty) = {}patch : NyanPatchModifierproperties : dict(ModifierProperty, ModifierProperty) = {}Soundplay_delay : floatsounds : orderedset(file)TerrainAmbientobject : GameEntitymax_density : intTerrainpath_costs : dict(PathType, int)ResearchableTechcondition : set(LogicElement)TranslatedSoundtranslations : set(LanguageSoundPair)TranslatedMarkupFiletranslations : set(LanguageMarkupPair)TranslatedObjectTranslatedStringtranslations : set(LanguageTextPair)Resourcename : TranslatedStringmax_storage : intResourceSpotdecay_rate : floatDropSiteaccepts_from : set(ResourceContainer)GameEntityvariants : set(Variant)Object - 10 + 9 UMLClass - 1190 - 3600 - 100 - 60 + 1080 + 3249 + 90 + 54 *Object* @@ -41,10 +41,10 @@ bg=red UMLClass - 410 - 3580 - 300 - 120 + 378 + 3231 + 270 + 108 *GameEntity* @@ -59,10 +59,10 @@ variants : set(Variant) UMLClass - 4930 - 3130 - 290 - 80 + 4446 + 2826 + 261 + 72 *DropSite* bg=green @@ -74,10 +74,10 @@ accepts_from : set(ResourceContainer) Relation - 700 - 3620 - 510 - 30 + 639 + 3267 + 459 + 27 lt=<<- 490.0;10.0;10.0;10.0 @@ -85,10 +85,10 @@ accepts_from : set(ResourceContainer) UMLClass - 850 - 3120 - 300 - 120 + 774 + 2817 + 270 + 108 *ResourceSpot* @@ -103,10 +103,10 @@ decay_rate : float UMLClass - 910 - 3300 - 180 - 80 + 828 + 2979 + 162 + 72 *Resource* bg=pink @@ -119,10 +119,10 @@ max_storage : int UMLClass - 0 - 2670 - 270 - 80 + 9 + 2412 + 243 + 72 *TranslatedString* bg=pink @@ -134,10 +134,10 @@ translations : set(LanguageTextPair) UMLClass - 370 - 2830 - 150 - 60 + 342 + 2556 + 135 + 54 *TranslatedObject* @@ -147,10 +147,10 @@ bg=pink UMLClass - 290 - 2670 - 290 - 80 + 270 + 2412 + 261 + 72 *TranslatedMarkupFile* bg=pink @@ -162,10 +162,10 @@ translations : set(LanguageMarkupPair) UMLClass - 600 - 2670 - 280 - 80 + 549 + 2412 + 252 + 72 *TranslatedSound* bg=pink @@ -177,10 +177,10 @@ translations : set(LanguageSoundPair) Relation - 430 - 2740 - 30 - 110 + 396 + 2475 + 27 + 99 lt=<<- 10.0;90.0;10.0;10.0 @@ -188,10 +188,10 @@ translations : set(LanguageSoundPair) Relation - 120 - 2740 - 340 - 60 + 117 + 2475 + 306 + 54 lt=- 320.0;40.0;10.0;40.0;10.0;10.0 @@ -199,10 +199,10 @@ translations : set(LanguageSoundPair) Relation - 430 - 2740 - 330 - 60 + 396 + 2475 + 297 + 54 lt=- 10.0;40.0;310.0;40.0;310.0;10.0 @@ -210,10 +210,10 @@ translations : set(LanguageSoundPair) UMLClass - 3260 - 2420 - 320 - 130 + 2943 + 2187 + 288 + 117 *ResearchableTech* bg=pink @@ -229,10 +229,10 @@ condition : set(LogicElement) UMLClass - 1330 - 3710 - 320 - 150 + 1206 + 3348 + 288 + 135 *Terrain* bg=pink @@ -249,10 +249,10 @@ path_costs : dict(PathType, int) UMLClass - 1530 - 3900 - 190 - 80 + 1386 + 3519 + 171 + 72 *TerrainAmbient* bg=pink @@ -265,10 +265,10 @@ max_density : int UMLClass - 1820 - 3250 - 190 - 80 + 1647 + 2934 + 171 + 72 *Sound* bg=pink @@ -281,10 +281,10 @@ sounds : orderedset(file) UMLClass - 1600 - 1250 - 360 - 80 + 1449 + 1134 + 324 + 72 *Modifier* bg=pink @@ -296,10 +296,10 @@ properties : dict(ModifierProperty, ModifierProperty) = {} UMLClass - 1330 - 3530 - 340 - 80 + 1206 + 3186 + 306 + 72 *Patch* bg=pink @@ -312,10 +312,10 @@ patch : NyanPatch UMLClass - 1820 - 3510 - 210 - 80 + 1647 + 3168 + 189 + 72 *Mod* bg=pink @@ -328,10 +328,10 @@ patches : orderedset(Patch) Relation - 1280 - 3620 - 2550 - 30 + 1161 + 3267 + 2295 + 27 lt=<<- 10.0;10.0;2530.0;10.0 @@ -339,10 +339,10 @@ patches : orderedset(Patch) UMLClass - 3810 - 3600 - 340 - 90 + 3438 + 3249 + 306 + 81 *Ability* bg=pink @@ -354,10 +354,10 @@ properties : dict(AbilityProperty, AbilityProperty) = {} UMLClass - 3620 - 4010 - 180 - 80 + 3267 + 3618 + 162 + 72 *Animated* bg=pink @@ -369,10 +369,10 @@ animations : set(Animation) UMLClass - 3620 - 3920 - 180 - 80 + 3267 + 3537 + 162 + 72 *CommandSound* bg=pink @@ -385,10 +385,10 @@ sounds : set(Sound) UMLClass - 4310 - 3840 - 180 - 100 + 3888 + 3465 + 162 + 90 *Move* bg=green @@ -402,10 +402,10 @@ path_type : PathType UMLClass - 4070 - 4280 - 290 - 80 + 3672 + 3861 + 261 + 72 *Formation* bg=green @@ -417,10 +417,10 @@ formations : set(GameEntityFormation) UMLClass - 4860 - 4320 - 180 - 80 + 4383 + 3897 + 162 + 72 *RegenerateAttribute* bg=green @@ -432,10 +432,10 @@ rate : AttributeRate UMLClass - 5960 - 1950 - 320 - 340 + 5373 + 1764 + 288 + 279 *ShootProjectile* bg=green @@ -444,8 +444,6 @@ bg=green projectiles : orderedset(GameEntity) min_projectiles : int max_projectiles : int -min_range : int -max_range : int // time until the ability can be used again reload_time : float // time to wait between the start of the ability @@ -469,10 +467,10 @@ blacklisted_entities : set(GameEntity) UMLClass - 4830 - 4520 - 210 - 80 + 4356 + 4077 + 189 + 72 *Resistance* bg=green @@ -484,10 +482,10 @@ resistances : set(Resistance) UMLClass - 4090 - 3840 - 160 - 80 + 3690 + 3465 + 144 + 72 *Fly* bg=green @@ -499,10 +497,10 @@ height : float Relation - 510 - 2850 - 750 - 770 + 468 + 2574 + 675 + 693 lt=<<- 730.0;750.0;730.0;10.0;10.0;10.0 @@ -510,10 +508,10 @@ height : float UMLClass - 2110 - 3060 - 190 - 80 + 1908 + 2763 + 171 + 72 *Cheat* bg=pink @@ -526,10 +524,10 @@ changes : orderedset(Patch) Relation - 1080 - 3340 - 180 - 30 + 981 + 3015 + 162 + 27 lt=- 160.0;10.0;10.0;10.0 @@ -537,10 +535,10 @@ changes : orderedset(Patch) Relation - 990 - 3230 - 30 - 90 + 900 + 2916 + 27 + 81 lt=<. 10.0;70.0;10.0;10.0 @@ -548,10 +546,10 @@ changes : orderedset(Patch) Relation - 1140 - 3150 - 120 - 30 + 1035 + 2844 + 108 + 27 lt=- 10.0;10.0;100.0;10.0 @@ -559,10 +557,10 @@ changes : orderedset(Patch) Relation - 1580 - 3850 - 30 - 70 + 1431 + 3474 + 27 + 63 lt=<. 10.0;50.0;10.0;10.0 @@ -570,10 +568,10 @@ changes : orderedset(Patch) Relation - 2040 - 3180 - 90 - 30 + 1845 + 2871 + 81 + 27 lt=- 10.0;10.0;70.0;10.0 @@ -581,10 +579,10 @@ changes : orderedset(Patch) Relation - 1470 - 3620 - 30 - 110 + 1332 + 3267 + 27 + 99 lt=- 10.0;10.0;10.0;90.0 @@ -592,10 +590,10 @@ changes : orderedset(Patch) UMLClass - 6560 - 3590 - 290 - 110 + 5913 + 3240 + 261 + 99 *PassiveTransformTo* bg=green @@ -610,10 +608,10 @@ transform_progress : set(Progress) UMLClass - 4570 - 2890 - 300 - 140 + 4122 + 2610 + 270 + 126 *Harvestable* bg=green @@ -629,10 +627,10 @@ harvestable_by_default : bool UMLClass - 4950 - 4630 - 240 - 80 + 4464 + 4176 + 216 + 72 *Live* bg=green @@ -644,10 +642,10 @@ attributes : set(AttributeSetting) UMLClass - 2110 - 3150 - 250 - 100 + 1908 + 2844 + 225 + 90 *Taunt* bg=pink @@ -661,10 +659,10 @@ sound : Sound Relation - 1910 - 3580 - 30 - 70 + 1728 + 3231 + 27 + 63 lt=- 10.0;10.0;10.0;50.0 @@ -672,10 +670,10 @@ sound : Sound Relation - 1770 - 1320 - 30 - 2330 + 1602 + 1197 + 27 + 2097 lt=- 10.0;10.0;10.0;2310.0 @@ -683,10 +681,10 @@ sound : Sound Relation - 1470 - 3600 - 30 - 50 + 1332 + 3249 + 27 + 45 lt=- 10.0;10.0;10.0;30.0 @@ -694,10 +692,10 @@ sound : Sound Relation - 2040 - 3090 - 90 - 30 + 1845 + 2790 + 81 + 27 lt=- 70.0;10.0;10.0;10.0 @@ -705,10 +703,10 @@ sound : Sound UMLClass - 6420 - 3740 - 310 - 110 + 5787 + 3375 + 279 + 99 *Despawn* bg=green @@ -723,10 +721,10 @@ state_change : optional(StateChanger) = None UMLClass - 630 - 4380 - 310 - 160 + 576 + 3951 + 279 + 144 *PlayerSetup* bg=pink @@ -744,10 +742,10 @@ game_setup : orderedset(Patch) Relation - 770 - 3620 - 30 - 780 + 702 + 3267 + 27 + 702 lt=- 10.0;760.0;10.0;10.0 @@ -755,10 +753,10 @@ game_setup : orderedset(Patch) Relation - 1720 - 3620 - 30 - 420 + 1557 + 3267 + 27 + 378 lt=- 10.0;10.0;10.0;400.0 @@ -766,10 +764,10 @@ game_setup : orderedset(Patch) UMLClass - 6420 - 3670 - 120 - 60 + 5787 + 3312 + 108 + 54 *Idle* @@ -779,10 +777,10 @@ bg=green UMLNote - 1550 - 1350 - 210 - 160 + 1404 + 1224 + 189 + 144 Modifier should only be used in cases where Patches don't @@ -798,10 +796,10 @@ bg=blue UMLClass - 1660 - 1100 - 220 - 80 + 1503 + 999 + 198 + 72 *ContinuousResource* bg=yellow @@ -813,10 +811,10 @@ rates : set(ResourceRate) UMLClass - 910 - 3430 - 180 - 80 + 828 + 3096 + 162 + 72 *ResourceAmount* bg=pink @@ -829,10 +827,10 @@ amount : int UMLClass - 530 - 3410 - 120 - 60 + 486 + 3078 + 108 + 54 *GoldAmount* @@ -841,10 +839,10 @@ amount : int UMLClass - 530 - 3480 - 120 - 60 + 486 + 3141 + 108 + 54 *StoneAmount* @@ -853,10 +851,10 @@ amount : int UMLClass - 530 - 3340 - 120 - 60 + 486 + 3015 + 108 + 54 *WoodAmount* @@ -865,10 +863,10 @@ amount : int UMLClass - 530 - 3270 - 120 - 60 + 486 + 2952 + 108 + 54 *FoodAmount* @@ -877,10 +875,10 @@ amount : int Relation - 1080 - 3460 - 180 - 30 + 981 + 3123 + 162 + 27 lt=- 160.0;10.0;10.0;10.0 @@ -888,10 +886,10 @@ amount : int Relation - 990 - 3370 - 30 - 80 + 900 + 3042 + 27 + 72 lt=<. 10.0;10.0;10.0;60.0 @@ -899,10 +897,10 @@ amount : int Relation - 660 - 3460 - 270 - 30 + 603 + 3123 + 243 + 27 lt=<<- 250.0;10.0;10.0;10.0 @@ -910,10 +908,10 @@ amount : int Relation - 640 - 3290 - 50 - 100 + 585 + 2970 + 45 + 90 lt=- 10.0;10.0;30.0;10.0;30.0;80.0 @@ -921,10 +919,10 @@ amount : int Relation - 640 - 3360 - 50 - 100 + 585 + 3033 + 45 + 90 lt=- 10.0;10.0;30.0;10.0;30.0;80.0 @@ -932,10 +930,10 @@ amount : int Relation - 640 - 3450 - 50 - 80 + 585 + 3114 + 45 + 72 lt=- 10.0;60.0;30.0;60.0;30.0;10.0 @@ -943,10 +941,10 @@ amount : int Relation - 640 - 3430 - 50 - 50 + 585 + 3096 + 45 + 45 lt=- 10.0;10.0;30.0;10.0;30.0;30.0 @@ -954,10 +952,10 @@ amount : int Relation - 1770 - 1170 - 30 - 100 + 1602 + 1062 + 27 + 90 lt=<<- 10.0;80.0;10.0;10.0 @@ -965,10 +963,10 @@ amount : int UMLClass - 1630 - 980 - 120 - 60 + 1476 + 891 + 108 + 54 *FeitoriaBonus* @@ -977,10 +975,10 @@ amount : int Relation - 1680 - 1030 - 120 - 90 + 1521 + 936 + 108 + 81 lt=<<- 100.0;70.0;100.0;40.0;10.0;40.0;10.0;10.0 @@ -988,10 +986,10 @@ amount : int UMLClass - 970 - 1010 - 190 - 80 + 882 + 918 + 171 + 72 *AttributeSettingsValue* bg=yellow @@ -1003,10 +1001,10 @@ attribute : Attribute UMLClass - 1020 - 940 - 140 - 60 + 927 + 855 + 126 + 54 *MoveSpeed* @@ -1016,10 +1014,10 @@ bg=yellow UMLClass - 940 - 690 - 220 - 80 + 855 + 630 + 198 + 72 *GatheringRate* bg=yellow @@ -1031,10 +1029,10 @@ resource_spot : ResourceSpot Relation - 1200 - 480 - 600 - 760 + 1089 + 441 + 540 + 684 lt=- 580.0;740.0;10.0;740.0;10.0;10.0 @@ -1042,10 +1040,10 @@ resource_spot : ResourceSpot Relation - 1150 - 960 - 80 - 30 + 1044 + 873 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -1053,10 +1051,10 @@ resource_spot : ResourceSpot Relation - 1150 - 720 - 80 - 30 + 1044 + 657 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -1064,10 +1062,10 @@ resource_spot : ResourceSpot UMLClass - 1810 - 980 - 120 - 60 + 1638 + 891 + 108 + 54 *RelicBonus* @@ -1076,10 +1074,10 @@ resource_spot : ResourceSpot Relation - 1770 - 1030 - 120 - 60 + 1602 + 936 + 108 + 54 lt=- 10.0;40.0;100.0;40.0;100.0;10.0 @@ -1087,10 +1085,10 @@ resource_spot : ResourceSpot UMLClass - 3610 - 2580 - 280 - 80 + 3258 + 2331 + 252 + 72 *Create* bg=green @@ -1102,10 +1100,10 @@ creatables : set(CreatableGameEntity) UMLClass - 4370 - 2670 - 230 - 80 + 3942 + 2412 + 207 + 72 *Trade* bg=green @@ -1119,10 +1117,10 @@ container : ResourceContainer UMLClass - 3610 - 2420 - 280 - 80 + 3258 + 2187 + 252 + 72 *Research* bg=green @@ -1134,10 +1132,10 @@ researchables : set(ResearchableTech) UMLClass - 370 - 3770 - 150 - 80 + 342 + 3402 + 135 + 72 *RandomVariant* @@ -1149,10 +1147,10 @@ chance_share : float Relation - 750 - 3800 - 50 - 30 + 684 + 3429 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -1160,10 +1158,10 @@ chance_share : float UMLClass - 4620 - 1640 - 260 - 90 + 4167 + 1485 + 234 + 81 *Storage* bg=green @@ -1177,10 +1175,10 @@ empty_condition : set(LogicElement) UMLClass - 4980 - 1480 - 310 - 120 + 4491 + 1341 + 279 + 108 *StorageElementDefinition* bg=pink @@ -1195,10 +1193,10 @@ state_change : optional(StateChanger) = None Relation - 4740 - 1600 - 30 - 60 + 4275 + 1449 + 27 + 54 lt=<. 10.0;10.0;10.0;40.0 @@ -1206,10 +1204,10 @@ state_change : optional(StateChanger) = None UMLClass - 4420 - 1970 - 300 - 90 + 3987 + 1782 + 270 + 81 *CollectStorage* bg=green @@ -1222,10 +1220,10 @@ storage_elements : set(GameEntity) UMLClass - 4420 - 1870 - 300 - 90 + 3987 + 1692 + 270 + 81 *RemoveStorage* bg=green @@ -1238,10 +1236,10 @@ storage_elements : set(GameEntity) UMLNote - 720 - 4550 - 220 - 80 + 657 + 4104 + 198 + 72 game_setup patches the unique features into the objects @@ -1253,10 +1251,10 @@ bg=blue UMLClass - 2030 - 4780 - 370 - 110 + 1836 + 4311 + 333 + 99 *Progress* bg=pink @@ -1272,10 +1270,10 @@ right_boundary : float Relation - 2140 - 3620 - 30 - 1180 + 1935 + 3267 + 27 + 1062 lt=- 10.0;10.0;10.0;1160.0 @@ -1283,10 +1281,10 @@ right_boundary : float UMLNote - 2160 - 4680 - 190 - 90 + 1953 + 4221 + 171 + 81 Stores what happens after a percentage of @@ -1299,10 +1297,10 @@ bg=blue UMLNote - 2580 - 4220 - 200 - 90 + 2331 + 3807 + 180 + 81 In AoE2 there is only one HarvestProgress State @@ -1314,10 +1312,10 @@ bg=blue UMLNote - 6290 - 2050 - 240 - 90 + 5670 + 1854 + 216 + 81 If min_projectiles is greater than the number of Projectiles in @@ -1329,10 +1327,10 @@ bg=blue UMLNote - 1770 - 3760 - 130 - 120 + 1602 + 3393 + 117 + 108 Abilities and StorageElements @@ -1347,10 +1345,10 @@ bg=blue UMLNote - 4930 - 3460 - 210 - 100 + 4446 + 3123 + 189 + 90 Villager Gather abilities can override the graphics of @@ -1363,10 +1361,10 @@ bg=blue UMLClass - 2110 - 3330 - 190 - 80 + 1908 + 3006 + 171 + 72 *Animation* bg=pink @@ -1378,10 +1376,10 @@ sprite : file Relation - 2000 - 3280 - 70 - 30 + 1809 + 2961 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -1389,10 +1387,10 @@ sprite : file UMLClass - 440 - 240 - 250 - 90 + 405 + 225 + 225 + 81 *Flyover* bg=yellow @@ -1406,10 +1404,10 @@ blacklisted_entities : set(GameEntity) Relation - 710 - 320 - 370 - 30 + 648 + 297 + 333 + 27 lt=- 350.0;10.0;10.0;10.0 @@ -1417,10 +1415,10 @@ blacklisted_entities : set(GameEntity) UMLClass - 370 - 340 - 320 - 70 + 342 + 315 + 288 + 63 *ElevationDifferenceLow* bg=yellow @@ -1432,10 +1430,10 @@ min_elevation_difference : optional(float) = None Relation - 680 - 270 - 60 - 30 + 621 + 252 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -1443,10 +1441,10 @@ min_elevation_difference : optional(float) = None Relation - 680 - 370 - 60 - 30 + 621 + 342 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -1454,10 +1452,10 @@ min_elevation_difference : optional(float) = None UMLNote - 0 - 100 - 190 - 70 + 9 + 99 + 171 + 63 Pink elements: @@ -1469,10 +1467,10 @@ bg=pink UMLNote - 0 - 180 - 190 - 70 + 9 + 171 + 171 + 63 Green elements: @@ -1484,10 +1482,10 @@ bg=green UMLNote - 0 - 260 - 190 - 70 + 9 + 243 + 171 + 63 Yellow elements: @@ -1499,10 +1497,10 @@ bg=yellow UMLNote - 0 - 440 - 190 - 70 + 9 + 405 + 171 + 63 White elements: @@ -1512,10 +1510,10 @@ AoE2 specific objects UMLClass - 3770 - 2730 - 120 - 60 + 3402 + 2466 + 108 + 54 *RallyPoint* @@ -1525,10 +1523,10 @@ bg=green UMLClass - 6130 - 3860 - 230 - 90 + 5526 + 3483 + 207 + 81 *Selectable* bg=green @@ -1540,10 +1538,10 @@ selection_box : SelectionBox UMLClass - 3770 - 4490 - 330 - 100 + 3402 + 4050 + 297 + 90 *ActiveTransformTo* bg=green @@ -1557,10 +1555,10 @@ transform_progress : set(Progress) UMLClass - 550 - 3770 - 210 - 80 + 504 + 3402 + 189 + 72 *Variant* bg=pink @@ -1573,10 +1571,10 @@ priority : int Relation - 510 - 3800 - 60 - 30 + 468 + 3429 + 54 + 27 lt=<<- 40.0;10.0;10.0;10.0 @@ -1584,10 +1582,10 @@ priority : int UMLClass - 1570 - 4260 - 210 - 80 + 1422 + 3843 + 189 + 72 *TechResearched* bg=pink @@ -1599,10 +1597,10 @@ tech : Tech UMLClass - 1550 - 4350 - 230 - 90 + 1404 + 3924 + 207 + 81 *GameEntityProgress* bg=pink @@ -1615,10 +1613,10 @@ status : ProgressStatus Relation - 1790 - 4220 - 30 - 900 + 1620 + 3807 + 27 + 810 lt=<<- 10.0;10.0;10.0;880.0 @@ -1626,10 +1624,10 @@ status : ProgressStatus UMLClass - 4460 - 1760 - 260 - 100 + 4023 + 1593 + 234 + 90 *TransferStorage* bg=green @@ -1643,10 +1641,10 @@ target_container : EntityContainer Relation - 1720 - 3680 - 70 - 30 + 1557 + 3321 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -1654,10 +1652,10 @@ target_container : EntityContainer UMLClass - 4780 - 1970 - 320 - 90 + 4311 + 1782 + 288 + 81 *SendBackToTask* bg=green @@ -1670,10 +1668,10 @@ blacklisted_entities : set(GameEntity) UMLClass - 4070 - 4370 - 250 - 80 + 3672 + 3942 + 225 + 72 *GameEntityStance* bg=green @@ -1685,10 +1683,10 @@ stances: set(GameEntityStance) UMLClass - 4400 - 4380 - 340 - 90 + 3969 + 3951 + 306 + 81 *GameEntityStance* bg=pink @@ -1702,10 +1700,10 @@ type_preference : orderedset(GameEntityType) Relation - 4310 - 4400 - 110 - 30 + 3888 + 3969 + 99 + 27 lt=<. 90.0;10.0;10.0;10.0 @@ -1713,10 +1711,10 @@ type_preference : orderedset(GameEntityType) UMLClass - 4600 - 3840 - 110 - 60 + 4149 + 3465 + 99 + 54 *Patrol* @@ -1726,10 +1724,10 @@ bg=pink UMLClass - 4600 - 3910 - 160 - 80 + 4149 + 3528 + 144 + 72 *Follow* bg=pink @@ -1741,10 +1739,10 @@ range : float Relation - 4550 - 3800 - 30 - 410 + 4104 + 3429 + 27 + 369 lt=<<- 10.0;10.0;10.0;390.0 @@ -1752,10 +1750,10 @@ range : float Relation - 4550 - 3940 - 70 - 30 + 4104 + 3555 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -1763,10 +1761,10 @@ range : float UMLClass - 4370 - 2580 - 290 - 80 + 3942 + 2331 + 261 + 72 *TradePost* bg=green @@ -1778,10 +1776,10 @@ trade_routes : set(TradeRoute) UMLClass - 4360 - 4490 - 100 - 60 + 3933 + 4050 + 90 + 54 *Aggressive* @@ -1791,10 +1789,10 @@ bg=pink UMLClass - 4360 - 4560 - 100 - 60 + 3933 + 4113 + 90 + 54 *Defensive* @@ -1804,10 +1802,10 @@ bg=pink UMLClass - 4340 - 4630 - 120 - 60 + 3915 + 4176 + 108 + 54 *StandGround* @@ -1817,10 +1815,10 @@ bg=pink UMLClass - 4360 - 4700 - 100 - 60 + 3933 + 4239 + 90 + 54 *Passive* @@ -1830,10 +1828,10 @@ bg=pink Relation - 4450 - 4580 - 60 - 30 + 4014 + 4131 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -1841,10 +1839,10 @@ bg=pink Relation - 4480 - 4460 - 30 - 290 + 4041 + 4023 + 27 + 261 lt=<<- 10.0;10.0;10.0;270.0 @@ -1852,10 +1850,10 @@ bg=pink Relation - 4450 - 4650 - 60 - 30 + 4014 + 4194 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -1863,10 +1861,10 @@ bg=pink Relation - 4450 - 4720 - 60 - 30 + 4014 + 4257 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -1874,10 +1872,10 @@ bg=pink UMLClass - 4600 - 3040 - 270 - 140 + 4149 + 2745 + 243 + 126 *Restock* bg=green @@ -1894,10 +1892,10 @@ amount : int UMLClass - 370 - 3860 - 170 - 80 + 342 + 3483 + 153 + 72 *PerspectiveVariant* @@ -1909,10 +1907,10 @@ angle : int Relation - 530 - 3840 - 140 - 80 + 486 + 3465 + 126 + 72 lt=<<- 120.0;10.0;120.0;60.0;10.0;60.0 @@ -1920,10 +1918,10 @@ angle : int UMLClass - 6010 - 4760 - 220 - 80 + 5418 + 4293 + 198 + 72 *Foundation* bg=green @@ -1935,10 +1933,10 @@ foundation_terrain : Terrain UMLClass - 5100 - 4320 - 220 - 80 + 4599 + 3897 + 198 + 72 *Pathable* bg=green @@ -1951,10 +1949,10 @@ path_costs : dict(PathType, int) UMLClass - 6420 - 3530 - 120 - 60 + 5787 + 3186 + 108 + 54 // Returns unit to Idle state @@ -1965,10 +1963,10 @@ bg=green UMLClass - 6420 - 3930 - 320 - 100 + 5787 + 3546 + 288 + 90 *Herdable* bg=green @@ -1981,10 +1979,10 @@ mode : HerdableMode UMLClass - 4930 - 3220 - 260 - 110 + 4446 + 2907 + 234 + 99 *DropResources* bg=green @@ -2000,10 +1998,10 @@ blacklisted_entities : set(GameEntity) UMLClass - 4750 - 4210 - 290 - 100 + 4284 + 3798 + 261 + 90 *Named* bg=green @@ -2017,10 +2015,10 @@ long_description : TranslatedMarkupFile UMLClass - 5100 - 4420 - 220 - 80 + 4599 + 3987 + 198 + 72 *Collision* bg=green @@ -2032,10 +2030,10 @@ hitbox : Hitbox UMLClass - 5700 - 4760 - 250 - 80 + 5139 + 4293 + 225 + 72 *AttributeChangeTracker* bg=green @@ -2048,10 +2046,10 @@ change_progress : set(Progress) UMLClass - 5960 - 1780 - 320 - 150 + 5373 + 1611 + 288 + 135 *Projectile* bg=green @@ -2070,10 +2068,10 @@ unignore_entities : set(GameEntity) UMLClass - 1610 - 4150 - 210 - 80 + 1458 + 3744 + 189 + 72 *Literal* bg=pink @@ -2085,10 +2083,10 @@ scope : LiteralScope UMLClass - 700 - 3300 - 180 - 80 + 639 + 2979 + 162 + 72 *ResourceContingent* bg=pink @@ -2101,10 +2099,10 @@ max_amount : int Relation - 870 - 3330 - 60 - 30 + 792 + 3006 + 54 + 27 lt=<<- 40.0;10.0;10.0;10.0 @@ -2112,10 +2110,10 @@ max_amount : int UMLClass - 4930 - 2950 - 250 - 80 + 4446 + 2664 + 225 + 72 *ProvideContingent* bg=green @@ -2127,10 +2125,10 @@ amount : set(ResourceAmount) UMLClass - 4930 - 3040 - 250 - 80 + 4446 + 2745 + 225 + 72 *UseContingent* bg=green @@ -2142,10 +2140,10 @@ amount : set(ResourceAmount) UMLClass - 3260 - 2560 - 320 - 160 + 2943 + 2313 + 288 + 144 *CreatableGameEntity* bg=pink @@ -2164,10 +2162,10 @@ placement_modes : set(PlacementMode) Relation - 3570 - 2610 - 60 - 30 + 3222 + 2358 + 54 + 27 lt=<. 10.0;10.0;40.0;10.0 @@ -2175,10 +2173,10 @@ placement_modes : set(PlacementMode) UMLClass - 2110 - 3510 - 190 - 80 + 1908 + 3168 + 171 + 72 *Terrain* bg=pink @@ -2190,10 +2188,10 @@ sprite : file Relation - 2040 - 3090 - 30 - 560 + 1845 + 2790 + 27 + 504 lt=- 10.0;10.0;10.0;540.0 @@ -2201,10 +2199,10 @@ sprite : file Relation - 2040 - 3360 - 90 - 30 + 1845 + 3033 + 81 + 27 lt=- 70.0;10.0;10.0;10.0 @@ -2212,10 +2210,10 @@ sprite : file UMLClass - 2460 - 4660 - 230 - 80 + 2223 + 4203 + 207 + 72 *Animated* bg=pink @@ -2228,10 +2226,10 @@ overrides : set(AnimationOverride) UMLClass - 2460 - 4840 - 190 - 80 + 2223 + 4365 + 171 + 72 *TerrainOverlay* bg=pink @@ -2245,10 +2243,10 @@ terrain_overlay : Terrain Relation - 2420 - 4690 - 60 - 30 + 2187 + 4230 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -2256,10 +2254,10 @@ terrain_overlay : Terrain Relation - 2420 - 4870 - 60 - 30 + 2187 + 4392 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -2267,10 +2265,10 @@ terrain_overlay : Terrain UMLClass - 4960 - 4770 - 220 - 110 + 4473 + 4302 + 198 + 99 *AttributeSetting* bg=pink @@ -2285,10 +2283,10 @@ starting_value : int UMLClass - 4960 - 5050 - 220 - 80 + 4473 + 4554 + 198 + 72 *ProtectingAttribute* bg=pink @@ -2300,10 +2298,10 @@ protects : Attribute Relation - 5060 - 4700 - 30 - 90 + 4563 + 4239 + 27 + 81 lt=<. 10.0;70.0;10.0;10.0 @@ -2311,10 +2309,10 @@ protects : Attribute Relation - 5060 - 5010 - 30 - 60 + 4563 + 4518 + 27 + 54 lt=<<- 10.0;10.0;10.0;40.0 @@ -2322,10 +2320,10 @@ protects : Attribute UMLClass - 5000 - 5170 - 150 - 60 + 4509 + 4662 + 135 + 54 *Shield (SWGB)* @@ -2334,10 +2332,10 @@ protects : Attribute Relation - 5060 - 5120 - 30 - 70 + 4563 + 4617 + 27 + 63 lt=<<- 10.0;10.0;10.0;50.0 @@ -2345,10 +2343,10 @@ protects : Attribute UMLClass - 5240 - 4940 - 100 - 60 + 4725 + 4455 + 90 + 54 *Faith* @@ -2357,10 +2355,10 @@ protects : Attribute Relation - 5180 - 4950 - 80 - 30 + 4671 + 4464 + 72 + 27 lt=<<- 10.0;10.0;60.0;10.0 @@ -2368,10 +2366,10 @@ protects : Attribute UMLClass - 4860 - 4410 - 180 - 80 + 4383 + 3978 + 162 + 72 *LineOfSight* bg=green @@ -2383,10 +2381,10 @@ range : float UMLClass - 5100 - 4210 - 210 - 80 + 4599 + 3798 + 189 + 72 *Cloak (SWGB)* bg=green @@ -2399,10 +2397,10 @@ interrupt_cooldown : float UMLClass - 6330 - 1740 - 270 - 130 + 5706 + 1575 + 243 + 117 *Accuracy* bg=pink @@ -2419,10 +2417,10 @@ blacklisted_entities : set(GameEntity) Relation - 6270 - 1780 - 80 - 30 + 5652 + 1611 + 72 + 27 lt=<. 60.0;10.0;10.0;10.0 @@ -2430,10 +2428,10 @@ blacklisted_entities : set(GameEntity) UMLClass - 4720 - 4890 - 160 - 80 + 4257 + 4410 + 144 + 72 *AttributeAmount* bg=pink @@ -2446,10 +2444,10 @@ amount : int Relation - 4870 - 4940 - 100 - 30 + 4392 + 4455 + 90 + 27 lt=<. 80.0;10.0;10.0;10.0 @@ -2457,10 +2455,10 @@ amount : int UMLClass - 750 - 3550 - 140 - 60 + 684 + 3204 + 126 + 54 *GameEntityType* @@ -2470,10 +2468,10 @@ bg=pink Relation - 700 - 3580 - 70 - 30 + 639 + 3231 + 63 + 27 lt=<. 50.0;10.0;10.0;10.0 @@ -2481,10 +2479,10 @@ bg=pink UMLClass - 5240 - 5010 - 100 - 60 + 4725 + 4518 + 90 + 54 *Health* @@ -2493,10 +2491,10 @@ bg=pink Relation - 5210 - 4950 - 50 - 110 + 4698 + 4464 + 45 + 99 lt=- 10.0;10.0;10.0;90.0;30.0;90.0 @@ -2504,10 +2502,10 @@ bg=pink Relation - 810 - 3600 - 30 - 50 + 738 + 3249 + 27 + 45 lt=- 10.0;30.0;10.0;10.0 @@ -2515,10 +2513,10 @@ bg=pink Relation - 960 - 3620 - 30 - 2130 + 873 + 3267 + 27 + 1917 lt=- 10.0;2110.0;10.0;10.0 @@ -2526,10 +2524,10 @@ bg=pink Relation - 960 - 5720 - 420 - 60 + 873 + 5157 + 378 + 54 lt=- 10.0;10.0;400.0;10.0;400.0;40.0 @@ -2537,10 +2535,10 @@ bg=pink UMLClass - 1180 - 5760 - 390 - 80 + 1071 + 5193 + 351 + 72 *Resistance* bg=pink @@ -2552,10 +2550,10 @@ properties : dict(ResistanceProperty, ResistanceProperty) = {} UMLClass - 400 - 5760 - 330 - 80 + 369 + 5193 + 297 + 72 *Effect* bg=pink @@ -2567,10 +2565,10 @@ properties : dict(EffectProperty, EffectProperty) = {} Relation - 560 - 5720 - 430 - 60 + 513 + 5157 + 387 + 54 lt=- 410.0;10.0;10.0;10.0;10.0;40.0 @@ -2578,10 +2576,10 @@ properties : dict(EffectProperty, EffectProperty) = {} UMLNote - 1210 - 5680 - 140 - 40 + 1098 + 5121 + 126 + 36 Resistors' side bg=blue @@ -2590,10 +2588,10 @@ bg=blue UMLNote - 570 - 5680 - 140 - 40 + 522 + 5121 + 126 + 36 Effectors' side bg=blue @@ -2602,10 +2600,10 @@ bg=blue UMLClass - 670 - 5990 - 350 - 130 + 612 + 5400 + 315 + 117 *FlatAttributeChange* bg=pink @@ -2621,10 +2619,10 @@ ignore_protection : set(ProtectingAttribute) UMLClass - 1480 - 5990 - 250 - 90 + 1341 + 5400 + 225 + 81 *FlatAttributeChange* bg=pink @@ -2637,10 +2635,10 @@ block_value : AttributeAmount UMLClass - 1040 - 4770 - 190 - 60 + 945 + 4302 + 171 + 54 *AttributeChangeType* @@ -2650,10 +2648,10 @@ bg=pink UMLClass - 670 - 6390 - 300 - 130 + 612 + 5760 + 270 + 117 *Convert* bg=orange @@ -2669,10 +2667,10 @@ cost_fail : optional(Cost) = None UMLClass - 1480 - 6390 - 290 - 130 + 1341 + 5760 + 261 + 117 *Convert* bg=orange @@ -2685,10 +2683,10 @@ chance_resist : float UMLClass - 740 - 6530 - 240 - 90 + 675 + 5886 + 216 + 81 *AoE2Convert* bg=orange @@ -2701,10 +2699,10 @@ skip_protected_rounds : int UMLClass - 1530 - 6530 - 290 - 100 + 1386 + 5886 + 261 + 90 *AoE2Convert* bg=orange @@ -2723,10 +2721,10 @@ protection_round_recharge_time : float UMLClass - 320 - 5890 - 180 - 60 + 297 + 5310 + 162 + 54 *ContinuousEffect* @@ -2737,10 +2735,10 @@ bg=orange UMLClass - 170 - 5990 - 330 - 130 + 162 + 5400 + 297 + 117 *FlatAttributeChange* bg=pink @@ -2756,10 +2754,10 @@ ignore_protection : set(ProtectingAttribute) UMLClass - 670 - 5890 - 170 - 60 + 612 + 5310 + 153 + 54 *DiscreteEffect* @@ -2769,10 +2767,10 @@ bg=orange UMLClass - 1480 - 5890 - 180 - 60 + 1341 + 5310 + 162 + 54 *DiscreteResistance* @@ -2782,10 +2780,10 @@ bg=orange UMLClass - 910 - 3520 - 180 - 80 + 828 + 3177 + 162 + 72 *ResourceRate* bg=pink @@ -2798,10 +2796,10 @@ rate : float Relation - 1080 - 3550 - 180 - 30 + 981 + 3204 + 162 + 27 lt=- 160.0;10.0;10.0;10.0 @@ -2809,10 +2807,10 @@ rate : float UMLClass - 4720 - 4980 - 160 - 80 + 4257 + 4491 + 144 + 72 *AttributeRate* bg=pink @@ -2825,10 +2823,10 @@ rate : float Relation - 560 - 5830 - 30 - 60 + 513 + 5256 + 27 + 54 lt=<<- 10.0;10.0;10.0;40.0 @@ -2836,10 +2834,10 @@ rate : float Relation - 400 - 5860 - 190 - 50 + 369 + 5283 + 171 + 45 lt=- 170.0;10.0;10.0;10.0;10.0;30.0 @@ -2847,10 +2845,10 @@ rate : float Relation - 560 - 5860 - 210 - 50 + 513 + 5283 + 189 + 45 lt=- 10.0;10.0;190.0;10.0;190.0;30.0 @@ -2858,10 +2856,10 @@ rate : float Relation - 1170 - 5860 - 200 - 50 + 1062 + 5283 + 180 + 45 lt=- 180.0;10.0;10.0;10.0;10.0;30.0 @@ -2869,10 +2867,10 @@ rate : float Relation - 1350 - 5830 - 30 - 60 + 1224 + 5256 + 27 + 54 lt=<<- 10.0;10.0;10.0;40.0 @@ -2880,10 +2878,10 @@ rate : float Relation - 1340 - 5860 - 240 - 50 + 1215 + 5283 + 216 + 45 lt=- 10.0;10.0;220.0;10.0;220.0;30.0 @@ -2891,10 +2889,10 @@ rate : float UMLClass - 1090 - 5890 - 180 - 60 + 990 + 5310 + 162 + 54 *ContinuousResistance* @@ -2904,10 +2902,10 @@ bg=orange Relation - 960 - 4790 - 100 - 30 + 873 + 4320 + 90 + 27 lt=- 10.0;10.0;80.0;10.0 @@ -2915,10 +2913,10 @@ bg=orange UMLClass - 1060 - 5990 - 210 - 90 + 963 + 5400 + 189 + 81 *FlatAttributeChange* bg=pink @@ -2931,10 +2929,10 @@ block_rate : AttributeRate Relation - 630 - 5910 - 60 - 140 + 576 + 5328 + 54 + 126 lt=<<- 40.0;10.0;10.0;10.0;10.0;120.0;40.0;120.0 @@ -2942,10 +2940,10 @@ block_rate : AttributeRate Relation - 490 - 5910 - 60 - 140 + 450 + 5328 + 54 + 126 lt=<<- 10.0;10.0;40.0;10.0;40.0;120.0;10.0;120.0 @@ -2953,10 +2951,10 @@ block_rate : AttributeRate Relation - 630 - 6320 - 60 - 130 + 576 + 5697 + 54 + 117 lt=- 10.0;10.0;10.0;110.0;40.0;110.0 @@ -2964,10 +2962,10 @@ block_rate : AttributeRate Relation - 710 - 6510 - 50 - 80 + 648 + 5868 + 45 + 72 lt=<<- 10.0;10.0;10.0;60.0;30.0;60.0 @@ -2975,10 +2973,10 @@ block_rate : AttributeRate Relation - 1500 - 6510 - 50 - 80 + 1359 + 5868 + 45 + 72 lt=<<- 10.0;10.0;10.0;60.0;30.0;60.0 @@ -2986,10 +2984,10 @@ block_rate : AttributeRate Relation - 1440 - 6020 - 60 - 330 + 1305 + 5427 + 54 + 297 lt=- 10.0;10.0;10.0;310.0;40.0;310.0 @@ -2997,10 +2995,10 @@ block_rate : AttributeRate Relation - 1440 - 5910 - 60 - 140 + 1305 + 5328 + 54 + 126 lt=<<- 40.0;10.0;10.0;10.0;10.0;120.0;40.0;120.0 @@ -3008,10 +3006,10 @@ block_rate : AttributeRate Relation - 1260 - 5910 - 60 - 140 + 1143 + 5328 + 54 + 126 lt=<<- 10.0;10.0;40.0;10.0;40.0;120.0;10.0;120.0 @@ -3019,10 +3017,10 @@ block_rate : AttributeRate UMLClass - 6220 - 2740 - 320 - 140 + 5607 + 2475 + 288 + 126 *ApplyDiscreteEffect* bg=green @@ -3042,10 +3040,10 @@ blacklisted_entities : set(GameEntity) UMLClass - 6220 - 2560 - 320 - 120 + 5607 + 2313 + 288 + 108 *ApplyContinuousEffect* bg=green @@ -3062,22 +3060,22 @@ blacklisted_entities : set(GameEntity) UMLClass - 6600 - 2460 - 140 - 60 + 5634 + 2214 + 135 + 54 -*MonkHeal* +*MonkHeal (Ranged)* UMLClass - 6600 - 2590 - 110 - 60 + 5949 + 2340 + 99 + 54 *Repair* @@ -3086,10 +3084,10 @@ blacklisted_entities : set(GameEntity) Relation - 630 - 6020 - 60 - 330 + 576 + 5427 + 54 + 297 lt=- 10.0;10.0;10.0;310.0;40.0;310.0 @@ -3097,10 +3095,10 @@ blacklisted_entities : set(GameEntity) UMLClass - 4600 - 3190 - 270 - 130 + 4149 + 2880 + 243 + 117 *Gather* bg=green @@ -3116,10 +3114,10 @@ container : ResourceContainer UMLClass - 670 - 6290 - 260 - 80 + 612 + 5670 + 234 + 72 *MakeHarvestable* bg=orange @@ -3131,10 +3129,10 @@ resource_spot : ResourceSpot UMLClass - 1480 - 6290 - 340 - 80 + 1341 + 5670 + 306 + 72 *MakeHarvestable* bg=orange @@ -3147,37 +3145,21 @@ resist_condition : set(LogicElement) Relation - 6440 - 2480 - 180 - 30 + 5688 + 2259 + 27 + 72 lt=<<- - 10.0;10.0;160.0;10.0 - - - UMLClass - - 6220 - 2450 - 230 - 80 - - *RangedContinuousEffect* -bg=green - --- -min_range : int -max_range : int - + 10.0;60.0;10.0;10.0 Relation - 6530 - 2610 - 90 - 30 + 5886 + 2358 + 81 + 27 lt=<<- 10.0;10.0;70.0;10.0 @@ -3185,10 +3167,10 @@ max_range : int UMLClass - 6600 - 2750 - 140 - 60 + 5949 + 2484 + 126 + 54 *SelfDestruct* @@ -3198,10 +3180,10 @@ max_range : int Relation - 6530 - 2770 - 90 - 30 + 5886 + 2502 + 81 + 27 lt=<<- 10.0;10.0;70.0;10.0 @@ -3209,22 +3191,22 @@ max_range : int UMLClass - 6260 - 3040 - 130 - 60 + 5634 + 2646 + 135 + 54 -*Convert* +*Convert (Ranged)* Relation - 6310 - 2980 - 30 - 80 + 5688 + 2592 + 27 + 72 lt=<<- 10.0;10.0;10.0;60.0 @@ -3232,10 +3214,10 @@ max_range : int UMLClass - 1040 - 4840 - 120 - 60 + 945 + 4365 + 108 + 54 *ConvertType* @@ -3245,10 +3227,10 @@ bg=pink Relation - 960 - 4860 - 100 - 30 + 873 + 4383 + 90 + 27 lt=- 10.0;10.0;80.0;10.0 @@ -3256,10 +3238,10 @@ bg=pink Relation - 1440 - 6320 - 60 - 140 + 1305 + 5697 + 54 + 126 lt=- 10.0;10.0;10.0;120.0;40.0;120.0 @@ -3267,10 +3249,10 @@ bg=pink UMLClass - 6600 - 2820 - 120 - 60 + 5949 + 2547 + 108 + 54 *Hunt* @@ -3279,37 +3261,10 @@ bg=pink UMLClass - 6220 - 2910 - 200 - 80 - - *RangedDiscreteEffect* -bg=green - --- -min_range : int -max_range : int - - - - Relation - - 6310 - 2870 - 30 - 60 - - lt=<<- - 10.0;10.0;10.0;40.0 - - - UMLClass - - 4780 - 1760 - 320 - 100 + 4311 + 1593 + 288 + 90 *EnterContainer* bg=green @@ -3323,10 +3278,10 @@ blacklisted_entities : set(GameEntity) UMLClass - 4780 - 1870 - 260 - 90 + 4311 + 1692 + 234 + 81 *ExitContainer* bg=green @@ -3338,10 +3293,10 @@ allowed_containers : set(EntityContainer) UMLClass - 2110 - 3260 - 160 - 60 + 1908 + 2943 + 144 + 54 *DiplomaticStance* @@ -3351,10 +3306,10 @@ bg=pink Relation - 2040 - 3280 - 90 - 30 + 1845 + 2961 + 81 + 27 lt=- 70.0;10.0;10.0;10.0 @@ -3362,10 +3317,10 @@ bg=pink UMLClass - 3620 - 4100 - 220 - 80 + 3267 + 3699 + 198 + 72 *Diplomatic* bg=pink @@ -3377,10 +3332,10 @@ stances : set(DiplomaticStance) Relation - 1050 - 30 - 180 - 480 + 954 + 36 + 162 + 432 lt=- 160.0;460.0;10.0;460.0;10.0;10.0 @@ -3388,10 +3343,10 @@ stances : set(DiplomaticStance) UMLClass - 2480 - 3320 - 100 - 60 + 2241 + 2997 + 90 + 54 *Self* @@ -3401,10 +3356,10 @@ bg=pink Relation - 2260 - 3280 - 290 - 30 + 2043 + 2961 + 261 + 27 lt=<<- 10.0;10.0;270.0;10.0 @@ -3412,10 +3367,10 @@ bg=pink Relation - 1200 - 200 - 320 - 310 + 1089 + 189 + 288 + 279 lt=- 300.0;10.0;170.0;10.0;170.0;290.0;10.0;290.0 @@ -3423,10 +3378,10 @@ bg=pink UMLClass - 1530 - 180 - 320 - 80 + 1386 + 171 + 288 + 72 *ElevationDifferenceLow* bg=yellow @@ -3438,10 +3393,10 @@ min_elevation_difference : optional(float) = None UMLClass - 230 - 6290 - 270 - 100 + 216 + 5670 + 243 + 90 *Lure* bg=orange @@ -3457,10 +3412,10 @@ min_distance_to_destination : float Relation - 490 - 6020 - 60 - 320 + 450 + 5427 + 54 + 288 lt=- 40.0;10.0;40.0;300.0;10.0;300.0 @@ -3468,10 +3423,10 @@ min_distance_to_destination : float UMLClass - 1020 - 6290 - 250 - 100 + 927 + 5670 + 225 + 90 *Lure* bg=orange @@ -3484,10 +3439,10 @@ type : LureType Relation - 1260 - 6020 - 60 - 320 + 1143 + 5427 + 54 + 288 lt=- 40.0;10.0;40.0;300.0;10.0;300.0 @@ -3495,10 +3450,10 @@ type : LureType UMLClass - 50 - 2540 - 160 - 80 + 54 + 2295 + 144 + 72 *LanguageTextPair* bg=pink @@ -3511,10 +3466,10 @@ string : text UMLClass - 340 - 2540 - 200 - 80 + 315 + 2295 + 180 + 72 *LanguageMarkupPair* bg=pink @@ -3527,10 +3482,10 @@ markup_file : file UMLClass - 660 - 2540 - 180 - 80 + 603 + 2295 + 162 + 72 *LanguageSoundPair* bg=pink @@ -3543,10 +3498,10 @@ sound : Sound Relation - 120 - 2610 - 30 - 80 + 117 + 2358 + 27 + 72 lt=<. 10.0;60.0;10.0;10.0 @@ -3554,10 +3509,10 @@ sound : Sound Relation - 430 - 2610 - 30 - 80 + 396 + 2358 + 27 + 72 lt=<. 10.0;60.0;10.0;10.0 @@ -3565,10 +3520,10 @@ sound : Sound Relation - 730 - 2610 - 30 - 80 + 666 + 2358 + 27 + 72 lt=<. 10.0;60.0;10.0;10.0 @@ -3576,10 +3531,10 @@ sound : Sound UMLClass - 570 - 2900 - 170 - 80 + 522 + 2619 + 153 + 72 *Language* bg=pink @@ -3591,10 +3546,10 @@ ietf_string : text Relation - 640 - 2850 - 30 - 70 + 585 + 2574 + 27 + 63 lt=- 10.0;50.0;10.0;10.0 @@ -3602,10 +3557,10 @@ ietf_string : text UMLClass - 4090 - 3930 - 160 - 80 + 3690 + 3546 + 144 + 72 *Turn* bg=green @@ -3617,10 +3572,10 @@ turn_speed : float UMLClass - 5100 - 4520 - 160 - 80 + 4599 + 4077 + 144 + 72 *Visibility* bg=green @@ -3632,10 +3587,10 @@ visible_in_fog : bool UMLClass - 4570 - 1480 - 350 - 130 + 4122 + 1341 + 315 + 117 *EntityContainer* bg=pink @@ -3651,10 +3606,10 @@ carry_progress : set(Progress) Relation - 4910 - 1530 - 90 - 30 + 4428 + 1386 + 81 + 27 lt=<. 70.0;10.0;10.0;10.0 @@ -3662,10 +3617,10 @@ carry_progress : set(Progress) UMLClass - 1230 - 5280 - 220 - 80 + 1116 + 4761 + 198 + 72 *Cost* bg=pink @@ -3677,10 +3632,10 @@ cost : Cost Relation - 1180 - 5400 - 70 - 30 + 1071 + 4869 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -3688,10 +3643,10 @@ cost : Cost UMLClass - 1770 - 3650 - 240 - 100 + 1602 + 3294 + 216 + 90 *AnimationOverride* bg=pink @@ -3705,10 +3660,10 @@ priority : int UMLClass - 1530 - 3400 - 220 - 80 + 1386 + 3069 + 198 + 72 *Diplomatic* bg=pink @@ -3720,10 +3675,10 @@ stances : set(DiplomaticStance) UMLClass - 740 - 4700 - 160 - 60 + 675 + 4239 + 144 + 54 *DropoffType* @@ -3733,10 +3688,10 @@ bg=pink Relation - 670 - 4720 - 90 - 30 + 612 + 4257 + 81 + 27 lt=<<- 70.0;10.0;10.0;10.0 @@ -3744,10 +3699,10 @@ bg=pink Relation - 670 - 4720 - 60 - 100 + 612 + 4257 + 54 + 90 lt=- 40.0;10.0;40.0;80.0;10.0;80.0 @@ -3755,10 +3710,10 @@ bg=pink Relation - 670 - 4650 - 60 - 100 + 612 + 4194 + 54 + 90 lt=- 40.0;80.0;40.0;10.0;10.0;10.0 @@ -3766,10 +3721,10 @@ bg=pink UMLClass - 520 - 4630 - 160 - 60 + 477 + 4176 + 144 + 54 *NoDropoff* @@ -3779,10 +3734,10 @@ bg=pink UMLClass - 520 - 4700 - 160 - 60 + 477 + 4239 + 144 + 54 *Linear* @@ -3792,10 +3747,10 @@ bg=pink UMLClass - 1280 - 4770 - 120 - 60 + 1161 + 4302 + 108 + 54 // This type is _only_ evaluated if all FlatAttributeChange Effects with other types are outside of the // range defined in the FlatAttributeChange Effect with type Fallback. @@ -3809,10 +3764,10 @@ bg=pink Relation - 1220 - 4790 - 80 - 30 + 1107 + 4320 + 72 + 27 lt=<<- 10.0;10.0;60.0;10.0 @@ -3820,10 +3775,10 @@ bg=pink Relation - 3570 - 2440 - 60 - 30 + 3222 + 2205 + 54 + 27 lt=<. 10.0;10.0;40.0;10.0 @@ -3831,10 +3786,10 @@ bg=pink UMLClass - 1040 - 4100 - 270 - 130 + 945 + 3699 + 243 + 117 *Tech* bg=pink @@ -3850,10 +3805,10 @@ updates : orderedset(Patch) Relation - 1230 - 3650 - 30 - 470 + 1116 + 3294 + 27 + 423 lt=<<- 10.0;10.0;10.0;450.0 @@ -3861,10 +3816,10 @@ updates : orderedset(Patch) UMLClass - 4730 - 2590 - 240 - 100 + 4266 + 2340 + 216 + 90 // Determines traded resource and resource amount *TradeRoute* @@ -3879,10 +3834,10 @@ end_trade_post : GameEntity Relation - 4590 - 2680 - 210 - 60 + 4140 + 2421 + 189 + 54 lt=<. 190.0;10.0;190.0;40.0;10.0;40.0 @@ -3890,10 +3845,10 @@ end_trade_post : GameEntity UMLClass - 5040 - 2590 - 170 - 60 + 4545 + 2340 + 153 + 54 *AoE2TradeRoute* @@ -3904,10 +3859,10 @@ bg=pink UMLClass - 720 - 6140 - 220 - 60 + 657 + 5535 + 198 + 54 *FlatAttributeDecrease* @@ -3917,10 +3872,10 @@ bg=orange UMLClass - 720 - 6210 - 220 - 60 + 657 + 5598 + 198 + 54 *FlatAttributeIncrease* @@ -3930,10 +3885,10 @@ bg=orange Relation - 690 - 6110 - 50 - 80 + 630 + 5508 + 45 + 72 lt=->> 30.0;60.0;10.0;60.0;10.0;10.0 @@ -3941,10 +3896,10 @@ bg=orange Relation - 690 - 6160 - 50 - 100 + 630 + 5553 + 45 + 90 lt=- 30.0;80.0;10.0;80.0;10.0;10.0 @@ -3952,10 +3907,10 @@ bg=orange UMLClass - 1540 - 6140 - 220 - 60 + 1395 + 5535 + 198 + 54 *FlatAttributeDecrease* @@ -3965,10 +3920,10 @@ bg=orange UMLClass - 1540 - 6210 - 220 - 60 + 1395 + 5598 + 198 + 54 *FlatAttributeIncrease* @@ -3978,10 +3933,10 @@ bg=orange Relation - 1500 - 6070 - 30 - 190 + 1359 + 5472 + 27 + 171 lt=<<- 10.0;10.0;10.0;170.0 @@ -3989,10 +3944,10 @@ bg=orange Relation - 1500 - 6160 - 60 - 30 + 1359 + 5553 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -4000,10 +3955,10 @@ bg=orange UMLClass - 230 - 6140 - 220 - 60 + 216 + 5535 + 198 + 54 *FlatAttributeDecrease* @@ -4013,10 +3968,10 @@ bg=orange UMLClass - 230 - 6210 - 220 - 60 + 216 + 5598 + 198 + 54 *FlatAttributeIncrease* @@ -4026,10 +3981,10 @@ bg=orange Relation - 440 - 6160 - 50 - 100 + 405 + 5553 + 45 + 90 lt=- 10.0;80.0;30.0;80.0;30.0;10.0 @@ -4037,10 +3992,10 @@ bg=orange Relation - 440 - 6110 - 50 - 80 + 405 + 5508 + 45 + 72 lt=->> 10.0;60.0;30.0;60.0;30.0;10.0 @@ -4048,10 +4003,10 @@ bg=orange UMLClass - 1020 - 6140 - 220 - 60 + 927 + 5535 + 198 + 54 *FlatAttributeDecrease* @@ -4061,10 +4016,10 @@ bg=orange UMLClass - 1020 - 6210 - 220 - 60 + 927 + 5598 + 198 + 54 *FlatAttributeIncrease* @@ -4074,10 +4029,10 @@ bg=orange Relation - 1230 - 6160 - 50 - 100 + 1116 + 5553 + 45 + 90 lt=- 10.0;80.0;30.0;80.0;30.0;10.0 @@ -4085,10 +4040,10 @@ bg=orange Relation - 1230 - 6070 - 50 - 120 + 1116 + 5472 + 45 + 108 lt=->> 10.0;100.0;30.0;100.0;30.0;10.0 @@ -4096,10 +4051,10 @@ bg=orange UMLClass - 1490 - 3160 - 250 - 80 + 1350 + 2853 + 225 + 72 *Formation* bg=pink @@ -4111,10 +4066,10 @@ subformations : set(Subformation) Relation - 1730 - 3190 - 70 - 30 + 1566 + 2880 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -4122,10 +4077,10 @@ subformations : set(Subformation) UMLClass - 1540 - 3270 - 180 - 80 + 1395 + 2952 + 162 + 72 *Subformation* bg=pink @@ -4137,10 +4092,10 @@ ordering_priority : int UMLClass - 4400 - 4280 - 220 - 80 + 3969 + 3861 + 198 + 72 *GameEntityFormation* bg=pink @@ -4153,10 +4108,10 @@ subformation : Subformation Relation - 4350 - 4310 - 70 - 30 + 3924 + 3888 + 63 + 27 lt=<. 50.0;10.0;10.0;10.0 @@ -4164,10 +4119,10 @@ subformation : Subformation Relation - 1710 - 3300 - 90 - 30 + 1548 + 2979 + 81 + 27 lt=- 10.0;10.0;70.0;10.0 @@ -4175,10 +4130,10 @@ subformation : Subformation Relation - 1620 - 3230 - 30 - 60 + 1467 + 2916 + 27 + 54 lt=<. 10.0;40.0;10.0;10.0 @@ -4186,10 +4141,10 @@ subformation : Subformation UMLClass - 1400 - 1660 - 210 - 90 + 1269 + 1503 + 189 + 81 *Scoped* bg=pink @@ -4203,24 +4158,13 @@ stances : set(DiplomaticStance) scope : ModifierScope - - Relation - - 6330 - 2520 - 30 - 60 - - lt=<<- - 10.0;40.0;10.0;10.0 - UMLClass - 670 - 6640 - 250 - 100 + 612 + 5985 + 225 + 90 *SendToContainer* bg=orange @@ -4233,10 +4177,10 @@ storages : set(EntityContainer) Relation - 630 - 6420 - 60 - 280 + 576 + 5787 + 54 + 252 lt=- 10.0;10.0;10.0;260.0;40.0;260.0 @@ -4244,10 +4188,10 @@ storages : set(EntityContainer) UMLClass - 1480 - 6640 - 260 - 100 + 1341 + 5985 + 234 + 90 *SendToContainer* bg=orange @@ -4261,10 +4205,10 @@ ignore_containers : set(EntityContainer) Relation - 1440 - 6430 - 60 - 270 + 1305 + 5796 + 54 + 243 lt=- 10.0;10.0;10.0;250.0;40.0;250.0 @@ -4272,10 +4216,10 @@ ignore_containers : set(EntityContainer) Relation - 3880 - 2750 - 60 - 870 + 3501 + 2484 + 54 + 783 lt=<<- 40.0;850.0;40.0;10.0;10.0;10.0 @@ -4283,10 +4227,10 @@ ignore_containers : set(EntityContainer) Relation - 3880 - 2610 - 60 - 170 + 3501 + 2358 + 54 + 153 lt=- 40.0;150.0;40.0;10.0;10.0;10.0 @@ -4294,10 +4238,10 @@ ignore_containers : set(EntityContainer) Relation - 3880 - 2450 - 60 - 190 + 3501 + 2214 + 54 + 171 lt=- 40.0;170.0;40.0;10.0;10.0;10.0 @@ -4305,10 +4249,10 @@ ignore_containers : set(EntityContainer) Relation - 4140 - 3620 - 2440 - 30 + 3735 + 3267 + 2196 + 27 lt=<<- 10.0;10.0;2420.0;10.0 @@ -4316,21 +4260,21 @@ ignore_containers : set(EntityContainer) Relation - 3570 - 3700 - 30 - 550 + 3222 + 3339 + 27 + 576 lt=<<- - 10.0;10.0;10.0;530.0 + 10.0;10.0;10.0;620.0 Relation - 3570 - 3950 - 70 - 30 + 3222 + 3564 + 63 + 27 lt=- 50.0;10.0;10.0;10.0 @@ -4338,10 +4282,10 @@ ignore_containers : set(EntityContainer) Relation - 3570 - 4040 - 70 - 30 + 3222 + 3645 + 63 + 27 lt=- 50.0;10.0;10.0;10.0 @@ -4349,10 +4293,10 @@ ignore_containers : set(EntityContainer) Relation - 3910 - 3680 - 30 - 830 + 3528 + 3321 + 27 + 747 lt=<<- 10.0;10.0;10.0;810.0 @@ -4360,10 +4304,10 @@ ignore_containers : set(EntityContainer) Relation - 4020 - 4310 - 70 - 120 + 3627 + 3888 + 63 + 108 lt=- 10.0;10.0;10.0;100.0;50.0;100.0 @@ -4371,10 +4315,10 @@ ignore_containers : set(EntityContainer) Relation - 3910 - 4310 - 180 - 30 + 3528 + 3888 + 162 + 27 lt=- 10.0;10.0;160.0;10.0 @@ -4382,10 +4326,10 @@ ignore_containers : set(EntityContainer) Relation - 4240 - 3870 - 60 - 120 + 3825 + 3492 + 54 + 108 lt=- 10.0;100.0;40.0;100.0;40.0;10.0 @@ -4393,10 +4337,10 @@ ignore_containers : set(EntityContainer) Relation - 4240 - 3620 - 60 - 280 + 3825 + 3267 + 54 + 252 lt=- 10.0;260.0;40.0;260.0;40.0;10.0 @@ -4404,10 +4348,10 @@ ignore_containers : set(EntityContainer) Relation - 4270 - 3870 - 60 - 30 + 3852 + 3492 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -4415,10 +4359,10 @@ ignore_containers : set(EntityContainer) Relation - 4650 - 2610 - 100 - 30 + 4194 + 2358 + 90 + 27 lt=<. 80.0;10.0;10.0;10.0 @@ -4426,10 +4370,10 @@ ignore_containers : set(EntityContainer) Relation - 4960 - 2610 - 100 - 30 + 4473 + 2358 + 90 + 27 lt=<<- 10.0;10.0;80.0;10.0 @@ -4437,10 +4381,10 @@ ignore_containers : set(EntityContainer) Relation - 5000 - 2610 - 30 - 120 + 4509 + 2358 + 27 + 108 lt=- 10.0;10.0;10.0;100.0 @@ -4448,10 +4392,10 @@ ignore_containers : set(EntityContainer) Relation - 3910 - 2500 - 480 - 30 + 3528 + 2259 + 432 + 27 lt=- 460.0;10.0;10.0;10.0 @@ -4459,10 +4403,10 @@ ignore_containers : set(EntityContainer) Relation - 4330 - 2500 - 60 - 140 + 3906 + 2259 + 54 + 126 lt=- 40.0;120.0;10.0;120.0;10.0;10.0 @@ -4470,10 +4414,10 @@ ignore_containers : set(EntityContainer) Relation - 4330 - 2610 - 60 - 120 + 3906 + 2358 + 54 + 108 lt=- 40.0;100.0;10.0;100.0;10.0;10.0 @@ -4481,10 +4425,10 @@ ignore_containers : set(EntityContainer) Relation - 4870 - 4990 - 100 - 30 + 4392 + 4500 + 90 + 27 lt=<. 80.0;10.0;10.0;10.0 @@ -4492,10 +4436,10 @@ ignore_containers : set(EntityContainer) Relation - 5060 - 3620 - 30 - 1030 + 4563 + 3267 + 27 + 927 lt=- 10.0;10.0;10.0;1010.0 @@ -4503,10 +4447,10 @@ ignore_containers : set(EntityContainer) Relation - 5030 - 4240 - 60 - 30 + 4536 + 3825 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4514,10 +4458,10 @@ ignore_containers : set(EntityContainer) Relation - 5030 - 4350 - 60 - 30 + 4536 + 3924 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4525,10 +4469,10 @@ ignore_containers : set(EntityContainer) Relation - 5060 - 4450 - 60 - 30 + 4563 + 4014 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4536,10 +4480,10 @@ ignore_containers : set(EntityContainer) Relation - 5030 - 4550 - 60 - 30 + 4536 + 4104 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4547,10 +4491,10 @@ ignore_containers : set(EntityContainer) Relation - 5060 - 4550 - 60 - 30 + 4563 + 4104 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4558,10 +4502,10 @@ ignore_containers : set(EntityContainer) Relation - 5030 - 4440 - 60 - 30 + 4536 + 4005 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -4569,10 +4513,10 @@ ignore_containers : set(EntityContainer) Relation - 5060 - 4240 - 60 - 30 + 4563 + 3825 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4580,10 +4524,10 @@ ignore_containers : set(EntityContainer) Relation - 5970 - 3620 - 30 - 1200 + 5382 + 3267 + 27 + 1080 lt=- 10.0;10.0;10.0;1180.0 @@ -4591,10 +4535,10 @@ ignore_containers : set(EntityContainer) Relation - 5940 - 4790 - 60 - 30 + 5355 + 4320 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4602,10 +4546,10 @@ ignore_containers : set(EntityContainer) Relation - 5970 - 4790 - 60 - 30 + 5382 + 4320 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4613,10 +4557,10 @@ ignore_containers : set(EntityContainer) Relation - 5060 - 4350 - 60 - 30 + 4563 + 3924 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4624,10 +4568,10 @@ ignore_containers : set(EntityContainer) Relation - 6380 - 3550 - 60 - 100 + 5751 + 3204 + 54 + 90 lt=- 40.0;10.0;10.0;10.0;10.0;80.0 @@ -4635,10 +4579,10 @@ ignore_containers : set(EntityContainer) Relation - 6380 - 3620 - 30 - 370 + 5751 + 3267 + 27 + 333 lt=- 10.0;350.0;10.0;10.0 @@ -4646,10 +4590,10 @@ ignore_containers : set(EntityContainer) Relation - 6380 - 3960 - 60 - 30 + 5751 + 3573 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4657,10 +4601,10 @@ ignore_containers : set(EntityContainer) Relation - 6180 - 2670 - 210 - 980 + 5571 + 2412 + 189 + 882 lt=- 190.0;10.0;190.0;40.0;10.0;40.0;10.0;960.0 @@ -4668,10 +4612,10 @@ ignore_containers : set(EntityContainer) Relation - 6530 - 2840 - 90 - 30 + 5886 + 2565 + 81 + 27 lt=<<- 10.0;10.0;70.0;10.0 @@ -4679,10 +4623,10 @@ ignore_containers : set(EntityContainer) Relation - 6360 - 2700 - 30 - 60 + 5733 + 2439 + 27 + 54 lt=- 10.0;10.0;10.0;40.0 @@ -4690,10 +4634,10 @@ ignore_containers : set(EntityContainer) Relation - 4890 - 2950 - 30 - 700 + 4410 + 2664 + 27 + 630 lt=- 10.0;680.0;10.0;10.0 @@ -4701,10 +4645,10 @@ ignore_containers : set(EntityContainer) Relation - 4890 - 2980 - 60 - 30 + 4410 + 2691 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -4712,10 +4656,10 @@ ignore_containers : set(EntityContainer) Relation - 4860 - 2950 - 60 - 30 + 4383 + 2664 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -4723,10 +4667,10 @@ ignore_containers : set(EntityContainer) Relation - 4860 - 3070 - 60 - 30 + 4383 + 2772 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -4734,10 +4678,10 @@ ignore_containers : set(EntityContainer) Relation - 4890 - 3070 - 60 - 30 + 4410 + 2772 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -4745,10 +4689,10 @@ ignore_containers : set(EntityContainer) Relation - 4890 - 3160 - 60 - 30 + 4410 + 2853 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -4756,10 +4700,10 @@ ignore_containers : set(EntityContainer) Relation - 4890 - 3250 - 60 - 30 + 4410 + 2934 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -4767,10 +4711,10 @@ ignore_containers : set(EntityContainer) Relation - 4860 - 3220 - 60 - 30 + 4383 + 2907 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -4778,10 +4722,10 @@ ignore_containers : set(EntityContainer) Relation - 4740 - 1720 - 1000 - 1930 + 4275 + 1557 + 900 + 1737 lt=- 980.0;1910.0;980.0;360.0;10.0;360.0;10.0;10.0 @@ -4789,10 +4733,10 @@ ignore_containers : set(EntityContainer) Relation - 5710 - 2070 - 270 - 30 + 5148 + 1872 + 243 + 27 lt=- 10.0;10.0;250.0;10.0 @@ -4800,10 +4744,10 @@ ignore_containers : set(EntityContainer) Relation - 5920 - 1870 - 60 - 230 + 5337 + 1692 + 54 + 207 lt=- 40.0;10.0;10.0;10.0;10.0;210.0 @@ -4811,10 +4755,10 @@ ignore_containers : set(EntityContainer) Relation - 4740 - 1790 - 60 - 30 + 4275 + 1620 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4822,10 +4766,10 @@ ignore_containers : set(EntityContainer) Relation - 4740 - 1900 - 60 - 30 + 4275 + 1719 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4833,10 +4777,10 @@ ignore_containers : set(EntityContainer) Relation - 4740 - 1990 - 60 - 30 + 4275 + 1800 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4844,10 +4788,10 @@ ignore_containers : set(EntityContainer) Relation - 4710 - 1990 - 60 - 30 + 4248 + 1800 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4855,10 +4799,10 @@ ignore_containers : set(EntityContainer) Relation - 4710 - 1900 - 60 - 30 + 4248 + 1719 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4866,10 +4810,10 @@ ignore_containers : set(EntityContainer) Relation - 4710 - 1790 - 60 - 30 + 4248 + 1620 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4877,10 +4821,10 @@ ignore_containers : set(EntityContainer) UMLClass - 6330 - 1890 - 140 - 60 + 5706 + 1710 + 126 + 54 *TargetMode* @@ -4890,10 +4834,10 @@ bg=pink Relation - 6270 - 1910 - 80 - 30 + 5652 + 1728 + 72 + 27 lt=<. 60.0;10.0;10.0;10.0 @@ -4901,10 +4845,10 @@ bg=pink UMLClass - 6540 - 1890 - 160 - 60 + 5895 + 1710 + 144 + 54 *CurrentPosition* @@ -4914,10 +4858,10 @@ bg=pink Relation - 6460 - 1910 - 100 - 30 + 5823 + 1728 + 90 + 27 lt=<<- 10.0;10.0;80.0;10.0 @@ -4925,10 +4869,10 @@ bg=pink UMLClass - 6540 - 1960 - 160 - 60 + 5895 + 1773 + 144 + 54 *ExpectedPosition* @@ -4938,10 +4882,10 @@ bg=pink Relation - 6500 - 1910 - 60 - 100 + 5859 + 1728 + 54 + 90 lt=- 10.0;10.0;10.0;80.0;40.0;80.0 @@ -4949,10 +4893,10 @@ bg=pink UMLClass - 1200 - 1670 - 140 - 60 + 1089 + 1512 + 126 + 54 *ModifierScope* @@ -4962,10 +4906,10 @@ bg=pink Relation - 1330 - 1690 - 90 - 30 + 1206 + 1530 + 81 + 27 lt=<. 10.0;10.0;70.0;10.0 @@ -4973,10 +4917,10 @@ bg=pink UMLClass - 4600 - 4090 - 110 - 60 + 4149 + 3690 + 99 + 54 *AttackMove* @@ -4986,10 +4930,10 @@ bg=pink Relation - 4550 - 4110 - 70 - 30 + 4104 + 3708 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -4997,10 +4941,10 @@ bg=pink UMLClass - 4510 - 3750 - 120 - 60 + 4068 + 3384 + 108 + 54 *MoveMode* @@ -5010,10 +4954,10 @@ bg=pink Relation - 4390 - 3770 - 140 - 90 + 3960 + 3402 + 126 + 81 lt=<. 120.0;10.0;10.0;10.0;10.0;70.0 @@ -5021,10 +4965,10 @@ bg=pink Relation - 1490 - 290 - 60 - 30 + 1350 + 270 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -5032,10 +4976,10 @@ bg=pink UMLClass - 1530 - 270 - 130 - 60 + 1386 + 252 + 117 + 54 *Stray* @@ -5045,10 +4989,10 @@ bg=yellow UMLClass - 2390 - 770 - 300 - 80 + 2160 + 702 + 270 + 72 *AbsoluteProjectileAmount* bg=yellow @@ -5060,10 +5004,10 @@ amount : float Relation - 1770 - 440 - 590 - 800 + 1602 + 405 + 531 + 720 lt=- 10.0;780.0;570.0;780.0;570.0;10.0 @@ -5071,10 +5015,10 @@ amount : float UMLClass - 920 - 780 - 240 - 80 + 837 + 711 + 216 + 72 *GatheringEfficiency* bg=yellow @@ -5086,10 +5030,10 @@ resource_spot : ResourceSpot Relation - 1150 - 810 - 80 - 30 + 1044 + 738 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5097,10 +5041,10 @@ resource_spot : ResourceSpot UMLClass - 1020 - 870 - 140 - 60 + 927 + 792 + 126 + 54 *ReloadTime* @@ -5110,10 +5054,10 @@ bg=yellow Relation - 1150 - 890 - 80 - 30 + 1044 + 810 + 72 + 27 lt=- 10.0;10.0;60.0;10.0 @@ -5121,10 +5065,10 @@ bg=yellow UMLClass - 1260 - 1010 - 280 - 80 + 1143 + 918 + 252 + 72 *CreationTime* bg=yellow @@ -5136,10 +5080,10 @@ creatables : set(CreatableGameEntity) Relation - 1200 - 1040 - 80 - 30 + 1089 + 945 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5147,10 +5091,10 @@ creatables : set(CreatableGameEntity) UMLClass - 1260 - 920 - 300 - 80 + 1143 + 837 + 270 + 72 *CreationResourceCost* bg=yellow @@ -5164,10 +5108,10 @@ creatables : set(CreatableGameEntity) Relation - 1200 - 960 - 80 - 30 + 1089 + 873 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5175,10 +5119,10 @@ creatables : set(CreatableGameEntity) UMLClass - 1260 - 650 - 290 - 80 + 1143 + 594 + 261 + 72 *ResearchResourceCost* bg=yellow @@ -5192,10 +5136,10 @@ researchables : set(ResearchableTech) Relation - 1200 - 680 - 80 - 30 + 1089 + 621 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5203,10 +5147,10 @@ researchables : set(ResearchableTech) Relation - 2330 - 800 - 80 - 30 + 2106 + 729 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5214,10 +5158,10 @@ researchables : set(ResearchableTech) UMLClass - 2390 - 680 - 220 - 80 + 2160 + 621 + 198 + 72 // Immediately unlocks a Tech as soon as the requirements are fulfilled *InstantTechResearch* @@ -5231,10 +5175,10 @@ condition : set(LogicElement) Relation - 2330 - 710 - 80 - 30 + 2106 + 648 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5242,16 +5186,15 @@ condition : set(LogicElement) UMLClass - 4930 - 3340 - 250 - 110 + 4446 + 3015 + 225 + 99 *Herd* bg=green -- -range : float strength : int allowed_types : set(GameEntityType) blacklisted_entities : set(GameEntity) @@ -5260,10 +5203,10 @@ blacklisted_entities : set(GameEntity) Relation - 4890 - 3370 - 60 - 30 + 4410 + 3042 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -5271,10 +5214,10 @@ blacklisted_entities : set(GameEntity) UMLClass - 880 - 510 - 280 - 80 + 801 + 468 + 252 + 72 *EntityContainerCapacity* bg=yellow @@ -5287,10 +5230,10 @@ container : EntityContainer UMLClass - 870 - 600 - 290 - 80 + 792 + 549 + 261 + 72 *StorageElementCapacity* bg=yellow @@ -5303,10 +5246,10 @@ storage_element : StorageElementDefinition Relation - 1150 - 540 - 80 - 30 + 1044 + 495 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5314,10 +5257,10 @@ storage_element : StorageElementDefinition Relation - 1150 - 630 - 80 - 30 + 1044 + 576 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5325,10 +5268,10 @@ storage_element : StorageElementDefinition UMLClass - 1260 - 740 - 290 - 80 + 1143 + 675 + 261 + 72 *ResearchTime* bg=yellow @@ -5341,10 +5284,10 @@ researchables : set(ResearchableTech) Relation - 1200 - 770 - 80 - 30 + 1089 + 702 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5352,10 +5295,10 @@ researchables : set(ResearchableTech) UMLClass - 2390 - 500 - 280 - 100 + 2160 + 459 + 252 + 90 // Reveal area around listed units *Reveal* @@ -5370,10 +5313,10 @@ blacklisted_entities : set(GameEntity) Relation - 2330 - 530 - 80 - 30 + 2106 + 486 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5381,10 +5324,10 @@ blacklisted_entities : set(GameEntity) UMLNote - 0 - 340 - 190 - 90 + 9 + 315 + 171 + 81 Orange elements: @@ -5397,10 +5340,10 @@ bg=orange UMLClass - 2390 - 860 - 320 - 100 + 2160 + 783 + 288 + 90 // The change values and fire rate of the provider and receiver are compared (divided) // with the result being added to the projectile amount of the receiver @@ -5419,10 +5362,10 @@ change_types : set(AttributeChangeType) UMLClass - 4640 - 3420 - 230 - 80 + 4185 + 3087 + 207 + 72 *RegenerateResourceSpot* bg=green @@ -5435,10 +5378,10 @@ resource_spot : ResourceSpot Relation - 4860 - 3450 - 60 - 30 + 4383 + 3114 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -5446,10 +5389,10 @@ resource_spot : ResourceSpot UMLClass - 1150 - 1760 - 120 - 60 + 1044 + 1593 + 108 + 54 // Only affect yourself (default for modifiers in GameEntity) @@ -5460,10 +5403,10 @@ bg=pink Relation - 1260 - 1720 - 70 - 100 + 1143 + 1557 + 63 + 90 lt=<<- 50.0;10.0;50.0;80.0;10.0;80.0 @@ -5471,10 +5414,10 @@ bg=pink UMLClass - 1020 - 1830 - 250 - 80 + 927 + 1656 + 225 + 72 // Affect all game entities in the list *GameEntityScope* @@ -5488,10 +5431,10 @@ blacklisted_entities : set(GameEntity) Relation - 1260 - 1790 - 70 - 90 + 1143 + 1620 + 63 + 81 lt=- 10.0;70.0;50.0;70.0;50.0;10.0 @@ -5499,21 +5442,21 @@ blacklisted_entities : set(GameEntity) Text - 0 - 0 - 230 - 30 + 9 + 9 + 207 + 27 - openage nyan data API v0.5.0 + openage nyan data API v0.6.0 UMLClass - 1420 - 3010 - 320 - 140 + 1287 + 2718 + 288 + 126 *StateChanger* bg=pink @@ -5531,10 +5474,10 @@ priority : int Relation - 1730 - 3060 - 70 - 30 + 1566 + 2763 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -5542,10 +5485,10 @@ priority : int UMLClass - 1980 - 500 - 310 - 90 + 1791 + 459 + 279 + 81 // Apply effects when in a container *InContainerContinuousEffect* @@ -5559,10 +5502,10 @@ ability : ApplyContinuousEffect Relation - 2280 - 530 - 80 - 30 + 2061 + 486 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5570,10 +5513,10 @@ ability : ApplyContinuousEffect UMLClass - 1980 - 610 - 310 - 90 + 1791 + 558 + 279 + 81 // Apply effects when in a container *InContainerDiscreteEffect* @@ -5587,10 +5530,10 @@ ability : ApplyDiscreteEffect Relation - 2280 - 640 - 80 - 30 + 2061 + 585 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5598,10 +5541,10 @@ ability : ApplyDiscreteEffect UMLClass - 4600 - 4160 - 110 - 60 + 4149 + 3753 + 99 + 54 *Normal* @@ -5611,10 +5554,10 @@ bg=pink Relation - 4550 - 4180 - 70 - 30 + 4104 + 3771 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -5622,10 +5565,10 @@ bg=pink UMLClass - 520 - 150 - 170 - 80 + 477 + 144 + 153 + 72 *Terrain* bg=yellow @@ -5637,10 +5580,10 @@ terrain : Terrain Relation - 680 - 180 - 60 - 30 + 621 + 171 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -5648,10 +5591,10 @@ terrain : Terrain UMLClass - 1530 - 0 - 170 - 80 + 1386 + 9 + 153 + 72 *Terrain* bg=yellow @@ -5663,10 +5606,10 @@ terrain : Terrain Relation - 1490 - 30 - 30 - 360 + 1350 + 36 + 27 + 324 lt=- 10.0;340.0;10.0;10.0 @@ -5674,10 +5617,10 @@ terrain : Terrain UMLClass - 2390 - 410 - 280 - 80 + 2160 + 378 + 252 + 72 // Reveal area around listed units *DiplomaticLineOfSight* @@ -5690,10 +5633,10 @@ diplomatic_stance : DiplomaticStance Relation - 2330 - 440 - 80 - 30 + 2106 + 405 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5701,10 +5644,10 @@ diplomatic_stance : DiplomaticStance UMLClass - 1040 - 4910 - 120 - 60 + 945 + 4428 + 108 + 54 *LureType* @@ -5714,10 +5657,10 @@ bg=pink Relation - 960 - 4930 - 100 - 30 + 873 + 4446 + 90 + 27 lt=- 10.0;10.0;80.0;10.0 @@ -5725,10 +5668,10 @@ bg=pink UMLClass - 1040 - 4980 - 200 - 60 + 945 + 4491 + 180 + 54 *SendToContainerType* @@ -5738,10 +5681,10 @@ bg=pink Relation - 960 - 5000 - 100 - 30 + 873 + 4509 + 90 + 27 lt=- 10.0;10.0;80.0;10.0 @@ -5749,10 +5692,10 @@ bg=pink UMLClass - 3450 - 2760 - 140 - 60 + 3114 + 2493 + 126 + 54 *PlacementMode* @@ -5762,10 +5705,10 @@ bg=pink Relation - 3490 - 2710 - 30 - 70 + 3150 + 2448 + 27 + 63 lt=<. 10.0;50.0;10.0;10.0 @@ -5773,10 +5716,10 @@ bg=pink UMLClass - 3570 - 3070 - 110 - 60 + 3222 + 2772 + 99 + 54 *Eject* @@ -5786,10 +5729,10 @@ bg=pink UMLClass - 3570 - 2840 - 210 - 130 + 3222 + 2565 + 189 + 117 *Place* bg=pink @@ -5805,10 +5748,10 @@ max_elevation_difference : int Relation - 3520 - 2810 - 30 - 390 + 3177 + 2538 + 27 + 351 lt=<<- 10.0;10.0;10.0;370.0 @@ -5816,10 +5759,10 @@ max_elevation_difference : int Relation - 3520 - 2870 - 70 - 30 + 3177 + 2592 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -5827,10 +5770,10 @@ max_elevation_difference : int UMLClass - 370 - 3950 - 260 - 180 + 342 + 3564 + 234 + 162 *AdjacentTilesVariant* @@ -5849,10 +5792,10 @@ north_west : optional(GameEntity) Relation - 620 - 3890 - 50 - 120 + 567 + 3510 + 45 + 108 lt=- 30.0;10.0;30.0;100.0;10.0;100.0 @@ -5860,10 +5803,10 @@ north_west : optional(GameEntity) UMLClass - 520 - 4770 - 160 - 60 + 477 + 4302 + 144 + 54 *InverseLinear* @@ -5873,10 +5816,10 @@ bg=pink UMLClass - 3620 - 3830 - 180 - 80 + 3267 + 3456 + 162 + 72 *ExecutionSound* bg=pink @@ -5889,10 +5832,10 @@ sounds : set(Sound) Relation - 3570 - 3860 - 70 - 30 + 3222 + 3483 + 63 + 27 lt=- 50.0;10.0;10.0;10.0 @@ -5900,10 +5843,10 @@ sounds : set(Sound) Relation - 3570 - 3770 - 70 - 30 + 3222 + 3402 + 63 + 27 lt=- 50.0;10.0;10.0;10.0 @@ -5911,10 +5854,10 @@ sounds : set(Sound) UMLClass - 3620 - 3740 - 230 - 80 + 3267 + 3375 + 207 + 72 *AnimationOverride* bg=pink @@ -5926,10 +5869,10 @@ overrides : set(AnimationOverride) UMLClass - 1980 - 410 - 310 - 80 + 1791 + 378 + 279 + 72 *RefundOnCondition* bg=yellow @@ -5942,10 +5885,10 @@ refund_amount : set(ResourceAmount) Relation - 2280 - 440 - 80 - 30 + 2061 + 405 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5953,10 +5896,10 @@ refund_amount : set(ResourceAmount) UMLClass - 1290 - 4410 - 230 - 80 + 1170 + 3978 + 207 + 72 *ProgressStatus* bg=pink @@ -5969,10 +5912,10 @@ progress : float Relation - 1510 - 4420 - 60 - 30 + 1368 + 3987 + 54 + 27 lt=<. 10.0;10.0;40.0;10.0 @@ -5980,10 +5923,10 @@ progress : float UMLClass - 170 - 6410 - 330 - 110 + 162 + 5778 + 297 + 99 *TimeRelativeAttributeChange* bg=pink @@ -5997,10 +5940,10 @@ ignore_protection : set(ProtectingAttribute) Relation - 490 - 6310 - 60 - 170 + 450 + 5688 + 54 + 153 lt=- 40.0;10.0;40.0;150.0;10.0;150.0 @@ -6008,10 +5951,10 @@ ignore_protection : set(ProtectingAttribute) UMLClass - 200 - 6540 - 260 - 60 + 189 + 5895 + 234 + 54 *TimeRelativeAttributeDecrease* @@ -6021,10 +5964,10 @@ bg=orange UMLClass - 200 - 6610 - 260 - 60 + 189 + 5958 + 234 + 54 *TimeRelativeAttributeIncrease* @@ -6034,10 +5977,10 @@ bg=orange Relation - 450 - 6560 - 50 - 100 + 414 + 5913 + 45 + 90 lt=- 10.0;80.0;30.0;80.0;30.0;10.0 @@ -6045,10 +5988,10 @@ bg=orange Relation - 450 - 6510 - 50 - 80 + 414 + 5868 + 45 + 72 lt=->> 10.0;60.0;30.0;60.0;30.0;10.0 @@ -6056,10 +5999,10 @@ bg=orange UMLClass - 250 - 6690 - 250 - 90 + 234 + 6030 + 225 + 81 *TimeRelativeProgressChange* bg=pink @@ -6072,10 +6015,10 @@ total_change_time : float Relation - 490 - 6450 - 60 - 300 + 450 + 5814 + 54 + 270 lt=- 40.0;10.0;40.0;280.0;10.0;280.0 @@ -6083,10 +6026,10 @@ total_change_time : float UMLClass - 1020 - 6410 - 250 - 90 + 927 + 5778 + 225 + 81 *TimeRelativeAttributeChange* bg=pink @@ -6098,10 +6041,10 @@ type : AttributeChangeType Relation - 1260 - 6310 - 60 - 160 + 1143 + 5688 + 54 + 144 lt=- 40.0;10.0;40.0;140.0;10.0;140.0 @@ -6109,10 +6052,10 @@ type : AttributeChangeType UMLClass - 1020 - 6540 - 220 - 60 + 927 + 5895 + 198 + 54 *FlatAttributeDecrease* @@ -6122,10 +6065,10 @@ bg=orange UMLClass - 1020 - 6610 - 220 - 60 + 927 + 5958 + 198 + 54 *FlatAttributeIncrease* @@ -6135,10 +6078,10 @@ bg=orange Relation - 1230 - 6560 - 50 - 100 + 1116 + 5913 + 45 + 90 lt=- 10.0;80.0;30.0;80.0;30.0;10.0 @@ -6146,10 +6089,10 @@ bg=orange Relation - 1230 - 6490 - 50 - 100 + 1116 + 5850 + 45 + 90 lt=->> 10.0;80.0;30.0;80.0;30.0;10.0 @@ -6157,10 +6100,10 @@ bg=orange UMLClass - 1020 - 6690 - 250 - 90 + 927 + 6030 + 225 + 81 *TimeRelativeProgressChange* bg=pink @@ -6173,10 +6116,10 @@ type : ProgressType Relation - 1260 - 6440 - 60 - 310 + 1143 + 5805 + 54 + 279 lt=- 40.0;10.0;40.0;290.0;10.0;290.0 @@ -6184,10 +6127,10 @@ type : ProgressType UMLClass - 2220 - 4180 - 120 - 60 + 2007 + 3771 + 108 + 54 *ProgressType* @@ -6197,10 +6140,10 @@ bg=pink Relation - 2140 - 4200 - 100 - 30 + 1935 + 3789 + 90 + 27 lt=- 10.0;10.0;80.0;10.0 @@ -6208,10 +6151,10 @@ bg=pink UMLClass - 5040 - 2670 - 270 - 80 + 4545 + 2412 + 243 + 72 *AoE1TradeRoute* bg=pink @@ -6224,10 +6167,10 @@ trade_amount : int UMLClass - 2460 - 4930 - 210 - 80 + 2223 + 4446 + 189 + 72 *StateChange* bg=pink @@ -6239,10 +6182,10 @@ state_change : StateChanger Relation - 2420 - 4960 - 60 - 30 + 2187 + 4473 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -6250,10 +6193,10 @@ state_change : StateChanger UMLClass - 2460 - 4750 - 150 - 80 + 2223 + 4284 + 135 + 72 *Terrain* bg=pink @@ -6267,10 +6210,10 @@ terrain : Terrain Relation - 2420 - 4780 - 60 - 30 + 2187 + 4311 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -6278,10 +6221,10 @@ terrain : Terrain Relation - 5970 - 4700 - 60 - 30 + 5382 + 4239 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -6289,10 +6232,10 @@ terrain : Terrain UMLClass - 6010 - 4670 - 260 - 80 + 5418 + 4212 + 234 + 72 *TerrainRequirement* bg=green @@ -6305,10 +6248,10 @@ blacklisted_terrains : set(Terrain) UMLClass - 6010 - 4570 - 250 - 90 + 5418 + 4122 + 225 + 81 *OverlayTerrain* bg=green @@ -6320,10 +6263,10 @@ terrain_overlay : Terrain UMLClass - 4950 - 4930 - 240 - 90 + 4464 + 4446 + 216 + 81 *Attribute* bg=pink @@ -6337,10 +6280,10 @@ abbreviation : TranslatedString Relation - 5060 - 4870 - 30 - 80 + 4563 + 4392 + 27 + 72 lt=<. 10.0;60.0;10.0;10.0 @@ -6348,10 +6291,10 @@ abbreviation : TranslatedString UMLClass - 1980 - 720 - 310 - 120 + 1791 + 657 + 279 + 108 *DepositResourcesOnProgress* bg=yellow @@ -6366,10 +6309,10 @@ blacklisted_entities : set(GameEntity) Relation - 2280 - 750 - 80 - 30 + 2061 + 684 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -6377,10 +6320,10 @@ blacklisted_entities : set(GameEntity) UMLClass - 4410 - 2130 - 140 - 60 + 3978 + 1926 + 126 + 54 *PriceMode* @@ -6390,10 +6333,10 @@ bg=pink Relation - 4470 - 2400 - 30 - 80 + 4032 + 2169 + 27 + 72 lt=<. 10.0;10.0;10.0;60.0 @@ -6401,10 +6344,10 @@ bg=pink Relation - 4540 - 2150 - 170 - 30 + 4095 + 1944 + 153 + 27 lt=<<- 10.0;10.0;150.0;10.0 @@ -6412,10 +6355,10 @@ bg=pink UMLClass - 4690 - 2130 - 110 - 60 + 4230 + 1926 + 99 + 54 *Fixed* @@ -6425,10 +6368,10 @@ bg=pink Relation - 3520 - 3090 - 70 - 30 + 3177 + 2790 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -6436,10 +6379,10 @@ bg=pink Relation - 5000 - 2700 - 60 - 30 + 4509 + 2439 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -6447,10 +6390,10 @@ bg=pink Relation - 4650 - 2150 - 30 - 100 + 4194 + 1944 + 27 + 90 lt=- 10.0;10.0;10.0;80.0 @@ -6458,10 +6401,10 @@ bg=pink Relation - 4650 - 2220 - 60 - 30 + 4194 + 2007 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -6469,10 +6412,10 @@ bg=pink UMLClass - 4690 - 2200 - 150 - 100 + 4230 + 1989 + 135 + 90 *Dynamic* bg=pink @@ -6486,10 +6429,10 @@ max_price : float UMLClass - 4770 - 2330 - 120 - 60 + 4302 + 2106 + 108 + 54 *PricePool* @@ -6499,10 +6442,10 @@ bg=pink Relation - 4700 - 2350 - 90 - 30 + 4239 + 2124 + 81 + 27 lt=<. 70.0;10.0;10.0;10.0 @@ -6510,10 +6453,10 @@ bg=pink UMLClass - 750 - 80 - 250 - 60 + 684 + 81 + 225 + 54 *TimeRelativeAttributeChange* @@ -6523,10 +6466,10 @@ bg=yellow Relation - 990 - 100 - 90 - 30 + 900 + 99 + 81 + 27 lt=- 70.0;10.0;10.0;10.0 @@ -6534,10 +6477,10 @@ bg=yellow UMLClass - 770 - 10 - 230 - 60 + 702 + 18 + 207 + 54 *TimeRelativeProgressChange* @@ -6547,10 +6490,10 @@ bg=yellow Relation - 990 - 30 - 90 - 30 + 900 + 36 + 81 + 27 lt=- 70.0;10.0;10.0;10.0 @@ -6558,10 +6501,10 @@ bg=yellow UMLClass - 540 - 500 - 150 - 60 + 495 + 459 + 135 + 54 *Unconditional* @@ -6571,10 +6514,10 @@ bg=yellow Relation - 710 - 180 - 30 - 370 + 648 + 171 + 27 + 333 lt=- 10.0;10.0;10.0;350.0 @@ -6582,10 +6525,10 @@ bg=yellow UMLClass - 1530 - 340 - 170 - 60 + 1386 + 315 + 153 + 54 *Unconditional* @@ -6595,10 +6538,10 @@ bg=yellow Relation - 1490 - 360 - 60 - 30 + 1350 + 333 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -6606,10 +6549,10 @@ bg=yellow UMLClass - 1010 - 2740 - 240 - 80 + 918 + 2475 + 216 + 72 *Cost* bg=pink @@ -6621,10 +6564,10 @@ payment_mode : PaymentMode UMLClass - 1310 - 2690 - 230 - 80 + 1188 + 2430 + 207 + 72 *ResourceCost* bg=pink @@ -6636,10 +6579,10 @@ amount : set(ResourceAmount) UMLClass - 1310 - 2790 - 230 - 80 + 1188 + 2520 + 207 + 72 *AttributeCost* bg=pink @@ -6651,10 +6594,10 @@ amount : set(AttributeAmount) Relation - 1240 - 2770 - 70 - 30 + 1125 + 2502 + 63 + 27 lt=<<- 10.0;10.0;50.0;10.0 @@ -6662,10 +6605,10 @@ amount : set(AttributeAmount) Relation - 1280 - 2720 - 50 - 70 + 1161 + 2457 + 45 + 63 lt=- 10.0;50.0;10.0;10.0;30.0;10.0 @@ -6673,10 +6616,10 @@ amount : set(AttributeAmount) Relation - 1280 - 2760 - 50 - 90 + 1161 + 2493 + 45 + 81 lt=- 10.0;10.0;10.0;70.0;30.0;70.0 @@ -6684,10 +6627,10 @@ amount : set(AttributeAmount) UMLClass - 1260 - 830 - 300 - 80 + 1143 + 756 + 270 + 72 *CreationAttributeCost* bg=yellow @@ -6701,10 +6644,10 @@ creatables : set(CreatableGameEntity) Relation - 1200 - 860 - 80 - 30 + 1089 + 783 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -6712,10 +6655,10 @@ creatables : set(CreatableGameEntity) Relation - 1150 - 1040 - 80 - 30 + 1044 + 945 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -6723,10 +6666,10 @@ creatables : set(CreatableGameEntity) UMLClass - 1260 - 560 - 290 - 80 + 1143 + 513 + 261 + 72 *ResearchAttributeCost* bg=yellow @@ -6740,10 +6683,10 @@ researchables : set(ResearchableTech) Relation - 1200 - 590 - 80 - 30 + 1089 + 540 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -6751,10 +6694,10 @@ researchables : set(ResearchableTech) Relation - 1120 - 2810 - 30 - 70 + 1017 + 2538 + 27 + 63 lt=- 10.0;50.0;10.0;10.0 @@ -6762,10 +6705,10 @@ researchables : set(ResearchableTech) UMLClass - 1060 - 2640 - 140 - 60 + 963 + 2385 + 126 + 54 *PaymentMode* @@ -6775,10 +6718,10 @@ bg=pink Relation - 1120 - 2690 - 30 - 70 + 1017 + 2430 + 27 + 63 lt=<. 10.0;10.0;10.0;50.0 @@ -6786,10 +6729,10 @@ bg=pink UMLClass - 1000 - 2420 - 110 - 60 + 909 + 2187 + 99 + 54 *Advance* @@ -6799,10 +6742,10 @@ bg=pink UMLClass - 1000 - 2560 - 110 - 60 + 909 + 2313 + 99 + 54 *Arrear* @@ -6812,10 +6755,10 @@ bg=pink UMLClass - 1000 - 2490 - 110 - 60 + 909 + 2250 + 99 + 54 *Adaptive* @@ -6825,10 +6768,10 @@ bg=pink Relation - 1140 - 2370 - 30 - 290 + 1035 + 2142 + 27 + 261 lt=<<- 10.0;270.0;10.0;10.0 @@ -6836,10 +6779,10 @@ bg=pink Relation - 1100 - 2580 - 70 - 30 + 999 + 2331 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -6847,10 +6790,10 @@ bg=pink Relation - 1100 - 2510 - 70 - 30 + 999 + 2268 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -6858,10 +6801,10 @@ bg=pink Relation - 1100 - 2440 - 70 - 30 + 999 + 2205 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -6869,10 +6812,10 @@ bg=pink UMLClass - 2450 - 4010 - 160 - 60 + 2214 + 3618 + 144 + 54 *AttributeChange* @@ -6882,10 +6825,10 @@ bg=pink Relation - 2330 - 4200 - 110 - 30 + 2106 + 3789 + 99 + 27 lt=<<- 10.0;10.0;90.0;10.0 @@ -6893,10 +6836,10 @@ bg=pink UMLClass - 200 - 6800 - 260 - 60 + 189 + 6129 + 234 + 54 *TimeRelativeProgressDecrease* @@ -6906,10 +6849,10 @@ bg=orange UMLClass - 200 - 6870 - 260 - 60 + 189 + 6192 + 234 + 54 *TimeRelativeProgressIncrease* @@ -6919,10 +6862,10 @@ bg=orange Relation - 450 - 6820 - 50 - 100 + 414 + 6147 + 45 + 90 lt=- 10.0;80.0;30.0;80.0;30.0;10.0 @@ -6930,10 +6873,10 @@ bg=orange Relation - 450 - 6770 - 50 - 80 + 414 + 6102 + 45 + 72 lt=->> 10.0;60.0;30.0;60.0;30.0;10.0 @@ -6941,10 +6884,10 @@ bg=orange UMLClass - 980 - 6800 - 260 - 60 + 891 + 6129 + 234 + 54 *TimeRelativeProgressDecrease* @@ -6954,10 +6897,10 @@ bg=orange UMLClass - 980 - 6870 - 260 - 60 + 891 + 6192 + 234 + 54 *TimeRelativeProgressIncrease* @@ -6967,10 +6910,10 @@ bg=orange Relation - 1230 - 6820 - 50 - 100 + 1116 + 6147 + 45 + 90 lt=- 10.0;80.0;30.0;80.0;30.0;10.0 @@ -6978,10 +6921,10 @@ bg=orange Relation - 1230 - 6770 - 50 - 80 + 1116 + 6102 + 45 + 72 lt=->> 10.0;60.0;30.0;60.0;30.0;10.0 @@ -6989,10 +6932,10 @@ bg=orange UMLClass - 1000 - 2350 - 110 - 60 + 909 + 2124 + 99 + 54 *Shadow* @@ -7002,10 +6945,10 @@ bg=pink Relation - 1100 - 2370 - 70 - 30 + 999 + 2142 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -7013,10 +6956,10 @@ bg=pink UMLClass - 6510 - 4060 - 160 - 60 + 5868 + 3663 + 144 + 54 *HerdableMode* @@ -7026,10 +6969,10 @@ bg=pink Relation - 6580 - 4020 - 30 - 60 + 5931 + 3627 + 27 + 54 lt=<. 10.0;40.0;10.0;10.0 @@ -7037,10 +6980,10 @@ bg=pink UMLClass - 6600 - 4150 - 160 - 60 + 5949 + 3744 + 144 + 54 *ClosestHerding* @@ -7050,10 +6993,10 @@ bg=pink Relation - 6550 - 4110 - 30 - 230 + 5904 + 3708 + 27 + 207 lt=<<- 10.0;10.0;10.0;210.0 @@ -7061,10 +7004,10 @@ bg=pink Relation - 6550 - 4170 - 70 - 30 + 5904 + 3762 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -7072,10 +7015,10 @@ bg=pink UMLClass - 6600 - 4220 - 200 - 60 + 5949 + 3807 + 180 + 54 *LongestTimeInRange* @@ -7085,10 +7028,10 @@ bg=pink UMLClass - 6600 - 4290 - 160 - 60 + 5949 + 3870 + 144 + 54 *MostHerding* @@ -7098,10 +7041,10 @@ bg=pink Relation - 6550 - 4240 - 70 - 30 + 5904 + 3825 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -7109,10 +7052,10 @@ bg=pink Relation - 6550 - 4310 - 70 - 30 + 5904 + 3888 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -7120,10 +7063,10 @@ bg=pink UMLClass - 1400 - 3900 - 120 - 60 + 1269 + 3519 + 108 + 54 *TerrainType* @@ -7133,10 +7076,10 @@ bg=pink Relation - 1450 - 3850 - 30 - 70 + 1314 + 3474 + 27 + 63 lt=<. 10.0;50.0;10.0;10.0 @@ -7144,10 +7087,10 @@ bg=pink UMLClass - 1230 - 5370 - 260 - 90 + 1116 + 4842 + 234 + 81 *Stacked* bg=pink @@ -7162,10 +7105,10 @@ distribution_type : DistributionType Relation - 1180 - 5310 - 70 - 30 + 1071 + 4788 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -7173,10 +7116,10 @@ distribution_type : DistributionType UMLClass - 1620 - 5390 - 160 - 60 + 1467 + 4860 + 144 + 54 *CalculationType* @@ -7190,10 +7133,10 @@ bg=pink Relation - 1480 - 5410 - 160 - 30 + 1341 + 4878 + 144 + 27 lt=<. 140.0;10.0;10.0;10.0 @@ -7201,10 +7144,10 @@ bg=pink UMLClass - 1700 - 5650 - 160 - 100 + 1539 + 5094 + 144 + 90 *Hyperbolic* bg=pink @@ -7221,10 +7164,10 @@ scale_factor : float Relation - 1660 - 5440 - 30 - 260 + 1503 + 4905 + 27 + 234 lt=<<- 10.0;10.0;10.0;240.0 @@ -7232,10 +7175,10 @@ scale_factor : float Relation - 1500 - 6230 - 60 - 30 + 1359 + 5616 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -7243,10 +7186,10 @@ scale_factor : float Relation - 1660 - 5670 - 60 - 30 + 1503 + 5112 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -7254,10 +7197,10 @@ scale_factor : float UMLClass - 1700 - 5540 - 160 - 100 + 1539 + 4995 + 144 + 90 *Linear* bg=pink @@ -7274,10 +7217,10 @@ scale_factor : float Relation - 1660 - 5560 - 60 - 30 + 1503 + 5013 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -7285,10 +7228,10 @@ scale_factor : float UMLClass - 1700 - 5470 - 120 - 60 + 1539 + 4932 + 108 + 54 *NoStack* @@ -7301,10 +7244,10 @@ bg=pink Relation - 1660 - 5490 - 60 - 30 + 1503 + 4950 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -7312,10 +7255,10 @@ bg=pink UMLClass - 3570 - 3140 - 190 - 80 + 3222 + 2835 + 171 + 72 *OwnStorage* bg=pink @@ -7327,10 +7270,10 @@ container : EntityContainer Relation - 3520 - 3170 - 70 - 30 + 3177 + 2862 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -7338,10 +7281,10 @@ container : EntityContainer UMLClass - 3580 - 2330 - 310 - 80 + 3231 + 2106 + 279 + 72 *ProductionQueue* bg=green @@ -7354,10 +7297,10 @@ production_modes : set(ProductionMode) Relation - 3880 - 2360 - 60 - 120 + 3501 + 2133 + 54 + 108 lt=- 40.0;100.0;40.0;10.0;10.0;10.0 @@ -7365,10 +7308,10 @@ production_modes : set(ProductionMode) UMLClass - 3670 - 2230 - 160 - 60 + 3312 + 2016 + 144 + 54 *ProductionMode* @@ -7378,10 +7321,10 @@ bg=pink Relation - 3740 - 2280 - 30 - 70 + 3375 + 2061 + 27 + 63 lt=<. 10.0;10.0;10.0;50.0 @@ -7389,10 +7332,10 @@ bg=pink UMLClass - 3940 - 2310 - 270 - 80 + 3555 + 2088 + 243 + 72 *Creatables* bg=pink @@ -7404,10 +7347,10 @@ exclude : set(CreatableGameEntity) Relation - 3820 - 2250 - 140 - 30 + 3447 + 2034 + 126 + 27 lt=<<- 10.0;10.0;120.0;10.0 @@ -7415,10 +7358,10 @@ exclude : set(CreatableGameEntity) UMLClass - 3940 - 2220 - 240 - 80 + 3555 + 2007 + 216 + 72 *Researchables* bg=pink @@ -7430,10 +7373,10 @@ exclude : set(ResearchableTech) Relation - 3900 - 2250 - 60 - 120 + 3519 + 2034 + 54 + 108 lt=- 10.0;10.0;10.0;100.0;40.0;100.0 @@ -7441,10 +7384,10 @@ exclude : set(ResearchableTech) UMLClass - 1640 - 4020 - 200 - 80 + 1485 + 3627 + 180 + 72 *LogicElement* bg=pink @@ -7456,10 +7399,10 @@ only_once : bool UMLClass - 1990 - 4120 - 100 - 60 + 1800 + 3717 + 90 + 54 *AND* @@ -7469,10 +7412,10 @@ bg=pink Relation - 1960 - 4090 - 30 - 320 + 1773 + 3690 + 27 + 288 lt=<<- 10.0;10.0;10.0;300.0 @@ -7480,10 +7423,10 @@ bg=pink UMLClass - 1990 - 4190 - 100 - 60 + 1800 + 3780 + 90 + 54 *OR* @@ -7493,10 +7436,10 @@ bg=pink UMLClass - 1990 - 4260 - 140 - 80 + 1800 + 3843 + 126 + 72 *SUBSETMIN* bg=pink @@ -7508,10 +7451,10 @@ size : int Relation - 1940 - 4210 - 50 - 30 + 1755 + 3798 + 45 + 27 lt=- 30.0;10.0;10.0;10.0 @@ -7519,10 +7462,10 @@ size : int Relation - 1960 - 4210 - 50 - 30 + 1773 + 3798 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -7530,10 +7473,10 @@ size : int Relation - 1960 - 4140 - 50 - 30 + 1773 + 3735 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -7541,10 +7484,10 @@ size : int Relation - 6350 - 3890 - 60 - 30 + 5724 + 3510 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -7552,10 +7495,10 @@ size : int Relation - 6380 - 3790 - 60 - 30 + 5751 + 3420 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -7563,10 +7506,10 @@ size : int Relation - 6380 - 3690 - 60 - 30 + 5751 + 3330 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -7574,10 +7517,10 @@ size : int UMLClass - 1340 - 4150 - 210 - 80 + 1215 + 3744 + 189 + 72 *LiteralScope* bg=pink @@ -7589,10 +7532,10 @@ stances : set(DiplomaticStance) Relation - 1540 - 4180 - 90 - 30 + 1395 + 3771 + 81 + 27 lt=<. 10.0;10.0;70.0;10.0 @@ -7600,10 +7543,10 @@ stances : set(DiplomaticStance) UMLClass - 1310 - 4250 - 100 - 60 + 1188 + 3834 + 90 + 54 *Any* @@ -7614,10 +7557,10 @@ bg=pink Relation - 1420 - 4220 - 30 - 150 + 1287 + 3807 + 27 + 135 lt=<<- 10.0;10.0;10.0;130.0 @@ -7625,10 +7568,10 @@ bg=pink UMLClass - 1310 - 4320 - 100 - 60 + 1188 + 3897 + 90 + 54 *Self* @@ -7638,10 +7581,10 @@ bg=pink Relation - 1400 - 4340 - 50 - 30 + 1269 + 3915 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -7649,10 +7592,10 @@ bg=pink Relation - 1400 - 4270 - 50 - 30 + 1269 + 3852 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -7660,10 +7603,10 @@ bg=pink Relation - 1770 - 4290 - 50 - 30 + 1602 + 3870 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -7671,10 +7614,10 @@ bg=pink Relation - 1770 - 4380 - 50 - 30 + 1602 + 3951 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -7682,10 +7625,10 @@ bg=pink UMLClass - 1560 - 4450 - 220 - 80 + 1413 + 4014 + 198 + 72 *ResourceSpotsDepleted* bg=pink @@ -7697,10 +7640,10 @@ only_enabled : bool UMLClass - 1560 - 4540 - 220 - 80 + 1413 + 4095 + 198 + 72 *Timer* bg=pink @@ -7712,10 +7655,10 @@ time : float Relation - 1770 - 4480 - 50 - 30 + 1602 + 4041 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -7723,10 +7666,10 @@ time : float Relation - 1770 - 4570 - 50 - 30 + 1602 + 4122 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -7734,10 +7677,10 @@ time : float UMLClass - 1560 - 4630 - 220 - 80 + 1413 + 4176 + 198 + 72 *AttributeBelowValue* bg=pink @@ -7750,10 +7693,10 @@ threshold : float UMLClass - 1560 - 4900 - 220 - 80 + 1413 + 4419 + 198 + 72 *ProjectilePassThrough* bg=pink @@ -7765,10 +7708,10 @@ pass_through_range : int UMLClass - 1590 - 4990 - 190 - 60 + 1440 + 4500 + 171 + 54 *ProjectileHitTerrain* @@ -7778,10 +7721,10 @@ bg=pink Relation - 1770 - 4660 - 50 - 30 + 1602 + 4203 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -7789,10 +7732,10 @@ bg=pink Relation - 1770 - 4930 - 50 - 30 + 1602 + 4446 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -7800,10 +7743,10 @@ bg=pink Relation - 1770 - 5010 - 50 - 30 + 1602 + 4518 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -7811,10 +7754,10 @@ bg=pink UMLClass - 5380 - 4420 - 140 - 100 + 4851 + 3987 + 126 + 90 *Hitbox* bg=pink @@ -7828,10 +7771,10 @@ radius_z : float Relation - 5310 - 4450 - 90 - 30 + 4788 + 4014 + 81 + 27 lt=<. 70.0;10.0;10.0;10.0 @@ -7839,16 +7782,15 @@ radius_z : float UMLClass - 5100 - 4100 - 270 - 100 + 4599 + 3699 + 243 + 90 *DetectCloak (SWGB)* bg=green -- -range : float allowed_types : set(GameEntityType) blacklisted_entities : set(GameEntity) @@ -7856,10 +7798,10 @@ blacklisted_entities : set(GameEntity) Relation - 5060 - 4130 - 60 - 30 + 4563 + 3726 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -7867,10 +7809,10 @@ blacklisted_entities : set(GameEntity) UMLClass - 1420 - 5490 - 160 - 60 + 1287 + 4950 + 144 + 54 *DistributionType* @@ -7886,10 +7828,10 @@ bg=pink Relation - 1360 - 5450 - 80 - 90 + 1233 + 4914 + 72 + 81 lt=<. 60.0;70.0;10.0;70.0;10.0;10.0 @@ -7897,10 +7839,10 @@ bg=pink UMLClass - 1510 - 5570 - 100 - 60 + 1368 + 5022 + 90 + 54 *Mean* @@ -7910,10 +7852,10 @@ bg=pink Relation - 1470 - 5540 - 30 - 80 + 1332 + 4995 + 27 + 72 lt=<<- 10.0;10.0;10.0;60.0 @@ -7921,10 +7863,10 @@ bg=pink Relation - 1470 - 5590 - 60 - 30 + 1332 + 5040 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -7932,10 +7874,10 @@ bg=pink UMLClass - 6250 - 4080 - 140 - 90 + 5634 + 3681 + 126 + 81 *Rectangle* bg=pink @@ -7948,10 +7890,10 @@ height : float UMLClass - 6070 - 4080 - 140 - 60 + 5472 + 3681 + 126 + 54 *MatchToSprite* @@ -7961,10 +7903,10 @@ bg=pink UMLClass - 6160 - 3980 - 140 - 60 + 5553 + 3591 + 126 + 54 *SelectionBox* @@ -7974,10 +7916,10 @@ bg=pink Relation - 6220 - 3940 - 30 - 60 + 5607 + 3555 + 27 + 54 lt=<. 10.0;40.0;10.0;10.0 @@ -7985,10 +7927,10 @@ bg=pink Relation - 6260 - 4030 - 30 - 70 + 5643 + 3636 + 27 + 63 lt=<<- 10.0;10.0;10.0;50.0 @@ -7996,10 +7938,10 @@ bg=pink Relation - 6180 - 4030 - 30 - 70 + 5571 + 3636 + 27 + 63 lt=<<- 10.0;10.0;10.0;50.0 @@ -8007,10 +7949,10 @@ bg=pink UMLClass - 1440 - 1760 - 170 - 80 + 1305 + 1593 + 153 + 72 *Stacked* bg=pink @@ -8022,10 +7964,10 @@ stack_limit : int Relation - 1650 - 1620 - 30 - 290 + 1494 + 1467 + 27 + 261 lt=<<- 10.0;10.0;10.0;270.0 @@ -8033,10 +7975,10 @@ stack_limit : int Relation - 5970 - 4600 - 60 - 30 + 5382 + 4149 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -8044,10 +7986,10 @@ stack_limit : int UMLClass - 1290 - 3320 - 120 - 60 + 1170 + 2997 + 108 + 54 *NyanPatch* @@ -8057,10 +7999,10 @@ bg=pink Relation - 1230 - 3340 - 80 - 30 + 1116 + 3015 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -8068,10 +8010,10 @@ bg=pink UMLClass - 1820 - 4630 - 220 - 80 + 1647 + 4176 + 198 + 72 *AttributeAbovePercentage* bg=pink @@ -8084,10 +8026,10 @@ threshold : float Relation - 1770 - 4750 - 50 - 30 + 1602 + 4284 + 45 + 27 lt=- 30.0;10.0;10.0;10.0 @@ -8095,10 +8037,10 @@ threshold : float Relation - 2040 - 3540 - 90 - 30 + 1845 + 3195 + 81 + 27 lt=- 70.0;10.0;10.0;10.0 @@ -8106,10 +8048,10 @@ threshold : float UMLClass - 2110 - 3420 - 190 - 80 + 1908 + 3087 + 171 + 72 *Palette* bg=pink @@ -8121,10 +8063,10 @@ palette : file Relation - 2040 - 3450 - 90 - 30 + 1845 + 3114 + 81 + 27 lt=- 70.0;10.0;10.0;10.0 @@ -8132,10 +8074,10 @@ palette : file Relation - 4550 - 3860 - 70 - 30 + 4104 + 3483 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -8143,10 +8085,10 @@ palette : file UMLClass - 4600 - 4000 - 160 - 80 + 4149 + 3609 + 144 + 72 *Guard* bg=pink @@ -8158,10 +8100,10 @@ range : float Relation - 4550 - 4030 - 70 - 30 + 4104 + 3636 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -8169,10 +8111,10 @@ range : float UMLClass - 3570 - 2980 - 210 - 80 + 3222 + 2691 + 189 + 72 *Replace* bg=pink @@ -8184,10 +8126,10 @@ game_entities : set(GameEntity) Relation - 3520 - 3010 - 70 - 30 + 3177 + 2718 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -8195,10 +8137,10 @@ game_entities : set(GameEntity) UMLClass - 1070 - 4000 - 120 - 60 + 972 + 3609 + 108 + 54 *TechType* @@ -8208,10 +8150,10 @@ bg=pink Relation - 1180 - 4020 - 80 - 30 + 1071 + 3627 + 72 + 27 lt=- 10.0;10.0;60.0;10.0 @@ -8219,10 +8161,10 @@ bg=pink Relation - 1120 - 4050 - 30 - 70 + 1017 + 3654 + 27 + 63 lt=<. 10.0;10.0;10.0;50.0 @@ -8230,10 +8172,10 @@ bg=pink UMLClass - 1090 - 3910 - 100 - 60 + 990 + 3528 + 90 + 54 *Any* @@ -8243,10 +8185,10 @@ bg=pink Relation - 1130 - 3960 - 30 - 60 + 1026 + 3573 + 27 + 54 lt=<<- 10.0;40.0;10.0;10.0 @@ -8254,10 +8196,10 @@ bg=pink UMLClass - 1410 - 3990 - 100 - 60 + 1278 + 3600 + 90 + 54 *Any* @@ -8267,10 +8209,10 @@ bg=pink Relation - 1450 - 3950 - 30 - 60 + 1314 + 3564 + 27 + 54 lt=<<- 10.0;10.0;10.0;40.0 @@ -8278,10 +8220,10 @@ bg=pink UMLClass - 720 - 3480 - 100 - 60 + 657 + 3141 + 90 + 54 *Any* @@ -8291,10 +8233,10 @@ bg=pink Relation - 810 - 3500 - 70 - 70 + 738 + 3159 + 63 + 63 lt=<<- 50.0;50.0;50.0;10.0;10.0;10.0 @@ -8302,10 +8244,10 @@ bg=pink UMLClass - 2460 - 4570 - 230 - 80 + 2223 + 4122 + 207 + 72 *AnimationOverlay* bg=pink @@ -8318,10 +8260,10 @@ overlays : set(Animation) Relation - 2420 - 4600 - 60 - 30 + 2187 + 4149 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -8329,10 +8271,10 @@ overlays : set(Animation) UMLClass - 1820 - 4540 - 220 - 80 + 1647 + 4095 + 198 + 72 *AttributeBelowPercentage* bg=pink @@ -8345,10 +8287,10 @@ threshold : float UMLClass - 1560 - 4720 - 220 - 80 + 1413 + 4257 + 198 + 72 *AttributeAboveValue* bg=pink @@ -8361,10 +8303,10 @@ threshold : float Relation - 1790 - 4570 - 50 - 30 + 1620 + 4122 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -8372,10 +8314,10 @@ threshold : float Relation - 1790 - 4660 - 50 - 30 + 1620 + 4203 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -8383,10 +8325,10 @@ threshold : float UMLClass - 370 - 420 - 320 - 70 + 342 + 387 + 288 + 63 *ElevationDifferenceHigh* bg=yellow @@ -8398,10 +8340,10 @@ min_elevation_difference : optional(float) = None Relation - 680 - 450 - 60 - 30 + 621 + 414 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -8409,10 +8351,10 @@ min_elevation_difference : optional(float) = None Relation - 680 - 520 - 60 - 30 + 621 + 477 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -8420,10 +8362,10 @@ min_elevation_difference : optional(float) = None UMLClass - 1530 + 1386 90 - 320 - 80 + 288 + 72 *ElevationDifferenceHigh* bg=yellow @@ -8435,10 +8377,10 @@ min_elevation_difference : optional(float) = None Relation - 1490 - 120 - 60 - 30 + 1350 + 117 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -8446,10 +8388,10 @@ min_elevation_difference : optional(float) = None UMLClass - 370 - 4140 - 130 - 60 + 342 + 3735 + 117 + 54 *MiscVariant* @@ -8459,10 +8401,10 @@ bg=pink Relation - 490 - 3970 - 180 - 220 + 450 + 3582 + 162 + 198 lt=- 160.0;10.0;160.0;200.0;10.0;200.0 @@ -8470,10 +8412,10 @@ bg=pink UMLClass - 4630 - 3330 - 240 - 80 + 4176 + 3006 + 216 + 72 *ResourceStorage* bg=green @@ -8485,10 +8427,10 @@ containers : set(ResourceContainer) Relation - 4860 - 3360 - 60 - 30 + 4383 + 3033 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -8496,10 +8438,10 @@ containers : set(ResourceContainer) UMLClass - 4360 - 3330 - 240 - 100 + 3933 + 3006 + 216 + 90 *ResourceContainer* bg=pink @@ -8513,10 +8455,10 @@ carry_progress : set(Progress) Relation - 4590 - 3360 - 60 - 30 + 4140 + 3033 + 54 + 27 lt=<. 10.0;10.0;40.0;10.0 @@ -8524,10 +8466,10 @@ carry_progress : set(Progress) UMLClass - 4390 - 3460 - 180 - 80 + 3960 + 3123 + 162 + 72 *InternalDropSite* bg=pink @@ -8539,10 +8481,10 @@ update_time : float Relation - 4470 - 3420 - 30 - 60 + 4032 + 3087 + 27 + 54 lt=<<- 10.0;10.0;10.0;40.0 @@ -8550,10 +8492,10 @@ update_time : float UMLClass - 1260 - 3010 - 140 - 60 + 1143 + 2718 + 126 + 54 *TransformPool* @@ -8563,10 +8505,10 @@ bg=pink Relation - 1390 - 3030 - 50 - 30 + 1260 + 2736 + 45 + 27 lt=<. 10.0;10.0;30.0;10.0 @@ -8574,10 +8516,10 @@ bg=pink Relation - 4450 - 4510 - 60 - 30 + 4014 + 4068 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -8585,10 +8527,10 @@ bg=pink UMLClass - 2360 - 3320 - 100 - 60 + 2133 + 2997 + 90 + 54 *Any* @@ -8598,10 +8540,10 @@ bg=pink Relation - 2400 - 3280 - 30 - 60 + 2169 + 2961 + 27 + 54 lt=- 10.0;40.0;10.0;10.0 @@ -8609,10 +8551,10 @@ bg=pink Relation - 2520 - 3280 - 30 - 60 + 2277 + 2961 + 27 + 54 lt=- 10.0;40.0;10.0;10.0 @@ -8620,10 +8562,10 @@ bg=pink UMLClass - 4300 - 2310 - 410 - 100 + 3879 + 2088 + 369 + 90 *ExchangeRate* bg=pink @@ -8637,10 +8579,10 @@ price_pool : optional(PricePool) = None UMLClass - 4370 - 2460 - 260 - 110 + 3942 + 2223 + 234 + 99 *ExchangeResources* bg=green @@ -8655,10 +8597,10 @@ exchange_modes : set(ExchangeMode) Relation - 4470 - 2180 - 30 - 150 + 4032 + 1971 + 27 + 135 lt=<. 10.0;10.0;10.0;130.0 @@ -8666,10 +8608,10 @@ exchange_modes : set(ExchangeMode) UMLClass - 4670 - 2470 - 160 - 80 + 4212 + 2232 + 144 + 72 *ExchangeMode* bg=pink @@ -8681,10 +8623,10 @@ fee_multiplier : float Relation - 4620 - 2500 - 70 - 30 + 4167 + 2259 + 63 + 27 lt=<. 50.0;10.0;10.0;10.0 @@ -8692,10 +8634,10 @@ fee_multiplier : float UMLClass - 4910 - 2440 - 100 - 60 + 4428 + 2205 + 90 + 54 *Sell* @@ -8705,10 +8647,10 @@ bg=pink UMLClass - 4910 - 2510 - 100 - 60 + 4428 + 2268 + 90 + 54 *Buy* @@ -8718,10 +8660,10 @@ bg=pink Relation - 4820 - 2480 - 110 - 30 + 4347 + 2241 + 99 + 27 lt=<<- 10.0;10.0;90.0;10.0 @@ -8729,10 +8671,10 @@ bg=pink Relation - 4820 - 2510 - 110 - 30 + 4347 + 2268 + 99 + 27 lt=<<- 10.0;10.0;90.0;10.0 @@ -8740,10 +8682,10 @@ bg=pink Relation - 1710 - 4090 - 30 - 80 + 1548 + 3690 + 27 + 72 lt=<<- 10.0;10.0;10.0;60.0 @@ -8751,10 +8693,10 @@ bg=pink Relation - 1830 - 4050 - 70 - 30 + 1656 + 3654 + 63 + 27 lt=<<- 10.0;10.0;50.0;10.0 @@ -8762,10 +8704,10 @@ bg=pink UMLClass - 1880 - 4020 - 190 - 80 + 1701 + 3627 + 171 + 72 *LogicGate* bg=pink @@ -8777,10 +8719,10 @@ inputs : set(LogicElement) UMLClass - 1990 - 4350 - 140 - 80 + 1800 + 3924 + 126 + 72 *SUBSETMAX* bg=pink @@ -8792,10 +8734,10 @@ size : int Relation - 1960 - 4380 - 50 - 30 + 1773 + 3951 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -8803,10 +8745,10 @@ size : int UMLClass - 1850 - 4120 - 100 - 60 + 1674 + 3717 + 90 + 54 *NOT* @@ -8816,10 +8758,10 @@ bg=pink Relation - 1940 - 4140 - 50 - 30 + 1755 + 3735 + 45 + 27 lt=- 30.0;10.0;10.0;10.0 @@ -8827,10 +8769,10 @@ bg=pink UMLClass - 1850 - 4190 - 100 - 60 + 1674 + 3780 + 90 + 54 *XOR* @@ -8840,10 +8782,10 @@ bg=pink Relation - 1960 - 4290 - 50 - 30 + 1773 + 3870 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -8851,10 +8793,10 @@ bg=pink UMLClass - 1850 - 4260 - 100 - 60 + 1674 + 3843 + 90 + 54 *MULTIXOR* @@ -8864,10 +8806,10 @@ bg=pink Relation - 1940 - 4280 - 50 - 30 + 1755 + 3861 + 45 + 27 lt=- 30.0;10.0;10.0;10.0 @@ -8875,10 +8817,10 @@ bg=pink UMLClass - 3510 - 3650 - 150 - 60 + 3168 + 3294 + 135 + 54 *AbilityProperty* @@ -8888,10 +8830,10 @@ bg=pink Relation - 3570 - 3620 - 30 - 50 + 3222 + 3267 + 27 + 45 lt=- 10.0;10.0;10.0;30.0 @@ -8899,10 +8841,10 @@ bg=pink Relation - 3570 - 4130 - 70 - 30 + 3222 + 3726 + 63 + 27 lt=- 50.0;10.0;10.0;10.0 @@ -8910,10 +8852,10 @@ bg=pink UMLClass - 3620 - 4190 - 220 - 80 + 3267 + 3780 + 180 + 72 *Lock* bg=pink @@ -8925,10 +8867,10 @@ lock_pool : LockPool Relation - 3570 - 4220 - 70 - 30 + 3222 + 3807 + 63 + 27 lt=- 50.0;10.0;10.0;10.0 @@ -8936,10 +8878,10 @@ lock_pool : LockPool UMLClass - 5100 - 4010 - 190 - 80 + 4599 + 3618 + 171 + 72 *Lock* bg=green @@ -8951,10 +8893,10 @@ lock_pools : set(LockPool) Relation - 5060 - 4040 - 60 - 30 + 4563 + 3645 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -8962,10 +8904,10 @@ lock_pools : set(LockPool) UMLClass - 5330 - 4010 - 150 - 80 + 4806 + 3618 + 135 + 72 *LockPool* bg=pink @@ -8977,10 +8919,10 @@ slots : int Relation - 5280 - 4040 - 70 - 30 + 4761 + 3645 + 63 + 27 lt=<. 50.0;10.0;10.0;10.0 @@ -8988,10 +8930,10 @@ slots : int UMLClass - 1550 - 1570 - 160 - 60 + 1404 + 1422 + 144 + 54 *ModifierProperty* @@ -9001,10 +8943,10 @@ bg=pink Relation - 1700 - 1590 - 100 - 30 + 1539 + 1440 + 90 + 27 lt=- 80.0;10.0;10.0;10.0 @@ -9012,10 +8954,10 @@ bg=pink Relation - 1600 - 1690 - 80 - 30 + 1449 + 1530 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -9023,10 +8965,10 @@ bg=pink Relation - 1600 - 1790 - 80 - 30 + 1449 + 1620 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -9034,10 +8976,10 @@ bg=pink UMLClass - 1440 - 1850 - 170 - 80 + 1305 + 1674 + 153 + 72 *Multiplier* bg=pink @@ -9049,10 +8991,10 @@ multiplier : float Relation - 1600 - 1880 - 80 - 30 + 1449 + 1701 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -9060,10 +9002,10 @@ multiplier : float Relation - 2330 - 890 - 80 - 30 + 2106 + 810 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -9071,10 +9013,10 @@ multiplier : float UMLNote - 1080 - 450 - 60 - 30 + 981 + 414 + 54 + 27 effect bg=blue @@ -9083,10 +9025,10 @@ bg=blue UMLNote - 1270 - 450 - 90 - 30 + 1152 + 414 + 81 + 27 resistance bg=blue @@ -9095,10 +9037,10 @@ bg=blue UMLNote - 730 - 290 - 60 - 30 + 666 + 270 + 54 + 27 flac bg=blue @@ -9107,10 +9049,10 @@ bg=blue UMLNote - 1390 - 170 - 60 - 30 + 1260 + 162 + 54 + 27 flac bg=blue @@ -9119,10 +9061,10 @@ bg=blue UMLClass - 690 - 5490 - 140 - 60 + 630 + 4950 + 126 + 54 *EffectProperty* @@ -9132,10 +9074,10 @@ bg=pink Relation - 820 - 5510 - 170 - 30 + 747 + 4968 + 153 + 27 lt=- 10.0;10.0;150.0;10.0 @@ -9143,10 +9085,10 @@ bg=pink Relation - 750 - 5130 - 30 - 380 + 684 + 4626 + 27 + 342 lt=<<- 10.0;360.0;10.0;10.0 @@ -9154,10 +9096,10 @@ bg=pink UMLClass - 520 - 5370 - 200 - 80 + 477 + 4842 + 180 + 72 *Cost* bg=pink @@ -9169,10 +9111,10 @@ cost : Cost UMLClass - 480 - 5280 - 240 - 80 + 441 + 4761 + 216 + 72 *Diplomatic* bg=pink @@ -9184,10 +9126,10 @@ stances : set(DiplomaticStance) Relation - 890 - 4720 - 100 - 30 + 810 + 4257 + 90 + 27 lt=- 10.0;10.0;80.0;10.0 @@ -9195,10 +9137,10 @@ stances : set(DiplomaticStance) UMLClass - 550 - 5190 - 170 - 80 + 504 + 4680 + 153 + 72 *AreaEffect* bg=pink @@ -9211,10 +9153,10 @@ dropoff : DropoffType Relation - 710 - 5220 - 70 - 30 + 648 + 4707 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -9222,10 +9164,10 @@ dropoff : DropoffType Relation - 710 - 5310 - 70 - 30 + 648 + 4788 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -9233,10 +9175,10 @@ dropoff : DropoffType Relation - 710 - 5400 - 70 - 30 + 648 + 4869 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -9244,10 +9186,10 @@ dropoff : DropoffType UMLClass - 1120 - 5490 - 150 - 60 + 1017 + 4950 + 135 + 54 *ResistanceProperty* @@ -9257,10 +9199,10 @@ bg=pink Relation - 960 - 5510 - 180 - 30 + 873 + 4968 + 162 + 27 lt=- 10.0;10.0;160.0;10.0 @@ -9268,10 +9210,10 @@ bg=pink Relation - 1180 - 5310 - 30 - 200 + 1071 + 4788 + 27 + 180 lt=<<- 10.0;180.0;10.0;10.0 @@ -9279,10 +9221,10 @@ bg=pink Relation - 2420 - 4530 - 30 - 460 + 2187 + 4086 + 27 + 414 lt=<<- 10.0;10.0;10.0;440.0 @@ -9290,10 +9232,10 @@ bg=pink UMLClass - 2320 - 4480 - 160 - 60 + 2097 + 4041 + 144 + 54 *ProgressProperty* @@ -9304,10 +9246,10 @@ bg=pink Relation - 2140 - 4500 - 200 - 30 + 1935 + 4059 + 180 + 27 lt=- 10.0;10.0;180.0;10.0 @@ -9315,10 +9257,10 @@ bg=pink Relation - 1960 - 3740 - 30 - 80 + 1773 + 3375 + 27 + 72 lt=<<- 10.0;10.0;10.0;60.0 @@ -9326,10 +9268,10 @@ bg=pink UMLClass - 1920 - 3800 - 100 - 60 + 1737 + 3429 + 90 + 54 *Reset* @@ -9339,10 +9281,10 @@ bg=pink UMLClass - 1280 - 3080 - 100 - 60 + 1161 + 2781 + 90 + 54 *Reset* @@ -9352,10 +9294,10 @@ bg=pink Relation - 1370 - 3100 - 70 - 30 + 1242 + 2799 + 63 + 27 lt=<<- 50.0;10.0;10.0;10.0 @@ -9363,10 +9305,10 @@ bg=pink UMLClass - 1590 - 5060 - 190 - 80 + 1440 + 4563 + 171 + 72 *StateChangeActive* bg=pink @@ -9378,10 +9320,10 @@ state_change : StateChanger Relation - 1770 - 5090 - 50 - 30 + 1602 + 4590 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -9389,10 +9331,10 @@ state_change : StateChanger UMLClass - 1560 - 4810 - 220 - 80 + 1413 + 4338 + 198 + 72 *OwnsGameEntity* bg=pink @@ -9404,10 +9346,10 @@ game_entity : GameEntity Relation - 1770 - 4840 - 50 - 30 + 1602 + 4365 + 45 + 27 lt=- 30.0;10.0;10.0;10.0 @@ -9415,10 +9357,10 @@ game_entity : GameEntity UMLClass - 6520 - 2910 - 330 - 80 + 5877 + 2628 + 297 + 72 *EffectBatch* bg=pink @@ -9432,10 +9374,10 @@ properties : dict(BatchProperty, BatchProperty) = {} UMLClass - 6910 - 2920 - 150 - 60 + 6228 + 2637 + 135 + 54 *BatchProperty* @@ -9446,10 +9388,10 @@ bg=pink UMLClass - 6980 - 3010 - 170 - 80 + 6291 + 2718 + 153 + 72 *Priority* bg=pink @@ -9462,10 +9404,10 @@ priority : int Relation - 6940 - 2970 - 30 - 190 + 6255 + 2682 + 27 + 171 lt=<<- 10.0;10.0;10.0;170.0 @@ -9473,10 +9415,10 @@ priority : int Relation - 6940 - 3040 - 60 - 30 + 6255 + 2745 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9484,10 +9426,10 @@ priority : int UMLClass - 6980 - 3100 - 170 - 80 + 6291 + 2799 + 153 + 72 *Chance* bg=pink @@ -9500,10 +9442,10 @@ chance : float Relation - 6940 - 3130 - 60 - 30 + 6255 + 2826 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9511,10 +9453,10 @@ chance : float Relation - 6840 - 2940 - 90 - 30 + 6165 + 2655 + 81 + 27 lt=<. 70.0;10.0;10.0;10.0 @@ -9522,10 +9464,10 @@ chance : float Relation - 6460 - 2870 - 80 - 100 + 5823 + 2592 + 72 + 90 lt=<. 60.0;80.0;10.0;80.0;10.0;10.0 @@ -9533,10 +9475,10 @@ chance : float UMLClass - 6620 - 3010 - 150 - 60 + 5967 + 2718 + 135 + 54 *UnorderedBatch* @@ -9547,10 +9489,10 @@ bg=pink Relation - 6580 - 2980 - 30 - 220 + 5931 + 2691 + 27 + 198 lt=<<- 10.0;10.0;10.0;200.0 @@ -9558,10 +9500,10 @@ bg=pink Relation - 6580 - 3030 - 60 - 30 + 5931 + 2736 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9569,10 +9511,10 @@ bg=pink UMLClass - 6620 - 3080 - 150 - 60 + 5967 + 2781 + 135 + 54 *OrderedBatch* @@ -9584,10 +9526,10 @@ bg=pink Relation - 6580 - 3100 - 60 - 30 + 5931 + 2799 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9595,10 +9537,10 @@ bg=pink UMLClass - 6620 - 3150 - 150 - 60 + 5967 + 2844 + 135 + 54 *ChainedBatch* @@ -9609,10 +9551,10 @@ bg=pink Relation - 6580 - 3170 - 60 - 30 + 5931 + 2862 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9620,10 +9562,10 @@ bg=pink UMLClass - 520 - 5100 - 200 - 80 + 477 + 4599 + 180 + 72 *Priority* bg=pink @@ -9635,10 +9577,10 @@ priority : int Relation - 710 - 5130 - 70 - 30 + 648 + 4626 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -9646,10 +9588,10 @@ priority : int UMLClass - 1380 - 3410 - 120 - 60 + 1251 + 3078 + 108 + 54 *PatchProperty* @@ -9659,10 +9601,10 @@ bg=pink Relation - 1490 - 3430 - 60 - 30 + 1350 + 3096 + 54 + 27 lt=<<- 10.0;10.0;40.0;10.0 @@ -9670,10 +9612,10 @@ bg=pink Relation - 1350 - 3370 - 30 - 180 + 1224 + 3042 + 27 + 162 lt=<. 10.0;10.0;10.0;160.0 @@ -9681,10 +9623,10 @@ bg=pink Relation - 1430 - 3460 - 30 - 90 + 1296 + 3123 + 27 + 81 lt=<. 10.0;10.0;10.0;70.0 @@ -9692,10 +9634,10 @@ bg=pink Relation - 1610 - 4040 - 50 - 30 + 1458 + 3645 + 45 + 27 lt=<<- 30.0;10.0;10.0;10.0 @@ -9703,10 +9645,10 @@ bg=pink UMLClass - 1520 - 4070 - 100 - 60 + 1377 + 3672 + 90 + 54 *True* @@ -9716,10 +9658,10 @@ bg=pink UMLClass - 1520 - 4000 - 100 - 60 + 1377 + 3609 + 90 + 54 *False* @@ -9729,10 +9671,10 @@ bg=pink Relation - 1610 - 4070 - 50 - 30 + 1458 + 3672 + 45 + 27 lt=<<- 30.0;10.0;10.0;10.0 @@ -9740,10 +9682,10 @@ bg=pink Relation - 1490 - 30 - 60 - 30 + 1350 + 36 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9751,10 +9693,10 @@ bg=pink Relation - 1490 - 210 - 60 - 30 + 1350 + 198 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9762,10 +9704,10 @@ bg=pink UMLClass - 160 - 3630 - 120 - 60 + 153 + 3276 + 108 + 54 *Tree* @@ -9774,10 +9716,10 @@ bg=pink UMLClass - 160 - 3700 - 120 - 60 + 153 + 3339 + 108 + 54 *Relic* @@ -9786,10 +9728,10 @@ bg=pink Relation - 300 - 3620 - 130 - 30 + 279 + 3267 + 117 + 27 lt=<<- 110.0;10.0;10.0;10.0 @@ -9797,10 +9739,10 @@ bg=pink Relation - 300 - 3510 - 30 - 310 + 279 + 3168 + 27 + 279 lt=- 10.0;10.0;10.0;290.0 @@ -9808,10 +9750,10 @@ bg=pink Relation - 270 - 3720 - 60 - 30 + 252 + 3357 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9819,10 +9761,10 @@ bg=pink UMLClass - 160 - 3560 - 120 - 60 + 153 + 3213 + 108 + 54 *Swordsman* @@ -9831,10 +9773,10 @@ bg=pink UMLClass - 160 - 3490 - 120 - 60 + 153 + 3150 + 108 + 54 *Barracks* @@ -9843,10 +9785,10 @@ bg=pink Relation - 270 - 3650 - 60 - 30 + 252 + 3294 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9854,10 +9796,10 @@ bg=pink Relation - 270 - 3580 - 60 - 30 + 252 + 3231 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9865,10 +9807,10 @@ bg=pink Relation - 270 - 3510 - 60 - 30 + 252 + 3168 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9876,10 +9818,10 @@ bg=pink UMLClass - 160 - 3770 - 120 - 60 + 153 + 3402 + 108 + 54 *Projectile* @@ -9888,10 +9830,10 @@ bg=pink Relation - 270 - 3790 - 60 - 30 + 252 + 3420 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9899,10 +9841,10 @@ bg=pink UMLNote - 10 - 3490 - 140 - 70 + 18 + 3150 + 126 + 63 All ingame objects are game entities @@ -9912,10 +9854,10 @@ bg=blue UMLClass - 2450 - 4150 - 120 - 60 + 2214 + 3744 + 108 + 54 *Construct* @@ -9925,10 +9867,10 @@ bg=pink UMLClass - 2450 - 4220 - 120 - 60 + 2214 + 3807 + 108 + 54 *Harvest* @@ -9938,10 +9880,10 @@ bg=pink UMLClass - 2450 - 4290 - 120 - 60 + 2214 + 3870 + 108 + 54 *Restock* @@ -9951,10 +9893,10 @@ bg=pink UMLClass - 2450 - 4080 - 120 - 60 + 2214 + 3681 + 108 + 54 *Carry* @@ -9964,10 +9906,10 @@ bg=pink UMLClass - 2450 - 4360 - 120 - 60 + 2214 + 3933 + 108 + 54 *Transform* @@ -9977,10 +9919,10 @@ bg=pink Relation - 2410 - 4030 - 30 - 380 + 2178 + 3636 + 27 + 342 lt=- 10.0;10.0;10.0;360.0 @@ -9988,10 +9930,10 @@ bg=pink Relation - 2410 - 4030 - 60 - 30 + 2178 + 3636 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9999,10 +9941,10 @@ bg=pink Relation - 2410 - 4100 - 60 - 30 + 2178 + 3699 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -10010,10 +9952,10 @@ bg=pink Relation - 2410 - 4170 - 60 - 30 + 2178 + 3762 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -10021,10 +9963,10 @@ bg=pink Relation - 2410 - 4240 - 60 - 30 + 2178 + 3825 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -10032,10 +9974,10 @@ bg=pink Relation - 2410 - 4310 - 60 - 30 + 2178 + 3888 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -10043,10 +9985,10 @@ bg=pink Relation - 2410 - 4380 - 60 - 30 + 2178 + 3951 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -10054,10 +9996,10 @@ bg=pink UMLClass - 5700 - 4670 - 250 - 80 + 5139 + 4212 + 225 + 72 *Constructable* bg=green @@ -10070,10 +10012,10 @@ construction_progress : set(Progress) Relation - 5940 - 4700 - 60 - 30 + 5355 + 4239 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -10081,10 +10023,10 @@ construction_progress : set(Progress) UMLClass - 2200 - 2230 - 110 - 60 + 1989 + 2016 + 99 + 54 *Node* @@ -10094,21 +10036,21 @@ bg=pink Relation - 2250 - 2280 - 30 - 280 + 2034 + 2061 + 27 + 342 lt=<<- - 10.0;10.0;10.0;260.0 + 10.0;10.0;10.0;360.0 UMLClass - 2070 - 2320 - 160 - 80 + 1872 + 2097 + 144 + 72 *Start* bg=pink @@ -10120,10 +10062,10 @@ next : Node Relation - 2220 - 2350 - 60 - 30 + 2007 + 2124 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -10131,10 +10073,10 @@ next : Node UMLClass - 1850 - 2320 - 150 - 80 + 1674 + 2097 + 135 + 72 *Activity* bg=pink @@ -10146,10 +10088,10 @@ start : Start Relation - 1990 - 2350 - 100 - 30 + 1800 + 2124 + 90 + 27 lt=<. 80.0;10.0;10.0;10.0 @@ -10157,10 +10099,10 @@ start : Start UMLClass - 2120 - 2420 - 110 - 60 + 1917 + 2187 + 99 + 54 *End* @@ -10170,10 +10112,10 @@ bg=pink Relation - 2220 - 2440 - 60 - 30 + 2007 + 2205 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -10181,10 +10123,10 @@ bg=pink UMLClass - 2290 - 2320 - 220 - 80 + 2070 + 2097 + 198 + 72 *XORGate* bg=pink @@ -10197,10 +10139,10 @@ default : Node UMLClass - 2290 - 2410 - 220 - 80 + 2070 + 2259 + 198 + 72 *XOREventGate* bg=pink @@ -10212,26 +10154,26 @@ next : dict(Event, Node) UMLClass - 2060 - 2500 - 170 - 90 + 1863 + 2259 + 153 + 81 *Ability* bg=pink -- next : Node -ability : abstract(Ability) +ability : Ability Relation - 2250 - 2350 - 60 - 30 + 2034 + 2124 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -10239,10 +10181,10 @@ ability : abstract(Ability) Relation - 2250 - 2440 - 60 - 30 + 2034 + 2286 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -10250,10 +10192,10 @@ ability : abstract(Ability) Relation - 2220 - 2530 - 60 - 30 + 2007 + 2286 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -10261,10 +10203,10 @@ ability : abstract(Ability) UMLClass - 3710 - 3360 - 180 - 90 + 3348 + 3033 + 162 + 81 *Activity* bg=green @@ -10276,10 +10218,10 @@ graph : Activity Relation - 3880 - 3390 - 60 - 30 + 3501 + 3060 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -10287,10 +10229,10 @@ graph : Activity UMLNote - 1790 - 2270 - 160 - 30 + 1620 + 2052 + 144 + 27 Unit behaviour graph bg=blue @@ -10299,10 +10241,10 @@ bg=blue UMLClass - 2560 - 2420 - 110 - 60 + 2349 + 2268 + 99 + 54 *Event* @@ -10312,21 +10254,21 @@ bg=pink Relation - 2500 - 2440 - 80 - 30 + 2259 + 2286 + 108 + 27 lt=<. - 60.0;10.0;10.0;10.0 + 100.0;10.0;10.0;10.0 Relation - 2580 - 2470 - 30 - 240 + 2367 + 2313 + 27 + 216 lt=<<- 10.0;10.0;10.0;220.0 @@ -10334,10 +10276,10 @@ bg=pink UMLClass - 2610 - 2500 - 160 - 80 + 2394 + 2340 + 144 + 72 *Wait* bg=pink @@ -10349,10 +10291,10 @@ time : float UMLClass - 2610 - 2590 - 120 - 60 + 2394 + 2421 + 108 + 54 *WaitAbility* @@ -10362,10 +10304,10 @@ bg=pink Relation - 2580 - 2530 - 50 - 30 + 2367 + 2367 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -10373,10 +10315,10 @@ bg=pink Relation - 2580 - 2610 - 50 - 30 + 2367 + 2439 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -10384,10 +10326,10 @@ bg=pink UMLClass - 2610 - 2660 - 160 - 60 + 2394 + 2484 + 144 + 54 *CommandInQueue* @@ -10397,10 +10339,10 @@ bg=pink Relation - 2580 - 2680 - 50 - 30 + 2367 + 2502 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -10408,10 +10350,10 @@ bg=pink UMLClass - 2520 - 2070 - 140 - 80 + 2196 + 1665 + 126 + 72 *Condition* bg=pink @@ -10423,21 +10365,21 @@ next : Node Relation - 2450 - 2100 - 90 - 240 + 2142 + 1692 + 72 + 423 lt=<. - 70.0;10.0;10.0;10.0;10.0;220.0 + 60.0;10.0;10.0;10.0;10.0;450.0 UMLClass - 2610 - 2170 - 160 - 60 + 2277 + 1755 + 144 + 54 *CommandInQueue* @@ -10447,21 +10389,21 @@ bg=pink Relation - 2580 - 2140 - 30 - 220 + 2250 + 1728 + 27 + 306 lt=<<- - 10.0;10.0;10.0;200.0 + 10.0;10.0;10.0;320.0 Relation - 2580 - 2190 - 50 - 30 + 2250 + 1773 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -10469,23 +10411,25 @@ bg=pink UMLClass - 2610 - 2240 - 160 - 60 + 2277 + 1818 + 144 + 72 - -*NextCommandIdle* -bg=pink + *NextCommand* +bg=pink + +-- +command : Command Relation - 2580 - 2260 - 50 - 30 + 2250 + 1836 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -10493,60 +10437,433 @@ bg=pink Relation - 1770 - 2350 - 100 - 30 + 1602 + 2124 + 90 + 27 lt=- 10.0;10.0;80.0;10.0 + + Relation + + 2250 + 1926 + 45 + 27 + + lt=- + 10.0;10.0;30.0;10.0 + UMLClass - 2610 - 2310 - 160 - 60 + 3915 + 3591 + 108 + 54 -*NextCommandMove* +*PathType* bg=pink Relation - 2580 - 2330 - 50 - 30 + 3960 + 3546 + 27 + 63 + + lt=<. + 10.0;50.0;10.0;10.0 + + + UMLClass + + 2070 + 2178 + 225 + 72 + + *XORSwitchGate* +bg=pink + +-- +switch : SwitchCondition +default : Node + + + + Relation + + 2034 + 2205 + 54 + 27 lt=- - 10.0;10.0;30.0;10.0 + 40.0;10.0;10.0;10.0 UMLClass - 4340 - 3980 - 120 - 60 + 2349 + 2187 + 126 + 54 -*PathType* +*SwitchCondition* bg=pink Relation - 4390 - 3930 - 30 - 70 + 2286 + 2205 + 81 + 27 + + lt=<. + 70.0;10.0;10.0;10.0 + + + UMLClass + + 2529 + 2178 + 180 + 72 + + *NextCommand* +bg=pink + +-- +next : dict(Command, Node) + + + + Relation + + 2466 + 2205 + 81 + 27 + + lt=<<- + 70.0;10.0;10.0;10.0 + + + UMLClass + + 1656 + 2466 + 99 + 54 + + +*Command* +bg=pink + + + + Relation + + 1602 + 2484 + 72 + 27 + + lt=- + 10.0;10.0;60.0;10.0 + + + UMLClass + + 1719 + 2601 + 99 + 54 + + +*Idle* +bg=pink + + + + UMLClass + + 1719 + 2664 + 99 + 54 + + +*Move* +bg=pink + + + + UMLClass + + 1719 + 2538 + 99 + 54 + + +*ApplyEffect* +bg=pink + + + + Relation + + 1683 + 2511 + 27 + 198 + + lt=<<- + 10.0;10.0;10.0;200.0 + + + Relation + + 1683 + 2556 + 54 + 27 + + lt=- + 10.0;10.0;40.0;10.0 + + + Relation + + 1683 + 2619 + 54 + 27 + + lt=- + 40.0;10.0;10.0;10.0 + + + Relation + + 1683 + 2682 + 54 + 27 + + lt=- + 10.0;10.0;40.0;10.0 + + + UMLClass + + 3267 + 3861 + 144 + 72 + + *Ranged* +bg=pink + +-- +min_range : float +max_range : float + + + + Relation + + 3222 + 3888 + 63 + 27 + + lt=- + 50.0;10.0;10.0;10.0 + + + UMLClass + + 2277 + 1899 + 162 + 72 + + *TargetInRange* +bg=pink + +-- +ability : Ability + + + + UMLClass + + 1863 + 2349 + 153 + 81 + + *Task* +bg=pink + +-- +next : Node +task : Task + + + + Relation + + 2007 + 2376 + 54 + 27 + + lt=- + 40.0;10.0;10.0;10.0 + + + UMLClass + + 1926 + 2466 + 90 + 54 + + +*Task* +bg=pink + + + + Relation + + 1962 + 2421 + 27 + 63 lt=<. 10.0;50.0;10.0;10.0 + + Relation + + 1962 + 2511 + 27 + 198 + + lt=<<- + 10.0;10.0;10.0;200.0 + + + UMLClass + + 1989 + 2538 + 162 + 54 + + +*PopCommandQueue* +bg=pink + + + + Relation + + 1962 + 2556 + 45 + 27 + + lt=- + 10.0;10.0;30.0;10.0 + + + UMLClass + + 1989 + 2601 + 162 + 54 + + +*ClearCommandQueue* +bg=pink + + + + Relation + + 1962 + 2619 + 45 + 27 + + lt=- + 10.0;10.0;30.0;10.0 + + + UMLClass + + 1989 + 2664 + 126 + 54 + + +*MoveToTarget* +bg=pink + + + + Relation + + 1962 + 2682 + 45 + 27 + + lt=- + 10.0;10.0;30.0;10.0 + + + UMLClass + + 2277 + 1980 + 162 + 72 + + *AbilityUsable* +bg=pink + +-- +ability : Ability + + + + Relation + + 2250 + 2007 + 45 + 27 + + lt=- + 10.0;10.0;30.0;10.0 + diff --git a/doc/nyan/api_reference/reference_ability.md b/doc/nyan/api_reference/reference_ability.md index dfddc1c82f..2bb20fa6f2 100644 --- a/doc/nyan/api_reference/reference_ability.md +++ b/doc/nyan/api_reference/reference_ability.md @@ -127,6 +127,26 @@ If the lock pool the ability with this property cannot become active. +## ability.property.type.Ranged + +```python +Ranged(AbilityProperty): + min_range : float + max_range : float +``` + +Abilities with this property can only be used within a specified range around the game entity. The property mostly affects abilities that are *targeted*, i.e. that are used on other game entities or locations in the game world. + +If the target of the ability is another game entity and said game entity has a `Collision` ability, the range check factors in the `Hitbox` boundaries of the targeted game entity when calculating the distance. + +Without this property, abilities behave as if `min_range` and `max_range` are `0.0`. + +**min_range** +Minimum distance to the target of the ability. + +**max_range** +Maximum distance to the target of the ability. + ## ability.type.ActiveTransformTo ```python @@ -327,16 +347,12 @@ Alters the abilities and modifiers of a game entity after the despawn condition ```python DetectCloak(Ability): - range : float allowed_types : set(children(GameEntityType)) blacklisted_entities : set(GameEntity) ``` Enables the game entity to decloak other game entities which use the `Cloak` ability. -**range** -Range around the game entity in which other game entities will be decloaked. - **allowed_types** Whitelist of game entity types that can be decloaked. @@ -543,7 +559,6 @@ Determines whether the resource spot is harvestable when it is created. If `True ```python Herd(Ability): - range : float strength : int allowed_types : set(children(GameEntityType)) blacklisted_entities : set(GameEntity) @@ -551,9 +566,6 @@ Herd(Ability): Allows a game entity to change the ownership of other game entities with the `Herdable` ability. -**range** -Minimum distance to a herdable game entity to make it change ownership. - **strength** Comparison value for situations when the game entity competes with other game entities for a herdable. The game entity with the highest `strength` value will always be prefered, even if other game entities fulfill the condition set by `mode` in `Herdable` better. @@ -787,38 +799,6 @@ RallyPoint(Ability): Allows a game entity to set a rally point on the map. Game entities spawned by the `Create` ability or ejected from a container will move to the rally point location. The rally point can be placed on another game entity. In that case, the game entities moving there will try to use an appropriate ability on it. -## ability.type.RangedContinuousEffect - -```python -RangedContinuousEffect(ApplyContinuousEffect): - min_range : int - max_range : int -``` - -Applies continuous effects on another game entity. This specialization of `ApplyContinuousEffect` allows ranged application. - -**min_range** -Minimum distance to target. - -**max_range** -Maximum distance to the target. - -## ability.type.RangedDiscreteEffect - -```python -RangedDiscreteEffect(ApplyDiscreteEffect): - min_range : int - max_range : int -``` - -Applies batches of discrete effects on another game entity. This specialization of `ApplyDiscreteEffect` allows ranged application. - -**min_range** -Minimum distance to target. - -**max_range** -Maximum distance to the target. - ## ability.type.RegenerateAttribute ```python @@ -966,8 +946,6 @@ ShootProjectile(Ability): projectiles : orderedset(GameEntity) min_projectiles : int max_projectiles : int - min_range : int - max_range : int reload_time : float spawn_delay : float projectile_delay : float @@ -994,12 +972,6 @@ Minimum amount of projectiles spawned. **max_projectiles** Maximum amount of projectiles spawned. -**min_range** -Minimum distance to the targeted game entity. - -**max_range** -Maximum distance to the targeted game entity. - **reload_time** Time until the ability can be used again in seconds. The timer starts after the *last* projectile has been fired. diff --git a/doc/nyan/api_reference/reference_util.md b/doc/nyan/api_reference/reference_util.md index dd9646e862..7667a1a10a 100644 --- a/doc/nyan/api_reference/reference_util.md +++ b/doc/nyan/api_reference/reference_util.md @@ -54,6 +54,18 @@ Generalization object for conditions that can be used in `XORGate` nodes. **node** Node that is visited when the condition is true. +## util.activity.condition.type.AbilityUsable + +```python +AbilityUsable(Condition): + ability : abstract(Ability) +``` + +Is true when an ability can be used by the game entity when the node is visited. + +**ability** +Ability definition used for the usability check. This can reference a specific ability of the game entity or an abstract API object from the `engine.ability.type` namespace. If a specific ability is referenced, the ability must be assigned to the game entity **and** be enabled for the check to pass. If an API object is referenced, at least one ability of the same type must be enabled for the check to pass. + ## util.activity.condition.type.CommandInQueue ```python @@ -61,25 +73,35 @@ CommandInQueue(Condition): pass ``` -Is true when the command queue is not empty when the node is visited. +Is true when the game entity's command queue is not empty when the node is visited. -## util.activity.condition.type.NextCommandIdle +## util.activity.condition.type.NextCommand ```python -NextCommandIdle(Condition): - pass +NextCommand(Condition): + command : children(Command) ``` -Is true when the next command in the queue is of type `Idle`. +Is true when the next command in the game entity's command queue is of a specific type. -## util.activity.condition.type.NextCommandMove +**command** +Command type checked by the condition. + +## util.activity.condition.type.TargetInRange ```python -NextCommandMove(Condition): - pass +TargetInRange(Condition): + ability : abstract(Ability) ``` -Is true when the next command in the queue is of type `Move`. +Is true when the target of the next command in the game entity's command queue is in range of an ability. + +**ability** +Ability definition used for the range check. + +This can reference a specific ability of the game entity or an abstract API object from the `engine.ability.type` namespace. If a specific ability is referenced, the ability must be assigned to the game entity and must be enabled. Otherwise, the range check fails. If an API object is referenced, the first active ability with the same type as the API object is executed. + +If the ability has the property `Ranged`, the attributes of this property are utilized for the range check calculations. If the ability does not have a `Ranged` property, the condition is only true when the game entity is at the same position as the target. ## util.activity.event.Event @@ -149,7 +171,7 @@ Next node in the activity graph. **ability** Ability that is executed. -This can reference a specific ability of the game entity or an abstract API object from the `engine.ability.type` namespace. If a specific ability is referenced, the ability must be assigned to the game entity and must not be disabled. Otherwise, the ability is not executed. If an API object is referenced, the first active ability with the same type as the API object is executed. +This can reference a specific ability of the game entity or an abstract API object from the `engine.ability.type` namespace. If a specific ability is referenced, the ability must be assigned to the game entity and must be enabled. Otherwise, the ability is not executed. If an API object is referenced, the first active ability with the same type as the API object is executed. ## util.activity.node.type.End @@ -172,6 +194,22 @@ Start of an activity. Does nothing but pointing to the next node. **next** Next node in the activity graph. +## util.activity.node.type.Task + +```python +Task(Node): + next : Node + task : children(Task) +``` + +Executes a task on the game entity when the node is visited. + +**next** +Next node in the activity graph. + +**task** +Task that is executed. + ## util.activity.node.type.XOREventGate ```python @@ -195,11 +233,84 @@ XORGate(Node): Gateway that branches the activity graph depending on the result of conditional queries. Queries are executed immediately when the node is visited. **next** -Mapping of conditional queries to the next node in the activity graph. The first query that evaluates to true is used to determine the next node. If no query evaluates to true, the `default` node is used. +Mapping of conditional queries to the next node in the activity graph. The first query that evaluates to true is used to determine the next node. If no query evaluates to true, the `default` node is used as fallback. **default** Default node that is used if no query evaluates to true. +## util.activity.node.type.XORSwitchGate + +```python +XORSwitchGate(Node): + switch : children(SwitchCondition) + default : Node +``` + +Gateway that branches the activity graph depending on the value of a runtime parameter. In comparison to `XORGate`, only one conditional query is done based on the value (similar to the behaviour of a [switch statement](https://en.wikipedia.org/wiki/Switch_statement)). The query is executed immediately when the node is visited. + +**switch** +Defines which runtime parameter is checked as well as the mapping of parameter value to the next node in the activity graph. If a value is encountered at query execution time that is not associated with a node, the `default` node is used as fallback. + +**default** +Default node that is used if a value does not have an associated node. + +## util.activity.switch_condition.SwitchCondition + +```python +SwitchCondition(Object): + pass +``` + +Generalization object for conditions that can be used in `XORSwitchGate` nodes. + +## util.activity.switch_condition.type.NextCommand + +```python +NextCommand(SwitchCondition): + next : dict(children(Command), Node) +``` + +Switches branches based on the type of command that is in the queue of the game entity. + +**next** +Mapping of command types to the next node in the activity graph. + +## util.activity.task.Task + +```python +Task(Object): + pass +``` + +Generalization object for tasks that can be used in `Task` nodes. + +## util.activity.task.type.ClearCommandQueue + +```python +ClearCommandQueue(Task): + pass +``` + +Clear the command queue of the game entity executing the activity. + +## util.activity.task.type.MoveToTarget + +```python +MoveToTarget(Task): + pass +``` + +Move to the current target of the game entity. The target may be a position or another game entity. If the game entity has no target at time of execution, the task is skipped. + +## util.activity.task.type.PopCommandQueue + +```python +PopCommandQueue(Task): + pass +``` + +Pop the front command from the command queue of the game entity executing the activity. + ## util.animation_override.AnimationOverride ```python @@ -420,6 +531,42 @@ The activation message that has to be typed into the chat console. **changes** Changes to API objects. +## util.command.Command + +```python +Command(Object): + pass +``` + +Generalization object for commands of a game entity. + +## util.command.type.ApplyEffect + +```python +ApplyEffect(Command): + pass +``` + +Game entity command for using the `ApplyEffect` ability. + +## util.command.type.Idle + +```python +Idle(Command): + pass +``` + +Game entity command for using the `Idle` ability. + +## util.command.type.Move + +```python +Move(Command): + pass +``` + +Game entity command for using the `Move` ability. + ## util.container_type.SendToContainerType ```python diff --git a/etc/gdb_pretty/printers.py b/etc/gdb_pretty/printers.py index fe6d43e397..9ae29539ce 100644 --- a/etc/gdb_pretty/printers.py +++ b/etc/gdb_pretty/printers.py @@ -1,4 +1,4 @@ -# Copyright 2024-2024 the openage authors. See copying.md for legal info. +# Copyright 2024-2025 the openage authors. See copying.md for legal info. """ Pretty printers for GDB. @@ -6,6 +6,7 @@ import re import gdb # type: ignore +import gdb.printing # type: ignore # TODO: Printers should inherit from gdb.ValuePrinter when gdb 14.1 is available in all distros. diff --git a/kevinfile b/kevinfile index 7a7353bbcd..e4acd0e036 100644 --- a/kevinfile +++ b/kevinfile @@ -6,7 +6,7 @@ sanity_check: - skip (? if job != "debian" ?) - make checkall + make checkmerge # Various optimisation options can affect warnings compiler generates. # Make sure both release and debug are tested. Arch job has more checks, diff --git a/libopenage/CMakeLists.txt b/libopenage/CMakeLists.txt index 030231baf7..30b7b23b01 100644 --- a/libopenage/CMakeLists.txt +++ b/libopenage/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright 2014-2019 the openage authors. See copying.md for legal info. +# Copyright 2014-2025 the openage authors. See copying.md for legal info. # main C++ library definitions. # dependency and source file setup for the resulting library. @@ -13,7 +13,7 @@ declare_binary(libopenage openage library allow_no_undefined) set_target_properties(libopenage PROPERTIES VERSION 0 AUTOMOC ON - AUTOGEN_TARGET_DEPENDS "codegen" + AUTOGEN_TARGET_DEPENDS "cppgen" ) ################################################## diff --git a/libopenage/curve/CMakeLists.txt b/libopenage/curve/CMakeLists.txt index 1aa1efb55b..eb52858f43 100644 --- a/libopenage/curve/CMakeLists.txt +++ b/libopenage/curve/CMakeLists.txt @@ -4,14 +4,10 @@ add_sources(libopenage discrete.cpp discrete_mod.cpp interpolated.cpp - iterator.cpp keyframe.cpp keyframe_container.cpp - map.cpp - map_filter_iterator.cpp - queue.cpp - queue_filter_iterator.cpp segmented.cpp ) +add_subdirectory("container") add_subdirectory("tests") diff --git a/libopenage/curve/base_curve.h b/libopenage/curve/base_curve.h index dc58885b98..de5c14201d 100644 --- a/libopenage/curve/base_curve.h +++ b/libopenage/curve/base_curve.h @@ -1,4 +1,4 @@ -// Copyright 2017-2024 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #pragma once @@ -47,6 +47,7 @@ class BaseCurve : public event::EventEntity { // registration. If you need to copy a curve, use the sync() method. // TODO: if copying is enabled again, these members have to be reassigned: _id, _idstr, last_element BaseCurve(const BaseCurve &) = delete; + BaseCurve &operator=(const BaseCurve &) = delete; BaseCurve(BaseCurve &&) = default; @@ -245,7 +246,7 @@ template std::pair BaseCurve::frame(const time::time_t &time) const { auto e = this->container.last(time, this->container.size()); auto elem = this->container.get(e); - return std::make_pair(elem.time(), elem.val()); + return elem.as_pair(); } @@ -254,7 +255,7 @@ std::pair BaseCurve::next_frame(const time::time_t &ti auto e = this->container.last(time, this->container.size()); e++; auto elem = this->container.get(e); - return std::make_pair(elem.time(), elem.val()); + return elem.as_pair(); } template diff --git a/libopenage/curve/container/CMakeLists.txt b/libopenage/curve/container/CMakeLists.txt new file mode 100644 index 0000000000..a187e01429 --- /dev/null +++ b/libopenage/curve/container/CMakeLists.txt @@ -0,0 +1,9 @@ +add_sources(libopenage + array.cpp + element_wrapper.cpp + iterator.cpp + map.cpp + map_filter_iterator.cpp + queue.cpp + queue_filter_iterator.cpp +) diff --git a/libopenage/curve/container/array.cpp b/libopenage/curve/container/array.cpp new file mode 100644 index 0000000000..609ee117a3 --- /dev/null +++ b/libopenage/curve/container/array.cpp @@ -0,0 +1,10 @@ +// Copyright 2024-2025 the openage authors. See copying.md for legal info. + + +#include "array.h" + +namespace openage::curve { + +// This file is intended to be empty + +} // openage::curve diff --git a/libopenage/curve/container/array.h b/libopenage/curve/container/array.h new file mode 100644 index 0000000000..e3d69af553 --- /dev/null +++ b/libopenage/curve/container/array.h @@ -0,0 +1,399 @@ +// Copyright 2024-2025 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include "curve/container/iterator.h" +#include "curve/keyframe_container.h" +#include "event/evententity.h" + + +namespace openage { +namespace curve { + +template +constexpr std::array, Size> init_default_vals(const std::array &default_vals) { + std::array, Size> containers; + for (size_t i = 0; i < Size; i++) { + containers[i] = KeyframeContainer(default_vals[i]); + } + return containers; +} + +template +class Array : event::EventEntity { +public: + /// Underlying container type. + using container_t = std::array, Size>; + + /// Uderlying iterator type + using const_iterator = typename std::array, Size>::const_iterator; + + /// Index type to access elements in the container. + using index_t = typename container_t::size_type; + + /** + * Create a new array curve container. + * + * @param loop Event loop this curve is registered on for notifications. + * @param id Unique identifier for this curve. + * @param idstr Human-readable identifier for this curve. + * @param notifier Function to call when this curve changes. + * @param default_vals Default values for the array elements. + */ + Array(const std::shared_ptr &loop, + size_t id, + const std::string &idstr = "", + const EventEntity::single_change_notifier ¬ifier = nullptr, + const std::array &default_vals = {}) : + EventEntity(loop, notifier), + containers{init_default_vals(default_vals)}, + _id{id}, + _idstr{idstr}, + loop{loop}, + last_elements{} {} + + // prevent copying because it invalidates the usage of unique ids and event + // registration. If you need to copy a curve, use the sync() method. + Array(const Array &) = delete; + Array &operator=(const Array &) = delete; + + Array(Array &&) = default; + + /** + * Get the last element with elem->time <= time. + * + * @param t Time of access. + * @param index Index of the array element. + * + * @return Value of the last element with time <= t. + */ + T at(const time::time_t &t, const index_t index) const; + + /** + * Get all elements at time t. + * + * @param t Time of access. + * + * @return Array of values at time t. + */ + std::array get(const time::time_t &t) const; + + /** + * Get the size of the array. + * + * @return Array size. + */ + consteval size_t size() const; + + /** + * Get the last keyframe value and time with elem->time <= time. + * + * @param t Time of access. + * @param index Index of the array element. + * + * @return Time-value pair of the last keyframe with time <= t. + */ + std::pair frame(const time::time_t &t, const index_t index) const; + + /** + * Get the first keyframe value and time with elem->time > time. + * + * If there is no keyframe with time > t, the behavior is undefined. + * + * @param t Time of access. + * @param index Index of the array element. + * + * @return Time-value pair of the first keyframe with time > t. + */ + std::pair next_frame(const time::time_t &t, const index_t index) const; + + /** + * Insert a new keyframe value at time t. + * + * If there is already a keyframe at time t, the new keyframe is inserted after the existing one. + * + * @param t Time of insertion. + * @param index Index of the array element. + * @param value Keyframe value. + */ + void set_insert(const time::time_t &t, const index_t index, T value); + + /** + * Insert a new keyframe value at time t. Erase all other keyframes with elem->time > t. + * + * @param t Time of insertion. + * @param index Index of the array element. + * @param value Keyframe value. + */ + void set_last(const time::time_t &t, const index_t index, T value); + + /** + * Replace all keyframes at elem->time == t with a new keyframe value. + * + * @param t Time of insertion. + * @param index Index of the array element. + * @param value Keyframe value. + */ + void set_replace(const time::time_t &t, const index_t index, T value); + + /** + * Copy keyframes from another container to this container. + * + * Replaces all keyframes beginning at t >= start with keyframes from \p other. + * + * @param other Curve that keyframes are copied from. + * @param start Start time at which keyframes are replaced (default = -INF). + * Using the default value replaces ALL keyframes of \p this with + * the keyframes of \p other. + */ + void sync(const Array &other, const time::time_t &start); + + /** + * Get the identifier of this curve. + * + * @return Identifier. + */ + size_t id() const override { + return this->_id; + } + + /** + * Get the human-readable identifier of this curve. + * + * @return Human-readable identifier. + */ + std::string idstr() const override { + if (this->_idstr.size() == 0) { + return std::to_string(this->id()); + } + return this->_idstr; + } + + + class ArrayIterator : public CurveIterator> { + public: + /** + * Construct the iterator from its boundary conditions: time and container + */ + ArrayIterator(const const_iterator &base, + const Array *base_container, + const time::time_t &to) : + CurveIterator>(base, base_container, -time::TIME_MAX, to) { + } + + + virtual bool valid() const override { + if (this->container->end().get_base() != this->get_base() && this->get_base()->begin()->time() <= this->to) { + return true; + } + return false; + } + + /** + * Get the keyFrame with a time <= this->to from the KeyframeContainer + * that this iterator currently points at + */ + const T &value() const override { + const auto &key_frame_container = *this->get_base(); + size_t hint_index = std::distance(this->container->begin().get_base(), this->get_base()); + size_t e = key_frame_container.last(this->to, last_elements[hint_index]); + this->last_elements[hint_index] = e; + return key_frame_container.get(e).val(); + } + + /** + * Cache hints for containers. Stores the index of the last keyframe accessed in each container. + * + * hints is used to speed up the search for keyframes. + * + * mutable as hints are updated by const read-only functions. + */ + mutable std::array::elem_ptr, Size> last_elements = {}; + }; + + + /** + * iterator pointing to a keyframe of the first KeyframeContainer in the curve at a given time + */ + ArrayIterator begin(const time::time_t &time = time::TIME_MIN) const; + + + /** + * iterator pointing after the last KeyframeContainer in the curve at a given time + */ + ArrayIterator end(const time::time_t &time = time::TIME_MAX) const; + + +private: + /** + * get a copy to the KeyframeContainer at index + */ + KeyframeContainer operator[](index_t index) const { + return this->containers.at(index); + } + + /** + * Containers for each array element. + * + * Each element is managed by a KeyframeContainer. + */ + container_t containers; + + /** + * Identifier for the container + */ + const size_t _id; + + /** + * Human-readable identifier for the container + */ + const std::string _idstr; + + /** + * The eventloop this curve was registered to + */ + const std::shared_ptr loop; + + /** + * Cache hints for containers. Stores the index of the last keyframe accessed in each container. + * + * hints is used to speed up the search for keyframes. + * + * mutable as hints are updated by const read-only functions. + */ + mutable std::array::elem_ptr, Size> last_elements = {}; +}; + + +template +std::pair Array::frame(const time::time_t &t, + const index_t index) const { + // find elem_ptr in container to get the last keyframe + auto hint = this->last_elements[index]; + auto frame_index = this->containers.at(index).last(t, hint); + + // update the hint + this->last_elements[index] = frame_index; + + return this->containers.at(index).get(frame_index).as_pair(); +} + +template +std::pair Array::next_frame(const time::time_t &t, + const index_t index) const { + // find elem_ptr in container to get the last keyframe with time <= t + auto hint = this->last_elements[index]; + auto frame_index = this->containers.at(index).last(t, hint); + + // increment the index to get the next keyframe + frame_index++; + + // update the hint + this->last_elements[index] = frame_index; + + return this->containers.at(index).get(frame_index).as_pair(); +} + +template +T Array::at(const time::time_t &t, + const index_t index) const { + // find elem_ptr in container to get the last keyframe with time <= t + auto hint = this->last_elements[index]; + auto e = this->containers.at(index).last(t, hint); + + // update the hint + this->last_elements[index] = e; + + return this->containers.at(index).get(e).val(); +} + +template +std::array Array::get(const time::time_t &t) const { + return [&](std::index_sequence) { + return std::array{this->at(t, I)...}; + }(std::make_index_sequence{}); +} + +template +consteval size_t Array::size() const { + return Size; +} + +template +void Array::set_insert(const time::time_t &t, + const index_t index, + T value) { + // find elem_ptr in container to get the last keyframe with time <= t + auto hint = this->last_elements[index]; + auto e = this->containers.at(index).insert_after(Keyframe{t, value}, hint); + + // update the hint + this->last_elements[index] = e; + + this->changes(t); +} + +template +void Array::set_last(const time::time_t &t, + const index_t index, + T value) { + // find elem_ptr in container to get the last keyframe with time <= t + auto hint = this->last_elements[index]; + auto e = this->containers.at(index).last(t, hint); + + // erase max one same-time value + if (this->containers.at(index).get(e).time() == t) { + e--; + } + + // erase all keyframes with time > t + this->containers.at(index).erase_after(e); + + // insert the new keyframe at the end + this->containers.at(index).insert_before(Keyframe{t, value}, e); + + // update the hint + this->last_elements[index] = hint; + + this->changes(t); +} + +template +void Array::set_replace(const time::time_t &t, + const index_t index, + T value) { + // find elem_ptr in container to get the last keyframe with time <= t + auto hint = this->last_elements[index]; + auto e = this->containers.at(index).insert_overwrite(Keyframe{t, value}, hint); + + // update the hint + this->last_elements[index] = e; + + this->changes(t); +} + +template +void Array::sync(const Array &other, + const time::time_t &start) { + for (index_t i = 0; i < Size; i++) { + this->containers[i].sync(other.containers[i], start); + } + + this->changes(start); +} + +template +auto Array::begin(const time::time_t &t) const -> ArrayIterator { + return ArrayIterator(this->containers.begin(), this, t); +} + + +template +auto Array::end(const time::time_t &t) const -> ArrayIterator { + return ArrayIterator(this->containers.end(), this, t); +} + +} // namespace curve +} // namespace openage diff --git a/libopenage/curve/container/element_wrapper.cpp b/libopenage/curve/container/element_wrapper.cpp new file mode 100644 index 0000000000..bdbca7346f --- /dev/null +++ b/libopenage/curve/container/element_wrapper.cpp @@ -0,0 +1,9 @@ +// Copyright 2024-2025 the openage authors. See copying.md for legal info. + +#include "element_wrapper.h" + +namespace openage::curve { + +// This file is intended to be empty + +} // namespace openage::curve diff --git a/libopenage/curve/container/element_wrapper.h b/libopenage/curve/container/element_wrapper.h new file mode 100644 index 0000000000..0032e4c6fe --- /dev/null +++ b/libopenage/curve/container/element_wrapper.h @@ -0,0 +1,95 @@ +// Copyright 2024-2025 the openage authors. See copying.md for legal info. + +#pragma once + +#include "time/time.h" + +namespace openage::curve { + +/** + * Wrapper for elements in a curve container. + * + * Stores the lifetime of the element (insertion time and erasure time) alongside the value. + */ +template +class element_wrapper { +public: + /** + * Create a new element with insertion time \p time and a given value. + * + * Erasure time is set to time::TIME_MAX, i.e. the element is alive indefinitely. + * + * @param time Insertion time of the element. + * @param value Element value. + */ + element_wrapper(const time::time_t &time, const T &value) : + _alive{time}, + _dead{time::TIME_MAX}, + _value{value} {} + + /** + * Create a new element with insertion time \p alive and erasure time \p dead and a given value. + * + * @param alive Insertion time of the element. + * @param dead Erasure time of the element. + * @param value Element value. + */ + element_wrapper(const time::time_t &alive, const time::time_t &dead, const T &value) : + _alive{alive}, + _dead{dead}, + _value{value} {} + + /** + * Get the insertion time of this element. + * + * @return Time when the element was inserted into the container. + */ + const time::time_t &alive() const { + return _alive; + } + + /** + * Get the erasure time of this element. + * + * @return Time when the element was erased from the container. + */ + const time::time_t &dead() const { + return _dead; + } + + /** + * Set the erasure time of this element. + * + * @param time Time when the element was erased from the container. + */ + void set_dead(const time::time_t &time) { + _dead = time; + } + + /** + * Get the value of this element. + * + * @return Value of the element. + */ + const T &value() const { + return _value; + } + +private: + /** + * Time of insertion of the element into the container + */ + time::time_t _alive; + + /** + * Time of erasure of the element from the container + */ + time::time_t _dead; + + /** + * Element value + */ + T _value; +}; + +} // namespace openage::curve diff --git a/libopenage/curve/iterator.cpp b/libopenage/curve/container/iterator.cpp similarity index 67% rename from libopenage/curve/iterator.cpp rename to libopenage/curve/container/iterator.cpp index b449d93812..594df3a5b8 100644 --- a/libopenage/curve/iterator.cpp +++ b/libopenage/curve/container/iterator.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2018 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #include "iterator.h" diff --git a/libopenage/curve/iterator.h b/libopenage/curve/container/iterator.h similarity index 97% rename from libopenage/curve/iterator.h rename to libopenage/curve/container/iterator.h index d0346e1b5d..7a4fb82d6b 100644 --- a/libopenage/curve/iterator.h +++ b/libopenage/curve/container/iterator.h @@ -1,4 +1,4 @@ -// Copyright 2017-2024 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/curve/map.cpp b/libopenage/curve/container/map.cpp similarity index 66% rename from libopenage/curve/map.cpp rename to libopenage/curve/container/map.cpp index 82b26f9ecc..a2469bfa5f 100644 --- a/libopenage/curve/map.cpp +++ b/libopenage/curve/container/map.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2018 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #include "map.h" diff --git a/libopenage/curve/map.h b/libopenage/curve/container/map.h similarity index 89% rename from libopenage/curve/map.h rename to libopenage/curve/container/map.h index 9712c89470..4997824a6d 100644 --- a/libopenage/curve/map.h +++ b/libopenage/curve/container/map.h @@ -1,4 +1,4 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #pragma once @@ -7,7 +7,8 @@ #include #include -#include "curve/map_filter_iterator.h" +#include "curve/container/element_wrapper.h" +#include "curve/container/map_filter_iterator.h" #include "time/time.h" #include "util/fixed_point.h" @@ -20,26 +21,14 @@ namespace openage::curve { */ template class UnorderedMap { - /** Internal container to access all data and metadata */ - struct map_element { - map_element(const val_t &v, const time::time_t &a, const time::time_t &d) : - value(v), - alive(a), - dead(d) {} - - val_t value; - time::time_t alive; - time::time_t dead; - }; - /** * Data holder. Maps keys to map elements. * Map elements themselves store when they are valid. */ - std::unordered_map container; + std::unordered_map> container; public: - using const_iterator = typename std::unordered_map::const_iterator; + using const_iterator = typename std::unordered_map>::const_iterator; std::optional> operator()(const time::time_t &, const key_t &) const; @@ -95,7 +84,7 @@ std::optional>> UnorderedMap::at(const time::time_t &time, const key_t &key) const { auto e = this->container.find(key); - if (e != this->container.end() and e->second.alive <= time and e->second.dead > time) { + if (e != this->container.end() and e->second.alive() <= time and e->second.dead() > time) { return MapFilterIterator>( e, this, @@ -160,7 +149,7 @@ UnorderedMap::insert(const time::time_t &alive, const time::time_t &dead, const key_t &key, const val_t &value) { - map_element e(value, alive, dead); + element_wrapper e{alive, dead, value}; auto it = this->container.insert(std::make_pair(key, e)); return MapFilterIterator>( it.first, diff --git a/libopenage/curve/map_filter_iterator.cpp b/libopenage/curve/container/map_filter_iterator.cpp similarity index 68% rename from libopenage/curve/map_filter_iterator.cpp rename to libopenage/curve/container/map_filter_iterator.cpp index da10b01774..bba3ffa478 100644 --- a/libopenage/curve/map_filter_iterator.cpp +++ b/libopenage/curve/container/map_filter_iterator.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2018 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #include "map_filter_iterator.h" diff --git a/libopenage/curve/map_filter_iterator.h b/libopenage/curve/container/map_filter_iterator.h similarity index 83% rename from libopenage/curve/map_filter_iterator.h rename to libopenage/curve/container/map_filter_iterator.h index 5e71c0789f..c9afceee88 100644 --- a/libopenage/curve/map_filter_iterator.h +++ b/libopenage/curve/container/map_filter_iterator.h @@ -1,8 +1,8 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #pragma once -#include "curve/iterator.h" +#include "curve/container/iterator.h" #include "time/time.h" @@ -35,8 +35,8 @@ class MapFilterIterator : public CurveIterator { using CurveIterator::operator=; virtual bool valid() const override { - return (this->get_base()->second.alive >= this->from - and this->get_base()->second.dead < this->to); + return (this->get_base()->second.alive() >= this->from + and this->get_base()->second.dead() < this->to); } /** @@ -44,7 +44,7 @@ class MapFilterIterator : public CurveIterator { * Nicer way of accessing it beside operator *. */ val_t const &value() const override { - return this->get_base()->second.value; + return this->get_base()->second.value(); } /** diff --git a/libopenage/curve/queue.cpp b/libopenage/curve/container/queue.cpp similarity index 58% rename from libopenage/curve/queue.cpp rename to libopenage/curve/container/queue.cpp index d994b6c82e..842a140045 100644 --- a/libopenage/curve/queue.cpp +++ b/libopenage/curve/container/queue.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2018 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #include "queue.h" diff --git a/libopenage/curve/queue.h b/libopenage/curve/container/queue.h similarity index 91% rename from libopenage/curve/queue.h rename to libopenage/curve/container/queue.h index 9604a76308..fb32a53cbb 100644 --- a/libopenage/curve/queue.h +++ b/libopenage/curve/container/queue.h @@ -1,4 +1,4 @@ -// Copyright 2017-2024 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #pragma once @@ -11,8 +11,9 @@ #include "error/error.h" -#include "curve/iterator.h" -#include "curve/queue_filter_iterator.h" +#include "curve/container/element_wrapper.h" +#include "curve/container/iterator.h" +#include "curve/container/queue_filter_iterator.h" #include "event/evententity.h" #include "time/time.h" #include "util/fixed_point.h" @@ -32,39 +33,11 @@ namespace curve { */ template class Queue : public event::EventEntity { - struct queue_wrapper { - // Insertion time of the element - time::time_t _alive; - // Erase time of the element - // TODO: this has to be mutable because erase() will complain otherwise - mutable time::time_t _dead; - // Element value - T value; - - queue_wrapper(const time::time_t &time, const T &value) : - _alive{time}, - _dead{time::TIME_MAX}, - value{value} {} - - const time::time_t &alive() const { - return _alive; - } - - const time::time_t &dead() const { - return _dead; - } - - // TODO: this has to be const because erase() will complain otherwise - void set_dead(const time::time_t &time) const { - _dead = time; - } - }; - public: /** * The underlaying container type. */ - using container_t = typename std::vector; + using container_t = typename std::vector>; /** * The index type to access elements in the container @@ -304,7 +277,7 @@ const T &Queue::front(const time::time_t &time) const { << ", container size: " << this->container.size() << ")"); - return this->container.at(at).value; + return this->container.at(at).value(); } @@ -330,7 +303,7 @@ const T &Queue::pop_front(const time::time_t &time) { this->changes(time); - return this->container.at(at).value; + return this->container.at(at).value(); } @@ -412,7 +385,7 @@ QueueFilterIterator> Queue::insert(const time::time_t &time, // Get the iterator to the insertion point iterator insertion_point = std::next(this->container.begin(), at); - insertion_point = this->container.insert(insertion_point, queue_wrapper{time, e}); + insertion_point = this->container.insert(insertion_point, element_wrapper{time, e}); // TODO: Inserting before any dead elements shoud reset their death time // since by definition, they cannot be popped before the new element diff --git a/libopenage/curve/queue_filter_iterator.cpp b/libopenage/curve/container/queue_filter_iterator.cpp similarity index 69% rename from libopenage/curve/queue_filter_iterator.cpp rename to libopenage/curve/container/queue_filter_iterator.cpp index fa0b3ad15a..b4ceb2b7e6 100644 --- a/libopenage/curve/queue_filter_iterator.cpp +++ b/libopenage/curve/container/queue_filter_iterator.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2018 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #include "queue_filter_iterator.h" diff --git a/libopenage/curve/queue_filter_iterator.h b/libopenage/curve/container/queue_filter_iterator.h similarity index 91% rename from libopenage/curve/queue_filter_iterator.h rename to libopenage/curve/container/queue_filter_iterator.h index 248abb44ad..6b2fa471f2 100644 --- a/libopenage/curve/queue_filter_iterator.h +++ b/libopenage/curve/container/queue_filter_iterator.h @@ -1,8 +1,8 @@ -// Copyright 2017-2024 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #pragma once -#include "curve/iterator.h" +#include "curve/container/iterator.h" #include "time/time.h" @@ -40,7 +40,7 @@ class QueueFilterIterator : public CurveIteratorget_base(); - return a.value; + return a.value(); } }; diff --git a/libopenage/curve/discrete.h b/libopenage/curve/discrete.h index 44fe132de6..b9f9b6b00c 100644 --- a/libopenage/curve/discrete.h +++ b/libopenage/curve/discrete.h @@ -1,4 +1,4 @@ -// Copyright 2017-2024 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #pragma once @@ -80,7 +80,7 @@ std::pair Discrete::get_time(const time::time_t &time) const this->last_element = e; auto elem = this->container.get(e); - return std::make_pair(elem.time, elem.value); + return elem.as_pair(); } @@ -97,7 +97,7 @@ std::optional> Discrete::get_previous(const time:: e--; auto elem = this->container.get(e); - return std::make_pair(elem.time(), elem.val()); + return elem.as_pair(); } } // namespace openage::curve diff --git a/libopenage/curve/keyframe.h b/libopenage/curve/keyframe.h index e868783cbb..cd2ebf6ced 100644 --- a/libopenage/curve/keyframe.h +++ b/libopenage/curve/keyframe.h @@ -1,4 +1,4 @@ -// Copyright 2019-2024 the openage authors. See copying.md for legal info. +// Copyright 2019-2025 the openage authors. See copying.md for legal info. #pragma once @@ -54,6 +54,15 @@ class Keyframe { return this->value; } + /** + * Get a time-value pair of this keyframe. + * + * @return Keyframe time-value pair. + */ + std::pair as_pair() const { + return {this->timestamp, this->value}; + } + public: /** * Value of the keyframe. diff --git a/libopenage/curve/keyframe_container.h b/libopenage/curve/keyframe_container.h index d9bb9a0bbd..8434942058 100644 --- a/libopenage/curve/keyframe_container.h +++ b/libopenage/curve/keyframe_container.h @@ -279,8 +279,6 @@ class KeyframeContainer { * Replaces all keyframes beginning at t >= start with keyframes from \p other. * * @param other Curve that keyframes are copied from. - * @param converter Function that converts the value type of \p other to the - * value type of \p this. * @param start Start time at which keyframes are replaced (default = -INF). * Using the default value replaces ALL keyframes of \p this with * the keyframes of \p other. diff --git a/libopenage/curve/tests/container.cpp b/libopenage/curve/tests/container.cpp index 020e2aad99..16da2476e9 100644 --- a/libopenage/curve/tests/container.cpp +++ b/libopenage/curve/tests/container.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2024 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #include #include @@ -9,11 +9,12 @@ #include #include -#include "curve/iterator.h" -#include "curve/map.h" -#include "curve/map_filter_iterator.h" -#include "curve/queue.h" -#include "curve/queue_filter_iterator.h" +#include "curve/container/array.h" +#include "curve/container/iterator.h" +#include "curve/container/map.h" +#include "curve/container/map_filter_iterator.h" +#include "curve/container/queue.h" +#include "curve/container/queue_filter_iterator.h" #include "event/event_loop.h" #include "testing/testing.h" @@ -56,7 +57,7 @@ void test_map() { // Basic tests test lookup in the middle of the range. { - auto t = map.at(2, 0); //At timestamp 2 element 0 + auto t = map.at(2, 0); // At timestamp 2 element 0 TESTEQUALS(t.has_value(), true); TESTEQUALS(t.value().value(), 0); t = map.at(20, 5); @@ -242,11 +243,115 @@ void test_queue() { TESTEQUALS(q.empty(100001), false); } +void test_array() { + auto loop = std::make_shared(); + + Array a(loop, 0); + a.set_insert(1, 0, 0); + a.set_insert(1, 1, 1); + a.set_insert(1, 2, 2); + a.set_insert(1, 3, 3); + // a = [[0:0, 1:0],[0:0, 1:1],[0:0, 1:2],[0:0, 1:3]] + + // test size + TESTEQUALS(a.size(), 4); + + // extracting array at time t == 1 + auto res = a.get(1); + auto expected = std::array{0, 1, 2, 3}; + TESTEQUALS(res.at(0), expected.at(0)); + TESTEQUALS(res.at(1), expected.at(1)); + TESTEQUALS(res.at(2), expected.at(2)); + TESTEQUALS(res.at(3), expected.at(3)); + TESTEQUALS(res.size(), expected.size()); + + // extracting array at time t == 0 + // array should have default values (== 0) for all keyframes + res = a.get(0); + TESTEQUALS(res.at(0), 0); + TESTEQUALS(res.at(1), 0); + TESTEQUALS(res.at(2), 0); + TESTEQUALS(res.at(3), 0); + + Array other(loop, 0); + other.set_last(0, 0, 999); + other.set_last(0, 1, 999); + other.set_last(0, 2, 999); + other.set_last(0, 3, 999); + + // inserting keyframes at time t == 1 + other.set_insert(1, 0, 4); + other.set_insert(1, 1, 5); + other.set_insert(1, 2, 6); + other.set_insert(1, 3, 7); + // other = [[0:999, 1:4],[0:999, 1:5],[0:999, 1:6],[0:999, 1:7]] + + TESTEQUALS(other.at(0, 0), 999); + TESTEQUALS(other.at(0, 1), 999); + TESTEQUALS(other.at(0, 2), 999); + TESTEQUALS(other.at(0, 3), 999); + TESTEQUALS(other.at(1, 0), 4); + TESTEQUALS(other.at(1, 1), 5); + TESTEQUALS(other.at(1, 2), 6); + TESTEQUALS(other.at(1, 3), 7); + + // sync keyframes from other to a + a.sync(other, 1); + // a = [[0:0, 1:4],[0:0, 1:5],[0:0, 1:6],[0:0, 1:7]] + + res = a.get(0); + TESTEQUALS(res.at(0), 0); + TESTEQUALS(res.at(1), 0); + TESTEQUALS(res.at(2), 0); + TESTEQUALS(res.at(3), 0); + res = a.get(1); + TESTEQUALS(res.at(0), 4); + TESTEQUALS(res.at(1), 5); + TESTEQUALS(res.at(2), 6); + TESTEQUALS(res.at(3), 7); + + // replace keyframes at time t == 2 + a.set_insert(2, 0, 15); + a.set_insert(2, 0, 20); + a.set_replace(2, 0, 25); + TESTEQUALS(a.at(2, 0), 25); + // a = [[0:0, 1:4, 2:25],[0:0, 1:5],[0:0, 1:6],[0:0, 1:7]] + + // set last keyframe at time t == 3 + a.set_insert(3, 0, 30); // a = [[0:0, 1:4, 2:25, 3:30], ... + a.set_insert(4, 0, 40); // a = [[0:0, 1:4, 2:25, 3:30, 4:40], ... + a.set_last(3, 0, 35); // a = [[0:0, 1:4, 2:25, 3:35],... + TESTEQUALS(a.at(4, 0), 35); + // a = [[0:0, 1:4, 2:25, 3:35],[0:0, 1:5],[0:0, 1:6],[0:0, 1:7]] + + // test frame and next_frame + auto frame = a.frame(1, 2); + TESTEQUALS(frame.first, 1); // time + TESTEQUALS(frame.second, 6); // value + + a.set_insert(5, 3, 40); + // a = [[0:0, 1:4, 2:25, 3:35],[0:0, 1:5],[0:0, 1:6],[0:0, 1:7, 5:40]] + auto next_frame = a.next_frame(1, 3); + TESTEQUALS(next_frame.first, 5); // time + TESTEQUALS(next_frame.second, 40); // value + + // Test begin and end + auto it = a.begin(1); + TESTEQUALS(*it, 4); + ++it; + TESTEQUALS(*it, 5); + ++it; + TESTEQUALS(*it, 6); + ++it; + TESTEQUALS(*it, 7); +} + void container() { test_map(); test_list(); test_queue(); + test_array(); } diff --git a/libopenage/curve/tests/curve_types.cpp b/libopenage/curve/tests/curve_types.cpp index 421d3fb4d7..935aa7141d 100644 --- a/libopenage/curve/tests/curve_types.cpp +++ b/libopenage/curve/tests/curve_types.cpp @@ -232,7 +232,7 @@ void curve_types() { TESTEQUALS(c.get(8), 4); } - //Check the discrete type + // Check the discrete type { auto f = std::make_shared(); Discrete c(f, 0); @@ -257,7 +257,7 @@ void curve_types() { TESTEQUALS(complex.get(10), "Test 10"); } - //Check the discrete mod type + // Check the discrete mod type { auto f = std::make_shared(); DiscreteMod c(f, 0); @@ -290,7 +290,7 @@ void curve_types() { TESTEQUALS(c.get_mod(15, 0), 0); } - //check set_last + // check set_last { auto f = std::make_shared(); Discrete c(f, 0); diff --git a/libopenage/datastructure/pairing_heap.h b/libopenage/datastructure/pairing_heap.h index dd348d8c55..df8d1ad66d 100644 --- a/libopenage/datastructure/pairing_heap.h +++ b/libopenage/datastructure/pairing_heap.h @@ -1,4 +1,4 @@ -// Copyright 2014-2024 the openage authors. See copying.md for legal info. +// Copyright 2014-2025 the openage authors. See copying.md for legal info. #pragma once @@ -17,6 +17,7 @@ */ #include +#include #include #include #include @@ -36,6 +37,9 @@ template class PairingHeap; +template +class PairingHeapIterator; + template > class PairingHeapNode { @@ -43,6 +47,7 @@ class PairingHeapNode { using this_type = PairingHeapNode; friend PairingHeap; + friend PairingHeapIterator; T data; compare cmp; @@ -186,6 +191,166 @@ class PairingHeapNode { }; +/** + * @brief Iterator class for PairingHeap. + * + * This class provides a bidirectional iterator for the PairingHeap data structure. + * It allows traversal of the heap in both forward and backward directions. + * It is depth-first traversal. + * + * @tparam T The type of elements stored in the heap. + * @tparam compare The comparison functor used to order the elements. + */ +template > +class PairingHeapIterator { +public: + using iterator_category = std::bidirectional_iterator_tag; + using value_type = T; + using difference_type = std::ptrdiff_t; + using pointer = T *; + using reference = T &; + + /** + * @brief Constructs an iterator starting at the given node. + * + * @param node The starting node for the iterator. + */ + PairingHeapIterator(PairingHeapNode *node) : + current(node) {} + + /** + * @brief Dereference operator. + * + * @return A reference to the data stored in the current node. + */ + reference operator*() const { + return current->data; + } + + /** + * @brief Member access operator. + * + * @return A pointer to the data stored in the current node. + */ + pointer operator->() const { + return &(current->data); + } + + + /** + * @brief Get current node. + * + * @return The current node. + */ + PairingHeapNode *node() { + return current; + } + + + /** + * @brief Pre-increment operator. + * + * Moves the iterator to the next node in the heap. + * + * @return A reference to the incremented iterator. + */ + PairingHeapIterator &operator++() { + if (current->first_child) { + current = current->first_child; + } + else if (current->next_sibling) { + current = current->next_sibling; + } + else { + while (current->parent && !current->parent->next_sibling) { + current = current->parent; + } + if (current->parent) { + current = current->parent->next_sibling; + } + else { + current = nullptr; + } + } + return *this; + } + + /** + * @brief Post-increment operator. + * + * Moves the iterator to the next node in the heap. + * + * @return A copy of the iterator before incrementing. + */ + PairingHeapIterator operator++(int) { + PairingHeapIterator tmp = *this; + ++(*this); + return tmp; + } + + /** + * @brief Pre-decrement operator. + * + * Moves the iterator to the previous node in the heap. + * + * @return A reference to the decremented iterator. + */ + PairingHeapIterator &operator--() { + if (current->prev_sibling) { + current = current->prev_sibling; + while (current->first_child) { + current = current->first_child; + while (current->next_sibling) { + current = current->next_sibling; + } + } + } + else if (current->parent) { + current = current->parent; + } + return *this; + } + + /** + * @brief Post-decrement operator. + * + * Moves the iterator to the previous node in the heap. + * + * @return A copy of the iterator before decrementing. + */ + PairingHeapIterator operator--(int) { + PairingHeapIterator tmp = *this; + --(*this); + return tmp; + } + + /** + * @brief Equality comparison operator. + * + * @param a The first iterator to compare. + * @param b The second iterator to compare. + * @return True if both iterators point to the same node, false otherwise. + */ + friend bool operator==(const PairingHeapIterator &a, const PairingHeapIterator &b) { + return a.current == b.current; + } + + /** + * @brief Inequality comparison operator. + * + * @param a The first iterator to compare. + * @param b The second iterator to compare. + * @return True if the iterators point to different nodes, false otherwise. + */ + friend bool operator!=(const PairingHeapIterator &a, const PairingHeapIterator &b) { + return a.current != b.current; + } + +private: + PairingHeapNode *current; ///< Pointer to the current node in the heap. +}; + + /** * (Quite) efficient heap implementation. */ @@ -196,6 +361,7 @@ class PairingHeap final { public: using element_t = heapnode_t *; using this_type = PairingHeap; + using iterator = PairingHeapIterator; /** * create a empty heap. @@ -404,11 +570,24 @@ class PairingHeap final { * erase all elements on the heap. */ void clear() { - auto delete_node = [](element_t node) { delete node; }; - this->iter_all(delete_node); + std::vector to_delete; + to_delete.reserve(this->size()); + + // collect all node pointers to delete + for (iterator it = this->begin(); it != this->end(); it++) { + to_delete.push_back(it.node()); + } + + // delete all nodes + for (element_t node : to_delete) { + delete node; + } + + // reset heap state to empty this->root_node = nullptr; this->node_count = 0; #if OPENAGE_PAIRINGHEAP_DEBUG + // clear the node set for debugging this->nodes.clear(); #endif } @@ -590,6 +769,14 @@ class PairingHeap final { this->walk_tree(this->root_node, func); } + iterator begin() const { + return iterator(this->root_node); + } + + iterator end() const { + return iterator(nullptr); + } + private: /** * Apply the given function to all nodes in the tree. diff --git a/libopenage/datastructure/tests.cpp b/libopenage/datastructure/tests.cpp index c375f78c17..40aa516951 100644 --- a/libopenage/datastructure/tests.cpp +++ b/libopenage/datastructure/tests.cpp @@ -1,4 +1,4 @@ -// Copyright 2014-2024 the openage authors. See copying.md for legal info. +// Copyright 2014-2025 the openage authors. See copying.md for legal info. #include "tests.h" @@ -135,6 +135,14 @@ void pairing_heap_3() { heap.push(heap_elem{3}); heap.push(heap_elem{4}); heap.push(heap_elem{5}); + + size_t i = 0; + std::array expected{0, 5, 4, 3, 2, 1}; + for (auto &elem : heap) { + TESTEQUALS(elem.data, expected.at(i)); + i++; + } + heap.pop(); // trigger pairing heap.clear(); diff --git a/libopenage/engine/engine.cpp b/libopenage/engine/engine.cpp index fa2c251b4d..02c0dd87d3 100644 --- a/libopenage/engine/engine.cpp +++ b/libopenage/engine/engine.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #include "engine.h" @@ -16,7 +16,7 @@ namespace openage::engine { Engine::Engine(mode mode, const util::Path &root_dir, const std::vector &mods, - bool debug_graphics) : + const renderer::window_settings &window_settings) : running{true}, run_mode{mode}, root_dir{root_dir}, @@ -55,8 +55,8 @@ Engine::Engine(mode mode, // if presenter is used, run it in a separate thread if (this->run_mode == mode::FULL) { - this->threads.emplace_back([&, debug_graphics]() { - this->presenter->run(debug_graphics); + this->threads.emplace_back([&]() { + this->presenter->run(window_settings); // Make sure that the presenter gets destructed in the same thread // otherwise OpenGL complains about missing contexts diff --git a/libopenage/engine/engine.h b/libopenage/engine/engine.h index 1fe4298061..0231054628 100644 --- a/libopenage/engine/engine.h +++ b/libopenage/engine/engine.h @@ -7,8 +7,10 @@ #include #include +#include "renderer/window.h" #include "util/path.h" + // TODO: Remove custom jthread definition when clang/libc++ finally supports it #if __llvm__ #if !__cpp_lib_jthread @@ -71,12 +73,12 @@ class Engine { * @param mode The run mode to use. * @param root_dir openage root directory. * @param mods The mods to load. - * @param debug_graphics If true, enable OpenGL debug logging. + * @param window_settings The settings to customize the display window (e.g. size, display mode, vsync). */ Engine(mode mode, const util::Path &root_dir, const std::vector &mods, - bool debug_graphics = false); + const renderer::window_settings &window_settings = {}); // engine should not be copied or moved Engine(const Engine &) = delete; diff --git a/libopenage/gamestate/component/api/live.cpp b/libopenage/gamestate/component/api/live.cpp index 01fafde5ea..f7a1f17d98 100644 --- a/libopenage/gamestate/component/api/live.cpp +++ b/libopenage/gamestate/component/api/live.cpp @@ -1,12 +1,12 @@ -// Copyright 2021-2023 the openage authors. See copying.md for legal info. +// Copyright 2021-2025 the openage authors. See copying.md for legal info. #include "live.h" #include +#include "curve/container/iterator.h" +#include "curve/container/map_filter_iterator.h" #include "curve/discrete.h" -#include "curve/iterator.h" -#include "curve/map_filter_iterator.h" #include "gamestate/component/types.h" diff --git a/libopenage/gamestate/component/api/live.h b/libopenage/gamestate/component/api/live.h index 2e1f5e41d5..4916713cdc 100644 --- a/libopenage/gamestate/component/api/live.h +++ b/libopenage/gamestate/component/api/live.h @@ -1,4 +1,4 @@ -// Copyright 2021-2024 the openage authors. See copying.md for legal info. +// Copyright 2021-2025 the openage authors. See copying.md for legal info. #pragma once @@ -7,7 +7,7 @@ #include -#include "curve/map.h" +#include "curve/container/map.h" #include "gamestate/component/api_component.h" #include "gamestate/component/types.h" #include "time/time.h" diff --git a/libopenage/gamestate/component/internal/command_queue.h b/libopenage/gamestate/component/internal/command_queue.h index a7905c4d24..fb3179b470 100644 --- a/libopenage/gamestate/component/internal/command_queue.h +++ b/libopenage/gamestate/component/internal/command_queue.h @@ -1,10 +1,10 @@ -// Copyright 2021-2024 the openage authors. See copying.md for legal info. +// Copyright 2021-2025 the openage authors. See copying.md for legal info. #pragma once #include -#include "curve/queue.h" +#include "curve/container/queue.h" #include "gamestate/component/internal/commands/base_command.h" #include "gamestate/component/internal_component.h" #include "gamestate/component/types.h" diff --git a/libopenage/gamestate/entity_factory.cpp b/libopenage/gamestate/entity_factory.cpp index f2f489206c..6e1a4c825f 100644 --- a/libopenage/gamestate/entity_factory.cpp +++ b/libopenage/gamestate/entity_factory.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #include "entity_factory.h" @@ -10,8 +10,8 @@ #include "error/error.h" +#include "curve/container/queue.h" #include "curve/discrete.h" -#include "curve/queue.h" #include "event/event_loop.h" #include "gamestate/activity/activity.h" #include "gamestate/activity/condition/command_in_queue.h" diff --git a/libopenage/gamestate/map.cpp b/libopenage/gamestate/map.cpp index 8300ce273d..88578fabdb 100644 --- a/libopenage/gamestate/map.cpp +++ b/libopenage/gamestate/map.cpp @@ -49,7 +49,7 @@ Map::Map(const std::shared_ptr &state, auto sector = grid->get_sector(chunk_idx); auto cost_field = sector->get_cost_field(); - cost_field->set_cost(tile_idx, path_cost.second); + cost_field->set_cost(tile_idx, path_cost.second, time::TIME_ZERO); } } } diff --git a/libopenage/gamestate/system/move.cpp b/libopenage/gamestate/system/move.cpp index 3da45ceab3..5a44baf113 100644 --- a/libopenage/gamestate/system/move.cpp +++ b/libopenage/gamestate/system/move.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2024 the openage authors. See copying.md for legal info. +// Copyright 2023-2025 the openage authors. See copying.md for legal info. #include "move.h" @@ -37,7 +37,8 @@ namespace openage::gamestate::system { std::vector find_path(const std::shared_ptr &pathfinder, path::grid_id_t grid_id, const coord::phys3 &start, - const coord::phys3 &end) { + const coord::phys3 &end, + const time::time_t &start_time) { auto start_tile = start.to_tile(); auto end_tile = end.to_tile(); @@ -46,6 +47,7 @@ std::vector find_path(const std::shared_ptr &pat grid_id, start_tile, end_tile, + start_time, }; auto tile_path = pathfinder->get_path(request); @@ -122,7 +124,7 @@ const time::time_t Move::move_default(const std::shared_ptrget_map(); auto pathfinder = map->get_pathfinder(); auto grid_id = map->get_grid_id(move_path_grid->get_name()); - auto waypoints = find_path(pathfinder, grid_id, current_pos, destination); + auto waypoints = find_path(pathfinder, grid_id, current_pos, destination, start_time); // use waypoints for movement double total_time = 0; diff --git a/libopenage/main.cpp b/libopenage/main.cpp index e7794be40c..2147cbe4e0 100644 --- a/libopenage/main.cpp +++ b/libopenage/main.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #include "main.h" @@ -31,7 +31,29 @@ int run_game(const main_arguments &args) { run_mode = openage::engine::Engine::mode::HEADLESS; } - openage::engine::Engine engine{run_mode, args.root_path, args.mods, args.gl_debug}; + // convert window arguments to window settings + renderer::window_settings win_settings = {}; + win_settings.width = args.window_args.width; + win_settings.height = args.window_args.height; + win_settings.vsync = args.window_args.vsync; + + renderer::window_mode wmode; + if (args.window_args.mode == "fullscreen") { + wmode = renderer::window_mode::FULLSCREEN; + } + else if (args.window_args.mode == "borderless") { + wmode = renderer::window_mode::BORDERLESS; + } + else if (args.window_args.mode == "windowed") { + wmode = renderer::window_mode::WINDOWED; + } + else { + throw Error(MSG(err) << "Invalid window mode: " << args.window_args.mode); + } + win_settings.mode = wmode; + win_settings.debug = args.gl_debug; + + openage::engine::Engine engine{run_mode, args.root_path, args.mods, win_settings}; engine.loop(); diff --git a/libopenage/main.h b/libopenage/main.h index 0e60c386cc..e99b374642 100644 --- a/libopenage/main.h +++ b/libopenage/main.h @@ -1,4 +1,4 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once @@ -16,6 +16,24 @@ namespace openage { +/** + * Window parameters struct. + * + * pxd: + * + * cppclass window_arguments: + * int width + * int height + * bool vsync + * string mode + */ +struct window_arguments { + int width; + int height; + bool vsync; + std::string mode; +}; + /** * Used for passing arguments to run_game. * @@ -26,12 +44,14 @@ namespace openage { * bool gl_debug * bool headless * vector[string] mods + * window_arguments window_args */ struct main_arguments { util::Path root_path; bool gl_debug; bool headless; std::vector mods; + window_arguments window_args; }; diff --git a/libopenage/pathfinding/CMakeLists.txt b/libopenage/pathfinding/CMakeLists.txt index 7f85264d01..33c2844ace 100644 --- a/libopenage/pathfinding/CMakeLists.txt +++ b/libopenage/pathfinding/CMakeLists.txt @@ -1,6 +1,7 @@ add_sources(libopenage cost_field.cpp definitions.cpp + field_cache.cpp flow_field.cpp grid.cpp integration_field.cpp diff --git a/libopenage/pathfinding/cost_field.cpp b/libopenage/pathfinding/cost_field.cpp index 3299084548..12889663eb 100644 --- a/libopenage/pathfinding/cost_field.cpp +++ b/libopenage/pathfinding/cost_field.cpp @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #include "cost_field.h" @@ -13,6 +13,7 @@ namespace openage::path { CostField::CostField(size_t size) : size{size}, + valid_until{time::TIME_MIN}, cells(this->size * this->size, COST_MIN) { log::log(DBG << "Created cost field with size " << this->size << "x" << this->size); } @@ -33,29 +34,34 @@ cost_t CostField::get_cost(size_t idx) const { return this->cells.at(idx); } -void CostField::set_cost(const coord::tile_delta &pos, cost_t cost) { - this->cells[pos.ne + pos.se * this->size] = cost; +void CostField::set_cost(const coord::tile_delta &pos, cost_t cost, const time::time_t &valid_until) { + this->set_cost(pos.ne + pos.se * this->size, cost, valid_until); } -void CostField::set_cost(size_t x, size_t y, cost_t cost) { - this->cells[x + y * this->size] = cost; -} - -void CostField::set_cost(size_t idx, cost_t cost) { - this->cells[idx] = cost; +void CostField::set_cost(size_t x, size_t y, cost_t cost, const time::time_t &valid_until) { + this->set_cost(x + y * this->size, cost, valid_until); } const std::vector &CostField::get_costs() const { return this->cells; } -void CostField::set_costs(std::vector &&cells) { +void CostField::set_costs(std::vector &&cells, const time::time_t &valid_until) { ENSURE(cells.size() == this->cells.size(), "cells vector has wrong size: " << cells.size() << "; expected: " << this->cells.size()); this->cells = std::move(cells); + this->valid_until = valid_until; +} + +bool CostField::is_dirty(const time::time_t &time) const { + return time >= this->valid_until; +} + +void CostField::clear_dirty() { + this->valid_until = time::TIME_MAX; } } // namespace openage::path diff --git a/libopenage/pathfinding/cost_field.h b/libopenage/pathfinding/cost_field.h index a1fc87b046..c03b494a84 100644 --- a/libopenage/pathfinding/cost_field.h +++ b/libopenage/pathfinding/cost_field.h @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #pragma once @@ -6,6 +6,7 @@ #include #include "pathfinding/types.h" +#include "time/time.h" namespace openage { @@ -64,8 +65,9 @@ class CostField { * * @param pos Coordinates of the cell (relative to field origin). * @param cost Cost to set. + * @param valid_until Time at which the cost value expires. */ - void set_cost(const coord::tile_delta &pos, cost_t cost); + void set_cost(const coord::tile_delta &pos, cost_t cost, const time::time_t &valid_until); /** * Set the cost at a specified position. @@ -73,16 +75,21 @@ class CostField { * @param x X-coordinate of the cell. * @param y Y-coordinate of the cell. * @param cost Cost to set. + * @param valid_until Time at which the cost value expires. */ - void set_cost(size_t x, size_t y, cost_t cost); + void set_cost(size_t x, size_t y, cost_t cost, const time::time_t &valid_until); /** * Set the cost at a specified position. * * @param idx Index of the cell. * @param cost Cost to set. + * @param valid_until Time at which the cost value expires. */ - void set_cost(size_t idx, cost_t cost); + inline void set_cost(size_t idx, cost_t cost, const time::time_t &valid_until) { + this->cells[idx] = cost; + this->valid_until = valid_until; + } /** * Get the cost field values. @@ -95,8 +102,23 @@ class CostField { * Set the cost field values. * * @param cells Cost field values. + * @param valid_until Time at which the cost value expires. */ - void set_costs(std::vector &&cells); + void set_costs(std::vector &&cells, const time::time_t &changed); + + /** + * Check if the cost field is dirty at the specified time. + * + * @param time Time of access to the cost field. + * + * @return Whether the cost field is dirty. + */ + bool is_dirty(const time::time_t &time) const; + + /** + * Clear the dirty flag. + */ + void clear_dirty(); private: /** @@ -104,6 +126,11 @@ class CostField { */ size_t size; + /** + * Time the cost field expires. + */ + time::time_t valid_until; + /** * Cost field values. */ diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp index 483261ebaf..31f66bb51f 100644 --- a/libopenage/pathfinding/demo/demo_0.cpp +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #include "demo_0.h" @@ -33,15 +33,17 @@ void path_demo_0(const util::Path &path) { // Cost field with some obstacles auto cost_field = std::make_shared(field_length); - cost_field->set_cost(coord::tile_delta{0, 0}, COST_IMPASSABLE); - cost_field->set_cost(coord::tile_delta{1, 0}, 254); - cost_field->set_cost(coord::tile_delta{4, 3}, 128); - cost_field->set_cost(coord::tile_delta{5, 3}, 128); - cost_field->set_cost(coord::tile_delta{6, 3}, 128); - cost_field->set_cost(coord::tile_delta{4, 4}, 128); - cost_field->set_cost(coord::tile_delta{5, 4}, 128); - cost_field->set_cost(coord::tile_delta{6, 4}, 128); - cost_field->set_cost(coord::tile_delta{1, 7}, COST_IMPASSABLE); + + const time::time_t time = time::TIME_ZERO; + cost_field->set_cost(coord::tile_delta{0, 0}, COST_IMPASSABLE, time); + cost_field->set_cost(coord::tile_delta{1, 0}, 254, time); + cost_field->set_cost(coord::tile_delta{4, 3}, 128, time); + cost_field->set_cost(coord::tile_delta{5, 3}, 128, time); + cost_field->set_cost(coord::tile_delta{6, 3}, 128, time); + cost_field->set_cost(coord::tile_delta{4, 4}, 128, time); + cost_field->set_cost(coord::tile_delta{5, 4}, 128, time); + cost_field->set_cost(coord::tile_delta{6, 4}, 128, time); + cost_field->set_cost(coord::tile_delta{1, 7}, COST_IMPASSABLE, time); log::log(INFO << "Created cost field"); // Create an integration field from the cost field diff --git a/libopenage/pathfinding/demo/demo_1.cpp b/libopenage/pathfinding/demo/demo_1.cpp index 1edeba52a1..61df3e8995 100644 --- a/libopenage/pathfinding/demo/demo_1.cpp +++ b/libopenage/pathfinding/demo/demo_1.cpp @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #include "demo_1.h" @@ -30,20 +30,30 @@ void path_demo_1(const util::Path &path) { // Initialize the cost field for each sector. for (auto §or : grid->get_sectors()) { auto cost_field = sector->get_cost_field(); - std::vector sector_cost = sectors_cost.at(sector->get_id()); - cost_field->set_costs(std::move(sector_cost)); + + // Read the data from the preconfigured table + std::vector sector_cost = SECTORS_COST.at(sector->get_id()); + + // Set the cost field for the sector + cost_field->set_costs(std::move(sector_cost), time::TIME_MAX); } - // Initialize portals between sectors. + // Initialize portals between sectors util::Vector2s grid_size = grid->get_size(); portal_id_t portal_id = 0; for (size_t y = 0; y < grid_size[1]; y++) { for (size_t x = 0; x < grid_size[0]; x++) { + // For each sector on the grid, we will seatch for portals to the east and south + // sectors and connect them + auto sector = grid->get_sector(x, y); if (x < grid_size[0] - 1) { + // Check the sector to the east auto neighbor = grid->get_sector(x + 1, y); auto portals = sector->find_portals(neighbor, PortalDirection::EAST_WEST, portal_id); + + // Add the portals to the sector and the neighbor for (auto &portal : portals) { sector->add_portal(portal); neighbor->add_portal(portal); @@ -51,8 +61,11 @@ void path_demo_1(const util::Path &path) { portal_id += portals.size(); } if (y < grid_size[1] - 1) { + // Check the sector to the south auto neighbor = grid->get_sector(x, y + 1); auto portals = sector->find_portals(neighbor, PortalDirection::NORTH_SOUTH, portal_id); + + // Add the portals to the sector and the neighbor for (auto &portal : portals) { sector->add_portal(portal); neighbor->add_portal(portal); @@ -62,7 +75,8 @@ void path_demo_1(const util::Path &path) { } } - // Connect portals inside sectors. + // Connect portals that can mutually reach each other + // within the same sector for (auto sector : grid->get_sectors()) { sector->connect_exits(); @@ -81,19 +95,25 @@ void path_demo_1(const util::Path &path) { auto pathfinder = std::make_shared(); pathfinder->add_grid(grid); + // Add a timer to measure the pathfinding speed util::Timer timer; - // Create a path request and get the path + // Create a path request from one end of the grid to the other coord::tile start{2, 26}; coord::tile target{36, 2}; - PathRequest path_request{ grid->get_id(), start, target, + time::TIME_ZERO, }; + + // Initialize the portal nodes of the grid + // This is used for the A* pathfinding search at the beginning grid->init_portal_nodes(); + timer.start(); + // Let the pathfinder search for a path Path path_result = pathfinder->get_path(path_request); timer.stop(); @@ -109,8 +129,11 @@ void path_demo_1(const util::Path &path) { auto render_manager = std::make_shared(qtapp, window, path, grid); log::log(INFO << "Created render manager for pathfinding demo"); + // Callbacks for mouse button events + // Used to set the start and target cells for pathfinding window->add_mouse_button_callback([&](const QMouseEvent &ev) { if (ev.type() == QEvent::MouseButtonRelease) { + // From the mouse position, calculate the position/cell on the grid auto cell_count_x = grid->get_size()[0] * grid->get_sector_size(); auto cell_count_y = grid->get_size()[1] * grid->get_sector_size(); auto window_size = window->get_size(); @@ -127,6 +150,7 @@ void path_demo_1(const util::Path &path) { grid->get_id(), start, target, + time::TIME_ZERO, }; timer.reset(); @@ -147,6 +171,7 @@ void path_demo_1(const util::Path &path) { grid->get_id(), start, target, + time::TIME_ZERO, }; timer.reset(); diff --git a/libopenage/pathfinding/demo/demo_1.h b/libopenage/pathfinding/demo/demo_1.h index db70efe084..b2d3037eb8 100644 --- a/libopenage/pathfinding/demo/demo_1.h +++ b/libopenage/pathfinding/demo/demo_1.h @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #pragma once @@ -182,7 +182,7 @@ class RenderManager1 { // Cost for the sectors in the grid // taken from Figure 23.1 in "Crowd Pathfinding and Steering Using Flow Field Tiles" -const std::vector> sectors_cost = { +const std::vector> SECTORS_COST = { { // clang-format off 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, diff --git a/libopenage/pathfinding/field_cache.cpp b/libopenage/pathfinding/field_cache.cpp new file mode 100644 index 0000000000..86f985e3ba --- /dev/null +++ b/libopenage/pathfinding/field_cache.cpp @@ -0,0 +1,32 @@ +// Copyright 2024-2025 the openage authors. See copying.md for legal info. + +#include "field_cache.h" + +namespace openage::path { + +void FieldCache::add(const cache_key_t cache_key, + const field_cache_t cache_entry) { + this->cache[cache_key] = cache_entry; +} + +bool FieldCache::evict(const cache_key_t cache_key) { + return this->cache.erase(cache_key) != 0; +} + +bool FieldCache::is_cached(const cache_key_t cache_key) const { + return this->cache.contains(cache_key); +} + +std::shared_ptr FieldCache::get_integration_field(const cache_key_t cache_key) const { + return this->cache.at(cache_key).first; +} + +std::shared_ptr FieldCache::get_flow_field(const cache_key_t cache_key) const { + return this->cache.at(cache_key).second; +} + +field_cache_t FieldCache::get(const cache_key_t cache_key) const { + return this->cache.at(cache_key); +} + +} // namespace openage::path diff --git a/libopenage/pathfinding/field_cache.h b/libopenage/pathfinding/field_cache.h new file mode 100644 index 0000000000..b5c39b44de --- /dev/null +++ b/libopenage/pathfinding/field_cache.h @@ -0,0 +1,110 @@ +// Copyright 2024-2025 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + +#include "pathfinding/types.h" +#include "util/hash.h" + + +namespace openage { +namespace coord { +struct tile_delta; +} // namespace coord + +namespace path { +class IntegrationField; +class FlowField; + +/** + * Cache to store already calculated flow and integration fields for the pathfinding algorithm. + */ +class FieldCache { +public: + FieldCache() = default; + ~FieldCache() = default; + + /** + * Adds a new field entry to the cache. + * + * @param cache_key Cache key for the field entry. + * @param cache_entry Field entry (integration field, flow field). + */ + void add(const cache_key_t cache_key, + const field_cache_t cache_entry); + + /** + * Evict field entry from the cache. + * + * @param cache_key Cache key for the field entry to evict. + * + * @return true if the cache key was found and evicted, false otherwise. + */ + bool evict(const cache_key_t cache_key); + + /** + * Check if there is a cached entry for a specific cache key. + * + * @param cache_key Cache key to check. + * + * @return true if a field entry is found for the cache key, false otherwise. + */ + bool is_cached(const cache_key_t cache_key) const; + + /** + * Get a cached integration field. + * + * @param cache_key Cache key for the field entry. + * + * @return Integration field. + */ + std::shared_ptr get_integration_field(const cache_key_t cache_key) const; + + /** + * Get a cached flow field. + * + * @param cache_key Cache key for the field entry. + * + * @return Flow field. + */ + std::shared_ptr get_flow_field(const cache_key_t cache_key) const; + + /** + * Get a cached field entry. + * + * Contains both integration field and flow field. + * + * @param cache_key Cache key for the field entry. + * + * @return Field entry (integration field, flow field). + */ + field_cache_t get(const cache_key_t cache_key) const; + +private: + /** + * Hash function for the field cache. + */ + struct pair_hash { + template + std::size_t operator()(const std::pair &pair) const { + return util::hash_combine(std::hash{}(pair.first), std::hash{}(pair.second)); + } + }; + + /** + * Cache for already computed fields. + * + * Key is the portal ID and the sector ID from which the field was entered. Fields that are cached are + * cleared of dynamic flags, i.e. wavefront or LOS flags. These have to be recalculated + * when the field is reused. + */ + std::unordered_map + cache; +}; + +} // namespace path +} // namespace openage diff --git a/libopenage/pathfinding/integrator.cpp b/libopenage/pathfinding/integrator.cpp index 265c5285cc..f4dcf324b1 100644 --- a/libopenage/pathfinding/integrator.cpp +++ b/libopenage/pathfinding/integrator.cpp @@ -1,17 +1,23 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #include "integrator.h" #include "log/log.h" #include "pathfinding/cost_field.h" +#include "pathfinding/field_cache.h" #include "pathfinding/flow_field.h" #include "pathfinding/integration_field.h" #include "pathfinding/portal.h" +#include "time/time.h" namespace openage::path { +Integrator::Integrator() : + field_cache{std::make_unique()} { +} + std::shared_ptr Integrator::integrate(const std::shared_ptr &cost_field, const coord::tile_delta &target, bool with_los) { @@ -36,24 +42,33 @@ std::shared_ptr Integrator::integrate(const std::shared_ptr &portal, const coord::tile_delta &target, + const time::time_t &time, bool with_los) { auto cache_key = std::make_pair(portal->get_id(), other_sector_id); - auto cached = this->field_cache.find(cache_key); - if (cached != this->field_cache.end()) { + if (cost_field->is_dirty(time)) { + log::log(DBG << "Evicting cached integration and flow fields for portal " << portal->get_id() + << " from sector " << other_sector_id); + this->field_cache->evict(cache_key); + } + else if (this->field_cache->is_cached(cache_key)) { log::log(DBG << "Using cached integration field for portal " << portal->get_id() << " from sector " << other_sector_id); + + // retrieve cached integration field + auto cached_integration_field = this->field_cache->get_integration_field(cache_key); + if (with_los) { log::log(SPAM << "Performing LOS pass on cached field"); // Make a copy of the cached field to avoid modifying the cached field - auto integration_field = std::make_shared(*cached->second.first); + auto integration_field = std::make_shared(*cached_integration_field); // Only integrate LOS; leave the rest of the field as is integration_field->integrate_los(cost_field, other, other_sector_id, portal, target); return integration_field; } - return cached->second.first; + return cached_integration_field; } log::log(DBG << "Integrating cost field for portal " << portal->get_id() @@ -99,15 +114,18 @@ std::shared_ptr Integrator::build(const std::shared_ptr &portal, bool with_los) { auto cache_key = std::make_pair(portal->get_id(), other_sector_id); - auto cached = this->field_cache.find(cache_key); - if (cached != this->field_cache.end()) { + if (this->field_cache->is_cached(cache_key)) { log::log(DBG << "Using cached flow field for portal " << portal->get_id() << " from sector " << other_sector_id); + + // retrieve cached flow field + auto cached_flow_field = this->field_cache->get_flow_field(cache_key); + if (with_los) { log::log(SPAM << "Transferring LOS flags to cached flow field"); // Make a copy of the cached flow field - auto flow_field = std::make_shared(*cached->second.second); + auto flow_field = std::make_shared(*cached_flow_field); // Transfer the LOS flags to the flow field flow_field->transfer_dynamic_flags(integration_field); @@ -115,7 +133,7 @@ std::shared_ptr Integrator::build(const std::shared_ptrsecond.second; + return cached_flow_field; } log::log(DBG << "Building flow field for portal " << portal->get_id() @@ -140,17 +158,28 @@ Integrator::get_return_t Integrator::get(const std::shared_ptr &cost_ sector_id_t other_sector_id, const std::shared_ptr &portal, const coord::tile_delta &target, + const time::time_t &time, bool with_los) { auto cache_key = std::make_pair(portal->get_id(), other_sector_id); - auto cached = this->field_cache.find(cache_key); - if (cached != this->field_cache.end()) { + if (cost_field->is_dirty(time)) { + log::log(DBG << "Evicting cached integration and flow fields for portal " << portal->get_id() + << " from sector " << other_sector_id); + this->field_cache->evict(cache_key); + } + else if (this->field_cache->is_cached(cache_key)) { log::log(DBG << "Using cached integration and flow fields for portal " << portal->get_id() << " from sector " << other_sector_id); + + // retrieve cached fields + auto cached_fields = this->field_cache->get(cache_key); + auto cached_integration_field = cached_fields.first; + auto cached_flow_field = cached_fields.second; + if (with_los) { log::log(SPAM << "Performing LOS pass on cached field"); // Make a copy of the cached integration field - auto integration_field = std::make_shared(*cached->second.first); + auto integration_field = std::make_shared(*cached_integration_field); // Only integrate LOS; leave the rest of the field as is integration_field->integrate_los(cost_field, other, other_sector_id, portal, target); @@ -158,7 +187,7 @@ Integrator::get_return_t Integrator::get(const std::shared_ptr &cost_ log::log(SPAM << "Transferring LOS flags to cached flow field"); // Make a copy of the cached flow field - auto flow_field = std::make_shared(*cached->second.second); + auto flow_field = std::make_shared(*cached_flow_field); // Transfer the LOS flags to the flow field flow_field->transfer_dynamic_flags(integration_field); @@ -166,10 +195,10 @@ Integrator::get_return_t Integrator::get(const std::shared_ptr &cost_ return std::make_pair(integration_field, flow_field); } - return cached->second; + return std::make_pair(cached_integration_field, cached_flow_field); } - auto integration_field = this->integrate(cost_field, other, other_sector_id, portal, target, with_los); + auto integration_field = this->integrate(cost_field, other, other_sector_id, portal, target, time, with_los); auto flow_field = this->build(integration_field, other, other_sector_id, portal); log::log(DBG << "Caching integration and flow fields for portal ID: " << portal->get_id() @@ -182,7 +211,9 @@ Integrator::get_return_t Integrator::get(const std::shared_ptr &cost_ std::shared_ptr cached_flow_field = std::make_shared(*flow_field); cached_flow_field->reset_dynamic_flags(); - this->field_cache[cache_key] = std::make_pair(cached_integration_field, cached_flow_field); + field_cache_t field_cache = field_cache_t(cached_integration_field, cached_flow_field); + + this->field_cache->add(cache_key, field_cache); return std::make_pair(integration_field, flow_field); } diff --git a/libopenage/pathfinding/integrator.h b/libopenage/pathfinding/integrator.h index c27d3c1eb1..490d5eaacb 100644 --- a/libopenage/pathfinding/integrator.h +++ b/libopenage/pathfinding/integrator.h @@ -1,13 +1,13 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #pragma once -#include #include -#include #include "pathfinding/types.h" +#include "pathfinding/field_cache.h" #include "util/hash.h" +#include "time/time.h" namespace openage { @@ -26,7 +26,11 @@ class Portal; */ class Integrator { public: - Integrator() = default; + /** + * Create a new integrator. + */ + Integrator(); + ~Integrator() = default; /** @@ -56,6 +60,7 @@ class Integrator { * @param other_sector_id Sector ID of the other side of the portal. * @param portal Portal. * @param target Coordinates of the target cell, relative to the integration field origin. + * @param time Time of the path request. * @param with_los If true an LOS pass is performed before cost integration. * * @return Integration field. @@ -65,6 +70,7 @@ class Integrator { sector_id_t other_sector_id, const std::shared_ptr &portal, const coord::tile_delta &target, + const time::time_t &time, bool with_los = true); /** @@ -114,6 +120,7 @@ class Integrator { * @param other_sector_id Sector ID of the other side of the portal. * @param portal Portal. * @param target Coordinates of the target cell, relative to the integration field origin. + * @param time Time of the path request. * @param with_los If true an LOS pass is performed before cost integration. * * @return Integration field and flow field. @@ -123,30 +130,14 @@ class Integrator { sector_id_t other_sector_id, const std::shared_ptr &portal, const coord::tile_delta &target, + const time::time_t &time, bool with_los = true); private: - /** - * Hash function for the field cache. - */ - struct pair_hash { - template - std::size_t operator()(const std::pair &pair) const { - return util::hash_combine(std::hash{}(pair.first), std::hash{}(pair.second)); - } - }; - /** * Cache for already computed fields. - * - * Key is the portal ID and the sector ID from which the field was entered. Fields that are cached are - * cleared of dynamic flags, i.e. wavefront or LOS flags. These have to be recalculated - * when the field is reused. */ - std::unordered_map, - get_return_t, - pair_hash> - field_cache; + std::unique_ptr field_cache; }; } // namespace path diff --git a/libopenage/pathfinding/path.h b/libopenage/pathfinding/path.h index d1c61ac325..df1b34a599 100644 --- a/libopenage/pathfinding/path.h +++ b/libopenage/pathfinding/path.h @@ -6,6 +6,7 @@ #include "coord/tile.h" #include "pathfinding/types.h" +#include "time/time.h" namespace openage::path { @@ -20,6 +21,8 @@ struct PathRequest { coord::tile start; /// Target position of the path. coord::tile target; + /// Time the request was made. + const time::time_t time; }; /** diff --git a/libopenage/pathfinding/pathfinder.cpp b/libopenage/pathfinding/pathfinder.cpp index e46919e62b..5a9c785f0c 100644 --- a/libopenage/pathfinding/pathfinder.cpp +++ b/libopenage/pathfinding/pathfinder.cpp @@ -155,6 +155,7 @@ const Path Pathfinder::get_path(const PathRequest &request) { prev_sector_id, portal, target_delta, + request.time, with_los); flow_fields.push_back(std::make_pair(next_sector_id, sector_fields.second)); @@ -231,8 +232,7 @@ const Pathfinder::portal_star_t Pathfinder::portal_a_star(const PathRequest &req auto portal_pos = portal->get_exit_center(start_sector->get_id()); auto portal_abs_pos = sector_pos + portal_pos; auto heuristic_cost = Pathfinder::heuristic_cost(portal_abs_pos, request.target); - - portal_node->current_cost = 0; + portal_node->current_cost = Pathfinder::heuristic_cost(portal_abs_pos, request.start); portal_node->heuristic_cost = heuristic_cost; portal_node->future_cost = portal_node->current_cost + heuristic_cost; @@ -270,11 +270,12 @@ const Pathfinder::portal_star_t Pathfinder::portal_a_star(const PathRequest &req } // get the exits of the current node - const auto &exits = current_node->get_exits(current_node->entry_sector); + ENSURE(current_node->entry_sector != std::nullopt, "Entry sector not set for portal node."); + const auto &exits = current_node->get_exits(current_node->entry_sector.value()); // evaluate all neighbors of the current candidate for further progress for (auto &[exit, distance_cost] : exits) { - exit->entry_sector = current_node->portal->get_exit_sector(current_node->entry_sector); + exit->entry_sector = current_node->portal->get_exit_sector(current_node->entry_sector.value()); bool not_visited = !visited_portals.contains(exit->portal->get_id()); if (not_visited) { @@ -290,9 +291,9 @@ const Pathfinder::portal_star_t Pathfinder::portal_a_star(const PathRequest &req if (not_visited or tentative_cost < exit->current_cost) { if (not_visited) { // Get heuristic cost (from exit node to target cell) - auto exit_sector = grid->get_sector(exit->portal->get_exit_sector(exit->entry_sector)); + auto exit_sector = grid->get_sector(exit->portal->get_exit_sector(exit->entry_sector.value())); auto exit_sector_pos = exit_sector->get_position().to_tile(sector_size); - auto exit_portal_pos = exit->portal->get_exit_center(exit->entry_sector); + auto exit_portal_pos = exit->portal->get_exit_center(exit->entry_sector.value()); exit->heuristic_cost = Pathfinder::heuristic_cost( exit_sector_pos + exit_portal_pos, request.target); @@ -470,7 +471,7 @@ int Pathfinder::distance_cost(const coord::tile_delta &portal1_pos, PortalNode::PortalNode(const std::shared_ptr &portal) : portal{portal}, - entry_sector{NULL}, + entry_sector{std::nullopt}, future_cost{std::numeric_limits::max()}, current_cost{std::numeric_limits::max()}, heuristic_cost{std::numeric_limits::max()}, diff --git a/libopenage/pathfinding/pathfinder.h b/libopenage/pathfinding/pathfinder.h index e2529987b7..8d569d78d8 100644 --- a/libopenage/pathfinding/pathfinder.h +++ b/libopenage/pathfinding/pathfinder.h @@ -4,6 +4,7 @@ #include #include +#include #include #include "coord/tile.h" @@ -209,7 +210,7 @@ class PortalNode : public std::enable_shared_from_this { /** * Sector where the portal is entered. */ - sector_id_t entry_sector; + std::optional entry_sector; /** * Future cost estimation value for this node. diff --git a/libopenage/pathfinding/sector.cpp b/libopenage/pathfinding/sector.cpp index f50dc209bf..5897bbef84 100644 --- a/libopenage/pathfinding/sector.cpp +++ b/libopenage/pathfinding/sector.cpp @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #include "sector.h" diff --git a/libopenage/pathfinding/sector.h b/libopenage/pathfinding/sector.h index a4ba94fa52..d684a399b6 100644 --- a/libopenage/pathfinding/sector.h +++ b/libopenage/pathfinding/sector.h @@ -1,4 +1,4 @@ -// Copyright 2024-2024 the openage authors. See copying.md for legal info. +// Copyright 2024-2025 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/pathfinding/tests.cpp b/libopenage/pathfinding/tests.cpp index 5f92496ff8..2ac0960c34 100644 --- a/libopenage/pathfinding/tests.cpp +++ b/libopenage/pathfinding/tests.cpp @@ -10,6 +10,7 @@ #include "pathfinding/integration_field.h" #include "pathfinding/integrator.h" #include "pathfinding/types.h" +#include "time/time.h" namespace openage { @@ -23,7 +24,8 @@ void flow_field() { // | 1 | 1 | 1 | // | 1 | X | 1 | // | 1 | 1 | 1 | - cost_field->set_costs({1, 1, 1, 1, 255, 1, 1, 1, 1}); + const time::time_t time = time::TIME_ZERO; + cost_field->set_costs({1, 1, 1, 1, 255, 1, 1, 1, 1}, time); // Test the different field types { diff --git a/libopenage/pathfinding/types.h b/libopenage/pathfinding/types.h index 1b48a49654..3229010a91 100644 --- a/libopenage/pathfinding/types.h +++ b/libopenage/pathfinding/types.h @@ -4,6 +4,8 @@ #include #include +#include +#include namespace openage::path { @@ -109,4 +111,17 @@ using sector_id_t = size_t; */ using portal_id_t = size_t; +class FlowField; +class IntegrationField; + +/** + * Cache key for accessing the field cache using a portal id and a sector id. + */ +using cache_key_t = std::pair; + +/** + * Returnable field cache entry pair containing an integration field and a flow field. + */ +using field_cache_t = std::pair, std::shared_ptr>; + } // namespace openage::path diff --git a/libopenage/presenter/presenter.cpp b/libopenage/presenter/presenter.cpp index a37362be95..9775b62d9b 100644 --- a/libopenage/presenter/presenter.cpp +++ b/libopenage/presenter/presenter.cpp @@ -32,7 +32,6 @@ #include "renderer/stages/skybox/render_stage.h" #include "renderer/stages/terrain/render_stage.h" #include "renderer/stages/world/render_stage.h" -#include "renderer/window.h" #include "time/time_loop.h" #include "util/path.h" @@ -48,10 +47,10 @@ Presenter::Presenter(const util::Path &root_dir, time_loop{time_loop} {} -void Presenter::run(bool debug_graphics) { +void Presenter::run(const renderer::window_settings window_settings) { log::log(INFO << "Presenter: Launching subsystems..."); - this->init_graphics(debug_graphics); + this->init_graphics(window_settings); this->init_input(); @@ -93,18 +92,14 @@ std::shared_ptr Presenter::init_window_system() { return std::make_shared(); } -void Presenter::init_graphics(bool debug) { +void Presenter::init_graphics(const renderer::window_settings &window_settings) { log::log(INFO << "Presenter: Initializing graphics subsystems..."); // Start up rendering framework this->gui_app = this->init_window_system(); // Window and renderer - renderer::window_settings settings; - settings.width = 1024; - settings.height = 768; - settings.debug = debug; - this->window = renderer::Window::create("openage presenter test", settings); + this->window = renderer::Window::create("openage presenter test", window_settings); this->renderer = this->window->make_renderer(); // Asset mangement diff --git a/libopenage/presenter/presenter.h b/libopenage/presenter/presenter.h index b43d4fc60f..2059daeed0 100644 --- a/libopenage/presenter/presenter.h +++ b/libopenage/presenter/presenter.h @@ -5,8 +5,10 @@ #include #include +#include "renderer/window.h" #include "util/path.h" + namespace qtgui { class GuiApplication; } @@ -87,9 +89,9 @@ class Presenter { /** * Start the presenter and initialize subsystems. * - * @param debug_graphics If true, enable OpenGL debug logging. + * @param window_settings The settings to customize the display window (e.g. size, display mode, vsync). */ - void run(bool debug_graphics = false); + void run(const renderer::window_settings window_settings = {}); /** * Set the game simulation controlled by this presenter. @@ -120,7 +122,7 @@ class Presenter { * - main renderer * - component renderers (Terrain, Game Entities, GUI) */ - void init_graphics(bool debug = false); + void init_graphics(const renderer::window_settings &window_settings = {}); /** * Initialize the GUI. diff --git a/libopenage/renderer/demo/CMakeLists.txt b/libopenage/renderer/demo/CMakeLists.txt index fb93e5279b..7567731afd 100644 --- a/libopenage/renderer/demo/CMakeLists.txt +++ b/libopenage/renderer/demo/CMakeLists.txt @@ -6,6 +6,7 @@ add_sources(libopenage demo_4.cpp demo_5.cpp demo_6.cpp + demo_7.cpp stresstest_0.cpp stresstest_1.cpp tests.cpp diff --git a/libopenage/renderer/demo/demo_0.cpp b/libopenage/renderer/demo/demo_0.cpp index 38fd467d0b..d027c2d1e0 100644 --- a/libopenage/renderer/demo/demo_0.cpp +++ b/libopenage/renderer/demo/demo_0.cpp @@ -2,6 +2,7 @@ #include "demo_0.h" +#include "renderer/demo/util.h" #include "renderer/gui/integration/public/gui_application_with_logger.h" #include "renderer/opengl/window.h" #include "renderer/render_pass.h" @@ -50,6 +51,10 @@ void renderer_demo_0(const util::Path &path) { false, }; + if (not check_uniform_completeness({display_stuff})) { + log::log(WARN << "Uniforms not complete."); + } + auto pass = renderer->add_render_pass({display_stuff}, renderer->get_display_target()); while (not window.should_close()) { diff --git a/libopenage/renderer/demo/demo_1.cpp b/libopenage/renderer/demo/demo_1.cpp index 23dfdfa67b..c28b7dc296 100644 --- a/libopenage/renderer/demo/demo_1.cpp +++ b/libopenage/renderer/demo/demo_1.cpp @@ -6,6 +6,7 @@ #include #include +#include "renderer/demo/util.h" #include "renderer/gui/integration/public/gui_application_with_logger.h" #include "renderer/opengl/window.h" #include "renderer/render_pass.h" @@ -172,6 +173,10 @@ void renderer_demo_1(const util::Path &path) { auto pass2 = renderer->add_render_pass({display_obj}, renderer->get_display_target()); + if (not check_uniform_completeness({obj1, obj2, obj3, proj_update, display_obj})) { + log::log(WARN << "Uniforms not complete."); + } + /* Data retrieved from the object index texture. */ resources::Texture2dData id_texture_data = id_texture->into_data(); bool texture_data_valid = false; @@ -188,7 +193,7 @@ void renderer_demo_1(const util::Path &path) { ssize_t y = qpos.y(); log::log(INFO << "Clicked at location (" << x << ", " << y << ")"); - if (!texture_data_valid) { + if (not texture_data_valid) { id_texture_data = id_texture->into_data(); texture_data_valid = true; } diff --git a/libopenage/renderer/demo/demo_2.cpp b/libopenage/renderer/demo/demo_2.cpp index e615d238ba..c2a223d51b 100644 --- a/libopenage/renderer/demo/demo_2.cpp +++ b/libopenage/renderer/demo/demo_2.cpp @@ -6,6 +6,7 @@ #include #include +#include "renderer/demo/util.h" #include "renderer/gui/integration/public/gui_application_with_logger.h" #include "renderer/opengl/window.h" #include "renderer/render_pass.h" @@ -224,6 +225,10 @@ void renderer_demo_2(const util::Path &path) { auto pass2 = renderer->add_render_pass({display_obj}, renderer->get_display_target()); + if (not check_uniform_completeness({proj_update, obj1, display_obj})) { + log::log(WARN << "Uniforms not complete."); + } + /* Data retrieved from the object index texture. */ resources::Texture2dData id_texture_data = id_texture->into_data(); bool texture_data_valid = false; @@ -243,7 +248,7 @@ void renderer_demo_2(const util::Path &path) { ssize_t y = qpos.y(); log::log(INFO << "Clicked at location (" << x << ", " << y << ")"); - if (!texture_data_valid) { + if (not texture_data_valid) { id_texture_data = id_texture->into_data(); texture_data_valid = true; } diff --git a/libopenage/renderer/demo/demo_4.cpp b/libopenage/renderer/demo/demo_4.cpp index fb39057e5d..7f4704a38a 100644 --- a/libopenage/renderer/demo/demo_4.cpp +++ b/libopenage/renderer/demo/demo_4.cpp @@ -5,6 +5,7 @@ #include #include +#include "renderer/demo/util.h" #include "renderer/gui/integration/public/gui_application_with_logger.h" #include "renderer/opengl/window.h" #include "renderer/render_pass.h" @@ -165,6 +166,10 @@ void renderer_demo_4(const util::Path &path) { auto pass2 = renderer->add_render_pass({display_obj}, renderer->get_display_target()); + if (not check_uniform_completeness({proj_update, obj1, display_obj})) { + log::log(WARN << "Uniforms not complete."); + } + window.add_resize_callback([&](size_t w, size_t h, double /*scale*/) { /* Calculate a projection matrix for the new screen size. */ float aspectRatio = float(w) / float(h); diff --git a/libopenage/renderer/demo/demo_5.cpp b/libopenage/renderer/demo/demo_5.cpp index 85752db18c..34e8f4db22 100644 --- a/libopenage/renderer/demo/demo_5.cpp +++ b/libopenage/renderer/demo/demo_5.cpp @@ -7,6 +7,7 @@ #include #include "renderer/camera/camera.h" +#include "renderer/demo/util.h" #include "renderer/gui/integration/public/gui_application_with_logger.h" #include "renderer/opengl/window.h" #include "renderer/render_pass.h" @@ -134,6 +135,10 @@ void renderer_demo_5(const util::Path &path) { "tex", gltex); + if (not check_uniform_completeness({terrain_obj})) { + log::log(WARN << "Uniforms not complete."); + } + // Move around the scene with WASD window.add_key_callback([&](const QKeyEvent &ev) { bool cam_update = false; diff --git a/libopenage/renderer/demo/demo_6.cpp b/libopenage/renderer/demo/demo_6.cpp index 9844503e64..e8beaafc6c 100644 --- a/libopenage/renderer/demo/demo_6.cpp +++ b/libopenage/renderer/demo/demo_6.cpp @@ -9,6 +9,7 @@ #include "renderer/camera/camera.h" #include "renderer/camera/frustum_2d.h" #include "renderer/camera/frustum_3d.h" +#include "renderer/demo/util.h" #include "renderer/gui/integration/public/gui_application_with_logger.h" #include "renderer/opengl/window.h" #include "renderer/render_pass.h" @@ -198,6 +199,7 @@ const renderer::Renderable RenderManagerDemo6::create_3d_obj() { auto terrain_unifs = this->obj_3d_shader->new_uniform_input( "tex", this->obj_3d_texture); + std::vector terrain_pos{}; terrain_pos.push_back({-25, -25, 0}); terrain_pos.push_back({25, -25, 0}); @@ -487,6 +489,10 @@ void RenderManagerDemo6::create_render_passes() { this->display_pass = renderer->add_render_pass( {display_obj_3d, display_obj_2d, display_obj_frame}, renderer->get_display_target()); + + if (not check_uniform_completeness({display_obj_3d, display_obj_2d, display_obj_frame})) { + log::log(WARN << "Uniforms not complete."); + } } } // namespace openage::renderer::tests diff --git a/libopenage/renderer/demo/demo_7.cpp b/libopenage/renderer/demo/demo_7.cpp new file mode 100644 index 0000000000..8c804a52d9 --- /dev/null +++ b/libopenage/renderer/demo/demo_7.cpp @@ -0,0 +1,83 @@ +// Copyright 2025-2025 the openage authors. See copying.md for legal info. + +#include "demo_7.h" + +#include "util/path.h" + +#include "renderer/demo/util.h" +#include "renderer/gui/integration/public/gui_application_with_logger.h" +#include "renderer/opengl/window.h" +#include "renderer/render_pass.h" +#include "renderer/render_target.h" +#include "renderer/resources/mesh_data.h" +#include "renderer/resources/shader_source.h" +#include "renderer/resources/shader_template.h" +#include "renderer/shader_program.h" + + +namespace openage::renderer::tests { + +void renderer_demo_7(const util::Path &path) { + // Basic setup + auto qtapp = std::make_shared(); + window_settings settings; + settings.width = 800; + settings.height = 600; + settings.debug = true; + + opengl::GlWindow window("Shader Commands Demo", settings); + auto renderer = window.make_renderer(); + + auto shaderdir = path / "assets" / "test" / "shaders"; + + // Initialize shader template + resources::ShaderTemplate frag_template(shaderdir / "demo_7_shader_command.frag.glsl"); + + // Load snippets from a snippet directory + frag_template.load_snippets(shaderdir / "demo_7_snippets"); + + auto vert_shader_file = (shaderdir / "demo_7_shader_command.vert.glsl").open(); + auto vert_shader_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::vertex, + vert_shader_file.read()); + vert_shader_file.close(); + + auto frag_shader_src = frag_template.generate_source(); + + auto shader = renderer->add_shader({vert_shader_src, frag_shader_src}); + + // Create a simple quad for rendering + auto quad = renderer->add_mesh_geometry(resources::MeshData::make_quad()); + + auto uniforms = shader->new_uniform_input("time", 0.0f); + + Renderable display_obj{ + uniforms, + quad, + false, + false, + }; + + if (not check_uniform_completeness({display_obj})) { + log::log(WARN << "Uniforms not complete."); + } + + auto pass = renderer->add_render_pass({display_obj}, renderer->get_display_target()); + + // Main loop + float time = 0.0f; + while (not window.should_close()) { + time += 0.016f; + uniforms->update("time", time); + + renderer->render(pass); + window.update(); + qtapp->process_events(); + + renderer->check_error(); + } + window.close(); +} + +} // namespace openage::renderer::tests diff --git a/libopenage/renderer/demo/demo_7.h b/libopenage/renderer/demo/demo_7.h new file mode 100644 index 0000000000..7642fe06c8 --- /dev/null +++ b/libopenage/renderer/demo/demo_7.h @@ -0,0 +1,22 @@ +// Copyright 2025-2025 the openage authors. See copying.md for legal info. + +#pragma once + +#include "util/path.h" + +namespace openage::renderer::tests { + +/** + * Demonstrate the shader template system for shader generation. + * - Window creation + * - Create a shader template + * - Load shader snippets (command) from files + * - Generate shader sources from the template + * - Creating a render pass + * - Creating a renderable from a mesh + * + * @param path Path to the project rootdir. + */ +void renderer_demo_7(const util::Path &path); + +} // namespace openage::renderer::tests diff --git a/libopenage/renderer/demo/tests.cpp b/libopenage/renderer/demo/tests.cpp index d3fb0e3c21..bab82af2e3 100644 --- a/libopenage/renderer/demo/tests.cpp +++ b/libopenage/renderer/demo/tests.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2024 the openage authors. See copying.md for legal info. +// Copyright 2015-2025 the openage authors. See copying.md for legal info. #include "tests.h" @@ -12,6 +12,7 @@ #include "renderer/demo/demo_4.h" #include "renderer/demo/demo_5.h" #include "renderer/demo/demo_6.h" +#include "renderer/demo/demo_7.h" #include "renderer/demo/stresstest_0.h" #include "renderer/demo/stresstest_1.h" @@ -47,6 +48,10 @@ void renderer_demo(int demo_id, const util::Path &path) { renderer_demo_6(path); break; + case 7: + renderer_demo_7(path); + break; + default: log::log(MSG(err) << "Unknown renderer demo requested: " << demo_id << "."); break; diff --git a/libopenage/renderer/demo/util.cpp b/libopenage/renderer/demo/util.cpp index aafffebf8d..0f39691cdb 100644 --- a/libopenage/renderer/demo/util.cpp +++ b/libopenage/renderer/demo/util.cpp @@ -1,8 +1,21 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #include "util.h" +#include "renderer/uniform_input.h" + namespace openage::renderer::tests { +bool check_uniform_completeness(const std::vector &renderables) { + // Iterate over each renderable object + for (const auto &renderable : renderables) { + if (renderable.uniform and not renderable.uniform->is_complete()) { + return false; + } + } + + return true; +} + } // namespace openage::renderer::tests diff --git a/libopenage/renderer/demo/util.h b/libopenage/renderer/demo/util.h index 9899867e2b..8d8130a306 100644 --- a/libopenage/renderer/demo/util.h +++ b/libopenage/renderer/demo/util.h @@ -1,7 +1,10 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #pragma once +#include + +#include "renderer/renderable.h" namespace openage::renderer::tests { @@ -11,4 +14,12 @@ namespace openage::renderer::tests { opengl::GlContext::check_error(); \ printf("after %s\n", txt); +/** + * Check if all uniform values for the given renderables have been set. + * + * @param renderables The list of renderable objects to check. + * @return true if all uniforms have been set, false otherwise. + */ +bool check_uniform_completeness(const std::vector &renderables); + } // namespace openage::renderer::tests diff --git a/libopenage/renderer/opengl/geometry.cpp b/libopenage/renderer/opengl/geometry.cpp index 0e2c930a2a..e0898edbe8 100644 --- a/libopenage/renderer/opengl/geometry.cpp +++ b/libopenage/renderer/opengl/geometry.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2024 the openage authors. See copying.md for legal info. +// Copyright 2015-2025 the openage authors. See copying.md for legal info. #include "geometry.h" @@ -54,6 +54,7 @@ void GlGeometry::update_verts_offset(std::vector const &verts, size_t o void GlGeometry::draw() const { switch (this->get_type()) { case geometry_t::bufferless_quad: + // any VAO must be bound before this call glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); break; diff --git a/libopenage/renderer/opengl/renderer.cpp b/libopenage/renderer/opengl/renderer.cpp index 33b734790f..6d8d909a5b 100644 --- a/libopenage/renderer/opengl/renderer.cpp +++ b/libopenage/renderer/opengl/renderer.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2024 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #include "renderer.h" @@ -15,6 +15,7 @@ #include "renderer/opengl/texture.h" #include "renderer/opengl/uniform_buffer.h" #include "renderer/opengl/uniform_input.h" +#include "renderer/opengl/vertex_array.h" #include "renderer/opengl/window.h" #include "renderer/resources/buffer_info.h" @@ -26,7 +27,8 @@ GlRenderer::GlRenderer(const std::shared_ptr &ctx, gl_context{ctx}, display{std::make_shared(ctx, viewport_size[0], - viewport_size[1])} { + viewport_size[1])}, + shared_quad_vao{std::make_shared(ctx)} { // color used to clear the color buffers glClearColor(0.0f, 0.0f, 0.0f, 0.0f); @@ -100,7 +102,7 @@ std::shared_ptr GlRenderer::add_uniform_buffer(resources::Uniform resources::UniformBufferInfo::get_size(input, info.get_layout()), resources::UniformBufferInfo::get_stride_size(input.type, info.get_layout()), input.count, - input.name}); + input.name}); offset += size; } @@ -169,6 +171,11 @@ void GlRenderer::render(const std::shared_ptr &pass) { auto gl_target = std::dynamic_pointer_cast(pass->get_target()); gl_target->bind_write(); + // ensure that an (empty) VAO is bound before rendering geometry + // a bound VAO is required to render bufferless quad geometries by OpenGL + // see https://www.khronos.org/opengl/wiki/Vertex_Rendering#Causes_of_rendering_failure + shared_quad_vao->bind(); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // TODO: Option for face culling diff --git a/libopenage/renderer/opengl/renderer.h b/libopenage/renderer/opengl/renderer.h index 458af24358..751af7cacc 100644 --- a/libopenage/renderer/opengl/renderer.h +++ b/libopenage/renderer/opengl/renderer.h @@ -1,4 +1,4 @@ -// Copyright 2017-2024 the openage authors. See copying.md for legal info. +// Copyright 2017-2025 the openage authors. See copying.md for legal info. #pragma once @@ -17,6 +17,7 @@ namespace opengl { class GlContext; class GlRenderPass; class GlRenderTarget; +class GlVertexArray; class GlWindow; /// The OpenGL specialization of the rendering interface. @@ -67,6 +68,14 @@ class GlRenderer final : public Renderer { /// The main screen surface as a render target. std::shared_ptr display; + + /// An empty vertex array object (VAO). + /// + /// This VAO has to be bound at the start of a render pass to ensure + /// that bufferless quad geometry can be drawn without errors. Drawing a + /// bufferless quad requires any VAO to be bound + /// see https://www.khronos.org/opengl/wiki/Vertex_Rendering#Causes_of_rendering_failure + std::shared_ptr shared_quad_vao; }; } // namespace opengl diff --git a/libopenage/renderer/opengl/uniform_input.cpp b/libopenage/renderer/opengl/uniform_input.cpp index 38c63f2d66..e6caed4ca8 100644 --- a/libopenage/renderer/opengl/uniform_input.cpp +++ b/libopenage/renderer/opengl/uniform_input.cpp @@ -50,4 +50,13 @@ GlUniformBufferInput::GlUniformBufferInput(const std::shared_ptr this->update_data.resize(offset); } +bool GlUniformInput::is_complete() const { + for (const auto &uniform : this->update_offs) { + if (not uniform.used) { + return false; + } + } + return true; +} + } // namespace openage::renderer::opengl diff --git a/libopenage/renderer/opengl/uniform_input.h b/libopenage/renderer/opengl/uniform_input.h index 961e12a2a5..c46c235414 100644 --- a/libopenage/renderer/opengl/uniform_input.h +++ b/libopenage/renderer/opengl/uniform_input.h @@ -35,6 +35,11 @@ class GlUniformInput final : public UniformInput { public: GlUniformInput(const std::shared_ptr &prog); + /** + * Check if all uniforms have been set. + */ + bool is_complete() const override; + /** * Store the IDs of the uniforms from the shader set by this uniform input. */ diff --git a/libopenage/renderer/opengl/window.cpp b/libopenage/renderer/opengl/window.cpp index 5f75717540..548575dd2d 100644 --- a/libopenage/renderer/opengl/window.cpp +++ b/libopenage/renderer/opengl/window.cpp @@ -57,6 +57,23 @@ GlWindow::GlWindow(const std::string &title, this->window->setFormat(format); this->window->create(); + // set display mode + // Reset to a known state + this->window->setWindowState(Qt::WindowNoState); + switch (settings.mode) { + case window_mode::WINDOWED: + // nothing to do because it's the default + break; + case window_mode::BORDERLESS: + this->window->setFlags(this->window->flags() | Qt::FramelessWindowHint); + break; + case window_mode::FULLSCREEN: + this->window->setWindowState(Qt::WindowFullScreen); + break; + default: + throw Error{MSG(err) << "Invalid window mode."}; + } + this->context = std::make_shared(this->window, settings.debug); if (not this->context->get_raw_context()->isValid()) { throw Error{MSG(err) << "Failed to create Qt OpenGL context."}; diff --git a/libopenage/renderer/resources/CMakeLists.txt b/libopenage/renderer/resources/CMakeLists.txt index c5946910e5..16a0e5eb5f 100644 --- a/libopenage/renderer/resources/CMakeLists.txt +++ b/libopenage/renderer/resources/CMakeLists.txt @@ -4,6 +4,7 @@ add_sources(libopenage mesh_data.cpp palette_info.cpp shader_source.cpp + shader_template.cpp texture_data.cpp texture_info.cpp texture_subinfo.cpp diff --git a/libopenage/renderer/resources/shader_template.cpp b/libopenage/renderer/resources/shader_template.cpp new file mode 100644 index 0000000000..c328ba82bc --- /dev/null +++ b/libopenage/renderer/resources/shader_template.cpp @@ -0,0 +1,75 @@ +// Copyright 2024-2025 the openage authors. See copying.md for legal info. + +#include "shader_template.h" + +#include + +#include "error/error.h" +#include "log/log.h" + +namespace openage::renderer::resources { + +ShaderTemplate::ShaderTemplate(const util::Path &template_path) { + auto file = template_path.open(); + this->template_code = file.read(); + file.close(); + + std::string marker = "// PLACEHOLDER: "; + size_t pos = 0; + + while ((pos = this->template_code.find(marker, pos)) != std::string::npos) { + size_t name_start = pos + marker.length(); + size_t line_end = this->template_code.find('\n', name_start); + std::string name = this->template_code.substr(name_start, line_end - name_start); + // Trim trailing whitespace (space, tab, carriage return, etc.) + name.erase(name.find_last_not_of(" \t\r\n") + 1); + + this->placeholders.push_back({name, pos, line_end - pos}); + pos = line_end; + } +} + +void ShaderTemplate::load_snippets(const util::Path &snippet_path) { + // load config here + util::Path snippet_path_copy = snippet_path; + for (const auto &entry : snippet_path_copy.iterdir()) { + if (entry.get_name().ends_with(".snippet")) { + add_snippet(entry); + } + } +} + +void ShaderTemplate::add_snippet(const util::Path &snippet_path) { + auto file = snippet_path.open(); + std::string content = file.read(); + file.close(); + + std::string name = snippet_path.get_stem(); + + snippets[name] = content; +} + +renderer::resources::ShaderSource ShaderTemplate::generate_source() const { + std::string result_src = template_code; + + // Replace placeholders in reverse order (to avoid offset issues) + for (auto it = placeholders.rbegin(); it != placeholders.rend(); ++it) { + const auto &ph = *it; + auto snippet_it = snippets.find(ph.name); + if (snippet_it != snippets.end()) { + result_src.replace(ph.position, ph.length, snippet_it->second); + } + else { + throw Error(MSG(err) << "Missing snippet for placeholder: " << ph.name); + } + } + + auto result = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::fragment, + std::move(result_src)); + + return result; +} + +} // namespace openage::renderer::world diff --git a/libopenage/renderer/resources/shader_template.h b/libopenage/renderer/resources/shader_template.h new file mode 100644 index 0000000000..3e6656629e --- /dev/null +++ b/libopenage/renderer/resources/shader_template.h @@ -0,0 +1,73 @@ +// Copyright 2024-2025 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include +#include + +#include "renderer/resources/shader_source.h" +#include "util/path.h" + +namespace openage { +namespace renderer { +namespace resources { + +/** + * Manages shader templates and their code snippets. + * Allows loading configurable shader commands and generating + * complete shader source code. + */ +class ShaderTemplate { +public: + /** + * Create a shader template from source code of shader. + * + * @param template_path Path to the template file. + */ + explicit ShaderTemplate(const util::Path &template_path); + + /** + * Load all snippets from a directory of .snippet files. + * + * @param snippet_path Path to directory containing snippet files. + */ + void load_snippets(const util::Path &snippet_path); + + /** + * Add a single code snippet to snippets map. + * + * @param name Snippet identifier. + * @param snippet_path Path to the snippet file. + */ + void add_snippet(const util::Path &snippet_path); + + /** + * Generate final shader source code with all snippets inserted. + * + * @return Complete shader code. + * @throws Error if any required placeholders are missing snippets. + */ + renderer::resources::ShaderSource generate_source() const; + +private: + /// Original template code with placeholders + std::string template_code; + /// Mapping of placeholder IDs to their code snippets + std::map snippets; + + /// Info about a placeholder found in the template + struct Placeholder { + std::string name; + size_t position; + size_t length; + }; + + /// All placeholders found in the template + /// precomputed on creation + std::vector placeholders; +}; +} // namespace world +} // namespace renderer +} // namespace openage diff --git a/libopenage/renderer/stages/world/render_stage.cpp b/libopenage/renderer/stages/world/render_stage.cpp index d8d93279af..eec13b602b 100644 --- a/libopenage/renderer/stages/world/render_stage.cpp +++ b/libopenage/renderer/stages/world/render_stage.cpp @@ -1,4 +1,4 @@ -// Copyright 2022-2024 the openage authors. See copying.md for legal info. +// Copyright 2022-2025 the openage authors. See copying.md for legal info. #include "render_stage.h" diff --git a/libopenage/renderer/stages/world/render_stage.h b/libopenage/renderer/stages/world/render_stage.h index f1256f3b57..7a0fe02a4c 100644 --- a/libopenage/renderer/stages/world/render_stage.h +++ b/libopenage/renderer/stages/world/render_stage.h @@ -1,4 +1,4 @@ -// Copyright 2022-2024 the openage authors. See copying.md for legal info. +// Copyright 2022-2025 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/renderer/uniform_input.h b/libopenage/renderer/uniform_input.h index 6f2536459b..39bb4ea983 100644 --- a/libopenage/renderer/uniform_input.h +++ b/libopenage/renderer/uniform_input.h @@ -66,6 +66,8 @@ class UniformInput : public DataInput { public: virtual ~UniformInput() = default; + virtual bool is_complete() const = 0; + void update() override; void update(const char *unif, int32_t val) override; void update(const char *unif, uint32_t val) override; diff --git a/libopenage/renderer/window.h b/libopenage/renderer/window.h index 71960e2d17..e3465bef4d 100644 --- a/libopenage/renderer/window.h +++ b/libopenage/renderer/window.h @@ -21,6 +21,15 @@ namespace openage::renderer { class WindowEventHandler; +/** + * Modes for window display. + */ +enum class window_mode { + FULLSCREEN, + BORDERLESS, + WINDOWED +}; + /** * Settings for creating a window. */ @@ -35,6 +44,8 @@ struct window_settings { bool vsync = true; // If true, enable debug logging for the selected backend. bool debug = false; + // Display mode for the window. + window_mode mode = window_mode::WINDOWED; }; diff --git a/libopenage/testing/testing.h b/libopenage/testing/testing.h index 50e03338e0..83f0efaab9 100644 --- a/libopenage/testing/testing.h +++ b/libopenage/testing/testing.h @@ -1,4 +1,4 @@ -// Copyright 2014-2024 the openage authors. See copying.md for legal info. +// Copyright 2014-2025 the openage authors. See copying.md for legal info. #pragma once @@ -51,7 +51,9 @@ bool fail(const log::message &msg); try { \ auto &&test_result_left = (left); \ if (test_result_left != (right)) { \ - TESTFAILMSG("unexpected value: " << (test_result_left)); \ + TESTFAILMSG(__FILE__ << ":" << __LINE__ << ": Expected " \ + << test_result_left << " and " \ + << (right) << " to be equal"); \ } \ } \ catch (::openage::testing::TestError & e) { \ @@ -63,6 +65,28 @@ bool fail(const log::message &msg); } \ while (0) +/** + * Asserts that the left expression does not equal the right expression, + * and that no exception is thrown. + */ +#define TESTNOTEQUALS(left, right) \ + do { \ + try { \ + auto &&test_result_left = (left); \ + if (test_result_left == (right)) { \ + TESTFAILMSG(__FILE__ << ":" << __LINE__ << ": Expected " \ + << test_result_left << " and " \ + << (right) << " to be NOT equal"); \ + } \ + } \ + catch (::openage::testing::TestError & e) { \ + throw; \ + } \ + catch (::openage::error::Error & e) { \ + TESTFAILMSG("unexpected exception: " << e); \ + } \ + } \ + while (0) /** * Asserts that the left expression equals the right expression, @@ -72,8 +96,9 @@ bool fail(const log::message &msg); do { \ try { \ auto &&test_result_left = (left); \ - if ((test_result_left < (right - epsilon)) or (test_result_left > (right + epsilon))) { \ - TESTFAILMSG("unexpected value: " << (test_result_left)); \ + auto &&test_result_right = (right); \ + if ((test_result_left < (test_result_right - epsilon)) or (test_result_left > (test_result_right + epsilon))) { \ + TESTFAILMSG(__FILE__ << ":" << __LINE__ << ": Expected " << (test_result_left) << " and " << (test_result_right) << " to be equal"); \ } \ } \ catch (::openage::testing::TestError & e) { \ @@ -99,7 +124,7 @@ bool fail(const log::message &msg); expr_has_thrown = true; \ } \ if (not expr_has_thrown) { \ - TESTFAILMSG("no exception"); \ + TESTFAILMSG(__FILE__ << ":" << __LINE__ << ": no exception"); \ } \ } \ while (0) @@ -114,7 +139,7 @@ bool fail(const log::message &msg); expression; \ } \ catch (::openage::error::Error & e) { \ - TESTFAILMSG("unexpected exception"); \ + TESTFAILMSG(__FILE__ << ":" << __LINE__ << ": unexpected exception"); \ } \ } \ while (0) diff --git a/libopenage/util/compiler.cpp b/libopenage/util/compiler.cpp index 2fd850920d..71100d3f92 100644 --- a/libopenage/util/compiler.cpp +++ b/libopenage/util/compiler.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2024 the openage authors. See copying.md for legal info. +// Copyright 2015-2025 the openage authors. See copying.md for legal info. #include "compiler.h" @@ -24,10 +24,10 @@ namespace util { std::string demangle(const char *symbol) { #ifdef _WIN32 - // TODO: demangle names for MSVC; Possibly using UnDecorateSymbolName - // https://msdn.microsoft.com/en-us/library/windows/desktop/ms681400(v=vs.85).aspx - // Could it be that MSVC's typeid(T).name() already returns a demangled name? It seems that .raw_name() returns the mangled name - return symbol; + // MSVC's typeid(T).name() already returns a demangled name + // unlike clang and gcc the MSVC demangled name is prefixed with "class " or "stuct " + // we remove the prefix to match the format of clang and gcc + return strchr(symbol, ' ') + 1; #else int status; char *buf = abi::__cxa_demangle(symbol, nullptr, nullptr, &status); diff --git a/libopenage/util/enum.h b/libopenage/util/enum.h index 394db95b93..c6a9b4666b 100644 --- a/libopenage/util/enum.h +++ b/libopenage/util/enum.h @@ -1,4 +1,4 @@ -// Copyright 2015-2024 the openage authors. See copying.md for legal info. +// Copyright 2015-2025 the openage authors. See copying.md for legal info. #pragma once @@ -44,7 +44,7 @@ namespace util { * NumericType numeric */ template -class OAAPI EnumValue { +class EnumValue { public: constexpr EnumValue(const char *value_name, NumericType numeric_value) : name(value_name), numeric(numeric_value) {} @@ -124,7 +124,7 @@ class OAAPI EnumValue { * bool operator >=(Enum[DerivedType] other) except + */ template -class OAAPI Enum { +class Enum { using this_type = Enum; public: diff --git a/libopenage/util/fixed_point.h b/libopenage/util/fixed_point.h index 4e97e77323..c751238cdf 100644 --- a/libopenage/util/fixed_point.h +++ b/libopenage/util/fixed_point.h @@ -1,8 +1,9 @@ -// Copyright 2015-2024 the openage authors. See copying.md for legal info. +// Copyright 2015-2025 the openage authors. See copying.md for legal info. #pragma once #include +#include #include #include #include @@ -85,14 +86,16 @@ constexpr static * If you change this class, remember to update the gdb pretty printers * in etc/gdb_pretty/printers.py. */ -template +template class FixedPoint { public: using raw_type = int_type; - using this_type = FixedPoint; + using this_type = FixedPoint; using unsigned_int_type = typename std::make_unsigned::type; + using unsigned_intermediate_type = typename std::make_unsigned::type; + using same_type_but_unsigned = FixedPoint; + fractional_bits, typename FixedPoint::unsigned_intermediate_type>; private: // Helper function to create the scaling factors that are used below. @@ -264,14 +267,14 @@ class FixedPoint { /** * Factory function to get a fixed-point number from a fixed-point number of different type. */ - template other_fractional_bits)>::type * = nullptr> - static constexpr FixedPoint from_fixedpoint(const FixedPoint &other) { + template other_fractional_bits)>::type * = nullptr> + static constexpr FixedPoint from_fixedpoint(const FixedPoint &other) { return FixedPoint::from_raw_value( safe_shift(static_cast(other.get_raw_value()))); } - template ::type * = nullptr> - static constexpr FixedPoint from_fixedpoint(const FixedPoint &other) { + template ::type * = nullptr> + static constexpr FixedPoint from_fixedpoint(const FixedPoint &other) { return FixedPoint::from_raw_value( static_cast(other.get_raw_value() / safe_shiftleft(1))); } @@ -380,14 +383,14 @@ class FixedPoint { return FixedPoint::this_type::from_raw_value(-this->raw_value); } - template - constexpr double hypot(const FixedPoint rhs) { + template + constexpr double hypot(const FixedPoint rhs) { return std::hypot(this->to_double(), rhs.to_double()); } - template - constexpr FixedPoint hypotfp(const FixedPoint rhs) { - return FixedPoint(this->hypot(rhs)); + template + constexpr FixedPoint hypotfp(const FixedPoint rhs) { + return FixedPoint(this->hypot(rhs)); } // Basic operators @@ -444,13 +447,69 @@ class FixedPoint { return is; } - constexpr double sqrt() { - return std::sqrt(this->to_double()); + /** + * Pure FixedPoint sqrt implementation using Heron's Algorithm. + * + * Note that this function is undefined for negative values. + * + * There's a small loss in precision depending on the value of fractional_bits and the position of + * the most significant bit: if the integer portion is very large, we won't have as much (absolute) + * precision. Ideally you would want the intermediate_type to be twice the size of raw_type to avoid + * any losses. + */ + constexpr FixedPoint sqrt() { + // Zero can cause issues later, so deal with now. + if (this->raw_value == 0) { + return zero(); + } + + // Check for negative values + if constexpr (std::is_signed()) { + ENSURE(this->raw_value > 0, "FixedPoint::sqrt() is undefined for negative values."); + } + + // A greater shift = more precision, but can overflow the intermediate type if too large. + size_t max_shift = std::countl_zero(static_cast(this->raw_value)) - 1; + size_t shift = max_shift > fractional_bits ? fractional_bits : max_shift; + + // shift + fractional bits must be an even number + if ((shift + fractional_bits) % 2) { + shift -= 1; + } + + // We can't use the safe shift since the shift value is unknown at compile time. + intermediate_type n = static_cast(this->raw_value) << shift; + intermediate_type guess = static_cast(1) << fractional_bits; + + for (size_t i = 0; i < fractional_bits; i++) { + intermediate_type prev = guess; + guess = (guess + n / guess) / 2; + if (guess == prev) { + break; + } + } + + // The sqrt operation halves the number of bits, so we'll we'll have to calculate a shift back + size_t unshift = fractional_bits - (shift + fractional_bits) / 2; + + return from_raw_value(guess << unshift); } constexpr double atan2(const FixedPoint &n) { return std::atan2(this->to_double(), n.to_double()); } + + constexpr double sin() { + return std::sin(this->to_double()); + } + + constexpr double cos() { + return std::cos(this->to_double()); + } + + constexpr double tan() { + return std::tan(this->to_double()); + } }; @@ -459,42 +518,42 @@ class FixedPoint { /** * FixedPoint + FixedPoint */ -template -constexpr FixedPoint operator+(const FixedPoint &lhs, const FixedPoint &rhs) { - return FixedPoint::from_raw_value(lhs.get_raw_value() + rhs.get_raw_value()); +template +constexpr FixedPoint operator+(const FixedPoint &lhs, const FixedPoint &rhs) { + return FixedPoint::from_raw_value(lhs.get_raw_value() + rhs.get_raw_value()); } /** * FixedPoint + double */ -template -constexpr FixedPoint operator+(const FixedPoint &lhs, const double &rhs) { - return FixedPoint{lhs} + FixedPoint::from_double(rhs); +template +constexpr FixedPoint operator+(const FixedPoint &lhs, const double &rhs) { + return FixedPoint{lhs} + FixedPoint::from_double(rhs); } /** * FixedPoint - FixedPoint */ -template -constexpr FixedPoint operator-(const FixedPoint &lhs, const FixedPoint &rhs) { - return FixedPoint::from_raw_value(lhs.get_raw_value() - rhs.get_raw_value()); +template +constexpr FixedPoint operator-(const FixedPoint &lhs, const FixedPoint &rhs) { + return FixedPoint::from_raw_value(lhs.get_raw_value() - rhs.get_raw_value()); } /** * FixedPoint - double */ -template -constexpr FixedPoint operator-(const FixedPoint &lhs, const double &rhs) { - return FixedPoint{lhs} - FixedPoint::from_double(rhs); +template +constexpr FixedPoint operator-(const FixedPoint &lhs, const double &rhs) { + return FixedPoint{lhs} - FixedPoint::from_double(rhs); } /** * FixedPoint * N */ -template -typename std::enable_if::value, FixedPoint>::type constexpr operator*(const FixedPoint lhs, const N &rhs) { - return FixedPoint::from_raw_value(lhs.get_raw_value() * rhs); +template +typename std::enable_if::value, FixedPoint>::type constexpr operator*(const FixedPoint lhs, const N &rhs) { + return FixedPoint::from_raw_value(lhs.get_raw_value() * rhs); } /* @@ -511,40 +570,41 @@ typename std::enable_if::value, FixedPoint>::type co * => a * a will overflow because: * a.rawvalue == 2^(16+16) == 2^32 * -> a.rawvalue * a.rawvalue == 2^64 => pwnt + * + * Use a larger intermediate type to prevent overflow */ -// template -// constexpr FixedPoint operator*(const FixedPoint lhs, const FixedPoint rhs) { -// I ret = 0; -// if (not __builtin_mul_overflow(lhs.get_raw_value(), rhs.get_raw_value(), &ret)) { -// throw std::overflow_error("FixedPoint multiplication overflow"); -// } +template +constexpr FixedPoint operator*(const FixedPoint lhs, const FixedPoint rhs) { + Inter ret = static_cast(lhs.get_raw_value()) * static_cast(rhs.get_raw_value()); + ret >>= F; -// return FixedPoint::from_raw_value(ret); -// } + return FixedPoint::from_raw_value(static_cast(ret)); +} /** * FixedPoint / FixedPoint */ -template -constexpr FixedPoint operator/(const FixedPoint lhs, const FixedPoint rhs) { - return FixedPoint::from_raw_value(div(lhs.get_raw_value(), rhs.get_raw_value()) << F); +template +constexpr FixedPoint operator/(const FixedPoint lhs, const FixedPoint rhs) { + Inter ret = div((static_cast(lhs.get_raw_value()) << F), static_cast(rhs.get_raw_value())); + return FixedPoint::from_raw_value(static_cast(ret)); } /** * FixedPoint / N */ -template -constexpr FixedPoint operator/(const FixedPoint lhs, const N &rhs) { - return FixedPoint::from_raw_value(div(lhs.get_raw_value(), static_cast(rhs))); +template +constexpr FixedPoint operator/(const FixedPoint lhs, const N &rhs) { + return FixedPoint::from_raw_value(div(lhs.get_raw_value(), static_cast(rhs))); } /** * FixedPoint % FixedPoint (modulo) */ -template -constexpr FixedPoint operator%(const FixedPoint lhs, const FixedPoint rhs) { +template +constexpr FixedPoint operator%(const FixedPoint lhs, const FixedPoint rhs) { auto div = (lhs / rhs); auto n = div.to_int(); return lhs - (rhs * n); @@ -557,56 +617,71 @@ constexpr FixedPoint operator%(const FixedPoint lhs, const FixedPoin // std function overloads namespace std { -template -constexpr double sqrt(openage::util::FixedPoint n) { - return n.sqrt(); +template +constexpr double sqrt(openage::util::FixedPoint n) { + return static_cast(n.sqrt()); } -template -constexpr double atan2(openage::util::FixedPoint x, openage::util::FixedPoint y) { +template +constexpr double atan2(openage::util::FixedPoint x, openage::util::FixedPoint y) { return x.atan2(y); } -template -constexpr openage::util::FixedPoint min(openage::util::FixedPoint x, openage::util::FixedPoint y) { - return openage::util::FixedPoint::from_raw_value( +template +constexpr double sin(openage::util::FixedPoint n) { + return n.sin(); +} + +template +constexpr double cos(openage::util::FixedPoint n) { + return n.cos(); +} + +template +constexpr double tan(openage::util::FixedPoint n) { + return n.tan(); +} + +template +constexpr openage::util::FixedPoint min(openage::util::FixedPoint x, openage::util::FixedPoint y) { + return openage::util::FixedPoint::from_raw_value( std::min(x.get_raw_value(), y.get_raw_value())); } -template -constexpr openage::util::FixedPoint max(openage::util::FixedPoint x, openage::util::FixedPoint y) { - return openage::util::FixedPoint::from_raw_value( +template +constexpr openage::util::FixedPoint max(openage::util::FixedPoint x, openage::util::FixedPoint y) { + return openage::util::FixedPoint::from_raw_value( std::max(x.get_raw_value(), y.get_raw_value())); } -template -constexpr openage::util::FixedPoint abs(openage::util::FixedPoint n) { - return openage::util::FixedPoint::from_raw_value( +template +constexpr openage::util::FixedPoint abs(openage::util::FixedPoint n) { + return openage::util::FixedPoint::from_raw_value( std::abs(n.get_raw_value())); } -template -constexpr double hypot(openage::util::FixedPoint x, openage::util::FixedPoint y) { +template +constexpr double hypot(openage::util::FixedPoint x, openage::util::FixedPoint y) { return x.hypot(y); } -template -struct hash> { - constexpr size_t operator()(const openage::util::FixedPoint &n) const { +template +struct hash> { + constexpr size_t operator()(const openage::util::FixedPoint &n) const { return std::hash{}(n.raw_value); } }; -template -struct numeric_limits> { - constexpr static openage::util::FixedPoint min() { - return openage::util::FixedPoint::min_value(); +template +struct numeric_limits> { + constexpr static openage::util::FixedPoint min() { + return openage::util::FixedPoint::min_value(); } - constexpr static openage::util::FixedPoint max() { - return openage::util::FixedPoint::max_value(); + constexpr static openage::util::FixedPoint max() { + return openage::util::FixedPoint::max_value(); } }; diff --git a/libopenage/util/fixed_point_test.cpp b/libopenage/util/fixed_point_test.cpp index 7a7fb2bc0c..b3690b8d15 100644 --- a/libopenage/util/fixed_point_test.cpp +++ b/libopenage/util/fixed_point_test.cpp @@ -1,4 +1,4 @@ -// Copyright 2016-2023 the openage authors. See copying.md for legal info. +// Copyright 2016-2025 the openage authors. See copying.md for legal info. #include "fixed_point.h" @@ -122,6 +122,87 @@ void fixed_point() { TESTEQUALS_FLOAT(TestTypeShort::e().to_double(), math::E, 1e-3); TESTEQUALS_FLOAT(TestTypeShort::pi().to_double(), math::PI, 1e-3); + { + using S = FixedPoint; + using T = FixedPoint; + + auto a = S::from_int(16U); + TESTNOTEQUALS((a*a).to_int(), 256U); + + auto b = T::from_int(16U); + TESTEQUALS((b*b).to_int(), 256U); + + auto c = T::from_int(17U); + TESTEQUALS((c*c).to_int(), 289U); + } + { + using S = FixedPoint; + auto a = S::from_int(256); + auto b = S::from_int(8); + TESTNOTEQUALS((a/b).to_int(), 32); + + + using T = FixedPoint; + auto c = T::from_int(256); + auto d = T::from_int(8); + TESTEQUALS((c/d).to_int(), 32); + } + { + using T = FixedPoint; + auto a = T::from_double(4.75); + auto b = T::from_double(3.5); + auto c = -a; + TESTEQUALS_FLOAT((a/b).to_double(), 4.75/3.5, 0.1); + TESTEQUALS_FLOAT((c/b).to_double(), -4.75/3.5, 0.1); + } + + // Pure FixedPoint sqrt tests + { + using T = FixedPoint; + TESTEQUALS_FLOAT(T(41231.131).sqrt(), 203.0545025356, 1e-7); + TESTEQUALS_FLOAT(T(547965.116).sqrt(), 740.2466588915, 1e-7); + + TESTEQUALS_FLOAT(T(2).sqrt(), T::sqrt_2(), 1e-9); + TESTEQUALS_FLOAT(2 / std::sqrt(T::pi()), T::inv2_sqrt_pi(), 1e-9); + + // Powers of two (anything over 2^15 will overflow (2^16)^2 = 2^32 >). + for (size_t i = 0; i < 15; i++) { + int64_t value = 1 << i; + TESTEQUALS_FLOAT(T(value * value).sqrt(), value, 1e-7); + } + + for (size_t i = 0; i < 100; i++) { + double value = 14.25 * i; + TESTEQUALS_FLOAT(T(value * value).sqrt(), value, 1e-7); + } + + // This one can go up to 2^63, but that would take years. + for (uint32_t i = 0; i < 65536; i++) { + T value = T::from_raw_value(i * i); + TESTEQUALS_FLOAT(value.sqrt(), std::sqrt(value.to_double()), 1e-7); + } + + // We lose some precision when raw_type == intermediate_type + for (uint64_t i = 1; i < std::numeric_limits::max(); i = (i * 2) ^ i) { + T value = T::from_raw_value(i * i); + if (value < 0) { + value = -value; + } + TESTEQUALS_FLOAT(value.sqrt(), std::sqrt(value.to_double()), 1e-4); + } + + using FP16_16 = FixedPoint; + for (uint32_t i = 1; i < 65536; i++) { + FP16_16 value = FP16_16::from_raw_value(i); + TESTEQUALS_FLOAT(value.sqrt(), std::sqrt(value.to_double()), 1e-4); + } + + + // Test with negative number + TESTTHROWS((FixedPoint::from_float(-3.25).sqrt())); + TESTNOEXCEPT((FixedPoint::from_float(3.25).sqrt())); + TESTNOEXCEPT((FixedPoint::from_float(-3.25).sqrt())); + } } }}} // openage::util::tests diff --git a/openage/__main__.py b/openage/__main__.py index 970643bb51..60c1c1bb4f 100644 --- a/openage/__main__.py +++ b/openage/__main__.py @@ -127,7 +127,7 @@ def main(argv=None): "codegen", parents=[global_cli])) - args = cli.parse_args(argv) + args, remaining_args = cli.parse_known_args(argv) dll_manager = None if sys.platform == 'win32': @@ -139,7 +139,7 @@ def main(argv=None): if not args.subcommand: # the user didn't specify a subcommand. default to 'main'. - args = main_cli.parse_args(argv) + args = main_cli.parse_args(remaining_args) args.dll_manager = dll_manager diff --git a/openage/cabextract/CMakeLists.txt b/openage/cabextract/CMakeLists.txt index 9a545259af..87f4fd36eb 100644 --- a/openage/cabextract/CMakeLists.txt +++ b/openage/cabextract/CMakeLists.txt @@ -1,6 +1,6 @@ add_cython_modules( lzxd.pyx - STANDALONE cabchecksum.pyx + cabchecksum.pyx ) add_py_modules( diff --git a/openage/convert/processor/export/texture_merge.pyx b/openage/convert/processor/export/texture_merge.pyx index f726d0af02..51ce4830ed 100644 --- a/openage/convert/processor/export/texture_merge.pyx +++ b/openage/convert/processor/export/texture_merge.pyx @@ -1,4 +1,4 @@ -# Copyright 2014-2023 the openage authors. See copying.md for legal info. +# Copyright 2014-2024 the openage authors. See copying.md for legal info. # # cython: infer_types=True # pylint: disable=too-many-locals @@ -9,15 +9,16 @@ a terrain texture. import numpy from enum import Enum +cimport cython +cimport numpy + from ....log import spam +from ...service.export.png.binpack cimport block from ...entity_object.export.texture import TextureImage -from ...service.export.png.binpack cimport Packer, DeterministicPacker, RowPacker, ColumnPacker, BinaryTreePacker, BestPacker +from ...service.export.png.binpack cimport DeterministicPacker, RowPacker, ColumnPacker, BinaryTreePacker, BestPacker from ...value_object.read.media.hardcoded.texture import (MAX_TEXTURE_DIMENSION, MARGIN, TERRAIN_ASPECT_RATIO) -cimport cython -cimport numpy - class PackerType(Enum): """ Packer types @@ -57,6 +58,7 @@ cdef void cmerge_frames(texture, packer_type=PackerType.BINPACK, cache=None) exc :type cache: list """ cdef list frames = texture.frames + cdef list blocks = [block(idx, frame.width, frame.height) for idx, frame in enumerate(frames)] if len(frames) == 0: raise ValueError("cannot create texture with empty input frame list") @@ -64,7 +66,7 @@ cdef void cmerge_frames(texture, packer_type=PackerType.BINPACK, cache=None) exc cdef BestPacker packer if cache: - packer = BestPacker([DeterministicPacker(margin=MARGIN,hints=cache)]) + packer = BestPacker([DeterministicPacker(margin=MARGIN, hints=cache)]) else: if packer_type == PackerType.ROW: @@ -81,7 +83,7 @@ cdef void cmerge_frames(texture, packer_type=PackerType.BINPACK, cache=None) exc RowPacker(margin=MARGIN), ColumnPacker(margin=MARGIN)]) - packer.pack(frames) + packer.pack(blocks) cdef int width = packer.width() cdef int height = packer.height() @@ -106,11 +108,11 @@ cdef void cmerge_frames(texture, packer_type=PackerType.BINPACK, cache=None) exc cdef int sub_h cdef list drawn_frames_meta = [] - for sub_frame in frames: + for index, sub_frame in enumerate(frames): sub_w = sub_frame.width sub_h = sub_frame.height - pos_x, pos_y = packer.pos(sub_frame) + pos_x, pos_y = packer.pos(index) spam("drawing frame %03d on atlas at %d x %d...", len(drawn_frames_meta), pos_x, pos_y) @@ -143,4 +145,4 @@ cdef void cmerge_frames(texture, packer_type=PackerType.BINPACK, cache=None) exc if isinstance(packer, BestPacker): # Only generate these values if no custom packer was used # TODO: It might make sense to do it anyway for debugging purposes - texture.best_packer_hints = packer.get_mapping_hints(frames) + texture.best_packer_hints = packer.get_mapping_hints(blocks) diff --git a/openage/convert/service/export/png/binpack.pxd b/openage/convert/service/export/png/binpack.pxd index e13725a367..5560441143 100644 --- a/openage/convert/service/export/png/binpack.pxd +++ b/openage/convert/service/export/png/binpack.pxd @@ -1,15 +1,19 @@ -# Copyright 2021-2021 the openage authors. See copying.md for legal info. +# Copyright 2021-2024 the openage authors. See copying.md for legal info. -from libcpp.memory cimport shared_ptr +from libcpp.unordered_map cimport unordered_map + +ctypedef (unsigned int, unsigned int, (unsigned int, unsigned int)) mapping_value cdef class Packer: cdef unsigned int margin - cdef dict mapping + cdef unordered_map[int, mapping_value] mapping cdef void pack(self, list blocks) - cdef (unsigned int, unsigned int) pos(self, block) + cdef (unsigned int, unsigned int) pos(self, int index) cdef unsigned int width(self) cdef unsigned int height(self) + cdef list get_mapping_hints(self, list blocks) + cdef (unsigned int) get_packer_settings(self) cdef class DeterministicPacker(Packer): pass @@ -20,9 +24,11 @@ cdef class BestPacker: cdef void pack(self, list blocks) cdef Packer best_packer(self) - cdef (unsigned int, unsigned int) pos(self, block) + cdef (unsigned int, unsigned int) pos(self, int index) cdef unsigned int width(self) cdef unsigned int height(self) + cdef list get_mapping_hints(self, list blocks) + cdef (unsigned int) get_packer_settings(self) cdef class RowPacker(Packer): pass @@ -34,12 +40,13 @@ cdef class BinaryTreePacker(Packer): cdef unsigned int aspect_ratio cdef packer_node *root - cdef void fit(self, block) - cdef packer_node *find_node(self, packer_node *root, unsigned int width, unsigned int height) - cdef packer_node *split_node(self, packer_node *node, unsigned int width, unsigned int height) - cdef packer_node *grow_node(self, unsigned int width, unsigned int height) - cdef packer_node *grow_right(self, unsigned int width, unsigned int height) - cdef packer_node *grow_down(self, unsigned int width, unsigned int height) + cdef void fit(self, block block) + cdef (unsigned int) get_packer_settings(self) + cdef packer_node *find_node(self, packer_node *root, unsigned int width, unsigned int height) noexcept + cdef packer_node *split_node(self, packer_node *node, unsigned int width, unsigned int height) noexcept + cdef packer_node *grow_node(self, unsigned int width, unsigned int height) noexcept + cdef packer_node *grow_right(self, unsigned int width, unsigned int height) noexcept + cdef packer_node *grow_down(self, unsigned int width, unsigned int height) noexcept cdef struct packer_node: unsigned int x @@ -49,3 +56,8 @@ cdef struct packer_node: bint used packer_node *down packer_node *right + +cdef struct block: + unsigned int index + unsigned int width + unsigned int height diff --git a/openage/convert/service/export/png/binpack.pyx b/openage/convert/service/export/png/binpack.pyx index 30e39cd95f..291be2a526 100644 --- a/openage/convert/service/export/png/binpack.pyx +++ b/openage/convert/service/export/png/binpack.pyx @@ -1,4 +1,4 @@ -# Copyright 2016-2023 the openage authors. See copying.md for legal info. +# Copyright 2016-2024 the openage authors. See copying.md for legal info. # # cython: infer_types=True,profile=False # TODO pylint: disable=C,R @@ -7,14 +7,10 @@ Routines for 2D binpacking """ -from enum import Enum - cimport cython -from libc.stdint cimport uintptr_t -from libc.stdlib cimport malloc - from libc.math cimport sqrt - +from libc.stdlib cimport malloc +from libcpp.unordered_map cimport unordered_map @cython.boundscheck(False) @cython.wraparound(False) @@ -24,6 +20,7 @@ cdef inline (unsigned int, unsigned int) factor(unsigned int n): Return two (preferable close) factors of n. """ cdef unsigned int a = sqrt(n) + cdef int num for num in range(a, 0, -1): if n % num == 0: return num, n // num @@ -33,9 +30,8 @@ cdef class Packer: """ Packs blocks. """ - def __init__(self, margin): + def __init__(self, int margin): self.margin = margin - self.mapping = {} cdef void pack(self, list blocks): """ @@ -45,31 +41,33 @@ cdef class Packer: """ raise NotImplementedError - cdef (unsigned int, unsigned int) pos(self, block): - return self.mapping[block] + cdef (unsigned int, unsigned int) pos(self, int index): + node = self.mapping[index] + return node[0], node[1] cdef unsigned int width(self): """ Gets the total width of the packing. """ - return max(self.pos(block)[0] + block.width for block in self.mapping) + return max(self.pos(idx)[0] + block[2][0] for idx, block in self.mapping) cdef unsigned int height(self): """ Gets the total height of the packing. """ - return max(self.pos(block)[1] + block.height for block in self.mapping) + return max(self.pos(idx)[1] + block[2][1] for idx, block in self.mapping) - def get_packer_settings(self): + cdef (unsigned int) get_packer_settings(self): """ Get the init parameters set for the packer. """ return (self.margin,) - def get_mapping_hints(self, blocks): - hints = [] + cdef list get_mapping_hints(self, list blocks): + cdef list hints = [] + cdef block block for block in blocks: - hints.append(self.pos(block)) + hints.append(self.pos(block.index)) return hints @@ -79,13 +77,13 @@ cdef class DeterministicPacker(Packer): Packs blocks based on predetermined settings. """ - def __init__(self, margin, hints): + def __init__(self, int margin, list hints): super().__init__(margin) self.hints = hints cdef void pack(self, list blocks): for idx, block in enumerate(blocks): - self.mapping[block] = self.hints[idx] + self.mapping[block.index] = self.hints[idx] cdef class BestPacker: @@ -97,18 +95,17 @@ cdef class BestPacker: self.current_best = None cdef void pack(self, list blocks): - cdef Packer p + cdef Packer packer for packer in self.packers: - p = packer - p.pack(blocks) + packer.pack(blocks) self.current_best = self.best_packer() cdef Packer best_packer(self): return min(self.packers, key=lambda Packer p: p.width() * p.height()) - cdef (unsigned int, unsigned int) pos(self, block): - return self.current_best.pos(block) + cdef (unsigned int, unsigned int) pos(self, int index): + return self.current_best.pos(index) cdef unsigned int width(self): return self.current_best.width() @@ -116,10 +113,10 @@ cdef class BestPacker: cdef unsigned int height(self): return self.current_best.height() - def get_packer_settings(self): + cdef (unsigned int) get_packer_settings(self): return self.current_best.get_packer_settings() - def get_mapping_hints(self, blocks): + cdef list get_mapping_hints(self, list blocks): return self.current_best.get_mapping_hints(blocks) @@ -129,8 +126,6 @@ cdef class RowPacker(Packer): """ cdef void pack(self, list blocks): - self.mapping = {} - cdef unsigned int num_rows cdef list rows @@ -148,7 +143,7 @@ cdef class RowPacker(Packer): x = 0 for block in row: - self.mapping[block] = (x, y) + self.mapping[block.index] = (x, y, (block.width, block.height)) x += block.width + self.margin y += max(block.height for block in row) + self.margin @@ -160,8 +155,6 @@ cdef class ColumnPacker(Packer): """ cdef void pack(self, list blocks): - self.mapping = {} - num_columns, _ = factor(len(blocks)) columns = [[] for _ in range(num_columns)] @@ -176,13 +169,13 @@ cdef class ColumnPacker(Packer): y = 0 for block in column: - self.mapping[block] = (x, y) + self.mapping[block.index] = (x, y, (block.width, block.height)) y += block.height + self.margin x += max(block.width for block in column) + self.margin -cdef inline (unsigned int, unsigned int, unsigned int, unsigned int) maxside_heuristic(block): +cdef inline (unsigned int, unsigned int, unsigned int, unsigned int) maxside_heuristic(block block): """ Heuristic: Order blocks by maximum side. """ @@ -202,27 +195,26 @@ cdef class BinaryTreePacker(Packer): textures. """ - def __init__(self, margin, aspect_ratio=1): + def __init__(self, int margin, int aspect_ratio=1): # ASF: what about heuristic=max_heuristic? super().__init__(margin) self.aspect_ratio = aspect_ratio self.root = NULL cdef void pack(self, list blocks): - self.mapping = {} self.root = NULL for block in sorted(blocks, key=maxside_heuristic, reverse=True): self.fit(block) - cdef (unsigned int, unsigned int) pos(self, block): - node = self.mapping[block] + cdef (unsigned int, unsigned int) pos(self, int index): + node = self.mapping[index] return node[0], node[1] - def get_packer_settings(self): + cdef (unsigned int) get_packer_settings(self): return (self.margin,) - cdef void fit(self, block): + cdef void fit(self, block block): cdef packer_node *node if self.root == NULL: self.root = malloc(sizeof(packer_node)) @@ -246,9 +238,9 @@ cdef class BinaryTreePacker(Packer): node = self.grow_node(block.width + self.margin, block.height + self.margin) - self.mapping[block] = (node.x, node.y) + self.mapping[block.index] = (node.x, node.y, (block.width, block.height)) - cdef packer_node *find_node(self, packer_node *root, unsigned int width, unsigned int height): + cdef packer_node *find_node(self, packer_node *root, unsigned int width, unsigned int height) noexcept: if root.used: return (self.find_node(root.right, width, height) or self.find_node(root.down, width, height)) @@ -256,7 +248,7 @@ cdef class BinaryTreePacker(Packer): elif width <= root.width and height <= root.height: return root - cdef packer_node *split_node(self, packer_node *node, unsigned int width, unsigned int height): + cdef packer_node *split_node(self, packer_node *node, unsigned int width, unsigned int height) noexcept: node.used = True node.down = malloc(sizeof(packer_node)) @@ -280,7 +272,7 @@ cdef class BinaryTreePacker(Packer): return node @cython.cdivision(True) - cdef packer_node *grow_node(self, unsigned int width, unsigned int height): + cdef packer_node *grow_node(self, unsigned int width, unsigned int height) noexcept: cdef bint can_grow_down = width <= self.root.width cdef bint can_grow_right = height <= self.root.height # assert can_grow_down or can_grow_right, "Bad block ordering heuristic" @@ -302,7 +294,7 @@ cdef class BinaryTreePacker(Packer): else: return self.grow_down(width, height) - cdef packer_node *grow_right(self, unsigned int width, unsigned int height): + cdef packer_node *grow_right(self, unsigned int width, unsigned int height) noexcept: old_root = self.root self.root = malloc(sizeof(packer_node)) @@ -327,7 +319,7 @@ cdef class BinaryTreePacker(Packer): if node != NULL: return self.split_node(node, width, height) - cdef packer_node *grow_down(self, unsigned int width, unsigned int height): + cdef packer_node *grow_down(self, unsigned int width, unsigned int height) noexcept: old_root = self.root self.root = malloc(sizeof(packer_node)) @@ -361,3 +353,8 @@ cdef struct packer_node: bint used packer_node *down packer_node *right + +cdef struct block: + unsigned int index + unsigned int width + unsigned int height diff --git a/openage/convert/value_object/read/media/smp.pyx b/openage/convert/value_object/read/media/smp.pyx index 49a2122175..03de7dd200 100644 --- a/openage/convert/value_object/read/media/smp.pyx +++ b/openage/convert/value_object/read/media/smp.pyx @@ -1,4 +1,4 @@ -# Copyright 2013-2023 the openage authors. See copying.md for legal info. +# Copyright 2013-2025 the openage authors. See copying.md for legal info. # # cython: infer_types=True @@ -22,6 +22,7 @@ from libcpp.vector cimport vector endianness = "< " +# Boundary of a row in a SMP layer. cdef struct boundary_def: Py_ssize_t left Py_ssize_t right @@ -62,10 +63,38 @@ cdef public dict LAYER_TYPES = { } +cdef class SMPMainLayer: + """ + Main graphic layer of an SMP. Stores the color information. + """ + pass + + +cdef class SMPShadowLayer: + """ + Shadow layer of an SMP. + """ + pass + + +cdef class SMPOutlineLayer: + """ + Outline layer of an SMP. + """ + pass + + +# fused type for the layer variants +ctypedef fused SMPLayerVariant: + SMPMainLayer + SMPShadowLayer + SMPOutlineLayer + + class SMP: """ Class for reading/converting the SMP image format (successor of SLP). - This format is used to store all graphics within AoE2: Definitive Edition. + This format is used to store all graphics within AoE2: Definitive Edition (Beta). """ # struct smp_header { @@ -104,15 +133,23 @@ class SMP: # }; smp_layer_header = Struct(endianness + "i i i i I I I I") - def __init__(self, data): + def __init__(self, data: bytes) -> None: + """ + Read the SMP file and store the frames in the object. + + :param data: SMP file data. + """ smp_header = SMP.smp_header.unpack_from(data) signature, version, frame_count, facet_count, frames_per_facet,\ checksum, file_size, source_format, comment = smp_header dbg("SMP") + spam(" signature: %s", signature.decode('ascii')) + spam(" version: %s", version) dbg(" frame count: %s", frame_count) dbg(" facet count: %s", facet_count) dbg(" facets per animation: %s", frames_per_facet) + spam(" checksum: %s", checksum) dbg(" file size: %s B", file_size) dbg(" source format: %s", source_format) dbg(" comment: %s", comment.decode('ascii')) @@ -155,24 +192,25 @@ class SMP: if layer_header.layer_type == 0x02: # layer that store the main graphic - self.main_frames.append(SMPMainLayer(layer_header, data)) + self.main_frames.append(SMPLayer(SMPMainLayer(), layer_header, data)) elif layer_header.layer_type == 0x04: # layer that stores a shadow - self.shadow_frames.append(SMPShadowLayer(layer_header, data)) + self.shadow_frames.append(SMPLayer(SMPShadowLayer(), layer_header, data)) elif layer_header.layer_type == 0x08 or \ layer_header.layer_type == 0x10: # layer that stores an outline - self.outline_frames.append(SMPOutlineLayer(layer_header, data)) + self.outline_frames.append(SMPLayer(SMPOutlineLayer(), layer_header, data)) else: raise Exception( f"unknown layer type: {layer_header.layer_type:#x} at offset {layer_header_offset:#x}" ) + spam(layer_header) - def get_frames(self, layer: int = 0): + def get_frames(self, layer: int = 0) -> list[SMPLayer]: """ Get the frames in the SMP. @@ -180,7 +218,6 @@ class SMP: - 0 = main graphics - 1 = shadow graphics - 2 = outline - :type layer: int """ cdef list frames @@ -203,7 +240,7 @@ class SMP: return frames - def __str__(self): + def __str__(self) -> str: ret = list() ret.extend([repr(self), "\n", SMPLayerHeader.repr_header(), "\n"]) @@ -211,15 +248,39 @@ class SMP: ret.extend([repr(frame), "\n"]) return "".join(ret) - def __repr__(self): + def __repr__(self) -> str: return f"SMP image<{len(self.main_frames):d} frames>" class SMPLayerHeader: - def __init__(self, width, height, hotspot_x, - hotspot_y, layer_type, outline_table_offset, - qdl_table_offset, flags, - frame_offset): + """ + Header of a layer in the SMP file. + """ + def __init__( + self, + width: int, + height: int, + hotspot_x: int, + hotspot_y: int, + layer_type: int, + outline_table_offset: int, + qdl_table_offset: int, + flags: int, + frame_offset: int + ) -> None: + """ + Create a SMP layer header. + + :param width: Width of the layer sprites. + :param height: Height of the layer sprites. + :param hotspot_x: X coordinate of the anchor point. + :param hotspot_y: Y coordinate of the anchor point. + :param layer_type: Type of the layer. + :param outline_table_offset: Offset of the outline table. + :param qdl_table_offset: Offset of the pixel command table. + :param flags: Flags of the layer. + :param frame_offset: Offset of the frame. + """ self.size = (width, height) self.hotspot = (hotspot_x, hotspot_y) @@ -234,16 +295,19 @@ class SMPLayerHeader: # the absolute offset of the frame self.frame_offset = frame_offset + # flags + self.flags = flags + self.palette_number = -1 @staticmethod - def repr_header(): + def repr_header() -> str: return ("width x height | hotspot x/y | " "layer type | " "offset (outline table|qdl table)" ) - def __repr__(self): + def __repr__(self) -> str: ret = ( "% 5d x% 7d | " % self.size, "% 4d /% 5d | " % self.hotspot, @@ -253,9 +317,10 @@ class SMPLayerHeader: ) return "".join(ret) + cdef class SMPLayer: """ - one layer inside the SMP. you can imagine it as a frame of a video. + Layer inside the SMP. you can imagine it as a frame of an animation. """ # struct smp_frame_row_edge { @@ -282,7 +347,29 @@ cdef class SMPLayer: # pixel matrix representing the final image cdef vector[vector[pixel]] pcolor - def __init__(self, layer_header, data): + def __init__( + self, + variant, # this argument must not be typed because cython can't handle it + layer_header: SMPLayerHeader, + data: bytes + ) -> None: + """ + Create a SMP layer. + + :param variant: Type of the layer. + :param layer_header: Header information of the layer. + :param data: Layer data. + """ + self.init(variant, layer_header, data) + + def init(self, SMPLayerVariant variant, layer_header: SMPLayerHeader, data: bytes) -> None: + """ + Read the pixel information of the SMP layer. + + :param variant: Type of the layer. + :param layer_header: Header information of the layer. + :param data: Layer data. + """ self.info = layer_header if not (isinstance(data, bytes) or isinstance(data, bytearray)): @@ -326,13 +413,18 @@ cdef class SMPLayer: self.cmd_offsets.push_back(cmd_offset) for i in range(row_count): - self.pcolor.push_back(self.create_color_row(data_raw, i)) + self.pcolor.push_back(self.create_color_row(variant, data_raw, i)) cdef vector[pixel] create_color_row(self, + SMPLayerVariant variant, const uint8_t[::1] &data_raw, Py_ssize_t rowid): """ - extract colors (pixels) for the given rowid. + Extract colors (pixels) for a pixel row in the layer. + + :param variant: Type of the layer. + :param data_raw: Raw data of the layer. + :param rowid: Index of the current row in the layer. """ cdef vector[pixel] row_data @@ -357,7 +449,8 @@ cdef class SMPLayer: row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) # process the drawing commands for this row. - self.process_drawing_cmds(data_raw, + self.process_drawing_cmds(variant, + data_raw, row_data, rowid, first_cmd_offset, pixel_count - bounds.right) @@ -383,58 +476,24 @@ cdef class SMPLayer: return row_data - @cython.boundscheck(False) - cdef void process_drawing_cmds(self, - const uint8_t[::1] &data_raw, - vector[pixel] &row_data, - Py_ssize_t rowid, - Py_ssize_t first_cmd_offset, - size_t expected_size): - pass - - def get_picture_data(self, palette): - """ - Convert the palette index matrix to a colored image. - """ - return determine_rgba_matrix(self.pcolor, palette) - - def get_hotspot(self): - """ - Return the layer's hotspot (the "center" of the image) - """ - return self.info.hotspot - - def get_palette_number(self): - """ - Return the layer's palette number. - - :return: Palette number of the layer. - :rtype: int - """ - return self.pcolor[0][0].palette & 0b00111111 - - def __repr__(self): - return repr(self.info) - - -cdef class SMPMainLayer(SMPLayer): - """ - SMPLayer for the main graphics sprite. - """ - - def __init__(self, layer_header, data): - super().__init__(layer_header, data) @cython.boundscheck(False) cdef void process_drawing_cmds(self, + SMPLayerVariant variant, const uint8_t[::1] &data_raw, vector[pixel] &row_data, Py_ssize_t rowid, Py_ssize_t first_cmd_offset, size_t expected_size): """ - extract colors (pixels) for the drawing commands - found for this row in the SMP frame. + Extract colors (pixels) from the drawing commands for a row in the layer. + + :param variant: Type of the layer. + :param data_raw: Raw data of the layer. + :param row_data: Stores the extracted pixels. May be prefilled with transparent pixels. + :param rowid: Index of the current row in the layer. + :param first_cmd_offset: Offset of the first drawing command in the data. + :param expected_size: Expected number of pixels in the row. """ # position in the data blob, we start at the first command of this row @@ -471,6 +530,15 @@ cdef class SMPMainLayer(SMPLayer): # eol (end of line) command, this row is finished now. eor = True + if SMPLayerVariant is SMPShadowLayer: + if row_data.size() < expected_size: + # copy the last drawn pixel + # (still stored in nextbyte) + # + # TODO: confirm that this is the + # right way to do it + row_data.push_back(pixel(color_shadow, nextbyte, 0, 0, 0)) + continue elif lower_crumb == 0b00000000: @@ -492,42 +560,61 @@ cdef class SMPMainLayer(SMPLayer): pixel_count = (cmd >> 2) + 1 for _ in range(pixel_count): - for _ in range(4): + if SMPLayerVariant is SMPShadowLayer: dpos += 1 - pixel_data.push_back(data_raw[dpos]) - - row_data.push_back(pixel(color_standard, - pixel_data[0], - pixel_data[1], - pixel_data[2], - pixel_data[3] & 0x1F)) # remove "usage" bit here - - pixel_data.clear() + nextbyte = data_raw[dpos] + row_data.push_back(pixel(color_shadow, nextbyte, 0, 0, 0)) + elif SMPLayerVariant is SMPOutlineLayer: + # we don't know the color the game wants + # so we just draw index 0 + row_data.push_back(pixel(color_outline, 0, 0, 0, 0)) + elif SMPLayerVariant is SMPMainLayer: + for _ in range(4): + dpos += 1 + pixel_data.push_back(data_raw[dpos]) + + row_data.push_back(pixel(color_standard, + pixel_data[0], + pixel_data[1], + pixel_data[2], + pixel_data[3] & 0x1F)) # remove "usage" bit here + pixel_data.clear() elif lower_crumb == 0b00000010: - # player_color command - # draw the following 'count' pixels - # pixels are stored as 4 byte palette and meta infos - # count = (cmd >> 2) + 1 - - pixel_count = (cmd >> 2) + 1 - - for _ in range(pixel_count): - for _ in range(4): - dpos += 1 - pixel_data.push_back(data_raw[dpos]) - - row_data.push_back(pixel(color_player, - pixel_data[0], - pixel_data[1], - pixel_data[2], - pixel_data[3] & 0x1F)) # remove "usage" bit here - - pixel_data.clear() + if (SMPLayerVariant is SMPMainLayer or SMPLayerVariant is SMPShadowLayer): + # player_color command + # draw the following 'count' pixels + # pixels are stored as 4 byte palette and meta infos + # count = (cmd >> 2) + 1 + + pixel_count = (cmd >> 2) + 1 + + for _ in range(pixel_count): + if SMPLayerVariant is SMPShadowLayer: + dpos += 1 + nextbyte = data_raw[dpos] + row_data.push_back(pixel(color_shadow, + nextbyte, 0, 0, 0)) + else: + for _ in range(4): + dpos += 1 + pixel_data.push_back(data_raw[dpos]) + + row_data.push_back(pixel(color_player, + pixel_data[0], + pixel_data[1], + pixel_data[2], + pixel_data[3] & 0x1F)) # remove "usage" bit here + pixel_data.clear() + else: + raise Exception( + f"unknown smp {self.info.layer_type} layer drawing command: " + + f"{cmd:#x} in row {rowid:d}" + ) else: raise Exception( - f"unknown smp main graphics layer drawing command: " + + f"unknown smp {self.info.layer_type} layer drawing command: " + f"{cmd:#x} in row {rowid:d}" ) @@ -537,203 +624,43 @@ cdef class SMPMainLayer(SMPLayer): # end of row reached, return the created pixel array. return - def get_damage_mask(self): - """ - Convert the 4th pixel byte to a mask used for damaged units. - """ - return determine_damage_matrix(self.pcolor) - -cdef class SMPShadowLayer(SMPLayer): - """ - SMPLayer for the shadow graphics. - """ - - def __init__(self, layer_header, data): - super().__init__(layer_header, data) - - @cython.boundscheck(False) - cdef void process_drawing_cmds(self, - const uint8_t[::1] &data_raw, - vector[pixel] &row_data, - Py_ssize_t rowid, - Py_ssize_t first_cmd_offset, - size_t expected_size): - """ - extract colors (pixels) for the drawing commands - found for this row in the SMP frame. + def get_picture_data(self, palette) -> numpy.ndarray: """ + Convert the palette index matrix to a RGBA image. - # position in the data blob, we start at the first command of this row - cdef Py_ssize_t dpos = first_cmd_offset - - # is the end of the current row reached? - cdef bool eor = False - - cdef uint8_t cmd - cdef uint8_t nextbyte - cdef uint8_t lower_crumb - cdef int pixel_count - - # work through commands till end of row. - while not eor: - if row_data.size() > expected_size: - raise Exception( - f"Only {expected_size:d} pixels should be drawn in row {rowid:d} " + - f"with layer type {self.info.layer_type:#x}, but we have {row_data.size():d} " + - f"already!" - ) - - # fetch drawing instruction - cmd = data_raw[dpos] - - # Last 2 bits store command type - lower_crumb = 0b00000011 & cmd - - # opcode: cmd, rowid: rowid - - if lower_crumb == 0b00000011: - # eol (end of line) command, this row is finished now. - eor = True - - # shadows sometimes need an extra pixel at - # the end - if row_data.size() < expected_size: - # copy the last drawn pixel - # (still stored in nextbyte) - # - # TODO: confirm that this is the - # right way to do it - row_data.push_back(pixel(color_shadow, - nextbyte, 0, 0, 0)) - - continue - - elif lower_crumb == 0b00000000: - # skip command - # draw 'count' transparent pixels - # count = (cmd >> 2) + 1 - - pixel_count = (cmd >> 2) + 1 - - for _ in range(pixel_count): - row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) - - elif lower_crumb == 0b00000001: - # color_list command - # draw the following 'count' pixels - # pixels are stored as 1 byte alpha values - # count = (cmd >> 2) + 1 - - pixel_count = (cmd >> 2) + 1 - - for _ in range(pixel_count): - - dpos += 1 - nextbyte = data_raw[dpos] - - row_data.push_back(pixel(color_shadow, - nextbyte, 0, 0, 0)) - - else: - raise Exception( - f"unknown smp shadow layer drawing command: " + - f"{cmd:#x} in row {rowid:d}") - - # process next command - dpos += 1 - - # end of row reached, return the created pixel array. - return - - -cdef class SMPOutlineLayer(SMPLayer): - """ - SMPLayer for the outline graphics. - """ - - def __init__(self, layer_header, data): - super().__init__(layer_header, data) - - @cython.boundscheck(False) - cdef void process_drawing_cmds(self, - const uint8_t[::1] &data_raw, - vector[pixel] &row_data, - Py_ssize_t rowid, - Py_ssize_t first_cmd_offset, - size_t expected_size): - """ - extract colors (pixels) for the drawing commands - found for this row in the SMP frame. + :param palette: Color palette used for pixels in the sprite. + :type palette: .colortable.ColorTable + :return: Array of RGBA values. """ + return determine_rgba_matrix(self.pcolor, palette) - # position in the data blob, we start at the first command of this row - cdef Py_ssize_t dpos = first_cmd_offset - - # is the end of the current row reached? - cdef bool eor = False - - cdef uint8_t cmd - cdef uint8_t nextbyte - cdef uint8_t lower_crumb - cdef int pixel_count - - # work through commands till end of row. - while not eor: - if row_data.size() > expected_size: - raise Exception( - f"Only {expected_size:d} pixels should be drawn in row {rowid:d} " + - f"with layer type {self.info.layer_type:#x}, but we have {row_data.size():d} " - f"already!" - ) - - # fetch drawing instruction - cmd = data_raw[dpos] - - # Last 2 bits store command type - lower_crumb = 0b00000011 & cmd - - # opcode: cmd, rowid: rowid - - if lower_crumb == 0b00000011: - # eol (end of line) command, this row is finished now. - eor = True - - continue - - elif lower_crumb == 0b00000000: - # skip command - # draw 'count' transparent pixels - # count = (cmd >> 2) + 1 - - pixel_count = (cmd >> 2) + 1 + def get_damage_mask(self) -> numpy.ndarray: + """ + Convert the 4th pixel byte to a mask used for damaged units. - for _ in range(pixel_count): - row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) + :return: Damage mask of the layer. + """ + return determine_damage_matrix(self.pcolor) - elif lower_crumb == 0b00000001: - # color_list command - # draw the following 'count' pixels - # as player outline colors. - # count = (cmd >> 2) + 1 + def get_hotspot(self) -> tuple[int, int]: + """ + Return the layer's hotspot (the "center" of the image). - pixel_count = (cmd >> 2) + 1 + :return: Hotspot of the layer. + """ + return self.info.hotspot - for _ in range(pixel_count): - # we don't know the color the game wants - # so we just draw index 0 - row_data.push_back(pixel(color_outline, - 0, 0, 0, 0)) + def get_palette_number(self) -> int: + """ + Return the layer's palette number. - else: - raise Exception( - f"unknown smp outline layer drawing command: " + - f"{cmd:#x} in row {rowid:d}") - # process next command - dpos += 1 + :return: Palette number of the layer. + """ + return self.pcolor[0][0].palette & 0b00111111 - # end of row reached, return the created pixel array. - return + def __repr__(self) -> str: + return repr(self.info) @cython.boundscheck(False) @@ -742,6 +669,9 @@ cdef numpy.ndarray determine_rgba_matrix(vector[vector[pixel]] &image_matrix, numpy.ndarray[numpy.uint8_t, ndim=2, mode="c"] palette): """ converts a palette index image matrix to an rgba matrix. + + :param image_matrix: A 2-dimensional array of SMP pixels. + :param palette: Color palette used for normal pixels in the sprite. """ cdef size_t height = image_matrix.size() diff --git a/openage/convert/value_object/read/media/smx.pyx b/openage/convert/value_object/read/media/smx.pyx index d1f61a2735..887440cf7b 100644 --- a/openage/convert/value_object/read/media/smx.pyx +++ b/openage/convert/value_object/read/media/smx.pyx @@ -1,4 +1,4 @@ -# Copyright 2019-2023 the openage authors. See copying.md for legal info. +# Copyright 2019-2025 the openage authors. See copying.md for legal info. # # cython: infer_types=True @@ -21,6 +21,8 @@ from libcpp.vector cimport vector # SMX files have little endian byte order endianness = "< " + +# Boundary of a row in a SMX layer. cdef struct boundary_def: Py_ssize_t left Py_ssize_t right @@ -61,6 +63,39 @@ cdef public dict LAYER_TYPES = { } +cdef class SMXMainLayer8to5: + """ + Main graphics layer of an SMX (compressed with 8to5). + """ + pass + +cdef class SMXMainLayer4plus1: + """ + Main graphics layer of an SMX (compressed with 4plus1). + """ + pass + +cdef class SMXShadowLayer: + """ + Shadow layer of an SMX. + """ + pass + +cdef class SMXOutlineLayer: + """ + Outline layer of an SMX. + """ + pass + + +# fused type for SMX layer variants +ctypedef fused SMXLayerVariant: + SMXMainLayer8to5 + SMXMainLayer4plus1 + SMXShadowLayer + SMXOutlineLayer + + class SMX: """ Class for reading/converting compressed SMX files (delivered @@ -94,14 +129,12 @@ class SMX: # }; smx_layer_header = Struct(endianness + "H H h h I i") - def __init__(self, data): + def __init__(self, data: bytes): """ - Read an SMX image file. + Read an SMX image file and store the frames in the object. - :param data: File content as bytes. - :type data: bytes, bytearray + :param data: SMX file data. """ - smx_header = SMX.smx_header.unpack_from(data) self.smp_type, version, frame_count, file_size_comp,\ file_size_uncomp, comment = smx_header @@ -182,18 +215,18 @@ class SMX: if layer_type is SMXLayerType.MAIN: if layer_header.compression_type == 0x08: - self.main_frames.append(SMXMainLayer8to5(layer_header, data)) + self.main_frames.append(SMXLayer(SMXMainLayer8to5(), layer_header, data)) elif layer_header.compression_type == 0x00: - self.main_frames.append(SMXMainLayer4plus1(layer_header, data)) + self.main_frames.append(SMXLayer(SMXMainLayer4plus1(), layer_header, data)) elif layer_type is SMXLayerType.SHADOW: - self.shadow_frames.append(SMXShadowLayer(layer_header, data)) + self.shadow_frames.append(SMXLayer(SMXShadowLayer(), layer_header, data)) elif layer_type is SMXLayerType.OUTLINE: - self.outline_frames.append(SMXOutlineLayer(layer_header, data)) + self.outline_frames.append(SMXLayer(SMXOutlineLayer(), layer_header, data)) - def get_frames(self, layer: int = 0): + def get_frames(self, layer: int = 0) -> list[SMXLayer]: """ Get the frames in the SMX. @@ -201,7 +234,6 @@ class SMX: - 0 = main graphics - 1 = shadow graphics - 2 = outline - :type layer: int """ cdef list frames @@ -237,16 +269,23 @@ class SMX: class SMXLayerHeader: - def __init__(self, layer_type, frame_type, - palette_number, - width, height, hotspot_x, hotspot_y, - outline_table_offset, - qdl_command_table_offset, - qdl_color_table_offset): + def __init__( + self, + layer_type: SMXLayerType, + frame_type: int, + palette_number: int, + width: int, + height: int, + hotspot_x: int, + hotspot_y: int, + outline_table_offset: int, + qdl_command_table_offset: int, + qdl_color_table_offset: int + ) -> None: """ Stores the header of a layer including additional info about its frame. - :param layer_type: Type of layer. Either main. shadow or outline. + :param layer_type: Type of layer. :param frame_type: Type of the frame the layer belongs to. :param palette_number: Palette number used for pixels in the frame. :param width: Width of layer in pixels. @@ -256,16 +295,6 @@ class SMXLayerHeader: :param outline_table_offset: Absolute position of the layer's outline table in the file. :param qdl_command_table_offset: Absolute position of the layer's command table in the file. :param qdl_color_table_offset: Absolute position of the layer's pixel data table in the file. - :type layer_type: str - :type frame_type: int - :type palette_number: int - :type width: int - :type height: int - :type hotspot_x: int - :type hotspot_y: int - :type outline_table_offset: int - :type qdl_command_table_offset: int - :type qdl_color_table_offset: int """ self.size = (width, height) @@ -281,12 +310,12 @@ class SMXLayerHeader: self.qdl_color_table_offset = qdl_color_table_offset @staticmethod - def repr_header(): + def repr_header() -> str: return ("layer type | width x height | " "hotspot x/y | " ) - def __repr__(self): + def __repr__(self) -> str: ret = ( "% s | " % self.layer_type, "% 5d x% 7d | " % self.size, @@ -300,7 +329,7 @@ class SMXLayerHeader: cdef class SMXLayer: """ - one layer inside the compressed SMP. + Layer inside the compressed SMX. """ # struct smp_layer_row_edge { @@ -319,15 +348,28 @@ cdef class SMXLayer: # pixel matrix representing the final image cdef vector[vector[pixel]] pcolor - def __init__(self, layer_header, data): + def __init__( + self, + variant, # this argument must not be typed because cython can't handle it + layer_header: SMXLayerHeader, + data: bytes + ) -> None: + """ + Create a SMX layer. + + :param variant: Type of the layer. + :param layer_header: Header information of the layer. + :param data: Layer data. """ - SMX layer definition superclass. There can be various types of - layers inside an SMX frame. + self.init(variant, layer_header, data) + def init(self, SMXLayerVariant variant, layer_header: SMXLayerHeader, data: bytes) -> None: + """ + SMX layer definition. There can be various types of layers inside an SMX frame. + + :param variant: Type of the layer. :param layer_header: Header definition of the layer. :param data: File content as bytes. - :type layer_header: SMXLayerHeader - :type data: bytes, bytearray """ self.info = layer_header @@ -367,19 +409,22 @@ cdef class SMXLayer: # process cmd table for i in range(row_count): cmd_offset, color_offset, chunk_pos, row_data = \ - self.create_color_row(data_raw, i, cmd_offset, color_offset, chunk_pos) - + self.create_color_row(variant, data_raw, i, cmd_offset, color_offset, chunk_pos) self.pcolor.push_back(row_data) + cdef inline (int, int, int, vector[pixel]) create_color_row(self, + SMXLayerVariant variant, const uint8_t[::1] &data_raw, Py_ssize_t rowid, int cmd_offset, int color_offset, int chunk_pos): """ - Extract colors (pixels) for the given rowid. + Extract colors (pixels) for a pixel row in the layer. + :param variant: Type of the layer. + :param data_raw: Raw data of the layer. :param rowid: Index of the current row in the layer. :param cmd_offset: Offset of the command table of the layer. :param color_offset: Offset of the color table of the layer. @@ -411,6 +456,7 @@ cdef class SMXLayer: # process the drawing commands for this row. next_cmd_offset, next_color_offset, chunk_pos, row_data = \ self.process_drawing_cmds( + variant, data_raw, row_data, rowid, @@ -427,8 +473,8 @@ cdef class SMXLayer: # verify size of generated row if row_data.size() != pixel_count: got = row_data.size() - summary = "%d/%d -> row %d, layer type %d, offset %d / %#x" % ( - got, pixel_count, rowid, self.info.layer_type, + summary = "%d/%d -> row %d, layer type %s, offset %d / %#x" % ( + got, pixel_count, rowid, repr(self.info.layer_type), first_cmd_offset, first_cmd_offset ) txt = "got %%s pixels than expected: %s, missing: %d" % ( @@ -438,70 +484,10 @@ cdef class SMXLayer: return next_cmd_offset, next_color_offset, chunk_pos, row_data - cdef (int, int, int, vector[pixel]) process_drawing_cmds(self, - const uint8_t[::1] &data_raw, - vector[pixel] &row_data, - Py_ssize_t rowid, - Py_ssize_t first_cmd_offset, - Py_ssize_t first_color_offset, - int chunk_pos, - size_t expected_size): - """ - Extracts pixel data from the layer data. Every layer type uses - its own implementation for better optimization. - - :param row_data: Pixel data is appended to this array. - :param rowid: Index of the current row in the layer. - :param first_cmd_offset: Offset of the first command of the current row. - :param first_color_offset: Offset of the first pixel data value of the current row. - :param chunk_pos: Current position in the compressed chunk. - :param expected_size: Expected length of row_data after encountering the EOR command. - """ - pass - - def get_picture_data(self, palette): - """ - Convert the palette index matrix to a RGBA image. - - :param main_palette: Color palette used for pixels in the sprite. - :type main_palette: .colortable.ColorTable - :return: Array of RGBA values. - :rtype: numpy.ndarray - """ - return determine_rgba_matrix(self.pcolor, palette) - - def get_hotspot(self): - """ - Return the layer's hotspot (the "center" of the image). - - :return: Hotspot of the layer. - :rtype: tuple - """ - return self.info.hotspot - - def get_palette_number(self): - """ - Return the layer's palette number. - - :return: Palette number of the layer. - :rtype: int - """ - return self.info.palette_number - - def __repr__(self): - return repr(self.info) - - -cdef class SMXMainLayer8to5(SMXLayer): - """ - Compressed SMP layer (compression type 8to5) for the main graphics sprite. - """ - - def __init__(self, layer_header, data): - super().__init__(layer_header, data) @cython.boundscheck(False) cdef inline (int, int, int, vector[pixel]) process_drawing_cmds(self, + SMXLayerVariant variant, const uint8_t[::1] &data_raw, vector[pixel] &row_data, Py_ssize_t rowid, @@ -512,6 +498,15 @@ cdef class SMXMainLayer8to5(SMXLayer): """ extract colors (pixels) for the drawing commands that were compressed with 8to5 compression. + + :param variant: Type of the layer. + :param data_raw: Raw data of the layer. + :param row_data: Stores the extracted pixels. May be prefilled with transparent pixels. + :param rowid: Row index. + :param first_cmd_offset: Offset of the first drawing command in the data. + :param first_color_offset: Offset of the first color command in the data. + :param chunk_pos: Current position in the compressed chunk. + :param expected_size: Expected number of pixels in the row. """ # position in the command array, we start at the first command of this row cdef Py_ssize_t dpos_cmd = first_cmd_offset @@ -544,6 +539,14 @@ cdef class SMXMainLayer8to5(SMXLayer): cdef uint8_t pixel_mask_even_2 = 0b11110000 cdef uint8_t pixel_mask_even_3 = 0b00111111 + if SMXLayerVariant is SMXMainLayer8to5 or SMXLayerVariant is SMXMainLayer4plus1: + # Position in the pixel data array + dpos_color = first_color_offset + + cdef uint8_t palette_section_block = 0 + cdef uint8_t palette_section = 0 + cdef uint8_t nextbyte = 0 + # work through commands till end of row. while not eor: if row_data.size() > expected_size: @@ -564,6 +567,17 @@ cdef class SMXMainLayer8to5(SMXLayer): eor = True dpos_cmd += 1 + # shadows sometimes need an extra pixel at + # the end + if SMXLayerVariant is SMXShadowLayer: + if row_data.size() < expected_size: + # copy the last drawn pixel + # (still stored in nextbyte) + # + # TODO: confirm that this is the + # right way to do it + row_data.push_back(pixel(color_shadow, + nextbyte, 0, 0, 0)) continue elif lower_crumb == 0b00000000: @@ -587,249 +601,176 @@ cdef class SMXMainLayer8to5(SMXLayer): pixel_count = (cmd >> 2) + 1 - for _ in range(pixel_count): - # Start fetching pixel data - if odd: - # Odd indices require manual extraction of each of the 4 values - - # Palette index. Essentially a rotation of (byte[1]byte[2]) - # by 6 to the left, then masking with 0x00FF. - pixel_data_odd_0 = data_raw[dpos_color + 1] - pixel_data_odd_1 = data_raw[dpos_color + 2] - pixel_data.push_back((pixel_data_odd_0 >> 2) | (pixel_data_odd_1 << 6)) - - # Palette section. Described in byte[2] in bits 4-5. - pixel_data.push_back((pixel_data_odd_1 >> 2) & 0x03) - - # Damage mask 1. Essentially a rotation of (byte[3]byte[4]) - # by 6 to the left, then masking with 0x00F0. - pixel_data_odd_2 = data_raw[dpos_color + 3] - pixel_data_odd_3 = data_raw[dpos_color + 4] - pixel_data.push_back(((pixel_data_odd_2 >> 2) | (pixel_data_odd_3 << 6)) & 0xF0) - - # Damage mask 2. Described in byte[4] in bits 0-5. - pixel_data.push_back((pixel_data_odd_3 >> 2) & 0x3F) - - row_data.push_back(pixel(color_standard, - pixel_data[0], - pixel_data[1], - pixel_data[2], - pixel_data[3])) - - # Go to next pixel - dpos_color += 5 - - else: - # Even indices can be read "as is". They just have to be masked. - for px_dpos in range(4): - pixel_data.push_back(data_raw[dpos_color + px_dpos]) - + if SMXLayerVariant is SMXMainLayer8to5: + pixel_data.reserve(4) + for _ in range(pixel_count): + # Start fetching pixel data + if odd: + # Odd indices require manual extraction of each of the 4 values + + # Palette index. Essentially a rotation of (byte[1]byte[2]) + # by 6 to the left, then masking with 0x00FF. + pixel_data_odd_0 = data_raw[dpos_color + 1] + pixel_data_odd_1 = data_raw[dpos_color + 2] + pixel_data.push_back((pixel_data_odd_0 >> 2) | (pixel_data_odd_1 << 6)) + + # Palette section. Described in byte[2] in bits 4-5. + pixel_data.push_back((pixel_data_odd_1 >> 2) & 0x03) + + # Damage mask 1. Essentially a rotation of (byte[3]byte[4]) + # by 6 to the left, then masking with 0x00F0. + pixel_data_odd_2 = data_raw[dpos_color + 3] + pixel_data_odd_3 = data_raw[dpos_color + 4] + pixel_data.push_back(((pixel_data_odd_2 >> 2) | (pixel_data_odd_3 << 6)) & 0xF0) + + # Damage mask 2. Described in byte[4] in bits 0-5. + pixel_data.push_back((pixel_data_odd_3 >> 2) & 0x3F) + + row_data.push_back(pixel(color_standard, + pixel_data[0], + pixel_data[1], + pixel_data[2], + pixel_data[3])) + + # Go to next pixel + dpos_color += 5 + + else: + # Even indices can be read "as is". They just have to be masked. + for i in range(4): + pixel_data.push_back(data_raw[dpos_color + i]) + + row_data.push_back(pixel(color_standard, + pixel_data[0], + pixel_data[1] & pixel_mask_even_1, + pixel_data[2] & pixel_mask_even_2, + pixel_data[3] & pixel_mask_even_3)) + + odd = not odd + pixel_data.clear() + + if SMXLayerVariant is SMXMainLayer4plus1: + palette_section_block = data_raw[dpos_color + (4 - chunk_pos)] + + for _ in range(pixel_count): + # Start fetching pixel data + palette_section = ( + palette_section_block >> (2 * chunk_pos)) & 0x03 row_data.push_back(pixel(color_standard, - pixel_data[0], - pixel_data[1] & pixel_mask_even_1, - pixel_data[2] & pixel_mask_even_2, - pixel_data[3] & pixel_mask_even_3)) - - odd = not odd - pixel_data.clear() - - elif lower_crumb == 0b00000010: - # player_color command - # draw the following 'count' pixels - # pixels are stored in 5 byte chunks - # even pixel indices have their info stored - # in byte[0] - byte[3]. odd pixel indices have - # their info stored in byte[1] - byte[4]. - # count = (cmd >> 2) + 1 - - pixel_count = (cmd >> 2) + 1 - - for _ in range(pixel_count): - # Start fetching pixel data - if odd: - # Odd indices require manual extraction of each of the 4 values - - # Palette index. Essentially a rotation of (byte[1]byte[2]) - # by 6 to the left, then masking with 0x00FF. - pixel_data_odd_0 = data_raw[dpos_color + 1] - pixel_data_odd_1 = data_raw[dpos_color + 2] - pixel_data.push_back((pixel_data_odd_0 >> 2) | (pixel_data_odd_1 << 6)) - - # Palette section. Described in byte[2] in bits 4-5. - pixel_data.push_back((pixel_data_odd_1 >> 2) & 0x03) - - # Damage modifier 1. Essentially a rotation of (byte[3]byte[4]) - # by 6 to the left, then masking with 0x00F0. - pixel_data_odd_2 = data_raw[dpos_color + 3] - pixel_data_odd_3 = data_raw[dpos_color + 4] - pixel_data.push_back(((pixel_data_odd_2 >> 2) | (pixel_data_odd_3 << 6)) & 0xF0) - - # Damage modifier 2. Described in byte[4] in bits 0-5. - pixel_data.push_back((pixel_data_odd_3 >> 2) & 0x3F) - - row_data.push_back(pixel(color_player, - pixel_data[0], - pixel_data[1], - pixel_data[2], - pixel_data[3])) - - # Go to next pixel - dpos_color += 5 - - else: - # Even indices can be read "as is". They just have to be masked. - for px_dpos in range(4): - pixel_data.push_back(data_raw[dpos_color + px_dpos]) - - row_data.push_back(pixel(color_player, - pixel_data[0], - pixel_data[1] & pixel_mask_even_1, - pixel_data[2] & pixel_mask_even_2, - pixel_data[3] & pixel_mask_even_3)) - - odd = not odd - pixel_data.clear() - - else: - raise Exception( - f"unknown smx main graphics layer drawing command: " + - f"{cmd:#x} in row {rowid:d}" - ) + data_raw[dpos_color], + palette_section, + 0, + 0)) - # Process next command - dpos_cmd += 1 + dpos_color += 1 + chunk_pos += 1 - return dpos_cmd, dpos_color, odd, row_data + # Skip to next chunk + if chunk_pos > 3: + chunk_pos = 0 + dpos_color += 1 # Skip palette section block + palette_section_block = data_raw[dpos_color + 4] + if SMXLayerVariant is SMXShadowLayer: + for _ in range(pixel_count): + dpos_cmd += 1 + nextbyte = data_raw[dpos_cmd] -cdef class SMXMainLayer4plus1(SMXLayer): - """ - Compressed SMP layer (compression type 4plus1) for the main graphics sprite. - """ - - def __init__(self, layer_header, data): - super().__init__(layer_header, data) - - @cython.boundscheck(False) - cdef inline (int, int, int, vector[pixel]) process_drawing_cmds(self, - const uint8_t[::1] &data_raw, - vector[pixel] &row_data, - Py_ssize_t rowid, - Py_ssize_t first_cmd_offset, - Py_ssize_t first_color_offset, - int chunk_pos, - size_t expected_size): - """ - extract colors (pixels) for the drawing commands that were - compressed with 4plus1 compression. - """ - # position in the data blob, we start at the first command of this row - cdef Py_ssize_t dpos_cmd = first_cmd_offset - - # Position in the pixel data array - cdef Py_ssize_t dpos_color = first_color_offset - - # Position in the compression chunk - cdef uint8_t dpos_chunk = chunk_pos - - # is the end of the current row reached? - cdef bool eor = False - - cdef uint8_t cmd = 0 - cdef uint8_t lower_crumb = 0 - cdef int pixel_count = 0 - cdef uint8_t palette_section_block = 0 - cdef uint8_t palette_section = 0 - - # work through commands till end of row. - while not eor: - if row_data.size() > expected_size: - raise Exception( - f"Only {expected_size:d} pixels should be drawn in row {rowid:d} " + - f"with layer type {self.info.layer_type:#x}, but we have {row_data.size():d} " + - f"already!" - ) - - # fetch drawing instruction - cmd = data_raw[dpos_cmd] - - # Last 2 bits store command type - lower_crumb = 0b00000011 & cmd - - if lower_crumb == 0b00000011: - # eor (end of row) command, this row is finished now. - eor = True - dpos_cmd += 1 - - continue - - elif lower_crumb == 0b00000000: - # skip command - # draw 'count' transparent pixels - # count = (cmd >> 2) + 1 - - pixel_count = (cmd >> 2) + 1 + row_data.push_back(pixel(color_shadow, + nextbyte, 0, 0, 0)) - for _ in range(pixel_count): - row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) - - elif lower_crumb == 0b00000001: - # color_list command - # draw the following 'count' pixels - # 4 pixels are stored in every 5 byte chunk. - # palette indices are contained in byte[0] - byte[3] - # palette sections are stored in byte[4] - # count = (cmd >> 2) + 1 - - pixel_count = (cmd >> 2) + 1 - - palette_section_block = data_raw[dpos_color + (4 - dpos_chunk)] - - for _ in range(pixel_count): - # Start fetching pixel data - palette_section = (palette_section_block >> (2 * dpos_chunk)) & 0x03 - row_data.push_back(pixel(color_standard, - data_raw[dpos_color], - palette_section, - 0, - 0)) - - dpos_color += 1 - dpos_chunk += 1 - - # Skip to next chunk - if dpos_chunk > 3: - dpos_chunk = 0 - dpos_color += 1 # Skip palette section block - palette_section_block = data_raw[dpos_color + 4] + if SMXLayerVariant is SMXOutlineLayer: + # we don't know the color the game wants + # so we just draw index 0 + for _ in range(pixel_count): + row_data.push_back(pixel(color_outline, + 0, 0, 0, 0)) elif lower_crumb == 0b00000010: - # player_color command - # draw the following 'count' pixels - # 4 pixels are stored in every 5 byte chunk. - # palette indices are contained in byte[0] - byte[3] - # palette sections are stored in byte[4] - # count = (cmd >> 2) + 1 + if SMXLayerVariant is SMXMainLayer8to5: + # player_color command + # draw the following 'count' pixels + # pixels are stored in 5 byte chunks + # even pixel indices have their info stored + # in byte[0] - byte[3]. odd pixel indices have + # their info stored in byte[1] - byte[4]. + # count = (cmd >> 2) + 1 + + pixel_count = (cmd >> 2) + 1 + + for _ in range(pixel_count): + # Start fetching pixel data + if odd: + # Odd indices require manual extraction of each of the 4 values + + # Palette index. Essentially a rotation of (byte[1]byte[2]) + # by 6 to the left, then masking with 0x00FF. + pixel_data_odd_0 = data_raw[dpos_color + 1] + pixel_data_odd_1 = data_raw[dpos_color + 2] + pixel_data.push_back((pixel_data_odd_0 >> 2) | (pixel_data_odd_1 << 6)) + + # Palette section. Described in byte[2] in bits 4-5. + pixel_data.push_back((pixel_data_odd_1 >> 2) & 0x03) + + # Damage modifier 1. Essentially a rotation of (byte[3]byte[4]) + # by 6 to the left, then masking with 0x00F0. + pixel_data_odd_2 = data_raw[dpos_color + 3] + pixel_data_odd_3 = data_raw[dpos_color + 4] + pixel_data.push_back(((pixel_data_odd_2 >> 2) | (pixel_data_odd_3 << 6)) & 0xF0) + + # Damage modifier 2. Described in byte[4] in bits 0-5. + pixel_data.push_back((pixel_data_odd_3 >> 2) & 0x3F) + + row_data.push_back(pixel(color_player, + pixel_data[0], + pixel_data[1], + pixel_data[2], + pixel_data[3])) + + # Go to next pixel + dpos_color += 5 + + else: + # Even indices can be read "as is". They just have to be masked. + for px_dpos in range(4): + pixel_data.push_back(data_raw[dpos_color + px_dpos]) + + row_data.push_back(pixel(color_player, + pixel_data[0], + pixel_data[1] & pixel_mask_even_1, + pixel_data[2] & pixel_mask_even_2, + pixel_data[3] & pixel_mask_even_3)) + + odd = not odd + pixel_data.clear() + + elif SMXLayerVariant is SMXMainLayer4plus1: + # player_color command + # draw the following 'count' pixels + # 4 pixels are stored in every 5 byte chunk. + # palette indices are contained in byte[0] - byte[3] + # palette sections are stored in byte[4] + # count = (cmd >> 2) + 1 + + pixel_count = (cmd >> 2) + 1 + + for _ in range(pixel_count): + # Start fetching pixel data + palette_section = (palette_section_block >> (2 * chunk_pos)) & 0x03 + row_data.push_back(pixel(color_player, + data_raw[dpos_color], + palette_section, + 0, + 0)) - pixel_count = (cmd >> 2) + 1 + dpos_color += 1 + chunk_pos += 1 - for _ in range(pixel_count): - # Start fetching pixel data - palette_section = (palette_section_block >> (2 * dpos_chunk)) & 0x03 - row_data.push_back(pixel(color_player, - data_raw[dpos_color], - palette_section, - 0, - 0)) - - dpos_color += 1 - dpos_chunk += 1 - - # Skip to next chunk - if dpos_chunk > 3: - dpos_chunk = 0 - dpos_color += 1 # Skip palette section block - palette_section_block = data_raw[dpos_color + 4] + # Skip to next chunk + if chunk_pos > 3: + chunk_pos = 0 + dpos_color += 1 # Skip palette section block + palette_section_block = data_raw[dpos_color + 4] else: raise Exception( @@ -840,203 +781,40 @@ cdef class SMXMainLayer4plus1(SMXLayer): # Process next command dpos_cmd += 1 - return dpos_cmd, dpos_color, dpos_chunk, row_data - - -cdef class SMXShadowLayer(SMXLayer): - """ - Compressed SMP layer for the shadow graphics. - """ + if SMXLayerVariant is SMXMainLayer8to5 or SMXLayerVariant is SMXMainLayer4plus1: + return dpos_cmd, dpos_color, chunk_pos, row_data + elif SMXLayerVariant is SMXOutlineLayer or SMXLayerVariant is SMXShadowLayer: + return dpos_cmd, dpos_cmd, chunk_pos, row_data - def __init__(self, layer_header, data): - super().__init__(layer_header, data) - @cython.boundscheck(False) - cdef inline (int, int, int, vector[pixel]) process_drawing_cmds(self, - const uint8_t[::1] &data_raw, - vector[pixel] &row_data, - Py_ssize_t rowid, - Py_ssize_t first_cmd_offset, - Py_ssize_t first_color_offset, - int chunk_pos, - size_t expected_size): + def get_picture_data(self, palette) -> numpy.ndarray: """ - extract colors (pixels) for the drawing commands - found for this row in the SMX layer. - """ - # position in the data blob, we start at the first command of this row - cdef Py_ssize_t dpos = first_cmd_offset - - # is the end of the current row reached? - cdef bool eor = False - - cdef uint8_t cmd = 0 - cdef uint8_t nextbyte = 0 - cdef uint8_t lower_crumb = 0 - cdef int pixel_count = 0 - - # work through commands till end of row. - while not eor: - if row_data.size() > expected_size: - raise Exception( - f"Only {expected_size:d} pixels should be drawn ifn row {rowid:d} " + - f"with layer type {self.info.layer_type:#x}, but we have {row_data.size():d} " + - f"already!" - ) - - # fetch drawing instruction - cmd = data_raw[dpos] - - # Last 2 bits store command type - lower_crumb = 0b00000011 & cmd - - if lower_crumb == 0b00000011: - # eol (end of line) command, this row is finished now. - eor = True - dpos += 1 - - # shadows sometimes need an extra pixel at - # the end - if row_data.size() < expected_size: - # copy the last drawn pixel - # (still stored in nextbyte) - # - # TODO: confirm that this is the - # right way to do it - row_data.push_back(pixel(color_shadow, - nextbyte, 0, 0, 0)) - - continue - - elif lower_crumb == 0b00000000: - # skip command - # draw 'count' transparent pixels - # count = (cmd >> 2) + 1 - - pixel_count = (cmd >> 2) + 1 - - for _ in range(pixel_count): - row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) - - elif lower_crumb == 0b00000001: - # color_list command - # draw the following 'count' pixels - # pixels are stored as 1 byte alpha values - # count = (cmd >> 2) + 1 - - pixel_count = (cmd >> 2) + 1 - - for _ in range(pixel_count): - dpos += 1 - nextbyte = data_raw[dpos] - - row_data.push_back(pixel(color_shadow, - nextbyte, 0, 0, 0)) - - else: - raise Exception( - f"unknown smp shadow layer drawing command: " + - f"{cmd:#x} in row {rowid}" - ) - - # process next command - dpos += 1 - - # end of row reached, return the created pixel array. - return dpos, dpos, chunk_pos, row_data - - -cdef class SMXOutlineLayer(SMXLayer): - """ - Compressed SMP layer for the outline graphics. - """ - - def __init__(self, layer_header, data): - super().__init__(layer_header, data) + Convert the palette index matrix to a RGBA image. - @cython.boundscheck(False) - cdef inline (int, int, int, vector[pixel]) process_drawing_cmds(self, - const uint8_t[::1] &data_raw, - vector[pixel] &row_data, - Py_ssize_t rowid, - Py_ssize_t first_cmd_offset, - Py_ssize_t first_color_offset, - int chunk_pos, - size_t expected_size): - """ - extract colors (pixels) for the drawing commands - found for this row in the SMX layer. + :param palette: Color palette used for pixels in the sprite. + :type palette: .colortable.ColorTable + :return: Array of RGBA values. """ - # position in the data blob, we start at the first command of this row - cdef Py_ssize_t dpos = first_cmd_offset - - # is the end of the current row reached? - cdef bool eor = False - - cdef uint8_t cmd = 0 - cdef uint8_t nextbyte = 0 - cdef uint8_t lower_crumb = 0 - cdef int pixel_count = 0 - - # work through commands till end of row. - while not eor: - if row_data.size() > expected_size: - raise Exception( - f"Only {expected_size:d} pixels should be drawn in row {rowid:d} " + - f"with layer type {self.info.layer_type:#x}, but we have {row_data.size():d} " + - f"already!" - ) - - # fetch drawing instruction - cmd = data_raw[dpos] - - # Last 2 bits store command type - lower_crumb = 0b00000011 & cmd - - # opcode: cmd, rowid: rowid - - if lower_crumb == 0b00000011: - # eol (end of line) command, this row is finished now. - eor = True - dpos += 1 - - continue - - elif lower_crumb == 0b00000000: - # skip command - # draw 'count' transparent pixels - # count = (cmd >> 2) + 1 - - pixel_count = (cmd >> 2) + 1 - - for _ in range(pixel_count): - row_data.push_back(pixel(color_transparent, 0, 0, 0, 0)) - - elif lower_crumb == 0b00000001: - # color_list command - # draw the following 'count' pixels - # as player outline colors. - # count = (cmd >> 2) + 1 + return determine_rgba_matrix(self.pcolor, palette) - pixel_count = (cmd >> 2) + 1 + def get_hotspot(self) -> tuple[int, int]: + """ + Return the layer's hotspot (the "center" of the image). - for _ in range(pixel_count): - # we don't know the color the game wants - # so we just draw index 0 - row_data.push_back(pixel(color_outline, - 0, 0, 0, 0)) + :return: Hotspot of the layer. + """ + return self.info.hotspot - else: - raise Exception( - f"unknown smp outline layer drawing command: " + - f"{cmd:#x} in row {rowid}" - ) + def get_palette_number(self) -> int: + """ + Return the layer's palette number. - # process next command - dpos += 1 + :return: Palette number of the layer. + """ + return self.info.palette_number - # end of row reached, return the created pixel array. - return dpos, dpos, chunk_pos, row_data + def __repr__(self): + return repr(self.info) @cython.boundscheck(False) diff --git a/openage/game/main.py b/openage/game/main.py index 0fdf007259..1ad1378426 100644 --- a/openage/game/main.py +++ b/openage/game/main.py @@ -36,6 +36,19 @@ def init_subparser(cli: ArgumentParser) -> None: help="Check if the assets are up to date" ) + cli.add_argument( + "--window-size", nargs=2, type=int, default=[1024, 768], + metavar=('WIDTH', 'HEIGHT'), + help="Initial window size in pixels") + + cli.add_argument( + "--vsync", action='store_true', + help="Enable vertical synchronization") + + cli.add_argument( + "--window-mode", choices=["fullscreen", "borderless", "windowed"], default="windowed", + help="Set the window mode") + def main(args, error): """ @@ -98,5 +111,13 @@ def main(args, error): # encode modpacks as bytes for the C++ interface args.modpacks = [modpack.encode('utf-8') for modpack in args.modpacks] + # Pass window parameters to engine + args.window_args = { + "width": args.window_size[0], + "height": args.window_size[1], + "vsync": args.vsync, + "window_mode": args.window_mode, + } + # start the game, continue in main_cpp.pyx! return run_game(args, root) diff --git a/openage/game/main_cpp.pyx b/openage/game/main_cpp.pyx index e7697b0ddb..d37545f87c 100644 --- a/openage/game/main_cpp.pyx +++ b/openage/game/main_cpp.pyx @@ -1,4 +1,4 @@ -# Copyright 2015-2023 the openage authors. See copying.md for legal info. +# Copyright 2015-2024 the openage authors. See copying.md for legal info. from cpython.ref cimport PyObject from libcpp.string cimport string @@ -37,6 +37,12 @@ def run_game(args, root_path): else: args_cpp.mods = vector[string]() + # window + args_cpp.window_args.width = args.window_args["width"] + args_cpp.window_args.height = args.window_args["height"] + args_cpp.window_args.vsync = args.window_args["vsync"] + args_cpp.window_args.mode = args.window_args["window_mode"].encode('utf-8') + # run the game! with nogil: result = run_game_cpp(args_cpp) diff --git a/openage/main/main.py b/openage/main/main.py index fbeb527ee4..05266ec744 100644 --- a/openage/main/main.py +++ b/openage/main/main.py @@ -28,6 +28,19 @@ def init_subparser(cli: ArgumentParser): "--modpacks", nargs="+", type=str, help="list of modpacks to load") + cli.add_argument( + "--window-size", nargs=2, type=int, default=[1024, 768], + metavar=('WIDTH', 'HEIGHT'), + help="Initial window size in pixels") + + cli.add_argument( + "--vsync", action='store_true', + help="Enable vertical synchronization") + + cli.add_argument( + "--window-mode", choices=["fullscreen", "borderless", "windowed"], default="windowed", + help="Set the window mode") + def main(args, error): """ @@ -106,5 +119,13 @@ def main(args, error): else: args.modpacks = [query_modpack(list(available_modpacks.keys())).encode("utf-8")] + # Pass window parameters to engine + args.window_args = { + "width": args.window_size[0], + "height": args.window_size[1], + "vsync": args.vsync, + "window_mode": args.window_mode, + } + # start the game, continue in main_cpp.pyx! return run_game(args, root) diff --git a/openage/main/main_cpp.pyx b/openage/main/main_cpp.pyx index e7697b0ddb..d37545f87c 100644 --- a/openage/main/main_cpp.pyx +++ b/openage/main/main_cpp.pyx @@ -1,4 +1,4 @@ -# Copyright 2015-2023 the openage authors. See copying.md for legal info. +# Copyright 2015-2024 the openage authors. See copying.md for legal info. from cpython.ref cimport PyObject from libcpp.string cimport string @@ -37,6 +37,12 @@ def run_game(args, root_path): else: args_cpp.mods = vector[string]() + # window + args_cpp.window_args.width = args.window_args["width"] + args_cpp.window_args.height = args.window_args["height"] + args_cpp.window_args.vsync = args.window_args["vsync"] + args_cpp.window_args.mode = args.window_args["window_mode"].encode('utf-8') + # run the game! with nogil: result = run_game_cpp(args_cpp) diff --git a/packaging/docker/devenv/Dockerfile.ubuntu.2204 b/packaging/docker/devenv/Dockerfile.ubuntu.2404 similarity index 75% rename from packaging/docker/devenv/Dockerfile.ubuntu.2204 rename to packaging/docker/devenv/Dockerfile.ubuntu.2404 index 4976e0c693..f57dd43321 100644 --- a/packaging/docker/devenv/Dockerfile.ubuntu.2204 +++ b/packaging/docker/devenv/Dockerfile.ubuntu.2404 @@ -1,4 +1,4 @@ -FROM ubuntu:23.04 +FROM ubuntu:24.04 RUN apt-get update && DEBIAN_FRONTEND="noninteractive" apt-get install -y sudo \ && sudo apt-get update \ @@ -8,8 +8,8 @@ RUN apt-get update && DEBIAN_FRONTEND="noninteractive" apt-get install -y sudo \ cmake \ cython3 \ flex \ - gcc-11 \ - g++-11 \ + gcc \ + g++ \ git \ libeigen3-dev \ libepoxy-dev \ @@ -37,3 +37,7 @@ RUN apt-get update && DEBIAN_FRONTEND="noninteractive" apt-get install -y sudo \ qml6-module-qtquick3d-spatialaudio \ && sudo apt-get clean \ && truncate -s 0 ~/.bash_history + +# At least cython >= 3.0.10 < 4.0.0 is required to avoid runtime errors +# TODO: Remove this line once cython is upgraded in Ubuntu 24.04.3 (expected around August 2025) +RUN pip install "cython>=3.0.10,<4.0.0" --break-system-packages \ No newline at end of file