diff --git a/.clang-format b/.clang-format index b870e0ee95..4191b164f7 100644 --- a/.clang-format +++ b/.clang-format @@ -12,7 +12,7 @@ AlignConsecutiveDeclarations: false AlignConsecutiveMacros: false AlignEscapedNewlines: DontAlign AlignOperands: Align -AlignTrailingComments: false +AlignTrailingComments: true AllowAllArgumentsOnNextLine: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: Never @@ -81,7 +81,7 @@ IndentCaseBlocks: false IndentCaseLabels: false IndentExternBlock: NoIndent IndentGotoLabels: false -IndentPPDirectives: None +IndentPPDirectives: BeforeHash IndentWidth: 4 IndentWrappedFunctionNames: false # clang-format-16 InsertNewlineAtEOF: true @@ -100,7 +100,7 @@ PenaltyBreakTemplateDeclaration: 10 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 200 PointerAlignment: Right -ReflowComments: false +ReflowComments: true SortIncludes: CaseInsensitive SortUsingDeclarations: true SpaceAfterCStyleCast: false diff --git a/.github/PULL_REQUEST_TEMPLATES/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATES/pull_request_template.md new file mode 100644 index 0000000000..537cbfb04a --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATES/pull_request_template.md @@ -0,0 +1,13 @@ +### Merge Checklist + + + + +- [ ] 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 checkmerge` and fixed all mentioned problems + + +### Description + + diff --git a/.github/PULL_REQUEST_TEMPLATES/release_template.md b/.github/PULL_REQUEST_TEMPLATES/release_template.md new file mode 100644 index 0000000000..96661d5de3 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATES/release_template.md @@ -0,0 +1,7 @@ +### Checklist + +- [ ] Changelog + - [ ] Release date + - [ ] Version number in title + - [ ] Full commit log +- [ ] Bump version in `openage_version` diff --git a/.github/workflows/macosx-ci.yml b/.github/workflows/macosx-ci.yml index c60d76a4da..2270121092 100644 --- a/.github/workflows/macosx-ci.yml +++ b/.github/workflows/macosx-ci.yml @@ -5,7 +5,7 @@ jobs: # https://docs.github.com/en/actions/reference/software-installed-on-github-hosted-runners # we install stuff not already there macos-build: - runs-on: macOS-latest + runs-on: macos-latest steps: - name: Checkout sources uses: actions/checkout@v4 @@ -19,7 +19,7 @@ jobs: string(TIMESTAMP current_date "%Y-%m-%d-%H:%M:%S" UTC) message("timestamp=${current_date}" >> $GITHUB_OUTPUT) - name: Cache dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache-deps with: path: | @@ -29,7 +29,7 @@ jobs: restore-keys: | ${{ runner.os }}-deps- - name: Cache ccache dir - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache-ccache with: path: ~/Library/Caches/ccache @@ -40,37 +40,23 @@ jobs: run: brew update-reset - name: Brew update run: brew update - - name: Install clang / LLVM 15.0.0 - run: | - set -x - brew install wget - mkdir -p /tmp/clang - cd /tmp/clang - wget https://github.com/llvm/llvm-project/releases/download/llvmorg-15.0.0/clang+llvm-15.0.0-x86_64-apple-darwin.tar.xz -O clang-15.0.0.tar.xz - ls - tar -xvf clang-15.0.0.tar.xz -C ~ - cd ~ - mv clang+llvm-15.0.0-x86_64-apple-darwin clang-15.0.0 - ~/clang-15.0.0/bin/clang++ --version - name: Brew install DeJaVu fonts - run: brew tap homebrew/cask-fonts && brew install font-dejavu - - name: Remove python's 2to3 link so that 'brew link' does not fail - run: rm '/usr/local/bin/2to3' && rm '/usr/local/bin/2to3-3.11' + run: brew install --cask font-dejavu - name: Install environment helpers with homebrew - run: brew install ccache - - name: Install dependencies with homebrew - run: brew install libepoxy freetype fontconfig harfbuzz sdl2 sdl2_image opus opusfile qt6 libogg libpng toml11 eigen + run: brew install --force ccache + - name: Install LLVM with homebrew + run: brew install --force llvm + - name: Install openage dependencies with homebrew + run: brew install --force cmake python3 libepoxy freetype fontconfig harfbuzz opus opusfile qt6 libogg libpng toml11 eigen - name: Install nyan dependencies with homebrew - run: brew install flex make + run: brew install --force flex make - name: Install python3 packages # cython, numpy and pygments are in homebrew, # but "cython is keg-only, which means it was not symlinked into /usr/local" # numpy pulls gcc as dep? and pygments doesn't work. - run: pip3 install --upgrade cython numpy mako lz4 pillow pygments toml + run: pip3 install --upgrade --break-system-packages cython numpy mako lz4 pillow pygments setuptools toml - name: Configure - run: | - CLANG_PATH="$HOME/clang-15.0.0/bin/clang++" - ./configure --compiler="$CLANG_PATH" --mode=debug --ccache --download-nyan + run: ./configure --compiler="$(brew --prefix llvm)/bin/clang++" --mode=release --ccache --download-nyan - name: Build run: make -j$(sysctl -n hw.logicalcpu) VERBOSE=1 - name: Test diff --git a/.github/workflows/ubuntu-22.04.yml b/.github/workflows/ubuntu-24.04.yml similarity index 63% rename from .github/workflows/ubuntu-22.04.yml rename to .github/workflows/ubuntu-24.04.yml index 3343df5506..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: | @@ -16,7 +16,7 @@ jobs: sudo docker save openage-devenv:latest | gzip > /tmp/staging/devenv.tar.gz shell: bash - name: Publish the Docker image - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: devenv-image-compressed.tar.gz path: '/tmp/staging/devenv.tar.gz' @@ -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 @@ -32,7 +32,7 @@ jobs: run: mkdir -p /tmp/image shell: bash - name: Download devenv image - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v4 with: name: devenv-image-compressed.tar.gz path: '/tmp/image' @@ -41,13 +41,13 @@ 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 tar -czvf /tmp/openage/openage-build.tar.gz ./build - name: Publish build artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: openage-build.tar.gz path: '/tmp/openage/openage-build.tar.gz' diff --git a/.github/workflows/windows-server-2019.yml b/.github/workflows/windows-server-2019.yml index 93a9ecf867..06f9fa0df4 100644 --- a/.github/workflows/windows-server-2019.yml +++ b/.github/workflows/windows-server-2019.yml @@ -15,7 +15,7 @@ jobs: vswhere -latest shell: pwsh - name: Setup Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.9' architecture: 'x64' @@ -24,7 +24,7 @@ jobs: mkdir download cd download $zipfile = "openage-dep-x64-windows.zip" - Invoke-WebRequest https://github.com/SFTtech/openage-dependencies/releases/download/v0.5.0/openage-dep-x64-windows.zip -OutFile $zipfile + Invoke-WebRequest https://github.com/SFTtech/openage-dependencies/releases/download/v0.5.1/openage-dep-x64-windows.zip -OutFile $zipfile Expand-Archive -Path $zipfile -DestinationPath . -Force Remove-Item $zipfile (Get-ChildItem . -Recurse -File).FullName @@ -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,9 +76,10 @@ 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@v3 + uses: actions/upload-artifact@v4 if: ${{ always() }} with: name: build-files @@ -86,7 +87,7 @@ jobs: if-no-files-found: error retention-days: 30 - name: Publish packaged artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: ${{ always() }} with: name: package-files diff --git a/.github/workflows/windows-server-2022.yml b/.github/workflows/windows-server-2022.yml index c4deba0d46..0ee700e8f3 100644 --- a/.github/workflows/windows-server-2022.yml +++ b/.github/workflows/windows-server-2022.yml @@ -15,7 +15,7 @@ jobs: vswhere -latest shell: pwsh - name: Setup Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.9' architecture: 'x64' @@ -24,7 +24,7 @@ jobs: mkdir download cd download $zipfile = "openage-dep-x64-windows.zip" - Invoke-WebRequest https://github.com/SFTtech/openage-dependencies/releases/download/v0.5.0/openage-dep-x64-windows.zip -OutFile $zipfile + Invoke-WebRequest https://github.com/SFTtech/openage-dependencies/releases/download/v0.5.1/openage-dep-x64-windows.zip -OutFile $zipfile Expand-Archive -Path $zipfile -DestinationPath . -Force Remove-Item $zipfile (Get-ChildItem . -Recurse -File).FullName @@ -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,9 +76,10 @@ 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@v3 + uses: actions/upload-artifact@v4 if: ${{ always() }} with: name: build-files @@ -86,7 +87,7 @@ jobs: if-no-files-found: error retention-days: 30 - name: Publish packaged artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: ${{ always() }} with: name: package-files 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 300076fb84..5acbd1b581 100644 --- a/.mailmap +++ b/.mailmap @@ -20,3 +20,6 @@ Tobias Feldballe Tobias Feldballe Jonas Borchelt Derek Frogget <114030121+derekfrogget@users.noreply.github.com> +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 00d3715d85..8666083783 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Goals ----- * Fully authentic look and feel - * This can only be approximated, since the behaviour of the original game is mostly undocumented, + * This can only be approximated since the behavior of the original game is mostly undocumented, and guessing/experimenting can only get you this close * We will not implement useless artificial limitations (max 30 selectable units...) * An easily-moddable content format: [**nyan** yet another notation](https://github.com/SFTtech/nyan) @@ -79,7 +79,7 @@ Current State of the Project **Important notice**: At the moment, "gameplay" is basically non-functional. We're implementing the internal game simulation (how units even do anything) with simplicity and extensibility in mind, so we had to get rid of the temporary (but kind of working) previous version. -With these changes we can (finally) actually make use of our converted asset packs and our nyan API! +With these changes, we can (finally) actually make use of our converted asset packs and our nyan API! We're working day and night to make gameplay return\*. If you're interested, we wrote detailed explanations on our blog: [Part 1](https://blog.openage.dev/new-gamestate-2020.html), [Part 2](https://blog.openage.dev/engine-core-modules.html), [Monthly Devlog](https://blog.openage.dev/tag/news.html). @@ -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) | @@ -99,16 +99,16 @@ If you're interested, we wrote detailed explanations on our blog: [Part 1](https Installation Packages --------------------- -There's many missing parts for an actually working game. +There are many missing parts for an actually working game. So if you "just wanna play", [you'll be disappointed](#current-state-of-the-project), unfortunately. -We strongly recommend to build the program from source to get the latest, greatest and shiniest project state :) +We strongly recommend building the program from source to get the latest, greatest, and shiniest project state :) -* For **Linux** check at [repology](https://repology.org/project/openage/versions) if your distribution has any packages available. Otherwise you need to build from source. - We don't release `*.deb`, `*.rpm`, flatpack, snap or AppImage packages yet. +* For **Linux** check at [repology](https://repology.org/project/openage/versions) if your distribution has any packages available. Otherwise, you need to build from source. + We don't release `*.deb`, `*.rpm`, Flatpak, snap or AppImage packages yet. * For **Windows** check our [release page](https://github.com/SFTtech/openage/releases) for the latest installer. - Otherwise, you need to build from source. + Otherwise, you need to build from the source. * For **macOS** we currently don't have any packages, you need to build from source. @@ -127,8 +127,11 @@ 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 `./bin/run`. + * 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. * Use your brain and react to the things you'll see. @@ -145,14 +148,13 @@ 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 ============ You might ask yourself now "Sounds cool, but how do I participate and ~~get famous~~ contribute useful features?". -Fortunately for you, there is a lot to do and we are very grateful for help. +Fortunately for you, there is a lot to do and we are very grateful for your help. ## Where do I start? @@ -160,7 +162,7 @@ Fortunately for you, there is a lot to do and we are very grateful for help. * **Ask us** in the [chat](https://matrix.to/#/#sfttech:matrix.org). Someone there could need help with something. * You can also **take the initiative** and fix a bug you found, create an issue for discussion or - implement a feature that we never though of, but always wanted. + implement a feature that we never thought of, but always wanted. ## Ok, I found something. What now? @@ -176,7 +178,7 @@ Fortunately for you, there is a lot to do and we are very grateful for help. ## How do I contribute my features/changes? * Read the **[contributing guide](/doc/contributing.md)**. -* You can upload work in progress (WIP) versions or drafts of your contribution to get feedback or support. +* You can upload work-in-progress (WIP) versions or drafts of your contribution to get feedback or support. * Tell us (again) when you want us to review your work. ## I want to help, but I'm not a programmer... diff --git a/assets/shaders/hud_drag_select.frag.glsl b/assets/shaders/hud_drag_select.frag.glsl new file mode 100644 index 0000000000..a6a36e5a1d --- /dev/null +++ b/assets/shaders/hud_drag_select.frag.glsl @@ -0,0 +1,10 @@ +#version 330 + +// Color of the drag rectangle +uniform vec4 in_col; + +layout(location=0) out vec4 out_col; + +void main() { + out_col = in_col; +} diff --git a/assets/shaders/hud_drag_select.vert.glsl b/assets/shaders/hud_drag_select.vert.glsl new file mode 100644 index 0000000000..527e0bb652 --- /dev/null +++ b/assets/shaders/hud_drag_select.vert.glsl @@ -0,0 +1,7 @@ +#version 330 + +layout(location=0) in vec2 position; + +void main() { + gl_Position = vec4(position, 0.0, 1.0); +} diff --git a/assets/shaders/terrain.vert.glsl b/assets/shaders/terrain.vert.glsl index aef9b45867..614f6f382b 100644 --- a/assets/shaders/terrain.vert.glsl +++ b/assets/shaders/terrain.vert.glsl @@ -7,9 +7,17 @@ out vec2 tex_pos; uniform mat4 model; +// camera parameters for transforming the object position +// and scaling the subtex to the correct size layout (std140) uniform camera { - mat4 view; - mat4 proj; + // view matrix (world to view space) + mat4 view; + // projection matrix (view to clip space) + mat4 proj; + // inverse zoom factor (1.0 / zoom) + float inv_zoom; + // inverse viewport size (1.0 / viewport size) + vec2 inv_viewport_size; }; void main() { diff --git a/assets/shaders/world2d.vert.glsl b/assets/shaders/world2d.vert.glsl index 7e01b5a414..7987d40ec3 100644 --- a/assets/shaders/world2d.vert.glsl +++ b/assets/shaders/world2d.vert.glsl @@ -5,10 +5,19 @@ layout(location=1) in vec2 uv; out vec2 vert_uv; -// transformation for object (not vertex!) position to clip space +// camera parameters for transforming the object position +// and scaling the subtex to the correct size layout (std140) uniform camera { - mat4 view; - mat4 proj; + // view matrix (world to view space) + mat4 view; + // projection matrix (view to clip space) + mat4 proj; + // inverse zoom factor (1.0 / zoom) + // high zoom = upscale subtex + // low zoom = downscale subtex + float inv_zoom; + // inverse viewport size (1.0 / viewport size) + vec2 inv_viewport_size; }; // can be used to move the object position in world space _before_ @@ -27,33 +36,66 @@ uniform bool flip_y; // parameters for scaling and moving the subtex // to the correct position in clip space -// offset from the subtex anchor -// moves the subtex relative to the subtex center -uniform vec2 anchor_offset; - +// animation scalefactor // scales the vertex positions so that they // match the subtex dimensions -uniform vec2 scale; +// +// high animation scale = downscale subtex +// low animation scale = upscale subtex +uniform float scale; + +// size of the subtex (in pixels) +uniform vec2 subtex_size; + +// offset of the subtex anchor point +// from the subtex center (in pixels) +// used to move the subtex so that the anchor point +// is at the object position +uniform vec2 anchor_offset; void main() { // translate the position of the object from world space to clip space // this is the position where we want to draw the subtex in 2D vec4 obj_clip_pos = proj * view * model * vec4(obj_world_position, 1.0); + // subtex has to be scaled to account for the zoom factor + // and the animation scale factor. essentially this is (animation scale / zoom). + float zoom_scale = scale * inv_zoom; + + // Scale the subtex vertices + // we have to account for the viewport size to get the correct dimensions + // and then scale the subtex to the zoom factor to get the correct size + vec2 vert_scale = zoom_scale * subtex_size * inv_viewport_size; + + // Scale the anchor offset with the same method as above + // to get the correct anchor position in the viewport + vec2 anchor_scale = zoom_scale * anchor_offset * inv_viewport_size; + + // if the subtex is flipped, we also need to flip the anchor offset + // essentially, we invert the coordinates for the flipped axis + float anchor_x = float(flip_x) * -1.0 * anchor_scale.x + float(!flip_x) * anchor_scale.x; + float anchor_y = float(flip_y) * -1.0 * anchor_scale.y + float(!flip_y) * anchor_scale.y; + // offset the clip position by the offset of the subtex anchor - // this basically aligns the object position and the subtex anchor - obj_clip_pos += vec4(anchor_offset.xy, 0.0, 0.0); + // imagine this as pinning the subtex to the object position at the subtex anchor point + obj_clip_pos += vec4(anchor_x, anchor_y, 0.0, 0.0); // create a move matrix for positioning the vertices - // uses the scale and the transformed object position in clip space - mat4 move = mat4(scale.x, 0.0, 0.0, 0.0, - 0.0, scale.y, 0.0, 0.0, - 0.0, 0.0, 1.0, 0.0, + // uses the vert scale and the transformed object position in clip space + mat4 move = mat4(vert_scale.x, 0.0, 0.0, 0.0, + 0.0, vert_scale.y, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, obj_clip_pos.x, obj_clip_pos.y, obj_clip_pos.z, 1.0); - // finally calculate the vertex position + // calculate the final vertex position gl_Position = move * vec4(v_position, 0.0, 1.0); + + // if the subtex is flipped, we also need to flip the uv tex coordinates + // essentially, we invert the coordinates for the flipped axis + + // !flip_x is default because OpenGL uses bottom-left as its origin float uv_x = float(!flip_x) * uv.x + float(flip_x) * (1.0 - uv.x); float uv_y = float(flip_y) * uv.y + float(!flip_y) * (1.0 - uv.y); + vert_uv = vec2(uv_x, uv_y); } diff --git a/assets/test/shaders/demo_6_2d.frag.glsl b/assets/test/shaders/demo_6_2d.frag.glsl new file mode 100644 index 0000000000..c4beb1dd8e --- /dev/null +++ b/assets/test/shaders/demo_6_2d.frag.glsl @@ -0,0 +1,37 @@ +#version 330 + +in vec2 vert_uv; + +layout(location=0) out vec4 col; + +uniform sampler2D tex; + +// position (top left corner) and size: (x, y, width, height) +uniform vec4 tile_params; + +vec2 uv = vec2( + vert_uv.x * tile_params.z + tile_params.x, + vert_uv.y * tile_params.w + tile_params.y +); + +void main() { + vec4 tex_val = texture(tex, uv); + int alpha = int(round(tex_val.a * 255)); + switch (alpha) { + case 0: + col = tex_val; + discard; + case 254: + col = vec4(1.0f, 0.0f, 0.0f, 1.0f); + break; + case 252: + col = vec4(0.0f, 1.0f, 0.0f, 1.0f); + break; + case 250: + col = vec4(0.0f, 0.0f, 1.0f, 1.0f); + break; + default: + col = tex_val; + break; + } +} diff --git a/assets/test/shaders/demo_6_2d.vert.glsl b/assets/test/shaders/demo_6_2d.vert.glsl new file mode 100644 index 0000000000..6a17baf834 --- /dev/null +++ b/assets/test/shaders/demo_6_2d.vert.glsl @@ -0,0 +1,83 @@ +#version 330 + +layout(location=0) in vec2 v_position; +layout(location=1) in vec2 uv; + +out vec2 vert_uv; + +// camera parameters for transforming the object position +// and scaling the subtex to the correct size +layout (std140) uniform camera { + // view matrix (world to view space) + mat4 view; + // projection matrix (view to clip space) + mat4 proj; + // inverse zoom factor (1.0 / zoom) + // high zoom = upscale subtex + // low zoom = downscale subtex + float inv_zoom; + // inverse viewport size (1.0 / viewport size) + vec2 inv_viewport_size; +}; + +// position of the object in world space +uniform vec3 obj_world_position; + +// parameters for scaling and moving the subtex +// to the correct position in clip space + +// animation scalefactor +// scales the vertex positions so that they +// match the subtex dimensions +// +// high animation scale = downscale subtex +// low animation scale = upscale subtex +uniform float scale; + +// size of the subtex (in pixels) +uniform vec2 subtex_size; + +// offset of the subtex anchor point +// from the subtex center (in pixels) +// used to move the subtex so that the anchor point +// is at the object position +uniform vec2 anchor_offset; + +void main() { + // translate the position of the object from world space to clip space + // this is the position where we want to draw the subtex in 2D + vec4 obj_clip_pos = proj * view * vec4(obj_world_position, 1.0); + + // subtex has to be scaled to account for the zoom factor + // and the animation scale factor. essentially this is (animation scale / zoom). + float zoom_scale = scale * inv_zoom; + + // Scale the subtex vertices + // we have to account for the viewport size to get the correct dimensions + // and then scale the subtex to the zoom factor to get the correct size + vec2 vert_scale = zoom_scale * subtex_size * inv_viewport_size; + + // Scale the anchor offset with the same method as above + // to get the correct anchor position in the viewport + vec2 anchor_scale = zoom_scale * anchor_offset * inv_viewport_size; + + // offset the clip position by the offset of the subtex anchor + // imagine this as pinning the subtex to the object position at the subtex anchor point + obj_clip_pos += vec4(anchor_scale.x, anchor_scale.y, 0.0, 0.0); + + // create a move matrix for positioning the vertices + // uses the vert scale and the transformed object position in clip space + mat4 move = mat4(vert_scale.x, 0.0, 0.0, 0.0, + 0.0, vert_scale.y, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + obj_clip_pos.x, obj_clip_pos.y, obj_clip_pos.z, 1.0); + + // calculate the final vertex position + gl_Position = move * vec4(v_position, 0.0, 1.0); + + // flip y axis because OpenGL uses bottom-left as its origin + float uv_x = uv.x; + float uv_y = 1.0 - uv.y; + + vert_uv = vec2(uv_x, uv_y); +} diff --git a/assets/test/shaders/demo_6_2d_frame.frag.glsl b/assets/test/shaders/demo_6_2d_frame.frag.glsl new file mode 100644 index 0000000000..747ed38e5a --- /dev/null +++ b/assets/test/shaders/demo_6_2d_frame.frag.glsl @@ -0,0 +1,9 @@ +#version 330 + +out vec4 outcol; + +uniform vec4 incol; + +void main() { + outcol = incol; +} diff --git a/assets/test/shaders/demo_6_2d_frame.vert.glsl b/assets/test/shaders/demo_6_2d_frame.vert.glsl new file mode 100644 index 0000000000..8d7d13b6be --- /dev/null +++ b/assets/test/shaders/demo_6_2d_frame.vert.glsl @@ -0,0 +1,58 @@ +#version 330 + +layout(location=0) in vec2 v_position; + +// camera parameters for transforming the object position +// and scaling the subtex to the correct size +layout (std140) uniform camera { + // view matrix (world to view space) + mat4 view; + // projection matrix (view to clip space) + mat4 proj; + // inverse zoom factor (1.0 / zoom) + float inv_zoom; + // inverse viewport size (1.0 / viewport size) + vec2 inv_viewport_size; +}; + +// position of the object in world space +uniform vec3 obj_world_position; + +// parameters for scaling and moving the subtex +// to the correct position in clip space + +// animation scalefactor +// scales the vertex positions so that they +// match the subtex dimensions +// +// high animation scale = downscale subtex +// low animation scale = upscale subtex +uniform float scale; + +// size of the frame (in pixels) +uniform vec2 frame_size; + +void main() { + // translate the position of the object from world space to clip space + // this is the position where we want to draw the subtex in 2D + vec4 obj_clip_pos = proj * view * vec4(obj_world_position, 1.0); + + // subtex has to be scaled to account for the zoom factor + // and the animation scale factor. essentially this is (animation scale / zoom). + float zoom_scale = scale * inv_zoom; + + // Scale the subtex vertices + // we have to account for the viewport size to get the correct dimensions + // and then scale the frame to the zoom factor to get the correct size + vec2 vert_scale = zoom_scale * frame_size * inv_viewport_size; + + // create a move matrix for positioning the vertices + // uses the vert scale and the transformed object position in clip space + mat4 move = mat4(vert_scale.x, 0.0, 0.0, 0.0, + 0.0, vert_scale.y, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + obj_clip_pos.x, obj_clip_pos.y, obj_clip_pos.z, 1.0); + + // calculate the final vertex position + gl_Position = move * vec4(v_position, 0.0, 1.0); +} diff --git a/assets/test/shaders/demo_6_2d_frustum_frame.vert.glsl b/assets/test/shaders/demo_6_2d_frustum_frame.vert.glsl new file mode 100644 index 0000000000..8c5556f713 --- /dev/null +++ b/assets/test/shaders/demo_6_2d_frustum_frame.vert.glsl @@ -0,0 +1,8 @@ +#version 330 + +layout(location=0) in vec2 v_position; + +void main() { + // flip the y coordinate in OpenGL + gl_Position = vec4(v_position.x, v_position.y, 0.0, 1.0); +} diff --git a/assets/test/shaders/demo_6_3d.frag.glsl b/assets/test/shaders/demo_6_3d.frag.glsl new file mode 100644 index 0000000000..f2d3c71bd8 --- /dev/null +++ b/assets/test/shaders/demo_6_3d.frag.glsl @@ -0,0 +1,13 @@ +#version 330 + +in vec2 tex_pos; + +layout(location=0) out vec4 out_col; + +uniform sampler2D tex; + +void main() +{ + vec4 tex_val = texture(tex, tex_pos); + out_col = tex_val; +} diff --git a/assets/test/shaders/demo_6_3d.vert.glsl b/assets/test/shaders/demo_6_3d.vert.glsl new file mode 100644 index 0000000000..95a552dcd6 --- /dev/null +++ b/assets/test/shaders/demo_6_3d.vert.glsl @@ -0,0 +1,24 @@ +#version 330 + +layout (location = 0) in vec3 position; +layout (location = 1) in vec2 uv; + +out vec2 tex_pos; + +// camera parameters for transforming the object position +// and scaling the subtex to the correct size +layout (std140) uniform camera { + // view matrix (world to view space) + mat4 view; + // projection matrix (view to clip space) + mat4 proj; + // inverse zoom factor (1.0 / zoom) + float inv_zoom; + // inverse viewport size (1.0 / viewport size) + vec2 inv_viewport_size; +}; + +void main() { + gl_Position = proj * view * vec4(position, 1.0); + tex_pos = vec2(uv.x, 1.0 - uv.y); +} diff --git a/assets/test/shaders/demo_6_display.frag.glsl b/assets/test/shaders/demo_6_display.frag.glsl new file mode 100644 index 0000000000..a6732d0c7f --- /dev/null +++ b/assets/test/shaders/demo_6_display.frag.glsl @@ -0,0 +1,10 @@ +#version 330 + +uniform sampler2D color_texture; + +in vec2 v_uv; +out vec4 col; + +void main() { + col = texture(color_texture, v_uv); +} diff --git a/assets/test/shaders/demo_6_display.vert.glsl b/assets/test/shaders/demo_6_display.vert.glsl new file mode 100644 index 0000000000..072e5d80b0 --- /dev/null +++ b/assets/test/shaders/demo_6_display.vert.glsl @@ -0,0 +1,10 @@ +#version 330 + +layout(location=0) in vec2 position; +layout(location=1) in vec2 uv; +out vec2 v_uv; + +void main() { + gl_Position = vec4(position, 0.0, 1.0); + v_uv = uv; +} 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/assets/test/shaders/pathfinding/CMakeLists.txt b/assets/test/shaders/pathfinding/CMakeLists.txt new file mode 100644 index 0000000000..2d30f59426 --- /dev/null +++ b/assets/test/shaders/pathfinding/CMakeLists.txt @@ -0,0 +1,4 @@ +install(DIRECTORY "." + DESTINATION "${ASSET_DIR}/test/shaders/pathfinding" + FILES_MATCHING PATTERN "*.glsl" +) diff --git a/assets/test/shaders/pathfinding/demo_0_cost_field.frag.glsl b/assets/test/shaders/pathfinding/demo_0_cost_field.frag.glsl new file mode 100644 index 0000000000..7ccb86dcf4 --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_0_cost_field.frag.glsl @@ -0,0 +1,17 @@ +#version 330 + +in float v_cost; + +out vec4 out_col; + +void main() +{ + if (v_cost == 255.0) { + out_col = vec4(0.0, 0.0, 0.0, 1.0); + return; + } + float cost = (v_cost / 256) * 2.0; + float red = clamp(cost, 0.0, 1.0); + float green = clamp(2.0 - cost, 0.0, 1.0); + out_col = vec4(red, green, 0.0, 1.0); +} diff --git a/assets/test/shaders/pathfinding/demo_0_cost_field.vert.glsl b/assets/test/shaders/pathfinding/demo_0_cost_field.vert.glsl new file mode 100644 index 0000000000..d12f0db1d9 --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_0_cost_field.vert.glsl @@ -0,0 +1,15 @@ +#version 330 + +layout (location = 0) in vec3 position; +layout (location = 1) in float cost; + +uniform mat4 model; +uniform mat4 view; +uniform mat4 proj; + +out float v_cost; + +void main() { + gl_Position = proj * view * model * vec4(position, 1.0); + v_cost = cost; +} diff --git a/assets/test/shaders/pathfinding/demo_0_display.frag.glsl b/assets/test/shaders/pathfinding/demo_0_display.frag.glsl new file mode 100644 index 0000000000..a6732d0c7f --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_0_display.frag.glsl @@ -0,0 +1,10 @@ +#version 330 + +uniform sampler2D color_texture; + +in vec2 v_uv; +out vec4 col; + +void main() { + col = texture(color_texture, v_uv); +} diff --git a/assets/test/shaders/pathfinding/demo_0_display.vert.glsl b/assets/test/shaders/pathfinding/demo_0_display.vert.glsl new file mode 100644 index 0000000000..6112530242 --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_0_display.vert.glsl @@ -0,0 +1,10 @@ +#version 330 + +layout(location=0) in vec2 position; +layout(location=1) in vec2 uv; +out vec2 v_uv; + +void main() { + gl_Position = vec4(position, 0.0, 1.0); + v_uv = uv; +} diff --git a/assets/test/shaders/pathfinding/demo_0_flow_field.frag.glsl b/assets/test/shaders/pathfinding/demo_0_flow_field.frag.glsl new file mode 100644 index 0000000000..b003945619 --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_0_flow_field.frag.glsl @@ -0,0 +1,38 @@ +#version 330 + +/// Flow field value +in float flow_val; + +/// Integration field flags +in float int_val; + +out vec4 outcol; + +int WAVEFRONT_BLOCKED = 0x04; +int LINE_OF_SIGHT = 0x20; +int PATHABLE = 0x10; + +void main() { + int flow_flags = int(flow_val) & 0xF0; + int int_flags = int(int_val); + if (bool(int_flags & WAVEFRONT_BLOCKED)) { + // wavefront blocked + outcol = vec4(0.9, 0.9, 0.9, 1.0); + return; + } + + if (bool(int_flags & LINE_OF_SIGHT)) { + // line of sight + outcol = vec4(1.0, 1.0, 1.0, 1.0); + return; + } + + if (bool(flow_flags & PATHABLE)) { + // pathable + outcol = vec4(0.7, 0.7, 0.7, 1.0); + return; + } + + // not pathable + outcol = vec4(0.0, 0.0, 0.0, 1.0); +} diff --git a/assets/test/shaders/pathfinding/demo_0_flow_field.vert.glsl b/assets/test/shaders/pathfinding/demo_0_flow_field.vert.glsl new file mode 100644 index 0000000000..03900a8daf --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_0_flow_field.vert.glsl @@ -0,0 +1,18 @@ +#version 330 + +layout(location=0) in vec3 position; +layout(location=1) in float flow_cell; +layout(location=2) in float int_cell; + +uniform mat4 model; +uniform mat4 view; +uniform mat4 proj; + +out float flow_val; +out float int_val; + +void main() { + gl_Position = proj * view * model * vec4(position, 1.0); + flow_val = flow_cell; + int_val = int_cell; +} diff --git a/assets/test/shaders/pathfinding/demo_0_grid.frag.glsl b/assets/test/shaders/pathfinding/demo_0_grid.frag.glsl new file mode 100644 index 0000000000..19d2e459c3 --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_0_grid.frag.glsl @@ -0,0 +1,8 @@ +#version 330 + +out vec4 out_col; + +void main() +{ + out_col = vec4(0.0, 0.0, 0.0, 0.3); +} diff --git a/assets/test/shaders/pathfinding/demo_0_grid.vert.glsl b/assets/test/shaders/pathfinding/demo_0_grid.vert.glsl new file mode 100644 index 0000000000..2f5eaeaa15 --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_0_grid.vert.glsl @@ -0,0 +1,11 @@ +#version 330 + +layout (location = 0) in vec3 position; + +uniform mat4 model; +uniform mat4 view; +uniform mat4 proj; + +void main() { + gl_Position = proj * view * model * vec4(position, 1.0); +} diff --git a/assets/test/shaders/pathfinding/demo_0_integration_field.frag.glsl b/assets/test/shaders/pathfinding/demo_0_integration_field.frag.glsl new file mode 100644 index 0000000000..d40715a338 --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_0_integration_field.frag.glsl @@ -0,0 +1,16 @@ +#version 330 + +in float v_cost; + +out vec4 out_col; + +void main() +{ + if (v_cost > 512.0) { + out_col = vec4(0.0, 0.0, 0.0, 1.0); + return; + } + float cost = 0.05 * v_cost; + float green = clamp(1.0 - cost, 0.0, 1.0); + out_col = vec4(0.75, green, 0.5, 1.0); +} diff --git a/assets/test/shaders/pathfinding/demo_0_integration_field.vert.glsl b/assets/test/shaders/pathfinding/demo_0_integration_field.vert.glsl new file mode 100644 index 0000000000..d12f0db1d9 --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_0_integration_field.vert.glsl @@ -0,0 +1,15 @@ +#version 330 + +layout (location = 0) in vec3 position; +layout (location = 1) in float cost; + +uniform mat4 model; +uniform mat4 view; +uniform mat4 proj; + +out float v_cost; + +void main() { + gl_Position = proj * view * model * vec4(position, 1.0); + v_cost = cost; +} diff --git a/assets/test/shaders/pathfinding/demo_0_obj.frag.glsl b/assets/test/shaders/pathfinding/demo_0_obj.frag.glsl new file mode 100644 index 0000000000..ebbb10bc8f --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_0_obj.frag.glsl @@ -0,0 +1,9 @@ +#version 330 + +uniform vec4 color; + +out vec4 outcol; + +void main() { + outcol = color; +} diff --git a/assets/test/shaders/pathfinding/demo_0_obj.vert.glsl b/assets/test/shaders/pathfinding/demo_0_obj.vert.glsl new file mode 100644 index 0000000000..527e0bb652 --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_0_obj.vert.glsl @@ -0,0 +1,7 @@ +#version 330 + +layout(location=0) in vec2 position; + +void main() { + gl_Position = vec4(position, 0.0, 1.0); +} diff --git a/assets/test/shaders/pathfinding/demo_0_vector.frag.glsl b/assets/test/shaders/pathfinding/demo_0_vector.frag.glsl new file mode 100644 index 0000000000..ebbb10bc8f --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_0_vector.frag.glsl @@ -0,0 +1,9 @@ +#version 330 + +uniform vec4 color; + +out vec4 outcol; + +void main() { + outcol = color; +} diff --git a/assets/test/shaders/pathfinding/demo_0_vector.vert.glsl b/assets/test/shaders/pathfinding/demo_0_vector.vert.glsl new file mode 100644 index 0000000000..8b6b015408 --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_0_vector.vert.glsl @@ -0,0 +1,11 @@ +#version 330 + +layout(location=0) in vec3 position; + +uniform mat4 model; +uniform mat4 view; +uniform mat4 proj; + +void main() { + gl_Position = proj * view * model * vec4(position, 1.0); +} diff --git a/assets/test/shaders/pathfinding/demo_1_display.frag.glsl b/assets/test/shaders/pathfinding/demo_1_display.frag.glsl new file mode 100644 index 0000000000..a6732d0c7f --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_1_display.frag.glsl @@ -0,0 +1,10 @@ +#version 330 + +uniform sampler2D color_texture; + +in vec2 v_uv; +out vec4 col; + +void main() { + col = texture(color_texture, v_uv); +} diff --git a/assets/test/shaders/pathfinding/demo_1_display.vert.glsl b/assets/test/shaders/pathfinding/demo_1_display.vert.glsl new file mode 100644 index 0000000000..6112530242 --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_1_display.vert.glsl @@ -0,0 +1,10 @@ +#version 330 + +layout(location=0) in vec2 position; +layout(location=1) in vec2 uv; +out vec2 v_uv; + +void main() { + gl_Position = vec4(position, 0.0, 1.0); + v_uv = uv; +} diff --git a/assets/test/shaders/pathfinding/demo_1_grid.frag.glsl b/assets/test/shaders/pathfinding/demo_1_grid.frag.glsl new file mode 100644 index 0000000000..19d2e459c3 --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_1_grid.frag.glsl @@ -0,0 +1,8 @@ +#version 330 + +out vec4 out_col; + +void main() +{ + out_col = vec4(0.0, 0.0, 0.0, 0.3); +} diff --git a/assets/test/shaders/pathfinding/demo_1_grid.vert.glsl b/assets/test/shaders/pathfinding/demo_1_grid.vert.glsl new file mode 100644 index 0000000000..cbf6712eef --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_1_grid.vert.glsl @@ -0,0 +1,11 @@ +#version 330 + +layout (location = 0) in vec2 position; + +uniform mat4 model; +uniform mat4 view; +uniform mat4 proj; + +void main() { + gl_Position = proj * view * model * vec4(position, 0.0, 1.0); +} diff --git a/assets/test/shaders/pathfinding/demo_1_obj.frag.glsl b/assets/test/shaders/pathfinding/demo_1_obj.frag.glsl new file mode 100644 index 0000000000..ebbb10bc8f --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_1_obj.frag.glsl @@ -0,0 +1,9 @@ +#version 330 + +uniform vec4 color; + +out vec4 outcol; + +void main() { + outcol = color; +} diff --git a/assets/test/shaders/pathfinding/demo_1_obj.vert.glsl b/assets/test/shaders/pathfinding/demo_1_obj.vert.glsl new file mode 100644 index 0000000000..bf804ffe53 --- /dev/null +++ b/assets/test/shaders/pathfinding/demo_1_obj.vert.glsl @@ -0,0 +1,11 @@ +#version 330 + +layout(location=0) in vec2 position; + +uniform mat4 model; +uniform mat4 view; +uniform mat4 proj; + +void main() { + gl_Position = proj * view * model * vec4(position, 0.0, 1.0); +} diff --git a/assets/test/textures/test_animation.sprite b/assets/test/textures/test_animation.sprite index 26481ff896..9f96f91b65 100644 --- a/assets/test/textures/test_animation.sprite +++ b/assets/test/textures/test_animation.sprite @@ -5,7 +5,7 @@ version 2 texture 0 "test_texture.texture" -scalefactor 1 +scalefactor 1.0 layer 0 mode=loop position=20 time_per_frame=0.125 diff --git a/assets/test/textures/test_missing.sprite b/assets/test/textures/test_missing.sprite index 00a166e27f..84b7b66676 100644 --- a/assets/test/textures/test_missing.sprite +++ b/assets/test/textures/test_missing.sprite @@ -5,7 +5,7 @@ version 2 texture 0 "test_missing.texture" -scalefactor 1 +scalefactor 1.0 layer 0 mode=once diff --git a/assets/test/textures/test_terrain.terrain b/assets/test/textures/test_terrain.terrain index bbcc8dcd7c..7588a69194 100644 --- a/assets/test/textures/test_terrain.terrain +++ b/assets/test/textures/test_terrain.terrain @@ -5,7 +5,7 @@ version 2 texture 0 "test_terrain.texture" -scalefactor 1 +scalefactor 1.0 layer 0 diff --git a/assets/test/textures/test_terrain2.png b/assets/test/textures/test_terrain2.png new file mode 100644 index 0000000000..100205bfa2 Binary files /dev/null and b/assets/test/textures/test_terrain2.png differ diff --git a/assets/test/textures/test_terrain2.terrain b/assets/test/textures/test_terrain2.terrain new file mode 100644 index 0000000000..2ac912d439 --- /dev/null +++ b/assets/test/textures/test_terrain2.terrain @@ -0,0 +1,12 @@ +# Copyright 2023-2023 the openage authors. See copying.md for legal info. +# openage terrain definition file + +version 2 + +texture 0 "test_terrain2.texture" + +scalefactor 1.0 + +layer 0 + +frame 0 0 0 0 diff --git a/assets/test/textures/test_terrain2.texture b/assets/test/textures/test_terrain2.texture new file mode 100644 index 0000000000..a2d431bd63 --- /dev/null +++ b/assets/test/textures/test_terrain2.texture @@ -0,0 +1,12 @@ +# Copyright 2023-2023 the openage authors. +# openage texture definition file + +version 1 + +imagefile "test_terrain2.png" + +size 500 500 + +pxformat rgba8 cbits=True + +subtex 0 0 500 500 0 0 diff --git a/buildsystem/DetectProjectVersion.cmake b/buildsystem/DetectProjectVersion.cmake index 8c45b1db64..67782509ec 100644 --- a/buildsystem/DetectProjectVersion.cmake +++ b/buildsystem/DetectProjectVersion.cmake @@ -12,7 +12,7 @@ endif() if(IS_DIRECTORY "${CMAKE_SOURCE_DIR}/.git") message(STATUS "Set PROJECT_VERSION from git.") execute_process( - COMMAND git describe --tags + COMMAND git describe --tags --long WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" RESULT_VARIABLE _RES OUTPUT_VARIABLE PROJECT_VERSION 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 dded8dd0a9..4d3237e36a 100644 --- a/buildsystem/codecompliance/__main__.py +++ b/buildsystem/codecompliance/__main__.py @@ -1,4 +1,4 @@ -# Copyright 2014-2023 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): """ @@ -231,7 +246,7 @@ def find_all_issues(args, check_files=None): if args.pystyle: from .pystyle import find_issues - yield from find_issues(check_files, ('openage', 'buildsystem')) + yield from find_issues(check_files, ('openage', 'buildsystem', 'etc/gdb_pretty')) if args.cython: from buildsystem.codecompliance.cython import find_issues @@ -243,12 +258,12 @@ def find_all_issues(args, check_files=None): if args.pylint: from .pylint import find_issues - yield from find_issues(check_files, ('openage', 'buildsystem')) + yield from find_issues(check_files, ('openage', 'buildsystem', 'etc/gdb_pretty')) if args.textfiles: from .textfiles import find_issues yield from find_issues( - ('openage', 'libopenage', 'buildsystem', 'doc', 'legal'), + ('openage', 'libopenage', 'buildsystem', 'doc', 'legal', 'etc/gdb_pretty'), ('.pxd', '.pyx', '.pxi', '.py', '.h', '.cpp', '.template', '', '.txt', '.md', '.conf', @@ -257,13 +272,16 @@ def find_all_issues(args, check_files=None): if args.legal: from .legal import find_issues yield from find_issues(check_files, - ('openage', 'buildsystem', 'libopenage'), + ('openage', 'buildsystem', 'libopenage', 'etc/gdb_pretty'), args.test_git_change_years) if args.filemodes: from .modes import find_issues yield from find_issues(check_files, ('openage', 'buildsystem', - 'libopenage')) + '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/authors.py b/buildsystem/codecompliance/authors.py index 502e3b2648..98b3a50e18 100644 --- a/buildsystem/codecompliance/authors.py +++ b/buildsystem/codecompliance/authors.py @@ -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. """ Checks whether all authors are properly listed in copying.md. @@ -39,7 +39,7 @@ def get_author_emails_copying_md(): """ with open("copying.md", encoding='utf8') as fobj: for line in fobj: - match = re.match("^.*\\|[^|]*\\|[^|]*\\|([^|]+)\\|.*$", line) + match = re.match(r"^.*\|[^|]*\|[^|]*\|([^|]+)\|.*$", line) if not match: continue 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/modules/FindSDL2Image.cmake b/buildsystem/modules/FindSDL2Image.cmake deleted file mode 100644 index 74b36be8ba..0000000000 --- a/buildsystem/modules/FindSDL2Image.cmake +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright 2014-2017 the openage authors. See copying.md for legal info. - -find_package(PackageHandleStandardArgs) - -if(APPLE) - find_library(SDL2IMAGE_LIBRARIES SDL2_image DOC "SDL2 library framework for MacOS X") - find_path(SDL2IMAGE_INCLUDE_DIRS SDL_image.h - HINTS - $ENV{SDL2DIR} - PATH_SUFFIXES include/SDL2 include - PATHS - ~/Library/Frameworks - /Library/Frameworks - /usr/local/include/SDL2 - /usr/include/SDL2 - /sw # Fink - /opt/local # DarwinPorts - /opt/csw # Blastwave - /opt - DOC "Include directory for SDL2_image under MacOS X" - ) -else() - find_library(SDL2IMAGE_LIBRARIES SDL2_image DOC "SDL2 library") - find_path(SDL2IMAGE_INCLUDE_DIRS SDL2/SDL_image.h DOC "Include directory for SDL2_image") -endif() - -# handle the QUIETLY and REQUIRED arguments and set SDL2Image_FOUND to TRUE if -# all listed variables are TRUE -find_package_handle_standard_args(SDL2Image DEFAULT_MSG SDL2IMAGE_LIBRARIES) 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/cfg/converter/games/game_editions.toml b/cfg/converter/games/game_editions.toml index 7b0331e81e..24628e8273 100644 --- a/cfg/converter/games/game_editions.toml +++ b/cfg/converter/games/game_editions.toml @@ -37,6 +37,11 @@ expansions = [] "C:/Program Files (x86)/Microsoft Games/Age of Empires II", ] + [AOC.targetmods.aoe2_base] + version = "0.5.1" + versionstr = "1.0c" + min_api_version = "0.5.0" + [AOCDEMO] name = "Age of Empires 2: The Conqueror's Trial Version" @@ -57,6 +62,12 @@ expansions = [] terrain = ["data/Terrain.drs"] blend = ["data/blendomatic.dat"] + [AOCDEMO.targetmods.trial_base] + version = "0.5.1" + versionstr = "Trial" + min_api_version = "0.5.0" + + [AOK] name = "Age of Empires 2: Age of Kings" game_edition_id = "AOK" @@ -90,6 +101,8 @@ expansions = [] "C:/Program Files (x86)/Microsoft Games/Age of Empires II", ] + [AOK.targetmods] + [AOE1DE] name = "Age of Empires 1: Definitive Edition (Steam)" @@ -131,6 +144,11 @@ expansions = [] "C:/Program Files (x86)/Steam/steamapps/common/AoEDE", ] + [AOE1DE.targetmods.de1_base] + version = "0.5.1" + versionstr = "1.0a" + min_api_version = "0.5.0" + [ROR] name = "Age of Empires 1: Rise of Rome" @@ -166,6 +184,11 @@ expansions = [] "C:/Program Files (x86)/Microsoft Games/Age of Empires", ] + [ROR.targetmods.aoe1_base] + version = "0.5.1" + versionstr = "1.0a" + min_api_version = "0.5.0" + [HDEDITION] name = "Age of Empires 2: HD Edition" @@ -205,6 +228,11 @@ expansions = [] "C:/Program Files (x86)/Steam/steamapps/common/Age2HD", ] + [HDEDITION.targetmods.hd_base] + version = "0.5.1" + versionstr = "5.8" + min_api_version = "0.5.0" + [AOE2DE] name = "Age of Empires 2: Definitive Edition" @@ -249,6 +277,11 @@ expansions = [] "C:/Program Files (x86)/Steam/steamapps/common/AoE2DE", ] + [AOE2DE.targetmods.de2_base] + version = "0.6.0" + versionstr = "Update 118476+" + min_api_version = "0.5.0" + [SWGB] name = "Star Wars: Galactic Battlegrounds" @@ -285,3 +318,8 @@ expansions = ["SWGB_CC"] "C:/Program Files (x86)/Steam/steamapps/common/STAR WARS - Galactic Battlegrounds Saga", "C:/Program Files/Steam/steamapps/common/STAR WARS - Galactic Battlegrounds Saga", ] + + [SWGB.targetmods.swgb_base] + version = "0.5.1" + versionstr = "1.1-gog4" + min_api_version = "0.5.0" diff --git a/cfg/converter/games/game_expansions.toml b/cfg/converter/games/game_expansions.toml index b3dd159c12..5aca31f780 100644 --- a/cfg/converter/games/game_expansions.toml +++ b/cfg/converter/games/game_expansions.toml @@ -3,31 +3,36 @@ file_version = "1.0" [AFRI_KING] -name = "African Kingdoms (HD)" +name = "African Kingdoms (HD)" game_edition_id = "AFRI_KING" -subfolder = "hd_ak" -support = "nope" -targetmod = [ "aoe2-ak", "aoe2-ak-graphics" ] +subfolder = "hd_ak" +support = "nope" +targetmod = ["aoe2-ak", "aoe2-ak-graphics"] [AFRI_KING.mediapaths] - graphics = [ "resources/_common/slp/" ] - sounds = [ "resources/_common/sound/" ] - interface = [ "resources/_common/drs/interface/" ] - terrain = [ "resources/_common/terrain/" ] + graphics = ["resources/_common/slp/"] + sounds = ["resources/_common/sound/"] + interface = ["resources/_common/drs/interface/"] + terrain = ["resources/_common/terrain/"] + + [AFRI_KING.targetmods] + [SWGB_CC] -name = "Clone Campaigns" +name = "Clone Campaigns" game_edition_id = "SWGB_CC" -subfolder = "swgb_cc" -support = "yes" -targetmod = [ "swgb-cc", "swgb-cc-graphics" ] +subfolder = "swgb_cc" +support = "yes" +targetmod = ["swgb-cc", "swgb-cc-graphics"] [SWGB_CC.mediapaths] - datfile = [ "Game/Data/genie_x1.dat" ] - gamedata = [ "Game/Data/genie_x1.dat" ] - graphics = [ "Game/Data/graphics_x1.drs" ] - language = [ "Game/language_x1.dll" ] - palettes = [ "Game/Data/interfac_x1.drs" ] - sounds = [ "Game/Data/sounds_x1.drs" ] - interface = [ "Game/Data/interfac_x1.drs" ] - terrain = [ "Game/Data/terrain_x1.drs" ] + datfile = ["Game/Data/genie_x1.dat"] + gamedata = ["Game/Data/genie_x1.dat"] + graphics = ["Game/Data/graphics_x1.drs"] + language = ["Game/language_x1.dll"] + palettes = ["Game/Data/interfac_x1.drs"] + sounds = ["Game/Data/sounds_x1.drs"] + interface = ["Game/Data/interfac_x1.drs"] + terrain = ["Game/Data/terrain_x1.drs"] + + [SWGB_CC.targetmods] diff --git a/copying.md b/copying.md index 310076a011..8c61b294e2 100644 --- a/copying.md +++ b/copying.md @@ -148,6 +148,25 @@ _the openage authors_ are: | Zoltán Ács | zoli111 | acszoltan111 à gmail dawt com | | Trevor Slocum | tslocum | trevor à rocket9labs dawt com | | Munawar Hafiz | munahaf | munawar dawt hafiz à gmail dawt com | +| Md Ashhar | ashhar | mdashhar01 à gmail dawt com | +| Fábio Barkoski | fabiobarkoski | fabiobarkoskii à gmail dawt com | +| Astitva Kamble | askastitva | astitvakamble5 à gmail dawt com | +| Haoyang Bi | AyiStar | ayistar à outlook dawt com | +| Michael Seibt | RoboSchmied | github à roboschmie dawt de | +| Nikhil Ghosh | NikhilGhosh75 | nghosh606 à gmail dawt com | +| 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/arch_linux.md b/doc/build_instructions/arch_linux.md index 7665cb9888..9c42ff64fd 100644 --- a/doc/build_instructions/arch_linux.md +++ b/doc/build_instructions/arch_linux.md @@ -4,7 +4,7 @@ This command should provide required packages from the Arch Linux repositories: -`sudo pacman -S --needed eigen python python-mako python-pillow python-numpy python-lz4 python-pygments cython libepoxy libogg libpng ttf-dejavu freetype2 fontconfig harfbuzz cmake sdl2 sdl2_image opusfile opus python-pylint python-toml qt6-declarative` +`sudo pacman -S --needed eigen python python-mako python-pillow python-numpy python-lz4 python-pygments cython libepoxy libogg libpng ttf-dejavu freetype2 fontconfig harfbuzz cmake opusfile opus python-pylint python-toml qt6-declarative qt6-multimedia` Additionally, you have to install [`toml11`](https://aur.archlinux.org/packages/toml11) from the AUR. If you have `yay`, you can run this command: diff --git a/doc/build_instructions/debian.md b/doc/build_instructions/debian.md index 5381ef165e..de74a192fc 100644 --- a/doc/build_instructions/debian.md +++ b/doc/build_instructions/debian.md @@ -1,6 +1,6 @@ # Prerequisite steps for Debian users - `sudo apt-get update` - - `sudo apt-get install cmake cython3 libeigen3-dev libepoxy-dev libfontconfig1-dev libfreetype-dev libharfbuzz-dev libogg-dev libopus-dev libopusfile-dev libpng-dev libsdl2-dev libsdl2-image-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` + - `sudo apt-get install 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. 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/fedora.md b/doc/build_instructions/fedora.md index c710a57282..e18e5f74c2 100644 --- a/doc/build_instructions/fedora.md +++ b/doc/build_instructions/fedora.md @@ -2,6 +2,6 @@ Run the following command: -`sudo dnf install clang cmake eigen3-devel fontconfig-devel gcc-c harfbuzz-devel libepoxy-devel libogg-devel libopusenc-devel libpng-devel opusfile-devel python3-Cython python3-devel python3-mako python3-numpy python3-lz4 python3-pillow python3-pygments python3-toml SDL2-devel SDL2_image-devel++ toml11-devel qt6-qtdeclarative-devel` +`sudo dnf install clang cmake eigen3-devel fontconfig-devel gcc-c harfbuzz-devel libepoxy-devel libogg-devel libopusenc-devel libpng-devel opusfile-devel python3-Cython python3-devel python3-mako python3-numpy python3-lz4 python3-pillow python3-pygments python3-toml toml11-devel qt6-qtdeclarative-devel qt6-qtmultimedia-devel` You will also need [nyan](https://github.com/SFTtech/nyan/blob/master/doc/building.md) and its dependencies. diff --git a/doc/build_instructions/freebsd.md b/doc/build_instructions/freebsd.md index b52d7e6177..fb53e1e9e2 100644 --- a/doc/build_instructions/freebsd.md +++ b/doc/build_instructions/freebsd.md @@ -2,7 +2,7 @@ This command should provide required packages for FreeBSD installation: -`sudo pkg install cmake cython eigen3 harfbuzz opus-tools opusfile png py-mako py-numpy py-lz4 py-pillow py-pygments py-toml pylint python qt6 sdl2 sdl2_image toml11` +`sudo pkg install cmake cython eigen3 harfbuzz opus-tools opusfile png py-mako py-numpy py-lz4 py-pillow py-pygments py-toml pylint python qt6 toml11` You will also need [nyan](https://github.com/SFTtech/nyan/blob/master/doc/building.md) and its dependencies. diff --git a/doc/build_instructions/macos.md b/doc/build_instructions/macos.md index ffe6e64f67..72458f6269 100644 --- a/doc/build_instructions/macos.md +++ b/doc/build_instructions/macos.md @@ -6,14 +6,10 @@ ``` brew update-reset && brew update -brew tap homebrew/cask-fonts -brew install font-dejavu -brew install cmake python3 libepoxy freetype fontconfig harfbuzz sdl2 sdl2_image opus opusfile qt6 libogg libpng toml11 eigen +brew install --cask font-dejavu +brew install cmake python3 libepoxy freetype fontconfig harfbuzz opus opusfile qt6 libogg libpng toml11 eigen brew install llvm -pip3 install cython numpy mako lz4 pillow pygments toml - -# optional, for documentation generation -brew install doxygen +pip3 install --upgrade --break-system-packages cython numpy mako lz4 pillow pygments setuptools toml ``` You will also need [nyan](https://github.com/SFTtech/nyan/blob/master/doc/building.md) and its dependencies: @@ -22,7 +18,14 @@ You will also need [nyan](https://github.com/SFTtech/nyan/blob/master/doc/buildi brew install flex make ``` +Optionally, for documentation generation: + +``` +brew install doxygen +``` + ## Clone the repository + ``` git clone https://github.com/SFTtech/openage cd openage @@ -30,8 +33,17 @@ cd openage ## Building +We advise against using the clang version that comes with macOS (Apple Clang) as it notoriously out of date and often causes compilation errors. Use homebrew's clang if you don't want any trouble. You can pass the path of homebrew clang to the openage `configure` script which will generate the CMake files for building: + +``` +# 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 +``` + +Afterwards, trigger the build using `make`: + ``` -./configure --compiler=$(which clang++) --mode=release --download-nyan make -j$(sysctl -n hw.ncpu) ``` @@ -40,7 +52,7 @@ make -j$(sysctl -n hw.ncpu) ## Running -`make run` or `./bin/run` launches the game. Try `./bin/run --help`! +`make run` or `cd bin && ./run` launches the game. Try `./run --help` if you don't know what to do! ## To create the documentation diff --git a/doc/build_instructions/nix.md b/doc/build_instructions/nix.md new file mode 100644 index 0000000000..e73497ebd6 --- /dev/null +++ b/doc/build_instructions/nix.md @@ -0,0 +1,78 @@ +# Building and developing on Nix based systems + +The openage repository is a [nix flake](https://wiki.nixos.org/wiki/Flakes) that +allow Nix users to easily build, install and develop openage. + +To build openage using nix + +1. make sure you have nix with flakes enabled, either by permanent system + configuration or by using command line flags, as described on + [the wiki](https://wiki.nixos.org/wiki/Flakes); +2. clone this repository and `cd` into it; +3. run `nix build .#openage` to start the build process; +4. the built artifact will be in `./result`. + +Nix will configure and build the source code automatically via the +`configurePhase` and `buildPhase` scripts, automatically provided by nix. + +If you want to build the derivation and run it immediately, you can use + +``` +nix run .#openage +``` + +instead of + +``` +nix build .#openage +./result/bin/openage +``` + +## Development + +You can get a development shell, with the required dependencies, by running + +``` +nix shell +``` + +This will download the same dependencies used for build and pop you in a +shell ready for use. You can call `configurePhase` and `buildPhase` to run +cmake configuration and building. + +Please note that nyan is downloaded from github as defined in `nix/nyan.nix`, +so if you want to provide nyan source tree, you'll need to modify that file +to use a path (such as `../../nyan`, relative to `nyan.nix`) instead of the +`fetchFromGitHub` function, for example: + +``` +# Clone both +git clone https://github.com/SFTtech/nyan +git clone https://github.com/SFTtech/openage +``` + +Then edit `openage/nix/nyan.nix` to use `../../nyan`, the result will be like + +``` +$ head -n18 openage/nix/nyan.nix + +``` +{ lib +, stdenv +, fetchFromGitHub +, clang +, cmake +, flex +}: +let + pname = "nyan"; + version = "0.3"; +in +stdenv.mkDerivation +{ + inherit pname version; + + src = ../../nyan; + + nativeBuildInputs = [ +``` diff --git a/doc/build_instructions/opensuse.md b/doc/build_instructions/opensuse.md index e2b51c41ef..8b967b1779 100644 --- a/doc/build_instructions/opensuse.md +++ b/doc/build_instructions/opensuse.md @@ -1,5 +1,5 @@ # Prerequisite steps for openSUSE users -- `zypper install --no-recommends cmake doxygen eigen3-devel fontconfig-devel gcc-c graphviz++ harfbuzz-devel libSDL2-devel libSDL2_image-devel libepoxy-devel libfreetype-dev libogg-devel libopus-devel libpng-devel libtoml11-dev libqt6-qtdeclarative-devel libqt6-qtquickcontrols opusfile-devel python3-Cython python3-Mako python3-lz4 python3-Pillow python3-Pygments python3-toml python3-devel` +- `zypper install --no-recommends cmake doxygen eigen3-devel fontconfig-devel gcc-c graphviz++ harfbuzz-devel libepoxy-devel libfreetype-dev libogg-devel libopus-devel libpng-devel libtoml11-dev qt6-declarative-dev qt6-quickcontrols2 qt6-multimedia-dev opusfile-devel python3-Cython python3-Mako python3-lz4 python3-Pillow python3-Pygments python3-toml python3-devel` You will also need [nyan](https://github.com/SFTtech/nyan/blob/master/doc/building.md) and its dependencies. diff --git a/doc/build_instructions/ubuntu.md b/doc/build_instructions/ubuntu.md index bc151975e2..46cd975fb3 100644 --- a/doc/build_instructions/ubuntu.md +++ b/doc/build_instructions/ubuntu.md @@ -2,16 +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 libsdl2-dev libsdl2-image-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` +```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 4c742e0e2c..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 sdl2 sdl2-image 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 game` - - 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 81d075eebb..923fde5507 100644 --- a/doc/building.md +++ b/doc/building.md @@ -29,15 +29,17 @@ 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 A python imaging library (PIL) -> pillow - RA toml - RA lz4 + RA setuptools (for python>=3.12 and cython<3.1) + A toml CR opengl >=3.3 CR libepoxy CR libpng + S clang-tidy R dejavu font CR eigen >=3 CR freetype2 @@ -47,15 +49,13 @@ Dependency list: CR nyan (https://github.com/SFTtech/nyan) CR O ncurses C mako - CR sdl2 - CR sdl2_image CR opusfile CRA opus CRA ogg S pycodestyle C pygments S pylint - CR qt6 >=6.2 (Core, Quick, QuickControls modules) + CR qt6 >=6.2 (Core, Quick, QuickControls, Multimedia modules) CR toml11 CR O vulkan @@ -85,9 +85,9 @@ described below for some of the most common ones: - [Arch Linux](build_instructions/arch_linux.md) - [FreeBSD](build_instructions/freebsd.md) - [Gentoo](build_instructions/gentoo.md) +- [Nix/NixOS](build_instructions/nix.md) - [Microsoft Windows](build_instructions/windows_msvc.md) - ### nyan installation `openage` depends on [`nyan`](https://github.com/SFTtech/nyan), which is the @@ -161,15 +161,14 @@ 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 - `make VERBOSE=1` -- My `SDL2_Image`/`Python`/whatever is installed somewhere, but `cmake` can't find it! +- My `Qt`/`Python`/whatever is installed somewhere, but `cmake` can't find it! - Run `ccmake` or `cmake-gui` in the build directory to see and change config variables. - You can manually tell `cmake` where to look. Try something along the lines of - - `./configure -- -DSDL2IMAGE_INCLUDE_DIRS=/whereever/sdl2_image/include/` + - `./configure -- -DPYTHON_INCLUDE_DIRS=/whereever/python/include/` - `-DPython3_EXECUTABLE=/your/py3/directory/` - I get compiler errors about missing header files 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/engine/v0.5.3.md b/doc/changelogs/engine/v0.5.3.md new file mode 100644 index 0000000000..a76735b68e --- /dev/null +++ b/doc/changelogs/engine/v0.5.3.md @@ -0,0 +1,55 @@ +# [0.5.3] - 2023-12-15 +All notable changes for version [0.5.3] 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) since release [0.4.0]. + +## Added + +- Temporary file/directory support for Python files +- More debug info in converter +- More fixed-point math functions +- Dependencies + - Qt Multimedia + - `setuptools` is now conditional dependency for Python >= 3.12 && Cython < 3.1 + +## Changed + +- Make `main` the default entrypoint command for openage binary + +## Removed + +- Legacy subsystem code + - Asset management (yes, there were 3 deprecated asset managers) + - `openage::AssetManager` + - `openage::LegacyAssetManager` + - `openage::presenter::AssetManager` + - Deprecated Coordinate types (`libopenage/coord`) + - CoordManager + - Deprecated transformations between types + - Gamedata dummy classes (`libopenage/gamedata`) + - Old gamestate + - Game logic (`libopenage/gamestate/old`) + - Unit handling (`libopenage/unit`) + - Old input system (`libopenage/input/legacy`) + - Old GUI (`libopenage/gui`) + - Old renderer + - Logic (`libopenage/presenter/legacy`) + - Data classes (texure, etc.) + - Old Terrain (`libopenage/terrain`) +- Dependencies + - SDL2 + - SDL2 Image + +## Fixed + +- Version tag format without `--long` crashes on tagged commits +- Dangling reference in modpack info file loading +- No graphics visible on Wayland +- Wrong anchor positions when sprite is mirrored +- Several typos in documentation + + +## Full commit log + +https://github.com/SFTtech/openage/compare/v0.5.2...v0.5.3 diff --git a/doc/changelogs/engine/v0.6.0.md b/doc/changelogs/engine/v0.6.0.md new file mode 100644 index 0000000000..18e981abc0 --- /dev/null +++ b/doc/changelogs/engine/v0.6.0.md @@ -0,0 +1,76 @@ +# [0.6.0] - 2024-11-26 +All notable changes for version [0.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) since release [0.4.0]. + +## Added + +- Flow field pathfinder +- Activity system for controlling the behavior of game entities +- Drag selection of game entities in the UI +- `clang-format` comment formatting +- Log messages for creation of uniform buffers +- nix flake +- GDB pretty printers for various internal data types + - Time types (`time::time_t`) + - Fixed point values + - Coordinate types + - openage arrays (`util::Vector`) + - Curve keyframes + - Flow field types +- Support for multiple meshes per terrain +- Frustum culling +- Example screenshots for the renderer demos +- Check for outdated modpacks on startup +- Camera boundaries to prevent camera movement outside of the map terrain +- Creation of temporary files/directories +- [Windows] Default paths for DLL searching on startup +- [Windows] DLL manager class in converter to support loading DLLs in multi-threaded conversion + +## Changed + +- Use multithreading for media export in the converter +- Rework curve container `Queue` to be more user-friendly + - `front(t)`/`pop_front(t)` now return the most recently added element before time `t` + - track lifetime of elements by storing insertion time (`alive` time) and erasure time (`dead` time) + - queue elements are now sorted by insertion time +- Curves now use `std::vector` for their keyframe storage to increase performance +- Window settings are passed as `struct` instead of arguments +- Sprite scaling is now handled in shader +- Replace `constexpr` with `consteval` where appropriate +- Optimize renderer + - Vectorize shader uniform (buffer) input storage + - Replace shared pointer usage with references +- Use raw pointers instead of shared pointers in pairing heap implementation + + +## Deprecated + +- Old pathfnder code (`libopenage/pathfinding`) + +## Removed + +- Exception propagation to Python with `_PyTraceback_Add` + +## Fixed + +- Bullet point formatting in event system documentation +- Uniform alignment in uniform buffers +- Input contexts are now handled in the correct order (top to bottom) +- Support for GCC 14 +- Support for Clang 19 +- DE2 conversion + - *The Mountain Royals* DLC + - *Battle for Greece* DLC +- Thread-safety of render entity +- Documentation for the single file converter +- Numerous documentation typos and mistakes +- [Windows] Order of inclusion for `DbgHelp.h` +- [Windows] Build instructions +- [macOS] Build instructions + + +## Full commit log + +https://github.com/SFTtech/openage/compare/v0.5.3...v0.6.0 diff --git a/doc/changelogs/nyan_api/v0.4.0.md b/doc/changelogs/nyan_api/v0.4.0.md index 5926977d97..4cf30034b1 100644 --- a/doc/changelogs/nyan_api/v0.4.0.md +++ b/doc/changelogs/nyan_api/v0.4.0.md @@ -19,7 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Rename `Entity` object to `Object` ## Added -### Utility module +### Ability module - Add `container : ResourceContainer` member to `Trade` ### Utility module @@ -56,4 +56,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Reference visualization -TBD +* [Gamedata](https://github.com/SFTtech/openage/blob/78051b7f894fdf7f7c6d44c05ac7239fe5a896cb/doc/nyan/aoe2_nyan_tree.svg) diff --git a/doc/changelogs/nyan_api/v0.4.1.md b/doc/changelogs/nyan_api/v0.4.1.md new file mode 100644 index 0000000000..d34ef0a04e --- /dev/null +++ b/doc/changelogs/nyan_api/v0.4.1.md @@ -0,0 +1,31 @@ +# [0.4.1] - 2023-12-02 +All notable changes for version [v0.4.1] 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 `Activity(Ability)` object; defines the behaviour of a game entity + +### Utility module +- Add `Activity(Entity)` object; stores behaviour node graph of a game entity +- Add `Node(Entity)` object; node in behaviour node graph +- Add `Ability(Node)` object +- Add `End(Node)` object +- Add `Start(Node)` object +- Add `XORGate(Node)` object +- Add `XOREventGate(Node)` object +- Add `Condition(Object)` object +- Add `CommandInQueue(Condition)` object +- Add `NextCommandIdle(Condition)` object +- Add `NextCommandMove(Condition)` object +- Add `Event(Entity)` object; event for behaviour node graph +- Add `Wait(Event)` object +- Add `WaitAbility(Event)` object +- Add `CommandInQueue(Event)` object + + +## Reference visualization + +* [Gamedata](https://github.com/SFTtech/openage/blob/408fc171552bc96a30549d05fceeb9d692fd9d1d/doc/nyan/aoe2_nyan_tree.svg) diff --git a/doc/changelogs/nyan_api/v0.5.0.md b/doc/changelogs/nyan_api/v0.5.0.md new file mode 100644 index 0000000000..127a2b4d37 --- /dev/null +++ b/doc/changelogs/nyan_api/v0.5.0.md @@ -0,0 +1,27 @@ +# [0.5.0] - 2024-07-29 +All notable changes for version [v0.5.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). + +## Renamed +### Ability module +- Rename `Hitbox` to `Collision` + +## Added +### Ability module +- Add `Pathable(Ability)` object; defines pathing costs for the game entity when the ability is active +- Add `path_type : PathType` member to `Move` + +### Utility module +- Add `PathType(Object)` object; associates move abilities with pathfinding grids +- Add `path_costs : dict(children(PathType), int)` member to `Terrain`; defines pathing costs for the terrain + +### Removed +### Ability module +- Remove `Passable(Ability)` object; functionality superceded by `Pathable` + + +## Reference visualization + +* [Gamedata](https://github.com/SFTtech/openage/blob/f1967c3c002d444510e50f54c9cdbb83419a9ec4/doc/nyan/aoe2_nyan_tree.svg) 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/converter/architecture_overview.md b/doc/code/converter/architecture_overview.md index 9247952246..22f46d1695 100644 --- a/doc/code/converter/architecture_overview.md +++ b/doc/code/converter/architecture_overview.md @@ -30,7 +30,7 @@ Value objects are used to store primitive data or as definition of how to read p In the converter, the parsers utilize these objects to extract attributes from the original Genie Engine file formats. Extracted attributes are also saved as value objects. -Value objects are treated as *immutable*. Operations on the objects's values will therefore always return +Value objects are treated as *immutable*. Operations on the objects values will therefore always return a new object instance with the result and leave the original values as-is. ### Entity Object diff --git a/doc/code/converter/workflow.md b/doc/code/converter/workflow.md index 5ddd2db621..b4dc4ead9e 100644 --- a/doc/code/converter/workflow.md +++ b/doc/code/converter/workflow.md @@ -66,7 +66,7 @@ content can be accessed in the same way as "loose" files from the source folder. After all relevant source folders are mounted, the converter will begin the main conversion process by reading the available data. Every source file format has -its own reader that is adpated to the constraints and features of the format. +its own reader that is adapted to the constraints and features of the format. However, reading always follows this general workflow: * **Registration**: Enumerates all files with the requested format. @@ -100,7 +100,7 @@ attribute values (e.g. `"attack"`). For example, a `uint32_t` value could be used for a normal integer value or define an ID to a graphics/sound resource. Every unique value type is associated with a Python object type used for storing the attribute. -* **Output mode**: Dtetermines whether the attribute is part of the reader output +* **Output mode**: Determines whether the attribute is part of the reader output or can be skipped (e.g. `SKIP`). The Reader parses attributes one by one and stores them in a `ValueMember` subclass @@ -162,7 +162,7 @@ In general, it involves these 3 steps: 1. Check if a concept group has a certain property 2. If true, create and assign nyan API objects associated with that property -3. Map values from concept group data to the objects' member values +3. Map values from concept group data to the objects member values This is repeated for every property and for every concept group. Most values can be mapped 1-to-1, although some require additional property checks. @@ -173,7 +173,7 @@ that contains the ID and the desired target filename. In the Export stage, the source filename for the given ID is then resolved and the file is parsed, converted and saved at the target location. -At the end of the mappping stage, the resulting nyan objects are put into nyan files +At the end of the mapping stage, the resulting nyan objects are put into nyan files and -- together will the media export requests -- are organized into modpacks which are passed to the exporter. diff --git a/doc/code/curves.md b/doc/code/curves.md index 2ead4a8c77..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,22 +199,54 @@ 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 keep track of when modifications happen and what changes -to an element are made. Deleting elements also does not erase elements from memory. -Instead, they are simply hidden for requests for time `t1` after the deletion time `t2` if -`t1 > t2`. +is that curve containers track the *lifespan* of each element, i.e. their insertion time, +modification time, and erasure time. Erasing elements also does not delete them from memory +and instead hides them for requests made after the erasure time. #### Queue -Queue curve containers store elements in first-in-first-out (FIFO) insertion order -while additionally keeping track of element insertion time. Requests for the front element -at time `t` will return the element that is in front of the queue at that time. -The queue can also be iterated over for a specific time `t` which allows access to -all elements that were in the queue at time `t`. +Queue curve containers are the equivalent to the `std::queue` C++ containers. As such, they +should be used in situations where first-in-first-out (FIFO) access patterns are desired. + +Elements in the queue are sorted by insertion time. The element with the earliest insertion time +is the *front* element of the queue. + +The front element at time `t` can be read via the `front(t)` method, which retrieves the first, +non-erased element with insertion time before or at `t`. In comparison, `pop_front(t)` returns +the same value as `front(t)` and additionally erases the element from the queue at time `t`. + +It should be stressed again that erasing an element does not delete it from memory but +simply ends its lifespan inside the curve container. `front(t)` and `pop_front(t)` always +consider elements with an active lifespan (i.e. elements that are not erased at time `t`). +As a side effect, `pop_front(t1)` and `front(t2)`/`pop_front(t2)` may return the same element +when `t2 < t1` because the element has not ended its lifespan at time `t2` yet. It is in +the responsibility of the caller to ensure that this behaviour does not cause any +side effects. + + +**Read** + +Read operations retrieve values for a specific point in time. + +| Method | Description | +| ---------- | --------------------------------------- | +| `front(t)` | Get front element at time `t` | +| `empty(t)` | Check if the queue is empty at time `t` | + +**Modify** + +Modify operations insert values for a specific point in time. + +| Method | Description | +| ------------------ | ------------------------------------------------------ | +| `insert(t, value)` | Insert a new element at time `t` | +| `pop_front(t)` | Get front element at time `t` and erase it at time `t` | +| `clear(t)` | Erase all elements inserted before time `t` | + #### Unordered Map @@ -221,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/game_simulation/images/activity_graph.svg b/doc/code/game_simulation/images/activity_graph.svg index bf87f779fa..4044797dc2 100644 --- a/doc/code/game_simulation/images/activity_graph.svg +++ b/doc/code/game_simulation/images/activity_graph.svg @@ -1,4 +1,4 @@ -IdleMoveStartMoveable?Can MoveCan't MoveWait for commandWait for Move to FinishEnd \ No newline at end of file +Idlecommand in queue?wait for commandbranch on commandGatherAttackMovemove finishednew commandcommand receivedMove \ No newline at end of file diff --git a/doc/code/gui.md b/doc/code/gui.md index 6c548c2b65..3f1a4afb17 100644 --- a/doc/code/gui.md +++ b/doc/code/gui.md @@ -51,7 +51,7 @@ qmlRegisterType("yay.sfttech.openage", 1, 0, "ResourceAmount 2. Specializations `struct Wrap` and `struct Unwrap` must be defined: ```cpp -namespace qtsdl { +namespace qtgui { template<> struct Wrap { using Type = ResourceAmountLink; @@ -61,13 +61,13 @@ template<> struct Unwrap { using Type = ResourceAmount; }; -} // namespace qtsdl +} // namespace qtgui ``` 3. Also ResourceAmount needs a public member to be added: ```cpp public: - qtsdl::GuiItemLink *gui_link + qtgui::GuiItemLink *gui_link ``` 4. Declare and implement needed properties and signals in the `ResourceAmountLink` using Qt property syntax. @@ -78,7 +78,7 @@ There is a class `GeneratorParameters` in `libopenage/` directory. It has a big list of parameters of different types like `generation_seed`, `player_radius`, `player_names`, etc. So, we're not going to write a Qt property for each one: -1. `GeneratorParameters` must derive from the `qtsdl::GuiPropertyMap`. +1. `GeneratorParameters` must derive from the `qtgui::GuiPropertyMap`. 2. `GeneratorParameters` should set its initial values like so: ```cpp @@ -96,7 +96,7 @@ qmlRegisterType("yay.sfttech.openage", 1, 0, "Generator 4. Specializations `struct Wrap` and `struct Unwrap` must be defined: ```cpp -namespace qtsdl { +namespace qtgui { template<> struct Wrap { using Type = GeneratorParametersLink; @@ -106,7 +106,7 @@ template<> struct Unwrap { using Type = GeneratorParameters; }; -} // namespace qtsdl +} // namespace qtgui ``` That results into a `ListModel`-like QML type with `display` and `edit` roles. diff --git a/doc/code/images/pathfinder_architecture.svg b/doc/code/images/pathfinder_architecture.svg new file mode 100644 index 0000000000..2046a1a486 --- /dev/null +++ b/doc/code/images/pathfinder_architecture.svg @@ -0,0 +1,175 @@ + + +GamestateMovement,collision, etc.PathfinderPathFlowFieldIntegrationFieldIntegratorPathfinderPortalCostFieldSectorGrid diff --git a/doc/code/images/pathfinder_architecture.uxf b/doc/code/images/pathfinder_architecture.uxf new file mode 100644 index 0000000000..3a051b2acf --- /dev/null +++ b/doc/code/images/pathfinder_architecture.uxf @@ -0,0 +1,271 @@ + + + 10 + + UMLClass + + 710 + 510 + 120 + 60 + + +*Grid* + + + + UMLClass + + 710 + 620 + 120 + 60 + + +*Sector* + + + + UMLClass + + 630 + 730 + 120 + 60 + + +*CostField* + + + + UMLClass + + 790 + 730 + 120 + 60 + + +*Portal* + + + + UMLClass + + 710 + 400 + 120 + 60 + + +*Pathfinder* + + + + UMLClass + + 980 + 400 + 120 + 60 + + +*Integrator* + + + + UMLClass + + 1040 + 490 + 160 + 60 + + +*IntegrationField* + + + + UMLClass + + 1040 + 560 + 120 + 60 + + +*FlowField* + + + + UMLClass + + 520 + 400 + 120 + 60 + + +*Path* + + + + Relation + + 760 + 450 + 30 + 80 + + lt=<. + 10.0;60.0;10.0;10.0 + + + Relation + + 760 + 560 + 30 + 80 + + lt=<. + 10.0;60.0;10.0;10.0 + + + Relation + + 720 + 670 + 30 + 80 + + lt=<. + 10.0;60.0;10.0;10.0 + + + Relation + + 800 + 670 + 30 + 80 + + lt=<. + 10.0;60.0;10.0;10.0 + + + Relation + + 820 + 420 + 180 + 30 + + lt=<. + 160.0;10.0;10.0;10.0 + + + Relation + + 990 + 450 + 30 + 160 + + lt=. + 10.0;140.0;10.0;10.0 + + + Relation + + 990 + 510 + 70 + 30 + + lt=<. + 50.0;10.0;10.0;10.0 + + + Relation + + 990 + 580 + 70 + 30 + + lt=<. + 50.0;10.0;10.0;10.0 + + + Relation + + 630 + 420 + 100 + 30 + + lt=<. + 10.0;10.0;80.0;10.0 + + + Relation + + 760 + 200 + 30 + 220 + + lt=<. + 10.0;200.0;10.0;10.0 + + + Text + + 460 + 330 + 240 + 70 + + *Pathfinder* +fontsize=22 +style=wordwrap + + + + Relation + + 450 + 300 + 770 + 30 + + lt=- + 10.0;10.0;750.0;10.0 + + + Text + + 730 + 160 + 130 + 70 + + Movement, collision, etc. +style=wordwrap + + + + Text + + 460 + 160 + 240 + 70 + + *Gamestate* +fontsize=22 +style=wordwrap + + + diff --git a/doc/code/input/README.md b/doc/code/input/README.md index ab059a668f..f7013dd4ee 100644 --- a/doc/code/input/README.md +++ b/doc/code/input/README.md @@ -86,11 +86,11 @@ the resulting GUI event. In any case, the resulting `QEvent` representing the raw input is converted to a generalized `input::Event` object containing the following information: - - **event class**: Basic event categorization, usually type of input device (e.g. keyboard, mouse, GUI) - - **code**: Unique identifier of the specific key/button that was pressed - - **modifiers**: Keyboard modifiers pressed alongside the key/button (e.g. CTRL, SHIFT, ALT) - - **state**: State of the button/key (e.g. pressed, released, double click) - - **raw event**: Reference to the original `QEvent` +- **event class**: Basic event categorization, usually type of input device (e.g. keyboard, mouse, GUI) +- **code**: Unique identifier of the specific key/button that was pressed +- **modifiers**: Keyboard modifiers pressed alongside the key/button (e.g. CTRL, SHIFT, ALT) +- **state**: State of the button/key (e.g. pressed, released, double click) +- **raw event**: Reference to the original `QEvent` The unique combination of *class*, *code*, *modifiers*, and *state* represents a specific key press and provides the **signature** of an input event. (Key) bindings are created by mapping signatures @@ -121,27 +121,27 @@ the actions taken by the input manager mainly consists of forwarding event data high-level interfaces. Therefore, these actions should not have any effect on the game simulation. `input_action` contains the following information: - - **action type**: One of the pre-defined types (see below). Used for determining a default action. - - **custom action function**: Executed instead of the default action if set (optional). - - **execution flags**: Key-value pairs for configuration settings (optional). +- **action type**: One of the pre-defined types (see below). Used for determining a default action. +- **custom action function**: Executed instead of the default action if set (optional). +- **execution flags**: Key-value pairs for configuration settings (optional). Most types have a default action that is executed unless a custom function is defined in `input_action`. These default actions are: - - **push context**: push a context on top of the stack - - **pop context**: remove the current top context - - **remove context**: remove a context from the stack - - **Controller**: forward event arguments in direction of gamestate (i.e. to high-level interface) - - **GUI**: forward event arguments to the GUI +- **push context**: push a context on top of the stack +- **pop context**: remove the current top context +- **remove context**: remove a context from the stack +- **Controller**: forward event arguments in direction of gamestate (i.e. to high-level interface) +- **GUI**: forward event arguments to the GUI In most cases, it should be sufficient to bind one of these options to an input event. Custom functions should only be used for edge cases which cannot be handled otherwise. For forwarding actions, additional arguments may be passed to the high-level interface. In the case of the controller, the input manager passes the `event_arguments` struct which contains: - - **input event** (i.e. the generalized `input::Event`) - - **mouse position** - - **mouse motion** (e.g. for calculating mouse movement direction) - - **flags**: Key-value pairs for configuration settings (optional) +- **input event** (i.e. the generalized `input::Event`) +- **mouse position** +- **mouse motion** (e.g. for calculating mouse movement direction) +- **flags**: Key-value pairs for configuration settings (optional) ### High-Level Interface (Controller) @@ -158,9 +158,9 @@ in binding context using the input event signature or class (similiar to how its interface). `binding_action` contains the following information: - - **transform function**: Transformation from event arguments to game event. - - **queue type**: Determines whether the created event is queued or passed to the gamestate immediately . - - **execution flags**: Key-value pairs for configuration settings (optional). +- **transform function**: Transformation from event arguments to game event. +- **queue type**: Determines whether the created event is queued or passed to the gamestate immediately . +- **execution flags**: Key-value pairs for configuration settings (optional). Game events can be queued before they are forwarded to the gamestate to allow chained commands, e.g. setting a bunch of waypoints before giving the final move command. diff --git a/doc/code/pathfinding/README.md b/doc/code/pathfinding/README.md new file mode 100644 index 0000000000..19d1dc8d19 --- /dev/null +++ b/doc/code/pathfinding/README.md @@ -0,0 +1,121 @@ +# Pathfinding + +openage's pathfinding subsystem implements structures used for navigating game entities in the +game world. These structures define movement costs for a map and allow search for a path +from one coordinate in the game world to another. + +Pathfinding is a subsystem of the [game simulation](/doc/code/game_simulation/README.md) where +it is primarily used for movement and placement of game entities. + +1. [Architecture](#architecture) +2. [Workflow](#workflow) + + +## Architecture + +The architecture of the pathfinder is heavily based on the article *Crowd Pathfinding and Steering* +*Using Flow Field Tiles* by Elijah Emerson (available [here](http://www.gameaipro.com/GameAIPro/GameAIPro_Chapter23_Crowd_Pathfinding_and_Steering_Using_Flow_Field_Tiles.pdf)). A core design +decision taken from this article was the usage of flow fields as the basis for the pathing algorithm. + +Flow fields offer a few advantages for large group movements, i.e. the movement you typically see in +RTS games like Age of Empires. For example, they allow reusing already computed path results for +subsequent pathing requests and can also be used for smart collision avoidance. One downside +of using flow fields can be the heavy upfront cost of pathing calculations. However, the downsides +can be mitigated to some degree with caching and high-level optimizations. + +The openage pathfinder is tile-based, like most flow field pathfinding implementations. Every tile +has a movement cost associated with it that represents the cost of a game entity moving on that tile. +When computing a path from A to B, the pathfinder tries to find the sequence of tiles with the cheapest +accumulated movement cost. + +In openage, the pathfinding subsystem is independent from the terrain implementation in the game +simulation. Terrain data may be used to influence movement cost during gameplay, but pathfinding +can be initialized without any requirements for terrain code. By only loosely connecting these two system, +we have a much more fine-grained control that allows for better optimization of the pathfinder. +The most relevant similarity between terrain and pathfinding code is that they use the same +[coordinate systems](/doc/code/coordinate-systems.md#tiletile3). To make the distinction between +pathfinding and terrain more clear, we use the term *cells* for tiles in the pathfinder. + +![UML pathfinding classes](/doc/code/images/pathfinder_architecture.svg) + +The relationship between classes in the pathfinder can be seen above. `Grid` is the top-level structure +for storage of movement cost. There may be multiple grids defined, one for each movement type +used by game entities in the game simulation. + +Every grid is subdivided into sectors, represented by the `Sector` class. Each sectors holds a +pointer to a `CostField` which stores the movement cost of individual cells. All sectors on the +same grid have a fixed square size that is determined by the grid. Thus, the sector size on +a grid is always consistent but different grid may utilize different sector sizes. + +Sectors on a grid are connect to each other with so-called portals, see the `Portal` +class. Portals represent a passable gateway between two sectors where game entities +can pass through. As such, portals store the coordinates of the cells in each sector +where game entities can pass. `Portal` objects are created for every continuous sequence of cells +on the edges between two sectors where the cells are passable on both sides of the two sectors. +Therefore, there may be multiple portals defined for the edge between two sectors. + +Additionally, each portal stores which other portals it can reach in a specific sector. The +result is a portal "graph" that can be searched separately to determine which portals +and sectors are visited for a specific pathing request. This is used in the high-level +pathfinder to preselect the sectors that flow fields need to be generated for (see the +[Workflow section](#workflow)). + +The individual movement cost of each cell in a sector are recorded in a `CostField` object. +The cost of a cell can range from 1 (minimum cost) to 254 (maximum cost), while a cost of 255 +makes a cell impassible. For Age of Empires, usually only the minimum and impassable cost +values are relevant. The cost field is built when the grid is first initialized and +individual cost of cells can be altered during gameplay events. + +To get a path between two coordinates, the game simulation mainly interfaces with a `Pathfinder` +object. The `Pathfinder` class calls the actual pathfinding algorithms used for searching +for a path and stores references to all available grids in the pathfinding subsystems. It can receive +`PathRequest` objects that contain information about the grid that should be searched as well as +the start and target coordinates of the desired path. Found paths are returned as `Path` objects +which store the waypoint coordinates for the computed path. + +Flow field calculations are controlled by an `Integrator` object, which process a cost field +from a sector as well as target coordinates to compute an `IntegrationField` and a `FlowField` object. +`IntegrationField`s are intermediary objects that store the accumulated cost of reaching +the target cell for each other cell in the field, using the cell values in the cost field as basis. +From the integration field, a `FlowField` object is created. Cells in the flow field store +movement vectors that point to their next cheapest neighbor cell in the integration field. `Path`s +may be computed from these flow field by simply following the movement vectors until the +target coordinate is reached. + + +## Workflow + +To initiate a new search, the pathfinder receives a `PathRequest` object, e.g. from the game simulation. +Every path request contains the following information: + +- ID of the grid to search +- start cell coordinates +- target cell coordinates + +The actual pathfinding algorithm is split into two stages. + +1. High-level portal-based search to identify the visited sectors on the grid +2. Low-level flow field-based search in the identified sectors to find the waypoints for the path + +The high-level search is accomplished by utilizing the portal graph, i.e. the +connections between portals in each sector. From the portal graph, a node mesh is created +that can be searched with a graph-traversal algorithm. For this purpose, the A\* algorithm +is used. The result of the high-level search is a list of sectors and portals that are +traversed by the path. + +The high-level search is mainly motivated by the need to avoid costly flow field +calculations on the whole grid. As the portal graph should already be precomputed when +a path request is made, the main influence on performance is the A\* algorithm. Given +a limited number of portals, the A\* search should overall be very cheap. + +The resulting list of sectors and portals is subsequently used in the low-level flow +field calculations. More details can be found in the [field types](field_types.md) document. +As a first step, the pathfinder uses its integrator to generate +a flow field for each identified sector. Generation starts with the target sector +and ends with the start sector. Flow field results are passed through at the cells +of the identified portals to make the flow between sectors seamless. + +In a second step, the pathfinder follows the movement vectors in the flow fields from +the start cell to the target cell. Waypoints are created for every direction change, so +that game entities can travel in straight lines between them. The list of waypoints +from start to target is then returned by the pathfinder via a `Path` object. diff --git a/doc/code/pathfinding/field_types.md b/doc/code/pathfinding/field_types.md new file mode 100644 index 0000000000..d7251da686 --- /dev/null +++ b/doc/code/pathfinding/field_types.md @@ -0,0 +1,78 @@ +# Field Types + +This document describes the field types used in the flow field pathfinding system. + +Most of the descriptions are based on the [*Crowd Pathfinding and Steering Using Flow Field Tiles*](http://www.gameaipro.com/GameAIPro/GameAIPro_Chapter23_Crowd_Pathfinding_and_Steering_Using_Flow_Field_Tiles.pdf) article by Elijah Emerson. + +## Cost Field + +A cost field is a square grid of cells that record the cost of movement on the location +of each cell. Higher cost values indicate that it is less desirable to move through that cell. +The field is usually initialized at the start of the game and persists for the lifetime of +the entire pathfinding grid. During gameplay, individual cell costs may be altered to reflect +changes in the environment. + +Cost values are represented as `uint8_t` (0-255) values. The range of usable cost values +is `1` to `254`. `255` is a special value that represents an impassable cell. `0` is reserved +for initialization and should not be used for flow field calculations. + +![Cost Field](images/cost_field.png) + +- **green**: minimum cost +- **red**: maximum cost +- **black**: impassable cell + +## Integration Field + +The integration field is created from a cost field when a path is requested. For a specific +target cell, the integration field stores the accumulated cost of reaching that cell from +every other cell in the field. + +Integration values are calculated using a wavefront algorithm. The algorithm starts at the +target cell(s) and propagates outward, updating the integration value of each cell it visits. +The integration value is calculated by adding the cost value of the current cell to the lowest +integration value of the 4 cardinal neighbors. The integration value of the target cell(s) is `0`. + +Integration values are represented as `uint16_t` (0-65535) values. The range of usable integration +values is `1` to `65534`. `65535` is a special value that represents an unreachable cell. During +initialization, all cells are set to `65535`. + +An additional refinement step in the form of line-of-sight testing may be performed before the +integration values are calculated. This step flags every cell that is in line of sight of the +target cell. This allows for smoother pathing, as game entities can move in a straight line to +the target cell. The algorithm for this step is described in more detail in section 23.6.2 +of the [*Crowd Pathfinding and Steering Using Flow Field Tiles*](http://www.gameaipro.com/GameAIPro/GameAIPro_Chapter23_Crowd_Pathfinding_and_Steering_Using_Flow_Field_Tiles.pdf) article. + +In addition to the integration values, the integration field also stores flags for each cell: + +- `FOUND`: cell has been visited +- `TARGET`: cell is a target cell +- `LOS`: cell is in line of sight of target cell +- `WAVEFRONT_BLOCKED`: cell is blocking line of sight to target cell + +![Integration Field](images/integration_field.png) + +- **green**: lower integration values +- **purple**: higher integration values +- **black**: unreachable cell + +## Flow Field + +Creating the flow field is the final step in the flow field calculation. The field +is created from the integration field. Cells in the flow field store the direction to +the neighbor cell with the lowest *integrated* cost. Thus, directions create a "flow" +towards the target cell. Following the directions from anywhere on the field will lead +to the shortest path to the target cell. + +Flow field values are represented as `uint8_t` values. The 4 least significant bits are used +to store the direction to the neighbor cell with the lowest integrated cost. Therefore, 8 +directions can be represented. The 4 most significant bits are used for flags: +- `PATHABLE`: cell is passable +- `LOS`: cell is in line of sight of target cell +- `TARGET`: cell is a target cell + +![Flow Field](images/flow_field.png) + +- **white**: line of sight +- **bright/dark grey**: passable cells (not in line of sight) +- **black**: impassable cell diff --git a/doc/code/pathfinding/images/cost_field.png b/doc/code/pathfinding/images/cost_field.png new file mode 100644 index 0000000000..8d820b93fa Binary files /dev/null and b/doc/code/pathfinding/images/cost_field.png differ diff --git a/doc/code/pathfinding/images/flow_field.png b/doc/code/pathfinding/images/flow_field.png new file mode 100644 index 0000000000..ec2985ece2 Binary files /dev/null and b/doc/code/pathfinding/images/flow_field.png differ diff --git a/doc/code/pathfinding/images/integration_field.png b/doc/code/pathfinding/images/integration_field.png new file mode 100644 index 0000000000..56df3abf49 Binary files /dev/null and b/doc/code/pathfinding/images/integration_field.png differ diff --git a/doc/code/renderer/demos.md b/doc/code/renderer/demos.md new file mode 100644 index 0000000000..82de8c4e86 --- /dev/null +++ b/doc/code/renderer/demos.md @@ -0,0 +1,185 @@ +# Interactive Demos & Stresstests + +openage builds contain several *interactive* renderer tech demo entrypoints that show of specific features. +These demos are also useful for learning the renderer API and for testing new functionality. In addition to +the demos, there are also stresstests that are used to test the performance of the renderer. +The source code for renderer demos and stresstests is located in [`libopenage/renderer/demo`](/libopenage/renderer/demo/). + +This documents describes the purpose of each demo and contains instructions on how to interact with them. + +1. [Demos](#demos) + 1. [Demo 0](#demo-0) + +## Demos + +### Demo 0 + +This demo shows the creation of a minmal renderer setup and the rendering of a simple mesh. + +The demo initializes a GUI application, a window, a renderer object, and a render pass. +It then loads a shader program and creates a single mesh object which is then rendered to the screen +using the shader program. + +The demo mostly follows the steps described in the [Level 1 Renderer - Basic Usage](level1.md#basic-usage) +documentation. + +```bash +cd bin && ./run test --demo renderer.tests.renderer_demo 0 +``` + +**Result:** + +![Demo 0](/doc/code/renderer/images/demo_0.png) + + +### Demo 1 + +This demo shows how simple *textured* meshes can be created and rendered. It also demonstrates +how to interact with the window and the renderer using window callbacks. + +```bash +cd bin && ./run test --demo renderer.tests.renderer_demo 1 +``` + +**Controls:** + +- LMB: Click on the textured meshes to get a debug message with the object ID. + +**Result:** + +![Demo 1](/doc/code/renderer/images/demo_1.png) + + +### Demo 2 + +In this demo, we show how animation and texture metadata files are parsed and used to +load and render the correct textures and animations for a mesh. + +```bash +cd bin && ./run test --demo renderer.tests.renderer_demo 2 +``` + +**Controls:** + +- LMB: Click on the sprite to get a debug message with the object ID. +- : Go back one frame in the animation. +- : Advance the animation by one frame. + +**Result:** + +![Demo 2](/doc/code/renderer/images/demo_2.png) + + +### Demo 3 + +This demo shows a minimal setup for the [Level 2 Renderer](level2.md) and how to render objects +with it. The demo also introduces the camera system and how to interact with it. + +```bash +cd bin && ./run test --demo renderer.tests.renderer_demo 3 +``` + +**Controls:** + +- W, A, S, D: Move the camera in the scene. +- `Mouse Wheel`: Zoom in and out. + +**Result:** + +![Demo 3](/doc/code/renderer/images/demo_3.png) + + +### Demo 4 + +This demos shows how animation frame timing works and how to control the animation speed +with the engine's internal clock. + +```bash +cd bin && ./run test --demo renderer.tests.renderer_demo 4 +``` + +**Controls:** + +- Space: Pause/resume the clock. +- +, -: Increase/decrease the simulation speed. +- Return: Toggle between real time and simulation time. + +**Result:** + +![Demo 4](/doc/code/renderer/images/demo_4.png) + + +### Demo 5 + +This demo shows how to create [uniform buffers](level1.md#uniform-buffers) and how to use them to pass data to shaders. +Additionally, uniform buffer usage for the camera system is demonstrated. + +```bash +cd bin && ./run test --demo renderer.tests.renderer_demo 5 +``` + +**Controls:** + +- W, A, S, D: Move the camera in the scene. + +**Result:** + +![Demo 5](/doc/code/renderer/images/demo_5.png) + + +### Demo 6 + +This demo shows how to use [frustum culling](level2.md#frustum-culling) in the renderer. + +```bash +cd bin && ./run test --demo renderer.tests.renderer_demo 6 +``` + +**Controls:** + +- W, A, S, D: Move the camera in the scene. + +**Result:** + +![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 + +This stresstest tests the performance when rendering an increasingly larger number of objects. + +```bash +cd bin && ./run test --demo renderer.tests.renderer_stresstest 0 +``` + +**Result:** + +![Stresstest 0](/doc/code/renderer/images/stresstest_0.png) + +### Stresstest 1 + +This stresstest tests the performance when [frustum culling](level2.md#frustum-culling) is enabled and an increasingly larger +number of objects is rendered on the screen. + +```bash +cd bin && ./run test --demo renderer.tests.renderer_stresstest 1 +``` + +**Result:** + +![Stresstest 1](/doc/code/renderer/images/stresstest_1.png) diff --git a/doc/code/renderer/images/demo_0.png b/doc/code/renderer/images/demo_0.png new file mode 100644 index 0000000000..6bcd90b06a Binary files /dev/null and b/doc/code/renderer/images/demo_0.png differ diff --git a/doc/code/renderer/images/demo_1.png b/doc/code/renderer/images/demo_1.png new file mode 100644 index 0000000000..3173aee42f Binary files /dev/null and b/doc/code/renderer/images/demo_1.png differ diff --git a/doc/code/renderer/images/demo_2.png b/doc/code/renderer/images/demo_2.png new file mode 100644 index 0000000000..17a90580f9 Binary files /dev/null and b/doc/code/renderer/images/demo_2.png differ diff --git a/doc/code/renderer/images/demo_3.png b/doc/code/renderer/images/demo_3.png new file mode 100644 index 0000000000..27af7e8dfb Binary files /dev/null and b/doc/code/renderer/images/demo_3.png differ diff --git a/doc/code/renderer/images/demo_4.png b/doc/code/renderer/images/demo_4.png new file mode 100644 index 0000000000..17a90580f9 Binary files /dev/null and b/doc/code/renderer/images/demo_4.png differ diff --git a/doc/code/renderer/images/demo_5.png b/doc/code/renderer/images/demo_5.png new file mode 100644 index 0000000000..b24588a0ce Binary files /dev/null and b/doc/code/renderer/images/demo_5.png differ diff --git a/doc/code/renderer/images/demo_6.png b/doc/code/renderer/images/demo_6.png new file mode 100644 index 0000000000..8b0b06496c Binary files /dev/null and b/doc/code/renderer/images/demo_6.png differ 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/images/stresstest_0.png b/doc/code/renderer/images/stresstest_0.png new file mode 100644 index 0000000000..21cbb8934e Binary files /dev/null and b/doc/code/renderer/images/stresstest_0.png differ diff --git a/doc/code/renderer/images/stresstest_1.png b/doc/code/renderer/images/stresstest_1.png new file mode 100644 index 0000000000..b578d4f158 Binary files /dev/null and b/doc/code/renderer/images/stresstest_1.png differ diff --git a/doc/code/renderer/level1.md b/doc/code/renderer/level1.md index 8a7d4e1eb0..b8dbc8fb30 100644 --- a/doc/code/renderer/level1.md +++ b/doc/code/renderer/level1.md @@ -6,7 +6,7 @@ Low-level renderer for communicating with the OpenGL and Vulkan APIs. 1. [Overview](#overview) 2. [Architecture](#architecture) -3. [Basic Usage](#basic--usage) +3. [Basic Usage](#basic-usage) 1. [Window/Renderer Creation](#windowrenderer-creation) 2. [Adding a Shader Program](#adding-a-shader-program) 3. [Creating a Renderable](#creating-a-renderable) @@ -14,8 +14,10 @@ Low-level renderer for communicating with the OpenGL and Vulkan APIs. 4. [Advanced Usage](#advanced-usage) 1. [Addressing Uniforms via numeric IDs](#addressing-uniforms-via-numeric-ids) 2. [Framebuffers / Multiple Render Passes](#framebuffers--multiple-render-passes) - 3. [Complex Geometry](#complex-geometry) - 4. [Uniform Buffers](#uniform-buffers) + 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) @@ -44,7 +46,7 @@ The `resources` namespace provides classes for initializing and loading meshes, These classes are independent from the specific OpenGL/Vulkan backends and can thus be passed to the abstract interface of the renderer renderer to make them usable with graphics hardware. -## Basic Usage +## Basic Usage Code examples can be found in the [renderer demos](/libopenage/renderer/demo/). See the [testing docs](/doc/code/testing.md#python-demos) on how to try them out. @@ -200,9 +202,11 @@ Finally, we can execute the rendering pipeline for all objects in the render pas renderer->render(pass); ``` + After rendering is finished, the window has to be updated to display the rendered result. @@ -312,6 +316,69 @@ Renderable obj { obj.depth_test = true; ``` + +### Defining Layers in a Render Pass + +Layers give more fine-grained control over the draw order of renderables in a render pass. Every +layer has a priority that determines when associated renderables are drawn. Lower priority +renderables are drawn earlier, higher priority renderables are drawn later. + +In comparison to using multiple render passes, layers do not require the (expensive) switching +of framebuffers between passes. The tradeoff is a slight overhead when inserting new renderables +into the render pass. + +To assign renderables to a layer, we have to specify the priority in the `RenderPass::add_renderables(..)` +function call. + +```c++ +Renderable obj { + input, + geom +}; +pass->add_renderables({ obj }, 42); +``` + +For existing layers, new renderables are always appended to the end of the layer. Renderables +are sorted into the correct position automatically when they are added: + +```c++ +pass->add_renderables({ obj1, obj2, obj3 }, 42); +pass->add_renderables({ obj4 }, 0); +pass->add_renderables({ obj5, obj6 }, 1337); +pass->add_renderables({ obj7 }, 0); +// draw order: obj4, obj7, obj1, obj2, obj3, obj5, obj6 +// layers: prio 0 | prio 42 | prio 1337 +``` + +When no priority is specified when calling `RenderPass::add_renderables(..)`, the highest +priority is assumed (which is `std::numeric_limits::max()`). Therefore, +objects added like this are always drawn last. It also means that these two calls are equal: + +```c++ +pass->add_renderables({ obj }); +pass->add_renderables({ obj }, std::numeric_limits::max()); +``` + + +Layers are created lazily during insertion if no layer with the specified priority exists yet. +We can also create layers explicitly for a specific priority: + +```c++ +pass->add_layer(42); +``` + +When executing the rendering pipeline for a specific pass, renderables are drawn layer by layer. +By default, the renderer clears the depth buffer when switching to a new layer. This is done +under the assumption that layers with higher priority should always draw over layers with lower +priority, even when depth tests are active. This behavior can be deactivated when explicitly +creating a layer: + +```c++ +// keep depth testing +pass->add_layer(42, false); +``` + + ### Complex Geometry For displaying complex geometry like 3D objects or non-rectangular surfaces, the renderer @@ -476,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/code/renderer/level2.md b/doc/code/renderer/level2.md index f7bf8bbe2a..cc927ef6a2 100644 --- a/doc/code/renderer/level2.md +++ b/doc/code/renderer/level2.md @@ -4,21 +4,22 @@ High-level renderer for transforming data from the gamestate to render objects f ## Overview -1. [Level 2](#level-2) - 1. [Overview](#overview) - 2. [Stages](#stages) - 1. [Updating Render Stages from the Gamestate](#updating-render-stages-from-the-gamestate) - 3. [Camera](#camera) +1. [Overview](#overview) +2. [Stages](#stages) + 1. [Updating Render Stages from the Gamestate](#updating-render-stages-from-the-gamestate) +3. [Camera](#camera) + 1. [Frustum Culling](#frustum-culling) ## Stages Every stage has its own subrenderer that manages a `RenderPass` from the level 1 renderer and updates it with `Renderable`s created using update information from the gamestate. Stages also store the vertex and fragment shaders used for drawing the renderable objects. -There are currently 5 stages in the level 2 rendering pipeline: +There are currently 6 stages in the level 2 rendering pipeline: 1. `SkyboxRenderer`: Draws the background behind the terrain (as a single color). 1. `TerrainRenderer`: Draws the terrain. Terrains are handled as textured 3D meshes. 1. `WorldRenderer`: Draws animations and sprites for units/buildings and other 2D ingame objects. +1. `HudRenderer`: Draws "Head-Up Display" elements like health bars, selection boxes, and others. 1. `GuiRenderer`: Draws the GUI overlay. The drawing part in this stage is actually done by Qt, while the level 1 renderer only provides the framebuffer. 1. `ScreenRenderer`: Alpha composites the framebuffer data of previous stages and draws them onto the screen (i.e. it overlays the outputs from the other stages). @@ -61,3 +62,40 @@ Zoom levels can also be adjusted with these methods: For displaying 3D objects, the `Camera` can also calculate a view matrix (`get_view_matrix()`) and projection matrix (`get_projection_matrix()`) that take current position and zoom level into account. Camera parameters may be used for raycasting operations, e.g. mouse picking/selection. Since the camera utilizes orthographic projection and a fied angle, the ray direction is exactly the same as the camera direction vector (accessible as `cam_direction`). To find the origin point of a ray for a pixel coordinate in the viewport, the `get_input_pos(..)` method can be used. This method calculates the position of the pixel coordinate on the othographic camera plane that represents the viewport. The result is the absolute position of the pixel coordinate inside the 3D scene. Ray origin point and direction can then be used to perform calculations for line-plane or line sphere intersections. + +### Frustum Culling + +Frustum culling is a technique used to discard objects that are outside the view frustum of the camera. +This can save a lot of computation time that would be spent on updating shaders and rendering objects +that are not visible in the camera's view. + +The openage renderer provides two frustum types: 2D and 3D frustums. 2D frustums are used +for sprite animations and other 2D objects, while 3D frustums are used for 3D objects. Both frustums +can be created from a camera object. + +```c++ +std::shared_ptr camera = std::make_shared(renderer, {800, 600}); + +Frustum2d frustum_2d = camera->get_frustum_2d(); +Frustum3d frustum_3d = camera->get_frustum_3d(); +``` + +`Frustum2d` and `Frustum3d` provide a method `is_visible(..)` that can be used to check if an object is +located inside the frustum. The required inputs differ depending on the frustum type. For 3D frustums, +only the 3D scene position is required: + +```c++ +bool is_visible = frustum_3d.is_visible({0.f, 0.f, 0.f}); +``` + +For 2D frustums, in addition to the 3D scene position, a model matrix, the animation's scalefactor +as well as the bounding box of the animation must be provided. + +```c++ +bool is_visible = frustum_2d.is_visible( + {0.f, 0.f, 0.f}, + model_matrix, // the model matrix of the animation + scalefactor, // how much the animation is scaled + {10, 20, 50, 10} // max distance from the center to the edges of the bounding box +); +``` diff --git a/doc/code_style/mom.cpp b/doc/code_style/mom.cpp index 6655d0956d..ad2bb5d07d 100644 --- a/doc/code_style/mom.cpp +++ b/doc/code_style/mom.cpp @@ -17,13 +17,16 @@ // The associated header file comes first! #include "mom.h" -// System includes follow, sorted alphabetically +// C++ std library includes follow, sorted alphabetically #include #include #include -#include -// Local includes next, sorted alphabetically +// External libraries are next, sorted alphabetically +#include +#include + +// Local includes come last, sorted alphabetically #include "../valve.h" #include "half_life.h" #include "log/log.h" 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/convert/convert_single_file.md b/doc/convert/convert_single_file.md index f8c7f16d31..630dbb869f 100644 --- a/doc/convert/convert_single_file.md +++ b/doc/convert/convert_single_file.md @@ -7,27 +7,27 @@ The invocation could be: SLPs in DRS archives (for older versions of Age of Empires 1, Age of Empires 2 and SWGB): ``` -python3 -m openage convert-file --palettes-path ~/games/aoe2/Data/interfac.drs --drs ~/games/aoe2/Data/graphics.drs 326.slp /tmp/rofl.png +python3 -m openage convert-file --palettes-path ~/games/aoe2/Data/ --drs ~/games/aoe2/Data/graphics.drs 326.slp /tmp/rofl.png ``` Standalone SLPs (Age of Empires 1: DE and Age of Empires 2: HD): ``` -python3 -m openage convert-file --palette-file ~/games/aoe2hd/Data/50500.bina 326.slp /tmp/rofl.png +python3 -m openage convert-file --palettes-path ~/games/aoede/Assets/Palettes 326.slp /tmp/rofl.png ``` Standalone SLDs (Age of Empires 2: DE): ``` -python3 -m openage convert-file --player-palette-file ~/games/aoe2de/Data/playercolor_blue.pal --palette-file ~/games/aoe2de/Data/b_west.pal u_elite_eagle.sld /tmp/rofl.png +python3 -m openage convert-file --palettes-path ~/games/aoe2de/Data/ u_elite_eagle.sld /tmp/rofl.png ``` Standalone SMXs (Age of Empires 2: DE): ``` -python3 -m openage convert-file --player-palette-file ~/games/aoe2de/Data/playercolor_blue.pal --palette-file ~/games/aoe2de/Data/b_west.pal u_elite_eagle.smx /tmp/rofl.png +python3 -m openage convert-file --palettes-path ~/games/aoe2de/Data/ u_elite_eagle.smx /tmp/rofl.png ``` Standalone SMPs (Age of Empires 2: DE): ``` -python3 -m openage convert-file --player-palette-file ~/games/aoe2de/Data/playercolor_blue.pal --palette-file ~/games/aoe2de/Data/b_west.pal u_elite_eagle.smp /tmp/rofl.png +python3 -m openage convert-file --palettes-path ~/games/aoe2de/resources/_common/palettes u_elite_eagle.smp /tmp/rofl.png ``` WAVs in DRS archives (for older versions of Age of Empires 1, Age of Empires 2 and SWGB): diff --git a/doc/debug.md b/doc/debug.md index e8f838e0b2..cf6c8880fc 100644 --- a/doc/debug.md +++ b/doc/debug.md @@ -21,10 +21,28 @@ gdb -ex 'set breakpoint pending on' -ex 'b openage::run_game' -ex run --args run ``` The game will be paused at the start of the function run_game() located in `libopenage/main.cpp` -#### Note: -The `run` executable is a compiled version of `run.py` that also embeds the interpreter. +**Note:** The `run` executable is a compiled version of `run.py` that also embeds the interpreter. The game is intended to be run by `run.py` but it is much easier to debug the `./run` file +### Pretty Printers + +Enabling pretty printing will make GDB's output much more readable, so we always recommend +to configure it in your setup. Your [favourite IDE](/doc/ide/) probably an option to enable pretty printers +for the standard library types. If not, you can get them from the [gcc repository](https://github.com/gcc-mirror/gcc/tree/master/libstdc%2B%2B-v3/python/libstdcxx) and register them in your local `.gdbinit` file. + +Additionally, we have created several custom GDB pretty printers for types used in `libopenage`, +the C++ library that contains the openage engine core. To enable them, you have to load the project's +own init file [openage.gdbinit](/etc/openage.gdbinit) when running GDB: + +```gdb +(gdb) source /etc/openage.gdbinit +``` + +Your IDE may be able to do this automatically for a debug run. Alternatively, you can configure +an [auto-loader](https://sourceware.org/gdb/current/onlinedocs/gdb.html/Python-Auto_002dloading.html#Python-Auto_002dloading) +that loads the scripts for you. + + ### GDBGUI [gdbgui](https://github.com/cs01/gdbgui) is a browser-based frontend for GDB. 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/ideas/gameplay.md b/doc/ideas/gameplay.md index e4e26becd2..29e25e2532 100644 --- a/doc/ideas/gameplay.md +++ b/doc/ideas/gameplay.md @@ -144,7 +144,7 @@ A mode similar to *Trouble in Terrorist Town* and *Secret Hitler*. The game star ### Pure Battle Mode -No buildings, just units. The game generates a map and players can choose a starting position. Then they have a few minutes and a set amount of resources to select an army composition and some techs. After the first phase is over they place their units on the battlefield and have to use what they assembled to destroy their opponent. Utilizing height advantages, microing and tactical positioning contrast the strategic decisions of creating the army. The player who destroys his opponent, inflicts the most resource damage to others or holds strategic positions wins the battle. +No buildings, just units. The game generates a map and players can choose a starting position. Then they have a few minutes and a set amount of resources to select an army composition and some techs. After the first phase is over they place their units on the battlefield and have to use what they assembled to destroy their opponent. Utilizing height advantages, microing and tactical positioning contrast the strategic decisions of creating the army. The player who destroys their opponent, inflicts the most resource damage to others or holds strategic positions wins the battle. ### Micro-nerd Mode (or Mod) @@ -373,7 +373,7 @@ Relics & Kings - e.g. they can have attack bonuses for special units or economic/military bonuses - special abilities for every relic could either be generated when the map is generated or when the relic is discovered based on the actual needs of the player - so the later you go out to get the relic the more it could get useful for you, because it could be better shaped on your personal military/economy but the risk is higher, that another player was going out before you - - if a player scouts the relic first and the ability gets generated in this moment, it will be for the player who scouted it first, so he knows, that this relic could help their own economy/military alot so the player will try to fight about this relic against the enemy heavily -> new gameplay aspect + - if a player scouts the relic first and the ability gets generated in this moment, it will be for the player who scouted it first, so they know, that this relic could help their own economy/military a lot so the player will try to fight about this relic against the enemy heavily -> new gameplay aspect - Relics could have ranged attributes - idea of the devs of AoE II diff --git a/doc/media/original-metadata.md b/doc/media/original-metadata.md index 5e5540fe44..b29583b44a 100644 --- a/doc/media/original-metadata.md +++ b/doc/media/original-metadata.md @@ -2,7 +2,7 @@ Original Metadata ================= All data relevant for the game (e.g. how much costs building the castle? -What cultures exist? Can my priest overclock his "Wololo?") +What cultures exist? Can my priest overclock their "Wololo?") are stored in a binary format in the file `empires2_x1_p1.dat`. The format is described in the [huge struct definition](/openage/convert/value_object/read/media/datfile/empiresdat.py). diff --git a/doc/nyan/aoe2_nyan_tree.svg b/doc/nyan/aoe2_nyan_tree.svg index 8b45542911..ba6d22e8d4 100644 --- a/doc/nyan/aoe2_nyan_tree.svg +++ b/doc/nyan/aoe2_nyan_tree.svg @@ -1,7 +1,7 @@ -AbilityUsableability : AbilityMoveToTargetClearCommandQueuePopCommandQueueTaskTasknext : Nodetask : TaskTargetInRangeability : AbilityRangedmin_range : floatmax_range : floatApplyEffectMoveIdleCommandNextCommandnext : dict(Command, Node)SwitchConditionXORSwitchGateswitch : SwitchConditiondefault : NodePathTypeNextCommandcommand : CommandCommandInQueueConditionnext : NodeCommandInQueueWaitAbilityWaittime : floatEventUnit behaviour graphActivitygraph : ActivityAbilitynext : Nodeability : 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)NormalGatestances : set (DiplomaticStance)PassableModeallowed_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.3.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 thatcan be applied on othergame entitiesEffects/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 : intHuntConvertTypeConvertConvert (Ranged)SelfDestructRangedContinuousEffectmin_range : intmax_range : intMakeHarvestableresource_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 : floatDiscreteResistanceDiscreteEffectFlatAttributeChangetype : AttributeChangeTypemin_change_rate : optional(AttributeRate) = Nonemax_change_rate : optional(AttributeRate) = Nonechange_rate : AttributeRateignore_protection : set(ProtectingAttribute)ContinuousEffectAoE2Convertguaranteed_resist_rounds : intprotected_rounds : intprotection_round_recharge_time : floatAoE2Convertskip_guaranteed_rounds : intskip_protected_rounds : intConverttype : ConvertTypechance_resist : floatConverttype : ConvertTypemin_chance_success : optional(float) = Nonemax_chance_success : optional(float) = Nonechance_success : floatcost_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 : AttributeAttributeSettingattribute : Attributemin_value : intmax_value : intstarting_value : intTerrainOverlayterrain_overlay : TerrainAnimatedoverrides : set(AnimationOverride)Terrainsprite : fileCreatableGameEntitygame_entity : GameEntityvariants : set(Variant)cost : Costcreation_time : floatcreation_sounds : set(Sound)condition : set(LogicElement)placement_modes : set(PlacementMode)UseContingentamount : set(ResourceAmount)ProvideContingentamount : set(ResourceAmount)ResourceContingentmin_amount : intmax_amount : intLiteralscope : LiteralScopeProjectilearc : intaccuracy : set(Accuracy)target_mode : TargetModeignored_types : set(GameEntityType)unignore_entities : set(GameEntity)AttributeChangeTrackerattribute : Attributechange_progress : set(Progress)HitboxCollisionhitbox : HitboxNamedname : TranslatedStringdescription : TranslatedMarkupFilelong_description : TranslatedMarkupFileDropResourcescontainers : set(ResourceContainer)search_range : floatallowed_types : set(GameEntityType)blacklisted_entities : set(GameEntity)Herdableadjacent_discover_range : floatmode : HerdableModeStopPassablePathablehitbox : Hitboxmode : PassableModepath_costs : dict(PathType, int)Foundationfoundation_terrain : TerrainPerspectiveVariantangle : intRestockauto_restock : booltarget : ResourceSpotrestock_time : floatmanual_cost : Costauto_cost : Costamount : intPassiveStandGroundDefensiveAggressiveTradePosttrade_routes : set(TradeRoute)Followrange : floatPatrolGameEntityStancesearch_range : floatability_preference : orderedset(Ability)type_preference : orderedset(GameEntityType)GameEntityStancestances: set(GameEntityStance)SendBackToTaskallowed_types : set(GameEntityType)blacklisted_entities : set(GameEntity)TransferStoragestorage_element : GameEntitysource_container : EntityContainertarget_container : EntityContainerGameEntityProgressgame_entity : GameEntitystatus : ProgressStatusTechResearchedtech : TechVariantchanges : orderedset(Patch)priority : intActiveTransformTotarget_state : StateChangertransform_time : floattransform_progress : set(Progress)Selectableselection_box : SelectionBoxRallyPointWhite elements:AoE2 specific objectsYellow elements:Modifiers (handled byengine implementation)Modifiers (handled by engineimplementation)Green elements:Abilities (handled by engineimplementation)Pink elements:Basic nyan API objectsElevationDifferenceLowmin_elevation_difference : optional(float) = NoneFlyoverrelative_angle : floatflyover_types : set(GameEntityType)blacklisted_entities : set(GameEntity)Animationsprite : fileVillager Gather abilities canoverride the graphics ofIdle,Move,Die and Despawnvia CarryProgress objects withAnimationOverridesAbilities andStorageElementscan use theseOverride typesto change anyother ability'sanimation.If min_projectiles is greater than thenumber of Projectiles in projectiles,the last projectile in the orderedsetshould be usedIf min_projectiles is greater thanthe number of Projectiles inprojectiles, the last projectilein the orderedset should be usedIn AoE2 there is onlyone HarvestProgressState in the interval[0,100], but AoM hadmore.one HarvestProgress Statein the interval [0,100],but AoM had more.Stores what happens aftera percentage ofconstruction, damage,transformation, etc. isreachedProgressproperties : dict(ProgressProperty, ProgressProperty) = {}type : ProgressTypeleft_boundary : floatright_boundary : floatciv_setup patches the uniquegame_setup patches the uniquefeatures into the objects(graphics, techs, boni, abilities,etc.)RemoveStoragecontainer : EntityContainerstorage_elements : set(GameEntity)CollectStoragecontainer : EntityContainerstorage_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)Modifier should only be used incases where Patches don'tModifier should only be usedin cases where Patches don'twork. For example, if thebonus is a percentage value orcontinuously stacks (likebonus is a percentage valueor continuously stacks (likeresources from the Feitoria).Modifier objects can still bepatched.IdlePlayerSetupname : TranslatedStringdescription : TranslatedMarkupFilelong_description : TranslatedMarkupFileleader_names : set(TranslatedString)modifiers : set(Modifier)starting_resources : set(ResourceAmount)game_setup : orderedset(Patch)Despawnactivation_condition : set(LogicElement)despawn_condition : set(LogicElement)despawn_time : floatstate_change : optional(StateChanger) = NoneTauntactivation_message : textdisplay_message : TranslatedStringsound : SoundLiveattributes : set(AttributeSetting)Harvestableresources : ResourceSpotharvest_progress : set(HarvestProgress)restock_progress : set(RestockProgress)harvest_progress : set(Progress)restock_progress : set(Progress)gatherer_limit : intharvestable_by_default : boolPassiveTransformTocondition : set(LogicElement)transform_time : floattarget_state : StateChangertransform_progress : set(Progress)Cheatactivation_message : textchanges : orderedset(Patch)Flyheight : floatResistanceresistances : set(Resistance)ShootProjectileprojectiles : orderedset(GameEntity)min_projectiles : intmax_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)Movespeed : floatmodes : set(MoveMode)path_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 : intTerrainname : TranslatedStringtypes : set(TerrainType)terrain_graphic : Terrainsound : Soundambience : set(TerrainAmbient)path_costs : dict(PathType, int)ResearchableTechtech : Techcost : Costresearch_time : floatresearch_sounds : set(Sound)condition : set(LogicElement)TranslatedSoundtranslations : set(LanguageSoundPair)TranslatedMarkupFiletranslations : set(LanguageMarkupPair)TranslatedObjectTranslatedStringtranslations : set(LanguageTextPair)Resourcename : TranslatedStringmax_storage : intResourceSpotresource : Resourcemax_amount : intstarting_amount : intdecay_rate : floatDropSiteaccepts_from : set(ResourceContainer)GameEntitytypes : set(GameEntityType)abilities : set(Ability)modifiers : set(Modifier)variants : set(Variant)EntityObject - + // Uncomment the following line to change the fontsize and font: // fontsize=10 // fontfamily=SansSerif //possible: SansSerif,Serif,Monospaced @@ -24,27 +24,27 @@ // This text will be stored with each diagram; use it for notes. - 7 + 9 UMLClass - 1666 - 2520 - 70 - 42 + 1080 + 3249 + 90 + 54 -*Entity* +*Object* bg=red UMLClass - 1120 - 2506 - 210 - 84 + 378 + 3231 + 270 + 108 *GameEntity* @@ -59,10 +59,10 @@ variants : set(Variant) UMLClass - 4284 - 2191 - 203 - 56 + 4446 + 2826 + 261 + 72 *DropSite* bg=green @@ -74,10 +74,10 @@ accepts_from : set(ResourceContainer) Relation - 1323 - 2534 - 357 - 21 + 639 + 3267 + 459 + 27 lt=<<- 490.0;10.0;10.0;10.0 @@ -85,10 +85,10 @@ accepts_from : set(ResourceContainer) UMLClass - 1428 - 2184 - 210 - 84 + 774 + 2817 + 270 + 108 *ResourceSpot* @@ -103,10 +103,10 @@ decay_rate : float UMLClass - 1470 - 2310 - 126 - 56 + 828 + 2979 + 162 + 72 *Resource* bg=pink @@ -119,10 +119,10 @@ max_storage : int UMLClass - 833 - 1869 - 189 - 56 + 9 + 2412 + 243 + 72 *TranslatedString* bg=pink @@ -134,10 +134,10 @@ translations : set(LanguageTextPair) UMLClass - 1092 - 1981 - 105 - 42 + 342 + 2556 + 135 + 54 *TranslatedObject* @@ -147,10 +147,10 @@ bg=pink UMLClass - 1036 - 1869 - 203 - 56 + 270 + 2412 + 261 + 72 *TranslatedMarkupFile* bg=pink @@ -162,10 +162,10 @@ translations : set(LanguageMarkupPair) UMLClass - 1253 - 1869 - 196 - 56 + 549 + 2412 + 252 + 72 *TranslatedSound* bg=pink @@ -177,10 +177,10 @@ translations : set(LanguageSoundPair) Relation - 1134 - 1918 - 21 - 77 + 396 + 2475 + 27 + 99 lt=<<- 10.0;90.0;10.0;10.0 @@ -188,10 +188,10 @@ translations : set(LanguageSoundPair) Relation - 917 - 1918 - 238 - 42 + 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 - 1134 - 1918 - 231 - 42 + 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 - 3115 - 1694 - 224 - 91 + 2943 + 2187 + 288 + 117 *ResearchableTech* bg=pink @@ -229,10 +229,10 @@ condition : set(LogicElement) UMLClass - 1764 - 2611 - 224 - 91 + 1206 + 3348 + 288 + 135 *Terrain* bg=pink @@ -242,16 +242,17 @@ name : TranslatedString types : set(TerrainType) terrain_graphic : Terrain sound : Sound -ambience : set(TerrainAmbient) +ambience : set(TerrainAmbient) +path_costs : dict(PathType, int) UMLClass - 1890 - 2730 - 133 - 56 + 1386 + 3519 + 171 + 72 *TerrainAmbient* bg=pink @@ -264,10 +265,10 @@ max_density : int UMLClass - 2107 - 2275 - 133 - 56 + 1647 + 2934 + 171 + 72 *Sound* bg=pink @@ -280,10 +281,10 @@ sounds : orderedset(file) UMLClass - 1953 - 875 - 252 - 56 + 1449 + 1134 + 324 + 72 *Modifier* bg=pink @@ -295,10 +296,10 @@ properties : dict(ModifierProperty, ModifierProperty) = {} UMLClass - 1764 - 2471 - 238 - 56 + 1206 + 3186 + 306 + 72 *Patch* bg=pink @@ -311,10 +312,10 @@ patch : NyanPatch UMLClass - 2107 - 2457 - 147 - 56 + 1647 + 3168 + 189 + 72 *Mod* bg=pink @@ -327,10 +328,10 @@ patches : orderedset(Patch) Relation - 1729 - 2534 - 1785 - 21 + 1161 + 3267 + 2295 + 27 lt=<<- 10.0;10.0;2530.0;10.0 @@ -338,10 +339,10 @@ patches : orderedset(Patch) UMLClass - 3500 - 2520 - 238 - 63 + 3438 + 3249 + 306 + 81 *Ability* bg=pink @@ -353,10 +354,10 @@ properties : dict(AbilityProperty, AbilityProperty) = {} UMLClass - 3367 - 2807 - 126 - 56 + 3267 + 3618 + 162 + 72 *Animated* bg=pink @@ -368,10 +369,10 @@ animations : set(Animation) UMLClass - 3367 - 2744 - 126 - 56 + 3267 + 3537 + 162 + 72 *CommandSound* bg=pink @@ -384,26 +385,27 @@ sounds : set(Sound) UMLClass - 3850 - 2688 - 126 - 56 + 3888 + 3465 + 162 + 90 *Move* bg=green -- speed : float -modes : set(MoveMode) +modes : set(MoveMode) +path_type : PathType UMLClass - 3682 - 2996 - 203 - 56 + 3672 + 3861 + 261 + 72 *Formation* bg=green @@ -415,10 +417,10 @@ formations : set(GameEntityFormation) UMLClass - 4235 - 3024 - 126 - 56 + 4383 + 3897 + 162 + 72 *RegenerateAttribute* bg=green @@ -430,10 +432,10 @@ rate : AttributeRate UMLClass - 5005 - 1365 - 224 - 238 + 5373 + 1764 + 288 + 279 *ShootProjectile* bg=green @@ -442,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 @@ -467,10 +467,10 @@ blacklisted_entities : set(GameEntity) UMLClass - 4214 - 3164 - 147 - 56 + 4356 + 4077 + 189 + 72 *Resistance* bg=green @@ -482,10 +482,10 @@ resistances : set(Resistance) UMLClass - 3696 - 2688 - 112 - 56 + 3690 + 3465 + 144 + 72 *Fly* bg=green @@ -497,10 +497,10 @@ height : float Relation - 1190 - 1995 - 525 - 539 + 468 + 2574 + 675 + 693 lt=<<- 730.0;750.0;730.0;10.0;10.0;10.0 @@ -508,10 +508,10 @@ height : float UMLClass - 2310 - 2142 - 133 - 56 + 1908 + 2763 + 171 + 72 *Cheat* bg=pink @@ -524,10 +524,10 @@ changes : orderedset(Patch) Relation - 1589 - 2338 - 126 - 21 + 981 + 3015 + 162 + 27 lt=- 160.0;10.0;10.0;10.0 @@ -535,10 +535,10 @@ changes : orderedset(Patch) Relation - 1526 - 2261 - 21 - 63 + 900 + 2916 + 27 + 81 lt=<. 10.0;70.0;10.0;10.0 @@ -546,10 +546,10 @@ changes : orderedset(Patch) Relation - 1631 - 2205 - 84 - 21 + 1035 + 2844 + 108 + 27 lt=- 10.0;10.0;100.0;10.0 @@ -557,10 +557,10 @@ changes : orderedset(Patch) Relation - 1911 - 2695 - 21 - 49 + 1431 + 3474 + 27 + 63 lt=<. 10.0;50.0;10.0;10.0 @@ -568,10 +568,10 @@ changes : orderedset(Patch) Relation - 2261 - 2226 - 63 - 21 + 1845 + 2871 + 81 + 27 lt=- 10.0;10.0;70.0;10.0 @@ -579,21 +579,21 @@ changes : orderedset(Patch) Relation - 1862 - 2534 - 21 - 91 + 1332 + 3267 + 27 + 99 lt=- - 10.0;10.0;10.0;110.0 + 10.0;10.0;10.0;90.0 UMLClass - 5425 - 2513 - 203 - 77 + 5913 + 3240 + 261 + 99 *PassiveTransformTo* bg=green @@ -608,18 +608,18 @@ transform_progress : set(Progress) UMLClass - 4032 - 2023 - 210 - 98 + 4122 + 2610 + 270 + 126 *Harvestable* bg=green -- resources : ResourceSpot -harvest_progress : set(HarvestProgress) -restock_progress : set(RestockProgress) +harvest_progress : set(Progress) +restock_progress : set(Progress) gatherer_limit : int harvestable_by_default : bool @@ -627,10 +627,10 @@ harvestable_by_default : bool UMLClass - 4298 - 3241 - 168 - 56 + 4464 + 4176 + 216 + 72 *Live* bg=green @@ -642,10 +642,10 @@ attributes : set(AttributeSetting) UMLClass - 2310 - 2205 - 175 - 70 + 1908 + 2844 + 225 + 90 *Taunt* bg=pink @@ -659,10 +659,10 @@ sound : Sound Relation - 2170 - 2506 - 21 - 49 + 1728 + 3231 + 27 + 63 lt=- 10.0;10.0;10.0;50.0 @@ -670,10 +670,10 @@ sound : Sound Relation - 2072 - 924 - 21 - 1631 + 1602 + 1197 + 27 + 2097 lt=- 10.0;10.0;10.0;2310.0 @@ -681,10 +681,10 @@ sound : Sound Relation - 1862 - 2520 - 21 - 35 + 1332 + 3249 + 27 + 45 lt=- 10.0;10.0;10.0;30.0 @@ -692,10 +692,10 @@ sound : Sound Relation - 2261 - 2163 - 63 - 21 + 1845 + 2790 + 81 + 27 lt=- 70.0;10.0;10.0;10.0 @@ -703,10 +703,10 @@ sound : Sound UMLClass - 5327 - 2618 - 217 - 77 + 5787 + 3375 + 279 + 99 *Despawn* bg=green @@ -721,10 +721,10 @@ state_change : optional(StateChanger) = None UMLClass - 1274 - 3066 - 217 - 112 + 576 + 3951 + 279 + 144 *PlayerSetup* bg=pink @@ -742,10 +742,10 @@ game_setup : orderedset(Patch) Relation - 1372 - 2534 - 21 - 546 + 702 + 3267 + 27 + 702 lt=- 10.0;760.0;10.0;10.0 @@ -753,10 +753,10 @@ game_setup : orderedset(Patch) Relation - 2037 - 2534 - 21 - 294 + 1557 + 3267 + 27 + 378 lt=- 10.0;10.0;10.0;400.0 @@ -764,10 +764,10 @@ game_setup : orderedset(Patch) UMLClass - 5327 - 2569 - 84 - 42 + 5787 + 3312 + 108 + 54 *Idle* @@ -777,22 +777,29 @@ bg=green UMLNote - 1918 - 945 - 147 - 112 - - Modifier should only be used in cases where Patches don't work. For example, if the bonus is a percentage value or continuously stacks (like resources from the Feitoria). Modifier objects can still be patched. + 1404 + 1224 + 189 + 144 + + Modifier should only be used +in cases where Patches don't +work. For example, if the +bonus is a percentage value +or continuously stacks (like +resources from the Feitoria). +Modifier objects can still be +patched. bg=blue UMLClass - 1995 - 770 - 154 - 56 + 1503 + 999 + 198 + 72 *ContinuousResource* bg=yellow @@ -804,10 +811,10 @@ rates : set(ResourceRate) UMLClass - 1470 - 2401 - 126 - 56 + 828 + 3096 + 162 + 72 *ResourceAmount* bg=pink @@ -820,10 +827,10 @@ amount : int UMLClass - 1204 - 2387 - 84 - 42 + 486 + 3078 + 108 + 54 *GoldAmount* @@ -832,10 +839,10 @@ amount : int UMLClass - 1204 - 2436 - 84 - 42 + 486 + 3141 + 108 + 54 *StoneAmount* @@ -844,10 +851,10 @@ amount : int UMLClass - 1204 - 2338 - 84 - 42 + 486 + 3015 + 108 + 54 *WoodAmount* @@ -856,10 +863,10 @@ amount : int UMLClass - 1204 - 2289 - 84 - 42 + 486 + 2952 + 108 + 54 *FoodAmount* @@ -868,10 +875,10 @@ amount : int Relation - 1589 - 2422 - 126 - 21 + 981 + 3123 + 162 + 27 lt=- 160.0;10.0;10.0;10.0 @@ -879,10 +886,10 @@ amount : int Relation - 1526 - 2359 - 21 - 56 + 900 + 3042 + 27 + 72 lt=<. 10.0;10.0;10.0;60.0 @@ -890,10 +897,10 @@ amount : int Relation - 1295 - 2422 - 189 - 21 + 603 + 3123 + 243 + 27 lt=<<- 250.0;10.0;10.0;10.0 @@ -901,10 +908,10 @@ amount : int Relation - 1281 - 2303 - 35 - 70 + 585 + 2970 + 45 + 90 lt=- 10.0;10.0;30.0;10.0;30.0;80.0 @@ -912,10 +919,10 @@ amount : int Relation - 1281 - 2352 - 35 - 70 + 585 + 3033 + 45 + 90 lt=- 10.0;10.0;30.0;10.0;30.0;80.0 @@ -923,10 +930,10 @@ amount : int Relation - 1281 - 2415 - 35 - 56 + 585 + 3114 + 45 + 72 lt=- 10.0;60.0;30.0;60.0;30.0;10.0 @@ -934,10 +941,10 @@ amount : int Relation - 1281 - 2401 - 35 - 35 + 585 + 3096 + 45 + 45 lt=- 10.0;10.0;30.0;10.0;30.0;30.0 @@ -945,10 +952,10 @@ amount : int Relation - 2072 - 819 - 21 - 70 + 1602 + 1062 + 27 + 90 lt=<<- 10.0;80.0;10.0;10.0 @@ -956,10 +963,10 @@ amount : int UMLClass - 1974 - 686 - 84 - 42 + 1476 + 891 + 108 + 54 *FeitoriaBonus* @@ -968,10 +975,10 @@ amount : int Relation - 2009 - 721 - 84 - 63 + 1521 + 936 + 108 + 81 lt=<<- 100.0;70.0;100.0;40.0;10.0;40.0;10.0;10.0 @@ -979,10 +986,10 @@ amount : int UMLClass - 1512 - 707 - 133 - 56 + 882 + 918 + 171 + 72 *AttributeSettingsValue* bg=yellow @@ -994,10 +1001,10 @@ attribute : Attribute UMLClass - 1547 - 658 - 98 - 42 + 927 + 855 + 126 + 54 *MoveSpeed* @@ -1007,10 +1014,10 @@ bg=yellow UMLClass - 1491 - 483 - 154 - 56 + 855 + 630 + 198 + 72 *GatheringRate* bg=yellow @@ -1022,10 +1029,10 @@ resource_spot : ResourceSpot Relation - 1673 - 336 - 420 - 532 + 1089 + 441 + 540 + 684 lt=- 580.0;740.0;10.0;740.0;10.0;10.0 @@ -1033,10 +1040,10 @@ resource_spot : ResourceSpot Relation - 1638 - 672 - 56 - 21 + 1044 + 873 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -1044,10 +1051,10 @@ resource_spot : ResourceSpot Relation - 1638 - 504 - 56 - 21 + 1044 + 657 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -1055,10 +1062,10 @@ resource_spot : ResourceSpot UMLClass - 2100 - 686 - 84 - 42 + 1638 + 891 + 108 + 54 *RelicBonus* @@ -1067,10 +1074,10 @@ resource_spot : ResourceSpot Relation - 2072 - 721 - 84 - 42 + 1602 + 936 + 108 + 54 lt=- 10.0;40.0;100.0;40.0;100.0;10.0 @@ -1078,10 +1085,10 @@ resource_spot : ResourceSpot UMLClass - 3360 - 1806 - 196 - 56 + 3258 + 2331 + 252 + 72 *Create* bg=green @@ -1093,10 +1100,10 @@ creatables : set(CreatableGameEntity) UMLClass - 3892 - 1869 - 161 - 56 + 3942 + 2412 + 207 + 72 *Trade* bg=green @@ -1110,10 +1117,10 @@ container : ResourceContainer UMLClass - 3360 - 1694 - 196 - 56 + 3258 + 2187 + 252 + 72 *Research* bg=green @@ -1125,10 +1132,10 @@ researchables : set(ResearchableTech) UMLClass - 1092 - 2639 - 105 - 56 + 342 + 3402 + 135 + 72 *RandomVariant* @@ -1140,10 +1147,10 @@ chance_share : float Relation - 1358 - 2660 - 35 - 21 + 684 + 3429 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -1151,10 +1158,10 @@ chance_share : float UMLClass - 4067 - 1148 - 182 - 63 + 4167 + 1485 + 234 + 81 *Storage* bg=green @@ -1168,10 +1175,10 @@ empty_condition : set(LogicElement) UMLClass - 4319 - 1036 - 217 - 84 + 4491 + 1341 + 279 + 108 *StorageElementDefinition* bg=pink @@ -1186,21 +1193,21 @@ state_change : optional(StateChanger) = None Relation - 4151 - 1113 - 21 - 49 + 4275 + 1449 + 27 + 54 lt=<. - 10.0;10.0;10.0;50.0 + 10.0;10.0;10.0;40.0 UMLClass - 3927 - 1379 - 210 - 63 + 3987 + 1782 + 270 + 81 *CollectStorage* bg=green @@ -1213,10 +1220,10 @@ storage_elements : set(GameEntity) UMLClass - 3927 - 1309 - 210 - 63 + 3987 + 1692 + 270 + 81 *RemoveStorage* bg=green @@ -1229,22 +1236,25 @@ storage_elements : set(GameEntity) UMLNote - 1337 - 3185 - 154 - 56 + 657 + 4104 + 198 + 72 - civ_setup patches the unique features into the objects (graphics, techs, boni, abilities, etc.) + game_setup patches the unique +features into the objects +(graphics, techs, boni, abilities, +etc.) bg=blue UMLClass - 2254 - 3346 - 259 - 77 + 1836 + 4311 + 333 + 99 *Progress* bg=pink @@ -1260,10 +1270,10 @@ right_boundary : float Relation - 2331 - 2534 - 21 - 826 + 1935 + 3267 + 27 + 1062 lt=- 10.0;10.0;10.0;1160.0 @@ -1271,71 +1281,90 @@ right_boundary : float UMLNote - 2345 - 3276 - 133 - 63 - - Stores what happens after a percentage of construction, damage, transformation, etc. is reached + 1953 + 4221 + 171 + 81 + + Stores what happens after +a percentage of +construction, damage, +transformation, etc. is +reached bg=blue UMLNote - 2639 - 2954 - 119 - 63 - - In AoE2 there is only one HarvestProgress State in the interval [0,100], but AoM had more. + 2331 + 3807 + 180 + 81 + + In AoE2 there is only +one HarvestProgress State +in the interval [0,100], +but AoM had more. bg=blue UMLNote - 5236 - 1435 - 168 - 63 - - If min_projectiles is greater than the number of Projectiles in projectiles, the last projectile in the orderedset should be used + 5670 + 1854 + 216 + 81 + + If min_projectiles is greater than +the number of Projectiles in +projectiles, the last projectile +in the orderedset should be used bg=blue UMLNote - 2072 - 2632 - 91 - 84 - - Abilities and StorageElements can use these Override types -to change any other ability's animation. + 1602 + 3393 + 117 + 108 + + Abilities and +StorageElements +can use these +Override types +to change any +other ability's +animation. bg=blue UMLNote - 4284 - 2422 - 147 - 70 + 4446 + 3123 + 189 + 90 - Villager Gather abilities can override the graphics of Idle,Move,Die and Despawn via CarryProgress objects with AnimationOverrides + Villager Gather abilities can +override the graphics of +Idle,Move,Die and Despawn +via CarryProgress objects with +AnimationOverrides bg=blue UMLClass - 2310 - 2331 - 133 - 56 + 1908 + 3006 + 171 + 72 *Animation* bg=pink @@ -1347,10 +1376,10 @@ sprite : file Relation - 2233 - 2296 - 49 - 21 + 1809 + 2961 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -1358,10 +1387,10 @@ sprite : file UMLClass - 1141 - 168 - 175 - 63 + 405 + 225 + 225 + 81 *Flyover* bg=yellow @@ -1375,10 +1404,10 @@ blacklisted_entities : set(GameEntity) Relation - 1330 - 224 - 259 - 21 + 648 + 297 + 333 + 27 lt=- 350.0;10.0;10.0;10.0 @@ -1386,10 +1415,10 @@ blacklisted_entities : set(GameEntity) UMLClass - 1092 - 238 - 224 - 49 + 342 + 315 + 288 + 63 *ElevationDifferenceLow* bg=yellow @@ -1401,10 +1430,10 @@ min_elevation_difference : optional(float) = None Relation - 1309 - 189 - 42 - 21 + 621 + 252 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -1412,10 +1441,10 @@ min_elevation_difference : optional(float) = None Relation - 1309 - 259 - 42 - 21 + 621 + 342 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -1423,10 +1452,10 @@ min_elevation_difference : optional(float) = None UMLNote - 0 - 889 - 133 - 49 + 9 + 99 + 171 + 63 Pink elements: @@ -1438,38 +1467,40 @@ bg=pink UMLNote - 0 - 945 - 133 - 49 + 9 + 171 + 171 + 63 Green elements: -Abilities (handled by engine implementation) +Abilities (handled by engine +implementation) bg=green UMLNote - 0 - 1001 - 133 - 49 + 9 + 243 + 171 + 63 Yellow elements: -Modifiers (handled by engine implementation) +Modifiers (handled by engine +implementation) bg=yellow UMLNote - 0 - 1127 - 133 - 49 + 9 + 405 + 171 + 63 White elements: @@ -1479,10 +1510,10 @@ AoE2 specific objects UMLClass - 3472 - 1911 - 84 - 42 + 3402 + 2466 + 108 + 54 *RallyPoint* @@ -1492,10 +1523,10 @@ bg=green UMLClass - 5124 - 2702 - 161 - 63 + 5526 + 3483 + 207 + 81 *Selectable* bg=green @@ -1507,10 +1538,10 @@ selection_box : SelectionBox UMLClass - 3472 - 3143 - 231 - 70 + 3402 + 4050 + 297 + 90 *ActiveTransformTo* bg=green @@ -1524,10 +1555,10 @@ transform_progress : set(Progress) UMLClass - 1218 - 2639 - 147 - 56 + 504 + 3402 + 189 + 72 *Variant* bg=pink @@ -1540,10 +1571,10 @@ priority : int Relation - 1190 - 2660 - 42 - 21 + 468 + 3429 + 54 + 27 lt=<<- 40.0;10.0;10.0;10.0 @@ -1551,10 +1582,10 @@ priority : int UMLClass - 1932 - 2982 - 147 - 56 + 1422 + 3843 + 189 + 72 *TechResearched* bg=pink @@ -1566,10 +1597,10 @@ tech : Tech UMLClass - 1918 - 3045 - 161 - 63 + 1404 + 3924 + 207 + 81 *GameEntityProgress* bg=pink @@ -1582,10 +1613,10 @@ status : ProgressStatus Relation - 2086 - 2954 - 21 - 630 + 1620 + 3807 + 27 + 810 lt=<<- 10.0;10.0;10.0;880.0 @@ -1593,10 +1624,10 @@ status : ProgressStatus UMLClass - 3955 - 1232 - 182 - 70 + 4023 + 1593 + 234 + 90 *TransferStorage* bg=green @@ -1610,10 +1641,10 @@ target_container : EntityContainer Relation - 2037 - 2576 - 49 - 21 + 1557 + 3321 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -1621,10 +1652,10 @@ target_container : EntityContainer UMLClass - 4179 - 1379 - 224 - 63 + 4311 + 1782 + 288 + 81 *SendBackToTask* bg=green @@ -1637,10 +1668,10 @@ blacklisted_entities : set(GameEntity) UMLClass - 3682 - 3059 - 175 - 56 + 3672 + 3942 + 225 + 72 *GameEntityStance* bg=green @@ -1652,10 +1683,10 @@ stances: set(GameEntityStance) UMLClass - 3913 - 3066 - 238 - 63 + 3969 + 3951 + 306 + 81 *GameEntityStance* bg=pink @@ -1669,10 +1700,10 @@ type_preference : orderedset(GameEntityType) Relation - 3850 - 3080 - 77 - 21 + 3888 + 3969 + 99 + 27 lt=<. 90.0;10.0;10.0;10.0 @@ -1680,10 +1711,10 @@ type_preference : orderedset(GameEntityType) UMLClass - 4053 - 2688 - 77 - 42 + 4149 + 3465 + 99 + 54 *Patrol* @@ -1693,10 +1724,10 @@ bg=pink UMLClass - 4053 - 2737 - 112 - 56 + 4149 + 3528 + 144 + 72 *Follow* bg=pink @@ -1708,10 +1739,10 @@ range : float Relation - 4018 - 2660 - 21 - 287 + 4104 + 3429 + 27 + 369 lt=<<- 10.0;10.0;10.0;390.0 @@ -1719,10 +1750,10 @@ range : float Relation - 4018 - 2758 - 49 - 21 + 4104 + 3555 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -1730,10 +1761,10 @@ range : float UMLClass - 3892 - 1806 - 203 - 56 + 3942 + 2331 + 261 + 72 *TradePost* bg=green @@ -1745,10 +1776,10 @@ trade_routes : set(TradeRoute) UMLClass - 3885 - 3143 - 70 - 42 + 3933 + 4050 + 90 + 54 *Aggressive* @@ -1758,10 +1789,10 @@ bg=pink UMLClass - 3885 - 3192 - 70 - 42 + 3933 + 4113 + 90 + 54 *Defensive* @@ -1771,10 +1802,10 @@ bg=pink UMLClass - 3871 - 3241 - 84 - 42 + 3915 + 4176 + 108 + 54 *StandGround* @@ -1784,10 +1815,10 @@ bg=pink UMLClass - 3885 - 3290 - 70 - 42 + 3933 + 4239 + 90 + 54 *Passive* @@ -1797,10 +1828,10 @@ bg=pink Relation - 3948 - 3206 - 42 - 21 + 4014 + 4131 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -1808,10 +1839,10 @@ bg=pink Relation - 3969 - 3122 - 21 - 203 + 4041 + 4023 + 27 + 261 lt=<<- 10.0;10.0;10.0;270.0 @@ -1819,10 +1850,10 @@ bg=pink Relation - 3948 - 3255 - 42 - 21 + 4014 + 4194 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -1830,10 +1861,10 @@ bg=pink Relation - 3948 - 3304 - 42 - 21 + 4014 + 4257 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -1841,10 +1872,10 @@ bg=pink UMLClass - 4053 - 2128 - 189 - 98 + 4149 + 2745 + 243 + 126 *Restock* bg=green @@ -1861,10 +1892,10 @@ amount : int UMLClass - 1092 - 2702 - 119 - 56 + 342 + 3483 + 153 + 72 *PerspectiveVariant* @@ -1876,10 +1907,10 @@ angle : int Relation - 1204 - 2688 - 98 - 56 + 486 + 3465 + 126 + 72 lt=<<- 120.0;10.0;120.0;60.0;10.0;60.0 @@ -1887,10 +1918,10 @@ angle : int UMLClass - 5040 - 3332 - 154 - 56 + 5418 + 4293 + 198 + 72 *Foundation* bg=green @@ -1902,26 +1933,26 @@ foundation_terrain : Terrain UMLClass - 4403 - 3017 - 154 - 63 + 4599 + 3897 + 198 + 72 - *Passable* + *Pathable* bg=green -- hitbox : Hitbox -mode : PassableMode +path_costs : dict(PathType, int) UMLClass - 5327 - 2471 - 84 - 42 + 5787 + 3186 + 108 + 54 // Returns unit to Idle state @@ -1932,10 +1963,10 @@ bg=green UMLClass - 5327 - 2751 - 224 - 70 + 5787 + 3546 + 288 + 90 *Herdable* bg=green @@ -1948,10 +1979,10 @@ mode : HerdableMode UMLClass - 4284 - 2254 - 182 - 77 + 4446 + 2907 + 234 + 99 *DropResources* bg=green @@ -1967,10 +1998,10 @@ blacklisted_entities : set(GameEntity) UMLClass - 4158 - 2947 - 203 - 70 + 4284 + 3798 + 261 + 90 *Named* bg=green @@ -1984,12 +2015,12 @@ long_description : TranslatedMarkupFile UMLClass - 4403 - 3087 - 98 - 56 + 4599 + 3987 + 198 + 72 - *Hitbox* + *Collision* bg=green -- @@ -1999,10 +2030,10 @@ hitbox : Hitbox UMLClass - 4823 - 3332 - 175 - 56 + 5139 + 4293 + 225 + 72 *AttributeChangeTracker* bg=green @@ -2015,10 +2046,10 @@ change_progress : set(Progress) UMLClass - 5005 - 1246 - 224 - 105 + 5373 + 1611 + 288 + 135 *Projectile* bg=green @@ -2037,10 +2068,10 @@ unignore_entities : set(GameEntity) UMLClass - 1960 - 2905 - 147 - 56 + 1458 + 3744 + 189 + 72 *Literal* bg=pink @@ -2052,10 +2083,10 @@ scope : LiteralScope UMLClass - 1323 - 2310 - 126 - 56 + 639 + 2979 + 162 + 72 *ResourceContingent* bg=pink @@ -2068,10 +2099,10 @@ max_amount : int Relation - 1442 - 2331 - 42 - 21 + 792 + 3006 + 54 + 27 lt=<<- 40.0;10.0;10.0;10.0 @@ -2079,10 +2110,10 @@ max_amount : int UMLClass - 4284 - 2065 - 175 - 56 + 4446 + 2664 + 225 + 72 *ProvideContingent* bg=green @@ -2094,10 +2125,10 @@ amount : set(ResourceAmount) UMLClass - 4284 - 2128 - 175 - 56 + 4446 + 2745 + 225 + 72 *UseContingent* bg=green @@ -2109,10 +2140,10 @@ amount : set(ResourceAmount) UMLClass - 3115 - 1792 - 224 - 112 + 2943 + 2313 + 288 + 144 *CreatableGameEntity* bg=pink @@ -2131,10 +2162,10 @@ placement_modes : set(PlacementMode) Relation - 3332 - 1827 - 42 - 21 + 3222 + 2358 + 54 + 27 lt=<. 10.0;10.0;40.0;10.0 @@ -2142,10 +2173,10 @@ placement_modes : set(PlacementMode) UMLClass - 2310 - 2457 - 133 - 56 + 1908 + 3168 + 171 + 72 *Terrain* bg=pink @@ -2157,10 +2188,10 @@ sprite : file Relation - 2261 - 2163 - 21 - 392 + 1845 + 2790 + 27 + 504 lt=- 10.0;10.0;10.0;540.0 @@ -2168,10 +2199,10 @@ sprite : file Relation - 2261 - 2352 - 63 - 21 + 1845 + 3033 + 81 + 27 lt=- 70.0;10.0;10.0;10.0 @@ -2179,10 +2210,10 @@ sprite : file UMLClass - 2555 - 3262 - 161 - 56 + 2223 + 4203 + 207 + 72 *Animated* bg=pink @@ -2195,10 +2226,10 @@ overrides : set(AnimationOverride) UMLClass - 2555 - 3388 - 133 - 56 + 2223 + 4365 + 171 + 72 *TerrainOverlay* bg=pink @@ -2212,10 +2243,10 @@ terrain_overlay : Terrain Relation - 2527 - 3283 - 42 - 21 + 2187 + 4230 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -2223,10 +2254,10 @@ terrain_overlay : Terrain Relation - 2527 - 3409 - 42 - 21 + 2187 + 4392 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -2234,10 +2265,10 @@ terrain_overlay : Terrain UMLClass - 4305 - 3339 - 154 - 77 + 4473 + 4302 + 198 + 99 *AttributeSetting* bg=pink @@ -2252,10 +2283,10 @@ starting_value : int UMLClass - 4305 - 3535 - 154 - 56 + 4473 + 4554 + 198 + 72 *ProtectingAttribute* bg=pink @@ -2267,10 +2298,10 @@ protects : Attribute Relation - 4375 - 3290 - 21 - 63 + 4563 + 4239 + 27 + 81 lt=<. 10.0;70.0;10.0;10.0 @@ -2278,10 +2309,10 @@ protects : Attribute Relation - 4375 - 3507 - 21 - 42 + 4563 + 4518 + 27 + 54 lt=<<- 10.0;10.0;10.0;40.0 @@ -2289,10 +2320,10 @@ protects : Attribute UMLClass - 4333 - 3619 - 105 - 42 + 4509 + 4662 + 135 + 54 *Shield (SWGB)* @@ -2301,10 +2332,10 @@ protects : Attribute Relation - 4375 - 3584 - 21 - 49 + 4563 + 4617 + 27 + 63 lt=<<- 10.0;10.0;10.0;50.0 @@ -2312,10 +2343,10 @@ protects : Attribute UMLClass - 4501 - 3458 - 70 - 42 + 4725 + 4455 + 90 + 54 *Faith* @@ -2324,10 +2355,10 @@ protects : Attribute Relation - 4459 - 3465 - 56 - 21 + 4671 + 4464 + 72 + 27 lt=<<- 10.0;10.0;60.0;10.0 @@ -2335,10 +2366,10 @@ protects : Attribute UMLClass - 4235 - 3087 - 126 - 56 + 4383 + 3978 + 162 + 72 *LineOfSight* bg=green @@ -2350,10 +2381,10 @@ range : float UMLClass - 4403 - 2947 - 147 - 56 + 4599 + 3798 + 189 + 72 *Cloak (SWGB)* bg=green @@ -2366,10 +2397,10 @@ interrupt_cooldown : float UMLClass - 5264 - 1218 - 189 - 91 + 5706 + 1575 + 243 + 117 *Accuracy* bg=pink @@ -2386,10 +2417,10 @@ blacklisted_entities : set(GameEntity) Relation - 5222 - 1246 - 56 - 21 + 5652 + 1611 + 72 + 27 lt=<. 60.0;10.0;10.0;10.0 @@ -2397,10 +2428,10 @@ blacklisted_entities : set(GameEntity) UMLClass - 4137 - 3423 - 112 - 56 + 4257 + 4410 + 144 + 72 *AttributeAmount* bg=pink @@ -2413,10 +2444,10 @@ amount : int Relation - 4242 - 3458 - 70 - 21 + 4392 + 4455 + 90 + 27 lt=<. 80.0;10.0;10.0;10.0 @@ -2424,10 +2455,10 @@ amount : int UMLClass - 1358 - 2485 - 98 - 42 + 684 + 3204 + 126 + 54 *GameEntityType* @@ -2437,10 +2468,10 @@ bg=pink Relation - 1323 - 2506 - 49 - 21 + 639 + 3231 + 63 + 27 lt=<. 50.0;10.0;10.0;10.0 @@ -2448,10 +2479,10 @@ bg=pink UMLClass - 4501 - 3507 - 70 - 42 + 4725 + 4518 + 90 + 54 *Health* @@ -2460,10 +2491,10 @@ bg=pink Relation - 4480 - 3465 - 35 - 77 + 4698 + 4464 + 45 + 99 lt=- 10.0;10.0;10.0;90.0;30.0;90.0 @@ -2471,10 +2502,10 @@ bg=pink Relation - 1400 - 2520 - 21 - 35 + 738 + 3249 + 27 + 45 lt=- 10.0;30.0;10.0;10.0 @@ -2482,10 +2513,10 @@ bg=pink Relation - 1505 - 2534 - 21 - 1491 + 873 + 3267 + 27 + 1917 lt=- 10.0;2110.0;10.0;10.0 @@ -2493,10 +2524,10 @@ bg=pink Relation - 1505 - 4004 - 294 - 42 + 873 + 5157 + 378 + 54 lt=- 10.0;10.0;400.0;10.0;400.0;40.0 @@ -2504,10 +2535,10 @@ bg=pink UMLClass - 1659 - 4032 - 273 - 56 + 1071 + 5193 + 351 + 72 *Resistance* bg=pink @@ -2519,10 +2550,10 @@ properties : dict(ResistanceProperty, ResistanceProperty) = {} UMLClass - 1113 - 4032 - 231 - 56 + 369 + 5193 + 297 + 72 *Effect* bg=pink @@ -2534,10 +2565,10 @@ properties : dict(EffectProperty, EffectProperty) = {} Relation - 1225 - 4004 - 301 - 42 + 513 + 5157 + 387 + 54 lt=- 410.0;10.0;10.0;10.0;10.0;40.0 @@ -2545,10 +2576,10 @@ properties : dict(EffectProperty, EffectProperty) = {} UMLNote - 1680 - 3976 - 98 - 28 + 1098 + 5121 + 126 + 36 Resistors' side bg=blue @@ -2557,10 +2588,10 @@ bg=blue UMLNote - 1232 - 3976 - 98 - 28 + 522 + 5121 + 126 + 36 Effectors' side bg=blue @@ -2569,10 +2600,10 @@ bg=blue UMLClass - 1302 - 4193 - 245 - 91 + 612 + 5400 + 315 + 117 *FlatAttributeChange* bg=pink @@ -2588,10 +2619,10 @@ ignore_protection : set(ProtectingAttribute) UMLClass - 1869 - 4193 - 175 - 63 + 1341 + 5400 + 225 + 81 *FlatAttributeChange* bg=pink @@ -2604,10 +2635,10 @@ block_value : AttributeAmount UMLClass - 1561 - 3339 - 133 - 42 + 945 + 4302 + 171 + 54 *AttributeChangeType* @@ -2617,10 +2648,10 @@ bg=pink UMLClass - 1302 - 4473 - 210 - 91 + 612 + 5760 + 270 + 117 *Convert* bg=orange @@ -2636,10 +2667,10 @@ cost_fail : optional(Cost) = None UMLClass - 1869 - 4473 - 203 - 91 + 1341 + 5760 + 261 + 117 *Convert* bg=orange @@ -2652,10 +2683,10 @@ chance_resist : float UMLClass - 1351 - 4571 - 168 - 63 + 675 + 5886 + 216 + 81 *AoE2Convert* bg=orange @@ -2668,10 +2699,10 @@ skip_protected_rounds : int UMLClass - 1904 - 4571 - 203 - 70 + 1386 + 5886 + 261 + 90 *AoE2Convert* bg=orange @@ -2690,10 +2721,10 @@ protection_round_recharge_time : float UMLClass - 1057 - 4123 - 126 - 42 + 297 + 5310 + 162 + 54 *ContinuousEffect* @@ -2704,10 +2735,10 @@ bg=orange UMLClass - 952 - 4193 - 231 - 91 + 162 + 5400 + 297 + 117 *FlatAttributeChange* bg=pink @@ -2723,10 +2754,10 @@ ignore_protection : set(ProtectingAttribute) UMLClass - 1302 - 4123 - 119 - 42 + 612 + 5310 + 153 + 54 *DiscreteEffect* @@ -2736,10 +2767,10 @@ bg=orange UMLClass - 1869 - 4123 - 126 - 42 + 1341 + 5310 + 162 + 54 *DiscreteResistance* @@ -2749,10 +2780,10 @@ bg=orange UMLClass - 1470 - 2464 - 126 - 56 + 828 + 3177 + 162 + 72 *ResourceRate* bg=pink @@ -2765,10 +2796,10 @@ rate : float Relation - 1589 - 2485 - 126 - 21 + 981 + 3204 + 162 + 27 lt=- 160.0;10.0;10.0;10.0 @@ -2776,10 +2807,10 @@ rate : float UMLClass - 4137 - 3486 - 112 - 56 + 4257 + 4491 + 144 + 72 *AttributeRate* bg=pink @@ -2792,10 +2823,10 @@ rate : float Relation - 1225 - 4081 - 21 - 42 + 513 + 5256 + 27 + 54 lt=<<- 10.0;10.0;10.0;40.0 @@ -2803,10 +2834,10 @@ rate : float Relation - 1113 - 4102 - 133 - 35 + 369 + 5283 + 171 + 45 lt=- 170.0;10.0;10.0;10.0;10.0;30.0 @@ -2814,10 +2845,10 @@ rate : float Relation - 1225 - 4102 - 147 - 35 + 513 + 5283 + 189 + 45 lt=- 10.0;10.0;190.0;10.0;190.0;30.0 @@ -2825,10 +2856,10 @@ rate : float Relation - 1652 - 4102 - 140 - 35 + 1062 + 5283 + 180 + 45 lt=- 180.0;10.0;10.0;10.0;10.0;30.0 @@ -2836,10 +2867,10 @@ rate : float Relation - 1778 - 4081 - 21 - 42 + 1224 + 5256 + 27 + 54 lt=<<- 10.0;10.0;10.0;40.0 @@ -2847,10 +2878,10 @@ rate : float Relation - 1771 - 4102 - 168 - 35 + 1215 + 5283 + 216 + 45 lt=- 10.0;10.0;220.0;10.0;220.0;30.0 @@ -2858,10 +2889,10 @@ rate : float UMLClass - 1596 - 4123 - 126 - 42 + 990 + 5310 + 162 + 54 *ContinuousResistance* @@ -2871,10 +2902,10 @@ bg=orange Relation - 1505 - 3353 - 70 - 21 + 873 + 4320 + 90 + 27 lt=- 10.0;10.0;80.0;10.0 @@ -2882,10 +2913,10 @@ bg=orange UMLClass - 1575 - 4193 - 147 - 63 + 963 + 5400 + 189 + 81 *FlatAttributeChange* bg=pink @@ -2898,10 +2929,10 @@ block_rate : AttributeRate Relation - 1274 - 4137 - 42 - 98 + 576 + 5328 + 54 + 126 lt=<<- 40.0;10.0;10.0;10.0;10.0;120.0;40.0;120.0 @@ -2909,10 +2940,10 @@ block_rate : AttributeRate Relation - 1176 - 4137 - 42 - 98 + 450 + 5328 + 54 + 126 lt=<<- 10.0;10.0;40.0;10.0;40.0;120.0;10.0;120.0 @@ -2920,10 +2951,10 @@ block_rate : AttributeRate Relation - 1274 - 4424 - 42 - 91 + 576 + 5697 + 54 + 117 lt=- 10.0;10.0;10.0;110.0;40.0;110.0 @@ -2931,10 +2962,10 @@ block_rate : AttributeRate Relation - 1330 - 4557 - 35 - 56 + 648 + 5868 + 45 + 72 lt=<<- 10.0;10.0;10.0;60.0;30.0;60.0 @@ -2942,10 +2973,10 @@ block_rate : AttributeRate Relation - 1883 - 4557 - 35 - 56 + 1359 + 5868 + 45 + 72 lt=<<- 10.0;10.0;10.0;60.0;30.0;60.0 @@ -2953,10 +2984,10 @@ block_rate : AttributeRate Relation - 1841 - 4214 - 42 - 231 + 1305 + 5427 + 54 + 297 lt=- 10.0;10.0;10.0;310.0;40.0;310.0 @@ -2964,10 +2995,10 @@ block_rate : AttributeRate Relation - 1841 - 4137 - 42 - 98 + 1305 + 5328 + 54 + 126 lt=<<- 40.0;10.0;10.0;10.0;10.0;120.0;40.0;120.0 @@ -2975,10 +3006,10 @@ block_rate : AttributeRate Relation - 1715 - 4137 - 42 - 98 + 1143 + 5328 + 54 + 126 lt=<<- 10.0;10.0;40.0;10.0;40.0;120.0;10.0;120.0 @@ -2986,10 +3017,10 @@ block_rate : AttributeRate UMLClass - 5187 - 1918 - 224 - 98 + 5607 + 2475 + 288 + 126 *ApplyDiscreteEffect* bg=green @@ -3009,10 +3040,10 @@ blacklisted_entities : set(GameEntity) UMLClass - 5187 - 1792 - 224 - 84 + 5607 + 2313 + 288 + 108 *ApplyContinuousEffect* bg=green @@ -3029,22 +3060,22 @@ blacklisted_entities : set(GameEntity) UMLClass - 5453 - 1722 - 98 - 42 + 5634 + 2214 + 135 + 54 -*MonkHeal* +*MonkHeal (Ranged)* UMLClass - 5453 - 1813 - 77 - 42 + 5949 + 2340 + 99 + 54 *Repair* @@ -3053,10 +3084,10 @@ blacklisted_entities : set(GameEntity) Relation - 1274 - 4214 - 42 - 231 + 576 + 5427 + 54 + 297 lt=- 10.0;10.0;10.0;310.0;40.0;310.0 @@ -3064,10 +3095,10 @@ blacklisted_entities : set(GameEntity) UMLClass - 4053 - 2233 - 189 - 91 + 4149 + 2880 + 243 + 117 *Gather* bg=green @@ -3083,10 +3114,10 @@ container : ResourceContainer UMLClass - 1302 - 4403 - 182 - 56 + 612 + 5670 + 234 + 72 *MakeHarvestable* bg=orange @@ -3098,10 +3129,10 @@ resource_spot : ResourceSpot UMLClass - 1869 - 4403 - 238 - 56 + 1341 + 5670 + 306 + 72 *MakeHarvestable* bg=orange @@ -3114,37 +3145,21 @@ resist_condition : set(LogicElement) Relation - 5341 - 1736 - 126 - 21 + 5688 + 2259 + 27 + 72 lt=<<- - 10.0;10.0;160.0;10.0 - - - UMLClass - - 5187 - 1715 - 161 - 56 - - *RangedContinuousEffect* -bg=green - --- -min_range : int -max_range : int - + 10.0;60.0;10.0;10.0 Relation - 5404 - 1827 - 63 - 21 + 5886 + 2358 + 81 + 27 lt=<<- 10.0;10.0;70.0;10.0 @@ -3152,10 +3167,10 @@ max_range : int UMLClass - 5453 - 1925 - 98 - 42 + 5949 + 2484 + 126 + 54 *SelfDestruct* @@ -3165,10 +3180,10 @@ max_range : int Relation - 5404 - 1939 - 63 - 21 + 5886 + 2502 + 81 + 27 lt=<<- 10.0;10.0;70.0;10.0 @@ -3176,22 +3191,22 @@ max_range : int UMLClass - 5215 - 2128 - 91 - 42 + 5634 + 2646 + 135 + 54 -*Convert* +*Convert (Ranged)* Relation - 5250 - 2086 - 21 - 56 + 5688 + 2592 + 27 + 72 lt=<<- 10.0;10.0;10.0;60.0 @@ -3199,10 +3214,10 @@ max_range : int UMLClass - 1561 - 3388 - 84 - 42 + 945 + 4365 + 108 + 54 *ConvertType* @@ -3212,10 +3227,10 @@ bg=pink Relation - 1505 - 3402 - 70 - 21 + 873 + 4383 + 90 + 27 lt=- 10.0;10.0;80.0;10.0 @@ -3223,10 +3238,10 @@ bg=pink Relation - 1841 - 4424 - 42 - 98 + 1305 + 5697 + 54 + 126 lt=- 10.0;10.0;10.0;120.0;40.0;120.0 @@ -3234,10 +3249,10 @@ bg=pink UMLClass - 5453 - 1974 - 84 - 42 + 5949 + 2547 + 108 + 54 *Hunt* @@ -3246,37 +3261,10 @@ bg=pink UMLClass - 5187 - 2037 - 140 - 56 - - *RangedDiscreteEffect* -bg=green - --- -min_range : int -max_range : int - - - - Relation - - 5250 - 2009 - 21 - 42 - - lt=<<- - 10.0;10.0;10.0;40.0 - - - UMLClass - - 4179 - 1232 - 224 - 70 + 4311 + 1593 + 288 + 90 *EnterContainer* bg=green @@ -3290,10 +3278,10 @@ blacklisted_entities : set(GameEntity) UMLClass - 4179 - 1309 - 182 - 63 + 4311 + 1692 + 234 + 81 *ExitContainer* bg=green @@ -3305,10 +3293,10 @@ allowed_containers : set(EntityContainer) UMLClass - 2310 - 2282 - 112 - 42 + 1908 + 2943 + 144 + 54 *DiplomaticStance* @@ -3318,10 +3306,10 @@ bg=pink Relation - 2261 - 2296 - 63 - 21 + 1845 + 2961 + 81 + 27 lt=- 70.0;10.0;10.0;10.0 @@ -3329,10 +3317,10 @@ bg=pink UMLClass - 3367 - 2870 - 154 - 56 + 3267 + 3699 + 198 + 72 *Diplomatic* bg=pink @@ -3344,10 +3332,10 @@ stances : set(DiplomaticStance) Relation - 1568 - 21 - 126 - 336 + 954 + 36 + 162 + 432 lt=- 160.0;460.0;10.0;460.0;10.0;10.0 @@ -3355,10 +3343,10 @@ stances : set(DiplomaticStance) UMLClass - 2569 - 2324 - 70 - 42 + 2241 + 2997 + 90 + 54 *Self* @@ -3368,10 +3356,10 @@ bg=pink Relation - 2415 - 2296 - 203 - 21 + 2043 + 2961 + 261 + 27 lt=<<- 10.0;10.0;270.0;10.0 @@ -3379,10 +3367,10 @@ bg=pink Relation - 1673 - 140 - 224 - 217 + 1089 + 189 + 288 + 279 lt=- 300.0;10.0;170.0;10.0;170.0;290.0;10.0;290.0 @@ -3390,10 +3378,10 @@ bg=pink UMLClass - 1904 - 126 - 224 - 56 + 1386 + 171 + 288 + 72 *ElevationDifferenceLow* bg=yellow @@ -3405,10 +3393,10 @@ min_elevation_difference : optional(float) = None UMLClass - 994 - 4403 - 189 - 70 + 216 + 5670 + 243 + 90 *Lure* bg=orange @@ -3424,10 +3412,10 @@ min_distance_to_destination : float Relation - 1176 - 4214 - 42 - 224 + 450 + 5427 + 54 + 288 lt=- 40.0;10.0;40.0;300.0;10.0;300.0 @@ -3435,10 +3423,10 @@ min_distance_to_destination : float UMLClass - 1547 - 4403 - 175 - 70 + 927 + 5670 + 225 + 90 *Lure* bg=orange @@ -3451,10 +3439,10 @@ type : LureType Relation - 1715 - 4214 - 42 - 224 + 1143 + 5427 + 54 + 288 lt=- 40.0;10.0;40.0;300.0;10.0;300.0 @@ -3462,10 +3450,10 @@ type : LureType UMLClass - 868 - 1778 - 112 - 56 + 54 + 2295 + 144 + 72 *LanguageTextPair* bg=pink @@ -3478,10 +3466,10 @@ string : text UMLClass - 1071 - 1778 - 140 - 56 + 315 + 2295 + 180 + 72 *LanguageMarkupPair* bg=pink @@ -3494,10 +3482,10 @@ markup_file : file UMLClass - 1295 - 1778 - 126 - 56 + 603 + 2295 + 162 + 72 *LanguageSoundPair* bg=pink @@ -3510,10 +3498,10 @@ sound : Sound Relation - 917 - 1827 - 21 - 56 + 117 + 2358 + 27 + 72 lt=<. 10.0;60.0;10.0;10.0 @@ -3521,10 +3509,10 @@ sound : Sound Relation - 1134 - 1827 - 21 - 56 + 396 + 2358 + 27 + 72 lt=<. 10.0;60.0;10.0;10.0 @@ -3532,10 +3520,10 @@ sound : Sound Relation - 1344 - 1827 - 21 - 56 + 666 + 2358 + 27 + 72 lt=<. 10.0;60.0;10.0;10.0 @@ -3543,10 +3531,10 @@ sound : Sound UMLClass - 1232 - 2030 - 119 - 56 + 522 + 2619 + 153 + 72 *Language* bg=pink @@ -3558,10 +3546,10 @@ ietf_string : text Relation - 1281 - 1995 - 21 - 49 + 585 + 2574 + 27 + 63 lt=- 10.0;50.0;10.0;10.0 @@ -3569,10 +3557,10 @@ ietf_string : text UMLClass - 3696 - 2751 - 112 - 56 + 3690 + 3546 + 144 + 72 *Turn* bg=green @@ -3584,10 +3572,10 @@ turn_speed : float UMLClass - 4403 - 3164 - 112 - 56 + 4599 + 4077 + 144 + 72 *Visibility* bg=green @@ -3599,10 +3587,10 @@ visible_in_fog : bool UMLClass - 4032 - 1036 - 245 - 84 + 4122 + 1341 + 315 + 117 *EntityContainer* bg=pink @@ -3618,10 +3606,10 @@ carry_progress : set(Progress) Relation - 4270 - 1071 - 63 - 21 + 4428 + 1386 + 81 + 27 lt=<. 70.0;10.0;10.0;10.0 @@ -3629,10 +3617,10 @@ carry_progress : set(Progress) UMLClass - 1694 - 3696 - 154 - 56 + 1116 + 4761 + 198 + 72 *Cost* bg=pink @@ -3644,10 +3632,10 @@ cost : Cost Relation - 1659 - 3780 - 49 - 21 + 1071 + 4869 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -3655,10 +3643,10 @@ cost : Cost UMLClass - 2072 - 2555 - 168 - 70 + 1602 + 3294 + 216 + 90 *AnimationOverride* bg=pink @@ -3672,10 +3660,10 @@ priority : int UMLClass - 1904 - 2380 - 154 - 56 + 1386 + 3069 + 198 + 72 *Diplomatic* bg=pink @@ -3687,10 +3675,10 @@ stances : set(DiplomaticStance) UMLClass - 1351 - 3290 - 112 - 42 + 675 + 4239 + 144 + 54 *DropoffType* @@ -3700,10 +3688,10 @@ bg=pink Relation - 1302 - 3304 - 63 - 21 + 612 + 4257 + 81 + 27 lt=<<- 70.0;10.0;10.0;10.0 @@ -3711,10 +3699,10 @@ bg=pink Relation - 1302 - 3304 - 42 - 70 + 612 + 4257 + 54 + 90 lt=- 40.0;10.0;40.0;80.0;10.0;80.0 @@ -3722,10 +3710,10 @@ bg=pink Relation - 1302 - 3255 - 42 - 70 + 612 + 4194 + 54 + 90 lt=- 40.0;80.0;40.0;10.0;10.0;10.0 @@ -3733,10 +3721,10 @@ bg=pink UMLClass - 1197 - 3241 - 112 - 42 + 477 + 4176 + 144 + 54 *NoDropoff* @@ -3746,10 +3734,10 @@ bg=pink UMLClass - 1197 - 3290 - 112 - 42 + 477 + 4239 + 144 + 54 *Linear* @@ -3759,10 +3747,10 @@ bg=pink UMLClass - 1729 - 3339 - 84 - 42 + 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. @@ -3776,10 +3764,10 @@ bg=pink Relation - 1687 - 3353 - 56 - 21 + 1107 + 4320 + 72 + 27 lt=<<- 10.0;10.0;60.0;10.0 @@ -3787,10 +3775,10 @@ bg=pink Relation - 3332 - 1708 - 42 - 21 + 3222 + 2205 + 54 + 27 lt=<. 10.0;10.0;40.0;10.0 @@ -3798,10 +3786,10 @@ bg=pink UMLClass - 1603 - 2786 - 189 - 84 + 945 + 3699 + 243 + 117 *Tech* bg=pink @@ -3817,21 +3805,21 @@ updates : orderedset(Patch) Relation - 1694 - 2555 - 21 - 245 + 1116 + 3294 + 27 + 423 lt=<<- - 10.0;10.0;10.0;330.0 + 10.0;10.0;10.0;450.0 UMLClass - 4144 - 1813 - 168 - 70 + 4266 + 2340 + 216 + 90 // Determines traded resource and resource amount *TradeRoute* @@ -3846,10 +3834,10 @@ end_trade_post : GameEntity Relation - 4046 - 1876 - 147 - 42 + 4140 + 2421 + 189 + 54 lt=<. 190.0;10.0;190.0;40.0;10.0;40.0 @@ -3857,10 +3845,10 @@ end_trade_post : GameEntity UMLClass - 4361 - 1813 - 119 - 42 + 4545 + 2340 + 153 + 54 *AoE2TradeRoute* @@ -3871,10 +3859,10 @@ bg=pink UMLClass - 1337 - 4298 - 154 - 42 + 657 + 5535 + 198 + 54 *FlatAttributeDecrease* @@ -3884,10 +3872,10 @@ bg=orange UMLClass - 1337 - 4347 - 154 - 42 + 657 + 5598 + 198 + 54 *FlatAttributeIncrease* @@ -3897,10 +3885,10 @@ bg=orange Relation - 1316 - 4277 - 35 - 56 + 630 + 5508 + 45 + 72 lt=->> 30.0;60.0;10.0;60.0;10.0;10.0 @@ -3908,10 +3896,10 @@ bg=orange Relation - 1316 - 4312 - 35 - 70 + 630 + 5553 + 45 + 90 lt=- 30.0;80.0;10.0;80.0;10.0;10.0 @@ -3919,10 +3907,10 @@ bg=orange UMLClass - 1911 - 4298 - 154 - 42 + 1395 + 5535 + 198 + 54 *FlatAttributeDecrease* @@ -3932,10 +3920,10 @@ bg=orange UMLClass - 1911 - 4347 - 154 - 42 + 1395 + 5598 + 198 + 54 *FlatAttributeIncrease* @@ -3945,10 +3933,10 @@ bg=orange Relation - 1883 - 4249 - 21 - 133 + 1359 + 5472 + 27 + 171 lt=<<- 10.0;10.0;10.0;170.0 @@ -3956,10 +3944,10 @@ bg=orange Relation - 1883 - 4312 - 42 - 21 + 1359 + 5553 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -3967,10 +3955,10 @@ bg=orange UMLClass - 994 - 4298 - 154 - 42 + 216 + 5535 + 198 + 54 *FlatAttributeDecrease* @@ -3980,10 +3968,10 @@ bg=orange UMLClass - 994 - 4347 - 154 - 42 + 216 + 5598 + 198 + 54 *FlatAttributeIncrease* @@ -3993,10 +3981,10 @@ bg=orange Relation - 1141 - 4312 - 35 - 70 + 405 + 5553 + 45 + 90 lt=- 10.0;80.0;30.0;80.0;30.0;10.0 @@ -4004,10 +3992,10 @@ bg=orange Relation - 1141 - 4277 - 35 - 56 + 405 + 5508 + 45 + 72 lt=->> 10.0;60.0;30.0;60.0;30.0;10.0 @@ -4015,10 +4003,10 @@ bg=orange UMLClass - 1547 - 4298 - 154 - 42 + 927 + 5535 + 198 + 54 *FlatAttributeDecrease* @@ -4028,10 +4016,10 @@ bg=orange UMLClass - 1547 - 4347 - 154 - 42 + 927 + 5598 + 198 + 54 *FlatAttributeIncrease* @@ -4041,10 +4029,10 @@ bg=orange Relation - 1694 - 4312 - 35 - 70 + 1116 + 5553 + 45 + 90 lt=- 10.0;80.0;30.0;80.0;30.0;10.0 @@ -4052,10 +4040,10 @@ bg=orange Relation - 1694 - 4249 - 35 - 84 + 1116 + 5472 + 45 + 108 lt=->> 10.0;100.0;30.0;100.0;30.0;10.0 @@ -4063,10 +4051,10 @@ bg=orange UMLClass - 1876 - 2212 - 175 - 56 + 1350 + 2853 + 225 + 72 *Formation* bg=pink @@ -4078,10 +4066,10 @@ subformations : set(Subformation) Relation - 2044 - 2233 - 49 - 21 + 1566 + 2880 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -4089,10 +4077,10 @@ subformations : set(Subformation) UMLClass - 1911 - 2289 - 126 - 56 + 1395 + 2952 + 162 + 72 *Subformation* bg=pink @@ -4104,10 +4092,10 @@ ordering_priority : int UMLClass - 3913 - 2996 - 154 - 56 + 3969 + 3861 + 198 + 72 *GameEntityFormation* bg=pink @@ -4120,10 +4108,10 @@ subformation : Subformation Relation - 3878 - 3017 - 49 - 21 + 3924 + 3888 + 63 + 27 lt=<. 50.0;10.0;10.0;10.0 @@ -4131,10 +4119,10 @@ subformation : Subformation Relation - 2030 - 2310 - 63 - 21 + 1548 + 2979 + 81 + 27 lt=- 10.0;10.0;70.0;10.0 @@ -4142,10 +4130,10 @@ subformation : Subformation Relation - 1967 - 2261 - 21 - 42 + 1467 + 2916 + 27 + 54 lt=<. 10.0;40.0;10.0;10.0 @@ -4153,10 +4141,10 @@ subformation : Subformation UMLClass - 1813 - 1162 - 147 - 63 + 1269 + 1503 + 189 + 81 *Scoped* bg=pink @@ -4170,24 +4158,13 @@ stances : set(DiplomaticStance) scope : ModifierScope - - Relation - - 5264 - 1764 - 21 - 42 - - lt=<<- - 10.0;40.0;10.0;10.0 - UMLClass - 1302 - 4648 - 175 - 70 + 612 + 5985 + 225 + 90 *SendToContainer* bg=orange @@ -4200,10 +4177,10 @@ storages : set(EntityContainer) Relation - 1274 - 4494 - 42 - 196 + 576 + 5787 + 54 + 252 lt=- 10.0;10.0;10.0;260.0;40.0;260.0 @@ -4211,10 +4188,10 @@ storages : set(EntityContainer) UMLClass - 1869 - 4648 - 182 - 70 + 1341 + 5985 + 234 + 90 *SendToContainer* bg=orange @@ -4228,10 +4205,10 @@ ignore_containers : set(EntityContainer) Relation - 1841 - 4501 - 42 - 189 + 1305 + 5796 + 54 + 243 lt=- 10.0;10.0;10.0;250.0;40.0;250.0 @@ -4239,10 +4216,10 @@ ignore_containers : set(EntityContainer) Relation - 3549 - 1925 - 42 - 609 + 3501 + 2484 + 54 + 783 lt=<<- 40.0;850.0;40.0;10.0;10.0;10.0 @@ -4250,10 +4227,10 @@ ignore_containers : set(EntityContainer) Relation - 3549 - 1827 - 42 - 119 + 3501 + 2358 + 54 + 153 lt=- 40.0;150.0;40.0;10.0;10.0;10.0 @@ -4261,10 +4238,10 @@ ignore_containers : set(EntityContainer) Relation - 3549 - 1715 - 42 - 133 + 3501 + 2214 + 54 + 171 lt=- 40.0;170.0;40.0;10.0;10.0;10.0 @@ -4272,10 +4249,10 @@ ignore_containers : set(EntityContainer) Relation - 3731 - 2534 - 1708 - 21 + 3735 + 3267 + 2196 + 27 lt=<<- 10.0;10.0;2420.0;10.0 @@ -4283,21 +4260,21 @@ ignore_containers : set(EntityContainer) Relation - 3332 - 2590 - 21 - 385 + 3222 + 3339 + 27 + 576 lt=<<- - 10.0;10.0;10.0;530.0 + 10.0;10.0;10.0;620.0 Relation - 3332 - 2765 - 49 - 21 + 3222 + 3564 + 63 + 27 lt=- 50.0;10.0;10.0;10.0 @@ -4305,10 +4282,10 @@ ignore_containers : set(EntityContainer) Relation - 3332 - 2828 - 49 - 21 + 3222 + 3645 + 63 + 27 lt=- 50.0;10.0;10.0;10.0 @@ -4316,10 +4293,10 @@ ignore_containers : set(EntityContainer) Relation - 3570 - 2576 - 21 - 581 + 3528 + 3321 + 27 + 747 lt=<<- 10.0;10.0;10.0;810.0 @@ -4327,10 +4304,10 @@ ignore_containers : set(EntityContainer) Relation - 3647 - 3017 - 49 - 84 + 3627 + 3888 + 63 + 108 lt=- 10.0;10.0;10.0;100.0;50.0;100.0 @@ -4338,10 +4315,10 @@ ignore_containers : set(EntityContainer) Relation - 3570 - 3017 - 126 - 21 + 3528 + 3888 + 162 + 27 lt=- 10.0;10.0;160.0;10.0 @@ -4349,10 +4326,10 @@ ignore_containers : set(EntityContainer) Relation - 3801 - 2709 - 42 - 84 + 3825 + 3492 + 54 + 108 lt=- 10.0;100.0;40.0;100.0;40.0;10.0 @@ -4360,10 +4337,10 @@ ignore_containers : set(EntityContainer) Relation - 3801 - 2534 - 42 - 196 + 3825 + 3267 + 54 + 252 lt=- 10.0;260.0;40.0;260.0;40.0;10.0 @@ -4371,10 +4348,10 @@ ignore_containers : set(EntityContainer) Relation - 3822 - 2709 - 42 - 21 + 3852 + 3492 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -4382,10 +4359,10 @@ ignore_containers : set(EntityContainer) Relation - 4088 - 1827 - 70 - 21 + 4194 + 2358 + 90 + 27 lt=<. 80.0;10.0;10.0;10.0 @@ -4393,10 +4370,10 @@ ignore_containers : set(EntityContainer) Relation - 4305 - 1827 - 70 - 21 + 4473 + 2358 + 90 + 27 lt=<<- 10.0;10.0;80.0;10.0 @@ -4404,10 +4381,10 @@ ignore_containers : set(EntityContainer) Relation - 4333 - 1827 - 21 - 84 + 4509 + 2358 + 27 + 108 lt=- 10.0;10.0;10.0;100.0 @@ -4415,10 +4392,10 @@ ignore_containers : set(EntityContainer) Relation - 3570 - 1750 - 336 - 21 + 3528 + 2259 + 432 + 27 lt=- 460.0;10.0;10.0;10.0 @@ -4426,10 +4403,10 @@ ignore_containers : set(EntityContainer) Relation - 3864 - 1750 - 42 - 98 + 3906 + 2259 + 54 + 126 lt=- 40.0;120.0;10.0;120.0;10.0;10.0 @@ -4437,10 +4414,10 @@ ignore_containers : set(EntityContainer) Relation - 3864 - 1827 - 42 - 84 + 3906 + 2358 + 54 + 108 lt=- 40.0;100.0;10.0;100.0;10.0;10.0 @@ -4448,10 +4425,10 @@ ignore_containers : set(EntityContainer) Relation - 4242 - 3493 - 70 - 21 + 4392 + 4500 + 90 + 27 lt=<. 80.0;10.0;10.0;10.0 @@ -4459,10 +4436,10 @@ ignore_containers : set(EntityContainer) Relation - 4375 - 2534 - 21 - 721 + 4563 + 3267 + 27 + 927 lt=- 10.0;10.0;10.0;1010.0 @@ -4470,10 +4447,10 @@ ignore_containers : set(EntityContainer) Relation - 4354 - 2968 - 42 - 21 + 4536 + 3825 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4481,10 +4458,10 @@ ignore_containers : set(EntityContainer) Relation - 4354 - 3045 - 42 - 21 + 4536 + 3924 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4492,10 +4469,10 @@ ignore_containers : set(EntityContainer) Relation - 4375 - 3108 - 42 - 21 + 4563 + 4014 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4503,10 +4480,10 @@ ignore_containers : set(EntityContainer) Relation - 4354 - 3185 - 42 - 21 + 4536 + 4104 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4514,10 +4491,10 @@ ignore_containers : set(EntityContainer) Relation - 4375 - 3185 - 42 - 21 + 4563 + 4104 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4525,10 +4502,10 @@ ignore_containers : set(EntityContainer) Relation - 4354 - 3108 - 42 - 21 + 4536 + 4005 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -4536,10 +4513,10 @@ ignore_containers : set(EntityContainer) Relation - 4375 - 2968 - 42 - 21 + 4563 + 3825 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4547,10 +4524,10 @@ ignore_containers : set(EntityContainer) Relation - 5012 - 2534 - 21 - 840 + 5382 + 3267 + 27 + 1080 lt=- 10.0;10.0;10.0;1180.0 @@ -4558,10 +4535,10 @@ ignore_containers : set(EntityContainer) Relation - 4991 - 3353 - 42 - 21 + 5355 + 4320 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4569,10 +4546,10 @@ ignore_containers : set(EntityContainer) Relation - 5012 - 3353 - 42 - 21 + 5382 + 4320 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4580,10 +4557,10 @@ ignore_containers : set(EntityContainer) Relation - 4375 - 3038 - 42 - 21 + 4563 + 3924 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4591,10 +4568,10 @@ ignore_containers : set(EntityContainer) Relation - 5299 - 2485 - 42 - 70 + 5751 + 3204 + 54 + 90 lt=- 40.0;10.0;10.0;10.0;10.0;80.0 @@ -4602,10 +4579,10 @@ ignore_containers : set(EntityContainer) Relation - 5299 - 2534 - 21 - 259 + 5751 + 3267 + 27 + 333 lt=- 10.0;350.0;10.0;10.0 @@ -4613,10 +4590,10 @@ ignore_containers : set(EntityContainer) Relation - 5299 - 2772 - 42 - 21 + 5751 + 3573 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4624,10 +4601,10 @@ ignore_containers : set(EntityContainer) Relation - 5159 - 1869 - 147 - 686 + 5571 + 2412 + 189 + 882 lt=- 190.0;10.0;190.0;40.0;10.0;40.0;10.0;960.0 @@ -4635,10 +4612,10 @@ ignore_containers : set(EntityContainer) Relation - 5404 - 1988 - 63 - 21 + 5886 + 2565 + 81 + 27 lt=<<- 10.0;10.0;70.0;10.0 @@ -4646,10 +4623,10 @@ ignore_containers : set(EntityContainer) Relation - 5285 - 1890 - 21 - 42 + 5733 + 2439 + 27 + 54 lt=- 10.0;10.0;10.0;40.0 @@ -4657,10 +4634,10 @@ ignore_containers : set(EntityContainer) Relation - 4256 - 2065 - 21 - 490 + 4410 + 2664 + 27 + 630 lt=- 10.0;680.0;10.0;10.0 @@ -4668,10 +4645,10 @@ ignore_containers : set(EntityContainer) Relation - 4256 - 2086 - 42 - 21 + 4410 + 2691 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -4679,10 +4656,10 @@ ignore_containers : set(EntityContainer) Relation - 4235 - 2065 - 42 - 21 + 4383 + 2664 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -4690,10 +4667,10 @@ ignore_containers : set(EntityContainer) Relation - 4235 - 2149 - 42 - 21 + 4383 + 2772 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -4701,10 +4678,10 @@ ignore_containers : set(EntityContainer) Relation - 4256 - 2149 - 42 - 21 + 4410 + 2772 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -4712,10 +4689,10 @@ ignore_containers : set(EntityContainer) Relation - 4256 - 2212 - 42 - 21 + 4410 + 2853 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -4723,10 +4700,10 @@ ignore_containers : set(EntityContainer) Relation - 4256 - 2275 - 42 - 21 + 4410 + 2934 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -4734,10 +4711,10 @@ ignore_containers : set(EntityContainer) Relation - 4235 - 2254 - 42 - 21 + 4383 + 2907 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -4745,10 +4722,10 @@ ignore_containers : set(EntityContainer) Relation - 4151 - 1204 - 700 - 1351 + 4275 + 1557 + 900 + 1737 lt=- 980.0;1910.0;980.0;360.0;10.0;360.0;10.0;10.0 @@ -4756,10 +4733,10 @@ ignore_containers : set(EntityContainer) Relation - 4830 - 1449 - 189 - 21 + 5148 + 1872 + 243 + 27 lt=- 10.0;10.0;250.0;10.0 @@ -4767,10 +4744,10 @@ ignore_containers : set(EntityContainer) Relation - 4977 - 1309 - 42 - 161 + 5337 + 1692 + 54 + 207 lt=- 40.0;10.0;10.0;10.0;10.0;210.0 @@ -4778,10 +4755,10 @@ ignore_containers : set(EntityContainer) Relation - 4151 - 1253 - 42 - 21 + 4275 + 1620 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4789,10 +4766,10 @@ ignore_containers : set(EntityContainer) Relation - 4151 - 1330 - 42 - 21 + 4275 + 1719 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4800,10 +4777,10 @@ ignore_containers : set(EntityContainer) Relation - 4151 - 1393 - 42 - 21 + 4275 + 1800 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4811,10 +4788,10 @@ ignore_containers : set(EntityContainer) Relation - 4130 - 1393 - 42 - 21 + 4248 + 1800 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4822,10 +4799,10 @@ ignore_containers : set(EntityContainer) Relation - 4130 - 1330 - 42 - 21 + 4248 + 1719 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4833,10 +4810,10 @@ ignore_containers : set(EntityContainer) Relation - 4130 - 1253 - 42 - 21 + 4248 + 1620 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -4844,10 +4821,10 @@ ignore_containers : set(EntityContainer) UMLClass - 5264 - 1323 - 98 - 42 + 5706 + 1710 + 126 + 54 *TargetMode* @@ -4857,10 +4834,10 @@ bg=pink Relation - 5222 - 1337 - 56 - 21 + 5652 + 1728 + 72 + 27 lt=<. 60.0;10.0;10.0;10.0 @@ -4868,10 +4845,10 @@ bg=pink UMLClass - 5411 - 1323 - 112 - 42 + 5895 + 1710 + 144 + 54 *CurrentPosition* @@ -4881,10 +4858,10 @@ bg=pink Relation - 5355 - 1337 - 70 - 21 + 5823 + 1728 + 90 + 27 lt=<<- 10.0;10.0;80.0;10.0 @@ -4892,10 +4869,10 @@ bg=pink UMLClass - 5411 - 1372 - 112 - 42 + 5895 + 1773 + 144 + 54 *ExpectedPosition* @@ -4905,10 +4882,10 @@ bg=pink Relation - 5383 - 1337 - 42 - 70 + 5859 + 1728 + 54 + 90 lt=- 10.0;10.0;10.0;80.0;40.0;80.0 @@ -4916,10 +4893,10 @@ bg=pink UMLClass - 1673 - 1169 - 98 - 42 + 1089 + 1512 + 126 + 54 *ModifierScope* @@ -4929,10 +4906,10 @@ bg=pink Relation - 1764 - 1183 - 63 - 21 + 1206 + 1530 + 81 + 27 lt=<. 10.0;10.0;70.0;10.0 @@ -4940,10 +4917,10 @@ bg=pink UMLClass - 4053 - 2863 - 77 - 42 + 4149 + 3690 + 99 + 54 *AttackMove* @@ -4953,10 +4930,10 @@ bg=pink Relation - 4018 - 2877 - 49 - 21 + 4104 + 3708 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -4964,10 +4941,10 @@ bg=pink UMLClass - 3990 - 2625 - 84 - 42 + 4068 + 3384 + 108 + 54 *MoveMode* @@ -4977,10 +4954,10 @@ bg=pink Relation - 3906 - 2639 - 98 - 63 + 3960 + 3402 + 126 + 81 lt=<. 120.0;10.0;10.0;10.0;10.0;70.0 @@ -4988,10 +4965,10 @@ bg=pink Relation - 1876 - 203 - 42 - 21 + 1350 + 270 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -4999,10 +4976,10 @@ bg=pink UMLClass - 1904 - 189 - 91 - 42 + 1386 + 252 + 117 + 54 *Stray* @@ -5012,10 +4989,10 @@ bg=yellow UMLClass - 2506 - 539 - 210 - 56 + 2160 + 702 + 270 + 72 *AbsoluteProjectileAmount* bg=yellow @@ -5027,10 +5004,10 @@ amount : float Relation - 2072 - 308 - 413 - 560 + 1602 + 405 + 531 + 720 lt=- 10.0;780.0;570.0;780.0;570.0;10.0 @@ -5038,10 +5015,10 @@ amount : float UMLClass - 1477 - 546 - 168 - 56 + 837 + 711 + 216 + 72 *GatheringEfficiency* bg=yellow @@ -5053,10 +5030,10 @@ resource_spot : ResourceSpot Relation - 1638 - 567 - 56 - 21 + 1044 + 738 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5064,10 +5041,10 @@ resource_spot : ResourceSpot UMLClass - 1547 - 609 - 98 - 42 + 927 + 792 + 126 + 54 *ReloadTime* @@ -5077,10 +5054,10 @@ bg=yellow Relation - 1638 - 623 - 56 - 21 + 1044 + 810 + 72 + 27 lt=- 10.0;10.0;60.0;10.0 @@ -5088,10 +5065,10 @@ bg=yellow UMLClass - 1715 - 707 - 196 - 56 + 1143 + 918 + 252 + 72 *CreationTime* bg=yellow @@ -5103,10 +5080,10 @@ creatables : set(CreatableGameEntity) Relation - 1673 - 728 - 56 - 21 + 1089 + 945 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5114,10 +5091,10 @@ creatables : set(CreatableGameEntity) UMLClass - 1715 - 644 - 210 - 56 + 1143 + 837 + 270 + 72 *CreationResourceCost* bg=yellow @@ -5131,10 +5108,10 @@ creatables : set(CreatableGameEntity) Relation - 1673 - 672 - 56 - 21 + 1089 + 873 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5142,10 +5119,10 @@ creatables : set(CreatableGameEntity) UMLClass - 1715 - 455 - 203 - 56 + 1143 + 594 + 261 + 72 *ResearchResourceCost* bg=yellow @@ -5159,10 +5136,10 @@ researchables : set(ResearchableTech) Relation - 1673 - 476 - 56 - 21 + 1089 + 621 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5170,10 +5147,10 @@ researchables : set(ResearchableTech) Relation - 2464 - 560 - 56 - 21 + 2106 + 729 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5181,10 +5158,10 @@ researchables : set(ResearchableTech) UMLClass - 2506 - 476 - 154 - 56 + 2160 + 621 + 198 + 72 // Immediately unlocks a Tech as soon as the requirements are fulfilled *InstantTechResearch* @@ -5198,10 +5175,10 @@ condition : set(LogicElement) Relation - 2464 - 497 - 56 - 21 + 2106 + 648 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5209,16 +5186,15 @@ condition : set(LogicElement) UMLClass - 4284 - 2338 - 175 - 77 + 4446 + 3015 + 225 + 99 *Herd* bg=green -- -range : float strength : int allowed_types : set(GameEntityType) blacklisted_entities : set(GameEntity) @@ -5227,10 +5203,10 @@ blacklisted_entities : set(GameEntity) Relation - 4256 - 2359 - 42 - 21 + 4410 + 3042 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -5238,10 +5214,10 @@ blacklisted_entities : set(GameEntity) UMLClass - 1449 - 357 - 196 - 56 + 801 + 468 + 252 + 72 *EntityContainerCapacity* bg=yellow @@ -5254,10 +5230,10 @@ container : EntityContainer UMLClass - 1442 - 420 - 203 - 56 + 792 + 549 + 261 + 72 *StorageElementCapacity* bg=yellow @@ -5270,10 +5246,10 @@ storage_element : StorageElementDefinition Relation - 1638 - 378 - 56 - 21 + 1044 + 495 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5281,10 +5257,10 @@ storage_element : StorageElementDefinition Relation - 1638 - 441 - 56 - 21 + 1044 + 576 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5292,10 +5268,10 @@ storage_element : StorageElementDefinition UMLClass - 1715 - 518 - 203 - 56 + 1143 + 675 + 261 + 72 *ResearchTime* bg=yellow @@ -5308,10 +5284,10 @@ researchables : set(ResearchableTech) Relation - 1673 - 539 - 56 - 21 + 1089 + 702 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5319,10 +5295,10 @@ researchables : set(ResearchableTech) UMLClass - 2506 - 350 - 196 - 70 + 2160 + 459 + 252 + 90 // Reveal area around listed units *Reveal* @@ -5337,10 +5313,10 @@ blacklisted_entities : set(GameEntity) Relation - 2464 - 371 - 56 - 21 + 2106 + 486 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5348,24 +5324,26 @@ blacklisted_entities : set(GameEntity) UMLNote - 0 - 1057 - 133 - 63 + 9 + 315 + 171 + 81 Orange elements: -Effects/Resistances that can be applied on other game entities +Effects/Resistances that can +be applied on other game +entities bg=orange UMLClass - 2506 - 602 - 224 - 70 + 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 @@ -5384,10 +5362,10 @@ change_types : set(AttributeChangeType) UMLClass - 4081 - 2394 - 161 - 56 + 4185 + 3087 + 207 + 72 *RegenerateResourceSpot* bg=green @@ -5400,10 +5378,10 @@ resource_spot : ResourceSpot Relation - 4235 - 2415 - 42 - 21 + 4383 + 3114 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -5411,10 +5389,10 @@ resource_spot : ResourceSpot UMLClass - 1638 - 1232 - 84 - 42 + 1044 + 1593 + 108 + 54 // Only affect yourself (default for modifiers in GameEntity) @@ -5425,10 +5403,10 @@ bg=pink Relation - 1715 - 1204 - 49 - 70 + 1143 + 1557 + 63 + 90 lt=<<- 50.0;10.0;50.0;80.0;10.0;80.0 @@ -5436,10 +5414,10 @@ bg=pink UMLClass - 1547 - 1281 - 175 - 56 + 927 + 1656 + 225 + 72 // Affect all game entities in the list *GameEntityScope* @@ -5453,10 +5431,10 @@ blacklisted_entities : set(GameEntity) Relation - 1715 - 1253 - 49 - 63 + 1143 + 1620 + 63 + 81 lt=- 10.0;70.0;50.0;70.0;50.0;10.0 @@ -5464,21 +5442,21 @@ blacklisted_entities : set(GameEntity) Text - 0 - 819 - 161 - 21 + 9 + 9 + 207 + 27 - openage nyan data API v0.3.0 + openage nyan data API v0.6.0 UMLClass - 1827 - 2107 - 224 - 98 + 1287 + 2718 + 288 + 126 *StateChanger* bg=pink @@ -5496,10 +5474,10 @@ priority : int Relation - 2044 - 2142 - 49 - 21 + 1566 + 2763 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -5507,10 +5485,10 @@ priority : int UMLClass - 2219 - 350 - 217 - 63 + 1791 + 459 + 279 + 81 // Apply effects when in a container *InContainerContinuousEffect* @@ -5524,10 +5502,10 @@ ability : ApplyContinuousEffect Relation - 2429 - 371 - 56 - 21 + 2061 + 486 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5535,10 +5513,10 @@ ability : ApplyContinuousEffect UMLClass - 2219 - 427 - 217 - 63 + 1791 + 558 + 279 + 81 // Apply effects when in a container *InContainerDiscreteEffect* @@ -5552,10 +5530,10 @@ ability : ApplyDiscreteEffect Relation - 2429 - 448 - 56 - 21 + 2061 + 585 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5563,10 +5541,10 @@ ability : ApplyDiscreteEffect UMLClass - 4053 - 2912 - 77 - 42 + 4149 + 3753 + 99 + 54 *Normal* @@ -5576,10 +5554,10 @@ bg=pink Relation - 4018 - 2926 - 49 - 21 + 4104 + 3771 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -5587,10 +5565,10 @@ bg=pink UMLClass - 1197 - 105 - 119 - 56 + 477 + 144 + 153 + 72 *Terrain* bg=yellow @@ -5602,10 +5580,10 @@ terrain : Terrain Relation - 1309 - 126 - 42 - 21 + 621 + 171 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -5613,10 +5591,10 @@ terrain : Terrain UMLClass - 1904 - 0 - 119 - 56 + 1386 + 9 + 153 + 72 *Terrain* bg=yellow @@ -5628,10 +5606,10 @@ terrain : Terrain Relation - 1876 - 21 - 21 - 252 + 1350 + 36 + 27 + 324 lt=- 10.0;340.0;10.0;10.0 @@ -5639,10 +5617,10 @@ terrain : Terrain UMLClass - 2506 - 287 - 196 - 56 + 2160 + 378 + 252 + 72 // Reveal area around listed units *DiplomaticLineOfSight* @@ -5655,10 +5633,10 @@ diplomatic_stance : DiplomaticStance Relation - 2464 - 308 - 56 - 21 + 2106 + 405 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5666,10 +5644,10 @@ diplomatic_stance : DiplomaticStance UMLClass - 1561 - 3437 - 84 - 42 + 945 + 4428 + 108 + 54 *LureType* @@ -5679,10 +5657,10 @@ bg=pink Relation - 1505 - 3451 - 70 - 21 + 873 + 4446 + 90 + 27 lt=- 10.0;10.0;80.0;10.0 @@ -5690,10 +5668,10 @@ bg=pink UMLClass - 1561 - 3486 - 140 - 42 + 945 + 4491 + 180 + 54 *SendToContainerType* @@ -5703,10 +5681,10 @@ bg=pink Relation - 1505 - 3500 - 70 - 21 + 873 + 4509 + 90 + 27 lt=- 10.0;10.0;80.0;10.0 @@ -5714,10 +5692,10 @@ bg=pink UMLClass - 3248 - 1932 - 98 - 42 + 3114 + 2493 + 126 + 54 *PlacementMode* @@ -5727,10 +5705,10 @@ bg=pink Relation - 3276 - 1897 - 21 - 49 + 3150 + 2448 + 27 + 63 lt=<. 10.0;50.0;10.0;10.0 @@ -5738,10 +5716,10 @@ bg=pink UMLClass - 3332 - 2149 - 77 - 42 + 3222 + 2772 + 99 + 54 *Eject* @@ -5751,10 +5729,10 @@ bg=pink UMLClass - 3332 - 1988 - 147 - 91 + 3222 + 2565 + 189 + 117 *Place* bg=pink @@ -5770,10 +5748,10 @@ max_elevation_difference : int Relation - 3297 - 1967 - 21 - 273 + 3177 + 2538 + 27 + 351 lt=<<- 10.0;10.0;10.0;370.0 @@ -5781,10 +5759,10 @@ max_elevation_difference : int Relation - 3297 - 2009 - 49 - 21 + 3177 + 2592 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -5792,10 +5770,10 @@ max_elevation_difference : int UMLClass - 1092 - 2765 - 182 - 126 + 342 + 3564 + 234 + 162 *AdjacentTilesVariant* @@ -5814,10 +5792,10 @@ north_west : optional(GameEntity) Relation - 1267 - 2723 - 35 - 84 + 567 + 3510 + 45 + 108 lt=- 30.0;10.0;30.0;100.0;10.0;100.0 @@ -5825,10 +5803,10 @@ north_west : optional(GameEntity) UMLClass - 1197 - 3339 - 112 - 42 + 477 + 4302 + 144 + 54 *InverseLinear* @@ -5838,10 +5816,10 @@ bg=pink UMLClass - 3367 - 2681 - 126 - 56 + 3267 + 3456 + 162 + 72 *ExecutionSound* bg=pink @@ -5854,10 +5832,10 @@ sounds : set(Sound) Relation - 3332 - 2702 - 49 - 21 + 3222 + 3483 + 63 + 27 lt=- 50.0;10.0;10.0;10.0 @@ -5865,10 +5843,10 @@ sounds : set(Sound) Relation - 3332 - 2639 - 49 - 21 + 3222 + 3402 + 63 + 27 lt=- 50.0;10.0;10.0;10.0 @@ -5876,10 +5854,10 @@ sounds : set(Sound) UMLClass - 3367 - 2618 - 161 - 56 + 3267 + 3375 + 207 + 72 *AnimationOverride* bg=pink @@ -5891,10 +5869,10 @@ overrides : set(AnimationOverride) UMLClass - 2219 - 287 - 217 - 56 + 1791 + 378 + 279 + 72 *RefundOnCondition* bg=yellow @@ -5907,10 +5885,10 @@ refund_amount : set(ResourceAmount) Relation - 2429 - 308 - 56 - 21 + 2061 + 405 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -5918,10 +5896,10 @@ refund_amount : set(ResourceAmount) UMLClass - 1736 - 3087 - 161 - 56 + 1170 + 3978 + 207 + 72 *ProgressStatus* bg=pink @@ -5934,10 +5912,10 @@ progress : float Relation - 1890 - 3094 - 42 - 21 + 1368 + 3987 + 54 + 27 lt=<. 10.0;10.0;40.0;10.0 @@ -5945,10 +5923,10 @@ progress : float UMLClass - 952 - 4487 - 231 - 77 + 162 + 5778 + 297 + 99 *TimeRelativeAttributeChange* bg=pink @@ -5962,10 +5940,10 @@ ignore_protection : set(ProtectingAttribute) Relation - 1176 - 4417 - 42 - 119 + 450 + 5688 + 54 + 153 lt=- 40.0;10.0;40.0;150.0;10.0;150.0 @@ -5973,10 +5951,10 @@ ignore_protection : set(ProtectingAttribute) UMLClass - 973 - 4578 - 182 - 42 + 189 + 5895 + 234 + 54 *TimeRelativeAttributeDecrease* @@ -5986,10 +5964,10 @@ bg=orange UMLClass - 973 - 4627 - 182 - 42 + 189 + 5958 + 234 + 54 *TimeRelativeAttributeIncrease* @@ -5999,10 +5977,10 @@ bg=orange Relation - 1148 - 4592 - 35 - 70 + 414 + 5913 + 45 + 90 lt=- 10.0;80.0;30.0;80.0;30.0;10.0 @@ -6010,10 +5988,10 @@ bg=orange Relation - 1148 - 4557 - 35 - 56 + 414 + 5868 + 45 + 72 lt=->> 10.0;60.0;30.0;60.0;30.0;10.0 @@ -6021,10 +5999,10 @@ bg=orange UMLClass - 1008 - 4683 - 175 - 63 + 234 + 6030 + 225 + 81 *TimeRelativeProgressChange* bg=pink @@ -6037,10 +6015,10 @@ total_change_time : float Relation - 1176 - 4515 - 42 - 210 + 450 + 5814 + 54 + 270 lt=- 40.0;10.0;40.0;280.0;10.0;280.0 @@ -6048,10 +6026,10 @@ total_change_time : float UMLClass - 1547 - 4487 - 175 - 63 + 927 + 5778 + 225 + 81 *TimeRelativeAttributeChange* bg=pink @@ -6063,10 +6041,10 @@ type : AttributeChangeType Relation - 1715 - 4417 - 42 - 112 + 1143 + 5688 + 54 + 144 lt=- 40.0;10.0;40.0;140.0;10.0;140.0 @@ -6074,10 +6052,10 @@ type : AttributeChangeType UMLClass - 1547 - 4578 - 154 - 42 + 927 + 5895 + 198 + 54 *FlatAttributeDecrease* @@ -6087,10 +6065,10 @@ bg=orange UMLClass - 1547 - 4627 - 154 - 42 + 927 + 5958 + 198 + 54 *FlatAttributeIncrease* @@ -6100,10 +6078,10 @@ bg=orange Relation - 1694 - 4592 - 35 - 70 + 1116 + 5913 + 45 + 90 lt=- 10.0;80.0;30.0;80.0;30.0;10.0 @@ -6111,10 +6089,10 @@ bg=orange Relation - 1694 - 4543 - 35 - 70 + 1116 + 5850 + 45 + 90 lt=->> 10.0;80.0;30.0;80.0;30.0;10.0 @@ -6122,10 +6100,10 @@ bg=orange UMLClass - 1547 - 4683 - 175 - 63 + 927 + 6030 + 225 + 81 *TimeRelativeProgressChange* bg=pink @@ -6138,10 +6116,10 @@ type : ProgressType Relation - 1715 - 4508 - 42 - 217 + 1143 + 5805 + 54 + 279 lt=- 40.0;10.0;40.0;290.0;10.0;290.0 @@ -6149,10 +6127,10 @@ type : ProgressType UMLClass - 2387 - 2926 - 84 - 42 + 2007 + 3771 + 108 + 54 *ProgressType* @@ -6162,10 +6140,10 @@ bg=pink Relation - 2331 - 2940 - 70 - 21 + 1935 + 3789 + 90 + 27 lt=- 10.0;10.0;80.0;10.0 @@ -6173,10 +6151,10 @@ bg=pink UMLClass - 4361 - 1869 - 189 - 56 + 4545 + 2412 + 243 + 72 *AoE1TradeRoute* bg=pink @@ -6189,10 +6167,10 @@ trade_amount : int UMLClass - 2555 - 3451 - 147 - 56 + 2223 + 4446 + 189 + 72 *StateChange* bg=pink @@ -6204,10 +6182,10 @@ state_change : StateChanger Relation - 2527 - 3472 - 42 - 21 + 2187 + 4473 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -6215,10 +6193,10 @@ state_change : StateChanger UMLClass - 2555 - 3325 - 105 - 56 + 2223 + 4284 + 135 + 72 *Terrain* bg=pink @@ -6232,10 +6210,10 @@ terrain : Terrain Relation - 2527 - 3346 - 42 - 21 + 2187 + 4311 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -6243,10 +6221,10 @@ terrain : Terrain Relation - 5012 - 3290 - 42 - 21 + 5382 + 4239 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -6254,10 +6232,10 @@ terrain : Terrain UMLClass - 5040 - 3269 - 182 - 56 + 5418 + 4212 + 234 + 72 *TerrainRequirement* bg=green @@ -6270,10 +6248,10 @@ blacklisted_terrains : set(Terrain) UMLClass - 5040 - 3199 - 175 - 63 + 5418 + 4122 + 225 + 81 *OverlayTerrain* bg=green @@ -6285,10 +6263,10 @@ terrain_overlay : Terrain UMLClass - 4298 - 3451 - 168 - 63 + 4464 + 4446 + 216 + 81 *Attribute* bg=pink @@ -6302,10 +6280,10 @@ abbreviation : TranslatedString Relation - 4375 - 3409 - 21 - 56 + 4563 + 4392 + 27 + 72 lt=<. 10.0;60.0;10.0;10.0 @@ -6313,10 +6291,10 @@ abbreviation : TranslatedString UMLClass - 2219 - 504 - 217 - 84 + 1791 + 657 + 279 + 108 *DepositResourcesOnProgress* bg=yellow @@ -6331,10 +6309,10 @@ blacklisted_entities : set(GameEntity) Relation - 2429 - 525 - 56 - 21 + 2061 + 684 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -6342,10 +6320,10 @@ blacklisted_entities : set(GameEntity) UMLClass - 3920 - 1491 - 98 - 42 + 3978 + 1926 + 126 + 54 *PriceMode* @@ -6355,10 +6333,10 @@ bg=pink Relation - 3962 - 1680 - 21 - 56 + 4032 + 2169 + 27 + 72 lt=<. 10.0;10.0;10.0;60.0 @@ -6366,10 +6344,10 @@ bg=pink Relation - 4011 - 1505 - 119 - 21 + 4095 + 1944 + 153 + 27 lt=<<- 10.0;10.0;150.0;10.0 @@ -6377,10 +6355,10 @@ bg=pink UMLClass - 4116 - 1491 - 77 - 42 + 4230 + 1926 + 99 + 54 *Fixed* @@ -6390,10 +6368,10 @@ bg=pink Relation - 3297 - 2163 - 49 - 21 + 3177 + 2790 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -6401,10 +6379,10 @@ bg=pink Relation - 4333 - 1890 - 42 - 21 + 4509 + 2439 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -6412,10 +6390,10 @@ bg=pink Relation - 4088 - 1505 - 21 - 70 + 4194 + 1944 + 27 + 90 lt=- 10.0;10.0;10.0;80.0 @@ -6423,10 +6401,10 @@ bg=pink Relation - 4088 - 1554 - 42 - 21 + 4194 + 2007 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -6434,10 +6412,10 @@ bg=pink UMLClass - 4116 - 1540 - 105 - 70 + 4230 + 1989 + 135 + 90 *Dynamic* bg=pink @@ -6451,10 +6429,10 @@ max_price : float UMLClass - 4172 - 1631 - 84 - 42 + 4302 + 2106 + 108 + 54 *PricePool* @@ -6464,10 +6442,10 @@ bg=pink Relation - 4123 - 1645 - 63 - 21 + 4239 + 2124 + 81 + 27 lt=<. 70.0;10.0;10.0;10.0 @@ -6475,10 +6453,10 @@ bg=pink UMLClass - 1358 - 56 - 175 - 42 + 684 + 81 + 225 + 54 *TimeRelativeAttributeChange* @@ -6488,10 +6466,10 @@ bg=yellow Relation - 1526 - 70 - 63 - 21 + 900 + 99 + 81 + 27 lt=- 70.0;10.0;10.0;10.0 @@ -6499,10 +6477,10 @@ bg=yellow UMLClass - 1372 - 7 - 161 - 42 + 702 + 18 + 207 + 54 *TimeRelativeProgressChange* @@ -6512,10 +6490,10 @@ bg=yellow Relation - 1526 - 21 - 63 - 21 + 900 + 36 + 81 + 27 lt=- 70.0;10.0;10.0;10.0 @@ -6523,10 +6501,10 @@ bg=yellow UMLClass - 1211 - 350 - 105 - 42 + 495 + 459 + 135 + 54 *Unconditional* @@ -6536,10 +6514,10 @@ bg=yellow Relation - 1330 - 126 - 21 - 259 + 648 + 171 + 27 + 333 lt=- 10.0;10.0;10.0;350.0 @@ -6547,10 +6525,10 @@ bg=yellow UMLClass - 1904 - 238 - 119 - 42 + 1386 + 315 + 153 + 54 *Unconditional* @@ -6560,10 +6538,10 @@ bg=yellow Relation - 1876 - 252 - 42 - 21 + 1350 + 333 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -6571,10 +6549,10 @@ bg=yellow UMLClass - 1540 - 1918 - 168 - 56 + 918 + 2475 + 216 + 72 *Cost* bg=pink @@ -6586,10 +6564,10 @@ payment_mode : PaymentMode UMLClass - 1750 - 1883 - 161 - 56 + 1188 + 2430 + 207 + 72 *ResourceCost* bg=pink @@ -6601,10 +6579,10 @@ amount : set(ResourceAmount) UMLClass - 1750 - 1953 - 161 - 56 + 1188 + 2520 + 207 + 72 *AttributeCost* bg=pink @@ -6616,10 +6594,10 @@ amount : set(AttributeAmount) Relation - 1701 - 1939 - 49 - 21 + 1125 + 2502 + 63 + 27 lt=<<- 10.0;10.0;50.0;10.0 @@ -6627,10 +6605,10 @@ amount : set(AttributeAmount) Relation - 1729 - 1904 - 35 - 49 + 1161 + 2457 + 45 + 63 lt=- 10.0;50.0;10.0;10.0;30.0;10.0 @@ -6638,10 +6616,10 @@ amount : set(AttributeAmount) Relation - 1729 - 1932 - 35 - 63 + 1161 + 2493 + 45 + 81 lt=- 10.0;10.0;10.0;70.0;30.0;70.0 @@ -6649,10 +6627,10 @@ amount : set(AttributeAmount) UMLClass - 1715 - 581 - 210 - 56 + 1143 + 756 + 270 + 72 *CreationAttributeCost* bg=yellow @@ -6666,10 +6644,10 @@ creatables : set(CreatableGameEntity) Relation - 1673 - 602 - 56 - 21 + 1089 + 783 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -6677,10 +6655,10 @@ creatables : set(CreatableGameEntity) Relation - 1638 - 728 - 56 - 21 + 1044 + 945 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -6688,10 +6666,10 @@ creatables : set(CreatableGameEntity) UMLClass - 1715 - 392 - 203 - 56 + 1143 + 513 + 261 + 72 *ResearchAttributeCost* bg=yellow @@ -6705,10 +6683,10 @@ researchables : set(ResearchableTech) Relation - 1673 - 413 - 56 - 21 + 1089 + 540 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -6716,10 +6694,10 @@ researchables : set(ResearchableTech) Relation - 1617 - 1967 - 21 - 49 + 1017 + 2538 + 27 + 63 lt=- 10.0;50.0;10.0;10.0 @@ -6727,10 +6705,10 @@ researchables : set(ResearchableTech) UMLClass - 1575 - 1848 - 98 - 42 + 963 + 2385 + 126 + 54 *PaymentMode* @@ -6740,21 +6718,21 @@ bg=pink Relation - 1617 - 1883 - 21 - 49 + 1017 + 2430 + 27 + 63 lt=<. - 10.0;50.0;10.0;10.0 + 10.0;10.0;10.0;50.0 UMLClass - 1533 - 1694 - 77 - 42 + 909 + 2187 + 99 + 54 *Advance* @@ -6764,10 +6742,10 @@ bg=pink UMLClass - 1533 - 1792 - 77 - 42 + 909 + 2313 + 99 + 54 *Arrear* @@ -6777,10 +6755,10 @@ bg=pink UMLClass - 1533 - 1743 - 77 - 42 + 909 + 2250 + 99 + 54 *Adaptive* @@ -6790,10 +6768,10 @@ bg=pink Relation - 1631 - 1659 - 21 - 203 + 1035 + 2142 + 27 + 261 lt=<<- 10.0;270.0;10.0;10.0 @@ -6801,10 +6779,10 @@ bg=pink Relation - 1603 - 1806 - 49 - 21 + 999 + 2331 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -6812,10 +6790,10 @@ bg=pink Relation - 1603 - 1757 - 49 - 21 + 999 + 2268 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -6823,10 +6801,10 @@ bg=pink Relation - 1603 - 1708 - 49 - 21 + 999 + 2205 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -6834,10 +6812,10 @@ bg=pink UMLClass - 2548 - 2807 - 112 - 42 + 2214 + 3618 + 144 + 54 *AttributeChange* @@ -6847,10 +6825,10 @@ bg=pink Relation - 2464 - 2940 - 77 - 21 + 2106 + 3789 + 99 + 27 lt=<<- 10.0;10.0;90.0;10.0 @@ -6858,10 +6836,10 @@ bg=pink UMLClass - 973 - 4760 - 182 - 42 + 189 + 6129 + 234 + 54 *TimeRelativeProgressDecrease* @@ -6871,10 +6849,10 @@ bg=orange UMLClass - 973 - 4809 - 182 - 42 + 189 + 6192 + 234 + 54 *TimeRelativeProgressIncrease* @@ -6884,10 +6862,10 @@ bg=orange Relation - 1148 - 4774 - 35 - 70 + 414 + 6147 + 45 + 90 lt=- 10.0;80.0;30.0;80.0;30.0;10.0 @@ -6895,10 +6873,10 @@ bg=orange Relation - 1148 - 4739 - 35 - 56 + 414 + 6102 + 45 + 72 lt=->> 10.0;60.0;30.0;60.0;30.0;10.0 @@ -6906,10 +6884,10 @@ bg=orange UMLClass - 1519 - 4760 - 182 - 42 + 891 + 6129 + 234 + 54 *TimeRelativeProgressDecrease* @@ -6919,10 +6897,10 @@ bg=orange UMLClass - 1519 - 4809 - 182 - 42 + 891 + 6192 + 234 + 54 *TimeRelativeProgressIncrease* @@ -6932,10 +6910,10 @@ bg=orange Relation - 1694 - 4774 - 35 - 70 + 1116 + 6147 + 45 + 90 lt=- 10.0;80.0;30.0;80.0;30.0;10.0 @@ -6943,10 +6921,10 @@ bg=orange Relation - 1694 - 4739 - 35 - 56 + 1116 + 6102 + 45 + 72 lt=->> 10.0;60.0;30.0;60.0;30.0;10.0 @@ -6954,10 +6932,10 @@ bg=orange UMLClass - 1533 - 1645 - 77 - 42 + 909 + 2124 + 99 + 54 *Shadow* @@ -6967,10 +6945,10 @@ bg=pink Relation - 1603 - 1659 - 49 - 21 + 999 + 2142 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -6978,10 +6956,10 @@ bg=pink UMLClass - 5390 - 2842 - 112 - 42 + 5868 + 3663 + 144 + 54 *HerdableMode* @@ -6991,10 +6969,10 @@ bg=pink Relation - 5439 - 2814 - 21 - 42 + 5931 + 3627 + 27 + 54 lt=<. 10.0;40.0;10.0;10.0 @@ -7002,10 +6980,10 @@ bg=pink UMLClass - 5453 - 2905 - 112 - 42 + 5949 + 3744 + 144 + 54 *ClosestHerding* @@ -7015,10 +6993,10 @@ bg=pink Relation - 5418 - 2877 - 21 - 161 + 5904 + 3708 + 27 + 207 lt=<<- 10.0;10.0;10.0;210.0 @@ -7026,10 +7004,10 @@ bg=pink Relation - 5418 - 2919 - 49 - 21 + 5904 + 3762 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -7037,10 +7015,10 @@ bg=pink UMLClass - 5453 - 2954 - 140 - 42 + 5949 + 3807 + 180 + 54 *LongestTimeInRange* @@ -7050,10 +7028,10 @@ bg=pink UMLClass - 5453 - 3003 - 112 - 42 + 5949 + 3870 + 144 + 54 *MostHerding* @@ -7063,10 +7041,10 @@ bg=pink Relation - 5418 - 2968 - 49 - 21 + 5904 + 3825 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -7074,10 +7052,10 @@ bg=pink Relation - 5418 - 3017 - 49 - 21 + 5904 + 3888 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -7085,10 +7063,10 @@ bg=pink UMLClass - 1778 - 2730 - 84 - 42 + 1269 + 3519 + 108 + 54 *TerrainType* @@ -7098,10 +7076,10 @@ bg=pink Relation - 1813 - 2695 - 21 - 49 + 1314 + 3474 + 27 + 63 lt=<. 10.0;50.0;10.0;10.0 @@ -7109,10 +7087,10 @@ bg=pink UMLClass - 1694 - 3759 - 182 - 63 + 1116 + 4842 + 234 + 81 *Stacked* bg=pink @@ -7127,10 +7105,10 @@ distribution_type : DistributionType Relation - 1659 - 3717 - 49 - 21 + 1071 + 4788 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -7138,10 +7116,10 @@ distribution_type : DistributionType UMLClass - 1967 - 3773 - 112 - 42 + 1467 + 4860 + 144 + 54 *CalculationType* @@ -7155,10 +7133,10 @@ bg=pink Relation - 1869 - 3787 - 112 - 21 + 1341 + 4878 + 144 + 27 lt=<. 140.0;10.0;10.0;10.0 @@ -7166,10 +7144,10 @@ bg=pink UMLClass - 2023 - 3955 - 112 - 70 + 1539 + 5094 + 144 + 90 *Hyperbolic* bg=pink @@ -7186,10 +7164,10 @@ scale_factor : float Relation - 1995 - 3808 - 21 - 182 + 1503 + 4905 + 27 + 234 lt=<<- 10.0;10.0;10.0;240.0 @@ -7197,10 +7175,10 @@ scale_factor : float Relation - 1883 - 4361 - 42 - 21 + 1359 + 5616 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -7208,10 +7186,10 @@ scale_factor : float Relation - 1995 - 3969 - 42 - 21 + 1503 + 5112 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -7219,10 +7197,10 @@ scale_factor : float UMLClass - 2023 - 3878 - 112 - 70 + 1539 + 4995 + 144 + 90 *Linear* bg=pink @@ -7239,10 +7217,10 @@ scale_factor : float Relation - 1995 - 3892 - 42 - 21 + 1503 + 5013 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -7250,10 +7228,10 @@ scale_factor : float UMLClass - 2023 - 3829 - 84 - 42 + 1539 + 4932 + 108 + 54 *NoStack* @@ -7266,10 +7244,10 @@ bg=pink Relation - 1995 - 3843 - 42 - 21 + 1503 + 4950 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -7277,10 +7255,10 @@ bg=pink UMLClass - 3332 - 2198 - 133 - 56 + 3222 + 2835 + 171 + 72 *OwnStorage* bg=pink @@ -7292,10 +7270,10 @@ container : EntityContainer Relation - 3297 - 2219 - 49 - 21 + 3177 + 2862 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -7303,10 +7281,10 @@ container : EntityContainer UMLClass - 3339 - 1631 - 217 - 56 + 3231 + 2106 + 279 + 72 *ProductionQueue* bg=green @@ -7319,10 +7297,10 @@ production_modes : set(ProductionMode) Relation - 3549 - 1652 - 42 - 84 + 3501 + 2133 + 54 + 108 lt=- 40.0;100.0;40.0;10.0;10.0;10.0 @@ -7330,10 +7308,10 @@ production_modes : set(ProductionMode) UMLClass - 3402 - 1561 - 112 - 42 + 3312 + 2016 + 144 + 54 *ProductionMode* @@ -7343,10 +7321,10 @@ bg=pink Relation - 3451 - 1596 - 21 - 49 + 3375 + 2061 + 27 + 63 lt=<. 10.0;10.0;10.0;50.0 @@ -7354,10 +7332,10 @@ bg=pink UMLClass - 3591 - 1617 - 189 - 56 + 3555 + 2088 + 243 + 72 *Creatables* bg=pink @@ -7369,10 +7347,10 @@ exclude : set(CreatableGameEntity) Relation - 3507 - 1575 - 98 - 21 + 3447 + 2034 + 126 + 27 lt=<<- 10.0;10.0;120.0;10.0 @@ -7380,10 +7358,10 @@ exclude : set(CreatableGameEntity) UMLClass - 3591 - 1554 - 168 - 56 + 3555 + 2007 + 216 + 72 *Researchables* bg=pink @@ -7395,10 +7373,10 @@ exclude : set(ResearchableTech) Relation - 3563 - 1575 - 42 - 84 + 3519 + 2034 + 54 + 108 lt=- 10.0;10.0;10.0;100.0;40.0;100.0 @@ -7406,10 +7384,10 @@ exclude : set(ResearchableTech) UMLClass - 1981 - 2814 - 140 - 56 + 1485 + 3627 + 180 + 72 *LogicElement* bg=pink @@ -7421,10 +7399,10 @@ only_once : bool UMLClass - 2226 - 2884 - 70 - 42 + 1800 + 3717 + 90 + 54 *AND* @@ -7434,10 +7412,10 @@ bg=pink Relation - 2205 - 2863 - 21 - 224 + 1773 + 3690 + 27 + 288 lt=<<- 10.0;10.0;10.0;300.0 @@ -7445,10 +7423,10 @@ bg=pink UMLClass - 2226 - 2933 - 70 - 42 + 1800 + 3780 + 90 + 54 *OR* @@ -7458,10 +7436,10 @@ bg=pink UMLClass - 2226 - 2982 - 98 - 56 + 1800 + 3843 + 126 + 72 *SUBSETMIN* bg=pink @@ -7473,10 +7451,10 @@ size : int Relation - 2191 - 2947 - 35 - 21 + 1755 + 3798 + 45 + 27 lt=- 30.0;10.0;10.0;10.0 @@ -7484,10 +7462,10 @@ size : int Relation - 2205 - 2947 - 35 - 21 + 1773 + 3798 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -7495,10 +7473,10 @@ size : int Relation - 2205 - 2898 - 35 - 21 + 1773 + 3735 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -7506,10 +7484,10 @@ size : int Relation - 5278 - 2723 - 42 - 21 + 5724 + 3510 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -7517,10 +7495,10 @@ size : int Relation - 5299 - 2653 - 42 - 21 + 5751 + 3420 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -7528,10 +7506,10 @@ size : int Relation - 5299 - 2583 - 42 - 21 + 5751 + 3330 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -7539,10 +7517,10 @@ size : int UMLClass - 1771 - 2905 - 147 - 56 + 1215 + 3744 + 189 + 72 *LiteralScope* bg=pink @@ -7554,10 +7532,10 @@ stances : set(DiplomaticStance) Relation - 1911 - 2926 - 63 - 21 + 1395 + 3771 + 81 + 27 lt=<. 10.0;10.0;70.0;10.0 @@ -7565,10 +7543,10 @@ stances : set(DiplomaticStance) UMLClass - 1750 - 2975 - 70 - 42 + 1188 + 3834 + 90 + 54 *Any* @@ -7579,10 +7557,10 @@ bg=pink Relation - 1827 - 2954 - 21 - 105 + 1287 + 3807 + 27 + 135 lt=<<- 10.0;10.0;10.0;130.0 @@ -7590,10 +7568,10 @@ bg=pink UMLClass - 1750 - 3024 - 70 - 42 + 1188 + 3897 + 90 + 54 *Self* @@ -7603,10 +7581,10 @@ bg=pink Relation - 1813 - 3038 - 35 - 21 + 1269 + 3915 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -7614,10 +7592,10 @@ bg=pink Relation - 1813 - 2989 - 35 - 21 + 1269 + 3852 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -7625,10 +7603,10 @@ bg=pink Relation - 2072 - 3003 - 35 - 21 + 1602 + 3870 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -7636,10 +7614,10 @@ bg=pink Relation - 2072 - 3066 - 35 - 21 + 1602 + 3951 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -7647,10 +7625,10 @@ bg=pink UMLClass - 1925 - 3115 - 154 - 56 + 1413 + 4014 + 198 + 72 *ResourceSpotsDepleted* bg=pink @@ -7662,10 +7640,10 @@ only_enabled : bool UMLClass - 1925 - 3178 - 154 - 56 + 1413 + 4095 + 198 + 72 *Timer* bg=pink @@ -7677,10 +7655,10 @@ time : float Relation - 2072 - 3136 - 35 - 21 + 1602 + 4041 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -7688,10 +7666,10 @@ time : float Relation - 2072 - 3199 - 35 - 21 + 1602 + 4122 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -7699,10 +7677,10 @@ time : float UMLClass - 1925 - 3241 - 154 - 56 + 1413 + 4176 + 198 + 72 *AttributeBelowValue* bg=pink @@ -7715,10 +7693,10 @@ threshold : float UMLClass - 1925 - 3430 - 154 - 56 + 1413 + 4419 + 198 + 72 *ProjectilePassThrough* bg=pink @@ -7730,10 +7708,10 @@ pass_through_range : int UMLClass - 1946 - 3493 - 133 - 42 + 1440 + 4500 + 171 + 54 *ProjectileHitTerrain* @@ -7743,10 +7721,10 @@ bg=pink Relation - 2072 - 3262 - 35 - 21 + 1602 + 4203 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -7754,10 +7732,10 @@ bg=pink Relation - 2072 - 3451 - 35 - 21 + 1602 + 4446 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -7765,10 +7743,10 @@ bg=pink Relation - 2072 - 3507 - 35 - 21 + 1602 + 4518 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -7776,10 +7754,10 @@ bg=pink UMLClass - 4529 - 3087 - 98 - 70 + 4851 + 3987 + 126 + 90 *Hitbox* bg=pink @@ -7793,37 +7771,10 @@ radius_z : float Relation - 4494 - 3108 - 49 - 21 - - lt=<. - 50.0;10.0;10.0;10.0 - - - UMLClass - - 4599 - 3017 - 224 - 63 - - *PassableMode* -bg=pink - --- -allowed_types : set(GameEntityType) -blacklisted_entities : set(GameEntity) - - - - Relation - - 4550 - 3038 - 63 - 21 + 4788 + 4014 + 81 + 27 lt=<. 70.0;10.0;10.0;10.0 @@ -7831,77 +7782,15 @@ blacklisted_entities : set(GameEntity) UMLClass - 4753 - 3143 - 154 - 56 - - *Gate* -bg=pink - --- -stances : set (DiplomaticStance) - - - - UMLClass - - 4753 - 3094 - 77 - 42 - - -*Normal* -bg=pink - - - - Relation - - 4725 - 3073 - 21 - 105 - - lt=<<- - 10.0;10.0;10.0;130.0 - - - Relation - - 4725 - 3108 - 42 - 21 - - lt=- - 10.0;10.0;40.0;10.0 - - - Relation - - 4725 - 3157 - 42 - 21 - - lt=- - 10.0;10.0;40.0;10.0 - - - UMLClass - - 4403 - 2870 - 189 - 70 + 4599 + 3699 + 243 + 90 *DetectCloak (SWGB)* bg=green -- -range : float allowed_types : set(GameEntityType) blacklisted_entities : set(GameEntity) @@ -7909,10 +7798,10 @@ blacklisted_entities : set(GameEntity) Relation - 4375 - 2891 - 42 - 21 + 4563 + 3726 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -7920,10 +7809,10 @@ blacklisted_entities : set(GameEntity) UMLClass - 1827 - 3843 - 112 - 42 + 1287 + 4950 + 144 + 54 *DistributionType* @@ -7939,10 +7828,10 @@ bg=pink Relation - 1785 - 3815 - 56 - 63 + 1233 + 4914 + 72 + 81 lt=<. 60.0;70.0;10.0;70.0;10.0;10.0 @@ -7950,10 +7839,10 @@ bg=pink UMLClass - 1890 - 3899 - 70 - 42 + 1368 + 5022 + 90 + 54 *Mean* @@ -7963,10 +7852,10 @@ bg=pink Relation - 1862 - 3878 - 21 - 56 + 1332 + 4995 + 27 + 72 lt=<<- 10.0;10.0;10.0;60.0 @@ -7974,10 +7863,10 @@ bg=pink Relation - 1862 - 3913 - 42 - 21 + 1332 + 5040 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -7985,10 +7874,10 @@ bg=pink UMLClass - 5208 - 2856 - 98 - 63 + 5634 + 3681 + 126 + 81 *Rectangle* bg=pink @@ -8001,10 +7890,10 @@ height : float UMLClass - 5082 - 2856 - 98 - 42 + 5472 + 3681 + 126 + 54 *MatchToSprite* @@ -8014,10 +7903,10 @@ bg=pink UMLClass - 5145 - 2786 - 98 - 42 + 5553 + 3591 + 126 + 54 *SelectionBox* @@ -8027,10 +7916,10 @@ bg=pink Relation - 5187 - 2758 - 21 - 42 + 5607 + 3555 + 27 + 54 lt=<. 10.0;40.0;10.0;10.0 @@ -8038,10 +7927,10 @@ bg=pink Relation - 5215 - 2821 - 21 - 49 + 5643 + 3636 + 27 + 63 lt=<<- 10.0;10.0;10.0;50.0 @@ -8049,10 +7938,10 @@ bg=pink Relation - 5159 - 2821 - 21 - 49 + 5571 + 3636 + 27 + 63 lt=<<- 10.0;10.0;10.0;50.0 @@ -8060,10 +7949,10 @@ bg=pink UMLClass - 1841 - 1232 - 119 - 56 + 1305 + 1593 + 153 + 72 *Stacked* bg=pink @@ -8075,10 +7964,10 @@ stack_limit : int Relation - 1988 - 1134 - 21 - 203 + 1494 + 1467 + 27 + 261 lt=<<- 10.0;10.0;10.0;270.0 @@ -8086,21 +7975,10 @@ stack_limit : int Relation - 1694 - 2744 - 98 - 21 - - lt=- - 10.0;10.0;120.0;10.0 - - - Relation - - 5012 - 3220 - 42 - 21 + 5382 + 4149 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -8108,10 +7986,10 @@ stack_limit : int UMLClass - 1736 - 2324 - 84 - 42 + 1170 + 2997 + 108 + 54 *NyanPatch* @@ -8121,10 +7999,10 @@ bg=pink Relation - 1694 - 2338 - 56 - 21 + 1116 + 3015 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -8132,10 +8010,10 @@ bg=pink UMLClass - 2107 - 3241 - 154 - 56 + 1647 + 4176 + 198 + 72 *AttributeAbovePercentage* bg=pink @@ -8148,10 +8026,10 @@ threshold : float Relation - 2072 - 3325 - 35 - 21 + 1602 + 4284 + 45 + 27 lt=- 30.0;10.0;10.0;10.0 @@ -8159,10 +8037,10 @@ threshold : float Relation - 2261 - 2478 - 63 - 21 + 1845 + 3195 + 81 + 27 lt=- 70.0;10.0;10.0;10.0 @@ -8170,10 +8048,10 @@ threshold : float UMLClass - 2310 - 2394 - 133 - 56 + 1908 + 3087 + 171 + 72 *Palette* bg=pink @@ -8185,10 +8063,10 @@ palette : file Relation - 2261 - 2415 - 63 - 21 + 1845 + 3114 + 81 + 27 lt=- 70.0;10.0;10.0;10.0 @@ -8196,10 +8074,10 @@ palette : file Relation - 4018 - 2702 - 49 - 21 + 4104 + 3483 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -8207,10 +8085,10 @@ palette : file UMLClass - 4053 - 2800 - 112 - 56 + 4149 + 3609 + 144 + 72 *Guard* bg=pink @@ -8222,10 +8100,10 @@ range : float Relation - 4018 - 2821 - 49 - 21 + 4104 + 3636 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -8233,10 +8111,10 @@ range : float UMLClass - 3332 - 2086 - 147 - 56 + 3222 + 2691 + 189 + 72 *Replace* bg=pink @@ -8248,10 +8126,10 @@ game_entities : set(GameEntity) Relation - 3297 - 2107 - 49 - 21 + 3177 + 2718 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -8259,10 +8137,10 @@ game_entities : set(GameEntity) UMLClass - 1582 - 2709 - 84 - 42 + 972 + 3609 + 108 + 54 *TechType* @@ -8272,10 +8150,10 @@ bg=pink Relation - 1659 - 2723 - 56 - 21 + 1071 + 3627 + 72 + 27 lt=- 10.0;10.0;60.0;10.0 @@ -8283,21 +8161,21 @@ bg=pink Relation - 1617 - 2744 - 21 - 56 + 1017 + 3654 + 27 + 63 lt=<. - 10.0;10.0;10.0;60.0 + 10.0;10.0;10.0;50.0 UMLClass - 1596 - 2646 - 70 - 42 + 990 + 3528 + 90 + 54 *Any* @@ -8307,21 +8185,10 @@ bg=pink Relation - 1659 - 2660 - 56 - 21 - - lt=- - 10.0;10.0;60.0;10.0 - - - Relation - - 1624 - 2681 - 21 - 42 + 1026 + 3573 + 27 + 54 lt=<<- 10.0;40.0;10.0;10.0 @@ -8329,10 +8196,10 @@ bg=pink UMLClass - 1813 - 2793 - 70 - 42 + 1278 + 3600 + 90 + 54 *Any* @@ -8342,10 +8209,10 @@ bg=pink Relation - 1834 - 2765 - 21 - 42 + 1314 + 3564 + 27 + 54 lt=<<- 10.0;10.0;10.0;40.0 @@ -8353,10 +8220,10 @@ bg=pink UMLClass - 1337 - 2436 - 70 - 42 + 657 + 3141 + 90 + 54 *Any* @@ -8366,10 +8233,10 @@ bg=pink Relation - 1400 - 2450 - 49 - 49 + 738 + 3159 + 63 + 63 lt=<<- 50.0;50.0;50.0;10.0;10.0;10.0 @@ -8377,10 +8244,10 @@ bg=pink UMLClass - 2555 - 3199 - 161 - 56 + 2223 + 4122 + 207 + 72 *AnimationOverlay* bg=pink @@ -8393,10 +8260,10 @@ overlays : set(Animation) Relation - 2527 - 3220 - 42 - 21 + 2187 + 4149 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -8404,10 +8271,10 @@ overlays : set(Animation) UMLClass - 2107 - 3178 - 154 - 56 + 1647 + 4095 + 198 + 72 *AttributeBelowPercentage* bg=pink @@ -8420,10 +8287,10 @@ threshold : float UMLClass - 1925 - 3304 - 154 - 56 + 1413 + 4257 + 198 + 72 *AttributeAboveValue* bg=pink @@ -8436,10 +8303,10 @@ threshold : float Relation - 2086 - 3199 - 35 - 21 + 1620 + 4122 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -8447,10 +8314,10 @@ threshold : float Relation - 2086 - 3262 - 35 - 21 + 1620 + 4203 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -8458,10 +8325,10 @@ threshold : float UMLClass - 1092 - 294 - 224 - 49 + 342 + 387 + 288 + 63 *ElevationDifferenceHigh* bg=yellow @@ -8473,10 +8340,10 @@ min_elevation_difference : optional(float) = None Relation - 1309 - 315 - 42 - 21 + 621 + 414 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -8484,10 +8351,10 @@ min_elevation_difference : optional(float) = None Relation - 1309 - 364 - 42 - 21 + 621 + 477 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -8495,10 +8362,10 @@ min_elevation_difference : optional(float) = None UMLClass - 1904 - 63 - 224 - 56 + 1386 + 90 + 288 + 72 *ElevationDifferenceHigh* bg=yellow @@ -8510,10 +8377,10 @@ min_elevation_difference : optional(float) = None Relation - 1876 - 84 - 42 - 21 + 1350 + 117 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -8521,10 +8388,10 @@ min_elevation_difference : optional(float) = None UMLClass - 1092 - 2898 - 91 - 42 + 342 + 3735 + 117 + 54 *MiscVariant* @@ -8534,10 +8401,10 @@ bg=pink Relation - 1176 - 2779 - 126 - 154 + 450 + 3582 + 162 + 198 lt=- 160.0;10.0;160.0;200.0;10.0;200.0 @@ -8545,10 +8412,10 @@ bg=pink UMLClass - 4074 - 2331 - 168 - 56 + 4176 + 3006 + 216 + 72 *ResourceStorage* bg=green @@ -8560,10 +8427,10 @@ containers : set(ResourceContainer) Relation - 4235 - 2352 - 42 - 21 + 4383 + 3033 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -8571,10 +8438,10 @@ containers : set(ResourceContainer) UMLClass - 3885 - 2331 - 168 - 63 + 3933 + 3006 + 216 + 90 *ResourceContainer* bg=pink @@ -8588,10 +8455,10 @@ carry_progress : set(Progress) Relation - 4046 - 2352 - 42 - 21 + 4140 + 3033 + 54 + 27 lt=<. 10.0;10.0;40.0;10.0 @@ -8599,10 +8466,10 @@ carry_progress : set(Progress) UMLClass - 3906 - 2415 - 126 - 56 + 3960 + 3123 + 162 + 72 *InternalDropSite* bg=pink @@ -8614,10 +8481,10 @@ update_time : float Relation - 3962 - 2387 - 21 - 42 + 4032 + 3087 + 27 + 54 lt=<<- 10.0;10.0;10.0;40.0 @@ -8625,10 +8492,10 @@ update_time : float UMLClass - 1715 - 2107 - 98 - 42 + 1143 + 2718 + 126 + 54 *TransformPool* @@ -8638,10 +8505,10 @@ bg=pink Relation - 1806 - 2121 - 35 - 21 + 1260 + 2736 + 45 + 27 lt=<. 10.0;10.0;30.0;10.0 @@ -8649,10 +8516,10 @@ bg=pink Relation - 3948 - 3157 - 42 - 21 + 4014 + 4068 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 @@ -8660,10 +8527,10 @@ bg=pink UMLClass - 2485 - 2324 - 70 - 42 + 2133 + 2997 + 90 + 54 *Any* @@ -8673,10 +8540,10 @@ bg=pink Relation - 2513 - 2296 - 21 - 42 + 2169 + 2961 + 27 + 54 lt=- 10.0;40.0;10.0;10.0 @@ -8684,10 +8551,10 @@ bg=pink Relation - 2597 - 2296 - 21 - 42 + 2277 + 2961 + 27 + 54 lt=- 10.0;40.0;10.0;10.0 @@ -8695,10 +8562,10 @@ bg=pink UMLClass - 3843 - 1617 - 287 - 70 + 3879 + 2088 + 369 + 90 *ExchangeRate* bg=pink @@ -8712,10 +8579,10 @@ price_pool : optional(PricePool) = None UMLClass - 3892 - 1722 - 182 - 77 + 3942 + 2223 + 234 + 99 *ExchangeResources* bg=green @@ -8730,10 +8597,10 @@ exchange_modes : set(ExchangeMode) Relation - 3962 - 1526 - 21 - 105 + 4032 + 1971 + 27 + 135 lt=<. 10.0;10.0;10.0;130.0 @@ -8741,10 +8608,10 @@ exchange_modes : set(ExchangeMode) UMLClass - 4102 - 1729 - 112 - 56 + 4212 + 2232 + 144 + 72 *ExchangeMode* bg=pink @@ -8756,10 +8623,10 @@ fee_multiplier : float Relation - 4067 - 1750 - 49 - 21 + 4167 + 2259 + 63 + 27 lt=<. 50.0;10.0;10.0;10.0 @@ -8767,10 +8634,10 @@ fee_multiplier : float UMLClass - 4270 - 1708 - 70 - 42 + 4428 + 2205 + 90 + 54 *Sell* @@ -8780,10 +8647,10 @@ bg=pink UMLClass - 4270 - 1757 - 70 - 42 + 4428 + 2268 + 90 + 54 *Buy* @@ -8793,10 +8660,10 @@ bg=pink Relation - 4207 - 1736 - 77 - 21 + 4347 + 2241 + 99 + 27 lt=<<- 10.0;10.0;90.0;10.0 @@ -8804,10 +8671,10 @@ bg=pink Relation - 4207 - 1757 - 77 - 21 + 4347 + 2268 + 99 + 27 lt=<<- 10.0;10.0;90.0;10.0 @@ -8815,10 +8682,10 @@ bg=pink Relation - 2030 - 2863 - 21 - 56 + 1548 + 3690 + 27 + 72 lt=<<- 10.0;10.0;10.0;60.0 @@ -8826,10 +8693,10 @@ bg=pink Relation - 2114 - 2835 - 49 - 21 + 1656 + 3654 + 63 + 27 lt=<<- 10.0;10.0;50.0;10.0 @@ -8837,10 +8704,10 @@ bg=pink UMLClass - 2149 - 2814 - 133 - 56 + 1701 + 3627 + 171 + 72 *LogicGate* bg=pink @@ -8852,10 +8719,10 @@ inputs : set(LogicElement) UMLClass - 2226 - 3045 - 98 - 56 + 1800 + 3924 + 126 + 72 *SUBSETMAX* bg=pink @@ -8867,10 +8734,10 @@ size : int Relation - 2205 - 3066 - 35 - 21 + 1773 + 3951 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -8878,10 +8745,10 @@ size : int UMLClass - 2128 - 2884 - 70 - 42 + 1674 + 3717 + 90 + 54 *NOT* @@ -8891,10 +8758,10 @@ bg=pink Relation - 2191 - 2898 - 35 - 21 + 1755 + 3735 + 45 + 27 lt=- 30.0;10.0;10.0;10.0 @@ -8902,10 +8769,10 @@ bg=pink UMLClass - 2128 - 2933 - 70 - 42 + 1674 + 3780 + 90 + 54 *XOR* @@ -8915,10 +8782,10 @@ bg=pink Relation - 2205 - 3003 - 35 - 21 + 1773 + 3870 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -8926,10 +8793,10 @@ bg=pink UMLClass - 2128 - 2982 - 70 - 42 + 1674 + 3843 + 90 + 54 *MULTIXOR* @@ -8939,10 +8806,10 @@ bg=pink Relation - 2191 - 2996 - 35 - 21 + 1755 + 3861 + 45 + 27 lt=- 30.0;10.0;10.0;10.0 @@ -8950,10 +8817,10 @@ bg=pink UMLClass - 3290 - 2555 - 105 - 42 + 3168 + 3294 + 135 + 54 *AbilityProperty* @@ -8963,10 +8830,10 @@ bg=pink Relation - 3332 - 2534 - 21 - 35 + 3222 + 3267 + 27 + 45 lt=- 10.0;10.0;10.0;30.0 @@ -8974,10 +8841,10 @@ bg=pink Relation - 3332 - 2891 - 49 - 21 + 3222 + 3726 + 63 + 27 lt=- 50.0;10.0;10.0;10.0 @@ -8985,10 +8852,10 @@ bg=pink UMLClass - 3367 - 2933 - 154 - 56 + 3267 + 3780 + 180 + 72 *Lock* bg=pink @@ -9000,10 +8867,10 @@ lock_pool : LockPool Relation - 3332 - 2954 - 49 - 21 + 3222 + 3807 + 63 + 27 lt=- 50.0;10.0;10.0;10.0 @@ -9011,10 +8878,10 @@ lock_pool : LockPool UMLClass - 4403 - 2807 - 133 - 56 + 4599 + 3618 + 171 + 72 *Lock* bg=green @@ -9026,10 +8893,10 @@ lock_pools : set(LockPool) Relation - 4375 - 2828 - 42 - 21 + 4563 + 3645 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9037,10 +8904,10 @@ lock_pools : set(LockPool) UMLClass - 4564 - 2807 - 105 - 56 + 4806 + 3618 + 135 + 72 *LockPool* bg=pink @@ -9052,10 +8919,10 @@ slots : int Relation - 4529 - 2828 - 49 - 21 + 4761 + 3645 + 63 + 27 lt=<. 50.0;10.0;10.0;10.0 @@ -9063,10 +8930,10 @@ slots : int UMLClass - 1918 - 1099 - 112 - 42 + 1404 + 1422 + 144 + 54 *ModifierProperty* @@ -9076,10 +8943,10 @@ bg=pink Relation - 2023 - 1113 - 70 - 21 + 1539 + 1440 + 90 + 27 lt=- 80.0;10.0;10.0;10.0 @@ -9087,10 +8954,10 @@ bg=pink Relation - 1953 - 1183 - 56 - 21 + 1449 + 1530 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -9098,10 +8965,10 @@ bg=pink Relation - 1953 - 1253 - 56 - 21 + 1449 + 1620 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -9109,10 +8976,10 @@ bg=pink UMLClass - 1841 - 1295 - 119 - 56 + 1305 + 1674 + 153 + 72 *Multiplier* bg=pink @@ -9124,10 +8991,10 @@ multiplier : float Relation - 1953 - 1316 - 56 - 21 + 1449 + 1701 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -9135,10 +9002,10 @@ multiplier : float Relation - 2464 - 623 - 56 - 21 + 2106 + 810 + 72 + 27 lt=- 60.0;10.0;10.0;10.0 @@ -9146,10 +9013,10 @@ multiplier : float UMLNote - 1589 - 315 - 42 - 21 + 981 + 414 + 54 + 27 effect bg=blue @@ -9158,10 +9025,10 @@ bg=blue UMLNote - 1722 - 315 - 63 - 21 + 1152 + 414 + 81 + 27 resistance bg=blue @@ -9170,10 +9037,10 @@ bg=blue UMLNote - 1344 - 203 - 42 - 21 + 666 + 270 + 54 + 27 flac bg=blue @@ -9182,10 +9049,10 @@ bg=blue UMLNote - 1806 - 119 - 42 - 21 + 1260 + 162 + 54 + 27 flac bg=blue @@ -9194,10 +9061,10 @@ bg=blue UMLClass - 1316 - 3843 - 98 - 42 + 630 + 4950 + 126 + 54 *EffectProperty* @@ -9207,10 +9074,10 @@ bg=pink Relation - 1407 - 3857 - 119 - 21 + 747 + 4968 + 153 + 27 lt=- 10.0;10.0;150.0;10.0 @@ -9218,10 +9085,10 @@ bg=pink Relation - 1358 - 3591 - 21 - 266 + 684 + 4626 + 27 + 342 lt=<<- 10.0;360.0;10.0;10.0 @@ -9229,10 +9096,10 @@ bg=pink UMLClass - 1197 - 3759 - 140 - 56 + 477 + 4842 + 180 + 72 *Cost* bg=pink @@ -9244,10 +9111,10 @@ cost : Cost UMLClass - 1169 - 3696 - 168 - 56 + 441 + 4761 + 216 + 72 *Diplomatic* bg=pink @@ -9259,10 +9126,10 @@ stances : set(DiplomaticStance) Relation - 1456 - 3304 - 70 - 21 + 810 + 4257 + 90 + 27 lt=- 10.0;10.0;80.0;10.0 @@ -9270,10 +9137,10 @@ stances : set(DiplomaticStance) UMLClass - 1218 - 3633 - 119 - 56 + 504 + 4680 + 153 + 72 *AreaEffect* bg=pink @@ -9286,10 +9153,10 @@ dropoff : DropoffType Relation - 1330 - 3654 - 49 - 21 + 648 + 4707 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -9297,10 +9164,10 @@ dropoff : DropoffType Relation - 1330 - 3717 - 49 - 21 + 648 + 4788 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -9308,10 +9175,10 @@ dropoff : DropoffType Relation - 1330 - 3780 - 49 - 21 + 648 + 4869 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -9319,10 +9186,10 @@ dropoff : DropoffType UMLClass - 1617 - 3843 - 105 - 42 + 1017 + 4950 + 135 + 54 *ResistanceProperty* @@ -9332,10 +9199,10 @@ bg=pink Relation - 1505 - 3857 - 126 - 21 + 873 + 4968 + 162 + 27 lt=- 10.0;10.0;160.0;10.0 @@ -9343,10 +9210,10 @@ bg=pink Relation - 1659 - 3717 - 21 - 140 + 1071 + 4788 + 27 + 180 lt=<<- 10.0;180.0;10.0;10.0 @@ -9354,10 +9221,10 @@ bg=pink Relation - 2527 - 3171 - 21 - 322 + 2187 + 4086 + 27 + 414 lt=<<- 10.0;10.0;10.0;440.0 @@ -9365,10 +9232,10 @@ bg=pink UMLClass - 2457 - 3136 - 112 - 42 + 2097 + 4041 + 144 + 54 *ProgressProperty* @@ -9379,10 +9246,10 @@ bg=pink Relation - 2331 - 3150 - 140 - 21 + 1935 + 4059 + 180 + 27 lt=- 10.0;10.0;180.0;10.0 @@ -9390,10 +9257,10 @@ bg=pink Relation - 2205 - 2618 - 21 - 56 + 1773 + 3375 + 27 + 72 lt=<<- 10.0;10.0;10.0;60.0 @@ -9401,10 +9268,10 @@ bg=pink UMLClass - 2177 - 2660 - 70 - 42 + 1737 + 3429 + 90 + 54 *Reset* @@ -9414,10 +9281,10 @@ bg=pink UMLClass - 1729 - 2156 - 70 - 42 + 1161 + 2781 + 90 + 54 *Reset* @@ -9427,10 +9294,10 @@ bg=pink Relation - 1792 - 2170 - 49 - 21 + 1242 + 2799 + 63 + 27 lt=<<- 50.0;10.0;10.0;10.0 @@ -9438,10 +9305,10 @@ bg=pink UMLClass - 1946 - 3542 - 133 - 56 + 1440 + 4563 + 171 + 72 *StateChangeActive* bg=pink @@ -9453,10 +9320,10 @@ state_change : StateChanger Relation - 2072 - 3563 - 35 - 21 + 1602 + 4590 + 45 + 27 lt=- 10.0;10.0;30.0;10.0 @@ -9464,10 +9331,10 @@ state_change : StateChanger UMLClass - 1925 - 3367 - 154 - 56 + 1413 + 4338 + 198 + 72 *OwnsGameEntity* bg=pink @@ -9479,10 +9346,10 @@ game_entity : GameEntity Relation - 2072 - 3388 - 35 - 21 + 1602 + 4365 + 45 + 27 lt=- 30.0;10.0;10.0;10.0 @@ -9490,10 +9357,10 @@ game_entity : GameEntity UMLClass - 5397 - 2037 - 231 - 56 + 5877 + 2628 + 297 + 72 *EffectBatch* bg=pink @@ -9507,10 +9374,10 @@ properties : dict(BatchProperty, BatchProperty) = {} UMLClass - 5670 - 2044 - 105 - 42 + 6228 + 2637 + 135 + 54 *BatchProperty* @@ -9521,10 +9388,10 @@ bg=pink UMLClass - 5719 - 2107 - 119 - 56 + 6291 + 2718 + 153 + 72 *Priority* bg=pink @@ -9537,10 +9404,10 @@ priority : int Relation - 5691 - 2079 - 21 - 133 + 6255 + 2682 + 27 + 171 lt=<<- 10.0;10.0;10.0;170.0 @@ -9548,10 +9415,10 @@ priority : int Relation - 5691 - 2128 - 42 - 21 + 6255 + 2745 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9559,10 +9426,10 @@ priority : int UMLClass - 5719 - 2170 - 119 - 56 + 6291 + 2799 + 153 + 72 *Chance* bg=pink @@ -9575,10 +9442,10 @@ chance : float Relation - 5691 - 2191 - 42 - 21 + 6255 + 2826 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9586,10 +9453,10 @@ chance : float Relation - 5621 - 2058 - 63 - 21 + 6165 + 2655 + 81 + 27 lt=<. 70.0;10.0;10.0;10.0 @@ -9597,10 +9464,10 @@ chance : float Relation - 5355 - 2009 - 56 - 70 + 5823 + 2592 + 72 + 90 lt=<. 60.0;80.0;10.0;80.0;10.0;10.0 @@ -9608,10 +9475,10 @@ chance : float UMLClass - 5467 - 2107 - 105 - 42 + 5967 + 2718 + 135 + 54 *UnorderedBatch* @@ -9622,10 +9489,10 @@ bg=pink Relation - 5439 - 2086 - 21 - 154 + 5931 + 2691 + 27 + 198 lt=<<- 10.0;10.0;10.0;200.0 @@ -9633,10 +9500,10 @@ bg=pink Relation - 5439 - 2121 - 42 - 21 + 5931 + 2736 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9644,10 +9511,10 @@ bg=pink UMLClass - 5467 - 2156 - 105 - 42 + 5967 + 2781 + 135 + 54 *OrderedBatch* @@ -9659,10 +9526,10 @@ bg=pink Relation - 5439 - 2170 - 42 - 21 + 5931 + 2799 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9670,10 +9537,10 @@ bg=pink UMLClass - 5467 - 2205 - 105 - 42 + 5967 + 2844 + 135 + 54 *ChainedBatch* @@ -9684,10 +9551,10 @@ bg=pink Relation - 5439 - 2219 - 42 - 21 + 5931 + 2862 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9695,10 +9562,10 @@ bg=pink UMLClass - 1197 - 3570 - 140 - 56 + 477 + 4599 + 180 + 72 *Priority* bg=pink @@ -9710,10 +9577,10 @@ priority : int Relation - 1330 - 3591 - 49 - 21 + 648 + 4626 + 63 + 27 lt=- 10.0;10.0;50.0;10.0 @@ -9721,10 +9588,10 @@ priority : int UMLClass - 1799 - 2387 - 84 - 42 + 1251 + 3078 + 108 + 54 *PatchProperty* @@ -9734,10 +9601,10 @@ bg=pink Relation - 1876 - 2401 - 42 - 21 + 1350 + 3096 + 54 + 27 lt=<<- 10.0;10.0;40.0;10.0 @@ -9745,10 +9612,10 @@ bg=pink Relation - 1778 - 2359 - 21 - 126 + 1224 + 3042 + 27 + 162 lt=<. 10.0;10.0;10.0;160.0 @@ -9756,10 +9623,10 @@ bg=pink Relation - 1834 - 2422 - 21 - 63 + 1296 + 3123 + 27 + 81 lt=<. 10.0;10.0;10.0;70.0 @@ -9767,10 +9634,10 @@ bg=pink Relation - 1960 - 2828 - 35 - 21 + 1458 + 3645 + 45 + 27 lt=<<- 30.0;10.0;10.0;10.0 @@ -9778,10 +9645,10 @@ bg=pink UMLClass - 1897 - 2849 - 70 - 42 + 1377 + 3672 + 90 + 54 *True* @@ -9791,10 +9658,10 @@ bg=pink UMLClass - 1897 - 2800 - 70 - 42 + 1377 + 3609 + 90 + 54 *False* @@ -9804,10 +9671,10 @@ bg=pink Relation - 1960 - 2849 - 35 - 21 + 1458 + 3672 + 45 + 27 lt=<<- 30.0;10.0;10.0;10.0 @@ -9815,10 +9682,10 @@ bg=pink Relation - 1876 - 21 - 42 - 21 + 1350 + 36 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9826,10 +9693,10 @@ bg=pink Relation - 1876 - 147 - 42 - 21 + 1350 + 198 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9837,10 +9704,10 @@ bg=pink UMLClass - 945 - 2541 - 84 - 42 + 153 + 3276 + 108 + 54 *Tree* @@ -9849,10 +9716,10 @@ bg=pink UMLClass - 945 - 2590 - 84 - 42 + 153 + 3339 + 108 + 54 *Relic* @@ -9861,10 +9728,10 @@ bg=pink Relation - 1043 - 2534 - 91 - 21 + 279 + 3267 + 117 + 27 lt=<<- 110.0;10.0;10.0;10.0 @@ -9872,10 +9739,10 @@ bg=pink Relation - 1043 - 2457 - 21 - 217 + 279 + 3168 + 27 + 279 lt=- 10.0;10.0;10.0;290.0 @@ -9883,10 +9750,10 @@ bg=pink Relation - 1022 - 2604 - 42 - 21 + 252 + 3357 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9894,10 +9761,10 @@ bg=pink UMLClass - 945 - 2492 - 84 - 42 + 153 + 3213 + 108 + 54 *Swordsman* @@ -9906,10 +9773,10 @@ bg=pink UMLClass - 945 - 2443 - 84 - 42 + 153 + 3150 + 108 + 54 *Barracks* @@ -9918,10 +9785,10 @@ bg=pink Relation - 1022 - 2555 - 42 - 21 + 252 + 3294 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9929,10 +9796,10 @@ bg=pink Relation - 1022 - 2506 - 42 - 21 + 252 + 3231 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9940,10 +9807,10 @@ bg=pink Relation - 1022 - 2457 - 42 - 21 + 252 + 3168 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9951,10 +9818,10 @@ bg=pink UMLClass - 945 - 2639 - 84 - 42 + 153 + 3402 + 108 + 54 *Projectile* @@ -9963,10 +9830,10 @@ bg=pink Relation - 1022 - 2653 - 42 - 21 + 252 + 3420 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -9974,22 +9841,23 @@ bg=pink UMLNote - 840 - 2443 - 98 - 49 + 18 + 3150 + 126 + 63 - All ingame objects are game entities + All ingame objects +are game entities bg=blue UMLClass - 2548 - 2905 - 84 - 42 + 2214 + 3744 + 108 + 54 *Construct* @@ -9999,10 +9867,10 @@ bg=pink UMLClass - 2548 - 2954 - 84 - 42 + 2214 + 3807 + 108 + 54 *Harvest* @@ -10012,10 +9880,10 @@ bg=pink UMLClass - 2548 - 3003 - 84 - 42 + 2214 + 3870 + 108 + 54 *Restock* @@ -10025,10 +9893,10 @@ bg=pink UMLClass - 2548 - 2856 - 84 - 42 + 2214 + 3681 + 108 + 54 *Carry* @@ -10038,10 +9906,10 @@ bg=pink UMLClass - 2548 - 3052 - 84 - 42 + 2214 + 3933 + 108 + 54 *Transform* @@ -10051,10 +9919,10 @@ bg=pink Relation - 2520 - 2821 - 21 - 266 + 2178 + 3636 + 27 + 342 lt=- 10.0;10.0;10.0;360.0 @@ -10062,10 +9930,10 @@ bg=pink Relation - 2520 - 2821 - 42 - 21 + 2178 + 3636 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -10073,10 +9941,10 @@ bg=pink Relation - 2520 - 2870 - 42 - 21 + 2178 + 3699 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -10084,10 +9952,10 @@ bg=pink Relation - 2520 - 2919 - 42 - 21 + 2178 + 3762 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -10095,10 +9963,10 @@ bg=pink Relation - 2520 - 2968 - 42 - 21 + 2178 + 3825 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -10106,10 +9974,10 @@ bg=pink Relation - 2520 - 3017 - 42 - 21 + 2178 + 3888 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -10117,10 +9985,10 @@ bg=pink Relation - 2520 - 3066 - 42 - 21 + 2178 + 3951 + 54 + 27 lt=- 10.0;10.0;40.0;10.0 @@ -10128,10 +9996,10 @@ bg=pink UMLClass - 4823 - 3269 - 175 - 56 + 5139 + 4212 + 225 + 72 *Constructable* bg=green @@ -10144,12 +10012,858 @@ construction_progress : set(Progress) Relation - 4991 - 3290 - 42 - 21 + 5355 + 4239 + 54 + 27 + + lt=- + 40.0;10.0;10.0;10.0 + + + UMLClass + + 1989 + 2016 + 99 + 54 + + +*Node* +bg=pink + + + + Relation + + 2034 + 2061 + 27 + 342 + + lt=<<- + 10.0;10.0;10.0;360.0 + + + UMLClass + + 1872 + 2097 + 144 + 72 + + *Start* +bg=pink + +-- +next : Node + + + + Relation + + 2007 + 2124 + 54 + 27 + + lt=- + 10.0;10.0;40.0;10.0 + + + UMLClass + + 1674 + 2097 + 135 + 72 + + *Activity* +bg=pink + +-- +start : Start + + + + Relation + + 1800 + 2124 + 90 + 27 + + lt=<. + 80.0;10.0;10.0;10.0 + + + UMLClass + + 1917 + 2187 + 99 + 54 + + +*End* +bg=pink + + + + Relation + + 2007 + 2205 + 54 + 27 + + lt=- + 10.0;10.0;40.0;10.0 + + + UMLClass + + 2070 + 2097 + 198 + 72 + + *XORGate* +bg=pink + +-- +next : orderedset(Condition) +default : Node + + + + UMLClass + + 2070 + 2259 + 198 + 72 + + *XOREventGate* +bg=pink + +-- +next : dict(Event, Node) + + + + UMLClass + + 1863 + 2259 + 153 + 81 + + *Ability* +bg=pink + +-- +next : Node +ability : Ability + + + + Relation + + 2034 + 2124 + 54 + 27 + + lt=- + 40.0;10.0;10.0;10.0 + + + Relation + + 2034 + 2286 + 54 + 27 + + lt=- + 10.0;10.0;40.0;10.0 + + + Relation + + 2007 + 2286 + 54 + 27 lt=- 40.0;10.0;10.0;10.0 + + UMLClass + + 3348 + 3033 + 162 + 81 + + *Activity* +bg=green + +-- +graph : Activity + + + + Relation + + 3501 + 3060 + 54 + 27 + + lt=- + 10.0;10.0;40.0;10.0 + + + UMLNote + + 1620 + 2052 + 144 + 27 + + Unit behaviour graph +bg=blue + + + + UMLClass + + 2349 + 2268 + 99 + 54 + + +*Event* +bg=pink + + + + Relation + + 2259 + 2286 + 108 + 27 + + lt=<. + 100.0;10.0;10.0;10.0 + + + Relation + + 2367 + 2313 + 27 + 216 + + lt=<<- + 10.0;10.0;10.0;220.0 + + + UMLClass + + 2394 + 2340 + 144 + 72 + + *Wait* +bg=pink + +-- +time : float + + + + UMLClass + + 2394 + 2421 + 108 + 54 + + +*WaitAbility* +bg=pink + + + + Relation + + 2367 + 2367 + 45 + 27 + + lt=- + 10.0;10.0;30.0;10.0 + + + Relation + + 2367 + 2439 + 45 + 27 + + lt=- + 10.0;10.0;30.0;10.0 + + + UMLClass + + 2394 + 2484 + 144 + 54 + + +*CommandInQueue* +bg=pink + + + + Relation + + 2367 + 2502 + 45 + 27 + + lt=- + 10.0;10.0;30.0;10.0 + + + UMLClass + + 2196 + 1665 + 126 + 72 + + *Condition* +bg=pink + +-- +next : Node + + + + Relation + + 2142 + 1692 + 72 + 423 + + lt=<. + 60.0;10.0;10.0;10.0;10.0;450.0 + + + UMLClass + + 2277 + 1755 + 144 + 54 + + +*CommandInQueue* +bg=pink + + + + Relation + + 2250 + 1728 + 27 + 306 + + lt=<<- + 10.0;10.0;10.0;320.0 + + + Relation + + 2250 + 1773 + 45 + 27 + + lt=- + 10.0;10.0;30.0;10.0 + + + UMLClass + + 2277 + 1818 + 144 + 72 + + *NextCommand* +bg=pink + +-- +command : Command + + + + Relation + + 2250 + 1836 + 45 + 27 + + lt=- + 10.0;10.0;30.0;10.0 + + + Relation + + 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 + + 3915 + 3591 + 108 + 54 + + +*PathType* +bg=pink + + + + Relation + + 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=- + 40.0;10.0;10.0;10.0 + + + UMLClass + + 2349 + 2187 + 126 + 54 + + +*SwitchCondition* +bg=pink + + + + Relation + + 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 6349ddc704..2bb20fa6f2 100644 --- a/doc/nyan/api_reference/reference_ability.md +++ b/doc/nyan/api_reference/reference_ability.md @@ -127,10 +127,30 @@ 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 -TransformTo(Ability): +ActiveTransformTo(Ability): target_state : StateChanger transform_time : float transform_progress : set(Progress) @@ -147,6 +167,18 @@ The time for the transformation to complete. **transform_progress** A set of `Progress` objects that can activate state changes and animation overrides while the transformation progresses. The objects in the set must have progress type `Restock`. +## ability.type.Activity + +```python +Activity(Ability): + graph: Activity +``` + +Defines the behaviour of a game entity. The behaviour is modelled as a directed node graph. Nodes in the graph correspond to actions that execute for the game entity or conditional queries and event triggers that indicate which path to take next. By traversing the node graph along its paths, the game entities actions are determined. See the [activity control flow](/doc/code/game_simulation/activity.md) documentation for more information. + +**graph** +Node graph that defines the behaviour of the game entity. + ## ability.type.ApplyContinuousEffect ```python @@ -247,6 +279,18 @@ Container the target game entity will be inserted into. A `Storage` ability with **storage_elements** Game entities that can be inserted into the container. The container must allow the `GameEntity` objects. +## ability.type.Collision + +```python +Collision(Ability): + hitbox : Hitbox +``` + +Adds collision behaviour to a game entity. + +**hitbox** +Defines the size (x, y, z) of the collision hitbox. + ## ability.type.Constructable ```python @@ -303,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. @@ -519,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) @@ -527,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. @@ -555,18 +591,6 @@ When other herdables are in this range around the herded game entity, they will **mode** Determines who gets ownership of the herdable game entity when multiple game entities using `Herd` are in range. -## ability.type.Hitbox - -```python -Hitbox(Ability): - hitbox : Hitbox -``` - -Adds a hitbox to a game entity that is used for collision with other game entities. - -**hitbox** -Defines the size (x, y, z) of the hitbox. - ## ability.type.Idle ```python @@ -616,8 +640,9 @@ Lock pools definitions. ```python Move(Ability): - speed : float - modes : set(MoveMode) + speed : float + modes : set(MoveMode) + path_type : children(PathType) ``` Allows a game entity to move around the map. @@ -626,7 +651,10 @@ Allows a game entity to move around the map. Speed of movement. **modes** -Type of movements that can be used. +Modes of movements that can be used. + +**path_type** +Path type determining which pathfinding grid is searched to find a path from the start to the goal location. ## ability.type.Named @@ -660,22 +688,6 @@ Temporarily replace the map terrain the game entity is positioned on with a spec **terrain_overlay** Terrain that is temporily replaces the existing map terrain. -## ability.type.Passable - -```python -Passable(Ability): - hitbox : Hitbox - mode : PassableMode -``` - -Deactivates a specified hitbox of the game entity for movement of other game entities. The hitbox is still relevant for the game entity's own movement. - -**hitbox** -Reference to the hitbox that should be deactivated. - -**mode** -Defines the game entities for which the hitbox is deactivated. - ## ability.type.PassiveTransformTo ```python @@ -700,6 +712,28 @@ State change activated after `transform_time` has passed. **transform_progress** Can alter the game entity while the transformation is in progress. The objects in the set must have progress type `Transform`. +## ability.type.Pathable + +```python +Pathable(Ability): + hitbox : Hitbox + path_costs : dict(children(PathType), int) +``` + +Lets a game entity influence the pathing costs on the (static) pathfinding grid. + +This ability should only be used for game entitie that never (or rarely) change positions as pathfinding grid recalculations are expensive. For dynamic pathfinding effects, using the `Collision` ability should be preferred. + +**hitbox** +Hitbox around the game entity that affects the underlying pathfinding grids. All grid cells that are covered by this hitbox ae influenced by the cost definitions in the `path_costs` attribute. + +**path_costs** +Costs of traversing the area defined by the `hitbox` attribute on the pathfinding grid. + +Keys are `PathType` objects that are associated with a pathfinding grid in the pathfinder. + +Values represent the pathing cost for the terrain on the pathfinding grid. Each value must be an integer between `1` and `255`. `1` defines the *minimum* possible cost and `254` represents the *maximum* possible cost. `255` signifies that the terrain is impassable for the specified path type. + ## ability.type.ProductionQueue ```python @@ -765,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 @@ -944,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 @@ -972,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_resistance.md b/doc/nyan/api_reference/reference_resistance.md index e38f838119..61a2aa06cb 100644 --- a/doc/nyan/api_reference/reference_resistance.md +++ b/doc/nyan/api_reference/reference_resistance.md @@ -317,7 +317,7 @@ Resistance to the `MakeHarvestable` effect. Resource spot that should be made harvestable. Effects of type `effect.discrete.make_harvestable.type.MakeHarvestable` are matched to this resistance if they store the same `ResourceSpot` object in their `resource_spot` member. Additionally, the target needs to have a `Harvestable` ability that contains the resource spot. **resist_condition** -Condition which must he fulfilled to make the resource spot harvestable. +Condition which must be fulfilled to make the resource spot harvestable. ## resistance.discrete.send_to_container.type.SendToContainer diff --git a/doc/nyan/api_reference/reference_util.md b/doc/nyan/api_reference/reference_util.md index 1dd19de1a8..7667a1a10a 100644 --- a/doc/nyan/api_reference/reference_util.md +++ b/doc/nyan/api_reference/reference_util.md @@ -30,6 +30,287 @@ Game entities types for which the accuracy value can be used. **blacklisted_entities** Blacklists game entities that have one of the types listed in `target_types`, but should not be covered by this `Accuracy` object. +## util.activity.Activity + +```python +Activity(Object): + start : Start +``` + +Stores a node graph for the behaviour of a game entity. Activities are assigned to game entities with the `Activity` ability. + +**start** +Starting node of the activity. + +## util.activity.condition.Condition + +```python +Condition(Object): + node : Node +``` + +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 +CommandInQueue(Condition): + pass +``` + +Is true when the game entity's command queue is not empty when the node is visited. + +## util.activity.condition.type.NextCommand + +```python +NextCommand(Condition): + command : children(Command) +``` + +Is true when the next command in the game entity's command queue is of a specific type. + +**command** +Command type checked by the condition. + +## util.activity.condition.type.TargetInRange + +```python +TargetInRange(Condition): + ability : abstract(Ability) +``` + +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 + +```python +Event(Object): + pass +``` + +Generalization object for events that can be used in `XOREventGate` nodes. + +## util.activity.event.type.CommandInQueue + +```python +CommandInQueue(Event): + pass +``` + +Fires after a new command has been added to the game entity's command queue. + +## util.activity.event.type.Wait + +```python +Wait(Event): + time : float +``` + +Fires after a certain amount of time has passed. + +**time** +Time in seconds to wait. + +If the value is zero or negative, the event fires immediately. + +## util.activity.event.type.WaitAbility + +```python +WaitAbility(Event): + pass +``` + +Fires at the exact time when a previously visited ability node has finished executing. + +In other words, the event fires when the ability is done with the associated task. For example, in case of the `Move` ability, the event fires when the game entity has reached its destination. + +## util.activity.node.Node + +```python +Node(Object): + pass +``` + +Generalization object for nodes in an activity graph. + +## util.activity.node.type.Ability + +```python +Ability(Node): + next : Node + ability : abstract(Ability) +``` + +Executes an ability of the game entity when the node is visited. + +**next** +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 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 + +```python +End(Node): + pass +``` + +End of an activity. Does nothing. + +## util.activity.node.type.Start + +```python +Start(Node): + next : Node +``` + +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 +XOREventGate(Node): + next : dict(Event, Node) +``` + +Gateway that branches the activity graph when a certain event occurs. Events are registered immediately when the node is visited and cancelled when the node is left. + +**next** +Mapping of events to the next node in the activity graph. The first event that occurs is used to determine the next node. + +## util.activity.node.type.XORGate + +```python +XORGate(Node): + next : orderedset(Condition) + default : 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 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 @@ -250,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 @@ -1321,43 +1638,6 @@ Patrol(MoveMode): Lets player set two or more waypoints that the game entity will follow. Stances from `GameEntityStance` ability are considered during movement. -## util.passable_mode.PassableMode - -```python -PassableMode(Object): - allowed_types : set(children(GameEntityType)) - blacklisted_entities : set(GameEntity) -``` - -Generalization object for all passable modes. Define passability options for the `Passable` ability. - -**allowed_types** -Lists the game entities types which can pass the hitbox. - -**blacklisted_entities** -Used to blacklist game entities that have one of the types listed in `allowed_types`, but should not be covered by this `PassableMode` object. - -## util.passable_mode.type.Gate - -```python -Gate(PassableMode): - stances : set(children(DiplomaticStance)) -``` - -Lets all compatible game entities from players with the specified stances pass through the hitbox. Game entities of players with other stances can also pass through while any unit is passing through. - -**stances** -Stances of players whose game entities are always allowed to pass though the hitbox. - -## util.passable_mode.type.Normal - -```python -Normal(PassableMode): - pass -``` - -Lets all compatible game entities pass through the hitbox. - ## util.patch.NyanPatch ```python @@ -1408,6 +1688,15 @@ The patch is applied to all players that have the specified diplomatic stances. **stances** Diplomatic stances of the players the patch should apply to. +## util.path_type.PathType + +```python +PathType(Object): + pass +``` + +Path type that is associated with an internal pathfinding grid at runtime. + ## util.payment_mode.PaymentMode ```python @@ -2243,6 +2532,7 @@ Terrain(Object): terrain_graphic : Terrain sound : Sound ambience : set(TerrainAmbient) + path_costs : dict(children(PathType), int) ``` Terrains define the properties of the ground which the game entities are placed on. @@ -2262,6 +2552,15 @@ Ambient sound played when the camera of the player is looking onto the terrain. **ambience** Ambient objects placed on the terrain. +**path_costs** +Base costs of traversing the pathfinding grid on map areas where the terrain is placed. + +Keys are `PathType` objects that are associated with a pathfinding grid in the pathfinder. + +Values represent the pathing cost for the terrain on the pathfinding grid. Each value must be an integer between `1` and `255`. `1` defines the *minimum* possible cost and `254` represents the *maximum* possible cost. `255` signifies that the terrain is impassable for the specified path type. + +For `PathType` objects that exist in the modpack but are not keys in this dict, a default cost value of `1` is assumed. + ## util.terrain.TerrainAmbient ```python diff --git a/doc/reverse_engineering/game_mechanics/formations.md b/doc/reverse_engineering/game_mechanics/formations.md index dd39371ccd..b93de426e1 100644 --- a/doc/reverse_engineering/game_mechanics/formations.md +++ b/doc/reverse_engineering/game_mechanics/formations.md @@ -123,7 +123,7 @@ The same rules apply, if more than two unit types are used. We will now have a l .............. ``` -Which unit type is sorted into the line first depends on the order in which the player selected the unit types (or how they are ordered inside the selection queue). In the first example of this section, the player selected the archers first and added the skirmishers to his selection. The second and above example would be the result of selecting longbowman first, archers second, skirmishers third and throwing axeman last. +Which unit type is sorted into the line first depends on the order in which the player selected the unit types (or how they are ordered inside the selection queue). In the first example of this section, the player selected the archers first and added the skirmishers to their selection. The second and above example would be the result of selecting longbowman first, archers second, skirmishers third and throwing axeman last. #### Distance Between Units diff --git a/doc/reverse_engineering/game_mechanics/monk_conversion.md b/doc/reverse_engineering/game_mechanics/monk_conversion.md index 583cb61ce1..cf101a942f 100644 --- a/doc/reverse_engineering/game_mechanics/monk_conversion.md +++ b/doc/reverse_engineering/game_mechanics/monk_conversion.md @@ -32,5 +32,5 @@ Units inside a building are not converted and will ungarrison. The same is true [Source](https://www.youtube.com/watch?v=_gjpDWfzaM0) * If a converted unit is "replaced" by the game, e.g. a villager changing to a farmer, the stats are not frozen anymore. -* Converted siege units are only frozen in tech level until the new owner researches a technology that changes the units stats, e.g. chemistry. As soon as the research finishes, the units are replaced with ones that are on the same tech level as the player. Weirdly enough, Onagers are replaced if the player researches Heavy Scorpions but not if he researches Siege/Capped Rams. +* Converted siege units are only frozen in tech level until the new owner researches a technology that changes the units stats, e.g. chemistry. As soon as the research finishes, the units are replaced with ones that are on the same tech level as the player. Weirdly enough, Onagers are replaced if the player researches Heavy Scorpions but not if they research Siege/Capped Rams. * The flaming projectile caused by chemistry research is tied to the tech level of the owner but the +1 damage is kept, if the unit is converted. diff --git a/doc/reverse_engineering/game_mechanics/selection.md b/doc/reverse_engineering/game_mechanics/selection.md index de08156fff..0016ed4ea4 100644 --- a/doc/reverse_engineering/game_mechanics/selection.md +++ b/doc/reverse_engineering/game_mechanics/selection.md @@ -8,7 +8,7 @@ This documents shows the methods of selecting and deselecting units. The player doesn't have to click directly on the sprite of a unit because there is a tolerance factor involved. The tolerance factor is roughly `1 unit width` horizontally and `1 unit height` vertically measured from the center of the unit. This means that clicking in the general area of a unit will be accepted as a valid selection most of the time. -If two units' areas of tolerance overlap, the unit that is in the front seems to be preferred, but clicking directly on the sprite of one of the player's own units will select the unit that is pointed at. When the area of tolerance overlaps with an enemy (or ally) unit, the unit of the player will always be preferred, even if he clicks directly on the sprite of the enemy unit. +If two units' areas of tolerance overlap, the unit that is in the front seems to be preferred, but clicking directly on the sprite of one of the player's own units will select the unit that is pointed at. When the area of tolerance overlaps with an enemy (or ally) unit, the unit of the player will always be preferred, even if they click directly on the sprite of the enemy unit. Relics and animals also have an area of tolerance regarding selection, while resource spots like trees, bushes, gold/stone mines as well as buildings don't. @@ -25,7 +25,7 @@ All of these have a specific purpose and will now be explained further. ### Selection Box -The selection box is the easiest way to select multiple units of any type in AoE2. When the player draws the box around the units he wants to select, the units inside the box will be added to the selection queue, **going from the top to the bottom of the box** until the limit of 40 units is reached. +The selection box is the easiest way to select multiple units of any type in AoE2. When the player draws the box around the units they want to select, the units inside the box will be added to the selection queue, **going from the top to the bottom of the box** until the limit of 40 units is reached. The tolerance factor is also used here, which can result in units which are slightly outside of the selection box to be selected. diff --git a/doc/reverse_engineering/game_mechanics/switching_villager_tasks.md b/doc/reverse_engineering/game_mechanics/switching_villager_tasks.md index 8e1db4838f..b82c84ae69 100644 --- a/doc/reverse_engineering/game_mechanics/switching_villager_tasks.md +++ b/doc/reverse_engineering/game_mechanics/switching_villager_tasks.md @@ -22,4 +22,4 @@ Note: This only happens if the builder was actively working on the construction ## Weird AoE2 Quirks -* If the cheat `aegis` (villagers gather instantly from a resource) is used in a game and the role of a villager changes from "hunter" to "shepard", he will dump all of the resources he is carrying "into" the sheep. +* If the cheat `aegis` (villagers gather instantly from a resource) is used in a game and the role of a villager changes from "hunter" to "shepard", they will dump all of the resources they are carrying "into" the sheep. diff --git a/doc/reverse_engineering/game_mechanics/town_bell.md b/doc/reverse_engineering/game_mechanics/town_bell.md index c3ee341498..c8748b061c 100644 --- a/doc/reverse_engineering/game_mechanics/town_bell.md +++ b/doc/reverse_engineering/game_mechanics/town_bell.md @@ -48,4 +48,4 @@ As soon as the town bell is triggered, the algorithm searches for villagers that In the next step, the algorithm tries to find the nearest garrison for a villager by calculating the distances between the villager and all buildings on the second list. The closest distance calculated and the corresponding garrison are then saved. Afterwards the villager is put into a third list, which is sorted by the *closest distance to any garrison*. This step is repeated for every villager. -Last but not least, the villagers are assigned to a garrison. Since the third list is sorted by closest distance, villagers that are the nearest to a building are guaranteed to get a place, which satisfies the condition that towers prefer the villagers close to them. If the villager is assigned to a building which is already full, the algorithm recalculates the distances to other garrisons and sorts him back into the list. +Last but not least, the villagers are assigned to a garrison. Since the third list is sorted by closest distance, villagers that are the nearest to a building are guaranteed to get a place, which satisfies the condition that towers prefer the villagers close to them. If the villager is assigned to a building which is already full, the algorithm recalculates the distances to other garrisons and sorts them back into the list. diff --git a/doc/reverse_engineering/game_mechanics/wolves.md b/doc/reverse_engineering/game_mechanics/wolves.md index 667a219956..9c467a1cd4 100644 --- a/doc/reverse_engineering/game_mechanics/wolves.md +++ b/doc/reverse_engineering/game_mechanics/wolves.md @@ -14,7 +14,7 @@ Line of sight depends on the selected difficulty of the game. Hard 12 tiles Hardest 12 tiles -As soon as a unit moves into the LOS of a wolf, he will chase and attack the unit. However some unit types are ignored, including: +As soon as a unit moves into the LOS of a wolf, they will chase and attack the unit. However some unit types are ignored, including: * King * Trade Cart diff --git a/doc/reverse_engineering/networking/02-header.md b/doc/reverse_engineering/networking/02-header.md index 4e7e57e67b..a361f5a4e4 100644 --- a/doc/reverse_engineering/networking/02-header.md +++ b/doc/reverse_engineering/networking/02-header.md @@ -20,7 +20,7 @@ end ## Description *:network_source_id*
-The *:network_id* of the person who sent the packet. A *:network_id* is different for every game, but is not generated randomly for all players. When joining the lobby, every player gets assigned `last_network_id - 2` as his own *:network_id* where *last_network_id* is the ID of the person who joined before him. +The *:network_id* of the person who sent the packet. A *:network_id* is different for every game, but is not generated randomly for all players. When joining the lobby, every player gets assigned `last_network_id - 2` as their own *:network_id* where *last_network_id* is the ID of the person who joined before them. *:network_dest_id*
The *:network_id* of the person who should receive the packet. Is only used for sync packets and remains unused for most commands. diff --git a/doc/reverse_engineering/networking/06-chat_message_spoofing.md b/doc/reverse_engineering/networking/06-chat_message_spoofing.md index 4c4d15092c..75d0516b08 100644 --- a/doc/reverse_engineering/networking/06-chat_message_spoofing.md +++ b/doc/reverse_engineering/networking/06-chat_message_spoofing.md @@ -23,7 +23,7 @@ To construct a message the following parameters are needed: * Player number of the spoofed sender * Player number of the receiver(s) -An attacker that is in game with other players will have no problems getting to know the player numbers. As the game is constantly synced, he can easily get the value for *:communication_turn*. +An attacker that is in game with other players will have no problems getting to know the player numbers. As the game is constantly synced, they can easily get the value for *:communication_turn*. Deriving the valid Sender ID of the receiving player is more difficult and depends on the attacker's ability to capture network traffic. The easiest way to discover all Player IDs is by capturing a few packets of normal gameplay beforehand. The IDs of Players 1-8 have a fixed byte position in the data stream. diff --git a/doc/reverse_engineering/networking/08-movement.md b/doc/reverse_engineering/networking/08-movement.md index 4909d82413..6c52684e60 100644 --- a/doc/reverse_engineering/networking/08-movement.md +++ b/doc/reverse_engineering/networking/08-movement.md @@ -74,7 +74,7 @@ end Always has the value `0x03`. *:player_id*
-The ID of the player who moves his units (`0x01` - `0x08`). +The ID of the player who moves their units (`0x01` - `0x08`). *:zero*
The two bytes following the *:player_id* are unused. diff --git a/doc/troubleshooting.md b/doc/troubleshooting.md index 5ed9cc440a..a955eca018 100644 --- a/doc/troubleshooting.md +++ b/doc/troubleshooting.md @@ -26,18 +26,3 @@ A workaround would be to make a backup of your AGE2 directory and let the conver backup at subfolder `AGE2/resources` delete all files ***except*** folders. Another workaround would be to backup your AGE2 folder and redownload it to have a clean install. After conversion you can replace it with the backup. - -## Installation - -### Cannot specify compile definitions for target "SDL2::SDL2" which is not built by this project - -This error is specific to a few operating systems. The main cause is that your SDL2 version is too old and does not -include the necessary CMake files defining the target. There is an indepth discussion about this -[here](https://discourse.libsdl.org/t/how-is-sdl2-supposed-to-be-used-with-cmake/31275/16). -As a solution, you should update your SDL packages to SDL >=2.0.12 or **compile the latest SDL2 and SDL2-image from -source**. The latest version includes the necessary CMake files to expose the `SDL2::SDL2` target. - -## Building on Debian 12 -On Debian you might get an error saying that it couldn't find SDL2 library. This happens because the CMAKE prefix and SDL2 path are not set correctly. -The solution is to append at the end of the `./configure` command the cmake variables for both the prefix and SDL2 path, like so: -`./configure -- -DCMAKE_PREFIX_PATH=/usr -DSDL2_DIR=/usr/include/SDL2` (you can use `find` to look for the correct paths) diff --git a/etc/gdb_pretty/__init__.py b/etc/gdb_pretty/__init__.py new file mode 100644 index 0000000000..0e697d0676 --- /dev/null +++ b/etc/gdb_pretty/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2024-2024 the openage authors. See copying.md for legal info. + +""" +GDB pretty printers for openage. +""" diff --git a/etc/gdb_pretty/printers.py b/etc/gdb_pretty/printers.py new file mode 100644 index 0000000000..9ae29539ce --- /dev/null +++ b/etc/gdb_pretty/printers.py @@ -0,0 +1,421 @@ +# Copyright 2024-2025 the openage authors. See copying.md for legal info. + +""" +Pretty printers for GDB. +""" + +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. + + +class PrinterControl(gdb.printing.PrettyPrinter): + """ + Exposes a pretty printer for a specific type. + + Printer are searched in the following order: + 1. Exact type name _with_ typedefs + 2. Regex of type name _without_ typedefs + """ + + def __init__(self, name: str): + super().__init__(name) + + self.name_printers = {} + self.regex_printers = {} + + def add_printer(self, type_name: str, printer): + """ + Adds a printer for a specific type name. + """ + self.name_printers[type_name] = printer + + def add_printer_regex(self, regex: str, printer): + """ + Adds a printer for a specific type name. + + :param regex: The regex to match the type name. + :type regex: str + """ + self.regex_printers[re.compile(regex)] = printer + + def __call__(self, val: gdb.Value): + # Check the exact type name with typedefa + type_name = val.type.name + if type_name in self.name_printers: + return self.name_printers[val.type.name](val) + + # Check the type name without typedefs and regex + type_name = val.type.unqualified().strip_typedefs().tag + if type_name is None: + return None + + for regex, printer in self.regex_printers.items(): + if regex.match(type_name): + return printer(val) + + return None + + +pp = PrinterControl('openage') +gdb.printing.register_pretty_printer(None, pp) + + +def printer_typedef(type_name: str): + """ + Decorator for pretty printers. + + :param type_name: The name of the type to register the printer for. + :type type_name: str + """ + def _register_printer(printer): + """ + Registers the printer with GDB. + """ + pp.add_printer(type_name, printer) + + return _register_printer + + +def printer_regex(regex: str): + """ + Decorator for pretty printers. + + :param regex: The regex to match the type name. + :type regex: str + """ + def _register_printer(printer): + """ + Registers the printer with GDB. + """ + pp.add_printer_regex(regex, printer) + + return _register_printer + + +def format_fixed_point(value: int, fractional_bits: int) -> float: + """ + Formats a fixed point value to a double. + + :param value: The fixed point value. + :type value: int + :param fractional_bits: The number of fractional bits. + :type fractional_bits: int + """ + to_double_factor = 1 / pow(2, fractional_bits) + return float(value) * to_double_factor + + +@printer_regex('^openage::coord::(camhud|chunk|input|phys|scene|term|tile|viewport)(2|3)?(_delta)?') +class CoordPrinter: + """ + Pretty printer for openage::coord types (CoordNeSe, CoordNeSeUp, CoordXY, CoordXYZ). + """ + + def __init__(self, val: gdb.Value): + self.__val = val + + # Each coord type has one parent which is either + # of CoordNeSe, CoordNeSeUp, CoordXY, CoordXYZ + # From this parent we can get the fields + self._parent_type = self.__val.type.fields()[0].type + + def to_string(self): + """ + Get the coord as a string. + """ + field_vals = [] + for child in self._parent_type.fields(): + # Include the fixed point coordinates in the summary + val = self.__val[child.name] + num = format_fixed_point( + int(val['raw_value']), + int(val.type.template_argument(1)) + ) + field_vals.append(f"{num:.5f}") + + # Example: phys3[1.00000, 2.00000, 3.00000] + return f"{self.__val.type.tag.split('::')[-1]}[{', '.join(field_vals)}]" + + def children(self): + """ + Get the displayed children of the coord. + """ + for child in self._parent_type.fields(): + yield (child.name, self.__val[child.name]) + + +@printer_typedef('openage::time::time_t') +class TimePrinter: + """ + Pretty printer for openage::time::time_t. + """ + + def __init__(self, val: gdb.Value): + self.__val = val + + def to_string(self): + """ + Get the time as a string. + + Format: SS.sss (e.g. 12.345s) + """ + seconds = format_fixed_point( + int(self.__val['raw_value']), + int(self.__val.type.template_argument(1)) + ) + + # show as seconds with millisecond precision + return f'{seconds:.3f}s' + + def children(self): + """ + Get the displayed children of the time value. + """ + yield ('raw_value', self.__val['raw_value']) + + +@printer_regex('^openage::util::FixedPoint<.*>') +class FixedPointPrinter: + """ + Pretty printer for openage::util::FixedPoint. + """ + + def __init__(self, val: gdb.Value): + self.__val = val + + def to_string(self): + """ + Get the fixed point value as a string. + + Format: 0.12345 + """ + num = format_fixed_point( + int(self.__val['raw_value']), + int(self.__val.type.template_argument(1)) + ) + return f'{num:.5f}' + + def children(self): + """ + Get the displayed children of the fixed point value. + """ + yield ('raw_value', self.__val['raw_value']) + + # calculate the precision of the fixed point value + # 16 * log10(2) = 16 * 0.30103 = 4.81648 + # do this manualy because it's usually optimized out by the compiler + fractional_bits = int(self.__val.type.template_argument(1)) + + precision = int(fractional_bits * 0.30103 + 1) + yield ('approx_precision', precision) + + +@printer_regex('^openage::util::Vector<.*>') +class VectorPrinter: + """ + Pretty printer for openage::util::Vector. + """ + + def __init__(self, val: gdb.Value): + self.__val = val + + def to_string(self): + """ + Get the vector as a string. + """ + size = self.__val.type.template_argument(0) + int_type = self.__val.type.template_argument(1) + return f'openage::util::Vector<{size}, {int_type}>' + + def children(self): + """ + Get the displayed children of the vector. + """ + size = self.__val.type.template_argument(0) + for i in range(size): + yield (str(i), self.__val['_M_elems'][i]) + + def child(self, index): + """ + Get the child at the given index. + """ + return self.__val['_M_elems'][index] + + def num_children(self): + """ + Get the number of children of the vector. + """ + return self.__val.type.template_argument(0) + + @staticmethod + def display_hint(): + """ + Get the display hint for the vector. + """ + return 'array' + + +@printer_regex('^openage::curve::Keyframe<.*>') +class KeyframePrinter: + """ + Pretty printer for openage::curve::Keyframe. + """ + + def __init__(self, val: gdb.Value): + self.__val = val + + def to_string(self): + """ + Get the keyframe as a string. + """ + return f'openage::curve::Keyframe<{self.__val.type.template_argument(0)}>' + + def children(self): + """ + Get the displayed children of the keyframe. + """ + yield ('time', self.__val['timestamp']) + yield ('value', self.__val['value']) + + +@printer_typedef('openage::path::flow_t') +class PathFlowTypePrinter: + """ + Pretty printer for openage::path::flow_t. + + TODO: Inherit from gdb.ValuePrinter when gdb 14.1 is available in all distros. + """ + + FLOW_FLAGS: dict = { + 0x10: 'PATHABLE', + 0x20: 'LOS', + 0x40: 'TARGET', + 0x80: 'UNUSED', + } + + FLOW_DIRECTION: dict = { + 0x00: 'NORTH', + 0x01: 'NORTHEAST', + 0x02: 'EAST', + 0x03: 'SOUTHEAST', + 0x04: 'SOUTH', + 0x05: 'SOUTHWEST', + 0x06: 'WEST', + 0x07: 'NORTHWEST', + } + + def __init__(self, val: gdb.Value): + self.__val = val + + def to_string(self): + """ + Get the flow type as a string. + """ + flow = int(self.__val) + flags = flow & 0xF0 + direction = flow & 0x0F + return (f"{self.FLOW_DIRECTION.get(direction, 'INVALID')} (" + f"{', '.join([flag for mask, flag in self.FLOW_FLAGS.items() if mask & flags])})") + + def children(self): + """ + Get the displayed children of the flow type. + """ + flow = int(self.__val) + flags = flow & 0xF0 + direction = flow & 0x0F + yield ('direction', self.FLOW_DIRECTION[direction]) + for mask, flag in self.FLOW_FLAGS.items(): + yield (flag, bool(flags & mask)) + + +# Integrated flags +INTEGRATED_FLAGS: dict = { + 0x01: 'UNUSED', + 0x02: 'FOUND', + 0x04: 'WAVEFRONT_BLOCKED', + 0x08: 'UNUSED', + 0x10: 'UNUSED', + 0x20: 'LOS', + 0x40: 'TARGET', + 0x80: 'UNUSED', +} + + +def get_integrated_flags_list(value: int) -> str: + """ + Get the list of flags as a string. + + :param value: The value to get the flags for. + :type value: int + """ + flags = [] + for mask, flag in INTEGRATED_FLAGS.items(): + if value & mask: + flags.append(flag) + + return ' | '.join(flags) + + +@printer_typedef('openage::path::integrated_flags_t') +class PathIntegratedFlagsTypePrinter: + """ + Pretty printer for openage::path::integrated_flags_t. + + TODO: Inherit from gdb.ValuePrinter when gdb 14.1 is available in all distros. + """ + + def __init__(self, val: gdb.Value): + self.__val = val + + def to_string(self): + """ + Get the integrate type as a string. + """ + integrate = int(self.__val) + return get_integrated_flags_list(integrate) + + def children(self): + """ + Get the displayed children of the integrate type. + """ + integrate = int(self.__val) + for mask, flag in INTEGRATED_FLAGS.items(): + yield (flag, bool(integrate & mask)) + + +@printer_typedef('openage::path::integrated_t') +class PathIntegratedTypePrinter: + """ + Pretty printer for openage::path::integrated_t. + + TODO: Inherit from gdb.ValuePrinter when gdb 14.1 is available in all distros. + """ + + def __init__(self, val: gdb.Value): + self.__val = val + + def to_string(self): + """ + Get the integrate type as a string. + """ + output_str = f'cost = {self.__val["cost"]}' + flags = get_integrated_flags_list(int(self.__val['flags'])) + if len(flags) > 0: + output_str += f' ({flags})' + return output_str + + def children(self): + """ + Get the displayed children of the integrate type. + """ + yield ('cost', self.__val['cost']) + yield ('flags', self.__val['flags']) + + +# TODO: curve types +# TODO: input event codes +# TODO: eigen types https://github.com/dmillard/eigengdb diff --git a/etc/openage.gdbinit b/etc/openage.gdbinit new file mode 100644 index 0000000000..c0e8b86ce4 --- /dev/null +++ b/etc/openage.gdbinit @@ -0,0 +1,10 @@ +python +import sys, os + +print("Loading openage.gdbinit") +print(f"Adding custom pretty-printers directory to the GDB path: {os.getcwd() + '../../etc'}") + +sys.path.insert(0, "../../etc") + +import gdb_pretty.printers +end diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000000..dae268d7dc --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1711460390, + "narHash": "sha256-akSgjDZL6pVHEfSE6sz1DNSXuYX6hq+P/1Z5IoYWs7E=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "44733514b72e732bd49f5511bd0203dea9b9a434", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-23.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000000..2e28a3b7dd --- /dev/null +++ b/flake.nix @@ -0,0 +1,39 @@ +{ + # This is a nix flake that contains a declarative definition of the openage + # and nyan packages, providing convenient and reproducible builds and + # development shells. + + description = "Free (as in freedom) open source clone of the Age of Empires II engine"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils, ... }: + flake-utils.lib.eachDefaultSystem (system: + let pkgs = import nixpkgs { inherit system; }; in + { + # This output is to build the derivation with `nix build` as well as to + # get development shells using `nix develop`. + # These are the packages provided by this flake: nyan and openage. + packages = rec { + # `nix build .#nyan` to build this + nyan = pkgs.callPackage ./nix/nyan.nix { }; + # `nix build .#openage` to build this + openage = pkgs.callPackage ./nix/openage.nix { + # Nyan is not provided by nixpkgs, but it comes from this flake + inherit (self.packages.${system}) nyan; + }; + # If no path is specified, openage is the default + default = openage; + }; + + # This output is to run the application directly with `nix run` + # (or `nix run .#openage` if you want to be explicit) + apps = rec { + openage = flake-utils.lib.mkApp { drv = self.packages.${system}.openage; }; + default = openage; + }; + }); +} 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 f1772bfc36..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" ) ################################################## @@ -53,9 +53,6 @@ find_library(FONTCONFIG_LIB fontconfig) find_package(toml11 REQUIRED) find_package(Freetype REQUIRED) find_package(PNG REQUIRED) -find_package(SDL2 CONFIG REQUIRED) -target_compile_definitions(SDL2::SDL2 INTERFACE SDL_MAIN_HANDLED) -find_package(SDL2Image REQUIRED) find_package(Opusfile REQUIRED) find_package(Epoxy REQUIRED) find_package(HarfBuzz 1.0.0 REQUIRED) @@ -65,8 +62,7 @@ set(CMAKE_THREAD_PREFER_PTHREAD TRUE) find_package(Threads REQUIRED) set(QT_VERSION_REQ "6.2") -find_package(Qt6Core ${QT_VERSION_REQ} REQUIRED) -find_package(Qt6Quick ${QT_VERSION_REQ} REQUIRED) +find_package(Qt6 ${QT_VERSION_REQ} REQUIRED COMPONENTS Core Quick Multimedia) if(WANT_BACKTRACE) find_package(GCCBacktrace) @@ -268,7 +264,6 @@ target_include_directories(libopenage ${EPOXY_INCLUDE_DIRS} ${OPUS_INCLUDE_DIRS} ${PNG_INCLUDE_DIRS} - ${SDL2IMAGE_INCLUDE_DIRS} ${HarfBuzz_INCLUDE_DIRS} ${QTPLATFORM_INCLUDE_DIRS} ) @@ -292,14 +287,13 @@ target_link_libraries(libopenage ${FREETYPE_LIBRARIES} ${EPOXY_LIBRARIES} ${MATH_LIB} - ${SDL2IMAGE_LIBRARIES} - SDL2::SDL2 ${UTIL_LIB} ${HarfBuzz_LIBRARIES} ${RT_LIB} ${EXECINFO_LIB} Qt6::Core Qt6::Quick + Qt6::Multimedia ) ################################################## @@ -322,19 +316,14 @@ get_codegen_scu_file() # are specified above the source file list. add_sources(libopenage - handlers.cpp - legacy_engine.cpp main.cpp options.cpp - screenshot.cpp - texture.cpp ${CMAKE_CURRENT_BINARY_DIR}/config.cpp ${CMAKE_CURRENT_BINARY_DIR}/version.cpp ${CODEGEN_SCU_FILE} ) pxdgen( - legacy_engine.h main.h ) @@ -349,9 +338,7 @@ add_subdirectory("datastructure") add_subdirectory("engine") add_subdirectory("error") add_subdirectory("event") -add_subdirectory("gamedata") add_subdirectory("gamestate") -add_subdirectory("gui") add_subdirectory("input") add_subdirectory("job") add_subdirectory("log") @@ -361,10 +348,7 @@ add_subdirectory("presenter") add_subdirectory("pyinterface") add_subdirectory("renderer") add_subdirectory("rng") -add_subdirectory("shader") -add_subdirectory("terrain") add_subdirectory("testing") add_subdirectory("time") -add_subdirectory("unit") add_subdirectory("util") add_subdirectory("versions") diff --git a/libopenage/assets/CMakeLists.txt b/libopenage/assets/CMakeLists.txt index 681d9882b9..234ac3b1b0 100644 --- a/libopenage/assets/CMakeLists.txt +++ b/libopenage/assets/CMakeLists.txt @@ -1,6 +1,4 @@ add_sources(libopenage - assetmanager.cpp - legacy_assetmanager.cpp mod_manager.cpp modpack.cpp ) diff --git a/libopenage/assets/assetmanager.cpp b/libopenage/assets/assetmanager.cpp deleted file mode 100644 index a62f0c2d34..0000000000 --- a/libopenage/assets/assetmanager.cpp +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright 2014-2023 the openage authors. See copying.md for legal info. - -/** - * TODO: Deprecated in favor of presenter/assets/asset_manager.h - * - */ - -#include "assetmanager.h" - -#if WITH_INOTIFY -#include /* for NAME_MAX */ -#include -#include -#endif - -#include "error/error.h" -#include "log/log.h" -#include "util/compiler.h" -#include "util/file.h" - -#include "texture.h" - -namespace openage { - -AssetManager::AssetManager(qtsdl::GuiItemLink *gui_link) : - missing_tex{nullptr}, - gui_link{gui_link} { -#if WITH_INOTIFY - // initialize the inotify instance - this->inotify_fd = inotify_init1(IN_NONBLOCK); - if (this->inotify_fd < 0) { - throw Error{MSG(err) << "Failed to initialize inotify!"}; - } -#endif -} - - -const util::Path &AssetManager::get_asset_dir() { - return this->asset_path; -} - - -void AssetManager::set_asset_dir(const util::Path &new_path) { - if (this->asset_path != new_path) { - this->asset_path = new_path; - this->clear(); - } -} - - -void AssetManager::set_display(presenter::LegacyDisplay *display) { - this->display = display; -} - - -presenter::LegacyDisplay *AssetManager::get_display() const { - return this->display; -} - -void AssetManager::set_engine(gamestate::GameSimulation *engine) { - this->engine = engine; -} - -gamestate::GameSimulation *AssetManager::get_engine() const { - return this->engine; -} - - -std::shared_ptr AssetManager::load_texture(const std::string &name, - bool use_metafile, - bool null_if_missing) { - // the texture to be associated with the given filename - std::shared_ptr tex; - - util::Path tex_path = this->asset_path[name]; - - // try to open the texture filename. - if (not tex_path.is_file()) { - // TODO: add/fetch inotify watch on the containing folder - // to display the tex as soon at it exists. - - if (null_if_missing) { - return nullptr; - } - else { - // return the big X texture instead - tex = this->get_missing_tex(); - } - } - else { - // create the texture! - tex = std::make_shared(tex_path, use_metafile); - -#if WITH_INOTIFY - std::string native_path = tex_path.resolve_native_path(); - - if (native_path.size() > 0) { - // create inotify update trigger for the requested file - - // TODO: let util::Path do the file watching - int wd = inotify_add_watch( - this->inotify_fd, - native_path.c_str(), - IN_CLOSE_WRITE); - - if (wd < 0) { - log::log(WARN << "Failed to add inotify watch for " << native_path); - } - else { - this->watch_fds[wd] = tex; - } - } -#endif - } - - // pass back the shared_ptr - return tex; -} - - -Texture *AssetManager::get_texture(const std::string &name, bool use_metafile, bool null_if_missing) { - // check whether the requested texture was loaded already - auto tex_it = this->textures.find(name); - - // the texture was not loaded yet: - if (tex_it == this->textures.end()) { - auto tex = this->load_texture(name, use_metafile, null_if_missing); - - if (tex.get() != nullptr) { - // insert the texture into the map - this->textures.insert(std::make_pair(name, tex)); - } - - // and return the texture pointer. - return tex.get(); - } - - return tex_it->second.get(); -} - - -void AssetManager::check_updates() { -#if WITH_INOTIFY - // buffer for at least 4 inotify events - char buf[4 * (sizeof(struct inotify_event) + NAME_MAX + 1)]; - ssize_t len; - - while (true) { - // fetch all events, the kernel won't write "half" structs. - len = read(this->inotify_fd, buf, sizeof(buf)); - - if (len == -1) { - if (errno == EAGAIN) { - // no events, nothing to do. - break; - } - else { - // something went wrong - log::log(WARN << "Failed to read inotify events!"); - break; - } - } - - // process fetched events, - // the kernel guarantees complete events in the buffer. - char *ptr = buf; - while (ptr < buf + len) { - auto *event = reinterpret_cast(ptr); - - if (event->mask & IN_CLOSE_WRITE) { - // TODO: this should invoke callback functions - this->watch_fds[event->wd]->reload(); - } - - // move the buffer ptr to the next event. - ptr += sizeof(struct inotify_event) + event->len; - } - } -#endif -} - -std::shared_ptr AssetManager::get_missing_tex() { - // if not loaded, fetch the "missing" texture (big red X). - if (this->missing_tex.get() == nullptr) [[unlikely]] { - this->missing_tex = std::make_shared( - this->asset_path["test"]["textures"]["missing.png"], - false); - } - - return this->missing_tex; -} - -void AssetManager::clear() { -#if WITH_INOTIFY - for (auto &watch_fd : this->watch_fds) { - int result = inotify_rm_watch(this->inotify_fd, watch_fd.first); - if (result < 0) { - log::log(WARN << "Failed to remove inotify watches"); - } - } - this->watch_fds.clear(); -#endif - - this->textures.clear(); -} - -} // namespace openage diff --git a/libopenage/assets/assetmanager.h b/libopenage/assets/assetmanager.h deleted file mode 100644 index 482f2ac672..0000000000 --- a/libopenage/assets/assetmanager.h +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright 2014-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include "config.h" - -#include -#include -#include - -#include "presenter/legacy/legacy.h" -#include "util/path.h" - -namespace qtsdl { -class GuiItemLink; -} // namespace qtsdl - -namespace openage { -class Texture; - -namespace gamestate { -class GameSimulation; -} - -/** - * Container class for all available assets. - * Responsible for loading, providing and updating requested files. - */ -class AssetManager final { -public: - explicit AssetManager(qtsdl::GuiItemLink *gui_link); - - /** - * Return the path where assets are found in. - */ - const util::Path &get_asset_dir(); - - /** - * Set the asset search path. - */ - void set_asset_dir(const util::Path &asset_dir); - - /** - * Set the game display of this asset manager. - * Called from QML. - */ - void set_display(presenter::LegacyDisplay *display); - - /** - * Return the display responsible for this asset manager. - */ - presenter::LegacyDisplay *get_display() const; - - /** - * Set the game engine of this asset manager. - * Called from QML. - */ - void set_engine(gamestate::GameSimulation *engine); - - /** - * Return the engine responsible for this asset manager. - */ - gamestate::GameSimulation *get_engine() const; - - /** - * Query the Texture for a given filename. - * - * @param name: the asset file name relative to the asset root. - * @param use_metafile: load subtexture information from meta file - * @param null_if_missing: instead of providing the "missing texture", - * return nullptr. - * @returns the queried texture handle. - */ - Texture *get_texture(const std::string &name, bool use_metafile = true, bool null_if_missing = false); - - /** - * Ask the kernel whether there were updates to watched files. - */ - void check_updates(); - -protected: - /** - * Create an internal texture handle. - */ - std::shared_ptr load_texture(const std::string &name, - bool use_metafile = true, - bool null_if_missing = false); - - /** - * Retrieves the texture for missing textures. - */ - std::shared_ptr get_missing_tex(); - -private: - void clear(); - - /** - * The display this asset manager is attached to. - */ - presenter::LegacyDisplay *display; - - /** - * The engine this asset manager is attached to. - */ - gamestate::GameSimulation *engine; - - /** - * The root directory for the available assets. - */ - util::Path asset_path; - - /** - * The replacement texture for missing textures. - */ - std::shared_ptr missing_tex; - - /** - * Map from texture filename to texture instance ptr. - */ - std::unordered_map> textures; - -#if WITH_INOTIFY - /** - * The file descriptor pointing to the inotify instance. - */ - int inotify_fd; - - /** - * Map from inotify watch handle fd to texture instance ptr. - * The kernel returns the handle fd when events are triggered. - */ - std::unordered_map> watch_fds; -#endif - -public: - qtsdl::GuiItemLink *gui_link; -}; - -} // namespace openage diff --git a/libopenage/assets/legacy_assetmanager.cpp b/libopenage/assets/legacy_assetmanager.cpp deleted file mode 100644 index 1058aee26e..0000000000 --- a/libopenage/assets/legacy_assetmanager.cpp +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright 2014-2023 the openage authors. See copying.md for legal info. - -/** - * TODO: Deprecated in favor of presenter/assets/asset_manager.h - * - */ - -#include "legacy_assetmanager.h" - -#if WITH_INOTIFY -#include /* for NAME_MAX */ -#include -#include -#endif - -#include "error/error.h" -#include "log/log.h" -#include "util/compiler.h" -#include "util/file.h" - -#include "texture.h" - -namespace openage { - -LegacyAssetManager::LegacyAssetManager(qtsdl::GuiItemLink *gui_link) : - missing_tex{nullptr}, - gui_link{gui_link} { -#if WITH_INOTIFY - // initialize the inotify instance - this->inotify_fd = inotify_init1(IN_NONBLOCK); - if (this->inotify_fd < 0) { - throw Error{MSG(err) << "Failed to initialize inotify!"}; - } -#endif -} - - -const util::Path &LegacyAssetManager::get_asset_dir() { - return this->asset_path; -} - - -void LegacyAssetManager::set_asset_dir(const util::Path &new_path) { - if (this->asset_path != new_path) { - this->asset_path = new_path; - this->clear(); - } -} - - -void LegacyAssetManager::set_display(presenter::LegacyDisplay *display) { - this->display = display; -} - - -presenter::LegacyDisplay *LegacyAssetManager::get_display() const { - return this->display; -} - -void LegacyAssetManager::set_engine(LegacyEngine *engine) { - this->engine = engine; -} - -LegacyEngine *LegacyAssetManager::get_engine() const { - return this->engine; -} - - -std::shared_ptr LegacyAssetManager::load_texture(const std::string &name, - bool use_metafile, - bool null_if_missing) { - // the texture to be associated with the given filename - std::shared_ptr tex; - - util::Path tex_path = this->asset_path[name]; - - // try to open the texture filename. - if (not tex_path.is_file()) { - // TODO: add/fetch inotify watch on the containing folder - // to display the tex as soon at it exists. - - if (null_if_missing) { - return nullptr; - } - else { - // return the big X texture instead - tex = this->get_missing_tex(); - } - } - else { - // create the texture! - tex = std::make_shared(tex_path, use_metafile); - -#if WITH_INOTIFY - std::string native_path = tex_path.resolve_native_path(); - - if (native_path.size() > 0) { - // create inotify update trigger for the requested file - - // TODO: let util::Path do the file watching - int wd = inotify_add_watch( - this->inotify_fd, - native_path.c_str(), - IN_CLOSE_WRITE); - - if (wd < 0) { - log::log(WARN << "Failed to add inotify watch for " << native_path); - } - else { - this->watch_fds[wd] = tex; - } - } -#endif - } - - // pass back the shared_ptr - return tex; -} - - -Texture *LegacyAssetManager::get_texture(const std::string &name, bool use_metafile, bool null_if_missing) { - // check whether the requested texture was loaded already - auto tex_it = this->textures.find(name); - - // the texture was not loaded yet: - if (tex_it == this->textures.end()) { - auto tex = this->load_texture(name, use_metafile, null_if_missing); - - if (tex.get() != nullptr) { - // insert the texture into the map - this->textures.insert(std::make_pair(name, tex)); - } - - // and return the texture pointer. - return tex.get(); - } - - return tex_it->second.get(); -} - - -void LegacyAssetManager::check_updates() { -#if WITH_INOTIFY - // buffer for at least 4 inotify events - char buf[4 * (sizeof(struct inotify_event) + NAME_MAX + 1)]; - ssize_t len; - - while (true) { - // fetch all events, the kernel won't write "half" structs. - len = read(this->inotify_fd, buf, sizeof(buf)); - - if (len == -1) { - if (errno == EAGAIN) { - // no events, nothing to do. - break; - } - else { - // something went wrong - log::log(WARN << "Failed to read inotify events!"); - break; - } - } - - // process fetched events, - // the kernel guarantees complete events in the buffer. - char *ptr = buf; - while (ptr < buf + len) { - auto *event = reinterpret_cast(ptr); - - if (event->mask & IN_CLOSE_WRITE) { - // TODO: this should invoke callback functions - this->watch_fds[event->wd]->reload(); - } - - // move the buffer ptr to the next event. - ptr += sizeof(struct inotify_event) + event->len; - } - } -#endif -} - -std::shared_ptr LegacyAssetManager::get_missing_tex() { - // if not loaded, fetch the "missing" texture (big red X). - if (this->missing_tex.get() == nullptr) [[unlikely]] { - this->missing_tex = std::make_shared( - this->asset_path["test"]["textures"]["missing.png"], - false); - } - - return this->missing_tex; -} - -void LegacyAssetManager::clear() { -#if WITH_INOTIFY - for (auto &watch_fd : this->watch_fds) { - int result = inotify_rm_watch(this->inotify_fd, watch_fd.first); - if (result < 0) { - log::log(WARN << "Failed to remove inotify watches"); - } - } - this->watch_fds.clear(); -#endif - - this->textures.clear(); -} - -} // namespace openage diff --git a/libopenage/assets/legacy_assetmanager.h b/libopenage/assets/legacy_assetmanager.h deleted file mode 100644 index 284719b1e1..0000000000 --- a/libopenage/assets/legacy_assetmanager.h +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright 2014-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include "config.h" - -#include -#include -#include - -#include "presenter/legacy/legacy.h" -#include "util/path.h" - -namespace qtsdl { -class GuiItemLink; -} // namespace qtsdl - -namespace openage { - -class LegacyEngine; -class Texture; - -/** - * Container class for all available assets. - * Responsible for loading, providing and updating requested files. - */ -class LegacyAssetManager final { -public: - explicit LegacyAssetManager(qtsdl::GuiItemLink *gui_link); - - /** - * Return the path where assets are found in. - */ - const util::Path &get_asset_dir(); - - /** - * Set the asset search path. - */ - void set_asset_dir(const util::Path &asset_dir); - - /** - * Set the game display of this asset manager. - * Called from QML. - */ - void set_display(presenter::LegacyDisplay *display); - - /** - * Return the display responsible for this asset manager. - */ - presenter::LegacyDisplay *get_display() const; - - /** - * Set the game engine of this asset manager. - * Called from QML. - */ - void set_engine(LegacyEngine *engine); - - /** - * Return the engine responsible for this asset manager. - */ - LegacyEngine *get_engine() const; - - /** - * Query the Texture for a given filename. - * - * @param name: the asset file name relative to the asset root. - * @param use_metafile: load subtexture information from meta file - * @param null_if_missing: instead of providing the "missing texture", - * return nullptr. - * @returns the queried texture handle. - */ - Texture *get_texture(const std::string &name, bool use_metafile = true, bool null_if_missing = false); - - /** - * Ask the kernel whether there were updates to watched files. - */ - void check_updates(); - -protected: - /** - * Create an internal texture handle. - */ - std::shared_ptr load_texture(const std::string &name, - bool use_metafile = true, - bool null_if_missing = false); - - /** - * Retrieves the texture for missing textures. - */ - std::shared_ptr get_missing_tex(); - -private: - void clear(); - - /** - * The display this asset manager is attached to. - */ - presenter::LegacyDisplay *display; - - /** - * The engine this asset manager is attached to. - */ - LegacyEngine *engine; - - /** - * The root directory for the available assets. - */ - util::Path asset_path; - - /** - * The replacement texture for missing textures. - */ - std::shared_ptr missing_tex; - - /** - * Map from texture filename to texture instance ptr. - */ - std::unordered_map> textures; - -#if WITH_INOTIFY - /** - * The file descriptor pointing to the inotify instance. - */ - int inotify_fd; - - /** - * Map from inotify watch handle fd to texture instance ptr. - * The kernel returns the handle fd when events are triggered. - */ - std::unordered_map> watch_fds; -#endif - -public: - qtsdl::GuiItemLink *gui_link; -}; - -} // namespace openage diff --git a/libopenage/assets/mod_manager.h b/libopenage/assets/mod_manager.h index b049603f83..3866130b22 100644 --- a/libopenage/assets/mod_manager.h +++ b/libopenage/assets/mod_manager.h @@ -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. #pragma once @@ -22,67 +22,67 @@ class ModManager { ~ModManager() = default; /** - * Adds a modpack to the list of available modpacks. - * - * @param info_file Path to the modpack definition file. - */ + * Adds a modpack to the list of available modpacks. + * + * @param info_file Path to the modpack definition file. + */ void register_modpack(const util::Path &info_file); /** - * Adds a modpack to the list of available modpacks. - * - * @param info Modpack definition. - */ + * Adds a modpack to the list of available modpacks. + * + * @param info Modpack definition. + */ void register_modpack(const ModpackInfo &info); /** - * Ready a set of modpacks with the given IDs. - * - * This prepares the modpacks for loading by valiating the integrity of - * the contained data, checking if the load order and mounting the - * assets into the virtual filesystem. - * - * TODO: - * - Mount modpacks into virtual filesystem. - * - Validate manifest.toml - * - Verify signature of manifest.toml (if signed) - * - * Note that data inside the modpack is not loaded yet. Data - * loading is done inside the simulation before the game starts or - * in case of media files, when they are requested during the game. - * - * Modpacks must have been registered with \p register_modpack() before - * activating. - * - * @param load_order Load order of modpacks. - */ + * Ready a set of modpacks with the given IDs. + * + * This prepares the modpacks for loading by valiating the integrity of + * the contained data, checking if the load order and mounting the + * assets into the virtual filesystem. + * + * TODO: + * - Mount modpacks into virtual filesystem. + * - Validate manifest.toml + * - Verify signature of manifest.toml (if signed) + * + * Note that data inside the modpack is not loaded yet. Data + * loading is done inside the simulation before the game starts or + * in case of media files, when they are requested during the game. + * + * Modpacks must have been registered with \p register_modpack() before + * activating. + * + * @param load_order Load order of modpacks. + */ void activate_modpacks(const std::vector &load_order); /** - * Get a loaded modpack by its ID. - * - * @param modpack_id ID of the modpack to get. - * - * @return Modpack with the given ID. - */ + * Get a loaded modpack by its ID. + * + * @param modpack_id ID of the modpack to get. + * + * @return Modpack with the given ID. + */ std::shared_ptr get_modpack(const std::string &modpack_id) const; /** - * Get the load order of modpacks. - * - * @return Modpack IDs in the order that they should be loaded. - */ + * Get the load order of modpacks. + * + * @return Modpack IDs in the order that they should be loaded. + */ const std::vector &get_load_order() const; /** - * Enumerates all modpack ids in a given directory. - * - * This also loads available modpack definition files. - * - * @param directory Path to the directory to enumerate. - * - * @return Infos of the identified modpacks. - */ + * Enumerates all modpack ids in a given directory. + * + * This also loads available modpack definition files. + * + * @param directory Path to the directory to enumerate. + * + * @return Infos of the identified modpacks. + */ static std::vector enumerate_modpacks(const util::Path &directory) { std::vector result; @@ -106,37 +106,37 @@ class ModManager { private: /** - * Set the order in which modpack data should be loaded. - * - * This also checks whether the given load order is valid, i.e. - * by checking if all dependencies/conflicts are resolved. - * - * TODO: Dynamically resolve load order? - * - * @param load_order Load order of modpacks. - */ + * Set the order in which modpack data should be loaded. + * + * This also checks whether the given load order is valid, i.e. + * by checking if all dependencies/conflicts are resolved. + * + * TODO: Dynamically resolve load order? + * + * @param load_order Load order of modpacks. + */ void set_load_order(const std::vector &load_order); /** - * TODO: Mount point for modpacks. - */ + * TODO: Mount point for modpacks. + */ util::Path asset_base_dir; /** - * Active modpacks. Maps their ID ('name' in the modpack definition file) - * to the modpack. - */ + * Active modpacks. Maps their ID ('name' in the modpack definition file) + * to the modpack. + */ std::unordered_map> active; /** - * Available modpacks that can be activated. Maps their ID ('name' in the modpack - * definition file) to the modpack info. - */ + * Available modpacks that can be activated. Maps their ID ('name' in the modpack + * definition file) to the modpack info. + */ std::unordered_map available; /** - * Load order of modpacks. - */ + * Load order of modpacks. + */ std::vector load_order; }; diff --git a/libopenage/assets/modpack.cpp b/libopenage/assets/modpack.cpp index acc3865696..e2854280ed 100644 --- a/libopenage/assets/modpack.cpp +++ b/libopenage/assets/modpack.cpp @@ -15,7 +15,7 @@ ModpackInfo parse_modepack_def(const util::Path &info_file) { const auto modpack_def = toml::parse(info_file.resolve_native_path()); // info table - const toml::table &info = toml::find(modpack_def, "info"); + const toml::table info = toml::find(modpack_def, "info"); if (info.contains("name")) { def.id = info.at("name").as_string(); } @@ -63,7 +63,7 @@ ModpackInfo parse_modepack_def(const util::Path &info_file) { } // assets table - const toml::table &assets = toml::find(modpack_def, "assets"); + const toml::table assets = toml::find(modpack_def, "assets"); std::vector includes{}; for (const auto &include : assets.at("include").as_array()) { includes.push_back(include.as_string()); @@ -81,7 +81,7 @@ ModpackInfo parse_modepack_def(const util::Path &info_file) { // dependency table if (modpack_def.contains("dependency")) { - const toml::table &dependency = toml::find(modpack_def, "dependency"); + const toml::table dependency = toml::find(modpack_def, "dependency"); std::vector deps{}; if (not dependency.contains("modpacks")) { @@ -97,7 +97,7 @@ ModpackInfo parse_modepack_def(const util::Path &info_file) { // conflicts table if (modpack_def.contains("conflict")) { - const toml::table &conflict = toml::find(modpack_def, "conflict"); + const toml::table conflict = toml::find(modpack_def, "conflict"); std::vector conflicts{}; if (not conflict.contains("modpacks")) { @@ -113,7 +113,7 @@ ModpackInfo parse_modepack_def(const util::Path &info_file) { // authors table if (modpack_def.contains("authors")) { - const toml::table &authors = toml::find(modpack_def, "authors"); + const toml::table authors = toml::find(modpack_def, "authors"); std::vector author_infos{}; for (const auto &author : authors) { AuthorInfo author_info{}; @@ -143,7 +143,7 @@ ModpackInfo parse_modepack_def(const util::Path &info_file) { author_info.roles = roles; } if (author.second.contains("contact")) { - const toml::table &contact = toml::find(author.second, "contact"); + const toml::table contact = toml::find(author.second, "contact"); std::unordered_map contacts{}; for (const auto &contact_info : contact) { contacts[contact_info.first] = contact_info.second.as_string(); @@ -158,7 +158,7 @@ ModpackInfo parse_modepack_def(const util::Path &info_file) { // authorgroups table if (modpack_def.contains("authorgroups")) { - const toml::table &authorgroups = toml::find(modpack_def, "authorgroups"); + const toml::table authorgroups = toml::find(modpack_def, "authorgroups"); std::vector author_group_infos{}; for (const auto &authorgroup : authorgroups) { AuthorGroupInfo author_group_info{}; diff --git a/libopenage/assets/modpack.h b/libopenage/assets/modpack.h index 7c858b5ed7..6d8b2f888d 100644 --- a/libopenage/assets/modpack.h +++ b/libopenage/assets/modpack.h @@ -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. #pragma once @@ -79,26 +79,26 @@ ModpackInfo parse_modepack_def(const util::Path &info_file); class Modpack { public: /** - * Create a new modpack. - * - * Loads the modpack using the information in the definition file. - * - * @param info_file Path to the modpack definition file. - */ + * Create a new modpack. + * + * Loads the modpack using the information in the definition file. + * + * @param info_file Path to the modpack definition file. + */ Modpack(const util::Path &info_file); /** - * Create a new modpack from an existing modpack info. - * - * @param info Modpack metadata information. - */ + * Create a new modpack from an existing modpack info. + * + * @param info Modpack metadata information. + */ Modpack(const ModpackInfo &info); /** - * Create a new modpack from an existing modpack info. - * - * @param info Modpack metadata information. - */ + * Create a new modpack from an existing modpack info. + * + * @param info Modpack metadata information. + */ Modpack(const ModpackInfo &&info); Modpack(const Modpack &) = delete; @@ -106,23 +106,23 @@ class Modpack { ~Modpack() = default; /** - * Get the metadata information of the modpack. - * - * @return Modpack metadata information. - */ + * Get the metadata information of the modpack. + * + * @return Modpack metadata information. + */ const ModpackInfo &get_info() const; /** - * Check if the modpack is valid. - * - * @return true if the modpack is valid, false otherwise. - */ + * Check if the modpack is valid. + * + * @return true if the modpack is valid, false otherwise. + */ bool check_integrity() const; private: /** - * Modpack metadata information. - */ + * Modpack metadata information. + */ ModpackInfo info; }; diff --git a/libopenage/audio/audio_manager.cpp b/libopenage/audio/audio_manager.cpp index 5bda65e6e9..b46fbcf7e2 100644 --- a/libopenage/audio/audio_manager.cpp +++ b/libopenage/audio/audio_manager.cpp @@ -1,96 +1,61 @@ -// Copyright 2014-2019 the openage authors. See copying.md for legal info. +// Copyright 2014-2023 the openage authors. See copying.md for legal info. #include "audio_manager.h" #include -#include #include +#include +#include +#include +#include + +#include "../log/log.h" +#include "../util/misc.h" #include "error.h" #include "hash_functions.h" #include "resource.h" -#include "../log/log.h" -#include "../util/misc.h" namespace openage { namespace audio { - - - -/** - * Wrapper class for the sdl audio device locking so - * the device doesn't deadlock because of funny exceptions. - */ -class SDLDeviceLock { -public: - explicit SDLDeviceLock(const SDL_AudioDeviceID &id) - : - dev_id{id} { - SDL_LockAudioDevice(this->dev_id); - } - - ~SDLDeviceLock() { - SDL_UnlockAudioDevice(this->dev_id); - } - - SDLDeviceLock(SDLDeviceLock &&) = delete; - SDLDeviceLock(const SDLDeviceLock &) = delete; - SDLDeviceLock& operator =(SDLDeviceLock &&) = delete; - SDLDeviceLock& operator =(const SDLDeviceLock &) = delete; - -private: - SDL_AudioDeviceID dev_id; -}; - - -AudioManager::AudioManager(job::JobManager *job_manager, - const std::string &device_name) - : +AudioManager::AudioManager(const std::shared_ptr &job_manager, + const std::string &device_name) : available{false}, job_manager{job_manager}, - device_name{device_name} { + device_name{device_name}, + device_format{std::make_shared()}, + device{nullptr}, + audio_sink{nullptr} { + // set desired audio output format + this->device_format->setSampleRate(48000); + this->device_format->setSampleFormat(QAudioFormat::SampleFormat::Int16); + this->device_format->setChannelCount(2); + + // find the device with the given name + for (auto &device : QMediaDevices::audioOutputs()) { + if (device.description().toStdString() == device_name) { + this->device = std::make_shared(device); + break; + } + } - if (SDL_Init(SDL_INIT_AUDIO) < 0) { - log::log(MSG(err) - << "SDL audio initialization failed: " - << SDL_GetError()); + // select the default device if the name was not found + if (not this->device) { + this->device = std::make_shared(QMediaDevices::defaultAudioOutput()); return; - } else { - log::log(MSG(info) << "SDL audio subsystems initialized"); } - // set desired audio output format - SDL_AudioSpec desired_spec; - SDL_zero(desired_spec); - desired_spec.freq = 48000; - desired_spec.format = AUDIO_S16LSB; - desired_spec.channels = 2; - desired_spec.samples = 4096; - desired_spec.userdata = this; - - // call back that is invoked once SDL needs the next chunk of data - desired_spec.callback = [] (void *userdata, uint8_t *stream, int len) { - auto *audio_manager = static_cast(userdata); - audio_manager->audio_callback(reinterpret_cast(stream), len / 2); - }; - - // convert device name to valid parameter for sdl call - // if the device name is empty, use a nullptr in order to indicate that the - // default device should be used - const char *c_device_name = device_name.empty() ? - nullptr : device_name.c_str(); - // open audio playback device - device_id = SDL_OpenAudioDevice(c_device_name, 0, &desired_spec, - &device_spec, 0); - - // no device could be opened - if (device_id == 0) { - log::log(MSG(err) << "Error opening audio device: " << SDL_GetError()); + if (not this->device->isFormatSupported(*this->device_format)) { + log::log(MSG(err) << "Audio device does not support the desired format!"); return; } + // create audio sink + this->audio_sink = std::make_unique(*this->device.get(), *this->device_format.get()); + // TODO: connect callback to get audio data to device + // initialize playing sounds vectors using sound_vector = std::vector>; playing_sounds.insert({category_t::GAME, sound_vector{}}); @@ -100,25 +65,23 @@ AudioManager::AudioManager(job::JobManager *job_manager, // create buffer for mixing this->mix_buffer = std::make_unique( - 4 * device_spec.samples * device_spec.channels - ); + 4 * device_format->bytesPerSample() * device_format->channelCount()); - log::log(MSG(info) << - "Using audio device: " - << (device_name.empty() ? "default" : device_name) - << " [freq=" << device_spec.freq - << ", format=" << device_spec.format - << ", channels=" << static_cast(device_spec.channels) - << ", samples=" << device_spec.samples - << "]"); + log::log(MSG(info) << "Using audio device: " + << (device_name.empty() ? "default" : device_name) + << " [sample rate=" << device_format->sampleRate() + << ", format=" << device_format->sampleFormat() + << ", channels=" << device_format->channelCount() + << ", samples=" << device_format->bytesPerSample() + << "]"); - SDL_PauseAudioDevice(device_id, 0); + this->audio_sink->stop(); this->available = true; } AudioManager::~AudioManager() { - SDL_CloseAudioDevice(device_id); + this->audio_sink->stop(); } void AudioManager::load_resources(const std::vector &sound_files) { @@ -148,11 +111,10 @@ Sound AudioManager::get_sound(category_t category, int id) { auto resource = resources.find(std::make_tuple(category, id)); if (resource == std::end(resources)) { throw audio::Error{ - MSG(err) << - "Sound resource does not exist: " - "category=" << category << ", " << - "id=" << id - }; + MSG(err) << "Sound resource does not exist: " + "category=" + << category << ", " + << "id=" << id}; } auto sound_impl = std::make_shared(resource->second); @@ -161,7 +123,7 @@ Sound AudioManager::get_sound(category_t category, int id) { void AudioManager::audio_callback(int16_t *stream, int length) { - std::memset(mix_buffer.get(), 0, length*4); + std::memset(mix_buffer.get(), 0, length * 4); // iterate over all categories for (auto &entry : this->playing_sounds) { @@ -181,28 +143,25 @@ void AudioManager::audio_callback(int16_t *stream, int length) { // write the mix buffer to the output stream and adjust volume for (int i = 0; i < length; i++) { - auto value = mix_buffer[i]/256; + auto value = mix_buffer[i] / 256; if (value > 32767) { value = 32767; - } else if (value < -32768) { + } + else if (value < -32768) { value = -32768; } stream[i] = static_cast(value); } } -void AudioManager::add_sound(const std::shared_ptr& sound) { - SDLDeviceLock lock{this->device_id}; - +void AudioManager::add_sound(const std::shared_ptr &sound) { auto category = sound->get_category(); auto &playing_list = this->playing_sounds.find(category)->second; // TODO probably check if sound already exists in playing list playing_list.push_back(sound); } -void AudioManager::remove_sound(const std::shared_ptr& sound) { - SDLDeviceLock lock{this->device_id}; - +void AudioManager::remove_sound(const std::shared_ptr &sound) { auto category = sound->get_category(); auto &playing_list = this->playing_sounds.find(category)->second; @@ -214,11 +173,11 @@ void AudioManager::remove_sound(const std::shared_ptr& sound) { } } -SDL_AudioSpec AudioManager::get_device_spec() const { - return this->device_spec; +const std::shared_ptr &AudioManager::get_device_spec() const { + return this->device_format; } -job::JobManager *AudioManager::get_job_manager() const { +const std::shared_ptr &AudioManager::get_job_manager() const { return this->job_manager; } @@ -228,32 +187,21 @@ bool AudioManager::is_available() const { std::vector AudioManager::get_devices() { + auto devices = QMediaDevices::audioOutputs(); + std::vector device_list; - auto num_devices = SDL_GetNumAudioDevices(0); - device_list.reserve(num_devices); - for (int i = 0; i < num_devices; i++) { - device_list.emplace_back(SDL_GetAudioDeviceName(i, 0)); - } - return device_list; -} + device_list.reserve(devices.size()); -std::vector AudioManager::get_drivers() { - std::vector driver_list; - auto num_drivers = SDL_GetNumAudioDrivers(); - driver_list.reserve(num_drivers); - for (int i = 0; i < num_drivers; i++) { - driver_list.emplace_back(SDL_GetAudioDriver(i)); + for (auto &device : devices) { + device_list.emplace_back(device.description().toStdString()); } - return driver_list; + + return device_list; } -std::string AudioManager::get_current_driver() { - const char *c_driver = SDL_GetCurrentAudioDriver(); - if (c_driver == nullptr) { - return ""; - } else { - return c_driver; - } +std::string AudioManager::get_default_device() { + return QMediaDevices::defaultAudioOutput().description().toStdString(); } -}} // openage::audio +} // namespace audio +} // namespace openage diff --git a/libopenage/audio/audio_manager.h b/libopenage/audio/audio_manager.h index d97197b5b5..8d3269a5a2 100644 --- a/libopenage/audio/audio_manager.h +++ b/libopenage/audio/audio_manager.h @@ -7,18 +7,19 @@ #include #include -#include +#include #include "category.h" #include "hash_functions.h" #include "resource_def.h" #include "sound.h" +Q_FORWARD_DECLARE_OBJC_CLASS(QAudioDevice); +Q_FORWARD_DECLARE_OBJC_CLASS(QAudioFormat); +Q_FORWARD_DECLARE_OBJC_CLASS(QAudioSink); namespace openage { -class LegacyEngine; - namespace job { class JobManager; } @@ -28,6 +29,8 @@ namespace audio { /** * This class provides audio functionality for openage. + * + * TODO: Finish porting to Qt. */ class AudioManager { public: @@ -35,7 +38,7 @@ class AudioManager { * Initializes the audio manager with the given device name. * If the name is empty, the default device is used. */ - AudioManager(job::JobManager *job_manager, + AudioManager(const std::shared_ptr &job_manager, const std::string &device_name = ""); ~AudioManager(); @@ -68,12 +71,12 @@ class AudioManager { /** * Returns the currently used audio output format. */ - SDL_AudioSpec get_device_spec() const; + const std::shared_ptr &get_device_spec() const; /** * Return the game engine the audio manager is attached to. */ - job::JobManager *get_job_manager() const; + const std::shared_ptr &get_job_manager() const; /** * If this audio manager is available. @@ -81,6 +84,16 @@ class AudioManager { */ bool is_available() const; + /** + * Returns a vector of all available device names. + */ + static std::vector get_devices(); + + /** + * Returns the default device name. + */ + static std::string get_default_device(); + private: void add_sound(const std::shared_ptr &sound); void remove_sound(const std::shared_ptr &sound); @@ -98,7 +111,7 @@ class AudioManager { /** * The job manager used in this audio manager for job queuing. */ - job::JobManager *job_manager; + std::shared_ptr job_manager; /** * the used audio device's name @@ -108,12 +121,17 @@ class AudioManager { /** * the audio output format */ - SDL_AudioSpec device_spec; + std::shared_ptr device_format; /** * the used audio device's id */ - SDL_AudioDeviceID device_id; + std::shared_ptr device; + + /** + * the audio sink + */ + std::shared_ptr audio_sink; /** * Buffer used for mixing audio to one stream. @@ -123,23 +141,6 @@ class AudioManager { std::unordered_map, std::shared_ptr> resources; std::unordered_map>> playing_sounds; - - // static functions -public: - /** - * Returns a vector of all available device names. - */ - static std::vector get_devices(); - - /** - * Returns a vector of all available driver names. - */ - static std::vector get_drivers(); - - /** - * Returns the name of the currently used driver. - */ - static std::string get_current_driver(); }; } // namespace audio diff --git a/libopenage/audio/category.h b/libopenage/audio/category.h index 074ebe29b3..8d208fb0a8 100644 --- a/libopenage/audio/category.h +++ b/libopenage/audio/category.h @@ -1,12 +1,10 @@ -// Copyright 2014-2017 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. #pragma once #include -namespace openage { -namespace audio { - +namespace openage::audio { enum class category_t { GAME, @@ -17,7 +15,7 @@ enum class category_t { const char *category_t_to_str(category_t val); -std::ostream &operator <<(std::ostream &os, category_t val); +std::ostream &operator<<(std::ostream &os, category_t val); -}} // openage::audio +} // namespace openage::audio diff --git a/libopenage/audio/dynamic_loader.h b/libopenage/audio/dynamic_loader.h index b8b0ade984..703982c121 100644 --- a/libopenage/audio/dynamic_loader.h +++ b/libopenage/audio/dynamic_loader.h @@ -1,13 +1,13 @@ -// Copyright 2014-2017 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. #pragma once #include #include +#include "../util/path.h" #include "format.h" #include "types.h" -#include "../util/path.h" namespace openage { @@ -43,7 +43,8 @@ class DynamicLoader { * @param offset the offset from the resource's beginning * @param chunk_size the number of int16_t values that fit in one chunk */ - virtual size_t load_chunk(int16_t *chunk_buffer, size_t offset, + virtual size_t load_chunk(int16_t *chunk_buffer, + size_t offset, size_t chunk_size) = 0; /** @@ -55,4 +56,5 @@ class DynamicLoader { format_t format); }; -}} // openage::audio +} // namespace audio +} // namespace openage diff --git a/libopenage/audio/dynamic_resource.cpp b/libopenage/audio/dynamic_resource.cpp index bb2347c07b..97aac8df37 100644 --- a/libopenage/audio/dynamic_resource.cpp +++ b/libopenage/audio/dynamic_resource.cpp @@ -4,7 +4,6 @@ #include "../job/job_manager.h" -#include "../legacy_engine.h" #include "../log/log.h" #include "audio_manager.h" diff --git a/libopenage/audio/dynamic_resource.h b/libopenage/audio/dynamic_resource.h index 1f98116f51..e1605d5487 100644 --- a/libopenage/audio/dynamic_resource.h +++ b/libopenage/audio/dynamic_resource.h @@ -1,4 +1,4 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once @@ -8,15 +8,15 @@ #include #include +#include "../datastructure/concurrent_queue.h" +#include "../job/job.h" +#include "../job/job_group.h" +#include "../util/path.h" #include "category.h" #include "dynamic_loader.h" #include "format.h" #include "resource.h" #include "types.h" -#include "../datastructure/concurrent_queue.h" -#include "../job/job.h" -#include "../job/job_group.h" -#include "../util/path.h" namespace openage { namespace audio { @@ -59,10 +59,10 @@ class DynamicResource : public Resource { category_t category, int id, const util::Path &path, - format_t format=format_t::OPUS, - int preload_amount=DEFAULT_PRELOAD_AMOUNT, - size_t chunk_size=DEFAULT_CHUNK_SIZE, - size_t max_chunks=DEFAULT_MAX_CHUNKS); + format_t format = format_t::OPUS, + int preload_amount = DEFAULT_PRELOAD_AMOUNT, + size_t chunk_size = DEFAULT_CHUNK_SIZE, + size_t max_chunks = DEFAULT_MAX_CHUNKS); virtual ~DynamicResource() = default; @@ -95,7 +95,7 @@ class DynamicResource : public Resource { static constexpr int DEFAULT_PRELOAD_AMOUNT = 10; /** The default used chunk size in bytes (100ms for 48kHz stereo audio). */ - static constexpr size_t DEFAULT_CHUNK_SIZE = 9600*2; + static constexpr size_t DEFAULT_CHUNK_SIZE = 9600 * 2; /** The default number of chunks, that can be loaded at the same time. */ static constexpr size_t DEFAULT_MAX_CHUNKS = 100; @@ -138,11 +138,11 @@ class DynamicResource : public Resource { * Resource chunk index to chunk mapping. * Loading and usage state is reached through this. */ - std::unordered_map> chunks; + std::unordered_map> chunks; /** The background loading job group. */ job::JobGroup loading_job_group; }; -} -} +} // namespace audio +} // namespace openage diff --git a/libopenage/audio/error.h b/libopenage/audio/error.h index 147df0e520..75dade0dd8 100644 --- a/libopenage/audio/error.h +++ b/libopenage/audio/error.h @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once @@ -12,4 +12,5 @@ class Error : public error::Error { Error(const log::message &msg); }; -}} // openage::audio +} // namespace audio +} // namespace openage diff --git a/libopenage/audio/format.h b/libopenage/audio/format.h index 37060abcbc..6fe6e92c1b 100644 --- a/libopenage/audio/format.h +++ b/libopenage/audio/format.h @@ -1,4 +1,4 @@ -// Copyright 2014-2017 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. #pragma once @@ -17,6 +17,7 @@ enum class format_t { const char *format_t_to_str(format_t val); -std::ostream &operator <<(std::ostream &os, format_t val); +std::ostream &operator<<(std::ostream &os, format_t val); -}} // openage::audio +} // namespace audio +} // namespace openage diff --git a/libopenage/audio/hash_functions.h b/libopenage/audio/hash_functions.h index 8e57a30fae..5fc18d3fcb 100644 --- a/libopenage/audio/hash_functions.h +++ b/libopenage/audio/hash_functions.h @@ -1,4 +1,4 @@ -// Copyright 2014-2016 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. #pragma once @@ -8,18 +8,18 @@ namespace std { -template<> +template <> struct hash<::openage::audio::category_t> { size_t operator()(const ::openage::audio::category_t &c) const { return static_cast(c); } }; -template<> -struct hash> { - size_t operator()(const std::tuple<::openage::audio::category_t,int> &t) const { +template <> +struct hash> { + size_t operator()(const std::tuple<::openage::audio::category_t, int> &t) const { return static_cast(std::get<0>(t)) << (sizeof(size_t) * 8 - 2) | std::get<1>(t); } }; -} +} // namespace std diff --git a/libopenage/audio/in_memory_loader.h b/libopenage/audio/in_memory_loader.h index 056f661246..4c21ba237d 100644 --- a/libopenage/audio/in_memory_loader.h +++ b/libopenage/audio/in_memory_loader.h @@ -1,4 +1,4 @@ -// Copyright 2014-2017 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. #pragma once @@ -6,9 +6,9 @@ #include #include +#include "../util/path.h" #include "format.h" #include "types.h" -#include "../util/path.h" namespace openage { @@ -47,4 +47,5 @@ class InMemoryLoader { format_t format); }; -}} // openage::audio +} // namespace audio +} // namespace openage diff --git a/libopenage/audio/in_memory_resource.h b/libopenage/audio/in_memory_resource.h index 0427f0ea1a..9573d7cb40 100644 --- a/libopenage/audio/in_memory_resource.h +++ b/libopenage/audio/in_memory_resource.h @@ -1,13 +1,13 @@ -// Copyright 2014-2017 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. #pragma once #include +#include "../util/path.h" #include "format.h" #include "resource.h" #include "types.h" -#include "../util/path.h" namespace openage { @@ -26,7 +26,7 @@ class InMemoryResource : public Resource { category_t category, int id, const util::Path &path, - format_t format=format_t::OPUS); + format_t format = format_t::OPUS); virtual ~InMemoryResource() = default; void use() override; @@ -35,5 +35,5 @@ class InMemoryResource : public Resource { audio_chunk_t get_data(size_t position, size_t data_length) override; }; -} -} +} // namespace audio +} // namespace openage diff --git a/libopenage/audio/loader_policy.h b/libopenage/audio/loader_policy.h index 98bb64a814..5f2acca7ce 100644 --- a/libopenage/audio/loader_policy.h +++ b/libopenage/audio/loader_policy.h @@ -1,4 +1,4 @@ -// Copyright 2014-2017 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. #pragma once @@ -17,6 +17,7 @@ enum class loader_policy_t { const char *loader_policy_t_to_str(loader_policy_t val); -std::ostream &operator <<(std::ostream &os, loader_policy_t val); +std::ostream &operator<<(std::ostream &os, loader_policy_t val); -}} // openage::audio +} // namespace audio +} // namespace openage diff --git a/libopenage/audio/opus_dynamic_loader.h b/libopenage/audio/opus_dynamic_loader.h index 5c6053cf11..a612a65237 100644 --- a/libopenage/audio/opus_dynamic_loader.h +++ b/libopenage/audio/opus_dynamic_loader.h @@ -1,14 +1,14 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once -#include #include +#include -#include "opus_loading.h" +#include "../util/path.h" #include "dynamic_loader.h" +#include "opus_loading.h" #include "types.h" -#include "../util/path.h" namespace openage { @@ -40,4 +40,5 @@ class OpusDynamicLoader : public DynamicLoader { size_t chunk_size) override; }; -}} // openage::audio +} // namespace audio +} // namespace openage diff --git a/libopenage/audio/opus_in_memory_loader.h b/libopenage/audio/opus_in_memory_loader.h index cf8b5b3ce1..021f23a1b2 100644 --- a/libopenage/audio/opus_in_memory_loader.h +++ b/libopenage/audio/opus_in_memory_loader.h @@ -1,12 +1,12 @@ -// Copyright 2014-2017 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. #pragma once #include +#include "../util/path.h" #include "in_memory_loader.h" #include "types.h" -#include "../util/path.h" namespace openage { @@ -28,4 +28,5 @@ class OpusInMemoryLoader : public InMemoryLoader { pcm_data_t get_resource() override; }; -}} // openage::audio +} // namespace audio +} // namespace openage diff --git a/libopenage/audio/opus_loading.h b/libopenage/audio/opus_loading.h index 1156fd1199..c2251cd8c2 100644 --- a/libopenage/audio/opus_loading.h +++ b/libopenage/audio/opus_loading.h @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once @@ -21,7 +21,7 @@ struct opus_file_t { * The opusfile handle, with a custom deleter that frees * the opusfile memory. */ - std::unique_ptr> handle; + std::unique_ptr> handle; /** * File used to supply the data. @@ -37,4 +37,5 @@ struct opus_file_t { */ opus_file_t open_opus_file(const util::Path &path); -}} // openage::audio +} // namespace audio +} // namespace openage diff --git a/libopenage/audio/resource.cpp b/libopenage/audio/resource.cpp index b5b3a3af55..66efcf52c2 100644 --- a/libopenage/audio/resource.cpp +++ b/libopenage/audio/resource.cpp @@ -3,7 +3,6 @@ #include "resource.h" #include "../error/error.h" -#include "../legacy_engine.h" #include "dynamic_resource.h" #include "in_memory_resource.h" @@ -30,7 +29,6 @@ int Resource::get_id() const { std::shared_ptr Resource::create_resource(AudioManager *manager, const resource_def &def) { - if (not def.location.is_file()) [[unlikely]] { throw Error{ERR << "sound file does not exist: " << def.location}; } diff --git a/libopenage/audio/resource.h b/libopenage/audio/resource.h index 0dfe58d0b5..91d53133ed 100644 --- a/libopenage/audio/resource.h +++ b/libopenage/audio/resource.h @@ -1,4 +1,4 @@ -// Copyright 2014-2017 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. #pragma once @@ -27,11 +27,11 @@ class Resource { Resource(AudioManager *manager, category_t category, int id); virtual ~Resource() = default; - Resource(const Resource&) = delete; - Resource &operator=(const Resource&) = delete; + Resource(const Resource &) = delete; + Resource &operator=(const Resource &) = delete; - Resource(Resource&&) = delete; - Resource &operator=(Resource&&) = delete; + Resource(Resource &&) = delete; + Resource &operator=(Resource &&) = delete; virtual category_t get_category() const; virtual int get_id() const; @@ -83,5 +83,5 @@ class Resource { int id; }; -} -} +} // namespace audio +} // namespace openage diff --git a/libopenage/audio/resource_def.h b/libopenage/audio/resource_def.h index b5e427e5aa..b18a79e9cf 100644 --- a/libopenage/audio/resource_def.h +++ b/libopenage/audio/resource_def.h @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once @@ -27,4 +27,5 @@ struct resource_def { }; -}} // openage::audio +} // namespace audio +} // namespace openage diff --git a/libopenage/audio/sound.h b/libopenage/audio/sound.h index 546e3bf136..f01a54cb53 100644 --- a/libopenage/audio/sound.h +++ b/libopenage/audio/sound.h @@ -1,4 +1,4 @@ -// Copyright 2014-2019 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. #pragma once @@ -23,7 +23,7 @@ class Resource; */ class SoundImpl { public: - SoundImpl(std::shared_ptr resource, int32_t volume=128); + SoundImpl(std::shared_ptr resource, int32_t volume = 128); ~SoundImpl(); /** @@ -167,4 +167,5 @@ class Sound { }; -}} // namespace openage::audio +} // namespace audio +} // namespace openage diff --git a/libopenage/audio/types.h b/libopenage/audio/types.h index 348b3c777c..221063092c 100644 --- a/libopenage/audio/types.h +++ b/libopenage/audio/types.h @@ -1,4 +1,4 @@ -// Copyright 2014-2017 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. #pragma once @@ -34,4 +34,5 @@ using pcm_data_t = std::vector; */ using pcm_chunk_t = std::vector; -}} // openage::audio +} // namespace audio +} // namespace openage diff --git a/libopenage/console/buf.h b/libopenage/console/buf.h index 71499f84cf..1260794624 100644 --- a/libopenage/console/buf.h +++ b/libopenage/console/buf.h @@ -1,4 +1,4 @@ -// Copyright 2014-2018 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. #pragma once @@ -51,12 +51,11 @@ struct buf_char { /** * Default copy constructor */ - buf_char(int cp, chrcol_t fgcol, chrcol_t bgcol, chrflags_t flags) - : - cp{cp}, - fgcol{fgcol}, - bgcol{bgcol}, - flags{flags} { + buf_char(int cp, chrcol_t fgcol, chrcol_t bgcol, chrflags_t flags) : + cp{cp}, + fgcol{fgcol}, + bgcol{bgcol}, + flags{flags} { } buf_char() = default; @@ -78,16 +77,12 @@ struct buf_char { */ chrflags_t flags; - bool operator ==(const buf_char &other) const { - return - (this->cp == other.cp) && - (this->fgcol == other.fgcol) && - (this->bgcol == other.bgcol) && - (this->flags == other.flags); + bool operator==(const buf_char &other) const { + return (this->cp == other.cp) && (this->fgcol == other.fgcol) && (this->bgcol == other.bgcol) && (this->flags == other.flags); } - bool operator !=(const buf_char &other) const { - return not (*this == other); + bool operator!=(const buf_char &other) const { + return not(*this == other); } }; @@ -116,20 +111,19 @@ struct buf_line { linetype_t type; }; -constexpr buf_line BUF_LINE_DEFAULT {LINE_EMPTY}; +constexpr buf_line BUF_LINE_DEFAULT{LINE_EMPTY}; class Buf { friend class NewBuf; public: - Buf(coord::term dims, coord::term_t scrollback_lines, coord::term_t min_width, - buf_char default_char_fmt = {0x20, 254, 0, 0}); + Buf(coord::term dims, coord::term_t scrollback_lines, coord::term_t min_width, buf_char default_char_fmt = {0x20, 254, 0, 0}); ~Buf(); /* * we don't want this object to be copyable */ - Buf& operator=(const Buf &) = delete; + Buf &operator=(const Buf &) = delete; Buf(const Buf &) = delete; @@ -407,7 +401,7 @@ class Buf { */ buf_line *screen_linedata; - //following this line are all terminal size related variables + // following this line are all terminal size related variables /** * minimum screen buffer width @@ -425,7 +419,7 @@ class Buf { */ coord::term_t scrollback_lines; - //following this line are all cursor state related variables + // following this line are all cursor state related variables /** * cursor position @@ -450,7 +444,7 @@ class Buf { */ bool cursor_special_lastcol; - //following this line are misc variables + // following this line are misc variables /** * true if we are currently reading an escape sequence @@ -518,4 +512,5 @@ class Buf { coord::term_t scrollback_pos; }; -}} // openage::console +} // namespace console +} // namespace openage diff --git a/libopenage/console/console.cpp b/libopenage/console/console.cpp index ae9c2cae77..a07d42b898 100644 --- a/libopenage/console/console.cpp +++ b/libopenage/console/console.cpp @@ -3,7 +3,6 @@ #include "console.h" #include "../error/error.h" -#include "../legacy_engine.h" #include "../log/log.h" #include "../util/strings.h" #include "../util/unicode.h" @@ -22,8 +21,8 @@ namespace console { * log console, command console */ -Console::Console(presenter::LegacyDisplay *engine) : - engine{engine}, +Console::Console(/* presenter::LegacyDisplay *display */) : + // display{display}, bottomleft{0, 0}, topright{1, 1}, charsize{1, 1}, @@ -45,6 +44,7 @@ Console::Console(presenter::LegacyDisplay *engine) : Console::~Console() {} +/* void Console::load_colors(std::vector &colortable) { for (auto &c : colortable) { this->termcolors.emplace_back(c); @@ -54,25 +54,28 @@ void Console::load_colors(std::vector &colortable) { throw Error(MSG(err) << "Exactly 256 terminal colors are required."); } } +*/ void Console::register_to_engine() { - this->engine->register_input_action(this); - this->engine->register_tick_action(this); - this->engine->register_drawhud_action(this); - this->engine->register_resize_action(this); + // TODO: Use new renderer + /* + this->display->register_input_action(this); + this->display->register_tick_action(this); + this->display->register_drawhud_action(this); + this->display->register_resize_action(this); - // Bind the console toggle key globally - auto &action = this->engine->get_action_manager(); - auto &global = this->engine->get_input_manager().get_global_context(); + Bind the console toggle key globally + auto &action = this->display->get_action_manager(); + auto &global = this->display->get_input_manager().get_global_context(); global.bind(action.get("TOGGLE_CONSOLE"), [this](const input::legacy::action_arg_t &) { this->set_visible(!this->visible); }); - // TODO: bind any needed input to InputContext + TODO: bind any needed input to InputContext - // toggle console will take highest priority + toggle console will take highest priority this->input_context.bind(action.get("TOGGLE_CONSOLE"), [this](const input::legacy::action_arg_t &) { this->set_visible(false); }); @@ -103,17 +106,21 @@ void Console::register_to_engine() { } }); this->input_context.utf8_mode = true; + */ } -void Console::set_visible(bool make_visible) { +void Console::set_visible(bool /* make_visible */) { + // TODO: Use new renderer + /* if (make_visible) { - this->engine->get_input_manager().push_context(&this->input_context); + this->display->get_input_manager().push_context(&this->input_context); this->visible = true; } else { - this->engine->get_input_manager().remove_context(&this->input_context); + this->display->get_input_manager().remove_context(&this->input_context); this->visible = false; } + */ } void Console::write(const char *text) { @@ -126,9 +133,11 @@ void Console::interpret(const std::string &command) { this->set_visible(false); } else if (command == "list") { - for (auto &line : this->engine->list_options()) { - this->write(line.c_str()); - } + // TODO: Use new renderer + + // for (auto &line : this->display->list_options()) { + // this->write(line.c_str()); + // } } else if (command.substr(0, 3) == "set") { std::size_t first_space = command.find(" "); @@ -154,7 +163,7 @@ void Console::interpret(const std::string &command) { } } } - +/* bool Console::on_tick() { if (!this->visible) { return true; @@ -170,7 +179,9 @@ bool Console::on_drawhud() { return true; } - draw::to_opengl(this->engine, this); + // TODO: Use new renderer + + // draw::to_opengl(this->display, this); return true; } @@ -201,6 +212,6 @@ bool Console::on_resize(coord::viewport_delta new_size) { return true; } - +*/ } // namespace console } // namespace openage diff --git a/libopenage/console/console.h b/libopenage/console/console.h index 8585373eb6..380a563e17 100644 --- a/libopenage/console/console.h +++ b/libopenage/console/console.h @@ -2,39 +2,31 @@ #pragma once -#include #include #include "../coord/pixel.h" -#include "../gamedata/color_dummy.h" -#include "../handlers.h" -#include "../input/legacy/input_manager.h" -#include "../presenter/legacy/legacy.h" #include "../renderer/font/font.h" #include "../util/color.h" #include "buf.h" namespace openage { -class LegacyEngine; - /** * In-game console subsystem. Featuring a full terminal emulator. + * + * TODO: Adapt to new engine subsystems. */ namespace console { -class Console : InputHandler - , TickHandler - , HudHandler - , ResizeHandler { +class Console { public: - Console(presenter::LegacyDisplay *renderer); + Console(/* presenter::LegacyDisplay *display */); ~Console(); /** * load the consoles color table */ - void load_colors(std::vector &colortable); + // void load_colors(std::vector &colortable); /** * register this console to the renderer. @@ -54,13 +46,14 @@ class Console : InputHandler */ void interpret(const std::string &command); - bool on_drawhud() override; - bool on_tick() override; - bool on_input(SDL_Event *event) override; - bool on_resize(coord::viewport_delta new_size) override; + // bool on_drawhud() override; + // bool on_tick() override; + // bool on_input(SDL_Event *event) override; + // bool on_resize(coord::viewport_delta new_size) override; protected: - presenter::LegacyDisplay *engine; + // TODO: Replace with new renderer + // presenter::LegacyDisplay *display; public: coord::camhud bottomleft; @@ -74,7 +67,7 @@ class Console : InputHandler Buf buf; renderer::Font font; - input::legacy::InputContext input_context; + // input::legacy::InputContext input_context; // the command state std::string command; diff --git a/libopenage/console/draw.cpp b/libopenage/console/draw.cpp index 237c16d91c..ba5b4588c9 100644 --- a/libopenage/console/draw.cpp +++ b/libopenage/console/draw.cpp @@ -10,7 +10,6 @@ #include "../util/timing.h" #include -#include "../renderer/text.h" #include "buf.h" #include "console.h" @@ -18,6 +17,7 @@ namespace openage { namespace console { namespace draw { +/* void to_opengl(presenter::LegacyDisplay *engine, Console *console) { coord::camhud topleft{ console->bottomleft.x, @@ -85,6 +85,7 @@ void to_opengl(presenter::LegacyDisplay *engine, Console *console) { } } } +*/ void to_terminal(Buf *buf, util::FD *fd, bool clear) { //move cursor, draw top left corner diff --git a/libopenage/console/draw.h b/libopenage/console/draw.h index 2e35555f05..13634ea5b4 100644 --- a/libopenage/console/draw.h +++ b/libopenage/console/draw.h @@ -2,12 +2,9 @@ #pragma once -#include "../presenter/legacy/legacy.h" namespace openage { -class LegacyEngine; - namespace util { class FD; } // namespace util @@ -22,7 +19,7 @@ namespace draw { /** * experimental and totally inefficient opengl draw of a terminal buffer. */ -void to_opengl(presenter::LegacyDisplay *engine, Console *console); +// void to_opengl(presenter::LegacyDisplay *engine, Console *console); /** * very early and inefficient printing of the console to a pty. diff --git a/libopenage/console/tests.cpp b/libopenage/console/tests.cpp index 445fb71b30..7da8dd02f4 100644 --- a/libopenage/console/tests.cpp +++ b/libopenage/console/tests.cpp @@ -1,12 +1,12 @@ -// Copyright 2014-2019 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. -#include #include +#include #ifdef _MSC_VER -#define STDOUT_FILENO 1 + #define STDOUT_FILENO 1 #else -#include + #include #endif #include "../util/fds.h" #include "../util/pty.h" @@ -15,8 +15,8 @@ #include #include -#include "../log/log.h" #include "../error/error.h" +#include "../log/log.h" #include "buf.h" #include "console.h" @@ -51,7 +51,7 @@ void render() { void interactive() { - #ifndef _WIN32 +#ifndef _WIN32 console::Buf buf{{80, 25}, 1337, 80}; struct winsize ws; @@ -75,7 +75,7 @@ void interactive() { throw Error(MSG(err) << "execl(\"" << shell << "\", \"" << shell << "\", nullptr) failed: " << strerror(errno)); } default: - //we are the parent + // we are the parent break; } @@ -143,7 +143,7 @@ void interactive() { ssize_t retval = read(ptyin.fd, rdbuf, rdbuf_size); switch (retval) { case -1: - switch(errno) { + switch (errno) { case EIO: loop = false; break; @@ -175,7 +175,7 @@ void interactive() { // show cursor termout.puts("\x1b[?25h"); - #endif /* _WIN32 */ +#endif /* _WIN32 */ } diff --git a/libopenage/coord/CMakeLists.txt b/libopenage/coord/CMakeLists.txt index 5123577052..7f01010d8a 100644 --- a/libopenage/coord/CMakeLists.txt +++ b/libopenage/coord/CMakeLists.txt @@ -3,7 +3,6 @@ add_sources(libopenage chunk.cpp coord_test.cpp - coordmanager.cpp declarations.cpp phys.cpp pixel.cpp diff --git a/libopenage/coord/chunk.cpp b/libopenage/coord/chunk.cpp index fd061586b7..62ab65be32 100644 --- a/libopenage/coord/chunk.cpp +++ b/libopenage/coord/chunk.cpp @@ -1,8 +1,20 @@ -// Copyright 2016-2018 the openage authors. See copying.md for legal info. +// Copyright 2016-2024 the openage authors. See copying.md for legal info. #include "chunk.h" +#include "coord/tile.h" + + namespace openage { namespace coord { -}} // namespace openage::coord +tile_delta chunk_delta::to_tile(tile_t tiles_per_chunk) const { + return tile_delta{this->ne * tiles_per_chunk, this->se * tiles_per_chunk}; +} + +tile chunk::to_tile(tile_t tiles_per_chunk) const { + return tile{this->ne * tiles_per_chunk, this->se * tiles_per_chunk}; +} + +} // namespace coord +} // namespace openage diff --git a/libopenage/coord/chunk.h b/libopenage/coord/chunk.h index 3790eb84cd..158888c9a1 100644 --- a/libopenage/coord/chunk.h +++ b/libopenage/coord/chunk.h @@ -1,10 +1,10 @@ -// Copyright 2016-2018 the openage authors. See copying.md for legal info. +// Copyright 2016-2024 the openage authors. See copying.md for legal info. #pragma once -#include "declarations.h" #include "coord_nese.gen.h" #include "coord_neseup.gen.h" +#include "declarations.h" namespace openage { namespace coord { @@ -12,10 +12,14 @@ namespace coord { struct chunk_delta : CoordNeSeRelative { using CoordNeSeRelative::CoordNeSeRelative; + + tile_delta to_tile(tile_t tiles_per_chunk) const; }; struct chunk : CoordNeSeAbsolute { using CoordNeSeAbsolute::CoordNeSeAbsolute; + + tile to_tile(tile_t tiles_per_chunk) const; }; struct chunk3_delta : CoordNeSeUpRelative { diff --git a/libopenage/coord/coord.h.template b/libopenage/coord/coord.h.template index d4af555ea0..278b486ae4 100644 --- a/libopenage/coord/coord.h.template +++ b/libopenage/coord/coord.h.template @@ -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. #pragma once @@ -24,6 +24,9 @@ namespace coord { * * 'Absolute' and 'Relative' are the absolute and relative types of the * derived class (CRTP). + * + * If you change this class, remember to update the gdb pretty printers + * in etc/gdb_pretty/printers.py. */ template struct Coord${camelcase}Absolute { @@ -71,12 +74,8 @@ struct Coord${camelcase}Absolute { return static_cast(*this); } - constexpr bool operator ==(const Absolute &other) const { - return ${formatted_members("(this->{0} == other.{0})", join_with=" && ")}; - } - - constexpr bool operator !=(const Absolute &other) const { - return !(*this == other); + friend constexpr bool operator ==(const Absolute &lhs, const Absolute &rhs) { + return ${formatted_members("(lhs.{0} == rhs.{0})", join_with=" && ")}; } }; @@ -87,6 +86,9 @@ struct Coord${camelcase}Absolute { * * 'Absolute' and 'Relative' are the absolute and relative types of the * derived class (CRTP). + * + * If you change this class, remember to update the gdb pretty printers + * in etc/gdb_pretty/printers.py. */ template struct Coord${camelcase}Relative { @@ -161,12 +163,8 @@ struct Coord${camelcase}Relative { return static_cast(*this); } - constexpr bool operator ==(const Relative &other) const { - return ${formatted_members("(this->{0} == other.{0})", join_with=" && ")}; - } - - constexpr bool operator !=(const Relative &other) const { - return !(*this == other); + friend constexpr bool operator ==(const Relative &lhs, const Relative &rhs) { + return ${formatted_members("(lhs.{0} == rhs.{0})", join_with=" && ")}; } }; diff --git a/libopenage/coord/coordmanager.cpp b/libopenage/coord/coordmanager.cpp deleted file mode 100644 index 7d175b0702..0000000000 --- a/libopenage/coord/coordmanager.cpp +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2017-2018 the openage authors. See copying.md for legal info. - -#include "coordmanager.h" - -namespace openage { -namespace coord { - -// no implementation needed - -}} diff --git a/libopenage/coord/coordmanager.h b/libopenage/coord/coordmanager.h deleted file mode 100644 index 19195de83e..0000000000 --- a/libopenage/coord/coordmanager.h +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include "phys.h" -#include "pixel.h" -#include "tile.h" - -namespace openage { -namespace coord { - - -/** - * Holds all coordinate-related state and metadata. - * - * Among other things, this stores the camera positions. - * - * TODO: rename to CoordState! - */ -class CoordManager final { -public: - explicit CoordManager() {}; - - /** - * What's the current size of the viewport? - * (what's the coordinate of the top right pixel + (1, 1)?) - */ - viewport_delta viewport_size{800, 600}; - - /** - * What place (in Phys3) is the origin of the CamGame coordinate system looking at? - */ - phys3 camgame_phys{10, 10, 0}; - - /** - * Where in the viewport is the origin of the CamGame coordinate system? - * (this is usually the center of the viewport). - */ - viewport camgame_viewport{400, 300}; - - /** - * Where in the viewport is the origin of the CamHUD coordinate system? - * (this is usually the bottom left corner of the viewport) - */ - viewport camhud_viewport{0, 0}; - - /** - * The size of a terrain tile, in pixels. - * Rile diamonds are 96 pixels wide and 48 pixels high. - * The area of each tile is 96 * 48 * 0.5 square pixels. - */ - // TODO: dynamically get from nyan data - camgame_delta tile_size{96, 48}; -}; - -}} // namespace openage::coord diff --git a/libopenage/coord/declarations.h b/libopenage/coord/declarations.h index 05b89cb4fa..698b594aac 100644 --- a/libopenage/coord/declarations.h +++ b/libopenage/coord/declarations.h @@ -74,8 +74,5 @@ using term_t = int; struct term_delta; struct term; -// forward declaration of the coord manager -class CoordManager; - } // namespace coord } // namespace openage diff --git a/libopenage/coord/phys.cpp b/libopenage/coord/phys.cpp index bb5e89498d..250709e2b2 100644 --- a/libopenage/coord/phys.cpp +++ b/libopenage/coord/phys.cpp @@ -2,11 +2,9 @@ #include "phys.h" -#include "coord/coordmanager.h" #include "coord/pixel.h" #include "coord/scene.h" #include "coord/tile.h" -#include "terrain/terrain.h" #include "util/math.h" #include "util/math_constants.h" @@ -65,16 +63,6 @@ scene2 phys2::to_scene2() const { return scene2(this->ne, this->se); } - -[[deprecated]] phys3 phys2::to_phys3(const Terrain & /* terrain */, phys_t altitude) const { - // TODO: once terrain elevations have been implemented, - // query the terrain elevation at {ne, se}. - phys_t elevation = 0; - - return phys3{this->ne, this->se, elevation + altitude}; -} - - double phys3_delta::length() const { return math::hypot3(this->ne.to_double(), this->se.to_double(), this->up.to_double()); } @@ -108,22 +96,6 @@ phys_angle_t phys3_delta::to_angle(const coord::phys2_delta &other) const { return phys_angle_t::from_float(angle); } - -[[deprecated]] camgame_delta phys3_delta::to_camgame(const CoordManager &mgr) const { - // apply transformation matrix to phys3_delta, to get 'scaled': - // (ne) - // (x) = (+1 +1 +0) (se) - // (y) = (+1 -1 +1) (up) - phys_t x = phys_t{this->ne} * (+1) + this->se * (+1) + this->up * (+0); - phys_t y = phys_t{this->ne} * (+1) + this->se * (-1) + this->up * (+1); - - // add scaling (w/2 for x, h/2 for y) - return camgame_delta{ - static_cast((x * (mgr.tile_size.x / 2)).to_int()), - static_cast((y * (mgr.tile_size.y / 2)).to_int())}; -} - - tile3 phys3::to_tile3() const { return tile3{this->ne.to_int(), this->se.to_int(), this->up.to_int()}; } @@ -143,20 +115,4 @@ scene3 phys3::to_scene3() const { return scene3{this->ne, this->se, this->up}; } - -[[deprecated]] camgame phys3::to_camgame(const CoordManager &mgr) const { - return (*this - mgr.camgame_phys).to_camgame(mgr) + camgame{0, 0}; -} - - -[[deprecated]] viewport phys3::to_viewport(const CoordManager &mgr) const { - return this->to_camgame(mgr).to_viewport(mgr); -} - - -[[deprecated]] camhud phys3::to_camhud(const CoordManager &mgr) const { - return this->to_viewport(mgr).to_camhud(mgr); -} - - } // namespace openage::coord diff --git a/libopenage/coord/phys.h b/libopenage/coord/phys.h index 3a88072fff..7d933410f3 100644 --- a/libopenage/coord/phys.h +++ b/libopenage/coord/phys.h @@ -44,9 +44,6 @@ struct phys2 : CoordNeSeAbsolute { tile to_tile() const; phys3 to_phys3(phys_t up = 0) const; scene2 to_scene2() const; - - // TODO: Remove - [[deprecated]] phys3 to_phys3(const Terrain &terrain, phys_t altitude = 0) const; }; struct phys3_delta : CoordNeSeUpRelative { @@ -66,9 +63,6 @@ struct phys3_delta : CoordNeSeUpRelative { // TODO: This DOES NOT use fixed point math currently phys_angle_t to_angle(const coord::phys2_delta &other = {-1, 1}) const; - - // TODO: Remove - [[deprecated]] camgame_delta to_camgame(const CoordManager &mgr) const; }; struct phys3 : CoordNeSeUpAbsolute { @@ -79,11 +73,6 @@ struct phys3 : CoordNeSeUpAbsolute { tile to_tile() const; phys2 to_phys2() const; scene3 to_scene3() const; - - // TODO: Remove - [[deprecated]] camgame to_camgame(const CoordManager &mgr) const; - [[deprecated]] viewport to_viewport(const CoordManager &mgr) const; - [[deprecated]] camhud to_camhud(const CoordManager &mgr) const; }; diff --git a/libopenage/coord/pixel.cpp b/libopenage/coord/pixel.cpp index 24ad6a507f..a480cba2dc 100644 --- a/libopenage/coord/pixel.cpp +++ b/libopenage/coord/pixel.cpp @@ -1,87 +1,28 @@ -// Copyright 2016-2023 the openage authors. See copying.md for legal info. +// Copyright 2016-2024 the openage authors. See copying.md for legal info. #include "pixel.h" -#include "coord/coordmanager.h" #include "coord/phys.h" #include "renderer/camera/camera.h" +#include "renderer/camera/definitions.h" + namespace openage { namespace coord { - -phys3_delta camgame_delta::to_phys3(const CoordManager &mgr, phys_t up) const { - // apply scaling factor; w/2 for x, h/2 for y - phys_t x = phys_t::from_int(this->x) / static_cast(mgr.tile_size.x / 2); - phys_t y = phys_t::from_int(this->y) / static_cast(mgr.tile_size.y / 2); - - // apply transformation matrix to 'scaled', - // to get the relative phys3 position - // - // a camgame position represents a line in the 3D phys space - // a camgame delta of 0 might actually correlate to an arbitrarily - // large phys delta. - // we select one specific point on that line by explicitly specifying the - // 'up' value of the result. - // - // the transformation matrix is: - // - // (ne) = (+0.5 +0.5 +0.5) ( x) - // (se) = (+0.5 -0.5 -0.5) ( y) - // (up) = (+0.0 +0.0 +1.0) (up) - phys3_delta result; - result.ne = (x + y + up) / 2L; - result.se = (x - y - up) / 2L; - result.up = (up); - - return result; -} - - -viewport_delta camgame_delta::to_viewport() const { - return viewport_delta{this->x, this->y}; -} - - -viewport camgame::to_viewport(const CoordManager &mgr) const { - // reverse of viewport::to_camgame - return (*this - camgame{0, 0}).to_viewport() + mgr.camgame_viewport; -} - - -camhud camgame::to_camhud(const CoordManager &mgr) const { - return this->to_viewport(mgr).to_camhud(mgr); -} - - -phys3 camgame::to_phys3(const CoordManager &mgr, phys_t up) const { - return (*this - camgame{0, 0}).to_phys3(mgr, up) + mgr.camgame_phys; -} - - -tile camgame::to_tile(const CoordManager &mgr, phys_t up) const { - return this->to_phys3(mgr, up).to_tile(); -} - - viewport_delta camhud_delta::to_viewport() const { return viewport_delta{this->x, this->y}; } -viewport camhud::to_viewport(const CoordManager &mgr) const { +viewport camhud::to_viewport() const { // reverse of viewport::to_camhud - return (*this - camhud{0, 0}).to_viewport() + mgr.camhud_viewport; -} - - -[[deprecated]] phys3_delta viewport_delta::to_phys3(const CoordManager &mgr, phys_t up) const { - return this->to_camgame().to_phys3(mgr, up); + return (*this - camhud{0, 0}).to_viewport() + viewport{0, 0}; } -camhud viewport::to_camhud(const CoordManager &mgr) const { - return camhud{0, 0} + (*this - mgr.camhud_viewport).to_camhud(); +camhud viewport::to_camhud() const { + return camhud{0, 0} + (*this - viewport{0, 0}).to_camhud(); } @@ -92,44 +33,18 @@ Eigen::Vector2f viewport::to_ndc_space(const std::shared_ptrto_camgame(mgr).to_phys3(mgr, up); -} - - -[[deprecated]] tile viewport::to_tile(const CoordManager &mgr, phys_t up) const { - return this->to_camgame(mgr).to_tile(mgr, up); -} - - -viewport_delta input_delta::to_viewport(const CoordManager &mgr) const { +viewport_delta input_delta::to_viewport(const std::shared_ptr &camera) const { viewport_delta result; result.x = this->x; - result.y = mgr.viewport_size.y - this->y; + result.y = camera->get_viewport_size()[1] - this->y; return result; } -[[deprecated]] camgame_delta input_delta::to_camgame(const CoordManager &mgr) const { - return this->to_viewport(mgr).to_camgame(); -} - - -[[deprecated]] phys3_delta input_delta::to_phys3(const CoordManager &mgr, phys_t up) const { - return this->to_viewport(mgr).to_camgame().to_phys3(mgr, up); -} - - -viewport input::to_viewport(const CoordManager &mgr) const { - return viewport{0, 0} + (*this - input{0, 0}).to_viewport(mgr); +viewport input::to_viewport(const std::shared_ptr &camera) const { + return viewport{0, 0} + (*this - input{0, 0}).to_viewport(camera); } @@ -141,7 +56,7 @@ phys3 input::to_phys3(const std::shared_ptr &camera) c scene3 input::to_scene3(const std::shared_ptr &camera) const { // Use raycasting to find the position // Direction and origin point are fetched from the camera - auto cam_dir = renderer::camera::cam_direction; + auto cam_dir = renderer::camera::CAM_DIRECTION; auto ray_origin = camera->get_input_pos(*this); // xz plane that we want to intersect with @@ -155,16 +70,5 @@ scene3 input::to_scene3(const std::shared_ptr &camera) return scene3(-p_intersect.z(), p_intersect.x(), 0.0f); } - -[[deprecated]] phys3 input::to_phys3(const CoordManager &mgr, phys_t up) const { - return this->to_viewport(mgr).to_camgame(mgr).to_phys3(mgr, up); -} - - -[[deprecated]] camgame input::to_camgame(const CoordManager &mgr) const { - return this->to_viewport(mgr).to_camgame(mgr); -} - - } // namespace coord } // namespace openage diff --git a/libopenage/coord/pixel.h b/libopenage/coord/pixel.h index 5c42b92a4a..e97106224b 100644 --- a/libopenage/coord/pixel.h +++ b/libopenage/coord/pixel.h @@ -21,41 +21,6 @@ namespace coord { * See doc/code/coordinate-systems.md for more information. */ - -// TODO: Remove -struct [[deprecated]] camgame_delta : CoordXYRelative { - using CoordXYRelative::CoordXYRelative; - - /** - * There are infinite solutions to this conversion problem because - * a 2D coordinate is converted into a 3D coordinate. - * - * The user needs to manually give the 'up' value of the phys3 result. - */ - phys3_delta to_phys3(const CoordManager &mgr, phys_t up = phys_t::zero()) const; - viewport_delta to_viewport() const; -}; - - -// TODO: Remove -struct [[deprecated]] camgame : CoordXYAbsolute { - using CoordXYAbsolute::CoordXYAbsolute; - - /** - * See the comments for camgame_delta::to_phys3. - * - * TODO: Once we have terrain elevation, 'up' will not mean the absolute - * elevation, but instead the returned phys3 coordinate will be - * the intersection between the camgame line and the 3d terrain + - * up altitude. - */ - viewport to_viewport(const CoordManager &mgr) const; - camhud to_camhud(const CoordManager &mgr) const; - phys3 to_phys3(const CoordManager &mgr, phys_t up = phys_t::zero()) const; - tile to_tile(const CoordManager &mgr, phys_t up = phys_t::zero()) const; -}; - - struct camhud_delta : CoordXYRelative { using CoordXYRelative::CoordXYRelative; @@ -68,7 +33,7 @@ struct camhud : CoordXYAbsolute { using CoordXYAbsolute::CoordXYAbsolute; // coordinate conversions - viewport to_viewport(const CoordManager &mgr) const; + viewport to_viewport() const; }; @@ -79,12 +44,6 @@ struct viewport_delta : CoordXYRelative { camhud_delta to_camhud() const { return camhud_delta{this->x, this->y}; } - - // TODO: Remove - [[deprecated]] constexpr camgame_delta to_camgame() const { - return camgame_delta{this->x, this->y}; - } - [[deprecated]] phys3_delta to_phys3(const CoordManager &mgr, phys_t up) const; }; @@ -92,15 +51,10 @@ struct viewport : CoordXYAbsolute { using CoordXYAbsolute::CoordXYAbsolute; // coordinate conversions - camhud to_camhud(const CoordManager &mgr) const; + camhud to_camhud() const; // renderer conversions Eigen::Vector2f to_ndc_space(const std::shared_ptr &camera) const; - - // TODO: Remove - [[deprecated]] camgame to_camgame(const CoordManager &mgr) const; - [[deprecated]] phys3 to_phys3(const CoordManager &mgr, phys_t up = phys_t::zero()) const; - [[deprecated]] tile to_tile(const CoordManager &mgr, phys_t up = phys_t::zero()) const; }; @@ -108,11 +62,7 @@ struct input_delta : CoordXYRelative { using CoordXYRelative::CoordXYRelative; // coordinate conversions - viewport_delta to_viewport(const CoordManager &mgr) const; - - // TODO: Remove - [[deprecated]] camgame_delta to_camgame(const CoordManager &mgr) const; - [[deprecated]] phys3_delta to_phys3(const CoordManager &mgr, phys_t up = phys_t::zero()) const; + viewport_delta to_viewport(const std::shared_ptr &camera) const; }; @@ -120,13 +70,9 @@ struct input : CoordXYAbsolute { using CoordXYAbsolute::CoordXYAbsolute; // coordinate conversions - viewport to_viewport(const CoordManager &mgr) const; + viewport to_viewport(const std::shared_ptr &camera) const; phys3 to_phys3(const std::shared_ptr &camera) const; scene3 to_scene3(const std::shared_ptr &camera) const; - - // TODO: Remove - [[deprecated]] phys3 to_phys3(const CoordManager &mgr, phys_t up = phys_t::zero()) const; - [[deprecated]] camgame to_camgame(const CoordManager &mgr) const; }; diff --git a/libopenage/coord/scene.cpp b/libopenage/coord/scene.cpp index d34519bfe4..45e262d519 100644 --- a/libopenage/coord/scene.cpp +++ b/libopenage/coord/scene.cpp @@ -4,7 +4,6 @@ #include -#include "coord/coordmanager.h" #include "coord/pixel.h" #include "coord/tile.h" #include "util/math.h" @@ -108,7 +107,7 @@ Eigen::Vector3f scene3_delta::to_world_space() const { } float scene3_delta::to_angle(const coord::scene2_delta &other) const { - return this->to_scene2().to_angle(); + return this->to_scene2().to_angle(other); } scene2 scene3::to_scene2() const { diff --git a/libopenage/coord/tile.cpp b/libopenage/coord/tile.cpp index dbd68a0d5a..e447819799 100644 --- a/libopenage/coord/tile.cpp +++ b/libopenage/coord/tile.cpp @@ -1,27 +1,47 @@ -// Copyright 2016-2023 the openage authors. See copying.md for legal info. +// Copyright 2016-2024 the openage authors. See copying.md for legal info. #include "tile.h" -#include "../terrain/terrain.h" -#include "coordmanager.h" +#include "chunk.h" +#include "phys.h" + namespace openage::coord { +phys2_delta tile_delta::to_phys2() const { + return phys2_delta{phys2::elem_t::from_int(this->ne), + phys2::elem_t::from_int(this->se)}; +} -tile3 tile::to_tile3(tile_t up) const { - return tile3(this->ne, this->se, up); +phys3_delta tile_delta::to_phys3(tile_t up) const { + return phys3_delta{phys3::elem_t::from_int(this->ne), + phys3::elem_t::from_int(this->se), + phys3::elem_t::from_int(up)}; } +tile3 tile::to_tile3(tile_t up) const { + return tile3{this->ne, this->se, up}; +} phys2 tile::to_phys2() const { - return phys2{phys3::elem_t::from_int(this->ne), phys3::elem_t::from_int(this->se)}; + return phys2{phys3::elem_t::from_int(this->ne), + phys3::elem_t::from_int(this->se)}; } - phys3 tile::to_phys3(tile_t up) const { return this->to_tile3(up).to_phys3(); } +phys2 tile::to_phys2_center() const { + return phys2{phys3::elem_t::from_int(this->ne) + 0.5, + phys3::elem_t::from_int(this->se) + 0.5}; +} + +phys3 tile::to_phys3_center(tile_t up) const { + return phys3{phys3::elem_t::from_int(this->ne) + 0.5, + phys3::elem_t::from_int(this->se) + 0.5, + phys3::elem_t::from_int(up)}; +} chunk tile::to_chunk() const { return chunk{ @@ -29,53 +49,45 @@ chunk tile::to_chunk() const { static_cast(util::div(this->se, tiles_per_chunk))}; } - tile_delta tile::get_pos_on_chunk() const { return tile_delta{ util::mod(this->ne, tiles_per_chunk), util::mod(this->se, tiles_per_chunk)}; } - -[[deprecated]] tile3 tile::to_tile3(const Terrain & /*terrain*/, tile_t altitude) const { - // TODO: once terrain elevations have been implemented, - // query the terrain elevation at {ne, se}. - tile_t elevation = 0; - - return tile3{this->ne, this->se, elevation + altitude}; -} - - -[[deprecated]] phys3 tile::to_phys3(const Terrain &terrain, tile_t altitude) const { - return this->to_tile3(terrain, altitude).to_phys3(); +tile_delta tile3_delta::to_tile() const { + return tile_delta{this->ne, this->se}; } - -[[deprecated]] camgame tile::to_camgame(const CoordManager &mgr, - const Terrain &terrain, - tile_t altitude) const { - return this->to_phys3(terrain, altitude).to_camgame(mgr); +phys3_delta tile3_delta::to_phys3() const { + return phys3_delta{phys3::elem_t::from_int(this->ne), + phys3::elem_t::from_int(this->se), + phys3::elem_t::from_int(up)}; } - -[[deprecated]] viewport tile::to_viewport(const CoordManager &mgr, - const Terrain &terrain, - tile_t altitude) const { - return this->to_camgame(mgr, terrain, altitude).to_viewport(mgr); +tile tile3::to_tile() const { + return tile{this->ne, this->se}; } - phys2 tile3::to_phys2() const { return this->to_tile().to_phys2(); } - phys3 tile3::to_phys3() const { - return phys3{ - phys3::elem_t::from_int(this->ne), - phys3::elem_t::from_int(this->se), - phys3::elem_t::from_int(this->up)}; + return phys3{phys3::elem_t::from_int(this->ne), + phys3::elem_t::from_int(this->se), + phys3::elem_t::from_int(this->up)}; } +phys2 tile3::to_phys2_center() const { + return phys2{phys3::elem_t::from_int(this->ne) + 0.5, + phys3::elem_t::from_int(this->se) + 0.5}; +} + +phys3 tile3::to_phys3_center() const { + return phys3{phys3::elem_t::from_int(this->ne) + 0.5, + phys3::elem_t::from_int(this->se) + 0.5, + phys3::elem_t::from_int(this->up)}; +} } // namespace openage::coord diff --git a/libopenage/coord/tile.h b/libopenage/coord/tile.h index 4570564d32..ec685b8002 100644 --- a/libopenage/coord/tile.h +++ b/libopenage/coord/tile.h @@ -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. #pragma once @@ -9,14 +9,8 @@ #include "declarations.h" namespace openage { - -class Terrain; - namespace coord { -class CoordManager; - - /* * Gameworld tile-related coordinate systems. * See doc/code/coordinate-systems.md for more information. @@ -25,6 +19,10 @@ class CoordManager; struct tile_delta : CoordNeSeRelative { using CoordNeSeRelative::CoordNeSeRelative; + + // coordinate conversions + phys2_delta to_phys2() const; + phys3_delta to_phys3(tile_t up = 0) const; }; struct tile : CoordNeSeAbsolute { @@ -37,16 +35,14 @@ struct tile : CoordNeSeAbsolute { * elevation. */ tile3 to_tile3(tile_t up = 0) const; + phys2 to_phys2() const; phys3 to_phys3(tile_t up = 0) const; + phys2 to_phys2_center() const; + phys3 to_phys3_center(tile_t up = 0) const; + chunk to_chunk() const; tile_delta get_pos_on_chunk() const; - - // TODO: Remove - [[deprecated]] tile3 to_tile3(const Terrain &terrain, tile_t altitude = 0) const; - [[deprecated]] phys3 to_phys3(const Terrain &terrain, tile_t altitude = 0) const; - [[deprecated]] camgame to_camgame(const CoordManager &mgr, const Terrain &terrain, tile_t altitude = 0) const; - [[deprecated]] viewport to_viewport(const CoordManager &mgr, const Terrain &terrain, tile_t altitude = 0) const; }; struct tile3_delta : CoordNeSeUpRelative { @@ -54,9 +50,8 @@ struct tile3_delta : CoordNeSeUpRelative { // coordinate conversions // simply discards the UP component of the coordinate delta. - constexpr tile_delta to_tile() const { - return tile_delta{this->ne, this->se}; - } + tile_delta to_tile() const; + phys3_delta to_phys3() const; }; struct tile3 : CoordNeSeUpAbsolute { @@ -64,11 +59,12 @@ struct tile3 : CoordNeSeUpAbsolute { // coordinate conversions // simply discards the UP component of the coordinate. - constexpr tile to_tile() const { - return tile{this->ne, this->se}; - } + tile to_tile() const; + phys2 to_phys2() const; phys3 to_phys3() const; + phys2 to_phys2_center() const; + phys3 to_phys3_center() const; }; 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 8c56586637..de5c14201d 100644 --- a/libopenage/curve/base_curve.h +++ b/libopenage/curve/base_curve.h @@ -1,10 +1,9 @@ -// 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 #include -#include #include #include #include @@ -40,7 +39,7 @@ class BaseCurve : public event::EventEntity { _id{id}, _idstr{idstr}, loop{loop}, - last_element{this->container.begin()} {} + last_element{this->container.size()} {} virtual ~BaseCurve() = default; @@ -48,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; @@ -104,52 +104,52 @@ class BaseCurve : public event::EventEntity { void check_integrity() const; /** - * Copy keyframes from another curve to this curve. After syncing, the two curves - * are guaranteed to return the same values for t >= start. - * - * The operation may insert new keyframes at \p start on the curve. - * - * @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. - */ + * Copy keyframes from another curve to this curve. After syncing, the two curves + * are guaranteed to return the same values for t >= start. + * + * The operation may insert new keyframes at \p start on the curve. + * + * @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 BaseCurve &other, - const time::time_t &start = std::numeric_limits::min()); + const time::time_t &start = time::TIME_MIN); /** - * Copy keyframes from another curve (with a different element type) to this curve. - * After syncing, the two curves are guaranteed to return the same values - * for t >= start. - * - * The operation may insert new keyframes at \p start on the curve. - * - * @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. - */ + * Copy keyframes from another curve (with a different element type) to this curve. + * After syncing, the two curves are guaranteed to return the same values + * for t >= start. + * + * The operation may insert new keyframes at \p start on the curve. + * + * @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. + */ template void sync(const BaseCurve &other, const std::function &converter, - const time::time_t &start = std::numeric_limits::min()); + const time::time_t &start = time::TIME_MIN); /** - * Get the identifier of this curve. - * - * @return Identifier. - */ + * 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. - */ + * 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()); @@ -163,10 +163,10 @@ class BaseCurve : public event::EventEntity { std::string str() const; /** - * Get the container containing all keyframes of this curve. - * - * @return Keyframe container. - */ + * Get the container containing all keyframes of this curve. + * + * @return Keyframe container. + */ const KeyframeContainer &get_container() const { return this->container; } @@ -193,9 +193,9 @@ class BaseCurve : public event::EventEntity { const std::shared_ptr loop; /** - * Cache the iterator for quickly finding the last accessed element (usually the end) + * Cache the index of the last accessed element (usually the end). */ - mutable typename KeyframeContainer::iterator last_element; + mutable typename KeyframeContainer::elem_ptr last_element; }; @@ -204,7 +204,7 @@ void BaseCurve::set_last(const time::time_t &at, const T &value) { auto hint = this->container.last(at, this->last_element); // erase max one same-time value - if (hint->time == at) { + if (this->container.get(hint).time() == at) { hint--; } @@ -221,7 +221,7 @@ template void BaseCurve::set_insert(const time::time_t &at, const T &value) { auto hint = this->container.insert_after(at, value, this->last_element); // check if this is now the final keyframe - if (hint->time > this->last_element->time) { + if (this->container.get(hint).time() > this->container.get(this->last_element).time()) { this->last_element = hint; } this->changes(at); @@ -244,16 +244,18 @@ void BaseCurve::erase(const time::time_t &at) { template std::pair BaseCurve::frame(const time::time_t &time) const { - auto e = this->container.last(time, this->container.end()); - return std::make_pair(e->time, e->value); + auto e = this->container.last(time, this->container.size()); + auto elem = this->container.get(e); + return elem.as_pair(); } template std::pair BaseCurve::next_frame(const time::time_t &time) const { - auto e = this->container.last(time, this->container.end()); + auto e = this->container.last(time, this->container.size()); e++; - return std::make_pair(e->time, e->value); + auto elem = this->container.get(e); + return elem.as_pair(); } template @@ -261,7 +263,7 @@ std::string BaseCurve::str() const { std::stringstream ss; ss << "Curve[" << this->idstr() << "]{" << std::endl; for (const auto &keyframe : this->container) { - ss << " " << keyframe.time << ": " << keyframe.value << "," << std::endl; + ss << " " << keyframe.time() << ": " << keyframe.val() << "," << std::endl; } ss << "}"; @@ -270,12 +272,12 @@ std::string BaseCurve::str() const { template void BaseCurve::check_integrity() const { - time::time_t last_time = std::numeric_limits::min(); + time::time_t last_time = time::TIME_MIN; for (const auto &keyframe : this->container) { - if (keyframe.time < last_time) { + if (keyframe.time() < last_time) { throw Error{MSG(err) << "curve is broken after t=" << last_time << ": " << this->str()}; } - last_time = keyframe.time; + last_time = keyframe.time(); } } 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 93% rename from libopenage/curve/iterator.h rename to libopenage/curve/container/iterator.h index 2500e083cd..7a4fb82d6b 100644 --- a/libopenage/curve/iterator.h +++ b/libopenage/curve/container/iterator.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 @@ -33,8 +33,8 @@ class CurveIterator { explicit CurveIterator(const container_t *c) : base{}, container{c}, - from{-std::numeric_limits::max()}, - to{+std::numeric_limits::max()} {} + from{-time::TIME_MAX}, + to{+time::TIME_MAX} {} protected: /** @@ -98,7 +98,7 @@ class CurveIterator { } /** - * Access the underlying + * Access the underlying iterator. */ const iterator_t &get_base() const { return base; 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 84% rename from libopenage/curve/map.h rename to libopenage/curve/container/map.h index 3a8374a128..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; @@ -48,10 +37,10 @@ class UnorderedMap { at(const time::time_t &, const key_t &) const; MapFilterIterator - begin(const time::time_t &e = std::numeric_limits::max()) const; + begin(const time::time_t &e = time::TIME_MAX) const; MapFilterIterator - end(const time::time_t &e = std::numeric_limits::max()) const; + end(const time::time_t &e = time::TIME_MAX) const; MapFilterIterator insert(const time::time_t &birth, const key_t &, const val_t &); @@ -95,12 +84,12 @@ 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, time, - std::numeric_limits::max()); + time::TIME_MAX); } else { return {}; @@ -114,7 +103,7 @@ UnorderedMap::begin(const time::time_t &time) const { this->container.begin(), this, time, - std::numeric_limits::max()); + time::TIME_MAX); } template @@ -123,7 +112,7 @@ UnorderedMap::end(const time::time_t &time) const { return MapFilterIterator>( this->container.end(), this, - -std::numeric_limits::max(), + -time::TIME_MAX, time); } @@ -149,7 +138,7 @@ UnorderedMap::insert(const time::time_t &alive, const val_t &value) { return this->insert( alive, - std::numeric_limits::max(), + time::TIME_MAX, key, value); } @@ -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 81% rename from libopenage/curve/map_filter_iterator.h rename to libopenage/curve/container/map_filter_iterator.h index e514330f67..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" @@ -32,13 +32,11 @@ class MapFilterIterator : public CurveIterator { const time::time_t &to) : CurveIterator(base, container, from, to) {} - MapFilterIterator(const MapFilterIterator &) = default; - 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); } /** @@ -46,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/container/queue.h b/libopenage/curve/container/queue.h new file mode 100644 index 0000000000..fb32a53cbb --- /dev/null +++ b/libopenage/curve/container/queue.h @@ -0,0 +1,446 @@ +// Copyright 2017-2025 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "error/error.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" + + +namespace openage { +namespace event { +class EventLoop; +} + +namespace curve { + +/** + * A container that manages events on a timeline. Every event has exactly one + * time it will happen. + * This container can be used to store interactions + */ +template +class Queue : public event::EventEntity { +public: + /** + * The underlaying container type. + */ + using container_t = typename std::vector>; + + /** + * The index type to access elements in the container + */ + using elem_ptr = typename container_t::size_type; + + /** + * The iterator type to access elements in the container + */ + using const_iterator = typename container_t::const_iterator; + using iterator = typename container_t::iterator; + + Queue(const std::shared_ptr &loop, + size_t id, + const std::string &idstr = "") : + EventEntity{loop}, + _id{id}, + _idstr{idstr}, + last_change{time::TIME_ZERO}, + front_start{0} {} + + // prevent accidental copy of queue + Queue(const Queue &) = delete; + + // Reading Access + + /** + * Get the first element inserted at t <= time. + * + * Ignores dead elements. + * + * @param time The time to get the element at. + * + * @return Queue element. + */ + const T &front(const time::time_t &time) const; + + /** + * Check if the queue is empty at the given time (no elements alive + * before t <= time). + * + * Ignores dead elements. + * + * @param time The time to check at. + * + * @return true if the queue is empty, false otherwise. + */ + bool empty(const time::time_t &time) const; + + // Modifying access + + /** + * Get the first element inserted at t <= time and erase it from the + * queue. + * + * Ignores dead elements. + * + * @param time The time to get the element at. + * @param value Queue element. + */ + const T &pop_front(const time::time_t &time); + + /** + * Get an iterator to the first element inserted at t >= time. + * + * Does not ignore dead elements. + * + * @param time The time to get the element at (default: \p time::TIME_MIN ). + * + * @return Iterator to the first element. + */ + QueueFilterIterator> begin(const time::time_t &time = time::TIME_MIN) const; + + /** + * Get an iterator to the last element in the queue at the given time. + * + * Does not ignore dead elements. + * + * @param t The time to get the element at (default: \p time::TIME_MAX ). + * + * @return Iterator to the last element. + */ + QueueFilterIterator> end(const time::time_t &time = time::TIME_MAX) const; + + /** + * Get an iterator to elements that are in the queue between two time frames. + * + * Does not ignore dead elements. + * + * @param begin Start time (default: \p time::TIME_MIN ). + * @param end End time (default: \p time::TIME_MAX ). + * + * @return Iterator to the first element in the time frame. + */ + QueueFilterIterator> between( + const time::time_t &begin = time::TIME_MIN, + const time::time_t &end = time::TIME_MAX) const; + + /** + * Erase an element from the queue. + * + * Does not ignore dead elements. + * + * @param it The iterator to the element to erase. + */ + void erase(const CurveIterator> &it); + + /** + * Insert a new element into the queue. + * + * @param time The time to insert at. + * @param e The element to insert. + * + * @return Iterator to the inserted element. + */ + QueueFilterIterator> insert(const time::time_t &time, const T &e); + + /** + * Erase all elements at t <= time. + * + * @param time The time to clear at. + */ + void clear(const time::time_t &time); + + /** + * Print the queue to stdout. + */ + void dump() { + for (auto i : container) { + std::cout << i.value << " at " << i.alive() << std::endl; + } + } + + /** + * 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; + } + +private: + /** + * Kill an element from the queue at the given time. + * + * The element is set to dead at the given time and is not accessible + * for pops with t > time. + * + * @param time The time to kill at. + * @param at The index of the element to kill. + */ + void kill(const time::time_t &time, elem_ptr at); + + /** + * Get the first alive element inserted at t <= time. + * + * @param time The time to get the element at. + * + * @return Index of the first alive element or end() if no such element exists. + */ + elem_ptr first_alive(const time::time_t &time) const; + + /** + * Identifier for the container + */ + const size_t _id; + + /** + * Human-readable identifier for the container + */ + const std::string _idstr; + + /** + * The container that stores the queue elements. + */ + container_t container; + + /** + * Simulation time of the last modifying change to the queue. + */ + time::time_t last_change; + + /** + * Caches the search start position for the next front() call. + * + * All positions before the index are guaranteed to be dead at t >= last_change. + */ + elem_ptr front_start; +}; + + +template +typename Queue::elem_ptr Queue::first_alive(const time::time_t &time) const { + elem_ptr hint = 0; + + // check if the access is later than the last change + if (this->last_change <= time) { + // start searching from the last front position + hint = this->front_start; + } + // else search from the beginning + + // Iterate until we find an alive element + while (hint != this->container.size() + and this->container.at(hint).alive() <= time) { + if (this->container.at(hint).dead() > time) { + return hint; + } + ++hint; + } + + return this->container.size(); +} + + +template +const T &Queue::front(const time::time_t &time) const { + elem_ptr at = this->first_alive(time); + ENSURE(at < this->container.size(), + "Tried accessing front at " << time << " but index " << at << " is invalid. " + << "The queue may be empty." + << "(last_change: " << this->last_change + << ", front_start: " << this->front_start + << ", container size: " << this->container.size() + << ")"); + + return this->container.at(at).value(); +} + + +template +const T &Queue::pop_front(const time::time_t &time) { + elem_ptr at = this->first_alive(time); + ENSURE(at < this->container.size(), + "Tried accessing front at " << time << " but index " << at << " is invalid. " + << "The queue may be empty." + << "(last_change: " << this->last_change + << ", front_start: " << this->front_start + << ", container size: " << this->container.size() + << ")"); + + // kill the element at time t + this->kill(time, at); + + // cache the search start position for the next front() call + // for pop time t, there should be no more elements alive before t + // so we can advance the front to the next element + this->last_change = time; + this->front_start = at + 1; + + this->changes(time); + + return this->container.at(at).value(); +} + + +template +bool Queue::empty(const time::time_t &time) const { + if (this->container.empty()) { + return true; + } + + return this->first_alive(time) == this->container.size(); +} + + +template +QueueFilterIterator> Queue::begin(const time::time_t &t) const { + for (auto it = this->container.begin(); it != this->container.end(); ++it) { + if (it->alive() >= t) { + return QueueFilterIterator>( + it, + this, + t, + time::TIME_MAX); + } + } + + return this->end(t); +} + + +template +QueueFilterIterator> Queue::end(const time::time_t &t) const { + return QueueFilterIterator>( + container.end(), + this, + t, + time::TIME_MAX); +} + + +template +QueueFilterIterator> Queue::between(const time::time_t &begin, + const time::time_t &end) const { + auto it = QueueFilterIterator>( + container.begin(), + this, + begin, + end); + if (not it.valid()) { + ++it; + } + return it; +} + + +template +void Queue::erase(const CurveIterator> &it) { + container.erase(it.get_base()); +} + + +template +void Queue::kill(const time::time_t &time, + elem_ptr at) { + this->container[at].set_dead(time); +} + + +template +QueueFilterIterator> Queue::insert(const time::time_t &time, + const T &e) { + elem_ptr at = this->container.size(); + while (at > 0) { + --at; + if (this->container.at(at).alive() <= time) { + ++at; + break; + } + } + + // Get the iterator to the insertion point + iterator insertion_point = std::next(this->container.begin(), at); + 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 + + // cache the insertion time + this->last_change = time; + + // if the new element is inserted before the current front element + // cache it as the new front element + if (at < this->front_start) { + this->front_start = at; + } + + auto ct = QueueFilterIterator>( + insertion_point, + this, + time, + time::TIME_MAX); + + if (!ct.valid()) { + ++ct; + } + + this->changes(time); + + return ct; +} + + +template +void Queue::clear(const time::time_t &time) { + elem_ptr at = this->first_alive(time); + + // no elements alive at t <= time + // so we don't have any changes + if (at == this->container.size()) { + return; + } + + // erase all elements alive at t <= time + while (this->container.at(at).alive() <= time + and at != this->container.size()) { + if (this->container.at(at).dead() > time) { + this->container[at].set_dead(time); + } + ++at; + } + + // cache the search start position for the next front() call + this->last_change = time; + this->front_start = at; + + this->changes(time); +} + + +} // namespace curve +} // namespace openage 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 85% rename from libopenage/curve/queue_filter_iterator.h rename to libopenage/curve/container/queue_filter_iterator.h index b8ecf77036..6b2fa471f2 100644 --- a/libopenage/curve/queue_filter_iterator.h +++ b/libopenage/curve/container/queue_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" @@ -33,14 +33,14 @@ class QueueFilterIterator : public CurveIteratorcontainer->end().get_base() != this->get_base()) { - return (this->get_base()->time() >= this->from and this->get_base()->time() < this->to); + return (this->get_base()->alive() >= this->from and this->get_base()->alive() < this->to); } return false; } const val_t &value() const override { const auto &a = *this->get_base(); - return a.value; + return a.value(); } }; diff --git a/libopenage/curve/continuous.h b/libopenage/curve/continuous.h index 3eb7170ddb..0cf438f237 100644 --- a/libopenage/curve/continuous.h +++ b/libopenage/curve/continuous.h @@ -1,4 +1,4 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once @@ -48,7 +48,7 @@ void Continuous::set_last(const time::time_t &at, const T &value) { auto hint = this->container.last(at, this->last_element); // erase all same-time entries - while (hint->time == at) { + while (this->container.get(hint).time() == at) { hint--; } diff --git a/libopenage/curve/discrete.h b/libopenage/curve/discrete.h index cdcb3daa31..b9f9b6b00c 100644 --- a/libopenage/curve/discrete.h +++ b/libopenage/curve/discrete.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 @@ -55,7 +55,7 @@ template T Discrete::get(const time::time_t &time) const { auto e = this->container.last(time, this->last_element); this->last_element = e; // TODO if Caching? - return e->value; + return this->container.get(e).val(); } @@ -78,7 +78,9 @@ template std::pair Discrete::get_time(const time::time_t &time) const { auto e = this->container.last(time, this->last_element); this->last_element = e; - return std::make_pair(e->time, e->value); + + auto elem = this->container.get(e); + return elem.as_pair(); } @@ -89,12 +91,13 @@ std::optional> Discrete::get_previous(const time:: // if we're not at the container head // go back one entry. - if (e == std::begin(this->container)) { + if (e == 0) { return {}; } e--; - return std::make_pair(e->time, e->value); + auto elem = this->container.get(e); + return elem.as_pair(); } } // namespace openage::curve diff --git a/libopenage/curve/discrete_mod.h b/libopenage/curve/discrete_mod.h index a641541688..953939f975 100644 --- a/libopenage/curve/discrete_mod.h +++ b/libopenage/curve/discrete_mod.h @@ -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. #pragma once @@ -65,8 +65,8 @@ class DiscreteMod : public Discrete { private: /** - * Length of the time interval of this curve (time between first and last keyframe). - */ + * Length of the time interval of this curve (time between first and last keyframe). + */ time::time_t time_length; }; @@ -93,7 +93,7 @@ void DiscreteMod::erase(const time::time_t &at) { BaseCurve::erase(at); if (this->time_length == at) { - this->time_length = this->last_element->time; + this->time_length = this->container.get(this->container.size() - 1).time(); } } diff --git a/libopenage/curve/interpolated.h b/libopenage/curve/interpolated.h index 49a189a124..564ba1d0af 100644 --- a/libopenage/curve/interpolated.h +++ b/libopenage/curve/interpolated.h @@ -1,4 +1,4 @@ -// Copyright 2019-2023 the openage authors. See copying.md for legal info. +// Copyright 2019-2024 the openage authors. See copying.md for legal info. #pragma once @@ -40,7 +40,7 @@ class Interpolated : public BaseCurve { template T Interpolated::get(const time::time_t &time) const { - const auto &e = this->container.last(time, this->last_element); + const auto e = this->container.last(time, this->last_element); this->last_element = e; auto nxt = e; @@ -48,21 +48,21 @@ T Interpolated::get(const time::time_t &time) const { time::time_t interval = 0; - auto offset = time - e->time; + auto offset = time - this->container.get(e).time(); - if (nxt != this->container.end()) { - interval = nxt->time - e->time; + if (nxt != this->container.size()) { + interval = this->container.get(nxt).time() - this->container.get(e).time(); } // here, offset > interval will never hold. // otherwise the underlying storage is broken. // If the next element is at the same time, just return the value of this one. - if (nxt == this->container.end() // use the last curve value + if (nxt == this->container.size() // use the last curve value || offset == 0 // values equal -> don't need to interpolate || interval == 0) { // values at the same time -> division-by-zero-error - return e->value; + return this->container.get(e).val(); } else { // Interpolation between time(now) and time(next) that has elapsed @@ -72,7 +72,8 @@ T Interpolated::get(const time::time_t &time) const { // TODO: nxt->value - e->value will produce wrong results if // the nxt->value < e->value and curve element type is unsigned // Example: nxt = 2, e = 4; type = uint8_t ==> 2 - 4 = 254 - return e->value + (nxt->value - e->value) * elapsed_frac; + auto diff_value = (this->container.get(nxt).val() - this->container.get(e).val()) * elapsed_frac; + return this->container.get(e).val() + diff_value; } } diff --git a/libopenage/curve/keyframe.h b/libopenage/curve/keyframe.h index d6b90af4b5..cd2ebf6ced 100644 --- a/libopenage/curve/keyframe.h +++ b/libopenage/curve/keyframe.h @@ -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. #pragma once @@ -11,6 +11,9 @@ namespace openage::curve { /** * A element of the curvecontainer. This is especially used to keep track of * the value-timing. + * + * If you change this class, remember to update the gdb pretty printers + * in etc/gdb_pretty/printers.py. */ template class Keyframe { @@ -24,17 +27,55 @@ class Keyframe { * New, default-constructed element at the given time */ Keyframe(const time::time_t &time) : - time{time} {} + timestamp{time} {} /** * New element fron time and value */ Keyframe(const time::time_t &time, const T &value) : - time{time}, - value{value} {} + value{value}, + timestamp{time} {} - const time::time_t time = std::numeric_limits::min(); + /** + * Get the time of this keyframe. + * + * @return Keyframe time. + */ + const time::time_t &time() const { + return this->timestamp; + } + + /** + * Get the value of this keyframe. + * + * @return Keyframe value. + */ + const T &val() const { + 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. + * + * Can be modified by the curve if necessary. + */ T value = T{}; + +private: + /** + * Time of the keyframe. + */ + time::time_t timestamp = time::TIME_MIN; }; } // namespace openage::curve diff --git a/libopenage/curve/keyframe_container.h b/libopenage/curve/keyframe_container.h index 1d7079498b..8434942058 100644 --- a/libopenage/curve/keyframe_container.h +++ b/libopenage/curve/keyframe_container.h @@ -1,4 +1,4 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once @@ -34,38 +34,39 @@ class KeyframeContainer { /** * The underlaying container type. - * - * The most important property of this container is the iterator validity on - * insert and remove. */ - using container_t = std::list; + using container_t = std::vector; + + /** + * The index type to access elements in the container + */ + using elem_ptr = typename container_t::size_type; /** * The iterator type to access elements in the container */ using iterator = typename container_t::const_iterator; - using const_iterator = typename container_t::const_iterator; /** - * Create a new container. - * - * Inserts a default element with value \p T() at \p time = -INF to ensure - * that accessing the container always returns an element. - * - * TODO: need the datamanger for change management - */ + * Create a new container. + * + * Inserts a default element with value \p T() at \p time = -INF to ensure + * that accessing the container always returns an element. + * + * TODO: need the datamanger for change management + */ KeyframeContainer(); /** - * Create a new container. - * - * Inserts a default element at \p time = -INF to ensure - * that accessing the container always returns an element. - * - * @param defaultval Value of default element at -INF. - * - * TODO: need the datamanger for change management - */ + * Create a new container. + * + * Inserts a default element at \p time = -INF to ensure + * that accessing the container always returns an element. + * + * @param defaultval Value of default element at -INF. + * + * TODO: need the datamanger for change management + */ KeyframeContainer(const T &defaultval); /** @@ -75,12 +76,16 @@ class KeyframeContainer { */ size_t size() const; + const keyframe_t &get(const elem_ptr &idx) const { + return this->container.at(idx); + } + /** * Get the last element in the curve which is at or before the given time. * (i.e. elem->time <= time). Given a hint where to start the search. */ - iterator last(const time::time_t &time, - const iterator &hint) const; + elem_ptr last(const time::time_t &time, + const elem_ptr &hint) const; /** * Get the last element with elem->time <= time, without a hint where to start @@ -90,23 +95,23 @@ class KeyframeContainer { * no chance for you to have a hint (or the container is known to be nearly * empty) */ - iterator last(const time::time_t &time) const { - return this->last(time, std::end(this->container)); + elem_ptr last(const time::time_t &time) const { + return this->last(time, this->container.size()); } /** * Get the last element in the curve which is before the given time. * (i.e. elem->time < time). Given a hint where to start the search. */ - iterator last_before(const time::time_t &time, - const iterator &hint) const; + elem_ptr last_before(const time::time_t &time, + const elem_ptr &hint) const; /** * Get the last element with elem->time < time, without a hint where to start * searching. */ - iterator last_before(const time::time_t &time) const { - return this->last_before(time, std::end(this->container)); + elem_ptr last_before(const time::time_t &time) const { + return this->last_before(time, this->container.size()); } /** @@ -116,8 +121,8 @@ class KeyframeContainer { * This function is not recommended for use, whenever possible, keep a hint * to insert the data. */ - iterator insert_before(const keyframe_t &value) { - return this->insert_before(value, std::end(this->container)); + elem_ptr insert_before(const keyframe_t &value) { + return this->insert_before(value, this->container.size()); } /** @@ -127,7 +132,7 @@ class KeyframeContainer { * history size. If there is a keyframe with identical time, this will * insert the new keyframe before the old one. */ - iterator insert_before(const keyframe_t &value, const iterator &hint); + elem_ptr insert_before(const keyframe_t &value, const elem_ptr &hint); /** * Create and insert a new element without submitting a hint. The search is @@ -135,8 +140,8 @@ class KeyframeContainer { * discouraged, use it only, if your really do not have the possibility to * get a hint. */ - iterator insert_before(const time::time_t &time, const T &value) { - return this->insert_before(keyframe_t(time, value), std::end(this->container)); + elem_ptr insert_before(const time::time_t &time, const T &value) { + return this->insert_before(keyframe_t(time, value), this->container.size()); } /** @@ -144,7 +149,7 @@ class KeyframeContainer { * If there is a value with identical time, this will insert the new value * before the old one. */ - iterator insert_before(const time::time_t &time, const T &value, const iterator &hint) { + elem_ptr insert_before(const time::time_t &time, const T &value, const elem_ptr &hint) { return this->insert_before(keyframe_t(time, value), hint); } @@ -155,8 +160,8 @@ class KeyframeContainer { * `overwrite_all` == true -> overwrite all same-time elements. * `overwrite_all` == false -> overwrite the last of the time-conflict elements. */ - iterator insert_overwrite(const keyframe_t &value, - const iterator &hint, + elem_ptr insert_overwrite(const keyframe_t &value, + const elem_ptr &hint, bool overwrite_all = false); /** @@ -165,9 +170,9 @@ class KeyframeContainer { * from the end of the data. The use of this function is discouraged, use it * only, if your really do not have the possibility to get a hint. */ - iterator insert_overwrite(const time::time_t &time, const T &value) { + elem_ptr insert_overwrite(const time::time_t &time, const T &value) { return this->insert_overwrite(keyframe_t(time, value), - std::end(this->container)); + this->container.size()); } /** @@ -177,9 +182,9 @@ class KeyframeContainer { * Provide a insertion hint to abbreviate the search for the * insertion point. */ - iterator insert_overwrite(const time::time_t &time, + elem_ptr insert_overwrite(const time::time_t &time, const T &value, - const iterator &hint, + const elem_ptr &hint, bool overwrite_all = false) { return this->insert_overwrite(keyframe_t(time, value), hint, overwrite_all); } @@ -189,7 +194,7 @@ class KeyframeContainer { * conflict. Give an approximate insertion location to minimize runtime on * big-history curves. */ - iterator insert_after(const keyframe_t &value, const iterator &hint); + elem_ptr insert_after(const keyframe_t &value, const elem_ptr &hint); /** * Insert a new value at given time which will be prepended to the block of @@ -197,37 +202,37 @@ class KeyframeContainer { * time from the end of the data. The use of this function is discouraged, * use it only, if your really do not have the possibility to get a hint. */ - iterator insert_after(const time::time_t &time, const T &value) { + elem_ptr insert_after(const time::time_t &time, const T &value) { return this->insert_after(keyframe_t(time, value), - std::end(this->container)); + this->container.size()); } /** * Create and insert a new element, which is added after a previous element with * identical time. Provide a insertion hint to abbreviate the search for the insertion point. */ - iterator insert_after(const time::time_t &time, const T &value, const iterator &hint) { + elem_ptr insert_after(const time::time_t &time, const T &value, const elem_ptr &hint) { return this->insert_after(keyframe_t(time, value), hint); } /** * Erase all elements that come after this last valid element. */ - iterator erase_after(iterator last_valid); + elem_ptr erase_after(elem_ptr last_valid); /** * Erase a single element from the curve. * Returns the element after the deleted one. */ - iterator erase(iterator it); + elem_ptr erase(elem_ptr it); /** * Erase all elements with given time. * Variant without hint, starts the search at the end of the container. * Returns the iterator after the deleted elements. */ - iterator erase(const time::time_t &time) { - return this->erase(time, std::end(this->container)); + elem_ptr erase(const time::time_t &time) { + return this->erase(time, this->container.size()); } /** @@ -239,8 +244,8 @@ class KeyframeContainer { * Or, if no elements with this time exist, * the iterator to the first element after the requested time is returned */ - iterator erase(const time::time_t &time, - const iterator &hint) { + elem_ptr erase(const time::time_t &time, + const elem_ptr &hint) { return this->erase_group(time, this->last(time, hint)); } @@ -259,46 +264,44 @@ class KeyframeContainer { } /** - * Remove all keyframes from the container, EXCEPT for the default value - * at -INF. - * - * Essentially, the container is reset to the state immediately after construction. - */ + * Remove all keyframes from the container, EXCEPT for the default value + * at -INF. + * + * Essentially, the container is reset to the state immediately after construction. + */ void clear() { this->container.erase(++this->begin(), this->end()); } /** - * 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 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. - */ - iterator sync(const KeyframeContainer &other, - const time::time_t &start = std::numeric_limits::min()); - - /** - * Copy keyframes from another container (with a different element type) to this container. - * - * 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. - */ + * 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. + */ + elem_ptr sync(const KeyframeContainer &other, + const time::time_t &start = time::TIME_MIN); + + /** + * Copy keyframes from another container (with a different element type) to this container. + * + * 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. + */ template - iterator sync(const KeyframeContainer &other, + elem_ptr sync(const KeyframeContainer &other, const std::function &converter, - const time::time_t &start = std::numeric_limits::min()); + const time::time_t &start = time::TIME_MIN); /** * Debugging method to be used from gdb to understand bugs better. @@ -314,8 +317,8 @@ class KeyframeContainer { * Erase elements with this time. * The iterator has to point to the last element of the same-time group. */ - iterator erase_group(const time::time_t &time, - const iterator &last_elem); + elem_ptr erase_group(const time::time_t &time, + const elem_ptr &last_elem); /** * The data store. @@ -328,7 +331,7 @@ template KeyframeContainer::KeyframeContainer() { // Create a default element at -Inf, that can always be dereferenced - so // there will by definition never be a element that cannot be dereferenced - this->container.push_back(keyframe_t(std::numeric_limits::min(), T())); + this->container.push_back(keyframe_t(time::TIME_MIN, T())); } @@ -336,7 +339,7 @@ template KeyframeContainer::KeyframeContainer(const T &defaultval) { // Create a default element at -Inf, that can always be dereferenced - so // there will by definition never be a element that cannot be dereferenced - this->container.push_back(keyframe_t(std::numeric_limits::min(), defaultval)); + this->container.push_back(keyframe_t(time::TIME_MIN, defaultval)); } @@ -355,28 +358,28 @@ size_t KeyframeContainer::size() const { * that determines the curve value for a searched time. */ template -typename KeyframeContainer::iterator KeyframeContainer::last(const time::time_t &time, - const iterator &hint) const { - iterator e = hint; - auto end = std::end(this->container); +typename KeyframeContainer::elem_ptr +KeyframeContainer::last(const time::time_t &time, + const KeyframeContainer::elem_ptr &hint) const { + elem_ptr at = hint; + const elem_ptr end = this->container.size(); - if (e != end and e->time <= time) { + if (at != end and this->container.at(at).time() <= time) { // walk to the right until the time is larget than the searched - // then go one to the left to get the last item with <= requested time - while (e != end && e->time <= time) { - e++; + while (at != end and this->container.at(at).time() <= time) { + ++at; } - e--; + // go one back, because we want the last element that is <= time + --at; } - else { // e == end or e->time > time - // walk to the left until the element time is smaller than or equal to the searched time - auto begin = std::begin(this->container); - while (e != begin and (e == end or e->time > time)) { - e--; + else { // idx == end or idx->time > time + // walk to the left until the element time is smaller than the searched time + while (at > 0 and (at == end or this->container.at(at).time() > time)) { + --at; } } - return e; + return at; } @@ -389,28 +392,28 @@ typename KeyframeContainer::iterator KeyframeContainer::last(const time::t * first element that matches the search time. */ template -typename KeyframeContainer::iterator KeyframeContainer::last_before(const time::time_t &time, - const iterator &hint) const { - iterator e = hint; - auto end = std::end(this->container); +typename KeyframeContainer::elem_ptr +KeyframeContainer::last_before(const time::time_t &time, + const KeyframeContainer::elem_ptr &hint) const { + elem_ptr at = hint; + const elem_ptr end = this->container.size(); - if (e != end and e->time < time) { + if (at != end and this->container.at(at).time() < time) { // walk to the right until the time is larget than the searched - // then go one to the left to get the last item with <= requested time - while (e != end && e->time <= time) { - e++; + while (at != end and this->container.at(at).time() <= time) { + ++at; } - e--; + // go one back, because we want the last element that is <= time + --at; } - else { // e == end or e->time > time + else { // idx == end or idx->time > time // walk to the left until the element time is smaller than the searched time - auto begin = std::begin(this->container); - while (e != begin and (e == end or e->time >= time)) { - e--; + while (at > 0 and (at == end or this->container.at(at).time() >= time)) { + --at; } } - return e; + return at; } @@ -418,21 +421,25 @@ typename KeyframeContainer::iterator KeyframeContainer::last_before(const * Determine where to insert based on time, and insert. */ template -typename KeyframeContainer::iterator +typename KeyframeContainer::elem_ptr KeyframeContainer::insert_before(const KeyframeContainer::keyframe_t &e, - const KeyframeContainer::iterator &hint) { - iterator at = this->last(e.time, hint); - // seek over all same-time elements, so we can insert before the first one - while (at != std::begin(this->container) and at->time == e.time) { - --at; + const KeyframeContainer::elem_ptr &hint) { + elem_ptr at = this->last(e.time(), hint); + + if (at == this->container.size()) { + this->container.push_back(e); + return at; } - // the above while-loop overshoots and selects the first non-equal element. - // set iterator one to the right, i.e. on the first same-value - if (at != std::end(this->container)) { - ++at; + // seek over all same-time elements, so we can insert before the first one + while (this->container.at(at).time() == e.time() and at > 0) { + at--; } - return this->container.insert(at, e); + + ++at; + + this->container.insert(this->begin() + at, e); + return at; } @@ -440,26 +447,28 @@ KeyframeContainer::insert_before(const KeyframeContainer::keyframe_t &e, * Determine where to insert based on time, and insert, overwriting value(s) with same time. */ template -typename KeyframeContainer::iterator -KeyframeContainer::insert_overwrite( - const KeyframeContainer::keyframe_t &e, - const KeyframeContainer::iterator &hint, - bool overwrite_all) { - iterator at = this->last(e.time, hint); +typename KeyframeContainer::elem_ptr +KeyframeContainer::insert_overwrite(const KeyframeContainer::keyframe_t &e, + const KeyframeContainer::elem_ptr &hint, + bool overwrite_all) { + elem_ptr at = this->last(e.time(), hint); + const elem_ptr end = this->container.size(); if (overwrite_all) { - at = this->erase_group(e.time, at); + at = this->erase_group(e.time(), at); } - else if (at != std::end(this->container)) { + else if (at != end) { // overwrite the same-time element - if (at->time == e.time) { - at = this->container.erase(at); + if (this->get(at).time() == e.time()) { + this->container.erase(this->begin() + at); } else { ++at; } } - return this->container.insert(at, e); + + this->container.insert(this->begin() + at, e); + return at; } @@ -468,16 +477,18 @@ KeyframeContainer::insert_overwrite( * If there is a time conflict, insert after the existing element. */ template -typename KeyframeContainer::iterator -KeyframeContainer::insert_after( - const KeyframeContainer::keyframe_t &e, - const KeyframeContainer::iterator &hint) { - iterator at = this->last(e.time, hint); +typename KeyframeContainer::elem_ptr +KeyframeContainer::insert_after(const KeyframeContainer::keyframe_t &e, + const KeyframeContainer::elem_ptr &hint) { + elem_ptr at = this->last(e.time(), hint); + const elem_ptr end = this->container.size(); - if (at != std::end(this->container)) { + if (at != end) { ++at; } - return this->container.insert(at, e); + + this->container.insert(this->begin() + at, e); + return at; } @@ -485,17 +496,15 @@ KeyframeContainer::insert_after( * Go from the end to the last_valid element, and call erase on all of them */ template -typename KeyframeContainer::iterator -KeyframeContainer::erase_after(KeyframeContainer::iterator last_valid) { +typename KeyframeContainer::elem_ptr +KeyframeContainer::erase_after(KeyframeContainer::elem_ptr last_valid) { // exclude the last_valid element from deletion - if (last_valid != this->container.end()) { - ++last_valid; + if (last_valid != this->container.size()) { + // Delete everything to the end. + const elem_ptr delete_start = last_valid + 1; + this->container.erase(this->begin() + delete_start, this->end()); } - // Delete everything to the end. - while (last_valid != this->container.end()) { - last_valid = this->container.erase(last_valid); - } return last_valid; } @@ -504,79 +513,73 @@ KeyframeContainer::erase_after(KeyframeContainer::iterator last_valid) { * Delete the element from the list and call delete on it. */ template -typename KeyframeContainer::iterator -KeyframeContainer::erase(KeyframeContainer::iterator e) { - return this->container.erase(e); +typename KeyframeContainer::elem_ptr +KeyframeContainer::erase(KeyframeContainer::elem_ptr e) { + this->container.erase(this->begin() + e); + return e; } template -typename KeyframeContainer::iterator +typename KeyframeContainer::elem_ptr KeyframeContainer::sync(const KeyframeContainer &other, const time::time_t &start) { // Delete elements after start time - iterator at = this->last_before(start, this->end()); + elem_ptr at = this->last_before(start, this->container.size()); at = this->erase_after(at); - auto at_other = other.begin(); - ++at_other; // always skip the first element (because it's the default value) + auto at_other = 1; // always skip the first element (because it's the default value) // Copy all elements from other with time >= start - while (at_other != other.end()) { - if (at_other->time >= start) { - at = this->insert_after(*at_other, at); + for (size_t i = at_other; i < other.size(); i++) { + if (other.get(i).time() >= start) { + at = this->insert_after(other.get(i), at); } - ++at_other; } - return at; + return this->container.size(); } template template -typename KeyframeContainer::iterator +typename KeyframeContainer::elem_ptr KeyframeContainer::sync(const KeyframeContainer &other, const std::function &converter, const time::time_t &start) { // Delete elements after start time - iterator at = this->last_before(start, this->end()); + elem_ptr at = this->last_before(start, this->container.size()); at = this->erase_after(at); - auto at_other = other.begin(); - ++at_other; // always skip the first element (because it's the default value) + auto at_other = 1; // always skip the first element (because it's the default value) // Copy all elements from other with time >= start - while (at_other != other.end()) { - if (at_other->time >= start) { - // Convert the value to the type of this container - at = this->insert_after(at_other->time, converter(at_other->value), at); + for (size_t i = at_other; i < other.size(); i++) { + if (other.get(i).time() >= start) { + at = this->insert_after(keyframe_t(other.get(i).time(), converter(other.get(i).val())), at); } - ++at_other; } - return at; + return this->container.size(); } template -typename KeyframeContainer::iterator +typename KeyframeContainer::elem_ptr KeyframeContainer::erase_group(const time::time_t &time, - const iterator &last_elem) { - iterator at = last_elem; + const KeyframeContainer::elem_ptr &last_elem) { + size_t at = last_elem; // if the time what we're looking for // erase elements until all element with that time are purged - while (at != std::end(this->container) and at->time == time) { - at = this->container.erase(at); - if (at != std::begin(this->container)) [[likely]] { - --at; - } + while (at != this->container.size() and this->container.at(at).time() == time) { + this->container.erase(this->container.begin() + at); + --at; } // we have to cancel one --at in order to return // the element after the group we deleted. - if (at != std::end(this->container)) { + if (at != this->container.size()) { ++at; } diff --git a/libopenage/curve/queue.h b/libopenage/curve/queue.h deleted file mode 100644 index 3ac8d20d9a..0000000000 --- a/libopenage/curve/queue.h +++ /dev/null @@ -1,299 +0,0 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include -#include -#include -#include - -#include "curve/iterator.h" -#include "curve/queue_filter_iterator.h" -#include "event/evententity.h" -#include "time/time.h" -#include "util/fixed_point.h" - - -namespace openage { -namespace event { -class EventLoop; -} - -namespace curve { - -/** - * A container that manages events on a timeline. Every event has exactly one - * time it will happen. - * This container can be used to store interactions - */ -template -class Queue : public event::EventEntity { - struct queue_wrapper { - time::time_t _time; - T value; - - queue_wrapper(const time::time_t &time, const T &value) : - _time{time}, - value{value} {} - - time::time_t time() const { - return _time; - } - }; - -public: - using container_t = typename std::deque; - using const_iterator = typename container_t::const_iterator; - using iterator = typename container_t::const_iterator; - - Queue(const std::shared_ptr &loop, - size_t id, - const std::string &idstr = "") : - EventEntity{loop}, - _id{id}, - _idstr{idstr}, - last_front{this->container.begin()} {} - - // prevent accidental copy of queue - Queue(const Queue &) = delete; - - // Reading Access - - /** - * Get the first element in the queue at the given time. - * - * @param time The time to get the element at. - * @return Queue element. - */ - const T &front(const time::time_t &time) const; - - /** - * Get the first element in the queue at the given time. - * - * @param time The time to get the element at. - * @param value Queue element. - */ - const T &pop_front(const time::time_t &time); - - /** - * Check if the queue is empty at a given time. - * - * @return true if the queue is empty, false otherwise. - */ - bool empty(const time::time_t &time) const; - - // Modifying access - - /** - * Get an iterator to the first/front element in the queue at the given time. - * - * @param t The time to get the element at. - * @return Iterator to the first element. - */ - QueueFilterIterator> begin( - const time::time_t &t = -std::numeric_limits::max()) const; - - /** - * Get an iterator to the last element in the queue at the given time. - * - * @param t The time to get the element at. - * @return Iterator to the last element. - */ - QueueFilterIterator> end( - const time::time_t &t = std::numeric_limits::max()) const; - - /** - * Get an iterator to elements that are in the queue between two time frames. - * - * @param begin Start time. - * @param end End time. - * @return Iterator to the first element in the time frame. - */ - QueueFilterIterator> between( - const time::time_t &begin = std::numeric_limits::max(), - const time::time_t &end = std::numeric_limits::max()) const; - - /** - * Erase an element from the queue. - * - * @param it The iterator to the element to erase. - */ - void erase(const CurveIterator> &it); - - /** - * Insert a new element into the queue. - * - * @param time The time to insert at. - * @param e The element to insert. - * @return Iterator to the inserted element. - */ - QueueFilterIterator> insert(const time::time_t &, const T &e); - - /** - * Erase all elements that are at or after the given time. - * - * @param time The time to clear at. - */ - void clear(const time::time_t &); - - /** - * Print the queue to stdout. - */ - void dump() { - for (auto i : container) { - std::cout << i.value << " at " << i.time() << std::endl; - } - } - - /** - * 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; - } - -private: - /** - * Identifier for the container - */ - const size_t _id; - - /** - * Human-readable identifier for the container - */ - const std::string _idstr; - - /** - * The container that stores the queue elements. - */ - container_t container; - - iterator last_front; -}; - - -template -const T &Queue::front(const time::time_t &t) const { - return this->begin(t).value(); -} - -template -bool Queue::empty(const time::time_t &time) const { - return this->last_front == this->begin(time).get_base(); -} - -template -inline const T &Queue::pop_front(const time::time_t &time) { - this->last_front = this->begin(time).get_base(); - return this->front(time); -} - -template -QueueFilterIterator> Queue::begin(const time::time_t &t) const { - for (auto it = this->container.begin(); it != this->container.end(); ++it) { - if (it->time() >= t) { - return QueueFilterIterator>( - it, - this, - t, - std::numeric_limits::max()); - } - } - - return this->end(t); -} - - -template -QueueFilterIterator> Queue::end(const time::time_t &t) const { - return QueueFilterIterator>( - container.end(), - this, - t, - std::numeric_limits::max()); -} - - -template -QueueFilterIterator> Queue::between( - const time::time_t &begin, - const time::time_t &end) const { - auto it = QueueFilterIterator>( - container.begin(), - this, - begin, - end); - if (!container.empty() && !it.valid()) { - ++it; - } - return it; -} - - -template -void Queue::erase(const CurveIterator> &it) { - container.erase(it.get_base()); - return; -} - - -template -QueueFilterIterator> Queue::insert( - const time::time_t &time, - const T &e) { - const_iterator insertion_point = this->container.end(); - for (auto it = this->container.begin(); it != this->container.end(); ++it) { - if (time < it->time()) { - insertion_point = this->container.insert(it, queue_wrapper(time, e)); - break; - } - } - if (insertion_point == this->container.end()) { - insertion_point = this->container.insert(this->container.end(), - queue_wrapper(time, e)); - } - - auto ct = QueueFilterIterator>( - insertion_point, - this, - time, - std::numeric_limits::max()); - - if (!ct.valid()) { - ++ct; - } - - this->changes(time); - - return ct; -} - - -template -void Queue::clear(const time::time_t &time) { - for (auto it = this->container.begin(); - it != this->container.end() and it->time() < time; - it = this->container.erase(it)) { - } - - this->changes(time); -} - - -} // namespace curve -} // namespace openage diff --git a/libopenage/curve/segmented.h b/libopenage/curve/segmented.h index e02572060f..98add2995e 100644 --- a/libopenage/curve/segmented.h +++ b/libopenage/curve/segmented.h @@ -1,4 +1,4 @@ -// Copyright 2019-2023 the openage authors. See copying.md for legal info. +// Copyright 2019-2024 the openage authors. See copying.md for legal info. #pragma once @@ -62,7 +62,7 @@ void Segmented::set_last_jump(const time::time_t &at, const T &leftval, const auto hint = this->container.last(at, this->last_element); // erase all one same-time values - while (hint->time == at) { + while (this->container.get(hint).time() == at) { hint--; } diff --git a/libopenage/curve/tests/container.cpp b/libopenage/curve/tests/container.cpp index a5efbf2887..16da2476e9 100644 --- a/libopenage/curve/tests/container.cpp +++ b/libopenage/curve/tests/container.cpp @@ -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. #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); @@ -149,11 +150,25 @@ void test_queue() { auto loop = std::make_shared(); Queue q{loop, 0}; - q.insert(0, 1); + + TESTEQUALS(q.empty(0), true); + TESTEQUALS(q.empty(1), true); + TESTEQUALS(q.empty(100001), true); + q.insert(2, 2); q.insert(4, 3); q.insert(10, 4); q.insert(100001, 5); + q.insert(100001, 6); + + TESTEQUALS(q.empty(0), true); + TESTEQUALS(q.empty(1), true); + TESTEQUALS(q.empty(2), false); + TESTEQUALS(q.empty(100001), false); + TESTEQUALS(q.empty(100002), false); + + q.insert(0, 1); + TESTEQUALS(*q.begin(0), 1); TESTEQUALS(*q.begin(1), 2); TESTEQUALS(*q.begin(2), 2); @@ -204,6 +219,131 @@ void test_queue() { } TESTEQUALS(reference.empty(), true); } + + TESTEQUALS(q.front(0), 1); + TESTEQUALS(q.front(2), 1); + TESTEQUALS(q.front(5), 1); + TESTEQUALS(q.front(10), 1); + TESTEQUALS(q.front(100001), 1); + + TESTEQUALS(q.pop_front(0), 1); + TESTEQUALS(q.empty(0), true); + + TESTEQUALS(q.pop_front(12), 2); + TESTEQUALS(q.empty(12), false); + + TESTEQUALS(q.pop_front(12), 3); + TESTEQUALS(q.empty(12), false); + + TESTEQUALS(q.pop_front(12), 4); + TESTEQUALS(q.empty(12), true); + + q.clear(0); + TESTEQUALS(q.empty(0), true); + 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); } @@ -211,6 +351,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 cab8473bc3..935aa7141d 100644 --- a/libopenage/curve/tests/curve_types.cpp +++ b/libopenage/curve/tests/curve_types.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #include #include @@ -27,8 +27,8 @@ void curve_types() { auto p0 = c.insert_before(0, 0); auto p1 = c.insert_before(1, 1); auto p2 = c.insert_before(10, 2); - auto pa = std::begin(c); - auto pe = std::end(c); + auto ib = 0; + auto ie = c.size(); // now contains: [-inf: 0, 0:0, 1:1, 10:2] @@ -36,133 +36,133 @@ void curve_types() { { auto it = c.begin(); - TESTEQUALS(it->value, 0); - TESTEQUALS(it->time, std::numeric_limits::min()); - TESTEQUALS((++it)->time, 0); - TESTEQUALS(it->value, 0); - TESTEQUALS((++it)->time, 1); - TESTEQUALS(it->value, 1); - TESTEQUALS((++it)->time, 10); - TESTEQUALS(it->value, 2); + TESTEQUALS(it->val(), 0); + TESTEQUALS(it->time(), time::TIME_MIN); + TESTEQUALS((++it)->time(), 0); + TESTEQUALS(it->val(), 0); + TESTEQUALS((++it)->time(), 1); + TESTEQUALS(it->val(), 1); + TESTEQUALS((++it)->time(), 10); + TESTEQUALS(it->val(), 2); } // last function tests without hints - TESTEQUALS(c.last(0)->value, 0); - TESTEQUALS(c.last(1)->value, 1); - TESTEQUALS(c.last(5)->value, 1); - TESTEQUALS(c.last(10)->value, 2); - TESTEQUALS(c.last(47)->value, 2); + TESTEQUALS(c.get(c.last(0)).val(), 0); + TESTEQUALS(c.get(c.last(1)).val(), 1); + TESTEQUALS(c.get(c.last(5)).val(), 1); + TESTEQUALS(c.get(c.last(10)).val(), 2); + TESTEQUALS(c.get(c.last(47)).val(), 2); // last() with hints. - TESTEQUALS(c.last(0, pa)->value, 0); - TESTEQUALS(c.last(1, pa)->value, 1); - TESTEQUALS(c.last(5, pa)->value, 1); - TESTEQUALS(c.last(10, pa)->value, 2); - TESTEQUALS(c.last(47, pa)->value, 2); - - TESTEQUALS(c.last(0, p0)->value, 0); - TESTEQUALS(c.last(1, p0)->value, 1); - TESTEQUALS(c.last(5, p0)->value, 1); - TESTEQUALS(c.last(10, p0)->value, 2); - TESTEQUALS(c.last(47, p0)->value, 2); - - TESTEQUALS(c.last(0, p1)->value, 0); - TESTEQUALS(c.last(1, p1)->value, 1); - TESTEQUALS(c.last(5, p1)->value, 1); - TESTEQUALS(c.last(10, p1)->value, 2); - TESTEQUALS(c.last(47, p1)->value, 2); - - TESTEQUALS(c.last(0, p2)->value, 0); - TESTEQUALS(c.last(1, p2)->value, 1); - TESTEQUALS(c.last(5, p2)->value, 1); - TESTEQUALS(c.last(10, p2)->value, 2); - TESTEQUALS(c.last(47, p2)->value, 2); - - TESTEQUALS(c.last(0, pe)->value, 0); - TESTEQUALS(c.last(1, pe)->value, 1); - TESTEQUALS(c.last(5, pe)->value, 1); - TESTEQUALS(c.last(10, pe)->value, 2); - TESTEQUALS(c.last(47, pe)->value, 2); + TESTEQUALS(c.get(c.last(0, ib)).val(), 0); + TESTEQUALS(c.get(c.last(1, ib)).val(), 1); + TESTEQUALS(c.get(c.last(5, ib)).val(), 1); + TESTEQUALS(c.get(c.last(10, ib)).val(), 2); + TESTEQUALS(c.get(c.last(47, ib)).val(), 2); + + TESTEQUALS(c.get(c.last(0, p0)).val(), 0); + TESTEQUALS(c.get(c.last(1, p0)).val(), 1); + TESTEQUALS(c.get(c.last(5, p0)).val(), 1); + TESTEQUALS(c.get(c.last(10, p0)).val(), 2); + TESTEQUALS(c.get(c.last(47, p0)).val(), 2); + + TESTEQUALS(c.get(c.last(0, p1)).val(), 0); + TESTEQUALS(c.get(c.last(1, p1)).val(), 1); + TESTEQUALS(c.get(c.last(5, p1)).val(), 1); + TESTEQUALS(c.get(c.last(10, p1)).val(), 2); + TESTEQUALS(c.get(c.last(47, p1)).val(), 2); + + TESTEQUALS(c.get(c.last(0, p2)).val(), 0); + TESTEQUALS(c.get(c.last(1, p2)).val(), 1); + TESTEQUALS(c.get(c.last(5, p2)).val(), 1); + TESTEQUALS(c.get(c.last(10, p2)).val(), 2); + TESTEQUALS(c.get(c.last(47, p2)).val(), 2); + + TESTEQUALS(c.get(c.last(0, ie)).val(), 0); + TESTEQUALS(c.get(c.last(1, ie)).val(), 1); + TESTEQUALS(c.get(c.last(5, ie)).val(), 1); + TESTEQUALS(c.get(c.last(10, ie)).val(), 2); + TESTEQUALS(c.get(c.last(47, ie)).val(), 2); // Now test the basic erase() function // Delete the 1-element, new values should be [-inf:0, 0:0, 10:2] c.erase(c.last(1)); - TESTEQUALS(c.last(1)->value, 0); - TESTEQUALS(c.last(5)->value, 0); - TESTEQUALS(c.last(47)->value, 2); + TESTEQUALS(c.get(c.last(1)).val(), 0); + TESTEQUALS(c.get(c.last(5)).val(), 0); + TESTEQUALS(c.get(c.last(47)).val(), 2); // should do nothing, since we delete all at > 99, // but the last element is at 10. should still be [-inf:0, 0:0, 10:2] c.erase_after(c.last(99)); - TESTEQUALS(c.last(47)->value, 2); + TESTEQUALS(c.get(c.last(47)).val(), 2); // now since 5 < 10, element with value 2 has to be gone // result should be [-inf:0, 0:0] c.erase_after(c.last(5)); - TESTEQUALS(c.last(47)->value, 0); + TESTEQUALS(c.get(c.last(47)).val(), 0); c.insert_overwrite(0, 42); - TESTEQUALS(c.last(100)->value, 42); - TESTEQUALS(c.last(100)->time, 0); + TESTEQUALS(c.get(c.last(100)).val(), 42); + TESTEQUALS(c.get(c.last(100)).time(), 0); // the curve now contains [-inf:0, 0:42] // let's change/add some more elements c.insert_overwrite(0, 10); - TESTEQUALS(c.last(100)->value, 10); + TESTEQUALS(c.get(c.last(100)).val(), 10); c.insert_after(0, 11); c.insert_after(0, 12); // now: [-inf:0, 0:10, 0:11, 0:12] - TESTEQUALS(c.last(0)->value, 12); - TESTEQUALS(c.last(10)->value, 12); + TESTEQUALS(c.get(c.last(0)).val(), 12); + TESTEQUALS(c.get(c.last(10)).val(), 12); c.insert_before(0, 2); // all the values at t=0 should be 2, 10, 11, 12 c.insert_after(1, 15); - TESTEQUALS(c.last(1)->value, 15); - TESTEQUALS(c.last(10)->value, 15); + TESTEQUALS(c.get(c.last(1)).val(), 15); + TESTEQUALS(c.get(c.last(10)).val(), 15); c.insert_overwrite(2, 20); - TESTEQUALS(c.last(1)->value, 15); - TESTEQUALS(c.last(2)->value, 20); - TESTEQUALS(c.last(10)->value, 20); + TESTEQUALS(c.get(c.last(1)).val(), 15); + TESTEQUALS(c.get(c.last(2)).val(), 20); + TESTEQUALS(c.get(c.last(10)).val(), 20); c.insert_before(3, 25); - TESTEQUALS(c.last(1)->value, 15); - TESTEQUALS(c.last(2)->value, 20); - TESTEQUALS(c.last(3)->value, 25); - TESTEQUALS(c.last(10)->value, 25); + TESTEQUALS(c.get(c.last(1)).val(), 15); + TESTEQUALS(c.get(c.last(2)).val(), 20); + TESTEQUALS(c.get(c.last(3)).val(), 25); + TESTEQUALS(c.get(c.last(10)).val(), 25); // now it should be [-inf: 0, 0: 2, 0: 10, 0: 11, 0: 12, 1: 15, 2: 20, // 3: 25] { auto it = c.begin(); - TESTEQUALS(it->time, std::numeric_limits::min()); - TESTEQUALS(it->value, 0); + TESTEQUALS(it->time(), time::TIME_MIN); + TESTEQUALS(it->val(), 0); - TESTEQUALS((++it)->time, 0); - TESTEQUALS(it->value, 2); + TESTEQUALS((++it)->time(), 0); + TESTEQUALS(it->val(), 2); - TESTEQUALS((++it)->time, 0); - TESTEQUALS(it->value, 10); + TESTEQUALS((++it)->time(), 0); + TESTEQUALS(it->val(), 10); - TESTEQUALS((++it)->time, 0); - TESTEQUALS(it->value, 11); + TESTEQUALS((++it)->time(), 0); + TESTEQUALS(it->val(), 11); - TESTEQUALS((++it)->time, 0); - TESTEQUALS(it->value, 12); + TESTEQUALS((++it)->time(), 0); + TESTEQUALS(it->val(), 12); - TESTEQUALS((++it)->time, 1); - TESTEQUALS(it->value, 15); + TESTEQUALS((++it)->time(), 1); + TESTEQUALS(it->val(), 15); - TESTEQUALS((++it)->time, 2); - TESTEQUALS(it->value, 20); + TESTEQUALS((++it)->time(), 2); + TESTEQUALS(it->val(), 20); - TESTEQUALS((++it)->time, 3); - TESTEQUALS(it->value, 25); + TESTEQUALS((++it)->time(), 3); + TESTEQUALS(it->val(), 25); } // TODO: test c.insert_overwrite and c.insert_after @@ -170,17 +170,17 @@ void curve_types() { KeyframeContainer c2; c2.sync(c, 1); // now c2 should be [-inf: 0, 1: 15, 2: 20, 3: 25] - TESTEQUALS(c2.last(0)->value, 0); - TESTEQUALS(c2.last(1)->value, 15); - TESTEQUALS(c2.last(2)->value, 20); - TESTEQUALS(c2.last(3)->value, 25); - TESTEQUALS(c2.last(10)->value, 25); + TESTEQUALS(c2.get(c2.last(0)).val(), 0); + TESTEQUALS(c2.get(c2.last(1)).val(), 15); + TESTEQUALS(c2.get(c2.last(2)).val(), 20); + TESTEQUALS(c2.get(c2.last(3)).val(), 25); + TESTEQUALS(c2.get(c2.last(10)).val(), 25); TESTEQUALS(c2.size(), 4); c.clear(); // now it should be [-inf: 0] - TESTEQUALS(c.last(0)->value, 0); - TESTEQUALS(c.last(1)->value, 0); + TESTEQUALS(c.get(c.last(0)).val(), 0); + TESTEQUALS(c.get(c.last(1)).val(), 0); TESTEQUALS(c.size(), 1); } @@ -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/cvar/cvar.h b/libopenage/cvar/cvar.h index d391c7874f..dad8df775a 100644 --- a/libopenage/cvar/cvar.h +++ b/libopenage/cvar/cvar.h @@ -1,4 +1,4 @@ -// Copyright 2016-2017 the openage authors. See copying.md for legal info. +// Copyright 2016-2024 the openage authors. See copying.md for legal info. #pragma once @@ -40,7 +40,6 @@ using set_func = std::function; * void load_config(Path path) except + */ class OAAPI CVarManager { - public: CVarManager(const util::Path &path); @@ -102,4 +101,5 @@ class OAAPI CVarManager { */ extern OAAPI pyinterface::PyIfFunc pyx_load_config_file; -}} // openage::cvar +} // namespace cvar +} // namespace openage diff --git a/libopenage/datastructure/concurrent_queue.h b/libopenage/datastructure/concurrent_queue.h index 61c0fcfa24..84d5366e5a 100644 --- a/libopenage/datastructure/concurrent_queue.h +++ b/libopenage/datastructure/concurrent_queue.h @@ -1,4 +1,4 @@ -// Copyright 2015-2020 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once @@ -47,8 +47,8 @@ class ConcurrentQueue { } /** Copies the front item in the queue and removes it from the queue. */ - template - T pop([[maybe_unused]] typename std::enable_if_t and std::is_copy_constructible_v>* t = nullptr) { + template + T pop([[maybe_unused]] typename std::enable_if_t and std::is_copy_constructible_v> *t = nullptr) { static_assert(sizeof...(None) == 0, "User-specified template arguments are prohibited."); std::scoped_lock lock{this->mutex}; T ret = this->front(); @@ -60,8 +60,8 @@ class ConcurrentQueue { } /** Moves the front item in the queue and removes it from the queue. */ - template - T pop([[maybe_unused]] typename std::enable_if_t>* t = nullptr) { + template + T pop([[maybe_unused]] typename std::enable_if_t> *t = nullptr) { static_assert(sizeof...(None) == 0, "User-specified template arguments are prohibited."); std::scoped_lock lock{this->mutex}; T ret = std::move(this->front()); @@ -70,8 +70,8 @@ class ConcurrentQueue { } /** Appends the given item to the queue by copying it. */ - template - void push(typename std::enable_if_t, const T&> item) { + template + void push(typename std::enable_if_t, const T &> item) { static_assert(sizeof...(None) == 0, "User-specified template arguments are prohibited."); std::unique_lock lock{this->mutex}; this->queue.push(item); @@ -80,8 +80,8 @@ class ConcurrentQueue { } /** Appends the given item to the queue by moving it. */ - template - void push(typename std::enable_if_t, T&&> item) { + template + void push(typename std::enable_if_t, T &&> item) { static_assert(sizeof...(None) == 0, "User-specified template arguments are prohibited."); std::unique_lock lock{this->mutex}; this->queue.push(std::move(item)); @@ -111,4 +111,4 @@ class ConcurrentQueue { std::condition_variable_any elements_available; }; -} // openage::datastructure +} // namespace openage::datastructure diff --git a/libopenage/datastructure/pairing_heap.h b/libopenage/datastructure/pairing_heap.h index e2ab2109d2..df8d1ad66d 100644 --- a/libopenage/datastructure/pairing_heap.h +++ b/libopenage/datastructure/pairing_heap.h @@ -1,4 +1,4 @@ -// Copyright 2014-2023 the openage authors. See copying.md for legal info. +// Copyright 2014-2025 the openage authors. See copying.md for legal info. #pragma once @@ -16,13 +16,14 @@ * Algorithmica 1, no. 1-4 (1986): 111-129. */ -#include #include +#include +#include #include #include -#include "../util/compiler.h" #include "../error/error.h" +#include "../util/compiler.h" #define OPENAGE_PAIRINGHEAP_DEBUG false @@ -31,34 +32,38 @@ namespace openage::datastructure { -template +template class PairingHeap; +template +class PairingHeapIterator; -template> -class PairingHeapNode : public std::enable_shared_from_this> { +template > +class PairingHeapNode { public: using this_type = PairingHeapNode; friend PairingHeap; + friend PairingHeapIterator; T data; compare cmp; -public: - PairingHeapNode(const T &data) - : + PairingHeapNode(const T &data) : data{data} {} - PairingHeapNode(T &&data) - : + PairingHeapNode(T &&data) : data{std::move(data)} {} ~PairingHeapNode() = default; + PairingHeapNode(const this_type &other) = delete; + + this_type &operator=(const this_type &other) = delete; + /** * Get contained node data. */ @@ -69,14 +74,14 @@ class PairingHeapNode : public std::enable_shared_from_this &node) { - node->add_child(this->shared_from_this()); + void become_child_of(this_type *const node) { + node->add_child(this); } /** * Add the given node as a child to this one. */ - void add_child(const std::shared_ptr &new_child) { + void add_child(this_type *const new_child) { // first child is the most recently attached one // it must not have siblings as they will get lost. @@ -88,7 +93,7 @@ class PairingHeapNode : public std::enable_shared_from_thisfirst_child = new_child; - new_child->parent = this->shared_from_this(); + new_child->parent = this; } /** @@ -96,23 +101,23 @@ class PairingHeapNode : public std::enable_shared_from_this link_with(const std::shared_ptr &node) { - std::shared_ptr new_root; - std::shared_ptr new_child; + this_type *link_with(this_type *const node) { + this_type *new_root; + this_type *new_child; if (this->cmp(this->data, node->data)) { - new_root = this->shared_from_this(); + new_root = this; new_child = node; } else { - new_root = node; - new_child = this->shared_from_this(); + new_root = node; + new_child = this; } // children of new root become siblings of new new_child // -> parent of new child = new root - // this whll be set by the add_child method + // this will be set by the add_child method new_child->prev_sibling = nullptr; new_child->next_sibling = nullptr; @@ -131,15 +136,15 @@ class PairingHeapNode : public std::enable_shared_from_this link_backwards() { + this_type *link_backwards() { if (this->next_sibling == nullptr) { // reached end, return this as current root, // the previous siblings will be linked to it. - return this->shared_from_this(); + return this; } // recurse to last sibling, - std::shared_ptr node = this->next_sibling->link_backwards(); + this_type *node = this->next_sibling->link_backwards(); // then link ourself to the new root. this->next_sibling = nullptr; @@ -156,9 +161,9 @@ class PairingHeapNode : public std::enable_shared_from_thisparent and this->parent->first_child == this->shared_from_this()) { - // we are the first child - // make the next sibling the first child + if (this->parent and this->parent->first_child == this) { + // we are child + // make the next sibling child this->parent->first_child = this->next_sibling; } // if we have a previous sibling @@ -179,43 +184,203 @@ class PairingHeapNode : public std::enable_shared_from_this first_child; - std::shared_ptr prev_sibling; - std::shared_ptr next_sibling; - std::shared_ptr parent; // for decrease-key and delete + this_type *first_child = nullptr; + this_type *prev_sibling = nullptr; + this_type *next_sibling = nullptr; + this_type *parent = nullptr; // for decrease-key and delete +}; + + +/** + * @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. */ -template, - typename heapnode_t=PairingHeapNode> +template , + typename heapnode_t = PairingHeapNode> class PairingHeap final { public: - using node_t = heapnode_t; - using element_t = std::shared_ptr; - using this_type = PairingHeap; - using cmp_t = compare; + using element_t = heapnode_t *; + using this_type = PairingHeap; + using iterator = PairingHeapIterator; /** * create a empty heap. */ - PairingHeap() - : + PairingHeap() : node_count(0), root_node(nullptr) { } - ~PairingHeap() = default; + ~PairingHeap() { + this->clear(); + }; /** * adds the given item to the heap. * O(1) */ element_t push(const T &item) { - element_t new_node = std::make_shared(item); + element_t new_node = new heapnode_t(item); this->push_node(new_node); return new_node; } @@ -225,25 +390,18 @@ class PairingHeap final { * O(1) */ element_t push(T &&item) { - element_t new_node = std::make_shared(std::move(item)); + element_t new_node = new heapnode_t(std::move(item)); this->push_node(new_node); return new_node; } - /** - * returns and removes the smallest item on the heap. - */ - T pop() { - return std::move(this->pop_node()->data); - } - /** * returns the smallest item on the heap and deletes it. * also known as delete_min. * _________ * Ω(log log n), O(2^(2*√log log n')) */ - element_t pop_node() { + T pop() { if (this->root_node == nullptr) { throw Error{MSG(err) << "Can't pop an empty heap!"}; } @@ -291,7 +449,8 @@ class PairingHeap final { // link0 was the only node first_pair = link0; link0->prev_sibling = nullptr; - } else { + } + else { previous_pair->next_sibling = link0; link0->prev_sibling = previous_pair; } @@ -319,36 +478,9 @@ class PairingHeap final { ret->first_child = nullptr; // and it's done! - return ret; - } - - /** - * Unlink a node from the heap. - * - * If the item is the current root, just pop(). - * else, cut the node from its parent, pop() that subtree - * and merge these trees. - * - * O(pop_node) - */ - void unlink_node(const element_t &node) { - if (node == this->root_node) { - this->pop_node(); - } - else { - node->loosen(); - - element_t real_root = this->root_node; - this->root_node = node; - this->pop_node(); - - element_t new_root = this->root_node; - this->root_node = real_root; - - if (new_root != nullptr) { - this->root_insert(new_root); - } - } + T data = std::move(ret->data); + delete ret; + return data; } /** @@ -394,14 +526,43 @@ class PairingHeap final { * * O(1) (but slower than decrease), and O(pop) when node is the root. */ - void update(const element_t &node) { + void update(element_t &node) { if (node != this->root_node) [[likely]] { - this->unlink_node(node); - this->push_node(node); + node = this->push(this->remove_node(node)); } else { // it's the root node, so we just pop and push it. - this->push_node(this->pop_node()); + node = this->push(this->pop()); + } + } + + /** + * remove a node from the heap. Return its data. + * + * If the item is the current root, just pop(). + * else, cut the node from its parent, pop() that subtree + * and merge these trees. + * + * O(pop_node) + */ + T remove_node(const element_t &node) { + if (node == this->root_node) { + return this->pop(); + } + else { + node->loosen(); + + element_t real_root = this->root_node; + this->root_node = node; + T data = this->pop(); + + element_t new_root = this->root_node; + this->root_node = real_root; + + if (new_root != nullptr) { + this->root_insert(new_root); + } + return data; } } @@ -409,9 +570,24 @@ class PairingHeap final { * erase all elements on the heap. */ void clear() { + 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 } @@ -582,29 +758,57 @@ class PairingHeap final { } #endif + /** + * Apply the given function to all nodes in the tree. + * + * @tparam reverse If true, the function is applied to the nodes in reverse order. + * @param func Function to apply to each node. + */ + template void iter_all(const std::function &func) const { - this->walk_tree(this->root_node, func); + this->walk_tree(this->root_node, func); } -protected: - void walk_tree(const element_t &root, - const std::function &func) const { + iterator begin() const { + return iterator(this->root_node); + } - func(root); + iterator end() const { + return iterator(nullptr); + } + +private: + /** + * Apply the given function to all nodes in the tree. + * + * @tparam reverse If true, the function is applied to the nodes in reverse order. + * @param start Starting node. + * @param func Function to apply to each node. + */ + template + void walk_tree(const element_t &start, + const std::function &func) const { + if constexpr (not reverse) { + func(start); + } - if (root) { - auto node = root->first_child; + if (start) { + auto node = start->first_child; while (true) { if (not node) { break; } - this->walk_tree(node, func); + this->walk_tree(node, func); node = node->next_sibling; } + if constexpr (reverse) { + func(start); + } } } + /** * adds the given node to the heap. * use this if the node was not in the heap before. @@ -612,10 +816,9 @@ class PairingHeap final { */ void push_node(const element_t &node) { this->root_insert(node); - #if OPENAGE_PAIRINGHEAP_DEBUG - auto ins = this->nodes.insert(node); - if (not ins.second) { + auto [iter, result] = this->nodes.insert(node); + if (not result) { throw Error{ERR << "node already known"}; } #endif @@ -629,12 +832,12 @@ class PairingHeap final { void root_insert(const element_t &node) { if (this->root_node == nullptr) [[unlikely]] { this->root_node = node; - } else { + } + else { this->root_node = this->root_node->link_with(node); } } -protected: compare cmp; size_t node_count; element_t root_node; @@ -644,4 +847,4 @@ class PairingHeap final { #endif }; -} // openage::datastructure +} // namespace openage::datastructure diff --git a/libopenage/datastructure/tests.cpp b/libopenage/datastructure/tests.cpp index 95fa02a9da..40aa516951 100644 --- a/libopenage/datastructure/tests.cpp +++ b/libopenage/datastructure/tests.cpp @@ -1,4 +1,4 @@ -// Copyright 2014-2023 the openage authors. See copying.md for legal info. +// Copyright 2014-2025 the openage authors. See copying.md for legal info. #include "tests.h" @@ -118,7 +118,8 @@ void pairing_heap_2() { heap.push(heap_elem{3}); // state: 1 2 3, now remove 2 - heap.unlink_node(node); + auto data = heap.remove_node(node); + TESTEQUALS(data.data, 2); // state: 1 3 TESTEQUALS(heap.pop().data, 1); @@ -134,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 9af6d4fe89..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}, @@ -56,7 +56,7 @@ Engine::Engine(mode mode, // if presenter is used, run it in a separate thread if (this->run_mode == mode::FULL) { this->threads.emplace_back([&]() { - this->presenter->run(debug_graphics); + 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 967174fe27..0231054628 100644 --- a/libopenage/engine/engine.h +++ b/libopenage/engine/engine.h @@ -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. #pragma once @@ -7,11 +7,13 @@ #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 + #if !__cpp_lib_jthread namespace std { class jthread : public thread { public: @@ -27,9 +29,9 @@ class jthread : public thread { } }; } // namespace std -#endif + #endif #else -#include + #include #endif @@ -67,16 +69,16 @@ class Engine { /** * Create the engine instance for this run. - * - * @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 mode The run mode to use. + * @param root_dir openage root directory. + * @param mods The mods to load. + * @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; @@ -87,8 +89,8 @@ class Engine { /** - * Run the main loop. - */ + * Run the main loop. + */ void loop(); /** @@ -99,38 +101,38 @@ class Engine { private: /** - * The run mode to use. - */ + * The run mode to use. + */ mode run_mode; /** - * openage root directory. - */ + * openage root directory. + */ util::Path root_dir; /** - * The threads used by the engine. - */ + * The threads used by the engine. + */ std::vector threads; /** - * Environment variables. - */ + * Environment variables. + */ std::shared_ptr cvar_manager; /** - * Controls and update the clock for time-based measurements. - */ + * Controls and update the clock for time-based measurements. + */ std::shared_ptr time_loop; /** - * Gameplay simulation. - */ + * Gameplay simulation. + */ std::shared_ptr simulation; /** - * Video/audio/input management. Can be nullptr in headless mode. - */ + * Video/audio/input management. Can be nullptr in headless mode. + */ std::shared_ptr presenter; }; diff --git a/libopenage/error/CMakeLists.txt b/libopenage/error/CMakeLists.txt index 9fefd61a5d..b54fa5e524 100644 --- a/libopenage/error/CMakeLists.txt +++ b/libopenage/error/CMakeLists.txt @@ -2,7 +2,6 @@ add_sources(libopenage backtrace.cpp demo.cpp error.cpp - gl_debug.cpp handlers.cpp stackanalyzer.cpp ) diff --git a/libopenage/error/backtrace.h b/libopenage/error/backtrace.h index ea736c75c6..b51805f994 100644 --- a/libopenage/error/backtrace.h +++ b/libopenage/error/backtrace.h @@ -1,4 +1,4 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once @@ -6,8 +6,8 @@ #include // pxd: from libcpp.string cimport string -#include #include +#include // pxd: from libopenage.pyinterface.functional cimport Func1 #include @@ -32,14 +32,14 @@ namespace error { * ctypedef const backtrace_symbol *backtrace_symbol_constptr */ struct backtrace_symbol { - std::string filename; // empty if unknown - unsigned int lineno; // 0 if unknown - std::string functionname; // empty if unknown - void *pc; // nullptr if unknown + std::string filename; // empty if unknown + unsigned int lineno; // 0 if unknown + std::string functionname; // empty if unknown + void *pc; // nullptr if unknown }; -std::ostream &operator <<(std::ostream &os, const backtrace_symbol &bt_sym); +std::ostream &operator<<(std::ostream &os, const backtrace_symbol &bt_sym); /** @@ -66,8 +66,8 @@ class Backtrace { * @param reversed * if true, the most recent call is given last. */ - virtual void get_symbols(std::function cb, - bool reversed=true) const = 0; + virtual void get_symbols(std::function cb, + bool reversed = true) const = 0; /** * Removes all the lower frames that are also present in the current stack. @@ -77,13 +77,16 @@ class Backtrace { * * Defaults to no-op. */ - virtual void trim_to_current_stack_frame() {}; + virtual void trim_to_current_stack_frame() { + return; + }; virtual ~Backtrace() = default; }; -std::ostream &operator <<(std::ostream &os, const Backtrace &bt); +std::ostream &operator<<(std::ostream &os, const Backtrace &bt); -}} // openage::error +} // namespace error +} // namespace openage diff --git a/libopenage/error/gl_debug.cpp b/libopenage/error/gl_debug.cpp deleted file mode 100644 index fc89d33d74..0000000000 --- a/libopenage/error/gl_debug.cpp +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2016-2023 the openage authors. See copying.md for legal info. - -#include "gl_debug.h" - -#include - -#include "log/message.h" - -#include "error/error.h" - -namespace openage::error { - -namespace { -void APIENTRY callback(GLenum source, GLenum, GLuint, GLenum, GLsizei, const GLchar *message, const void *) { - const char *source_name; - - switch (source) { - case GL_DEBUG_SOURCE_API: - source_name = "API"; - break; - case GL_DEBUG_SOURCE_WINDOW_SYSTEM: - source_name = "window system"; - break; - case GL_DEBUG_SOURCE_SHADER_COMPILER: - source_name = "shader compiler"; - break; - case GL_DEBUG_SOURCE_THIRD_PARTY: - source_name = "third party"; - break; - case GL_DEBUG_SOURCE_APPLICATION: - source_name = "application"; - break; - case GL_DEBUG_SOURCE_OTHER: - source_name = "other"; - break; - default: - source_name = "unknown"; - break; - } - - throw Error(MSG(err) << "OpenGL error from " << source_name << ": '" << message << "'."); -} - -} // namespace - -SDL_GLContext create_debug_context(SDL_Window *window) { - SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG); - - auto ctx = SDL_GL_CreateContext(window); - - if (ctx != nullptr) { - GLint flags; - glGetIntegerv(GL_CONTEXT_FLAGS, &flags); - - if (!(flags & GL_CONTEXT_FLAG_DEBUG_BIT)) - throw Error(MSG(err) << "Failed creating a debug OpenGL context."); - - glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_FALSE); - - glDebugMessageControl(GL_DONT_CARE, GL_DEBUG_TYPE_ERROR, GL_DONT_CARE, 0, nullptr, GL_TRUE); - glDebugMessageControl(GL_DONT_CARE, GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR, GL_DONT_CARE, 0, nullptr, GL_TRUE); - - glDebugMessageCallback(callback, nullptr); - glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); - } - - return ctx; -} - -} // namespace openage::error diff --git a/libopenage/error/gl_debug.h b/libopenage/error/gl_debug.h deleted file mode 100644 index a874c1086e..0000000000 --- a/libopenage/error/gl_debug.h +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2016-2016 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -namespace openage { -namespace error { - -SDL_GLContext create_debug_context(SDL_Window *window); - -}} // openage::error diff --git a/libopenage/error/handlers.cpp b/libopenage/error/handlers.cpp index f608b0e78f..b3e61450b2 100644 --- a/libopenage/error/handlers.cpp +++ b/libopenage/error/handlers.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. /* * This file holds handlers for std::terminate and SIGSEGV. @@ -17,9 +17,9 @@ #include #ifdef _MSC_VER -#include + #include #else -#include + #include #endif #include "util/init.h" diff --git a/libopenage/error/handlers.h b/libopenage/error/handlers.h index 4679c336c2..bb483ace89 100644 --- a/libopenage/error/handlers.h +++ b/libopenage/error/handlers.h @@ -1,4 +1,4 @@ -// Copyright 2016-2018 the openage authors. See copying.md for legal info. +// Copyright 2016-2024 the openage authors. See copying.md for legal info. #pragma once @@ -21,4 +21,5 @@ namespace error { OAAPI void set_exit_ok(bool value); -}} // openage::error +} // namespace error +} // namespace openage diff --git a/libopenage/error/stackanalyzer.cpp b/libopenage/error/stackanalyzer.cpp index 878cb01025..cc04dae05d 100644 --- a/libopenage/error/stackanalyzer.cpp +++ b/libopenage/error/stackanalyzer.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 "stackanalyzer.h" @@ -30,8 +30,8 @@ constexpr uint64_t base_skip_frames = 1; #if WITH_BACKTRACE -// use modern -#include + // use modern + #include namespace openage { namespace error { @@ -59,7 +59,7 @@ struct backtrace_state *bt_state; util::OnInit init_backtrace_state([]() { bt_state = backtrace_create_state( nullptr, // auto-determine filename - 1, // threaded + 1, // threaded backtrace_error_callback, nullptr // passed to the callback ); @@ -189,8 +189,8 @@ void StackAnalyzer::get_symbols(std::function cb #else // WITHOUT_BACKTRACE -#ifdef _WIN32 -#include + #ifdef _WIN32 + #include namespace openage { namespace error { @@ -208,10 +208,10 @@ void StackAnalyzer::analyze() { } // namespace error } // namespace openage -#else // not _MSC_VER + #else // not _MSC_VER -// use GNU's -#include + // use GNU's + #include namespace openage::error { @@ -256,7 +256,7 @@ void StackAnalyzer::analyze() { } // namespace openage::error -#endif // for _MSC_VER or GNU execinfo + #endif // for _MSC_VER or GNU execinfo namespace openage::error { diff --git a/libopenage/event/demo/aicontroller.h b/libopenage/event/demo/aicontroller.h index 6200b1d99d..a54784887f 100644 --- a/libopenage/event/demo/aicontroller.h +++ b/libopenage/event/demo/aicontroller.h @@ -1,4 +1,4 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once @@ -12,4 +12,4 @@ std::vector get_ai_inputs(const std::shared_ptr &player, const std::shared_ptr &ball, const time::time_t &now); -} // openage::event::demo +} // namespace openage::event::demo diff --git a/libopenage/event/demo/gui.cpp b/libopenage/event/demo/gui.cpp index 100509c0f4..66d0bbf79d 100644 --- a/libopenage/event/demo/gui.cpp +++ b/libopenage/event/demo/gui.cpp @@ -1,25 +1,25 @@ -// Copyright 2016-2023 the openage authors. See copying.md for legal info. +// Copyright 2016-2024 the openage authors. See copying.md for legal info. #include "gui.h" // the gui requires ncurses. #if WITH_NCURSES -#include -#include -#include -#include -#ifdef __MINGW32__ -#include -#else -#include -#endif // __MINGW32__ -#include - -#include "curve/continuous.h" -#include "curve/discrete.h" -#include "event/demo/gamestate.h" -#include "util/fixed_point.h" + #include + #include + #include + #include + #ifdef __MINGW32__ + #include + #else + #include + #endif // __MINGW32__ + #include + + #include "curve/continuous.h" + #include "curve/discrete.h" + #include "event/demo/gamestate.h" + #include "util/fixed_point.h" namespace openage::event::demo { diff --git a/libopenage/event/demo/gui.h b/libopenage/event/demo/gui.h index de21a99d02..8e447d808f 100644 --- a/libopenage/event/demo/gui.h +++ b/libopenage/event/demo/gui.h @@ -1,18 +1,18 @@ -// 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 #include "config.h" #if WITH_NCURSES -#include -#include -#include -#include - -#include "event/demo/gamestate.h" -#include "time/time.h" -#include "util/vector.h" + #include + #include + #include + #include + + #include "event/demo/gamestate.h" + #include "time/time.h" + #include "util/vector.h" namespace openage::event::demo { diff --git a/libopenage/event/demo/main.cpp b/libopenage/event/demo/main.cpp index 442e9910c7..f5100b2e82 100644 --- a/libopenage/event/demo/main.cpp +++ b/libopenage/event/demo/main.cpp @@ -1,10 +1,10 @@ -// Copyright 2016-2023 the openage authors. See copying.md for legal info. +// Copyright 2016-2024 the openage authors. See copying.md for legal info. #include "main.h" #include #include -#include +#include #include "config.h" #include "event/demo/aicontroller.h" @@ -13,13 +13,14 @@ #include "event/demo/physics.h" #include "event/event.h" #include "event/event_loop.h" +#include "renderer/gui/integration/public/gui_application_with_logger.h" #if WITH_NCURSES -#ifdef __MINGW32__ -#include -#else -#include -#endif // __MINGW32__ + #ifdef __MINGW32__ + #include + #else + #include + #endif // __MINGW32__ #endif @@ -41,6 +42,7 @@ enum class timescale { void curvepong(bool disable_gui, bool no_human) { + renderer::gui::GuiApplicationWithLogger gui_app{}; bool enable_gui = not disable_gui; bool human_player = not no_human; @@ -99,6 +101,8 @@ void curvepong(bool disable_gui, bool no_human) { while (state->p1->lives->get(now) > 0 and state->p2->lives->get(now) > 0) { auto loop_start = Clock::now(); + gui_app.process_events(); + #if WITH_NCURSES if (enable_gui) { gui->clear(); @@ -157,7 +161,7 @@ void curvepong(bool disable_gui, bool no_human) { if (speed == timescale::NOSLEEP) { // increase the simulation loop time a bit - SDL_Delay(5); + std::this_thread::sleep_for(std::chrono::milliseconds(5)); } dt_ms_t dt_us = Clock::now() - loop_start; @@ -166,7 +170,7 @@ void curvepong(bool disable_gui, bool no_human) { dt_ms_t wait_time = per_frame - dt_us; if (wait_time > dt_ms_t::zero()) { - SDL_Delay(wait_time.count()); + std::this_thread::sleep_for(wait_time); } } diff --git a/libopenage/event/demo/main.h b/libopenage/event/demo/main.h index f873a11bda..097ef40f3d 100644 --- a/libopenage/event/demo/main.h +++ b/libopenage/event/demo/main.h @@ -1,4 +1,4 @@ -// Copyright 2018-2019 the openage authors. See copying.md for legal info. +// Copyright 2018-2024 the openage authors. See copying.md for legal info. #pragma once @@ -12,4 +12,4 @@ namespace openage::event::demo { OAAPI void curvepong(bool disable_gui, bool no_human); -} // openage::event::demo +} // namespace openage::event::demo diff --git a/libopenage/event/demo/physics.cpp b/libopenage/event/demo/physics.cpp index d43365f042..e7e969a533 100644 --- a/libopenage/event/demo/physics.cpp +++ b/libopenage/event/demo/physics.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #include "physics.h" @@ -9,13 +9,13 @@ #include #if WITH_NCURSES -#ifdef __MINGW32__ -#include -#else -#include -#endif // __MINGW32__ + #ifdef __MINGW32__ + #include + #else + #include + #endif // __MINGW32__ -#include "gui.h" + #include "gui.h" #endif #include "error/error.h" @@ -104,7 +104,7 @@ class BallReflectWall : public DependencyEventHandler { auto pos = positioncurve->get(now); if (speed[1] == 0) { - return std::numeric_limits::max(); + return time::TIME_MAX; } time::time_t ty = 0; @@ -227,7 +227,7 @@ class BallReflectPanel : public DependencyEventHandler { auto pos = positioncurve->get(now); if (speed[0] == 0) - return std::numeric_limits::max(); + return time::TIME_MAX; time::time_t ty = 0; @@ -377,7 +377,7 @@ void Physics::init(const std::shared_ptr &gstate, loop->create_event("demo.ball.reflect_panel", state->ball->position, state, now); // FIXME once "reset": deregister - //reset(state, mgr, now); + // reset(state, mgr, now); } void Physics::process_input(const std::shared_ptr &state, @@ -466,11 +466,11 @@ void Physics::process_input(const std::shared_ptr &state, Physics::reset(state, *mgr, now); break; - //if (player->state->get(now).state == PongEvent::LOST) { + // if (player->state->get(now).state == PongEvent::LOST) { // state->ball.position->set_last(now, state.display_boundary * 0.5); - //} - //update_ball(state, now, init_recursion_limit); - //break; + // } + // update_ball(state, now, init_recursion_limit); + // break; default: break; diff --git a/libopenage/event/event.h b/libopenage/event/event.h index 72c943d807..46d63df555 100644 --- a/libopenage/event/event.h +++ b/libopenage/event/event.h @@ -1,4 +1,4 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once @@ -12,6 +12,8 @@ namespace openage::event { class EventEntity; +using event_hash_t = size_t; + /** * The actual one event that may be called - it is used to manage the event itself. * It does not need to be stored. @@ -36,7 +38,7 @@ class Event : public std::enable_shared_from_this { */ void reschedule(const time::time_t reference_time); - size_t hash() const { + event_hash_t hash() const { return this->myhash; } @@ -61,8 +63,8 @@ class Event : public std::enable_shared_from_this { void depend_on(const std::shared_ptr &dependency); /** - * Cancel the event. - */ + * Cancel the event. + */ void cancel(const time::time_t reference_time); /** @@ -111,7 +113,7 @@ class Event : public std::enable_shared_from_this { time::time_t last_change_time = time::time_t::min_value(); /** Precalculated std::hash for the event */ - size_t myhash; + event_hash_t myhash; }; diff --git a/libopenage/event/event_loop.cpp b/libopenage/event/event_loop.cpp index eb9e8ca6f1..26bf176978 100644 --- a/libopenage/event/event_loop.cpp +++ b/libopenage/event/event_loop.cpp @@ -151,7 +151,7 @@ int EventLoop::execute_events(const time::time_t &time_until, time::time_t new_time = event->get_eventhandler()->predict_invoke_time( target, state, event->get_time()); - if (new_time != std::numeric_limits::min()) { + if (new_time != time::TIME_MIN) { event->set_time(new_time); log::log(DBG << "Loop: repeating event \"" << event->get_eventhandler()->id() @@ -204,7 +204,7 @@ void EventLoop::update_changes(const std::shared_ptr &state) { time::time_t new_time = evnt->get_eventhandler() ->predict_invoke_time(entity, state, change.time); - if (new_time != std::numeric_limits::min()) { + if (new_time != time::TIME_MIN) { log::log(DBG << "Loop: due to a change, rescheduling event of '" << evnt->get_eventhandler()->id() << "' on entity '" << entity->idstr() diff --git a/libopenage/event/event_loop.h b/libopenage/event/event_loop.h index 5a5a45bc64..01ee49fdaf 100644 --- a/libopenage/event/event_loop.h +++ b/libopenage/event/event_loop.h @@ -1,4 +1,4 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once @@ -33,31 +33,31 @@ class EventLoop { public: /** - * Create a new event loop. - */ + * Create a new event loop. + */ EventLoop() = default; ~EventLoop() = default; /** - * Register a new event handler. - * - * Created event can reference the event handler ID to invoke it on - * execution. - * - * @param eventhandler Event handler. - */ + * Register a new event handler. + * + * Created event can reference the event handler ID to invoke it on + * execution. + * + * @param eventhandler Event handler. + */ void add_event_handler(const std::shared_ptr eventhandler); /** * Add a new event to the queue using a registered event handler. - * - * @param eventhandler Event handler ID. The handler must already be registered on the loop. - * @param target Target entity. Can be \p nullptr. - * @param state Global state. - * @param reference_time Reference time to calculate the event execution time. The actual - * depends execution time on the type of event and may be changed - * by other events. - * @param params Event parameters map (default = {}). Passed to the event handler on event execution. + * + * @param eventhandler Event handler ID. The handler must already be registered on the loop. + * @param target Target entity. Can be \p nullptr. + * @param state Global state. + * @param reference_time Reference time to calculate the event execution time. The actual + * depends execution time on the type of event and may be changed + * by other events. + * @param params Event parameters map (default = {}). Passed to the event handler on event execution. */ std::shared_ptr create_event(const std::string eventhandler, const std::shared_ptr target, @@ -67,19 +67,19 @@ class EventLoop { /** * Add a new event to the queue using an arbritary event handler. If an event handler - * with the same ID is already registered, the registered event handler will be used - * instead. - * - * TODO: Why use this function when one can simply add the event handler and use the other - * create_event function? - * - * @param eventhandler Event handler. - * @param target Target entity. Can be \p nullptr. - * @param state Global state. - * @param reference_time Reference time to calculate the event execution time. The actual - * depends execution time on the type of event and may be changed - * by other events. - * @param params Event parameters map (default = {}). Passed to the event handler on event execution. + * with the same ID is already registered, the registered event handler will be used + * instead. + * + * TODO: Why use this function when one can simply add the event handler and use the other + * create_event function? + * + * @param eventhandler Event handler. + * @param target Target entity. Can be \p nullptr. + * @param state Global state. + * @param reference_time Reference time to calculate the event execution time. The actual + * depends execution time on the type of event and may be changed + * by other events. + * @param params Event parameters map (default = {}). Passed to the event handler on event execution. */ std::shared_ptr create_event(const std::shared_ptr eventhandler, const std::shared_ptr target, @@ -89,33 +89,33 @@ class EventLoop { /** * Execute events in the queue with execution time <= a given point in time. - * - * @param time_until Maximum time until which events are executed. - * @param state Global state. + * + * @param time_until Maximum time until which events are executed. + * @param state Global state. */ void reach_time(const time::time_t &time_until, const std::shared_ptr &state); /** * Initiate a reevaluation of a given event at a given time. - * + * * This usually happens because this event depended on an event entity * that got changed at this time. - * + * * This inserts the event into the changes queue * so it will be evaluated in the next loop iteration. - * - * @param event Event to reevaluate. - * @param changes_at Time at which the event should be reevaluated. + * + * @param event Event to reevaluate. + * @param changes_at Time at which the event should be reevaluated. */ void create_change(const std::shared_ptr event, const time::time_t changes_at); /** - * Get the event queue. - * - * @return Event queue. - */ + * Get the event queue. + * + * @return Event queue. + */ const EventQueue &get_queue() const { return this->queue; } @@ -124,9 +124,9 @@ class EventLoop { /** * Execute events in the queue with execution time <= a given point in time. * - * @param time_until Maximum time until which events are executed. - * @param state Global state. - * + * @param time_until Maximum time until which events are executed. + * @param state Global state. + * * @returns number of events processed */ int execute_events(const time::time_t &time_until, @@ -134,8 +134,8 @@ class EventLoop { /** * Call all the time change functions. This is constant on the state! - * - * @param state Global state. + * + * @param state Global state. */ void update_changes(const std::shared_ptr &state); diff --git a/libopenage/event/eventqueue.cpp b/libopenage/event/eventqueue.cpp index 83a96e6e16..51a0c2fdaa 100644 --- a/libopenage/event/eventqueue.cpp +++ b/libopenage/event/eventqueue.cpp @@ -35,7 +35,7 @@ std::shared_ptr EventQueue::create_event(const std::shared_ptrset_time(event->get_eventhandler() ->predict_invoke_time(trgt, state, reference_time)); - if (event->get_time() == std::numeric_limits::min()) { + if (event->get_time() == time::TIME_MIN) { log::log(DBG << "Queue: ignoring insertion of event " << event->get_eventhandler()->id() << " because no execution was scheduled."); diff --git a/libopenage/event/eventstore.cpp b/libopenage/event/eventstore.cpp index 7e88b4e638..f8a949f7a9 100644 --- a/libopenage/event/eventstore.cpp +++ b/libopenage/event/eventstore.cpp @@ -1,4 +1,4 @@ -// Copyright 2018-2023 the openage authors. See copying.md for legal info. +// Copyright 2018-2024 the openage authors. See copying.md for legal info. #include "eventstore.h" @@ -56,7 +56,7 @@ bool EventStore::erase(const std::shared_ptr &event) { bool erased = false; auto it = this->events.find(event); if (it != std::end(this->events)) { - this->heap.unlink_node(it->second); + this->heap.remove_node(it->second); this->events.erase(it); erased = true; } diff --git a/libopenage/event/state.h b/libopenage/event/state.h index 3bad6f9a7d..6ab585cba2 100644 --- a/libopenage/event/state.h +++ b/libopenage/event/state.h @@ -1,4 +1,4 @@ -// Copyright 2018-2023 the openage authors. See copying.md for legal info. +// Copyright 2018-2024 the openage authors. See copying.md for legal info. #pragma once @@ -12,8 +12,8 @@ class EventLoop; class State { public: - State(const std::shared_ptr &/*mgr*/) {} + State(const std::shared_ptr & /*mgr*/) {} virtual ~State() = default; }; -} // openage::event +} // namespace openage::event diff --git a/libopenage/game_renderer.cpp b/libopenage/game_renderer.cpp deleted file mode 100644 index 530d87ad92..0000000000 --- a/libopenage/game_renderer.cpp +++ /dev/null @@ -1,210 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#include -#include -#include -#include -#include - -#include "console/console.h" -#include "game_renderer.h" -#include "gamedata/color_dummy.h" -#include "gamestate/old/game_main.h" -#include "gamestate/old/game_spec.h" -#include "input/input_manager.h" -#include "legacy_engine.h" -#include "log/log.h" -#include "renderer/text.h" -#include "terrain/terrain.h" -#include "unit/action.h" -#include "unit/command.h" -#include "unit/producer.h" -#include "unit/unit.h" -#include "unit/unit_texture.h" -#include "util/externalprofiler.h" -#include "util/timer.h" - -namespace openage { - -RenderOptions::RenderOptions() : - OptionNode{"RendererOptions"}, - draw_debug{this, "draw_debug", false}, - terrain_blending{this, "terrain_blending", true} {} - -GameRenderer::GameRenderer(LegacyEngine *e) : - engine{e} { - // set options structure - this->settings.set_parent(this->engine); - - // engine callbacks - this->engine->register_draw_action(this); - - // fetch asset loading dir - util::Path asset_dir = engine->get_root_dir()["assets"]; - - // load textures and stuff - gaben = new Texture{asset_dir["test"]["textures"]["gaben.png"]}; - - std::vector player_color_lines = util::read_csv_file( - asset_dir["converted/player_palette.docx"]); - - std::unique_ptr playercolors = std::make_unique(player_color_lines.size() * 4); - for (size_t i = 0; i < player_color_lines.size(); i++) { - auto line = &player_color_lines[i]; - playercolors[i * 4] = line->r / 255.0; - playercolors[i * 4 + 1] = line->g / 255.0; - playercolors[i * 4 + 2] = line->b / 255.0; - playercolors[i * 4 + 3] = line->a / 255.0; - } - - // shader initialisation - // read shader source codes and create shader objects for wrapping them. - const char *shader_header_code = "#version 120\n"; - std::string equals_epsilon_code = asset_dir["shaders/equalsEpsilon.glsl"].open().read(); - std::string texture_vert_code = asset_dir["shaders/maptexture.vert.glsl"].open().read(); - auto plaintexture_vert = std::make_unique( - GL_VERTEX_SHADER, - std::initializer_list{shader_header_code, texture_vert_code.c_str()}); - - std::string texture_frag_code = asset_dir["shaders/maptexture.frag.glsl"].open().read(); - auto plaintexture_frag = std::make_unique( - GL_FRAGMENT_SHADER, - std::initializer_list{shader_header_code, texture_frag_code.c_str()}); - - std::string teamcolor_frag_code = asset_dir["shaders/teamcolors.frag.glsl"].open().read(); - std::stringstream ss; - ss << player_color_lines.size(); - auto teamcolor_frag = std::make_unique( - GL_FRAGMENT_SHADER, - std::initializer_list{ - shader_header_code, - ("#define NUM_OF_PLAYER_COLORS " + ss.str() + "\n").c_str(), - equals_epsilon_code.c_str(), - teamcolor_frag_code.c_str()}); - - std::string alphamask_vert_code = asset_dir["shaders/alphamask.vert.glsl"].open().read(); - auto alphamask_vert = std::make_unique( - GL_VERTEX_SHADER, - std::initializer_list{shader_header_code, alphamask_vert_code.c_str()}); - - std::string alphamask_frag_code = asset_dir["shaders/alphamask.frag.glsl"].open().read(); - auto alphamask_frag = std::make_unique( - GL_FRAGMENT_SHADER, - std::initializer_list{shader_header_code, alphamask_frag_code.c_str()}); - - std::string texturefont_vert_code = asset_dir["shaders/texturefont.vert.glsl"].open().read(); - auto texturefont_vert = std::make_unique( - GL_VERTEX_SHADER, - std::initializer_list{shader_header_code, texturefont_vert_code.c_str()}); - - std::string texturefont_frag_code = asset_dir["shaders/texturefont.frag.glsl"].open().read(); - auto texturefont_frag = std::make_unique( - GL_FRAGMENT_SHADER, - std::initializer_list{shader_header_code, texturefont_frag_code.c_str()}); - - // create program for rendering simple textures - texture_shader::program = new shader::Program(plaintexture_vert.get(), plaintexture_frag.get()); - texture_shader::program->link(); - texture_shader::texture = texture_shader::program->get_uniform_id("texture"); - texture_shader::tex_coord = texture_shader::program->get_attribute_id("tex_coordinates"); - texture_shader::program->use(); - glUniform1i(texture_shader::texture, 0); - texture_shader::program->stopusing(); - - - // create program for tinting textures at alpha-marked pixels - // with team colors - teamcolor_shader::program = new shader::Program(plaintexture_vert.get(), teamcolor_frag.get()); - teamcolor_shader::program->link(); - teamcolor_shader::texture = teamcolor_shader::program->get_uniform_id("texture"); - teamcolor_shader::tex_coord = teamcolor_shader::program->get_attribute_id("tex_coordinates"); - teamcolor_shader::player_id_var = teamcolor_shader::program->get_uniform_id("player_number"); - teamcolor_shader::alpha_marker_var = teamcolor_shader::program->get_uniform_id("alpha_marker"); - teamcolor_shader::player_color_var = teamcolor_shader::program->get_uniform_id("player_color"); - teamcolor_shader::program->use(); - glUniform1i(teamcolor_shader::texture, 0); - glUniform1f(teamcolor_shader::alpha_marker_var, 254.0 / 255.0); - // fill the teamcolor shader's player color table: - glUniform4fv(teamcolor_shader::player_color_var, 64, playercolors.get()); - teamcolor_shader::program->stopusing(); - - - // create program for drawing textures that are alpha-masked before - alphamask_shader::program = new shader::Program(alphamask_vert.get(), alphamask_frag.get()); - alphamask_shader::program->link(); - alphamask_shader::base_coord = alphamask_shader::program->get_attribute_id("base_tex_coordinates"); - alphamask_shader::mask_coord = alphamask_shader::program->get_attribute_id("mask_tex_coordinates"); - alphamask_shader::show_mask = alphamask_shader::program->get_uniform_id("show_mask"); - alphamask_shader::base_texture = alphamask_shader::program->get_uniform_id("base_texture"); - alphamask_shader::mask_texture = alphamask_shader::program->get_uniform_id("mask_texture"); - alphamask_shader::program->use(); - glUniform1i(alphamask_shader::base_texture, 0); - glUniform1i(alphamask_shader::mask_texture, 1); - alphamask_shader::program->stopusing(); - - // Create program for texture based font rendering - texturefont_shader::program = new shader::Program(texturefont_vert.get(), texturefont_frag.get()); - texturefont_shader::program->link(); - texturefont_shader::texture = texturefont_shader::program->get_uniform_id("texture"); - texturefont_shader::color = texturefont_shader::program->get_uniform_id("color"); - texturefont_shader::tex_coord = texturefont_shader::program->get_attribute_id("tex_coordinates"); - texturefont_shader::program->use(); - glUniform1i(texturefont_shader::texture, 0); - texturefont_shader::program->stopusing(); - - // Renderer keybinds - // TODO: a renderer settings struct - // would allow these to be put somewhere better - input::ActionManager &action = this->engine->get_action_manager(); - auto &global_input_context = engine->get_input_manager().get_global_context(); - global_input_context.bind(action.get("TOGGLE_BLENDING"), [this](const input::action_arg_t &) { - this->settings.terrain_blending.value = !this->settings.terrain_blending.value; - }); - global_input_context.bind(action.get("TOGGLE_UNIT_DEBUG"), [this](const input::action_arg_t &) { - this->settings.draw_debug.value = !this->settings.draw_debug.value; - - log::log(MSG(dbg) << "Toggle debug grid"); - - // TODO remove this hack, use render settings instead - UnitAction::show_debug = !UnitAction::show_debug; - }); - - log::log(MSG(dbg) << "Loaded Renderer"); -} - -GameRenderer::~GameRenderer() { - // oh noes, release hl3 before that! - delete this->gaben; - - delete texture_shader::program; - delete teamcolor_shader::program; - delete alphamask_shader::program; - delete texturefont_shader::program; -} - - -bool GameRenderer::on_draw() { - // draw terrain - GameMain *game = this->engine->get_game(); - - if (game) { - // draw gaben, our great and holy protector, bringer of the half-life 3. - gaben->draw(this->engine->coord, coord::camgame{0, 0}); - - // TODO move render code out of terrain - if (game->terrain) { - game->terrain->draw(this->engine, &this->settings); - } - } - return true; -} - -GameMain *GameRenderer::game() const { - return this->engine->get_game(); -} - -GameSpec *GameRenderer::game_spec() const { - return this->game()->get_spec(); -} - -} // namespace openage diff --git a/libopenage/gamedata/CMakeLists.txt b/libopenage/gamedata/CMakeLists.txt deleted file mode 100644 index 9eb8632148..0000000000 --- a/libopenage/gamedata/CMakeLists.txt +++ /dev/null @@ -1,16 +0,0 @@ -add_sources(libopenage - blending_mode_dummy.cpp - civilisation_dummy.cpp - color_dummy.cpp - gamedata_dummy.cpp - graphic_dummy.cpp - player_color_dummy.cpp - research_dummy.cpp - sound_dummy.cpp - string_resource_dummy.cpp - tech_dummy.cpp - terrain_dummy.cpp - texture_dummy.cpp - unit_dummy.cpp - util_dummy.cpp -) diff --git a/libopenage/gamedata/about b/libopenage/gamedata/about deleted file mode 100644 index f9e60f268e..0000000000 --- a/libopenage/gamedata/about +++ /dev/null @@ -1,3 +0,0 @@ -all source files in this directory are auto-generated during the build process. - -this file mainly exists to ensure that the directory isn't empty. diff --git a/libopenage/gamedata/blending_mode_dummy.cpp b/libopenage/gamedata/blending_mode_dummy.cpp deleted file mode 100644 index 468c55c84e..0000000000 --- a/libopenage/gamedata/blending_mode_dummy.cpp +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2013-2021 the openage authors. See copying.md for legal info. - -// Warning: this file is a dummy file and was auto-generated by the v0.4.1 converter; -// its purpose is to keep the deprecated gamestate compilable and intact; -// these files keep only the minimum functionality and should not be changed; -// For details, see buildsystem/codegen.cmake and openage/codegen. - - -#include -#include -#include "blending_mode_dummy.h" -#include "error/error.h" -#include "util/strings.h" - - -namespace openage { -namespace gamedata { - -constexpr size_t blending_mode::member_count; -int blending_mode::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', blending_mode::member_count - ); - - if (buf.size() != blending_mode::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing blending_mode led to " - << buf.size() - << " columns (expected " - << blending_mode::member_count - << ")!" - ); - } - - if (sscanf(buf[0].c_str(), "%d", &this->blend_mode) != 1) { return 0; } - - return -1; -} - -bool blending_mode::recurse(const openage::util::CSVCollection & /*storage*/, const std::string & /*basedir*/) { - return true; -} - -} // gamedata -} // openage diff --git a/libopenage/gamedata/blending_mode_dummy.h b/libopenage/gamedata/blending_mode_dummy.h deleted file mode 100644 index 55b29788ae..0000000000 --- a/libopenage/gamedata/blending_mode_dummy.h +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2013-2021 the openage authors. See copying.md for legal info. - -// Warning: this file is a dummy file and was auto-generated by the v0.4.1 converter; -// its purpose is to keep the deprecated gamestate compilable and intact; -// these files keep only the minimum functionality and should not be changed; -// For details, see buildsystem/codegen.cmake and openage/codegen. - -#pragma once - -#include -#include -#include -#include "util/csv.h" - - - -namespace openage { -namespace gamedata { - -/** - * describes one blending mode, a blending transition shape between two different terrain types. - */ -struct blending_mode { - int32_t blend_mode; - static constexpr size_t member_count = 1; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -} // gamedata -} // openage diff --git a/libopenage/gamedata/civilisation_dummy.cpp b/libopenage/gamedata/civilisation_dummy.cpp deleted file mode 100644 index e4b0300edf..0000000000 --- a/libopenage/gamedata/civilisation_dummy.cpp +++ /dev/null @@ -1,273 +0,0 @@ -// Copyright 2013-2021 the openage authors. See copying.md for legal info. - -// Warning: this file is a dummy file and was auto-generated by the v0.4.1 converter; -// its purpose is to keep the deprecated gamestate compilable and intact; -// these files keep only the minimum functionality and should not be changed; -// For details, see buildsystem/codegen.cmake and openage/codegen. - - -#include -#include -#include -#include "civilisation_dummy.h" -#include "error/error.h" -#include "unit_dummy.h" -#include "util_dummy.h" -#include "util/csv.h" -#include "util/path.h" -#include "util/strings.h" - - -namespace openage { -namespace gamedata { - -constexpr size_t civilisation::member_count; -int civilisation::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', civilisation::member_count - ); - - if (buf.size() != civilisation::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing civilisation led to " - << buf.size() - << " columns (expected " - << civilisation::member_count - << ")!" - ); - } - - if (sscanf(buf[0].c_str(), "%hhd", &this->player_type) != 1) { return 0; } - this->name = buf[1]; - if (sscanf(buf[2].c_str(), "%hd", &this->tech_tree_id) != 1) { return 2; } - if (sscanf(buf[3].c_str(), "%hd", &this->team_bonus_id) != 1) { return 3; } - if (sscanf(buf[5].c_str(), "%hhd", &this->icon_set) != 1) { return 5; } - this->units.subdata_meta.filename = buf[6]; - - return -1; -} - -bool civilisation::recurse(const openage::util::CSVCollection &storage, const std::string &basedir) { - this->units.recurse(storage, basedir); - - return true; -} - -int unit_types::fill(const std::string & /*line*/) { - return -1; -} -bool unit_types::recurse(const openage::util::CSVCollection &storage, - const std::string &basedir) { - - // the .filename was set by the previous entry parser already - // so now read the index-file entries - this->subdata_meta.read(storage, basedir); - - int subtype_count = this->subdata_meta.data.size(); - if (subtype_count != 9) { - throw openage::error::Error( - ERR << "multisubtype index file entry count mismatched!" - << subtype_count << " != 9" - ); - } - - // the recursed data files are relative to the subdata_meta filename - std::string metadata_dir = basedir + openage::util::fslike::PATHSEP + openage::util::dirname(this->subdata_meta.filename); - int idx; - int idxtry; - - // read subtype 'action' - idx = -1; - idxtry = 0; - // find the index of the subdata in the metadata - for (auto &file_reference : this->subdata_meta.data) { - if (file_reference.subtype == "action") { - idx = idxtry; - break; - } - idxtry += 1; - } - if (idx == -1) { - throw openage::error::Error( - ERR << "multisubtype index file contains no entry for action!" - ); - } - - // the filename is relative to the metadata file! - this->action.filename = this->subdata_meta.data[idx].filename; - this->action.read(storage, metadata_dir); - - // read subtype 'animated' - idx = -1; - idxtry = 0; - // find the index of the subdata in the metadata - for (auto &file_reference : this->subdata_meta.data) { - if (file_reference.subtype == "animated") { - idx = idxtry; - break; - } - idxtry += 1; - } - if (idx == -1) { - throw openage::error::Error( - ERR << "multisubtype index file contains no entry for animated!" - ); - } - - // the filename is relative to the metadata file! - this->animated.filename = this->subdata_meta.data[idx].filename; - this->animated.read(storage, metadata_dir); - - // read subtype 'building' - idx = -1; - idxtry = 0; - // find the index of the subdata in the metadata - for (auto &file_reference : this->subdata_meta.data) { - if (file_reference.subtype == "building") { - idx = idxtry; - break; - } - idxtry += 1; - } - if (idx == -1) { - throw openage::error::Error( - ERR << "multisubtype index file contains no entry for building!" - ); - } - - // the filename is relative to the metadata file! - this->building.filename = this->subdata_meta.data[idx].filename; - this->building.read(storage, metadata_dir); - - // read subtype 'doppelganger' - idx = -1; - idxtry = 0; - // find the index of the subdata in the metadata - for (auto &file_reference : this->subdata_meta.data) { - if (file_reference.subtype == "doppelganger") { - idx = idxtry; - break; - } - idxtry += 1; - } - if (idx == -1) { - throw openage::error::Error( - ERR << "multisubtype index file contains no entry for doppelganger!" - ); - } - - // the filename is relative to the metadata file! - this->doppelganger.filename = this->subdata_meta.data[idx].filename; - this->doppelganger.read(storage, metadata_dir); - - // read subtype 'living' - idx = -1; - idxtry = 0; - // find the index of the subdata in the metadata - for (auto &file_reference : this->subdata_meta.data) { - if (file_reference.subtype == "living") { - idx = idxtry; - break; - } - idxtry += 1; - } - if (idx == -1) { - throw openage::error::Error( - ERR << "multisubtype index file contains no entry for living!" - ); - } - - // the filename is relative to the metadata file! - this->living.filename = this->subdata_meta.data[idx].filename; - this->living.read(storage, metadata_dir); - - // read subtype 'missile' - idx = -1; - idxtry = 0; - // find the index of the subdata in the metadata - for (auto &file_reference : this->subdata_meta.data) { - if (file_reference.subtype == "missile") { - idx = idxtry; - break; - } - idxtry += 1; - } - if (idx == -1) { - throw openage::error::Error( - ERR << "multisubtype index file contains no entry for missile!" - ); - } - - // the filename is relative to the metadata file! - this->missile.filename = this->subdata_meta.data[idx].filename; - this->missile.read(storage, metadata_dir); - - // read subtype 'moving' - idx = -1; - idxtry = 0; - // find the index of the subdata in the metadata - for (auto &file_reference : this->subdata_meta.data) { - if (file_reference.subtype == "moving") { - idx = idxtry; - break; - } - idxtry += 1; - } - if (idx == -1) { - throw openage::error::Error( - ERR << "multisubtype index file contains no entry for moving!" - ); - } - - // the filename is relative to the metadata file! - this->moving.filename = this->subdata_meta.data[idx].filename; - this->moving.read(storage, metadata_dir); - - // read subtype 'object' - idx = -1; - idxtry = 0; - // find the index of the subdata in the metadata - for (auto &file_reference : this->subdata_meta.data) { - if (file_reference.subtype == "object") { - idx = idxtry; - break; - } - idxtry += 1; - } - if (idx == -1) { - throw openage::error::Error( - ERR << "multisubtype index file contains no entry for object!" - ); - } - - // the filename is relative to the metadata file! - this->object.filename = this->subdata_meta.data[idx].filename; - this->object.read(storage, metadata_dir); - - // read subtype 'tree' - idx = -1; - idxtry = 0; - // find the index of the subdata in the metadata - for (auto &file_reference : this->subdata_meta.data) { - if (file_reference.subtype == "tree") { - idx = idxtry; - break; - } - idxtry += 1; - } - if (idx == -1) { - throw openage::error::Error( - ERR << "multisubtype index file contains no entry for tree!" - ); - } - - // the filename is relative to the metadata file! - this->tree.filename = this->subdata_meta.data[idx].filename; - this->tree.read(storage, metadata_dir); - - return true; -} - -} // gamedata -} // openage diff --git a/libopenage/gamedata/civilisation_dummy.h b/libopenage/gamedata/civilisation_dummy.h deleted file mode 100644 index 1627b4112f..0000000000 --- a/libopenage/gamedata/civilisation_dummy.h +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2013-2021 the openage authors. See copying.md for legal info. - -// Warning: this file is a dummy file and was auto-generated by the v0.4.1 converter; -// its purpose is to keep the deprecated gamestate compilable and intact; -// these files keep only the minimum functionality and should not be changed; -// For details, see buildsystem/codegen.cmake and openage/codegen. - -#pragma once - -#include -#include -#include -#include -#include "unit_dummy.h" -#include "util_dummy.h" -#include "util/csv.h" - - - -namespace openage { -namespace gamedata { - -struct unit_types { - struct openage::util::csv_subdata action; - struct openage::util::csv_subdata animated; - struct openage::util::csv_subdata building; - struct openage::util::csv_subdata doppelganger; - struct openage::util::csv_subdata living; - struct openage::util::csv_subdata missile; - struct openage::util::csv_subdata moving; - struct openage::util::csv_subdata object; - struct openage::util::csv_subdata tree; - struct openage::util::csv_subdata subdata_meta; - - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -/** - * describes a civilisation. - */ -struct civilisation { - int8_t player_type; - std::string name; - int16_t tech_tree_id; - int16_t team_bonus_id; - int8_t icon_set; - unit_types units; - static constexpr size_t member_count = 7; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -} // gamedata -} // openage diff --git a/libopenage/gamedata/color_dummy.cpp b/libopenage/gamedata/color_dummy.cpp deleted file mode 100644 index 9985fb0b9d..0000000000 --- a/libopenage/gamedata/color_dummy.cpp +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2013-2021 the openage authors. See copying.md for legal info. - -// Warning: this file is a dummy file and was auto-generated by the v0.4.1 converter; -// its purpose is to keep the deprecated gamestate compilable and intact; -// these files keep only the minimum functionality and should not be changed; -// For details, see buildsystem/codegen.cmake and openage/codegen. - - -#include -#include -#include "color_dummy.h" -#include "error/error.h" -#include "util/strings.h" - - -namespace openage { -namespace gamedata { - -constexpr size_t palette_color::member_count; -int palette_color::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', palette_color::member_count - ); - - if (buf.size() != palette_color::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing palette_color led to " - << buf.size() - << " columns (expected " - << palette_color::member_count - << ")!" - ); - } - - if (sscanf(buf[0].c_str(), "%d", &this->idx) != 1) { return 0; } - if (sscanf(buf[1].c_str(), "%hhu", &this->r) != 1) { return 1; } - if (sscanf(buf[2].c_str(), "%hhu", &this->g) != 1) { return 2; } - if (sscanf(buf[3].c_str(), "%hhu", &this->b) != 1) { return 3; } - if (sscanf(buf[4].c_str(), "%hhu", &this->a) != 1) { return 4; } - - return -1; -} - -bool palette_color::recurse(const openage::util::CSVCollection & /*storage*/, const std::string & /*basedir*/) { - return true; -} - -} // gamedata -} // openage diff --git a/libopenage/gamedata/color_dummy.h b/libopenage/gamedata/color_dummy.h deleted file mode 100644 index 4c6915e5cb..0000000000 --- a/libopenage/gamedata/color_dummy.h +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2013-2021 the openage authors. See copying.md for legal info. - -// Warning: this file is a dummy file and was auto-generated by the v0.4.1 converter; -// its purpose is to keep the deprecated gamestate compilable and intact; -// these files keep only the minimum functionality and should not be changed; -// For details, see buildsystem/codegen.cmake and openage/codegen. - -#pragma once - -#include -#include -#include -#include "util/csv.h" - - - -namespace openage { -namespace gamedata { - -/** - * indexed color storage. - */ -struct palette_color { - int32_t idx; - uint8_t r; - uint8_t g; - uint8_t b; - uint8_t a; - static constexpr size_t member_count = 5; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -} // gamedata -} // openage diff --git a/libopenage/gamedata/gamedata_dummy.cpp b/libopenage/gamedata/gamedata_dummy.cpp deleted file mode 100644 index 959df5237a..0000000000 --- a/libopenage/gamedata/gamedata_dummy.cpp +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2013-2021 the openage authors. See copying.md for legal info. - -// Warning: this file is a dummy file and was auto-generated by the v0.4.1 converter; -// its purpose is to keep the deprecated gamestate compilable and intact; -// these files keep only the minimum functionality and should not be changed; -// For details, see buildsystem/codegen.cmake and openage/codegen. - - -#include -#include -#include "error/error.h" -#include "gamedata_dummy.h" -#include "util/strings.h" - - -namespace openage { -namespace gamedata { - -constexpr size_t empiresdat::member_count; -int empiresdat::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', empiresdat::member_count - ); - - if (buf.size() != empiresdat::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing empiresdat led to " - << buf.size() - << " columns (expected " - << empiresdat::member_count - << ")!" - ); - } - - this->versionstr = buf[0]; - this->terrain_restrictions.filename = buf[1]; - this->player_colors.filename = buf[2]; - this->sounds.filename = buf[3]; - this->graphics.filename = buf[4]; - if (sscanf(buf[5].c_str(), "%d", &this->virt_function_ptr) != 1) { return 5; } - if (sscanf(buf[6].c_str(), "%d", &this->map_pointer) != 1) { return 6; } - if (sscanf(buf[7].c_str(), "%d", &this->map_width) != 1) { return 7; } - if (sscanf(buf[8].c_str(), "%d", &this->map_height) != 1) { return 8; } - if (sscanf(buf[9].c_str(), "%d", &this->world_width) != 1) { return 9; } - if (sscanf(buf[10].c_str(), "%d", &this->world_height) != 1) { return 10; } - this->tile_sizes.filename = buf[11]; - if (sscanf(buf[12].c_str(), "%hd", &this->padding1) != 1) { return 12; } - this->terrains.filename = buf[13]; - this->terrain_border.filename = buf[14]; - if (sscanf(buf[15].c_str(), "%d", &this->map_row_offset) != 1) { return 15; } - if (sscanf(buf[16].c_str(), "%f", &this->map_min_x) != 1) { return 16; } - if (sscanf(buf[17].c_str(), "%f", &this->map_min_y) != 1) { return 17; } - if (sscanf(buf[18].c_str(), "%f", &this->map_max_x) != 1) { return 18; } - if (sscanf(buf[19].c_str(), "%f", &this->map_max_y) != 1) { return 19; } - if (sscanf(buf[20].c_str(), "%f", &this->map_max_xplus1) != 1) { return 20; } - if (sscanf(buf[21].c_str(), "%f", &this->map_min_yplus1) != 1) { return 21; } - if (sscanf(buf[22].c_str(), "%hd", &this->current_row) != 1) { return 22; } - if (sscanf(buf[23].c_str(), "%hd", &this->current_column) != 1) { return 23; } - if (sscanf(buf[24].c_str(), "%hd", &this->block_beginn_row) != 1) { return 24; } - if (sscanf(buf[25].c_str(), "%hd", &this->block_end_row) != 1) { return 25; } - if (sscanf(buf[26].c_str(), "%hd", &this->block_begin_column) != 1) { return 26; } - if (sscanf(buf[27].c_str(), "%hd", &this->block_end_column) != 1) { return 27; } - if (sscanf(buf[28].c_str(), "%d", &this->search_map_ptr) != 1) { return 28; } - if (sscanf(buf[29].c_str(), "%d", &this->search_map_rows_ptr) != 1) { return 29; } - if (sscanf(buf[30].c_str(), "%hhd", &this->any_frame_change) != 1) { return 30; } - if (sscanf(buf[31].c_str(), "%hhd", &this->map_visible_flag) != 1) { return 31; } - if (sscanf(buf[32].c_str(), "%hhd", &this->fog_flag) != 1) { return 32; } - this->effect_bundles.filename = buf[33]; - this->unit_headers.filename = buf[34]; - this->civs.filename = buf[35]; - this->researches.filename = buf[36]; - if (sscanf(buf[37].c_str(), "%d", &this->time_slice) != 1) { return 37; } - if (sscanf(buf[38].c_str(), "%d", &this->unit_kill_rate) != 1) { return 38; } - if (sscanf(buf[39].c_str(), "%d", &this->unit_kill_total) != 1) { return 39; } - if (sscanf(buf[40].c_str(), "%d", &this->unit_hitpoint_rate) != 1) { return 40; } - if (sscanf(buf[41].c_str(), "%d", &this->unit_hitpoint_total) != 1) { return 41; } - if (sscanf(buf[42].c_str(), "%d", &this->razing_kill_rate) != 1) { return 42; } - if (sscanf(buf[43].c_str(), "%d", &this->razing_kill_total) != 1) { return 43; } - if (sscanf(buf[44].c_str(), "%d", &this->total_unit_tech_groups) != 1) { return 44; } - this->age_connections.filename = buf[45]; - this->building_connections.filename = buf[46]; - this->unit_connections.filename = buf[47]; - this->tech_connections.filename = buf[48]; - - return -1; -} - -bool empiresdat::recurse(const openage::util::CSVCollection &storage, const std::string &basedir) { - this->terrain_restrictions.read(storage, basedir); - this->player_colors.read(storage, basedir); - this->sounds.read(storage, basedir); - this->graphics.read(storage, basedir); - this->tile_sizes.read(storage, basedir); - this->terrains.read(storage, basedir); - this->terrain_border.read(storage, basedir); - this->effect_bundles.read(storage, basedir); - this->unit_headers.read(storage, basedir); - this->civs.read(storage, basedir); - this->researches.read(storage, basedir); - this->age_connections.read(storage, basedir); - this->building_connections.read(storage, basedir); - this->unit_connections.read(storage, basedir); - this->tech_connections.read(storage, basedir); - - return true; -} - -} // gamedata -} // openage diff --git a/libopenage/gamedata/gamedata_dummy.h b/libopenage/gamedata/gamedata_dummy.h deleted file mode 100644 index d702b8cd4f..0000000000 --- a/libopenage/gamedata/gamedata_dummy.h +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2013-2021 the openage authors. See copying.md for legal info. - -// Warning: this file is a dummy file and was auto-generated by the v0.4.1 converter; -// its purpose is to keep the deprecated gamestate compilable and intact; -// these files keep only the minimum functionality and should not be changed; -// For details, see buildsystem/codegen.cmake and openage/codegen. - -#pragma once - -#include -#include -#include -#include "civilisation_dummy.h" -#include "graphic_dummy.h" -#include "player_color_dummy.h" -#include "research_dummy.h" -#include "sound_dummy.h" -#include "tech_dummy.h" -#include "terrain_dummy.h" -#include "unit_dummy.h" -#include "util/csv.h" - - - -namespace openage { -namespace gamedata { - -/** - * empires2_x1_p1.dat structure - */ -struct empiresdat { - std::string versionstr; - openage::util::csv_subdata terrain_restrictions; - openage::util::csv_subdata player_colors; - openage::util::csv_subdata sounds; - openage::util::csv_subdata graphics; - int32_t virt_function_ptr; - int32_t map_pointer; - int32_t map_width; - int32_t map_height; - int32_t world_width; - int32_t world_height; - openage::util::csv_subdata tile_sizes; - int16_t padding1; - openage::util::csv_subdata terrains; - openage::util::csv_subdata terrain_border; - int32_t map_row_offset; - float map_min_x; - float map_min_y; - float map_max_x; - float map_max_y; - float map_max_xplus1; - float map_min_yplus1; - int16_t current_row; - int16_t current_column; - int16_t block_beginn_row; - int16_t block_end_row; - int16_t block_begin_column; - int16_t block_end_column; - int32_t search_map_ptr; - int32_t search_map_rows_ptr; - int8_t any_frame_change; - int8_t map_visible_flag; - int8_t fog_flag; - openage::util::csv_subdata effect_bundles; - openage::util::csv_subdata unit_headers; - openage::util::csv_subdata civs; - openage::util::csv_subdata researches; - int32_t time_slice; - int32_t unit_kill_rate; - int32_t unit_kill_total; - int32_t unit_hitpoint_rate; - int32_t unit_hitpoint_total; - int32_t razing_kill_rate; - int32_t razing_kill_total; - int32_t total_unit_tech_groups; - openage::util::csv_subdata age_connections; - openage::util::csv_subdata building_connections; - openage::util::csv_subdata unit_connections; - openage::util::csv_subdata tech_connections; - static constexpr size_t member_count = 49; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -} // gamedata -} // openage diff --git a/libopenage/gamedata/graphic_dummy.cpp b/libopenage/gamedata/graphic_dummy.cpp deleted file mode 100644 index 6a0d6b63cb..0000000000 --- a/libopenage/gamedata/graphic_dummy.cpp +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright 2013-2021 the openage authors. See copying.md for legal info. - -// Warning: this file is a dummy file and was auto-generated by the v0.4.1 converter; -// its purpose is to keep the deprecated gamestate compilable and intact; -// these files keep only the minimum functionality and should not be changed; -// For details, see buildsystem/codegen.cmake and openage/codegen. - - -#include -#include -#include "error/error.h" -#include "graphic_dummy.h" -#include "util/strings.h" - - -namespace openage { -namespace gamedata { - -constexpr size_t graphic::member_count; -constexpr size_t graphic_attack_sound::member_count; -constexpr size_t graphic_delta::member_count; -constexpr size_t sound_prop::member_count; -int graphic_attack_sound::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', graphic_attack_sound::member_count - ); - - if (buf.size() != graphic_attack_sound::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing graphic_attack_sound led to " - << buf.size() - << " columns (expected " - << graphic_attack_sound::member_count - << ")!" - ); - } - - this->sound_props.filename = buf[0]; - - return -1; -} - -bool graphic_attack_sound::recurse(const openage::util::CSVCollection &storage, const std::string &basedir) { - this->sound_props.read(storage, basedir); - - return true; -} - -int graphic_delta::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', graphic_delta::member_count - ); - - if (buf.size() != graphic_delta::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing graphic_delta led to " - << buf.size() - << " columns (expected " - << graphic_delta::member_count - << ")!" - ); - } - - if (sscanf(buf[0].c_str(), "%hd", &this->graphic_id) != 1) { return 0; } - if (sscanf(buf[1].c_str(), "%hd", &this->padding_1) != 1) { return 1; } - if (sscanf(buf[2].c_str(), "%d", &this->sprite_ptr) != 1) { return 2; } - if (sscanf(buf[3].c_str(), "%hd", &this->offset_x) != 1) { return 3; } - if (sscanf(buf[4].c_str(), "%hd", &this->offset_y) != 1) { return 4; } - if (sscanf(buf[5].c_str(), "%hd", &this->display_angle) != 1) { return 5; } - if (sscanf(buf[6].c_str(), "%hd", &this->padding_2) != 1) { return 6; } - - return -1; -} - -bool graphic_delta::recurse(const openage::util::CSVCollection & /*storage*/, const std::string & /*basedir*/) { - return true; -} - -int graphic::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', graphic::member_count - ); - - if (buf.size() != graphic::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing graphic led to " - << buf.size() - << " columns (expected " - << graphic::member_count - << ")!" - ); - } - - this->name = buf[0]; - this->filename = buf[1]; - if (sscanf(buf[2].c_str(), "%d", &this->slp_id) != 1) { return 2; } - if (sscanf(buf[3].c_str(), "%hhd", &this->is_loaded) != 1) { return 3; } - if (sscanf(buf[4].c_str(), "%hhd", &this->old_color_flag) != 1) { return 4; } - // parse enum graphics_layer - if (buf[5] == "DUMMY") { - this->layer = graphics_layer::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[5] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[6].c_str(), "%hhd", &this->player_color_force_id) != 1) { return 6; } - if (sscanf(buf[7].c_str(), "%hhd", &this->adapt_color) != 1) { return 7; } - if (sscanf(buf[8].c_str(), "%hhu", &this->transparent_selection) != 1) { return 8; } - if (sscanf(buf[9].c_str(), "%hd", &this->sound_id) != 1) { return 9; } - if (sscanf(buf[10].c_str(), "%hu", &this->frame_count) != 1) { return 10; } - if (sscanf(buf[11].c_str(), "%hu", &this->angle_count) != 1) { return 11; } - if (sscanf(buf[12].c_str(), "%f", &this->speed_adjust) != 1) { return 12; } - if (sscanf(buf[13].c_str(), "%f", &this->frame_rate) != 1) { return 13; } - if (sscanf(buf[14].c_str(), "%f", &this->replay_delay) != 1) { return 14; } - if (sscanf(buf[15].c_str(), "%hhd", &this->sequence_type) != 1) { return 15; } - if (sscanf(buf[16].c_str(), "%hd", &this->graphic_id) != 1) { return 16; } - if (sscanf(buf[17].c_str(), "%hhd", &this->mirroring_mode) != 1) { return 17; } - if (sscanf(buf[18].c_str(), "%hhd", &this->editor_flag) != 1) { return 18; } - this->graphic_deltas.filename = buf[19]; - this->graphic_attack_sounds.filename = buf[20]; - - return -1; -} - -bool graphic::recurse(const openage::util::CSVCollection &storage, const std::string &basedir) { - this->graphic_deltas.read(storage, basedir); - this->graphic_attack_sounds.read(storage, basedir); - - return true; -} - -int sound_prop::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', sound_prop::member_count - ); - - if (buf.size() != sound_prop::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing sound_prop led to " - << buf.size() - << " columns (expected " - << sound_prop::member_count - << ")!" - ); - } - - if (sscanf(buf[0].c_str(), "%hd", &this->sound_delay) != 1) { return 0; } - if (sscanf(buf[1].c_str(), "%hd", &this->sound_id) != 1) { return 1; } - - return -1; -} - -bool sound_prop::recurse(const openage::util::CSVCollection & /*storage*/, const std::string & /*basedir*/) { - return true; -} - -} // gamedata -} // openage diff --git a/libopenage/gamedata/graphic_dummy.h b/libopenage/gamedata/graphic_dummy.h deleted file mode 100644 index 34f0a2a82f..0000000000 --- a/libopenage/gamedata/graphic_dummy.h +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2013-2021 the openage authors. See copying.md for legal info. - -// Warning: this file is a dummy file and was auto-generated by the v0.4.1 converter; -// its purpose is to keep the deprecated gamestate compilable and intact; -// these files keep only the minimum functionality and should not be changed; -// For details, see buildsystem/codegen.cmake and openage/codegen. - -#pragma once - -#include -#include -#include -#include "util/csv.h" - - - -namespace openage { -namespace gamedata { - -/** - * delta definitions for ingame graphics files. - */ -struct graphic_delta { - int16_t graphic_id; - int16_t padding_1; - int32_t sprite_ptr; - int16_t offset_x; - int16_t offset_y; - int16_t display_angle; - int16_t padding_2; - static constexpr size_t member_count = 7; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -enum class graphics_layer { - DUMMY -}; - - -/** - * sound id and delay definition for graphics sounds. - */ -struct sound_prop { - int16_t sound_delay; - int16_t sound_id; - static constexpr size_t member_count = 2; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -/** - * attack sounds for a given graphics file. - */ -struct graphic_attack_sound { - openage::util::csv_subdata sound_props; - static constexpr size_t member_count = 1; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -/** - * metadata for ingame graphics files. - */ -struct graphic { - std::string name; - std::string filename; - int32_t slp_id; - int8_t is_loaded; - int8_t old_color_flag; - graphics_layer layer; - int8_t player_color_force_id; - int8_t adapt_color; - uint8_t transparent_selection; - int16_t sound_id; - uint16_t frame_count; - uint16_t angle_count; - float speed_adjust; - float frame_rate; - float replay_delay; - int8_t sequence_type; - int16_t graphic_id; - int8_t mirroring_mode; - int8_t editor_flag; - openage::util::csv_subdata graphic_deltas; - openage::util::csv_subdata graphic_attack_sounds; - static constexpr size_t member_count = 21; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -} // gamedata -} // openage diff --git a/libopenage/gamedata/player_color_dummy.cpp b/libopenage/gamedata/player_color_dummy.cpp deleted file mode 100644 index 949b0de3c1..0000000000 --- a/libopenage/gamedata/player_color_dummy.cpp +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2013-2021 the openage authors. See copying.md for legal info. - -// Warning: this file is a dummy file and was auto-generated by the v0.4.1 converter; -// its purpose is to keep the deprecated gamestate compilable and intact; -// these files keep only the minimum functionality and should not be changed; -// For details, see buildsystem/codegen.cmake and openage/codegen. - - -#include -#include -#include "error/error.h" -#include "player_color_dummy.h" -#include "util/strings.h" - - -namespace openage { -namespace gamedata { - -constexpr size_t player_color::member_count; -int player_color::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', player_color::member_count - ); - - if (buf.size() != player_color::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing player_color led to " - << buf.size() - << " columns (expected " - << player_color::member_count - << ")!" - ); - } - - if (sscanf(buf[0].c_str(), "%d", &this->id) != 1) { return 0; } - if (sscanf(buf[1].c_str(), "%d", &this->player_color_base) != 1) { return 1; } - if (sscanf(buf[2].c_str(), "%d", &this->outline_color) != 1) { return 2; } - if (sscanf(buf[3].c_str(), "%d", &this->unit_selection_color1) != 1) { return 3; } - if (sscanf(buf[4].c_str(), "%d", &this->unit_selection_color2) != 1) { return 4; } - if (sscanf(buf[5].c_str(), "%d", &this->minimap_color1) != 1) { return 5; } - if (sscanf(buf[6].c_str(), "%d", &this->minimap_color2) != 1) { return 6; } - if (sscanf(buf[7].c_str(), "%d", &this->minimap_color3) != 1) { return 7; } - if (sscanf(buf[8].c_str(), "%d", &this->statistics_text_color) != 1) { return 8; } - - return -1; -} - -bool player_color::recurse(const openage::util::CSVCollection & /*storage*/, const std::string & /*basedir*/) { - return true; -} - -} // gamedata -} // openage diff --git a/libopenage/gamedata/player_color_dummy.h b/libopenage/gamedata/player_color_dummy.h deleted file mode 100644 index ced379fba4..0000000000 --- a/libopenage/gamedata/player_color_dummy.h +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2013-2021 the openage authors. See copying.md for legal info. - -// Warning: this file is a dummy file and was auto-generated by the v0.4.1 converter; -// its purpose is to keep the deprecated gamestate compilable and intact; -// these files keep only the minimum functionality and should not be changed; -// For details, see buildsystem/codegen.cmake and openage/codegen. - -#pragma once - -#include -#include -#include -#include "util/csv.h" - - - -namespace openage { -namespace gamedata { - -/** - * describes player color settings. - */ -struct player_color { - int32_t id; - int32_t player_color_base; - int32_t outline_color; - int32_t unit_selection_color1; - int32_t unit_selection_color2; - int32_t minimap_color1; - int32_t minimap_color2; - int32_t minimap_color3; - int32_t statistics_text_color; - static constexpr size_t member_count = 9; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -} // gamedata -} // openage diff --git a/libopenage/gamedata/research_dummy.cpp b/libopenage/gamedata/research_dummy.cpp deleted file mode 100644 index 3b3a9af15b..0000000000 --- a/libopenage/gamedata/research_dummy.cpp +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2013-2021 the openage authors. See copying.md for legal info. - -// Warning: this file is a dummy file and was auto-generated by the v0.4.1 converter; -// its purpose is to keep the deprecated gamestate compilable and intact; -// these files keep only the minimum functionality and should not be changed; -// For details, see buildsystem/codegen.cmake and openage/codegen. - - -#include -#include -#include "error/error.h" -#include "research_dummy.h" -#include "util/strings.h" - - -namespace openage { -namespace gamedata { - -constexpr size_t tech::member_count; -constexpr size_t tech_resource_cost::member_count; -int tech::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', tech::member_count - ); - - if (buf.size() != tech::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing tech led to " - << buf.size() - << " columns (expected " - << tech::member_count - << ")!" - ); - } - - this->research_resource_costs.filename = buf[1]; - if (sscanf(buf[2].c_str(), "%hd", &this->required_tech_count) != 1) { return 2; } - if (sscanf(buf[3].c_str(), "%hd", &this->civilization_id) != 1) { return 3; } - if (sscanf(buf[4].c_str(), "%hd", &this->full_tech_mode) != 1) { return 4; } - if (sscanf(buf[5].c_str(), "%hd", &this->research_location_id) != 1) { return 5; } - if (sscanf(buf[6].c_str(), "%hu", &this->language_dll_name) != 1) { return 6; } - if (sscanf(buf[7].c_str(), "%hu", &this->language_dll_description) != 1) { return 7; } - if (sscanf(buf[8].c_str(), "%hd", &this->research_time) != 1) { return 8; } - if (sscanf(buf[9].c_str(), "%hd", &this->tech_effect_id) != 1) { return 9; } - if (sscanf(buf[10].c_str(), "%hd", &this->tech_type) != 1) { return 10; } - if (sscanf(buf[11].c_str(), "%hd", &this->icon_id) != 1) { return 11; } - if (sscanf(buf[12].c_str(), "%hhd", &this->button_id) != 1) { return 12; } - if (sscanf(buf[13].c_str(), "%d", &this->language_dll_help) != 1) { return 13; } - if (sscanf(buf[14].c_str(), "%d", &this->language_dll_techtree) != 1) { return 14; } - if (sscanf(buf[15].c_str(), "%d", &this->hotkey) != 1) { return 15; } - this->name = buf[16]; - - return -1; -} - -bool tech::recurse(const openage::util::CSVCollection &storage, const std::string &basedir) { - this->research_resource_costs.read(storage, basedir); - - return true; -} - -int tech_resource_cost::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', tech_resource_cost::member_count - ); - - if (buf.size() != tech_resource_cost::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing tech_resource_cost led to " - << buf.size() - << " columns (expected " - << tech_resource_cost::member_count - << ")!" - ); - } - - // parse enum resource_types - if (buf[0] == "DUMMY") { - this->type_id = resource_types::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[0] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[1].c_str(), "%hd", &this->amount) != 1) { return 1; } - if (sscanf(buf[2].c_str(), "%hhd", &this->enabled) != 1) { return 2; } - - return -1; -} - -bool tech_resource_cost::recurse(const openage::util::CSVCollection & /*storage*/, const std::string & /*basedir*/) { - return true; -} - -} // gamedata -} // openage diff --git a/libopenage/gamedata/research_dummy.h b/libopenage/gamedata/research_dummy.h deleted file mode 100644 index f1a3205c16..0000000000 --- a/libopenage/gamedata/research_dummy.h +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2013-2021 the openage authors. See copying.md for legal info. - -// Warning: this file is a dummy file and was auto-generated by the v0.4.1 converter; -// its purpose is to keep the deprecated gamestate compilable and intact; -// these files keep only the minimum functionality and should not be changed; -// For details, see buildsystem/codegen.cmake and openage/codegen. - -#pragma once - -#include -#include -#include -#include "unit_dummy.h" -#include "util/csv.h" - - - -namespace openage { -namespace gamedata { - -/** - * amount definition for a single type resource for researches. - */ -struct tech_resource_cost { - resource_types type_id; - int16_t amount; - int8_t enabled; - static constexpr size_t member_count = 3; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -/** - * one researchable technology. - */ -struct tech { - openage::util::csv_subdata research_resource_costs; - int16_t required_tech_count; - int16_t civilization_id; - int16_t full_tech_mode; - int16_t research_location_id; - uint16_t language_dll_name; - uint16_t language_dll_description; - int16_t research_time; - int16_t tech_effect_id; - int16_t tech_type; - int16_t icon_id; - int8_t button_id; - int32_t language_dll_help; - int32_t language_dll_techtree; - int32_t hotkey; - std::string name; - static constexpr size_t member_count = 17; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -} // gamedata -} // openage diff --git a/libopenage/gamedata/sound_dummy.cpp b/libopenage/gamedata/sound_dummy.cpp deleted file mode 100644 index 67d781d0c0..0000000000 --- a/libopenage/gamedata/sound_dummy.cpp +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2013-2021 the openage authors. See copying.md for legal info. - -// Warning: this file is a dummy file and was auto-generated by the v0.4.1 converter; -// its purpose is to keep the deprecated gamestate compilable and intact; -// these files keep only the minimum functionality and should not be changed; -// For details, see buildsystem/codegen.cmake and openage/codegen. - - -#include -#include -#include "error/error.h" -#include "sound_dummy.h" -#include "util/strings.h" - - -namespace openage { -namespace gamedata { - -constexpr size_t sound::member_count; -constexpr size_t sound_item::member_count; -int sound::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', sound::member_count - ); - - if (buf.size() != sound::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing sound led to " - << buf.size() - << " columns (expected " - << sound::member_count - << ")!" - ); - } - - if (sscanf(buf[0].c_str(), "%hd", &this->sound_id) != 1) { return 0; } - if (sscanf(buf[1].c_str(), "%hd", &this->play_delay) != 1) { return 1; } - if (sscanf(buf[2].c_str(), "%d", &this->cache_time) != 1) { return 2; } - this->sound_items.filename = buf[3]; - - return -1; -} - -int sound_item::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', sound_item::member_count - ); - - if (buf.size() != sound_item::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing sound_item led to " - << buf.size() - << " columns (expected " - << sound_item::member_count - << ")!" - ); - } - - this->filename = buf[0]; - if (sscanf(buf[1].c_str(), "%d", &this->resource_id) != 1) { return 1; } - if (sscanf(buf[2].c_str(), "%hd", &this->probablilty) != 1) { return 2; } - if (sscanf(buf[3].c_str(), "%hd", &this->civilization_id) != 1) { return 3; } - if (sscanf(buf[4].c_str(), "%hd", &this->icon_set) != 1) { return 4; } - - return -1; -} - -bool sound_item::recurse(const openage::util::CSVCollection & /*storage*/, const std::string & /*basedir*/) { - return true; -} - -bool sound::recurse(const openage::util::CSVCollection &storage, const std::string &basedir) { - this->sound_items.read(storage, basedir); - - return true; -} - -} // gamedata -} // openage diff --git a/libopenage/gamedata/sound_dummy.h b/libopenage/gamedata/sound_dummy.h deleted file mode 100644 index 7c004cba5a..0000000000 --- a/libopenage/gamedata/sound_dummy.h +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2013-2021 the openage authors. See copying.md for legal info. - -// Warning: this file is a dummy file and was auto-generated by the v0.4.1 converter; -// its purpose is to keep the deprecated gamestate compilable and intact; -// these files keep only the minimum functionality and should not be changed; -// For details, see buildsystem/codegen.cmake and openage/codegen. - -#pragma once - -#include -#include -#include -#include "util/csv.h" - - - -namespace openage { -namespace gamedata { - -/** - * one possible file for a sound. - */ -struct sound_item { - std::string filename; - int32_t resource_id; - int16_t probablilty; - int16_t civilization_id; - int16_t icon_set; - static constexpr size_t member_count = 5; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -/** - * describes a sound, consisting of several sound items. - */ -struct sound { - int16_t sound_id; - int16_t play_delay; - int32_t cache_time; - openage::util::csv_subdata sound_items; - static constexpr size_t member_count = 4; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -} // gamedata -} // openage diff --git a/libopenage/gamedata/string_resource_dummy.cpp b/libopenage/gamedata/string_resource_dummy.cpp deleted file mode 100644 index 7030d15150..0000000000 --- a/libopenage/gamedata/string_resource_dummy.cpp +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2013-2021 the openage authors. See copying.md for legal info. - -// Warning: this file is a dummy file and was auto-generated by the v0.4.1 converter; -// its purpose is to keep the deprecated gamestate compilable and intact; -// these files keep only the minimum functionality and should not be changed; -// For details, see buildsystem/codegen.cmake and openage/codegen. - - -#include -#include -#include "error/error.h" -#include "string_resource_dummy.h" -#include "util/strings.h" - - -namespace openage { -namespace gamedata { - -constexpr size_t string_resource::member_count; -int string_resource::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', string_resource::member_count - ); - - if (buf.size() != string_resource::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing string_resource led to " - << buf.size() - << " columns (expected " - << string_resource::member_count - << ")!" - ); - } - - if (sscanf(buf[0].c_str(), "%d", &this->id) != 1) { return 0; } - this->lang = buf[1]; - this->text = buf[2]; - - return -1; -} - -bool string_resource::recurse(const openage::util::CSVCollection & /*storage*/, const std::string & /*basedir*/) { - return true; -} - -} // gamedata -} // openage diff --git a/libopenage/gamedata/string_resource_dummy.h b/libopenage/gamedata/string_resource_dummy.h deleted file mode 100644 index 7673489566..0000000000 --- a/libopenage/gamedata/string_resource_dummy.h +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2013-2021 the openage authors. See copying.md for legal info. - -// Warning: this file is a dummy file and was auto-generated by the v0.4.1 converter; -// its purpose is to keep the deprecated gamestate compilable and intact; -// these files keep only the minimum functionality and should not be changed; -// For details, see buildsystem/codegen.cmake and openage/codegen. - -#pragma once - -#include -#include -#include -#include "util/csv.h" - - - -namespace openage { -namespace gamedata { - -/** - * string id/language to text mapping, extracted from language.dll file. - */ -struct string_resource { - int32_t id; - std::string lang; - std::string text; - static constexpr size_t member_count = 3; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -} // gamedata -} // openage diff --git a/libopenage/gamedata/tech_dummy.cpp b/libopenage/gamedata/tech_dummy.cpp deleted file mode 100644 index f2697695db..0000000000 --- a/libopenage/gamedata/tech_dummy.cpp +++ /dev/null @@ -1,264 +0,0 @@ -// Copyright 2013-2021 the openage authors. See copying.md for legal info. - -// Warning: this file is a dummy file and was auto-generated by the v0.4.1 converter; -// its purpose is to keep the deprecated gamestate compilable and intact; -// these files keep only the minimum functionality and should not be changed; -// For details, see buildsystem/codegen.cmake and openage/codegen. - - -#include -#include -#include "error/error.h" -#include "tech_dummy.h" -#include "util/strings.h" - - -namespace openage { -namespace gamedata { - -constexpr size_t age_tech_tree::member_count; -constexpr size_t building_connection::member_count; -constexpr size_t effect_bundle::member_count; -constexpr size_t other_connection::member_count; -constexpr size_t research_connection::member_count; -constexpr size_t tech_effect::member_count; -constexpr size_t unit_connection::member_count; -int age_tech_tree::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', age_tech_tree::member_count - ); - - if (buf.size() != age_tech_tree::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing age_tech_tree led to " - << buf.size() - << " columns (expected " - << age_tech_tree::member_count - << ")!" - ); - } - - if (sscanf(buf[0].c_str(), "%d", &this->id) != 1) { return 0; } - if (sscanf(buf[1].c_str(), "%hhd", &this->status) != 1) { return 1; } - if (sscanf(buf[5].c_str(), "%d", &this->connected_slots_used) != 1) { return 5; } - this->other_connections.filename = buf[7]; - if (sscanf(buf[8].c_str(), "%hhd", &this->building_level_count) != 1) { return 8; } - if (sscanf(buf[11].c_str(), "%hhd", &this->max_age_length) != 1) { return 11; } - if (sscanf(buf[12].c_str(), "%d", &this->line_mode) != 1) { return 12; } - - return -1; -} - -bool age_tech_tree::recurse(const openage::util::CSVCollection &storage, const std::string &basedir) { - this->other_connections.read(storage, basedir); - - return true; -} - -int building_connection::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', building_connection::member_count - ); - - if (buf.size() != building_connection::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing building_connection led to " - << buf.size() - << " columns (expected " - << building_connection::member_count - << ")!" - ); - } - - if (sscanf(buf[0].c_str(), "%d", &this->id) != 1) { return 0; } - if (sscanf(buf[4].c_str(), "%d", &this->connected_slots_used) != 1) { return 4; } - this->other_connections.filename = buf[6]; - if (sscanf(buf[7].c_str(), "%hhd", &this->location_in_age) != 1) { return 7; } - if (sscanf(buf[10].c_str(), "%d", &this->line_mode) != 1) { return 10; } - if (sscanf(buf[11].c_str(), "%d", &this->enabling_research) != 1) { return 11; } - - return -1; -} - -bool building_connection::recurse(const openage::util::CSVCollection &storage, const std::string &basedir) { - this->other_connections.read(storage, basedir); - - return true; -} - -int effect_bundle::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', effect_bundle::member_count - ); - - if (buf.size() != effect_bundle::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing effect_bundle led to " - << buf.size() - << " columns (expected " - << effect_bundle::member_count - << ")!" - ); - } - - this->name = buf[0]; - this->effects.filename = buf[1]; - - return -1; -} - -bool effect_bundle::recurse(const openage::util::CSVCollection &storage, const std::string &basedir) { - this->effects.read(storage, basedir); - - return true; -} - -int other_connection::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', other_connection::member_count - ); - - if (buf.size() != other_connection::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing other_connection led to " - << buf.size() - << " columns (expected " - << other_connection::member_count - << ")!" - ); - } - - // parse enum connection_mode - if (buf[0] == "DUMMY") { - this->other_connection = connection_mode::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[0] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - - return -1; -} - -bool other_connection::recurse(const openage::util::CSVCollection & /*storage*/, const std::string & /*basedir*/) { - return true; -} - -int research_connection::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', research_connection::member_count - ); - - if (buf.size() != research_connection::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing research_connection led to " - << buf.size() - << " columns (expected " - << research_connection::member_count - << ")!" - ); - } - - if (sscanf(buf[0].c_str(), "%d", &this->id) != 1) { return 0; } - if (sscanf(buf[1].c_str(), "%hhd", &this->status) != 1) { return 1; } - if (sscanf(buf[2].c_str(), "%d", &this->upper_building) != 1) { return 2; } - if (sscanf(buf[6].c_str(), "%d", &this->connected_slots_used) != 1) { return 6; } - this->other_connections.filename = buf[8]; - if (sscanf(buf[9].c_str(), "%d", &this->vertical_line) != 1) { return 9; } - if (sscanf(buf[10].c_str(), "%d", &this->location_in_age) != 1) { return 10; } - if (sscanf(buf[11].c_str(), "%d", &this->line_mode) != 1) { return 11; } - - return -1; -} - -bool research_connection::recurse(const openage::util::CSVCollection &storage, const std::string &basedir) { - this->other_connections.read(storage, basedir); - - return true; -} - -int tech_effect::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', tech_effect::member_count - ); - - if (buf.size() != tech_effect::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing tech_effect led to " - << buf.size() - << " columns (expected " - << tech_effect::member_count - << ")!" - ); - } - - // parse enum effect_apply_type - if (buf[0] == "DUMMY") { - this->type_id = effect_apply_type::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[0] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[1].c_str(), "%hd", &this->attr_a) != 1) { return 1; } - if (sscanf(buf[2].c_str(), "%hd", &this->attr_b) != 1) { return 2; } - if (sscanf(buf[3].c_str(), "%hd", &this->attr_c) != 1) { return 3; } - if (sscanf(buf[4].c_str(), "%f", &this->attr_d) != 1) { return 4; } - - return -1; -} - -bool tech_effect::recurse(const openage::util::CSVCollection & /*storage*/, const std::string & /*basedir*/) { - return true; -} - -int unit_connection::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', unit_connection::member_count - ); - - if (buf.size() != unit_connection::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing unit_connection led to " - << buf.size() - << " columns (expected " - << unit_connection::member_count - << ")!" - ); - } - - if (sscanf(buf[0].c_str(), "%d", &this->id) != 1) { return 0; } - if (sscanf(buf[1].c_str(), "%hhd", &this->status) != 1) { return 1; } - if (sscanf(buf[2].c_str(), "%d", &this->upper_building) != 1) { return 2; } - if (sscanf(buf[3].c_str(), "%d", &this->connected_slots_used) != 1) { return 3; } - this->other_connections.filename = buf[5]; - if (sscanf(buf[6].c_str(), "%d", &this->vertical_line) != 1) { return 6; } - if (sscanf(buf[8].c_str(), "%d", &this->location_in_age) != 1) { return 8; } - if (sscanf(buf[9].c_str(), "%d", &this->required_research) != 1) { return 9; } - if (sscanf(buf[10].c_str(), "%d", &this->line_mode) != 1) { return 10; } - if (sscanf(buf[11].c_str(), "%d", &this->enabling_research) != 1) { return 11; } - - return -1; -} - -bool unit_connection::recurse(const openage::util::CSVCollection &storage, const std::string &basedir) { - this->other_connections.read(storage, basedir); - - return true; -} - -} // gamedata -} // openage diff --git a/libopenage/gamedata/tech_dummy.h b/libopenage/gamedata/tech_dummy.h deleted file mode 100644 index bac2cc13ff..0000000000 --- a/libopenage/gamedata/tech_dummy.h +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright 2013-2021 the openage authors. See copying.md for legal info. - -// Warning: this file is a dummy file and was auto-generated by the v0.4.1 converter; -// its purpose is to keep the deprecated gamestate compilable and intact; -// these files keep only the minimum functionality and should not be changed; -// For details, see buildsystem/codegen.cmake and openage/codegen. - -#pragma once - -#include -#include -#include -#include "util/csv.h" - - - -namespace openage { -namespace gamedata { - -enum class connection_mode { - DUMMY -}; - - -enum class effect_apply_type { - DUMMY -}; - - -/** - * misc connection for a building/unit/research connection - */ -struct other_connection { - connection_mode other_connection; - static constexpr size_t member_count = 1; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -/** - * items available when this age was reached. - */ -struct age_tech_tree { - int32_t id; - int8_t status; - int32_t connected_slots_used; - openage::util::csv_subdata other_connections; - int8_t building_level_count; - int8_t max_age_length; - int32_t line_mode; - static constexpr size_t member_count = 13; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -/** - * new available buildings/units/researches when this building was created. - */ -struct building_connection { - int32_t id; - int32_t connected_slots_used; - openage::util::csv_subdata other_connections; - int8_t location_in_age; - int32_t line_mode; - int32_t enabling_research; - static constexpr size_t member_count = 12; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -/** - * applied effect for a research technology. - */ -struct tech_effect { - effect_apply_type type_id; - int16_t attr_a; - int16_t attr_b; - int16_t attr_c; - float attr_d; - static constexpr size_t member_count = 5; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -/** - * a bundle of effects. - */ -struct effect_bundle { - std::string name; - openage::util::csv_subdata effects; - static constexpr size_t member_count = 2; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -/** - * research updates to apply when activating the technology. - */ -struct research_connection { - int32_t id; - int8_t status; - int32_t upper_building; - int32_t connected_slots_used; - openage::util::csv_subdata other_connections; - int32_t vertical_line; - int32_t location_in_age; - int32_t line_mode; - static constexpr size_t member_count = 12; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -/** - * unit updates to apply when activating the technology. - */ -struct unit_connection { - int32_t id; - int8_t status; - int32_t upper_building; - int32_t connected_slots_used; - openage::util::csv_subdata other_connections; - int32_t vertical_line; - int32_t location_in_age; - int32_t required_research; - int32_t line_mode; - int32_t enabling_research; - static constexpr size_t member_count = 12; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -} // gamedata -} // openage diff --git a/libopenage/gamedata/terrain_dummy.cpp b/libopenage/gamedata/terrain_dummy.cpp deleted file mode 100644 index d99f430a4f..0000000000 --- a/libopenage/gamedata/terrain_dummy.cpp +++ /dev/null @@ -1,274 +0,0 @@ -// Copyright 2013-2021 the openage authors. See copying.md for legal info. - -// Warning: this file is a dummy file and was auto-generated by the v0.4.1 converter; -// its purpose is to keep the deprecated gamestate compilable and intact; -// these files keep only the minimum functionality and should not be changed; -// For details, see buildsystem/codegen.cmake and openage/codegen. - - -#include -#include -#include "error/error.h" -#include "terrain_dummy.h" -#include "util/strings.h" - - -namespace openage { -namespace gamedata { - -constexpr size_t frame_data::member_count; -constexpr size_t terrain_animation::member_count; -constexpr size_t terrain_border::member_count; -constexpr size_t terrain_pass_graphic::member_count; -constexpr size_t terrain_restriction::member_count; -constexpr size_t terrain_type::member_count; -constexpr size_t tile_size::member_count; -int frame_data::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', frame_data::member_count - ); - - if (buf.size() != frame_data::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing frame_data led to " - << buf.size() - << " columns (expected " - << frame_data::member_count - << ")!" - ); - } - - if (sscanf(buf[0].c_str(), "%hd", &this->frame_count) != 1) { return 0; } - if (sscanf(buf[1].c_str(), "%hd", &this->angle_count) != 1) { return 1; } - if (sscanf(buf[2].c_str(), "%hd", &this->shape_id) != 1) { return 2; } - - return -1; -} - -bool frame_data::recurse(const openage::util::CSVCollection & /*storage*/, const std::string & /*basedir*/) { - return true; -} - -int terrain_animation::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', terrain_animation::member_count - ); - - if (buf.size() != terrain_animation::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing terrain_animation led to " - << buf.size() - << " columns (expected " - << terrain_animation::member_count - << ")!" - ); - } - - if (sscanf(buf[0].c_str(), "%hhd", &this->is_animated) != 1) { return 0; } - if (sscanf(buf[1].c_str(), "%hd", &this->animation_frame_count) != 1) { return 1; } - if (sscanf(buf[2].c_str(), "%hd", &this->pause_frame_count) != 1) { return 2; } - if (sscanf(buf[3].c_str(), "%f", &this->interval) != 1) { return 3; } - if (sscanf(buf[4].c_str(), "%f", &this->pause_between_loops) != 1) { return 4; } - if (sscanf(buf[5].c_str(), "%hd", &this->frame) != 1) { return 5; } - if (sscanf(buf[6].c_str(), "%hd", &this->draw_frame) != 1) { return 6; } - if (sscanf(buf[7].c_str(), "%f", &this->animate_last) != 1) { return 7; } - if (sscanf(buf[8].c_str(), "%hhd", &this->frame_changed) != 1) { return 8; } - if (sscanf(buf[9].c_str(), "%hhd", &this->drawn) != 1) { return 9; } - - return -1; -} - -bool terrain_animation::recurse(const openage::util::CSVCollection & /*storage*/, const std::string & /*basedir*/) { - return true; -} - -int terrain_border::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', terrain_border::member_count - ); - - if (buf.size() != terrain_border::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing terrain_border led to " - << buf.size() - << " columns (expected " - << terrain_border::member_count - << ")!" - ); - } - - if (sscanf(buf[0].c_str(), "%hhd", &this->enabled) != 1) { return 0; } - if (sscanf(buf[1].c_str(), "%hhd", &this->random) != 1) { return 1; } - this->internal_name = buf[2]; - this->filename = buf[3]; - if (sscanf(buf[4].c_str(), "%d", &this->slp_id) != 1) { return 4; } - if (sscanf(buf[5].c_str(), "%d", &this->shape_ptr) != 1) { return 5; } - if (sscanf(buf[6].c_str(), "%d", &this->sound_id) != 1) { return 6; } - if (sscanf(buf[8].c_str(), "%hhd", &this->is_animated) != 1) { return 8; } - if (sscanf(buf[9].c_str(), "%hd", &this->animation_frame_count) != 1) { return 9; } - if (sscanf(buf[10].c_str(), "%hd", &this->pause_frame_count) != 1) { return 10; } - if (sscanf(buf[11].c_str(), "%f", &this->interval) != 1) { return 11; } - if (sscanf(buf[12].c_str(), "%f", &this->pause_between_loops) != 1) { return 12; } - if (sscanf(buf[13].c_str(), "%hd", &this->frame) != 1) { return 13; } - if (sscanf(buf[14].c_str(), "%hd", &this->draw_frame) != 1) { return 14; } - if (sscanf(buf[15].c_str(), "%f", &this->animate_last) != 1) { return 15; } - if (sscanf(buf[16].c_str(), "%hhd", &this->frame_changed) != 1) { return 16; } - if (sscanf(buf[17].c_str(), "%hhd", &this->drawn) != 1) { return 17; } - this->frames.filename = buf[18]; - if (sscanf(buf[19].c_str(), "%hd", &this->draw_tile) != 1) { return 19; } - if (sscanf(buf[20].c_str(), "%hd", &this->underlay_terrain) != 1) { return 20; } - if (sscanf(buf[21].c_str(), "%hd", &this->border_style) != 1) { return 21; } - - return -1; -} - -bool terrain_border::recurse(const openage::util::CSVCollection &storage, const std::string &basedir) { - this->frames.read(storage, basedir); - - return true; -} - -int terrain_pass_graphic::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', terrain_pass_graphic::member_count - ); - - if (buf.size() != terrain_pass_graphic::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing terrain_pass_graphic led to " - << buf.size() - << " columns (expected " - << terrain_pass_graphic::member_count - << ")!" - ); - } - - if (sscanf(buf[0].c_str(), "%d", &this->slp_id_exit_tile) != 1) { return 0; } - if (sscanf(buf[1].c_str(), "%d", &this->slp_id_enter_tile) != 1) { return 1; } - if (sscanf(buf[2].c_str(), "%d", &this->slp_id_walk_tile) != 1) { return 2; } - if (sscanf(buf[3].c_str(), "%d", &this->replication_amount) != 1) { return 3; } - - return -1; -} - -bool terrain_pass_graphic::recurse(const openage::util::CSVCollection & /*storage*/, const std::string & /*basedir*/) { - return true; -} - -int terrain_restriction::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', terrain_restriction::member_count - ); - - if (buf.size() != terrain_restriction::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing terrain_restriction led to " - << buf.size() - << " columns (expected " - << terrain_restriction::member_count - << ")!" - ); - } - - this->pass_graphics.filename = buf[1]; - - return -1; -} - -bool terrain_restriction::recurse(const openage::util::CSVCollection &storage, const std::string &basedir) { - this->pass_graphics.read(storage, basedir); - - return true; -} - -int terrain_type::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', terrain_type::member_count - ); - - if (buf.size() != terrain_type::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing terrain_type led to " - << buf.size() - << " columns (expected " - << terrain_type::member_count - << ")!" - ); - } - - if (sscanf(buf[0].c_str(), "%hhd", &this->enabled) != 1) { return 0; } - if (sscanf(buf[1].c_str(), "%hhd", &this->random) != 1) { return 1; } - this->internal_name = buf[2]; - this->filename = buf[3]; - if (sscanf(buf[4].c_str(), "%d", &this->slp_id) != 1) { return 4; } - if (sscanf(buf[5].c_str(), "%d", &this->shape_ptr) != 1) { return 5; } - if (sscanf(buf[6].c_str(), "%d", &this->sound_id) != 1) { return 6; } - if (sscanf(buf[7].c_str(), "%d", &this->blend_priority) != 1) { return 7; } - if (sscanf(buf[8].c_str(), "%d", &this->blend_mode) != 1) { return 8; } - if (sscanf(buf[9].c_str(), "%hhu", &this->map_color_hi) != 1) { return 9; } - if (sscanf(buf[10].c_str(), "%hhu", &this->map_color_med) != 1) { return 10; } - if (sscanf(buf[11].c_str(), "%hhu", &this->map_color_low) != 1) { return 11; } - if (sscanf(buf[12].c_str(), "%hhu", &this->map_color_cliff_lt) != 1) { return 12; } - if (sscanf(buf[13].c_str(), "%hhu", &this->map_color_cliff_rt) != 1) { return 13; } - if (sscanf(buf[14].c_str(), "%hhd", &this->passable_terrain) != 1) { return 14; } - if (sscanf(buf[15].c_str(), "%hhd", &this->impassable_terrain) != 1) { return 15; } - if (sscanf(buf[16].c_str(), "%hhd", &this->is_animated) != 1) { return 16; } - if (sscanf(buf[17].c_str(), "%hd", &this->animation_frame_count) != 1) { return 17; } - if (sscanf(buf[18].c_str(), "%hd", &this->pause_frame_count) != 1) { return 18; } - if (sscanf(buf[19].c_str(), "%f", &this->interval) != 1) { return 19; } - if (sscanf(buf[20].c_str(), "%f", &this->pause_between_loops) != 1) { return 20; } - if (sscanf(buf[21].c_str(), "%hd", &this->frame) != 1) { return 21; } - if (sscanf(buf[22].c_str(), "%hd", &this->draw_frame) != 1) { return 22; } - if (sscanf(buf[23].c_str(), "%f", &this->animate_last) != 1) { return 23; } - if (sscanf(buf[24].c_str(), "%hhd", &this->frame_changed) != 1) { return 24; } - if (sscanf(buf[25].c_str(), "%hhd", &this->drawn) != 1) { return 25; } - this->elevation_graphics.filename = buf[26]; - if (sscanf(buf[27].c_str(), "%hd", &this->terrain_replacement_id) != 1) { return 27; } - if (sscanf(buf[28].c_str(), "%hd", &this->terrain_to_draw0) != 1) { return 28; } - if (sscanf(buf[29].c_str(), "%hd", &this->terrain_to_draw1) != 1) { return 29; } - if (sscanf(buf[34].c_str(), "%hd", &this->terrain_units_used_count) != 1) { return 34; } - - return -1; -} - -bool terrain_type::recurse(const openage::util::CSVCollection &storage, const std::string &basedir) { - this->elevation_graphics.read(storage, basedir); - - return true; -} - -int tile_size::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', tile_size::member_count - ); - - if (buf.size() != tile_size::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing tile_size led to " - << buf.size() - << " columns (expected " - << tile_size::member_count - << ")!" - ); - } - - if (sscanf(buf[0].c_str(), "%hd", &this->width) != 1) { return 0; } - if (sscanf(buf[1].c_str(), "%hd", &this->height) != 1) { return 1; } - if (sscanf(buf[2].c_str(), "%hd", &this->delta_z) != 1) { return 2; } - - return -1; -} - -bool tile_size::recurse(const openage::util::CSVCollection & /*storage*/, const std::string & /*basedir*/) { - return true; -} - -} // gamedata -} // openage diff --git a/libopenage/gamedata/terrain_dummy.h b/libopenage/gamedata/terrain_dummy.h deleted file mode 100644 index 6985296ed0..0000000000 --- a/libopenage/gamedata/terrain_dummy.h +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright 2013-2021 the openage authors. See copying.md for legal info. - -// Warning: this file is a dummy file and was auto-generated by the v0.4.1 converter; -// its purpose is to keep the deprecated gamestate compilable and intact; -// these files keep only the minimum functionality and should not be changed; -// For details, see buildsystem/codegen.cmake and openage/codegen. - -#pragma once - -#include -#include -#include -#include "util/csv.h" - - - -namespace openage { -namespace gamedata { - -/** - * specification of terrain frames. - */ -struct frame_data { - int16_t frame_count; - int16_t angle_count; - int16_t shape_id; - static constexpr size_t member_count = 3; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -/** - * describes animation properties of a terrain type - */ -struct terrain_animation { - int8_t is_animated; - int16_t animation_frame_count; - int16_t pause_frame_count; - float interval; - float pause_between_loops; - int16_t frame; - int16_t draw_frame; - float animate_last; - int8_t frame_changed; - int8_t drawn; - static constexpr size_t member_count = 10; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -struct terrain_pass_graphic { - int32_t slp_id_exit_tile; - int32_t slp_id_enter_tile; - int32_t slp_id_walk_tile; - int32_t replication_amount; - static constexpr size_t member_count = 4; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -/** - * size definition of one terrain tile. - */ -struct tile_size { - int16_t width; - int16_t height; - int16_t delta_z; - static constexpr size_t member_count = 3; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -/** - * TODO - */ -struct terrain_restriction { - openage::util::csv_subdata pass_graphics; - static constexpr size_t member_count = 2; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -/** - * one inter-terraintile border specification. - */ -struct terrain_border : terrain_animation { - int8_t enabled; - int8_t random; - std::string internal_name; - std::string filename; - int32_t slp_id; - int32_t shape_ptr; - int32_t sound_id; - openage::util::csv_subdata frames; - int16_t draw_tile; - int16_t underlay_terrain; - int16_t border_style; - static constexpr size_t member_count = 22; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -/** - * describes a terrain type, like water, ice, etc. - */ -struct terrain_type : terrain_animation { - int8_t enabled; - int8_t random; - std::string internal_name; - std::string filename; - int32_t slp_id; - int32_t shape_ptr; - int32_t sound_id; - int32_t blend_priority; - int32_t blend_mode; - uint8_t map_color_hi; - uint8_t map_color_med; - uint8_t map_color_low; - uint8_t map_color_cliff_lt; - uint8_t map_color_cliff_rt; - int8_t passable_terrain; - int8_t impassable_terrain; - openage::util::csv_subdata elevation_graphics; - int16_t terrain_replacement_id; - int16_t terrain_to_draw0; - int16_t terrain_to_draw1; - int16_t terrain_units_used_count; - static constexpr size_t member_count = 35; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -} // gamedata -} // openage diff --git a/libopenage/gamedata/texture_dummy.cpp b/libopenage/gamedata/texture_dummy.cpp deleted file mode 100644 index 6d599c663b..0000000000 --- a/libopenage/gamedata/texture_dummy.cpp +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2013-2021 the openage authors. See copying.md for legal info. - -// Warning: this file is a dummy file and was auto-generated by the v0.4.1 converter; -// its purpose is to keep the deprecated gamestate compilable and intact; -// these files keep only the minimum functionality and should not be changed; -// For details, see buildsystem/codegen.cmake and openage/codegen. - - -#include -#include -#include "error/error.h" -#include "texture_dummy.h" -#include "util/strings.h" - - -namespace openage { -namespace gamedata { - -constexpr size_t subtexture::member_count; -int subtexture::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', subtexture::member_count - ); - - if (buf.size() != subtexture::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing subtexture led to " - << buf.size() - << " columns (expected " - << subtexture::member_count - << ")!" - ); - } - - if (sscanf(buf[0].c_str(), "%d", &this->x) != 1) { return 0; } - if (sscanf(buf[1].c_str(), "%d", &this->y) != 1) { return 1; } - if (sscanf(buf[2].c_str(), "%d", &this->w) != 1) { return 2; } - if (sscanf(buf[3].c_str(), "%d", &this->h) != 1) { return 3; } - if (sscanf(buf[4].c_str(), "%d", &this->cx) != 1) { return 4; } - if (sscanf(buf[5].c_str(), "%d", &this->cy) != 1) { return 5; } - - return -1; -} - -bool subtexture::recurse(const openage::util::CSVCollection & /*storage*/, const std::string & /*basedir*/) { - return true; -} - -} // gamedata -} // openage diff --git a/libopenage/gamedata/texture_dummy.h b/libopenage/gamedata/texture_dummy.h deleted file mode 100644 index 9ac92d4e3e..0000000000 --- a/libopenage/gamedata/texture_dummy.h +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2013-2021 the openage authors. See copying.md for legal info. - -// Warning: this file is a dummy file and was auto-generated by the v0.4.1 converter; -// its purpose is to keep the deprecated gamestate compilable and intact; -// these files keep only the minimum functionality and should not be changed; -// For details, see buildsystem/codegen.cmake and openage/codegen. - -#pragma once - -#include -#include -#include -#include "util/csv.h" - - - -namespace openage { -namespace gamedata { - -/** - * one sprite, as part of a texture atlas. - * - * this struct stores information about positions and sizes - * of sprites included in the 'big texture'. - */ -struct subtexture { - int32_t x; - int32_t y; - int32_t w; - int32_t h; - int32_t cx; - int32_t cy; - static constexpr size_t member_count = 6; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -} // gamedata -} // openage diff --git a/libopenage/gamedata/unit_dummy.cpp b/libopenage/gamedata/unit_dummy.cpp deleted file mode 100644 index 190e6774a3..0000000000 --- a/libopenage/gamedata/unit_dummy.cpp +++ /dev/null @@ -1,2820 +0,0 @@ -// Copyright 2013-2021 the openage authors. See copying.md for legal info. - -// Warning: this file is a dummy file and was auto-generated by the v0.4.1 converter; -// its purpose is to keep the deprecated gamestate compilable and intact; -// these files keep only the minimum functionality and should not be changed; -// For details, see buildsystem/codegen.cmake and openage/codegen. - - -#include -#include -#include "error/error.h" -#include "unit_dummy.h" -#include "util/strings.h" - - -namespace openage { -namespace gamedata { - -constexpr size_t action_unit::member_count; -constexpr size_t animated_unit::member_count; -constexpr size_t building_annex::member_count; -constexpr size_t building_unit::member_count; -constexpr size_t damage_graphic::member_count; -constexpr size_t doppelganger_unit::member_count; -constexpr size_t hit_type::member_count; -constexpr size_t living_unit::member_count; -constexpr size_t missile_unit::member_count; -constexpr size_t moving_unit::member_count; -constexpr size_t projectile_unit::member_count; -constexpr size_t resource_cost::member_count; -constexpr size_t resource_storage::member_count; -constexpr size_t tree_unit::member_count; -constexpr size_t unit_command::member_count; -constexpr size_t unit_header::member_count; -constexpr size_t unit_object::member_count; -int action_unit::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', action_unit::member_count - ); - - if (buf.size() != action_unit::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing action_unit led to " - << buf.size() - << " columns (expected " - << action_unit::member_count - << ")!" - ); - } - - if (sscanf(buf[0].c_str(), "%hd", &this->id0) != 1) { return 0; } - if (sscanf(buf[1].c_str(), "%hu", &this->language_dll_name) != 1) { return 1; } - if (sscanf(buf[2].c_str(), "%hu", &this->language_dll_creation) != 1) { return 2; } - // parse enum unit_classes - if (buf[3] == "DUMMY") { - this->unit_class = unit_classes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[3] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[4].c_str(), "%hd", &this->idle_graphic0) != 1) { return 4; } - if (sscanf(buf[5].c_str(), "%hd", &this->idle_graphic1) != 1) { return 5; } - if (sscanf(buf[6].c_str(), "%hd", &this->dying_graphic) != 1) { return 6; } - if (sscanf(buf[7].c_str(), "%hd", &this->undead_graphic) != 1) { return 7; } - if (sscanf(buf[8].c_str(), "%hhd", &this->death_mode) != 1) { return 8; } - if (sscanf(buf[9].c_str(), "%hd", &this->hit_points) != 1) { return 9; } - if (sscanf(buf[10].c_str(), "%f", &this->line_of_sight) != 1) { return 10; } - if (sscanf(buf[11].c_str(), "%hhd", &this->garrison_capacity) != 1) { return 11; } - if (sscanf(buf[12].c_str(), "%f", &this->radius_x) != 1) { return 12; } - if (sscanf(buf[13].c_str(), "%f", &this->radius_y) != 1) { return 13; } - if (sscanf(buf[14].c_str(), "%f", &this->radius_z) != 1) { return 14; } - if (sscanf(buf[15].c_str(), "%hd", &this->train_sound_id) != 1) { return 15; } - if (sscanf(buf[16].c_str(), "%hd", &this->damage_sound_id) != 1) { return 16; } - if (sscanf(buf[17].c_str(), "%hd", &this->dead_unit_id) != 1) { return 17; } - if (sscanf(buf[18].c_str(), "%hhd", &this->placement_mode) != 1) { return 18; } - if (sscanf(buf[19].c_str(), "%hhd", &this->can_be_built_on) != 1) { return 19; } - if (sscanf(buf[20].c_str(), "%hd", &this->icon_id) != 1) { return 20; } - if (sscanf(buf[21].c_str(), "%hhd", &this->hidden_in_editor) != 1) { return 21; } - if (sscanf(buf[22].c_str(), "%hd", &this->old_portrait_icon_id) != 1) { return 22; } - if (sscanf(buf[23].c_str(), "%hhd", &this->enabled) != 1) { return 23; } - if (sscanf(buf[24].c_str(), "%hd", &this->placement_side_terrain0) != 1) { return 24; } - if (sscanf(buf[25].c_str(), "%hd", &this->placement_side_terrain1) != 1) { return 25; } - if (sscanf(buf[26].c_str(), "%hd", &this->placement_terrain0) != 1) { return 26; } - if (sscanf(buf[27].c_str(), "%hd", &this->placement_terrain1) != 1) { return 27; } - if (sscanf(buf[28].c_str(), "%f", &this->clearance_size_x) != 1) { return 28; } - if (sscanf(buf[29].c_str(), "%f", &this->clearance_size_y) != 1) { return 29; } - // parse enum elevation_modes - if (buf[30] == "DUMMY") { - this->elevation_mode = elevation_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[30] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum fog_visibility - if (buf[31] == "DUMMY") { - this->visible_in_fog = fog_visibility::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[31] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum ground_type - if (buf[32] == "DUMMY") { - this->terrain_restriction = ground_type::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[32] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[33].c_str(), "%hhd", &this->fly_mode) != 1) { return 33; } - if (sscanf(buf[34].c_str(), "%hd", &this->resource_capacity) != 1) { return 34; } - if (sscanf(buf[35].c_str(), "%f", &this->resource_decay) != 1) { return 35; } - // parse enum blast_types - if (buf[36] == "DUMMY") { - this->blast_defense_level = blast_types::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[36] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum combat_levels - if (buf[37] == "DUMMY") { - this->combat_level = combat_levels::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[37] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum interaction_modes - if (buf[38] == "DUMMY") { - this->interaction_mode = interaction_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[38] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum minimap_modes - if (buf[39] == "DUMMY") { - this->map_draw_level = minimap_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[39] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum command_attributes - if (buf[40] == "DUMMY") { - this->unit_level = command_attributes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[40] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[41].c_str(), "%f", &this->attack_reaction) != 1) { return 41; } - if (sscanf(buf[42].c_str(), "%hhd", &this->minimap_color) != 1) { return 42; } - if (sscanf(buf[43].c_str(), "%d", &this->language_dll_help) != 1) { return 43; } - if (sscanf(buf[44].c_str(), "%d", &this->language_dll_hotkey_text) != 1) { return 44; } - if (sscanf(buf[45].c_str(), "%d", &this->hot_keys) != 1) { return 45; } - if (sscanf(buf[46].c_str(), "%hhd", &this->recyclable) != 1) { return 46; } - if (sscanf(buf[47].c_str(), "%hhd", &this->enable_auto_gather) != 1) { return 47; } - if (sscanf(buf[48].c_str(), "%hhd", &this->doppelgaenger_on_death) != 1) { return 48; } - if (sscanf(buf[49].c_str(), "%hhd", &this->resource_gather_drop) != 1) { return 49; } - if (sscanf(buf[50].c_str(), "%hhu", &this->occlusion_mode) != 1) { return 50; } - // parse enum obstruction_types - if (buf[51] == "DUMMY") { - this->obstruction_type = obstruction_types::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[51] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[52].c_str(), "%hhd", &this->obstruction_class) != 1) { return 52; } - if (sscanf(buf[53].c_str(), "%hhu", &this->trait) != 1) { return 53; } - if (sscanf(buf[54].c_str(), "%hhd", &this->civilization_id) != 1) { return 54; } - if (sscanf(buf[55].c_str(), "%hd", &this->attribute_piece) != 1) { return 55; } - // parse enum selection_effects - if (buf[56] == "DUMMY") { - this->selection_effect = selection_effects::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[56] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[65].c_str(), "%hhd", &this->convert_terrain) != 1) { return 65; } - this->name = buf[66]; - if (sscanf(buf[67].c_str(), "%hd", &this->id1) != 1) { return 67; } - if (sscanf(buf[68].c_str(), "%hd", &this->id2) != 1) { return 68; } - if (sscanf(buf[69].c_str(), "%f", &this->speed) != 1) { return 69; } - if (sscanf(buf[70].c_str(), "%hd", &this->move_graphics) != 1) { return 70; } - if (sscanf(buf[71].c_str(), "%hd", &this->run_graphics) != 1) { return 71; } - if (sscanf(buf[72].c_str(), "%f", &this->turn_speed) != 1) { return 72; } - if (sscanf(buf[73].c_str(), "%hhd", &this->old_size_class) != 1) { return 73; } - if (sscanf(buf[74].c_str(), "%hd", &this->trail_unit_id) != 1) { return 74; } - if (sscanf(buf[75].c_str(), "%hhu", &this->trail_opsions) != 1) { return 75; } - if (sscanf(buf[76].c_str(), "%f", &this->trail_spacing) != 1) { return 76; } - if (sscanf(buf[77].c_str(), "%hhd", &this->old_move_algorithm) != 1) { return 77; } - if (sscanf(buf[78].c_str(), "%f", &this->turn_radius) != 1) { return 78; } - if (sscanf(buf[79].c_str(), "%f", &this->turn_radius_speed) != 1) { return 79; } - if (sscanf(buf[80].c_str(), "%f", &this->max_yaw_per_sec_moving) != 1) { return 80; } - if (sscanf(buf[81].c_str(), "%f", &this->stationary_yaw_revolution_time) != 1) { return 81; } - if (sscanf(buf[82].c_str(), "%f", &this->max_yaw_per_sec_stationary) != 1) { return 82; } - if (sscanf(buf[83].c_str(), "%hd", &this->default_task_id) != 1) { return 83; } - if (sscanf(buf[84].c_str(), "%f", &this->search_radius) != 1) { return 84; } - if (sscanf(buf[85].c_str(), "%f", &this->work_rate) != 1) { return 85; } - if (sscanf(buf[87].c_str(), "%hhd", &this->task_group) != 1) { return 87; } - if (sscanf(buf[88].c_str(), "%hd", &this->command_sound_id) != 1) { return 88; } - if (sscanf(buf[89].c_str(), "%hd", &this->stop_sound_id) != 1) { return 89; } - if (sscanf(buf[90].c_str(), "%hhd", &this->run_pattern) != 1) { return 90; } - - return -1; -} - -bool action_unit::recurse(const openage::util::CSVCollection &storage, const std::string &basedir) { - this->resource_storage.read(storage, basedir); - this->damage_graphics.read(storage, basedir); - - return true; -} - -int animated_unit::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', animated_unit::member_count - ); - - if (buf.size() != animated_unit::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing animated_unit led to " - << buf.size() - << " columns (expected " - << animated_unit::member_count - << ")!" - ); - } - - if (sscanf(buf[0].c_str(), "%hd", &this->id0) != 1) { return 0; } - if (sscanf(buf[1].c_str(), "%hu", &this->language_dll_name) != 1) { return 1; } - if (sscanf(buf[2].c_str(), "%hu", &this->language_dll_creation) != 1) { return 2; } - // parse enum unit_classes - if (buf[3] == "DUMMY") { - this->unit_class = unit_classes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[3] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[4].c_str(), "%hd", &this->idle_graphic0) != 1) { return 4; } - if (sscanf(buf[5].c_str(), "%hd", &this->idle_graphic1) != 1) { return 5; } - if (sscanf(buf[6].c_str(), "%hd", &this->dying_graphic) != 1) { return 6; } - if (sscanf(buf[7].c_str(), "%hd", &this->undead_graphic) != 1) { return 7; } - if (sscanf(buf[8].c_str(), "%hhd", &this->death_mode) != 1) { return 8; } - if (sscanf(buf[9].c_str(), "%hd", &this->hit_points) != 1) { return 9; } - if (sscanf(buf[10].c_str(), "%f", &this->line_of_sight) != 1) { return 10; } - if (sscanf(buf[11].c_str(), "%hhd", &this->garrison_capacity) != 1) { return 11; } - if (sscanf(buf[12].c_str(), "%f", &this->radius_x) != 1) { return 12; } - if (sscanf(buf[13].c_str(), "%f", &this->radius_y) != 1) { return 13; } - if (sscanf(buf[14].c_str(), "%f", &this->radius_z) != 1) { return 14; } - if (sscanf(buf[15].c_str(), "%hd", &this->train_sound_id) != 1) { return 15; } - if (sscanf(buf[16].c_str(), "%hd", &this->damage_sound_id) != 1) { return 16; } - if (sscanf(buf[17].c_str(), "%hd", &this->dead_unit_id) != 1) { return 17; } - if (sscanf(buf[18].c_str(), "%hhd", &this->placement_mode) != 1) { return 18; } - if (sscanf(buf[19].c_str(), "%hhd", &this->can_be_built_on) != 1) { return 19; } - if (sscanf(buf[20].c_str(), "%hd", &this->icon_id) != 1) { return 20; } - if (sscanf(buf[21].c_str(), "%hhd", &this->hidden_in_editor) != 1) { return 21; } - if (sscanf(buf[22].c_str(), "%hd", &this->old_portrait_icon_id) != 1) { return 22; } - if (sscanf(buf[23].c_str(), "%hhd", &this->enabled) != 1) { return 23; } - if (sscanf(buf[24].c_str(), "%hd", &this->placement_side_terrain0) != 1) { return 24; } - if (sscanf(buf[25].c_str(), "%hd", &this->placement_side_terrain1) != 1) { return 25; } - if (sscanf(buf[26].c_str(), "%hd", &this->placement_terrain0) != 1) { return 26; } - if (sscanf(buf[27].c_str(), "%hd", &this->placement_terrain1) != 1) { return 27; } - if (sscanf(buf[28].c_str(), "%f", &this->clearance_size_x) != 1) { return 28; } - if (sscanf(buf[29].c_str(), "%f", &this->clearance_size_y) != 1) { return 29; } - // parse enum elevation_modes - if (buf[30] == "DUMMY") { - this->elevation_mode = elevation_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[30] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum fog_visibility - if (buf[31] == "DUMMY") { - this->visible_in_fog = fog_visibility::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[31] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum ground_type - if (buf[32] == "DUMMY") { - this->terrain_restriction = ground_type::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[32] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[33].c_str(), "%hhd", &this->fly_mode) != 1) { return 33; } - if (sscanf(buf[34].c_str(), "%hd", &this->resource_capacity) != 1) { return 34; } - if (sscanf(buf[35].c_str(), "%f", &this->resource_decay) != 1) { return 35; } - // parse enum blast_types - if (buf[36] == "DUMMY") { - this->blast_defense_level = blast_types::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[36] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum combat_levels - if (buf[37] == "DUMMY") { - this->combat_level = combat_levels::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[37] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum interaction_modes - if (buf[38] == "DUMMY") { - this->interaction_mode = interaction_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[38] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum minimap_modes - if (buf[39] == "DUMMY") { - this->map_draw_level = minimap_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[39] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum command_attributes - if (buf[40] == "DUMMY") { - this->unit_level = command_attributes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[40] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[41].c_str(), "%f", &this->attack_reaction) != 1) { return 41; } - if (sscanf(buf[42].c_str(), "%hhd", &this->minimap_color) != 1) { return 42; } - if (sscanf(buf[43].c_str(), "%d", &this->language_dll_help) != 1) { return 43; } - if (sscanf(buf[44].c_str(), "%d", &this->language_dll_hotkey_text) != 1) { return 44; } - if (sscanf(buf[45].c_str(), "%d", &this->hot_keys) != 1) { return 45; } - if (sscanf(buf[46].c_str(), "%hhd", &this->recyclable) != 1) { return 46; } - if (sscanf(buf[47].c_str(), "%hhd", &this->enable_auto_gather) != 1) { return 47; } - if (sscanf(buf[48].c_str(), "%hhd", &this->doppelgaenger_on_death) != 1) { return 48; } - if (sscanf(buf[49].c_str(), "%hhd", &this->resource_gather_drop) != 1) { return 49; } - if (sscanf(buf[50].c_str(), "%hhu", &this->occlusion_mode) != 1) { return 50; } - // parse enum obstruction_types - if (buf[51] == "DUMMY") { - this->obstruction_type = obstruction_types::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[51] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[52].c_str(), "%hhd", &this->obstruction_class) != 1) { return 52; } - if (sscanf(buf[53].c_str(), "%hhu", &this->trait) != 1) { return 53; } - if (sscanf(buf[54].c_str(), "%hhd", &this->civilization_id) != 1) { return 54; } - if (sscanf(buf[55].c_str(), "%hd", &this->attribute_piece) != 1) { return 55; } - // parse enum selection_effects - if (buf[56] == "DUMMY") { - this->selection_effect = selection_effects::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[56] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[57].c_str(), "%f", &this->selection_shape_x) != 1) { return 57; } - if (sscanf(buf[58].c_str(), "%f", &this->selection_shape_y) != 1) { return 58; } - if (sscanf(buf[59].c_str(), "%f", &this->selection_shape_z) != 1) { return 59; } - this->resource_storage.filename = buf[60]; - this->damage_graphics.filename = buf[61]; - if (sscanf(buf[62].c_str(), "%hd", &this->selection_sound_id) != 1) { return 62; } - if (sscanf(buf[63].c_str(), "%hd", &this->dying_sound_id) != 1) { return 63; } - // parse enum attack_modes - if (buf[64] == "DUMMY") { - this->old_attack_mode = attack_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[64] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[65].c_str(), "%hhd", &this->convert_terrain) != 1) { return 65; } - this->name = buf[66]; - if (sscanf(buf[67].c_str(), "%hd", &this->id1) != 1) { return 67; } - if (sscanf(buf[68].c_str(), "%hd", &this->id2) != 1) { return 68; } - if (sscanf(buf[69].c_str(), "%f", &this->speed) != 1) { return 69; } - - return -1; -} - -bool animated_unit::recurse(const openage::util::CSVCollection &storage, const std::string &basedir) { - this->resource_storage.read(storage, basedir); - this->damage_graphics.read(storage, basedir); - - return true; -} - -int building_annex::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', building_annex::member_count - ); - - if (buf.size() != building_annex::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing building_annex led to " - << buf.size() - << " columns (expected " - << building_annex::member_count - << ")!" - ); - } - - if (sscanf(buf[0].c_str(), "%hd", &this->unit_id) != 1) { return 0; } - if (sscanf(buf[1].c_str(), "%f", &this->misplaced0) != 1) { return 1; } - if (sscanf(buf[2].c_str(), "%f", &this->misplaced1) != 1) { return 2; } - - return -1; -} - -bool building_annex::recurse(const openage::util::CSVCollection & /*storage*/, const std::string & /*basedir*/) { - return true; -} - -int building_unit::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', building_unit::member_count - ); - - if (buf.size() != building_unit::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing building_unit led to " - << buf.size() - << " columns (expected " - << building_unit::member_count - << ")!" - ); - } - - if (sscanf(buf[0].c_str(), "%hd", &this->id0) != 1) { return 0; } - if (sscanf(buf[1].c_str(), "%hu", &this->language_dll_name) != 1) { return 1; } - if (sscanf(buf[2].c_str(), "%hu", &this->language_dll_creation) != 1) { return 2; } - // parse enum unit_classes - if (buf[3] == "DUMMY") { - this->unit_class = unit_classes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[3] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[4].c_str(), "%hd", &this->idle_graphic0) != 1) { return 4; } - if (sscanf(buf[5].c_str(), "%hd", &this->idle_graphic1) != 1) { return 5; } - if (sscanf(buf[6].c_str(), "%hd", &this->dying_graphic) != 1) { return 6; } - if (sscanf(buf[7].c_str(), "%hd", &this->undead_graphic) != 1) { return 7; } - if (sscanf(buf[8].c_str(), "%hhd", &this->death_mode) != 1) { return 8; } - if (sscanf(buf[9].c_str(), "%hd", &this->hit_points) != 1) { return 9; } - if (sscanf(buf[10].c_str(), "%f", &this->line_of_sight) != 1) { return 10; } - if (sscanf(buf[11].c_str(), "%hhd", &this->garrison_capacity) != 1) { return 11; } - if (sscanf(buf[12].c_str(), "%f", &this->radius_x) != 1) { return 12; } - if (sscanf(buf[13].c_str(), "%f", &this->radius_y) != 1) { return 13; } - if (sscanf(buf[14].c_str(), "%f", &this->radius_z) != 1) { return 14; } - if (sscanf(buf[15].c_str(), "%hd", &this->train_sound_id) != 1) { return 15; } - if (sscanf(buf[16].c_str(), "%hd", &this->damage_sound_id) != 1) { return 16; } - if (sscanf(buf[17].c_str(), "%hd", &this->dead_unit_id) != 1) { return 17; } - if (sscanf(buf[18].c_str(), "%hhd", &this->placement_mode) != 1) { return 18; } - if (sscanf(buf[19].c_str(), "%hhd", &this->can_be_built_on) != 1) { return 19; } - if (sscanf(buf[20].c_str(), "%hd", &this->icon_id) != 1) { return 20; } - if (sscanf(buf[21].c_str(), "%hhd", &this->hidden_in_editor) != 1) { return 21; } - if (sscanf(buf[22].c_str(), "%hd", &this->old_portrait_icon_id) != 1) { return 22; } - if (sscanf(buf[23].c_str(), "%hhd", &this->enabled) != 1) { return 23; } - if (sscanf(buf[24].c_str(), "%hd", &this->placement_side_terrain0) != 1) { return 24; } - if (sscanf(buf[25].c_str(), "%hd", &this->placement_side_terrain1) != 1) { return 25; } - if (sscanf(buf[26].c_str(), "%hd", &this->placement_terrain0) != 1) { return 26; } - if (sscanf(buf[27].c_str(), "%hd", &this->placement_terrain1) != 1) { return 27; } - if (sscanf(buf[28].c_str(), "%f", &this->clearance_size_x) != 1) { return 28; } - if (sscanf(buf[29].c_str(), "%f", &this->clearance_size_y) != 1) { return 29; } - // parse enum elevation_modes - if (buf[30] == "DUMMY") { - this->elevation_mode = elevation_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[30] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum fog_visibility - if (buf[31] == "DUMMY") { - this->visible_in_fog = fog_visibility::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[31] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum ground_type - if (buf[32] == "DUMMY") { - this->terrain_restriction = ground_type::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[32] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[33].c_str(), "%hhd", &this->fly_mode) != 1) { return 33; } - if (sscanf(buf[34].c_str(), "%hd", &this->resource_capacity) != 1) { return 34; } - if (sscanf(buf[35].c_str(), "%f", &this->resource_decay) != 1) { return 35; } - // parse enum blast_types - if (buf[36] == "DUMMY") { - this->blast_defense_level = blast_types::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[36] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum combat_levels - if (buf[37] == "DUMMY") { - this->combat_level = combat_levels::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[37] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum interaction_modes - if (buf[38] == "DUMMY") { - this->interaction_mode = interaction_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[38] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum minimap_modes - if (buf[39] == "DUMMY") { - this->map_draw_level = minimap_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[39] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum command_attributes - if (buf[40] == "DUMMY") { - this->unit_level = command_attributes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[40] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[41].c_str(), "%f", &this->attack_reaction) != 1) { return 41; } - if (sscanf(buf[42].c_str(), "%hhd", &this->minimap_color) != 1) { return 42; } - if (sscanf(buf[43].c_str(), "%d", &this->language_dll_help) != 1) { return 43; } - if (sscanf(buf[44].c_str(), "%d", &this->language_dll_hotkey_text) != 1) { return 44; } - if (sscanf(buf[45].c_str(), "%d", &this->hot_keys) != 1) { return 45; } - if (sscanf(buf[46].c_str(), "%hhd", &this->recyclable) != 1) { return 46; } - if (sscanf(buf[47].c_str(), "%hhd", &this->enable_auto_gather) != 1) { return 47; } - if (sscanf(buf[48].c_str(), "%hhd", &this->doppelgaenger_on_death) != 1) { return 48; } - if (sscanf(buf[49].c_str(), "%hhd", &this->resource_gather_drop) != 1) { return 49; } - if (sscanf(buf[50].c_str(), "%hhu", &this->occlusion_mode) != 1) { return 50; } - // parse enum obstruction_types - if (buf[51] == "DUMMY") { - this->obstruction_type = obstruction_types::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[51] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[52].c_str(), "%hhd", &this->obstruction_class) != 1) { return 52; } - if (sscanf(buf[53].c_str(), "%hhu", &this->trait) != 1) { return 53; } - if (sscanf(buf[54].c_str(), "%hhd", &this->civilization_id) != 1) { return 54; } - if (sscanf(buf[55].c_str(), "%hd", &this->attribute_piece) != 1) { return 55; } - // parse enum selection_effects - if (buf[56] == "DUMMY") { - this->selection_effect = selection_effects::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[56] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[57].c_str(), "%f", &this->selection_shape_x) != 1) { return 57; } - if (sscanf(buf[58].c_str(), "%f", &this->selection_shape_y) != 1) { return 58; } - if (sscanf(buf[59].c_str(), "%f", &this->selection_shape_z) != 1) { return 59; } - this->resource_storage.filename = buf[60]; - this->damage_graphics.filename = buf[61]; - if (sscanf(buf[62].c_str(), "%hd", &this->selection_sound_id) != 1) { return 62; } - if (sscanf(buf[63].c_str(), "%hd", &this->dying_sound_id) != 1) { return 63; } - // parse enum attack_modes - if (buf[64] == "DUMMY") { - this->old_attack_mode = attack_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[64] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[65].c_str(), "%hhd", &this->convert_terrain) != 1) { return 65; } - this->name = buf[66]; - if (sscanf(buf[67].c_str(), "%hd", &this->id1) != 1) { return 67; } - if (sscanf(buf[68].c_str(), "%hd", &this->id2) != 1) { return 68; } - if (sscanf(buf[69].c_str(), "%f", &this->speed) != 1) { return 69; } - if (sscanf(buf[70].c_str(), "%hd", &this->move_graphics) != 1) { return 70; } - if (sscanf(buf[71].c_str(), "%hd", &this->run_graphics) != 1) { return 71; } - if (sscanf(buf[72].c_str(), "%f", &this->turn_speed) != 1) { return 72; } - if (sscanf(buf[73].c_str(), "%hhd", &this->old_size_class) != 1) { return 73; } - if (sscanf(buf[74].c_str(), "%hd", &this->trail_unit_id) != 1) { return 74; } - if (sscanf(buf[75].c_str(), "%hhu", &this->trail_opsions) != 1) { return 75; } - if (sscanf(buf[76].c_str(), "%f", &this->trail_spacing) != 1) { return 76; } - if (sscanf(buf[77].c_str(), "%hhd", &this->old_move_algorithm) != 1) { return 77; } - if (sscanf(buf[78].c_str(), "%f", &this->turn_radius) != 1) { return 78; } - if (sscanf(buf[79].c_str(), "%f", &this->turn_radius_speed) != 1) { return 79; } - if (sscanf(buf[80].c_str(), "%f", &this->max_yaw_per_sec_moving) != 1) { return 80; } - if (sscanf(buf[81].c_str(), "%f", &this->stationary_yaw_revolution_time) != 1) { return 81; } - if (sscanf(buf[82].c_str(), "%f", &this->max_yaw_per_sec_stationary) != 1) { return 82; } - if (sscanf(buf[83].c_str(), "%hd", &this->default_task_id) != 1) { return 83; } - if (sscanf(buf[84].c_str(), "%f", &this->search_radius) != 1) { return 84; } - if (sscanf(buf[85].c_str(), "%f", &this->work_rate) != 1) { return 85; } - if (sscanf(buf[87].c_str(), "%hhd", &this->task_group) != 1) { return 87; } - if (sscanf(buf[88].c_str(), "%hd", &this->command_sound_id) != 1) { return 88; } - if (sscanf(buf[89].c_str(), "%hd", &this->stop_sound_id) != 1) { return 89; } - if (sscanf(buf[90].c_str(), "%hhd", &this->run_pattern) != 1) { return 90; } - if (sscanf(buf[91].c_str(), "%hd", &this->default_armor) != 1) { return 91; } - this->attacks.filename = buf[92]; - this->armors.filename = buf[93]; - // parse enum boundary_ids - if (buf[94] == "DUMMY") { - this->boundary_id = boundary_ids::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[94] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[95].c_str(), "%f", &this->weapon_range_max) != 1) { return 95; } - if (sscanf(buf[96].c_str(), "%f", &this->blast_range) != 1) { return 96; } - if (sscanf(buf[97].c_str(), "%f", &this->attack_speed) != 1) { return 97; } - if (sscanf(buf[98].c_str(), "%hd", &this->attack_projectile_primary_unit_id) != 1) { return 98; } - if (sscanf(buf[99].c_str(), "%hd", &this->accuracy) != 1) { return 99; } - if (sscanf(buf[100].c_str(), "%hhd", &this->break_off_combat) != 1) { return 100; } - if (sscanf(buf[101].c_str(), "%hd", &this->frame_delay) != 1) { return 101; } - // parse enum range_damage_type - if (buf[103] == "DUMMY") { - this->blast_level_offence = range_damage_type::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[103] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[104].c_str(), "%f", &this->weapon_range_min) != 1) { return 104; } - if (sscanf(buf[105].c_str(), "%f", &this->accuracy_dispersion) != 1) { return 105; } - if (sscanf(buf[106].c_str(), "%hd", &this->attack_sprite_id) != 1) { return 106; } - if (sscanf(buf[107].c_str(), "%hd", &this->melee_armor_displayed) != 1) { return 107; } - if (sscanf(buf[108].c_str(), "%hd", &this->attack_displayed) != 1) { return 108; } - if (sscanf(buf[109].c_str(), "%f", &this->range_displayed) != 1) { return 109; } - if (sscanf(buf[110].c_str(), "%f", &this->reload_time_displayed) != 1) { return 110; } - this->resource_cost.filename = buf[111]; - if (sscanf(buf[112].c_str(), "%hd", &this->creation_time) != 1) { return 112; } - if (sscanf(buf[113].c_str(), "%hd", &this->train_location_id) != 1) { return 113; } - if (sscanf(buf[114].c_str(), "%f", &this->rear_attack_modifier) != 1) { return 114; } - if (sscanf(buf[115].c_str(), "%f", &this->flank_attack_modifier) != 1) { return 115; } - // parse enum creatable_types - if (buf[116] == "DUMMY") { - this->creatable_type = creatable_types::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[116] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[117].c_str(), "%hhd", &this->hero_mode) != 1) { return 117; } - if (sscanf(buf[118].c_str(), "%d", &this->garrison_graphic) != 1) { return 118; } - if (sscanf(buf[119].c_str(), "%f", &this->attack_projectile_count) != 1) { return 119; } - if (sscanf(buf[120].c_str(), "%hhd", &this->attack_projectile_max_count) != 1) { return 120; } - if (sscanf(buf[121].c_str(), "%f", &this->attack_projectile_spawning_area_width) != 1) { return 121; } - if (sscanf(buf[122].c_str(), "%f", &this->attack_projectile_spawning_area_length) != 1) { return 122; } - if (sscanf(buf[123].c_str(), "%f", &this->attack_projectile_spawning_area_randomness) != 1) { return 123; } - if (sscanf(buf[124].c_str(), "%d", &this->attack_projectile_secondary_unit_id) != 1) { return 124; } - if (sscanf(buf[125].c_str(), "%d", &this->special_graphic_id) != 1) { return 125; } - if (sscanf(buf[126].c_str(), "%hhd", &this->special_activation) != 1) { return 126; } - if (sscanf(buf[127].c_str(), "%hd", &this->pierce_armor_displayed) != 1) { return 127; } - if (sscanf(buf[128].c_str(), "%hd", &this->construction_graphic_id) != 1) { return 128; } - if (sscanf(buf[129].c_str(), "%hd", &this->snow_graphic_id) != 1) { return 129; } - if (sscanf(buf[130].c_str(), "%hhd", &this->adjacent_mode) != 1) { return 130; } - if (sscanf(buf[131].c_str(), "%hd", &this->graphics_angle) != 1) { return 131; } - if (sscanf(buf[132].c_str(), "%hhd", &this->disappears_when_built) != 1) { return 132; } - if (sscanf(buf[133].c_str(), "%hd", &this->stack_unit_id) != 1) { return 133; } - if (sscanf(buf[134].c_str(), "%hd", &this->foundation_terrain_id) != 1) { return 134; } - if (sscanf(buf[135].c_str(), "%hd", &this->old_overlay_id) != 1) { return 135; } - if (sscanf(buf[136].c_str(), "%hd", &this->research_id) != 1) { return 136; } - if (sscanf(buf[137].c_str(), "%hhd", &this->can_burn) != 1) { return 137; } - this->building_annex.filename = buf[138]; - if (sscanf(buf[139].c_str(), "%hd", &this->head_unit_id) != 1) { return 139; } - if (sscanf(buf[140].c_str(), "%hd", &this->transform_unit_id) != 1) { return 140; } - if (sscanf(buf[141].c_str(), "%hd", &this->transform_sound_id) != 1) { return 141; } - if (sscanf(buf[142].c_str(), "%hd", &this->construction_sound_id) != 1) { return 142; } - // parse enum garrison_types - if (buf[143] == "DUMMY") { - this->garrison_type = garrison_types::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[143] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[144].c_str(), "%f", &this->garrison_heal_rate) != 1) { return 144; } - if (sscanf(buf[145].c_str(), "%f", &this->garrison_repair_rate) != 1) { return 145; } - if (sscanf(buf[146].c_str(), "%hd", &this->salvage_unit_id) != 1) { return 146; } - - return -1; -} - -bool building_unit::recurse(const openage::util::CSVCollection &storage, const std::string &basedir) { - this->resource_storage.read(storage, basedir); - this->damage_graphics.read(storage, basedir); - this->attacks.read(storage, basedir); - this->armors.read(storage, basedir); - this->resource_cost.read(storage, basedir); - this->building_annex.read(storage, basedir); - - return true; -} - -int damage_graphic::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', damage_graphic::member_count - ); - - if (buf.size() != damage_graphic::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing damage_graphic led to " - << buf.size() - << " columns (expected " - << damage_graphic::member_count - << ")!" - ); - } - - if (sscanf(buf[0].c_str(), "%hd", &this->graphic_id) != 1) { return 0; } - if (sscanf(buf[1].c_str(), "%hhd", &this->damage_percent) != 1) { return 1; } - if (sscanf(buf[2].c_str(), "%hhd", &this->old_apply_mode) != 1) { return 2; } - // parse enum damage_draw_type - if (buf[3] == "DUMMY") { - this->apply_mode = damage_draw_type::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[3] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - - return -1; -} - -bool damage_graphic::recurse(const openage::util::CSVCollection & /*storage*/, const std::string & /*basedir*/) { - return true; -} - -int doppelganger_unit::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', doppelganger_unit::member_count - ); - - if (buf.size() != doppelganger_unit::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing doppelganger_unit led to " - << buf.size() - << " columns (expected " - << doppelganger_unit::member_count - << ")!" - ); - } - - if (sscanf(buf[0].c_str(), "%hd", &this->id0) != 1) { return 0; } - if (sscanf(buf[1].c_str(), "%hu", &this->language_dll_name) != 1) { return 1; } - if (sscanf(buf[2].c_str(), "%hu", &this->language_dll_creation) != 1) { return 2; } - // parse enum unit_classes - if (buf[3] == "DUMMY") { - this->unit_class = unit_classes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[3] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[4].c_str(), "%hd", &this->idle_graphic0) != 1) { return 4; } - if (sscanf(buf[5].c_str(), "%hd", &this->idle_graphic1) != 1) { return 5; } - if (sscanf(buf[6].c_str(), "%hd", &this->dying_graphic) != 1) { return 6; } - if (sscanf(buf[7].c_str(), "%hd", &this->undead_graphic) != 1) { return 7; } - if (sscanf(buf[8].c_str(), "%hhd", &this->death_mode) != 1) { return 8; } - if (sscanf(buf[9].c_str(), "%hd", &this->hit_points) != 1) { return 9; } - if (sscanf(buf[10].c_str(), "%f", &this->line_of_sight) != 1) { return 10; } - if (sscanf(buf[11].c_str(), "%hhd", &this->garrison_capacity) != 1) { return 11; } - if (sscanf(buf[12].c_str(), "%f", &this->radius_x) != 1) { return 12; } - if (sscanf(buf[13].c_str(), "%f", &this->radius_y) != 1) { return 13; } - if (sscanf(buf[14].c_str(), "%f", &this->radius_z) != 1) { return 14; } - if (sscanf(buf[15].c_str(), "%hd", &this->train_sound_id) != 1) { return 15; } - if (sscanf(buf[16].c_str(), "%hd", &this->damage_sound_id) != 1) { return 16; } - if (sscanf(buf[17].c_str(), "%hd", &this->dead_unit_id) != 1) { return 17; } - if (sscanf(buf[18].c_str(), "%hhd", &this->placement_mode) != 1) { return 18; } - if (sscanf(buf[19].c_str(), "%hhd", &this->can_be_built_on) != 1) { return 19; } - if (sscanf(buf[20].c_str(), "%hd", &this->icon_id) != 1) { return 20; } - if (sscanf(buf[21].c_str(), "%hhd", &this->hidden_in_editor) != 1) { return 21; } - if (sscanf(buf[22].c_str(), "%hd", &this->old_portrait_icon_id) != 1) { return 22; } - if (sscanf(buf[23].c_str(), "%hhd", &this->enabled) != 1) { return 23; } - if (sscanf(buf[24].c_str(), "%hd", &this->placement_side_terrain0) != 1) { return 24; } - if (sscanf(buf[25].c_str(), "%hd", &this->placement_side_terrain1) != 1) { return 25; } - if (sscanf(buf[26].c_str(), "%hd", &this->placement_terrain0) != 1) { return 26; } - if (sscanf(buf[27].c_str(), "%hd", &this->placement_terrain1) != 1) { return 27; } - if (sscanf(buf[28].c_str(), "%f", &this->clearance_size_x) != 1) { return 28; } - if (sscanf(buf[29].c_str(), "%f", &this->clearance_size_y) != 1) { return 29; } - // parse enum elevation_modes - if (buf[30] == "DUMMY") { - this->elevation_mode = elevation_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[30] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum fog_visibility - if (buf[31] == "DUMMY") { - this->visible_in_fog = fog_visibility::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[31] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum ground_type - if (buf[32] == "DUMMY") { - this->terrain_restriction = ground_type::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[32] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[33].c_str(), "%hhd", &this->fly_mode) != 1) { return 33; } - if (sscanf(buf[34].c_str(), "%hd", &this->resource_capacity) != 1) { return 34; } - if (sscanf(buf[35].c_str(), "%f", &this->resource_decay) != 1) { return 35; } - // parse enum blast_types - if (buf[36] == "DUMMY") { - this->blast_defense_level = blast_types::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[36] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum combat_levels - if (buf[37] == "DUMMY") { - this->combat_level = combat_levels::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[37] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum interaction_modes - if (buf[38] == "DUMMY") { - this->interaction_mode = interaction_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[38] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum minimap_modes - if (buf[39] == "DUMMY") { - this->map_draw_level = minimap_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[39] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum command_attributes - if (buf[40] == "DUMMY") { - this->unit_level = command_attributes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[40] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[41].c_str(), "%f", &this->attack_reaction) != 1) { return 41; } - if (sscanf(buf[42].c_str(), "%hhd", &this->minimap_color) != 1) { return 42; } - if (sscanf(buf[43].c_str(), "%d", &this->language_dll_help) != 1) { return 43; } - if (sscanf(buf[44].c_str(), "%d", &this->language_dll_hotkey_text) != 1) { return 44; } - if (sscanf(buf[45].c_str(), "%d", &this->hot_keys) != 1) { return 45; } - if (sscanf(buf[46].c_str(), "%hhd", &this->recyclable) != 1) { return 46; } - if (sscanf(buf[47].c_str(), "%hhd", &this->enable_auto_gather) != 1) { return 47; } - if (sscanf(buf[48].c_str(), "%hhd", &this->doppelgaenger_on_death) != 1) { return 48; } - if (sscanf(buf[49].c_str(), "%hhd", &this->resource_gather_drop) != 1) { return 49; } - if (sscanf(buf[50].c_str(), "%hhu", &this->occlusion_mode) != 1) { return 50; } - // parse enum obstruction_types - if (buf[51] == "DUMMY") { - this->obstruction_type = obstruction_types::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[51] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[52].c_str(), "%hhd", &this->obstruction_class) != 1) { return 52; } - if (sscanf(buf[53].c_str(), "%hhu", &this->trait) != 1) { return 53; } - if (sscanf(buf[54].c_str(), "%hhd", &this->civilization_id) != 1) { return 54; } - if (sscanf(buf[55].c_str(), "%hd", &this->attribute_piece) != 1) { return 55; } - // parse enum selection_effects - if (buf[56] == "DUMMY") { - this->selection_effect = selection_effects::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[56] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[57].c_str(), "%f", &this->selection_shape_x) != 1) { return 57; } - if (sscanf(buf[58].c_str(), "%f", &this->selection_shape_y) != 1) { return 58; } - if (sscanf(buf[59].c_str(), "%f", &this->selection_shape_z) != 1) { return 59; } - this->resource_storage.filename = buf[60]; - this->damage_graphics.filename = buf[61]; - if (sscanf(buf[62].c_str(), "%hd", &this->selection_sound_id) != 1) { return 62; } - if (sscanf(buf[63].c_str(), "%hd", &this->dying_sound_id) != 1) { return 63; } - // parse enum attack_modes - if (buf[64] == "DUMMY") { - this->old_attack_mode = attack_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[64] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[65].c_str(), "%hhd", &this->convert_terrain) != 1) { return 65; } - this->name = buf[66]; - if (sscanf(buf[67].c_str(), "%hd", &this->id1) != 1) { return 67; } - if (sscanf(buf[68].c_str(), "%hd", &this->id2) != 1) { return 68; } - if (sscanf(buf[69].c_str(), "%f", &this->speed) != 1) { return 69; } - - return -1; -} - -bool doppelganger_unit::recurse(const openage::util::CSVCollection &storage, const std::string &basedir) { - this->resource_storage.read(storage, basedir); - this->damage_graphics.read(storage, basedir); - - return true; -} - -int hit_type::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', hit_type::member_count - ); - - if (buf.size() != hit_type::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing hit_type led to " - << buf.size() - << " columns (expected " - << hit_type::member_count - << ")!" - ); - } - - // parse enum hit_class - if (buf[0] == "DUMMY") { - this->type_id = hit_class::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[0] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[1].c_str(), "%hd", &this->amount) != 1) { return 1; } - - return -1; -} - -bool hit_type::recurse(const openage::util::CSVCollection & /*storage*/, const std::string & /*basedir*/) { - return true; -} - -int living_unit::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', living_unit::member_count - ); - - if (buf.size() != living_unit::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing living_unit led to " - << buf.size() - << " columns (expected " - << living_unit::member_count - << ")!" - ); - } - - if (sscanf(buf[0].c_str(), "%hd", &this->id0) != 1) { return 0; } - if (sscanf(buf[1].c_str(), "%hu", &this->language_dll_name) != 1) { return 1; } - if (sscanf(buf[2].c_str(), "%hu", &this->language_dll_creation) != 1) { return 2; } - // parse enum unit_classes - if (buf[3] == "DUMMY") { - this->unit_class = unit_classes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[3] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[4].c_str(), "%hd", &this->idle_graphic0) != 1) { return 4; } - if (sscanf(buf[5].c_str(), "%hd", &this->idle_graphic1) != 1) { return 5; } - if (sscanf(buf[6].c_str(), "%hd", &this->dying_graphic) != 1) { return 6; } - if (sscanf(buf[7].c_str(), "%hd", &this->undead_graphic) != 1) { return 7; } - if (sscanf(buf[8].c_str(), "%hhd", &this->death_mode) != 1) { return 8; } - if (sscanf(buf[9].c_str(), "%hd", &this->hit_points) != 1) { return 9; } - if (sscanf(buf[10].c_str(), "%f", &this->line_of_sight) != 1) { return 10; } - if (sscanf(buf[11].c_str(), "%hhd", &this->garrison_capacity) != 1) { return 11; } - if (sscanf(buf[12].c_str(), "%f", &this->radius_x) != 1) { return 12; } - if (sscanf(buf[13].c_str(), "%f", &this->radius_y) != 1) { return 13; } - if (sscanf(buf[14].c_str(), "%f", &this->radius_z) != 1) { return 14; } - if (sscanf(buf[15].c_str(), "%hd", &this->train_sound_id) != 1) { return 15; } - if (sscanf(buf[16].c_str(), "%hd", &this->damage_sound_id) != 1) { return 16; } - if (sscanf(buf[17].c_str(), "%hd", &this->dead_unit_id) != 1) { return 17; } - if (sscanf(buf[18].c_str(), "%hhd", &this->placement_mode) != 1) { return 18; } - if (sscanf(buf[19].c_str(), "%hhd", &this->can_be_built_on) != 1) { return 19; } - if (sscanf(buf[20].c_str(), "%hd", &this->icon_id) != 1) { return 20; } - if (sscanf(buf[21].c_str(), "%hhd", &this->hidden_in_editor) != 1) { return 21; } - if (sscanf(buf[22].c_str(), "%hd", &this->old_portrait_icon_id) != 1) { return 22; } - if (sscanf(buf[23].c_str(), "%hhd", &this->enabled) != 1) { return 23; } - if (sscanf(buf[24].c_str(), "%hd", &this->placement_side_terrain0) != 1) { return 24; } - if (sscanf(buf[25].c_str(), "%hd", &this->placement_side_terrain1) != 1) { return 25; } - if (sscanf(buf[26].c_str(), "%hd", &this->placement_terrain0) != 1) { return 26; } - if (sscanf(buf[27].c_str(), "%hd", &this->placement_terrain1) != 1) { return 27; } - if (sscanf(buf[28].c_str(), "%f", &this->clearance_size_x) != 1) { return 28; } - if (sscanf(buf[29].c_str(), "%f", &this->clearance_size_y) != 1) { return 29; } - // parse enum elevation_modes - if (buf[30] == "DUMMY") { - this->elevation_mode = elevation_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[30] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum fog_visibility - if (buf[31] == "DUMMY") { - this->visible_in_fog = fog_visibility::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[31] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum ground_type - if (buf[32] == "DUMMY") { - this->terrain_restriction = ground_type::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[32] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[33].c_str(), "%hhd", &this->fly_mode) != 1) { return 33; } - if (sscanf(buf[34].c_str(), "%hd", &this->resource_capacity) != 1) { return 34; } - if (sscanf(buf[35].c_str(), "%f", &this->resource_decay) != 1) { return 35; } - // parse enum blast_types - if (buf[36] == "DUMMY") { - this->blast_defense_level = blast_types::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[36] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum combat_levels - if (buf[37] == "DUMMY") { - this->combat_level = combat_levels::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[37] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum interaction_modes - if (buf[38] == "DUMMY") { - this->interaction_mode = interaction_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[38] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum minimap_modes - if (buf[39] == "DUMMY") { - this->map_draw_level = minimap_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[39] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum command_attributes - if (buf[40] == "DUMMY") { - this->unit_level = command_attributes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[40] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[41].c_str(), "%f", &this->attack_reaction) != 1) { return 41; } - if (sscanf(buf[42].c_str(), "%hhd", &this->minimap_color) != 1) { return 42; } - if (sscanf(buf[43].c_str(), "%d", &this->language_dll_help) != 1) { return 43; } - if (sscanf(buf[44].c_str(), "%d", &this->language_dll_hotkey_text) != 1) { return 44; } - if (sscanf(buf[45].c_str(), "%d", &this->hot_keys) != 1) { return 45; } - if (sscanf(buf[46].c_str(), "%hhd", &this->recyclable) != 1) { return 46; } - if (sscanf(buf[47].c_str(), "%hhd", &this->enable_auto_gather) != 1) { return 47; } - if (sscanf(buf[48].c_str(), "%hhd", &this->doppelgaenger_on_death) != 1) { return 48; } - if (sscanf(buf[49].c_str(), "%hhd", &this->resource_gather_drop) != 1) { return 49; } - if (sscanf(buf[50].c_str(), "%hhu", &this->occlusion_mode) != 1) { return 50; } - // parse enum obstruction_types - if (buf[51] == "DUMMY") { - this->obstruction_type = obstruction_types::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[51] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[52].c_str(), "%hhd", &this->obstruction_class) != 1) { return 52; } - if (sscanf(buf[53].c_str(), "%hhu", &this->trait) != 1) { return 53; } - if (sscanf(buf[54].c_str(), "%hhd", &this->civilization_id) != 1) { return 54; } - if (sscanf(buf[55].c_str(), "%hd", &this->attribute_piece) != 1) { return 55; } - // parse enum selection_effects - if (buf[56] == "DUMMY") { - this->selection_effect = selection_effects::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[56] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[57].c_str(), "%f", &this->selection_shape_x) != 1) { return 57; } - if (sscanf(buf[58].c_str(), "%f", &this->selection_shape_y) != 1) { return 58; } - if (sscanf(buf[59].c_str(), "%f", &this->selection_shape_z) != 1) { return 59; } - this->resource_storage.filename = buf[60]; - this->damage_graphics.filename = buf[61]; - if (sscanf(buf[62].c_str(), "%hd", &this->selection_sound_id) != 1) { return 62; } - if (sscanf(buf[63].c_str(), "%hd", &this->dying_sound_id) != 1) { return 63; } - // parse enum attack_modes - if (buf[64] == "DUMMY") { - this->old_attack_mode = attack_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[64] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[65].c_str(), "%hhd", &this->convert_terrain) != 1) { return 65; } - this->name = buf[66]; - if (sscanf(buf[67].c_str(), "%hd", &this->id1) != 1) { return 67; } - if (sscanf(buf[68].c_str(), "%hd", &this->id2) != 1) { return 68; } - if (sscanf(buf[69].c_str(), "%f", &this->speed) != 1) { return 69; } - if (sscanf(buf[70].c_str(), "%hd", &this->move_graphics) != 1) { return 70; } - if (sscanf(buf[71].c_str(), "%hd", &this->run_graphics) != 1) { return 71; } - if (sscanf(buf[72].c_str(), "%f", &this->turn_speed) != 1) { return 72; } - if (sscanf(buf[73].c_str(), "%hhd", &this->old_size_class) != 1) { return 73; } - if (sscanf(buf[74].c_str(), "%hd", &this->trail_unit_id) != 1) { return 74; } - if (sscanf(buf[75].c_str(), "%hhu", &this->trail_opsions) != 1) { return 75; } - if (sscanf(buf[76].c_str(), "%f", &this->trail_spacing) != 1) { return 76; } - if (sscanf(buf[77].c_str(), "%hhd", &this->old_move_algorithm) != 1) { return 77; } - if (sscanf(buf[78].c_str(), "%f", &this->turn_radius) != 1) { return 78; } - if (sscanf(buf[79].c_str(), "%f", &this->turn_radius_speed) != 1) { return 79; } - if (sscanf(buf[80].c_str(), "%f", &this->max_yaw_per_sec_moving) != 1) { return 80; } - if (sscanf(buf[81].c_str(), "%f", &this->stationary_yaw_revolution_time) != 1) { return 81; } - if (sscanf(buf[82].c_str(), "%f", &this->max_yaw_per_sec_stationary) != 1) { return 82; } - if (sscanf(buf[83].c_str(), "%hd", &this->default_task_id) != 1) { return 83; } - if (sscanf(buf[84].c_str(), "%f", &this->search_radius) != 1) { return 84; } - if (sscanf(buf[85].c_str(), "%f", &this->work_rate) != 1) { return 85; } - if (sscanf(buf[87].c_str(), "%hhd", &this->task_group) != 1) { return 87; } - if (sscanf(buf[88].c_str(), "%hd", &this->command_sound_id) != 1) { return 88; } - if (sscanf(buf[89].c_str(), "%hd", &this->stop_sound_id) != 1) { return 89; } - if (sscanf(buf[90].c_str(), "%hhd", &this->run_pattern) != 1) { return 90; } - if (sscanf(buf[91].c_str(), "%hd", &this->default_armor) != 1) { return 91; } - this->attacks.filename = buf[92]; - this->armors.filename = buf[93]; - // parse enum boundary_ids - if (buf[94] == "DUMMY") { - this->boundary_id = boundary_ids::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[94] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[95].c_str(), "%f", &this->weapon_range_max) != 1) { return 95; } - if (sscanf(buf[96].c_str(), "%f", &this->blast_range) != 1) { return 96; } - if (sscanf(buf[97].c_str(), "%f", &this->attack_speed) != 1) { return 97; } - if (sscanf(buf[98].c_str(), "%hd", &this->attack_projectile_primary_unit_id) != 1) { return 98; } - if (sscanf(buf[99].c_str(), "%hd", &this->accuracy) != 1) { return 99; } - if (sscanf(buf[100].c_str(), "%hhd", &this->break_off_combat) != 1) { return 100; } - if (sscanf(buf[101].c_str(), "%hd", &this->frame_delay) != 1) { return 101; } - // parse enum range_damage_type - if (buf[103] == "DUMMY") { - this->blast_level_offence = range_damage_type::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[103] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[104].c_str(), "%f", &this->weapon_range_min) != 1) { return 104; } - if (sscanf(buf[105].c_str(), "%f", &this->accuracy_dispersion) != 1) { return 105; } - if (sscanf(buf[106].c_str(), "%hd", &this->attack_sprite_id) != 1) { return 106; } - if (sscanf(buf[107].c_str(), "%hd", &this->melee_armor_displayed) != 1) { return 107; } - if (sscanf(buf[108].c_str(), "%hd", &this->attack_displayed) != 1) { return 108; } - if (sscanf(buf[109].c_str(), "%f", &this->range_displayed) != 1) { return 109; } - if (sscanf(buf[110].c_str(), "%f", &this->reload_time_displayed) != 1) { return 110; } - this->resource_cost.filename = buf[111]; - if (sscanf(buf[112].c_str(), "%hd", &this->creation_time) != 1) { return 112; } - if (sscanf(buf[113].c_str(), "%hd", &this->train_location_id) != 1) { return 113; } - if (sscanf(buf[114].c_str(), "%f", &this->rear_attack_modifier) != 1) { return 114; } - if (sscanf(buf[115].c_str(), "%f", &this->flank_attack_modifier) != 1) { return 115; } - // parse enum creatable_types - if (buf[116] == "DUMMY") { - this->creatable_type = creatable_types::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[116] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[117].c_str(), "%hhd", &this->hero_mode) != 1) { return 117; } - if (sscanf(buf[118].c_str(), "%d", &this->garrison_graphic) != 1) { return 118; } - if (sscanf(buf[119].c_str(), "%f", &this->attack_projectile_count) != 1) { return 119; } - if (sscanf(buf[120].c_str(), "%hhd", &this->attack_projectile_max_count) != 1) { return 120; } - if (sscanf(buf[121].c_str(), "%f", &this->attack_projectile_spawning_area_width) != 1) { return 121; } - if (sscanf(buf[122].c_str(), "%f", &this->attack_projectile_spawning_area_length) != 1) { return 122; } - if (sscanf(buf[123].c_str(), "%f", &this->attack_projectile_spawning_area_randomness) != 1) { return 123; } - if (sscanf(buf[124].c_str(), "%d", &this->attack_projectile_secondary_unit_id) != 1) { return 124; } - if (sscanf(buf[125].c_str(), "%d", &this->special_graphic_id) != 1) { return 125; } - if (sscanf(buf[126].c_str(), "%hhd", &this->special_activation) != 1) { return 126; } - if (sscanf(buf[127].c_str(), "%hd", &this->pierce_armor_displayed) != 1) { return 127; } - - return -1; -} - -bool living_unit::recurse(const openage::util::CSVCollection &storage, const std::string &basedir) { - this->resource_storage.read(storage, basedir); - this->damage_graphics.read(storage, basedir); - this->attacks.read(storage, basedir); - this->armors.read(storage, basedir); - this->resource_cost.read(storage, basedir); - - return true; -} - -int missile_unit::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', missile_unit::member_count - ); - - if (buf.size() != missile_unit::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing missile_unit led to " - << buf.size() - << " columns (expected " - << missile_unit::member_count - << ")!" - ); - } - - if (sscanf(buf[0].c_str(), "%hd", &this->id0) != 1) { return 0; } - if (sscanf(buf[1].c_str(), "%hu", &this->language_dll_name) != 1) { return 1; } - if (sscanf(buf[2].c_str(), "%hu", &this->language_dll_creation) != 1) { return 2; } - // parse enum unit_classes - if (buf[3] == "DUMMY") { - this->unit_class = unit_classes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[3] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[4].c_str(), "%hd", &this->idle_graphic0) != 1) { return 4; } - if (sscanf(buf[5].c_str(), "%hd", &this->idle_graphic1) != 1) { return 5; } - if (sscanf(buf[6].c_str(), "%hd", &this->dying_graphic) != 1) { return 6; } - if (sscanf(buf[7].c_str(), "%hd", &this->undead_graphic) != 1) { return 7; } - if (sscanf(buf[8].c_str(), "%hhd", &this->death_mode) != 1) { return 8; } - if (sscanf(buf[9].c_str(), "%hd", &this->hit_points) != 1) { return 9; } - if (sscanf(buf[10].c_str(), "%f", &this->line_of_sight) != 1) { return 10; } - if (sscanf(buf[11].c_str(), "%hhd", &this->garrison_capacity) != 1) { return 11; } - if (sscanf(buf[12].c_str(), "%f", &this->radius_x) != 1) { return 12; } - if (sscanf(buf[13].c_str(), "%f", &this->radius_y) != 1) { return 13; } - if (sscanf(buf[14].c_str(), "%f", &this->radius_z) != 1) { return 14; } - if (sscanf(buf[15].c_str(), "%hd", &this->train_sound_id) != 1) { return 15; } - if (sscanf(buf[16].c_str(), "%hd", &this->damage_sound_id) != 1) { return 16; } - if (sscanf(buf[17].c_str(), "%hd", &this->dead_unit_id) != 1) { return 17; } - if (sscanf(buf[18].c_str(), "%hhd", &this->placement_mode) != 1) { return 18; } - if (sscanf(buf[19].c_str(), "%hhd", &this->can_be_built_on) != 1) { return 19; } - if (sscanf(buf[20].c_str(), "%hd", &this->icon_id) != 1) { return 20; } - if (sscanf(buf[21].c_str(), "%hhd", &this->hidden_in_editor) != 1) { return 21; } - if (sscanf(buf[22].c_str(), "%hd", &this->old_portrait_icon_id) != 1) { return 22; } - if (sscanf(buf[23].c_str(), "%hhd", &this->enabled) != 1) { return 23; } - if (sscanf(buf[24].c_str(), "%hd", &this->placement_side_terrain0) != 1) { return 24; } - if (sscanf(buf[25].c_str(), "%hd", &this->placement_side_terrain1) != 1) { return 25; } - if (sscanf(buf[26].c_str(), "%hd", &this->placement_terrain0) != 1) { return 26; } - if (sscanf(buf[27].c_str(), "%hd", &this->placement_terrain1) != 1) { return 27; } - if (sscanf(buf[28].c_str(), "%f", &this->clearance_size_x) != 1) { return 28; } - if (sscanf(buf[29].c_str(), "%f", &this->clearance_size_y) != 1) { return 29; } - // parse enum elevation_modes - if (buf[30] == "DUMMY") { - this->elevation_mode = elevation_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[30] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum fog_visibility - if (buf[31] == "DUMMY") { - this->visible_in_fog = fog_visibility::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[31] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum ground_type - if (buf[32] == "DUMMY") { - this->terrain_restriction = ground_type::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[32] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[33].c_str(), "%hhd", &this->fly_mode) != 1) { return 33; } - if (sscanf(buf[34].c_str(), "%hd", &this->resource_capacity) != 1) { return 34; } - if (sscanf(buf[35].c_str(), "%f", &this->resource_decay) != 1) { return 35; } - // parse enum blast_types - if (buf[36] == "DUMMY") { - this->blast_defense_level = blast_types::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[36] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum combat_levels - if (buf[37] == "DUMMY") { - this->combat_level = combat_levels::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[37] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum interaction_modes - if (buf[38] == "DUMMY") { - this->interaction_mode = interaction_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[38] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum minimap_modes - if (buf[39] == "DUMMY") { - this->map_draw_level = minimap_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[39] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum command_attributes - if (buf[40] == "DUMMY") { - this->unit_level = command_attributes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[40] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[41].c_str(), "%f", &this->attack_reaction) != 1) { return 41; } - if (sscanf(buf[42].c_str(), "%hhd", &this->minimap_color) != 1) { return 42; } - if (sscanf(buf[43].c_str(), "%d", &this->language_dll_help) != 1) { return 43; } - if (sscanf(buf[44].c_str(), "%d", &this->language_dll_hotkey_text) != 1) { return 44; } - if (sscanf(buf[45].c_str(), "%d", &this->hot_keys) != 1) { return 45; } - if (sscanf(buf[46].c_str(), "%hhd", &this->recyclable) != 1) { return 46; } - if (sscanf(buf[47].c_str(), "%hhd", &this->enable_auto_gather) != 1) { return 47; } - if (sscanf(buf[48].c_str(), "%hhd", &this->doppelgaenger_on_death) != 1) { return 48; } - if (sscanf(buf[49].c_str(), "%hhd", &this->resource_gather_drop) != 1) { return 49; } - if (sscanf(buf[50].c_str(), "%hhu", &this->occlusion_mode) != 1) { return 50; } - // parse enum obstruction_types - if (buf[51] == "DUMMY") { - this->obstruction_type = obstruction_types::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[51] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[52].c_str(), "%hhd", &this->obstruction_class) != 1) { return 52; } - if (sscanf(buf[53].c_str(), "%hhu", &this->trait) != 1) { return 53; } - if (sscanf(buf[54].c_str(), "%hhd", &this->civilization_id) != 1) { return 54; } - if (sscanf(buf[55].c_str(), "%hd", &this->attribute_piece) != 1) { return 55; } - // parse enum selection_effects - if (buf[56] == "DUMMY") { - this->selection_effect = selection_effects::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[56] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[57].c_str(), "%f", &this->selection_shape_x) != 1) { return 57; } - if (sscanf(buf[58].c_str(), "%f", &this->selection_shape_y) != 1) { return 58; } - if (sscanf(buf[59].c_str(), "%f", &this->selection_shape_z) != 1) { return 59; } - this->resource_storage.filename = buf[60]; - this->damage_graphics.filename = buf[61]; - if (sscanf(buf[62].c_str(), "%hd", &this->selection_sound_id) != 1) { return 62; } - if (sscanf(buf[63].c_str(), "%hd", &this->dying_sound_id) != 1) { return 63; } - // parse enum attack_modes - if (buf[64] == "DUMMY") { - this->old_attack_mode = attack_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[64] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[65].c_str(), "%hhd", &this->convert_terrain) != 1) { return 65; } - this->name = buf[66]; - if (sscanf(buf[67].c_str(), "%hd", &this->id1) != 1) { return 67; } - if (sscanf(buf[68].c_str(), "%hd", &this->id2) != 1) { return 68; } - if (sscanf(buf[69].c_str(), "%f", &this->speed) != 1) { return 69; } - if (sscanf(buf[70].c_str(), "%hd", &this->move_graphics) != 1) { return 70; } - if (sscanf(buf[71].c_str(), "%hd", &this->run_graphics) != 1) { return 71; } - if (sscanf(buf[72].c_str(), "%f", &this->turn_speed) != 1) { return 72; } - if (sscanf(buf[73].c_str(), "%hhd", &this->old_size_class) != 1) { return 73; } - if (sscanf(buf[74].c_str(), "%hd", &this->trail_unit_id) != 1) { return 74; } - if (sscanf(buf[75].c_str(), "%hhu", &this->trail_opsions) != 1) { return 75; } - if (sscanf(buf[76].c_str(), "%f", &this->trail_spacing) != 1) { return 76; } - if (sscanf(buf[77].c_str(), "%hhd", &this->old_move_algorithm) != 1) { return 77; } - if (sscanf(buf[78].c_str(), "%f", &this->turn_radius) != 1) { return 78; } - if (sscanf(buf[79].c_str(), "%f", &this->turn_radius_speed) != 1) { return 79; } - if (sscanf(buf[80].c_str(), "%f", &this->max_yaw_per_sec_moving) != 1) { return 80; } - if (sscanf(buf[81].c_str(), "%f", &this->stationary_yaw_revolution_time) != 1) { return 81; } - if (sscanf(buf[82].c_str(), "%f", &this->max_yaw_per_sec_stationary) != 1) { return 82; } - if (sscanf(buf[83].c_str(), "%hd", &this->default_task_id) != 1) { return 83; } - if (sscanf(buf[84].c_str(), "%f", &this->search_radius) != 1) { return 84; } - if (sscanf(buf[85].c_str(), "%f", &this->work_rate) != 1) { return 85; } - if (sscanf(buf[87].c_str(), "%hhd", &this->task_group) != 1) { return 87; } - if (sscanf(buf[88].c_str(), "%hd", &this->command_sound_id) != 1) { return 88; } - if (sscanf(buf[89].c_str(), "%hd", &this->stop_sound_id) != 1) { return 89; } - if (sscanf(buf[90].c_str(), "%hhd", &this->run_pattern) != 1) { return 90; } - if (sscanf(buf[91].c_str(), "%hd", &this->default_armor) != 1) { return 91; } - this->attacks.filename = buf[92]; - this->armors.filename = buf[93]; - // parse enum boundary_ids - if (buf[94] == "DUMMY") { - this->boundary_id = boundary_ids::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[94] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[95].c_str(), "%f", &this->weapon_range_max) != 1) { return 95; } - if (sscanf(buf[96].c_str(), "%f", &this->blast_range) != 1) { return 96; } - if (sscanf(buf[97].c_str(), "%f", &this->attack_speed) != 1) { return 97; } - if (sscanf(buf[98].c_str(), "%hd", &this->attack_projectile_primary_unit_id) != 1) { return 98; } - if (sscanf(buf[99].c_str(), "%hd", &this->accuracy) != 1) { return 99; } - if (sscanf(buf[100].c_str(), "%hhd", &this->break_off_combat) != 1) { return 100; } - if (sscanf(buf[101].c_str(), "%hd", &this->frame_delay) != 1) { return 101; } - // parse enum range_damage_type - if (buf[103] == "DUMMY") { - this->blast_level_offence = range_damage_type::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[103] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[104].c_str(), "%f", &this->weapon_range_min) != 1) { return 104; } - if (sscanf(buf[105].c_str(), "%f", &this->accuracy_dispersion) != 1) { return 105; } - if (sscanf(buf[106].c_str(), "%hd", &this->attack_sprite_id) != 1) { return 106; } - if (sscanf(buf[107].c_str(), "%hd", &this->melee_armor_displayed) != 1) { return 107; } - if (sscanf(buf[108].c_str(), "%hd", &this->attack_displayed) != 1) { return 108; } - if (sscanf(buf[109].c_str(), "%f", &this->range_displayed) != 1) { return 109; } - if (sscanf(buf[110].c_str(), "%f", &this->reload_time_displayed) != 1) { return 110; } - if (sscanf(buf[111].c_str(), "%hhd", &this->projectile_type) != 1) { return 111; } - if (sscanf(buf[112].c_str(), "%hhd", &this->smart_mode) != 1) { return 112; } - if (sscanf(buf[113].c_str(), "%hhd", &this->drop_animation_mode) != 1) { return 113; } - if (sscanf(buf[114].c_str(), "%hhd", &this->penetration_mode) != 1) { return 114; } - if (sscanf(buf[115].c_str(), "%hhd", &this->area_of_effect_special) != 1) { return 115; } - if (sscanf(buf[116].c_str(), "%f", &this->projectile_arc) != 1) { return 116; } - - return -1; -} - -bool missile_unit::recurse(const openage::util::CSVCollection &storage, const std::string &basedir) { - this->resource_storage.read(storage, basedir); - this->damage_graphics.read(storage, basedir); - this->attacks.read(storage, basedir); - this->armors.read(storage, basedir); - - return true; -} - -int moving_unit::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', moving_unit::member_count - ); - - if (buf.size() != moving_unit::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing moving_unit led to " - << buf.size() - << " columns (expected " - << moving_unit::member_count - << ")!" - ); - } - - if (sscanf(buf[0].c_str(), "%hd", &this->id0) != 1) { return 0; } - if (sscanf(buf[1].c_str(), "%hu", &this->language_dll_name) != 1) { return 1; } - if (sscanf(buf[2].c_str(), "%hu", &this->language_dll_creation) != 1) { return 2; } - // parse enum unit_classes - if (buf[3] == "DUMMY") { - this->unit_class = unit_classes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[3] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[4].c_str(), "%hd", &this->idle_graphic0) != 1) { return 4; } - if (sscanf(buf[5].c_str(), "%hd", &this->idle_graphic1) != 1) { return 5; } - if (sscanf(buf[6].c_str(), "%hd", &this->dying_graphic) != 1) { return 6; } - if (sscanf(buf[7].c_str(), "%hd", &this->undead_graphic) != 1) { return 7; } - if (sscanf(buf[8].c_str(), "%hhd", &this->death_mode) != 1) { return 8; } - if (sscanf(buf[9].c_str(), "%hd", &this->hit_points) != 1) { return 9; } - if (sscanf(buf[10].c_str(), "%f", &this->line_of_sight) != 1) { return 10; } - if (sscanf(buf[11].c_str(), "%hhd", &this->garrison_capacity) != 1) { return 11; } - if (sscanf(buf[12].c_str(), "%f", &this->radius_x) != 1) { return 12; } - if (sscanf(buf[13].c_str(), "%f", &this->radius_y) != 1) { return 13; } - if (sscanf(buf[14].c_str(), "%f", &this->radius_z) != 1) { return 14; } - if (sscanf(buf[15].c_str(), "%hd", &this->train_sound_id) != 1) { return 15; } - if (sscanf(buf[16].c_str(), "%hd", &this->damage_sound_id) != 1) { return 16; } - if (sscanf(buf[17].c_str(), "%hd", &this->dead_unit_id) != 1) { return 17; } - if (sscanf(buf[18].c_str(), "%hhd", &this->placement_mode) != 1) { return 18; } - if (sscanf(buf[19].c_str(), "%hhd", &this->can_be_built_on) != 1) { return 19; } - if (sscanf(buf[20].c_str(), "%hd", &this->icon_id) != 1) { return 20; } - if (sscanf(buf[21].c_str(), "%hhd", &this->hidden_in_editor) != 1) { return 21; } - if (sscanf(buf[22].c_str(), "%hd", &this->old_portrait_icon_id) != 1) { return 22; } - if (sscanf(buf[23].c_str(), "%hhd", &this->enabled) != 1) { return 23; } - if (sscanf(buf[24].c_str(), "%hd", &this->placement_side_terrain0) != 1) { return 24; } - if (sscanf(buf[25].c_str(), "%hd", &this->placement_side_terrain1) != 1) { return 25; } - if (sscanf(buf[26].c_str(), "%hd", &this->placement_terrain0) != 1) { return 26; } - if (sscanf(buf[27].c_str(), "%hd", &this->placement_terrain1) != 1) { return 27; } - if (sscanf(buf[28].c_str(), "%f", &this->clearance_size_x) != 1) { return 28; } - if (sscanf(buf[29].c_str(), "%f", &this->clearance_size_y) != 1) { return 29; } - // parse enum elevation_modes - if (buf[30] == "DUMMY") { - this->elevation_mode = elevation_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[30] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum fog_visibility - if (buf[31] == "DUMMY") { - this->visible_in_fog = fog_visibility::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[31] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum ground_type - if (buf[32] == "DUMMY") { - this->terrain_restriction = ground_type::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[32] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[33].c_str(), "%hhd", &this->fly_mode) != 1) { return 33; } - if (sscanf(buf[34].c_str(), "%hd", &this->resource_capacity) != 1) { return 34; } - if (sscanf(buf[35].c_str(), "%f", &this->resource_decay) != 1) { return 35; } - // parse enum blast_types - if (buf[36] == "DUMMY") { - this->blast_defense_level = blast_types::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[36] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum combat_levels - if (buf[37] == "DUMMY") { - this->combat_level = combat_levels::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[37] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum interaction_modes - if (buf[38] == "DUMMY") { - this->interaction_mode = interaction_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[38] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum minimap_modes - if (buf[39] == "DUMMY") { - this->map_draw_level = minimap_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[39] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum command_attributes - if (buf[40] == "DUMMY") { - this->unit_level = command_attributes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[40] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[41].c_str(), "%f", &this->attack_reaction) != 1) { return 41; } - if (sscanf(buf[42].c_str(), "%hhd", &this->minimap_color) != 1) { return 42; } - if (sscanf(buf[43].c_str(), "%d", &this->language_dll_help) != 1) { return 43; } - if (sscanf(buf[44].c_str(), "%d", &this->language_dll_hotkey_text) != 1) { return 44; } - if (sscanf(buf[45].c_str(), "%d", &this->hot_keys) != 1) { return 45; } - if (sscanf(buf[46].c_str(), "%hhd", &this->recyclable) != 1) { return 46; } - if (sscanf(buf[47].c_str(), "%hhd", &this->enable_auto_gather) != 1) { return 47; } - if (sscanf(buf[48].c_str(), "%hhd", &this->doppelgaenger_on_death) != 1) { return 48; } - if (sscanf(buf[49].c_str(), "%hhd", &this->resource_gather_drop) != 1) { return 49; } - if (sscanf(buf[50].c_str(), "%hhu", &this->occlusion_mode) != 1) { return 50; } - // parse enum obstruction_types - if (buf[51] == "DUMMY") { - this->obstruction_type = obstruction_types::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[51] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[52].c_str(), "%hhd", &this->obstruction_class) != 1) { return 52; } - if (sscanf(buf[53].c_str(), "%hhu", &this->trait) != 1) { return 53; } - if (sscanf(buf[54].c_str(), "%hhd", &this->civilization_id) != 1) { return 54; } - if (sscanf(buf[55].c_str(), "%hd", &this->attribute_piece) != 1) { return 55; } - // parse enum selection_effects - if (buf[56] == "DUMMY") { - this->selection_effect = selection_effects::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[56] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[57].c_str(), "%f", &this->selection_shape_x) != 1) { return 57; } - if (sscanf(buf[58].c_str(), "%f", &this->selection_shape_y) != 1) { return 58; } - if (sscanf(buf[59].c_str(), "%f", &this->selection_shape_z) != 1) { return 59; } - this->resource_storage.filename = buf[60]; - this->damage_graphics.filename = buf[61]; - if (sscanf(buf[62].c_str(), "%hd", &this->selection_sound_id) != 1) { return 62; } - if (sscanf(buf[63].c_str(), "%hd", &this->dying_sound_id) != 1) { return 63; } - // parse enum attack_modes - if (buf[64] == "DUMMY") { - this->old_attack_mode = attack_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[64] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[65].c_str(), "%hhd", &this->convert_terrain) != 1) { return 65; } - this->name = buf[66]; - if (sscanf(buf[67].c_str(), "%hd", &this->id1) != 1) { return 67; } - if (sscanf(buf[68].c_str(), "%hd", &this->id2) != 1) { return 68; } - if (sscanf(buf[69].c_str(), "%f", &this->speed) != 1) { return 69; } - if (sscanf(buf[70].c_str(), "%hd", &this->move_graphics) != 1) { return 70; } - if (sscanf(buf[71].c_str(), "%hd", &this->run_graphics) != 1) { return 71; } - if (sscanf(buf[72].c_str(), "%f", &this->turn_speed) != 1) { return 72; } - if (sscanf(buf[73].c_str(), "%hhd", &this->old_size_class) != 1) { return 73; } - if (sscanf(buf[74].c_str(), "%hd", &this->trail_unit_id) != 1) { return 74; } - if (sscanf(buf[75].c_str(), "%hhu", &this->trail_opsions) != 1) { return 75; } - if (sscanf(buf[76].c_str(), "%f", &this->trail_spacing) != 1) { return 76; } - if (sscanf(buf[77].c_str(), "%hhd", &this->old_move_algorithm) != 1) { return 77; } - if (sscanf(buf[78].c_str(), "%f", &this->turn_radius) != 1) { return 78; } - if (sscanf(buf[79].c_str(), "%f", &this->turn_radius_speed) != 1) { return 79; } - if (sscanf(buf[80].c_str(), "%f", &this->max_yaw_per_sec_moving) != 1) { return 80; } - if (sscanf(buf[81].c_str(), "%f", &this->stationary_yaw_revolution_time) != 1) { return 81; } - if (sscanf(buf[82].c_str(), "%f", &this->max_yaw_per_sec_stationary) != 1) { return 82; } - - return -1; -} - -bool moving_unit::recurse(const openage::util::CSVCollection &storage, const std::string &basedir) { - this->resource_storage.read(storage, basedir); - this->damage_graphics.read(storage, basedir); - - return true; -} - -int projectile_unit::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', projectile_unit::member_count - ); - - if (buf.size() != projectile_unit::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing projectile_unit led to " - << buf.size() - << " columns (expected " - << projectile_unit::member_count - << ")!" - ); - } - - if (sscanf(buf[0].c_str(), "%hd", &this->id0) != 1) { return 0; } - if (sscanf(buf[1].c_str(), "%hu", &this->language_dll_name) != 1) { return 1; } - if (sscanf(buf[2].c_str(), "%hu", &this->language_dll_creation) != 1) { return 2; } - // parse enum unit_classes - if (buf[3] == "DUMMY") { - this->unit_class = unit_classes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[3] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[4].c_str(), "%hd", &this->idle_graphic0) != 1) { return 4; } - if (sscanf(buf[5].c_str(), "%hd", &this->idle_graphic1) != 1) { return 5; } - if (sscanf(buf[6].c_str(), "%hd", &this->dying_graphic) != 1) { return 6; } - if (sscanf(buf[7].c_str(), "%hd", &this->undead_graphic) != 1) { return 7; } - if (sscanf(buf[8].c_str(), "%hhd", &this->death_mode) != 1) { return 8; } - if (sscanf(buf[9].c_str(), "%hd", &this->hit_points) != 1) { return 9; } - if (sscanf(buf[10].c_str(), "%f", &this->line_of_sight) != 1) { return 10; } - if (sscanf(buf[11].c_str(), "%hhd", &this->garrison_capacity) != 1) { return 11; } - if (sscanf(buf[12].c_str(), "%f", &this->radius_x) != 1) { return 12; } - if (sscanf(buf[13].c_str(), "%f", &this->radius_y) != 1) { return 13; } - if (sscanf(buf[14].c_str(), "%f", &this->radius_z) != 1) { return 14; } - if (sscanf(buf[15].c_str(), "%hd", &this->train_sound_id) != 1) { return 15; } - if (sscanf(buf[16].c_str(), "%hd", &this->damage_sound_id) != 1) { return 16; } - if (sscanf(buf[17].c_str(), "%hd", &this->dead_unit_id) != 1) { return 17; } - if (sscanf(buf[18].c_str(), "%hhd", &this->placement_mode) != 1) { return 18; } - if (sscanf(buf[19].c_str(), "%hhd", &this->can_be_built_on) != 1) { return 19; } - if (sscanf(buf[20].c_str(), "%hd", &this->icon_id) != 1) { return 20; } - if (sscanf(buf[21].c_str(), "%hhd", &this->hidden_in_editor) != 1) { return 21; } - if (sscanf(buf[22].c_str(), "%hd", &this->old_portrait_icon_id) != 1) { return 22; } - if (sscanf(buf[23].c_str(), "%hhd", &this->enabled) != 1) { return 23; } - if (sscanf(buf[24].c_str(), "%hd", &this->placement_side_terrain0) != 1) { return 24; } - if (sscanf(buf[25].c_str(), "%hd", &this->placement_side_terrain1) != 1) { return 25; } - if (sscanf(buf[26].c_str(), "%hd", &this->placement_terrain0) != 1) { return 26; } - if (sscanf(buf[27].c_str(), "%hd", &this->placement_terrain1) != 1) { return 27; } - if (sscanf(buf[28].c_str(), "%f", &this->clearance_size_x) != 1) { return 28; } - if (sscanf(buf[29].c_str(), "%f", &this->clearance_size_y) != 1) { return 29; } - // parse enum elevation_modes - if (buf[30] == "DUMMY") { - this->elevation_mode = elevation_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[30] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum fog_visibility - if (buf[31] == "DUMMY") { - this->visible_in_fog = fog_visibility::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[31] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum ground_type - if (buf[32] == "DUMMY") { - this->terrain_restriction = ground_type::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[32] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[33].c_str(), "%hhd", &this->fly_mode) != 1) { return 33; } - if (sscanf(buf[34].c_str(), "%hd", &this->resource_capacity) != 1) { return 34; } - if (sscanf(buf[35].c_str(), "%f", &this->resource_decay) != 1) { return 35; } - // parse enum blast_types - if (buf[36] == "DUMMY") { - this->blast_defense_level = blast_types::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[36] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum combat_levels - if (buf[37] == "DUMMY") { - this->combat_level = combat_levels::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[37] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum interaction_modes - if (buf[38] == "DUMMY") { - this->interaction_mode = interaction_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[38] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum minimap_modes - if (buf[39] == "DUMMY") { - this->map_draw_level = minimap_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[39] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum command_attributes - if (buf[40] == "DUMMY") { - this->unit_level = command_attributes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[40] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[41].c_str(), "%f", &this->attack_reaction) != 1) { return 41; } - if (sscanf(buf[42].c_str(), "%hhd", &this->minimap_color) != 1) { return 42; } - if (sscanf(buf[43].c_str(), "%d", &this->language_dll_help) != 1) { return 43; } - if (sscanf(buf[44].c_str(), "%d", &this->language_dll_hotkey_text) != 1) { return 44; } - if (sscanf(buf[45].c_str(), "%d", &this->hot_keys) != 1) { return 45; } - if (sscanf(buf[46].c_str(), "%hhd", &this->recyclable) != 1) { return 46; } - if (sscanf(buf[47].c_str(), "%hhd", &this->enable_auto_gather) != 1) { return 47; } - if (sscanf(buf[48].c_str(), "%hhd", &this->doppelgaenger_on_death) != 1) { return 48; } - if (sscanf(buf[49].c_str(), "%hhd", &this->resource_gather_drop) != 1) { return 49; } - if (sscanf(buf[50].c_str(), "%hhu", &this->occlusion_mode) != 1) { return 50; } - // parse enum obstruction_types - if (buf[51] == "DUMMY") { - this->obstruction_type = obstruction_types::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[51] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[52].c_str(), "%hhd", &this->obstruction_class) != 1) { return 52; } - if (sscanf(buf[53].c_str(), "%hhu", &this->trait) != 1) { return 53; } - if (sscanf(buf[54].c_str(), "%hhd", &this->civilization_id) != 1) { return 54; } - if (sscanf(buf[55].c_str(), "%hd", &this->attribute_piece) != 1) { return 55; } - // parse enum selection_effects - if (buf[56] == "DUMMY") { - this->selection_effect = selection_effects::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[56] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[57].c_str(), "%f", &this->selection_shape_x) != 1) { return 57; } - if (sscanf(buf[58].c_str(), "%f", &this->selection_shape_y) != 1) { return 58; } - if (sscanf(buf[59].c_str(), "%f", &this->selection_shape_z) != 1) { return 59; } - this->resource_storage.filename = buf[60]; - this->damage_graphics.filename = buf[61]; - if (sscanf(buf[62].c_str(), "%hd", &this->selection_sound_id) != 1) { return 62; } - if (sscanf(buf[63].c_str(), "%hd", &this->dying_sound_id) != 1) { return 63; } - // parse enum attack_modes - if (buf[64] == "DUMMY") { - this->old_attack_mode = attack_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[64] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[65].c_str(), "%hhd", &this->convert_terrain) != 1) { return 65; } - this->name = buf[66]; - if (sscanf(buf[67].c_str(), "%hd", &this->id1) != 1) { return 67; } - if (sscanf(buf[68].c_str(), "%hd", &this->id2) != 1) { return 68; } - if (sscanf(buf[69].c_str(), "%f", &this->speed) != 1) { return 69; } - if (sscanf(buf[70].c_str(), "%hd", &this->move_graphics) != 1) { return 70; } - if (sscanf(buf[71].c_str(), "%hd", &this->run_graphics) != 1) { return 71; } - if (sscanf(buf[72].c_str(), "%f", &this->turn_speed) != 1) { return 72; } - if (sscanf(buf[73].c_str(), "%hhd", &this->old_size_class) != 1) { return 73; } - if (sscanf(buf[74].c_str(), "%hd", &this->trail_unit_id) != 1) { return 74; } - if (sscanf(buf[75].c_str(), "%hhu", &this->trail_opsions) != 1) { return 75; } - if (sscanf(buf[76].c_str(), "%f", &this->trail_spacing) != 1) { return 76; } - if (sscanf(buf[77].c_str(), "%hhd", &this->old_move_algorithm) != 1) { return 77; } - if (sscanf(buf[78].c_str(), "%f", &this->turn_radius) != 1) { return 78; } - if (sscanf(buf[79].c_str(), "%f", &this->turn_radius_speed) != 1) { return 79; } - if (sscanf(buf[80].c_str(), "%f", &this->max_yaw_per_sec_moving) != 1) { return 80; } - if (sscanf(buf[81].c_str(), "%f", &this->stationary_yaw_revolution_time) != 1) { return 81; } - if (sscanf(buf[82].c_str(), "%f", &this->max_yaw_per_sec_stationary) != 1) { return 82; } - if (sscanf(buf[83].c_str(), "%hd", &this->default_task_id) != 1) { return 83; } - if (sscanf(buf[84].c_str(), "%f", &this->search_radius) != 1) { return 84; } - if (sscanf(buf[85].c_str(), "%f", &this->work_rate) != 1) { return 85; } - if (sscanf(buf[87].c_str(), "%hhd", &this->task_group) != 1) { return 87; } - if (sscanf(buf[88].c_str(), "%hd", &this->command_sound_id) != 1) { return 88; } - if (sscanf(buf[89].c_str(), "%hd", &this->stop_sound_id) != 1) { return 89; } - if (sscanf(buf[90].c_str(), "%hhd", &this->run_pattern) != 1) { return 90; } - if (sscanf(buf[91].c_str(), "%hd", &this->default_armor) != 1) { return 91; } - this->attacks.filename = buf[92]; - this->armors.filename = buf[93]; - // parse enum boundary_ids - if (buf[94] == "DUMMY") { - this->boundary_id = boundary_ids::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[94] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[95].c_str(), "%f", &this->weapon_range_max) != 1) { return 95; } - if (sscanf(buf[96].c_str(), "%f", &this->blast_range) != 1) { return 96; } - if (sscanf(buf[97].c_str(), "%f", &this->attack_speed) != 1) { return 97; } - if (sscanf(buf[98].c_str(), "%hd", &this->attack_projectile_primary_unit_id) != 1) { return 98; } - if (sscanf(buf[99].c_str(), "%hd", &this->accuracy) != 1) { return 99; } - if (sscanf(buf[100].c_str(), "%hhd", &this->break_off_combat) != 1) { return 100; } - if (sscanf(buf[101].c_str(), "%hd", &this->frame_delay) != 1) { return 101; } - // parse enum range_damage_type - if (buf[103] == "DUMMY") { - this->blast_level_offence = range_damage_type::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[103] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[104].c_str(), "%f", &this->weapon_range_min) != 1) { return 104; } - if (sscanf(buf[105].c_str(), "%f", &this->accuracy_dispersion) != 1) { return 105; } - if (sscanf(buf[106].c_str(), "%hd", &this->attack_sprite_id) != 1) { return 106; } - if (sscanf(buf[107].c_str(), "%hd", &this->melee_armor_displayed) != 1) { return 107; } - if (sscanf(buf[108].c_str(), "%hd", &this->attack_displayed) != 1) { return 108; } - if (sscanf(buf[109].c_str(), "%f", &this->range_displayed) != 1) { return 109; } - if (sscanf(buf[110].c_str(), "%f", &this->reload_time_displayed) != 1) { return 110; } - - return -1; -} - -bool projectile_unit::recurse(const openage::util::CSVCollection &storage, const std::string &basedir) { - this->resource_storage.read(storage, basedir); - this->damage_graphics.read(storage, basedir); - this->attacks.read(storage, basedir); - this->armors.read(storage, basedir); - - return true; -} - -int resource_cost::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', resource_cost::member_count - ); - - if (buf.size() != resource_cost::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing resource_cost led to " - << buf.size() - << " columns (expected " - << resource_cost::member_count - << ")!" - ); - } - - // parse enum resource_types - if (buf[0] == "DUMMY") { - this->type_id = resource_types::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[0] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[1].c_str(), "%hd", &this->amount) != 1) { return 1; } - if (sscanf(buf[2].c_str(), "%hd", &this->enabled) != 1) { return 2; } - - return -1; -} - -bool resource_cost::recurse(const openage::util::CSVCollection & /*storage*/, const std::string & /*basedir*/) { - return true; -} - -int resource_storage::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', resource_storage::member_count - ); - - if (buf.size() != resource_storage::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing resource_storage led to " - << buf.size() - << " columns (expected " - << resource_storage::member_count - << ")!" - ); - } - - if (sscanf(buf[0].c_str(), "%hd", &this->type) != 1) { return 0; } - if (sscanf(buf[1].c_str(), "%f", &this->amount) != 1) { return 1; } - // parse enum resource_handling - if (buf[2] == "DUMMY") { - this->used_mode = resource_handling::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[2] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - - return -1; -} - -bool resource_storage::recurse(const openage::util::CSVCollection & /*storage*/, const std::string & /*basedir*/) { - return true; -} - -int tree_unit::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', tree_unit::member_count - ); - - if (buf.size() != tree_unit::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing tree_unit led to " - << buf.size() - << " columns (expected " - << tree_unit::member_count - << ")!" - ); - } - - if (sscanf(buf[0].c_str(), "%hd", &this->id0) != 1) { return 0; } - if (sscanf(buf[1].c_str(), "%hu", &this->language_dll_name) != 1) { return 1; } - if (sscanf(buf[2].c_str(), "%hu", &this->language_dll_creation) != 1) { return 2; } - // parse enum unit_classes - if (buf[3] == "DUMMY") { - this->unit_class = unit_classes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[3] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[4].c_str(), "%hd", &this->idle_graphic0) != 1) { return 4; } - if (sscanf(buf[5].c_str(), "%hd", &this->idle_graphic1) != 1) { return 5; } - if (sscanf(buf[6].c_str(), "%hd", &this->dying_graphic) != 1) { return 6; } - if (sscanf(buf[7].c_str(), "%hd", &this->undead_graphic) != 1) { return 7; } - if (sscanf(buf[8].c_str(), "%hhd", &this->death_mode) != 1) { return 8; } - if (sscanf(buf[9].c_str(), "%hd", &this->hit_points) != 1) { return 9; } - if (sscanf(buf[10].c_str(), "%f", &this->line_of_sight) != 1) { return 10; } - if (sscanf(buf[11].c_str(), "%hhd", &this->garrison_capacity) != 1) { return 11; } - if (sscanf(buf[12].c_str(), "%f", &this->radius_x) != 1) { return 12; } - if (sscanf(buf[13].c_str(), "%f", &this->radius_y) != 1) { return 13; } - if (sscanf(buf[14].c_str(), "%f", &this->radius_z) != 1) { return 14; } - if (sscanf(buf[15].c_str(), "%hd", &this->train_sound_id) != 1) { return 15; } - if (sscanf(buf[16].c_str(), "%hd", &this->damage_sound_id) != 1) { return 16; } - if (sscanf(buf[17].c_str(), "%hd", &this->dead_unit_id) != 1) { return 17; } - if (sscanf(buf[18].c_str(), "%hhd", &this->placement_mode) != 1) { return 18; } - if (sscanf(buf[19].c_str(), "%hhd", &this->can_be_built_on) != 1) { return 19; } - if (sscanf(buf[20].c_str(), "%hd", &this->icon_id) != 1) { return 20; } - if (sscanf(buf[21].c_str(), "%hhd", &this->hidden_in_editor) != 1) { return 21; } - if (sscanf(buf[22].c_str(), "%hd", &this->old_portrait_icon_id) != 1) { return 22; } - if (sscanf(buf[23].c_str(), "%hhd", &this->enabled) != 1) { return 23; } - if (sscanf(buf[24].c_str(), "%hd", &this->placement_side_terrain0) != 1) { return 24; } - if (sscanf(buf[25].c_str(), "%hd", &this->placement_side_terrain1) != 1) { return 25; } - if (sscanf(buf[26].c_str(), "%hd", &this->placement_terrain0) != 1) { return 26; } - if (sscanf(buf[27].c_str(), "%hd", &this->placement_terrain1) != 1) { return 27; } - if (sscanf(buf[28].c_str(), "%f", &this->clearance_size_x) != 1) { return 28; } - if (sscanf(buf[29].c_str(), "%f", &this->clearance_size_y) != 1) { return 29; } - // parse enum elevation_modes - if (buf[30] == "DUMMY") { - this->elevation_mode = elevation_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[30] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum fog_visibility - if (buf[31] == "DUMMY") { - this->visible_in_fog = fog_visibility::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[31] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum ground_type - if (buf[32] == "DUMMY") { - this->terrain_restriction = ground_type::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[32] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[33].c_str(), "%hhd", &this->fly_mode) != 1) { return 33; } - if (sscanf(buf[34].c_str(), "%hd", &this->resource_capacity) != 1) { return 34; } - if (sscanf(buf[35].c_str(), "%f", &this->resource_decay) != 1) { return 35; } - // parse enum blast_types - if (buf[36] == "DUMMY") { - this->blast_defense_level = blast_types::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[36] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum combat_levels - if (buf[37] == "DUMMY") { - this->combat_level = combat_levels::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[37] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum interaction_modes - if (buf[38] == "DUMMY") { - this->interaction_mode = interaction_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[38] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum minimap_modes - if (buf[39] == "DUMMY") { - this->map_draw_level = minimap_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[39] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum command_attributes - if (buf[40] == "DUMMY") { - this->unit_level = command_attributes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[40] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[41].c_str(), "%f", &this->attack_reaction) != 1) { return 41; } - if (sscanf(buf[42].c_str(), "%hhd", &this->minimap_color) != 1) { return 42; } - if (sscanf(buf[43].c_str(), "%d", &this->language_dll_help) != 1) { return 43; } - if (sscanf(buf[44].c_str(), "%d", &this->language_dll_hotkey_text) != 1) { return 44; } - if (sscanf(buf[45].c_str(), "%d", &this->hot_keys) != 1) { return 45; } - if (sscanf(buf[46].c_str(), "%hhd", &this->recyclable) != 1) { return 46; } - if (sscanf(buf[47].c_str(), "%hhd", &this->enable_auto_gather) != 1) { return 47; } - if (sscanf(buf[48].c_str(), "%hhd", &this->doppelgaenger_on_death) != 1) { return 48; } - if (sscanf(buf[49].c_str(), "%hhd", &this->resource_gather_drop) != 1) { return 49; } - if (sscanf(buf[50].c_str(), "%hhu", &this->occlusion_mode) != 1) { return 50; } - // parse enum obstruction_types - if (buf[51] == "DUMMY") { - this->obstruction_type = obstruction_types::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[51] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[52].c_str(), "%hhd", &this->obstruction_class) != 1) { return 52; } - if (sscanf(buf[53].c_str(), "%hhu", &this->trait) != 1) { return 53; } - if (sscanf(buf[54].c_str(), "%hhd", &this->civilization_id) != 1) { return 54; } - if (sscanf(buf[55].c_str(), "%hd", &this->attribute_piece) != 1) { return 55; } - // parse enum selection_effects - if (buf[56] == "DUMMY") { - this->selection_effect = selection_effects::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[56] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[57].c_str(), "%f", &this->selection_shape_x) != 1) { return 57; } - if (sscanf(buf[58].c_str(), "%f", &this->selection_shape_y) != 1) { return 58; } - if (sscanf(buf[59].c_str(), "%f", &this->selection_shape_z) != 1) { return 59; } - this->resource_storage.filename = buf[60]; - this->damage_graphics.filename = buf[61]; - if (sscanf(buf[62].c_str(), "%hd", &this->selection_sound_id) != 1) { return 62; } - if (sscanf(buf[63].c_str(), "%hd", &this->dying_sound_id) != 1) { return 63; } - // parse enum attack_modes - if (buf[64] == "DUMMY") { - this->old_attack_mode = attack_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[64] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[65].c_str(), "%hhd", &this->convert_terrain) != 1) { return 65; } - this->name = buf[66]; - if (sscanf(buf[67].c_str(), "%hd", &this->id1) != 1) { return 67; } - if (sscanf(buf[68].c_str(), "%hd", &this->id2) != 1) { return 68; } - - return -1; -} - -bool tree_unit::recurse(const openage::util::CSVCollection &storage, const std::string &basedir) { - this->resource_storage.read(storage, basedir); - this->damage_graphics.read(storage, basedir); - - return true; -} - -int unit_command::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', unit_command::member_count - ); - - if (buf.size() != unit_command::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing unit_command led to " - << buf.size() - << " columns (expected " - << unit_command::member_count - << ")!" - ); - } - - if (sscanf(buf[0].c_str(), "%hd", &this->command_used) != 1) { return 0; } - if (sscanf(buf[1].c_str(), "%hd", &this->command_id) != 1) { return 1; } - if (sscanf(buf[2].c_str(), "%hhd", &this->is_default) != 1) { return 2; } - // parse enum command_ability - if (buf[3] == "DUMMY") { - this->type = command_ability::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[3] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[4].c_str(), "%hd", &this->class_id) != 1) { return 4; } - if (sscanf(buf[5].c_str(), "%hd", &this->unit_id) != 1) { return 5; } - if (sscanf(buf[6].c_str(), "%hd", &this->terrain_id) != 1) { return 6; } - if (sscanf(buf[7].c_str(), "%hd", &this->resource_in) != 1) { return 7; } - if (sscanf(buf[8].c_str(), "%hd", &this->resource_multiplier) != 1) { return 8; } - if (sscanf(buf[9].c_str(), "%hd", &this->resource_out) != 1) { return 9; } - if (sscanf(buf[10].c_str(), "%hd", &this->unused_resource) != 1) { return 10; } - if (sscanf(buf[11].c_str(), "%f", &this->work_value1) != 1) { return 11; } - if (sscanf(buf[12].c_str(), "%f", &this->work_value2) != 1) { return 12; } - if (sscanf(buf[13].c_str(), "%f", &this->work_range) != 1) { return 13; } - if (sscanf(buf[14].c_str(), "%hhd", &this->search_mode) != 1) { return 14; } - if (sscanf(buf[15].c_str(), "%f", &this->search_time) != 1) { return 15; } - if (sscanf(buf[16].c_str(), "%hhd", &this->enable_targeting) != 1) { return 16; } - if (sscanf(buf[17].c_str(), "%hhd", &this->combat_level_flag) != 1) { return 17; } - if (sscanf(buf[18].c_str(), "%hd", &this->gather_type) != 1) { return 18; } - // parse enum selection_type - if (buf[19] == "DUMMY") { - this->owner_type = selection_type::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[19] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[20].c_str(), "%hhd", &this->carry_check) != 1) { return 20; } - if (sscanf(buf[21].c_str(), "%hhd", &this->state_build) != 1) { return 21; } - if (sscanf(buf[22].c_str(), "%hd", &this->move_sprite_id) != 1) { return 22; } - if (sscanf(buf[23].c_str(), "%hd", &this->proceed_sprite_id) != 1) { return 23; } - if (sscanf(buf[24].c_str(), "%hd", &this->work_sprite_id) != 1) { return 24; } - if (sscanf(buf[25].c_str(), "%hd", &this->carry_sprite_id) != 1) { return 25; } - if (sscanf(buf[26].c_str(), "%hd", &this->resource_gather_sound_id) != 1) { return 26; } - if (sscanf(buf[27].c_str(), "%hd", &this->resource_deposit_sound_id) != 1) { return 27; } - - return -1; -} - -bool unit_command::recurse(const openage::util::CSVCollection & /*storage*/, const std::string & /*basedir*/) { - return true; -} - -int unit_header::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', unit_header::member_count - ); - - if (buf.size() != unit_header::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing unit_header led to " - << buf.size() - << " columns (expected " - << unit_header::member_count - << ")!" - ); - } - - // remember if the following members are undefined - if (buf[0] == "data_absent") { - this->exists = 0; - } else if (buf[0] == "data_exists") { - this->exists = 1; - } else { - throw openage::error::Error(ERR << "unexpected value '"<< buf[0] << "' for ContinueReadMember"); - } - this->unit_commands.filename = buf[1]; - - return -1; -} - -bool unit_header::recurse(const openage::util::CSVCollection &storage, const std::string &basedir) { - this->unit_commands.read(storage, basedir); - - return true; -} - -int unit_object::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', unit_object::member_count - ); - - if (buf.size() != unit_object::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing unit_object led to " - << buf.size() - << " columns (expected " - << unit_object::member_count - << ")!" - ); - } - - if (sscanf(buf[0].c_str(), "%hd", &this->id0) != 1) { return 0; } - if (sscanf(buf[1].c_str(), "%hu", &this->language_dll_name) != 1) { return 1; } - if (sscanf(buf[2].c_str(), "%hu", &this->language_dll_creation) != 1) { return 2; } - // parse enum unit_classes - if (buf[3] == "DUMMY") { - this->unit_class = unit_classes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[3] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[4].c_str(), "%hd", &this->idle_graphic0) != 1) { return 4; } - if (sscanf(buf[5].c_str(), "%hd", &this->idle_graphic1) != 1) { return 5; } - if (sscanf(buf[6].c_str(), "%hd", &this->dying_graphic) != 1) { return 6; } - if (sscanf(buf[7].c_str(), "%hd", &this->undead_graphic) != 1) { return 7; } - if (sscanf(buf[8].c_str(), "%hhd", &this->death_mode) != 1) { return 8; } - if (sscanf(buf[9].c_str(), "%hd", &this->hit_points) != 1) { return 9; } - if (sscanf(buf[10].c_str(), "%f", &this->line_of_sight) != 1) { return 10; } - if (sscanf(buf[11].c_str(), "%hhd", &this->garrison_capacity) != 1) { return 11; } - if (sscanf(buf[12].c_str(), "%f", &this->radius_x) != 1) { return 12; } - if (sscanf(buf[13].c_str(), "%f", &this->radius_y) != 1) { return 13; } - if (sscanf(buf[14].c_str(), "%f", &this->radius_z) != 1) { return 14; } - if (sscanf(buf[15].c_str(), "%hd", &this->train_sound_id) != 1) { return 15; } - if (sscanf(buf[16].c_str(), "%hd", &this->damage_sound_id) != 1) { return 16; } - if (sscanf(buf[17].c_str(), "%hd", &this->dead_unit_id) != 1) { return 17; } - if (sscanf(buf[18].c_str(), "%hhd", &this->placement_mode) != 1) { return 18; } - if (sscanf(buf[19].c_str(), "%hhd", &this->can_be_built_on) != 1) { return 19; } - if (sscanf(buf[20].c_str(), "%hd", &this->icon_id) != 1) { return 20; } - if (sscanf(buf[21].c_str(), "%hhd", &this->hidden_in_editor) != 1) { return 21; } - if (sscanf(buf[22].c_str(), "%hd", &this->old_portrait_icon_id) != 1) { return 22; } - if (sscanf(buf[23].c_str(), "%hhd", &this->enabled) != 1) { return 23; } - if (sscanf(buf[24].c_str(), "%hd", &this->placement_side_terrain0) != 1) { return 24; } - if (sscanf(buf[25].c_str(), "%hd", &this->placement_side_terrain1) != 1) { return 25; } - if (sscanf(buf[26].c_str(), "%hd", &this->placement_terrain0) != 1) { return 26; } - if (sscanf(buf[27].c_str(), "%hd", &this->placement_terrain1) != 1) { return 27; } - if (sscanf(buf[28].c_str(), "%f", &this->clearance_size_x) != 1) { return 28; } - if (sscanf(buf[29].c_str(), "%f", &this->clearance_size_y) != 1) { return 29; } - // parse enum elevation_modes - if (buf[30] == "DUMMY") { - this->elevation_mode = elevation_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[30] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum fog_visibility - if (buf[31] == "DUMMY") { - this->visible_in_fog = fog_visibility::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[31] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum ground_type - if (buf[32] == "DUMMY") { - this->terrain_restriction = ground_type::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[32] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[33].c_str(), "%hhd", &this->fly_mode) != 1) { return 33; } - if (sscanf(buf[34].c_str(), "%hd", &this->resource_capacity) != 1) { return 34; } - if (sscanf(buf[35].c_str(), "%f", &this->resource_decay) != 1) { return 35; } - // parse enum blast_types - if (buf[36] == "DUMMY") { - this->blast_defense_level = blast_types::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[36] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum combat_levels - if (buf[37] == "DUMMY") { - this->combat_level = combat_levels::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[37] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum interaction_modes - if (buf[38] == "DUMMY") { - this->interaction_mode = interaction_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[38] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum minimap_modes - if (buf[39] == "DUMMY") { - this->map_draw_level = minimap_modes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[39] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - // parse enum command_attributes - if (buf[40] == "DUMMY") { - this->unit_level = command_attributes::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[40] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[41].c_str(), "%f", &this->attack_reaction) != 1) { return 41; } - if (sscanf(buf[42].c_str(), "%hhd", &this->minimap_color) != 1) { return 42; } - if (sscanf(buf[43].c_str(), "%d", &this->language_dll_help) != 1) { return 43; } - if (sscanf(buf[44].c_str(), "%d", &this->language_dll_hotkey_text) != 1) { return 44; } - if (sscanf(buf[45].c_str(), "%d", &this->hot_keys) != 1) { return 45; } - if (sscanf(buf[46].c_str(), "%hhd", &this->recyclable) != 1) { return 46; } - if (sscanf(buf[47].c_str(), "%hhd", &this->enable_auto_gather) != 1) { return 47; } - if (sscanf(buf[48].c_str(), "%hhd", &this->doppelgaenger_on_death) != 1) { return 48; } - if (sscanf(buf[49].c_str(), "%hhd", &this->resource_gather_drop) != 1) { return 49; } - if (sscanf(buf[50].c_str(), "%hhu", &this->occlusion_mode) != 1) { return 50; } - // parse enum obstruction_types - if (buf[51] == "DUMMY") { - this->obstruction_type = obstruction_types::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[51] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[52].c_str(), "%hhd", &this->obstruction_class) != 1) { return 52; } - if (sscanf(buf[53].c_str(), "%hhu", &this->trait) != 1) { return 53; } - if (sscanf(buf[54].c_str(), "%hhd", &this->civilization_id) != 1) { return 54; } - if (sscanf(buf[55].c_str(), "%hd", &this->attribute_piece) != 1) { return 55; } - // parse enum selection_effects - if (buf[56] == "DUMMY") { - this->selection_effect = selection_effects::DUMMY; - } - else { - throw openage::error::Error( - MSG(err) - << "unknown enum value '" << buf[56] - << "' encountered. valid are: " - "DUMMY\n---\n" - );} - if (sscanf(buf[65].c_str(), "%hhd", &this->convert_terrain) != 1) { return 65; } - this->name = buf[66]; - if (sscanf(buf[67].c_str(), "%hd", &this->id1) != 1) { return 67; } - if (sscanf(buf[68].c_str(), "%hd", &this->id2) != 1) { return 68; } - - return -1; -} - -bool unit_object::recurse(const openage::util::CSVCollection &storage, const std::string &basedir) { - this->resource_storage.read(storage, basedir); - this->damage_graphics.read(storage, basedir); - - return true; -} - -} // gamedata -} // openage diff --git a/libopenage/gamedata/unit_dummy.h b/libopenage/gamedata/unit_dummy.h deleted file mode 100644 index 4398fa0d11..0000000000 --- a/libopenage/gamedata/unit_dummy.h +++ /dev/null @@ -1,515 +0,0 @@ -// Copyright 2013-2021 the openage authors. See copying.md for legal info. - -// Warning: this file is a dummy file and was auto-generated by the v0.4.1 converter; -// its purpose is to keep the deprecated gamestate compilable and intact; -// these files keep only the minimum functionality and should not be changed; -// For details, see buildsystem/codegen.cmake and openage/codegen. - -#pragma once - -#include -#include -#include -#include "util/csv.h" - - - -namespace openage { -namespace gamedata { - -enum class attack_modes { - DUMMY -}; - - -enum class blast_types { - DUMMY -}; - - -enum class boundary_ids { - DUMMY -}; - - -/** - * a possible building annex. - */ -struct building_annex { - int16_t unit_id; - float misplaced0; - float misplaced1; - static constexpr size_t member_count = 3; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -enum class combat_levels { - DUMMY -}; - - -enum class command_ability { - DUMMY -}; - - -enum class command_attributes { - DUMMY -}; - - -enum class creatable_types { - DUMMY -}; - - -enum class damage_draw_type { - DUMMY -}; - - -enum class elevation_modes { - DUMMY -}; - - -enum class fog_visibility { - DUMMY -}; - - -enum class garrison_types { - DUMMY -}; - - -enum class ground_type { - DUMMY, - WATER, - WATER_0x0D, - WATER_SHIP_0x03, - WATER_SHIP_0x0F, - SOLID, - FOUNDATION, - NO_ICE_0x08, - FOREST -}; - - -enum class hit_class { - DUMMY -}; - - -enum class interaction_modes { - DUMMY -}; - - -enum class minimap_modes { - DUMMY -}; - - -enum class obstruction_types { - DUMMY -}; - - -enum class range_damage_type { - DUMMY -}; - - -enum class resource_handling { - DUMMY -}; - - -enum class resource_types { - DUMMY -}; - - -enum class selection_effects { - DUMMY -}; - - -enum class selection_type { - DUMMY -}; - - -enum class unit_classes { - DUMMY, - BUILDING, - CIVILIAN, - TREES, - HERDABLE, - GOLD_MINE, - STONE_MINE, - BERRY_BUSH, - PREY_ANIMAL, - SEA_FISH, - FISHING_BOAT -}; - - -/** - * stores one possible unit image that is displayed at a given damage percentage. - */ -struct damage_graphic { - int16_t graphic_id; - int8_t damage_percent; - int8_t old_apply_mode; - damage_draw_type apply_mode; - static constexpr size_t member_count = 4; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -/** - * determines the resource storage capacity for one unit mode. - */ -struct resource_storage { - int16_t type; - float amount; - resource_handling used_mode; - static constexpr size_t member_count = 3; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -/** - * base properties for all units. - */ -struct unit_object { - int16_t id0; - uint16_t language_dll_name; - uint16_t language_dll_creation; - unit_classes unit_class; - int16_t idle_graphic0; - int16_t idle_graphic1; - int16_t dying_graphic; - int16_t undead_graphic; - int8_t death_mode; - int16_t hit_points; - float line_of_sight; - int8_t garrison_capacity; - float radius_x; - float radius_y; - float radius_z; - int16_t train_sound_id; - int16_t damage_sound_id; - int16_t dead_unit_id; - int8_t placement_mode; - int8_t can_be_built_on; - int16_t icon_id; - int8_t hidden_in_editor; - int16_t old_portrait_icon_id; - int8_t enabled; - int16_t placement_side_terrain0; - int16_t placement_side_terrain1; - int16_t placement_terrain0; - int16_t placement_terrain1; - float clearance_size_x; - float clearance_size_y; - elevation_modes elevation_mode; - fog_visibility visible_in_fog; - ground_type terrain_restriction; - int8_t fly_mode; - int16_t resource_capacity; - float resource_decay; - blast_types blast_defense_level; - combat_levels combat_level; - interaction_modes interaction_mode; - minimap_modes map_draw_level; - command_attributes unit_level; - float attack_reaction; - int8_t minimap_color; - int32_t language_dll_help; - int32_t language_dll_hotkey_text; - int32_t hot_keys; - int8_t recyclable; - int8_t enable_auto_gather; - int8_t doppelgaenger_on_death; - int8_t resource_gather_drop; - uint8_t occlusion_mode; - obstruction_types obstruction_type; - int8_t obstruction_class; - uint8_t trait; - int8_t civilization_id; - int16_t attribute_piece; - selection_effects selection_effect; - float selection_shape_x; - float selection_shape_y; - float selection_shape_z; - openage::util::csv_subdata resource_storage; - openage::util::csv_subdata damage_graphics; - int16_t selection_sound_id; - int16_t dying_sound_id; - attack_modes old_attack_mode; - int8_t convert_terrain; - std::string name; - int16_t id1; - int16_t id2; - static constexpr size_t member_count = 69; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -/** - * adds speed property to units. - */ -struct animated_unit : unit_object { - float speed; - static constexpr size_t member_count = 70; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -/** - * weird doppelganger unit thats actually the same as an animated unit. - */ -struct doppelganger_unit : animated_unit { - static constexpr size_t member_count = 70; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -/** - * adds walking graphics, rotations and tracking properties to units. - */ -struct moving_unit : doppelganger_unit { - int16_t move_graphics; - int16_t run_graphics; - float turn_speed; - int8_t old_size_class; - int16_t trail_unit_id; - uint8_t trail_opsions; - float trail_spacing; - int8_t old_move_algorithm; - float turn_radius; - float turn_radius_speed; - float max_yaw_per_sec_moving; - float stationary_yaw_revolution_time; - float max_yaw_per_sec_stationary; - static constexpr size_t member_count = 83; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -/** - * adds search radius and work properties, as well as movement sounds. - */ -struct action_unit : moving_unit { - int16_t default_task_id; - float search_radius; - float work_rate; - int8_t task_group; - int16_t command_sound_id; - int16_t stop_sound_id; - int8_t run_pattern; - static constexpr size_t member_count = 91; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -/** - * stores attack amount for a damage type. - */ -struct hit_type { - hit_class type_id; - int16_t amount; - static constexpr size_t member_count = 2; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -/** - * adds attack and armor properties to units. - */ -struct projectile_unit : action_unit { - int16_t default_armor; - openage::util::csv_subdata attacks; - openage::util::csv_subdata armors; - boundary_ids boundary_id; - float weapon_range_max; - float blast_range; - float attack_speed; - int16_t attack_projectile_primary_unit_id; - int16_t accuracy; - int8_t break_off_combat; - int16_t frame_delay; - range_damage_type blast_level_offence; - float weapon_range_min; - float accuracy_dispersion; - int16_t attack_sprite_id; - int16_t melee_armor_displayed; - int16_t attack_displayed; - float range_displayed; - float reload_time_displayed; - static constexpr size_t member_count = 111; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -/** - * adds missile specific unit properties. - */ -struct missile_unit : projectile_unit { - int8_t projectile_type; - int8_t smart_mode; - int8_t drop_animation_mode; - int8_t penetration_mode; - int8_t area_of_effect_special; - float projectile_arc; - static constexpr size_t member_count = 117; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -/** - * stores cost for one resource for creating the unit. - */ -struct resource_cost { - resource_types type_id; - int16_t amount; - int16_t enabled; - static constexpr size_t member_count = 3; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -/** - * just a tree unit. - */ -struct tree_unit : unit_object { - static constexpr size_t member_count = 69; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -/** - * a command a single unit may receive by script or human. - */ -struct unit_command { - int16_t command_used; - int16_t command_id; - int8_t is_default; - command_ability type; - int16_t class_id; - int16_t unit_id; - int16_t terrain_id; - int16_t resource_in; - int16_t resource_multiplier; - int16_t resource_out; - int16_t unused_resource; - float work_value1; - float work_value2; - float work_range; - int8_t search_mode; - float search_time; - int8_t enable_targeting; - int8_t combat_level_flag; - int16_t gather_type; - selection_type owner_type; - int8_t carry_check; - int8_t state_build; - int16_t move_sprite_id; - int16_t proceed_sprite_id; - int16_t work_sprite_id; - int16_t carry_sprite_id; - int16_t resource_gather_sound_id; - int16_t resource_deposit_sound_id; - static constexpr size_t member_count = 28; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -/** - * stores a bunch of unit commands. - */ -struct unit_header { - uint8_t exists; - openage::util::csv_subdata unit_commands; - static constexpr size_t member_count = 2; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -/** - * adds creation location and garrison unit properties. - */ -struct living_unit : projectile_unit { - openage::util::csv_subdata resource_cost; - int16_t creation_time; - int16_t train_location_id; - float rear_attack_modifier; - float flank_attack_modifier; - creatable_types creatable_type; - int8_t hero_mode; - int32_t garrison_graphic; - float attack_projectile_count; - int8_t attack_projectile_max_count; - float attack_projectile_spawning_area_width; - float attack_projectile_spawning_area_length; - float attack_projectile_spawning_area_randomness; - int32_t attack_projectile_secondary_unit_id; - int32_t special_graphic_id; - int8_t special_activation; - int16_t pierce_armor_displayed; - static constexpr size_t member_count = 128; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -/** - * construction graphics and garrison building properties for units. - */ -struct building_unit : living_unit { - int16_t construction_graphic_id; - int16_t snow_graphic_id; - int8_t adjacent_mode; - int16_t graphics_angle; - int8_t disappears_when_built; - int16_t stack_unit_id; - int16_t foundation_terrain_id; - int16_t old_overlay_id; - int16_t research_id; - int8_t can_burn; - openage::util::csv_subdata building_annex; - int16_t head_unit_id; - int16_t transform_unit_id; - int16_t transform_sound_id; - int16_t construction_sound_id; - garrison_types garrison_type; - float garrison_heal_rate; - float garrison_repair_rate; - int16_t salvage_unit_id; - static constexpr size_t member_count = 148; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -} // gamedata -} // openage diff --git a/libopenage/gamedata/util_dummy.cpp b/libopenage/gamedata/util_dummy.cpp deleted file mode 100644 index ee4c97bb50..0000000000 --- a/libopenage/gamedata/util_dummy.cpp +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2013-2021 the openage authors. See copying.md for legal info. - -// Warning: this file is a dummy file and was auto-generated by the v0.4.1 converter; -// its purpose is to keep the deprecated gamestate compilable and intact; -// these files keep only the minimum functionality and should not be changed; -// For details, see buildsystem/codegen.cmake and openage/codegen. - - -#include -#include "error/error.h" -#include "util_dummy.h" -#include "util/strings.h" - - -namespace openage { -namespace gamedata { - -constexpr size_t multisubtype_ref::member_count; -int multisubtype_ref::fill(const std::string &line) { - std::vector buf = openage::util::split_escape( - line, ',', multisubtype_ref::member_count - ); - - if (buf.size() != multisubtype_ref::member_count) { - throw openage::error::Error( - ERR - << "Tokenizing multisubtype_ref led to " - << buf.size() - << " columns (expected " - << multisubtype_ref::member_count - << ")!" - ); - } - - this->subtype = buf[0]; - this->filename = buf[1]; - - return -1; -} - -bool multisubtype_ref::recurse(const openage::util::CSVCollection & /*storage*/, const std::string & /*basedir*/) { - return true; -} - -} // gamedata -} // openage diff --git a/libopenage/gamedata/util_dummy.h b/libopenage/gamedata/util_dummy.h deleted file mode 100644 index 87c6b21868..0000000000 --- a/libopenage/gamedata/util_dummy.h +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2013-2021 the openage authors. See copying.md for legal info. - -// Warning: this file is a dummy file and was auto-generated by the v0.4.1 converter; -// its purpose is to keep the deprecated gamestate compilable and intact; -// these files keep only the minimum functionality and should not be changed; -// For details, see buildsystem/codegen.cmake and openage/codegen. - -#pragma once - -#include -#include -#include "util/csv.h" - - - -namespace openage { -namespace gamedata { - -/** - * format for multi-subtype references - */ -struct multisubtype_ref { - std::string subtype; - std::string filename; - static constexpr size_t member_count = 2; - int fill(const std::string &line); - bool recurse(const openage::util::CSVCollection &storage, const std::string &basedir); - -}; - -} // gamedata -} // openage diff --git a/libopenage/gamestate/CMakeLists.txt b/libopenage/gamestate/CMakeLists.txt index c89d9cd52d..3f0b57bf6b 100644 --- a/libopenage/gamestate/CMakeLists.txt +++ b/libopenage/gamestate/CMakeLists.txt @@ -5,9 +5,12 @@ add_sources(libopenage game_state.cpp game.cpp manager.cpp + map.cpp player.cpp simulation.cpp terrain_chunk.cpp + terrain_factory.cpp + terrain_tile.cpp terrain.cpp types.cpp world.cpp @@ -20,6 +23,3 @@ add_subdirectory(component/) add_subdirectory(demo/) add_subdirectory(event/) add_subdirectory(system/) - -# TODO: remove once migration is done. -add_subdirectory(old/) diff --git a/libopenage/gamestate/activity/CMakeLists.txt b/libopenage/gamestate/activity/CMakeLists.txt index 27b43c1776..78a78e7ab0 100644 --- a/libopenage/gamestate/activity/CMakeLists.txt +++ b/libopenage/gamestate/activity/CMakeLists.txt @@ -1,12 +1,15 @@ add_sources(libopenage activity.cpp end_node.cpp - event_node.cpp node.cpp start_node.cpp task_node.cpp task_system_node.cpp tests.cpp types.cpp - xor_node.cpp + xor_event_gate.cpp + xor_gate.cpp ) + +add_subdirectory("event") +add_subdirectory("condition") diff --git a/libopenage/gamestate/activity/activity.cpp b/libopenage/gamestate/activity/activity.cpp index 7ee478eda8..9af770b8fd 100644 --- a/libopenage/gamestate/activity/activity.cpp +++ b/libopenage/gamestate/activity/activity.cpp @@ -6,8 +6,8 @@ namespace openage::gamestate::activity { Activity::Activity(activity_id id, - activity_label label, - const std::shared_ptr &start) : + const std::shared_ptr &start, + activity_label label) : id{id}, label{label}, start{start} { diff --git a/libopenage/gamestate/activity/activity.h b/libopenage/gamestate/activity/activity.h index f5be9d254a..af86f434df 100644 --- a/libopenage/gamestate/activity/activity.h +++ b/libopenage/gamestate/activity/activity.h @@ -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. #pragma once @@ -18,19 +18,52 @@ using activity_label = std::string; */ class Activity { public: + /** + * Create a new activity. + * + * @param id Unique ID. + * @param start Start node in the graph. + * @param label Human-readable label (optional). + */ Activity(activity_id id, - activity_label label = "", - const std::shared_ptr &start = {}); + const std::shared_ptr &start, + activity_label label = ""); + /** + * Get the unique ID of this activity. + * + * @return Unique ID. + */ activity_id get_id() const; + /** + * Get the human-readable label of this activity. + * + * @return Human-readable label. + */ const activity_label get_label() const; + /** + * Get the start node of this activity. + * + * @return Start node. + */ const std::shared_ptr &get_start() const; private: + /** + * Unique ID. + */ const activity_id id; + + /** + * Human-readable label. + */ const activity_label label; + + /** + * Start node. + */ std::shared_ptr start; }; diff --git a/libopenage/gamestate/activity/condition/CMakeLists.txt b/libopenage/gamestate/activity/condition/CMakeLists.txt new file mode 100644 index 0000000000..aabd159b0c --- /dev/null +++ b/libopenage/gamestate/activity/condition/CMakeLists.txt @@ -0,0 +1,4 @@ +add_sources(libopenage + command_in_queue.cpp + next_command.cpp +) diff --git a/libopenage/gamestate/activity/condition/command_in_queue.cpp b/libopenage/gamestate/activity/condition/command_in_queue.cpp new file mode 100644 index 0000000000..7e300701f1 --- /dev/null +++ b/libopenage/gamestate/activity/condition/command_in_queue.cpp @@ -0,0 +1,19 @@ +// Copyright 2023-2023 the openage authors. See copying.md for legal info. + +#include "next_command.h" + +#include "gamestate/component/internal/command_queue.h" +#include "gamestate/game_entity.h" + + +namespace openage::gamestate::activity { + +bool command_in_queue(const time::time_t &time, + const std::shared_ptr &entity) { + auto command_queue = std::dynamic_pointer_cast( + entity->get_component(component::component_t::COMMANDQUEUE)); + + return not command_queue->get_queue().empty(time); +} + +} // namespace openage::gamestate::activity diff --git a/libopenage/gamestate/activity/condition/command_in_queue.h b/libopenage/gamestate/activity/condition/command_in_queue.h new file mode 100644 index 0000000000..67c7a794cc --- /dev/null +++ b/libopenage/gamestate/activity/condition/command_in_queue.h @@ -0,0 +1,28 @@ +// Copyright 2023-2023 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + +#include "time/time.h" + + +namespace openage::gamestate { +class GameEntity; + +namespace activity { + +/** + * Condition for command in queue check in the activity system. + * + * @param time Time when the condition is checked. + * @param entity Game entity. + * + * @return true if there is at least one command in the entity's command queue, false otherwise. + */ +bool command_in_queue(const time::time_t &time, + const std::shared_ptr &entity); + +} // namespace activity +} // namespace openage::gamestate diff --git a/libopenage/gamestate/activity/condition/next_command.cpp b/libopenage/gamestate/activity/condition/next_command.cpp new file mode 100644 index 0000000000..c0b619a783 --- /dev/null +++ b/libopenage/gamestate/activity/condition/next_command.cpp @@ -0,0 +1,37 @@ +// Copyright 2023-2023 the openage authors. See copying.md for legal info. + +#include "next_command.h" + +#include "gamestate/component/internal/command_queue.h" +#include "gamestate/game_entity.h" + + +namespace openage::gamestate::activity { + +bool next_command_idle(const time::time_t &time, + const std::shared_ptr &entity) { + auto command_queue = std::dynamic_pointer_cast( + entity->get_component(component::component_t::COMMANDQUEUE)); + + if (command_queue->get_queue().empty(time)) { + return false; + } + + auto command = command_queue->get_queue().front(time); + return command->get_type() == component::command::command_t::MOVE; +} + +bool next_command_move(const time::time_t &time, + const std::shared_ptr &entity) { + auto command_queue = std::dynamic_pointer_cast( + entity->get_component(component::component_t::COMMANDQUEUE)); + + if (command_queue->get_queue().empty(time)) { + return false; + } + + auto command = command_queue->get_queue().front(time); + return command->get_type() == component::command::command_t::MOVE; +} + +} // namespace openage::gamestate::activity diff --git a/libopenage/gamestate/activity/condition/next_command.h b/libopenage/gamestate/activity/condition/next_command.h new file mode 100644 index 0000000000..046a18cec6 --- /dev/null +++ b/libopenage/gamestate/activity/condition/next_command.h @@ -0,0 +1,39 @@ +// Copyright 2023-2023 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + +#include "time/time.h" + + +namespace openage::gamestate { +class GameEntity; + +namespace activity { + +/** + * Condition for next command check in the activity system. + * + * @param time Time when the condition is checked. + * @param entity Game entity. + * + * @return true if the entity has a idle command next in the queue, false otherwise. + */ +bool next_command_idle(const time::time_t &time, + const std::shared_ptr &entity); + +/** + * Condition for next command check in the activity system. + * + * @param time Time when the condition is checked. + * @param entity Game entity. + * + * @return true if the entity has a move command next in the queue, false otherwise. + */ +bool next_command_move(const time::time_t &time, + const std::shared_ptr &entity); + +} // namespace activity +} // namespace openage::gamestate diff --git a/libopenage/gamestate/activity/end_node.cpp b/libopenage/gamestate/activity/end_node.cpp index 01ecfebade..f933a08172 100644 --- a/libopenage/gamestate/activity/end_node.cpp +++ b/libopenage/gamestate/activity/end_node.cpp @@ -8,13 +8,9 @@ namespace openage::gamestate::activity { -EndNode::EndNode(node_id id, - node_label label) : +EndNode::EndNode(node_id_t id, + node_label_t label) : Node{id, label, {}} { } -void EndNode::add_output(const std::shared_ptr & /* output */) { - throw Error{ERR << "End node cannot have outputs"}; -} - } // namespace openage::gamestate::activity diff --git a/libopenage/gamestate/activity/end_node.h b/libopenage/gamestate/activity/end_node.h index c068f39475..b172325e10 100644 --- a/libopenage/gamestate/activity/end_node.h +++ b/libopenage/gamestate/activity/end_node.h @@ -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. #pragma once @@ -21,27 +21,18 @@ namespace openage::gamestate::activity { class EndNode : public Node { public: /** - * Create a new end node. - * - * @param id Unique identifier for this node. - * @param label Human-readable label (optional). - */ - EndNode(node_id id, - node_label label = "End"); + * Create a new end node. + * + * @param id Unique identifier for this node. + * @param label Human-readable label (optional). + */ + EndNode(node_id_t id, + node_label_t label = "End"); virtual ~EndNode() = default; inline node_t get_type() const override { return node_t::END; } - - /** - * Throws an error since end nodes are not supposed to have outputs - * - * @param output Output node. - * - * @throws openage::Error - */ - [[noreturn]] void add_output(const std::shared_ptr &output) override; }; } // namespace openage::gamestate::activity diff --git a/libopenage/gamestate/activity/event/CMakeLists.txt b/libopenage/gamestate/activity/event/CMakeLists.txt new file mode 100644 index 0000000000..863fa6d28a --- /dev/null +++ b/libopenage/gamestate/activity/event/CMakeLists.txt @@ -0,0 +1,4 @@ +add_sources(libopenage + command_in_queue.cpp + wait.cpp +) diff --git a/libopenage/gamestate/activity/event/command_in_queue.cpp b/libopenage/gamestate/activity/event/command_in_queue.cpp new file mode 100644 index 0000000000..af57692078 --- /dev/null +++ b/libopenage/gamestate/activity/event/command_in_queue.cpp @@ -0,0 +1,35 @@ +// Copyright 2023-2023 the openage authors. See copying.md for legal info. + +#include "command_in_queue.h" + +#include "event/event_loop.h" +#include "event/evententity.h" +#include "gamestate/component/internal/command_queue.h" +#include "gamestate/game_entity.h" +#include "gamestate/game_state.h" +#include "gamestate/manager.h" + + +namespace openage::gamestate::activity { + +std::shared_ptr primer_command_in_queue(const time::time_t &, + const std::shared_ptr &entity, + const std::shared_ptr &loop, + const std::shared_ptr &state, + size_t next_id) { + openage::event::EventHandler::param_map::map_t params{{"next", next_id}}; // move->get_id(); + auto ev = loop->create_event("game.process_command", + entity->get_manager(), + state, + // event is not executed until a command is available + time::TIME_MAX, + params); + auto entity_queue = std::dynamic_pointer_cast( + entity->get_component(component::component_t::COMMANDQUEUE)); + auto &queue = entity_queue->get_queue(); + queue.add_dependent(ev); + + return ev; +}; + +} // namespace openage::gamestate::activity diff --git a/libopenage/gamestate/activity/event/command_in_queue.h b/libopenage/gamestate/activity/event/command_in_queue.h new file mode 100644 index 0000000000..ebb71c166d --- /dev/null +++ b/libopenage/gamestate/activity/event/command_in_queue.h @@ -0,0 +1,42 @@ +// Copyright 2023-2023 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + +#include "time/time.h" + + +namespace openage { +namespace event { +class Event; +class EventLoop; +} // namespace event + +namespace gamestate { +class GameEntity; +class GameState; + +namespace activity { + +/** + * Primer for command in queue events in the activity system. + * + * @param time Current simulation time. + * @param entity Game entity. + * @param loop Event loop that the event is registered on. + * @param state Game state. + * @param next_id ID of the next node in the activity graph. + * + * @return Scheduled event. + */ +std::shared_ptr primer_command_in_queue(const time::time_t &, + const std::shared_ptr &entity, + const std::shared_ptr &loop, + const std::shared_ptr &state, + size_t next_id); + +} // namespace activity +} // namespace gamestate +} // namespace openage diff --git a/libopenage/gamestate/activity/event/wait.cpp b/libopenage/gamestate/activity/event/wait.cpp new file mode 100644 index 0000000000..6bfd03f632 --- /dev/null +++ b/libopenage/gamestate/activity/event/wait.cpp @@ -0,0 +1,29 @@ +// Copyright 2023-2023 the openage authors. See copying.md for legal info. + +#include "wait.h" + +#include "event/event_loop.h" +#include "event/evententity.h" +#include "gamestate/game_entity.h" +#include "gamestate/game_state.h" +#include "gamestate/manager.h" + + +namespace openage::gamestate::activity { + +std::shared_ptr primer_wait(const time::time_t &time, + const std::shared_ptr &entity, + const std::shared_ptr &loop, + const std::shared_ptr &state, + size_t next_id) { + openage::event::EventHandler::param_map::map_t params{{"next", next_id}}; + auto ev = loop->create_event("game.wait", + entity->get_manager(), + state, + time, + params); + + return ev; +}; + +} // namespace openage::gamestate::activity diff --git a/libopenage/gamestate/activity/event/wait.h b/libopenage/gamestate/activity/event/wait.h new file mode 100644 index 0000000000..c33be2a5be --- /dev/null +++ b/libopenage/gamestate/activity/event/wait.h @@ -0,0 +1,44 @@ +// Copyright 2023-2023 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + +#include "time/time.h" + + +namespace openage { +namespace event { +class Event; +class EventLoop; +} // namespace event + +namespace gamestate { +class GameEntity; +class GameState; + +namespace activity { + + +/** + * Primer for wait events in the activity system. + * + * @param time Wait until this time. If the time is in the past, the event is executed immediately. + * @param entity Game entity. + * @param loop Event loop that the event is registered on. + * @param state Game state. + * @param next_id ID of the next node in the activity graph. + * + * @return Scheduled event. + */ +std::shared_ptr primer_wait(const time::time_t &time, + const std::shared_ptr &entity, + const std::shared_ptr &loop, + const std::shared_ptr &state, + size_t next_id); + + +} // namespace activity +} // namespace gamestate +} // namespace openage diff --git a/libopenage/gamestate/activity/event_node.cpp b/libopenage/gamestate/activity/event_node.cpp deleted file mode 100644 index 3d28e007a3..0000000000 --- a/libopenage/gamestate/activity/event_node.cpp +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. - -#include "event_node.h" - -#include - - -namespace openage::gamestate::activity { - -XorEventGate::XorEventGate(node_id id, - node_label label, - const std::vector> &outputs, - event_primer_func_t primer_func, - event_next_func_t next_func) : - Node{id, label, outputs}, - primer_func{primer_func}, - next_func{next_func} { -} - -void XorEventGate::add_output(const std::shared_ptr &output) { - this->outputs.emplace(output->get_id(), output); -} - -void XorEventGate::set_primer_func(event_primer_func_t primer_func) { - this->primer_func = primer_func; -} - -void XorEventGate::set_next_func(event_next_func_t next_func) { - this->next_func = next_func; -} - -event_primer_func_t XorEventGate::get_primer_func() const { - return this->primer_func; -} - -event_next_func_t XorEventGate::get_next_func() const { - return this->next_func; -} - -} // namespace openage::gamestate::activity diff --git a/libopenage/gamestate/activity/event_node.h b/libopenage/gamestate/activity/event_node.h deleted file mode 100644 index a753682b68..0000000000 --- a/libopenage/gamestate/activity/event_node.h +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include -#include - -#include "error/error.h" -#include "log/message.h" - -#include "gamestate/activity/node.h" -#include "gamestate/activity/types.h" -#include "time/time.h" - - -namespace openage { -namespace event { -class Event; -class EventLoop; -} // namespace event - -namespace gamestate { -class GameEntity; -class GameState; - -namespace activity { - -using event_store_t = std::vector>; - -/* */ -/** - * Create and register an event on the event loop - * - * @param time Time at which the primer function is executed. - * @param entity Game entity that the node is associated with. - * @param loop Event loop that events are registered on. - * @param state Game state. - * - * @return List of events registered on the event loop. - */ -using event_primer_func_t = std::function &, - const std::shared_ptr &, - const std::shared_ptr &)>; - -/** - * Decide which node to visit after the event is handled. - * - * @param time Time at which the next function is executed. - * @param entity Game entity that the node is associated with. - * @param loop Event loop that events are registered on. - * @param state Game state. - * - * @return ID of the next node to visit. - */ -using event_next_func_t = std::function &, - const std::shared_ptr &, - const std::shared_ptr &)>; - - -static const event_primer_func_t no_event = [](const time::time_t &, - const std::shared_ptr &, - const std::shared_ptr &, - const std::shared_ptr &) { - throw Error{ERR << "No event primer function registered."}; - return event_store_t{}; -}; - -static const event_next_func_t no_next = [](const time::time_t &, - const std::shared_ptr &, - const std::shared_ptr &, - const std::shared_ptr &) { - throw Error{ERR << "No event next function registered."}; - return 0; -}; - - -/** - * Waits for an event to be executed before continuing the control flow. - */ -class XorEventGate : public Node { -public: - /** - * Create a new exclusive event gateway. - * - * @param id Unique identifier for this node. - * @param label Human-readable label (optional). - * @param outputs Output nodes (can be set later). - * @param primer_func Function to create and register the event. - * @param next_func Function to decide which node to visit after the event is handled. - */ - XorEventGate(node_id id, - node_label label = "Event", - const std::vector> &outputs = {}, - event_primer_func_t primer_func = no_event, - event_next_func_t next_func = no_next); - virtual ~XorEventGate() = default; - - inline node_t get_type() const override { - return node_t::XOR_EVENT_GATE; - } - - /** - * Add an output node. - * - * @param output Output node. - */ - void add_output(const std::shared_ptr &output) override; - - /** - * Set the function to create the event. - * - * @param primer_func Event creation function. - */ - void set_primer_func(event_primer_func_t primer_func); - - /** - * Set the function to decide which node to visit after the event is handled. - * - * @param next_func Next node function. - */ - void set_next_func(event_next_func_t next_func); - - /** - * Get the function to create the event. - * - * @return Event creation function. - */ - event_primer_func_t get_primer_func() const; - - /** - * Get the function to decide which node to visit after the event is handled. - * - * @return Next node function. - */ - event_next_func_t get_next_func() const; - -private: - /** - * Creates the event when the node is visited. - */ - event_primer_func_t primer_func; - - /** - * Decide which node to visit after the event is handled. - */ - event_next_func_t next_func; -}; - -} // namespace activity -} // namespace gamestate -} // namespace openage diff --git a/libopenage/gamestate/activity/node.cpp b/libopenage/gamestate/activity/node.cpp index 5c7f007d74..833b4031eb 100644 --- a/libopenage/gamestate/activity/node.cpp +++ b/libopenage/gamestate/activity/node.cpp @@ -10,8 +10,8 @@ namespace openage::gamestate::activity { -Node::Node(node_id id, - node_label label, +Node::Node(node_id_t id, + node_label_t label, const std::vector> &outputs) : outputs{}, id{id}, @@ -22,11 +22,11 @@ Node::Node(node_id id, } } -node_id Node::get_id() const { +node_id_t Node::get_id() const { return this->id; } -const node_label Node::get_label() const { +const node_label_t Node::get_label() const { return this->label; } @@ -42,7 +42,7 @@ std::string Node::str() const { return ret.str(); } -const std::shared_ptr &Node::next(node_id id) const { +const std::shared_ptr &Node::next(node_id_t id) const { if (not this->outputs.contains(id)) [[unlikely]] { throw Error{MSG(err) << "Node " << this->str() << " has no output with id " << id}; } diff --git a/libopenage/gamestate/activity/node.h b/libopenage/gamestate/activity/node.h index 9a8edfcf9f..ed56277930 100644 --- a/libopenage/gamestate/activity/node.h +++ b/libopenage/gamestate/activity/node.h @@ -13,8 +13,8 @@ namespace openage::gamestate::activity { -using node_id = size_t; -using node_label = std::string; +using node_id_t = size_t; +using node_label_t = std::string; /** * Node in the flow graph describing the activity. @@ -28,8 +28,8 @@ class Node { * @param label Human-readable label (optional). * @param outputs Output nodes. */ - Node(node_id id, - node_label label = "", + Node(node_id_t id, + node_label_t label = "", const std::vector> &outputs = {}); virtual ~Node() = default; @@ -45,14 +45,14 @@ class Node { * * @return The unique identifier. */ - node_id get_id() const; + node_id_t get_id() const; /** * Get the human-readable label for this node. * * @return Human-readable label. */ - const node_label get_label() const; + const node_label_t get_label() const; /** * Get a human-readable string representation of this node. @@ -67,31 +67,24 @@ class Node { * @param id Unique identifier of the output node. * @return Output node. */ - const std::shared_ptr &next(node_id id) const; - - /** - * Add an output node. - * - * @param output Output node. - */ - virtual void add_output(const std::shared_ptr &output) = 0; + const std::shared_ptr &next(node_id_t id) const; protected: /** * Output nodes. */ - std::unordered_map> outputs; + std::unordered_map> outputs; private: /** * Unique identifier for this node. */ - const node_id id; + const node_id_t id; /** * Human-readable label. */ - const node_label label; + const node_label_t label; }; } // namespace openage::gamestate::activity diff --git a/libopenage/gamestate/activity/start_node.cpp b/libopenage/gamestate/activity/start_node.cpp index 8686fc2f4e..a6b400fb1e 100644 --- a/libopenage/gamestate/activity/start_node.cpp +++ b/libopenage/gamestate/activity/start_node.cpp @@ -8,8 +8,8 @@ namespace openage::gamestate::activity { -StartNode::StartNode(node_id id, - node_label label, +StartNode::StartNode(node_id_t id, + node_label_t label, const std::shared_ptr &output) : Node{id, label} { if (output) { @@ -22,7 +22,7 @@ void StartNode::add_output(const std::shared_ptr &output) { this->outputs.emplace(output->get_id(), output); } -node_id StartNode::get_next() const { +node_id_t StartNode::get_next() const { return (*this->outputs.begin()).first; } diff --git a/libopenage/gamestate/activity/start_node.h b/libopenage/gamestate/activity/start_node.h index bec65e6ea7..6a9406b381 100644 --- a/libopenage/gamestate/activity/start_node.h +++ b/libopenage/gamestate/activity/start_node.h @@ -25,8 +25,8 @@ class StartNode : public Node { * @param label Human-readable label (optional). * @param output Next node to visit (can be set later). */ - StartNode(node_id id, - node_label label = "Start", + StartNode(node_id_t id, + node_label_t label = "Start", const std::shared_ptr &output = nullptr); virtual ~StartNode() = default; @@ -41,7 +41,7 @@ class StartNode : public Node { * * @param output Output node. */ - void add_output(const std::shared_ptr &output) override; + void add_output(const std::shared_ptr &output); /** * Get the next node to visit. @@ -49,7 +49,7 @@ class StartNode : public Node { * @param time Current time. * @return Next node to visit. */ - node_id get_next() const; + node_id_t get_next() const; }; } // namespace openage::gamestate::activity diff --git a/libopenage/gamestate/activity/task_node.cpp b/libopenage/gamestate/activity/task_node.cpp index 36b5de6663..59186eb451 100644 --- a/libopenage/gamestate/activity/task_node.cpp +++ b/libopenage/gamestate/activity/task_node.cpp @@ -8,8 +8,8 @@ namespace openage::gamestate::activity { -TaskCustom::TaskCustom(node_id id, - node_label label, +TaskCustom::TaskCustom(node_id_t id, + node_label_t label, const std::shared_ptr &output, task_func_t task_func) : Node{id, label}, @@ -32,7 +32,7 @@ task_func_t TaskCustom::get_task_func() const { return this->task_func; } -node_id TaskCustom::get_next() const { +node_id_t TaskCustom::get_next() const { return (*this->outputs.begin()).first; } diff --git a/libopenage/gamestate/activity/task_node.h b/libopenage/gamestate/activity/task_node.h index 0efd3f784b..86dd7a48b9 100644 --- a/libopenage/gamestate/activity/task_node.h +++ b/libopenage/gamestate/activity/task_node.h @@ -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. #pragma once @@ -32,15 +32,15 @@ static const task_func_t no_task = [](const time::time_t &, class TaskCustom : public Node { public: /** - * Create a new task node. - * - * @param id Unique identifier for this node. - * @param label Human-readable label (optional). - * @param task_func Action to perform when visiting this node (can be set later). - * @param output Next node to visit (optional). - */ - TaskCustom(node_id id, - node_label label = "TaskCustom", + * Create a new task node. + * + * @param id Unique identifier for this node. + * @param label Human-readable label (optional). + * @param task_func Action to perform when visiting this node (can be set later). + * @param output Next node to visit (optional). + */ + TaskCustom(node_id_t id, + node_label_t label = "TaskCustom", const std::shared_ptr &output = nullptr, task_func_t task_func = no_task); virtual ~TaskCustom() = default; @@ -50,24 +50,24 @@ class TaskCustom : public Node { } /** - * Set the current output node. - * - * @param output Output node. - */ - void add_output(const std::shared_ptr &output) override; + * Set the current output node. + * + * @param output Output node. + */ + void add_output(const std::shared_ptr &output); /** - * Set the task function. - * - * @param task_func Action to perform when visiting this node. - */ + * Set the task function. + * + * @param task_func Action to perform when visiting this node. + */ void set_task_func(task_func_t task_func); /** - * Get the task function. - * - * @return Action to perform when visiting this node. - */ + * Get the task function. + * + * @return Action to perform when visiting this node. + */ task_func_t get_task_func() const; /** @@ -76,12 +76,12 @@ class TaskCustom : public Node { * @param time Current time. * @return Next node to visit. */ - node_id get_next() const; + node_id_t get_next() const; private: /** - * Action to perform when visiting this node. - */ + * Action to perform when visiting this node. + */ task_func_t task_func; }; diff --git a/libopenage/gamestate/activity/task_system_node.cpp b/libopenage/gamestate/activity/task_system_node.cpp index 158bcc41b2..c73e00475e 100644 --- a/libopenage/gamestate/activity/task_system_node.cpp +++ b/libopenage/gamestate/activity/task_system_node.cpp @@ -8,8 +8,8 @@ namespace openage::gamestate::activity { -TaskSystemNode::TaskSystemNode(node_id id, - node_label label, +TaskSystemNode::TaskSystemNode(node_id_t id, + node_label_t label, const std::shared_ptr &output, system::system_id_t system_id) : Node{id, label}, @@ -32,7 +32,7 @@ system::system_id_t TaskSystemNode::get_system_id() const { return this->system_id; } -node_id TaskSystemNode::get_next() const { +node_id_t TaskSystemNode::get_next() const { return (*this->outputs.begin()).first; } diff --git a/libopenage/gamestate/activity/task_system_node.h b/libopenage/gamestate/activity/task_system_node.h index 8f9e6b1163..8724352b0e 100644 --- a/libopenage/gamestate/activity/task_system_node.h +++ b/libopenage/gamestate/activity/task_system_node.h @@ -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. #pragma once @@ -23,10 +23,10 @@ class TaskSystemNode : public Node { * @param id Unique identifier for this node. * @param label Human-readable label (optional). * @param output Next node to visit (optional). - * @param system_id System to run when visiting this node (can be set later). + * @param system_id System to run when visiting this node (can be set later). */ - TaskSystemNode(node_id id, - node_label label = "TaskSystem", + TaskSystemNode(node_id_t id, + node_label_t label = "TaskSystem", const std::shared_ptr &output = nullptr, system::system_id_t system_id = system::system_id_t::NONE); virtual ~TaskSystemNode() = default; @@ -36,24 +36,24 @@ class TaskSystemNode : public Node { } /** - * Set the current output node. - * - * @param output Output node. - */ - void add_output(const std::shared_ptr &output) override; + * Set the current output node. + * + * @param output Output node. + */ + void add_output(const std::shared_ptr &output); /** - * Set the system id. - * - * @param system_id System to run when visiting this node. - */ + * Set the system id. + * + * @param system_id System to run when visiting this node. + */ void set_system_id(system::system_id_t system_id); /** - * Get the system id. - * - * @return System to run when visiting this node. - */ + * Get the system id. + * + * @return System to run when visiting this node. + */ system::system_id_t get_system_id() const; /** @@ -62,12 +62,12 @@ class TaskSystemNode : public Node { * @param time Current time. * @return Next node to visit. */ - node_id get_next() const; + node_id_t get_next() const; private: /** - * System to run when visiting this node. - */ + * System to run when visiting this node. + */ system::system_id_t system_id; }; diff --git a/libopenage/gamestate/activity/tests.cpp b/libopenage/gamestate/activity/tests.cpp index 7588e5a9b0..1ddad0e7e8 100644 --- a/libopenage/gamestate/activity/tests.cpp +++ b/libopenage/gamestate/activity/tests.cpp @@ -15,12 +15,12 @@ #include "log/message.h" #include "gamestate/activity/end_node.h" -#include "gamestate/activity/event_node.h" #include "gamestate/activity/node.h" #include "gamestate/activity/start_node.h" #include "gamestate/activity/task_node.h" #include "gamestate/activity/types.h" -#include "gamestate/activity/xor_node.h" +#include "gamestate/activity/xor_event_gate.h" +#include "gamestate/activity/xor_gate.h" #include "time/time.h" @@ -33,7 +33,8 @@ namespace openage::gamestate::tests { * @param current_node Node where the control flow starts from. * @return Node where the control flow should continue. */ -const std::shared_ptr activity_flow(const std::shared_ptr ¤t_node); +const std::shared_ptr activity_flow(const std::shared_ptr ¤t_node, + const std::optional ev_params = std::nullopt); /** @@ -57,11 +58,11 @@ class TestActivityManager : public event::EventEntity { return "TestActivityManager"; } - void run() { + void run(const std::optional ev_params = std::nullopt) { if (not current_node) { throw Error{ERR << "No current node given"}; } - this->current_node = activity_flow(this->current_node); + this->current_node = activity_flow(this->current_node, ev_params); } std::shared_ptr current_node; @@ -93,9 +94,9 @@ class TestActivityHandler : public event::OnceEventHandler { const std::shared_ptr &target, const std::shared_ptr & /* state */, const time::time_t & /* time */, - const param_map & /* params */) override { + const param_map ¶ms) override { auto mgr_target = std::dynamic_pointer_cast(target); - mgr_target->run(); + mgr_target->run(params); } time::time_t predict_invoke_time(const std::shared_ptr & /* target */, @@ -106,14 +107,27 @@ class TestActivityHandler : public event::OnceEventHandler { }; -const std::shared_ptr activity_flow(const std::shared_ptr ¤t_node) { +const std::shared_ptr activity_flow(const std::shared_ptr ¤t_node, + const std::optional ev_params) { + // events that are currently being listened for + // in the gamestate these are stored in the activity component + static std::vector> events; + auto current = current_node; if (current->get_type() == activity::node_t::XOR_EVENT_GATE) { - auto node = std::static_pointer_cast(current); - auto event_next = node->get_next_func(); - auto next_id = event_next(0, nullptr, nullptr, nullptr); - current = node->next(next_id); + log::log(INFO << "Continuing from event node"); + if (not ev_params.has_value()) { + throw Error{ERR << "XorEventGate: No event parameters given on continue"}; + } + + auto next_id = ev_params.value().get("next"); + current = current->next(next_id); + + // cancel all other events that the manager may have been waiting for + for (auto &event : events) { + event->cancel(0); + } } while (current->get_type() != activity::node_t::END) { @@ -138,16 +152,30 @@ const std::shared_ptr activity_flow(const std::shared_ptr(current); - auto condition = node->get_condition_func(); - auto next_id = condition(0, nullptr); + auto next_id = node->get_default()->get_id(); + for (auto &condition : node->get_conditions()) { + auto condition_func = condition.second; + if (condition_func(0, nullptr)) { + next_id = condition.first; + break; + } + } current = node->next(next_id); } break; case activity::node_t::XOR_EVENT_GATE: { auto node = std::static_pointer_cast(current); - auto event_primer = node->get_primer_func(); - event_primer(0, nullptr, nullptr, nullptr); + auto event_primers = node->get_primers(); + for (auto &primer : event_primers) { + auto ev = primer.second(0, + nullptr, + nullptr, + nullptr, + primer.first); + events.push_back(ev); + } // wait for event + log::log(INFO << "Waiting for event"); return current; } break; default: @@ -157,7 +185,7 @@ const std::shared_ptr activity_flow(const std::shared_ptrstr()); } - log::log(INFO << "Reached end note: " << current->str()); + log::log(INFO << "Reached end node: " << current->str()); return current; } @@ -203,44 +231,44 @@ void activity_demo() { }); // Conditional branch - size_t counter = 0; - xor_node->add_output(task1); - xor_node->add_output(event_node); - xor_node->set_condition_func([&](const time::time_t & /* time */, - const std::shared_ptr & /* entity */) { + static size_t counter = 0; + activity::condition_t branch_task1 = [&](const time::time_t & /* time */, + const std::shared_ptr & /* entity */) { log::log(INFO << "Checking condition (counter < 4): counter=" << counter); if (counter < 4) { log::log(INFO << "Selecting path 1 (back to task node " << task1->get_id() << ")"); counter++; - return task1->get_id(); + return true; } - + return false; + }; + xor_node->add_output(task1, branch_task1); + activity::condition_t branch_event = [&](const time::time_t & /* time */, + const std::shared_ptr & /* entity */) { + // No check needed here, the event node is always selected log::log(INFO << "Selecting path 2 (to event node " << event_node->get_id() << ")"); - - return event_node->get_id(); - }); + return true; + }; + xor_node->add_output(event_node, branch_event); + xor_node->set_default(event_node); // event node - event_node->add_output(task2); - event_node->set_primer_func([&](const time::time_t & /* time */, - const std::shared_ptr & /* entity */, - const std::shared_ptr & /* loop */, - const std::shared_ptr & /* state */) { + activity::event_primer_t primer = [&](const time::time_t & /* time */, + const std::shared_ptr & /* entity */, + const std::shared_ptr & /* loop */, + const std::shared_ptr & /* state */, + size_t next_id) { log::log(INFO << "Setting up event"); + event::EventHandler::param_map::map_t params{{"next", next_id}}; auto ev = loop->create_event("test.activity", mgr, state, - 0); - return activity::event_store_t{ev}; - }); - event_node->set_next_func([&task2](const time::time_t & /* time */, - const std::shared_ptr & /* entity */, - const std::shared_ptr & /* loop */, - const std::shared_ptr & /* state */) { - log::log(INFO << "Selecting next node (task node " << task2->get_id() << ")"); - return task2->get_id(); - }); + 0, + params); + return ev; + }; + event_node->add_output(task2, primer); // task 2 task2->add_output(end); diff --git a/libopenage/gamestate/activity/xor_event_gate.cpp b/libopenage/gamestate/activity/xor_event_gate.cpp new file mode 100644 index 0000000000..f018cfd96a --- /dev/null +++ b/libopenage/gamestate/activity/xor_event_gate.cpp @@ -0,0 +1,42 @@ +// Copyright 2023-2023 the openage authors. See copying.md for legal info. + +#include "xor_event_gate.h" + +#include + + +namespace openage::gamestate::activity { + +XorEventGate::XorEventGate(node_id_t id, + node_label_t label) : + Node{id, label}, + primers{} { +} + +XorEventGate::XorEventGate(node_id_t id, + node_label_t label, + const std::vector> &outputs, + const std::map &primers) : + Node{id, label, outputs}, + primers{} { + if (primers.size() != outputs.size()) { + throw Error{MSG(err) << "XorEventGate " << this->str() << " has " << outputs.size() + << " outputs but " << primers.size() << " primers"}; + } + + for (const auto &[id, primer] : primers) { + this->primers.emplace(id, primer); + } +} + +void XorEventGate::add_output(const std::shared_ptr &output, + const event_primer_t &primer) { + this->outputs.emplace(output->get_id(), output); + this->primers.emplace(output->get_id(), primer); +} + +const std::map &XorEventGate::get_primers() const { + return this->primers; +} + +} // namespace openage::gamestate::activity diff --git a/libopenage/gamestate/activity/xor_event_gate.h b/libopenage/gamestate/activity/xor_event_gate.h new file mode 100644 index 0000000000..38c8ccacb1 --- /dev/null +++ b/libopenage/gamestate/activity/xor_event_gate.h @@ -0,0 +1,112 @@ +// Copyright 2023-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include +#include + +#include "error/error.h" +#include "log/message.h" + +#include "gamestate/activity/node.h" +#include "gamestate/activity/types.h" +#include "time/time.h" + + +namespace openage { +namespace event { +class Event; +class EventLoop; +} // namespace event + +namespace gamestate { +class GameEntity; +class GameState; + +namespace activity { + + +/** + * Create and register an event on the event loop. + * + * When the event is executed, the control flow continues on the branch + * associated with the event. + * + * @param time Time at which the primer function is executed. + * @param entity Game entity that the activity is assigned to. + * @param loop Event loop that events are registered on. + * @param state Game state. + * @param next_id ID of the next node to visit. This is passed as an event parameter. + * + * @return Event registered on the event loop. + */ +using event_primer_t = std::function(const time::time_t &, + const std::shared_ptr &, + const std::shared_ptr &, + const std::shared_ptr &, + size_t next_id)>; + + +/** + * Waits for an event to be executed before continuing the control flow. + */ +class XorEventGate : public Node { +public: + /** + * Create a new exclusive event gateway. + * + * @param id Unique identifier for this node. + * @param label Human-readable label (optional). + */ + XorEventGate(node_id_t id, + node_label_t label = "EventGateWay"); + + /** + * Create a new exclusive event gateway. + * + * @param id Unique identifier for this node. + * @param label Human-readable label. + * @param outputs Output nodes. + * @param primers Event primers for each output node. + */ + XorEventGate(node_id_t id, + node_label_t label, + const std::vector> &outputs, + const std::map &primers); + + virtual ~XorEventGate() = default; + + inline node_t get_type() const override { + return node_t::XOR_EVENT_GATE; + } + + /** + * Add an output node. + * + * @param output Output node. + * @param primer Creation function for the event associated with the output node. + */ + void add_output(const std::shared_ptr &output, + const event_primer_t &primer); + + /** + * Get the output->event primer mappings. + * + * @return Event primer functions for each output node. + */ + const std::map &get_primers() const; + +private: + /** + * Maps output node IDs to event primer functions. + * + * Events are created and registered on the event loop when the node is visited. + */ + std::map primers; +}; + +} // namespace activity +} // namespace gamestate +} // namespace openage diff --git a/libopenage/gamestate/activity/xor_gate.cpp b/libopenage/gamestate/activity/xor_gate.cpp new file mode 100644 index 0000000000..5d37908f8b --- /dev/null +++ b/libopenage/gamestate/activity/xor_gate.cpp @@ -0,0 +1,58 @@ +// Copyright 2023-2023 the openage authors. See copying.md for legal info. + +#include "xor_gate.h" + +#include + + +namespace openage::gamestate::activity { + +XorGate::XorGate(node_id_t id, + node_label_t label) : + Node{id, label, {}}, + conditions{}, + default_node{nullptr} { +} + +XorGate::XorGate(node_id_t id, + node_label_t label, + const std::vector> &outputs, + const std::vector &conditions, + const std::shared_ptr &default_node) : + Node{id, label, outputs}, + conditions{}, + default_node{default_node} { + if (conditions.size() != outputs.size()) [[unlikely]] { + throw Error{MSG(err) << "XorGate " << this->str() << " has " << outputs.size() + << " outputs but " << conditions.size() << " conditions"}; + } + + for (size_t i = 0; i < conditions.size(); ++i) { + this->conditions.emplace(outputs[i]->get_id(), conditions[i]); + } +} + +void XorGate::add_output(const std::shared_ptr &output, + const condition_t condition_func) { + this->outputs.emplace(output->get_id(), output); + this->conditions.emplace(output->get_id(), condition_func); +} + +const std::map &XorGate::get_conditions() const { + return this->conditions; +} + +const std::shared_ptr &XorGate::get_default() const { + return this->default_node; +} + +void XorGate::set_default(const std::shared_ptr &node) { + if (this->default_node != nullptr) { + throw Error{MSG(err) << "XorGate " << this->str() << " already has a default node"}; + } + + this->outputs.emplace(node->get_id(), node); + this->default_node = node; +} + +} // namespace openage::gamestate::activity diff --git a/libopenage/gamestate/activity/xor_gate.h b/libopenage/gamestate/activity/xor_gate.h new file mode 100644 index 0000000000..f73287ed86 --- /dev/null +++ b/libopenage/gamestate/activity/xor_gate.h @@ -0,0 +1,119 @@ +// Copyright 2023-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include +#include +#include + +#include "error/error.h" +#include "log/message.h" + +#include "gamestate/activity/node.h" +#include "gamestate/activity/types.h" +#include "time/time.h" + + +namespace openage::gamestate { +class GameEntity; + +namespace activity { + +/** + * Function that determines if an output node is chosen. + * + * @param time Current game time. + * @param entity Entity that is executing the activity. + * + * @return true if the output node is chosen, false otherwise. + */ +using condition_t = std::function &)>; + + +/** + * Chooses one of its output nodes based on conditions. + */ +class XorGate : public Node { +public: + /** + * Creates a new condition node. + * + * @param id Unique identifier of the node. + * @param label Label of the node (optional). + */ + XorGate(node_id_t id, + node_label_t label = "ExclusiveGateway"); + + /** + * Creates a new condition node. + * + * @param id Unique identifier of the node. + * @param label Label of the node. + * @param outputs Output nodes. + * @param conditions Conditions for each output node. + * @param default_node Default output node. Chosen if no condition is true. + */ + XorGate(node_id_t id, + node_label_t label, + const std::vector> &outputs, + const std::vector &conditions, + const std::shared_ptr &default_node); + + virtual ~XorGate() = default; + + inline node_t get_type() const override { + return node_t::XOR_GATE; + } + + /** + * Add an output node. + * + * @param output Output node. + * @param condition_func Function that determines whether this output node is chosen. + * This must be a valid node ID of one of the output nodes. + */ + void add_output(const std::shared_ptr &output, + const condition_t condition_func); + + /** + * Get the output->condition mappings. + * + * @return Conditions for each output node. + */ + const std::map &get_conditions() const; + + /** + * Get the default output node. + * + * @return Default output node. + */ + const std::shared_ptr &get_default() const; + + /** + * Set the the default output node. + * + * This node is chosen if no condition is true. + * + * @param node Default output node. + */ + void set_default(const std::shared_ptr &node); + +private: + /** + * Maps output node IDs to condition functions. + * + * Conditions are checked in order they appear in the map. + */ + std::map conditions; + + /** + * Default output node. Chosen if no condition is true. + */ + std::shared_ptr default_node; +}; + +} // namespace activity +} // namespace openage::gamestate diff --git a/libopenage/gamestate/activity/xor_node.cpp b/libopenage/gamestate/activity/xor_node.cpp deleted file mode 100644 index 644362f877..0000000000 --- a/libopenage/gamestate/activity/xor_node.cpp +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. - -#include "xor_node.h" - -#include - - -namespace openage::gamestate::activity { - -XorGate::XorGate(node_id id, - node_label label, - const std::vector> &outputs, - condition_func_t condition_func) : - Node{id, label, outputs}, - condition_func{condition_func} { -} - -void XorGate::add_output(const std::shared_ptr &output) { - this->outputs.emplace(output->get_id(), output); -} - -void XorGate::set_condition_func(condition_func_t condition_func) { - this->condition_func = condition_func; -} - -condition_func_t XorGate::get_condition_func() const { - return this->condition_func; -} - -} // namespace openage::gamestate::activity diff --git a/libopenage/gamestate/activity/xor_node.h b/libopenage/gamestate/activity/xor_node.h deleted file mode 100644 index 39efca6209..0000000000 --- a/libopenage/gamestate/activity/xor_node.h +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include -#include - -#include "error/error.h" -#include "log/message.h" - -#include "gamestate/activity/node.h" -#include "gamestate/activity/types.h" -#include "time/time.h" - - -namespace openage::gamestate { -class GameEntity; - -namespace activity { - -using condition_func_t = std::function &)>; - -static const condition_func_t no_condition = [](const time::time_t &, - const std::shared_ptr &) -> node_id { - throw Error{MSG(err) << "No condition function set."}; -}; - - -/** - * Chooses one of its output nodes based on a condition. - */ -class XorGate : public Node { -public: - /** - * Creates a new condition node. - * - * @param id Unique identifier of the node. - * @param label Label of the node (optional). - * @param outputs Output nodes (can be set later). - * @param condition_func Function that determines which output node is chosen (can be set later). - * This must be a valid node ID of one of the output nodes. - */ - XorGate(node_id id, - node_label label = "ExclusiveGateway", - const std::vector> &outputs = {}, - condition_func_t condition_func = no_condition); - virtual ~XorGate() = default; - - inline node_t get_type() const override { - return node_t::XOR_GATE; - } - - /** - * Add an output node. - * - * @param output Output node. - */ - void add_output(const std::shared_ptr &output) override; - - /** - * Set the function that determines which output node is chosen. - * - * @param condition_func Function that determines which output node is chosen. - * This must be a valid node ID of one of the output nodes. - */ - void set_condition_func(condition_func_t condition_func); - - /** - * Get the function that determines which output node is chosen. - * - * @return Function that determines which output node is chosen. - */ - condition_func_t get_condition_func() const; - -private: - /** - * Determines which output node is chosen. - */ - condition_func_t condition_func; -}; - -} // namespace activity -} // namespace openage::gamestate diff --git a/libopenage/gamestate/api/CMakeLists.txt b/libopenage/gamestate/api/CMakeLists.txt index e235b05137..f3ad8d7b40 100644 --- a/libopenage/gamestate/api/CMakeLists.txt +++ b/libopenage/gamestate/api/CMakeLists.txt @@ -1,11 +1,13 @@ add_sources(libopenage ability.cpp + activity.cpp animation.cpp definitions.cpp patch.cpp player_setup.cpp property.cpp sound.cpp + terrain.cpp types.cpp util.cpp ) diff --git a/libopenage/gamestate/api/ability.h b/libopenage/gamestate/api/ability.h index 34565d9f0e..d0ba6ea54e 100644 --- a/libopenage/gamestate/api/ability.h +++ b/libopenage/gamestate/api/ability.h @@ -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. #pragma once @@ -14,33 +14,33 @@ namespace openage::gamestate::api { class APIAbility { public: /** - * Check if a nyan object is an Ability (type == \p engine.ability.Ability). - * - * @param obj nyan object. - * - * @return true if the object is an ability, else false. - */ + * Check if a nyan object is an Ability (type == \p engine.ability.Ability). + * + * @param obj nyan object. + * + * @return true if the object is an ability, else false. + */ static bool is_ability(const nyan::Object &obj); /** - * Check if an ability has a given property. - * - * @param ability \p Ability nyan object (type == \p engine.ability.Ability). - * @param property Property type. - * - * @return true if the ability has the property, else false. - */ + * Check if an ability has a given property. + * + * @param ability \p Ability nyan object (type == \p engine.ability.Ability). + * @param property Property type. + * + * @return true if the ability has the property, else false. + */ static bool check_property(const nyan::Object &ability, const ability_property_t &property); /** - * Get the nyan object for a property from an ability. - * - * @param ability \p Ability nyan object (type == \p engine.ability.Ability). - * @param property Property type. - * - * @return \p Property nyan object (type == \p engine.ability.property.Property). - */ + * Get the nyan object for a property from an ability. + * + * @param ability \p Ability nyan object (type == \p engine.ability.Ability). + * @param property Property type. + * + * @return \p Property nyan object (type == \p engine.ability.property.Property). + */ static const nyan::Object get_property(const nyan::Object &ability, const ability_property_t &property); }; diff --git a/libopenage/gamestate/api/activity.cpp b/libopenage/gamestate/api/activity.cpp new file mode 100644 index 0000000000..edd596abe8 --- /dev/null +++ b/libopenage/gamestate/api/activity.cpp @@ -0,0 +1,115 @@ +// Copyright 2023-2023 the openage authors. See copying.md for legal info. + +#include "activity.h" + +#include "gamestate/api/definitions.h" + + +namespace openage::gamestate::api { + +bool APIActivity::is_activity(const nyan::Object &obj) { + nyan::fqon_t immediate_parent = obj.get_parents()[0]; + return immediate_parent == "engine.util.activity.Activity"; +} + +nyan::Object APIActivity::get_start(const nyan::Object &activity) { + auto obj_value = activity.get("Activity.start"); + + std::shared_ptr db_view = activity.get_view(); + return db_view->get_object(obj_value->get_name()); +} + + +bool APIActivityNode::is_node(const nyan::Object &obj) { + nyan::fqon_t immediate_parent = obj.get_parents()[0]; + return immediate_parent == "engine.util.activity.node.Node"; +} + +activity::node_t APIActivityNode::get_type(const nyan::Object &node) { + nyan::fqon_t immediate_parent = node.get_parents()[0]; + return ACTIVITY_NODE_DEFS.get(immediate_parent); +} + +std::vector APIActivityNode::get_next(const nyan::Object &node) { + switch (APIActivityNode::get_type(node)) { + // 0 next nodes + case activity::node_t::END: { + return {}; + } + // 1 next node + case activity::node_t::TASK_SYSTEM: { + auto next = node.get("Ability.next"); + std::shared_ptr db_view = node.get_view(); + return {db_view->get_object(next->get_name())}; + } + case activity::node_t::START: { + auto next = node.get("Start.next"); + std::shared_ptr db_view = node.get_view(); + return {db_view->get_object(next->get_name())}; + } + // 1+ next nodes + case activity::node_t::XOR_GATE: { + auto conditions = node.get("XORGate.next"); + std::shared_ptr db_view = node.get_view(); + + std::vector next_nodes; + for (auto &condition : conditions->get()) { + auto condition_value = std::dynamic_pointer_cast(condition.get_ptr()); + auto condition_obj = db_view->get_object(condition_value->get_name()); + + auto next_node_value = condition_obj.get("Condition.next"); + next_nodes.push_back(db_view->get_object(next_node_value->get_name())); + } + + auto default_next = node.get("XORGate.default"); + next_nodes.push_back(db_view->get_object(default_next->get_name())); + + return next_nodes; + } + case activity::node_t::XOR_EVENT_GATE: { + auto next = node.get("XOREventGate.next"); + std::shared_ptr db_view = node.get_view(); + + std::vector next_nodes; + for (auto &next_node : next->get()) { + auto next_node_value = std::dynamic_pointer_cast(next_node.second.get_ptr()); + next_nodes.push_back(db_view->get_object(next_node_value->get_name())); + } + + return next_nodes; + } + default: + throw Error(MSG(err) << "Unknown activity node type."); + } +} + +system::system_id_t APIActivityNode::get_system_id(const nyan::Object &ability_node) { + auto ability = ability_node.get("Ability.ability"); + + if (not ACTIVITY_TASK_SYSTEM_DEFS.contains(ability->get_name())) [[unlikely]] { + throw Error(MSG(err) << "Ability '" << ability->get_name() << "' has no associated system defined."); + } + + return ACTIVITY_TASK_SYSTEM_DEFS.get(ability->get_name()); +} + +bool APIActivityCondition::is_condition(const nyan::Object &obj) { + nyan::fqon_t immediate_parent = obj.get_parents()[0]; + return immediate_parent == "engine.util.activity.condition.Condition"; +} + +activity::condition_t APIActivityCondition::get_condition(const nyan::Object &condition) { + nyan::fqon_t immediate_parent = condition.get_parents()[0]; + return ACTIVITY_CONDITIONS.get(immediate_parent); +} + +bool APIActivityEvent::is_event(const nyan::Object &obj) { + nyan::fqon_t immediate_parent = obj.get_parents()[0]; + return immediate_parent == "engine.util.activity.event.Event"; +} + +activity::event_primer_t APIActivityEvent::get_primer(const nyan::Object &event) { + return ACTIVITY_EVENT_PRIMERS.get(event.get_name()); +} + +} // namespace openage::gamestate::api diff --git a/libopenage/gamestate/api/activity.h b/libopenage/gamestate/api/activity.h new file mode 100644 index 0000000000..2001aa7548 --- /dev/null +++ b/libopenage/gamestate/api/activity.h @@ -0,0 +1,138 @@ +// Copyright 2023-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include + +#include "gamestate/activity/types.h" +#include "gamestate/activity/xor_event_gate.h" +#include "gamestate/activity/xor_gate.h" +#include "gamestate/system/types.h" + + +namespace openage::gamestate { + +namespace api { + +/** + * Helper class for creating Activity objects from the nyan API. + */ +class APIActivity { +public: + /** + * Check if a nyan object is an Activity (type == \p engine.util.activity.Activity). + * + * @param obj nyan object. + * + * @return true if the object is an activity, else false. + */ + static bool is_activity(const nyan::Object &obj); + + /** + * Get the start node of an activity. + * + * @param activity nyan object. + * + * @return nyan object handle of the start node. + */ + static nyan::Object get_start(const nyan::Object &activity); +}; + +/** + * Helper class for creating Activity node objects from the nyan API. + */ +class APIActivityNode { +public: + /** + * Check if a nyan object is a node (type == \p engine.util.activity.node.Node). + * + * @param obj nyan object. + * + * @return true if the object is a node, else false. + */ + static bool is_node(const nyan::Object &obj); + + /** + * Get the type of a node. + * + * @param node nyan object. + * + * @return Type of the node. + */ + static activity::node_t get_type(const nyan::Object &node); + + /** + * Get the next nodes of a node. + * + * The number of next nodes depends on the type of the node and can range + * from 0 (end nodes) to n (gateways). + * + * @param node nyan object. + * + * @return nyan object handles of the next nodes. + */ + static std::vector get_next(const nyan::Object &node); + + /** + * Get the system id of an Ability node. + * + * @param node nyan object. + * + * @return System ID of the node. + */ + static system::system_id_t get_system_id(const nyan::Object &ability_node); +}; + +/** + * Helper class for creating Activity condition objects from the nyan API. + */ +class APIActivityCondition { +public: + /** + * Check if a nyan object is a condition (type == \p engine.util.activity.condition.Condition). + * + * @param obj nyan object. + * + * @return true if the object is a condition, else false. + */ + static bool is_condition(const nyan::Object &obj); + + /** + * Get the condition function for a condition. + * + * @param condition nyan object. + * + * @return Condition function. + */ + static activity::condition_t get_condition(const nyan::Object &condition); +}; + +/** + * Helper class for creating Activity event objects from the nyan API. + */ +class APIActivityEvent { +public: + /** + * Check if a nyan object is an event (type == \p engine.util.activity.event.Event). + * + * @param obj nyan object. + * + * @return true if the object is an event, else false. + */ + static bool is_event(const nyan::Object &obj); + + /** + * Get the primer function for an event type. + * + * @param event nyan object. + * + * @return Event primer function. + */ + static activity::event_primer_t get_primer(const nyan::Object &event); +}; + + +} // namespace api +} // namespace openage::gamestate diff --git a/libopenage/gamestate/api/animation.h b/libopenage/gamestate/api/animation.h index a002749786..c372f2769d 100644 --- a/libopenage/gamestate/api/animation.h +++ b/libopenage/gamestate/api/animation.h @@ -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. #pragma once @@ -16,34 +16,34 @@ namespace openage::gamestate::api { class APIAnimation { public: /** - * Check if a nyan object is an animation (type == \p engine.util.animation.Animation). - * - * @param obj nyan object handle. - * - * @return true if the object is an animation, else false. - */ + * Check if a nyan object is an animation (type == \p engine.util.animation.Animation). + * + * @param obj nyan object handle. + * + * @return true if the object is an animation, else false. + */ static bool is_animation(nyan::Object &obj); /** - * Get the sprite path of an animation. - * - * The path is relative to the directory the modpack is mounted in. - * - * @param animation \p Animation nyan object (type == \p engine.util.animation.Animation). - * - * @return Relative path to the animation sprite file. - */ + * Get the sprite path of an animation. + * + * The path is relative to the directory the modpack is mounted in. + * + * @param animation \p Animation nyan object (type == \p engine.util.animation.Animation). + * + * @return Relative path to the animation sprite file. + */ static const std::string get_animation_path(const nyan::Object &animation); /** - * Get the sprite paths for a collection of animations. - * - * Paths are relative to the directory the modpack is mounted in. - * - * @param animations \p Animation nyan objects (type == \p engine.util.animation.Animation). - * - * @return Relative paths to the animation sprite files. - */ + * Get the sprite paths for a collection of animations. + * + * Paths are relative to the directory the modpack is mounted in. + * + * @param animations \p Animation nyan objects (type == \p engine.util.animation.Animation). + * + * @return Relative paths to the animation sprite files. + */ static const std::vector get_animation_paths(const std::vector &animations); }; diff --git a/libopenage/gamestate/api/definitions.h b/libopenage/gamestate/api/definitions.h index d06553c89e..f982571f94 100644 --- a/libopenage/gamestate/api/definitions.h +++ b/libopenage/gamestate/api/definitions.h @@ -8,12 +8,22 @@ #include #include "datastructure/constexpr_map.h" +#include "gamestate/activity/condition/command_in_queue.h" +#include "gamestate/activity/condition/next_command.h" +#include "gamestate/activity/event/command_in_queue.h" +#include "gamestate/activity/event/wait.h" +#include "gamestate/activity/types.h" +#include "gamestate/activity/xor_event_gate.h" +#include "gamestate/activity/xor_gate.h" #include "gamestate/api/types.h" +#include "gamestate/system/types.h" namespace openage::gamestate::api { -/** Maps internal ability types to nyan API values **/ +/** + * Maps internal ability types to nyan API values. + */ static const auto ABILITY_DEFS = datastructure::create_const_map( std::pair(ability_t::IDLE, nyan::ValueHolder(std::make_shared("engine.ability.type.Idle"))), @@ -24,8 +34,9 @@ static const auto ABILITY_DEFS = datastructure::create_const_map("engine.ability.type.Turn")))); - -/** Maps internal property types to nyan API values **/ +/** + * Maps internal property types to nyan API values. + */ static const auto ABILITY_PROPERTY_DEFS = datastructure::create_const_map( std::pair(ability_property_t::ANIMATED, nyan::ValueHolder(std::make_shared("engine.ability.property.type.Animated"))), @@ -40,6 +51,54 @@ static const auto ABILITY_PROPERTY_DEFS = datastructure::create_const_map("engine.ability.property.type.Lock")))); +/** + * Maps API activity node types to engine node types. + */ +static const auto ACTIVITY_NODE_DEFS = datastructure::create_const_map( + std::pair("engine.util.activity.node.type.Start", + activity::node_t::START), + std::pair("engine.util.activity.node.type.End", + activity::node_t::END), + std::pair("engine.util.activity.node.type.Ability", + activity::node_t::TASK_SYSTEM), + std::pair("engine.util.activity.node.type.XORGate", + activity::node_t::XOR_GATE), + std::pair("engine.util.activity.node.type.XOREventGate", + activity::node_t::XOR_EVENT_GATE)); + +/** + * Maps API activity task system types to engine system types. + * + * TODO: Expand this to include all systems. + */ +static const auto ACTIVITY_TASK_SYSTEM_DEFS = datastructure::create_const_map( + std::pair("engine.ability.type.Idle", + system::system_id_t::IDLE), + std::pair("engine.ability.type.Move", + system::system_id_t::MOVE_COMMAND)); + +/** + * Maps API activity condition types to engine condition types. + */ +static const auto ACTIVITY_CONDITIONS = datastructure::create_const_map( + std::pair("engine.util.activity.condition.type.CommandInQueue", + std::function(gamestate::activity::command_in_queue)), + std::pair("engine.util.activity.condition.type.NextCommandIdle", + std::function(gamestate::activity::next_command_idle)), + std::pair("engine.util.activity.condition.type.NextCommandMove", + std::function(gamestate::activity::next_command_move))); + +static const auto ACTIVITY_EVENT_PRIMERS = datastructure::create_const_map( + std::pair("engine.util.activity.event.type.CommandInQueue", + std::function(gamestate::activity::primer_command_in_queue)), + std::pair("engine.util.activity.event.type.Wait", + std::function(gamestate::activity::primer_wait)), + std::pair("engine.util.activity.event.type.WaitAbility", + std::function(gamestate::activity::primer_wait))); + +/** + * Maps internal patch property types to nyan API values. + */ static const auto PATCH_PROPERTY_DEFS = datastructure::create_const_map( std::pair(patch_property_t::DIPLOMATIC, nyan::ValueHolder(std::make_shared("engine.patch.property.type.Diplomatic")))); diff --git a/libopenage/gamestate/api/patch.h b/libopenage/gamestate/api/patch.h index d0547b6f61..576c5791d3 100644 --- a/libopenage/gamestate/api/patch.h +++ b/libopenage/gamestate/api/patch.h @@ -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. #pragma once @@ -15,33 +15,33 @@ namespace openage::gamestate::api { class APIPatch { public: /** - * Check if a nyan object is a patch (type == \p engine.util.patch.Patch). - * - * @param obj nyan object handle. - * - * @return true if the object is a patch, else false. - */ + * Check if a nyan object is a patch (type == \p engine.util.patch.Patch). + * + * @param obj nyan object handle. + * + * @return true if the object is a patch, else false. + */ static bool is_patch(const nyan::Object &obj); /** - * Check if a patch has a given property. - * - * @param patch \p Patch nyan object (type == \p engine.util.patch.Patch). - * @param property Property type. - * - * @return true if the patch has the property, else false. - */ + * Check if a patch has a given property. + * + * @param patch \p Patch nyan object (type == \p engine.util.patch.Patch). + * @param property Property type. + * + * @return true if the patch has the property, else false. + */ static bool check_property(const nyan::Object &patch, const patch_property_t &property); /** - * Get the nyan object for a property from a patch. - * - * @param patch \p Patch nyan object (type == \p engine.util.patch.Patch). - * @param property Property type. - * - * @return \p Property nyan object (type == \p engine.util.patch.property.PatchProperty). - */ + * Get the nyan object for a property from a patch. + * + * @param patch \p Patch nyan object (type == \p engine.util.patch.Patch). + * @param property Property type. + * + * @return \p Property nyan object (type == \p engine.util.patch.property.PatchProperty). + */ static const nyan::Object get_property(const nyan::Object &patch, const patch_property_t &property); }; diff --git a/libopenage/gamestate/api/player_setup.h b/libopenage/gamestate/api/player_setup.h index f3e23fbc81..fef91b0f54 100644 --- a/libopenage/gamestate/api/player_setup.h +++ b/libopenage/gamestate/api/player_setup.h @@ -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. #pragma once @@ -15,39 +15,39 @@ namespace openage::gamestate::api { class APIPlayerSetup { public: /** - * Check if a nyan object is a player setup (type == \p engine.util.setup.PlayerSetup). - * - * @param obj nyan object handle. - * - * @return true if the object is a player setup, else false. - */ + * Check if a nyan object is a player setup (type == \p engine.util.setup.PlayerSetup). + * + * @param obj nyan object handle. + * + * @return true if the object is a player setup, else false. + */ static bool is_player_setup(const nyan::Object &obj); /** - * Get the modifiers of a player setup. - * - * @param player_setup nyan object (type == \p engine.util.setup.PlayerSetup). - * - * @return \p Modifier nyan objects (type == \p engine.modifier.Modifier). - */ + * Get the modifiers of a player setup. + * + * @param player_setup nyan object (type == \p engine.util.setup.PlayerSetup). + * + * @return \p Modifier nyan objects (type == \p engine.modifier.Modifier). + */ static const std::vector get_modifiers(const nyan::Object &player_setup); /** - * Get the starting resources of a player setup. - * - * @param player_setup nyan object (type == \p engine.util.setup.PlayerSetup). - * - * @return \p ResourceAmount nyan objects (type == \p engine.util.resource.ResourceAmount). - */ + * Get the starting resources of a player setup. + * + * @param player_setup nyan object (type == \p engine.util.setup.PlayerSetup). + * + * @return \p ResourceAmount nyan objects (type == \p engine.util.resource.ResourceAmount). + */ static const std::vector get_start_resources(const nyan::Object &player_setup); /** - * Get the initial patches of a player setup. - * - * @param player_setup nyan object (type == \p engine.util.setup.PlayerSetup). - * - * @return \p Patch nyan objects (type == \p engine.util.patch.Patch). - */ + * Get the initial patches of a player setup. + * + * @param player_setup nyan object (type == \p engine.util.setup.PlayerSetup). + * + * @return \p Patch nyan objects (type == \p engine.util.patch.Patch). + */ static const std::vector get_patches(const nyan::Object &player_setup); }; diff --git a/libopenage/gamestate/api/property.h b/libopenage/gamestate/api/property.h index 702abd2088..0d42c16596 100644 --- a/libopenage/gamestate/api/property.h +++ b/libopenage/gamestate/api/property.h @@ -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. #pragma once @@ -15,48 +15,48 @@ namespace openage::gamestate::api { class APIAbilityProperty { public: /** - * Check if a nyan object is a property (type == \p engine.ability.property.Property). - * - * @param obj nyan object handle. - * - * @return true if the object is a property, else false. - */ + * Check if a nyan object is a property (type == \p engine.ability.property.Property). + * + * @param obj nyan object handle. + * + * @return true if the object is a property, else false. + */ static bool is_property(const nyan::Object &obj); /** - * Get the animations of an \p Animated property (type == \p engine.ability.property.type.Animated). - * - * @param property \p Property nyan object (type == \p engine.ability.property.Property). - * - * @return \p Animation nyan objects (type == \p engine.util.animation.Animation). - */ + * Get the animations of an \p Animated property (type == \p engine.ability.property.type.Animated). + * + * @param property \p Property nyan object (type == \p engine.ability.property.Property). + * + * @return \p Animation nyan objects (type == \p engine.util.animation.Animation). + */ static const std::vector get_animations(const nyan::Object &property); /** - * Get the sounds of a \p CommandSound property (type == \p engine.ability.property.type.CommandSound). - * - * @param property \p Property nyan object (type == \p engine.ability.property.Property). - * - * @return \p Sound nyan objects (type == \p engine.util.sound.Sound). - */ + * Get the sounds of a \p CommandSound property (type == \p engine.ability.property.type.CommandSound). + * + * @param property \p Property nyan object (type == \p engine.ability.property.Property). + * + * @return \p Sound nyan objects (type == \p engine.util.sound.Sound). + */ static const std::vector get_command_sounds(const nyan::Object &property); /** - * Get the sounds of an \p ExecutionSound property (type == \p engine.ability.property.type.ExecutionSound). - * - * @param property \p Property nyan object (type == \p engine.ability.property.Property). - * - * @return \p Sound nyan objects (type == \p engine.util.sound.Sound). - */ + * Get the sounds of an \p ExecutionSound property (type == \p engine.ability.property.type.ExecutionSound). + * + * @param property \p Property nyan object (type == \p engine.ability.property.Property). + * + * @return \p Sound nyan objects (type == \p engine.util.sound.Sound). + */ static const std::vector get_execution_sounds(const nyan::Object &property); /** - * Get the sounds of a \p Diplomatic property (type == \p engine.ability.property.type.Diplomatic). - * - * @param property \p Property nyan object (type == \p engine.ability.property.Property). - * - * @return \p DiplomaticStance nyan objects (type == \p engine.util.diplomatic_stance.DiplomaticStance). - */ + * Get the sounds of a \p Diplomatic property (type == \p engine.ability.property.type.Diplomatic). + * + * @param property \p Property nyan object (type == \p engine.ability.property.Property). + * + * @return \p DiplomaticStance nyan objects (type == \p engine.util.diplomatic_stance.DiplomaticStance). + */ static const std::vector get_diplo_stances(const nyan::Object &property); }; diff --git a/libopenage/gamestate/api/sound.h b/libopenage/gamestate/api/sound.h index c45fc7c397..44d2634f4d 100644 --- a/libopenage/gamestate/api/sound.h +++ b/libopenage/gamestate/api/sound.h @@ -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. #pragma once @@ -16,34 +16,34 @@ namespace openage::gamestate::api { class APISound { public: /** - * Check if a nyan object is a sound (type == \p engine.util.sound.Sound). - * - * @param obj nyan object handle. - * - * @return true if the object is a sound, else false. - */ + * Check if a nyan object is a sound (type == \p engine.util.sound.Sound). + * + * @param obj nyan object handle. + * + * @return true if the object is a sound, else false. + */ static bool is_sound(const nyan::Object &obj); /** - * Get the sound path of a sound. - * - * The path is relative to the directory the modpack is mounted in. - * - * @param sound \p Sound nyan object (type == \p engine.util.sound.Sound). - * - * @return Relative path to the sound file. - */ + * Get the sound path of a sound. + * + * The path is relative to the directory the modpack is mounted in. + * + * @param sound \p Sound nyan object (type == \p engine.util.sound.Sound). + * + * @return Relative path to the sound file. + */ static const std::string get_sound_path(const nyan::Object &sound); /** - * Get the sound paths for a collection of sounds. - * - * Paths are relative to the directory the modpack is mounted in. - * - * @param sounds \p Sound nyan objects (type == \p engine.util.sound.Sound). - * - * @return Relative paths to the sound files. - */ + * Get the sound paths for a collection of sounds. + * + * Paths are relative to the directory the modpack is mounted in. + * + * @param sounds \p Sound nyan objects (type == \p engine.util.sound.Sound). + * + * @return Relative paths to the sound files. + */ static const std::vector get_sound_paths(const std::vector &sounds); }; } // namespace openage::gamestate::api diff --git a/libopenage/gamestate/api/terrain.cpp b/libopenage/gamestate/api/terrain.cpp new file mode 100644 index 0000000000..ad5e300b47 --- /dev/null +++ b/libopenage/gamestate/api/terrain.cpp @@ -0,0 +1,38 @@ +// Copyright 2023-2024 the openage authors. See copying.md for legal info. + +#include "terrain.h" + +#include + +#include "gamestate/api/util.h" + + +namespace openage::gamestate::api { + +bool APITerrain::is_terrain(const nyan::Object &obj) { + nyan::fqon_t immediate_parent = obj.get_parents()[0]; + return immediate_parent == "engine.util.terrain.Terrain"; +} + +const std::string APITerrain::get_terrain_path(const nyan::Object &terrain) { + nyan::Object terrain_texture_obj = terrain.get_object("Terrain.terrain_graphic"); + std::string terrain_path = terrain_texture_obj.get_file("Terrain.sprite"); + + return resolve_file_path(terrain, terrain_path); +} + +const std::unordered_map APITerrain::get_path_costs(const nyan::Object &terrain) { + std::unordered_map result; + + nyan::dict_t path_costs = terrain.get_dict("Terrain.path_costs"); + for (const auto &pair : path_costs) { + auto key = std::dynamic_pointer_cast(pair.first.get_ptr()); + auto value = std::dynamic_pointer_cast(pair.second.get_ptr()); + + result.emplace(key->get_name(), value->get()); + } + + return result; +} + +} // namespace openage::gamestate::api diff --git a/libopenage/gamestate/api/terrain.h b/libopenage/gamestate/api/terrain.h new file mode 100644 index 0000000000..d049bce279 --- /dev/null +++ b/libopenage/gamestate/api/terrain.h @@ -0,0 +1,44 @@ +// Copyright 2023-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include + + +namespace openage::gamestate::api { + +class APITerrain { +public: + /** + * Check if a nyan object is a terrain (type == \p engine.util.terrain.Terrain). + * + * @param obj nyan object handle. + * + * @return true if the object is a terrain, else false. + */ + static bool is_terrain(const nyan::Object &obj); + + /** + * Get the terrain path of a terrain. + * + * The path is relative to the directory the modpack is mounted in. + * + * @param terrain \p Terrain nyan object (type == \p engine.util.terrain.Terrain). + * + * @return Relative path to the terrain file. + */ + static const std::string get_terrain_path(const nyan::Object &terrain); + + /** + * Get the path costs of a terrain. + * + * @param terrain \p Terrain nyan object (type == \p engine.util.terrain.Terrain). + * + * @return Path costs for the cost fields of the pathfinder. + */ + static const std::unordered_map get_path_costs(const nyan::Object &terrain); +}; + +} // namespace openage::gamestate::api diff --git a/libopenage/gamestate/component/api/CMakeLists.txt b/libopenage/gamestate/component/api/CMakeLists.txt index 16947be1ba..8588909bf2 100644 --- a/libopenage/gamestate/component/api/CMakeLists.txt +++ b/libopenage/gamestate/component/api/CMakeLists.txt @@ -2,5 +2,6 @@ add_sources(libopenage idle.cpp live.cpp move.cpp + selectable.cpp turn.cpp ) diff --git a/libopenage/gamestate/component/api/idle.h b/libopenage/gamestate/component/api/idle.h index f9d42e5330..e8b114ff67 100644 --- a/libopenage/gamestate/component/api/idle.h +++ b/libopenage/gamestate/component/api/idle.h @@ -1,4 +1,4 @@ -// Copyright 2021-2023 the openage authors. See copying.md for legal info. +// Copyright 2021-2024 the openage authors. See copying.md for legal info. #pragma once @@ -7,7 +7,7 @@ namespace openage::gamestate::component { -class Idle : public APIComponent { +class Idle final : public APIComponent { public: using APIComponent::APIComponent; 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 dd3d8cb0a0..4916713cdc 100644 --- a/libopenage/gamestate/component/api/live.h +++ b/libopenage/gamestate/component/api/live.h @@ -1,4 +1,4 @@ -// Copyright 2021-2023 the openage authors. See copying.md for legal info. +// Copyright 2021-2025 the openage authors. See copying.md for legal info. #pragma once @@ -7,37 +7,37 @@ #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" namespace openage::gamestate::component { -class Live : public APIComponent { +class Live final : public APIComponent { public: using APIComponent::APIComponent; component_t get_type() const override; /** - * Add a new attribute to the component attributes. - * - * @param time The time at which the attribute is added. - * @param attribute Attribute identifier (fqon of the nyan object). - * @param starting_values Attribute values at the time of addition. - */ + * Add a new attribute to the component attributes. + * + * @param time The time at which the attribute is added. + * @param attribute Attribute identifier (fqon of the nyan object). + * @param starting_values Attribute values at the time of addition. + */ void add_attribute(const time::time_t &time, const nyan::fqon_t &attribute, std::shared_ptr> starting_values); /** - * Set the value of an attribute at a given time. - * - * @param time The time at which the attribute is set. - * @param attribute Attribute identifier (fqon of the nyan object). - * @param value New attribute value. - */ + * Set the value of an attribute at a given time. + * + * @param time The time at which the attribute is set. + * @param attribute Attribute identifier (fqon of the nyan object). + * @param value New attribute value. + */ void set_attribute(const time::time_t &time, const nyan::fqon_t &attribute, int64_t value); diff --git a/libopenage/gamestate/component/api/move.h b/libopenage/gamestate/component/api/move.h index 1cc6d65cf7..99d87b8d72 100644 --- a/libopenage/gamestate/component/api/move.h +++ b/libopenage/gamestate/component/api/move.h @@ -1,4 +1,4 @@ -// Copyright 2021-2023 the openage authors. See copying.md for legal info. +// Copyright 2021-2024 the openage authors. See copying.md for legal info. #pragma once @@ -8,7 +8,7 @@ namespace openage::gamestate::component { -class Move : public APIComponent { +class Move final : public APIComponent { public: using APIComponent::APIComponent; diff --git a/libopenage/gamestate/component/api/selectable.cpp b/libopenage/gamestate/component/api/selectable.cpp new file mode 100644 index 0000000000..c397a146de --- /dev/null +++ b/libopenage/gamestate/component/api/selectable.cpp @@ -0,0 +1,12 @@ +// Copyright 2023-2023 the openage authors. See copying.md for legal info. + +#include "selectable.h" + + +namespace openage::gamestate::component { + +component_t Selectable::get_type() const { + return component_t::SELECTABLE; +} + +} // namespace openage::gamestate::component diff --git a/libopenage/gamestate/component/api/selectable.h b/libopenage/gamestate/component/api/selectable.h new file mode 100644 index 0000000000..b37f674444 --- /dev/null +++ b/libopenage/gamestate/component/api/selectable.h @@ -0,0 +1,20 @@ +// Copyright 2023-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include "gamestate/component/api_component.h" +#include "gamestate/component/types.h" + + +namespace openage::gamestate::component { + +class Selectable final : public APIComponent { +public: + using APIComponent::APIComponent; + + component_t get_type() const override; +}; + +} // namespace openage::gamestate::component diff --git a/libopenage/gamestate/component/api/turn.h b/libopenage/gamestate/component/api/turn.h index 881bb05de9..9506bcd6db 100644 --- a/libopenage/gamestate/component/api/turn.h +++ b/libopenage/gamestate/component/api/turn.h @@ -1,4 +1,4 @@ -// Copyright 2021-2023 the openage authors. See copying.md for legal info. +// Copyright 2021-2024 the openage authors. See copying.md for legal info. #pragma once @@ -10,7 +10,7 @@ namespace openage::gamestate::component { -class Turn : public APIComponent { +class Turn final : public APIComponent { public: using APIComponent::APIComponent; diff --git a/libopenage/gamestate/component/api_component.h b/libopenage/gamestate/component/api_component.h index e994103c5e..6a18018500 100644 --- a/libopenage/gamestate/component/api_component.h +++ b/libopenage/gamestate/component/api_component.h @@ -1,4 +1,4 @@ -// Copyright 2021-2023 the openage authors. See copying.md for legal info. +// Copyright 2021-2024 the openage authors. See copying.md for legal info. #pragma once @@ -58,13 +58,13 @@ class APIComponent : public Component { private: /** - * nyan object holding the data for the component. - */ + * nyan object holding the data for the component. + */ nyan::Object ability; /** - * Determines if the component is available to its game entity. - */ + * Determines if the component is available to its game entity. + */ curve::Discrete enabled; }; diff --git a/libopenage/gamestate/component/base_component.h b/libopenage/gamestate/component/base_component.h index d444a57c5f..53a7e5aab9 100644 --- a/libopenage/gamestate/component/base_component.h +++ b/libopenage/gamestate/component/base_component.h @@ -1,4 +1,4 @@ -// Copyright 2021-2023 the openage authors. See copying.md for legal info. +// Copyright 2021-2024 the openage authors. See copying.md for legal info. #pragma once @@ -14,10 +14,10 @@ class Component { virtual ~Component() = default; /** - * Get the component type of the component. - * - * @return Component type of the component. - */ + * Get the component type of the component. + * + * @return Component type of the component. + */ virtual component_t get_type() const = 0; }; diff --git a/libopenage/gamestate/component/internal/activity.h b/libopenage/gamestate/component/internal/activity.h index a52a97bd57..ae0fd73389 100644 --- a/libopenage/gamestate/component/internal/activity.h +++ b/libopenage/gamestate/component/internal/activity.h @@ -1,4 +1,4 @@ -// Copyright 2021-2023 the openage authors. See copying.md for legal info. +// Copyright 2021-2024 the openage authors. See copying.md for legal info. #pragma once @@ -27,83 +27,83 @@ class Node; namespace component { -class Activity : public InternalComponent { +class Activity final : public InternalComponent { public: /** - * Creates a new activity component. - * - * @param loop Event loop that all events from the component are registered on. - * @param start_activity Initial activity flow graph. - */ + * Creates a new activity component. + * + * @param loop Event loop that all events from the component are registered on. + * @param start_activity Initial activity flow graph. + */ Activity(const std::shared_ptr &loop, const std::shared_ptr &start_activity); component_t get_type() const override; /** - * Get the initial activity. - * - * @return Initial activity. - */ + * Get the initial activity. + * + * @return Initial activity. + */ const std::shared_ptr &get_start_activity() const; /** - * Get the node in the activity flow graph at a given time. - * - * @param time Time at which the node is requested. - * @return Current node in the flow graph. - */ + * Get the node in the activity flow graph at a given time. + * + * @param time Time at which the node is requested. + * @return Current node in the flow graph. + */ const std::shared_ptr get_node(const time::time_t &time) const; /** - * Sets the current node in the activity flow graph at a given time. - * - * @param time Time at which the node is set. - * @param node Current node in the flow graph. - */ + * Sets the current node in the activity flow graph at a given time. + * + * @param time Time at which the node is set. + * @param node Current node in the flow graph. + */ void set_node(const time::time_t &time, const std::shared_ptr &node); /** - * Set the current node to the start node of the start activity. - * - * @param time Time at which the node is set. - */ + * Set the current node to the start node of the start activity. + * + * @param time Time at which the node is set. + */ void init(const time::time_t &time); /** - * Add a scheduled event that is waited for to progress in the node graph. - * - * @param event Event to add. - */ + * Add a scheduled event that is waited for to progress in the node graph. + * + * @param event Event to add. + */ void add_event(const std::shared_ptr &event); /** - * Cancel all scheduled events. - * - * @param time Time at which the events are cancelled. - */ + * Cancel all scheduled events. + * + * @param time Time at which the events are cancelled. + */ void cancel_events(const time::time_t &time); private: /** - * Initial activity that encapsulates the entity's control flow graph. - * - * When a game entity is spawned, the activity system should advance in - * this activity flow graph to initialize the entity's action state. - * - * TODO: Define as curve, so it's changeable? - */ + * Initial activity that encapsulates the entity's control flow graph. + * + * When a game entity is spawned, the activity system should advance in + * this activity flow graph to initialize the entity's action state. + * + * TODO: Define as curve, so it's changeable? + */ std::shared_ptr start_activity; /** - * Current node in the activity flow graph. - */ + * Current node in the activity flow graph. + */ curve::Discrete> node; /** - * Scheduled events that are waited for to progress in the node graph. - */ + * Scheduled events that are waited for to progress in the node graph. + */ std::vector> scheduled_events; }; diff --git a/libopenage/gamestate/component/internal/command_queue.cpp b/libopenage/gamestate/component/internal/command_queue.cpp index 6896f63cdf..4c6963a75d 100644 --- a/libopenage/gamestate/component/internal/command_queue.cpp +++ b/libopenage/gamestate/component/internal/command_queue.cpp @@ -22,11 +22,11 @@ void CommandQueue::add_command(const time::time_t &time, this->command_queue.insert(time, command); } -const curve::Queue> &CommandQueue::get_queue() const { +curve::Queue> &CommandQueue::get_queue() { return this->command_queue; } -std::shared_ptr CommandQueue::pop_command(const time::time_t &time) { +const std::shared_ptr CommandQueue::pop_command(const time::time_t &time) { return this->command_queue.pop_front(time); } diff --git a/libopenage/gamestate/component/internal/command_queue.h b/libopenage/gamestate/component/internal/command_queue.h index 6c16d5cbc7..fb3179b470 100644 --- a/libopenage/gamestate/component/internal/command_queue.h +++ b/libopenage/gamestate/component/internal/command_queue.h @@ -1,10 +1,10 @@ -// Copyright 2021-2023 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" @@ -19,7 +19,7 @@ class EventLoop; namespace gamestate::component { -class CommandQueue : public InternalComponent { +class CommandQueue final : public InternalComponent { public: /** * Creates an Ownership component. @@ -44,7 +44,7 @@ class CommandQueue : public InternalComponent { * * @return Command queue. */ - const curve::Queue> &get_queue() const; + curve::Queue> &get_queue(); /** * Get the command in the front of the queue. @@ -53,7 +53,7 @@ class CommandQueue : public InternalComponent { * * @return Command in the front of the queue or nullptr if the queue is empty. */ - std::shared_ptr pop_command(const time::time_t &time); + const std::shared_ptr pop_command(const time::time_t &time); private: /** diff --git a/libopenage/gamestate/component/internal/commands/base_command.h b/libopenage/gamestate/component/internal/commands/base_command.h index 61b76c4bc5..2cdfd51169 100644 --- a/libopenage/gamestate/component/internal/commands/base_command.h +++ b/libopenage/gamestate/component/internal/commands/base_command.h @@ -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. #pragma once @@ -15,10 +15,10 @@ class Command { virtual ~Command() = default; /** - * Get the type of the command. - * - * @return Command type. - */ + * Get the type of the command. + * + * @return Command type. + */ virtual command_t get_type() const = 0; }; diff --git a/libopenage/gamestate/component/internal/commands/custom.h b/libopenage/gamestate/component/internal/commands/custom.h index 2cc8a6d910..14b67f80a5 100644 --- a/libopenage/gamestate/component/internal/commands/custom.h +++ b/libopenage/gamestate/component/internal/commands/custom.h @@ -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. #pragma once @@ -16,10 +16,10 @@ namespace openage::gamestate::component::command { class CustomCommand : public Command { public: /** - * Create a new custom command. - * - * @param id Command identifier. - */ + * Create a new custom command. + * + * @param id Command identifier. + */ CustomCommand(const std::string &id); virtual ~CustomCommand() = default; @@ -28,16 +28,16 @@ class CustomCommand : public Command { } /** - * Get the command identifier. - * - * @return Command identifier. - */ + * Get the command identifier. + * + * @return Command identifier. + */ const std::string &get_id() const; private: /** - * Command identifier. - */ + * Command identifier. + */ const std::string id; // TODO: Payload diff --git a/libopenage/gamestate/component/internal/commands/move.h b/libopenage/gamestate/component/internal/commands/move.h index 0516dddb46..f550b546d3 100644 --- a/libopenage/gamestate/component/internal/commands/move.h +++ b/libopenage/gamestate/component/internal/commands/move.h @@ -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. #pragma once @@ -15,10 +15,10 @@ namespace openage::gamestate::component::command { class MoveCommand : public Command { public: /** - * Creates a new move command. - * - * @param target Target position coordinates. - */ + * Creates a new move command. + * + * @param target Target position coordinates. + */ MoveCommand(const coord::phys3 &target); virtual ~MoveCommand() = default; @@ -27,16 +27,16 @@ class MoveCommand : public Command { } /** - * Get the target position. - * - * @return Target position coordinates. - */ + * Get the target position. + * + * @return Target position coordinates. + */ const coord::phys3 &get_target() const; private: /** - * Target position. - */ + * Target position. + */ const coord::phys3 target; }; diff --git a/libopenage/gamestate/component/internal/ownership.h b/libopenage/gamestate/component/internal/ownership.h index cc0f275249..ab4b30bed3 100644 --- a/libopenage/gamestate/component/internal/ownership.h +++ b/libopenage/gamestate/component/internal/ownership.h @@ -1,4 +1,4 @@ -// Copyright 2021-2023 the openage authors. See copying.md for legal info. +// Copyright 2021-2024 the openage authors. See copying.md for legal info. #pragma once @@ -19,7 +19,7 @@ class EventLoop; namespace gamestate::component { -class Ownership : public InternalComponent { +class Ownership final : public InternalComponent { public: /** * Creates an Ownership component. @@ -50,16 +50,16 @@ class Ownership : public InternalComponent { void set_owner(const time::time_t &time, const player_id_t owner_id); /** - * Get the owner IDs over time. - * - * @return Owner ID curve. - */ + * Get the owner IDs over time. + * + * @return Owner ID curve. + */ const curve::Discrete &get_owners() const; private: /** - * Owner ID storage over time. - */ + * Owner ID storage over time. + */ curve::Discrete owner; }; diff --git a/libopenage/gamestate/component/internal/position.cpp b/libopenage/gamestate/component/internal/position.cpp index 61611382cf..7c4b7bbb83 100644 --- a/libopenage/gamestate/component/internal/position.cpp +++ b/libopenage/gamestate/component/internal/position.cpp @@ -1,4 +1,4 @@ -// Copyright 2021-2023 the openage authors. See copying.md for legal info. +// Copyright 2021-2024 the openage authors. See copying.md for legal info. #include "position.h" @@ -60,7 +60,7 @@ const curve::Segmented &Position::get_angles() const { void Position::set_angle(const time::time_t &time, const coord::phys_angle_t &angle) { auto old_angle = this->angle.get(time); - this->angle.set_insert_jump(time, old_angle, angle); + this->angle.set_last_jump(time, old_angle, angle); } } // namespace openage::gamestate::component diff --git a/libopenage/gamestate/component/internal/position.h b/libopenage/gamestate/component/internal/position.h index af0502fbae..ff4cab0da2 100644 --- a/libopenage/gamestate/component/internal/position.h +++ b/libopenage/gamestate/component/internal/position.h @@ -1,4 +1,4 @@ -// Copyright 2021-2023 the openage authors. See copying.md for legal info. +// Copyright 2021-2024 the openage authors. See copying.md for legal info. #pragma once @@ -20,76 +20,76 @@ class EventLoop; namespace gamestate::component { -class Position : public InternalComponent { +class Position final : public InternalComponent { public: /** - * Create a Position component. - * - * @param loop Event loop that all events from the component are registered on. - * @param initial_pos Initial position at creation time. - * @param creation_time Ingame creation time of the component. - */ + * Create a Position component. + * + * @param loop Event loop that all events from the component are registered on. + * @param initial_pos Initial position at creation time. + * @param creation_time Ingame creation time of the component. + */ Position(const std::shared_ptr &loop, const coord::phys3 &initial_pos, const time::time_t &creation_time); /** - * Create a Position component. - * - * @param loop Event loop that all events from the component are registered on. - */ + * Create a Position component. + * + * @param loop Event loop that all events from the component are registered on. + */ Position(const std::shared_ptr &loop); component_t get_type() const override; /** - * Get the positions in the world coordinate system over time. - * - * @return Position curve. - */ + * Get the positions in the world coordinate system over time. + * + * @return Position curve. + */ const curve::Continuous &get_positions() const; /** - * Set the position at a given time. - * - * This adds a new keyframe to the position curve. - * - * @param time Time at which the position is set. - * @param pos New position. - */ + * Set the position at a given time. + * + * This adds a new keyframe to the position curve. + * + * @param time Time at which the position is set. + * @param pos New position. + */ void set_position(const time::time_t &time, const coord::phys3 &pos); /** - * Get the directions in degrees over time. - * - * @return Direction curve. - */ + * Get the directions in degrees over time. + * + * @return Direction curve. + */ const curve::Segmented &get_angles() const; /** - * Set the angle at a given time. - * - * This adds a new keyframe to the angle curve. - * - * @param time Time at which the angle is set. - * @param angle New angle. - */ + * Set the angle at a given time. + * + * This adds a new keyframe to the angle curve. + * + * @param time Time at which the angle is set. + * @param angle New angle. + */ void set_angle(const time::time_t &time, const coord::phys_angle_t &angle); private: /** - * Position storage over time. - */ + * Position storage over time. + */ curve::Continuous position; /** - * Angle the entity is facing over time. - * - * Represents degrees in the range [0, 360). At angle 0, the entity is facing - * towards the camera (direction vector {x, y} = {-1, 1}). - * - * Rotation is clockwise, so at 90 degrees the entity is facing left. - */ + * Angle the entity is facing over time. + * + * Represents degrees in the range [0, 360). At angle 0, the entity is facing + * towards the camera (direction vector {x, y} = {-1, 1}). + * + * Rotation is clockwise, so at 90 degrees the entity is facing left. + */ curve::Segmented angle; }; diff --git a/libopenage/gamestate/component/types.h b/libopenage/gamestate/component/types.h index 500b42692d..5e87b8ed23 100644 --- a/libopenage/gamestate/component/types.h +++ b/libopenage/gamestate/component/types.h @@ -19,6 +19,7 @@ enum class component_t { IDLE, TURN, MOVE, + SELECTABLE, LIVE }; diff --git a/libopenage/gamestate/entity_factory.cpp b/libopenage/gamestate/entity_factory.cpp index f6c3d6f660..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,18 +10,23 @@ #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" #include "gamestate/activity/end_node.h" -#include "gamestate/activity/event_node.h" +#include "gamestate/activity/event/command_in_queue.h" +#include "gamestate/activity/event/wait.h" #include "gamestate/activity/start_node.h" #include "gamestate/activity/task_system_node.h" -#include "gamestate/activity/xor_node.h" +#include "gamestate/activity/xor_event_gate.h" +#include "gamestate/activity/xor_gate.h" +#include "gamestate/api/activity.h" #include "gamestate/component/api/idle.h" #include "gamestate/component/api/live.h" #include "gamestate/component/api/move.h" +#include "gamestate/component/api/selectable.h" #include "gamestate/component/api/turn.h" #include "gamestate/component/internal/activity.h" #include "gamestate/component/internal/command_queue.h" @@ -38,7 +43,6 @@ #include "time/time.h" #include "util/fixed_point.h" - namespace openage::gamestate { /** @@ -47,19 +51,9 @@ namespace openage::gamestate { * The activity is as follows: * |------------------------------------------------------| * | v - * Start -> Idle -> Condition -> Wait for command -> Move -> Wait for move -> End - * ^ | - * |------------------------------------------------------| - * - * TODO: Should be: - * |----------------------------------------------------------------------| - * | v - * Start -> Idle -> -> Condition -> Wait for command <-> Condition -> Move -> Wait or command -> End - * ^ |^ | - * |---------------------------------------------||---------------------| - * (new condition in the middle: check if there is a command, if not go back to wait for command) - * (new node: go back to node 1 if there is no command) - * (node 5: wait for a command OR for a the wait time) + * Start -> Idle -> Condition -> Condition -> Wait for command -> Move -> Wait for move -> End + * ^ | + * |----------------------------------------------------------------| * * TODO: Replace with config */ @@ -67,89 +61,48 @@ std::shared_ptr create_test_activity() { auto start = std::make_shared(0); auto idle = std::make_shared(1, "Idle"); auto condition_moveable = std::make_shared(2); - auto wait_for_command = std::make_shared(3); - auto condition_command = std::make_shared(4); + auto condition_command = std::make_shared(3); + auto wait_for_command = std::make_shared(4); auto move = std::make_shared(5, "Move"); auto wait_for_move = std::make_shared(6); auto end = std::make_shared(7); start->add_output(idle); + // idle after start idle->add_output(condition_moveable); idle->set_system_id(system::system_id_t::IDLE); - condition_moveable->add_output(wait_for_command); - condition_moveable->add_output(end); - condition_moveable->set_condition_func([&](const time::time_t & /* time */, - const std::shared_ptr &entity) { - if (entity->has_component(component::component_t::MOVE)) { - return 3; // wait_for_command->get_id(); - } + // branch 1: check if the entity is moveable + activity::condition_t command_branch = [&](const time::time_t & /* time */, + const std::shared_ptr &entity) { + return entity->has_component(component::component_t::MOVE); + }; + condition_moveable->add_output(condition_command, command_branch); - return 7; // end->get_id(); - }); - - wait_for_command->add_output(move); - wait_for_command->set_primer_func([](const time::time_t & /* time */, - const std::shared_ptr &entity, - const std::shared_ptr &loop, - const std::shared_ptr &state) { - auto ev = loop->create_event("game.process_command", - entity->get_manager(), - state, - // event is not executed until a command is available - std::numeric_limits::max()); - auto entity_queue = std::dynamic_pointer_cast( - entity->get_component(component::component_t::COMMANDQUEUE)); - auto &queue = const_cast> &>(entity_queue->get_queue()); - queue.add_dependent(ev); - - return activity::event_store_t{ev}; - }); - wait_for_command->set_next_func([](const time::time_t &time, - const std::shared_ptr &entity, - const std::shared_ptr &, - const std::shared_ptr &) { - auto entity_queue = std::dynamic_pointer_cast( - entity->get_component(component::component_t::COMMANDQUEUE)); - auto &queue = entity_queue->get_queue(); - - if (queue.empty(time)) { - throw Error{ERR << "Command queue is empty"}; - } - auto &com = queue.front(time); - if (com->get_type() == component::command::command_t::MOVE) { - return 5; // move->get_id(); - } + // default: if it's not moveable, go straight to the end + condition_moveable->set_default(end); + + // branch 1: check if there is already a command in the queue + condition_command->add_output(move, gamestate::activity::command_in_queue); - throw Error{ERR << "Unknown command type"}; - }); + // default: if there is no command, wait for a command + condition_command->set_default(wait_for_command); + // wait for a command event + wait_for_command->add_output(move, gamestate::activity::primer_command_in_queue); + + // move move->add_output(wait_for_move); move->set_system_id(system::system_id_t::MOVE_COMMAND); - wait_for_move->add_output(idle); - wait_for_move->add_output(condition_command); - wait_for_move->add_output(end); - wait_for_move->set_primer_func([](const time::time_t &time, - const std::shared_ptr &entity, - const std::shared_ptr &loop, - const std::shared_ptr &state) { - auto ev = loop->create_event("game.wait", - entity->get_manager(), - state, - time); - - return activity::event_store_t{ev}; - }); - wait_for_move->set_next_func([&](const time::time_t &, - const std::shared_ptr &, - const std::shared_ptr &, - const std::shared_ptr &) { - return 1; // idle->get_id(); - }); - - return std::make_shared(0, "test", start); + // branch 1: wait for move event to finish + wait_for_move->add_output(idle, gamestate::activity::primer_wait); + + // branch 2: wait for a new command event + wait_for_move->add_output(move, gamestate::activity::primer_command_in_queue); + + return std::make_shared(0, start, "test"); } EntityFactory::EntityFactory() : @@ -210,6 +163,7 @@ void EntityFactory::init_components(const std::shared_ptrget_object(nyan_entity); nyan::set_t abilities = nyan_obj.get_set("GameEntity.abilities"); + std::optional activity_ability; for (const auto &ability_val : abilities) { auto ability_fqon = std::dynamic_pointer_cast(ability_val.get_ptr())->get_name(); auto ability_obj = owner_db_view->get_object(ability_fqon); @@ -238,7 +192,7 @@ void EntityFactory::init_components(const std::shared_ptradd_attribute(std::numeric_limits::min(), + live->add_attribute(time::TIME_MIN, attribute.get_name(), std::make_shared>(loop, 0, @@ -247,11 +201,161 @@ void EntityFactory::init_components(const std::shared_ptr(loop, ability_obj); + entity->add_component(selectable); + } } - // must be initialized after all other components - auto activity = std::make_shared(loop, create_test_activity()); - entity->add_component(activity); + if (activity_ability) { + init_activity(loop, owner_db_view, entity, activity_ability.value()); + } + else { + auto activity = std::make_shared(loop, create_test_activity()); + entity->add_component(activity); + } +} + +void EntityFactory::init_activity(const std::shared_ptr &loop, + const std::shared_ptr &owner_db_view, + const std::shared_ptr &entity, + const nyan::Object &ability) { + nyan::Object graph = ability.get_object("Activity.graph"); + + // Check if the activity is already exists in the cache + if (this->activity_cache.contains(graph.get_name())) { + auto activity = this->activity_cache.at(graph.get_name()); + auto component = std::make_shared(loop, activity); + entity->add_component(component); + + return; + } + + auto start_obj = api::APIActivity::get_start(graph); + + size_t node_id = 0; + + std::deque nyan_nodes; + std::unordered_map> node_id_map{}; + std::unordered_map visited{}; + std::shared_ptr start_node; + + // First pass: create all nodes using breadth-first search + nyan_nodes.push_back(start_obj); + while (!nyan_nodes.empty()) { + auto node = nyan_nodes.front(); + nyan_nodes.pop_front(); + + if (visited.contains(node.get_name())) { + continue; + } + + // Create the node + switch (api::APIActivityNode::get_type(node)) { + case activity::node_t::END: + break; + case activity::node_t::START: + start_node = std::make_shared(node_id); + node_id_map[node_id] = start_node; + break; + case activity::node_t::TASK_SYSTEM: { + auto task_node = std::make_shared(node_id); + task_node->set_system_id(api::APIActivityNode::get_system_id(node)); + node_id_map[node_id] = task_node; + break; + } + case activity::node_t::XOR_GATE: + node_id_map[node_id] = std::make_shared(node_id); + break; + case activity::node_t::XOR_EVENT_GATE: + node_id_map[node_id] = std::make_shared(node_id); + break; + default: + throw Error{ERR << "Unknown activity node type of node: " << node.get_name()}; + } + + // Get the node's outputs + auto next_nodes = api::APIActivityNode::get_next(node); + nyan_nodes.insert(nyan_nodes.end(), next_nodes.begin(), next_nodes.end()); + + visited.insert({node.get_name(), node_id}); + node_id++; + } + + // Second pass: connect the nodes + for (const auto ¤t_node : visited) { + auto nyan_node = owner_db_view->get_object(current_node.first); + auto activity_node = node_id_map[current_node.second]; + + switch (activity_node->get_type()) { + case activity::node_t::END: + break; + case activity::node_t::START: { + auto start = std::static_pointer_cast(activity_node); + auto output_fqon = nyan_node.get("Start.next")->get_name(); + auto output_id = visited[output_fqon]; + auto output_node = node_id_map[output_id]; + start->add_output(output_node); + break; + } + case activity::node_t::TASK_SYSTEM: { + auto task_system = std::static_pointer_cast(activity_node); + auto output_fqon = nyan_node.get("Ability.next")->get_name(); + auto output_id = visited[output_fqon]; + auto output_node = node_id_map[output_id]; + task_system->add_output(output_node); + break; + } + case activity::node_t::XOR_GATE: { + auto xor_gate = std::static_pointer_cast(activity_node); + auto conditions = nyan_node.get("XORGate.next"); + for (auto &condition : conditions->get()) { + auto condition_value = std::dynamic_pointer_cast(condition.get_ptr()); + auto condition_obj = owner_db_view->get_object(condition_value->get_name()); + + auto output_value = condition_obj.get("Condition.next")->get_name(); + auto output_id = visited[output_value]; + auto output_node = node_id_map[output_id]; + + xor_gate->add_output(output_node, api::APIActivityCondition::get_condition(condition_obj)); + } + + auto default_fqon = nyan_node.get("XORGate.default")->get_name(); + auto default_id = visited[default_fqon]; + auto default_node = node_id_map[default_id]; + xor_gate->set_default(default_node); + break; + } + case activity::node_t::XOR_EVENT_GATE: { + auto xor_event_gate = std::static_pointer_cast(activity_node); + auto next = nyan_node.get("XOREventGate.next"); + for (auto &next_node : next->get()) { + auto event_value = std::dynamic_pointer_cast(next_node.first.get_ptr()); + auto event_obj = owner_db_view->get_object(event_value->get_name()); + + auto next_node_value = std::dynamic_pointer_cast(next_node.second.get_ptr()); + auto next_node_obj = owner_db_view->get_object(next_node_value->get_name()); + + auto output_id = visited[next_node_obj.get_name()]; + auto output_node = node_id_map[output_id]; + + xor_event_gate->add_output(output_node, api::APIActivityEvent::get_primer(event_obj)); + } + break; + } + default: + throw Error{ERR << "Unknown activity node type of node: " << current_node.first}; + } + } + + auto activity = std::make_shared(0, start_node, graph.get_name()); + this->activity_cache.insert({graph.get_name(), activity}); + + auto component = std::make_shared(loop, activity); + entity->add_component(component); } entity_id_t EntityFactory::get_next_entity_id() { diff --git a/libopenage/gamestate/entity_factory.h b/libopenage/gamestate/entity_factory.h index 5a369d995e..11efbe3f2e 100644 --- a/libopenage/gamestate/entity_factory.h +++ b/libopenage/gamestate/entity_factory.h @@ -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. #pragma once @@ -21,6 +21,11 @@ class RenderFactory; } namespace gamestate { + +namespace activity { +class Activity; +} // namespace activity + class GameEntity; class GameState; class Player; @@ -31,17 +36,17 @@ class Player; class EntityFactory { public: /** - * Create a new entity factory for game entities. - */ + * Create a new entity factory for game entities. + */ EntityFactory(); ~EntityFactory() = default; /** - * Create a new game entity. + * Create a new game entity. * * This just creates the entity. The caller is responsible for initializing * its components and placing it into the game. - * + * * @param loop Event loop for the gamestate. * @param state State of the game. * @param owner_id ID of the player owning the entity. @@ -92,11 +97,16 @@ class EntityFactory { const std::shared_ptr &entity, const nyan::fqon_t &nyan_entity); + void init_activity(const std::shared_ptr &loop, + const std::shared_ptr &owner_db_view, + const std::shared_ptr &entity, + const nyan::Object &ability); + /** - * Get a unique ID for creating a game entity. - * - * @return Unique ID for a game entity. - */ + * Get a unique ID for creating a game entity. + * + * @return Unique ID for a game entity. + */ entity_id_t get_next_entity_id(); /** @@ -107,8 +117,8 @@ class EntityFactory { player_id_t get_next_player_id(); /** - * ID of the next game entity to be created. - */ + * ID of the next game entity to be created. + */ entity_id_t next_entity_id; /** @@ -124,8 +134,13 @@ class EntityFactory { // TODO: Cache created game entities. /** - * Mutex for thread safety. - */ + * Cache for activities. + */ + std::unordered_map> activity_cache; + + /** + * Mutex for thread safety. + */ std::shared_mutex mutex; }; } // namespace gamestate diff --git a/libopenage/gamestate/event/CMakeLists.txt b/libopenage/gamestate/event/CMakeLists.txt index 309ba96909..4c885d82b1 100644 --- a/libopenage/gamestate/event/CMakeLists.txt +++ b/libopenage/gamestate/event/CMakeLists.txt @@ -1,4 +1,5 @@ add_sources(libopenage + drag_select.cpp process_command.cpp send_command.cpp spawn_entity.cpp diff --git a/libopenage/gamestate/event/drag_select.cpp b/libopenage/gamestate/event/drag_select.cpp new file mode 100644 index 0000000000..efb43b0ab1 --- /dev/null +++ b/libopenage/gamestate/event/drag_select.cpp @@ -0,0 +1,100 @@ +// Copyright 2023-2023 the openage authors. See copying.md for legal info. + +#include "drag_select.h" + +#include + +#include "coord/phys.h" +#include "coord/pixel.h" +#include "coord/scene.h" +#include "curve/discrete.h" +#include "gamestate/component/internal/ownership.h" +#include "gamestate/component/internal/position.h" +#include "gamestate/game_entity.h" +#include "gamestate/game_state.h" +#include "gamestate/types.h" + + +namespace openage::gamestate::event { + +DragSelectHandler::DragSelectHandler() : + OnceEventHandler{"game.drag_select"} {} + +void DragSelectHandler::setup_event(const std::shared_ptr & /* event */, + const std::shared_ptr & /* state */) { + // TODO +} + +void DragSelectHandler::invoke(openage::event::EventLoop & /* loop */, + const std::shared_ptr & /* target */, + const std::shared_ptr &state, + const time::time_t &time, + const param_map ¶ms) { + auto gstate = std::dynamic_pointer_cast(state); + + size_t controlled_id = params.get("controlled", 0); + + Eigen::Matrix4f id_matrix = Eigen::Matrix4f::Identity(); + Eigen::Matrix4f cam_matrix = params.get("camera_matrix", id_matrix); + Eigen::Vector2f drag_start = params.get("drag_start", Eigen::Vector2f{0, 0}); + Eigen::Vector2f drag_end = params.get("drag_end", Eigen::Vector2f{0, 0}); + + // Boundaries of the rectangle + float top = std::max(drag_start.y(), drag_end.y()); + float bottom = std::min(drag_start.y(), drag_end.y()); + float left = std::min(drag_start.x(), drag_end.x()); + float right = std::max(drag_start.x(), drag_end.x()); + + log::log(SPAM << "Drag select rectangle (NDC):"); + log::log(SPAM << "\tTop: " << top); + log::log(SPAM << "\tBottom: " << bottom); + log::log(SPAM << "\tLeft: " << left); + log::log(SPAM << "\tRight: " << right); + + std::vector selected; + for (auto &entity : gstate->get_game_entities()) { + if (not entity.second->has_component(component::component_t::SELECTABLE)) { + // skip entities that are not selectable + continue; + } + + // Check if the entity is owned by the controlled player + // TODO: Check this using Selectable diplomatic property + auto owner = std::dynamic_pointer_cast( + entity.second->get_component(component::component_t::OWNERSHIP)); + if (owner->get_owners().get(time) != controlled_id) { + // only select entities of the controlled player + continue; + } + + // Get the position of the entity in the viewport + auto pos = std::dynamic_pointer_cast( + entity.second->get_component(component::component_t::POSITION)); + auto current_pos = pos->get_positions().get(time); + auto world_pos = current_pos.to_scene3().to_world_space(); + Eigen::Vector4f clip_pos = cam_matrix * Eigen::Vector4f{world_pos.x(), world_pos.y(), world_pos.z(), 1}; + + // Check if the entity is in the rectangle + if (clip_pos.x() > left + and clip_pos.x() < right + and clip_pos.y() > bottom + and clip_pos.y() < top) { + selected.push_back(entity.first); + } + } + + // Select the units + auto select_cb = params.get("select_cb", + std::function ids)>{ + [](const std::vector /* ids */) {}}); + select_cb(selected); +} + +time::time_t DragSelectHandler::predict_invoke_time(const std::shared_ptr & /* target */, + const std::shared_ptr & /* state */, + const time::time_t &at) { + return at; +} + + +} // namespace openage::gamestate::event diff --git a/libopenage/gamestate/event/drag_select.h b/libopenage/gamestate/event/drag_select.h new file mode 100644 index 0000000000..29a7338f27 --- /dev/null +++ b/libopenage/gamestate/event/drag_select.h @@ -0,0 +1,46 @@ +// Copyright 2023-2023 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include + +#include "event/evententity.h" +#include "event/eventhandler.h" + + +namespace openage { + +namespace event { +class EventLoop; +class Event; +class State; +} // namespace event + +namespace gamestate::event { + +/** + * Drag select game entities. + */ +class DragSelectHandler : public openage::event::OnceEventHandler { +public: + DragSelectHandler(); + ~DragSelectHandler() = default; + + void setup_event(const std::shared_ptr &event, + const std::shared_ptr &state) override; + + void invoke(openage::event::EventLoop &loop, + const std::shared_ptr &target, + const std::shared_ptr &state, + const time::time_t &time, + const param_map ¶ms) override; + + time::time_t predict_invoke_time(const std::shared_ptr &target, + const std::shared_ptr &state, + const time::time_t &at) override; +}; + +} // namespace gamestate::event +} // namespace openage diff --git a/libopenage/gamestate/event/process_command.cpp b/libopenage/gamestate/event/process_command.cpp index 4783fe89c8..d00762d8c9 100644 --- a/libopenage/gamestate/event/process_command.cpp +++ b/libopenage/gamestate/event/process_command.cpp @@ -19,16 +19,15 @@ void ProcessCommandHandler::invoke(openage::event::EventLoop & /* loop */, const std::shared_ptr &target, const std::shared_ptr & /* state */, const time::time_t &time, - const param_map & /* params */) { + const param_map ¶ms) { auto mgr = std::dynamic_pointer_cast(target); - mgr->run_activity_system(time); + mgr->run_activity_system(time, params); } time::time_t ProcessCommandHandler::predict_invoke_time(const std::shared_ptr & /* target */, - const std::shared_ptr & /* state */, - const time::time_t &at) { + const std::shared_ptr & /* state */, + const time::time_t &at) { return at; } - } // namespace openage::gamestate::event diff --git a/libopenage/gamestate/event/process_command.h b/libopenage/gamestate/event/process_command.h index 8b940dd616..db4fb18157 100644 --- a/libopenage/gamestate/event/process_command.h +++ b/libopenage/gamestate/event/process_command.h @@ -17,8 +17,12 @@ class EventEntity; class State; } // namespace event -namespace gamestate::event { +namespace gamestate { +class GameEntity; +class GameState; + +namespace event { /** * Process a command from a game entity command queue. */ @@ -42,5 +46,6 @@ class ProcessCommandHandler : public openage::event::OnceEventHandler { }; -} // namespace gamestate::event +} // namespace event +} // namespace gamestate } // namespace openage diff --git a/libopenage/gamestate/event/spawn_entity.cpp b/libopenage/gamestate/event/spawn_entity.cpp index 5cc1cf27df..66f6df9be7 100644 --- a/libopenage/gamestate/event/spawn_entity.cpp +++ b/libopenage/gamestate/event/spawn_entity.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 "spawn_entity.h" @@ -19,6 +19,7 @@ #include "gamestate/game_entity.h" #include "gamestate/game_state.h" #include "gamestate/manager.h" +#include "gamestate/map.h" #include "gamestate/types.h" // TODO: Testing @@ -76,6 +77,52 @@ static const std::vector trial_test_entities = { "trial_base.data.game_entity.generic.barracks.barracks.Barracks", }; +// TODO: Remove hardcoded test entity references +// declared static so we only have to build the vector once +static std::vector test_entities; + + +void build_test_entities(const std::shared_ptr &gstate) { + auto modpack_ids = gstate->get_mod_manager()->get_load_order(); + for (auto &modpack_id : modpack_ids) { + if (modpack_id == "aoe1_base") { + test_entities.insert(test_entities.end(), + aoe1_test_entities.begin(), + aoe1_test_entities.end()); + } + else if (modpack_id == "de1_base") { + test_entities.insert(test_entities.end(), + de1_test_entities.begin(), + de1_test_entities.end()); + } + else if (modpack_id == "aoe2_base") { + test_entities.insert(test_entities.end(), + aoe2_test_entities.begin(), + aoe2_test_entities.end()); + } + else if (modpack_id == "de2_base") { + test_entities.insert(test_entities.end(), + de2_test_entities.begin(), + de2_test_entities.end()); + } + else if (modpack_id == "hd_base") { + test_entities.insert(test_entities.end(), + hd_test_entities.begin(), + hd_test_entities.end()); + } + else if (modpack_id == "swgb_base") { + test_entities.insert(test_entities.end(), + swgb_test_entities.begin(), + swgb_test_entities.end()); + } + else if (modpack_id == "trial_base") { + test_entities.insert(test_entities.end(), + trial_test_entities.begin(), + trial_test_entities.end()); + } + } +} + Spawner::Spawner(const std::shared_ptr &loop) : EventEntity(loop) { @@ -109,56 +156,33 @@ void SpawnEntityHandler::invoke(openage::event::EventLoop & /* loop */, const param_map ¶ms) { auto gstate = std::dynamic_pointer_cast(state); + // Check if spawn position is on the map + auto pos = params.get("position", gamestate::WORLD_ORIGIN); + auto map_size = gstate->get_map()->get_size(); + if (not(pos.ne >= 0 + and pos.ne < map_size[0] + and pos.se >= 0 + and pos.se < map_size[1])) { + // Do nothing if the spawn position is not on the map + log::log(DBG << "Entity spawn failed: " + << "Spawn position " << pos + << " is not inside the map area " + << "(map size: " << map_size << ")"); + return; + } + auto nyan_db = gstate->get_db_view(); auto game_entities = nyan_db->get_obj_children_all("engine.util.game_entity.GameEntity"); - // TODO: Remove hardcoded test entity references - static std::vector test_entities; // declared static so we only have to do this once if (test_entities.empty()) { - auto modpack_ids = gstate->get_mod_manager()->get_load_order(); - for (auto &modpack_id : modpack_ids) { - if (modpack_id == "aoe1_base") { - test_entities.insert(test_entities.end(), - aoe1_test_entities.begin(), - aoe1_test_entities.end()); - } - else if (modpack_id == "de1_base") { - test_entities.insert(test_entities.end(), - de1_test_entities.begin(), - de1_test_entities.end()); - } - else if (modpack_id == "aoe2_base") { - test_entities.insert(test_entities.end(), - aoe2_test_entities.begin(), - aoe2_test_entities.end()); - } - else if (modpack_id == "de2_base") { - test_entities.insert(test_entities.end(), - de2_test_entities.begin(), - de2_test_entities.end()); - } - else if (modpack_id == "hd_base") { - test_entities.insert(test_entities.end(), - hd_test_entities.begin(), - hd_test_entities.end()); - } - else if (modpack_id == "swgb_base") { - test_entities.insert(test_entities.end(), - swgb_test_entities.begin(), - swgb_test_entities.end()); - } - else if (modpack_id == "trial_base") { - test_entities.insert(test_entities.end(), - trial_test_entities.begin(), - trial_test_entities.end()); - } + build_test_entities(gstate); + + // Do nothing if there are no test entities + if (test_entities.empty()) { + return; } } - if (test_entities.empty()) { - // Do nothing because we don't have anything to spawn - return; - } static uint8_t index = 0; nyan::fqon_t nyan_entity = test_entities.at(index); @@ -175,7 +199,6 @@ void SpawnEntityHandler::invoke(openage::event::EventLoop & /* loop */, auto entity_pos = std::dynamic_pointer_cast( entity->get_component(component::component_t::POSITION)); - auto pos = params.get("position", gamestate::WORLD_ORIGIN); entity_pos->set_position(time, pos); entity_pos->set_angle(time, coord::phys_angle_t::from_int(315)); @@ -188,11 +211,6 @@ void SpawnEntityHandler::invoke(openage::event::EventLoop & /* loop */, activity->init(time); entity->get_manager()->run_activity_system(time); - // TODO: Select the unit when it's created - // very dumb but it gets the job done - auto select_cb = params.get("select_cb", std::function{}); - select_cb(entity->get_id()); - gstate->add_game_entity(entity); } diff --git a/libopenage/gamestate/event/spawn_entity.h b/libopenage/gamestate/event/spawn_entity.h index 12f99378b3..948df0a855 100644 --- a/libopenage/gamestate/event/spawn_entity.h +++ b/libopenage/gamestate/event/spawn_entity.h @@ -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. #pragma once @@ -41,11 +41,11 @@ class Spawner : public openage::event::EventEntity { class SpawnEntityHandler : public openage::event::OnceEventHandler { public: /** - * Creates a new SpawnEntityHandler. - * - * @param loop: Event loop that the components register on. - * @param factory: Factory that is used to create the entity. - */ + * Creates a new SpawnEntityHandler. + * + * @param loop: Event loop that the components register on. + * @param factory: Factory that is used to create the entity. + */ SpawnEntityHandler(const std::shared_ptr &loop, const std::shared_ptr &factory); ~SpawnEntityHandler() = default; @@ -92,13 +92,13 @@ class SpawnEntityHandler : public openage::event::OnceEventHandler { private: /** - * Event loop that the entity components are registered on. - */ + * Event loop that the entity components are registered on. + */ std::shared_ptr loop; /** - * The factory that is used to create the entity. - */ + * The factory that is used to create the entity. + */ std::shared_ptr factory; }; diff --git a/libopenage/gamestate/event/wait.cpp b/libopenage/gamestate/event/wait.cpp index 2d067086fa..eb2e793334 100644 --- a/libopenage/gamestate/event/wait.cpp +++ b/libopenage/gamestate/event/wait.cpp @@ -19,15 +19,16 @@ void WaitHandler::invoke(openage::event::EventLoop & /* loop */, const std::shared_ptr &target, const std::shared_ptr & /* state */, const time::time_t &time, - const param_map & /* params */) { + const param_map ¶ms) { auto mgr = std::dynamic_pointer_cast(target); - mgr->run_activity_system(time); + mgr->run_activity_system(time, params); } time::time_t WaitHandler::predict_invoke_time(const std::shared_ptr & /* target */, - const std::shared_ptr & /* state */, - const time::time_t &at) { + const std::shared_ptr & /* state */, + const time::time_t &at) { return at; } + } // namespace openage::gamestate::event diff --git a/libopenage/gamestate/event/wait.h b/libopenage/gamestate/event/wait.h index 2c0d9732f2..1c74015984 100644 --- a/libopenage/gamestate/event/wait.h +++ b/libopenage/gamestate/event/wait.h @@ -17,7 +17,11 @@ class EventEntity; class State; } // namespace event -namespace gamestate::event { +namespace gamestate { +class GameEntity; +class GameState; + +namespace event { /** * Waits until the event is handled and calls back the entity manager. @@ -40,5 +44,7 @@ class WaitHandler : public openage::event::OnceEventHandler { const std::shared_ptr &state, const time::time_t &at) override; }; -} // namespace gamestate::event + +} // namespace event +} // namespace gamestate } // namespace openage diff --git a/libopenage/gamestate/game.cpp b/libopenage/gamestate/game.cpp index d31310945d..e63499d769 100644 --- a/libopenage/gamestate/game.cpp +++ b/libopenage/gamestate/game.cpp @@ -1,4 +1,4 @@ -// Copyright 2018-2023 the openage authors. See copying.md for legal info. +// Copyright 2018-2024 the openage authors. See copying.md for legal info. #include "game.h" @@ -13,16 +13,21 @@ #include "assets/modpack.h" #include "gamestate/entity_factory.h" #include "gamestate/game_state.h" +#include "gamestate/map.h" +#include "gamestate/terrain.h" +#include "gamestate/terrain_factory.h" #include "gamestate/universe.h" #include "util/path.h" #include "util/strings.h" +#include "coord/tile.h" namespace openage::gamestate { Game::Game(const std::shared_ptr &event_loop, const std::shared_ptr &mod_manager, - const std::shared_ptr &entity_factory) : + const std::shared_ptr &entity_factory, + const std::shared_ptr &terrain_factory) : db{nyan::Database::create()}, state{std::make_shared(this->db, event_loop)}, universe{std::make_shared(state)} { @@ -39,6 +44,8 @@ Game::Game(const std::shared_ptr &event_loop, // This can be removed when we spawn based on game logic rather than // hardcoded entity types. this->state->set_mod_manager(mod_manager); + + this->generate_terrain(terrain_factory); } const std::shared_ptr &Game::get_state() const { @@ -47,6 +54,7 @@ const std::shared_ptr &Game::get_state() const { void Game::attach_renderer(const std::shared_ptr &render_factory) { this->universe->attach_renderer(render_factory); + this->state->get_map()->get_terrain()->attach_renderer(render_factory); } void Game::load_data(const std::shared_ptr &mod_manager) { @@ -89,7 +97,7 @@ void Game::load_path(const util::Path &base_dir, auto base_path = base_dir.resolve_native_path(); auto search_path = base_dir / mod_dir / search; - auto fileload_func = [&base_path, &mod_dir](const std::string &filename) { + auto fileload_func = [&base_path](const std::string &filename) { // nyan wants a string filepath, so we have to construct it from the // path and subpath parameters log::log(INFO << "Loading .nyan file: " << filename); @@ -98,7 +106,7 @@ void Game::load_path(const util::Path &base_dir, }; // file loading - if (search_path.is_file() && search_path.get_suffix() == ".nyan") { + if (search_path.is_file() and search_path.get_suffix() == ".nyan") { auto loc = mod_dir + "/" + search; this->db->load(loc, fileload_func); return; @@ -119,4 +127,24 @@ void Game::load_path(const util::Path &base_dir, } } +void Game::generate_terrain(const std::shared_ptr &terrain_factory) { + auto chunk0 = terrain_factory->add_chunk(this->state, + util::Vector2s{10, 10}, + coord::tile_delta{0, 0}); + auto chunk1 = terrain_factory->add_chunk(this->state, + util::Vector2s{10, 10}, + coord::tile_delta{10, 0}); + auto chunk2 = terrain_factory->add_chunk(this->state, + util::Vector2s{10, 10}, + coord::tile_delta{0, 10}); + auto chunk3 = terrain_factory->add_chunk(this->state, + util::Vector2s{10, 10}, + coord::tile_delta{10, 10}); + + auto terrain = terrain_factory->add_terrain({20, 20}, {chunk0, chunk1, chunk2, chunk3}); + + auto map = std::make_shared(this->state, terrain); + this->state->set_map(map); +} + } // namespace openage::gamestate diff --git a/libopenage/gamestate/game.h b/libopenage/gamestate/game.h index aecfd3bd72..e48c837b19 100644 --- a/libopenage/gamestate/game.h +++ b/libopenage/gamestate/game.h @@ -1,4 +1,4 @@ -// Copyright 2018-2023 the openage authors. See copying.md for legal info. +// Copyright 2018-2024 the openage authors. See copying.md for legal info. #pragma once @@ -30,6 +30,7 @@ class Path; namespace gamestate { class GameState; class EntityFactory; +class TerrainFactory; class Universe; /** @@ -47,18 +48,19 @@ class Game { /** * Create a new game. * - * @param event_loop Event simulation loop for the gamestate. - * @param mod_manager Mod manager. + * @param event_loop Event simulation loop for the gamestate. + * @param mod_manager Mod manager. * @param entity_factory Factory for creating entities. Used for creating the players. */ Game(const std::shared_ptr &event_loop, const std::shared_ptr &mod_manager, - const std::shared_ptr &entity_factory); + const std::shared_ptr &entity_factory, + const std::shared_ptr &terrain_factory); ~Game() = default; /** - * Get the current game state. - */ + * Get the current game state. + */ const std::shared_ptr &get_state() const; /** @@ -72,35 +74,44 @@ class Game { private: /** - * Load game data from the filesystem. - * - * @param mod_manager Mod manager. - */ + * Load game data from the filesystem. + * + * @param mod_manager Mod manager. + */ void load_data(const std::shared_ptr &mod_manager); /** - * Load game data from the filesystem recursively. - * - * TODO: Move this into nyan. - * - * @param base_dir Base directory where mods are stored. - * @param mod_dir Name of the mod directory. - * @param search Search path relative to the mod directory. - * @param recursive if true, recursively search subfolders if the the search path is a directory. - */ + * Load game data from the filesystem recursively. + * + * TODO: Move this into nyan. + * + * @param base_dir Base directory where mods are stored. + * @param mod_dir Name of the mod directory. + * @param search Search path relative to the mod directory. + * @param recursive if true, recursively search subfolders if the the search path is a directory. + */ void load_path(const util::Path &base_dir, const std::string &mod_dir, const std::string &search, bool recursive = false); /** - * Nyan game data database. - */ + * Generate the terrain for the current game. + * + * TODO: Use a real map generator. + * + * @param terrain_factory Factory for creating terrain objects. + */ + void generate_terrain(const std::shared_ptr &terrain_factory); + + /** + * Nyan game data database. + */ std::shared_ptr db; /** - * State of the current game. - */ + * State of the current game. + */ std::shared_ptr state; /** diff --git a/libopenage/gamestate/game_entity.cpp b/libopenage/gamestate/game_entity.cpp index e8b7a9e086..421c2847e7 100644 --- a/libopenage/gamestate/game_entity.cpp +++ b/libopenage/gamestate/game_entity.cpp @@ -1,4 +1,4 @@ -// Copyright 2022-2023 the openage authors. See copying.md for legal info. +// Copyright 2022-2024 the openage authors. See copying.md for legal info. #include "game_entity.h" @@ -9,7 +9,7 @@ #include "gamestate/component/api/move.h" #include "gamestate/component/base_component.h" #include "gamestate/component/internal/position.h" -#include "renderer/stages/world/world_render_entity.h" +#include "renderer/stages/world/render_entity.h" namespace openage::gamestate { @@ -30,7 +30,7 @@ entity_id_t GameEntity::get_id() const { return this->id; } -void GameEntity::set_render_entity(const std::shared_ptr &entity) { +void GameEntity::set_render_entity(const std::shared_ptr &entity) { // TODO: Transfer state from old render entity to new one? this->render_entity = entity; diff --git a/libopenage/gamestate/game_entity.h b/libopenage/gamestate/game_entity.h index a79f167e7f..923aed2eab 100644 --- a/libopenage/gamestate/game_entity.h +++ b/libopenage/gamestate/game_entity.h @@ -1,4 +1,4 @@ -// Copyright 2022-2023 the openage authors. See copying.md for legal info. +// Copyright 2022-2024 the openage authors. See copying.md for legal info. #pragma once @@ -14,7 +14,7 @@ namespace openage { namespace renderer::world { -class WorldRenderEntity; +class RenderEntity; } namespace gamestate { @@ -30,10 +30,10 @@ class Component; class GameEntity { public: /** - * Create a new game entity. - * - * @param id Unique identifier. - */ + * Create a new game entity. + * + * @param id Unique identifier. + */ GameEntity(entity_id_t id); ~GameEntity() = default; @@ -51,10 +51,10 @@ class GameEntity { std::shared_ptr copy(entity_id_t id); /** - * Get the unique identifier of this entity. - * - * @return Unique identifier. - */ + * Get the unique identifier of this entity. + * + * @return Unique identifier. + */ entity_id_t get_id() const; /** @@ -62,7 +62,7 @@ class GameEntity { * * @param entity New render entity. */ - void set_render_entity(const std::shared_ptr &entity); + void set_render_entity(const std::shared_ptr &entity); /** * Set the event manager of this entity. @@ -79,32 +79,32 @@ class GameEntity { const std::shared_ptr &get_manager() const; /** - * Get a component of this entity. - * - * @param type Component type. - */ + * Get a component of this entity. + * + * @param type Component type. + */ const std::shared_ptr &get_component(component::component_t type); /** - * Add a component to this entity. - * - * @param component Component to add. - */ + * Add a component to this entity. + * + * @param component Component to add. + */ void add_component(const std::shared_ptr &component); /** - * Check if this entity has a component of the given type. - * - * @param type Component type. - */ + * Check if this entity has a component of the given type. + * + * @param type Component type. + */ bool has_component(component::component_t type); /** - * Update the render entity. - * - * @param time Simulation time of the update. - * @param animation_path Animation path used at \p time. - */ + * Update the render entity. + * + * @param time Simulation time of the update. + * @param animation_path Path to the animation definition used at \p time. + */ void render_update(const time::time_t &time, const std::string &animation_path); @@ -133,14 +133,16 @@ class GameEntity { entity_id_t id; /** - * Data components. - */ + * Data components. + * + * TODO: Multiple components of the same type. + */ std::unordered_map> components; /** * Render entity for pushing updates to the renderer. Can be \p nullptr. */ - std::shared_ptr render_entity; + std::shared_ptr render_entity; /** * Event manager. diff --git a/libopenage/gamestate/game_state.cpp b/libopenage/gamestate/game_state.cpp index 9cda363379..4bf0aaa348 100644 --- a/libopenage/gamestate/game_state.cpp +++ b/libopenage/gamestate/game_state.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 "game_state.h" @@ -37,6 +37,10 @@ void GameState::add_player(const std::shared_ptr &player) { this->players[player->get_id()] = player; } +void GameState::set_map(const std::shared_ptr &map) { + this->map = map; +} + const std::shared_ptr &GameState::get_game_entity(entity_id_t id) const { if (!this->game_entities.contains(id)) [[unlikely]] { throw Error(MSG(err) << "Game entity with ID " << id << " does not exist"); @@ -55,6 +59,10 @@ const std::shared_ptr &GameState::get_player(player_id_t id) const { return this->players.at(id); } +const std::shared_ptr &GameState::get_map() const { + return this->map; +} + const std::shared_ptr &GameState::get_mod_manager() const { return this->mod_manager; } diff --git a/libopenage/gamestate/game_state.h b/libopenage/gamestate/game_state.h index d252acaadc..98cdbc187a 100644 --- a/libopenage/gamestate/game_state.h +++ b/libopenage/gamestate/game_state.h @@ -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. #pragma once @@ -26,6 +26,7 @@ class EventLoop; namespace gamestate { class GameEntity; +class Map; class Player; /** @@ -37,51 +38,58 @@ class Player; class GameState : public openage::event::State { public: /** - * Create a new game state. - * - * @param db Nyan game data database. - * @param event_loop Event loop for the game state. - */ + * Create a new game state. + * + * @param db Nyan game data database. + * @param event_loop Event loop for the game state. + */ explicit GameState(const std::shared_ptr &db, const std::shared_ptr &event_loop); /** - * Get the nyan database view for the whole game. - * - * Players have individual views for their own data. - * - * @return nyan database view. - */ + * Get the nyan database view for the whole game. + * + * Players have individual views for their own data. + * + * @return nyan database view. + */ const std::shared_ptr &get_db_view(); /** - * Add a new game entity to the index. - * - * @param entity New game entity. - */ + * Add a new game entity to the index. + * + * @param entity New game entity. + */ void add_game_entity(const std::shared_ptr &entity); /** - * Add a new player to the index. - * - * @param player New player. - */ + * Add a new player to the index. + * + * @param player New player. + */ void add_player(const std::shared_ptr &player); /** - * Get a game entity by its ID. - * - * @param id ID of the game entity. + * Set the map of the current game. * - * @return Game entity with the given ID. - */ + * @param terrain Map object. + */ + void set_map(const std::shared_ptr &map); + + /** + * Get a game entity by its ID. + * + * @param id ID of the game entity. + * + * @return Game entity with the given ID. + */ const std::shared_ptr &get_game_entity(entity_id_t id) const; /** - * Get all game entities in the current game. - * - * @return Map of all game entities in the current game by their ID. - */ + * Get all game entities in the current game. + * + * @return Map of all game entities in the current game by their ID. + */ const std::unordered_map> &get_game_entities() const; /** @@ -94,30 +102,42 @@ class GameState : public openage::event::State { const std::shared_ptr &get_player(player_id_t id) const; /** - * TODO: Only for testing. - */ + * Get the map of the current game. + * + * @return Map object. + */ + const std::shared_ptr &get_map() const; + + /** + * TODO: Only for testing. + */ const std::shared_ptr &get_mod_manager() const; void set_mod_manager(const std::shared_ptr &mod_manager); private: /** - * View for the nyan game data database. - */ + * View for the nyan game data database. + */ std::shared_ptr db_view; /** - * Map of all game entities in the current game by their ID. - */ + * Map of all game entities in the current game by their ID. + */ std::unordered_map> game_entities; /** - * Map of all players in the current game by their ID. - */ + * Map of all players in the current game by their ID. + */ std::unordered_map> players; /** - * TODO: Only for testing - */ + * Map of the current game. + */ + std::shared_ptr map; + + /** + * TODO: Only for testing + */ std::shared_ptr mod_manager; }; } // namespace gamestate diff --git a/libopenage/gamestate/manager.cpp b/libopenage/gamestate/manager.cpp index 9c8f6c1899..f00ccab22c 100644 --- a/libopenage/gamestate/manager.cpp +++ b/libopenage/gamestate/manager.cpp @@ -20,9 +20,10 @@ GameEntityManager::GameEntityManager(const std::shared_ptr &ev_params) { log::log(DBG << "Running activity system for entity " << this->game_entity->get_id()); - system::Activity::advance(this->game_entity, time, this->loop, this->state); + system::Activity::advance(time, this->game_entity, this->loop, this->state, ev_params); } size_t GameEntityManager::id() const { diff --git a/libopenage/gamestate/manager.h b/libopenage/gamestate/manager.h index 6131c94bd2..8d6976c806 100644 --- a/libopenage/gamestate/manager.h +++ b/libopenage/gamestate/manager.h @@ -4,9 +4,11 @@ #include #include +#include #include #include "event/evententity.h" +#include "event/eventhandler.h" #include "time/time.h" @@ -26,7 +28,8 @@ class GameEntityManager : public openage::event::EventEntity { const std::shared_ptr &game_entity); ~GameEntityManager() = default; - void run_activity_system(const time::time_t &time); + void run_activity_system(const time::time_t &time, + const std::optional &ev_params = std::nullopt); size_t id() const override; std::string idstr() const override; diff --git a/libopenage/gamestate/map.cpp b/libopenage/gamestate/map.cpp new file mode 100644 index 0000000000..88578fabdb --- /dev/null +++ b/libopenage/gamestate/map.cpp @@ -0,0 +1,81 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "map.h" + +#include + +#include "gamestate/api/terrain.h" +#include "gamestate/game_state.h" +#include "gamestate/terrain.h" +#include "gamestate/terrain_chunk.h" +#include "pathfinding/cost_field.h" +#include "pathfinding/grid.h" +#include "pathfinding/pathfinder.h" +#include "pathfinding/sector.h" + + +namespace openage::gamestate { +Map::Map(const std::shared_ptr &state, + const std::shared_ptr &terrain) : + terrain{terrain}, + pathfinder{std::make_shared()}, + grid_lookup{} { + // Create a grid for each path type + // TODO: This is non-deterministic because of the unordered set. Is this a problem? + auto nyan_db = state->get_db_view(); + std::unordered_set path_types = nyan_db->get_obj_children_all("engine.util.path_type.PathType"); + size_t grid_idx = 0; + auto chunk_size = this->terrain->get_chunk(0)->get_size(); + auto side_length = std::max(chunk_size[0], chunk_size[1]); + auto grid_size = this->terrain->get_chunks_size(); + for (const auto &path_type : path_types) { + auto grid = std::make_shared(grid_idx, grid_size, side_length); + this->pathfinder->add_grid(grid); + + this->grid_lookup.emplace(path_type, grid_idx); + grid_idx += 1; + } + + // Set path costs + for (size_t chunk_idx = 0; chunk_idx < this->terrain->get_chunks().size(); ++chunk_idx) { + auto chunk_terrain = this->terrain->get_chunk(chunk_idx); + for (size_t tile_idx = 0; tile_idx < chunk_terrain->get_tiles().size(); ++tile_idx) { + auto tile = chunk_terrain->get_tile(tile_idx); + auto path_costs = api::APITerrain::get_path_costs(tile.terrain); + + for (const auto &path_cost : path_costs) { + auto grid_id = this->grid_lookup.at(path_cost.first); + auto grid = this->pathfinder->get_grid(grid_id); + + auto sector = grid->get_sector(chunk_idx); + auto cost_field = sector->get_cost_field(); + cost_field->set_cost(tile_idx, path_cost.second, time::TIME_ZERO); + } + } + } + + // Connect sectors with portals + for (const auto &path_type : this->grid_lookup) { + auto grid = this->pathfinder->get_grid(path_type.second); + grid->init_portals(); + grid->init_portal_nodes(); + } +} + +const util::Vector2s &Map::get_size() const { + return this->terrain->get_size(); +} + +const std::shared_ptr &Map::get_terrain() const { + return this->terrain; +} + +const std::shared_ptr &Map::get_pathfinder() const { + return this->pathfinder; +} + +path::grid_id_t Map::get_grid_id(const nyan::fqon_t &path_grid) const { + return this->grid_lookup.at(path_grid); +} + +} // namespace openage::gamestate diff --git a/libopenage/gamestate/map.h b/libopenage/gamestate/map.h new file mode 100644 index 0000000000..2817f74457 --- /dev/null +++ b/libopenage/gamestate/map.h @@ -0,0 +1,86 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + +#include + +#include "pathfinding/types.h" +#include "util/vector.h" + + +namespace openage { +namespace path { +class Pathfinder; +} // namespace path + +namespace gamestate { +class GameState; +class Terrain; + +class Map { +public: + /** + * Create a new map from existing terrain. + * + * Initializes the pathfinder with the terrain path costs. + * + * @param state Game state. + * @param terrain Terrain object. + */ + Map(const std::shared_ptr &state, + const std::shared_ptr &terrain); + + ~Map() = default; + + /** + * Get the size of the map. + * + * @return Map size (width x height). + */ + const util::Vector2s &get_size() const; + + /** + * Get the terrain of the map. + * + * @return Terrain. + */ + const std::shared_ptr &get_terrain() const; + + /** + * Get the pathfinder for the map. + * + * @return Pathfinder. + */ + const std::shared_ptr &get_pathfinder() const; + + /** + * Get the grid ID associated with a nyan path grid object. + * + * @param path_grid Path grid object fqon. + * + * @return Grid ID. + */ + path::grid_id_t get_grid_id(const nyan::fqon_t &path_grid) const; + +private: + /** + * Terrain. + */ + std::shared_ptr terrain; + + /** + * Pathfinder. + */ + std::shared_ptr pathfinder; + + /** + * Lookup table for mapping path grid objects in nyan to grid indices. + */ + std::unordered_map grid_lookup; +}; + +} // namespace gamestate +} // namespace openage diff --git a/libopenage/gamestate/old/CMakeLists.txt b/libopenage/gamestate/old/CMakeLists.txt deleted file mode 100644 index d141c14607..0000000000 --- a/libopenage/gamestate/old/CMakeLists.txt +++ /dev/null @@ -1,15 +0,0 @@ -add_sources(libopenage - civilisation.cpp - cost.cpp - game_main.cpp - game_save.cpp - game_spec.cpp - generator.cpp - market.cpp - player.cpp - population_tracker.cpp - resource.cpp - score.cpp - team.cpp - types.cpp -) diff --git a/libopenage/gamestate/old/civilisation.cpp b/libopenage/gamestate/old/civilisation.cpp deleted file mode 100644 index 6139ed23fe..0000000000 --- a/libopenage/gamestate/old/civilisation.cpp +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2015-2018 the openage authors. See copying.md for legal info. - -#include "civilisation.h" - -#include "../../log/log.h" -#include "../../unit/unit_type.h" - - -namespace openage { - -Civilisation::Civilisation(const GameSpec &spec, int id) - : - civ_id{id}, - civ_name{spec.get_civ_name(id)} { - this->initialise_unit_types(spec); -} - - -std::vector> Civilisation::object_meta() const { - return civ_objects; -} - - -std::vector Civilisation::get_category(const std::string &c) const { - auto cat = this->categories.find(c); - if (cat == this->categories.end()) { - return std::vector(); - } - return cat->second; -} - - -std::vector Civilisation::get_type_categories() const { - return this->all_categories; -} - - -const gamedata::building_unit *Civilisation::get_building_data(index_t unit_id) const { - if (this->buildings.count(unit_id) == 0) { - log::log(MSG(info) << " -> ignoring unit_id: " << unit_id); - return nullptr; - } - return this->buildings.at(unit_id); -} - - -void Civilisation::initialise_unit_types(const GameSpec &spec) { - log::log(MSG(dbg) << "Init units of civilisation " << civ_name); - spec.create_unit_types(this->civ_objects, this->civ_id); - for (auto &type : this->civ_objects) { - this->add_to_category(type->name(), type->id()); - } -} - - -void Civilisation::add_to_category(const std::string &c, index_t type) { - if (this->categories.count(c) == 0) { - this->all_categories.push_back(c); - this->categories[c] = std::vector(); - } - this->categories[c].push_back(type); -} - - -} diff --git a/libopenage/gamestate/old/civilisation.h b/libopenage/gamestate/old/civilisation.h deleted file mode 100644 index 97045031ea..0000000000 --- a/libopenage/gamestate/old/civilisation.h +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include -#include - -#include "game_spec.h" - -namespace openage { - -/** - * contains the initial tech structure for - * one civilisation - */ -class Civilisation { -public: - Civilisation(const GameSpec &spec, int id); - - /** - * civ index - */ - const int civ_id; - - /** - * civ name - */ - const std::string civ_name; - - /** - * return all the objects available to this civ - */ - std::vector> object_meta() const; - - /** - * return all types in a particular named category - */ - std::vector get_category(const std::string &c) const; - - /** - * return all used categories, such as living, building or projectile - */ - std::vector get_type_categories() const; - - /** - * gamedata for a building - */ - const gamedata::building_unit *get_building_data(index_t unit_id) const; - - /** - * initialise the unit meta data - */ - void initialise_unit_types(const GameSpec &spec); - -private: - - /** - * creates and adds items to categories - */ - void add_to_category(const std::string &c, index_t type); - - /** - * unit types which can be produced by this civilisation. - */ - std::vector> civ_objects; - - /** - * all available categories of units - */ - std::vector all_categories; - - /** - * category lists - */ - std::unordered_map> categories; - - /** - * used for annex creation - */ - std::unordered_map buildings; - -}; - -} diff --git a/libopenage/gamestate/old/cost.cpp b/libopenage/gamestate/old/cost.cpp deleted file mode 100644 index 089cb6da4b..0000000000 --- a/libopenage/gamestate/old/cost.cpp +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2017-2021 the openage authors. See copying.md for legal info. - -#include "cost.h" -#include "player.h" - - -namespace openage { - -ResourceCost::ResourceCost() - : - type{cost_type::constant}, - resources{} {} - -ResourceCost::ResourceCost(const ResourceBundle& resources) - : - type{cost_type::constant}, - resources{} { - this->resources.set(resources); -} - -ResourceCost::ResourceCost(cost_type type, const ResourceBundle& multiplier) - : - type{type}, - resources{} { - this->resources.set(multiplier); -} - -ResourceCost::~ResourceCost() = default; - -void ResourceCost::set(cost_type type, const ResourceBundle& resources) { - this->type = type; - this->resources.set(resources); -} - -const ResourceBundle ResourceCost::get(const Player& player) const { - if (type == cost_type::constant) { - return resources; - } - - // calculate dynamic cost - ResourceBundle resources = this->resources.clone(); - if (type == cost_type::workforce) { - resources *= player.get_workforce_count(); - } - return resources; -} - -} // openage diff --git a/libopenage/gamestate/old/cost.h b/libopenage/gamestate/old/cost.h deleted file mode 100644 index 749aa50986..0000000000 --- a/libopenage/gamestate/old/cost.h +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2017-2019 the openage authors. See copying.md for legal info. - -#pragma once - -#include "resource.h" - - -namespace openage { - -class Player; - -/** - * Types of dynamic cost calculation (and one constant). - * - * Used in ResourceCost - * TODO use in TimeCost - */ -enum class cost_type : int { - /** Constant resources. */ - constant, - /** Dynamic cost based on the workforce. */ - workforce -}; - -/** - * A container for a constant or dynamic ResourceBundle representing the cost. - */ -class ResourceCost { -public: - - /** - * Constant zero cost - */ - ResourceCost(); - - /** - * Constant cost - */ - ResourceCost(const ResourceBundle& resources); - - /** - * Dynamic cost - */ - ResourceCost(cost_type type, const ResourceBundle& multiplier); - - virtual ~ResourceCost(); - - void set(cost_type type, const ResourceBundle& multiplier); - - /** - * Returns the cost. - */ - const ResourceBundle get(const Player& player) const; - -private: - - cost_type type; - - ResourceBundle resources; - -}; - -// TODO implement TimeCost - -} // namespace openage diff --git a/libopenage/gamestate/old/game_main.cpp b/libopenage/gamestate/old/game_main.cpp deleted file mode 100644 index bd26fe6e48..0000000000 --- a/libopenage/gamestate/old/game_main.cpp +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2014-2023 the openage authors. See copying.md for legal info. - -#include "game_main.h" - -#include "../../legacy_engine.h" -#include "../../log/log.h" -#include "../../terrain/terrain.h" -#include "../../unit/unit_type.h" -#include "game_spec.h" -#include "generator.h" - - -namespace openage { - -GameMain::GameMain(const Generator &generator) : - OptionNode{"GameMain"}, - terrain{generator.terrain()}, - placed_units{}, - spec{generator.get_spec()} { - // players - this->players.reserve(generator.player_names().size()); - unsigned int i = 0; - for (auto &name : generator.player_names()) { - this->players.push_back(std::make_shared(this->add_civ(i), i, name)); - i++; - } - - // initialise types only after all players are added - for (auto &p : this->players) { - p->initialise_unit_types(); - } - - // initialise units - this->placed_units.set_terrain(this->terrain); - generator.add_units(*this); -} - -GameMain::~GameMain() = default; - -unsigned int GameMain::player_count() const { - return this->players.size(); -} - -Player *GameMain::get_player(unsigned int player_id) { - return this->players.at(player_id).get(); -} - -unsigned int GameMain::team_count() const { - return this->teams.size(); -} - -Team *GameMain::get_team(unsigned int team_id) { - return &this->teams.at(team_id); -} - -GameSpec *GameMain::get_spec() { - return this->spec.get(); -} - -void GameMain::update(time_nsec_t lastframe_duration) { - this->placed_units.update_all(lastframe_duration); -} - -Civilisation *GameMain::add_civ(int civ_id) { - auto new_civ = std::make_shared(*this->spec, civ_id); - this->civs.emplace_back(new_civ); - return new_civ.get(); -} - -GameMainHandle::GameMainHandle(qtsdl::GuiItemLink *gui_link) : - game{}, - engine{}, - gui_link{gui_link} { -} - -void GameMainHandle::set_engine(LegacyEngine *engine) { - ENSURE(!this->engine || this->engine == engine, "relinking GameMain to another engine is not supported and not caught properly"); - this->engine = engine; -} - -void GameMainHandle::clear() { - if (this->engine) { - this->game = nullptr; - this->display->end_game(); - announce_running(); - } -} - -void GameMainHandle::set_game(std::unique_ptr &&game) { - if (this->engine) { - ENSURE(game, "linking game to engine problem"); - - // remember the pointer - this->game = game.get(); - - // then pass on the game to the engine - this->display->start_game(std::move(game)); - - announce_running(); - } -} - -GameMain *GameMainHandle::get_game() const { - return this->game; -} - -bool GameMainHandle::is_game_running() const { - return this->game != nullptr; -} - -void GameMainHandle::announce_running() { - emit this->gui_signals.game_running(this->game); -} - -} // namespace openage diff --git a/libopenage/gamestate/old/game_main.h b/libopenage/gamestate/old/game_main.h deleted file mode 100644 index 13d1ceb9d1..0000000000 --- a/libopenage/gamestate/old/game_main.h +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright 2014-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include -#include - -#include - -#include "../../options.h" -#include "../../presenter/legacy/legacy.h" -#include "../../terrain/terrain.h" -#include "../../unit/unit_container.h" -#include "../../util/timing.h" -#include "market.h" -#include "player.h" -#include "team.h" - -namespace openage { - -class LegacyEngine; -class Generator; -class Terrain; - - -/** - * Contains information for a single game - * This information must be synced across network clients - * - * TODO: include a list of actions to be saved - * as the game replay file - */ -class GameMain : public options::OptionNode { -public: - GameMain(const Generator &generator); - ~GameMain(); - - /** - * the number of players - */ - unsigned int player_count() const; - - /** - * player by index - */ - Player *get_player(unsigned int player_id); - - /** - * the number of teams - */ - unsigned int team_count() const; - - /** - * team by id - */ - Team *get_team(unsigned int team_id); - - /** - * the spec in this games settings - */ - GameSpec *get_spec(); - - /** - * updates the game by one frame - */ - void update(time_nsec_t lastframe_duration); - - /** - * map information - */ - std::shared_ptr terrain; - - /** - * all teams in the game - */ - std::vector teams; - - /** - * The global market (the global market prices). - */ - Market market; - - /** - * all the objects that have been placed. - */ - UnitContainer placed_units; - -private: - /** - * all players in the game - * no objects should be added of removed once populated - */ - std::vector> players; - - /** - * creates a random civ, owned and managed by this game - */ - Civilisation *add_civ(int civ_id); - - /** - * civs used in this game - */ - std::vector> civs; - - std::shared_ptr spec; -}; - -} // namespace openage - -namespace qtsdl { -class GuiItemLink; -} // namespace qtsdl - -namespace openage { - -class GameMainSignals : public QObject { - Q_OBJECT - -public: -signals: - void game_running(bool running); -}; - - -/** - * Class linked to the QML object "GameMain" via GameMainLink. - * Gets instanciated from QML. - */ -class GameMainHandle { -public: - explicit GameMainHandle(qtsdl::GuiItemLink *gui_link); - - void set_engine(LegacyEngine *engine); - - /** - * End the game and delete the game handle. - */ - void clear(); - - /** - * Pass the given game to the engine and start it. - */ - void set_game(std::unique_ptr &&game); - - /** - * Return the game. - */ - GameMain *get_game() const; - - /** - * Test if there is a game running. - */ - bool is_game_running() const; - - /** - * Emit a qt signal to notify for changes in a running game. - */ - void announce_running(); - -private: - /** - * The game state as currently owned by the engine, - * just remembered here to access it quickly. - */ - GameMain *game; - - /** - * The engine the main game handle is attached to. - */ - LegacyEngine *engine; - - /** - * The engine the main game handle is attached to. - */ - presenter::LegacyDisplay *display; - -public: - GameMainSignals gui_signals; - qtsdl::GuiItemLink *gui_link; -}; - -} // namespace openage diff --git a/libopenage/gamestate/old/game_save.cpp b/libopenage/gamestate/old/game_save.cpp deleted file mode 100644 index 47d598530b..0000000000 --- a/libopenage/gamestate/old/game_save.cpp +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright 2015-2021 the openage authors. See copying.md for legal info. - -#include "game_save.h" - -#include -#include - -#include "version.h" -#include "../../log/log.h" -#include "../../terrain/terrain_chunk.h" -#include "../../unit/producer.h" -#include "../../unit/unit.h" -#include "../../unit/unit_type.h" -#include "../../versions/compiletime.h" -#include "game_main.h" -#include "game_save.h" -#include "game_spec.h" - -namespace openage::gameio { - -void save_unit(std::ofstream &file, Unit *unit) { - file << unit->unit_type->id() << std::endl; - file << unit->get_attribute().player.player_number << std::endl; - coord::tile pos = unit->location->pos.start; - file << pos.ne << " " << pos.se << std::endl; - - bool has_building_attr = unit->has_attribute(attr_type::building); - file << has_building_attr << std::endl; - if (has_building_attr) { - file << unit->get_attribute().completed << std::endl; - } -} - -void load_unit(std::ifstream &file, GameMain *game) { - int pr_id; - int player_no; - coord::tile_t ne, se; - file >> pr_id; - file >> player_no; - file >> ne; - file >> se; - - UnitType &saved_type = *game->get_player(player_no)->get_type(pr_id); - auto ref = game->placed_units.new_unit(saved_type, *game->get_player(player_no), coord::tile{ne, se}.to_phys3(*game->terrain)); - - bool has_building_attr; - file >> has_building_attr; - if (has_building_attr) { - float completed; - file >> completed; - if (completed >= 1.0f && ref.is_valid()) { - complete_building(*ref.get()); - } - } -} - -void save_tile_content(std::ofstream &file, openage::TileContent *content) { - file << content->terrain_id << std::endl; - file << content->obj.size() << std::endl; -} - -TileContent load_tile_content(std::ifstream &file) { - openage::TileContent content; - file >> content.terrain_id; - - unsigned int o_size; - file >> o_size; - return content; -} - -void save(openage::GameMain *game, const std::string &fname) { - std::ofstream file(fname, std::ofstream::out); - log::log(MSG(dbg) << "saving " + fname); - - // metadata - file << save_label << std::endl; - file << save_version << std::endl; - file << versions::engine_version << std::endl; - - // how many chunks - std::vector used = game->terrain->used_chunks(); - file << used.size() << std::endl; - - // save each chunk - for (coord::chunk &position : used) { - file << position.ne << " " << position.se << std::endl; - openage::TerrainChunk *chunk = game->terrain->get_chunk(position); - - file << chunk->tile_count << std::endl; - for (size_t p = 0; p < chunk->tile_count; ++p) { - save_tile_content( file, chunk->get_data(p) ); - } - } - - // save units - std::vector units = game->placed_units.all_units(); - file << units.size() << std::endl; - for (Unit *u : units) { - save_unit(file, u); - } -} - -void load(openage::GameMain *game, const std::string &fname) { - std::ifstream file(fname, std::ifstream::in); - if (!file.good()) { - log::log(MSG(dbg) << "could not find " + fname); - return; - } - log::log(MSG(dbg) << "loading " + fname); - - // load metadata - std::string file_label; - file >> file_label; - if (file_label != save_label) { - log::log(MSG(warn) << fname << " is not a savefile"); - return; - } - std::string version; - file >> version; - if (version != save_version) { - log::log(MSG(warn) << "savefile has different version"); - } - std::string build; - file >> build; - - // read terrain chunks - unsigned int num_chunks; - file >> num_chunks; - for (unsigned int c = 0; c < num_chunks; ++c) { - coord::chunk_t ne, se; - size_t tile_count; - file >> ne; - file >> se; - file >> tile_count; - openage::TerrainChunk *chunk = game->terrain->get_create_chunk(coord::chunk{ne, se}); - for (size_t p = 0; p < tile_count; ++p) { - *chunk->get_data(p) = load_tile_content( file ); - } - } - - game->placed_units.reset(); - unsigned int num_units; - file >> num_units; - for (unsigned int u = 0; u < num_units; ++u) { - load_unit( file, game ); - } -} - -} // openage::gameio diff --git a/libopenage/gamestate/old/game_save.h b/libopenage/gamestate/old/game_save.h deleted file mode 100644 index 36ddb49b82..0000000000 --- a/libopenage/gamestate/old/game_save.h +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2015-2021 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -namespace openage { - -class GameMain; -class Terrain; - -namespace gameio { - -const std::string save_label = "openage-save-file"; -const std::string save_version = "v0.1"; - -/** - * a game save function that sometimes works - */ -void save(openage::GameMain *, const std::string &fname); - -/** - * a game load function that sometimes works - */ -void load(openage::GameMain *, const std::string &fname); - -}} // openage::gameio diff --git a/libopenage/gamestate/old/game_spec.cpp b/libopenage/gamestate/old/game_spec.cpp deleted file mode 100644 index c35528d62f..0000000000 --- a/libopenage/gamestate/old/game_spec.cpp +++ /dev/null @@ -1,523 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#include "game_spec.h" - -#include - -#include "../../audio/error.h" -#include "../../audio/resource_def.h" -#include "../../legacy_engine.h" -#include "../../gamedata/blending_mode_dummy.h" -#include "../../gamedata/string_resource_dummy.h" -#include "../../gamedata/terrain_dummy.h" -#include "../../log/log.h" -#include "../../rng/global_rng.h" -#include "../../unit/producer.h" -#include "../../util/compiler.h" -#include "../../util/strings.h" -#include "../../util/timer.h" -#include "assets/legacy_assetmanager.h" -#include "civilisation.h" - - -namespace openage { - -GameSpec::GameSpec(LegacyAssetManager *am) : - assetmanager{am}, - gamedata_loaded{false} { -} - -GameSpec::~GameSpec() = default; - - -bool GameSpec::initialize() { - util::Timer load_timer; - load_timer.start(); - - const util::Path &asset_dir = this->assetmanager->get_asset_dir(); - - log::log(MSG(info) << "Loading game specification files..."); - - std::vector string_resources = util::read_csv_file( - asset_dir["converted/string_resources.docx"]); - - try { - // read the packed csv file - util::CSVCollection raw_gamedata{ - asset_dir["converted/gamedata/gamedata.docx"]}; - - // parse the original game description files - this->gamedata = raw_gamedata.read( - "gamedata-empiresdat.docx"); - - this->load_terrain(this->gamedata[0]); - - // process and load the game description files - this->on_gamedata_loaded(this->gamedata[0]); - this->gamedata_loaded = true; - } - catch (Error &exc) { - // rethrow allmighty openage exceptions - throw; - } - catch (std::exception &exc) { - // unfortunately we have no idea of the std::exception backtrace - throw Error{ERR << "gamedata could not be loaded: " - << util::typestring(exc) - << ": " << exc.what()}; - } - - log::log(MSG(info).fmt("Loading time [data]: %5.3f s", - load_timer.getval() / 1e9)); - return true; -} - -bool GameSpec::load_complete() const { - return this->gamedata_loaded; -} - -terrain_meta *GameSpec::get_terrain_meta() { - return &this->terrain_data; -} - -index_t GameSpec::get_slp_graphic(index_t slp) { - return this->slp_to_graphic[slp]; -} - -Texture *GameSpec::get_texture(index_t graphic_id) const { - if (graphic_id <= 0 || this->graphics.count(graphic_id) == 0) { - log::log(MSG(dbg) << " -> ignoring graphics_id: " << graphic_id); - return nullptr; - } - - auto g = this->graphics.at(graphic_id); - int slp_id = g->slp_id; - if (slp_id <= 0) { - log::log(MSG(dbg) << " -> ignoring negative slp_id: " << slp_id); - return nullptr; - } - - log::log(MSG(dbg) << " slp id/name: " << slp_id << " " << g->name); - std::string tex_fname = util::sformat("converted/graphics/%d.slp.png", slp_id); - - return this->get_texture(tex_fname, true); -} - -Texture *GameSpec::get_texture(const std::string &file_name, bool use_metafile) const { - // return nullptr if the texture wasn't found (3rd param) - return this->assetmanager->get_texture(file_name, use_metafile, true); -} - -std::shared_ptr GameSpec::get_unit_texture(index_t unit_id) const { - if (this->unit_textures.count(unit_id) == 0) { - if (unit_id > 0) { - log::log(MSG(dbg) << " -> ignoring unit_id: " << unit_id); - } - return nullptr; - } - return this->unit_textures.at(unit_id); -} - -const Sound *GameSpec::get_sound(index_t sound_id) const { - if (this->available_sounds.count(sound_id) == 0) { - if (sound_id > 0) { - log::log(MSG(dbg) << " -> ignoring sound_id: " << sound_id); - } - return nullptr; - } - return &this->available_sounds.at(sound_id); -} - - -const gamedata::graphic *GameSpec::get_graphic_data(index_t grp_id) const { - if (this->graphics.count(grp_id) == 0) { - log::log(MSG(dbg) << " -> ignoring grp_id: " << grp_id); - return nullptr; - } - return this->graphics.at(grp_id); -} - -std::vector GameSpec::get_command_data(index_t unit_id) const { - if (this->commands.count(unit_id) == 0) { - return std::vector(); // empty vector - } - return this->commands.at(unit_id); -} - -std::string GameSpec::get_civ_name(int civ_id) const { - return this->gamedata[0].civs.data[civ_id].name; -} - -void GameSpec::create_unit_types(unit_meta_list &objects, int civ_id) const { - if (!this->load_complete()) { - return; - } - - // create projectile types first - for (auto &obj : this->gamedata[0].civs.data[civ_id].units.missile.data) { - this->load_missile(obj, objects); - } - - // create object unit types - for (auto &obj : this->gamedata[0].civs.data[civ_id].units.object.data) { - this->load_object(obj, objects); - } - - // create dead unit types - for (auto &unit : this->gamedata[0].civs.data[civ_id].units.moving.data) { - this->load_object(unit, objects); - } - - // create living unit types - for (auto &unit : this->gamedata[0].civs.data[civ_id].units.living.data) { - this->load_living(unit, objects); - } - - // create building unit types - for (auto &building : this->gamedata[0].civs.data[civ_id].units.building.data) { - this->load_building(building, objects); - } -} - - -LegacyAssetManager *GameSpec::get_asset_manager() const { - return this->assetmanager; -} - - -void GameSpec::on_gamedata_loaded(const gamedata::empiresdat &gamedata) { - const util::Path &asset_dir = this->assetmanager->get_asset_dir(); - util::Path sound_dir = asset_dir["converted/sounds"]; - - // create graphic id => graphic map - for (auto &graphic : gamedata.graphics.data) { - this->graphics[graphic.graphic_id] = &graphic; - this->slp_to_graphic[graphic.slp_id] = graphic.graphic_id; - } - - log::log(INFO << "Loading textures..."); - - // create complete set of unit textures - for (auto &g : this->graphics) { - this->unit_textures.insert({g.first, std::make_shared(*this, g.second)}); - } - - log::log(INFO << "Loading sounds..."); - - // playable sound files for the audio manager - std::vector load_sound_files; - - // all sounds defined in the game specification - for (const gamedata::sound &sound : gamedata.sounds.data) { - std::vector sound_items; - - // each sound may have multiple variation, - // processed in this loop - // these are the single sound files. - for (const gamedata::sound_item &item : sound.sound_items.data) { - if (item.resource_id < 0) { - log::log(SPAM << " Invalid sound resource id < 0"); - continue; - } - - std::string snd_filename = util::sformat("%d.opus", item.resource_id); - util::Path snd_path = sound_dir[snd_filename]; - - if (not snd_path.is_file()) { - continue; - } - - // single items for a sound (so that we can ramdomize it) - sound_items.push_back(item.resource_id); - - // the single sound will be loaded in the audio system. - audio::resource_def resource{ - audio::category_t::GAME, - item.resource_id, - snd_path, - audio::format_t::OPUS, - audio::loader_policy_t::DYNAMIC}; - load_sound_files.push_back(resource); - } - - - // create test sound objects that can be played later - this->available_sounds.insert({sound.sound_id, - Sound{ - this, - std::move(sound_items)}}); - } - - // TODO: move out the loading of the sound. - // this class only provides the names and locations - - // load the requested sounds. - audio::AudioManager &am = this->assetmanager->get_display()->get_audio_manager(); - am.load_resources(load_sound_files); - - // this final step occurs after loading media - // as producers require both the graphics and sounds - this->create_abilities(gamedata); -} - -bool GameSpec::valid_graphic_id(index_t graphic_id) const { - if (graphic_id <= 0 || this->graphics.count(graphic_id) == 0) { - return false; - } - if (this->graphics.at(graphic_id)->slp_id <= 0) { - return false; - } - return true; -} - -void GameSpec::load_building(const gamedata::building_unit &building, unit_meta_list &list) const { - // check graphics - if (this->valid_graphic_id(building.idle_graphic0)) { - auto meta_type = std::make_shared("Building", building.id0, [this, &building](const Player &owner) { - return std::make_shared(owner, *this, &building); - }); - list.emplace_back(meta_type); - } -} - -void GameSpec::load_living(const gamedata::living_unit &unit, unit_meta_list &list) const { - // check graphics - if (this->valid_graphic_id(unit.dying_graphic) && this->valid_graphic_id(unit.idle_graphic0) && this->valid_graphic_id(unit.move_graphics)) { - auto meta_type = std::make_shared("Living", unit.id0, [this, &unit](const Player &owner) { - return std::make_shared(owner, *this, &unit); - }); - list.emplace_back(meta_type); - } -} - -void GameSpec::load_object(const gamedata::unit_object &object, unit_meta_list &list) const { - // check graphics - if (this->valid_graphic_id(object.idle_graphic0)) { - auto meta_type = std::make_shared("Object", object.id0, [this, &object](const Player &owner) { - return std::make_shared(owner, *this, &object); - }); - list.emplace_back(meta_type); - } -} - -void GameSpec::load_missile(const gamedata::missile_unit &proj, unit_meta_list &list) const { - // check graphics - if (this->valid_graphic_id(proj.idle_graphic0)) { - auto meta_type = std::make_shared("Projectile", proj.id0, [this, &proj](const Player &owner) { - return std::make_shared(owner, *this, &proj); - }); - list.emplace_back(meta_type); - } -} - - -void GameSpec::load_terrain(const gamedata::empiresdat &gamedata) { - // fetch blending modes - util::Path convert_dir = this->assetmanager->get_asset_dir()["converted"]; - std::vector blending_meta = util::read_csv_file( - convert_dir["blending_modes.docx"]); - - // copy the terrain metainformation - std::vector terrain_meta = gamedata.terrains.data; - - // remove any disabled textures - terrain_meta.erase( - std::remove_if( - terrain_meta.begin(), - terrain_meta.end(), - [](const gamedata::terrain_type &t) { - return not t.enabled; - }), - terrain_meta.end()); - - // result attributes - this->terrain_data.terrain_id_count = terrain_meta.size(); - this->terrain_data.blendmode_count = blending_meta.size(); - this->terrain_data.textures.resize(terrain_data.terrain_id_count); - this->terrain_data.blending_masks.reserve(terrain_data.blendmode_count); - this->terrain_data.terrain_id_priority_map = std::make_unique( - this->terrain_data.terrain_id_count); - this->terrain_data.terrain_id_blendmode_map = std::make_unique( - this->terrain_data.terrain_id_count); - this->terrain_data.influences_buf = std::make_unique( - this->terrain_data.terrain_id_count); - - - log::log(MSG(dbg) << "Terrain prefs: " - << "tiletypes=" << terrain_data.terrain_id_count << ", " - "blendmodes=" - << terrain_data.blendmode_count); - - // create tile textures (snow, ice, grass, whatever) - for (size_t terrain_id = 0; - terrain_id < terrain_data.terrain_id_count; - terrain_id++) { - auto line = &terrain_meta[terrain_id]; - - // TODO: terrain double-define check? - terrain_data.terrain_id_priority_map[terrain_id] = line->blend_priority; - terrain_data.terrain_id_blendmode_map[terrain_id] = line->blend_mode; - - // TODO: remove hardcoding and rely on nyan data - auto terraintex_filename = util::sformat("converted/terrain/%d.slp.png", - line->slp_id); - - auto new_texture = this->assetmanager->get_texture(terraintex_filename, true); - - terrain_data.textures[terrain_id] = new_texture; - } - - // create blending masks (see doc/media/blendomatic) - for (size_t i = 0; i < terrain_data.blendmode_count; i++) { - auto line = &blending_meta[i]; - - // TODO: remove hardcodingn and use nyan data - std::string mask_filename = util::sformat("converted/blendomatic/mode%02d.png", - line->blend_mode); - terrain_data.blending_masks[i] = this->assetmanager->get_texture(mask_filename); - } -} - - -void GameSpec::create_abilities(const gamedata::empiresdat &gamedata) { - // use game data unit commands - int headers = gamedata.unit_headers.data.size(); - int total = 0; - - // it seems the index of the header indicates the unit - for (int i = 0; i < headers; ++i) { - // init unit command vector - std::vector list; - - // add each element - auto &head = gamedata.unit_headers.data[i]; - for (auto &cmd : head.unit_commands.data) { - total++; - - // commands either have a class id or a unit id - // log::dbg("unit command %d %d -> class %d, unit %d, resource %d", i, cmd.id, cmd.class_id, cmd.unit_id, cmd.resource_in); - list.push_back(&cmd); - } - - // insert to command map - this->commands[i] = list; - } -} - - -void Sound::play() const { - if (this->sound_items.size() <= 0) { - return; - } - - int rand = rng::random_range(0, this->sound_items.size()); - int sndid = this->sound_items.at(rand); - - try { - // TODO: buhuuuu gnargghh this has to be moved to the asset loading subsystem hnnnng - audio::AudioManager &am = this->game_spec->get_asset_manager()->get_display()->get_audio_manager(); - - if (not am.is_available()) { - return; - } - - audio::Sound sound = am.get_sound(audio::category_t::GAME, sndid); - sound.play(); - } - catch (audio::Error &e) { - log::log(MSG(warn) << "cannot play: " << e); - } -} - -GameSpecHandle::GameSpecHandle(qtsdl::GuiItemLink *gui_link) : - active{}, - asset_manager{}, - gui_signals{std::make_shared()}, - gui_link{gui_link} { -} - -void GameSpecHandle::set_active(bool active) { - this->active = active; - - this->start_loading_if_needed(); -} - -void GameSpecHandle::set_asset_manager(LegacyAssetManager *asset_manager) { - if (this->asset_manager != asset_manager) { - this->asset_manager = asset_manager; - - this->start_loading_if_needed(); - } -} - -bool GameSpecHandle::is_ready() const { - return this->spec && this->spec->load_complete(); -} - -void GameSpecHandle::invalidate() { - this->spec = nullptr; - - if (this->asset_manager) - this->asset_manager->check_updates(); - - this->start_loading_if_needed(); -} - -void GameSpecHandle::announce_spec() { - if (this->spec && this->spec->load_complete()) - emit this->gui_signals->game_spec_loaded(this->spec); -} - -std::shared_ptr GameSpecHandle::get_spec() { - return this->spec; -} - -void GameSpecHandle::start_loading_if_needed() { - if (this->active && this->asset_manager && !this->spec) { - // create the game specification - this->spec = std::make_shared(this->asset_manager); - - // the load the data - this->start_load_job(); - } -} - -void GameSpecHandle::start_load_job() { - // store the shared pointers in another sharedptr - // so we can pass them to the other thread - auto spec_and_job = std::make_tuple(this->spec, this->gui_signals, job::Job{}); - auto spec_and_job_ptr = std::make_shared(spec_and_job); - - // lambda to be executed to actually load the data files. - auto perform_load = [spec_and_job_ptr] { - return std::get>(*spec_and_job_ptr)->initialize(); - }; - - auto load_finished = [gui_signals_ptr = this->gui_signals.get()](job::result_function_t result) { - bool load_ok; - try { - load_ok = result(); - } - catch (Error &) { - // TODO: display that error in the ui. - throw; - } - catch (std::exception &) { - // TODO: same here. - throw Error{ERR << "gamespec loading failed!"}; - } - - if (load_ok) { - // send the signal that the load job was finished - emit gui_signals_ptr->load_job_finished(); - } - }; - - job::JobManager *job_mgr = this->asset_manager->get_engine()->get_job_manager(); - - std::get>(*spec_and_job_ptr) = job_mgr->enqueue( - perform_load, - load_finished); -} - -} // namespace openage diff --git a/libopenage/gamestate/old/game_spec.h b/libopenage/gamestate/old/game_spec.h deleted file mode 100644 index c5a9a66348..0000000000 --- a/libopenage/gamestate/old/game_spec.h +++ /dev/null @@ -1,330 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include "../../gamedata/gamedata_dummy.h" -#include "../../gamedata/graphic_dummy.h" -#include "../../job/job.h" -#include "../../unit/unit_texture.h" -#include "../../util/csv.h" -#include "terrain/terrain.h" -#include "types.h" - -#include -#include -#include - - -namespace openage { - -class LegacyAssetManager; -class GameSpec; -class UnitType; -class UnitTypeMeta; -class Player; - - -/** - * could use unique ptr - */ -using unit_type_list = std::vector>; -using unit_meta_list = std::vector>; - - -/** - * simple sound object - * TODO: move to assetmanager - */ -class Sound { -public: - Sound(GameSpec *spec, std::vector &&sound_items) : - sound_items{sound_items}, - game_spec{spec} {} - - void play() const; - - std::vector sound_items; - - GameSpec *game_spec; -}; - - -/** - * GameSpec gives a collection of all game elements - * this currently includes unit types and terrain types - * This provides a system which can easily allow game modding - * - * uses the LegacyAssetManager to gather - * graphic data, composite textures and sounds. - * - * all types are sorted and stored by id values, - * each data object is referenced by a type and id pair - * - * dealing directly with files done by asset manager - * TODO: should the audio loading should be moved there? - */ -class GameSpec { -public: - GameSpec(LegacyAssetManager *am); - virtual ~GameSpec(); - - /** - * perform the main loading job. - * this loads all the data into the storage. - */ - bool initialize(); - - /** - * Check if loading has been completed, - * a load percent would be nice - */ - bool load_complete() const; - - /** - * return data used for constructing terrain objects - */ - terrain_meta *get_terrain_meta(); - - /** - * reverse lookup of slp - */ - index_t get_slp_graphic(index_t slp); - - /** - * lookup using a texture id, this specifically avoids returning the missing placeholder texture - */ - Texture *get_texture(index_t graphic_id) const; - - /** - * lookup using a texture file name - */ - Texture *get_texture(const std::string &file_name, bool use_metafile = true) const; - - /** - * get unit texture by graphic id -- this is an directional texture - * which also includes graphic deltas - */ - std::shared_ptr get_unit_texture(index_t graphic_id) const; - - /** - * get sound by sound id - */ - const Sound *get_sound(index_t sound_id) const; - - /** - * gamedata for a graphic - * nyan will have to replace this somehow - */ - const gamedata::graphic *get_graphic_data(index_t grp_id) const; - - /** - * get available commands for a unit id - * nyan will have to replace this somehow - */ - std::vector get_command_data(index_t unit_id) const; - - /** - * returns the name of a civ by index - */ - std::string get_civ_name(int civ_id) const; - - /** - * makes initial unit types for a particular civ id - */ - void create_unit_types(unit_meta_list &objects, int civ_id) const; - - /** - * Return the asset manager used for loading resources - * of this game specification. - */ - LegacyAssetManager *get_asset_manager() const; - -private: - /** - * check graphic id is valid - */ - bool valid_graphic_id(index_t) const; - - /** - * create unit abilities from game data - */ - void create_abilities(const gamedata::empiresdat &gamedata); - - /** - * loads required assets to construct a buildings. - * adds to the type list if the object can be created safely. - */ - void load_building(const gamedata::building_unit &, unit_meta_list &) const; - - /** - * loads assets for living things. - */ - void load_living(const gamedata::living_unit &, unit_meta_list &) const; - - /** - * load assets for other game objects (not building and living). - */ - void load_object(const gamedata::unit_object &, unit_meta_list &) const; - - /** - * load missile assets. - */ - void load_missile(const gamedata::missile_unit &, unit_meta_list &) const; - - /** - * fill in the terrain_data attribute of this - */ - void load_terrain(const gamedata::empiresdat &gamedata); - - /** - * Invoked when the gamedata has been loaded. - */ - void on_gamedata_loaded(const gamedata::empiresdat &gamedata); - - /** - * Asset management entity that is responsible for textures, sounds, etc. - */ - LegacyAssetManager *assetmanager; - - /** - * The full original gamedata tree. - */ - std::vector gamedata; - - /** - * data used for constructing terrain objects - */ - terrain_meta terrain_data; - - /** - * slp to graphic id reverse lookup - */ - std::unordered_map slp_to_graphic; - - /** - * map graphic id to gamedata graphic. - */ - std::unordered_map graphics; - - /** - * commands available for each unit id - */ - std::unordered_map> commands; - - /** - * graphic ids -> unit texture for that id - */ - std::unordered_map> unit_textures; - - /** - * sound ids mapped to playable sounds for all available sounds. - */ - std::unordered_map available_sounds; - - /** - * has game data been load yet - */ - bool gamedata_loaded; -}; - -} // namespace openage - -namespace qtsdl { -class GuiItemLink; -} // namespace qtsdl - -namespace openage { - -class GameSpecSignals; - -/** - * Game specification instanciated in QML. - * Linked to the "GameSpec" QML type. - * - * Wraps the "GameSpec" C++ class from above. - */ -class GameSpecHandle { -public: - explicit GameSpecHandle(qtsdl::GuiItemLink *gui_link); - - /** - * Control whether this specification can be loaded (=true) - * or will not be loaded (=false). - */ - void set_active(bool active); - - /** - * invoked from qml when the asset_manager member is set. - */ - void set_asset_manager(LegacyAssetManager *asset_manager); - - /** - * Return if the specification was fully loaded. - */ - bool is_ready() const; - - /** - * forget everything about the specification and - * reload it with `start_loading_if_needed`. - */ - void invalidate(); - - /** - * signal about a loaded spec if any - */ - void announce_spec(); - - /** - * Return the contained game specification. - */ - std::shared_ptr get_spec(); - -private: - /** - * load the game specification if not already present. - */ - void start_loading_if_needed(); - - /** - * Actually dispatch the loading job to the job manager. - */ - void start_load_job(); - - /** - * called from the job manager when the loading job finished. - */ - void on_loaded(job::result_function_t result); - - /** - * The real game specification. - */ - std::shared_ptr spec; - - /** - * enables the loading of the game specification. - */ - bool active; - - LegacyAssetManager *asset_manager; - -public: - std::shared_ptr gui_signals; - qtsdl::GuiItemLink *gui_link; -}; - -class GameSpecSignals : public QObject { - Q_OBJECT - -public: -signals: - /* - * Some load job has finished. - * - * To be sure that the latest result is used, do the verification at the point of use. - */ - void load_job_finished(); - - void game_spec_loaded(std::shared_ptr loaded_game_spec); -}; - -} // namespace openage diff --git a/libopenage/gamestate/old/generator.cpp b/libopenage/gamestate/old/generator.cpp deleted file mode 100644 index b60ce1ffd7..0000000000 --- a/libopenage/gamestate/old/generator.cpp +++ /dev/null @@ -1,323 +0,0 @@ -// Copyright 2015-2021 the openage authors. See copying.md for legal info. - -#include "generator.h" - -#include "../../log/log.h" -#include "../../rng/rng.h" -#include "../../terrain/terrain_chunk.h" -#include "../../unit/unit.h" -#include "../../util/math_constants.h" -#include "game_main.h" -#include "game_save.h" -#include "game_spec.h" - - -namespace openage { - -coord::tile random_tile(rng::RNG &rng, tileset_t tiles) { - if (tiles.empty()) { - log::log(MSG(err) << "random tile failed"); - return coord::tile{0, 0}; - } - uint64_t index = rng.random() % tiles.size(); - auto it = std::begin(tiles); - std::advance(it, index); - return *it; -} - - -Region::Region(int size) - : - owner{0}, - object_id{0}, - terrain_id{0}, - center{0, 0} { - for (int ne = -size; ne < size; ++ne) { - for (int se = -size; se < size; ++se) { - this->tiles.emplace(coord::tile{ne, se}); - } - } -} - -Region::Region(coord::tile center, tileset_t tiles) - : - owner{0}, - object_id{0}, - terrain_id{0}, - center(center), - tiles{tiles} { -} - -tileset_t Region::get_tiles() const { - return this->tiles; -} - -coord::tile Region::get_center() const { - return this->center; -} - -coord::tile Region::get_tile(rng::RNG &rng) const { - return random_tile(rng, this->tiles); -} - - -tileset_t Region::subset(rng::RNG &rng, coord::tile start_point, unsigned int number, double p) const { - if (p == 0.0) { - return tileset_t(); - } - - // the set of included tiles - std::unordered_set subtiles; - subtiles.emplace(start_point); - - // outside layer of tiles - std::unordered_set edge_set; - - while (subtiles.size() < number) { - if (edge_set.empty()) { - - // try fill the edge list - for (auto &t : subtiles) { - - // check adjacent tiles - for (int i = 0; i < 4; ++i) { - coord::tile adj = t + neigh_tiles[i]; - if (this->tiles.count(adj) && - !subtiles.count(adj)) { - edge_set.emplace(adj); - } - } - } - if (edge_set.empty()) { - - // unable to grow further - return subtiles; - } - } - - // transfer a random tile - coord::tile next_tile = random_tile(rng, edge_set); - edge_set.erase(next_tile); - if (rng.probability(p)) { - subtiles.emplace(next_tile); - } - } - return subtiles; -} - -Region Region::take_tiles(rng::RNG &rng, coord::tile start_point, unsigned int number, double p) { - - tileset_t new_set = this->subset(rng, start_point, number, p); - - // erase from current set - for (auto &t: new_set) { - this->tiles.erase(t); - } - - Region new_region(start_point, new_set); - new_region.terrain_id = this->terrain_id; - return new_region; -} - -Region Region::take_random(rng::RNG &rng, unsigned int number, double p) { - return this->take_tiles(rng, this->get_tile(rng), number, p); -} - -Generator::Generator(qtsdl::GuiItemLink *gui_link) - : - gui_link{gui_link} -{ - this->setv("generation_seed", 4321); - this->setv("terrain_size", 2); - this->setv("terrain_base_id", 0); - this->setv("player_area", 850); - this->setv("player_radius", 10); - this->setv("load_filename", "/tmp/default_save.oas"); - this->setv("from_file", false); - // TODO pick the users name - this->set_csv("player_names", std::vector{"Jonas", "Michael"}); -} - -std::shared_ptr Generator::get_spec() const { - return this->spec; -} - -std::vector Generator::player_names() const { - auto result = this->get_csv("player_names"); - - // gaia is player 0 - result.insert(result.begin(), "Gaia"); - - return result; -} - -void Generator::create_regions() { - - // get option settings - int seed = this->getv("generation_seed"); - int size = this->getv("terrain_size"); - int base_id = this->getv("terrain_base_id"); - int p_area = this->getv("player_area"); - int p_radius = this->getv("player_radius"); - - // enforce some lower limits - size = std::max(1, size); - base_id = std::max(0, base_id); - p_area = std::max(50, p_area); - p_radius = std::max(2, p_radius); - - rng::RNG rng(seed); - Region base(size * 16); - base.terrain_id = base_id; - std::vector player_regions; - - int player_count = this->player_names().size() - 1; - for (int i = 0; i < player_count; ++i) { - log::log(MSG(dbg) << "generate player " << i); - - // space players in a circular pattern - double angle = static_cast(i) / static_cast(player_count); - int ne = size * p_radius * sin(math::TAU * angle); - int se = size * p_radius * cos(math::TAU * angle); - coord::tile player_tile{ne, se}; - - Region player = base.take_tiles(rng, player_tile, p_area, 0.5); - player.terrain_id = 10; - - Region obj_space = player.take_tiles(rng, player.get_center(), p_area / 5, 0.5); - obj_space.owner = i + 1; - obj_space.terrain_id = 8; - - Region trees1 = player.take_random(rng, p_area / 10, 0.3); - trees1.terrain_id = 9; - trees1.object_id = 349; - - Region trees2 = player.take_random(rng, p_area / 10, 0.3); - trees2.terrain_id = 9; - trees2.object_id = 351; - - Region stone = player.take_random(rng, 5, 0.3); - stone.object_id = 102; - - Region gold = player.take_random(rng, 7, 0.3); - gold.object_id = 66; - - Region forage = player.take_random(rng, 6, 0.3); - forage.object_id = 59; - - Region sheep = player.take_random(rng, 4, 0.3); - sheep.owner = obj_space.owner; - sheep.object_id = 594; - - player_regions.push_back(player); - player_regions.push_back(obj_space); - player_regions.push_back(trees1); - player_regions.push_back(trees2); - player_regions.push_back(stone); - player_regions.push_back(gold); - player_regions.push_back(forage); - player_regions.push_back(sheep); - } - - for (int i = 0; i < 6; ++i) { - Region extra_trees = base.take_random(rng, 160, 0.3); - extra_trees.terrain_id = 9; - extra_trees.object_id = 349; - player_regions.push_back(extra_trees); - } - - // set regions - this->regions.clear(); - this->regions.push_back(base); - for (auto &r : player_regions) { - this->regions.push_back(r); - } -} - - -std::shared_ptr Generator::terrain() const { - auto terrain = std::make_shared(this->spec->get_terrain_meta(), true); - for (auto &r : this->regions) { - for (auto &tile : r.get_tiles()) { - TerrainChunk *chunk = terrain->get_create_chunk(tile); - chunk->get_data(tile.get_pos_on_chunk())->terrain_id = r.terrain_id; - } - } - - // mark the 0, 0 tile. - coord::tile debug_tile_pos{0, 0}; - terrain->get_data(debug_tile_pos)->terrain_id = 6; - return terrain; -} - -void Generator::add_units(GameMain &m) const { - for (auto &r : this->regions) { - - // Regions filled with resource objects - // trees / mines - if (r.object_id) { - Player* p = m.get_player(r.owner); - auto otype = p->get_type(r.object_id); - if (!otype) { - break; - } - for (auto &tile : r.get_tiles()) { - m.placed_units.new_unit(*otype, *p, tile.to_phys3(*m.terrain)); - } - } - - // A space for starting town center and villagers - else if (r.owner) { - Player* p = m.get_player(r.owner); - auto tctype = p->get_type(109); // town center - auto mvtype = p->get_type(83); // male villager - auto fvtype = p->get_type(293); // female villager - auto sctype = p->get_type(448); // scout cavarly - if (!tctype || !mvtype || !fvtype || !sctype) { - break; - } - - coord::tile tile = r.get_center(); - tile.ne -= 1; - tile.se -= 1; - - // Place a completed town center - auto ref = m.placed_units.new_unit(*tctype, *p, tile.to_phys3(*m.terrain)); - if (ref.is_valid()) { - complete_building(*ref.get()); - } - - // Place three villagers - tile.ne -= 1; - m.placed_units.new_unit(*fvtype, *p, tile.to_phys3(*m.terrain)); - tile.se += 1; - m.placed_units.new_unit(*mvtype, *p, tile.to_phys3(*m.terrain)); - tile.se += 1; - m.placed_units.new_unit(*fvtype, *p, tile.to_phys3(*m.terrain)); - // TODO uncomment when the scout looks better - //tile.se += 2; - //m.placed_units.new_unit(*sctype, *p, tile.to_tile3().to_phys3()); - } - } -} - -std::unique_ptr Generator::create(std::shared_ptr spec) { - ENSURE(spec->load_complete(), "spec hasn't been checked or was invalidated"); - this->spec = spec; - - if (this->getv("from_file")) { - // create an empty game - this->regions.clear(); - - auto game = std::make_unique(*this); - gameio::load(game.get(), this->getv("load_filename")); - - return game; - } else { - // generation - this->create_regions(); - return std::make_unique(*this); - } -} - -} // namespace openage diff --git a/libopenage/gamestate/old/generator.h b/libopenage/gamestate/old/generator.h deleted file mode 100644 index a2d4bf58b7..0000000000 --- a/libopenage/gamestate/old/generator.h +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -#include "../../coord/tile.h" -#include "../../gui/guisys/public/gui_property_map.h" - -namespace qtsdl { -class GuiItemLink; -} // qtsdl - -namespace openage { - -class GameSpec; -class Terrain; -class GameMain; - -namespace rng { -class RNG; -} // openage::rng - -/** - * the type to store a set of tiles - */ -using tileset_t = std::unordered_set; - -/** - * picks a random tile from a set - */ -coord::tile random_tile(rng::RNG &rng, tileset_t tiles); - -/** - * the four directions available for 2d tiles - */ -constexpr coord::tile_delta const neigh_tiles[] = { - { 1, 0}, - {-1, 0}, - { 0, 1}, - { 0, -1} -}; - -/** - * A region is a set of tiles around a starting point, - * including functions to create child regions - */ -class Region { -public: - /** - * a square of tiles ranging from - * {-size, -size} to {size, size} - */ - Region(int size); - - /** - * a specified set of tiles - */ - Region(coord::tile center, tileset_t tiles); - - /** - * all tiles in this region - */ - tileset_t get_tiles() const; - - /** - * the center point of the region - */ - coord::tile get_center() const; - - /** - * picks a random tile from this subset - */ - coord::tile get_tile(rng::RNG &rng) const; - - /** - * find a group of tiles inside this region, number is the number of tiles to be contained - * in the subset, p is a probability between 0.0 and 1.0 which produces various shapes. a value - * of 1.0 produces circular shapes, where as a low value produces more scattered shapes. a value - * of 0.0 should not be used, and will always return no tiles - */ - tileset_t subset(rng::RNG &rng, coord::tile start_tile, unsigned int number, double p) const; - - /** - * removes the given set of tiles from this region, which get split of as a new child region - */ - Region take_tiles(rng::RNG &rng, coord::tile start_tile, unsigned int number, double p); - - /** - * similiar to take_tiles, but takes a random group of tiles - */ - Region take_random(rng::RNG &rng, unsigned int number, double p); - - /** - * player id of the owner, 0 for none - */ - int owner; - - /** - * the object to be placed on each tile of this region - * 0 for placing no object - */ - int object_id; - - /** - * the base terrain for this region - */ - int terrain_id; - -private: - - /** - * the center tile of this region - */ - coord::tile center; - - /** - * tiles in this region - */ - tileset_t tiles; -}; - - -/** - * Manages creation and setup of new games - * - * required values used to construct a game - * this includes game spec and players - * - * this will be identical for each networked - * player in a game - */ -class Generator : public qtsdl::GuiPropertyMap { -public: - explicit Generator(qtsdl::GuiItemLink *gui_link); - - /** - * game spec used by this generator - */ - std::shared_ptr get_spec() const; - - /** - * return the list of player names - */ - std::vector player_names() const; - - /** - * returns the generated terrain - */ - std::shared_ptr terrain() const; - - /** - * places all initial objects - */ - void add_units(GameMain &m) const; - - /** - * Create a game from a specification. - */ - std::unique_ptr create(std::shared_ptr spec); - -private: - void create_regions(); - - /** - * data version used to create a game - */ - std::shared_ptr spec; - - /** - * the generated data - */ - std::vector regions; - -public: - qtsdl::GuiItemLink *gui_link; -}; - -} // namespace openage diff --git a/libopenage/gamestate/old/market.cpp b/libopenage/gamestate/old/market.cpp deleted file mode 100644 index 4b1d50bc24..0000000000 --- a/libopenage/gamestate/old/market.cpp +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2017-2019 the openage authors. See copying.md for legal info. - -#include "player.h" -#include "market.h" - -namespace openage { - -Market::Market() { - // the default prices when a game starts - this->base_prices[game_resource::wood] = 100; - this->base_prices[game_resource::food] = 100; - this->base_prices[game_resource::stone] = 130; -} - -// Price calculation is documented at doc/reverse_engineering/market.md#prices - -bool Market::sell(Player &player, const game_resource res) { - // deduct the standard MARKET_TRANSACTION_AMOUNT of the selling res - if (player.deduct(res, MARKET_TRANSACTION_AMOUNT)) { - // if deduct was successful - // calc the gold received from selling MARKET_TRANSACTION_AMOUNT of res - double amount = this->get_sell_prices(player).get(res); - player.receive(game_resource::gold, amount); - - // decrease the price - this->base_prices[res] -= MARKET_PRICE_D; - if (this->base_prices.get(res) < MARKET_PRICE_MIN) { - this->base_prices[res] = MARKET_PRICE_MIN; - } - return true; - } - return false; -} - -bool Market::buy(Player &player, const game_resource res) { - // calc the gold needed to buy MARKET_TRANSACTION_AMOUNT of res - double price = this->get_buy_prices(player).get(res); - if (player.deduct(game_resource::gold, price)) { - // if deduct was successful - player.receive(res, MARKET_TRANSACTION_AMOUNT); - - // increase the price - this->base_prices[res] += MARKET_PRICE_D; - if (this->base_prices.get(res) > MARKET_PRICE_MAX) { - this->base_prices[res] = MARKET_PRICE_MAX; - } - return true; - } - return false; -} - -ResourceBundle Market::get_buy_prices(const Player &player) const { - return this->get_prices(player, true); -} - -ResourceBundle Market::get_sell_prices(const Player &player) const { - return this->get_prices(player, false); -} - -ResourceBundle Market::get_prices(const Player &player, const bool is_buy) const { - double mult = this->get_multiplier(player, is_buy); - - auto rb = ResourceBundle(this->base_prices); - rb *= mult; - rb.round(); // round to nearest integer - return rb; -} - -double Market::get_multiplier(const Player &/*player*/, const bool is_buy) const { - double base = 0.3; - // TODO change multiplier based on civ bonuses and player researched techs - double mult = base; - - if (is_buy) { - mult = 1 + mult; - } - else { - mult = 1 - mult; - } - - return mult; -} - -} // openage diff --git a/libopenage/gamestate/old/market.h b/libopenage/gamestate/old/market.h deleted file mode 100644 index 643668fe59..0000000000 --- a/libopenage/gamestate/old/market.h +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2017-2019 the openage authors. See copying.md for legal info. - -#pragma once - -#include "resource.h" - -namespace openage { - -class Player; - -constexpr double MARKET_PRICE_D = 3.0; -constexpr double MARKET_PRICE_MIN = 20.0; -constexpr double MARKET_PRICE_MAX = 9999.0; -constexpr double MARKET_TRANSACTION_AMOUNT = 100.0; - -/** - * The global market prices. - * - * Price calculation is documented at doc/reverse_engineering/market.md#prices - */ -class Market { -public: - Market(); - - /** - * The given player sells the given resource for gold. - * Returns true when the transaction is successful. - */ - bool sell(Player &player, const game_resource res); - - /** - * The given player buys the given resource with gold. - * Returns true when the transaction is successful. - */ - bool buy(Player &player, const game_resource res); - - /** - * Get the selling prices for a given player. - */ - ResourceBundle get_buy_prices(const Player &player) const; - - /** - * Get the buying prices for a given player. - */ - ResourceBundle get_sell_prices(const Player &player) const; - -protected: - - /** - * The getBuyPrices and getSellPrices are redirected here. - */ - ResourceBundle get_prices(const Player &player, const bool is_buy) const; - - /** - * Get the multiplier for the base prices - */ - double get_multiplier(const Player &player, const bool is_buy) const; - -private: - - /** - * Stores the base price values of each resource. - * - * The ResourceBundle is used to represent the prices instead of the amounts - * for each resource. - */ - ResourceBundle base_prices; - -}; - -} // openage diff --git a/libopenage/gamestate/old/player.cpp b/libopenage/gamestate/old/player.cpp deleted file mode 100644 index ed6bd9e8b9..0000000000 --- a/libopenage/gamestate/old/player.cpp +++ /dev/null @@ -1,291 +0,0 @@ -// Copyright 2015-2018 the openage authors. See copying.md for legal info. - -#include "player.h" - -#include - -#include "../../log/log.h" -#include "../../unit/unit.h" -#include "../../unit/unit_type.h" -#include "../../util/math_constants.h" -#include "team.h" - - -namespace openage { - -Player::Player(Civilisation *civ, unsigned int number, std::string name) - : - player_number{number}, - color{number}, - civ{civ}, - name{std::move(name)}, - team{nullptr}, - population{0, 200}, // TODO change, get population cap max from game options - score{this}, - age{1} { // TODO change, get starting age from game options - // starting resources - // TODO change, get starting resources from game options - this->resources.set_all(1000); - // TODO change, get starting resources capacity from game options or nyan - this->resources_capacity.set_all(math::DOUBLE_INF / 2); // half to avoid overflows - this->on_resources_change(); -} - -bool Player::operator ==(const Player &other) const { - return this->player_number == other.player_number; -} - -bool Player::is_enemy(const Player &other) const { - - return !this->is_ally(other); -} - -bool Player::is_ally(const Player &other) const { - - if (this->player_number == other.player_number) { - return true; // same player - } - - if (this->team && this->team->is_member(other)) { - return true; // same team - } - - // everyone else is enemy - return false; -} - -bool Player::owns(Unit &unit) const { - if (unit.has_attribute(attr_type::owner)) { - return this == &unit.get_attribute().player; - } - return false; -} - -void Player::receive(const ResourceBundle& amount) { - this->resources += amount; - this->on_resources_change(); -} - -void Player::receive(const game_resource resource, double amount) { - this->resources[resource] += amount; - this->on_resources_change(); -} - -bool Player::can_receive(const ResourceBundle& amount) const { - return this->resources_capacity.has(this->resources, amount); -} - -bool Player::can_receive(const game_resource resource, double amount) const { - return this->resources_capacity.get(resource) >= this->resources.get(resource) + amount; -} - -bool Player::deduct(const ResourceBundle& amount) { - if (this->resources.deduct(amount)) { - this->on_resources_change(); - return true; - } - return false; -} - -bool Player::deduct(const game_resource resource, double amount) { - if (this->resources[resource] >= amount) { - this->resources[resource] -= amount; - this->on_resources_change(); - return true; - } - return false; -} - -bool Player::can_deduct(const ResourceBundle& amount) const { - return this->resources.has(amount); -} - -bool Player::can_deduct(const game_resource resource, double amount) const { - return this->resources.get(resource) >= amount; -} - -double Player::amount(const game_resource resource) const { - return this->resources.get(resource); -} - -bool Player::can_make(const UnitType &type) const { - return this->can_deduct(type.cost.get(*this)) && - this->get_units_have(type.id()) + this->get_units_pending(type.id()) < type.have_limit && - this->get_units_had(type.id()) + this->get_units_pending(type.id()) < type.had_limit; -} - -size_t Player::type_count() { - return this->available_ids.size(); -} - -UnitType *Player::get_type(index_t type_id) const { - if (this->available_ids.count(type_id) == 0) { - if (type_id > 0) { - log::log(MSG(info) << " -> ignoring type_id: " << type_id); - } - return nullptr; - } - return this->available_ids.at(type_id); -} - -UnitType *Player::get_type_index(size_t type_index) const { - if (type_index < available_objects.size()) { - return available_objects.at(type_index).get(); - } - log::log(MSG(info) << " -> ignoring type_index: " << type_index); - return nullptr; -} - - -void Player::initialise_unit_types() { - log::log(MSG(info) << name << " has civilisation " << this->civ->civ_name); - for (auto &type : this->civ->object_meta()) { - auto shared_type = type->init(*this); - index_t id = shared_type->id(); - this->available_objects.emplace_back(shared_type); - this->available_ids[id] = shared_type.get(); - } -} - -void Player::active_unit_added(Unit *unit, bool from_pending) { - // check if unit is actually active - if (this->is_unit_pending(unit)) { - this->units_pending[unit->unit_type->id()] += 1; - return; - } - - if (from_pending) { - this->units_pending[unit->unit_type->id()] -= 1; - } - - this->units_have[unit->unit_type->id()] += 1; - this->units_had[unit->unit_type->id()] += 1; - // TODO handle here building dependencies - - // population - if (unit->has_attribute(attr_type::population)) { - auto popul = unit->get_attribute(); - if (popul.demand > 0) { - this->population.demand_population(popul.demand); - } - if (popul.capacity > 0) { - this->population.add_capacity(popul.capacity); - } - } - - // resources capacity - if (unit->has_attribute(attr_type::storage)) { - auto storage = unit->get_attribute(); - this->resources_capacity += storage.capacity; - this->on_resources_change(); - } - - // score - // TODO improve selectors - if (unit->unit_type->id() == 82 || unit->unit_type->id() == 276) { // Castle, Wonder - this->score.add_score(score_category::society, unit->unit_type->cost.get(*this).sum() * 0.2); - } else if (unit->has_attribute(attr_type::building) || unit->has_attribute(attr_type::population)) { // building, living - this->score.add_score(score_category::economy, unit->unit_type->cost.get(*this).sum() * 0.2); - } - - // TODO handle here on create unit triggers - // TODO check for unit based win conditions -} - -void Player::active_unit_removed(Unit *unit) { - // check if unit is actually active - if (this->is_unit_pending(unit)) { - this->units_pending[unit->unit_type->id()] -= 1; - return; - } - - this->units_have[unit->unit_type->id()] -= 1; - // TODO handle here building dependencies - - // population - if (unit->has_attribute(attr_type::population)) { - auto popul = unit->get_attribute(); - if (popul.demand > 0) { - this->population.free_population(popul.demand); - } - if (popul.capacity > 0) { - this->population.remove_capacity(popul.capacity); - } - } - - // resources capacity - if (unit->has_attribute(attr_type::storage)) { - auto storage = unit->get_attribute(); - this->resources_capacity -= storage.capacity; - this->on_resources_change(); - } - - // score - // TODO improve selectors - if (unit->unit_type->id() == 82 || unit->unit_type->id() == 276) { // Castle, Wonder - // nothing - } else if (unit->has_attribute(attr_type::building) || unit->has_attribute(attr_type::population)) { // building, living - this->score.remove_score(score_category::economy, unit->unit_type->cost.get(*this).sum() * 0.2); - } - - // TODO handle here on death unit triggers - // TODO check for unit based win conditions -} - -void Player::killed_unit(const Unit & unit) { - // score - this->score.add_score(score_category::military, unit.unit_type->cost.get(*this).sum() * 0.2); -} - -void Player::advance_age() { - this->age += 1; -} - -void Player::on_resources_change() { - - // capacity overflow - if (! (this->resources_capacity >= this->resources_capacity)) { - this->resources.limit(this->resources_capacity); - } - - // score - this->score.update_resources(this->resources); - - // TODO check for resource based win conditions -} - -int Player::get_units_have(int type_id) const { - if (this->units_have.count(type_id)) { - return this->units_have.at(type_id); - } - return 0; -} - -int Player::get_units_had(int type_id) const { - if (this->units_had.count(type_id)) { - return this->units_had.at(type_id); - } - return 0; -} - -int Player::get_units_pending(int type_id) const { - if (this->units_pending.count(type_id)) { - return this->units_pending.at(type_id); - } - return 0; -} - -bool Player::is_unit_pending(Unit *unit) const { - // TODO check aslo if unit is training - return unit->has_attribute(attr_type::building) && unit->get_attribute().completed < 1.0f; -} - -int Player::get_workforce_count() const { - // TODO get all units tagged as work force - return this->units_have.at(83) + this->units_have.at(293) + // villagers - this->units_have.at(13) + // fishing ship - this->units_have.at(128) + // trade cart - this->units_have.at(545); // transport ship -} - -} // openage diff --git a/libopenage/gamestate/old/player.h b/libopenage/gamestate/old/player.h deleted file mode 100644 index 469a6c1f98..0000000000 --- a/libopenage/gamestate/old/player.h +++ /dev/null @@ -1,244 +0,0 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include - -#include "civilisation.h" -#include "population_tracker.h" -#include "resource.h" -#include "score.h" - - -namespace openage { - -class Unit; -class Team; - -class Player { -public: - Player(Civilisation *civ, unsigned int number, std::string name); - - /** - * values 0 .. player count - 1 - */ - const unsigned int player_number; - - /** - * values 1 .. player count - * would be better to have rgb color value - */ - const unsigned int color; - - /** - * civilisation and techs of this player - */ - const Civilisation *civ; - - /** - * visible name of this player - */ - const std::string name; - - /** - * the team of this player - * nullptr if member of no team - */ - Team *team; - - /** - * checks if two players are the same - */ - bool operator ==(const Player &other) const; - - /** - * the specified player is an enemy of this player - */ - bool is_enemy(const Player &) const; - - /** - * the specified player is an ally of this player - */ - bool is_ally(const Player &) const; - - /** - * this player owns the specified unit - */ - bool owns(Unit &) const; - - /** - * add to stockpile - */ - void receive(const ResourceBundle& amount); - void receive(const game_resource resource, double amount); - - /** - * Check if can add to stockpile - */ - bool can_receive(const ResourceBundle& amount) const; - bool can_receive(const game_resource resource, double amount) const; - - /** - * remove from stockpile if available - */ - bool deduct(const ResourceBundle& amount); - bool deduct(const game_resource resource, double amount); - - /** - * Check if the player has enough resources to deduct the given amount. - */ - bool can_deduct(const ResourceBundle& amount) const; - bool can_deduct(const game_resource resource, double amount) const; - - /** - * current stockpile amount - */ - double amount(const game_resource resource) const; - - /** - * Check if the player can make a new unit of the given type - */ - bool can_make(const UnitType &type) const; - - /** - * total number of unit types available - */ - size_t type_count(); - - /** - * unit types by aoe gamedata unit ids -- the unit type which corresponds to an aoe unit id - */ - UnitType *get_type(index_t type_id) const; - - /** - * unit types by list index -- a continuous array of all types - * probably not a useful function / can be removed - */ - UnitType *get_type_index(size_t type_index) const; - - /** - * initialise with the base tech level - */ - void initialise_unit_types(); - - /** - * Keeps track of the population information. - */ - PopulationTracker population; - - /** - * The score of the player. - */ - PlayerScore score; - - /** - * Called when a unit is created and active. - * - * If the unit was pending when create (constuction site, training) the method must be - * called again when the unit activates (with the from_penging param set to true) - */ - void active_unit_added(Unit *unit, bool from_pending=false); - - /** - * Called when a unit is destroyed. - */ - void active_unit_removed(Unit *unit); - - /** - * Called when a unit is killed by this player. - */ - void killed_unit(const Unit & unit); - - /** - * Advance to next age; - */ - void advance_age(); - - // Getters - - /** - * Get the number of units the player has for each unit type id. - */ - int get_units_have(int type_id) const; - - /** - * Get the number of units the player ever had for each unit type id. - */ - int get_units_had(int type_id) const; - - /** - * Get the number of units the player has being made for each unit type id. - */ - int get_units_pending(int type_id) const; - - /** - * Get the current age. - * The first age has the value 1. - */ - int get_age() const { return age; } - - /** - * The number of units considered part of the workforce. - */ - int get_workforce_count() const; - - -private: - - bool is_unit_pending(Unit *unit) const; - - /** - * The resources this player currently has - */ - ResourceBundle resources; - - /** - * The resources capacities this player currently has - */ - ResourceBundle resources_capacity; - - /** - * Called when the resources amounts change. - */ - void on_resources_change(); - - /** - * unit types which can be produced by this player. - * TODO revisit, can be simplified? - */ - unit_type_list available_objects; - - /** - * available objects mapped using type id - * unit ids -> unit type for that id - * TODO revisit, can be simplified? - */ - std::unordered_map available_ids; - - /** - * The number of units the player has for each unit type id. - * Used for and event triggers. - */ - std::unordered_map units_have; - - /** - * The number of units the player ever had for each unit type id. - * Used for unit dependencies (eg. Farm). - */ - std::unordered_map units_had; - - /* - * The number of units the player has being made for each unit type id. - * Used for unit limits (eg. Town Center). - */ - std::unordered_map units_pending; - - /** - * The current age. - */ - int age; - -}; - -} // openage diff --git a/libopenage/gamestate/old/population_tracker.cpp b/libopenage/gamestate/old/population_tracker.cpp deleted file mode 100644 index fdb79590f6..0000000000 --- a/libopenage/gamestate/old/population_tracker.cpp +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2017-2019 the openage authors. See copying.md for legal info. - -#include "population_tracker.h" - -namespace openage { - -PopulationTracker::PopulationTracker(int capacity_static, int capacity_max) { - this->demand = 0; - this->capacity_static = capacity_static; - this->capacity_real = 0; - this->capacity_max = capacity_max; - this->update_capacity(); -} - -void PopulationTracker::demand_population(int i) { - this->demand += i; - // TODO triger gui update -} - -void PopulationTracker::free_population(int i) { - this->demand -= i; - // TODO triger gui update -} - -void PopulationTracker::add_capacity_static(int i) { - this->capacity_static += i; - this->update_capacity(); -} - -void PopulationTracker::add_capacity(int i) { - this->capacity_real += i; - this->update_capacity(); -} - -void PopulationTracker::remove_capacity(int i) { - this->capacity_real -= i; - this->update_capacity(); -} - -void PopulationTracker::add_capacity_max(int i) { - this->capacity_max += i; - this->update_capacity(); -} - -void PopulationTracker::update_capacity() { - this->capacity_total = this->capacity_static + this->capacity_real; - // check the capacity limit - if (this->capacity_total > this->capacity_max) { - this->capacity = this->capacity_max; - } else { - this->capacity = this->capacity_total; - } - // TODO triger gui update -} - -int PopulationTracker::get_demand() const { - return this->demand; -} - -int PopulationTracker::get_capacity() const { - return this->capacity; -} - -int PopulationTracker::get_space() const { - return this->capacity - this->demand; -} - -int PopulationTracker::get_capacity_overflow() const { - return this->capacity_total - this->capacity; -} - -bool PopulationTracker::is_capacity_maxed() const { - return this->capacity >= this->capacity_max; -} - -} // openage diff --git a/libopenage/gamestate/old/population_tracker.h b/libopenage/gamestate/old/population_tracker.h deleted file mode 100644 index 431c0f28ea..0000000000 --- a/libopenage/gamestate/old/population_tracker.h +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2017-2019 the openage authors. See copying.md for legal info. - -#pragma once - -namespace openage { - -/** - * Keeps track of the population size and capacity. - */ -class PopulationTracker { -public: - - PopulationTracker(int capacity_static, int capacity_max); - - /** - * Add to the population demand - */ - void demand_population(int i); - - /** - * Remove from the population demand - */ - void free_population(int i); - - /** - * Changes the capacity given by civ bonuses - */ - void add_capacity_static(int i); - - /** - * Add to the capacity given by units - */ - void add_capacity(int i); - - /** - * Remove from the capacity given by units - */ - void remove_capacity(int i); - - /** - * Changes the max capacity given by civ bonuses - */ - void add_capacity_max(int i); - - int get_demand() const; - - int get_capacity() const; - - /** - * Returns the available population capacity for new units. - */ - int get_space() const; - - /** - * Returns the population capacity over the max limit. - */ - int get_capacity_overflow() const; - - /** - * Check if the population capacity has reached the max limit. - */ - bool is_capacity_maxed() const; - -private: - - /** - * Calculates the capacity values based on the limits. - * Must be called when a capacity variable changes. - */ - void update_capacity(); - - /** - * The population demand - */ - int demand; - - /** - * The population capacity given by civ bonuses - */ - int capacity_static; - - /** - * The population capacity given by units - */ - int capacity_real; - - /** - * The max population capacity - */ - int capacity_max; - - // generated values - - /** - * All the population capacity without the limitation - */ - int capacity_total; - - /** - * All the population capacity with the limitation - */ - int capacity; - -}; - -} // openage diff --git a/libopenage/gamestate/old/resource.cpp b/libopenage/gamestate/old/resource.cpp deleted file mode 100644 index 2c3acdda19..0000000000 --- a/libopenage/gamestate/old/resource.cpp +++ /dev/null @@ -1,208 +0,0 @@ -// Copyright 2015-2021 the openage authors. See copying.md for legal info. - -#include -#include -#include - -#include "resource.h" - -namespace openage { - -ResourceBundle Resources::create_bundle() const { - return ResourceBundle(*this); -} - -ResourceBundle::ResourceBundle() - : - ResourceBundle{4} { -} - -ResourceBundle::ResourceBundle(const Resources& resources) - : - ResourceBundle{static_cast(resources.get_count())} { -} - -ResourceBundle::ResourceBundle(const int count) - : - count{count}, - value{new double[count] {0}} { -} - -ResourceBundle::ResourceBundle(const ResourceBundle &resources) - : - ResourceBundle{resources.count} { - this->set(resources); -} - -ResourceBundle ResourceBundle::clone() const { - return ResourceBundle(*this); -} - -ResourceBundle::~ResourceBundle() { - delete[] value; -} - -void ResourceBundle::expand(const ResourceBundle& other) { - this->expand(other.count); -} - -void ResourceBundle::expand(const int count) { - if (this->count == count) { - return; - } - // create new array with old values - auto *new_value = new double[count]; - for (int i = 0; i < this->count; i++) { - new_value[i] = this->value[i]; - } - // replace the private variables - this->count = count; - delete[] value; - this->value = new_value; -} - -bool ResourceBundle::operator> (const ResourceBundle& other) const { - for (int i = 0; i < this->count; i++) { - if (!(this->get(i) > other.get(i))) { - return false; - } - } - // check also resources that are not in both bundles - for (int i = this->count; i < other.count; i++) { - if (other.get(i) > 0) { - return false; - } - } - return true; -} - -bool ResourceBundle::operator>= (const ResourceBundle& other) const { - for (int i = 0; i < this->count; i++) { - if (!(this->get(i) >= other.get(i))) { - return false; - } - } - // check also resources that are not in both bundles - for (int i = this->count; i < other.count; i++) { - if (other.get(i) > 0) { - return false; - } - } - return true; -} - -ResourceBundle& ResourceBundle::operator+= (const ResourceBundle& other) { - this->expand(other); - for (int i = 0; i < this->count; i++) { - (*this)[i] += other.get(i); - } - return *this; -} - -ResourceBundle& ResourceBundle::operator-= (const ResourceBundle& other) { - this->expand(other); - for (int i = 0; i < this->count; i++) { - (*this)[i] -= other.get(i); - } - return *this; -} - -ResourceBundle& ResourceBundle::operator*= (const double a) { - for (int i = 0; i < this->count; i++) { - (*this)[i] *= a; - } - return *this; -} - -ResourceBundle& ResourceBundle::round() { - for (int i = 0; i < this->count; i++) { - (*this)[i] = std::round(this->get(i)); - } - return *this; -} - -bool ResourceBundle::has(const ResourceBundle& amount) const { - return *this >= amount; -} - -bool ResourceBundle::has(const ResourceBundle& amount1, const ResourceBundle& amount2) const { - for (int i = 0; i < this->count; i++) { - if (!(this->get(i) >= amount1.get(i) + amount2.get(i))) { - return false; - } - } - // check also resources that are not in both bundles - for (int i = this->count; i < amount1.count; i++) { - if (amount1.get(i) > 0) { - return false; - } - } - for (int i = this->count; i < amount2.count; i++) { - if (amount2.get(i) > 0) { - return false; - } - } - return true; -} - -bool ResourceBundle::deduct(const ResourceBundle& amount) { - if (this->has(amount)) { - *this -= amount; - return true; - } - return false; -} - -void ResourceBundle::set(const ResourceBundle &amount) { - this->expand(amount); - for (int i = 0; i < this->count; i++) { - (*this)[i] = amount.get(i); - } -} - -void ResourceBundle::set_all(const double amount) { - for (int i = 0; i < this->count; i++) { - (*this)[i] = amount; - } -} - -void ResourceBundle::limit(const ResourceBundle &limits) { - for (int i = 0; i < this->min_count(limits); i++) { - if (this->get(i) > limits.get(i)) { - (*this)[i] = limits.get(i); - } - } -} - -double ResourceBundle::sum() const { - double sum = 0; - for (int i = 0; i < this->count; i++) { - sum += this->get(i); - } - return sum; -} - -int ResourceBundle::min_count(const ResourceBundle &other) { - return this->count <= other.count ? this->count : other.count; -} - -} // openage - -namespace std { - -string to_string(const openage::game_resource &res) { - switch (res) { - case openage::game_resource::wood: - return "wood"; - case openage::game_resource::food: - return "food"; - case openage::game_resource::gold: - return "gold"; - case openage::game_resource::stone: - return "stone"; - default: - return "unknown"; - } -} - -} // namespace std diff --git a/libopenage/gamestate/old/resource.h b/libopenage/gamestate/old/resource.h deleted file mode 100644 index 4a52b5c008..0000000000 --- a/libopenage/gamestate/old/resource.h +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright 2015-2018 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include - -namespace openage { - -class ResourceBundle; - -/** - * A resource - */ -class Resource { -public: - - Resource(); - - virtual int id() const = 0; - - virtual std::string name() const = 0; - - // TODO add images and icons - -}; - -class ResourceProducer : public Resource { -public: - - ResourceProducer(int id, std::string name) - : - _id{id}, - _name{name} { } - - int id() const override { return _id; } - - std::string name() const override { return _name; } - -private: - - int _id; - std::string _name; -}; - -/** - * All the resources. - * - * The the ids of the resources must be inside [0, count). - * - * The static variables wood, food, gold, stone are the ids of the representing resource. - * Any extension of Resources must use this ids as they are an engine dependency (at the moment). - */ -class Resources { -public: - - Resources(); - - virtual unsigned int get_count() const = 0; - - virtual const Resource& get_resource(int id) const = 0; - - ResourceBundle create_bundle() const; - - // TODO remove when the engine is fully decupled from the data - static const int wood = 0; - static const int food = 1; - static const int gold = 2; - static const int stone = 3; - -}; - -class ClassicResources : public Resources { -public: - - ClassicResources() - : - resources{{Resources::wood, "wood"}, - {Resources::food, "food"}, - {Resources::gold, "gold"}, - {Resources::stone, "stone"}} { - } - - unsigned int get_count() const override { return 4; } - - const Resource& get_resource(int id) const override { return this->resources[id]; }; - -private: - - const ResourceProducer resources[4]; -}; - -// TODO remove, here for backwards compatibility -enum class game_resource : int { - wood = 0, - food = 1, - gold = 2, - stone = 3, - RESOURCE_TYPE_COUNT = 4 -}; - - -/** - * A set of amounts of game resources. - * - * Can be also used to store other information about the resources. - * - * TODO change amounts from doubles to integers - */ -class ResourceBundle { -public: - - // TODO remove, here for backwards compatibility - ResourceBundle(); - - ResourceBundle(const Resources& resources); - - virtual ~ResourceBundle(); - - ResourceBundle(const ResourceBundle& other); - - ResourceBundle clone() const; - - bool operator> (const ResourceBundle& other) const; - bool operator>= (const ResourceBundle& other) const; - - ResourceBundle& operator+= (const ResourceBundle& other); - ResourceBundle& operator-= (const ResourceBundle& other); - - ResourceBundle& operator*= (const double a); - - /** - * Round each value to the nearest integer. - * Returns itself. - */ - ResourceBundle& round(); - - bool has(const ResourceBundle& amount) const; - - bool has(const ResourceBundle& amount1, const ResourceBundle& amount2) const; - - /** - * If amount can't be deducted return false, else deduct the given amount - * and return true. - */ - bool deduct(const ResourceBundle& amount); - - void set(const ResourceBundle& amount); - - void set_all(const double amount); - - void limit(const ResourceBundle& limits); - - double& operator[] (const game_resource res) { return value[static_cast(res)]; } - double& operator[] (const int id) { return value[id]; } - - // Getters - - double get(const game_resource res) const { return value[static_cast(res)]; } - double get(const int id) const { return value[id]; } - - /** - * Returns the sum of all the resources. - */ - double sum() const; - - /** - * The number of resources - */ - int get_count() const { return count; }; - -private: - - ResourceBundle(const int count); - - void expand(const ResourceBundle& other); - - void expand(const int count); - - /** - * The number of resources - */ - int count; - - double *value; - - int min_count(const ResourceBundle& other); - -}; - -} // namespace openage - -namespace std { - -std::string to_string(const openage::game_resource &res); - -/** - * hasher for game resource - */ -template<> struct hash { - typedef underlying_type::type underlying_type; - - size_t operator()(const openage::game_resource &arg) const { - hash hasher; - return hasher(static_cast(arg)); - } -}; - -} // namespace std diff --git a/libopenage/gamestate/old/score.cpp b/libopenage/gamestate/old/score.cpp deleted file mode 100644 index 9d26d9c9f0..0000000000 --- a/libopenage/gamestate/old/score.cpp +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2017-2021 the openage authors. See copying.md for legal info. - -#include - -#include "player.h" -#include "score.h" -#include "team.h" -#include "../../log/log.h" - -namespace openage { - -Score::Score() - : - score{0}, - score_total{0}, - score_exploration{0}, - score_resources{0} { -} - -void Score::add_score(const score_category cat, double value) { - this->add_score(cat, static_cast(std::lround(value))); -} - -void Score::add_score(const score_category cat, int value) { - this->score[static_cast(cat)] += value; - this->update_score(); -} - -void Score::remove_score(const score_category cat, double value) { - this->remove_score(cat, static_cast(std::lround(value))); -} - -void Score::remove_score(const score_category cat, int value) { - this->score[static_cast(cat)] -= value; - this->update_score(); -} - -void Score::update_map_explored(double progress) { - this->remove_score(score_category::technology, this->score_exploration); - this->score_exploration = progress * 1000; - this->add_score(score_category::technology, this->score_exploration); -} - -void Score::update_resources(const ResourceBundle & resources) { - this->remove_score(score_category::economy, this->score_resources); - this->score_resources = resources.sum() * 0.1; - this->add_score(score_category::economy, this->score_resources); -} - -void Score::update_score() { - this->score_total = 0; - for (int i = 0; i < static_cast(score_category::SCORE_CATEGORY_COUNT); i++) { - this->score_total += this->get_score(i); - } -} - -PlayerScore::PlayerScore(Player *player) - : - Score(), - player{player} { -} - -void PlayerScore::update_score() { - Score::update_score(); - // update team score - if (this->player->team) { - this->player->team->score.update_score(); - } -} - -TeamScore::TeamScore(Team *team) - : - Score(), - team{team} { -} - -void TeamScore::update_score() { - // scores are the corresponding sums of players score - for (int i = 0; i < static_cast(score_category::SCORE_CATEGORY_COUNT); i++) { - this->score[i] = 0; - } - for (auto player : this->team->get_players()) { - for (int i = 0; i < static_cast(score_category::SCORE_CATEGORY_COUNT); i++) { - this->score[i] += player->score.get_score(i); - } - } - Score::update_score(); -} - -} // openage - -namespace std { - -string to_string(const openage::score_category &cat) { - switch (cat) { - case openage::score_category::military: - return "military"; - case openage::score_category::economy: - return "economy"; - case openage::score_category::technology: - return "technology"; - case openage::score_category::society: - return "society"; - default: - return "unknown"; - } -} - -} // namespace std diff --git a/libopenage/gamestate/old/score.h b/libopenage/gamestate/old/score.h deleted file mode 100644 index 2a8c762290..0000000000 --- a/libopenage/gamestate/old/score.h +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright 2017-2018 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -#include "resource.h" - - -namespace openage { - -class Player; -class Team; - -/** - * The categories of sub-scores that sum to a player's score. - */ -enum class score_category : int { - /** 20% of units killed cost */ - military, - /** 20% of alive units cost and 10% of resources */ - economy, - /** 20% of researched technologies and 10 for each 1% of map explored*/ - technology, - /** 20% of Castles and Wonders cost */ - society, - SCORE_CATEGORY_COUNT -}; - -/** - * Keeps track of a score and all the sub-scores - */ -class Score { -public: - - Score(); - - void add_score(const score_category cat, double value); - void add_score(const score_category cat, int value); - - void remove_score(const score_category cat, double value); - void remove_score(const score_category cat, int value); - - /** - * Updates map exploration precentance based sub-scores - */ - void update_map_explored(double progress); - - /** - * Updates resource based sub-scores - */ - void update_resources(const ResourceBundle & resources); - - /** - * Calculates the total score from the sub-scores. - * TODO update gui here - */ - virtual void update_score(); - - // Getters - - int get_score(const score_category cat) const { return score[static_cast(cat)]; } - int get_score(const int index) const { return score[index]; } - - int get_score_total() const { return score_total; } - -protected: - - int score[static_cast(score_category::SCORE_CATEGORY_COUNT)]; - - // generated values - - int score_total; - -private: - - /** Used by update_map_explored. */ - int score_exploration; - - /** Used by update_resources. */ - int score_resources; -}; - - -/** - * The score of a player - */ -class PlayerScore : public Score { -public: - - PlayerScore(Player *player); - - virtual void update_score() override; - -protected: - -private: - - Player *player; -}; - - -/** - * The score of a team - */ -class TeamScore : public Score { -public: - - TeamScore(Team *team); - - virtual void update_score() override; - -protected: - -private: - - Team *team; -}; - -} // namespace openage - -namespace std { - -std::string to_string(const openage::score_category &cat); - -/** - * hasher for score_category - * TODO decide if needed, not used at the moment - */ -template<> -struct hash { - typedef underlying_type::type underlying_type; - - size_t operator()(const openage::score_category &arg) const { - hash hasher; - return hasher(static_cast(arg)); - } -}; - -} // namespace std diff --git a/libopenage/gamestate/old/team.cpp b/libopenage/gamestate/old/team.cpp deleted file mode 100644 index 92e556437f..0000000000 --- a/libopenage/gamestate/old/team.cpp +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2016-2021 the openage authors. See copying.md for legal info. - -#include "team.h" - -#include "player.h" -#include "score.h" -#include - - -namespace openage { - -Team::Team(unsigned int id) - : - Team{id, "Anonymous Team", nullptr} {} - -Team::Team(unsigned int id, std::string name) - : - Team{id, std::move(name), nullptr} {} - -Team::Team(unsigned int id, std::string name, Player *leader) - : - id{id}, - name{std::move(name)}, - score{this} { - - if (leader) { - this->add_member(*leader, member_type::leader); - } -} - -bool Team::operator ==(const Team &other) const { - return this->id == other.id; -} - -void Team::add_member(Player &player, const member_type type) { - // if already exists, replace member type - this->members[&player] = type; - // change player team pointer - player.team = this; -} - -void Team::change_member_type(Player &player, const member_type type) { - auto p = this->members.find(&player); - if (p != this->members.end()) { - this->members[&player] = type; - } -} - -bool Team::is_member(const Player &player) const { - auto p = this->members.find(&player); - return (p != this->members.end()); -} - -void Team::remove_member(Player &player) { - this->members.erase(&player); - // change player team pointer - player.team = nullptr; -} - -member_type Team::get_member_type(Player &player) { - auto p = this->members.find(&player); - if (p != this->members.end()) { - return this->members[&player]; - } - // return pseudo member type for completion - return member_type::none; -} - -std::vector Team::get_players() const { - std::vector players; - for (auto& i : members) { - players.push_back(i.first); - } - return players; -} - -} // openage diff --git a/libopenage/gamestate/old/team.h b/libopenage/gamestate/old/team.h deleted file mode 100644 index 657909df9e..0000000000 --- a/libopenage/gamestate/old/team.h +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2016-2019 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include -#include - - -#include "score.h" - - -namespace openage { - -class Player; - -/** - * Types of membership - */ -enum class member_type { - leader, - member, - recruit, - none // pseudo type -}; - -/** - * A team of players - */ -class Team { -public: - Team(unsigned int id); - Team(unsigned int id, std::string name); - Team(unsigned int id, std::string name, Player *leader); - - /** - * unique id of the team - */ - const unsigned int id; - - /** - * visible name of this team - */ - const std::string name; - - bool operator ==(const Team &other) const; - - - void add_member(Player &player, const member_type type); - - void change_member_type(Player &player, const member_type type); - - bool is_member(const Player &player) const; - - void remove_member(Player &player); - - member_type get_member_type(Player &player); - - /** - * TODO find a better way to get all the players - */ - std::vector get_players() const; - - /** - * The score of the team, based on the team's players score. - */ - TeamScore score; - -private: - - std::unordered_map members; - -}; - -} // openage diff --git a/libopenage/gamestate/old/types.cpp b/libopenage/gamestate/old/types.cpp deleted file mode 100644 index ca80997a97..0000000000 --- a/libopenage/gamestate/old/types.cpp +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2018-2023 the openage authors. See copying.md for legal info. - -#include "types.h" - - -namespace openage { - -} // openage diff --git a/libopenage/gamestate/old/types.h b/libopenage/gamestate/old/types.h deleted file mode 100644 index 805cd63627..0000000000 --- a/libopenage/gamestate/old/types.h +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2018-2023 the openage authors. See copying.md for legal info. - -#pragma once - - -namespace openage { - -/** - * The key type mapped to data objects. - * Used for graphics indices, sounds, ... - * TODO: get rid of this. - */ -using index_t = int; - - -} // openage diff --git a/libopenage/gamestate/player.cpp b/libopenage/gamestate/player.cpp index e57c4d8a35..cb7aeed3ea 100644 --- a/libopenage/gamestate/player.cpp +++ b/libopenage/gamestate/player.cpp @@ -13,6 +13,13 @@ Player::Player(player_id_t id, db_view{db_view} { } +std::shared_ptr Player::copy(entity_id_t id) { + auto copy = std::shared_ptr(new Player(*this)); + copy->set_id(id); + + return copy; +} + player_id_t Player::get_id() const { return this->id; } @@ -21,4 +28,8 @@ const std::shared_ptr &Player::get_db_view() const { return this->db_view; } +void Player::set_id(entity_id_t id) { + this->id = id; +} + } // namespace openage::gamestate diff --git a/libopenage/gamestate/player.h b/libopenage/gamestate/player.h index db6c53b128..f7ef4987ac 100644 --- a/libopenage/gamestate/player.h +++ b/libopenage/gamestate/player.h @@ -1,4 +1,4 @@ -// Copyright 2018-2023 the openage authors. See copying.md for legal info. +// Copyright 2018-2024 the openage authors. See copying.md for legal info. #pragma once @@ -27,15 +27,20 @@ class Player { Player(player_id_t id, const std::shared_ptr &db_view); - // players can't be copied to prevent duplicate IDs - Player(const Player &) = delete; Player(Player &&) = default; - - Player &operator=(const Player &) = delete; Player &operator=(Player &&) = default; ~Player() = default; + /** + * Copy this player. + * + * @param id Unique identifier. + * + * @return Copy of this player. + */ + std::shared_ptr copy(entity_id_t id); + /** * Get the unique ID of the player. * @@ -50,15 +55,33 @@ class Player { */ const std::shared_ptr &get_db_view() const; +protected: + /** + * A player cannot be default copied because of their unique ID. + * + * \p copy() must be used instead. + */ + Player(const Player &) = default; + Player &operator=(const Player &) = default; + private: + /** + * Set the unique identifier of this player. + * + * Only called by \p copy(). + * + * @param id New ID. + */ + void set_id(entity_id_t id); + /** * Player ID. Must be unique. */ - const player_id_t id; + player_id_t id; /** - * Player view of the nyan game data database. - */ + * Player view of the nyan game data database. + */ std::shared_ptr db_view; }; diff --git a/libopenage/gamestate/simulation.cpp b/libopenage/gamestate/simulation.cpp index c925888815..ed2627c95e 100644 --- a/libopenage/gamestate/simulation.cpp +++ b/libopenage/gamestate/simulation.cpp @@ -1,14 +1,16 @@ -// Copyright 2013-2023 the openage authors. See copying.md for legal info. +// Copyright 2013-2024 the openage authors. See copying.md for legal info. #include "simulation.h" #include "assets/mod_manager.h" #include "event/event_loop.h" #include "gamestate/entity_factory.h" +#include "gamestate/event/drag_select.h" #include "gamestate/event/process_command.h" #include "gamestate/event/send_command.h" #include "gamestate/event/spawn_entity.h" #include "gamestate/event/wait.h" +#include "gamestate/terrain_factory.h" #include "time/clock.h" #include "time/time_loop.h" @@ -27,6 +29,7 @@ GameSimulation::GameSimulation(const util::Path &root_dir, time_loop{time_loop}, event_loop{std::make_shared()}, entity_factory{std::make_shared()}, + terrain_factory{std::make_shared()}, mod_manager{std::make_shared(this->root_dir / "assets" / "converted")}, spawner{std::make_shared(this->event_loop)}, commander{std::make_shared(this->event_loop)} { @@ -42,7 +45,7 @@ GameSimulation::GameSimulation(const util::Path &root_dir, void GameSimulation::run() { this->start(); while (this->running) { - auto current_time = this->time_loop->get_clock()->get_time(); + time::time_t current_time = this->time_loop->get_clock()->get_time(); this->event_loop->reach_time(current_time, this->game->get_state()); } log::log(MSG(info) << "Game simulation loop exited"); @@ -54,9 +57,11 @@ void GameSimulation::start() { this->init_event_handlers(); + // TODO: wait for presenter to initialize before starting? this->game = std::make_shared(event_loop, this->mod_manager, - this->entity_factory); + this->entity_factory, + this->terrain_factory); this->running = true; @@ -114,6 +119,7 @@ void GameSimulation::attach_renderer(const std::shared_ptrgame->attach_renderer(render_factory); this->entity_factory->attach_renderer(render_factory); + this->terrain_factory->attach_renderer(render_factory); } void GameSimulation::set_modpacks(const std::vector &modpacks) { @@ -128,11 +134,13 @@ void GameSimulation::set_modpacks(const std::vector &modpacks) { } void GameSimulation::init_event_handlers() { + auto drag_select_handler = std::make_shared(); auto spawn_handler = std::make_shared(this->event_loop, this->entity_factory); auto command_handler = std::make_shared(); auto manager_handler = std::make_shared(); auto wait_handler = std::make_shared(); + this->event_loop->add_event_handler(drag_select_handler); this->event_loop->add_event_handler(spawn_handler); this->event_loop->add_event_handler(command_handler); this->event_loop->add_event_handler(manager_handler); diff --git a/libopenage/gamestate/simulation.h b/libopenage/gamestate/simulation.h index aab81c524e..b7e4331775 100644 --- a/libopenage/gamestate/simulation.h +++ b/libopenage/gamestate/simulation.h @@ -1,4 +1,4 @@ -// Copyright 2013-2023 the openage authors. See copying.md for legal info. +// Copyright 2013-2024 the openage authors. See copying.md for legal info. #pragma once @@ -31,6 +31,7 @@ class TimeLoop; namespace gamestate { class EntityFactory; class Game; +class TerrainFactory; namespace event { class Commander; @@ -46,10 +47,10 @@ class GameSimulation final { public: /** * Create the game simulation subsystems depending on the requested run mode. - * - * @param root_dir openage root directory. - * @param cvar_manager Environment variable manager. - * @param time_loop Time management loop. + * + * @param root_dir openage root directory. + * @param cvar_manager Environment variable manager. + * @param time_loop Time management loop. */ GameSimulation(const util::Path &root_dir, const std::shared_ptr &cvar_manager, @@ -84,32 +85,32 @@ class GameSimulation final { /** * Get this simulation's cvar manager. - * - * @return CVarManager instance. + * + * @return CVarManager instance. */ const std::shared_ptr get_cvar_manager(); /** - * Get the game running in the simulation. - * - * @return Game instance. - */ + * Get the game running in the simulation. + * + * @return Game instance. + */ const std::shared_ptr get_game(); /** - * Get the event loop for the gamestate. - * - * @return Event loop. - */ + * Get the event loop for the gamestate. + * + * @return Event loop. + */ const std::shared_ptr get_event_loop(); /** - * Get the event entity for spawing game entities. - * - * TODO: Move somewhere else or remove. - * - * @return Spawner for entity creation. - */ + * Get the event entity for spawing game entities. + * + * TODO: Move somewhere else or remove. + * + * @return Spawner for entity creation. + */ const std::shared_ptr get_spawner(); /** @@ -129,10 +130,10 @@ class GameSimulation final { void attach_renderer(const std::shared_ptr &render_factory); /** - * Set the modpacks to load for a game. - * - * @param modpacks IDs of the modpacks to load. - */ + * Set the modpacks to load for a game. + * + * @param modpacks IDs of the modpacks to load. + */ void set_modpacks(const std::vector &modpacks); /** @@ -143,8 +144,8 @@ class GameSimulation final { private: /** - * Initialize event handlers. - */ + * Initialize event handlers. + */ void init_event_handlers(); /** @@ -169,18 +170,23 @@ class GameSimulation final { std::shared_ptr time_loop; /** - * Event loop for processing events in the game. - */ + * Event loop for processing events in the game. + */ std::shared_ptr event_loop; /** - * Factory for creating game entities. - */ + * Factory for creating game entities. + */ std::shared_ptr entity_factory; /** - * Mod manager. - */ + * Factory for creating terrain. + */ + std::shared_ptr terrain_factory; + + /** + * Mod manager. + */ std::shared_ptr mod_manager; // TODO: move somewhere sensible or remove @@ -191,8 +197,8 @@ class GameSimulation final { std::shared_ptr game; /** - * Mutex for thread-safe access to the simulation. - */ + * Mutex for thread-safe access to the simulation. + */ std::shared_mutex mutex; }; diff --git a/libopenage/gamestate/system/activity.cpp b/libopenage/gamestate/system/activity.cpp index 240b5c7c50..7ef1d574b4 100644 --- a/libopenage/gamestate/system/activity.cpp +++ b/libopenage/gamestate/system/activity.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 "activity.h" @@ -9,13 +9,13 @@ #include "error/error.h" #include "log/message.h" -#include "gamestate/activity/event_node.h" #include "gamestate/activity/node.h" #include "gamestate/activity/start_node.h" #include "gamestate/activity/task_node.h" #include "gamestate/activity/task_system_node.h" #include "gamestate/activity/types.h" -#include "gamestate/activity/xor_node.h" +#include "gamestate/activity/xor_event_gate.h" +#include "gamestate/activity/xor_gate.h" #include "gamestate/component/internal/activity.h" #include "gamestate/component/types.h" #include "gamestate/game_entity.h" @@ -27,10 +27,11 @@ namespace openage::gamestate::system { -void Activity::advance(const std::shared_ptr &entity, - const time::time_t &start_time, +void Activity::advance(const time::time_t &start_time, + const std::shared_ptr &entity, const std::shared_ptr &loop, - const std::shared_ptr &state) { + const std::shared_ptr &state, + const std::optional &ev_params) { auto activity_component = std::dynamic_pointer_cast( entity->get_component(component::component_t::ACTIVITY)); auto current_node = activity_component->get_node(start_time); @@ -44,10 +45,12 @@ void Activity::advance(const std::shared_ptr &entity, if (current_node->get_type() == activity::node_t::XOR_EVENT_GATE) { // returning to a event gateway means that the event has been triggered // move to the next node here - auto node = std::static_pointer_cast(current_node); - auto event_next = node->get_next_func(); - auto next_id = event_next(start_time, entity, loop, state); - current_node = node->next(next_id); + if (not ev_params.has_value()) { + throw Error{ERR << "XorEventGate: No event parameters given on continue"}; + } + + auto next_id = ev_params.value().get("next"); + current_node = current_node->next(next_id); // cancel all other events that the manager may have been waiting for activity_component->cancel_events(start_time); @@ -76,21 +79,31 @@ void Activity::advance(const std::shared_ptr &entity, case activity::node_t::TASK_SYSTEM: { auto node = std::static_pointer_cast(current_node); auto task = node->get_system_id(); - event_wait_time = Activity::handle_subsystem(entity, start_time, task); + event_wait_time = Activity::handle_subsystem(start_time, entity, state, task); auto next_id = node->get_next(); current_node = node->next(next_id); } break; case activity::node_t::XOR_GATE: { auto node = std::static_pointer_cast(current_node); - auto condition = node->get_condition_func(); - auto next_id = condition(start_time, entity); + auto next_id = node->get_default()->get_id(); + for (auto &condition : node->get_conditions()) { + auto condition_func = condition.second; + if (condition_func(start_time, entity)) { + next_id = condition.first; + break; + } + } current_node = node->next(next_id); } break; case activity::node_t::XOR_EVENT_GATE: { auto node = std::static_pointer_cast(current_node); - auto event_primer = node->get_primer_func(); - auto evs = event_primer(start_time + event_wait_time, entity, loop, state); - for (auto &ev : evs) { + auto event_primers = node->get_primers(); + for (auto &primer : event_primers) { + auto ev = primer.second(start_time + event_wait_time, + entity, + loop, + state, + primer.first); activity_component->add_event(ev); } @@ -107,18 +120,20 @@ void Activity::advance(const std::shared_ptr &entity, activity_component->set_node(start_time, current_node); } -const time::time_t Activity::handle_subsystem(const std::shared_ptr &entity, - const time::time_t &start_time, +const time::time_t Activity::handle_subsystem(const time::time_t &start_time, + const std::shared_ptr &entity, + const std::shared_ptr &state, system_id_t system_id) { switch (system_id) { case system_id_t::IDLE: return Idle::idle(entity, start_time); break; case system_id_t::MOVE_COMMAND: - return Move::move_command(entity, start_time); + return Move::move_command(entity, state, start_time); break; case system_id_t::MOVE_DEFAULT: - return Move::move_default(entity, {1, 1, 1}, start_time); + // TODO: replace destination value with a parameter + return Move::move_default(entity, state, {1, 1, 1}, start_time); break; default: throw Error{ERR << "Unhandled subsystem " << static_cast(system_id)}; diff --git a/libopenage/gamestate/system/activity.h b/libopenage/gamestate/system/activity.h index d5283fceca..11dcce68ca 100644 --- a/libopenage/gamestate/system/activity.h +++ b/libopenage/gamestate/system/activity.h @@ -1,11 +1,13 @@ -// 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 -#include "time/time.h" +#include "event/eventhandler.h" #include "gamestate/system/types.h" +#include "time/time.h" namespace openage { @@ -23,29 +25,32 @@ namespace system { class Activity { public: /** - * Advance in the activity flow graph of the game entity. - * - * @param entity Game entity. - * @param start_time Start time of change. - */ - static void advance(const std::shared_ptr &entity, - const time::time_t &start_time, + * Advance in the activity flow graph of the game entity. + * + * @param start_time Start time of change. + * @param entity Game entity. + */ + static void advance(const time::time_t &start_time, + const std::shared_ptr &entity, const std::shared_ptr &loop, - const std::shared_ptr &state); + const std::shared_ptr &state, + const std::optional &ev_params = std::nullopt); private: /** * Run a built-in engine subsystem. * - * @param entity Game entity. * @param start_time Start time of change. + * @param entity Game entity. + * @param state Game state. * @param system_id ID of the subsystem to run. * - * @return Runtime of the change in simulation time. + * @return Runtime of the change in simulation time. */ - static const time::time_t handle_subsystem(const std::shared_ptr &entity, - const time::time_t &start_time, - system_id_t system_id); + static const time::time_t handle_subsystem(const time::time_t &start_time, + const std::shared_ptr &entity, + const std::shared_ptr &state, + system_id_t system_id); }; } // namespace system diff --git a/libopenage/gamestate/system/idle.h b/libopenage/gamestate/system/idle.h index 8a9a6e6a9a..eb4434fdb7 100644 --- a/libopenage/gamestate/system/idle.h +++ b/libopenage/gamestate/system/idle.h @@ -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. #pragma once @@ -15,18 +15,18 @@ namespace system { class Idle { public: /** - * Let a game entity idle. - * - * This does not change the state of a unit. It only changes its animation and - * sounds. - * - * @param entity Game entity. - * @param start_time Start time of change. - * - * @return Runtime of the change in simulation time. - */ + * Let a game entity idle. + * + * This does not change the state of a unit. It only changes its animation and + * sounds. + * + * @param entity Game entity. + * @param start_time Start time of change. + * + * @return Runtime of the change in simulation time. + */ static const time::time_t idle(const std::shared_ptr &entity, - const time::time_t &start_time); + const time::time_t &start_time); }; } // namespace system diff --git a/libopenage/gamestate/system/move.cpp b/libopenage/gamestate/system/move.cpp index 3ebd02085b..5a44baf113 100644 --- a/libopenage/gamestate/system/move.cpp +++ b/libopenage/gamestate/system/move.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 "move.h" @@ -24,11 +24,59 @@ #include "gamestate/component/internal/position.h" #include "gamestate/component/types.h" #include "gamestate/game_entity.h" +#include "gamestate/game_state.h" +#include "gamestate/map.h" +#include "pathfinding/path.h" +#include "pathfinding/pathfinder.h" #include "util/fixed_point.h" 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 time::time_t &start_time) { + auto start_tile = start.to_tile(); + auto end_tile = end.to_tile(); + + // Search for a path between the start and end tiles + path::PathRequest request{ + grid_id, + start_tile, + end_tile, + start_time, + }; + auto tile_path = pathfinder->get_path(request); + + if (tile_path.status != path::PathResult::FOUND) { + // No path found + return {}; + } + + // Get the waypoints of the path + std::vector path; + path.reserve(tile_path.waypoints.size()); + + // Start poition is first waypoint + path.push_back(start); + + // Pathfinder waypoints contain start and end tile; we can ignore them + for (size_t i = 1; i < tile_path.waypoints.size() - 1; ++i) { + auto tile = tile_path.waypoints.at(i); + path.push_back(tile.to_phys3_center()); + } + + // End position is last waypoint + path.push_back(end); + + return path; +} + const time::time_t Move::move_command(const std::shared_ptr &entity, + const std::shared_ptr &state, const time::time_t &start_time) { auto command_queue = std::dynamic_pointer_cast( entity->get_component(component::component_t::COMMANDQUEUE)); @@ -40,11 +88,12 @@ const time::time_t Move::move_command(const std::shared_ptrget_target(), start_time); + return Move::move_default(entity, state, command->get_target(), start_time); } const time::time_t Move::move_default(const std::shared_ptr &entity, + const std::shared_ptr &state, const coord::phys3 &destination, const time::time_t &start_time) { if (not entity->has_component(component::component_t::MOVE)) [[unlikely]] { @@ -61,6 +110,7 @@ const time::time_t Move::move_default(const std::shared_ptrget_component(component::component_t::MOVE)); auto move_ability = move_component->get_ability(); auto move_speed = move_ability.get("Move.speed"); + auto move_path_grid = move_ability.get("Move.path_type"); auto pos_component = std::dynamic_pointer_cast( entity->get_component(component::component_t::POSITION)); @@ -70,38 +120,58 @@ const time::time_t Move::move_default(const std::shared_ptris_infinite_positive()) { - auto angle_diff = new_angle - current_angle; - if (angle_diff < 0) { - // get the positive difference - angle_diff = angle_diff * -1; - } - if (angle_diff > 180) { - // always use the smaller angle - angle_diff = angle_diff - 360; - angle_diff = angle_diff * -1; + // Find path + auto map = state->get_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, start_time); + + // use waypoints for movement + double total_time = 0; + pos_component->set_position(start_time, current_pos); + for (size_t i = 1; i < waypoints.size(); ++i) { + auto prev_waypoint = waypoints[i - 1]; + auto cur_waypoint = waypoints[i]; + + auto path_vector = cur_waypoint - prev_waypoint; + auto path_angle = path_vector.to_angle(); + + // rotation + if (not turn_speed->is_infinite_positive()) { + auto angle_diff = path_angle - current_angle; + if (angle_diff < 0) { + // get the positive difference + angle_diff = angle_diff * -1; + } + if (angle_diff > 180) { + // always use the smaller angle + angle_diff = angle_diff - 360; + angle_diff = angle_diff * -1; + } + + // Set an intermediate position keyframe to halt the game entity + // until the rotation is done + double turn_time = angle_diff.to_double() / turn_speed->get(); + total_time += turn_time; + pos_component->set_position(start_time + total_time, prev_waypoint); + + // update current angle for next waypoint + current_angle = path_angle; } + pos_component->set_angle(start_time + total_time, path_angle); - turn_time = angle_diff.to_double() / turn_speed->get(); - } - pos_component->set_angle(start_time + turn_time, new_angle); + // movement + double move_time = 0; + if (not move_speed->is_infinite_positive()) { + auto distance = path_vector.length(); + move_time = distance / move_speed->get(); + } + total_time += move_time; - // movement - double move_time = 0; - if (not move_speed->is_infinite_positive()) { - auto distance = path.length(); - move_time = distance / move_speed->get(); + pos_component->set_position(start_time + total_time, cur_waypoint); } - pos_component->set_position(start_time, current_pos); - pos_component->set_position(start_time + turn_time + move_time, destination); - + // properties auto ability = move_component->get_ability(); if (api::APIAbility::check_property(ability, api::ability_property_t::ANIMATED)) { auto property = api::APIAbility::get_property(ability, api::ability_property_t::ANIMATED); @@ -113,7 +183,7 @@ const time::time_t Move::move_default(const std::shared_ptr &entity, - const time::time_t &start_time); + const std::shared_ptr &state, + const time::time_t &start_time); /** - * Move a game entity to a destination. - * - * @param entity Game entity. - * @param destination Destination coordinates. - * @param start_time Start time of change. - * - * @return Runtime of the change in simulation time. - */ + * Move a game entity to a destination. + * + * @param entity Game entity. + * @param state Game state. + * @param destination Destination coordinates. + * @param start_time Start time of change. + * + * @return Runtime of the change in simulation time. + */ static const time::time_t move_default(const std::shared_ptr &entity, - const coord::phys3 &destination, - const time::time_t &start_time); + const std::shared_ptr &state, + const coord::phys3 &destination, + const time::time_t &start_time); }; } // namespace system diff --git a/libopenage/gamestate/terrain.cpp b/libopenage/gamestate/terrain.cpp index 247b457251..97c5ef5b51 100644 --- a/libopenage/gamestate/terrain.cpp +++ b/libopenage/gamestate/terrain.cpp @@ -1,4 +1,4 @@ -// Copyright 2018-2023 the openage authors. See copying.md for legal info. +// Copyright 2018-2024 the openage authors. See copying.md for legal info. #include "terrain.h" @@ -6,37 +6,109 @@ #include #include -#include "renderer/stages/terrain/terrain_render_entity.h" +#include "error/error.h" + +#include "gamestate/terrain_chunk.h" +#include "renderer/render_factory.h" + namespace openage::gamestate { -Terrain::Terrain(const std::string &texture_path) : +Terrain::Terrain() : size{0, 0}, - height_map{}, - texture_path{texture_path}, - render_entity{nullptr} { - // TODO: Actual terrain generation code - this->size = util::Vector2s{10, 10}; - - // fill the terrain grid with height values - this->height_map.reserve(this->size[0] * this->size[1]); - for (size_t i = 0; i < this->size[0] * this->size[1]; ++i) { - this->height_map.push_back(0.0f); - } + chunks{} { + // TODO: Get actual size of terrain. } -void Terrain::push_to_render() { - if (this->render_entity != nullptr) { - this->render_entity->update(this->size, - this->height_map, - this->texture_path); +Terrain::Terrain(const util::Vector2s &size, + std::vector> &&chunks) : + size{size}, + chunks{std::move(chunks)} { + // Check if chunk is correct + coord::tile_delta current_offset{0, 0}; + util::Vector2s all_chunks_size{0, 0}; + if (this->chunks.size() > 0) { + all_chunks_size = this->chunks[0]->get_size(); + } + + for (const auto &chunk : this->chunks) { + auto chunk_size = chunk->get_size(); + + // Check chunk delta + auto chunk_offset = chunk->get_offset(); + if (current_offset != chunk_offset) [[unlikely]] { + throw error::Error{ERR << "Chunk offset of chunk " << chunk->get_offset() + << " does not match position in vector: " << chunk_offset + << " (expected: " << current_offset << ")"}; + } + + current_offset.ne += chunk_size[0]; + + // Wrap around to the next row + if (current_offset.ne == static_cast(size[0])) { + current_offset.ne = 0; + current_offset.se += chunk_size[1]; + } + // Check if chunk size is correct + else if (chunk_size != chunk_size) [[unlikely]] { + throw error::Error{ERR << "Chunk size of chunk " << chunk->get_offset() + << " is not equal to the first chunk size: " << chunk_size + << " (expected: " << all_chunks_size << ")"}; + } + else if (chunk_size[0] != chunk_size[1]) { + throw error::Error{ERR << "Chunk size of chunk " << chunk->get_offset() + << " is not square: " << chunk_size}; + } + + // Check terrain boundaries + if (current_offset.ne > static_cast(size[0])) { + throw error::Error{ERR << "Width of chunk " << chunk->get_offset() + << " exceeds terrain width: " << chunk_size[0] + << " (max width: " << size[0] << ")"}; + } + else if (current_offset.se > static_cast(size[1])) { + throw error::Error{ERR << "Height of chunk " << chunk->get_offset() + << " exceeds terrain height: " << chunk_size[1] + << " (max height: " << size[1] << ")"}; + } } } -void Terrain::set_render_entity(const std::shared_ptr &entity) { - this->render_entity = entity; +const util::Vector2s &Terrain::get_size() const { + return this->size; +} + +const util::Vector2s Terrain::get_chunks_size() const { + auto chunk_size = this->chunks[0]->get_size(); + return {this->size[0] / chunk_size[0], this->size[1] / chunk_size[1]}; +} - this->push_to_render(); +void Terrain::add_chunk(const std::shared_ptr &chunk) { + this->chunks.push_back(chunk); +} + +const std::vector> &Terrain::get_chunks() const { + return this->chunks; +} + +const std::shared_ptr &Terrain::get_chunk(size_t idx) const { + return this->chunks.at(idx); +} + +const std::shared_ptr &Terrain::get_chunk(const coord::chunk &pos) const { + size_t index = pos.ne + pos.se * this->size[0]; + + return this->get_chunk(index); +} + +void Terrain::attach_renderer(const std::shared_ptr &render_factory) { + for (auto &chunk : this->get_chunks()) { + auto render_entity = render_factory->add_terrain_render_entity(chunk->get_size(), + chunk->get_offset()); + chunk->set_render_entity(render_entity); + + chunk->render_update(time::TIME_ZERO); + } } } // namespace openage::gamestate diff --git a/libopenage/gamestate/terrain.h b/libopenage/gamestate/terrain.h index 1e1aef9846..d77eaf939c 100644 --- a/libopenage/gamestate/terrain.h +++ b/libopenage/gamestate/terrain.h @@ -1,4 +1,4 @@ -// Copyright 2018-2023 the openage authors. See copying.md for legal info. +// Copyright 2018-2024 the openage authors. See copying.md for legal info. #pragma once @@ -6,45 +6,117 @@ #include #include +#include "coord/chunk.h" #include "util/vector.h" + namespace openage { -namespace renderer::terrain { -class TerrainRenderEntity; -} +namespace renderer { +class RenderFactory; +} // namespace renderer namespace gamestate { +class TerrainChunk; /** * Entity for managing the map terrain of a game. */ class Terrain { public: - Terrain(const std::string &texture_path); + /** + * Create a new terrain. + */ + Terrain(); ~Terrain() = default; /** - * Set the current render entity of the terrain. + * Create a new terrain. * - * @param entity New render entity. + * Chunks must conform to these constraints: + * - All chunks that are not last in a row OR columns must have the same size (width x height). + * - All chunks that are not last in a row OR columns must be square (width == height). + * - The last chunk in a row may have a different width. + * - The last chunk in a column may have a different height. + * + * @param size Total size of the terrain in tiles (width x height). + * @param chunks Terrain chunks. */ - void set_render_entity(const std::shared_ptr &entity); + Terrain(const util::Vector2s &size, + std::vector> &&chunks); -private: - // test connection to renderer - void push_to_render(); + /** + * Get the size of the terrain (in tiles). + * + * @return Terrain tile size (width x height). + */ + const util::Vector2s &get_size() const; + + /** + * Get the size of the terrain (in chunks). + * + * @return Terrain chunk size (width x height). + */ + const util::Vector2s get_chunks_size() const; + + /** + * Add a chunk to the terrain. + * + * @param chunk New chunk. + */ + void add_chunk(const std::shared_ptr &chunk); + + /** + * Get the chunks of the terrain. + * + * @return Terrain chunks. + */ + const std::vector> &get_chunks() const; + + /** + * Get a specific chunk of the terrain. + * + * @param idx Index of the chunk. + * + * @return Terrain chunk. + */ + const std::shared_ptr &get_chunk(size_t idx) const; - // size of the map - // origin is the left corner - // x = top left edge; y = top right edge + /** + * Get a specific chunk of the terrain. + * + * @param pos Position of the chunk in the terrain. + * + * @return Terrain chunk. + */ + const std::shared_ptr &get_chunk(const coord::chunk &pos) const; + + /** + * Attach a renderer which enables graphical display. + * + * TODO: We currently have to do attach this here too in addition to the terrain + * factory because the renderer gets attached AFTER the terrain is + * already created. In the future, the game should wait for the renderer + * before creating the terrain. + * + * @param render_factory Factory for creating connector objects for gamestate->renderer + * communication. + */ + void attach_renderer(const std::shared_ptr &render_factory); + +private: + /** + * Total size of the terrain in tiles (width x height). + * + * Origin is the top left corner (left corner with camera projection). + */ util::Vector2s size; - // Heights of the terrain grid - std::vector height_map; - // path to a texture - std::string texture_path; - // render entity for pushing updates to - std::shared_ptr render_entity; + /** + * Subdivision of the main terrain entity. + * + * Ordered in rows from left to right, top to bottom. + */ + std::vector> chunks; }; } // namespace gamestate diff --git a/libopenage/gamestate/terrain_chunk.cpp b/libopenage/gamestate/terrain_chunk.cpp index d434a7a921..3c624ff700 100644 --- a/libopenage/gamestate/terrain_chunk.cpp +++ b/libopenage/gamestate/terrain_chunk.cpp @@ -1,8 +1,60 @@ -// Copyright 2018-2018 the openage authors. See copying.md for legal info. +// Copyright 2018-2024 the openage authors. See copying.md for legal info. #include "terrain_chunk.h" namespace openage::gamestate { -} // openage::gamestate +TerrainChunk::TerrainChunk(const util::Vector2s size, + const coord::tile_delta offset, + const std::vector &&tiles) : + size{size}, + offset{offset}, + tiles{std::move(tiles)} { + if (this->size[0] > MAX_CHUNK_WIDTH || this->size[1] > MAX_CHUNK_HEIGHT) { + throw Error(MSG(err) << "Terrain chunk size exceeds maximum size: " + << this->size[0] << "x" << this->size[1] << " > " + << MAX_CHUNK_WIDTH << "x" << MAX_CHUNK_HEIGHT); + } +} + +void TerrainChunk::set_render_entity(const std::shared_ptr &entity) { + this->render_entity = entity; +} + +const util::Vector2s &TerrainChunk::get_size() const { + return this->size; +} + +const coord::tile_delta &TerrainChunk::get_offset() const { + return this->offset; +} + +const std::vector &TerrainChunk::get_tiles() const { + return this->tiles; +} + +const TerrainTile &TerrainChunk::get_tile(size_t idx) const { + return this->tiles.at(idx); +} + +const TerrainTile &TerrainChunk::get_tile(const coord::tile &pos) const { + return this->tiles.at(pos.ne + pos.se * this->size[0]); +} + +void TerrainChunk::render_update(const time::time_t &time) { + if (this->render_entity != nullptr) { + // TODO: Update individual tiles instead of the whole chunk + std::vector> tiles; + tiles.reserve(this->tiles.size()); + for (const auto &tile : this->tiles) { + tiles.emplace_back(tile.elevation, tile.terrain_asset_path); + } + + this->render_entity->update(this->size, + tiles, + time); + } +} + +} // namespace openage::gamestate diff --git a/libopenage/gamestate/terrain_chunk.h b/libopenage/gamestate/terrain_chunk.h index 784aec7c9e..68d0a2e6f2 100644 --- a/libopenage/gamestate/terrain_chunk.h +++ b/libopenage/gamestate/terrain_chunk.h @@ -1,14 +1,109 @@ -// Copyright 2018-2023 the openage authors. See copying.md for legal info. +// Copyright 2018-2024 the openage authors. See copying.md for legal info. #pragma once +#include + +#include "coord/tile.h" +#include "gamestate/terrain_tile.h" +#include "renderer/stages/terrain/render_entity.h" +#include "time/time.h" +#include "util/vector.h" + namespace openage::gamestate { +const size_t MAX_CHUNK_WIDTH = 16; +const size_t MAX_CHUNK_HEIGHT = 16; + + /** * Subdivision of the main terrain entity. */ class TerrainChunk { +public: + TerrainChunk(const util::Vector2s size, + const coord::tile_delta offset, + const std::vector &&tiles); + ~TerrainChunk() = default; + + /** + * Set the current render entity of the terrain. + * + * @param entity New render entity. + */ + void set_render_entity(const std::shared_ptr &entity); + + /** + * Get the size of this terrain chunk. + * + * @return Size of the terrain chunk (in tiles). + */ + const util::Vector2s &get_size() const; + + /** + * Get the offset of this terrain chunk to the terrain origin. + * + * @return Offset of the terrain chunk (in tiles). + */ + const coord::tile_delta &get_offset() const; + + /** + * Get the tiles of this terrain chunk. + * + * @return Terrain tiles. + */ + const std::vector &get_tiles() const; + + /** + * Get the tile at the given index. + * + * @param idx Index of the tile. + * + * @return Terrain tile. + */ + const TerrainTile &get_tile(size_t idx) const; + + /** + * Get the tile at the given position. + * + * @param pos Position of the tile. + * + * @return Terrain tile. + */ + const TerrainTile &get_tile(const coord::tile &pos) const; + + /** + * Update the render entity. + * + * @param time Simulation time of the update. + */ + void render_update(const time::time_t &time); + +private: + /** + * Size of the terrain chunk. + * Origin is the left corner. + * x = top left edge; y = top right edge. + */ + util::Vector2s size; + + /** + * Offset of the terrain chunk to the origin. + */ + coord::tile_delta offset; + + /** + * Terrain tile info of the terrain chunk. + * + * Layout is row-major. + */ + std::vector tiles; + + /** + * Render entity for pushing updates to the renderer. Can be \p nullptr. + */ + std::shared_ptr render_entity; }; } // namespace openage::gamestate diff --git a/libopenage/gamestate/terrain_factory.cpp b/libopenage/gamestate/terrain_factory.cpp new file mode 100644 index 0000000000..3f16a6acf1 --- /dev/null +++ b/libopenage/gamestate/terrain_factory.cpp @@ -0,0 +1,268 @@ +// Copyright 2023-2024 the openage authors. See copying.md for legal info. + +#include "terrain_factory.h" + +#include + +#include + +#include "gamestate/api/terrain.h" +#include "gamestate/terrain.h" +#include "gamestate/terrain_chunk.h" +#include "gamestate/terrain_tile.h" +#include "renderer/render_factory.h" +#include "renderer/stages/terrain/render_entity.h" +#include "time/time.h" + +#include "assets/mod_manager.h" +#include "gamestate/game_state.h" + + +namespace openage::gamestate { + +// TODO: Remove test terrain references +static const std::vector test_terrain_paths = { + "../test/textures/test_terrain.terrain", + "../test/textures/test_terrain2.terrain", +}; + +static const std::vector aoe1_test_terrain = { + "aoe1_base.data.terrain.forest.forest.Forest", + "aoe1_base.data.terrain.grass.grass.Grass", + "aoe1_base.data.terrain.dirt.dirt.Dirt", + "aoe1_base.data.terrain.water.water.Water", +}; +static const std::vector de1_test_terrain = { + "aoe1_base.data.terrain.desert.desert.Desert", + "aoe1_base.data.terrain.grass.grass.Grass", + "aoe1_base.data.terrain.dirt.dirt.Dirt", + "aoe1_base.data.terrain.water.water.Water", +}; +static const std::vector aoe2_test_terrain = { + "aoe2_base.data.terrain.foundation.foundation.Foundation", + "aoe2_base.data.terrain.grass.grass.Grass", + "aoe2_base.data.terrain.dirt.dirt.Dirt", + "aoe2_base.data.terrain.water.water.Water", +}; +static const std::vector de2_test_terrain = { + "de2_base.data.terrain.foundation.foundation.Foundation", + "de2_base.data.terrain.grass.grass.Grass", + "de2_base.data.terrain.dirt.dirt.Dirt", + "de2_base.data.terrain.water.water.Water", +}; +static const std::vector hd_test_terrain = { + "hd_base.data.terrain.foundation.foundation.Foundation", + "hd_base.data.terrain.grass.grass.Grass", + "hd_base.data.terrain.dirt.dirt.Dirt", + "hd_base.data.terrain.water.water.Water", +}; +static const std::vector swgb_test_terrain = { + "swgb_base.data.terrain.foundation.foundation.Foundation", + "swgb_base.data.terrain.grass2.grass2.Grass2", + "swgb_base.data.terrain.desert0.desert0.Desert0", + "swgb_base.data.terrain.water1.water1.Water1", +}; +static const std::vector trial_test_terrain = { + "trial_base.data.terrain.foundation.foundation.Foundation", + "trial_base.data.terrain.grass.grass.Grass", + "trial_base.data.terrain.dirt.dirt.Dirt", + "trial_base.data.terrain.water.water.Water", +}; + +// TODO: Remove hardcoded test texture references +static std::vector test_terrains; // declare static so we only have to do this once +static bool has_graphics = false; + +void build_test_terrains(const std::shared_ptr &gstate) { + auto modpack_ids = gstate->get_mod_manager()->get_load_order(); + for (auto &modpack_id : modpack_ids) { + if (modpack_id == "aoe1_base") { + test_terrains.insert(test_terrains.end(), + aoe1_test_terrain.begin(), + aoe1_test_terrain.end()); + has_graphics = false; + } + else if (modpack_id == "de1_base") { + test_terrains.insert(test_terrains.end(), + de1_test_terrain.begin(), + de1_test_terrain.end()); + has_graphics = false; + } + else if (modpack_id == "aoe2_base") { + test_terrains.insert(test_terrains.end(), + aoe2_test_terrain.begin(), + aoe2_test_terrain.end()); + has_graphics = true; + } + else if (modpack_id == "de2_base") { + test_terrains.insert(test_terrains.end(), + de2_test_terrain.begin(), + de2_test_terrain.end()); + has_graphics = false; + } + else if (modpack_id == "hd_base") { + test_terrains.insert(test_terrains.end(), + hd_test_terrain.begin(), + hd_test_terrain.end()); + has_graphics = true; + } + else if (modpack_id == "swgb_base") { + test_terrains.insert(test_terrains.end(), + swgb_test_terrain.begin(), + swgb_test_terrain.end()); + has_graphics = true; + } + else if (modpack_id == "trial_base") { + test_terrains.insert(test_terrains.end(), + trial_test_terrain.begin(), + trial_test_terrain.end()); + has_graphics = true; + } + } +} + +// Layout of terrain tiles on chunk 0 +// values are the terrain index +static const std::array layout_chunk0{ + // clang-format off + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, + 0, 0, 0, 0, 1, 1, 0, 0, 0, 2, + 0, 0, 0, 0, 1, 1, 0, 0, 0, 2, + 0, 0, 0, 1, 3, 3, 1, 0, 0, 0, + 0, 0, 1, 1, 3, 3, 1, 1, 0, 0, + 0, 1, 1, 1, 3, 3, 1, 1, 1, 0, + 0, 0, 1, 1, 3, 3, 1, 0, 0, 0, + // clang-format on +}; + +// Layout of terrain tiles on chunk 1 +// values are the terrain index +static const std::array layout_chunk1{ + // clang-format off + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, + 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, + 0, 0, 0, 2, 2, 2, 0, 0, 0, 0, + 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, + 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, + 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, + // clang-format on +}; + +// Layout of terrain tiles on chunk 2 +// values are the terrain index +static const std::array layout_chunk2{ + // clang-format off + 1, 1, 1, 1, 3, 3, 1, 0, 0, 0, + 1, 1, 1, 1, 3, 3, 1, 1, 0, 0, + 1, 1, 1, 1, 3, 3, 1, 1, 0, 0, + 1, 1, 1, 3, 3, 3, 3, 1, 0, 0, + 1, 3, 3, 3, 3, 3, 3, 3, 1, 2, + 1, 3, 3, 3, 2, 2, 2, 3, 1, 2, + 3, 3, 3, 3, 2, 2, 2, 3, 1, 2, + 1, 3, 3, 3, 2, 2, 2, 3, 1, 1, + 1, 3, 3, 3, 3, 3, 3, 3, 3, 1, + 1, 3, 3, 3, 3, 3, 3, 3, 1, 0, + // clang-format on +}; + +// Layout of terrain tiles on chunk 3 +// values are the terrain index +static const std::array layout_chunk3{ + // clang-format off + 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, + 0, 0, 0, 2, 2, 2, 0, 0, 0, 0, + 0, 0, 2, 2, 2, 2, 1, 1, 2, 0, + 0, 2, 2, 2, 2, 2, 1, 2, 0, 0, + 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, + 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, + 0, 0, 2, 2, 2, 2, 2, 0, 0, 0, + 0, 0, 0, 2, 2, 2, 0, 0, 0, 0, + 0, 0, 0, 2, 2, 2, 0, 0, 0, 0, + 0, 0, 0, 2, 2, 2, 0, 0, 0, 0, + // clang-format on +}; + + +static const std::vector> layout_chunks{ + layout_chunk0, + layout_chunk1, + layout_chunk2, + layout_chunk3, +}; + + +std::shared_ptr TerrainFactory::add_terrain(const util::Vector2s &size, + std::vector> &&chunks) { + // TODO: Replace this with a proper terrain generator. + auto terrain = std::make_shared(size, std::move(chunks)); + + return terrain; +} + +std::shared_ptr TerrainFactory::add_chunk(const std::shared_ptr &gstate, + const util::Vector2s size, + const coord::tile_delta offset) { + std::string terrain_info_path; + + // TODO: Remove test texture references + // ========== + nyan::Object terrain_obj; + if (test_terrains.empty()) { + build_test_terrains(gstate); + } + + // fill the chunk with tiles + std::vector tiles{}; + tiles.reserve(size[0] * size[1]); + + static size_t test_chunk_index = 0; + if (not test_terrains.empty()) { + // use one of the modpack terrain textures + if (test_chunk_index >= layout_chunks.size()) { + test_chunk_index = 0; + } + + for (size_t i = 0; i < size[0] * size[1]; ++i) { + size_t terrain_index = layout_chunks.at(test_chunk_index).at(i); + terrain_obj = gstate->get_db_view()->get_object(test_terrains.at(terrain_index)); + + if (has_graphics) { + terrain_info_path = api::APITerrain::get_terrain_path(terrain_obj); + } + else { + terrain_info_path = test_terrain_paths.at(test_chunk_index % test_terrain_paths.size()); + } + + tiles.push_back({terrain_obj, terrain_info_path, terrain_elevation_t::zero()}); + } + + test_chunk_index += 1; + } + // ========== + + auto chunk = std::make_shared(size, offset, std::move(tiles)); + + if (this->render_factory) { + auto render_entity = this->render_factory->add_terrain_render_entity(size, offset); + chunk->set_render_entity(render_entity); + + chunk->render_update(time::TIME_ZERO); + } + + return chunk; +} + +void TerrainFactory::attach_renderer(const std::shared_ptr &render_factory) { + std::unique_lock lock{this->mutex}; + + this->render_factory = render_factory; +} + +} // namespace openage::gamestate diff --git a/libopenage/gamestate/terrain_factory.h b/libopenage/gamestate/terrain_factory.h new file mode 100644 index 0000000000..f5ce40b283 --- /dev/null +++ b/libopenage/gamestate/terrain_factory.h @@ -0,0 +1,82 @@ +// Copyright 2023-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + +#include "coord/tile.h" +#include "util/vector.h" + + +namespace openage { + +namespace renderer { +class RenderFactory; +} + +namespace gamestate { +class GameState; +class Terrain; +class TerrainChunk; + +/** + * Creates terrain data (tiles, chunks, etc.) to generate a map. + */ +class TerrainFactory { +public: + /** + * Create a new terrain factory. + */ + TerrainFactory() = default; + ~TerrainFactory() = default; + + /** + * Create a new empty terrain object. + * + * @return New terrain object. + */ + std::shared_ptr add_terrain(const util::Vector2s &size, + std::vector> &&chunks); + + /** + * Create a new empty terrain chunk. + * + * @param size Size of the chunk. + * @param offset Offset of the chunk. + * + * @return New terrain chunk. + */ + std::shared_ptr add_chunk(const std::shared_ptr &gstate, + const util::Vector2s size, + const coord::tile_delta offset); + + // TODO: Add tiles + // std::shared_ptr add_tile(const std::shared_ptr &loop, + // const std::shared_ptr &state, + // const nyan::fqon_t &nyan_entity); + + /** + * Attach a render factory for graphical display. + * + * This enables rendering for all created terrain chunks. + * + * @param render_factory Factory for creating connector objects for gamestate->renderer + * communication. + */ + void attach_renderer(const std::shared_ptr &render_factory); + +private: + /** + * Factory for creating connector objects to the renderer which make game entities displayable. + */ + std::shared_ptr render_factory; + + /** + * Mutex for thread safety. + */ + std::shared_mutex mutex; +}; + +} // namespace gamestate +} // namespace openage diff --git a/libopenage/gamestate/terrain_tile.cpp b/libopenage/gamestate/terrain_tile.cpp new file mode 100644 index 0000000000..0fcb157d06 --- /dev/null +++ b/libopenage/gamestate/terrain_tile.cpp @@ -0,0 +1,10 @@ +// Copyright 2023-2023 the openage authors. See copying.md for legal info. + +#include "terrain_tile.h" + + +namespace openage::gamestate { + +// This file is intentionally empty. + +} // namespace openage::gamestate diff --git a/libopenage/gamestate/terrain_tile.h b/libopenage/gamestate/terrain_tile.h new file mode 100644 index 0000000000..6d6dd99b55 --- /dev/null +++ b/libopenage/gamestate/terrain_tile.h @@ -0,0 +1,39 @@ +// Copyright 2023-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + +#include + +#include "util/fixed_point.h" + + +namespace openage::gamestate { + +using terrain_elevation_t = util::FixedPoint; + +/** + * A single terrain tile. + */ +struct TerrainTile { + /** + * Terrain definition used by this tile. + */ + nyan::Object terrain; + + /** + * Path to the terrain asset used by this tile. + * + * TODO: Remove this and fetch the asset path from the terrain definition. + */ + std::string terrain_asset_path; + + /** + * Height of this tile on the terrain. + */ + terrain_elevation_t elevation; +}; + +} // namespace openage::gamestate diff --git a/libopenage/gamestate/universe.cpp b/libopenage/gamestate/universe.cpp index 6299296734..cf44782e7d 100644 --- a/libopenage/gamestate/universe.cpp +++ b/libopenage/gamestate/universe.cpp @@ -10,9 +10,6 @@ namespace openage::gamestate { Universe::Universe(const std::shared_ptr &state) : world{std::make_shared(state)} { - // TODO - auto texpath = "../test/textures/test_terrain.terrain"; - this->terrain = std::make_shared(texpath); } std::shared_ptr Universe::get_world() { @@ -26,10 +23,6 @@ std::shared_ptr Universe::get_terrain() { void Universe::attach_renderer(const std::shared_ptr &render_factory) { this->render_factory = render_factory; - // TODO: Notify entities somwhere else? - auto terrain_render_entity = this->render_factory->add_terrain_render_entity(); - this->terrain->set_render_entity(terrain_render_entity); - this->world->attach_renderer(render_factory); } diff --git a/libopenage/gamestate/universe.h b/libopenage/gamestate/universe.h index ed77f84095..60df5a18b2 100644 --- a/libopenage/gamestate/universe.h +++ b/libopenage/gamestate/universe.h @@ -1,4 +1,4 @@ -// Copyright 2022-2023 the openage authors. See copying.md for legal info. +// Copyright 2022-2024 the openage authors. See copying.md for legal info. #pragma once @@ -18,13 +18,15 @@ class World; /** * Entity for managing the "physical" game world entities (units, buildings, etc.) as well as * conceptual entities (players, resources, ...). + * + * TODO: Remove Universe and other subclasses. */ class Universe { public: /** * Create a new universe. * - * @param state State of the game. + * @param state State of the game. */ Universe(const std::shared_ptr &state); ~Universe() = default; diff --git a/libopenage/gamestate/world.h b/libopenage/gamestate/world.h index 2fd825143b..34f1470a43 100644 --- a/libopenage/gamestate/world.h +++ b/libopenage/gamestate/world.h @@ -1,4 +1,4 @@ -// Copyright 2018-2023 the openage authors. See copying.md for legal info. +// Copyright 2018-2024 the openage authors. See copying.md for legal info. #pragma once @@ -23,7 +23,7 @@ class World { /** * Create a new world. * - * @param state State of the game. + * @param state State of the game. */ World(const std::shared_ptr &state); ~World() = default; @@ -38,8 +38,8 @@ class World { private: /** - * State of the current game. - */ + * State of the current game. + */ std::shared_ptr state; /** diff --git a/libopenage/gui/CMakeLists.txt b/libopenage/gui/CMakeLists.txt deleted file mode 100644 index e63046c401..0000000000 --- a/libopenage/gui/CMakeLists.txt +++ /dev/null @@ -1,25 +0,0 @@ -add_sources(libopenage - assetmanager_link.cpp - actions_list_model.cpp - category_contents_list_model.cpp - engine_info.cpp - engine_link.cpp - game_control_link.cpp - game_creator.cpp - game_main_link.cpp - game_saver.cpp - game_spec_link.cpp - generator_link.cpp - gui.cpp - main_args_link.cpp - registrations.cpp - resources_list_model.cpp -) - -add_subdirectory("guisys") - -add_sources(libopenage - ${QT_SDL_SOURCES} -) - -add_subdirectory("integration") diff --git a/libopenage/gui/actions_list_model.cpp b/libopenage/gui/actions_list_model.cpp deleted file mode 100644 index 67f2d2780f..0000000000 --- a/libopenage/gui/actions_list_model.cpp +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright 2016-2019 the openage authors. See copying.md for legal info. - -#include "actions_list_model.h" - -#include "../log/log.h" -#include "game_control_link.h" - -#include -#include - -namespace openage { -namespace gui { - -namespace { -const int registration = qmlRegisterType("yay.sfttech.openage", 1, 0, "ActionsListModel"); -} - -ActionsListModel::ActionsListModel(QObject *parent) - : - QAbstractListModel{parent}, - action_mode{} { - Q_UNUSED(registration); -} - -ActionsListModel::~ActionsListModel() = default; - -ActionButtonsType ActionsListModel::get_active_buttons() const { - return this->active_buttons; -} - -void ActionsListModel::set_active_buttons(const ActionButtonsType &active_buttons) { - if (this->active_buttons == active_buttons) { - return; - } - this->active_buttons = active_buttons; - - switch (active_buttons) { - case ActionButtonsType::None: - this->clear_buttons(); - break; - - case ActionButtonsType::MilitaryUnits: - this->clear_buttons(); - this->set_icons_source("image://by-filename/converted/interface/hudactions.slp.png"); - this->beginResetModel(); - this->add_button(6, -1, static_cast(GroupIDs::NoGroup), "SET_ABILITY_PATROL"); - this->add_button(7, -1, static_cast(GroupIDs::NoGroup), "SET_ABILITY_GUARD"); - this->add_button(8, -1, static_cast(GroupIDs::NoGroup), "SET_ABILITY_FOLLOW"); - this->add_button(59, -1, static_cast(GroupIDs::NoGroup), "KILL_UNIT"); - this->add_button(2, -1, static_cast(GroupIDs::NoGroup), "SET_ABILITY_GARRISON"); - - this->add_button(9, 53, static_cast(GroupIDs::StanceGroup), "AGGRESSIVE_STANCE"); - this->add_button(10, 52, static_cast(GroupIDs::StanceGroup), "DEFENSIVE_STANCE"); - this->add_button(11, 51, static_cast(GroupIDs::StanceGroup), "HOLD_STANCE"); - this->add_button(50, 54, static_cast(GroupIDs::StanceGroup), "PASSIVE_STANCE"); - this->add_button(3, -1, static_cast(GroupIDs::NoGroup), "STOP"); - this->endResetModel(); - break; - - case ActionButtonsType::CivilianUnits: - this->clear_buttons(); - this->set_icons_source("image://by-filename/converted/interface/hudactions.slp.png"); - this->beginResetModel(); - this->add_button(30, -1, static_cast(GroupIDs::NoGroup), "BUILD_MENU"); - this->add_button(31, -1, static_cast(GroupIDs::NoGroup), "BUILD_MENU_MIL"); - this->add_button(28, -1, static_cast(GroupIDs::NoGroup), "SET_ABILITY_REPAIR"); - this->add_button(59, -1, static_cast(GroupIDs::NoGroup), "KILL_UNIT"); - this->add_button(2, -1, static_cast(GroupIDs::NoGroup), "SET_ABILITY_GARRISON"); - - this->add_button(-1, -1, static_cast(GroupIDs::NoGroup), ""); - this->add_button(-1, -1, static_cast(GroupIDs::NoGroup), ""); - this->add_button(-1, -1, static_cast(GroupIDs::NoGroup), ""); - this->add_button(-1, -1, static_cast(GroupIDs::NoGroup), ""); - this->add_button(3, -1, static_cast(GroupIDs::NoGroup), "STOP"); - this->endResetModel(); - break; - - case ActionButtonsType::BuildMenu: - this->clear_buttons(); - this->set_icons_source("image://by-filename/converted/interface/50705.slp.png"); - this->beginResetModel(); - this->add_button(34, -1, static_cast(GroupIDs::NoGroup), "BUILDING_HOUS"); - this->add_button(20, -1, static_cast(GroupIDs::NoGroup), "BUILDING_MILL"); - this->add_button(39, -1, static_cast(GroupIDs::NoGroup), "BUILDING_MINE"); - this->add_button(40, -1, static_cast(GroupIDs::NoGroup), "BUILDING_SMIL"); - this->add_button(13, -1, static_cast(GroupIDs::NoGroup), "BUILDING_DOCK"); - this->add_button(35, -1, static_cast(GroupIDs::NoGroup), "BUILDING_FARM"); - this->add_button(4, -1, static_cast(GroupIDs::NoGroup), "BUILDING_BLAC"); - this->add_button(16, -1, static_cast(GroupIDs::NoGroup), "BUILDING_MRKT"); - this->add_button(10, -1, static_cast(GroupIDs::NoGroup), "BUILDING_CRCH"); - this->add_button(32, -1, static_cast(GroupIDs::NoGroup), "BUILDING_UNIV"); - this->add_button(28, -1, static_cast(GroupIDs::NoGroup), "BUILDING_RTWC"); - this->add_button(37, -1, static_cast(GroupIDs::NoGroup), "BUILDING_WNDR"); - // the go back button is not in this slp (is in hudactions.slp.png) - this->endResetModel(); - break; - - case ActionButtonsType::MilBuildMenu: - this->clear_buttons(); - this->set_icons_source("image://by-filename/converted/interface/50705.slp.png"); - this->beginResetModel(); - this->add_button(2, -1, static_cast(GroupIDs::NoGroup), "BUILDING_BRKS"); - this->add_button(0, -1, static_cast(GroupIDs::NoGroup), "BUILDING_ARRG"); - this->add_button(23, -1, static_cast(GroupIDs::NoGroup), "BUILDING_STBL"); - this->add_button(22, -1, static_cast(GroupIDs::NoGroup), "BUILDING_SIWS"); - this->add_button(38, -1, static_cast(GroupIDs::NoGroup), "BUILDING_WCTWX"); - this->add_button(30, -1, static_cast(GroupIDs::NoGroup), "BUILDING_WALL"); - this->add_button(29, -1, static_cast(GroupIDs::NoGroup), "BUILDING_WALL2"); - this->add_button(25, -1, static_cast(GroupIDs::NoGroup), "BUILDING_WCTW"); - this->add_button(42, -1, static_cast(GroupIDs::NoGroup), "BUILDING_WCTW4"); - this->add_button(36, -1, static_cast(GroupIDs::NoGroup), "BUILDING_GTCA2"); - this->add_button(7, -1, static_cast(GroupIDs::NoGroup), "BUILDING_CSTL"); - // the go back button is not in this slp (is in hudactions.slp.png) - this->endResetModel(); - break; - - default: - log::log(MSG(warn) << "Unknown action mode selection"); - } -} - -ActionModeLink* ActionsListModel::get_action_mode() const { - return this->action_mode; -} - -void ActionsListModel::set_action_mode(ActionModeLink *action_mode) { - if (this->action_mode != action_mode) { - if (this->action_mode != nullptr) { - QObject::disconnect(this->action_mode, - &ActionModeLink::buttons_type_changed, - this, - &ActionsListModel::on_buttons_type_changed); - } - - this->action_mode = action_mode; - - QObject::connect(this->action_mode, - &ActionModeLink::buttons_type_changed, - this, - &ActionsListModel::on_buttons_type_changed); - } -} - -QUrl ActionsListModel::get_icons_source() const { - return QUrl(this->icons_source); -} - -void ActionsListModel::set_icons_source(QUrl icons_source) { - this->icons_source = std::move(icons_source); -} - -void ActionsListModel::set_icons_source(const std::string &icons_source) { - this->icons_source = QUrl(icons_source.c_str()); - emit this->icons_source_changed(this->icons_source); -} - -Q_INVOKABLE void ActionsListModel::set_initial_buttons() { - this->set_active_buttons(ActionButtonsType::None); -} - -void ActionsListModel::on_buttons_type_changed(const ActionButtonsType buttons_type) { - this->set_active_buttons(buttons_type); -} - -QHash ActionsListModel::roleNames() const { - QHash roles; - roles[static_cast(ActionsRoles::IconRole)] = "ico"; - roles[static_cast(ActionsRoles::IconCheckedRole)] = "icoChk"; - roles[static_cast(ActionsRoles::GroupIDRole)] = "grpID"; - roles[static_cast(ActionsRoles::NameRole)] = "name"; - return roles; -} - -int ActionsListModel::rowCount(const QModelIndex&) const { - return this->buttons.size(); -} - -QVariant ActionsListModel::data(const QModelIndex &index, int role) const { - return this->buttons.at(index.row()).value(role); -} - -QMap ActionsListModel::itemData(const QModelIndex &index) const { - return this->buttons.at(index.row()); -} - -void ActionsListModel::clear_buttons() { - this->beginResetModel(); - this->buttons.clear(); - this->endResetModel(); -} - -void ActionsListModel::add_button(int ico, int ico_chk, int grp_id, const char *name) { - QMap map; - map[static_cast(ActionsRoles::IconRole)] = QVariant(ico); - map[static_cast(ActionsRoles::IconCheckedRole)] = QVariant(ico_chk); - map[static_cast(ActionsRoles::GroupIDRole)] = QVariant(grp_id); - map[static_cast(ActionsRoles::NameRole)] = QVariant(name); - this->buttons.push_back(map); -} - -}} // namespace openage::gui diff --git a/libopenage/gui/actions_list_model.h b/libopenage/gui/actions_list_model.h deleted file mode 100644 index e0ce30f605..0000000000 --- a/libopenage/gui/actions_list_model.h +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2016-2019 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -#include "game_control_link.h" - -#include -#include -#include - -namespace openage { -namespace gui { - -/** - * Model used for the Action Buttons to render (e.g. for civilian units, - * military units, buildings etc.) - */ -class ActionsListModel : public QAbstractListModel { - Q_OBJECT - - Q_PROPERTY(ActionButtonsType active_buttons READ get_active_buttons WRITE set_active_buttons) - Q_PROPERTY(ActionModeLink* action_mode READ get_action_mode WRITE set_action_mode) - Q_PROPERTY(QUrl iconsSource READ get_icons_source WRITE set_icons_source NOTIFY icons_source_changed) - -public: - ActionsListModel(QObject *parent=nullptr); - virtual ~ActionsListModel(); - - ActionButtonsType get_active_buttons() const; - void set_active_buttons(const ActionButtonsType &active_buttons); - - ActionModeLink* get_action_mode() const; - void set_action_mode(ActionModeLink* action_mode); - - QUrl get_icons_source() const; - void set_icons_source(QUrl icons_source); - - Q_INVOKABLE void set_initial_buttons(); - - enum class ActionsRoles { - IconRole = Qt::UserRole + 1, - IconCheckedRole, - GroupIDRole, - NameRole - }; - - enum class GroupIDs { - NoGroup, - StanceGroup - }; - -signals: - void icons_source_changed(const QUrl icons_source); - -private slots: - void on_buttons_type_changed(const ActionButtonsType buttons_type); - -private: - virtual QHash roleNames() const override; - virtual int rowCount(const QModelIndex&) const override; - virtual QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override; - virtual QMap itemData(const QModelIndex &index) const override; - - /** - * Utility function to create a QUrl from a string and set it as iconsSource - */ - void set_icons_source(const std::string &icons_source); - - /** - * Clears all buttons - */ - void clear_buttons(); - - /** - * Shortcut to creating a QMap for a button - */ - void add_button(int ico, int ico_chk, int grp_id, const char *name); - - ActionButtonsType active_buttons; - ActionModeLink *action_mode; - QUrl icons_source; - std::vector> buttons; -}; - -}} // namespace openage::gui diff --git a/libopenage/gui/assetmanager_link.cpp b/libopenage/gui/assetmanager_link.cpp deleted file mode 100644 index 7d05bd5564..0000000000 --- a/libopenage/gui/assetmanager_link.cpp +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#include "assetmanager_link.h" - -#include - -#include "engine_link.h" - -namespace openage { - -class LegacyEngine; - -namespace gui { - -namespace { -const int registration = qmlRegisterType("yay.sfttech.openage", 1, 0, "LegacyAssetManager"); -} - -AssetManagerLink::AssetManagerLink(QObject *parent) : - GuiItemQObject{parent}, - GuiItem{this} { - Q_UNUSED(registration); -} - -AssetManagerLink::~AssetManagerLink() = default; - - -const util::Path &AssetManagerLink::get_asset_dir() const { - return this->asset_dir; -} - - -void AssetManagerLink::set_asset_dir(const util::Path &asset_dir) { - static auto f = [](LegacyAssetManager *_this, const util::Path &dir) { - _this->set_asset_dir(dir); - }; - this->s(f, this->asset_dir, asset_dir); -} - - -EngineLink *AssetManagerLink::get_engine() const { - return this->engine; -} - - -void AssetManagerLink::set_engine(EngineLink *engine_link) { - static auto f = [](LegacyAssetManager *_this, LegacyEngine *engine) { - _this->set_engine(engine); - }; - this->s(f, this->engine, engine_link); -} - - -} // namespace gui -} // namespace openage diff --git a/libopenage/gui/assetmanager_link.h b/libopenage/gui/assetmanager_link.h deleted file mode 100644 index effa0abc33..0000000000 --- a/libopenage/gui/assetmanager_link.h +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include "../util/path.h" -#include "assets/legacy_assetmanager.h" -#include "guisys/link/gui_item.h" - - -namespace openage { -namespace gui { -class AssetManagerLink; -class EngineLink; -} // namespace gui -} // namespace openage - -namespace qtsdl { -template <> -struct Wrap { - using Type = openage::gui::AssetManagerLink; -}; - -template <> -struct Unwrap { - using Type = openage::LegacyAssetManager; -}; - -} // namespace qtsdl - - -namespace openage { -namespace gui { - -class AssetManagerLink : public qtsdl::GuiItemQObject - , public qtsdl::GuiItem { - Q_OBJECT - - Q_PROPERTY(openage::util::Path assetDir READ get_asset_dir WRITE set_asset_dir) - Q_MOC_INCLUDE("gui/engine_link.h") - Q_PROPERTY(openage::gui::EngineLink *engine READ get_engine WRITE set_engine) - -public: - explicit AssetManagerLink(QObject *parent = nullptr); - virtual ~AssetManagerLink(); - - const util::Path &get_asset_dir() const; - void set_asset_dir(const util::Path &data_dir); - - EngineLink *get_engine() const; - void set_engine(EngineLink *engine); - -private: - util::Path asset_dir; - EngineLink *engine; -}; - -} // namespace gui -} // namespace openage diff --git a/libopenage/gui/category_contents_list_model.cpp b/libopenage/gui/category_contents_list_model.cpp deleted file mode 100644 index 334bf7570f..0000000000 --- a/libopenage/gui/category_contents_list_model.cpp +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. - -#include "category_contents_list_model.h" - -#include - -#include "game_control_link.h" - -namespace openage { -namespace gui { - -namespace { -const int registration = qmlRegisterType("yay.sfttech.openage", 1, 0, "Category"); -} - -CategoryContentsListModel::CategoryContentsListModel(QObject *parent) - : - QAbstractListModel{parent}, - editor_mode{} { - Q_UNUSED(registration); -} - -CategoryContentsListModel::~CategoryContentsListModel() = default; - -QString CategoryContentsListModel::get_name() const { - return this->name; -} - -void CategoryContentsListModel::set_name(const QString &name) { - if (this->name != name) { - this->name = name; - - this->on_categories_content_changed(); - } -} - -EditorModeLink* CategoryContentsListModel::get_editor_mode() const { - return this->editor_mode; -} - -void CategoryContentsListModel::set_editor_mode(EditorModeLink *editor_mode) { - if (this->editor_mode != editor_mode) { - if (this->editor_mode) { - QObject::disconnect(this->editor_mode, &EditorModeLink::categories_content_changed, this, &CategoryContentsListModel::on_categories_content_changed); - QObject::disconnect(this->editor_mode, &EditorModeLink::category_content_changed, this, &CategoryContentsListModel::on_category_content_changed); - } - - this->editor_mode = editor_mode; - - if (this->editor_mode) { - QObject::connect(this->editor_mode, &EditorModeLink::categories_content_changed, this, &CategoryContentsListModel::on_categories_content_changed); - QObject::connect(this->editor_mode, &EditorModeLink::category_content_changed, this, &CategoryContentsListModel::on_category_content_changed); - } - - this->on_categories_content_changed(); - } -} - -void CategoryContentsListModel::on_category_content_changed(const std::string &category_name, std::vector> type_and_texture) { - if (this->name == QString::fromStdString(category_name)) { - this->beginResetModel(); - this->type_and_texture = type_and_texture; - this->endResetModel(); - } -} - -void CategoryContentsListModel::on_categories_content_changed() { - if (this->editor_mode) - this->editor_mode->announce_category_content(this->name.toStdString()); -} - -QHash CategoryContentsListModel::roleNames() const { - auto names = this->QAbstractListModel::roleNames(); - names.insert(Qt::UserRole + 1, "typeId"); - return names; -} - -int CategoryContentsListModel::rowCount(const QModelIndex&) const { - return this->type_and_texture.size(); -} - -QVariant CategoryContentsListModel::data(const QModelIndex &index, int role) const { - switch (role) { - case Qt::DisplayRole: - return std::get<1>(this->type_and_texture[index.row()]); - - case Qt::UserRole + 1: - return std::get<0>(this->type_and_texture[index.row()]); - - default: - break; - } - - return QVariant{}; -} - -}} // namespace openage::gui diff --git a/libopenage/gui/category_contents_list_model.h b/libopenage/gui/category_contents_list_model.h deleted file mode 100644 index b8dde68526..0000000000 --- a/libopenage/gui/category_contents_list_model.h +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include -#include -#include - -#include "../gamestate/old/types.h" - -#include - -namespace openage { -namespace gui { - -class EditorModeLink; - -/** - * Adaptor for the contents of a category of the Civilisation. - */ -class CategoryContentsListModel : public QAbstractListModel { - Q_OBJECT - - Q_PROPERTY(QString name READ get_name WRITE set_name) - Q_PROPERTY(openage::gui::EditorModeLink *editorMode READ get_editor_mode WRITE set_editor_mode) - -public: - CategoryContentsListModel(QObject *parent = nullptr); - virtual ~CategoryContentsListModel(); - - QString get_name() const; - void set_name(const QString &name); - - EditorModeLink *get_editor_mode() const; - void set_editor_mode(EditorModeLink *editor_mode); - -private slots: - void on_category_content_changed(const std::string &category_name, std::vector> type_and_texture); - void on_categories_content_changed(); - -private: - virtual QHash roleNames() const override; - virtual int rowCount(const QModelIndex &) const override; - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - - std::vector> type_and_texture; - - QString name; - EditorModeLink *editor_mode; -}; - -} // namespace gui -} // namespace openage diff --git a/libopenage/gui/engine_info.cpp b/libopenage/gui/engine_info.cpp deleted file mode 100644 index fad4eef3d3..0000000000 --- a/libopenage/gui/engine_info.cpp +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. - -#include "engine_info.h" - -namespace openage { -namespace gui { - -EngineQMLInfo::EngineQMLInfo(LegacyEngine *engine, - const util::Path &asset_dir) : - engine{engine}, - asset_dir{asset_dir} {} - -} // namespace gui -} // namespace openage diff --git a/libopenage/gui/engine_info.h b/libopenage/gui/engine_info.h deleted file mode 100644 index 145821f483..0000000000 --- a/libopenage/gui/engine_info.h +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include "../presenter/legacy/legacy.h" -#include "../util/path.h" -#include "guisys/public/gui_singleton_items_info.h" - -namespace openage { -class LegacyEngine; - -namespace presenter { -class LegacyDisplay; -} - -namespace gui { - -/** - * This container is attached to the QML engine. - * - * It allows that one can access the members in the qml engine context then. - * That also means the members accessible during creation of any singleton QML item. - * - * This struct is used to link the openage Engine with QML in engine_link.cpp. - */ -class EngineQMLInfo : public qtsdl::GuiSingletonItemsInfo { -public: - EngineQMLInfo(LegacyEngine *engine, const util::Path &asset_dir); - - /** - * The openage engine, so it can be "used" in QML as a "QML Singleton". - * With this pointer, all of QML can find back to the engine. - */ - LegacyEngine *engine; - - /** - * The openage display. - */ - presenter::LegacyDisplay *display; - - /** - * Search path for finding assets n stuff. - */ - util::Path asset_dir; -}; - - -} // namespace gui -} // namespace openage diff --git a/libopenage/gui/engine_link.cpp b/libopenage/gui/engine_link.cpp deleted file mode 100644 index f98ca82ea4..0000000000 --- a/libopenage/gui/engine_link.cpp +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#include "engine_link.h" - -#include - -#include "../error/error.h" - -#include "../legacy_engine.h" - -#include "guisys/link/qml_engine_with_singleton_items_info.h" -#include "guisys/link/qtsdl_checked_static_cast.h" - -namespace openage::gui { - -namespace { -// this pushes the EngineLink in the QML engine. -// a qml engine calls the static provider() to obtain a handle. -const int registration = qmlRegisterSingletonType("yay.sfttech.openage", 1, 0, "LegacyEngine", &EngineLink::provider); -} // namespace - - -EngineLink::EngineLink(QObject *parent, LegacyEngine *engine) : - GuiSingletonItem{parent}, - core{engine} { - Q_UNUSED(registration); - - ENSURE(!unwrap(this)->gui_link, "Sharing singletons between QML engines is not supported for now."); - - // when the engine announces that the global key bindings - // changed, update the display. - QObject::connect( - &unwrap(this)->gui_signals, - &EngineSignals::global_binds_changed, - this, - &EngineLink::on_global_binds_changed); - - // trigger the engine signal, - // which then triggers this->on_global_binds_changed. - // unwrap(this)->announce_global_binds(); -} - -EngineLink::~EngineLink() { - unwrap(this)->gui_link = nullptr; -} - -// a qml engine requests a handle to the engine link with that static -// method we do this by extracting the per-qmlengine singleton from the -// engine (the qmlenginewithsingletoninfo), then just return the new link -// instance -QObject *EngineLink::provider(QQmlEngine *engine, QJSEngine *) { - // cast the engine to our specialization - qtsdl::QmlEngineWithSingletonItemsInfo *engine_with_singleton_items_info = qtsdl::checked_static_cast(engine); - - // get the singleton container out of the custom qml engine - auto info = static_cast( - engine_with_singleton_items_info->get_singleton_items_info()); - ENSURE(info, "qml-globals were lost or not passed to the gui subsystem"); - - // owned by the QML engine - // this handle contains the pointer to the openage engine, - // obtained through the qmlengine - return new EngineLink{nullptr, info->engine}; -} - -QStringList EngineLink::get_global_binds() const { - return this->global_binds; -} - -void EngineLink::stop() { - this->core->stop(); -} - -void EngineLink::on_global_binds_changed(const std::vector &global_binds) { - QStringList new_global_binds; - - // create the qstring list from the std string list - // which is then displayed in the ui - std::transform( - std::begin(global_binds), - std::end(global_binds), - std::back_inserter(new_global_binds), - [](const std::string &s) { - return QString::fromStdString(s); - }); - - new_global_binds.sort(); - - if (this->global_binds != new_global_binds) { - this->global_binds = new_global_binds; - emit this->global_binds_changed(); - } -} - -} // namespace openage::gui diff --git a/libopenage/gui/engine_link.h b/libopenage/gui/engine_link.h deleted file mode 100644 index dcd587035a..0000000000 --- a/libopenage/gui/engine_link.h +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -#include "../util/path.h" -#include "guisys/link/gui_singleton_item.h" - -QT_FORWARD_DECLARE_CLASS(QQmlEngine) -QT_FORWARD_DECLARE_CLASS(QJSEngine) - -namespace openage { -class LegacyEngine; - -namespace gui { -class EngineLink; -} // namespace gui -} // namespace openage - -namespace qtsdl { -template <> -struct Wrap { - using Type = openage::gui::EngineLink; -}; - -template <> -struct Unwrap { - using Type = openage::LegacyEngine; -}; - -} // namespace qtsdl - -namespace openage { -namespace gui { - -class EngineLink : public qtsdl::GuiSingletonItem { - Q_OBJECT - - /** - * The text list of global key bindings. - * displayed so one can see what keys are active. - */ - Q_PROPERTY(QStringList globalBinds - READ get_global_binds - NOTIFY global_binds_changed) - -public: - explicit EngineLink(QObject *parent, LegacyEngine *engine); - virtual ~EngineLink(); - - static QObject *provider(QQmlEngine *, QJSEngine *); - - template - U *get() const { - return core; - } - - QStringList get_global_binds() const; - - Q_INVOKABLE void stop(); - -signals: - void global_binds_changed(); - -private slots: - void on_global_binds_changed(const std::vector &global_binds); - -private: - LegacyEngine *core; - - QStringList global_binds; -}; - -} // namespace gui -} // namespace openage diff --git a/libopenage/gui/game_control_link.cpp b/libopenage/gui/game_control_link.cpp deleted file mode 100644 index 4c41678892..0000000000 --- a/libopenage/gui/game_control_link.cpp +++ /dev/null @@ -1,456 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#include "game_control_link.h" - -#include - -#include - -#include "../legacy_engine.h" -#include "../unit/action.h" -#include "../unit/unit.h" -#include "engine_link.h" -#include "game_main_link.h" - -namespace openage::gui { - -namespace { -const int registration_mode = qmlRegisterUncreatableType("yay.sfttech.openage", 1, 0, "OutputMode", "OutputMode is an abstract interface for the concrete modes like EditorMode or ActionMode."); -const int registration_create = qmlRegisterType("yay.sfttech.openage", 1, 0, "CreateMode"); -const int registration_action = qmlRegisterType("yay.sfttech.openage", 1, 0, "ActionMode"); -const int registration_editor = qmlRegisterType("yay.sfttech.openage", 1, 0, "EditorMode"); -const int registration = qmlRegisterType("yay.sfttech.openage", 1, 0, "GameControl"); -} // namespace - -OutputModeLink::OutputModeLink(QObject *parent) : - GuiItemQObject{parent}, - QQmlParserStatus{}, - GuiItemInterface{} { -} - -OutputModeLink::~OutputModeLink() = default; - -QString OutputModeLink::get_name() const { - return this->name; -} - -QStringList OutputModeLink::get_binds() const { - return this->binds; -} - -void OutputModeLink::on_announced(const std::string &name) { - auto new_name = QString::fromStdString(name); - - if (this->name != new_name) { - this->name = new_name; - emit this->name_changed(); - } -} - -void OutputModeLink::on_binds_changed( - const std::vector &binds) { - QStringList new_binds; - std::transform( - std::begin(binds), - std::end(binds), - std::back_inserter(new_binds), - [](const std::string &s) { - return QString::fromStdString(s); - }); - - if (this->binds != new_binds) { - this->binds = new_binds; - emit this->binds_changed(); - } -} - -void OutputModeLink::classBegin() { -} - -void OutputModeLink::on_core_adopted() { - QObject::connect(&unwrap(this)->gui_signals, - &OutputModeSignals::announced, - this, - &OutputModeLink::on_announced); - - QObject::connect(&unwrap(this)->gui_signals, - &OutputModeSignals::binds_changed, - this, - &OutputModeLink::on_binds_changed); -} - -void OutputModeLink::componentComplete() { - static auto f = [](OutputMode *_this) { - _this->announce(); - }; - this->i(f); -} - -CreateModeLink::CreateModeLink(QObject *parent) : - Inherits{parent} { - Q_UNUSED(registration_create); -} - -CreateModeLink::~CreateModeLink() = default; - -ActionModeLink::ActionModeLink(QObject *parent) : - Inherits{parent} { - Q_UNUSED(registration_action); -} - -ActionModeLink::~ActionModeLink() = default; - -QString ActionModeLink::get_ability() const { - return this->ability; -} - -QString ActionModeLink::get_population() const { - return this->population; -} - -int ActionModeLink::get_selection_size() const { - return this->selection ? this->selection->get_units_count() : 0; -} - -bool ActionModeLink::get_population_warn() const { - return this->population_warn; -} - -void ActionModeLink::act(const QString &action) { - emit this->action_triggered(action.toStdString()); -} - -void ActionModeLink::on_ability_changed(const std::string &ability) { - this->ability = QString::fromStdString(ability); - emit this->ability_changed(); -} - -void ActionModeLink::on_buttons_type_changed(const ActionButtonsType buttons_type) { - emit this->buttons_type_changed(buttons_type); -} - -void ActionModeLink::on_population_changed(int demand, int capacity, bool warn) { - this->population = QString::number(demand) + "/" + QString::number(capacity); - this->population_warn = warn; - emit this->population_changed(); -} - -void ActionModeLink::on_selection_changed(const UnitSelection *unit_selection, const Player *player) { - this->selection = unit_selection; - - if (this->selection->get_units_count() == 1) { - auto &ref = this->selection->get_first_unit(); - if (ref.is_valid()) { - Unit *u = ref.get(); - - this->selection_name = QString::fromStdString(u->unit_type->name()); - // the icons are split into two sprites - if (u->unit_type->unit_class == gamedata::unit_classes::BUILDING) { - this->selection_icon = QString::fromStdString("50706.slp.png." + std::to_string(u->unit_type->icon)); - } - else { - this->selection_icon = QString::fromStdString("50730.slp.png." + std::to_string(u->unit_type->icon)); - } - this->selection_type = QString::fromStdString("(type: " + std::to_string(u->unit_type->id()) + " " + u->top()->name() + ")"); - - if (u->has_attribute(attr_type::owner)) { - auto &own_attr = u->get_attribute(); - if (own_attr.player.civ->civ_id != 0) { // not gaia - this->selection_owner = QString::fromStdString( - own_attr.player.name + "\n" + own_attr.player.civ->civ_name + "\n" + (!player || *player == own_attr.player ? "" : player->is_ally(own_attr.player) ? "Ally" : - "Enemy")); - // TODO find the team status of the player - } - else { - this->selection_owner = QString::fromStdString(" "); - } - } - - if (u->has_attribute(attr_type::hitpoints) && u->has_attribute(attr_type::damaged)) { - auto &hp = u->get_attribute(); - auto &dm = u->get_attribute(); - // TODO replace ascii health bar with real one - if (hp.hp >= 200) { - this->selection_hp = QString::fromStdString(progress(dm.hp * 1.0f / hp.hp, 8) + " " + std::to_string(dm.hp) + "/" + std::to_string(hp.hp)); - } - else { - this->selection_hp = QString::fromStdString(progress(dm.hp * 1.0f / hp.hp, 4) + " " + std::to_string(dm.hp) + "/" + std::to_string(hp.hp)); - } - } - else { - this->selection_hp = QString::fromStdString(" "); - } - - std::string lines; - if (u->has_attribute(attr_type::resource)) { - auto &res_attr = u->get_attribute(); - lines += std::to_string((int)res_attr.amount) + " " + std::to_string(res_attr.resource_type) + "\n"; - } - if (u->has_attribute(attr_type::building)) { - auto &build_attr = u->get_attribute(); - if (build_attr.completed < 1) { - lines += "Building: " + progress(build_attr.completed, 16) + " " + std::to_string((int)(100 * build_attr.completed)) + "%\n"; - } - } - if (u->has_attribute(attr_type::garrison)) { - auto &garrison_attr = u->get_attribute(); - if (garrison_attr.content.size() > 0) { - lines += "Garrison: " + std::to_string(garrison_attr.content.size()) + " units\n"; - } - } - lines += "\n"; - if (u->has_attribute(attr_type::population)) { - auto &population_attr = u->get_attribute(); - if (population_attr.demand > 1) { - lines += "Population demand: " + std::to_string(population_attr.demand) + " units\n"; - } - if (population_attr.capacity > 0) { - lines += "Population capacity: " + std::to_string(population_attr.capacity) + " units\n"; - } - } - this->selection_attrs = QString::fromStdString(lines); - } - } - else if (this->selection->get_units_count() > 1) { - this->selection_name = QString::fromStdString( - std::to_string(this->selection->get_units_count()) + " units"); - } - - - emit this->selection_changed(); -} - -// TODO remove -std::string ActionModeLink::progress(float progress, int size) { - std::string bar = "["; - for (int i = 0; i < size; i++) { - bar += (i < progress * size ? "=" : " "); - } - return bar + "]"; -} - -void ActionModeLink::on_core_adopted() { - this->Inherits::on_core_adopted(); - QObject::connect(&unwrap(this)->gui_signals, - &ActionModeSignals::ability_changed, - this, - &ActionModeLink::on_ability_changed); - QObject::connect(&unwrap(this)->gui_signals, - &ActionModeSignals::population_changed, - this, - &ActionModeLink::on_population_changed); - QObject::connect(&unwrap(this)->gui_signals, - &ActionModeSignals::selection_changed, - this, - &ActionModeLink::on_selection_changed); - QObject::connect(&unwrap(this)->gui_signals, - &ActionModeSignals::buttons_type_changed, - this, - &ActionModeLink::on_buttons_type_changed); - QObject::connect(this, - &ActionModeLink::action_triggered, - &unwrap(this)->gui_signals, - &ActionModeSignals::on_action); -} - -EditorModeLink::EditorModeLink(QObject *parent) : - Inherits{parent}, - current_type_id{-1}, - current_terrain_id{-1}, - paint_terrain{} { - Q_UNUSED(registration_editor); -} - -EditorModeLink::~EditorModeLink() = default; - -int EditorModeLink::get_current_type_id() const { - return this->current_type_id; -} - -void EditorModeLink::set_current_type_id(int current_type_id) { - static auto f = [](EditorMode *_this, int current_type_id) { - _this->set_current_type_id(current_type_id); - }; - this->s(f, this->current_type_id, current_type_id); -} - -int EditorModeLink::get_current_terrain_id() const { - return this->current_terrain_id; -} - -void EditorModeLink::set_current_terrain_id(int current_terrain_id) { - static auto f = [](EditorMode *_this, int current_terrain_id) { - _this->set_current_terrain_id(current_terrain_id); - }; - this->s(f, this->current_terrain_id, current_terrain_id); -} - -bool EditorModeLink::get_paint_terrain() const { - return this->paint_terrain; -} - -void EditorModeLink::set_paint_terrain(bool paint_terrain) { - static auto f = [](EditorMode *_this, int paint_terrain) { - _this->set_paint_terrain(paint_terrain); - }; - this->s(f, this->paint_terrain, paint_terrain); -} - -QStringList EditorModeLink::get_categories() const { - return this->categories; -} - -void EditorModeLink::announce_category_content(const std::string &category_name) { - static auto f = [](EditorMode *_this, const std::string &category_name) { - _this->announce_category_content(category_name); - }; - this->i(f, category_name); -} - -void EditorModeLink::on_categories_changed(const std::vector &categories) { - this->categories.clear(); - std::transform(std::begin(categories), std::end(categories), std::back_inserter(this->categories), [](const std::string &s) { return QString::fromStdString(s); }); - emit this->categories_changed(); -} - -void EditorModeLink::on_core_adopted() { - this->Inherits::on_core_adopted(); - QObject::connect(&unwrap(this)->gui_signals, &EditorModeSignals::toggle, this, &EditorModeLink::toggle); - QObject::connect(&unwrap(this)->gui_signals, &EditorModeSignals::categories_changed, this, &EditorModeLink::on_categories_changed); - QObject::connect(&unwrap(this)->gui_signals, &EditorModeSignals::categories_content_changed, this, &EditorModeLink::categories_content_changed); - QObject::connect(&unwrap(this)->gui_signals, &EditorModeSignals::category_content_changed, this, &EditorModeLink::category_content_changed); -} - -GameControlLink::GameControlLink(QObject *parent) : - GuiItemQObject{parent}, - QQmlParserStatus{}, - GuiItem{this}, - mode{}, - effective_mode_index{-1}, - mode_index{-1}, - engine{}, - game{}, - current_civ_index{} { - Q_UNUSED(registration_mode); - Q_UNUSED(registration); -} - -GameControlLink::~GameControlLink() = default; - -void GameControlLink::classBegin() { -} - -void GameControlLink::on_core_adopted() { - QObject::connect(&unwrap(this)->gui_signals, &GameControlSignals::mode_changed, this, &GameControlLink::on_mode_changed); - QObject::connect(&unwrap(this)->gui_signals, &GameControlSignals::modes_changed, this, &GameControlLink::on_modes_changed); - QObject::connect(&unwrap(this)->gui_signals, &GameControlSignals::current_player_name_changed, this, &GameControlLink::on_current_player_name_changed); - QObject::connect(&unwrap(this)->gui_signals, &GameControlSignals::current_civ_index_changed, this, &GameControlLink::on_current_civ_index_changed); -} - -void GameControlLink::componentComplete() { - static auto f = [](GameControl *_this) { - _this->announce_mode(); - _this->announce_current_player_name(); - }; - this->i(f); -} - -OutputModeLink *GameControlLink::get_mode() const { - return this->mode; -} - -int GameControlLink::get_effective_mode_index() const { - return this->effective_mode_index; -} - -int GameControlLink::get_mode_index() const { - return this->mode_index; -} - -void GameControlLink::set_mode_index(int mode) { - static auto f = [](GameControl *_this, int mode) { - _this->set_mode(mode, true); - }; - - this->sf(f, this->mode_index, mode); -} - -QVariantList GameControlLink::get_modes() const { - return this->modes; -} - -void GameControlLink::set_modes(const QVariantList &modes) { - static auto f = [](GameControl *_this, const QVariantList &modes) { - std::vector new_modes; - - for (auto m : modes) - if (m.canConvert()) - new_modes.push_back(unwrap(m.value())); - - _this->set_modes(new_modes); - }; - - this->s(f, this->modes, modes); -} - -EngineLink *GameControlLink::get_engine() const { - return this->engine; -} - -void GameControlLink::set_engine(EngineLink *engine) { - static auto f = [](GameControl *_this, LegacyEngine *engine) { - _this->set_engine(engine); - }; - this->s(f, this->engine, engine); -} - -GameMainLink *GameControlLink::get_game() const { - return this->game; -} - -void GameControlLink::set_game(GameMainLink *game) { - static auto f = [](GameControl *_this, GameMainHandle *game) { - _this->set_game(game); - }; - this->s(f, this->game, game); -} - -QString GameControlLink::get_current_player_name() const { - return this->current_player_name; -} - -int GameControlLink::get_current_civ_index() const { - return this->current_civ_index; -} - -void GameControlLink::on_mode_changed(OutputMode *mode, int mode_index) { - auto new_mode = qtsdl::wrap(mode); - - if (this->mode != new_mode || this->effective_mode_index != mode_index) { - this->effective_mode_index = mode_index; - this->mode = new_mode; - emit this->mode_changed(); - } -} - -void GameControlLink::on_modes_changed(OutputMode *mode, int mode_index) { - static auto f = [](GameControl *_this, int mode) { - _this->set_mode(mode); - }; - this->i(f, this->mode_index); - - this->on_mode_changed(mode, mode_index); - emit this->modes_changed(); -} - -void GameControlLink::on_current_player_name_changed(const std::string ¤t_player_name) { - this->current_player_name = QString::fromStdString(current_player_name); - emit this->current_player_name_changed(); -} - -void GameControlLink::on_current_civ_index_changed(int current_civ_index) { - this->current_civ_index = current_civ_index; - emit this->current_civ_index_changed(); -} - -} // namespace openage::gui diff --git a/libopenage/gui/game_control_link.h b/libopenage/gui/game_control_link.h deleted file mode 100644 index 9dde68e924..0000000000 --- a/libopenage/gui/game_control_link.h +++ /dev/null @@ -1,333 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -#include "../presenter/legacy/game_control.h" -#include "guisys/link/gui_item.h" - -namespace openage { -namespace gui { - -class GameControlLink; - -class OutputModeLink; - -} // namespace gui -} // namespace openage - -namespace qtsdl { -template <> -struct Wrap { - using Type = openage::gui::OutputModeLink; -}; - -template <> -struct Unwrap { - using Type = openage::OutputMode; -}; - -} // namespace qtsdl - -namespace openage { -namespace gui { - -class OutputModeLink : public qtsdl::GuiItemQObject - , public QQmlParserStatus - , public qtsdl::GuiItemInterface { - Q_OBJECT - - Q_INTERFACES(QQmlParserStatus) - Q_PROPERTY(QString name READ get_name NOTIFY name_changed) - Q_PROPERTY(QStringList binds READ get_binds NOTIFY binds_changed) - -public: - OutputModeLink(QObject *parent = nullptr); - virtual ~OutputModeLink(); - - QString get_name() const; - QStringList get_binds() const; - -signals: - void name_changed(); - void binds_changed(); - -private slots: - void on_announced(const std::string &name); - void on_binds_changed(const std::vector &binds); - -protected: - virtual void classBegin() override; - virtual void on_core_adopted() override; - virtual void componentComplete() override; - -private: - QString name; - QStringList binds; -}; - -class CreateModeLink; - -} // namespace gui -} // namespace openage - -namespace qtsdl { -template <> -struct Wrap { - using Type = openage::gui::CreateModeLink; -}; - -template <> -struct Unwrap { - using Type = openage::CreateMode; -}; - -} // namespace qtsdl - -namespace openage { -namespace gui { - -class CreateModeLink : public qtsdl::Inherits { - Q_OBJECT - -public: - CreateModeLink(QObject *parent = nullptr); - virtual ~CreateModeLink(); -}; - -class ActionModeLink; - -} // namespace gui -} // namespace openage - -namespace qtsdl { -template <> -struct Wrap { - using Type = openage::gui::ActionModeLink; -}; - -template <> -struct Unwrap { - using Type = openage::ActionMode; -}; - -} // namespace qtsdl - -namespace openage { -namespace gui { - -class ActionModeLink : public qtsdl::Inherits { - Q_OBJECT - - Q_PROPERTY(QString ability READ get_ability NOTIFY ability_changed) - Q_PROPERTY(QString population READ get_population NOTIFY population_changed) - Q_PROPERTY(bool population_warn READ get_population_warn NOTIFY population_changed) - - Q_PROPERTY(int selection_size READ get_selection_size NOTIFY selection_changed) - - Q_PROPERTY(QString selection_name MEMBER selection_name NOTIFY selection_changed) - Q_PROPERTY(QString selection_icon MEMBER selection_icon NOTIFY selection_changed) - Q_PROPERTY(QString selection_type MEMBER selection_type NOTIFY selection_changed) - Q_PROPERTY(QString selection_owner MEMBER selection_owner NOTIFY selection_changed) - Q_PROPERTY(QString selection_hp MEMBER selection_hp NOTIFY selection_changed) - Q_PROPERTY(QString selection_attrs MEMBER selection_attrs NOTIFY selection_changed) - -public: - ActionModeLink(QObject *parent = nullptr); - virtual ~ActionModeLink(); - - QString get_ability() const; - QString get_population() const; - bool get_population_warn() const; - int get_selection_size() const; - - Q_INVOKABLE void act(const QString &action); - -signals: - void ability_changed(); - void action_triggered(const std::string &ability); - void buttons_type_changed(const ActionButtonsType buttons_type); - void population_changed(); - void selection_changed(); - -private slots: - void on_ability_changed(const std::string &ability); - void on_buttons_type_changed(const ActionButtonsType buttons_type); - void on_population_changed(int demand, int capacity, bool warn); - void on_selection_changed(const UnitSelection *unit_selection, const Player *player); - -private: - virtual void on_core_adopted() override; - - QString ability; - QString population; - bool population_warn; - const UnitSelection *selection = nullptr; - - QString selection_name; - QString selection_icon; - QString selection_type; - QString selection_owner; - QString selection_hp; - QString selection_attrs; - - std::string progress(float progress, int size); -}; - -class EditorModeLink; - -} // namespace gui -} // namespace openage - -namespace qtsdl { -template <> -struct Wrap { - using Type = openage::gui::EditorModeLink; -}; - -template <> -struct Unwrap { - using Type = openage::EditorMode; -}; - -} // namespace qtsdl - -namespace openage { -namespace gui { - -class EditorModeLink : public qtsdl::Inherits { - Q_OBJECT - - Q_PROPERTY(int currentTypeId READ get_current_type_id WRITE set_current_type_id) - Q_PROPERTY(int currentTerrainId READ get_current_terrain_id WRITE set_current_terrain_id) - Q_PROPERTY(bool paintTerrain READ get_paint_terrain WRITE set_paint_terrain) - Q_PROPERTY(QStringList categories READ get_categories NOTIFY categories_changed) - -public: - EditorModeLink(QObject *parent = nullptr); - virtual ~EditorModeLink(); - - int get_current_type_id() const; - void set_current_type_id(int current_type_id); - - int get_current_terrain_id() const; - void set_current_terrain_id(int current_terrain_id); - - bool get_paint_terrain() const; - void set_paint_terrain(bool paint_terrain); - - QStringList get_categories() const; - - void announce_category_content(const std::string &category_name); - -signals: - void toggle(); - void categories_changed(); - void categories_content_changed(); - void category_content_changed(const std::string &category_name, std::vector> type_and_texture); - -private slots: - void on_categories_changed(const std::vector &categories); - -private: - virtual void on_core_adopted() override; - - int current_type_id; - int current_terrain_id; - bool paint_terrain; - QStringList categories; -}; - -class EngineLink; -class GameMainLink; -class GameControlLink; - -} // namespace gui -} // namespace openage - -namespace qtsdl { -template <> -struct Wrap { - using Type = openage::gui::GameControlLink; -}; - -template <> -struct Unwrap { - using Type = openage::GameControl; -}; - -} // namespace qtsdl - -namespace openage { -namespace gui { - -class GameControlLink : public qtsdl::GuiItemQObject - , public QQmlParserStatus - , public qtsdl::GuiItem { - Q_OBJECT - - Q_INTERFACES(QQmlParserStatus) - Q_PROPERTY(openage::gui::OutputModeLink *mode READ get_mode NOTIFY mode_changed) - Q_PROPERTY(int effectiveModeIndex READ get_effective_mode_index NOTIFY mode_changed) - Q_PROPERTY(int modeIndex READ get_mode_index WRITE set_mode_index) - Q_PROPERTY(QVariantList modes READ get_modes WRITE set_modes NOTIFY modes_changed) - Q_MOC_INCLUDE("gui/engine_link.h") - Q_MOC_INCLUDE("gui/game_main_link.h") - Q_PROPERTY(openage::gui::EngineLink *engine READ get_engine WRITE set_engine) - Q_PROPERTY(openage::gui::GameMainLink *game READ get_game WRITE set_game) - Q_PROPERTY(QString currentPlayerName READ get_current_player_name NOTIFY current_player_name_changed) - Q_PROPERTY(int currentCivIndex READ get_current_civ_index NOTIFY current_civ_index_changed) - -public: - explicit GameControlLink(QObject *parent = nullptr); - virtual ~GameControlLink(); - - OutputModeLink *get_mode() const; - int get_effective_mode_index() const; - - int get_mode_index() const; - void set_mode_index(int mode); - - QVariantList get_modes() const; - void set_modes(const QVariantList &modes); - - EngineLink *get_engine() const; - void set_engine(EngineLink *engine); - - GameMainLink *get_game() const; - void set_game(GameMainLink *game); - - QString get_current_player_name() const; - int get_current_civ_index() const; - -signals: - void mode_changed(); - void modes_changed(); - void current_player_name_changed(); - void current_civ_index_changed(); - -private slots: - void on_mode_changed(OutputMode *mode, int mode_index); - void on_modes_changed(OutputMode *mode, int mode_index); - void on_current_player_name_changed(const std::string ¤t_player_name); - void on_current_civ_index_changed(int current_civ_index); - -private: - virtual void classBegin() override; - virtual void on_core_adopted() override; - virtual void componentComplete() override; - - OutputModeLink *mode = nullptr; - int effective_mode_index; - int mode_index; - QVariantList modes; - // TODO: remove engine because it's already accessible through the game - EngineLink *engine = nullptr; - GameMainLink *game = nullptr; - QString current_player_name; - int current_civ_index; -}; - -} // namespace gui -} // namespace openage diff --git a/libopenage/gui/game_creator.cpp b/libopenage/gui/game_creator.cpp deleted file mode 100644 index f187ab5ef2..0000000000 --- a/libopenage/gui/game_creator.cpp +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2015-2021 the openage authors. See copying.md for legal info. - -#include "game_creator.h" - -#include - -#include "../gamestate/old/game_main.h" -#include "../gamestate/old/game_spec.h" -#include "../gamestate/old/generator.h" - -#include "game_main_link.h" -#include "game_spec_link.h" -#include "generator_link.h" - -namespace openage::gui { - -namespace { -const int registration = qmlRegisterType("yay.sfttech.openage", 1, 0, "GameCreator"); -} - -GameCreator::GameCreator(QObject *parent) - : - QObject{parent}, - game{}, - game_spec{}, - generator_parameters{} { - Q_UNUSED(registration); -} - -GameCreator::~GameCreator() = default; - -QString GameCreator::get_error_string() const { - return this->error_string; -} - -void GameCreator::activate() { - static auto f = [] (GameMainHandle *game, - GameSpecHandle *game_spec, - Generator *generator, - std::shared_ptr callback) { - - QString error_msg; - - if (game->is_game_running()) { - error_msg = "close existing game before loading"; - } - else if (not game_spec->is_ready()) { - error_msg = "game data has not finished loading"; - } - else { - auto game_main = generator->create(game_spec->get_spec()); - - if (game_main) { - game->set_game(std::move(game_main)); - } - else { - error_msg = "unknown error"; - } - } - - emit callback->error_message(error_msg); - }; - - if (this->game && this->game_spec && this->generator_parameters) { - std::shared_ptr callback = std::make_shared(); - - QObject::connect(callback.get(), &GameCreatorSignals::error_message, this, &GameCreator::on_processed); - - this->game->i(f, this->game_spec, this->generator_parameters, callback); - } else { - this->on_processed([this] { - if (!this->game) - return "provide 'game' before loading"; - if (!this->game_spec) - return "provide 'gameSpec' before loading"; - if (!this->generator_parameters) - return "provide 'generatorParameters' before loading"; - else - ENSURE(false, "unhandled case for refusal to create a game"); - - return "unknown error"; - }()); - } -} - -void GameCreator::clearErrors() { - this->error_string.clear(); - emit this->error_string_changed(); -} - -void GameCreator::on_processed(const QString &error_string) { - this->error_string = error_string; - emit this->error_string_changed(); -} - -} // namespace openage::gui diff --git a/libopenage/gui/game_creator.h b/libopenage/gui/game_creator.h deleted file mode 100644 index b3efb1ff4c..0000000000 --- a/libopenage/gui/game_creator.h +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -namespace openage { -namespace gui { - -class GameMainLink; -class GameSpecLink; -class GeneratorLink; - -class GameCreator : public QObject { - Q_OBJECT - - Q_ENUMS(State) - Q_PROPERTY(QString errorString READ get_error_string NOTIFY error_string_changed) - Q_MOC_INCLUDE("gui/game_main_link.h") - Q_MOC_INCLUDE("gui/game_spec_link.h") - Q_MOC_INCLUDE("gui/generator_link.h") - Q_PROPERTY(openage::gui::GameMainLink *game MEMBER game NOTIFY game_changed) - Q_PROPERTY(openage::gui::GameSpecLink *gameSpec MEMBER game_spec NOTIFY game_spec_changed) - Q_PROPERTY(openage::gui::GeneratorLink *generatorParameters MEMBER generator_parameters NOTIFY generator_parameters_changed) - -public: - explicit GameCreator(QObject *parent = nullptr); - virtual ~GameCreator(); - - QString get_error_string() const; - - Q_INVOKABLE void activate(); - Q_INVOKABLE void clearErrors(); - -public slots: - void on_processed(const QString &error_string); - -signals: - void error_string_changed(); - void game_changed(); - void game_spec_changed(); - void generator_parameters_changed(); - -private: - QString error_string; - GameMainLink *game; - GameSpecLink *game_spec; - GeneratorLink *generator_parameters; -}; - -class GameCreatorSignals : public QObject { - Q_OBJECT - -public: -signals: - void error_message(const QString &error); -}; - -} // namespace gui -} // namespace openage diff --git a/libopenage/gui/game_main_link.cpp b/libopenage/gui/game_main_link.cpp deleted file mode 100644 index 35b4af4c65..0000000000 --- a/libopenage/gui/game_main_link.cpp +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#include "game_main_link.h" - -#include - -#include "../legacy_engine.h" -#include "engine_link.h" - -namespace openage::gui { - -namespace { -const int registration = qmlRegisterType("yay.sfttech.openage", 1, 0, "GameMain"); -} - -GameMainLink::GameMainLink(QObject *parent) : - GuiItemQObject{parent}, - QQmlParserStatus{}, - GuiItem{this}, - state{}, - active{}, - engine{} { - Q_UNUSED(registration); -} - -GameMainLink::~GameMainLink() = default; - -void GameMainLink::classBegin() { -} - -void GameMainLink::on_core_adopted() { - QObject::connect(&unwrap(this)->gui_signals, &GameMainSignals::game_running, this, &GameMainLink::on_game_running); -} - -void GameMainLink::componentComplete() { - static auto f = [](GameMainHandle *_this) { - _this->announce_running(); - }; - this->i(f); -} - -GameMainLink::State GameMainLink::get_state() const { - return this->state; -} - -EngineLink *GameMainLink::get_engine() const { - return this->engine; -} - -void GameMainLink::set_engine(EngineLink *engine) { - static auto f = [](GameMainHandle *_this, LegacyEngine *engine) { - _this->set_engine(engine); - }; - this->s(f, this->engine, engine); -} - -void GameMainLink::clear() { - static auto f = [](GameMainHandle *_this) { - _this->clear(); - }; - this->i(f); -} - -void GameMainLink::on_game_running(bool running) { - auto state = running ? State::Running : State::Null; - - if (this->state != state) { - this->state = state; - emit this->state_changed(); - } -} - -} // namespace openage::gui diff --git a/libopenage/gui/game_main_link.h b/libopenage/gui/game_main_link.h deleted file mode 100644 index 0277cab6b8..0000000000 --- a/libopenage/gui/game_main_link.h +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2015-2018 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -#include "guisys/link/gui_item.h" - -#include "../gamestate/old/game_main.h" - -namespace openage { -namespace gui { - -class EngineLink; -class GameMainLink; - -}} // namespace openage::gui - -namespace qtsdl { -template<> -struct Wrap { - using Type = openage::gui::GameMainLink; -}; - -template<> -struct Unwrap { - using Type = openage::GameMainHandle; -}; - -} // namespace qtsdl - -namespace openage { -namespace gui { - -class GameMainLink : public qtsdl::GuiItemQObject, public QQmlParserStatus, public qtsdl::GuiItem { - Q_OBJECT - - Q_INTERFACES(QQmlParserStatus) - Q_ENUMS(State) - Q_PROPERTY(State state READ get_state NOTIFY state_changed) - Q_PROPERTY(openage::gui::EngineLink* engine READ get_engine WRITE set_engine) - -public: - explicit GameMainLink(QObject *parent=nullptr); - virtual ~GameMainLink(); - - enum class State { - Null, Running - }; - - State get_state() const; - - EngineLink* get_engine() const; - void set_engine(EngineLink *engine); - - Q_INVOKABLE void clear(); - -signals: - void state_changed(); - -private slots: - void on_game_running(bool running); - -private: - virtual void classBegin() override; - virtual void on_core_adopted() override; - virtual void componentComplete() override; - - State state; - bool active; - EngineLink *engine; -}; - -}} // namespace openage::gui diff --git a/libopenage/gui/game_saver.cpp b/libopenage/gui/game_saver.cpp deleted file mode 100644 index f6793207ca..0000000000 --- a/libopenage/gui/game_saver.cpp +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2016-2021 the openage authors. See copying.md for legal info. - -#include "game_saver.h" - -#include - -#include "../gamestate/old/game_save.h" -#include "../gamestate/old/game_main.h" -#include "../gamestate/old/generator.h" - -#include "game_main_link.h" -#include "generator_link.h" - -namespace openage::gui { - -namespace { -const int registration = qmlRegisterType("yay.sfttech.openage", 1, 0, "GameSaver"); -} - -GameSaver::GameSaver(QObject *parent) - : - QObject{parent}, - game{}, - generator_parameters{} { - Q_UNUSED(registration); -} - -GameSaver::~GameSaver() = default; - -QString GameSaver::get_error_string() const { - return this->error_string; -} - -// called when the save-game button is pressed: -void GameSaver::activate() { - static auto f = [] (GameMainHandle *game, - Generator *generator, - std::shared_ptr callback) { - - QString error_msg; - - if (!game->is_game_running()) { - error_msg = "no open game to save"; - } else { - auto filename = generator->getv("load_filename"); - gameio::save(game->get_game(), filename); - } - - emit callback->error_message(error_msg); - }; - - if (this->game && this->generator_parameters) { - std::shared_ptr callback = std::make_shared(); - QObject::connect(callback.get(), - &GameSaverSignals::error_message, - this, - &GameSaver::on_processed); - - this->game->i(f, this->generator_parameters, callback); - } - else { - QString error_msg = "unknown error"; - - if (!this->game) { - error_msg = "provide 'game' before saving"; - } - - if (!this->generator_parameters) { - error_msg = "provide 'generatorParameters' before saving"; - } - else { - ENSURE(false, "unhandled case for refusal to create a game"); - } - - this->on_processed(error_msg); - } -} - -void GameSaver::clearErrors() { - this->error_string.clear(); - emit this->error_string_changed(); -} - -void GameSaver::on_processed(const QString &error_string) { - this->error_string = error_string; - emit this->error_string_changed(); -} - -} // namespace openage::gui diff --git a/libopenage/gui/game_saver.h b/libopenage/gui/game_saver.h deleted file mode 100644 index 13a4183de6..0000000000 --- a/libopenage/gui/game_saver.h +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2016-2016 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -namespace openage { -namespace gui { - -class GameMainLink; -class GeneratorLink; - -class GameSaver : public QObject { - Q_OBJECT - - Q_ENUMS(State) - Q_PROPERTY(QString errorString READ get_error_string NOTIFY error_string_changed) - Q_PROPERTY(openage::gui::GameMainLink* game MEMBER game NOTIFY game_changed) - Q_PROPERTY(openage::gui::GeneratorLink* generatorParameters MEMBER generator_parameters NOTIFY generator_parameters_changed) - -public: - explicit GameSaver(QObject *parent=nullptr); - virtual ~GameSaver(); - - QString get_error_string() const; - - Q_INVOKABLE void activate(); - Q_INVOKABLE void clearErrors(); - -public slots: - void on_processed(const QString &error_string); - -signals: - void error_string_changed(); - void game_changed(); - void generator_parameters_changed(); - -private: - QString error_string; - GameMainLink *game; - GeneratorLink *generator_parameters; -}; - -class GameSaverSignals : public QObject { - Q_OBJECT - -public: -signals: - void error_message(const QString &error); -}; - -}} // namespace openage::gui diff --git a/libopenage/gui/game_spec_link.cpp b/libopenage/gui/game_spec_link.cpp deleted file mode 100644 index dea041ce5c..0000000000 --- a/libopenage/gui/game_spec_link.cpp +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#include "game_spec_link.h" - -#include - -#include "assetmanager_link.h" - -namespace openage::gui { - -namespace { -const int registration = qmlRegisterType("yay.sfttech.openage", 1, 0, "GameSpec"); -const int registration_of_ptr = qRegisterMetaType>("shared_ptr"); -} - -GameSpecLink::GameSpecLink(QObject *parent) - : - GuiItemQObject{parent}, - QQmlParserStatus{}, - GuiItem{this}, - state{}, - active{}, - asset_manager{}, - terrain_id_count{} { - Q_UNUSED(registration); - Q_UNUSED(registration_of_ptr); -} - -GameSpecLink::~GameSpecLink() = default; - -void GameSpecLink::classBegin() { -} - -void GameSpecLink::on_core_adopted() { - auto core = unwrap(this); - QObject::connect(core->gui_signals.get(), &GameSpecSignals::load_job_finished, this, &GameSpecLink::on_load_job_finished); - QObject::connect(core->gui_signals.get(), &GameSpecSignals::game_spec_loaded, this, &GameSpecLink::on_game_spec_loaded); -} - -void GameSpecLink::componentComplete() { - this->on_load_job_finished(); -} - -void GameSpecLink::on_load_job_finished() { - static auto f = [] (GameSpecHandle *_this) { - _this->announce_spec(); - }; - this->i(f); -} - -void GameSpecLink::on_game_spec_loaded(std::shared_ptr loaded_game_spec) { - this->loaded_game_spec = loaded_game_spec; - - this->terrain_id_count = this->loaded_game_spec->get_terrain_meta()->terrain_id_count; - - this->state = State::Ready; - - emit this->state_changed(); - emit this->terrain_id_count_changed(); - emit this->game_spec_loaded(this, this->loaded_game_spec); -} - -std::shared_ptr GameSpecLink::get_loaded_spec() { - return this->loaded_game_spec; -} - -GameSpecLink::State GameSpecLink::get_state() const { - return this->state; -} - -void GameSpecLink::invalidate() { - static auto f = [] (GameSpecHandle *_this) { - _this->invalidate(); - }; - this->i(f); - - this->set_state(this->active ? State::Loading : State::Null); -} - -bool GameSpecLink::get_active() const { - return this->active; -} - -void GameSpecLink::set_active(bool active) { - static auto f = [] (GameSpecHandle *_this, bool active) { - _this->set_active(active); - }; - this->s(f, this->active, active); - - this->set_state(this->active && this->state == State::Null ? State::Loading : this->state); -} - -AssetManagerLink* GameSpecLink::get_asset_manager() const { - return this->asset_manager; -} - -void GameSpecLink::set_asset_manager(AssetManagerLink *asset_manager) { - static auto f = [] (GameSpecHandle *_this, LegacyAssetManager *asset_manager) { - _this->set_asset_manager(asset_manager); - }; - this->s(f, this->asset_manager, asset_manager); -} - -int GameSpecLink::get_terrain_id_count() const { - return this->terrain_id_count; -} - -void GameSpecLink::set_state(GameSpecLink::State state) { - if (state != this->state) { - this->state = state; - emit this->state_changed(); - } -} - -} // namespace openage::gui diff --git a/libopenage/gui/game_spec_link.h b/libopenage/gui/game_spec_link.h deleted file mode 100644 index ac2b2dd96e..0000000000 --- a/libopenage/gui/game_spec_link.h +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -#include -#include - -#include "guisys/link/gui_item.h" - -#include "../gamestate/old/game_spec.h" - -namespace openage { - -class GameSpec; - -namespace gui { - -class AssetManagerLink; -class GameSpecLink; - -} // namespace gui -} // namespace openage - -namespace qtsdl { -template <> -struct Wrap { - using Type = openage::gui::GameSpecLink; -}; - -template <> -struct Unwrap { - using Type = openage::GameSpecHandle; -}; - -} // namespace qtsdl - -namespace openage { -namespace gui { - -class GameSpecLink : public qtsdl::GuiItemQObject - , public QQmlParserStatus - , public qtsdl::GuiItem { - Q_OBJECT - - Q_INTERFACES(QQmlParserStatus) - Q_ENUMS(State) - Q_PROPERTY(State state READ get_state NOTIFY state_changed) - Q_PROPERTY(bool active READ get_active WRITE set_active) - Q_PROPERTY(openage::gui::AssetManagerLink *assetManager READ get_asset_manager WRITE set_asset_manager) - Q_PROPERTY(int terrainIdCount READ get_terrain_id_count NOTIFY terrain_id_count_changed) - -public: - explicit GameSpecLink(QObject *parent = nullptr); - virtual ~GameSpecLink(); - - enum class State { - Null, - Loading, - Ready - }; - - State get_state() const; - - bool get_active() const; - void set_active(bool active); - - AssetManagerLink *get_asset_manager() const; - void set_asset_manager(AssetManagerLink *asset_manager); - - int get_terrain_id_count() const; - - Q_INVOKABLE void invalidate(); - - std::shared_ptr get_loaded_spec(); - -signals: - /** - * Pass loaded assets to the image provider. - * - * Provider will check if it's still attached to that spec. - * Also it may be invalidated in the meantime, so share the ownership. - */ - void game_spec_loaded(GameSpecLink *game_spec, std::shared_ptr loaded_game_spec); - - void state_changed(); - void terrain_id_count_changed(); - -private slots: - void on_load_job_finished(); - void on_game_spec_loaded(std::shared_ptr loaded_game_spec); - -private: - virtual void classBegin() override; - virtual void on_core_adopted() override; - virtual void componentComplete() override; - - void set_state(State state); - - State state; - bool active; - AssetManagerLink *asset_manager; - int terrain_id_count; - - std::shared_ptr loaded_game_spec; -}; - -} // namespace gui -} // namespace openage diff --git a/libopenage/gui/generator_link.cpp b/libopenage/gui/generator_link.cpp deleted file mode 100644 index 1b0be636aa..0000000000 --- a/libopenage/gui/generator_link.cpp +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. - -#include "generator_link.h" - -#include - -#include "guisys/link/gui_property_map_impl.h" - -namespace openage::gui { - -namespace -{ -const int registration = qmlRegisterType("yay.sfttech.openage", 1, 0, "GeneratorParameters"); -} - -GeneratorLink::GeneratorLink(QObject *parent) - : - GuiListModel{parent}, - GuiItemListModel{this} { - Q_UNUSED(registration); -} - -GeneratorLink::~GeneratorLink() = default; - -} // namespace openage::gui diff --git a/libopenage/gui/generator_link.h b/libopenage/gui/generator_link.h deleted file mode 100644 index 7dd3f0567f..0000000000 --- a/libopenage/gui/generator_link.h +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include "gamestate/old/generator.h" - -#include "guisys/link/gui_item_list_model.h" -#include "guisys/link/gui_list_model.h" - -namespace openage { -class Generator; -namespace gui { -class GeneratorLink; -} -} // namespace openage - -namespace qtsdl { -template <> -struct Wrap { - using Type = openage::gui::GeneratorLink; -}; - -template <> -struct Unwrap { - using Type = openage::Generator; -}; - -} // namespace qtsdl - -namespace openage { -namespace gui { - -class GeneratorLink : public qtsdl::GuiListModel - , public qtsdl::GuiItemListModel { - Q_OBJECT - -public: - GeneratorLink(QObject *parent = nullptr); - virtual ~GeneratorLink(); -}; - -} // namespace gui -} // namespace openage diff --git a/libopenage/gui/gui.cpp b/libopenage/gui/gui.cpp deleted file mode 100644 index b9b600d8c5..0000000000 --- a/libopenage/gui/gui.cpp +++ /dev/null @@ -1,191 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -// include first to make opengl and libepoxy happy. -#include "../shader/program.h" -#include "../shader/shader.h" - -#include "gui.h" - -#include "../legacy_engine.h" -#include "../util/path.h" -#include "engine_info.h" - - -namespace openage { -namespace gui { - - -GUI::GUI(SDL_Window *window, - const std::string &source, - const std::string &rootdir, - EngineQMLInfo *info) : - application{}, - render_updater{}, - renderer{window}, - game_logic_updater{}, - image_provider_by_filename{ - &render_updater, - GuiGameSpecImageProvider::Type::ByFilename}, - image_provider_by_graphic_id{ - &render_updater, - GuiGameSpecImageProvider::Type::ByGraphicId}, - image_provider_by_terrain_id{ - &render_updater, - GuiGameSpecImageProvider::Type::ByTerrainId}, - engine{ - &renderer, - {&image_provider_by_filename, - &image_provider_by_graphic_id, - &image_provider_by_terrain_id}, - info}, - subtree{ - &renderer, - &game_logic_updater, - &engine, - source, - rootdir}, - input{&renderer, &game_logic_updater} { - info->display->register_resize_action(this); - info->display->register_input_action(this); - info->display->register_drawhud_action(this); - - util::Path shader_dir = info->asset_dir / "shaders"; - - const char *shader_header_code = "#version 120\n"; - - auto text_vert_file = (shader_dir / "identity.vert.glsl").open(); - std::string texture_vert_code = text_vert_file.read(); - auto plaintexture_vert = std::make_unique( - GL_VERTEX_SHADER, - std::initializer_list{shader_header_code, texture_vert_code.c_str()}); - text_vert_file.close(); - - auto text_frag_file = (shader_dir / "maptexture.frag.glsl").open(); - std::string texture_frag_code = text_frag_file.read(); - auto plaintexture_frag = std::make_unique( - GL_FRAGMENT_SHADER, - std::initializer_list{shader_header_code, texture_frag_code.c_str()}); - text_vert_file.close(); - - this->textured_screen_quad_shader = std::make_unique( - plaintexture_vert.get(), - plaintexture_frag.get()); - - this->textured_screen_quad_shader->link(); - this->tex_loc = this->textured_screen_quad_shader->get_uniform_id("texture"); - this->textured_screen_quad_shader->use(); - glUniform1i(this->tex_loc, 0); - this->textured_screen_quad_shader->stopusing(); - - const float screen_quad[] = { - -1.f, - -1.f, - 1.f, - -1.f, - 1.f, - 1.f, - -1.f, - 1.f, - }; - - glGenBuffers(1, &this->screen_quad_vbo); - - glBindBuffer(GL_ARRAY_BUFFER, this->screen_quad_vbo); - glBufferData(GL_ARRAY_BUFFER, sizeof(screen_quad), screen_quad, GL_STATIC_DRAW); - glBindBuffer(GL_ARRAY_BUFFER, 0); -} - -GUI::~GUI() { - glDeleteBuffers(1, &this->screen_quad_vbo); -} - -void GUI::process_events() { - this->game_logic_updater.process_callbacks(); - this->application.processEvents(); -} - -bool GUI::on_resize(coord::viewport_delta new_size) { - this->renderer.resize(new_size.x, new_size.y); - return true; -} - -bool GUI::on_input(SDL_Event *event) { - return not this->input.process(event); -} - -namespace { -/** - * Restores blending function. - */ -class BlendPreserver { -public: - BlendPreserver() : - was_on{}, - src{}, - dst{} { - glGetBooleanv(GL_BLEND, &this->was_on); - - if (this->was_on != GL_FALSE) { - glGetIntegerv(GL_BLEND_SRC_ALPHA, &this->src); - glGetIntegerv(GL_BLEND_DST_ALPHA, &this->dst); - } - } - - ~BlendPreserver() { - if (this->was_on != GL_FALSE) { - glEnable(GL_BLEND); - glBlendFunc(this->src, this->dst); - } - else { - glDisable(GL_BLEND); - } - } - -private: - GLboolean was_on; - GLint src; - GLint dst; -}; - -} // namespace - -bool GUI::on_drawhud() { - this->render_updater.process_callbacks(); - - BlendPreserver preserve_blend; - - auto tex = this->renderer.render(); - - glEnable(GL_BLEND); - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - - this->textured_screen_quad_shader->use(); - - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, tex); - - glEnableVertexAttribArray(this->textured_screen_quad_shader->pos_id); - - glBindBuffer(GL_ARRAY_BUFFER, this->screen_quad_vbo); - glVertexAttribPointer( - this->textured_screen_quad_shader->pos_id, - 2, - GL_FLOAT, - GL_FALSE, - 2 * sizeof(float), - 0); - - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - - glDisableVertexAttribArray(this->textured_screen_quad_shader->pos_id); - glBindBuffer(GL_ARRAY_BUFFER, 0); - - glBindTexture(GL_TEXTURE_2D, 0); - - this->textured_screen_quad_shader->stopusing(); - - return true; -} - -} // namespace gui -} // namespace openage diff --git a/libopenage/gui/gui.h b/libopenage/gui/gui.h deleted file mode 100644 index 0abb70ff5e..0000000000 --- a/libopenage/gui/gui.h +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include - -#include "../handlers.h" -#include "guisys/public/gui_engine.h" -#include "guisys/public/gui_event_queue.h" -#include "guisys/public/gui_input.h" -#include "guisys/public/gui_renderer.h" -#include "guisys/public/gui_subtree.h" -#include "integration/public/gui_application_with_logger.h" -#include "integration/public/gui_game_spec_image_provider.h" - - -namespace qtsdl { -class GuiSingletonItemsInfo; -} // namespace qtsdl - -namespace openage { -namespace shader { -class Program; -} // namespace shader - -namespace gui { - -class EngineQMLInfo; - - -/** - * Main entry point for the openage Qt-based user interface. - * - * Legacy variant for the "old" renderer. - */ -class GUI : public InputHandler - , public ResizeHandler - , public HudHandler { -public: - explicit GUI(SDL_Window *window, - const std::string &source, - const std::string &rootdir, - EngineQMLInfo *info = nullptr); - virtual ~GUI(); - - void process_events(); - -private: - virtual bool on_resize(coord::viewport_delta new_size) override; - virtual bool on_input(SDL_Event *event) override; - virtual bool on_drawhud() override; - - GLint tex_loc; - GLuint screen_quad_vbo; - - GuiApplicationWithLogger application; - qtsdl::GuiEventQueue render_updater; - qtsdl::GuiRenderer renderer; - qtsdl::GuiEventQueue game_logic_updater; - GuiGameSpecImageProvider image_provider_by_filename; - GuiGameSpecImageProvider image_provider_by_graphic_id; - GuiGameSpecImageProvider image_provider_by_terrain_id; - qtsdl::GuiEngine engine; - qtsdl::GuiSubtree subtree; - qtsdl::GuiInput input; - - // needs to be deallocated before the GuiRenderer - // it accesses opengl api functions which require a - // current context: - std::unique_ptr textured_screen_quad_shader; -}; - -} // namespace gui -} // namespace openage diff --git a/libopenage/gui/guisys/CMakeLists.txt b/libopenage/gui/guisys/CMakeLists.txt deleted file mode 100644 index 922c3ec72c..0000000000 --- a/libopenage/gui/guisys/CMakeLists.txt +++ /dev/null @@ -1,43 +0,0 @@ -list(APPEND QT_SDL_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/link/gui_item.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/link/gui_list_model.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/link/gui_property_map_impl.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/link/gui_singleton_item.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/link/qml_engine_with_singleton_items_info.cpp -) - -list(APPEND QT_SDL_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/public/gui_application.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/public/gui_engine.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/public/gui_event_queue.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/public/gui_image_provider.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/public/gui_input.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/public/gui_property_map.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/public/gui_renderer.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/public/gui_subtree.cpp -) - -list(APPEND QT_SDL_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/private/game_logic_caller.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/private/gui_application_impl.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/private/gui_callback.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/private/gui_ctx_setup.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/private/gui_dedicated_thread.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/private/gui_engine_impl.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/private/gui_event_queue_impl.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/private/gui_image_provider_impl.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/private/gui_input_impl.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/private/gui_renderer_impl.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/private/gui_rendering_setup_routines.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/private/gui_subtree_impl.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/private/opengl_debug_logger.cpp -) - -list(APPEND QT_SDL_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/private/livereload/deferred_initial_constant_property_values.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/private/livereload/gui_live_reloader.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/private/livereload/recursive_directory_watcher.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/private/livereload/recursive_directory_watcher_worker.cpp -) - -set(QT_SDL_SOURCES ${QT_SDL_SOURCES} PARENT_SCOPE) diff --git a/libopenage/gui/guisys/link/gui_item.cpp b/libopenage/gui/guisys/link/gui_item.cpp deleted file mode 100644 index d8116501c4..0000000000 --- a/libopenage/gui/guisys/link/gui_item.cpp +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. - -#include "gui_item.h" - -namespace qtsdl { - - -QString name_tidier(const char *name) { - QString cleaner_name = QString::fromLatin1(name); - cleaner_name.remove(QRegularExpression("qtsdl|PersistentCoreHolder")); - return cleaner_name; -} - -GuiItemQObject::GuiItemQObject(QObject *parent) - : - QObject{parent}, - GuiItemBase{} { -} - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/link/gui_singleton_item.cpp b/libopenage/gui/guisys/link/gui_singleton_item.cpp deleted file mode 100644 index c0348a4b90..0000000000 --- a/libopenage/gui/guisys/link/gui_singleton_item.cpp +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. - -#include "gui_singleton_item.h" - -namespace qtsdl { - -GuiSingletonItem::GuiSingletonItem(QObject *parent) - : - QObject{parent}, - GuiItemLink{} { -} - -GuiSingletonItem::~GuiSingletonItem() = default; - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/link/gui_singleton_item.h b/libopenage/gui/guisys/link/gui_singleton_item.h deleted file mode 100644 index 73c322ba4d..0000000000 --- a/libopenage/gui/guisys/link/gui_singleton_item.h +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -#include "gui_item_link.h" - -namespace qtsdl { - -class GuiSingletonItem : public QObject, public GuiItemLink { - Q_OBJECT - -public: - explicit GuiSingletonItem(QObject *parent=nullptr); - virtual ~GuiSingletonItem(); -}; - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/link/qml_engine_with_singleton_items_info.cpp b/libopenage/gui/guisys/link/qml_engine_with_singleton_items_info.cpp deleted file mode 100644 index 8b6f403f29..0000000000 --- a/libopenage/gui/guisys/link/qml_engine_with_singleton_items_info.cpp +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. - -#include "qml_engine_with_singleton_items_info.h" - -#include - -namespace qtsdl { - -QmlEngineWithSingletonItemsInfo::QmlEngineWithSingletonItemsInfo(std::vector> &&image_providers, GuiSingletonItemsInfo *singleton_items_info) - : - QmlEngineWithSingletonItemsInfo{image_providers, singleton_items_info} { -} - -QmlEngineWithSingletonItemsInfo::QmlEngineWithSingletonItemsInfo(std::vector> &image_providers, GuiSingletonItemsInfo *singleton_items_info) - : - QQmlEngine{}, - image_providers(image_providers.size()), - singleton_items_info{singleton_items_info} { - - std::transform(std::begin(image_providers), std::end(image_providers), std::begin(this->image_providers), [] (const std::unique_ptr &image_provider) { - return image_provider.get(); - }); - - std::for_each(std::begin(image_providers), std::end(image_providers), [this] (std::unique_ptr &image_provider) { - auto id = image_provider->get_id(); - this->addImageProvider(id, image_provider.release()); - }); -} - -QmlEngineWithSingletonItemsInfo::~QmlEngineWithSingletonItemsInfo() { - std::for_each(std::begin(this->image_providers), std::end(this->image_providers), [this] (GuiImageProviderImpl *image_provider) { - image_provider->give_up(); - this->removeImageProvider(image_provider->get_id()); - }); -} - -GuiSingletonItemsInfo* QmlEngineWithSingletonItemsInfo::get_singleton_items_info() const { - return this->singleton_items_info; -} - -std::vector QmlEngineWithSingletonItemsInfo::get_image_providers() const { - return this->image_providers; -} - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/link/qml_engine_with_singleton_items_info.h b/libopenage/gui/guisys/link/qml_engine_with_singleton_items_info.h deleted file mode 100644 index 2470f24cce..0000000000 --- a/libopenage/gui/guisys/link/qml_engine_with_singleton_items_info.h +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -#include - -#include "../private/gui_image_provider_impl.h" - -namespace qtsdl { - -class GuiSingletonItemsInfo; - -/** - * The Qml Engine used by openage. - * - * It's extended to contain the "singleton items info" and a list of image providers. - * The singleton item info is just a struct that allows to carry some variables - * in the qml-engine, namely the openage-engine. - * - * That way, the openage-engine and the qml-engine have a 1:1 relation and - * qml can access the main engine directly. - */ -class QmlEngineWithSingletonItemsInfo : public QQmlEngine { - Q_OBJECT - -public: - explicit QmlEngineWithSingletonItemsInfo(std::vector> &&image_providers, GuiSingletonItemsInfo *singleton_items_info=nullptr); - explicit QmlEngineWithSingletonItemsInfo(std::vector> &image_providers, GuiSingletonItemsInfo *singleton_items_info=nullptr); - virtual ~QmlEngineWithSingletonItemsInfo(); - - GuiSingletonItemsInfo* get_singleton_items_info() const; - std::vector get_image_providers() const; - -private: - std::vector image_providers; - GuiSingletonItemsInfo *singleton_items_info; -}; - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/link/qtsdl_checked_static_cast.h b/libopenage/gui/guisys/link/qtsdl_checked_static_cast.h deleted file mode 100644 index ecd5506984..0000000000 --- a/libopenage/gui/guisys/link/qtsdl_checked_static_cast.h +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -namespace qtsdl { - -template -T checked_static_cast(U *u) { - assert(dynamic_cast(u)); - return static_cast(u); -} - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/game_logic_caller.cpp b/libopenage/gui/guisys/private/game_logic_caller.cpp deleted file mode 100644 index 3e277f686b..0000000000 --- a/libopenage/gui/guisys/private/game_logic_caller.cpp +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. - -#include "game_logic_caller.h" - -#include "gui_callback.h" - -namespace qtsdl { - -GameLogicCaller::GameLogicCaller() - : - QObject{} { -} - -void GameLogicCaller::set_game_logic_callback(GuiCallback *game_logic_callback) { - QObject::disconnect(this, &GameLogicCaller::in_game_logic_thread, nullptr, nullptr); - QObject::disconnect(this, &GameLogicCaller::in_game_logic_thread_blocking, nullptr, nullptr); - QObject::connect(this, &GameLogicCaller::in_game_logic_thread, game_logic_callback, &GuiCallback::process); - QObject::connect(this, &GameLogicCaller::in_game_logic_thread_blocking, game_logic_callback, &GuiCallback::process_blocking, Qt::DirectConnection); -} - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/game_logic_caller.h b/libopenage/gui/guisys/private/game_logic_caller.h deleted file mode 100644 index ef858b67b4..0000000000 --- a/libopenage/gui/guisys/private/game_logic_caller.h +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -#include - -namespace qtsdl { - -class GuiCallback; - -/** - * Attaches to the GuiCallbackImpl. - */ -class GameLogicCaller : public QObject { - Q_OBJECT - -public: - explicit GameLogicCaller(); - - /** - * Set up signal to be able to run code in the game logic thread. - */ - void set_game_logic_callback(GuiCallback *game_logic_callback); - -signals: - void in_game_logic_thread(const std::function& f) const; - void in_game_logic_thread_blocking(const std::function& f) const; -}; - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/gui_application_impl.cpp b/libopenage/gui/guisys/private/gui_application_impl.cpp deleted file mode 100644 index bd5f6eeb78..0000000000 --- a/libopenage/gui/guisys/private/gui_application_impl.cpp +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. - -#include "gui_application_impl.h" - -#include -#include - -#include -#include -#include - -namespace qtsdl { - -std::weak_ptr GuiApplicationImpl::instance; - -std::shared_ptr GuiApplicationImpl::get() { - std::shared_ptr candidate = GuiApplicationImpl::instance.lock(); - - assert(!candidate || std::this_thread::get_id() == candidate->owner); - - // Ensure that OpenGL is used and not OpenGL ES. - // This occurred in macos. See issue #1177 (PR #1179) - if (!candidate) { - QSurfaceFormat format; - format.setRenderableType(QSurfaceFormat::OpenGL); - QSurfaceFormat::setDefaultFormat(format); - } - - return candidate ? candidate : std::shared_ptr{new GuiApplicationImpl}; -} - -GuiApplicationImpl::~GuiApplicationImpl() { - assert(std::this_thread::get_id() == this->owner); -} - -void GuiApplicationImpl::processEvents() { - assert(std::this_thread::get_id() == this->owner); -#ifndef __APPLE__ - this->app.processEvents(); -#endif -} - -namespace { - int argc = 1; - char arg[] = "qtsdl"; - char *argv = &arg[0]; -} - -GuiApplicationImpl::GuiApplicationImpl() - : -#ifndef NDEBUG - owner{std::this_thread::get_id()}, -#endif - app{argc, &argv} -{ - // Set locale back to POSIX for the decimal point parsing (see qcoreapplication.html#locale-settings). - std::locale::global(std::locale().combine>(std::locale::classic())); - - qInfo() << "Compiled with Qt" << QT_VERSION_STR << "and run with Qt" << qVersion(); -} - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/gui_application_impl.h b/libopenage/gui/guisys/private/gui_application_impl.h deleted file mode 100644 index 14cd858691..0000000000 --- a/libopenage/gui/guisys/private/gui_application_impl.h +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include - -#include - -namespace qtsdl { - -/** - * Houses gui logic event queue. - * - * To launch it in a dedicated thread, use qtsdl::GuiDedicatedThread instead. - */ -class GuiApplicationImpl { -public: - static std::shared_ptr get(); - - ~GuiApplicationImpl(); - - void processEvents(); - -private: - GuiApplicationImpl(); - - GuiApplicationImpl(const GuiApplicationImpl&) = delete; - GuiApplicationImpl& operator=(const GuiApplicationImpl&) = delete; - -#ifndef NDEBUG - const std::thread::id owner; -#endif - - QGuiApplication app; - - static std::weak_ptr instance; -}; - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/gui_callback.cpp b/libopenage/gui/guisys/private/gui_callback.cpp deleted file mode 100644 index 57c63ee01c..0000000000 --- a/libopenage/gui/guisys/private/gui_callback.cpp +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. - -#include "gui_callback.h" - -#include - -namespace qtsdl { - -namespace { -const int registration = qRegisterMetaType>("function"); -} - -GuiCallback::GuiCallback() - : - QObject{} { - Q_UNUSED(registration); -} - -GuiCallback::~GuiCallback() = default; - -void GuiCallback::process(const std::function &f) { - f(); -} - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/gui_callback.h b/libopenage/gui/guisys/private/gui_callback.h deleted file mode 100644 index e42515b15f..0000000000 --- a/libopenage/gui/guisys/private/gui_callback.h +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -#include -#include - -namespace qtsdl { - -class GuiCallback : public QObject { - Q_OBJECT - -public: - GuiCallback(); - virtual ~GuiCallback(); - -signals: - void process_blocking(const std::function &f); - -public slots: - void process(const std::function &f); -}; - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/gui_ctx_setup.cpp b/libopenage/gui/guisys/private/gui_ctx_setup.cpp deleted file mode 100644 index aea120b89c..0000000000 --- a/libopenage/gui/guisys/private/gui_ctx_setup.cpp +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. - -#include "gui_ctx_setup.h" - -#include - -#include - -#include "opengl_debug_logger.h" -#include "platforms/context_extraction.h" - -namespace qtsdl { - -CtxExtractionException::CtxExtractionException(const std::string &what_arg) : - std::runtime_error{what_arg} { -} - -QOpenGLContext *CtxExtractionMode::get_ctx() { - return &this->ctx; -} - -GuiUniqueRenderingContext::GuiUniqueRenderingContext(SDL_Window *window) : - CtxExtractionMode{} { - QVariant handle; - WId id; - - // std::tie(handle, id) = extract_native_context(window); - - // if (handle.isValid()) { - // // pass the SDL opengl context so qt can use it - // this->ctx.setNativeHandle(handle); - this->ctx.create(); - assert(this->ctx.isValid()); - - // reuse the sdl window - QWindow *w = QWindow::fromWinId(id); // fails on Wayland! - w->setSurfaceType(QSurface::OpenGLSurface); - - if (this->ctx.makeCurrent(w)) { - return; - } - // } - - throw CtxExtractionException("adding GUI to the main rendering context failed"); -} - -void GuiUniqueRenderingContext::pre_render() { -} - -void GuiUniqueRenderingContext::post_render() { -} - -GuiSeparateRenderingContext::GuiSeparateRenderingContext(SDL_Window *window) : - CtxExtractionMode{} { - QVariant handle; - - // std::tie(handle, this->make_current_back) = extract_native_context_and_switchback_func(window); - - // if (handle.isValid()) { - // this->main_ctx.setNativeHandle(handle); - this->main_ctx.create(); - assert(this->main_ctx.isValid()); - - auto context_debug_parameters = get_current_opengl_debug_parameters(this->main_ctx); - - this->ctx.setFormat(this->main_ctx.format()); - this->ctx.setShareContext(&this->main_ctx); - this->ctx.create(); - assert(this->ctx.isValid()); - assert(!(this->main_ctx.format().options() ^ this->ctx.format().options()).testFlag(QSurfaceFormat::DebugContext)); - - this->offscreen_surface.setFormat(this->ctx.format()); - this->offscreen_surface.create(); - - this->pre_render(); - apply_opengl_debug_parameters(context_debug_parameters, this->ctx); - this->post_render(); - // } - // else { - // throw CtxExtractionException("creating separate context for GUI failed"); - // } -} - -GuiSeparateRenderingContext::~GuiSeparateRenderingContext() { - this->pre_render(); - this->ctx_logger.reset(); - this->post_render(); -} - -void GuiSeparateRenderingContext::pre_render() { - if (!this->ctx.makeCurrent(&this->offscreen_surface)) { - assert(false); - return; - } -} - -void GuiSeparateRenderingContext::post_render() { - this->make_current_back(); -} - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/gui_ctx_setup.h b/libopenage/gui/guisys/private/gui_ctx_setup.h deleted file mode 100644 index bcc88e932a..0000000000 --- a/libopenage/gui/guisys/private/gui_ctx_setup.h +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2017-2017 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include -#include - -#include -#include - -struct SDL_Window; - -QT_FORWARD_DECLARE_CLASS(QOpenGLDebugLogger) - -namespace qtsdl { - -class CtxExtractionException : public std::runtime_error { -public: - explicit CtxExtractionException(const std::string &what_arg); -}; - -/** - * Abstract base for the method of getting a Qt-usable context. - */ -class CtxExtractionMode { -public: - virtual ~CtxExtractionMode() { - } - - /** - * @return context that can be used by Qt - */ - QOpenGLContext* get_ctx(); - - /** - * Function that must be called before rendering the GUI. - */ - virtual void pre_render() = 0; - - /** - * Function that must be called after rendering the GUI. - */ - virtual void post_render() = 0; - -protected: - QOpenGLContext ctx; -}; - -/** - * Use the same context to render the GUI. - */ -class GuiUniqueRenderingContext : public CtxExtractionMode { -public: - explicit GuiUniqueRenderingContext(SDL_Window *window); - - virtual void pre_render() override; - virtual void post_render() override; -}; - -/** - * Create a separate context to render the GUI, make it shared with the main context. - */ -class GuiSeparateRenderingContext : public CtxExtractionMode { -public: - explicit GuiSeparateRenderingContext(SDL_Window *window); - virtual ~GuiSeparateRenderingContext(); - - virtual void pre_render() override; - virtual void post_render() override; - -private: - /** - * GL context of the game - */ - QOpenGLContext main_ctx; - - /** - * GL debug logger of the GL context of the GUI - */ - std::unique_ptr ctx_logger; - - /** - * Function to make the game context current - */ - std::function make_current_back; - - /** - * Surface that is needed to make the GUI context current - */ - QOffscreenSurface offscreen_surface; -}; - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/gui_dedicated_thread.cpp b/libopenage/gui/guisys/private/gui_dedicated_thread.cpp deleted file mode 100644 index 5eab4a77d0..0000000000 --- a/libopenage/gui/guisys/private/gui_dedicated_thread.cpp +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. - -#include "gui_dedicated_thread.h" - -#include -#include - -#include - -#include "gui_application_impl.h" - -namespace qtsdl { - -std::weak_ptr GuiDedicatedThread::instance; - -bool GuiDedicatedThread::exists = false; -std::mutex GuiDedicatedThread::existence_guard; -std::condition_variable GuiDedicatedThread::destroyed; - -GuiDedicatedThread::GuiDedicatedThread() - : - worker{} { - - bool gui_started = false; - std::mutex gui_started_guard; - std::unique_lock lck{gui_started_guard}; - - std::condition_variable proceed_cond; - - this->worker = std::thread{[&] { - auto app = GuiApplicationImpl::get(); - - { - std::unique_lock lckInGui{gui_started_guard}; - gui_started = true; - } - - proceed_cond.notify_one(); - - QCoreApplication::instance()->exec(); - }}; - - proceed_cond.wait(lck, [&] {return gui_started;}); -} - -GuiDedicatedThread::~GuiDedicatedThread() { - QCoreApplication::instance()->quit(); - this->worker.join(); -} - -std::shared_ptr GuiDedicatedThread::get() { - std::shared_ptr candidate; - - std::unique_lock lck{GuiDedicatedThread::existence_guard}; - - GuiDedicatedThread::destroyed.wait(lck, [&candidate] { - return (candidate = GuiDedicatedThread::instance.lock()) || !GuiDedicatedThread::exists; - }); - - if (!candidate) { - GuiDedicatedThread::instance = candidate = std::shared_ptr{new GuiDedicatedThread, [] (GuiDedicatedThread *p) { - delete p; - - if (p) { - std::unique_lock dlck{GuiDedicatedThread::existence_guard}; - GuiDedicatedThread::exists = false; - - dlck.unlock(); - GuiDedicatedThread::destroyed.notify_all(); - } - }}; - - GuiDedicatedThread::exists = true; - - lck.unlock(); - GuiDedicatedThread::destroyed.notify_all(); - } - - return candidate; -} - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/gui_dedicated_thread.h b/libopenage/gui/guisys/private/gui_dedicated_thread.h deleted file mode 100644 index f96339b5c7..0000000000 --- a/libopenage/gui/guisys/private/gui_dedicated_thread.h +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include -#include -#include - -namespace qtsdl { - -/** - * Runs the gui logic in separate thread. - * - * For sharing the thread with something else, use qtsdl::GuiApplicationImpl instead. - */ -class GuiDedicatedThread { -public: - static std::shared_ptr get(); - - ~GuiDedicatedThread(); - -private: - GuiDedicatedThread(); - - std::thread worker; - - static std::weak_ptr instance; - - static bool exists; - static std::mutex existence_guard; - static std::condition_variable destroyed; -}; - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/gui_engine_impl.cpp b/libopenage/gui/guisys/private/gui_engine_impl.cpp deleted file mode 100644 index e83141fb13..0000000000 --- a/libopenage/gui/guisys/private/gui_engine_impl.cpp +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. - -#include "gui_engine_impl.h" - -#include - -#include -#include -#include - -#include "../public/gui_engine.h" -#include "gui_image_provider_impl.h" -#include "gui_renderer_impl.h" - -namespace qtsdl { - -GuiEngineImpl::GuiEngineImpl(GuiRenderer *renderer, - const std::vector &image_providers, - GuiSingletonItemsInfo *singleton_items_info) - : - QObject{}, - renderer{}, - engine{GuiImageProviderImpl::take_ownership(image_providers), singleton_items_info} { - - QThread *gui_thread = QCoreApplication::instance()->thread(); - this->moveToThread(gui_thread); - this->engine.moveToThread(gui_thread); - this->watcher.moveToThread(gui_thread); - - assert(!this->engine.incubationController()); - this->attach_to(GuiRendererImpl::impl(renderer)); - - QObject::connect(this, - &GuiEngineImpl::rootDirsPathsChanged, - &this->watcher, - &RecursiveDirectoryWatcher::rootDirsPathsChanged); - - QObject::connect(&this->watcher, - &RecursiveDirectoryWatcher::changeDetected, - this, - &GuiEngineImpl::onReload); -} - -GuiEngineImpl::~GuiEngineImpl() = default; - -GuiEngineImpl* GuiEngineImpl::impl(GuiEngine *engine) { - return engine->impl.get(); -} - -void GuiEngineImpl::attach_to(GuiRendererImpl *renderer) { - this->renderer = renderer; - this->engine.setIncubationController(this->renderer->get_window()->incubationController()); -} - -QQmlEngine* GuiEngineImpl::get_qml_engine() { - return &this->engine; -} - -void GuiEngineImpl::onReload() { - qDebug("reloading GUI"); - this->engine.clearComponentCache(); - emit this->reload(); -} - -void GuiEngineImpl::add_root_dir_path(const QString &root_dir_path) { - this->root_dirs_paths.push_back(root_dir_path); - emit this->rootDirsPathsChanged(this->root_dirs_paths); -} - -void GuiEngineImpl::remove_root_dir_path(const QString &root_dir_path) { - if (this->root_dirs_paths.removeOne(root_dir_path)) - emit this->rootDirsPathsChanged(this->root_dirs_paths); - else - qWarning() << "Failed to remove path watched by ReloadableQmlEngine."; -} - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/gui_engine_impl.h b/libopenage/gui/guisys/private/gui_engine_impl.h deleted file mode 100644 index 57f78569aa..0000000000 --- a/libopenage/gui/guisys/private/gui_engine_impl.h +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include - -#include "../link/qml_engine_with_singleton_items_info.h" -#include "livereload/recursive_directory_watcher.h" - -QT_FORWARD_DECLARE_CLASS(QQuickWindow) - -namespace qtsdl { - -class GuiRenderer; -class GuiRendererImpl; -class GuiImageProvider; -class GuiEngine; -class GuiSingletonItemsInfo; - -class GuiEngineImpl : public QObject { - Q_OBJECT - -public: - explicit GuiEngineImpl(GuiRenderer *renderer, - const std::vector &image_providers=std::vector(), - GuiSingletonItemsInfo *singleton_items_info=nullptr); - virtual ~GuiEngineImpl(); - - static GuiEngineImpl* impl(GuiEngine *engine); - - QQmlEngine* get_qml_engine(); - - void add_root_dir_path(const QString &root_dir_path); - void remove_root_dir_path(const QString &root_dir_path); - -signals: - void reload(); - void rootDirsPathsChanged(const QStringList&); - -public slots: - void attach_to(GuiRendererImpl *renderer); - void onReload(); - -private: - GuiRendererImpl *renderer; - - QmlEngineWithSingletonItemsInfo engine; - RecursiveDirectoryWatcher watcher; - - QStringList root_dirs_paths; -}; - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/gui_event_queue_impl.cpp b/libopenage/gui/guisys/private/gui_event_queue_impl.cpp deleted file mode 100644 index bef677b91d..0000000000 --- a/libopenage/gui/guisys/private/gui_event_queue_impl.cpp +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. - -#include "gui_event_queue_impl.h" - -#include - -#ifdef __APPLE__ -#include -#endif -#include - -#include "../public/gui_event_queue.h" - -namespace qtsdl { - -GuiEventQueueImpl::GuiEventQueueImpl() - : - thread{QThread::currentThread()} { -} - -GuiEventQueueImpl::~GuiEventQueueImpl() = default; - -GuiEventQueueImpl* GuiEventQueueImpl::impl(GuiEventQueue *event_queue) { - return event_queue->impl.get(); -} - -void GuiEventQueueImpl::process_callbacks() { - assert(QThread::currentThread() == this->thread); -#ifdef __APPLE__ - if (QThread::currentThread() != QCoreApplication::instance()->thread()) this->callback_processor.processEvents(); -#else - this->callback_processor.processEvents(); -#endif -} - -QThread* GuiEventQueueImpl::get_thread() { - return this->thread; -} - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/gui_event_queue_impl.h b/libopenage/gui/guisys/private/gui_event_queue_impl.h deleted file mode 100644 index 5c97155f0d..0000000000 --- a/libopenage/gui/guisys/private/gui_event_queue_impl.h +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include - -QT_FORWARD_DECLARE_CLASS(QThread) - -namespace qtsdl { - -class GuiEventQueue; - -/** - * Provides synchronization with some game thread. - */ -class GuiEventQueueImpl { -public: - explicit GuiEventQueueImpl(); - ~GuiEventQueueImpl(); - - static GuiEventQueueImpl* impl(GuiEventQueue *event_queue); - - void process_callbacks(); - - QThread* get_thread(); - -private: - QThread * const thread; - QEventLoop callback_processor; -}; - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/gui_image_provider_impl.cpp b/libopenage/gui/guisys/private/gui_image_provider_impl.cpp deleted file mode 100644 index 1badada7d2..0000000000 --- a/libopenage/gui/guisys/private/gui_image_provider_impl.cpp +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. - -#include "gui_image_provider_impl.h" - -#include - -#include "../public/gui_image_provider.h" - -namespace qtsdl { - -GuiImageProviderImpl::GuiImageProviderImpl() - : - QQuickImageProvider{QQmlImageProviderBase::Texture, QQuickImageProvider::ForceAsynchronousImageLoading} { -} - -GuiImageProviderImpl::~GuiImageProviderImpl() = default; - -std::unique_ptr GuiImageProviderImpl::take_ownership(GuiImageProvider *image_provider) { - std::unique_ptr ptr{image_provider->impl.release()}; - image_provider->impl = decltype(image_provider->impl) {ptr.get(), [] (GuiImageProviderImpl*) {}}; - return ptr; -} - -std::vector> GuiImageProviderImpl::take_ownership(const std::vector &image_providers) { - std::vector> image_provider_owning_ptrs(image_providers.size()); - - std::transform(std::begin(image_providers), std::end(image_providers), std::begin(image_provider_owning_ptrs), static_cast(*)(GuiImageProvider*)>(GuiImageProviderImpl::take_ownership)); - - return image_provider_owning_ptrs; -} - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/gui_image_provider_impl.h b/libopenage/gui/guisys/private/gui_image_provider_impl.h deleted file mode 100644 index 409fa1815e..0000000000 --- a/libopenage/gui/guisys/private/gui_image_provider_impl.h +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include - -#include - -namespace qtsdl { - -class GuiImageProvider; - -class GuiImageProviderImpl : public QQuickImageProvider { -public: - explicit GuiImageProviderImpl(); - virtual ~GuiImageProviderImpl(); - - static std::unique_ptr take_ownership(GuiImageProvider *image_provider); - static std::vector> take_ownership(const std::vector &image_providers); - - virtual const char* get_id() const = 0; - - virtual void give_up() = 0; -}; - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/gui_input_impl.cpp b/libopenage/gui/guisys/private/gui_input_impl.cpp deleted file mode 100644 index a92daf4134..0000000000 --- a/libopenage/gui/guisys/private/gui_input_impl.cpp +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#include "gui_input_impl.h" - -#include -#include -#include - -#include - -#include "../public/gui_event_queue.h" -#include "../public/gui_renderer.h" -#include "gui_event_queue_impl.h" -#include "gui_renderer_impl.h" - -namespace qtsdl { - -GuiInputImpl::GuiInputImpl(GuiRenderer *renderer, GuiEventQueue *game_logic_updater) : - QObject{}, - mouse_buttons_state{}, - game_logic_updater{GuiEventQueueImpl::impl(game_logic_updater)} { - const bool logic_diff_input = this->game_logic_updater->get_thread() != QThread::currentThread(); - const bool gui_diff_input = QCoreApplication::instance()->thread() != QThread::currentThread(); - const Qt::ConnectionType input_to_gui = gui_diff_input ? logic_diff_input ? Qt::BlockingQueuedConnection : Qt::QueuedConnection : Qt::DirectConnection; - - QObject::connect(this, &GuiInputImpl::input_event, GuiRendererImpl::impl(renderer)->get_window(), &EventHandlingQuickWindow::on_input_event, input_to_gui); -} - -GuiInputImpl::~GuiInputImpl() = default; - -namespace { -static_assert(!(Qt::LeftButton & (static_cast(Qt::LeftButton) - 1)), "Qt non-one-bit mask."); -static_assert(!(Qt::RightButton & (static_cast(Qt::RightButton) - 1)), "Qt non-one-bit mask."); -static_assert(!(Qt::MiddleButton & (static_cast(Qt::MiddleButton) - 1)), "Qt non-one-bit mask."); -static_assert(!(Qt::XButton1 & (static_cast(Qt::XButton1) - 1)), "Qt non-one-bit mask."); -static_assert(!(Qt::XButton2 & (static_cast(Qt::XButton2) - 1)), "Qt non-one-bit mask."); - -static_assert(SDL_BUTTON_LMASK == Qt::LeftButton, "SDL/Qt mouse button mask incompatibility."); -static_assert(1 << (SDL_BUTTON_LEFT - 1) == Qt::LeftButton, "SDL/Qt mouse button mask incompatibility."); - -// Right and middle are swapped. -static_assert(SDL_BUTTON_RMASK == Qt::MiddleButton, "SDL/Qt mouse button mask incompatibility."); -static_assert(1 << (SDL_BUTTON_RIGHT - 1) == Qt::MiddleButton, "SDL/Qt mouse button mask incompatibility."); - -static_assert(SDL_BUTTON_MMASK == Qt::RightButton, "SDL/Qt mouse button mask incompatibility."); -static_assert(1 << (SDL_BUTTON_MIDDLE - 1) == Qt::RightButton, "SDL/Qt mouse button mask incompatibility."); - -static_assert(SDL_BUTTON_X1MASK == Qt::XButton1, "SDL/Qt mouse button mask incompatibility."); -static_assert(1 << (SDL_BUTTON_X1 - 1) == Qt::XButton1, "SDL/Qt mouse button mask incompatibility."); - -static_assert(SDL_BUTTON_X2MASK == Qt::XButton2, "SDL/Qt mouse button mask incompatibility."); -static_assert(1 << (SDL_BUTTON_X2 - 1) == Qt::XButton2, "SDL/Qt mouse button mask incompatibility."); - -static_assert(Qt::MiddleButton >> 1 == Qt::RightButton, "Qt::RightButton or Qt::MiddleButton has moved."); - -int sdl_mouse_mask_to_qt(Uint32 state) { - return (state & (Qt::LeftButton | Qt::XButton1 | Qt::XButton2)) | ((state & Qt::RightButton) << 1) | ((state & Qt::MiddleButton) >> 1); -} - -Qt::MouseButtons sdl_mouse_state_to_qt(Uint32 state) { - return static_cast(sdl_mouse_mask_to_qt(state)); -} - -Qt::MouseButton sdl_mouse_btn_to_qt(Uint8 button) { - return static_cast(sdl_mouse_mask_to_qt(1 << (button - 1))); -} - -int sdl_key_to_qt(SDL_Keycode sym) { - switch (sym) { - case SDLK_BACKSPACE: - return Qt::Key_Backspace; - case SDLK_DELETE: - return Qt::Key_Delete; - default: - return 0; - } -} -} // namespace - -bool GuiInputImpl::process(SDL_Event *e) { - switch (e->type) { - case SDL_MOUSEMOTION: { - QMouseEvent ev{QEvent::MouseMove, QPoint{e->motion.x, e->motion.y}, Qt::MouseButton::NoButton, this->mouse_buttons_state = sdl_mouse_state_to_qt(e->motion.state), Qt::KeyboardModifier::NoModifier}; - ev.setAccepted(false); - - // Allow dragging stuff under the gui overlay. - return relay_input_event(&ev, e->motion.state & (SDL_BUTTON_LMASK | SDL_BUTTON_MMASK | SDL_BUTTON_RMASK)); - } - - case SDL_MOUSEBUTTONDOWN: { - auto button = sdl_mouse_btn_to_qt(e->button.button); - QMouseEvent ev{QEvent::MouseButtonPress, QPoint{e->button.x, e->button.y}, button, this->mouse_buttons_state |= button, Qt::KeyboardModifier::NoModifier}; - ev.setAccepted(false); - - bool accepted = relay_input_event(&ev); - - if (e->button.clicks == 2) { - QMouseEvent ev_dbl{QEvent::MouseButtonDblClick, QPoint{e->button.x, e->button.y}, button, this->mouse_buttons_state, Qt::KeyboardModifier::NoModifier}; - ev_dbl.setAccepted(false); - accepted = relay_input_event(&ev_dbl) || accepted; - } - - return accepted; - } - - case SDL_MOUSEBUTTONUP: { - auto button = sdl_mouse_btn_to_qt(e->button.button); - QMouseEvent ev{QEvent::MouseButtonRelease, QPoint{e->button.x, e->button.y}, button, this->mouse_buttons_state &= ~button, Qt::KeyboardModifier::NoModifier}; - ev.setAccepted(false); - - // Allow dragging stuff under the gui overlay: when no item is grabbed, it probably means that initial MousButtonPress was outside gui. - return relay_input_event(&ev, true); - } - - case SDL_MOUSEWHEEL: { - QPoint pos; - SDL_GetMouseState(&pos.rx(), &pos.ry()); - - QWheelEvent ev{ - pos, - pos, - QPoint{}, - QPoint{e->wheel.x, e->wheel.y}, - this->mouse_buttons_state, - Qt::KeyboardModifier::NoModifier, // Correct states? - Qt::ScrollPhase::NoScrollPhase, // ^ - false, - }; - ev.setAccepted(false); - - return relay_input_event(&ev); - } - - case SDL_KEYDOWN: { - QKeyEvent ev{QEvent::KeyPress, sdl_key_to_qt(e->key.keysym.sym), Qt::NoModifier, QChar(static_cast(e->key.keysym.sym))}; - ev.setAccepted(false); - return relay_input_event(&ev); - } - - case SDL_KEYUP: { - QKeyEvent ev{QEvent::KeyRelease, sdl_key_to_qt(e->key.keysym.sym), Qt::NoModifier, QChar(static_cast(e->key.keysym.sym))}; - ev.setAccepted(false); - return relay_input_event(&ev); - } - - default: - return false; - } -} - -bool GuiInputImpl::relay_input_event(QEvent *ev, bool only_if_grabbed) { - const bool logic_diff_input = this->game_logic_updater->get_thread() != QThread::currentThread(); - std::atomic processed{false}; - - emit this->input_event(&processed, ev, only_if_grabbed); - - // We have sent an event to the gui thread and want a response about collision. But the gui can be - // executing a getter that blocks the gui thread while doing something in the logic thread. - // So, if the logic thread is the same as the input thread, it should be running somehow. - // - // TODO: if/when the logic thread or input thread of the main game is made separate, give mutex or - // queue to the gui in order to replace this busy wait. - if (!logic_diff_input) - while (!processed) { - this->game_logic_updater->process_callbacks(); - QThread::usleep(1); - } - - return ev->isAccepted(); -} - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/gui_input_impl.h b/libopenage/gui/guisys/private/gui_input_impl.h deleted file mode 100644 index c3665159a5..0000000000 --- a/libopenage/gui/guisys/private/gui_input_impl.h +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -#include - -#include - -namespace qtsdl { - -class GuiRenderer; -class GuiEventQueue; -class GuiEventQueueImpl; - -class GuiInputImpl : public QObject { - Q_OBJECT - -public: - explicit GuiInputImpl(GuiRenderer *renderer, GuiEventQueue *game_logic_updater); - virtual ~GuiInputImpl(); - - /** - * Returns true if the event was accepted. - */ - bool process(SDL_Event *e); - -signals: - void input_event(std::atomic *processed, QEvent *ev, bool only_if_grabbed=false); - -private: - bool relay_input_event(QEvent *ev, bool only_if_grabbed=false); - - Qt::MouseButtons mouse_buttons_state; - GuiEventQueueImpl *game_logic_updater; -}; - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/gui_renderer_impl.cpp b/libopenage/gui/guisys/private/gui_renderer_impl.cpp deleted file mode 100644 index cf3bed6a63..0000000000 --- a/libopenage/gui/guisys/private/gui_renderer_impl.cpp +++ /dev/null @@ -1,270 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#include "gui_renderer_impl.h" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../public/gui_renderer.h" - - -namespace qtsdl { - -namespace { -const int registration = qRegisterMetaType *>("atomic_bool_ptr"); -} - -EventHandlingQuickWindow::EventHandlingQuickWindow(QQuickRenderControl *render_control) : - QQuickWindow{render_control}, - focused_item{} { - Q_UNUSED(registration); -} - -EventHandlingQuickWindow::~EventHandlingQuickWindow() = default; - -void EventHandlingQuickWindow::on_input_event(std::atomic *processed, QEvent *event, bool only_if_grabbed) { - if (!only_if_grabbed || this->mouseGrabberItem()) { - if (this->focused_item && (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease)) { - QCoreApplication::instance()->sendEvent(this->focused_item, event); - } - else { - QCoreApplication::instance()->sendEvent(this, event); - - auto change_focus = [this](QQuickItem *item) { - if (this->focused_item != item) { - if (this->focused_item) { - QFocusEvent focus_out{QEvent::FocusOut, Qt::ActiveWindowFocusReason}; - QCoreApplication::instance()->sendEvent(this->focused_item, &focus_out); - } - - if (item) { - QFocusEvent focus_in{QEvent::FocusIn, Qt::ActiveWindowFocusReason}; - QCoreApplication::instance()->sendEvent(item, &focus_in); - } - } - - this->focused_item = item; - }; - - // Loose keyboard focus when clicked outside of gui. - if (event->type() == QEvent::MouseButtonPress && !event->isAccepted()) - change_focus(nullptr); - - // Normally, the QQuickWindow would handle keyboard focus automatically, but it can't because neither QQuickWindow nor - // its target QWindow respond to requestActivate(). Which means no focus event propagation when injecting mouse clicks. - // So, the workaround is to look specifically for TextFields and give them focus directly. - // TODO: to remove when the proper focus for the foreign (that obtained from QWindow::fromWinId()) windows is implemented (Qt 5.6). - if (this->mouseGrabberItem() && this->mouseGrabberItem()->metaObject()->superClass() && this->mouseGrabberItem()->metaObject()->superClass()->className() == QString("QQuickTextInput") && (event->type() == QEvent::MouseButtonPress)) - change_focus(this->mouseGrabberItem()); - } - } - - *processed = true; -} - -void EventHandlingQuickWindow::on_resized(const QSize &size) { - this->resize(size); -} - -GuiRendererImpl::GuiRendererImpl(SDL_Window *window) : - QObject{}, - gui_rendering_setup_routines{window}, - need_fbo_resize{true}, - need_sync{}, - need_render{}, - gui_locked{}, - renderer_waiting_on_cond{} { - this->moveToThread(QCoreApplication::instance()->thread()); - - QObject::connect(&this->render_control, &QQuickRenderControl::renderRequested, [&]() { - this->need_render = true; - }); - - QObject::connect(&this->render_control, &QQuickRenderControl::sceneChanged, this, &GuiRendererImpl::on_scene_changed); - - this->window = std::make_unique(&this->render_control); - this->window->moveToThread(QCoreApplication::instance()->thread()); - QObject::connect(this, &GuiRendererImpl::resized, this->window.get(), &EventHandlingQuickWindow::on_resized); - // this->window->setClearBeforeRendering(true); - this->window->setColor(QColor{0, 0, 0, 0}); - - QObject::connect(&*this->window, &QQuickWindow::sceneGraphInitialized, this, [this] { - std::tie(this->new_fbo_width, this->new_fbo_height) = std::make_tuple(this->window->width(), this->window->height()); - this->need_fbo_resize = true; - }); - - QObject::connect(&*this->window, &QQuickWindow::widthChanged, [this] { this->new_fbo_width = this->window->width(); this->need_fbo_resize = true; }); - QObject::connect(&*this->window, &QQuickWindow::heightChanged, [this] { this->new_fbo_height = this->window->height(); this->need_fbo_resize = true; }); - - GuiRenderingCtxActivator activate_render(this->gui_rendering_setup_routines); - - // TODO: Make independent from OpenGL - this->window->setGraphicsDevice( - QQuickGraphicsDevice::fromOpenGLContext(this->gui_rendering_setup_routines.get_ctx())); - this->render_control.initialize(); -} - -void GuiRendererImpl::on_scene_changed() { - this->need_sync = true; - this->need_render = true; - this->render_control.polishItems(); -} - -void GuiRendererImpl::reinit_fbo_if_needed() { - assert(QThread::currentThread() == this->gui_rendering_setup_routines.get_ctx()->thread()); - - if (this->need_fbo_resize) { - this->fbo = std::make_unique(QSize(this->new_fbo_width, this->new_fbo_height), QOpenGLFramebufferObject::CombinedDepthStencil); - - // dirty workaround; texture id from our own implementation should be passed here - QQuickRenderTarget target = QQuickRenderTarget::fromOpenGLTexture(this->fbo->texture(), this->fbo->size()); - - this->window->setRenderTarget(target); - this->need_fbo_resize = false; - } - - assert(this->fbo); -} - -GuiRendererImpl::~GuiRendererImpl() { - // TODO: MAYBE: - // the this->ctx member frees the - // gl context even though that's SDL's job. - // - // the qt doc says that a native context isn't destroyed! - // but somehow it is lost or destroyed! - // https://doc.qt.io/qt-5/qopenglcontext.html#setNativeHandle -} - -GuiRendererImpl *GuiRendererImpl::impl(GuiRenderer *renderer) { - return renderer->impl.get(); -} - -GLuint GuiRendererImpl::render() { - GuiRenderingCtxActivator activate_render(this->gui_rendering_setup_routines); - - this->reinit_fbo_if_needed(); - - this->render_control.beginFrame(); - - // QQuickRenderControl::sync() must be called from the render thread while the gui thread is stopped. - if (this->need_sync) { - if (QCoreApplication::instance()->thread() != QThread::currentThread()) { - std::unique_lock lck{this->gui_guard}; - - if (this->need_sync) { - QCoreApplication::instance()->postEvent(this, new QEvent{QEvent::User}, INT_MAX); - - this->renderer_waiting_on_cond = true; - this->gui_locked_cond.wait(lck, [this] { return this->gui_locked; }); - this->renderer_waiting_on_cond = false; - - this->render_control.sync(); - - this->need_sync = false; - this->gui_locked = false; - - lck.unlock(); - this->gui_locked_cond.notify_one(); - } - } - else { - this->render_control.sync(); - } - } - - this->render_control.render(); - this->render_control.endFrame(); - - // this->window->resetOpenGLState(); - - return this->fbo->texture(); -} - -void GuiRendererImpl::make_sure_render_thread_unlocked() { - assert(QThread::currentThread() == QCoreApplication::instance()->thread()); - - if (this->need_sync && QThread::currentThread() != this->render_control.thread()) { - std::unique_lock lck{this->gui_guard}; - - if (this->renderer_waiting_on_cond) { - this->process_freeze(std::move(lck)); - QCoreApplication::instance()->removePostedEvents(this, QEvent::User); - } - } -} - -bool GuiRendererImpl::make_sure_render_thread_wont_sync() { - assert(QThread::currentThread() == QCoreApplication::instance()->thread()); - - if (this->need_sync && QThread::currentThread() != this->render_control.thread()) { - std::unique_lock lck{this->gui_guard}; - - if (this->renderer_waiting_on_cond) { - this->process_freeze(std::move(lck)); - QCoreApplication::instance()->removePostedEvents(this, QEvent::User); - assert(!this->need_sync); - } - else { - assert(this->need_sync); - this->need_sync = false; - return true; - } - } - - return false; -} - -void GuiRendererImpl::demand_sync() { - assert(QThread::currentThread() == QCoreApplication::instance()->thread()); - this->need_sync = true; -} - -bool GuiRendererImpl::event(QEvent *e) { - if (e->type() == QEvent::User) { - std::unique_lock lck{this->gui_guard}; - this->process_freeze(std::move(lck)); - return true; - } - else { - return this->QObject::event(e); - } -} - -void GuiRendererImpl::process_freeze(std::unique_lock lck) { - this->gui_locked = true; - - lck.unlock(); - this->gui_locked_cond.notify_one(); - - lck.lock(); - this->gui_locked_cond.wait(lck, [this] { return !this->gui_locked; }); -} - -EventHandlingQuickWindow *GuiRendererImpl::get_window() { - return &*this->window; -} - -void GuiRendererImpl::resize(const QSize &size) { - emit this->resized(size); -} - -TemporaryDisableGuiRendererSync::TemporaryDisableGuiRendererSync(GuiRendererImpl &renderer) : - renderer{renderer}, - need_sync{renderer.make_sure_render_thread_wont_sync()} { -} - -TemporaryDisableGuiRendererSync::~TemporaryDisableGuiRendererSync() { - if (this->need_sync) - renderer.demand_sync(); -} - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/gui_renderer_impl.h b/libopenage/gui/guisys/private/gui_renderer_impl.h deleted file mode 100644 index 31c1330eea..0000000000 --- a/libopenage/gui/guisys/private/gui_renderer_impl.h +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#ifndef __APPLE__ -#ifdef _MSC_VER -#define NOMINMAX -#include -#endif //_MSC_VER -#include -#else // __APPLE__ -#include -#endif - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "gui_rendering_setup_routines.h" - -struct SDL_Window; - -QT_FORWARD_DECLARE_CLASS(QOpenGLFramebufferObject) - -namespace qtsdl { - -class GuiRenderer; - -class EventHandlingQuickWindow : public QQuickWindow { - Q_OBJECT - -public: - explicit EventHandlingQuickWindow(QQuickRenderControl *render_control); - virtual ~EventHandlingQuickWindow(); - -public slots: - void on_input_event(std::atomic *processed, QEvent *event, bool only_if_grabbed); - void on_resized(const QSize &size); - -private: - // TODO: to remove when the proper focus for the foreign (that obtained from QWindow::fromWinId()) windows is implemented (Qt 5.6). - QQuickItem *focused_item; -}; - -/** - * Passes the native graphic context to Qt. - */ -class GuiRendererImpl : public QObject { - Q_OBJECT - -public: - explicit GuiRendererImpl(SDL_Window *window); - ~GuiRendererImpl(); - - static GuiRendererImpl *impl(GuiRenderer *renderer); - - /** - * @return texture ID where GUI was rendered - */ - GLuint render(); - - void resize(const QSize &size); - - EventHandlingQuickWindow *get_window(); - - /** - * When render thread is locked waiting for the gui thread to finish its current event and - * go to the high-priority 'freeze' event; but the gui thread can't finish the current event - * because it's going to lock the game-logic thread that will lock the render thread somehow. - * - * In this situation the gui thread should call this function to immediately process the 'freeze' - * event handler inside current event and remove the event from the gui queue. - * - * 'GuiRendererImpl::need_sync' is only set from the gui thread, so after the calling this function, - * we are fine for entire duration of the processing of the current event. - * - * If 'GuiRendererImpl::need_sync' is set, this function blocks until the render thread comes around - * to do the 'QQuickRenderControl::sync()'. If it's not good enough, it's possible to implement two - * separate functions to set 'GuiRendererImpl::need_sync' to false and then back to true. - */ - void make_sure_render_thread_unlocked(); - - /** - * Assures that the render thread won't try to stop the gui thread for syncing. - * - * Must be called from the gui thread. - * - * @return true if need_sync was set to false (you should restore it with the demand_sync()) - */ - bool make_sure_render_thread_wont_sync(); - - /** - * Sets 'GuiRendererImpl::need_sync' to true. - * - * Must be called from the gui thread. - */ - void demand_sync(); - -signals: - void resized(const QSize &size); - -private: - virtual bool event(QEvent *e) override; - - void process_freeze(std::unique_lock lck); - -private slots: - void on_scene_changed(); - -private: - /** - * If size changes, then create a new FBO for GUI rendering - */ - void reinit_fbo_if_needed(); - - /** - * Contains rendering context - * Use GuiRenderingCtxActivator to enable it - */ - GuiRenderingSetupRoutines gui_rendering_setup_routines; - - /** - * Contains scene graph of the GUI - */ - std::unique_ptr window; - - /** - * Object for sending render command to Qt - */ - QQuickRenderControl render_control; - - /** - * FBO where the GUI is rendered - */ - std::unique_ptr fbo; - - std::atomic new_fbo_width; - std::atomic new_fbo_height; - std::atomic need_fbo_resize; - - std::atomic need_sync; - std::atomic need_render; - - bool gui_locked; - std::mutex gui_guard; - std::condition_variable gui_locked_cond; - bool renderer_waiting_on_cond; -}; - -class TemporaryDisableGuiRendererSync { -public: - explicit TemporaryDisableGuiRendererSync(GuiRendererImpl &renderer); - ~TemporaryDisableGuiRendererSync(); - -private: - TemporaryDisableGuiRendererSync(const TemporaryDisableGuiRendererSync &) = delete; - TemporaryDisableGuiRendererSync &operator=(const TemporaryDisableGuiRendererSync &) = delete; - - GuiRendererImpl &renderer; - const bool need_sync; -}; - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/gui_rendering_setup_routines.cpp b/libopenage/gui/guisys/private/gui_rendering_setup_routines.cpp deleted file mode 100644 index cfb6745438..0000000000 --- a/libopenage/gui/guisys/private/gui_rendering_setup_routines.cpp +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2017-2019 the openage authors. See copying.md for legal info. - -#include "gui_rendering_setup_routines.h" - -#include - -#include - -#include "gui_ctx_setup.h" - -namespace qtsdl { - -GuiRenderingSetupRoutines::GuiRenderingSetupRoutines(SDL_Window *window) { - try { - this->ctx_extraction_mode = std::make_unique(window); - } catch (const CtxExtractionException&) { - - qInfo() << "Falling back to separate render context for GUI"; - - try { - this->ctx_extraction_mode = std::make_unique(window); - } catch (const CtxExtractionException&) { - assert(false && "setting up context for GUI failed"); - } - } -} - -GuiRenderingSetupRoutines::~GuiRenderingSetupRoutines() = default; - -QOpenGLContext* GuiRenderingSetupRoutines::get_ctx() { - return this->ctx_extraction_mode->get_ctx(); -} - -void GuiRenderingSetupRoutines::pre_render() { - this->ctx_extraction_mode->pre_render(); -} - -void GuiRenderingSetupRoutines::post_render() { - this->ctx_extraction_mode->post_render(); -} - -GuiRenderingCtxActivator::GuiRenderingCtxActivator(GuiRenderingSetupRoutines &rendering_setup_routines) - : - rendering_setup_routines{&rendering_setup_routines} { - - this->rendering_setup_routines->pre_render(); -} - -GuiRenderingCtxActivator::~GuiRenderingCtxActivator() { - if (this->rendering_setup_routines) - this->rendering_setup_routines->post_render(); -} - -GuiRenderingCtxActivator::GuiRenderingCtxActivator(GuiRenderingCtxActivator&& o) - : - rendering_setup_routines{o.rendering_setup_routines} { - - o.rendering_setup_routines = nullptr; -} - -GuiRenderingCtxActivator& GuiRenderingCtxActivator::operator=(GuiRenderingCtxActivator&& o) { - this->rendering_setup_routines = o.rendering_setup_routines; - o.rendering_setup_routines = nullptr; - return *this; -} - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/gui_rendering_setup_routines.h b/libopenage/gui/guisys/private/gui_rendering_setup_routines.h deleted file mode 100644 index e392d70cb1..0000000000 --- a/libopenage/gui/guisys/private/gui_rendering_setup_routines.h +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2017-2017 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -#include - -struct SDL_Window; - -QT_FORWARD_DECLARE_CLASS(QOpenGLContext) - -namespace qtsdl { - -class CtxExtractionMode; - -class GuiRenderingCtxActivator; - -/** - * Returns a GL context usable by Qt classes. - * Provides pre- and post-rendering functions to make the context usable for GUI rendering. - */ -class GuiRenderingSetupRoutines { -public: - explicit GuiRenderingSetupRoutines(SDL_Window *window); - ~GuiRenderingSetupRoutines(); - - QOpenGLContext* get_ctx(); - -private: - friend class GuiRenderingCtxActivator; - void pre_render(); - void post_render(); - - std::unique_ptr ctx_extraction_mode; -}; - -/** - * Prepares the context for rendering the GUI for one frame. - * Activator must be destroyed as soon as the GUI has executed its frame render call. - */ -class GuiRenderingCtxActivator { -public: - explicit GuiRenderingCtxActivator(GuiRenderingSetupRoutines &rendering_setup_routines); - ~GuiRenderingCtxActivator(); - - GuiRenderingCtxActivator(GuiRenderingCtxActivator&& o); - GuiRenderingCtxActivator& operator=(GuiRenderingCtxActivator&& o); - -private: - GuiRenderingCtxActivator(const GuiRenderingCtxActivator&) = delete; - GuiRenderingCtxActivator& operator=(const GuiRenderingCtxActivator&) = delete; - - GuiRenderingSetupRoutines *rendering_setup_routines; -}; - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/gui_subtree_impl.cpp b/libopenage/gui/guisys/private/gui_subtree_impl.cpp deleted file mode 100644 index 5bfcb73fa9..0000000000 --- a/libopenage/gui/guisys/private/gui_subtree_impl.cpp +++ /dev/null @@ -1,275 +0,0 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. - -#include "gui_subtree_impl.h" -#include "gui_renderer_impl.h" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "gui_engine_impl.h" -#include "../link/gui_item.h" -#include "../public/gui_subtree.h" -#include "gui_event_queue_impl.h" - -namespace qtsdl { - - -GuiSubtreeImpl::GuiSubtreeImpl(GuiRenderer *renderer, - GuiEventQueue *game_logic_updater, - GuiEngine *engine, - const QString &source, - const QString &rootdir) - : - QObject{}, - renderer{}, - engine{}, - root{} { - - QObject::connect( - &this->game_logic_callback, - &GuiCallback::process_blocking, - this, - &GuiSubtreeImpl::on_process_game_logic_callback_blocking, - Qt::DirectConnection - ); - - QObject::connect( - this, - &GuiSubtreeImpl::process_game_logic_callback_blocking, - &this->game_logic_callback, - &GuiCallback::process, - (QCoreApplication::instance()->thread() != QThread::currentThread() - ? Qt::BlockingQueuedConnection - : Qt::DirectConnection) - ); - - this->moveToThread(QCoreApplication::instance()->thread()); - this->attach_to(GuiEventQueueImpl::impl(game_logic_updater)); - this->attach_to(GuiRendererImpl::impl(renderer)); - this->attach_to(GuiEngineImpl::impl(engine), rootdir); - - // Should now be initialized by the engine-attaching - assert(this->root_component); - - // Need to queue the loading because some underlying game logic elements - // require the loop to be running (maybe some things that are created after - // the gui). - QMetaObject::invokeMethod(this->root_component.get(), - "loadUrl", Qt::QueuedConnection, - Q_ARG(QUrl, QUrl::fromLocalFile(source))); -} - -GuiSubtreeImpl::~GuiSubtreeImpl() = default; - -void GuiSubtreeImpl::onEngineReloaded() { - const QUrl source = this->root_component->url(); - - this->destroy_root(); - - this->root_component = std::make_unique(this->engine.get_qml_engine()); - - QObject::connect( - this->root_component.get(), - &QQmlComponent::statusChanged, - this, - &GuiSubtreeImpl::component_status_changed - ); - - this->root_component->loadUrl(source); -} - -void GuiSubtreeImpl::attach_to(GuiEventQueueImpl *game_logic_updater) { - this->game_logic_callback.moveToThread(game_logic_updater->get_thread()); -} - -void GuiSubtreeImpl::attach_to(GuiRendererImpl *renderer) { - assert(renderer); - - if (this->renderer) - QObject::disconnect(this->renderer, nullptr, this, nullptr); - - this->renderer = renderer; - - QObject::connect( - this->renderer, - &GuiRendererImpl::resized, - this, - &GuiSubtreeImpl::on_resized - ); - this->reparent_root(); -} - -void GuiSubtreeImpl::attach_to(GuiEngineImpl *engine_impl, const QString &root_dir) { - if (this->engine.has_subtree()) { - this->destroy_root(); - this->engine = GuiEngineImplConnection{}; - } - - this->root_component = std::make_unique(engine_impl->get_qml_engine()); - - QObject::connect( - this->root_component.get(), - &QQmlComponent::statusChanged, - this, - &GuiSubtreeImpl::component_status_changed - ); - - // operator = && - this->engine = GuiEngineImplConnection{this, engine_impl, root_dir}; - - this->root_component->moveToThread(QCoreApplication::instance()->thread()); -} - -void GuiSubtreeImpl::component_status_changed(QQmlComponent::Status status) { - if (QQmlComponent::Error == status) { - qCritical("%s", qUtf8Printable(this->root_component->errorString())); - return; - } - - if (QQmlComponent::Ready == status) { - assert(!this->root); - - this->root = qobject_cast(this->root_component->beginCreate(this->engine.rootContext())); - assert(this->root); - - this->init_persistent_items(); - - this->root_component->completeCreate(); - - this->reparent_root(); - } -} - -void GuiSubtreeImpl::on_resized(const QSize &size) { - if (this->root) - this->root->setSize(size); -} - -void GuiSubtreeImpl::on_process_game_logic_callback_blocking(const std::function &f) { - TemporaryDisableGuiRendererSync {*this->renderer}; - emit this->process_game_logic_callback_blocking(f); -} - -void GuiSubtreeImpl::init_persistent_items() { - auto persistent = this->root->findChildren(); - - for (auto ap : persistent) - ap->get_attachee()->set_game_logic_callback(&this->game_logic_callback); - - this->reloader.init_persistent_items(persistent); -} - -void GuiSubtreeImpl::reparent_root() { - if (this->root) { - QQuickWindow *window = this->renderer->get_window(); - - this->root->setParentItem(window->contentItem()); - this->root->setSize(QSize{window->width(), window->height()}); - } -} - -void GuiSubtreeImpl::destroy_root() { - if (this->root) { - this->root->setParent(nullptr); - this->root->setParentItem(nullptr); - this->root->deleteLater(); - this->root = nullptr; - } -} - -GuiEngineImplConnection::GuiEngineImplConnection() - : - subtree{}, - engine{} { -} - -GuiEngineImplConnection::GuiEngineImplConnection(GuiSubtreeImpl *subtree, - GuiEngineImpl *engine, - QString root_dir) - : - subtree{subtree}, - engine{engine}, - root_dir{std::move(root_dir)} { - - assert(this->subtree); - assert(this->engine); - - QObject::connect( - this->engine, - &GuiEngineImpl::reload, - this->subtree, - &GuiSubtreeImpl::onEngineReloaded - ); - - // add the directory so it can be watched for changes. - this->engine->add_root_dir_path(this->root_dir); -} - -GuiEngineImplConnection::~GuiEngineImplConnection() { - this->disconnect(); -} - -void GuiEngineImplConnection::disconnect() { - if (this->has_subtree()) { - assert(this->engine); - - QObject::disconnect( - this->engine, - &GuiEngineImpl::reload, - this->subtree, - &GuiSubtreeImpl::onEngineReloaded - ); - - if (not this->root_dir.isEmpty()) { - this->engine->remove_root_dir_path(this->root_dir); - } - } -} - -GuiEngineImplConnection::GuiEngineImplConnection(GuiEngineImplConnection &&cnx) noexcept - : - subtree{cnx.subtree}, - engine{cnx.engine} { - - cnx.subtree = nullptr; - cnx.engine = nullptr; -} - -GuiEngineImplConnection& GuiEngineImplConnection::operator=(GuiEngineImplConnection &&cnx) noexcept { - this->disconnect(); - - this->subtree = cnx.subtree; - this->engine = cnx.engine; - - cnx.subtree = nullptr; - cnx.engine = nullptr; - - return *this; -} - -bool GuiEngineImplConnection::has_subtree() const { - return this->subtree != nullptr; -} - -QQmlContext* GuiEngineImplConnection::rootContext() const { - assert(this->subtree); - assert(this->engine); - return this->engine->get_qml_engine()->rootContext(); -} - -QQmlEngine* GuiEngineImplConnection::get_qml_engine() const { - assert(this->subtree); - assert(this->engine); - return this->engine->get_qml_engine(); -} - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/gui_subtree_impl.h b/libopenage/gui/guisys/private/gui_subtree_impl.h deleted file mode 100644 index 6c737c11f1..0000000000 --- a/libopenage/gui/guisys/private/gui_subtree_impl.h +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include -#include - -#include -#include - -#include "gui_callback.h" -#include "livereload/gui_live_reloader.h" - -QT_FORWARD_DECLARE_CLASS(QQuickItem) - -namespace qtsdl { - -class GuiRenderer; -class GuiRendererImpl; -class GuiEventQueue; -class GuiEventQueueImpl; -class GuiEngine; -class GuiEngineImpl; -class GuiSubtreeImpl; - -class GuiEngineImplConnection { -public: - GuiEngineImplConnection(); - explicit GuiEngineImplConnection(GuiSubtreeImpl *subtree, - GuiEngineImpl *engine, - QString root_dir); - ~GuiEngineImplConnection(); - - GuiEngineImplConnection(GuiEngineImplConnection &&cnx) noexcept; - GuiEngineImplConnection& operator=(GuiEngineImplConnection &&cnx) noexcept; - - bool has_subtree() const; - - QQmlContext* rootContext() const; - QQmlEngine* get_qml_engine() const; - -private: - GuiEngineImplConnection(const GuiEngineImplConnection &cnx) = delete; - GuiEngineImplConnection& operator=(const GuiEngineImplConnection &cnx) = delete; - - void disconnect(); - - GuiSubtreeImpl *subtree; - GuiEngineImpl *engine; - QString root_dir; -}; - - -class GuiSubtreeImpl : public QObject { - Q_OBJECT - -public: - explicit GuiSubtreeImpl(GuiRenderer *renderer, - GuiEventQueue *game_logic_updater, - GuiEngine *engine, - const QString &source, - const QString &rootdir); - virtual ~GuiSubtreeImpl(); - -public slots: - void onEngineReloaded(); - - void attach_to(GuiEventQueueImpl *game_logic_updater); - void attach_to(GuiRendererImpl *renderer); - void attach_to(GuiEngineImpl *engine, const QString &source); - -private slots: - void component_status_changed(QQmlComponent::Status status); - void on_resized(const QSize &size); - void on_process_game_logic_callback_blocking(const std::function &f); - -signals: - void process_game_logic_callback_blocking(const std::function &f); - -private: - void init_persistent_items(); - - void reparent_root(); - void destroy_root(); - - GuiRendererImpl *renderer; - GuiEngineImplConnection engine; - GuiLiveReloader reloader; - - GuiCallback game_logic_callback; - - std::unique_ptr root_component; - QQuickItem *root; -}; - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/livereload/recursive_directory_watcher.cpp b/libopenage/gui/guisys/private/livereload/recursive_directory_watcher.cpp deleted file mode 100644 index 6e3422dc0c..0000000000 --- a/libopenage/gui/guisys/private/livereload/recursive_directory_watcher.cpp +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. - -#include "recursive_directory_watcher.h" - -#include -#include - -#include "recursive_directory_watcher_worker.h" - -namespace qtsdl { - -RecursiveDirectoryWatcher::RecursiveDirectoryWatcher(QObject *parent) - : - QObject{parent} { - - QSemaphore wait_worker_started; - - this->worker = std::async(std::launch::async, [this, &wait_worker_started] { - QEventLoop loop; - QObject::connect(this, &RecursiveDirectoryWatcher::quit, &loop, &QEventLoop::quit); - - RecursiveDirectoryWatcherWorker watcher; - QObject::connect(&watcher, &RecursiveDirectoryWatcherWorker::changeDetected, this, &RecursiveDirectoryWatcher::changeDetected); - QObject::connect(this, &RecursiveDirectoryWatcher::rootDirsPathsChanged, &watcher, &RecursiveDirectoryWatcherWorker::onRootDirsPathsChanged); - - wait_worker_started.release(); - - loop.exec(); - }); - - wait_worker_started.acquire(); -} - -RecursiveDirectoryWatcher::~RecursiveDirectoryWatcher() { - emit this->quit(); - this->worker.wait(); -} - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/livereload/recursive_directory_watcher.h b/libopenage/gui/guisys/private/livereload/recursive_directory_watcher.h deleted file mode 100644 index d55e7b8f99..0000000000 --- a/libopenage/gui/guisys/private/livereload/recursive_directory_watcher.h +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -#include -#include - -namespace qtsdl { - -/** - * Emits a signal when anything changes in the directories. - */ -class RecursiveDirectoryWatcher : public QObject { - Q_OBJECT - -public: - explicit RecursiveDirectoryWatcher(QObject *parent = nullptr); - virtual ~RecursiveDirectoryWatcher(); - -signals: - void changeDetected(); - void rootDirsPathsChanged(const QStringList&); - void quit(); - -private: - std::future worker; -}; - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/livereload/recursive_directory_watcher_worker.cpp b/libopenage/gui/guisys/private/livereload/recursive_directory_watcher_worker.cpp deleted file mode 100644 index ff8e9f7242..0000000000 --- a/libopenage/gui/guisys/private/livereload/recursive_directory_watcher_worker.cpp +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. - -#include "recursive_directory_watcher_worker.h" - -#include - -#include -#include - -namespace qtsdl { - -namespace { -const int batch_ms = 100; -} - -RecursiveDirectoryWatcherWorker::RecursiveDirectoryWatcherWorker() - : - QObject{} { - - this->batching_timer.setInterval(batch_ms); - this->batching_timer.setSingleShot(true); - QObject::connect(&this->batching_timer, &QTimer::timeout, this, &RecursiveDirectoryWatcherWorker::changeDetected); - QObject::connect(&this->batching_timer, &QTimer::timeout, this, &RecursiveDirectoryWatcherWorker::restartWatching); -} - -void RecursiveDirectoryWatcherWorker::onRootDirsPathsChanged(const QStringList &root_dirs_paths) { - if (this->root_dirs_paths != root_dirs_paths) { - this->root_dirs_paths = root_dirs_paths; - this->restartWatching(); - } -} - -namespace { -QStringList collect_entries_to_watch(const QStringList &root_dirs_paths) { - QStringList root_dirs_paths_no_duplicates = root_dirs_paths; - root_dirs_paths_no_duplicates.removeDuplicates(); - - QStringList entries_to_watch; - - std::for_each(std::begin(root_dirs_paths_no_duplicates), std::end(root_dirs_paths_no_duplicates), [&entries_to_watch] (const QString& root_dir_path) { - QDirIterator it{root_dir_path, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks}; - - while (it.hasNext()) { - entries_to_watch.append(it.next()); - } - }); - - return entries_to_watch; -} -} - -void RecursiveDirectoryWatcherWorker::restartWatching() { - this->restart_watching(collect_entries_to_watch(this->root_dirs_paths)); -} - -void RecursiveDirectoryWatcherWorker::restart_watching(const QStringList &entries_to_watch) { - this->watcher.reset(); - this->watcher = std::make_unique(); - - QObject::connect(&*this->watcher, &QFileSystemWatcher::directoryChanged, this, &RecursiveDirectoryWatcherWorker::onEntryChanged); - - if (entries_to_watch.empty()) - qWarning() << "RecursiveDirectoryWatcheWorker hasn't found any files to watch."; - else - this->watcher->addPaths(entries_to_watch); -} - -void RecursiveDirectoryWatcherWorker::onEntryChanged() { - if (!this->batching_timer.isActive()) - this->batching_timer.start(); -} - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/livereload/recursive_directory_watcher_worker.h b/libopenage/gui/guisys/private/livereload/recursive_directory_watcher_worker.h deleted file mode 100644 index 88af26d97d..0000000000 --- a/libopenage/gui/guisys/private/livereload/recursive_directory_watcher_worker.h +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -#include -#include -#include - -namespace qtsdl { - -/** - * Emits a signal when anything changes in the directories. - */ -class RecursiveDirectoryWatcherWorker : public QObject { - Q_OBJECT - -public: - RecursiveDirectoryWatcherWorker(); - -signals: - void changeDetected(); - -public slots: - void onRootDirsPathsChanged(const QStringList &root_dirs_paths); - void restartWatching(); - -private slots: - void onEntryChanged(); - -private: - void restart_watching(const QStringList &entries_to_watch); - - /** - * Actual watcher. - * Its event processing and destruction has to be in the same separate thread. - */ - std::unique_ptr watcher; - - /** - * Waits to glue multiple changes into one event. - */ - QTimer batching_timer; - - QStringList root_dirs_paths; -}; - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/opengl_debug_logger.cpp b/libopenage/gui/guisys/private/opengl_debug_logger.cpp deleted file mode 100644 index b61bc58f20..0000000000 --- a/libopenage/gui/guisys/private/opengl_debug_logger.cpp +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. - -#include "opengl_debug_logger.h" - -#include -#include -#include - -#ifdef __APPLE__ -// from https://www.khronos.org/registry/OpenGL/api/GL/glext.h -#define GL_DEBUG_CALLBACK_FUNCTION 0x8244 -#define GL_DEBUG_OUTPUT_SYNCHRONOUS 0x8242 -#define GL_DEBUG_TYPE_ERROR 0x824C -#define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR 0x824E -#endif - -namespace qtsdl { - -gl_debug_parameters get_current_opengl_debug_parameters(QOpenGLContext ¤t_source_context) { - gl_debug_parameters params{}; - - if (QOpenGLVersionFunctionsFactory::get(¤t_source_context)) - if ((params.is_debug = current_source_context.format().options().testFlag(QSurfaceFormat::DebugContext))) { - glGetPointerv(GL_DEBUG_CALLBACK_FUNCTION, ¶ms.callback); - params.synchronous = glIsEnabled(GL_DEBUG_OUTPUT_SYNCHRONOUS); - } - - return params; -} - -void apply_opengl_debug_parameters(gl_debug_parameters params, QOpenGLContext ¤t_dest_context) { - if (params.is_debug && params.callback) { - if (auto functions = QOpenGLVersionFunctionsFactory::get(¤t_dest_context)) { - functions->initializeOpenGLFunctions(); - - functions->glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_FALSE); - - functions->glDebugMessageControl(GL_DONT_CARE, GL_DEBUG_TYPE_ERROR, GL_DONT_CARE, 0, nullptr, GL_TRUE); - functions->glDebugMessageControl(GL_DONT_CARE, GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR, GL_DONT_CARE, 0, nullptr, GL_TRUE); - - functions->glDebugMessageCallback((GLDEBUGPROC)params.callback, nullptr); - - if (params.synchronous) - glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); - } - } -} - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/opengl_debug_logger.h b/libopenage/gui/guisys/private/opengl_debug_logger.h deleted file mode 100644 index 7fda1cad80..0000000000 --- a/libopenage/gui/guisys/private/opengl_debug_logger.h +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -QT_FORWARD_DECLARE_CLASS(QOpenGLContext) - -namespace qtsdl { -struct gl_debug_parameters { - /** - * True if the GL context is a debug context - */ - bool is_debug; - - /** - * Function that GL context uses to report debug messages - */ - GLvoid *callback; - - /** - * True if debug callback calling method is chosen to be synchronous - */ - bool synchronous; -}; - -/** - * Get debugging settings of the current GL context - * - * @param current_source_context current GL context - * @return debugging settings - */ -gl_debug_parameters get_current_opengl_debug_parameters(QOpenGLContext ¤t_source_context); - -/** - * Create a GL logger in the current GL context - * - * @param params debugging settings - * @param current_dest_context current GL context to which parameters will be applied - */ -void apply_opengl_debug_parameters(gl_debug_parameters params, QOpenGLContext ¤t_dest_context); - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/platforms/context_extraction.h b/libopenage/gui/guisys/private/platforms/context_extraction.h deleted file mode 100644 index baf13818bb..0000000000 --- a/libopenage/gui/guisys/private/platforms/context_extraction.h +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include - -#include -#include - -struct SDL_Window; - -namespace qtsdl { - -/** - * @return current context (or null) and id of the window - */ -std::tuple extract_native_context(SDL_Window *window); - -/** - * @return current context (or null) and function to get it back to the window - */ -std::tuple> extract_native_context_and_switchback_func(SDL_Window *window); - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/platforms/context_extraction_cocoa.mm b/libopenage/gui/guisys/private/platforms/context_extraction_cocoa.mm deleted file mode 100644 index 076924d8af..0000000000 --- a/libopenage/gui/guisys/private/platforms/context_extraction_cocoa.mm +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. - -#include - -#include "context_extraction.h" - -#include - -#include "SDL_syswm.h" -#import - -namespace qtsdl { - -std::tuple extract_native_context(SDL_Window *window) { - - assert(window); - - NSOpenGLContext *current_context = [NSOpenGLContext currentContext]; - assert(current_context); - - NSView *view = nullptr; - - SDL_SysWMinfo wm_info; - SDL_VERSION(&wm_info.version); - - - if (SDL_GetWindowWMInfo(window, &wm_info)) { - NSWindow *ns_window = wm_info.info.cocoa.window; - view = [ns_window contentView]; - assert(view); - } - return std::make_tuple(QVariant::fromValue(QCocoaNativeContext(current_context)), reinterpret_cast(view)); -} - -std::tuple> extract_native_context_and_switchback_func(SDL_Window *window) { - assert(window); - - NSOpenGLContext *current_context = [NSOpenGLContext currentContext]; - assert(current_context); - - NSView *view = nullptr; - - SDL_SysWMinfo wm_info; - SDL_VERSION(&wm_info.version); - - if (SDL_GetWindowWMInfo(window, &wm_info)) { - NSWindow *ns_window = wm_info.info.cocoa.window; - view = [ns_window contentView]; - assert(view); - - return std::make_tuple(QVariant::fromValue(QCocoaNativeContext(current_context)), [current_context] { - [current_context makeCurrentContext]; - }); - } - - return std::tuple>{}; -} - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/platforms/context_extraction_win32.cpp b/libopenage/gui/guisys/private/platforms/context_extraction_win32.cpp deleted file mode 100644 index 86aba09703..0000000000 --- a/libopenage/gui/guisys/private/platforms/context_extraction_win32.cpp +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. - -#include - -#include -#include "context_extraction.h" - -#include -#include -#include - -namespace qtsdl { - -std::tuple extract_native_context(SDL_Window *window) { - assert(window); - - HGLRC current_context; - SDL_SysWMinfo wm_info; - SDL_VERSION(&wm_info.version); - if (SDL_GetWindowWMInfo(window, &wm_info)) { - current_context = wglGetCurrentContext(); - assert(current_context); - } - QWGLNativeContext nativeContext(current_context, wm_info.info.win.window); - return {QVariant::fromValue(nativeContext), reinterpret_cast(wm_info.info.win.window)}; -} - -std::tuple> extract_native_context_and_switchback_func(SDL_Window *window) { - assert(window); - - HGLRC current_context; - SDL_SysWMinfo wm_info; - SDL_VERSION(&wm_info.version); - if (SDL_GetWindowWMInfo(window, &wm_info)) { - current_context = wglGetCurrentContext(); - assert(current_context); - - return std::make_tuple(QVariant::fromValue(QWGLNativeContext(current_context, wm_info.info.win.window)), [wm_info, current_context] { - wglMakeCurrent(wm_info.info.win.hdc, current_context); - }); - } - - return std::tuple>{}; -} - - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/platforms/context_extraction_x11.cpp b/libopenage/gui/guisys/private/platforms/context_extraction_x11.cpp deleted file mode 100644 index e320c9b6e4..0000000000 --- a/libopenage/gui/guisys/private/platforms/context_extraction_x11.cpp +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#include - -#include "context_extraction.h" - -// #include -#include -#include - -// DO NOT INCLUDE ANYTHING HERE, X11 HEADERS BREAK STUFF - -namespace qtsdl { - -// std::tuple extract_native_context(SDL_Window *window) { -// assert(window); - -// GLXContext current_context = nullptr; -// SDL_SysWMinfo wm_info; -// SDL_VERSION(&wm_info.version); - -// if (SDL_GetWindowWMInfo(window, &wm_info)) { -// assert(wm_info.info.x11.display); - -// current_context = glXGetCurrentContext(); -// assert(current_context); - -// return std::make_tuple( -// QVariant::fromValue( -// QGLXNativeContext(current_context, -// wm_info.info.x11.display, -// wm_info.info.x11.window)), -// wm_info.info.x11.window -// ); -// } - -// return std::tuple{}; -// } - -// std::tuple> extract_native_context_and_switchback_func(SDL_Window *window) { -// assert(window); - -// GLXContext current_context; -// SDL_SysWMinfo wm_info; -// SDL_VERSION(&wm_info.version); - -// if (SDL_GetWindowWMInfo(window, &wm_info)) { -// assert(wm_info.info.x11.display); - -// current_context = glXGetCurrentContext(); -// assert(current_context); - -// return std::make_tuple(QVariant::fromValue(QGLXNativeContext(current_context, wm_info.info.x11.display, wm_info.info.x11.window)), [wm_info, current_context] { -// glXMakeCurrent(wm_info.info.x11.display, wm_info.info.x11.window, current_context); -// }); -// } - -// return std::tuple>{}; -// } - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/public/gui_application.cpp b/libopenage/gui/guisys/public/gui_application.cpp deleted file mode 100644 index 1b7a511095..0000000000 --- a/libopenage/gui/guisys/public/gui_application.cpp +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. - -#include - -#include "../public/gui_application.h" - -#include "../private/gui_application_impl.h" - -namespace qtsdl { - -GuiApplication::GuiApplication() - : - application{GuiApplicationImpl::get()} { -} - -GuiApplication::GuiApplication(std::shared_ptr application) - : - application{application} { -} - -GuiApplication::~GuiApplication() = default; - -void GuiApplication::processEvents() { - this->application->processEvents(); -} - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/public/gui_application.h b/libopenage/gui/guisys/public/gui_application.h deleted file mode 100644 index 6b64e6b111..0000000000 --- a/libopenage/gui/guisys/public/gui_application.h +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -namespace qtsdl { - -class GuiApplicationImpl; - -/** - * Houses gui logic event queue. - */ -class GuiApplication { -public: - GuiApplication(); - GuiApplication(std::shared_ptr application); - ~GuiApplication(); - - void processEvents(); - -private: - std::shared_ptr application; -}; - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/public/gui_engine.cpp b/libopenage/gui/guisys/public/gui_engine.cpp deleted file mode 100644 index 689a4ff87f..0000000000 --- a/libopenage/gui/guisys/public/gui_engine.cpp +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. - -#include "../public/gui_engine.h" - -#include "../private/gui_engine_impl.h" - -namespace qtsdl { - -GuiEngine::GuiEngine(GuiRenderer *renderer, const std::vector &image_providers, GuiSingletonItemsInfo *singleton_items_info) - : - impl{std::make_unique(renderer, image_providers, singleton_items_info)} { -} - -GuiEngine::~GuiEngine() = default; - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/public/gui_engine.h b/libopenage/gui/guisys/public/gui_engine.h deleted file mode 100644 index 153ceb11ab..0000000000 --- a/libopenage/gui/guisys/public/gui_engine.h +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include - -namespace qtsdl { - -class GuiRenderer; -class GuiImageProvider; -class GuiEngineImpl; -class GuiSingletonItemsInfo; - -/** - * Represents one QML execution environment. - */ -class GuiEngine { -public: - explicit GuiEngine(GuiRenderer *renderer, - const std::vector &image_providers = std::vector(), - GuiSingletonItemsInfo *singleton_items_info = nullptr); - ~GuiEngine(); - -private: - friend class GuiEngineImpl; - std::unique_ptr impl; -}; - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/public/gui_event_queue.cpp b/libopenage/gui/guisys/public/gui_event_queue.cpp deleted file mode 100644 index 4b9866c1a9..0000000000 --- a/libopenage/gui/guisys/public/gui_event_queue.cpp +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. - -#include "../public/gui_event_queue.h" - -#include "../private/gui_event_queue_impl.h" - -namespace qtsdl { - -GuiEventQueue::GuiEventQueue() - : - impl{std::make_unique()} { -} - -GuiEventQueue::~GuiEventQueue() = default; - -void GuiEventQueue::process_callbacks() { - this->impl->process_callbacks(); -} - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/public/gui_event_queue.h b/libopenage/gui/guisys/public/gui_event_queue.h deleted file mode 100644 index d7b6076e9a..0000000000 --- a/libopenage/gui/guisys/public/gui_event_queue.h +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -namespace qtsdl { - -class GuiEventQueueImpl; - -/** - * Provides synchronization with some game thread. - */ -class GuiEventQueue { -public: - explicit GuiEventQueue(); - ~GuiEventQueue(); - - void process_callbacks(); - -private: - friend class GuiEventQueueImpl; - std::unique_ptr impl; -}; - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/public/gui_image_provider.cpp b/libopenage/gui/guisys/public/gui_image_provider.cpp deleted file mode 100644 index 961a0f3c20..0000000000 --- a/libopenage/gui/guisys/public/gui_image_provider.cpp +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. - -#include "../public/gui_image_provider.h" - -#include "../private/gui_image_provider_impl.h" - -namespace qtsdl { - -GuiImageProvider::GuiImageProvider(std::unique_ptr impl) - : - impl{impl.release(), std::default_delete()} { -} - -GuiImageProvider::~GuiImageProvider() = default; - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/public/gui_image_provider.h b/libopenage/gui/guisys/public/gui_image_provider.h deleted file mode 100644 index 6a5e074769..0000000000 --- a/libopenage/gui/guisys/public/gui_image_provider.h +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include - -namespace qtsdl { - -class GuiImageProviderImpl; - -class GuiImageProvider { -public: - explicit GuiImageProvider(std::unique_ptr impl); - ~GuiImageProvider(); - -private: - friend class GuiImageProviderImpl; - std::unique_ptr> impl; -}; - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/public/gui_input.cpp b/libopenage/gui/guisys/public/gui_input.cpp deleted file mode 100644 index c2e007d944..0000000000 --- a/libopenage/gui/guisys/public/gui_input.cpp +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. - -#include "../public/gui_input.h" - -#include "../private/gui_input_impl.h" - -namespace qtsdl { - -GuiInput:: GuiInput(GuiRenderer *renderer, GuiEventQueue *game_logic_updater) - : - impl{std::make_unique(renderer, game_logic_updater)} { -} - -GuiInput::~GuiInput() = default; - -bool GuiInput::process(SDL_Event *event) { - return this->impl->process(event); -} - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/public/gui_input.h b/libopenage/gui/guisys/public/gui_input.h deleted file mode 100644 index 74e8adb118..0000000000 --- a/libopenage/gui/guisys/public/gui_input.h +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -#include - -namespace qtsdl { - -class GuiRenderer; -class GuiEventQueue; -class GuiInputImpl; - -/** - * Converts SDL input events into Qt events. - */ -class GuiInput { -public: - explicit GuiInput(GuiRenderer *renderer, GuiEventQueue *game_logic_updater); - ~GuiInput(); - - /** - * Returns true if the event was accepted. - */ - bool process(SDL_Event *event); - -private: - friend class GuiInputImpl; - std::unique_ptr impl; -}; - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/public/gui_property_map.cpp b/libopenage/gui/guisys/public/gui_property_map.cpp deleted file mode 100644 index b52b3a31b3..0000000000 --- a/libopenage/gui/guisys/public/gui_property_map.cpp +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#include "gui_property_map.h" - -#include -#include -#include -#include - -#include -#include - -#include - -#include "../link/gui_property_map_impl.h" - -namespace qtsdl { - -GuiPropertyMap::GuiPropertyMap() : - impl{std::make_unique()} { - assert(QCoreApplication::instance()); -} - -GuiPropertyMap::~GuiPropertyMap() = default; - -namespace { -template -T getter(const QObject &map, const char *name) { - auto v = map.property(name); - - if (!v.isValid()) - return T(); - - using namespace std::string_literals; - - if (!v.canConvert()) - throw std::runtime_error("Can't interpret a property '"s + name + "' of type '"s + v.typeName() + "' as '"s + typeid(T).name() + "'."s); - - return v.value(); -} - -template -void setter(QObject &map, const char *name, T v) { - map.setProperty(name, QVariant::fromValue(v)); -} - -template <> -void setter(QObject &map, const char *name, const char *v) { - map.setProperty(name, v); -} - -std::vector strings_to_vector(const QStringList &strings) { - std::vector result(strings.size()); - std::transform(std::begin(strings), std::end(strings), std::begin(result), [](const QString &s) { return s.toStdString(); }); - - return result; -} - -QStringList vector_to_strings(const std::vector &v) { - QStringList strings; - std::transform(std::begin(v), std::end(v), std::back_inserter(strings), [](const std::string &s) { return QString::fromStdString(s); }); - - return strings; -} -} // namespace - -template <> -bool GuiPropertyMap::getv(const char *name) const { - return getter(*this->impl, name); -} - -template <> -void GuiPropertyMap::setv(const char *name, bool v) { - setter(*this->impl, name, v); -} - -template <> -int GuiPropertyMap::getv(const char *name) const { - return getter(*this->impl, name); -} - -template <> -void GuiPropertyMap::setv(const char *name, int v) { - setter(*this->impl, name, v); -} - -template <> -double GuiPropertyMap::getv(const char *name) const { - return getter(*this->impl, name); -} - -template <> -void GuiPropertyMap::setv(const char *name, double v) { - setter(*this->impl, name, v); -} - -template <> -std::string GuiPropertyMap::getv(const char *name) const { - return getter(*this->impl, name).toStdString(); -} - -template <> -void GuiPropertyMap::setv(const char *name, const std::string &v) { - setter(*this->impl, name, QString::fromStdString(v)); -} - -template <> -void GuiPropertyMap::setv(const char *name, const char *v) { - setter(*this->impl, name, v); -} - -template <> -std::vector GuiPropertyMap::getv>(const char *name) const { - return strings_to_vector(getter(*this->impl, name)); -} - -template <> -void GuiPropertyMap::setv &>(const char *name, const std::vector &v) { - setter(*this->impl, name, vector_to_strings(v)); -} - -template <> -QString GuiPropertyMap::getv(const char *name) const { - return getter(*this->impl, name); -} - -template <> -void GuiPropertyMap::setv(const char *name, const QString &v) { - setter(*this->impl, name, v); -} - -template <> -QStringList GuiPropertyMap::getv(const char *name) const { - return getter(*this->impl, name); -} - -template <> -void GuiPropertyMap::setv(const char *name, const QStringList &v) { - setter(*this->impl, name, v); -} - -void GuiPropertyMap::setv(const char *name, const std::string &v) { - this->setv(name, v); -} - -void GuiPropertyMap::setv(const std::string &name, const std::string &v) { - this->setv(name.c_str(), v); -} - -void GuiPropertyMap::setv(const char *name, const std::vector &v) { - this->setv &>(name, v); -} - -void GuiPropertyMap::setv(const std::string &name, const std::vector &v) { - this->setv &>(name.c_str(), v); -} - -std::vector GuiPropertyMap::get_csv(const char *name) const { - return strings_to_vector(getter(*this->impl, name).split(QRegularExpression("\\s*,\\s*"))); -} - -void GuiPropertyMap::set_csv(const char *name, const std::vector &v) { - setter(*this->impl, name, vector_to_strings(v).join(", ")); -} - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/public/gui_property_map.h b/libopenage/gui/guisys/public/gui_property_map.h deleted file mode 100644 index 7c67238027..0000000000 --- a/libopenage/gui/guisys/public/gui_property_map.h +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include -#include - -namespace qtsdl { - -class GuiPropertyMapImpl; - -struct GuiPropertyMap { - GuiPropertyMap(); - ~GuiPropertyMap(); - - template - T getv(const char *name) const; - - template - T getv(const std::string &name) const { - return this->getv(name.c_str()); - } - - std::vector get_csv(const char *name) const; - - std::vector get_csv(const std::string &name) const { - return this->get_csv(name.c_str()); - } - - template - void setv(const char *name, T v); - - template - void setv(const std::string &name, T v) { - this->setv(name.c_str(), v); - } - - void set_csv(const char *name, const std::vector &v); - - void set_csv(const std::string &name, const std::vector &v) { - this->set_csv(name.c_str(), v); - } - - void setv(const char *name, const std::string &v); - void setv(const std::string &name, const std::string &v); - - void setv(const char *name, const std::vector &v); - void setv(const std::string &name, const std::vector &v); - - std::unique_ptr impl; -}; - -template<> -bool GuiPropertyMap::getv(const char *name) const; - -template<> -void GuiPropertyMap::setv(const char *name, bool v); - -template<> -int GuiPropertyMap::getv(const char *name) const; - -template<> -void GuiPropertyMap::setv(const char *name, int v); - -template<> -double GuiPropertyMap::getv(const char *name) const; - -template<> -void GuiPropertyMap::setv(const char *name, double v); - -template<> -std::string GuiPropertyMap::getv(const char *name) const; - -template<> -void GuiPropertyMap::setv(const char *name, const std::string &v); - -template<> -void GuiPropertyMap::setv(const char *name, const char *v); - -template<> -std::vector GuiPropertyMap::getv>(const char *name) const; - -template<> -void GuiPropertyMap::setv&>(const char *name, const std::vector &v); - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/public/gui_renderer.cpp b/libopenage/gui/guisys/public/gui_renderer.cpp deleted file mode 100644 index 7fb182f0b0..0000000000 --- a/libopenage/gui/guisys/public/gui_renderer.cpp +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. - -#include "../public/gui_renderer.h" - -#include - -#include "../private/gui_renderer_impl.h" - -namespace qtsdl { - -GuiRenderer::GuiRenderer(SDL_Window *window) - : - impl{std::make_unique(window)} { -} - -GuiRenderer::~GuiRenderer() = default; - -GLuint GuiRenderer::render() { - return this->impl->render(); -} - -void GuiRenderer::resize(int w, int h) { - this->impl->resize(QSize{w, h}); -} - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/public/gui_renderer.h b/libopenage/gui/guisys/public/gui_renderer.h deleted file mode 100644 index 7139dee405..0000000000 --- a/libopenage/gui/guisys/public/gui_renderer.h +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -#ifndef __APPLE__ -#ifdef _MSC_VER -#define NOMINMAX -#include -#endif //_MSC_VER -#include -#else // __APPLE__ -#include -#endif - -struct SDL_Window; - -namespace qtsdl { - -class GuiRendererImpl; - -/** - * Passes the native graphic context to Qt. - */ -class GuiRenderer { -public: - // TODO: allow FBO variant - explicit GuiRenderer(SDL_Window *window); - ~GuiRenderer(); - - GLuint render(); - void resize(int w, int h); - -private: - friend class GuiRendererImpl; - std::unique_ptr impl; -}; - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/public/gui_singleton_items_info.h b/libopenage/gui/guisys/public/gui_singleton_items_info.h deleted file mode 100644 index 1889b4c2c0..0000000000 --- a/libopenage/gui/guisys/public/gui_singleton_items_info.h +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. - -#pragma once - -namespace qtsdl { - -/** - * Accessible during creation of any singleton QML item. - */ -class GuiSingletonItemsInfo { -}; - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/public/gui_subtree.cpp b/libopenage/gui/guisys/public/gui_subtree.cpp deleted file mode 100644 index 00abcedab1..0000000000 --- a/libopenage/gui/guisys/public/gui_subtree.cpp +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. - -#include "gui_subtree.h" - -#include "../private/gui_subtree_impl.h" - -namespace qtsdl { - -GuiSubtree::GuiSubtree(GuiRenderer *renderer, - GuiEventQueue *game_logic_updater, - GuiEngine *engine, - const std::string &source, - const std::string &rootdir) - : - impl{std::make_unique( - renderer, - game_logic_updater, - engine, - QString::fromStdString(source), - QString::fromStdString(rootdir) - )} {} - -GuiSubtree::~GuiSubtree() = default; - -} // namespace qtsdl diff --git a/libopenage/gui/guisys/public/gui_subtree.h b/libopenage/gui/guisys/public/gui_subtree.h deleted file mode 100644 index ffd4141b2a..0000000000 --- a/libopenage/gui/guisys/public/gui_subtree.h +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include - - -namespace qtsdl { - -class GuiRenderer; -class GuiEventQueue; -class GuiEngine; -class GuiSubtreeImpl; - - -/** - * A root item that loads its code from source url. - * - * rootdir is the qml file root folder which is watched for changes. - */ -class GuiSubtree { -public: - explicit GuiSubtree(GuiRenderer *renderer, - GuiEventQueue *game_logic_updater, - GuiEngine *engine, - const std::string &source, - const std::string &rootdir); - - ~GuiSubtree(); - -private: - friend class GuiSubtreeImpl; - std::unique_ptr impl; -}; - -} // namespace qtsdl diff --git a/libopenage/gui/integration/CMakeLists.txt b/libopenage/gui/integration/CMakeLists.txt deleted file mode 100644 index 0bb272b5c5..0000000000 --- a/libopenage/gui/integration/CMakeLists.txt +++ /dev/null @@ -1,2 +0,0 @@ -add_subdirectory("private") -add_subdirectory("public") diff --git a/libopenage/gui/integration/private/CMakeLists.txt b/libopenage/gui/integration/private/CMakeLists.txt deleted file mode 100644 index 97f03fc4f4..0000000000 --- a/libopenage/gui/integration/private/CMakeLists.txt +++ /dev/null @@ -1,15 +0,0 @@ -add_sources(libopenage - gui_filled_texture_handles.cpp - gui_game_spec_image_provider_by_filename_impl.cpp - gui_game_spec_image_provider_by_graphic_id_impl.cpp - gui_game_spec_image_provider_by_id_impl.cpp - gui_game_spec_image_provider_by_terrain_id_impl.cpp - gui_game_spec_image_provider_impl.cpp - gui_image_provider_link.cpp - gui_log.cpp - gui_make_standalone_subtexture.cpp - gui_standalone_subtexture.cpp - gui_texture.cpp - gui_texture_factory.cpp - gui_texture_handle.cpp -) diff --git a/libopenage/gui/integration/private/gui_game_spec_image_provider_by_filename_impl.cpp b/libopenage/gui/integration/private/gui_game_spec_image_provider_by_filename_impl.cpp deleted file mode 100644 index 216422e5ba..0000000000 --- a/libopenage/gui/integration/private/gui_game_spec_image_provider_by_filename_impl.cpp +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2015-2021 the openage authors. See copying.md for legal info. - -#include "gui_game_spec_image_provider_by_filename_impl.h" - -#include "../../../error/error.h" - -#include "../../../gamestate/old/game_spec.h" - -namespace openage::gui { - -GuiGameSpecImageProviderByFilenameImpl::GuiGameSpecImageProviderByFilenameImpl(qtsdl::GuiEventQueue *render_updater) - : - GuiGameSpecImageProviderImpl{render_updater} { -} - -GuiGameSpecImageProviderByFilenameImpl::~GuiGameSpecImageProviderByFilenameImpl() = default; - -const char* GuiGameSpecImageProviderByFilenameImpl::id() { - return "by-filename"; -} - -const char* GuiGameSpecImageProviderByFilenameImpl::get_id() const { - return GuiGameSpecImageProviderByFilenameImpl::id(); -} - -TextureHandle GuiGameSpecImageProviderByFilenameImpl::get_texture_handle(const QString &id) { - ENSURE(this->loaded_game_spec, "trying to actually get a texture from a non-loaded spec"); - - auto filename = id.section(".", 0, -2); - auto subid_str = id.section(".", -1, -1); - - bool ok = false; - const int subid = subid_str.toInt(&ok); - - if (not filename.isEmpty() and ok) { - auto tex = this->loaded_game_spec->get_texture(filename.toStdString()); - - if (tex != nullptr) { - return TextureHandle{tex, subid}; - } - else { - return this->get_missing_texture(); - } - } else { - qWarning("Invalid texture id: 'image://%s/%s'. Example formatting: 'image://%s/myfile.png.18'.", this->get_id(), qUtf8Printable(id), this->get_id()); - return this->get_missing_texture(); - } -} - -} // namespace openage::gui diff --git a/libopenage/gui/integration/private/gui_game_spec_image_provider_by_filename_impl.h b/libopenage/gui/integration/private/gui_game_spec_image_provider_by_filename_impl.h deleted file mode 100644 index 0def78ae82..0000000000 --- a/libopenage/gui/integration/private/gui_game_spec_image_provider_by_filename_impl.h +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. - -#pragma once - -#include "gui_game_spec_image_provider_impl.h" - -namespace openage { -namespace gui { - -/** - * Exposes game textures to the Qt by their file name. - * - * Id has a form of . where is an integer. - */ -class GuiGameSpecImageProviderByFilenameImpl : public GuiGameSpecImageProviderImpl { -public: - explicit GuiGameSpecImageProviderByFilenameImpl(qtsdl::GuiEventQueue *render_updater); - virtual ~GuiGameSpecImageProviderByFilenameImpl(); - - static const char* id(); - -private: - virtual const char* get_id() const override; - virtual TextureHandle get_texture_handle(const QString &id) override; -}; - -}} // namespace openage::gui diff --git a/libopenage/gui/integration/private/gui_game_spec_image_provider_by_graphic_id_impl.cpp b/libopenage/gui/integration/private/gui_game_spec_image_provider_by_graphic_id_impl.cpp deleted file mode 100644 index ede6e5ef21..0000000000 --- a/libopenage/gui/integration/private/gui_game_spec_image_provider_by_graphic_id_impl.cpp +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2015-2021 the openage authors. See copying.md for legal info. - -#include "gui_game_spec_image_provider_by_graphic_id_impl.h" - -#include "../../../error/error.h" - -#include "../../../gamestate/old/game_spec.h" - -namespace openage::gui { - -GuiGameSpecImageProviderByGraphicIdImpl::GuiGameSpecImageProviderByGraphicIdImpl(qtsdl::GuiEventQueue *render_updater) - : - GuiGameSpecImageProviderByIdImpl{render_updater} { -} - -GuiGameSpecImageProviderByGraphicIdImpl::~GuiGameSpecImageProviderByGraphicIdImpl() = default; - -const char* GuiGameSpecImageProviderByGraphicIdImpl::id() { - return "by-graphic-id"; -} - -const char* GuiGameSpecImageProviderByGraphicIdImpl::get_id() const { - return GuiGameSpecImageProviderByGraphicIdImpl::id(); -} - -openage::Texture* GuiGameSpecImageProviderByGraphicIdImpl::get_texture(int texture_id) { - ENSURE(this->loaded_game_spec, "trying to actually get a texture from a non-loaded spec"); - return this->loaded_game_spec->get_texture(texture_id); -} - -} // namespace openage::gui diff --git a/libopenage/gui/integration/private/gui_game_spec_image_provider_by_graphic_id_impl.h b/libopenage/gui/integration/private/gui_game_spec_image_provider_by_graphic_id_impl.h deleted file mode 100644 index 02e1c1be4b..0000000000 --- a/libopenage/gui/integration/private/gui_game_spec_image_provider_by_graphic_id_impl.h +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. - -#pragma once - -#include "gui_game_spec_image_provider_by_id_impl.h" - -namespace openage { -namespace gui { - -/** - * Exposes game textures to the Qt by their id. - * - * Numeric id has a form of .. - */ -class GuiGameSpecImageProviderByGraphicIdImpl : public GuiGameSpecImageProviderByIdImpl { -public: - explicit GuiGameSpecImageProviderByGraphicIdImpl(qtsdl::GuiEventQueue *render_updater); - virtual ~GuiGameSpecImageProviderByGraphicIdImpl(); - - static const char* id(); - -private: - virtual const char* get_id() const override; - virtual openage::Texture* get_texture(int texture_id) override; -}; - -}} // namespace openage::gui diff --git a/libopenage/gui/integration/private/gui_game_spec_image_provider_by_id_impl.cpp b/libopenage/gui/integration/private/gui_game_spec_image_provider_by_id_impl.cpp deleted file mode 100644 index 82752e06de..0000000000 --- a/libopenage/gui/integration/private/gui_game_spec_image_provider_by_id_impl.cpp +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#include "gui_game_spec_image_provider_by_id_impl.h" - -#include "../../../error/error.h" - -#include "../../../gamestate/old/game_spec.h" -#include "gui_texture_factory.h" - -namespace openage::gui { - -GuiGameSpecImageProviderByIdImpl::GuiGameSpecImageProviderByIdImpl(qtsdl::GuiEventQueue *render_updater) : - GuiGameSpecImageProviderImpl{render_updater} { -} - -GuiGameSpecImageProviderByIdImpl::~GuiGameSpecImageProviderByIdImpl() = default; - -TextureHandle GuiGameSpecImageProviderByIdImpl::get_texture_handle(const QString &id) { - ENSURE(this->loaded_game_spec, "trying to actually get a texture from a non-loaded spec"); - - auto ids = id.split("."); - - if (ids.size() == 2) { - bool id_ok = false, subid_ok = false; - const int texture_id = ids[0].toInt(&id_ok); - const int subid = ids[1].toInt(&subid_ok); - - auto tex = (id_ok and subid_ok) ? - this->get_texture(texture_id) : - nullptr; - - if (tex != nullptr and subid < static_cast(tex->get_subtexture_count())) { - return TextureHandle{tex, subid}; - } - else { - return this->get_missing_texture(); - } - } - else { - qWarning("Invalid texture id: 'image://%s/%s'. Example formatting: 'image://%s/7366.18'.", this->get_id(), qUtf8Printable(id), this->get_id()); - return this->get_missing_texture(); - } -} - -} // namespace openage::gui diff --git a/libopenage/gui/integration/private/gui_game_spec_image_provider_by_id_impl.h b/libopenage/gui/integration/private/gui_game_spec_image_provider_by_id_impl.h deleted file mode 100644 index d52402a669..0000000000 --- a/libopenage/gui/integration/private/gui_game_spec_image_provider_by_id_impl.h +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. - -#pragma once - -#include "gui_game_spec_image_provider_impl.h" - -namespace openage { -namespace gui { - -/** - * Base for providers that expose textures to the Qt by their id. - * - * Numeric id has a form of .. - */ -class GuiGameSpecImageProviderByIdImpl : public GuiGameSpecImageProviderImpl { -public: - explicit GuiGameSpecImageProviderByIdImpl(qtsdl::GuiEventQueue *render_updater); - virtual ~GuiGameSpecImageProviderByIdImpl(); - -private: - virtual TextureHandle get_texture_handle(const QString &id) override; - virtual openage::Texture* get_texture(int texture_id) = 0; -}; - -}} // namespace openage::gui diff --git a/libopenage/gui/integration/private/gui_game_spec_image_provider_by_terrain_id_impl.cpp b/libopenage/gui/integration/private/gui_game_spec_image_provider_by_terrain_id_impl.cpp deleted file mode 100644 index f875118636..0000000000 --- a/libopenage/gui/integration/private/gui_game_spec_image_provider_by_terrain_id_impl.cpp +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2015-2021 the openage authors. See copying.md for legal info. - -#include "gui_game_spec_image_provider_by_terrain_id_impl.h" - -#include "../../../error/error.h" - -#include "../../../gamestate/old/game_spec.h" - -namespace openage::gui { - -GuiGameSpecImageProviderByTerrainIdImpl::GuiGameSpecImageProviderByTerrainIdImpl(qtsdl::GuiEventQueue *render_updater) - : - GuiGameSpecImageProviderByIdImpl{render_updater} { -} - -GuiGameSpecImageProviderByTerrainIdImpl::~GuiGameSpecImageProviderByTerrainIdImpl() = default; - -const char* GuiGameSpecImageProviderByTerrainIdImpl::id() { - return "by-terrain-id"; -} - -const char* GuiGameSpecImageProviderByTerrainIdImpl::get_id() const { - return GuiGameSpecImageProviderByTerrainIdImpl::id(); -} - -openage::Texture* GuiGameSpecImageProviderByTerrainIdImpl::get_texture(int texture_id) { - ENSURE(this->loaded_game_spec, "trying to actually get a texture from a non-loaded spec"); - auto meta = this->loaded_game_spec->get_terrain_meta(); - return meta && texture_id >=0 && texture_id < std::distance(std::begin(meta->textures), std::end(meta->textures)) ? meta->textures[texture_id] : nullptr; -} - -} // namespace openage::gui diff --git a/libopenage/gui/integration/private/gui_game_spec_image_provider_by_terrain_id_impl.h b/libopenage/gui/integration/private/gui_game_spec_image_provider_by_terrain_id_impl.h deleted file mode 100644 index d5aba95279..0000000000 --- a/libopenage/gui/integration/private/gui_game_spec_image_provider_by_terrain_id_impl.h +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. - -#pragma once - -#include "gui_game_spec_image_provider_by_id_impl.h" - -namespace openage { -namespace gui { - -/** - * Exposes terrain textures to the Qt by their id. - * - * Numeric id has a form of . where texture-id is the position in the terrain_meta. - */ -class GuiGameSpecImageProviderByTerrainIdImpl : public GuiGameSpecImageProviderByIdImpl { -public: - explicit GuiGameSpecImageProviderByTerrainIdImpl(qtsdl::GuiEventQueue *render_updater); - virtual ~GuiGameSpecImageProviderByTerrainIdImpl(); - - static const char* id(); - -private: - virtual const char* get_id() const override; - virtual openage::Texture* get_texture(int texture_id) override; -}; - -}} // namespace openage::gui diff --git a/libopenage/gui/integration/private/gui_game_spec_image_provider_impl.cpp b/libopenage/gui/integration/private/gui_game_spec_image_provider_impl.cpp deleted file mode 100644 index 4be3693854..0000000000 --- a/libopenage/gui/integration/private/gui_game_spec_image_provider_impl.cpp +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright 2015-2021 the openage authors. See copying.md for legal info. - -#include "gui_game_spec_image_provider_impl.h" - -#include - -#include - -#include "../../../error/error.h" - -#include "../../../gamestate/old/game_spec.h" -#include "../../guisys/private/gui_event_queue_impl.h" - -#include "gui_texture_factory.h" -#include "gui_filled_texture_handles.h" - -namespace openage::gui { - -GuiGameSpecImageProviderImpl::GuiGameSpecImageProviderImpl(qtsdl::GuiEventQueue *render_updater) - : - GuiImageProviderImpl{}, - invalidated{}, - filled_handles{std::make_shared()}, - ended{} { - - QThread *render_thread = qtsdl::GuiEventQueueImpl::impl(render_updater)->get_thread(); - this->render_thread_callback.moveToThread(render_thread); - QObject::connect(&this->render_thread_callback, &qtsdl::GuiCallback::process_blocking, &this->render_thread_callback, &qtsdl::GuiCallback::process, render_thread != QThread::currentThread() ? Qt::BlockingQueuedConnection : Qt::DirectConnection); -} - -GuiGameSpecImageProviderImpl::~GuiGameSpecImageProviderImpl() = default; - -void GuiGameSpecImageProviderImpl::on_game_spec_loaded(const std::shared_ptr& loaded_game_spec) { - ENSURE(loaded_game_spec, "spec hasn't been checked or was invalidated"); - - std::unique_lock lck{this->loaded_game_spec_mutex}; - - if (invalidated || loaded_game_spec != this->loaded_game_spec) { - migrate_to_new_game_spec(loaded_game_spec); - invalidated = false; - - lck.unlock(); - this->loaded_game_spec_cond.notify_one(); - } -} - -void GuiGameSpecImageProviderImpl::migrate_to_new_game_spec(const std::shared_ptr& loaded_game_spec) { - ENSURE(loaded_game_spec, "spec hasn't been checked or was invalidated"); - - if (this->loaded_game_spec) { - auto keep_old_game_spec = this->loaded_game_spec; - - // Need to set up this because the load functions use this->loaded_game_spec internally. - this->loaded_game_spec = loaded_game_spec; - - emit this->render_thread_callback.process_blocking([this] { - using namespace std::placeholders; - this->filled_handles->refresh_all_handles_with_texture(std::bind(&GuiGameSpecImageProviderImpl::overwrite_texture_handle, this, _1, _2, _3)); - }); - } else { - this->loaded_game_spec = loaded_game_spec; - } -} - -void GuiGameSpecImageProviderImpl::overwrite_texture_handle(const QString &id, const QSize &requested_size, SizedTextureHandle *filled_handle) { - const TextureHandle texture_handle = this->get_texture_handle(id); - *filled_handle = {texture_handle, aspect_fit_size(texture_handle, requested_size)}; -} - -void GuiGameSpecImageProviderImpl::on_game_spec_invalidated() { - std::unique_lock lck{this->loaded_game_spec_mutex}; - - if (this->loaded_game_spec) { - const TextureHandle missing_texture = get_missing_texture(); - - emit this->render_thread_callback.process_blocking([this, &missing_texture] { - this->filled_handles->fill_all_handles_with_texture(missing_texture); - }); - - invalidated = true; - } -} - -GuiFilledTextureHandleUser GuiGameSpecImageProviderImpl::fill_texture_handle(const QString &id, const QSize &requested_size, SizedTextureHandle *filled_handle) { - this->overwrite_texture_handle(id, requested_size, filled_handle); - return GuiFilledTextureHandleUser(this->filled_handles, id, requested_size, filled_handle); -} - -TextureHandle GuiGameSpecImageProviderImpl::get_missing_texture() { - return TextureHandle{this->loaded_game_spec->get_texture("missing.png", false), -1}; -} - -QQuickTextureFactory* GuiGameSpecImageProviderImpl::requestTexture(const QString &id, QSize *size, const QSize &requestedSize) { - std::unique_lock lck{this->loaded_game_spec_mutex}; - - this->loaded_game_spec_cond.wait(lck, [this] {return this->ended || this->loaded_game_spec;}); - - if (this->ended) { - qWarning("ImageProvider was stopped during the load, so it'll appear like the requestTexture() isn't implemented."); - return this->GuiImageProviderImpl::requestTexture(id, size, requestedSize); - } else { - auto tex_factory = new GuiTextureFactory{this, id, requestedSize}; - *size = tex_factory->textureSize(); - return tex_factory; - } -} - -void GuiGameSpecImageProviderImpl::give_up() { - { - std::unique_lock lck{this->loaded_game_spec_mutex}; - this->ended = true; - } - - this->loaded_game_spec_cond.notify_one(); -} - -} // namespace openage::gui diff --git a/libopenage/gui/integration/private/gui_game_spec_image_provider_impl.h b/libopenage/gui/integration/private/gui_game_spec_image_provider_impl.h deleted file mode 100644 index 4882d67447..0000000000 --- a/libopenage/gui/integration/private/gui_game_spec_image_provider_impl.h +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include -#include -#include -#include - -#include "../../guisys/private/gui_image_provider_impl.h" -#include "../../guisys/private/gui_callback.h" -#include "gui_texture_handle.h" -#include "gui_filled_texture_handles.h" - -namespace qtsdl { - -class GuiEventQueue; -class GuiEventQueueImpl; - -} // namespace qtsdl - -namespace openage { - -class GameSpec; -class Texture; - -namespace gui { - -class GuiTexture; - -/** - * Exposes game textures to the Qt. - */ -class GuiGameSpecImageProviderImpl : public qtsdl::GuiImageProviderImpl { -public: - explicit GuiGameSpecImageProviderImpl(qtsdl::GuiEventQueue *render_updater); - virtual ~GuiGameSpecImageProviderImpl(); - - /** - * Unblocks the provider. - * - * Refreshes already loaded assets (if this->loaded_game_spec wasn't null before the call). - * - * @param loaded_game_spec new source (can't be null) - */ - void on_game_spec_loaded(const std::shared_ptr& loaded_game_spec); - - /** - * Set to every sprite the 'missing texture' from current spec. - * - * Needed as a visible reaction to setting the property to null. - * We can't unload the textures without recreating the engine, so we keep the old source. - */ - void on_game_spec_invalidated(); - - /** - * Fills in the provided handle object. - * - * Memorizes pointer to it in order to update it if the source changes. - * - * @return pointer to all memorized handles, so the client can unsubscribe. - */ - GuiFilledTextureHandleUser fill_texture_handle(const QString &id, const QSize &requested_size, SizedTextureHandle *filled_handle); - -protected: - TextureHandle get_missing_texture(); - - std::shared_ptr loaded_game_spec; - - /** - * When true, still uses the old source but shows the 'missing texture'. - */ - bool invalidated; - -private: - virtual TextureHandle get_texture_handle(const QString &id) = 0; - virtual QQuickTextureFactory* requestTexture(const QString &id, QSize *size, const QSize &requestedSize) override; - virtual void give_up() override; - - /** - * Change the already produced texture handles to use new source. - */ - void migrate_to_new_game_spec(const std::shared_ptr& loaded_game_spec); - - void overwrite_texture_handle(const QString &id, const QSize &requested_size, SizedTextureHandle *filled_handle); - - /** - * Changing the texture handles underneath the QSGTexture to reflect the reload of the GameSpec. - * - * It's not a proper Qt usage, so the live reload of the game assets for the gui may break in future Qt releases. - * When it breaks, this feature should be implemented via the recreation of the qml engine. - */ - std::shared_ptr filled_handles; - - std::mutex loaded_game_spec_mutex; - std::condition_variable loaded_game_spec_cond; - bool ended; - - qtsdl::GuiCallback render_thread_callback; -}; - -}} // namespace openage::gui diff --git a/libopenage/gui/integration/private/gui_image_provider_link.cpp b/libopenage/gui/integration/private/gui_image_provider_link.cpp deleted file mode 100644 index a0454449c7..0000000000 --- a/libopenage/gui/integration/private/gui_image_provider_link.cpp +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. - -#include "gui_image_provider_link.h" - -#include - -#include - -#include "../../../error/error.h" - -#include "../../guisys/link/qml_engine_with_singleton_items_info.h" -#include "../../guisys/link/qtsdl_checked_static_cast.h" - -#include "../../game_spec_link.h" -#include "gui_game_spec_image_provider_impl.h" - -#include "gui_game_spec_image_provider_by_filename_impl.h" -#include "gui_game_spec_image_provider_by_graphic_id_impl.h" -#include "gui_game_spec_image_provider_by_terrain_id_impl.h" - -namespace openage::gui { - -namespace { -const int registration_by_filename = qmlRegisterSingletonType("yay.sfttech.openage", 1, 0, "ImageProviderByFilename", &GuiImageProviderLink::provider_by_filename); -const int registration_by_id = qmlRegisterSingletonType("yay.sfttech.openage", 1, 0, "ImageProviderById", &GuiImageProviderLink::provider_by_graphic_id); -const int registration_by_terrain_id = qmlRegisterSingletonType("yay.sfttech.openage", 1, 0, "ImageProviderByTerrainId", &GuiImageProviderLink::provider_by_terrain_id); -} - -GuiImageProviderLink::GuiImageProviderLink(QObject *parent, GuiGameSpecImageProviderImpl &image_provider) - : - QObject{parent}, - image_provider{image_provider}, - game_spec{} { - Q_UNUSED(registration_by_filename); - Q_UNUSED(registration_by_id); - Q_UNUSED(registration_by_terrain_id); -} - -GuiImageProviderLink::~GuiImageProviderLink() = default; - -GameSpecLink* GuiImageProviderLink::get_game_spec() const { - return this->game_spec; -} - -void GuiImageProviderLink::set_game_spec(GameSpecLink *game_spec) { - if (this->game_spec != game_spec) { - if (this->game_spec) - QObject::disconnect(this->game_spec.data(), &GameSpecLink::game_spec_loaded, this, &GuiImageProviderLink::on_game_spec_loaded); - - if (!game_spec) - this->image_provider.on_game_spec_invalidated(); - - this->game_spec = game_spec; - - if (this->game_spec) { - QObject::connect(this->game_spec.data(), &GameSpecLink::game_spec_loaded, this, &GuiImageProviderLink::on_game_spec_loaded); - - if (std::shared_ptr spec = this->game_spec->get_loaded_spec()) - this->image_provider.on_game_spec_loaded(spec); - } - } -} - -QObject* GuiImageProviderLink::provider(QQmlEngine *engine, const char *id) { - qtsdl::QmlEngineWithSingletonItemsInfo *engine_with_singleton_items_info = qtsdl::checked_static_cast(engine); - auto image_providers = engine_with_singleton_items_info->get_image_providers(); - - auto found_it = std::find_if(std::begin(image_providers), std::end(image_providers), [id] (qtsdl::GuiImageProviderImpl *image_provider) { - return image_provider->get_id() == id; - }); - - ENSURE(found_it != std::end(image_providers), "The image provider '" << id << "' wasn't created or wasn't passed to the QML engine creation function."); - - // owned by the QML engine - return new GuiImageProviderLink{nullptr, *qtsdl::checked_static_cast(*found_it)}; -} - -QObject* GuiImageProviderLink::provider_by_filename(QQmlEngine *engine, QJSEngine*) { - return GuiImageProviderLink::provider(engine, GuiGameSpecImageProviderByFilenameImpl::id()); -} - -QObject* GuiImageProviderLink::provider_by_graphic_id(QQmlEngine *engine, QJSEngine*) { - return GuiImageProviderLink::provider(engine, GuiGameSpecImageProviderByGraphicIdImpl::id()); -} - -QObject* GuiImageProviderLink::provider_by_terrain_id(QQmlEngine *engine, QJSEngine*) { - return GuiImageProviderLink::provider(engine, GuiGameSpecImageProviderByTerrainIdImpl::id()); -} - -void GuiImageProviderLink::on_game_spec_loaded(GameSpecLink *game_spec, std::shared_ptr loaded_game_spec) { - if (this->game_spec == game_spec) - this->image_provider.on_game_spec_loaded(loaded_game_spec); -} - -} // namespace openage::gui diff --git a/libopenage/gui/integration/private/gui_image_provider_link.h b/libopenage/gui/integration/private/gui_image_provider_link.h deleted file mode 100644 index d0f4c3e944..0000000000 --- a/libopenage/gui/integration/private/gui_image_provider_link.h +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -#include -#include - -QT_FORWARD_DECLARE_CLASS(QQmlEngine) -QT_FORWARD_DECLARE_CLASS(QJSEngine) - -namespace openage { - -class GameSpec; - -namespace gui { - -class GuiGameSpecImageProviderImpl; -class GameSpecLink; - -class GuiImageProviderLink : public QObject { - Q_OBJECT - - Q_PROPERTY(openage::gui::GameSpecLink* gameSpec READ get_game_spec WRITE set_game_spec) - -public: - explicit GuiImageProviderLink(QObject *parent, GuiGameSpecImageProviderImpl &image_provider); - virtual ~GuiImageProviderLink(); - - GameSpecLink* get_game_spec() const; - - /** - * Sets the game spec to load textures from. - * - * Setting to null doesn't detach from the spec, but picks the - * 'missing texture' from that spec and sets it to every sprite. - */ - void set_game_spec(GameSpecLink *game_spec); - - static QObject* provider(QQmlEngine*, const char *id); - static QObject* provider_by_filename(QQmlEngine*, QJSEngine*); - static QObject* provider_by_graphic_id(QQmlEngine*, QJSEngine*); - static QObject* provider_by_terrain_id(QQmlEngine*, QJSEngine*); - -private slots: - /** - * Pass loaded assets to the image provider. - * - * Need to check if we still attached to that spec. - * Also it may be invalidated in the meantime, so share the ownership. - */ - void on_game_spec_loaded(GameSpecLink *game_spec, std::shared_ptr loaded_game_spec); - -private: - GuiGameSpecImageProviderImpl &image_provider; - QPointer game_spec; -}; - -}} // namespace openage::gui diff --git a/libopenage/gui/integration/private/gui_log.cpp b/libopenage/gui/integration/private/gui_log.cpp deleted file mode 100644 index 481374f9de..0000000000 --- a/libopenage/gui/integration/private/gui_log.cpp +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#include "../../integration/private/gui_log.h" - -#include - -#include "../../../log/log.h" -#include "log/message.h" - - -namespace openage { -namespace gui { - -void gui_log(QtMsgType type, const QMessageLogContext &context, const QString &msg) { - log::level msg_lvl; - - switch (type) { - case QtDebugMsg: - msg_lvl = log::level::dbg; - break; - case QtInfoMsg: - msg_lvl = log::level::info; - break; - case QtWarningMsg: - msg_lvl = log::level::warn; - break; - case QtCriticalMsg: - msg_lvl = log::level::err; - break; - case QtFatalMsg: - msg_lvl = log::level::crit; - break; - default: - msg_lvl = log::level::warn; - break; - } - - log::MessageBuilder builder{ - context.file != nullptr ? context.file : "", - static_cast(context.line), - context.function != nullptr ? context.function : "", - msg_lvl}; - - // TODO: maybe it's not UTF-8 - // TODO: Qt should become a LogSource - log::log(builder << msg.toUtf8().data()); - - if (type == QtFatalMsg) - abort(); -} - -} // namespace gui -} // namespace openage diff --git a/libopenage/gui/integration/private/gui_log.h b/libopenage/gui/integration/private/gui_log.h deleted file mode 100644 index 4966f5e182..0000000000 --- a/libopenage/gui/integration/private/gui_log.h +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -namespace openage { -namespace gui { - -void gui_log(QtMsgType type, const QMessageLogContext &context, const QString &msg); - -}} // namespace openage::gui diff --git a/libopenage/gui/integration/private/gui_make_standalone_subtexture.cpp b/libopenage/gui/integration/private/gui_make_standalone_subtexture.cpp deleted file mode 100644 index 854f5a65bc..0000000000 --- a/libopenage/gui/integration/private/gui_make_standalone_subtexture.cpp +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. - -#include "gui_make_standalone_subtexture.h" -#include "gui_standalone_subtexture.h" - -namespace openage { -namespace gui { - -std::unique_ptr make_standalone_subtexture(GLuint id, const QSize &size) { - return std::make_unique(id, size); -} - -}} // namespace openage::gui diff --git a/libopenage/gui/integration/private/gui_texture.cpp b/libopenage/gui/integration/private/gui_texture.cpp deleted file mode 100644 index 7890981918..0000000000 --- a/libopenage/gui/integration/private/gui_texture.cpp +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#include - -#include - -#include "../../../texture.h" -#include "gui_make_standalone_subtexture.h" -#include "gui_texture.h" - -namespace openage::gui { - -GuiTexture::GuiTexture(const SizedTextureHandle &texture_handle) : - QSGTexture{}, - texture_handle(texture_handle) { -} - -GuiTexture::~GuiTexture() = default; - -void GuiTexture::bind() { - glBindTexture(GL_TEXTURE_2D, this->textureId()); -} - -qint64 GuiTexture::comparisonKey() const { - // TODO: Qt5 What does this do?????? - return 0; -} - -bool GuiTexture::hasAlphaChannel() const { - // assume 32bit textures - return true; -} - -bool GuiTexture::hasMipmaps() const { - return false; -} - -bool GuiTexture::isAtlasTexture() const { - return openage::gui::isAtlasTexture(this->texture_handle); -} - -namespace { -GLuint create_compatible_texture(GLuint texture_id, GLsizei w, GLsizei h) { - glBindTexture(GL_TEXTURE_2D, texture_id); - - GLint min_filter; - GLint mag_filter; - GLint iformat; - - glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, &min_filter); - glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, &mag_filter); - glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &iformat); - - GLuint new_texture_id; - glGenTextures(1, &new_texture_id); - glBindTexture(GL_TEXTURE_2D, new_texture_id); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter); - - glTexImage2D(GL_TEXTURE_2D, 0, iformat, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); - - glBindTexture(GL_TEXTURE_2D, 0); - - return new_texture_id; -} -} // namespace - -QSGTexture *GuiTexture::removedFromAtlas(QRhiResourceUpdateBatch *resourceUpdates /* = nullptr */) const { - if (this->isAtlasTexture()) { - if (!this->standalone) { - auto tex = this->texture_handle.texture; - auto sub = tex->get_subtexture(this->texture_handle.subid); - - GLuint sub_texture_id = create_compatible_texture(tex->get_texture_id(), sub->w, sub->h); - - std::array fbo; - glGenFramebuffers(fbo.size(), &fbo.front()); - - glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo[0]); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, this->textureId(), 0); - glReadBuffer(GL_COLOR_ATTACHMENT0); - - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo[1]); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, sub_texture_id, 0); - glDrawBuffer(GL_COLOR_ATTACHMENT0); - - glBlitFramebuffer(sub->x, sub->y, sub->x + sub->w, sub->y + sub->h, 0, 0, sub->w, sub->h, GL_COLOR_BUFFER_BIT, GL_NEAREST); - - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - glDeleteFramebuffers(fbo.size(), &fbo.front()); - - this->standalone = make_standalone_subtexture(sub_texture_id, QSize(sub->w, sub->h)); - } - - return this->standalone.get(); - } - - return nullptr; -} - -QRectF GuiTexture::normalizedTextureSubRect() const { - if (this->isAtlasTexture()) { - auto tex = this->texture_handle.texture; - auto sub = tex->get_subtexture(this->texture_handle.subid); - return QTransform::fromScale(tex->w, tex->h).inverted().mapRect(QRectF(sub->x, sub->y, sub->w, sub->h)); - } - else { - return QSGTexture::normalizedTextureSubRect(); - } -} - -int GuiTexture::textureId() const { - return this->texture_handle.texture->get_texture_id(); -} - -QSize GuiTexture::textureSize() const { - return openage::gui::textureSize(this->texture_handle); -} - -} // namespace openage::gui diff --git a/libopenage/gui/integration/private/gui_texture_factory.cpp b/libopenage/gui/integration/private/gui_texture_factory.cpp deleted file mode 100644 index 06abcdada3..0000000000 --- a/libopenage/gui/integration/private/gui_texture_factory.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. - -#include "gui_texture_factory.h" - -#include "gui_game_spec_image_provider_impl.h" -#include "../../../texture.h" -#include "gui_texture.h" -#include "gui_filled_texture_handles.h" - -namespace openage::gui { - -GuiTextureFactory::GuiTextureFactory(GuiGameSpecImageProviderImpl *provider, const QString &id, const QSize &requested_size) - : - texture_handle(), - texture_handle_user{provider->fill_texture_handle(id, requested_size, &this->texture_handle)} { -} - -GuiTextureFactory::~GuiTextureFactory() = default; - -QSGTexture* GuiTextureFactory::createTexture(QQuickWindow *window) const { - Q_UNUSED(window); - return new GuiTexture{this->texture_handle}; -} - -int GuiTextureFactory::textureByteCount() const { - // assume 32bit textures - return this->texture_handle.texture->w * this->texture_handle.texture->h * 4; -} - -QSize GuiTextureFactory::textureSize() const { - return openage::gui::textureSize(this->texture_handle); -} - -} // namespace openage::gui diff --git a/libopenage/gui/integration/private/gui_texture_factory.h b/libopenage/gui/integration/private/gui_texture_factory.h deleted file mode 100644 index bf4007c0a3..0000000000 --- a/libopenage/gui/integration/private/gui_texture_factory.h +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -#include - -#include "gui_texture_handle.h" -#include "gui_filled_texture_handles.h" - -namespace openage { -namespace gui { - -class GuiGameSpecImageProviderImpl; - -class GuiTextureFactory : public QQuickTextureFactory { - Q_OBJECT - -public: - explicit GuiTextureFactory(GuiGameSpecImageProviderImpl *provider, const QString &id, const QSize &requested_size); - virtual ~GuiTextureFactory(); - - virtual QSGTexture* createTexture(QQuickWindow *window) const override; - virtual int textureByteCount() const override; - virtual QSize textureSize() const override; - -private: - SizedTextureHandle texture_handle; - GuiFilledTextureHandleUser texture_handle_user; -}; - -}} // namespace openage::gui diff --git a/libopenage/gui/integration/public/CMakeLists.txt b/libopenage/gui/integration/public/CMakeLists.txt deleted file mode 100644 index c7a535bb1f..0000000000 --- a/libopenage/gui/integration/public/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -add_sources(libopenage - gui_application_with_logger.cpp - gui_game_spec_image_provider.cpp -) diff --git a/libopenage/gui/integration/public/gui_application_with_logger.cpp b/libopenage/gui/integration/public/gui_application_with_logger.cpp deleted file mode 100644 index 0b845f42fc..0000000000 --- a/libopenage/gui/integration/public/gui_application_with_logger.cpp +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. - -#include "gui_application_with_logger.h" - -#include "../../guisys/private/gui_application_impl.h" -#include "../private/gui_log.h" - -namespace openage::gui { - -namespace { -std::shared_ptr create() { - qInstallMessageHandler(gui_log); - return qtsdl::GuiApplicationImpl::get(); -} -} - -GuiApplicationWithLogger::GuiApplicationWithLogger() - : - GuiApplication{create()} { -} - -GuiApplicationWithLogger::~GuiApplicationWithLogger() = default; - -} // namespace openage::gui diff --git a/libopenage/gui/integration/public/gui_application_with_logger.h b/libopenage/gui/integration/public/gui_application_with_logger.h deleted file mode 100644 index 69a074a196..0000000000 --- a/libopenage/gui/integration/public/gui_application_with_logger.h +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. - -#pragma once - -#include "../../guisys/public/gui_application.h" - -namespace openage { -namespace gui { - -/** - * Houses gui logic event queue and attaches to game logger. - */ -class GuiApplicationWithLogger : public qtsdl::GuiApplication { -public: - GuiApplicationWithLogger(); - ~GuiApplicationWithLogger(); -}; - -}} // namespace openage::gui diff --git a/libopenage/gui/integration/public/gui_game_spec_image_provider.cpp b/libopenage/gui/integration/public/gui_game_spec_image_provider.cpp deleted file mode 100644 index 0fb622807b..0000000000 --- a/libopenage/gui/integration/public/gui_game_spec_image_provider.cpp +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. - -#include "gui_game_spec_image_provider.h" - -#include "../../../error/error.h" - -#include "../private/gui_game_spec_image_provider_by_filename_impl.h" -#include "../private/gui_game_spec_image_provider_by_graphic_id_impl.h" -#include "../private/gui_game_spec_image_provider_by_terrain_id_impl.h" - -namespace openage::gui { - -GuiGameSpecImageProvider::GuiGameSpecImageProvider(qtsdl::GuiEventQueue *render_updater, Type type) - : - GuiImageProvider{[render_updater, type] () -> std::unique_ptr { - switch(type) { - case Type::ByFilename: - return std::make_unique(render_updater); - - case Type::ByGraphicId: - return std::make_unique(render_updater); - - case Type::ByTerrainId: - return std::make_unique(render_updater); - - default: - break; - } - - ENSURE(false, "unhandled image provider type"); - return std::unique_ptr{}; - }()} { -} - -GuiGameSpecImageProvider::~GuiGameSpecImageProvider() = default; - -} // namespace openage::gui diff --git a/libopenage/gui/integration/public/gui_game_spec_image_provider.h b/libopenage/gui/integration/public/gui_game_spec_image_provider.h deleted file mode 100644 index 2727d90af6..0000000000 --- a/libopenage/gui/integration/public/gui_game_spec_image_provider.h +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. - -#pragma once - -#include "../../guisys/public/gui_image_provider.h" - -namespace qtsdl { -class GuiEventQueue; -} // namespace qtsdl - -namespace openage { -namespace gui { - -class GuiGameSpecImageProvider : public qtsdl::GuiImageProvider { -public: - enum class Type { - ByFilename, - ByGraphicId, - ByTerrainId, - }; - - explicit GuiGameSpecImageProvider(qtsdl::GuiEventQueue *render_updater, Type type); - ~GuiGameSpecImageProvider(); -}; - -}} // namespace openage::gui diff --git a/libopenage/gui/main_args_link.cpp b/libopenage/gui/main_args_link.cpp deleted file mode 100644 index 8d7b11daa4..0000000000 --- a/libopenage/gui/main_args_link.cpp +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. - -#include "main_args_link.h" - -#include - -#include "../error/error.h" - -#include "engine_info.h" -#include "guisys/link/qml_engine_with_singleton_items_info.h" -#include "guisys/link/qtsdl_checked_static_cast.h" - -namespace openage::gui { - -namespace { -// register "MainArgs" in the qml engine to be used globally. -const int registration = qmlRegisterSingletonType("yay.sfttech.openage", 1, 0, "MainArgs", &MainArgsLink::provider); -} - - -MainArgsLink::MainArgsLink(QObject *parent, const util::Path &asset_dir) - : - QObject{parent}, - asset_dir{asset_dir} { - Q_UNUSED(registration); -} - - -QObject* MainArgsLink::provider(QQmlEngine *engine, QJSEngine*) { - auto *engine_with_singleton_items_info = qtsdl::checked_static_cast(engine); - auto info = static_cast(engine_with_singleton_items_info->get_singleton_items_info()); - ENSURE(info, "globals were lost or not passed to the gui subsystem"); - - // owned by the QML engine - return new MainArgsLink{nullptr, info->asset_dir}; -} - -} // namespace openage::gui diff --git a/libopenage/gui/main_args_link.h b/libopenage/gui/main_args_link.h deleted file mode 100644 index a615860d23..0000000000 --- a/libopenage/gui/main_args_link.h +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -#include "../util/path.h" - -QT_FORWARD_DECLARE_CLASS(QQmlEngine) -QT_FORWARD_DECLARE_CLASS(QJSEngine) - -namespace openage { -namespace gui { - -/** - * Used to make arguments of the game available in QML. - */ -class MainArgsLink : public QObject { - Q_OBJECT - - Q_PROPERTY(openage::util::Path assetDir MEMBER asset_dir CONSTANT) - -public: - explicit MainArgsLink(QObject *parent, const util::Path &asset_dir); - virtual ~MainArgsLink() = default; - - /** - * Generates the MainArgsLink object which is then used within QML. - */ - static QObject* provider(QQmlEngine*, QJSEngine*); - -private: - util::Path asset_dir; -}; - -}} // namespace openage::gui diff --git a/libopenage/gui/registrations.cpp b/libopenage/gui/registrations.cpp deleted file mode 100644 index d3325f9416..0000000000 --- a/libopenage/gui/registrations.cpp +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2017-2017 the openage authors. See copying.md for legal info. - -#include "registrations.h" - -#include - -namespace openage { -namespace gui { - -const int reg_path = qRegisterMetaType("util::Path"); - -}} // openage::gui diff --git a/libopenage/gui/registrations.h b/libopenage/gui/registrations.h deleted file mode 100644 index 2bc595ceb9..0000000000 --- a/libopenage/gui/registrations.h +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2017-2017 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -#include "../util/path.h" - -namespace openage { -namespace gui { - - -}} // openage::gui - -Q_DECLARE_METATYPE(openage::util::Path) diff --git a/libopenage/gui/resources_list_model.cpp b/libopenage/gui/resources_list_model.cpp deleted file mode 100644 index ad0106663d..0000000000 --- a/libopenage/gui/resources_list_model.cpp +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2016-2023 the openage authors. See copying.md for legal info. - -#include "resources_list_model.h" - -#include -#include - -#include - -#include "../error/error.h" - -#include "../presenter/legacy/game_control.h" -#include "game_control_link.h" - -namespace openage::gui { - -namespace { -const int registration = qmlRegisterType("yay.sfttech.openage", 1, 0, "Resources"); - -const auto resource_type_count = static_cast::type>(game_resource::RESOURCE_TYPE_COUNT); - -} // namespace - -ResourcesListModel::ResourcesListModel(QObject *parent) : - QAbstractListModel(parent), - amounts(resource_type_count), - action_mode() { - Q_UNUSED(registration); -} - -ResourcesListModel::~ResourcesListModel() = default; - -ActionModeLink *ResourcesListModel::get_action_mode() const { - return this->action_mode; -} - -void ResourcesListModel::set_action_mode(ActionModeLink *action_mode) { - if (this->action_mode != action_mode) { - if (this->action_mode) { - QObject::disconnect(&unwrap(this->action_mode)->gui_signals, &ActionModeSignals::resource_changed, this, &ResourcesListModel::on_resource_changed); - } - - this->action_mode = action_mode; - - if (this->action_mode) { - QObject::connect(&unwrap(this->action_mode)->gui_signals, &ActionModeSignals::resource_changed, this, &ResourcesListModel::on_resource_changed); - } - } -} - -void ResourcesListModel::on_resource_changed(game_resource resource, int amount) { - int i = static_cast(resource); - ENSURE(i >= 0 && i < std::distance(std::begin(this->amounts), std::end(this->amounts)), "Res type index is out of range: '" << i << "'."); - - if (this->amounts[i] != amount) { - auto model_index = this->index(i); - - this->amounts[i] = amount; - emit this->dataChanged(model_index, model_index, QVector{Qt::DisplayRole}); - } -} - -int ResourcesListModel::rowCount(const QModelIndex &) const { - ENSURE(resource_type_count == this->amounts.size(), "Res type count is compile-time const '" << resource_type_count << "', but got '" << this->amounts.size() << "'."); - return this->amounts.size(); -} - -QVariant ResourcesListModel::data(const QModelIndex &index, int role) const { - const int i = index.row(); - - switch (role) { - case Qt::DisplayRole: - ENSURE(i >= 0 && i < std::distance(std::begin(this->amounts), std::end(this->amounts)), "Res type index is out of range: '" << i << "'."); - return this->amounts[index.row()]; - - default: - return QVariant{}; - } -} - -} // namespace openage::gui diff --git a/libopenage/gui/resources_list_model.h b/libopenage/gui/resources_list_model.h deleted file mode 100644 index 574618fb78..0000000000 --- a/libopenage/gui/resources_list_model.h +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2016-2018 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include - -#include - -#include "../gamestate/old/resource.h" - -namespace openage { -namespace gui { - -class ActionModeLink; - -/** - * Resource table for the gui. - */ -class ResourcesListModel : public QAbstractListModel { - Q_OBJECT - - Q_PROPERTY(openage::gui::ActionModeLink* actionMode READ get_action_mode WRITE set_action_mode) - -public: - ResourcesListModel(QObject *parent=nullptr); - virtual ~ResourcesListModel(); - - ActionModeLink* get_action_mode() const; - void set_action_mode(ActionModeLink *action_mode); - -private slots: - void on_resource_changed(game_resource resource, int amount); - -private: - virtual int rowCount(const QModelIndex&) const override; - virtual QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override; - - std::vector amounts; - - ActionModeLink *action_mode; -}; - -}} // namespace openage::gui diff --git a/libopenage/handlers.cpp b/libopenage/handlers.cpp deleted file mode 100644 index 8ced9fecfa..0000000000 --- a/libopenage/handlers.cpp +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright 2014-2015 the openage authors. See copying.md for legal info. - -#include "handlers.h" - -namespace openage { - -} // namespace openage diff --git a/libopenage/handlers.h b/libopenage/handlers.h deleted file mode 100644 index 7df7300d45..0000000000 --- a/libopenage/handlers.h +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2014-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -#include "coord/pixel.h" - - -namespace openage { - -class LegacyEngine; - -/** - * superclass for all possible drawing operations in the game. - */ -class DrawHandler { -public: - virtual ~DrawHandler() = default; - - /** - * execute the drawing action. - */ - virtual bool on_draw() = 0; -}; - -/** - * superclass for all possible drawing operations in the game. - */ -class HudHandler { -public: - virtual ~HudHandler() = default; - - /** - * execute the drawing action. - */ - virtual bool on_drawhud() = 0; -}; - -/** - * superclass for all calculations being done on engine tick. - */ -class TickHandler { -public: - virtual ~TickHandler() = default; - - /** - * execute the tick action. - */ - virtual bool on_tick() = 0; -}; - -/** - * superclass for handling any input event. - */ -class InputHandler { -public: - virtual ~InputHandler() = default; - - /** - * execute the input handler. - */ - virtual bool on_input(SDL_Event *event) = 0; -}; - -/** - * superclass for handling a window resize event. - */ -class ResizeHandler { -public: - virtual ~ResizeHandler() = default; - - /** - * execute the resize handler. - */ - virtual bool on_resize(coord::viewport_delta new_size) = 0; -}; - -} // namespace openage diff --git a/libopenage/input/CMakeLists.txt b/libopenage/input/CMakeLists.txt index d27770d98a..f24e810608 100644 --- a/libopenage/input/CMakeLists.txt +++ b/libopenage/input/CMakeLists.txt @@ -7,5 +7,4 @@ add_sources(libopenage text_to_event.cpp ) -add_subdirectory("legacy") add_subdirectory("controller") diff --git a/libopenage/input/action.h b/libopenage/input/action.h index 4bad86c412..f6ca5296d3 100644 --- a/libopenage/input/action.h +++ b/libopenage/input/action.h @@ -23,8 +23,9 @@ enum class input_action_t { PUSH_CONTEXT, POP_CONTEXT, REMOVE_CONTEXT, - ENGINE, + GAME, CAMERA, + HUD, GUI, CUSTOM, }; diff --git a/libopenage/input/controller/CMakeLists.txt b/libopenage/input/controller/CMakeLists.txt index fd940942a7..eab16635b6 100644 --- a/libopenage/input/controller/CMakeLists.txt +++ b/libopenage/input/controller/CMakeLists.txt @@ -1,2 +1,3 @@ add_subdirectory("camera") add_subdirectory("game") +add_subdirectory("hud") diff --git a/libopenage/input/controller/camera/binding_context.h b/libopenage/input/controller/camera/binding_context.h index a01341e548..0bc1325078 100644 --- a/libopenage/input/controller/camera/binding_context.h +++ b/libopenage/input/controller/camera/binding_context.h @@ -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. #pragma once @@ -25,9 +25,9 @@ class BindingContext { * Bind a specific key combination to a binding. * * This is the first matching priority. - * - * @param ev Input event triggering the action. - * @param bind Binding for the event. + * + * @param ev Input event triggering the action. + * @param bind Binding for the event. */ void bind(const Event &ev, const binding_action bind); @@ -35,26 +35,26 @@ class BindingContext { * Bind an event class to an action. * * This is the second matching priority. - * - * @param ev Input event triggering the action. - * @param bind Binding for the event. + * + * @param ev Input event triggering the action. + * @param bind Binding for the event. */ void bind(const event_class &cl, const binding_action bind); /** - * Check whether a specific key event is bound in this context. - * - * @param ev Input event. - * - * @return true if event is bound, else false. - */ + * Check whether a specific key event is bound in this context. + * + * @param ev Input event. + * + * @return true if event is bound, else false. + */ bool is_bound(const Event &ev) const; /** - * Get the bindings for a specific event. - * - * @param ev Input event mapped to the binding. - */ + * Get the bindings for a specific event. + * + * @param ev Input event mapped to the binding. + */ const binding_action &lookup(const Event &ev) const; private: diff --git a/libopenage/input/controller/camera/controller.h b/libopenage/input/controller/camera/controller.h index bdc64df28c..b668c18823 100644 --- a/libopenage/input/controller/camera/controller.h +++ b/libopenage/input/controller/camera/controller.h @@ -6,6 +6,7 @@ #include "input/event.h" + namespace openage { namespace renderer::camera { @@ -27,12 +28,13 @@ class Controller { ~Controller() = default; /** - * Process an input event from the input manager. - * - * @param ev Input event and arguments. - * - * @return true if the event is accepted, else false. - */ + * Process an input event from the input manager. + * + * @param ev Input event and arguments. + * @param ctx Binding context that maps input events to camera actions. + * + * @return true if the event is accepted, else false. + */ bool process(const event_arguments &ev_args, const std::shared_ptr &ctx); }; diff --git a/libopenage/input/controller/game/binding.cpp b/libopenage/input/controller/game/binding.cpp index abfc8b39b8..17f90f942a 100644 --- a/libopenage/input/controller/game/binding.cpp +++ b/libopenage/input/controller/game/binding.cpp @@ -4,4 +4,4 @@ namespace openage::input::game { -} // namespace openage::input::engine +} // namespace openage::input::game diff --git a/libopenage/input/controller/game/binding.h b/libopenage/input/controller/game/binding.h index abddb2385f..8b94dd8875 100644 --- a/libopenage/input/controller/game/binding.h +++ b/libopenage/input/controller/game/binding.h @@ -3,6 +3,7 @@ #pragma once #include +#include #include #include "event/event.h" @@ -14,7 +15,7 @@ class Controller; using binding_flags_t = std::unordered_map; using binding_func_t = std::function(const event_arguments &, - const Controller &)>; + const std::shared_ptr)>; /** diff --git a/libopenage/input/controller/game/binding_context.cpp b/libopenage/input/controller/game/binding_context.cpp index 98a1a3d60e..9f963f4a11 100644 --- a/libopenage/input/controller/game/binding_context.cpp +++ b/libopenage/input/controller/game/binding_context.cpp @@ -36,4 +36,4 @@ const binding_action &BindingContext::lookup(const Event &ev) const { throw Error{MSG(err) << "Event is not bound in binding_action context."}; } -} // namespace openage::input::engine +} // namespace openage::input::game diff --git a/libopenage/input/controller/game/binding_context.h b/libopenage/input/controller/game/binding_context.h index d63b6e4c8c..318e37661d 100644 --- a/libopenage/input/controller/game/binding_context.h +++ b/libopenage/input/controller/game/binding_context.h @@ -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. #pragma once @@ -25,9 +25,9 @@ class BindingContext { * Bind a specific key combination to a binding. * * This is the first matching priority. - * - * @param ev Input event triggering the action. - * @param bind Binding for the event. + * + * @param ev Input event triggering the action. + * @param bind Binding for the event. */ void bind(const Event &ev, const binding_action bind); @@ -35,26 +35,26 @@ class BindingContext { * Bind an event class to an action. * * This is the second matching priority. - * - * @param ev Input event triggering the action. - * @param bind Binding for the event. + * + * @param ev Input event triggering the action. + * @param bind Binding for the event. */ void bind(const event_class &cl, const binding_action bind); /** - * Check whether a specific key event is bound in this context. - * - * @param ev Input event. - * - * @return true if event is bound, else false. - */ + * Check whether a specific key event is bound in this context. + * + * @param ev Input event. + * + * @return true if event is bound, else false. + */ bool is_bound(const Event &ev) const; /** - * Get the bindings for a specific event. - * - * @param ev Input event mapped to the binding. - */ + * Get the bindings for a specific event. + * + * @param ev Input event mapped to the binding. + */ const binding_action &lookup(const Event &ev) const; private: @@ -69,4 +69,4 @@ class BindingContext { std::unordered_map by_class; }; -} // namespace openage::input::engine +} // namespace openage::input::game diff --git a/libopenage/input/controller/game/controller.cpp b/libopenage/input/controller/game/controller.cpp index d12f7e1135..96ccf53429 100644 --- a/libopenage/input/controller/game/controller.cpp +++ b/libopenage/input/controller/game/controller.cpp @@ -2,6 +2,8 @@ #include "controller.h" +#include "log/log.h" + #include "event/event_loop.h" #include "event/evententity.h" #include "event/state.h" @@ -16,6 +18,8 @@ #include "time/time_loop.h" #include "coord/phys.h" +#include "renderer/camera/camera.h" + namespace openage::input::game { @@ -45,9 +49,11 @@ const std::vector &Controller::get_selected() const { return this->selected; } -void Controller::set_selected(std::vector ids) { +void Controller::set_selected(const std::vector ids) { std::unique_lock lock{this->mutex}; + log::log(DBG << "Selected " << ids.size() << " entities"); + this->selected = ids; } @@ -60,7 +66,8 @@ bool Controller::process(const event_arguments &ev_args, const std::shared_ptrlookup(ev_args.e); - auto game_event = bind.transform(ev_args, *this); + auto controller = this->shared_from_this(); + auto game_event = bind.transform(ev_args, controller); switch (bind.action_type) { case forward_action_t::SEND: @@ -85,21 +92,40 @@ bool Controller::process(const event_arguments &ev_args, const std::shared_ptrmutex}; + + log::log(DBG << "Drag select start at " << start); + this->drag_select_start = start; +} + +const coord::input Controller::get_drag_select_start() const { + std::unique_lock lock{this->mutex}; + + if (not this->drag_select_start.has_value()) { + throw Error{ERR << "Drag select start not set."}; + } + + return this->drag_select_start.value(); +} + +void Controller::reset_drag_select() { + std::unique_lock lock{this->mutex}; + + log::log(DBG << "Drag select start reset"); + this->drag_select_start = std::nullopt; +} + void setup_defaults(const std::shared_ptr &ctx, const std::shared_ptr &time_loop, const std::shared_ptr &simulation, const std::shared_ptr &camera) { binding_func_t create_entity_event{[&](const event_arguments &args, - const Controller &controller) { + const std::shared_ptr controller) { auto mouse_pos = args.mouse.to_phys3(camera); event::EventHandler::param_map::map_t params{ {"position", mouse_pos}, - {"owner", controller.get_controlled()}, - // TODO: Remove - {"select_cb", std::function{[&controller](gamestate::entity_id_t id) { - auto &mut_controller = const_cast(controller); - mut_controller.set_selected({id}); - }}}, + {"owner", controller->get_controlled()}, }; auto event = simulation->get_event_loop()->create_event( @@ -112,17 +138,21 @@ void setup_defaults(const std::shared_ptr &ctx, }}; binding_action create_entity_action{forward_action_t::SEND, create_entity_event}; - Event ev_mouse_lmb{event_class::MOUSE_BUTTON, Qt::MouseButton::LeftButton, Qt::NoModifier, QEvent::MouseButtonRelease}; + Event ev_mouse_lmb_ctrl{ + event_class::MOUSE_BUTTON, + Qt::MouseButton::LeftButton, + Qt::KeyboardModifier::ControlModifier, + QEvent::MouseButtonRelease}; - ctx->bind(ev_mouse_lmb, create_entity_action); + ctx->bind(ev_mouse_lmb_ctrl, create_entity_action); binding_func_t move_entity{[&](const event_arguments &args, - const Controller &controller) { + const std::shared_ptr controller) { auto mouse_pos = args.mouse.to_phys3(camera); event::EventHandler::param_map::map_t params{ {"type", gamestate::component::command::command_t::MOVE}, {"target", mouse_pos}, - {"entity_ids", controller.get_selected()}, + {"entity_ids", controller->get_selected()}, }; auto event = simulation->get_event_loop()->create_event( @@ -135,9 +165,67 @@ void setup_defaults(const std::shared_ptr &ctx, }}; binding_action move_entity_action{forward_action_t::SEND, move_entity}; - Event ev_mouse_rmb{event_class::MOUSE_BUTTON, Qt::MouseButton::RightButton, Qt::NoModifier, QEvent::MouseButtonRelease}; + Event ev_mouse_rmb{ + event_class::MOUSE_BUTTON, + Qt::MouseButton::RightButton, + Qt::KeyboardModifier::NoModifier, + QEvent::MouseButtonRelease}; ctx->bind(ev_mouse_rmb, move_entity_action); + + binding_func_t init_drag_selection{[&](const event_arguments &args, + const std::shared_ptr controller) { + controller->set_drag_select_start(args.mouse); + return nullptr; + }}; + + binding_action init_drag_selection_action{forward_action_t::CLEAR, init_drag_selection}; + Event ev_mouse_lmb_press{ + event_class::MOUSE_BUTTON, + Qt::MouseButton::LeftButton, + Qt::KeyboardModifier::NoModifier, + QEvent::MouseButtonPress}; + + ctx->bind(ev_mouse_lmb_press, init_drag_selection_action); + + binding_func_t drag_selection{ + [&](const event_arguments &args, + const std::shared_ptr controller) { + Eigen::Matrix4f cam_matrix = camera->get_projection_matrix() * camera->get_view_matrix(); + event::EventHandler::param_map::map_t params{ + {"controlled", controller->get_controlled()}, + {"drag_start", controller->get_drag_select_start().to_viewport(camera).to_ndc_space(camera)}, + {"drag_end", args.mouse.to_viewport(camera).to_ndc_space(camera)}, + {"camera_matrix", cam_matrix}, + {"select_cb", + std::function ids)>{ + [controller]( + const std::vector ids) { + controller->set_selected(ids); + }}}, + }; + + auto event = simulation->get_event_loop()->create_event( + "game.drag_select", + simulation->get_commander(), + simulation->get_game()->get_state(), + time_loop->get_clock()->get_time(), + params); + + // Reset drag selection start + controller->reset_drag_select(); + + return event; + }}; + + binding_action drag_selection_action{forward_action_t::CLEAR, drag_selection}; + Event ev_mouse_lmb_release{ + event_class::MOUSE_BUTTON, + Qt::MouseButton::LeftButton, + Qt::KeyboardModifier::NoModifier, + QEvent::MouseButtonRelease}; + + ctx->bind(ev_mouse_lmb_release, drag_selection_action); } diff --git a/libopenage/input/controller/game/controller.h b/libopenage/input/controller/game/controller.h index 2ef4439087..a9d690f622 100644 --- a/libopenage/input/controller/game/controller.h +++ b/libopenage/input/controller/game/controller.h @@ -2,13 +2,17 @@ #pragma once +#include #include +#include #include +#include "coord/pixel.h" #include "curve/discrete.h" #include "gamestate/types.h" #include "input/event.h" + namespace openage { namespace gamestate { @@ -29,10 +33,8 @@ class BindingContext; * Controllers handle inputs from outside of a game (e.g. GUI, AI, scripts, ...) * and pass the resulting events to game entities. They also act as a form of * access control for using in-game functionality of game entities. - * - * TODO: Connection to engine */ -class Controller { +class Controller : public std::enable_shared_from_this { public: Controller(const std::unordered_set &controlled_factions, size_t active_faction_id); @@ -40,75 +42,102 @@ class Controller { ~Controller() = default; /** - * Switch the actively controlled faction by the controller. - * The ID must be in the list of controlled factions. - * - * @param faction_id ID of the new active faction. - */ + * Switch the actively controlled faction by the controller. + * The ID must be in the list of controlled factions. + * + * @param faction_id ID of the new active faction. + */ void set_control(size_t faction_id); /** - * Get the ID of the faction actively controlled by the controller. - * - * @return ID of the active faction. - */ + * Get the ID of the faction actively controlled by the controller. + * + * @return ID of the active faction. + */ size_t get_controlled() const; /** - * Get the currently selected entities. - * - * @return Selected entities. - */ + * Get the currently selected entities. + * + * @return Selected entities. + */ const std::vector &get_selected() const; /** - * Set the currently selected entities. - * - * @param ids Selected entities. - */ - void set_selected(std::vector ids); + * Set the currently selected entities. + * + * @param ids Selected entities. + */ + void set_selected(const std::vector ids); /** - * Process an input event from the input manager. - * - * @param ev_args Input event and arguments. - * @param ctx Binding context for looking up the event transformation. - * - * @return true if the event is accepted, else false. - */ + * Process an input event from the input manager. + * + * @param ev_args Input event and arguments. + * @param ctx Binding context for looking up the event transformation. + * + * @return true if the event is accepted, else false. + */ bool process(const event_arguments &ev_args, const std::shared_ptr &ctx); + /** + * Set the start position of a drag selection. + * + * @param start Start position of the drag selection. + */ + void set_drag_select_start(const coord::input &start); + + /** + * Get the start position of a drag selection. + * + * @return Start position of the drag selection. + */ + const coord::input get_drag_select_start() const; + + /** + * Reset the drag select start position. + */ + void reset_drag_select(); + private: /** - * List of factions controllable by this controller. - */ + * Factions controllable by this controller. + */ std::unordered_set controlled_factions; /** - * ID of the currently active faction. - */ + * ID of the currently active faction. + */ size_t active_faction_id; /** - * Currently selected entities. - */ + * Currently selected entities. + */ std::vector selected; /** - * Queue for gamestate events generated from inputs. - */ + * Queue for gamestate events generated from inputs. + */ std::vector> outqueue; /** - * Mutex for threaded access. - */ + * Start position of a drag selection. + * + * TODO: Move this into an input event. + */ + std::optional drag_select_start; + + /** + * Mutex for threaded access. + */ mutable std::recursive_mutex mutex; }; /** * Setup default controller action bindings: * - * - Mouse click: Create game entity. + * - CTRL + Left Mouse click: Create game entity. + * - Right Mouse click: Move game entity. * * @param ctx Binding context the actions are added to. * @param time_loop Time loop for getting simulation time. diff --git a/libopenage/input/controller/hud/CMakeLists.txt b/libopenage/input/controller/hud/CMakeLists.txt new file mode 100644 index 0000000000..0d348caf31 --- /dev/null +++ b/libopenage/input/controller/hud/CMakeLists.txt @@ -0,0 +1,5 @@ +add_sources(libopenage + binding_context.cpp + binding.cpp + controller.cpp +) diff --git a/libopenage/input/controller/hud/binding.cpp b/libopenage/input/controller/hud/binding.cpp new file mode 100644 index 0000000000..1c886a3d43 --- /dev/null +++ b/libopenage/input/controller/hud/binding.cpp @@ -0,0 +1,7 @@ +// Copyright 2023-2023 the openage authors. See copying.md for legal info. + +#include "binding.h" + +namespace openage::input::hud { + +} // namespace openage::input::hud diff --git a/libopenage/input/controller/hud/binding.h b/libopenage/input/controller/hud/binding.h new file mode 100644 index 0000000000..716a7e4f39 --- /dev/null +++ b/libopenage/input/controller/hud/binding.h @@ -0,0 +1,30 @@ +// Copyright 2023-2023 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + +#include "event/event.h" +#include "input/event.h" + +namespace openage::input::hud { +class Controller; + +using binding_flags_t = std::unordered_map; +using binding_func_t = std::function)>; + + +/** + * Action taken by the controller when receiving an input. + * + * @param action Maps an input event to a camera action. + * @param flags Additional parameters for the transformation. + */ +struct binding_action { + const binding_func_t action; + const binding_flags_t flags = {}; +}; + +} // namespace openage::input::hud diff --git a/libopenage/input/controller/hud/binding_context.cpp b/libopenage/input/controller/hud/binding_context.cpp new file mode 100644 index 0000000000..fb1d7a5e25 --- /dev/null +++ b/libopenage/input/controller/hud/binding_context.cpp @@ -0,0 +1,39 @@ +// Copyright 2023-2023 the openage authors. See copying.md for legal info. + +#include "binding_context.h" + + +namespace openage::input::hud { + +BindingContext::BindingContext() : + by_event{} {} + +void BindingContext::bind(const Event &ev, const binding_action bind) { + this->by_event.emplace(std::make_pair(ev, bind)); +} + +void BindingContext::bind(const event_class &cl, const binding_action bind) { + this->by_class.emplace(std::make_pair(cl, bind)); +} + +bool BindingContext::is_bound(const Event &ev) const { + return this->by_event.contains(ev) || this->by_class.contains(ev.cc.cl); +} + +const binding_action &BindingContext::lookup(const Event &ev) const { + auto event_lookup = this->by_event.find(ev); + if (event_lookup != std::end(this->by_event)) { + return (*event_lookup).second; + } + + for (auto eclass : ev.cc.get_classes()) { + auto class_lookup = this->by_class.find(eclass); + if (class_lookup != std::end(this->by_class)) { + return (*class_lookup).second; + } + } + + throw Error{MSG(err) << "Event is not bound in binding_action context."}; +} + +} // namespace openage::input::hud diff --git a/libopenage/input/controller/hud/binding_context.h b/libopenage/input/controller/hud/binding_context.h new file mode 100644 index 0000000000..c2a269b512 --- /dev/null +++ b/libopenage/input/controller/hud/binding_context.h @@ -0,0 +1,72 @@ +// Copyright 2023-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include "input/controller/hud/binding.h" +#include "input/event.h" + +namespace openage::input::hud { + +/** + * Maps input events to HUD actons. + */ +class BindingContext { +public: + /** + * Create a new binding context. + */ + BindingContext(); + + ~BindingContext() = default; + + /** + * Bind a specific key combination to a binding. + * + * This is the first matching priority. + * + * @param ev Input event triggering the action. + * @param bind Binding for the event. + */ + void bind(const Event &ev, const binding_action bind); + + /** + * Bind an event class to an action. + * + * This is the second matching priority. + * + * @param ev Input event triggering the action. + * @param bind Binding for the event. + */ + void bind(const event_class &cl, const binding_action bind); + + /** + * Check whether a specific key event is bound in this context. + * + * @param ev Input event. + * + * @return true if event is bound, else false. + */ + bool is_bound(const Event &ev) const; + + /** + * Get the bindings for a specific event. + * + * @param ev Input event mapped to the binding. + */ + const binding_action &lookup(const Event &ev) const; + +private: + /** + * Maps specific input events to bindings. + */ + std::unordered_map by_event; + + /** + * Maps event classes to bindings. + */ + std::unordered_map by_class; +}; + +} // namespace openage::input::hud diff --git a/libopenage/input/controller/hud/controller.cpp b/libopenage/input/controller/hud/controller.cpp new file mode 100644 index 0000000000..af89923d1b --- /dev/null +++ b/libopenage/input/controller/hud/controller.cpp @@ -0,0 +1,88 @@ +// Copyright 2023-2024 the openage authors. See copying.md for legal info. + +#include "controller.h" + +#include + +#include "input/controller/hud/binding_context.h" + +#include "renderer/stages/hud/render_entity.h" +#include "renderer/stages/hud/render_stage.h" + + +namespace openage::input::hud { + +Controller::Controller() : + drag_entity{nullptr} {} + +bool Controller::process(const event_arguments &ev_args, + const std::shared_ptr &ctx) { + if (not ctx->is_bound(ev_args.e)) { + return false; + } + + auto bind = ctx->lookup(ev_args.e); + bind.action(ev_args, this->shared_from_this()); + + return true; +} + +void Controller::set_drag_entity(const std::shared_ptr &entity) { + this->drag_entity = entity; +} + +const std::shared_ptr &Controller::get_drag_entity() const { + return this->drag_entity; +} + +void setup_defaults(const std::shared_ptr &ctx, + const std::shared_ptr &hud_renderer) { + binding_func_t drag_selection_init{[&](const event_arguments &args, + const std::shared_ptr controller) { + auto render_entity = std::make_shared(args.mouse); + hud_renderer->add_drag_entity(render_entity); + controller->set_drag_entity(render_entity); + }}; + + binding_action drag_selection_init_action{drag_selection_init}; + Event ev_mouse_lmb_press{ + event_class::MOUSE_BUTTON, + Qt::MouseButton::LeftButton, + Qt::KeyboardModifier::NoModifier, + QEvent::MouseButtonPress}; + + ctx->bind(ev_mouse_lmb_press, drag_selection_init_action); + + binding_func_t drag_selection_move{[&](const event_arguments &args, + const std::shared_ptr controller) { + if (controller->get_drag_entity()) { + controller->get_drag_entity()->update(args.mouse); + } + }}; + + binding_action drag_selection_move_action{drag_selection_move}; + Event ev_mouse_move{ + event_class::MOUSE_MOVE, + Qt::MouseButton::NoButton, + Qt::KeyboardModifier::NoModifier, + QEvent::MouseMove}; + + ctx->bind(ev_mouse_move, drag_selection_move_action); + + binding_func_t drag_selection_end{[&](const event_arguments & /* args */, + const std::shared_ptr controller) { + hud_renderer->remove_drag_entity(); + controller->set_drag_entity(nullptr); + }}; + + binding_action drag_selection_end_action{drag_selection_end}; + Event ev_mouse_lmb_release{ + event_class::MOUSE_BUTTON, + Qt::MouseButton::LeftButton, + Qt::KeyboardModifier::NoModifier, + QEvent::MouseButtonRelease}; + + ctx->bind(ev_mouse_lmb_release, drag_selection_end_action); +} + +} // namespace openage::input::hud diff --git a/libopenage/input/controller/hud/controller.h b/libopenage/input/controller/hud/controller.h new file mode 100644 index 0000000000..44d64ee833 --- /dev/null +++ b/libopenage/input/controller/hud/controller.h @@ -0,0 +1,75 @@ +// Copyright 2023-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include "input/event.h" + + +namespace openage { + +namespace renderer::hud { +class DragRenderEntity; +class HudRenderStage; +} // namespace renderer::hud + +namespace input::hud { +class BindingContext; + +/** + * Control athe HUD with input from the input manager. + */ +class Controller : public std::enable_shared_from_this { +public: + Controller(); + + ~Controller() = default; + + /** + * Process an input event from the input manager. + * + * @param ev Input event and arguments. + * @param ctx Binding context that maps input events to HUD actions. + * + * @return true if the event is accepted, else false. + */ + bool process(const event_arguments &ev_args, + const std::shared_ptr &ctx); + + /** + * Set the render entity for the selection box. + * + * @param entity New render entity. + */ + void set_drag_entity(const std::shared_ptr &entity); + + /** + * Get the render entity for the selection box. + * + * @return Render entity for the selection box. + */ + const std::shared_ptr &get_drag_entity() const; + +private: + /** + * Render entity for the selection box. + */ + std::shared_ptr drag_entity; +}; + +/** + * Setup default HUD action bindings: + * + * - Mouse drag: selection box draw + * + * TODO: Make this configurable. + * + * @param ctx Binding context the actions are added to. + * @param hud_renderer HUD render stage that is used to render the selection box. + */ +void setup_defaults(const std::shared_ptr &ctx, + const std::shared_ptr &hud_renderer); + +} // namespace input::hud +} // namespace openage diff --git a/libopenage/input/event.h b/libopenage/input/event.h index 31e9986b32..0749eebfdd 100644 --- a/libopenage/input/event.h +++ b/libopenage/input/event.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 @@ -30,11 +30,11 @@ enum class event_class { GUI, // keyboard subclasses - ALPHA, // abc - DIGIT, // 123 - PRINT, // remaining printable chars + ALPHA, // abc + DIGIT, // 123 + PRINT, // remaining printable chars NONPRINT, // tab, return, backspace, delete - OTHER, // arrows, home, end + OTHER, // arrows, home, end // mouse subclasses MOUSE_BUTTON, diff --git a/libopenage/input/input_context.cpp b/libopenage/input/input_context.cpp index ef733b0e98..bdedf60f37 100644 --- a/libopenage/input/input_context.cpp +++ b/libopenage/input/input_context.cpp @@ -15,35 +15,53 @@ const std::string &InputContext::get_id() { return this->id; } -void InputContext::set_engine_bindings(const std::shared_ptr &bindings) { - this->engine_bindings = bindings; +void InputContext::set_game_bindings(const std::shared_ptr &bindings) { + this->game_bindings = bindings; } void InputContext::set_camera_bindings(const std::shared_ptr &bindings) { this->camera_bindings = bindings; } -const std::shared_ptr &InputContext::get_engine_bindings() { - return this->engine_bindings; +void InputContext::set_hud_bindings(const std::shared_ptr &bindings) { + this->hud_bindings = bindings; +} + +const std::shared_ptr &InputContext::get_game_bindings() { + return this->game_bindings; } const std::shared_ptr &InputContext::get_camera_bindings() { return this->camera_bindings; } +const std::shared_ptr &InputContext::get_hud_bindings() { + return this->hud_bindings; +} + void InputContext::bind(const Event &ev, const input_action act) { - this->by_event.emplace(std::make_pair(ev, act)); + std::vector actions{act}; + this->by_event.emplace(std::make_pair(ev, actions)); } void InputContext::bind(const event_class &cl, const input_action act) { - this->by_class.emplace(std::make_pair(cl, act)); + std::vector actions{act}; + this->by_class.emplace(std::make_pair(cl, actions)); +} + +void InputContext::bind(const Event &ev, const std::vector &&acts) { + this->by_event.emplace(std::make_pair(ev, std::move(acts))); +} + +void InputContext::bind(const event_class &cl, const std::vector &&acts) { + this->by_class.emplace(std::make_pair(cl, std::move(acts))); } bool InputContext::is_bound(const Event &ev) const { return this->by_event.contains(ev) || this->by_class.contains(ev.cc.cl); } -const input_action &InputContext::lookup(const Event &ev) const { +const std::vector &InputContext::lookup(const Event &ev) const { auto event_lookup = this->by_event.find(ev); if (event_lookup != std::end(this->by_event)) { return (*event_lookup).second; diff --git a/libopenage/input/input_context.h b/libopenage/input/input_context.h index f3d2d41903..5e287d40d1 100644 --- a/libopenage/input/input_context.h +++ b/libopenage/input/input_context.h @@ -1,8 +1,9 @@ -// 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 #include +#include #include "input/action.h" #include "input/event.h" @@ -17,6 +18,10 @@ namespace game { class BindingContext; } +namespace hud { +class BindingContext; +} + /** * An input context contains all keybindings and actions * active in e.g. the HUD only. @@ -36,75 +41,109 @@ class InputContext { virtual ~InputContext() = default; /** - * Get the unique ID of the context. - * - * @return Context ID. - */ + * Get the unique ID of the context. + * + * @return Context ID. + */ const std::string &get_id(); /** - * Set the associated context for binding input events to game events. - * - * @param bindings Binding context for gamestate events. - */ - void set_engine_bindings(const std::shared_ptr &bindings); + * Set the associated context for binding input events to game events. + * + * @param bindings Binding context for gamestate events. + */ + void set_game_bindings(const std::shared_ptr &bindings); /** - * Set the associated context for binding input events to camera actions. - * - * @param bindings Binding context for camera actions. - */ + * Set the associated context for binding input events to camera actions. + * + * @param bindings Binding context for camera actions. + */ void set_camera_bindings(const std::shared_ptr &bindings); /** - * Get the associated context for binding input events to game events. - * - * @return Binding context of the input context. - */ - const std::shared_ptr &get_engine_bindings(); + * Set the associated context for binding input events to HUD actions. + * + * @param bindings Binding context for HUD actions. + */ + void set_hud_bindings(const std::shared_ptr &bindings); /** - * Get the associated context for binding input events to camera actions. - * - * @return Binding context of the input context. - */ + * Get the associated context for binding input events to game events. + * + * @return Binding context of the input context. + */ + const std::shared_ptr &get_game_bindings(); + + /** + * Get the associated context for binding input events to camera actions. + * + * @return Binding context of the input context. + */ const std::shared_ptr &get_camera_bindings(); /** - * Bind a specific key combination to an action. + * Get the associated context for binding input events to HUD actions. + * + * @return Binding context of the input context. + */ + const std::shared_ptr &get_hud_bindings(); + + /** + * Bind a specific key combination to a single action. * * This is the first matching priority. - * - * @param ev Input event triggering the action. - * @param act Function executing the action. + * + * @param ev Input event triggering the action. + * @param act Action executed by the event. */ void bind(const Event &ev, const input_action act); /** - * Bind an event class to an action. + * Bind an event class to a single action. * * This is the second matching priority. - * - * @param ev Input event triggering the action. - * @param act Function executing the action. + * + * @param ev Input event triggering the action. + * @param act Action executed by the event. */ void bind(const event_class &cl, const input_action act); /** - * Check whether a specific key event is bound in this context. - * - * @param ev Input event. - * - * @return true if event is bound, else false. - */ + * Bind a specific key combination to a list of actions. + * + * This is the first matching priority. + * + * @param ev Input event triggering the action. + * @param act Actions executed by the event. + */ + void bind(const Event &ev, const std::vector &&acts); + + /** + * Bind an event class to a list of actions. + * + * This is the second matching priority. + * + * @param ev Input event triggering the action. + * @param act Actions executed by the event. + */ + void bind(const event_class &cl, const std::vector &&acts); + + /** + * Check whether a specific key event is bound in this context. + * + * @param ev Input event. + * + * @return true if event is bound, else false. + */ bool is_bound(const Event &ev) const; /** - * Get the action(s) bound to the given event. - * - * @param ev Input event triggering the action. - */ - const input_action &lookup(const Event &ev) const; + * Get the action(s) bound to the given event. + * + * @param ev Input event triggering the action. + */ + const std::vector &lookup(const Event &ev) const; /** * Get all event->action bindings in this context. @@ -123,29 +162,34 @@ class InputContext { private: /** - * Unique ID of the context. - */ + * Unique ID of the context. + */ std::string id; /** * Maps specific input events to actions. */ - std::unordered_map by_event; + std::unordered_map, event_hash> by_event; /** * Maps event classes to actions. */ - std::unordered_map by_class; + std::unordered_map, event_class_hash> by_class; /** - * Additional context for engine events. - */ - std::shared_ptr engine_bindings; + * Additional context for game simulation events. + */ + std::shared_ptr game_bindings; /** - * Additional context for camera actions. - */ + * Additional context for camera actions. + */ std::shared_ptr camera_bindings; + + /** + * Additional context for HUD actions. + */ + std::shared_ptr hud_bindings; }; } // namespace openage::input diff --git a/libopenage/input/input_manager.cpp b/libopenage/input/input_manager.cpp index 47ff5c1834..4857863eb1 100644 --- a/libopenage/input/input_manager.cpp +++ b/libopenage/input/input_manager.cpp @@ -1,21 +1,24 @@ -// 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 "input_manager.h" #include "input/controller/camera/controller.h" #include "input/controller/game/controller.h" +#include "input/controller/hud/controller.h" #include "input/event.h" #include "input/input_context.h" #include "renderer/gui/guisys/public/gui_input.h" + namespace openage::input { InputManager::InputManager() : global_context{std::make_shared("main")}, active_contexts{}, available_contexts{}, - engine_controller{nullptr}, + game_controller{nullptr}, camera_controller{nullptr}, + hud_controller{nullptr}, gui_input{nullptr} { } @@ -27,8 +30,12 @@ void InputManager::set_camera_controller(const std::shared_ptrcamera_controller = controller; } -void InputManager::set_engine_controller(const std::shared_ptr &controller) { - this->engine_controller = controller; +void InputManager::set_game_controller(const std::shared_ptr &controller) { + this->game_controller = controller; +} + +void InputManager::set_hud_controller(const std::shared_ptr controller) { + this->hud_controller = controller; } const std::shared_ptr &InputManager::get_global_context() { @@ -125,20 +132,23 @@ bool InputManager::process(const QEvent &ev) { input::Event input_ev{ev}; // Check context list on top of the stack (most recent bound first) - for (auto const &ctx : this->active_contexts) { + for (size_t i = this->active_contexts.size(); i > 0; --i) { + auto &ctx = this->active_contexts.at(i - 1); if (ctx->is_bound(input_ev)) { - this->process_action(input_ev, - ctx->lookup(input_ev), - ctx); + auto &actions = ctx->lookup(input_ev); + for (auto const &action : actions) { + this->process_action(input_ev, action, ctx); + } return true; } } // If no local keybinds were bound, check the global keybinds if (this->global_context->is_bound(input_ev)) { - this->process_action(input_ev, - this->global_context->lookup(input_ev), - this->global_context); + auto &actions = this->global_context->lookup(input_ev); + for (auto const &action : actions) { + this->process_action(input_ev, action, this->global_context); + } return true; } @@ -173,14 +183,18 @@ void InputManager::process_action(const input::Event &ev, this->pop_context(ctx_id); break; } - case input_action_t::ENGINE: - this->engine_controller->process(args, ctx->get_engine_bindings()); + case input_action_t::GAME: + this->game_controller->process(args, ctx->get_game_bindings()); break; case input_action_t::CAMERA: this->camera_controller->process(args, ctx->get_camera_bindings()); break; + case input_action_t::HUD: + this->hud_controller->process(args, ctx->get_hud_bindings()); + break; + case input_action_t::GUI: this->gui_input->process(args.e.get_event()); break; @@ -196,6 +210,9 @@ void InputManager::process_action(const input::Event &ev, void setup_defaults(const std::shared_ptr &ctx) { + // hud + input_action hud_action{input_action_t::HUD}; + // camera input_action camera_action{input_action_t::CAMERA}; @@ -212,16 +229,19 @@ void setup_defaults(const std::shared_ptr &ctx) { ctx->bind(ev_down, camera_action); ctx->bind(ev_wheel_up, camera_action); ctx->bind(ev_wheel_down, camera_action); - ctx->bind(event_class::MOUSE_MOVE, camera_action); + ctx->bind(event_class::MOUSE_MOVE, {camera_action, hud_action}); - // engine - input_action engine_action{input_action_t::ENGINE}; + // game + input_action game_action{input_action_t::GAME}; Event ev_mouse_lmb{event_class::MOUSE_BUTTON, Qt::LeftButton, Qt::NoModifier, QEvent::MouseButtonRelease}; Event ev_mouse_rmb{event_class::MOUSE_BUTTON, Qt::RightButton, Qt::NoModifier, QEvent::MouseButtonRelease}; - ctx->bind(ev_mouse_lmb, engine_action); - ctx->bind(ev_mouse_rmb, engine_action); + ctx->bind(ev_mouse_lmb, {game_action, hud_action}); + ctx->bind(ev_mouse_rmb, {game_action, hud_action}); + + // also forward all other mouse button events + ctx->bind(event_class::MOUSE_BUTTON, {game_action, hud_action}); } diff --git a/libopenage/input/input_manager.h b/libopenage/input/input_manager.h index 7b6403eeaa..25b6315341 100644 --- a/libopenage/input/input_manager.h +++ b/libopenage/input/input_manager.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 @@ -24,7 +24,11 @@ class Controller; namespace game { class Controller; -} // namespace engine +} // namespace game + +namespace hud { +class Controller; +} // namespace hud class InputContext; @@ -40,24 +44,31 @@ class InputManager { /** * Set the GUI input handler. - * - * @param gui_input GUI input handler. + * + * @param gui_input GUI input handler. */ void set_gui(const std::shared_ptr &gui_input); /** - * Set the controller for the camera. - * - * @param controller Camera controller. - */ + * Set the controller for the camera. + * + * @param controller Camera controller. + */ void set_camera_controller(const std::shared_ptr &controller); /** - * Set the controller for the engine. - * - * @param controller Engine controller. - */ - void set_engine_controller(const std::shared_ptr &controller); + * Set the controller for the game simulation. + * + * @param controller Game controller. + */ + void set_game_controller(const std::shared_ptr &controller); + + /** + * Set the controller for the HUD. + * + * @param controller HUD controller. + */ + void set_hud_controller(const std::shared_ptr controller); /** * returns the global keybind context. @@ -80,7 +91,7 @@ class InputManager { /** * Push a context on top of the stack, making it the - * current top context. + * current top context. * * if other contexts are registered afterwards, * it wanders down the stack, i.e. loses priority. @@ -89,7 +100,7 @@ class InputManager { /** * Push the context with the specified ID on top of the stack, - * making it the current top context. + * making it the current top context. * * if other contexts are registered afterwards, * it wanders down the stack, i.e. loses priority. @@ -130,23 +141,23 @@ class InputManager { void set_motion(int x, int y); /** - * Process an input event from the Qt window management. - * - * @param ev Qt input event. - * - * @return true if the event is accepted, else false. - */ + * Process an input event from the Qt window management. + * + * @param ev Qt input event. + * + * @return true if the event is accepted, else false. + */ bool process(const QEvent &ev); private: /** - * Process the (default) action for an input event. - * - * @param ev Input event. - * @param action Action bound to the event. - * @param bind_ctx Context the action is bound in. - */ + * Process the (default) action for an input event. + * + * @param ev Input event. + * @param action Action bound to the event. + * @param bind_ctx Context the action is bound in. + */ void process_action(const input::Event &ev, const input_action &action, const std::shared_ptr &ctx); @@ -164,24 +175,29 @@ class InputManager { /** * Map of all available contexts, referencable by an ID. - * - * TODO: Move this to cvar manager? + * + * TODO: Move this to cvar manager? */ std::unordered_map> available_contexts; /** - * Interface to the engine. - */ - std::shared_ptr engine_controller; + * Interface to the game simulation. + */ + std::shared_ptr game_controller; /** - * Interface to the camera. - */ + * Interface to the camera. + */ std::shared_ptr camera_controller; /** - * Interface to the GUI. - */ + * Interface to the HUD. + */ + std::shared_ptr hud_controller; + + /** + * Interface to the GUI. + */ std::shared_ptr gui_input; /** diff --git a/libopenage/input/legacy/CMakeLists.txt b/libopenage/input/legacy/CMakeLists.txt deleted file mode 100644 index 799b91e1ee..0000000000 --- a/libopenage/input/legacy/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -add_sources(libopenage - action.cpp - event.cpp - input_context.cpp - input_manager.cpp - text_to_event.cpp -) - -pxdgen( - input_manager.h -) diff --git a/libopenage/input/legacy/action.cpp b/libopenage/input/legacy/action.cpp deleted file mode 100644 index 83d324f161..0000000000 --- a/libopenage/input/legacy/action.cpp +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#include "action.h" - -#include - -#include "cvar/cvar.h" -#include "input/legacy/input_manager.h" -#include "log/log.h" -#include "log/message.h" -#include "util/repr.h" - - -namespace openage { -namespace input::legacy { - -namespace { - - -// the list of action names that will be added during the constructor of ActionManager. -const std::vector DEFAULT_ACTIONS = { - "START_GAME", - "STOP_GAME", - "TOGGLE_HUD", - "SCREENSHOT", - "TOGGLE_DEBUG_OVERLAY", - "TOGGLE_DEBUG_GRID", - "QUICK_SAVE", - "QUICK_LOAD", - "TOGGLE_MODE", - "TOGGLE_MENU", - "TOGGLE_ITEM", - "TOGGLE_BLENDING", - "TOGGLE_PROFILER", - "TOGGLE_CONSTRUCT_MODE", - "TOGGLE_UNIT_DEBUG", - "TRAIN_OBJECT", - "ENABLE_BUILDING_PLACEMENT", - "DISABLE_SET_ABILITY", - "SET_ABILITY_MOVE", - "SET_ABILITY_GATHER", - "SET_ABILITY_GARRISON", - "TOGGLE_CONSOLE", - "SPAWN_VILLAGER", - "KILL_UNIT", - "BUILD_MENU", - "BUILD_MENU_MIL", - "CANCEL", - "BUILDING_HOUS", - "BUILDING_MILL", - "BUILDING_MINE", - "BUILDING_SMIL", - "BUILDING_DOCK", - "BUILDING_FARM", - "BUILDING_BLAC", - "BUILDING_MRKT", - "BUILDING_CRCH", - "BUILDING_UNIV", - "BUILDING_RTWC", - "BUILDING_WNDR", - "BUILDING_BRKS", - "BUILDING_ARRG", - "BUILDING_STBL", - "BUILDING_SIWS", - "BUILDING_WCTWX", - "BUILDING_WALL", - "BUILDING_WALL2", - "BUILDING_WCTW", - "BUILDING_WCTW4", - "BUILDING_GTCA2", - "BUILDING_CSTL", - "BUILDING_TOWN_CENTER", - "SWITCH_TO_PLAYER_1", - "SWITCH_TO_PLAYER_2", - "SWITCH_TO_PLAYER_3", - "SWITCH_TO_PLAYER_4", - "SWITCH_TO_PLAYER_5", - "SWITCH_TO_PLAYER_6", - "SWITCH_TO_PLAYER_7", - "SWITCH_TO_PLAYER_8", - "UP_ARROW", - "DOWN_ARROW", - "LEFT_ARROW", - "RIGHT_ARROW", - "SELECT", - "DESELECT", - "BEGIN_SELECTION", - "END_SELECTION", - "FORWARD", - "BACK", - "PAINT_TERRAIN", - "BUILDING_5", - "BUILDING_6", - "BUILDING_7", - "BUILDING_8", - "BUILD", - "KEEP_BUILDING", - "INCREASE_SELECTION", - "ORDER_SELECT"}; - -} // anonymous namespace - - -ActionManager::ActionManager(InputManager *input_manager, - const std::shared_ptr &cvar_manager) : - input_manager{input_manager}, - cvar_manager{cvar_manager} { - this->create("UNDEFINED"); - - for (auto &type : DEFAULT_ACTIONS) { - this->create(type); - } -} // anonymous namespace - - -bool ActionManager::create(const std::string &type) { - if (this->actions.find(type) != this->actions.end()) { - // that action is already in the list. fail. - // TODO: throw an exception instead? - // for now, just print a warning log message - log::log(WARN << "can not create action " - << util::repr(type) << ": already exists"); - return false; - } - - action_t action_id = this->next_action_id++; - - this->reverse_map[action_id] = type; - this->actions[type] = action_id; - - // and the corresponding cvar, which modifies the action bindings - // TODO: this has nothing to do with the actionmanager! - // remove the cvarmanager-access here! - this->cvar_manager->create(type, std::make_pair( - // the cvar's getter - [type, this]() { - return this->input_manager->get_bind(type); - }, - // the cvar's setter - [type, this](const std::string &value) { - this->input_manager->set_bind(value.c_str(), type); - })); - - return true; -} - - -action_t ActionManager::get(const std::string &type) { - auto it = this->actions.find(type); - if (it != this->actions.end()) { - return it->second; - } - else { - return this->actions.at("UNDEFINED"); - } -} - - -std::string ActionManager::get_name(const action_t action) { - auto it = this->reverse_map.find(action); - if (it != this->reverse_map.end()) { - return it->second; - } - else { - return "UNDEFINED"; - } -} - - -bool ActionManager::is(const std::string &type, const action_t action) { - return this->get(type) == action; -} - - -} // namespace input::legacy -} // namespace openage diff --git a/libopenage/input/legacy/action.h b/libopenage/input/legacy/action.h deleted file mode 100644 index dadd618ef9..0000000000 --- a/libopenage/input/legacy/action.h +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include -#include - -#include "coord/pixel.h" -#include "input/legacy/event.h" - -namespace openage { - -class LegacyEngine; - -namespace cvar { -class CVarManager; -} // namespace cvar - -namespace input::legacy { - -class InputManager; - -/** - * Used to identify actions. - */ -using action_t = unsigned int; - - -// TODO this whole class seems rather obsolete... -// remove it and instead provide a proper class for action_t? -/** - * The action manager manages all the actions allow creation, access - * information and equality. - */ -class ActionManager { -public: - ActionManager(InputManager *input_manager, - const std::shared_ptr &cvar_manager); - -public: - action_t get(const std::string &type); - std::string get_name(const action_t action); - bool is(const std::string &type, const action_t action); - -private: - bool create(const std::string &type); - - // mapping from action name to numbers - std::unordered_map actions; - // for high-speed reverse lookups (action number -> name) - std::unordered_map reverse_map; - - InputManager *const input_manager; - const std::shared_ptr cvar_manager; - - // the id of the next action that is added via create(). - action_t next_action_id = 0; -}; - - -// TODO: use action_hint_t = std::pair -// for example a building command -// std::make_pair(action_t::BUILD, 123) -using action_id_t = action_t; - - -/** - * Contains information about a triggered event. - */ -struct action_arg_t { - // Triggering event - const Event e; - - // Mouse position - const coord::input mouse; - const coord::input_delta motion; - - // hints for arg receiver - // these get set globally in input manager - std::vector hints; -}; - - -/** - * Performs the effect of an action. - */ -using action_func_t = std::function; - - -/** - * For receivers of sets of events a bool is returned - * to indicate if the event was used. - */ -using action_check_t = std::function; - - -} // namespace input::legacy -} // namespace openage diff --git a/libopenage/input/legacy/event.cpp b/libopenage/input/legacy/event.cpp deleted file mode 100644 index a2c5d1c004..0000000000 --- a/libopenage/input/legacy/event.cpp +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#include "event.h" - -#include -#include - -namespace openage::input::legacy { - -ClassCode::ClassCode(event_class cl, code_t code) : - eclass(cl), - code(code) { -} - - -std::vector ClassCode::get_classes() const { - std::vector result; - - // use event_base to traverse up the class tree - event_class ec = this->eclass; - result.push_back(ec); - while (event_base.count(ec) > 0) { - ec = event_base.at(ec); - result.push_back(ec); - } - return result; -} - - -bool ClassCode::has_class(const event_class &ec) const { - for (auto c : this->get_classes()) { - if (c == ec) { - return true; - } - } - return false; -} - - -bool operator==(ClassCode a, ClassCode b) { - return a.eclass == b.eclass && a.code == b.code; -} - - -Event::Event(event_class cl, code_t code, modset_t mod) : - cc(cl, code), - mod(std::move(mod)) {} - - -Event::Event(event_class cl, std::string text, modset_t mod) : - cc(cl, 0), - mod(std::move(mod)), - utf8(std::move(text)) {} - - -char Event::as_char() const { - return (cc.code & 0xff); -} - - -std::string Event::as_utf8() const { - return utf8; -} - - -std::string Event::info() const { - std::string result; - result += "[Event: "; - result += std::to_string(static_cast(this->cc.eclass)); - result += ", "; - result += std::to_string(this->cc.code); - result += ", "; - result += this->as_char(); - result += " ("; - result += std::to_string(this->mod.size()); - result += ")]"; - return result; -} - - -bool Event::operator==(const Event &other) const { - return this->cc == other.cc && this->mod == other.mod && this->utf8 == other.utf8; -} - - -int event_hash::operator()(const Event &e) const { - return class_code_hash()(e.cc); // ^ std::hash()(e.mod) * 3664657; -} - - -int event_class_hash::operator()(const event_class &c) const { - return std::hash()(static_cast(c)); -} - - -int modifier_hash::operator()(const modifier &m) const { - return std::hash()(static_cast(m)); -} - - -int class_code_hash::operator()(const ClassCode &e) const { - return event_class_hash()(e.eclass) ^ std::hash()(e.code) * 3664657; -} - - -modset_t sdl_mod(SDL_Keymod mod) { - // Remove modifiers like num lock and caps lock - // mod = static_cast(mod & this->used_keymods); - modset_t result; - if (mod & KMOD_CTRL) { - result.emplace(modifier::CTRL); - } - if (mod & KMOD_SHIFT) { - result.emplace(modifier::SHIFT); - } - if (mod & KMOD_ALT) { - result.emplace(modifier::ALT); - } - return result; -} - - -Event sdl_key(SDL_Keycode code, SDL_Keymod mod) { - // sdl values for non printable keys - if (code & (1 << 30)) { - return Event(event_class::OTHER, code, sdl_mod(mod)); - } - else { - event_class ec; - char c = (code & 0xff); - if (isdigit(c)) { - ec = event_class::DIGIT; - } - else if (isalpha(c)) { - ec = event_class::ALPHA; - } - else if (isprint(c)) { - ec = event_class::PRINT; - } - else { - ec = event_class::NONPRINT; - } - return Event(ec, code, sdl_mod(mod)); - } -} - -Event utf8(const std::string &text) { - return Event(event_class::UTF8, text, modset_t()); -} - -Event sdl_mouse(int button, SDL_Keymod mod) { - return Event(event_class::MOUSE_BUTTON, button, sdl_mod(mod)); -} - -Event sdl_mouse_up_down(int button, bool up, SDL_Keymod mod) { - return Event(up ? event_class::MOUSE_BUTTON_UP : event_class::MOUSE_BUTTON_DOWN, button, sdl_mod(mod)); -} - -Event sdl_wheel(int direction, SDL_Keymod mod) { - return Event(event_class::MOUSE_WHEEL, direction, sdl_mod(mod)); -} - - -} // namespace openage::input::legacy diff --git a/libopenage/input/legacy/event.h b/libopenage/input/legacy/event.h deleted file mode 100644 index 82d8e9a62e..0000000000 --- a/libopenage/input/legacy/event.h +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include -#include -#include - -#include - -namespace openage { -namespace input::legacy { - -/** - * highest level classes of input - */ -enum class event_class { - ANY, - KEYBOARD, - CHAR, // basic keycodes (lower-case, non-modified) - ALPHA, // abc - DIGIT, // 123 - PRINT, // remaining printable chars - NONPRINT, // tab, return, backspace, delete - OTHER, // arrows, home, end - UTF8, // events with utf8 encoded data - MOUSE, - MOUSE_BUTTON, - MOUSE_BUTTON_UP, - MOUSE_BUTTON_DOWN, - MOUSE_WHEEL, - MOUSE_MOTION -}; - - -struct event_class_hash { - int operator()(const event_class &s) const; -}; - - -/** - * each event type mapped to parent type - */ -static std::unordered_map event_base{ - {event_class::KEYBOARD, event_class::ANY}, - {event_class::CHAR, event_class::KEYBOARD}, - {event_class::ALPHA, event_class::CHAR}, - {event_class::DIGIT, event_class::CHAR}, - {event_class::PRINT, event_class::CHAR}, - {event_class::NONPRINT, event_class::KEYBOARD}, - {event_class::OTHER, event_class::KEYBOARD}, - {event_class::UTF8, event_class::KEYBOARD}, - {event_class::MOUSE, event_class::ANY}, - {event_class::MOUSE_BUTTON, event_class::MOUSE}, - {event_class::MOUSE_WHEEL, event_class::MOUSE}, - {event_class::MOUSE_MOTION, event_class::MOUSE}, -}; - -/** - * mods set on an event - */ -enum class modifier { - CTRL, - ALT, - SHIFT -}; - - -struct modifier_hash { - int operator()(const modifier &s) const; -}; - - -/** - * types used by events - */ -using code_t = int; -using modset_t = std::unordered_set; - - -/** - * base event type containing event handler and event code - */ -class ClassCode { -public: - ClassCode(event_class cl, code_t code); - - /** - * classes ordered with most specific first - */ - std::vector get_classes() const; - bool has_class(const event_class &c) const; - - const event_class eclass; - const code_t code; -}; - - -bool operator==(ClassCode a, ClassCode b); - - -struct class_code_hash { - int operator()(const ClassCode &k) const; -}; - - -/** - * Input event, as triggered by some input device like - * mouse, kezb, joystick, tablet, microwave or dildo. - * Some modifier keys may also be pressed during the event. - */ -class Event { -public: - Event(event_class cl, code_t code, modset_t mod); - Event(event_class cl, std::string, modset_t mod); - - /** - * Return keyboard text as char - * returns 0 for non-text events - */ - char as_char() const; - - /** - * Returns a utf encoded char - * or an empty string for non-utf8 events - */ - std::string as_utf8() const; - - /** - * logable debug info - */ - std::string info() const; - - bool operator==(const Event &other) const; - - const ClassCode cc; - const modset_t mod; - const std::string utf8; -}; - - -struct event_hash { - int operator()(const Event &e) const; -}; - - -using event_set_t = std::unordered_set; - - -// SDL mapping functions - -modset_t sdl_mod(SDL_Keymod mod); -Event sdl_key(SDL_Keycode code, SDL_Keymod mod = KMOD_NONE); -Event utf8(const std::string &text); -Event sdl_mouse(int button, SDL_Keymod mod = KMOD_NONE); -Event sdl_mouse_up_down(int button, bool up, SDL_Keymod mod = KMOD_NONE); -Event sdl_wheel(int direction, SDL_Keymod mod = KMOD_NONE); - - -} // namespace input::legacy -} // namespace openage diff --git a/libopenage/input/legacy/input_context.cpp b/libopenage/input/legacy/input_context.cpp deleted file mode 100644 index b77ae592fe..0000000000 --- a/libopenage/input/legacy/input_context.cpp +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#include "input_context.h" - -#include "input/legacy/input_manager.h" - - -namespace openage { -namespace input::legacy { - - -InputContext::InputContext() : - InputContext{nullptr} {} - - -InputContext::InputContext(InputManager *manager) : - utf8_mode{false} { - this->register_to(manager); -} - - -std::vector InputContext::active_binds() const { - if (this->input_manager == nullptr) { - return {}; - } - - // TODO: try to purge this backpointer to the input manager. - return this->input_manager->active_binds(this->by_type); -} - -void InputContext::bind(action_t type, const action_func_t act) { - this->by_type.emplace(std::make_pair(type, act)); -} - -void InputContext::bind(const Event &ev, const action_func_t act) { - this->by_event.emplace(std::make_pair(ev, act)); -} - -void InputContext::bind(event_class ec, const action_check_t act) { - this->by_class.emplace(std::make_pair(ec, act)); -} - -bool InputContext::execute_if_bound(const action_arg_t &arg) { - // arg type hints are highest priority - for (auto &h : arg.hints) { - auto action = this->by_type.find(h); - if (action != this->by_type.end()) { - action->second(arg); - return true; - } - } - - // specific event mappings - auto action = this->by_event.find(arg.e); - if (action != this->by_event.end()) { - action->second(arg); - return true; - } - - // check all possible class mappings - for (auto &c : arg.e.cc.get_classes()) { - auto action = this->by_class.find(c); - if (action != this->by_class.end() && action->second(arg)) { - return true; - } - } - - return false; -} - - -void InputContext::register_to(InputManager *manager) { - this->input_manager = manager; -} - - -void InputContext::unregister() { - this->input_manager = nullptr; -} - - -} // namespace input::legacy -} // namespace openage diff --git a/libopenage/input/legacy/input_context.h b/libopenage/input/legacy/input_context.h deleted file mode 100644 index ccd0f32cd1..0000000000 --- a/libopenage/input/legacy/input_context.h +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include -#include -#include - -#include "input/legacy/action.h" -#include "input/legacy/event.h" - -namespace openage { -namespace input::legacy { - -class InputManager; - - -/** - * An input context contains all keybindings and actions - * active in e.g. the HUD only. - * For the console, there's a different input context. - * That way, each context can have the same keys - * assigned to different actions, the active context - * decides, which one to trigger. - */ -class InputContext { -public: - /** - * Create an unbound input context. - */ - InputContext(); - - /** - * Create a bound context, assigned to its manager. - */ - InputContext(InputManager *manager); - - virtual ~InputContext() = default; - - /** - * a list of all keys of this context - * which are bound currently in the active context. - * - * TODO: move this method to the input manager. - * as InputManager::active_binds(const InputContext &) const; - */ - std::vector active_binds() const; - - /** - * bind a specific action idetifier - * this is the highest matching priority - */ - void bind(action_t type, const action_func_t act); - - /** - * bind a specific event - * this is the second matching priority - */ - void bind(const Event &ev, const action_func_t act); - - /** - * bind all events of a specific class - * this is the lowest matching priority - */ - void bind(event_class ec, const action_check_t act); - - /** - * lookup an action. If it is bound, execute it. - * @return true when the action is executed, false else. - */ - bool execute_if_bound(const action_arg_t &e); - - /** - * Called by the InputManager where this context - * shall be registered to. - */ - void register_to(InputManager *manager); - - /** - * Remove the registration to an input manager. - */ - void unregister(); - - - /** - * Affects which keyboard events are received: - * true to accpet utf8 text events, - * false to receive regular char events - */ - bool utf8_mode; - -private: - /** - * Input manager this context is bound to. - */ - InputManager *input_manager; - - /** - * Maps an action id to a event execution function. - */ - std::unordered_map by_type; - - /** - * map specific overriding events - */ - std::unordered_map by_event; - - /** - * event to action map - * event_class as key, to ensure all events can be mapped - */ - std::unordered_map by_class; -}; - -} // namespace input::legacy -} // namespace openage diff --git a/libopenage/input/legacy/input_manager.cpp b/libopenage/input/legacy/input_manager.cpp deleted file mode 100644 index f8ce7e2999..0000000000 --- a/libopenage/input/legacy/input_manager.cpp +++ /dev/null @@ -1,406 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#include -#include - -#include "input/legacy/action.h" -#include "input/legacy/input_manager.h" -#include "input/legacy/text_to_event.h" -#include "log/log.h" - - -namespace openage::input::legacy { - -InputManager::InputManager(ActionManager *action_manager) : - action_manager{action_manager}, - global_context{this}, - relative_mode{false} { - this->global_context.register_to(this); -} - - -namespace { - -std::string mod_set_string(modset_t mod) { - if (not mod.empty()) { - for (auto &it : mod) { - switch (it) { - case modifier::ALT: - return "ALT + "; - case modifier::CTRL: - return "CTRL + "; - case modifier::SHIFT: - return "SHIFT + "; - default: - break; - } - } - } - return ""; -} - -std::string event_as_string(const Event &event) { - if (not event.as_utf8().empty()) { - return mod_set_string(event.mod) + event.as_utf8(); - } - else { - if (event.cc.eclass == event_class::MOUSE_WHEEL) { - if (event.cc.code == -1) { - return mod_set_string(event.mod) + "Wheel down"; - } - else { - return mod_set_string(event.mod) + "Wheel up"; - } - } - return mod_set_string(event.mod) + SDL_GetKeyName(event.cc.code); - } -} - -} // namespace - - -std::string InputManager::get_bind(const std::string &action_str) { - action_t action = this->action_manager->get(action_str); - if (this->action_manager->is("UNDEFINED", action)) { - return ""; - } - - auto it = this->keys.find(action); - if (it == this->keys.end()) { - return " "; - } - - switch (it->second.cc.eclass) { - case event_class::MOUSE_BUTTON: - return this->mouse_bind_to_string(it->second); - case event_class::MOUSE_WHEEL: - return this->wheel_bind_to_string(it->second); - default: - return this->key_bind_to_string(it->second); - } -} - -bool InputManager::set_bind(const std::string &bind_str, const std::string &action_str) { - try { - action_t action = this->action_manager->get(action_str); - if (this->action_manager->is("UNDEFINED", action)) { - return false; - } - - Event ev = text_to_event(bind_str); - - auto it = this->keys.find(action); - if (it != this->keys.end()) { - this->keys.erase(it); - } - this->keys.emplace(std::make_pair(action, ev)); - - return true; - } - catch (int error) { - return false; - } -} - -std::string InputManager::key_bind_to_string(const Event &ev) { - std::string key_str = std::string(SDL_GetKeyName(ev.cc.code)); - - auto end = ev.mod.end(); - if (ev.mod.find(modifier::ALT) != end) { - key_str = "Alt " + key_str; - } - if (ev.mod.find(modifier::SHIFT) != end) { - key_str = "Shift " + key_str; - } - if (ev.mod.find(modifier::CTRL) != end) { - key_str = "Ctrl " + key_str; - } - return key_str; -} - -std::string InputManager::mouse_bind_to_string(const Event &ev) { - return "MOUSE " + std::to_string(ev.cc.code); -} - -std::string InputManager::wheel_bind_to_string(const Event &ev) { - std::string base = "WHEEL "; - switch (ev.cc.code) { - case 1: - return base + "UP"; - case -1: - return base + "DOWN"; - default: - return ""; - } -} - -InputContext &InputManager::get_global_context() { - return this->global_context; -} - -InputContext &InputManager::get_top_context() { - // return the global input context - // if no override is pushed - if (this->contexts.empty()) { - return this->global_context; - } - return *this->contexts.back(); -} - -void InputManager::push_context(InputContext *context) { - // push the context to the top - this->contexts.push_back(context); - - context->register_to(this); -} - - -void InputManager::remove_context(InputContext *context) { - if (this->contexts.empty()) { - return; - } - - for (auto it = this->contexts.begin(); it != this->contexts.end(); ++it) { - if ((*it) == context) { - this->contexts.erase(it); - context->unregister(); - return; - } - } -} - - -bool InputManager::ignored(const Event &e) { - // filter duplicate utf8 events - // these are ignored unless the top mode enables - // utf8 mode, in which case regular char codes are ignored - return ((e.cc.has_class(event_class::CHAR) || e.cc.has_class(event_class::UTF8)) && this->get_top_context().utf8_mode != e.cc.has_class(event_class::UTF8)); -} - - -bool InputManager::trigger(const Event &e) { - if (this->ignored(e)) { - return false; - } - - // arg passed to receivers - action_arg_t arg{e, this->mouse_position, this->mouse_motion, {}}; - - for (auto &it : this->keys) { - if (e == it.second) { - arg.hints.emplace_back(it.first); - } - } - - // Check context list on top of the stack (most recent bound first) - for (auto it = this->contexts.rbegin(); it != this->contexts.rend(); ++it) { - if ((*it)->execute_if_bound(arg)) { - return true; - } - } - - // If no local keybinds were bound, check the global keybinds - return this->global_context.execute_if_bound(arg); -} - - -void InputManager::set_state(const Event &ev, bool is_down) { - if (this->ignored(ev)) { - return; - } - - // update key states - this->keymod = ev.mod; - bool was_down = this->states[ev.cc]; - this->states[ev.cc] = is_down; - - // a key going from pressed to unpressed - // will automatically trigger event handling - if (was_down && !is_down) { - this->trigger(ev); - } -} - - -void InputManager::set_mouse(int x, int y) { - auto last_position = this->mouse_position; - this->mouse_position = coord::input{coord::pixel_t{x}, coord::pixel_t{y}}; - this->mouse_motion = this->mouse_position - last_position; -} - - -void InputManager::set_motion(int x, int y) { - this->mouse_motion.x = x; - this->mouse_motion.y = y; -} - - -void InputManager::set_relative(bool mode) { - if (this->relative_mode == mode) { - return; - } - - // change mode - this->relative_mode = mode; - if (this->relative_mode) { - SDL_SetRelativeMouseMode(SDL_TRUE); - } - else { - SDL_SetRelativeMouseMode(SDL_FALSE); - } -} - -bool InputManager::is_mouse_at_edge(Edge edge, int window_size) { - int x, y; - SDL_GetMouseState(&x, &y); - - // Border width to consider screen edges for scrolling. - // AoE II appears to be approx 10px. - // TODO: make configurable through cvar. - const int edge_offset = 10; - - if (edge == Edge::LEFT && x <= edge_offset) { - return true; - } - if (edge == Edge::RIGHT && x >= window_size - edge_offset - 1) { - return true; - } - if (edge == Edge::UP && y <= edge_offset) { - return true; - } - if (edge == Edge::DOWN && y >= window_size - edge_offset - 1) { - return true; - } - - return false; -} - -bool InputManager::is_down(const ClassCode &cc) const { - auto it = this->states.find(cc); - if (it != this->states.end()) { - return it->second; - } - return false; -} - - -bool InputManager::is_down(event_class ec, code_t code) const { - return is_down(ClassCode(ec, code)); -} - - -bool InputManager::is_down(SDL_Keycode k) const { - return is_down(sdl_key(k).cc); -} - - -bool InputManager::is_mod_down(modifier mod) const { - return (this->keymod.count(mod) > 0); -} - - -modset_t InputManager::get_mod() const { - SDL_Keymod mod = SDL_GetModState(); - return sdl_mod(mod); -} - - -bool InputManager::on_input(SDL_Event *e) { - // top level input handler - switch (e->type) { - case SDL_KEYUP: { - SDL_Keycode code = reinterpret_cast(e)->keysym.sym; - Event ev = sdl_key(code, SDL_GetModState()); - this->set_state(ev, false); - break; - } // case SDL_KEYUP - - case SDL_KEYDOWN: { - SDL_Keycode code = reinterpret_cast(e)->keysym.sym; - this->set_state(sdl_key(code, SDL_GetModState()), true); - break; - } // case SDL_KEYDOWN - - case SDL_TEXTINPUT: { - this->trigger(utf8(e->text.text)); - break; - } // case SDL_TEXTINPUT - - case SDL_MOUSEBUTTONUP: { - this->set_relative(false); - this->trigger(sdl_mouse_up_down(e->button.button, true, SDL_GetModState())); - Event ev = sdl_mouse(e->button.button, SDL_GetModState()); - this->set_state(ev, false); - break; - } // case SDL_MOUSEBUTTONUP - - case SDL_MOUSEBUTTONDOWN: { - // TODO: set which buttons - if (e->button.button == 2) { - this->set_relative(true); - } - this->trigger(sdl_mouse_up_down(e->button.button, false, SDL_GetModState())); - Event ev = sdl_mouse(e->button.button, SDL_GetModState()); - this->set_state(ev, true); - break; - } // case SDL_MOUSEBUTTONDOWN - - case SDL_MOUSEMOTION: { - if (this->relative_mode) { - this->set_motion(e->motion.xrel, e->motion.yrel); - } - else { - this->set_mouse(e->button.x, e->button.y); - } - - // must occur after setting mouse position - Event ev(event_class::MOUSE_MOTION, 0, this->get_mod()); - this->trigger(ev); - break; - } // case SDL_MOUSEMOTION - - case SDL_MOUSEWHEEL: { - Event ev = sdl_wheel(e->wheel.y, SDL_GetModState()); - this->trigger(ev); - break; - } // case SDL_MOUSEWHEEL - - } // switch (e->type) - - return true; -} - - -std::vector InputManager::active_binds(const std::unordered_map &ctx_actions) const { - std::vector result; - - // TODO: this only checks the by_type mappings, the others are missing! - for (auto &action : ctx_actions) { - std::string keyboard_key; - - for (auto &key : this->keys) { - if (key.first == action.first) { - keyboard_key = event_as_string(key.second); - break; - } - } - - // this is only possible if the action is registered, - // then this->input_manager != nullptr. - // TODO: try to purge the action manager access here. - // TODO: get_name takes O(n) time - std::string action_type_str = this->get_action_manager()->get_name(action.first); - - result.push_back(keyboard_key + " : " + action_type_str); - } - - return result; -} - - -ActionManager *InputManager::get_action_manager() const { - return this->action_manager; -} - - -} // namespace openage::input::legacy diff --git a/libopenage/input/legacy/input_manager.h b/libopenage/input/legacy/input_manager.h deleted file mode 100644 index 92a0044524..0000000000 --- a/libopenage/input/legacy/input_manager.h +++ /dev/null @@ -1,256 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#pragma once - -// pxd: from libcpp cimport bool -#include -// pxd: from libcpp.string cimport string -#include -#include - -#include "handlers.h" -#include "input/legacy/action.h" -#include "input/legacy/event.h" -#include "input/legacy/input_context.h" - - -namespace openage { - - -/** - * The openage input layer. - * It gets all the events and processes them accordingly. - */ -namespace input::legacy { - -/** - * maps actions to events. - */ -using binding_map_t = std::unordered_map; - - -/** - * The input manager manages all input layers (hud, game, ...) - * and triggers the registered actions depending on the active layer. - * - * pxd: - * - * cppclass InputManager: - * bool set_bind(char* bind_char, string action) except + - * string get_bind(string action) except + - */ -class InputManager : public InputHandler { -public: - /** - * Screen edges used for edge scrolling. - */ - enum class Edge { - LEFT, - RIGHT, - UP, - DOWN - }; - - InputManager(ActionManager *action_manager); - - /** - * Return the string representation of the bind assignated to an action. - */ - std::string get_bind(const std::string &action); - - /** - * Set the given action to be triggered by the given bind (key/mouse - * /wheel). Remove previous assignation. Do nothing if either they - * given bind or action is invalid/unknow. - */ - bool set_bind(const std::string &bind_str, const std::string &action); - - /** - * Return the string representation of the key event. - */ - std::string key_bind_to_string(const Event &ev); - - /** - * Return the string representation of the mouse event. - */ - std::string mouse_bind_to_string(const Event &ev); - - /** - * Return the key representation of the event. - */ - std::string wheel_bind_to_string(const Event &ev); - - /** - * returns the global keybind context. - * actions bound here will be retained even when override_context is called. - */ - InputContext &get_global_context(); - - /** - * Returns the context on top. - * Note there is always a top context - * since the global context will be - * considered on top when none are registered - */ - InputContext &get_top_context(); - - /** - * register a hotkey context by pushing it onto the stack. - * - * this adds the given pointer to the `contexts` list. - * that way the context lays on "top". - * - * if other contexts are registered afterwards, - * it wanders down the stack, i.e. looses priority. - */ - void push_context(InputContext *context); - - /** - * removes any matching registered context from the stack. - * - * the removal is done by finding the given pointer - * in the `contexts` lists, then deleting it in there. - */ - void remove_context(InputContext *context); - - /** - * true if the given event type is being ignored - */ - bool ignored(const Event &e); - - /** - * manages the pressing of an input event (key, mouse, ...). - * first checks whether an action is bound to it. - * if it is, look for an handler to execute that handler. - * returns true if the event was responded to - */ - bool trigger(const Event &e); - - /** - * sets the state of a specific key - */ - void set_state(const Event &ev, bool is_down); - - /** - * updates mouse position state and motion - */ - void set_mouse(int x, int y); - - /** - * updates mouse motion only - */ - void set_motion(int x, int y); - - /** - * enable relative mouse mode - */ - void set_relative(bool mode); - - /** - * Query whether cursor is at edgo of screen - * - * edge variable is enum Edges - * - * @return true when the mouse is at the queried screen edge, false else. - */ - bool is_mouse_at_edge(Edge edge, int window_size); - - /** - * Query stored pressing stat for a key. - * - * note that the function stores a unknown/new keycode - * as 'not pressed' if requested - * @return true when the key is pressed, false else. - */ - bool is_down(const ClassCode &cc) const; - bool is_down(event_class ec, code_t code) const; - - /** - * Most cases should use above is_down(class, code) - * instead to avoid relying on sdl types - * - * Query stored pressing stat for a key. - * @return true when the key is pressed, false else. - */ - bool is_down(SDL_Keycode k) const; - - /** - * Checks whether a key modifier is held down. - */ - bool is_mod_down(modifier mod) const; - - /** - * When a SDL event happens, this is called. - */ - bool on_input(SDL_Event *e) override; - - /** - * Return a string representation of active key bindings - * from the given context. - */ - std::vector active_binds(const std::unordered_map &ctx_actions) const; - - /** - * Get the action manager attached to this input manager. - */ - ActionManager *get_action_manager() const; - -private: - modset_t get_mod() const; - - /** - * The action manager to used for keybind action lookups. - */ - ActionManager *action_manager; - - /** - * The global context. Used as fallback. - */ - InputContext global_context; - - /** - * Maps actions to events. - */ - binding_map_t keys; - - /** - * Stack of active input contexts. - * The most recent entry is pushed on top of the stack. - */ - std::vector contexts; - - /** - * key to is_down map. - * stores a mapping between keycodes and its pressing state. - * a true value means the key is currently pressed, - * false indicates the key is untouched. - */ - std::unordered_map states; - - /** - * Current key modifiers. - * Included ALL modifiers including num lock and caps lock. - */ - modset_t keymod; - - /** - * mode where mouse position is ignored - * used for map scrolling - */ - bool relative_mode; - - /** - * mouse position in the window - */ - coord::input mouse_position{0, 0}; - - /** - * mouse position relative to the last frame position. - */ - coord::input_delta mouse_motion{0, 0}; - - friend InputContext; -}; - -} // namespace input::legacy -} // namespace openage diff --git a/libopenage/input/legacy/text_to_event.cpp b/libopenage/input/legacy/text_to_event.cpp deleted file mode 100644 index c0d410456b..0000000000 --- a/libopenage/input/legacy/text_to_event.cpp +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright 2016-2023 the openage authors. See copying.md for legal info. - -#include "text_to_event.h" - -#include -#include -#include - -#include "error/error.h" -#include "log/log.h" -#include "testing/testing.h" - - -namespace openage { -namespace input::legacy { - -namespace { -const std::vector modifiers{ - KMOD_LCTRL, KMOD_LSHIFT, KMOD_RCTRL, KMOD_RSHIFT, KMOD_LALT, KMOD_RALT, KMOD_LGUI, KMOD_RGUI, KMOD_CTRL, KMOD_SHIFT, KMOD_ALT, KMOD_GUI, KMOD_MODE, KMOD_CAPS, KMOD_NUM}; - -const std::regex event_pattern{ - "(?:(?:(LCtrl)|(LShift)|(RCtrl)|(RShift)|(LAlt)|(RAlt)|(LGui)|(RGUI)|(Ctrl)|(Shift)|(Alt)|(Gui)|(AltGr)|(Caps)|(NumLck))[[:space:]]+)?" // modifier (optional) - "([^[:space:]]+)" // key - "(?:[[:space:]]+([^[:space:]]+))?" // parameter, like direction (optional) - "[[:space:]]*"}; - -void check_modifiers_once() { - static bool checked = false; - - if (!checked) { - checked = true; - ENSURE(event_pattern.mark_count() == modifiers.size() + 2, "Groups in the input event regex pattern: one per key modifier, key itself and amount."); - } -} - -Event to_event(const std::string &event_type, const std::string ¶m, const int mod) { - try { - if (event_type == "MOUSE") - return sdl_mouse(std::stoi(param), static_cast(mod)); - else if (event_type == "MOUSE_UP") - return sdl_mouse_up_down(std::stoi(param), true, static_cast(mod)); - else if (event_type == "MOUSE_DOWN") - return sdl_mouse_up_down(std::stoi(param), false, static_cast(mod)); - } - catch (std::logic_error &) { - throw Error(MSG(err) << "could not parse mouse button '" << param << "'!"); - } - - if (event_type == "WHEEL") { - if (param == "1" || param == "UP") { - return sdl_wheel(1, static_cast(mod)); - } - else if (param == "-1" || param == "DOWN") { - return sdl_wheel(-1, static_cast(mod)); - } - - throw Error(MSG(err) << "could not parse mouse wheel amount '" << param << "'!"); - } - - SDL_Keycode key_code = SDL_GetKeyFromName(event_type.c_str()); - if (key_code == SDLK_UNKNOWN) { - throw Error(MSG(err) << "could not parse key '" << event_type << "'!"); - } - - if (!param.empty()) - log::log(MSG(warn) << "nothing expected after key name '" << event_type << "', but got '" << param << "'."); - - return sdl_key(key_code, static_cast(mod)); -} - -} // namespace - -/** - * Convert a string to an event, throw if the string is not a valid event. - */ -Event text_to_event(const std::string &event_str) { - check_modifiers_once(); - ENSURE(event_str.find('\n'), "Input event string representation must be one line, got '" << event_str << "'."); - - int mod = 0; - std::smatch event_elements; - - if (std::regex_match(event_str, event_elements, event_pattern)) { - /** - * First element is the entire match, so search from the second. - */ - auto groups_it = std::begin(event_elements) + 1; - - auto first_non_empty = std::find_if(groups_it, std::end(event_elements), [](const std::ssub_match &element) { - return element.length(); - }); - - ENSURE(first_non_empty != std::end(event_elements), "Nothing captured from string representation of event '" << event_str << "': regex pattern is broken."); - - auto index_first_non_empty = std::distance(groups_it, first_non_empty); - - if (index_first_non_empty < std::distance(std::begin(modifiers), std::end(modifiers))) - mod = modifiers[index_first_non_empty]; - - auto event_type = groups_it[modifiers.size()].str(); - ENSURE(!event_type.empty(), "Empty group where key was expected in string representation of event '" << event_str << "': regex pattern is broken."); - - auto param = groups_it[modifiers.size() + 1].str(); - - return to_event(event_type, param, mod); - } - else { - throw Error(MSG(err) << "could not parse keybinding '" << event_str << "'!"); - } -} - -namespace tests { -void parse_event_string() { - text_to_event("q") == Event{event_class::ALPHA, SDL_GetKeyFromName("q"), modset_t{}} || TESTFAIL; - text_to_event("Return") == Event{event_class::NONPRINT, SDL_GetKeyFromName("Return"), modset_t{}} || TESTFAIL; - text_to_event("Ctrl p") == Event{event_class::ALPHA, SDL_GetKeyFromName("p"), sdl_mod(static_cast(KMOD_CTRL))} || TESTFAIL; - text_to_event("Shift MOUSE 1") == Event{event_class::MOUSE_BUTTON, 1, sdl_mod(static_cast(KMOD_SHIFT))} || TESTFAIL; - text_to_event("MOUSE_UP 1") == Event{event_class::MOUSE_BUTTON_UP, 1, modset_t{}} || TESTFAIL; - text_to_event("WHEEL -1") == Event{event_class::MOUSE_WHEEL, -1, modset_t{}} || TESTFAIL; - TESTTHROWS(text_to_event("")); - TESTTHROWS(text_to_event("WHEEL")); - TESTTHROWS(text_to_event("MOUSE")); - TESTTHROWS(text_to_event("MOUSE_DOWN")); - TESTTHROWS(text_to_event("Blurb MOUSE 1")); - TESTTHROWS(text_to_event("Shift MICKEY_MOUSE 1")); - TESTTHROWS(text_to_event("WHEEL TEAR_OFF")); -} - -} // namespace tests - -} // namespace input::legacy -} // namespace openage diff --git a/libopenage/input/legacy/text_to_event.h b/libopenage/input/legacy/text_to_event.h deleted file mode 100644 index 745f33e04b..0000000000 --- a/libopenage/input/legacy/text_to_event.h +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2016-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -#include "input/legacy/event.h" - -namespace openage { -namespace input::legacy { - -/** - * Convert a string to an event, throw if the string is not a valid event. - */ -Event text_to_event(const std::string &event_str); - -} // namespace input::legacy -} // namespace openage diff --git a/libopenage/input/tests.cpp b/libopenage/input/tests.cpp index 735ee3ddce..11860e6449 100644 --- a/libopenage/input/tests.cpp +++ b/libopenage/input/tests.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 "error/error.h" #include "log/log.h" @@ -14,7 +14,8 @@ void action_demo() { auto qtapp = std::make_shared(); // create a window where we get our inputs from - renderer::opengl::GlWindow window("openage input test", 800, 600); + + renderer::opengl::GlWindow window("openage input test"); // manager that receives window inputs // the manager creates its own global context with ID "main" @@ -86,16 +87,40 @@ void action_demo() { input_action catch_all{input_action_t::CUSTOM, nop}; // events that map to specific keys/buttons - Event ev_up{event_class::KEYBOARD, Qt::Key_Up, Qt::NoModifier, QEvent::KeyRelease}; - Event ev_down{event_class::KEYBOARD, Qt::Key_Down, Qt::NoModifier, QEvent::KeyRelease}; - - Event ev_w{event_class::KEYBOARD, Qt::Key_W, Qt::NoModifier, QEvent::KeyRelease}; - Event ev_a{event_class::KEYBOARD, Qt::Key_A, Qt::NoModifier, QEvent::KeyRelease}; - Event ev_s{event_class::KEYBOARD, Qt::Key_S, Qt::NoModifier, QEvent::KeyRelease}; - Event ev_d{event_class::KEYBOARD, Qt::Key_D, Qt::NoModifier, QEvent::KeyRelease}; - - Event ev_lmb{event_class::MOUSE, Qt::LeftButton, Qt::NoModifier, QEvent::MouseButtonRelease}; - Event ev_rmb{event_class::MOUSE, Qt::RightButton, Qt::NoModifier, QEvent::MouseButtonRelease}; + Event ev_up{event_class::KEYBOARD, + Qt::Key::Key_Up, + Qt::KeyboardModifier::NoModifier, + QEvent::KeyRelease}; + Event ev_down{event_class::KEYBOARD, + Qt::Key::Key_Down, + Qt::KeyboardModifier::NoModifier, + QEvent::KeyRelease}; + + Event ev_w{event_class::KEYBOARD, + Qt::Key::Key_W, + Qt::KeyboardModifier::NoModifier, + QEvent::KeyRelease}; + Event ev_a{event_class::KEYBOARD, + Qt::Key::Key_A, + Qt::KeyboardModifier::NoModifier, + QEvent::KeyRelease}; + Event ev_s{event_class::KEYBOARD, + Qt::Key::Key_S, + Qt::KeyboardModifier::NoModifier, + QEvent::KeyRelease}; + Event ev_d{event_class::KEYBOARD, + Qt::Key::Key_D, + Qt::KeyboardModifier::NoModifier, + QEvent::KeyRelease}; + + Event ev_lmb{event_class::MOUSE_BUTTON, + Qt::MouseButton::LeftButton, + Qt::KeyboardModifier::NoModifier, + QEvent::MouseButtonRelease}; + Event ev_rmb{event_class::MOUSE_BUTTON, + Qt::MouseButton::RightButton, + Qt::KeyboardModifier::NoModifier, + QEvent::MouseButtonRelease}; // bind events to actions in the contexts mgr.get_global_context()->bind(ev_up, push_a); diff --git a/libopenage/job/abortable_job_state.h b/libopenage/job/abortable_job_state.h index 5f0036ddce..ff7b01af27 100644 --- a/libopenage/job/abortable_job_state.h +++ b/libopenage/job/abortable_job_state.h @@ -1,4 +1,4 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once @@ -17,7 +17,7 @@ namespace job { * providing two function objects to the job's function. One is used to check * whether the job should be aborted, while the other one aborts the job. */ -template +template class AbortableJobState : public TypedJobStateBase { public: /** The job's function. */ @@ -25,8 +25,7 @@ class AbortableJobState : public TypedJobStateBase { /** Creates a new abortable job with the given function and callback. */ AbortableJobState(abortable_function_t function, - callback_function_t callback) - : + callback_function_t callback) : TypedJobStateBase{callback}, function{function} { } @@ -48,4 +47,5 @@ class AbortableJobState : public TypedJobStateBase { }; -}} // namespace openage::job +} // namespace job +} // namespace openage diff --git a/libopenage/job/job.h b/libopenage/job/job.h index 78181dc7a6..46841de674 100644 --- a/libopenage/job/job.h +++ b/libopenage/job/job.h @@ -1,4 +1,4 @@ -// Copyright 2014-2016 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. #pragma once @@ -24,7 +24,7 @@ class JobManager; * * @param T the job's result type */ -template +template class Job { private: /** A shared pointer to the job's shared state. */ @@ -58,7 +58,8 @@ class Job { ENSURE(this->state->finished.load(), "trying to report a result of an unfinished job"); if (this->state->exception != nullptr) { std::rethrow_exception(this->state->exception); - } else { + } + else { return std::move(this->state->result); } } @@ -68,8 +69,7 @@ class Job { * Creates a job with the given shared state. This method may only be called * by the job manager. */ - Job(std::shared_ptr> state) - : + Job(std::shared_ptr> state) : state{state} { } @@ -81,5 +81,5 @@ class Job { friend class JobManager; }; -} -} +} // namespace job +} // namespace openage diff --git a/libopenage/job/job_aborted_exception.h b/libopenage/job/job_aborted_exception.h index 77d1379a81..34916b33d9 100644 --- a/libopenage/job/job_aborted_exception.h +++ b/libopenage/job/job_aborted_exception.h @@ -1,4 +1,4 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once @@ -15,4 +15,5 @@ class JobAbortedException : public std::exception { } }; -}} // openage::job +} // namespace job +} // namespace openage diff --git a/libopenage/job/job_group.h b/libopenage/job/job_group.h index 70812217c4..3b911ee4e6 100644 --- a/libopenage/job/job_group.h +++ b/libopenage/job/job_group.h @@ -1,4 +1,4 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once @@ -42,9 +42,9 @@ class JobGroup { * @param callback the callback function that is executed, when the background * job has finished */ - template + template Job enqueue(job_function_t function, - callback_function_t callback={}) { + callback_function_t callback = {}) { ENSURE(this->parent_worker, "job group has no worker thread associated"); auto state = std::make_shared>(function, callback); this->parent_worker->enqueue(state); @@ -63,9 +63,9 @@ class JobGroup { * @param callback the callback function that is executed, when the background * job has finished */ - template + template Job enqueue(abortable_function_t function, - callback_function_t callback={}) { + callback_function_t callback = {}) { ENSURE(this->parent_worker, "job group has no worker thread associated"); auto state = std::make_shared>(function, callback); this->parent_worker->enqueue(state); @@ -83,5 +83,5 @@ class JobGroup { friend class JobManager; }; -} -} +} // namespace job +} // namespace openage diff --git a/libopenage/job/job_manager.h b/libopenage/job/job_manager.h index 65e323f320..b06a56bcad 100644 --- a/libopenage/job/job_manager.h +++ b/libopenage/job/job_manager.h @@ -1,4 +1,4 @@ -// Copyright 2014-2019 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. #pragma once @@ -65,11 +65,11 @@ class JobManager { /** Destructor that stops the job manager if it is still running. */ ~JobManager(); - JobManager(const JobManager&) = delete; - JobManager(JobManager&&) = delete; + JobManager(const JobManager &) = delete; + JobManager(JobManager &&) = delete; - JobManager &operator=(const JobManager&) = delete; - JobManager &operator=(JobManager&&) = delete; + JobManager &operator=(const JobManager &) = delete; + JobManager &operator=(JobManager &&) = delete; /** Start the job manager's worker threads. */ void start(); @@ -89,9 +89,9 @@ class JobManager { * @param callback the callback function that is executed, when the background * job has finished */ - template + template Job enqueue(job_function_t function, - callback_function_t callback={}) { + callback_function_t callback = {}) { auto state = std::make_shared>(function, callback); this->enqueue_state(state); return Job{state}; @@ -109,9 +109,9 @@ class JobManager { * @param callback the callback function that is executed, when the background * job has finished */ - template + template Job enqueue(abortable_function_t function, - callback_function_t callback={}) { + callback_function_t callback = {}) { auto state = std::make_shared>(function, callback); this->enqueue_state(state); return Job{state}; @@ -152,5 +152,5 @@ class JobManager { friend class Worker; }; -} -} +} // namespace job +} // namespace openage diff --git a/libopenage/job/job_state.h b/libopenage/job/job_state.h index 882bcb044d..0d32bf7d42 100644 --- a/libopenage/job/job_state.h +++ b/libopenage/job/job_state.h @@ -1,4 +1,4 @@ -// Copyright 2014-2016 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. #pragma once @@ -14,15 +14,14 @@ namespace job { * A job state supports simple job's with functions that return a single * result. While executing the job, it cannot be aborted safely. */ -template +template class JobState : public TypedJobStateBase { public: /** A function object which is executed by the JobManager. */ job_function_t function; /** Creates a new JobState with the given function, that is to be executed. */ - JobState(job_function_t function, callback_function_t callback) - : + JobState(job_function_t function, callback_function_t callback) : TypedJobStateBase{callback}, function{function} { } @@ -31,10 +30,10 @@ class JobState : public TypedJobStateBase { virtual ~JobState() = default; protected: - T execute_and_get(should_abort_t /*should_abort*/) override{ + T execute_and_get(should_abort_t /*should_abort*/) override { return this->function(); } }; -} -} +} // namespace job +} // namespace openage diff --git a/libopenage/job/job_state_base.h b/libopenage/job/job_state_base.h index ccd1015e21..ef3f7e236f 100644 --- a/libopenage/job/job_state_base.h +++ b/libopenage/job/job_state_base.h @@ -1,4 +1,4 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once @@ -38,5 +38,5 @@ class JobStateBase { virtual size_t get_thread_id() = 0; }; -} -} +} // namespace job +} // namespace openage diff --git a/libopenage/job/typed_job_state_base.h b/libopenage/job/typed_job_state_base.h index 4acbb5a8eb..e0f2f360ed 100644 --- a/libopenage/job/typed_job_state_base.h +++ b/libopenage/job/typed_job_state_base.h @@ -1,4 +1,4 @@ -// Copyright 2014-2017 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. #pragma once @@ -6,8 +6,8 @@ #include #include -#include "../util/thread_id.h" #include "../error/error.h" +#include "../util/thread_id.h" #include "job_aborted_exception.h" #include "job_state_base.h" #include "types.h" @@ -22,7 +22,7 @@ namespace job { * @param T the result type of this job state. This type must have a default * constructor and support move semantics. */ -template +template class TypedJobStateBase : public JobStateBase { public: /** Id of the thread, that created this job state. */ @@ -48,8 +48,7 @@ class TypedJobStateBase : public JobStateBase { std::exception_ptr exception; /** Creates a new typed job with the given callback. */ - TypedJobStateBase(callback_function_t callback) - : + TypedJobStateBase(callback_function_t callback) : thread_id{openage::util::get_current_thread_id()}, callback{callback}, finished{false} { @@ -66,9 +65,11 @@ class TypedJobStateBase : public JobStateBase { bool execute(should_abort_t should_abort) override { try { this->result = this->execute_and_get(should_abort); - } catch (JobAbortedException &e) { + } + catch (JobAbortedException &e) { return true; - } catch (...) { + } + catch (...) { this->exception = std::current_exception(); } this->finished.store(true); @@ -85,7 +86,8 @@ class TypedJobStateBase : public JobStateBase { auto get_result = [this]() { if (this->exception != nullptr) { std::rethrow_exception(this->exception); - } else { + } + else { return std::move(this->result); } }; @@ -105,4 +107,5 @@ class TypedJobStateBase : public JobStateBase { virtual T execute_and_get(should_abort_t should_abort) = 0; }; -}} // openage::job +} // namespace job +} // namespace openage diff --git a/libopenage/job/types.h b/libopenage/job/types.h index bc2dbd077e..a197663da6 100644 --- a/libopenage/job/types.h +++ b/libopenage/job/types.h @@ -1,4 +1,4 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once @@ -12,7 +12,7 @@ namespace job { * * @param T the job's result type */ -template +template using job_function_t = std::function; /** @@ -21,8 +21,8 @@ using job_function_t = std::function; * * @param T the job's result type */ -template -using abortable_function_t = std::function,std::function)>; +template +using abortable_function_t = std::function, std::function)>; /** * Type of a function to retrieve the result of a job. If the job threw an @@ -30,7 +30,7 @@ using abortable_function_t = std::function,std::function * * @param T the job's result type */ -template +template using result_function_t = std::function; /** @@ -39,7 +39,7 @@ using result_function_t = std::function; * * @param T the job's result type */ -template +template using callback_function_t = std::function)>; /** Type of a function that returns whether a job should be aborted. */ @@ -48,5 +48,5 @@ using should_abort_t = std::function; /** Type of a function that aborts a job. */ using abort_t = std::function; -} -} +} // namespace job +} // namespace openage diff --git a/libopenage/job/worker.h b/libopenage/job/worker.h index 6332ac8b30..b5873feedc 100644 --- a/libopenage/job/worker.h +++ b/libopenage/job/worker.h @@ -1,4 +1,4 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once @@ -79,5 +79,5 @@ class Worker { void process(); }; -} -} +} // namespace job +} // namespace openage diff --git a/libopenage/legacy_engine.cpp b/libopenage/legacy_engine.cpp deleted file mode 100644 index af5480032b..0000000000 --- a/libopenage/legacy_engine.cpp +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2013-2023 the openage authors. See copying.md for legal info. - -#include "legacy_engine.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "config.h" -#include "error/error.h" -#include "log/log.h" -#include "presenter/legacy/legacy.h" -#include "version.h" -#include "versions/compiletime.h" - -namespace openage { - -LegacyEngine::LegacyEngine(enum mode mode, - const util::Path &root_dir, - const std::shared_ptr &cvar_manager) : - running{false}, - run_mode{mode}, - root_dir{root_dir}, - job_manager{SDL_GetCPUCount()}, - qml_info{this, root_dir["assets"]}, - cvar_manager{cvar_manager}, - profiler{this}, - gui_link{} { - if (mode == mode::LEGACY) { - this->old_display = std::make_unique(root_dir, this); - return; - } - - // TODO: implement FULL and HEADLESS mode :) -} - - -LegacyEngine::~LegacyEngine() {} - - -void LegacyEngine::run() { - try { - this->job_manager.start(); - this->running = true; - - if (this->run_mode == mode::LEGACY) { - this->old_display->loop(); - } - - this->running = false; - } - catch (...) { - this->job_manager.stop(); - throw; - } -} - - -void LegacyEngine::stop() { - this->job_manager.stop(); - this->running = false; -} - - -const util::Path &LegacyEngine::get_root_dir() { - return this->root_dir; -} - - -job::JobManager *LegacyEngine::get_job_manager() { - return &this->job_manager; -} - - -std::shared_ptr LegacyEngine::get_cvar_manager() { - return this->cvar_manager; -} - -gui::EngineQMLInfo LegacyEngine::get_qml_info() { - return this->qml_info; -} - -} // namespace openage diff --git a/libopenage/legacy_engine.h b/libopenage/legacy_engine.h deleted file mode 100644 index bc8b187dff..0000000000 --- a/libopenage/legacy_engine.h +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright 2013-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "log/file_logsink.h" -#include "log/log.h" -// pxd: from libopenage.cvar cimport CVarManager -#include "cvar/cvar.h" -#include "gui/engine_info.h" -#include "handlers.h" -#include "input/legacy/action.h" -#include "job/job_manager.h" -#include "options.h" -#include "unit/selection.h" -#include "util/externalprofiler.h" -#include "util/path.h" -#include "util/profiler.h" -#include "util/strings.h" - - -/** - * Main openage namespace to store all things that make the have to do with the game. - * - * Game entity management, graphics drawing, gui stuff, input handling etc. - * So basically everything that makes the game work lies in here... - */ -namespace openage { - -namespace gui { -class GuiItemLink; -} // namespace gui - -namespace presenter { -class LegacyDisplay; -} // namespace presenter - - -/** - * Qt signals for the engine. - */ -class EngineSignals : public QObject { - Q_OBJECT - -public: -signals: - void global_binds_changed(const std::vector &global_binds); -}; - - -/** - * main engine container. - * - * central foundation for everything the openage engine is capable of. - * - * pxd: - * - * cppclass LegacyEngine: - * - * InputManager &get_input_manager() except + - * CVarManager &get_cvar_manager() except + - */ -class LegacyEngine final { -public: - enum class mode { - LEGACY, - HEADLESS, - FULL, - }; - - LegacyEngine(); - - /** - * engine initialization method. - * starts the engine subsystems depending on the requested run mode. - */ - LegacyEngine(mode mode, - const util::Path &root_dir, - const std::shared_ptr &cvar_manager); - - /** - * engine copy constructor. - */ - LegacyEngine(const LegacyEngine ©) = delete; - - /** - * engine assignment operator. - */ - LegacyEngine &operator=(const LegacyEngine ©) = delete; - - /** - * engine move constructor. - */ - LegacyEngine(LegacyEngine &&other) = delete; - - /** - * engine move operator. - */ - LegacyEngine &operator=(LegacyEngine &&other) = delete; - -public: - /** - * engine destructor, cleans up memory etc. - * deletes opengl context, the SDL window, and engine variables. - */ - ~LegacyEngine(); - - /** - * starts the engine loop. - */ - void run(); - - /** - * enqueues the stop of the main loop. - */ - void stop(); - - /** - * return the data directory where the engine was started from. - */ - const util::Path &get_root_dir(); - - /** - * return this engine's job manager. - * - * TODO: remove ptr access - */ - job::JobManager *get_job_manager(); - - /** - * return this engine's cvar manager. - */ - std::shared_ptr get_cvar_manager(); - - /** - * return this engine's qml info. - */ - gui::EngineQMLInfo get_qml_info(); - - /** - * current engine state variable. - * to be set to false to stop the engine loop. - */ - bool running; - - /** - * profiler used by the engine - */ - util::ExternalProfiler external_profiler; - -private: - /** - * Run-mode of the engine, this determines the basic modules to be loaded. - */ - mode run_mode; - - /** - * The engine root directory. - * Uses the openage fslike path abstraction that can mount paths into one. - * - * This means that this path does simulataneously lead to global assets, - * home-folder-assets, settings, and basically the whole filesystem access. - * - * TODO: move this to a settings class, which then also hosts cvar and the options system. - */ - util::Path root_dir; - - /** - * the engine's job manager, for asynchronous background task queuing. - */ - job::JobManager job_manager; - - - /** - * This stores information to be accessible from the QML engine. - * - * Information in there (such as a pointer to the this engine) - * is then usable from within qml files, after some additional magic. - */ - gui::EngineQMLInfo qml_info; - - /** - * the engine's cvar manager. - */ - std::shared_ptr cvar_manager; - - - /** - * the engines profiler - */ - util::Profiler profiler; - - /** - * Logsink to store messages to the filesystem. - */ - std::unique_ptr logsink_file; - - /** - * old, deprecated, and initial implementation of the renderer and game simulation. - * TODO: remove - */ - std::unique_ptr old_display; - -public: - /** - * Signal emitting capability for the engine. - */ - EngineSignals gui_signals; - - /** - * Link to the Qt GUI. - */ - gui::GuiItemLink *gui_link; -}; - -} // namespace openage diff --git a/libopenage/log/level.h b/libopenage/log/level.h index ef7322ec63..0d35f163c5 100644 --- a/libopenage/log/level.h +++ b/libopenage/log/level.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 @@ -50,11 +50,11 @@ struct OAAPI level : util::Enum { level(); #ifdef __MINGW32__ -// Do not try to optimize these out even if it seems they are not used. -// Namely MIN that is not used within the library. -#define NOOPTIMIZE __attribute__((__used__)) + // Do not try to optimize these out even if it seems they are not used. + // Namely MIN that is not used within the library. + #define NOOPTIMIZE __attribute__((__used__)) #else -#define NOOPTIMIZE + #define NOOPTIMIZE #endif // _win32 static constexpr level_value MIN NOOPTIMIZE{{"min loglevel", -1000}, "5"}; diff --git a/libopenage/log/logsink.h b/libopenage/log/logsink.h index 6edfa7890c..a4583ee48e 100644 --- a/libopenage/log/logsink.h +++ b/libopenage/log/logsink.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 @@ -46,9 +46,9 @@ class LogSink { /** -* Holds a list of all registered log sinks; -* Maintained from the LogSink constructors/destructors. -*/ + * Holds a list of all registered log sinks; + * Maintained from the LogSink constructors/destructors. + */ class OAAPI LogSinkList { public: static LogSinkList &instance(); diff --git a/libopenage/log/message.h b/libopenage/log/message.h index 09b888a9ac..ca534e2efe 100644 --- a/libopenage/log/message.h +++ b/libopenage/log/message.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 @@ -11,7 +11,7 @@ #include #include "../util/compiler.h" -#include "../util/constexpr.h" +#include "../util/consteval.h" #include "../util/stringformatter.h" #include "config.h" #include "logsink.h" @@ -21,11 +21,11 @@ #if defined(__GNUC__) -#define OPENAGE_FUNC_NAME __PRETTY_FUNCTION__ + #define OPENAGE_FUNC_NAME __PRETTY_FUNCTION__ #elif defined(_MSC_VER) -#define OPENAGE_FUNC_NAME __FUNCSIG__ + #define OPENAGE_FUNC_NAME __FUNCSIG__ #else -#define OPENAGE_FUNC_NAME __FUNCTION__ + #define OPENAGE_FUNC_NAME __FUNCTION__ #endif namespace openage { @@ -165,7 +165,7 @@ class OAAPI MessageBuilder : public util::StringFormatter { // for use with existing log::level objects #define MSG_LVLOBJ(LVLOBJ) \ ::openage::log::MessageBuilder( \ - ::openage::util::constexpr_::strip_prefix( \ + ::openage::util::consteval_::strip_prefix( \ __FILE__, \ ::openage::config::buildsystem_sourcefile_dir), \ __LINE__, \ diff --git a/libopenage/log/stdout_logsink.cpp b/libopenage/log/stdout_logsink.cpp index 647af51c49..99daecf5cd 100644 --- a/libopenage/log/stdout_logsink.cpp +++ b/libopenage/log/stdout_logsink.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 "stdout_logsink.h" @@ -7,8 +7,8 @@ #include #ifdef _MSC_VER -#define WIN32_LEAN_AND_MEAN -#include + #define WIN32_LEAN_AND_MEAN + #include #endif #include "log/level.h" 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/main/demo/interactive/interactive.cpp b/libopenage/main/demo/interactive/interactive.cpp index 69a8d83948..d38e8b2c85 100644 --- a/libopenage/main/demo/interactive/interactive.cpp +++ b/libopenage/main/demo/interactive/interactive.cpp @@ -1,7 +1,9 @@ -// 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 "interactive.h" +#include + #include "log/log.h" #include "assets/mod_manager.h" diff --git a/libopenage/main/demo/pong/aicontroller.cpp b/libopenage/main/demo/pong/aicontroller.cpp index bdd1b7df3a..fd635d2a21 100644 --- a/libopenage/main/demo/pong/aicontroller.cpp +++ b/libopenage/main/demo/pong/aicontroller.cpp @@ -34,7 +34,7 @@ std::vector get_ai_inputs(const std::shared_ptr &player, time::time_t ty_hit = 0, tx_hit = 0; if (speed[0] == 0) { - tx_hit = std::numeric_limits::max(); + tx_hit = time::TIME_MAX; } else if (speed[0] > 0) { tx_hit = time::time_t::from_double((area_width - ball_pos[0]) / speed[0]); @@ -44,7 +44,7 @@ std::vector get_ai_inputs(const std::shared_ptr &player, } if (speed[1] == 0) { - ty_hit = std::numeric_limits::max(); + ty_hit = time::TIME_MAX; } else if (speed[1] > 0) { ty_hit = time::time_t::from_double((area_height - ball_pos[1]) / speed[1]); diff --git a/libopenage/main/demo/pong/aicontroller.h b/libopenage/main/demo/pong/aicontroller.h index 9bdfd2502e..f642a22460 100644 --- a/libopenage/main/demo/pong/aicontroller.h +++ b/libopenage/main/demo/pong/aicontroller.h @@ -1,4 +1,4 @@ -// Copyright 2019-2023 the openage authors. See copying.md for legal info. +// Copyright 2019-2024 the openage authors. See copying.md for legal info. #pragma once @@ -12,7 +12,6 @@ std::vector get_ai_inputs( const std::shared_ptr &ball, const std::shared_ptr> &area_size, const time::time_t &now, - bool right_player -); + bool right_player); -} // openage::main::tests::pong +} // namespace openage::main::tests::pong diff --git a/libopenage/main/demo/pong/gui.cpp b/libopenage/main/demo/pong/gui.cpp index 9800357ec0..bc2d334b3e 100644 --- a/libopenage/main/demo/pong/gui.cpp +++ b/libopenage/main/demo/pong/gui.cpp @@ -1,4 +1,4 @@ -// Copyright 2019-2023 the openage authors. See copying.md for legal info. +// Copyright 2019-2024 the openage authors. See copying.md for legal info. #include "gui.h" @@ -6,8 +6,8 @@ #include #include -#include "main/demo/pong/gamestate.h" #include "log/log.h" +#include "main/demo/pong/gamestate.h" #include "renderer/geometry.h" #include "renderer/opengl/context.h" #include "renderer/opengl/shader.h" @@ -34,7 +34,7 @@ const std::vector &Gui::get_inputs(const std::shared_ptr /* for (all inputs from window) { - add key to inputs vector; + add key to inputs vector; } */ @@ -78,7 +78,7 @@ constexpr const int max_log_msgs = 10; Gui::Gui() : - window{"openage engine test", 800, 600}, + window{"openage engine test", {800, 600}}, renderer{window.make_renderer()} { auto vshader_src = renderer::resources::ShaderSource( renderer::resources::shader_lang_t::glsl, @@ -199,7 +199,7 @@ void Gui::draw(const std::shared_ptr &state, const time::time_t &now) auto ball_pos = state->ball->position->get(now); auto ball_pos_matrix = Eigen::Affine3f::Identity(); ball_pos_matrix.prescale(Eigen::Vector3f(ball_size, ball_size, 1.0f)); - //ball_pos_matrix.prerotate(Eigen::AngleAxisf(45.0f * math::PI / 180.0f, Eigen::Vector3f::UnitZ())); + // ball_pos_matrix.prerotate(Eigen::AngleAxisf(45.0f * math::PI / 180.0f, Eigen::Vector3f::UnitZ())); ball_pos_matrix.pretranslate(Eigen::Vector3f(ball_pos[0], ball_pos[1], 0.0f)); this->ball.uniform->update("pos", ball_pos_matrix.matrix()); diff --git a/libopenage/main/demo/pong/physics.cpp b/libopenage/main/demo/pong/physics.cpp index 72a1f65c96..e2aa12e28e 100644 --- a/libopenage/main/demo/pong/physics.cpp +++ b/libopenage/main/demo/pong/physics.cpp @@ -75,7 +75,7 @@ class BallReflectWall : public event::DependencyEventHandler { auto screen_size = state->area_size->get(now); if (speed[1] == 0) { - return std::numeric_limits::max(); + return time::TIME_MAX; } time::time_t ty = 0; @@ -199,7 +199,7 @@ class BallReflectPanel : public event::DependencyEventHandler { auto screen_size = state->area_size->get(now); if (speed[0] == 0) - return std::numeric_limits::max(); + return time::TIME_MAX; time::time_t ty = 0; diff --git a/libopenage/nyan/db.h b/libopenage/nyan/db.h deleted file mode 100644 index 23a39980a6..0000000000 --- a/libopenage/nyan/db.h +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2018-2019 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -#include "../log/log.h" -#include "../error/error.h" - -namespace openage::nyan { - -} diff --git a/libopenage/options.h b/libopenage/options.h index 320b14a54d..0798007f7b 100644 --- a/libopenage/options.h +++ b/libopenage/options.h @@ -36,10 +36,11 @@ using option_list = std::vector; /** * stores a type and value + * + * TODO: What is this used for? */ class OptionValue { public: - /** * value ownership managed by this */ @@ -71,13 +72,13 @@ class OptionValue { /** * Checks equality */ - bool operator ==(const OptionValue &other) const; + bool operator==(const OptionValue &other) const; /** * Assignment, reference values share their values * non reference values are copied */ - const OptionValue &operator =(const OptionValue &other); + const OptionValue &operator=(const OptionValue &other); /** * Value converted to a string @@ -87,7 +88,7 @@ class OptionValue { /** * read inner type - the templated type must match */ - template + template const T &value() const { return var->get(); } @@ -95,13 +96,12 @@ class OptionValue { const option_type type; private: - /** * set the value */ void set(const OptionValue &other); - template + template void set_value(const OptionValue &other) { const T &other_value = other.value(); if (this->var) { @@ -125,7 +125,6 @@ class OptionValue { */ bool owner; util::VariableBase *var; - }; OptionValue parse(option_type t, const std::string &s); @@ -152,7 +151,6 @@ class OptionAction { private: const opt_func_t function; - }; /** @@ -162,8 +160,9 @@ class OptionAction { * with console interaction or gui elements */ class OptionNode { - template + template friend class Var; + public: OptionNode(std::string panel_name); virtual ~OptionNode(); @@ -171,7 +170,7 @@ class OptionNode { /** * lists all available options in a readable format */ - std::vector list_options(bool recurse=false, const std::string &indent=""); + std::vector list_options(bool recurse = false, const std::string &indent = ""); /** * shows all available variable names @@ -189,7 +188,7 @@ class OptionNode { /** * shortcut for get_variable(name).value() */ - template + template const T &getv(const std::string &name) { return this->get_variable(name).value(); } @@ -217,7 +216,6 @@ class OptionNode { const std::string name; protected: - /** * add types to the interface */ @@ -226,7 +224,6 @@ class OptionNode { void add_action(const OptionAction &action); private: - /** * add child nodes */ @@ -264,13 +261,11 @@ class OptionNode { * option node allowing reflection, while also * being directly accessable as a typed member */ -template +template class Var : public util::Variable { public: - Var(OptionNode *owner, const std::string &name, const T &init) - : + Var(OptionNode *owner, const std::string &name, const T &init) : util::Variable{init} { - owner->add(name, this->value); } }; diff --git a/libopenage/pathfinding/CMakeLists.txt b/libopenage/pathfinding/CMakeLists.txt index ad828995c9..33c2844ace 100644 --- a/libopenage/pathfinding/CMakeLists.txt +++ b/libopenage/pathfinding/CMakeLists.txt @@ -1,6 +1,18 @@ add_sources(libopenage - a_star.cpp - heuristics.cpp + cost_field.cpp + definitions.cpp + field_cache.cpp + flow_field.cpp + grid.cpp + integration_field.cpp + integrator.cpp path.cpp + pathfinder.cpp + portal.cpp + sector.cpp tests.cpp + types.cpp ) + +add_subdirectory("demo") +add_subdirectory("legacy") diff --git a/libopenage/pathfinding/cost_field.cpp b/libopenage/pathfinding/cost_field.cpp new file mode 100644 index 0000000000..12889663eb --- /dev/null +++ b/libopenage/pathfinding/cost_field.cpp @@ -0,0 +1,67 @@ +// Copyright 2024-2025 the openage authors. See copying.md for legal info. + +#include "cost_field.h" + +#include "error/error.h" +#include "log/log.h" + +#include "coord/tile.h" +#include "pathfinding/definitions.h" + + +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); +} + +size_t CostField::get_size() const { + return this->size; +} + +cost_t CostField::get_cost(const coord::tile_delta &pos) const { + return this->cells.at(pos.ne + pos.se * this->size); +} + +cost_t CostField::get_cost(size_t x, size_t y) const { + return this->cells.at(x + y * this->size); +} + +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, 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, 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, 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 new file mode 100644 index 0000000000..c03b494a84 --- /dev/null +++ b/libopenage/pathfinding/cost_field.h @@ -0,0 +1,141 @@ +// Copyright 2024-2025 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + +#include "pathfinding/types.h" +#include "time/time.h" + + +namespace openage { +namespace coord { +struct tile_delta; +} // namespace coord + +namespace path { + +/** + * Cost field in the flow-field pathfinding algorithm. + */ +class CostField { +public: + /** + * Create a square cost field with a specified size. + * + * @param size Side length of the field. + */ + CostField(size_t size); + + /** + * Get the size of the cost field. + * + * @return Size of the cost field. + */ + size_t get_size() const; + + /** + * Get the cost at a specified position. + * + * @param pos Coordinates of the cell (relative to field origin). + * @return Cost at the specified position. + */ + cost_t get_cost(const coord::tile_delta &pos) const; + + /** + * Get the cost at a specified position. + * + * @param x X-coordinate of the cell. + * @param y Y-coordinate of the cell. + * @return Cost at the specified position. + */ + cost_t get_cost(size_t x, size_t y) const; + + /** + * Get the cost at a specified position. + * + * @param idx Index of the cell. + * @return Cost at the specified position. + */ + cost_t get_cost(size_t idx) const; + + /** + * Set the cost at a specified position. + * + * @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, const time::time_t &valid_until); + + /** + * Set the cost at a specified position. + * + * @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, 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. + */ + 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. + * + * @return Cost field values. + */ + const std::vector &get_costs() const; + + /** + * 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, 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: + /** + * Side length of the field. + */ + size_t size; + + /** + * Time the cost field expires. + */ + time::time_t valid_until; + + /** + * Cost field values. + */ + std::vector cells; +}; + +} // namespace path +} // namespace openage diff --git a/libopenage/pathfinding/definitions.cpp b/libopenage/pathfinding/definitions.cpp new file mode 100644 index 0000000000..6428dd7ded --- /dev/null +++ b/libopenage/pathfinding/definitions.cpp @@ -0,0 +1,10 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "types.h" + + +namespace openage::path { + +// this file is intentionally empty + +} // namespace openage::path diff --git a/libopenage/pathfinding/definitions.h b/libopenage/pathfinding/definitions.h new file mode 100644 index 0000000000..2ace3c79ab --- /dev/null +++ b/libopenage/pathfinding/definitions.h @@ -0,0 +1,101 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include "pathfinding/types.h" + + +namespace openage::path { + +/** + * Init value for a cells in the cost grid. + * + * Should not be used for actual costs. + */ +constexpr cost_t COST_INIT = 0; + +/** + * Minimum possible cost for a passable cell in the cost grid. + */ +constexpr cost_t COST_MIN = 1; + +/** + * Maximum possible cost for a passable cell in the cost grid. + */ +constexpr cost_t COST_MAX = 254; + +/** + * Cost value for an impassable cell in the cost grid. + */ +constexpr cost_t COST_IMPASSABLE = 255; + + +/** + * Start value for goal cells. + */ +constexpr integrated_cost_t INTEGRATED_COST_START = 0; + +/** + * Unreachable value for a cells in the integration grid. + */ +constexpr integrated_cost_t INTEGRATED_COST_UNREACHABLE = std::numeric_limits::max(); + +/** + * Line of sight flag in an integrated_flags_t value. + */ +constexpr integrated_flags_t INTEGRATE_LOS_MASK = 0x20; + +/** + * Target flag in an integrated_flags_t value. + */ +constexpr integrated_flags_t INTEGRATE_TARGET_MASK = 0x40; + +/** + * Found flag in an integrated_flags_t value. + */ +constexpr integrated_flags_t INTEGRATE_FOUND_MASK = 0x02; + +/** + * Wavefront blocked flag in an integrated_flags_t value. + */ +constexpr integrated_flags_t INTEGRATE_WAVEFRONT_BLOCKED_MASK = 0x04; + +/** + * Initial value for a cell in the integration grid. + */ +constexpr integrated_t INTEGRATE_INIT = {INTEGRATED_COST_UNREACHABLE, 0}; + + +/** + * Initial value for a flow field cell. + */ +constexpr flow_t FLOW_INIT = 0; + +/** + * Mask for the flow direction bits in a flow_t value. + */ +constexpr flow_t FLOW_DIR_MASK = 0x0F; + +/** + * Mask for the flow flag bits in a flow_t value. + */ +constexpr flow_t FLOW_FLAGS_MASK = 0xF0; + +/** + * Pathable flag in a flow_t value. + */ +constexpr flow_t FLOW_PATHABLE_MASK = 0x10; + +/** + * Line of sight flag in a flow_t value. + */ +constexpr flow_t FLOW_LOS_MASK = 0x20; + +/** + * Target flag in a flow_t value. + */ +constexpr flow_t FLOW_TARGET_MASK = 0x40; + +} // namespace openage::path diff --git a/libopenage/pathfinding/demo/CMakeLists.txt b/libopenage/pathfinding/demo/CMakeLists.txt new file mode 100644 index 0000000000..938d6ee3f0 --- /dev/null +++ b/libopenage/pathfinding/demo/CMakeLists.txt @@ -0,0 +1,9 @@ +add_sources(libopenage + demo_0.cpp + demo_1.cpp + tests.cpp +) + +pxdgen( + tests.h +) diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp new file mode 100644 index 0000000000..31f66bb51f --- /dev/null +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -0,0 +1,921 @@ +// Copyright 2024-2025 the openage authors. See copying.md for legal info. + +#include "demo_0.h" + +#include +#include + +#include "coord/tile.h" +#include "pathfinding/cost_field.h" +#include "pathfinding/flow_field.h" +#include "pathfinding/integration_field.h" +#include "renderer/camera/camera.h" +#include "renderer/camera/definitions.h" +#include "renderer/gui/integration/public/gui_application_with_logger.h" +#include "renderer/opengl/window.h" +#include "renderer/render_pass.h" +#include "renderer/renderable.h" +#include "renderer/renderer.h" +#include "renderer/resources/mesh_data.h" +#include "renderer/resources/shader_source.h" +#include "renderer/resources/texture_info.h" +#include "renderer/shader_program.h" +#include "util/math_constants.h" +#include "util/vector.h" + + +namespace openage::path::tests { + +void path_demo_0(const util::Path &path) { + // Side length of the field + // Creates a 10x10 grid + auto field_length = 10; + + // Cost field with some obstacles + auto cost_field = std::make_shared(field_length); + + 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 + auto integration_field = std::make_shared(field_length); + log::log(INFO << "Created integration field"); + + // Set cell (7, 7) to be the initial target cell + auto wavefront_blocked = integration_field->integrate_los(cost_field, coord::tile_delta{7, 7}); + integration_field->integrate_cost(cost_field, std::move(wavefront_blocked)); + log::log(INFO << "Calculated integration field for target cell (7, 7)"); + + // Create a flow field from the integration field + auto flow_field = std::make_shared(field_length); + log::log(INFO << "Created flow field"); + + flow_field->build(integration_field); + log::log(INFO << "Built flow field from integration field"); + + // Render the grid and the pathfinding results + auto qtapp = std::make_shared(); + + // Create a window for rendering + renderer::window_settings settings; + settings.width = 1440; + settings.height = 720; + auto window = std::make_shared("openage pathfinding test", settings); + auto render_manager = std::make_shared(qtapp, window, path); + log::log(INFO << "Created render manager for pathfinding demo"); + + // Show the cost field on startup + render_manager->show_cost_field(cost_field); + auto current_field = RenderManager0::field_t::COST; + log::log(INFO << "Showing cost field"); + + // Make steering vector visibility toggleable + auto vectors_visible = false; + + // Enable mouse button callbacks + window->add_mouse_button_callback([&](const QMouseEvent &ev) { + if (ev.type() == QEvent::MouseButtonRelease) { + if (ev.button() == Qt::RightButton) { // Set target cell + auto tile_pos = render_manager->select_tile(ev.position().x(), ev.position().y()); + auto grid_x = tile_pos.first; + auto grid_y = tile_pos.second; + + if (grid_x >= 0 and grid_x < field_length and grid_y >= 0 and grid_y < field_length) { + log::log(INFO << "Selected new target cell (" << grid_x << ", " << grid_y << ")"); + + // Recalculate the integration field and the flow field + integration_field->reset(); + auto wavefront_blocked = integration_field->integrate_los(cost_field, + coord::tile_delta{grid_x, grid_y}); + integration_field->integrate_cost(cost_field, std::move(wavefront_blocked)); + log::log(INFO << "Calculated integration field for target cell (" + << grid_x << ", " << grid_y << ")"); + + flow_field->reset(); + flow_field->build(integration_field); + log::log(INFO << "Built flow field from integration field"); + + // Show the new field values and vectors + switch (current_field) { + case RenderManager0::field_t::COST: + render_manager->show_cost_field(cost_field); + break; + case RenderManager0::field_t::INTEGRATION: + render_manager->show_integration_field(integration_field); + break; + case RenderManager0::field_t::FLOW: + render_manager->show_flow_field(flow_field, integration_field); + break; + } + + if (vectors_visible) { + render_manager->show_vectors(flow_field); + } + } + } + } + }); + + // Enable key callbacks + window->add_key_callback([&](const QKeyEvent &ev) { + if (ev.type() == QEvent::KeyRelease) { + if (ev.key() == Qt::Key_F1) { // Show cost field + render_manager->show_cost_field(cost_field); + current_field = RenderManager0::field_t::COST; + log::log(INFO << "Showing cost field"); + } + else if (ev.key() == Qt::Key_F2) { // Show integration field + render_manager->show_integration_field(integration_field); + current_field = RenderManager0::field_t::INTEGRATION; + log::log(INFO << "Showing integration field"); + } + else if (ev.key() == Qt::Key_F3) { // Show flow field + render_manager->show_flow_field(flow_field, integration_field); + current_field = RenderManager0::field_t::FLOW; + log::log(INFO << "Showing flow field"); + } + else if (ev.key() == Qt::Key_F4) { // Show steering vectors + if (vectors_visible) { + render_manager->hide_vectors(); + vectors_visible = false; + log::log(INFO << "Hiding steering vectors"); + } + else { + render_manager->show_vectors(flow_field); + vectors_visible = true; + log::log(INFO << "Showing steering vectors"); + } + } + } + }); + + log::log(INFO << "Instructions:"); + log::log(INFO << " 1. Press F1/F2/F3 to show cost/integration/flow field"); + log::log(INFO << " 2. Press F4 to toggle steering vectors"); + + // Run the render loop + render_manager->run(); +} + + +RenderManager0::RenderManager0(const std::shared_ptr &app, + const std::shared_ptr &window, + const util::Path &path) : + path{path}, + app{app}, + window{window}, + renderer{window->make_renderer()}, + camera{std::make_shared(renderer, window->get_size())} { + // Position camera to look at center of the grid + camera->look_at_coord({5, 5, 0}); + window->add_resize_callback([&](size_t w, size_t h, double /*scale*/) { + camera->resize(w, h); + }); + + this->init_shaders(); + this->init_passes(); +} + + +void RenderManager0::run() { + while (not this->window->should_close()) { + this->app->process_events(); + + this->renderer->render(this->background_pass); + this->renderer->render(this->field_pass); + this->renderer->render(this->vector_pass); + this->renderer->render(this->grid_pass); + this->renderer->render(this->display_pass); + + this->window->update(); + + this->renderer->check_error(); + } + this->window->close(); +} + +void RenderManager0::show_cost_field(const std::shared_ptr &field) { + Eigen::Matrix4f model = Eigen::Matrix4f::Identity(); + auto unifs = this->cost_shader->new_uniform_input( + "model", + model, + "view", + this->camera->get_view_matrix(), + "proj", + this->camera->get_projection_matrix()); + auto mesh = RenderManager0::get_cost_field_mesh(field); + auto geometry = this->renderer->add_mesh_geometry(mesh); + renderer::Renderable renderable{ + unifs, + geometry, + true, + true, + }; + this->field_pass->set_renderables({renderable}); +} + +void RenderManager0::show_integration_field(const std::shared_ptr &field) { + Eigen::Matrix4f model = Eigen::Matrix4f::Identity(); + auto unifs = this->integration_shader->new_uniform_input( + "model", + model, + "view", + this->camera->get_view_matrix(), + "proj", + this->camera->get_projection_matrix()); + auto mesh = get_integration_field_mesh(field, 4); + auto geometry = this->renderer->add_mesh_geometry(mesh); + renderer::Renderable renderable{ + unifs, + geometry, + true, + true, + }; + this->field_pass->set_renderables({renderable}); +} + +void RenderManager0::show_flow_field(const std::shared_ptr &flow_field, + const std::shared_ptr &int_field) { + Eigen::Matrix4f model = Eigen::Matrix4f::Identity(); + auto unifs = this->flow_shader->new_uniform_input( + "model", + model, + "view", + this->camera->get_view_matrix(), + "proj", + this->camera->get_projection_matrix()); + auto mesh = get_flow_field_mesh(flow_field, int_field, 4); + auto geometry = this->renderer->add_mesh_geometry(mesh); + renderer::Renderable renderable{ + unifs, + geometry, + true, + true, + }; + this->field_pass->set_renderables({renderable}); +} + +void RenderManager0::show_vectors(const std::shared_ptr &field) { + static const std::unordered_map offset_dir{ + {flow_dir_t::NORTH, {-0.25f, 0.0f, 0.0f}}, + {flow_dir_t::NORTH_EAST, {-0.25f, 0.0f, -0.25f}}, + {flow_dir_t::EAST, {0.0f, 0.0f, -0.25f}}, + {flow_dir_t::SOUTH_EAST, {0.25f, 0.0f, -0.25f}}, + {flow_dir_t::SOUTH, {0.25f, 0.0f, 0.0f}}, + {flow_dir_t::SOUTH_WEST, {0.25f, 0.0f, 0.25f}}, + {flow_dir_t::WEST, {0.0f, 0.0f, 0.25f}}, + {flow_dir_t::NORTH_WEST, {-0.25f, 0.0f, 0.25f}}, + }; + + this->vector_pass->clear_renderables(); + for (size_t y = 0; y < field->get_size(); ++y) { + for (size_t x = 0; x < field->get_size(); ++x) { + auto cell = field->get_cell(coord::tile_delta(x, y)); + if (cell & FLOW_PATHABLE_MASK and not(cell & FLOW_LOS_MASK)) { + Eigen::Affine3f arrow_model = Eigen::Affine3f::Identity(); + arrow_model.translate(Eigen::Vector3f(y + 0.5, 0, -1.0f * x - 0.5)); + auto dir = static_cast(cell & FLOW_DIR_MASK); + arrow_model.translate(offset_dir.at(dir)); + + auto rotation_rad = (cell & FLOW_DIR_MASK) * -45 * math::DEGSPERRAD; + arrow_model.rotate(Eigen::AngleAxisf(rotation_rad, Eigen::Vector3f::UnitY())); + auto arrow_unifs = this->vector_shader->new_uniform_input( + "model", + arrow_model.matrix(), + "view", + camera->get_view_matrix(), + "proj", + camera->get_projection_matrix(), + "color", + Eigen::Vector4f{0.0f, 0.0f, 0.0f, 1.0f}); + auto arrow_mesh = get_arrow_mesh(); + auto arrow_geometry = renderer->add_mesh_geometry(arrow_mesh); + renderer::Renderable arrow_renderable{ + arrow_unifs, + arrow_geometry, + true, + true, + }; + this->vector_pass->add_renderables(std::move(arrow_renderable)); + } + } + } +} + +void RenderManager0::hide_vectors() { + this->vector_pass->clear_renderables(); +} + +std::pair RenderManager0::select_tile(double x, double y) { + auto grid_plane_normal = Eigen::Vector3f{0, 1, 0}; + auto grid_plane_point = Eigen::Vector3f{0, 0, 0}; + auto camera_direction = renderer::camera::CAM_DIRECTION; + auto camera_position = camera->get_input_pos( + coord::input(x, y)); + + Eigen::Vector3f intersect = camera_position + camera_direction * (grid_plane_point - camera_position).dot(grid_plane_normal) / camera_direction.dot(grid_plane_normal); + auto grid_x = static_cast(-1 * intersect[2]); + auto grid_y = static_cast(intersect[0]); + + return {grid_x, grid_y}; +} + +void RenderManager0::init_shaders() { + // Shader sources + auto shaderdir = this->path / "assets" / "test" / "shaders" / "pathfinding"; + + // Shader for rendering the cost field + auto cf_vshader_file = shaderdir / "demo_0_cost_field.vert.glsl"; + auto cf_vshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::vertex, + cf_vshader_file); + + auto cf_fshader_file = shaderdir / "demo_0_cost_field.frag.glsl"; + auto cf_fshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::fragment, + cf_fshader_file); + + // Shader for rendering the integration field + auto if_vshader_file = shaderdir / "demo_0_integration_field.vert.glsl"; + auto if_vshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::vertex, + if_vshader_file); + + auto if_fshader_file = shaderdir / "demo_0_integration_field.frag.glsl"; + auto if_fshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::fragment, + if_fshader_file); + + // Shader for rendering the flow field + auto ff_vshader_file = shaderdir / "demo_0_flow_field.vert.glsl"; + auto ff_vshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::vertex, + ff_vshader_file); + + auto ff_fshader_file = shaderdir / "demo_0_flow_field.frag.glsl"; + auto ff_fshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::fragment, + ff_fshader_file); + + // Shader for rendering steering vectors + auto vec_vshader_file = shaderdir / "demo_0_vector.vert.glsl"; + auto vec_vshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::vertex, + vec_vshader_file); + + auto vec_fshader_file = shaderdir / "demo_0_vector.frag.glsl"; + auto vec_fshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::fragment, + vec_fshader_file); + + // Shader for rendering the grid + auto grid_vshader_file = shaderdir / "demo_0_grid.vert.glsl"; + auto grid_vshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::vertex, + grid_vshader_file); + + auto grid_fshader_file = shaderdir / "demo_0_grid.frag.glsl"; + auto grid_fshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::fragment, + grid_fshader_file); + + // Shader for 2D monocolored objects + auto obj_vshader_file = shaderdir / "demo_0_obj.vert.glsl"; + auto obj_vshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::vertex, + obj_vshader_file); + + auto obj_fshader_file = shaderdir / "demo_0_obj.frag.glsl"; + auto obj_fshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::fragment, + obj_fshader_file); + + // Shader for rendering to the display target + auto display_vshader_file = shaderdir / "demo_0_display.vert.glsl"; + auto display_vshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::vertex, + display_vshader_file); + + auto display_fshader_file = shaderdir / "demo_0_display.frag.glsl"; + auto display_fshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::fragment, + display_fshader_file); + + // Create the shaders + this->cost_shader = renderer->add_shader({cf_vshader_src, cf_fshader_src}); + this->integration_shader = renderer->add_shader({if_vshader_src, if_fshader_src}); + this->flow_shader = renderer->add_shader({ff_vshader_src, ff_fshader_src}); + this->vector_shader = renderer->add_shader({vec_vshader_src, vec_fshader_src}); + this->grid_shader = renderer->add_shader({grid_vshader_src, grid_fshader_src}); + this->obj_shader = renderer->add_shader({obj_vshader_src, obj_fshader_src}); + this->display_shader = renderer->add_shader({display_vshader_src, display_fshader_src}); +} + +void RenderManager0::init_passes() { + auto size = this->window->get_size(); + + // Make a framebuffer for the background render pass to draw into + auto background_texture = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::rgba8)); + auto depth_texture = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::depth24)); + auto fbo = renderer->create_texture_target({background_texture, depth_texture}); + + // Background object for contrast between field and display + auto background_unifs = obj_shader->new_uniform_input( + "color", + Eigen::Vector4f{64.0 / 256, 128.0 / 256, 196.0 / 256, 1.0}); + auto background = renderer->add_mesh_geometry(renderer::resources::MeshData::make_quad()); + renderer::Renderable background_obj{ + background_unifs, + background, + true, + true, + }; + this->background_pass = renderer->add_render_pass({background_obj}, fbo); + + // Make a framebuffer for the field render pass to draw into + auto field_texture = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::rgba8)); + auto depth_texture_2 = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::depth24)); + auto field_fbo = renderer->create_texture_target({field_texture, depth_texture_2}); + this->field_pass = renderer->add_render_pass({}, field_fbo); + + // Make a framebuffer for the vector render passes to draw into + auto vector_texture = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::rgba8)); + auto depth_texture_3 = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::depth24)); + auto vector_fbo = renderer->create_texture_target({vector_texture, depth_texture_3}); + this->vector_pass = renderer->add_render_pass({}, vector_fbo); + + // Make a framebuffer for the grid render passes to draw into + auto grid_texture = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::rgba8)); + auto depth_texture_4 = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::depth24)); + auto grid_fbo = renderer->create_texture_target({grid_texture, depth_texture_4}); + + // Create object for the grid + Eigen::Matrix4f model = Eigen::Matrix4f::Identity(); + auto grid_unifs = grid_shader->new_uniform_input( + "model", + model, + "view", + camera->get_view_matrix(), + "proj", + camera->get_projection_matrix()); + auto grid_mesh = this->get_grid_mesh(10); + auto grid_geometry = renderer->add_mesh_geometry(grid_mesh); + renderer::Renderable grid_obj{ + grid_unifs, + grid_geometry, + true, + true, + }; + this->grid_pass = renderer->add_render_pass({grid_obj}, grid_fbo); + + // Make two objects that draw the results of the previous passes onto the screen + // in the display render pass + auto bg_texture_unif = display_shader->new_uniform_input("color_texture", background_texture); + auto quad = renderer->add_mesh_geometry(renderer::resources::MeshData::make_quad()); + renderer::Renderable bg_pass_obj{ + bg_texture_unif, + quad, + true, + true, + }; + auto field_texture_unif = display_shader->new_uniform_input("color_texture", field_texture); + renderer::Renderable field_pass_obj{ + field_texture_unif, + quad, + true, + true, + }; + auto vector_texture_unif = display_shader->new_uniform_input("color_texture", vector_texture); + renderer::Renderable vector_pass_obj{ + vector_texture_unif, + quad, + true, + true, + }; + auto grid_texture_unif = display_shader->new_uniform_input("color_texture", grid_texture); + renderer::Renderable grid_pass_obj{ + grid_texture_unif, + quad, + true, + true, + }; + this->display_pass = renderer->add_render_pass( + {bg_pass_obj, field_pass_obj, vector_pass_obj, grid_pass_obj}, + renderer->get_display_target()); +} + +renderer::resources::MeshData RenderManager0::get_cost_field_mesh(const std::shared_ptr &field, + size_t resolution) { + // increase by 1 in every dimension because to get the vertex length + // of each dimension + util::Vector2s size{ + field->get_size() * resolution + 1, + field->get_size() * resolution + 1, + }; + auto vert_distance = 1.0f / resolution; + + // add vertices for the cells of the grid + std::vector verts{}; + auto vert_count = size[0] * size[1]; + verts.reserve(vert_count * 4); + for (int i = 0; i < static_cast(size[0]); ++i) { + for (int j = 0; j < static_cast(size[1]); ++j) { + // for each vertex, compare the surrounding tiles + std::vector surround{}; + if (j - 1 >= 0 and i - 1 >= 0) { + auto cost = field->get_cost((i - 1) / resolution, (j - 1) / resolution); + surround.push_back(cost); + } + if (j < static_cast(field->get_size()) and i - 1 >= 0) { + auto cost = field->get_cost((i - 1) / resolution, j / resolution); + surround.push_back(cost); + } + if (j < static_cast(field->get_size()) and i < static_cast(field->get_size())) { + auto cost = field->get_cost(i / resolution, j / resolution); + surround.push_back(cost); + } + if (j - 1 >= 0 and i < static_cast(field->get_size())) { + auto cost = field->get_cost(i / resolution, (j - 1) / resolution); + surround.push_back(cost); + } + // use the cost of the most expensive surrounding tile + auto max_cost = *std::max_element(surround.begin(), surround.end()); + + coord::scene3 v{ + static_cast(i * vert_distance), + static_cast(j * vert_distance), + 0, + }; + auto world_v = v.to_world_space(); + verts.push_back(world_v[0]); + verts.push_back(world_v[1]); + verts.push_back(world_v[2]); + verts.push_back(max_cost); + } + } + + // split the grid into triangles using an index array + std::vector idxs; + idxs.reserve((size[0] - 1) * (size[1] - 1) * 6); + // iterate over all tiles in the grid by columns, i.e. starting + // from the left corner to the bottom corner if you imagine it from + // the camera's point of view + for (size_t i = 0; i < size[0] - 1; ++i) { + for (size_t j = 0; j < size[1] - 1; ++j) { + // since we are working on tiles, we split each tile into two triangles + // with counter-clockwise vertex order + idxs.push_back(j + i * size[1]); // bottom left + idxs.push_back(j + 1 + i * size[1]); // bottom right + idxs.push_back(j + size[1] + i * size[1]); // top left + idxs.push_back(j + 1 + i * size[1]); // bottom right + idxs.push_back(j + size[1] + 1 + i * size[1]); // top right + idxs.push_back(j + size[1] + i * size[1]); // top left + } + } + + renderer::resources::VertexInputInfo info{ + {renderer::resources::vertex_input_t::V3F32, renderer::resources::vertex_input_t::F32}, + renderer::resources::vertex_layout_t::AOS, + renderer::resources::vertex_primitive_t::TRIANGLES, + renderer::resources::index_t::U16}; + + auto const vert_data_size = verts.size() * sizeof(float); + std::vector vert_data(vert_data_size); + std::memcpy(vert_data.data(), verts.data(), vert_data_size); + + auto const idx_data_size = idxs.size() * sizeof(uint16_t); + std::vector idx_data(idx_data_size); + std::memcpy(idx_data.data(), idxs.data(), idx_data_size); + + return {std::move(vert_data), std::move(idx_data), info}; +} + +renderer::resources::MeshData RenderManager0::get_integration_field_mesh(const std::shared_ptr &field, + size_t resolution) { + // increase by 1 in every dimension because to get the vertex length + // of each dimension + util::Vector2s size{ + field->get_size() * resolution + 1, + field->get_size() * resolution + 1, + }; + auto vert_distance = 1.0f / resolution; + + // add vertices for the cells of the grid + std::vector verts{}; + auto vert_count = size[0] * size[1]; + verts.reserve(vert_count * 4); + for (int i = 0; i < static_cast(size[0]); ++i) { + for (int j = 0; j < static_cast(size[1]); ++j) { + // for each vertex, compare the surrounding tiles + std::vector surround{}; + if (j - 1 >= 0 and i - 1 >= 0) { + auto cost = field->get_cell((i - 1) / resolution, (j - 1) / resolution).cost; + surround.push_back(cost); + } + if (j < static_cast(field->get_size()) and i - 1 >= 0) { + auto cost = field->get_cell((i - 1) / resolution, j / resolution).cost; + surround.push_back(cost); + } + if (j < static_cast(field->get_size()) and i < static_cast(field->get_size())) { + auto cost = field->get_cell(i / resolution, j / resolution).cost; + surround.push_back(cost); + } + if (j - 1 >= 0 and i < static_cast(field->get_size())) { + auto cost = field->get_cell(i / resolution, (j - 1) / resolution).cost; + surround.push_back(cost); + } + // use the cost of the most expensive surrounding tile + auto max_cost = *std::max_element(surround.begin(), surround.end()); + + coord::scene3 v{ + static_cast(i * vert_distance), + static_cast(j * vert_distance), + 0, + }; + auto world_v = v.to_world_space(); + verts.push_back(world_v[0]); + verts.push_back(world_v[1]); + verts.push_back(world_v[2]); + verts.push_back(max_cost); + } + } + + // split the grid into triangles using an index array + std::vector idxs; + idxs.reserve((size[0] - 1) * (size[1] - 1) * 6); + // iterate over all tiles in the grid by columns, i.e. starting + // from the left corner to the bottom corner if you imagine it from + // the camera's point of view + for (size_t i = 0; i < size[0] - 1; ++i) { + for (size_t j = 0; j < size[1] - 1; ++j) { + // since we are working on tiles, we split each tile into two triangles + // with counter-clockwise vertex order + idxs.push_back(j + i * size[1]); // bottom left + idxs.push_back(j + 1 + i * size[1]); // bottom right + idxs.push_back(j + size[1] + i * size[1]); // top left + idxs.push_back(j + 1 + i * size[1]); // bottom right + idxs.push_back(j + size[1] + 1 + i * size[1]); // top right + idxs.push_back(j + size[1] + i * size[1]); // top left + } + } + + renderer::resources::VertexInputInfo info{ + {renderer::resources::vertex_input_t::V3F32, renderer::resources::vertex_input_t::F32}, + renderer::resources::vertex_layout_t::AOS, + renderer::resources::vertex_primitive_t::TRIANGLES, + renderer::resources::index_t::U16}; + + auto const vert_data_size = verts.size() * sizeof(float); + std::vector vert_data(vert_data_size); + std::memcpy(vert_data.data(), verts.data(), vert_data_size); + + auto const idx_data_size = idxs.size() * sizeof(uint16_t); + std::vector idx_data(idx_data_size); + std::memcpy(idx_data.data(), idxs.data(), idx_data_size); + + return {std::move(vert_data), std::move(idx_data), info}; +} + +renderer::resources::MeshData RenderManager0::get_flow_field_mesh(const std::shared_ptr &flow_field, + const std::shared_ptr &int_field, + size_t resolution) { + // increase by 1 in every dimension because to get the vertex length + // of each dimension + util::Vector2s size{ + flow_field->get_size() * resolution + 1, + flow_field->get_size() * resolution + 1, + }; + auto vert_distance = 1.0f / resolution; + + // add vertices for the cells of the grid + std::vector verts{}; + auto vert_count = size[0] * size[1]; + verts.reserve(vert_count * 5); + for (int i = 0; i < static_cast(size[0]); ++i) { + for (int j = 0; j < static_cast(size[1]); ++j) { + // for each vertex, compare the surrounding tiles + std::vector ff_surround{}; + std::vector int_surround{}; + if (j - 1 >= 0 and i - 1 >= 0) { + auto ff_cost = flow_field->get_cell((i - 1) / resolution, (j - 1) / resolution); + ff_surround.push_back(ff_cost); + + auto int_flags = int_field->get_cell((i - 1) / resolution, (j - 1) / resolution).flags; + int_surround.push_back(int_flags); + } + if (j < static_cast(flow_field->get_size()) and i - 1 >= 0) { + auto ff_cost = flow_field->get_cell((i - 1) / resolution, j / resolution); + ff_surround.push_back(ff_cost); + + auto int_flags = int_field->get_cell((i - 1) / resolution, j / resolution).flags; + int_surround.push_back(int_flags); + } + if (j < static_cast(flow_field->get_size()) and i < static_cast(flow_field->get_size())) { + auto ff_cost = flow_field->get_cell(i / resolution, j / resolution); + ff_surround.push_back(ff_cost); + + auto int_flags = int_field->get_cell(i / resolution, j / resolution).flags; + int_surround.push_back(int_flags); + } + if (j - 1 >= 0 and i < static_cast(flow_field->get_size())) { + auto ff_cost = flow_field->get_cell(i / resolution, (j - 1) / resolution); + ff_surround.push_back(ff_cost); + + auto int_flags = int_field->get_cell(i / resolution, (j - 1) / resolution).flags; + int_surround.push_back(int_flags); + } + // combine the flags of the sorrounding tiles + auto ff_max_flags = 0; + for (auto &val : ff_surround) { + ff_max_flags |= val & 0xF0; + } + auto int_max_flags = 0; + for (auto &val : int_surround) { + int_max_flags |= val; + } + + coord::scene3 v{ + static_cast(i * vert_distance), + static_cast(j * vert_distance), + 0, + }; + auto world_v = v.to_world_space(); + verts.push_back(world_v[0]); + verts.push_back(world_v[1]); + verts.push_back(world_v[2]); + verts.push_back(ff_max_flags); + verts.push_back(int_max_flags); + } + } + + // split the grid into triangles using an index array + std::vector idxs; + idxs.reserve((size[0] - 1) * (size[1] - 1) * 6); + // iterate over all tiles in the grid by columns, i.e. starting + // from the left corner to the bottom corner if you imagine it from + // the camera's point of view + for (size_t i = 0; i < size[0] - 1; ++i) { + for (size_t j = 0; j < size[1] - 1; ++j) { + // since we are working on tiles, we split each tile into two triangles + // with counter-clockwise vertex order + idxs.push_back(j + i * size[1]); // bottom left + idxs.push_back(j + 1 + i * size[1]); // bottom right + idxs.push_back(j + size[1] + i * size[1]); // top left + idxs.push_back(j + 1 + i * size[1]); // bottom right + idxs.push_back(j + size[1] + 1 + i * size[1]); // top right + idxs.push_back(j + size[1] + i * size[1]); // top left + } + } + + renderer::resources::VertexInputInfo info{ + {renderer::resources::vertex_input_t::V3F32, + renderer::resources::vertex_input_t::F32, + renderer::resources::vertex_input_t::F32}, + renderer::resources::vertex_layout_t::AOS, + renderer::resources::vertex_primitive_t::TRIANGLES, + renderer::resources::index_t::U16}; + + auto const vert_data_size = verts.size() * sizeof(float); + std::vector vert_data(vert_data_size); + std::memcpy(vert_data.data(), verts.data(), vert_data_size); + + auto const idx_data_size = idxs.size() * sizeof(uint16_t); + std::vector idx_data(idx_data_size); + std::memcpy(idx_data.data(), idxs.data(), idx_data_size); + + return {std::move(vert_data), std::move(idx_data), info}; +} + +renderer::resources::MeshData RenderManager0::get_arrow_mesh() { + // vertices for the arrow + // x, y, z + std::vector verts{ + // clang-format off + 0.2f, 0.01f, -0.05f, + 0.2f, 0.01f, 0.05f, + -0.2f, 0.01f, -0.05f, + -0.2f, 0.01f, 0.05f, + // clang-format on + }; + + renderer::resources::VertexInputInfo info{ + {renderer::resources::vertex_input_t::V3F32}, + renderer::resources::vertex_layout_t::AOS, + renderer::resources::vertex_primitive_t::TRIANGLE_STRIP}; + + auto const vert_data_size = verts.size() * sizeof(float); + std::vector vert_data(vert_data_size); + std::memcpy(vert_data.data(), verts.data(), vert_data_size); + + return {std::move(vert_data), info}; +} + +renderer::resources::MeshData RenderManager0::get_grid_mesh(size_t side_length) { + // increase by 1 in every dimension because to get the vertex length + // of each dimension + util::Vector2s size{side_length + 1, side_length + 1}; + + // add vertices for the cells of the grid + std::vector verts{}; + auto vert_count = size[0] * size[1]; + verts.reserve(vert_count * 3); + for (int i = 0; i < (int)size[0]; ++i) { + for (int j = 0; j < (int)size[1]; ++j) { + coord::scene3 v{ + static_cast(i), + static_cast(j), + 0, + }; + auto world_v = v.to_world_space(); + verts.push_back(world_v[0]); + verts.push_back(world_v[1]); + verts.push_back(world_v[2]); + } + } + + // split the grid into lines using an index array + std::vector idxs; + idxs.reserve((size[0] - 1) * (size[1] - 1) * 8); + // iterate over all tiles in the grid by columns, i.e. starting + // from the left corner to the bottom corner if you imagine it from + // the camera's point of view + for (size_t i = 0; i < size[0] - 1; ++i) { + for (size_t j = 0; j < size[1] - 1; ++j) { + // since we are working on tiles, we just draw a square using the 4 vertices + idxs.push_back(j + i * size[1]); // bottom left + idxs.push_back(j + 1 + i * size[1]); // bottom right + idxs.push_back(j + 1 + i * size[1]); // bottom right + idxs.push_back(j + size[1] + 1 + i * size[1]); // top right + idxs.push_back(j + size[1] + 1 + i * size[1]); // top right + idxs.push_back(j + size[1] + i * size[1]); // top left + idxs.push_back(j + size[1] + i * size[1]); // top left + idxs.push_back(j + i * size[1]); // bottom left + } + } + + renderer::resources::VertexInputInfo info{ + {renderer::resources::vertex_input_t::V3F32}, + renderer::resources::vertex_layout_t::AOS, + renderer::resources::vertex_primitive_t::LINES, + renderer::resources::index_t::U16}; + + auto const vert_data_size = verts.size() * sizeof(float); + std::vector vert_data(vert_data_size); + std::memcpy(vert_data.data(), verts.data(), vert_data_size); + + auto const idx_data_size = idxs.size() * sizeof(uint16_t); + std::vector idx_data(idx_data_size); + std::memcpy(idx_data.data(), idxs.data(), idx_data_size); + + return {std::move(vert_data), std::move(idx_data), info}; +} + + +} // namespace openage::path::tests diff --git a/libopenage/pathfinding/demo/demo_0.h b/libopenage/pathfinding/demo/demo_0.h new file mode 100644 index 0000000000..adf5651971 --- /dev/null +++ b/libopenage/pathfinding/demo/demo_0.h @@ -0,0 +1,282 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include "util/path.h" + + +namespace openage { +namespace renderer { +class RenderPass; +class Renderer; +class ShaderProgram; +class Window; + +namespace camera { +class Camera; +} // namespace camera + +namespace gui { +class GuiApplicationWithLogger; +} // namespace gui + +namespace resources { +class MeshData; +} // namespace resources +} // namespace renderer + +namespace path { +class CostField; +class IntegrationField; +class FlowField; + +namespace tests { + +/** + * Show the functionality of the different flowfield types: + * - Cost field + * - Integration field + * - Flow field + * + * Visualizes the pathfinding results using our rendering backend. + * + * @param path Path to the project rootdir. + */ +void path_demo_0(const util::Path &path); + + +/** + * Manages the graphical display of the pathfinding demo. + */ +class RenderManager0 { +public: + enum class field_t { + COST, + INTEGRATION, + FLOW + }; + + /** + * Create a new render manager. + * + * @param app GUI application. + * @param window Window to render to. + * @param path Path to the project rootdir. + */ + RenderManager0(const std::shared_ptr &app, + const std::shared_ptr &window, + const util::Path &path); + ~RenderManager0() = default; + + /** + * Run the render loop. + */ + void run(); + + /** + * Draw a cost field to the screen. + * + * @param field Cost field. + */ + void show_cost_field(const std::shared_ptr &field); + + /** + * Draw an integration field to the screen. + * + * @param field Integration field. + */ + void show_integration_field(const std::shared_ptr &field); + + /** + * Draw a flow field to the screen. + * + * @param flow_field Flow field. + * @param int_field Integration field. + */ + void show_flow_field(const std::shared_ptr &flow_field, + const std::shared_ptr &int_field); + + /** + * Draw the steering vectors of a flow field to the screen. + * + * @param field Flow field. + */ + void show_vectors(const std::shared_ptr &field); + + /** + * Hide drawn steering vectors. + */ + void hide_vectors(); + + /** + * Get the cell coordinates at a given screen position. + * + * @param x X coordinate. + * @param y Y coordinate. + */ + std::pair select_tile(double x, double y); + +private: + /** + * Load the shader sources for the demo and create the shader programs. + */ + void init_shaders(); + + /** + * Create the following render passes for the demo: + * - Background pass: Mono-colored background object. + * - Field pass; Renders the cost, integration and flow fields. + * - Vector pass: Renders the steering vectors of a flow field. + * - Grid pass: Renders a grid on top of the fields. + * - Display pass: Draws the results of previous passes to the screen. + */ + void init_passes(); + + + /** + * Create a mesh for the cost field. + * + * @param field Cost field to visualize. + * @param resolution Determines the number of subdivisions per grid cell. The number of + * quads per cell is resolution^2. (default = 2) + * + * @return Mesh data for the cost field. + */ + static renderer::resources::MeshData get_cost_field_mesh(const std::shared_ptr &field, + size_t resolution = 2); + + /** + * Create a mesh for the integration field. + * + * @param field Integration field to visualize. + * @param resolution Determines the number of subdivisions per grid cell. The number of + * quads per cell is resolution^2. (default = 2) + * + * @return Mesh data for the integration field. + */ + static renderer::resources::MeshData get_integration_field_mesh(const std::shared_ptr &field, + size_t resolution = 2); + + /** + * Create a mesh for the flow field. + * + * @param flow_field Flow field to visualize. + * @param int_field Integration field. + */ + static renderer::resources::MeshData get_flow_field_mesh(const std::shared_ptr &flow_field, + const std::shared_ptr &int_field, + size_t resolution = 2); + + /** + * Create a mesh for an arrow. + * + * @return Mesh data for an arrow. + */ + static renderer::resources::MeshData get_arrow_mesh(); + + /** + * Create a mesh for the grid. + * + * @param side_length Length of the grid's side. + * + * @return Mesh data for the grid. + */ + static renderer::resources::MeshData get_grid_mesh(size_t side_length); + + /** + * Path to the project rootdir. + */ + const util::Path &path; + + /* Renderer objects */ + + /** + * Qt GUI application. + */ + std::shared_ptr app; + + /** + * openage window to render to. + */ + std::shared_ptr window; + + /** + * openage renderer instance. + */ + std::shared_ptr renderer; + + /** + * Camera to view the scene. + */ + std::shared_ptr camera; + + /* Shader programs */ + + /** + * Shader program for rendering a cost field. + */ + std::shared_ptr cost_shader; + + /** + * Shader program for rendering a integration field. + */ + std::shared_ptr integration_shader; + + /** + * Shader program for rendering a flow field. + */ + std::shared_ptr flow_shader; + + /** + * Shader program for rendering steering vectors. + */ + std::shared_ptr vector_shader; + + /** + * Shader program for rendering a grid. + */ + std::shared_ptr grid_shader; + + /** + * Shader program for rendering 2D mono-colored objects. + */ + std::shared_ptr obj_shader; + + /** + * Shader program for rendering the final display. + */ + std::shared_ptr display_shader; + + /* Render passes */ + + /** + * Background pass: Mono-colored background object. + */ + std::shared_ptr background_pass; + + /** + * Field pass: Renders the cost, integration and flow fields. + */ + std::shared_ptr field_pass; + + /** + * Vector pass: Renders the steering vectors of a flow field. + */ + std::shared_ptr vector_pass; + + /** + * Grid pass: Renders a grid on top of the fields. + */ + std::shared_ptr grid_pass; + + /** + * Display pass: Draws the results of previous passes to the screen. + */ + std::shared_ptr display_pass; +}; + +} // namespace tests +} // namespace path +} // namespace openage diff --git a/libopenage/pathfinding/demo/demo_1.cpp b/libopenage/pathfinding/demo/demo_1.cpp new file mode 100644 index 0000000000..61df3e8995 --- /dev/null +++ b/libopenage/pathfinding/demo/demo_1.cpp @@ -0,0 +1,745 @@ +// Copyright 2024-2025 the openage authors. See copying.md for legal info. + +#include "demo_1.h" + +#include + +#include "pathfinding/cost_field.h" +#include "pathfinding/grid.h" +#include "pathfinding/path.h" +#include "pathfinding/pathfinder.h" +#include "pathfinding/portal.h" +#include "pathfinding/sector.h" +#include "util/timer.h" + +#include "renderer/gui/integration/public/gui_application_with_logger.h" +#include "renderer/opengl/window.h" +#include "renderer/render_pass.h" +#include "renderer/renderable.h" +#include "renderer/resources/shader_source.h" +#include "renderer/resources/texture_info.h" +#include "renderer/shader_program.h" +#include "renderer/window.h" + + +namespace openage::path::tests { + +void path_demo_1(const util::Path &path) { + auto grid = std::make_shared(0, util::Vector2s{4, 3}, 10); + + // Initialize the cost field for each sector. + for (auto §or : grid->get_sectors()) { + auto cost_field = sector->get_cost_field(); + + // 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 + 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); + } + 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); + } + portal_id += portals.size(); + } + } + } + + // Connect portals that can mutually reach each other + // within the same sector + for (auto sector : grid->get_sectors()) { + sector->connect_exits(); + + log::log(MSG(info) << "Sector " << sector->get_id() << " has " << sector->get_portals().size() << " portals."); + for (auto portal : sector->get_portals()) { + log::log(MSG(info) << " Portal " << portal->get_id() << ":"); + log::log(MSG(info) << " Connects sectors: " << sector->get_id() << " to " + << portal->get_exit_sector(sector->get_id())); + log::log(MSG(info) << " Entry start: " << portal->get_entry_start(sector->get_id())); + log::log(MSG(info) << " Entry end: " << portal->get_entry_end(sector->get_id())); + log::log(MSG(info) << " Connected portals: " << portal->get_connected(sector->get_id()).size()); + } + } + + // Create a pathfinder for searching paths on the grid + auto pathfinder = std::make_shared(); + pathfinder->add_grid(grid); + + // Add a timer to measure the pathfinding speed + util::Timer timer; + + // 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(); + + log::log(INFO << "Pathfinding took " << timer.getval() / 1000 << " us"); + + // Create a renderer to display the grid and path + auto qtapp = std::make_shared(); + + renderer::window_settings settings; + settings.width = 1024; + settings.height = 768; + auto window = std::make_shared("openage pathfinding test", settings); + 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(); + + double cell_size_x = static_cast(window_size[0]) / cell_count_x; + double cell_size_y = static_cast(window_size[1]) / cell_count_y; + + coord::tile_t grid_x = ev.position().x() / cell_size_x; + coord::tile_t grid_y = ev.position().y() / cell_size_y; + + if (ev.button() == Qt::RightButton) { // Set target cell + target = coord::tile{grid_x, grid_y}; + PathRequest new_path_request{ + grid->get_id(), + start, + target, + time::TIME_ZERO, + }; + + timer.reset(); + timer.start(); + path_result = pathfinder->get_path(new_path_request); + timer.stop(); + + log::log(INFO << "Pathfinding took " << timer.getval() / 1000 << " us"); + + if (path_result.status == PathResult::FOUND) { + // Create renderables for the waypoints of the path + render_manager->create_waypoint_tiles(path_result); + } + } + else if (ev.button() == Qt::LeftButton) { // Set start cell + start = coord::tile{grid_x, grid_y}; + PathRequest new_path_request{ + grid->get_id(), + start, + target, + time::TIME_ZERO, + }; + + timer.reset(); + timer.start(); + path_result = pathfinder->get_path(new_path_request); + timer.stop(); + + log::log(INFO << "Pathfinding took " << timer.getval() / 1000 << " us"); + + if (path_result.status == PathResult::FOUND) { + // Create renderables for the waypoints of the path + render_manager->create_waypoint_tiles(path_result); + } + } + } + }); + + // Create renderables for the waypoints of the path + render_manager->create_waypoint_tiles(path_result); + + // Run the renderer pss to draw the grid and path into a window + render_manager->run(); +} + + +RenderManager1::RenderManager1(const std::shared_ptr &app, + const std::shared_ptr &window, + const util::Path &path, + const std::shared_ptr &grid) : + path{path}, + grid{grid}, + app{app}, + window{window}, + renderer{window->make_renderer()} { + this->init_shaders(); + this->init_passes(); +} + +void RenderManager1::run() { + while (not this->window->should_close()) { + this->app->process_events(); + + this->renderer->render(this->background_pass); + this->renderer->render(this->field_pass); + this->renderer->render(this->waypoint_pass); + this->renderer->render(this->grid_pass); + this->renderer->render(this->display_pass); + + this->window->update(); + + this->renderer->check_error(); + } + this->window->close(); +} + +void RenderManager1::init_passes() { + auto size = this->window->get_size(); + + // Make a framebuffer for the background render pass to draw into + auto background_texture = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::rgba8)); + auto depth_texture = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::depth24)); + auto fbo = renderer->create_texture_target({background_texture, depth_texture}); + + // Background object for contrast between field and display + Eigen::Matrix4f id_matrix = Eigen::Matrix4f::Identity(); + auto background_unifs = obj_shader->new_uniform_input( + "color", + Eigen::Vector4f{1.0, 1.0, 1.0, 1.0}, + "model", + id_matrix, + "view", + id_matrix, + "proj", + id_matrix); + auto background = renderer->add_mesh_geometry(renderer::resources::MeshData::make_quad()); + renderer::Renderable background_obj{ + background_unifs, + background, + true, + true, + }; + this->background_pass = renderer->add_render_pass({background_obj}, fbo); + + // Make a framebuffer for the field render pass to draw into + auto field_texture = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::rgba8)); + auto depth_texture_2 = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::depth24)); + auto field_fbo = renderer->create_texture_target({field_texture, depth_texture_2}); + this->field_pass = renderer->add_render_pass({}, field_fbo); + this->create_impassible_tiles(this->grid); + this->create_portal_tiles(this->grid); + + // Make a framebuffer for the grid render passes to draw into + auto grid_texture = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::rgba8)); + auto depth_texture_3 = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::depth24)); + auto grid_fbo = renderer->create_texture_target({grid_texture, depth_texture_3}); + + // Make a framebuffer for the waypoint render pass to draw into + auto waypoint_texture = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::rgba8)); + auto depth_texture_4 = renderer->add_texture( + renderer::resources::Texture2dInfo(size[0], + size[1], + renderer::resources::pixel_format::depth24)); + auto waypoint_fbo = renderer->create_texture_target({waypoint_texture, depth_texture_4}); + this->waypoint_pass = renderer->add_render_pass({}, waypoint_fbo); + + // Create object for the grid + auto model = Eigen::Affine3f::Identity(); + model.prescale(Eigen::Vector3f{ + 2.0f / (this->grid->get_size()[0] * this->grid->get_sector_size()), + 2.0f / (this->grid->get_size()[1] * this->grid->get_sector_size()), + 1.0f}); + model.pretranslate(Eigen::Vector3f{-1.0f, -1.0f, 0.0f}); + auto grid_unifs = grid_shader->new_uniform_input( + "model", + model.matrix(), + "view", + id_matrix, + "proj", + id_matrix); + auto grid_mesh = this->get_grid_mesh(this->grid); + auto grid_geometry = renderer->add_mesh_geometry(grid_mesh); + renderer::Renderable grid_obj{ + grid_unifs, + grid_geometry, + true, + true, + }; + this->grid_pass = renderer->add_render_pass({grid_obj}, grid_fbo); + + // Make two objects that draw the results of the previous passes onto the screen + // in the display render pass + auto bg_texture_unif = display_shader->new_uniform_input("color_texture", background_texture); + auto quad = renderer->add_mesh_geometry(renderer::resources::MeshData::make_quad()); + renderer::Renderable bg_pass_obj{ + bg_texture_unif, + quad, + true, + true, + }; + auto field_texture_unif = display_shader->new_uniform_input("color_texture", field_texture); + renderer::Renderable field_pass_obj{ + field_texture_unif, + quad, + true, + true, + }; + auto waypoint_texture_unif = display_shader->new_uniform_input("color_texture", waypoint_texture); + renderer::Renderable waypoint_pass_obj{ + waypoint_texture_unif, + quad, + true, + true, + }; + auto grid_texture_unif = display_shader->new_uniform_input("color_texture", grid_texture); + renderer::Renderable grid_pass_obj{ + grid_texture_unif, + quad, + true, + true, + }; + + this->display_pass = renderer->add_render_pass( + {bg_pass_obj, field_pass_obj, waypoint_pass_obj, grid_pass_obj}, + renderer->get_display_target()); +} + +void RenderManager1::init_shaders() { + // Shader sources + auto shaderdir = this->path / "assets" / "test" / "shaders" / "pathfinding"; + + // Shader for rendering the grid + auto grid_vshader_file = shaderdir / "demo_1_grid.vert.glsl"; + auto grid_vshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::vertex, + grid_vshader_file); + + auto grid_fshader_file = shaderdir / "demo_1_grid.frag.glsl"; + auto grid_fshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::fragment, + grid_fshader_file); + + // Shader for 2D monocolored objects + auto obj_vshader_file = shaderdir / "demo_1_obj.vert.glsl"; + auto obj_vshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::vertex, + obj_vshader_file); + + auto obj_fshader_file = shaderdir / "demo_1_obj.frag.glsl"; + auto obj_fshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::fragment, + obj_fshader_file); + + // Shader for rendering to the display target + auto display_vshader_file = shaderdir / "demo_1_display.vert.glsl"; + auto display_vshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::vertex, + display_vshader_file); + + auto display_fshader_file = shaderdir / "demo_1_display.frag.glsl"; + auto display_fshader_src = renderer::resources::ShaderSource( + renderer::resources::shader_lang_t::glsl, + renderer::resources::shader_stage_t::fragment, + display_fshader_file); + + // Create the shaders + this->grid_shader = renderer->add_shader({grid_vshader_src, grid_fshader_src}); + this->obj_shader = renderer->add_shader({obj_vshader_src, obj_fshader_src}); + this->display_shader = renderer->add_shader({display_vshader_src, display_fshader_src}); +} + + +renderer::resources::MeshData RenderManager1::get_grid_mesh(const std::shared_ptr &grid) { + // increase by 1 in every dimension because to get the vertex length + // of each dimension + util::Vector2s size{ + grid->get_size()[0] * grid->get_sector_size() + 1, + grid->get_size()[1] * grid->get_sector_size() + 1}; + + // add vertices for the cells of the grid + std::vector verts{}; + auto vert_count = size[0] * size[1]; + verts.reserve(vert_count * 2); + for (int i = 0; i < (int)size[0]; ++i) { + for (int j = 0; j < (int)size[1]; ++j) { + verts.push_back(i); + verts.push_back(j); + } + } + + // split the grid into lines using an index array + std::vector idxs; + idxs.reserve((size[0] - 1) * (size[1] - 1) * 8); + // iterate over all tiles in the grid by columns, i.e. starting + // from the left corner to the bottom corner if you imagine it from + // the camera's point of view + for (size_t i = 0; i < size[0] - 1; ++i) { + for (size_t j = 0; j < size[1] - 1; ++j) { + // since we are working on tiles, we just draw a square using the 4 vertices + idxs.push_back(j + i * size[1]); // bottom left + idxs.push_back(j + 1 + i * size[1]); // bottom right + idxs.push_back(j + 1 + i * size[1]); // bottom right + idxs.push_back(j + size[1] + 1 + i * size[1]); // top right + idxs.push_back(j + size[1] + 1 + i * size[1]); // top right + idxs.push_back(j + size[1] + i * size[1]); // top left + idxs.push_back(j + size[1] + i * size[1]); // top left + idxs.push_back(j + i * size[1]); // bottom left + } + } + + renderer::resources::VertexInputInfo info{ + {renderer::resources::vertex_input_t::V2F32}, + renderer::resources::vertex_layout_t::AOS, + renderer::resources::vertex_primitive_t::LINES, + renderer::resources::index_t::U16}; + + auto const vert_data_size = verts.size() * sizeof(float); + std::vector vert_data(vert_data_size); + std::memcpy(vert_data.data(), verts.data(), vert_data_size); + + auto const idx_data_size = idxs.size() * sizeof(uint16_t); + std::vector idx_data(idx_data_size); + std::memcpy(idx_data.data(), idxs.data(), idx_data_size); + + return {std::move(vert_data), std::move(idx_data), info}; +} + +void RenderManager1::create_impassible_tiles(const std::shared_ptr &grid) { + auto width = grid->get_size()[0]; + auto height = grid->get_size()[1]; + auto sector_size = grid->get_sector_size(); + + float tile_offset_width = 2.0f / (width * sector_size); + float tile_offset_height = 2.0f / (height * sector_size); + + for (size_t sector_x = 0; sector_x < width; sector_x++) { + for (size_t sector_y = 0; sector_y < height; sector_y++) { + auto sector = grid->get_sector(sector_x, sector_y); + auto cost_field = sector->get_cost_field(); + for (size_t y = 0; y < sector_size; y++) { + for (size_t x = 0; x < sector_size; x++) { + auto cost = cost_field->get_cost(x, y); + if (cost == COST_IMPASSABLE) { + std::array tile_data{ + -1.0f + x * tile_offset_width + sector_size * sector_x * tile_offset_width, + 1.0f - y * tile_offset_height - sector_size * sector_y * tile_offset_height, + -1.0f + x * tile_offset_width + sector_size * sector_x * tile_offset_width, + 1.0f - (y + 1) * tile_offset_height - sector_size * sector_y * tile_offset_height, + -1.0f + (x + 1) * tile_offset_width + sector_size * sector_x * tile_offset_width, + 1.0f - y * tile_offset_height - sector_size * sector_y * tile_offset_height, + -1.0f + (x + 1) * tile_offset_width + sector_size * sector_x * tile_offset_width, + 1.0f - (y + 1) * tile_offset_height - sector_size * sector_y * tile_offset_height, + }; + renderer::resources::VertexInputInfo info{ + {renderer::resources::vertex_input_t::V2F32}, + renderer::resources::vertex_layout_t::AOS, + renderer::resources::vertex_primitive_t::TRIANGLE_STRIP}; + + auto const data_size = tile_data.size() * sizeof(float); + std::vector verts(data_size); + std::memcpy(verts.data(), tile_data.data(), data_size); + + auto tile_mesh = renderer::resources::MeshData(std::move(verts), info); + auto tile_geometry = renderer->add_mesh_geometry(tile_mesh); + + Eigen::Matrix4f id_matrix = Eigen::Matrix4f::Identity(); + auto tile_unifs = obj_shader->new_uniform_input( + "color", + Eigen::Vector4f{0.0, 0.0, 0.0, 1.0}, + "model", + id_matrix, + "view", + id_matrix, + "proj", + id_matrix); + auto tile_obj = renderer::Renderable{ + tile_unifs, + tile_geometry, + true, + true, + }; + this->field_pass->add_renderables({tile_obj}); + } + } + } + } + } +} + +void RenderManager1::create_portal_tiles(const std::shared_ptr &grid) { + auto width = grid->get_size()[0]; + auto height = grid->get_size()[1]; + auto sector_size = grid->get_sector_size(); + + float tile_offset_width = 2.0f / (width * sector_size); + float tile_offset_height = 2.0f / (height * sector_size); + + for (size_t sector_x = 0; sector_x < width; sector_x++) { + for (size_t sector_y = 0; sector_y < height; sector_y++) { + auto sector = grid->get_sector(sector_x, sector_y); + + for (auto &portal : sector->get_portals()) { + auto start = portal->get_entry_start(sector->get_id()); + auto end = portal->get_entry_end(sector->get_id()); + auto direction = portal->get_direction(); + + std::vector tiles; + if (direction == PortalDirection::NORTH_SOUTH) { + auto y = start.se; + for (auto x = start.ne; x <= end.ne; ++x) { + tiles.push_back(coord::tile(x, y)); + } + } + else { + auto x = start.ne; + for (auto y = start.se; y <= end.se; ++y) { + tiles.push_back(coord::tile(x, y)); + } + } + + for (auto tile : tiles) { + std::array tile_data{ + -1.0f + tile.ne * tile_offset_width + sector_size * sector_x * tile_offset_width, + 1.0f - tile.se * tile_offset_height - sector_size * sector_y * tile_offset_height, + -1.0f + tile.ne * tile_offset_width + sector_size * sector_x * tile_offset_width, + 1.0f - (tile.se + 1) * tile_offset_height - sector_size * sector_y * tile_offset_height, + -1.0f + (tile.ne + 1) * tile_offset_width + sector_size * sector_x * tile_offset_width, + 1.0f - tile.se * tile_offset_height - sector_size * sector_y * tile_offset_height, + -1.0f + (tile.ne + 1) * tile_offset_width + sector_size * sector_x * tile_offset_width, + 1.0f - (tile.se + 1) * tile_offset_height - sector_size * sector_y * tile_offset_height, + }; + renderer::resources::VertexInputInfo info{ + {renderer::resources::vertex_input_t::V2F32}, + renderer::resources::vertex_layout_t::AOS, + renderer::resources::vertex_primitive_t::TRIANGLE_STRIP}; + + auto const data_size = tile_data.size() * sizeof(float); + std::vector verts(data_size); + std::memcpy(verts.data(), tile_data.data(), data_size); + + auto tile_mesh = renderer::resources::MeshData(std::move(verts), info); + auto tile_geometry = renderer->add_mesh_geometry(tile_mesh); + + Eigen::Matrix4f id_matrix = Eigen::Matrix4f::Identity(); + auto tile_unifs = obj_shader->new_uniform_input( + "color", + Eigen::Vector4f{0.0, 0.0, 0.0, 0.3}, + "model", + id_matrix, + "view", + id_matrix, + "proj", + id_matrix); + auto tile_obj = renderer::Renderable{ + tile_unifs, + tile_geometry, + true, + true, + }; + this->field_pass->add_renderables({tile_obj}); + } + } + } + } +} + +void RenderManager1::create_waypoint_tiles(const Path &path) { + auto width = grid->get_size()[0]; + auto height = grid->get_size()[1]; + auto sector_size = grid->get_sector_size(); + + float tile_offset_width = 2.0f / (width * sector_size); + float tile_offset_height = 2.0f / (height * sector_size); + + Eigen::Matrix4f id_matrix = Eigen::Matrix4f::Identity(); + + this->waypoint_pass->clear_renderables(); + + // Draw in-between waypoints + for (size_t i = 1; i < path.waypoints.size() - 1; i++) { + auto tile = path.waypoints[i]; + std::array tile_data{ + -1.0f + tile.ne * tile_offset_width, + 1.0f - tile.se * tile_offset_height, + -1.0f + tile.ne * tile_offset_width, + 1.0f - (tile.se + 1) * tile_offset_height, + -1.0f + (tile.ne + 1) * tile_offset_width, + 1.0f - tile.se * tile_offset_height, + -1.0f + (tile.ne + 1) * tile_offset_width, + 1.0f - (tile.se + 1) * tile_offset_height, + }; + + renderer::resources::VertexInputInfo info{ + {renderer::resources::vertex_input_t::V2F32}, + renderer::resources::vertex_layout_t::AOS, + renderer::resources::vertex_primitive_t::TRIANGLE_STRIP}; + + auto const data_size = tile_data.size() * sizeof(float); + std::vector verts(data_size); + std::memcpy(verts.data(), tile_data.data(), data_size); + + auto tile_mesh = renderer::resources::MeshData(std::move(verts), info); + auto tile_geometry = renderer->add_mesh_geometry(tile_mesh); + + auto tile_unifs = obj_shader->new_uniform_input( + "color", + Eigen::Vector4f{0.0, 0.25, 1.0, 1.0}, + "model", + id_matrix, + "view", + id_matrix, + "proj", + id_matrix); + auto tile_obj = renderer::Renderable{ + tile_unifs, + tile_geometry, + true, + true, + }; + this->waypoint_pass->add_renderables({tile_obj}); + } + + // Draw start and end waypoints with different colors + auto start_tile = path.waypoints.front(); + std::array start_tile_data{ + -1.0f + start_tile.ne * tile_offset_width, + 1.0f - start_tile.se * tile_offset_height, + -1.0f + start_tile.ne * tile_offset_width, + 1.0f - (start_tile.se + 1) * tile_offset_height, + -1.0f + (start_tile.ne + 1) * tile_offset_width, + 1.0f - start_tile.se * tile_offset_height, + -1.0f + (start_tile.ne + 1) * tile_offset_width, + 1.0f - (start_tile.se + 1) * tile_offset_height, + }; + + renderer::resources::VertexInputInfo start_info{ + {renderer::resources::vertex_input_t::V2F32}, + renderer::resources::vertex_layout_t::AOS, + renderer::resources::vertex_primitive_t::TRIANGLE_STRIP}; + + auto const start_data_size = start_tile_data.size() * sizeof(float); + std::vector start_verts(start_data_size); + std::memcpy(start_verts.data(), start_tile_data.data(), start_data_size); + + auto start_tile_mesh = renderer::resources::MeshData(std::move(start_verts), start_info); + auto start_tile_geometry = renderer->add_mesh_geometry(start_tile_mesh); + auto start_tile_unifs = obj_shader->new_uniform_input( + "color", + Eigen::Vector4f{0.0, 0.5, 0.0, 1.0}, + "model", + id_matrix, + "view", + id_matrix, + "proj", + id_matrix); + auto start_tile_obj = renderer::Renderable{ + start_tile_unifs, + start_tile_geometry, + true, + true, + }; + + auto end_tile = path.waypoints.back(); + std::array end_tile_data{ + -1.0f + end_tile.ne * tile_offset_width, + 1.0f - end_tile.se * tile_offset_height, + -1.0f + end_tile.ne * tile_offset_width, + 1.0f - (end_tile.se + 1) * tile_offset_height, + -1.0f + (end_tile.ne + 1) * tile_offset_width, + 1.0f - end_tile.se * tile_offset_height, + -1.0f + (end_tile.ne + 1) * tile_offset_width, + 1.0f - (end_tile.se + 1) * tile_offset_height, + }; + + renderer::resources::VertexInputInfo end_info{ + {renderer::resources::vertex_input_t::V2F32}, + renderer::resources::vertex_layout_t::AOS, + renderer::resources::vertex_primitive_t::TRIANGLE_STRIP}; + + auto const end_data_size = end_tile_data.size() * sizeof(float); + std::vector end_verts(end_data_size); + std::memcpy(end_verts.data(), end_tile_data.data(), end_data_size); + + auto end_tile_mesh = renderer::resources::MeshData(std::move(end_verts), end_info); + auto end_tile_geometry = renderer->add_mesh_geometry(end_tile_mesh); + auto end_tile_unifs = obj_shader->new_uniform_input( + "color", + Eigen::Vector4f{1.0, 0.5, 0.0, 1.0}, + "model", + id_matrix, + "view", + id_matrix, + "proj", + id_matrix); + + auto end_tile_obj = renderer::Renderable{ + end_tile_unifs, + end_tile_geometry, + true, + true, + }; + + this->waypoint_pass->add_renderables({start_tile_obj, end_tile_obj}); +} + +} // namespace openage::path::tests diff --git a/libopenage/pathfinding/demo/demo_1.h b/libopenage/pathfinding/demo/demo_1.h new file mode 100644 index 0000000000..b2d3037eb8 --- /dev/null +++ b/libopenage/pathfinding/demo/demo_1.h @@ -0,0 +1,357 @@ +// Copyright 2024-2025 the openage authors. See copying.md for legal info. + +#pragma once + +#include "pathfinding/definitions.h" +#include "pathfinding/path.h" +#include "renderer/resources/mesh_data.h" +#include "util/path.h" + + +namespace openage { +namespace renderer { +class Renderer; +class RenderPass; +class ShaderProgram; +class Window; + +namespace gui { +class GuiApplicationWithLogger; +} // namespace gui + +} // namespace renderer + +namespace path { +class Grid; + +namespace tests { + + +/** + * Show the functionality of the high-level pathfinder: + * - Grids + * - Sectors + * - Portals + * + * Visualizes the pathfinding results using our rendering backend. + * + * @param path Path to the project rootdir. + */ +void path_demo_1(const util::Path &path); + + +/** + * Manages the graphical display of the pathfinding demo. + */ +class RenderManager1 { +public: + /** + * Create a new render manager. + * + * @param app GUI application. + * @param window Window to render to. + * @param path Path to the project rootdir. + */ + RenderManager1(const std::shared_ptr &app, + const std::shared_ptr &window, + const util::Path &path, + const std::shared_ptr &grid); + ~RenderManager1() = default; + + /** + * Run the render loop. + */ + void run(); + + /** + * Create renderables for the waypoint tiles of a path. + * + * @param path Path object. + */ + void create_waypoint_tiles(const Path &path); + +private: + /** + * Load the shader sources for the demo and create the shader programs. + */ + void init_shaders(); + + /** + * Create the following render passes for the demo: + * - Background pass: Mono-colored background object. + * - Field pass; Renders the cost, integration and flow fields. + * - Grid pass: Renders a grid on top of the cost field. + * - Display pass: Draws the results of previous passes to the screen. + */ + void init_passes(); + + /** + * Create a mesh for the grid. + * + * @param grid Pathing grid. + * + * @return Mesh data for the grid. + */ + static renderer::resources::MeshData get_grid_mesh(const std::shared_ptr &grid); + + /** + * Create renderables for the impassible tiles in the grid. + * + * @param grid Pathing grid. + */ + void create_impassible_tiles(const std::shared_ptr &grid); + + /** + * Create renderables for the portal tiles in the grid. + * + * @param grid Pathing grid. + */ + void create_portal_tiles(const std::shared_ptr &grid); + + /** + * Path to the project rootdir. + */ + const util::Path &path; + + /** + * Pathing grid of the demo. + */ + std::shared_ptr grid; + + /* Renderer objects */ + + /** + * Qt GUI application. + */ + std::shared_ptr app; + + /** + * openage window to render to. + */ + std::shared_ptr window; + + /** + * openage renderer instance. + */ + std::shared_ptr renderer; + + /* Shader programs */ + + /** + * Shader program for rendering a grid. + */ + std::shared_ptr grid_shader; + + /** + * Shader program for rendering 2D mono-colored objects. + */ + std::shared_ptr obj_shader; + + /** + * Shader program for rendering the final display. + */ + std::shared_ptr display_shader; + + /* Render passes */ + + /** + * Background pass: Mono-colored background object. + */ + std::shared_ptr background_pass; + + /** + * Field pass: Renders the cost field. + */ + std::shared_ptr field_pass; + + /** + * Grid pass: Renders a grid on top of the cost field. + */ + std::shared_ptr grid_pass; + + /** + * Waypoint pass: Renders the path and its waypoints. + */ + std::shared_ptr waypoint_pass; + + /** + * Display pass: Draws the results of previous passes to the screen. + */ + std::shared_ptr display_pass; +}; + +// 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 = { + { + // clang-format off + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 255, 255, 255, 255, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 255, 255, 255, 255, 1, 1, + 1, 1, 1, 255, 255, 255, 255, 255, 1, 1, + 1, 1, 255, 255, 255, 255, 255, 1, 1, 1, + 1, 1, 255, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 255, 1, 1, 1, 1, 1, 1, 1, + // clang-format on + }, + { + // clang-format off + 1, 1, 255, 255, 255, 255, 255, 255, 255, 255, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 255, 1, 1, 255, 1, 1, 1, + 1, 1, 1, 255, 1, 255, 255, 1, 1, 1, + 1, 1, 1, 255, 255, 255, 255, 255, 255, 255, + 1, 1, 1, 1, 255, 255, 255, 1, 1, 1, + 1, 1, 1, 1, 255, 255, 1, 1, 1, 1, + 1, 1, 1, 255, 1, 1, 1, 1, 1, 1, + // clang-format on + }, + { + // clang-format off + 255, 255, 1, 1, 1, 1, 1, 255, 255, 255, + 1, 1, 1, 1, 1, 1, 1, 1, 255, 255, + 1, 1, 1, 1, 1, 1, 1, 1, 255, 255, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 255, 255, 255, 1, 1, 1, + 1, 1, 1, 1, 1, 255, 255, 255, 255, 255, + 1, 1, 1, 1, 255, 255, 255, 255, 255, 1, + 1, 1, 1, 1, 255, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 255, 1, 1, 1, 1, 255, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + // clang-format on + }, + { + // clang-format off + 255, 255, 1, 1, 1, 1, 1, 1, 1, 1, + 255, 255, 1, 1, 1, 1, 1, 1, 1, 1, + 255, 255, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 255, 1, 1, 1, 1, 1, 1, + 1, 1, 255, 255, 1, 1, 1, 1, 1, 1, + 255, 255, 255, 255, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + // clang-format on + }, + { + // clang-format off + 1, 1, 255, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 255, 1, 1, 1, 1, 1, 255, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 255, 1, 1, 1, 1, 1, + 1, 1, 1, 255, 255, 1, 1, 1, 1, 1, + 1, 1, 255, 1, 255, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 255, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 255, 255, 255, 255, 255, 255, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 255, 255, 255, + // clang-format on + }, + { + // clang-format off + 1, 1, 1, 255, 1, 1, 1, 1, 1, 255, + 1, 1, 1, 255, 1, 255, 255, 255, 255, 255, + 1, 1, 1, 255, 1, 1, 1, 1, 255, 255, + 1, 1, 1, 255, 1, 1, 1, 1, 255, 255, + 1, 255, 255, 1, 1, 1, 1, 1, 255, 255, + 1, 1, 255, 1, 1, 255, 1, 1, 1, 1, + 1, 1, 255, 1, 1, 255, 1, 1, 1, 1, + 255, 255, 255, 255, 255, 255, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 255, 1, 1, 1, 1, 1, 1, 1, 1, 1, + // clang-format on + }, + { + // clang-format off + 255, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 255, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 255, 1, 1, 1, 1, 1, 1, 1, 255, 255, + 255, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 255, 1, 1, 1, 1, 1, 255, 1, 1, 1, + 255, 1, 1, 1, 1, 1, 255, 1, 1, 1, + 255, 1, 1, 1, 1, 1, 255, 1, 1, 1, + 255, 1, 1, 1, 1, 1, 255, 255, 255, 255, + 255, 1, 1, 1, 255, 255, 255, 255, 255, 1, + 255, 1, 1, 1, 1, 1, 1, 1, 1, 1, + // clang-format on + }, + { + // clang-format off + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 255, 255, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 255, 255, 255, 255, 255, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 255, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 255, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + // clang-format on + }, + { + // clang-format off + 1, 1, 255, 1, 1, 1, 1, 255, 255, 255, + 1, 1, 255, 1, 1, 1, 255, 255, 1, 1, + 1, 1, 1, 1, 1, 255, 255, 255, 1, 1, + 1, 1, 1, 1, 255, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 255, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 255, + 1, 1, 1, 1, 1, 1, 1, 1, 255, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + // clang-format on + }, + { + // clang-format off + 255, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 255, 1, 1, 1, 1, 1, 1, + 1, 1, 255, 255, 255, 255, 255, 255, 1, 1, + 1, 255, 255, 255, 1, 1, 1, 1, 1, 1, + 255, 255, 255, 255, 1, 1, 1, 1, 1, 1, + 255, 1, 1, 1, 1, 1, 1, 1, 1, 255, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + // clang-format on + }, + { + // clang-format off + 255, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 255, 255, 255, 255, 255, 1, 1, + 1, 1, 255, 255, 1, 1, 1, 255, 1, 1, + 1, 255, 255, 1, 1, 1, 1, 1, 1, 1, + 255, 255, 1, 1, 1, 1, 1, 1, 1, 1, + 255, 255, 1, 1, 1, 255, 255, 255, 1, 1, + 1, 1, 1, 1, 1, 1, 255, 1, 1, 1, + 1, 1, 1, 255, 255, 255, 255, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + // clang-format on + }, + { + // clang-format off + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + // clang-format on + }}; + +} // namespace tests +} // namespace path +} // namespace openage diff --git a/libopenage/pathfinding/demo/tests.cpp b/libopenage/pathfinding/demo/tests.cpp new file mode 100644 index 0000000000..633dddb621 --- /dev/null +++ b/libopenage/pathfinding/demo/tests.cpp @@ -0,0 +1,29 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "tests.h" + +#include "log/log.h" + +#include "pathfinding/demo/demo_0.h" +#include "pathfinding/demo/demo_1.h" + + +namespace openage::path::tests { + +void path_demo(int demo_id, const util::Path &path) { + switch (demo_id) { + case 0: + path_demo_0(path); + break; + + case 1: + path_demo_1(path); + break; + + default: + log::log(MSG(err) << "Unknown pathfinding demo requested: " << demo_id << "."); + break; + } +} + +} // namespace openage::path::tests diff --git a/libopenage/pathfinding/demo/tests.h b/libopenage/pathfinding/demo/tests.h new file mode 100644 index 0000000000..2f411f6bf1 --- /dev/null +++ b/libopenage/pathfinding/demo/tests.h @@ -0,0 +1,20 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include "../../util/compiler.h" +// pxd: from libopenage.util.path cimport Path + + +namespace openage { +namespace util { +class Path; +} // namespace util + +namespace path::tests { + +// pxd: void path_demo(int demo_id, Path path) except + +OAAPI void path_demo(int demo_id, const util::Path &path); + +} // namespace path::tests +} // namespace openage 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/flow_field.cpp b/libopenage/pathfinding/flow_field.cpp new file mode 100644 index 0000000000..e582345b48 --- /dev/null +++ b/libopenage/pathfinding/flow_field.cpp @@ -0,0 +1,283 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "flow_field.h" + +#include + +#include "error/error.h" +#include "log/log.h" + +#include "coord/tile.h" +#include "pathfinding/integration_field.h" +#include "pathfinding/portal.h" + + +namespace openage::path { + +FlowField::FlowField(size_t size) : + size{size}, + cells(this->size * this->size, FLOW_INIT) { + log::log(DBG << "Created flow field with size " << this->size << "x" << this->size); +} + +FlowField::FlowField(const std::shared_ptr &integration_field) : + size{integration_field->get_size()}, + cells(this->size * this->size, FLOW_INIT) { + this->build(integration_field); +} + +size_t FlowField::get_size() const { + return this->size; +} + +flow_t FlowField::get_cell(const coord::tile_delta &pos) const { + return this->cells.at(pos.ne + pos.se * this->size); +} + +flow_t FlowField::get_cell(size_t x, size_t y) const { + return this->cells.at(x + y * this->size); +} + +flow_t FlowField::get_cell(size_t idx) const { + return this->cells.at(idx); +} + +flow_dir_t FlowField::get_dir(const coord::tile_delta &pos) const { + return static_cast(this->get_cell(pos) & FLOW_DIR_MASK); +} + +flow_dir_t FlowField::get_dir(size_t x, size_t y) const { + return static_cast(this->get_cell(x, y) & FLOW_DIR_MASK); +} + +flow_dir_t FlowField::get_dir(size_t idx) const { + return static_cast(this->get_cell(idx) & FLOW_DIR_MASK); +} + +void FlowField::build(const std::shared_ptr &integration_field) { + ENSURE(integration_field->get_size() == this->get_size(), + "integration field size " + << integration_field->get_size() << "x" << integration_field->get_size() + << " does not match flow field size " + << this->get_size() << "x" << this->get_size()); + + auto &integrate_cells = integration_field->get_cells(); + auto &flow_cells = this->cells; + + for (size_t y = 0; y < this->size; ++y) { + for (size_t x = 0; x < this->size; ++x) { + size_t idx = y * this->size + x; + + const auto &integrate_cell = integrate_cells[idx]; + auto &flow_cell = flow_cells[idx]; + + if (integrate_cell.cost == INTEGRATED_COST_UNREACHABLE) { + // Cell cannot be used as path + continue; + } + + flow_t transfer_flags = integrate_cell.flags & FLOW_FLAGS_MASK; + flow_cell |= transfer_flags; + + if (flow_cell & FLOW_TARGET_MASK) { + // target cells are pathable + flow_cell |= FLOW_PATHABLE_MASK; + + // they also have a preset flow direction so we can skip here + continue; + } + + // Store which of the non-diagonal directions are unreachable. + // north == 0x01, east == 0x02, south == 0x04, west == 0x08 + uint8_t directions_unreachable = 0x00; + + // Find the neighbor with the smallest cost. + flow_dir_t direction = static_cast(flow_cell & FLOW_DIR_MASK); + auto smallest_cost = INTEGRATED_COST_UNREACHABLE; + + // Cardinal directions + if (y > 0) { + auto cost = integrate_cells[idx - this->size].cost; + if (cost == INTEGRATED_COST_UNREACHABLE) { + directions_unreachable |= 0x01; + } + else if (cost < smallest_cost) { + smallest_cost = cost; + direction = flow_dir_t::NORTH; + } + } + if (x < this->size - 1) { + auto cost = integrate_cells[idx + 1].cost; + if (cost == INTEGRATED_COST_UNREACHABLE) { + directions_unreachable |= 0x02; + } + else if (cost < smallest_cost) { + smallest_cost = cost; + direction = flow_dir_t::EAST; + } + } + if (y < this->size - 1) { + auto cost = integrate_cells[idx + this->size].cost; + if (cost == INTEGRATED_COST_UNREACHABLE) { + directions_unreachable |= 0x04; + } + else if (cost < smallest_cost) { + smallest_cost = cost; + direction = flow_dir_t::SOUTH; + } + } + if (x > 0) { + auto cost = integrate_cells[idx - 1].cost; + if (cost == INTEGRATED_COST_UNREACHABLE) { + directions_unreachable |= 0x08; + } + else if (cost < smallest_cost) { + smallest_cost = cost; + direction = flow_dir_t::WEST; + } + } + + // Diagonal directions + if (x < this->size - 1 and y > 0 + and not(directions_unreachable & 0x01 and directions_unreachable & 0x02)) { + auto cost = integrate_cells[idx - this->size + 1].cost; + if (cost < smallest_cost) { + smallest_cost = cost; + direction = flow_dir_t::NORTH_EAST; + } + } + if (x < this->size - 1 and y < this->size - 1 + and not(directions_unreachable & 0x02 and directions_unreachable & 0x04)) { + auto cost = integrate_cells[idx + this->size + 1].cost; + if (cost < smallest_cost) { + smallest_cost = cost; + direction = flow_dir_t::SOUTH_EAST; + } + } + if (x > 0 and y < this->size - 1 + and not(directions_unreachable & 0x04 and directions_unreachable & 0x08)) { + auto cost = integrate_cells[idx + this->size - 1].cost; + if (cost < smallest_cost) { + smallest_cost = cost; + direction = flow_dir_t::SOUTH_WEST; + } + } + if (x > 0 and y > 0 + and not(directions_unreachable & 0x01 and directions_unreachable & 0x08)) { + auto cost = integrate_cells[idx - this->size - 1].cost; + if (cost < smallest_cost) { + smallest_cost = cost; + direction = flow_dir_t::NORTH_WEST; + } + } + + // Set the flow field cell to pathable. + flow_cell |= FLOW_PATHABLE_MASK; + + // Set the flow field cell to the direction of the smallest cost. + flow_cell |= static_cast(direction); + } + } +} + +void FlowField::build(const std::shared_ptr &integration_field, + const std::shared_ptr & /* other */, + sector_id_t other_sector_id, + const std::shared_ptr &portal) { + ENSURE(integration_field->get_size() == this->get_size(), + "integration field size " + << integration_field->get_size() << "x" << integration_field->get_size() + << " does not match flow field size " + << this->get_size() << "x" << this->get_size()); + + auto &flow_cells = this->cells; + auto direction = portal->get_direction(); + + // portal entry and exit cell coordinates + auto entry_start = portal->get_entry_start(other_sector_id); + auto exit_start = portal->get_exit_start(other_sector_id); + auto exit_end = portal->get_exit_end(other_sector_id); + + // TODO: Compare integration values from other side of portal + // auto &integrate_cells = integration_field->get_cells(); + + // set the direction for the flow field cells that are part of the portal + if (direction == PortalDirection::NORTH_SOUTH) { + bool other_is_north = entry_start.se > exit_start.se; + if (other_is_north) { + auto y = exit_start.se; + for (auto x = exit_start.ne; x <= exit_end.ne; ++x) { + auto idx = y * this->size + x; + flow_cells[idx] = flow_cells[idx] | FLOW_PATHABLE_MASK; + flow_cells[idx] = flow_cells[idx] | static_cast(flow_dir_t::NORTH); + } + } + else { + auto y = exit_start.se; + for (auto x = exit_start.ne; x <= exit_end.ne; ++x) { + auto idx = y * this->size + x; + flow_cells[idx] = flow_cells[idx] | FLOW_PATHABLE_MASK; + flow_cells[idx] = flow_cells[idx] | static_cast(flow_dir_t::SOUTH); + } + } + } + else if (direction == PortalDirection::EAST_WEST) { + bool other_is_east = entry_start.ne < exit_start.ne; + if (other_is_east) { + auto x = exit_start.ne; + for (auto y = exit_start.se; y <= exit_end.se; ++y) { + auto idx = y * this->size + x; + flow_cells[idx] = flow_cells[idx] | FLOW_PATHABLE_MASK; + flow_cells[idx] = flow_cells[idx] | static_cast(flow_dir_t::EAST); + } + } + else { + auto x = exit_start.ne; + for (auto y = exit_start.se; y <= exit_end.se; ++y) { + auto idx = y * this->size + x; + flow_cells[idx] = flow_cells[idx] | FLOW_PATHABLE_MASK; + flow_cells[idx] = flow_cells[idx] | static_cast(flow_dir_t::WEST); + } + } + } + else { + throw Error(ERR << "Invalid portal direction: " << static_cast(direction)); + } + + this->build(integration_field); +} + +const std::vector &FlowField::get_cells() const { + return this->cells; +} + +void FlowField::reset() { + std::fill(this->cells.begin(), this->cells.end(), FLOW_INIT); + + log::log(DBG << "Flow field has been reset"); +} + +void FlowField::reset_dynamic_flags() { + flow_t mask = 0xFF & ~(FLOW_LOS_MASK); + for (flow_t &cell : this->cells) { + cell = cell & mask; + } + + log::log(DBG << "Flow field dynamic flags have been reset"); +} + +void FlowField::transfer_dynamic_flags(const std::shared_ptr &integration_field) { + auto &integrate_cells = integration_field->get_cells(); + auto &flow_cells = this->cells; + + for (size_t idx = 0; idx < integrate_cells.size(); ++idx) { + if (integrate_cells[idx].flags & INTEGRATE_LOS_MASK) { + // Cell is in line of sight + flow_cells[idx] |= FLOW_LOS_MASK; + } + } + + log::log(DBG << "Flow field dynamic flags have been transferred"); +} + +} // namespace openage::path diff --git a/libopenage/pathfinding/flow_field.h b/libopenage/pathfinding/flow_field.h new file mode 100644 index 0000000000..5dc91fed96 --- /dev/null +++ b/libopenage/pathfinding/flow_field.h @@ -0,0 +1,173 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include +#include + +#include "pathfinding/definitions.h" +#include "pathfinding/types.h" + + +namespace openage { +namespace coord { +struct tile_delta; +} // namespace coord + +namespace path { +class IntegrationField; +class Portal; + +class FlowField { +public: + /** + * Create a square flow field with a specified size. + * + * @param size Side length of the field. + */ + FlowField(size_t size); + + /** + * Create a flow field from an existing integration field. + * + * @param integration_field Integration field. + */ + FlowField(const std::shared_ptr &integration_field); + + /** + * Get the size of the flow field. + * + * @return Size of the flow field. + */ + size_t get_size() const; + + /** + * Get the flow field value at a specified position. + * + * @param pos Coordinates of the cell (relative to field origin). + * + * @return Flowfield value at the specified position. + */ + flow_t get_cell(const coord::tile_delta &pos) const; + + /** + * Get the flow field value at a specified position. + * + * @param x X-coordinate of the cell. + * @param y Y-coordinate of the cell. + * + * @return Flowfield value at the specified position. + */ + flow_t get_cell(size_t x, size_t y) const; + + /** + * Get the flow field direction at a specified position. + * + * @param idx Index of the cell. + * + * @return Flowfield value at the specified position. + */ + flow_t get_cell(size_t idx) const; + + /** + * Get the flow field direction at a specified position. + * + * @param pos Coordinates of the cell (relative to field origin). + * + * @return Flowfield direction at the specified position. + */ + flow_dir_t get_dir(const coord::tile_delta &pos) const; + + /** + * Get the flow field direction at a specified position. + * + * @param x X-coordinate of the cell. + * @param y Y-coordinate of the cell. + * + * @return Flowfield direction at the specified position. + */ + flow_dir_t get_dir(size_t x, size_t y) const; + + /** + * Get the flow field direction at a specified position. + * + * @param idx Index of the cell. + * + * @return Flowfield direction at the specified position. + */ + flow_dir_t get_dir(size_t idx) const; + + /** + * Build the flow field. + * + * @param integration_field Integration field. + */ + void build(const std::shared_ptr &integration_field); + + /** + * Build the flow field for a portal. + * + * @param integration_field Integration field. + * @param other Integration field of the other sector. + * @param other_sector_id Sector ID of the other field. + * @param portal Portal connecting the two fields. + */ + void build(const std::shared_ptr &integration_field, + const std::shared_ptr &other, + sector_id_t other_sector_id, + const std::shared_ptr &portal); + + /** + * Get the flow field values. + * + * @return Flow field values. + */ + const std::vector &get_cells() const; + + /** + * Reset the flow field values for rebuilding the field. + */ + void reset(); + + /** + * Reset all flags that are dependent on the path target location. + * + * These flags should be removed when the field is cached and reused for + * other targets. + * + * Relevant flags are: + * - FLOW_LOS_MASK + * - FLOW_WAVEFRONT_BLOCKED_MASK + */ + void reset_dynamic_flags(); + + /** + * Transfer dynamic flags from an integration field. + * + * These flags should be transferred when the field is copied from cache. + * Flow field directions are not altered. + * + * Relevant flags are: + * - FLOW_LOS_MASK + * - FLOW_WAVEFRONT_BLOCKED_MASK + * + * @param integration_field Integration field. + */ + void transfer_dynamic_flags(const std::shared_ptr &integration_field); + +private: + /** + * Side length of the field. + */ + size_t size; + + /** + * Flow field cells. + */ + std::vector cells; +}; + +} // namespace path +} // namespace openage diff --git a/libopenage/pathfinding/grid.cpp b/libopenage/pathfinding/grid.cpp new file mode 100644 index 0000000000..45427b009d --- /dev/null +++ b/libopenage/pathfinding/grid.cpp @@ -0,0 +1,124 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "grid.h" + +#include "error/error.h" + +#include "coord/chunk.h" +#include "pathfinding/cost_field.h" +#include "pathfinding/sector.h" + + +namespace openage::path { + +Grid::Grid(grid_id_t id, + const util::Vector2s &size, + size_t sector_size) : + id{id}, + size{size}, + sector_size{sector_size} { + for (size_t y = 0; y < size[1]; y++) { + for (size_t x = 0; x < size[0]; x++) { + this->sectors.push_back( + std::make_shared(x + y * this->size[0], + coord::chunk(x, y), + sector_size)); + } + } +} + +Grid::Grid(grid_id_t id, + const util::Vector2s &size, + std::vector> &§ors) : + id{id}, + size{size}, + sectors{std::move(sectors)} { + ENSURE(this->sectors.size() == size[0] * size[1], + "Grid has size " << size[0] << "x" << size[1] << " (" << size[0] * size[1] << " sectors), " + << "but only " << this->sectors.size() << " sectors were provided"); + + this->sector_size = sectors.at(0)->get_cost_field()->get_size(); +} + +grid_id_t Grid::get_id() const { + return this->id; +} + +const util::Vector2s &Grid::get_size() const { + return this->size; +} + +size_t Grid::get_sector_size() const { + return this->sector_size; +} + +const std::shared_ptr &Grid::get_sector(size_t x, size_t y) { + return this->sectors.at(x + y * this->size[0]); +} + +const std::shared_ptr &Grid::get_sector(sector_id_t id) const { + return this->sectors.at(id); +} + +const std::vector> &Grid::get_sectors() const { + return this->sectors; +} + +void Grid::init_portals() { + // Create portals between neighboring sectors. + portal_id_t portal_id = 0; + for (size_t y = 0; y < this->size[1]; y++) { + for (size_t x = 0; x < this->size[0]; x++) { + auto sector = this->get_sector(x, y); + + if (x < this->size[0] - 1) { + auto neighbor = this->get_sector(x + 1, y); + auto portals = sector->find_portals(neighbor, PortalDirection::EAST_WEST, portal_id); + for (auto &portal : portals) { + sector->add_portal(portal); + neighbor->add_portal(portal); + } + portal_id += portals.size(); + } + if (y < this->size[1] - 1) { + auto neighbor = this->get_sector(x, y + 1); + auto portals = sector->find_portals(neighbor, PortalDirection::NORTH_SOUTH, portal_id); + for (auto &portal : portals) { + sector->add_portal(portal); + neighbor->add_portal(portal); + } + portal_id += portals.size(); + } + } + } + + // Connect mutually reachable exits of sectors. + for (auto §or : this->sectors) { + sector->connect_exits(); + } +} + +const nodemap_t &Grid::get_portal_map() { + return portal_nodes; +} + +void Grid::init_portal_nodes() { + // create portal_nodes + for (auto §or : this->sectors) { + for (auto &portal : sector->get_portals()) { + if (!this->portal_nodes.contains(portal->get_id())) { + auto portal_node = std::make_shared(portal); + portal_node->node_sector_0 = sector->get_id(); + portal_node->node_sector_1 = portal_node->portal->get_exit_sector(sector->get_id()); + this->portal_nodes[portal->get_id()] = portal_node; + } + } + } + + // init portal_node exits + for (auto &[id, node] : this->portal_nodes) { + node->init_exits(this->portal_nodes); + } +} + +} // namespace openage::path diff --git a/libopenage/pathfinding/grid.h b/libopenage/pathfinding/grid.h new file mode 100644 index 0000000000..314d107a2e --- /dev/null +++ b/libopenage/pathfinding/grid.h @@ -0,0 +1,139 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include +#include + +#include "pathfinding/pathfinder.h" +#include "pathfinding/types.h" +#include "util/vector.h" + + +namespace openage::path { +class Sector; + +/** + * Grid for flow field pathfinding. + */ +class Grid { +public: + /** + * Create a new empty grid of width x height sectors with a specified size. + * + * @param id ID of the grid. + * @param size Size of the grid in sectors (width x height). + * @param sector_size Side length of each sector. + */ + Grid(grid_id_t id, + const util::Vector2s &size, + size_t sector_size); + + /** + * Create a grid of width x height sectors from a list of existing sectors. + * + * @param id ID of the grid. + * @param size Size of the grid in sectors (width x height). + * @param sectors Existing sectors. + */ + Grid(grid_id_t id, + const util::Vector2s &size, + std::vector> &§ors); + + /** + * Get the ID of the grid. + * + * @return ID of the grid. + */ + grid_id_t get_id() const; + + /** + * Get the size of the grid (in number of sectors). + * + * @return Size of the grid (in number of sectors) (width x height). + */ + const util::Vector2s &get_size() const; + + /** + * Get the side length of the sectors on the grid (in number of cells). + * + * @return Sector side length (in number of cells). + */ + size_t get_sector_size() const; + + /** + * Get the sector at a specified position. + * + * @param x X coordinate. + * @param y Y coordinate. + * + * @return Sector at the specified position. + */ + const std::shared_ptr &get_sector(size_t x, size_t y); + + /** + * Get the sector with a specified ID + * + * @param id ID of the sector. + * + * @return Sector with the specified ID. + */ + const std::shared_ptr &get_sector(sector_id_t id) const; + + /** + * Get the sectors of the grid. + * + * @return Sectors of the grid. + */ + const std::vector> &get_sectors() const; + + /** + * Initialize the portals of the sectors on the grid. + * + * This should be called after all sectors' cost fields have been initialized. + */ + void init_portals(); + + /** + * returns map of portal ids to portal nodes + */ + const nodemap_t &get_portal_map(); + + /** + * Initialize the portal nodes of the grid with neigbouring nodes and distance costs. + */ + void init_portal_nodes(); + +private: + /** + * ID of the grid. + */ + grid_id_t id; + + /** + * Size of the grid (width x height). + */ + util::Vector2s size; + + /** + * Side length of the grid sectors. + */ + size_t sector_size; + + /** + * Sectors of the grid. + */ + std::vector> sectors; + + /** + * maps portal_ids to portal nodes, which store their neigbouring nodes and associated distance costs + * for pathfinding + */ + + nodemap_t portal_nodes; +}; + + +} // namespace openage::path diff --git a/libopenage/pathfinding/heuristics.cpp b/libopenage/pathfinding/heuristics.cpp deleted file mode 100644 index 9a1a1213dc..0000000000 --- a/libopenage/pathfinding/heuristics.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2014-2018 the openage authors. See copying.md for legal info. - -#include "heuristics.h" - -#include -#include - - -namespace openage { -namespace path { - -cost_t manhattan_cost(const coord::phys3 &start, const coord::phys3 &end) { - cost_t dx = std::abs(start.ne - end.ne).to_float(); - cost_t dy = std::abs(start.se - end.se).to_float(); - return dx + dy; -} - -cost_t chebyshev_cost(const coord::phys3 &start, const coord::phys3 &end) { - cost_t dx = std::abs(start.ne - end.ne).to_float(); - cost_t dy = std::abs(start.se - end.se).to_float(); - return std::max(dx, dy); -} - -cost_t euclidean_cost(const coord::phys3 &start, const coord::phys3 &end) { - return (end - start).length(); -} - -cost_t euclidean_squared_cost(const coord::phys3 &start, const coord::phys3 &end) { - cost_t dx = (start.ne - end.ne).to_float(); - cost_t dy = (start.se - end.se).to_float(); - return dx * dx + dy * dy; -} - -}} // openage::path diff --git a/libopenage/pathfinding/integration_field.cpp b/libopenage/pathfinding/integration_field.cpp new file mode 100644 index 0000000000..26134506b8 --- /dev/null +++ b/libopenage/pathfinding/integration_field.cpp @@ -0,0 +1,719 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "integration_field.h" + +#include + +#include "error/error.h" +#include "log/log.h" + +#include "coord/tile.h" +#include "pathfinding/cost_field.h" +#include "pathfinding/definitions.h" +#include "pathfinding/portal.h" + + +namespace openage::path { + +IntegrationField::IntegrationField(size_t size) : + size{size}, + cells(this->size * this->size, INTEGRATE_INIT) { + log::log(DBG << "Created integration field with size " << this->size << "x" << this->size); +} + +size_t IntegrationField::get_size() const { + return this->size; +} + +const integrated_t &IntegrationField::get_cell(const coord::tile_delta &pos) const { + return this->cells.at(pos.ne + pos.se * this->size); +} + +const integrated_t &IntegrationField::get_cell(size_t x, size_t y) const { + return this->cells.at(x + y * this->size); +} + +const integrated_t &IntegrationField::get_cell(size_t idx) const { + return this->cells.at(idx); +} + +std::vector IntegrationField::integrate_los(const std::shared_ptr &cost_field, + const coord::tile_delta &target) { + ENSURE(cost_field->get_size() == this->get_size(), + "cost field size " + << cost_field->get_size() << "x" << cost_field->get_size() + << " does not match integration field size " + << this->get_size() << "x" << this->get_size()); + + ENSURE(target.ne >= 0 + and target.se >= 0 + and target.ne < static_cast(this->size) + and target.se < static_cast(this->size), + "target cell (" << target.ne << ", " << target.se << ") " + << "is out of bounds for integration field of size " + << this->size << "x" << this->size); + + std::vector start_cells; + integrated_cost_t start_cost = INTEGRATED_COST_START; + + // Target cell index + auto target_idx = target.ne + target.se * this->size; + + this->cells[target_idx].cost = start_cost; + this->cells[target_idx].flags |= INTEGRATE_TARGET_MASK; + + if (cost_field->get_cost(target_idx) > COST_MIN) { + // Do a preliminary LOS integration wave for targets that have cost > min cost + // This avoids the bresenham's line algorithm calculations + // (which wouldn't return accurate results for blocker == target) + // and makes sure that sorrounding cells that are min cost are considered + // in line-of-sight. + + this->cells[target_idx].flags |= INTEGRATE_FOUND_MASK; + this->cells[target_idx].flags |= INTEGRATE_LOS_MASK; + + // Add neighbors to current wave + if (target.se > 0) { + start_cells.push_back(target_idx - this->size); + } + if (target.ne > 0) { + start_cells.push_back(target_idx - 1); + } + if (target.se < static_cast(this->size - 1)) { + start_cells.push_back(target_idx + this->size); + } + if (target.ne < static_cast(this->size - 1)) { + start_cells.push_back(target_idx + 1); + } + + // Increment wave cost as we technically handled the first wave in this block + start_cost += 1; + } + else { + // Move outwards from the target cell, updating the integration field + start_cells.push_back(target_idx); + } + + return this->integrate_los(cost_field, target, start_cost, std::move(start_cells)); +} + +std::vector IntegrationField::integrate_los(const std::shared_ptr &cost_field, + const std::shared_ptr &other, + sector_id_t other_sector_id, + const std::shared_ptr &portal, + const coord::tile_delta &target) { + ENSURE(cost_field->get_size() == this->get_size(), + "cost field size " + << cost_field->get_size() << "x" << cost_field->get_size() + << " does not match integration field size " + << this->get_size() << "x" << this->get_size()); + + std::vector wavefront_blocked_portal; + + std::vector start_cells; + + auto exit_start = portal->get_exit_start(other_sector_id); + auto exit_end = portal->get_exit_end(other_sector_id); + auto entry_start = portal->get_entry_start(other_sector_id); + + auto x_diff = exit_start.ne - entry_start.ne; + auto y_diff = exit_start.se - entry_start.se; + + auto &cost_cells = cost_field->get_costs(); + auto &other_cells = other->get_cells(); + + // transfer masks for flags from the other side of the portal + // only LOS and wavefront blocked flags are relevant + integrated_flags_t transfer_mask = INTEGRATE_LOS_MASK | INTEGRATE_WAVEFRONT_BLOCKED_MASK; + + // every portal cell is a target cell + for (auto y = exit_start.se; y <= exit_end.se; ++y) { + for (auto x = exit_start.ne; x <= exit_end.ne; ++x) { + // cell index on the exit side of the portal + auto target_idx = x + y * this->size; + + // cell index on the entry side of the portal + auto entry_idx = x - x_diff + (y - y_diff) * this->size; + + // Set the cost of all target cells to the start value + this->cells[target_idx].cost = INTEGRATED_COST_START; + this->cells[target_idx].flags = other_cells[entry_idx].flags & transfer_mask; + + this->cells[target_idx].flags |= INTEGRATE_TARGET_MASK; + + if (not(this->cells[target_idx].flags & transfer_mask)) { + // If neither LOS nor wavefront blocked flags are set for the portal entry, + // the portal exit cell doesn't affect the LOS and we can skip further checks + continue; + } + + // Get the cost of the current cell + auto cell_cost = cost_cells[target_idx]; + + if (cell_cost > COST_MIN or this->cells[target_idx].flags & INTEGRATE_WAVEFRONT_BLOCKED_MASK) { + // cell blocks line of sight + + // set the blocked flag for the cell if it wasn't set already + this->cells[target_idx].flags |= INTEGRATE_WAVEFRONT_BLOCKED_MASK; + wavefront_blocked_portal.push_back(target_idx); + + // set the found flag for the cell, so that the start costs + // are not changed in the main LOS integration + this->cells[target_idx].flags |= INTEGRATE_FOUND_MASK; + + // check each neighbor for a corner + auto corners = this->get_los_corners(cost_field, target, coord::tile_delta(x, y)); + for (auto &corner : corners) { + // draw a line from the corner to the edge of the field + // to get the cells blocked by the corner + auto blocked_cells = this->bresenhams_line(target, corner.first, corner.second); + for (auto blocked_idx : blocked_cells) { + if (cost_cells[blocked_idx] > COST_MIN) { + // stop if blocked_idx is not min cost + // because this idx may create a new corner + break; + } + // set the blocked flag for the cell + this->cells[blocked_idx].flags |= INTEGRATE_WAVEFRONT_BLOCKED_MASK; + + // clear los flag if it was set + this->cells[blocked_idx].flags &= ~INTEGRATE_LOS_MASK; + + wavefront_blocked_portal.push_back(blocked_idx); + } + } + continue; + } + + // the cell has the LOS flag and is added to the start cells + start_cells.push_back(target_idx); + } + } + + if (start_cells.empty()) { + // Main LOS integration will not enter its loop + // so we can take a shortcut and just return the + // wavefront blocked cells we already found + return wavefront_blocked_portal; + } + + // Call main LOS integration function + auto wavefront_blocked_main = this->integrate_los(cost_field, + target, + INTEGRATED_COST_START, + std::move(start_cells)); + wavefront_blocked_main.insert(wavefront_blocked_main.end(), + wavefront_blocked_portal.begin(), + wavefront_blocked_portal.end()); + return wavefront_blocked_main; +} + +std::vector IntegrationField::integrate_los(const std::shared_ptr &cost_field, + const coord::tile_delta &target, + integrated_cost_t start_cost, + std::vector &&start_wave) { + ENSURE(cost_field->get_size() == this->get_size(), + "cost field size " + << cost_field->get_size() << "x" << cost_field->get_size() + << " does not match integration field size " + << this->get_size() << "x" << this->get_size()); + + // Store the wavefront_blocked cells + std::vector wavefront_blocked; + + // Cells that still have to be visited by the current wave + std::vector current_wave = std::move(start_wave); + + // Cells that have to be visited in the next wave + std::vector next_wave; + + // Preallocate ~30% of the field size for the wavefront + // This reduces the number of reallocations on push_back operations + // TODO: Find "optimal" value for reserve + current_wave.reserve(this->size * 3); + next_wave.reserve(this->size * 3); + + // Cost of the current wave + integrated_cost_t wave_cost = start_cost; + + // Get the cost field values + auto &cost_cells = cost_field->get_costs(); + auto &integrate_cells = this->cells; + + do { + for (size_t i = 0; i < current_wave.size(); ++i) { + // inner loop: handle a wave + auto idx = current_wave[i]; + auto &cell = integrate_cells[idx]; + + if (cell.flags & INTEGRATE_FOUND_MASK) { + // Skip cells that are already in the line of sight + continue; + } + else if (cell.flags & INTEGRATE_WAVEFRONT_BLOCKED_MASK) { + // Stop at cells that are blocked by a LOS corner + cell.cost = wave_cost - 1 + cost_cells[idx]; + cell.flags |= INTEGRATE_FOUND_MASK; + continue; + } + + // Add the current cell to the found cells + cell.flags |= INTEGRATE_FOUND_MASK; + + // Get the x and y coordinates of the current cell + auto x = idx % this->size; + auto y = idx / this->size; + + // Get the cost of the current cell + auto cell_cost = cost_cells[idx]; + + if (cell_cost > COST_MIN) { + // cell blocks line of sight + // and we have to check for corners + if (cell_cost != COST_IMPASSABLE) { + // Add the current cell to the blocked wavefront if it's not a wall + wavefront_blocked.push_back(idx); + cell.cost = wave_cost - 1 + cell_cost; + cell.flags |= INTEGRATE_WAVEFRONT_BLOCKED_MASK; + } + + // check each neighbor for a corner + auto corners = this->get_los_corners(cost_field, target, coord::tile_delta(x, y)); + for (auto &corner : corners) { + // draw a line from the corner to the edge of the field + // to get the cells blocked by the corner + auto blocked_cells = this->bresenhams_line(target, corner.first, corner.second); + for (auto blocked_idx : blocked_cells) { + if (cost_cells[blocked_idx] > COST_MIN) { + // stop if blocked_idx is impassable + break; + } + // set the blocked flag for the cell + integrate_cells[blocked_idx].flags |= INTEGRATE_WAVEFRONT_BLOCKED_MASK; + + // clear los flag if it was set + integrate_cells[blocked_idx].flags &= ~INTEGRATE_LOS_MASK; + + wavefront_blocked.push_back(blocked_idx); + } + } + continue; + } + + // The cell is in the line of sight at min cost + // Set the LOS flag and cost + cell.cost = wave_cost; + cell.flags |= INTEGRATE_LOS_MASK; + + // Search the neighbors of the current cell + if (y > 0) { + auto neighbor_idx = idx - this->size; + next_wave.push_back(neighbor_idx); + } + if (x > 0) { + auto neighbor_idx = idx - 1; + next_wave.push_back(neighbor_idx); + } + if (y < this->size - 1) { + auto neighbor_idx = idx + this->size; + next_wave.push_back(neighbor_idx); + } + if (x < this->size - 1) { + auto neighbor_idx = idx + 1; + next_wave.push_back(neighbor_idx); + } + } + + // increment the cost and advance the wavefront outwards + wave_cost += 1; + current_wave.swap(next_wave); + next_wave.clear(); + } + while (not current_wave.empty()); + + return wavefront_blocked; +} + +void IntegrationField::integrate_cost(const std::shared_ptr &cost_field, + const coord::tile_delta &target) { + ENSURE(cost_field->get_size() == this->get_size(), + "cost field size " + << cost_field->get_size() << "x" << cost_field->get_size() + << " does not match integration field size " + << this->get_size() << "x" << this->get_size()); + + // Target cell index + auto target_idx = target.ne + target.se * this->size; + + // Move outwards from the target cell, updating the integration field + this->cells[target_idx].cost = INTEGRATED_COST_START; + this->cells[target_idx].flags |= INTEGRATE_TARGET_MASK; + this->integrate_cost(cost_field, {target_idx}); +} + +void IntegrationField::integrate_cost(const std::shared_ptr &cost_field, + sector_id_t other_sector_id, + const std::shared_ptr &portal) { + ENSURE(cost_field->get_size() == this->get_size(), + "cost field size " + << cost_field->get_size() << "x" << cost_field->get_size() + << " does not match integration field size " + << this->get_size() << "x" << this->get_size()); + + // Integrate the cost of the cells on the exit side (this) of the portal + std::vector start_cells; + auto exit_start = portal->get_exit_start(other_sector_id); + auto exit_end = portal->get_exit_end(other_sector_id); + for (auto y = exit_start.se; y <= exit_end.se; ++y) { + for (auto x = exit_start.ne; x <= exit_end.ne; ++x) { + // every portal cell is a target cell + auto target_idx = x + y * this->size; + + // Set the cost of all target cells to the start value + this->cells[target_idx].cost = INTEGRATED_COST_START; + this->cells[target_idx].flags |= INTEGRATE_TARGET_MASK; + start_cells.push_back(target_idx); + + // TODO: Transfer flags and cost from the other integration field + } + } + + // Integrate the rest of the cost field + this->integrate_cost(cost_field, std::move(start_cells)); +} + +void IntegrationField::integrate_cost(const std::shared_ptr &cost_field, + std::vector &&start_cells) { + // Cells that still have to be visited by the current wave + std::vector current_wave = std::move(start_cells); + + // Cells that have to be visited in the next wave + std::vector next_wave; + + // Preallocate ~30% of the field size for the wavefront + // This reduces the number of reallocations on push_back operations + // TODO: Find "optimal" value for reserve + current_wave.reserve(this->size * 3); + next_wave.reserve(this->size * 3); + + // Get the cost field values + auto &cost_cells = cost_field->get_costs(); + + // Move outwards from the wavefront, updating the integration field + while (not current_wave.empty()) { + for (size_t i = 0; i < current_wave.size(); ++i) { + auto idx = current_wave[i]; + + // Get the x and y coordinates of the current cell + auto x = idx % this->size; + auto y = idx / this->size; + + auto integrated_current = this->cells[idx].cost; + + // Get the neighbors of the current cell + if (y > 0) { + auto neighbor_idx = idx - this->size; + this->update_neighbor(neighbor_idx, + cost_cells[neighbor_idx], + integrated_current, + next_wave); + } + if (x > 0) { + auto neighbor_idx = idx - 1; + this->update_neighbor(neighbor_idx, + cost_cells[neighbor_idx], + integrated_current, + next_wave); + } + if (y < this->size - 1) { + auto neighbor_idx = idx + this->size; + this->update_neighbor(neighbor_idx, + cost_cells[neighbor_idx], + integrated_current, + next_wave); + } + if (x < this->size - 1) { + auto neighbor_idx = idx + 1; + this->update_neighbor(neighbor_idx, + cost_cells[neighbor_idx], + integrated_current, + next_wave); + } + } + + current_wave.swap(next_wave); + next_wave.clear(); + } +} + +const std::vector &IntegrationField::get_cells() const { + return this->cells; +} + +void IntegrationField::reset() { + std::fill(this->cells.begin(), this->cells.end(), INTEGRATE_INIT); + + log::log(DBG << "Integration field has been reset"); +} + +void IntegrationField::reset_dynamic_flags() { + integrated_flags_t mask = 0xFF & ~(INTEGRATE_LOS_MASK | INTEGRATE_WAVEFRONT_BLOCKED_MASK | INTEGRATE_FOUND_MASK); + for (integrated_t &cell : this->cells) { + cell.flags = cell.flags & mask; + } + + log::log(DBG << "Integration field dynamic flags have been reset"); +} + +void IntegrationField::update_neighbor(size_t idx, + cost_t cell_cost, + integrated_cost_t integrated_cost, + std::vector &wave) { + ENSURE(cell_cost > COST_INIT, "cost field cell value must be non-zero"); + + // Check if the cell is impassable + // then we don't need to update the integration field + if (cell_cost == COST_IMPASSABLE) { + return; + } + + auto cost = integrated_cost + cell_cost; + if (cost < this->cells[idx].cost) { + // If the new integration value is smaller than the current one, + // update the cell and add it to the open list + this->cells[idx].cost = cost; + + wave.push_back(idx); + } +} + +std::vector> IntegrationField::get_los_corners(const std::shared_ptr &cost_field, + const coord::tile_delta &target, + const coord::tile_delta &blocker) { + std::vector> corners; + + // Get the cost of the blocking cell's neighbors + + // Set all costs to IMPASSABLE at the beginning + auto top_cost = COST_IMPASSABLE; + auto left_cost = COST_IMPASSABLE; + auto bottom_cost = COST_IMPASSABLE; + auto right_cost = COST_IMPASSABLE; + + std::pair top_left{blocker.ne, blocker.se}; + std::pair top_right{blocker.ne + 1, blocker.se}; + std::pair bottom_left{blocker.ne, blocker.se + 1}; + std::pair bottom_right{blocker.ne + 1, blocker.se + 1}; + + // Get neighbor costs (if they exist) + if (blocker.se > 0) { + top_cost = cost_field->get_cost(blocker.ne, blocker.se - 1); + } + if (blocker.ne > 0) { + left_cost = cost_field->get_cost(blocker.ne - 1, blocker.se); + } + if (static_cast(blocker.se) < this->size - 1) { + bottom_cost = cost_field->get_cost(blocker.ne, blocker.se + 1); + } + if (static_cast(blocker.ne) < this->size - 1) { + right_cost = cost_field->get_cost(blocker.ne + 1, blocker.se); + } + + // Check which corners are blocking LOS + // TODO: Currently super complicated and could likely be optimized + if (blocker.ne == target.ne) { + // blocking cell is parallel to target on y-axis + if (blocker.se < target.se) { + if (left_cost == COST_MIN) { + // top + corners.push_back(bottom_left); + } + if (right_cost == COST_MIN) { + // top + corners.push_back(bottom_right); + } + } + else { + if (left_cost == COST_MIN) { + // bottom + corners.push_back(top_left); + } + if (right_cost == COST_MIN) { + // bottom + corners.push_back(top_right); + } + } + } + else if (blocker.se == target.se) { + // blocking cell is parallel to target on x-axis + if (blocker.ne < target.ne) { + if (top_cost == COST_MIN) { + // right + corners.push_back(top_right); + } + if (bottom_cost == COST_MIN) { + // right + corners.push_back(bottom_right); + } + } + else { + if (top_cost == COST_MIN) { + // left + corners.push_back(top_left); + } + if (bottom_cost == COST_MIN) { + // left + corners.push_back(bottom_left); + } + } + } + else { + // blocking cell is diagonal to target on + if (blocker.ne < target.ne) { + if (blocker.se < target.se) { + // top and right + if (top_cost == COST_MIN and right_cost == COST_MIN) { + // right + corners.push_back(top_right); + } + if (left_cost == COST_MIN and bottom_cost == COST_MIN) { + // bottom + corners.push_back(bottom_left); + } + } + else { + // bottom and right + if (bottom_cost == COST_MIN and right_cost == COST_MIN) { + // right + corners.push_back(bottom_right); + } + if (left_cost == COST_MIN and top_cost == COST_MIN) { + // top + corners.push_back(top_left); + } + } + } + else { + if (blocker.se < target.se) { + // top and left + if (top_cost == COST_MIN and left_cost == COST_MIN) { + // left + corners.push_back(top_left); + } + if (right_cost == COST_MIN and bottom_cost == COST_MIN) { + // bottom + corners.push_back(bottom_right); + } + } + else { + // bottom and left + if (bottom_cost == COST_MIN and left_cost == COST_MIN) { + // left + corners.push_back(bottom_left); + } + if (right_cost == COST_MIN and top_cost == COST_MIN) { + // top + corners.push_back(top_right); + } + } + } + } + + return corners; +} + +std::vector IntegrationField::bresenhams_line(const coord::tile_delta &target, + int corner_x, + int corner_y) { + std::vector cells; + + // cell coordinates + // these have to be offset depending on the line direction + auto cell_x = corner_x; + auto cell_y = corner_y; + + // field edge boundary + int boundary = this->size; + + // target coordinates + // offset by 0.5 to get the center of the cell + double tx = target.ne + 0.5; + double ty = target.se + 0.5; + + // slope of the line + double dx = std::abs(tx - corner_x); + double dy = std::abs(ty - corner_y); + auto m = dy / dx; + + // error margin for the line + // if the error is greater than 1.0, we have to move in the y direction + auto error = m; + + // Check which direction the line is going + if (corner_x < tx) { + if (corner_y < ty) { // left and up + cell_y -= 1; + cell_x -= 1; + while (cell_x >= 0 and cell_y >= 0) { + cells.push_back(cell_x + cell_y * this->size); + if (error > 1.0) { + cell_y -= 1; + error -= 1.0; + } + else { + cell_x -= 1; + error += m; + } + } + } + + else { // left and down + cell_x -= 1; + while (cell_x >= 0 and cell_y < boundary) { + cells.push_back(cell_x + cell_y * this->size); + if (error > 1.0) { + cell_y += 1; + error -= 1.0; + } + else { + cell_x -= 1; + error += m; + } + } + } + } + else { + if (corner_y < ty) { // right and up + cell_y -= 1; + while (cell_x < boundary and cell_y >= 0) { + cells.push_back(cell_x + cell_y * this->size); + if (error > 1.0) { + cell_y -= 1; + error -= 1.0; + } + else { + cell_x += 1; + error += m; + } + } + } + else { // right and down + while (cell_x < boundary and cell_y < boundary) { + cells.push_back(cell_x + cell_y * this->size); + if (error > 1.0) { + cell_y += 1; + error -= 1.0; + } + else { + cell_x += 1; + error += m; + } + } + } + } + + return cells; +} + + +} // namespace openage::path diff --git a/libopenage/pathfinding/integration_field.h b/libopenage/pathfinding/integration_field.h new file mode 100644 index 0000000000..c9149cc82f --- /dev/null +++ b/libopenage/pathfinding/integration_field.h @@ -0,0 +1,236 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include +#include +#include + +#include "pathfinding/types.h" + + +namespace openage { +namespace coord { +struct tile_delta; +} // namespace coord + +namespace path { +class CostField; +class Portal; + +/** + * Integration field in the flow-field pathfinding algorithm. + */ +class IntegrationField { +public: + /** + * Create a square integration field with a specified size. + * + * @param size Side length of the field. + */ + IntegrationField(size_t size); + + /** + * Get the size of the integration field. + * + * @return Size of the integration field. + */ + size_t get_size() const; + + /** + * Get the integration value at a specified position. + * + * @param pos Coordinates of the cell (relative to field origin). + * @return Integration value at the specified position. + */ + const integrated_t &get_cell(const coord::tile_delta &pos) const; + + /** + * Get the integration value at a specified position. + * + * @param x X-coordinate of the cell. + * @param y Y-coordinate of the cell. + * @return Integration value at the specified position. + */ + const integrated_t &get_cell(size_t x, size_t y) const; + + /** + * Get the integration value at a specified position. + * + * @param idx Index of the cell. + * @return Integration value at the specified position. + */ + const integrated_t &get_cell(size_t idx) const; + + /** + * Calculate the line-of-sight integration flags for a target cell. + * + * The target cell coordinates must lie within the field. + * + * Returns a list of cells that are flagged as "wavefront blocked". These cells + * can be used as a starting point for the cost integration. + * + * @param cost_field Cost field to integrate. + * @param target Coordinates of the target cell (relative to field origin). + * + * @return Cells flagged as "wavefront blocked". + */ + std::vector integrate_los(const std::shared_ptr &cost_field, + const coord::tile_delta &target); + + /** + * Calculate the line-of-sight integration flags starting from a portal to another + * integration field. + * + * Returns a list of cells that are flagged as "wavefront blocked". These cells + * can be used as a starting point for the cost integration. + * + * @param cost_field Cost field to integrate. + * @param other Integration field of the other sector. + * @param other_sector_id Sector ID of the other integration field. + * @param portal Portal connecting the two fields. + * @param target Coordinates of the target cell (relative to field origin). + * + * @return Cells flagged as "wavefront blocked". + */ + std::vector integrate_los(const std::shared_ptr &cost_field, + const std::shared_ptr &other, + sector_id_t other_sector_id, + const std::shared_ptr &portal, + const coord::tile_delta &target); + + /** + * Calculate the line-of-sight integration flags for a target cell. + * + * Returns a list of cells that are flagged as "wavefront blocked". These cells + * can be used as a starting point for the cost integration. + * + * @param cost_field Cost field to integrate. + * @param target Coordinates of the target cell (relative to field origin). + * @param start_cost Integration cost for the start wave. + * @param start_wave Cells used for the first LOS integration wave. The wavefront + * expands outwards from these cells. + * + * @return Cells flagged as "wavefront blocked". + */ + std::vector integrate_los(const std::shared_ptr &cost_field, + const coord::tile_delta &target, + integrated_cost_t start_cost, + std::vector &&start_wave); + + /** + * Calculate the cost integration field starting from a target cell. + * + * @param cost_field Cost field to integrate. + * @param target Coordinates of the target cell. + */ + void integrate_cost(const std::shared_ptr &cost_field, + const coord::tile_delta &target); + + /** + * Calculate the cost integration field starting from a portal to another + * integration field. + * + * @param cost_field Cost field to integrate. + * @param other_sector_id Sector ID of the other integration field. + * @param portal Portal connecting the two fields. + */ + void integrate_cost(const std::shared_ptr &cost_field, + sector_id_t other_sector_id, + const std::shared_ptr &portal); + + /** + * Calculate the cost integration field starting from a wavefront. + * + * @param cost_field Cost field to integrate. + * @param start_cells Cells flagged as "wavefront blocked" from a LOS pass. + */ + void integrate_cost(const std::shared_ptr &cost_field, + std::vector &&start_cells); + + /** + * Get the integration field values. + * + * @return Integration field values. + */ + const std::vector &get_cells() const; + + /** + * Reset the integration field for a new integration. + */ + void reset(); + + /** + * Reset all flags that are dependent on the path target location. These + * flags should be removed when the field is cached and reused for + * other targets. + * + * Relevant flags are: + * - INTEGRATE_LOS_MASK + * - INTEGRATE_WAVEFRONT_BLOCKED_MASK + * - INTEGRATE_FOUND_MASK + */ + void reset_dynamic_flags(); + +private: + /** + * Update a neigbor cell during the cost integration process. + * + * @param idx Index of the neighbor cell that is updated. + * @param cell_cost Cost of the neighbor cell from the cost field. + * @param integrated_cost Current integrated cost of the updating cell in the integration field. + * @param wave List of cells that are part of the next wavefront. + * + * @return New integration value of the cell. + */ + void update_neighbor(size_t idx, + cost_t cell_cost, + integrated_cost_t integrated_cost, + std::vector &wave); + + /** + * Get the LOS corners around a cell. + * + * @param cost_field Cost field to integrate. + * @param target Cell coordinates of the target (relative to field origin). + * @param blocker Cell coordinates of the cell blocking LOS (relative to field origin). + * + * @return Field coordinates of the LOS corners. + */ + std::vector> get_los_corners(const std::shared_ptr &cost_field, + const coord::tile_delta &target, + const coord::tile_delta &blocker); + + /** + * Get the cells in a bresenham's line between the corner cell and the field edge. + * + * This function is a modified version of the bresenham's line algorithm that + * retrieves the cells between the corner point and the field's edge, rather than + * the cells between two arbitrary points. We do this because the intersection + * point with the field edge is unknown. + * + * @param target Cell coordinates of the target (relative to field origin). + * @param corner_x X field coordinate edge of the LOS corner. + * @param corner_y Y field coordinate edge of the LOS corner. + * + * @return Cell indices of the LOS line. + */ + std::vector bresenhams_line(const coord::tile_delta &target, + int corner_x, + int corner_y); + + /** + * Side length of the field. + */ + size_t size; + + /** + * Integration field values. + */ + std::vector cells; +}; + +} // namespace path +} // namespace openage diff --git a/libopenage/pathfinding/integrator.cpp b/libopenage/pathfinding/integrator.cpp new file mode 100644 index 0000000000..f4dcf324b1 --- /dev/null +++ b/libopenage/pathfinding/integrator.cpp @@ -0,0 +1,221 @@ +// 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) { + auto integration_field = std::make_shared(cost_field->get_size()); + + log::log(DBG << "Integrating cost field for target coord " << target); + if (with_los) { + log::log(SPAM << "Performing LOS pass"); + auto wavefront_blocked = integration_field->integrate_los(cost_field, target); + integration_field->integrate_cost(cost_field, std::move(wavefront_blocked)); + } + else { + log::log(SPAM << "Skipping LOS pass"); + integration_field->integrate_cost(cost_field, target); + } + + return integration_field; +} + +std::shared_ptr Integrator::integrate(const std::shared_ptr &cost_field, + const std::shared_ptr &other, + 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); + 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_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_integration_field; + } + + log::log(DBG << "Integrating cost field for portal " << portal->get_id() + << " from sector " << other_sector_id); + + // Create a new integration field + auto integration_field = std::make_shared(cost_field->get_size()); + + // LOS pass + std::vector wavefront_blocked; + if (with_los) { + log::log(SPAM << "Performing LOS pass"); + wavefront_blocked = integration_field->integrate_los(cost_field, other, other_sector_id, portal, target); + } + + // Cost integration + if (wavefront_blocked.empty()) { + // No LOS pass or no blocked cells + // use the portal as the target + integration_field->integrate_cost(cost_field, other_sector_id, portal); + } + else { + // LOS pass was performed and some cells were blocked + // use the blocked cells as the start wave + integration_field->integrate_cost(cost_field, std::move(wavefront_blocked)); + } + + return integration_field; +} + +std::shared_ptr Integrator::build(const std::shared_ptr &integration_field) { + auto flow_field = std::make_shared(integration_field->get_size()); + + log::log(DBG << "Building flow field from integration field"); + flow_field->build(integration_field); + + return flow_field; +} + +std::shared_ptr Integrator::build(const std::shared_ptr &integration_field, + const std::shared_ptr &other, + sector_id_t other_sector_id, + const std::shared_ptr &portal, + bool with_los) { + auto cache_key = std::make_pair(portal->get_id(), other_sector_id); + 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_flow_field); + + // Transfer the LOS flags to the flow field + flow_field->transfer_dynamic_flags(integration_field); + + return flow_field; + } + + return cached_flow_field; + } + + log::log(DBG << "Building flow field for portal " << portal->get_id() + << " from sector " << other_sector_id); + + auto flow_field = std::make_shared(integration_field->get_size()); + flow_field->build(integration_field, other, other_sector_id, portal); + + return flow_field; +} + +Integrator::get_return_t Integrator::get(const std::shared_ptr &cost_field, + const coord::tile_delta &target) { + auto integration_field = this->integrate(cost_field, target); + auto flow_field = this->build(integration_field); + + return std::make_pair(integration_field, flow_field); +} + +Integrator::get_return_t Integrator::get(const std::shared_ptr &cost_field, + const std::shared_ptr &other, + 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); + 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_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); + + 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_flow_field); + + // Transfer the LOS flags to the flow field + flow_field->transfer_dynamic_flags(integration_field); + + return std::make_pair(integration_field, flow_field); + } + + return std::make_pair(cached_integration_field, cached_flow_field); + } + + 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() + << ", sector ID: " << other_sector_id); + + // Copy the fields to the cache. + std::shared_ptr cached_integration_field = std::make_shared(*integration_field); + cached_integration_field->reset_dynamic_flags(); + + std::shared_ptr cached_flow_field = std::make_shared(*flow_field); + cached_flow_field->reset_dynamic_flags(); + + 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); +} + +} // namespace openage::path diff --git a/libopenage/pathfinding/integrator.h b/libopenage/pathfinding/integrator.h new file mode 100644 index 0000000000..490d5eaacb --- /dev/null +++ b/libopenage/pathfinding/integrator.h @@ -0,0 +1,144 @@ +// Copyright 2024-2025 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include "pathfinding/types.h" +#include "pathfinding/field_cache.h" +#include "util/hash.h" +#include "time/time.h" + + +namespace openage { +namespace coord { +struct tile_delta; +} // namespace coord + +namespace path { +class CostField; +class FlowField; +class IntegrationField; +class Portal; + +/** + * Integrator for the flow field pathfinding algorithm. + */ +class Integrator { +public: + /** + * Create a new integrator. + */ + Integrator(); + + ~Integrator() = default; + + /** + * Integrate the cost field for a target. + * + * This should be used for the field containing the target cell. + * The target coordinates must lie within the boundaries of the cost field. + * + * @param cost_field Cost field. + * @param target Coordinates of the target cell. + * @param with_los If true an LOS pass is performed before cost integration. + * + * @return Integration field. + */ + std::shared_ptr integrate(const std::shared_ptr &cost_field, + const coord::tile_delta &target, + bool with_los = true); + + /** + * Integrate the cost field from a portal. + * + * This should be used for the fields on the portal path that are not the target field. + * The target coordinates must be relative to the origin of the sector the cost field belongs to. + * + * @param cost_field Cost field. + * @param other Integration field of the other side of the portal. + * @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. + */ + std::shared_ptr integrate(const std::shared_ptr &cost_field, + const std::shared_ptr &other, + 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); + + /** + * Build the flow field from an integration field. + * + * @param integration_field Integration field. + * + * @return Flow field. + */ + std::shared_ptr build(const std::shared_ptr &integration_field); + + /** + * Build the flow field from a portal. + * + * @param integration_field Integration field. + * @param other Integration field of the other side of the portal. + * @param other_sector_id Sector ID of the other side of the portal. + * @param portal Portal. + * @param with_los If true LOS flags are calculated if the flow field is in cache. + * + * @return Flow field. + */ + std::shared_ptr build(const std::shared_ptr &integration_field, + const std::shared_ptr &other, + sector_id_t other_sector_id, + const std::shared_ptr &portal, + bool with_los = true); + + using get_return_t = std::pair, std::shared_ptr>; + + /** + * Get the integration field and flow field for a target. + * + * @param cost_field Cost field. + * @param target Coordinates of the target cell. + * + * @return Integration field and flow field. + */ + get_return_t get(const std::shared_ptr &cost_field, + const coord::tile_delta &target); + + /** + * Get the integration field and flow field from a portal. + * + * @param cost_field Cost field. + * @param other Integration field of the other side of the portal. + * @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. + */ + get_return_t get(const std::shared_ptr &cost_field, + const std::shared_ptr &other, + 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: + /** + * Cache for already computed fields. + */ + std::unique_ptr field_cache; +}; + +} // namespace path +} // namespace openage diff --git a/libopenage/pathfinding/legacy/CMakeLists.txt b/libopenage/pathfinding/legacy/CMakeLists.txt new file mode 100644 index 0000000000..ad828995c9 --- /dev/null +++ b/libopenage/pathfinding/legacy/CMakeLists.txt @@ -0,0 +1,6 @@ +add_sources(libopenage + a_star.cpp + heuristics.cpp + path.cpp + tests.cpp +) diff --git a/libopenage/pathfinding/a_star.cpp b/libopenage/pathfinding/legacy/a_star.cpp similarity index 69% rename from libopenage/pathfinding/a_star.cpp rename to libopenage/pathfinding/legacy/a_star.cpp index 03b85def97..afeabeeefd 100644 --- a/libopenage/pathfinding/a_star.cpp +++ b/libopenage/pathfinding/legacy/a_star.cpp @@ -1,4 +1,4 @@ -// Copyright 2014-2019 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. /** @file * @@ -14,60 +14,41 @@ #include -#include "../datastructure/pairing_heap.h" -#include "../log/log.h" -#include "../terrain/terrain.h" -#include "../terrain/terrain_object.h" -#include "../util/strings.h" -#include "path.h" -#include "heuristics.h" +#include "datastructure/pairing_heap.h" +#include "log/log.h" +#include "pathfinding/legacy/heuristics.h" +#include "pathfinding/legacy/path.h" +#include "util/strings.h" namespace openage { -namespace path { +namespace path::legacy { Path to_point(coord::phys3 start, coord::phys3 end, std::function passable) { - auto valid_end = [&](const coord::phys3 &point) -> bool { return euclidean_squared_cost(point, end) < path_grid_size.to_float(); }; - auto heuristic = [&](const coord::phys3 &point) -> cost_t { + auto heuristic = [&](const coord::phys3 &point) -> cost_old_t { return euclidean_cost(point, end); }; return a_star(start, valid_end, heuristic, passable); } - -Path to_object(openage::TerrainObject *to_move, - openage::TerrainObject *end, - coord::phys_t rad) { - coord::phys3 start = to_move->pos.draw; - auto valid_end = [&](const coord::phys3 &pos) -> bool { - return end->from_edge(pos) < rad; - }; - auto heuristic = [&](const coord::phys3 &pos) -> cost_t { - return (end->from_edge(pos) - to_move->min_axis() / 2L).to_float(); - }; - return a_star(start, valid_end, heuristic, to_move->passable); -} - - Path find_nearest(coord::phys3 start, std::function valid_end, std::function passable) { // Use Dijkstra (heuristic = 0) - auto zero = [](const coord::phys3 &) -> cost_t { return .0f; }; + auto zero = [](const coord::phys3 &) -> cost_old_t { return .0f; }; return a_star(start, valid_end, zero, passable); } Path a_star(coord::phys3 start, std::function valid_end, - std::function heuristic, + std::function heuristic, std::function passable) { - // path node storage, always provides cheapest next node. heap_t node_candidates; @@ -93,9 +74,7 @@ Path a_star(coord::phys3 start, // node to terminate the search was found if (valid_end(best_candidate->position)) { - log::log(MSG(dbg) << - "path cost is " << - util::FloatFixed<3, 8>{closest_node->future_cost}); + log::log(MSG(dbg) << "path cost is " << util::FloatFixed<3, 8>{closest_node->future_cost}); return best_candidate->generate_backtrace(); } @@ -115,7 +94,7 @@ Path a_star(coord::phys3 start, } bool not_visited = (visited_tiles.count(neighbor->position) == 0); - cost_t new_past_cost = best_candidate->past_cost + best_candidate->cost_to(*neighbor); + cost_old_t new_past_cost = best_candidate->past_cost + best_candidate->cost_to(*neighbor); // if new cost is better than the previous path if (not_visited or new_past_cost < neighbor->past_cost) { @@ -128,26 +107,26 @@ Path a_star(coord::phys3 start, } // update new cost knowledge - neighbor->past_cost = new_past_cost; - neighbor->future_cost = neighbor->past_cost + neighbor->heuristic_cost; + neighbor->past_cost = new_past_cost; + neighbor->future_cost = neighbor->past_cost + neighbor->heuristic_cost; neighbor->path_predecessor = best_candidate; if (not_visited) { neighbor->heap_node = node_candidates.push(neighbor); visited_tiles[neighbor->position] = neighbor; - } else { + } + else { node_candidates.decrease(neighbor->heap_node); } } } } - log::log(MSG(dbg) << - "incomplete path cost is " << - util::FloatFixed<3, 8>{closest_node->future_cost}); + log::log(MSG(dbg) << "incomplete path cost is " << util::FloatFixed<3, 8>{closest_node->future_cost}); return closest_node->generate_backtrace(); } -}} // namespace openage::path +} // namespace path::legacy +} // namespace openage diff --git a/libopenage/pathfinding/a_star.h b/libopenage/pathfinding/legacy/a_star.h similarity index 72% rename from libopenage/pathfinding/a_star.h rename to libopenage/pathfinding/legacy/a_star.h index 15d95066e1..a2704bb5f1 100644 --- a/libopenage/pathfinding/a_star.h +++ b/libopenage/pathfinding/legacy/a_star.h @@ -1,4 +1,4 @@ -// Copyright 2014-2016 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. #pragma once @@ -9,9 +9,7 @@ namespace openage { -class TerrainObject; - -namespace path { +namespace path::legacy { /** * path between two static points @@ -20,13 +18,6 @@ Path to_point(coord::phys3 start, coord::phys3 end, std::function passable); -/** - * path between 2 objects, with how close to come to end point - */ -Path to_object(TerrainObject *to_move, - TerrainObject *end, - coord::phys_t rad); - /** * path to nearest object with lambda */ @@ -44,8 +35,8 @@ Path find_nearest(coord::phys3 start, */ Path a_star(coord::phys3 start, std::function valid_end, - std::function heuristic, + std::function heuristic, std::function passable); -} // namespace path +} // namespace path::legacy } // namespace openage diff --git a/libopenage/pathfinding/legacy/heuristics.cpp b/libopenage/pathfinding/legacy/heuristics.cpp new file mode 100644 index 0000000000..e064fa1ad7 --- /dev/null +++ b/libopenage/pathfinding/legacy/heuristics.cpp @@ -0,0 +1,35 @@ +// Copyright 2014-2024 the openage authors. See copying.md for legal info. + +#include "heuristics.h" + +#include +#include + + +namespace openage { +namespace path::legacy { + +cost_old_t manhattan_cost(const coord::phys3 &start, const coord::phys3 &end) { + cost_old_t dx = std::abs(start.ne - end.ne).to_float(); + cost_old_t dy = std::abs(start.se - end.se).to_float(); + return dx + dy; +} + +cost_old_t chebyshev_cost(const coord::phys3 &start, const coord::phys3 &end) { + cost_old_t dx = std::abs(start.ne - end.ne).to_float(); + cost_old_t dy = std::abs(start.se - end.se).to_float(); + return std::max(dx, dy); +} + +cost_old_t euclidean_cost(const coord::phys3 &start, const coord::phys3 &end) { + return (end - start).length(); +} + +cost_old_t euclidean_squared_cost(const coord::phys3 &start, const coord::phys3 &end) { + cost_old_t dx = (start.ne - end.ne).to_float(); + cost_old_t dy = (start.se - end.se).to_float(); + return dx * dx + dy * dy; +} + +} // namespace path::legacy +} // namespace openage diff --git a/libopenage/pathfinding/heuristics.h b/libopenage/pathfinding/legacy/heuristics.h similarity index 51% rename from libopenage/pathfinding/heuristics.h rename to libopenage/pathfinding/legacy/heuristics.h index 7755f1288a..77707fbc9f 100644 --- a/libopenage/pathfinding/heuristics.h +++ b/libopenage/pathfinding/legacy/heuristics.h @@ -1,45 +1,45 @@ -// Copyright 2014-2017 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. #pragma once -#include "path.h" +#include "pathfinding/legacy/path.h" namespace openage { -namespace path { +namespace path::legacy { /** * function pointer type for distance estimation functions. */ -using heuristic_t = cost_t (*)(const coord::phys3 &start, const coord::phys3 &end); +using heuristic_t = cost_old_t (*)(const coord::phys3 &start, const coord::phys3 &end); /** * Manhattan distance cost estimation. * @returns the sum of the x and y difference. */ -cost_t manhattan_cost(const coord::phys3 &start, const coord::phys3 &end); +cost_old_t manhattan_cost(const coord::phys3 &start, const coord::phys3 &end); /** * Chebyshev distance cost estimation. * @returns y or x difference, whichever is higher. */ -cost_t chebyshev_cost(const coord::phys3 &start, const coord::phys3 &end); +cost_old_t chebyshev_cost(const coord::phys3 &start, const coord::phys3 &end); /** * Euclidean distance cost estimation. * @returns the hypotenuse length of the rectangular triangle formed. */ -cost_t euclidean_cost(const coord::phys3 &start, const coord::phys3 &end); +cost_old_t euclidean_cost(const coord::phys3 &start, const coord::phys3 &end); /** * Squared euclidean distance cost estimation. * @returns the square of the hypotenuse length of the rectangular triangle formed. */ -cost_t euclidean_squared_cost(const coord::phys3 &start, const coord::phys3 &end); +cost_old_t euclidean_squared_cost(const coord::phys3 &start, const coord::phys3 &end); /** * Calculate euclidean distance from a already calculated squared euclidean distance */ -cost_t euclidean_squared_to_euclidean_cost(const cost_t euclidean_squared_value); +cost_old_t euclidean_squared_to_euclidean_cost(const cost_old_t euclidean_squared_value); -} // namespace path +} // namespace path::legacy } // namespace openage diff --git a/libopenage/pathfinding/legacy/path.cpp b/libopenage/pathfinding/legacy/path.cpp new file mode 100644 index 0000000000..dafb63e686 --- /dev/null +++ b/libopenage/pathfinding/legacy/path.cpp @@ -0,0 +1,115 @@ +// Copyright 2014-2024 the openage authors. See copying.md for legal info. + +#include + +#include "pathfinding/legacy/path.h" + +namespace openage::path::legacy { + + +bool compare_node_cost::operator()(const node_pt &lhs, const node_pt &rhs) const { + // TODO: use node operator < + return lhs->future_cost < rhs->future_cost; +} + + +Node::Node(const coord::phys3 &pos, node_pt prev) : + position(pos), + tile_position(pos.to_tile3().to_tile()), + direction{}, + visited{false}, + was_best{false}, + factor{1.0f}, + path_predecessor{prev}, + heap_node(nullptr) { + if (prev) { + this->direction = (this->position - prev->position).normalize(); + + // TODO: add dot product to coord + cost_old_t similarity = ((this->direction.ne.to_float() * prev->direction.ne.to_float()) + (this->direction.se.to_float() * prev->direction.se.to_float())); + this->factor += (1 - similarity); + } +} + + +Node::Node(const coord::phys3 &pos, node_pt prev, cost_old_t past, cost_old_t heuristic) : + Node{pos, prev} { + this->past_cost = past; + this->heuristic_cost = heuristic; + this->future_cost = past + heuristic; +} + + +bool Node::operator<(const Node &other) const { + return this->future_cost < other.future_cost; +} + + +bool Node::operator==(const Node &other) const { + return this->position == other.position; +} + + +cost_old_t Node::cost_to(const Node &other) const { + // ignore the up-position, thus convert to phys2 + return ((this->position - other.position).to_phys2().length() + * other.factor * this->factor); +} + + +Path Node::generate_backtrace() { + std::vector waypoints; + + node_pt current = this->shared_from_this(); + do { + Node other = *current; + waypoints.push_back(*current); + current = current->path_predecessor; + } + while (current != nullptr); + waypoints.pop_back(); // remove start + + return {waypoints}; +} + + +std::vector Node::get_neighbors(const nodemap_t &nodes, float scale) { + std::vector neighbors; + neighbors.reserve(8); + for (int n = 0; n < 8; ++n) { + coord::phys3 n_pos = this->position + (neigh_phys[n] * scale); + + if (nodes.count(n_pos) > 0) { + neighbors.push_back(nodes.at(n_pos)); + } + else { + neighbors.push_back(std::make_shared(n_pos, this->shared_from_this())); + } + } + return neighbors; +} + + +bool passable_line(node_pt start, node_pt end, std::function passable, float samples) { + // interpolate between points and make passablity checks + // (dont check starting position) + for (int i = 1; i <= samples; ++i) { + // TODO: needs more fixed-point + double percent = static_cast(i) / samples; + coord::phys_t ne = (1.0 - percent) * start->position.ne.to_double() + percent * end->position.ne.to_double(); + coord::phys_t se = (1.0 - percent) * start->position.se.to_double() + percent * end->position.se.to_double(); + coord::phys_t up = (1.0 - percent) * start->position.up.to_double() + percent * end->position.up.to_double(); + + if (!passable(coord::phys3{ne, se, up})) { + return false; + } + } + return true; +} + + +Path::Path(const std::vector &nodes) : + waypoints{nodes} {} + + +} // namespace openage::path::legacy diff --git a/libopenage/pathfinding/legacy/path.h b/libopenage/pathfinding/legacy/path.h new file mode 100644 index 0000000000..59851ecd51 --- /dev/null +++ b/libopenage/pathfinding/legacy/path.h @@ -0,0 +1,209 @@ +// Copyright 2014-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include +#include + +#include "coord/phys.h" +#include "coord/tile.h" +#include "datastructure/pairing_heap.h" +#include "util/hash.h" +#include "util/misc.h" + + +namespace openage { + +namespace path::legacy { + +class Node; +class Path; + +/** + * The data type for movement cost + */ +using cost_old_t = float; + +/** + * Type for storing navigation nodes. + */ +using node_pt = std::shared_ptr; + +/** + * Type for mapping tiles to nodes. + */ +using nodemap_t = std::unordered_map; + + +/** + * Cost comparison for node_pt. + * Extracts the ptr from the shared_ptr. + * Calls operator < on Node. + */ +struct compare_node_cost { + bool operator()(const node_pt &lhs, const node_pt &rhs) const; +}; + +/** + * Priority queue node item type. + */ +using heap_t = datastructure::PairingHeap; + +/** + * Size of phys-coord grid for path nodes. + */ +constexpr coord::phys_t path_grid_size{1.f / 8}; + +/** + * Phys3 delta coordinates to select for path neighbors. + */ +constexpr coord::phys3_delta const neigh_phys[] = { + {path_grid_size * 1, path_grid_size * -1, 0}, + {path_grid_size * 1, path_grid_size * 0, 0}, + {path_grid_size * 1, path_grid_size * 1, 0}, + {path_grid_size * 0, path_grid_size * 1, 0}, + {path_grid_size * -1, path_grid_size * 1, 0}, + {path_grid_size * -1, path_grid_size * 0, 0}, + {path_grid_size * -1, path_grid_size * -1, 0}, + {path_grid_size * 0, path_grid_size * -1, 0}}; + +/** + * + */ +bool passable_line(node_pt start, node_pt end, std::function passable, float samples = 5.0f); + +/** + * One navigation waypoint in a path. + */ +class Node : public std::enable_shared_from_this { +public: + Node(const coord::phys3 &pos, node_pt prev); + Node(const coord::phys3 &pos, node_pt prev, cost_old_t past, cost_old_t heuristic); + + /** + * Orders nodes according to their future cost value. + */ + bool operator<(const Node &other) const; + + /** + * Compare the node to another one. + * They are the same if their position is. + */ + bool operator==(const Node &other) const; + + /** + * Calculates the actual movement cose to another node. + */ + cost_old_t cost_to(const Node &other) const; + + /** + * Create a backtrace path beginning at this node. + */ + Path generate_backtrace(); + + /** + * Get all neighbors of this graph node. + */ + std::vector get_neighbors(const nodemap_t &, float scale = 1.0f); + + /** + * The tile position this node is associated to. + * todo make const + */ + coord::phys3 position; + coord::tile tile_position; + coord::phys3_delta direction; // for path smoothing + + /** + * Future cost estimation value for this node. + */ + cost_old_t future_cost; + + /** + * Evaluated past cost value for the node. + * This stores the actual cost from start to this node. + */ + cost_old_t past_cost; + + /** + * Heuristic cost cache. + * Calculated once, is the heuristic distance from this node + * to the goal. + */ + cost_old_t heuristic_cost; + + /** + * Can this node be passed? + */ + bool accessible = false; + + /** + * Has this Node been visited? + */ + bool visited = false; + + /** + * Does this node already have an alternative path? + * If the node was once selected as the best next hop, + * this is set to true. + */ + bool was_best = false; + + /** + * Factor to adjust movement cost. + * default: 1 + */ + cost_old_t factor; + + /** + * Node where this one was reached by least cost. + */ + node_pt path_predecessor; + + /** + * Priority queue node that contains this path node. + */ + heap_t::element_t heap_node; +}; + + +/** + * Represents a planned trajectory. + * Generated by pathfinding algorithms. + */ +class Path { +public: + Path() = default; + Path(const std::vector &nodes); + + /** + * These are the waypoints to navigate in order. + * Includes the start and end node. + */ + std::vector waypoints; +}; + +} // namespace path::legacy +} // namespace openage + + +namespace std { + +/** + * Hash function for path nodes. + * Just uses their position. + */ +template <> +struct hash { + size_t operator()(const openage::path::legacy::Node &x) const { + openage::coord::phys3 node_pos = x.position; + size_t hash = openage::util::type_hash(); + hash = openage::util::hash_combine(hash, std::hash{}(node_pos.ne)); + hash = openage::util::hash_combine(hash, std::hash{}(node_pos.se)); + return hash; + } +}; + +} // namespace std diff --git a/libopenage/pathfinding/path_utils.h b/libopenage/pathfinding/legacy/path_utils.h similarity index 50% rename from libopenage/pathfinding/path_utils.h rename to libopenage/pathfinding/legacy/path_utils.h index c4f4b6fcca..2c87923ad4 100644 --- a/libopenage/pathfinding/path_utils.h +++ b/libopenage/pathfinding/legacy/path_utils.h @@ -1,9 +1,9 @@ -// Copyright 2014-2016 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. #pragma once namespace openage { -namespace path { +namespace path::legacy { } // namespace path } // namespace openage diff --git a/libopenage/pathfinding/legacy/tests.cpp b/libopenage/pathfinding/legacy/tests.cpp new file mode 100644 index 0000000000..b160eff91a --- /dev/null +++ b/libopenage/pathfinding/legacy/tests.cpp @@ -0,0 +1,317 @@ +// Copyright 2015-2024 the openage authors. See copying.md for legal info. + +#include "log/log.h" +#include "testing/testing.h" + +#include "pathfinding/legacy/heuristics.h" +#include "pathfinding/legacy/path.h" + +namespace openage { +namespace path { +namespace tests { + +/** + * This function tests setting up basic nodes that point to a previous node. + * Tests that direction is set correctly and that factor is set correctly. + */ +void node_0() { + coord::phys3 p0{0, 0, 0}; + coord::phys3 p1{1, 0, 0}; + coord::phys3 p2{1, 1, 0}; + coord::phys3 p3{1, -1, 0}; + coord::phys3 p4{2, 0, 0}; + coord::phys3 p5{2, 2, 0}; + coord::phys3 p6{2, -2, 0}; + + legacy::node_pt n0 = std::make_unique(p0, nullptr); + legacy::node_pt n1 = std::make_unique(p1, n0); + legacy::node_pt n2 = std::make_unique(p2, n1); + legacy::node_pt n3 = std::make_unique(p3, n1); + legacy::node_pt n4 = std::make_unique(p0, n1); + + // Testing how the factor is effected from the change in + // direction from one node to another + TESTEQUALS(n1->direction.ne, 1); + TESTEQUALS(n1->direction.se, 0); + + // Expect this to be 2 since the similarity between nodes is zero + TESTEQUALS(n1->factor, 2); + + TESTEQUALS(n2->direction.ne, 0); + TESTEQUALS(n2->direction.se, 1); + + // Expect this to be 2 since it takes a 90 degree turn from n1 + TESTEQUALS(n2->factor, 2); + + TESTEQUALS(n3->direction.ne, 0); + TESTEQUALS(n3->direction.se, -1); + + // Expect this to be 2 since it takes a 90 degree turn from n1 + TESTEQUALS(n3->factor, 2); + + TESTEQUALS(n4->direction.ne, -1); + TESTEQUALS(n4->direction.se, 0); + + // Expect this to be 3 since it takes a 180 degree turn from n1 + TESTEQUALS(n4->factor, 3); + + // Testing that the distance from the previous node noes not + // effect the factor, only change in direction + + n1 = std::make_unique(p4, n0); + n2 = std::make_unique(p5, n1); + n3 = std::make_unique(p6, n1); + n4 = std::make_unique(p0, n1); + + TESTEQUALS(n1->direction.ne, 1); + TESTEQUALS(n1->direction.se, 0); + + // Expect this to be 2 since the similarity between nodes is zero + TESTEQUALS(n1->factor, 2); + TESTEQUALS(n2->direction.ne, 0); + TESTEQUALS(n2->direction.se, 1); + + // Expect this to be 2 since it takes a 90 degree turn from n1 + TESTEQUALS(n2->factor, 2); + TESTEQUALS(n3->direction.ne, 0); + TESTEQUALS(n3->direction.se, -1); + + // Expect this to be 2 since it takes a 90 degree turn from n1 + TESTEQUALS(n3->factor, 2); + TESTEQUALS(n4->direction.ne, -1); + TESTEQUALS(n4->direction.se, 0); + + // Expect this to be 3 since it takes a 180 degree turn from n1 + TESTEQUALS(n4->factor, 3); +} + +/** + * This function tests Node->cost_to. The testing is done on 2 unrelated + * nodes (They have no previous node) to test the basic cost without adding + * the cost from node->factor. + */ +void node_cost_to_0() { + // Testing basic cost_to with ne only + coord::phys3 p0{0, 0, 0}; + coord::phys3 p1{10, 0, 0}; + + legacy::node_pt n0 = std::make_unique(p0, nullptr); + legacy::node_pt n1 = std::make_unique(p1, nullptr); + + TESTEQUALS(n0->cost_to(*n1), 10); + TESTEQUALS(n1->cost_to(*n0), 10); + + // Testing basic cost_to with se only + coord::phys3 p2{0, 5, 0}; + + legacy::node_pt n2 = std::make_unique(p2, nullptr); + + TESTEQUALS(n0->cost_to(*n2), 5); + TESTEQUALS(n2->cost_to(*n0), 5); + + // Testing cost_to with both se and ne: + coord::phys3 p3{3, 4, 0}; // -> sqrt(3*3 + 4*4) == 5 + + legacy::node_pt n3 = std::make_unique(p3, nullptr); + TESTEQUALS(n0->cost_to(*n3), 5); + TESTEQUALS(n3->cost_to(*n0), 5); + + // Test cost_to and check that `up` has no effect + coord::phys3 p4{3, 4, 8}; + + legacy::node_pt n4 = std::make_unique(p4, nullptr); + + TESTEQUALS(n0->cost_to(*n4), 5); + TESTEQUALS(n4->cost_to(*n0), 5); +} + +/** + * This function tests Node->cost_to. The testing is done on the neighbor + * nodes to test how the directional factor effects the cost. + */ +void node_cost_to_1() { + // Set up coords so that n1 will have a direction of ne = 1 + // but n0 with not be in n1s neighbors + coord::phys3 p0{-0.125, 0, 0}; + coord::phys3 p1{0.125, 0, 0}; + + legacy::node_pt n0 = std::make_unique(p0, nullptr); + legacy::node_pt n1 = std::make_unique(p1, n0); + + // We expect twice the normal cost since n0 had not direction + // thus we get a factor of 2 on n1 + TESTEQUALS_FLOAT(n0->cost_to(*n1), 0.5, 0.001); + TESTEQUALS_FLOAT(n1->cost_to(*n0), 0.5, 0.001); + + legacy::nodemap_t visited_tiles; + visited_tiles[n0->position] = n0; + + // Collect the costs to go to all the neighbors of n1 + std::vector costs; + for (legacy::node_pt neighbor : n1->get_neighbors(visited_tiles, 1)) { + costs.push_back(n1->cost_to(*neighbor)); + } + + TESTEQUALS_FLOAT(costs[0], 0.45711, 0.001); + TESTEQUALS_FLOAT(costs[1], 0.25, 0.001); + TESTEQUALS_FLOAT(costs[2], 0.45711, 0.001); + TESTEQUALS_FLOAT(costs[3], 0.5, 0.001); + TESTEQUALS_FLOAT(costs[4], 0.95709, 0.001); + TESTEQUALS_FLOAT(costs[5], 0.75, 0.001); + TESTEQUALS_FLOAT(costs[6], 0.95709, 0.001); + TESTEQUALS_FLOAT(costs[7], 0.5, 0.001); +} + +/** + * This function does a basic test of generating a backtrace from the + * last node in a path. + */ +void node_generate_backtrace_0() { + coord::phys3 p0{0, 0, 0}; + coord::phys3 p1{10, 0, 0}; + coord::phys3 p2{20, 0, 0}; + coord::phys3 p3{30, 0, 0}; + + legacy::node_pt n0 = std::make_unique(p0, nullptr); + legacy::node_pt n1 = std::make_unique(p1, n0); + legacy::node_pt n2 = std::make_unique(p2, n1); + legacy::node_pt n3 = std::make_unique(p3, n2); + + legacy::Path path = n3->generate_backtrace(); + + (path.waypoints[0] == *n3) or TESTFAIL; + (path.waypoints[1] == *n2) or TESTFAIL; + (path.waypoints[2] == *n1) or TESTFAIL; +} + +/** + * This function tests Node->get_neighbors and how the scale effects + * the neighbors given. + */ +void node_get_neighbors_0() { + coord::phys3 p0{0, 0, 0}; + + legacy::node_pt n0 = std::make_unique(p0, nullptr); + legacy::nodemap_t map; + + // Testing get_neighbors returning all surounding tiles with + // a factor of 1 + + std::vector neighbors = n0->get_neighbors(map, 1); + TESTEQUALS(neighbors.size(), 8); + + TESTEQUALS_FLOAT(neighbors[0]->position.ne.to_double(), 0.125, 0.001); + TESTEQUALS_FLOAT(neighbors[0]->position.se.to_double(), -0.125, 0.001); + + TESTEQUALS_FLOAT(neighbors[1]->position.ne.to_double(), 0.125, 0.001); + TESTEQUALS_FLOAT(neighbors[1]->position.se.to_double(), 0, 0.001); + + TESTEQUALS_FLOAT(neighbors[2]->position.ne.to_double(), 0.125, 0.001); + TESTEQUALS_FLOAT(neighbors[2]->position.se.to_double(), 0.125, 0.001); + + TESTEQUALS_FLOAT(neighbors[3]->position.ne.to_double(), 0, 0.001); + TESTEQUALS_FLOAT(neighbors[3]->position.se.to_double(), 0.125, 0.001); + + TESTEQUALS_FLOAT(neighbors[4]->position.ne.to_double(), -0.125, 0.001); + TESTEQUALS_FLOAT(neighbors[4]->position.se.to_double(), 0.125, 0.001); + + TESTEQUALS_FLOAT(neighbors[5]->position.ne.to_double(), -0.125, 0.001); + TESTEQUALS_FLOAT(neighbors[5]->position.se.to_double(), 0, 0.001); + + TESTEQUALS_FLOAT(neighbors[6]->position.ne.to_double(), -0.125, 0.001); + TESTEQUALS_FLOAT(neighbors[6]->position.se.to_double(), -0.125, 0.001); + + TESTEQUALS_FLOAT(neighbors[7]->position.ne.to_double(), 0, 0.001); + TESTEQUALS_FLOAT(neighbors[7]->position.se.to_double(), -0.125, 0.001); + + // Testing how a larger scale changes the neighbors generated + neighbors = n0->get_neighbors(map, 2); + TESTEQUALS(neighbors.size(), 8); + + TESTEQUALS_FLOAT(neighbors[0]->position.ne.to_double(), 0.25, 0.001); + TESTEQUALS_FLOAT(neighbors[0]->position.se.to_double(), -0.25, 0.001); + + TESTEQUALS_FLOAT(neighbors[1]->position.ne.to_double(), 0.25, 0.001); + TESTEQUALS_FLOAT(neighbors[1]->position.se.to_double(), 0, 0.001); + + TESTEQUALS_FLOAT(neighbors[2]->position.ne.to_double(), 0.25, 0.001); + TESTEQUALS_FLOAT(neighbors[2]->position.se.to_double(), 0.25, 0.001); + + TESTEQUALS_FLOAT(neighbors[3]->position.ne.to_double(), 0, 0.001); + TESTEQUALS_FLOAT(neighbors[3]->position.se.to_double(), 0.25, 0.001); + + TESTEQUALS_FLOAT(neighbors[4]->position.ne.to_double(), -0.25, 0.001); + TESTEQUALS_FLOAT(neighbors[4]->position.se.to_double(), 0.25, 0.001); + + TESTEQUALS_FLOAT(neighbors[5]->position.ne.to_double(), -0.25, 0.001); + TESTEQUALS_FLOAT(neighbors[5]->position.se.to_double(), 0, 0.001); + + TESTEQUALS_FLOAT(neighbors[6]->position.ne.to_double(), -0.25, 0.001); + TESTEQUALS_FLOAT(neighbors[6]->position.se.to_double(), -0.25, 0.001); + + TESTEQUALS_FLOAT(neighbors[7]->position.ne.to_double(), 0, 0.001); + TESTEQUALS_FLOAT(neighbors[7]->position.se.to_double(), -0.25, 0.001); +} + +/** + * This is a helper passable function that alwalys returns true. + */ +bool always_passable(const coord::phys3 &) { + return true; +} + +/** + * This is a helper passable function that always returns false. + */ +bool not_passable(const coord::phys3 &) { + return false; +} + +/** + * This is a helper passable function that only returns true when + * pos.ne == 20. + */ +bool sometimes_passable(const coord::phys3 &pos) { + if (pos.ne == 20) { + return false; + } + else { + return true; + } +} + +/** + * This function tests passable_line. Tests with always false, always true, + * and position dependant functions being passed in as args. + */ +void node_passable_line_0() { + coord::phys3 p0{0, 0, 0}; + coord::phys3 p1{1000, 0, 0}; + + legacy::node_pt n0 = std::make_unique(p0, nullptr); + legacy::node_pt n1 = std::make_unique(p1, n0); + + TESTEQUALS(path::legacy::passable_line(n0, n1, path::tests::always_passable), true); + TESTEQUALS(path::legacy::passable_line(n0, n1, path::tests::not_passable), false); + + // The next 2 cases show that a different sample can change the results + // for the same path + TESTEQUALS(path::legacy::passable_line(n0, n1, path::tests::sometimes_passable, 10), true); + TESTEQUALS(path::legacy::passable_line(n0, n1, path::tests::sometimes_passable, 50), false); +} + +/** + * Top level node test. + */ +void path_node() { + node_0(); + node_cost_to_0(); + node_cost_to_1(); + node_generate_backtrace_0(); + node_get_neighbors_0(); + node_passable_line_0(); +} + +} // namespace tests +} // namespace path +} // namespace openage diff --git a/libopenage/pathfinding/path.cpp b/libopenage/pathfinding/path.cpp index c04e5876c4..fe13989e77 100644 --- a/libopenage/pathfinding/path.cpp +++ b/libopenage/pathfinding/path.cpp @@ -1,135 +1,10 @@ -// Copyright 2014-2019 the openage authors. See copying.md for legal info. - -#include +// Copyright 2024-2024 the openage authors. See copying.md for legal info. #include "path.h" -#include "../terrain/terrain.h" - -namespace openage::path { - - -bool compare_node_cost::operator ()(const node_pt &lhs, const node_pt &rhs) const { - // TODO: use node operator < - return lhs->future_cost < rhs->future_cost; -} - - -Node::Node(const coord::phys3 &pos, node_pt prev) - : - position(pos), - tile_position(pos.to_tile3().to_tile()), - direction{}, - visited{false}, - was_best{false}, - factor{1.0f}, - path_predecessor{prev}, - heap_node(nullptr) { - - if (prev) { - this->direction = (this->position - prev->position).normalize(); - - // TODO: add dot product to coord - cost_t similarity = ((this->direction.ne.to_float() * - prev->direction.ne.to_float()) + - (this->direction.se.to_float() * - prev->direction.se.to_float())); - this->factor += (1 - similarity); - } -} - - -Node::Node(const coord::phys3 &pos, node_pt prev, cost_t past, cost_t heuristic) - : - Node{pos, prev} { - this->past_cost = past; - this->heuristic_cost = heuristic; - this->future_cost = past + heuristic; -} - - -bool Node::operator <(const Node &other) const { - return this->future_cost < other.future_cost; -} - - -bool Node::operator ==(const Node &other) const { - return this->position == other.position; -} -cost_t Node::cost_to(const Node &other) const { - // ignore the up-position, thus convert to phys2 - return ((this->position - other.position).to_phys2().length() - * other.factor * this->factor); -} - - -Path Node::generate_backtrace() { - std::vector waypoints; - - node_pt current = this->shared_from_this(); - do { - Node other = *current; - waypoints.push_back(*current); - current = current->path_predecessor; - } while (current != nullptr); - waypoints.pop_back(); // remove start - - return {waypoints}; -} - - -std::vector Node::get_neighbors(const nodemap_t &nodes, float scale) { - std::vector neighbors; - neighbors.reserve(8); - for (int n = 0; n < 8; ++n) { - coord::phys3 n_pos = this->position + (neigh_phys[n] * scale); - - if (nodes.count(n_pos) > 0) { - neighbors.push_back( nodes.at(n_pos) ); - } - else { - neighbors.push_back( std::make_shared(n_pos, this->shared_from_this()) ); - } - } - return neighbors; -} - - -bool passable_line(node_pt start, node_pt end, std::function passable, float samples) { - // interpolate between points and make passablity checks - // (dont check starting position) - for (int i = 1; i <= samples; ++i) { - // TODO: needs more fixed-point - double percent = static_cast(i) / samples; - coord::phys_t ne = (1.0 - percent) * start->position.ne.to_double() + percent * end->position.ne.to_double(); - coord::phys_t se = (1.0 - percent) * start->position.se.to_double() + percent * end->position.se.to_double(); - coord::phys_t up = (1.0 - percent) * start->position.up.to_double() + percent * end->position.up.to_double(); - - if (!passable(coord::phys3{ne, se, up})) { - return false; - } - } - return true; -} - - -Path::Path(const std::vector &nodes) - : - waypoints{nodes} {} - - -void Path::draw_path(const coord::CoordManager &mgr) { - glLineWidth(1); - glColor3f(0.3, 1.0, 0.3); - glBegin(GL_LINES); { - for (Node &n : waypoints) { - coord::viewport draw_pos = n.position.to_viewport(mgr); - glVertex3f(draw_pos.x, draw_pos.y, 0); - } - } - glEnd(); -} +namespace openage::path { +// this file is intentionally empty -} // openage::path +} // namespace openage::path diff --git a/libopenage/pathfinding/path.h b/libopenage/pathfinding/path.h index 16784cbd90..df1b34a599 100644 --- a/libopenage/pathfinding/path.h +++ b/libopenage/pathfinding/path.h @@ -1,217 +1,42 @@ -// Copyright 2014-2019 the openage authors. See copying.md for legal info. +// Copyright 2024-2024 the openage authors. See copying.md for legal info. #pragma once -#include -#include -#include #include -#include "../coord/phys.h" -#include "../coord/tile.h" -#include "../datastructure/pairing_heap.h" -#include "../util/misc.h" -#include "../util/hash.h" +#include "coord/tile.h" +#include "pathfinding/types.h" +#include "time/time.h" - -namespace openage { - -namespace coord { -class CoordManager; -} - -namespace path { - -class Node; -class Path; - -/** - * The data type for movement cost - */ -using cost_t = float; - -/** - * Type for storing navigation nodes. - */ -using node_pt = std::shared_ptr; - -/** - * Type for mapping tiles to nodes. - */ -using nodemap_t = std::unordered_map; - - -/** - * Cost comparison for node_pt. - * Extracts the ptr from the shared_ptr. - * Calls operator < on Node. - */ -struct compare_node_cost { - bool operator ()(const node_pt &lhs, const node_pt &rhs) const; -}; - -/** - * Priority queue node item type. - */ -using heap_t = datastructure::PairingHeap; - -/** - * Size of phys-coord grid for path nodes. - */ -constexpr coord::phys_t path_grid_size{1.f/8}; - -/** - * Phys3 delta coordinates to select for path neighbors. - */ -constexpr coord::phys3_delta const neigh_phys[] = { - {path_grid_size * 1, path_grid_size * -1, 0}, - {path_grid_size * 1, path_grid_size * 0, 0}, - {path_grid_size * 1, path_grid_size * 1, 0}, - {path_grid_size * 0, path_grid_size * 1, 0}, - {path_grid_size * -1, path_grid_size * 1, 0}, - {path_grid_size * -1, path_grid_size * 0, 0}, - {path_grid_size * -1, path_grid_size * -1, 0}, - {path_grid_size * 0, path_grid_size * -1, 0} -}; +namespace openage::path { /** - * + * Path request for the pathfinder. */ -bool passable_line(node_pt start, node_pt end, std::functionpassable, float samples=5.0f); - -/** - * One navigation waypoint in a path. - */ -class Node: public std::enable_shared_from_this { -public: - Node(const coord::phys3 &pos, node_pt prev); - Node(const coord::phys3 &pos, node_pt prev, cost_t past, cost_t heuristic); - - /** - * Orders nodes according to their future cost value. - */ - bool operator <(const Node &other) const; - - /** - * Compare the node to another one. - * They are the same if their position is. - */ - bool operator ==(const Node &other) const; - - /** - * Calculates the actual movement cose to another node. - */ - cost_t cost_to(const Node &other) const; - - /** - * Create a backtrace path beginning at this node. - */ - Path generate_backtrace(); - - /** - * Get all neighbors of this graph node. - */ - std::vector get_neighbors(const nodemap_t &, float scale=1.0f); - - /** - * The tile position this node is associated to. - * todo make const - */ - coord::phys3 position; - coord::tile tile_position; - coord::phys3_delta direction; // for path smoothing - - /** - * Future cost estimation value for this node. - */ - cost_t future_cost; - - /** - * Evaluated past cost value for the node. - * This stores the actual cost from start to this node. - */ - cost_t past_cost; - - /** - * Heuristic cost cache. - * Calculated once, is the heuristic distance from this node - * to the goal. - */ - cost_t heuristic_cost; - - /** - * Can this node be passed? - */ - bool accessible = false; - - /** - * Has this Node been visited? - */ - bool visited = false; - - /** - * Does this node already have an alternative path? - * If the node was once selected as the best next hop, - * this is set to true. - */ - bool was_best = false; - - /** - * Factor to adjust movement cost. - * default: 1 - */ - cost_t factor; - - /** - * Node where this one was reached by least cost. - */ - node_pt path_predecessor; - - /** - * Priority queue node that contains this path node. - */ - heap_t::element_t heap_node; -}; - - -/** - * Represents a planned trajectory. - * Generated by pathfinding algorithms. - */ -class Path { -public: - Path() = default; - Path(const std::vector &nodes); - - void draw_path(const coord::CoordManager &mgr); - - /** - * These are the waypoints to navigate in order. - * Includes the start and end node. - */ - std::vector waypoints; +struct PathRequest { + /// ID of the grid to use for pathfinding. + size_t grid_id; + /// Start position of the path. + coord::tile start; + /// Target position of the path. + coord::tile target; + /// Time the request was made. + const time::time_t time; }; -} // namespace path -} // namespace openage - - -namespace std { - /** - * Hash function for path nodes. - * Just uses their position. + * Path found by the pathfinder. */ -template<> -struct hash { - size_t operator ()(const openage::path::Node &x) const { - openage::coord::phys3 node_pos = x.position; - size_t hash = openage::util::type_hash(); - hash = openage::util::hash_combine(hash, std::hash{}(node_pos.ne)); - hash = openage::util::hash_combine(hash, std::hash{}(node_pos.se)); - return hash; - } +struct Path { + /// ID of the grid to used for pathfinding. + size_t grid_id; + /// Status + PathResult status; + /// Waypoints of the path. + /// First waypoint is the start position of the path request. + /// Last waypoint is the target position of the path request. + std::vector waypoints; }; -} // namespace std +} // namespace openage::path diff --git a/libopenage/pathfinding/pathfinder.cpp b/libopenage/pathfinding/pathfinder.cpp new file mode 100644 index 0000000000..5a9c785f0c --- /dev/null +++ b/libopenage/pathfinding/pathfinder.cpp @@ -0,0 +1,568 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "pathfinder.h" + +#include "coord/chunk.h" +#include "coord/phys.h" +#include "error/error.h" +#include "pathfinding/cost_field.h" +#include "pathfinding/flow_field.h" +#include "pathfinding/grid.h" +#include "pathfinding/integration_field.h" +#include "pathfinding/integrator.h" +#include "pathfinding/portal.h" +#include "pathfinding/sector.h" + + +namespace openage::path { + +Pathfinder::Pathfinder() : + grids{}, + integrator{std::make_shared()} { +} + +const Path Pathfinder::get_path(const PathRequest &request) { + auto grid = this->grids.at(request.grid_id); + auto sector_size = grid->get_sector_size(); + + // Check if the target is within the grid + auto grid_size = grid->get_size(); + auto grid_width = grid_size[0] * sector_size; + auto grid_height = grid_size[1] * sector_size; + if (request.target.ne < 0 + or request.target.se < 0 + or request.target.ne >= static_cast(grid_width) + or request.target.se >= static_cast(grid_height)) { + log::log(DBG << "Path not found (start = " + << request.start << "; target = " + << request.target << "): " + << "Target is out of bounds."); + return Path{request.grid_id, PathResult::OUT_OF_BOUNDS, {}}; + } + + auto start_sector_x = request.start.ne / sector_size; + auto start_sector_y = request.start.se / sector_size; + auto start_sector = grid->get_sector(start_sector_x, start_sector_y); + + auto target_sector_x = request.target.ne / sector_size; + auto target_sector_y = request.target.se / sector_size; + auto target_sector = grid->get_sector(target_sector_x, target_sector_y); + + auto target = request.target - target_sector->get_position().to_tile(sector_size); + if (target_sector->get_cost_field()->get_cost(target) == COST_IMPASSABLE) { + // TODO: This may be okay if the target is a building or unit + log::log(DBG << "Path not found (start = " + << request.start << "; target = " + << request.target << "): " + << "Target is impassable."); + return Path{request.grid_id, PathResult::NOT_FOUND, {}}; + } + + // Integrate the target field + coord::tile_delta target_delta = request.target - target_sector->get_position().to_tile(sector_size); + auto target_integration_field = this->integrator->integrate(target_sector->get_cost_field(), + target_delta); + + if (target_sector == start_sector) { + auto start = request.start - start_sector->get_position().to_tile(sector_size); + + if (target_integration_field->get_cell(start.ne, start.se).cost != INTEGRATED_COST_UNREACHABLE) { + // Exit early if the start and target are in the same sector + // and are reachable from within the same sector + auto flow_field = this->integrator->build(target_integration_field); + auto flow_field_waypoints = this->get_waypoints({std::make_pair(target_sector->get_id(), flow_field)}, request); + + std::vector waypoints{}; + if (flow_field_waypoints.at(0) != request.start) { + waypoints.push_back(request.start); + } + waypoints.insert(waypoints.end(), flow_field_waypoints.begin(), flow_field_waypoints.end()); + + log::log(DBG << "Path found (start = " + << request.start << "; target = " + << request.target << "): " + << "Path is within the same sector."); + return Path{request.grid_id, PathResult::FOUND, waypoints}; + } + } + + // Check which portals are reachable from the target field + std::unordered_set target_portal_ids; + for (auto &portal : target_sector->get_portals()) { + auto center_cell = portal->get_entry_center(target_sector->get_id()); + + if (target_integration_field->get_cell(center_cell).cost != INTEGRATED_COST_UNREACHABLE) { + target_portal_ids.insert(portal->get_id()); + } + } + + // Check which portals are reachable from the start field + coord::tile_delta start = request.start - start_sector->get_position().to_tile(sector_size); + auto start_integration_field = this->integrator->integrate(start_sector->get_cost_field(), + start, + false); + + std::unordered_set start_portal_ids; + for (auto &portal : start_sector->get_portals()) { + auto center_cell = portal->get_entry_center(start_sector->get_id()); + + if (start_integration_field->get_cell(center_cell).cost != INTEGRATED_COST_UNREACHABLE) { + start_portal_ids.insert(portal->get_id()); + } + } + + if (target_portal_ids.empty() or start_portal_ids.empty()) { + // Exit early if no portals are reachable from the start or target + log::log(DBG << "Path not found (start = " + << request.start << "; target = " + << request.target << "): " + << "No portals are reachable from the start or target."); + return Path{request.grid_id, PathResult::NOT_FOUND, {}}; + } + + // High-level pathfinding + // Find the portals to use to get from the start to the target + auto portal_result = this->portal_a_star(request, target_portal_ids, start_portal_ids); + auto portal_status = portal_result.first; + auto portal_path = portal_result.second; + + // Low-level pathfinding + // Find the path within the sectors + + // Build flow field for the target sector + auto prev_integration_field = target_integration_field; + auto prev_flow_field = this->integrator->build(prev_integration_field); + auto prev_sector_id = target_sector->get_id(); + + Integrator::get_return_t sector_fields{prev_integration_field, prev_flow_field}; + + std::vector>> flow_fields; + flow_fields.reserve(portal_path.size() + 1); + flow_fields.push_back(std::make_pair(target_sector->get_id(), sector_fields.second)); + + int los_depth = 1; + + for (auto &portal : portal_path) { + auto prev_sector = grid->get_sector(prev_sector_id); + auto next_sector_id = portal->get_exit_sector(prev_sector_id); + auto next_sector = grid->get_sector(next_sector_id); + + target_delta = request.target - next_sector->get_position().to_tile(sector_size); + bool with_los = los_depth > 0; + + sector_fields = this->integrator->get(next_sector->get_cost_field(), + prev_integration_field, + prev_sector_id, + portal, + target_delta, + request.time, + with_los); + flow_fields.push_back(std::make_pair(next_sector_id, sector_fields.second)); + + prev_integration_field = sector_fields.first; + prev_sector_id = next_sector_id; + los_depth -= 1; + } + + // reverse the flow fields so they are ordered from start to target + std::reverse(flow_fields.begin(), flow_fields.end()); + + // traverse the flow fields to get the waypoints + auto flow_field_waypoints = this->get_waypoints(flow_fields, request); + std::vector waypoints{}; + if (flow_field_waypoints.at(0) != request.start) { + waypoints.push_back(request.start); + } + waypoints.insert(waypoints.end(), flow_field_waypoints.begin(), flow_field_waypoints.end()); + + if (portal_status == PathResult::NOT_FOUND) { + log::log(DBG << "Path not found (start = " + << request.start << "; target = " + << request.target << ")"); + } + else { + log::log(DBG << "Path found (start = " + << request.start << "; target = " + << request.target << ")"); + } + + return Path{request.grid_id, portal_status, waypoints}; +} + +const std::shared_ptr &Pathfinder::get_grid(grid_id_t id) const { + return this->grids.at(id); +} + +void Pathfinder::add_grid(const std::shared_ptr &grid) { + this->grids[grid->get_id()] = grid; +} + +const Pathfinder::portal_star_t Pathfinder::portal_a_star(const PathRequest &request, + const std::unordered_set &target_portal_ids, + const std::unordered_set &start_portal_ids) const { + std::vector> result; + + auto grid = this->grids.at(request.grid_id); + auto &portal_map = grid->get_portal_map(); + auto sector_size = grid->get_sector_size(); + + auto start_sector_x = request.start.ne / sector_size; + auto start_sector_y = request.start.se / sector_size; + auto start_sector = grid->get_sector(start_sector_x, start_sector_y); + + // path node storage, always provides cheapest next node. + heap_t node_candidates; + + std::unordered_set visited_portals; + + // TODO: Compute cost to travel from one portal to another when creating portals + // const int distance_cost = 1; + + // create start nodes + for (auto &portal : start_sector->get_portals()) { + if (not start_portal_ids.contains(portal->get_id())) { + // only consider portals that are reachable from the start cell + continue; + } + + auto &portal_node = portal_map.at(portal->get_id()); + portal_node->entry_sector = start_sector->get_id(); + + auto sector_pos = grid->get_sector(portal->get_exit_sector(start_sector->get_id()))->get_position().to_tile(sector_size); + 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 = Pathfinder::heuristic_cost(portal_abs_pos, request.start); + portal_node->heuristic_cost = heuristic_cost; + portal_node->future_cost = portal_node->current_cost + heuristic_cost; + + portal_node->heap_node = node_candidates.push(portal_node); + portal_node->prev_portal = nullptr; + portal_node->was_best = false; + visited_portals.insert(portal->get_id()); + } + + // track the closest we can get to the end position + // used when no path is found + auto closest_node = node_candidates.top(); + + // while there are candidates to visit + while (not node_candidates.empty()) { + auto current_node = node_candidates.pop(); + + current_node->was_best = true; + + // check if the current node is a portal in the target sector that can + // be reached from the target cell + auto exit_portal_id = current_node->portal->get_id(); + if (target_portal_ids.contains(exit_portal_id)) { + auto backtrace = current_node->generate_backtrace(); + for (auto &node : backtrace) { + result.push_back(node->portal); + } + log::log(DBG << "Portal path found with " << result.size() << " portal traversals."); + return std::make_pair(PathResult::FOUND, result); + } + + // check if the current node is the closest to the target + if (current_node->heuristic_cost < closest_node->heuristic_cost) { + closest_node = current_node; + } + + // get the exits of the current node + 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.value()); + bool not_visited = !visited_portals.contains(exit->portal->get_id()); + + if (not_visited) { + exit->was_best = false; + } + else if (exit->was_best) { + continue; + } + + + auto tentative_cost = current_node->current_cost + distance_cost; + + 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.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.value()); + exit->heuristic_cost = Pathfinder::heuristic_cost( + exit_sector_pos + exit_portal_pos, + request.target); + } + + // update the cost knowledge + exit->current_cost = tentative_cost; + exit->future_cost = exit->current_cost + exit->heuristic_cost; + exit->prev_portal = current_node; + + if (not_visited) { + exit->heap_node = node_candidates.push(exit); + visited_portals.insert(exit->portal->get_id()); + } + else { + node_candidates.decrease(exit->heap_node); + } + } + } + } + + // no path found, return the closest node + auto backtrace = closest_node->generate_backtrace(); + for (auto &node : backtrace) { + result.push_back(node->portal); + } + + log::log(DBG << "Portal path not found."); + log::log(DBG << "Closest portal: " << closest_node->portal->get_id()); + return std::make_pair(PathResult::NOT_FOUND, result); +} + +const std::vector Pathfinder::get_waypoints(const std::vector>> &flow_fields, + const PathRequest &request) const { + ENSURE(flow_fields.size() > 0, "At least 1 flow field is required for finding waypoints."); + + std::vector waypoints; + + auto grid = this->get_grid(request.grid_id); + auto sector_size = grid->get_sector_size(); + coord::tile_t start_x = request.start.ne % sector_size; + coord::tile_t start_y = request.start.se % sector_size; + + bool los_reached = false; + + coord::tile_t current_x = start_x; + coord::tile_t current_y = start_y; + flow_dir_t current_direction = flow_fields.at(0).second->get_dir(current_x, current_y); + for (size_t i = 0; i < flow_fields.size(); ++i) { + auto §or = grid->get_sector(flow_fields[i].first); + auto sector_pos = sector->get_position().to_tile(sector_size); + auto &flow_field = flow_fields[i].second; + + // navigate the flow field vectors until we reach its edge (or the target) + flow_t cell; + do { + cell = flow_field->get_cell(current_x, current_y); + + if (cell & FLOW_LOS_MASK) { + // check if we reached an LOS cell + auto cell_pos = sector_pos + coord::tile_delta(current_x, current_y); + waypoints.push_back(cell_pos); + los_reached = true; + break; + } + + // check if we need to change direction + auto cell_direction = static_cast(cell & FLOW_DIR_MASK); + if (cell_direction != current_direction) { + // add the current cell as a waypoint + auto cell_pos = sector_pos + coord::tile_delta(current_x, current_y); + waypoints.push_back(cell_pos); + current_direction = cell_direction; + } + + // move to the next cell + switch (current_direction) { + case flow_dir_t::NORTH: + current_y -= 1; + break; + case flow_dir_t::NORTH_EAST: + current_x += 1; + current_y -= 1; + break; + case flow_dir_t::EAST: + current_x += 1; + break; + case flow_dir_t::SOUTH_EAST: + current_x += 1; + current_y += 1; + break; + case flow_dir_t::SOUTH: + current_y += 1; + break; + case flow_dir_t::SOUTH_WEST: + current_x -= 1; + current_y += 1; + break; + case flow_dir_t::WEST: + current_x -= 1; + break; + case flow_dir_t::NORTH_WEST: + current_x -= 1; + current_y -= 1; + break; + default: + throw Error{ERR << "Invalid flow direction: " << static_cast(current_direction)}; + } + } + while (not(cell & FLOW_TARGET_MASK)); + + if (los_reached or i == flow_fields.size() - 1) { + // exit the loop if we found an LOS cell or reached + // the target cell in the last flow field + break; + } + + // reset the current position for the next flow field + switch (current_direction) { + case flow_dir_t::NORTH: + current_y = sector_size - 1; + break; + case flow_dir_t::NORTH_EAST: + current_x = current_x + 1; + current_y = sector_size - 1; + break; + case flow_dir_t::EAST: + current_x = 0; + break; + case flow_dir_t::SOUTH_EAST: + current_x = 0; + current_y = current_y + 1; + break; + case flow_dir_t::SOUTH: + current_y = 0; + break; + case flow_dir_t::SOUTH_WEST: + current_x = current_x - 1; + current_y = 0; + break; + case flow_dir_t::WEST: + current_x = sector_size - 1; + break; + case flow_dir_t::NORTH_WEST: + current_x = sector_size - 1; + current_y = current_y - 1; + break; + default: + throw Error{ERR << "Invalid flow direction: " << static_cast(current_direction)}; + } + } + + // add the target position as the last waypoint + waypoints.push_back(request.target); + + return waypoints; +} + +int Pathfinder::heuristic_cost(const coord::tile &portal_pos, + const coord::tile &target_pos) { + auto portal_phys_pos = portal_pos.to_phys2(); + auto target_phys_pos = target_pos.to_phys2(); + auto delta = target_phys_pos - portal_phys_pos; + + return delta.length(); +} + +int Pathfinder::distance_cost(const coord::tile_delta &portal1_pos, + const coord::tile_delta &portal2_pos) { + auto delta = portal2_pos.to_phys2() - portal1_pos.to_phys2(); + + return delta.length(); +} + + +PortalNode::PortalNode(const std::shared_ptr &portal) : + portal{portal}, + entry_sector{std::nullopt}, + future_cost{std::numeric_limits::max()}, + current_cost{std::numeric_limits::max()}, + heuristic_cost{std::numeric_limits::max()}, + was_best{false}, + prev_portal{nullptr}, + heap_node{nullptr} {} + +PortalNode::PortalNode(const std::shared_ptr &portal, + sector_id_t entry_sector, + const node_t &prev_portal) : + portal{portal}, + entry_sector{entry_sector}, + future_cost{std::numeric_limits::max()}, + current_cost{std::numeric_limits::max()}, + heuristic_cost{std::numeric_limits::max()}, + was_best{false}, + prev_portal{prev_portal}, + heap_node{nullptr} {} + +PortalNode::PortalNode(const std::shared_ptr &portal, + sector_id_t entry_sector, + const node_t &prev_portal, + int past_cost, + int heuristic_cost) : + portal{portal}, + entry_sector{entry_sector}, + future_cost{past_cost + heuristic_cost}, + current_cost{past_cost}, + heuristic_cost{heuristic_cost}, + was_best{false}, + prev_portal{prev_portal}, + heap_node{nullptr} { +} + +bool PortalNode::operator<(const PortalNode &other) const { + return this->future_cost < other.future_cost; +} + +bool PortalNode::operator==(const PortalNode &other) const { + return this->portal->get_id() == other.portal->get_id(); +} + +std::vector PortalNode::generate_backtrace() { + std::vector waypoints; + + node_t current = this->shared_from_this(); + do { + waypoints.push_back(current); + current = current->prev_portal; + } + while (current != nullptr); + + return waypoints; +} + +void PortalNode::init_exits(const nodemap_t &node_map) { + auto exits = this->portal->get_exits(this->node_sector_0); + for (auto &exit : exits) { + int distance_cost = Pathfinder::distance_cost( + this->portal->get_exit_center(this->node_sector_0), + exit->get_entry_center(this->node_sector_1)); + + auto exit_node = node_map.at(exit->get_id()); + this->exits_1[exit_node] = distance_cost; + } + + exits = this->portal->get_exits(this->node_sector_1); + for (auto &exit : exits) { + int distance_cost = Pathfinder::distance_cost( + this->portal->get_exit_center(this->node_sector_1), + exit->get_entry_center(this->node_sector_0)); + + auto exit_node = node_map.at(exit->get_id()); + this->exits_0[exit_node] = distance_cost; + } +} + +const PortalNode::exits_t &PortalNode::get_exits(sector_id_t entry_sector) { + ENSURE(entry_sector == this->node_sector_0 || entry_sector == this->node_sector_1, "Invalid entry sector"); + + if (this->node_sector_0 == entry_sector) { + return exits_1; + } + else { + return exits_0; + } +} + + +bool compare_node_cost::operator()(const node_t &lhs, const node_t &rhs) const { + return *lhs < *rhs; +} + +} // namespace openage::path diff --git a/libopenage/pathfinding/pathfinder.h b/libopenage/pathfinding/pathfinder.h new file mode 100644 index 0000000000..8d569d78d8 --- /dev/null +++ b/libopenage/pathfinding/pathfinder.h @@ -0,0 +1,272 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include +#include + +#include "coord/tile.h" +#include "datastructure/pairing_heap.h" +#include "pathfinding/path.h" +#include "pathfinding/types.h" + + +namespace openage::path { +class Grid; +class Integrator; +class Portal; +class FlowField; + +/** + * Pathfinder for flow field pathfinding. + * + * The pathfinder manages the grids defining the pathable ingame areas and + * provides an interface for making pathfinding requests. + * + * Pathfinding consists of a multi-step process: First, there is a high-level + * search using A* to identify the sectors of the grid that should be traversed. + * Afterwards, flow fields are calculated from the target sector to the start + * sector, which are then used to guide the actual unit movement. + */ +class Pathfinder { +public: + /** + * Create a new pathfinder. + */ + Pathfinder(); + ~Pathfinder() = default; + + /** + * Get the grid at a specified index. + * + * @param id ID of the grid. + * + * @return Pathfinding grid. + */ + const std::shared_ptr &get_grid(grid_id_t id) const; + + /** + * Add a grid to the pathfinder. + * + * @param grid Grid to add. + */ + void add_grid(const std::shared_ptr &grid); + + /** + * Get the path for a pathfinding request. + * + * @param request Pathfinding request. + * + * @return Path found by the pathfinder. + */ + const Path get_path(const PathRequest &request); + + + /** + * Calculate the distance cost between two portals. + * + * @param portal1_pos Center of the first portal (relative to sector origin). + * @param portal2_pos Center of the second portal (relative to sector origin). + * + * @return Distance cost between the portal centers. + */ + static int distance_cost(const coord::tile_delta &portal1_pos, const coord::tile_delta &portal2_pos); + +private: + using portal_star_t = std::pair>>; + + /** + * High-level pathfinder. Uses A* to find the path through the portals of sectors. + * + * @param request Pathfinding request. + * @param target_portal_ids IDs of portals that can be reached from the target cell. + * @param start_portal_ids IDs of portals that can be reached from the start cell. + * + * @return Portals to traverse in order to reach the target. + */ + const portal_star_t portal_a_star(const PathRequest &request, + const std::unordered_set &target_portal_ids, + const std::unordered_set &start_portal_ids) const; + + /** + * Low-level pathfinder. Uses flow fields to find the path through the sectors. + * + * @param flow_fields Flow fields for the sectors. + * @param request Pathfinding request. + * + * @return Waypoint coordinates to traverse in order to reach the target. + */ + const std::vector get_waypoints(const std::vector>> &flow_fields, + const PathRequest &request) const; + + /** + * Calculate the heuristic cost between a portal and a target cell. + * + * @param portal_pos Position of the portal (absolute on the grid). + * This should be the center of the portal exit. + * @param target_pos Position of the target cell (absolute on the grid). + * + * @return Heuristic cost between the cells. + */ + static int heuristic_cost(const coord::tile &portal_pos, const coord::tile &target_pos); + + + /** + * Grids managed by this pathfinder. + * + * Each grid can have separate pathing. + */ + std::unordered_map> grids; + + /** + * Integrator for flow field calculations. + */ + std::shared_ptr integrator; +}; + + +class PortalNode; + +using node_t = std::shared_ptr; + +/** + * Cost comparison for node_t on the pairing heap. + * + * Extracts the nodes from the shared_ptr and compares them. We have + * to use a custom comparison function because otherwise the shared_ptr + * would be compared instead of the actual node. + */ +struct compare_node_cost { + bool operator()(const node_t &lhs, const node_t &rhs) const; +}; + +using heap_t = datastructure::PairingHeap; +using nodemap_t = std::unordered_map; + +/** + * One navigation waypoint in a path. + */ +class PortalNode : public std::enable_shared_from_this { +public: + PortalNode(const std::shared_ptr &portal); + PortalNode(const std::shared_ptr &portal, + sector_id_t entry_sector, + const node_t &prev_portal); + PortalNode(const std::shared_ptr &portal, + sector_id_t entry_sector, + const node_t &prev_portal, + int past_cost, + int heuristic_cost); + + /** + * Orders nodes according to their future cost value. + */ + bool operator<(const PortalNode &other) const; + + /** + * Compare the node to another one. + * They are the same if their portal is. + */ + bool operator==(const PortalNode &other) const; + + /** + * Calculates the actual movement cose to another node. + */ + int cost_to(const PortalNode &other) const; + + /** + * Create a backtrace path beginning at this node. + */ + std::vector generate_backtrace(); + + /** + * init PortalNode::exits. + */ + void init_exits(const nodemap_t &node_map); + + + /** + * maps node_t of a neigbhour portal to the distance cost to travel between the portals + */ + using exits_t = std::map; + + + /** + * Get the exit portals reachable via the portal when entering from a specified sector. + * + * @param entry_sector Sector from which the portal is entered. + * + * @return Exit portals nodes reachable from the portal. + */ + const exits_t &get_exits(sector_id_t entry_sector); + + /** + * The portal this node is associated to. + */ + std::shared_ptr portal; + + /** + * Sector where the portal is entered. + */ + std::optional entry_sector; + + /** + * Future cost estimation value for this node. + */ + int future_cost; + + /** + * Evaluated past cost value for the node. + * This stores the actual cost from start to this node. + */ + int current_cost; + + /** + * Heuristic cost cache. + * Calculated once, is the heuristic distance from this node + * to the goal. + */ + int heuristic_cost; + + /** + * Does this node already have an alternative path? + * If the node was once selected as the best next hop, + * this is set to true. + */ + bool was_best = false; + + /** + * Node where this one was reached by least cost. + */ + node_t prev_portal; + + /** + * Priority queue node that contains this path node. + */ + heap_t::element_t heap_node; + + /** + * First sector connected by the portal. + */ + sector_id_t node_sector_0; + + /** + * Second sector connected by the portal. + */ + sector_id_t node_sector_1; + + /** + * Exits in sector 0 reachable from the portal. + */ + exits_t exits_0; + + /** + * Exits in sector 1 reachable from the portal. + */ + exits_t exits_1; +}; + + +} // namespace openage::path diff --git a/libopenage/pathfinding/portal.cpp b/libopenage/pathfinding/portal.cpp new file mode 100644 index 0000000000..9b8f64cd51 --- /dev/null +++ b/libopenage/pathfinding/portal.cpp @@ -0,0 +1,162 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "portal.h" + +#include "error/error.h" + + +namespace openage::path { + +Portal::Portal(portal_id_t id, + sector_id_t sector0, + sector_id_t sector1, + PortalDirection direction, + const coord::tile_delta &cell_start, + const coord::tile_delta &cell_end) : + id{id}, + sector0{sector0}, + sector1{sector1}, + sector0_exits{}, + sector1_exits{}, + direction{direction}, + cell_start{cell_start}, + cell_end{cell_end} { +} + +portal_id_t Portal::get_id() const { + return this->id; +} + +const std::vector> &Portal::get_connected(sector_id_t sector) const { + ENSURE(sector == this->sector0 || sector == this->sector1, "Portal does not connect to sector"); + + if (sector == this->sector0) { + return this->sector0_exits; + } + return this->sector1_exits; +} + +const std::vector> &Portal::get_exits(sector_id_t entry_sector) const { + ENSURE(entry_sector == this->sector0 || entry_sector == this->sector1, "Invalid entry sector"); + + if (entry_sector == this->sector0) { + return this->sector1_exits; + } + return this->sector0_exits; +} + +void Portal::set_exits(sector_id_t sector, const std::vector> &exits) { + ENSURE(sector == this->sector0 || sector == this->sector1, "Portal does not connect to sector"); + + if (sector == this->sector0) { + this->sector0_exits = exits; + } + else { + this->sector1_exits = exits; + } +} + +sector_id_t Portal::get_exit_sector(sector_id_t entry_sector) const { + ENSURE(entry_sector == this->sector0 || entry_sector == this->sector1, "Invalid entry sector"); + + if (entry_sector == this->sector0) { + return this->sector1; + } + return this->sector0; +} + +const coord::tile_delta Portal::get_entry_start(sector_id_t entry_sector) const { + ENSURE(entry_sector == this->sector0 || entry_sector == this->sector1, "Invalid entry sector"); + + if (entry_sector == this->sector0) { + return this->get_sector0_start(); + } + + return this->get_sector1_start(); +} + +const coord::tile_delta Portal::get_entry_center(sector_id_t entry_sector) const { + ENSURE(entry_sector == this->sector0 || entry_sector == this->sector1, "Invalid entry sector"); + + if (entry_sector == this->sector0) { + auto start = this->get_sector0_start(); + auto end = this->get_sector0_end(); + return {(start.ne + end.ne) / 2, (start.se + end.se) / 2}; + } + + auto start = this->get_sector1_start(); + auto end = this->get_sector1_end(); + return {(start.ne + end.ne) / 2, (start.se + end.se) / 2}; +} + +const coord::tile_delta Portal::get_entry_end(sector_id_t entry_sector) const { + ENSURE(entry_sector == this->sector0 || entry_sector == this->sector1, "Invalid entry sector"); + + if (entry_sector == this->sector0) { + return this->get_sector0_end(); + } + + return this->get_sector1_end(); +} + +const coord::tile_delta Portal::get_exit_start(sector_id_t entry_sector) const { + ENSURE(entry_sector == this->sector0 || entry_sector == this->sector1, "Invalid entry sector"); + + if (entry_sector == this->sector0) { + return this->get_sector1_start(); + } + + return this->get_sector0_start(); +} + +const coord::tile_delta Portal::get_exit_center(sector_id_t entry_sector) const { + ENSURE(entry_sector == this->sector0 || entry_sector == this->sector1, "Invalid entry sector"); + + if (entry_sector == this->sector0) { + auto start = this->get_sector1_start(); + auto end = this->get_sector1_end(); + return {(start.ne + end.ne) / 2, (start.se + end.se) / 2}; + } + + auto start = this->get_sector0_start(); + auto end = this->get_sector0_end(); + return {(start.ne + end.ne) / 2, (start.se + end.se) / 2}; +} + +const coord::tile_delta Portal::get_exit_end(sector_id_t entry_sector) const { + ENSURE(entry_sector == this->sector0 || entry_sector == this->sector1, "Invalid entry sector"); + + if (entry_sector == this->sector0) { + return this->get_sector1_end(); + } + + return this->get_sector0_end(); +} + +PortalDirection Portal::get_direction() const { + return this->direction; +} + +const coord::tile_delta &Portal::get_sector0_start() const { + return this->cell_start; +} + +const coord::tile_delta &Portal::get_sector0_end() const { + return this->cell_end; +} + +const coord::tile_delta Portal::get_sector1_start() const { + if (this->direction == PortalDirection::NORTH_SOUTH) { + return {this->cell_start.ne, 0}; + } + return {0, this->cell_start.se}; +} + +const coord::tile_delta Portal::get_sector1_end() const { + if (this->direction == PortalDirection::NORTH_SOUTH) { + return {this->cell_end.ne, 0}; + } + return {0, this->cell_end.se}; +} + +} // namespace openage::path diff --git a/libopenage/pathfinding/portal.h b/libopenage/pathfinding/portal.h new file mode 100644 index 0000000000..6593eea852 --- /dev/null +++ b/libopenage/pathfinding/portal.h @@ -0,0 +1,246 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include + +#include "coord/tile.h" +#include "pathfinding/types.h" + + +namespace openage::path { +class CostField; + +/** + * Possible directions of a portal node. + */ +enum class PortalDirection { + NORTH_SOUTH, + EAST_WEST +}; + +/** + * Biderectional gateway for connecting two sectors in the flow field pathfinder. + * + * Portals are located at the border of two sectors (0 and 1), and allow units to move between them. + * For each of these sectors, the portal stores the start and end coordinates where the + * sectors overlap as well as the other portals that can be reached in the same + * sector. For simplicity, the portal is assumed to be a straight line of cells from the start + * to the end. + * + * The portal is bidirectional, meaning that it can be entered from either sector and + * exited into the other sector. The direction of the portal from one sector to the other + * is stored in the portal node. As a convention and to simplify computations, sector 0 must be + * the either the north or east sector on the grid in relation to sector 1. + */ +class Portal { +public: + /** + * Create a new portal between two sectors. + * + * As a convention, sector 0 must be the either the north or east sector + * on the grid in relation to sector 1. + * + * @param id ID of the portal. Should be unique per grid. + * @param sector0 First sector connected by the portal. + * Must be north or east on the grid in relation to sector 1. + * @param sector1 Second sector connected by the portal. + * Must be south or west on the grid in relation to sector 0. + * @param direction Direction of the portal from sector 0 to sector 1. + * @param cell_start Start cell coordinate in sector 0 (relative to sector origin). + * @param cell_end End cell coordinate in sector 0 (relative to sector origin). + */ + Portal(portal_id_t id, + sector_id_t sector0, + sector_id_t sector1, + PortalDirection direction, + const coord::tile_delta &cell_start, + const coord::tile_delta &cell_end); + + ~Portal() = default; + + /** + * Get the ID of the portal. + * + * IDs are unique per grid. + * + * @return ID of the portal. + */ + portal_id_t get_id() const; + + /** + * Get the connected portals in the specified sector. + * + * @param sector Sector ID. + * + * @return Connected portals in the sector. + */ + const std::vector> &get_connected(sector_id_t sector) const; + + /** + * Get the exit portals reachable via the portal when entering from a specified sector. + * + * @param entry_sector Sector from which the portal is entered. + * + * @return Exit portals reachable from the portal. + */ + const std::vector> &get_exits(sector_id_t entry_sector) const; + + /** + * Set the exit portals reachable for a specified sector. + * + * @param sector Sector for which the exit portals are set. + * @param exits Exit portals reachable from the portal. + */ + void set_exits(sector_id_t sector, const std::vector> &exits); + + /** + * Get the cost field of the sector where the portal is exited. + * + * @param entry_sector Sector from which the portal is entered. + * + * @return Cost field of the sector where the portal is exited. + */ + sector_id_t get_exit_sector(sector_id_t entry_sector) const; + + /** + * Get the cell coordinates of the start of the portal in the entry sector. + * + * @param entry_sector Sector from which the portal is entered. + * + * @return Cell coordinates of the start of the portal in the entry sector (relative to sector origin). + */ + const coord::tile_delta get_entry_start(sector_id_t entry_sector) const; + + /** + * Get the cell coordinates of the center of the portal in the entry sector. + * + * @param entry_sector Sector from which the portal is entered. + * + * @return Cell coordinates of the center of the portal in the entry sector (relative to sector origin). + */ + const coord::tile_delta get_entry_center(sector_id_t entry_sector) const; + + /** + * Get the cell coordinates of the start of the portal in the entry sector. + * + * @param entry_sector Sector from which the portal is entered. + * + * @return Cell coordinates of the start of the portal in the entry sector (relative to sector origin). + */ + const coord::tile_delta get_entry_end(sector_id_t entry_sector) const; + + /** + * Get the cell coordinates of the start of the portal in the exit sector. + * + * @param entry_sector Sector from which the portal is entered. + * + * @return Cell coordinates of the start of the portal in the exit sector (relative to sector origin). + */ + const coord::tile_delta get_exit_start(sector_id_t entry_sector) const; + + /** + * Get the cell coordinates of the center of the portal in the exit sector. + * + * @param entry_sector Sector from which the portal is entered. + * + * @return Cell coordinates of the center of the portal in the exit sector (relative to sector origin). + */ + const coord::tile_delta get_exit_center(sector_id_t entry_sector) const; + + /** + * Get the cell coordinates of the end of the portal in the exit sector. + * + * @param entry_sector Sector from which the portal is entered. + * + * @return Cell coordinates of the end of the portal in the exit sector (relative to sector origin). + */ + const coord::tile_delta get_exit_end(sector_id_t entry_sector) const; + + /** + * Get the direction of the portal from sector 0 to sector 1. + * + * @return Direction of the portal. + */ + PortalDirection get_direction() const; + +private: + /** + * Get the start cell coordinates of the portal. + * + * @return Start cell coordinates of the portal (relative to sector origin). + */ + const coord::tile_delta &get_sector0_start() const; + + /** + * Get the end cell coordinates of the portal. + * + * @return End cell coordinates of the portal (relative to sector origin). + */ + const coord::tile_delta &get_sector0_end() const; + + /** + * Get the start cell coordinates of the portal. + * + * @return Start cell coordinates of the portal (relative to sector origin). + */ + const coord::tile_delta get_sector1_start() const; + + /** + * Get the end cell coordinates of the portal. + * + * @return End cell coordinates of the portal (relative to sector origin). + */ + const coord::tile_delta get_sector1_end() const; + + /** + * ID of the portal. + */ + portal_id_t id; + + /** + * First sector connected by the portal. + */ + sector_id_t sector0; + + /** + * Second sector connected by the portal. + */ + sector_id_t sector1; + + /** + * Exits in sector 0 reachable from the portal. + * + * TODO: Also store avarage cost to reach each exit. + */ + std::vector> sector0_exits; + + /** + * Exits in sector 1 reachable from the portal. + * + * TODO: Also store avarage cost to reach each exit. + */ + std::vector> sector1_exits; + + /** + * Direction of the portal from sector 0 to sector 1. + */ + PortalDirection direction; + + /** + * Start cell coordinate in sector 0 (relative to sector origin). + * + * Coordinates for sector 1 are calculated on-the-fly using the direction. + */ + coord::tile_delta cell_start; + + /** + * End cell coordinate in sector 0 (relative to sector origin). + * + * Coordinates for sector 1 are calculated on-the-fly using the direction. + */ + coord::tile_delta cell_end; +}; +} // namespace openage::path diff --git a/libopenage/pathfinding/sector.cpp b/libopenage/pathfinding/sector.cpp new file mode 100644 index 0000000000..5897bbef84 --- /dev/null +++ b/libopenage/pathfinding/sector.cpp @@ -0,0 +1,252 @@ +// Copyright 2024-2025 the openage authors. See copying.md for legal info. + +#include "sector.h" + +#include +#include + +#include "error/error.h" + +#include "coord/tile.h" +#include "pathfinding/cost_field.h" +#include "pathfinding/definitions.h" + + +namespace openage::path { + +Sector::Sector(sector_id_t id, const coord::chunk &position, size_t field_size) : + id{id}, + position{position}, + cost_field{std::make_shared(field_size)} { +} + +Sector::Sector(sector_id_t id, const coord::chunk &position, const std::shared_ptr &cost_field) : + id{id}, + position{position}, + cost_field{cost_field} { +} + +const sector_id_t &Sector::get_id() const { + return this->id; +} + +const coord::chunk &Sector::get_position() const { + return this->position; +} + +const std::shared_ptr &Sector::get_cost_field() const { + return this->cost_field; +} + +const std::vector> &Sector::get_portals() const { + return this->portals; +} + +void Sector::add_portal(const std::shared_ptr &portal) { + this->portals.push_back(portal); +} + +std::vector> Sector::find_portals(const std::shared_ptr &other, + PortalDirection direction, + portal_id_t next_id) const { + ENSURE(this->cost_field->get_size() == other->get_cost_field()->get_size(), "Sector size mismatch"); + + std::vector> result; + + // cost field of the other sector + auto other_cost = other->get_cost_field(); + + // compare the edges of the sectors + size_t start = 0; + size_t end = 0; + bool passable_edge = false; + for (size_t i = 0; i < this->cost_field->get_size(); ++i) { + auto coord_this = coord::tile_delta{0, 0}; + auto coord_other = coord::tile_delta{0, 0}; + if (direction == PortalDirection::NORTH_SOUTH) { + // right edge; top to bottom + coord_this = coord::tile_delta(i, this->cost_field->get_size() - 1); + coord_other = coord::tile_delta(i, 0); + } + else if (direction == PortalDirection::EAST_WEST) { + // bottom edge; east to west + coord_this = coord::tile_delta(this->cost_field->get_size() - 1, i); + coord_other = coord::tile_delta(0, i); + } + + if (this->cost_field->get_cost(coord_this) != COST_IMPASSABLE + and other_cost->get_cost(coord_other) != COST_IMPASSABLE) { + // both sides of the edge are passable + if (not passable_edge) { + // start a new portal + start = i; + passable_edge = true; + } + // else: we already started a portal + + end = i; + if (i != this->cost_field->get_size() - 1) { + // continue to next tile unless we are at the last tile + // then we have to end the current portal + continue; + } + } + + if (passable_edge) { + // create a new portal + auto coord_start = coord::tile_delta{0, 0}; + auto coord_end = coord::tile_delta{0, 0}; + if (direction == PortalDirection::NORTH_SOUTH) { + // right edge; top to bottom + coord_start = coord::tile_delta(start, this->cost_field->get_size() - 1); + coord_end = coord::tile_delta(end, this->cost_field->get_size() - 1); + } + else if (direction == PortalDirection::EAST_WEST) { + // bottom edge; east to west + coord_start = coord::tile_delta(this->cost_field->get_size() - 1, start); + coord_end = coord::tile_delta(this->cost_field->get_size() - 1, end); + } + + result.push_back( + std::make_shared( + next_id, + this->id, + other->get_id(), + direction, + coord_start, + coord_end)); + passable_edge = false; + next_id += 1; + } + } + + return result; +} + +void Sector::connect_exits() { + if (this->portals.empty()) { + return; + } + + std::unordered_set portal_ids; + for (const auto &portal : this->portals) { + portal_ids.insert(portal->get_id()); + } + + // check all portals in the sector + std::vector> search_portals = this->portals; + while (not portal_ids.empty()) { + auto portal = search_portals.back(); + search_portals.pop_back(); + portal_ids.erase(portal->get_id()); + + auto start = portal->get_entry_start(this->id); + auto end = portal->get_entry_end(this->id); + + std::unordered_set visited; + std::deque open_list; + std::vector neighbors; + neighbors.reserve(4); + + if (portal->get_direction() == PortalDirection::NORTH_SOUTH) { + // right edge; top to bottom + for (auto i = start.se; i <= end.se; ++i) { + open_list.push_back(start.ne + i * this->cost_field->get_size()); + } + } + else if (portal->get_direction() == PortalDirection::EAST_WEST) { + // bottom edge; east to west + for (auto i = start.ne; i <= end.ne; ++i) { + open_list.push_back(i + start.se * this->cost_field->get_size()); + } + } + + // flood fill the grid to find connected portals + while (not open_list.empty()) { + auto current = open_list.front(); + open_list.pop_front(); + + if (visited.contains(current)) { + continue; + } + + // Get the x and y coordinates of the current cell + auto x = current % this->cost_field->get_size(); + auto y = current / this->cost_field->get_size(); + + // check the neighbors + if (y > 0) { + neighbors.push_back(current - this->cost_field->get_size()); + } + if (x > 0) { + neighbors.push_back(current - 1); + } + if (y < this->cost_field->get_size() - 1) { + neighbors.push_back(current + this->cost_field->get_size()); + } + if (x < this->cost_field->get_size() - 1) { + neighbors.push_back(current + 1); + } + + // add the neighbors to the open list + for (const auto &neighbor : neighbors) { + if (this->cost_field->get_cost(neighbor) != COST_IMPASSABLE) { + open_list.push_back(neighbor); + } + } + neighbors.clear(); + + // mark the current cell as visited + // TODO: Record the cost of reaching this cell + visited.insert(current); + } + + // check if the visited cells are connected to another portal + std::vector> connected_portals; + for (auto &exit : this->portals) { + if (exit->get_id() == portal->get_id()) { + // skip the current portal + continue; + } + + // get the start cell of the exit portal + // we only have to check one cell since the flood fill + // should reach any exit cell + auto exit_start = exit->get_entry_start(this->id); + auto exit_cell = exit_start.ne + exit_start.se * this->cost_field->get_size(); + + // check if the exit cell is connected to the visited cells + if (visited.contains(exit_cell)) { + connected_portals.push_back(exit); + } + } + + // set the exits for the current portal + portal->set_exits(this->id, connected_portals); + + // All connected portals share the same exits + // so we can connect them here + for (auto &connected : connected_portals) { + // make a new vector with all connected portals except the current one + std::vector> other_connected; + for (auto &other : connected_portals) { + if (other->get_id() != connected->get_id()) { + other_connected.push_back(other); + } + } + + // add the original portal as it is not in the connected portals vector + other_connected.push_back(portal); + + // set the exits for the connected portal + connected->set_exits(this->id, other_connected); + + // we don't need to food fill for this portal since we have + // found all exits, so we can remove it from the portals that + // should be searched + portal_ids.erase(connected->get_id()); + } + } +} + +} // namespace openage::path diff --git a/libopenage/pathfinding/sector.h b/libopenage/pathfinding/sector.h new file mode 100644 index 0000000000..d684a399b6 --- /dev/null +++ b/libopenage/pathfinding/sector.h @@ -0,0 +1,129 @@ +// Copyright 2024-2025 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include + +#include "coord/chunk.h" +#include "pathfinding/portal.h" +#include "pathfinding/types.h" + + +namespace openage::path { +class CostField; +class Portal; + +/** + * Sector in a grid for flow field pathfinding. + * + * Sectors consist of a cost field and a list of portals connecting them to adjacent + * sectors. + */ +class Sector { +public: + /** + * Create a new sector with a specified ID and an uninitialized cost field. + * + * @param id ID of the sector. Should be unique per grid. + * @param position Position of the sector in the grid. + * @param field_size Size of the cost field. + */ + Sector(sector_id_t id, + const coord::chunk &position, + size_t field_size); + + /** + * Create a new sector with a specified ID and an existing cost field. + * + * @param id ID of the sector. Should be unique per grid. + * @param position Position of the sector in the grid. + * @param cost_field Cost field of the sector. + */ + Sector(sector_id_t id, + const coord::chunk &position, + const std::shared_ptr &cost_field); + + /** + * Get the ID of this sector. + * + * IDs are unique per grid. + * + * @return ID of the sector. + */ + const sector_id_t &get_id() const; + + /** + * Get the position of this sector in the grid. + * + * @return Position of the sector (absolute on the grid). + */ + const coord::chunk &get_position() const; + + /** + * Get the cost field of this sector. + * + * @return Cost field of this sector. + */ + const std::shared_ptr &get_cost_field() const; + + /** + * Get the portals connecting this sector to other sectors. + * + * @return Outgoing portals of this sector. + */ + const std::vector> &get_portals() const; + + /** + * Add a portal to another sector. + * + * @param portal Portal to another sector. + */ + void add_portal(const std::shared_ptr &portal); + + /** + * Find portals connecting this sector to another sector. + * + * @param other Sector to which the portals should connect. + * @param direction Direction from this sector to \p other sector. + * @param next_id ID of the next portal to be created. Should be unique per grid. + * + * @return Portals connecting this sector to \p other sector. + */ + std::vector> find_portals(const std::shared_ptr &other, + PortalDirection direction, + portal_id_t next_id) const; + + /** + * Connect all portals that are mutually reachable. + * + * This method should be called after all sectors and portals have + * been created and initialized. + */ + void connect_exits(); + +private: + /** + * ID of the sector. + */ + sector_id_t id; + + /** + * Position of the sector (absolute on the grid). + */ + coord::chunk position; + + /** + * Cost field of the sector. + */ + std::shared_ptr cost_field; + + /** + * Portals of the sector. + */ + std::vector> portals; +}; + + +} // namespace openage::path diff --git a/libopenage/pathfinding/tests.cpp b/libopenage/pathfinding/tests.cpp index 35f29295e8..2ac0960c34 100644 --- a/libopenage/pathfinding/tests.cpp +++ b/libopenage/pathfinding/tests.cpp @@ -1,316 +1,119 @@ -// Copyright 2015-2018 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. -#include "../log/log.h" -#include "../testing/testing.h" +#include "log/log.h" +#include "testing/testing.h" + +#include "coord/tile.h" +#include "pathfinding/cost_field.h" +#include "pathfinding/definitions.h" +#include "pathfinding/flow_field.h" +#include "pathfinding/integration_field.h" +#include "pathfinding/integrator.h" +#include "pathfinding/types.h" +#include "time/time.h" -#include "heuristics.h" -#include "path.h" namespace openage { namespace path { namespace tests { -/** - * This function tests setting up basic nodes that point to a previous node. - * Tests that direction is set correctly and that factor is set correctly. - */ -void node_0() { - coord::phys3 p0{0, 0, 0}; - coord::phys3 p1{1, 0, 0}; - coord::phys3 p2{1, 1, 0}; - coord::phys3 p3{1, -1, 0}; - coord::phys3 p4{2, 0, 0}; - coord::phys3 p5{2, 2, 0}; - coord::phys3 p6{2, -2, 0}; - - node_pt n0 = std::make_unique(p0, nullptr); - node_pt n1 = std::make_unique(p1, n0); - node_pt n2 = std::make_unique(p2, n1); - node_pt n3 = std::make_unique(p3, n1); - node_pt n4 = std::make_unique(p0, n1); - - // Testing how the factor is effected from the change in - // direction from one node to another - TESTEQUALS(n1->direction.ne, 1); - TESTEQUALS(n1->direction.se, 0); - - // Expect this to be 2 since the similarity between nodes is zero - TESTEQUALS(n1->factor, 2); - - TESTEQUALS(n2->direction.ne, 0); - TESTEQUALS(n2->direction.se, 1); - - // Expect this to be 2 since it takes a 90 degree turn from n1 - TESTEQUALS(n2->factor, 2); - - TESTEQUALS(n3->direction.ne, 0); - TESTEQUALS(n3->direction.se, -1); - - // Expect this to be 2 since it takes a 90 degree turn from n1 - TESTEQUALS(n3->factor, 2); - - TESTEQUALS(n4->direction.ne, -1); - TESTEQUALS(n4->direction.se, 0); - - // Expect this to be 3 since it takes a 180 degree turn from n1 - TESTEQUALS(n4->factor, 3); - - // Testing that the distance from the previous node noes not - // effect the factor, only change in direction - - n1 = std::make_unique(p4, n0); - n2 = std::make_unique(p5, n1); - n3 = std::make_unique(p6, n1); - n4 = std::make_unique(p0, n1); - - TESTEQUALS(n1->direction.ne, 1); - TESTEQUALS(n1->direction.se, 0); - - // Expect this to be 2 since the similarity between nodes is zero - TESTEQUALS(n1->factor, 2); - TESTEQUALS(n2->direction.ne, 0); - TESTEQUALS(n2->direction.se, 1); - - // Expect this to be 2 since it takes a 90 degree turn from n1 - TESTEQUALS(n2->factor, 2); - TESTEQUALS(n3->direction.ne, 0); - TESTEQUALS(n3->direction.se, -1); - - // Expect this to be 2 since it takes a 90 degree turn from n1 - TESTEQUALS(n3->factor, 2); - TESTEQUALS(n4->direction.ne, -1); - TESTEQUALS(n4->direction.se, 0); - - // Expect this to be 3 since it takes a 180 degree turn from n1 - TESTEQUALS(n4->factor, 3); -} - -/** - * This function tests Node->cost_to. The testing is done on 2 unrelated - * nodes (They have no previous node) to test the basic cost without adding - * the cost from node->factor. - */ -void node_cost_to_0() { - // Testing basic cost_to with ne only - coord::phys3 p0{0, 0, 0}; - coord::phys3 p1{10, 0, 0}; - - node_pt n0 = std::make_unique(p0, nullptr); - node_pt n1 = std::make_unique(p1, nullptr); - - TESTEQUALS(n0->cost_to(*n1), 10); - TESTEQUALS(n1->cost_to(*n0), 10); - - // Testing basic cost_to with se only - coord::phys3 p2{0, 5, 0}; - - node_pt n2 = std::make_unique(p2, nullptr); - - TESTEQUALS(n0->cost_to(*n2), 5); - TESTEQUALS(n2->cost_to(*n0), 5); - - // Testing cost_to with both se and ne: - coord::phys3 p3{3, 4, 0}; // -> sqrt(3*3 + 4*4) == 5 - - node_pt n3 = std::make_unique(p3, nullptr); - TESTEQUALS(n0->cost_to(*n3), 5); - TESTEQUALS(n3->cost_to(*n0), 5); - - // Test cost_to and check that `up` has no effect - coord::phys3 p4{3, 4, 8}; - - node_pt n4 = std::make_unique(p4, nullptr); - - TESTEQUALS(n0->cost_to(*n4), 5); - TESTEQUALS(n4->cost_to(*n0), 5); -} - -/** - * This function tests Node->cost_to. The testing is done on the neighbor - * nodes to test how the directional factor effects the cost. - */ -void node_cost_to_1() { - // Set up coords so that n1 will have a direction of ne = 1 - // but n0 with not be in n1s neighbors - coord::phys3 p0{-0.125, 0, 0}; - coord::phys3 p1{0.125, 0, 0}; - - node_pt n0 = std::make_unique(p0, nullptr); - node_pt n1 = std::make_unique(p1, n0); - - // We expect twice the normal cost since n0 had not direction - // thus we get a factor of 2 on n1 - TESTEQUALS_FLOAT(n0->cost_to(*n1), 0.5, 0.001); - TESTEQUALS_FLOAT(n1->cost_to(*n0), 0.5, 0.001); - - nodemap_t visited_tiles; - visited_tiles[n0->position] = n0; - - // Collect the costs to go to all the neighbors of n1 - std::vector costs; - for (node_pt neighbor : n1->get_neighbors(visited_tiles, 1)) { - costs.push_back(n1->cost_to(*neighbor)); +void flow_field() { + // Create initial cost grid + auto cost_field = std::make_shared(3); + + // | 1 | 1 | 1 | + // | 1 | X | 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 + { + auto integration_field = std::make_shared(3); + integration_field->integrate_cost(cost_field, coord::tile_delta{2, 2}); + auto &int_cells = integration_field->get_cells(); + + // The integration field should look like: + // | 4 | 3 | 2 | + // | 3 | X | 1 | + // | 2 | 1 | 0 | + auto int_expected = std::vector{ + 4, + 3, + 2, + 3, + 65535, + 1, + 2, + 1, + 0, + }; + + // The flow field for targeting (2, 2) hould look like this: + // | E | SE | S | + // | SE | X | S | + // | E | E | N | + auto ff_expected = std::vector{ + FLOW_PATHABLE_MASK | static_cast(flow_dir_t::EAST), + FLOW_PATHABLE_MASK | static_cast(flow_dir_t::SOUTH_EAST), + FLOW_PATHABLE_MASK | static_cast(flow_dir_t::SOUTH), + FLOW_PATHABLE_MASK | static_cast(flow_dir_t::SOUTH_EAST), + 0, + FLOW_PATHABLE_MASK | static_cast(flow_dir_t::SOUTH), + FLOW_PATHABLE_MASK | static_cast(flow_dir_t::EAST), + FLOW_PATHABLE_MASK | static_cast(flow_dir_t::EAST), + FLOW_TARGET_MASK | FLOW_PATHABLE_MASK | static_cast(flow_dir_t::NORTH), + }; + + // Compare the integration field cells with the expected values + for (size_t i = 0; i < int_cells.size(); i++) { + TESTEQUALS(int_cells[i].cost, int_expected[i]); + } + + // Build the flow field + auto flow_field = std::make_shared(3); + flow_field->build(integration_field); + auto ff_cells = flow_field->get_cells(); + + // Compare the flow field cells with the expected values + for (size_t i = 0; i < ff_cells.size(); i++) { + TESTEQUALS(ff_cells[i], ff_expected[i]); + } } - TESTEQUALS_FLOAT(costs[0], 0.45711, 0.001); - TESTEQUALS_FLOAT(costs[1], 0.25, 0.001); - TESTEQUALS_FLOAT(costs[2], 0.45711, 0.001); - TESTEQUALS_FLOAT(costs[3], 0.5, 0.001); - TESTEQUALS_FLOAT(costs[4], 0.95709, 0.001); - TESTEQUALS_FLOAT(costs[5], 0.75, 0.001); - TESTEQUALS_FLOAT(costs[6], 0.95709, 0.001); - TESTEQUALS_FLOAT(costs[7], 0.5, 0.001); -} - -/** - * This function does a basic test of generating a backtrace from the - * last node in a path. - */ -void node_generate_backtrace_0() { - coord::phys3 p0{0, 0, 0}; - coord::phys3 p1{10, 0, 0}; - coord::phys3 p2{20, 0, 0}; - coord::phys3 p3{30, 0, 0}; - - node_pt n0 = std::make_unique(p0, nullptr); - node_pt n1 = std::make_unique(p1, n0); - node_pt n2 = std::make_unique(p2, n1); - node_pt n3 = std::make_unique(p3, n2); - - Path path = n3->generate_backtrace(); - - (path.waypoints[0] == *n3) or TESTFAIL; - (path.waypoints[1] == *n2) or TESTFAIL; - (path.waypoints[2] == *n1) or TESTFAIL; -} - -/** - * This function tests Node->get_neighbors and how the scale effects - * the neighbors given. - */ -void node_get_neighbors_0() { - coord::phys3 p0{0, 0, 0}; - - node_pt n0 = std::make_unique(p0, nullptr); - nodemap_t map; - - // Testing get_neighbors returning all surounding tiles with - // a factor of 1 - - std::vector neighbors = n0->get_neighbors(map, 1); - TESTEQUALS(neighbors.size(), 8); - - TESTEQUALS_FLOAT(neighbors[0]->position.ne.to_double(), 0.125, 0.001); - TESTEQUALS_FLOAT(neighbors[0]->position.se.to_double(), -0.125, 0.001); - - TESTEQUALS_FLOAT(neighbors[1]->position.ne.to_double(), 0.125, 0.001); - TESTEQUALS_FLOAT(neighbors[1]->position.se.to_double(), 0, 0.001); - - TESTEQUALS_FLOAT(neighbors[2]->position.ne.to_double(), 0.125, 0.001); - TESTEQUALS_FLOAT(neighbors[2]->position.se.to_double(), 0.125, 0.001); - - TESTEQUALS_FLOAT(neighbors[3]->position.ne.to_double(), 0, 0.001); - TESTEQUALS_FLOAT(neighbors[3]->position.se.to_double(), 0.125, 0.001); - - TESTEQUALS_FLOAT(neighbors[4]->position.ne.to_double(), -0.125, 0.001); - TESTEQUALS_FLOAT(neighbors[4]->position.se.to_double(), 0.125, 0.001); - - TESTEQUALS_FLOAT(neighbors[5]->position.ne.to_double(), -0.125, 0.001); - TESTEQUALS_FLOAT(neighbors[5]->position.se.to_double(), 0, 0.001); - - TESTEQUALS_FLOAT(neighbors[6]->position.ne.to_double(), -0.125, 0.001); - TESTEQUALS_FLOAT(neighbors[6]->position.se.to_double(), -0.125, 0.001); - - TESTEQUALS_FLOAT(neighbors[7]->position.ne.to_double(), 0, 0.001); - TESTEQUALS_FLOAT(neighbors[7]->position.se.to_double(), -0.125, 0.001); - - // Testing how a larger scale changes the neighbors generated - neighbors = n0->get_neighbors(map, 2); - TESTEQUALS(neighbors.size(), 8); - - TESTEQUALS_FLOAT(neighbors[0]->position.ne.to_double(), 0.25, 0.001); - TESTEQUALS_FLOAT(neighbors[0]->position.se.to_double(), -0.25, 0.001); - - TESTEQUALS_FLOAT(neighbors[1]->position.ne.to_double(), 0.25, 0.001); - TESTEQUALS_FLOAT(neighbors[1]->position.se.to_double(), 0, 0.001); - - TESTEQUALS_FLOAT(neighbors[2]->position.ne.to_double(), 0.25, 0.001); - TESTEQUALS_FLOAT(neighbors[2]->position.se.to_double(), 0.25, 0.001); - - TESTEQUALS_FLOAT(neighbors[3]->position.ne.to_double(), 0, 0.001); - TESTEQUALS_FLOAT(neighbors[3]->position.se.to_double(), 0.25, 0.001); - - TESTEQUALS_FLOAT(neighbors[4]->position.ne.to_double(), -0.25, 0.001); - TESTEQUALS_FLOAT(neighbors[4]->position.se.to_double(), 0.25, 0.001); - - TESTEQUALS_FLOAT(neighbors[5]->position.ne.to_double(), -0.25, 0.001); - TESTEQUALS_FLOAT(neighbors[5]->position.se.to_double(), 0, 0.001); - - TESTEQUALS_FLOAT(neighbors[6]->position.ne.to_double(), -0.25, 0.001); - TESTEQUALS_FLOAT(neighbors[6]->position.se.to_double(), -0.25, 0.001); - - TESTEQUALS_FLOAT(neighbors[7]->position.ne.to_double(), 0, 0.001); - TESTEQUALS_FLOAT(neighbors[7]->position.se.to_double(), -0.25, 0.001); -} - -/** - * This is a helper passable function that alwalys returns true. - */ -bool always_passable(const coord::phys3 &) { - return true; -} - -/** - * This is a helper passable function that always returns false. - */ -bool not_passable(const coord::phys3 &) { - return false; -} - -/** - * This is a helper passable function that only returns true when - * pos.ne == 20. - */ -bool sometimes_passable(const coord::phys3 &pos) { - if (pos.ne == 20) { - return false; - } else { - return true; + // Integrator test + { + // Integrator for managing the flow field + auto integrator = std::make_shared(); + + // Build the flow field + auto flow_field = integrator->get(cost_field, coord::tile_delta{2, 2}).second; + auto &ff_cells = flow_field->get_cells(); + + // The flow field for targeting (2, 2) hould look like this: + // | E | SE | S | + // | SE | X | S | + // | E | E | N | + auto ff_expected = std::vector{ + FLOW_PATHABLE_MASK | static_cast(flow_dir_t::EAST), + FLOW_PATHABLE_MASK | static_cast(flow_dir_t::SOUTH_EAST), + FLOW_LOS_MASK | FLOW_PATHABLE_MASK | static_cast(flow_dir_t::SOUTH), + FLOW_PATHABLE_MASK | static_cast(flow_dir_t::SOUTH_EAST), + 0, + FLOW_LOS_MASK | FLOW_PATHABLE_MASK | static_cast(flow_dir_t::SOUTH), + FLOW_LOS_MASK | FLOW_PATHABLE_MASK | static_cast(flow_dir_t::EAST), + FLOW_LOS_MASK | FLOW_PATHABLE_MASK | static_cast(flow_dir_t::EAST), + FLOW_LOS_MASK | FLOW_PATHABLE_MASK | FLOW_TARGET_MASK, + }; + + // Compare the flow field cells with the expected values + for (size_t i = 0; i < ff_cells.size(); i++) { + TESTEQUALS(ff_cells[i], ff_expected[i]); + } } } -/** - * This function tests passable_line. Tests with always false, always true, - * and position dependant functions being passed in as args. - */ -void node_passable_line_0() { - coord::phys3 p0{0, 0, 0}; - coord::phys3 p1{1000, 0, 0}; - - node_pt n0 = std::make_unique(p0, nullptr); - node_pt n1 = std::make_unique(p1, n0); - - TESTEQUALS(path::passable_line(n0, n1, path::tests::always_passable), true); - TESTEQUALS(path::passable_line(n0, n1, path::tests::not_passable), false); - - // The next 2 cases show that a different sample can change the results - // for the same path - TESTEQUALS(path::passable_line(n0, n1, path::tests::sometimes_passable, 10), true); - TESTEQUALS(path::passable_line(n0, n1, path::tests::sometimes_passable, 50), false); -} - -/** - * Top level node test. - */ -void path_node() { - node_0(); - node_cost_to_0(); - node_cost_to_1(); - node_generate_backtrace_0(); - node_get_neighbors_0(); - node_passable_line_0(); -} } // namespace tests -} // namespace pathfinding +} // namespace path } // namespace openage diff --git a/libopenage/pathfinding/types.cpp b/libopenage/pathfinding/types.cpp new file mode 100644 index 0000000000..6428dd7ded --- /dev/null +++ b/libopenage/pathfinding/types.cpp @@ -0,0 +1,10 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "types.h" + + +namespace openage::path { + +// this file is intentionally empty + +} // namespace openage::path diff --git a/libopenage/pathfinding/types.h b/libopenage/pathfinding/types.h new file mode 100644 index 0000000000..3229010a91 --- /dev/null +++ b/libopenage/pathfinding/types.h @@ -0,0 +1,127 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include +#include + + +namespace openage::path { + +/** + * Path result type. + */ +enum class PathResult { + /// Path was found. + FOUND, + /// Path was not found. + NOT_FOUND, + /// Target is not on grid. + OUT_OF_BOUNDS, +}; + +/** + * Movement cost in the cost field. + * + * TODO: Cost stamps + * + * 0: uninitialized + * 1-254: normal cost + * 255: impassable + */ +using cost_t = uint8_t; + +/** + * Integrated cost in the integration field. + */ +using integrated_cost_t = uint16_t; + +/** + * Integrated field cell flags. + */ +using integrated_flags_t = uint8_t; + +/** + * Integration field cell value. + */ +struct integrated_t { + /** + * Total integrated cost. + */ + integrated_cost_t cost; + + /** + * Flags. + * + * Bit 0-3: Shared flags with the flow field. + * - 0: Unused. + * - 1: Target flag. + * - 2: Line of sight flag. + * - 3: Unused. + * Bit 4-7: Integration field specific flags. + * - 4: Unused. + * - 5: Wave front blocked flag. + * - 6: LOS found flag. + * - 7: Unused. + */ + integrated_flags_t flags; +}; + +/** + * Flow field direction types. + * + * Encoded into the flow_t values. + */ +enum class flow_dir_t : uint8_t { + NORTH = 0x00, + NORTH_EAST = 0x01, + EAST = 0x02, + SOUTH_EAST = 0x03, + SOUTH = 0x04, + SOUTH_WEST = 0x05, + WEST = 0x06, + NORTH_WEST = 0x07, +}; + +/** + * Flow field cell value. + * + * Bit 0: Unused. + * Bit 1: Target flag. + * Bit 2: Line of sight flag. + * Bit 3: Pathable flag. + * Bits 4-7: flow direction. + */ +using flow_t = uint8_t; + +/** + * Grid identifier. + */ +using grid_id_t = size_t; + +/** + * Sector identifier (unique per grid). + */ +using sector_id_t = size_t; + +/** + * Portal identifier (unique per grid). + */ +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/CMakeLists.txt b/libopenage/presenter/CMakeLists.txt index d0fc81f7be..5a824fab5a 100644 --- a/libopenage/presenter/CMakeLists.txt +++ b/libopenage/presenter/CMakeLists.txt @@ -1,6 +1,3 @@ add_sources(libopenage presenter.cpp ) - -add_subdirectory("legacy") -add_subdirectory("assets") diff --git a/libopenage/presenter/assets/CMakeLists.txt b/libopenage/presenter/assets/CMakeLists.txt deleted file mode 100644 index 59346cbc3e..0000000000 --- a/libopenage/presenter/assets/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -add_sources(libopenage - asset_manager.cpp -) diff --git a/libopenage/presenter/assets/asset_manager.cpp b/libopenage/presenter/assets/asset_manager.cpp deleted file mode 100644 index 0077d0db95..0000000000 --- a/libopenage/presenter/assets/asset_manager.cpp +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright 2014-2023 the openage authors. See copying.md for legal info. - -#include "asset_manager.h" - -#if WITH_INOTIFY -#include /* for NAME_MAX */ -#include -#include -#endif - -#include "error/error.h" -#include "log/log.h" -#include "texture.h" -#include "util/compiler.h" -#include "util/file.h" - -namespace openage::presenter { - -AssetManager::AssetManager() : - missing_tex{nullptr} { -#if WITH_INOTIFY - // initialize the inotify instance - this->inotify_fd = inotify_init1(IN_NONBLOCK); - if (this->inotify_fd < 0) { - throw Error{MSG(err) << "Failed to initialize inotify!"}; - } -#endif -} - -AssetManager::AssetManager(const util::Path &asset_dir) : - asset_dir{asset_dir}, - missing_tex{nullptr} { -#if WITH_INOTIFY - // initialize the inotify instance - this->inotify_fd = inotify_init1(IN_NONBLOCK); - if (this->inotify_fd < 0) { - throw Error{MSG(err) << "Failed to initialize inotify!"}; - } -#endif -} - - -const util::Path &AssetManager::get_asset_dir() { - return this->asset_dir; -} - - -void AssetManager::set_asset_dir(const util::Path &new_path) { - if (this->asset_dir != new_path) { - this->asset_dir = new_path; - this->clear(); - } -} - - -std::shared_ptr AssetManager::load_texture(const std::string &name, - bool use_metafile, - bool null_if_missing) { - // the texture to be associated with the given filename - std::shared_ptr tex; - - util::Path tex_path = this->asset_dir[name]; - - // try to open the texture filename. - if (not tex_path.is_file()) { - // TODO: add/fetch inotify watch on the containing folder - // to display the tex as soon at it exists. - - if (null_if_missing) { - return nullptr; - } - else { - // return the big X texture instead - tex = this->get_missing_tex(); - } - } - else { - // create the texture! - tex = std::make_shared(tex_path, use_metafile); - -#if WITH_INOTIFY - std::string native_path = tex_path.resolve_native_path(); - - if (native_path.size() > 0) { - // create inotify update trigger for the requested file - - // TODO: let util::Path do the file watching - int wd = inotify_add_watch( - this->inotify_fd, - native_path.c_str(), - IN_CLOSE_WRITE); - - if (wd < 0) { - log::log(WARN << "Failed to add inotify watch for " << native_path); - } - else { - this->watch_fds[wd] = tex; - } - } -#endif - } - - // pass back the shared_ptr - return tex; -} - - -Texture *AssetManager::get_texture(const std::string &name, bool use_metafile, bool null_if_missing) { - // check whether the requested texture was loaded already - auto tex_it = this->textures.find(name); - - // the texture was not loaded yet: - if (tex_it == this->textures.end()) { - auto tex = this->load_texture(name, use_metafile, null_if_missing); - - if (tex.get() != nullptr) { - // insert the texture into the map - this->textures.insert(std::make_pair(name, tex)); - } - - // and return the texture pointer. - return tex.get(); - } - - return tex_it->second.get(); -} - - -void AssetManager::check_updates() { -#if WITH_INOTIFY - // buffer for at least 4 inotify events - char buf[4 * (sizeof(struct inotify_event) + NAME_MAX + 1)]; - ssize_t len; - - while (true) { - // fetch all events, the kernel won't write "half" structs. - len = read(this->inotify_fd, buf, sizeof(buf)); - - if (len == -1) { - if (errno == EAGAIN) { - // no events, nothing to do. - break; - } - else { - // something went wrong - log::log(WARN << "Failed to read inotify events!"); - break; - } - } - - // process fetched events, - // the kernel guarantees complete events in the buffer. - char *ptr = buf; - while (ptr < buf + len) { - auto *event = reinterpret_cast(ptr); - - if (event->mask & IN_CLOSE_WRITE) { - // TODO: this should invoke callback functions - this->watch_fds[event->wd]->reload(); - } - - // move the buffer ptr to the next event. - ptr += sizeof(struct inotify_event) + event->len; - } - } -#endif -} - -std::shared_ptr AssetManager::get_missing_tex() { - // if not loaded, fetch the "missing" texture (big red X). - if (this->missing_tex.get() == nullptr) [[unlikely]] { - this->missing_tex = std::make_shared( - this->asset_dir["test"]["textures"]["missing.png"], - false); - } - - return this->missing_tex; -} - -void AssetManager::clear() { -#if WITH_INOTIFY - for (auto &watch_fd : this->watch_fds) { - int result = inotify_rm_watch(this->inotify_fd, watch_fd.first); - if (result < 0) { - log::log(WARN << "Failed to remove inotify watches"); - } - } - this->watch_fds.clear(); -#endif - - this->textures.clear(); -} - -} // namespace openage::presenter diff --git a/libopenage/presenter/assets/asset_manager.h b/libopenage/presenter/assets/asset_manager.h deleted file mode 100644 index 48075eeaeb..0000000000 --- a/libopenage/presenter/assets/asset_manager.h +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright 2014-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include "config.h" - -#include -#include -#include - -#include "util/path.h" - -namespace qtsdl { -class GuiItemLink; -} // namespace qtsdl - -namespace openage { -class Texture; -} // namespace openage - -namespace openage::presenter { - -/** - * Container class for all available assets. - * Responsible for loading, providing and updating requested files. - */ -class AssetManager final { -public: - AssetManager(); - AssetManager(const util::Path &asset_dir); - - /** - * Return the path where assets are found in. - */ - const util::Path &get_asset_dir(); - - /** - * Set the asset search path. - */ - void set_asset_dir(const util::Path &new_path); - - /** - * Query the Texture for a given filename. - * - * @param name: the asset file name relative to the asset root. - * @param use_metafile: load subtexture information from meta file - * @param null_if_missing: instead of providing the "missing texture", - * return nullptr. - * @returns the queried texture handle. - */ - Texture *get_texture(const std::string &name, - bool use_metafile = true, - bool null_if_missing = false); - - /** - * Ask the kernel whether there were updates to watched files. - */ - void check_updates(); - -protected: - /** - * Create an internal texture handle. - */ - std::shared_ptr load_texture(const std::string &name, - bool use_metafile = true, - bool null_if_missing = false); - - /** - * Retrieves the texture for missing textures. - */ - std::shared_ptr get_missing_tex(); - -private: - void clear(); - - /** - * The root directory for the available assets. - */ - util::Path asset_dir; - - /** - * The replacement texture for missing textures. - */ - std::shared_ptr missing_tex; - - /** - * Map from texture filename to texture instance ptr. - */ - std::unordered_map> textures; - -#if WITH_INOTIFY - /** - * The file descriptor pointing to the inotify instance. - */ - int inotify_fd; - - /** - * Map from inotify watch handle fd to texture instance ptr. - * The kernel returns the handle fd when events are triggered. - */ - std::unordered_map> watch_fds; -#endif -}; - -} // namespace openage::presenter diff --git a/libopenage/presenter/legacy/CMakeLists.txt b/libopenage/presenter/legacy/CMakeLists.txt deleted file mode 100644 index d4add0acd2..0000000000 --- a/libopenage/presenter/legacy/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -add_sources(libopenage - game_control.cpp - legacy.cpp - legacy_renderer.cpp -) - -pxdgen( - legacy.h -) diff --git a/libopenage/presenter/legacy/game_control.cpp b/libopenage/presenter/legacy/game_control.cpp deleted file mode 100644 index 91255b1b54..0000000000 --- a/libopenage/presenter/legacy/game_control.cpp +++ /dev/null @@ -1,916 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#include "game_control.h" - -#include "error/error.h" -#include "gamestate/old/game_spec.h" -#include "legacy_engine.h" -#include "log/log.h" -#include "renderer/color.h" -#include "terrain/terrain_chunk.h" -#include "util/strings.h" - - -namespace openage { - -OutputMode::OutputMode(qtsdl::GuiItemLink *gui_link) : - game_control{nullptr}, - gui_link{gui_link} { -} - -OutputMode::~OutputMode() { - // An empty deconstructor prevents our clients from needing - // to implement one. - // - // The deconstructor is needed in the first place to stop - // the compiler from generating ud2 (undefined) instructors - // for the body of this method. -} - -void OutputMode::announce() { - emit this->gui_signals.announced(this->name()); - - emit this->gui_signals.binds_changed(this->active_binds()); -} - -void OutputMode::set_game_control(GameControl *game_control) { - this->game_control = game_control; - - this->on_game_control_set(); - this->announce(); -} - -CreateMode::CreateMode(qtsdl::GuiItemLink *gui_link) : - OutputMode{gui_link} {} - -bool CreateMode::available() const { - return true; -} - -std::string CreateMode::name() const { - return "Creation Mode"; -} - -void CreateMode::on_game_control_set() {} - -void CreateMode::on_enter() {} - -void CreateMode::on_exit() {} - -void CreateMode::render() {} - - -ActionModeSignals::ActionModeSignals(ActionMode *action_mode) : - action_mode(action_mode) { -} - -void ActionModeSignals::on_action(const std::string &action_name) { - presenter::LegacyDisplay *display = this->action_mode->game_control->get_display(); - - const input::legacy::action_t &action = display->get_action_manager().get(action_name); - input::legacy::InputContext *top_ctxt = &display->get_input_manager().get_top_context(); - - if (top_ctxt == this->action_mode || top_ctxt == &this->action_mode->building_context || top_ctxt == &this->action_mode->build_menu_context || top_ctxt == &this->action_mode->build_menu_mil_context) { - // create an action that just relays the action. - input::legacy::action_arg_t action_arg{ - input::legacy::Event{input::legacy::event_class::ANY, 0, input::legacy::modset_t{}}, - coord::input{0, 0}, - coord::input_delta{0, 0}, - {action}}; - top_ctxt->execute_if_bound(action_arg); - } -} - -ActionMode::ActionMode(qtsdl::GuiItemLink *gui_link) : - OutputMode{gui_link}, - selection{nullptr}, - use_set_ability{false}, - type_focus{nullptr}, - selecting{false}, - rng{rng::random_seed()}, - gui_signals{this} {} - - -void ActionMode::on_game_control_set() { - ENSURE(this->game_control != nullptr, "no control was actually set"); - - LegacyEngine *engine = this->game_control->get_engine(); - presenter::LegacyDisplay *display = this->game_control->get_display(); - ENSURE(engine != nullptr, "engine must be known!"); - - auto &action = display->get_action_manager(); - auto input = &display->get_input_manager(); - coord::CoordManager &coord = display->coord; - - // TODO: the selection should not be used in here! - this->selection = display->get_unit_selection(); - ENSURE(this->selection != nullptr, "selection must be fetched!"); - - this->bind(action.get("TRAIN_OBJECT"), - [this](const input::legacy::action_arg_t &) { - // attempt to train editor selected object - - // randomly select between male and female villagers - auto player = this->game_control->get_current_player(); - auto type = player->get_type(this->rng.probability(0.5) ? 83 : 293); - - Command cmd(*player, type); - cmd.add_flag(command_flag::interrupt); - this->selection->all_invoke(cmd); - }); - - this->bind(action.get("ENABLE_BUILDING_PLACEMENT"), - [](const input::legacy::action_arg_t &) { - // this->building_placement = true; - }); - - this->bind(action.get("DISABLE_SET_ABILITY"), - [this](const input::legacy::action_arg_t &) { - this->use_set_ability = false; - }); - - this->bind(action.get("SET_ABILITY_MOVE"), - [this](const input::legacy::action_arg_t &) { - this->use_set_ability = true; - this->ability = ability_type::move; - emit this->gui_signals.ability_changed(std::to_string(this->ability)); - }); - - this->bind(action.get("SET_ABILITY_GATHER"), - [this](const input::legacy::action_arg_t &) { - this->use_set_ability = true; - this->ability = ability_type::gather; - emit this->gui_signals.ability_changed(std::to_string(this->ability)); - }); - - this->bind(action.get("SET_ABILITY_GARRISON"), - [this](const input::legacy::action_arg_t &) { - this->use_set_ability = true; - this->ability = ability_type::garrison; - emit this->gui_signals.ability_changed(std::to_string(this->ability)); - }); - - this->bind(action.get("SET_ABILITY_REPAIR"), [this](const input::legacy::action_arg_t &) { - this->use_set_ability = true; - this->ability = ability_type::repair; - emit this->gui_signals.ability_changed(std::to_string(this->ability)); - }); - - this->bind(action.get("SPAWN_VILLAGER"), - [this, display](const input::legacy::action_arg_t &) { - auto player = this->game_control->get_current_player(); - - if (player->type_count() > 0) { - UnitType &type = *player->get_type(590); - - // TODO tile position - display->get_game()->placed_units.new_unit(type, *player, this->mousepos_phys3); - } - }); - - this->bind(action.get("KILL_UNIT"), - [this](const input::legacy::action_arg_t &) { - this->selection->kill_unit(*this->game_control->get_current_player()); - }); - - this->bind(action.get("BUILD_MENU"), - [this, input](const input::legacy::action_arg_t &) { - log::log(MSG(dbg) << "Opening build menu"); - input->push_context(&this->build_menu_context); - this->announce_buttons_type(); - }); - - this->bind(action.get("BUILD_MENU_MIL"), - [this, input](const input::legacy::action_arg_t &) { - log::log(MSG(dbg) << "Opening military build menu"); - input->push_context(&this->build_menu_mil_context); - this->announce_buttons_type(); - }); - - this->build_menu_context.bind(action.get("CANCEL"), - [this, input](const input::legacy::action_arg_t &) { - input->remove_context(&this->build_menu_context); - this->announce_buttons_type(); - }); - - this->build_menu_mil_context.bind(action.get("CANCEL"), - [this, input](const input::legacy::action_arg_t &) { - input->remove_context(&this->build_menu_mil_context); - this->announce_buttons_type(); - }); - - // Villager build commands - auto bind_building_key = [this, input](input::legacy::action_t action, int building, input::legacy::InputContext *ctxt) { - ctxt->bind(action, [this, building, ctxt, input](const input::legacy::action_arg_t &) { - auto player = this->game_control->get_current_player(); - if (this->selection->contains_builders(*player)) { - auto player = this->game_control->get_current_player(); - this->type_focus = player->get_type(building); - - if (player->can_make(*this->type_focus)) { - if (&input->get_top_context() != &this->building_context) { - input->remove_context(ctxt); - input->push_context(&this->building_context); - this->announce_buttons_type(); - } - } - else { - // TODO show in game error message - } - } - }); - }; - - bind_building_key(action.get("BUILDING_HOUS"), 70, &this->build_menu_context); // House - bind_building_key(action.get("BUILDING_MILL"), 68, &this->build_menu_context); // Mill - bind_building_key(action.get("BUILDING_MINE"), 584, &this->build_menu_context); // Mining Camp - bind_building_key(action.get("BUILDING_SMIL"), 562, &this->build_menu_context); // Lumber Camp - bind_building_key(action.get("BUILDING_DOCK"), 47, &this->build_menu_context); // Dock - // TODO: Doesn't show until it is placed - bind_building_key(action.get("BUILDING_FARM"), 50, &this->build_menu_context); // Farm - bind_building_key(action.get("BUILDING_BLAC"), 103, &this->build_menu_context); // Blacksmith - bind_building_key(action.get("BUILDING_MRKT"), 84, &this->build_menu_context); // Market - bind_building_key(action.get("BUILDING_CRCH"), 104, &this->build_menu_context); // Monastery - bind_building_key(action.get("BUILDING_UNIV"), 209, &this->build_menu_context); // University - bind_building_key(action.get("BUILDING_RTWC"), 109, &this->build_menu_context); // Town Center - bind_building_key(action.get("BUILDING_WNDR"), 276, &this->build_menu_context); // Wonder - - bind_building_key(action.get("BUILDING_BRKS"), 12, &this->build_menu_mil_context); // Barracks - bind_building_key(action.get("BUILDING_ARRG"), 87, &this->build_menu_mil_context); // Archery Range - bind_building_key(action.get("BUILDING_STBL"), 101, &this->build_menu_mil_context); // Stable - bind_building_key(action.get("BUILDING_SIWS"), 49, &this->build_menu_mil_context); // Siege Workshop - bind_building_key(action.get("BUILDING_WCTWX"), 598, &this->build_menu_mil_context); // Outpost - // TODO for palisade and stone wall: Drag walls, automatically adjust orientation - // TODO: This just cycles through all palisade textures - bind_building_key(action.get("BUILDING_WALL"), 72, &this->build_menu_mil_context); // Palisade Wall - // TODO: Fortified wall has a different ID - bind_building_key(action.get("BUILDING_WALL2"), 117, &this->build_menu_mil_context); // Stone Wall - // TODO: Upgraded versions have different IDs - bind_building_key(action.get("BUILDING_WCTW"), 79, &this->build_menu_mil_context); // Watch Tower - bind_building_key(action.get("BUILDING_WCTW4"), 236, &this->build_menu_mil_context); // Bombard Tower - // TODO: Gate placement - 659 is horizontal closed - bind_building_key(action.get("BUILDING_GTCA2"), 659, &this->build_menu_mil_context); // Gate - bind_building_key(action.get("BUILDING_CSTL"), 82, &this->build_menu_mil_context); // Castle - - this->building_context.bind(action.get("CANCEL"), - [this, input](const input::legacy::action_arg_t &) { - input->remove_context(&this->building_context); - this->announce_buttons_type(); - this->type_focus = nullptr; - }); - - auto bind_build = [this, input, &coord](input::legacy::action_t action, const bool increase) { - this->building_context.bind(action, [this, increase, input, &coord](const input::legacy::action_arg_t &arg) { - this->mousepos_phys3 = arg.mouse.to_phys3(coord, 0); - this->mousepos_tile = this->mousepos_phys3.to_tile(); - - bool placed = this->place_selection(this->mousepos_phys3); - - if (placed && !increase) { - this->type_focus = nullptr; - input->remove_context(&this->building_context); - this->announce_buttons_type(); - } - }); - }; - - bind_build(action.get("BUILD"), false); - bind_build(action.get("KEEP_BUILDING"), true); - - auto bind_select = [this, input, display](input::legacy::action_t action, const bool increase) { - this->bind(action, [this, increase, input, display](const input::legacy::action_arg_t &arg) { - auto mousepos_camgame = arg.mouse.to_camgame(display->coord); - Terrain *terrain = display->get_game()->terrain.get(); - - this->selection->drag_update(mousepos_camgame); - this->selection->drag_release(*this->game_control->get_current_player(), terrain, increase); - InputContext *top_ctxt = &input->get_top_context(); - - if ((this->selection->get_selection_type() != selection_type_t::own_units or this->selection->contains_military(*this->game_control->get_current_player())) - and (top_ctxt == &this->build_menu_context || top_ctxt == &this->build_menu_mil_context)) { - input->remove_context(top_ctxt); - } - this->announce_buttons_type(); - this->announce_current_selection(); - }); - }; - - bind_select(action.get("SELECT"), false); - bind_select(action.get("INCREASE_SELECTION"), true); - - this->bind(action.get("ORDER_SELECT"), [this, input, &coord](const input::legacy::action_arg_t &arg) { - if (this->type_focus) { - // right click can cancel building placement - this->type_focus = nullptr; - input->remove_context(&this->building_context); - this->announce_buttons_type(); - } - - auto cmd = this->get_action(arg.mouse.to_phys3(coord)); - cmd.add_flag(command_flag::interrupt); - this->selection->all_invoke(cmd); - this->use_set_ability = false; - }); - - this->bind(action.get("BEGIN_SELECTION"), [this](const input::legacy::action_arg_t &) { - this->selecting = true; - }); - - this->bind(action.get("END_SELECTION"), [this](const input::legacy::action_arg_t &) { - this->selecting = false; - }); - - this->bind(input::legacy::event_class::MOUSE, [this, &coord](const input::legacy::action_arg_t &arg) { - auto mousepos_camgame = arg.mouse.to_camgame(coord); - this->mousepos_phys3 = mousepos_camgame.to_phys3(coord); - this->mousepos_tile = this->mousepos_phys3.to_tile(); - - // drag selection box - if (arg.e.cc == input::legacy::ClassCode(input::legacy::event_class::MOUSE_MOTION, 0) && this->selecting && !this->type_focus) { - this->selection->drag_update(mousepos_camgame); - this->announce_current_selection(); - return true; - } - return false; - }); -} - -bool ActionMode::available() const { - if (this->game_control == nullptr) { - log::log(MSG(warn) << "ActionMode::available() queried without " - "game control being attached."); - return false; - } - - presenter::LegacyDisplay *display = this->game_control->get_display(); - if (display->get_game() != nullptr) { - return true; - } - else { - log::log(MSG(warn) << "Cannot enter action mode without a game"); - return false; - } -} - -void ActionMode::on_enter() {} - -void ActionMode::on_exit() { - // Since on_exit is called after removing the active mode, if the top context isn't the global one then it must - // be either a build menu or the building context - presenter::LegacyDisplay *display = this->game_control->get_display(); - auto *input_manager = &display->get_input_manager(); - InputContext *top_ctxt = &input_manager->get_top_context(); - if (top_ctxt != &input_manager->get_global_context()) { - if (top_ctxt == &this->building_context) { - this->type_focus = nullptr; - } - input_manager->remove_context(top_ctxt); - this->announce_buttons_type(); - } - this->selecting = false; -} - -Command ActionMode::get_action(const coord::phys3 &pos) const { - presenter::LegacyDisplay *display = this->game_control->get_display(); - GameMain *game = display->get_game(); - - auto obj = game->terrain->obj_at_point(pos); - if (obj) { - Command c(*this->game_control->get_current_player(), &obj->unit, pos); - if (this->use_set_ability) { - c.set_ability(ability); - } - return c; - } - else { - Command c(*this->game_control->get_current_player(), pos); - if (this->use_set_ability) { - c.set_ability(ability); - } - return c; - } -} - -bool ActionMode::place_selection(coord::phys3 point) { - if (this->type_focus) { - auto player = this->game_control->get_current_player(); - - if (player->can_make(*this->type_focus)) { - // confirm building placement with left click - // first create foundation using the producer - presenter::LegacyDisplay *display = this->game_control->get_display(); - - UnitContainer *container = &display->get_game()->placed_units; - UnitReference new_building = container->new_unit(*this->type_focus, *player, point); - - // task all selected villagers to build - // TODO: editor placed objects are completed already - if (new_building.is_valid()) { - player->deduct(this->type_focus->cost.get(*player)); // TODO change, move elsewheres - - Command cmd(*player, new_building.get()); - cmd.set_ability(ability_type::build); - cmd.add_flag(command_flag::interrupt); - this->selection->all_invoke(cmd); - return true; - } - } - else { - // TODO show in game error message - return false; - } - } - return false; -} - -void ActionMode::render() { - ENSURE(this->game_control != nullptr, "game_control is unset"); - LegacyEngine *engine = this->game_control->get_engine(); - presenter::LegacyDisplay *display = this->game_control->get_display(); - - ENSURE(engine != nullptr, "engine is needed to render ActionMode"); - - if (GameMain *game = display->get_game()) { - Player *player = this->game_control->get_current_player(); - - this->announce_resources(); - - if (this->selection && this->selection->get_units_count() > 0) { - this->announce_current_selection(); - } - - // when a building is being placed - if (this->type_focus) { - auto txt = this->type_focus->default_texture(); - auto size = this->type_focus->foundation_size; - - tile_range center = building_center(this->mousepos_phys3, size, *game->terrain); - txt->sample(display->coord, center.draw.to_camhud(display->coord), player->color); - } - } - else { - display->render_text({0, 140}, 12, renderer::Colors::WHITE, "Action Mode requires a game"); - } - - ENSURE(this->selection != nullptr, "selection not set"); - - this->selection->on_drawhud(); -} - -std::string ActionMode::name() const { - return "Action Mode"; -} - -void ActionMode::announce() { - this->OutputMode::announce(); - - this->announce_resources(); - emit this->gui_signals.ability_changed( - this->use_set_ability ? std::to_string(this->ability) : ""); -} - -void ActionMode::announce_resources() { - if (this->game_control) { - if (Player *player = this->game_control->get_current_player()) { - for (auto i = static_cast::type>(game_resource::RESOURCE_TYPE_COUNT); i != 0; --i) { - auto resource_type = static_cast(i - 1); - - emit this->gui_signals.resource_changed( - resource_type, - static_cast(player->amount(resource_type))); - } - emit this->gui_signals.population_changed(player->population.get_demand(), player->population.get_capacity(), player->population.get_space() <= 0 && !player->population.is_capacity_maxed()); - } - } -} - -void ActionMode::announce_buttons_type() { - ActionButtonsType buttons_type; - presenter::LegacyDisplay *display = this->game_control->get_display(); - InputContext *top_ctxt = &display->get_input_manager().get_top_context(); - if (top_ctxt == &this->build_menu_context) { - buttons_type = ActionButtonsType::BuildMenu; - } - else if (top_ctxt == &this->build_menu_mil_context) { - buttons_type = ActionButtonsType::MilBuildMenu; - } - else if (top_ctxt == &this->building_context || this->selection->get_selection_type() != selection_type_t::own_units) { - buttons_type = ActionButtonsType::None; - } - else if (this->selection->contains_military(*this->game_control->get_current_player())) { - buttons_type = ActionButtonsType::MilitaryUnits; - } - else { - buttons_type = ActionButtonsType::CivilianUnits; - } - - if (buttons_type != this->buttons_type) { - this->buttons_type = buttons_type; - emit this->gui_signals.buttons_type_changed(buttons_type); - - // announce the changed input context - this->announce(); - } -} - -EditorModeSignals::EditorModeSignals(EditorMode *editor_mode) : - editor_mode{editor_mode} { -} - -void EditorModeSignals::on_current_player_name_changed() { - this->editor_mode->announce_categories(); -} - -EditorMode::EditorMode(qtsdl::GuiItemLink *gui_link) : - OutputMode{gui_link}, - editor_current_terrain{-1}, - current_type_id{-1}, - paint_terrain{}, - gui_signals{this} {} - - -void EditorMode::on_game_control_set() { - presenter::LegacyDisplay *display = this->game_control->get_display(); - auto &action = display->get_action_manager(); - - // bind required hotkeys - this->bind(action.get("ENABLE_BUILDING_PLACEMENT"), [this](const input::legacy::action_arg_t &) { - log::log(MSG(dbg) << "change category"); - emit this->gui_signals.toggle(); - }); - - this->bind(input::legacy::event_class::MOUSE, [this, display](const input::legacy::action_arg_t &arg) { - if (arg.e.cc == input::legacy::ClassCode(input::legacy::event_class::MOUSE_BUTTON, 1) || display->get_input_manager().is_down(input::legacy::event_class::MOUSE_BUTTON, 1)) { - if (this->paint_terrain) { - this->paint_terrain_at(arg.mouse.to_viewport(display->coord)); - } - else { - this->paint_entity_at(arg.mouse.to_viewport(display->coord), false); - } - return true; - } - else if (arg.e.cc == input::legacy::ClassCode(input::legacy::event_class::MOUSE_BUTTON, 3) || display->get_input_manager().is_down(input::legacy::event_class::MOUSE_BUTTON, 3)) { - if (!this->paint_terrain) { - this->paint_entity_at(arg.mouse.to_viewport(display->coord), true); - } - return true; - } - return false; - }); -} - -bool EditorMode::available() const { - if (this->game_control == nullptr) { - log::log(MSG(warn) << "game_control not yet linked to EditorMode"); - return false; - } - else if (this->game_control->get_display()->get_game() != nullptr) { - return true; - } - else { - log::log(MSG(warn) << "Cannot enter editor mode without a game"); - return false; - } -} - -void EditorMode::on_enter() {} - -void EditorMode::on_exit() {} - -void EditorMode::render() {} - -std::string EditorMode::name() const { - return "Editor mode"; -} - -void EditorMode::set_current_type_id(int current_type_id) { - this->current_type_id = current_type_id; -} - -void EditorMode::set_current_terrain_id(terrain_t current_terrain_id) { - this->editor_current_terrain = current_terrain_id; -} - -void EditorMode::set_paint_terrain(bool paint_terrain) { - this->paint_terrain = paint_terrain; -} - -void EditorMode::paint_terrain_at(const coord::viewport &point) { - presenter::LegacyDisplay *display = this->game_control->get_display(); - Terrain *terrain = display->get_game()->terrain.get(); - - auto mousepos_tile = point.to_tile(display->coord); - - TerrainChunk *chunk = terrain->get_create_chunk(mousepos_tile); - chunk->get_data(mousepos_tile.get_pos_on_chunk())->terrain_id = editor_current_terrain; -} - -void EditorMode::paint_entity_at(const coord::viewport &point, const bool del) { - presenter::LegacyDisplay *display = this->game_control->get_display(); - GameMain *game = display->get_game(); - Terrain *terrain = game->terrain.get(); - - auto mousepos_phys3 = point.to_phys3(display->coord); - auto mousepos_tile = mousepos_phys3.to_tile(); - - TerrainChunk *chunk = terrain->get_create_chunk(mousepos_tile); - // TODO : better detection of presence of unit - if (!chunk->get_data(mousepos_tile.get_pos_on_chunk())->obj.empty()) { - if (del) { - // delete first object currently standing at the clicked position - TerrainObject *obj = chunk->get_data(mousepos_tile.get_pos_on_chunk())->obj[0]; - obj->remove(); - } - } - else if (!del && this->current_type_id != -1) { - Player *player = this->game_control->get_current_player(); - UnitType *selected_type = player->get_type(this->current_type_id); - - // tile is empty so try creating a unit - UnitContainer *container = &game->placed_units; - container->new_unit(*selected_type, *this->game_control->get_current_player(), mousepos_phys3); - } -} - -void EditorMode::announce_categories() { - if (this->game_control) - if (auto player = this->game_control->get_current_player()) - emit this->gui_signals.categories_changed(player->civ->get_type_categories()); - - emit this->gui_signals.categories_content_changed(); -} - -void EditorMode::announce_category_content(const std::string &category_name) { - if (this->game_control) - if (auto player = this->game_control->get_current_player()) { - auto inds = player->civ->get_category(category_name); - std::vector> type_and_texture(inds.size()); - - auto it = std::begin(type_and_texture); - - std::for_each(std::begin(inds), std::end(inds), [player, &it](auto index) { - *it++ = std::make_tuple(index, player->get_type(index)->default_texture()->id); - }); - - emit this->gui_signals.category_content_changed(category_name, type_and_texture); - } -} - -void EditorMode::announce() { - OutputMode::announce(); - this->announce_categories(); -} - -void EditorMode::set_game_control(GameControl *game_control) { - if (this->game_control != game_control) { - if (this->game_control) - QObject::disconnect( - &this->game_control->gui_signals, - &GameControlSignals::current_player_name_changed, - &this->gui_signals, - &EditorModeSignals::on_current_player_name_changed); - - // actually set the control - OutputMode::set_game_control(game_control); - - if (this->game_control) - QObject::connect( - &this->game_control->gui_signals, - &GameControlSignals::current_player_name_changed, - &this->gui_signals, - &EditorModeSignals::on_current_player_name_changed); - - this->announce_categories(); - } - else { - // just set the control - OutputMode::set_game_control(game_control); - } -} - -GameControlSignals::GameControlSignals(GameControl *game_control) : - game_control{game_control} { -} - -void GameControlSignals::on_game_running(bool running) { - if (running) - this->game_control->announce_current_player_name(); -} - -GameControl::GameControl(qtsdl::GuiItemLink *gui_link) : - engine{nullptr}, - game{nullptr}, - active_mode{nullptr}, - active_mode_index{-1}, - current_player{1}, - gui_signals{this}, - gui_link{gui_link} { -} - -void GameControl::set_engine(LegacyEngine *engine) { - // TODO: decide to either go for a full Engine QML-singleton or for a regular object - ENSURE(!this->engine || this->engine == engine, "relinking GameControl to another engine is not supported and not caught properly"); - - if (not this->engine) { - this->engine = engine; - - // add handlers - this->display->register_drawhud_action(this, -1); - - auto &action = this->display->get_action_manager(); - - auto &global_input_context = display->get_input_manager().get_global_context(); - - // advance the active mode - global_input_context.bind(action.get("TOGGLE_CONSTRUCT_MODE"), [this](const input::legacy::action_arg_t &) { - this->set_mode((this->active_mode_index + 1) % this->modes.size()); - }); - - // Switching between players with the 1-8 keys - auto bind_player_switch = [this, &global_input_context](input::legacy::action_t action, size_t player_index) { - global_input_context.bind(action, [this, player_index](const input::legacy::action_arg_t &) { - if (this->current_player != player_index) - if (auto game = this->display->get_game()) - if (player_index < game->player_count()) { - this->current_player = player_index; - this->announce_current_player_name(); - } - }); - }; - - bind_player_switch(action.get("SWITCH_TO_PLAYER_1"), 0); - bind_player_switch(action.get("SWITCH_TO_PLAYER_2"), 1); - bind_player_switch(action.get("SWITCH_TO_PLAYER_3"), 2); - bind_player_switch(action.get("SWITCH_TO_PLAYER_4"), 3); - bind_player_switch(action.get("SWITCH_TO_PLAYER_5"), 4); - bind_player_switch(action.get("SWITCH_TO_PLAYER_6"), 5); - bind_player_switch(action.get("SWITCH_TO_PLAYER_7"), 6); - bind_player_switch(action.get("SWITCH_TO_PLAYER_8"), 7); - - this->display->announce_global_binds(); - } -} -void GameControl::set_display(presenter::LegacyDisplay *display) { - this->display = display; -} - -void GameControl::set_game(GameMainHandle *game) { - if (this->game != game) { - if (this->game) - QObject::disconnect(&this->game->gui_signals, - &GameMainSignals::game_running, - &this->gui_signals, - &GameControlSignals::on_game_running); - - this->game = game; - - if (this->game) - QObject::connect(&this->game->gui_signals, - &GameMainSignals::game_running, - &this->gui_signals, - &GameControlSignals::on_game_running); - } -} - -void GameControl::set_modes(const std::vector &modes) { - const int old_mode_index = this->active_mode_index; - - this->set_mode(-1); - this->modes = modes; - - for (auto mode : this->modes) { - // link the controller to the mode - mode->set_game_control(this); - } - - // announce the newly set modes - emit this->gui_signals.modes_changed(this->active_mode, - this->active_mode_index); - - // try to enter the mode we were in before - // assuming its index didn't change. - if (old_mode_index != -1) { - if (old_mode_index < std::distance(std::begin(this->modes), - std::end(this->modes))) { - this->set_mode(old_mode_index, true); - } - else { - log::log(MSG(warn) << "couldn't enter previous gui mode #" - << old_mode_index << " as it vanished."); - } - } -} - -void GameControl::announce_mode() { - emit this->gui_signals.mode_changed(this->active_mode, - this->active_mode_index); - - if (this->active_mode != nullptr) { - emit this->active_mode->announce(); - } -} - -void GameControl::announce_current_player_name() { - if (Player *player = this->get_current_player()) { - emit this->gui_signals.current_player_name_changed( - "[" + std::to_string(player->color) + "] " + player->name); - emit this->gui_signals.current_civ_index_changed(player->civ->civ_id); - } -} - -void ActionMode::announce_current_selection() { - Player *player = this->game_control->get_current_player(); - emit this->gui_signals.selection_changed(this->selection, player); -} - -bool GameControl::on_drawhud() { - // render the active mode - if (this->active_mode) - this->active_mode->render(); - - return true; -} - -Player *GameControl::get_current_player() const { - if (auto game = this->display->get_game()) { - unsigned int number = game->player_count(); - return game->get_player(this->current_player % number); - } - return nullptr; -} - -void GameControl::set_mode(int mode_index, bool signal_if_unchanged) { - bool need_to_signal = signal_if_unchanged; - - // do we wanna set a new mode? - if (mode_index != -1) { - // if the new mode is valid, available and not active at the moment - if (mode_index < std::distance(std::begin(this->modes), - std::end(this->modes)) - && this->modes[mode_index]->available() - && this->active_mode_index != mode_index) { - ENSURE(!this->active_mode == (this->active_mode_index == -1), - "inconsistency between the active mode index and pointer"); - - // exit from the old mode - if (this->active_mode) { - this->display->get_input_manager().remove_context( - this->active_mode); - - // trigger the exit callback of the mode - if (this->active_mode) { - this->active_mode->on_exit(); - } - } - - // set the new active mode - this->active_mode_index = mode_index; - this->active_mode = this->modes[mode_index]; - - // trigger the entry callback - this->active_mode->on_enter(); - - // add the mode-local input context - this->display->get_input_manager().push_context( - this->active_mode); - - need_to_signal = true; - } - } - else { - // unassign the active mode - if (this->active_mode) { - // remove the input context - this->display->get_input_manager().remove_context( - this->active_mode); - - this->active_mode_index = -1; - this->active_mode = nullptr; - - need_to_signal = true; - } - } - - if (need_to_signal) { - this->announce_mode(); - } -} - -LegacyEngine *GameControl::get_engine() const { - if (this->engine == nullptr) { - throw Error{MSG(err) << "game control doesn't have a valid engine pointer yet"}; - } - - return this->engine; -} - -presenter::LegacyDisplay *GameControl::get_display() const { - if (this->engine == nullptr) { - throw Error{MSG(err) << "game control doesn't have a valid engine pointer yet"}; - } - - return this->display; -} - - -} // namespace openage diff --git a/libopenage/presenter/legacy/game_control.h b/libopenage/presenter/legacy/game_control.h deleted file mode 100644 index cad9deab7b..0000000000 --- a/libopenage/presenter/legacy/game_control.h +++ /dev/null @@ -1,395 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -#include - -#include "coord/pixel.h" -#include "gamestate/old/game_main.h" -#include "gui/guisys/link/gui_item_link.h" -#include "handlers.h" -#include "input/legacy/input_context.h" -#include "presenter/legacy/legacy.h" -#include "rng/rng.h" -#include "unit/command.h" -#include "unit/selection.h" -#include "unit/unit_type.h" - -namespace qtsdl { -class GuiItemLink; -} // namespace qtsdl - -namespace openage { - -class ActionMode; -class EditorMode; -class LegacyEngine; -class GameControl; - - -/** - * Signals for a gui mode. - */ -class OutputModeSignals : public QObject { - Q_OBJECT - -public: -signals: - /** - * Signal is triggered to anounce a new output mode. - * name: name of the gui mode. - */ - void announced(const std::string &name); - - /** - * Signal is triggered when the bindings of the mode change. - * binds: list of strings that describe keybindings. - */ - void binds_changed(const std::vector &binds); -}; - - -/** - * A target for input handling and gui rendering. - * This allows to switch to different display UIs. - */ -class OutputMode : public input::legacy::InputContext { -public: - explicit OutputMode(qtsdl::GuiItemLink *gui_link); - virtual ~OutputMode(); - - /** - * Is this mode able to be used? - */ - virtual bool available() const = 0; - - /** - * Called when switching to this mode. - */ - virtual void on_enter() = 0; - - /** - * Called when the mode is left. - */ - virtual void on_exit() = 0; - - /** - * Display this mode. - */ - virtual void render() = 0; - - /** - * Used for displaying the current mode. - */ - virtual std::string name() const = 0; - - /** - * Emit the "announced" signal with (name, InputContext::active_binds). - */ - virtual void announce(); - - /** - * Called when the GameControl is set in QML. - */ - virtual void set_game_control(GameControl *game_control); - - /** - * Called after GameControl has been set by QML from `set_game_control`. - */ - virtual void on_game_control_set() = 0; - -protected: - GameControl *game_control; - -public: - /** - * Signals to be triggered when the mode canges. - */ - OutputModeSignals gui_signals; - - qtsdl::GuiItemLink *gui_link; -}; - - -/** - * This is mainly the game editor. - * Shows menus to choose units to build. - */ -class CreateMode : public OutputMode { -public: - CreateMode(qtsdl::GuiItemLink *gui_link); - - bool available() const override; - void on_enter() override; - void on_exit() override; - void render() override; - std::string name() const override; - void on_game_control_set() override; -}; - -enum class ActionButtonsType { - None, - MilitaryUnits, - CivilianUnits, - BuildMenu, - MilBuildMenu -}; - - -class ActionModeSignals : public QObject { - Q_OBJECT - -public: - explicit ActionModeSignals(ActionMode *action_mode); - -public slots: - void on_action(const std::string &action_name); - -signals: - void resource_changed(game_resource resource, int amount); - void population_changed(int demand, int capacity, bool warn); - void selection_changed(const UnitSelection *unit_selection, const Player *player); - void ability_changed(const std::string &ability); - void buttons_type_changed(const ActionButtonsType type); - -private: - ActionMode *action_mode; -}; - - -/** - * This is the main game UI. - * - * Used to control units, issue commands, basically this is where you - * sink your time in when playing. - */ -class ActionMode : public OutputMode { -public: - ActionMode(qtsdl::GuiItemLink *gui_link); - - bool available() const override; - void on_enter() override; - void on_exit() override; - void render() override; - std::string name() const override; - void on_game_control_set() override; - -private: - friend ActionModeSignals; - /** - * sends to gui the properties that it needs - */ - virtual void announce() override; - - /** - * sends to gui the amounts of resources - */ - void announce_resources(); - - /** - * sends to gui the buttons it should use for the action buttons - * (if changed) - */ - void announce_buttons_type(); - - void announce_current_selection(); - - /** - * decides which type of right mouse click command - * to issue based on position. - * - * if a unit is at the position the command should target the unit, - * otherwise target ground position - */ - Command get_action(const coord::phys3 &pos) const; - - /** - * Register the keybindings. - */ - void create_binds(); - - /** - * used after opening the build menu - */ - InputContext build_menu_context; - - /** - * used after opening the military build menu - */ - InputContext build_menu_mil_context; - - /** - * used when selecting the building placement - */ - InputContext building_context; - - /** - * places hovering building - */ - bool place_selection(coord::phys3 point); - - // currently selected units - UnitSelection *selection; - - // restrict command abilities - bool use_set_ability; - ability_type ability; - - // a selected type for placement - UnitType *type_focus; - - // TODO these shouldn't be here. remove them ASAP. - // they are used to carry over mouse information - // into some of the game control lambda functions - coord::phys3 mousepos_phys3{0, 0, 0}; - coord::tile mousepos_tile{0, 0}; - bool selecting; - - ActionButtonsType buttons_type; - - // used for random type creation - rng::RNG rng; - -public: - ActionModeSignals gui_signals; -}; - - -class EditorModeSignals : public QObject { - Q_OBJECT - -public: - explicit EditorModeSignals(EditorMode *editor_mode); - -public slots: - void on_current_player_name_changed(); - -signals: - void toggle(); - void categories_changed(const std::vector &categories); - void categories_content_changed(); - void category_content_changed(const std::string &category_name, std::vector> &type_and_texture); - -private: - EditorMode *editor_mode; -}; - -/** - * UI mode to provide an interface for map editing. - */ -class EditorMode : public OutputMode { -public: - explicit EditorMode(qtsdl::GuiItemLink *gui_link); - - bool available() const override; - void on_enter() override; - void on_exit() override; - void render() override; - std::string name() const override; - void on_game_control_set() override; - - void set_current_type_id(int current_type_id); - void set_current_terrain_id(openage::terrain_t current_terrain_id); - void set_paint_terrain(bool paint_terrain); - - bool on_single_click(int button, coord::viewport point); - - void announce_categories(); - void announce_category_content(const std::string &category_name); - -private: - virtual void announce() override; - virtual void set_game_control(GameControl *game_control) override; - - void paint_terrain_at(const coord::viewport &point); - void paint_entity_at(const coord::viewport &point, const bool del); - - /** - * currently selected terrain id - */ - terrain_t editor_current_terrain; - int current_type_id; - std::string category; - - /** - * mouse click mode: - * true = terrain painting, - * false = unit placement - */ - bool paint_terrain; - -public: - EditorModeSignals gui_signals; -}; - - -class GameControlSignals : public QObject { - Q_OBJECT - -public: - explicit GameControlSignals(GameControl *game_control); - -public slots: - void on_game_running(bool running); - -signals: - void mode_changed(OutputMode *mode, int mode_index); - void modes_changed(OutputMode *mode, int mode_index); - - void current_player_name_changed(const std::string ¤t_player_name); - void current_civ_index_changed(int current_civ_index); - void is_selected_unit_changed(bool is_selected_unit); - -private: - GameControl *game_control; -}; - - -/** - * connects the gui system with the game engine - * switches between contexts such as editor mode and - * action mode - * - * hud rendering and input handling is redirected to the active mode - */ -class GameControl : public openage::HudHandler { -public: - explicit GameControl(qtsdl::GuiItemLink *gui_link); - - void set_engine(LegacyEngine *engine); - void set_display(presenter::LegacyDisplay *engine); - void set_game(GameMainHandle *game); - - void set_modes(const std::vector &modes); - - void set_mode(int mode, bool signal_if_unchanged = false); - void announce_mode(); - void announce_current_player_name(); - - bool on_drawhud() override; - - Player *get_current_player() const; - LegacyEngine *get_engine() const; - presenter::LegacyDisplay *get_display() const; - -private: - LegacyEngine *engine; - presenter::LegacyDisplay *display; - GameMainHandle *game; - - // control modes - std::vector modes; - - OutputMode *active_mode; - int active_mode_index; - - size_t current_player; - -public: - GameControlSignals gui_signals; - qtsdl::GuiItemLink *gui_link; -}; - -} // namespace openage diff --git a/libopenage/presenter/legacy/legacy.cpp b/libopenage/presenter/legacy/legacy.cpp deleted file mode 100644 index a959b1da35..0000000000 --- a/libopenage/presenter/legacy/legacy.cpp +++ /dev/null @@ -1,550 +0,0 @@ -// Copyright 2020-2023 the openage authors. See copying.md for legal info. - -#include "legacy.h" -#include - -#include "../../config.h" -#include "../../error/error.h" -#include "../../gamestate/old/game_main.h" -#include "../../gamestate/old/generator.h" -#include "../../gui/gui.h" -#include "../../log/log.h" -#include "../../texture.h" -#include "../../unit/selection.h" -#include "../../util/color.h" -#include "../../util/fps.h" -#include "../../util/opengl.h" -#include "../../util/strings.h" -#include "../../util/timer.h" -#include "../../version.h" -#include "legacy_engine.h" - -namespace openage::presenter { - - -LegacyDisplay::LegacyDisplay(const util::Path &path, LegacyEngine *engine) : - OptionNode{"Engine"}, - drawing_debug_overlay{this, "drawing_debug_overlay", false}, - drawing_huds{this, "drawing_huds", true}, - screenshot_manager{engine->get_job_manager()}, - action_manager{&this->input_manager, std::shared_ptr(engine->get_cvar_manager())}, - audio_manager{engine->get_job_manager()}, - input_manager{&this->action_manager} { - // TODO get this from config system (cvar) - int32_t fps_limit = 0; - if (fps_limit > 0) { - this->ns_per_frame = 1e9 / fps_limit; - } - else { - this->ns_per_frame = 0; - } - - this->font_manager = std::make_unique(); - for (uint32_t size : {12, 14, 20}) { - fonts[size] = this->font_manager->get_font("DejaVu Serif", "Book", size); - } - - // enqueue the engine's own input handler to the - // execution list. - this->register_resize_action(this); - - if (SDL_Init(SDL_INIT_VIDEO) < 0) { - throw Error(MSG(err) << "SDL video initialization: " << SDL_GetError()); - } - else { - log::log(MSG(info) << "Initialized SDL video subsystems."); - } - - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); - SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1); - SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); - SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); - - int32_t window_flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_MAXIMIZED; - this->window = SDL_CreateWindow( - "openage", - SDL_WINDOWPOS_CENTERED, - SDL_WINDOWPOS_CENTERED, - this->coord.viewport_size.x, - this->coord.viewport_size.y, - window_flags); - - if (this->window == nullptr) { - throw Error(MSG(err) << "Failed to create SDL window: " << SDL_GetError()); - } - - // load support for the PNG image formats, jpg bit: IMG_INIT_JPG - int wanted_image_formats = IMG_INIT_PNG; - int sdlimg_inited = IMG_Init(wanted_image_formats); - if ((sdlimg_inited & wanted_image_formats) != wanted_image_formats) { - throw Error(MSG(err) << "Failed to init PNG support: " << IMG_GetError()); - } - - if (false) { - this->glcontext = error::create_debug_context(this->window); - } - else { - this->glcontext = SDL_GL_CreateContext(this->window); - } - - if (this->glcontext == nullptr) { - throw Error(MSG(err) << "Failed creating OpenGL context: " << SDL_GetError()); - } - - // check the OpenGL version, for shaders n stuff - if (!epoxy_is_desktop_gl() || epoxy_gl_version() < 21) { - throw Error(MSG(err) << "OpenGL 2.1 not available"); - } - - // to quote the standard doc: - // 'The value gives a rough estimate - // of the largest texture that the GL can handle' - // -> wat? - // anyways, we need at least 1024x1024. - int max_texture_size; - glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size); - log::log(MSG(dbg) << "Maximum supported texture size: " << max_texture_size); - if (max_texture_size < 1024) { - throw Error(MSG(err) << "Maximum supported texture size too small: " << max_texture_size); - } - - int max_texture_units; - glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &max_texture_units); - log::log(MSG(dbg) << "Maximum supported texture units: " << max_texture_units); - if (max_texture_units < 2) { - throw Error(MSG(err) << "Your GPU has not enough texture units: " << max_texture_units); - } - - // vsync on - SDL_GL_SetSwapInterval(1); - - // enable alpha blending - glEnable(GL_BLEND); - - // order of drawing relevant for depth - // what gets drawn last is displayed on top. - glDisable(GL_DEPTH_TEST); - - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - //// -- initialize the gui - util::Path qml_root = engine->get_root_dir() / "assets" / "qml"; - if (not qml_root.is_dir()) { - throw Error{ERR << "could not find qml root folder " << qml_root}; - } - - util::Path qml_root_file = qml_root / "main.qml"; - if (not qml_root_file.is_file()) { - throw Error{ERR << "could not find main.qml file " << qml_root_file}; - } - - // TODO: in order to support qml-mods, the fslike and filelike - // library has to be integrated into qt. For now, - // figure out the absolute paths here and pass them in. - - std::string qml_root_str = qml_root.resolve_native_path(); - std::string qml_root_file_str = qml_root_file.resolve_native_path(); - - this->gui = std::make_unique( - this->window, // sdl window for the gui - qml_root_file_str, // entry qml file, absolute path. - qml_root_str // directory to watch for qml file changes - // &engine->get_qml_info() // qml data: Engine *, the data directory, ... - ); - //// -- gui initialization - - - // register the engines input manager - this->register_input_action(&this->input_manager); - - - // initialize engine related global keybinds - auto &global_input_context = this->get_input_manager().get_global_context(); - - input::legacy::ActionManager &action = this->get_action_manager(); - global_input_context.bind(action.get("STOP_GAME"), [engine](const input::legacy::action_arg_t &) { - engine->stop(); - }); - global_input_context.bind(action.get("TOGGLE_HUD"), [this](const input::legacy::action_arg_t &) { - this->drawing_huds.value = !this->drawing_huds.value; - }); - global_input_context.bind(action.get("SCREENSHOT"), [this](const input::legacy::action_arg_t &) { - this->get_screenshot_manager().save_screenshot(this->coord.viewport_size); - }); - global_input_context.bind(action.get("TOGGLE_DEBUG_OVERLAY"), [this](const input::legacy::action_arg_t &) { - this->drawing_debug_overlay.value = !this->drawing_debug_overlay.value; - }); - global_input_context.bind(action.get("TOGGLE_PROFILER"), [engine](const input::legacy::action_arg_t &) { - if (engine->external_profiler.currently_profiling) { - engine->external_profiler.stop(); - engine->external_profiler.show_results(); - } - else { - engine->external_profiler.start(); - } - }); - global_input_context.bind(input::legacy::event_class::MOUSE, [this](const input::legacy::action_arg_t &arg) { - if (arg.e.cc.has_class(input::legacy::event_class::MOUSE_MOTION) && this->get_input_manager().is_down(input::legacy::event_class::MOUSE_BUTTON, 2)) { - this->move_phys_camera(arg.motion.x, arg.motion.y); - return true; - } - return false; - }); - - this->text_renderer = std::make_unique(); - this->unit_selection = std::make_unique(engine); -} - - -LegacyDisplay::~LegacyDisplay() { - // deallocate the gui system - // this looses the opengl context in the qtsdl::GuiRenderer - // deallocation (of the QtOpenGLContext). - // so no gl* functions will be called after the gl context is gone. - this->gui.reset(nullptr); - - SDL_GL_DeleteContext(this->glcontext); - SDL_DestroyWindow(this->window); - IMG_Quit(); - SDL_Quit(); -} - - -bool LegacyDisplay::draw_debug_overlay() { - // Draw FPS counter in the lower right corner - this->render_text( - {this->coord.viewport_size.x - 70, 15}, - 20, - renderer::Colors::WHITE, - "%.0f fps", - this->fps_counter.display_fps); - - // Draw version string in the lower left corner - this->render_text( - {5, 35}, - 20, - renderer::Colors::WHITE, - "openage %s", - version::version); - this->render_text( - {5, 15}, - 12, - renderer::Colors::WHITE, - "%s", - config::config_option_string); - - // this->profiler.show(true); - - return true; -} - - -void LegacyDisplay::register_input_action(InputHandler *handler) { - this->on_input_event.push_back(handler); -} - - -void LegacyDisplay::register_tick_action(TickHandler *handler) { - this->on_engine_tick.push_back(handler); -} - - -void LegacyDisplay::register_drawhud_action(HudHandler *handler, int order) { - if (order < 0) { - this->on_drawhud.insert(this->on_drawhud.begin(), handler); - } - else { - this->on_drawhud.push_back(handler); - } -} - - -void LegacyDisplay::register_draw_action(DrawHandler *handler) { - this->on_drawgame.push_back(handler); -} - - -void LegacyDisplay::register_resize_action(ResizeHandler *handler) { - this->on_resize_handler.push_back(handler); -} - - -bool LegacyDisplay::on_resize(coord::viewport_delta new_size) { - // TODO: move all this to the renderer! - - log::log(MSG(dbg) << "engine window resize to " - << new_size.x << "x" << new_size.y); - - // update engine window size - this->coord.viewport_size = new_size; - - // update camgame window position, set it to center. - this->coord.camgame_viewport = coord::viewport{0, 0} + (this->coord.viewport_size / 2); - - // update camhud window position, set to lower left corner - this->coord.camhud_viewport = {0, coord::pixel_t{this->coord.viewport_size.y}}; - - // reset previous projection matrix - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - - // update OpenGL viewport: the rendering area - glViewport(0, 0, this->coord.viewport_size.x, this->coord.viewport_size.y); - - // set orthographic projection: left, right, bottom, top, near_val, far_val - glOrtho(0, this->coord.viewport_size.x, 0, this->coord.viewport_size.y, 9001, -1); - - // reset the modelview matrix - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - - return true; -} - - -renderer::TextRenderer *LegacyDisplay::get_text_renderer() { - return this->text_renderer.get(); -} - - -time_nsec_t LegacyDisplay::lastframe_duration_nsec() const { - return this->fps_counter.nsec_lastframe; -} - - -void LegacyDisplay::announce_global_binds() { - // emit this->gui_signals.global_binds_changed( - // this->get_input_manager().get_global_context().active_binds()); -} - - -UnitSelection *LegacyDisplay::get_unit_selection() { - return this->unit_selection.get(); -} - - -void LegacyDisplay::render_text(coord::viewport position, size_t size, const renderer::Color &color, const char *format, ...) { - auto it = this->fonts.find(size); - if (it == this->fonts.end()) { - throw Error(MSG(err) << "Unknown font size requested: " << size); - } - - renderer::Font *font = it->second; - - std::string buf; - va_list vl; - va_start(vl, format); - util::vsformat(format, vl, buf); - va_end(vl); - - this->text_renderer->set_font(font); - this->text_renderer->set_color(color); - this->text_renderer->draw(position.x, position.y, buf); -} - - -void LegacyDisplay::move_phys_camera(float x, float y, float amount) { - // calculate camera position delta from velocity and frame duration - coord::camgame_delta cam_delta{ - static_cast(+x * amount), - static_cast(-y * amount)}; - - // update camera phys position - this->coord.camgame_phys += cam_delta.to_phys3(this->coord, 0); -} - - -GameMain *LegacyDisplay::get_game() { - return this->game.get(); -} - - -void LegacyDisplay::start_game(std::unique_ptr &&game) { - // TODO: maybe implement a proper 1-to-1 connection - ENSURE(game, "linking game to engine problem"); - - this->game = std::move(game); - this->game->set_parent(this); -} - - -void LegacyDisplay::start_game(const Generator &generator) { - this->game = std::make_unique(generator); - this->game->set_parent(this); -} - - -void LegacyDisplay::end_game() { - this->game = nullptr; - this->unit_selection->clear(); -} - - -void LegacyDisplay::loop() { - //SDL_Event event; - //util::Timer cap_timer; - // - //while (this->running) { - // this->profiler.start_frame_measure(); - // this->fps_counter.frame(); - // cap_timer.reset(false); - // - // this->job_manager.execute_callbacks(); - // - // this->profiler.start_measure("events", {1.0, 0.0, 0.0}); - // // top level input handling - // while (SDL_PollEvent(&event)) { - // switch (event.type) { - // case SDL_QUIT: - // this->stop(); - // break; - // - // case SDL_WINDOWEVENT: { - // if (event.window.event == SDL_WINDOWEVENT_RESIZED) { - // coord::viewport_delta new_size{event.window.data1, event.window.data2}; - // - // // call additional handlers for the resize event - // for (auto &handler : on_resize_handler) { - // if (!handler->on_resize(new_size)) { - // break; - // } - // } - // } - // break; - // } - // - // default: - // for (auto &action : this->on_input_event) { - // if (false == action->on_input(&event)) { - // break; - // } - // } - // } // switch event - // } - // - // // here, call to Qt and process all the gui events. - // this->gui->process_events(); - // - // if (this->game) { - // // read camera movement input keys and/or cursor location, - // // and move camera accordingly. - // - // // camera movement speed, in pixels per millisecond - // // one pixel per millisecond equals 14.3 tiles/second - // float mov_x = 0.0, mov_y = 0.0, cam_movement_speed_keyboard = 0.5; - // Uint32 win_flags = SDL_GetWindowFlags(this->window); - // bool inp_focus = win_flags & SDL_WINDOW_INPUT_FOCUS; - // - // input::InputManager &input = this->get_input_manager(); - // using Edge = input::InputManager::Edge; - // - // if (inp_focus and (input.is_down(SDLK_LEFT) or input.is_mouse_at_edge(Edge::LEFT, coord.viewport_size.x))) { - // mov_x = -cam_movement_speed_keyboard; - // } - // if (inp_focus and (input.is_down(SDLK_RIGHT) or input.is_mouse_at_edge(Edge::RIGHT, coord.viewport_size.x))) { - // mov_x = cam_movement_speed_keyboard; - // } - // if (inp_focus and (input.is_down(SDLK_DOWN) or input.is_mouse_at_edge(Edge::DOWN, coord.viewport_size.y))) { - // mov_y = cam_movement_speed_keyboard; - // } - // if (inp_focus and (input.is_down(SDLK_UP) or input.is_mouse_at_edge(Edge::UP, coord.viewport_size.y))) { - // mov_y = -cam_movement_speed_keyboard; - // } - // - // // perform camera movement - // this->move_phys_camera( - // mov_x, - // mov_y, - // static_cast(this->lastframe_duration_nsec()) / 1e6); - // - // // update the currently running game - // this->game->update(this->lastframe_duration_nsec()); - // } - // this->profiler.end_measure("events"); - // - // // call engine tick callback methods - // for (auto &action : this->on_engine_tick) { - // if (false == action->on_tick()) { - // break; - // } - // } - // - // this->profiler.start_measure("rendering", {0.0, 1.0, 0.0}); - // - // // clear the framebuffer to black - // // in the future, we might disable it for lazy drawing - // glClearColor(0.0, 0.0, 0.0, 0.0); - // glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - // - // // invoke all game drawing handlers - // for (auto &action : this->on_drawgame) { - // if (false == action->on_draw()) { - // break; - // } - // } - // - // util::gl_check_error(); - // - // // draw the fps overlay - // if (this->drawing_debug_overlay.value) { - // this->draw_debug_overlay(); - // } - // - // // invoke all hud drawing callback methods - // if (this->drawing_huds.value) { - // for (auto &action : this->on_drawhud) { - // if (false == action->on_drawhud()) { - // break; - // } - // } - // } - // - // this->text_renderer->render(); - // - // util::gl_check_error(); - // - // this->profiler.end_measure("rendering"); - // - // this->profiler.start_measure("idle", {0.0, 0.0, 1.0}); - // - // // the rendering is done - // // swap the drawing buffers to actually show the frame - // SDL_GL_SwapWindow(window); - // - // if (this->ns_per_frame != 0) { - // uint64_t ns_for_current_frame = cap_timer.getval(); - // if (ns_for_current_frame < this->ns_per_frame) { - // SDL_Delay((this->ns_per_frame - ns_for_current_frame) / 1e6); - // } - // } - // - // this->profiler.end_measure("idle"); - // - // this->profiler.end_frame_measure(); - //} -} - - -audio::AudioManager &LegacyDisplay::get_audio_manager() { - return this->audio_manager; -} - - -ScreenshotManager &LegacyDisplay::get_screenshot_manager() { - return this->screenshot_manager; -} - - -input::legacy::ActionManager &LegacyDisplay::get_action_manager() { - return this->action_manager; -} - - -input::legacy::InputManager &LegacyDisplay::get_input_manager() { - return this->input_manager; -} - - -} // namespace openage::presenter diff --git a/libopenage/presenter/legacy/legacy.h b/libopenage/presenter/legacy/legacy.h deleted file mode 100644 index b4a82443fd..0000000000 --- a/libopenage/presenter/legacy/legacy.h +++ /dev/null @@ -1,302 +0,0 @@ -// Copyright 2020-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include "../../audio/audio_manager.h" -// pxd: from libopenage.coord.coordmanager cimport CoordManager -#include "../../coord/coordmanager.h" -#include "../../error/gl_debug.h" -#include "../../handlers.h" -// pxd: from libopenage.input.input_manager cimport InputManager -#include "../../input/legacy/input_manager.h" -#include "../../options.h" -#include "../../renderer/font/font.h" -#include "../../renderer/font/font_manager.h" -#include "../../renderer/text.h" -#include "../../screenshot.h" -#include "../../util/fps.h" -#include "../../util/path.h" -#include "../../util/timing.h" - - -namespace openage { - -class DrawHandler; -class TickHandler; -class ResizeHandler; - -class Generator; -class GameSpec; -class GameMain; -class UnitSelection; - -namespace gui { -class GUI; -} // namespace gui - -namespace renderer { - -class Font; -class FontManager; -class TextRenderer; -class Color; - -} // namespace renderer - - -namespace presenter { - -/** - * Temporary container class for the legacy renderer implementation. - */ -class LegacyDisplay final : public ResizeHandler - , public options::OptionNode { -public: - LegacyDisplay(const util::Path &path, LegacyEngine *engine); - - ~LegacyDisplay(); - - /** - * Draw the game version and the current FPS on screen. - */ - bool draw_debug_overlay(); - - /** - * register a new input event handler, run for each input event. - */ - void register_input_action(InputHandler *handler); - - /** - * register a tick action, executed upon engine tick. - */ - void register_tick_action(TickHandler *handler); - - /** - * register a hud drawing handler, drawn in hud coordinates. - * order: 1 above, -1 below - */ - void register_drawhud_action(HudHandler *handler, int order = 1); - - /** - * register a draw handler, run in game coordinates. - */ - void register_draw_action(DrawHandler *handler); - - /** - * register a resize handler, run when the window size changes. - */ - void register_resize_action(ResizeHandler *handler); - - /** - * window resize handler function. - * recalculates opengl settings like viewport and projection matrices. - */ - bool on_resize(coord::viewport_delta new_size) override; - - /** - * return this engine's text renderer. - */ - renderer::TextRenderer *get_text_renderer(); - - /** - * return the number of nanoseconds that have passed - * for rendering the last frame. - * - * use that for fps-independent input actions. - */ - time_nsec_t lastframe_duration_nsec() const; - - /** - * send keybindings help string to gui. - */ - void announce_global_binds(); - - /** - * return this engine's unit selection. - */ - UnitSelection *get_unit_selection(); - - /** - * render text at a position with the specified font size - */ - void render_text(coord::viewport position, size_t size, const renderer::Color &color, const char *format, ...) ATTRIBUTE_FORMAT(5, 6); - - /** - * move the phys3 camera incorporated in the engine - */ - void move_phys_camera(float x, float y, float amount = 1.0); - - /** - * Start a game with the given game generator. - */ - void start_game(const Generator &generator); - - /** - * Start a game with the given initialized game. - */ - void start_game(std::unique_ptr &&game); - - /** - * Stop the running game. - */ - void end_game(); - - /** - * return currently running game or null if a game is not - * currently running - */ - GameMain *get_game(); - - /** - * legacy engine loop function. - * this will be looped once per frame when the game is running. - * - * the loop invokes fps counting, SDL event handling, - * view translation, and calling the main draw_method. - */ - void loop(); - -public: - /** - * return this engine's audio manager. - */ - audio::AudioManager &get_audio_manager(); - - /** - * return this engine's screenshot manager. - */ - ScreenshotManager &get_screenshot_manager(); - - /** - * return this engine's action manager. - */ - input::legacy::ActionManager &get_action_manager(); - - /** - * return this engine's keybind manager. - */ - input::legacy::InputManager &get_input_manager(); - - /** - * FPS and game version are drawn when this is true. - */ - options::Var drawing_debug_overlay; - - /** - * this allows to disable drawing of every registered hud. - */ - options::Var drawing_huds; - - /** - * The coordinate state. - */ - coord::CoordManager coord; - -private: - /** - * the currently running game - */ - std::unique_ptr game; - - /** - * how many nanoseconds are in a frame (1e9 / fps_limit). - * 0 if there is no fps limit. - */ - time_nsec_t ns_per_frame; - - /** - * input event processor objects. - * called for each captured sdl input event. - */ - std::vector on_input_event; - - /** - * run every time the game is being drawn, - * with the renderer set to the camgame system - */ - std::vector on_drawgame; - - /** - * run every time the hud is being drawn, - * with the renderer set to the camhud system - */ - std::vector on_drawhud; - - /** - * list of handlers that are executed upon a resize event. - */ - std::vector on_resize_handler; - - /** - * run on every engine tick, after input handling, before rendering - */ - std::vector on_engine_tick; - - /** - * the frame counter measuring fps. - */ - util::FrameCounter fps_counter; - - /** - * the engine's screenshot manager. - */ - ScreenshotManager screenshot_manager; - - /** - * the engine's action manager. - */ - input::legacy::ActionManager action_manager; - - /** - * the engine's audio manager. - */ - audio::AudioManager audio_manager; - - /** - * the engine's keybind manager. - */ - input::legacy::InputManager input_manager; - - /** - * the engine's unit selection. - */ - std::unique_ptr unit_selection; - - /** - * the text fonts to be used for (can you believe it?) texts. - * maps fontsize -> font - */ - std::unordered_map fonts; - - /** - * SDL window where everything is displayed within. - */ - SDL_Window *window; - - /** - * SDL OpenGL context, we'll only have one, - * but it would allow having multiple ones. - * - * This is actually a void * but sdl2 thought it was a good idea to - * name it like a differently. - */ - SDL_GLContext glcontext; - - /** - * Qt GUI system - */ - std::unique_ptr gui; - - /** - * ttf font loading manager - */ - std::unique_ptr font_manager; - - /** - * 2d text renderer - */ - std::unique_ptr text_renderer; -}; - -} // namespace presenter -} // namespace openage diff --git a/libopenage/presenter/legacy/legacy_renderer.cpp b/libopenage/presenter/legacy/legacy_renderer.cpp deleted file mode 100644 index 3e65693f36..0000000000 --- a/libopenage/presenter/legacy/legacy_renderer.cpp +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#include "legacy_renderer.h" - -#include -#include -#include -#include -#include - -#include "../../console/console.h" -#include "../../gamedata/color_dummy.h" -#include "../../gamestate/old/game_main.h" -#include "../../gamestate/old/game_spec.h" -#include "../../input/legacy/input_manager.h" -#include "../../legacy_engine.h" -#include "../../log/log.h" -#include "../../renderer/text.h" -#include "../../unit/action.h" -#include "../../unit/command.h" -#include "../../unit/producer.h" -#include "../../unit/unit.h" -#include "../../unit/unit_texture.h" -#include "../../util/externalprofiler.h" -#include "../../util/timer.h" -#include "legacy.h" - -namespace openage { - - -RenderOptions::RenderOptions() : - OptionNode{"RendererOptions"}, - draw_debug{this, "draw_debug", false}, - terrain_blending{this, "terrain_blending", true} {} - - -LegacyRenderer::LegacyRenderer(LegacyEngine *e, presenter::LegacyDisplay *d) : - engine{e}, - display{d} { - // set options structure - this->settings.set_parent(this->display); - - // display callbacks - this->display->register_draw_action(this); - - // fetch asset loading dir - util::Path asset_dir = this->engine->get_root_dir()["assets"]; - - // load textures and stuff - gaben = new Texture{asset_dir["test"]["textures"]["gaben.png"]}; - - std::vector player_color_lines = util::read_csv_file( - asset_dir["converted/player_palette.docx"]); - - std::unique_ptr playercolors = std::make_unique(player_color_lines.size() * 4); - for (size_t i = 0; i < player_color_lines.size(); i++) { - auto line = &player_color_lines[i]; - playercolors[i * 4] = line->r / 255.0; - playercolors[i * 4 + 1] = line->g / 255.0; - playercolors[i * 4 + 2] = line->b / 255.0; - playercolors[i * 4 + 3] = line->a / 255.0; - } - - // shader initialisation - // read shader source codes and create shader objects for wrapping them. - const char *shader_header_code = "#version 120\n"; - std::string equals_epsilon_code = asset_dir["shaders/equalsEpsilon.glsl"].open().read(); - std::string texture_vert_code = asset_dir["shaders/maptexture.vert.glsl"].open().read(); - auto plaintexture_vert = std::make_unique( - GL_VERTEX_SHADER, - std::initializer_list{shader_header_code, texture_vert_code.c_str()}); - - std::string texture_frag_code = asset_dir["shaders/maptexture.frag.glsl"].open().read(); - auto plaintexture_frag = std::make_unique( - GL_FRAGMENT_SHADER, - std::initializer_list{shader_header_code, texture_frag_code.c_str()}); - - std::string teamcolor_frag_code = asset_dir["shaders/teamcolors.frag.glsl"].open().read(); - std::stringstream ss; - ss << player_color_lines.size(); - auto teamcolor_frag = std::make_unique( - GL_FRAGMENT_SHADER, - std::initializer_list{ - shader_header_code, - ("#define NUM_OF_PLAYER_COLORS " + ss.str() + "\n").c_str(), - equals_epsilon_code.c_str(), - teamcolor_frag_code.c_str()}); - - std::string alphamask_vert_code = asset_dir["shaders/alphamask.vert.glsl"].open().read(); - auto alphamask_vert = std::make_unique( - GL_VERTEX_SHADER, - std::initializer_list{shader_header_code, alphamask_vert_code.c_str()}); - - std::string alphamask_frag_code = asset_dir["shaders/alphamask.frag.glsl"].open().read(); - auto alphamask_frag = std::make_unique( - GL_FRAGMENT_SHADER, - std::initializer_list{shader_header_code, alphamask_frag_code.c_str()}); - - std::string texturefont_vert_code = asset_dir["shaders/texturefont.vert.glsl"].open().read(); - auto texturefont_vert = std::make_unique( - GL_VERTEX_SHADER, - std::initializer_list{shader_header_code, texturefont_vert_code.c_str()}); - - std::string texturefont_frag_code = asset_dir["shaders/texturefont.frag.glsl"].open().read(); - auto texturefont_frag = std::make_unique( - GL_FRAGMENT_SHADER, - std::initializer_list{shader_header_code, texturefont_frag_code.c_str()}); - - // create program for rendering simple textures - texture_shader::program = new shader::Program(plaintexture_vert.get(), plaintexture_frag.get()); - texture_shader::program->link(); - texture_shader::texture = texture_shader::program->get_uniform_id("texture"); - texture_shader::tex_coord = texture_shader::program->get_attribute_id("tex_coordinates"); - texture_shader::program->use(); - glUniform1i(texture_shader::texture, 0); - texture_shader::program->stopusing(); - - - // create program for tinting textures at alpha-marked pixels - // with team colors - teamcolor_shader::program = new shader::Program(plaintexture_vert.get(), teamcolor_frag.get()); - teamcolor_shader::program->link(); - teamcolor_shader::texture = teamcolor_shader::program->get_uniform_id("texture"); - teamcolor_shader::tex_coord = teamcolor_shader::program->get_attribute_id("tex_coordinates"); - teamcolor_shader::player_id_var = teamcolor_shader::program->get_uniform_id("player_number"); - teamcolor_shader::alpha_marker_var = teamcolor_shader::program->get_uniform_id("alpha_marker"); - teamcolor_shader::player_color_var = teamcolor_shader::program->get_uniform_id("player_color"); - teamcolor_shader::program->use(); - glUniform1i(teamcolor_shader::texture, 0); - glUniform1f(teamcolor_shader::alpha_marker_var, 254.0 / 255.0); - // fill the teamcolor shader's player color table: - glUniform4fv(teamcolor_shader::player_color_var, 64, playercolors.get()); - teamcolor_shader::program->stopusing(); - - - // create program for drawing textures that are alpha-masked before - alphamask_shader::program = new shader::Program(alphamask_vert.get(), alphamask_frag.get()); - alphamask_shader::program->link(); - alphamask_shader::base_coord = alphamask_shader::program->get_attribute_id("base_tex_coordinates"); - alphamask_shader::mask_coord = alphamask_shader::program->get_attribute_id("mask_tex_coordinates"); - alphamask_shader::show_mask = alphamask_shader::program->get_uniform_id("show_mask"); - alphamask_shader::base_texture = alphamask_shader::program->get_uniform_id("base_texture"); - alphamask_shader::mask_texture = alphamask_shader::program->get_uniform_id("mask_texture"); - alphamask_shader::program->use(); - glUniform1i(alphamask_shader::base_texture, 0); - glUniform1i(alphamask_shader::mask_texture, 1); - alphamask_shader::program->stopusing(); - - // Create program for texture based font rendering - texturefont_shader::program = new shader::Program(texturefont_vert.get(), texturefont_frag.get()); - texturefont_shader::program->link(); - texturefont_shader::texture = texturefont_shader::program->get_uniform_id("texture"); - texturefont_shader::color = texturefont_shader::program->get_uniform_id("color"); - texturefont_shader::tex_coord = texturefont_shader::program->get_attribute_id("tex_coordinates"); - texturefont_shader::program->use(); - glUniform1i(texturefont_shader::texture, 0); - texturefont_shader::program->stopusing(); - - // Renderer keybinds - // TODO: a renderer settings struct - // would allow these to be put somewhere better - input::legacy::ActionManager &action = this->display->get_action_manager(); - auto &global_input_context = this->display->get_input_manager().get_global_context(); - global_input_context.bind(action.get("TOGGLE_BLENDING"), [this](const input::legacy::action_arg_t &) { - this->settings.terrain_blending.value = !this->settings.terrain_blending.value; - }); - global_input_context.bind(action.get("TOGGLE_UNIT_DEBUG"), [this](const input::legacy::action_arg_t &) { - this->settings.draw_debug.value = !this->settings.draw_debug.value; - - log::log(MSG(dbg) << "Toggle debug grid"); - - // TODO remove this hack, use render settings instead - UnitAction::show_debug = !UnitAction::show_debug; - }); - - log::log(MSG(dbg) << "Loaded Renderer"); -} - -LegacyRenderer::~LegacyRenderer() { - // oh noes, release hl3 before that! - delete this->gaben; - - delete texture_shader::program; - delete teamcolor_shader::program; - delete alphamask_shader::program; - delete texturefont_shader::program; -} - - -bool LegacyRenderer::on_draw() { - // draw terrain - GameMain *game = this->display->get_game(); - - if (game) { - // draw gaben, our great and holy protector, bringer of the half-life 3. - gaben->draw(this->display->coord, coord::camgame{0, 0}); - - // TODO move render code out of terrain - if (game->terrain) { - game->terrain->draw(this->display, &this->settings); - } - } - return true; -} - -GameMain *LegacyRenderer::game() const { - return this->display->get_game(); -} - -GameSpec *LegacyRenderer::game_spec() const { - return this->game()->get_spec(); -} - -} // namespace openage diff --git a/libopenage/presenter/legacy/legacy_renderer.h b/libopenage/presenter/legacy/legacy_renderer.h deleted file mode 100644 index ea60a76623..0000000000 --- a/libopenage/presenter/legacy/legacy_renderer.h +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include -#include -#include - -#include "../../coord/tile.h" -#include "../../gamestate/old/game_spec.h" -#include "../../handlers.h" -#include "../../options.h" -#include "legacy.h" - -namespace openage { - -class GameMain; - -/** - * Options for the renderer. - * These will be included in the user interface - * via reflection, so adding new members will - * always be visible - * - * TODO include fog drawing etc - */ -class RenderOptions : public options::OptionNode { -public: - RenderOptions(); - - options::Var draw_debug; - options::Var terrain_blending; -}; - -/** - * renders the editor and action views - */ -class LegacyRenderer : DrawHandler { -public: - LegacyRenderer(LegacyEngine *e, presenter::LegacyDisplay *d); - ~LegacyRenderer(); - - bool on_draw() override; - - /** - * the game this renderer is using - */ - GameMain *game() const; - - /** - * GameSpec used by this renderer - */ - GameSpec *game_spec() const; - - Texture *gaben; - - RenderOptions settings; - -private: - LegacyEngine *engine; - presenter::LegacyDisplay *display; -}; - -} // namespace openage diff --git a/libopenage/presenter/presenter.cpp b/libopenage/presenter/presenter.cpp index 925d2d650d..9775b62d9b 100644 --- a/libopenage/presenter/presenter.cpp +++ b/libopenage/presenter/presenter.cpp @@ -1,4 +1,4 @@ -// Copyright 2019-2023 the openage authors. See copying.md for legal info. +// Copyright 2019-2024 the openage authors. See copying.md for legal info. #include "presenter.h" @@ -12,26 +12,30 @@ #include "input/controller/camera/controller.h" #include "input/controller/game/binding_context.h" #include "input/controller/game/controller.h" +#include "input/controller/hud/binding_context.h" +#include "input/controller/hud/controller.h" #include "input/input_context.h" #include "input/input_manager.h" #include "log/log.h" #include "renderer/camera/camera.h" #include "renderer/gui/gui.h" #include "renderer/gui/integration/public/gui_application_with_logger.h" -#include "renderer/gui/qml_info.h" #include "renderer/render_factory.h" +#include "renderer/render_pass.h" +#include "renderer/render_target.h" #include "renderer/resources/assets/asset_manager.h" #include "renderer/resources/shader_source.h" #include "renderer/resources/texture_info.h" #include "renderer/stages/camera/manager.h" -#include "renderer/stages/screen/screen_renderer.h" -#include "renderer/stages/skybox/skybox_renderer.h" -#include "renderer/stages/terrain/terrain_renderer.h" -#include "renderer/stages/world/world_renderer.h" -#include "renderer/window.h" +#include "renderer/stages/hud/render_stage.h" +#include "renderer/stages/screen/render_stage.h" +#include "renderer/stages/skybox/render_stage.h" +#include "renderer/stages/terrain/render_stage.h" +#include "renderer/stages/world/render_stage.h" #include "time/time_loop.h" #include "util/path.h" + namespace openage::presenter { Presenter::Presenter(const util::Path &root_dir, @@ -43,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(); @@ -76,8 +80,7 @@ void Presenter::run(bool debug_graphics) { void Presenter::set_simulation(const std::shared_ptr &simulation) { this->simulation = simulation; - auto render_factory = std::make_shared(this->terrain_renderer, - this->world_renderer); + auto render_factory = std::make_shared(this->terrain_renderer, this->world_renderer); this->simulation->attach_renderer(render_factory); } @@ -89,11 +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(); - this->window = renderer::Window::create("openage presenter test", 1024, 768, debug); + + // Window and renderer + this->window = renderer::Window::create("openage presenter test", window_settings); this->renderer = this->window->make_renderer(); // Asset mangement @@ -104,16 +110,26 @@ void Presenter::init_graphics(bool debug) { this->asset_manager->set_placeholder_animation(missing_tex); // Camera - this->camera = std::make_shared(this->renderer, - this->window->get_size()); + this->camera = std::make_shared(this->renderer, this->window->get_size()); + this->camera->look_at_coord(coord::scene3{10.0, 10.0, 0}); // Center camera on the map this->window->add_resize_callback([this](size_t w, size_t h, double /*scale*/) { this->camera->resize(w, h); }); + // Camera manager this->camera_manager = std::make_shared(this->camera); + // TODO: Make boundaries dynamic based on map size. + this->camera_manager->set_camera_boundaries( + renderer::camera::CameraBoundaries{ + renderer::camera::X_BOUND_MIN, + renderer::camera::X_BOUND_MAX, + renderer::camera::Y_BOUND_MIN, + renderer::camera::Y_BOUND_MAX, + renderer::camera::Z_BOUND_MIN, + renderer::camera::Z_BOUND_MAX}); // Skybox - this->skybox_renderer = std::make_shared( + this->skybox_renderer = std::make_shared( this->window, this->renderer, this->root_dir["assets"]["shaders"]); @@ -121,7 +137,7 @@ void Presenter::init_graphics(bool debug) { this->render_passes.push_back(this->skybox_renderer->get_render_pass()); // Terrain - this->terrain_renderer = std::make_shared( + this->terrain_renderer = std::make_shared( this->window, this->renderer, this->camera, @@ -131,7 +147,7 @@ void Presenter::init_graphics(bool debug) { this->render_passes.push_back(this->terrain_renderer->get_render_pass()); // Units/buildings - this->world_renderer = std::make_shared( + this->world_renderer = std::make_shared( this->window, this->renderer, this->camera, @@ -140,12 +156,21 @@ void Presenter::init_graphics(bool debug) { this->time_loop->get_clock()); this->render_passes.push_back(this->world_renderer->get_render_pass()); + // HUD + this->hud_renderer = std::make_shared( + this->window, + this->renderer, + this->camera, + this->root_dir["assets"]["shaders"], + this->asset_manager, + this->time_loop->get_clock()); + this->render_passes.push_back(this->hud_renderer->get_render_pass()); + this->init_gui(); this->init_final_render_pass(); if (this->simulation) { - auto render_factory = std::make_shared(this->terrain_renderer, - this->world_renderer); + auto render_factory = std::make_shared(this->terrain_renderer, this->world_renderer); this->simulation->attach_renderer(render_factory); } @@ -181,10 +206,10 @@ void Presenter::init_gui() { this->gui = std::make_shared( this->gui_app, // Qt application wrapper - this->window, // window for the gui + this->window, // window for the gui qml_root_file, // entry qml file, absolute path. - qml_root, // directory to watch for qml file changes - qml_assets, // qml data: Engine *, the data directory, ... + qml_root, // directory to watch for qml file changes + qml_assets, // qml data: Engine *, the data directory, ... this->renderer // openage renderer ); @@ -201,6 +226,9 @@ void Presenter::init_input() { this->input_manager->process(ev); }); this->window->add_mouse_button_callback([&](const QMouseEvent &ev) { + this->input_manager->process(ev); + }); + this->window->add_mouse_move_callback([&](const QMouseEvent &ev) { this->input_manager->set_mouse(ev.position().x(), ev.position().y()); this->input_manager->process(ev); }); @@ -216,12 +244,12 @@ void Presenter::init_input() { log::log(INFO << "Loading game simulation controls"); // TODO: Remove hardcoding - auto engine_controller = std::make_shared( + auto game_controller = std::make_shared( std::unordered_set{0, 1, 2, 3}, 0); auto engine_context = std::make_shared(); input::game::setup_defaults(engine_context, this->time_loop, this->simulation, this->camera); - this->input_manager->set_engine_controller(engine_controller); - input_ctx->set_engine_bindings(engine_context); + this->input_manager->set_game_controller(game_controller); + input_ctx->set_game_bindings(engine_context); } // attach GUI if it's initialized @@ -240,12 +268,22 @@ void Presenter::init_input() { input_ctx->set_camera_bindings(camera_context); } + // setup HUD controls + if (this->hud_renderer) { + log::log(INFO << "Loading HUD controls"); + auto hud_controller = std::make_shared(); + auto hud_context = std::make_shared(); + input::hud::setup_defaults(hud_context, this->hud_renderer); + this->input_manager->set_hud_controller(hud_controller); + input_ctx->set_hud_bindings(hud_context); + } + log::log(INFO << "Presenter: Input subsystem initialized"); } void Presenter::init_final_render_pass() { // Final output to window - this->screen_renderer = std::make_shared( + this->screen_renderer = std::make_shared( this->window, this->renderer, this->root_dir["assets"]["shaders"]); @@ -270,9 +308,11 @@ void Presenter::init_final_render_pass() { } void Presenter::render() { + // TODO: Pass current time to update() instead of fetching it in renderer this->camera_manager->update(); this->terrain_renderer->update(); this->world_renderer->update(); + this->hud_renderer->update(); this->gui->render(); for (auto &pass : this->render_passes) { diff --git a/libopenage/presenter/presenter.h b/libopenage/presenter/presenter.h index 6e7817ed2d..2059daeed0 100644 --- a/libopenage/presenter/presenter.h +++ b/libopenage/presenter/presenter.h @@ -1,12 +1,14 @@ -// Copyright 2019-2023 the openage authors. See copying.md for legal info. +// Copyright 2019-2024 the openage authors. See copying.md for legal info. #pragma once #include #include +#include "renderer/window.h" #include "util/path.h" + namespace qtgui { class GuiApplication; } @@ -41,20 +43,24 @@ namespace gui { class GUI; } +namespace hud { +class HudRenderStage; +} + namespace screen { -class ScreenRenderer; +class ScreenRenderStage; } namespace skybox { -class SkyboxRenderer; +class SkyboxRenderStage; } namespace terrain { -class TerrainRenderer; +class TerrainRenderStage; } namespace world { -class WorldRenderer; +class WorldRenderStage; } namespace resources { @@ -83,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. @@ -116,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. @@ -178,22 +184,27 @@ class Presenter { /** * Graphics output for the map background. */ - std::shared_ptr skybox_renderer; + std::shared_ptr skybox_renderer; /** * Graphics output for terrain. */ - std::shared_ptr terrain_renderer; + std::shared_ptr terrain_renderer; /** * Graphics output for units/buildings. */ - std::shared_ptr world_renderer; + std::shared_ptr world_renderer; + + /** + * Graphics output for the HUD. + */ + std::shared_ptr hud_renderer; /** * Final graphics output to the window screen. */ - std::shared_ptr screen_renderer; + std::shared_ptr screen_renderer; /** * Manager for loading/storing asset resources. diff --git a/libopenage/pyinterface/defs.h b/libopenage/pyinterface/defs.h index 3e00a07b8a..2af26198eb 100644 --- a/libopenage/pyinterface/defs.h +++ b/libopenage/pyinterface/defs.h @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once @@ -7,6 +7,6 @@ #ifndef Py_OBJECT_H // pxd: from cpython.ref cimport PyObject extern "C" { - typedef struct _object PyObject; +typedef struct _object PyObject; } #endif diff --git a/libopenage/pyinterface/exctranslate.h b/libopenage/pyinterface/exctranslate.h index 92124d3968..760d87c159 100644 --- a/libopenage/pyinterface/exctranslate.h +++ b/libopenage/pyinterface/exctranslate.h @@ -1,4 +1,4 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once @@ -146,4 +146,5 @@ OAAPI void set_exc_translation_funcs( void (*describe_py_exception)(PyException *)); -}} // openage::pyinterface +} // namespace pyinterface +} // namespace openage diff --git a/libopenage/pyinterface/exctranslate_tests.h b/libopenage/pyinterface/exctranslate_tests.h index 9446fe147b..622ec0165f 100644 --- a/libopenage/pyinterface/exctranslate_tests.h +++ b/libopenage/pyinterface/exctranslate_tests.h @@ -1,4 +1,4 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once @@ -44,4 +44,6 @@ OAAPI void bounce_call(const Func &func, int times); extern OAAPI PyIfFunc, int> bounce_call_py; -}}} // openage::pyinterface::tests +} // namespace tests +} // namespace pyinterface +} // namespace openage diff --git a/libopenage/pyinterface/functional.h b/libopenage/pyinterface/functional.h index b6e7e51e8c..1f5801ea74 100644 --- a/libopenage/pyinterface/functional.h +++ b/libopenage/pyinterface/functional.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 @@ -54,53 +54,52 @@ namespace pyinterface { * initialization time, use PyIfFunc instead of Func; that class has some * additional code to verify successful initialization. */ -template +template class Func { public: - Func() - : + Func() : fptr{nullptr} {} // for construction from lambdas and other callables (from C++). - template + template Func(F &&f) { this->fptr = f; } - template + template Func(std::reference_wrapper f) { this->fptr = f; } // for construction from std::function objects (from C++). - Func(const std::function &f) { + Func(const std::function &f) { this->fptr = f; } - Func(std::function &&f) { + Func(std::function &&f) { this->fptr = f; } // for assignment of lambdas and other callables (from C++). - template - Func &operator =(F &&f) { + template + Func &operator=(F &&f) { this->fptr = f; return *this; } - template - Func &operator =(std::reference_wrapper f) { + template + Func &operator=(std::reference_wrapper f) { this->fptr = f; return *this; } // for assignment of std::function objects (from C++). - Func &operator =(const std::function &f) { + Func &operator=(const std::function &f) { this->fptr = f; return *this; } - Func &operator =(std::function &&f) { + Func &operator=(std::function &&f) { this->fptr = f; return *this; } @@ -111,9 +110,10 @@ class Func { inline void check_fptr() const { if (not this->fptr) [[unlikely]] { throw Error( - MSG(err) << "Uninitialized Func object at " << - util::symbol_name(static_cast(this)) << ": " - "Can not call or convert to std::function.", + MSG(err) << "Uninitialized Func object at " + << util::symbol_name(static_cast(this)) + << ": " + "Can not call or convert to std::function.", true // collect backtrace info ); @@ -123,7 +123,7 @@ class Func { /** * for direct usage (mostly from Cython) */ - ReturnType call(ArgTypes ...args) const { + ReturnType call(ArgTypes... args) const { this->check_fptr(); return this->fptr(args...); } @@ -132,7 +132,7 @@ class Func { * for implicit conversion to std::function, * for usage in a context where std::function would be expected. */ - operator const std::function &() const { + operator const std::function &() const { this->check_fptr(); return this->fptr; } @@ -140,7 +140,7 @@ class Func { /** * for explicit conversion to std::function. */ - const std::function &get() const { + const std::function &get() const { this->check_fptr(); return this->fptr; } @@ -154,9 +154,9 @@ class Func { * Note that with clang, it's possible to directly pass function pointers, while with * gcc they need to be explicitly converted. Meh. */ - template - inline void bind(util::FunctionPtr f, BoundArgTypes ...bound_args) { - this->bind_catchexcept_impl::value, BoundArgTypes ...>(f, bound_args...); + template + inline void bind(util::FunctionPtr f, BoundArgTypes... bound_args) { + this->bind_catchexcept_impl::value, BoundArgTypes...>(f, bound_args...); } @@ -164,9 +164,9 @@ class Func { /** * Specialization for bind() with void return types. */ - template - inline typename std::enable_if::type bind_catchexcept_impl(util::FunctionPtr f, BoundArgTypes ...bound_args) { - this->fptr = [=](ArgTypes ...args) -> ReturnType { + template + inline typename std::enable_if::type bind_catchexcept_impl(util::FunctionPtr f, BoundArgTypes... bound_args) { + this->fptr = [=](ArgTypes... args) -> ReturnType { f.ptr(bound_args..., args...); translate_exc_py_to_cpp(); }; @@ -176,9 +176,9 @@ class Func { /** * Specialization for bind() with non-void return types. */ - template - inline typename std::enable_if::type bind_catchexcept_impl(util::FunctionPtr f, BoundArgTypes ...bound_args) { - this->fptr = [=](ArgTypes ...args) -> ReturnType { + template + inline typename std::enable_if::type bind_catchexcept_impl(util::FunctionPtr f, BoundArgTypes... bound_args) { + this->fptr = [=](ArgTypes... args) -> ReturnType { ReturnType &&result = f.ptr(bound_args..., args...); translate_exc_py_to_cpp(); return result; @@ -190,62 +190,62 @@ class Func { /** * Like bind, but does _not_ add an exception checker. */ - template - void bind_noexcept(util::FunctionPtr f, BoundArgTypes ...bound_args) { - this->fptr = [=](ArgTypes ...args) -> ReturnType { + template + void bind_noexcept(util::FunctionPtr f, BoundArgTypes... bound_args) { + this->fptr = [=](ArgTypes... args) -> ReturnType { return f.ptr(bound_args..., args...); }; } // non-variadic aliases for bind, for use by Cython - inline void bind0(ReturnType (*f)(ArgTypes ...)) { + inline void bind0(ReturnType (*f)(ArgTypes...)) { this->bind<>( - util::FunctionPtr(f)); + util::FunctionPtr(f)); } - inline void bind_noexcept0(ReturnType (*f)(ArgTypes ...)) { + inline void bind_noexcept0(ReturnType (*f)(ArgTypes...)) { this->bind_noexcept<>( - util::FunctionPtr(f)); + util::FunctionPtr(f)); } - template - inline void bind1(ReturnType (*f)(BoundArgType0, ArgTypes ...), BoundArgType0 bound_arg0) { + template + inline void bind1(ReturnType (*f)(BoundArgType0, ArgTypes...), BoundArgType0 bound_arg0) { this->bind( - util::FunctionPtr(f), bound_arg0); + util::FunctionPtr(f), bound_arg0); } - template - inline void bind_noexcept1(ReturnType (*f)(BoundArgType0, ArgTypes ...), BoundArgType0 bound_arg0) { + template + inline void bind_noexcept1(ReturnType (*f)(BoundArgType0, ArgTypes...), BoundArgType0 bound_arg0) { this->bind_noexcept( - util::FunctionPtr(f), bound_arg0); + util::FunctionPtr(f), bound_arg0); } - template - inline void bind2(ReturnType (*f)(BoundArgType0, BoundArgType1, ArgTypes ...), BoundArgType0 bound_arg0, BoundArgType1 bound_arg1) { + template + inline void bind2(ReturnType (*f)(BoundArgType0, BoundArgType1, ArgTypes...), BoundArgType0 bound_arg0, BoundArgType1 bound_arg1) { this->bind( - util::FunctionPtr(f), bound_arg0, bound_arg1); + util::FunctionPtr(f), bound_arg0, bound_arg1); } - template - inline void bind_noexcept2(ReturnType (*f)(BoundArgType0, BoundArgType1, ArgTypes ...), BoundArgType0 bound_arg0, BoundArgType1 bound_arg1) { + template + inline void bind_noexcept2(ReturnType (*f)(BoundArgType0, BoundArgType1, ArgTypes...), BoundArgType0 bound_arg0, BoundArgType1 bound_arg1) { this->bind_noexcept( - util::FunctionPtr(f), bound_arg0, bound_arg1); + util::FunctionPtr(f), bound_arg0, bound_arg1); } - template - inline void bind3(ReturnType (*f)(BoundArgType0, BoundArgType1, BoundArgType2, ArgTypes ...), BoundArgType0 bound_arg0, BoundArgType1 bound_arg1, BoundArgType2 bound_arg2) { + template + inline void bind3(ReturnType (*f)(BoundArgType0, BoundArgType1, BoundArgType2, ArgTypes...), BoundArgType0 bound_arg0, BoundArgType1 bound_arg1, BoundArgType2 bound_arg2) { this->bind( - util::FunctionPtr(f), bound_arg0, bound_arg1, bound_arg2); + util::FunctionPtr(f), bound_arg0, bound_arg1, bound_arg2); } - template - inline void bind_noexcept3(ReturnType (*f)(BoundArgType0, BoundArgType1, BoundArgType2, ArgTypes ...), BoundArgType0 bound_arg0, BoundArgType1 bound_arg1, BoundArgType2 bound_arg2) { + template + inline void bind_noexcept3(ReturnType (*f)(BoundArgType0, BoundArgType1, BoundArgType2, ArgTypes...), BoundArgType0 bound_arg0, BoundArgType1 bound_arg1, BoundArgType2 bound_arg2) { this->bind_noexcept( - util::FunctionPtr(f), bound_arg0, bound_arg1, bound_arg2); + util::FunctionPtr(f), bound_arg0, bound_arg1, bound_arg2); } private: - std::function fptr; + std::function fptr; }; @@ -271,7 +271,7 @@ class Func { * void bind_noexcept2 [BT0, BT1] (RT (*f)(BT0, BT1) with gil, BT0, BT1 ) except + * void bind_noexcept3 [BT0, BT1, BT2] (RT (*f)(BT0, BT1, BT2) with gil, BT0, BT1, BT2) except + */ -template +template using Func0 = Func; /* @@ -292,7 +292,7 @@ using Func0 = Func; * void bind_noexcept2 [BT0, BT1] (RT (*f)(BT0, BT1, AT0) with gil, BT0, BT1 ) except + * void bind_noexcept3 [BT0, BT1, BT2] (RT (*f)(BT0, BT1, BT2, AT0) with gil, BT0, BT1, BT2) except + */ -template +template using Func1 = Func; /* @@ -314,7 +314,7 @@ using Func1 = Func; * void bind_noexcept3 [BT0, BT1, BT2] (RT (*f)(BT0, BT1, BT2, AT0, AT1) with gil, BT0, BT1, BT2) except + */ -template +template using Func2 = Func; /* @@ -336,7 +336,7 @@ using Func2 = Func; * void bind_noexcept3 [BT0, BT1, BT2] (RT (*f)(BT0, BT1, BT2, AT0, AT1, AT2) with gil, BT0, BT1, BT2) except + */ -template +template using Func3 = Func; /* @@ -358,7 +358,7 @@ using Func3 = Func; * void bind_noexcept3 [BT0, BT1, BT2] (RT (*f)(BT0, BT1, BT2, AT0, AT1, AT2, AT3) with gil, BT0, BT1, BT2) except + */ -template +template using Func4 = Func; @@ -381,7 +381,7 @@ using Func4 = Func; * void bind_noexcept3 [BT0, BT1, BT2] (RT (*f)(BT0, BT1, BT2, AT0, AT1, AT2, AT3, AT4) with gil, BT0, BT1, BT2) except + */ -template +template using Func5 = Func; @@ -403,15 +403,16 @@ using Func5 = Func; * ctypedef Func4 PyIfFunc4 * ctypedef Func5 PyIfFunc5 */ -template -class PyIfFunc : public Func { +template +class PyIfFunc : public Func { public: PyIfFunc() { add_py_if_component(this, [=, this]() -> bool { try { this->check_fptr(); return true; - } catch (Error &) { + } + catch (Error &) { return false; } }); @@ -422,16 +423,17 @@ class PyIfFunc : public Func { } // no copy construction! - PyIfFunc(const PyIfFunc &other) = delete; - PyIfFunc(PyIfFunc &&other) = delete; - PyIfFunc &operator =(const PyIfFunc &other) = delete; - PyIfFunc &operator =(PyIfFunc &&other) = delete; + PyIfFunc(const PyIfFunc &other) = delete; + PyIfFunc(PyIfFunc &&other) = delete; + PyIfFunc &operator=(const PyIfFunc &other) = delete; + PyIfFunc &operator=(PyIfFunc &&other) = delete; // but you may convert this to a regular Func object. - operator const Func &() const { - return static_cast>(this->fptr); + operator const Func &() const { + return static_cast>(this->fptr); } }; -}} // openage::pyinterface +} // namespace pyinterface +} // namespace openage diff --git a/libopenage/pyinterface/pyexception.h b/libopenage/pyinterface/pyexception.h index 2cdb7260c2..92cd676553 100644 --- a/libopenage/pyinterface/pyexception.h +++ b/libopenage/pyinterface/pyexception.h @@ -1,4 +1,4 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once @@ -33,12 +33,13 @@ class PyExceptionBacktrace : public error::Backtrace { /** * ref is a raw reference to the associated PyObject. */ - PyExceptionBacktrace(PyObject *ref) : ref{ref} {} + PyExceptionBacktrace(PyObject *ref) : + ref{ref} {} /** * Accesses the associated Python exception object to translate the traceback as needed. */ - void get_symbols(std::function cb, bool reversed) const override; + void get_symbols(std::function cb, bool reversed) const override; private: PyObject *ref; @@ -83,4 +84,5 @@ class OAAPI PyException : public error::Error { extern OAAPI PyIfFunc> pyexception_bt_get_symbols; -}} // openage::pyinterface +} // namespace pyinterface +} // namespace openage diff --git a/libopenage/pyinterface/pyobject.h b/libopenage/pyinterface/pyobject.h index 0e90717e88..7ce39505e6 100644 --- a/libopenage/pyinterface/pyobject.h +++ b/libopenage/pyinterface/pyobject.h @@ -1,4 +1,4 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once @@ -72,13 +72,13 @@ class OAAPI PyObjectRef { * Assigns from an other PyObjectRef * (calls Py_XDECREF on the old value, and Py_XINCREF on the new one). */ - PyObjectRef &operator =(const PyObjectRef &other); + PyObjectRef &operator=(const PyObjectRef &other); /** * Move-assigns from an other PyObject * (calls Py_XDECREF on the old value). */ - PyObjectRef &operator =(PyObjectRef &&other); + PyObjectRef &operator=(PyObjectRef &&other); /** * Destroys the object, calls Py_XDECREF. @@ -116,12 +116,11 @@ class OAAPI PyObjectRef { /** * obj(args...) */ - template + template PyObjectRef call(Args... args) const { // this vector collects the function call arguments - std::vector arg_objs { - PyObjectRef(args)... - }; + std::vector arg_objs{ + PyObjectRef(args)...}; return this->call_impl(arg_objs); } @@ -251,7 +250,7 @@ class OAAPI PyObjectRef { * Implicit conversion to PyObject *. * Mainly for convenience to avoid all the get_ref() calls. */ - PyObject *operator ()() const noexcept { + PyObject *operator()() const noexcept { return this->ref; } @@ -285,7 +284,7 @@ using PyObj = PyObjectRef; /** * Stream operator for printing PyObjects */ -std::ostream &operator <<(std::ostream &os, const PyObjectRef &ref); +std::ostream &operator<<(std::ostream &os, const PyObjectRef &ref); // now follow the various Python callbacks that implement all of the above, @@ -313,7 +312,7 @@ extern OAAPI PyIfFunc py_callable; // pxd: PyIfFunc2[void, PyObjectRefPtr, PyObjectPtr] py_call0 extern OAAPI PyIfFunc py_call0; // pxd: PyIfFunc3[void, PyObjectRefPtr, PyObjectPtr, vector[PyObjectPtr]] py_calln -extern OAAPI PyIfFunc&> py_calln; +extern OAAPI PyIfFunc &> py_calln; // pxd: PyIfFunc2[cppbool, PyObjectPtr, string] py_hasattr extern OAAPI PyIfFunc py_hasattr; // pxd: PyIfFunc3[void, PyObjectRefPtr, PyObjectPtr, string] py_getattr @@ -346,13 +345,13 @@ extern OAAPI PyIfFunc py_modulename; extern OAAPI PyIfFunc py_classname; // pxd: PyIfFunc2[void, PyObjectRefPtr, const string] py_builtin -extern OAAPI PyIfFunc py_builtin; +extern OAAPI PyIfFunc py_builtin; // pxd: PyIfFunc2[void, PyObjectRefPtr, const string] py_import -extern OAAPI PyIfFunc py_import; +extern OAAPI PyIfFunc py_import; // pxd: PyIfFunc2[void, PyObjectRefPtr, const string] py_createstr -extern OAAPI PyIfFunc py_createstr; +extern OAAPI PyIfFunc py_createstr; // pxd: PyIfFunc2[void, PyObjectRefPtr, const string] py_createbytes -extern OAAPI PyIfFunc py_createbytes; +extern OAAPI PyIfFunc py_createbytes; // pxd: PyIfFunc2[void, PyObjectRefPtr, int] py_createint extern OAAPI PyIfFunc py_createint; // pxd: PyIfFunc1[void, PyObjectRefPtr] py_createdict @@ -367,7 +366,7 @@ extern OAAPI PyObjectRef True; // pxd: PyObjectRef False extern OAAPI PyObjectRef False; -} // pyinterface +} // namespace pyinterface /** @@ -440,5 +439,5 @@ using pyinterface::True; */ using pyinterface::False; -} // py -} // openage +} // namespace py +} // namespace openage diff --git a/libopenage/pyinterface/pyobject_tests.h b/libopenage/pyinterface/pyobject_tests.h index fa8492c0ab..3c8ab49d9d 100644 --- a/libopenage/pyinterface/pyobject_tests.h +++ b/libopenage/pyinterface/pyobject_tests.h @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once @@ -9,4 +9,6 @@ namespace tests { void pyobject(); void pyobject_demo(); -}}} // openage::pyinterface +} // namespace tests +} // namespace pyinterface +} // namespace openage diff --git a/libopenage/pyinterface/setup.h b/libopenage/pyinterface/setup.h index 2aa387c8d9..34a4fbf36e 100644 --- a/libopenage/pyinterface/setup.h +++ b/libopenage/pyinterface/setup.h @@ -1,4 +1,4 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #include @@ -30,7 +30,7 @@ namespace pyinterface { * It shall return true if the object has been properly initialized, * and shall not throw any exceptions. */ -void add_py_if_component(void *thisptr, std::function checker); +void add_py_if_component(void *thisptr, std::function checker); /** @@ -56,4 +56,5 @@ void destroy_py_if_component(void *thisptr); OAAPI void check(); -}} // openage::pyinterface +} // namespace pyinterface +} // namespace openage diff --git a/libopenage/renderer/CMakeLists.txt b/libopenage/renderer/CMakeLists.txt index 8a889de978..dfcb5e8b9e 100644 --- a/libopenage/renderer/CMakeLists.txt +++ b/libopenage/renderer/CMakeLists.txt @@ -1,12 +1,13 @@ add_sources(libopenage - animation.cpp color.cpp definitions.cpp geometry.cpp render_factory.cpp + render_pass.cpp + render_target.cpp + renderable.cpp renderer.cpp shader_program.cpp - text.cpp texture.cpp texture_array.cpp types.cpp diff --git a/libopenage/renderer/animation.cpp b/libopenage/renderer/animation.cpp deleted file mode 100644 index a9b0a54e37..0000000000 --- a/libopenage/renderer/animation.cpp +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2021-2021 the openage authors. See copying.md for legal info. - -#include "animation.h" - -namespace openage::renderer { - -Animation2d::Animation2d(const resources::Animation2dInfo &info) : - info{info} {} - -const resources::Animation2dInfo &Animation2d::get_info() const { - return this->info; -} - -} // namespace openage::renderer diff --git a/libopenage/renderer/animation.h b/libopenage/renderer/animation.h deleted file mode 100644 index aa5e27f8cf..0000000000 --- a/libopenage/renderer/animation.h +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2021-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include "renderer/resources/animation/animation_info.h" - -namespace openage::renderer { - -class Animation2d { -public: - Animation2d() = default; - ~Animation2d() = default; - - /** - * Get the animation information. - * - * @return Animation information. - */ - const resources::Animation2dInfo &get_info() const; - -protected: - /** - * Creates a 2D animation. - */ - Animation2d(const resources::Animation2dInfo &info); - - /** - * Information about the animation layers, angles and frames. - */ - resources::Animation2dInfo info; -}; - -} // namespace openage::renderer diff --git a/libopenage/renderer/camera/CMakeLists.txt b/libopenage/renderer/camera/CMakeLists.txt index f4ae421320..71de5c7bd1 100644 --- a/libopenage/renderer/camera/CMakeLists.txt +++ b/libopenage/renderer/camera/CMakeLists.txt @@ -1,3 +1,7 @@ add_sources(libopenage + boundaries.cpp camera.cpp + definitions.cpp + frustum_2d.cpp + frustum_3d.cpp ) diff --git a/libopenage/renderer/camera/boundaries.cpp b/libopenage/renderer/camera/boundaries.cpp new file mode 100644 index 0000000000..ca77344c7e --- /dev/null +++ b/libopenage/renderer/camera/boundaries.cpp @@ -0,0 +1,9 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "boundaries.h" + + +namespace openage::renderer::camera { + + +} // namespace openage::renderer::camera diff --git a/libopenage/renderer/camera/boundaries.h b/libopenage/renderer/camera/boundaries.h new file mode 100644 index 0000000000..484a398455 --- /dev/null +++ b/libopenage/renderer/camera/boundaries.h @@ -0,0 +1,25 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +namespace openage::renderer::camera { + +/** + * Defines boundaries for the camera's view. + */ +struct CameraBoundaries { + /// The minimum boundary for the camera's X-coordinate. + float x_min; + /// The maximum boundary for the camera's X-coordinate. + float x_max; + /// The minimum boundary for the camera's Y-coordinate. + float y_min; + /// The maximum boundary for the camera's Y-coordinate. + float y_max; + /// The minimum boundary for the camera's Z-coordinate. + float z_min; + /// The maximum boundary for the camera's Z-coordinate. + float z_max; +}; + +} // namespace openage::renderer::camera diff --git a/libopenage/renderer/camera/camera.cpp b/libopenage/renderer/camera/camera.cpp index 604ddf440f..14504998e0 100644 --- a/libopenage/renderer/camera/camera.cpp +++ b/libopenage/renderer/camera/camera.cpp @@ -1,4 +1,4 @@ -// Copyright 2022-2023 the openage authors. See copying.md for legal info. +// Copyright 2022-2024 the openage authors. See copying.md for legal info. #include "camera.h" @@ -19,19 +19,16 @@ Camera::Camera(const std::shared_ptr &renderer, util::Vector2s viewport_size) : scene_pos{Eigen::Vector3f(0.0f, 10.0f, 0.0f)}, viewport_size{viewport_size}, - zoom{1.0f}, - max_zoom_out{64.0f}, - default_zoom_ratio{1.0f / 49}, + zoom{DEFAULT_ZOOM}, + max_zoom_out{DEFAULT_MAX_ZOOM_OUT}, + default_zoom_ratio{DEFAULT_ZOOM_RATIO}, moved{true}, zoom_changed{true}, view{Eigen::Matrix4f::Identity()}, proj{Eigen::Matrix4f::Identity()} { this->look_at_scene(Eigen::Vector3f(0.0f, 0.0f, 0.0f)); - resources::UBOInput view_input{"view", resources::ubo_input_t::M4F32}; - resources::UBOInput proj_input{"proj", resources::ubo_input_t::M4F32}; - auto ubo_info = resources::UniformBufferInfo{resources::ubo_layout_t::STD140, {view_input, proj_input}}; - this->uniform_buffer = renderer->add_uniform_buffer(ubo_info); + this->init_uniform_buffer(renderer); log::log(INFO << "Created new camera at position " << "(" << this->scene_pos[0] @@ -55,10 +52,7 @@ Camera::Camera(const std::shared_ptr &renderer, viewport_changed{true}, view{Eigen::Matrix4f::Identity()}, proj{Eigen::Matrix4f::Identity()} { - resources::UBOInput view_input{"view", resources::ubo_input_t::M4F32}; - resources::UBOInput proj_input{"proj", resources::ubo_input_t::M4F32}; - auto ubo_info = resources::UniformBufferInfo{resources::ubo_layout_t::STD140, {view_input, proj_input}}; - this->uniform_buffer = renderer->add_uniform_buffer(ubo_info); + this->init_uniform_buffer(renderer); log::log(INFO << "Created new camera at position " << "(" << this->scene_pos[0] @@ -67,38 +61,7 @@ Camera::Camera(const std::shared_ptr &renderer, } void Camera::look_at_scene(Eigen::Vector3f scene_pos) { - if (scene_pos[1] > this->scene_pos[1]) { - // TODO: camera can't look at a position that's - // higher than it's own position - } - - // TODO: Although the below method should be faster, calculating and adding the direction - // vector from scene_pos to new_pos may be easier to understand - // i.e. new_pos = scene_pos + b/sin(30) * direction_vec - - // due to the fixed angle, the centered scene position - // and the new camera position form a right triangle. - // - // c - + new camera pos - // - |b - // center +------+ - // a - // - // we can calculate the new camera position via the offset a - // using the angle and length of side b. - auto y_delta = this->scene_pos[1] - scene_pos[1]; // b (vertical distance) - auto xz_distance = y_delta * std::numbers::sqrt3; // a (horizontal distance); a = b * (cos(30°) / sin(30°)) - - // get x and z offsets - // the camera is pointed diagonally to the negative x and z axis - // a is the length of the diagonal from camera.xz to scene_pos.xz - // so the x and z offest are sides of a square with the same diagonal - auto side_length = xz_distance / std::numbers::sqrt2; - auto new_pos = Eigen::Vector3f( - scene_pos[0] + side_length, - this->scene_pos[1], // height unchanged - scene_pos[2] + side_length); - + auto new_pos = calc_look_at(scene_pos); this->move_to(new_pos); } @@ -108,15 +71,17 @@ void Camera::look_at_coord(coord::scene3 coord_pos) { this->look_at_scene(scene_pos); } -void Camera::move_to(Eigen::Vector3f scene_pos) { - // TODO: Check and set bounds for where the camera can go and check them here +void Camera::move_to(Eigen::Vector3f scene_pos, const CameraBoundaries &camera_boundaries) { + scene_pos[0] = std::clamp(scene_pos[0], camera_boundaries.x_min, camera_boundaries.x_max); + scene_pos[1] = std::clamp(scene_pos[1], camera_boundaries.y_min, camera_boundaries.y_max); + scene_pos[2] = std::clamp(scene_pos[2], camera_boundaries.z_min, camera_boundaries.z_max); this->scene_pos = scene_pos; this->moved = true; } -void Camera::move_rel(Eigen::Vector3f direction, float delta) { - this->move_to(this->scene_pos + (direction * delta)); +void Camera::move_rel(Eigen::Vector3f direction, float delta, const CameraBoundaries &camera_boundaries) { + this->move_to(this->scene_pos + (direction * delta), camera_boundaries); } void Camera::set_zoom(float zoom) { @@ -155,11 +120,11 @@ const Eigen::Matrix4f &Camera::get_view_matrix() { return this->view; } - auto direction = cam_direction.normalized(); + auto direction = CAM_DIRECTION.normalized(); Eigen::Vector3f eye = this->scene_pos; Eigen::Vector3f center = this->scene_pos + direction; // look in the direction of the camera - Eigen::Vector3f up = Eigen::Vector3f(0.0f, 1.0f, 0.0f); + Eigen::Vector3f up = CAM_UP; Eigen::Vector3f f = center - eye; f.normalize(); @@ -203,7 +168,7 @@ const Eigen::Matrix4f &Camera::get_projection_matrix() { float halfheight = this->viewport_size[1] / 2; // get zoom level - float real_zoom = 0.5f * this->default_zoom_ratio * this->zoom; + float real_zoom = this->get_real_zoom_factor(); // zoom by narrowing or widening viewport around focus point. // narrow viewport => zoom in (projected area gets *smaller* while screen size stays the same) @@ -216,10 +181,11 @@ const Eigen::Matrix4f &Camera::get_projection_matrix() { Eigen::Matrix4f mat = Eigen::Matrix4f::Identity(); mat(0, 0) = 2.0f / (right - left); mat(1, 1) = 2.0f / (top - bottom); - mat(2, 2) = -2.0f / (1000.0f - (-1.0f)); // clip near and far planes (TODO: necessary?) + mat(2, 2) = -2.0f / (DEFAULT_FAR_DISTANCE - DEFAULT_NEAR_DISTANCE); // clip near and far planes (TODO: necessary?) mat(0, 3) = -(right + left) / (right - left); mat(1, 3) = -(top + bottom) / (top - bottom); - mat(2, 3) = -(1000.0f + (-1.0f)) / (1000.0f - (-1.0f)); // clip near and far planes (TODO: necessary?) + mat(2, 3) = -(DEFAULT_FAR_DISTANCE + DEFAULT_NEAR_DISTANCE) + / (DEFAULT_FAR_DISTANCE - DEFAULT_NEAR_DISTANCE); // clip near and far planes (TODO: necessary?) // Cache matrix for subsequent calls this->proj = mat; @@ -237,10 +203,10 @@ Eigen::Vector3f Camera::get_input_pos(const coord::input &coord) const { // calculate up (u) and right (s) vectors for camera // these define the camera plane in 3D space that the input // coord exists on - auto direction = cam_direction.normalized(); + auto direction = CAM_DIRECTION.normalized(); Eigen::Vector3f eye = this->scene_pos; Eigen::Vector3f center = this->scene_pos + direction; - Eigen::Vector3f up = Eigen::Vector3f(0.0f, 1.0f, 0.0f); + Eigen::Vector3f up = CAM_UP; Eigen::Vector3f f = center - eye; f.normalize(); @@ -253,7 +219,7 @@ Eigen::Vector3f Camera::get_input_pos(const coord::input &coord) const { // this is the same calculation as for the projection matrix float halfwidth = this->viewport_size[0] / 2; float halfheight = this->viewport_size[1] / 2; - float real_zoom = 0.5f * this->default_zoom_ratio * this->zoom; + float real_zoom = this->get_real_zoom_factor(); // calculate x and y offset on the camera plane relative to the camera position float x = +(2.0f * coord.x / this->viewport_size[0] - 1) * (halfwidth * real_zoom); @@ -269,4 +235,71 @@ const std::shared_ptr &Camera::get_uniform_buffer() con return this->uniform_buffer; } +const Frustum2d Camera::get_frustum_2d() { + return {this->viewport_size, + this->get_view_matrix(), + this->get_projection_matrix(), + this->get_zoom()}; +} + +const Frustum3d Camera::get_frustum_3d() const { + return {this->viewport_size, + DEFAULT_NEAR_DISTANCE, + DEFAULT_FAR_DISTANCE, + this->scene_pos, + CAM_DIRECTION, + CAM_UP, + this->get_real_zoom_factor()}; +} + +void Camera::init_uniform_buffer(const std::shared_ptr &renderer) { + resources::UBOInput view_input{"view", resources::ubo_input_t::M4F32}; + resources::UBOInput proj_input{"proj", resources::ubo_input_t::M4F32}; + resources::UBOInput inv_zoom_input{"inv_zoom", resources::ubo_input_t::F32}; + resources::UBOInput inv_viewport_size{"inv_viewport_size", resources::ubo_input_t::V2F32}; + auto ubo_info = resources::UniformBufferInfo{ + resources::ubo_layout_t::STD140, + {view_input, proj_input, inv_zoom_input, inv_viewport_size}}; + this->uniform_buffer = renderer->add_uniform_buffer(ubo_info); +} + +Eigen::Vector3f Camera::calc_look_at(Eigen::Vector3f target) { + if (target[1] > this->scene_pos[1]) { + // TODO: camera can't look at a position that's + // higher than it's own position + } + + // TODO: Although the below method should be faster, calculating and adding the direction + // vector from scene_pos to new_pos may be easier to understand + // i.e. new_pos = scene_pos + b/sin(30) * direction_vec + + // due to the fixed angle, the centered scene position + // and the new camera position form a right triangle. + // + // c - + new camera pos + // - |b + // center +------+ + // a + // + // we can calculate the new camera position via the offset a + // using the angle and length of side b. + auto y_delta = this->scene_pos[1] - target[1]; // b (vertical distance) + auto xz_distance = y_delta * std::numbers::sqrt3; // a (horizontal distance); a = b * (cos(30°) / sin(30°)) + + // get x and z offsets + // the camera is pointed diagonally to the negative x and z axis + // a is the length of the diagonal from camera.xz to scene_pos.xz + // so the x and z offest are sides of a square with the same diagonal + auto side_length = xz_distance / std::numbers::sqrt2; + return Eigen::Vector3f( + target[0] + side_length, + this->scene_pos[1], // height unchanged + target[2] + side_length); +} + +inline float Camera::get_real_zoom_factor() const { + return 0.5f * this->default_zoom_ratio * this->zoom; +} + + } // namespace openage::renderer::camera diff --git a/libopenage/renderer/camera/camera.h b/libopenage/renderer/camera/camera.h index cb7b1868fb..5e5759c06e 100644 --- a/libopenage/renderer/camera/camera.h +++ b/libopenage/renderer/camera/camera.h @@ -1,10 +1,12 @@ -// Copyright 2022-2023 the openage authors. See copying.md for legal info. +// Copyright 2022-2024 the openage authors. See copying.md for legal info. #pragma once #include #include +#include #include +#include #include @@ -12,122 +14,124 @@ #include "coord/scene.h" #include "util/vector.h" +#include "renderer/camera/boundaries.h" +#include "renderer/camera/definitions.h" +#include "renderer/camera/frustum_2d.h" +#include "renderer/camera/frustum_3d.h" + + namespace openage::renderer { class Renderer; class UniformBuffer; namespace camera { -/** - * Camera direction (= where it looks at). - * Uses a dimetric perspective like in AoE with the (fixed) angles - * yaw = -135 degrees - * pitch = -30 degrees - */ -static const Eigen::Vector3f cam_direction{ - -1 * (std::sqrt(6.f) / 4), - -0.5f, - -1 * (std::sqrt(6.f) / 4), -}; - /** * Camera for selecting what part of the ingame world is displayed. * + * The camera uses orthographic projection as it is primarily used for + * 2D rendering. + * * TODO: Vulkan version. */ class Camera { public: /** - * Create a new camera for the renderer. - * - * The camera uses default values. Its centered on the origin of the scene (0.0f, 0.0f, 0.0f) - * and has a zoom level of 1.0f. - * - * @param viewport_size Initial viewport size of the camera (width x height). - */ + * Create a new camera for the renderer. + * + * The camera uses default values. Its centered on the origin of the scene (0.0f, 0.0f, 0.0f) + * and has a zoom level of 1.0f. + * + * @param renderer openage renderer instance. + * @param viewport_size Initial viewport size of the camera (width x height). + */ Camera(const std::shared_ptr &renderer, util::Vector2s viewport_size); /** - * Create a new camera for the renderer. - * - * @param viewport_size Viewport size of the camera (width x height). - * @param scene_pos Position of the camera in the scene. - * @param zoom Zoom level of the camera (defaults to 1.0f). - * @param max_zoom_out Maximum zoom out level (defaults to 64.0f). - * @param default_zoom_ratio Default zoom level calibration (defaults to (1.0f / 49)). - */ + * Create a new camera for the renderer. + * + * @param renderer openage renderer instance. + * @param viewport_size Viewport size of the camera (width x height). + * @param scene_pos Position of the camera in the scene. + * @param zoom Zoom level of the camera (defaults to 1.0f). + * @param max_zoom_out Maximum zoom out level (defaults to 64.0f). + * @param default_zoom_ratio Default zoom level calibration (defaults to (1.0f / 49)). + */ Camera(const std::shared_ptr &renderer, util::Vector2s viewport_size, Eigen::Vector3f scene_pos, - float zoom = 1.0f, - float max_zoom_out = 64.0f, - float default_zoom_ratio = 1.0f / 49); + float zoom = DEFAULT_ZOOM, + float max_zoom_out = DEFAULT_MAX_ZOOM_OUT, + float default_zoom_ratio = DEFAULT_ZOOM_RATIO); ~Camera() = default; /** - * Move the camera so that the center of its viewpoint points - * to the given position in the 3D scene. - * - * @param scene_pos Position in the 3D scene that the camera should center on. - */ + * Move the camera so that the center of its viewpoint points + * to the given position in the 3D scene. + * + * @param scene_pos Position in the 3D scene that the camera should center on. + */ void look_at_scene(Eigen::Vector3f scene_pos); /** - * Move the camera so that the center of its viewpoint points - * to the given ingame coordinates. - * - * @param scene_pos Position of the ingame coordinates that the camera should center on. - */ + * Move the camera so that the center of its viewpoint points + * to the given ingame coordinates. + * + * @param scene_pos Position of the ingame coordinates that the camera should center on. + */ void look_at_coord(coord::scene3 coord_pos); /** - * Move the camera position in the direction of a given vector. - * - * @param scene_pos New 3D position of the camera in the scene. - */ - void move_to(Eigen::Vector3f scene_pos); - - /** - * Move the camera position in the direction of a given vector. - * - * @param direction Direction vector. Added to the current position. - * @param delta Delta for controlling the amount by which the camera is moved. The - * value is multiplied with the directional vector before its applied to - * the positional vector. - */ - void move_rel(Eigen::Vector3f direction, float delta = 1.0f); - - /** - * Set the zoom level of the camera. Values smaller than 1.0f let the - * camera zoom in, values greater than 1.0f let the camera zoom out. - * - * The max zoom in value is 0.05f. Passing values lower than that - * will just set the zoom level to max zoom. - * - * For incremental zooming, use the \p zoom_in() and \p zoom_out() - * methods. - * - * @param zoom New zoom level. - */ + * Move the camera position in the direction of a given vector. + * + * @param scene_pos New 3D position of the camera in the scene. + * @param camera_boundaries 3D boundaries for the camera. + */ + void move_to(Eigen::Vector3f scene_pos, const CameraBoundaries &camera_boundaries = DEFAULT_CAM_BOUNDARIES); + + /** + * Move the camera position in the direction of a given vector taking the + * camera boundaries into account. + * + * @param direction Direction vector. Added to the current position. + * @param delta Delta for controlling the amount by which the camera is moved. The + * value is multiplied with the directional vector before its applied to + * the positional vector. + * @param camera_boundaries 3D boundaries for the camera. + */ + void move_rel(Eigen::Vector3f direction, float delta = 1.0f, const CameraBoundaries &camera_boundaries = DEFAULT_CAM_BOUNDARIES); + + /** + * Set the zoom level of the camera. Values smaller than 1.0f let the + * camera zoom in, values greater than 1.0f let the camera zoom out. + * + * The max zoom in value is 0.05f. Passing values lower than that + * will just set the zoom level to max zoom. + * + * For incremental zooming, use the \p zoom_in() and \p zoom_out() + * methods. + * + * @param zoom New zoom level. + */ void set_zoom(float zoom); /** - * Zoom into the scene. - * - * Decreases the current zoom level. - * - * @param zoom_delta How far the camera should zoom in. - */ + * Zoom into the scene. + * + * Decreases the current zoom level. + * + * @param zoom_delta How far the camera should zoom in. + */ void zoom_in(float zoom_delta); /** - * Zoom out of the scene. - * - * Increases the current zoom level. - * - * @param zoom_delta How far the camera should zoom out. - */ + * Zoom out of the scene. + * + * Increases the current zoom level. + * + * @param zoom_delta How far the camera should zoom out. + */ void zoom_out(float zoom_delta); /** @@ -139,138 +143,184 @@ class Camera { void resize(size_t width, size_t height); /** - * Get the current zoom level of the camera. - * - * @return Zoom level. - */ + * Get the current zoom level of the camera. + * + * Determines the scale of rendered objects. + * + * @return Zoom level. + */ float get_zoom() const; /** - * Get the view matrix for this camera. - * - * @return Camera view matrix. - */ + * Get the view matrix for this camera. + * + * @return Camera view matrix. + */ const Eigen::Matrix4f &get_view_matrix(); /** - * Get the projection matrix for this camera. - * - * @return Camera projection matrix. - */ + * Get the projection matrix for this camera. + * + * @return Camera projection matrix. + */ const Eigen::Matrix4f &get_projection_matrix(); /** - * Get the size of the camera viewport. - * - * @return Viewport size as a 2D vector (x, y). - */ + * Get the size of the camera viewport. + * + * @return Viewport size as a 2D vector (x, y). + */ const util::Vector2s &get_viewport_size() const; /** - * Get the corresponding 3D position of a 2D input coordinate in the viewport. - * The position is on the plane created by the camera's orthographic projection. - * - * This may be used to get the 3D position of a mouse click and subsequent - * ray casting calculations. - * - * @param coord 2D input coordinate in the viewport. - * - * @return Position of the input in the 3D scene. - */ + * Get the corresponding 3D position of a 2D input coordinate in the viewport. + * The position is on the plane created by the camera's orthographic projection. + * + * This may be used to get the 3D position of a mouse click and subsequent + * ray casting calculations. + * + * @param coord 2D input coordinate in the viewport. + * + * @return Position of the input in the 3D scene. + */ Eigen::Vector3f get_input_pos(const coord::input &coord) const; /** - * Get the uniform buffer for this camera. - * - * @return Uniform buffer. - */ + * Get the uniform buffer for this camera. + * + * @return Uniform buffer. + */ const std::shared_ptr &get_uniform_buffer() const; + /** + * Get a 2D frustum object for this camera. + * + * @return Frustum object. + */ + const Frustum2d get_frustum_2d(); + + /** + * Get a 3D frustum object for this camera. + * + * @return Frustum object. + */ + const Frustum3d get_frustum_3d() const; + + private: /** - * Position in the 3D scene. - */ + * Create the uniform buffer for the camera. + * + * @param renderer openage renderer instance. + */ + void init_uniform_buffer(const std::shared_ptr &renderer); + + /** + * Calculates the camera's position needed to center its view on the given target. + * + * @param target The target position in the 3D scene the camera should focus on. + */ + Eigen::Vector3f calc_look_at(Eigen::Vector3f target); + + /** + * Get the zoom factor applied to the camera projection. + * + * The zoom factor is calculated as + * + * zoom * zoom_ratio * 0.5f + * + * Note that this zoom factor should NOT be used for sprite scaling, but + * only for 3D projection matrix calculations. For sprite scaling, use + * \p get_zoom() . + * + * @return Zoom factor for projection. + */ + inline float get_real_zoom_factor() const; + + /** + * Position in the 3D scene. + */ Eigen::Vector3f scene_pos; /** - * Size of the camera viewport. - */ + * Size of the camera viewport. + */ util::Vector2s viewport_size; /** - * Zoom level. - * - * 0.0f < z < 1.0f => zoom in - * z = 1.0f => default view - * z > 1.0f => zoom out - */ + * Zoom level. + * + * 0.0f < z < 1.0f => zoom in + * z = 1.0f => default view + * z > 1.0f => zoom out + */ float zoom; /** - * Maximum possible zoom in level. - * - * Has to be above 0.0f, otherwise we get zero division errors. - */ + * Maximum possible zoom in level. + * + * Has to be above 0.0f, otherwise we get zero division errors. + */ static constexpr float MAX_ZOOM_IN = 0.005f; /** - * Maximum possible zoom out level. - * - * This can be set per camera. - */ + * Maximum possible zoom out level. + * + * This can be set per camera. + */ float max_zoom_out; /** - * Modifier that controls what the default zoom level (zoom = 1.0f) - * looks like. Essentially, this value is also a zoom that is pre-applied - * before other calculations. - * - * This value is important for calibrating the default zoom to match - * the pixel size of assets. For example, terrain in AoE2 has a height - * of 49 pixels, while in the renderer scene the height is always 1.0. - * Setting \p default_zoom_ratio to 1 / 49 ensure that the default zoom - * level matches the pixel ration of the original game. - */ + * Modifier that controls what the default zoom level (zoom = 1.0f) + * looks like. Essentially, this value is also a zoom that is pre-applied + * before other calculations. + * + * This value is important for calibrating the default zoom to match + * the pixel size of assets. For example, terrain in AoE2 has a height + * of 49 pixels, while in the renderer scene the height is always 1.0. + * Setting \p default_zoom_ratio to 1 / 49 ensure that the default zoom + * level matches the pixel ration of the original game. + */ float default_zoom_ratio; /** - * Flag set when the camera is moved. - * - * If true, the view matrix needs to be recalculated. - */ + * Flag set when the camera is moved. + * + * If true, the view matrix needs to be recalculated. + */ bool moved; /** - * Flag set when the camera zoom is changed. - * - * If true, the projection matrix needs to be recalculated. - */ + * Flag set when the camera zoom is changed. + * + * If true, the projection matrix needs to be recalculated. + */ bool zoom_changed; /** - * Flag set when the camera viewport is resized. - * - * If true, the projection matrix needs to be recalculated. - */ + * Flag set when the camera viewport is resized. + * + * If true, the projection matrix needs to be recalculated. + */ bool viewport_changed; /** - * Current view matrix for the camera. - * - * Cached because it may be requested many times. - */ + * Current view matrix for the camera. + * + * Cached because it may be requested many times. + */ Eigen::Matrix4f view; /** - * Current projection matrix for the camera. - * - * Cached because it may be requested many times. - */ + * Current projection matrix for the camera. + * + * Cached because it may be requested many times. + */ Eigen::Matrix4f proj; /** - * Uniform buffer for the camera matrices. - */ + * Uniform buffer for the camera matrices. + */ std::shared_ptr uniform_buffer; }; diff --git a/libopenage/renderer/camera/definitions.cpp b/libopenage/renderer/camera/definitions.cpp new file mode 100644 index 0000000000..9011323855 --- /dev/null +++ b/libopenage/renderer/camera/definitions.cpp @@ -0,0 +1,9 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "definitions.h" + +namespace openage::renderer::camera { + +// this file is intentionally empty + +} // namespace openage::renderer::camera diff --git a/libopenage/renderer/camera/definitions.h b/libopenage/renderer/camera/definitions.h new file mode 100644 index 0000000000..7c3bbdd5d3 --- /dev/null +++ b/libopenage/renderer/camera/definitions.h @@ -0,0 +1,82 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + +#include "renderer/camera/boundaries.h" + +namespace openage::renderer::camera { + +/** + * Camera direction (= where it looks at). + * Uses a dimetric perspective like in AoE with the (fixed) angles + * yaw = -135 degrees + * pitch = -30 degrees + */ +static const Eigen::Vector3f CAM_DIRECTION{ + -1 * (std::sqrt(6.f) / 4), + -0.5f, + -1 * (std::sqrt(6.f) / 4), +}; + +/** + * Camera up vector. + */ +static const Eigen::Vector3f CAM_UP{0.0f, 1.0f, 0.0f}; + +/** + * Default near distance of the camera. + * + * Determines how close objects can be to the camera before they are not rendered anymore. + */ +static constexpr float DEFAULT_NEAR_DISTANCE = 0.01f; + +/** + * Default far distance of the camera. + * + * Determines how far objects can be from the camera before they are not rendered anymore. + */ +static constexpr float DEFAULT_FAR_DISTANCE = 100.0f; + +/** + * Default zoom level of the camera. + */ +static constexpr float DEFAULT_ZOOM = 1.0f; + +/** + * Maximum zoom out level of the camera. + */ +static constexpr float DEFAULT_MAX_ZOOM_OUT = 64.0f; + +/** + * Default zoom ratio. + * + * This adjusts the zoom level of the camera to the size of sprites in the game, + * so that 1 pixel in a sprite == 1 pixel on the screen. + * + * 1.0f / 49 is the default value for the AoE2 sprites. + */ +static constexpr float DEFAULT_ZOOM_RATIO = 1.0f / 49; + +static constexpr CameraBoundaries DEFAULT_CAM_BOUNDARIES{ + std::numeric_limits::lowest(), + std::numeric_limits::max(), + std::numeric_limits::lowest(), + std::numeric_limits::max(), + std::numeric_limits::lowest(), + std::numeric_limits::max()}; + +/** + * Constant values for the camera bounds (based on current fix terrain grid of 20x20). + * TODO: Make boundaries dynamic based on map size. + */ +static constexpr float X_BOUND_MIN = 12.25f; +static constexpr float X_BOUND_MAX = 32.25f; +static constexpr float Y_BOUND_MIN = 0.0f; +static constexpr float Y_BOUND_MAX = 20.0f; +static constexpr float Z_BOUND_MIN = -8.25f; +static constexpr float Z_BOUND_MAX = 12.25f; + +} // namespace openage::renderer::camera diff --git a/libopenage/renderer/camera/frustum_2d.cpp b/libopenage/renderer/camera/frustum_2d.cpp new file mode 100644 index 0000000000..4729c2ac3e --- /dev/null +++ b/libopenage/renderer/camera/frustum_2d.cpp @@ -0,0 +1,60 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "frustum_2d.h" + + +namespace openage::renderer::camera { + +Frustum2d::Frustum2d(const util::Vector2s &viewport_size, + const Eigen::Matrix4f &view_matrix, + const Eigen::Matrix4f &projection_matrix, + const float zoom) { + this->update(viewport_size, view_matrix, projection_matrix, zoom); +} + +void Frustum2d::update(const util::Vector2s &viewport_size, + const Eigen::Matrix4f &view_matrix, + const Eigen::Matrix4f &projection_matrix, + const float zoom) { + this->pixel_size_ndc = {2.0f / viewport_size[0], 2.0f / viewport_size[1]}; + this->inv_zoom_factor = 1.0f / zoom; + + // calculate the transformation matrix + this->transform_matrix = projection_matrix * view_matrix; +} + +bool Frustum2d::in_frustum(const Eigen::Vector3f &scene_pos, + const Eigen::Matrix4f &model_matrix, + const float scalefactor, + const util::Vector4i &boundaries) const { + // calculate the position of the scene object in screen space + Eigen::Vector4f clip_pos = this->transform_matrix * model_matrix * scene_pos.homogeneous(); + float x_ndc = clip_pos[0]; + float y_ndc = clip_pos[1]; + + float zoom_scale = scalefactor * this->inv_zoom_factor; + + // Scale the boundaries by the zoom factor and the pixel size + float left_bound = boundaries[0] * zoom_scale * this->pixel_size_ndc[0]; + float right_bound = boundaries[1] * zoom_scale * this->pixel_size_ndc[0]; + float top_bound = boundaries[2] * zoom_scale * this->pixel_size_ndc[1]; + float bottom_bound = boundaries[3] * zoom_scale * this->pixel_size_ndc[1]; + + // check if the object boundaries are inside the frustum + if (x_ndc - left_bound >= 1.0f) { + return false; + } + if (x_ndc + right_bound <= -1.0f) { + return false; + } + if (y_ndc + top_bound <= -1.0f) { + return false; + } + if (y_ndc - bottom_bound >= 1.0f) { + return false; + } + + return true; +} + +} // namespace openage::renderer::camera diff --git a/libopenage/renderer/camera/frustum_2d.h b/libopenage/renderer/camera/frustum_2d.h new file mode 100644 index 0000000000..b479fcbc18 --- /dev/null +++ b/libopenage/renderer/camera/frustum_2d.h @@ -0,0 +1,83 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include "util/vector.h" + +namespace openage::renderer::camera { + +/** + * Frustum for culling objects outside of a camera view in 2D screen space. + * This frustum object should be used for sprite culling as sprites do not exist in 3D world space. + * They are directly projected and reszied to the screen space. + * + * Used for frustum culling (https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling) + * in the renderer. + */ +class Frustum2d { +public: + /** + * Create a new 2D frustum. + * + * @param viewport_size Size of the camera viewport (width x height). + * @param view_matrix View matrix of the camera. + * @param projection_matrix Projection matrix of the camera. + * @param zoom Zoom of the camera. + */ + Frustum2d(const util::Vector2s &viewport_size, + const Eigen::Matrix4f &view_matrix, + const Eigen::Matrix4f &projection_matrix, + const float zoom); + + /** + * Update the frustum with new camera parameters. + * + * @param viewport_size Size of the camera viewport (width x height). + * @param view_matrix View matrix of the camera. + * @param projection_matrix Projection matrix of the camera. + * @param zoom Zoom of the camera. + */ + void update(const util::Vector2s &viewport_size, + const Eigen::Matrix4f &view_matrix, + const Eigen::Matrix4f &projection_matrix, + const float zoom); + + /** + * Check if a scene object is inside the frustum. + * + * @param scene_pos 3D scene coordinates. + * @param model_matrix Model matrix of the object. + * @param scalefactor Scale factor of the animation. + * @param boundaries Boundaries of the animation (in pixels): left, right, top, bottom. + * + * @return true if the object is inside the frustum, false otherwise. + */ + bool in_frustum(const Eigen::Vector3f &scene_pos, + const Eigen::Matrix4f &model_matrix, + const float scalefactor, + const util::Vector4i &boundaries) const; + +private: + /** + * Camera transformation matrix. + * + * Pre-calculated from view and projection matrix. + */ + Eigen::Matrix4f transform_matrix; + + /** + * Size of a pixel (width x height) in clip space. + * + * Uses normalized device coordinates (NDC) for the pixel size. + */ + Eigen::Vector2f pixel_size_ndc; + + /** + * Zoom factor of the camera. + */ + float inv_zoom_factor; +}; + +} // namespace openage::renderer::camera diff --git a/libopenage/renderer/camera/frustum_3d.cpp b/libopenage/renderer/camera/frustum_3d.cpp new file mode 100644 index 0000000000..0f4b9bb340 --- /dev/null +++ b/libopenage/renderer/camera/frustum_3d.cpp @@ -0,0 +1,132 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "frustum_3d.h" + + +namespace openage::renderer::camera { + +Frustum3d::Frustum3d(const util::Vector2s &viewport_size, + const float near_distance, + const float far_distance, + const Eigen::Vector3f &camera_pos, + const Eigen::Vector3f &cam_direction, + const Eigen::Vector3f &up_direction, + const float zoom_factor) { + this->update(viewport_size, + near_distance, + far_distance, + camera_pos, + cam_direction, + up_direction, + zoom_factor); +} + +void Frustum3d::update(const util::Vector2s &viewport_size, + const float near_distance, + const float far_distance, + const Eigen::Vector3f &camera_pos, + const Eigen::Vector3f &cam_direction, + const Eigen::Vector3f &up_direction, + const float zoom_factor) { + // offsets are adjusted by zoom + // this is the same calculation as for the projection matrix + float halfscreenwidth = viewport_size[0] / 2; + float halfscreenheight = viewport_size[1] / 2; + + float halfwidth = halfscreenwidth * zoom_factor; + float halfheight = halfscreenheight * zoom_factor; + + Eigen::Vector3f direction = cam_direction.normalized(); + Eigen::Vector3f eye = camera_pos; + Eigen::Vector3f center = camera_pos + direction; + + // calculate up (u) and right (s) vectors for camera + // these define the camera plane in 3D space that the input + Eigen::Vector3f f = center - eye; + f.normalize(); + Eigen::Vector3f s = f.cross(up_direction); + s.normalize(); + Eigen::Vector3f u = s.cross(f); + u.normalize(); + + // calculate distance of the camera position to the near and far plane + Eigen::Vector3f near_position = camera_pos + direction * near_distance; + Eigen::Vector3f far_position = camera_pos + direction * far_distance; + + // The frustum is a cuboid box with 8 points defining it (4 on the near plane, 4 on the far plane) + Eigen::Vector3f near_top_left = near_position - s * halfwidth + u * halfheight; + Eigen::Vector3f near_top_right = near_position + s * halfwidth + u * halfheight; + Eigen::Vector3f near_bottom_left = near_position - s * halfwidth - u * halfheight; + Eigen::Vector3f near_bottom_right = near_position + s * halfwidth - u * halfheight; + + Eigen::Vector3f far_top_left = far_position - s * halfwidth + u * halfheight; + // Eigen::Vector3f far_top_right = far_position + s * halfwidth + u * halfheight; // unused + Eigen::Vector3f far_bottom_left = far_position - s * halfwidth - u * halfheight; + Eigen::Vector3f far_bottom_right = far_position + s * halfwidth - u * halfheight; + + // The near and far planes are easiest to compute, as they should be in the direction of the camera + this->near_face_normal = cam_direction.normalized(); + this->far_face_normal = -1.0f * cam_direction.normalized(); + + // The distance of the plane from the origin is the dot product of the normal and a point on the plane + this->near_face_distance = this->near_face_normal.dot(near_position); + this->far_face_distance = this->far_face_normal.dot(far_position); + + // Each left, right, top, and bottom plane are defined by three points on the plane + this->left_face_normal = (near_bottom_left - near_top_left).cross(far_bottom_left - near_bottom_left); + this->right_face_normal = (far_bottom_right - near_bottom_right).cross(near_bottom_right - near_top_right); + this->top_face_normal = (near_top_right - near_top_left).cross(near_top_left - far_top_left); + this->bottom_face_normal = (near_bottom_left - far_bottom_left).cross(near_bottom_right - near_bottom_left); + + // for orthographic projection, the normal of left/right should equal -s/s + // and the normal of top/bottom should equal u/-u + this->left_face_normal.normalize(); + this->right_face_normal.normalize(); + this->top_face_normal.normalize(); + this->bottom_face_normal.normalize(); + + // calculate the distance of the planes to the origin + this->left_face_distance = this->left_face_normal.dot(near_bottom_left); + this->right_face_distance = this->right_face_normal.dot(far_bottom_right); + this->top_face_distance = this->top_face_normal.dot(near_top_right); + this->bottom_face_distance = this->bottom_face_normal.dot(near_bottom_left); +} + +bool Frustum3d::in_frustum(const Eigen::Vector3f &pos) const { + // For each plane, if a point is behind one of the frustum planes, it is not within the frustum + float distance; + + distance = this->top_face_normal.dot(pos) - this->top_face_distance; + if (distance < 0) { + return false; + } + + distance = this->bottom_face_normal.dot(pos) - this->bottom_face_distance; + if (distance < 0) { + return false; + } + + distance = this->left_face_normal.dot(pos) - this->left_face_distance; + if (distance < 0) { + return false; + } + + distance = this->right_face_normal.dot(pos) - this->right_face_distance; + if (distance < 0) { + return false; + } + + distance = this->far_face_normal.dot(pos) - this->far_face_distance; + if (distance < 0) { + return false; + } + + distance = this->bottom_face_normal.dot(pos) - this->bottom_face_distance; + if (distance < 0) { + return false; + } + + return true; +} + +} // namespace openage::renderer::camera diff --git a/libopenage/renderer/camera/frustum_3d.h b/libopenage/renderer/camera/frustum_3d.h new file mode 100644 index 0000000000..789b7f1064 --- /dev/null +++ b/libopenage/renderer/camera/frustum_3d.h @@ -0,0 +1,133 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include "util/vector.h" + + +namespace openage::renderer::camera { + +/** + * Frustum for culling objects outside of a camera viewcone in 3D space. The frustum + * is defined by 6 planes (top, bottom, right, left, far, near) that define the boundaries + * of the viewing volume of the camera. + * + * Used for frustum culling (https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling) + * in the renderer. + * + * As the openage camera uses orthographic projection, the frustum is a box, i.e. plane opposite + * to each other are parallel. + */ +class Frustum3d { +public: + /** + * Create a new frustum. + * + * @param viewport_size Size of the viewport (width x height). + * @param near_distance Near distance of the frustum. + * @param far_distance Far distance of the frustum. + * @param camera_pos Scene position of the camera. + * @param cam_direction Direction the camera is looking at. + * @param up_direction Up direction of the camera. + * @param zoom_factor Zoom factor of the camera. + */ + Frustum3d(const util::Vector2s &viewport_size, + const float near_distance, + const float far_distance, + const Eigen::Vector3f &camera_pos, + const Eigen::Vector3f &cam_direction, + const Eigen::Vector3f &up_direction, + const float zoom_factor); + + /** + * Update this frustum with the new camera parameters. + * + * @param viewport_size Size of the viewport (width x height). + * @param near_distance Near distance of the frustum. + * @param far_distance Far distance of the frustum. + * @param camera_pos Scene position of the camera. + * @param cam_direction Direction the camera is looking at. + * @param up_direction Up direction of the camera. + * @param zoom_factor Zoom factor of the camera. + */ + void update(const util::Vector2s &viewport_size, + const float near_distance, + const float far_distance, + const Eigen::Vector3f &camera_pos, + const Eigen::Vector3f &cam_direction, + const Eigen::Vector3f &up_direction, + const float zoom_factor); + + /** + * Check whether a point in the scene is inside the frustum. + * + * @param scene_pos 3D scene coordinates. + * + * @return true if the point is inside the frustum, else false. + */ + bool in_frustum(const Eigen::Vector3f &scene_pos) const; + +private: + /** + * Normal vector of the top face. + */ + Eigen::Vector3f top_face_normal; + + /** + * Normal vector of the bottom face. + */ + Eigen::Vector3f bottom_face_normal; + + /** + * Normal vector of the right face. + */ + Eigen::Vector3f right_face_normal; + + /** + * Normal vector of the left face. + */ + Eigen::Vector3f left_face_normal; + + /** + * Normal vector of the far face. + */ + Eigen::Vector3f far_face_normal; + + /** + * Normal vector of the near face. + */ + Eigen::Vector3f near_face_normal; + + /** + * Shortest distance from the top face to the scene origin. + */ + float top_face_distance; + + /** + * Shortest distance from the bottom face to the scene origin. + */ + float bottom_face_distance; + + /** + * Shortest distance from the right face to the scene origin. + */ + float right_face_distance; + + /** + * Shortest distance from the left face to the scene origin. + */ + float left_face_distance; + + /** + * Shortest distance from the far face to the scene origin. + */ + float far_face_distance; + + /** + * Shortest distance from the near face to the scene origin. + */ + float near_face_distance; +}; +} // namespace openage::renderer::camera diff --git a/libopenage/renderer/color.cpp b/libopenage/renderer/color.cpp index 7333dfb073..f03f883a0a 100644 --- a/libopenage/renderer/color.cpp +++ b/libopenage/renderer/color.cpp @@ -1,12 +1,11 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #include "color.h" namespace openage { namespace renderer { -Color::Color() - : +Color::Color() : r{0}, g{0}, b{0}, @@ -14,8 +13,7 @@ Color::Color() // Empty } -Color::Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a) - : +Color::Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a) : r{r}, g{g}, b{b}, @@ -32,6 +30,7 @@ bool Color::operator!=(const Color &other) const { } Color Colors::WHITE = {255, 255, 255, 255}; -Color Colors::BLACK = { 0, 0, 0, 255}; +Color Colors::BLACK = {0, 0, 0, 255}; -}} // openage::renderer +} // namespace renderer +} // namespace openage diff --git a/libopenage/renderer/color.h b/libopenage/renderer/color.h index 96b13f81f7..a7d445a56a 100644 --- a/libopenage/renderer/color.h +++ b/libopenage/renderer/color.h @@ -1,4 +1,4 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once @@ -9,7 +9,6 @@ namespace renderer { class Color { public: - Color(); Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a); @@ -22,15 +21,13 @@ class Color { uint8_t g; uint8_t b; uint8_t a; - }; class Colors { public: - static Color WHITE; static Color BLACK; - }; -}} // openage::renderer +} // namespace renderer +} // namespace openage diff --git a/libopenage/renderer/definitions.h b/libopenage/renderer/definitions.h index 594f817d1a..49e07c30c1 100644 --- a/libopenage/renderer/definitions.h +++ b/libopenage/renderer/definitions.h @@ -1,9 +1,12 @@ -// 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 "coord/scene.h" + /** * Hardcoded definitions for parameters used in the renderer. * @@ -16,4 +19,9 @@ namespace openage::renderer { */ constexpr coord::scene3 SCENE_ORIGIN = coord::scene3{0, 0, 0}; +/** + * Maximum priority value for a render pass layer. + */ +static constexpr int64_t LAYER_PRIORITY_MAX = std::numeric_limits::max(); + } // namespace openage::renderer diff --git a/libopenage/renderer/demo/CMakeLists.txt b/libopenage/renderer/demo/CMakeLists.txt index 24a5c20d84..7567731afd 100644 --- a/libopenage/renderer/demo/CMakeLists.txt +++ b/libopenage/renderer/demo/CMakeLists.txt @@ -5,7 +5,10 @@ add_sources(libopenage demo_3.cpp demo_4.cpp demo_5.cpp + demo_6.cpp + demo_7.cpp stresstest_0.cpp + stresstest_1.cpp tests.cpp util.cpp ) diff --git a/libopenage/renderer/demo/demo_0.cpp b/libopenage/renderer/demo/demo_0.cpp index 1db9061318..d027c2d1e0 100644 --- a/libopenage/renderer/demo/demo_0.cpp +++ b/libopenage/renderer/demo/demo_0.cpp @@ -1,19 +1,27 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #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" +#include "renderer/render_target.h" #include "renderer/resources/mesh_data.h" #include "renderer/resources/shader_source.h" #include "renderer/shader_program.h" + namespace openage::renderer::tests { void renderer_demo_0(const util::Path &path) { auto qtapp = std::make_shared(); - opengl::GlWindow window("openage renderer test", 800, 600, true); + window_settings settings; + settings.width = 800; + settings.height = 600; + settings.debug = true; + opengl::GlWindow window("openage renderer test", settings); auto renderer = window.make_renderer(); auto shaderdir = path / "assets" / "test" / "shaders"; @@ -43,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_0.h b/libopenage/renderer/demo/demo_0.h index 4cdb1d831b..7ea7e68f19 100644 --- a/libopenage/renderer/demo/demo_0.h +++ b/libopenage/renderer/demo/demo_0.h @@ -1,4 +1,4 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/renderer/demo/demo_1.cpp b/libopenage/renderer/demo/demo_1.cpp index da6bad36d0..c28b7dc296 100644 --- a/libopenage/renderer/demo/demo_1.cpp +++ b/libopenage/renderer/demo/demo_1.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #include "demo_1.h" @@ -6,20 +6,28 @@ #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" +#include "renderer/render_target.h" #include "renderer/resources/shader_source.h" #include "renderer/resources/texture_data.h" #include "renderer/shader_program.h" #include "renderer/texture.h" #include "util/math_constants.h" + namespace openage::renderer::tests { void renderer_demo_1(const util::Path &path) { auto qtapp = std::make_shared(); - opengl::GlWindow window("openage renderer test", 800, 600, true); + window_settings settings; + settings.width = 800; + settings.height = 600; + settings.debug = true; + opengl::GlWindow window("openage renderer test", settings); auto renderer = window.make_renderer(); auto shaderdir = path / "assets" / "test" / "shaders"; @@ -165,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; @@ -181,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_1.h b/libopenage/renderer/demo/demo_1.h index 6b1c3168ee..d978603040 100644 --- a/libopenage/renderer/demo/demo_1.h +++ b/libopenage/renderer/demo/demo_1.h @@ -1,4 +1,4 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/renderer/demo/demo_2.cpp b/libopenage/renderer/demo/demo_2.cpp index 931371c0c6..c2a223d51b 100644 --- a/libopenage/renderer/demo/demo_2.cpp +++ b/libopenage/renderer/demo/demo_2.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #include "demo_2.h" @@ -6,8 +6,11 @@ #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" +#include "renderer/render_target.h" #include "renderer/resources/animation/angle_info.h" #include "renderer/resources/animation/frame_info.h" #include "renderer/resources/parser/parse_sprite.h" @@ -17,12 +20,17 @@ #include "renderer/shader_program.h" #include "renderer/texture.h" + namespace openage::renderer::tests { void renderer_demo_2(const util::Path &path) { auto qtapp = std::make_shared(); - opengl::GlWindow window("openage renderer test", 800, 600, true); + window_settings settings; + settings.width = 800; + settings.height = 600; + settings.debug = true; + opengl::GlWindow window("openage renderer test", settings); auto renderer = window.make_renderer(); /* Load texture file standalone. */ @@ -150,10 +158,10 @@ void renderer_demo_2(const util::Path &path) { 1.0f)); /* Pass uniforms to the shaders. - mv : The upscaling matrix - offset_tile : Subtexture coordinates (as floats relative to texture image size) - u_id : Identifier - tex : OpenGL texture reference + mv : The upscaling matrix + offset_tile : Subtexture coordinates (as floats relative to texture image size) + u_id : Identifier + tex : OpenGL texture reference */ auto obj1_unifs = obj_shader->new_uniform_input( "mv", @@ -217,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; @@ -236,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_2.h b/libopenage/renderer/demo/demo_2.h index 077f61ac33..211e5e9310 100644 --- a/libopenage/renderer/demo/demo_2.h +++ b/libopenage/renderer/demo/demo_2.h @@ -1,4 +1,4 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/renderer/demo/demo_3.cpp b/libopenage/renderer/demo/demo_3.cpp index 6e0dc1f5ca..7de6372303 100644 --- a/libopenage/renderer/demo/demo_3.cpp +++ b/libopenage/renderer/demo/demo_3.cpp @@ -1,32 +1,40 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #include "demo_3.h" #include #include +#include "coord/tile.h" #include "renderer/camera/camera.h" #include "renderer/gui/integration/public/gui_application_with_logger.h" #include "renderer/opengl/window.h" #include "renderer/render_factory.h" +#include "renderer/render_pass.h" +#include "renderer/render_target.h" #include "renderer/resources/assets/asset_manager.h" #include "renderer/resources/shader_source.h" #include "renderer/stages/camera/manager.h" -#include "renderer/stages/screen/screen_renderer.h" -#include "renderer/stages/skybox/skybox_renderer.h" -#include "renderer/stages/terrain/terrain_render_entity.h" -#include "renderer/stages/terrain/terrain_renderer.h" -#include "renderer/stages/world/world_render_entity.h" -#include "renderer/stages/world/world_renderer.h" +#include "renderer/stages/screen/render_stage.h" +#include "renderer/stages/skybox/render_stage.h" +#include "renderer/stages/terrain/render_entity.h" +#include "renderer/stages/terrain/render_stage.h" +#include "renderer/stages/world/render_entity.h" +#include "renderer/stages/world/render_stage.h" #include "renderer/uniform_buffer.h" #include "time/clock.h" + namespace openage::renderer::tests { void renderer_demo_3(const util::Path &path) { auto qtapp = std::make_shared(); - auto window = std::make_shared("openage renderer test", 800, 600, true); + window_settings settings; + settings.width = 800; + settings.height = 600; + settings.debug = true; + auto window = std::make_shared("openage renderer test", settings); auto renderer = window->make_renderer(); // Clock required by world renderer for timing animation frames @@ -44,6 +52,18 @@ void renderer_demo_3(const util::Path &path) { // it is updated each frame before the render stages auto cam_manager = std::make_shared(camera); + // Set boundaries for camera movement in the scene + // this restricts camera movement to the area defined by the boundaries + // i.e. the map terrain in this case + cam_manager->set_camera_boundaries( + camera::CameraBoundaries{ + 12.25f, + 22.25f, + 0.0f, + 20.0f, + 2.25f, + 12.25f}); + // Render stages // every stage use a different subrenderer that manages renderables, // shaders, textures & more. @@ -56,14 +76,14 @@ void renderer_demo_3(const util::Path &path) { path["assets"]["test"]); // Renders the background - auto skybox_renderer = std::make_shared( + auto skybox_renderer = std::make_shared( window, renderer, path["assets"]["shaders"]); skybox_renderer->set_color(1.0f, 0.5f, 0.0f, 1.0f); // orange color // Renders the terrain in 3D - auto terrain_renderer = std::make_shared( + auto terrain_renderer = std::make_shared( window, renderer, camera, @@ -72,7 +92,7 @@ void renderer_demo_3(const util::Path &path) { clock); // Renders units/buildings/other objects - auto world_renderer = std::make_shared( + auto world_renderer = std::make_shared( window, renderer, camera, @@ -90,7 +110,7 @@ void renderer_demo_3(const util::Path &path) { // Final output on screen has its own subrenderer // It takes the outputs of all previous render passes // and blends them together - auto screen_renderer = std::make_shared( + auto screen_renderer = std::make_shared( window, renderer, path["assets"]["shaders"]); @@ -113,37 +133,36 @@ void renderer_demo_3(const util::Path &path) { // Create some entities to populate the scene auto render_factory = std::make_shared(terrain_renderer, world_renderer); - // Terrain - auto terrain0 = render_factory->add_terrain_render_entity(); - // Fill a 10x10 terrain grid with height values auto terrain_size = util::Vector2s{10, 10}; - std::vector height_map{}; - height_map.reserve(terrain_size[0] * terrain_size[1]); + std::vector> tiles{}; + tiles.reserve(terrain_size[0] * terrain_size[1]); for (size_t i = 0; i < terrain_size[0] * terrain_size[1]; ++i) { - height_map.push_back(0.0f); + tiles.emplace_back(0.0f, "./textures/test_terrain.terrain"); } + // Create entity for terrain rendering + auto terrain0 = render_factory->add_terrain_render_entity(terrain_size, + coord::tile_delta{0, 0}); + // Create "test bumps" in the terrain to check if rendering works - height_map[11] = 1.0f; - height_map[23] = 2.3f; - height_map[42] = 4.2f; - height_map[69] = 6.9f; // nice + tiles[11].first = 1.0f; + tiles[23].first = 2.3f; + tiles[42].first = 4.2f; + tiles[69].first = 6.9f; // nice // A hill - height_map[55] = 3.0f; // center - height_map[45] = 2.0f; // bottom left slope - height_map[35] = 1.0f; - height_map[56] = 1.0f; // bottom right slope (little steeper) - height_map[65] = 2.0f; // top right slope - height_map[75] = 1.0f; - height_map[54] = 2.0f; // top left slope - height_map[53] = 1.0f; + tiles[55].first = 3.0f; // center + tiles[45].first = 2.0f; // bottom left slope + tiles[35].first = 1.0f; + tiles[56].first = 1.0f; // bottom right slope (little steeper) + tiles[65].first = 2.0f; // top right slope + tiles[75].first = 1.0f; + tiles[54].first = 2.0f; // top left slope + tiles[53].first = 1.0f; // send the terrain data to the terrain renderer - terrain0->update(terrain_size, - height_map, - "./textures/test_terrain.terrain"); + terrain0->update(terrain_size, tiles); // World entities auto world0 = render_factory->add_world_render_entity(); diff --git a/libopenage/renderer/demo/demo_3.h b/libopenage/renderer/demo/demo_3.h index fda210acbd..9eec5dc74c 100644 --- a/libopenage/renderer/demo/demo_3.h +++ b/libopenage/renderer/demo/demo_3.h @@ -1,4 +1,4 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #pragma once @@ -10,7 +10,6 @@ namespace openage::renderer::tests { * Show off the render stages in the level 2 renderer and the camera * system. * - Window creation - * - Loading shaders * - Creating a camera * - Initializing the level 2 render stages: skybox, terrain, world, screen * - Adding renderables to the render stages via the render factory diff --git a/libopenage/renderer/demo/demo_4.cpp b/libopenage/renderer/demo/demo_4.cpp index 82250cd1dc..7f4704a38a 100644 --- a/libopenage/renderer/demo/demo_4.cpp +++ b/libopenage/renderer/demo/demo_4.cpp @@ -1,12 +1,15 @@ -// 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 "demo_4.h" #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" +#include "renderer/render_target.h" #include "renderer/resources/animation/angle_info.h" #include "renderer/resources/animation/frame_info.h" #include "renderer/resources/frame_timing.h" @@ -16,11 +19,16 @@ #include "renderer/shader_program.h" #include "time/clock.h" + namespace openage::renderer::tests { void renderer_demo_4(const util::Path &path) { auto qtapp = std::make_shared(); - opengl::GlWindow window("openage renderer test", 800, 600, true); + window_settings settings; + settings.width = 800; + settings.height = 600; + settings.debug = true; + opengl::GlWindow window("openage renderer test", settings); auto renderer = window.make_renderer(); /* Clock for timed display */ @@ -97,10 +105,10 @@ void renderer_demo_4(const util::Path &path) { 1.0f)); /* Pass uniforms to the shaders. - mv : The upscaling matrix - offset_tile : Subtexture coordinates (as floats relative to texture image size) - u_id : Identifier - tex : OpenGL texture reference + mv : The upscaling matrix + offset_tile : Subtexture coordinates (as floats relative to texture image size) + u_id : Identifier + tex : OpenGL texture reference */ auto obj1_unifs = obj_shader->new_uniform_input( "mv", @@ -158,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_4.h b/libopenage/renderer/demo/demo_4.h index 7c80e947e1..c3ce3ad4b5 100644 --- a/libopenage/renderer/demo/demo_4.h +++ b/libopenage/renderer/demo/demo_4.h @@ -1,4 +1,4 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/renderer/demo/demo_5.cpp b/libopenage/renderer/demo/demo_5.cpp index b8adc896e3..34e8f4db22 100644 --- a/libopenage/renderer/demo/demo_5.cpp +++ b/libopenage/renderer/demo/demo_5.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 "demo_5.h" @@ -7,8 +7,11 @@ #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" +#include "renderer/render_target.h" #include "renderer/resources/buffer_info.h" #include "renderer/resources/shader_source.h" #include "renderer/resources/texture_data.h" @@ -22,7 +25,11 @@ namespace openage::renderer::tests { void renderer_demo_5(const util::Path &path) { auto qtapp = std::make_shared(); - opengl::GlWindow window("openage renderer test", 800, 600, true); + window_settings settings; + settings.width = 800; + settings.height = 600; + settings.debug = true; + opengl::GlWindow window("openage renderer test", settings); auto renderer = window.make_renderer(); auto size = window.get_size(); @@ -78,7 +85,7 @@ void renderer_demo_5(const util::Path &path) { auto const vert_data_size = verts.size() * sizeof(float); std::vector vert_data(vert_data_size); - std::memcpy(vert_data.data(), reinterpret_cast(verts.data()), vert_data_size); + std::memcpy(vert_data.data(), verts.data(), vert_data_size); resources::MeshData meshdata{std::move(vert_data), info}; @@ -128,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 new file mode 100644 index 0000000000..e8beaafc6c --- /dev/null +++ b/libopenage/renderer/demo/demo_6.cpp @@ -0,0 +1,498 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "demo_6.h" + +#include + +#include "curve/continuous.h" +#include "curve/segmented.h" +#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" +#include "renderer/renderer.h" +#include "renderer/resources/animation/angle_info.h" +#include "renderer/resources/animation/frame_info.h" +#include "renderer/resources/animation/layer_info.h" +#include "renderer/resources/parser/parse_sprite.h" +#include "renderer/resources/parser/parse_terrain.h" +#include "renderer/resources/shader_source.h" +#include "renderer/resources/texture_info.h" +#include "renderer/shader_program.h" +#include "renderer/texture.h" +#include "renderer/uniform_buffer.h" +#include "time/clock.h" +#include "util/path.h" +#include "util/vector.h" + + +namespace openage::renderer::tests { + +void renderer_demo_6(const util::Path &path) { + auto render_mgr = RenderManagerDemo6{path}; + + // Create render objects + auto renderables_2d = render_mgr.create_2d_obj(); + auto renderables_3d = render_mgr.create_3d_obj(); + auto renderables_frame = render_mgr.create_frame_obj(); + + // Add objects to the render passes + render_mgr.obj_2d_pass->add_renderables(std::move(renderables_2d)); + render_mgr.obj_3d_pass->add_renderables({renderables_3d}); + render_mgr.frame_pass->add_renderables(std::move(renderables_frame)); + + // Move the camera with the WASD keys + // This is where we will also update the frustum for the 2D objects + render_mgr.window->add_key_callback([&](const QKeyEvent &ev) { + if (ev.type() == QEvent::KeyPress) { + auto key = ev.key(); + + // move_frame moves the camera in the specified direction in the next drawn frame + switch (key) { + case Qt::Key_W: { // forward + render_mgr.camera->move_rel(Eigen::Vector3f(-1.0f, 0.0f, -1.0f), 0.2f); + } break; + case Qt::Key_A: { // left + render_mgr.camera->move_rel(Eigen::Vector3f(-1.0f, 0.0f, 1.0f), 0.1f); + } break; + case Qt::Key_S: { // back + render_mgr.camera->move_rel(Eigen::Vector3f(1.0f, 0.0f, 1.0f), 0.2f); + } break; + case Qt::Key_D: { // right + render_mgr.camera->move_rel(Eigen::Vector3f(1.0f, 0.0f, -1.0f), 0.1f); + } break; + default: + break; + } + + // Update the camera uniform buffer + auto new_cam_unifs = render_mgr.camera->get_uniform_buffer()->new_uniform_input( + "view", + render_mgr.camera->get_view_matrix(), + "proj", + render_mgr.camera->get_projection_matrix()); + render_mgr.camera->get_uniform_buffer()->update_uniforms(new_cam_unifs); + + /* Generate the frustum for the 2D objects */ + + // Copy the camera used for drawing + // + // In this demo, we will manipulate the frustum camera + // to create a slightly smaller frustum for the 2D objects + // to show the effect of frustum culling. + // + // In the real renderer, the normal camera would be used. + auto frustum_camera = *render_mgr.camera; + + // Downsize the frustum to 70% of the camera size + float frustum_factor = 0.7f; + auto frustum_cam_size = util::Vector2s{render_mgr.camera->get_viewport_size()[0] * frustum_factor, + render_mgr.camera->get_viewport_size()[1] * frustum_factor}; + frustum_camera.resize(frustum_cam_size[0], frustum_cam_size[1]); + + // Get a 2D frustum object + auto frustum_2d = frustum_camera.get_frustum_2d(); + frustum_2d.update(frustum_camera.get_viewport_size(), + frustum_camera.get_view_matrix(), + frustum_camera.get_projection_matrix(), + frustum_camera.get_zoom()); + + // Check if the 2D scene objects are in the frustum + // and update the renderables in the 2D render pass + auto renderables_2d = render_mgr.create_2d_obj(); + std::vector renderables_in_frustum{}; + for (size_t i = 0; i < render_mgr.obj_2d_positions.size(); ++i) { + auto pos = render_mgr.obj_2d_positions.at(i); + bool in_frustum = frustum_2d.in_frustum(pos.to_world_space(), + Eigen::Matrix4f::Identity(), + render_mgr.animation_2d_info.get_scalefactor(), + render_mgr.animation_2d_info.get_max_bounds()); + if (in_frustum) { + // Only add objects that are in the frustum + renderables_in_frustum.push_back(renderables_2d.at(i)); + } + } + + // Clear the renderables in the 2D render pass + // and add ONLY the renderables that are inside the frustum + render_mgr.obj_2d_pass->clear_renderables(); + render_mgr.obj_2d_pass->add_renderables(std::move(renderables_in_frustum)); + } + }); + + // Draw everything + render_mgr.run(); +} + +RenderManagerDemo6::RenderManagerDemo6(const util::Path &path) : + path{path} { + this->setup(); +} + +void RenderManagerDemo6::run() { + while (not window->should_close()) { + this->qtapp->process_events(); + + // Draw everything + renderer->render(this->obj_3d_pass); + renderer->render(this->obj_2d_pass); + renderer->render(this->frame_pass); + renderer->render(this->display_pass); + + // Display final output on screen + this->window->update(); + } +} + +const std::vector RenderManagerDemo6::create_2d_obj() { + std::vector renderables; + for (size_t i = 0; i < this->obj_2d_positions.size(); ++i) { + // Create renderable for 2D animation + auto scale = this->animation_2d_info.get_scalefactor(); + auto angle = this->obj_2d_angles.at(i); + auto frame_info = this->animation_2d_info.get_layer(0).get_angle(angle)->get_frame(0); + auto tex_id = frame_info->get_texture_idx(); + auto subtex_id = frame_info->get_subtexture_idx(); + auto subtex = this->animation_2d_info.get_texture(tex_id)->get_subtex_info(subtex_id); + auto subtex_size = subtex.get_size(); + Eigen::Vector2f subtex_size_vec{ + static_cast(subtex_size[0]), + static_cast(subtex_size[1])}; + auto anchor_params = subtex.get_anchor_params(); + auto anchor_params_vec = Eigen::Vector2f{ + static_cast(anchor_params[0]), + static_cast(anchor_params[1])}; + auto tile_params = subtex.get_subtex_coords(); + auto scene_pos = this->obj_2d_positions.at(i); + auto animation_2d_unifs = this->obj_2d_shader->new_uniform_input( + "obj_world_position", + scene_pos.to_world_space(), + "scale", + scale, + "subtex_size", + subtex_size_vec, + "anchor_offset", + anchor_params_vec, + "tex", + this->obj_2d_texture, + "tile_params", + tile_params); + auto quad = this->renderer->add_mesh_geometry(resources::MeshData::make_quad()); + Renderable animation_2d_obj{ + animation_2d_unifs, + quad, + true, + true, + }; + + renderables.push_back(animation_2d_obj); + } + + return renderables; +} + +const renderer::Renderable RenderManagerDemo6::create_3d_obj() { + // Create renderable for terrain + 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}); + terrain_pos.push_back({-25, 25, 0}); + terrain_pos.push_back({25, 25, 0}); + std::vector terrain_verts{}; + for (size_t i = 0; i < terrain_pos.size(); ++i) { + auto scene_pos = terrain_pos.at(i).to_world_space(); + terrain_verts.push_back(scene_pos[0]); + terrain_verts.push_back(scene_pos[1]); + terrain_verts.push_back(scene_pos[2]); + terrain_verts.push_back(0.0f + i / 2); + terrain_verts.push_back(0.0f + i % 2); + } + auto vert_info = resources::VertexInputInfo{ + {resources::vertex_input_t::V3F32, resources::vertex_input_t::V2F32}, + resources::vertex_layout_t::AOS, + resources::vertex_primitive_t::TRIANGLE_STRIP, + }; + std::vector vert_data(terrain_verts.size() * sizeof(float)); + std::memcpy(vert_data.data(), terrain_verts.data(), vert_data.size()); + auto terrain_mesh = resources::MeshData{std::move(vert_data), vert_info}; + auto terrain_geometry = this->renderer->add_mesh_geometry(terrain_mesh); + Renderable terrain_obj{ + terrain_unifs, + terrain_geometry, + true, + true, + }; + + return terrain_obj; +} + +const std::vector RenderManagerDemo6::create_frame_obj() { + std::vector renderables; + for (auto scene_pos : this->obj_2d_positions) { + // Create renderables for frame around sprites + std::array frame_verts{-1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f}; + std::vector frame_vert_data(frame_verts.size() * sizeof(float)); + std::memcpy(frame_vert_data.data(), frame_verts.data(), frame_vert_data.size()); + auto frame_vert_info = resources::VertexInputInfo{ + {resources::vertex_input_t::V2F32}, + resources::vertex_layout_t::AOS, + resources::vertex_primitive_t::LINE_LOOP, + }; + auto frame_mesh = resources::MeshData{std::move(frame_vert_data), frame_vert_info}; + auto frame_geometry = this->renderer->add_mesh_geometry(frame_mesh); + + auto scale = this->animation_2d_info.get_scalefactor(); + auto max_frame_size = this->animation_2d_info.get_max_size(); + auto frame_size = Eigen::Vector2f{ + static_cast(max_frame_size[0]), + static_cast(max_frame_size[1])}; + auto frame_unifs = this->frame_shader->new_uniform_input( + "obj_world_position", + scene_pos.to_world_space(), + "scale", + scale, + "frame_size", + frame_size, + "incol", + Eigen::Vector4f{0.0f, 0.0f, 1.0f, 1.0f}); + Renderable frame_obj{ + frame_unifs, + frame_geometry, + true, + true, + }; + + renderables.push_back(frame_obj); + } + + // Create renderable for frustum frame + std::array frame_verts{-0.7f, -0.7f, -0.7f, 0.7f, 0.7f, 0.7f, 0.7f, -0.7f}; + std::vector frame_vert_data(frame_verts.size() * sizeof(float)); + std::memcpy(frame_vert_data.data(), frame_verts.data(), frame_vert_data.size()); + auto frame_vert_info = resources::VertexInputInfo{ + {resources::vertex_input_t::V2F32}, + resources::vertex_layout_t::AOS, + resources::vertex_primitive_t::LINE_LOOP, + }; + auto frame_mesh = resources::MeshData{std::move(frame_vert_data), frame_vert_info}; + auto frame_geometry = this->renderer->add_mesh_geometry(frame_mesh); + + auto frame_unifs = this->frustum_shader->new_uniform_input( + "incol", + Eigen::Vector4f{1.0f, 0.0f, 0.0f, 1.0f}); + Renderable frame_obj{ + frame_unifs, + frame_geometry, + true, + true, + }; + renderables.push_back(frame_obj); + + return renderables; +} + +void RenderManagerDemo6::setup() { + this->qtapp = std::make_shared(); + + // Create the window and renderer + window_settings settings; + settings.width = 1024; + settings.height = 768; + settings.debug = true; + this->window = std::make_shared("openage renderer test", settings); + this->renderer = window->make_renderer(); + + this->load_shaders(); + this->load_assets(); + this->create_camera(); + this->create_render_passes(); +} + +void RenderManagerDemo6::load_shaders() { + // Shader + auto shaderdir = this->path / "assets" / "test" / "shaders"; + + /* Shader for 3D objects*/ + auto obj_vshader_file = (shaderdir / "demo_6_3d.vert.glsl").open(); + auto obj_vshader_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::vertex, + obj_vshader_file.read()); + obj_vshader_file.close(); + + auto obj_fshader_file = (shaderdir / "demo_6_3d.frag.glsl").open(); + auto obj_fshader_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::fragment, + obj_fshader_file.read()); + obj_fshader_file.close(); + + /* Shader for 2D animations */ + auto obj_2d_vshader_file = (shaderdir / "demo_6_2d.vert.glsl").open(); + auto obj_2d_vshader_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::vertex, + obj_2d_vshader_file.read()); + obj_2d_vshader_file.close(); + + auto obj_2d_fshader_file = (shaderdir / "demo_6_2d.frag.glsl").open(); + auto obj_2d_fshader_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::fragment, + obj_2d_fshader_file.read()); + obj_2d_fshader_file.close(); + + /* Shader for frames */ + auto frame_vshader_file = (shaderdir / "demo_6_2d_frame.vert.glsl").open(); + auto frame_vshader_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::vertex, + frame_vshader_file.read()); + frame_vshader_file.close(); + + auto frame_fshader_file = (shaderdir / "demo_6_2d_frame.frag.glsl").open(); + auto frame_fshader_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::fragment, + frame_fshader_file.read()); + frame_fshader_file.close(); + + /* Shader for frustrum frame */ + auto frustum_vshader_file = (shaderdir / "demo_6_2d_frustum_frame.vert.glsl").open(); + auto frustum_vshader_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::vertex, + frustum_vshader_file.read()); + frustum_vshader_file.close(); + + // Use the same fragment shader as the frame shader + auto frustum_fshader_src = frame_fshader_src; + + /* Shader for rendering to the screen */ + auto display_vshader_file = (shaderdir / "demo_6_display.vert.glsl").open(); + auto display_vshader_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::vertex, + display_vshader_file.read()); + display_vshader_file.close(); + + auto display_fshader_file = (shaderdir / "demo_6_display.frag.glsl").open(); + auto display_fshader_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::fragment, + display_fshader_file.read()); + display_fshader_file.close(); + + // Create shader programs + this->obj_3d_shader = this->renderer->add_shader({obj_vshader_src, obj_fshader_src}); + this->obj_2d_shader = this->renderer->add_shader({obj_2d_vshader_src, obj_2d_fshader_src}); + this->frame_shader = this->renderer->add_shader({frame_vshader_src, frame_fshader_src}); + this->frustum_shader = this->renderer->add_shader({frustum_vshader_src, frustum_fshader_src}); + this->display_shader = this->renderer->add_shader({display_vshader_src, display_fshader_src}); +} + +void RenderManagerDemo6::load_assets() { + // Load assets for 2D objects + auto animation_2d_path = this->path / "assets" / "test" / "textures" / "test_tank.sprite"; + this->animation_2d_info = resources::parser::parse_sprite_file(animation_2d_path); + + auto tex_info = this->animation_2d_info.get_texture(0); + auto tex_data = resources::Texture2dData{*tex_info}; + this->obj_2d_texture = this->renderer->add_texture(tex_data); + + // Load assets for 3D objects + auto terrain_path = this->path / "assets" / "test" / "textures" / "test_terrain.terrain"; + this->terrain_3d_info = resources::parser::parse_terrain_file(terrain_path); + + auto terrain_tex_info = this->terrain_3d_info.get_texture(0); + auto terrain_tex_data = resources::Texture2dData{*terrain_tex_info}; + this->obj_3d_texture = this->renderer->add_texture(terrain_tex_data); +} + +void RenderManagerDemo6::create_camera() { + // Camera + this->camera = std::make_shared(renderer, window->get_size()); + this->camera->set_zoom(2.0f); + + // Bind the camera uniform buffer to the shaders + obj_3d_shader->bind_uniform_buffer("camera", this->camera->get_uniform_buffer()); + obj_2d_shader->bind_uniform_buffer("camera", this->camera->get_uniform_buffer()); + frame_shader->bind_uniform_buffer("camera", this->camera->get_uniform_buffer()); + + // Update the camera uniform buffer + auto camera_unifs = camera->get_uniform_buffer()->new_uniform_input( + "view", + this->camera->get_view_matrix(), + "proj", + this->camera->get_projection_matrix(), + "inv_zoom", + 1.0f / this->camera->get_zoom()); + auto viewport_size = this->camera->get_viewport_size(); + Eigen::Vector2f viewport_size_vec{ + 1.0f / static_cast(viewport_size[0]), + 1.0f / static_cast(viewport_size[1])}; + camera_unifs->update("inv_viewport_size", viewport_size_vec); + this->camera->get_uniform_buffer()->update_uniforms(camera_unifs); +} + +void RenderManagerDemo6::create_render_passes() { + // Create render passes + auto window_size = window->get_size(); + auto color_texture0 = renderer->add_texture(resources::Texture2dInfo(window_size[0], + window_size[1], + resources::pixel_format::rgba8)); + auto fbo0 = renderer->create_texture_target({color_texture0}); + this->obj_3d_pass = renderer->add_render_pass({}, fbo0); + + auto color_texture1 = renderer->add_texture(resources::Texture2dInfo(window_size[0], + window_size[1], + resources::pixel_format::rgba8)); + auto fbo1 = renderer->create_texture_target({color_texture1}); + this->obj_2d_pass = renderer->add_render_pass({}, fbo1); + + auto color_texture2 = renderer->add_texture(resources::Texture2dInfo(window_size[0], + window_size[1], + resources::pixel_format::rgba8)); + auto fbo2 = renderer->create_texture_target({color_texture2}); + this->frame_pass = renderer->add_render_pass({}, fbo2); + + // Create render pass for rendering to screen + auto quad = renderer->add_mesh_geometry(resources::MeshData::make_quad()); + auto color_texture0_unif = display_shader->new_uniform_input("color_texture", color_texture0); + Renderable display_obj_3d{ + color_texture0_unif, + quad, + true, + true, + }; + auto color_texture1_unif = display_shader->new_uniform_input("color_texture", color_texture1); + Renderable display_obj_2d{ + color_texture1_unif, + quad, + true, + true, + }; + auto color_texture2_unif = display_shader->new_uniform_input("color_texture", color_texture2); + Renderable display_obj_frame{ + color_texture2_unif, + quad, + true, + true, + }; + 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_6.h b/libopenage/renderer/demo/demo_6.h new file mode 100644 index 0000000000..f86728819b --- /dev/null +++ b/libopenage/renderer/demo/demo_6.h @@ -0,0 +1,126 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include "coord/scene.h" +#include "renderer/renderable.h" +#include "renderer/resources/animation/animation_info.h" +#include "renderer/resources/terrain/terrain_info.h" +#include "util/path.h" + + +namespace openage::renderer { +class RenderPass; +class Renderer; +class ShaderProgram; +class Texture2d; + +namespace camera { +class Camera; +} + +namespace gui { +class GuiApplicationWithLogger; +} + +namespace opengl { +class GlWindow; +} + +namespace tests { + +/** + * Show the usage of frustum culling in the renderer. + * - Window creation + * - Loading shaders + * - Creating a camera + * - 2D and 3D frustum retrieval + * - Manipulating the frustum + * - Rendering objects with frustum culling + * + * @param path Path to the openage asset directory. + */ +void renderer_demo_6(const util::Path &path); + + +/** + * Render manager that handles drawing, object creation, etc. + */ +class RenderManagerDemo6 { +public: + RenderManagerDemo6(const util::Path &path); + void run(); + + /* Create objects to render */ + + /// Create 2D objects (sprites) + const std::vector create_2d_obj(); + /// Create 3D objects (terrain) + const renderer::Renderable create_3d_obj(); + /// Create frames around 2D objects. These represents the boundaries of the objects + /// that are used by the frustum culling algorithm. + const std::vector create_frame_obj(); + + /// Qt application + std::shared_ptr qtapp; + + /// OpenGL window + std::shared_ptr window; + + /// Camera + std::shared_ptr camera; + + /// Render passes + std::shared_ptr obj_2d_pass; + std::shared_ptr obj_3d_pass; + std::shared_ptr frame_pass; + std::shared_ptr display_pass; + + /// 2D object (sprite) positions + const std::array obj_2d_positions = { + coord::scene3{0, 0, 0}, + coord::scene3{-4, -4, 0}, + coord::scene3{4, 4, 0}, + coord::scene3{-2, 3, 0}, + coord::scene3{3, -2, 0}, + }; + + /// Rendered angles of the 2D objects + const std::array obj_2d_angles = {0, 1, 2, 3, 4}; + + /// Animation and texture information + resources::Animation2dInfo animation_2d_info; + resources::TerrainInfo terrain_3d_info; + +private: + /// Setup everything necessary for rendering. + void setup(); + + /// Load shaders, assets, create camera, and render passes. + void load_shaders(); + void load_assets(); + void create_camera(); + void create_render_passes(); + + /// Directory path + util::Path path; + + /// Renderer + std::shared_ptr renderer; + + /// Shaders + std::shared_ptr obj_2d_shader; + std::shared_ptr obj_3d_shader; + std::shared_ptr frame_shader; + std::shared_ptr frustum_shader; + std::shared_ptr display_shader; + + /// Textures for the rendered objects (2D and 3D) + std::shared_ptr obj_2d_texture; + std::shared_ptr obj_3d_texture; +}; + +} // namespace tests +} // namespace openage::renderer 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/stresstest_0.cpp b/libopenage/renderer/demo/stresstest_0.cpp index 77b8f0f33b..eae208c5d3 100644 --- a/libopenage/renderer/demo/stresstest_0.cpp +++ b/libopenage/renderer/demo/stresstest_0.cpp @@ -1,22 +1,25 @@ -// 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 "stresstest_0.h" #include +#include "coord/tile.h" #include "renderer/camera/camera.h" #include "renderer/gui/integration/public/gui_application_with_logger.h" #include "renderer/opengl/window.h" #include "renderer/render_factory.h" +#include "renderer/render_pass.h" +#include "renderer/render_target.h" #include "renderer/resources/assets/asset_manager.h" #include "renderer/resources/shader_source.h" #include "renderer/stages/camera/manager.h" -#include "renderer/stages/screen/screen_renderer.h" -#include "renderer/stages/skybox/skybox_renderer.h" -#include "renderer/stages/terrain/terrain_render_entity.h" -#include "renderer/stages/terrain/terrain_renderer.h" -#include "renderer/stages/world/world_render_entity.h" -#include "renderer/stages/world/world_renderer.h" +#include "renderer/stages/screen/render_stage.h" +#include "renderer/stages/skybox/render_stage.h" +#include "renderer/stages/terrain/render_entity.h" +#include "renderer/stages/terrain/render_stage.h" +#include "renderer/stages/world/render_entity.h" +#include "renderer/stages/world/render_stage.h" #include "renderer/uniform_buffer.h" #include "time/clock.h" #include "util/fps.h" @@ -27,7 +30,12 @@ namespace openage::renderer::tests { void renderer_stresstest_0(const util::Path &path) { auto qtapp = std::make_shared(); - auto window = std::make_shared("openage renderer test", 1024, 768, true); + window_settings settings; + settings.width = 1024; + settings.height = 768; + settings.vsync = false; + settings.debug = true; + auto window = std::make_shared("openage renderer test", settings); auto renderer = window->make_renderer(); // Clock required by world renderer for timing animation frames @@ -43,7 +51,14 @@ void renderer_stresstest_0(const util::Path &path) { "view", camera->get_view_matrix(), "proj", - camera->get_projection_matrix()); + camera->get_projection_matrix(), + "inv_zoom", + 1.0f / camera->get_zoom()); + auto viewport_size = camera->get_viewport_size(); + Eigen::Vector2f viewport_size_vec{ + 1.0f / static_cast(viewport_size[0]), + 1.0f / static_cast(viewport_size[1])}; + cam_unifs->update("inv_viewport_size", viewport_size_vec); camera->get_uniform_buffer()->update_uniforms(cam_unifs); // Render stages @@ -58,14 +73,14 @@ void renderer_stresstest_0(const util::Path &path) { path["assets"]["test"]); // Renders the background - auto skybox_renderer = std::make_shared( + auto skybox_renderer = std::make_shared( window, renderer, path["assets"]["shaders"]); skybox_renderer->set_color(1.0f, 0.5f, 0.0f, 1.0f); // orange color // Renders the terrain in 3D - auto terrain_renderer = std::make_shared( + auto terrain_renderer = std::make_shared( window, renderer, camera, @@ -74,7 +89,7 @@ void renderer_stresstest_0(const util::Path &path) { clock); // Renders units/buildings/other objects - auto world_renderer = std::make_shared( + auto world_renderer = std::make_shared( window, renderer, camera, @@ -92,7 +107,7 @@ void renderer_stresstest_0(const util::Path &path) { // Final output on screen has its own subrenderer // It takes the outputs of all previous render passes // and blends them together - auto screen_renderer = std::make_shared( + auto screen_renderer = std::make_shared( window, renderer, path["assets"]["shaders"]); @@ -116,24 +131,23 @@ void renderer_stresstest_0(const util::Path &path) { // Create some entities to populate the scene auto render_factory = std::make_shared(terrain_renderer, world_renderer); - // Terrain - auto terrain0 = render_factory->add_terrain_render_entity(); - // Fill a 10x10 terrain grid with height values auto terrain_size = util::Vector2s{10, 10}; - std::vector height_map{}; - height_map.reserve(terrain_size[0] * terrain_size[1]); + std::vector> tiles{}; + tiles.reserve(terrain_size[0] * terrain_size[1]); for (size_t i = 0; i < terrain_size[0] * terrain_size[1]; ++i) { - height_map.push_back(0.0f); + tiles.emplace_back(0.0f, "./textures/test_terrain.terrain"); } + // Create entity for terrain rendering + auto terrain0 = render_factory->add_terrain_render_entity(terrain_size, + coord::tile_delta{0, 0}); + // send the terrain data to the terrain renderer - terrain0->update(terrain_size, - height_map, - "./textures/test_terrain.terrain"); + terrain0->update(terrain_size, tiles); // World entities - std::vector> render_entities{}; + std::vector> render_entities{}; auto add_world_entity = [&](const coord::phys3 initial_pos, const time::time_t time) { const auto animation_path = "./textures/test_tank_mirrored.sprite"; diff --git a/libopenage/renderer/demo/stresstest_1.cpp b/libopenage/renderer/demo/stresstest_1.cpp new file mode 100644 index 0000000000..9d2d43968b --- /dev/null +++ b/libopenage/renderer/demo/stresstest_1.cpp @@ -0,0 +1,236 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "stresstest_1.h" + +#include + +#include "coord/tile.h" +#include "renderer/camera/camera.h" +#include "renderer/gui/integration/public/gui_application_with_logger.h" +#include "renderer/opengl/window.h" +#include "renderer/render_factory.h" +#include "renderer/render_pass.h" +#include "renderer/resources/assets/asset_manager.h" +#include "renderer/resources/shader_source.h" +#include "renderer/stages/camera/manager.h" +#include "renderer/stages/screen/render_stage.h" +#include "renderer/stages/skybox/render_stage.h" +#include "renderer/stages/terrain/render_entity.h" +#include "renderer/stages/terrain/render_stage.h" +#include "renderer/stages/world/render_entity.h" +#include "renderer/stages/world/render_stage.h" +#include "renderer/uniform_buffer.h" +#include "time/clock.h" +#include "util/fps.h" + +namespace openage::renderer::tests { +void renderer_stresstest_1(const util::Path &path) { + auto qtapp = std::make_shared(); + + // Create the window and renderer + window_settings settings; + settings.width = 1024; + settings.height = 768; + settings.vsync = false; + settings.debug = true; + auto window = std::make_shared("openage renderer test", settings); + auto renderer = window->make_renderer(); + + // Clock required by world renderer for timing animation frames + auto clock = std::make_shared(); + + // Camera + // our viewport into the game world + // on this one, enable frustum culling + auto camera = std::make_shared(renderer, + window->get_size(), + Eigen::Vector3f{17.0f, 10.0f, 7.0f}, + 1.f, + 64.f, + 1.f / 49.f); + auto cam_unifs = camera->get_uniform_buffer()->create_empty_input(); + cam_unifs->update( + "view", + camera->get_view_matrix(), + "proj", + camera->get_projection_matrix(), + "inv_zoom", + 1.0f / camera->get_zoom()); + auto viewport_size = camera->get_viewport_size(); + Eigen::Vector2f viewport_size_vec{ + 1.0f / static_cast(viewport_size[0]), + 1.0f / static_cast(viewport_size[1])}; + cam_unifs->update("inv_viewport_size", viewport_size_vec); + camera->get_uniform_buffer()->update_uniforms(cam_unifs); + + // Render stages + // every stage use a different subrenderer that manages renderables, + // shaders, textures & more. + std::vector> + render_passes{}; + + // TODO: Make this optional for subrenderers? + auto asset_manager = std::make_shared( + renderer, + path["assets"]["test"]); + + // Renders the background + auto skybox_renderer = std::make_shared( + window, + renderer, + path["assets"]["shaders"]); + skybox_renderer->set_color(1.0f, 0.5f, 0.0f, 1.0f); // orange color + + // Renders the terrain in 3D + auto terrain_renderer = std::make_shared( + window, + renderer, + camera, + path["assets"]["shaders"], + asset_manager, + clock); + + // Renders units/buildings/other objects + auto world_renderer = std::make_shared( + window, + renderer, + camera, + path["assets"]["shaders"], + asset_manager, + clock); + + // Enable frustum culling + renderer::world::WorldRenderStage::ENABLE_FRUSTUM_CULLING = true; + + // Store the render passes of the renderers + // The order is important as its also the order in which they + // are rendered and drawn onto the screen. + render_passes.push_back(skybox_renderer->get_render_pass()); + render_passes.push_back(terrain_renderer->get_render_pass()); + render_passes.push_back(world_renderer->get_render_pass()); + + // Final output on screen has its own subrenderer + // It takes the outputs of all previous render passes + // and blends them together + auto screen_renderer = std::make_shared( + window, + renderer, + path["assets"]["shaders"]); + std::vector> targets{}; + for (auto &pass : render_passes) { + targets.push_back(pass->get_target()); + } + screen_renderer->set_render_targets(targets); + + window->add_resize_callback([&](size_t, size_t, double /*scale*/) { + // Acquire the render targets for all previous passes + std::vector> targets{}; + for (size_t i = 0; i < render_passes.size() - 1; ++i) { + targets.push_back(render_passes[i]->get_target()); + } + screen_renderer->set_render_targets(targets); + }); + + render_passes.push_back(screen_renderer->get_render_pass()); + + // Create some entities to populate the scene + auto render_factory = std::make_shared(terrain_renderer, world_renderer); + + // Fill a 10x10 terrain grid with height values + auto terrain_size = util::Vector2s{10, 10}; + std::vector> tiles{}; + tiles.reserve(terrain_size[0] * terrain_size[1]); + for (size_t i = 0; i < terrain_size[0] * terrain_size[1]; ++i) { + tiles.emplace_back(0.0f, "./textures/test_terrain.terrain"); + } + + // Create entity for terrain rendering + auto terrain0 = render_factory->add_terrain_render_entity(terrain_size, + coord::tile_delta{0, 0}); + + // send the terrain data to the terrain renderer + terrain0->update(terrain_size, tiles); + + std::vector> render_entities{}; + auto add_world_entity = [&](const coord::phys3 initial_pos, + const time::time_t time) { + const auto animation_path = "./textures/test_tank_mirrored.sprite"; + + auto position = curve::Continuous{nullptr, 0, "", nullptr, coord::phys3(0, 0, 0)}; + position.set_insert(time, initial_pos); + position.set_insert(time + 1, initial_pos + coord::phys3_delta{5, 0, 0}); + position.set_insert(time + 2, initial_pos + coord::phys3_delta{12, -2, 0}); + position.set_insert(time + 3, initial_pos + coord::phys3_delta{5, 5, 0}); + position.set_insert(time + 4, initial_pos + coord::phys3_delta{12, 12, 0}); + position.set_insert(time + 5, initial_pos + coord::phys3_delta{5, 9, 0}); + position.set_insert(time + 6, initial_pos + coord::phys3_delta{-2, 12, 0}); + position.set_insert(time + 7, initial_pos + coord::phys3_delta{0, 5, 0}); + position.set_insert(time + 8, initial_pos); + + auto angle = curve::Segmented{nullptr, 0}; + angle.set_insert(time, coord::phys_angle_t::from_int(225)); + angle.set_insert_jump(time + 1, coord::phys_angle_t::from_int(225), coord::phys_angle_t::from_int(210)); + angle.set_insert_jump(time + 2, coord::phys_angle_t::from_int(210), coord::phys_angle_t::from_int(0)); + angle.set_insert_jump(time + 3, coord::phys_angle_t::from_int(0), coord::phys_angle_t::from_int(270)); + angle.set_insert_jump(time + 4, coord::phys_angle_t::from_int(270), coord::phys_angle_t::from_int(60)); + angle.set_insert_jump(time + 5, coord::phys_angle_t::from_int(60), coord::phys_angle_t::from_int(45)); + angle.set_insert_jump(time + 6, coord::phys_angle_t::from_int(45), coord::phys_angle_t::from_int(120)); + angle.set_insert_jump(time + 7, coord::phys_angle_t::from_int(120), coord::phys_angle_t::from_int(135)); + angle.set_insert_jump(time + 8, coord::phys_angle_t::from_int(135), coord::phys_angle_t::from_int(225)); + + auto entity = render_factory->add_world_render_entity(); + entity->update(render_entities.size(), + position, + angle, + animation_path, + time); + render_entities.push_back(entity); + }; + + // Stop after 1000 entities + size_t entity_limit = 1000; + + clock->start(); + + util::FrameCounter timer; + + time::time_t next_entity = clock->get_real_time(); + while (render_entities.size() <= entity_limit) { + // Print FPS + timer.frame(); + std::cout + << "Entities: " << render_entities.size() + << " -- " + << "FPS: " << timer.fps << "\r" << std::flush; + + qtapp->process_events(); + + // Advance time + clock->update_time(); + auto current_time = clock->get_real_time(); + if (current_time > next_entity) { + add_world_entity(coord::phys3(0.5, 0.5, 0.0f), clock->get_time()); + next_entity = current_time + 0.05; + } + + // Update the renderables of the subrenderers + terrain_renderer->update(); + world_renderer->update(); + + // Draw everything + for (auto &pass : render_passes) { + renderer->render(pass); + } + + renderer->check_error(); + + // Display final output on screen + window->update(); + } + + clock->stop(); + log::log(MSG(info) << "Stopped after rendering " << render_entities.size() << " entities"); + + window->close(); +} +} // namespace openage::renderer::tests diff --git a/libopenage/renderer/demo/stresstest_1.h b/libopenage/renderer/demo/stresstest_1.h new file mode 100644 index 0000000000..a2dbde6136 --- /dev/null +++ b/libopenage/renderer/demo/stresstest_1.h @@ -0,0 +1,16 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include "util/path.h" + +namespace openage::renderer::tests { + +/** + * Stresstest for the renderer's frustum culling feature. + * + * @param path Path to the openage asset directory. + */ +void renderer_stresstest_1(const util::Path &path); + +} diff --git a/libopenage/renderer/demo/tests.cpp b/libopenage/renderer/demo/tests.cpp index 95f6bd40af..bab82af2e3 100644 --- a/libopenage/renderer/demo/tests.cpp +++ b/libopenage/renderer/demo/tests.cpp @@ -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. #include "tests.h" @@ -11,8 +11,10 @@ #include "renderer/demo/demo_3.h" #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" namespace openage::renderer::tests { @@ -42,6 +44,14 @@ void renderer_demo(int demo_id, const util::Path &path) { renderer_demo_5(path); break; + case 6: + renderer_demo_6(path); + break; + + case 7: + renderer_demo_7(path); + break; + default: log::log(MSG(err) << "Unknown renderer demo requested: " << demo_id << "."); break; @@ -53,7 +63,9 @@ OAAPI void renderer_stresstest(int demo_id, const util::Path &path) { case 0: renderer_stresstest_0(path); break; - + case 1: + renderer_stresstest_1(path); + break; default: log::log(MSG(err) << "Unknown renderer stresstest 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/font/font.h b/libopenage/renderer/font/font.h index 24d44df619..65496b3f62 100644 --- a/libopenage/renderer/font/font.h +++ b/libopenage/renderer/font/font.h @@ -1,4 +1,4 @@ -// Copyright 2015-2021 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once @@ -68,8 +68,8 @@ struct font_description { font_description(std::string font_file, unsigned int size, font_direction direction = font_direction::left_to_right, - std::string language="en", - std::string script="Latin"); + std::string language = "en", + std::string script = "Latin"); /** * Constructs a font_description instance. @@ -89,11 +89,9 @@ struct font_description { bool operator==(const font_description &other) const; bool operator!=(const font_description &other) const; - }; class Font { - public: /** * Create a font instance from the description. @@ -189,14 +187,14 @@ class Font { // The HarfBuzz font instance that drives the operations of this font hb_font_t *hb_font; - }; -}} // openage::renderer +} // namespace renderer +} // namespace openage namespace std { -template<> +template <> struct hash { size_t operator()(const openage::renderer::font_description &fd) const { size_t hash = std::hash()(std::type_index(typeid(openage::renderer::font_description))); @@ -206,4 +204,4 @@ struct hash { } }; -} +} // namespace std diff --git a/libopenage/renderer/font/font_manager.h b/libopenage/renderer/font/font_manager.h index 43d4f4e729..540ee1649d 100644 --- a/libopenage/renderer/font/font_manager.h +++ b/libopenage/renderer/font/font_manager.h @@ -1,4 +1,4 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once @@ -14,7 +14,6 @@ namespace openage { namespace renderer { class FreeTypeLibrary { - public: FT_Library ft_library; @@ -36,13 +35,11 @@ class FreeTypeLibrary { FreeTypeLibrary(FreeTypeLibrary &&other) = delete; FreeTypeLibrary &operator=(FreeTypeLibrary &&other) = delete; - }; class Font; class FontManager { - public: /** * Gets the filepath of a particular font family and style. @@ -80,7 +77,7 @@ class FontManager { * @param size: The size of the font in points. * @returns The pointer to font instance. */ - Font *get_font(const char* font_file, unsigned int size); + Font *get_font(const char *font_file, unsigned int size); private: // The freetype library instance @@ -88,7 +85,7 @@ class FontManager { // Font cache. the hash of font's description is used as the key std::unordered_map> fonts; - }; -}} // openage::renderer +} // namespace renderer +} // namespace openage diff --git a/libopenage/renderer/font/glyph_atlas.h b/libopenage/renderer/font/glyph_atlas.h index 8e77ce2eaa..2f8be76763 100644 --- a/libopenage/renderer/font/glyph_atlas.h +++ b/libopenage/renderer/font/glyph_atlas.h @@ -1,4 +1,4 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once @@ -24,7 +24,6 @@ namespace renderer { * glyph atlas can be easily modified. */ class GlyphAtlas { - public: /** * Datastructure for a single atlas entry @@ -129,7 +128,7 @@ class GlyphAtlas { // List of shelves currently used in the atlas std::vector shelves; - }; -}} // openage::renderer +} // namespace renderer +} // namespace openage diff --git a/libopenage/renderer/geometry.h b/libopenage/renderer/geometry.h index 90ce58b8cd..f6f6853925 100644 --- a/libopenage/renderer/geometry.h +++ b/libopenage/renderer/geometry.h @@ -1,10 +1,10 @@ -// Copyright 2015-2018 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once -#include -#include #include +#include +#include namespace openage { @@ -30,13 +30,13 @@ class Geometry { /// In a meshed geometry, updates the vertex data. The size and type of the vertex data has to be the same as before. /// If the mesh is indexed, indices will stay the same. /// @throws if there is a size mismatch between the new and old vertex data - void update_verts(std::vector const& verts); + void update_verts(std::vector const &verts); /// In a meshed geometry, updates the vertex data starting from the offset-th vertex. The type of the vertex /// data has to be the same as it was on initializing the geometry. The size plus the offset cannot exceed the /// previous size of the vertex data. If the mesh is indexed, indices will stay the same. /// @throws if there is a size mismatch between the new and old vertex data - virtual void update_verts_offset(std::vector const& verts, size_t offset) = 0; + virtual void update_verts_offset(std::vector const &verts, size_t offset) = 0; protected: /// Initialize the geometry to a given type. @@ -46,4 +46,5 @@ class Geometry { geometry_t type; }; -}} // openage::renderer +} // namespace renderer +} // namespace openage diff --git a/libopenage/renderer/gui/CMakeLists.txt b/libopenage/renderer/gui/CMakeLists.txt index 60a1676d76..fdddb92d06 100644 --- a/libopenage/renderer/gui/CMakeLists.txt +++ b/libopenage/renderer/gui/CMakeLists.txt @@ -1,8 +1,5 @@ add_sources(libopenage - assetmanager_link.cpp - engine_link.cpp gui.cpp - qml_info.cpp ) add_subdirectory("guisys") diff --git a/libopenage/renderer/gui/assetmanager_link.cpp b/libopenage/renderer/gui/assetmanager_link.cpp deleted file mode 100644 index 600cf51ffb..0000000000 --- a/libopenage/renderer/gui/assetmanager_link.cpp +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#include "assetmanager_link.h" - -#include - -#include "renderer/gui/engine_link.h" - -namespace openage { - -class LegacyEngine; - -namespace renderer { -namespace gui { - -namespace { -const int registration = qmlRegisterType("yay.sfttech.openage", 1, 0, "AssetManager"); -} - -AssetManagerLink::AssetManagerLink(QObject *parent) : - GuiItemQObject{parent}, - GuiItem{this} { - Q_UNUSED(registration); -} - -AssetManagerLink::~AssetManagerLink() = default; - - -const util::Path &AssetManagerLink::get_asset_dir() const { - return this->asset_dir; -} - - -void AssetManagerLink::set_asset_dir(const util::Path &asset_dir) { - static auto f = [](AssetManager *_this, const util::Path &dir) { - _this->set_asset_dir(dir); - }; - this->s(f, this->asset_dir, asset_dir); -} - - -EngineLink *AssetManagerLink::get_engine() const { - return this->engine; -} - - -void AssetManagerLink::set_engine(EngineLink *engine_link) { - static auto f = [](AssetManager *_this, gamestate::GameSimulation *engine) { - _this->set_engine(engine); - }; - this->s(f, this->engine, engine_link); -} - - -} // namespace gui -} // namespace renderer -} // namespace openage diff --git a/libopenage/renderer/gui/assetmanager_link.h b/libopenage/renderer/gui/assetmanager_link.h deleted file mode 100644 index 3378558855..0000000000 --- a/libopenage/renderer/gui/assetmanager_link.h +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include "assets/assetmanager.h" -#include "gui/guisys/link/gui_item.h" -#include "util/path.h" - - -namespace openage { -namespace renderer::gui { -class AssetManagerLink; -class EngineLink; -} // namespace renderer::gui -} // namespace openage - -namespace qtsdl { -template <> -struct Wrap { - using Type = openage::renderer::gui::AssetManagerLink; -}; - -template <> -struct Unwrap { - using Type = openage::AssetManager; -}; - -} // namespace qtsdl - - -namespace openage { -namespace renderer { -namespace gui { - -class AssetManagerLink : public qtsdl::GuiItemQObject - , public qtsdl::GuiItem { - Q_OBJECT - - Q_PROPERTY(openage::util::Path assetDir READ get_asset_dir WRITE set_asset_dir) - Q_MOC_INCLUDE("renderer/gui/engine_link.h") - Q_PROPERTY(EngineLink *engine READ get_engine WRITE set_engine) - -public: - explicit AssetManagerLink(QObject *parent = nullptr); - virtual ~AssetManagerLink(); - - const util::Path &get_asset_dir() const; - void set_asset_dir(const util::Path &data_dir); - - EngineLink *get_engine() const; - void set_engine(EngineLink *engine); - -private: - util::Path asset_dir; - EngineLink *engine; -}; - -} // namespace gui -} // namespace renderer -} // namespace openage diff --git a/libopenage/renderer/gui/engine_link.cpp b/libopenage/renderer/gui/engine_link.cpp deleted file mode 100644 index b4045a583c..0000000000 --- a/libopenage/renderer/gui/engine_link.cpp +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#include "engine_link.h" - -#include - -#include "error/error.h" - -#include "gamestate/simulation.h" - -#include "gui/guisys/link/qml_engine_with_singleton_items_info.h" -#include "gui/guisys/link/qtsdl_checked_static_cast.h" -#include "renderer/gui/qml_info.h" - -namespace openage::renderer::gui { - -namespace { -// this pushes the EngineLink in the QML engine. -// a qml engine calls the static provider() to obtain a handle. -const int registration = qmlRegisterSingletonType("yay.sfttech.openage", 1, 0, "Engine", &EngineLink::provider); -} // namespace - - -EngineLink::EngineLink(QObject *parent, gamestate::GameSimulation *engine) : - GuiSingletonItem{parent}, - core{engine} { - Q_UNUSED(registration); - - // ENSURE(!unwrap(this)->gui_link, "Sharing singletons between QML engines is not supported for now."); - - // when the engine announces that the global key bindings - // changed, update the display. - // QObject::connect( - // &unwrap(this)->gui_signals, - // &EngineSignals::global_binds_changed, - // this, - // &EngineLink::on_global_binds_changed); - - // trigger the engine signal, - // which then triggers this->on_global_binds_changed. - // unwrap(this)->announce_global_binds(); -} - -EngineLink::~EngineLink() { - // unwrap(this)->gui_link = nullptr; -} - -// a qml engine requests a handle to the engine link with that static -// method we do this by extracting the per-qmlengine singleton from the -// engine (the qmlenginewithsingletoninfo), then just return the new link -// instance -QObject *EngineLink::provider(QQmlEngine *engine, QJSEngine *) { - // cast the engine to our specialization - qtsdl::QmlEngineWithSingletonItemsInfo *engine_with_singleton_items_info = qtsdl::checked_static_cast(engine); - - // get the singleton container out of the custom qml engine - auto info = static_cast( - engine_with_singleton_items_info->get_singleton_items_info()); - ENSURE(info, "qml-globals were lost or not passed to the gui subsystem"); - - // owned by the QML engine - // this handle contains the pointer to the openage engine, - // obtained through the qmlengine - return new EngineLink{nullptr, info->engine}; -} - -QStringList EngineLink::get_global_binds() const { - return this->global_binds; -} - -void EngineLink::stop() { - this->core->stop(); -} - -void EngineLink::on_global_binds_changed(const std::vector &global_binds) { - QStringList new_global_binds; - - // create the qstring list from the std string list - // which is then displayed in the ui - std::transform( - std::begin(global_binds), - std::end(global_binds), - std::back_inserter(new_global_binds), - [](const std::string &s) { - return QString::fromStdString(s); - }); - - new_global_binds.sort(); - - if (this->global_binds != new_global_binds) { - this->global_binds = new_global_binds; - emit this->global_binds_changed(); - } -} - -} // namespace openage::renderer::gui diff --git a/libopenage/renderer/gui/engine_link.h b/libopenage/renderer/gui/engine_link.h deleted file mode 100644 index 9b0b7089cf..0000000000 --- a/libopenage/renderer/gui/engine_link.h +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -#include "gui/guisys/link/gui_singleton_item.h" -#include "util/path.h" - -QT_FORWARD_DECLARE_CLASS(QQmlEngine) -QT_FORWARD_DECLARE_CLASS(QJSEngine) - -namespace openage { - -namespace gamestate { -class GameSimulation; -} - -namespace renderer::gui { -class EngineLink; -} // namespace renderer::gui -} // namespace openage - -namespace qtsdl { -template <> -struct Wrap { - using Type = openage::renderer::gui::EngineLink; -}; - -template <> -struct Unwrap { - using Type = openage::gamestate::GameSimulation; -}; - -} // namespace qtsdl - -namespace openage { -namespace renderer { -namespace gui { - -class EngineLink : public qtsdl::GuiSingletonItem { - Q_OBJECT - - /** - * The text list of global key bindings. - * displayed so one can see what keys are active. - */ - Q_PROPERTY(QStringList globalBinds - READ get_global_binds - NOTIFY global_binds_changed) - -public: - explicit EngineLink(QObject *parent, gamestate::GameSimulation *engine); - virtual ~EngineLink(); - - static QObject *provider(QQmlEngine *, QJSEngine *); - - template - U *get() const { - return core; - } - - QStringList get_global_binds() const; - - Q_INVOKABLE void stop(); - -signals: - void global_binds_changed(); - -private slots: - void on_global_binds_changed(const std::vector &global_binds); - -private: - gamestate::GameSimulation *core; - - QStringList global_binds; -}; - -} // namespace gui -} // namespace renderer -} // namespace openage diff --git a/libopenage/renderer/gui/gui.cpp b/libopenage/renderer/gui/gui.cpp index c877c4a537..879a709d0c 100644 --- a/libopenage/renderer/gui/gui.cpp +++ b/libopenage/renderer/gui/gui.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 "gui.h" @@ -6,8 +6,9 @@ #include "renderer/gui/guisys/public/gui_input.h" #include "renderer/gui/guisys/public/gui_renderer.h" #include "renderer/gui/integration/public/gui_application_with_logger.h" -#include "renderer/gui/qml_info.h" #include "renderer/opengl/context.h" +#include "renderer/render_pass.h" +#include "renderer/render_target.h" #include "renderer/renderer.h" #include "renderer/resources/shader_source.h" #include "renderer/resources/texture_info.h" @@ -15,6 +16,7 @@ #include "renderer/window.h" #include "util/path.h" + namespace openage::renderer::gui { GUI::GUI(std::shared_ptr app, @@ -24,8 +26,6 @@ GUI::GUI(std::shared_ptr app, const util::Path &assetdir, const std::shared_ptr &renderer) : application{app}, - render_updater{}, - game_logic_updater{}, gui_renderer{std::make_shared(window)}, gui_input{std::make_shared(gui_renderer)}, engine{std::make_shared(gui_renderer)}, @@ -34,10 +34,10 @@ GUI::GUI(std::shared_ptr app, engine, source.resolve_native_path(), rootdir.resolve_native_path()}, - //input{&gui_renderer, &game_logic_updater} - //image_provider_by_filename{ - // &render_updater, - // openage::gui::GuiGameSpecImageProvider::Type::ByFilename}, + // input{&gui_renderer, &game_logic_updater} + // image_provider_by_filename{ + // &render_updater, + // openage::gui::GuiGameSpecImageProvider::Type::ByFilename}, renderer{renderer} { // everything alright before we create the gui stuff? renderer::opengl::GlContext::check_error(); diff --git a/libopenage/renderer/gui/gui.h b/libopenage/renderer/gui/gui.h index bb83217c48..7baa7306d4 100644 --- a/libopenage/renderer/gui/gui.h +++ b/libopenage/renderer/gui/gui.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 @@ -6,8 +6,6 @@ #include #include -#include "gui/guisys/public/gui_event_queue.h" -#include "gui/integration/public/gui_game_spec_image_provider.h" #include "renderer/gui/guisys/public/gui_subtree.h" namespace qtgui { @@ -31,8 +29,6 @@ class UniformInput; namespace gui { -class QMLInfo; - /** * Interface (= HUD) of the game. * @@ -51,10 +47,10 @@ class GUI { virtual ~GUI() = default; /** - * Get the input handler of the GUI. - * - * @return Input handler of the GUI. - */ + * Get the input handler of the GUI. + * + * @return Input handler of the GUI. + */ std::shared_ptr get_input_handler() const; /** @@ -100,15 +96,6 @@ class GUI { */ std::shared_ptr application; - /** - * TODO - */ - qtsdl::GuiEventQueue render_updater; - /** - * TODO - */ - qtsdl::GuiEventQueue game_logic_updater; - /** * Qt-based renderer for the GUI texture. Draws into * \p gui_texture. @@ -130,8 +117,6 @@ class GUI { */ qtgui::GuiSubtree subtree; - // openage::gui::GuiGameSpecImageProvider image_provider_by_filename; - /** * Reference to the openage renderer. * Used to fetch texture objects for the GUI texture. diff --git a/libopenage/renderer/gui/guisys/CMakeLists.txt b/libopenage/renderer/gui/guisys/CMakeLists.txt index d4f5349e29..3b454bfec5 100644 --- a/libopenage/renderer/gui/guisys/CMakeLists.txt +++ b/libopenage/renderer/gui/guisys/CMakeLists.txt @@ -1,4 +1,13 @@ +list(APPEND QT_SDL_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/link/gui_item.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/link/gui_list_model.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/link/gui_property_map_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/link/gui_singleton_item.cpp +) + list(APPEND QTGUI_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/private/livereload/deferred_initial_constant_property_values.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/private/livereload/gui_live_reloader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/private/livereload/recursive_directory_watcher_worker.cpp ${CMAKE_CURRENT_SOURCE_DIR}/private/livereload/recursive_directory_watcher.cpp ) diff --git a/libopenage/renderer/gui/guisys/link/gui_item.cpp b/libopenage/renderer/gui/guisys/link/gui_item.cpp new file mode 100644 index 0000000000..20bbb7c8c0 --- /dev/null +++ b/libopenage/renderer/gui/guisys/link/gui_item.cpp @@ -0,0 +1,20 @@ +// Copyright 2015-2023 the openage authors. See copying.md for legal info. + +#include "gui_item.h" + + +namespace qtgui { + + +QString name_tidier(const char *name) { + QString cleaner_name = QString::fromLatin1(name); + cleaner_name.remove(QRegularExpression("qtgui|PersistentCoreHolder")); + return cleaner_name; +} + +GuiItemQObject::GuiItemQObject(QObject *parent) : + QObject{parent}, + GuiItemBase{} { +} + +} // namespace qtgui diff --git a/libopenage/gui/guisys/link/gui_item.h b/libopenage/renderer/gui/guisys/link/gui_item.h similarity index 62% rename from libopenage/gui/guisys/link/gui_item.h rename to libopenage/renderer/gui/guisys/link/gui_item.h index 00d252dba3..a0a5001404 100644 --- a/libopenage/gui/guisys/link/gui_item.h +++ b/libopenage/renderer/gui/guisys/link/gui_item.h @@ -1,29 +1,28 @@ -// Copyright 2015-2021 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once -#include #include #include +#include #include #include #include -#include +#include #include +#include #include -#include -#include "../private/game_logic_caller.h" -#include "../private/livereload/deferred_initial_constant_property_values.h" -#include "qtsdl_checked_static_cast.h" -#include "gui_item_link.h" +#include "renderer/gui/guisys/link/gui_item_link.h" +#include "renderer/gui/guisys/link/qtgui_checked_static_cast.h" +#include "renderer/gui/guisys/private/livereload/deferred_initial_constant_property_values.h" -namespace qtsdl { +namespace qtgui { /** - * Cleans a text from unneeded content like "qtsdl". + * Cleans a text from unneeded content like "qtgui". */ QString name_tidier(const char *name); @@ -43,17 +42,15 @@ class PersistentCoreHolderBase { virtual void adopt_shell(GuiItemLink *link) = 0; }; -template +template class PersistentCoreHolder : public PersistentCoreHolderBase { public: - PersistentCoreHolder(std::unique_ptr core) - : + PersistentCoreHolder(std::unique_ptr core) : PersistentCoreHolderBase{}, core{std::move(core)} { } - explicit PersistentCoreHolder() - : + explicit PersistentCoreHolder() : PersistentCoreHolderBase{} { } @@ -74,13 +71,13 @@ class GuiItemBase : public DeferredInitialConstantPropertyValues { friend class GuiLiveReloader; friend class GuiSubtreeImpl; - template + template friend class GuiItemMethods; - template + template friend class GuiItemCoreInstantiator; - template + template friend class GuiItemListModel; /** @@ -109,26 +106,18 @@ class GuiItemBase : public DeferredInitialConstantPropertyValues { virtual void on_core_adopted() { } - /** - * Set up signal to be able to run code in the game logic thread. - */ - void set_game_logic_callback(GuiCallback *game_logic_callback) { - this->game_logic_caller.set_game_logic_callback(game_logic_callback); - } - protected: - GameLogicCaller game_logic_caller; std::function()> instantiate_core_func; - std::function do_adopt_core_func; + std::function do_adopt_core_func; std::function on_core_adopted_func; }; -template +template class GuiItemOrigin { - template + template friend class GuiItemMethods; - template + template friend class GuiItemCoreInstantiator; /** @@ -140,10 +129,9 @@ class GuiItemOrigin { /** * Member function of the GuiPersistentItem */ -template +template class GuiItemMethods { public: - #ifndef NDEBUG virtual ~GuiItemMethods() { } @@ -152,12 +140,13 @@ class GuiItemMethods { /** * Get core. */ - template - U* get() const { - if (checked_static_cast(this)->holder->core) { - assert(checked_static_cast(checked_static_cast(this)->holder->core.get())); - return checked_static_cast(checked_static_cast(this)->holder->core.get()); - } else { + template + U *get() const { + if (checked_static_cast(this)->holder->core) { + assert(checked_static_cast(checked_static_cast(this)->holder->core.get())); + return checked_static_cast(checked_static_cast(this)->holder->core.get()); + } + else { return nullptr; } } @@ -165,12 +154,13 @@ class GuiItemMethods { /** * Get core. */ - template - U* get() { - if (checked_static_cast(this)->holder->core) { - assert(checked_static_cast(checked_static_cast(this)->holder->core.get())); - return checked_static_cast(checked_static_cast(this)->holder->core.get()); - } else { + template + U *get() { + if (checked_static_cast(this)->holder->core) { + assert(checked_static_cast(checked_static_cast(this)->holder->core.get())); + return checked_static_cast(checked_static_cast(this)->holder->core.get()); + } + else { return nullptr; } } @@ -178,21 +168,16 @@ class GuiItemMethods { /** * Invoke a function in the logic thread (the thread of the core of this object). */ - template - void i(F f, Args&& ... args) { - GuiItemBase *base = checked_static_cast(checked_static_cast(this)); - - emit base->game_logic_caller.in_game_logic_thread([=, this] { - static_assert_about_unwrapping(this))), decltype(unwrap_if_can(args))...>(); - f(unwrap(checked_static_cast(this)), unwrap_if_can(args)...); - }); + template + void i(F f, Args &&...args) { + GuiItemBase *base = checked_static_cast(checked_static_cast(this)); } protected: /** * Set property. */ - template + template void s(F f, P &p, A &&arg) { if (p != arg) { p = std::forward(arg); @@ -203,7 +188,7 @@ class GuiItemMethods { /** * Set property even if it's the same. */ - template + template void sf(F f, P &p, A &&arg) { p = std::forward(arg); this->assign_while_catching_constant_properies_inits(f, p); @@ -213,38 +198,31 @@ class GuiItemMethods { /** * Static QML properties assignments during the init phase are stored for a batch assignment at the end of the init phase. */ - template + template void assign_while_catching_constant_properies_inits(F f, A &&arg) { - GuiItemBase *base = checked_static_cast(checked_static_cast(this)); - - static_assert_about_unwrapping(this))), decltype(unwrap_if_can(arg))>(); + GuiItemBase *base = checked_static_cast(checked_static_cast(this)); - if (base->init_over) - emit base->game_logic_caller.in_game_logic_thread([=, this] {f(unwrap(checked_static_cast(this)), unwrap_if_can(arg));}); - else - base->static_properties_assignments.push_back([=, this] { - emit base->game_logic_caller.in_game_logic_thread([=, this] {f(unwrap(checked_static_cast(this)), unwrap_if_can(arg));}); - }); + static_assert_about_unwrapping(this))), decltype(unwrap_if_can(arg))>(); } }; -class GuiItemQObject : public QObject, public GuiItemBase, public GuiItemLink { +class GuiItemQObject : public QObject + , public GuiItemBase + , public GuiItemLink { Q_OBJECT public: - explicit GuiItemQObject(QObject *parent=nullptr); + explicit GuiItemQObject(QObject *parent = nullptr); }; -template +template class GuiItemCoreInstantiator : public GuiItemMethods { public: /** * Sets up a factory for the type T. */ - explicit GuiItemCoreInstantiator(GuiItemBase *item_base) - : + explicit GuiItemCoreInstantiator(GuiItemBase *item_base) : GuiItemMethods{} { - using namespace std::placeholders; item_base->instantiate_core_func = std::bind(&GuiItemCoreInstantiator::instantiate_core, this); @@ -256,11 +234,12 @@ class GuiItemCoreInstantiator : public GuiItemMethods { * Creates and returns a core object of type Unwrap::Type inside a holder. */ std::unique_ptr instantiate_core() { - T *origin = checked_static_cast(this); + T *origin = checked_static_cast(this); if (origin->holder) { return std::unique_ptr(); - } else { + } + else { auto core = std::make_unique::Type>(origin); return std::make_uniqueholder)>::type>(std::move(core)); } @@ -272,11 +251,12 @@ class GuiItemCoreInstantiator : public GuiItemMethods { void do_adopt_core(PersistentCoreHolderBase *holder, const QString &tag) { assert(holder); - T *origin = checked_static_cast(this); + T *origin = checked_static_cast(this); if (origin->holder) { qFatal("Error in QML code: GuiLiveReloader was asked to use same tag '%s' for multiple objects.", qUtf8Printable(tag)); - } else { + } + else { if (typeid(decltype(*origin->holder)) != typeid(*holder)) { qFatal( "Error in QML code: GuiLiveReloader was asked " @@ -284,17 +264,17 @@ class GuiItemCoreInstantiator : public GuiItemMethods { "using tag '%s'.", qUtf8Printable(name_tidier(typeid(decltype(*origin->holder)).name())), qUtf8Printable(name_tidier(typeid(*holder).name())), - qUtf8Printable(tag) - ); - } else { + qUtf8Printable(tag)); + } + else { origin->holder = checked_static_castholder)>(holder); origin->holder->adopt_shell(origin); } } } - GuiItemCoreInstantiator(const GuiItemCoreInstantiator&) = delete; - GuiItemCoreInstantiator& operator=(const GuiItemCoreInstantiator&) = delete; + GuiItemCoreInstantiator(const GuiItemCoreInstantiator &) = delete; + GuiItemCoreInstantiator &operator=(const GuiItemCoreInstantiator &) = delete; }; /** @@ -308,24 +288,24 @@ class GuiItemCoreInstantiator : public GuiItemMethods { * * @tparam T type of the concrete shell class (pass the descendant class) */ -template -class GuiItem : public GuiItemOrigin, public GuiItemCoreInstantiator { +template +class GuiItem : public GuiItemOrigin + , public GuiItemCoreInstantiator { public: /** * Creates an empty QObject shell for a core that is wrappable into a type *T. */ - explicit GuiItem(GuiItemBase *item_base) - : + explicit GuiItem(GuiItemBase *item_base) : GuiItemOrigin{}, GuiItemCoreInstantiator{item_base} { } }; -template -class GuiItemInterface : public GuiItemOrigin, public GuiItemMethods { +template +class GuiItemInterface : public GuiItemOrigin + , public GuiItemMethods { public: - explicit GuiItemInterface() - : + explicit GuiItemInterface() : GuiItemOrigin{}, GuiItemMethods{} { } @@ -335,16 +315,15 @@ namespace { class NullClass { NullClass() = delete; }; -} +} // namespace /** * Shadows inherited member functions. */ -template -class Shadow: public T { +template +class Shadow : public T { public: - Shadow(QObject *parent=nullptr) - : + Shadow(QObject *parent = nullptr) : T{parent} { } @@ -357,8 +336,9 @@ class Shadow: public T { /** * Switches the factory to production of the instances of the derived class. */ -template -class Inherits: public Shadow, public GuiItemCoreInstantiator

{ +template +class Inherits : public Shadow + , public GuiItemCoreInstantiator

{ public: using Shadow::get; using GuiItemCoreInstantiator

::get; @@ -369,8 +349,7 @@ class Inherits: public Shadow, public GuiItemCoreInstantiator

{ using Shadow::sf; using GuiItemCoreInstantiator

::sf; - Inherits(QObject *parent=nullptr) - : + Inherits(QObject *parent = nullptr) : Shadow{parent}, GuiItemCoreInstantiator

{this} { } @@ -379,4 +358,4 @@ class Inherits: public Shadow, public GuiItemCoreInstantiator

{ } }; -} // namespace qtsdl +} // namespace qtgui diff --git a/libopenage/gui/guisys/link/gui_item_link.h b/libopenage/renderer/gui/guisys/link/gui_item_link.h similarity index 62% rename from libopenage/gui/guisys/link/gui_item_link.h rename to libopenage/renderer/gui/guisys/link/gui_item_link.h index 43191ea2ec..0965aa2b4d 100644 --- a/libopenage/gui/guisys/link/gui_item_link.h +++ b/libopenage/renderer/gui/guisys/link/gui_item_link.h @@ -1,12 +1,14 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. +// Copyright 2015-2023 the openage authors. See copying.md for legal info. #pragma once #include +#include -#include "qtsdl_checked_static_cast.h" +#include "renderer/gui/guisys/link/qtgui_checked_static_cast.h" -namespace qtsdl { + +namespace qtgui { /** * Base for QObject wrapper of the domain-specific classes. @@ -22,30 +24,30 @@ class GuiItemLink { /** * If the core 'MyClass' has a shell 'MyClassLink' then 'Wrap' must have a 'using Type = MyClassLink' */ -template +template struct Wrap { }; /** * If the core 'MyClass' has a shell 'MyClassLink' then 'Unwrap' must have a 'using Type = MyClass' */ -template +template struct Unwrap { }; -template -typename Unwrap::Type* unwrap(T *t) { +template +typename Unwrap::Type *unwrap(T *t) { return t ? t->template get::Type>() : nullptr; } -template -const typename Unwrap::Type* unwrap(const T *t) { +template +const typename Unwrap::Type *unwrap(const T *t) { return t ? t->template get::Type>() : nullptr; } -template -const typename Wrap::Type* wrap(const U *u) { - return checked_static_cast::Type*>(u->gui_link); +template +const typename Wrap::Type *wrap(const U *u) { + return checked_static_cast::Type *>(u->gui_link); } /** @@ -53,33 +55,33 @@ const typename Wrap::Type* wrap(const U *u) { */ class GuiSingletonItem; -template -typename Wrap::Type* wrap(U *u, typename std::enable_if::Type>::value>::type* = nullptr) { - return u ? checked_static_cast::Type*>(u->gui_link) : nullptr; +template +typename Wrap::Type *wrap(U *u, typename std::enable_if::Type>::value>::type * = nullptr) { + return u ? checked_static_cast::Type *>(u->gui_link) : nullptr; } -template -typename Wrap::Type* wrap(U *u, typename std::enable_if::Type>::value>::type* = nullptr) { - return u ? checked_static_cast::Type*>(u->gui_link) : nullptr; +template +typename Wrap::Type *wrap(U *u, typename std::enable_if::Type>::value>::type * = nullptr) { + return u ? checked_static_cast::Type *>(u->gui_link) : nullptr; } -template -constexpr P&& wrap_if_can(typename std::remove_reference

::type&& p) noexcept { +template +constexpr P &&wrap_if_can(typename std::remove_reference

::type &&p) noexcept { return std::forward

(p); } -template +template T wrap_if_can(typename Unwrap::type>::Type *u) { return wrap(u); } -template -P unwrap_if_can(P& p) { +template +P unwrap_if_can(P &p) { return p; } -template::Type* = nullptr> -typename Unwrap::Type* unwrap_if_can(T *t) { +template ::Type * = nullptr> +typename Unwrap::Type *unwrap_if_can(T *t) { return unwrap(t); } @@ -87,10 +89,10 @@ typename Unwrap::Type* unwrap_if_can(T *t) { * Checking that callable can be called with given argument types. */ struct can_call_test { - template + template static decltype(std::declval()(std::declval()...), std::true_type()) f(int); - template + template static std::false_type f(...); }; @@ -100,7 +102,7 @@ struct can_call_test { * @tparam F callable * @tparam A arguments to test against the callable */ -template +template struct can_call : decltype(can_call_test::f(0)) { }; @@ -113,9 +115,9 @@ struct can_call : decltype(can_call_test::f(0)) { * @tparam F callable * @tparam A arguments to test against the callable */ -template +template constexpr void static_assert_about_unwrapping() { static_assert(can_call{}, "One of possible causes: if you're passing SomethingLink*, then don't forget to #include \"something_link.h\"."); } -} // namespace qtsdl +} // namespace qtgui diff --git a/libopenage/gui/guisys/link/gui_item_list_model.h b/libopenage/renderer/gui/guisys/link/gui_item_list_model.h similarity index 69% rename from libopenage/gui/guisys/link/gui_item_list_model.h rename to libopenage/renderer/gui/guisys/link/gui_item_list_model.h index 38e902d4d3..ccb3d34ea7 100644 --- a/libopenage/gui/guisys/link/gui_item_list_model.h +++ b/libopenage/renderer/gui/guisys/link/gui_item_list_model.h @@ -1,27 +1,26 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once #include -#include "gui_item.h" -#include "gui_property_map_impl.h" -#include "gui_list_model.h" +#include "renderer/gui/guisys/link/gui_item.h" +#include "renderer/gui/guisys/link/gui_list_model.h" +#include "renderer/gui/guisys/link/gui_property_map_impl.h" -namespace qtsdl { + +namespace qtgui { /** * A shell object fir cores inherited from GuiPropertyMap * * Core can use a key-value interface while in QML it looks like a ListModel. */ -template +template class GuiItemListModel : public GuiItem { public: - explicit GuiItemListModel(GuiItemBase *item_base) - : + explicit GuiItemListModel(GuiItemBase *item_base) : GuiItem{item_base} { - item_base->on_core_adopted_func = std::bind(&GuiItemListModel::on_core_adopted, this); } @@ -33,9 +32,9 @@ class GuiItemListModel : public GuiItem { } void establish_from_gui_propagation() { - auto _this = checked_static_cast(this); + auto _this = checked_static_cast(this); - QObject::connect(_this, &GuiListModel::changed_from_gui, [_this] (const QByteArray &name, const QVariant &value) { + QObject::connect(_this, &GuiListModel::changed_from_gui, [_this](const QByteArray &name, const QVariant &value) { emit _this->game_logic_caller.in_game_logic_thread_blocking([&] { unwrap(_this)->impl->setProperty(name, value); }); @@ -43,14 +42,14 @@ class GuiItemListModel : public GuiItem { } void establish_to_gui_propagation() { - auto _this = checked_static_cast(this); + auto _this = checked_static_cast(this); auto properties = unwrap(_this)->impl.get(); QObject::connect(properties, &GuiPropertyMapImpl::property_changed, _this, &GuiListModel::on_property_changed); } void do_initial_to_gui_propagation() { - auto _this = checked_static_cast(this); + auto _this = checked_static_cast(this); auto properties = unwrap(_this)->impl.get(); auto property_names = properties->dynamicPropertyNames(); @@ -58,7 +57,7 @@ class GuiItemListModel : public GuiItem { std::vector> values; values.reserve(property_names.size()); - std::transform(std::begin(property_names), std::end(property_names), std::back_inserter(values), [properties] (const QByteArray &name) { + std::transform(std::begin(property_names), std::end(property_names), std::back_inserter(values), [properties](const QByteArray &name) { return std::make_tuple(name, properties->property(name)); }); @@ -66,4 +65,4 @@ class GuiItemListModel : public GuiItem { } }; -} // namespace qtsdl +} // namespace qtgui diff --git a/libopenage/gui/guisys/link/gui_list_model.cpp b/libopenage/renderer/gui/guisys/link/gui_list_model.cpp similarity index 85% rename from libopenage/gui/guisys/link/gui_list_model.cpp rename to libopenage/renderer/gui/guisys/link/gui_list_model.cpp index 72bbf20663..467468020b 100644 --- a/libopenage/gui/guisys/link/gui_list_model.cpp +++ b/libopenage/renderer/gui/guisys/link/gui_list_model.cpp @@ -1,13 +1,13 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. +// Copyright 2015-2023 the openage authors. See copying.md for legal info. #include "gui_list_model.h" #include -namespace qtsdl { -GuiListModel::GuiListModel(QObject *parent) - : +namespace qtgui { + +GuiListModel::GuiListModel(QObject *parent) : QAbstractListModel{parent} { } @@ -20,7 +20,7 @@ void GuiListModel::set(const std::vector> &valu } void GuiListModel::on_property_changed(const QByteArray &name, const QVariant &value) { - auto foundIt = std::find_if(std::begin(this->values), std::end(this->values), [&name] (std::tuple& p) { + auto foundIt = std::find_if(std::begin(this->values), std::end(this->values), [&name](std::tuple &p) { return std::get(p) == name; }); @@ -33,14 +33,15 @@ void GuiListModel::on_property_changed(const QByteArray &name, const QVariant &v auto i = this->index(std::distance(std::begin(this->values), foundIt)); emit this->dataChanged(i, i, {Qt::EditRole}); } - } else { + } + else { this->beginInsertRows(QModelIndex(), this->values.size(), this->values.size()); this->values.emplace(std::end(this->values), name, value); this->endInsertRows(); } } -int GuiListModel::rowCount(const QModelIndex&) const { +int GuiListModel::rowCount(const QModelIndex &) const { return values.size(); } @@ -71,4 +72,4 @@ bool GuiListModel::setData(const QModelIndex &index, const QVariant &value, int return false; } -} // namespace qtsdl +} // namespace qtgui diff --git a/libopenage/gui/guisys/link/gui_list_model.h b/libopenage/renderer/gui/guisys/link/gui_list_model.h similarity index 58% rename from libopenage/gui/guisys/link/gui_list_model.h rename to libopenage/renderer/gui/guisys/link/gui_list_model.h index 6f93ce48b2..3a3b85c52e 100644 --- a/libopenage/gui/guisys/link/gui_list_model.h +++ b/libopenage/renderer/gui/guisys/link/gui_list_model.h @@ -1,24 +1,27 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. +// Copyright 2015-2023 the openage authors. See copying.md for legal info. #pragma once -#include #include +#include #include -#include "gui_item.h" +#include "renderer/gui/guisys/link/gui_item.h" + -namespace qtsdl { +namespace qtgui { /** * Adapts core that uses a property map to QAbstractListModel interface. */ -class GuiListModel : public QAbstractListModel, public GuiItemBase, public GuiItemLink { +class GuiListModel : public QAbstractListModel + , public GuiItemBase + , public GuiItemLink { Q_OBJECT public: - GuiListModel(QObject *parent=nullptr); + GuiListModel(QObject *parent = nullptr); virtual ~GuiListModel(); void set(const std::vector> &values); @@ -30,12 +33,12 @@ public slots: void changed_from_gui(const char *name, const QVariant &value); private: - virtual int rowCount(const QModelIndex&) const override; + virtual int rowCount(const QModelIndex &) const override; virtual Qt::ItemFlags flags(const QModelIndex &index) const override; - virtual QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override; - virtual bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole) override; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; std::vector> values; }; -} // namespace qtsdl +} // namespace qtgui diff --git a/libopenage/gui/guisys/link/gui_property_map_impl.cpp b/libopenage/renderer/gui/guisys/link/gui_property_map_impl.cpp similarity index 66% rename from libopenage/gui/guisys/link/gui_property_map_impl.cpp rename to libopenage/renderer/gui/guisys/link/gui_property_map_impl.cpp index 885589c38b..d6204ae719 100644 --- a/libopenage/gui/guisys/link/gui_property_map_impl.cpp +++ b/libopenage/renderer/gui/guisys/link/gui_property_map_impl.cpp @@ -1,16 +1,16 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. +// Copyright 2015-2023 the openage authors. See copying.md for legal info. #include "gui_property_map_impl.h" #include #include -#include "qtsdl_checked_static_cast.h" +#include "renderer/gui/guisys/link/qtgui_checked_static_cast.h" -namespace qtsdl { -GuiPropertyMapImpl::GuiPropertyMapImpl() - : +namespace qtgui { + +GuiPropertyMapImpl::GuiPropertyMapImpl() : QObject{} { } @@ -18,7 +18,7 @@ GuiPropertyMapImpl::~GuiPropertyMapImpl() = default; bool GuiPropertyMapImpl::event(QEvent *e) { if (e->type() == QEvent::DynamicPropertyChange) { - auto property_name = checked_static_cast(e)->propertyName(); + auto property_name = checked_static_cast(e)->propertyName(); emit this->property_changed(property_name, this->property(property_name)); return true; } @@ -26,4 +26,4 @@ bool GuiPropertyMapImpl::event(QEvent *e) { return this->QObject::event(e); } -} // namespace qtsdl +} // namespace qtgui diff --git a/libopenage/gui/guisys/link/gui_property_map_impl.h b/libopenage/renderer/gui/guisys/link/gui_property_map_impl.h similarity index 74% rename from libopenage/gui/guisys/link/gui_property_map_impl.h rename to libopenage/renderer/gui/guisys/link/gui_property_map_impl.h index a7054345be..d0118f4c58 100644 --- a/libopenage/gui/guisys/link/gui_property_map_impl.h +++ b/libopenage/renderer/gui/guisys/link/gui_property_map_impl.h @@ -1,10 +1,11 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. +// Copyright 2015-2023 the openage authors. See copying.md for legal info. #pragma once #include -namespace qtsdl { + +namespace qtgui { class GuiPropertyMapImpl : public QObject { Q_OBJECT @@ -20,4 +21,4 @@ class GuiPropertyMapImpl : public QObject { virtual bool event(QEvent *e) override; }; -} // namespace qtsdl +} // namespace qtgui diff --git a/libopenage/renderer/gui/guisys/link/gui_singleton_item.cpp b/libopenage/renderer/gui/guisys/link/gui_singleton_item.cpp new file mode 100644 index 0000000000..dc01d27e4e --- /dev/null +++ b/libopenage/renderer/gui/guisys/link/gui_singleton_item.cpp @@ -0,0 +1,15 @@ +// Copyright 2015-2023 the openage authors. See copying.md for legal info. + +#include "gui_singleton_item.h" + + +namespace qtgui { + +GuiSingletonItem::GuiSingletonItem(QObject *parent) : + QObject{parent}, + GuiItemLink{} { +} + +GuiSingletonItem::~GuiSingletonItem() = default; + +} // namespace qtgui diff --git a/libopenage/renderer/gui/guisys/link/gui_singleton_item.h b/libopenage/renderer/gui/guisys/link/gui_singleton_item.h new file mode 100644 index 0000000000..8024ecf032 --- /dev/null +++ b/libopenage/renderer/gui/guisys/link/gui_singleton_item.h @@ -0,0 +1,21 @@ +// Copyright 2015-2023 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include "renderer/gui/guisys/link/gui_item_link.h" + + +namespace qtgui { + +class GuiSingletonItem : public QObject + , public GuiItemLink { + Q_OBJECT + +public: + explicit GuiSingletonItem(QObject *parent = nullptr); + virtual ~GuiSingletonItem(); +}; + +} // namespace qtgui diff --git a/libopenage/renderer/gui/guisys/link/qtgui_checked_static_cast.h b/libopenage/renderer/gui/guisys/link/qtgui_checked_static_cast.h new file mode 100644 index 0000000000..7b51a89e03 --- /dev/null +++ b/libopenage/renderer/gui/guisys/link/qtgui_checked_static_cast.h @@ -0,0 +1,16 @@ +// Copyright 2015-2023 the openage authors. See copying.md for legal info. + +#pragma once + +#include + + +namespace qtgui { + +template +T checked_static_cast(U *u) { + assert(dynamic_cast(u)); + return static_cast(u); +} + +} // namespace qtgui diff --git a/libopenage/renderer/gui/guisys/private/gui_application_impl.cpp b/libopenage/renderer/gui/guisys/private/gui_application_impl.cpp index c0ef0dd1b7..fb8674b69e 100644 --- a/libopenage/renderer/gui/guisys/private/gui_application_impl.cpp +++ b/libopenage/renderer/gui/guisys/private/gui_application_impl.cpp @@ -2,12 +2,12 @@ #include "gui_application_impl.h" -#include #include +#include #include -#include #include +#include namespace qtgui { @@ -41,22 +41,20 @@ void GuiApplicationImpl::processEvents() { } namespace { - int argc = 1; - char arg[] = "qtsdl"; - char *argv = &arg[0]; -} +int argc = 1; +char arg[] = "qtgui"; +char *argv = &arg[0]; +} // namespace -GuiApplicationImpl::GuiApplicationImpl() - : +GuiApplicationImpl::GuiApplicationImpl() : #ifndef NDEBUG owner{std::this_thread::get_id()}, #endif - app{argc, &argv} -{ + app{argc, &argv} { // Set locale back to POSIX for the decimal point parsing (see qcoreapplication.html#locale-settings). std::locale::global(std::locale().combine>(std::locale::classic())); qInfo() << "Compiled with Qt" << QT_VERSION_STR << "and run with Qt" << qVersion(); } -} // namespace qtsdl +} // namespace qtgui diff --git a/libopenage/renderer/gui/guisys/private/gui_application_impl.h b/libopenage/renderer/gui/guisys/private/gui_application_impl.h index 7294cf8827..7af12c0fa4 100644 --- a/libopenage/renderer/gui/guisys/private/gui_application_impl.h +++ b/libopenage/renderer/gui/guisys/private/gui_application_impl.h @@ -1,9 +1,9 @@ -// Copyright 2015-2022 the openage authors. See copying.md for legal info. +// Copyright 2015-2023 the openage authors. See copying.md for legal info. #pragma once -#include #include +#include #include @@ -12,7 +12,7 @@ namespace qtgui { /** * Houses gui logic event queue. * - * To launch it in a dedicated thread, use qtsdl::GuiDedicatedThread instead. + * To launch it in a dedicated thread, use qtgui::GuiDedicatedThread instead. */ class GuiApplicationImpl { public: @@ -25,8 +25,8 @@ class GuiApplicationImpl { private: GuiApplicationImpl(); - GuiApplicationImpl(const GuiApplicationImpl&) = delete; - GuiApplicationImpl& operator=(const GuiApplicationImpl&) = delete; + GuiApplicationImpl(const GuiApplicationImpl &) = delete; + GuiApplicationImpl &operator=(const GuiApplicationImpl &) = delete; #ifndef NDEBUG const std::thread::id owner; diff --git a/libopenage/renderer/gui/guisys/private/gui_ctx_setup.cpp b/libopenage/renderer/gui/guisys/private/gui_ctx_setup.cpp index d5c32b56e4..49b05af242 100644 --- a/libopenage/renderer/gui/guisys/private/gui_ctx_setup.cpp +++ b/libopenage/renderer/gui/guisys/private/gui_ctx_setup.cpp @@ -4,10 +4,11 @@ #include +#include #include #include +#include -#include "gui/guisys/private/platforms/context_extraction.h" #include "renderer/gui/guisys/private/opengl_debug_logger.h" #include "renderer/opengl/context.h" #include "renderer/opengl/window.h" diff --git a/libopenage/renderer/gui/guisys/private/gui_engine_impl.h b/libopenage/renderer/gui/guisys/private/gui_engine_impl.h index bee83ef4ea..019a9c7a1f 100644 --- a/libopenage/renderer/gui/guisys/private/gui_engine_impl.h +++ b/libopenage/renderer/gui/guisys/private/gui_engine_impl.h @@ -1,4 +1,4 @@ -// Copyright 2015-2022 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once @@ -37,8 +37,8 @@ class GuiQmlEngineImpl : public QObject { static GuiQmlEngineImpl *impl(GuiQmlEngine *engine); /** - * Get the underlying QQmlEngine object. - */ + * Get the underlying QQmlEngine object. + */ std::shared_ptr get_qml_engine(); /** diff --git a/libopenage/gui/guisys/private/livereload/deferred_initial_constant_property_values.cpp b/libopenage/renderer/gui/guisys/private/livereload/deferred_initial_constant_property_values.cpp similarity index 84% rename from libopenage/gui/guisys/private/livereload/deferred_initial_constant_property_values.cpp rename to libopenage/renderer/gui/guisys/private/livereload/deferred_initial_constant_property_values.cpp index 932ca20791..f17f7f2a0e 100644 --- a/libopenage/gui/guisys/private/livereload/deferred_initial_constant_property_values.cpp +++ b/libopenage/renderer/gui/guisys/private/livereload/deferred_initial_constant_property_values.cpp @@ -1,11 +1,10 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. +// Copyright 2015-2023 the openage authors. See copying.md for legal info. #include "deferred_initial_constant_property_values.h" -namespace qtsdl { +namespace qtgui { -DeferredInitialConstantPropertyValues::DeferredInitialConstantPropertyValues() - : +DeferredInitialConstantPropertyValues::DeferredInitialConstantPropertyValues() : init_over{} { } @@ -27,4 +26,4 @@ bool DeferredInitialConstantPropertyValues::is_init_over() const { return this->init_over; } -} // namespace qtsdl +} // namespace qtgui diff --git a/libopenage/gui/guisys/private/livereload/deferred_initial_constant_property_values.h b/libopenage/renderer/gui/guisys/private/livereload/deferred_initial_constant_property_values.h similarity index 88% rename from libopenage/gui/guisys/private/livereload/deferred_initial_constant_property_values.h rename to libopenage/renderer/gui/guisys/private/livereload/deferred_initial_constant_property_values.h index 699c9ed64c..1add8b41e1 100644 --- a/libopenage/gui/guisys/private/livereload/deferred_initial_constant_property_values.h +++ b/libopenage/renderer/gui/guisys/private/livereload/deferred_initial_constant_property_values.h @@ -1,11 +1,11 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. +// Copyright 2015-2023 the openage authors. See copying.md for legal info. #pragma once -#include #include +#include -namespace qtsdl { +namespace qtgui { /** * Stores static properties during initialization to be able to assign them after all 'liveReloadTag' properties are set. @@ -36,4 +36,4 @@ class DeferredInitialConstantPropertyValues { std::vector> static_properties_assignments; }; -} // namespace qtsdl +} // namespace qtgui diff --git a/libopenage/gui/guisys/private/livereload/gui_live_reloader.cpp b/libopenage/renderer/gui/guisys/private/livereload/gui_live_reloader.cpp similarity index 98% rename from libopenage/gui/guisys/private/livereload/gui_live_reloader.cpp rename to libopenage/renderer/gui/guisys/private/livereload/gui_live_reloader.cpp index 474de95dc9..142ec7892e 100644 --- a/libopenage/gui/guisys/private/livereload/gui_live_reloader.cpp +++ b/libopenage/renderer/gui/guisys/private/livereload/gui_live_reloader.cpp @@ -8,7 +8,7 @@ #include "../../link/gui_item.h" #include "deferred_initial_constant_property_values.h" -namespace qtsdl { +namespace qtgui { namespace { const int registration = qmlRegisterUncreatableType("yay.sfttech.livereload", 1, 0, "LR", "LR is non-instantiable. It provides the 'LR.tag' attached property."); @@ -88,4 +88,4 @@ void GuiLiveReloader::init_persistent_items(const QList -#include #include +#include +#include -#include #include #include +#include #include #include // since qt 5.14, the std::hash of q* types are included in qt #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) namespace std { -template<> +template <> struct hash { - size_t operator()(const QString& val) const noexcept { + size_t operator()(const QString &val) const noexcept { return qHash(val); } }; -} +} // namespace std #endif -namespace qtsdl { +namespace qtgui { class GuiItemBase; @@ -39,7 +39,7 @@ class GuiLiveReloaderAttachedProperty : public QObject { public: explicit GuiLiveReloaderAttachedProperty(QObject *object); - GuiItemBase* get_attachee() const; + GuiItemBase *get_attachee() const; QString get_tag() const; void set_tag(const QString &tag); @@ -60,7 +60,7 @@ class GuiLiveReloaderAttachedPropertyProvider : public QObject { Q_OBJECT public: - static GuiLiveReloaderAttachedProperty* qmlAttachedProperties(QObject*); + static GuiLiveReloaderAttachedProperty *qmlAttachedProperties(QObject *); }; class PersistentCoreHolderBase; @@ -69,15 +69,14 @@ class PersistentCoreHolderBase; * Stores objects that need to be kept alive across GUI reloads. */ class GuiLiveReloader { - public: - void init_persistent_items(const QList &items); + void init_persistent_items(const QList &items); private: using TagToPreservableMap = std::unordered_map>; TagToPreservableMap preservable; }; -} // namespace qtsdl +} // namespace qtgui -QML_DECLARE_TYPEINFO(qtsdl::GuiLiveReloaderAttachedPropertyProvider, QML_HAS_ATTACHED_PROPERTIES) +QML_DECLARE_TYPEINFO(qtgui::GuiLiveReloaderAttachedPropertyProvider, QML_HAS_ATTACHED_PROPERTIES) diff --git a/libopenage/renderer/gui/guisys/private/opengl_debug_logger.cpp b/libopenage/renderer/gui/guisys/private/opengl_debug_logger.cpp index 61025528a6..0f565efaae 100644 --- a/libopenage/renderer/gui/guisys/private/opengl_debug_logger.cpp +++ b/libopenage/renderer/gui/guisys/private/opengl_debug_logger.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #include "opengl_debug_logger.h" @@ -7,11 +7,11 @@ #include #ifdef __APPLE__ -// from https://www.khronos.org/registry/OpenGL/api/GL/glext.h -#define GL_DEBUG_CALLBACK_FUNCTION 0x8244 -#define GL_DEBUG_OUTPUT_SYNCHRONOUS 0x8242 -#define GL_DEBUG_TYPE_ERROR 0x824C -#define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR 0x824E + // from https://www.khronos.org/registry/OpenGL/api/GL/glext.h + #define GL_DEBUG_CALLBACK_FUNCTION 0x8244 + #define GL_DEBUG_OUTPUT_SYNCHRONOUS 0x8242 + #define GL_DEBUG_TYPE_ERROR 0x824C + #define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR 0x824E #endif namespace qtgui { diff --git a/libopenage/renderer/gui/guisys/private/opengl_debug_logger.h b/libopenage/renderer/gui/guisys/private/opengl_debug_logger.h index f2763c96e7..3c297e197f 100644 --- a/libopenage/renderer/gui/guisys/private/opengl_debug_logger.h +++ b/libopenage/renderer/gui/guisys/private/opengl_debug_logger.h @@ -40,4 +40,4 @@ gl_debug_parameters get_current_opengl_debug_parameters(QOpenGLContext ¤t_ */ void apply_opengl_debug_parameters(gl_debug_parameters params, QOpenGLContext ¤t_dest_context); -} // namespace qtsdl +} // namespace qtgui diff --git a/libopenage/renderer/gui/guisys/public/gui_application.cpp b/libopenage/renderer/gui/guisys/public/gui_application.cpp index 08a93aa42d..cd07289f87 100644 --- a/libopenage/renderer/gui/guisys/public/gui_application.cpp +++ b/libopenage/renderer/gui/guisys/public/gui_application.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2022 the openage authors. See copying.md for legal info. +// Copyright 2015-2023 the openage authors. See copying.md for legal info. #include @@ -8,13 +8,11 @@ namespace qtgui { -GuiApplication::GuiApplication() - : +GuiApplication::GuiApplication() : application{GuiApplicationImpl::get()} { } -GuiApplication::GuiApplication(std::shared_ptr application) - : +GuiApplication::GuiApplication(std::shared_ptr application) : application{application} { } @@ -24,4 +22,4 @@ void GuiApplication::process_events() { this->application->processEvents(); } -} // namespace qtsdl +} // namespace qtgui diff --git a/libopenage/renderer/gui/integration/private/CMakeLists.txt b/libopenage/renderer/gui/integration/private/CMakeLists.txt index 1ef1a3946a..3457b62e3a 100644 --- a/libopenage/renderer/gui/integration/private/CMakeLists.txt +++ b/libopenage/renderer/gui/integration/private/CMakeLists.txt @@ -1,3 +1,8 @@ add_sources(libopenage - gui_log.cpp + gui_filled_texture_handles.cpp + gui_log.cpp + gui_make_standalone_subtexture.cpp + gui_standalone_subtexture.cpp + gui_texture.cpp + gui_texture_handle.cpp ) diff --git a/libopenage/gui/integration/private/gui_filled_texture_handles.cpp b/libopenage/renderer/gui/integration/private/gui_filled_texture_handles.cpp similarity index 70% rename from libopenage/gui/integration/private/gui_filled_texture_handles.cpp rename to libopenage/renderer/gui/integration/private/gui_filled_texture_handles.cpp index 20df907316..db664516b4 100644 --- a/libopenage/gui/integration/private/gui_filled_texture_handles.cpp +++ b/libopenage/renderer/gui/integration/private/gui_filled_texture_handles.cpp @@ -1,12 +1,13 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. +// Copyright 2015-2023 the openage authors. See copying.md for legal info. #include "gui_filled_texture_handles.h" #include -#include "gui_texture_handle.h" +#include "renderer/gui/integration/private/gui_texture_handle.h" -namespace openage::gui { + +namespace openage::renderer::gui { GuiFilledTextureHandles::GuiFilledTextureHandles() = default; @@ -19,39 +20,38 @@ void GuiFilledTextureHandles::add_texture_handle(const QString &id, const QSize void GuiFilledTextureHandles::free_texture_handle(SizedTextureHandle *filled_handle) { std::unique_lock lck{this->handles_mutex}; - this->handles.erase(std::remove_if(std::begin(this->handles), std::end(this->handles), [filled_handle] (const std::tuple& handle) { - return std::get(handle) == filled_handle; - }), std::end(this->handles)); + this->handles.erase(std::remove_if(std::begin(this->handles), std::end(this->handles), [filled_handle](const std::tuple &handle) { + return std::get(handle) == filled_handle; + }), + std::end(this->handles)); } void GuiFilledTextureHandles::fill_all_handles_with_texture(const TextureHandle &texture) { std::unique_lock lck{this->handles_mutex}; - std::for_each(std::begin(this->handles), std::end(this->handles), [&texture] (std::tuple& handle) { - auto filled_handle = std::get(handle); + std::for_each(std::begin(this->handles), std::end(this->handles), [&texture](std::tuple &handle) { + auto filled_handle = std::get(handle); *filled_handle = {texture, textureSize(*filled_handle)}; }); } -void GuiFilledTextureHandles::refresh_all_handles_with_texture(const std::function& refresher) { +void GuiFilledTextureHandles::refresh_all_handles_with_texture(const std::function &refresher) { std::unique_lock lck{this->handles_mutex}; std::vector refreshed_handles(this->handles.size()); - std::transform(std::begin(this->handles), std::end(this->handles), std::begin(refreshed_handles), [refresher] (const std::tuple& handle) { + std::transform(std::begin(this->handles), std::end(this->handles), std::begin(refreshed_handles), [refresher](const std::tuple &handle) { SizedTextureHandle refreshed_handles; refresher(std::get(handle), std::get(handle), &refreshed_handles); return refreshed_handles; }); for (std::size_t i = 0, e = refreshed_handles.size(); i != e; ++i) - *std::get(this->handles[i]) = refreshed_handles[i]; + *std::get(this->handles[i]) = refreshed_handles[i]; } -GuiFilledTextureHandleUser::GuiFilledTextureHandleUser(std::shared_ptr texture_handles, const QString &id, const QSize &requested_size, SizedTextureHandle *filled_handle) - : +GuiFilledTextureHandleUser::GuiFilledTextureHandleUser(std::shared_ptr texture_handles, const QString &id, const QSize &requested_size, SizedTextureHandle *filled_handle) : texture_handles{std::move(texture_handles)}, filled_handle{filled_handle} { - this->texture_handles->add_texture_handle(id, requested_size, filled_handle); } @@ -60,4 +60,4 @@ GuiFilledTextureHandleUser::~GuiFilledTextureHandleUser() { texture_handles->free_texture_handle(filled_handle); } -} // namespace openage::gui +} // namespace openage::renderer::gui diff --git a/libopenage/gui/integration/private/gui_filled_texture_handles.h b/libopenage/renderer/gui/integration/private/gui_filled_texture_handles.h similarity index 74% rename from libopenage/gui/integration/private/gui_filled_texture_handles.h rename to libopenage/renderer/gui/integration/private/gui_filled_texture_handles.h index 8d88a7d688..afca15e1c2 100644 --- a/libopenage/gui/integration/private/gui_filled_texture_handles.h +++ b/libopenage/renderer/gui/integration/private/gui_filled_texture_handles.h @@ -1,17 +1,16 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. +// Copyright 2015-2023 the openage authors. See copying.md for legal info. #pragma once -#include -#include -#include #include +#include +#include +#include -#include #include +#include -namespace openage { -namespace gui { +namespace openage::renderer::gui { class TextureHandle; class SizedTextureHandle; @@ -31,7 +30,7 @@ class GuiFilledTextureHandles { void free_texture_handle(SizedTextureHandle *filled_handle); void fill_all_handles_with_texture(const TextureHandle &texture); - void refresh_all_handles_with_texture(const std::function& refresher); + void refresh_all_handles_with_texture(const std::function &refresher); private: /** @@ -40,7 +39,7 @@ class GuiFilledTextureHandles { * It's not a proper Qt usage, so the live reload of the game assets for the gui may break in future Qt releases. * When it breaks, this feature should be implemented via the recreation of the qml engine. */ - std::vector> handles; + std::vector> handles; std::mutex handles_mutex; }; @@ -49,14 +48,14 @@ class GuiFilledTextureHandleUser { GuiFilledTextureHandleUser(std::shared_ptr texture_handles, const QString &id, const QSize &requested_size, SizedTextureHandle *filled_handle); ~GuiFilledTextureHandleUser(); - GuiFilledTextureHandleUser(GuiFilledTextureHandleUser&&) noexcept = default; + GuiFilledTextureHandleUser(GuiFilledTextureHandleUser &&) noexcept = default; private: - GuiFilledTextureHandleUser(const GuiFilledTextureHandleUser&) = delete; - GuiFilledTextureHandleUser& operator=(const GuiFilledTextureHandleUser&) = delete; + GuiFilledTextureHandleUser(const GuiFilledTextureHandleUser &) = delete; + GuiFilledTextureHandleUser &operator=(const GuiFilledTextureHandleUser &) = delete; std::shared_ptr texture_handles; SizedTextureHandle *filled_handle; }; -}} // namespace openage::gui +} // namespace openage::renderer::gui diff --git a/libopenage/renderer/gui/integration/private/gui_make_standalone_subtexture.cpp b/libopenage/renderer/gui/integration/private/gui_make_standalone_subtexture.cpp new file mode 100644 index 0000000000..5151a3934a --- /dev/null +++ b/libopenage/renderer/gui/integration/private/gui_make_standalone_subtexture.cpp @@ -0,0 +1,14 @@ +// Copyright 2015-2023 the openage authors. See copying.md for legal info. + +#include "gui_make_standalone_subtexture.h" + +#include "renderer/gui/integration/private/gui_standalone_subtexture.h" + + +namespace openage::renderer::gui { + +std::unique_ptr make_standalone_subtexture(GLuint id, const QSize &size) { + return std::make_unique(id, size); +} + +} // namespace openage::renderer::gui diff --git a/libopenage/gui/integration/private/gui_make_standalone_subtexture.h b/libopenage/renderer/gui/integration/private/gui_make_standalone_subtexture.h similarity index 87% rename from libopenage/gui/integration/private/gui_make_standalone_subtexture.h rename to libopenage/renderer/gui/integration/private/gui_make_standalone_subtexture.h index e971f478c1..1a74659582 100644 --- a/libopenage/gui/integration/private/gui_make_standalone_subtexture.h +++ b/libopenage/renderer/gui/integration/private/gui_make_standalone_subtexture.h @@ -8,8 +8,7 @@ #include -namespace openage { -namespace gui { +namespace openage::renderer::gui { /* * Reason for this is to resolve epoxy header clash that occur @@ -20,5 +19,4 @@ namespace gui { */ std::unique_ptr make_standalone_subtexture(GLuint id, const QSize &size); -} // namespace gui -} // namespace openage +} // namespace openage::renderer::gui diff --git a/libopenage/gui/integration/private/gui_standalone_subtexture.cpp b/libopenage/renderer/gui/integration/private/gui_standalone_subtexture.cpp similarity index 91% rename from libopenage/gui/integration/private/gui_standalone_subtexture.cpp rename to libopenage/renderer/gui/integration/private/gui_standalone_subtexture.cpp index 3f55d77bc3..63ca492169 100644 --- a/libopenage/gui/integration/private/gui_standalone_subtexture.cpp +++ b/libopenage/renderer/gui/integration/private/gui_standalone_subtexture.cpp @@ -2,8 +2,7 @@ #include "gui_standalone_subtexture.h" -namespace openage { -namespace gui { +namespace openage::renderer::gui { GuiStandaloneSubtexture::GuiStandaloneSubtexture(GLuint id, const QSize &size) : id(id), @@ -43,5 +42,4 @@ QSize GuiStandaloneSubtexture::textureSize() const { return this->size; } -} // namespace gui -} // namespace openage +} // namespace openage::renderer::gui diff --git a/libopenage/gui/integration/private/gui_standalone_subtexture.h b/libopenage/renderer/gui/integration/private/gui_standalone_subtexture.h similarity index 89% rename from libopenage/gui/integration/private/gui_standalone_subtexture.h rename to libopenage/renderer/gui/integration/private/gui_standalone_subtexture.h index 3512d2d066..462e82f370 100644 --- a/libopenage/gui/integration/private/gui_standalone_subtexture.h +++ b/libopenage/renderer/gui/integration/private/gui_standalone_subtexture.h @@ -5,8 +5,7 @@ #include #include -namespace openage { -namespace gui { +namespace openage::renderer::gui { class GuiStandaloneSubtexture : public QSGTexture { Q_OBJECT @@ -30,5 +29,4 @@ class GuiStandaloneSubtexture : public QSGTexture { const QSize size; }; -} // namespace gui -} // namespace openage +} // namespace openage::renderer::gui diff --git a/libopenage/renderer/gui/integration/private/gui_texture.cpp b/libopenage/renderer/gui/integration/private/gui_texture.cpp new file mode 100644 index 0000000000..daa6f4020a --- /dev/null +++ b/libopenage/renderer/gui/integration/private/gui_texture.cpp @@ -0,0 +1,62 @@ +// Copyright 2015-2023 the openage authors. See copying.md for legal info. + +#include + +#include + +#include "renderer/gui/integration/private/gui_make_standalone_subtexture.h" +#include "renderer/gui/integration/private/gui_texture.h" + + +namespace openage::renderer::gui { + +GuiTexture::GuiTexture(const SizedTextureHandle &texture_handle) : + QSGTexture{}, + texture_handle(texture_handle) { +} + +GuiTexture::~GuiTexture() = default; + +void GuiTexture::bind() { + glBindTexture(GL_TEXTURE_2D, this->textureId()); +} + +qint64 GuiTexture::comparisonKey() const { + // TODO: Qt5 What does this do?????? + return 0; +} + +bool GuiTexture::hasAlphaChannel() const { + // assume 32bit textures + return true; +} + +bool GuiTexture::hasMipmaps() const { + return false; +} + +bool GuiTexture::isAtlasTexture() const { + return openage::renderer::gui::isAtlasTexture(this->texture_handle); +} + +QSGTexture *GuiTexture::removedFromAtlas(QRhiResourceUpdateBatch * /* resourceUpdates */ /* = nullptr */) const { + if (this->isAtlasTexture()) { + return this->standalone.get(); + } + + return nullptr; +} + +QRectF GuiTexture::normalizedTextureSubRect() const { + return QSGTexture::normalizedTextureSubRect(); +} + +int GuiTexture::textureId() const { + return 0; +} + +QSize GuiTexture::textureSize() const { + return openage::renderer::gui::textureSize(this->texture_handle); +} + +} // namespace openage::renderer::gui diff --git a/libopenage/gui/integration/private/gui_texture.h b/libopenage/renderer/gui/integration/private/gui_texture.h similarity index 91% rename from libopenage/gui/integration/private/gui_texture.h rename to libopenage/renderer/gui/integration/private/gui_texture.h index aa84a0c10e..1972822763 100644 --- a/libopenage/gui/integration/private/gui_texture.h +++ b/libopenage/renderer/gui/integration/private/gui_texture.h @@ -8,8 +8,7 @@ #include "gui_texture_handle.h" -namespace openage { -namespace gui { +namespace openage::renderer::gui { class GuiTexture : public QSGTexture { Q_OBJECT @@ -35,5 +34,4 @@ class GuiTexture : public QSGTexture { mutable std::unique_ptr standalone; }; -} // namespace gui -} // namespace openage +} // namespace openage::renderer::gui diff --git a/libopenage/gui/integration/private/gui_texture_handle.cpp b/libopenage/renderer/gui/integration/private/gui_texture_handle.cpp similarity index 62% rename from libopenage/gui/integration/private/gui_texture_handle.cpp rename to libopenage/renderer/gui/integration/private/gui_texture_handle.cpp index da9c974c80..1174d933b3 100644 --- a/libopenage/gui/integration/private/gui_texture_handle.cpp +++ b/libopenage/renderer/gui/integration/private/gui_texture_handle.cpp @@ -1,43 +1,32 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. +// Copyright 2015-2023 the openage authors. See copying.md for legal info. #include "gui_texture_handle.h" #include -#include "../../../texture.h" -namespace openage { -namespace gui { +namespace openage::renderer::gui { -SizedTextureHandle::SizedTextureHandle() - : - TextureHandle{nullptr, 0}, +SizedTextureHandle::SizedTextureHandle() : + TextureHandle{0}, size{} { } -SizedTextureHandle::SizedTextureHandle(const TextureHandle &handle, const QSize &size) - : +SizedTextureHandle::SizedTextureHandle(const TextureHandle &handle, const QSize &size) : TextureHandle(handle), size{size} { } bool isAtlasTexture(const TextureHandle &texture_handle) { - return texture_handle.subid >= 0 && texture_handle.texture->get_subtexture_count() > 1; + return texture_handle.subid >= 0; } QSize textureSize(const SizedTextureHandle &texture_handle) { return texture_handle.size; } -QSize native_size(const TextureHandle &texture_handle) { - auto tex = texture_handle.texture; - - if (isAtlasTexture(texture_handle)) { - auto sub = tex->get_subtexture(texture_handle.subid); - return QSize(sub->w, sub->h); - } else { - return QSize(tex->w, tex->h); - } +QSize native_size(const TextureHandle & /* texture_handle */) { + return QSize(0, 0); } QSize aspect_fit_size(const TextureHandle &texture_handle, const QSize &requested_size) { @@ -48,9 +37,10 @@ QSize aspect_fit_size(const TextureHandle &texture_handle, const QSize &requeste // If requested_size.isEmpty() then the caller don't care how big one or two dimensions can grow. return size.scaled(bounding_size, requested_size.isEmpty() && (requested_size.width() > size.width() || requested_size.height() > size.height()) ? Qt::KeepAspectRatioByExpanding : Qt::KeepAspectRatio); - } else { + } + else { return size; } } -}} // namespace openage::gui +} // namespace openage::renderer::gui diff --git a/libopenage/gui/integration/private/gui_texture_handle.h b/libopenage/renderer/gui/integration/private/gui_texture_handle.h similarity index 83% rename from libopenage/gui/integration/private/gui_texture_handle.h rename to libopenage/renderer/gui/integration/private/gui_texture_handle.h index 58b86ee1bb..f9cdf59686 100644 --- a/libopenage/gui/integration/private/gui_texture_handle.h +++ b/libopenage/renderer/gui/integration/private/gui_texture_handle.h @@ -1,18 +1,13 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. +// Copyright 2015-2023 the openage authors. See copying.md for legal info. #pragma once #include -namespace openage { - -class Texture; - -namespace gui { +namespace openage::renderer::gui { class TextureHandle { public: - openage::Texture *texture; int subid; }; @@ -36,4 +31,4 @@ QSize native_size(const TextureHandle &texture_handle); */ QSize aspect_fit_size(const TextureHandle &texture_handle, const QSize &requested_size); -}} // namespace openage::gui +} // namespace openage::renderer::gui diff --git a/libopenage/renderer/gui/qml_info.cpp b/libopenage/renderer/gui/qml_info.cpp deleted file mode 100644 index 16e03f333b..0000000000 --- a/libopenage/renderer/gui/qml_info.cpp +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. - -#include "qml_info.h" - -namespace openage { -namespace renderer { -namespace gui { - -QMLInfo::QMLInfo(gamestate::GameSimulation *engine, const util::Path &asset_dir) : - engine{engine}, - asset_dir{asset_dir} {} - -} // namespace gui -} // namespace renderer -} // namespace openage diff --git a/libopenage/renderer/gui/qml_info.h b/libopenage/renderer/gui/qml_info.h deleted file mode 100644 index a5a668a086..0000000000 --- a/libopenage/renderer/gui/qml_info.h +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include "gui/guisys/public/gui_singleton_items_info.h" -#include "util/path.h" - -namespace openage { - -namespace gamestate { -class GameSimulation; -} - -namespace presenter { -class Presenter; -} - -namespace renderer { -namespace gui { - -class QMLInfo : public qtsdl::GuiSingletonItemsInfo { -public: - QMLInfo(gamestate::GameSimulation *engine, const util::Path &asset_dir); - - /** - * The openage engine, so it can be "used" in QML as a "QML Singleton". - * With this pointer, all of QML can find back to the engine. - */ - gamestate::GameSimulation *engine; - - /** - * The openage display. - */ - presenter::Presenter *display; - - /** - * Search path for finding assets n stuff. - */ - util::Path asset_dir; -}; - - -} // namespace gui -} // namespace renderer -} // namespace openage diff --git a/libopenage/renderer/opengl/CMakeLists.txt b/libopenage/renderer/opengl/CMakeLists.txt index eb93f1df56..300dac811f 100644 --- a/libopenage/renderer/opengl/CMakeLists.txt +++ b/libopenage/renderer/opengl/CMakeLists.txt @@ -2,6 +2,7 @@ add_sources(libopenage buffer.cpp context.cpp debug.cpp + error.cpp framebuffer.cpp geometry.cpp render_pass.cpp diff --git a/libopenage/renderer/opengl/context.cpp b/libopenage/renderer/opengl/context.cpp index d65fe4aae1..59f82339aa 100644 --- a/libopenage/renderer/opengl/context.cpp +++ b/libopenage/renderer/opengl/context.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 #include @@ -29,10 +29,11 @@ gl_context_spec GlContext::find_spec() { for (size_t i_ver = 0; i_ver < gl_versions.size(); ++i_ver) { QOpenGLContext test_context{}; - auto tf = test_format; + test_format.setMajorVersion(gl_versions[i_ver].first); test_format.setMinorVersion(gl_versions[i_ver].second); + test_context.setFormat(test_format); test_context.create(); if (!test_context.isValid()) { @@ -49,6 +50,7 @@ gl_context_spec GlContext::find_spec() { } QOpenGLContext test_context{}; + test_context.setFormat(test_format); test_context.create(); if (!test_context.isValid()) { throw Error(MSG(err) << "Failed to create OpenGL context which previously succeeded. This should not happen!"); @@ -72,6 +74,8 @@ gl_context_spec GlContext::find_spec() { caps.max_texture_slots = temp; glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &temp); caps.max_vertex_attributes = temp; + glGetIntegerv(GL_MAX_UNIFORM_LOCATIONS, &temp); + caps.max_uniform_locations = temp; glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &temp); caps.max_uniform_buffer_bindings = temp; @@ -154,6 +158,10 @@ std::shared_ptr GlContext::get_raw_context() const { return this->gl_context; } +GLuint GlContext::get_default_framebuffer_id() { + return this->gl_context->defaultFramebufferObject(); +} + gl_context_spec GlContext::get_specs() const { return this->specs; } diff --git a/libopenage/renderer/opengl/context.h b/libopenage/renderer/opengl/context.h index 68e3e39c1f..d0a3f4352a 100644 --- a/libopenage/renderer/opengl/context.h +++ b/libopenage/renderer/opengl/context.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,7 +16,7 @@ class GlShaderProgram; /** * Stores information about context capabilities and limitations. -*/ + */ struct gl_context_spec { /// The maximum number of vertex attributes in a shader. size_t max_vertex_attributes; @@ -24,6 +24,8 @@ struct gl_context_spec { size_t max_texture_slots; /// The maximum size of a single dimension of a texture. size_t max_texture_size; + /// The maximum number of uniform locations per shader. + size_t max_uniform_locations; /// The maximum number of binding points for uniform blocks /// in a single shader. size_t max_uniform_buffer_bindings; @@ -66,6 +68,17 @@ class GlContext { */ std::shared_ptr get_raw_context() const; + /** + * Get the ID of the default framebuffer used for displaying to + * the window. + * + * This value may change on every frame, so it should be called every + * time the default framebuffer is bound. + * + * @return ID of the default (display) framebuffer. + */ + unsigned int get_default_framebuffer_id(); + /** * Get the capabilities of this context. */ @@ -75,7 +88,7 @@ class GlContext { * Activate or deactivate VSync for this context. * * TODO: This currently does not work at runtime. vsync must be set before - * the QApplication is created. + * the QWindow is created. * * @param on \p true to activate VSync, \p false to deactivate. */ @@ -111,29 +124,29 @@ class GlContext { void set_current_program(const std::shared_ptr &prog); /** - * Get a free uniform buffer binding point that is not bound to any buffer. - * - * The number of available binding points is limited by the OpenGL implementation. - * When the context is created, there are \p capabilities.max_uniform_buffer_bindings - * free binding points available. - * - * @return Binding point ID. - * - * @throw Error if no binding point is available. - */ + * Get a free uniform buffer binding point that is not bound to any buffer. + * + * The number of available binding points is limited by the OpenGL implementation. + * When the context is created, there are \p capabilities.max_uniform_buffer_bindings + * free binding points available. + * + * @return Binding point ID. + * + * @throw Error if no binding point is available. + */ size_t get_uniform_buffer_binding(); /** - * Free a buffer binding point, indicating that newly created buffers can use it. - * - * When calling this function, it must be ensured that the binding point is not - * assigned to any buffer or shader. Otherwise, reassigning the binding point - * can corrupt the uniform data. - * - * @param binding_point Binding point ID. - * - * @throw Error if the binding point is not valid. - */ + * Free a buffer binding point, indicating that newly created buffers can use it. + * + * When calling this function, it must be ensured that the binding point is not + * assigned to any buffer or shader. Otherwise, reassigning the binding point + * can corrupt the uniform data. + * + * @param binding_point Binding point ID. + * + * @throw Error if the binding point is not valid. + */ void free_uniform_buffer_binding(size_t binding_point); /** @@ -170,8 +183,8 @@ class GlContext { std::weak_ptr last_program; /** - * Store the currently active binding points for uniform buffers. - */ + * Store the currently active binding points for uniform buffers. + */ std::vector uniform_buffer_bindings; }; diff --git a/libopenage/renderer/opengl/debug.h b/libopenage/renderer/opengl/debug.h index 3c945eb76f..69deff452d 100644 --- a/libopenage/renderer/opengl/debug.h +++ b/libopenage/renderer/opengl/debug.h @@ -1,4 +1,4 @@ -// Copyright 2022-2023 the openage authors. See copying.md for legal info. +// Copyright 2022-2024 the openage authors. See copying.md for legal info. #pragma once @@ -32,7 +32,7 @@ class GlDebugLogHandler : public QObject { /** * Stop logging OpenGL debug messages. - */ + */ void stop(); public slots: diff --git a/libopenage/util/opengl.cpp b/libopenage/renderer/opengl/error.cpp similarity index 79% rename from libopenage/util/opengl.cpp rename to libopenage/renderer/opengl/error.cpp index 1cc8b31ee3..c47bf5a85d 100644 --- a/libopenage/util/opengl.cpp +++ b/libopenage/renderer/opengl/error.cpp @@ -1,20 +1,18 @@ -// Copyright 2014-2016 the openage authors. See copying.md for legal info. +// Copyright 2014-2023 the openage authors. See copying.md for legal info. -#include "opengl.h" +#include "error.h" #include -#include "../error/error.h" +#include "error/error.h" -namespace openage { -namespace util { +namespace openage::renderer::opengl { void gl_check_error() { int glerrorstate = 0; glerrorstate = glGetError(); if (glerrorstate != GL_NO_ERROR) { - const char *errormsg; //generate error message @@ -63,11 +61,12 @@ void gl_check_error() { // unknown error state errormsg = "unknown error"; } - throw Error(MSG(err) << - "OpenGL error state after running draw method: " << glerrorstate << "\n" - "\t" << errormsg << "\n" - << "Run the game with --gl-debug to get more information: './run game --gl-debug'."); + throw Error(MSG(err) << "OpenGL error state after running draw method: " + << glerrorstate << "\n" + "\t" + << errormsg << "\n" + << "Run the engine with --gl-debug to get more information."); } } -}} // openage::util +} // namespace openage::renderer::opengl diff --git a/libopenage/util/opengl.h b/libopenage/renderer/opengl/error.h similarity index 51% rename from libopenage/util/opengl.h rename to libopenage/renderer/opengl/error.h index d1c3a2e300..5fe692fd2d 100644 --- a/libopenage/util/opengl.h +++ b/libopenage/renderer/opengl/error.h @@ -1,9 +1,8 @@ -// Copyright 2014-2016 the openage authors. See copying.md for legal info. +// Copyright 2014-2023 the openage authors. See copying.md for legal info. #pragma once -namespace openage { -namespace util { +namespace openage::renderer::opengl { /** * query the current opengl context for any errors. @@ -12,5 +11,4 @@ namespace util { */ void gl_check_error(); -} -} +} // namespace openage::renderer::opengl diff --git a/libopenage/renderer/opengl/framebuffer.cpp b/libopenage/renderer/opengl/framebuffer.cpp index e9862ff7ce..0ad46c1563 100644 --- a/libopenage/renderer/opengl/framebuffer.cpp +++ b/libopenage/renderer/opengl/framebuffer.cpp @@ -1,18 +1,29 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #include "framebuffer.h" +#include "log/log.h" + +#include "renderer/opengl/context.h" #include "renderer/opengl/texture.h" namespace openage::renderer::opengl { +GlFramebuffer::GlFramebuffer(const std::shared_ptr &context) : + GlSimpleObject(context, + [](GLuint /*handle*/) {}), + type{gl_framebuffer_t::display} { + log::log(MSG(dbg) << "Created OpenGL framebuffer with display target"); +} + // TODO the validity of this object is contingent // on its texture existing. use shared_ptr? GlFramebuffer::GlFramebuffer(const std::shared_ptr &context, std::vector> const &textures) : GlSimpleObject(context, - [](GLuint handle) { glDeleteFramebuffers(1, &handle); }) { + [](GLuint handle) { glDeleteFramebuffers(1, &handle); }), + type{gl_framebuffer_t::textures} { GLuint handle; glGenFramebuffers(1, &handle); this->handle = handle; @@ -21,6 +32,10 @@ GlFramebuffer::GlFramebuffer(const std::shared_ptr &context, std::vector drawBuffers; + if (textures.empty()) { + throw Error{ERR << "At least 1 texture must be assigned to texture framebuffer."}; + } + size_t colorTextureCount = 0; for (auto const &texture : textures) { // TODO figure out attachment points from pixel formats @@ -39,14 +54,30 @@ GlFramebuffer::GlFramebuffer(const std::shared_ptr &context, if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { throw Error(MSG(err) << "Could not create OpenGL framebuffer."); } + + log::log(MSG(dbg) << "Created OpenGL framebuffer with texture targets"); +} + +gl_framebuffer_t GlFramebuffer::get_type() const { + return this->type; } void GlFramebuffer::bind_read() const { - glBindFramebuffer(GL_READ_FRAMEBUFFER, *this->handle); + if (this->type == gl_framebuffer_t::textures) { + glBindFramebuffer(GL_READ_FRAMEBUFFER, *this->handle); + } + else { + glBindFramebuffer(GL_READ_FRAMEBUFFER, this->context->get_default_framebuffer_id()); + } } void GlFramebuffer::bind_write() const { - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, *this->handle); + if (this->type == gl_framebuffer_t::textures) { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, *this->handle); + } + else { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, this->context->get_default_framebuffer_id()); + } } } // namespace openage::renderer::opengl diff --git a/libopenage/renderer/opengl/framebuffer.h b/libopenage/renderer/opengl/framebuffer.h index ccd544f57e..398f7e81d0 100644 --- a/libopenage/renderer/opengl/framebuffer.h +++ b/libopenage/renderer/opengl/framebuffer.h @@ -1,4 +1,4 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once @@ -7,30 +7,75 @@ #include "renderer/opengl/simple_object.h" -namespace openage { -namespace renderer { -namespace opengl { +namespace openage::renderer::opengl { class GlTexture2d; -/// Represents an OpenGL Framebuffer Object. -/// It is a collection of bitmap targets that can be drawn into -/// and read from. +/** + * The type of OpenGL framebuffer. + */ +enum class gl_framebuffer_t { + /** + * The actual window. This is visible to the user after swapping front and back buffers. + */ + display, + /** + * A bunch of textures. These can be color texture, depth textures, etc. + */ + textures, +}; + +/** + * Represents an OpenGL Framebuffer Object. + * It is a collection of bitmap targets that can be drawn into + * and read from. + */ class GlFramebuffer final : public GlSimpleObject { public: - /// Construct a framebuffer pointing at the given textures. - /// Texture are attached to points specific to their pixel format, - /// e.g. a depth texture will be set as the depth target. + /** + * Construct a framebuffer pointing at the default framebuffer - the window. + * + * Drawing into this framebuffer draws onto the screen. + * + * @param context OpenGL context used for drawing. + */ + GlFramebuffer(const std::shared_ptr &context); + + /** + * Construct a framebuffer pointing at the given textures. + * + * Texture are attached to points specific to their pixel format, + * e.g. a depth texture will be set as the depth target. + * + * @param context OpenGL context used for drawing. + * @param textures Textures targeted by the framebuffer. They are automatically + * attached to the correct attachement points depending on their type. + */ GlFramebuffer(const std::shared_ptr &context, std::vector> const &textures); - /// Bind this framebuffer to GL_READ_FRAMEBUFFER. + /** + * Get the type of this framebuffer. + * + * @return Framebuffer type. + */ + gl_framebuffer_t get_type() const; + + /** + * Bind this framebuffer to \p GL_READ_FRAMEBUFFER. + */ void bind_read() const; - /// Bind this framebuffer to GL_DRAW_FRAMEBUFFER. + /** + * Bind this framebuffer to \p GL_DRAW_FRAMEBUFFER. + */ void bind_write() const; + +private: + /** + * Type of this framebuffer. + */ + gl_framebuffer_t type; }; -} // namespace opengl -} // namespace renderer -} // namespace openage +} // namespace openage::renderer::opengl diff --git a/libopenage/renderer/opengl/geometry.cpp b/libopenage/renderer/opengl/geometry.cpp index a01b4183af..e0898edbe8 100644 --- a/libopenage/renderer/opengl/geometry.cpp +++ b/libopenage/renderer/opengl/geometry.cpp @@ -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. #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; @@ -68,7 +69,7 @@ void GlGeometry::draw() const { glDrawElements(mesh.primitive, mesh.vert_count, *mesh.index_type, nullptr); } else { - glDrawArrays(GL_TRIANGLE_STRIP, 0, mesh.vert_count); + glDrawArrays(mesh.primitive, 0, mesh.vert_count); } break; diff --git a/libopenage/renderer/opengl/lookup.h b/libopenage/renderer/opengl/lookup.h index e399e98399..9521369ef5 100644 --- a/libopenage/renderer/opengl/lookup.h +++ b/libopenage/renderer/opengl/lookup.h @@ -1,4 +1,4 @@ -// Copyright 2018-2023 the openage authors. See copying.md for legal info. +// Copyright 2018-2024 the openage authors. See copying.md for legal info. // Lookup tables for translating between OpenGL-specific values and generic renderer values, // as well as mapping things like type sizes within OpenGL. @@ -33,6 +33,10 @@ static constexpr auto GL_UNIFORM_TYPE_SIZE = datastructure::create_const_map renderables, +GlRenderPass::GlRenderPass(std::vector &&renderables, const std::shared_ptr &target) : - RenderPass(renderables, target), - is_optimised(false) {} + RenderPass(std::move(renderables), target), + is_optimized(false) { +} + +void GlRenderPass::set_renderables(std::vector &&renderables) { + RenderPass::set_renderables(std::move(renderables)); + this->is_optimized = false; +} -const std::vector &GlRenderPass::get_renderables() const { - return this->renderables; +void GlRenderPass::add_renderables(std::vector &&renderables, int64_t priority) { + RenderPass::add_renderables(std::move(renderables), priority); + this->is_optimized = false; } -void GlRenderPass::set_renderables(std::vector renderables) { - this->renderables = renderables; - this->is_optimised = false; +void GlRenderPass::add_renderables(Renderable &&renderable, int64_t priority) { + RenderPass::add_renderables(std::move(renderable), priority); + this->is_optimized = false; } -bool GlRenderPass::get_is_optimised() const { - return this->is_optimised; +bool GlRenderPass::get_is_optimized() const { + return this->is_optimized; } -void GlRenderPass::set_is_optimised(bool flag) { - this->is_optimised = flag; +void GlRenderPass::set_is_optimized(bool flag) { + this->is_optimized = flag; } } // namespace openage::renderer::opengl diff --git a/libopenage/renderer/opengl/render_pass.h b/libopenage/renderer/opengl/render_pass.h index 2994a0e7f7..87ea8448c9 100644 --- a/libopenage/renderer/opengl/render_pass.h +++ b/libopenage/renderer/opengl/render_pass.h @@ -1,24 +1,28 @@ -// Copyright 2019-2023 the openage authors. See copying.md for legal info. +// Copyright 2019-2024 the openage authors. See copying.md for legal info. #pragma once -#include "../renderer.h" +#include "renderer/render_pass.h" +#include "renderer/renderable.h" + namespace openage::renderer::opengl { class GlRenderPass final : public RenderPass { public: - GlRenderPass(std::vector, - const std::shared_ptr &); + GlRenderPass(std::vector &&renderables, + const std::shared_ptr &target); + + void set_renderables(std::vector &&renderables); + void add_renderables(std::vector &&renderables, int64_t priority = LAYER_PRIORITY_MAX); + void add_renderables(Renderable &&renderable, int64_t priority = LAYER_PRIORITY_MAX); - void set_renderables(std::vector); - const std::vector &get_renderables() const; - void set_is_optimised(bool); - bool get_is_optimised() const; + void set_is_optimized(bool); + bool get_is_optimized() const; private: /// Whether the renderables order is optimised - bool is_optimised; + bool is_optimized; }; } // namespace openage::renderer::opengl diff --git a/libopenage/renderer/opengl/render_target.cpp b/libopenage/renderer/opengl/render_target.cpp index b2f844d83b..b965dc58ea 100644 --- a/libopenage/renderer/opengl/render_target.cpp +++ b/libopenage/renderer/opengl/render_target.cpp @@ -1,30 +1,56 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #include "render_target.h" +#include "error/error.h" +#include "log/log.h" + #include "renderer/opengl/texture.h" + namespace openage::renderer::opengl { -GlRenderTarget::GlRenderTarget(size_t width, size_t height) : - type(gl_render_target_t::display), - size(width, height) {} +GlRenderTarget::GlRenderTarget(const std::shared_ptr &context, size_t width, size_t height) : + type(gl_render_target_t::framebuffer), + size(width, height), + framebuffer(context) { + log::log(MSG(dbg) << "Created OpenGL render target for default framebuffer"); +} GlRenderTarget::GlRenderTarget(const std::shared_ptr &context, const std::vector> &textures) : - type(gl_render_target_t::textures), + type(gl_render_target_t::framebuffer), framebuffer({context, textures}), textures(textures) { // TODO: Check if the textures are all the same size this->size = this->textures.value().at(0)->get_info().get_size(); + + log::log(MSG(dbg) << "Created OpenGL render target for textures"); +} + +resources::Texture2dData GlRenderTarget::into_data() { + // make sure the framebuffer is bound + this->bind_read(); + + std::vector pxdata(this->size.first * this->size.second * 4); + glReadPixels(0, 0, this->size.first, this->size.second, GL_RGBA, GL_UNSIGNED_BYTE, pxdata.data()); + + resources::Texture2dInfo info{this->size.first, + this->size.second, + resources::pixel_format::rgba8}; + return resources::Texture2dData{info, std::move(pxdata)}; +} + +gl_render_target_t GlRenderTarget::get_type() const { + return this->type; } std::vector> GlRenderTarget::get_texture_targets() { std::vector> textures{}; - if (this->type == gl_render_target_t::display) { + if (this->framebuffer->get_type() == gl_framebuffer_t::display) { return textures; } - //else upcast pointers + // else upcast pointers for (auto tex : this->textures.value()) { auto new_ptr = dynamic_pointer_cast(tex); textures.push_back(new_ptr); @@ -33,8 +59,9 @@ std::vector> GlRenderTarget::get_texture_targets() { } void GlRenderTarget::resize(size_t width, size_t height) { - if (this->type != gl_render_target_t::display) { - throw Error{ERR << "Texture render target should not be resized. Create a new one instead."}; + if (this->framebuffer->get_type() == gl_framebuffer_t::textures) { + throw Error{ERR << "Render target with textured framebuffer should not be resized. " + << "Create a new one instead."}; } this->size = std::make_pair(width, height); @@ -46,23 +73,11 @@ void GlRenderTarget::bind_write() const { // different sizes glViewport(0, 0, size.first, size.second); - if (this->type == gl_render_target_t::textures) { - this->framebuffer->bind_write(); - } - else { - // 0 is the default, window framebuffer - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - } + this->framebuffer->bind_write(); } void GlRenderTarget::bind_read() const { - if (this->type == gl_render_target_t::textures) { - this->framebuffer->bind_read(); - } - else { - // 0 is the default, window framebuffer - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - } + this->framebuffer->bind_read(); } } // namespace openage::renderer::opengl diff --git a/libopenage/renderer/opengl/render_target.h b/libopenage/renderer/opengl/render_target.h index 546ac1e8ca..5c6061e070 100644 --- a/libopenage/renderer/opengl/render_target.h +++ b/libopenage/renderer/opengl/render_target.h @@ -1,10 +1,11 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once #include #include "renderer/opengl/framebuffer.h" +#include "renderer/render_target.h" #include "renderer/renderer.h" @@ -17,50 +18,106 @@ namespace opengl { class GlTexture2d; -/// The type of OpenGL render target +/** + * The type of OpenGL render target. + */ enum class gl_render_target_t { - /// The actual window. This is visible to the user after swapping front and back buffers - display, - /// A bunch of textures - textures, + /** + * Render into a framebuffer. + */ + framebuffer, // TODO renderbuffers mixed with textures }; -/// Represents an OpenGL target that can be drawn into. -/// It can be either a framebuffer or the display (the window). +/** + * Represents an OpenGL target that can be drawn into. + * It can be either a framebuffer with texture attachements or the display (the window). + */ class GlRenderTarget final : public RenderTarget { public: - /// Construct a render target pointed at the default framebuffer - the window. - GlRenderTarget(size_t width, size_t height); - - /// Construct a render target pointing at the given textures. - /// Texture are attached to points specific to their pixel format, - /// e.g. a depth texture will be set as the depth target. + /** + * Construct a render target pointed at the default framebuffer - the window. + * + * @param context OpenGL context used for drawing. + * @param width Current width of the window. + * @param height Current height of the window. + */ + GlRenderTarget(const std::shared_ptr &context, + size_t width, + size_t height); + + /** + * Construct a render target pointing at the given textures. + * Texture are attached to points specific to their pixel format, + * e.g. a depth texture will be set as the depth target. + * + * @param context OpenGL context used for drawing. + * @param textures Texture attachements. + */ GlRenderTarget(const std::shared_ptr &context, std::vector> const &textures); - // Get the targeted textures + /** + * Get the pixels stored in the render target's buffer. + * + * @return Texture data with the image contents of the buffer. + */ + resources::Texture2dData into_data() override; + + /** + * Get the type of this render target. + * + * @return Render target type. + */ + gl_render_target_t get_type() const; + + /** + * Get the targeted textures. + * + * @return Textures drawn into by the render target. + */ std::vector> get_texture_targets() override; - // Resize the render target for scaling viewport correctly. + /** + * Resize the render target to the specified dimensions. + * + * This is used to scale the viewport to the correct size when + * binding the render target with write access. + * + * @param width New width. + * @param height New height. + */ void resize(size_t width, size_t height); - /// Bind this render target to be drawn into. + /** + * Bind this render target to be drawn into. + */ void bind_write() const; - /// Bind this render target to be read from. + /** + * Bind this render target to be read from. + */ void bind_read() const; private: + /** + * Type of this render target. + */ gl_render_target_t type; - // Size of the window or the texture target + /** + * Size of the window or the texture targets. + */ std::pair size; - /// For textures target type, the framebuffer. + /** + * For framebuffer target type, the framebuffer. + */ std::optional framebuffer; - // target textures if the render target is an fbo + /** + * Target textures if the render target is a textured fbo. + */ std::optional>> textures; }; diff --git a/libopenage/renderer/opengl/renderer.cpp b/libopenage/renderer/opengl/renderer.cpp index f0e513f14c..6d8d909a5b 100644 --- a/libopenage/renderer/opengl/renderer.cpp +++ b/libopenage/renderer/opengl/renderer.cpp @@ -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. #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" @@ -24,9 +25,21 @@ namespace openage::renderer::opengl { GlRenderer::GlRenderer(const std::shared_ptr &ctx, const util::Vector2s &viewport_size) : gl_context{ctx}, - display{std::make_shared(viewport_size[0], viewport_size[1])} { + display{std::make_shared(ctx, + viewport_size[0], + 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); + // global GL alpha blending settings - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + // glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glBlendFuncSeparate( + GL_SRC_ALPHA, // source (incoming) RGB factor + GL_ONE_MINUS_SRC_ALPHA, // destination (underlying) RGB factor + GL_ONE, // source (incoming) alpha factor + GL_ONE_MINUS_SRC_ALPHA // destination (underlying) alpha factor + ); // global GL depth testing settings glDepthFunc(GL_LEQUAL); @@ -75,18 +88,23 @@ std::shared_ptr GlRenderer::get_display_target() { std::shared_ptr GlRenderer::add_uniform_buffer(resources::UniformBufferInfo const &info) { auto inputs = info.get_inputs(); - std::unordered_map uniforms{}; + std::vector uniforms{}; size_t offset = 0; for (auto const &input : inputs) { auto type = GL_UBO_INPUT_TYPE.get(input.type); - uniforms.emplace( - std::make_pair(input.name, - GlInBlockUniform{type, - offset, - resources::UniformBufferInfo::get_size(input, info.get_layout()), - resources::UniformBufferInfo::get_stride_size(input.type, info.get_layout()), - input.count})); - offset += resources::UniformBufferInfo::get_size(input, info.get_layout()); + auto size = resources::UniformBufferInfo::get_size(input, info.get_layout()); + + // align offset to the size of the type + offset += offset % size; + uniforms.push_back( + GlInBlockUniform{type, + offset, + resources::UniformBufferInfo::get_size(input, info.get_layout()), + resources::UniformBufferInfo::get_stride_size(input.type, info.get_layout()), + input.count, + input.name}); + + offset += size; } return std::make_shared(this->gl_context, @@ -128,10 +146,9 @@ void GlRenderer::resize_display_target(size_t width, size_t height) { this->display->resize(width, height); } -void GlRenderer::optimise(const std::shared_ptr &pass) { - if (!pass->get_is_optimised()) { - auto renderables = pass->get_renderables(); - std::stable_sort(renderables.begin(), renderables.end(), [](const Renderable &a, const Renderable &b) { +void GlRenderer::optimize(const std::shared_ptr &pass) { + if (!pass->get_is_optimized()) { + pass->sort([](const Renderable &a, const Renderable &b) { GLuint shader_a = std::dynamic_pointer_cast( std::dynamic_pointer_cast(a.uniform)->get_program()) ->get_handle(); @@ -141,8 +158,7 @@ void GlRenderer::optimise(const std::shared_ptr &pass) { return shader_a < shader_b; }); - pass->set_renderables(renderables); - pass->set_is_optimised(true); + pass->set_is_optimized(true); } } @@ -155,41 +171,60 @@ void GlRenderer::render(const std::shared_ptr &pass) { auto gl_target = std::dynamic_pointer_cast(pass->get_target()); gl_target->bind_write(); - glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + // 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 // glEnable(GL_CULL_FACE); auto gl_pass = std::dynamic_pointer_cast(pass); - GlRenderer::optimise(gl_pass); - - for (auto const &obj : gl_pass->get_renderables()) { - if (obj.alpha_blending) { - glEnable(GL_BLEND); - } - else { - glDisable(GL_BLEND); - } + // TODO: Optimization is disabled for now. Figure out how to do this without calling it every frame + // GlRenderer::optimize(gl_pass); - if (obj.depth_test) { - glEnable(GL_DEPTH_TEST); - } - else { - glDisable(GL_DEPTH_TEST); - } + // render all objects in the pass + const auto &layers = gl_pass->get_layers(); + const auto &renderables = gl_pass->get_renderables(); - auto in = std::dynamic_pointer_cast(obj.uniform); - auto program = std::static_pointer_cast(in->get_program()); + // Draw by layers + for (size_t i = 0; i < layers.size(); i++) { + const auto &layer = layers[i]; + const auto &objects = renderables[i]; - // this also calls program->use() - program->update_uniforms(in); + if (layer.clear_depth) { + glClear(GL_DEPTH_BUFFER_BIT); + } - // draw the geometry - if (obj.geometry != nullptr) { - auto geom = std::dynamic_pointer_cast(obj.geometry); - // TODO read obj.blend + family - geom->draw(); + for (auto const &obj : objects) { + if (obj.alpha_blending) { + glEnable(GL_BLEND); + } + else { + glDisable(GL_BLEND); + } + + if (obj.depth_test) { + glEnable(GL_DEPTH_TEST); + } + else { + glDisable(GL_DEPTH_TEST); + } + + auto in = std::dynamic_pointer_cast(obj.uniform); + auto program = std::static_pointer_cast(in->get_program()); + + // this also calls program->use() + program->update_uniforms(in); + + // draw the geometry + if (obj.geometry != nullptr) { + auto geom = std::dynamic_pointer_cast(obj.geometry); + // TODO read obj.blend + family + geom->draw(); + } } } } diff --git a/libopenage/renderer/opengl/renderer.h b/libopenage/renderer/opengl/renderer.h index 4d9038808a..751af7cacc 100644 --- a/libopenage/renderer/opengl/renderer.h +++ b/libopenage/renderer/opengl/renderer.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 @@ -17,6 +17,7 @@ namespace opengl { class GlContext; class GlRenderPass; class GlRenderTarget; +class GlVertexArray; class GlWindow; /// The OpenGL specialization of the rendering interface. @@ -60,13 +61,21 @@ class GlRenderer final : public Renderer { private: /// Optimize the render pass by reordering stuff - static void optimise(const std::shared_ptr &); + static void optimize(const std::shared_ptr &pass); /// The GL context. std::shared_ptr gl_context; /// 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/shader_data.cpp b/libopenage/renderer/opengl/shader_data.cpp index d4f7e040e2..1bfd912518 100644 --- a/libopenage/renderer/opengl/shader_data.cpp +++ b/libopenage/renderer/opengl/shader_data.cpp @@ -1,3 +1,10 @@ // Copyright 2023-2023 the openage authors. See copying.md for legal info. #include "shader_data.h" + + +namespace openage::renderer::opengl { + +// this file is intentionally empty + +} // namespace openage::renderer::opengl diff --git a/libopenage/renderer/opengl/shader_data.h b/libopenage/renderer/opengl/shader_data.h index 83d140e591..d60d16cb85 100644 --- a/libopenage/renderer/opengl/shader_data.h +++ b/libopenage/renderer/opengl/shader_data.h @@ -1,9 +1,11 @@ -// 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 #include +#include #include @@ -18,10 +20,17 @@ struct GlUniform { GLenum type; /** - * Location of the uniform for use with glUniform and glGetUniform. + * Location of the uniform for use with glUniform and glGetUniform. * NOT the same as the uniform index. - */ + */ GLuint location; + + /** + * Only used for sampler uniforms. + * + * Texture unit to which the sampler is bound. + */ + std::optional tex_unit; }; /** @@ -31,28 +40,33 @@ struct GlInBlockUniform { GLenum type; /** - * Offset relative to the beginning of the block at which this uniform is placed. - */ + * Offset relative to the beginning of the block at which this uniform is placed. + */ size_t offset; /** - * The size in bytes of the whole uniform. If the uniform is an array, + * The size in bytes of the whole uniform. If the uniform is an array, * the size of the whole array. - */ + */ size_t size; /** - * Only relevant for arrays and matrices. + * Only relevant for arrays and matrices. * In arrays, specifies the distance between the start of each element. * In row-major matrices, specifies the distance between the start of each row. * In column-major matrices, specifies the distance between the start of each column. - */ + */ size_t stride; /** - * Only relevant for arrays. The number of elements in the array. - */ + * Only relevant for arrays. The number of elements in the array. + */ size_t count; + + /** + * Name of the block uniform. + */ + std::string name; }; /** @@ -62,19 +76,19 @@ struct GlUniformBlock { GLuint index; /** - * Size of the entire block. How uniforms are packed within depends + * Size of the entire block. How uniforms are packed within depends * on the block layout and is described in corresponding GlUniforms. - */ + */ size_t data_size; /** - * Maps uniform names within this block to their descriptions. - */ - std::unordered_map uniforms; + * Maps uniform names within this block to their descriptions. + */ + std::vector uniforms; /** - * The binding point assigned to this block. - */ + * The binding point assigned to this block. + */ GLuint binding_point; }; diff --git a/libopenage/renderer/opengl/shader_program.cpp b/libopenage/renderer/opengl/shader_program.cpp index 67127ecc1f..4b46a8263c 100644 --- a/libopenage/renderer/opengl/shader_program.cpp +++ b/libopenage/renderer/opengl/shader_program.cpp @@ -1,4 +1,4 @@ -// Copyright 2013-2023 the openage authors. See copying.md for legal info. +// Copyright 2013-2024 the openage authors. See copying.md for legal info. #include "shader_program.h" @@ -9,9 +9,9 @@ #include "datastructure/constexpr_map.h" #include "error/error.h" #include "log/log.h" -#include "util/opengl.h" #include "renderer/opengl/context.h" +#include "renderer/opengl/error.h" #include "renderer/opengl/geometry.h" #include "renderer/opengl/lookup.h" #include "renderer/opengl/shader.h" @@ -125,7 +125,7 @@ GlShaderProgram::GlShaderProgram(const std::shared_ptr &context, std::vector uniform_indices(val); glGetActiveUniformBlockiv(handle, i_unif_block, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, uniform_indices.data()); - std::unordered_map uniforms; + std::vector uniforms; for (GLuint const i_unif : uniform_indices) { in_block_unifs.insert(i_unif); @@ -152,14 +152,14 @@ GlShaderProgram::GlShaderProgram(const std::shared_ptr &context, // We do not need to handle sampler types here like in the uniform loop below, // because named blocks cannot contain samplers. - uniforms.insert(std::make_pair( - name.data(), + uniforms.push_back( GlInBlockUniform{ type, size_t(offset), size_t(count) * GL_UNIFORM_TYPE_SIZE.get(type), size_t(stride), - size_t(count)})); + size_t(count), + std::string(name.data())}); } // ENSURE(block_binding < caps.max_uniform_buffer_bindings, @@ -179,7 +179,7 @@ GlShaderProgram::GlShaderProgram(const std::shared_ptr &context, block_binding})); } - GLuint tex_unit = 0; + GLuint tex_unit_id = 0; // Extract information about uniforms in the default block. @@ -204,26 +204,30 @@ GlShaderProgram::GlShaderProgram(const std::shared_ptr &context, GLuint loc = glGetUniformLocation(handle, name.data()); - this->uniforms.push_back({type, loc}); + this->uniforms.push_back({type, loc, std::nullopt}); this->uniforms_by_name.insert(std::make_pair( name.data(), unif_id)); if (type == GL_SAMPLER_2D) { - ENSURE(tex_unit < caps.max_texture_slots, + ENSURE(tex_unit_id < caps.max_texture_slots, "Tried to create an OpenGL shader that uses more texture sampler uniforms " << "than there are texture unit slots (" << caps.max_texture_slots << " available)."); - this->texunits_per_unifs.insert(std::make_pair(unif_id, tex_unit)); + this->uniforms[unif_id].tex_unit = tex_unit_id; - tex_unit += 1; + tex_unit_id += 1; } // Increment uniform ID unif_id += 1; } + // Resize the texture unit bindings + // to number of texture units used by the shader + this->textures_per_texunits.resize(tex_unit_id); + // Extract vertex attribute descriptions. for (GLuint i_attrib = 0; i_attrib < attrib_count; ++i_attrib) { GLint size; @@ -253,10 +257,10 @@ GlShaderProgram::GlShaderProgram(const std::shared_ptr &context, for (auto const &pair : this->uniform_blocks) { log::log(MSG(dbg) << "(" << pair.second.index << ") " << pair.first << " (size: " << pair.second.data_size << ") {"); - for (auto const &unif_pair : pair.second.uniforms) { - log::log(MSG(dbg) << "\t+" << unif_pair.second.offset - << " " << unif_pair.first << ": " - << GLSL_TYPE_NAME.get(unif_pair.second.type)); + for (auto const &unif : pair.second.uniforms) { + log::log(MSG(dbg) << "\t+" << unif.offset + << " " << unif.name << ": " + << GLSL_TYPE_NAME.get(unif.type)); } log::log(MSG(dbg) << "}"); } @@ -304,32 +308,39 @@ void GlShaderProgram::use() { std::static_pointer_cast( this->shared_from_this())); - for (auto const &pair : this->textures_per_texunits) { + for (size_t i = 0; i < this->textures_per_texunits.size(); ++i) { + auto &tex_unit = this->textures_per_texunits[i]; + if (not tex_unit) { + continue; + } + + if (not glIsTexture(*tex_unit)) { + // By the time we use the shader again, the texture may have been deleted + // but if it's fixed afterwards using update_uniforms, the render state + // will still be fine + // We can free the texture unit in this case + tex_unit = std::nullopt; + continue; + } + // We have to bind the texture to their texture units here because // the texture unit bindings are global to the context. Each time // the shader switches, it is possible that some other shader overwrote // these, and since we want the uniform values to persist across update_uniforms // calls, we have to set them more often than just on update_uniforms. - glActiveTexture(GL_TEXTURE0 + pair.first); - glBindTexture(GL_TEXTURE_2D, pair.second); - - // By the time we call bind, the texture may have been deleted, but if it's fixed - // afterwards using update_uniforms, the render state will still be fine, so we can ignore - // this error. - // TODO this will swallow actual errors elsewhere, and should be avoided. how? - // probably by holding the texture object as shared_ptr as long as it is bound - glGetError(); + glActiveTexture(GL_TEXTURE0 + i); + glBindTexture(GL_TEXTURE_2D, *tex_unit); } } bool GlShaderProgram::in_use() const { - return this->context->get_current_program().lock() == this->shared_from_this(); + return this->context->get_current_program().lock().get() == this; } void GlShaderProgram::update_uniforms(std::shared_ptr const &unif_in) { - ENSURE(unif_in->get_program() == this->shared_from_this(), "Uniform input passed to different shader than it was created with."); + ENSURE(unif_in->get_program().get() == this, "Uniform input passed to different shader than it was created with."); // TODO: use glProgramUniform when we're on OpenGL 4.1 // then we don't need to "use" and then call glUniform* @@ -338,10 +349,19 @@ void GlShaderProgram::update_uniforms(std::shared_ptr const &uni this->use(); } + const auto &update_offs = unif_in->update_offs; + const auto &used_uniforms = unif_in->used_uniforms; + const auto &uniforms = this->uniforms; uint8_t const *data = unif_in->update_data.data(); - for (auto const &pair : unif_in->update_offs) { - uint8_t const *ptr = data + pair.second; - const auto &unif = this->uniforms.at(pair.first); + + size_t unif_count = used_uniforms.size(); + for (size_t i = 0; i < unif_count; ++i) { + uniform_id_t unif_id = used_uniforms[i]; + + const auto &update_off = update_offs[unif_id]; + uint8_t const *ptr = data + update_off.offset; + + const auto &unif = uniforms[unif_id]; auto loc = unif.location; switch (unif.type) { @@ -395,13 +415,20 @@ void GlShaderProgram::update_uniforms(std::shared_ptr const &uni glUniformMatrix4fv(loc, 1, GLboolean(false), reinterpret_cast(ptr)); break; case GL_SAMPLER_2D: { - GLuint tex_unit = this->texunits_per_unifs.at(pair.first); + ENSURE(unif.tex_unit, + "Tried to access texture unit for uniform that has no texture unit assigned."); + GLuint tex_unit_id = *unif.tex_unit; + GLuint tex = *reinterpret_cast(ptr); - glActiveTexture(GL_TEXTURE0 + tex_unit); + glActiveTexture(GL_TEXTURE0 + tex_unit_id); glBindTexture(GL_TEXTURE_2D, tex); // TODO: maybe call this at a more appropriate position - glUniform1i(loc, tex_unit); - this->textures_per_texunits[tex_unit] = tex; + glUniform1i(loc, tex_unit_id); + ENSURE(tex_unit_id < this->textures_per_texunits.size(), + "Tried to assign texture to non-existant texture unit at index " + << tex_unit_id + << " (max: " << this->textures_per_texunits.size() << ")."); + this->textures_per_texunits[tex_unit_id] = tex; break; } default: @@ -434,6 +461,14 @@ uniform_id_t GlShaderProgram::get_uniform_id(const char *name) { return this->uniforms_by_name.at(name); } +const std::vector &GlShaderProgram::get_uniforms() const { + return this->uniforms; +} + +const std::unordered_map &GlShaderProgram::get_uniform_blocks() const { + return this->uniform_blocks; +} + bool GlShaderProgram::has_uniform(const char *name) { return this->uniforms_by_name.contains(name); } @@ -445,208 +480,190 @@ void GlShaderProgram::bind_uniform_buffer(const char *block_name, std::shared_pt auto gl_buffer = std::dynamic_pointer_cast(buffer); auto &block = this->uniform_blocks[block_name]; - // TODO: Check if the uniform buffer matches the block definition + // Check if the uniform buffer matches the block definition + for (auto const &unif : block.uniforms) { + ENSURE(gl_buffer->has_uniform(unif.name.c_str()), + "Uniform buffer does not contain uniform '" << unif.name << "' required by block " << block_name); + } block.binding_point = gl_buffer->get_binding_point(); glUniformBlockBinding(*this->handle, block.index, block.binding_point); } -void GlShaderProgram::set_unif(std::shared_ptr const &in, +void GlShaderProgram::set_unif(UniformInput &in, const char *unif, void const *val, + size_t size, GLenum type) { - auto unif_in = std::dynamic_pointer_cast(in); - auto uniform_id = this->uniforms_by_name.find(unif); ENSURE(uniform_id != std::end(this->uniforms_by_name), "Tried to set uniform '" << unif << "' that does not exist in the shader program."); - auto const &unif_info = this->uniforms.at(uniform_id->second); - - ENSURE(type == unif_info.type, - "Tried to set uniform '" << unif << "' to a value of the wrong type."); - - size_t size = get_uniform_type_size(type); - - auto update_off = unif_in->update_offs.find(uniform_id->second); - if (update_off != std::end(unif_in->update_offs)) [[likely]] { // always used after the uniform value is written once - // already wrote to this uniform since last upload - size_t off = update_off->second; - memcpy(unif_in->update_data.data() + off, val, size); - } - else { - // first time writing to this uniform since last upload, so - // extend the buffer before storing the uniform value - size_t prev_size = unif_in->update_data.size(); - unif_in->update_data.resize(prev_size + size); - memcpy(unif_in->update_data.data() + prev_size, val, size); - unif_in->update_offs.emplace(uniform_id->second, prev_size); - } + this->set_unif(in, uniform_id->second, val, size, type); } -void GlShaderProgram::set_unif(std::shared_ptr const &in, - const uniform_id_t &unif_id, +void GlShaderProgram::set_unif(UniformInput &in, + uniform_id_t unif_id, void const *val, + size_t size, GLenum type) { - auto unif_in = std::dynamic_pointer_cast(in); + auto &unif_in = dynamic_cast(in); ENSURE(unif_id < this->uniforms.size(), "Tried to set uniform '" << unif_id << "' that does not exist in the shader program."); - auto const &unif_info = this->uniforms.at(unif_id); + ENSURE(unif_id < this->uniforms.size(), + "Tried to set uniform with invalid ID " << unif_id); + + auto const &unif_info = this->uniforms[unif_id]; ENSURE(type == unif_info.type, "Tried to set uniform '" << unif_id << "' to a value of the wrong type."); - size_t size = get_uniform_type_size(type); - - auto update_off = unif_in->update_offs.find(unif_id); - if (update_off != std::end(unif_in->update_offs)) [[likely]] { // always used after the uniform value is written once - // already wrote to this uniform since last upload - size_t off = update_off->second; - memcpy(unif_in->update_data.data() + off, val, size); - } - else { - // first time writing to this uniform since last upload, so - // extend the buffer before storing the uniform value - size_t prev_size = unif_in->update_data.size(); - unif_in->update_data.resize(prev_size + size); - memcpy(unif_in->update_data.data() + prev_size, val, size); - unif_in->update_offs.emplace(unif_id, prev_size); + auto &update_off = unif_in.update_offs[unif_id]; + auto offset = update_off.offset; + memcpy(unif_in.update_data.data() + offset, val, size); + if (not update_off.used) [[unlikely]] { // only true if the uniform value was not set before + auto lower_bound = std::lower_bound( + std::begin(unif_in.used_uniforms), + std::end(unif_in.used_uniforms), + unif_id); + unif_in.used_uniforms.insert(lower_bound, unif_id); + update_off.used = true; } } -void GlShaderProgram::set_i32(std::shared_ptr const &in, const char *unif, int32_t val) { - this->set_unif(in, unif, &val, GL_INT); +void GlShaderProgram::set_i32(UniformInput &in, const char *unif, int32_t val) { + this->set_unif(in, unif, &val, get_uniform_type_size(GL_INT), GL_INT); } -void GlShaderProgram::set_u32(std::shared_ptr const &in, const char *unif, uint32_t val) { - this->set_unif(in, unif, &val, GL_UNSIGNED_INT); +void GlShaderProgram::set_u32(UniformInput &in, const char *unif, uint32_t val) { + this->set_unif(in, unif, &val, get_uniform_type_size(GL_UNSIGNED_INT), GL_UNSIGNED_INT); } -void GlShaderProgram::set_f32(std::shared_ptr const &in, const char *unif, float val) { - this->set_unif(in, unif, &val, GL_FLOAT); +void GlShaderProgram::set_f32(UniformInput &in, const char *unif, float val) { + this->set_unif(in, unif, &val, get_uniform_type_size(GL_FLOAT), GL_FLOAT); } -void GlShaderProgram::set_f64(std::shared_ptr const &in, const char *unif, double val) { +void GlShaderProgram::set_f64(UniformInput &in, const char *unif, double val) { // TODO requires extension - this->set_unif(in, unif, &val, GL_DOUBLE); + this->set_unif(in, unif, &val, get_uniform_type_size(GL_DOUBLE), GL_DOUBLE); } -void GlShaderProgram::set_bool(std::shared_ptr const &in, const char *unif, bool val) { - this->set_unif(in, unif, &val, GL_BOOL); +void GlShaderProgram::set_bool(UniformInput &in, const char *unif, bool val) { + this->set_unif(in, unif, &val, get_uniform_type_size(GL_BOOL), GL_BOOL); } -void GlShaderProgram::set_v2f32(std::shared_ptr const &in, const char *unif, Eigen::Vector2f const &val) { - this->set_unif(in, unif, &val, GL_FLOAT_VEC2); +void GlShaderProgram::set_v2f32(UniformInput &in, const char *unif, Eigen::Vector2f const &val) { + this->set_unif(in, unif, &val, get_uniform_type_size(GL_FLOAT_VEC2), GL_FLOAT_VEC2); } -void GlShaderProgram::set_v3f32(std::shared_ptr const &in, const char *unif, Eigen::Vector3f const &val) { - this->set_unif(in, unif, &val, GL_FLOAT_VEC3); +void GlShaderProgram::set_v3f32(UniformInput &in, const char *unif, Eigen::Vector3f const &val) { + this->set_unif(in, unif, &val, get_uniform_type_size(GL_FLOAT_VEC3), GL_FLOAT_VEC3); } -void GlShaderProgram::set_v4f32(std::shared_ptr const &in, const char *unif, Eigen::Vector4f const &val) { - this->set_unif(in, unif, &val, GL_FLOAT_VEC4); +void GlShaderProgram::set_v4f32(UniformInput &in, const char *unif, Eigen::Vector4f const &val) { + this->set_unif(in, unif, &val, get_uniform_type_size(GL_FLOAT_VEC4), GL_FLOAT_VEC4); } -void GlShaderProgram::set_v2i32(std::shared_ptr const &in, const char *unif, Eigen::Vector2i const &val) { - this->set_unif(in, unif, &val, GL_INT_VEC2); +void GlShaderProgram::set_v2i32(UniformInput &in, const char *unif, Eigen::Vector2i const &val) { + this->set_unif(in, unif, &val, get_uniform_type_size(GL_INT_VEC2), GL_INT_VEC2); } -void GlShaderProgram::set_v3i32(std::shared_ptr const &in, const char *unif, Eigen::Vector3i const &val) { - this->set_unif(in, unif, &val, GL_INT_VEC3); +void GlShaderProgram::set_v3i32(UniformInput &in, const char *unif, Eigen::Vector3i const &val) { + this->set_unif(in, unif, &val, get_uniform_type_size(GL_INT_VEC3), GL_INT_VEC3); } -void GlShaderProgram::set_v4i32(std::shared_ptr const &in, const char *unif, Eigen::Vector4i const &val) { - this->set_unif(in, unif, &val, GL_INT_VEC4); +void GlShaderProgram::set_v4i32(UniformInput &in, const char *unif, Eigen::Vector4i const &val) { + this->set_unif(in, unif, &val, get_uniform_type_size(GL_INT_VEC4), GL_INT_VEC4); } -void GlShaderProgram::set_v2ui32(std::shared_ptr const &in, const char *unif, Eigen::Vector2 const &val) { - this->set_unif(in, unif, &val, GL_UNSIGNED_INT_VEC2); +void GlShaderProgram::set_v2ui32(UniformInput &in, const char *unif, Eigen::Vector2 const &val) { + this->set_unif(in, unif, &val, get_uniform_type_size(GL_UNSIGNED_INT_VEC2), GL_UNSIGNED_INT_VEC2); } -void GlShaderProgram::set_v3ui32(std::shared_ptr const &in, const char *unif, Eigen::Vector3 const &val) { - this->set_unif(in, unif, &val, GL_UNSIGNED_INT_VEC3); +void GlShaderProgram::set_v3ui32(UniformInput &in, const char *unif, Eigen::Vector3 const &val) { + this->set_unif(in, unif, &val, get_uniform_type_size(GL_UNSIGNED_INT_VEC3), GL_UNSIGNED_INT_VEC3); } -void GlShaderProgram::set_v4ui32(std::shared_ptr const &in, const char *unif, Eigen::Vector4 const &val) { - this->set_unif(in, unif, &val, GL_UNSIGNED_INT_VEC4); +void GlShaderProgram::set_v4ui32(UniformInput &in, const char *unif, Eigen::Vector4 const &val) { + this->set_unif(in, unif, &val, get_uniform_type_size(GL_UNSIGNED_INT_VEC4), GL_UNSIGNED_INT_VEC4); } -void GlShaderProgram::set_m4f32(std::shared_ptr const &in, const char *unif, Eigen::Matrix4f const &val) { - this->set_unif(in, unif, val.data(), GL_FLOAT_MAT4); +void GlShaderProgram::set_m4f32(UniformInput &in, const char *unif, Eigen::Matrix4f const &val) { + this->set_unif(in, unif, val.data(), get_uniform_type_size(GL_FLOAT_MAT4), GL_FLOAT_MAT4); } -void GlShaderProgram::set_tex(std::shared_ptr const &in, const char *unif, std::shared_ptr const &val) { +void GlShaderProgram::set_tex(UniformInput &in, const char *unif, std::shared_ptr const &val) { auto tex = std::dynamic_pointer_cast(val); GLuint handle = tex->get_handle(); - this->set_unif(in, unif, &handle, GL_SAMPLER_2D); + this->set_unif(in, unif, &handle, get_uniform_type_size(GL_SAMPLER_2D), GL_SAMPLER_2D); } -void GlShaderProgram::set_i32(std::shared_ptr const &in, const uniform_id_t &id, int32_t val) { - this->set_unif(in, id, &val, GL_INT); +void GlShaderProgram::set_i32(UniformInput &in, uniform_id_t id, int32_t val) { + this->set_unif(in, id, &val, get_uniform_type_size(GL_INT), GL_INT); } -void GlShaderProgram::set_u32(std::shared_ptr const &in, const uniform_id_t &id, uint32_t val) { - this->set_unif(in, id, &val, GL_UNSIGNED_INT); +void GlShaderProgram::set_u32(UniformInput &in, uniform_id_t id, uint32_t val) { + this->set_unif(in, id, &val, get_uniform_type_size(GL_UNSIGNED_INT), GL_UNSIGNED_INT); } -void GlShaderProgram::set_f32(std::shared_ptr const &in, const uniform_id_t &id, float val) { - this->set_unif(in, id, &val, GL_FLOAT); +void GlShaderProgram::set_f32(UniformInput &in, uniform_id_t id, float val) { + this->set_unif(in, id, &val, get_uniform_type_size(GL_FLOAT), GL_FLOAT); } -void GlShaderProgram::set_f64(std::shared_ptr const &in, const uniform_id_t &id, double val) { +void GlShaderProgram::set_f64(UniformInput &in, uniform_id_t id, double val) { // TODO requires extension - this->set_unif(in, id, &val, GL_DOUBLE); + this->set_unif(in, id, &val, get_uniform_type_size(GL_DOUBLE), GL_DOUBLE); } -void GlShaderProgram::set_bool(std::shared_ptr const &in, const uniform_id_t &id, bool val) { - this->set_unif(in, id, &val, GL_BOOL); +void GlShaderProgram::set_bool(UniformInput &in, uniform_id_t id, bool val) { + this->set_unif(in, id, &val, get_uniform_type_size(GL_BOOL), GL_BOOL); } -void GlShaderProgram::set_v2f32(std::shared_ptr const &in, const uniform_id_t &id, Eigen::Vector2f const &val) { - this->set_unif(in, id, &val, GL_FLOAT_VEC2); +void GlShaderProgram::set_v2f32(UniformInput &in, uniform_id_t id, Eigen::Vector2f const &val) { + this->set_unif(in, id, &val, get_uniform_type_size(GL_FLOAT_VEC2), GL_FLOAT_VEC2); } -void GlShaderProgram::set_v3f32(std::shared_ptr const &in, const uniform_id_t &id, Eigen::Vector3f const &val) { - this->set_unif(in, id, &val, GL_FLOAT_VEC3); +void GlShaderProgram::set_v3f32(UniformInput &in, uniform_id_t id, Eigen::Vector3f const &val) { + this->set_unif(in, id, &val, get_uniform_type_size(GL_FLOAT_VEC3), GL_FLOAT_VEC3); } -void GlShaderProgram::set_v4f32(std::shared_ptr const &in, const uniform_id_t &id, Eigen::Vector4f const &val) { - this->set_unif(in, id, &val, GL_FLOAT_VEC4); +void GlShaderProgram::set_v4f32(UniformInput &in, uniform_id_t id, Eigen::Vector4f const &val) { + this->set_unif(in, id, &val, get_uniform_type_size(GL_FLOAT_VEC4), GL_FLOAT_VEC4); } -void GlShaderProgram::set_v2i32(std::shared_ptr const &in, const uniform_id_t &id, Eigen::Vector2i const &val) { - this->set_unif(in, id, &val, GL_INT_VEC2); +void GlShaderProgram::set_v2i32(UniformInput &in, uniform_id_t id, Eigen::Vector2i const &val) { + this->set_unif(in, id, &val, get_uniform_type_size(GL_INT_VEC2), GL_INT_VEC2); } -void GlShaderProgram::set_v3i32(std::shared_ptr const &in, const uniform_id_t &id, Eigen::Vector3i const &val) { - this->set_unif(in, id, &val, GL_INT_VEC3); +void GlShaderProgram::set_v3i32(UniformInput &in, uniform_id_t id, Eigen::Vector3i const &val) { + this->set_unif(in, id, &val, get_uniform_type_size(GL_INT_VEC3), GL_INT_VEC3); } -void GlShaderProgram::set_v4i32(std::shared_ptr const &in, const uniform_id_t &id, Eigen::Vector4i const &val) { - this->set_unif(in, id, &val, GL_INT_VEC4); +void GlShaderProgram::set_v4i32(UniformInput &in, uniform_id_t id, Eigen::Vector4i const &val) { + this->set_unif(in, id, &val, get_uniform_type_size(GL_INT_VEC4), GL_INT_VEC4); } -void GlShaderProgram::set_v2ui32(std::shared_ptr const &in, const uniform_id_t &id, Eigen::Vector2 const &val) { - this->set_unif(in, id, &val, GL_UNSIGNED_INT_VEC2); +void GlShaderProgram::set_v2ui32(UniformInput &in, uniform_id_t id, Eigen::Vector2 const &val) { + this->set_unif(in, id, &val, get_uniform_type_size(GL_UNSIGNED_INT_VEC2), GL_UNSIGNED_INT_VEC2); } -void GlShaderProgram::set_v3ui32(std::shared_ptr const &in, const uniform_id_t &id, Eigen::Vector3 const &val) { - this->set_unif(in, id, &val, GL_UNSIGNED_INT_VEC3); +void GlShaderProgram::set_v3ui32(UniformInput &in, uniform_id_t id, Eigen::Vector3 const &val) { + this->set_unif(in, id, &val, get_uniform_type_size(GL_UNSIGNED_INT_VEC3), GL_UNSIGNED_INT_VEC3); } -void GlShaderProgram::set_v4ui32(std::shared_ptr const &in, const uniform_id_t &id, Eigen::Vector4 const &val) { - this->set_unif(in, id, &val, GL_UNSIGNED_INT_VEC4); +void GlShaderProgram::set_v4ui32(UniformInput &in, uniform_id_t id, Eigen::Vector4 const &val) { + this->set_unif(in, id, &val, get_uniform_type_size(GL_UNSIGNED_INT_VEC4), GL_UNSIGNED_INT_VEC4); } -void GlShaderProgram::set_m4f32(std::shared_ptr const &in, const uniform_id_t &id, Eigen::Matrix4f const &val) { - this->set_unif(in, id, val.data(), GL_FLOAT_MAT4); +void GlShaderProgram::set_m4f32(UniformInput &in, uniform_id_t id, Eigen::Matrix4f const &val) { + this->set_unif(in, id, val.data(), get_uniform_type_size(GL_FLOAT_MAT4), GL_FLOAT_MAT4); } -void GlShaderProgram::set_tex(std::shared_ptr const &in, const uniform_id_t &id, std::shared_ptr const &val) { +void GlShaderProgram::set_tex(UniformInput &in, uniform_id_t id, std::shared_ptr const &val) { auto tex = std::dynamic_pointer_cast(val); GLuint handle = tex->get_handle(); - this->set_unif(in, id, &handle, GL_SAMPLER_2D); + this->set_unif(in, id, &handle, get_uniform_type_size(GL_SAMPLER_2D), GL_SAMPLER_2D); } } // namespace openage::renderer::opengl diff --git a/libopenage/renderer/opengl/shader_program.h b/libopenage/renderer/opengl/shader_program.h index 7fa2d96c7a..e5c75374c3 100644 --- a/libopenage/renderer/opengl/shader_program.h +++ b/libopenage/renderer/opengl/shader_program.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 @@ -30,51 +30,80 @@ class GlShaderProgram final : public ShaderProgram , public GlSimpleObject { public: /** - * Tries to create a shader program from the given sources. + * Tries to create a shader program from the given sources. * Throws an exception on compile/link errors. - */ + */ explicit GlShaderProgram(const std::shared_ptr &, const std::vector &); /** - * Bind this program as the currently used one in the OpenGL context. - */ + * Bind this program as the currently used one in the OpenGL context. + */ void use(); /** - * Check if this program is currently in use in the OpenGL context. - * - * @return true if the program is in use, false otherwise. - */ + * Check if this program is currently in use in the OpenGL context. + * + * @return true if the program is in use, false otherwise. + */ bool in_use() const; /** - * Updates the uniform values with the given input specification. - * - * @param input The uniform input specification. - */ + * Updates the uniform values with the given input specification. + * + * @param input The uniform input specification. + */ void update_uniforms(std::shared_ptr const &unif_in); /** - * Get the uniform block with the given name. - * - * @param block_name Name of the uniform block. - * - * @return Uniform block. - */ + * Get the uniform block with the given name. + * + * @param block_name Name of the uniform block. + * + * @return Uniform block. + */ const GlUniformBlock &get_uniform_block(const char *block_name) const; + /** + * Get the uniform ID for the given uniform name. + * + * @param name Name of the uniform in the shader code. + * + * @return ID of the uniform. + */ uniform_id_t get_uniform_id(const char *name) override; + /** + * Get the uniforms in the default block of the shader program. + * This does not include uniforms in blocks. + * + * @return Uniforms in the shader program. + */ + const std::vector &get_uniforms() const; + + /** + * Get the map of uniform blocks in the shader program. + * + * @return Uniform blocks in the shader program. + */ + const std::unordered_map &get_uniform_blocks() const; + + /** + * Check whether the shader program contains a uniform variable with the given name. + * + * @param name Name of the uniform in the shader code. + * + * @return true if the shader program contains the uniform, false otherwise. + */ bool has_uniform(const char *name) override; /** - * Binds a uniform block in the shader program to the same binding point as - * the given uniform buffer. - * - * @param buffer Uniform buffer to bind. - * @param block_name Name of the uniform block in the shader program. - */ + * Binds a uniform block in the shader program to the same binding point as + * the given uniform buffer. + * + * @param buffer Uniform buffer to bind. + * @param block_name Name of the uniform block in the shader program. + */ void bind_uniform_buffer(const char *block_name, std::shared_ptr const &) override; @@ -82,39 +111,39 @@ class GlShaderProgram final : public ShaderProgram protected: std::shared_ptr new_unif_in() override; - void set_i32(std::shared_ptr const &, const char *, int32_t) override; - void set_u32(std::shared_ptr const &, const char *, uint32_t) override; - void set_f32(std::shared_ptr const &, const char *, float) override; - void set_f64(std::shared_ptr const &, const char *, double) override; - void set_bool(std::shared_ptr const &, const char *, bool) override; - void set_v2f32(std::shared_ptr const &, const char *, Eigen::Vector2f const &) override; - void set_v3f32(std::shared_ptr const &, const char *, Eigen::Vector3f const &) override; - void set_v4f32(std::shared_ptr const &, const char *, Eigen::Vector4f const &) override; - void set_v2i32(std::shared_ptr const &, const char *, Eigen::Vector2i const &) override; - void set_v3i32(std::shared_ptr const &, const char *, Eigen::Vector3i const &) override; - void set_v4i32(std::shared_ptr const &, const char *, Eigen::Vector4i const &) override; - void set_v2ui32(std::shared_ptr const &, const char *, Eigen::Vector2 const &) override; - void set_v3ui32(std::shared_ptr const &, const char *, Eigen::Vector3 const &) override; - void set_v4ui32(std::shared_ptr const &, const char *, Eigen::Vector4 const &) override; - void set_m4f32(std::shared_ptr const &, const char *, Eigen::Matrix4f const &) override; - void set_tex(std::shared_ptr const &, const char *, std::shared_ptr const &) override; - - void set_i32(std::shared_ptr const &, const uniform_id_t &, int32_t) override; - void set_u32(std::shared_ptr const &, const uniform_id_t &, uint32_t) override; - void set_f32(std::shared_ptr const &, const uniform_id_t &, float) override; - void set_f64(std::shared_ptr const &, const uniform_id_t &, double) override; - void set_bool(std::shared_ptr const &, const uniform_id_t &, bool) override; - void set_v2f32(std::shared_ptr const &, const uniform_id_t &, Eigen::Vector2f const &) override; - void set_v3f32(std::shared_ptr const &, const uniform_id_t &, Eigen::Vector3f const &) override; - void set_v4f32(std::shared_ptr const &, const uniform_id_t &, Eigen::Vector4f const &) override; - void set_v2i32(std::shared_ptr const &, const uniform_id_t &, Eigen::Vector2i const &) override; - void set_v3i32(std::shared_ptr const &, const uniform_id_t &, Eigen::Vector3i const &) override; - void set_v4i32(std::shared_ptr const &, const uniform_id_t &, Eigen::Vector4i const &) override; - void set_v2ui32(std::shared_ptr const &, const uniform_id_t &, Eigen::Vector2 const &) override; - void set_v3ui32(std::shared_ptr const &, const uniform_id_t &, Eigen::Vector3 const &) override; - void set_v4ui32(std::shared_ptr const &, const uniform_id_t &, Eigen::Vector4 const &) override; - void set_m4f32(std::shared_ptr const &, const uniform_id_t &, Eigen::Matrix4f const &) override; - void set_tex(std::shared_ptr const &, const uniform_id_t &, std::shared_ptr const &) override; + void set_i32(UniformInput &in, const char *, int32_t) override; + void set_u32(UniformInput &in, const char *, uint32_t) override; + void set_f32(UniformInput &in, const char *, float) override; + void set_f64(UniformInput &in, const char *, double) override; + void set_bool(UniformInput &in, const char *, bool) override; + void set_v2f32(UniformInput &in, const char *, Eigen::Vector2f const &) override; + void set_v3f32(UniformInput &in, const char *, Eigen::Vector3f const &) override; + void set_v4f32(UniformInput &in, const char *, Eigen::Vector4f const &) override; + void set_v2i32(UniformInput &in, const char *, Eigen::Vector2i const &) override; + void set_v3i32(UniformInput &in, const char *, Eigen::Vector3i const &) override; + void set_v4i32(UniformInput &in, const char *, Eigen::Vector4i const &) override; + void set_v2ui32(UniformInput &in, const char *, Eigen::Vector2 const &) override; + void set_v3ui32(UniformInput &in, const char *, Eigen::Vector3 const &) override; + void set_v4ui32(UniformInput &in, const char *, Eigen::Vector4 const &) override; + void set_m4f32(UniformInput &in, const char *, Eigen::Matrix4f const &) override; + void set_tex(UniformInput &in, const char *, std::shared_ptr const &) override; + + void set_i32(UniformInput &in, uniform_id_t id, int32_t) override; + void set_u32(UniformInput &in, uniform_id_t id, uint32_t) override; + void set_f32(UniformInput &in, uniform_id_t id, float) override; + void set_f64(UniformInput &in, uniform_id_t id, double) override; + void set_bool(UniformInput &in, uniform_id_t id, bool) override; + void set_v2f32(UniformInput &in, uniform_id_t id, Eigen::Vector2f const &) override; + void set_v3f32(UniformInput &in, uniform_id_t id, Eigen::Vector3f const &) override; + void set_v4f32(UniformInput &in, uniform_id_t id, Eigen::Vector4f const &) override; + void set_v2i32(UniformInput &in, uniform_id_t id, Eigen::Vector2i const &) override; + void set_v3i32(UniformInput &in, uniform_id_t id, Eigen::Vector3i const &) override; + void set_v4i32(UniformInput &in, uniform_id_t id, Eigen::Vector4i const &) override; + void set_v2ui32(UniformInput &in, uniform_id_t id, Eigen::Vector2 const &) override; + void set_v3ui32(UniformInput &in, uniform_id_t id, Eigen::Vector3 const &) override; + void set_v4ui32(UniformInput &in, uniform_id_t id, Eigen::Vector4 const &) override; + void set_m4f32(UniformInput &in, uniform_id_t id, Eigen::Matrix4f const &) override; + void set_tex(UniformInput &in, uniform_id_t id, std::shared_ptr const &) override; private: /** @@ -125,22 +154,32 @@ class GlShaderProgram final : public ShaderProgram * If performance is important, use the alternative \p set_unif(..) implementation * that works on IDs instead. * - * @param unif_in Uniform input. + * @param in Uniform input. * @param name Name of the uniform. * @param value Value to set. + * @param size Size of the value (in bytes). * @param type Type of the value. */ - void set_unif(std::shared_ptr const &, const char *, void const *, GLenum); + void set_unif(UniformInput &in, + const char *name, + void const *value, + size_t size, + GLenum type); /** * Set the uniform value via uniform ID from a uniform input. * - * @param unif_in Uniform input. - * @param id ID of the uniform. + * @param in Uniform input. + * @param unif_id ID of the uniform. * @param value Value to set. + * @param size Size of the value (in bytes). * @param type Type of the value. */ - void set_unif(std::shared_ptr const &, const uniform_id_t &, void const *, GLenum); + void set_unif(UniformInput &in, + uniform_id_t unif_id, + void const *value, + size_t size, + GLenum type); /// Uniforms in the shader program. Contains only /// uniforms in the default block, i.e. not within named blocks. @@ -155,11 +194,9 @@ class GlShaderProgram final : public ShaderProgram /// Maps per-vertex attribute names to their descriptions. std::unordered_map attribs; - /// Maps sampler uniform names to their assigned texture units. - std::unordered_map texunits_per_unifs; - - /// Maps texture units to the texture handles that are currently bound to them. - std::unordered_map textures_per_texunits; + /// Store which texture handles are currently bound to the shader's texture units. + /// A value of std::nullopt means the texture unit is unbound (no texture assigned). + std::vector> textures_per_texunits; /// Whether this program has been validated. bool validated; diff --git a/libopenage/renderer/opengl/texture.cpp b/libopenage/renderer/opengl/texture.cpp index d3d83051a2..5e795bcd82 100644 --- a/libopenage/renderer/opengl/texture.cpp +++ b/libopenage/renderer/opengl/texture.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 "texture.h" @@ -54,7 +54,8 @@ GlTexture2d::GlTexture2d(const std::shared_ptr &context, glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - log::log(MSG(dbg) << "Created OpenGL texture from data"); + log::log(MSG(dbg) << "Created OpenGL texture from data (size: " + << size.first << "x" << size.second << ")"); } GlTexture2d::GlTexture2d(const std::shared_ptr &context, @@ -70,7 +71,7 @@ GlTexture2d::GlTexture2d(const std::shared_ptr &context, auto fmt_in_out = GL_PIXEL_FORMAT.get(this->info.get_format()); - auto dims = this->info.get_size(); + auto size = this->info.get_size(); glPixelStorei(GL_UNPACK_ALIGNMENT, this->info.get_row_alignment()); @@ -78,8 +79,8 @@ GlTexture2d::GlTexture2d(const std::shared_ptr &context, GL_TEXTURE_2D, 0, std::get<0>(fmt_in_out), - dims.first, - dims.second, + size.first, + size.second, 0, std::get<1>(fmt_in_out), std::get<2>(fmt_in_out), @@ -89,7 +90,8 @@ GlTexture2d::GlTexture2d(const std::shared_ptr &context, glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - log::log(MSG(dbg) << "Created OpenGL texture from parameters"); + log::log(MSG(dbg) << "Created OpenGL texture from info parameters (size: " + << size.first << "x" << size.second << ")"); } resources::Texture2dData GlTexture2d::into_data() { diff --git a/libopenage/renderer/opengl/texture_array.cpp b/libopenage/renderer/opengl/texture_array.cpp index 2266145cbe..41821b5180 100644 --- a/libopenage/renderer/opengl/texture_array.cpp +++ b/libopenage/renderer/opengl/texture_array.cpp @@ -1,4 +1,4 @@ -// Copyright 2018-2023 the openage authors. See copying.md for legal info. +// Copyright 2018-2024 the openage authors. See copying.md for legal info. #include "texture_array.h" @@ -31,20 +31,22 @@ GlTexture2dArray::GlTexture2dArray(const std::shared_ptr &context, size_t i = 0; for (auto const &tex : data) { glTexSubImage3D(GL_TEXTURE_2D_ARRAY, - 0, // mipmap number - 0, - 0, - i, // xoffset, yoffset, zoffset - size.first, - size.second, - 1, // width, height, depth + 0, // mipmap number + 0, // xoffset + 0, // yoffset + i, // zoffset + size.first, // width + size.second, // height, + 1, // depth std::get<1>(fmt_in_out), // format std::get<2>(fmt_in_out), // type - tex.get_data() // data + tex.get_data() // data ); i += 1; } + + log::log(MSG(dbg) << "Created OpenGL texture array from data"); } GlTexture2dArray::GlTexture2dArray(const std::shared_ptr &context, @@ -65,22 +67,22 @@ GlTexture2dArray::GlTexture2dArray(const std::shared_ptr &context, // Create empty image glTexImage3D(GL_TEXTURE_2D_ARRAY, - 0, // mipmap level + 0, // mipmap level std::get<0>(fmt_in_out), // gpu texel format - size.first, // width - size.second, // height - n_layers, // depth - 0, // border + size.first, // width + size.second, // height + n_layers, // depth + 0, // border std::get<1>(fmt_in_out), // cpu pixel format std::get<2>(fmt_in_out), // cpu pixel type - nullptr // data + nullptr // data ); // TODO these are outdated, use sampler settings glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - log::log(MSG(dbg) << "Created an OpenGL texture array."); + log::log(MSG(dbg) << "Created OpenGL texture array from info parameters"); } void GlTexture2dArray::upload(size_t layer, resources::Texture2dData const &data) { @@ -101,10 +103,10 @@ void GlTexture2dArray::upload(size_t layer, resources::Texture2dData const &data layer, // xoffset, yoffset, zoffset size.first, size.second, - 1, // width, height, depth + 1, // width, height, depth std::get<1>(fmt_in_out), // format std::get<2>(fmt_in_out), // type - data.get_data() // data + data.get_data() // data ); } diff --git a/libopenage/renderer/opengl/uniform_buffer.cpp b/libopenage/renderer/opengl/uniform_buffer.cpp index 28f78edb18..cc3aa3d3dc 100644 --- a/libopenage/renderer/opengl/uniform_buffer.cpp +++ b/libopenage/renderer/opengl/uniform_buffer.cpp @@ -1,8 +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. #include "uniform_buffer.h" #include "error/error.h" +#include "log/log.h" + #include "renderer/opengl/context.h" #include "renderer/opengl/lookup.h" #include "renderer/opengl/texture.h" @@ -14,7 +16,7 @@ namespace openage::renderer::opengl { GlUniformBuffer::GlUniformBuffer(const std::shared_ptr &context, size_t size, - std::unordered_map uniforms, + std::vector uniforms, GLuint binding_point, GLenum usage) : GlSimpleObject(context, @@ -31,7 +33,17 @@ GlUniformBuffer::GlUniformBuffer(const std::shared_ptr &context, this->bind(); glBufferData(GL_UNIFORM_BUFFER, this->data_size, NULL, usage); + uniform_id_t unif_id = 0; + for (auto &uniform : uniforms) { + this->uniforms_by_name.insert(std::make_pair(uniform.name, unif_id)); + unif_id += 1; + } + glBindBufferRange(GL_UNIFORM_BUFFER, this->binding_point, *this->handle, 0, this->data_size); + + log::log(MSG(dbg) << "Created OpenGL uniform buffer (size: " + << this->data_size << ", binding point: " + << this->binding_point << ")"); } GLuint GlUniformBuffer::get_binding_point() const { @@ -45,23 +57,35 @@ void GlUniformBuffer::set_binding_point(GLuint binding_point) { void GlUniformBuffer::update_uniforms(std::shared_ptr const &unif_in) { auto glunif_in = std::dynamic_pointer_cast(unif_in); - ENSURE(glunif_in->get_buffer() == this->shared_from_this(), "Uniform input passed to different buffer than it was created with."); + ENSURE(glunif_in->get_buffer().get() == this, "Uniform input passed to different buffer than it was created with."); this->bind(); + const auto &update_offs = glunif_in->update_offs; + const auto &used_uniforms = glunif_in->used_uniforms; + const auto &uniforms = this->uniforms; uint8_t const *data = glunif_in->update_data.data(); - for (auto const &pair : glunif_in->update_offs) { - uint8_t const *ptr = data + pair.second; - auto unif_def = this->uniforms[pair.first]; - auto loc = unif_def.offset; - auto size = unif_def.size; + + size_t unif_count = used_uniforms.size(); + for (size_t i = 0; i < unif_count; ++i) { + uniform_id_t unif_id = used_uniforms[i]; + auto offset = update_offs[unif_id]; + + uint8_t const *ptr = data + offset.offset; + auto &unif = uniforms[unif_id]; + auto loc = unif.offset; + auto size = unif.size; glBufferSubData(GL_UNIFORM_BUFFER, loc, size, ptr); } } -bool GlUniformBuffer::has_uniform(const char *unif) { - return this->uniforms.count(unif) != 0; +const std::vector &GlUniformBuffer::get_uniforms() const { + return this->uniforms; +} + +bool GlUniformBuffer::has_uniform(const char *name) { + return this->uniforms_by_name.contains(name); } void GlUniformBuffer::bind() const { @@ -74,97 +98,96 @@ std::shared_ptr GlUniformBuffer::new_unif_in() { return in; } -void GlUniformBuffer::set_unif(std::shared_ptr const &in, const char *unif, void const *val, GLenum type) { - auto unif_in = std::dynamic_pointer_cast(in); +void GlUniformBuffer::set_unif(UniformBufferInput &in, const char *unif, void const *val, GLenum type) { + auto &unif_in = dynamic_cast(in); - auto uniform = this->uniforms.find(unif); - ENSURE(uniform != std::end(this->uniforms), - "Tried to set uniform " << unif << " that does not exist in the shader program."); + auto unif_id = this->uniforms_by_name.find(unif)->second; + ENSURE(unif_id < this->uniforms.size(), + "Tried to set uniform with invalid ID " << unif_id); - auto const &unif_data = uniform->second; + auto const &unif_data = this->uniforms[unif_id]; ENSURE(type == unif_data.type, "Tried to set uniform " << unif << " to a value of the wrong type."); - size_t size = get_uniform_type_size(type); - - auto update_off = unif_in->update_offs.find(unif); - if (update_off != std::end(unif_in->update_offs)) [[likely]] { // always used after the uniform value is written once - // already wrote to this uniform since last upload - size_t off = update_off->second; - memcpy(unif_in->update_data.data() + off, val, size); - } - else { - // first time writing to this uniform since last upload, so - // extend the buffer before storing the uniform value - size_t prev_size = unif_in->update_data.size(); - unif_in->update_data.resize(prev_size + size); - memcpy(unif_in->update_data.data() + prev_size, val, size); - unif_in->update_offs.emplace(unif, prev_size); + size_t size = GL_UNIFORM_TYPE_SIZE.get(type); + ENSURE(size == unif_data.size, + "Tried to set uniform " << unif << " to a value of the wrong size."); + + auto &update_off = unif_in.update_offs[unif_id]; + auto offset = update_off.offset; + memcpy(unif_in.update_data.data() + offset, val, size); + if (not update_off.used) [[unlikely]] { // only true if the uniform value was not set before + auto lower_bound = std::lower_bound( + std::begin(unif_in.used_uniforms), + std::end(unif_in.used_uniforms), + unif_id); + unif_in.used_uniforms.insert(lower_bound, unif_id); + update_off.used = true; } } -void GlUniformBuffer::set_i32(std::shared_ptr const &in, const char *unif, int32_t val) { +void GlUniformBuffer::set_i32(UniformBufferInput &in, const char *unif, int32_t val) { this->set_unif(in, unif, &val, GL_INT); } -void GlUniformBuffer::set_u32(std::shared_ptr const &in, const char *unif, uint32_t val) { +void GlUniformBuffer::set_u32(UniformBufferInput &in, const char *unif, uint32_t val) { this->set_unif(in, unif, &val, GL_UNSIGNED_INT); } -void GlUniformBuffer::set_f32(std::shared_ptr const &in, const char *unif, float val) { +void GlUniformBuffer::set_f32(UniformBufferInput &in, const char *unif, float val) { this->set_unif(in, unif, &val, GL_FLOAT); } -void GlUniformBuffer::set_f64(std::shared_ptr const &in, const char *unif, double val) { +void GlUniformBuffer::set_f64(UniformBufferInput &in, const char *unif, double val) { this->set_unif(in, unif, &val, GL_DOUBLE); } -void GlUniformBuffer::set_bool(std::shared_ptr const &in, const char *unif, bool val) { +void GlUniformBuffer::set_bool(UniformBufferInput &in, const char *unif, bool val) { this->set_unif(in, unif, &val, GL_BOOL); } -void GlUniformBuffer::set_v2f32(std::shared_ptr const &in, const char *unif, Eigen::Vector2f const &val) { +void GlUniformBuffer::set_v2f32(UniformBufferInput &in, const char *unif, Eigen::Vector2f const &val) { this->set_unif(in, unif, val.data(), GL_FLOAT_VEC2); } -void GlUniformBuffer::set_v3f32(std::shared_ptr const &in, const char *unif, Eigen::Vector3f const &val) { +void GlUniformBuffer::set_v3f32(UniformBufferInput &in, const char *unif, Eigen::Vector3f const &val) { this->set_unif(in, unif, val.data(), GL_FLOAT_VEC3); } -void GlUniformBuffer::set_v4f32(std::shared_ptr const &in, const char *unif, Eigen::Vector4f const &val) { +void GlUniformBuffer::set_v4f32(UniformBufferInput &in, const char *unif, Eigen::Vector4f const &val) { this->set_unif(in, unif, val.data(), GL_FLOAT_VEC4); } -void GlUniformBuffer::set_v2i32(std::shared_ptr const &in, const char *unif, Eigen::Vector2i const &val) { +void GlUniformBuffer::set_v2i32(UniformBufferInput &in, const char *unif, Eigen::Vector2i const &val) { this->set_unif(in, unif, val.data(), GL_INT_VEC2); } -void GlUniformBuffer::set_v3i32(std::shared_ptr const &in, const char *unif, Eigen::Vector3i const &val) { +void GlUniformBuffer::set_v3i32(UniformBufferInput &in, const char *unif, Eigen::Vector3i const &val) { this->set_unif(in, unif, val.data(), GL_INT_VEC3); } -void GlUniformBuffer::set_v4i32(std::shared_ptr const &in, const char *unif, Eigen::Vector4i const &val) { +void GlUniformBuffer::set_v4i32(UniformBufferInput &in, const char *unif, Eigen::Vector4i const &val) { this->set_unif(in, unif, val.data(), GL_INT_VEC4); } -void GlUniformBuffer::set_v2ui32(std::shared_ptr const &in, const char *unif, Eigen::Vector2 const &val) { +void GlUniformBuffer::set_v2ui32(UniformBufferInput &in, const char *unif, Eigen::Vector2 const &val) { this->set_unif(in, unif, val.data(), GL_UNSIGNED_INT_VEC2); } -void GlUniformBuffer::set_v3ui32(std::shared_ptr const &in, const char *unif, Eigen::Vector3 const &val) { +void GlUniformBuffer::set_v3ui32(UniformBufferInput &in, const char *unif, Eigen::Vector3 const &val) { this->set_unif(in, unif, val.data(), GL_UNSIGNED_INT_VEC3); } -void GlUniformBuffer::set_v4ui32(std::shared_ptr const &in, const char *unif, Eigen::Vector4 const &val) { +void GlUniformBuffer::set_v4ui32(UniformBufferInput &in, const char *unif, Eigen::Vector4 const &val) { this->set_unif(in, unif, val.data(), GL_UNSIGNED_INT_VEC4); } -void GlUniformBuffer::set_m4f32(std::shared_ptr const &in, const char *unif, Eigen::Matrix4f const &val) { +void GlUniformBuffer::set_m4f32(UniformBufferInput &in, const char *unif, Eigen::Matrix4f const &val) { this->set_unif(in, unif, val.data(), GL_FLOAT_MAT4); } -void GlUniformBuffer::set_tex(std::shared_ptr const &in, const char *unif, std::shared_ptr const &val) { +void GlUniformBuffer::set_tex(UniformBufferInput &in, const char *unif, std::shared_ptr const &val) { auto tex = std::dynamic_pointer_cast(val); GLuint handle = tex->get_handle(); this->set_unif(in, unif, &handle, GL_SAMPLER_2D); diff --git a/libopenage/renderer/opengl/uniform_buffer.h b/libopenage/renderer/opengl/uniform_buffer.h index e26f101efc..24fd1e23a2 100644 --- a/libopenage/renderer/opengl/uniform_buffer.h +++ b/libopenage/renderer/opengl/uniform_buffer.h @@ -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. #pragma once @@ -22,22 +22,29 @@ class GlUniformBuffer final : public UniformBuffer public: GlUniformBuffer(const std::shared_ptr &context, size_t size, - std::unordered_map uniforms, + std::vector uniforms, GLuint binding_point = 0, GLenum usage = GL_DYNAMIC_DRAW); /** - * Get the binding point of the buffer. - * - * @return Binding point ID. - */ + * Get the binding point of the buffer. + * + * @return Binding point ID. + */ GLuint get_binding_point() const; /** - * Set the binding point of the buffer. - * - * @param binding_point Binding point ID. - */ + * Get the uniform buffers uniforms. + * + * @return Uniforms in the shader program. + */ + const std::vector &get_uniforms() const; + + /** + * Set the binding point of the buffer. + * + * @param binding_point Binding point ID. + */ void set_binding_point(GLuint binding_point); void update_uniforms(std::shared_ptr const &unif_in) override; @@ -45,59 +52,64 @@ class GlUniformBuffer final : public UniformBuffer bool has_uniform(const char *) override; /** - * Bind the buffer. - */ + * Bind the buffer. + */ void bind() const; protected: std::shared_ptr new_unif_in() override; - void set_i32(std::shared_ptr const &, const char *, int32_t) override; - void set_u32(std::shared_ptr const &, const char *, uint32_t) override; - void set_f32(std::shared_ptr const &, const char *, float) override; - void set_f64(std::shared_ptr const &, const char *, double) override; - void set_bool(std::shared_ptr const &, const char *, bool) override; - void set_v2f32(std::shared_ptr const &, const char *, Eigen::Vector2f const &) override; - void set_v3f32(std::shared_ptr const &, const char *, Eigen::Vector3f const &) override; - void set_v4f32(std::shared_ptr const &, const char *, Eigen::Vector4f const &) override; - void set_v2i32(std::shared_ptr const &, const char *, Eigen::Vector2i const &) override; - void set_v3i32(std::shared_ptr const &, const char *, Eigen::Vector3i const &) override; - void set_v4i32(std::shared_ptr const &, const char *, Eigen::Vector4i const &) override; - void set_v2ui32(std::shared_ptr const &, const char *, Eigen::Vector2 const &) override; - void set_v3ui32(std::shared_ptr const &, const char *, Eigen::Vector3 const &) override; - void set_v4ui32(std::shared_ptr const &, const char *, Eigen::Vector4 const &) override; - void set_m4f32(std::shared_ptr const &, const char *, Eigen::Matrix4f const &) override; - void set_tex(std::shared_ptr const &, const char *, std::shared_ptr const &) override; + void set_i32(UniformBufferInput &in, const char *, int32_t) override; + void set_u32(UniformBufferInput &in, const char *, uint32_t) override; + void set_f32(UniformBufferInput &in, const char *, float) override; + void set_f64(UniformBufferInput &in, const char *, double) override; + void set_bool(UniformBufferInput &in, const char *, bool) override; + void set_v2f32(UniformBufferInput &in, const char *, Eigen::Vector2f const &) override; + void set_v3f32(UniformBufferInput &in, const char *, Eigen::Vector3f const &) override; + void set_v4f32(UniformBufferInput &in, const char *, Eigen::Vector4f const &) override; + void set_v2i32(UniformBufferInput &in, const char *, Eigen::Vector2i const &) override; + void set_v3i32(UniformBufferInput &in, const char *, Eigen::Vector3i const &) override; + void set_v4i32(UniformBufferInput &in, const char *, Eigen::Vector4i const &) override; + void set_v2ui32(UniformBufferInput &in, const char *, Eigen::Vector2 const &) override; + void set_v3ui32(UniformBufferInput &in, const char *, Eigen::Vector3 const &) override; + void set_v4ui32(UniformBufferInput &in, const char *, Eigen::Vector4 const &) override; + void set_m4f32(UniformBufferInput &in, const char *, Eigen::Matrix4f const &) override; + void set_tex(UniformBufferInput &in, const char *, std::shared_ptr const &) override; private: /** - * Update a uniform value in a uniform buffer input object. - * - * Note that the uniform buffer itself is not updated by this. Data is only uploaded - * to the buffer when \p update_uniforms is eventually called. - * - * @param in Uniform buffer input object. - * @param name Name of the uniform. - * @param val Pointer to the value to update the uniform with. - * @param type Type of the uniform. - */ - void set_unif(std::shared_ptr const &in, + * Update a uniform value in a uniform buffer input object. + * + * Note that the uniform buffer itself is not updated by this. Data is only uploaded + * to the buffer when \p update_uniforms is eventually called. + * + * @param in Uniform buffer input object. + * @param name Name of the uniform. + * @param val Pointer to the value to update the uniform with. + * @param type Type of the uniform. + */ + void set_unif(UniformBufferInput &in, const char *name, void const *val, GLenum type); /** - * Uniform definitions inside the buffer. - */ - std::unordered_map uniforms; + * Uniform definitions inside the buffer. + */ + std::vector uniforms; + + /** + * Maps uniform names to their ID (the index in the uniform vector). + */ + std::unordered_map uniforms_by_name; /** - * Size of the buffer (in bytes). - */ + * Size of the buffer (in bytes). + */ size_t data_size; /** - * Binding point of the buffer. - */ + * Binding point of the buffer. + */ GLuint binding_point; }; diff --git a/libopenage/renderer/opengl/uniform_input.cpp b/libopenage/renderer/opengl/uniform_input.cpp index d8c7eeaba8..e6caed4ca8 100644 --- a/libopenage/renderer/opengl/uniform_input.cpp +++ b/libopenage/renderer/opengl/uniform_input.cpp @@ -1,13 +1,62 @@ -// Copyright 2019-2023 the openage authors. See copying.md for legal info. +// Copyright 2019-2024 the openage authors. See copying.md for legal info. #include "uniform_input.h" +#include "renderer/opengl/lookup.h" +#include "renderer/opengl/shader_program.h" +#include "renderer/opengl/uniform_buffer.h" +#include "renderer/opengl/util.h" + + namespace openage::renderer::opengl { -GlUniformInput::GlUniformInput(std::shared_ptr const &prog) : - UniformInput{prog} {} +GlUniformInput::GlUniformInput(const std::shared_ptr &prog) : + UniformInput{prog} { + auto glprog = std::dynamic_pointer_cast(prog); + + // Reserve space for the used uniforms. + this->used_uniforms.reserve(glprog->get_uniforms().size()); + + // Calculate the byte-wise offsets of all uniforms. + size_t offset = 0; + this->update_offs.reserve(glprog->get_uniforms().size()); + for (auto &uniform : glprog->get_uniforms()) { + this->update_offs.push_back({offset, false}); + offset += GL_UNIFORM_TYPE_SIZE.get(uniform.type); + } + + // Resize the update data buffer to the total size of all uniforms. + this->update_data.resize(offset); +} + +GlUniformBufferInput::GlUniformBufferInput(const std::shared_ptr &buffer) : + UniformBufferInput{buffer} { + auto glBuf = std::dynamic_pointer_cast(buffer); + + auto uniforms = glBuf->get_uniforms(); + + // Reserve space for the used uniforms. + this->used_uniforms.reserve(uniforms.size()); + + // Calculate the byte-wise offsets of all uniforms. + size_t offset = 0; + this->update_offs.reserve(uniforms.size()); + for (auto &uniform : uniforms) { + this->update_offs.push_back({uniform.offset, false}); + offset += GL_UNIFORM_TYPE_SIZE.get(uniform.type); + } + + // Resize the update data buffer to the total size of all uniforms. + this->update_data.resize(offset); +} -GlUniformBufferInput::GlUniformBufferInput(std::shared_ptr const &buffer) : - UniformBufferInput{buffer} {} +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 4fdb79e72e..c46c235414 100644 --- a/libopenage/renderer/opengl/uniform_input.h +++ b/libopenage/renderer/opengl/uniform_input.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 @@ -18,27 +18,44 @@ class UniformBuffer; namespace opengl { class GlShaderProgram; +class GlUniformBuffer; /** * Describes OpenGL-specific uniform valuations. */ class GlUniformInput final : public UniformInput { +private: + struct GlUniformOffset { + // Offset in the update_data buffer. + size_t offset; + /// Dtermine whether the uniform value has been set. + bool used; + }; + public: - GlUniformInput(std::shared_ptr const &); + 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. + */ + std::vector used_uniforms; /** - * We store uniform updates lazily. They are only actually uploaded to GPU - * when a draw call is made. - * - * \p update_offs maps the uniform IDs to where their - * value is in \p update_data in terms of a byte-wise offset. This is only a partial - * valuation, so not all uniforms have to be present here. - */ - std::unordered_map update_offs; + * Store offsets of uniforms in the update_data buffer and + * whether the uniform value has been set. + * + * Index in the vector corresponds to the uniform ID in the shader. + */ + std::vector update_offs; /** - * Buffer containing untyped uniform update data. - */ + * Buffer containing untyped uniform update data. + */ std::vector update_data; }; @@ -46,22 +63,32 @@ class GlUniformInput final : public UniformInput { * Describes OpenGL-specific uniform buffer valuations. */ class GlUniformBufferInput final : public UniformBufferInput { +private: + struct GlUniformOffset { + // Offset in the update_data buffer. + size_t offset; + /// Dtermine whether the uniform value has been set. + bool used; + }; + + public: - GlUniformBufferInput(std::shared_ptr const &); + GlUniformBufferInput(const std::shared_ptr &buffer); + + /** + * Store the IDs of the uniforms from the shader set by this uniform input. + */ + std::vector used_uniforms; /** - * We store uniform updates lazily. They are only actually uploaded to GPU - * when a draw call is made. - * - * \p update_offs maps the uniform names to where their - * value is in \p update_data in terms of a byte-wise offset. This is only a partial - * valuation, so not all uniforms have to be present here. - */ - std::unordered_map update_offs; + * Store offsets of uniforms in the update_data buffer and + * whether the uniform value has been set. + */ + std::vector update_offs; /** - * Buffer containing untyped uniform update data. - */ + * Buffer containing untyped uniform update data. + */ std::vector update_data; }; diff --git a/libopenage/renderer/opengl/util.cpp b/libopenage/renderer/opengl/util.cpp index 7a8a6e2f91..81185b6d97 100644 --- a/libopenage/renderer/opengl/util.cpp +++ b/libopenage/renderer/opengl/util.cpp @@ -1,68 +1,7 @@ -// 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 "util.h" -#include "error/error.h" - - namespace openage::renderer::opengl { -size_t get_uniform_type_size(GLenum type) { - switch (type) { - case GL_BOOL: - return 1; - break; - case GL_BOOL_VEC2: - return 2; - break; - case GL_BOOL_VEC3: - return 3; - break; - case GL_BOOL_VEC4: - case GL_FLOAT: - case GL_INT: - case GL_UNSIGNED_INT: - case GL_SAMPLER_1D: - case GL_SAMPLER_2D: - case GL_SAMPLER_2D_ARRAY: - case GL_SAMPLER_3D: - case GL_SAMPLER_CUBE: - return 4; - break; - - case GL_FLOAT_VEC2: - case GL_INT_VEC2: - case GL_UNSIGNED_INT_VEC2: - return 8; - break; - - case GL_FLOAT_VEC3: - case GL_INT_VEC3: - case GL_UNSIGNED_INT_VEC3: - return 12; - break; - - case GL_FLOAT_VEC4: - case GL_INT_VEC4: - case GL_UNSIGNED_INT_VEC4: - case GL_FLOAT_MAT2: - return 16; - break; - - case GL_FLOAT_MAT3: - return 36; - break; - - case GL_FLOAT_MAT4: - return 64; - break; - - default: - throw Error(MSG(err) << "Unknown GL uniform type: " << type); - break; - } - - return 0; -} - } // namespace openage::renderer::opengl diff --git a/libopenage/renderer/opengl/util.h b/libopenage/renderer/opengl/util.h index 3ad5b4626e..5ed01ed43c 100644 --- a/libopenage/renderer/opengl/util.h +++ b/libopenage/renderer/opengl/util.h @@ -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. #pragma once @@ -6,16 +6,22 @@ #include +#include "renderer/opengl/lookup.h" + namespace openage::renderer::opengl { /** * Get the sizes of a uniform value with a given uniform type. * + * Guaranteed to be a evaluated at compile time. + * * @param type Uniform type. * * @return Size of uniform value (in bytes). */ -size_t get_uniform_type_size(GLenum type); +consteval size_t get_uniform_type_size(GLenum type) { + return GL_UNIFORM_TYPE_SIZE.get(type); +} } // namespace openage::renderer::opengl diff --git a/libopenage/renderer/opengl/window.cpp b/libopenage/renderer/opengl/window.cpp index daedbf7537..548575dd2d 100644 --- a/libopenage/renderer/opengl/window.cpp +++ b/libopenage/renderer/opengl/window.cpp @@ -1,27 +1,25 @@ -// Copyright 2018-2023 the openage authors. See copying.md for legal info. +// Copyright 2018-2024 the openage authors. See copying.md for legal info. #include "window.h" +#include +#include +#include +#include + #include "error/error.h" -#include "gui/guisys/public/gui_application.h" #include "log/log.h" + #include "renderer/opengl/context.h" #include "renderer/opengl/renderer.h" #include "renderer/window_event_handler.h" -#include -#include -#include -#include - namespace openage::renderer::opengl { GlWindow::GlWindow(const std::string &title, - size_t width, - size_t height, - bool debug) : - Window{width, height} { + window_settings settings) : + Window{settings.width, settings.height} { if (QGuiApplication::instance() == nullptr) { // Qt windows need to attach to a QtGuiApplication throw Error{MSG(err) << "Failed to create Qt window: QGuiApplication has not been created yet."}; @@ -31,7 +29,7 @@ GlWindow::GlWindow(const std::string &title, this->window = std::make_shared(); this->window->setTitle(QString::fromStdString(title)); - this->window->resize(width, height); + this->window->resize(settings.width, settings.height); this->window->setSurfaceType(QSurface::OpenGLSurface); @@ -40,6 +38,10 @@ GlWindow::GlWindow(const std::string &title, format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile); format.setSwapBehavior(QSurfaceFormat::SwapBehavior::DoubleBuffer); + if (not settings.vsync) { + format.setSwapInterval(0); + } + format.setMajorVersion(gl_specs.major_version); format.setMinorVersion(gl_specs.minor_version); @@ -47,7 +49,7 @@ GlWindow::GlWindow(const std::string &title, format.setDepthBufferSize(24); format.setStencilBufferSize(8); - if (debug) { + if (settings.debug) { format.setOption(QSurfaceFormat::DebugContext); } @@ -55,7 +57,24 @@ GlWindow::GlWindow(const std::string &title, this->window->setFormat(format); this->window->create(); - this->context = std::make_shared(this->window, debug); + // 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."}; } @@ -120,13 +139,18 @@ void GlWindow::update() { } break; case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: - case QEvent::MouseMove: case QEvent::MouseButtonDblClick: { auto const ev = std::dynamic_pointer_cast(event); for (auto &cb : this->on_mouse_button) { cb(*ev); } } break; + case QEvent::MouseMove: { + auto const ev = std::dynamic_pointer_cast(event); + for (auto &cb : this->on_mouse_move) { + cb(*ev); + } + } break; case QEvent::Wheel: { auto const ev = std::dynamic_pointer_cast(event); for (auto &cb : this->on_mouse_wheel) { @@ -146,7 +170,7 @@ std::shared_ptr GlWindow::make_renderer() { auto renderer = std::make_shared(this->get_context(), this->size * this->scale_dpr); - this->add_resize_callback([this, renderer](size_t w, size_t h, double scale) { + this->add_resize_callback([renderer](size_t w, size_t h, double scale) { // this up-scales all the default framebuffer to the "bigger" highdpi window. renderer->resize_display_target(w * scale, h * scale); }); diff --git a/libopenage/renderer/opengl/window.h b/libopenage/renderer/opengl/window.h index 9d1600a329..7bf5ca7af2 100644 --- a/libopenage/renderer/opengl/window.h +++ b/libopenage/renderer/opengl/window.h @@ -1,4 +1,4 @@ -// Copyright 2018-2023 the openage authors. See copying.md for legal info. +// Copyright 2018-2024 the openage authors. See copying.md for legal info. #pragma once @@ -25,14 +25,10 @@ class GlWindow final : public Window { * Create a shiny window with the given title. * * @param title The window title. - * @param width Width (in pixels). - * @param height Height (in pixels). - * @param debug If true, enable OpenGL debug logging. + * @param settings Settings for creating the window. */ GlWindow(const std::string &title, - size_t width, - size_t height, - bool debug = false); + window_settings settings = {}); ~GlWindow(); void set_size(size_t width, size_t height) override; diff --git a/libopenage/renderer/render_factory.cpp b/libopenage/renderer/render_factory.cpp index 463cb09d4a..3dee6dd6ae 100644 --- a/libopenage/renderer/render_factory.cpp +++ b/libopenage/renderer/render_factory.cpp @@ -1,28 +1,30 @@ -// Copyright 2022-2023 the openage authors. See copying.md for legal info. +// Copyright 2022-2024 the openage authors. See copying.md for legal info. #include "render_factory.h" -#include "renderer/stages/terrain/terrain_render_entity.h" -#include "renderer/stages/terrain/terrain_renderer.h" -#include "renderer/stages/world/world_render_entity.h" -#include "renderer/stages/world/world_renderer.h" +#include "coord/phys.h" +#include "renderer/stages/terrain/render_entity.h" +#include "renderer/stages/terrain/render_stage.h" +#include "renderer/stages/world/render_entity.h" +#include "renderer/stages/world/render_stage.h" namespace openage::renderer { -RenderFactory::RenderFactory(const std::shared_ptr terrain_renderer, - const std::shared_ptr world_renderer) : +RenderFactory::RenderFactory(const std::shared_ptr terrain_renderer, + const std::shared_ptr world_renderer) : terrain_renderer{terrain_renderer}, world_renderer{world_renderer} { } -std::shared_ptr RenderFactory::add_terrain_render_entity() { - auto entity = std::make_shared(); - this->terrain_renderer->set_render_entity(entity); +std::shared_ptr RenderFactory::add_terrain_render_entity(const util::Vector2s chunk_size, + const coord::tile_delta chunk_offset) { + auto entity = std::make_shared(); + this->terrain_renderer->add_render_entity(entity, chunk_size, chunk_offset.to_phys2().to_scene2()); return entity; } -std::shared_ptr RenderFactory::add_world_render_entity() { - auto entity = std::make_shared(); +std::shared_ptr RenderFactory::add_world_render_entity() { + auto entity = std::make_shared(); this->world_renderer->add_render_entity(entity); return entity; diff --git a/libopenage/renderer/render_factory.h b/libopenage/renderer/render_factory.h index 84f2b6eb39..a211e3edf4 100644 --- a/libopenage/renderer/render_factory.h +++ b/libopenage/renderer/render_factory.h @@ -1,18 +1,22 @@ -// Copyright 2022-2023 the openage authors. See copying.md for legal info. +// Copyright 2022-2024 the openage authors. See copying.md for legal info. #pragma once #include +#include "coord/tile.h" +#include "util/vector.h" + + namespace openage::renderer { namespace terrain { -class TerrainRenderer; -class TerrainRenderEntity; +class TerrainRenderStage; +class RenderEntity; } // namespace terrain namespace world { -class WorldRenderer; -class WorldRenderEntity; +class WorldRenderStage; +class RenderEntity; } // namespace world /** @@ -23,39 +27,46 @@ class WorldRenderEntity; class RenderFactory { public: /** - * Create a new factory for render entities. - * - * @param terrain_renderer Terrain renderer. - * @param world_renderer World renderer. - */ - RenderFactory(const std::shared_ptr terrain_renderer, - const std::shared_ptr world_renderer); + * Create a new factory for render entities. + * + * @param terrain_renderer Terrain renderer. + * @param world_renderer World renderer. + */ + RenderFactory(const std::shared_ptr terrain_renderer, + const std::shared_ptr world_renderer); ~RenderFactory() = default; /** - * Create a new terrain render entity and register it at the terrain renderer. - * - * @return Render entity for pushing terrain updates. - */ - std::shared_ptr add_terrain_render_entity(); + * Create a new terrain render entity and register it at the terrain renderer. + * + * Render entities for terrain are associated with chunks, so a new render entity + * will result in the creation of a new chunk in the renderer. + * + * Size/offset of the chunk in the game simulation should match size/offset + * in the renderer. + * + * @return Render entity for pushing terrain updates. + */ + std::shared_ptr add_terrain_render_entity(const util::Vector2s chunk_size, + const coord::tile_delta chunk_offset); /** - * Create a new world render entity and register it at the world renderer. - * - * @return Render entity for pushing terrain updates. - */ - std::shared_ptr add_world_render_entity(); + * Create a new world render entity and register it at the world renderer. + * + * @return Render entity for pushing terrain updates. + */ + std::shared_ptr add_world_render_entity(); private: /** - * Render stage for terrain drawing. - */ - std::shared_ptr terrain_renderer; + * Render stage for terrain drawing. + */ + std::shared_ptr terrain_renderer; /** - * Render stage for game entity drawing. - */ - std::shared_ptr world_renderer; + * Render stage for game entity drawing. + */ + std::shared_ptr world_renderer; }; } // namespace openage::renderer diff --git a/libopenage/renderer/render_pass.cpp b/libopenage/renderer/render_pass.cpp new file mode 100644 index 0000000000..c925fc0a89 --- /dev/null +++ b/libopenage/renderer/render_pass.cpp @@ -0,0 +1,118 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "render_pass.h" + +#include "log/log.h" + + +namespace openage::renderer { + +RenderPass::RenderPass(std::vector &&renderables, + const std::shared_ptr &target) : + renderables{}, + target{target}, + layers{} { + // Add a default layer with the lowest priority + this->add_layer(0, LAYER_PRIORITY_MAX); + + // Add the renderables to the pass + this->add_renderables(std::move(renderables)); + + log::log(MSG(dbg) << "Created render pass"); +} + +const std::vector> &RenderPass::get_renderables() const { + return this->renderables; +} + +const std::vector &RenderPass::get_layers() const { + return this->layers; +} + +const std::shared_ptr &RenderPass::get_target() const { + return this->target; +} + +void RenderPass::set_target(const std::shared_ptr &target) { + this->target = target; +} + +void RenderPass::set_renderables(std::vector &&renderables) { + this->clear_renderables(); + this->add_renderables(std::move(renderables)); +} + +void RenderPass::add_renderables(std::vector &&renderables, int64_t priority) { + if (priority == LAYER_PRIORITY_MAX) { + // Add the renderables to the last (default) layer + this->renderables.back().insert(this->renderables.back().end(), + std::make_move_iterator(renderables.begin()), + std::make_move_iterator(renderables.end())); + return; + } + + // Index of the layer where the renderables will be inserted + size_t layer_index = 0; + + // Priority of the last observed layer + int64_t current_priority = LAYER_PRIORITY_MAX; + + // Find the index in renderables to insert the renderables + for (size_t i = 0; i < this->layers.size(); i++) { + auto &layer = this->layers.at(i); + if (layer.priority > priority) { + // Priority of the next layer is lower than the desired priority + // Insert the renderables directly before this layer + break; + } + current_priority = layer.priority; + layer_index = i; + } + + if (current_priority != priority) { + // Lazily add a new layer with the desired priority + this->add_layer(layer_index, priority); + } + + this->renderables[layer_index].insert(this->renderables[layer_index].end(), + std::make_move_iterator(renderables.begin()), + std::make_move_iterator(renderables.end())); +} + +void RenderPass::add_renderables(Renderable &&renderable, int64_t priority) { + this->add_renderables(std::vector{std::move(renderable)}, priority); +} + +void RenderPass::add_layer(int64_t priority, bool clear_depth) { + size_t layer_index = 0; + for (const auto &layer : this->layers) { + if (layer.priority > priority) { + break; + } + layer_index++; + } + + this->add_layer(layer_index, priority, clear_depth); +} + +void RenderPass::add_layer(size_t index, int64_t priority, bool clear_depth) { + this->layers.insert(this->layers.begin() + index, Layer{priority, clear_depth}); + this->renderables.insert(this->renderables.begin() + index, std::vector{}); +} + +void RenderPass::clear_renderables() { + // Keep layer definitions, but reset the length of each layer to 0 + for (size_t i = 0; i < this->layers.size(); i++) { + this->renderables[i].clear(); + } +} + +void RenderPass::sort(const compare_func &compare) { + for (size_t i = 0; i < this->layers.size(); i++) { + std::stable_sort(this->renderables[i].begin(), + this->renderables[i].end(), + compare); + } +} + +} // namespace openage::renderer diff --git a/libopenage/renderer/render_pass.h b/libopenage/renderer/render_pass.h new file mode 100644 index 0000000000..43c99bd659 --- /dev/null +++ b/libopenage/renderer/render_pass.h @@ -0,0 +1,161 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include + +#include "renderer/definitions.h" +#include "renderer/renderable.h" + + +namespace openage { +namespace renderer { +class RenderTarget; + +/** + * Defines a layer in the render pass. A layer is a slice of the renderables + * that have the same priority. Each layer can have its own settings. + * + * // TODO: We could also move these settings to the render pass itself and use + * // multiple render passes to achieve the same effect. Then we might + * // not need layers at all. + */ +struct Layer { + /// Priority of the renderables. + int64_t priority; + /// Whether to clear the depth buffer before rendering this layer. + bool clear_depth = true; +}; + +/** + * A render pass is a series of draw calls represented by renderables that output + * into the given render target. + */ +class RenderPass { +public: + virtual ~RenderPass() = default; + + /** + * Get the renderables of the render pass. + * + * @return Renderables of the render pass. + */ + const std::vector> &get_renderables() const; + + /** + * Get the layers of the render pass. + * + * @return Layers of the render pass. + */ + const std::vector &get_layers() const; + + /** + * Set the render target to write to. + * + * @param target Render target. + */ + void set_target(const std::shared_ptr &target); + + /** + * Get the render target of the render pass. + * + * @return Render target. + */ + const std::shared_ptr &get_target() const; + + /** + * Replace the current renderables with the given list of renderables. + * + * @param renderables New renderables. + */ + void set_renderables(std::vector &&renderables); + + /** + * Append renderables to the render pass with a given priority. + * + * @param renderables New renderables. + * @param priority Priority of the renderables. Layers with higher priority are drawn later. + */ + void add_renderables(std::vector &&renderables, + int64_t priority = LAYER_PRIORITY_MAX); + + /** + * Append a single renderable to the render pass with a given priority. + * + * @param renderable New renderable. + * @param priority Priority of the renderable. Layers with higher priority are drawn later. + */ + void add_renderables(Renderable &&renderable, + int64_t priority = LAYER_PRIORITY_MAX); + + /** + * Add a new layer to the render pass. + * + * @param priority Priority of the layer. Layers with higher priority are drawn first. + * @param clear_depth If true clears the depth buffer before rendering this layer. + */ + void add_layer(int64_t priority, bool clear_depth = true); + + /** + * Clear the list of renderables + */ + void clear_renderables(); + + using compare_func = std::function; + + /** + * Sort the renderables using the given comparison function. + * + * Layers are sorted individually, so the order of layers is not changed. + * + * @param compare Comparison function. + */ + void sort(const compare_func &compare); + +protected: + /** + * Create a new RenderPass. This is called from Renderer::add_render_pass, + * which then creates the proper subclass of RenderPass, depending on the backend. + * + * @param renderables The renderables to draw. + * @param target Render target to write to. + */ + RenderPass(std::vector &&renderables, const std::shared_ptr &target); + + /** + * The renderables to draw. + * + * Kept sorted by layer priorities (lowest to highest priority). + */ + std::vector> renderables; + +private: + /** + * Add a new layer to the render pass at the given index. + * + * @param index Index in \p layers member to insert the new layer. + * @param priority Priority of the layer. Layers with higher priority are drawn first. + * @param clear_depth If true clears the depth buffer before rendering this layer. + */ + void add_layer(size_t index, int64_t priority, bool clear_depth = true); + + /** + * Render target to write to. + */ + std::shared_ptr target; + + /** + * Stores the layers of the render pass. + * + * Layers are slices of the renderables that have the same priority. + * They can assign different settings to the renderables in the slice. + * + * Sorted from lowest to highest priority. + */ + std::vector layers; +}; + +} // namespace renderer +} // namespace openage diff --git a/libopenage/renderer/render_target.cpp b/libopenage/renderer/render_target.cpp new file mode 100644 index 0000000000..158687cc42 --- /dev/null +++ b/libopenage/renderer/render_target.cpp @@ -0,0 +1,8 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "render_target.h" + + +namespace openage::renderer { + +} // namespace openage::renderer diff --git a/libopenage/renderer/render_target.h b/libopenage/renderer/render_target.h new file mode 100644 index 0000000000..4db95701f2 --- /dev/null +++ b/libopenage/renderer/render_target.h @@ -0,0 +1,38 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + + +namespace openage { +namespace renderer { +class Texture2d; + +namespace resources { +class Texture2dData; +} // namespace resources + +/// The abstract base for a render target. +class RenderTarget : public std::enable_shared_from_this { +protected: + RenderTarget() = default; + +public: + virtual ~RenderTarget() = default; + + /** + * Get an image from the pixels in the render target's framebuffer. + * + * This should only be called _after_ rendering to the framebuffer has finished. + * + * @return RGBA texture data. + */ + virtual resources::Texture2dData into_data() = 0; + + virtual std::vector> get_texture_targets() = 0; +}; + +} // namespace renderer +} // namespace openage diff --git a/libopenage/renderer/renderable.cpp b/libopenage/renderer/renderable.cpp new file mode 100644 index 0000000000..213abdf36e --- /dev/null +++ b/libopenage/renderer/renderable.cpp @@ -0,0 +1,8 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "renderable.h" + + +namespace openage::renderer { + +} // namespace openage::renderer diff --git a/libopenage/renderer/renderable.h b/libopenage/renderer/renderable.h new file mode 100644 index 0000000000..a122db9679 --- /dev/null +++ b/libopenage/renderer/renderable.h @@ -0,0 +1,62 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include + + +namespace openage { +namespace renderer { +class Geometry; +class UniformInput; + +/** + * A renderable is a set of a shader UniformInput and a possible draw call. + * Usually it is one "step" in a RenderPass. + * + * The UniformInput only stores the values the CPU at first. When the renderer + * "executes" the Renderable in a pass, the UniformInput values are uploaded to + * the shader on the GPU they were created with. + * + * If the geometry is nullptr, the uniform values are uploaded to the shader, + * but no draw call is performed. This can be used to, for example, first set + * the values of uniforms that many objects have in common, and then only + * upload the uniforms that vary between them in each draw call. This works + * because uniform values in any given shader are preserved across a render + * pass. + * + * If geometry is set (i.e. it is not nullptr), the renderer draws the geometry + * with the shader and other settings in the renderable. The result is written + * into the render target, defined in the RenderPass. + */ +struct Renderable { + /// Uniform values to be set in the appropriate shader. Contains a reference + /// to the correct shader, and this is the shader that will be used for + /// drawing if geometry is present. + std::shared_ptr uniform; + /// The geometry. It can be a simple primitive or a complex mesh. + /// Can be nullptr to only set uniforms but do not perform draw call. + std::shared_ptr geometry; + /// Whether to perform alpha-based color blending with whatever was in the + /// render target before. + bool alpha_blending = true; + /// Whether to perform depth testing and discard occluded fragments. + bool depth_test = true; +}; + +/** + * Simplified form of Renderable, which is just an update for a shader. + * When the ShaderUpdate is processed in a RenderPass, + * the new uniform values (set on the CPU first with unif_in.update(...)) + * are uploaded to the GPU. + */ +struct ShaderUpdate : Renderable { + ShaderUpdate(std::shared_ptr const &uniform) : + Renderable{uniform, nullptr} {} + + ShaderUpdate(std::shared_ptr &&uniform) : + Renderable{std::move(uniform), nullptr} {} +}; + +} // namespace renderer +} // namespace openage diff --git a/libopenage/renderer/renderer.cpp b/libopenage/renderer/renderer.cpp index 63f61da76e..e59d1ff421 100644 --- a/libopenage/renderer/renderer.cpp +++ b/libopenage/renderer/renderer.cpp @@ -1,39 +1,7 @@ -// Copyright 2019-2023 the openage authors. See copying.md for legal info. +// Copyright 2019-2024 the openage authors. See copying.md for legal info. #include "renderer.h" namespace openage::renderer { -RenderPass::RenderPass(std::vector renderables, - const std::shared_ptr &target) : - renderables(std::move(renderables)), - target{target} {} - - -const std::shared_ptr &RenderPass::get_target() const { - return this->target; -} - - -void RenderPass::set_target(const std::shared_ptr &target) { - this->target = target; -} - -void RenderPass::set_renderables(std::vector renderables) { - this->renderables = std::move(renderables); -} -void RenderPass::add_renderables(std::vector renderables) { - for (auto item : renderables) { - this->renderables.push_back(item); - } -} - -void RenderPass::add_renderables(Renderable renderable) { - this->renderables.push_back(std::move(renderable)); -} - -void RenderPass::clear_renderables() { - this->renderables.clear(); -} - } // namespace openage::renderer diff --git a/libopenage/renderer/renderer.h b/libopenage/renderer/renderer.h index 8dec3dec2f..907d9079df 100644 --- a/libopenage/renderer/renderer.h +++ b/libopenage/renderer/renderer.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 @@ -7,6 +7,8 @@ #include #include +#include "renderer/renderable.h" + namespace openage { namespace renderer { @@ -21,97 +23,13 @@ class UniformBufferInfo; class ShaderProgram; class Geometry; +class RenderPass; +class RenderTarget; class Texture2d; class UniformBuffer; class UniformInput; -/// The abstract base for a render target. -class RenderTarget : public std::enable_shared_from_this { -protected: - RenderTarget() = default; - -public: - virtual ~RenderTarget() = default; - - virtual std::vector> get_texture_targets() = 0; -}; - -/// A renderable is a set of a shader UniformInput and a possible draw call. -/// Usually it is one "step" in a RenderPass. -/// -/// The UniformInput only stores the values the CPU at first. When the renderer -/// "executes" the Renderable in a pass, the UniformInput values are uploaded to -/// the shader on the GPU they were created with. -/// -/// If the geometry is nullptr, the uniform values are uploaded to the shader, -/// but no draw call is performed. This can be used to, for example, first set -/// the values of uniforms that many objects have in common, and then only -/// upload the uniforms that vary between them in each draw call. This works -/// because uniform values in any given shader are preserved across a render -/// pass. -/// -/// If geometry is set (i.e. it is not nullptr), the renderer draws the geometry -/// with the shader and other settings in the renderable. The result is written -/// into the render target, defined in the RenderPass. -struct Renderable { - /// Uniform values to be set in the appropriate shader. Contains a reference - /// to the correct shader, and this is the shader that will be used for - /// drawing if geometry is present. - std::shared_ptr uniform; - /// The geometry. It can be a simple primitive or a complex mesh. - /// Can be nullptr to only set uniforms but do not perform draw call. - std::shared_ptr geometry; - /// Whether to perform alpha-based color blending with whatever was in the - /// render target before. - bool alpha_blending = true; - /// Whether to perform depth testing and discard occluded fragments. - bool depth_test = true; -}; - - -/// Simplified form of Renderable, which is just an update for a shader. -/// When the ShaderUpdate is processed in a RenderPass, -/// the new uniform values (set on the CPU first with unif_in.update(...)) -/// are uploaded to the GPU. -struct ShaderUpdate : Renderable { - ShaderUpdate(std::shared_ptr const &uniform) : - Renderable{uniform, nullptr} {} - - ShaderUpdate(std::shared_ptr &&uniform) : - Renderable{std::move(uniform), nullptr} {} -}; - - -/// A render pass is a series of draw calls represented by renderables that output into the given render target. -class RenderPass { -protected: - /// Create a new RenderPass. This is called from Renderer::add_render_pass, - /// which then creates the proper subclass of RenderPass, depending on the backend. - RenderPass(std::vector, const std::shared_ptr &); - /// The renderables to parse and possibly execute. - std::vector renderables; - -public: - virtual ~RenderPass() = default; - void set_target(const std::shared_ptr &); - const std::shared_ptr &get_target() const; - - // Replace the current renderables - void set_renderables(std::vector); - // Append renderables to the end of the list of renderables - void add_renderables(std::vector); - // Append a single renderable to the end of the list of renderables - void add_renderables(Renderable); - // Clear the list of renderables - void clear_renderables(); - -private: - /// The render target to write into. - std::shared_ptr target; -}; - - /// The renderer. This class is used for performing all graphics operations. It is abstract and has implementations /// for various low-level graphics APIs like OpenGL. class Renderer { 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/animation/angle_info.h b/libopenage/renderer/resources/animation/angle_info.h index 14e4a8b8da..3b84e0423d 100644 --- a/libopenage/renderer/resources/animation/angle_info.h +++ b/libopenage/renderer/resources/animation/angle_info.h @@ -1,4 +1,4 @@ -// Copyright 2021-2023 the openage authors. See copying.md for legal info. +// Copyright 2021-2024 the openage authors. See copying.md for legal info. #pragma once @@ -14,9 +14,9 @@ class FrameInfo; * Describe whether/how the frame is mirrored */ enum class flip_type { - NONE, // do not flip + NONE, // do not flip FLIP_X, // flip across x axis - FLIP_Y // flip across y axis + FLIP_Y // flip across y axis }; /** @@ -32,7 +32,7 @@ class AngleInfo { * @param angle_start Rotation in degress at which the frames are displayed. * @param frames Frame information. * @param mirror_from Mirror frames from another angle instead of using uniquely - * defined frames. + * defined frames. */ AngleInfo(const float angle_start, std::vector> &frames, @@ -50,10 +50,10 @@ class AngleInfo { float get_angle_start() const; /** - * Check if the angle is mirrored from another angle. - * - * @return true if a mirrored angle has been defined, else false. - */ + * Check if the angle is mirrored from another angle. + * + * @return true if a mirrored angle has been defined, else false. + */ bool is_mirrored() const; /** @@ -88,18 +88,18 @@ class AngleInfo { private: /** - * Starting rotation in degress at which the frames are displayed. - */ + * Starting rotation in degress at which the frames are displayed. + */ float angle_start; /** - * Frame information. - */ + * Frame information. + */ std::vector> frames; /** - * Mirrored angle information. - */ + * Mirrored angle information. + */ std::shared_ptr mirror_from = nullptr; /** diff --git a/libopenage/renderer/resources/animation/animation_info.cpp b/libopenage/renderer/resources/animation/animation_info.cpp index 0f309a6fa7..64939a3f56 100644 --- a/libopenage/renderer/resources/animation/animation_info.cpp +++ b/libopenage/renderer/resources/animation/animation_info.cpp @@ -1,7 +1,12 @@ -// Copyright 2021-2023 the openage authors. See copying.md for legal info. +// Copyright 2021-2024 the openage authors. See copying.md for legal info. #include "animation_info.h" +#include "renderer/resources/animation/angle_info.h" +#include "renderer/resources/animation/frame_info.h" +#include "renderer/resources/texture_info.h" + + namespace openage::renderer::resources { Animation2dInfo::Animation2dInfo(const float scalefactor, @@ -9,7 +14,36 @@ Animation2dInfo::Animation2dInfo(const float scalefactor, std::vector &layers) : scalefactor{scalefactor}, texture_infos{textures}, - layers{layers} {} + layers{layers}, + max_bounds{} { + // Calculate max bounds + for (auto &layer : this->layers) { + for (size_t i = 0; i < layer.get_angle_count(); ++i) { + auto &angle = layer.get_angle(i); + for (size_t j = 0; j < angle->get_frame_count(); ++j) { + auto &frame = angle->get_frame(j); + auto tex_idx = frame->get_texture_idx(); + auto subtex_idx = frame->get_subtexture_idx(); + + auto &tex = this->texture_infos.at(tex_idx); + auto &subtex = tex->get_subtex_info(subtex_idx); + + auto subtex_size = subtex.get_size(); + auto anchor_pos = subtex.get_anchor_pos(); + + int left_margin = anchor_pos.x(); + int right_margin = subtex_size.x() - anchor_pos.x(); + int top_margin = anchor_pos.y(); + int bottom_margin = subtex_size.y() - anchor_pos.y(); + + this->max_bounds[0] = std::max(this->max_bounds[0], left_margin); + this->max_bounds[1] = std::max(this->max_bounds[1], right_margin); + this->max_bounds[2] = std::max(this->max_bounds[2], top_margin); + this->max_bounds[3] = std::max(this->max_bounds[3], bottom_margin); + } + } + } +} float Animation2dInfo::get_scalefactor() const { return this->scalefactor; @@ -31,4 +65,13 @@ const LayerInfo &Animation2dInfo::get_layer(size_t idx) const { return this->layers.at(idx); } +const util::Vector4i &Animation2dInfo::get_max_bounds() const { + return this->max_bounds; +} + +const util::Vector2s Animation2dInfo::get_max_size() const { + return {this->max_bounds[0] + this->max_bounds[1], + this->max_bounds[2] + this->max_bounds[3]}; +} + } // namespace openage::renderer::resources diff --git a/libopenage/renderer/resources/animation/animation_info.h b/libopenage/renderer/resources/animation/animation_info.h index be53acb180..1e778d892e 100644 --- a/libopenage/renderer/resources/animation/animation_info.h +++ b/libopenage/renderer/resources/animation/animation_info.h @@ -1,4 +1,4 @@ -// Copyright 2021-2023 the openage authors. See copying.md for legal info. +// Copyright 2021-2024 the openage authors. See copying.md for legal info. #pragma once @@ -7,6 +7,7 @@ #include #include "renderer/resources/animation/layer_info.h" +#include "util/vector.h" namespace openage::renderer::resources { @@ -79,6 +80,25 @@ class Animation2dInfo { */ const LayerInfo &get_layer(size_t idx) const; + /** + * Get the maximum boundaries for all frames in the animation. + * + * This represents the maximum distance from the anchor point to the + * left, right, top, bottom edges of the frames (in pixels). + * + * @return Boundaries of the animation (in pixels): [left, right, top, bottom] + */ + const util::Vector4i &get_max_bounds() const; + + /** + * Get the maximum size for all frames in the animation. + * + * This represents the maximum width and height of the frames (in pixels). + * + * @return Size of the animation (in pixels): [width, height] + */ + const util::Vector2s get_max_size() const; + private: /** * Scaling factor of the animation across all layers at default zoom level. @@ -94,6 +114,14 @@ class Animation2dInfo { * Layer information. */ std::vector layers; + + /** + * Maximum boundaries for all frames in the animation. + * + * This represents the maximum distance from the anchor point to the + * left, right, top, bottom edges of the frames (in pixels). + */ + util::Vector4i max_bounds; }; } // namespace openage::renderer::resources diff --git a/libopenage/renderer/resources/animation/frame_info.h b/libopenage/renderer/resources/animation/frame_info.h index 3742c678d4..5d43e2edfb 100644 --- a/libopenage/renderer/resources/animation/frame_info.h +++ b/libopenage/renderer/resources/animation/frame_info.h @@ -1,4 +1,4 @@ -// Copyright 2021-2023 the openage authors. See copying.md for legal info. +// Copyright 2021-2024 the openage authors. See copying.md for legal info. #pragma once @@ -16,9 +16,9 @@ class FrameInfo { * Create a 2D Frame Info. * * @param texture_idx Index of the texture containing the frame in the - * Animation2dInfo object. + * Animation2dInfo object. * @param subtexture_idx Index of the subtexture containing the frame - * in the Texture2dInfo object. + * in the Texture2dInfo object. */ FrameInfo(const size_t texture_idx, const size_t subtexture_idx); @@ -42,13 +42,13 @@ class FrameInfo { private: /** - * Index of the texture containing the frame in the Animation2dInfo object. - */ + * Index of the texture containing the frame in the Animation2dInfo object. + */ size_t texture_idx; /** - * Index of the subtexture containing the frame in the Texture2dInfo object. - */ + * Index of the subtexture containing the frame in the Texture2dInfo object. + */ size_t subtexture_idx; }; diff --git a/libopenage/renderer/resources/animation/layer_info.h b/libopenage/renderer/resources/animation/layer_info.h index eafb92dfec..163b1c0aa6 100644 --- a/libopenage/renderer/resources/animation/layer_info.h +++ b/libopenage/renderer/resources/animation/layer_info.h @@ -1,4 +1,4 @@ -// Copyright 2021-2023 the openage authors. See copying.md for legal info. +// Copyright 2021-2024 the openage authors. See copying.md for legal info. #pragma once @@ -12,9 +12,9 @@ class AngleInfo; class FrameTiming; enum class display_mode { - OFF, // Only show first frame + OFF, // Only show first frame ONCE, // Play loop once - LOOP // Loop indefinitely + LOOP // Loop indefinitely }; /** @@ -95,10 +95,10 @@ class LayerInfo { const std::shared_ptr &get_direction_angle(float direction) const; /** - * Get the frame timing information of this layer. - * - * @return Frame timing information. - */ + * Get the frame timing information of this layer. + * + * @return Frame timing information. + */ const std::shared_ptr &get_frame_timing() const; private: diff --git a/libopenage/renderer/resources/assets/asset_manager.h b/libopenage/renderer/resources/assets/asset_manager.h index 0d45e473dd..b77c29afe2 100644 --- a/libopenage/renderer/resources/assets/asset_manager.h +++ b/libopenage/renderer/resources/assets/asset_manager.h @@ -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. #pragma once @@ -36,32 +36,32 @@ class Texture2dInfo; class AssetManager { public: /** - * Create a new asset manager. - * - * @param renderer The openage renderer instance. - * @param asset_base_dir Base path for all assets. - */ + * Create a new asset manager. + * + * @param renderer The openage renderer instance. + * @param asset_base_dir Base path for all assets. + */ AssetManager(const std::shared_ptr &renderer, const util::Path &asset_base_dir); ~AssetManager() = default; /** - * Prevent accidental copy or assignment because it would defeat the - * point of a persistent cache. - */ + * Prevent accidental copy or assignment because it would defeat the + * point of a persistent cache. + */ AssetManager(const AssetManager &) = delete; AssetManager &operator=(const AssetManager &) = delete; /** - * Get the corresponding asset for the specified path. - * - * If the asset does not exist in the cache yet, it will be loaded - * using the given path. - * - * @param path Path to the asset resource. - * - * @return Texture resource at the given path. - */ + * Get the corresponding asset for the specified path. + * + * If the asset does not exist in the cache yet, it will be loaded + * using the given path. + * + * @param path Path to the asset resource. + * + * @return Texture resource at the given path. + */ const std::shared_ptr &request_animation(const util::Path &path); const std::shared_ptr &request_blpattern(const util::Path &path); const std::shared_ptr &request_bltable(const util::Path &path); @@ -70,16 +70,16 @@ class AssetManager { const std::shared_ptr &request_texture(const util::Path &path); /** - * Get the corresponding asset for a path string relative to the - * asset base directory. - * - * If the asset does not exist in the cache yet, it will be loaded - * using the given path. - * - * @param path Relative path to the asset resource (from the asset base dir). - * - * @return Texture resource at the given path. - */ + * Get the corresponding asset for a path string relative to the + * asset base directory. + * + * If the asset does not exist in the cache yet, it will be loaded + * using the given path. + * + * @param path Relative path to the asset resource (from the asset base dir). + * + * @return Texture resource at the given path. + */ const std::shared_ptr &request_animation(const std::string &rel_path); const std::shared_ptr &request_blpattern(const std::string &rel_path); const std::shared_ptr &request_bltable(const std::string &rel_path); @@ -95,11 +95,11 @@ class AssetManager { using placeholder_texture_t = std::optional>>; /** - * Set a placeholder asset that is returned in case a regular request cannot - * find the requested asset. - * - * @param path Path to the placeholder asset resource. - */ + * Set a placeholder asset that is returned in case a regular request cannot + * find the requested asset. + * + * @param path Path to the placeholder asset resource. + */ void set_placeholder_animation(const util::Path &path); void set_placeholder_blpattern(const util::Path &path); void set_placeholder_bltable(const util::Path &path); @@ -108,10 +108,10 @@ class AssetManager { void set_placeholder_texture(const util::Path &path); /** - * Get the placeholder asset for a specific asset type. - * - * @return Placeholder asset resource. - */ + * Get the placeholder asset for a specific asset type. + * + * @return Placeholder asset resource. + */ const placeholder_anim_t &get_placeholder_animation(); const placeholder_blpattern_t &get_placeholder_blpattern(); const placeholder_bltable_t &get_placeholder_bltable(); @@ -120,39 +120,39 @@ class AssetManager { const placeholder_texture_t &get_placeholder_texture(); /** - * Get the texture manager for accessing cached image resources. - * - * @return Texture manager. - */ + * Get the texture manager for accessing cached image resources. + * + * @return Texture manager. + */ const std::shared_ptr &get_texture_manager(); private: /** - * openage renderer. - */ + * openage renderer. + */ std::shared_ptr renderer; /** - * Cache of already loaded assets. - */ + * Cache of already loaded assets. + */ std::shared_ptr cache; /** - * Manages individual textures/image resources used by the - * high level asset formats cached by the asset manager. - */ + * Manages individual textures/image resources used by the + * high level asset formats cached by the asset manager. + */ std::shared_ptr texture_manager; /** - * Base path for all assets. - * - * TODO: This should be the mount point of modpacks? - */ + * Base path for all assets. + * + * TODO: This should be the mount point of modpacks? + */ util::Path asset_base_dir; /** - * Placeholder assets that can be used if a resource is not found. - */ + * Placeholder assets that can be used if a resource is not found. + */ placeholder_anim_t placeholder_animation; placeholder_blpattern_t placeholder_blpattern; placeholder_bltable_t placeholder_bltable; diff --git a/libopenage/renderer/resources/assets/cache.h b/libopenage/renderer/resources/assets/cache.h index 34ec62567e..803fc0b6fd 100644 --- a/libopenage/renderer/resources/assets/cache.h +++ b/libopenage/renderer/resources/assets/cache.h @@ -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. #pragma once @@ -27,19 +27,19 @@ class Texture2dInfo; class AssetCache { public: /** - * Create a new asset cache. - */ + * Create a new asset cache. + */ AssetCache() = default; ~AssetCache() = default; /** - * Get the corresponding asset for the specified path. - * - * @param path Path to the asset resource. - * - * @return Asset resource at the given path. + * Get the corresponding asset for the specified path. + * + * @param path Path to the asset resource. + * + * @return Asset resource at the given path. * @throw Error if the resource is not in the cache. - */ + */ const std::shared_ptr &get_animation(const util::Path &path); const std::shared_ptr &get_blpattern(const util::Path &path); const std::shared_ptr &get_bltable(const util::Path &path); @@ -49,12 +49,12 @@ class AssetCache { // TODO: use std::optional> /** - * Map a specific asset info to the given path and add it to the cache. - * Overwrites existing references if the path already exists in the cache. - * - * @param path Path to the asset resource. - * @param info Existing asset object. - */ + * Map a specific asset info to the given path and add it to the cache. + * Overwrites existing references if the path already exists in the cache. + * + * @param path Path to the asset resource. + * @param info Existing asset object. + */ void add_animation(const util::Path &path, const std::shared_ptr info); void add_blpattern(const util::Path &path, const std::shared_ptr info); void add_bltable(const util::Path &path, const std::shared_ptr info); @@ -63,10 +63,10 @@ class AssetCache { void add_texture(const util::Path &path, const std::shared_ptr info); /** - * Remove an asset reference from the cache. - * - * @param path Path to the asset resource. - */ + * Remove an asset reference from the cache. + * + * @param path Path to the asset resource. + */ void remove_animation(const util::Path &path); void remove_blpattern(const util::Path &path); void remove_bltable(const util::Path &path); @@ -75,12 +75,12 @@ class AssetCache { void remove_texture(const util::Path &path); /** - * Check if an asset reference is in the cache. - * - * @param path Path to the asset resource. + * Check if an asset reference is in the cache. + * + * @param path Path to the asset resource. * * @return true if the resource is cached, else false. - */ + */ bool check_animation_cache(const util::Path &path); bool check_blpattern_cache(const util::Path &path); bool check_bltable_cache(const util::Path &path); @@ -97,33 +97,33 @@ class AssetCache { using texture_cache_t = std::unordered_map>; /** - * Cache of already loaded animations. - */ + * Cache of already loaded animations. + */ anim_cache_t loaded_animations; /** - * Cache of already loaded blending patterns. - */ + * Cache of already loaded blending patterns. + */ blpattern_cache_t loaded_blpatterns; /** - * Cache of already loaded blending tables. - */ + * Cache of already loaded blending tables. + */ bltable_cache_t loaded_bltables; /** - * Cache of already loaded colour palettes. - */ + * Cache of already loaded colour palettes. + */ palette_cache_t loaded_palettes; /** - * Cache of already loaded terrains. - */ + * Cache of already loaded terrains. + */ terrain_cache_t loaded_terrains; /** - * Cache of already loaded textures. - */ + * Cache of already loaded textures. + */ texture_cache_t loaded_textures; }; diff --git a/libopenage/renderer/resources/assets/texture_manager.h b/libopenage/renderer/resources/assets/texture_manager.h index cf7dbdab60..55c365081a 100644 --- a/libopenage/renderer/resources/assets/texture_manager.h +++ b/libopenage/renderer/resources/assets/texture_manager.h @@ -1,4 +1,4 @@ -// Copyright 2022-2023 the openage authors. See copying.md for legal info. +// Copyright 2022-2024 the openage authors. See copying.md for legal info. #pragma once @@ -26,89 +26,89 @@ namespace resources { class TextureManager { public: /** - * Create a new texture manager. - * - * @param renderer The openage renderer instance. - */ + * Create a new texture manager. + * + * @param renderer The openage renderer instance. + */ TextureManager(const std::shared_ptr &renderer); ~TextureManager() = default; /** - * Prevent accidental copy or assignment because it would defeat the - * point of a persistent cache. - */ + * Prevent accidental copy or assignment because it would defeat the + * point of a persistent cache. + */ TextureManager(const TextureManager &) = delete; TextureManager &operator=(const TextureManager &) = delete; /** - * Get the corresponding texture for the specified path. - * - * If the texture does not exist in the cache yet, it will be loaded - * using the given path. - * - * @param path Path to the texture resource. - * - * @return Texture resource at the given path. - */ + * Get the corresponding texture for the specified path. + * + * If the texture does not exist in the cache yet, it will be loaded + * using the given path. + * + * @param path Path to the texture resource. + * + * @return Texture resource at the given path. + */ const std::shared_ptr &request(const util::Path &path); /** - * Load the texture at the given path. Does nothing if the path - * already exists in the cache. - * - * @param path Path to the texture resource. - */ + * Load the texture at the given path. Does nothing if the path + * already exists in the cache. + * + * @param path Path to the texture resource. + */ void add(const util::Path &path); /** - * Assign a specific texture to the given path. Overwrites existing - * textures references if the path already exists in the cache. - * - * @param path Path to the texture resource. - * @param texture Existing texture object. - */ + * Assign a specific texture to the given path. Overwrites existing + * textures references if the path already exists in the cache. + * + * @param path Path to the texture resource. + * @param texture Existing texture object. + */ void add(const util::Path &path, const std::shared_ptr &texture); /** - * Remove a texture reference from the cache. - * - * @param path Path to the texture resource. - */ + * Remove a texture reference from the cache. + * + * @param path Path to the texture resource. + */ void remove(const util::Path &path); /** - * Set the placeholder texture. - * - * @param path Path to the texture resource. - */ + * Set the placeholder texture. + * + * @param path Path to the texture resource. + */ void set_placeholder(const util::Path &path); using placeholder_t = std::optional>>; /** - * Get the placeholder texture. - * - * @return Placeholder texture. Can be \p nullptr. - */ + * Get the placeholder texture. + * + * @return Placeholder texture. Can be \p nullptr. + */ const placeholder_t &get_placeholder() const; private: /** - * openage renderer. - */ + * openage renderer. + */ std::shared_ptr renderer; using texture_cache_t = std::unordered_map>; /** - * Cache of already created textures. - */ + * Cache of already created textures. + */ texture_cache_t loaded; /** - * Placeholder texture to use if a texture could not be loaded. - */ + * Placeholder texture to use if a texture could not be loaded. + */ placeholder_t placeholder; }; diff --git a/libopenage/renderer/resources/buffer_info.cpp b/libopenage/renderer/resources/buffer_info.cpp index c3215d63ba..de9a1751c8 100644 --- a/libopenage/renderer/resources/buffer_info.cpp +++ b/libopenage/renderer/resources/buffer_info.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 "buffer_info.h" @@ -27,7 +27,13 @@ const std::vector &UniformBufferInfo::get_inputs() const { size_t UniformBufferInfo::get_size() const { size_t size = 0; for (const auto &input : this->inputs) { - size += this->get_size(input, this->layout); + // size of the input type + size_t input_size = this->get_size(input, this->layout); + + // inputs must additionally be aligned to a multiple of their size + size_t align_size = size % input_size; + + size += input_size + align_size; } return size; } diff --git a/libopenage/renderer/resources/buffer_info.h b/libopenage/renderer/resources/buffer_info.h index b654c3090f..1059169f94 100644 --- a/libopenage/renderer/resources/buffer_info.h +++ b/libopenage/renderer/resources/buffer_info.h @@ -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. #pragma once @@ -81,71 +81,71 @@ static constexpr auto STD140_INPUT_SIZE = datastructure::create_const_map &inputs); /** - * Create a new uniform buffer definition. - * - * @param layout Layout of the uniform block targeted by the buffer. - * @param inputs Uniform definitions inside the uniform block. - */ + * Create a new uniform buffer definition. + * + * @param layout Layout of the uniform block targeted by the buffer. + * @param inputs Uniform definitions inside the uniform block. + */ UniformBufferInfo(ubo_layout_t layout, std::vector &&inputs); /** - * Get the layout of the uniform block targeted by the buffer. - * - * @return Uniform block layout. - */ + * Get the layout of the uniform block targeted by the buffer. + * + * @return Uniform block layout. + */ ubo_layout_t get_layout() const; /** - * Get the uniform definitions inside the uniform block. - * - * @return Uniform definitions. - */ + * Get the uniform definitions inside the uniform block. + * + * @return Uniform definitions. + */ const std::vector &get_inputs() const; /** - * Get the total size of the uniform block (in bytes). - * - * @return Size of the uniform block (in bytes). - */ + * Get the total size of the uniform block (in bytes). + * + * @return Size of the uniform block (in bytes). + */ size_t get_size() const; /** - * Get the size of a single uniform input (in bytes). - * - * @param input Uniform input type. - * @param layout Layout of the uniform block targeted by the buffer. - * - * @return Size of the uniform input (in bytes). - */ + * Get the size of a single uniform input (in bytes). + * + * @param input Uniform input type. + * @param layout Layout of the uniform block targeted by the buffer. + * + * @return Size of the uniform input (in bytes). + */ static size_t get_size(const UBOInput &input, ubo_layout_t layout = ubo_layout_t::STD140); /** - * Get the stride size of a uniform input in an array (in bytes). - * - * @param input Uniform input type. - * @param layout Layout of the uniform block targeted by the buffer. - * - * @return Size of the uniform input (in bytes). - */ + * Get the stride size of a uniform input in an array (in bytes). + * + * @param input Uniform input type. + * @param layout Layout of the uniform block targeted by the buffer. + * + * @return Size of the uniform input (in bytes). + */ static size_t get_stride_size(ubo_input_t input, ubo_layout_t layout = ubo_layout_t::STD140); private: /** - * Uniform block layout. - */ + * Uniform block layout. + */ ubo_layout_t layout; /** - * Uniform definitions. - */ + * Uniform definitions. + */ std::vector inputs; }; diff --git a/libopenage/renderer/resources/frame_timing.h b/libopenage/renderer/resources/frame_timing.h index c253501900..adb36b23a6 100644 --- a/libopenage/renderer/resources/frame_timing.h +++ b/libopenage/renderer/resources/frame_timing.h @@ -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. #pragma once @@ -20,86 +20,86 @@ class FrameTiming { using keyframe_t = time::time_t; /** - * Create a new frame timing sequence. - * - * @param total_length Total length of the sequence (in seconds). - * @param keyframes Time of each frame (in seconds). Expected to be sorted. - */ + * Create a new frame timing sequence. + * + * @param total_length Total length of the sequence (in seconds). + * @param keyframes Time of each frame (in seconds). Expected to be sorted. + */ FrameTiming(const time::time_t &total_length, const std::vector &&keyframes); ~FrameTiming() = default; /** - * Set the total length of the frame timing sequence. - * - * @param total_length Total length of the sequence (in seconds). - */ + * Set the total length of the frame timing sequence. + * + * @param total_length Total length of the sequence (in seconds). + */ void set_total_length(const time::time_t &total_length); /** - * Insert a new keyframe into the sequence. - * - * Insertion complexity is linear (O(n)), so it should be avoided during rendering - * when possible. Prefer to create several alternative sequence instead if the - * timing needs to be changed. - * - * @param time Time of the new keyframe (in seconds). - */ + * Insert a new keyframe into the sequence. + * + * Insertion complexity is linear (O(n)), so it should be avoided during rendering + * when possible. Prefer to create several alternative sequence instead if the + * timing needs to be changed. + * + * @param time Time of the new keyframe (in seconds). + */ void insert(const time::time_t &time); /** - * Get the index of the frame in the sequence that should be displayed at the - * given time (closest frame with t <= time). - * - * @param time Time in the sequence (in seconds). - * @return Frame index in the sequence. - */ + * Get the index of the frame in the sequence that should be displayed at the + * given time (closest frame with t <= time). + * + * @param time Time in the sequence (in seconds). + * @return Frame index in the sequence. + */ size_t get_frame(const time::time_t &time) const; /** - * Get the index of the frame in the sequence that should be displayed at a specified - * time. This method is intended for looping frame sequences. - * - * Sequence time is determined from the modulus of the time difference between - * \p current (current simulation time) and \p start (time when the sequence - * was first started): - * - * time = (current - start) % total_length - * - * @param current Current simulation time (in seconds). - * @param start Start time of the sequence (in seconds). - * @return Frame index in the sequence. - */ + * Get the index of the frame in the sequence that should be displayed at a specified + * time. This method is intended for looping frame sequences. + * + * Sequence time is determined from the modulus of the time difference between + * \p current (current simulation time) and \p start (time when the sequence + * was first started): + * + * time = (current - start) % total_length + * + * @param current Current simulation time (in seconds). + * @param start Start time of the sequence (in seconds). + * @return Frame index in the sequence. + */ size_t get_frame(const time::time_t ¤t, const time::time_t &start) const; /** - * Get the number of frames in the sequence. - * - * @return Number of frames. - */ + * Get the number of frames in the sequence. + * + * @return Number of frames. + */ size_t size() const; private: /** - * Search for the closest frame index with t <= time. - * - * Uses binary search to find the frame index. Complexity is therefore - * O(log_2(this->keyframes.size())). - * - * @param time Time in the sequence (in seconds). - * @return Frame index in the sequence. - */ + * Search for the closest frame index with t <= time. + * + * Uses binary search to find the frame index. Complexity is therefore + * O(log_2(this->keyframes.size())). + * + * @param time Time in the sequence (in seconds). + * @return Frame index in the sequence. + */ size_t search_frame(const time::time_t &time) const; /** - * Time of each frame in the sequence relative to the sequence start (in seconds). - */ + * Time of each frame in the sequence relative to the sequence start (in seconds). + */ std::vector keyframes; /** - * Total length of the sequence (in seconds). - */ + * Total length of the sequence (in seconds). + */ time::time_t total_length; }; diff --git a/libopenage/renderer/resources/mesh_data.cpp b/libopenage/renderer/resources/mesh_data.cpp index 74fb72b629..d228d15326 100644 --- a/libopenage/renderer/resources/mesh_data.cpp +++ b/libopenage/renderer/resources/mesh_data.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #include "mesh_data.h" @@ -35,11 +35,18 @@ size_t vertex_input_count(vertex_input_t in) { return vin_count.get(in); } -VertexInputInfo::VertexInputInfo(std::vector inputs, vertex_layout_t layout, vertex_primitive_t primitive) : - inputs(std::move(inputs)), layout(layout), primitive(primitive) {} +VertexInputInfo::VertexInputInfo(std::vector inputs, + vertex_layout_t layout, + vertex_primitive_t primitive) : + inputs(std::move(inputs)), + layout(layout), primitive(primitive) {} -VertexInputInfo::VertexInputInfo(std::vector inputs, vertex_layout_t layout, vertex_primitive_t primitive, index_t index_type) : - inputs(std::move(inputs)), layout(layout), primitive(primitive), index_type(index_type) {} +VertexInputInfo::VertexInputInfo(std::vector inputs, + vertex_layout_t layout, + vertex_primitive_t primitive, + index_t index_type) : + inputs(std::move(inputs)), + layout(layout), primitive(primitive), index_type(index_type) {} void VertexInputInfo::add_shader_input_map(std::unordered_map &&in_map) { for (auto mapping : in_map) { @@ -105,12 +112,30 @@ VertexInputInfo MeshData::get_info() const { /// Vertices of a quadrilateral filling the whole screen. /// Format: (pos, tex_coords) = (x, y, u, v) static constexpr const std::array QUAD_DATA_CENTERED = { - {-1.0f, 1.0f, 0.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 0.0f}}; + // clang-format off + // prevent clang-format from putting this matrix on single line + { + -1.0f, 1.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 0.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 1.0f, 0.0f + } + // clang-format on +}; /// Vertices of a quad from (0, 0) to (1, 1) /// Format: (pos, tex_coords) = (x, y, u, v) static constexpr const std::array QUAD_DATA_UNIT = { - {0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f}}; + // clang-format off + // prevent clang-format from putting this matrix on single line + { + 0.0f, 1.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 0.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 1.0f, + 1.0f, 0.0f, 1.0f, 0.0f + } + // clang-format on +}; namespace { @@ -123,7 +148,7 @@ MeshData create_float_mesh(const std::array &src) { auto const data_size = size * sizeof(float); std::vector verts(data_size); - std::memcpy(verts.data(), reinterpret_cast(src.data()), data_size); + std::memcpy(verts.data(), src.data(), data_size); VertexInputInfo info{ {vertex_input_t::V2F32, vertex_input_t::V2F32}, @@ -154,11 +179,29 @@ MeshData MeshData::make_quad(float sidelength, bool centered) { if (centered) { float halfsidelength = sidelength / 2; positions = { - {-halfsidelength, halfsidelength, 0.0f, 1.0f, -halfsidelength, -halfsidelength, 0.0f, 0.0f, halfsidelength, halfsidelength, 1.0f, 1.0f, halfsidelength, -halfsidelength, 1.0f, 0.0f}}; + // clang-format off + // prevent clang-format from putting this matrix on single line + { + -halfsidelength, halfsidelength, 0.0f, 1.0f, + -halfsidelength, -halfsidelength, 0.0f, 0.0f, + halfsidelength, halfsidelength, 1.0f, 1.0f, + halfsidelength, -halfsidelength, 1.0f, 0.0f + } + // clang-format on + }; } else { positions = { - {0.0f, sidelength, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, sidelength, sidelength, 1.0f, 1.0f, sidelength, 0.0f, 1.0f, 0.0f}}; + // clang-format off + // prevent clang-format from putting this matrix on single line + { + 0.0f, sidelength, 0.0f, 1.0f, + 0.0f, 0.0f, 0.0f, 0.0f, + sidelength, sidelength, 1.0f, 1.0f, + sidelength, 0.0f, 1.0f, 0.0f + } + // clang-format on + }; } return create_float_mesh(positions); @@ -174,11 +217,28 @@ MeshData MeshData::make_quad(float width, float height, bool centered) { float halfwidth = width / 2; float halfheight = height / 2; positions = { - {-halfwidth, halfheight, 0.0f, 1.0f, -halfwidth, -halfheight, 0.0f, 0.0f, halfwidth, halfheight, 1.0f, 1.0f, halfwidth, -halfheight, 1.0f, 0.0f}}; + { + // clang-format off + // prevent clang-format from putting this matrix on single line + -halfwidth, halfheight, 0.0f, 1.0f, + -halfwidth, -halfheight, 0.0f, 0.0f, + halfwidth, halfheight, 1.0f, 1.0f, + halfwidth, -halfheight, 1.0f, 0.0f + // clang-format on + }}; } else { positions = { - {0.0f, height, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, width, height, 1.0f, 1.0f, width, 0.0f, 1.0f, 0.0f}}; + // clang-format off + // prevent clang-format from putting this matrix on single line + { + 0.0f, height, 0.0f, 1.0f, + 0.0f, 0.0f, 0.0f, 0.0f, + width, height, 1.0f, 1.0f, + width, 0.0f, 1.0f, 0.0f + } + // clang-format on + }; } return create_float_mesh(positions); diff --git a/libopenage/renderer/resources/mesh_data.h b/libopenage/renderer/resources/mesh_data.h index 1ad0d7a3f4..a39124cbe3 100644 --- a/libopenage/renderer/resources/mesh_data.h +++ b/libopenage/renderer/resources/mesh_data.h @@ -1,4 +1,4 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once @@ -29,6 +29,7 @@ enum class vertex_primitive_t { POINTS, LINES, LINE_STRIP, + LINE_LOOP, TRIANGLES, TRIANGLE_STRIP, TRIANGLE_FAN, diff --git a/libopenage/renderer/resources/palette_info.h b/libopenage/renderer/resources/palette_info.h index d147522feb..61de54be1e 100644 --- a/libopenage/renderer/resources/palette_info.h +++ b/libopenage/renderer/resources/palette_info.h @@ -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. #pragma once @@ -34,17 +34,17 @@ class PaletteInfo { ~PaletteInfo() = default; /** - * Get a color in the palette with a specified index. - * - * @return Normalized RGBA color vector. - */ + * Get a color in the palette with a specified index. + * + * @return Normalized RGBA color vector. + */ Eigen::Vector4f get_color(size_t idx); /** - * Get the colors of the palette. - * - * @return List of normalized RGBA colors. - */ + * Get the colors of the palette. + * + * @return List of normalized RGBA colors. + */ const std::vector get_colors(); private: diff --git a/libopenage/renderer/resources/parser/common.cpp b/libopenage/renderer/resources/parser/common.cpp index 30fa97118c..c270ebf891 100644 --- a/libopenage/renderer/resources/parser/common.cpp +++ b/libopenage/renderer/resources/parser/common.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 "common.h" diff --git a/libopenage/renderer/resources/parser/parse_blendmask.cpp b/libopenage/renderer/resources/parser/parse_blendmask.cpp index d7f6e4aa74..b5b9338b72 100644 --- a/libopenage/renderer/resources/parser/parse_blendmask.cpp +++ b/libopenage/renderer/resources/parser/parse_blendmask.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 "parse_blendmask.h" @@ -40,7 +40,7 @@ blending_mask parse_mask(const std::vector &args) { if (args[0].starts_with("0b")) { // discard prefix because std::stoul doesn't understand binary prefixes std::string strip = args[0].substr(2, args[2].size() - 1); - dir = std::stoul(args[0], nullptr, 2); + dir = std::stoul(strip, nullptr, 2); } else { dir = std::stoul(args[0]); @@ -58,16 +58,16 @@ blending_mask parse_mask(const std::vector &args) { return mask; } -BlendPatternInfo parse_blendmask_file(const util::Path &file, +BlendPatternInfo parse_blendmask_file(const util::Path &path, const std::shared_ptr &cache) { - if (not file.is_file()) [[unlikely]] { + if (not path.is_file()) [[unlikely]] { throw Error(MSG(err) << "Reading .blmask file '" - << file.get_name() + << path.get_name() << "' failed. Reason: File not found"); } - auto content = file.open(); - auto lines = content.get_lines(); + auto file = path.open(); + auto lines = file.get_lines(); float scalefactor = 1.0; std::vector textures; @@ -79,7 +79,7 @@ BlendPatternInfo parse_blendmask_file(const util::Path &file, if (version_no != 2) { throw Error(MSG(err) << "Reading .blmask file '" - << file.get_name() + << path.get_name() << "' failed. Reason: Version " << version_no << " not supported"); } @@ -104,7 +104,7 @@ BlendPatternInfo parse_blendmask_file(const util::Path &file, // TODO: Avoid double lookup with keywordfuncs.find(args[0]) if (not keywordfuncs.contains(args[0])) [[unlikely]] { throw Error(MSG(err) << "Reading .blmask file '" - << file.get_name() + << path.get_name() << "' failed. Reason: Keyword " << args[0] << " is not defined"); } @@ -128,7 +128,7 @@ BlendPatternInfo parse_blendmask_file(const util::Path &file, // Parse textures std::vector> texture_infos; for (auto texture : textures) { - util::Path texturepath = (file.get_parent() / texture.path); + util::Path texturepath = (path.get_parent() / texture.path); if (cache && cache->check_texture_cache(texturepath)) { // already loaded diff --git a/libopenage/renderer/resources/parser/parse_blendtable.cpp b/libopenage/renderer/resources/parser/parse_blendtable.cpp index c37579ce36..e564085820 100644 --- a/libopenage/renderer/resources/parser/parse_blendtable.cpp +++ b/libopenage/renderer/resources/parser/parse_blendtable.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 "parse_blendtable.h" @@ -67,16 +67,16 @@ PatternData parse_pattern(const std::vector &args) { return pattern; } -BlendTableInfo parse_blendtable_file(const util::Path &file, +BlendTableInfo parse_blendtable_file(const util::Path &path, const std::shared_ptr &cache) { - if (not file.is_file()) [[unlikely]] { + if (not path.is_file()) [[unlikely]] { throw Error(MSG(err) << "Reading .bltable file '" - << file.get_name() + << path.get_name() << "' failed. Reason: File not found"); } - auto content = file.open(); - auto lines = content.get_lines(); + auto file = path.open(); + auto lines = file.get_lines(); std::vector blendtable; std::vector patterns; @@ -87,7 +87,7 @@ BlendTableInfo parse_blendtable_file(const util::Path &file, if (version_no != 1) { throw Error(MSG(err) << "Reading .bltable file '" - << file.get_name() + << path.get_name() << "' failed. Reason: Version " << version_no << " not supported"); } @@ -110,7 +110,7 @@ BlendTableInfo parse_blendtable_file(const util::Path &file, // TODO: Avoid double lookup with keywordfuncs.find(args[0]) if (not keywordfuncs.contains(args[0])) [[unlikely]] { throw Error(MSG(err) << "Reading .bltable file '" - << file.get_name() + << path.get_name() << "' failed. Reason: Keyword " << args[0] << " is not defined"); } @@ -125,7 +125,7 @@ BlendTableInfo parse_blendtable_file(const util::Path &file, i += 1; if (i >= lines.size()) { throw Error(MSG(err) << "Reading .bltable file '" - << file.get_name() + << path.get_name() << "' failed. Reason: Matrix for keyword " << args[0] << " is malformed"); } @@ -142,7 +142,7 @@ BlendTableInfo parse_blendtable_file(const util::Path &file, std::vector> pattern_infos; for (auto pattern : patterns) { - util::Path maskpath = (file.get_parent() / pattern.path); + util::Path maskpath = (path.get_parent() / pattern.path); if (cache && cache->check_blpattern_cache(maskpath)) { // already loaded diff --git a/libopenage/renderer/resources/parser/parse_palette.cpp b/libopenage/renderer/resources/parser/parse_palette.cpp index 344466dd86..abea683f6a 100644 --- a/libopenage/renderer/resources/parser/parse_palette.cpp +++ b/libopenage/renderer/resources/parser/parse_palette.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 "parse_palette.h" @@ -63,15 +63,15 @@ std::vector parse_colours(const std::vector &lines) { return entries; } -PaletteInfo parse_palette_file(const util::Path &file) { - if (not file.is_file()) [[unlikely]] { +PaletteInfo parse_palette_file(const util::Path &path) { + if (not path.is_file()) [[unlikely]] { throw Error(MSG(err) << "Reading .opal file '" - << file.get_name() + << path.get_name() << "' failed. Reason: File not found"); } - auto content = file.open(); - auto lines = content.get_lines(); + auto file = path.open(); + auto lines = file.get_lines(); size_t entries = 0; std::vector colours; @@ -82,7 +82,7 @@ PaletteInfo parse_palette_file(const util::Path &file) { if (version_no != 1) { throw Error(MSG(err) << "Reading .opal file '" - << file.get_name() + << path.get_name() << "' failed. Reason: Version " << version_no << " not supported"); } @@ -105,7 +105,7 @@ PaletteInfo parse_palette_file(const util::Path &file) { // TODO: Avoid double lookup with keywordfuncs.find(args[0]) if (not keywordfuncs.contains(args[0])) [[unlikely]] { throw Error(MSG(err) << "Reading .opal file '" - << file.get_name() + << path.get_name() << "' failed. Reason: Keyword " << args[0] << " is not defined"); } @@ -120,7 +120,7 @@ PaletteInfo parse_palette_file(const util::Path &file) { i += 1; if (i >= lines.size()) { throw Error(MSG(err) << "Reading .opal file '" - << file.get_name() + << path.get_name() << "' failed. Reason: Matrix for keyword " << args[0] << " is malformed"); } diff --git a/libopenage/renderer/resources/parser/parse_sprite.cpp b/libopenage/renderer/resources/parser/parse_sprite.cpp index 2156cc8422..01534ad755 100644 --- a/libopenage/renderer/resources/parser/parse_sprite.cpp +++ b/libopenage/renderer/resources/parser/parse_sprite.cpp @@ -1,4 +1,4 @@ -// Copyright 2021-2023 the openage authors. See copying.md for legal info. +// Copyright 2021-2024 the openage authors. See copying.md for legal info. #include "parse_sprite.h" @@ -134,16 +134,16 @@ FrameData parse_frame(const std::vector &args) { return frame; } -Animation2dInfo parse_sprite_file(const util::Path &file, +Animation2dInfo parse_sprite_file(const util::Path &path, const std::shared_ptr &cache) { - if (not file.is_file()) [[unlikely]] { + if (not path.is_file()) [[unlikely]] { throw Error(MSG(err) << "Reading .sprite file '" - << file.get_name() + << path.get_name() << "' failed. Reason: File not found"); } - auto content = file.open(); - auto lines = content.get_lines(); + auto file = path.open(); + auto lines = file.get_lines(); float scalefactor = 1.0; std::vector textures; @@ -162,7 +162,7 @@ Animation2dInfo parse_sprite_file(const util::Path &file, if (version_no != 2) { throw Error(MSG(err) << "Reading .sprite file '" - << file.get_name() + << path.get_name() << "' failed. Reason: Version " << version_no << " not supported"); } @@ -188,7 +188,7 @@ Animation2dInfo parse_sprite_file(const util::Path &file, frames.at(frame.angle).push_back(frame); // check for the largest index, so we can use it to - // interpolate the total animation length + // interpolate the total animation length if (frame.index > largest_frame_idx) { largest_frame_idx = frame.index; } @@ -204,7 +204,7 @@ Animation2dInfo parse_sprite_file(const util::Path &file, // TODO: Avoid double lookup with keywordfuncs.find(args[0]) if (not keywordfuncs.contains(args[0])) [[unlikely]] { throw Error(MSG(err) << "Reading .sprite file '" - << file.get_name() + << path.get_name() << "' failed. Reason: Keyword " << args[0] << " is not defined"); } @@ -228,6 +228,13 @@ Animation2dInfo parse_sprite_file(const util::Path &file, return a1.degree < a2.degree; }); + // Order layers by position + std::sort(layers.begin(), + layers.end(), + [](LayerData &l1, LayerData &l2) { + return l1.position < l2.position; + }); + // Create ID map. Resolves IDs used in the file to array indices std::unordered_map texture_id_map; for (size_t i = 0; i < textures.size(); ++i) { @@ -310,7 +317,7 @@ Animation2dInfo parse_sprite_file(const util::Path &file, // Parse textures std::vector> texture_infos; for (auto texture : textures) { - util::Path texturepath = (file.get_parent() / texture.path); + util::Path texturepath = (path.get_parent() / texture.path); if (cache && cache->check_texture_cache(texturepath)) { // already loaded diff --git a/libopenage/renderer/resources/parser/parse_sprite.h b/libopenage/renderer/resources/parser/parse_sprite.h index b2021190b3..5f989f5aa3 100644 --- a/libopenage/renderer/resources/parser/parse_sprite.h +++ b/libopenage/renderer/resources/parser/parse_sprite.h @@ -1,4 +1,4 @@ -// Copyright 2021-2023 the openage authors. See copying.md for legal info. +// Copyright 2021-2024 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/renderer/resources/parser/parse_terrain.cpp b/libopenage/renderer/resources/parser/parse_terrain.cpp index cc7b2f4814..9b17f4608e 100644 --- a/libopenage/renderer/resources/parser/parse_terrain.cpp +++ b/libopenage/renderer/resources/parser/parse_terrain.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 "parse_terrain.h" @@ -139,16 +139,16 @@ TerrainFrameData parse_terrain_frame(const std::vector &args) { return frame; } -TerrainInfo parse_terrain_file(const util::Path &file, +TerrainInfo parse_terrain_file(const util::Path &path, const std::shared_ptr &cache) { - if (not file.is_file()) [[unlikely]] { + if (not path.is_file()) [[unlikely]] { throw Error(MSG(err) << "Reading .terrain file '" - << file.get_name() + << path.get_name() << "' failed. Reason: File not found"); } - auto content = file.open(); - auto lines = content.get_lines(); + auto file = path.open(); + auto lines = file.get_lines(); float scalefactor = 1.0; std::vector textures; @@ -167,7 +167,7 @@ TerrainInfo parse_terrain_file(const util::Path &file, if (version_no != 2) { throw Error(MSG(err) << "Reading .terrain file '" - << file.get_name() + << path.get_name() << "' failed. Reason: Version " << version_no << " not supported"); } @@ -193,7 +193,7 @@ TerrainInfo parse_terrain_file(const util::Path &file, frames.at(frame.layer_id).push_back(frame); // check for the largest index, so we can use it to - // interpolate the total animation length + // interpolate the total animation length if (frame.index > largest_frame_idx) { largest_frame_idx = frame.index; } @@ -209,7 +209,7 @@ TerrainInfo parse_terrain_file(const util::Path &file, // TODO: Avoid double lookup with keywordfuncs.find(args[0]) if (not keywordfuncs.contains(args[0])) [[unlikely]] { throw Error(MSG(err) << "Reading .terrain file '" - << file.get_name() + << path.get_name() << "' failed. Reason: Keyword " << args[0] << " is not defined"); } @@ -226,6 +226,13 @@ TerrainInfo parse_terrain_file(const util::Path &file, }); } + // Order layers by position + std::sort(layers.begin(), + layers.end(), + [](TerrainLayerData &l1, TerrainLayerData &l2) { + return l1.position < l2.position; + }); + // Create ID map. Resolves IDs used in the file to array indices std::unordered_map texture_id_map; for (size_t i = 0; i < textures.size(); ++i) { @@ -262,7 +269,7 @@ TerrainInfo parse_terrain_file(const util::Path &file, // Parse textures std::vector> texture_infos; for (auto texture : textures) { - util::Path texturepath = (file.get_parent() / texture.path); + util::Path texturepath = (path.get_parent() / texture.path); if (cache && cache->check_texture_cache(texturepath)) { // already loaded @@ -280,7 +287,7 @@ TerrainInfo parse_terrain_file(const util::Path &file, std::shared_ptr blendtable_info; if (blendtable) { - util::Path tablepath = (file.get_parent() / blendtable.value().path); + util::Path tablepath = (path.get_parent() / blendtable.value().path); if (cache && cache->check_bltable_cache(tablepath)) { // already loaded diff --git a/libopenage/renderer/resources/parser/parse_terrain.h b/libopenage/renderer/resources/parser/parse_terrain.h index 6aad27ce6e..ea5c582f38 100644 --- a/libopenage/renderer/resources/parser/parse_terrain.h +++ b/libopenage/renderer/resources/parser/parse_terrain.h @@ -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. #pragma once diff --git a/libopenage/renderer/resources/parser/parse_texture.cpp b/libopenage/renderer/resources/parser/parse_texture.cpp index 7da91211f4..6f1ee622d3 100644 --- a/libopenage/renderer/resources/parser/parse_texture.cpp +++ b/libopenage/renderer/resources/parser/parse_texture.cpp @@ -1,4 +1,4 @@ -// Copyright 2021-2023 the openage authors. See copying.md for legal info. +// Copyright 2021-2024 the openage authors. See copying.md for legal info. #include "parse_texture.h" @@ -139,15 +139,15 @@ SubtextureData parse_subtex(const std::vector &args) { return subtex; } -Texture2dInfo parse_texture_file(const util::Path &file) { - if (not file.is_file()) [[unlikely]] { +Texture2dInfo parse_texture_file(const util::Path &path) { + if (not path.is_file()) [[unlikely]] { throw Error(MSG(err) << "Reading .texture file '" - << file.get_name() + << path.get_name() << "' failed. Reason: File not found"); } - auto content = file.open(); - auto lines = content.get_lines(); + auto file = path.open(); + auto lines = file.get_lines(); std::string imagefile; SizeData size; @@ -160,7 +160,7 @@ Texture2dInfo parse_texture_file(const util::Path &file) { if (version_no != 1) { throw Error(MSG(err) << "Reading .texture file '" - << file.get_name() + << path.get_name() << "' failed. Reason: Version " << version_no << " not supported"); } @@ -188,7 +188,7 @@ Texture2dInfo parse_texture_file(const util::Path &file) { // TODO: Avoid double lookup with keywordfuncs.find(args[0]) if (not keywordfuncs.contains(args[0])) [[unlikely]] { throw Error(MSG(err) << "Reading .texture file '" - << file.get_name() + << path.get_name() << "' failed. Reason: Keyword " << args[0] << " is not defined"); } @@ -208,7 +208,7 @@ Texture2dInfo parse_texture_file(const util::Path &file) { size.height); } - auto imagepath = file.get_parent() / imagefile; + auto imagepath = path.get_parent() / imagefile; auto align = guess_row_alignment(size.width); return Texture2dInfo(size.width, size.height, pxformat.format, imagepath, align, std::move(subinfos)); 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/resources/terrain/blendtable_info.h b/libopenage/renderer/resources/terrain/blendtable_info.h index 933a31201c..dfdefcbd97 100644 --- a/libopenage/renderer/resources/terrain/blendtable_info.h +++ b/libopenage/renderer/resources/terrain/blendtable_info.h @@ -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. #pragma once @@ -29,8 +29,8 @@ class BlendTableInfo { /** * Get the blending lookup table. - * - * @return Lookup table for blending patterns. + * + * @return Lookup table for blending patterns. */ const std::vector &get_table() const; diff --git a/libopenage/renderer/resources/terrain/frame_info.h b/libopenage/renderer/resources/terrain/frame_info.h index b4673dc377..046fc9514f 100644 --- a/libopenage/renderer/resources/terrain/frame_info.h +++ b/libopenage/renderer/resources/terrain/frame_info.h @@ -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. #pragma once @@ -17,9 +17,9 @@ class TerrainFrameInfo { * Create a 2D Frame Info. * * @param texture_idx Index of the texture containing the frame in the - * TerrainInfo object. + * TerrainInfo object. * @param subtexture_idx Index of the subtexture containing the frame - * in the Texture2dInfo object. + * in the Texture2dInfo object. * @param priority Blending priority. * @param priority Blend mode index. */ @@ -61,27 +61,27 @@ class TerrainFrameInfo { private: /** - * Index of the texture containing the frame in the TerrainInfo object. - */ + * Index of the texture containing the frame in the TerrainInfo object. + */ size_t texture_idx; /** - * Index of the subtexture containing the frame in the Texture2dInfo object. - */ + * Index of the subtexture containing the frame in the Texture2dInfo object. + */ size_t subtexture_idx; /** - * Decides which blending table of the two adjacent terrain textures is selected. - * - * If two adjacent terrains have equal priority, the blending table of the terrain - * with a lower x coordinate value is selected. If the x coordinate value is also - * equal, the blending table of the terrain with the lowest y coordinate is selected. - */ + * Decides which blending table of the two adjacent terrain textures is selected. + * + * If two adjacent terrains have equal priority, the blending table of the terrain + * with a lower x coordinate value is selected. If the x coordinate value is also + * equal, the blending table of the terrain with the lowest y coordinate is selected. + */ size_t priority; /** - * Used for looking up the blending pattern index in a blending table. - */ + * Used for looking up the blending pattern index in a blending table. + */ std::optional blend_mode; }; diff --git a/libopenage/renderer/resources/texture_data.cpp b/libopenage/renderer/resources/texture_data.cpp index 0e765b61d7..fe11c19ce5 100644 --- a/libopenage/renderer/resources/texture_data.cpp +++ b/libopenage/renderer/resources/texture_data.cpp @@ -177,7 +177,7 @@ void Texture2dData::store(const util::Path &file) const { QImage image{this->data.data(), size.first, size.second, pix_fmt}; - // Call sdl_image for saving the screenshot to PNG + // Call QImage for saving the screenshot to PNG std::string path = file.resolve_native_path_w(); image.save(path.c_str()); } diff --git a/libopenage/renderer/resources/texture_data.h b/libopenage/renderer/resources/texture_data.h index 0107959d36..004a2bda1e 100644 --- a/libopenage/renderer/resources/texture_data.h +++ b/libopenage/renderer/resources/texture_data.h @@ -31,8 +31,8 @@ class Texture2dData { /// Create a texture from info. /// - /// Uses SDL Image internally. For supported image file types, - /// see the SDL_Image initialization in the engine. + /// Uses QImage internally. For supported image file types, + /// see the QImage initialization in the engine. Texture2dData(Texture2dInfo const &info); /// Construct by moving the information and raw texture data from somewhere else. diff --git a/libopenage/renderer/resources/texture_info.h b/libopenage/renderer/resources/texture_info.h index f51f34ed73..7884fe0f79 100644 --- a/libopenage/renderer/resources/texture_info.h +++ b/libopenage/renderer/resources/texture_info.h @@ -1,4 +1,4 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once @@ -162,8 +162,8 @@ class Texture2dInfo { /** * Get the coordinates of a specific subtexture inside the main texture. * Coordinates are returned as normalized values (floats in range 0.0 to 1.0). - * - * @deprecated Use \p get_subtex_tile_params() instead. + * + * @deprecated Use \p get_subtex_tile_params() instead. * * @param subidx Index of the subtexture. * diff --git a/libopenage/renderer/resources/texture_subinfo.cpp b/libopenage/renderer/resources/texture_subinfo.cpp index d5c2168c4e..047becc76d 100644 --- a/libopenage/renderer/resources/texture_subinfo.cpp +++ b/libopenage/renderer/resources/texture_subinfo.cpp @@ -1,4 +1,4 @@ -// Copyright 2013-2023 the openage authors. See copying.md for legal info. +// Copyright 2013-2024 the openage authors. See copying.md for legal info. #include "texture_subinfo.h" @@ -38,7 +38,7 @@ const Eigen::Vector2i &Texture2dSubInfo::get_anchor_pos() const { return this->anchor_pos; } -const Eigen::Vector4f &Texture2dSubInfo::get_tile_params() const { +const Eigen::Vector4f &Texture2dSubInfo::get_subtex_coords() const { return this->tile_params; } diff --git a/libopenage/renderer/resources/texture_subinfo.h b/libopenage/renderer/resources/texture_subinfo.h index 139d63e0d6..70b1d039f3 100644 --- a/libopenage/renderer/resources/texture_subinfo.h +++ b/libopenage/renderer/resources/texture_subinfo.h @@ -1,4 +1,4 @@ -// Copyright 2013-2023 the openage authors. See copying.md for legal info. +// Copyright 2013-2024 the openage authors. See copying.md for legal info. #pragma once @@ -24,8 +24,8 @@ class Texture2dSubInfo { * @param h Height of subtexture. * @param cx Vertical anchor of subtexture. * @param cy Horizontal anchor of subtexture. - * @param atlas_width Width of the texture atlas containing the subtexture. - * @param atlas_height Height of the texture atlas containing the subtexture. + * @param atlas_width Width of the texture atlas containing the subtexture. + * @param atlas_height Height of the texture atlas containing the subtexture. */ Texture2dSubInfo(int32_t x, int32_t y, @@ -39,52 +39,52 @@ class Texture2dSubInfo { Texture2dSubInfo() = default; /** - * Get the position of the subtexture within the atlas. - * - * @return Pixel coordinates as 2-dimensional Eigen vector: (x, y) - */ + * Get the position of the subtexture within the atlas. + * + * @return Pixel coordinates as 2-dimensional Eigen vector: (x, y) + */ const Eigen::Vector2i &get_pos() const; /** - * Get the size of the subtexture. - * - * @return Pixel coordinates as 2-dimensional Eigen vector: (width, height) - */ + * Get the size of the subtexture. + * + * @return Pixel coordinates as 2-dimensional Eigen vector: (width, height) + */ const Eigen::Vector2 &get_size() const; /** - * Get the position of the subtexture anchor. - * - * @return Anchor coordinates as 2-dimensional Eigen vector: (x, y) - */ + * Get the position of the subtexture anchor (origin == top left). + * + * @return Anchor coordinates as 2-dimensional Eigen vector: (x, y) + */ const Eigen::Vector2i &get_anchor_pos() const; /** - * Get the normalized shader parameters of the subtexture. Use in the shader - * to sample the subtexture from the atlas. - * - * Values are in range (0.0, 1.0) and can be passed directly to a shader uniform. - * These parameters pre-computed and should be used whenever possible. - * - * @return Tile parameters as 4-dimensional Eigen vector: (x, y, width, height) - */ - const Eigen::Vector4f &get_tile_params() const; + * Get the normalized shader parameters of the subtexture. Use in the shader + * to sample the subtexture from the atlas. + * + * Values are in range (0.0, 1.0) and can be passed directly to a shader uniform. + * These parameters pre-computed and should be used whenever possible. + * + * @return Tile parameters as 4-dimensional Eigen vector: (x, y, width, height) + */ + const Eigen::Vector4f &get_subtex_coords() const; /** - * Get the anchor parameters of the subtexture center. Used in the model matrix - * to calculate the offset position for displaying the subtexture inside - * the OpenGL viewport. - * - * The parameters represent the pixel distance of the anchor point to the subtexture - * center, multiplied by 2 to account for the normalized viewport size (which is 2.0 - * because it spans from -1.0 to 1.0). - * - * To get the normalized offset distance, the parameters have to be divided by the - * viewport size and then multiplied by additional scaling factors (e.g. from the - * animation). - * - * @return Parameters as 2-dimensional Eigen vector: (x, y) - */ + * Get the anchor parameters of the subtexture center. Used in the shader + * to calculate the offset position for displaying the subtexture inside + * the OpenGL viewport. + * + * The parameters represent the pixel distance of the anchor point to the subtexture + * center, multiplied by 2 to account for the normalized viewport size (which is 2.0 + * because it spans from -1.0 to 1.0). + * + * To get the normalized offset distance, the parameters have to be divided by the + * viewport size and then multiplied by additional scaling factors (e.g. from the + * animation). + * + * @return Parameters as 2-dimensional Eigen vector: (x, y) + */ const Eigen::Vector2i &get_anchor_params() const; private: @@ -104,13 +104,13 @@ class Texture2dSubInfo { Eigen::Vector2i anchor_pos; /** - * Pre-computed normalized coordinates of the subtexture. - */ + * Pre-computed normalized coordinates of the subtexture. + */ Eigen::Vector4f tile_params; /** - * Pre-computed normalized coordinates of the subtexture anchor. - */ + * Pre-computed normalized coordinates of the subtexture anchor. + */ Eigen::Vector2i anchor_params; }; diff --git a/libopenage/renderer/shader_program.h b/libopenage/renderer/shader_program.h index de515f213a..a0d27dc5b0 100644 --- a/libopenage/renderer/shader_program.h +++ b/libopenage/renderer/shader_program.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 @@ -45,12 +45,12 @@ class ShaderProgram : public std::enable_shared_from_this { virtual bool has_uniform(const char *name) = 0; /** - * Binds a uniform block in the shader program to the same binding point as - * the given uniform buffer. - * - * @param buffer Uniform buffer to bind. - * @param block_name Name of the uniform block in the shader program. - */ + * Binds a uniform block in the shader program to the same binding point as + * the given uniform buffer. + * + * @param buffer Uniform buffer to bind. + * @param block_name Name of the uniform block in the shader program. + */ virtual void bind_uniform_buffer(const char *block_name, std::shared_ptr const &) = 0; @@ -94,39 +94,39 @@ class ShaderProgram : public std::enable_shared_from_this { /** * Set a uniform input variable in the actual shader program. */ - virtual void set_i32(std::shared_ptr const &, const char *, int32_t) = 0; - virtual void set_u32(std::shared_ptr const &, const char *, uint32_t) = 0; - virtual void set_f32(std::shared_ptr const &, const char *, float) = 0; - virtual void set_f64(std::shared_ptr const &, const char *, double) = 0; - virtual void set_bool(std::shared_ptr const &, const char *, bool) = 0; - virtual void set_v2f32(std::shared_ptr const &, const char *, Eigen::Vector2f const &) = 0; - virtual void set_v3f32(std::shared_ptr const &, const char *, Eigen::Vector3f const &) = 0; - virtual void set_v4f32(std::shared_ptr const &, const char *, Eigen::Vector4f const &) = 0; - virtual void set_v2i32(std::shared_ptr const &, const char *, Eigen::Vector2i const &) = 0; - virtual void set_v3i32(std::shared_ptr const &, const char *, Eigen::Vector3i const &) = 0; - virtual void set_v4i32(std::shared_ptr const &, const char *, Eigen::Vector4i const &) = 0; - virtual void set_v2ui32(std::shared_ptr const &, const char *, Eigen::Vector2 const &) = 0; - virtual void set_v3ui32(std::shared_ptr const &, const char *, Eigen::Vector3 const &) = 0; - virtual void set_v4ui32(std::shared_ptr const &, const char *, Eigen::Vector4 const &) = 0; - virtual void set_m4f32(std::shared_ptr const &, const char *, Eigen::Matrix4f const &) = 0; - virtual void set_tex(std::shared_ptr const &, const char *, std::shared_ptr const &) = 0; - - virtual void set_i32(std::shared_ptr const &, const uniform_id_t &, int32_t) = 0; - virtual void set_u32(std::shared_ptr const &, const uniform_id_t &, uint32_t) = 0; - virtual void set_f32(std::shared_ptr const &, const uniform_id_t &, float) = 0; - virtual void set_f64(std::shared_ptr const &, const uniform_id_t &, double) = 0; - virtual void set_bool(std::shared_ptr const &, const uniform_id_t &, bool) = 0; - virtual void set_v2f32(std::shared_ptr const &, const uniform_id_t &, Eigen::Vector2f const &) = 0; - virtual void set_v3f32(std::shared_ptr const &, const uniform_id_t &, Eigen::Vector3f const &) = 0; - virtual void set_v4f32(std::shared_ptr const &, const uniform_id_t &, Eigen::Vector4f const &) = 0; - virtual void set_v2i32(std::shared_ptr const &, const uniform_id_t &, Eigen::Vector2i const &) = 0; - virtual void set_v3i32(std::shared_ptr const &, const uniform_id_t &, Eigen::Vector3i const &) = 0; - virtual void set_v4i32(std::shared_ptr const &, const uniform_id_t &, Eigen::Vector4i const &) = 0; - virtual void set_v2ui32(std::shared_ptr const &, const uniform_id_t &, Eigen::Vector2 const &) = 0; - virtual void set_v3ui32(std::shared_ptr const &, const uniform_id_t &, Eigen::Vector3 const &) = 0; - virtual void set_v4ui32(std::shared_ptr const &, const uniform_id_t &, Eigen::Vector4 const &) = 0; - virtual void set_m4f32(std::shared_ptr const &, const uniform_id_t &, Eigen::Matrix4f const &) = 0; - virtual void set_tex(std::shared_ptr const &, const uniform_id_t &, std::shared_ptr const &) = 0; + virtual void set_i32(UniformInput &in, const char *, int32_t) = 0; + virtual void set_u32(UniformInput &in, const char *, uint32_t) = 0; + virtual void set_f32(UniformInput &in, const char *, float) = 0; + virtual void set_f64(UniformInput &in, const char *, double) = 0; + virtual void set_bool(UniformInput &in, const char *, bool) = 0; + virtual void set_v2f32(UniformInput &in, const char *, Eigen::Vector2f const &) = 0; + virtual void set_v3f32(UniformInput &in, const char *, Eigen::Vector3f const &) = 0; + virtual void set_v4f32(UniformInput &in, const char *, Eigen::Vector4f const &) = 0; + virtual void set_v2i32(UniformInput &in, const char *, Eigen::Vector2i const &) = 0; + virtual void set_v3i32(UniformInput &in, const char *, Eigen::Vector3i const &) = 0; + virtual void set_v4i32(UniformInput &in, const char *, Eigen::Vector4i const &) = 0; + virtual void set_v2ui32(UniformInput &in, const char *, Eigen::Vector2 const &) = 0; + virtual void set_v3ui32(UniformInput &in, const char *, Eigen::Vector3 const &) = 0; + virtual void set_v4ui32(UniformInput &in, const char *, Eigen::Vector4 const &) = 0; + virtual void set_m4f32(UniformInput &in, const char *, Eigen::Matrix4f const &) = 0; + virtual void set_tex(UniformInput &in, const char *, std::shared_ptr const &) = 0; + + virtual void set_i32(UniformInput &in, uniform_id_t id, int32_t) = 0; + virtual void set_u32(UniformInput &in, uniform_id_t id, uint32_t) = 0; + virtual void set_f32(UniformInput &in, uniform_id_t id, float) = 0; + virtual void set_f64(UniformInput &in, uniform_id_t id, double) = 0; + virtual void set_bool(UniformInput &in, uniform_id_t id, bool) = 0; + virtual void set_v2f32(UniformInput &in, uniform_id_t id, Eigen::Vector2f const &) = 0; + virtual void set_v3f32(UniformInput &in, uniform_id_t id, Eigen::Vector3f const &) = 0; + virtual void set_v4f32(UniformInput &in, uniform_id_t id, Eigen::Vector4f const &) = 0; + virtual void set_v2i32(UniformInput &in, uniform_id_t id, Eigen::Vector2i const &) = 0; + virtual void set_v3i32(UniformInput &in, uniform_id_t id, Eigen::Vector3i const &) = 0; + virtual void set_v4i32(UniformInput &in, uniform_id_t id, Eigen::Vector4i const &) = 0; + virtual void set_v2ui32(UniformInput &in, uniform_id_t id, Eigen::Vector2 const &) = 0; + virtual void set_v3ui32(UniformInput &in, uniform_id_t id, Eigen::Vector3 const &) = 0; + virtual void set_v4ui32(UniformInput &in, uniform_id_t id, Eigen::Vector4 const &) = 0; + virtual void set_m4f32(UniformInput &in, uniform_id_t id, Eigen::Matrix4f const &) = 0; + virtual void set_tex(UniformInput &in, uniform_id_t id, std::shared_ptr const &) = 0; }; } // namespace renderer diff --git a/libopenage/renderer/stages/CMakeLists.txt b/libopenage/renderer/stages/CMakeLists.txt index 5be8fb465b..77d6faa091 100644 --- a/libopenage/renderer/stages/CMakeLists.txt +++ b/libopenage/renderer/stages/CMakeLists.txt @@ -1,4 +1,9 @@ +add_sources(libopenage + render_entity.cpp +) + add_subdirectory(camera/) +add_subdirectory(hud/) add_subdirectory(screen/) add_subdirectory(skybox/) add_subdirectory(terrain/) diff --git a/libopenage/renderer/stages/camera/manager.cpp b/libopenage/renderer/stages/camera/manager.cpp index 580f7db597..d3cbd3f3b2 100644 --- a/libopenage/renderer/stages/camera/manager.cpp +++ b/libopenage/renderer/stages/camera/manager.cpp @@ -1,21 +1,23 @@ -// 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 "manager.h" #include +#include -#include "renderer/camera/camera.h" #include "renderer/uniform_buffer.h" #include "renderer/uniform_input.h" namespace openage::renderer::camera { -CameraManager::CameraManager(const std::shared_ptr &camera) : +CameraManager::CameraManager(const std::shared_ptr &camera, + const CameraBoundaries &camera_boundaries) : camera{camera}, move_motion_directions{static_cast(MoveDirection::NONE)}, zoom_motion_direction{static_cast(ZoomDirection::NONE)}, move_motion_speed{0.2f}, - zoom_motion_speed{0.05f} { + zoom_motion_speed{0.05f}, + camera_boundaries{camera_boundaries} { this->uniforms = this->camera->get_uniform_buffer()->new_uniform_input( "view", camera->get_view_matrix(), @@ -33,18 +35,18 @@ void CameraManager::move_frame(MoveDirection direction, float speed) { case MoveDirection::LEFT: // half the speed because the relationship between forward/back and // left/right is 1:2 in our ortho projection. - this->camera->move_rel(Eigen::Vector3f(-1.0f, 0.0f, 1.0f), speed / 2); + this->camera->move_rel(Eigen::Vector3f(-1.0f, 0.0f, 1.0f), speed / 2, this->camera_boundaries); break; case MoveDirection::RIGHT: // half the speed because the relationship between forward/back and // left/right is 1:2 in our ortho projection. - this->camera->move_rel(Eigen::Vector3f(1.0f, 0.0f, -1.0f), speed / 2); + this->camera->move_rel(Eigen::Vector3f(1.0f, 0.0f, -1.0f), speed / 2, this->camera_boundaries); break; case MoveDirection::FORWARD: - this->camera->move_rel(Eigen::Vector3f(-1.0f, 0.0f, -1.0f), speed); + this->camera->move_rel(Eigen::Vector3f(-1.0f, 0.0f, -1.0f), speed, this->camera_boundaries); break; case MoveDirection::BACKWARD: - this->camera->move_rel(Eigen::Vector3f(1.0f, 0.0f, 1.0f), speed); + this->camera->move_rel(Eigen::Vector3f(1.0f, 0.0f, 1.0f), speed, this->camera_boundaries); break; default: @@ -66,6 +68,10 @@ void CameraManager::zoom_frame(ZoomDirection direction, float speed) { } } +void CameraManager::set_camera_boundaries(const CameraBoundaries &camera_boundaries) { + this->camera_boundaries = camera_boundaries; +} + void CameraManager::update_motion() { if (this->move_motion_directions != static_cast(MoveDirection::NONE)) { Eigen::Vector3f move_dir{0.0f, 0.0f, 0.0f}; @@ -83,7 +89,7 @@ void CameraManager::update_motion() { move_dir += Eigen::Vector3f(1.0f, 0.0f, 1.0f); } - this->camera->move_rel(move_dir, this->move_motion_speed); + this->camera->move_rel(move_dir, this->move_motion_speed, this->camera_boundaries); } if (this->zoom_motion_direction != static_cast(ZoomDirection::NONE)) { @@ -97,11 +103,25 @@ void CameraManager::update_motion() { } void CameraManager::update_uniforms() { + // transformation matrices this->uniforms->update( "view", - camera->get_view_matrix(), + this->camera->get_view_matrix(), "proj", - camera->get_projection_matrix()); + this->camera->get_projection_matrix()); + + // zoom scaling + this->uniforms->update( + "inv_zoom", + 1.0f / this->camera->get_zoom()); + + auto viewport_size = this->camera->get_viewport_size(); + Eigen::Vector2f viewport_size_vec{ + 1.0f / static_cast(viewport_size[0]), + 1.0f / static_cast(viewport_size[1])}; + this->uniforms->update("inv_viewport_size", viewport_size_vec); + + // update the uniform buffer this->camera->get_uniform_buffer()->update_uniforms(this->uniforms); } diff --git a/libopenage/renderer/stages/camera/manager.h b/libopenage/renderer/stages/camera/manager.h index 342b4bd8ef..36d605e959 100644 --- a/libopenage/renderer/stages/camera/manager.h +++ b/libopenage/renderer/stages/camera/manager.h @@ -1,9 +1,11 @@ -// 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 +#include "renderer/camera/camera.h" namespace openage::renderer { class UniformBufferInput; @@ -44,65 +46,74 @@ enum class ZoomDirection { class CameraManager { public: /** - * Create a new camera manager. - * - * @param camera Camera to manage. - */ - CameraManager(const std::shared_ptr &camera); + * Create a new camera manager. + * + * @param camera Camera to manage. + * @param camera_boundaries Boundaries for the camera movement in the scene. + */ + CameraManager(const std::shared_ptr &camera, + const CameraBoundaries &camera_boundaries = DEFAULT_CAM_BOUNDARIES); ~CameraManager() = default; /** - * Update the camera position and zoom level. - * - * Additionally updates the camera uniform buffer with the view and projection - * matrices. - */ + * Update the camera position and zoom level. + * + * Additionally updates the camera uniform buffer with the view and projection + * matrices. + */ void update(); /** - * Move into the given direction for the current frame. - * - * @param direction Move direction. - * @param delta Move speed, i.e. a distance multiplier. - */ + * Move into the given direction for the current frame. + * + * @param direction Move direction. + * @param delta Move speed, i.e. a distance multiplier. + */ void move_frame(MoveDirection direction, float speed = 1.0f); /** - * Zoom into the given direction for the current frame. - * - * @param direction Zoom direction. - * @param delta Zoom speed, i.e. a distance multiplier. - */ + * Zoom into the given direction for the current frame. + * + * @param direction Zoom direction. + * @param delta Zoom speed, i.e. a distance multiplier. + */ void zoom_frame(ZoomDirection direction, float speed = 1.0f); /** - * Set the move directions of the camera. - * - * @param directions Bitfield of the move directions. - */ + * Set the move directions of the camera. + * + * @param directions Bitfield of the move directions. + */ void set_move_motion_dirs(int directions); /** - * Set the zoom direction of the camera. - * - * @param direction Zoom direction. - */ + * Set the zoom direction of the camera. + * + * @param direction Zoom direction. + */ void set_zoom_motion_dir(int direction); /** - * Set the move speed of the camera. - * - * @param speed Speed of the camera. - */ + * Set the move speed of the camera. + * + * @param speed Speed of the camera. + */ void set_move_motion_speed(float speed); /** - * Set the zoom speed of the camera. - * - * @param speed Speed of the camera. - */ + * Set the zoom speed of the camera. + * + * @param speed Speed of the camera. + */ void set_zoom_motion_speed(float speed); + /** + * Set boundaries for camera movement in the scene. + * + * @param camera_boundaries XYZ boundaries for the camera movement. + */ + void set_camera_boundaries(const CameraBoundaries &camera_boundaries); + private: /** * Update the camera parameters. @@ -110,39 +121,44 @@ class CameraManager { void update_motion(); /** - * Update the camera uniform buffer. - */ + * Update the camera uniform buffer. + */ void update_uniforms(); /** - * Camera. - */ + * Camera. + */ std::shared_ptr camera; /** - * Bitfield of the current move motion directions. - */ + * Bitfield of the current move motion directions. + */ int move_motion_directions; /** - * Bitfield of the current zoom motion direction. - */ + * Bitfield of the current zoom motion direction. + */ int zoom_motion_direction; /** - * Move motion speed of the camera. - */ + * Move motion speed of the camera. + */ float move_motion_speed; /** - * Zoom motion speed of the camera. - */ + * Zoom motion speed of the camera. + */ float zoom_motion_speed; /** - * Uniform buffer input for the camera. - */ + * Uniform buffer input for the camera. + */ std::shared_ptr uniforms; + + /** + * Camera boundaries for X and Z movement. Contains minimum and maximum values for each axes. + */ + CameraBoundaries camera_boundaries; }; } // namespace camera diff --git a/libopenage/renderer/stages/hud/CMakeLists.txt b/libopenage/renderer/stages/hud/CMakeLists.txt new file mode 100644 index 0000000000..811fd929ed --- /dev/null +++ b/libopenage/renderer/stages/hud/CMakeLists.txt @@ -0,0 +1,5 @@ +add_sources(libopenage + object.cpp + render_entity.cpp + render_stage.cpp +) diff --git a/libopenage/renderer/stages/hud/object.cpp b/libopenage/renderer/stages/hud/object.cpp new file mode 100644 index 0000000000..6e23b6663a --- /dev/null +++ b/libopenage/renderer/stages/hud/object.cpp @@ -0,0 +1,130 @@ +// Copyright 2023-2024 the openage authors. See copying.md for legal info. + +#include "object.h" + +#include "renderer/geometry.h" +#include "renderer/stages/hud/render_entity.h" + + +namespace openage::renderer::hud { + +HudDragObject::HudDragObject(const std::shared_ptr &asset_manager) : + require_renderable{true}, + changed{false}, + camera{nullptr}, + asset_manager{asset_manager}, + render_entity{nullptr}, + drag_pos{nullptr, 0, "", nullptr, {0, 0}}, + drag_start{0, 0}, + uniforms{nullptr}, + geometry{nullptr}, + last_update{0.0} { +} + +void HudDragObject::set_render_entity(const std::shared_ptr &entity) { + this->render_entity = entity; + this->fetch_updates(); +} + +void HudDragObject::set_camera(const std::shared_ptr &camera) { + this->camera = camera; +} + +void HudDragObject::fetch_updates(const time::time_t &time) { + if (not this->render_entity->is_changed()) { + // exit early because there is nothing to do + return; + } + + // Get data from render entity + this->drag_start = this->render_entity->get_drag_start(); + + // Thread-safe access to curves needs a lock on the render entity's mutex + auto read_lock = this->render_entity->get_read_lock(); + + this->drag_pos.sync(this->render_entity->get_drag_pos() /* , this->last_update */); + + // Unlock the render entity mutex + read_lock.unlock(); + + // Set self to changed so that world renderer can update the renderable + this->changed = true; + this->render_entity->clear_changed_flag(); + this->last_update = time; +} + +void HudDragObject::update_uniforms(const time::time_t & /* time */) { + // TODO: Only update uniforms that changed since last update + if (this->uniforms == nullptr) [[unlikely]] { + return; + } + + // TODO: Do something with the uniforms +} + +void HudDragObject::update_geometry(const time::time_t &time) { + // TODO: Only update geometry that changed since last update + if (this->geometry == nullptr) [[unlikely]] { + return; + } + + auto drag_start_ndc = this->drag_start.to_viewport(this->camera).to_ndc_space(this->camera); + auto drag_pos_ndc = this->drag_pos.get(time).to_viewport(this->camera).to_ndc_space(this->camera); + + float top = std::max(drag_start_ndc.y(), drag_pos_ndc.y()); + float bottom = std::min(drag_start_ndc.y(), drag_pos_ndc.y()); + float left = std::min(drag_start_ndc.x(), drag_pos_ndc.x()); + float right = std::max(drag_start_ndc.x(), drag_pos_ndc.x()); + + log::log(SPAM << "top: " << top + << ", bottom: " << bottom + << ", left: " << left + << ", right: " << right); + + std::array quad_vertices{ + left, top, 0.0f, 1.0f, // top left corner + left, + bottom, + 0.0f, + 0.0f, // bottom left corner + right, + top, + 1.0f, + 1.0f, // top right corner + right, + bottom, + 1.0f, + 0.0f // bottom right corner + }; + + std::vector vertex_data(quad_vertices.size() * sizeof(float)); + std::memcpy(vertex_data.data(), quad_vertices.data(), vertex_data.size()); + + this->geometry->update_verts(vertex_data); +} + +bool HudDragObject::requires_renderable() { + return this->require_renderable; +} + +void HudDragObject::clear_requires_renderable() { + this->require_renderable = false; +} + +bool HudDragObject::is_changed() { + return this->changed; +} + +void HudDragObject::clear_changed_flag() { + this->changed = false; +} + +void HudDragObject::set_uniforms(const std::shared_ptr &uniforms) { + this->uniforms = uniforms; +} + +void HudDragObject::set_geometry(const std::shared_ptr &geometry) { + this->geometry = geometry; +} + +} // namespace openage::renderer::hud diff --git a/libopenage/renderer/stages/hud/object.h b/libopenage/renderer/stages/hud/object.h new file mode 100644 index 0000000000..d81e51ed8d --- /dev/null +++ b/libopenage/renderer/stages/hud/object.h @@ -0,0 +1,178 @@ +// Copyright 2023-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include +#include + +#include "coord/pixel.h" +#include "curve/continuous.h" +#include "time/time.h" + + +namespace openage::renderer { +class Geometry; +class UniformInput; + +namespace camera { +class Camera; +} + +namespace resources { +class AssetManager; +class Animation2dInfo; +} // namespace resources + +namespace hud { +class DragRenderEntity; + +/** + * Stores the state of a renderable object in the HUD render stage. + */ +class HudDragObject { +public: + /** + * Create a new object for the HUD render stage. + * + * @param asset_manager Asset manager for loading resources. + */ + HudDragObject(const std::shared_ptr &asset_manager); + ~HudDragObject() = default; + + /** + * Set the world render entity. + * + * @param entity New world render entity. + */ + void set_render_entity(const std::shared_ptr &entity); + + /** + * Set the current camera of the scene. + * + * @param camera Camera object viewing the scene. + */ + void set_camera(const std::shared_ptr &camera); + + /** + * Fetch updates from the render entity. + * + * @param time Current simulation time. + */ + void fetch_updates(const time::time_t &time = 0.0); + + /** + * Update the uniforms of the renderable associated with this object. + * + * @param time Current simulation time. + */ + void update_uniforms(const time::time_t &time = 0.0); + + /** + * Update the geometry of the renderable associated with this object. + * + * @param time Current simulation time. + */ + void update_geometry(const time::time_t &time = 0.0); + + /** + * Check whether a new renderable needs to be created for this mesh. + * + * If true, the old renderable should be removed from the render pass. + * The updated uniforms and geometry should be passed to this mesh. + * Afterwards, clear the requirement flag with \p clear_requires_renderable(). + * + * @return true if a new renderable is required, else false. + */ + bool requires_renderable(); + + /** + * Indicate to this mesh that a new renderable has been created. + */ + void clear_requires_renderable(); + + /** + * Check whether the object was changed by \p update(). + * + * @return true if changes were made, else false. + */ + bool is_changed(); + + /** + * Clear the update flag by setting it to false. + */ + void clear_changed_flag(); + + /** + * Set the reference to the uniform inputs of the renderable + * associated with this object. Relevant uniforms are updated + * when calling \p update(). + * + * @param uniforms Uniform inputs of this object's renderable. + */ + void set_uniforms(const std::shared_ptr &uniforms); + + /** + * Set the geometry of the renderable associated with this object. + * + * The geometry is updated when calling \p update(). + * + * @param geometry Geometry of this object's renderable. + */ + void set_geometry(const std::shared_ptr &geometry); + +private: + /** + * Stores whether a new renderable for this object needs to be created + * for the render pass. + */ + bool require_renderable; + + /** + * Stores whether the \p update() call changed the object. + */ + bool changed; + + /** + * Camera for model uniforms. + */ + std::shared_ptr camera; + + /** + * Asset manager for central accessing and loading asset resources. + */ + std::shared_ptr asset_manager; + + /** + * Source for positional and texture data. + */ + std::shared_ptr render_entity; + + /** + * Position of the dragged corner. + */ + curve::Continuous drag_pos; + + /** + * Position of the start corner. + */ + coord::input drag_start; + + /** + * Shader uniforms for the renderable in the HUD render pass. + */ + std::shared_ptr uniforms; + + /** + * Geometry of the renderable in the HUD render pass. + */ + std::shared_ptr geometry; + + /** + * Time of the last update call. + */ + time::time_t last_update; +}; +} // namespace hud +} // namespace openage::renderer diff --git a/libopenage/renderer/stages/hud/render_entity.cpp b/libopenage/renderer/stages/hud/render_entity.cpp new file mode 100644 index 0000000000..2ff3f75649 --- /dev/null +++ b/libopenage/renderer/stages/hud/render_entity.cpp @@ -0,0 +1,38 @@ +// Copyright 2023-2024 the openage authors. See copying.md for legal info. + +#include "render_entity.h" + +#include + + +namespace openage::renderer::hud { + +DragRenderEntity::DragRenderEntity(const coord::input drag_start) : + renderer::RenderEntity{}, + drag_pos{nullptr, 0, "", nullptr, drag_start}, + drag_start{drag_start} { +} + +void DragRenderEntity::update(const coord::input drag_pos, + const time::time_t time) { + std::unique_lock lock{this->mutex}; + + this->drag_pos.set_insert(time, drag_pos); + + this->last_update = time; + this->changed = true; +} + +const curve::Continuous &DragRenderEntity::get_drag_pos() { + std::shared_lock lock{this->mutex}; + + return this->drag_pos; +} + +const coord::input DragRenderEntity::get_drag_start() { + std::shared_lock lock{this->mutex}; + + return this->drag_start; +} + +} // namespace openage::renderer::hud diff --git a/libopenage/renderer/stages/hud/render_entity.h b/libopenage/renderer/stages/hud/render_entity.h new file mode 100644 index 0000000000..9d15204386 --- /dev/null +++ b/libopenage/renderer/stages/hud/render_entity.h @@ -0,0 +1,69 @@ +// Copyright 2023-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include "coord/pixel.h" +#include "curve/continuous.h" +#include "renderer/stages/render_entity.h" + + +namespace openage::renderer::hud { + +/** + * Render entity for pushing drag selection updates to the HUD renderer. + */ +class DragRenderEntity final : public renderer::RenderEntity { +public: + /** + * Create a new render entity for drag selection in the HUD. + * + * @param drag_start Position of the start corner. + */ + DragRenderEntity(const coord::input drag_start); + ~DragRenderEntity() = default; + + /** + * Update the render entity with information from the gamestate + * or input system. + * + * Updating the render entity with this method is thread-safe. + * + * @param drag_pos Position of the dragged corner. + * @param time Current simulation time. + */ + void update(const coord::input drag_pos, + const time::time_t time = 0.0); + + /** + * Get the position of the dragged corner. + * + * Accessing the drag position curve REQUIRES a read lock on the render entity + * (using \p get_read_lock()) to ensure thread safety. + * + * @return Coordinates of the dragged corner. + */ + const curve::Continuous &get_drag_pos(); + + /** + * Get the position of the start corner. + * + * Accessing the drag start is thread-safe. + * + * @return Coordinates of the start corner. + */ + const coord::input get_drag_start(); + +private: + /** + * Position of the dragged corner. + */ + curve::Continuous drag_pos; + + /** + * Position of the start corner. + */ + coord::input drag_start; +}; +} // namespace openage::renderer::hud diff --git a/libopenage/renderer/stages/hud/render_stage.cpp b/libopenage/renderer/stages/hud/render_stage.cpp new file mode 100644 index 0000000000..d5d7c5b83f --- /dev/null +++ b/libopenage/renderer/stages/hud/render_stage.cpp @@ -0,0 +1,130 @@ +// Copyright 2023-2024 the openage authors. See copying.md for legal info. + +#include "render_stage.h" + +#include "renderer/camera/camera.h" +#include "renderer/opengl/context.h" +#include "renderer/render_pass.h" +#include "renderer/render_target.h" +#include "renderer/resources/assets/asset_manager.h" +#include "renderer/resources/shader_source.h" +#include "renderer/resources/texture_info.h" +#include "renderer/shader_program.h" +#include "renderer/stages/hud/object.h" +#include "renderer/texture.h" +#include "renderer/window.h" +#include "time/clock.h" + + +namespace openage::renderer::hud { + +HudRenderStage::HudRenderStage(const std::shared_ptr &window, + const std::shared_ptr &renderer, + const std::shared_ptr &camera, + const util::Path &shaderdir, + const std::shared_ptr &asset_manager, + const std::shared_ptr clock) : + renderer{renderer}, + camera{camera}, + asset_manager{asset_manager}, + drag_object{nullptr}, + clock{clock} { + renderer::opengl::GlContext::check_error(); + + auto size = window->get_size(); + this->initialize_render_pass(size[0], size[1], shaderdir); + + window->add_resize_callback([this](size_t width, size_t height, double /*scale*/) { + this->resize(width, height); + }); + + log::log(INFO << "Created render stage 'HUD'"); +} + +std::shared_ptr HudRenderStage::get_render_pass() { + return this->render_pass; +} + +void HudRenderStage::add_drag_entity(const std::shared_ptr entity) { + std::unique_lock lock{this->mutex}; + + auto hud_object = std::make_shared(this->asset_manager); + hud_object->set_render_entity(entity); + hud_object->set_camera(this->camera); + this->drag_object = hud_object; +} + +void HudRenderStage::remove_drag_entity() { + std::unique_lock lock{this->mutex}; + + this->drag_object = nullptr; + this->render_pass->clear_renderables(); +} + +void HudRenderStage::update() { + std::unique_lock lock{this->mutex}; + auto current_time = this->clock->get_real_time(); + + if (this->drag_object) { + this->drag_object->fetch_updates(current_time); + if (this->drag_object->requires_renderable()) { + auto geometry = this->renderer->add_mesh_geometry(resources::MeshData::make_quad()); + auto transform_unifs = this->drag_select_shader->new_uniform_input( + "in_col", + Eigen::Vector4f{0.0f, 0.0f, 0.0f, 0.2f}); + + Renderable display_obj{ + transform_unifs, + geometry, + true, + true, + }; + + this->render_pass->add_renderables(std::move(display_obj)); + this->drag_object->clear_requires_renderable(); + + this->drag_object->set_uniforms(transform_unifs); + this->drag_object->set_geometry(geometry); + } + this->drag_object->update_uniforms(current_time); + this->drag_object->update_geometry(current_time); + } +} + +void HudRenderStage::resize(size_t width, size_t height) { + this->output_texture = renderer->add_texture(resources::Texture2dInfo(width, height, resources::pixel_format::rgba8)); + this->depth_texture = renderer->add_texture(resources::Texture2dInfo(width, height, resources::pixel_format::depth24)); + + auto fbo = this->renderer->create_texture_target({this->output_texture, this->depth_texture}); + this->render_pass->set_target(fbo); +} + +void HudRenderStage::initialize_render_pass(size_t width, + size_t height, + const util::Path &shaderdir) { + // Drag select shader + auto vert_shader_file = (shaderdir / "hud_drag_select.vert.glsl").open(); + auto vert_shader_src = renderer::resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::vertex, + vert_shader_file.read()); + vert_shader_file.close(); + + auto frag_shader_file = (shaderdir / "hud_drag_select.frag.glsl").open(); + auto frag_shader_src = renderer::resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::fragment, + frag_shader_file.read()); + frag_shader_file.close(); + + this->drag_select_shader = this->renderer->add_shader({vert_shader_src, frag_shader_src}); + + // Texture targets + this->output_texture = renderer->add_texture(resources::Texture2dInfo(width, height, resources::pixel_format::rgba8)); + this->depth_texture = renderer->add_texture(resources::Texture2dInfo(width, height, resources::pixel_format::depth24)); + + auto fbo = this->renderer->create_texture_target({this->output_texture, this->depth_texture}); + this->render_pass = this->renderer->add_render_pass({}, fbo); +} + +} // namespace openage::renderer::hud diff --git a/libopenage/renderer/stages/hud/render_stage.h b/libopenage/renderer/stages/hud/render_stage.h new file mode 100644 index 0000000000..047aac6349 --- /dev/null +++ b/libopenage/renderer/stages/hud/render_stage.h @@ -0,0 +1,163 @@ +// Copyright 2023-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include + +#include "util/path.h" + +namespace openage { + +namespace time { +class Clock; +} + +namespace renderer { +class Renderer; +class RenderPass; +class ShaderProgram; +class Texture2d; +class Window; + +namespace camera { +class Camera; +} + +namespace resources { +class AssetManager; +} + +namespace hud { +class HudDragObject; +class DragRenderEntity; + +/** + * Renderer for the "Heads-Up Display" (HUD). + * Draws UI elements that are not part of the GUI, e.g. health bars, selection boxes, minimap, etc. + * + * TODO: Currently only supports drag selection. + */ +class HudRenderStage { +public: + /** + * Create a new render stage for the HUD. + * + * @param window openage window targeted for rendering. + * @param renderer openage low-level renderer. + * @param camera Camera used for the rendered scene. + * @param shaderdir Directory containing the shader source files. + * @param asset_manager Asset manager for loading resources. + * @param clock Simulation clock for timing animations. + */ + HudRenderStage(const std::shared_ptr &window, + const std::shared_ptr &renderer, + const std::shared_ptr &camera, + const util::Path &shaderdir, + const std::shared_ptr &asset_manager, + const std::shared_ptr clock); + ~HudRenderStage() = default; + + /** + * Get the render pass of the HUD renderer. + * + * @return Render pass for HUD drawing. + */ + std::shared_ptr get_render_pass(); + + /** + * Add a new render entity for drag selection. + * + * @param render_entity New render entity. + */ + void add_drag_entity(const std::shared_ptr entity); + + /** + * Remove the render object for drag selection. + * + * @param render_entity Render entity to remove. + */ + void remove_drag_entity(); + + /** + * Update the render entities and render positions. + */ + void update(); + + /** + * Resize the FBO for the HUD rendering. This basically updates the output + * texture size. + * + * @param width New width of the FBO. + * @param height New height of the FBO. + */ + void resize(size_t width, size_t height); + +private: + /** + * Create the render pass for HUD drawing. + * + * Called during initialization of the HUD renderer. + * + * @param width Width of the FBO. + * @param height Height of the FBO. + * @param shaderdir Directory containg the shader source files. + */ + void initialize_render_pass(size_t width, + size_t height, + const util::Path &shaderdir); + + /** + * Reference to the openage renderer. + */ + std::shared_ptr renderer; + + /** + * Camera for model uniforms. + */ + std::shared_ptr camera; + + /** + * Texture manager for loading assets. + */ + std::shared_ptr asset_manager; + + /** + * Render pass for the HUD drawing. + */ + std::shared_ptr render_pass; + + /** + * Render object for the drag select rectangle. + */ + std::shared_ptr drag_object; + + /** + * Shader for rendering the drag select rectangle. + */ + std::shared_ptr drag_select_shader; + + /** + * Simulation clock for timing animations. + */ + std::shared_ptr clock; + + /** + * Output texture. + */ + std::shared_ptr output_texture; + + /** + * Depth texture. + */ + std::shared_ptr depth_texture; + + /** + * Mutex for protecting threaded access. + */ + std::shared_mutex mutex; +}; +} // namespace hud +} // namespace renderer +} // namespace openage diff --git a/libopenage/renderer/stages/render_entity.cpp b/libopenage/renderer/stages/render_entity.cpp new file mode 100644 index 0000000000..dc95de9a6d --- /dev/null +++ b/libopenage/renderer/stages/render_entity.cpp @@ -0,0 +1,37 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "render_entity.h" + +#include + + +namespace openage::renderer { + +RenderEntity::RenderEntity() : + changed{false}, + last_update{time::time_t::zero()} { +} + +time::time_t RenderEntity::get_update_time() { + std::shared_lock lock{this->mutex}; + + return this->last_update; +} + +bool RenderEntity::is_changed() { + std::shared_lock lock{this->mutex}; + + return this->changed; +} + +void RenderEntity::clear_changed_flag() { + std::unique_lock lock{this->mutex}; + + this->changed = false; +} + +std::shared_lock RenderEntity::get_read_lock() { + return std::shared_lock{this->mutex}; +} + +} // namespace openage::renderer diff --git a/libopenage/renderer/stages/render_entity.h b/libopenage/renderer/stages/render_entity.h new file mode 100644 index 0000000000..f441452968 --- /dev/null +++ b/libopenage/renderer/stages/render_entity.h @@ -0,0 +1,83 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + +#include + +#include "time/time.h" + + +namespace openage::renderer { + +/** + * Interface for render entities that allow pushing updates from game simulation + * to renderer. + */ +class RenderEntity { +public: + ~RenderEntity() = default; + + /** + * Get the time of the last update. + * + * Accessing the update time is thread-safe. + * + * @return Time of last update. + */ + time::time_t get_update_time(); + + /** + * Check whether the render entity has received new updates from the + * gamestate. + * + * @return true if updates have been received, else false. + */ + bool is_changed(); + + /** + * Clear the update flag by setting it to false. + */ + void clear_changed_flag(); + + /** + * Get a shared lock for thread-safe reading from the render entity. + * + * The caller is responsible for unlocking the mutex after reading. + * + * @return Lock for the render entity. + */ + std::shared_lock get_read_lock(); + +protected: + /** + * Create a new render entity. + * + * Members are initialized to these values by default: + * - \p changed: false + * - \p last_update: time::time_t::zero() + * + * Declared as protected to prevent direct instantiation of this class. + */ + RenderEntity(); + + /** + * Flag for determining if the render entity has been updated by the + * corresponding gamestate entity. Set to true every time \p update() + * is called. + */ + bool changed; + + /** + * Time of the last update call. + */ + time::time_t last_update; + + /** + * Mutex for protecting threaded access. + */ + std::shared_mutex mutex; +}; +} // namespace openage::renderer diff --git a/libopenage/renderer/stages/screen/CMakeLists.txt b/libopenage/renderer/stages/screen/CMakeLists.txt index b964aae19b..589edbe681 100644 --- a/libopenage/renderer/stages/screen/CMakeLists.txt +++ b/libopenage/renderer/stages/screen/CMakeLists.txt @@ -1,3 +1,4 @@ add_sources(libopenage - screen_renderer.cpp + render_stage.cpp + screenshot.cpp ) diff --git a/libopenage/renderer/stages/screen/screen_renderer.cpp b/libopenage/renderer/stages/screen/render_stage.cpp similarity index 75% rename from libopenage/renderer/stages/screen/screen_renderer.cpp rename to libopenage/renderer/stages/screen/render_stage.cpp index 1fa045abcf..3d07e4ed3e 100644 --- a/libopenage/renderer/stages/screen/screen_renderer.cpp +++ b/libopenage/renderer/stages/screen/render_stage.cpp @@ -1,8 +1,10 @@ -// Copyright 2022-2023 the openage authors. See copying.md for legal info. +// Copyright 2022-2024 the openage authors. See copying.md for legal info. -#include "screen_renderer.h" +#include "render_stage.h" #include "renderer/opengl/context.h" +#include "renderer/render_pass.h" +#include "renderer/render_target.h" #include "renderer/renderer.h" #include "renderer/resources/mesh_data.h" #include "renderer/resources/shader_source.h" @@ -15,9 +17,9 @@ namespace openage::renderer::screen { -ScreenRenderer::ScreenRenderer(const std::shared_ptr & /* window */, - const std::shared_ptr &renderer, - const util::Path &shaderdir) : +ScreenRenderStage::ScreenRenderStage(const std::shared_ptr & /* window */, + const std::shared_ptr &renderer, + const util::Path &shaderdir) : renderer{renderer}, render_targets{}, pass_outputs{} { @@ -28,16 +30,16 @@ ScreenRenderer::ScreenRenderer(const std::shared_ptr & /* window */, log::log(INFO << "Created render stage 'Screen'"); } -std::shared_ptr ScreenRenderer::get_render_pass() { +std::shared_ptr ScreenRenderStage::get_render_pass() { return this->render_pass; } -void ScreenRenderer::set_render_targets(const std::vector> &targets) { +void ScreenRenderStage::set_render_targets(const std::vector> &targets) { this->render_targets = targets; this->update_render_pass(); } -void ScreenRenderer::initialize_render_pass(const util::Path &shaderdir) { +void ScreenRenderStage::initialize_render_pass(const util::Path &shaderdir) { auto vert_shader_file = (shaderdir / "final.vert.glsl").open(); auto vert_shader_src = renderer::resources::ShaderSource( resources::shader_lang_t::glsl, @@ -59,7 +61,7 @@ void ScreenRenderer::initialize_render_pass(const util::Path &shaderdir) { this->render_pass = renderer->add_render_pass({}, renderer->get_display_target()); } -void ScreenRenderer::update_render_pass() { +void ScreenRenderStage::update_render_pass() { auto quad = renderer->add_mesh_geometry(renderer::resources::MeshData::make_quad()); std::vector output_layers{}; @@ -87,7 +89,7 @@ void ScreenRenderer::update_render_pass() { output_layers.push_back(display_obj); } this->render_pass->clear_renderables(); - this->render_pass->add_renderables(output_layers); + this->render_pass->add_renderables(std::move(output_layers)); } } // namespace openage::renderer::screen diff --git a/libopenage/renderer/stages/screen/screen_renderer.h b/libopenage/renderer/stages/screen/render_stage.h similarity index 80% rename from libopenage/renderer/stages/screen/screen_renderer.h rename to libopenage/renderer/stages/screen/render_stage.h index 7aae82a4be..4dc40c0d03 100644 --- a/libopenage/renderer/stages/screen/screen_renderer.h +++ b/libopenage/renderer/stages/screen/render_stage.h @@ -1,4 +1,4 @@ -// Copyright 2022-2023 the openage authors. See copying.md for legal info. +// Copyright 2022-2024 the openage authors. See copying.md for legal info. #pragma once @@ -22,12 +22,19 @@ namespace screen { * (i.e. the renderers display target). This should always be the * last render stage. */ -class ScreenRenderer { +class ScreenRenderStage { public: - ScreenRenderer(const std::shared_ptr &window, - const std::shared_ptr &renderer, - const util::Path &shaderdir); - ~ScreenRenderer() = default; + /** + * Create a new render stage for drawing to the screen. + * + * @param window openage window targeted for rendering. + * @param renderer openage low-level renderer. + * @param shaderdir Directory containing the shader source files. + */ + ScreenRenderStage(const std::shared_ptr &window, + const std::shared_ptr &renderer, + const util::Path &shaderdir); + ~ScreenRenderStage() = default; /** * Get the render pass of the screen renderer. diff --git a/libopenage/renderer/stages/screen/screenshot.cpp b/libopenage/renderer/stages/screen/screenshot.cpp new file mode 100644 index 0000000000..583641b6c0 --- /dev/null +++ b/libopenage/renderer/stages/screen/screenshot.cpp @@ -0,0 +1,68 @@ +// Copyright 2014-2024 the openage authors. See copying.md for legal info. + +#include "screenshot.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "job/job_manager.h" +#include "log/log.h" +#include "renderer/render_pass.h" +#include "renderer/render_target.h" +#include "renderer/resources/texture_data.h" +#include "renderer/stages/screen/render_stage.h" +#include "util/strings.h" + + +namespace openage::renderer::screen { + + +ScreenshotManager::ScreenshotManager(std::shared_ptr &renderer, + util::Path &outdir, + std::shared_ptr &job_mgr) : + outdir{outdir}, + count{0}, + last_time{0}, + renderer{renderer}, + job_manager{job_mgr} { +} + +std::string ScreenshotManager::gen_next_filename() { + std::time_t t = std::time(NULL); + + if (t == this->last_time) { + this->count++; + } + else { + this->count = 0; + this->last_time = t; + } + + // these two values (32) *must* be the same for safety reasons + char timestamp[32]; + std::strftime(timestamp, 32, "%Y-%m-%d_%H-%M-%S", std::localtime(&t)); + + return util::sformat("openage_%s_%02d.png", timestamp, this->count); +} + + +void ScreenshotManager::save_screenshot() { + // get screenshot image from scren renderer + auto pass = this->renderer->get_render_pass(); + auto target = pass->get_target(); + auto image = target->into_data(); + + auto store_function = [this, image]() { + image.store(this->outdir / this->gen_next_filename()); + return true; + }; + this->job_manager->enqueue(store_function); +} + +} // namespace openage::renderer::screen diff --git a/libopenage/renderer/stages/screen/screenshot.h b/libopenage/renderer/stages/screen/screenshot.h new file mode 100644 index 0000000000..718eabdf0d --- /dev/null +++ b/libopenage/renderer/stages/screen/screenshot.h @@ -0,0 +1,80 @@ +// Copyright 2014-2023 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include + +#include "util/path.h" + + +namespace openage { + +namespace job { +class JobManager; +} + +namespace renderer::screen { +class ScreenRenderStage; + +/** + * Takes screenshots, duh. + */ +class ScreenshotManager { +public: + /** + * Create a new screenshot manager. + * + * @param renderer Screen render stage to take the screenshot from. + * @param outdir Directory where the screenshots are saved. + * @param job_mgr Job manager to use for writing the screenshot to disk. + */ + ScreenshotManager(std::shared_ptr &renderer, + util::Path &outdir, + std::shared_ptr &job_mgr); + + ~ScreenshotManager() = default; + + /** + * Generate and save a screenshot of the last frame. + */ + void save_screenshot(); + +private: + /** + * Generates a filename for the screenshot. + * + * @return Filename for the screenshot. + */ + std::string gen_next_filename(); + + /** + * Directory where the screenshots are saved. + */ + util::Path outdir; + + /** + * Counter for the screenshot filename. Used if multiple screenshots + * are taken in the same second. + */ + unsigned count; + + /** + * Last time when a screenshot was taken. + */ + std::time_t last_time; + + /** + * Screen render stage to take the screenshot from. + */ + std::shared_ptr renderer; + + /** + * Job manager to use for writing the screenshot to disk. + */ + std::shared_ptr job_manager; +}; + +} // namespace renderer::screen +} // namespace openage diff --git a/libopenage/renderer/stages/skybox/CMakeLists.txt b/libopenage/renderer/stages/skybox/CMakeLists.txt index 0bafbc06db..2da05f4873 100644 --- a/libopenage/renderer/stages/skybox/CMakeLists.txt +++ b/libopenage/renderer/stages/skybox/CMakeLists.txt @@ -1,3 +1,3 @@ add_sources(libopenage - skybox_renderer.cpp + render_stage.cpp ) diff --git a/libopenage/renderer/stages/skybox/skybox_renderer.cpp b/libopenage/renderer/stages/skybox/render_stage.cpp similarity index 72% rename from libopenage/renderer/stages/skybox/skybox_renderer.cpp rename to libopenage/renderer/stages/skybox/render_stage.cpp index cc2777d324..b6fe47a7db 100644 --- a/libopenage/renderer/stages/skybox/skybox_renderer.cpp +++ b/libopenage/renderer/stages/skybox/render_stage.cpp @@ -1,8 +1,10 @@ -// Copyright 2022-2023 the openage authors. See copying.md for legal info. +// Copyright 2022-2024 the openage authors. See copying.md for legal info. -#include "skybox_renderer.h" +#include "render_stage.h" #include "renderer/opengl/context.h" +#include "renderer/render_pass.h" +#include "renderer/render_target.h" #include "renderer/renderer.h" #include "renderer/resources/mesh_data.h" #include "renderer/resources/shader_source.h" @@ -14,9 +16,9 @@ namespace openage::renderer::skybox { -SkyboxRenderer::SkyboxRenderer(const std::shared_ptr &window, - const std::shared_ptr &renderer, - const util::Path &shaderdir) : +SkyboxRenderStage::SkyboxRenderStage(const std::shared_ptr &window, + const std::shared_ptr &renderer, + const util::Path &shaderdir) : renderer{renderer}, bg_color{0.0, 0.0, 0.0, 1.0} // black { @@ -32,11 +34,11 @@ SkyboxRenderer::SkyboxRenderer(const std::shared_ptr &window, log::log(INFO << "Created render stage 'Skybox'"); } -std::shared_ptr SkyboxRenderer::get_render_pass() { +std::shared_ptr SkyboxRenderStage::get_render_pass() { return this->render_pass; } -void SkyboxRenderer::set_color(const Eigen::Vector4i col) { +void SkyboxRenderStage::set_color(const Eigen::Vector4i col) { this->bg_color = Eigen::Vector4f( col[0] / 255, col[1] / 255, @@ -45,7 +47,7 @@ void SkyboxRenderer::set_color(const Eigen::Vector4i col) { this->color_unif->update("in_col", this->bg_color); } -void SkyboxRenderer::set_color(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { +void SkyboxRenderStage::set_color(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { this->bg_color = Eigen::Vector4f( r / 255, g / 255, @@ -54,26 +56,26 @@ void SkyboxRenderer::set_color(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { this->color_unif->update("in_col", this->bg_color); } -void SkyboxRenderer::set_color(const Eigen::Vector4f col) { +void SkyboxRenderStage::set_color(const Eigen::Vector4f col) { this->bg_color = col; this->color_unif->update("in_col", this->bg_color); } -void SkyboxRenderer::set_color(float r, float g, float b, float a) { +void SkyboxRenderStage::set_color(float r, float g, float b, float a) { this->bg_color = Eigen::Vector4f(r, g, b, a); this->color_unif->update("in_col", this->bg_color); } -void SkyboxRenderer::resize(size_t width, size_t height) { +void SkyboxRenderStage::resize(size_t width, size_t height) { this->output_texture = renderer->add_texture(resources::Texture2dInfo(width, height, resources::pixel_format::rgba8)); auto fbo = this->renderer->create_texture_target({this->output_texture}); this->render_pass->set_target(fbo); } -void SkyboxRenderer::initialize_render_pass(size_t width, - size_t height, - const util::Path &shaderdir) { +void SkyboxRenderStage::initialize_render_pass(size_t width, + size_t height, + const util::Path &shaderdir) { auto vert_shader_file = (shaderdir / "skybox.vert.glsl").open(); auto vert_shader_src = renderer::resources::ShaderSource( resources::shader_lang_t::glsl, diff --git a/libopenage/renderer/stages/skybox/skybox_renderer.h b/libopenage/renderer/stages/skybox/render_stage.h similarity index 67% rename from libopenage/renderer/stages/skybox/skybox_renderer.h rename to libopenage/renderer/stages/skybox/render_stage.h index 910b971ccd..e6967be901 100644 --- a/libopenage/renderer/stages/skybox/skybox_renderer.h +++ b/libopenage/renderer/stages/skybox/render_stage.h @@ -1,4 +1,4 @@ -// Copyright 2022-2023 the openage authors. See copying.md for legal info. +// Copyright 2022-2024 the openage authors. See copying.md for legal info. #pragma once @@ -24,12 +24,19 @@ namespace skybox { * a black devilish void of nothingness. Maybe "hellbox" is more * appropriate.) */ -class SkyboxRenderer { +class SkyboxRenderStage { public: - SkyboxRenderer(const std::shared_ptr &window, - const std::shared_ptr &renderer, - const util::Path &shaderdir); - ~SkyboxRenderer() = default; + /** + * Create a new render stage for the skybox. + * + * @param window openage window targeted for rendering. + * @param renderer openage low-level renderer. + * @param shaderdir Directory containing the shader source files. + */ + SkyboxRenderStage(const std::shared_ptr &window, + const std::shared_ptr &renderer, + const util::Path &shaderdir); + ~SkyboxRenderStage() = default; /** * Get the render pass of the skybox renderer. @@ -39,26 +46,26 @@ class SkyboxRenderer { std::shared_ptr get_render_pass(); /** - * Set the color used for the skybox using integer values - * for each channel (0 to 255). - * - * @param col RGBA color value. - */ + * Set the color used for the skybox using integer values + * for each channel (0 to 255). + * + * @param col RGBA color value. + */ void set_color(const Eigen::Vector4i col); void set_color(uint8_t r, uint8_t g, uint8_t b, uint8_t a); /** - * Set the color used for the skybox using float values - * for each channel (0.0 to 1.0). - * - * @param col RGBA color value. - */ + * Set the color used for the skybox using float values + * for each channel (0.0 to 1.0). + * + * @param col RGBA color value. + */ void set_color(const Eigen::Vector4f col); void set_color(float r, float g, float b, float a); /** * Resize the FBO for the skybox rendering. This basically updates the output - * texture size. + * texture size. * * @param width New width of the FBO. * @param height New height of the FBO. @@ -96,13 +103,13 @@ class SkyboxRenderer { std::shared_ptr output_texture; /** - * Background color. - */ + * Background color. + */ Eigen::Vector4f bg_color; /** - * Stores background color uniform for the shader. - */ + * Stores background color uniform for the shader. + */ std::shared_ptr color_unif; }; diff --git a/libopenage/renderer/stages/terrain/CMakeLists.txt b/libopenage/renderer/stages/terrain/CMakeLists.txt index 6da55d3490..7a55cd28ed 100644 --- a/libopenage/renderer/stages/terrain/CMakeLists.txt +++ b/libopenage/renderer/stages/terrain/CMakeLists.txt @@ -1,6 +1,7 @@ add_sources(libopenage - terrain_mesh.cpp - terrain_model.cpp - terrain_render_entity.cpp - terrain_renderer.cpp + chunk.cpp + mesh.cpp + model.cpp + render_entity.cpp + render_stage.cpp ) diff --git a/libopenage/renderer/stages/terrain/chunk.cpp b/libopenage/renderer/stages/terrain/chunk.cpp new file mode 100644 index 0000000000..58a95d2a95 --- /dev/null +++ b/libopenage/renderer/stages/terrain/chunk.cpp @@ -0,0 +1,168 @@ +// Copyright 2023-2024 the openage authors. See copying.md for legal info. + +#include "chunk.h" + +#include "renderer/resources/assets/asset_manager.h" +#include "renderer/resources/mesh_data.h" +#include "renderer/stages/terrain/mesh.h" +#include "renderer/stages/terrain/render_entity.h" + + +namespace openage::renderer::terrain { + +TerrainChunk::TerrainChunk(const std::shared_ptr &asset_manager, + const util::Vector2s size, + const coord::scene2_delta offset) : + size{size}, + offset{offset}, + asset_manager{asset_manager} {} + +void TerrainChunk::set_render_entity(const std::shared_ptr &entity) { + this->render_entity = entity; +} + +void TerrainChunk::fetch_updates(const time::time_t & /* time */) { + // TODO: Don't create model if render entity is not set + if (not this->render_entity) { + return; + } + + // Check render entity for updates + if (not this->render_entity->is_changed()) { + return; + } + + // Get the terrain data from the render entity + auto terrain_size = this->render_entity->get_size(); + auto terrain_paths = this->render_entity->get_terrain_paths(); + auto tiles = this->render_entity->get_tiles(); + auto heightmap_verts = this->render_entity->get_vertices(); + + // Recreate the mesh data + // TODO: Change mesh instead of recreating it + // TODO: Multiple meshes + this->meshes.clear(); + for (const auto &terrain_path : terrain_paths) { + auto new_mesh = this->create_mesh(terrain_size, tiles, heightmap_verts, terrain_path); + new_mesh->create_model_matrix(this->offset); + this->meshes.push_back(new_mesh); + } + + // auto new_mesh = this->create_mesh(); + // new_mesh->create_model_matrix(this->offset); + // this->meshes.clear(); + // this->meshes.push_back(new_mesh); + + // Indicate to the render entity that its updates have been processed. + this->render_entity->clear_changed_flag(); +} + +void TerrainChunk::update_uniforms(const time::time_t &time) { + for (auto &mesh : this->meshes) { + mesh->update_uniforms(time); + } +} + +const std::vector> &TerrainChunk::get_meshes() const { + return this->meshes; +} + +std::shared_ptr TerrainChunk::create_mesh(const util::Vector2s vert_size, + const RenderEntity::tiles_t &tiles, + const std::vector &heightmap_verts, + const std::string &texture_path) { + auto v_width = vert_size[0]; + auto v_height = vert_size[1]; + + // vertex data for the mesh + std::vector mesh_verts{}; + + // vertex indices for the mesh + std::vector idxs{}; + + // maps indices of verts in the heightmap to indices in the vertex data vector + std::unordered_map index_map; + + for (size_t i = 0; i < v_width - 1; ++i) { + for (size_t j = 0; j < v_height - 1; ++j) { + auto tile = tiles.at(j + i * (v_height - 1)); + if (tile.second != texture_path) { + // Skip tiles with different textures + continue; + } + + // indices of the vertices of the current tile + // in the hightmap + std::array tile_verts{ + j + i * v_height, // top left + j + (i + 1) * v_height, // bottom left + j + 1 + (i + 1) * v_height, // bottom right + j + 1 + i * v_height, // top right + }; + + // add the vertices of the current tile to the vertex data vector + for (size_t v_idx : tile_verts) { + // skip if the vertex is already in the vertex data vector + if (not index_map.contains(v_idx)) { + auto v = heightmap_verts[v_idx]; + auto v_vec = v.to_world_space(); + mesh_verts.push_back(v_vec[0]); + mesh_verts.push_back(v_vec[1]); + mesh_verts.push_back(v_vec[2]); + mesh_verts.push_back((v.ne / 10).to_float()); + mesh_verts.push_back((v.se / 10).to_float()); + + // update the index map + // since new verts are added to the end of the vertex data vector + // the mapped index is the current size of the index map + index_map[v_idx] = index_map.size(); + } + } + + // first triangle + idxs.push_back(index_map[tile_verts[0]]); // top left + idxs.push_back(index_map[tile_verts[1]]); // bottom left + idxs.push_back(index_map[tile_verts[2]]); // bottom right + + // second triangle + idxs.push_back(index_map[tile_verts[0]]); // top left + idxs.push_back(index_map[tile_verts[2]]); // bottom right + idxs.push_back(index_map[tile_verts[3]]); // top right + } + } + + resources::VertexInputInfo info{ + {resources::vertex_input_t::V3F32, resources::vertex_input_t::V2F32}, + resources::vertex_layout_t::AOS, + resources::vertex_primitive_t::TRIANGLES, + resources::index_t::U16}; + + auto const vert_data_size_new = mesh_verts.size() * sizeof(float); + std::vector vert_data_new(vert_data_size_new); + std::memcpy(vert_data_new.data(), mesh_verts.data(), vert_data_size_new); + + auto const idx_data_size = idxs.size() * sizeof(uint16_t); + std::vector idx_data(idx_data_size); + std::memcpy(idx_data.data(), idxs.data(), idx_data_size); + + resources::MeshData meshdata{std::move(vert_data_new), std::move(idx_data), info}; + + // Create the terrain mesh + auto terrain_info = this->asset_manager->request_terrain(texture_path); + auto terrain_mesh = std::make_shared( + this->asset_manager, + terrain_info, + std::move(meshdata)); + + return terrain_mesh; +} + +util::Vector2s &TerrainChunk::get_size() { + return this->size; +} + +coord::scene2_delta &TerrainChunk::get_offset() { + return this->offset; +} + +} // namespace openage::renderer::terrain diff --git a/libopenage/renderer/stages/terrain/chunk.h b/libopenage/renderer/stages/terrain/chunk.h new file mode 100644 index 0000000000..7d3d9fcc70 --- /dev/null +++ b/libopenage/renderer/stages/terrain/chunk.h @@ -0,0 +1,130 @@ +// Copyright 2023-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include + +#include "coord/scene.h" +#include "renderer/stages/terrain/render_entity.h" +#include "time/time.h" +#include "util/vector.h" + + +namespace openage::renderer { + +namespace resources { +class AssetManager; +} + +namespace terrain { +class TerrainRenderMesh; + +/** + * Stores the state of a terrain chunk in the terrain render stage. + */ +class TerrainChunk { +public: + /** + * Create a new terrain chunk. + * + * @param asset_manager Asset manager for loading textures. + */ + TerrainChunk(const std::shared_ptr &asset_manager, + const util::Vector2s size, + const coord::scene2_delta offset); + + ~TerrainChunk() = default; + + /** + * Set the terrain render entity for vertex updates of this mesh. + * + * @param entity New terrain render entity. + * @param size Size of the chunk in tiles. + * @param offset Offset of the chunk from origin in tiles. + */ + void set_render_entity(const std::shared_ptr &entity); + + /** + * Fetch updates from the render entity. + * + * @param time Current simulation time. + */ + void fetch_updates(const time::time_t &time = 0.0); + + /** + * Update the uniforms of the meshes. + * + * @param time Current simulation time. + */ + void update_uniforms(const time::time_t &time = 0.0); + + /** + * Get the meshes composing the terrain. + * + * @return Vector of terrain meshes. + */ + const std::vector> &get_meshes() const; + + /** + * Get the size of the chunk in tiles. + * + * @return Size of the chunk (in tiles). + */ + util::Vector2s &get_size(); + + /** + * Get the offset of the chunk from origin in tiles. + * + * @return Offset of the chunk (in tiles). + */ + coord::scene2_delta &get_offset(); + +private: + /** + * Create a terrain mesh from the data provided by the render entity. + * + * @param vert_size Size of the terrain in vertices. + * @param tiles Data for each tile (elevation, terrain path). + * @param heightmap_verts Position of each vertex in the chunk. + * @param texture_path Path to the texture for the terrain. + * + * @return New terrain mesh. + */ + std::shared_ptr create_mesh(const util::Vector2s vert_size, + const RenderEntity::tiles_t &tiles, + const std::vector &heightmap_verts, + const std::string &texture_path); + + /** + * Size of the chunk in tiles (width x height). + */ + util::Vector2s size; + + /** + * Offset of the chunk from origin in tiles (x, y). + */ + coord::scene2_delta offset; + + /** + * Meshes composing the terrain. Each mesh represents a drawable vertex surface + * and a texture. + */ + std::vector> meshes; + + /** + * Asset manager for central accessing and loading textures. + */ + std::shared_ptr asset_manager; + + /** + * Source for ingame terrain coordinates. These coordinates are translated into + * our render vertex mesh when \p update() is called. + */ + std::shared_ptr render_entity; +}; + + +} // namespace terrain +} // namespace openage::renderer diff --git a/libopenage/renderer/stages/terrain/terrain_mesh.cpp b/libopenage/renderer/stages/terrain/mesh.cpp similarity index 67% rename from libopenage/renderer/stages/terrain/terrain_mesh.cpp rename to libopenage/renderer/stages/terrain/mesh.cpp index 1085e315d4..b5af5bee69 100644 --- a/libopenage/renderer/stages/terrain/terrain_mesh.cpp +++ b/libopenage/renderer/stages/terrain/mesh.cpp @@ -1,6 +1,6 @@ -// Copyright 2022-2023 the openage authors. See copying.md for legal info. +// Copyright 2022-2024 the openage authors. See copying.md for legal info. -#include "terrain_mesh.h" +#include "mesh.h" #include #include @@ -20,21 +20,21 @@ TerrainRenderMesh::TerrainRenderMesh(const std::shared_ptr &asset_manager, - const curve::Discrete &terrain_path, + const std::shared_ptr &info, renderer::resources::MeshData &&mesh) : require_renderable{true}, changed{true}, asset_manager{asset_manager}, - terrain_info{nullptr, 0}, + terrain_info{nullptr}, uniforms{nullptr}, mesh{std::move(mesh)} { - this->set_terrain_path(terrain_path); + this->set_terrain_info(info); } void TerrainRenderMesh::set_mesh(renderer::resources::MeshData &&mesh) { @@ -47,19 +47,12 @@ const renderer::resources::MeshData &TerrainRenderMesh::get_mesh() { return this->mesh; } -void TerrainRenderMesh::set_terrain_path(const curve::Discrete &terrain_path) { +void TerrainRenderMesh::set_terrain_info(const std::shared_ptr &info) { this->changed = true; - this->terrain_info.sync(terrain_path, - std::function(const std::string &)>( - [&](const std::string &path) { - if (path.empty()) { - return std::shared_ptr{nullptr}; - } - return this->asset_manager->request_terrain(path); - })); + this->terrain_info = info; } -void TerrainRenderMesh::update_uniforms(const time::time_t &time) { +void TerrainRenderMesh::update_uniforms(const time::time_t & /* time */) { // TODO: Only update uniforms that changed since last update if (this->uniforms == nullptr) [[unlikely]] { return; @@ -70,9 +63,9 @@ void TerrainRenderMesh::update_uniforms(const time::time_t &time) { } // local space -> world space - this->uniforms->update("model", this->get_model_matrix()); + this->uniforms->update("model", this->model_matrix); - auto tex_info = this->terrain_info.get(time)->get_texture(0); + auto tex_info = this->terrain_info->get_texture(0); auto tex_manager = this->asset_manager->get_texture_manager(); auto texture = tex_manager->request(tex_info->get_image_path().value()); @@ -97,10 +90,11 @@ const std::shared_ptr &TerrainRenderMesh::get_uniforms() return this->uniforms; } -Eigen::Matrix4f TerrainRenderMesh::get_model_matrix() { +void TerrainRenderMesh::create_model_matrix(const coord::scene2_delta &offset) { // TODO: Needs input from engine - auto transform = Eigen::Affine3f::Identity(); - return transform.matrix(); + auto model = Eigen::Affine3f::Identity(); + model.translate(offset.to_world_space()); + this->model_matrix = model.matrix(); } bool TerrainRenderMesh::is_changed() { diff --git a/libopenage/renderer/stages/terrain/mesh.h b/libopenage/renderer/stages/terrain/mesh.h new file mode 100644 index 0000000000..00bab1b1ed --- /dev/null +++ b/libopenage/renderer/stages/terrain/mesh.h @@ -0,0 +1,170 @@ +// Copyright 2022-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + +#include + +#include "coord/scene.h" +#include "renderer/resources/mesh_data.h" +#include "time/time.h" +#include "util/vector.h" + + +namespace openage::renderer { +class UniformInput; + +namespace resources { +class AssetManager; +class TerrainInfo; +} // namespace resources + +namespace terrain { + +/** + * Drawable chunk of terrain with a single texture. + */ +class TerrainRenderMesh { +public: + /** + * Create a new terrain render mesh with empty values. + * + * Mesh and texture need to be set before the terrain mesh becomes renderable. + * + * @param asset_manager Asset manager for central accessing and loading textures. + */ + TerrainRenderMesh(const std::shared_ptr &asset_manager); + + /** + * Create a new terrain render mesh. + * + * @param asset_manager Asset manager for central accessing and loading textures. + * @param info Terrain info for the renderable. + * @param mesh Vertex data of the mesh. + */ + TerrainRenderMesh(const std::shared_ptr &asset_manager, + const std::shared_ptr &info, + renderer::resources::MeshData &&mesh); + + ~TerrainRenderMesh() = default; + + /** + * Set the reference to the uniform inputs of the renderable + * associated with this mesh. Relevant uniforms are updated + * when calling \p update(). + * + * @param uniforms Uniform inputs of this mesh's renderable. + */ + void set_uniforms(const std::shared_ptr &uniforms); + + /** + * Get the reference to the uniform inputs of the mesh's renderable. + * + * @return Uniform inputs of the renderable. + */ + const std::shared_ptr &get_uniforms(); + + /** + * Set the vertex mesh for the terrain. + * + * @param mesh Mesh for creating a renderer geometry object. + */ + void set_mesh(renderer::resources::MeshData &&mesh); + + /** + * Get the vertex mesh for the terrain. + * + * @return Mesh for creating a renderer geometry object. + */ + const renderer::resources::MeshData &get_mesh(); + + /** + * Set the terrain info that is drawn onto the mesh. + * + * @param texture Terrain info. + */ + void set_terrain_info(const std::shared_ptr &info); + + /** + * Update the uniforms of the renderable associated with this object. + * + * @param time Current simulation time. + */ + void update_uniforms(const time::time_t &time = 0.0); + + /** + * Check whether a new renderable needs to be created for this mesh. + * + * If true, the old renderable should be removed from the render pass. + * The updated uniforms and geometry should be passed to this mesh. + * Afterwards, clear the requirement flag with \p clear_requires_renderable(). + * + * @return true if a new renderable is required, else false. + */ + bool requires_renderable(); + + /** + * Indicate to this mesh that a new renderable has been created. + */ + void clear_requires_renderable(); + + /** + * Create the model transformation matrix for rendering. + * + * @param offset Offset of the terrain mesh to the scene origin. + */ + void create_model_matrix(const coord::scene2_delta &offset); + + /** + * Check whether the mesh or texture were changed. + * + * @return true if changes were made, else false. + */ + bool is_changed(); + + /** + * Clear the update flag by setting it to false. + */ + void clear_changed_flag(); + +private: + /** + * Stores whether a new renderable for this mesh needs to be created + * for the render pass. + */ + bool require_renderable; + + /** + * Stores whether the the mesh or the texture are updated. + */ + bool changed; + + /** + * Asset manager for central accessing and loading textures. + */ + std::shared_ptr asset_manager; + + /** + * Terrain information for the renderables. + */ + std::shared_ptr terrain_info; + + /** + * Shader uniforms for the renderable in the terrain render pass. + */ + std::shared_ptr uniforms; + + /** + * Pre-transformation vertices for the terrain model. + */ + renderer::resources::MeshData mesh; + + /** + * Transformation matrix for the terrain model. + */ + Eigen::Matrix4f model_matrix; +}; +} // namespace terrain +} // namespace openage::renderer diff --git a/libopenage/renderer/stages/terrain/model.cpp b/libopenage/renderer/stages/terrain/model.cpp new file mode 100644 index 0000000000..30e235c1bd --- /dev/null +++ b/libopenage/renderer/stages/terrain/model.cpp @@ -0,0 +1,59 @@ +// Copyright 2022-2024 the openage authors. See copying.md for legal info. + +#include "model.h" + +#include +#include +#include +#include + +#include + +#include "coord/scene.h" +#include "renderer/resources/assets/asset_manager.h" +#include "renderer/resources/mesh_data.h" +#include "renderer/stages/terrain/chunk.h" +#include "renderer/stages/terrain/render_entity.h" +#include "util/fixed_point.h" +#include "util/vector.h" + + +namespace openage::renderer::terrain { + +TerrainRenderModel::TerrainRenderModel(const std::shared_ptr &asset_manager) : + chunks{}, + camera{nullptr}, + asset_manager{asset_manager} { +} + +void TerrainRenderModel::add_chunk(const std::shared_ptr &entity, + const util::Vector2s size, + const coord::scene2_delta offset) { + auto chunk = std::make_shared(this->asset_manager, size, offset); + chunk->set_render_entity(entity); + chunk->fetch_updates(); + + this->chunks.push_back(chunk); +} + +void TerrainRenderModel::set_camera(const std::shared_ptr &camera) { + this->camera = camera; +} + +void TerrainRenderModel::fetch_updates(const time::time_t &time) { + for (auto &chunk : this->chunks) { + chunk->fetch_updates(time); + } +} + +void TerrainRenderModel::update_uniforms(const time::time_t &time) { + for (auto &chunk : this->chunks) { + chunk->update_uniforms(time); + } +} + +const std::vector> &TerrainRenderModel::get_chunks() const { + return this->chunks; +} + +} // namespace openage::renderer::terrain diff --git a/libopenage/renderer/stages/terrain/model.h b/libopenage/renderer/stages/terrain/model.h new file mode 100644 index 0000000000..951599855d --- /dev/null +++ b/libopenage/renderer/stages/terrain/model.h @@ -0,0 +1,99 @@ +// Copyright 2022-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + +#include "coord/scene.h" +#include "time/time.h" +#include "util/vector.h" + + +namespace openage::renderer { + +namespace camera { +class Camera; +} + +namespace resources { +class AssetManager; +} + +namespace terrain { +class RenderEntity; +class TerrainRenderMesh; +class TerrainChunk; + +/** + * 3D model of the whole terrain. Combines the individual meshes + * into one single structure. + */ +class TerrainRenderModel { +public: + /** + * Create a new model for the terrain. + * + * @param asset_manager Asset manager for loading resources. + */ + TerrainRenderModel(const std::shared_ptr &asset_manager); + ~TerrainRenderModel() = default; + + /** + * Add a new chunk to the terrain model. + * + * @param entity Render entity of the chunk. + * @param chunk_size Size of the chunk in tiles. + * @param chunk_offset Offset of the chunk from origin in tiles. + */ + void add_chunk(const std::shared_ptr &entity, + const util::Vector2s chunk_size, + const coord::scene2_delta chunk_offset); + + /** + * Set the current camera of the scene. + * + * @param camera Camera object viewing the scene. + */ + void set_camera(const std::shared_ptr &camera); + + /** + * Fetch updates from the render entity. + * + * @param time Current simulation time. + */ + void fetch_updates(const time::time_t &time = 0.0); + + /** + * Update the uniforms of the renderable associated with this object. + * + * @param time Current simulation time. + */ + void update_uniforms(const time::time_t &time = 0.0); + + /** + * Get the chunks composing the terrain. + * + * @return Chunks of the terrain. + */ + const std::vector> &get_chunks() const; + +private: + /** + * Chunks composing the terrain. + */ + std::vector> chunks; + + /** + * Camera for view and projection uniforms. + */ + std::shared_ptr camera; + + /** + * Asset manager for central accessing and loading textures. + */ + std::shared_ptr asset_manager; +}; + +} // namespace terrain +} // namespace openage::renderer diff --git a/libopenage/renderer/stages/terrain/render_entity.cpp b/libopenage/renderer/stages/terrain/render_entity.cpp new file mode 100644 index 0000000000..2316f5dab7 --- /dev/null +++ b/libopenage/renderer/stages/terrain/render_entity.cpp @@ -0,0 +1,132 @@ +// Copyright 2022-2024 the openage authors. See copying.md for legal info. + +#include "render_entity.h" + +#include +#include +#include + + +namespace openage::renderer::terrain { + +RenderEntity::RenderEntity() : + renderer::RenderEntity{}, + size{0, 0}, + tiles{}, + terrain_paths{}, + vertices{} { +} + +void RenderEntity::update_tile(const util::Vector2s size, + const coord::tile &pos, + const terrain_elevation_t elevation, + const std::string terrain_path, + const time::time_t time) { + std::unique_lock lock{this->mutex}; + + if (this->vertices.empty()) { + throw Error(MSG(err) << "Cannot update tile: Vertices have not been initialized yet."); + } + + // find the postion of the tile in the vertex array + auto left_corner = pos.ne * size[0] + pos.se; + + // update the 4 vertices of the tile + this->vertices[left_corner].up = elevation.to_float(); // left corner + this->vertices[left_corner + 1].up = elevation.to_float(); // bottom corner + this->vertices[left_corner + (size[0] + 1)].up = elevation.to_float(); // top corner + this->vertices[left_corner + (size[0] + 2)].up = elevation.to_float(); // right corner + + // update tile + this->tiles[left_corner] = {elevation, terrain_path}; + + // update the last update time + this->last_update = time; + + // update the terrain paths + this->terrain_paths.insert(terrain_path); + + this->changed = true; +} + +void RenderEntity::update(const util::Vector2s size, + const tiles_t tiles, + const time::time_t time) { + std::unique_lock lock{this->mutex}; + + // increase by 1 in every dimension because tiles + // size is number of tiles, but we want number of vertices + util::Vector2i tile_size{size[0], size[1]}; + this->size = util::Vector2s{size[0] + 1, size[1] + 1}; + + // transfer mesh + auto vert_count = this->size[0] * this->size[1]; + this->vertices.clear(); + this->vertices.reserve(vert_count); + for (int j = 0; j < (int)this->size[1]; ++j) { + for (int i = 0; i < (int)this->size[0]; ++i) { + // for each vertex, compare the surrounding tiles + std::vector surround{}; + if (j - 1 >= 0 and i - 1 >= 0) { + surround.push_back(tiles[(i - 1) * size[1] + j - 1].first.to_float()); + } + if (j < tile_size[1] and i - 1 >= 0) { + surround.push_back(tiles[(i - 1) * size[1] + j].first.to_float()); + } + if (j < tile_size[1] and i < tile_size[0]) { + surround.push_back(tiles[i * size[1] + j].first.to_float()); + } + if (j - 1 >= 0 and i < tile_size[0]) { + surround.push_back(tiles[i * size[1] + j - 1].first.to_float()); + } + // select the height of the highest surrounding tile + auto max_height = *std::max_element(surround.begin(), surround.end()); + coord::scene3 v{ + static_cast(i), + static_cast(j), + max_height, + }; + this->vertices.push_back(v); + } + } + + // update tiles + this->tiles = tiles; + + // update the last update time + this->last_update = time; + + // update the terrain paths + this->terrain_paths.clear(); + for (const auto &tile : this->tiles) { + this->terrain_paths.insert(tile.second); + } + + this->changed = true; +} + +const std::vector RenderEntity::get_vertices() { + std::shared_lock lock{this->mutex}; + + return this->vertices; +} + +const RenderEntity::tiles_t RenderEntity::get_tiles() { + std::shared_lock lock{this->mutex}; + + return this->tiles; +} + +const std::unordered_set RenderEntity::get_terrain_paths() { + std::shared_lock lock{this->mutex}; + + return this->terrain_paths; +} + +const util::Vector2s RenderEntity::get_size() { + std::shared_lock lock{this->mutex}; + + return this->size; +} + +} // namespace openage::renderer::terrain diff --git a/libopenage/renderer/stages/terrain/render_entity.h b/libopenage/renderer/stages/terrain/render_entity.h new file mode 100644 index 0000000000..0f726a2351 --- /dev/null +++ b/libopenage/renderer/stages/terrain/render_entity.h @@ -0,0 +1,118 @@ +// Copyright 2022-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include + +#include "coord/scene.h" +#include "coord/tile.h" +#include "curve/discrete.h" +#include "renderer/stages/render_entity.h" +#include "util/vector.h" + + +namespace openage::renderer::terrain { + +/** + * Render entity for pushing updates to the Terrain renderer. + */ +class RenderEntity final : public renderer::RenderEntity { +public: + RenderEntity(); + ~RenderEntity() = default; + + using terrain_elevation_t = util::FixedPoint; + using tiles_t = std::vector>; + + /** + * Update a single tile of the displayed terrain (chunk) with information from the + * gamestate. + * + * Updating the render entity with this method is thread-safe. + * + * @param size Size of the terrain in tiles (width x length) + * @param pos Position of the tile in the chunk. + * @param elevation Height of terrain tile. + * @param terrain_path Path to the terrain definition. + * @param time Simulation time of the update. + */ + void update_tile(const util::Vector2s size, + const coord::tile &pos, + const terrain_elevation_t elevation, + const std::string terrain_path, + const time::time_t time = 0.0); + + /** + * Update the full grid of the displayed terrain (chunk) with information from the + * gamestate. + * + * Updating the render entity with this method is thread-safe. + * + * @param size Size of the terrain in tiles (width x length) + * @param tiles Animation data for each tile (elevation, terrain path). + * @param time Simulation time of the update. + */ + void update(const util::Vector2s size, + const tiles_t tiles, + const time::time_t time = 0.0); + + /** + * Get the vertices of the terrain. + * + * Accessing the terrain vertices is thread-safe. + * + * @return Vector of vertex coordinates. + */ + const std::vector get_vertices(); + + /** + * Get the tiles of the terrain. + * + * Accessing the terrain tiles is thread-safe. + * + * @return Terrain tiles. + */ + const tiles_t get_tiles(); + + /** + * Get the terrain paths used in the terrain. + * + * Accessing the terrain paths is thread-safe. + * + * @return Terrain paths. + */ + const std::unordered_set get_terrain_paths(); + + /** + * Get the number of vertices on each side of the terrain. + * + * Accessing the vertices size is thread-safe. + * + * @return Vector with width as first element and height as second element. + */ + const util::Vector2s get_size(); + +private: + /** + * Chunk dimensions (width x height). + */ + util::Vector2s size; + + /** + * Terrain tile information (elevation, terrain path). + */ + tiles_t tiles; + + /** + * Terrain texture paths used in \p tiles . + */ + std::unordered_set terrain_paths; + + /** + * Terrain vertices (ingame coordinates). + */ + std::vector vertices; +}; +} // namespace openage::renderer::terrain diff --git a/libopenage/renderer/stages/terrain/terrain_renderer.cpp b/libopenage/renderer/stages/terrain/render_stage.cpp similarity index 52% rename from libopenage/renderer/stages/terrain/terrain_renderer.cpp rename to libopenage/renderer/stages/terrain/render_stage.cpp index 62b7de8207..01376d886b 100644 --- a/libopenage/renderer/stages/terrain/terrain_renderer.cpp +++ b/libopenage/renderer/stages/terrain/render_stage.cpp @@ -1,27 +1,30 @@ -// Copyright 2022-2023 the openage authors. See copying.md for legal info. +// Copyright 2022-2024 the openage authors. See copying.md for legal info. -#include "terrain_renderer.h" +#include "render_stage.h" #include "renderer/camera/camera.h" #include "renderer/opengl/context.h" +#include "renderer/render_pass.h" +#include "renderer/render_target.h" #include "renderer/renderer.h" #include "renderer/resources/shader_source.h" #include "renderer/resources/texture_info.h" #include "renderer/shader_program.h" -#include "renderer/stages/terrain/terrain_mesh.h" -#include "renderer/stages/terrain/terrain_model.h" +#include "renderer/stages/terrain/chunk.h" +#include "renderer/stages/terrain/mesh.h" +#include "renderer/stages/terrain/model.h" #include "renderer/window.h" #include "time/clock.h" namespace openage::renderer::terrain { -TerrainRenderer::TerrainRenderer(const std::shared_ptr &window, - const std::shared_ptr &renderer, - const std::shared_ptr &camera, - const util::Path &shaderdir, - const std::shared_ptr &asset_manager, - const std::shared_ptr &clock) : +TerrainRenderStage::TerrainRenderStage(const std::shared_ptr &window, + const std::shared_ptr &renderer, + const std::shared_ptr &camera, + const util::Path &shaderdir, + const std::shared_ptr &asset_manager, + const std::shared_ptr &clock) : renderer{renderer}, camera{camera}, render_entity{nullptr}, @@ -41,47 +44,50 @@ TerrainRenderer::TerrainRenderer(const std::shared_ptr &window, log::log(INFO << "Created render stage 'Terrain'"); } -std::shared_ptr TerrainRenderer::get_render_pass() { +std::shared_ptr TerrainRenderStage::get_render_pass() { return this->render_pass; } -void TerrainRenderer::set_render_entity(const std::shared_ptr entity) { +void TerrainRenderStage::add_render_entity(const std::shared_ptr entity, + const util::Vector2s chunk_size, + const coord::scene2_delta chunk_offset) { std::unique_lock lock{this->mutex}; this->render_entity = entity; - this->model->set_render_entity(this->render_entity); + this->model->add_chunk(this->render_entity, chunk_size, chunk_offset); this->update(); } -void TerrainRenderer::update() { +void TerrainRenderStage::update() { this->model->fetch_updates(); auto current_time = this->clock->get_real_time(); - for (auto &mesh : this->model->get_meshes()) { - if (mesh->requires_renderable()) [[unlikely]] { /*probably doesn't happen that often?*/ - // TODO: Update uniforms and geometry individually, depending on what changed - // TODO: Update existing renderable instead of recreating it - auto geometry = this->renderer->add_mesh_geometry(mesh->get_mesh()); - auto transform_unifs = this->display_shader->create_empty_input(); - - Renderable display_obj{ - transform_unifs, - geometry, - true, - true, // it's a 3D object, so we need depth testing - }; - - // TODO: Remove old renderable instead of clearing everything - this->render_pass->clear_renderables(); - this->render_pass->add_renderables(display_obj); - mesh->clear_requires_renderable(); - - mesh->set_uniforms(transform_unifs); + for (auto &chunk : this->model->get_chunks()) { + for (auto &mesh : chunk->get_meshes()) { + if (mesh->requires_renderable()) [[unlikely]] { /*probably doesn't happen that often?*/ + // TODO: Update uniforms and geometry individually, depending on what changed + // TODO: Update existing renderable instead of recreating it + auto geometry = this->renderer->add_mesh_geometry(mesh->get_mesh()); + auto transform_unifs = this->display_shader->create_empty_input(); + + Renderable display_obj{ + transform_unifs, + geometry, + true, + true, // it's a 3D object, so we need depth testing + }; + + // TODO: Remove old renderable instead of clearing everything + this->render_pass->add_renderables(std::move(display_obj)); + mesh->clear_requires_renderable(); + + mesh->set_uniforms(transform_unifs); + } } } this->model->update_uniforms(current_time); } -void TerrainRenderer::resize(size_t width, size_t height) { +void TerrainRenderStage::resize(size_t width, size_t height) { this->output_texture = renderer->add_texture(resources::Texture2dInfo(width, height, resources::pixel_format::rgba8)); this->depth_texture = renderer->add_texture(resources::Texture2dInfo(width, height, resources::pixel_format::depth24)); @@ -89,9 +95,9 @@ void TerrainRenderer::resize(size_t width, size_t height) { this->render_pass->set_target(fbo); } -void TerrainRenderer::initialize_render_pass(size_t width, - size_t height, - const util::Path &shaderdir) { +void TerrainRenderStage::initialize_render_pass(size_t width, + size_t height, + const util::Path &shaderdir) { auto vert_shader_file = (shaderdir / "terrain.vert.glsl").open(); auto vert_shader_src = renderer::resources::ShaderSource( resources::shader_lang_t::glsl, diff --git a/libopenage/renderer/stages/terrain/terrain_renderer.h b/libopenage/renderer/stages/terrain/render_stage.h similarity index 63% rename from libopenage/renderer/stages/terrain/terrain_renderer.h rename to libopenage/renderer/stages/terrain/render_stage.h index 286567c34a..6066dc90aa 100644 --- a/libopenage/renderer/stages/terrain/terrain_renderer.h +++ b/libopenage/renderer/stages/terrain/render_stage.h @@ -1,11 +1,14 @@ -// Copyright 2022-2023 the openage authors. See copying.md for legal info. +// Copyright 2022-2024 the openage authors. See copying.md for legal info. #pragma once #include #include +#include "coord/scene.h" #include "util/path.h" +#include "util/vector.h" + namespace openage { @@ -29,22 +32,32 @@ class AssetManager; } namespace terrain { -class TerrainRenderEntity; +class RenderEntity; class TerrainRenderMesh; class TerrainRenderModel; /** * Manage and render terrain geometry and graphics. */ -class TerrainRenderer { +class TerrainRenderStage { public: - TerrainRenderer(const std::shared_ptr &window, - const std::shared_ptr &renderer, - const std::shared_ptr &camera, - const util::Path &shaderdir, - const std::shared_ptr &asset_manager, - const std::shared_ptr &clock); - ~TerrainRenderer() = default; + /** + * Create a new render stage for the terrain. + * + * @param window openage window targeted for rendering. + * @param renderer openage low-level renderer. + * @param camera Camera used for the rendered scene. + * @param shaderdir Directory containing the shader source files. + * @param asset_manager Asset manager for loading resources. + * @param clock Simulation clock for timing animations. + */ + TerrainRenderStage(const std::shared_ptr &window, + const std::shared_ptr &renderer, + const std::shared_ptr &camera, + const util::Path &shaderdir, + const std::shared_ptr &asset_manager, + const std::shared_ptr &clock); + ~TerrainRenderStage() = default; /** * Get the render pass of the terrain renderer. @@ -54,11 +67,15 @@ class TerrainRenderer { std::shared_ptr get_render_pass(); /** - * Set the current render entity of the terrain renderer. + * Add a new render entity to the terrain renderer. + * + * This creates a new terrain chunk and add it to the model. * * @param render_entity New render entity. */ - void set_render_entity(const std::shared_ptr entity); + void add_render_entity(const std::shared_ptr entity, + const util::Vector2s chunk_size, + const coord::scene2_delta chunk_offset); /** * Update the terrain mesh and texture information. @@ -67,7 +84,7 @@ class TerrainRenderer { /** * Resize the FBO for the terrain rendering. This basically updates the output - * texture size. + * texture size. * * @param width New width of the FBO. * @param height New height of the FBO. @@ -102,7 +119,7 @@ class TerrainRenderer { /** * Engine interface for updating terrain draw information. */ - std::shared_ptr render_entity; + std::shared_ptr render_entity; /** * 3D model of the terrain. diff --git a/libopenage/renderer/stages/terrain/terrain_mesh.h b/libopenage/renderer/stages/terrain/terrain_mesh.h deleted file mode 100644 index a571007204..0000000000 --- a/libopenage/renderer/stages/terrain/terrain_mesh.h +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright 2022-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include - -#include - -#include "curve/discrete.h" -#include "renderer/resources/mesh_data.h" -#include "time/time.h" - - -namespace openage::renderer { -class UniformInput; - -namespace resources { -class AssetManager; -class TerrainInfo; -} // namespace resources - -namespace terrain { - -/** - * Drawable chunk of terrain with a single texture. - */ -class TerrainRenderMesh { -public: - /** - * Create a new terrain render mesh with empty values. - * - * Mesh and texture need to be set before the terrain mesh becomes renderable. - * - * @param asset_manager Asset manager for central accessing and loading textures. - */ - TerrainRenderMesh(const std::shared_ptr &asset_manager); - - /** - * Create a new terrain render mesh. - * - * @param asset_manager Asset manager for central accessing and loading textures. - * @param info Terrain info for the renderable. - * @param mesh Vertex data of the mesh. - */ - TerrainRenderMesh(const std::shared_ptr &asset_manager, - const curve::Discrete &terrain_path, - renderer::resources::MeshData &&mesh); - - ~TerrainRenderMesh() = default; - - /** - * Set the reference to the uniform inputs of the renderable - * associated with this mesh. Relevant uniforms are updated - * when calling \p update(). - * - * @param uniforms Uniform inputs of this mesh's renderable. - */ - void set_uniforms(const std::shared_ptr &uniforms); - - /** - * Get the reference to the uniform inputs of the mesh's renderable. - * - * @return Uniform inputs of the renderable. - */ - const std::shared_ptr &get_uniforms(); - - /** - * Set the vertex mesh for the terrain. - * - * @param mesh Mesh for creating a renderer geometry object. - */ - void set_mesh(renderer::resources::MeshData &&mesh); - - /** - * Get the vertex mesh for the terrain. - * - * @return Mesh for creating a renderer geometry object. - */ - const renderer::resources::MeshData &get_mesh(); - - /** - * Set the terrain info that is drawn onto the mesh. - * - * @param texture Terrain info. - */ - void set_terrain_path(const curve::Discrete &info); - - /** - * Update the uniforms of the renderable associated with this object. - * - * @param time Current simulation time. - */ - void update_uniforms(const time::time_t &time = 0.0); - - /** - * Check whether a new renderable needs to be created for this mesh. - * - * If true, the old renderable should be removed from the render pass. - * The updated uniforms and geometry should be passed to this mesh. - * Afterwards, clear the requirement flag with \p clear_requires_renderable(). - * - * @return true if a new renderable is required, else false. - */ - bool requires_renderable(); - - /** - * Indicate to this mesh that a new renderable has been created. - */ - void clear_requires_renderable(); - - /** - * Get the model transformation matrix for rendering. - * - * @return Model matrix. - */ - Eigen::Matrix4f get_model_matrix(); - - /** - * Check whether the mesh or texture were changed. - * - * @return true if changes were made, else false. - */ - bool is_changed(); - - /** - * Clear the update flag by setting it to false. - */ - void clear_changed_flag(); - -private: - /** - * Stores whether a new renderable for this mesh needs to be created - * for the render pass. - */ - bool require_renderable; - - /** - * Stores whether the the mesh or the texture are updated. - */ - bool changed; - - /** - * Asset manager for central accessing and loading textures. - */ - std::shared_ptr asset_manager; - - /** - * Terrain information for the renderables. - */ - curve::Discrete> terrain_info; - - /** - * Shader uniforms for the renderable in the terrain render pass. - */ - std::shared_ptr uniforms; - - /** - * Pre-transformation vertices for the terrain model. - */ - renderer::resources::MeshData mesh; -}; -} // namespace terrain -} // namespace openage::renderer diff --git a/libopenage/renderer/stages/terrain/terrain_model.cpp b/libopenage/renderer/stages/terrain/terrain_model.cpp deleted file mode 100644 index 4b73b556c9..0000000000 --- a/libopenage/renderer/stages/terrain/terrain_model.cpp +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright 2022-2023 the openage authors. See copying.md for legal info. - -#include "terrain_model.h" - -#include -#include -#include -#include - -#include - -#include "coord/scene.h" -#include "renderer/resources/assets/asset_manager.h" -#include "renderer/resources/mesh_data.h" -#include "renderer/stages/terrain/terrain_mesh.h" -#include "renderer/stages/terrain/terrain_render_entity.h" -#include "util/fixed_point.h" -#include "util/vector.h" - - -namespace openage::renderer::terrain { - -TerrainRenderModel::TerrainRenderModel(const std::shared_ptr &asset_manager) : - meshes{}, - camera{nullptr}, - asset_manager{asset_manager}, - render_entity{nullptr} { -} - -void TerrainRenderModel::set_render_entity(const std::shared_ptr &entity) { - this->render_entity = entity; - this->fetch_updates(); -} - -void TerrainRenderModel::set_camera(const std::shared_ptr &camera) { - this->camera = camera; -} - -void TerrainRenderModel::fetch_updates() { - // TODO: Don't create model if render entity is not set - if (not this->render_entity) { - return; - } - - // Check render entity for updates - if (not this->render_entity->is_changed()) { - return; - } - // TODO: Change mesh instead of recreating it - // TODO: Multiple meshes - auto new_mesh = this->create_mesh(); - this->meshes.clear(); - this->meshes.push_back(new_mesh); - - // Indicate to the render entity that its updates have been processed. - this->render_entity->clear_changed_flag(); -} - -void TerrainRenderModel::update_uniforms(const time::time_t &time) { - for (auto &mesh : this->meshes) { - mesh->update_uniforms(time); - } -} - -const std::vector> &TerrainRenderModel::get_meshes() const { - return this->meshes; -} - -std::shared_ptr TerrainRenderModel::create_mesh() { - // Update mesh - auto size = this->render_entity->get_size(); - auto src_verts = this->render_entity->get_vertices(); - - // dst_verts places vertices in order - // (left to right, bottom to top) - std::vector dst_verts{}; - dst_verts.reserve(src_verts.size() * 5); - for (auto v : src_verts) { - // Transform to scene coords - auto v_vec = v.to_world_space(); - dst_verts.push_back(v_vec[0]); - dst_verts.push_back(v_vec[1]); - dst_verts.push_back(v_vec[2]); - // TODO: Texture scaling - dst_verts.push_back((v.ne / 10).to_float()); - dst_verts.push_back((v.se / 10).to_float()); - } - - // split the grid into triangles using an index array - std::vector idxs; - idxs.reserve((size[0] - 1) * (size[1] - 1) * 6); - // iterate over all tiles in the grid by columns, i.e. starting - // from the left corner to the bottom corner if you imagine it from - // the camera's point of view - for (size_t i = 0; i < size[0] - 1; ++i) { - for (size_t j = 0; j < size[1] - 1; ++j) { - // since we are working on tiles, we split each tile into two triangles - // with counter-clockwise vertex order - idxs.push_back(j + i * size[1]); // bottom left - idxs.push_back(j + 1 + i * size[1]); // bottom right - idxs.push_back(j + size[1] + i * size[1]); // top left - idxs.push_back(j + 1 + i * size[1]); // bottom right - idxs.push_back(j + size[1] + 1 + i * size[1]); // top right - idxs.push_back(j + size[1] + i * size[1]); // top left - } - } - - resources::VertexInputInfo info{ - {resources::vertex_input_t::V3F32, resources::vertex_input_t::V2F32}, - resources::vertex_layout_t::AOS, - resources::vertex_primitive_t::TRIANGLES, - resources::index_t::U16}; - - auto const vert_data_size = dst_verts.size() * sizeof(float); - std::vector vert_data(vert_data_size); - std::memcpy(vert_data.data(), reinterpret_cast(dst_verts.data()), vert_data_size); - - auto const idx_data_size = idxs.size() * sizeof(uint16_t); - std::vector idx_data(idx_data_size); - std::memcpy(idx_data.data(), reinterpret_cast(idxs.data()), idx_data_size); - - resources::MeshData meshdata{std::move(vert_data), std::move(idx_data), info}; - - // Update textures - auto tex_manager = this->asset_manager->get_texture_manager(); - - // TODO: Support multiple textures per terrain - - auto terrain_mesh = std::make_shared( - this->asset_manager, - this->render_entity->get_terrain_path(), - std::move(meshdata)); - - return terrain_mesh; -} - -} // namespace openage::renderer::terrain diff --git a/libopenage/renderer/stages/terrain/terrain_model.h b/libopenage/renderer/stages/terrain/terrain_model.h deleted file mode 100644 index 4a1ec15aed..0000000000 --- a/libopenage/renderer/stages/terrain/terrain_model.h +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2022-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include - -#include "time/time.h" - - -namespace openage::renderer { - -namespace camera { -class Camera; -} - -namespace resources { -class AssetManager; -} - -namespace terrain { -class TerrainRenderEntity; -class TerrainRenderMesh; - -/** - * 3D model of the whole terrain. Combines the individual meshes - * into one single structure. - */ -class TerrainRenderModel { -public: - TerrainRenderModel(const std::shared_ptr &asset_manager); - ~TerrainRenderModel() = default; - - /** - * Set the terrain render entity for vertex updates of this mesh. - * - * @param entity New terrain render entity. - */ - void set_render_entity(const std::shared_ptr &entity); - - /** - * Set the current camera of the scene. - * - * @param camera Camera object viewing the scene. - */ - void set_camera(const std::shared_ptr &camera); - - /** - * Fetch updates from the render entity. - */ - void fetch_updates(); - - /** - * Update the uniforms of the renderable associated with this object. - * - * @param time Current simulation time. - */ - void update_uniforms(const time::time_t &time = 0.0); - - /** - * Get the meshes composing the terrain. - * - * @return Vector of terrain meshes. - */ - const std::vector> &get_meshes() const; - -private: - /** - * Create a terrain mesh from the data provided by the render entity. - * - * @return New terrain mesh. - */ - std::shared_ptr create_mesh(); - - /** - * Meshes composing the terrain. Each mesh represents a drawable vertex surface - * and a texture. - */ - std::vector> meshes; - - /** - * Camera for view and projection uniforms. - */ - std::shared_ptr camera; - - /** - * Asset manager for central accessing and loading textures. - */ - std::shared_ptr asset_manager; - - /** - * Source for ingame terrain coordinates. These coordinates are translated into - * our render vertex mesh when \p update() is called. - */ - std::shared_ptr render_entity; -}; - -} // namespace terrain -} // namespace openage::renderer diff --git a/libopenage/renderer/stages/terrain/terrain_render_entity.cpp b/libopenage/renderer/stages/terrain/terrain_render_entity.cpp deleted file mode 100644 index a142461cb0..0000000000 --- a/libopenage/renderer/stages/terrain/terrain_render_entity.cpp +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2022-2023 the openage authors. See copying.md for legal info. - -#include "terrain_render_entity.h" - -#include -#include -#include - - -namespace openage::renderer::terrain { - -TerrainRenderEntity::TerrainRenderEntity() : - changed{false}, - size{0, 0}, - vertices{}, - terrain_path{nullptr, 0} { -} - -void TerrainRenderEntity::update(util::Vector2s size, - std::vector height_map, - const std::string terrain_path, - const time::time_t time) { - std::unique_lock lock{this->mutex}; - - // increase by 1 in every dimension because height_map - // size is number of tiles, but we want number of vertices - util::Vector2i tile_size{size[0], size[1]}; - this->size = util::Vector2s{size[0] + 1, size[1] + 1}; - - // transfer mesh - auto vert_count = this->size[0] * this->size[1]; - this->vertices.clear(); - this->vertices.reserve(vert_count); - for (int i = 0; i < (int)this->size[0]; ++i) { - for (int j = 0; j < (int)this->size[1]; ++j) { - // for each vertex, compare the surrounding tiles - std::vector surround{}; - if (j - 1 >= 0 and i - 1 >= 0) { - surround.push_back(height_map[(i - 1) * size[1] + j - 1]); - } - if (j < tile_size[1] and i - 1 >= 0) { - surround.push_back(height_map[(i - 1) * size[1] + j]); - } - if (j < tile_size[1] and i < tile_size[0]) { - surround.push_back(height_map[i * size[1] + j]); - } - if (j - 1 >= 0 and i < tile_size[0]) { - surround.push_back(height_map[i * size[1] + j - 1]); - } - // select the height of the highest surrounding tile - auto max_height = *std::max_element(surround.begin(), surround.end()); - coord::scene3 v{ - static_cast(i), - static_cast(j), - max_height, - }; - this->vertices.push_back(v); - } - } - - // set texture path - this->terrain_path.set_last(time, terrain_path); - - this->changed = true; -} - -const std::vector &TerrainRenderEntity::get_vertices() { - std::shared_lock lock{this->mutex}; - - return this->vertices; -} - -const curve::Discrete &TerrainRenderEntity::get_terrain_path() { - std::shared_lock lock{this->mutex}; - - return this->terrain_path; -} - -const util::Vector2s &TerrainRenderEntity::get_size() { - std::shared_lock lock{this->mutex}; - - return this->size; -} - -bool TerrainRenderEntity::is_changed() { - std::shared_lock lock{this->mutex}; - - return this->changed; -} - -void TerrainRenderEntity::clear_changed_flag() { - std::unique_lock lock{this->mutex}; - - this->changed = false; -} - -} // namespace openage::renderer::terrain diff --git a/libopenage/renderer/stages/terrain/terrain_render_entity.h b/libopenage/renderer/stages/terrain/terrain_render_entity.h deleted file mode 100644 index 2d074e2590..0000000000 --- a/libopenage/renderer/stages/terrain/terrain_render_entity.h +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2022-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include -#include -#include - -#include "coord/scene.h" -#include "curve/discrete.h" -#include "time/time.h" -#include "util/vector.h" - - -namespace openage::renderer { - -namespace terrain { - -class TerrainRenderEntity { -public: - TerrainRenderEntity(); - ~TerrainRenderEntity() = default; - - /** - * Update the render entity with information from the - * gamestate. - * - * @param size Size of the terrain in tiles (width x length) - * @param height_map Height of terrain tiles. - * @param terrain_path Path to the terrain definition. - * @param time Simulation time of the update. - */ - void update(util::Vector2s size, - std::vector height_map, - const std::string terrain_path, - const time::time_t time = 0.0); - - /** - * Get the vertices of the terrain. - * - * @return Vector of vertex coordinates. - */ - const std::vector &get_vertices(); - - /** - * Get the texture mapping for the terrain. - * - * TODO: Return the actual mapping. - * - * @return Texture mapping of textures to vertex area. - */ - const curve::Discrete &get_terrain_path(); - - /** - * Get the number of vertices on each side of the terrain. - * - * @return Vector with width as first element and height as second element. - */ - const util::Vector2s &get_size(); - - /** - * Check whether the render entity has received new updates from the - * gamestate. - * - * @return true if updates have been received, else false. - */ - bool is_changed(); - - /** - * Clear the update flag by setting it to false. - */ - void clear_changed_flag(); - -private: - /** - * Flag for determining if the render entity has been updated by the - * corresponding gamestate entity. Set to true every time \p update() - * is called. - */ - bool changed; - - /** - * Terrain dimensions (width x height). - */ - util::Vector2s size; - - /** - * Terrain vertices (ingame coordinates). - */ - std::vector vertices; - - /** - * Path to the terrain definition file. - */ - curve::Discrete terrain_path; - - // std::unordered_map texture_map; // texture -> vertex indices - - /** - * Mutex for protecting threaded access. - */ - std::shared_mutex mutex; -}; -} // namespace terrain -} // namespace openage::renderer diff --git a/libopenage/renderer/stages/world/CMakeLists.txt b/libopenage/renderer/stages/world/CMakeLists.txt index 1bf4758b19..811fd929ed 100644 --- a/libopenage/renderer/stages/world/CMakeLists.txt +++ b/libopenage/renderer/stages/world/CMakeLists.txt @@ -1,5 +1,5 @@ add_sources(libopenage - world_object.cpp - world_render_entity.cpp - world_renderer.cpp + object.cpp + render_entity.cpp + render_stage.cpp ) diff --git a/libopenage/renderer/stages/world/object.cpp b/libopenage/renderer/stages/world/object.cpp new file mode 100644 index 0000000000..2396c94ed2 --- /dev/null +++ b/libopenage/renderer/stages/world/object.cpp @@ -0,0 +1,244 @@ +// Copyright 2022-2024 the openage authors. See copying.md for legal info. + +#include "object.h" + +#include +#include +#include +#include +#include + +#include + +#include "renderer/camera/frustum_2d.h" +#include "renderer/definitions.h" +#include "renderer/resources/animation/angle_info.h" +#include "renderer/resources/animation/animation_info.h" +#include "renderer/resources/animation/frame_info.h" +#include "renderer/resources/animation/layer_info.h" +#include "renderer/resources/assets/asset_manager.h" +#include "renderer/resources/assets/texture_manager.h" +#include "renderer/resources/frame_timing.h" +#include "renderer/resources/mesh_data.h" +#include "renderer/resources/texture_info.h" +#include "renderer/resources/texture_subinfo.h" +#include "renderer/stages/world/render_entity.h" +#include "renderer/uniform_input.h" +#include "util/fixed_point.h" +#include "util/vector.h" + + +namespace openage::renderer::world { + +WorldObject::WorldObject(const std::shared_ptr &asset_manager) : + require_renderable{true}, + changed{false}, + asset_manager{asset_manager}, + render_entity{nullptr}, + ref_id{0}, + position{nullptr, 0, "", nullptr, SCENE_ORIGIN}, + angle{nullptr, 0, "", nullptr, 0}, + animation_info{nullptr, 0}, + layer_uniforms{}, + last_update{0.0} { +} + +void WorldObject::set_render_entity(const std::shared_ptr &entity) { + this->render_entity = entity; + this->fetch_updates(); +} + +void WorldObject::fetch_updates(const time::time_t &time) { + // TODO: Calling this once per frame is very expensive + auto layer_count = this->get_required_layer_count(time); + if (this->layer_uniforms.size() != layer_count) { + // The number of layers changed, so we need to update the renderables + this->require_renderable = true; + } + + if (not this->render_entity->is_changed()) { + // exit early because there is nothing to update + return; + } + + // Get data from render entity + this->ref_id = this->render_entity->get_id(); + + // Thread-safe access to curves needs a lock on the render entity's mutex + auto read_lock = this->render_entity->get_read_lock(); + this->position.sync(this->render_entity->get_position()); + this->animation_info.sync(this->render_entity->get_animation_path(), + std::function(const std::string &)>( + [&](const std::string &path) { + if (path.empty()) { + auto placeholder = this->asset_manager->get_placeholder_animation(); + if (placeholder) { + return (*placeholder).second; + } + return std::shared_ptr{nullptr}; + } + return this->asset_manager->request_animation(path); + }), + this->last_update); + this->angle.sync(this->render_entity->get_angle(), this->last_update); + + // Unlock mutex of the render entity + read_lock.unlock(); + + // Set self to changed so that world renderer can update the renderable + this->changed = true; + this->render_entity->clear_changed_flag(); + this->last_update = time; +} + +void WorldObject::update_uniforms(const time::time_t &time) { + // TODO: Only update uniforms that changed since last update + if (this->layer_uniforms.empty()) [[unlikely]] { + return; + } + + // Object world position + auto current_pos = this->position.get(time); + + // Direction angle the object is facing towards currently + auto angle_degrees = this->angle.get(time).to_float(); + + // Animation information + auto [last_update, animation_info] = this->animation_info.frame(time); + + for (size_t layer_idx = 0; layer_idx < this->layer_uniforms.size(); ++layer_idx) { + auto &layer_unifs = this->layer_uniforms.at(layer_idx); + layer_unifs->update(this->obj_world_position, current_pos.to_world_space()); + + // Frame subtexture + auto &layer = animation_info->get_layer(layer_idx); + auto &angle = layer.get_direction_angle(angle_degrees); + + // Flip subtexture horizontally if angle is mirrored + if (angle->is_mirrored()) { + layer_unifs->update(this->flip_x, true); + } + else { + layer_unifs->update(this->flip_x, false); + } + + // Current frame index considering current time + size_t frame_idx; + switch (layer.get_display_mode()) { + case renderer::resources::display_mode::ONCE: + case renderer::resources::display_mode::LOOP: { + // ONCE and LOOP are animated based on time + auto &timing = layer.get_frame_timing(); + frame_idx = timing->get_frame(time, last_update); + } break; + case renderer::resources::display_mode::OFF: + default: + // OFF only shows the first frame + frame_idx = 0; + break; + } + + // Index of texture and subtexture where the frame's pixels are located + auto &frame_info = angle->get_frame(frame_idx); + auto tex_idx = frame_info->get_texture_idx(); + auto subtex_idx = frame_info->get_subtexture_idx(); + + auto &tex_info = animation_info->get_texture(tex_idx); + auto &tex_manager = this->asset_manager->get_texture_manager(); + auto &texture = tex_manager->request(tex_info->get_image_path().value()); + layer_unifs->update(this->tex, texture); + + // Subtexture coordinates.inside texture + auto coords = tex_info->get_subtex_info(subtex_idx).get_subtex_coords(); + layer_unifs->update(this->tile_params, coords); + + // Animation scale factor + // Scales the subtex up or down in the shader + auto scale = animation_info->get_scalefactor(); + layer_unifs->update(this->scale, scale); + + // Subtexture size in pixels + auto subtex_size = tex_info->get_subtex_info(subtex_idx).get_size(); + Eigen::Vector2f subtex_size_vec{ + static_cast(subtex_size[0]), + static_cast(subtex_size[1])}; + layer_unifs->update(this->subtex_size, subtex_size_vec); + + // Anchor point offset (in pixels) + // moves the subtex in the shader so that the anchor point is at the object's position + auto anchor = tex_info->get_subtex_info(subtex_idx).get_anchor_params(); + Eigen::Vector2f anchor_offset{ + static_cast(anchor[0]), + static_cast(anchor[1])}; + layer_unifs->update(this->anchor_offset, anchor_offset); + } +} + +uint32_t WorldObject::get_id() { + return this->ref_id; +} + +const renderer::resources::MeshData WorldObject::get_mesh() { + return resources::MeshData::make_quad(); +} + +const Eigen::Matrix4f WorldObject::get_model_matrix() { + return Eigen::Matrix4f::Identity(); +} + +bool WorldObject::requires_renderable() const { + return this->require_renderable; +} + +void WorldObject::clear_requires_renderable() { + this->require_renderable = false; +} + +size_t WorldObject::get_required_layer_count(const time::time_t &time) const { + auto animation_info = this->animation_info.get(time); + if (not animation_info) { + return 0; + } + + return animation_info->get_layer_count(); +} + +std::vector WorldObject::get_layer_positions(const time::time_t &time) const { + auto animation_info = this->animation_info.get(time); + if (not animation_info) { + return {}; + } + + std::vector positions; + for (size_t i = 0; i < animation_info->get_layer_count(); ++i) { + auto layer = animation_info->get_layer(i); + positions.push_back(layer.get_position()); + } + + return positions; +} + +bool WorldObject::is_changed() { + return this->changed; +} + +void WorldObject::clear_changed_flag() { + this->changed = false; +} + +void WorldObject::set_uniforms(std::vector> &&uniforms) { + this->layer_uniforms = std::move(uniforms); +} + +bool WorldObject::is_visible(const camera::Frustum2d &frustum, + const time::time_t &time) { + static const Eigen::Matrix4f model_matrix = this->get_model_matrix(); + Eigen::Vector3f current_pos = this->position.get(time).to_world_space(); + auto animation_info = this->animation_info.get(time); + return frustum.in_frustum(current_pos, + model_matrix, + animation_info->get_scalefactor(), + animation_info->get_max_bounds()); +} + +} // namespace openage::renderer::world diff --git a/libopenage/renderer/stages/world/object.h b/libopenage/renderer/stages/world/object.h new file mode 100644 index 0000000000..c5579f62a0 --- /dev/null +++ b/libopenage/renderer/stages/world/object.h @@ -0,0 +1,215 @@ +// Copyright 2022-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include +#include + +#include "coord/scene.h" +#include "curve/continuous.h" +#include "curve/discrete.h" +#include "curve/segmented.h" +#include "renderer/resources/mesh_data.h" +#include "renderer/types.h" +#include "time/time.h" + + +namespace openage::renderer { +class UniformInput; + +namespace camera { +class Frustum2d; +} + +namespace resources { +class AssetManager; +class Animation2dInfo; +} // namespace resources + +namespace world { +class RenderEntity; + +/** + * Stores the state of a renderable object in the World render stage. + */ +class WorldObject { +public: + /** + * Create a new object for the World render stage. + * + * @param asset_manager Asset manager for loading resources. + */ + WorldObject(const std::shared_ptr &asset_manager); + ~WorldObject() = default; + + /** + * Set the world render entity. + * + * @param entity New world render entity. + */ + void set_render_entity(const std::shared_ptr &entity); + + /** + * Fetch updates from the render entity. + * + * @param time Current simulation time. + */ + void fetch_updates(const time::time_t &time = 0.0); + + /** + * Update the uniforms of the renderable associated with this object. + * + * @param time Current simulation time. + */ + void update_uniforms(const time::time_t &time = 0.0); + + /** + * Get the ID of the corresponding game entity. + * + * @return Game entity ID. + */ + uint32_t get_id(); + + /** + * Get the quad for creating the geometry. + * + * Since the object is a bunch of sprite layers, the mesh is always a quad. + * + * @return Mesh for creating a renderer geometry object. + */ + static const renderer::resources::MeshData get_mesh(); + + /** + * Get the model matrix for the uniform input of a layer. + * + * @return Model matrix. + */ + static const Eigen::Matrix4f get_model_matrix(); + + /** + * Check whether a new renderable needs to be created for this mesh. + * + * If true, the old renderable should be removed from the render pass. + * The updated uniforms and geometry should be passed to this mesh. + * Afterwards, clear the requirement flag with \p clear_requires_renderable(). + * + * @return true if a new renderable is required, else false. + */ + bool requires_renderable() const; + + /** + * Indicate to this mesh that a new renderable has been created. + */ + void clear_requires_renderable(); + + /** + * Get the number of layers required by this object. + * + * @return Number of layers. + */ + size_t get_required_layer_count(const time::time_t &time) const; + + std::vector get_layer_positions(const time::time_t &time) const; + + /** + * Check whether the object was changed by \p update(). + * + * @return true if changes were made, else false. + */ + bool is_changed(); + + /** + * Clear the update flag by setting it to false. + */ + void clear_changed_flag(); + + /** + * Set the uniform inputs for the layers of this object. + * Layer uniforms are updated on every update call. + * + * @param uniforms Uniform inputs of this object's layers. + */ + void set_uniforms(std::vector> &&uniforms); + + /** + * Check whether the object is visible in the camera view. + * + * @param frustum Camera frustum for culling. + * @param time Current simulation time. + * + * @return true if the object is visible, else false. + */ + bool is_visible(const camera::Frustum2d &frustum, + const time::time_t &time); + + /** + * Shader uniform IDs for setting uniform values. + */ + inline static uniform_id_t obj_world_position; + inline static uniform_id_t flip_x; + inline static uniform_id_t flip_y; + inline static uniform_id_t tex; + inline static uniform_id_t tile_params; + inline static uniform_id_t scale; + inline static uniform_id_t subtex_size; + inline static uniform_id_t anchor_offset; + +private: + /** + * Stores whether a new renderable for this object needs to be created + * for the render pass. + */ + bool require_renderable; + + /** + * Stores whether the \p update() call changed the object. + */ + bool changed; + + /** + * Asset manager for central accessing and loading asset resources. + */ + std::shared_ptr asset_manager; + + /** + * Entity that gets updates from the gamestate, e.g. the position and + * requested animation data. + */ + std::shared_ptr render_entity; + + /** + * Reference ID for passing interaction with the graphic (e.g. mouse clicks) back to + * the engine. + */ + uint32_t ref_id; + + /** + * Position of the object. + */ + curve::Continuous position; + + /** + * Angle of the object. + */ + curve::Segmented angle; + + /** + * Animation information for the layers. + */ + curve::Discrete> animation_info; + + /** + * Shader uniforms for the layers of the object. Each layer corresponds to a + * renderable in the render pass. + */ + std::vector> layer_uniforms; + + /** + * Time of the last update call. + */ + time::time_t last_update; +}; +} // namespace world +} // namespace openage::renderer diff --git a/libopenage/renderer/stages/world/render_entity.cpp b/libopenage/renderer/stages/world/render_entity.cpp new file mode 100644 index 0000000000..7a8e145741 --- /dev/null +++ b/libopenage/renderer/stages/world/render_entity.cpp @@ -0,0 +1,80 @@ +// Copyright 2022-2024 the openage authors. See copying.md for legal info. + +#include "render_entity.h" + +#include +#include + +#include "renderer/definitions.h" + + +namespace openage::renderer::world { + +RenderEntity::RenderEntity() : + renderer::RenderEntity{}, + ref_id{0}, + position{nullptr, 0, "", nullptr, SCENE_ORIGIN}, + angle{nullptr, 0, "", nullptr, 0}, + animation_path{nullptr, 0} { +} + +void RenderEntity::update(const uint32_t ref_id, + const curve::Continuous &position, + const curve::Segmented &angle, + const std::string animation_path, + const time::time_t time) { + std::unique_lock lock{this->mutex}; + + this->ref_id = ref_id; + std::function to_scene3 = [](const coord::phys3 &pos) { + return pos.to_scene3(); + }; + this->position.sync(position, + std::function([](const coord::phys3 &pos) { + return pos.to_scene3(); + }), + this->last_update); + this->angle.sync(angle, this->last_update); + this->animation_path.set_last(time, animation_path); + this->changed = true; + this->last_update = time; +} + +void RenderEntity::update(const uint32_t ref_id, + const coord::phys3 position, + const std::string animation_path, + const time::time_t time) { + std::unique_lock lock{this->mutex}; + + this->ref_id = ref_id; + this->position.set_last(time, position.to_scene3()); + this->animation_path.set_last(time, animation_path); + this->changed = true; + this->last_update = time; +} + +uint32_t RenderEntity::get_id() { + std::shared_lock lock{this->mutex}; + + return this->ref_id; +} + +const curve::Continuous &RenderEntity::get_position() { + std::shared_lock lock{this->mutex}; + + return this->position; +} + +const curve::Segmented &RenderEntity::get_angle() { + std::shared_lock lock{this->mutex}; + + return this->angle; +} + +const curve::Discrete &RenderEntity::get_animation_path() { + std::shared_lock lock{this->mutex}; + + return this->animation_path; +} + +} // namespace openage::renderer::world diff --git a/libopenage/renderer/stages/world/world_render_entity.h b/libopenage/renderer/stages/world/render_entity.h similarity index 60% rename from libopenage/renderer/stages/world/world_render_entity.h rename to libopenage/renderer/stages/world/render_entity.h index ae656da20d..ed9011b8a4 100644 --- a/libopenage/renderer/stages/world/world_render_entity.h +++ b/libopenage/renderer/stages/world/render_entity.h @@ -1,35 +1,36 @@ -// Copyright 2022-2023 the openage authors. See copying.md for legal info. +// Copyright 2022-2024 the openage authors. See copying.md for legal info. #pragma once #include -#include -#include #include -#include - #include "coord/phys.h" #include "coord/scene.h" #include "curve/continuous.h" #include "curve/discrete.h" #include "curve/segmented.h" -#include "time/time.h" +#include "renderer/stages/render_entity.h" namespace openage::renderer::world { -class WorldRenderEntity { +/** + * Render entity for pushing updates to the World renderer. + */ +class RenderEntity final : public renderer::RenderEntity { public: - WorldRenderEntity(); - ~WorldRenderEntity() = default; + RenderEntity(); + ~RenderEntity() = default; /** * Update the render entity with information from the gamestate. * + * Updating the render entity with this method is thread-safe. + * * @param ref_id Game entity ID. * @param position Position of the game entity inside the game world. - * @param angle Angle of the game entity inside the game world. + * @param angle Angle of the game entity inside the game world. * @param animation_path Path to the animation definition. * @param time Simulation time of the update. */ @@ -40,10 +41,12 @@ class WorldRenderEntity { const time::time_t time = 0.0); /** - * Thus function is for DEBUGGING and should not be used. - * + * This function is for DEBUGGING and should not be used. + * * Update the render entity with information from the gamestate. * + * Updating the render entity with this method is thread-safe. + * * @param ref_id Game entity ID. * @param position Position of the game entity inside the game world. * @param animation_path Path to the animation definition. @@ -57,6 +60,8 @@ class WorldRenderEntity { /** * Get the ID of the corresponding game entity. * + * Accessing the game entity ID is thread-safe. + * * @return Game entity ID. */ uint32_t get_id(); @@ -64,52 +69,34 @@ class WorldRenderEntity { /** * Get the position of the entity inside the game world. * + * Accessing the position curve REQUIRES a read lock on the render entity + * (using \p get_read_lock()) to ensure thread safety. + * * @return Position curve of the entity. */ const curve::Continuous &get_position(); /** - * Get the angle of the entity inside the game world. - * - * @return Angle curve of the entity. - */ - const curve::Segmented &get_angle(); - - /** - * Get the animation definition path. - * - * @return Path to the animation definition file. - */ - const curve::Discrete &get_animation_path(); - - /** - * Get the time of the last update. + * Get the angle of the entity inside the game world. * - * @return Time of last update. - */ - time::time_t get_update_time(); - - /** - * Check whether the render entity has received new updates from the - * gamestate. + * Accessing the angle curve REQUIRES a read lock on the render entity + * (using \p get_read_lock()) to ensure thread safety. * - * @return true if updates have been received, else false. + * @return Angle curve of the entity. */ - bool is_changed(); + const curve::Segmented &get_angle(); /** - * Clear the update flag by setting it to false. + * Get the animation definition path. + * + * Accessing the animation path curve requires a read lock on the render entity + * (using \p get_read_lock()) to ensure thread safety. + * + * @return Path to the animation definition file. */ - void clear_changed_flag(); + const curve::Discrete &get_animation_path(); private: - /** - * Flag for determining if the render entity has been updated by the - * corresponding gamestate entity. Set to true every time \p update() - * is called. - */ - bool changed; - /** * ID of the game entity in the gamestate. */ @@ -121,23 +108,13 @@ class WorldRenderEntity { curve::Continuous position; /** - * Angle of the entity inside the game world. - */ + * Angle of the entity inside the game world. + */ curve::Segmented angle; /** * Path to the animation definition file. */ curve::Discrete animation_path; - - /** - * Time of the last update call. - */ - time::time_t last_update; - - /** - * Mutex for protecting threaded access. - */ - std::shared_mutex mutex; }; } // namespace openage::renderer::world diff --git a/libopenage/renderer/stages/world/world_renderer.cpp b/libopenage/renderer/stages/world/render_stage.cpp similarity index 61% rename from libopenage/renderer/stages/world/world_renderer.cpp rename to libopenage/renderer/stages/world/render_stage.cpp index 69bc67b54c..eec13b602b 100644 --- a/libopenage/renderer/stages/world/world_renderer.cpp +++ b/libopenage/renderer/stages/world/render_stage.cpp @@ -1,14 +1,17 @@ -// Copyright 2022-2023 the openage authors. See copying.md for legal info. +// Copyright 2022-2025 the openage authors. See copying.md for legal info. -#include "world_renderer.h" +#include "render_stage.h" #include "renderer/camera/camera.h" +#include "renderer/camera/frustum_3d.h" #include "renderer/opengl/context.h" +#include "renderer/render_pass.h" +#include "renderer/render_target.h" #include "renderer/resources/assets/asset_manager.h" #include "renderer/resources/shader_source.h" #include "renderer/resources/texture_info.h" #include "renderer/shader_program.h" -#include "renderer/stages/world/world_object.h" +#include "renderer/stages/world/object.h" #include "renderer/texture.h" #include "renderer/window.h" #include "time/clock.h" @@ -16,12 +19,14 @@ namespace openage::renderer::world { -WorldRenderer::WorldRenderer(const std::shared_ptr &window, - const std::shared_ptr &renderer, - const std::shared_ptr &camera, - const util::Path &shaderdir, - const std::shared_ptr &asset_manager, - const std::shared_ptr clock) : +bool WorldRenderStage::ENABLE_FRUSTUM_CULLING = false; + +WorldRenderStage::WorldRenderStage(const std::shared_ptr &window, + const std::shared_ptr &renderer, + const std::shared_ptr &camera, + const util::Path &shaderdir, + const std::shared_ptr &asset_manager, + const std::shared_ptr clock) : renderer{renderer}, camera{camera}, asset_manager{asset_manager}, @@ -41,58 +46,69 @@ WorldRenderer::WorldRenderer(const std::shared_ptr &window, log::log(INFO << "Created render stage 'World'"); } -std::shared_ptr WorldRenderer::get_render_pass() { +std::shared_ptr WorldRenderStage::get_render_pass() { return this->render_pass; } -void WorldRenderer::add_render_entity(const std::shared_ptr entity) { +void WorldRenderStage::add_render_entity(const std::shared_ptr entity) { std::unique_lock lock{this->mutex}; auto world_object = std::make_shared(this->asset_manager); world_object->set_render_entity(entity); - world_object->set_camera(this->camera); this->render_objects.push_back(world_object); } -void WorldRenderer::update() { +void WorldRenderStage::update() { std::unique_lock lock{this->mutex}; auto current_time = this->clock->get_real_time(); + auto &camera_frustum = this->camera->get_frustum_2d(); for (auto &obj : this->render_objects) { obj->fetch_updates(current_time); + + if (WorldRenderStage::ENABLE_FRUSTUM_CULLING + and not obj->is_visible(camera_frustum, current_time)) { + continue; + } + if (obj->is_changed()) { if (obj->requires_renderable()) { - Eigen::Matrix4f model_m = Eigen::Matrix4f::Identity(); - - // Set uniforms that don't change or are not changed often - auto transform_unifs = this->display_shader->new_uniform_input( - "model", - model_m, - "flip_x", - false, - "flip_y", - false, - "u_id", - obj->get_id()); - - Renderable display_obj{ - transform_unifs, - this->default_geometry, - true, - true, - }; - - this->render_pass->add_renderables(display_obj); + auto layer_positions = obj->get_layer_positions(current_time); + static const Eigen::Matrix4f model_matrix = obj->get_model_matrix(); + + std::vector> transform_unifs; + for (auto layer_pos : layer_positions) { + // Set uniforms that don't change or are not changed often + auto layer_unifs = this->display_shader->new_uniform_input( + "model", + model_matrix, + "flip_x", + false, + "flip_y", + false, + "u_id", + obj->get_id()); + + Renderable display_obj{ + layer_unifs, + this->default_geometry, + true, + true, + }; + this->render_pass->add_renderables(std::move(display_obj), layer_pos); + transform_unifs.push_back(layer_unifs); + } + obj->clear_requires_renderable(); // update remaining uniforms for the object - obj->set_uniforms(transform_unifs); + obj->set_uniforms(std::move(transform_unifs)); } } obj->update_uniforms(current_time); } } -void WorldRenderer::resize(size_t width, size_t height) { +void WorldRenderStage::resize(size_t width, size_t height) { this->output_texture = renderer->add_texture(resources::Texture2dInfo(width, height, resources::pixel_format::rgba8)); this->depth_texture = renderer->add_texture(resources::Texture2dInfo(width, height, resources::pixel_format::depth24)); this->id_texture = renderer->add_texture(resources::Texture2dInfo(width, height, resources::pixel_format::r32ui)); @@ -101,9 +117,9 @@ void WorldRenderer::resize(size_t width, size_t height) { this->render_pass->set_target(fbo); } -void WorldRenderer::initialize_render_pass(size_t width, - size_t height, - const util::Path &shaderdir) { +void WorldRenderStage::initialize_render_pass(size_t width, + size_t height, + const util::Path &shaderdir) { auto vert_shader_file = (shaderdir / "world2d.vert.glsl").open(); auto vert_shader_src = renderer::resources::ShaderSource( resources::shader_lang_t::glsl, @@ -129,13 +145,14 @@ void WorldRenderer::initialize_render_pass(size_t width, this->render_pass = this->renderer->add_render_pass({}, fbo); } -void WorldRenderer::init_uniform_ids() { +void WorldRenderStage::init_uniform_ids() { WorldObject::obj_world_position = this->display_shader->get_uniform_id("obj_world_position"); WorldObject::flip_x = this->display_shader->get_uniform_id("flip_x"); WorldObject::flip_y = this->display_shader->get_uniform_id("flip_y"); WorldObject::tex = this->display_shader->get_uniform_id("tex"); WorldObject::tile_params = this->display_shader->get_uniform_id("tile_params"); WorldObject::scale = this->display_shader->get_uniform_id("scale"); + WorldObject::subtex_size = this->display_shader->get_uniform_id("subtex_size"); WorldObject::anchor_offset = this->display_shader->get_uniform_id("anchor_offset"); } diff --git a/libopenage/renderer/stages/world/world_renderer.h b/libopenage/renderer/stages/world/render_stage.h similarity index 60% rename from libopenage/renderer/stages/world/world_renderer.h rename to libopenage/renderer/stages/world/render_stage.h index e7e9e0c596..7a0fe02a4c 100644 --- a/libopenage/renderer/stages/world/world_renderer.h +++ b/libopenage/renderer/stages/world/render_stage.h @@ -1,4 +1,4 @@ -// Copyright 2022-2023 the openage authors. See copying.md for legal info. +// Copyright 2022-2025 the openage authors. See copying.md for legal info. #pragma once @@ -31,21 +31,36 @@ class AssetManager; } namespace world { -class WorldRenderEntity; +class RenderEntity; class WorldObject; /** * Renderer for drawing and displaying entities in the game world (units, buildings, etc.) */ -class WorldRenderer { +class WorldRenderStage { public: - WorldRenderer(const std::shared_ptr &window, - const std::shared_ptr &renderer, - const std::shared_ptr &camera, - const util::Path &shaderdir, - const std::shared_ptr &asset_manager, - const std::shared_ptr clock); - ~WorldRenderer() = default; + /** + * Enable or disable frustum culling (default = false). + */ + static bool ENABLE_FRUSTUM_CULLING; + + /** + * Create a new render stage for the game world. + * + * @param window openage window targeted for rendering. + * @param renderer openage low-level renderer. + * @param camera Camera used for the rendered scene. + * @param shaderdir Directory containing the shader source files. + * @param asset_manager Asset manager for loading resources. + * @param clock Simulation clock for timing animations. + */ + WorldRenderStage(const std::shared_ptr &window, + const std::shared_ptr &renderer, + const std::shared_ptr &camera, + const util::Path &shaderdir, + const std::shared_ptr &asset_manager, + const std::shared_ptr clock); + ~WorldRenderStage() = default; /** * Get the render pass of the world renderer. @@ -59,7 +74,7 @@ class WorldRenderer { * * @param render_entity New render entity. */ - void add_render_entity(const std::shared_ptr entity); + void add_render_entity(const std::shared_ptr entity); /** * Update the render entities and render positions. @@ -68,7 +83,7 @@ class WorldRenderer { /** * Resize the FBO for the world rendering. This basically updates the output - * texture size. + * texture size. * * @param width New width of the FBO. * @param height New height of the FBO. @@ -85,10 +100,15 @@ class WorldRenderer { * @param height Height of the FBO. * @param shaderdir Directory containg the shader source files. */ - void initialize_render_pass(size_t width, - size_t height, - const util::Path &shaderdir); + void initialize_render_pass(size_t width, size_t height, const util::Path &shaderdir); + /** + * Fetch the uniform IDs for the uniforms of the world shader from OpenGL + * and assign them to the WorldObject class. + * + * This method must be called after the shader program has been created but + * before any uniforms are set. + */ void init_uniform_ids(); /** @@ -127,12 +147,12 @@ class WorldRenderer { std::shared_ptr clock; /** - * Default geometry for every world object. - * - * Since all world objects are sprites, their mesh is always quad - * with the same vertex info. Reusing the geometry allows us to - * use the same vetrex buffer for every object. - */ + * Default geometry for every world object. + * + * Since all world objects are sprites, their mesh is always quad + * with the same vertex info. Reusing the geometry allows us to + * use the same vetrex buffer for every object. + */ const std::shared_ptr default_geometry; /** diff --git a/libopenage/renderer/stages/world/world_object.cpp b/libopenage/renderer/stages/world/world_object.cpp deleted file mode 100644 index 79960d236c..0000000000 --- a/libopenage/renderer/stages/world/world_object.cpp +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright 2022-2023 the openage authors. See copying.md for legal info. - -#include "world_object.h" - -#include -#include -#include -#include -#include - -#include - -#include "renderer/camera/camera.h" -#include "renderer/definitions.h" -#include "renderer/resources/animation/angle_info.h" -#include "renderer/resources/animation/animation_info.h" -#include "renderer/resources/animation/frame_info.h" -#include "renderer/resources/animation/layer_info.h" -#include "renderer/resources/assets/asset_manager.h" -#include "renderer/resources/assets/texture_manager.h" -#include "renderer/resources/frame_timing.h" -#include "renderer/resources/mesh_data.h" -#include "renderer/resources/texture_info.h" -#include "renderer/resources/texture_subinfo.h" -#include "renderer/stages/world/world_render_entity.h" -#include "renderer/uniform_input.h" -#include "util/fixed_point.h" -#include "util/vector.h" - - -namespace openage::renderer::world { - -WorldObject::WorldObject(const std::shared_ptr &asset_manager) : - require_renderable{true}, - changed{false}, - camera{nullptr}, - asset_manager{asset_manager}, - render_entity{nullptr}, - ref_id{0}, - position{nullptr, 0, "", nullptr, SCENE_ORIGIN}, - angle{nullptr, 0, "", nullptr, 0}, - animation_info{nullptr, 0}, - uniforms{nullptr}, - last_update{0.0} { -} - -void WorldObject::set_render_entity(const std::shared_ptr &entity) { - this->render_entity = entity; - this->fetch_updates(); -} - -void WorldObject::set_camera(const std::shared_ptr &camera) { - this->camera = camera; -} - -void WorldObject::fetch_updates(const time::time_t &time) { - if (not this->render_entity->is_changed()) { - // exit early because there is nothing to do - return; - } - // Get data from render entity - this->ref_id = this->render_entity->get_id(); - this->position.sync(this->render_entity->get_position()); - this->animation_info.sync(this->render_entity->get_animation_path(), - std::function(const std::string &)>( - [&](const std::string &path) { - if (path.empty()) { - auto placeholder = this->asset_manager->get_placeholder_animation(); - if (placeholder) { - return (*placeholder).second; - } - return std::shared_ptr{nullptr}; - } - return this->asset_manager->request_animation(path); - }), - this->last_update); - this->angle.sync(this->render_entity->get_angle(), this->last_update); - - // Set self to changed so that world renderer can update the renderable - this->changed = true; - this->render_entity->clear_changed_flag(); - this->last_update = time; -} - -void WorldObject::update_uniforms(const time::time_t &time) { - // TODO: Only update uniforms that changed since last update - if (this->uniforms == nullptr) [[unlikely]] { - return; - } - - // Object world position - auto current_pos = this->position.get(time); - this->uniforms->update(this->obj_world_position, current_pos.to_world_space()); - - // Direction angle the object is facing towards currently - auto angle_degrees = this->angle.get(time).to_float(); - - // Frame subtexture - auto animation_info = this->animation_info.get(time); - auto &layer = animation_info->get_layer(0); // TODO: Support multiple layers - auto &angle = layer.get_direction_angle(angle_degrees); - - // Flip subtexture horizontally if angle is mirrored - if (angle->is_mirrored()) { - this->uniforms->update(this->flip_x, true); - } - else { - this->uniforms->update(this->flip_x, false); - } - - // Current frame index considering current time - size_t frame_idx; - switch (layer.get_display_mode()) { - case renderer::resources::display_mode::ONCE: - case renderer::resources::display_mode::LOOP: { - // ONCE and LOOP are animated based on time - auto &timing = layer.get_frame_timing(); - frame_idx = timing->get_frame(time, this->render_entity->get_update_time()); - } break; - case renderer::resources::display_mode::OFF: - default: - // OFF only shows the first frame - frame_idx = 0; - break; - } - - // Index of texture and subtexture where the frame's pixels are located - auto &frame_info = angle->get_frame(frame_idx); - auto tex_idx = frame_info->get_texture_idx(); - auto subtex_idx = frame_info->get_subtexture_idx(); - - auto &tex_info = animation_info->get_texture(tex_idx); - auto &tex_manager = this->asset_manager->get_texture_manager(); - auto &texture = tex_manager->request(tex_info->get_image_path().value()); - this->uniforms->update(this->tex, texture); - - // Subtexture coordinates.inside texture - auto coords = tex_info->get_subtex_info(subtex_idx).get_tile_params(); - this->uniforms->update(this->tile_params, coords); - - // scale and keep width x height ratio of texture - // when the viewport size changes - auto scale = animation_info->get_scalefactor() / this->camera->get_zoom(); - auto screen_size = this->camera->get_viewport_size(); - auto subtex_size = tex_info->get_subtex_info(subtex_idx).get_size(); - - // Scaling with viewport size and zoom - auto scale_vec = Eigen::Vector2f{ - scale * (static_cast(subtex_size[0]) / screen_size[0]), - scale * (static_cast(subtex_size[1]) / screen_size[1])}; - this->uniforms->update(this->scale, scale_vec); - - // Move subtexture in scene so that its anchor point is at the object's position - auto anchor = tex_info->get_subtex_info(subtex_idx).get_anchor_params(); - auto anchor_offset = Eigen::Vector2f{ - scale * (static_cast(anchor[0]) / screen_size[0]), - scale * (static_cast(anchor[1]) / screen_size[1])}; - this->uniforms->update(this->anchor_offset, anchor_offset); -} - -uint32_t WorldObject::get_id() { - return this->ref_id; -} - -const renderer::resources::MeshData WorldObject::get_mesh() { - return resources::MeshData::make_quad(); -} - -bool WorldObject::requires_renderable() { - return this->require_renderable; -} - -void WorldObject::clear_requires_renderable() { - this->require_renderable = false; -} - -bool WorldObject::is_changed() { - return this->changed; -} - -void WorldObject::clear_changed_flag() { - this->changed = false; -} - -void WorldObject::set_uniforms(const std::shared_ptr &uniforms) { - this->uniforms = uniforms; -} - -} // namespace openage::renderer::world diff --git a/libopenage/renderer/stages/world/world_object.h b/libopenage/renderer/stages/world/world_object.h deleted file mode 100644 index 2893084ed3..0000000000 --- a/libopenage/renderer/stages/world/world_object.h +++ /dev/null @@ -1,188 +0,0 @@ -// Copyright 2022-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include -#include -#include - -#include "coord/scene.h" -#include "curve/continuous.h" -#include "curve/discrete.h" -#include "curve/segmented.h" -#include "renderer/resources/mesh_data.h" -#include "renderer/types.h" -#include "time/time.h" - - -namespace openage::renderer { -class UniformInput; - -namespace camera { -class Camera; -} - -namespace resources { -class AssetManager; -class Animation2dInfo; -} // namespace resources - -namespace world { -class WorldRenderEntity; - -class WorldObject { -public: - WorldObject(const std::shared_ptr &asset_manager); - ~WorldObject() = default; - - /** - * Set the world render entity. - * - * @param entity New world render entity. - */ - void set_render_entity(const std::shared_ptr &entity); - - /** - * Set the current camera of the scene. - * - * @param camera Camera object viewing the scene. - */ - void set_camera(const std::shared_ptr &camera); - - /** - * Fetch updates from the render entity. - * - * @param time Current simulation time. - */ - void fetch_updates(const time::time_t &time = 0.0); - - /** - * Update the uniforms of the renderable associated with this object. - * - * @param time Current simulation time. - */ - void update_uniforms(const time::time_t &time = 0.0); - - /** - * Get the ID of the corresponding game entity. - * - * @return Game entity ID. - */ - uint32_t get_id(); - - /** - * Get the quad for creating the geometry. - * - * @return Mesh for creating a renderer geometry object. - */ - static const renderer::resources::MeshData get_mesh(); - - /** - * Check whether a new renderable needs to be created for this mesh. - * - * If true, the old renderable should be removed from the render pass. - * The updated uniforms and geometry should be passed to this mesh. - * Afterwards, clear the requirement flag with \p clear_requires_renderable(). - * - * @return true if a new renderable is required, else false. - */ - bool requires_renderable(); - - /** - * Indicate to this mesh that a new renderable has been created. - */ - void clear_requires_renderable(); - - /** - * Check whether the object was changed by \p update(). - * - * @return true if changes were made, else false. - */ - bool is_changed(); - - /** - * Clear the update flag by setting it to false. - */ - void clear_changed_flag(); - - /** - * Set the reference to the uniform inputs of the renderable - * associated with this object. Relevant uniforms are updated - * when calling \p update(). - * - * @param uniforms Uniform inputs of this object's renderable. - */ - void set_uniforms(const std::shared_ptr &uniforms); - - /** - * Shader uniform IDs for setting uniform values. - */ - inline static uniform_id_t obj_world_position; - inline static uniform_id_t flip_x; - inline static uniform_id_t flip_y; - inline static uniform_id_t tex; - inline static uniform_id_t tile_params; - inline static uniform_id_t scale; - inline static uniform_id_t anchor_offset; - -private: - /** - * Stores whether a new renderable for this object needs to be created - * for the render pass. - */ - bool require_renderable; - - /** - * Stores whether the \p update() call changed the object. - */ - bool changed; - - /** - * Camera for model uniforms. - */ - std::shared_ptr camera; - - /** - * Asset manager for central accessing and loading asset resources. - */ - std::shared_ptr asset_manager; - - /** - * Source for positional and texture data. - */ - std::shared_ptr render_entity; - - /** - * Reference ID for passing interaction with the graphic (e.g. mouse clicks) back to - * the engine. - */ - uint32_t ref_id; - - /** - * Position of the object. - */ - curve::Continuous position; - - /** - * Angle of the object. - */ - curve::Segmented angle; - - /** - * Animation information for the renderables. - */ - curve::Discrete> animation_info; - - /** - * Shader uniforms for the renderable in the terrain render pass. - */ - std::shared_ptr uniforms; - - /** - * Time of the last update call. - */ - time::time_t last_update; -}; -} // namespace world -} // namespace openage::renderer diff --git a/libopenage/renderer/stages/world/world_render_entity.cpp b/libopenage/renderer/stages/world/world_render_entity.cpp deleted file mode 100644 index 8cba90ca40..0000000000 --- a/libopenage/renderer/stages/world/world_render_entity.cpp +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2022-2023 the openage authors. See copying.md for legal info. - -#include "world_render_entity.h" - -#include -#include - -#include "renderer/definitions.h" - - -namespace openage::renderer::world { - -WorldRenderEntity::WorldRenderEntity() : - changed{false}, - ref_id{0}, - position{nullptr, 0, "", nullptr, SCENE_ORIGIN}, - angle{nullptr, 0, "", nullptr, 0}, - animation_path{nullptr, 0}, - last_update{0.0} { -} - -void WorldRenderEntity::update(const uint32_t ref_id, - const curve::Continuous &position, - const curve::Segmented &angle, - const std::string animation_path, - const time::time_t time) { - std::unique_lock lock{this->mutex}; - - this->ref_id = ref_id; - std::function to_scene3 = [](const coord::phys3 &pos) { - return pos.to_scene3(); - }; - this->position.sync(position, - std::function([](const coord::phys3 &pos) { - return pos.to_scene3(); - }), - this->last_update); - this->angle.sync(angle, this->last_update); - this->animation_path.set_last(time, animation_path); - this->changed = true; - this->last_update = time; -} - -void WorldRenderEntity::update(const uint32_t ref_id, - const coord::phys3 position, - const std::string animation_path, - const time::time_t time) { - std::unique_lock lock{this->mutex}; - - this->ref_id = ref_id; - this->position.set_last(time, position.to_scene3()); - this->animation_path.set_last(time, animation_path); - this->changed = true; - this->last_update = time; -} - -uint32_t WorldRenderEntity::get_id() { - std::shared_lock lock{this->mutex}; - - return this->ref_id; -} - -const curve::Continuous &WorldRenderEntity::get_position() { - std::shared_lock lock{this->mutex}; - - return this->position; -} - -const curve::Segmented &WorldRenderEntity::get_angle() { - std::shared_lock lock{this->mutex}; - - return this->angle; -} - -const curve::Discrete &WorldRenderEntity::get_animation_path() { - std::shared_lock lock{this->mutex}; - - return this->animation_path; -} - -time::time_t WorldRenderEntity::get_update_time() { - std::shared_lock lock{this->mutex}; - - return this->last_update; -} - -bool WorldRenderEntity::is_changed() { - std::shared_lock lock{this->mutex}; - - return this->changed; -} - -void WorldRenderEntity::clear_changed_flag() { - std::unique_lock lock{this->mutex}; - - this->changed = false; -} - -} // namespace openage::renderer::world diff --git a/libopenage/renderer/text.cpp b/libopenage/renderer/text.cpp deleted file mode 100644 index 9bb108cc33..0000000000 --- a/libopenage/renderer/text.cpp +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright 2015-2018 the openage authors. See copying.md for legal info. - -#include "text.h" - -#include - -#include - -#include "../util/strings.h" -#include "font/font.h" - -namespace openage { - -namespace texturefont_shader { -shader::Program *program; -GLint texture, color, tex_coord; -} // namespace texture_shader - -namespace renderer { - -struct text_render_vertex { - float x; - float y; - float u; - float v; - - text_render_vertex() - : - text_render_vertex{0.0f, 0.0f, 0.0f, 0.0f} { - } - - text_render_vertex(float x, float y, float u, float v) - : - x{x}, - y{y}, - u{u}, - v{v} { - } -}; - -struct text_render_task { - GLenum mode; - Color color; - unsigned int num_elements; - unsigned int offset; -}; - -TextRenderer::TextRenderer() - : - current_font{nullptr}, - current_color{255, 255, 255, 255}, - is_dirty{true}, - vbo{0}, - ibo{0} { - - glGenBuffers(1, &this->vbo); - glGenBuffers(1, &this->ibo); -} - -TextRenderer::~TextRenderer() { - if (this->vbo != 0u) { - glDeleteBuffers(1, &this->vbo); - } - if (this->ibo != 0u) { - glDeleteBuffers(1, &this->ibo); - } -} - -void TextRenderer::set_font(Font *font) { - if (this->current_font == font) { - return; - } - - this->current_font = font; - this->is_dirty = true; -} - -void TextRenderer::set_color(const Color &color) { - if (this->current_color == color) { - return; - } - - this->current_color = color; - this->is_dirty = true; -} - -void TextRenderer::draw(coord::viewport position, const char *format, ...) { - std::string text; - va_list vl; - va_start(vl, format); - util::vsformat(format, vl, text); - va_end(vl); - - this->draw(position.x, position.y, text); -} - -void TextRenderer::draw(coord::viewport position, const std::string &text) { - this->draw(position.x, position.y, text); -} - -void TextRenderer::draw(int x, int y, const std::string &text) { - if (this->is_dirty || this->render_batches.empty()) { - this->render_batches.emplace_back(this->current_font, this->current_color); - this->is_dirty = false; - } - - this->render_batches.back().passes.emplace_back(x, y, text); -} - -void TextRenderer::render() { - // Sort the batches by font - std::sort(std::begin(this->render_batches), std::end(this->render_batches), - [](const text_render_batch &a, const text_render_batch &b) -> bool { - return a.font < b.font; - }); - - // Merge consecutive batches if font and color values are same - for (auto current_batch = std::begin(this->render_batches); current_batch != std::end(this->render_batches); ) { - auto next_batch = current_batch; - next_batch++; - if (next_batch != std::end(this->render_batches) && - current_batch->font == next_batch->font && - current_batch->color == next_batch->color) { - // Merge the render passes of current and next batches and remove the next batch - std::move(std::begin(next_batch->passes), - std::end(next_batch->passes), - std::back_inserter(current_batch->passes)); - this->render_batches.erase(next_batch); - } else { - current_batch++; - } - } - - size_t index = 0; - std::vector vertices; - std::vector indices; - std::vector render_tasks; - render_tasks.reserve(this->render_batches.size()); - unsigned int offset = 0; - - // Compute vertices and indices - for (auto &batch : this->render_batches) { - Font *font = batch.font; - - unsigned int num_elements = 0; - - for (auto &pass : batch.passes) { - auto x = static_cast(pass.x); - auto y = static_cast(pass.y); - - std::vector glyphs = font->get_glyphs(pass.text); - - // We will create 4 vertices & 6 indices for each glyph (GL_TRIANGLES) - vertices.resize(vertices.size() + glyphs.size() * 4); - indices.resize(indices.size() + glyphs.size() * 6); - - codepoint_t previous_glyph = 0; - for (codepoint_t glyph : glyphs) { - GlyphAtlas::Entry entry = this->glyph_atlas.get(font, glyph); - - float x0 = x + entry.glyph.x_offset; - float y0 = y + entry.glyph.y_offset - entry.glyph.height; - float x1 = x0 + entry.glyph.width; - float y1 = y0 + entry.glyph.height; - - vertices[index*4 + 0] = {x0, y0, entry.u0, entry.v0}; - vertices[index*4 + 1] = {x0, y1, entry.u0, entry.v1}; - vertices[index*4 + 2] = {x1, y1, entry.u1, entry.v1}; - vertices[index*4 + 3] = {x1, y0, entry.u1, entry.v0}; - - indices[index*6 + 0] = index*4 + 0; - indices[index*6 + 1] = index*4 + 1; - indices[index*6 + 2] = index*4 + 2; - indices[index*6 + 3] = index*4 + 2; - indices[index*6 + 4] = index*4 + 3; - indices[index*6 + 5] = index*4 + 0; - - // Advance the pen position - x += entry.glyph.x_advance; - y += entry.glyph.y_advance; - - // Handle font kerning - if (previous_glyph != 0) { - x += font->get_horizontal_kerning(previous_glyph, glyph); - } - - index++; - num_elements += 6; - previous_glyph = glyph; - } - } - - text_render_task render_task{GL_TRIANGLES, batch.color, num_elements, offset}; - render_tasks.push_back(render_task); - - offset += num_elements; - } - - // Upload vertices and indices - glBindBuffer(GL_ARRAY_BUFFER, this->vbo); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->ibo); - - glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(text_render_vertex), &vertices[0], GL_STATIC_DRAW); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), &indices[0], GL_STATIC_DRAW); - - texturefont_shader::program->use(); - - this->glyph_atlas.bind(0); - - glEnableVertexAttribArray(texturefont_shader::program->pos_id); - glEnableVertexAttribArray(texturefont_shader::tex_coord); - - glVertexAttribPointer(texturefont_shader::program->pos_id, 2, GL_FLOAT, GL_FALSE, - sizeof(text_render_vertex), (GLvoid *) offsetof(text_render_vertex, x)); - glVertexAttribPointer(texturefont_shader::tex_coord, 2, GL_FLOAT, GL_FALSE, - sizeof(text_render_vertex), (GLvoid *) offsetof(text_render_vertex, u)); - - for (auto &task : render_tasks) { - glUniform4f(texturefont_shader::color, - task.color.r/255.f, task.color.g/255.f, task.color.b/255.f, task.color.a/255.f); - glDrawElements(task.mode, task.num_elements, GL_UNSIGNED_INT, (GLvoid *) (task.offset * sizeof(unsigned int))); - } - - glDisableVertexAttribArray(texturefont_shader::program->pos_id); - glDisableVertexAttribArray(texturefont_shader::tex_coord); - - texturefont_shader::program->stopusing(); - - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - - // Clear the render batches for next frame - this->render_batches.clear(); -} - -}} // openage::renderer diff --git a/libopenage/renderer/text.h b/libopenage/renderer/text.h deleted file mode 100644 index 116cbe647a..0000000000 --- a/libopenage/renderer/text.h +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include -#include - -#include "../coord/pixel.h" -#include "../shader/program.h" -#include "color.h" -#include "font/glyph_atlas.h" - -namespace openage { - -namespace texturefont_shader { -extern shader::Program *program; -extern GLint texture, color, tex_coord; -} // openage::texturefont_shader - -namespace renderer { - -/** - * Can render text with OpenGL. - * - * TODO: move to the main renderer! - */ -class TextRenderer { - -public: - /** - * Requires a working OpenGL context to create buffer objects. - */ - TextRenderer(); - - virtual ~TextRenderer(); - - /** - * Set the font to be used for the future text draw calls. - * - * @param font: the font to be used. - */ - void set_font(Font *font); - - /** - * Set the color to be used for the future text draw calls. - * - * @param color: the color to be used. - */ - void set_color(const Color &color); - - /** - * Draw a formatted string at the specified position. - * - * @param position: where the text should be displayed. - * @param format: the text format - */ - void draw(coord::viewport position, const char *format, ...); - - /** - * Draw text at the specified position. - * - * @param position: where the text should be displayed. - * @param text: the text to be displayed. - */ - void draw(coord::viewport position, const std::string &text); - - /** - * Draw text at the specified position. - * - * @param x: the position in x-direction. - * @param y: the position in y-direction. - * @param text: the text to be displayed. - */ - void draw(int x, int y, const std::string &text); - - /** - * Render all the text draw requests made during the frame. - */ - void render(); - -private: - /** - * A single text draw request containing the text and position. - */ - struct text_render_batch_pass { - int x; - int y; - std::string text; - - text_render_batch_pass(int x, int y, const std::string &text) - : - x{x}, - y{y}, - text{text} { - } - }; - - /** - * The set of text draw requests with the same font and color. - */ - struct text_render_batch { - Font *font; - Color color; - std::vector passes; - - text_render_batch(Font *font, const Color &color) - : - font{font}, - color{color} { - } - }; - - Font *current_font; - Color current_color; - bool is_dirty; - std::vector render_batches; - - GlyphAtlas glyph_atlas; - - GLuint vbo; - GLuint ibo; -}; - -}} // openage::renderer diff --git a/libopenage/renderer/texture.h b/libopenage/renderer/texture.h index 7e14cba664..c2abdab2d2 100644 --- a/libopenage/renderer/texture.h +++ b/libopenage/renderer/texture.h @@ -1,4 +1,4 @@ -// Copyright 2015-2018 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once @@ -15,7 +15,7 @@ class Texture2d { virtual ~Texture2d(); /// Returns the texture information. - const resources::Texture2dInfo& get_info() const; + const resources::Texture2dInfo &get_info() const; /// Copies this texture's data from graphics hardware into a CPU-accessible /// Texture2dData buffer. @@ -23,14 +23,15 @@ class Texture2d { /// Uploads the provided data into the GPU texture storage. The format has /// to match the format this Texture was originally created with. - virtual void upload(resources::Texture2dData const&) = 0; + virtual void upload(resources::Texture2dData const &) = 0; protected: /// Constructs the base with the given information. - Texture2d(const resources::Texture2dInfo&); + Texture2d(const resources::Texture2dInfo &); /// Information about the size, format, etc. of this texture. resources::Texture2dInfo info; }; -}} +} // namespace renderer +} // namespace openage diff --git a/libopenage/renderer/types.h b/libopenage/renderer/types.h index 56fdee7b49..cf764266ed 100644 --- a/libopenage/renderer/types.h +++ b/libopenage/renderer/types.h @@ -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. #pragma once @@ -12,4 +12,14 @@ namespace openage::renderer { */ using uniform_id_t = uint32_t; -} +/** + * Graphics API types. + */ +enum class graphics_api_t { + DEFAULT, + OPENGL, + VULKAN, +}; + + +} // namespace openage::renderer diff --git a/libopenage/renderer/uniform_buffer.h b/libopenage/renderer/uniform_buffer.h index 2ba5790127..c91edf1132 100644 --- a/libopenage/renderer/uniform_buffer.h +++ b/libopenage/renderer/uniform_buffer.h @@ -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. #pragma once @@ -21,10 +21,10 @@ class UniformBuffer : public std::enable_shared_from_this { virtual ~UniformBuffer() = default; /** - * Update the uniforms in the buffer. - * - * @param unif_in Uniform input to update the buffer with. - */ + * Update the uniforms in the buffer. + * + * @param unif_in Uniform input to update the buffer with. + */ virtual void update_uniforms(std::shared_ptr const &unif_in) = 0; /** @@ -50,22 +50,22 @@ class UniformBuffer : public std::enable_shared_from_this { protected: virtual std::shared_ptr new_unif_in() = 0; - virtual void set_i32(std::shared_ptr const &, const char *, int32_t) = 0; - virtual void set_u32(std::shared_ptr const &, const char *, uint32_t) = 0; - virtual void set_f32(std::shared_ptr const &, const char *, float) = 0; - virtual void set_f64(std::shared_ptr const &, const char *, double) = 0; - virtual void set_bool(std::shared_ptr const &, const char *, bool) = 0; - virtual void set_v2f32(std::shared_ptr const &, const char *, Eigen::Vector2f const &) = 0; - virtual void set_v3f32(std::shared_ptr const &, const char *, Eigen::Vector3f const &) = 0; - virtual void set_v4f32(std::shared_ptr const &, const char *, Eigen::Vector4f const &) = 0; - virtual void set_v2i32(std::shared_ptr const &, const char *, Eigen::Vector2i const &) = 0; - virtual void set_v3i32(std::shared_ptr const &, const char *, Eigen::Vector3i const &) = 0; - virtual void set_v4i32(std::shared_ptr const &, const char *, Eigen::Vector4i const &) = 0; - virtual void set_v2ui32(std::shared_ptr const &, const char *, Eigen::Vector2 const &) = 0; - virtual void set_v3ui32(std::shared_ptr const &, const char *, Eigen::Vector3 const &) = 0; - virtual void set_v4ui32(std::shared_ptr const &, const char *, Eigen::Vector4 const &) = 0; - virtual void set_m4f32(std::shared_ptr const &, const char *, Eigen::Matrix4f const &) = 0; - virtual void set_tex(std::shared_ptr const &, const char *, std::shared_ptr const &) = 0; + virtual void set_i32(UniformBufferInput &in, const char *, int32_t) = 0; + virtual void set_u32(UniformBufferInput &in, const char *, uint32_t) = 0; + virtual void set_f32(UniformBufferInput &in, const char *, float) = 0; + virtual void set_f64(UniformBufferInput &in, const char *, double) = 0; + virtual void set_bool(UniformBufferInput &in, const char *, bool) = 0; + virtual void set_v2f32(UniformBufferInput &in, const char *, Eigen::Vector2f const &) = 0; + virtual void set_v3f32(UniformBufferInput &in, const char *, Eigen::Vector3f const &) = 0; + virtual void set_v4f32(UniformBufferInput &in, const char *, Eigen::Vector4f const &) = 0; + virtual void set_v2i32(UniformBufferInput &in, const char *, Eigen::Vector2i const &) = 0; + virtual void set_v3i32(UniformBufferInput &in, const char *, Eigen::Vector3i const &) = 0; + virtual void set_v4i32(UniformBufferInput &in, const char *, Eigen::Vector4i const &) = 0; + virtual void set_v2ui32(UniformBufferInput &in, const char *, Eigen::Vector2 const &) = 0; + virtual void set_v3ui32(UniformBufferInput &in, const char *, Eigen::Vector3 const &) = 0; + virtual void set_v4ui32(UniformBufferInput &in, const char *, Eigen::Vector4 const &) = 0; + virtual void set_m4f32(UniformBufferInput &in, const char *, Eigen::Matrix4f const &) = 0; + virtual void set_tex(UniformBufferInput &in, const char *, std::shared_ptr const &) = 0; }; } // namespace openage::renderer diff --git a/libopenage/renderer/uniform_input.cpp b/libopenage/renderer/uniform_input.cpp index 8ad865ca04..b3abbe7ba8 100644 --- a/libopenage/renderer/uniform_input.cpp +++ b/libopenage/renderer/uniform_input.cpp @@ -1,4 +1,4 @@ -// Copyright 2019-2023 the openage authors. See copying.md for legal info. +// Copyright 2019-2024 the openage authors. See copying.md for legal info. #include "uniform_input.h" @@ -14,140 +14,140 @@ UniformInput::UniformInput(std::shared_ptr const &prog) : void UniformInput::update() {} void UniformInput::update(const char *unif, int32_t val) { - this->program->set_i32(this->shared_from_this(), unif, val); + this->program->set_i32(*this, unif, val); } void UniformInput::update(const char *unif, uint32_t val) { - this->program->set_u32(this->shared_from_this(), unif, val); + this->program->set_u32(*this, unif, val); } void UniformInput::update(const char *unif, float val) { - this->program->set_f32(this->shared_from_this(), unif, val); + this->program->set_f32(*this, unif, val); } void UniformInput::update(const char *unif, double val) { - this->program->set_f64(this->shared_from_this(), unif, val); + this->program->set_f64(*this, unif, val); } void UniformInput::update(const char *unif, bool val) { - this->program->set_bool(this->shared_from_this(), unif, val); + this->program->set_bool(*this, unif, val); } void UniformInput::update(const char *unif, Eigen::Vector2f const &val) { - this->program->set_v2f32(this->shared_from_this(), unif, val); + this->program->set_v2f32(*this, unif, val); } void UniformInput::update(const char *unif, Eigen::Vector3f const &val) { - this->program->set_v3f32(this->shared_from_this(), unif, val); + this->program->set_v3f32(*this, unif, val); } void UniformInput::update(const char *unif, Eigen::Vector4f const &val) { - this->program->set_v4f32(this->shared_from_this(), unif, val); + this->program->set_v4f32(*this, unif, val); } void UniformInput::update(const char *unif, Eigen::Vector2i const &val) { - this->program->set_v2i32(this->shared_from_this(), unif, val); + this->program->set_v2i32(*this, unif, val); } void UniformInput::update(const char *unif, Eigen::Vector3i const &val) { - this->program->set_v3i32(this->shared_from_this(), unif, val); + this->program->set_v3i32(*this, unif, val); } void UniformInput::update(const char *unif, Eigen::Vector4i const &val) { - this->program->set_v4i32(this->shared_from_this(), unif, val); + this->program->set_v4i32(*this, unif, val); } void UniformInput::update(const char *unif, Eigen::Vector2 const &val) { - this->program->set_v2ui32(this->shared_from_this(), unif, val); + this->program->set_v2ui32(*this, unif, val); } void UniformInput::update(const char *unif, Eigen::Vector3 const &val) { - this->program->set_v3ui32(this->shared_from_this(), unif, val); + this->program->set_v3ui32(*this, unif, val); } void UniformInput::update(const char *unif, Eigen::Vector4 const &val) { - this->program->set_v4ui32(this->shared_from_this(), unif, val); + this->program->set_v4ui32(*this, unif, val); } void UniformInput::update(const char *unif, std::shared_ptr const &val) { - this->program->set_tex(this->shared_from_this(), unif, val); + this->program->set_tex(*this, unif, val); } void UniformInput::update(const char *unif, std::shared_ptr &val) { - this->program->set_tex(this->shared_from_this(), unif, val); + this->program->set_tex(*this, unif, val); } void UniformInput::update(const char *unif, Eigen::Matrix4f const &val) { - this->program->set_m4f32(this->shared_from_this(), unif, val); + this->program->set_m4f32(*this, unif, val); } -void UniformInput::update(const uniform_id_t &id, int32_t val) { - this->program->set_i32(this->shared_from_this(), id, val); +void UniformInput::update(uniform_id_t id, int32_t val) { + this->program->set_i32(*this, id, val); } -void UniformInput::update(const uniform_id_t &id, uint32_t val) { - this->program->set_u32(this->shared_from_this(), id, val); +void UniformInput::update(uniform_id_t id, uint32_t val) { + this->program->set_u32(*this, id, val); } -void UniformInput::update(const uniform_id_t &id, float val) { - this->program->set_f32(this->shared_from_this(), id, val); +void UniformInput::update(uniform_id_t id, float val) { + this->program->set_f32(*this, id, val); } -void UniformInput::update(const uniform_id_t &id, double val) { - this->program->set_f64(this->shared_from_this(), id, val); +void UniformInput::update(uniform_id_t id, double val) { + this->program->set_f64(*this, id, val); } -void UniformInput::update(const uniform_id_t &id, bool val) { - this->program->set_bool(this->shared_from_this(), id, val); +void UniformInput::update(uniform_id_t id, bool val) { + this->program->set_bool(*this, id, val); } -void UniformInput::update(const uniform_id_t &id, Eigen::Vector2f const &val) { - this->program->set_v2f32(this->shared_from_this(), id, val); +void UniformInput::update(uniform_id_t id, Eigen::Vector2f const &val) { + this->program->set_v2f32(*this, id, val); } -void UniformInput::update(const uniform_id_t &id, Eigen::Vector3f const &val) { - this->program->set_v3f32(this->shared_from_this(), id, val); +void UniformInput::update(uniform_id_t id, Eigen::Vector3f const &val) { + this->program->set_v3f32(*this, id, val); } -void UniformInput::update(const uniform_id_t &id, Eigen::Vector4f const &val) { - this->program->set_v4f32(this->shared_from_this(), id, val); +void UniformInput::update(uniform_id_t id, Eigen::Vector4f const &val) { + this->program->set_v4f32(*this, id, val); } -void UniformInput::update(const uniform_id_t &id, Eigen::Vector2i const &val) { - this->program->set_v2i32(this->shared_from_this(), id, val); +void UniformInput::update(uniform_id_t id, Eigen::Vector2i const &val) { + this->program->set_v2i32(*this, id, val); } -void UniformInput::update(const uniform_id_t &id, Eigen::Vector3i const &val) { - this->program->set_v3i32(this->shared_from_this(), id, val); +void UniformInput::update(uniform_id_t id, Eigen::Vector3i const &val) { + this->program->set_v3i32(*this, id, val); } -void UniformInput::update(const uniform_id_t &id, Eigen::Vector4i const &val) { - this->program->set_v4i32(this->shared_from_this(), id, val); +void UniformInput::update(uniform_id_t id, Eigen::Vector4i const &val) { + this->program->set_v4i32(*this, id, val); } -void UniformInput::update(const uniform_id_t &id, Eigen::Vector2 const &val) { - this->program->set_v2ui32(this->shared_from_this(), id, val); +void UniformInput::update(uniform_id_t id, Eigen::Vector2 const &val) { + this->program->set_v2ui32(*this, id, val); } -void UniformInput::update(const uniform_id_t &id, Eigen::Vector3 const &val) { - this->program->set_v3ui32(this->shared_from_this(), id, val); +void UniformInput::update(uniform_id_t id, Eigen::Vector3 const &val) { + this->program->set_v3ui32(*this, id, val); } -void UniformInput::update(const uniform_id_t &id, Eigen::Vector4 const &val) { - this->program->set_v4ui32(this->shared_from_this(), id, val); +void UniformInput::update(uniform_id_t id, Eigen::Vector4 const &val) { + this->program->set_v4ui32(*this, id, val); } -void UniformInput::update(const uniform_id_t &id, std::shared_ptr const &val) { - this->program->set_tex(this->shared_from_this(), id, val); +void UniformInput::update(uniform_id_t id, std::shared_ptr const &val) { + this->program->set_tex(*this, id, val); } -void UniformInput::update(const uniform_id_t &id, std::shared_ptr &val) { - this->program->set_tex(this->shared_from_this(), id, val); +void UniformInput::update(uniform_id_t id, std::shared_ptr &val) { + this->program->set_tex(*this, id, val); } -void UniformInput::update(const uniform_id_t &id, Eigen::Matrix4f const &val) { - this->program->set_m4f32(this->shared_from_this(), id, val); +void UniformInput::update(uniform_id_t id, Eigen::Matrix4f const &val) { + this->program->set_m4f32(*this, id, val); } @@ -157,71 +157,71 @@ UniformBufferInput::UniformBufferInput(std::shared_ptr const &buf void UniformBufferInput::update() {} void UniformBufferInput::update(const char *unif, int32_t val) { - this->buffer->set_i32(this->shared_from_this(), unif, val); + this->buffer->set_i32(*this, unif, val); } void UniformBufferInput::update(const char *unif, uint32_t val) { - this->buffer->set_u32(this->shared_from_this(), unif, val); + this->buffer->set_u32(*this, unif, val); } void UniformBufferInput::update(const char *unif, float val) { - this->buffer->set_f32(this->shared_from_this(), unif, val); + this->buffer->set_f32(*this, unif, val); } void UniformBufferInput::update(const char *unif, double val) { - this->buffer->set_f64(this->shared_from_this(), unif, val); + this->buffer->set_f64(*this, unif, val); } void UniformBufferInput::update(const char *unif, bool val) { - this->buffer->set_bool(this->shared_from_this(), unif, val); + this->buffer->set_bool(*this, unif, val); } void UniformBufferInput::update(const char *unif, Eigen::Vector2f const &val) { - this->buffer->set_v2f32(this->shared_from_this(), unif, val); + this->buffer->set_v2f32(*this, unif, val); } void UniformBufferInput::update(const char *unif, Eigen::Vector3f const &val) { - this->buffer->set_v3f32(this->shared_from_this(), unif, val); + this->buffer->set_v3f32(*this, unif, val); } void UniformBufferInput::update(const char *unif, Eigen::Vector4f const &val) { - this->buffer->set_v4f32(this->shared_from_this(), unif, val); + this->buffer->set_v4f32(*this, unif, val); } void UniformBufferInput::update(const char *unif, Eigen::Vector2i const &val) { - this->buffer->set_v2i32(this->shared_from_this(), unif, val); + this->buffer->set_v2i32(*this, unif, val); } void UniformBufferInput::update(const char *unif, Eigen::Vector3i const &val) { - this->buffer->set_v3i32(this->shared_from_this(), unif, val); + this->buffer->set_v3i32(*this, unif, val); } void UniformBufferInput::update(const char *unif, Eigen::Vector4i const &val) { - this->buffer->set_v4i32(this->shared_from_this(), unif, val); + this->buffer->set_v4i32(*this, unif, val); } void UniformBufferInput::update(const char *unif, Eigen::Vector2 const &val) { - this->buffer->set_v2ui32(this->shared_from_this(), unif, val); + this->buffer->set_v2ui32(*this, unif, val); } void UniformBufferInput::update(const char *unif, Eigen::Vector3 const &val) { - this->buffer->set_v3ui32(this->shared_from_this(), unif, val); + this->buffer->set_v3ui32(*this, unif, val); } void UniformBufferInput::update(const char *unif, Eigen::Vector4 const &val) { - this->buffer->set_v4ui32(this->shared_from_this(), unif, val); + this->buffer->set_v4ui32(*this, unif, val); } void UniformBufferInput::update(const char *unif, std::shared_ptr const &val) { - this->buffer->set_tex(this->shared_from_this(), unif, val); + this->buffer->set_tex(*this, unif, val); } void UniformBufferInput::update(const char *unif, std::shared_ptr &val) { - this->buffer->set_tex(this->shared_from_this(), unif, val); + this->buffer->set_tex(*this, unif, val); } void UniformBufferInput::update(const char *unif, Eigen::Matrix4f const &val) { - this->buffer->set_m4f32(this->shared_from_this(), unif, val); + this->buffer->set_m4f32(*this, unif, val); } } // namespace openage::renderer diff --git a/libopenage/renderer/uniform_input.h b/libopenage/renderer/uniform_input.h index 515a0a0e4e..39bb4ea983 100644 --- a/libopenage/renderer/uniform_input.h +++ b/libopenage/renderer/uniform_input.h @@ -1,4 +1,4 @@ -// Copyright 2019-2023 the openage authors. See copying.md for legal info. +// Copyright 2019-2024 the openage authors. See copying.md for legal info. #pragma once @@ -53,9 +53,8 @@ class DataInput { /** * Abstract base for uniform input. Besides the uniform values, it stores information about * which shader program the input was created for. -*/ -class UniformInput : public DataInput - , public std::enable_shared_from_this { + */ +class UniformInput : public DataInput { protected: /** * Create a new uniform input for a given shader program. @@ -67,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; @@ -86,23 +87,23 @@ class UniformInput : public DataInput void update(const char *unif, std::shared_ptr &val) override; void update(const char *unif, Eigen::Matrix4f const &val) override; - void update(const uniform_id_t &id, int32_t val); - void update(const uniform_id_t &id, uint32_t val); - void update(const uniform_id_t &id, float val); - void update(const uniform_id_t &id, double val); - void update(const uniform_id_t &id, bool val); - void update(const uniform_id_t &id, Eigen::Vector2f const &val); - void update(const uniform_id_t &id, Eigen::Vector3f const &val); - void update(const uniform_id_t &id, Eigen::Vector4f const &val); - void update(const uniform_id_t &id, Eigen::Vector2i const &val); - void update(const uniform_id_t &id, Eigen::Vector3i const &val); - void update(const uniform_id_t &id, Eigen::Vector4i const &val); - void update(const uniform_id_t &id, Eigen::Vector2 const &val); - void update(const uniform_id_t &id, Eigen::Vector3 const &val); - void update(const uniform_id_t &id, Eigen::Vector4 const &val); - void update(const uniform_id_t &id, std::shared_ptr const &val); - void update(const uniform_id_t &id, std::shared_ptr &val); - void update(const uniform_id_t &id, Eigen::Matrix4f const &val); + void update(uniform_id_t id, int32_t val); + void update(uniform_id_t id, uint32_t val); + void update(uniform_id_t id, float val); + void update(uniform_id_t id, double val); + void update(uniform_id_t id, bool val); + void update(uniform_id_t id, Eigen::Vector2f const &val); + void update(uniform_id_t id, Eigen::Vector3f const &val); + void update(uniform_id_t id, Eigen::Vector4f const &val); + void update(uniform_id_t id, Eigen::Vector2i const &val); + void update(uniform_id_t id, Eigen::Vector3i const &val); + void update(uniform_id_t id, Eigen::Vector4i const &val); + void update(uniform_id_t id, Eigen::Vector2 const &val); + void update(uniform_id_t id, Eigen::Vector3 const &val); + void update(uniform_id_t id, Eigen::Vector4 const &val); + void update(uniform_id_t id, std::shared_ptr const &val); + void update(uniform_id_t id, std::shared_ptr &val); + void update(uniform_id_t id, Eigen::Matrix4f const &val); /** * Catch-all template in order to handle unsupported types and avoid infinite recursion. diff --git a/libopenage/renderer/vulkan/graphics_device.cpp b/libopenage/renderer/vulkan/graphics_device.cpp index 2853c3f34e..25874225d3 100644 --- a/libopenage/renderer/vulkan/graphics_device.cpp +++ b/libopenage/renderer/vulkan/graphics_device.cpp @@ -1,7 +1,8 @@ -// Copyright 2017-2019 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #include "graphics_device.h" +#include #include #include "../../error/error.h" @@ -21,7 +22,7 @@ std::optional VlkGraphicsDevice::find_device_surface_supp // Figure out if any of the families supports graphics for (size_t i = 0; i < q_fams.size(); i++) { - auto const& q_fam = q_fams[i]; + auto const &q_fam = q_fams[i]; if (q_fam.queueCount > 0) { if ((q_fam.queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0u) { @@ -52,10 +53,11 @@ std::optional VlkGraphicsDevice::find_device_surface_supp if (maybe_present_fam) { details.graphics_fam = *maybe_graphics_fam; details.maybe_present_fam = {}; - } else { + } + else { // Otherwise look for a present-only queue for (size_t i = 0; i < q_fams.size(); i++) { - auto const& q_fam = q_fams[i]; + auto const &q_fam = q_fams[i]; if (q_fam.queueCount > 0) { VkBool32 support = VK_FALSE; vkGetPhysicalDeviceSurfaceSupportKHR(dev, i, surf, &support); @@ -88,9 +90,8 @@ std::optional VlkGraphicsDevice::find_device_surface_supp return details; } -VlkGraphicsDevice::VlkGraphicsDevice(VkPhysicalDevice dev, std::vector const& q_fams) - : phys_device(dev) -{ +VlkGraphicsDevice::VlkGraphicsDevice(VkPhysicalDevice dev, std::vector const &q_fams) : + phys_device(dev) { // Prepare queue creation info for each family requested std::vector q_infos(q_fams.size()); const float p = 1.0f; @@ -103,15 +104,15 @@ VlkGraphicsDevice::VlkGraphicsDevice(VkPhysicalDevice dev, std::vector } // Request these extensions - std::vector ext_names = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; + std::vector ext_names = {VK_KHR_SWAPCHAIN_EXTENSION_NAME}; // Check if extensions are available auto exts = vk_do_ritual(vkEnumerateDeviceExtensionProperties, dev, nullptr); for (auto ext : ext_names) { - if (std::count_if(exts.begin(), exts.end(), [=] (VkExtensionProperties const& p) { - return std::strcmp(p.extensionName, ext) == 0; - } ) == 0 - ) { + if (std::count_if(exts.begin(), exts.end(), [=](VkExtensionProperties const &p) { + return std::strcmp(p.extensionName, ext) == 0; + }) + == 0) { throw Error(MSG(err) << "Tried to instantiate device, but it's missing this extension: " << ext); } } @@ -122,21 +123,21 @@ VlkGraphicsDevice::VlkGraphicsDevice(VkPhysicalDevice dev, std::vector vkGetPhysicalDeviceProperties(this->phys_device, &dev_props); log::log(MSG(dbg) << "Chosen Vulkan graphics device: " << dev_props.deviceName); log::log(MSG(dbg) << "Device extensions:"); - for (auto const& ext : exts) { + for (auto const &ext : exts) { log::log(MSG(dbg) << "\t" << ext.extensionName); } } #endif // Prepare device creation - VkDeviceCreateInfo create_dev {}; + VkDeviceCreateInfo create_dev{}; create_dev.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; create_dev.queueCreateInfoCount = q_infos.size(); create_dev.pQueueCreateInfos = q_infos.data(); create_dev.enabledExtensionCount = ext_names.size(); create_dev.ppEnabledExtensionNames = ext_names.data(); - VkPhysicalDeviceFeatures features {}; + VkPhysicalDeviceFeatures features{}; // TODO request features create_dev.pEnabledFeatures = &features; @@ -165,4 +166,4 @@ VlkGraphicsDevice::~VlkGraphicsDevice() { vkDestroyDevice(this->device, nullptr); } -} // openage::renderer::vulkan +} // namespace openage::renderer::vulkan diff --git a/libopenage/renderer/vulkan/graphics_device.h b/libopenage/renderer/vulkan/graphics_device.h index e29297461c..9cf8ffe23c 100644 --- a/libopenage/renderer/vulkan/graphics_device.h +++ b/libopenage/renderer/vulkan/graphics_device.h @@ -1,4 +1,4 @@ -// Copyright 2017-2018 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once @@ -57,7 +57,7 @@ class VlkGraphicsDevice { /// Given a physical device and a list of queue family indices in that device, instantiates /// a logical device with a queue per each of the families. The device has to support the /// swapchain extension. - VlkGraphicsDevice(VkPhysicalDevice dev, std::vector const& q_fams); + VlkGraphicsDevice(VkPhysicalDevice dev, std::vector const &q_fams); VkPhysicalDevice get_physical_device() const; VkDevice get_device() const; @@ -68,4 +68,6 @@ class VlkGraphicsDevice { ~VlkGraphicsDevice(); }; -}}} // openage::renderer::vulkan +} // namespace vulkan +} // namespace renderer +} // namespace openage diff --git a/libopenage/renderer/vulkan/loader.cpp b/libopenage/renderer/vulkan/loader.cpp index fdeb6f8c94..2dcf6bd2fc 100644 --- a/libopenage/renderer/vulkan/loader.cpp +++ b/libopenage/renderer/vulkan/loader.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2018 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #include "loader.h" @@ -9,14 +9,14 @@ namespace openage { namespace renderer { namespace vulkan { -VlkLoader::VlkLoader() - : inited(false) {} +VlkLoader::VlkLoader() : + inited(false) {} void VlkLoader::init(VkInstance instance) { - #ifndef NDEBUG +#ifndef NDEBUG this->pCreateDebugReportCallbackEXT = PFN_vkCreateDebugReportCallbackEXT(vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT")); this->pDestroyDebugReportCallbackEXT = PFN_vkDestroyDebugReportCallbackEXT(vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT")); - #endif +#endif this->inited = true; } @@ -24,10 +24,9 @@ void VlkLoader::init(VkInstance instance) { #ifndef NDEBUG VkResult VlkLoader::vkCreateDebugReportCallbackEXT( VkInstance instance, - const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, - const VkAllocationCallbacks* pAllocator, - VkDebugReportCallbackEXT* pCallback -) { + const VkDebugReportCallbackCreateInfoEXT *pCreateInfo, + const VkAllocationCallbacks *pAllocator, + VkDebugReportCallbackEXT *pCallback) { if (!this->inited) { throw Error(MSG(err) << "Tried to request function from Vulkan extension loader before initializing it."); } @@ -42,8 +41,7 @@ VkResult VlkLoader::vkCreateDebugReportCallbackEXT( void VlkLoader::vkDestroyDebugReportCallbackEXT( VkInstance instance, VkDebugReportCallbackEXT callback, - const VkAllocationCallbacks* pAllocator -) { + const VkAllocationCallbacks *pAllocator) { if (!this->inited) { throw Error(MSG(err) << "Tried to request function from Vulkan extension loader before initializing it."); } @@ -54,4 +52,6 @@ void VlkLoader::vkDestroyDebugReportCallbackEXT( } #endif -}}} // openage::renderer::vulkan +} // namespace vulkan +} // namespace renderer +} // namespace openage diff --git a/libopenage/renderer/vulkan/loader.h b/libopenage/renderer/vulkan/loader.h index 66375790c0..9436eb806c 100644 --- a/libopenage/renderer/vulkan/loader.h +++ b/libopenage/renderer/vulkan/loader.h @@ -1,4 +1,4 @@ -// Copyright 2017-2018 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once @@ -28,19 +28,19 @@ class VlkLoader { /// Part of VK_EXT_debug_report, allows setting a callback for debug events. VkResult vkCreateDebugReportCallbackEXT( VkInstance instance, - const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, - const VkAllocationCallbacks* pAllocator, - VkDebugReportCallbackEXT* pCallback - ); + const VkDebugReportCallbackCreateInfoEXT *pCreateInfo, + const VkAllocationCallbacks *pAllocator, + VkDebugReportCallbackEXT *pCallback); /// Part of VK_EXT_debug_report, destroys the debug callback object. void vkDestroyDebugReportCallbackEXT( VkInstance instance, VkDebugReportCallbackEXT callback, - const VkAllocationCallbacks* pAllocator - ); + const VkAllocationCallbacks *pAllocator); #endif }; -}}} // openage::renderer::vulkan +} // namespace vulkan +} // namespace renderer +} // namespace openage diff --git a/libopenage/renderer/vulkan/render_target.h b/libopenage/renderer/vulkan/render_target.h index 49db9e5ef1..32cc4784d7 100644 --- a/libopenage/renderer/vulkan/render_target.h +++ b/libopenage/renderer/vulkan/render_target.h @@ -1,12 +1,16 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once #include +#include -#include "../renderer.h" +#include -#include "graphics_device.h" +#include "log/log.h" + +#include "renderer/render_target.h" +#include "renderer/vulkan/graphics_device.h" namespace openage { @@ -154,9 +158,9 @@ class VlkDrawableDisplay { }; class VlkFramebuffer final : public RenderTarget { - //std::vector attachments; - //VkFramebuffer framebuffer; - //VkViewport viewport; + // std::vector attachments; + // VkFramebuffer framebuffer; + // VkViewport viewport; public: VlkFramebuffer(VkRenderPass /*pass*/, std::vector const & /*attachments*/) { diff --git a/libopenage/renderer/vulkan/renderer.h b/libopenage/renderer/vulkan/renderer.h index 7bb13cd6ce..f1afc1c99d 100644 --- a/libopenage/renderer/vulkan/renderer.h +++ b/libopenage/renderer/vulkan/renderer.h @@ -1,11 +1,11 @@ -// Copyright 2017-2018 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once #include -#include "../renderer.h" #include "../../util/path.h" +#include "../renderer.h" #include "graphics_device.h" @@ -21,12 +21,13 @@ class VlkRenderer { VkSurfaceKHR surface; public: - VlkRenderer(VkInstance instance, VkSurfaceKHR surface) - : instance(instance) - , surface(surface) {} + VlkRenderer(VkInstance instance, VkSurfaceKHR surface) : + instance(instance), surface(surface) {} /// Testing function that draws a triangle. Not part of the final renderer implementation. - void do_the_thing(util::Path& dir); + void do_the_thing(util::Path &dir); }; -}}} // openage::renderer::vulkan +} // namespace vulkan +} // namespace renderer +} // namespace openage diff --git a/libopenage/renderer/vulkan/shader_program.h b/libopenage/renderer/vulkan/shader_program.h index e46030dd13..e1b2b7c0b2 100644 --- a/libopenage/renderer/vulkan/shader_program.h +++ b/libopenage/renderer/vulkan/shader_program.h @@ -1,12 +1,14 @@ -// Copyright 2017-2018 the openage authors. See copying.md for legal info. +// Copyright 2017-2023 the openage authors. See copying.md for legal info. #pragma once -#include "../../error/error.h" -#include "../../log/log.h" +#include "error/error.h" +#include "log/log.h" -#include "../resources/shader_source.h" -#include "../shader_program.h" +#include + +#include "renderer/resources/shader_source.h" +#include "renderer/shader_program.h" namespace openage { @@ -15,12 +17,18 @@ namespace vulkan { static VkShaderStageFlagBits vk_shader_stage(resources::shader_stage_t stage) { switch (stage) { - case resources::shader_stage_t::vertex: return VK_SHADER_STAGE_VERTEX_BIT; - case resources::shader_stage_t::geometry: return VK_SHADER_STAGE_GEOMETRY_BIT; - case resources::shader_stage_t::tesselation_control: return VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT; - case resources::shader_stage_t::tesselation_evaluation: return VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT; - case resources::shader_stage_t::fragment: return VK_SHADER_STAGE_FRAGMENT_BIT; - default: throw Error(MSG(err) << "Unknown shader stage."); + case resources::shader_stage_t::vertex: + return VK_SHADER_STAGE_VERTEX_BIT; + case resources::shader_stage_t::geometry: + return VK_SHADER_STAGE_GEOMETRY_BIT; + case resources::shader_stage_t::tesselation_control: + return VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT; + case resources::shader_stage_t::tesselation_evaluation: + return VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT; + case resources::shader_stage_t::fragment: + return VK_SHADER_STAGE_FRAGMENT_BIT; + default: + throw Error(MSG(err) << "Unknown shader stage."); } } @@ -29,11 +37,11 @@ class VlkShaderProgram /* final : public ShaderProgram */ { std::vector modules; std::vector pipeline_stage_infos; - explicit VlkShaderProgram(VkDevice dev, std::vector const& srcs) { + explicit VlkShaderProgram(VkDevice dev, std::vector const &srcs) { // TODO reflect with spirv-cross // TODO if glsl, compile to spirv with libshaderc - for (auto const& src : srcs) { + for (auto const &src : srcs) { if (src.get_lang() != resources::shader_lang_t::spirv) { throw Error(MSG(err) << "Unsupported shader language in Vulkan shader."); } @@ -41,7 +49,7 @@ class VlkShaderProgram /* final : public ShaderProgram */ { VkShaderModuleCreateInfo cr_shdr = {}; cr_shdr.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; cr_shdr.codeSize = src.get_source().size(); - cr_shdr.pCode = reinterpret_cast(src.get_source().data()); + cr_shdr.pCode = reinterpret_cast(src.get_source().data()); VkShaderModule mod; VK_CALL_CHECKED(vkCreateShaderModule, dev, &cr_shdr, nullptr, &mod); @@ -61,4 +69,6 @@ class VlkShaderProgram /* final : public ShaderProgram */ { } }; -}}} // openage::renderer::vulkan +} // namespace vulkan +} // namespace renderer +} // namespace openage diff --git a/libopenage/renderer/vulkan/util.h b/libopenage/renderer/vulkan/util.h index 04ab3c872c..6e97551098 100644 --- a/libopenage/renderer/vulkan/util.h +++ b/libopenage/renderer/vulkan/util.h @@ -1,4 +1,4 @@ -// Copyright 2017-2018 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once @@ -8,7 +8,7 @@ template -std::vector vk_do_ritual(R2 (*func)(uint32_t*, R*)) { +std::vector vk_do_ritual(R2 (*func)(uint32_t *, R *)) { uint32_t count = 0; func(&count, nullptr); std::vector ret(count); @@ -18,7 +18,7 @@ std::vector vk_do_ritual(R2 (*func)(uint32_t*, R*)) { } template -std::vector vk_do_ritual(R2 (*func)(T, uint32_t*, R*), T2&& a) { +std::vector vk_do_ritual(R2 (*func)(T, uint32_t *, R *), T2 &&a) { uint32_t count = 0; func(std::forward(a), &count, nullptr); std::vector ret(count); @@ -28,7 +28,7 @@ std::vector vk_do_ritual(R2 (*func)(T, uint32_t*, R*), T2&& a) { } template -std::vector vk_do_ritual(R2 (*func)(T, U, uint32_t*, R*), T2&& a, U2&& b) { +std::vector vk_do_ritual(R2 (*func)(T, U, uint32_t *, R *), T2 &&a, U2 &&b) { uint32_t count = 0; func(std::forward(a), std::forward(b), &count, nullptr); std::vector ret(count); @@ -38,7 +38,7 @@ std::vector vk_do_ritual(R2 (*func)(T, U, uint32_t*, R*), T2&& a, U2&& b) { } template -std::vector vk_do_ritual(R2 (*func)(T, U, V, uint32_t*, R*), T2&& a, U2&& b, V2&& c) { +std::vector vk_do_ritual(R2 (*func)(T, U, V, uint32_t *, R *), T2 &&a, U2 &&b, V2 &&c) { uint32_t count = 0; func(std::forward(a), std::forward(b), std::forward(c), &count, nullptr); std::vector ret(count); @@ -47,7 +47,7 @@ std::vector vk_do_ritual(R2 (*func)(T, U, V, uint32_t*, R*), T2&& a, U2&& b, return ret; } -#define VK_CALL_CHECKED(fun, ...) \ +#define VK_CALL_CHECKED(fun, ...) \ { \ VkResult res = fun(__VA_ARGS__); \ if (res != VK_SUCCESS) { \ diff --git a/libopenage/renderer/window.cpp b/libopenage/renderer/window.cpp index 5cdd3b2052..88dbefb92e 100644 --- a/libopenage/renderer/window.cpp +++ b/libopenage/renderer/window.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 "window.h" @@ -11,13 +11,11 @@ namespace openage::renderer { std::shared_ptr Window::create(const std::string &title, - size_t width, - size_t height, - bool debug) { + window_settings settings) { // currently we only have a functional GL window // TODO: support other renderer windows // and add some selection mechanism. - return std::make_shared(title, width, height, debug); + return std::make_shared(title, settings); } @@ -41,6 +39,10 @@ void Window::add_mouse_button_callback(const mouse_button_cb_t &cb) { this->on_mouse_button.push_back(cb); } +void Window::add_mouse_move_callback(const mouse_move_cb_t &cb) { + this->on_mouse_move.push_back(cb); +} + void Window::add_mouse_wheel_callback(const mouse_wheel_cb_t &cb) { this->on_mouse_wheel.push_back(cb); } diff --git a/libopenage/renderer/window.h b/libopenage/renderer/window.h index 18c7ce04d8..e3465bef4d 100644 --- a/libopenage/renderer/window.h +++ b/libopenage/renderer/window.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 @@ -6,11 +6,12 @@ #include #include -#include "../util/vector.h" -#include "renderer.h" - #include +#include "renderer/renderer.h" +#include "renderer/types.h" +#include "util/vector.h" + QT_FORWARD_DECLARE_CLASS(QWindow) QT_FORWARD_DECLARE_CLASS(QKeyEvent) QT_FORWARD_DECLARE_CLASS(QMouseEvent) @@ -20,22 +21,49 @@ namespace openage::renderer { class WindowEventHandler; +/** + * Modes for window display. + */ +enum class window_mode { + FULLSCREEN, + BORDERLESS, + WINDOWED +}; + +/** + * Settings for creating a window. + */ +struct window_settings { + // Width of the window in pixels. + size_t width = 1024; + // Height of the window in pixels. + size_t height = 768; + // Graphics API to use in the window's renderer. + graphics_api_t backend = graphics_api_t::DEFAULT; + // If true, enable vsync. + 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; +}; + + +/** + * Represents a window that can be used to display graphics. + */ class Window { public: /** * Create a new Window instance for displaying stuff. * * @param title Window title shown in the Desktop Environment. - * @param width Width in pixels. - * @param height Height in pixels. - * @param debug If true, enable OpenGL debug logging. + * @param settings Settings for creating the window. * * @return The created Window instance. */ static std::shared_ptr create(const std::string &title, - size_t width, - size_t height, - bool debug = false); + window_settings settings = {}); virtual ~Window() = default; @@ -62,6 +90,7 @@ class Window { using key_cb_t = std::function; using mouse_button_cb_t = std::function; + using mouse_move_cb_t = std::function; using mouse_wheel_cb_t = std::function; using resize_cb_t = std::function; @@ -79,6 +108,13 @@ class Window { */ void add_mouse_button_callback(const mouse_button_cb_t &cb); + /** + * Register a function that executes when the mouse is moved. + * + * @param cb Callback function. + */ + void add_mouse_move_callback(const mouse_move_cb_t &cb); + /** * Register a function that executes when a mouse wheel action is used. * @@ -156,6 +192,11 @@ class Window { */ std::vector on_mouse_button; + /** + * Callbacks for mouse move actions. + */ + std::vector on_mouse_move; + /** * Callbacks for mouse wheel actions. */ diff --git a/libopenage/rng/global_rng.h b/libopenage/rng/global_rng.h index 8ed475ae22..9fbe0f3f47 100644 --- a/libopenage/rng/global_rng.h +++ b/libopenage/rng/global_rng.h @@ -1,6 +1,10 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. +// Copyright 2015-2023 the openage authors. See copying.md for legal info. + #pragma once +#include + + /** @file * This file contains functions for the global random number generator. * diff --git a/libopenage/rng/rng.h b/libopenage/rng/rng.h index 4f96ba31ab..83faddee68 100644 --- a/libopenage/rng/rng.h +++ b/libopenage/rng/rng.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 @@ -21,13 +21,13 @@ class RNG { */ explicit RNG(uint64_t seed); - [[maybe_unused]] /** + /** * Initializes the rng using data from the buffer pointed to by data * @param data The buffer that contains data for seeding the rng * @param count The number of bytes in the buffer * @throws Error if 0 bytes are passed */ - RNG(const void *data, size_t count); + [[maybe_unused]] RNG(const void *data, size_t count); /** @@ -73,7 +73,7 @@ class RNG { /** * Retrieves a random value from the generator */ - inline uint64_t operator ()() { + inline uint64_t operator()() { return this->random(); } @@ -189,13 +189,13 @@ class RNG { * Reads the rng from a stream * @throws Error if reading from the stream fails */ -std::istream &operator >>(std::istream &instream, RNG &inrng); +std::istream &operator>>(std::istream &instream, RNG &inrng); /** * Writes the rng state to a stream * @throws Error if writing data fails */ -std::ostream &operator <<(std::ostream &ostream, const RNG &inrng); +std::ostream &operator<<(std::ostream &ostream, const RNG &inrng); /** diff --git a/libopenage/screenshot.cpp b/libopenage/screenshot.cpp deleted file mode 100644 index dfb3d2d5e1..0000000000 --- a/libopenage/screenshot.cpp +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright 2014-2019 the openage authors. See copying.md for legal info. - -#include "screenshot.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "coord/pixel.h" -#include "job/job_manager.h" -#include "log/log.h" -#include "util/strings.h" - -namespace openage { - - -ScreenshotManager::ScreenshotManager(job::JobManager *job_mgr) - : - count{0}, - job_manager{job_mgr} { -} - - -ScreenshotManager::~ScreenshotManager() {} - - -std::string ScreenshotManager::gen_next_filename() { - - std::time_t t = std::time(NULL); - - if (t == this->last_time) { - this->count++; - } else { - this->count = 0; - this->last_time = t; - } - - // these two values (32) *must* be the same for safety reasons - char timestamp[32]; - std::strftime(timestamp, 32, "%Y-%m-%d_%H-%M-%S", std::localtime(&t)); - - return util::sformat("/tmp/openage_%s_%02d.png", timestamp, this->count); -} - - -void ScreenshotManager::save_screenshot(coord::viewport_delta size) { - coord::pixel_t width = size.x, - height = size.y; - - std::shared_ptr pxdata(new uint8_t[4*width*height], std::default_delete()); - glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pxdata.get()); - - auto encode_function = [this, pxdata, size] () { - return this->encode_png(pxdata, size); - }; - this->job_manager->enqueue(encode_function); -} - - -bool ScreenshotManager::encode_png(std::shared_ptr pxdata, - coord::viewport_delta size) { - std::FILE *fout = NULL; - coord::pixel_t width = size.x, - height = size.y; - auto warn_fn = [] (png_structp /*png_ptr*/, png_const_charp message) { - log::log(MSG(err) << "Creating screenshot failed: libpng error: " << message); - }; - auto err_fn = [] (png_structp png_ptr, png_const_charp message) { - log::log(MSG(err) << "Creating screenshot failed: libpng error: " << message); - longjmp(png_jmpbuf(png_ptr), 1); - }; - - png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, - (png_voidp) NULL, - err_fn, warn_fn); - if (!png_ptr) - return false; - - png_infop info_ptr = png_create_info_struct(png_ptr); - if (!info_ptr) { - png_destroy_write_struct(&png_ptr, (png_infopp) NULL); - return false; - } - - if (setjmp(png_jmpbuf(png_ptr))) { - std::fclose(fout); - png_destroy_write_struct(&png_ptr, &info_ptr); - return false; - } - - std::string filename = this->gen_next_filename(); - fout = std::fopen(filename.c_str(), "wb"); - if (fout == NULL) { - png_destroy_write_struct(&png_ptr, &info_ptr); - log::log(MSG(err) << "Could not open '"<< filename << "': " - << std::string(strerror(errno))); - return false; - } - - png_init_io(png_ptr, fout); - - png_set_IHDR(png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB, - PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, - PNG_FILTER_TYPE_DEFAULT); - - // Put image row pointer into info_ptr so that we can use the high-level - // write interface. - std::vector row_ptrs; - - // Invert rows. - row_ptrs.reserve(height); - for (int i = 1; i <= height; i++) { - row_ptrs.push_back(pxdata.get() + (height - i) * 4 * width); - } - png_set_rows(png_ptr, info_ptr, &row_ptrs[0]); - - //TODO: print ingame message. - log::log(MSG(info) << "Saving screenshot to '" << filename << "'."); - - png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_STRIP_FILLER_AFTER, NULL); - - std::fclose(fout); - png_destroy_write_struct(&png_ptr, &info_ptr); - return true; -} - -} // openage diff --git a/libopenage/screenshot.h b/libopenage/screenshot.h deleted file mode 100644 index 221f0b43ab..0000000000 --- a/libopenage/screenshot.h +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2014-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include -#include - -#include "coord/pixel.h" - -namespace openage { - -namespace job { -class JobManager; -} - -/** - * Takes screenshots, duh. - * - * TODO: move into renderer! - */ -class ScreenshotManager { -public: - /** - * Initializes the screenshot manager with the given job manager. - */ - ScreenshotManager(job::JobManager* job_mgr); - - ~ScreenshotManager(); - - /** To be called to save a screenshot. */ - void save_screenshot(coord::viewport_delta size); - - /** To be called by the job manager. Returns true on success, false otherwise. */ - bool encode_png(std::shared_ptr pxdata, - coord::viewport_delta size); - - /** size of the game window, in coord_sdl */ - coord::viewport_delta window_size; - -private: - - /** to be called to get the next screenshot filename into the array */ - std::string gen_next_filename(); - - /** contains the number to be in the next screenshot filename */ - unsigned count; - - /** contains the last time when a screenshot was taken */ - std::time_t last_time; - - /** the job manager this screenshot manager uses */ - job::JobManager *job_manager; -}; - -} // openage diff --git a/libopenage/shader/CMakeLists.txt b/libopenage/shader/CMakeLists.txt deleted file mode 100644 index 6477fa9fc7..0000000000 --- a/libopenage/shader/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -add_sources(libopenage - program.cpp - shader.cpp -) diff --git a/libopenage/shader/program.cpp b/libopenage/shader/program.cpp deleted file mode 100644 index 479d91a6ae..0000000000 --- a/libopenage/shader/program.cpp +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright 2013-2023 the openage authors. See copying.md for legal info. - -#include "program.h" - -#include -#include -#include - -#include "../error/error.h" -#include "../log/log.h" -#include "../util/compiler.h" -#include "../util/file.h" -#include "../util/strings.h" - -#include "shader.h" - -namespace openage::shader { - -Program::Program() : is_linked(false), vert(nullptr), frag(nullptr), geom(nullptr) { - this->id = glCreateProgram(); -} - -Program::Program(Shader *s0, Shader *s1) : Program{} { - this->attach_shader(s0); - this->attach_shader(s1); -} - -Program::~Program() { - glDeleteProgram(this->id); -} - -void Program::attach_shader(Shader *s) { - switch (s->type) { - case GL_VERTEX_SHADER: - this->vert = s; - break; - case GL_FRAGMENT_SHADER: - this->frag = s; - break; - case GL_GEOMETRY_SHADER: - this->geom = s; - break; - } - glAttachShader(this->id, s->id); -} - -void Program::link() { - glLinkProgram(this->id); - this->check(GL_LINK_STATUS); - glValidateProgram(this->id); - this->check(GL_VALIDATE_STATUS); - this->is_linked = true; - this->post_link_hook(); - - if (this->vert != nullptr) { - glDetachShader(this->id, this->vert->id); - } - if (this->frag != nullptr) { - glDetachShader(this->id, this->frag->id); - } - if (this->geom != nullptr) { - glDetachShader(this->id, this->geom->id); - } -} - -/** - * checks a given status for this program. - * - * @param what_to_check GL_LINK_STATUS GL_VALIDATE_STATUS GL_COMPILE_STATUS - */ -void Program::check(GLenum what_to_check) { - GLint status; - glGetProgramiv(this->id, what_to_check, &status); - - if (status != GL_TRUE) { - GLint loglen; - glGetProgramiv(this->id, GL_INFO_LOG_LENGTH, &loglen); - char *infolog = new char[loglen]; - glGetProgramInfoLog(this->id, loglen, nullptr, infolog); - - const char *what_str; - switch(what_to_check) { - case GL_LINK_STATUS: - what_str = "linking"; - break; - case GL_VALIDATE_STATUS: - what_str = "validation"; - break; - case GL_COMPILE_STATUS: - what_str = "compiliation"; - break; - default: - what_str = ""; - break; - } - - auto errormsg = MSG(err); - errormsg << "Program " << what_str << " failed\n" << infolog; - delete[] infolog; - - throw Error(errormsg); - } -} - -void Program::use() { - glUseProgram(this->id); -} - -void Program::stopusing() { - glUseProgram(static_cast(0)); -} - -GLint Program::get_uniform_id(const char *name) { - return glGetUniformLocation(this->id, name); -} - -GLint Program::get_attribute_id(const char *name) { - if (!this->is_linked) [[unlikely]] { - throw Error(MSG(err) << - "Attribute " << name << - " was queried before program was linked!"); - } - - GLint aid = glGetAttribLocation(this->id, name); - - if (aid == -1) [[unlikely]] { - this->dump_active_attributes(); - throw Error(MSG(err) << - "Attribute " << name << " queried but not found or active" - " (pwnt by the compiler)."); - } - - return aid; -} - -void Program::set_attribute_id(const char *name, GLuint id) { - if (!this->is_linked) { - glBindAttribLocation(this->id, id, name); - } - else { - //TODO: maybe enable overwriting, but after that relink the program - throw Error(MSG(err) << "assigned attribute " << name << " = " << id - << " after program was linked!"); - } -} - -void Program::dump_active_attributes() { - auto msg = MSG(warn); - msg << "Dumping shader program active attribute list:"; - - GLint num_attribs; - glGetProgramiv(this->id, GL_ACTIVE_ATTRIBUTES, &num_attribs); - - GLint attrib_max_length; - glGetProgramiv(this->id, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &attrib_max_length); - - for (int i = 0; i < num_attribs; i++) { - GLsizei attrib_length; - GLint attrib_size; - GLenum attrib_type; - char *attrib_name = new char[attrib_max_length]; - glGetActiveAttrib(this->id, i, attrib_max_length, &attrib_length, - &attrib_size, &attrib_type, attrib_name); - - msg << "\n" << - "-> attribute " << attrib_name << ": " - " : type=" << attrib_type << ", size=" << attrib_size; - delete[] attrib_name; - } -} - - -void Program::post_link_hook() { - this->pos_id = this->get_attribute_id("vertex_position"); - this->mvpm_id = this->get_uniform_id("mvp_matrix"); -} - -} // openage::shader diff --git a/libopenage/shader/program.h b/libopenage/shader/program.h deleted file mode 100644 index c6848d68be..0000000000 --- a/libopenage/shader/program.h +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2013-2016 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -namespace openage { -namespace shader { - -class Shader; - -class Program { -public: - GLuint id; - GLint pos_id, mvpm_id; - - Program(); - Program(Shader *s0, Shader *s1); - ~Program(); - - void attach_shader(Shader *s); - - void link(); - - void use(); - void stopusing(); - - GLint get_uniform_id(const char *name); - GLint get_attribute_id(const char *name); - - void set_attribute_id(const char *name, GLuint id); - - void dump_active_attributes(); - -private: - bool is_linked; - Shader *vert, *frag, *geom; - - void check(GLenum what_to_check); - GLint get_info(GLenum pname); - char *get_log(); - void post_link_hook(); -}; - - -}} // openage::shader diff --git a/libopenage/shader/shader.cpp b/libopenage/shader/shader.cpp deleted file mode 100644 index f9a7c555db..0000000000 --- a/libopenage/shader/shader.cpp +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2013-2019 the openage authors. See copying.md for legal info. - -#include "shader.h" - -#include -#include - -#include "../error/error.h" -#include "../log/log.h" -#include "../util/file.h" -#include "../util/strings.h" - -namespace openage::shader { - -const char *type_to_string(GLenum type) { - switch (type) { - case GL_VERTEX_SHADER: - return "vertex"; - case GL_FRAGMENT_SHADER: - return "fragment"; - case GL_GEOMETRY_SHADER: - return "geometry"; - default: - return "unknown"; - } -} - -Shader::Shader(GLenum type, std::initializer_list sources) { - //create shader - this->id = glCreateShader(type); - - //store type - this->type = type; - - //load shader source - std::vector x = std::vector(sources); - glShaderSource(this->id, x.size(), x.data(), nullptr); - - //compile shader source - glCompileShader(this->id); - - //check compiliation result - GLint status; - glGetShaderiv(this->id, GL_COMPILE_STATUS, &status); - - if (status != GL_TRUE) { - GLint loglen; - glGetShaderiv(this->id, GL_INFO_LOG_LENGTH, &loglen); - - auto infolog = std::make_unique(loglen); - glGetShaderInfoLog(this->id, loglen, nullptr, infolog.get()); - - auto errmsg = MSG(err); - errmsg << "Failed to compile " << type_to_string(type) << " shader\n" << infolog; - - glDeleteShader(this->id); - - throw Error(errmsg); - } -} - -Shader::~Shader() { - glDeleteShader(this->id); -} - -} // openage::shader diff --git a/libopenage/shader/shader.h b/libopenage/shader/shader.h deleted file mode 100644 index a8a263e6b9..0000000000 --- a/libopenage/shader/shader.h +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2013-2016 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -#include - -namespace openage { -namespace shader { - -const char *type_to_string(GLenum type); - -class Shader { -public: - Shader(GLenum type, std::initializer_list sources); - ~Shader(); - - GLuint id; - GLenum type; -}; - -}} // openage::shader diff --git a/libopenage/terrain/CMakeLists.txt b/libopenage/terrain/CMakeLists.txt deleted file mode 100644 index 2dbe9a39cc..0000000000 --- a/libopenage/terrain/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -add_sources(libopenage - terrain.cpp - terrain_chunk.cpp - terrain_object.cpp - terrain_outline.cpp - terrain_search.cpp -) diff --git a/libopenage/terrain/terrain.cpp b/libopenage/terrain/terrain.cpp deleted file mode 100644 index a360878984..0000000000 --- a/libopenage/terrain/terrain.cpp +++ /dev/null @@ -1,732 +0,0 @@ -// Copyright 2013-2023 the openage authors. See copying.md for legal info. - -#include "terrain.h" - -#include -#include -#include -#include - -#include "../coord/chunk.h" -#include "../coord/pixel.h" -#include "../coord/tile.h" -#include "../error/error.h" -#include "../legacy_engine.h" -#include "../log/log.h" -#include "../util/misc.h" -#include "../util/strings.h" - -#include "terrain_chunk.h" -#include "terrain_object.h" - -namespace openage { - -TileContent::TileContent() : - terrain_id{0} { -} - -TileContent::~TileContent() = default; - -Terrain::Terrain(terrain_meta *meta, bool is_infinite) : - infinite{is_infinite}, - meta{meta} { - // TODO: - //this->limit_positive = - //this->limit_negative = - - // maps chunk position to chunks - this->chunks = std::unordered_map{}; -} - -Terrain::~Terrain() { - log::log(MSG(dbg) << "Cleanup terrain"); - - for (auto &chunk : this->chunks) { - // this chunk was autogenerated, so clean it up - if (chunk.second->manually_created == false) { - delete chunk.second; - } - } -} - -std::vector Terrain::used_chunks() const { - std::vector result; - for (auto &c : chunks) { - result.push_back(c.first); - } - return result; -} - -bool Terrain::fill(const int *data, const coord::tile_delta &size) { - bool was_cut = false; - - coord::tile pos = {0, 0}; - for (; pos.ne < size.ne; pos.ne++) { - for (pos.se = 0; pos.se < size.se; pos.se++) { - if (this->check_tile(pos) == tile_state::invalid) { - was_cut = true; - continue; - } - int terrain_id = data[pos.ne * size.ne + pos.se]; - TerrainChunk *chunk = this->get_create_chunk(pos); - chunk->get_data(pos)->terrain_id = terrain_id; - } - } - return was_cut; -} - -void Terrain::attach_chunk(TerrainChunk *new_chunk, - const coord::chunk &position, - bool manually_created) { - new_chunk->set_terrain(this); - new_chunk->manually_created = manually_created; - log::log(MSG(dbg) << "Inserting new chunk at (" << position.ne << "," << position.se << ")"); - this->chunks[position] = new_chunk; - - struct chunk_neighbors neigh = this->get_chunk_neighbors(position); - for (int i = 0; i < 8; i++) { - TerrainChunk *neighbor = neigh.neighbor[i]; - if (neighbor != nullptr) { - //set the new chunks neighbor to the neighbor chunk - new_chunk->neighbors.neighbor[i] = neighbor; - - //set the neighbors neighbor on the opposite direction - //to the new chunk - neighbor->neighbors.neighbor[(i + 4) % 8] = new_chunk; - - log::log(MSG(dbg) << "Neighbor " << i << " gets notified of new neighbor."); - } - else { - log::log(MSG(dbg) << "Neighbor " << i << " not found."); - } - } -} - -TerrainChunk *Terrain::get_chunk(const coord::chunk &position) { - auto iter = this->chunks.find(position); - - if (iter == this->chunks.end()) { - return nullptr; - } - else { - return iter->second; - } -} - -TerrainChunk *Terrain::get_chunk(const coord::tile &position) { - return this->get_chunk(position.to_chunk()); -} - -TerrainChunk *Terrain::get_create_chunk(const coord::chunk &position) { - TerrainChunk *res = this->get_chunk(position); - if (res == nullptr) { - res = new TerrainChunk(); - this->attach_chunk(res, position, false); - } - return res; -} - -TerrainChunk *Terrain::get_create_chunk(const coord::tile &position) { - return this->get_create_chunk(position.to_chunk()); -} - -TileContent *Terrain::get_data(const coord::tile &position) { - TerrainChunk *c = this->get_chunk(position.to_chunk()); - if (c == nullptr) { - return nullptr; - } - else { - return c->get_data(position.get_pos_on_chunk()); - } -} - -TerrainObject *Terrain::obj_at_point(const coord::phys3 &point) { - coord::tile t = point.to_tile(); - TileContent *tc = this->get_data(t); - if (!tc) { - return nullptr; - } - - // prioritise selecting the smallest object - TerrainObject *smallest = nullptr; - for (auto obj_ptr : tc->obj) { - if (obj_ptr->contains(point) && (!smallest || obj_ptr->min_axis() < smallest->min_axis())) { - smallest = obj_ptr; - } - } - return smallest; -} - -bool Terrain::validate_terrain(terrain_t terrain_id) { - if (terrain_id >= static_cast(this->meta->terrain_id_count)) { - throw Error(MSG(err) << "Requested terrain_id is out of range: " << terrain_id); - } - else { - return true; - } -} - -bool Terrain::validate_mask(ssize_t mask_id) { - if (mask_id >= static_cast(this->meta->blendmode_count)) { - throw Error(MSG(err) << "Requested mask_id is out of range: " << mask_id); - } - else { - return true; - } -} - -int Terrain::priority(terrain_t terrain_id) { - this->validate_terrain(terrain_id); - return this->meta->terrain_id_priority_map[terrain_id]; -} - -int Terrain::blendmode(terrain_t terrain_id) { - this->validate_terrain(terrain_id); - return this->meta->terrain_id_blendmode_map[terrain_id]; -} - -Texture *Terrain::texture(terrain_t terrain_id) { - this->validate_terrain(terrain_id); - return this->meta->textures[terrain_id]; -} - -Texture *Terrain::blending_mask(ssize_t mask_id) { - this->validate_mask(mask_id); - return this->meta->blending_masks[mask_id]; -} - -unsigned Terrain::get_subtexture_id(const coord::tile &pos, unsigned atlas_size) { - unsigned result = 0; - - result += util::mod(pos.se, atlas_size); - result *= atlas_size; - result += util::mod(pos.ne, atlas_size); - - return result; -} - -struct chunk_neighbors Terrain::get_chunk_neighbors(const coord::chunk &position) { - struct chunk_neighbors ret; - - for (int i = 0; i < 8; i++) { - coord::chunk tmp{ - position.ne + (coord::chunk_t)neigh_offsets[i].ne, - position.se + (coord::chunk_t)neigh_offsets[i].se}; - ret.neighbor[i] = this->get_chunk(tmp); - } - - return ret; -} - -int Terrain::get_blending_mode(terrain_t base_id, terrain_t neighbor_id) { - /* - * this function may require much more code, but this simple - * magnitude comparison seems to do the job. - * feel free to confirm or fix the behavior. - * - * my guess is that the blending mode encodes another information - * not publicly noticed yet: the overlay priority. - * the higher the blendmode id, the higher the mode priority. - * this may also be the reason why there are mask duplicates - * in blendomatic.dat - * - * funny enough, just using the modes in the dat file lead - * to a totally wrong render. the convert script reassigns the - * blending modes with a simple key=>val mapping, - * and after that, it looks perfect. - */ - - int base_mode = this->blendmode(base_id); - int neighbor_mode = this->blendmode(neighbor_id); - - if (neighbor_mode > base_mode) { - return neighbor_mode; - } - else { - return base_mode; - } -} - -tile_state Terrain::check_tile(const coord::tile &position) { - if (!this->check_tile_position(position)) { - return tile_state::invalid; - } - else { - TerrainChunk *chunk = this->get_chunk(position); - if (chunk == nullptr) { - return tile_state::creatable; - } - else { - return tile_state::existing; - } - } -} - -bool Terrain::check_tile_position(const coord::tile & /*pos*/) { - if (this->infinite) { - return true; - } - else { - throw Error(ERR << "non-infinite terrains are not supported yet"); - } -} - -void Terrain::draw(presenter::LegacyDisplay *display, RenderOptions *settings) { - // TODO: move this draw invokation to a render manager. - // it can reorder the draw instructions and minimize texture switching. - - // query the window coordinates from the display first - coord::viewport wbl = coord::viewport{0, 0}; - coord::viewport wbr = coord::viewport{display->coord.viewport_size.x, 0}; - coord::viewport wtl = coord::viewport{0, display->coord.viewport_size.y}; - coord::viewport wtr = coord::viewport{display->coord.viewport_size.x, display->coord.viewport_size.y}; - - // top left, bottom right tile coordinates - // that are currently visible in the window - // then convert them to tile coordinates. - coord::tile tl = wtl.to_tile(display->coord); - coord::tile tr = wtr.to_tile(display->coord); - coord::tile bl = wbl.to_tile(display->coord); - coord::tile br = wbr.to_tile(display->coord); - - // main terrain calculation call: get the `terrain_render_data` - auto draw_data = this->create_draw_advice(tl, tr, br, bl, true); - - // TODO: the following loop is totally inefficient and shit. - // it reloads the drawing texture to the gpu FOR EACH TILE! - // nevertheless, currently it works. - - // draw the terrain ground - for (auto &tile : draw_data.tiles) { - // iterate over all layers to be drawn - for (int i = 0; i < tile.count; i++) { - struct tile_data *layer = &tile.data[i]; - - // position, where the tile is drawn - coord::tile tile_pos = layer->pos; - - int mask_id = layer->mask_id; - Texture *texture = layer->tex; - int subtexture_id = layer->subtexture_id; - Texture *mask_texture = layer->mask_tex; - - texture->draw(display->coord, *this, tile_pos, ALPHAMASKED, subtexture_id, mask_texture, mask_id); - } - } - - // TODO: drawing buildings can't be the job of the terrain.. - // draw the buildings - for (auto &object : draw_data.objects) { - // object->draw(*display); - } -} - -struct terrain_render_data Terrain::create_draw_advice(const coord::tile &ab, - const coord::tile &cd, - const coord::tile &ef, - const coord::tile &gh, - bool blending_enabled) { - /* - * The passed parameters define the screen corners. - * - * ne, se coordinates - * o = screen corner, where the tile coordinates can be queried. - * x = corner of the rhombus that will be drawn, calculated by all o. - * - * cb - * x - * . . - * . . - * ab o===========o cd - * . = visible = . - * gb x = screen = x cf - * . = = . - * gh o===========o ef - * . . - * . . - * x - * gf - * - * The rendering area may be optimized further in the future, - * to exactly fit the visible screen. - * For now, we are drawing the big rhombus. - */ - - // procedure: find all the tiles to be drawn - // and store them to a tile drawing instruction structure - struct terrain_render_data data; - - // vector of tiles to be drawn - std::vector *tiles = &data.tiles; - - // ordered set of objects on the terrain (buildings.) - // it's ordered by the visibility layers. - auto objects = &data.objects; - - coord::tile gb = {gh.ne, ab.se}; - coord::tile cf = {cd.ne, ef.se}; - - // hint the vector about the number of tiles it will contain - size_t tiles_count = std::abs(cf.ne - gb.ne) * std::abs(cf.se - gb.se); - tiles->reserve(tiles_count); - - // sweep the whole rhombus area - for (coord::tile tilepos = gb; tilepos.ne <= cf.ne; tilepos.ne++) { - for (tilepos.se = gb.se; tilepos.se <= cf.se; tilepos.se++) { - // get the terrain tile drawing data - auto tile = this->create_tile_advice(tilepos, blending_enabled); - tiles->push_back(tile); - - // get the object standing on the tile - // TODO: make the terrain independent of objects standing on it. - TileContent *tile_content = this->get_data(tilepos); - if (tile_content != nullptr) { - for (auto obj_item : tile_content->obj) { - objects->insert(obj_item); - } - } - } - } - - return data; -} - - -struct tile_draw_data Terrain::create_tile_advice(coord::tile position, bool blending_enabled) { - // this struct will be filled with all tiles and overlays to draw. - struct tile_draw_data tile; - tile.count = 0; - - TileContent *base_tile_content = this->get_data(position); - - // chunk of this tile does not exist - if (base_tile_content == nullptr) { - return tile; - } - - struct tile_data base_tile_data; - - // the base terrain id of the tile - base_tile_data.terrain_id = base_tile_content->terrain_id; - - // the base terrain is not existant. - if (base_tile_data.terrain_id < 0) { - return tile; - } - - this->validate_terrain(base_tile_data.terrain_id); - - Texture *tex = this->texture(base_tile_data.terrain_id); - - base_tile_data.state = tile_state::existing; - base_tile_data.pos = position; - base_tile_data.priority = this->priority(base_tile_data.terrain_id); - base_tile_data.tex = tex; - base_tile_data.subtexture_id = this->get_subtexture_id( - position, - std::sqrt(tex->get_subtexture_count())); - base_tile_data.blend_mode = -1; - base_tile_data.mask_tex = nullptr; - base_tile_data.mask_id = -1; - - tile.data[tile.count] = base_tile_data; - tile.count += 1; - - // blendomatic!!111 - // see doc/media/blendomatic for the idea behind this. - if (blending_enabled) { - // the neighbors of the base tile - struct neighbor_tile neigh_data[8]; - - // get all neighbor tiles around position, reset the influence directions. - this->get_neighbors(position, neigh_data, this->meta->influences_buf.get()); - - // create influence list (direction, priority) - // strip and order influences, get the final influence data structure - struct influence_group influence_group = this->calculate_influences( - &base_tile_data, - neigh_data, - this->meta->influences_buf.get()); - - // create the draw_masks from the calculated influences - this->calculate_masks(position, &tile, &influence_group); - } - - return tile; -} - -void Terrain::get_neighbors(coord::tile basepos, - neighbor_tile *neigh_data, - influence *influences_by_terrain_id) { - // walk over all given neighbor tiles and store them to the influence list, - // group them by terrain id. - - for (int neigh_id = 0; neigh_id < 8; neigh_id++) { - // the current neighbor - auto neighbor = &neigh_data[neigh_id]; - - // calculate the pos of the neighbor tile - coord::tile neigh_pos = basepos + neigh_offsets[neigh_id]; - - // get the neighbor data - TileContent *neigh_content = this->get_data(neigh_pos); - - // chunk for neighbor or single tile is not existant - if (neigh_content == nullptr || neigh_content->terrain_id < 0) { - neighbor->state = tile_state::missing; - } - else { - neighbor->terrain_id = neigh_content->terrain_id; - neighbor->state = tile_state::existing; - neighbor->priority = this->priority(neighbor->terrain_id); - - // reset influence directions for this tile - influences_by_terrain_id[neighbor->terrain_id].direction = 0; - } - } -} - -struct influence_group Terrain::calculate_influences(struct tile_data *base_tile, - struct neighbor_tile *neigh_data, - struct influence *influences_by_terrain_id) { - // influences to actually draw (-> maximum 8) - struct influence_group influences {}; - influences.count = 0; - - // process adjacent neighbors first, - // then add diagonal influences, if no adjacent influence was found - constexpr int neigh_id_lookup[] = {1, 3, 5, 7, 0, 2, 4, 6}; - - for (int i = 0; i < 8; i++) { - // diagonal neighbors: (neigh_id % 2) == 0 - // adjacent neighbors: (neigh_id % 2) == 1 - - int neigh_id = neigh_id_lookup[i]; - bool is_adjacent_neighbor = neigh_id % 2 == 1; - bool is_diagonal_neighbor = not is_adjacent_neighbor; - - // the current neighbor_tile. - auto neighbor = &neigh_data[neigh_id]; - - // neighbor is nonexistant - if (neighbor->state == tile_state::missing) { - continue; - } - - // neighbor only interesting if it's a different terrain than the base. - // if it is the same id, the priorities are equal. - // neighbor draws over the base if it's priority is greater. - if (neighbor->priority > base_tile->priority) { - // get influence storage for the neighbor terrain id - // to group influences by id - auto influence = &influences_by_terrain_id[neighbor->terrain_id]; - - // check if diagonal influence is valid - if (is_diagonal_neighbor) { - // get the adjacent neighbors to the current diagonal - // influence - // (a & 0x07) == (a % 8) - uint8_t adj_neigh_0 = (neigh_id - 1) & 0x07; - uint8_t adj_neigh_1 = (neigh_id + 1) & 0x07; - - uint8_t neigh_mask = (1 << adj_neigh_0) | (1 << adj_neigh_1); - - // the adjacent neigbors are already influencing - // the current tile, therefore don't apply the diagonal mask - if ((influence->direction & neigh_mask) != 0) { - continue; - } - } - - // this terrain id hasn't had influence so far: - // add it to the list of influences. - if (influence->direction == 0) { - influences.terrain_ids[influences.count] = neighbor->terrain_id; - influences.count += 1; - } - - // as tile i has influence for this priority - // => bit i is set to 1 by 2^i - influence->direction |= 1 << neigh_id; - influence->priority = neighbor->priority; - influence->terrain_id = neighbor->terrain_id; - } - } - - // influences_by_terrain_id will be merged in the following, - // unused terrain ids will be dropped now. - - // shrink the big influence buffer that had entries for all terrains - // by copying the possible (max 8) influences to a separate buffer. - for (int k = 0; k < influences.count; k++) { - int relevant_id = influences.terrain_ids[k]; - influences.data[k] = influences_by_terrain_id[relevant_id]; - } - - // order the influences by their priority - for (int k = 1; k < influences.count; k++) { - struct influence tmp_influence = influences.data[k]; - - int l = k - 1; - while (l >= 0 && influences.data[l].priority > tmp_influence.priority) { - influences.data[l + 1] = influences.data[l]; - l -= 1; - } - - influences.data[l + 1] = tmp_influence; - } - - return influences; -} - - -void Terrain::calculate_masks(coord::tile position, - struct tile_draw_data *tile_data, - struct influence_group *influences) { - // influences are grouped by terrain id. - // the direction member has each bit set to 1 that is an influence from that direction. - // create a mask for this direction combination. - - // the base tile is stored at position 0 of the draw_mask - terrain_t base_terrain_id = tile_data->data[0].terrain_id; - - // iterate over all neighbors (with different terrain_ids) that have influence - for (ssize_t i = 0; i < influences->count; i++) { - // neighbor id of the current influence - char direction_bits = influences->data[i].direction; - - // all bits are 0 -> no influence directions stored. - // => no influence can be ignored. - if (direction_bits == 0) { - continue; - } - - terrain_t neighbor_terrain_id = influences->data[i].terrain_id; - int adjacent_mask_id = -1; - - /* neighbor ids: - 0 - 7 1 => 8 neighbors that can have influence on - 6 @ 2 the mask id selection. - 5 3 - 4 - */ - - // filter adjacent and diagonal influences neighbor_id: 76543210 - uint8_t direction_bits_adjacent = direction_bits & 0xAA; //0b10101010 - uint8_t direction_bits_diagonal = direction_bits & 0x55; //0b01010101 - - switch (direction_bits_adjacent) { - case 0x08: //0b00001000 - adjacent_mask_id = 0; //0..3 - break; - case 0x02: //0b00000010 - adjacent_mask_id = 4; //4..7 - break; - case 0x20: //0b00100000 - adjacent_mask_id = 8; //8..11 - break; - case 0x80: //0b10000000 - adjacent_mask_id = 12; //12..15 - break; - case 0x22: //0b00100010 - adjacent_mask_id = 20; - break; - case 0x88: //0b10001000 - adjacent_mask_id = 21; - break; - case 0xA0: //0b10100000 - adjacent_mask_id = 22; - break; - case 0x82: //0b10000010 - adjacent_mask_id = 23; - break; - case 0x28: //0b00101000 - adjacent_mask_id = 24; - break; - case 0x0A: //0b00001010 - adjacent_mask_id = 25; - break; - case 0x2A: //0b00101010 - adjacent_mask_id = 26; - break; - case 0xA8: //0b10101000 - adjacent_mask_id = 27; - break; - case 0xA2: //0b10100010 - adjacent_mask_id = 28; - break; - case 0x8A: //0b10001010 - adjacent_mask_id = 29; - break; - case 0xAA: //0b10101010 - adjacent_mask_id = 30; - break; - } - - // if it's the linear adjacent mask, cycle the 4 possible masks. - // e.g. long shorelines don't look the same then. - // maskid == 0x08 0x02 0x80 0x20 for that. - if (adjacent_mask_id <= 12 && adjacent_mask_id % 4 == 0) { - //we have 4 = 2^2 anti redundancy masks, so keep the last 2 bits - uint8_t anti_redundancy_offset = (position.ne + position.se) & 0x03; - adjacent_mask_id += anti_redundancy_offset; - } - - // get the blending mode (the mask selection) for this transition - // the mode is dependent on the two meeting terrain types - int blend_mode = this->get_blending_mode(base_terrain_id, neighbor_terrain_id); - - // append the mask for the adjacent blending - if (adjacent_mask_id >= 0) { - struct tile_data *overlay = &tile_data->data[tile_data->count]; - overlay->pos = position; - overlay->mask_id = adjacent_mask_id; - overlay->blend_mode = blend_mode; - overlay->terrain_id = neighbor_terrain_id; - overlay->tex = this->texture(neighbor_terrain_id); - overlay->subtexture_id = this->get_subtexture_id( - position, - std::sqrt(overlay->tex->get_subtexture_count())); - overlay->mask_tex = this->blending_mask(blend_mode); - overlay->state = tile_state::existing; - - tile_data->count += 1; - } - - // append the mask for the diagonal blending - if (direction_bits_diagonal > 0) { - for (int l = 0; l < 4; l++) { - // generate one mask for each influencing diagonal neighbor id. - // even if they all have the same terrain_id, - // because we don't have combined diagonal influence masks. - - // l == 0: pos = 0b000000001, mask = 18 - // l == 1: pos = 0b000000100, mask = 16 - // l == 2: pos = 0b000010000, mask = 17 - // l == 3: pos = 0b001000000, mask = 19 - - int current_direction_bit = 1 << (l * 2); - constexpr int diag_mask_id_map[4] = {18, 16, 17, 19}; - - if (direction_bits_diagonal & current_direction_bit) { - struct tile_data *overlay = &tile_data->data[tile_data->count]; - overlay->pos = position; - overlay->mask_id = diag_mask_id_map[l]; - overlay->blend_mode = blend_mode; - overlay->terrain_id = neighbor_terrain_id; - overlay->tex = this->texture(neighbor_terrain_id); - overlay->subtexture_id = this->get_subtexture_id( - position, - std::sqrt(overlay->tex->get_subtexture_count())); - overlay->mask_tex = this->blending_mask(blend_mode); - overlay->state = tile_state::existing; - - tile_data->count += 1; - } - } - } - } -} - -} // namespace openage diff --git a/libopenage/terrain/terrain.h b/libopenage/terrain/terrain.h deleted file mode 100644 index bb1d876843..0000000000 --- a/libopenage/terrain/terrain.h +++ /dev/null @@ -1,404 +0,0 @@ -// Copyright 2013-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "../coord/chunk.h" -#include "../coord/phys.h" -#include "../coord/pixel.h" -#include "../coord/tile.h" -#include "../texture.h" -#include "../util/misc.h" -#include "assets/legacy_assetmanager.h" - -namespace openage { - -class LegacyEngine; -class RenderOptions; -class TerrainChunk; -class TerrainObject; - -/** - * type that for terrain ids. - * it's signed so that -1 can indicate a missing tile. - * TODO: get rid of the signedness. - */ -using terrain_t = int; - -/** - * hashing for chunk coordinates. - * - * this allows storage of chunk coords as keys in an unordered map. - */ -struct coord_chunk_hash { - size_t operator()(const coord::chunk &input) const { - constexpr int half_size_t_bits = sizeof(size_t) * 4; - - return ((size_t)input.ne << half_size_t_bits) | input.se; - } -}; - - -/** - * describes the properties of one terrain tile. - * - * this includes the terrain_id (ice, water, grass, ...) - * and the list of objects which have a bounding box overlapping the tile - */ -class TileContent { -public: - TileContent(); - ~TileContent(); - terrain_t terrain_id; - std::vector obj; -}; - - -/** - * coordinate offsets for getting tile neighbors by their id. - */ -constexpr coord::tile_delta const neigh_offsets[] = { - {1, -1}, - {1, 0}, - {1, 1}, - {0, 1}, - {-1, 1}, - {-1, 0}, - {-1, -1}, - {0, -1}}; - - -/** - * describes the state of a terrain tile. - */ -enum class tile_state { - missing, //!< tile is not created yet - existing, //!< tile is already existing - creatable, //!< tile does not exist but can be created - invalid, //!< tile does not exist and can not be created -}; - - -/** - * storage for influences by neighbor tiles. - */ -struct influence { - uint8_t direction; //!< bitmask for influence directions, bit 0 = neighbor 0, etc. - int priority; //!< the blending priority for this influence - terrain_t terrain_id; //!< the terrain id of the influence -}; - -/** - * influences for one tile. - * as a tile has 8 adjacent and diagonal neighbors, - * the maximum number of influences is 8. - */ -struct influence_group { - int count; - terrain_t terrain_ids[8]; - struct influence data[8]; -}; - -/** - * one influence on another tile. - */ -struct neighbor_tile { - terrain_t terrain_id; - tile_state state; - int priority; -}; - -/** - * storage data for a single terrain tile. - */ -struct tile_data { - terrain_t terrain_id; - coord::tile pos{0, 0}; - int subtexture_id; - Texture *tex; - int priority; - int mask_id; - int blend_mode; - Texture *mask_tex; - tile_state state; -}; - -/** - * collection of drawing data for a single tile. - * because of influences, a maximum of 8+1 draws - * could be requested. - */ -struct tile_draw_data { - ssize_t count; - struct tile_data data[9]; -}; - -/** - * the complete render instruction collection for the terrain. - * this is passed to the renderer and will be drawn on screen. - */ -struct terrain_render_data { - std::vector tiles; - std::set> objects; -}; - -/** - * specification for all available - * tile types and blending data - */ -struct terrain_meta { - size_t terrain_id_count; - size_t blendmode_count; - - std::vector textures; - std::vector blending_masks; - - std::unique_ptr terrain_id_priority_map; - std::unique_ptr terrain_id_blendmode_map; - - std::unique_ptr influences_buf; -}; - -/** - * the terrain class is the main top-management interface - * for dealing with cost-benefit analysis to maximize company profits. - * - * actually this is just the entrypoint and container for the terrain chunks. - */ -class Terrain { -public: - Terrain(terrain_meta *meta, bool is_infinite); - ~Terrain(); - - bool infinite; //!< chunks are automagically created as soon as they are referenced - - // TODO: finite terrain limits - // TODO: non-square shaped terrain bounds - - /** - * returns a list of all referenced chunks - */ - std::vector used_chunks() const; - - /** - * fill the terrain with given terrain_id values. - * @returns whether the data filled on the terrain was cut because of - * the terrains size limit. - */ - bool fill(const int *data, const coord::tile_delta &size); - - /** - * Attach a chunk to the terrain, to a given position. - * - * @param new_chunk The chunk to be attached - * @param position The chunk position where the chunk will be placed - * @param manually_created Was this chunk created manually? If true, it will not be free'd automatically - */ - void attach_chunk(TerrainChunk *new_chunk, const coord::chunk &position, bool manual = true); - - /** - * get a terrain chunk by a given chunk position. - * - * @return the chunk if exists, nullptr else - */ - TerrainChunk *get_chunk(const coord::chunk &position); - - /** - * get a terrain chunk by a given tile position. - * - * @return the chunk it exists, nullptr else - */ - TerrainChunk *get_chunk(const coord::tile &position); - - /** - * get or create a terrain chunk for a given chunk position. - * - * @return the (maybe newly created) chunk - */ - TerrainChunk *get_create_chunk(const coord::chunk &position); - - /** - * get or create a terrain chunk for a given tile position. - * - * @return the (maybe newly created) chunk - */ - TerrainChunk *get_create_chunk(const coord::tile &position); - - /** - * return tile data for the given position. - * - * the only reason the chunks exist, is because of this data. - */ - TileContent *get_data(const coord::tile &position); - - /** - * an object which contains the given point, null otherwise - */ - TerrainObject *obj_at_point(const coord::phys3 &point); - - /** - * get the neighbor chunks of a given chunk. - * - * - * chunk neighbor ids: - * 0 / <- ne - * 7 1 - * 6 @ 2 - * 5 3 - * 4 \ <- se - * - * ne se - * 0: 1 -1 - * 1: 1 0 - * 2: 1 1 - * 3: 0 1 - * 4: -1 1 - * 5: -1 0 - * 6: -1 -1 - * 7: 0 -1 - * - * @param position: the position of the center chunk. - */ - struct chunk_neighbors get_chunk_neighbors(const coord::chunk &position); - - /** - * return the subtexture offset id for a given tile position. - * the maximum offset is determined by the atlas size. - * - * this function returns always the right value, so that neighbor tiles - * of the same terrain (like grass-grass) are matching (without blendomatic). - * -> e.g. grass only map. - */ - unsigned get_subtexture_id(const coord::tile &pos, unsigned atlas_size); - - /** - * checks the creation state and premissions of a given tile position. - */ - tile_state check_tile(const coord::tile &position); - - /** - * checks whether the given tile position is allowed to exist on this terrain. - */ - // TODO: rename to is_tile_position_valid - bool check_tile_position(const coord::tile &position); - - /** - * validate whether the given terrain id is available. - */ - bool validate_terrain(terrain_t terrain_id); - - /** - * validate whether the given mask id is available. - */ - bool validate_mask(ssize_t mask_id); - - /** - * return the blending priority for a given terrain id. - */ - int priority(terrain_t terrain_id); - - /** - * return the blending mode/blendomatic mask set for a given terrain id. - */ - int blendmode(terrain_t terrain_id); - - /** - * get the terrain texture for a given terrain id. - */ - Texture *texture(terrain_t terrain_id); - - /** - * get the blendomatic mask with the given mask id. - */ - Texture *blending_mask(ssize_t mask_id); - - /** - * return the blending mode id for two given neighbor ids. - */ - int get_blending_mode(terrain_t base_id, terrain_t neighbor_id); - - /** - * draw the currently visible terrain area on screen. - * @param display: the display where the terrain should be drawn to. - */ - void draw(presenter::LegacyDisplay *display, RenderOptions *settings); - - /** - * create the drawing instruction data. - * - * created draw data according to the given tile boundaries. - * - * - * @param ab: upper left tile - * @param cd: upper right tile - * @param ef: lower right tile - * @param gh: lower left tile - * - * @returns a drawing instruction struct that contains all information for rendering - */ - struct terrain_render_data create_draw_advice(const coord::tile &ab, - const coord::tile &cd, - const coord::tile &ef, - const coord::tile &gh, - bool blending_enabled); - - /** - * create rendering and blending information for a single tile on the terrain. - */ - struct tile_draw_data create_tile_advice(coord::tile position, bool blending_enabled); - - /** - * gather neighbors of a given base tile. - * - * @param basepos: the base position, around which the neighbors will be fetched - * @param neigh_tiles: the destination buffer where the neighbors will be stored - * @param influences_by_terrain_id: influence buffer that is reset in the same step - */ - void get_neighbors(coord::tile basepos, - struct neighbor_tile *neigh_tiles, - struct influence *influences_by_terrain_id); - - /** - * look at neighbor tiles around the base_tile, and store the influence bits. - * - * @param base_tile: the base tile for which influences are calculated - * @param neigh_tiles: the neigbors of base_tile - * @param influences_by_terrain_id: influences will be stored to this buffer, as bitmasks - * @returns an influence group that describes the maximum 8 possible influences on the base_tile - */ - struct influence_group calculate_influences(struct tile_data *base_tile, - struct neighbor_tile *neigh_tiles, - struct influence *influences_by_terrain_id); - - /** - * calculate blending masks for a given tile position. - * - * @param position: the base tile position, for which the masks are calculated - * @param tile_data: the buffer where the created drawing layers will be stored in - * @param influences: the buffer where calculated influences were stored to - * - * @see calculate_influences - */ - void calculate_masks(coord::tile position, - struct tile_draw_data *tile_data, - struct influence_group *influences); - -private: - /** - * terrain meta data - */ - terrain_meta *meta; - - /** - * maps chunk coordinates to chunks. - */ - std::unordered_map chunks; -}; - -} // namespace openage diff --git a/libopenage/terrain/terrain_chunk.cpp b/libopenage/terrain/terrain_chunk.cpp deleted file mode 100644 index 46d877175e..0000000000 --- a/libopenage/terrain/terrain_chunk.cpp +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright 2013-2023 the openage authors. See copying.md for legal info. - -#include "terrain_chunk.h" - -#include - -#include "../coord/phys.h" -#include "../coord/pixel.h" -#include "../coord/tile.h" -#include "../error/error.h" -#include "../legacy_engine.h" -#include "../log/log.h" -#include "../texture.h" -#include "../util/misc.h" - -#include "terrain.h" -#include "terrain_object.h" - -namespace openage { - - -TerrainChunk::TerrainChunk() : - manually_created{true} { - this->tile_count = std::pow(chunk_size, 2); - - // the data array for this chunk. - // each element describes the tile data. - this->data = new TileContent[this->tile_count]; - - // initialize all neighbors as nonexistant - for (int i = 0; i < 8; i++) { - this->neighbors.neighbor[i] = nullptr; - } - - log::log(MSG(dbg) << "Terrain chunk created: " - << "size=" << chunk_size << ", " - << "tiles=" << this->tile_count); -} - - -TerrainChunk::~TerrainChunk() { - delete[] this->data; -} - -TileContent *TerrainChunk::get_data(coord::tile abspos) { - return this->get_data(abspos.get_pos_on_chunk()); -} - -TileContent *TerrainChunk::get_data(coord::tile_delta pos) { - return this->get_data(this->tile_position(pos)); -} - -TileContent *TerrainChunk::get_data(size_t pos) { - return &this->data[pos]; -} - -TileContent *TerrainChunk::get_data_neigh(coord::tile_delta pos) { - // determine the neighbor id by the given position - int neighbor_id = this->neighbor_id_by_pos(pos); - - // if the location is not on the current chunk, the neighbor id is != -1 - if (neighbor_id != -1) { - // get the chunk where the requested neighbor tile lies on. - TerrainChunk *neigh_chunk = this->neighbors.neighbor[neighbor_id]; - - // this neighbor does not exist, so the tile does not exist. - if (neigh_chunk == nullptr) { - return nullptr; - } - - // get position of tile on neighbor - size_t pos_on_neighbor = this->tile_position_neigh(pos); - - return neigh_chunk->get_data(pos_on_neighbor); - } - // the position lies on the current chunk. - else { - return this->get_data(pos); - } -} - -/* - * get the chunk neighbor id by a given position not lying on this chunk. - * - * neighbor ids: - * - * ne - * -- - * /| - * 0 - * 7 1 - * 6 @ 2 - * 5 3 - * 4 - * \| - * -- - * se - */ -int TerrainChunk::neighbor_id_by_pos(coord::tile_delta pos) { - int neigh_id = -1; - - if (pos.ne < 0) { - if (pos.se < 0) { - neigh_id = 6; - } - else if (pos.se >= (ssize_t)chunk_size) { - neigh_id = 4; - } - else { - neigh_id = 5; - } - } - else if (pos.ne >= (ssize_t)chunk_size) { - if (pos.se < 0) { - neigh_id = 0; - } - else if (pos.se >= (ssize_t)chunk_size) { - neigh_id = 2; - } - else { - neigh_id = 1; - } - } - else { - if (pos.se < 0) { - neigh_id = 7; - } - else if (pos.se >= (ssize_t)chunk_size) { - neigh_id = 3; - } - else { - neigh_id = -1; - } - } - return neigh_id; -} - -/* - * calculates the memory position of a given tile location. - * - * give this function isometric coordinates, it returns the tile index. - * - * # is a single terrain tile: - * - * 3 - * 2 # - * 1 # # - * ne= 0 # * # - * # # # # - * se= 0 # # # - * 1 # # - * 2 # - * 3 - * - * for example, * is at position (2, 1) - * the returned index would be 6 (count for each ne row, starting at se=0) - */ -size_t TerrainChunk::tile_position(coord::tile_delta pos) { - if (this->neighbor_id_by_pos(pos) != -1) { - throw Error(MSG(err) << "Tile " - "(" - << pos.ne << ", " << pos.se << ") " - "has been requested, but is not part of this chunk."); - } - - return pos.se * chunk_size + pos.ne; -} - -size_t TerrainChunk::tile_position_neigh(coord::tile_delta pos) { - // get position of tile on neighbor - pos.ne = util::mod(pos.ne); - pos.se = util::mod(pos.se); - - return pos.se * chunk_size + pos.ne; -} - -size_t TerrainChunk::get_tile_count() { - return this->tile_count; -} - -size_t TerrainChunk::get_size() { - return chunk_size; -} - -void TerrainChunk::set_terrain(Terrain *parent) { - this->terrain = parent; -} - -} // namespace openage diff --git a/libopenage/terrain/terrain_chunk.h b/libopenage/terrain/terrain_chunk.h deleted file mode 100644 index 0fd5cceecd..0000000000 --- a/libopenage/terrain/terrain_chunk.h +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2013-2018 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include - -#include "../coord/pixel.h" -#include "../coord/tile.h" -#include "../texture.h" -#include "../util/file.h" - -namespace openage { - -class Terrain; -class TerrainChunk; -class TileContent; -class TerrainObject; - - -/** -the number of tiles per direction on a chunk -*/ -constexpr size_t chunk_size = 16; - -/** -adjacent neighbors of a chunk. - -neighbor ids: - 0 - 7 1 - 6 @ 2 - 5 3 - 4 -*/ -struct chunk_neighbors { - TerrainChunk *neighbor[8]; -}; - -/** -terrain chunk class represents one chunk of the the drawn terrain. -*/ -class TerrainChunk { -public: - TerrainChunk(); - ~TerrainChunk(); - - /** - * stores the length for one chunk side. - */ - size_t size; - - /** - * number of tiles on that chunk (this->size^2) - */ - size_t tile_count; - - /** - * stores the chunk data, one tile_content struct for each tile. - */ - TileContent *data; - - /** - * the terrain to which this chunk belongs to. - */ - Terrain *terrain; - - /** - * the 8 neighbors this chunk has. - */ - chunk_neighbors neighbors; - - /** - * draws the terrain chunk on screen. - * - * @param chunk_pos the chunk position where it will be drawn - */ - void draw(coord::chunk chunk_pos); - - /** - * get tile data by absolute coordinates. - */ - TileContent *get_data(coord::tile abspos); - - /** - * get tile data by coordinates that are releative to this chunk. - */ - TileContent *get_data(coord::tile_delta pos); - - /** - * get tile data by memory position. - */ - TileContent *get_data(size_t pos); - - /** - * get the tile data a given tile position relative to this chunk. - * - * also queries neighbors if the position is not on this chunk. - */ - TileContent *get_data_neigh(coord::tile_delta pos); - - int neighbor_id_by_pos(coord::tile_delta pos); - - size_t tile_position(coord::tile_delta pos); - size_t tile_position_neigh(coord::tile_delta pos); - size_t get_tile_count(); - - size_t tiles_in_row(unsigned int row); - size_t get_size(); - - void set_terrain(Terrain *parent); - - bool manually_created; -}; - -} // namespace openage diff --git a/libopenage/terrain/terrain_object.cpp b/libopenage/terrain/terrain_object.cpp deleted file mode 100644 index fdcf611ae3..0000000000 --- a/libopenage/terrain/terrain_object.cpp +++ /dev/null @@ -1,481 +0,0 @@ -// Copyright 2013-2023 the openage authors. See copying.md for legal info. - -#include "terrain_object.h" - -#include -#include - -#include "../coord/phys.h" -#include "../coord/pixel.h" -#include "../coord/tile.h" -#include "../error/error.h" -#include "../legacy_engine.h" -#include "../texture.h" -#include "../unit/unit.h" - -#include "terrain.h" -#include "terrain_chunk.h" -#include "terrain_outline.h" - -namespace openage { - -TerrainObject::TerrainObject(Unit &u) : - unit(u), - passable{[](const coord::phys3 &) -> bool { return true; }}, - draw{[](const LegacyEngine & /*e*/) {}}, - state{object_state::removed}, - occupied_chunk_count{0}, - parent{nullptr} { -} - -TerrainObject::~TerrainObject() { - // remove all connections from terrain - this->unit.log(MSG(dbg) << "Cleanup terrain object"); - this->remove(); -} - -bool TerrainObject::is_floating() const { - // if parent is floating then all children also are - if (this->parent && this->parent->is_floating()) { - return true; - } - return this->state == object_state::floating; -} - -bool TerrainObject::is_placed() const { - // if object has a parent it must be placed - if (this->parent && !this->parent->is_placed()) { - return false; - } - return this->state == object_state::placed || this->state == object_state::placed_no_collision; -} - - -bool TerrainObject::check_collisions() const { - // if object has a parent it must be placed - if (this->parent && !this->parent->is_placed()) { - return false; - } - return this->state == object_state::placed; -} - -void TerrainObject::draw_outline(const coord::CoordManager &coord) const { - this->outline_texture->draw(coord, this->pos.draw); -} - -bool TerrainObject::place(object_state init_state) { - if (this->state == object_state::removed) { - throw Error(MSG(err) << "Building cannot change state with no position"); - } - - // remove any other floating objects - // which intersect with the new placement - // if non-floating objects are on the foundation - // then this placement will fail - for (coord::tile temp_pos : tile_list(this->pos)) { - std::vector to_remove; - TerrainChunk *chunk = this->get_terrain()->get_chunk(temp_pos); - - if (chunk == nullptr) { - continue; - } - - for (auto obj : chunk->get_data(temp_pos)->obj) { - // ignore self and annexes of self - if (obj != this && obj->get_parent() != this) { - if (obj->is_floating()) { - // floating objects get removed - to_remove.push_back(obj); - } - else if (obj->check_collisions()) { - // solid objects obstruct placement - return false; - } - } - } - - // all obstructing objects get deleted - for (auto remove_obj : to_remove) { - remove_obj->unit.location = nullptr; - } - } - - // set new state - this->state = init_state; - return true; -} - -bool TerrainObject::place(const std::shared_ptr &t, coord::phys3 &position, object_state init_state) { - if (this->state != object_state::removed) { - throw Error(MSG(err) << "This object has already been placed."); - } - else if (init_state == object_state::removed) { - throw Error(MSG(err) << "Cannot place an object with removed state."); - } - - // use passiblity test - if (not this->passable(position)) { - return false; - } - - // place on terrain - this->place_unchecked(t, position); - - // set state - this->state = init_state; - return true; -} - -bool TerrainObject::move(coord::phys3 &position) { - if (this->state == object_state::removed) { - return false; - } - auto old_state = this->state; - - // TODO should do outside of this function - bool can_move = this->passable(position); - if (can_move) { - this->remove(); - this->place_unchecked(this->get_terrain(), position); - this->state = old_state; - } - return can_move; -} - -void TerrainObject::remove() { - // remove all children first - for (auto &c : this->children) { - c->remove(); - } - this->children.clear(); - - if (this->occupied_chunk_count == 0 || this->state == object_state::removed) { - return; - } - - for (coord::tile temp_pos : tile_list(this->pos)) { - TerrainChunk *chunk = this->get_terrain()->get_chunk(temp_pos); - - if (chunk == nullptr) { - continue; - } - - auto &v = chunk->get_data(temp_pos.get_pos_on_chunk())->obj; - auto position_it = std::remove_if( - std::begin(v), - std::end(v), - [this](TerrainObject *obj) { - return this == obj; - }); - v.erase(position_it, std::end(v)); - } - - this->occupied_chunk_count = 0; - this->state = object_state::removed; -} - -void TerrainObject::set_ground(int id, int additional) { - if (not this->is_placed()) { - throw Error(MSG(err) << "Setting ground for object that is not placed yet."); - } - - coord::tile temp_pos = this->pos.start; - temp_pos.ne -= additional; - temp_pos.se -= additional; - while (temp_pos.ne < this->pos.end.ne + additional) { - while (temp_pos.se < this->pos.end.se + additional) { - TerrainChunk *chunk = this->get_terrain()->get_chunk(temp_pos); - - if (chunk == nullptr) { - continue; - } - - chunk->get_data(temp_pos.get_pos_on_chunk())->terrain_id = id; - temp_pos.se++; - } - temp_pos.se = this->pos.start.se - additional; - temp_pos.ne++; - } -} - -const TerrainObject *TerrainObject::get_parent() const { - return this->parent; -} - -std::vector TerrainObject::get_children() const { - // TODO: a better performing way of doing this - // for example accept a lambda to use for each element - // or maintain a duplicate class field for raw pointers - - std::vector result; - for (auto &obj : this->children) { - result.push_back(obj.get()); - } - return result; -} - -bool TerrainObject::operator<(const TerrainObject &other) { - if (this == &other) { - return false; - } - - auto this_ne = this->pos.draw.ne; - auto this_se = this->pos.draw.se; - auto other_ne = other.pos.draw.ne; - auto other_se = other.pos.draw.se; - - auto this_ypos = this_ne - this_se; - auto other_ypos = other_ne - other_se; - - if (this_ypos < other_ypos) { - return false; - } - else if (this_ypos == other_ypos) { - if (this_ne > other_ne) { - return false; - } - else if (this_ne == other_ne) { - return this_se > other_se; - } - } - - return true; -} - -void TerrainObject::place_unchecked(const std::shared_ptr &t, coord::phys3 &position) { - // storing the position: - this->pos = this->get_range(position, *t); - this->terrain = t; - this->occupied_chunk_count = 0; - - bool chunk_known = false; - - - // set pointers to this object on each terrain tile - // where the building will stand and block the ground - for (coord::tile temp_pos : tile_list(this->pos)) { - TerrainChunk *chunk = this->get_terrain()->get_chunk(temp_pos); - - if (chunk == nullptr) { - continue; - } - - for (int c = 0; c < this->occupied_chunk_count; c++) { - if (this->occupied_chunk[c] == chunk) { - chunk_known = true; - } - } - - if (not chunk_known) { - this->occupied_chunk[this->occupied_chunk_count] = chunk; - this->occupied_chunk_count += 1; - } - else { - chunk_known = false; - } - - chunk->get_data(temp_pos.get_pos_on_chunk())->obj.push_back(this); - } -} - -SquareObject::SquareObject(Unit &u, coord::tile_delta foundation_size) : - SquareObject(u, foundation_size, square_outline(foundation_size)) { -} - -SquareObject::SquareObject(Unit &u, coord::tile_delta foundation_size, std::shared_ptr out_tex) : - TerrainObject(u), - size(foundation_size) { - this->outline_texture = out_tex; -} - -SquareObject::~SquareObject() = default; - -tile_range SquareObject::get_range(const coord::phys3 &pos, const Terrain &terrain) const { - return building_center(pos, this->size, terrain); -} - -coord::phys_t SquareObject::from_edge(const coord::phys3 &point) const { - // clamp between start and end - coord::phys2 start_phys = this->pos.start.to_phys2(); - coord::phys2 end_phys = this->pos.end.to_phys2(); - - coord::phys_t cx = std::max(start_phys.ne, std::min(end_phys.ne, point.ne)); - coord::phys_t cy = std::max(start_phys.se, std::min(end_phys.se, point.se)); - - // distance to clamped point - coord::phys_t dx = point.ne - cx; - coord::phys_t dy = point.se - cy; - return std::hypot(dx, dy); -} - -coord::phys3 SquareObject::on_edge(const coord::phys3 &angle, coord::phys_t /* extra */) const { - // clamp between start and end - // TODO extra is unused - coord::phys2 start_phys = this->pos.start.to_phys2(); - coord::phys2 end_phys = this->pos.end.to_phys2(); - coord::phys_t cx = std::max(start_phys.ne, std::min(end_phys.ne, angle.ne)); - coord::phys_t cy = std::max(start_phys.se, std::min(end_phys.se, angle.se)); - - // todo use extra distance - return coord::phys3{cx, cy, 0}; -} - -bool SquareObject::contains(const coord::phys3 &other) const { - coord::tile other_tile = other.to_tile3().to_tile(); - - for (coord::tile check_pos : tile_list(this->pos)) { - if (check_pos == other_tile) { - return true; - } - } - return false; -} - -bool SquareObject::intersects(const TerrainObject &other, const coord::phys3 &position) const { - if (const auto *sq = dynamic_cast(&other)) { - auto terrain_ptr = this->terrain.lock(); - if (not terrain_ptr) { - throw Error{ERR << "object is not associated to a valid terrain"}; - } - - tile_range rng = this->get_range(position, *terrain_ptr); - return this->pos.end.ne < rng.start.ne - || rng.end.ne < sq->pos.start.ne - || rng.end.se < sq->pos.start.se - || rng.end.se < sq->pos.start.se; - } - else if (const auto *rad = dynamic_cast(&other)) { - auto terrain_ptr = this->terrain.lock(); - if (not terrain_ptr) { - throw Error{ERR << "object is not associated to a valid terrain"}; - } - // clamp between start and end - tile_range rng = this->get_range(position, *terrain_ptr); - coord::phys2 start_phys = rng.start.to_phys2(); - coord::phys2 end_phys = rng.end.to_phys2(); - coord::phys_t cx = std::max(start_phys.ne, std::min(end_phys.ne, rad->pos.draw.ne)); - coord::phys_t cy = std::max(start_phys.se, std::min(end_phys.se, rad->pos.draw.se)); - - // distance to square object base - coord::phys_t dx = rad->pos.draw.ne - cx; - coord::phys_t dy = rad->pos.draw.se - cy; - return std::hypot(dx, dy) < rad->phys_radius.to_double(); - } - return false; -} - -coord::phys_t SquareObject::min_axis() const { - return std::min(this->size.ne, this->size.se); -} - -RadialObject::RadialObject(Unit &u, float rad) : - RadialObject(u, rad, radial_outline(rad)) { -} - -RadialObject::RadialObject(Unit &u, float rad, std::shared_ptr out_tex) : - TerrainObject(u), - phys_radius(rad) { - this->outline_texture = out_tex; -} - -RadialObject::~RadialObject() = default; - -tile_range RadialObject::get_range(const coord::phys3 &pos, const Terrain & /*terrain*/) const { - tile_range result; - - // create bounds - coord::phys3 p_start = pos, p_end = pos; - p_start.ne -= this->phys_radius; - p_start.se -= this->phys_radius; - p_end.ne += this->phys_radius; - p_end.se += this->phys_radius; - - // set result - result.start = p_start.to_tile3().to_tile(); - result.end = p_end.to_tile3().to_tile() + coord::tile_delta{1, 1}; - result.draw = pos; - return result; -} - -coord::phys_t RadialObject::from_edge(const coord::phys3 &point) const { - return std::max( - coord::phys_t(point.to_phys2().distance(this->pos.draw.to_phys2())) - this->phys_radius, - static_cast(0)); -} - -coord::phys3 RadialObject::on_edge(const coord::phys3 &angle, coord::phys_t extra) const { - return this->pos.draw + (angle - this->pos.draw).to_phys2().normalize((this->phys_radius + extra).to_double()).to_phys3(); -} - -bool RadialObject::contains(const coord::phys3 &other) const { - return this->pos.draw.to_phys2().distance(other.to_phys2()) < this->phys_radius.to_double(); -} - -bool RadialObject::intersects(const TerrainObject &other, const coord::phys3 &position) const { - if (const auto *sq = dynamic_cast(&other)) { - return sq->from_edge(position) < this->phys_radius; - } - else if (const auto *rad = dynamic_cast(&other)) { - return position.to_phys2().distance(rad->pos.draw.to_phys2()) < (this->phys_radius + rad->phys_radius).to_double(); - } - return false; -} - -coord::phys_t RadialObject::min_axis() const { - return this->phys_radius * 2; -} - -std::vector tile_list(const tile_range &rng) { - std::vector tiles; - - coord::tile check_pos = rng.start; - while (check_pos.ne < rng.end.ne) { - while (check_pos.se < rng.end.se) { - tiles.push_back(check_pos); - check_pos.se += 1; - } - check_pos.se = rng.start.se; - check_pos.ne += 1; - } - - // a case when the objects radius is zero - if (tiles.empty()) { - tiles.push_back(rng.start); - } - return tiles; -} - -tile_range building_center(coord::phys3 west, coord::tile_delta size, const Terrain &terrain) { - tile_range result; - - // TODO it should be possible that the building is placed on any position, - // not just tile positions. - result.start = west.to_tile(); - result.end = result.start + size; - - coord::phys2 draw_pos = result.start.to_phys2(); - - draw_pos.ne += coord::phys_t(size.ne / 2.0f); - draw_pos.se += coord::phys_t(size.se / 2.0f); - - result.draw = draw_pos.to_phys3(terrain); - return result; -} - -bool complete_building(Unit &u) { - if (u.has_attribute(attr_type::building)) { - auto &build = u.get_attribute(); - build.completed = 1.0f; - - // set ground under a completed building - auto target_location = u.location.get(); - bool placed_ok = target_location->place(build.completion_state); - if (placed_ok) { - target_location->set_ground(build.foundation_terrain, 0); - } - return placed_ok; - } - return false; -} - -} // namespace openage diff --git a/libopenage/terrain/terrain_object.h b/libopenage/terrain/terrain_object.h deleted file mode 100644 index 4b6f76097b..0000000000 --- a/libopenage/terrain/terrain_object.h +++ /dev/null @@ -1,351 +0,0 @@ -// Copyright 2013-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include - -#include "../coord/phys.h" -#include "../coord/tile.h" - -namespace openage { - -class LegacyEngine; -class Terrain; -class TerrainChunk; -class Texture; -class Unit; - -/** - * only placed will enable collision checks - */ -enum class object_state { - removed, - floating, - placed, - placed_no_collision -}; - -/** - * A rectangle or square of tiles which is the minimim - * space to fit the units foundation or radius - * the end tile will have ne and se values greater or equal to - * the start tile - */ -struct tile_range { - coord::tile start{0, 0}; - coord::tile end{0, 0}; // start <= end - coord::phys3 draw{0, 0, 0}; // gets used as center point of radial objects -}; - -/** - * get all tiles in the tile range -- useful for iterating - * returns a flat list of tiles between the rectangle enclosed - * by the tile_range start and end tiles - */ -std::vector tile_list(const tile_range &rng); - -/** - * given the west most point of a building foundation and the tile_delta - * size of the foundation, this will return the tile range covered by the base, - * which includes start and end tiles, and phys3 center point (used for drawing) - */ -tile_range building_center(coord::phys3 west, coord::tile_delta size, const Terrain &terrain); - -/** - * sets a building to a fully completed state - */ -bool complete_building(Unit &); - -/** - * Base class for map location types which include square tile aligned - * positions and radial positions This enables two inheriting classes - * SquareObject and RadialObject to specify different areas of the map - * - * All TerrainObjects are owned by a Unit, to construct TerrainObjects, - * use the make_location function on any Unit - * - * This class allows intersection testing between two TerrainObjects to - * cover all cases of intersection (water, land or flying objects) the units - * lambda function is used which takes a tile and returns a bool value of - * whether that tile can be passed by this - * - * The name of this class is likely to change to TerrainBase or TerrainSpace - */ -class TerrainObject : public std::enable_shared_from_this { -public: - TerrainObject(Unit &u); - TerrainObject(const TerrainObject &) = delete; // disable copy constructor - TerrainObject(TerrainObject &&) = delete; // disable move constructor - virtual ~TerrainObject(); - - /** - * the range of tiles which are covered by this object - */ - tile_range pos; - - /* - * unit which is inside this base - * used to find the unit from user actions - * - * every terrain object should contain a single unit - */ - Unit &unit; - - /** - * is the object a floating outline -- it is only an indicator - * of where a building will be built, but not yet started building - * and does not affect any collisions - */ - bool is_floating() const; - - /** - * returns true if this object has been placed. this indicates that the object has a position and exists - * on the map, floating buildings are not considered placed as they are only an indicator - * for where something can begin construction - */ - bool is_placed() const; - - /** - * should this object be tested for collisions, which decides whether another object is allowed - * to overlap the location of this object. arrows and decaying objects will return false - */ - bool check_collisions() const; - - /** - * decide which terrains this object can be on - * this function should be true if given a valid position for the object - */ - std::function passable; - - /** - * specifies content to be drawn - */ - std::function draw; - - /** - * draws outline of this terrain space in current position - */ - void draw_outline(const coord::CoordManager &coord) const; - - /** - * changes the placement state of this object keeping the existing - * position. this is useful for upgrading a floating building to a placed state - */ - bool place(object_state init_state); - - /** - * binds the TerrainObject to a certain TerrainChunk. - * - * @param terrain: the terrain where the object will be placed onto. - * @param pos: (tile) position of the (nw,sw) corner - * @param init_state should be floating, placed or placed_no_collision - * @returns true when the object was placed, false when it did not fit at pos. - */ - bool place(const std::shared_ptr &t, coord::phys3 &pos, object_state init_state); - - /** - * moves the object -- returns false if object cannot be moved here - */ - bool move(coord::phys3 &pos); - - /** - * remove this TerrainObject from the terrain chunks. - */ - void remove(); - - /** - * sets all the ground below the object to a terrain id. - * - * @param id: the terrain id to which the ground is set - * @param additional: amount of additional space arround the building - */ - void set_ground(int id, int additional = 0); - - - /** - * appends new annex location for this object - * - * this does not replace any existing annex - */ - template - TerrainObject *make_annex(Arg... args) { - this->children.emplace_back(std::unique_ptr(new T(this->unit, args...))); - auto &annex_ptr = this->children.back(); - annex_ptr->parent = this; - return annex_ptr.get(); - } - - /** - * Returns the parent terrain object, - * if null the object has no parent which is - * the case for most objects - * - * objects with a parent are owned by that object - * and to be placed on the map the parent must also be placed - */ - const TerrainObject *get_parent() const; - - /** - * Returns a list of child objects, this is the inverse of the - * get_parent() function - * - * TODO: this does not perform optimally and is likely to change - */ - std::vector get_children() const; - - /* - * terrain this object was placed on - */ - std::shared_ptr get_terrain() const { - return terrain.lock(); - } - - /** - * comparison for TerrainObjects. - * - * sorting for vertical placement. - * by using this order algorithm, the overlapping order - * is optimal so the objects can be drawn in correct order. - */ - bool operator<(const TerrainObject &other); - - /** - * returns the range of tiles covered if the object was in the given pos - * @param pos the position to find a range for - */ - virtual tile_range get_range(const coord::phys3 &pos, const Terrain &terrain) const = 0; - - /** - * how far is a point from the edge of this object - */ - virtual coord::phys_t from_edge(const coord::phys3 &point) const = 0; - - /** - * get a position on the edge of this object - */ - virtual coord::phys3 on_edge(const coord::phys3 &angle, coord::phys_t extra = 0) const = 0; - - /** - * does this space contain a given point - */ - virtual bool contains(const coord::phys3 &other) const = 0; - - /** - * would this intersect with another object if it were positioned at the given point - */ - virtual bool intersects(const TerrainObject &other, const coord::phys3 &position) const = 0; - - /** - * the shortest line that can be placed across the objects center - */ - virtual coord::phys_t min_axis() const = 0; - -protected: - object_state state; - - std::weak_ptr terrain; - int occupied_chunk_count; - TerrainChunk *occupied_chunk[4]; - - /** - * annexes and grouped units - */ - TerrainObject *parent; - std::vector> children; - - /** - * texture for drawing outline - */ - std::shared_ptr outline_texture; - - /** - * placement function which does not check passibility - * used only when passibilty is already checked - * otherwise the place function should be used - * this does not modify the units placement state - */ - void place_unchecked(const std::shared_ptr &t, coord::phys3 &position); -}; - -/** - * terrain object class represents one immobile object on the map (building, trees, fish, ...). - * can only be constructed by unit->make_location(...) - */ -class SquareObject : public TerrainObject { -public: - virtual ~SquareObject(); - - - /** - * tile size of this objects base - */ - const coord::tile_delta size; - - /** - * calculate object start and end positions. - * - * @param pos: the center position of the building - * - * set the center position to "middle", - * start_pos is % and end_pos = & - * - * for a building, the # tile will be "the clicked one": - * @ @ @ - * @ @ @ @ %# & - * @ @ @ % # & @ - * % # @ & @ @ - * @ @ @ @ - * @ @ - * @ - */ - tile_range get_range(const coord::phys3 &pos, const Terrain &terrain) const override; - - coord::phys_t from_edge(const coord::phys3 &point) const override; - coord::phys3 on_edge(const coord::phys3 &angle, coord::phys_t extra = 0) const override; - bool contains(const coord::phys3 &other) const override; - bool intersects(const TerrainObject &other, const coord::phys3 &position) const override; - coord::phys_t min_axis() const override; - -private: - SquareObject(Unit &u, coord::tile_delta foundation_size); - SquareObject(Unit &u, coord::tile_delta foundation_size, std::shared_ptr out_tex); - - - friend class TerrainObject; - friend class Unit; -}; - -/** - * Represents circular shaped objects (movable game units) - * can only be constructed by unit->make_location(...) - */ -class RadialObject : public TerrainObject { -public: - virtual ~RadialObject(); - - /** - * radius of this cirular space - */ - const coord::phys_t phys_radius; - - /** - * finds the range covered if the object was in a position - */ - tile_range get_range(const coord::phys3 &pos, const Terrain &terrain) const override; - - coord::phys_t from_edge(const coord::phys3 &point) const override; - coord::phys3 on_edge(const coord::phys3 &angle, coord::phys_t extra = 0) const override; - bool contains(const coord::phys3 &other) const override; - bool intersects(const TerrainObject &other, const coord::phys3 &position) const override; - coord::phys_t min_axis() const override; - -private: - RadialObject(Unit &u, float rad); - RadialObject(Unit &u, float rad, std::shared_ptr out_tex); - - friend class TerrainObject; - friend class Unit; -}; - -} // namespace openage diff --git a/libopenage/terrain/terrain_outline.cpp b/libopenage/terrain/terrain_outline.cpp deleted file mode 100644 index 6c20fd5983..0000000000 --- a/libopenage/terrain/terrain_outline.cpp +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2014-2019 the openage authors. See copying.md for legal info. - -#include -#include - -#include "../texture.h" -#include "terrain_outline.h" - -namespace openage { - -std::shared_ptr square_outline(coord::tile_delta foundation_size) { - int width = (foundation_size.ne + foundation_size.se) * 48; - int height = (foundation_size.ne + foundation_size.se) * 24; - - auto image_data = std::make_unique(width * height); - for (int i = 0; i < width; ++i) { - for (int j = 0; j < height; ++j) { - float w_percent = (float) abs(i - (width / 2)) / (float) (width / 2); - float h_percent = (float) abs(j - (height / 2)) / (float) (height / 2); - - // draw line where (w_percent + h_percent) == 1 - // line variable is in range 0.0 to 1.0 - float line = 1.0f - fabs(1.0f - fabs(h_percent + w_percent)); - unsigned char inten = 255 * pow(line, 16 * width/96); - image_data[i + j * width] = (inten << 24) | (inten << 16) | (inten << 8) | inten; - } - } - - return std::make_shared(width, height, std::move(image_data)); -} - -std::shared_ptr radial_outline(float radius) { - // additional pixels around the edge - int border = 4; - - // image size - int width = border + radius * 96 * 2; - int height = border + radius * 48 * 2; - int half_width = width / 2; - int half_height = height / 2; - - auto image_data = std::make_unique(width * height); - - for (int i = 0; i < width; ++i) { - for (int j = 0; j < height; ++j) { - float w_percent = (float) (border+i-half_width) / (float) (half_width-border); - float h_percent = (float) (border/2+j-half_height) / (float) (half_height-border/2); - - // line drawn where distance to image center == 1 - // line variable is in range 0.0 to 1.0 - float line = 1.0f - fabs(1.0f - std::hypot(w_percent, h_percent)); - unsigned char inten = 255 * pow(line, 32 * width/96); - image_data[i + j * width] = (inten << 24) | (inten << 16) | (inten << 8) | inten; - } - } - - return std::make_shared(width, height, std::move(image_data)); -} - -} // namespace openage diff --git a/libopenage/terrain/terrain_outline.h b/libopenage/terrain/terrain_outline.h deleted file mode 100644 index 8e66fadb6d..0000000000 --- a/libopenage/terrain/terrain_outline.h +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2014-2016 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -#include "../coord/tile.h" - -namespace openage { - -class Texture; - -/** - * Generate a isometric square outline texture - */ -std::shared_ptr square_outline(coord::tile_delta foundation_size); - -/** - * Generate a isometric circle outline texture - */ -std::shared_ptr radial_outline(float radius); - -} // namespace openage diff --git a/libopenage/terrain/terrain_search.cpp b/libopenage/terrain/terrain_search.cpp deleted file mode 100644 index 7e3d0db22f..0000000000 --- a/libopenage/terrain/terrain_search.cpp +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. - -#include - -#include "terrain.h" -#include "terrain_object.h" -#include "terrain_search.h" - -namespace openage { - -TerrainObject *find_near(const TerrainObject &start, - std::function found, - unsigned int search_limit) { - - auto terrain = start.get_terrain(); - auto tile = start.pos.draw.to_tile3().to_tile(); - TerrainSearch search(terrain, tile); - - for (unsigned int i = 0; i < search_limit; ++i) { - for (auto o : terrain->get_data(tile)->obj) { - - // invalid pointers are removed when the object is deleted - if (found(*o)) { - return o; - } - } - tile = search.next_tile(); - } - - return nullptr; -} - -TerrainObject *find_in_radius(const TerrainObject &start, - std::function found, - float radius) { - auto terrain = start.get_terrain(); - coord::tile start_tile = start.pos.draw.to_tile3().to_tile(); - TerrainSearch search(terrain, start_tile, radius); - - // next_tile will first return the starting tile, so we need to run it once. We also - // shouldn't discard this tile - if it isn't useful, ignore it in found - coord::tile tile = search.next_tile(); - do { - for (auto o : terrain->get_data(tile)->obj) { - if (found(*o)) { - return o; - } - } - tile = search.next_tile(); - // coord::tile doesn't have a != operator, so we need to use !(a==b) - } while (!(tile == start_tile)); - - return nullptr; -} - -TerrainSearch::TerrainSearch(std::shared_ptr t, coord::tile s) - : - TerrainSearch(t, s, .0f) { -} - -TerrainSearch::TerrainSearch(std::shared_ptr t, coord::tile s, float radius) - : - terrain{t}, - start(s), - previous_radius{.0f}, - max_radius{radius} { -} - -coord::tile TerrainSearch::start_tile() const { - return this->start; -} - -void TerrainSearch::reset() { - std::queue empty; - std::swap(this->tiles, empty); - this->visited.clear(); - this->tiles.push(this->start); - this->visited.insert(this->start); -} - -coord::tile TerrainSearch::next_tile() { - // conditions for returning to the initial tile - if (this->tiles.empty() || - (this->max_radius && this->previous_radius > this->max_radius)) { - this->reset(); - } - coord::tile result = this->tiles.front(); - this->tiles.pop(); - - for (auto i = 0; i < 4; ++i) { - auto to_add = result + neigh_tile[i]; - - // check not visited and tile is within map - if (this->visited.count(to_add) == 0 && terrain->get_data(to_add)) { - this->visited.insert(to_add); - this->tiles.push(to_add); - } - } - - this->previous_radius = std::hypot(result.ne - start.ne, result.se - start.se); - return result; -} - -} // namespace openage diff --git a/libopenage/terrain/terrain_search.h b/libopenage/terrain/terrain_search.h deleted file mode 100644 index 3221db31d2..0000000000 --- a/libopenage/terrain/terrain_search.h +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include -#include -#include -#include - -#include "../coord/tile.h" - -namespace openage { - -class Terrain; -class TerrainObject; - -TerrainObject *find_near(const TerrainObject &start, - std::function found, - unsigned int search_limit=500); -TerrainObject *find_in_radius(const TerrainObject &start, - std::function found, - float radius); - -constexpr coord::tile_delta const neigh_tile[] = { - {0, 1}, - {0, -1}, - {1, 0}, - {-1, 0} -}; - -/** - * searches outward from a point and returns nearby objects - * The state of the search is kept within the class, which allows - * a user to look at a limited number of tiles per update cycle - */ -class TerrainSearch { -public: - /** - * next_tile will cover all tiles on the map - */ - TerrainSearch(std::shared_ptr t, coord::tile s); - - /** - * next_tile will iterate over a range of tiles within a radius - */ - TerrainSearch(std::shared_ptr t, coord::tile s, float radius); - ~TerrainSearch() = default; - - /** - * the tile the search began on - */ - coord::tile start_tile() const; - - /** - * restarts the search from the start tile - */ - void reset(); - - /** - * returns all objects on the next tile - */ - coord::tile next_tile(); - -private: - const std::shared_ptr terrain; - const coord::tile start; - std::queue tiles; - std::unordered_set visited; - float previous_radius, max_radius; - -}; - -} // namespace openage diff --git a/libopenage/testing/testing.h b/libopenage/testing/testing.h index b8ae4f2946..83f0efaab9 100644 --- a/libopenage/testing/testing.h +++ b/libopenage/testing/testing.h @@ -1,4 +1,4 @@ -// Copyright 2014-2016 the openage authors. See copying.md for legal info. +// Copyright 2014-2025 the openage authors. See copying.md for legal info. #pragma once @@ -51,15 +51,42 @@ 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) { \ + } \ + catch (::openage::testing::TestError & e) { \ throw; \ - } catch (::openage::error::Error &e) { \ + } \ + catch (::openage::error::Error & e) { \ TESTFAILMSG("unexpected exception: " << e); \ } \ - } while (0) + } \ + 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, @@ -69,16 +96,19 @@ 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) { \ + } \ + catch (::openage::testing::TestError & e) { \ throw; \ - } catch (::openage::error::Error &e) { \ + } \ + catch (::openage::error::Error & e) { \ TESTFAILMSG("unexpected exception: " << e); \ } \ - } while (0) + } \ + while (0) /** @@ -89,13 +119,15 @@ bool fail(const log::message &msg); bool expr_has_thrown = false; \ try { \ expression; \ - } catch (::openage::error::Error &e) { \ + } \ + catch (::openage::error::Error & e) { \ expr_has_thrown = true; \ } \ if (not expr_has_thrown) { \ - TESTFAILMSG("no exception"); \ + TESTFAILMSG(__FILE__ << ":" << __LINE__ << ": no exception"); \ } \ - } while (0) + } \ + while (0) /** @@ -105,10 +137,13 @@ bool fail(const log::message &msg); do { \ try { \ expression; \ - } catch (::openage::error::Error &e) { \ - TESTFAILMSG("unexpected exception"); \ } \ - } while (0) + catch (::openage::error::Error & e) { \ + TESTFAILMSG(__FILE__ << ":" << __LINE__ << ": unexpected exception"); \ + } \ + } \ + while (0) -}} // openage::testing +} // namespace testing +} // namespace openage diff --git a/libopenage/testing/testlist.h b/libopenage/testing/testlist.h index d3da00e128..21ffcbe0eb 100644 --- a/libopenage/testing/testlist.h +++ b/libopenage/testing/testlist.h @@ -1,4 +1,4 @@ -// Copyright 2014-2017 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. #pragma once @@ -21,4 +21,5 @@ namespace testing { OAAPI void run_method(const std::string &name); -}} // openage::testing +} // namespace testing +} // namespace openage diff --git a/libopenage/texture.cpp b/libopenage/texture.cpp deleted file mode 100644 index ec5ebf2202..0000000000 --- a/libopenage/texture.cpp +++ /dev/null @@ -1,447 +0,0 @@ -// Copyright 2013-2019 the openage authors. See copying.md for legal info. - -#include "texture.h" - -#include -#include - -#include -#include - -#include "log/log.h" -#include "error/error.h" -#include "util/csv.h" -#include "coord/phys.h" - -namespace openage { - -// TODO: remove these global variables!!! -// definition of the shaders, -// they are "external" in the header. -namespace texture_shader { -shader::Program *program; -GLint texture, tex_coord; -} - -namespace teamcolor_shader { -shader::Program *program; -GLint texture, tex_coord; -GLint player_id_var, alpha_marker_var, player_color_var; -} - -namespace alphamask_shader { -shader::Program *program; -GLint base_texture, mask_texture, base_coord, mask_coord, show_mask; -} - -Texture::Texture(int width, int height, std::unique_ptr data) - : - use_metafile{false} { - ENSURE(glGenBuffers != nullptr, "gl not initialized properly"); - - this->w = width; - this->h = height; - this->buffer = std::make_unique(); - this->buffer->transferred = false; - this->buffer->texture_format_in = GL_RGBA8; - this->buffer->texture_format_out = GL_RGBA; - this->buffer->data = std::move(data); - this->subtextures.push_back({0, 0, this->w, this->h, this->w/2, this->h/2}); -} - -Texture::Texture(const util::Path &filename, bool use_metafile) - : - use_metafile{use_metafile}, - filename{filename} { - - // load the texture upon creation - this->load(); -} - -void Texture::load() { - // TODO: use libpng directly. - SDL_Surface *surface; - - // TODO: this will break if there is no native path. - // but then we need to load the image - // from the buffer provided by this->filename.open_r().read(). - - std::string native_path = this->filename.resolve_native_path(); - surface = IMG_Load(native_path.c_str()); - - if (!surface) { - throw Error( - MSG(err) << - "SDL_Image could not load texture from " - << this->filename << " (= " << native_path << "): " - << IMG_GetError() - ); - } else { - log::log(MSG(dbg) << "Texture has been loaded from " << native_path); - } - - this->buffer = std::make_unique(); - - // glTexImage2D format determination - switch (surface->format->BytesPerPixel) { - case 3: // RGB 24 bit - this->buffer->texture_format_in = GL_RGB8; - this->buffer->texture_format_out - = surface->format->Rmask == 0x000000ff - ? GL_RGB - : GL_BGR; - break; - case 4: // RGBA 32 bit - this->buffer->texture_format_in = GL_RGBA8; - this->buffer->texture_format_out - = surface->format->Rmask == 0x000000ff - ? GL_RGBA - : GL_BGRA; - break; - default: - throw Error(MSG(err) << - "Unknown texture bit depth for " << this->filename << ": " << - surface->format->BytesPerPixel << " bytes per pixel"); - - } - - this->w = surface->w; - this->h = surface->h; - - // temporary buffer for pixel data - this->buffer->transferred = false; - this->buffer->data = std::make_unique(this->w * this->h); - std::memcpy( - this->buffer->data.get(), - surface->pixels, - this->w * this->h * surface->format->BytesPerPixel - ); - SDL_FreeSurface(surface); - - if (use_metafile) { - // get subtexture information from the exported metainfo file - this->subtextures = util::read_csv_file( - filename.with_suffix(".docx") - ); - } - else { - // we don't have a subtexture description file. - // use the whole image as one texture then. - gamedata::subtexture s{0, 0, this->w, this->h, this->w/2, this->h/2}; - - this->subtextures.push_back(s); - } -} - -GLuint Texture::make_gl_texture(int iformat, int oformat, int w, int h, void *data) const { - // generate 1 texture handle - GLuint textureid; - glGenTextures(1, &textureid); - glBindTexture(GL_TEXTURE_2D, textureid); - - // sdl surface -> opengl texture - glTexImage2D( - GL_TEXTURE_2D, 0, - iformat, w, h, 0, - oformat, GL_UNSIGNED_BYTE, data - ); - - // settings for later drawing - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - return textureid; -} - -void Texture::load_in_glthread() const { - if (not this->buffer->transferred) { - this->buffer->id = this->make_gl_texture( - this->buffer->texture_format_in, - this->buffer->texture_format_out, - this->w, - this->h, - this->buffer->data.get() - ); - this->buffer->data = nullptr; - glGenBuffers(1, &this->buffer->vertbuf); - this->buffer->transferred = true; - } -} - - -void Texture::unload() { - glDeleteTextures(1, &this->buffer->id); - glDeleteBuffers(1, &this->buffer->vertbuf); -} - - -void Texture::reload() { - this->unload(); - this->load(); -} - - -Texture::~Texture() { - this->unload(); -} - - -void Texture::fix_hotspots(unsigned x, unsigned y) { - for (auto &subtexture : this->subtextures) { - subtexture.cx = x; - subtexture.cy = y; - } -} - - -void Texture::draw(const coord::CoordManager &mgr, const coord::camhud pos, - unsigned int mode, bool mirrored, - int subid, unsigned player) const { - this->draw(pos.to_viewport(mgr), mode, mirrored, subid, player, nullptr, -1); -} - - -void Texture::draw(const coord::CoordManager &mgr, coord::camgame pos, - unsigned int mode, bool mirrored, - int subid, unsigned player) const { - this->draw(pos.to_viewport(mgr), mode, mirrored, subid, player, nullptr, -1); -} - - -void Texture::draw(const coord::CoordManager &mgr, coord::phys3 pos, - unsigned int mode, bool mirrored, - int subid, unsigned player) const { - this->draw(pos.to_viewport(mgr), mode, mirrored, subid, player, nullptr, -1); -} - - -void Texture::draw(const coord::CoordManager &mgr, const Terrain &terrain, - coord::tile pos, unsigned int mode, int subid, - Texture *alpha_texture, int alpha_subid) const { - - // currently used for drawing terrain tiles. - this->draw(pos.to_viewport(mgr, terrain), mode, false, - subid, 0, alpha_texture, alpha_subid); -} - - -void Texture::draw(coord::viewport pos, - unsigned int mode, bool mirrored, - int subid, unsigned player, - Texture *alpha_texture, int alpha_subid) const { - - this->load_in_glthread(); - - glColor4f(1, 1, 1, 1); - - bool use_playercolors = false; - bool use_alphashader = false; - const gamedata::subtexture *mtx; - - int *pos_id, *texcoord_id, *masktexcoord_id; - - // is this texture drawn with an alpha mask? - if ((mode & ALPHAMASKED) && alpha_subid >= 0 && alpha_texture != nullptr) { - alphamask_shader::program->use(); - - // bind the alpha mask texture to slot 1 - glActiveTexture(GL_TEXTURE1); - glEnable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, alpha_texture->get_texture_id()); - - // get the alphamask subtexture (the blend mask!) - mtx = alpha_texture->get_subtexture(alpha_subid); - pos_id = &alphamask_shader::program->pos_id; - texcoord_id = &alphamask_shader::base_coord; - masktexcoord_id = &alphamask_shader::mask_coord; - use_alphashader = true; - } - // is this texure drawn with replaced pixels for team coloring? - else if (mode & PLAYERCOLORED) { - teamcolor_shader::program->use(); - - //set the desired player id in the shader - glUniform1i(teamcolor_shader::player_id_var, player); - pos_id = &teamcolor_shader::program->pos_id; - texcoord_id = &teamcolor_shader::tex_coord; - use_playercolors = true; - } - // mkay, we just draw the plain texture otherwise. - else { - texture_shader::program->use(); - pos_id = &texture_shader::program->pos_id; - texcoord_id = &texture_shader::tex_coord; - } - - glActiveTexture(GL_TEXTURE0); - glEnable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, this->buffer->id); - - const gamedata::subtexture *tx = this->get_subtexture(subid); - - int left, right, top, bottom; - - // coordinates where the texture will be drawn on screen. - bottom = pos.y - (tx->h - tx->cy); - top = bottom + tx->h; - - if (not mirrored) { - left = pos.x - tx->cx; - right = left + tx->w; - } else { - left = pos.x + tx->cx; - right = left - tx->w; - } - - // convert the texture boundaries to float - // these will be the vertex coordinates. - float leftf, rightf, topf, bottomf; - leftf = (float) left; - rightf = (float) right; - topf = (float) top; - bottomf = (float) bottom; - - // subtexture coordinates - // left, right, top and bottom bounds as coordinates - // these pick the requested area out of the big texture. - float txl, txr, txt, txb; - this->get_subtexture_coordinates(tx, &txl, &txr, &txt, &txb); - - float mtxl=0, mtxr=0, mtxt=0, mtxb=0; - if (use_alphashader) { - alpha_texture->get_subtexture_coordinates(mtx, &mtxl, &mtxr, &mtxt, &mtxb); - } - - // this array will be uploaded to the GPU. - // it contains all dynamic vertex data (position, tex coordinates, mask coordinates) - float vdata[] { - leftf, topf, - leftf, bottomf, - rightf, bottomf, - rightf, topf, - txl, txt, - txl, txb, - txr, txb, - txr, txt, - mtxl, mtxt, - mtxl, mtxb, - mtxr, mtxb, - mtxr, mtxt - }; - - - // store vertex buffer data, TODO: prepare this sometime earlier. - glBindBuffer(GL_ARRAY_BUFFER, this->buffer->vertbuf); - glBufferData(GL_ARRAY_BUFFER, sizeof(vdata), vdata, GL_STREAM_DRAW); - - // enable vertex buffer and bind it to the vertex attribute - glEnableVertexAttribArray(*pos_id); - glEnableVertexAttribArray(*texcoord_id); - if (use_alphashader) { - glEnableVertexAttribArray(*masktexcoord_id); - } - - // set data types, offsets in the vdata array - glVertexAttribPointer(*pos_id, 2, GL_FLOAT, GL_FALSE, 0, (void *)(0)); - glVertexAttribPointer(*texcoord_id, 2, GL_FLOAT, GL_FALSE, 0, (void *)(sizeof(float) * 8)); - if (use_alphashader) { - glVertexAttribPointer(*masktexcoord_id, 2, GL_FLOAT, GL_FALSE, 0, (void *)(sizeof(float) * 8 * 2)); - } - - // draw the vertex array - glDrawArrays(GL_QUADS, 0, 4); - - - // unbind the current buffer - glBindBuffer(GL_ARRAY_BUFFER, 0); - - glDisableVertexAttribArray(*pos_id); - glDisableVertexAttribArray(*texcoord_id); - if (use_alphashader) { - glDisableVertexAttribArray(*masktexcoord_id); - } - - // disable the shaders. - if (use_playercolors) { - teamcolor_shader::program->stopusing(); - } else if (use_alphashader) { - alphamask_shader::program->stopusing(); - glActiveTexture(GL_TEXTURE1); - glDisable(GL_TEXTURE_2D); - } else { - texture_shader::program->stopusing(); - } - - glActiveTexture(GL_TEXTURE0); - glDisable(GL_TEXTURE_2D); - - //////////////////////////////////////// - /* - int size = 2; - float r = 1.0f, g = 1.0f, b = 0.0f; - glPushMatrix(); - glTranslatef(leftf, bottomf, 0); - glColor3f(r, g, b); - glBegin(GL_TRIANGLES); - glVertex3f(-size, -size, 0); - glVertex3f(-size, size, 0); - glVertex3f(size, size, 0); - glVertex3f(size, size, 0); - glVertex3f(-size, -size, 0); - glVertex3f(size, -size, 0); - glEnd(); - glPopMatrix(); - */ - //////////////////////////////////////// -} - - -const gamedata::subtexture *Texture::get_subtexture(uint64_t subid) const { - if (subid < this->subtextures.size()) { - return &this->subtextures[subid]; - } - else { - throw Error{ - ERR << "Unknown subtexture requested for texture " - << this->filename << ": " << subid - }; - } -} - - -void Texture::get_subtexture_coordinates(uint64_t subid, - float *txl, float *txr, - float *txt, float *txb) const { - const gamedata::subtexture *tx = this->get_subtexture(subid); - this->get_subtexture_coordinates(tx, txl, txr, txt, txb); -} - - -void Texture::get_subtexture_coordinates(const gamedata::subtexture *tx, - float *txl, float *txr, - float *txt, float *txb) const { - *txl = ((float)tx->x) /this->w; - *txr = ((float)(tx->x + tx->w)) /this->w; - *txt = ((float)tx->y) /this->h; - *txb = ((float)(tx->y + tx->h)) /this->h; -} - - -size_t Texture::get_subtexture_count() const { - return this->subtextures.size(); -} - - -void Texture::get_subtexture_size(uint64_t subid, int *w, int *h) const { - const gamedata::subtexture *subtex = this->get_subtexture(subid); - *w = subtex->w; - *h = subtex->h; -} - - -GLuint Texture::get_texture_id() const { - this->load_in_glthread(); - return this->buffer->id; -} - -} // openage diff --git a/libopenage/texture.h b/libopenage/texture.h deleted file mode 100644 index 5469fe11e3..0000000000 --- a/libopenage/texture.h +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright 2013-2021 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include -#include - -#include "gamedata/texture_dummy.h" -#include "coord/pixel.h" -#include "coord/tile.h" -#include "shader/program.h" -#include "shader/shader.h" -#include "util/path.h" - - -namespace openage { -class Terrain; - -namespace util { -class Path; -} - -namespace coord { -class CoordManager; -} - -namespace texture_shader { -extern shader::Program *program; -extern GLint texture, tex_coord; -} // namespace texture_shader - -namespace teamcolor_shader { -extern shader::Program *program; -extern GLint texture, tex_coord; -extern GLint player_id_var, alpha_marker_var, player_color_var; -} // namespace teamcolor_shader - -namespace alphamask_shader { -extern shader::Program *program; -extern GLint base_texture, mask_texture, base_coord, mask_coord, show_mask; -} // namespace alphamask_shader - -// bitmasks for shader modes -constexpr int PLAYERCOLORED = 1 << 0; -constexpr int ALPHAMASKED = 1 << 1; - -/** - * enables transfer of data to opengl - */ -struct gl_texture_buffer { - GLuint id, vertbuf; - - // this requires loading on the main thread - bool transferred; - int texture_format_in; - int texture_format_out; - std::unique_ptr data; -}; - - -/** - * A texture for rendering graphically. - * - * You may believe it or not, but this class represents a single texture, - * which can be drawn on the screen. - * - * The class supports subtextures, so that one big texture can contain - * several small images. These are the ones actually to be rendered. - */ -class Texture { -public: - int w; - int h; - - /** - * Create a texture from a rgba8 array. - * It will have w * h * 32bit storage. - */ - Texture(int width, int height, std::unique_ptr data); - - /** - * Create a texture from a existing image file. - * For supported image file types, see the SDL_Image initialization in the engine. - */ - Texture(const util::Path &filename, bool use_metafile=false); - ~Texture(); - - /** - * Draws the texture at hud coordinates. - */ - void draw(const coord::CoordManager &mgr, coord::camhud pos, unsigned int mode=0, bool mirrored=false, int subid=0, unsigned player=0) const; - - /** - * Draws the texture at game coordinates. - */ - void draw(const coord::CoordManager &mgr, coord::camgame pos, unsigned int mode=0, bool mirrored=false, int subid=0, unsigned player=0) const; - - /** - * Draws the texture at phys coordinates. - */ - void draw(const coord::CoordManager &mgr, coord::phys3 pos, unsigned int mode=0, bool mirrored=false, int subid=0, unsigned player=0) const; - - /** - * Draws the texture at tile coordinates. - */ - void draw(const coord::CoordManager &mgr, const Terrain &terrain, coord::tile pos, unsigned int mode, int subid, Texture *alpha_texture, int alpha_subid) const; - - /** - * Draws the texture at window coordinates. - */ - void draw(coord::viewport pos, unsigned int mode, bool mirrored, int subid, unsigned player, Texture *alpha_texture, int alpha_subid) const; - - /** - * Reload the image file. Used for inotify refreshing. - */ - void reload(); - - /** - * Get the subtexture coordinates by its id. - */ - const gamedata::subtexture *get_subtexture(uint64_t subid) const; - - /** - * @return the number of available subtextures - */ - size_t get_subtexture_count() const; - - /** - * Fetch the size of the given subtexture. - * @param subid: index of the requested subtexture - * @param w: the subtexture width - * @param h: the subtexture height - */ - void get_subtexture_size(uint64_t subid, int *w, int *h) const; - - /** - * get atlas subtexture coordinates. - * - * left, right, top and bottom bounds as coordinates - * these pick the requested area out of the big texture. - * returned as floats in range 0.0 to 1.0 - */ - void get_subtexture_coordinates(uint64_t subid, float *txl, float *txr, float *txt, float *txb) const; - void get_subtexture_coordinates(const gamedata::subtexture *subtex, float *txl, float *txr, float *txt, float *txb) const; - - /** - * fixes the hotspots of all subtextures to (x,y). - * this is a temporary workaround; such fixes should actually be done in the - * convert script. - */ - void fix_hotspots(unsigned x, unsigned y); - - /** - * activates the influence of a given alpha mask to this texture. - */ - void activate_alphamask(Texture *mask, uint64_t subid); - - /** - * disable a previously activated alpha mask. - */ - void disable_alphamask(); - - /** - * returns the opengl texture id of this texture. - */ - GLuint get_texture_id() const; - -private: - std::unique_ptr buffer; - std::vector subtextures; - bool use_metafile; - - util::Path filename; - - void load(); - - /** - * The texture loadin must occur on the thread that manages the gl context. - */ - void load_in_glthread() const; - GLuint make_gl_texture(int iformat, int oformat, int w, int h, void *) const; - void unload(); - -}; - -} // namespace openage diff --git a/libopenage/time/clock.h b/libopenage/time/clock.h index 1021f599cd..05fbb9590a 100644 --- a/libopenage/time/clock.h +++ b/libopenage/time/clock.h @@ -1,4 +1,4 @@ -// Copyright 2022-2023 the openage authors. See copying.md for legal info. +// Copyright 2022-2024 the openage authors. See copying.md for legal info. #pragma once @@ -38,20 +38,20 @@ class Clock { ~Clock() = default; /** - * Get the current state of the clock. - */ + * Get the current state of the clock. + */ ClockState get_state(); /** - * Update the simulation time. - */ + * Update the simulation time. + */ void update_time(); /** * Get the current simulation time (in seconds). - * - * The returned value has a precision of milliseconds, so it is - * accurate to three decimal places. + * + * The returned value has a precision of milliseconds, so it is + * accurate to three decimal places. * * @return Time passed (in seconds). */ @@ -59,28 +59,28 @@ class Clock { /** * Get the current simulation time without speed adjustments. - * - * The returned value has a precision of milliseconds, so it is - * accurate to three decimal places. + * + * The returned value has a precision of milliseconds, so it is + * accurate to three decimal places. * * @return Time passed (in seconds). */ time::time_t get_real_time(); /** - * Get the current speed of the clock. + * Get the current speed of the clock. * * @return Speed of the clock. - */ + */ speed_t get_speed(); /** - * Set the speed of the clock. + * Set the speed of the clock. * * Simulation time is updated before changing speed. - * - * @param speed New speed of the clock. - */ + * + * @param speed New speed of the clock. + */ void set_speed(speed_t speed); /** @@ -109,8 +109,8 @@ class Clock { private: /** - * Status of the clock (init, running, stopped, ...). - */ + * Status of the clock (init, running, stopped, ...). + */ ClockState state; /** @@ -119,29 +119,29 @@ class Clock { uint16_t max_tick_time; /** - * How fast time passes relative to real time. - */ + * How fast time passes relative to real time. + */ speed_t speed; /** - * Last point in time where the clock was updated. - */ + * Last point in time where the clock was updated. + */ timepoint_t last_check; /** - * Start of the simulation in real time. - */ + * Start of the simulation in real time. + */ timepoint_t start_time; /** - * Stores how much time has passed inside the simulation (in milliseconds). - */ + * Stores how much time has passed inside the simulation (in milliseconds). + */ time::time_t sim_time; /** - * Stores how much time has passed inside the simulation (in milliseconds) + * Stores how much time has passed inside the simulation (in milliseconds) * _without_ speed adjustments (i.e. it acts as if speed = 1.0). - */ + */ time::time_t sim_real_time; /** diff --git a/libopenage/time/time.h b/libopenage/time/time.h index a6851f679c..ceac0c6365 100644 --- a/libopenage/time/time.h +++ b/libopenage/time/time.h @@ -15,4 +15,19 @@ namespace openage::time { */ using time_t = util::FixedPoint; +/** + * Minimum time value. + */ +static constexpr time_t TIME_MIN = std::numeric_limits::min(); + +/** + * Maximum time value. + */ +static constexpr time_t TIME_MAX = std::numeric_limits::max(); + +/** + * Zero time value (start of simulation). + */ +static constexpr time_t TIME_ZERO = time_t::zero(); + } // namespace openage::time diff --git a/libopenage/time/time_loop.h b/libopenage/time/time_loop.h index b48916d4e4..1a9e4dccdd 100644 --- a/libopenage/time/time_loop.h +++ b/libopenage/time/time_loop.h @@ -1,4 +1,4 @@ -// Copyright 2021-2023 the openage authors. See copying.md for legal info. +// Copyright 2021-2024 the openage authors. See copying.md for legal info. #pragma once @@ -15,20 +15,20 @@ class Clock; class TimeLoop { public: /** - * Create a new time loop with default values. - */ + * Create a new time loop with default values. + */ TimeLoop(); /** - * Create a new time loop from an existing and clock. - */ + * Create a new time loop from an existing and clock. + */ TimeLoop(const std::shared_ptr clock); ~TimeLoop() = default; /** * Run the time loop. - * - * Updates the clock and dispatches events that happened. + * + * Updates the clock and dispatches events that happened. */ void run(); @@ -43,10 +43,10 @@ class TimeLoop { void stop(); /** - * Get the clock used by this time loop. - * - * @return Simulation clock. - */ + * Get the clock used by this time loop. + * + * @return Simulation clock. + */ const std::shared_ptr get_clock(); private: @@ -56,8 +56,8 @@ class TimeLoop { bool running; /** - * Manage time and speed inside the simulation. - */ + * Manage time and speed inside the simulation. + */ std::shared_ptr clock; /** diff --git a/libopenage/unit/CMakeLists.txt b/libopenage/unit/CMakeLists.txt deleted file mode 100644 index 3c7a6e27cc..0000000000 --- a/libopenage/unit/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -add_sources(libopenage - ability.cpp - action.cpp - attribute.cpp - attributes.cpp - command.cpp - producer.cpp - research.cpp - selection.cpp - unit.cpp - unit_container.cpp - unit_texture.cpp - unit_type.cpp -) diff --git a/libopenage/unit/ability.cpp b/libopenage/unit/ability.cpp deleted file mode 100644 index 0ab1eef175..0000000000 --- a/libopenage/unit/ability.cpp +++ /dev/null @@ -1,446 +0,0 @@ -// Copyright 2014-2021 the openage authors. See copying.md for legal info. - -#include - -#include "../terrain/terrain_object.h" -#include "../gamestate/old/cost.h" -#include "../gamestate/old/player.h" -#include "ability.h" -#include "action.h" -#include "command.h" -#include "research.h" -#include "unit.h" - -namespace openage { - -bool UnitAbility::has_hitpoints(Unit &target) { - return target.has_attribute(attr_type::damaged) && - target.get_attribute().hp > 0; -} - -bool UnitAbility::is_damaged(Unit &target) { - return target.has_attribute(attr_type::damaged) && target.has_attribute(attr_type::hitpoints) && - target.get_attribute().hp < target.get_attribute().hp; -} - -bool UnitAbility::has_resource(Unit &target) { - return target.has_attribute(attr_type::resource) && !target.has_attribute(attr_type::worker) && - target.get_attribute().amount > 0; -} - -bool UnitAbility::is_same_player(Unit &to_modify, Unit &target) { - if (to_modify.has_attribute(attr_type::owner) && - target.has_attribute(attr_type::owner)) { - auto &mod_player = to_modify.get_attribute().player; - auto &tar_player = target.get_attribute().player; - return mod_player.color == tar_player.color; - } - return false; - -} - -bool UnitAbility::is_ally(Unit &to_modify, Unit &target) { - if (to_modify.has_attribute(attr_type::owner) && - target.has_attribute(attr_type::owner)) { - auto &mod_player = to_modify.get_attribute().player; - auto &tar_player = target.get_attribute().player; - return mod_player.is_ally(tar_player); - } - return false; -} - -bool UnitAbility::is_enemy(Unit &to_modify, Unit &target) { - if (to_modify.has_attribute(attr_type::owner) && - target.has_attribute(attr_type::owner)) { - auto &mod_player = to_modify.get_attribute().player; - auto &tar_player = target.get_attribute().player; - return mod_player.is_enemy(tar_player); - } - return false; -} - -MoveAbility::MoveAbility(const Sound *s) - : - sound{s} { -} - -bool MoveAbility::can_invoke(Unit &to_modify, const Command &cmd) { - if (cmd.has_position()) { - return bool(to_modify.location); - } - else if (cmd.has_unit()) { - return to_modify.location && - cmd.unit()->location && - &to_modify != cmd.unit(); // cannot target self - } - return false; -} - -void MoveAbility::invoke(Unit &to_modify, const Command &cmd, bool play_sound) { - to_modify.log(MSG(dbg) << "invoke move action"); - if (play_sound && this->sound) { - this->sound->play(); - } - - if (cmd.has_position()) { - auto target = cmd.position(); - to_modify.push_action(std::make_unique(&to_modify, target)); - } - else if (cmd.has_unit()) { - auto target = cmd.unit(); - - // distance from the targets edge that is required to stop moving - coord::phys_t radius = path::path_grid_size + (to_modify.location->min_axis() / 2L); - - // add the range of the unit if cmd indicator is set - if (cmd.has_flag(command_flag::use_range) && to_modify.has_attribute(attr_type::attack)) { - auto &att = to_modify.get_attribute(); - radius += att.max_range; - } - to_modify.push_action(std::make_unique(&to_modify, target->get_ref(), radius)); - } -} - -SetPointAbility::SetPointAbility() = default; - -bool SetPointAbility::can_invoke(Unit &to_modify, const Command &cmd) { - return cmd.has_position() && - to_modify.has_attribute(attr_type::building); -} - -void SetPointAbility::invoke(Unit &to_modify, const Command &cmd, bool) { - auto &build_attr = to_modify.get_attribute(); - build_attr.gather_point = cmd.position(); -} - - -GarrisonAbility::GarrisonAbility(const Sound *s) - : - sound{s} { -} - -bool GarrisonAbility::can_invoke(Unit &to_modify, const Command &cmd) { - if (!cmd.has_unit()) { - return false; - } - Unit &target = *cmd.unit(); - - // make sure buildings are completed - if (target.has_attribute(attr_type::building)) { - auto &build_attr = target.get_attribute(); - if (build_attr.completed < 1.0f) { - return false; - } - } - return to_modify.location && - target.has_attribute(attr_type::garrison) && - is_ally(to_modify, target); -} - -void GarrisonAbility::invoke(Unit &to_modify, const Command &cmd, bool play_sound) { - to_modify.log(MSG(dbg) << "invoke garrison action"); - if (play_sound && this->sound) { - this->sound->play(); - } - to_modify.push_action(std::make_unique(&to_modify, cmd.unit()->get_ref())); -} - -UngarrisonAbility::UngarrisonAbility(const Sound *s) - : - sound{s} { -} - -bool UngarrisonAbility::can_invoke(Unit &to_modify, const Command &cmd) { - if (to_modify.has_attribute(attr_type::garrison)) { - auto &garrison_attr = to_modify.get_attribute(); - return cmd.has_position() && !garrison_attr.content.empty(); - } - return false; -} - -void UngarrisonAbility::invoke(Unit &to_modify, const Command &cmd, bool play_sound) { - to_modify.log(MSG(dbg) << "invoke ungarrison action"); - if (play_sound && this->sound) { - this->sound->play(); - } - - // add as secondary, so primary action is not disrupted - to_modify.secondary_action(std::make_unique(&to_modify, cmd.position())); -} - -TrainAbility::TrainAbility(const Sound *s) - : - sound{s} { -} - -bool TrainAbility::can_invoke(Unit &to_modify, const Command &cmd) { - if (to_modify.has_attribute(attr_type::building)) { - auto &build_attr = to_modify.get_attribute(); - return cmd.has_type() && 1.0f <= build_attr.completed; - } - return false; -} - -void TrainAbility::invoke(Unit &to_modify, const Command &cmd, bool play_sound) { - to_modify.log(MSG(dbg) << "invoke train action"); - if (play_sound && this->sound) { - this->sound->play(); - } - to_modify.push_action(std::make_unique(&to_modify, cmd.type())); -} - -ResearchAbility::ResearchAbility(const Sound *s) - : - sound{s} { -} - -bool ResearchAbility::can_invoke(Unit &to_modify, const Command &cmd) { - if (to_modify.has_attribute(attr_type::owner) && cmd.has_research()) { - auto &player = to_modify.get_attribute().player; - auto research = cmd.research(); - return research->can_start() && - player.can_deduct(research->type->get_research_cost().get(player)); - } - return false; -} - -void ResearchAbility::invoke(Unit &to_modify, const Command &cmd, bool play_sound) { - if (play_sound && this->sound) { - this->sound->play(); - } - to_modify.push_action(std::make_unique(&to_modify, cmd.research())); -} - -BuildAbility::BuildAbility(const Sound *s) - : - sound{s} { -} - -bool BuildAbility::can_invoke(Unit &to_modify, const Command &cmd) { - if (cmd.has_unit()) { - Unit *target = cmd.unit(); - return to_modify.location && - is_same_player(to_modify, *target) && - target->has_attribute(attr_type::building) && - target->get_attribute().completed < 1.0f; - } - return false; -} - -void BuildAbility::invoke(Unit &to_modify, const Command &cmd, bool play_sound) { - to_modify.log(MSG(dbg) << "invoke build action"); - if (play_sound && this->sound) { - this->sound->play(); - } - - if (cmd.has_unit()) { - to_modify.push_action(std::make_unique(&to_modify, cmd.unit()->get_ref())); - } -} - -GatherAbility::GatherAbility(const Sound *s) - : - sound{s} { -} - -bool GatherAbility::can_invoke(Unit &to_modify, const Command &cmd) { - if (cmd.has_unit()) { - Unit &target = *cmd.unit(); - return &to_modify != &target && - to_modify.location && - to_modify.has_attribute(attr_type::worker) && - has_resource(target); - } - return false; -} - -void GatherAbility::invoke(Unit &to_modify, const Command &cmd, bool play_sound) { - to_modify.log(MSG(dbg) << "invoke gather action"); - if (play_sound && this->sound) { - this->sound->play(); - } - - Unit *target = cmd.unit(); - try { - to_modify.push_action(std::make_unique(&to_modify, target->get_ref())); - } catch (const std::invalid_argument &e) { - to_modify.log(MSG(dbg) << "invoke gather action cancelled due to an exception. Reason: " << e.what()); - } -} - -AttackAbility::AttackAbility(const Sound *s) - : - sound{s} { -} - -bool AttackAbility::can_invoke(Unit &to_modify, const Command &cmd) { - if (cmd.has_unit()) { - Unit &target = *cmd.unit(); - bool target_is_resource = has_resource(target); - return &to_modify != &target && - to_modify.location && - target.location && - target.location->is_placed() && - to_modify.has_attribute(attr_type::attack) && - has_hitpoints(target) && - (is_enemy(to_modify, target) || target_is_resource) && - (cmd.has_flag(command_flag::attack_res) == target_is_resource); - } - return false; -} - -void AttackAbility::invoke(Unit &to_modify, const Command &cmd, bool play_sound) { - to_modify.log(MSG(dbg) << "invoke attack action"); - if (play_sound && this->sound) { - this->sound->play(); - } - - Unit *target = cmd.unit(); - to_modify.push_action(std::make_unique(&to_modify, target->get_ref())); -} - -RepairAbility::RepairAbility(const Sound *s) - : - sound{s} { -} - -bool RepairAbility::can_invoke(Unit &to_modify, const Command &cmd) { - if (cmd.has_unit()) { - Unit &target = *cmd.unit(); - return &to_modify != &target && - to_modify.location && - target.location && - target.location->is_placed() && - is_damaged(target) && - is_ally(to_modify, target); - } - return false; -} - -void RepairAbility::invoke(Unit &to_modify, const Command &cmd, bool play_sound) { - to_modify.log(MSG(dbg) << "invoke repair action"); - if (play_sound && this->sound) { - this->sound->play(); - } - - Unit *target = cmd.unit(); - to_modify.push_action(std::make_unique(&to_modify, target->get_ref())); -} - -HealAbility::HealAbility(const Sound *s) - : - sound{s} { -} - -bool HealAbility::can_invoke(Unit &to_modify, const Command &cmd) { - if (cmd.has_unit()) { - Unit &target = *cmd.unit(); - return &to_modify != &target && - to_modify.location && - target.location && - target.location->is_placed() && - is_damaged(target) && - is_ally(to_modify, target); - } - return false; -} - -void HealAbility::invoke(Unit &to_modify, const Command &cmd, bool play_sound) { - to_modify.log(MSG(dbg) << "invoke heal action"); - if (play_sound && this->sound) { - this->sound->play(); - } - - Unit *target = cmd.unit(); - to_modify.push_action(std::make_unique(&to_modify, target->get_ref())); -} - -PatrolAbility::PatrolAbility(const Sound *s) - : - sound{s} { -} - -bool PatrolAbility::can_invoke(Unit &/*to_modify*/, const Command &/*cmd*/) { - // TODO implement - return false; -} - -void PatrolAbility::invoke(Unit &to_modify, const Command &/*cmd*/, bool play_sound) { - to_modify.log(MSG(dbg) << "not implemented"); - if (play_sound && this->sound) { - this->sound->play(); - } - // TODO implement -} - -ConvertAbility::ConvertAbility(const Sound *s) - : - sound{s} { -} - -bool ConvertAbility::can_invoke(Unit &to_modify, const Command &cmd) { - if (cmd.has_unit()) { - Unit &target = *cmd.unit(); - return &to_modify != &target && - to_modify.location && - target.location && - target.location->is_placed() && - is_enemy(to_modify, target); - } - return false; -} - -void ConvertAbility::invoke(Unit &to_modify, const Command &cmd, bool play_sound) { - to_modify.log(MSG(dbg) << "invoke convert action"); - if (play_sound && this->sound) { - this->sound->play(); - } - - Unit *target = cmd.unit(); - to_modify.push_action(std::make_unique(&to_modify, target->get_ref())); -} - -ability_set UnitAbility::set_from_list(const std::vector &items) { - ability_set result; - for (auto i : items) { - result[static_cast(i)] = 1; - } - return result; -} - -} // namespace openage - -namespace std { - -string to_string(const openage::ability_type &at) { - switch (at) { - case openage::ability_type::move: - return "move"; - case openage::ability_type::garrison: - return "garrison"; - case openage::ability_type::ungarrison: - return "ungarrison"; - case openage::ability_type::patrol: - return "patrol"; - case openage::ability_type::train: - return "train"; - case openage::ability_type::build: - return "build"; - case openage::ability_type::research: - return "research"; - case openage::ability_type::gather: - return "gather"; - case openage::ability_type::attack: - return "attack"; - case openage::ability_type::convert: - return "convert"; - case openage::ability_type::repair: - return "repair"; - case openage::ability_type::heal: - return "heal"; - default: - return "unknown"; - } -} - -} // namespace std diff --git a/libopenage/unit/ability.h b/libopenage/unit/ability.h deleted file mode 100644 index 71985ebe8c..0000000000 --- a/libopenage/unit/ability.h +++ /dev/null @@ -1,382 +0,0 @@ -// Copyright 2014-2021 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include -#include -#include -#include - -#include "../coord/phys.h" - -namespace openage { - -class Command; -class Sound; -class Unit; -class UnitAction; -class UnitTexture; -class UnitType; - -/** - * roughly the same as command_ability in game data - */ -enum class ability_type { - move, - patrol, - set_point, - garrison, - ungarrison, - train, - build, - research, - gather, - attack, - convert, - repair, - heal, - MAX -}; - -/** - * a container where each ability uses 1 bit - */ -constexpr int ability_type_size = static_cast(ability_type::MAX); -using ability_set = std::bitset; -using ability_id_t = unsigned int; - -/** - * all bits set to 1 - */ -const ability_set ability_all = ability_set().set(); - -/** - * the order abilities should be used when available - */ -static std::vector ability_priority { - ability_type::gather, // targeting - ability_type::convert, - ability_type::repair, - ability_type::heal, - ability_type::attack, - ability_type::build, - ability_type::move, // positional - ability_type::patrol, - ability_type::garrison, - ability_type::ungarrison, // inside buildings - ability_type::train, - ability_type::research, - ability_type::set_point, -}; - - -/** - * Abilities create an action when given a target - * some abilities target positions such as moving or patrolling - * others target other game objects, such as attacking or - * collecting relics - * - * Abilities are constructed with a default unit texture, but allow the texture - * to be specified with the invoke function - */ -class UnitAbility { -public: - virtual ~UnitAbility() {} - - virtual ability_type type() = 0; - - /** - * true if the paramaters allow an action to be performed - */ - virtual bool can_invoke(Unit &to_modify, const Command &cmd) = 0; - - /** - * applies command to a given unit - */ - virtual void invoke(Unit &to_modify, const Command &cmd, bool play_sound=false) = 0; - - /** - * some common functions - */ - bool has_hitpoints(Unit &target); - bool is_damaged(Unit &target); - bool has_resource(Unit &target); - bool is_same_player(Unit &to_modify, Unit &target); - bool is_ally(Unit &to_modify, Unit &target); - bool is_enemy(Unit &to_modify, Unit &target); - - /** - * set bits corresponding to abilities, useful for initialising an ability_set - * using a brace enclosed list - */ - static ability_set set_from_list(const std::vector &items); - -}; - -/** - * initiates a move action when given a valid target - */ -class MoveAbility: public UnitAbility { -public: - MoveAbility(const Sound *s=nullptr); - - ability_type type() override { - return ability_type::move; - } - - bool can_invoke(Unit &to_modify, const Command &cmd) override; - - void invoke(Unit &to_modify, const Command &cmd, bool play_sound=false) override; - -private: - const Sound *sound; -}; - -/** - * sets the gather point on buildings - */ -class SetPointAbility: public UnitAbility { -public: - SetPointAbility(); - - ability_type type() override { - return ability_type::set_point; - } - - bool can_invoke(Unit &to_modify, const Command &cmd) override; - - void invoke(Unit &to_modify, const Command &cmd, bool play_sound=false) override; -}; - - -/** - * ability to garrision inside a building - */ -class GarrisonAbility: public UnitAbility { -public: - GarrisonAbility(const Sound *s=nullptr); - - ability_type type() override { - return ability_type::garrison; - } - - bool can_invoke(Unit &to_modify, const Command &cmd) override; - - void invoke(Unit &to_modify, const Command &cmd, bool play_sound=false) override; - -private: - const Sound *sound; -}; - -/** - * ability to ungarrision a building - */ -class UngarrisonAbility: public UnitAbility { -public: - UngarrisonAbility(const Sound *s=nullptr); - - ability_type type() override { - return ability_type::ungarrison; - } - - bool can_invoke(Unit &to_modify, const Command &cmd) override; - - void invoke(Unit &to_modify, const Command &cmd, bool play_sound=false) override; - -private: - const Sound *sound; -}; - -/** - * buildings train new objects - */ -class TrainAbility: public UnitAbility { -public: - TrainAbility(const Sound *s=nullptr); - - ability_type type() override { - return ability_type::train; - } - - bool can_invoke(Unit &to_modify, const Command &cmd) override; - - void invoke(Unit &to_modify, const Command &cmd, bool play_sound=false) override; - -private: - const Sound *sound; -}; - -/** - * initiates a research - */ -class ResearchAbility: public UnitAbility { -public: - ResearchAbility(const Sound *s=nullptr); - - ability_type type() override { - return ability_type::research; - } - - bool can_invoke(Unit &to_modify, const Command &cmd) override; - - void invoke(Unit &to_modify, const Command &cmd, bool play_sound=false) override; - -private: - const Sound *sound; -}; - -/** - * villagers build new buildings - */ -class BuildAbility: public UnitAbility { -public: - BuildAbility(const Sound *s=nullptr); - - ability_type type() override { - return ability_type::build; - } - - bool can_invoke(Unit &to_modify, const Command &cmd) override; - - void invoke(Unit &to_modify, const Command &cmd, bool play_sound=false) override; - -private: - const Sound *sound; -}; - -/** - * initiates an gather resource action when given a valid target - */ -class GatherAbility: public UnitAbility { -public: - GatherAbility(const Sound *s=nullptr); - - ability_type type() override { - return ability_type::gather; - } - - bool can_invoke(Unit &to_modify, const Command &cmd) override; - - void invoke(Unit &to_modify, const Command &cmd, bool play_sound=false) override; - -private: - const Sound *sound; -}; - -/** - * initiates an attack action when given a valid target - */ -class AttackAbility: public UnitAbility { -public: - AttackAbility(const Sound *s=nullptr); - - ability_type type() override { - return ability_type::attack; - } - - bool can_invoke(Unit &to_modify, const Command &cmd) override; - - void invoke(Unit &to_modify, const Command &cmd, bool play_sound=false) override; - -private: - const Sound *sound; -}; - -/** - * initiates a repair action when given a valid target - */ -class RepairAbility: public UnitAbility { -public: - RepairAbility(const Sound *s=nullptr); - - ability_type type() override { - return ability_type::repair; - } - - bool can_invoke(Unit &to_modify, const Command &cmd) override; - - void invoke(Unit &to_modify, const Command &cmd, bool play_sound=false) override; - -private: - const Sound *sound; -}; - -/** - * initiates a heal action when given a valid target - */ -class HealAbility: public UnitAbility { -public: - HealAbility(const Sound *s=nullptr); - - ability_type type() override { - return ability_type::heal; - } - - bool can_invoke(Unit &to_modify, const Command &cmd) override; - - void invoke(Unit &to_modify, const Command &cmd, bool play_sound=false) override; - -private: - const Sound *sound; -}; - -/** - * initiates a patrol action when given a valid target - * TODO implement - */ -class PatrolAbility: public UnitAbility { -public: - PatrolAbility(const Sound *s=nullptr); - - ability_type type() override { - return ability_type::patrol; - } - - bool can_invoke(Unit &to_modify, const Command &cmd) override; - - void invoke(Unit &to_modify, const Command &cmd, bool play_sound=false) override; - -private: - const Sound *sound; -}; - -/** - * initiates a convert action when given a valid target - */ -class ConvertAbility: public UnitAbility { -public: - ConvertAbility(const Sound *s=nullptr); - - ability_type type() override { - return ability_type::convert; - } - - bool can_invoke(Unit &to_modify, const Command &cmd) override; - - void invoke(Unit &to_modify, const Command &cmd, bool play_sound=false) override; - -private: - const Sound *sound; -}; - -} // namespace openage - -namespace std { - -std::string to_string(const openage::ability_type &at); - -/** - * hasher for ability type enum - */ -template<> -struct hash { - typedef underlying_type::type underlying_type; - - size_t operator()(const openage::ability_type &arg) const { - hash hasher; - return hasher(static_cast(arg)); - } -}; - -} // namespace std diff --git a/libopenage/unit/action.cpp b/libopenage/unit/action.cpp deleted file mode 100644 index 50a03e2155..0000000000 --- a/libopenage/unit/action.cpp +++ /dev/null @@ -1,1297 +0,0 @@ -// Copyright 2014-2023 the openage authors. See copying.md for legal info. - -#include -#include - -#include "../legacy_engine.h" -#include "../pathfinding/a_star.h" -#include "../pathfinding/heuristics.h" -#include "../terrain/terrain.h" -#include "../terrain/terrain_search.h" -#include "action.h" -#include "command.h" -#include "producer.h" -#include "research.h" -#include "unit_texture.h" - -namespace openage { - -IntervalTimer::IntervalTimer(unsigned int interval) : - IntervalTimer{interval, -1} { -} - -IntervalTimer::IntervalTimer(unsigned int interval, int max_triggers) : - interval{interval}, - max_triggers{max_triggers}, - time_left{interval}, - triggers{0} { -} - -void IntervalTimer::skip_to_trigger() { - this->time_left = 0; -} - -bool IntervalTimer::update(unsigned int time) { - if (this->triggers == this->max_triggers) { - return false; - } - else if (this->time_left > time) { - this->time_left -= time; - return false; - } - else { - this->time_left += this->interval - time; - this->triggers += 1; - return true; - } -} - -unsigned int IntervalTimer::get_time_left() const { - return this->time_left; -} - -float IntervalTimer::get_progress() const { - return 1.0f - (this->time_left * 1.0f / this->interval); -} - -bool IntervalTimer::has_triggers() const { - return this->triggers > 0; -} - -bool IntervalTimer::finished() const { - return this->triggers == this->max_triggers; -} - -bool UnitAction::show_debug = false; - -coord::phys_t UnitAction::adjacent_range(Unit *u) { - return path::path_grid_size * 3 + (u->location->min_axis() / 2L); -} - -coord::phys_t UnitAction::get_attack_range(Unit *u) { - coord::phys_t range = adjacent_range(u); - if (u->has_attribute(attr_type::attack)) { - auto &attack = u->get_attribute(); - range += attack.max_range; - } - return range; -} - -coord::phys_t UnitAction::get_heal_range(Unit *u) { - coord::phys_t range = adjacent_range(u); - if (u->has_attribute(attr_type::heal)) { - auto &heal = u->get_attribute(); - range += heal.range; - } - return range; -} - -UnitAction::UnitAction(Unit *u, graphic_type initial_gt) : - entity{u}, - graphic{initial_gt}, - frame{.0f}, - frame_rate{.0f} { - auto &g_set = this->current_graphics(); - if (g_set.count(initial_gt) > 0) { - auto utex = g_set.at(initial_gt); - if (utex) { - this->frame_rate = utex->frame_rate; - } - else { - this->entity->log(MSG(dbg) << "Broken graphic (null)"); - } - } - else { - this->entity->log(MSG(dbg) << "Broken graphic (not available)"); - } - - if (this->frame_rate == 0) { - // a random starting point for static graphics - // this creates variations in trees / houses etc - // this value is also deterministic to match across clients - this->frame = (u->id * u->id * 19249) & 0xff; - } -} - -graphic_type UnitAction::type() const { - return this->graphic; -} - -float UnitAction::current_frame() const { - return this->frame; -} - -const graphic_set &UnitAction::current_graphics() const { - // return the default graphic - return this->entity->unit_type->graphics; -} - -void UnitAction::draw_debug(const LegacyEngine &engine) { - // draw debug content if available - if (show_debug && this->debug_draw_action) { - this->debug_draw_action(engine); - } -} - -void UnitAction::face_towards(const coord::phys3 pos) { - if (this->entity->has_attribute(attr_type::direction)) { - auto &d_attr = this->entity->get_attribute(); - d_attr.unit_dir = pos - this->entity->location->pos.draw; - } -} - -bool UnitAction::damage_unit(Unit &target) { - bool killed = false; - - if (target.has_attribute(attr_type::damaged)) { - auto &dm = target.get_attribute(); - - // this is the damage calculation system - - if (dm.hp == 0) { - // already killed, do nothing - } - else if (target.has_attribute(attr_type::armor) && this->entity->has_attribute(attr_type::attack)) { - auto &armor = target.get_attribute().armor; - auto &damage = this->entity->get_attribute().damage; - - unsigned int actual_damage = 0; - for (const auto &pair : armor) { - auto search = damage.find(pair.first); - if (search != damage.end()) { - if (pair.second < search->second) { - actual_damage += search->second - pair.second; - } - } - } - // TODO add elevation modifier here - if (actual_damage < 1) { - actual_damage = 1; - } - - if (dm.hp > actual_damage) { - dm.hp -= actual_damage; - } - else { - dm.hp = 0; - killed = true; - } - } - else { - // TODO remove (keep for testing) - unsigned int dmg = 1; - if (dm.hp > dmg) { - dm.hp -= dmg; - } - else { - dm.hp = 0; - killed = true; - } - } - } - - if (killed) { - // if killed, give credit to a player - if (this->entity->has_attribute(attr_type::owner)) { - auto &owner = this->entity->get_attribute().player; - owner.killed_unit(target); - } - } - - return killed; -} - -void UnitAction::move_to(Unit &target, bool use_range) { - auto &player = this->entity->get_attribute().player; - Command cmd(player, &target); - cmd.set_ability(ability_type::move); - if (use_range) { - cmd.add_flag(command_flag::use_range); - } - this->entity->queue_cmd(cmd); -} - -TargetAction::TargetAction(Unit *u, graphic_type gt, UnitReference r, coord::phys_t rad) : - UnitAction(u, gt), - target{r}, - target_type_id{0}, - repath_attempts{10}, - end_action{false}, - radius{rad} { - // update type - if (this->target.is_valid()) { - auto target_ptr = this->target.get(); - this->target_type_id = target_ptr->unit_type->id(); - } - - // initial value for distance - this->update_distance(); -} - -TargetAction::TargetAction(Unit *u, graphic_type gt, UnitReference r) : - TargetAction(u, gt, r, adjacent_range(u)) { -} - -void TargetAction::update(unsigned int time) { - auto target_ptr = this->update_distance(); - if (!target_ptr) { - return; // target has become invalid - } - - // this update moves a unit within radius of the target - // once within the radius the update gets passed to the class - // derived from TargetAction - - // set direction unit should face - this->face_towards(target_ptr->location->pos.draw); - - // move to within the set radius - if (this->dist_to_target <= this->radius) { - // the derived class controls what to - // do when in range of the target - this->update_in_range(time, target_ptr); - this->repath_attempts = 10; - } - else if (this->repath_attempts) { - // out of range so try move towards - // if this unit has a move ability - this->move_to(*target_ptr); - this->repath_attempts -= 1; - } - else { - // unit is stuck - this->end_action = true; - } -} - -void TargetAction::on_completion() { - // do not retask if action is forced to end - if (this->end_action || !this->entity->location) { - return; - } - - // retask units on nearby objects - // such as gathers targeting a new resource - // when the current target expires - this->on_completion_in_range(this->target_type_id); -} - -bool TargetAction::completed() const { - if (this->end_action || !this->target.is_valid() || !this->target.get()->location) { - return true; - } - return this->completed_in_range(this->target.get()); -} - -coord::phys_t TargetAction::distance_to_target() { - return this->dist_to_target; -} - -Unit *TargetAction::update_distance() { - if (!this->target.is_valid()) { - return nullptr; - } - - // make sure object is not garrisoned - auto target_ptr = this->target.get(); - if (!target_ptr->location) { - return nullptr; - } - - // update distance - this->dist_to_target = target_ptr->location->from_edge(this->entity->location->pos.draw); - - // return the targeted unit - return target_ptr; -} - -UnitReference TargetAction::get_target() const { - return this->target; -} - -int TargetAction::get_target_type_id() const { - return this->target_type_id; -} - -void TargetAction::set_target(UnitReference new_target) { - if (new_target.is_valid()) { - this->target = new_target; - this->update_distance(); - } - else { - this->end_action = true; - } -} - -DecayAction::DecayAction(Unit *e) : - UnitAction(e, graphic_type::standing), - end_frame{.0f} { - auto &g_set = this->current_graphics(); - if (g_set.count(this->graphic) > 0) { - this->end_frame = g_set.at(this->graphic)->frame_count - 1; - } -} - -void DecayAction::update(unsigned int time) { - this->frame += time * this->frame_rate / 10000.0f; -} - -void DecayAction::on_completion() {} - -bool DecayAction::completed() const { - return this->frame > this->end_frame; -} - -DeadAction::DeadAction(Unit *e, std::function on_complete) : - UnitAction(e, graphic_type::dying), - end_frame{.0f}, - on_complete_func{on_complete} { - auto &g_set = this->current_graphics(); - if (g_set.count(graphic) > 0) { - this->end_frame = g_set.at(graphic)->frame_count - 1; - } -} - -void DeadAction::update(unsigned int time) { - if (this->entity->has_attribute(attr_type::damaged)) { - auto &dm = this->entity->get_attribute(); - dm.hp = 0; - } - - // decay resources - if (this->entity->has_attribute(attr_type::resource)) { - auto &resource = this->entity->get_attribute(); - if (resource.decay > 0) { - resource.amount -= resource.decay; - } - } - - // inc frame but do not pass the end frame - // the end frame will remain if the object carries resources - if (this->frame < this->end_frame) { - this->frame += 0.001 + time * this->frame_rate / 3.0f; - } - else { - this->frame = this->end_frame; - } -} - -void DeadAction::on_completion() { - if (this->entity->has_attribute(attr_type::owner)) { - auto &owner = this->entity->get_attribute().player; - owner.active_unit_removed(this->entity); // TODO move before the start of dead action? - } - - this->on_complete_func(); -} - -bool DeadAction::completed() const { - // check resource, trees/huntables with resource are not removed but not workers - if (this->entity->has_attribute(attr_type::resource) && !this->entity->has_attribute(attr_type::worker)) { - auto &res_attr = this->entity->get_attribute(); - return res_attr.amount <= 0; // cannot complete when resource remains - } - return this->frame > this->end_frame; -} - -FoundationAction::FoundationAction(Unit *e, bool add_destruction) : - UnitAction(e, graphic_type::construct), - add_destruct_effect{add_destruction}, - cancel{false} { -} - -void FoundationAction::update(unsigned int) { - if (!this->entity->location) { - this->cancel = true; - } -} - -void FoundationAction::on_completion() { - // do nothing if construction is cancelled - if (this->cancel) { - return; - } - - if (this->entity->has_attribute(attr_type::owner)) { - auto &owner = this->entity->get_attribute().player; - owner.active_unit_added(this->entity, true); - } - - // add destruction effect when available - if (this->add_destruct_effect) { - this->entity->push_action(std::make_unique(this->entity), true); - } - this->entity->push_action(std::make_unique(this->entity), true); -} - -bool FoundationAction::completed() const { - return this->cancel || (this->entity->has_attribute(attr_type::building) && (this->entity->get_attribute().completed >= 1.0f)); -} - -IdleAction::IdleAction(Unit *e) : - UnitAction(e, graphic_type::standing) { - auto terrain = this->entity->location->get_terrain(); - auto current_tile = this->entity->location->pos.draw.to_tile3().to_tile(); - this->search = std::make_shared(terrain, current_tile, 5.0f); - - // currently allow attack and heal automatically - this->auto_abilities = UnitAbility::set_from_list({ability_type::attack, ability_type::heal}); -} - -void IdleAction::update(unsigned int time) { - // auto task searching - if (this->entity->location && this->entity->has_attribute(attr_type::owner) && this->entity->has_attribute(attr_type::attack) && this->entity->has_attribute(attr_type::formation) && this->entity->get_attribute().stance != attack_stance::do_nothing) { - // restart search from new tile when moved - auto terrain = this->entity->location->get_terrain(); - auto current_tile = this->entity->location->pos.draw.to_tile3().to_tile(); - if (!(current_tile == this->search->start_tile())) { - this->search = std::make_shared(terrain, current_tile, 5.0f); - } - - // search one tile per update - // next tile will always be valid - coord::tile tile = this->search->next_tile(); - auto tile_data = terrain->get_data(tile); - auto &player = this->entity->get_attribute().player; - - // find and actions which can be invoked - for (auto object_location : tile_data->obj) { - Command to_object(player, &object_location->unit); - - // only allow abilities in the set of auto ability types - to_object.set_ability_set(auto_abilities); - if (this->entity->queue_cmd(to_object)) { - break; - } - } - } - - // generate resources - // TODO move elsewhere - if (this->entity->has_attribute(attr_type::resource_generator) && this->entity->has_attribute(attr_type::owner)) { - auto &player = this->entity->get_attribute().player; - auto &resource_generator = this->entity->get_attribute(); - - ResourceBundle resources = resource_generator.resources.clone(); - if (resource_generator.rate == 0) { - resources *= time; - } - else { - // TODO add in intervals and not continuously - resources *= time * resource_generator.rate; - } - - player.receive(resources); - } - - // unit carrying resources take the carrying sprite when idle - // we're not updating frames because the carrying sprite is walking - if (entity->has_attribute(attr_type::worker)) { - auto &worker_resource = this->entity->get_attribute(); - if (worker_resource.amount > 0) { - this->graphic = graphic_type::carrying; - } - else { - this->graphic = graphic_type::standing; - this->frame += time * this->frame_rate / 20.0f; - } - } - else { - // inc frame - this->frame += time * this->frame_rate / 20.0f; - } -} - -void IdleAction::on_completion() {} - -bool IdleAction::completed() const { - if (this->entity->has_attribute(attr_type::damaged)) { - auto &dm = this->entity->get_attribute(); - return dm.hp == 0; - } - else if (this->entity->has_attribute(attr_type::resource)) { - auto &res_attr = this->entity->get_attribute(); - return res_attr.amount <= 0.0; - } - return false; -} - -MoveAction::MoveAction(Unit *e, coord::phys3 tar, bool repath) : - UnitAction{e, graphic_type::walking}, - unit_target{}, - target(tar), - radius{path::path_grid_size}, - allow_repath{repath}, - end_action{false} { - this->initialise(); -} - -MoveAction::MoveAction(Unit *e, UnitReference tar, coord::phys_t within_range) : - UnitAction{e, graphic_type::walking}, - unit_target{tar}, - target(tar.get()->location->pos.draw), - radius{within_range}, - allow_repath{false}, - end_action{false} { - this->initialise(); -} - -void MoveAction::initialise() { - // switch workers to the carrying graphic - if (this->entity->has_attribute(attr_type::worker)) { - auto &worker_resource = this->entity->get_attribute(); - if (worker_resource.amount > 0) { - this->graphic = graphic_type::carrying; - } - } - - // set initial distance - this->set_distance(); - - // set an initial path - this->set_path(); - // this->debug_draw_action = [&](const Engine &engine) { - // this->path.draw_path(engine.coord); - // }; -} - -MoveAction::~MoveAction() = default; - -void MoveAction::update(unsigned int time) { - if (this->unit_target.is_valid()) { - // a unit is targeted, which may move - auto &target_object = this->unit_target.get()->location; - - // check for garrisoning objects - if (!target_object) { - this->end_action = true; - return; - } - coord::phys3 &target_pos = target_object->pos.draw; - coord::phys3 &unit_pos = this->entity->location->pos.draw; - - // repath if target changes tiles by a threshold - // this repathing is more frequent when the unit is - // close to its target - coord::phys_t tdx = target_pos.ne - this->target.ne; - coord::phys_t tdy = target_pos.se - this->target.se; - coord::phys_t udx = unit_pos.ne - this->target.ne; - coord::phys_t udy = unit_pos.se - this->target.se; - if (this->path.waypoints.empty() || std::hypot(tdx, tdy) > std::hypot(udx, udy)) { - this->target = target_pos; - this->set_path(); - } - } - - // path not found - if (this->path.waypoints.empty()) { - if (!this->allow_repath) { - this->entity->log(MSG(dbg) << "Path not found -- drop action"); - this->end_action = true; - } - return; - } - - // find distance to move in this update - auto &sp_attr = this->entity->get_attribute(); - double distance_to_move = sp_attr.unit_speed.to_double() * time; - - // current position and direction - coord::phys3 new_position = this->entity->location->pos.draw; - auto &d_attr = this->entity->get_attribute(); - coord::phys3_delta new_direction = d_attr.unit_dir; - - while (distance_to_move > 0) { - if (this->path.waypoints.empty()) { - break; - } - - // find a point to move directly towards - coord::phys3 waypoint = this->next_waypoint(); - coord::phys3_delta move_dir = waypoint - new_position; - - // normalise dir - double distance_to_waypoint = std::hypot(move_dir.ne, move_dir.se); - - if (distance_to_waypoint <= distance_to_move) { - distance_to_move -= distance_to_waypoint; - - // change entity position and direction - new_position = waypoint; - new_direction = move_dir; - - // remove the waypoint - this->path.waypoints.pop_back(); - } - else { - // change entity position and direction - new_position += move_dir * (distance_to_move / distance_to_waypoint); - new_direction = move_dir; - break; - } - } - - // check move collisions - bool move_completed = this->entity->location->move(new_position); - if (move_completed) { - d_attr.unit_dir = new_direction; - this->set_distance(); - } - else { - // cases for modifying path when blocked - if (this->allow_repath) { - this->entity->log(MSG(dbg) << "Path blocked -- finding new path"); - this->set_path(); - } - else { - this->entity->log(MSG(dbg) << "Path blocked -- drop action"); - this->end_action = true; - } - } - - // inc frame - this->frame += time * this->frame_rate / 5.0f; -} - -void MoveAction::on_completion() {} - -bool MoveAction::completed() const { - // no more waypoints to a static location - if (this->end_action || (!this->unit_target.is_valid() && this->path.waypoints.empty())) { - return true; - } - - //close enough to end action - if (this->distance_to_target < this->radius) { - return true; - } - return false; -} - - -coord::phys3 MoveAction::next_waypoint() const { - if (this->path.waypoints.size() > 0) { - return this->path.waypoints.back().position; - } - else { - throw Error{MSG(err) << "No next waypoint available!"}; - } -} - - -void MoveAction::set_path() { - if (this->unit_target.is_valid()) { - this->path = path::to_object(this->entity->location.get(), this->unit_target.get()->location.get(), this->radius); - } - else { - coord::phys3 start = this->entity->location->pos.draw; - coord::phys3 end = this->target; - this->path = path::to_point(start, end, this->entity->location->passable); - } -} - -void MoveAction::set_distance() { - if (this->unit_target.is_valid()) { - auto &target_object = this->unit_target.get()->location; - coord::phys3 &unit_pos = this->entity->location->pos.draw; - this->distance_to_target = target_object->from_edge(unit_pos); - } - else { - coord::phys3_delta move_dir = this->target - this->entity->location->pos.draw; - this->distance_to_target = static_cast(std::hypot(move_dir.ne, move_dir.se)); - } -} - -GarrisonAction::GarrisonAction(Unit *e, UnitReference build) : - TargetAction{e, graphic_type::standing, build}, - complete{false} { -} - -void GarrisonAction::update_in_range(unsigned int, Unit *target_unit) { - auto &garrison_attr = target_unit->get_attribute(); - garrison_attr.content.push_back(this->entity->get_ref()); - - if (this->entity->location) { - this->entity->location->remove(); - this->entity->location = nullptr; - } - this->complete = true; -} - -UngarrisonAction::UngarrisonAction(Unit *e, const coord::phys3 &pos) : - UnitAction{e, graphic_type::standing}, - position(pos), - complete{false} { -} - -void UngarrisonAction::update(unsigned int) { - auto &garrison_attr = this->entity->get_attribute(); - - // try unload all objects currently garrisoned - auto position_it = std::remove_if( - std::begin(garrison_attr.content), - std::end(garrison_attr.content), - [this](UnitReference &u) { - if (u.is_valid()) { - // ptr to unit being ungarrisoned - Unit *unit_ptr = u.get(); - - // make sure it was placed outside - if (unit_ptr->unit_type->place_beside(unit_ptr, this->entity->location.get())) { - // task unit to move to position - auto &player = this->entity->get_attribute().player; - Command cmd(player, this->position); - cmd.set_ability(ability_type::move); - unit_ptr->queue_cmd(cmd); - return true; - } - } - return false; - }); - - // remove elements which were ungarrisoned - garrison_attr.content.erase(position_it, std::end(garrison_attr.content)); - - // completed when no units are remaining - this->complete = garrison_attr.content.empty(); -} - -void UngarrisonAction::on_completion() {} - -TrainAction::TrainAction(Unit *e, UnitType *pp) : - UnitAction{e, graphic_type::standing}, - trained{pp}, - timer{10000, 1}, // TODO get the training time from unit type - started{false}, - complete{false} { - // TODO deduct resources -} - -void TrainAction::update(unsigned int time) { - if (!this->started) { - // check if there is enough population capacity - if (!this->trained->default_attributes.has(attr_type::population)) { - this->started = true; - } - else { - auto &player = this->entity->get_attribute().player; - auto &population_demand = this->trained->default_attributes.get().demand; - bool can_start = population_demand == 0 || population_demand <= player.population.get_space(); - // TODO trigger not enough population capacity message - this->started = can_start; - } - } - - if (this->started) { - // place unit when ready - if (this->timer.finished() || this->timer.update(time)) { - // create using the producer - UnitContainer *container = this->entity->get_container(); - auto &player = this->entity->get_attribute().player; - auto uref = container->new_unit(*this->trained, player, this->entity->location.get()); - - // make sure unit got placed - // try again next update if cannot place - if (uref.is_valid()) { - if (this->entity->has_attribute(attr_type::building)) { - // use a move command to the gather point - auto &build_attr = this->entity->get_attribute(); - Command cmd(player, build_attr.gather_point); - cmd.set_ability(ability_type::move); - uref.get()->queue_cmd(cmd); - } - this->complete = true; - } - } - } -} - -void TrainAction::on_completion() { - if (!this->complete) { - // TODO give back the resources - } -} - -ResearchAction::ResearchAction(Unit *e, Research *research) : - UnitAction{e, graphic_type::standing}, - research{research}, - timer{research->type->get_research_time(), 1}, - complete{false} { - this->research->started(); -} - -void ResearchAction::update(unsigned int time) { - if (timer.update(time)) { - this->complete = true; - this->research->apply(); - this->research->completed(); - } -} - -void ResearchAction::on_completion() { - if (!this->complete) { - this->research->stopped(); - } -} - -BuildAction::BuildAction(Unit *e, UnitReference foundation) : - TargetAction{e, graphic_type::work, foundation}, - complete{.0f}, - build_rate{.0001f} { - // update the units type - if (this->entity->has_attribute(attr_type::multitype)) { - this->entity->get_attribute().switchType(gamedata::unit_classes::BUILDING, this->entity); - } -} - -void BuildAction::update_in_range(unsigned int time, Unit *target_unit) { - if (target_unit->has_attribute(attr_type::building)) { - auto &build = target_unit->get_attribute(); - - // upgrade floating outlines - auto target_location = target_unit->location.get(); - if (target_location->is_floating()) { - // try to place the object - if (target_location->place(object_state::placed)) { - // modify ground terrain - if (build.foundation_terrain > 0) { - target_location->set_ground(build.foundation_terrain, 0); - } - } - else { - // failed to start construction - this->complete = 1.0f; - return; - } - } - - // increment building completion - build.completed += build_rate * time; - this->complete = build.completed; - - if (this->complete >= 1.0f) { - this->complete = build.completed = 1.0f; - target_location->place(build.completion_state); - } - } - else { - this->complete = 1.0f; - } - - // inc frame - this->frame += time * this->frame_rate / 2.5f; -} - -void BuildAction::on_completion() { - if (this->get_target().is_valid() && this->get_target().get()->get_attribute().completed < 1.0f) { - // The BuildAction was just aborted and we shouldn't look for new buildings - return; - } - this->entity->log(MSG(dbg) << "Done building, searching for new building"); - auto valid = [this](const TerrainObject &obj) { - if (!this->entity->get_attribute().player.owns(obj.unit) || !obj.unit.has_attribute(attr_type::building) || obj.unit.get_attribute().completed >= 1.0f) { - return false; - } - this->entity->log(MSG(dbg) << "Found unit " << obj.unit.logsource_name()); - return true; - }; - - TerrainObject *new_target = find_in_radius(*this->entity->location, valid, BuildAction::search_tile_distance); - if (new_target != nullptr) { - this->entity->log(MSG(dbg) << "Found new building, queueing command"); - Command cmd(this->entity->get_attribute().player, &new_target->unit); - this->entity->queue_cmd(cmd); - } - else { - this->entity->log(MSG(dbg) << "Didn't find new building"); - } -} - -RepairAction::RepairAction(Unit *e, UnitReference tar) : - TargetAction{e, graphic_type::work, tar}, - timer{80}, - complete{false} { - if (!tar.is_valid()) { - // the target no longer exists - complete = true; - } - else { - Unit *target = tar.get(); - - if (!target->has_attribute(attr_type::building)) { - this->timer.set_interval(this->timer.get_interval() * 4); - } - - // cost formula: 0.5 * (target cost) / (target max hp) - auto &hp = target->get_attribute(); - auto &owner = this->entity->get_attribute(); - - // get the target unit's cost - this->cost += target->unit_type->cost.get(owner.player); - this->cost *= 0.5 / hp.hp; - - if (!owner.player.deduct(this->cost)) { - // no resources to start - this->complete = true; - } - } -} - -void RepairAction::update_in_range(unsigned int time, Unit *target_unit) { - auto &hp = target_unit->get_attribute(); - auto &dm = target_unit->get_attribute(); - - if (dm.hp >= hp.hp) { - // repaired by something else - this->complete = true; - } - else if (this->timer.update(time)) { - dm.hp += 1; - - if (dm.hp >= hp.hp) { - this->complete = true; - } - - if (!this->complete) { - auto &owner = this->entity->get_attribute(); - if (!owner.player.deduct(this->cost)) { - // no resources to continue - this->complete = true; - } - } - } - - // inc frame - this->frame += time * this->frame_rate / 2.5f; -} - -GatherAction::GatherAction(Unit *e, UnitReference tar) : - TargetAction{e, graphic_type::work, tar}, - complete{false}, - target_resource{true}, - target{tar} { - Unit *target = this->target.get(); - this->resource_class = target->unit_type->unit_class; - - // handle unit type changes based on resource class - if (this->entity->has_attribute(attr_type::multitype)) { - this->entity->get_attribute().switchType(this->resource_class, this->entity); - } - - // set the type of gatherer - auto &worker_resource = this->entity->get_attribute(); - if (target->has_attribute(attr_type::resource)) { - auto &resource_attr = target->get_attribute(); - if (worker_resource.resource_type != resource_attr.resource_type) { - worker_resource.amount = 0; - } - worker_resource.resource_type = resource_attr.resource_type; - } - else { - throw std::invalid_argument("Unit reference has no resource attribute"); - } -} - -GatherAction::~GatherAction() = default; - -void GatherAction::update_in_range(unsigned int time, Unit *targeted_resource) { - auto &worker = this->entity->get_attribute(); - auto &worker_resource = this->entity->get_attribute(); - if (this->target_resource) { - // the targets attributes - if (!targeted_resource->has_attribute(attr_type::resource)) { - complete = true; - return; - } - - // attack objects which have hitpoints (trees, hunt, sheep) - if (this->entity->has_attribute(attr_type::owner) && targeted_resource->has_attribute(attr_type::damaged)) { - auto &pl = this->entity->get_attribute(); - auto &dm = targeted_resource->get_attribute(); - - // only attack if hitpoints remain - if (dm.hp > 0) { - Command cmd(pl.player, targeted_resource); - cmd.set_ability(ability_type::attack); - cmd.add_flag(command_flag::attack_res); - this->entity->queue_cmd(cmd); - return; - } - } - - // need to return to dropsite - if (worker_resource.amount > worker.capacity) { - // move to dropsite location - this->target_resource = false; - this->set_target(this->nearest_dropsite(worker_resource.resource_type)); - } - else { - auto &resource_attr = targeted_resource->get_attribute(); - if (resource_attr.amount <= 0.0) { - // when the resource runs out - if (worker_resource.amount > 0.0) { - this->target_resource = false; - this->set_target(this->nearest_dropsite(worker_resource.resource_type)); - } - else { - this->complete = true; - } - } - else { - // transfer using gather rate - double amount = worker.gather_rate[worker_resource.resource_type] - * resource_attr.gather_rate * time; - worker_resource.amount += amount; - resource_attr.amount -= amount; - } - } - } - else { - // dropsite has been reached - // add value to player stockpile - auto &player = this->entity->get_attribute().player; - player.receive(worker_resource.resource_type, worker_resource.amount); - worker_resource.amount = 0.0; - - // make sure the resource still exists - if (this->target.is_valid() && this->target.get()->get_attribute().amount > 0.0) { - // return to resource collection - this->target_resource = true; - this->set_target(this->target); - } - else { - // resource depleted - this->complete = true; - } - } - - // inc frame - this->frame += time * this->frame_rate / 3.0f; -} - -void GatherAction::on_completion_in_range(int target_type) { - // find a different target with same type - TerrainObject *new_target = nullptr; - new_target = find_near(*this->entity->location, - [target_type](const TerrainObject &obj) { - return obj.unit.unit_type->id() == target_type && !obj.unit.has_attribute(attr_type::worker) && obj.unit.has_attribute(attr_type::resource) && obj.unit.get_attribute().amount > 0.0; - }); - - if (new_target) { - this->entity->log(MSG(dbg) << "auto retasking"); - auto &pl_attr = this->entity->get_attribute(); - Command cmd(pl_attr.player, &new_target->unit); - this->entity->queue_cmd(cmd); - } -} - -UnitReference GatherAction::nearest_dropsite(game_resource res_type) { - // find nearest dropsite from the targeted resource - auto ds = find_near(*this->target.get()->location, - [=, this](const TerrainObject &obj) { - if (not obj.unit.has_attribute(attr_type::building) or &obj.unit == this->entity or &obj.unit == this->target.get()) { - return false; - } - - return obj.unit.get_attribute().completed >= 1.0f && obj.unit.has_attribute(attr_type::owner) && obj.unit.get_attribute().player.owns(*this->entity) && obj.unit.has_attribute(attr_type::dropsite) && obj.unit.get_attribute().accepting_resource(res_type); - }); - - if (ds) { - return ds->unit.get_ref(); - } - else { - this->entity->log(MSG(dbg) << "no dropsite found"); - return UnitReference(); - } -} - -AttackAction::AttackAction(Unit *e, UnitReference tar) : - TargetAction{e, graphic_type::attack, tar, get_attack_range(e)}, - timer{500} { // TODO get fire rate from unit type - - // check if attacking a non resource unit - if (this->entity->has_attribute(attr_type::worker) && (!tar.get()->has_attribute(attr_type::resource) || tar.get()->has_attribute(attr_type::worker))) { - // switch to default villager graphics - if (this->entity->has_attribute(attr_type::multitype)) { - this->entity->get_attribute().switchType(gamedata::unit_classes::CIVILIAN, this->entity); - } - } - - // TODO rivit logic, a start inside the animation should be provided - this->timer.skip_to_trigger(); -} - -AttackAction::~AttackAction() = default; - -void AttackAction::update_in_range(unsigned int time, Unit *target_ptr) { - if (this->timer.update(time)) { - this->attack(*target_ptr); - } - - // inc frame - this->frame += time * this->current_graphics().at(graphic)->frame_count * 1.0f / this->timer.get_interval(); -} - -bool AttackAction::completed_in_range(Unit *target_ptr) const { - auto &dm = target_ptr->get_attribute(); - return dm.hp < 1; // is unit still alive? -} - -void AttackAction::attack(Unit &target) { - auto &attack = this->entity->get_attribute(); - if (attack.ptype) { - // add projectile to the game - this->fire_projectile(attack, target.location->pos.draw); - } - else { - this->damage_unit(target); - } -} - -void AttackAction::fire_projectile(const Attribute &att, const coord::phys3 &target) { - // container terrain and initial position - UnitContainer *container = this->entity->get_container(); - coord::phys3 current_pos = this->entity->location->pos.draw; - current_pos.up = att.init_height; - - // create using the producer - auto &player = this->entity->get_attribute().player; - auto projectile_ref = container->new_unit(*att.ptype, player, current_pos); - - // send towards target using a projectile ability (creates projectile motion action) - if (projectile_ref.is_valid()) { - auto projectile = projectile_ref.get(); - auto &projectile_attr = projectile->get_attribute(); - projectile_attr.launcher = this->entity->get_ref(); - projectile_attr.launched = true; - projectile->push_action(std::make_unique(projectile, target), true); - } - else { - this->entity->log(MSG(dbg) << "projectile launch failed"); - } -} - - -HealAction::HealAction(Unit *e, UnitReference tar) : - TargetAction{e, graphic_type::heal, tar, get_attack_range(e)}, - timer{this->entity->get_attribute().rate} { -} - -HealAction::~HealAction() = default; - -void HealAction::update_in_range(unsigned int time, Unit *target_ptr) { - if (this->timer.update(time)) { - this->heal(*target_ptr); - } - - // inc frame - this->frame += time * this->current_graphics().at(graphic)->frame_count * 1.0f / this->timer.get_interval(); -} - -bool HealAction::completed_in_range(Unit *target_ptr) const { - auto &hp = target_ptr->get_attribute(); - auto &dm = target_ptr->get_attribute(); - return dm.hp >= hp.hp; // is unit at full hitpoints? -} - -void HealAction::heal(Unit &target) { - auto &heal = this->entity->get_attribute(); - - // TODO move to separate function heal_unit (like damage_unit)? - // heal object - if (target.has_attribute(attr_type::hitpoints) && target.has_attribute(attr_type::damaged)) { - auto &hp = target.get_attribute(); - auto &dm = target.get_attribute(); - if ((dm.hp + heal.life) < hp.hp) { - dm.hp += heal.life; - } - else { - dm.hp = hp.hp; - } - } -} - - -ConvertAction::ConvertAction(Unit *e, UnitReference tar) : - TargetAction{e, graphic_type::attack, tar}, - complete{.0f} { -} - -void ConvertAction::update_in_range(unsigned int, Unit *) {} - -ProjectileAction::ProjectileAction(Unit *e, coord::phys3 target) : - UnitAction{e, graphic_type::standing}, - has_hit{false} { - // find speed to move - auto &sp_attr = this->entity->get_attribute(); - double projectile_speed = sp_attr.unit_speed.to_double(); - - // arc of projectile - auto &pr_attr = this->entity->get_attribute(); - float projectile_arc = pr_attr.projectile_arc; - - // distance and time to target - coord::phys3_delta d = target - this->entity->location->pos.draw; - double distance_to_target = d.length(); - double flight_time = distance_to_target / projectile_speed; - - - if (projectile_arc < 0) { - // TODO negative values probably indicate something - projectile_arc += 0.2; - } - - // now figure gravity from arc parameter - // TODO projectile arc is the ratio between horizontal and - // vertical components of the initial direction - this->grav = 0.01f * (exp(pow(projectile_arc, 0.5f)) - 1) * projectile_speed; - - // inital launch direction - auto &d_attr = this->entity->get_attribute(); - d_attr.unit_dir = d * (projectile_speed / distance_to_target); - - // account for initial height - coord::phys_t initial_height = this->entity->location->pos.draw.up; - d_attr.unit_dir.up = coord::phys_t((grav * flight_time) / 2) - (initial_height * (1 / flight_time)); -} - -ProjectileAction::~ProjectileAction() = default; - -void ProjectileAction::update(unsigned int time) { - auto &d_attr = this->entity->get_attribute(); - - // apply gravity - d_attr.unit_dir.up -= this->grav * time; - - coord::phys3 new_position = this->entity->location->pos.draw + d_attr.unit_dir * time; - if (!this->entity->location->move(new_position)) { - // TODO implement friendly_fire (now friendly_fire is always on), attack_attribute.friendly_fire - - // find object which was hit - auto terrain = this->entity->location->get_terrain(); - TileContent *tc = terrain->get_data(new_position.to_tile3().to_tile()); - if (tc && !tc->obj.empty()) { - for (auto obj_location : tc->obj) { - if (this->entity->location.get() != obj_location && obj_location->check_collisions()) { - this->damage_unit(obj_location->unit); - break; - } - } - } - - // TODO implement area of effect, attack_attribute.area_of_effect - - has_hit = true; - } - - // inc frame - this->frame += time * this->frame_rate; -} - -void ProjectileAction::on_completion() {} - -bool ProjectileAction::completed() const { - return (has_hit || this->entity->location->pos.draw.up <= 0); -} - -} // namespace openage diff --git a/libopenage/unit/action.h b/libopenage/unit/action.h deleted file mode 100644 index 60299dc577..0000000000 --- a/libopenage/unit/action.h +++ /dev/null @@ -1,736 +0,0 @@ -// Copyright 2014-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include - -#include "../gamestate/old/resource.h" -#include "../pathfinding/path.h" -#include "attribute.h" -#include "research.h" -#include "unit.h" -#include "unit_container.h" - -namespace openage { - -class TerrainSearch; - -// TODO use a type instead of unsigned int for time - -/** - * A interval triggering timer used in actions. - * TODO find a better name for triggers - */ -class IntervalTimer { -public: - /** - * Constructs a timer with a given interval - */ - IntervalTimer(unsigned int interval); - - /** - * Constructs a timer with a given interval which will - * stop after a given number of triggers. - */ - IntervalTimer(unsigned int interval, int max_triggers); - - void skip_to_trigger(); - - bool update(unsigned int time); - - /** - * Returns the time until the next trigger - */ - unsigned int get_time_left() const; - - float get_progress() const; - - /** - * Returns true if at least one interval has passed. - */ - bool has_triggers() const; - - /** - * Returns true if the interval passed have reached the max. - */ - bool finished() const; - - /** - * Returns the number of intervals passed. - */ - int get_triggers() const { - return this->triggers; - } - - unsigned int get_interval() const { - return this->interval; - } - - void set_interval(unsigned int interval) { - this->interval = interval; - } - -private: - unsigned int interval; - - int max_triggers; - - unsigned int time_left; - - int triggers; -}; - - -/** - * Actions can be pushed onto any units action stack - * - * Each update cycle will perform the update function of the - * action on top of this stack - */ -class UnitAction { -public: - /** - * Require unit to be updated and an initial graphic type - */ - UnitAction(Unit *u, graphic_type initial_gt); - - virtual ~UnitAction() {} - - /** - * type of graphic this action should use - */ - graphic_type type() const; - - /** - * frame number to use on the current graphic - */ - float current_frame() const; - - /** - * each action has its own update functionality which gets called when this - * is the active action - */ - virtual void update(unsigned int time) = 0; - - /** - * action to perform when popped from a units action stack - */ - virtual void on_completion() = 0; - - /** - * gets called for all actions on stack each update cycle - * @return true when action is completed so it and everything above it can be popped - */ - virtual bool completed() const = 0; - - /** - * checks if the action can be interrupted, allowing it to be popped if the user - * specifies a new action, if false the action must reach a completed state - * before removal - * eg dead action must be completed and cannot be discarded - */ - virtual bool allow_interrupt() const = 0; - - /** - * control whether stack can discard the action automatically and - * should the stack be modifiable when this action is on top - * - * if true this action must complete and will not allow new actions - * to be pushed while it is active and also does not update the secondary actions - */ - virtual bool allow_control() const = 0; - - /** - * debug string to identify action types - */ - virtual std::string name() const = 0; - - /** - * determines which graphic should be used for drawing this unit - * finds the default graphic using the units type, used by most actions - * - * this virtual function is overriden for special cases such as - * villager task graphics - */ - virtual const graphic_set ¤t_graphics() const; - - void draw_debug(const LegacyEngine &engine); - - /** - * common functions for actions - */ - void face_towards(const coord::phys3 pos); - - /** - * Damage a unit, returns true if the unit was killed in the process - */ - bool damage_unit(Unit &target); - - void move_to(Unit &target, bool use_range = true); - - /** - * produce debug info such as visualising paths - */ - static bool show_debug; - - /** - * a small distance to which units are considered touching - * when within this distance - */ - static coord::phys_t adjacent_range(Unit *u); - - /** - * looks at an ranged attributes on the unit - * otherwise returns same as adjacent_range() - */ - static coord::phys_t get_attack_range(Unit *u); - - /** - * looks at heal attribute on the unit - * otherwise returns same as adjacent_range() - */ - static coord::phys_t get_heal_range(Unit *u); - -protected: - /** - * the entity being updated - */ - Unit *entity; - - /** - * common graphic controls - */ - graphic_type graphic; - float frame; - float frame_rate; - - /** - * additional drawing for debug purposes - */ - std::function debug_draw_action; -}; - -/** - * Base class for actions which target another unit such as - * gather, attack, heal and convert - * TODO implement min range - */ -class TargetAction : public UnitAction { -public: - /** - * action_rad is how close a unit must come to another - * unit to be considered to touch the other, for example in - * gathering resource and melee attack - */ - TargetAction(Unit *e, graphic_type gt, UnitReference r, coord::phys_t action_rad); - - /** - * this constructor uses the default action radius formula which will - * bring the object as near to the target as the pathing grid will allow. - */ - TargetAction(Unit *e, graphic_type gt, UnitReference r); - virtual ~TargetAction() {} - - void update(unsigned int time) override; - void on_completion() override; - bool completed() const override; - bool allow_interrupt() const override { - return true; - } - bool allow_control() const override { - return true; - } - virtual std::string name() const override = 0; - - /** - * Control units action when in range of the target - */ - virtual void update_in_range(unsigned int, Unit *) = 0; - virtual void on_completion_in_range(int target_type) = 0; - virtual bool completed_in_range(Unit *) const = 0; - - coord::phys_t distance_to_target(); - Unit *update_distance(); - - UnitReference get_target() const; - int get_target_type_id() const; - - /** - * changes target, ending action when new target is invalid - */ - void set_target(UnitReference new_target); - -private: - UnitReference target; - int target_type_id; - int repath_attempts; - bool end_action; - - /** - * tracks distance to target from last update - */ - coord::phys_t dist_to_target, radius; -}; - -/** - * plays a fixed number of frames for the units dying animation - */ -class DecayAction : public UnitAction { -public: - DecayAction(Unit *e); - virtual ~DecayAction() {} - - void update(unsigned int time) override; - void on_completion() override; - bool completed() const override; - bool allow_interrupt() const override { - return false; - } - bool allow_control() const override { - return false; - } - std::string name() const override { - return "decay"; - } - -private: - float end_frame; -}; - -/** - * plays a fixed number of frames for the units dying animation - */ -class DeadAction : public UnitAction { -public: - DeadAction( - Unit *e, - std::function on_complete = []() {}); - virtual ~DeadAction() {} - - void update(unsigned int time) override; - void on_completion() override; - bool completed() const override; - bool allow_interrupt() const override { - return false; - } - bool allow_control() const override { - return false; - } - std::string name() const override { - return "dead"; - } - -private: - float end_frame; - std::function on_complete_func; -}; - -/** - * places an idle action on the stack once building is complete - */ -class FoundationAction : public UnitAction { -public: - FoundationAction(Unit *e, bool add_destuction = false); - virtual ~FoundationAction() {} - - void update(unsigned int time) override; - void on_completion() override; - bool completed() const override; - bool allow_interrupt() const override { - return true; - } - bool allow_control() const override { - return false; - } - std::string name() const override { - return "foundation"; - } - -private: - bool add_destruct_effect, cancel; -}; - -/** - * keeps an entity in a fixed position - */ -class IdleAction : public UnitAction { -public: - IdleAction(Unit *e); - virtual ~IdleAction() {} - - void update(unsigned int time) override; - void on_completion() override; - bool completed() const override; - bool allow_interrupt() const override { - return false; - } - bool allow_control() const override { - return true; - } - std::string name() const override { - return "idle"; - } - -private: - // look for auto task actions - std::shared_ptr search; - ability_set auto_abilities; -}; - -/** - * moves an entity to another location - */ -class MoveAction : public UnitAction { -public: - /** - * moves unit to a given fixed location - */ - MoveAction(Unit *e, coord::phys3 tar, bool repath = true); - - /** - * moves a unit to within a distance to another unit - */ - MoveAction(Unit *e, UnitReference tar, coord::phys_t within_range); - virtual ~MoveAction(); - - void update(unsigned int time) override; - void on_completion() override; - bool completed() const override; - bool allow_interrupt() const override { - return true; - } - bool allow_control() const override { - return true; - } - std::string name() const override { - return "move"; - } - - coord::phys3 next_waypoint() const; - -private: - UnitReference unit_target; - coord::phys3 target; - - // how near the units should come to target - coord::phys_t distance_to_target, radius; - - path::Path path; - - // should a new path be found if unit gets blocked - bool allow_repath, end_action; - - void initialise(); - - /** - * use a star to find a path to target - */ - void set_path(); - - /** - * updates the distance_to_target value - */ - void set_distance(); -}; - -/** - * garrison inside a building - */ -class GarrisonAction : public TargetAction { -public: - GarrisonAction(Unit *e, UnitReference build); - virtual ~GarrisonAction() {} - - void update_in_range(unsigned int time, Unit *target_unit) override; - void on_completion_in_range(int) override {} - bool completed_in_range(Unit *) const override { - return this->complete; - } - std::string name() const override { - return "garrison"; - } - -private: - bool complete; -}; - -/** - * garrison inside a building - */ -class UngarrisonAction : public UnitAction { -public: - UngarrisonAction(Unit *e, const coord::phys3 &pos); - virtual ~UngarrisonAction() {} - - void update(unsigned int time) override; - void on_completion() override; - bool completed() const override { - return this->complete; - } - bool allow_interrupt() const override { - return true; - } - bool allow_control() const override { - return true; - } - std::string name() const override { - return "ungarrison"; - } - -private: - coord::phys3 position; - bool complete; -}; - -/** - * trains a new unit - */ -class TrainAction : public UnitAction { -public: - TrainAction(Unit *e, UnitType *pp); - virtual ~TrainAction() {} - - void update(unsigned int time) override; - void on_completion() override; - bool completed() const override { - return this->complete; - } - bool allow_interrupt() const override { - return false; - } - bool allow_control() const override { - return true; - } - std::string name() const override { - return "train"; - } - - float get_progress() const { - return this->timer.get_progress(); - } - -private: - UnitType *trained; - - IntervalTimer timer; - bool started; - bool complete; -}; - -/** - * trains a new unit - */ -class ResearchAction : public UnitAction { -public: - ResearchAction(Unit *e, Research *research); - virtual ~ResearchAction() {} - - void update(unsigned int time) override; - void on_completion() override; - bool completed() const override { - return this->complete; - } - bool allow_interrupt() const override { - return false; - } - bool allow_control() const override { - return true; - } - std::string name() const override { - return "train"; - } - - float get_progress() const { - return this->timer.get_progress(); - } - const ResearchType *get_research_type() const { - return this->research->type; - } - -private: - Research *research; - - IntervalTimer timer; - bool complete; -}; - -/** - * builds a building - */ -class BuildAction : public TargetAction { -public: - BuildAction(Unit *e, UnitReference foundation); - virtual ~BuildAction() {} - - void update_in_range(unsigned int time, Unit *target_unit) override; - void on_completion_in_range(int) override {} - bool completed_in_range(Unit *) const override { - return this->complete >= 1.0f; - } - void on_completion() override; - std::string name() const override { - return "build"; - } - - float get_progress() const { - return this->complete; - } - -private: - float complete, build_rate; - static constexpr float search_tile_distance = 9.0f; -}; - -/** - * repairs a unit - */ -class RepairAction : public TargetAction { -public: - RepairAction(Unit *e, UnitReference tar); - virtual ~RepairAction() {} - - void update_in_range(unsigned int time, Unit *target_unit) override; - void on_completion_in_range(int) override {} - bool completed_in_range(Unit *) const override { - return this->complete; - } - std::string name() const override { - return "repair"; - } - -private: - /** - * stores the cost of the repair for 1hp - */ - ResourceBundle cost; - - IntervalTimer timer; - bool complete; -}; - -/** - * gathers resource from another object - */ -class GatherAction : public TargetAction { -public: - GatherAction(Unit *e, UnitReference tar); - virtual ~GatherAction(); - - void update_in_range(unsigned int time, Unit *target_unit) override; - void on_completion_in_range(int target_type) override; - bool completed_in_range(Unit *) const override { - return this->complete; - } - std::string name() const override { - return "gather"; - } - -private: - bool complete, target_resource; - UnitReference target; - gamedata::unit_classes resource_class; - UnitReference nearest_dropsite(game_resource res_type); -}; - -/** - * attacks another unit - */ -class AttackAction : public TargetAction { -public: - AttackAction(Unit *e, UnitReference tar); - virtual ~AttackAction(); - - void update_in_range(unsigned int time, Unit *target_unit) override; - void on_completion_in_range(int) override {} - bool completed_in_range(Unit *) const override; - std::string name() const override { - return "attack"; - } - -private: - IntervalTimer timer; - - /** - * use attack action - */ - void attack(Unit &target); - - /** - * add a projectile game object which moves towards the target - */ - void fire_projectile(const Attribute &att, const coord::phys3 &target); -}; - -/** - * heals another unit - */ -class HealAction : public TargetAction { -public: - HealAction(Unit *e, UnitReference tar); - virtual ~HealAction(); - - void update_in_range(unsigned int time, Unit *target_unit) override; - void on_completion_in_range(int) override {} - bool completed_in_range(Unit *) const override; - std::string name() const override { - return "heal"; - } - -private: - IntervalTimer timer; - - /** - * use heal action - */ - void heal(Unit &target); -}; - -/** - * convert an object - */ -class ConvertAction : public TargetAction { -public: - ConvertAction(Unit *e, UnitReference tar); - virtual ~ConvertAction() {} - - void update_in_range(unsigned int time, Unit *target_unit) override; - void on_completion_in_range(int) override {} - bool completed_in_range(Unit *) const override { - return this->complete >= 1.0f; - } - std::string name() const override { - return "convert"; - } - -private: - float complete; -}; - -/** - * moves object to fly in a parabolic shape - */ -class ProjectileAction : public UnitAction { -public: - ProjectileAction(Unit *e, coord::phys3 target); - virtual ~ProjectileAction(); - - void update(unsigned int time) override; - void on_completion() override; - bool completed() const override; - bool allow_interrupt() const override { - return false; - } - bool allow_control() const override { - return false; - } - std::string name() const override { - return "projectile"; - } - -private: - double grav; - bool has_hit; -}; - -} // namespace openage diff --git a/libopenage/unit/attribute.cpp b/libopenage/unit/attribute.cpp deleted file mode 100644 index 6826bded35..0000000000 --- a/libopenage/unit/attribute.cpp +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2016-2017 the openage authors. See copying.md for legal info. - -#include "attribute.h" -#include "unit.h" -#include "unit_type.h" - -namespace openage { - -bool Attribute::accepting_resource(game_resource res) const { - return std::find(resource_types.begin(), resource_types.end(), res) != resource_types.end(); -} - -void Attribute::switchType(const gamedata::unit_classes cls, Unit *unit) const { - auto search = this->types.find(cls); - if (search != this->types.end()) { - auto &player = unit->get_attribute(); - search->second->reinitialise(unit, player.player); - } -} - -} // namespace openage diff --git a/libopenage/unit/attribute.h b/libopenage/unit/attribute.h deleted file mode 100644 index 2390c1dacc..0000000000 --- a/libopenage/unit/attribute.h +++ /dev/null @@ -1,650 +0,0 @@ -// Copyright 2014-2021 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include -#include - -#include "../coord/tile.h" -#include "../gamedata/unit_dummy.h" -#include "../terrain/terrain_object.h" -#include "../gamestate/old/resource.h" -#include "unit_container.h" - -namespace std { - -/** - * hasher for unit classes enum type - */ -template<> struct hash { - typedef underlying_type::type underlying_type; - - size_t operator()(const openage::gamedata::unit_classes &arg) const { - hash hasher; - return hasher(static_cast(arg)); - } -}; - -} // namespace std - -namespace openage { - -/** - * Types of action graphics - */ -enum class graphic_type { - construct, - shadow, - decay, - dying, - standing, - walking, - carrying, - attack, - heal, - work -}; - -class UnitTexture; - -/** - * Collection of graphics attached to each unit. - */ -using graphic_set = std::map>; - -/** - * List of unit's attribute types. - */ -enum class attr_type { - owner, - population, - damaged, - hitpoints, - armor, - attack, - formation, - heal, - speed, - direction, - projectile, - building, - dropsite, - resource, - resource_generator, - worker, - storage, - multitype, - garrison -}; - -/** - * List of unit's attack stance. - * Can be used for buildings also. - */ -enum class attack_stance { - aggressive, - defensive, - stand_ground, - do_nothing -}; - -/** - * List of unit's formation. - * Effect applys on a group of units. - */ -enum class attack_formation { - line, - staggered, - box, - flank -}; - - -/** - * this type gets specialized for each attribute - */ -template class Attribute; - -/** - * Wraps a templated attribute - */ -class AttributeContainer { -public: - AttributeContainer() {} - - AttributeContainer(attr_type t) - : - type{t} {} - - virtual ~AttributeContainer() = default; - - attr_type type; - - /** - * shared attributes are common across all units of - * one type, such as max hp, and gather rates - * - * non shared attributes include a units current hp, - * and the amount a villager is carrying - */ - virtual bool shared() const = 0; - - /** - * Produces an copy of the attribute. - */ - virtual std::shared_ptr copy() const = 0; -}; - -/** - * An unordered_map with a int key used as a type id - * and a unsigned int value used as the amount - */ -using typeamount_map = std::unordered_map; - -/** - * Wraps a templated shared attribute - * - * Shared attributes are common across all units of - * one type - */ -class SharedAttributeContainer: public AttributeContainer { -public: - - SharedAttributeContainer(attr_type t) - : - AttributeContainer{t} {} - - bool shared() const override { - return true; - } -}; - -/** - * Wraps a templated unshared attribute - * - * Shared attributes are copied for each unit of - * one type - */ -class UnsharedAttributeContainer: public AttributeContainer { -public: - - UnsharedAttributeContainer(attr_type t) - : - AttributeContainer{t} {} - - bool shared() const override { - return false; - } -}; - -// ----------------------------- -// attribute definitions go here -// ----------------------------- - -class Player; - -template<> class Attribute: public SharedAttributeContainer { -public: - Attribute(Player &p) - : - SharedAttributeContainer{attr_type::owner}, - player(p) {} - - std::shared_ptr copy() const override { - return std::make_shared>(*this); - } - - Player &player; -}; - -/** - * The max hitpoints and health bar information. - * TODO change bar information stucture - */ -template<> class Attribute: public SharedAttributeContainer { -public: - Attribute(unsigned int i) - : - SharedAttributeContainer{attr_type::hitpoints}, - hp{i} {} - - std::shared_ptr copy() const override { - return std::make_shared>(*this); - } - - /** - * The max hitpoints - */ - unsigned int hp; - float hp_bar_height; -}; - -/** - * The population capacity and the population demand. - */ -template<> class Attribute: public SharedAttributeContainer { -public: - Attribute(int demand, int capacity) - : - SharedAttributeContainer{attr_type::population}, - demand{demand}, - capacity{capacity} {} - - std::shared_ptr copy() const override { - return std::make_shared>(*this); - } - - int demand; - int capacity; -}; - -/** - * The current hitpoints. - * TODO add last damage taken timestamp - */ -template<> class Attribute: public UnsharedAttributeContainer { -public: - Attribute(unsigned int i) - : - UnsharedAttributeContainer{attr_type::damaged}, - hp{i} {} - - std::shared_ptr copy() const override { - return std::make_shared>(*this); - } - - /** - * The current hitpoint - */ - unsigned int hp; -}; - -template<> class Attribute: public SharedAttributeContainer { -public: - Attribute(typeamount_map a) - : - SharedAttributeContainer{attr_type::armor}, - armor{a} {} - - std::shared_ptr copy() const override { - return std::make_shared>(*this); - } - - typeamount_map armor; -}; - -/** - * TODO can a unit have multiple attacks such as villagers hunting map target classes onto attacks - * TODO remove the first constructor and the default values after (keep for now for compatibility) - */ -template<> class Attribute: public SharedAttributeContainer { -public: - // TODO remove (keep for testing) - // 4 = gamedata::hit_class::UNITS_MELEE (not exported at the moment) - Attribute(UnitType *type, coord::phys_t r, coord::phys_t h, unsigned int d) - : - Attribute{type, r, h, {{4, d}}} {} - - Attribute(UnitType *type, coord::phys_t r, coord::phys_t h, typeamount_map d, - coord::phys_t min_range=0, bool friendly_fire=false, - coord::phys_t area_of_effect=0) - : - SharedAttributeContainer{attr_type::attack}, - ptype{type}, - min_range{min_range}, - max_range{r}, - init_height{h}, - damage{d}, - friendly_fire{friendly_fire}, - area_of_effect{area_of_effect} {} - - std::shared_ptr copy() const override { - return std::make_shared>(*this); - } - - /** - * The projectile's unit type - */ - UnitType *ptype; - - /** - * The min range of the attack - * TODO not used - */ - coord::phys_t min_range; - - /** - * The max range of the attack - */ - coord::phys_t max_range; - - /** - * The height from which the projectile starts - */ - coord::phys_t init_height; - - typeamount_map damage; - - /** - * If the attack can damage allied (friendly) units. - * TODO not used - */ - bool friendly_fire; - - /** - * The radius of the area of effect of the attack or 0 if there is no area_of_effect. - * TODO not used - */ - coord::phys_t area_of_effect; -}; - -/** - * The attack stance and formation - * TODO store patrol and follow command information - */ -template<> class Attribute: public UnsharedAttributeContainer { -public: - - Attribute() - : - Attribute{attack_stance::do_nothing} {} - - Attribute(attack_stance stance) - : - UnsharedAttributeContainer{attr_type::formation}, - stance{stance}, - formation{attack_formation::line} {} - - std::shared_ptr copy() const override { - return std::make_shared>(*this); - } - - attack_stance stance; - attack_formation formation; -}; - -/** - * Healing capabilities. - */ -template<> class Attribute: public SharedAttributeContainer { -public: - Attribute(coord::phys_t r, unsigned int l, unsigned int ra) - : - SharedAttributeContainer{attr_type::heal}, - range{r}, - life{l}, - rate{ra} {} - - std::shared_ptr copy() const override { - return std::make_shared>(*this); - } - - /** - * The max range of the healing. - */ - coord::phys_t range; - - /** - * Life healed in each cycle - */ - unsigned int life; - - /** - * The period of each heal cycle - */ - unsigned int rate; -}; - -template<> class Attribute: public SharedAttributeContainer { -public: - Attribute(coord::phys_t sp) - : - SharedAttributeContainer{attr_type::speed}, - unit_speed{sp} {} - - std::shared_ptr copy() const override { - return std::make_shared>(*this); - } - - // TODO rename to default or normal - coord::phys_t unit_speed; -}; - -template<> class Attribute: public UnsharedAttributeContainer { -public: - Attribute(coord::phys3_delta dir) - : - UnsharedAttributeContainer{attr_type::direction}, - unit_dir(dir) {} - - std::shared_ptr copy() const override { - return std::make_shared>(*this); - } - - coord::phys3_delta unit_dir; -}; - -template<> class Attribute: public UnsharedAttributeContainer { -public: - Attribute(float arc) - : - UnsharedAttributeContainer{attr_type::projectile}, - projectile_arc{arc}, - launched{false} {} - - std::shared_ptr copy() const override { - return std::make_shared>(*this); - } - - float projectile_arc; - UnitReference launcher; - bool launched; -}; - -/** - * TODO revisit after unit training is improved - */ -template<> class Attribute: public UnsharedAttributeContainer { -public: - Attribute(int foundation_terrain, UnitType *pp, coord::phys3 gather_point) - : - UnsharedAttributeContainer{attr_type::building}, - completed{.0f}, - foundation_terrain{foundation_terrain}, - pp{pp}, - gather_point{gather_point} {} - - std::shared_ptr copy() const override { - return std::make_shared>(*this); - } - - float completed; - int foundation_terrain; - - // set the TerrainObject to this state - // once building has been completed - object_state completion_state; - - // TODO: list allowed trainable producers - UnitType *pp; - - /** - * The go to point after a unit is created. - */ - coord::phys3 gather_point; -}; - -/** - * The resources that are accepted to be dropped. - */ -template<> class Attribute: public SharedAttributeContainer { -public: - Attribute(std::vector types) - : - SharedAttributeContainer{attr_type::dropsite}, - resource_types{types} {} - - std::shared_ptr copy() const override { - return std::make_shared>(*this); - } - - bool accepting_resource(game_resource res) const; - - std::vector resource_types; -}; - -/** - * Resource capacity of a trees, mines, animal, worker etc. - */ -template<> class Attribute: public UnsharedAttributeContainer { -public: - Attribute() - : - Attribute{game_resource::food, 0} {} - - Attribute(game_resource type, double init_amount, double decay=0.0, double gather_rate=1.0) - : - UnsharedAttributeContainer{attr_type::resource}, - resource_type{type}, - amount{init_amount}, - decay{decay}, - gather_rate{gather_rate} {} - - std::shared_ptr copy() const override { - return std::make_shared>(*this); - } - - game_resource resource_type; - - double amount; - - /** - * The rate of decay - */ - double decay; - - /** - * The gather rate multiplier (1.0 is the identity) - */ - double gather_rate; - -}; - -/** - * Resource generator eg. relic. - * While a unit is idle and contains this attribute, it will generate resources for its owner. - * - * A rate of zero means that the generation is continuously and not in intervals. - */ -template<> class Attribute: public SharedAttributeContainer { -public: - - Attribute(ResourceBundle resources, double rate=0) - : - SharedAttributeContainer{attr_type::resource_generator}, - resources{resources}, - rate{rate} {} - - std::shared_ptr copy() const override { - return std::make_shared>(*this); - } - - ResourceBundle resources; - - double rate; - -}; - -/** - * The worker's capacity and gather rates. - */ -template<> class Attribute: public SharedAttributeContainer { -public: - Attribute() - : - SharedAttributeContainer{attr_type::worker} {} - - std::shared_ptr copy() const override { - return std::make_shared>(*this); - } - - /** - * The max number of resources that can be carried. - */ - double capacity; - - /** - * The gather rate for each resource. - * The ResourceBundle class is used but instead of amounts it stores gather rates. - */ - ResourceBundle gather_rate; -}; - -/** - * The worker's capacity and gather rates. - */ -template<> class Attribute: public SharedAttributeContainer { -public: - Attribute() - : - SharedAttributeContainer{attr_type::storage} {} - - std::shared_ptr copy() const override { - return std::make_shared>(*this); - } - - /** - * The capacity for each resource. - */ - ResourceBundle capacity; -}; - -class Unit; - -/** - * Stores the collection of unit types based on a unit class. - * It is used mostly for units with multiple graphics (villagers, trebuchets). - */ -template<> class Attribute: public SharedAttributeContainer { -public: - Attribute() - : - SharedAttributeContainer{attr_type::multitype} {} - - std::shared_ptr copy() const override { - return std::make_shared>(*this); - } - - /** - * Switch the type of a unit based on a given unit class - */ - void switchType(const gamedata::unit_classes cls, Unit *unit) const; - - /** - * The collection of unit class to unit type pairs - */ - std::unordered_map types; -}; - -/** - * Units put inside a building. - * TODO add capacity per type of unit - */ -template<> class Attribute: public UnsharedAttributeContainer { -public: - Attribute() - : - UnsharedAttributeContainer{attr_type::garrison} {} - - std::shared_ptr copy() const override { - return std::make_shared>(*this); - } - - /** - * The units that are garrisoned. - */ - std::vector content; -}; - -} // namespace openage diff --git a/libopenage/unit/attributes.cpp b/libopenage/unit/attributes.cpp deleted file mode 100644 index faa4fa0df5..0000000000 --- a/libopenage/unit/attributes.cpp +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2017-2017 the openage authors. See copying.md for legal info. - -#include "attributes.h" - -namespace openage { - -void Attributes::add(const std::shared_ptr attr) { - this->attrs[attr->type] = attr; -} - -void Attributes::add_copies(const Attributes &other) { - this->add_copies(other, true, true); -} - -void Attributes::add_copies(const Attributes &other, bool shared, bool unshared) { - for (auto &i : other.attrs) { - auto &attr = *i.second.get(); - - if (attr.shared()) { - if (shared) { - // pass self - this->add(i.second); - } - } - else if (unshared) { - // create copy - this->add(attr.copy()); - } - } -} - -bool Attributes::remove(const attr_type type) { - return this->attrs.erase(type) > 0; -} - -bool Attributes::has(const attr_type type) const { - return this->attrs.find(type) != this->attrs.end(); -} - -std::shared_ptr Attributes::get(const attr_type type) const { - return this->attrs.at(type); -} - -} // namespace openage diff --git a/libopenage/unit/attributes.h b/libopenage/unit/attributes.h deleted file mode 100644 index cc02ac1193..0000000000 --- a/libopenage/unit/attributes.h +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2017-2017 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -#include "attribute.h" - -namespace openage { - -/** - * Contains a group of attributes. - * Can contain only one attribute of each type. - */ -class Attributes { -public: - Attributes() {} - - /** - * Add an attribute or replace any attribute of the same type. - */ - void add(const std::shared_ptr attr); - - /** - * Add copies of all the attributes from the given Attributes. - */ - void add_copies(const Attributes &attrs); - - /** - * Add copies of all the attributes from the given Attributes. - * If shared is false, shared attributes are ignored. - * If unshared is false, unshared attributes are ignored. - */ - void add_copies(const Attributes &attrs, bool shared, bool unshared); - - /** - * Remove an attribute based on the type. - */ - bool remove(const attr_type type); - - /** - * Check if the attribute of the given type exists. - */ - bool has(const attr_type type) const; - - /** - * Get the attribute based on the type. - */ - std::shared_ptr get(const attr_type type) const; - - /** - * Get the attribute - */ - template - Attribute &get() const { - return *reinterpret_cast *>(this->attrs.at(T).get()); - } - -private: - - std::map> attrs; -}; - -} // namespace openage diff --git a/libopenage/unit/command.cpp b/libopenage/unit/command.cpp deleted file mode 100644 index ec83664394..0000000000 --- a/libopenage/unit/command.cpp +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2014-2017 the openage authors. See copying.md for legal info. - -#include "command.h" - -namespace openage { - -Command::Command(const Player &p, Unit *unit, bool haspos, UnitType *t, Research *res) - : - player(p), - has_pos{haspos}, - u{unit}, - unit_type{t}, - res{res} { - this->modifiers.set(); -} - -Command::Command(const Player &p, Unit *unit) - : - Command{p, unit, false, nullptr, nullptr} { -} - -Command::Command(const Player &p, coord::phys3 position) - : - Command{p, nullptr, true, nullptr, nullptr} { - this->pos = position; -} - -Command::Command(const Player &p, Unit *unit, coord::phys3 position) - : - Command{p, unit, true, nullptr, nullptr} { - this->pos = position; -} - -Command::Command(const Player &p, UnitType *t) - : - Command{p, nullptr, false, t, nullptr} { -} - -Command::Command(const Player &p, Research *res) - : - Command{p, nullptr, false, nullptr, res} { -} - -Command::Command(const Player &p, UnitType *t, coord::phys3 position) - : - Command{p, nullptr, true, t, nullptr} { - this->pos = position; -} - -bool Command::has_unit() const { - return this->u; -} - -bool Command::has_position() const { - return this->has_pos; -} - -bool Command::has_type() const { - return this->unit_type; -} - -bool Command::has_research() const { - return this->res; -} - -Unit *Command::unit() const { - return this->u; -} - -coord::phys3 Command::position() const { - return this->pos; -} - -UnitType *Command::type() const { - return this->unit_type; -} - -Research *Command::research() const { - return this->res; -} - -void Command::set_ability(ability_type t) { - this->modifiers = 0; - this->modifiers[static_cast(t)] = true; -} - -void Command::set_ability_set(ability_set set) { - this->modifiers = set; -} - -const ability_set &Command::ability() const { - return this->modifiers; -} - -void Command::add_flag(command_flag flag) { - this->flags.insert(flag); -} - -bool Command::has_flag(command_flag flag) const { - return 0 < this->flags.count(flag); -} - -} // namespace openage diff --git a/libopenage/unit/command.h b/libopenage/unit/command.h deleted file mode 100644 index 4719e09e0b..0000000000 --- a/libopenage/unit/command.h +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright 2014-2018 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -#include "../coord/phys.h" -#include "ability.h" - -namespace openage { - -/** - * additional flags which may affect some abilities - */ -enum class command_flag { - interrupt, // the user directly issued this command, stopping other actions - use_range, // move command account for units range - attack_res // allow attack on a resource object -}; - -} // namespace openage - -namespace std { - -/** - * hasher for game command flags - */ -template<> -struct hash { - typedef underlying_type::type underlying_type; - - size_t operator()(const openage::command_flag &arg) const { - hash hasher; - return hasher(static_cast(arg)); - } -}; - -} // namespace std - -namespace openage { - -class Player; -class Research; -class Unit; -class UnitType; - -/* - * Game command from the ui - * TODO reorganize the names of the optional variables and their getters - */ -class Command { -public: - - /** - * target another unit - */ - Command(const Player &, Unit *unit); - - /** - * target a position - */ - Command(const Player &, coord::phys3 position); - - /** - * target another unit or a position - */ - Command(const Player &, Unit *unit, coord::phys3 position); - - /** - * select a type - */ - Command(const Player &, UnitType *t); - - /** - * select a research - */ - Command(const Player &, Research *res); - - /** - * place building foundation - */ - Command(const Player &, UnitType *, coord::phys3); - - bool has_unit() const; - bool has_position() const; - bool has_type() const; - bool has_research() const; - - Unit *unit() const; - coord::phys3 position() const; - UnitType *type() const; - Research *research() const; - - /** - * sets invoked ability type, no other type may be used if - * this gets set - * - * @param type allows a specific ability type to be used - * for example to set a unit to patrol rather than the default move - */ - void set_ability(ability_type t); - - /** - * restricts action to a set of possible ability types - */ - void set_ability_set(ability_set set); - - /** - * the ability types allowed to use this command - */ - const ability_set &ability() const; - - /** - * add addition option to this command - */ - void add_flag(command_flag flag); - - /** - * read the range setting - */ - bool has_flag(command_flag flag) const; - - /** - * player who created the command - */ - const Player &player; - -private: - - /** - * basic constructor, which shouldnt be used directly - */ - Command(const Player &, Unit *unit, bool haspos, UnitType *t, Research *res); - - bool has_pos; - Unit *u; - coord::phys3 pos = {0, 0, 0}; // TODO: make pos a c++17 optional - UnitType *unit_type; - Research *res; - - /** - * additional options - */ - std::unordered_set flags; - - /** - * select actions to use when targeting - */ - ability_set modifiers; - -}; - -} // namespace openage diff --git a/libopenage/unit/producer.cpp b/libopenage/unit/producer.cpp deleted file mode 100644 index 2e6ed907e6..0000000000 --- a/libopenage/unit/producer.cpp +++ /dev/null @@ -1,861 +0,0 @@ -// Copyright 2014-2023 the openage authors. See copying.md for legal info. - -#include - -#include "../legacy_engine.h" -#include "../gamedata/unit_dummy.h" -#include "../log/log.h" -#include "../terrain/terrain.h" -#include "../terrain/terrain_object.h" -#include "../terrain/terrain_outline.h" -#include "../util/strings.h" -#include "ability.h" -#include "action.h" -#include "producer.h" -#include "unit.h" -#include "unit_texture.h" - -/** @file - * Many values in this file are hardcoded, due to limited understanding of how the original - * game files work -- as more becomes known these will be removed. - * - * It is likely the conversion from gamedata to openage units will be done by the nyan - * system in future - */ - -namespace openage { - -std::unordered_set allowed_terrains(const gamedata::ground_type &restriction) { - // returns a terrain whitelist for a given restriction - // you can also define a blacklist for brevity, it will be converted and returned - std::unordered_set whitelist; - std::unordered_set blacklist; - - // 1, 14, and 15 are water, 2 is shore - if (restriction == gamedata::ground_type::WATER || restriction == gamedata::ground_type::WATER_0x0D || restriction == gamedata::ground_type::WATER_SHIP_0x03 || restriction == gamedata::ground_type::WATER_SHIP_0x0F) { - whitelist.insert(1); // water - whitelist.insert(2); // shore - whitelist.insert(4); // shallows - whitelist.insert(14); // medium water - whitelist.insert(15); // deep water - } - else if (restriction == gamedata::ground_type::SOLID) { - blacklist.insert(1); // water - blacklist.insert(4); // shallows - blacklist.insert(14); // medium water - blacklist.insert(15); // deep water - } - else if (restriction == gamedata::ground_type::FOUNDATION || restriction == gamedata::ground_type::NO_ICE_0x08 || restriction == gamedata::ground_type::FOREST) { - blacklist.insert(1); // water - blacklist.insert(4); // shallows - blacklist.insert(14); // medium water - blacklist.insert(15); // deep water - blacklist.insert(18); // ice - } - else { - log::log(MSG(warn) << "undefined terrain restriction, assuming solid"); - blacklist.insert(1); // water - blacklist.insert(4); // shallows - blacklist.insert(14); // medium water - blacklist.insert(15); // deep water - } - - // if we're using a blacklist, fill out a whitelist with everything not on it - if (blacklist.size() > 0) { - // Allow all terrains that are not on blacklist - for (int i = 0; i < 32; ++i) { - if (blacklist.count(i) == 0) { - whitelist.insert(i); - } - } - } - - return whitelist; -} - -ResourceBundle create_resource_cost(game_resource resource, int amount) { - ResourceBundle resources = ResourceBundle(); - resources[resource] = amount; - return resources; -} - -ObjectProducer::ObjectProducer(const Player &owner, const GameSpec &spec, const gamedata::unit_object *ud) : - UnitType(owner), - dataspec(spec), - unit_data(*ud), - terrain_outline{nullptr}, - default_tex{spec.get_unit_texture(ud->idle_graphic0)}, - dead_unit_id{ud->dead_unit_id} { - // copy the class type - this->unit_class = this->unit_data.unit_class; - this->icon = this->unit_data.icon_id; - - // for now just look for type names ending with "_D" - this->decay = unit_data.name.substr(unit_data.name.length() - 2) == "_D"; - - // find suitable sounds - int creation_sound = this->unit_data.train_sound_id; - int dying_sound = this->unit_data.dying_sound_id; - if (creation_sound == -1) { - creation_sound = this->unit_data.damage_sound_id; - } - if (creation_sound == -1) { - creation_sound = this->unit_data.selection_sound_id; - } - if (dying_sound == -1) { - dying_sound = 323; //generic explosion sound - } - on_create = spec.get_sound(creation_sound); - on_destroy = spec.get_sound(dying_sound); - - // convert the float to the discrete foundation size... - this->foundation_size = { - static_cast(this->unit_data.radius_x * 2), - static_cast(this->unit_data.radius_y * 2), - }; - - // shape of the outline - if (this->unit_data.obstruction_class > 1) { - this->terrain_outline = radial_outline(this->unit_data.radius_x); - } - else { - this->terrain_outline = square_outline(this->foundation_size); - } - - // graphic set - auto standing = spec.get_unit_texture(this->unit_data.idle_graphic0); - if (!standing) { - // indicates problems with data converion - throw Error(MSG(err) << "Unit id " << this->unit_data.id0 - << " has invalid graphic data, try reconverting the data"); - } - this->graphics[graphic_type::standing] = standing; - auto dying_tex = spec.get_unit_texture(this->unit_data.dying_graphic); - if (dying_tex) { - this->graphics[graphic_type::dying] = dying_tex; - } - - // default extra graphics - this->graphics[graphic_type::attack] = this->graphics[graphic_type::standing]; - this->graphics[graphic_type::work] = this->graphics[graphic_type::standing]; - - // pull extra graphics from unit commands - auto cmds = spec.get_command_data(this->unit_data.id0); - for (auto cmd : cmds) { - // same attack / work graphic - if (cmd->work_sprite_id == -1 && cmd->proceed_sprite_id > 0) { - auto task = spec.get_unit_texture(cmd->proceed_sprite_id); - if (task) { - this->graphics[graphic_type::work] = task; - this->graphics[graphic_type::attack] = task; - } - } - - // separate work and attack graphics - if (cmd->work_sprite_id > 0 && cmd->proceed_sprite_id > 0) { - auto attack = spec.get_unit_texture(cmd->proceed_sprite_id); - auto work = spec.get_unit_texture(cmd->work_sprite_id); - if (attack) { - this->graphics[graphic_type::attack] = attack; - } - if (work) { - this->graphics[graphic_type::work] = work; - } - } - - // villager carrying resources graphics - if (cmd->carry_sprite_id > 0) { - auto carry = spec.get_unit_texture(cmd->carry_sprite_id); - this->graphics[graphic_type::carrying] = carry; - } - } - - // TODO get cost, temp fixed cost of 50 food - this->cost.set(cost_type::constant, create_resource_cost(game_resource::food, 50)); -} - -ObjectProducer::~ObjectProducer() = default; - -int ObjectProducer::id() const { - return this->unit_data.id0; -} - -int ObjectProducer::parent_id() const { - int uid = this->unit_data.id0; - - // male types - if (uid == 156 || uid == 120 || uid == 592 || uid == 123 || uid == 579 || uid == 124) { - return 83; - } - - // female types - else if (uid == 222 || uid == 354 || uid == 590 || uid == 218 || uid == 581 || uid == 220) { - return 293; - } - return uid; -} - -std::string ObjectProducer::name() const { - return this->unit_data.name; -} - -void ObjectProducer::initialise(Unit *unit, Player &player) { - ENSURE(this->owner == player, "unit init from a UnitType of a wrong player which breaks tech levels"); - - // log attributes - unit->log(MSG(dbg) << "setting unit type " << this->unit_data.id0 << " " << this->unit_data.name); - - // reset existing attributes - unit->reset(); - - // initialise unit - unit->unit_type = this; - - // colour - unit->add_attribute(std::make_shared>(player)); - - // hitpoints if available - if (this->unit_data.hit_points > 0) { - unit->add_attribute(std::make_shared>(this->unit_data.hit_points)); - unit->add_attribute(std::make_shared>(this->unit_data.hit_points)); - } - - // collectable resources - if (this->unit_data.unit_class == gamedata::unit_classes::TREES) { - unit->add_attribute(std::make_shared>(game_resource::wood, 125)); - } - else if (this->unit_data.unit_class == gamedata::unit_classes::BERRY_BUSH) { - unit->add_attribute(std::make_shared>(game_resource::food, 100)); - } - else if (this->unit_data.unit_class == gamedata::unit_classes::SEA_FISH) { - unit->add_attribute(std::make_shared>(game_resource::food, 200)); - } - else if (this->unit_data.unit_class == gamedata::unit_classes::PREY_ANIMAL) { - unit->add_attribute(std::make_shared>(game_resource::food, 140)); - } - else if (this->unit_data.unit_class == gamedata::unit_classes::HERDABLE) { - unit->add_attribute(std::make_shared>(game_resource::food, 100, 0.1)); - } - else if (this->unit_data.unit_class == gamedata::unit_classes::GOLD_MINE) { - unit->add_attribute(std::make_shared>(game_resource::gold, 800)); - } - else if (this->unit_data.unit_class == gamedata::unit_classes::STONE_MINE) { - unit->add_attribute(std::make_shared>(game_resource::stone, 350)); - } - - // decaying units have a timed lifespan - if (decay) { - unit->push_action(std::make_unique(unit), true); - } - else { - // if destruction graphic is available - if (this->dead_unit_id) { - unit->push_action( - std::make_unique( - unit, - [this, unit, &player]() { - // modify unit to have dead type - UnitType *t = player.get_type(this->dead_unit_id); - if (t) { - t->initialise(unit, player); - } - }), - true); - } - else if (this->graphics.count(graphic_type::dying) > 0) { - unit->push_action(std::make_unique(unit), true); - } - - // the default action - unit->push_action(std::make_unique(unit), true); - } - - // give required abilitys - for (auto &a : this->type_abilities) { - unit->give_ability(a); - } -} - -TerrainObject *ObjectProducer::place(Unit *u, std::shared_ptr terrain, coord::phys3 init_pos) const { - // create new object with correct base shape - if (this->unit_data.obstruction_class > 1) { - u->make_location(this->unit_data.radius_x, this->terrain_outline); - } - else { - u->make_location(this->foundation_size, this->terrain_outline); - } - - // find set of allowed terrains - std::unordered_set terrains = allowed_terrains(this->unit_data.terrain_restriction); - - /* - * decide what terrain is passable using this lambda - * currently unit avoids water and tiles with another unit - * this function should be true if pos is a valid position of the object - */ - TerrainObject *obj_ptr = u->location.get(); - std::weak_ptr terrain_ptr = terrain; - u->location->passable = [obj_ptr, terrain_ptr, terrains](const coord::phys3 &pos) -> bool { - // if location is deleted, then so is this lambda (deleting terrain implies location is deleted) - // so locking objects here will not return null - auto terrain = terrain_ptr.lock(); - - // look at all tiles in the bases range - for (coord::tile check_pos : tile_list(obj_ptr->get_range(pos, *terrain))) { - TileContent *tc = terrain->get_data(check_pos); - - // invalid tile types - if (!tc || terrains.count(tc->terrain_id) == 0) { - return false; - } - - // compare with objects intersecting the units tile - // ensure no intersections with other objects - for (auto obj_cmp : tc->obj) { - if (obj_ptr != obj_cmp && obj_cmp->check_collisions() && obj_ptr->intersects(*obj_cmp, pos)) { - return false; - } - } - } - return true; - }; - - // u->location->draw = [u, obj_ptr](const Engine &e) { - // if (u->selected) { - // obj_ptr->draw_outline(e.coord); - // } - // u->draw(e); - // }; - - // try to place the obj, it knows best whether it will fit. - auto state = this->decay ? object_state::placed_no_collision : object_state::placed; - if (u->location->place(terrain, init_pos, state)) { - if (this->on_create) { - this->on_create->play(); - } - return u->location.get(); - } - - // placing at the given position failed - u->log(MSG(dbg) << "failed to place object"); - return nullptr; -} - -MovableProducer::MovableProducer(const Player &owner, const GameSpec &spec, const gamedata::projectile_unit *um) : - ObjectProducer(owner, spec, um), - unit_data(*um), - on_move{spec.get_sound(this->unit_data.command_sound_id)}, - on_attack{spec.get_sound(this->unit_data.command_sound_id)}, - projectile{this->unit_data.attack_projectile_primary_unit_id} { - // extra graphics if available - // villagers have invalid attack and walk graphics - // it seems these come from the command data instead - auto walk = spec.get_unit_texture(this->unit_data.move_graphics); - if (!walk) { - // use standing instead - walk = this->graphics[graphic_type::standing]; - } - this->graphics[graphic_type::walking] = walk; - - // reuse as carry graphic if not already set - if (this->graphics.count(graphic_type::carrying) == 0) { - this->graphics[graphic_type::carrying] = walk; - } - - auto attack = spec.get_unit_texture(this->unit_data.attack_sprite_id); - if (attack && attack->is_valid()) { - this->graphics[graphic_type::attack] = attack; - } - - // extra abilities - this->type_abilities.emplace_back(std::make_shared(this->on_move)); - this->type_abilities.emplace_back(std::make_shared(this->on_attack)); -} - -MovableProducer::~MovableProducer() = default; - -void MovableProducer::initialise(Unit *unit, Player &player) { - /* - * call base function - */ - ObjectProducer::initialise(unit, player); - - /* - * basic attributes - */ - if (!unit->has_attribute(attr_type::direction)) { - unit->add_attribute(std::make_shared>(coord::phys3_delta{1, 0, 0})); - } - - /* - * distance per millisecond -- consider original game speed - * where 1.5 in game seconds pass in 1 real second - */ - coord::phys_t sp = this->unit_data.speed / 666; - unit->add_attribute(std::make_shared>(sp)); - - // projectile of melee attacks - UnitType *proj_type = this->owner.get_type(this->projectile); - if (this->unit_data.attack_projectile_primary_unit_id > 0 && proj_type) { - // calculate requirements for ranged attacks - coord::phys_t range_phys = this->unit_data.weapon_range_max; - unit->add_attribute(std::make_shared>(proj_type, range_phys, 48000, 1)); - } - else { - unit->add_attribute(std::make_shared>(nullptr, 0, 0, 1)); - } - unit->add_attribute(std::make_shared>()); -} - -TerrainObject *MovableProducer::place(Unit *unit, std::shared_ptr terrain, coord::phys3 init_pos) const { - return ObjectProducer::place(unit, terrain, init_pos); -} - -LivingProducer::LivingProducer(const Player &owner, const GameSpec &spec, const gamedata::living_unit *ud) : - MovableProducer(owner, spec, ud), - unit_data(*ud) { - // extra abilities - this->type_abilities.emplace_back(std::make_shared(this->on_move)); -} - -LivingProducer::~LivingProducer() = default; - -void LivingProducer::initialise(Unit *unit, Player &player) { - /* - * call base function - */ - MovableProducer::initialise(unit, player); - - // population of 1 for all movable units - if (this->unit_data.unit_class != gamedata::unit_classes::HERDABLE) { - unit->add_attribute(std::make_shared>(1, 0)); - } - - // add worker attributes - if (this->unit_data.unit_class == gamedata::unit_classes::CIVILIAN) { - unit->add_attribute(std::make_shared>()); - unit->add_attribute(std::make_shared>()); - unit->add_attribute(std::make_shared>()); - - // add graphic ids for resource actions - auto &worker_attr = unit->get_attribute(); - worker_attr.capacity = 10.0; - worker_attr.gather_rate[game_resource::wood] = 0.002; - worker_attr.gather_rate[game_resource::food] = 0.002; - worker_attr.gather_rate[game_resource::gold] = 0.002; - worker_attr.gather_rate[game_resource::stone] = 0.002; - - auto &multitype_attr = unit->get_attribute(); - // currently not sure where the game data keeps these values - // todo PREY_ANIMAL SEA_FISH - if (this->parent_id() == 83) { - // male graphics - multitype_attr.types[gamedata::unit_classes::CIVILIAN] = this->parent_type(); // get default villager - multitype_attr.types[gamedata::unit_classes::BUILDING] = this->owner.get_type(156); // builder 118 - multitype_attr.types[gamedata::unit_classes::BERRY_BUSH] = this->owner.get_type(120); // forager - multitype_attr.types[gamedata::unit_classes::HERDABLE] = this->owner.get_type(592); // sheperd - multitype_attr.types[gamedata::unit_classes::TREES] = this->owner.get_type(123); // woodcutter - multitype_attr.types[gamedata::unit_classes::GOLD_MINE] = this->owner.get_type(579); // gold miner - multitype_attr.types[gamedata::unit_classes::STONE_MINE] = this->owner.get_type(124); // stone miner - } - else { - // female graphics - multitype_attr.types[gamedata::unit_classes::CIVILIAN] = this->parent_type(); // get default villager - multitype_attr.types[gamedata::unit_classes::BUILDING] = this->owner.get_type(222); // builder 212 - multitype_attr.types[gamedata::unit_classes::BERRY_BUSH] = this->owner.get_type(354); // forager - multitype_attr.types[gamedata::unit_classes::HERDABLE] = this->owner.get_type(590); // sheperd - multitype_attr.types[gamedata::unit_classes::TREES] = this->owner.get_type(218); // woodcutter - multitype_attr.types[gamedata::unit_classes::GOLD_MINE] = this->owner.get_type(581); // gold miner - multitype_attr.types[gamedata::unit_classes::STONE_MINE] = this->owner.get_type(220); // stone miner - } - unit->give_ability(std::make_shared(this->on_attack)); - unit->give_ability(std::make_shared(this->on_attack)); - unit->give_ability(std::make_shared(this->on_attack)); - } - else if (this->unit_data.unit_class == gamedata::unit_classes::FISHING_BOAT) { - unit->add_attribute(std::make_shared>()); - unit->add_attribute(std::make_shared>()); - - // add fishing abilites - auto &worker_attr = unit->get_attribute(); - worker_attr.capacity = 15.0; - worker_attr.gather_rate[game_resource::food] = 0.002; - - unit->give_ability(std::make_shared(this->on_attack)); - } -} - -TerrainObject *LivingProducer::place(Unit *unit, std::shared_ptr terrain, coord::phys3 init_pos) const { - return MovableProducer::place(unit, terrain, init_pos); -} - -BuildingProducer::BuildingProducer(const Player &owner, const GameSpec &spec, const gamedata::building_unit *ud) : - UnitType(owner), - unit_data{*ud}, - texture{spec.get_unit_texture(ud->idle_graphic0)}, - destroyed{spec.get_unit_texture(ud->dying_graphic)}, - projectile{this->unit_data.attack_projectile_primary_unit_id}, - foundation_terrain{ud->foundation_terrain_id}, - enable_collisions{this->unit_data.id0 != 109} { // 109 = town center - - // copy the class type - this->unit_class = this->unit_data.unit_class; - this->icon = this->unit_data.icon_id; - - // find suitable sounds - int creation_sound = this->unit_data.train_sound_id; - int dying_sound = this->unit_data.dying_sound_id; - if (creation_sound == -1) { - creation_sound = this->unit_data.damage_sound_id; - } - if (creation_sound == -1) { - creation_sound = this->unit_data.selection_sound_id; - } - if (dying_sound == -1) { - dying_sound = 323; //generic explosion sound - } - on_create = spec.get_sound(creation_sound); - on_destroy = spec.get_sound(dying_sound); - - // convert the float to the discrete foundation size... - this->foundation_size = { - static_cast(this->unit_data.radius_x * 2), - static_cast(this->unit_data.radius_y * 2), - }; - - // graphic set - this->graphics[graphic_type::construct] = spec.get_unit_texture(ud->construction_graphic_id); - this->graphics[graphic_type::standing] = spec.get_unit_texture(ud->idle_graphic0); - this->graphics[graphic_type::attack] = spec.get_unit_texture(ud->idle_graphic0); - auto dying_tex = spec.get_unit_texture(ud->dying_graphic); - if (dying_tex) { - this->graphics[graphic_type::dying] = dying_tex; - } - - this->terrain_outline = square_outline(this->foundation_size); - - // TODO get cost, temp fixed cost of 100 wood - this->cost.set(cost_type::constant, create_resource_cost(game_resource::wood, 100)); -} - -BuildingProducer::~BuildingProducer() = default; - -int BuildingProducer::id() const { - return this->unit_data.id0; -} - -int BuildingProducer::parent_id() const { - return this->unit_data.id0; -} - -std::string BuildingProducer::name() const { - return this->unit_data.name; -} - -void BuildingProducer::initialise(Unit *unit, Player &player) { - ENSURE(this->owner == player, "unit init from a UnitType of a wrong player which breaks tech levels"); - - // log type - unit->log(MSG(dbg) << "setting unit type " << this->unit_data.id0 << " " << this->unit_data.name); - - // initialize graphic set - unit->unit_type = this; - - auto player_attr = std::make_shared>(player); - unit->add_attribute(player_attr); - - // building specific attribute - auto build_attr = std::make_shared>( - this->foundation_terrain, - this->owner.get_type(293), // fem_villager, male is 83 - unit->location->pos.draw); - build_attr->completion_state = this->enable_collisions ? object_state::placed : object_state::placed_no_collision; - unit->add_attribute(build_attr); - - // garrison and hp for all buildings - unit->add_attribute(std::make_shared>()); - unit->add_attribute(std::make_shared>(this->unit_data.hit_points)); - unit->add_attribute(std::make_shared>(this->unit_data.hit_points)); - - // population - if (this->id() == 109 || this->id() == 70) { // Town center, House - unit->add_attribute(std::make_shared>(0, 5)); - } - else if (this->id() == 82) { // Castle - unit->add_attribute(std::make_shared>(0, 20)); - } - - // limits - if (this->id() == 109 || this->id() == 276) { // Town center, Wonder - this->have_limit = 2; // TODO change to 1, 2 is for testing - } - - bool has_destruct_graphic = this->destroyed != nullptr; - unit->push_action(std::make_unique(unit, has_destruct_graphic), true); - - UnitType *proj_type = this->owner.get_type(this->projectile); - if (this->unit_data.attack_projectile_primary_unit_id > 0 && proj_type) { - coord::phys_t range_phys = this->unit_data.weapon_range_max; - unit->add_attribute(std::make_shared>(proj_type, range_phys, 350000, 1)); - // formation is used only for the attack_stance - unit->add_attribute(std::make_shared>(attack_stance::aggressive)); - unit->give_ability(std::make_shared()); - } - - // dropsite attribute - std::vector accepted_resources = this->get_accepted_resources(); - if (accepted_resources.size() != 0) { - unit->add_attribute(std::make_shared>(accepted_resources)); - } - - // building can train new units and ungarrison - unit->give_ability(std::make_shared()); - unit->give_ability(std::make_shared()); - unit->give_ability(std::make_shared()); -} - -std::vector BuildingProducer::get_accepted_resources() { - //TODO use a more general approach instead of hard coded ids - - auto id_in = [=, this](std::initializer_list ids) { - return std::any_of(ids.begin(), ids.end(), [=, this](int n) { return n == this->id(); }); - }; - - if (this->id() == 109) { // Town center - return std::vector{ - game_resource::wood, - game_resource::food, - game_resource::gold, - game_resource::stone}; - } - else if (id_in({584, 585, 586, 587})) { // Mine - return std::vector{ - game_resource::gold, - game_resource::stone}; - } - else if (id_in({68, 129, 130, 131})) { // Mill - return std::vector{ - game_resource::food}; - } - else if (id_in({562, 563, 564, 565})) { // Lumberjack camp - return std::vector{ - game_resource::wood}; - } - - return std::vector(); -} - -TerrainObject *BuildingProducer::place(Unit *u, std::shared_ptr terrain, coord::phys3 init_pos) const { - // buildings have a square base - u->make_location(this->foundation_size, this->terrain_outline); - - /* - * decide what terrain is passable using this lambda - * currently unit avoids water and tiles with another unit - * this function should be true if pos is a valid position of the object - */ - TerrainObject *obj_ptr = u->location.get(); - std::weak_ptr terrain_ptr = terrain; - - // find set of allowed terrains - std::unordered_set terrains = allowed_terrains(this->unit_data.terrain_restriction); - - u->location->passable = [obj_ptr, terrain_ptr, terrains](const coord::phys3 &pos) -> bool { - auto terrain = terrain_ptr.lock(); - - // look at all tiles in the bases range - for (coord::tile check_pos : tile_list(obj_ptr->get_range(pos, *terrain))) { - TileContent *tc = terrain->get_data(check_pos); - - // check if terrains are suitable and free of content - if (!tc || !terrains.count(tc->terrain_id) || tc->obj.size()) { - return false; - } - } - - return true; - }; - - // drawing function - // bool draw_outline = this->enable_collisions; - // u->location->draw = [u, obj_ptr, draw_outline](const Engine &e) { - // if (u->selected && draw_outline) { - // obj_ptr->draw_outline(e.coord); - // } - // u->draw(e); - // }; - - // try to place the obj, it knows best whether it will fit. - auto state = object_state::floating; - if (!u->location->place(terrain, init_pos, state)) { - return nullptr; - } - - // annex objects - for (unsigned i = 0; i < 4; ++i) { - const gamedata::building_annex &annex = this->unit_data.building_annex.data[i]; - if (annex.unit_id > 0) { - // make objects for annex - coord::phys3 a_pos = u->location->pos.draw; - a_pos.ne += annex.misplaced0; - a_pos.se += annex.misplaced1; - this->make_annex(*u, terrain, annex.unit_id, a_pos, i == 0); - } - } - - // TODO: play sound once built - if (this->on_create) { - this->on_create->play(); - } - return u->location.get(); -} - -TerrainObject *BuildingProducer::make_annex(Unit &u, std::shared_ptr t, int annex_id, coord::phys3 annex_pos, bool c) const { - // for use in lambda drawing functions - auto annex_type = this->owner.get_type(annex_id); - if (!annex_type) { - u.log(MSG(warn) << "Invalid annex type id " << annex_id); - return nullptr; - } - - // foundation size - coord::tile_delta annex_foundation = annex_type->foundation_size; - - // producers place by the nw tile - coord::phys3 start_tile = annex_pos; - start_tile.ne -= annex_foundation.ne / 2.0; - start_tile.se -= annex_foundation.se / 2.0; - - // create and place on terrain - TerrainObject *annex_loc = u.location->make_annex(annex_foundation); - object_state state = c ? object_state::placed : object_state::placed_no_collision; - annex_loc->place(t, start_tile, state); - - // create special drawing functions for annexes, - annex_loc->draw = [annex_loc, annex_type, &u, c](const LegacyEngine &e) { - // hack which draws the outline in the right order - // removable once rendering system is improved - if (c && u.selected) { - // annex_loc->get_parent()->draw_outline(e.coord); - } - - // only draw if building is completed - if (u.has_attribute(attr_type::building) && u.get_attribute().completed >= 1.0f) { - u.draw(annex_loc, annex_type->graphics, e); - } - }; - return annex_loc; -} - -ProjectileProducer::ProjectileProducer(const Player &owner, const GameSpec &spec, const gamedata::missile_unit *pd) : - UnitType(owner), - unit_data{*pd}, - tex{spec.get_unit_texture(this->unit_data.idle_graphic0)}, - sh{spec.get_unit_texture(3379)}, // 3379 = general arrow shadow - destroyed{spec.get_unit_texture(this->unit_data.dying_graphic)} { - // copy the class type - this->unit_class = this->unit_data.unit_class; - - // graphic set - this->graphics[graphic_type::standing] = this->tex; - this->graphics[graphic_type::shadow] = this->sh; - if (destroyed) { - this->graphics[graphic_type::dying] = destroyed; - } - - // outline - terrain_outline = radial_outline(pd->radius_y); -} - -ProjectileProducer::~ProjectileProducer() = default; - -int ProjectileProducer::id() const { - return this->unit_data.id0; -} - -int ProjectileProducer::parent_id() const { - return this->unit_data.id0; -} - -std::string ProjectileProducer::name() const { - return this->unit_data.name; -} - -void ProjectileProducer::initialise(Unit *unit, Player &player) { - ENSURE(this->owner == player, "unit init from a UnitType of a wrong player which breaks tech levels"); - - // initialize graphic set - unit->unit_type = this; - - auto player_attr = std::make_shared>(player); - unit->add_attribute(player_attr); - - // projectile speed - coord::phys_t sp = this->unit_data.speed / 666; - unit->add_attribute(std::make_shared>(sp)); - unit->add_attribute(std::make_shared>(this->unit_data.projectile_arc)); - unit->add_attribute(std::make_shared>(coord::phys3_delta{1, 0, 0})); - - // if destruction graphic is available - if (this->destroyed) { - unit->push_action(std::make_unique(unit), true); - } -} - -TerrainObject *ProjectileProducer::place(Unit *u, std::shared_ptr terrain, coord::phys3 init_pos) const { - /* - * radial base shape without collision checking - */ - u->make_location(this->unit_data.radius_y, this->terrain_outline); - - TerrainObject *obj_ptr = u->location.get(); - std::weak_ptr terrain_ptr = terrain; - u->location->passable = [obj_ptr, u, terrain_ptr](const coord::phys3 &pos) -> bool { - if (pos.up > 64000) { - return true; - } - - // avoid intersections with launcher - Unit *launcher = nullptr; - auto terrain = terrain_ptr.lock(); - if (u->has_attribute(attr_type::projectile)) { - auto &pr_attr = u->get_attribute(); - if (pr_attr.launched && pr_attr.launcher.is_valid()) { - launcher = pr_attr.launcher.get(); - } - else { - return true; - } - } - else { - return true; - } - - // look at all tiles in the bases range - for (coord::tile check_pos : tile_list(obj_ptr->get_range(pos, *terrain))) { - TileContent *tc = terrain->get_data(check_pos); - if (!tc) - return false; - - // ensure no intersections with other objects - for (auto obj_cmp : tc->obj) { - if (obj_ptr != obj_cmp && &obj_cmp->unit != launcher && obj_cmp->check_collisions() && obj_ptr->intersects(*obj_cmp, pos)) { - return false; - } - } - } - return true; - }; - - u->location->draw = [u](const LegacyEngine &e) { - u->draw(e); - }; - - // try to place the obj, it knows best whether it will fit. - if (u->location->place(terrain, init_pos, object_state::placed_no_collision)) { - return u->location.get(); - } - return nullptr; -} - -} // namespace openage diff --git a/libopenage/unit/producer.h b/libopenage/unit/producer.h deleted file mode 100644 index 804df598cf..0000000000 --- a/libopenage/unit/producer.h +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright 2014-2021 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include -#include - -#include "../coord/tile.h" -#include "../gamedata/gamedata_dummy.h" -#include "../gamedata/graphic_dummy.h" -#include "../gamestate/old/player.h" -#include "unit.h" -#include "unit_type.h" - -namespace openage { - -class GameMain; -class GameSpec; -class Terrain; -class TerrainObject; -class Texture; -class Sound; - -class UnitAbility; -class UnitAction; -class UnitTexture; - - -std::unordered_set allowed_terrains(const gamedata::ground_type &restriction); - -/** - * base game data unit type - */ -class ObjectProducer: public UnitType { -public: - ObjectProducer(const Player &owner, const GameSpec &spec, const gamedata::unit_object *ud); - virtual ~ObjectProducer(); - - int id() const override; - int parent_id() const override; - std::string name() const override; - void initialise(Unit *, Player &) override; - TerrainObject *place(Unit *, std::shared_ptr, coord::phys3) const override; - -protected: - const GameSpec &dataspec; - const gamedata::unit_object unit_data; - - /** - * decaying objects have a timed lifespan - */ - bool decay; - - /** - * Sound id played when object is created or destroyed. - */ - const Sound *on_create; - const Sound *on_destroy; - std::shared_ptr terrain_outline; - std::shared_ptr default_tex; - int dead_unit_id; -}; - -/** - * movable unit types - */ -class MovableProducer: public ObjectProducer { -public: - MovableProducer(const Player &owner, const GameSpec &spec, const gamedata::projectile_unit *); - virtual ~MovableProducer(); - - void initialise(Unit *, Player &) override; - TerrainObject *place(Unit *, std::shared_ptr, coord::phys3) const override; - -protected: - const gamedata::projectile_unit unit_data; - UnitTexture *moving; - UnitTexture *attacking; - const Sound *on_move; - const Sound *on_attack; - int projectile; - -}; - -/** - * temporary class -- will be replaced with nyan system in future - * Stores graphics and attributes for a single unit type - * in aoe living units are derived from objects - */ -class LivingProducer: public MovableProducer { -public: - LivingProducer(const Player &owner, const GameSpec &spec, const gamedata::living_unit *); - virtual ~LivingProducer(); - - void initialise(Unit *, Player &) override; - TerrainObject *place(Unit *, std::shared_ptr, coord::phys3) const override; - -private: - const gamedata::living_unit unit_data; -}; - -/** - * Stores graphics and attributes for a building type - * Will be replaced with nyan system in future - * in aoe buildings are derived from living units - */ -class BuildingProducer: public UnitType { -public: - BuildingProducer(const Player &owner, - const GameSpec &spec, - const gamedata::building_unit *ud); - virtual ~BuildingProducer(); - - int id() const override; - int parent_id() const override; - std::string name() const override; - void initialise(Unit *, Player &) override; - TerrainObject *place(Unit *, std::shared_ptr, coord::phys3) const override; - -private: - const gamedata::building_unit unit_data; - - /** - * Sound id played when object is created or destroyed. - */ - const Sound *on_create; - const Sound *on_destroy; - std::shared_ptr terrain_outline; - std::shared_ptr texture; - std::shared_ptr destroyed; - int projectile; - int foundation_terrain; - std::vector get_accepted_resources(); - - /** - * used for objects like town centers or gates - * where the base does not apply collision checks - */ - bool enable_collisions; - - TerrainObject *make_annex(Unit &u, std::shared_ptr t, int annex_id, coord::phys3 annex_pos, bool c) const; -}; - -/** - * creates projectiles - * todo use MovableProducer as base class - */ -class ProjectileProducer: public UnitType { -public: - ProjectileProducer(const Player &owner, const GameSpec &spec, const gamedata::missile_unit *); - virtual ~ProjectileProducer(); - - int id() const override; - int parent_id() const override; - std::string name() const override; - void initialise(Unit *, Player &) override; - TerrainObject *place(Unit *, std::shared_ptr, coord::phys3) const override; - -private: - const gamedata::missile_unit unit_data; - std::shared_ptr terrain_outline; - std::shared_ptr tex; - std::shared_ptr sh; // shadow texture - std::shared_ptr destroyed; -}; - -} // namespace openage diff --git a/libopenage/unit/research.cpp b/libopenage/unit/research.cpp deleted file mode 100644 index 7161565583..0000000000 --- a/libopenage/unit/research.cpp +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2017-2018 the openage authors. See copying.md for legal info. - -#include "../gamestate/old/player.h" -#include "research.h" - - -namespace openage { - -ResearchType::ResearchType(Player &owner) - : - owner{owner} { -} - -std::shared_ptr ResearchType::initialise() const { - return std::make_shared(this); -} - -Research::Research(const ResearchType *type) - : - type{type}, - active_count{0}, - completed_count{0} { -} - -void Research::started() { - this->active_count += 1; -} - -void Research::stopped() { - this->active_count -= 1; -} - -void Research::completed() { - this->active_count -= 1; - this->completed_count += 1; -} - -bool Research::can_start() const { - return this->active_count + this->completed_count < this->type->get_max_repeats(); -} - -bool Research::is_active() const { - return this->active_count > 0; -} - -bool Research::is_researched() const { - return this->completed_count == this->type->get_max_repeats(); -} - -void Research::apply() { - // apply the patch - this->type->apply(); - - // perform category based actions - if (type->category() == research_category::age_advance) { - type->owner.advance_age(); - - } else if (type->category() == research_category::generic) { - // TODO implement a way to handle this category - } -} - -} // namespace openage diff --git a/libopenage/unit/research.h b/libopenage/unit/research.h deleted file mode 100644 index edd73ee8ed..0000000000 --- a/libopenage/unit/research.h +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright 2017-2017 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include - - -namespace openage { - -class Player; -class Research; -class ResourceCost; - -enum class research_category : int { - /** - * Research which modify unit type data. - */ - unit_upgrade, - /** - * Research which modify unit type data and also progresses the next age. - * Separate category for simplification. - */ - age_advance, - /** - * Research which modify unit type data and something else. - * (eg. see enemy line of sight) - */ - generic, - RESEARCH_CATEGORY_COUNT -}; - -/** - * Describes a research for a single player - * - * The get_max_repeats (with a default value of 1) can allow for a research to be - * performed multiple types - */ -class ResearchType { -public: - - ResearchType(Player &owner); - - /** - * Gets the unique id of this research type. - */ - virtual int id() const = 0; - - /** - * Gets the name of the research. - */ - virtual std::string name() const = 0; - - /** - * Gets the research category of the research. - */ - virtual research_category category() const = 0; - - /** - * Creates a single Research object. - * Must be called only once. - */ - std::shared_ptr initialise() const; - - /** - * The player who owns this research type - */ - Player &owner; - - /** - * How many times it can be researched. - * All classic researches have a value of 1. - */ - int get_max_repeats() const { return 1; } - - virtual unsigned int get_research_time() const = 0; - - virtual ResourceCost get_research_cost() const = 0; - - /** - * Performs the modifications (eg. apply patch to the unit types) - */ - virtual void apply() const = 0; - -protected: - -private: - -}; - -class NyanResearchType : public ResearchType { - // TODO POST-NYAN Implement - - NyanResearchType(Player &owner); - -}; - -/** - * At most one Research must exist for each ResearchType. - * - * A research represents how many times the research type has been completed (completed count) - * and also how many unit are researching it now (active count). - */ -class Research { -public: - - Research(const ResearchType *type); - - const ResearchType *type; - - /** - * Called when a unit started researching this research. - */ - void started(); - - /** - * Called when a unit stopped researching this research before completing it. - */ - void stopped(); - - /** - * Called when a unit completed researching this research. - */ - void completed(); - - /** - * Returns true if a unit can start researching this research. - */ - bool can_start() const; - - /** - * Returns true if any unit is researching this research. - */ - bool is_active() const; - - /** - * Returns true if it has nothing more to offer (reached max repeats). - */ - bool is_researched() const; - - /** - * Apply the modifications to the owner player. - */ - void apply(); - -protected: - - /** - * The number of units that are researching this research - */ - int active_count; - - /** - * The number of times this research has been completed - */ - int completed_count; - -private: - -}; - -} // namespace openage diff --git a/libopenage/unit/selection.cpp b/libopenage/unit/selection.cpp deleted file mode 100644 index 49ddd78440..0000000000 --- a/libopenage/unit/selection.cpp +++ /dev/null @@ -1,309 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#include "selection.h" - -#include -#include - -#include "../coord/tile.h" -#include "../legacy_engine.h" -#include "../log/log.h" -#include "../renderer/text.h" -#include "../terrain/terrain.h" -#include "action.h" -#include "command.h" -#include "producer.h" -#include "unit.h" - - -namespace openage { - -UnitSelection::UnitSelection(LegacyEngine *engine) : - selection_type{selection_type_t::nothing}, - drag_active{false}, - engine{engine} { -} - -bool UnitSelection::on_drawhud() { - // // the drag selection box - // if (drag_active) { - // coord::viewport s = this->start.to_viewport(this->engine->coord); - // coord::viewport e = this->end.to_viewport(this->engine->coord); - // glLineWidth(1); - // glColor3f(1.0, 1.0, 1.0); - // glBegin(GL_LINE_LOOP); { - // glVertex3f(s.x, s.y, 0); - // glVertex3f(e.x, s.y, 0); - // glVertex3f(e.x, e.y, 0); - // glVertex3f(s.x, e.y, 0); - // } - // glEnd(); - // } - // - // // draw hp bars for each selected unit - // glLineWidth(3); - // for (auto u : this->units) { - // if (u.second.is_valid()) { - // Unit *unit_ptr = u.second.get(); - // if (unit_ptr->location && - // unit_ptr->has_attribute(attr_type::hitpoints) && - // unit_ptr->has_attribute(attr_type::damaged)) { - // - // auto &hp = unit_ptr->get_attribute(); - // auto &dm = unit_ptr->get_attribute(); - // float percent = static_cast(dm.hp) / static_cast(hp.hp); - // int mid = percent * 28.0f - 14.0f; - // - // coord::phys3 &pos_phys3 = unit_ptr->location->pos.draw; - // auto pos = pos_phys3.to_viewport(this->engine->coord); - // // green part - // glColor3f(0.0, 1.0, 0.0); - // glBegin(GL_LINES); { - // glVertex3f(pos.x - 14, pos.y + 60, 0); - // glVertex3f(pos.x + mid, pos.y + 60, 0); - // } - // glEnd(); - // - // // red part - // glColor3f(1.0, 0.0, 0.0); - // glBegin(GL_LINES); { - // glVertex3f(pos.x + mid, pos.y + 60, 0); - // glVertex3f(pos.x + 14, pos.y + 60, 0); - // } - // glEnd(); - // } - // } - // } - // glColor3f(1.0, 1.0, 1.0); // reset - - // ui graphics 3404 and 3405 - return true; -} - -void UnitSelection::drag_begin(coord::camgame pos) { - this->start = pos; - this->end = pos; - this->drag_active = true; -} - -// called as the entry point for the selection -// from ActionMode. -void UnitSelection::drag_update(coord::camgame pos) { - if (!this->drag_active) { - this->drag_begin(pos); - } - this->end = pos; -} - -void UnitSelection::drag_release(const Player &player, Terrain *terrain, bool append) { - if (this->start == this->end) { - this->select_point(player, terrain, this->start, append); - } - else { - this->select_space(player, terrain, this->start, this->end, append); - } - this->drag_active = false; -} - -void UnitSelection::clear() { - for (auto u : this->units) { - if (u.second.is_valid()) { - u.second.get()->selected = false; - } - } - this->units.clear(); - this->selection_type = selection_type_t::nothing; -} - -void UnitSelection::toggle_unit(const Player &player, Unit *u, bool append) { - if (this->units.count(u->id) == 0) { - this->add_unit(player, u, append); - } - else { - this->remove_unit(u); - } -} - -void UnitSelection::add_unit(const Player &player, Unit *u, bool append) { - // Only select resources and units with hitpoints > 0 - if (u->has_attribute(attr_type::resource) || (u->has_attribute(attr_type::damaged) && u->get_attribute().hp > 0)) { - selection_type_t unit_type = get_unit_selection_type(player, u); - int unit_type_i = static_cast(unit_type); - int selection_type_i = static_cast(this->selection_type); - - if (unit_type_i > selection_type_i) { - // Don't select this unit as it has too low priority - return; - } - - if (unit_type_i < selection_type_i) { - // Upgrade selection to a higher priority selection - this->clear(); - this->selection_type = unit_type; - } - - // Can't select multiple enemies at once - if (not(unit_type == selection_type_t::own_units || (unit_type == selection_type_t::own_buildings && append))) { - this->clear(); - this->selection_type = unit_type; // Clear resets selection_type - } - - // Finally, add the unit to the selection - u->selected = true; - this->units[u->id] = u->get_ref(); - } -} - -void UnitSelection::remove_unit(Unit *u) { - u->selected = false; - this->units.erase(u->id); - - if (this->units.empty()) { - this->selection_type = selection_type_t::nothing; - } -} - -selection_type_t UnitSelection::get_selection_type() { - return this->selection_type; -} - -void UnitSelection::kill_unit(const Player &player) { - if (this->units.empty()) { - return; - } - - UnitReference &ref = this->units.begin()->second; - if (!ref.is_valid()) { - this->units.erase(this->units.begin()); - - if (this->units.empty()) { - this->selection_type = selection_type_t::nothing; - } - } - else { - Unit *u = ref.get(); - - // Check color: you can only kill your own units - if (u->is_own_unit(player)) { - this->remove_unit(u); - u->delete_unit(); - } - } -} - -bool UnitSelection::contains_builders(const Player &player) { - for (auto &it : units) { - if (it.second.is_valid() && it.second.get()->get_ability(ability_type::build) && it.second.get()->is_own_unit(player)) { - return true; - } - } - return false; -} - -bool UnitSelection::contains_military(const Player &player) { - for (auto &it : units) { - if (it.second.is_valid() && !it.second.get()->get_ability(ability_type::build) && it.second.get()->is_own_unit(player)) { - return true; - } - } - return false; -} - -void UnitSelection::select_point(const Player &player, Terrain *terrain, coord::camgame point, bool append) { - //if (!append) { - // this->clear(); - //} - // - //if (!terrain) { - // log::log(MSG(warn) << "selection terrain not specified"); - // return; - //} - // - //// find any object at selected point - //auto obj = terrain->obj_at_point(point.to_phys3(this->engine->coord)); - //if (obj) { - // this->toggle_unit(player, &obj->unit); - //} -} - -void UnitSelection::select_space(const Player &player, Terrain *terrain, coord::camgame point0, coord::camgame point1, bool append) { - if (!append) { - this->clear(); - } - - coord::camgame min{std::min(point0.x, point1.x), std::min(point0.y, point1.y)}; - coord::camgame max{std::max(point0.x, point1.x), std::max(point0.y, point1.y)}; - - // look at each tile in the range and find all units - //for (coord::tile check_pos : tiles_in_range(point0, point1, this->engine->coord)) { - // TileContent *tc = terrain->get_data(check_pos); - // if (tc) { - // // find objects within selection box - // for (auto unit_location : tc->obj) { - // coord::camgame pos = unit_location->pos.draw.to_camgame(this->engine->coord); - // - // if ((pos.x > min.x and pos.x < max.x) and (pos.y > min.y and pos.y < max.y)) { - // this->add_unit(player, &unit_location->unit, append); - // } - // } - // } - //} -} - -void UnitSelection::all_invoke(Command &cmd) { - for (auto u : this->units) { - if (u.second.is_valid() && u.second.get()->is_own_unit(cmd.player)) { - // allow unit to find best use of the command - // TODO: queue_cmd returns ability which allows playing of sound - u.second.get()->queue_cmd(cmd); - } - } -} - -selection_type_t UnitSelection::get_unit_selection_type(const Player &player, Unit *u) { - bool is_building = u->has_attribute(attr_type::building); - - // Check owner - // TODO implement allied units - if (u->is_own_unit(player)) { - return is_building ? selection_type_t::own_buildings : selection_type_t::own_units; - } - else { - return is_building ? selection_type_t::enemy_building : selection_type_t::enemy_unit; - } -} - -std::vector tiles_in_range(coord::camgame p1, coord::camgame p2, const coord::CoordManager &coord) { - // the remaining corners - coord::camgame p3 = coord::camgame{p1.x, p2.y}; - coord::camgame p4 = coord::camgame{p2.x, p1.y}; - coord::camgame pts[4]{p1, p2, p3, p4}; - - // find the range of tiles covered - coord::tile t1 = pts[0].to_tile(coord); - coord::tile min = t1; - coord::tile max = t1; - - for (unsigned i = 1; i < 4; ++i) { - coord::tile t = pts[i].to_tile(coord); - min.ne = std::min(min.ne, t.ne); - min.se = std::min(min.se, t.se); - max.ne = std::max(max.ne, t.ne); - max.se = std::max(max.se, t.se); - } - - // find all units in the boxed region - std::vector tiles; - coord::tile check_pos = min; - while (check_pos.ne <= max.ne) { - while (check_pos.se <= max.se) { - tiles.push_back(check_pos); - check_pos.se += 1; - } - check_pos.se = min.se; - check_pos.ne += 1; - } - return tiles; -} - -} // namespace openage diff --git a/libopenage/unit/selection.h b/libopenage/unit/selection.h deleted file mode 100644 index e946266009..0000000000 --- a/libopenage/unit/selection.h +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include - -#include "../coord/pixel.h" -#include "../handlers.h" -#include "ability.h" -#include "unit_container.h" - -namespace openage { - -class LegacyEngine; -class Terrain; - -std::vector tiles_in_range(coord::camgame p1, coord::camgame p2, const coord::CoordManager &coord); - -/** - * A selection of units always has a type - * You can't select units from multiple types at once - * Earlier types have precedence over later types - * - * So you can select a group of units, or a building (or multiple if append is on) - * Enemy units, enemy buildings and other objects may only be selected one at a time - */ -enum class selection_type_t { - own_units, - own_buildings, - enemy_unit, - enemy_building, - nothing -}; - -/** - * a user interface component allowing control of a selected group - */ -class UnitSelection : public HudHandler { -public: - UnitSelection(LegacyEngine *engine); - - bool on_drawhud() override; - void drag_begin(coord::camgame pos); - void drag_update(coord::camgame pos); - void drag_release(const Player &player, Terrain *terrain, bool append = false); - - void clear(); - - void toggle_unit(const Player &player, Unit *u, bool append = false); - void add_unit(const Player &player, Unit *u, bool append = false); - void remove_unit(Unit *u); - - selection_type_t get_selection_type(); - - /** - * kill a single unit in the selection - */ - void kill_unit(const Player &player); - - /** - * checks whether there are any builders in the selection - */ - bool contains_builders(const Player &player); - - /** - * checks whether there are any military units (i.e. non-builders) in the selection - */ - bool contains_military(const Player &player); - - /** - * point unit selection - */ - void select_point(const Player &player, Terrain *terrain, coord::camgame p, bool append = false); - - /** - * boxed unit selection - */ - void select_space(const Player &player, Terrain *terrain, coord::camgame p1, coord::camgame p2, bool append = false); - - /** - * uses command on every selected unit - */ - void all_invoke(Command &cmd); - - int get_units_count() const { - return this->units.size(); - } - - const UnitReference &get_first_unit() const { - return this->units.begin()->second; - } - -private: - /** - * Check whether the currently selected units may be selected at the same time - * If not, deselect some units - * This is the order in which the checks occur: - * Own units > own building(s) > enemy unit > enemy building > any object - * - * So you can select a group of units, or a building (or multiple if append is on) - * Enemy units, enemy buildings and other objects may only be selected one at a time - */ - selection_type_t get_unit_selection_type(const Player &player, Unit *); - - std::unordered_map units; - selection_type_t selection_type; - - bool drag_active; - // TODO: turn these into a C++17 optional - coord::camgame start = {0, 0}, end = {0, 0}; - - /** - * Engine where this selection is attached to. - */ - LegacyEngine *engine; -}; - -} // namespace openage diff --git a/libopenage/unit/type_pair.cpp b/libopenage/unit/type_pair.cpp deleted file mode 100644 index 725f63c072..0000000000 --- a/libopenage/unit/type_pair.cpp +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. - -#include "type_pair.h" - -namespace openage { - -UnitType::UnitType() {} - -bool UnitType::match(Unit *) { - // TODO: types - return true; -} - -TypePair::TypePair() {} - -} // namespace openage diff --git a/libopenage/unit/type_pair.h b/libopenage/unit/type_pair.h deleted file mode 100644 index 7b96cd45bd..0000000000 --- a/libopenage/unit/type_pair.h +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. - -#pragma once - -namespace openage { - -/** - * units in aoc have a class and a type - */ -class UnitType { -public: - UnitType(); - - /** - * the unit must have either same class or id as this - */ - bool match(class Unit *); - -private: - unsigned int class_id; - unsigned int unit_type_id; -}; - -/** - * many effects in aoc use a pair structure - * such as attack bonuses, armour and selection commands - */ -class TypePair { -public: - TypePair(); - -private: - UnitType a, b; - -}; - -} // namespace openage diff --git a/libopenage/unit/unit.cpp b/libopenage/unit/unit.cpp deleted file mode 100644 index 483d651bd1..0000000000 --- a/libopenage/unit/unit.cpp +++ /dev/null @@ -1,326 +0,0 @@ -// Copyright 2014-2023 the openage authors. See copying.md for legal info. - -#include -#include -#include - -#include "../legacy_engine.h" -#include "../terrain/terrain.h" - -#include "ability.h" -#include "action.h" -#include "command.h" -#include "producer.h" -#include "unit.h" -#include "unit_texture.h" - -namespace openage { - -Unit::Unit(UnitContainer *c, id_t id) : - id{id}, - unit_type{nullptr}, - selected{false}, - pop_destructables{false}, - container(c) {} - -Unit::~Unit() { - // remove any used location from the map - if (this->location) { - this->location->remove(); - } -} - -void Unit::reset() { - this->ability_available.clear(); - this->action_stack.clear(); - this->pop_destructables = false; -} - -bool Unit::has_action() const { - return !this->action_stack.empty(); -} - -bool Unit::accept_commands() const { - return (this->has_action() && this->top()->allow_control()); -} - -bool Unit::is_own_unit(const Player &player) { - return player.owns(*this); -} - -UnitAction *Unit::top() const { - if (this->action_stack.empty()) { - throw Error{MSG(err) << "Unit stack empty - no top action exists"}; - } - return this->action_stack.back().get(); -} - -UnitAction *Unit::before(const UnitAction *action) const { - auto start = std::find_if( - std::begin(this->action_stack), - std::end(this->action_stack), - [action](const std::unique_ptr &a) { - return action == a.get(); - }); - - if (start != std::begin(this->action_stack) && start != std::end(this->action_stack)) { - return (*(start - 1)).get(); - } - return nullptr; -} - -bool Unit::update(time_nsec_t lastframe_duration) { - // if unit is not on the map then do nothing - if (!this->location) { - return true; - } - - // unit is dead (not player controlled) - if (this->pop_destructables) { - this->erase_after( - [](std::unique_ptr &e) { - return e->allow_interrupt() || e->allow_control(); - }, - false); - } - - /* - * the active action is on top - */ - if (this->has_action()) { - // TODO: change the entire unit action timing - // to a higher resolution like nsecs or usecs. - - // time as float, in milliseconds. - auto time_elapsed = lastframe_duration / 1e6; - - this->top()->update(time_elapsed); - - // the top primary action specifies whether - // secondary actions are updated - if (this->top()->allow_control()) { - this->update_secondary(time_elapsed); - } - - // check completion of all primary actions, - // pop completed actions and anything above - this->erase_after( - [](std::unique_ptr &e) { - return e->completed(); - }); - } - - // apply new queued commands - this->apply_all_cmds(); - - return true; -} - -void Unit::update_secondary(int64_t time_elapsed) { - // update secondary actions and remove when completed - auto position_it = std::remove_if( - std::begin(this->action_secondary), - std::end(this->action_secondary), - [time_elapsed](std::unique_ptr &action) { - action->update(time_elapsed); - return action->completed(); - }); - this->action_secondary.erase(position_it, std::end(this->action_secondary)); -} - -void Unit::apply_all_cmds() { - std::lock_guard lock(this->command_queue_lock); - while (!this->command_queue.empty()) { - auto &action = this->command_queue.front(); - this->apply_cmd(action.first, action.second); - this->command_queue.pop(); - } -} - - -void Unit::apply_cmd(std::shared_ptr ability, const Command &cmd) { - // if the interrupt flag is set, discard ongoing actions - bool is_direct = cmd.has_flag(command_flag::interrupt); - if (is_direct) { - this->stop_actions(); - } - if (ability->can_invoke(*this, cmd)) { - ability->invoke(*this, cmd, is_direct); - } -} - - -void Unit::draw(const LegacyEngine &engine) { - // the top action decides the graphic type and action - this->draw(this->location.get(), this->top()->current_graphics(), engine); -} - - -void Unit::draw(TerrainObject *loc, const graphic_set &grpc, const LegacyEngine &engine) { - ENSURE(loc != nullptr, "there should always be a location for a placed unit"); - - auto top_action = this->top(); - auto draw_graphic = top_action->type(); - if (grpc.count(draw_graphic) == 0) { - this->log(MSG(warn) << "Graphic not available"); - return; - } - - // the texture to draw with - auto draw_texture = grpc.at(draw_graphic); - if (!draw_texture) { - this->log(MSG(warn) << "Graphic null"); - return; - } - - // frame specified by the current action - auto draw_frame = top_action->current_frame(); - this->draw(loc->pos.draw, draw_texture, draw_frame, engine); - - // draw a shadow if the graphic is available - if (grpc.count(graphic_type::shadow) > 0) { - auto draw_shadow = grpc.at(graphic_type::shadow); - if (draw_shadow) { - // position without height component - // TODO: terrain elevation - coord::phys3 shadow_pos = loc->pos.draw; - shadow_pos.up = 0; - this->draw(shadow_pos, draw_shadow, draw_frame, engine); - } - } - - // draw debug details - top_action->draw_debug(engine); -} - - -void Unit::draw(coord::phys3 draw_pos, std::shared_ptr graphic, unsigned int frame, const LegacyEngine &engine) { - // players color if available - unsigned color = 0; - if (this->has_attribute(attr_type::owner)) { - auto &own_attr = this->get_attribute(); - color = own_attr.player.color; - } - - // check if object has a direction - if (this->has_attribute(attr_type::direction)) { - // directional textures - auto &d_attr = this->get_attribute(); - coord::phys3_delta draw_dir = d_attr.unit_dir; - // graphic->draw(engine.coord, draw_pos.to_camgame(engine.coord), draw_dir, frame, color); - } - else { - // graphic->draw(engine.coord, draw_pos.to_camgame(engine.coord), frame, color); - } -} - -void Unit::give_ability(std::shared_ptr ability) { - this->ability_available.emplace(std::make_pair(ability->type(), ability)); -} - -UnitAbility *Unit::get_ability(ability_type type) { - if (this->ability_available.count(type) > 0) { - return this->ability_available[type].get(); - } - return nullptr; -} - -void Unit::push_action(std::unique_ptr action, bool force) { - // unit not being deleted -- can control unit - if (force || this->accept_commands()) { - this->action_stack.push_back(std::move(action)); - } -} - -void Unit::secondary_action(std::unique_ptr action) { - this->action_secondary.push_back(std::move(action)); -} - -void Unit::add_attribute(std::shared_ptr attr) { - this->attributes.add(attr); -} - -void Unit::add_attributes(const Attributes &attr) { - this->attributes.add_copies(attr); -} - -void Unit::add_attributes(const Attributes &attr, bool shared, bool unshared) { - this->attributes.add_copies(attr, shared, unshared); -} - -bool Unit::has_attribute(attr_type type) const { - return this->attributes.has(type); -} - -std::shared_ptr Unit::queue_cmd(const Command &cmd) { - std::lock_guard lock(this->command_queue_lock); - - // following the specified ability priority - // find suitable ability for this target if available - for (auto &ability : ability_priority) { - auto pair = this->ability_available.find(ability); - if (pair != this->ability_available.end() && cmd.ability()[static_cast(pair->first)] && pair->second->can_invoke(*this, cmd)) { - command_queue.push(std::make_pair(pair->second, cmd)); - return pair->second; - } - } - return nullptr; -} - -void Unit::delete_unit() { - this->pop_destructables = true; -} - -void Unit::stop_gather() { - this->erase_after( - [](std::unique_ptr &e) { - return e->name() == "gather"; - }, - false); -} - -void Unit::stop_actions() { - // work around for workers continuing to work after retasking - if (this->has_attribute(attr_type::worker)) { - this->stop_gather(); - } - - // discard all interruptible tasks - this->erase_after( - [](std::unique_ptr &e) { - return e->allow_interrupt(); - }); -} - -UnitReference Unit::get_ref() { - return UnitReference(this->container, id, this); -} - -UnitContainer *Unit::get_container() const { - return this->container; -} - -std::string Unit::logsource_name() { - return "Unit " + std::to_string(this->id); -} - -void Unit::erase_after(std::function &)> func, bool run_completed) { - auto position_it = std::find_if( - std::begin(this->action_stack), - std::end(this->action_stack), - func); - - if (position_it != std::end(this->action_stack)) { - auto completed_action = std::move(*position_it); - - // erase from the stack - this->action_stack.erase(position_it, std::end(this->action_stack)); - - // perform any completion actions - if (run_completed) { - completed_action->on_completion(); - } - } -} - -} // namespace openage diff --git a/libopenage/unit/unit.h b/libopenage/unit/unit.h deleted file mode 100644 index 01416a6ac8..0000000000 --- a/libopenage/unit/unit.h +++ /dev/null @@ -1,323 +0,0 @@ -// Copyright 2014-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include -#include -#include -#include - -#include "../coord/phys.h" -#include "../handlers.h" -#include "../log/logsource.h" -#include "../terrain/terrain_object.h" -#include "../util/timing.h" -#include "ability.h" -#include "attribute.h" -#include "attributes.h" -#include "command.h" -#include "unit_container.h" - - -namespace openage { - -class UnitAbility; -class UnitAction; - -/** - * A game object with current state represented by a stack of actions - * since this class represents both unit and building objects it may be better to - * name as GameObject - * - * it is possible that abilities are not required here and they could be moved - * to selection controller -- units only need the attributes - */ -class Unit : public log::LogSource { -public: - Unit(UnitContainer *c, id_t id); - - /** - * unit cleanup will delete terrain object - */ - virtual ~Unit(); - - /** - * this units unique id value - */ - const id_t id; - - /** - * type of this object, this is set by the the UnitType which - * was most recently applied to this unit - */ - const UnitType *unit_type; - - /** - * should selection features be drawn - * TODO: should be a pointer to selection to be updated - * when unit is removed, or null if not selected - */ - bool selected; - - /** - * space on the map used by this unit - * null if the object is not yet placed or garrisoned - * TODO: make private field - */ - std::unique_ptr location; - - /** - * constructs a new location for this unit replacing any - * existing locatio - * - * uses same args as the location constructor - * except the first which will filled automatically - */ - template - void make_location(Arg... args) { - // remove any existing location first - if (this->location) { - this->location->remove(); - } - - // since Unit is a friend of the location - // make_shared will not work - this->location = std::unique_ptr(new T(*this, args...)); - } - - /** - * removes all actions and abilities - * current attributes are kept - */ - void reset(); - - /** - * checks the entity has an action, if it has no action it should be removed from the game - * @return true if the entity currently has an action - */ - bool has_action() const; - - /** - * true when the unit is alive and able to add new actions - */ - bool accept_commands() const; - - /** - * checks whether the current player is the owner of this unit - */ - bool is_own_unit(const Player &player); - - /** - * returns the current action on top of the stack - */ - UnitAction *top() const; - - /** - * returns action under the passed action in the stack - * returns null if stack size is less than 2 - */ - UnitAction *before(const UnitAction *action) const; - - /** - * update this object using the action currently on top of the stack - */ - bool update(time_nsec_t lastframe_duration); - - /** - * draws this action by taking the graphic type of the top action - * the graphic is found from the current graphic set - * - * this function should be used for most draw purposes - */ - void draw(const LegacyEngine &engine); - - /** - * an generalized draw function which is useful for drawing annexes - */ - void draw(TerrainObject *loc, const graphic_set &graphics, const LegacyEngine &engine); - - /** - * draws with a specific graphic and frame - */ - void draw(coord::phys3 draw_pos, std::shared_ptr graphic, unsigned int frame, const LegacyEngine &engine); - - /** - * adds an available ability to this unit - * this turns targeted objects into actions which are pushed - * onto the stack, eg. targeting a relic may push a collect relic action - */ - void give_ability(std::shared_ptr); - - /** - * get ability with specified type, null if not available - * - * To invoke commands use the invoke function instead - */ - UnitAbility *get_ability(ability_type type); - - /** - * adds a new action on top of the action stack - * will be performed immediately - */ - void push_action(std::unique_ptr action, bool force = false); - - /** - * adds a secondary action which is always updated - */ - void secondary_action(std::unique_ptr action); - - /** - * give a new attribute this this unit - * this is used to set things like color, hitpoints and speed - */ - void add_attribute(std::shared_ptr attr); - - /** - * Give new attributes to this unit. - * This is used to add the default attributes - */ - void add_attributes(const Attributes &attr); - - /** - * Give new attributes to this unit. - * If shared is false, shared attributes are ignored. - * If unshared is false, unshared attributes are ignored. - */ - void add_attributes(const Attributes &attr, bool shared, bool unshared); - - /** - * returns whether attribute is available - */ - bool has_attribute(attr_type type) const; - - /** - * returns attribute based on templated value - */ - template - Attribute &get_attribute() { - return *reinterpret_cast *>(attributes.get(T).get()); - // TODO change to (templates errors) - //return attributes.get(); - } - - /** - * queues a command to be applied to this unit on the next update - * - * @return the ability which will apply the command if an action was created - * otherwise nullptr is returned when no ability can handle the command - */ - std::shared_ptr queue_cmd(const Command &cmd); - - /** - * removes all gather actions without calling their on_complete actions - * this cancels the gathering action completely - */ - void stop_gather(); - - /** - * removes all actions above and including the first interuptable action - * this will stop any of the units current moving or attacking actions - * a direct command from the user will invoke this function - */ - void stop_actions(); - - /** - * begins unit removal by popping some actions - * - * this is the action that occurs when pressing the delete key - * which plays death sequence and does not remove instantly - */ - void delete_unit(); - - /** - * get a reference which can check against the container - * to ensure this object still exists - */ - UnitReference get_ref(); - - /** - * the container used when constructing this unit - */ - UnitContainer *get_container() const; - - /** - * Returns the unit's name as the LogSource name. - */ - std::string logsource_name() override; - - /** - * Unit attributes include color, hitpoints, speed, objects garrisoned etc - * contains 0 or 1 values for each type - */ - Attributes attributes; - -private: - /** - * ability available -- actions that this entity - * can perform when controlled - */ - std::unordered_map> ability_available; - - - /** - * action stack -- top action determines graphic to be drawn - */ - std::vector> action_stack; - - - /** - * secondary actions are always updated - */ - std::vector> action_secondary; - - - /** - * queue commands to be applied on the next update - */ - std::queue, const Command>> command_queue; - - /** - * mutex controlling updates to the command queue - */ - std::mutex command_queue_lock; - - - /** - * pop any destructable actions on the next update cycle - * and prevent additional actions being added - */ - bool pop_destructables; - - /** - * the container that updates this unit - */ - UnitContainer *container; - - /** - * applies new commands as part of the units update process - */ - void apply_all_cmds(); - - /** - * applies one command using a chosen ability - * locks the command queue mutex while operating - */ - void apply_cmd(std::shared_ptr ability, const Command &cmd); - - /** - * update all secondary actions - */ - void update_secondary(int64_t time_elapsed); - - /** - * erase from action specified by func to the end of the stack - * all actions erased will have the on_complete function called - * - * @param run_completed usually each action has an on_complete() function called when it is removed - * but when run_completed is false this on_complete() function is not called for all popped actions - */ - void erase_after(std::function &)> func, bool run_completed = true); -}; - -} // namespace openage diff --git a/libopenage/unit/unit_container.cpp b/libopenage/unit/unit_container.cpp deleted file mode 100644 index 672fedacd9..0000000000 --- a/libopenage/unit/unit_container.cpp +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright 2014-2019 the openage authors. See copying.md for legal info. - -#include "unit_container.h" - -#include - -#include "../log/log.h" -#include "../terrain/terrain_object.h" -#include "producer.h" -#include "unit.h" - - -namespace openage { - -reference_data::reference_data(const UnitContainer *c, id_t id, Unit *u) - : - container{c}, - unit_id{id}, - unit_ptr{u} { - if (!u) { - throw Error{MSG(err) << "Cannot reference null unit pointer"}; - } -} - - -UnitReference::UnitReference() - : - data{nullptr} {} - - -UnitReference::UnitReference(const UnitContainer *c, id_t id, Unit *u) - : - data{std::make_shared(c, id, u)} {} - - -bool UnitReference::is_valid() const { - return this->data && - this->data->container->valid_id(this->data->unit_id); -} - - -Unit *UnitReference::get() const { - if (!this->is_valid()) { - throw Error{MSG(err) << "Unit reference is no longer valid"}; - } - return this->data->unit_ptr; -} - - -UnitContainer::UnitContainer() - : - next_new_id{1} {} - - -UnitContainer::~UnitContainer() = default; - - -void UnitContainer::reset() { - this->live_units.clear(); -} - -void UnitContainer::set_terrain(std::shared_ptr &t) { - this->terrain = t; -} - -std::shared_ptr UnitContainer::get_terrain() const { - if (this->terrain.expired()) { - throw Error{MSG(err) << "Terrain has expired"}; - } - return this->terrain.lock(); -} - - -bool UnitContainer::valid_id(id_t id) const { - return (this->live_units.count(id) > 0); -} - - -UnitReference UnitContainer::get_unit(id_t id) { - if (this->valid_id(id)) { - return UnitReference(this, id, this->live_units[id].get()); - } - else { - return UnitReference(this, id, nullptr); - } -} - -UnitReference UnitContainer::new_unit() { - auto id = next_new_id; - next_new_id += 1; - - this->live_units.emplace(id, std::make_unique(this, id)); - return this->live_units[id]->get_ref(); -} - -UnitReference UnitContainer::new_unit(UnitType &type, - Player &owner, - coord::phys3 position) { - - auto new_id = next_new_id; - next_new_id += 1; - - auto newobj = std::make_unique(this, new_id); - - // try placing unit at this location - auto terrain_shared = this->get_terrain(); - auto placed = type.place(newobj.get(), terrain_shared, position); - if (placed) { - type.initialise(newobj.get(), owner); - owner.active_unit_added(newobj.get()); // TODO change, move elsewhere - auto id = newobj->id; - this->live_units.emplace(id, std::move(newobj)); - return this->live_units[id]->get_ref(); - } - return UnitReference(); // is not valid -} - -UnitReference UnitContainer::new_unit(UnitType &type, - Player &owner, - TerrainObject *other) { - auto new_id = next_new_id; - next_new_id += 1; - - auto newobj = std::make_unique(this, new_id); - - // try placing unit - TerrainObject *placed = type.place_beside(newobj.get(), other); - if (placed) { - type.initialise(newobj.get(), owner); - owner.active_unit_added(newobj.get()); // TODO change, move elsewhere - auto id = newobj->id; - this->live_units.emplace(id, std::move(newobj)); - return this->live_units[id]->get_ref(); - } - return UnitReference(); // is not valid -} - - -bool dispatch_command(id_t, const Command &) { - return true; -} - -bool UnitContainer::update_all(time_nsec_t lastframe_duration) { - // update everything and find objects with no actions - std::vector to_remove; - - for (auto &obj : this->live_units) { - obj.second->update(lastframe_duration); - - if (not obj.second->has_action()) { - to_remove.push_back(obj.first); - } - } - - // cleanup and removal of objects - for (auto &obj : to_remove) { - - // unique pointer triggers cleanup - this->live_units.erase(obj); - } - return true; -} - -std::vector UnitContainer::all_units() { - std::vector result; - for (auto &u : this->live_units) { - result.push_back(u.second.get()); - } - return result; -} - -} // namespace openage diff --git a/libopenage/unit/unit_container.h b/libopenage/unit/unit_container.h deleted file mode 100644 index 89c6848990..0000000000 --- a/libopenage/unit/unit_container.h +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2014-2017 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include -#include - -#include "../coord/tile.h" -#include "../handlers.h" -#include "../util/timing.h" - - -namespace openage { - -class Command; -class Player; -class Terrain; -class TerrainObject; -class Unit; -class UnitContainer; -class UnitType; - - -/** - * Type used to identify each single unit in the game. - */ -using id_t = unsigned long int; - - -/** - * immutable reference data - */ -struct reference_data { - reference_data(const UnitContainer *c, id_t id, Unit *); - - const UnitContainer *const container; - const id_t unit_id; - Unit *const unit_ptr; -}; - -/** - * Reference to a single unit, which may have been removed - * from the game, check is_valid() before calling get() - */ -class UnitReference { -public: - - /** - * create an invalid reference - */ - UnitReference(); - - /** - * create referece by unit id - */ - UnitReference(const UnitContainer *c, id_t id, Unit *); - - bool is_valid() const; - Unit *get() const; - -private: - - /** - * The default copy constructor and assignment - * will just copy the shared pointer - */ - std::shared_ptr data; -}; - -/** - * the list of units that are currently in use - * will also give a view of the current game state for networking in later milestones - */ -class UnitContainer { -public: - UnitContainer(); - ~UnitContainer(); - - void reset(); - - /** - * sets terrain to initialise units on - */ - void set_terrain(std::shared_ptr &t); - - /** - * returns the terrain which units are placed on - */ - std::shared_ptr get_terrain() const; - - /** - * checks the id is valid - */ - bool valid_id(id_t id) const; - - /** - * returns a reference to a unit - */ - UnitReference get_unit(id_t id); - - /** - * creates a new unit without initialising - */ - UnitReference new_unit(); - - /** - * adds a new unit to the container and initialises using a unit type - */ - UnitReference new_unit(UnitType &type, Player &owner, coord::phys3 position); - - /** - * adds a new unit to the container and initialises using a unit type - * places outside an existing object using the player of that object - */ - UnitReference new_unit(UnitType &type, Player &owner, TerrainObject *other); - - /** - * give a command to a unit -- unit creation and deletion should be done as commands - */ - bool dispatch_command(id_t to_id, const Command &cmd); - - /** - * update dispatched by the game engine on each physics tick. - * this will update all game objects. - */ - bool update_all(time_nsec_t lastframe_duration); - - /** - * gets a list of all units in the container - */ - std::vector all_units(); - -private: - id_t next_new_id; - - /** - * mapping unit ids to unit objects - */ - std::unordered_map> live_units; - - /** - * Terrain for initialising new units - */ - std::weak_ptr terrain; -}; - -} // namespace openage diff --git a/libopenage/unit/unit_texture.cpp b/libopenage/unit/unit_texture.cpp deleted file mode 100644 index a146f471b2..0000000000 --- a/libopenage/unit/unit_texture.cpp +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright 2015-2021 the openage authors. See copying.md for legal info. - -#include "unit_texture.h" - -#include -#include - -#include "../coord/phys.h" -#include "../coord/pixel.h" -#include "../gamestate/old/game_spec.h" -#include "../log/log.h" -#include "../texture.h" -#include "../util/math.h" -#include "../util/math_constants.h" - - -namespace openage { - -UnitTexture::UnitTexture(GameSpec &spec, uint16_t graphic_id, bool delta) - : - UnitTexture{spec, spec.get_graphic_data(graphic_id), delta} {} - -UnitTexture::UnitTexture(GameSpec &spec, const gamedata::graphic *graphic, bool delta) - : - id{graphic->graphic_id}, - sound_id{graphic->sound_id}, - frame_count{graphic->frame_count}, - angle_count{graphic->angle_count}, - mirroring_mode{graphic->mirroring_mode}, - frame_rate{graphic->frame_rate}, - use_up_angles{graphic->mirroring_mode == 24}, - use_deltas{delta}, - texture{nullptr}, - draw_this{true}, - sound{nullptr}, - delta_id{graphic->graphic_deltas.data} { - this->initialise(spec); -} - -bool UnitTexture::is_valid() const { - return texture; -} - -coord::viewport UnitTexture::size() const { - return coord::viewport{this->texture->w, this->texture->h}; -} - -void UnitTexture::sample(const coord::CoordManager &coord, const coord::camhud &draw_pos, unsigned color) const { - - // draw delta list first - for (auto &d : this->deltas) { - coord::camhud_delta dlt = coord::camhud_delta{d.second.x, d.second.y}; - d.first->sample(coord, draw_pos + dlt, color); - } - - // draw texture - if (this->draw_this) { - this->texture->draw(coord, draw_pos, PLAYERCOLORED, false, 0, color); - } -} - -void UnitTexture::draw(const coord::CoordManager &coord, const coord::camgame &draw_pos, unsigned int frame, unsigned color) const { - - // draw delta list first - for (auto &d : this->deltas) { - d.first->draw(coord, draw_pos + d.second, frame, color); - } - - // draw texture - if (this->draw_this) { - unsigned int to_draw = frame % this->texture->get_subtexture_count(); - this->texture->draw(coord, draw_pos, PLAYERCOLORED, false, to_draw, color); - } -} - -void UnitTexture::draw(const coord::CoordManager &coord, const coord::camgame &draw_pos, coord::phys3_delta &dir, unsigned int frame, unsigned color) const { - unsigned int frame_to_use = frame; - if (this->use_up_angles) { - // up 1 => tilt 0 - // up -1 => tilt 1 - // up has a scale 5 times smaller - double len = math::hypot3(dir.ne.to_double(), dir.se.to_double(), dir.up.to_double()/5); - double up = dir.up.to_double()/(5.0 * len); - frame_to_use = (0.5 - (0.5 * up)) * this->frame_count; - } - else if (this->sound && frame == 0.0) { - this->sound->play(); - } - - // the index for the current direction - unsigned int angle = dir_group(dir, this->angle_count); - - // mirroring is used to make additional image sets - bool mirror = false; - if (this->angles_included <= angle) { - // this->angles_included <= angle < this->angle_count - angle = this->top_frame - angle; - mirror = true; - } - - // draw delta list first - for (auto &d : this->deltas) { - d.first->draw(coord, draw_pos + d.second, dir, frame, color); - } - - if (this->draw_this) { - unsigned int to_draw = this->subtexture(this->texture, angle, frame_to_use); - this->texture->draw(coord, draw_pos, PLAYERCOLORED, mirror, to_draw, color); - } -} - -void UnitTexture::initialise(GameSpec &spec) { - this->texture = spec.get_texture(this->id); - this->sound = spec.get_sound(this->sound_id); - if (not is_valid()) { - this->draw_this = false; - } - - // find deltas - if (this->use_deltas) for (auto d : this->delta_id) { - if (spec.get_graphic_data(d.graphic_id)) { - auto ut = std::make_unique(spec, d.graphic_id, false); - if (ut->is_valid()) { - this->deltas.push_back({std::move(ut), coord::camgame_delta{d.offset_x, d.offset_y}}); - } - } - } - - if (this->draw_this) { - - // the graphic frame count includes deltas - unsigned int subtextures = this->texture->get_subtexture_count(); - if (subtextures >= this->frame_count) { - - // angles with graphic data - this->angles_included = subtextures / this->frame_count; - this->angles_mirrored = this->angle_count - this->angles_included; - this->safe_frame_count = this->frame_count; - } - else { - this->angles_included = 1; - this->angles_mirrored = 0; - this->safe_frame_count = subtextures; - } - - // find the top direction for mirroring over - this->top_frame = this->angle_count - (1 - (this->angles_included - this->angles_mirrored) / 2); - } -} - -unsigned int UnitTexture::subtexture(const Texture *t, unsigned int angle, unsigned int frame) const { - unsigned int tex_frames = t->get_subtexture_count(); - unsigned int count = tex_frames / this->angles_included; - unsigned int to_draw = angle * count + (frame % count); - if (tex_frames <= to_draw) { - log::log(MSG(err) << "Subtexture out of range (" << angle << ", " << frame << ")"); - return 0; - } - return to_draw; -} - -unsigned int dir_group(coord::phys3_delta dir, unsigned int angles) { - unsigned int first_angle = 5 * angles / 8; - - // normalise direction vector - double len = std::hypot(dir.ne, dir.se); - double dir_ne = static_cast(dir.ne) / len; - double dir_se = static_cast(dir.se) / len; - - // formula to find the correct angle - return static_cast( - round(angles * atan2(dir_se, dir_ne) * (math::INV_PI / 2)) - + first_angle - ) % angles; -} - - -} // namespace openage diff --git a/libopenage/unit/unit_texture.h b/libopenage/unit/unit_texture.h deleted file mode 100644 index 20efe9eede..0000000000 --- a/libopenage/unit/unit_texture.h +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright 2015-2021 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include - -#include "../coord/phys.h" -#include "../gamedata/graphic_dummy.h" - -namespace openage { - -class GameSpec; -class Texture; -class Sound; - -/** - * Handling animated and directional textures based on the game - * graphics data. - * - * These objects handle the drawing of regular textures to use a - * unit's direction and include delta graphics. - * - * This type can also deal with playing position based game sounds. - */ -class UnitTexture { -public: - /** - * Delta option specifies whether the delta graphics are included. - * - * Note that the game data contains loops in delta links - * which mean recursive loading should be avoided - */ - UnitTexture(GameSpec &spec, uint16_t graphic_id, bool delta=true); - UnitTexture(GameSpec &spec, const gamedata::graphic *graphic, bool delta=true); - - /** - * const attributes of the graphic - */ - const int16_t id; - const int16_t sound_id; - const unsigned int frame_count; - const unsigned int angle_count; - const int16_t mirroring_mode; - const float frame_rate; - - /** - * draw object with vertical orientation (arrows) - * adding an addtion degree of orientation - */ - const bool use_up_angles; - - /** - * use delta information - */ - const bool use_deltas; - - /** - * invalid unit textures will cause errors if drawn - */ - bool is_valid() const; - - /** - * pixel size of this texture - */ - coord::viewport size() const; - - /** - * a sample drawing for hud - */ - void sample(const coord::CoordManager &coord, const coord::camhud &draw_pos, unsigned color=1) const; - - /** - * draw object with no direction - */ - void draw(const coord::CoordManager &coord, const coord::camgame &draw_pos, unsigned int frame, unsigned color) const; - - /** - * draw object with direction - */ - void draw(const coord::CoordManager &coord, const coord::camgame &draw_pos, coord::phys3_delta &dir, unsigned int frame, unsigned color) const; - - /** - * initialise graphic data - */ - void initialise(GameSpec &spec); - -private: - /** - * use a regular texture for drawing - */ - const Texture *texture; - - /** - * the above frame count covers the entire graphic (with deltas) - * the actual number in the base texture may be different - */ - unsigned int safe_frame_count; - unsigned int angles_included; - unsigned int angles_mirrored; - unsigned int top_frame; - - // avoid drawing missing graphics - bool draw_this; - const Sound *sound; - - // delta graphic ids - std::vector delta_id; - - // delta graphics - std::vector, coord::camgame_delta>> deltas; - - /** - * find which subtexture should be used for drawing this texture - */ - unsigned int subtexture(const Texture *t, unsigned int angle, unsigned int frame) const; -}; - -/** - * the set of images to used based on unit direction, - * usually 8 directions to draw for each unit (3 are mirrored) - * - * @param dir a world space direction, - * @param angles number of angles, usually 8 - * @param first_angle offset added to angle, modulo number of angles - * @return image set index - */ -unsigned int dir_group(coord::phys3_delta dir, unsigned int angles=8); - -} // namespace openage diff --git a/libopenage/unit/unit_type.cpp b/libopenage/unit/unit_type.cpp deleted file mode 100644 index bc760bd955..0000000000 --- a/libopenage/unit/unit_type.cpp +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright 2015-2021 the openage authors. See copying.md for legal info. - -#include "../gamestate/old/player.h" -#include "../terrain/terrain_object.h" -#include "../util/math_constants.h" -#include "action.h" -#include "unit.h" -#include "unit_container.h" -#include "unit_type.h" - -#include - -namespace openage { - -UnitTypeMeta::UnitTypeMeta(std::string name, int id, init_func f) - : - init{f}, - type_name{std::move(name)}, - type_id{id} { -} - -std::string UnitTypeMeta::name() const { - return this->type_name; -} - -int UnitTypeMeta::id() const { - return this->type_id; -} - -UnitType::UnitType(const Player &owner) - : - owner{owner}, - have_limit{math::INT_INF}, - had_limit{math::INT_INF} { -} - -void UnitType::reinitialise(Unit *unit, Player &player) { - // In case reinitialise is not implemented separately - - Attributes tmp; - // copy only unshared - tmp.add_copies(unit->attributes, false, true); - // initialise the new unit - this->initialise(unit, player); - // replace new unshared attributes with the old - unit->attributes.add_copies(tmp); -} - -bool UnitType::operator==(const UnitType &other) const { - return this->type_abilities == other.type_abilities; -} - -bool UnitType::operator!=(const UnitType &other) const { - return !(*this == other); -} - -UnitTexture *UnitType::default_texture() { - return this->graphics[graphic_type::standing].get(); -} - -TerrainObject *UnitType::place_beside(Unit *u, TerrainObject const *other) const { - if (!u || !other) { - return nullptr; - } - - // find the range of possible tiles - tile_range outline{other->pos.start - coord::tile_delta{1, 1}, - other->pos.end + coord::tile_delta{1, 1}, - other->pos.draw}; - - // find a free position adjacent to the object - auto terrain = other->get_terrain(); - for (coord::tile temp_pos : tile_list(outline)) { - TerrainChunk *chunk = terrain->get_chunk(temp_pos); - - if (chunk == nullptr) { - continue; - } - - auto placed = this->place(u, terrain, temp_pos.to_phys3(*terrain)); - if (placed) { - return placed; - } - } - return nullptr; -} - -void UnitType::copy_attributes(Unit *unit) const { - unit->add_attributes(this->default_attributes); -} - -void UnitType::upgrade(const std::shared_ptr &attr) { - this->default_attributes.add(attr); -} - -UnitType *UnitType::parent_type() const { - return this->owner.get_type(this->parent_id()); -} - -NyanType::NyanType(const Player &owner) - : - UnitType(owner) { - // TODO: the type should be given attributes and abilities -} - -NyanType::~NyanType() = default; - -int NyanType::id() const { - return 1; -} - -int NyanType::parent_id() const { - return -1; -} - -std::string NyanType::name() const { - return "Nyan"; -} - -void NyanType::initialise(Unit *unit, Player &) { - // removes all actions and abilities - unit->reset(); - - // initialise unit - unit->unit_type = this; - - // the parsed nyan data gives the list of attributes - // and abilities which are given to the unit - for (auto &ability : this->type_abilities) { - unit->give_ability(ability); - } - - // copy all attributes - this->copy_attributes(unit); - - // give idle action - unit->push_action(std::make_unique(unit), true); -} - -TerrainObject *NyanType::place(Unit *unit, std::shared_ptr terrain, coord::phys3 init_pos) const { - // the parsed nyan data gives the rules for terrain placement - // which includes valid terrains, base radius and shape - - unit->make_location(coord::tile_delta{1, 1}); - - // allow unit to go anywhere - unit->location->passable = [](const coord::phys3 &) { - return true; - }; - - // try to place the obj, it knows best whether it will fit. - if (unit->location->place(terrain, init_pos, object_state::placed)) { - return unit->location.get(); - } - - // placing at the given position failed - unit->log(MSG(dbg) << "failed to place object"); - return nullptr; -} - -} // namespace openage diff --git a/libopenage/unit/unit_type.h b/libopenage/unit/unit_type.h deleted file mode 100644 index 5e960915a3..0000000000 --- a/libopenage/unit/unit_type.h +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright 2015-2021 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include -#include -#include - -#include "../coord/phys.h" -#include "../gamestate/old/cost.h" -#include "attributes.h" - -namespace openage { - -class Player; -class Terrain; -class TerrainObject; -class Unit; -class UnitAbility; -class UnitContainer; -class UnitTexture; - - -/** - * an abstract unit type which is not yet owned by any player - */ -class UnitTypeMeta { -public: - using type_ptr = std::shared_ptr; - using init_func = std::function; - UnitTypeMeta(std::string name, int id, init_func f); - - std::string name() const; - - int id() const; - - /** - * creates the base unit type for a player - */ - const init_func init; - -private: - const std::string type_name; - const int type_id; - -}; - -/** - * UnitType has to main roles: - * - * initialise(unit, player) should be called on a unit to give it a type and the required attributes, abilities and initial actions - * of that type - * - * place(unit, terrain, initial position) is called to customise how the unit gets added to the world -- used to setup the TerrainObject location - * - * UnitType is connected to a player to allow independent tech levels - */ -class UnitType { -public: - UnitType(const Player &owner); - virtual ~UnitType() {} - - /** - * gets the unique id of this unit type - */ - virtual int id() const = 0; - - /** - * gets the parent id of this unit type - * which is used for village base and gather types - */ - virtual int parent_id() const = 0; - - /** - * gets the name of the unit type being produced - */ - virtual std::string name() const = 0; - - /** - * Initialize units attributes to this type spec - * - * This can be called using existing units to modify type - * Ensure that the unit has been placed before seting the units type - * - * TODO: make const - */ - virtual void initialise(Unit *, Player &) = 0; - - /** - * Initialize units shared attributes only to this type spec - * - * This can be called using existing units to modify type if the type - * Ensure that the unit has been placed before seting the units type - * - * TODO define if pure vitrual or not / should be in nyan? - */ - virtual void reinitialise(Unit *, Player &); - - /** - * set unit in place -- return if placement was successful - * - * This should be used when initially creating a unit or - * when a unit is ungarrsioned from a building or object - * TODO: make const - */ - virtual TerrainObject *place(Unit *, std::shared_ptr, coord::phys3) const = 0; - - /** - * compare if two types are the same - */ - bool operator==(const UnitType &other) const; - bool operator!=(const UnitType &other) const; - - /** - * Get a default texture for HUD drawing - */ - UnitTexture *default_texture(); - - /** - * similar to place but places adjacent to an existing object - */ - TerrainObject *place_beside(Unit *, TerrainObject const *) const; - - /** - * copy attributes of this unit type to a new unit instance - */ - void copy_attributes(Unit *unit) const; - - /** - * upgrades one attribute of this unit type - */ - void upgrade(const std::shared_ptr &attr); - - /** - * returns type matching parent_id() - */ - UnitType *parent_type() const; - - /** - * the player who owns this unit type - */ - const Player &owner; - - /** - * all instances of units made from this unit type - * this could allow all units of a type to be upgraded - */ - std::vector instances; - - /** - * abilities given to all instances - */ - std::vector> type_abilities; - - /** - * default attributes which get copied to new units - */ - Attributes default_attributes; - - /** - * The cost of the unit. - */ - ResourceCost cost; - - /** - * The max number of units of this type that a player can have at an instance. - * Use negative values for special cases. - */ - int have_limit; - - /** - * The max number of units of this type that a player can create. - * Use negative values for special cases. - */ - int had_limit; - - /** - * The set of graphics used for this type - */ - graphic_set graphics; - - /** - * The index of the icon representing this unit - */ - int icon; - - /** - * the square dimensions of the placement - */ - coord::tile_delta foundation_size; - - /** - * raw game data class of this unit instance - */ - gamedata::unit_classes unit_class; -}; - -/** - * An example of how nyan can work with the type system - */ -class NyanType: public UnitType { -public: - /** - * TODO: give the parsed nyan attributes - * to the constructor - */ - NyanType(const Player &owner); - virtual ~NyanType(); - - int id() const override; - int parent_id() const override; - std::string name() const override; - void initialise(Unit *, Player &) override; - TerrainObject *place(Unit *, std::shared_ptr, coord::phys3) const override; - -}; - -} // namespace openage diff --git a/libopenage/util/CMakeLists.txt b/libopenage/util/CMakeLists.txt index ac5b585f06..d278f4b427 100644 --- a/libopenage/util/CMakeLists.txt +++ b/libopenage/util/CMakeLists.txt @@ -2,7 +2,6 @@ add_sources(libopenage color.cpp compiler.cpp constinit_vector.cpp - csv.cpp enum.cpp enum_test.cpp externalprofiler.cpp @@ -20,10 +19,8 @@ add_sources(libopenage matrix_test.cpp misc.cpp misc_test.cpp - opengl.cpp os.cpp path.cpp - profiler.cpp quaternion.cpp quaternion_test.cpp repr.cpp diff --git a/libopenage/util/algorithm.h b/libopenage/util/algorithm.h index a4d22a9d9b..cd68d4481d 100644 --- a/libopenage/util/algorithm.h +++ b/libopenage/util/algorithm.h @@ -1,4 +1,4 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once @@ -13,7 +13,7 @@ namespace util { /** * std::for_each except just on containers. */ -template +template inline Function for_each(Container &&container, Function &&func) { // why cpp why... return std::for_each(std::begin(std::forward(container)), @@ -24,7 +24,7 @@ inline Function for_each(Container &&container, Function &&func) { /** * Filters items from a container which satisfy a certain predicate. */ -template +template inline void remove_from(Container &container, Function &&func) { container.erase(std::remove_if(std::begin(container), std::end(container), diff --git a/libopenage/util/color.cpp b/libopenage/util/color.cpp index 5254606f74..fed90a54a4 100644 --- a/libopenage/util/color.cpp +++ b/libopenage/util/color.cpp @@ -1,4 +1,4 @@ -// Copyright 2013-2019 the openage authors. See copying.md for legal info. +// Copyright 2013-2023 the openage authors. See copying.md for legal info. #include "color.h" @@ -6,13 +6,6 @@ namespace openage::util { -col::col(gamedata::palette_color c) { - this->r = c.r; - this->g = c.g; - this->b = c.b; - this->a = c.a; -} - void col::use() { //TODO use glColor4b glColor4f(r / 255.f, g / 255.f, b / 255.f, a / 255.f); @@ -22,4 +15,4 @@ void col::use(float alpha) { glColor4f(r / 255.f, g / 255.f, b / 255.f, alpha); } -} // openage::util +} // namespace openage::util diff --git a/libopenage/util/color.h b/libopenage/util/color.h index 326b1a1e2d..ee7199a8b7 100644 --- a/libopenage/util/color.h +++ b/libopenage/util/color.h @@ -1,15 +1,14 @@ -// Copyright 2013-2021 the openage authors. See copying.md for legal info. +// Copyright 2013-2023 the openage authors. See copying.md for legal info. #pragma once -#include "../gamedata/color_dummy.h" namespace openage { namespace util { struct col { - col(unsigned r, unsigned g, unsigned b, unsigned a) : r{r}, g{g}, b{b}, a{a} {} - col(gamedata::palette_color c); + col(unsigned r, unsigned g, unsigned b, unsigned a) : + r{r}, g{g}, b{b}, a{a} {} unsigned r, g, b, a; @@ -17,4 +16,5 @@ struct col { void use(float alpha); }; -}} // openage::util +} // namespace util +} // namespace openage diff --git a/libopenage/util/compiler.cpp b/libopenage/util/compiler.cpp index b854d91278..71100d3f92 100644 --- a/libopenage/util/compiler.cpp +++ b/libopenage/util/compiler.cpp @@ -1,22 +1,22 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. +// Copyright 2015-2025 the openage authors. See copying.md for legal info. #include "compiler.h" #ifndef _WIN32 -#include -#include + #include + #include #else -#define WIN32_LEAN_AND_MEAN -#include -#include + #define WIN32_LEAN_AND_MEAN + #include + #include #endif #include "strings.h" #include #include -#include #include +#include namespace openage { namespace util { @@ -24,17 +24,18 @@ 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); if (status != 0) { return symbol; - } else { + } + else { std::string result{buf}; free(buf); return result; @@ -78,12 +79,12 @@ std::optional symbol_name_win(const void *addr) { constexpr int buffer_size = sizeof(SYMBOL_INFO) + name_buffer_size * sizeof(char); std::array buffer; - SYMBOL_INFO *symbol_info = reinterpret_cast(buffer.data()); + SYMBOL_INFO *symbol_info = reinterpret_cast(buffer.data()); symbol_info->SizeOfStruct = sizeof(SYMBOL_INFO); symbol_info->MaxNameLen = name_buffer_size; - if (SymFromAddr(process_handle, reinterpret_cast(addr), nullptr, symbol_info)) { + if (SymFromAddr(process_handle, reinterpret_cast(addr), nullptr, symbol_info)) { return std::string(symbol_info->Name); } } @@ -92,7 +93,7 @@ std::optional symbol_name_win(const void *addr) { } -} +} // namespace #endif std::string symbol_name(const void *addr, bool require_exact_addr, bool no_pure_addrs) { @@ -100,8 +101,7 @@ std::string symbol_name(const void *addr, bool require_exact_addr, bool no_pure_ auto symbol_name_result = symbol_name_win(addr); - if (!initialized_symbol_handler_successfully || - !symbol_name_result.has_value()) { + if (!initialized_symbol_handler_successfully || !symbol_name_result.has_value()) { return no_pure_addrs ? "" : addr_to_string(addr); } @@ -113,8 +113,9 @@ std::string symbol_name(const void *addr, bool require_exact_addr, bool no_pure_ if (dladdr(addr, &addr_info) == 0) { // dladdr has... failed. return no_pure_addrs ? "" : addr_to_string(addr); - } else { - size_t symbol_offset = (size_t) addr - (size_t) addr_info.dli_saddr; + } + else { + size_t symbol_offset = (size_t)addr - (size_t)addr_info.dli_saddr; if (addr_info.dli_sname == nullptr or (symbol_offset != 0 and require_exact_addr)) { return no_pure_addrs ? "" : addr_to_string(addr); @@ -123,7 +124,8 @@ std::string symbol_name(const void *addr, bool require_exact_addr, bool no_pure_ if (symbol_offset == 0) { // this is our symbol name. return demangle(addr_info.dli_sname); - } else { + } + else { return util::sformat("%s+0x%lx", demangle(addr_info.dli_sname).c_str(), symbol_offset); @@ -138,7 +140,8 @@ bool is_symbol(const void *addr) { if (!initialized_symbol_handler_successfully) { return true; - } else { + } + else { return symbol_name_win(addr).has_value(); } @@ -154,4 +157,5 @@ bool is_symbol(const void *addr) { } -}} // openage::util +} // namespace util +} // namespace openage diff --git a/libopenage/util/compiler.h b/libopenage/util/compiler.h index 9068284abf..9419bc1fa0 100644 --- a/libopenage/util/compiler.h +++ b/libopenage/util/compiler.h @@ -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. #pragma once @@ -18,6 +18,8 @@ /** * DLL entry-point decorations. */ +// clang-format off +// otherwise clang-format removes indentation #if defined(_WIN32) #if defined(libopenage_EXPORTS) #define OAAPI __declspec(dllexport) @@ -40,18 +42,21 @@ #define HAVE_SSIZE_T 1 #endif // HAVE_SSIZE_T #endif // _MSC_VER +// clang-format on /** * Software breakpoint if you're too lazy * to add it in gdb but instead wanna add it into the code directly. */ +// clang-format off #ifdef _WIN32 #define BREAKPOINT __debugbreak() #else #include #define BREAKPOINT raise(SIGTRAP) #endif +// clang-format on /** @@ -89,7 +94,7 @@ std::string demangle(const char *symbol); * * pxd: string symbol_name(const void *addr) except + */ -OAAPI std::string symbol_name(const void *addr, bool require_exact_addr=true, bool no_pure_addrs=false); +OAAPI std::string symbol_name(const void *addr, bool require_exact_addr = true, bool no_pure_addrs = false); /** @@ -129,4 +134,5 @@ std::string typestring(const T &ref) { } -}} // openage::util +} // namespace util +} // namespace openage diff --git a/libopenage/util/compress/bitstream.h b/libopenage/util/compress/bitstream.h index 9548689cd3..43d6a45c01 100644 --- a/libopenage/util/compress/bitstream.h +++ b/libopenage/util/compress/bitstream.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 @@ -20,7 +20,7 @@ namespace compress { * * See lzxd.h for documentation. */ -using read_callback_t = std::function; +using read_callback_t = std::function; /** @@ -65,7 +65,7 @@ using read_callback_t = std::function; * Calling the modeswitch methods while already in the respective mode does * nothing. */ -template +template class BitStream { public: /** @@ -148,7 +148,8 @@ class BitStream { if (read_bytes == 0) [[unlikely]] { if (this->eof) { throw Error(MSG(err) << "Unexpected EOF in the middle of a block"); - } else { + } + else { read_bytes = 2; this->inbuf[0] = 0; this->inbuf[1] = 0; @@ -156,7 +157,7 @@ class BitStream { } } - if (read_bytes > (int) inbuf_size) [[unlikely]] { + if (read_bytes > (int)inbuf_size) [[unlikely]] { throw Error(MSG(err) << "read() returned more data than requested"); } @@ -174,7 +175,7 @@ class BitStream { /* * allow our friend HuffmanTable to directly use ensure_bits, peek_bits and remove_bits. */ - template + template friend class HuffmanTable; /** @@ -266,7 +267,7 @@ class BitStream { * * If min_discard is given, at least that amount of bits is discarded. */ - void align_bitstream(unsigned int min_discard=0) { + void align_bitstream(unsigned int min_discard = 0) { unsigned int nbits = this->stream_position % 16; if (nbits != 0) { nbits = 16 - nbits; @@ -292,8 +293,7 @@ class BitStream { } public: - BitStream(read_callback_t read_callback) - : + BitStream(read_callback_t read_callback) : eof{false}, read_callback{read_callback}, i_ptr{inbuf}, @@ -302,7 +302,6 @@ class BitStream { bits_left{0}, stream_position{0}, bitstream_mode{true} { - static_assert(inbuf_size >= 2, "inbuf size must be at least 2"); static_assert(inbuf_size % 2 == 0, "inbuf size must be even"); } @@ -368,8 +367,8 @@ class BitStream { unsigned int read_4bytes_le() { unsigned int result; - result = this->read_single_byte() << 0; - result |= this->read_single_byte() << 8; + result = this->read_single_byte() << 0; + result |= this->read_single_byte() << 8; result |= this->read_single_byte() << 16; result |= this->read_single_byte() << 24; @@ -404,7 +403,7 @@ class BitStream { * Discards 1 to 16 bits to align the bitstream first. */ void switch_to_bytestream_mode() { - if (! this->bitstream_mode) { + if (!this->bitstream_mode) { return; } @@ -433,4 +432,6 @@ class BitStream { }; -}}} // openage::util::compress +} // namespace compress +} // namespace util +} // namespace openage diff --git a/libopenage/util/compress/lzxd.h b/libopenage/util/compress/lzxd.h index b3607a5228..4c9dbea30e 100644 --- a/libopenage/util/compress/lzxd.h +++ b/libopenage/util/compress/lzxd.h @@ -1,4 +1,4 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once @@ -54,8 +54,8 @@ class OAAPI LZXDecompressor { * resets, such as in CAB LZX streams. */ LZXDecompressor(read_callback_t read_callback, - unsigned int window_bits=21, - unsigned int reset_interval=0); + unsigned int window_bits = 21, + unsigned int reset_interval = 0); /** * Frees the internally-allocated LZXDStream object. @@ -83,8 +83,10 @@ class OAAPI LZXDecompressor { LZXDecompressor(const LZXDecompressor &other) = delete; LZXDecompressor(LZXDecompressor &&other) = delete; - LZXDecompressor &operator =(const LZXDecompressor &other) = delete; - LZXDecompressor &operator =(LZXDecompressor &&other) = delete; + LZXDecompressor &operator=(const LZXDecompressor &other) = delete; + LZXDecompressor &operator=(LZXDecompressor &&other) = delete; }; -}}} // openage::util::compress +} // namespace compress +} // namespace util +} // namespace openage diff --git a/libopenage/util/constexpr.h b/libopenage/util/consteval.h similarity index 72% rename from libopenage/util/constexpr.h rename to libopenage/util/consteval.h index 451ba9701a..8ddcd22f09 100644 --- a/libopenage/util/constexpr.h +++ b/libopenage/util/consteval.h @@ -1,4 +1,4 @@ -// Copyright 2014-2018 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. #pragma once @@ -6,16 +6,16 @@ /** - * This namespace contains constexpr functions, i.e. C++14 functions that are designed - * to run at compile-time. + * This namespace contains consteval functions, i.e. C++20 functions that are designed + * to be evaluated at compile-time. */ -namespace openage::util::constexpr_ { +namespace openage::util::consteval_ { /** * Returns true IFF the string literals have equal content. */ -constexpr bool streq(const char *a, const char *b) { - for (;*a == *b; ++a, ++b) { +consteval bool streq(const char *a, const char *b) { + for (; *a == *b; ++a, ++b) { if (*a == '\0') { return true; } @@ -27,7 +27,7 @@ constexpr bool streq(const char *a, const char *b) { /** * Returns the length of the string literal, excluding the terminating NULL byte. */ -constexpr size_t strlen(const char *str) { +consteval size_t strlen(const char *str) { for (size_t len = 0;; ++len) { if (str[len] == '\0') { return len; @@ -56,14 +56,16 @@ struct truncated_string_literal { * * Raises 'false' if str doesn't end in the given suffix. */ -constexpr truncated_string_literal get_prefix(const char *str, const char *suffix) { +consteval truncated_string_literal get_prefix(const char *str, const char *suffix) { if (strlen(str) < strlen(suffix)) { // suffix is longer than str throw false; - } else if (streq(str + (strlen(str) - strlen(suffix)), suffix)) { + } + else if (streq(str + (strlen(str) - strlen(suffix)), suffix)) { // str ends with suffix return truncated_string_literal{str, strlen(str) - strlen(suffix)}; - } else { + } + else { throw false; } } @@ -72,14 +74,14 @@ constexpr truncated_string_literal get_prefix(const char *str, const char *suffi /** * Creates a truncated_string_literal from a regular string literal. */ -constexpr truncated_string_literal create_truncated_string_literal(const char *str) { +consteval truncated_string_literal create_truncated_string_literal(const char *str) { return truncated_string_literal{str, strlen(str)}; } /** * Tests whether a string literal starts with the given prefix. */ -constexpr bool has_prefix(const char *str, const truncated_string_literal prefix) { +consteval bool has_prefix(const char *str, const truncated_string_literal prefix) { for (size_t pos = 0; pos < prefix.length; ++pos) { if (str[pos] != prefix.literal[pos]) { return false; @@ -94,10 +96,11 @@ constexpr bool has_prefix(const char *str, const truncated_string_literal prefix * * If the string literal doesn't have that prefix, returns the string literal itself. */ -constexpr const char *strip_prefix(const char *str, const truncated_string_literal prefix) { +consteval const char *strip_prefix(const char *str, const truncated_string_literal prefix) { if (has_prefix(str, prefix)) { return str + prefix.length; - } else { + } + else { return str; } } @@ -108,8 +111,8 @@ constexpr const char *strip_prefix(const char *str, const truncated_string_liter * * If the string literal doesn't have that prefix, returns the string literal itself. */ -constexpr const char *strip_prefix(const char *str, const char *prefix) { +consteval const char *strip_prefix(const char *str, const char *prefix) { return strip_prefix(str, create_truncated_string_literal(prefix)); } -} // namespace openage::util::constexpr_ +} // namespace openage::util::consteval_ diff --git a/libopenage/util/constinit_vector.h b/libopenage/util/constinit_vector.h index e0f2be48a6..0b49506296 100644 --- a/libopenage/util/constinit_vector.h +++ b/libopenage/util/constinit_vector.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 @@ -20,10 +20,11 @@ namespace util { * be pretty uncommon; there generally are better ways of guaranteeing dynamic * initialization order, such as static function variables). */ -template +template class ConstInitVector { public: - constexpr ConstInitVector() noexcept : data{nullptr}, capacity{16}, count{0} {} + constexpr ConstInitVector() noexcept : + data{nullptr}, capacity{16}, count{0} {} ~ConstInitVector() { @@ -43,8 +44,8 @@ class ConstInitVector { */ ConstInitVector(const ConstInitVector &other) = delete; ConstInitVector(ConstInitVector &&other) = delete; - ConstInitVector &operator =(const ConstInitVector &other) = delete; - ConstInitVector &operator =(ConstInitVector &&other) = delete; + ConstInitVector &operator=(const ConstInitVector &other) = delete; + ConstInitVector &operator=(ConstInitVector &&other) = delete; void push_back(const T &val) { @@ -60,7 +61,7 @@ class ConstInitVector { size_t newcapacity = capacity * 2; T *newdata = alloc.allocate(newcapacity); for (size_t i = 0; i < this->capacity; i++) { - new(static_cast(&newdata[i])) T(std::move_if_noexcept(this->data[i])); + new (static_cast(&newdata[i])) T(std::move_if_noexcept(this->data[i])); (this->data[i]).~T(); } alloc.deallocate(this->data, this->capacity); @@ -69,7 +70,7 @@ class ConstInitVector { } // add val at the end. - new(static_cast(&this->data[this->count])) T(val); + new (static_cast(&this->data[this->count])) T(val); this->count += 1; } @@ -78,7 +79,7 @@ class ConstInitVector { * The returned reference is invalid if n >= this->size(). * It may be invalidated by a call to push_back(). */ - const T &operator[] (size_t idx) const { + const T &operator[](size_t idx) const { return this->data[idx]; } @@ -97,4 +98,5 @@ class ConstInitVector { }; -}} // openage::util +} // namespace util +} // namespace openage diff --git a/libopenage/util/csv.cpp b/libopenage/util/csv.cpp deleted file mode 100644 index e0bf825c3b..0000000000 --- a/libopenage/util/csv.cpp +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2013-2023 the openage authors. See copying.md for legal info. - -#include "csv.h" - -#include - -#include "file.h" -#include "../error/error.h" -#include "../log/log.h" - - -namespace openage { -namespace util { - - -CSVCollection::CSVCollection(const Path &entryfile_path) { - - auto file = entryfile_path.open(); - log::log(DBG << "Loading csv collection: " << file); - - // The file format is defined in: - // openage/convert/dataformat/data_definition.py - // example: - // - // ### some/folder/and/filename.docx - // # irrelevant - // # comments - // data,stuff,moar,bla - - // store the currently read file name - std::string current_file; - - for (auto &line : file.get_lines()) { - // a new file starts: - if (line[0] == '#' and - line[1] == '#' and - line[2] == '#' and - line[3] == ' ') { - - // remove the "### " - current_file = (line.erase(0, 4)); - - // create a vector to put lines in - this->data.emplace(current_file, std::vector{}); - } - else { - if (line.empty() or line[0] == '#') { - continue; - } - - if (current_file.size() > 0) [[likely]] { - // add line to the current file linelist - this->data.at(current_file).push_back(line); - } - else { - throw Error{ - ERR << "csv collection content encountered " - << "without known target in " << entryfile_path - << ", linedata: " << line - }; - } - } - } - - log::log(INFO << "Loaded multi-csv file: " - << this->data.size() << " sub-files"); -} - -}} // openage::util diff --git a/libopenage/util/csv.h b/libopenage/util/csv.h deleted file mode 100644 index 88d5ea0322..0000000000 --- a/libopenage/util/csv.h +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright 2013-2017 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "../error/error.h" -#include "compiler.h" -#include "fslike/native.h" -#include "path.h" - - -namespace openage { -namespace util { - - -/** - * Collection of multiple csv files. - * Read from a packed csv that contains all the data. - * - * Then, data can be read recursively. - */ -class CSVCollection { -public: - - /** - * Type for storing csv data: - * {filename: [line, ...]}. - */ - using csv_file_map_t = std::unordered_map>; - - /** - * Initialize the collection by reading the given file. - * This file must contain the data that this collection is made up of. - */ - explicit CSVCollection(const Path &entryfile); - virtual ~CSVCollection() = default; - - - /** - * This function is the entry point to load the whole file tree recursively. - * - * Should be called again from the .recurse() method of the struct. - * - * The internal flow is as follows: - * * read entries of the given files - * (call to the generated field parsers (the fill function)) - * * then, recurse into referenced subdata entries - * (this implementation is generated) - * * from there, reach this function again to read each subdata entry. - * - */ - template - std::vector read(const std::string &filename) const { - - // first read the content from the data - auto result = this->get_data(filename); - - std::string dir = dirname(filename); - - size_t line_count = 0; - - // then recurse into each subdata entry. - for (auto &entry : result) { - line_count += 1; - - if (not entry.recurse(*this, dir)) { - throw Error{ - MSG(err) - << "Failed to read follow-up files for " - << filename << ":" << line_count - }; - } - } - - return result; - } - - - /** - * Parse the data from one file in the map. - */ - template - std::vector get_data(const std::string &filename) const { - - size_t line_count = 0; - - std::vector ret; - - // locate the data in the collection - auto it = this->data.find(filename); - - if (it != std::end(this->data)) { - const std::vector &lines = it->second; - - for (auto &line : lines) { - line_count += 1; - lineformat current_line_data; - - // use the line copy to fill the current line struct. - int error_column = current_line_data.fill(line); - if (error_column != -1) { - throw Error{ - ERR - << "Failed to read CSV file: " - << filename << ":" << line_count << ":" << error_column - << ": error parsing " << line - }; - } - - ret.push_back(current_line_data); - } - } - else { - throw Error{ - ERR << "File was not found in csv cache: '" - << filename << "'" - }; - } - - return ret; - } - -protected: - csv_file_map_t data; -}; - - -/** - * Referenced file tree structure. - * - * Used for storing information for subtype members - * that need to be recursed. - */ -template -struct csv_subdata { - /** - * File where to read subdata from. - * This name is relative to the file that defined the subdata! - */ - std::string filename; - - /** - * Data that was read from the file with this->filename. - */ - std::vector data; - - /** - * Read the data of the lineformat from the collection. - * Can descend recursively into dependencies. - */ - bool read(const CSVCollection &collection, const std::string &basedir) { - std::string next_file = basedir; - if (basedir.size() > 0) { - next_file += fslike::PATHSEP; - } - next_file += this->filename; - - this->data = collection.read(next_file); - return true; - } - - /** - * Convenience operator to access data - */ - const lineformat &operator [](size_t idx) const { - return this->data[idx]; - } -}; - - -/** - * read a single csv file. - * call the destination struct .fill() method for actually storing line data - */ -template -std::vector read_csv_file(const Path &path) { - - File csv = path.open(); - - std::vector ret; - size_t line_count = 0; - - for (auto &line : csv.get_lines()) { - line_count += 1; - - // ignore comments and empty lines - if (line.empty() || line[0] == '#') { - continue; - } - - lineformat current_line_data; - - // use the line copy to fill the current line struct. - int error_column = current_line_data.fill(line); - if (error_column != -1) { - throw Error{ - ERR - << "Failed to read CSV file: " - << csv << ":" << line_count << ":" << error_column - << ": error parsing " << line - }; - } - - ret.push_back(current_line_data); - } - - return ret; -} - -}} // openage::util diff --git a/libopenage/util/enum.h b/libopenage/util/enum.h index 07e07256a3..c6a9b4666b 100644 --- a/libopenage/util/enum.h +++ b/libopenage/util/enum.h @@ -1,4 +1,4 @@ -// Copyright 2015-2021 the openage authors. See copying.md for legal info. +// Copyright 2015-2025 the openage authors. See copying.md for legal info. #pragma once @@ -43,45 +43,46 @@ namespace util { * const char *name * NumericType numeric */ -template -class OAAPI EnumValue { +template +class EnumValue { public: - constexpr EnumValue(const char *value_name, NumericType numeric_value): name(value_name), numeric(numeric_value) {} + constexpr EnumValue(const char *value_name, NumericType numeric_value) : + name(value_name), numeric(numeric_value) {} // enum values cannot be copied EnumValue(const EnumValue &other) = delete; - EnumValue &operator =(const EnumValue &other) = delete; + EnumValue &operator=(const EnumValue &other) = delete; // an explicit deletion of the implicitly defined copy constructor and assignment operator // will implicitly delete the implicitly defined move constructor and assignment operator. // yay for C++ // enum values are equal if the pointers are equal. - constexpr bool operator ==(const DerivedType &other) const { + constexpr bool operator==(const DerivedType &other) const { return (this == &other); } - constexpr bool operator !=(const DerivedType &other) const { + constexpr bool operator!=(const DerivedType &other) const { return !(*this == other); } - constexpr bool operator <=(const DerivedType &other) const { + constexpr bool operator<=(const DerivedType &other) const { return this->numeric <= other.numeric; } - constexpr bool operator <(const DerivedType &other) const { + constexpr bool operator<(const DerivedType &other) const { return this->numeric < other.numeric; } - constexpr bool operator >=(const DerivedType &other) const { + constexpr bool operator>=(const DerivedType &other) const { return this->numeric >= other.numeric; } - constexpr bool operator >(const DerivedType &other) const { + constexpr bool operator>(const DerivedType &other) const { return this->numeric > other.numeric; } - friend std::ostream &operator <<(std::ostream &os, const DerivedType &arg) { + friend std::ostream &operator<<(std::ostream &os, const DerivedType &arg) { os << util::typestring() << "::" << arg.name; return os; } @@ -122,49 +123,50 @@ class OAAPI EnumValue { * bool operator <=(Enum[DerivedType] other) except + * bool operator >=(Enum[DerivedType] other) except + */ -template -class OAAPI Enum { +template +class Enum { using this_type = Enum; public: // disallow the empty constructor to ensure that value is always a valid pointer. constexpr Enum() = delete; - constexpr Enum(const DerivedType &value) : value{&value} {} + constexpr Enum(const DerivedType &value) : + value{&value} {} constexpr explicit operator const DerivedType &() const { return *this->value; } - constexpr Enum &operator =(const DerivedType &value) { + constexpr Enum &operator=(const DerivedType &value) { this->value = &value; return *this; } - constexpr const DerivedType *operator ->() const { + constexpr const DerivedType *operator->() const { return this->value; } - constexpr bool operator ==(const this_type &other) const { + constexpr bool operator==(const this_type &other) const { return *this->value == *other.value; } - constexpr bool operator !=(const this_type &other) const { + constexpr bool operator!=(const this_type &other) const { return *this->value != *other.value; } - constexpr bool operator <=(const this_type &other) const { + constexpr bool operator<=(const this_type &other) const { return *this->value <= *other.value; } - constexpr bool operator <(const this_type &other) const { + constexpr bool operator<(const this_type &other) const { return *this->value < *other.value; } - constexpr bool operator >=(const this_type &other) const { + constexpr bool operator>=(const this_type &other) const { return *this->value >= *other.value; } - constexpr bool operator >(const this_type &other) const { + constexpr bool operator>(const this_type &other) const { return *this->value > *other.value; } @@ -172,7 +174,7 @@ class OAAPI Enum { return *this->value; } - friend std::ostream &operator <<(std::ostream &os, const this_type &arg) { + friend std::ostream &operator<<(std::ostream &os, const this_type &arg) { os << *arg.value; return os; } @@ -181,4 +183,5 @@ class OAAPI Enum { const DerivedType *value; }; -}} // openage::util +} // namespace util +} // namespace openage diff --git a/libopenage/util/enum_test.h b/libopenage/util/enum_test.h index 6f6409d5a6..0cc9ce0cab 100644 --- a/libopenage/util/enum_test.h +++ b/libopenage/util/enum_test.h @@ -1,4 +1,4 @@ -// Copyright 2015-2018 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once @@ -48,4 +48,6 @@ struct OAAPI testenum : Enum { static constexpr testenum_value bar{{"bar", 2}, "barrrrrrrrrrrrrrrrr"}; }; -}}} // openage::util::tests +} // namespace tests +} // namespace util +} // namespace openage diff --git a/libopenage/util/externalsstream.h b/libopenage/util/externalsstream.h index 8f30c3d3dd..3211b0c30a 100644 --- a/libopenage/util/externalsstream.h +++ b/libopenage/util/externalsstream.h @@ -1,4 +1,4 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #include #include @@ -43,8 +43,7 @@ class ExternalOStringStream : public std::ostream { /** * Creates a stream without a valid accumulator. */ - explicit ExternalOStringStream() - : + explicit ExternalOStringStream() : std::ostream{&this->buf} {} /** @@ -60,4 +59,5 @@ class ExternalOStringStream : public std::ostream { }; -}} // openage::util +} // namespace util +} // namespace openage diff --git a/libopenage/util/fds.cpp b/libopenage/util/fds.cpp index 468d3354bc..8d5188c10d 100644 --- a/libopenage/util/fds.cpp +++ b/libopenage/util/fds.cpp @@ -1,4 +1,4 @@ -// Copyright 2014-2019 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. #include "fds.h" @@ -7,12 +7,12 @@ #include #include -#include #include "pty.h" +#include #ifdef _WIN32 -#include + #include #else -#include + #include #endif #include "unicode.h" @@ -28,10 +28,10 @@ FD::FD(int fd, bool set_nonblocking) { this->close_on_destroy = true; if (set_nonblocking) { - #ifndef _WIN32 +#ifndef _WIN32 int flags = ::fcntl(this->fd, F_GETFL, 0); ::fcntl(this->fd, F_SETFL, flags | O_NONBLOCK); - #endif +#endif } } @@ -64,31 +64,32 @@ int FD::putbyte(char c) { int FD::putcp(int cp) { char utf8buf[5]; if (util::utf8_encode(cp, utf8buf) == 0) { - //unrepresentable character (question mark in black rhombus) + // unrepresentable character (question mark in black rhombus) return this->puts("\uFFFD"); - } else { + } + else { return this->puts(utf8buf); } } int FD::printf(const char *format, ...) { const unsigned buf_size = 16; - char *buf = static_cast(malloc(sizeof(char) * buf_size)); + char *buf = static_cast(malloc(sizeof(char) * buf_size)); if (!buf) { return -1; } va_list vl; - //first, try to vsnprintf to a buffer of length 16 + // first, try to vsnprintf to a buffer of length 16 va_start(vl, format); unsigned len = vsnprintf(buf, buf_size, format, vl); va_end(vl); - //if that wasn't enough, allocate more memory and try again + // if that wasn't enough, allocate more memory and try again if (len >= buf_size) { char *oldbuf = buf; - buf = static_cast(realloc(oldbuf, sizeof(char) * (len + 1))); + buf = static_cast(realloc(oldbuf, sizeof(char) * (len + 1))); if (!buf) { free(oldbuf); return -1; @@ -99,42 +100,42 @@ int FD::printf(const char *format, ...) { va_end(vl); } - //output buf to the socket + // output buf to the socket int result = this->puts(buf); - //free the buffer + // free the buffer free(buf); return result; } void FD::setinputmodecanon() { - #ifndef _WIN32 +#ifndef _WIN32 if (::isatty(this->fd)) { - //get the terminal settings for stdin + // get the terminal settings for stdin ::tcgetattr(this->fd, &this->old_tio); - //backup old settings + // backup old settings struct termios new_tio = this->old_tio; - //disable buffered i/o (canonical mode) and local echo + // disable buffered i/o (canonical mode) and local echo new_tio.c_lflag &= (~ICANON & ~ECHO & ~ISIG); - //set the settings + // set the settings ::tcsetattr(this->fd, TCSANOW, &new_tio); this->restore_input_mode_on_destroy = true; } - #endif /* _WIN32 */ +#endif /* _WIN32 */ } void FD::restoreinputmode() { - #ifndef _WIN32 +#ifndef _WIN32 if (::isatty(this->fd)) { ::tcsetattr(this->fd, TCSANOW, &this->old_tio); this->restore_input_mode_on_destroy = false; } - #endif /* _WIN32 */ +#endif /* _WIN32 */ } -} // openage::util +} // namespace openage::util diff --git a/libopenage/util/fds.h b/libopenage/util/fds.h index fe5bb08abf..309384e1ad 100644 --- a/libopenage/util/fds.h +++ b/libopenage/util/fds.h @@ -1,11 +1,11 @@ -// Copyright 2014-2019 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. #pragma once #include #ifndef _WIN32 -#include + #include #endif namespace openage { @@ -89,4 +89,5 @@ class FD { #endif }; -}} // openage::util +} // namespace util +} // namespace openage diff --git a/libopenage/util/file.cpp b/libopenage/util/file.cpp index 0acea80695..d035ee34f2 100644 --- a/libopenage/util/file.cpp +++ b/libopenage/util/file.cpp @@ -1,4 +1,4 @@ -// Copyright 2013-2019 the openage authors. See copying.md for legal info. +// Copyright 2013-2024 the openage authors. See copying.md for legal info. #include "file.h" @@ -9,11 +9,14 @@ #include #include -#include "path.h" -#include "filelike/native.h" -#include "filelike/python.h" -#include "../error/error.h" -#include "../log/log.h" +#include "error/error.h" +#include "log/log.h" + +#include "util/filelike/native.h" +#include "util/filelike/python.h" +#include "util/fslike/directory.h" +#include "util/path.h" +#include "util/strings.h" namespace openage::util { @@ -23,8 +26,7 @@ File::File() = default; // yes. i'm sorry. but cython can't enum class yet. -File::File(const std::string &path, int mode) - : +File::File(const std::string &path, int mode) : File{path, static_cast(mode)} {} @@ -33,8 +35,7 @@ File::File(const std::string &path, mode_t mode) { } -File::File(std::shared_ptr filelike) - : +File::File(std::shared_ptr filelike) : filelike{filelike} {} @@ -102,13 +103,7 @@ std::vector File::get_lines() { // TODO: relay the get_lines to the underlaying filelike // which may do a better job in getting the lines. // instead, we read everything and then split up into lines. - std::string line; - std::vector result{}; - std::istringstream content{this->read()}; - - while (std::getline(content, line)) { - result.push_back(line); - } + std::vector result = util::split_newline(this->read()); return result; } @@ -119,7 +114,7 @@ std::shared_ptr File::get_fileobj() const { } -std::ostream &operator <<(std::ostream &stream, const File &file) { +std::ostream &operator<<(std::ostream &stream, const File &file) { stream << "File("; file.filelike->repr(stream); stream << ")"; @@ -127,5 +122,21 @@ std::ostream &operator <<(std::ostream &stream, const File &file) { return stream; } +File File::get_temp_file(bool executable) { + fslike::Directory temp_dir = fslike::Directory::get_temp_directory(); + std::string file_name = std::tmpnam(nullptr); + std::ostringstream dir_path; + temp_dir.repr(dir_path); + + if (executable) { + // 0755 == rwxr-xr-x + File file_wrapper = File(dir_path.str() + file_name, 0755); + return file_wrapper; + } + + // 0644 == rw-r--r-- + File file_wrapper = File(dir_path.str() + file_name, 0644); + return file_wrapper; +} -} // openage::util +} // namespace openage::util diff --git a/libopenage/util/file.h b/libopenage/util/file.h index 217bd0f943..e2c9a8a992 100644 --- a/libopenage/util/file.h +++ b/libopenage/util/file.h @@ -1,4 +1,4 @@ -// Copyright 2013-2019 the openage authors. See copying.md for legal info. +// Copyright 2013-2024 the openage authors. See copying.md for legal info. #pragma once @@ -56,7 +56,7 @@ class OAAPI File { /** * Open a filesystem path. */ - File(const std::string &path, mode_t mode=mode_t::R); + File(const std::string &path, mode_t mode = mode_t::R); /** * Create a file from an already created filelike. @@ -77,7 +77,7 @@ class OAAPI File { * Read data from the file and return a string. * If max is negative, return the full remaining file. */ - std::string read(ssize_t max=-1); + std::string read(ssize_t max = -1); /** * Read data from the file into a buffer, @@ -87,24 +87,26 @@ class OAAPI File { * * Returns the number of bytes that were read. */ - size_t read_to(void *buf, ssize_t max=-1); + size_t read_to(void *buf, ssize_t max = -1); bool readable(); void write(const std::string &data); bool writable(); - void seek(ssize_t offset, seek_t how=seek_t::SET); + void seek(ssize_t offset, seek_t how = seek_t::SET); bool seekable(); size_t tell(); void close(); void flush(); ssize_t size(); std::vector get_lines(); - std::shared_ptr get_fileobj() const; + static File get_temp_file(bool executable = false); + protected: std::shared_ptr filelike; - friend std::ostream &operator <<(std::ostream &stream, const File &file); + friend std::ostream &operator<<(std::ostream &stream, const File &file); }; -}} // openage::util +} // namespace util +} // namespace openage diff --git a/libopenage/util/filelike/filelike.h b/libopenage/util/filelike/filelike.h index 370c1eb99c..ed1bc8b2f4 100644 --- a/libopenage/util/filelike/filelike.h +++ b/libopenage/util/filelike/filelike.h @@ -1,4 +1,4 @@ -// Copyright 2017-2018 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once @@ -46,9 +46,9 @@ class OAAPI FileLike { * Don't change the numbers, they're used from Cython. */ enum class seek_t : int { - SET = 0, //!< offset from file beginning - CUR = 1, //!< offset from current position - END = 2 //!< offset from file end + SET = 0, //!< offset from file beginning + CUR = 1, //!< offset from current position + END = 2 //!< offset from file end }; /** @@ -70,8 +70,8 @@ class OAAPI FileLike { virtual ~FileLike() = default; - virtual std::string read(ssize_t max=-1) = 0; - virtual size_t read_to(void *buf, ssize_t max=-1) = 0; + virtual std::string read(ssize_t max = -1) = 0; + virtual size_t read_to(void *buf, ssize_t max = -1) = 0; virtual bool readable() = 0; @@ -79,7 +79,7 @@ class OAAPI FileLike { virtual bool writable() = 0; - virtual void seek(ssize_t offset, seek_t how=seek_t::SET) = 0; + virtual void seek(ssize_t offset, seek_t how = seek_t::SET) = 0; virtual bool seekable() = 0; virtual size_t tell() = 0; virtual void close() = 0; @@ -93,4 +93,6 @@ class OAAPI FileLike { }; -}}} // openage::util::filelike +} // namespace filelike +} // namespace util +} // namespace openage diff --git a/libopenage/util/filelike/native.h b/libopenage/util/filelike/native.h index d245c92f2c..60844db7ff 100644 --- a/libopenage/util/filelike/native.h +++ b/libopenage/util/filelike/native.h @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once @@ -19,7 +19,7 @@ namespace filelike { */ class Native : public FileLike { public: - Native(const std::string &path, mode_t mode=mode_t::R); + Native(const std::string &path, mode_t mode = mode_t::R); virtual ~Native(); std::string read(ssize_t max) override; @@ -31,7 +31,7 @@ class Native : public FileLike { bool writable() override; - void seek(ssize_t offset, seek_t how=seek_t::SET) override; + void seek(ssize_t offset, seek_t how = seek_t::SET) override; bool seekable() override; size_t tell() override; void close() override; @@ -48,4 +48,6 @@ class Native : public FileLike { std::fstream file; }; -}}} // openage::util::filelike +} // namespace filelike +} // namespace util +} // namespace openage diff --git a/libopenage/util/filelike/python.h b/libopenage/util/filelike/python.h index 91ba161c92..02e397092e 100644 --- a/libopenage/util/filelike/python.h +++ b/libopenage/util/filelike/python.h @@ -1,4 +1,4 @@ -// Copyright 2017-2019 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once @@ -41,7 +41,7 @@ class Python : public FileLike { bool writable() override; - void seek(ssize_t offset, seek_t how=seek_t::SET) override; + void seek(ssize_t offset, seek_t how = seek_t::SET) override; bool seekable() override; size_t tell() override; void close() override; @@ -95,4 +95,6 @@ extern OAAPI pyinterface::PyIfFunc pyx_file_flush; extern OAAPI pyinterface::PyIfFunc pyx_file_size; -}}} // openage::util::filelike +} // namespace filelike +} // namespace util +} // namespace openage diff --git a/libopenage/util/fixed_point.h b/libopenage/util/fixed_point.h index 196ca17f54..c751238cdf 100644 --- a/libopenage/util/fixed_point.h +++ b/libopenage/util/fixed_point.h @@ -1,8 +1,9 @@ -// Copyright 2015-2023 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 @@ -53,8 +54,8 @@ constexpr static /** - * Helper function that performs either a safe shift-right (amount > 0), - * or a safe shift-left (amount < 0). + * Helper function that performs either a safe shift-right (amount < 0), + * or a safe shift-left (amount >= 0). */ template constexpr static @@ -81,15 +82,20 @@ constexpr static * For example, * FixedPoint * can store values from -2**32 to +2**32 with a constant precision of 2**-32. + * + * 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. @@ -169,6 +175,74 @@ class FixedPoint { return FixedPoint::from_int(0); } + /** + * Math constants represented in FixedPoint + */ + // naming, definition and value are kept compatible with `math_constants.h` + static constexpr FixedPoint e() { + return from_fixedpoint(FixedPoint::from_raw_value(6267931151224907085ll)); + } + + static constexpr FixedPoint log2e() { + return from_fixedpoint(FixedPoint::from_raw_value(3326628274461080622ll)); + } + + static constexpr FixedPoint log10e() { + return from_fixedpoint(FixedPoint::from_raw_value(1001414895036696345ll)); + } + + static constexpr FixedPoint ln2() { + return from_fixedpoint(FixedPoint::from_raw_value(1598288580650331957ll)); + } + + static constexpr FixedPoint ln10() { + return from_fixedpoint(FixedPoint::from_raw_value(5309399739799983627ll)); + } + + static constexpr FixedPoint pi() { + return from_fixedpoint(FixedPoint::from_raw_value(7244019458077122842ll)); + } + + static constexpr FixedPoint pi_2() { + return from_fixedpoint(FixedPoint::from_raw_value(3622009729038561421ll)); + } + + static constexpr FixedPoint pi_4() { + return from_fixedpoint(FixedPoint::from_raw_value(1811004864519280710ll)); + } + + static constexpr FixedPoint inv_pi() { + return from_fixedpoint(FixedPoint::from_raw_value(733972625820500306ll)); + } + + static constexpr FixedPoint inv2_pi() { + return from_fixedpoint(FixedPoint::from_raw_value(1467945251641000613ll)); + } + + static constexpr FixedPoint inv2_sqrt_pi() { + return from_fixedpoint(FixedPoint::from_raw_value(2601865214189558307ll)); + } + + static constexpr FixedPoint tau() { + return from_fixedpoint(FixedPoint::from_raw_value(7244019458077122842ll)); + } + + static constexpr FixedPoint degs_per_rad() { + return from_fixedpoint(FixedPoint::from_raw_value(40244552544872904ll)); + } + + static constexpr FixedPoint rads_per_deg() { + return from_fixedpoint(FixedPoint::from_raw_value(8257192040480628449ll)); + } + + static constexpr FixedPoint sqrt_2() { + return from_fixedpoint(FixedPoint::from_raw_value(3260954456333195553ll)); + } + + static constexpr FixedPoint inv_sqrt_2() { + return from_fixedpoint(FixedPoint::from_raw_value(1630477228166597776ll)); + } + /** * Factory function to get a fixed-point number from an integer. */ @@ -193,10 +267,16 @@ class FixedPoint { /** * Factory function to get a fixed-point number from a fixed-point number of different type. */ - template - 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(other.get_raw_value())); + safe_shift(static_cast(other.get_raw_value()))); + } + + 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))); } /** @@ -303,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 @@ -367,8 +447,68 @@ 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()); } }; @@ -378,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); } /* @@ -430,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); @@ -476,51 +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) { + return x.atan2(y); +} + +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( +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 569d177e42..b3690b8d15 100644 --- a/libopenage/util/fixed_point_test.cpp +++ b/libopenage/util/fixed_point_test.cpp @@ -1,4 +1,4 @@ -// Copyright 2016-2018 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" @@ -6,6 +6,7 @@ #include "../testing/testing.h" #include "stringformatter.h" +#include "math_constants.h" namespace openage { namespace util { @@ -58,6 +59,7 @@ void fixed_point() { TESTEQUALS_FLOAT((e * 10).to_double(), 108.3 * 10, 1e-7); TESTEQUALS_FLOAT((e / 10).to_double(), 108.3 / 10, 1e-7); TESTEQUALS_FLOAT(std::sqrt(e), sqrt(108.3), 1e-7); + TESTEQUALS_FLOAT(std::atan2(e, f), atan2(108.3, -12.4), 1e-7); TESTEQUALS_FLOAT(std::abs(-e).to_double(), 108.3, 1e-7); TESTEQUALS_FLOAT(std::hypot(e, f), hypot(108.3, -12.4), 1e-7); TESTEQUALS_FLOAT(std::min(e, f), -12.4, 1e-7); @@ -97,6 +99,110 @@ void fixed_point() { std::stringstream sstr("1234.5678"); sstr >> e; TESTEQUALS_FLOAT(e.to_double(), 1234.5678, 1e-7); + + TESTEQUALS_FLOAT(TestType::e().to_double(), math::E, 1e-7); + TESTEQUALS_FLOAT(TestType::log2e().to_double(), math::LOG2E, 1e-7); + TESTEQUALS_FLOAT(TestType::log10e().to_double(), math::LOG10E, 1e-7); + TESTEQUALS_FLOAT(TestType::ln2().to_double(), math::LN2, 1e-7); + TESTEQUALS_FLOAT(TestType::ln10().to_double(), math::LN10, 1e-7); + TESTEQUALS_FLOAT(TestType::pi().to_double(), math::PI, 1e-7); + TESTEQUALS_FLOAT(TestType::pi_2().to_double(), math::PI_2, 1e-7); + TESTEQUALS_FLOAT(TestType::pi_4().to_double(), math::PI_4, 1e-7); + TESTEQUALS_FLOAT(TestType::inv_pi().to_double(), math::INV_PI, 1e-7); + TESTEQUALS_FLOAT(TestType::inv2_pi().to_double(), math::INV2_PI, 1e-7); + TESTEQUALS_FLOAT(TestType::inv2_sqrt_pi().to_double(), math::INV2_SQRT_PI, 1e-7); + TESTEQUALS_FLOAT(TestType::tau().to_double(), math::TAU, 1e-7); + TESTEQUALS_FLOAT(TestType::degs_per_rad().to_double(), math::DEGSPERRAD, 1e-7); + TESTEQUALS_FLOAT(TestType::rads_per_deg().to_double(), math::RADSPERDEG, 1e-7); + TESTEQUALS_FLOAT(TestType::sqrt_2().to_double(), math::SQRT_2, 1e-7); + TESTEQUALS_FLOAT(TestType::inv_sqrt_2().to_double(), math::INV_SQRT_2, 1e-7); + + + using TestTypeShort = FixedPoint; + 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/libopenage/util/fps.h b/libopenage/util/fps.h index 45959126c3..d853047c42 100644 --- a/libopenage/util/fps.h +++ b/libopenage/util/fps.h @@ -1,4 +1,4 @@ -// Copyright 2013-2019 the openage authors. See copying.md for legal info. +// Copyright 2013-2024 the openage authors. See copying.md for legal info. #pragma once @@ -34,4 +34,5 @@ class FrameCounter { Timer frame_timer; }; -}} // openage::util +} // namespace util +} // namespace openage diff --git a/libopenage/util/fslike/directory.cpp b/libopenage/util/fslike/directory.cpp index 22055e2c32..847ffe4d80 100644 --- a/libopenage/util/fslike/directory.cpp +++ b/libopenage/util/fslike/directory.cpp @@ -1,50 +1,49 @@ -// Copyright 2017-2019 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #include "directory.h" // HACK: windows.h defines max and min as macros. This results in compile errors. #ifdef _WIN32 -// defining `NOMINMAX` disables the definition of those macros. -#define NOMINMAX + // defining `NOMINMAX` disables the definition of those macros. + #define NOMINMAX #endif #include #include #include +#include #include #include #include #include #ifdef __APPLE__ -#include + #include #endif #ifdef _WIN32 -#include -#include -#include -// HACK: What the heck? I want the std::filesystem library! -#define O_NOCTTY 0 -#define O_NONBLOCK 0 -#define W_OK 2 + #include + #include + #include + // HACK: What the heck? I want the std::filesystem library! + #define O_NOCTTY 0 + #define O_NONBLOCK 0 + #define W_OK 2 #else // ! _MSC_VER -#include + #include #endif -#include "./native.h" #include "../file.h" #include "../filelike/native.h" #include "../misc.h" #include "../path.h" +#include "./native.h" namespace openage::util::fslike { -Directory::Directory(std::string basepath, bool create_if_missing) - : +Directory::Directory(std::string basepath, bool create_if_missing) : basepath{std::move(basepath)} { - if (create_if_missing) { this->mkdirs({}); } @@ -78,8 +77,7 @@ bool Directory::is_file(const Path::parts_t &parts) { auto stat_result = this->do_stat(parts); // test for regular file - if (std::get<1>(stat_result) == 0 and - S_ISREG(std::get<0>(stat_result).st_mode)) { + if (std::get<1>(stat_result) == 0 and S_ISREG(std::get<0>(stat_result).st_mode)) { return true; } @@ -91,8 +89,7 @@ bool Directory::is_dir(const Path::parts_t &parts) { auto stat_result = this->do_stat(parts); // test for regular file - if (std::get<1>(stat_result) == 0 and - S_ISDIR(std::get<0>(stat_result).st_mode)) { + if (std::get<1>(stat_result) == 0 and S_ISDIR(std::get<0>(stat_result).st_mode)) { return true; } @@ -104,9 +101,7 @@ bool Directory::writable(const Path::parts_t &parts) { Path::parts_t parts_test = parts; // try to find the first existing path-part - while (not (this->is_dir(parts_test) or - this->is_file(parts_test))) { - + while (not(this->is_dir(parts_test) or this->is_file(parts_test))) { if (parts_test.size() == 0) { throw Error{ERR << "file not found"}; } @@ -120,7 +115,6 @@ bool Directory::writable(const Path::parts_t &parts) { std::vector Directory::list(const Path::parts_t &parts) { - const std::string path = this->resolve(parts); std::vector ret; @@ -145,7 +139,6 @@ std::vector Directory::list(const Path::parts_t &parts) { bool Directory::mkdirs(const Path::parts_t &parts) { - Path::parts_t all_parts = util::split(this->basepath, PATHSEP); vector_extend(all_parts, parts); @@ -158,9 +151,7 @@ bool Directory::mkdirs(const Path::parts_t &parts) { struct stat buf; // it it exists already, try creating the next one - if (stat(dirpath.c_str(), &buf) == 0 and - S_ISDIR(buf.st_mode)) { - + if (stat(dirpath.c_str(), &buf) == 0 and S_ISDIR(buf.st_mode)) { continue; } @@ -184,40 +175,35 @@ bool Directory::mkdirs(const Path::parts_t &parts) { File Directory::open_r(const Path::parts_t &parts) { return File{ std::make_shared(this->resolve(parts), - filelike::Native::mode_t::R) - }; + filelike::Native::mode_t::R)}; } File Directory::open_w(const Path::parts_t &parts) { return File{ std::make_shared(this->resolve(parts), - filelike::Native::mode_t::W) - }; + filelike::Native::mode_t::W)}; } File Directory::open_rw(const Path::parts_t &parts) { return File{ std::make_shared(this->resolve(parts), - filelike::Native::mode_t::RW) - }; + filelike::Native::mode_t::RW)}; } File Directory::open_a(const Path::parts_t &parts) { return File{ std::make_shared(this->resolve(parts), - filelike::Native::mode_t::A) - }; + filelike::Native::mode_t::A)}; } File Directory::open_ar(const Path::parts_t &parts) { return File{ std::make_shared(this->resolve(parts), - filelike::Native::mode_t::AR) - }; + filelike::Native::mode_t::AR)}; } @@ -228,9 +214,9 @@ std::string Directory::get_native_path(const Path::parts_t &parts) { bool Directory::rename(const Path::parts_t &parts, const Path::parts_t &target_parts) { - return std::rename(this->resolve(parts).c_str(), - this->resolve(target_parts).c_str()) == 0; + this->resolve(target_parts).c_str()) + == 0; } @@ -245,9 +231,8 @@ bool Directory::touch(const Path::parts_t &parts) { // create the file if missing int fd = open( path.c_str(), - O_WRONLY|O_CREAT|O_NOCTTY|O_NONBLOCK, - 0666 - ); + O_WRONLY | O_CREAT | O_NOCTTY | O_NONBLOCK, + 0666); if (fd < 0) { return false; @@ -308,4 +293,12 @@ std::ostream &Directory::repr(std::ostream &stream) { return stream; } -} // openage::util::fslike +Directory Directory::get_temp_directory() { + std::filesystem::path path = std::filesystem::temp_directory_path() / std::tmpnam(nullptr); + std::string temp_dir_path = path.string(); + bool create = true; + Directory directory = Directory(temp_dir_path, create); + return directory; +} + +} // namespace openage::util::fslike diff --git a/libopenage/util/fslike/directory.h b/libopenage/util/fslike/directory.h index 838caf6288..9f72d49766 100644 --- a/libopenage/util/fslike/directory.h +++ b/libopenage/util/fslike/directory.h @@ -1,4 +1,4 @@ -// Copyright 2017-2019 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once @@ -23,7 +23,7 @@ namespace fslike { */ class Directory : public FSLike { public: - Directory(std::string basepath, bool create_if_missing=false); + Directory(std::string basepath, bool create_if_missing = false); bool is_file(const Path::parts_t &parts) override; bool is_dir(const Path::parts_t &parts) override; @@ -48,6 +48,8 @@ class Directory : public FSLike { std::ostream &repr(std::ostream &) override; + static Directory get_temp_directory(); + protected: /** * resolve the path to an actually usable one. @@ -59,5 +61,6 @@ class Directory : public FSLike { std::string basepath; }; - -}}} // openage::util::fslike +} // namespace fslike +} // namespace util +} // namespace openage diff --git a/libopenage/util/fslike/fslike.h b/libopenage/util/fslike/fslike.h index 5973782f8c..f9b36631e0 100644 --- a/libopenage/util/fslike/fslike.h +++ b/libopenage/util/fslike/fslike.h @@ -1,4 +1,4 @@ -// Copyright 2017-2018 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once @@ -104,4 +104,6 @@ class OAAPI FSLike : public std::enable_shared_from_this { virtual std::ostream &repr(std::ostream &) = 0; }; -}}} // openage::util::fslike +} // namespace fslike +} // namespace util +} // namespace openage diff --git a/libopenage/util/fslike/native.h b/libopenage/util/fslike/native.h index 75f4936681..5a343b54f5 100644 --- a/libopenage/util/fslike/native.h +++ b/libopenage/util/fslike/native.h @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once @@ -13,4 +13,6 @@ namespace fslike { constexpr char PATHSEP = '/'; -}}} // openage::util::fslike +} // namespace fslike +} // namespace util +} // namespace openage diff --git a/libopenage/util/fslike/python.h b/libopenage/util/fslike/python.h index afc8d34f90..483c2e40ce 100644 --- a/libopenage/util/fslike/python.h +++ b/libopenage/util/fslike/python.h @@ -1,4 +1,4 @@ -// Copyright 2017-2019 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once @@ -76,65 +76,67 @@ class Python : public FSLike { // pxd: PyIfFunc2[bool, PyObjectPtr, const vector[string]] pyx_fs_is_file -extern OAAPI pyinterface::PyIfFunc&> pyx_fs_is_file; +extern OAAPI pyinterface::PyIfFunc &> pyx_fs_is_file; // pxd: PyIfFunc2[bool, PyObjectPtr, const vector[string]] pyx_fs_is_dir -extern OAAPI pyinterface::PyIfFunc&> pyx_fs_is_dir; +extern OAAPI pyinterface::PyIfFunc &> pyx_fs_is_dir; // pxd: PyIfFunc2[bool, PyObjectPtr, const vector[string]] pyx_fs_writable -extern OAAPI pyinterface::PyIfFunc&> pyx_fs_writable; +extern OAAPI pyinterface::PyIfFunc &> pyx_fs_writable; // pxd: PyIfFunc2[vector[string], PyObjectPtr, const vector[string]] pyx_fs_list -extern OAAPI pyinterface::PyIfFunc, PyObject *, const std::vector&> pyx_fs_list; +extern OAAPI pyinterface::PyIfFunc, PyObject *, const std::vector &> pyx_fs_list; // pxd: PyIfFunc2[bool, PyObjectPtr, const vector[string]] pyx_fs_mkdirs -extern OAAPI pyinterface::PyIfFunc&> pyx_fs_mkdirs; +extern OAAPI pyinterface::PyIfFunc &> pyx_fs_mkdirs; // pxd: PyIfFunc2[File, PyObjectPtr, const vector[string]] pyx_fs_open_r -extern OAAPI pyinterface::PyIfFunc&> pyx_fs_open_r; +extern OAAPI pyinterface::PyIfFunc &> pyx_fs_open_r; // pxd: PyIfFunc2[File, PyObjectPtr, const vector[string]] pyx_fs_open_w -extern OAAPI pyinterface::PyIfFunc&> pyx_fs_open_w; +extern OAAPI pyinterface::PyIfFunc &> pyx_fs_open_w; // pxd: PyIfFunc2[File, PyObjectPtr, const vector[string]] pyx_fs_open_rw -extern OAAPI pyinterface::PyIfFunc&> pyx_fs_open_rw; +extern OAAPI pyinterface::PyIfFunc &> pyx_fs_open_rw; // pxd: PyIfFunc2[File, PyObjectPtr, const vector[string]] pyx_fs_open_a -extern OAAPI pyinterface::PyIfFunc&> pyx_fs_open_a; +extern OAAPI pyinterface::PyIfFunc &> pyx_fs_open_a; // pxd: PyIfFunc2[File, PyObjectPtr, const vector[string]] pyx_fs_open_ar -extern OAAPI pyinterface::PyIfFunc&> pyx_fs_open_ar; +extern OAAPI pyinterface::PyIfFunc &> pyx_fs_open_ar; // pxd: PyIfFunc2[Path, PyObjectPtr, const vector[string]] pyx_fs_resolve_r -extern OAAPI pyinterface::PyIfFunc&> pyx_fs_resolve_r; +extern OAAPI pyinterface::PyIfFunc &> pyx_fs_resolve_r; // pxd: PyIfFunc2[Path, PyObjectPtr, const vector[string]] pyx_fs_resolve_w -extern OAAPI pyinterface::PyIfFunc&> pyx_fs_resolve_w; +extern OAAPI pyinterface::PyIfFunc &> pyx_fs_resolve_w; // pxd: PyIfFunc2[PyObjectRef, PyObjectPtr, const vector[string]] pyx_fs_get_native_path -extern OAAPI pyinterface::PyIfFunc&> pyx_fs_get_native_path; +extern OAAPI pyinterface::PyIfFunc &> pyx_fs_get_native_path; // pxd: PyIfFunc3[bool, PyObjectPtr, const vector[string], const vector[string]] pyx_fs_rename -extern OAAPI pyinterface::PyIfFunc&, const std::vector&> pyx_fs_rename; +extern OAAPI pyinterface::PyIfFunc &, const std::vector &> pyx_fs_rename; // pxd: PyIfFunc2[bool, PyObjectPtr, const vector[string]] pyx_fs_rmdir -extern OAAPI pyinterface::PyIfFunc&> pyx_fs_rmdir; +extern OAAPI pyinterface::PyIfFunc &> pyx_fs_rmdir; // pxd: PyIfFunc2[bool, PyObjectPtr, const vector[string]] pyx_fs_touch -extern OAAPI pyinterface::PyIfFunc&> pyx_fs_touch; +extern OAAPI pyinterface::PyIfFunc &> pyx_fs_touch; // pxd: PyIfFunc2[bool, PyObjectPtr, const vector[string]] pyx_fs_unlink -extern OAAPI pyinterface::PyIfFunc&> pyx_fs_unlink; +extern OAAPI pyinterface::PyIfFunc &> pyx_fs_unlink; // pxd: PyIfFunc2[int, PyObjectPtr, const vector[string]] pyx_fs_get_mtime -extern OAAPI pyinterface::PyIfFunc&> pyx_fs_get_mtime; +extern OAAPI pyinterface::PyIfFunc &> pyx_fs_get_mtime; // pxd: PyIfFunc2[uint64_t, PyObjectPtr, const vector[string]] pyx_fs_get_filesize -extern OAAPI pyinterface::PyIfFunc&> pyx_fs_get_filesize; +extern OAAPI pyinterface::PyIfFunc &> pyx_fs_get_filesize; // pxd: PyIfFunc1[bool, PyObjectPtr] pyx_fs_is_fslike_directory extern OAAPI pyinterface::PyIfFunc pyx_fs_is_fslike_directory; -}}} // openage::util::fslike +} // namespace fslike +} // namespace util +} // namespace openage diff --git a/libopenage/util/hash.h b/libopenage/util/hash.h index 2e13b6e2a7..114f7a9b82 100644 --- a/libopenage/util/hash.h +++ b/libopenage/util/hash.h @@ -1,10 +1,10 @@ -// Copyright 2015-2021 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once +#include #include #include -#include #include #include @@ -17,7 +17,7 @@ namespace util { template size_t type_hash() { - return std::hash()(std::type_index(typeid(T))); + return std::hash()(std::type_index(typeid(T))); } /** @@ -29,7 +29,6 @@ size_t type_hash() { size_t hash_combine(size_t hash1, size_t hash2); - /** \class Siphash * Contains a Siphash implementration. * @@ -49,7 +48,7 @@ class Siphash { * @param k Key to use with this hasher. * @return Reference to itself, for method chaining. */ - Siphash& set_key(std::array key); + Siphash &set_key(std::array key); /** @@ -81,4 +80,5 @@ class Siphash { }; -}} // openage::util +} // namespace util +} // namespace openage diff --git a/libopenage/util/init.h b/libopenage/util/init.h index 3a3a80a7ae..1251e9ab85 100644 --- a/libopenage/util/init.h +++ b/libopenage/util/init.h @@ -1,4 +1,4 @@ -// Copyright 2015-2018 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once @@ -23,7 +23,7 @@ namespace util { */ class OnInit { public: - explicit OnInit(std::function code) { + explicit OnInit(std::function code) { code(); } @@ -34,8 +34,8 @@ class OnInit { OnInit(const OnInit &) = delete; OnInit(OnInit &&) = delete; - OnInit &operator =(const OnInit &) = delete; - OnInit &operator =(OnInit &&) = delete; + OnInit &operator=(const OnInit &) = delete; + OnInit &operator=(OnInit &&) = delete; }; @@ -47,8 +47,7 @@ class OnInit { */ class OnDeInit { public: - explicit OnDeInit(std::function code) - : + explicit OnDeInit(std::function code) : code{code} {} @@ -57,15 +56,16 @@ class OnDeInit { } private: - std::function code; + std::function code; // nope. OnDeInit(const OnDeInit &) = delete; OnDeInit(OnDeInit &&) = delete; - OnDeInit &operator =(const OnDeInit &) = delete; - OnDeInit &operator =(OnDeInit &&) = delete; + OnDeInit &operator=(const OnDeInit &) = delete; + OnDeInit &operator=(OnDeInit &&) = delete; }; -}} // openage::util +} // namespace util +} // namespace openage diff --git a/libopenage/util/language.h b/libopenage/util/language.h index 9d0e5a887e..889cdbd001 100644 --- a/libopenage/util/language.h +++ b/libopenage/util/language.h @@ -1,4 +1,4 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once @@ -16,16 +16,16 @@ namespace util { * Simple wrapper type that contains a function pointer. * Needed as workaround for http://stackoverflow.com/questions/31040075 */ -template +template class FunctionPtr { public: /** * Implicit conversion from raw type. */ - FunctionPtr(ReturnType (*ptr)(ArgTypes ...)) : + FunctionPtr(ReturnType (*ptr)(ArgTypes...)) : ptr{ptr} {} - ReturnType (*ptr)(ArgTypes ...); + ReturnType (*ptr)(ArgTypes...); }; @@ -34,8 +34,9 @@ class FunctionPtr { * readers, compilers and linters that you are, in fact, ignoring the * function's return value on purpose. */ -template +template inline void ignore_result(T /* unused result */) {} -}} // openage::util +} // namespace util +} // namespace openage diff --git a/libopenage/util/macro/concat.h b/libopenage/util/macro/concat.h index 90c069b3d2..dd057ccd9f 100644 --- a/libopenage/util/macro/concat.h +++ b/libopenage/util/macro/concat.h @@ -1,23 +1,24 @@ -// Copyright 2013-2017 the openage authors. See copying.md for legal info. +// Copyright 2013-2024 the openage authors. See copying.md for legal info. #pragma once -#define CONCAT_1(OP, X) (X) -#define CONCAT_2(OP, X, Y) (X) OP (Y) -#define CONCAT_3(OP, X, Y, Z) (X) OP (Y) OP (Z) +#define CONCAT_1(OP, X) (X) +#define CONCAT_2(OP, X, Y) (X) OP(Y) +#define CONCAT_3(OP, X, Y, Z) (X) OP(Y) OP(Z) #define CONCAT_N(_1, _2, _3, NAME, ...) NAME #ifdef _MSC_VER -#define CONCAT_COUNT_ARGS_IMPL(args) CONCAT_N args -#define CONCAT_COUNT_ARGS(...) CONCAT_COUNT_ARGS_IMPL((__VA_ARGS__, 3, 2, 1)) -#define CONCAT_HELPER2(count) CONCAT_##count -#define CONCAT_HELPER1(count) CONCAT_HELPER2(count) -#define CONCAT_HELPER(count) CONCAT_HELPER1(count) -#define CONCAT_GLUE(x, y) x y -#define CONCAT(OP, ...) CONCAT_GLUE(CONCAT_HELPER(CONCAT_COUNT_ARGS(__VA_ARGS__)), (OP, __VA_ARGS__)) + #define CONCAT_COUNT_ARGS_IMPL(args) CONCAT_N args + #define CONCAT_COUNT_ARGS(...) CONCAT_COUNT_ARGS_IMPL((__VA_ARGS__, 3, 2, 1)) + #define CONCAT_HELPER2(count) CONCAT_##count + #define CONCAT_HELPER1(count) CONCAT_HELPER2(count) + #define CONCAT_HELPER(count) CONCAT_HELPER1(count) + #define CONCAT_GLUE(x, y) x y + #define CONCAT(OP, ...) CONCAT_GLUE(CONCAT_HELPER(CONCAT_COUNT_ARGS(__VA_ARGS__)), (OP, __VA_ARGS__)) #else -#define CONCAT(OP, ...) CONCAT_N(__VA_ARGS__, \ - CONCAT_3, CONCAT_2, CONCAT_1 \ - ) (OP, __VA_ARGS__) + #define CONCAT(OP, ...) CONCAT_N(__VA_ARGS__, \ + CONCAT_3, \ + CONCAT_2, \ + CONCAT_1)(OP, __VA_ARGS__) #endif diff --git a/libopenage/util/macro/loop.h b/libopenage/util/macro/loop.h index 0678afb960..c868a12694 100644 --- a/libopenage/util/macro/loop.h +++ b/libopenage/util/macro/loop.h @@ -1,23 +1,24 @@ -// Copyright 2013-2017 the openage authors. See copying.md for legal info. +// Copyright 2013-2024 the openage authors. See copying.md for legal info. #pragma once -#define LOOP_1(MACRO, X) MACRO(X) -#define LOOP_2(MACRO, X, Y) MACRO(X), MACRO(Y) +#define LOOP_1(MACRO, X) MACRO(X) +#define LOOP_2(MACRO, X, Y) MACRO(X), MACRO(Y) #define LOOP_3(MACRO, X, Y, Z) MACRO(X), MACRO(Y), MACRO(Z) #define LOOP_N(_1, _2, _3, NAME, ...) NAME #ifdef _MSC_VER -#define LOOP_COUNT_ARGS_IMPL(args) LOOP_N args -#define LOOP_COUNT_ARGS(...) LOOP_COUNT_ARGS_IMPL((__VA_ARGS__, 3, 2, 1)) -#define LOOP_HELPER2(count) LOOP_##count -#define LOOP_HELPER1(count) LOOP_HELPER2(count) -#define LOOP_HELPER(count) LOOP_HELPER1(count) -#define LOOP_GLUE(x, y) x y -#define LOOP(MACRO, ...) LOOP_GLUE(LOOP_HELPER(LOOP_COUNT_ARGS(__VA_ARGS__)), (MACRO, __VA_ARGS__)) + #define LOOP_COUNT_ARGS_IMPL(args) LOOP_N args + #define LOOP_COUNT_ARGS(...) LOOP_COUNT_ARGS_IMPL((__VA_ARGS__, 3, 2, 1)) + #define LOOP_HELPER2(count) LOOP_##count + #define LOOP_HELPER1(count) LOOP_HELPER2(count) + #define LOOP_HELPER(count) LOOP_HELPER1(count) + #define LOOP_GLUE(x, y) x y + #define LOOP(MACRO, ...) LOOP_GLUE(LOOP_HELPER(LOOP_COUNT_ARGS(__VA_ARGS__)), (MACRO, __VA_ARGS__)) #else -#define LOOP(MACRO, ...) LOOP_N(__VA_ARGS__, \ - LOOP_3, LOOP_2, LOOP_1 \ - ) (MACRO, __VA_ARGS__) + #define LOOP(MACRO, ...) LOOP_N(__VA_ARGS__, \ + LOOP_3, \ + LOOP_2, \ + LOOP_1)(MACRO, __VA_ARGS__) #endif diff --git a/libopenage/util/math.h b/libopenage/util/math.h index f31b650a1c..afbdc81b8a 100644 --- a/libopenage/util/math.h +++ b/libopenage/util/math.h @@ -1,4 +1,4 @@ -// Copyright 2015-2018 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once @@ -7,15 +7,16 @@ namespace openage { namespace math { -template +template T square(T arg) { return arg * arg; } -template +template T hypot3(T x, T y, T z) { return sqrt(x * x + y * y + z * z); } -}} // openage::util +} // namespace math +} // namespace openage diff --git a/libopenage/util/math_constants.h b/libopenage/util/math_constants.h index fe5172549e..97a402b49c 100644 --- a/libopenage/util/math_constants.h +++ b/libopenage/util/math_constants.h @@ -1,14 +1,16 @@ -// 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 -#include #include +#include namespace openage { namespace math { // TODO: use std::numbers instead of these where appropriate +// clang-format off +// keep equal signs aligned for readability constexpr double E = std::numbers::e; //!< e constexpr double LOG2E = std::numbers::log2e; //!< log_2 e constexpr double LOG10E = std::numbers::log10e; //!< log_10 e @@ -25,9 +27,11 @@ constexpr double DEGSPERRAD = 0.017453292519943295769; //!< tau/360 constexpr double RADSPERDEG = 57.29577951308232087679; //!< 360/tau constexpr double SQRT_2 = std::numbers::sqrt2; //!< sqrt(2) constexpr double INV_SQRT_2 = 0.707106781186547524401; //!< 1/sqrt(2) +// clang-format on constexpr unsigned int UINT_INF = std::numeric_limits::max(); constexpr int INT_INF = std::numeric_limits::max(); constexpr double DOUBLE_INF = std::numeric_limits::max(); -}} // openage::math +} // namespace math +} // namespace openage diff --git a/libopenage/util/misc.h b/libopenage/util/misc.h index 9ea6591567..9fdfcc9ef9 100644 --- a/libopenage/util/misc.h +++ b/libopenage/util/misc.h @@ -1,14 +1,14 @@ -// Copyright 2013-2023 the openage authors. See copying.md for legal info. +// Copyright 2013-2024 the openage authors. See copying.md for legal info. #pragma once #include -#include #include #include +#include +#include #include #include -#include #include "../error/error.h" #include "compiler.h" @@ -28,13 +28,13 @@ extern std::string empty_string; * modulo operation that guarantees to return positive values. */ template -constexpr -T mod(T x, T m) { +constexpr T mod(T x, T m) { T r = x % m; if (r < 0) { return r + m; - } else { + } + else { return r; } } @@ -43,13 +43,13 @@ T mod(T x, T m) { * compiletime defined modulo function. */ template -constexpr -T mod(T x) { +constexpr T mod(T x) { T r = x % modulo; if (r < 0) { return r + modulo; - } else { + } + else { return r; } } @@ -59,10 +59,9 @@ T mod(T x) { * compiletime defined rotate left function */ template -constexpr -T rol(T x) { - static_assert(sizeof(T)*CHAR_BIT > amount && amount > 0, "invalid rotation amount"); - return (x << amount) | (x >> (sizeof(T)*CHAR_BIT - amount)); +constexpr T rol(T x) { + static_assert(sizeof(T) * CHAR_BIT > amount && amount > 0, "invalid rotation amount"); + return (x << amount) | (x >> (sizeof(T) * CHAR_BIT - amount)); } @@ -71,8 +70,7 @@ T rol(T x) { * which always rounds to -inf */ template -constexpr -inline T div(T x, T m) { +constexpr inline T div(T x, T m) { return (x - mod(x, m)) / m; } @@ -84,7 +82,7 @@ inline T div(T x, T m) { */ template struct less { - bool operator ()(const T x, const T y) const { + bool operator()(const T x, const T y) const { return *x < *y; } }; @@ -108,7 +106,7 @@ static constexpr size_t uint64_s = 8; * @return Input data as a 64 bit number. */ inline uint64_t -array8_to_uint64(const uint8_t *start, size_t count, bool big_endian=false) { +array8_to_uint64(const uint8_t *start, size_t count, bool big_endian = false) { if (count > uint64_s) { throw Error(MSG(err) << "Tried to copy more than " << uint64_s << " bytes"); } @@ -142,7 +140,7 @@ array8_to_uint64(const uint8_t *start, size_t count, bool big_endian=false) { * @return Input data as a 8 bit number array. */ inline std::vector -uint64_to_array8(const uint64_t value, bool big_endian=false) { +uint64_to_array8(const uint64_t value, bool big_endian = false) { std::vector result(uint64_s, 0); if (big_endian) { @@ -187,7 +185,7 @@ inline constexpr size_t array64_size(size_t count) { * @return Input data as a 64 bit number vector. */ inline std::vector -array8_to_array64(const uint8_t *start, size_t count, bool big_endian=false) { +array8_to_array64(const uint8_t *start, size_t count, bool big_endian = false) { size_t size{array64_size(count)}; std::vector result(size, 0); @@ -197,8 +195,7 @@ array8_to_array64(const uint8_t *start, size_t count, bool big_endian=false) { result[i] = array8_to_uint64( start + (i * uint64_s), std::min(rem_bytes, uint64_s), - big_endian - ); + big_endian); } return result; } @@ -219,7 +216,7 @@ array8_to_array64(const uint8_t *start, size_t count, bool big_endian=false) { * @return Input data as a 8 bit number vector. */ inline std::vector -array64_to_array8(const uint64_t *start, size_t count, bool big_endian=false) { +array64_to_array8(const uint64_t *start, size_t count, bool big_endian = false) { std::vector result; result.reserve(count * uint64_s); @@ -274,7 +271,8 @@ void vector_remove_swap_end(std::vector &vec, size_t idx) { else if (idx < vec.size()) { std::swap(vec[idx], vec.back()); vec.pop_back(); - } else { + } + else { return; } } @@ -287,9 +285,8 @@ void vector_remove_swap_end(std::vector &vec, size_t idx) { */ template struct SharedPtrLess { - bool operator ()(const std::shared_ptr &left, - const std::shared_ptr &right) { - + bool operator()(const std::shared_ptr &left, + const std::shared_ptr &right) { if (not left or not right) [[unlikely]] { return false; } diff --git a/libopenage/util/os.cpp b/libopenage/util/os.cpp index c3bbea2da6..ccc68d86c6 100644 --- a/libopenage/util/os.cpp +++ b/libopenage/util/os.cpp @@ -1,4 +1,4 @@ -// Copyright 2014-2019 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. #include "os.h" @@ -7,16 +7,16 @@ #ifdef _WIN32 // TODO not yet implemented #else -#include + #include #endif #ifdef __APPLE__ -#include + #include #endif #ifdef __FreeBSD__ -#include -#include + #include + #include #endif #include "../log/log.h" @@ -77,8 +77,7 @@ std::string self_exec_filename() { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, - -1 - }; + -1}; while (true) { std::unique_ptr buf{new char[bufsize]}; @@ -103,23 +102,23 @@ int execute_file(const char *path, bool background) { // TODO some sort of shell-open, not yet implemented return -1; // failure #else - std::string runner = ""; -#ifdef __APPLE__ - runner = subprocess::which("open"); -#else - runner = subprocess::which("xdg-open"); - // fallback - if (runner.size() == 0) { - runner = subprocess::which("gnome-open"); - } -#endif - if (runner.size() == 0) { - log::log(MSG(err) << "could not locate file-opener"); - return -1; - } + std::string runner = ""; + #ifdef __APPLE__ + runner = subprocess::which("open"); + #else + runner = subprocess::which("xdg-open"); + // fallback + if (runner.size() == 0) { + runner = subprocess::which("gnome-open"); + } + #endif + if (runner.size() == 0) { + log::log(MSG(err) << "could not locate file-opener"); + return -1; + } - return subprocess::call({runner.c_str(), path, nullptr}, not background); + return subprocess::call({runner.c_str(), path, nullptr}, not background); #endif } -} // namespace openage +} // namespace openage::os diff --git a/libopenage/util/os.h b/libopenage/util/os.h index 305fdd1de4..4573c5e10d 100644 --- a/libopenage/util/os.h +++ b/libopenage/util/os.h @@ -1,4 +1,4 @@ -// Copyright 2014-2016 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. #pragma once @@ -24,7 +24,7 @@ std::string self_exec_filename(); /** * tries to xdg-open the file */ -int execute_file(const char *path, bool background=true); +int execute_file(const char *path, bool background = true); } // namespace os } // namespace openage diff --git a/libopenage/util/path.cpp b/libopenage/util/path.cpp index ff23253533..e521a4ed9e 100644 --- a/libopenage/util/path.cpp +++ b/libopenage/util/path.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 "path.h" diff --git a/libopenage/util/path.h b/libopenage/util/path.h index d16e1734c6..5d4e079812 100644 --- a/libopenage/util/path.h +++ b/libopenage/util/path.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 diff --git a/libopenage/util/profiler.cpp b/libopenage/util/profiler.cpp deleted file mode 100644 index 9b0917edd4..0000000000 --- a/libopenage/util/profiler.cpp +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#include "profiler.h" -#include "../legacy_engine.h" -#include "../renderer/color.h" -#include "misc.h" - -#include -#include -#include -#include - -namespace openage::util { - -Profiler::Profiler(LegacyEngine *engine) : - engine{engine} {} - - -Profiler::~Profiler() { - this->unregister_all(); -} - -void Profiler::register_component(const std::string &com, color component_color) { - if (this->registered(com)) { - return; - } - - component_time_data cdt; - cdt.display_name = com; - cdt.drawing_color = component_color; - - for (auto &val : cdt.history) { - val = 0; - } - - this->components[com] = cdt; -} - -void Profiler::unregister_component(const std::string &com) { - if (not this->registered(com)) { - return; - } - - this->components.erase(com); -} - -void Profiler::unregister_all() { - std::vector registered_components = this->registered_components(); - - for (auto com : registered_components) { - this->unregister_component(com); - } -} - -std::vector Profiler::registered_components() { - std::vector registered_components; - for (auto &pair : this->components) { - registered_components.push_back(pair.first); - } - - return registered_components; -} - -void Profiler::start_measure(const std::string &com, color component_color) { - if (not this->engine_in_debug_mode()) { - return; - } - - if (not this->registered(com)) { - this->register_component(com, component_color); - } - - this->components[com].start = std::chrono::high_resolution_clock::now(); -} - -void Profiler::end_measure(const std::string &com) { - if (not this->engine_in_debug_mode()) { - return; - } - - if (this->registered(com)) { - std::chrono::high_resolution_clock::time_point end = std::chrono::high_resolution_clock::now(); - this->components[com].duration = end - this->components[com].start; - } -} - -void Profiler::draw_component_performance(const std::string &com) { - color rgb = this->components[com].drawing_color; - glColor4f(rgb.r, rgb.g, rgb.b, 1.0); - - glLineWidth(1.0); - glBegin(GL_LINE_STRIP); - float x_offset = 0.0; - float offset_factor = static_cast(PROFILER_CANVAS_WIDTH) / static_cast(MAX_DURATION_HISTORY); - float percentage_factor = static_cast(PROFILER_CANVAS_HEIGHT) / 100.0; - - for (auto i = this->insert_pos; mod(i, MAX_DURATION_HISTORY) != mod(this->insert_pos - 1, MAX_DURATION_HISTORY); ++i) { - i = mod(i, MAX_DURATION_HISTORY); - - auto percentage = this->components[com].history.at(i); - glVertex3f(PROFILER_CANVAS_POSITION_X + x_offset, PROFILER_CANVAS_POSITION_Y + percentage * percentage_factor, 0.0); - x_offset += offset_factor; - } - glEnd(); - - // reset color - glColor4f(1.0, 1.0, 1.0, 1.0); -} - -void Profiler::show(bool debug_mode) { - if (debug_mode) { - this->show(); - } -} - -void Profiler::show() { - this->draw_canvas(); - this->draw_legend(); - - for (auto com : this->components) { - this->draw_component_performance(com.first); - } -} - -bool Profiler::registered(const std::string &com) const { - return this->components.find(com) != this->components.end(); -} - -unsigned Profiler::size() const { - return this->components.size(); -} - -void Profiler::start_frame_measure() { - if (this->engine_in_debug_mode()) { - this->frame_start = std::chrono::high_resolution_clock::now(); - } -} - -void Profiler::end_frame_measure() { - if (not this->engine_in_debug_mode()) { - return; - } - - auto frame_end = std::chrono::high_resolution_clock::now(); - this->frame_duration = frame_end - this->frame_start; - - for (auto com : this->registered_components()) { - double percentage = this->duration_to_percentage(this->components[com].duration); - this->append_to_history(com, percentage); - } - - this->insert_pos++; -} - -void Profiler::draw_canvas() { - glColor4f(0.2, 0.2, 0.2, PROFILER_CANVAS_ALPHA); - glRecti(PROFILER_CANVAS_POSITION_X, - PROFILER_CANVAS_POSITION_Y, - PROFILER_CANVAS_POSITION_X + PROFILER_CANVAS_WIDTH, - PROFILER_CANVAS_POSITION_Y + PROFILER_CANVAS_HEIGHT); -} - -void Profiler::draw_legend() { - int offset = 0; - for (auto com : this->components) { - glColor4f(com.second.drawing_color.r, com.second.drawing_color.g, com.second.drawing_color.b, 1.0); - int box_x = PROFILER_CANVAS_POSITION_X + 2; - int box_y = PROFILER_CANVAS_POSITION_Y - PROFILER_COM_BOX_HEIGHT - 2 - offset; - glRecti(box_x, box_y, box_x + PROFILER_COM_BOX_WIDTH, box_y + PROFILER_COM_BOX_HEIGHT); - - glColor4f(0.2, 0.2, 0.2, 1); - coord::viewport position = coord::viewport{box_x + PROFILER_COM_BOX_WIDTH + 2, box_y + 2}; - // this->display->render_text(position, 12, renderer::Colors::WHITE, "%s", com.second.display_name.c_str()); - - offset += PROFILER_COM_BOX_HEIGHT + 2; - } -} - -double Profiler::duration_to_percentage(std::chrono::high_resolution_clock::duration duration) { - double dur = std::chrono::duration_cast(duration).count(); - double ref = std::chrono::duration_cast(this->frame_duration).count(); - double percentage = dur / ref * 100; - return percentage; -} - -void Profiler::append_to_history(const std::string &com, double percentage) { - if (this->insert_pos == MAX_DURATION_HISTORY) { - this->insert_pos = 0; - } - this->components[com].history[this->insert_pos] = percentage; -} - -bool Profiler::engine_in_debug_mode() { - // if (this->display->drawing_debug_overlay.value) { - // return true; - // } - // else { - // return false; - // } - return true; -} - -} // namespace openage::util diff --git a/libopenage/util/profiler.h b/libopenage/util/profiler.h deleted file mode 100644 index 24d3946394..0000000000 --- a/libopenage/util/profiler.h +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. - -#pragma once - -#include -#include -#include -#include -#include - -constexpr int MAX_DURATION_HISTORY = 100; -constexpr int PROFILER_CANVAS_WIDTH = 250; -constexpr int PROFILER_CANVAS_HEIGHT = 120; -constexpr int PROFILER_CANVAS_POSITION_X = 0; -constexpr int PROFILER_CANVAS_POSITION_Y = 300; -constexpr float PROFILER_CANVAS_ALPHA = 0.6f; -constexpr int PROFILER_COM_BOX_WIDTH = 30; -constexpr int PROFILER_COM_BOX_HEIGHT = 15; - -namespace openage { - -class LegacyEngine; - - -namespace util { - -struct color { - float r, g, b; -}; - -struct component_time_data { - std::string display_name; - color drawing_color; - std::chrono::high_resolution_clock::time_point start; - std::chrono::high_resolution_clock::duration duration; - std::array history; -}; - -class Profiler { -public: - Profiler(LegacyEngine *engine); - ~Profiler(); - - /** - * registers a component - * @param com the identifier to distinguish the components - * @param component_color color of the plotted line - */ - void register_component(const std::string &com, color component_color); - - /** - * unregisters an individual component - * @param com component name which should be unregistered - */ - void unregister_component(const std::string &com); - - /** - * unregisters all remaining components - */ - void unregister_all(); - - /** - *returns a vector of registered component names - */ - std::vector registered_components(); - - /* - * starts a measurement for the component com. If com is not yet - * registered, its getting registered and the profiler uses the color - * information given by component_color. The default value is white. - */ - void start_measure(const std::string &com, color component_color = {1.0, 1.0, 1.0}); - - /* - * stops the measurement for the component com. If com is not yet - * registered it does nothing. - */ - void end_measure(const std::string &com); - - /* - * draws the profiler gui if debug_mode is set - */ - void show(bool debug_mode); - - /* - * draws the profiler gui - */ - void show(); - - /* - * true if the component com is already registered, otherwise false - */ - bool registered(const std::string &com) const; - - /* - * returns the number of registered components - */ - unsigned size() const; - - /** - * sets the start point for the actual frame which is used as a reference - * value for the registered components - */ - void start_frame_measure(); - - /** - * sets the end point for the reference time used to compute the portions - * of the components. Each recorded measurement for the registered components - * get appended to their history complete the measurement. - */ - void end_frame_measure(); - -private: - void draw_canvas(); - void draw_legend(); - void draw_component_performance(const std::string &com); - double duration_to_percentage(std::chrono::high_resolution_clock::duration duration); - void append_to_history(const std::string &com, double percentage); - bool engine_in_debug_mode(); - - std::chrono::high_resolution_clock::time_point frame_start; - std::chrono::high_resolution_clock::duration frame_duration; - std::unordered_map components; - int insert_pos = 0; - - LegacyEngine *engine; -}; - -} // namespace util -} // namespace openage diff --git a/libopenage/util/pty.h b/libopenage/util/pty.h index cb071af71a..cb85f38865 100644 --- a/libopenage/util/pty.h +++ b/libopenage/util/pty.h @@ -1,16 +1,16 @@ -// Copyright 2014-2017 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. #pragma once #ifdef __APPLE__ -# include + #include #elif defined(__FreeBSD__) -# include -# include -# include -# include + #include + #include + #include + #include #elif _WIN32 // TODO not yet implemented #else -# include + #include #endif diff --git a/libopenage/util/quaternion.h b/libopenage/util/quaternion.h index 671d2a2410..b0761028ad 100644 --- a/libopenage/util/quaternion.h +++ b/libopenage/util/quaternion.h @@ -1,11 +1,11 @@ -// Copyright 2017-2018 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once #include -#include "matrix.h" #include "math_constants.h" +#include "matrix.h" #include "vector.h" #include "../error/error.h" @@ -35,15 +35,13 @@ class Quaternion { static constexpr T default_eps = 1e-4; - Quaternion(T w, T x, T y, T z) - : + Quaternion(T w, T x, T y, T z) : w{w}, x{x}, y{y}, z{z} {} /** * Create a identity quaternion. */ - Quaternion() - : + Quaternion() : w{1}, x{0}, y{0}, z{0} {} /** @@ -62,9 +60,8 @@ class Quaternion { * max diagonal entry <=> max (|x|, |y|, |z|) * which is larger than |w| and >= 1/2 */ - template + template Quaternion(const Matrix &mat) { - static_assert(N == 3 or N == 4, "only 3 and 4 dimensional matrices can be converted to a quaternion!"); T trace = mat.trace(); @@ -72,7 +69,8 @@ class Quaternion { if (N == 4) { trace_cmp -= mat[3][3]; - } else { + } + else { trace += 1.0; } @@ -111,14 +109,15 @@ class Quaternion { if (N == 4) { trace_ordered += mat[3][3]; - } else { + } + else { trace_ordered += 1.0; } T trace_root = std::sqrt(trace_ordered); - *ptrs[n0] = trace_root * 0.5; // = w - trace_root = 0.5 / trace_root; // = 1/4w + *ptrs[n0] = trace_root * 0.5; // = w + trace_root = 0.5 / trace_root; // = 1/4w *ptrs[n1] = (mat[n0][n1] + mat[n1][n0]) * trace_root; *ptrs[n2] = (mat[n2][n0] + mat[n0][n2]) * trace_root; @@ -134,8 +133,8 @@ class Quaternion { Quaternion(const this_type &other) = default; Quaternion(this_type &&other) = default; - Quaternion &operator =(const this_type &other) = default; - Quaternion &operator =(this_type &&other) = default; + Quaternion &operator=(const this_type &other) = default; + Quaternion &operator=(this_type &&other) = default; virtual ~Quaternion() = default; @@ -148,8 +147,7 @@ class Quaternion { std::cos(rot), axis[0] * std::sin(rot), axis[1] * std::sin(rot), - axis[2] * std::sin(rot) - }; + axis[2] * std::sin(rot)}; return q; } @@ -240,15 +238,15 @@ class Quaternion { /** * Test if the rotation of both quaternions is the same. */ - bool equals(const this_type &other, T eps=default_eps) const { + bool equals(const this_type &other, T eps = default_eps) const { T ori = this->dot(other); - return (1 - (ori*ori)) < eps; + return (1 - (ori * ori)) < eps; } /** * Test if both quaternion store the same numbers. */ - bool equals_number(const this_type &other, T eps=default_eps) const { + bool equals_number(const this_type &other, T eps = default_eps) const { bool result = true; (this->w - other.w) < eps or (result = false); (this->x - other.x) < eps or (result = false); @@ -261,7 +259,7 @@ class Quaternion { * Test rotation equality with another quaternion * with given precision in radians. */ - bool equals_rad(const this_type &other, T rad_eps=default_eps) const { + bool equals_rad(const this_type &other, T rad_eps = default_eps) const { T ori = this->dot(other); T angle = std::acos((2.0 * (ori * ori)) - 1.0); @@ -272,7 +270,7 @@ class Quaternion { * Test rotation equality with another quaternion * with given precision in degree. */ - bool equals_deg(const this_type &other, T deg_eps=default_eps) const { + bool equals_deg(const this_type &other, T deg_eps = default_eps) const { return this->equals_rad(other, deg_eps * math::RADSPERDEG); } @@ -280,27 +278,29 @@ class Quaternion { * Generate the corresponding rotation matrix. */ Matrix3t to_matrix() const { - T x2 = this->x * 2; - T y2 = this->y * 2; - T z2 = this->z * 2; + T x2 = this->x * 2; + T y2 = this->y * 2; + T z2 = this->z * 2; T x2w = x2 * this->w; T y2w = y2 * this->w; T z2w = z2 * this->w; - T x3 = x2 * this->x; + T x3 = x2 * this->x; T y2x = y2 * this->x; T z2x = z2 * this->x; - T y3 = y2 * this->y; + T y3 = y2 * this->y; T z2y = z2 * this->y; - T z3 = z2 * this->z; + T z3 = z2 * this->z; + // clang-format off Matrix3t m{ 1.0 - (y3 + z3), y2x - z2w, z2x + y2w, y2x + z2w, 1.0 - (x3 + z3), z2y - x2w, z2x - y2w, z2y + x2w, 1.0 - (x3 + y3) }; + // clang-format on return m; } @@ -308,7 +308,7 @@ class Quaternion { /** * Transforms a vector by this quaternion. */ - Vector3t operator *(const Vector3t &vec) const { + Vector3t operator*(const Vector3t &vec) const { Vector3t axis{this->x, this->y, this->z}; Vector3t axis_vec_normal = axis.cross_product(vec); @@ -320,7 +320,7 @@ class Quaternion { return vec + axis_vec_normal + axis_vec_inplane; } - const this_type &operator +=(const this_type &other) { + const this_type &operator+=(const this_type &other) { this->w += other.w; this->x += other.x; this->y += other.y; @@ -329,13 +329,13 @@ class Quaternion { return *this; } - this_type operator +(const this_type &other) const { + this_type operator+(const this_type &other) const { this_type q{*this}; q += other; return q; } - const this_type &operator -=(const this_type &other) { + const this_type &operator-=(const this_type &other) { this->w -= other.w; this->x -= other.x; this->y -= other.y; @@ -344,13 +344,13 @@ class Quaternion { return *this; } - this_type operator -(const this_type &other) const { + this_type operator-(const this_type &other) const { this_type q{*this}; q -= other; return q; } - const this_type &operator *=(const T &fac) { + const this_type &operator*=(const T &fac) { this->w *= fac; this->x *= fac; this->y *= fac; @@ -359,21 +359,21 @@ class Quaternion { return *this; } - this_type operator *(const T &fac) const { + this_type operator*(const T &fac) const { this_type q{*this}; q *= fac; return q; } - const this_type &operator *=(const this_type &other) { - T w_new = (this->w * other.w - this->x * other.x - - this->y * other.y - this->z * other.z); - T x_new = (this->w * other.x + this->x * other.w + - this->y * other.z - this->z * other.y); - T y_new = (this->w * other.y - this->x * other.z + - this->y * other.w + this->z * other.x); - T z_new = (this->w * other.z + this->x * other.y - - this->y * other.x + this->z * other.w); + const this_type &operator*=(const this_type &other) { + T w_new = (this->w * other.w - this->x * other.x + - this->y * other.y - this->z * other.z); + T x_new = (this->w * other.x + this->x * other.w + + this->y * other.z - this->z * other.y); + T y_new = (this->w * other.y - this->x * other.z + + this->y * other.w + this->z * other.x); + T z_new = (this->w * other.z + this->x * other.y + - this->y * other.x + this->z * other.w); this->w = w_new; this->x = x_new; @@ -383,13 +383,13 @@ class Quaternion { return *this; } - this_type operator *(const this_type &other) const { + this_type operator*(const this_type &other) const { this_type q{*this}; q *= other; return q; } - const this_type &operator /=(const T &fac) { + const this_type &operator/=(const T &fac) { this->w /= fac; this->x /= fac; this->y /= fac; @@ -398,28 +398,25 @@ class Quaternion { return *this; } - this_type operator /(const T &fac) const { + this_type operator/(const T &fac) const { this_type q{*this}; q /= fac; return q; } - const this_type operator -() const { + const this_type operator-() const { return this_type{-this->w, -this->x, -this->y, -this->z}; } - bool operator ==(const this_type &other) const { - return ((this->w == other.w) and - (this->x == other.x) and - (this->y == other.y) and - (this->z == other.z)); + bool operator==(const this_type &other) const { + return ((this->w == other.w) and (this->x == other.x) and (this->y == other.y) and (this->z == other.z)); } - bool operator !=(const this_type &other) const { - return not (*this == other); + bool operator!=(const this_type &other) const { + return not(*this == other); } - friend std::ostream &operator <<(std::ostream &o, const this_type &q) { + friend std::ostream &operator<<(std::ostream &o, const this_type &q) { o << "Quaternion(" << q.w << ", " << q.x; o << ", " << q.y << ", " << q.z << ")"; return o; @@ -435,4 +432,5 @@ class Quaternion { using Quaternionf = Quaternion; using Quaterniond = Quaternion; -}} // openage::util +} // namespace util +} // namespace openage diff --git a/libopenage/util/signal.h b/libopenage/util/signal.h index 7e589462ac..b84b2deec8 100644 --- a/libopenage/util/signal.h +++ b/libopenage/util/signal.h @@ -1,4 +1,4 @@ -// Copyright 2014-2016 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. #pragma once @@ -6,8 +6,8 @@ // TODO: change these to ifndef __linux ? #ifdef __APPLE__ - typedef void (*sighandler_t)(int); +typedef void (*sighandler_t)(int); #endif #ifdef _WIN32 - typedef void (*sighandler_t)(int); +typedef void (*sighandler_t)(int); #endif diff --git a/libopenage/util/stringformatter.h b/libopenage/util/stringformatter.h index d87142328f..6426efa35b 100644 --- a/libopenage/util/stringformatter.h +++ b/libopenage/util/stringformatter.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 @@ -88,15 +88,14 @@ class CachableOSStream { * As an optimization, instead of creating a new ExternalOStringStream object, * CachableOSStream.acquire() is used internally. */ -template +template class StringFormatter { public: /** * @param buffer * All input data is appended to this object. */ - StringFormatter(std::string &output) - : + StringFormatter(std::string &output) : output{&output}, stream_ptr{nullptr} {} @@ -115,11 +114,10 @@ class StringFormatter { : output{other.output}, stream_ptr{other.stream_ptr} { - other.stream_ptr = nullptr; } - StringFormatter &operator =(StringFormatter &&other) noexcept { + StringFormatter &operator=(StringFormatter &&other) noexcept { this->output = other.output; this->stream_ptr = other.stream_ptr; @@ -129,11 +127,11 @@ class StringFormatter { // no copy construction! StringFormatter(const StringFormatter &) = delete; - StringFormatter &operator =(const StringFormatter &) = delete; + StringFormatter &operator=(const StringFormatter &) = delete; // These methods allow usage like an ostream object. - template - ChildType &operator <<(const T &t) { + template + ChildType &operator<<(const T &t) { if (this->should_format()) { this->ensure_stream_obj(); this->stream_ptr->stream << t; @@ -142,7 +140,7 @@ class StringFormatter { } - ChildType &operator <<(std::ios &(*x)(std::ios &)) { + ChildType &operator<<(std::ios &(*x)(std::ios &)) { if (this->should_format()) { this->ensure_stream_obj(); this->stream_ptr->stream << x; @@ -151,7 +149,7 @@ class StringFormatter { } - ChildType &operator <<(std::ostream &(*x)(std::ostream &)) { + ChildType &operator<<(std::ostream &(*x)(std::ostream &)) { if (this->should_format()) { this->ensure_stream_obj(); this->stream_ptr->stream << x; @@ -162,7 +160,7 @@ class StringFormatter { // Optimizations to prevent needless stream-acquiring if just a simple // string is printed. - ChildType &operator <<(const char *s) { + ChildType &operator<<(const char *s) { if (this->should_format()) { this->output->append(s); } @@ -170,7 +168,7 @@ class StringFormatter { } - ChildType &operator <<(const std::string &s) { + ChildType &operator<<(const std::string &s) { if (this->should_format()) { this->output->append(s); } @@ -192,8 +190,8 @@ class StringFormatter { // Allow direct inputting of stuff that's wrapped in the C++11 pointer types. - template - ChildType &operator <<(const std::unique_ptr &ptr) { + template + ChildType &operator<<(const std::unique_ptr &ptr) { if (this->should_format()) { *this << ptr.get(); } @@ -201,8 +199,8 @@ class StringFormatter { } - template - ChildType &operator <<(const std::shared_ptr &ptr) { + template + ChildType &operator<<(const std::shared_ptr &ptr) { if (this->should_format()) { *this << ptr.get(); } @@ -221,8 +219,8 @@ class StringFormatter { } /** - * Returns if formatting should actually occur. - */ + * Returns if formatting should actually occur. + */ virtual bool should_format() const { return true; } @@ -269,14 +267,12 @@ class Formatter : public StringFormatter {}; */ class FString : public StringFormatter { public: - FString() - : + FString() : StringFormatter{this->buffer} {} // allow assignment and construction from std::string. - FString(const std::string &other) - : + FString(const std::string &other) : StringFormatter{this->buffer}, buffer{other} {} @@ -285,12 +281,12 @@ class FString : public StringFormatter { StringFormatter{this->buffer}, buffer{std::move(other)} {} - FString &operator =(const std::string &other) { + FString &operator=(const std::string &other) { this->buffer = other; return *this; } - FString &operator =(std::string &&other) noexcept { + FString &operator=(std::string &&other) noexcept { this->buffer = std::move(other); return *this; } @@ -305,7 +301,7 @@ class FString : public StringFormatter { return this->buffer; } - operator std::string () && { + operator std::string() && { return std::move(this->buffer); } @@ -319,4 +315,5 @@ class FString : public StringFormatter { }; -}} // namespace openage::util +} // namespace util +} // namespace openage diff --git a/libopenage/util/strings.cpp b/libopenage/util/strings.cpp index 4a9ce1436e..9243992db5 100644 --- a/libopenage/util/strings.cpp +++ b/libopenage/util/strings.cpp @@ -1,17 +1,17 @@ -// Copyright 2013-2023 the openage authors. See copying.md for legal info. +// Copyright 2013-2024 the openage authors. See copying.md for legal info. #include "strings.h" -#include #include +#include #include #include #include #include -#include "config.h" #include "../error/error.h" #include "compiler.h" +#include "config.h" namespace openage::util { @@ -72,12 +72,10 @@ size_t rstrip(char *s) { size_t strippedlen = strlen(s); while (strippedlen > 0) { - if (s[strippedlen - 1] == '\n' || - s[strippedlen - 1] == ' ' || - s[strippedlen - 1] == '\t') { - + if (s[strippedlen - 1] == '\n' || s[strippedlen - 1] == ' ' || s[strippedlen - 1] == '\t') { strippedlen -= 1; - } else { + } + else { break; } } @@ -141,7 +139,6 @@ std::vector split(const std::string &txt, char delimiter) { std::vector split_escape(const std::string &txt, char delim, size_t size_hint) { - // output vector std::vector items; if (size_hint) [[likely]] { @@ -161,7 +158,6 @@ std::vector split_escape(const std::string &txt, char delim, size_t // copy characters to buf, and a buf is emitted as a token // when the delimiter or end is reached. while (true) { - // end of input string if (*r == '\0') { items.emplace_back(std::begin(buf), std::end(buf)); @@ -210,4 +206,17 @@ std::vector split_escape(const std::string &txt, char delim, size_t return items; } -} // openage::util +std::vector split_newline(const std::string &txt) { + auto lines = split(txt, '\n'); + + // remove the '\r' from the end of each line + for (auto &line : lines) { + if (not line.empty() and line.back() == '\r') { + line.pop_back(); + } + } + + return lines; +} + +} // namespace openage::util diff --git a/libopenage/util/strings.h b/libopenage/util/strings.h index 029cf57773..69f2759fb4 100644 --- a/libopenage/util/strings.h +++ b/libopenage/util/strings.h @@ -1,4 +1,4 @@ -// Copyright 2013-2018 the openage authors. See copying.md for legal info. +// Copyright 2013-2024 the openage authors. See copying.md for legal info. #pragma once @@ -11,9 +11,9 @@ #include #if defined(__GNUC__) -#define ATTRIBUTE_FORMAT(i, j) __attribute__ ((format (printf, i, j))) + #define ATTRIBUTE_FORMAT(i, j) __attribute__((format(printf, i, j))) #else -#define ATTRIBUTE_FORMAT(i, j) + #define ATTRIBUTE_FORMAT(i, j) #endif namespace openage { @@ -24,14 +24,14 @@ namespace util { * Quick-formatter for floats when working with string streams. * Usage: cout << FormatFloat{1.0, 10}; */ -template +template struct FloatFixed { float value; }; -template -std::ostream &operator <<(std::ostream &os, FloatFixed f) { +template +std::ostream &operator<<(std::ostream &os, FloatFixed f) { static_assert(decimals < 50, "Refusing to print float with >= 50 decimals"); static_assert(w < 70, "Refusing to print float with a width >= 70"); @@ -84,7 +84,7 @@ bool string_matches_pattern(const char *str, const char *pattern); * Split a string at a delimiter, push the result back in an iterator. * Why doesn't the fucking standard library have std::string::split(delimiter)? */ -template +template void split(const std::string &txt, char delimiter, ret_t result) { std::stringstream splitter; splitter.str(txt); @@ -113,6 +113,13 @@ std::vector split(const std::string &txt, char delim); * to literal X, including the deliminiter. */ std::vector split_escape(const std::string &txt, - char delim, size_t size_hint=0); + char delim, + size_t size_hint = 0); -}} // openage::util +/** + * Newline splitter that works with both \n and \r\n. + */ +std::vector split_newline(const std::string &txt); + +} // namespace util +} // namespace openage diff --git a/libopenage/util/subprocess.cpp b/libopenage/util/subprocess.cpp index 98ae6720eb..c9a7670159 100644 --- a/libopenage/util/subprocess.cpp +++ b/libopenage/util/subprocess.cpp @@ -1,19 +1,19 @@ -// Copyright 2014-2019 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. #include "subprocess.h" +#include #include #include -#include #ifdef _WIN32 // TODO not yet implemented #else -#include -#include -#include -#include -#include + #include + #include + #include + #include + #include #endif #include "../log/log.h" @@ -90,7 +90,7 @@ int call(const std::vector &argv, bool wait, const char *redirect_ if (redirect_stdout_to != nullptr) { replacement_stdout_fd = open( redirect_stdout_to, - O_WRONLY | O_CREAT|O_TRUNC|O_CLOEXEC, + O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644); if (replacement_stdout_fd < 0) { @@ -215,8 +215,8 @@ int call(const std::vector &argv, bool wait, const char *redirect_ } if (child_errno > 0) { - log::log(MSG(err) << "execv has failed: " << strerror(child_errno)); - return -1; + log::log(MSG(err) << "execv has failed: " << strerror(child_errno)); + return -1; } } @@ -235,7 +235,7 @@ int call(const std::vector &argv, bool wait, const char *redirect_ // everything went well. return status; - #endif +#endif } } // namespace openage::subprocess diff --git a/libopenage/util/subprocess.h b/libopenage/util/subprocess.h index 1588fd7d11..ef2fb881ae 100644 --- a/libopenage/util/subprocess.h +++ b/libopenage/util/subprocess.h @@ -1,4 +1,4 @@ -// Copyright 2014-2017 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. #pragma once @@ -40,8 +40,8 @@ std::string which(const char *name); * the existing file is overwritten. */ int call(const std::vector &argv, - bool wait=false, - const char *redirect_stdout_to=nullptr); + bool wait = false, + const char *redirect_stdout_to = nullptr); } // namespace subprocess } // namespace openage diff --git a/libopenage/util/thread_id.cpp b/libopenage/util/thread_id.cpp index 0bcfc0abc9..3a178c441d 100644 --- a/libopenage/util/thread_id.cpp +++ b/libopenage/util/thread_id.cpp @@ -1,13 +1,13 @@ -// Copyright 2015-2018 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #include "thread_id.h" #include "config.h" #if HAVE_THREAD_LOCAL_STORAGE -#include + #include #else -#include + #include #endif namespace openage { @@ -23,9 +23,8 @@ namespace util { */ class ThreadIdSupplier { public: - ThreadIdSupplier() - : - val{ThreadIdSupplier::counting_id++} {} + ThreadIdSupplier() : + val{ThreadIdSupplier::counting_id++} {} const size_t val; @@ -42,12 +41,13 @@ std::atomic ThreadIdSupplier::counting_id{0}; #endif size_t get_current_thread_id() { - #if HAVE_THREAD_LOCAL_STORAGE +#if HAVE_THREAD_LOCAL_STORAGE static thread_local ThreadIdSupplier current_thread_id; return current_thread_id.val; - #else +#else return std::hash()(std::this_thread::get_id()); - #endif +#endif } -}} // namespace openage::util +} // namespace util +} // namespace openage diff --git a/libopenage/util/thread_id.h b/libopenage/util/thread_id.h index 20fa0e07ac..d84c966890 100644 --- a/libopenage/util/thread_id.h +++ b/libopenage/util/thread_id.h @@ -1,4 +1,4 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once @@ -15,4 +15,5 @@ namespace util { */ size_t get_current_thread_id(); -}} // namespace openage::util +} // namespace util +} // namespace openage diff --git a/libopenage/util/timer.h b/libopenage/util/timer.h index 4ce7610741..64d7ff58e3 100644 --- a/libopenage/util/timer.h +++ b/libopenage/util/timer.h @@ -1,4 +1,4 @@ -// Copyright 2013-2016 the openage authors. See copying.md for legal info. +// Copyright 2013-2024 the openage authors. See copying.md for legal info. #pragma once @@ -31,12 +31,12 @@ class Timer { /** * creates the timer, in either stopped or running state. */ - Timer(bool stopped=true); + Timer(bool stopped = true); /** * resets the timer, in either stopped or running state. */ - void reset(bool stopped=true); + void reset(bool stopped = true); /** * stops/pauses the timer. @@ -66,4 +66,5 @@ class Timer { }; -}} // openage::util +} // namespace util +} // namespace openage diff --git a/libopenage/util/timing.h b/libopenage/util/timing.h index 9bfaf30e4f..d67c3debe9 100644 --- a/libopenage/util/timing.h +++ b/libopenage/util/timing.h @@ -1,4 +1,4 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once @@ -27,4 +27,5 @@ time_nsec_t get_monotonic_time(); */ time_nsec_t get_real_time(); -}} // namespace openage::timing +} // namespace timing +} // namespace openage diff --git a/libopenage/util/unicode.h b/libopenage/util/unicode.h index 2576c9bcc9..094029f2d0 100644 --- a/libopenage/util/unicode.h +++ b/libopenage/util/unicode.h @@ -1,4 +1,4 @@ -// Copyright 2013-2016 the openage authors. See copying.md for legal info. +// Copyright 2013-2024 the openage authors. See copying.md for legal info. #pragma once @@ -107,4 +107,5 @@ size_t utf8_last_char_size(char *str); */ void utf8_pop_back(std::string &str); -}} // openage::util +} // namespace util +} // namespace openage diff --git a/libopenage/util/variable.h b/libopenage/util/variable.h index c7908cc606..0692dc727e 100644 --- a/libopenage/util/variable.h +++ b/libopenage/util/variable.h @@ -1,4 +1,4 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once @@ -15,23 +15,23 @@ class VariableBase { /** * sets the type and value */ - template void set(const V &value); + template + void set(const V &value); /** * returns the stored value * throws an exception if the template * does not match the set type */ - template const T &get() const; - + template + const T &get() const; }; -template +template class Variable : public VariableBase { public: - Variable(const T &initial_value) - : + Variable(const T &initial_value) : value(initial_value) {} @@ -50,15 +50,16 @@ class Variable : public VariableBase { }; -template +template const T &VariableBase::get() const { return dynamic_cast &>(*this).get(); } -template -void VariableBase::set(const V& value) { +template +void VariableBase::set(const V &value) { return dynamic_cast &>(*this).set(value); } -}} // openage::util +} // namespace util +} // namespace openage diff --git a/libopenage/util/vector.h b/libopenage/util/vector.h index 062ab8f693..e79283fb64 100644 --- a/libopenage/util/vector.h +++ b/libopenage/util/vector.h @@ -1,4 +1,4 @@ -// Copyright 2015-2019 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once @@ -20,8 +20,11 @@ namespace openage::util { * * N = dimensions * T = underlying single value type (double, float, ...) + * + * If you change this class, remember to update the gdb pretty printers + * in etc/gdb_pretty/printers.py. */ -template +template class Vector : public std::array { public: static_assert(N > 0, "0-dimensional vector not allowed"); @@ -45,18 +48,16 @@ class Vector : public std::array { /** * Constructor for initialisation with N T values */ - template - Vector(Ts ... args) - : + template + Vector(Ts... args) : std::array{static_cast(args)...} { - static_assert(sizeof...(args) == N, "not all values supplied."); } /** * Cast every value to NT and return the new Vector. */ - template + template Vector casted() const { Vector ret; std::copy(std::begin(*this), std::end(*this), std::begin(ret)); @@ -66,7 +67,7 @@ class Vector : public std::array { /** * Equality test with given precision. */ - bool equals(const this_type &other, T eps=default_eps) { + bool equals(const this_type &other, T eps = default_eps) { for (size_t i = 0; i < N; i++) { T diff = std::abs((*this)[i] - other[i]); if (diff >= eps) { @@ -79,7 +80,7 @@ class Vector : public std::array { /** * Vector addition with assignment */ - this_type &operator +=(const this_type &other) { + this_type &operator+=(const this_type &other) { for (size_t i = 0; i < N; i++) { (*this)[i] += other[i]; } @@ -89,7 +90,7 @@ class Vector : public std::array { /** * Vector addition */ - this_type operator +(const this_type &other) const { + this_type operator+(const this_type &other) const { this_type res(*this); res += other; return res; @@ -98,7 +99,7 @@ class Vector : public std::array { /** * Vector subtraction with assignment */ - this_type &operator -=(const this_type &other) { + this_type &operator-=(const this_type &other) { for (size_t i = 0; i < N; i++) { (*this)[i] -= other[i]; } @@ -108,7 +109,7 @@ class Vector : public std::array { /** * Vector subtraction */ - this_type operator -(const this_type &other) const { + this_type operator-(const this_type &other) const { this_type res(*this); res -= other; return res; @@ -117,7 +118,7 @@ class Vector : public std::array { /** * Scalar multiplication with assignment */ - this_type &operator *=(T a) { + this_type &operator*=(T a) { for (size_t i = 0; i < N; i++) { (*this)[i] *= a; } @@ -127,7 +128,7 @@ class Vector : public std::array { /** * Scalar multiplication */ - this_type operator *(T a) const { + this_type operator*(T a) const { this_type res(*this); res *= a; return res; @@ -136,7 +137,7 @@ class Vector : public std::array { /** * Scalar division with assignment */ - this_type &operator /=(T a) { + this_type &operator/=(T a) { for (size_t i = 0; i < N; i++) { (*this)[i] /= a; } @@ -146,7 +147,7 @@ class Vector : public std::array { /** * Scalar division */ - this_type operator /(T a) const { + this_type operator/(T a) const { this_type res(*this); res /= a; return res; @@ -181,50 +182,50 @@ class Vector : public std::array { /** * Cross-product of two 3-dimensional vectors */ - template - typename std::enable_if::type - /*Vector*/ cross_product(const this_type &other) const { + template + typename std::enable_if::type + /*Vector*/ + cross_product(const this_type &other) const { return this_type( ((*this)[1] * other[2] - (*this)[2] * other[1]), ((*this)[2] * other[0] - (*this)[0] * other[2]), - ((*this)[0] * other[1] - (*this)[1] * other[0]) - ); + ((*this)[0] * other[1] - (*this)[1] * other[0])); } /** * Scalar multiplication with swapped arguments */ - friend this_type operator *(T a, const this_type &v) { + friend this_type operator*(T a, const this_type &v) { return v * a; } /** * Print to output stream using '<<' */ - friend std::ostream &operator <<(std::ostream &o, const this_type &v) { + friend std::ostream &operator<<(std::ostream &o, const this_type &v) { o << "("; - for (size_t i = 0; i < N-1; i++) { + for (size_t i = 0; i < N - 1; i++) { o << v[i] << ", "; } - o << v[N-1] << ")"; + o << v[N - 1] << ")"; return o; } }; -template +template using Vector2t = Vector<2, T>; -template +template using Vector3t = Vector<3, T>; -template +template using Vector4t = Vector<4, T>; -template +template using Vectorf = Vector; -template +template using Vectord = Vector; using Vector2f = Vector<2, float>; @@ -247,4 +248,4 @@ using Vector2ss = Vector<2, ssize_t>; using Vector3ss = Vector<3, ssize_t>; using Vector4ss = Vector<4, ssize_t>; -} // openage::util +} // namespace openage::util diff --git a/libopenage/versions/versions.cpp b/libopenage/versions/versions.cpp index c94d898f33..29655b6db9 100644 --- a/libopenage/versions/versions.cpp +++ b/libopenage/versions/versions.cpp @@ -1,9 +1,9 @@ -// Copyright 2020-2020 the openage authors. See copying.md for legal info. +// Copyright 2020-2024 the openage authors. See copying.md for legal info. #include "versions.h" #ifdef __linux__ -#include + #include #endif #include @@ -11,37 +11,21 @@ #include #include #include -#include #include -#include "versions/compiletime.h" #include "../util/strings.h" +#include "versions/compiletime.h" namespace openage::versions { std::map get_version_numbers() { - std::map version_numbers; - // SDL runtime version number - SDL_version sdl_runtime_version; - SDL_GetVersion(&sdl_runtime_version); - version_numbers.emplace("SDL-runtime", util::sformat("%d.%d.%d", - sdl_runtime_version.major, - sdl_runtime_version.minor, - sdl_runtime_version.patch)); - // Eigen compiletime version number - version_numbers.emplace("Eigen", util::sformat("%d.%d.%d", - EIGEN_WORLD_VERSION, - EIGEN_MAJOR_VERSION, - EIGEN_MINOR_VERSION)); + version_numbers.emplace("Eigen", util::sformat("%d.%d.%d", EIGEN_WORLD_VERSION, EIGEN_MAJOR_VERSION, EIGEN_MINOR_VERSION)); // Harfbuzz compiletime version number - version_numbers.emplace("Harfbuzz", util::sformat("%d.%d.%d", - HB_VERSION_MAJOR, - HB_VERSION_MINOR, - HB_VERSION_MICRO)); + version_numbers.emplace("Harfbuzz", util::sformat("%d.%d.%d", HB_VERSION_MAJOR, HB_VERSION_MINOR, HB_VERSION_MICRO)); // Add Qt version number version_numbers.emplace("Qt", QT_VERSION_STR); @@ -49,38 +33,17 @@ std::map get_version_numbers() { // Add nyan version number version_numbers.emplace("nyan", nyan_version); - // Add OpenGL version number - // TODO: set the same SDL_GL_CONTEXT_MAJOR_VERSION as the real renderer - if (SDL_Init(SDL_INIT_VIDEO) != 0) { - version_numbers.emplace("OpenGL", SDL_GetError()); - } - else { - SDL_Window *window = SDL_CreateWindow("Query OpenGL version", - 0, 0, 640, 480, - SDL_WINDOW_OPENGL|SDL_WINDOW_HIDDEN); - if (window != nullptr) { - SDL_GLContext glcontext = SDL_GL_CreateContext(window); - if (glcontext != nullptr) { - version_numbers.emplace("OpenGL", - reinterpret_cast(glGetString(GL_VERSION))); - SDL_GL_DeleteContext(glcontext); - } - SDL_DestroyWindow(window); - } - SDL_Quit(); - } - // Add Opus version number std::string opus_version = opus_get_version_string(); version_numbers.emplace("Opus", opus_version.substr(opus_version.find(' ') + 1)); + // TODO: Add OpenGL version number + #ifdef __linux__ // Add libc version number if not MacOSX version_numbers.emplace("libc-runtime", gnu_get_libc_version()); - version_numbers.emplace("libc-compile", util::sformat("%d.%d", - __GLIBC__, - __GLIBC_MINOR__)); + version_numbers.emplace("libc-compile", util::sformat("%d.%d", __GLIBC__, __GLIBC_MINOR__)); #endif #ifdef __APPLE__ diff --git a/libopenage/versions/versions.h b/libopenage/versions/versions.h index cdbb25bbe9..310e98ad0d 100644 --- a/libopenage/versions/versions.h +++ b/libopenage/versions/versions.h @@ -1,4 +1,4 @@ -// Copyright 2020-2020 the openage authors. See copying.md for legal info. +// Copyright 2020-2024 the openage authors. See copying.md for legal info. #pragma once @@ -19,6 +19,6 @@ namespace openage::versions { * pxd: * map[string,string] get_version_numbers() except + */ -OAAPI std::map get_version_numbers(); +OAAPI std::map get_version_numbers(); } // namespace openage::versions diff --git a/nix/nyan.nix b/nix/nyan.nix new file mode 100644 index 0000000000..18ccb74288 --- /dev/null +++ b/nix/nyan.nix @@ -0,0 +1,44 @@ +{ lib +, stdenv +, fetchFromGitHub +, clang +, cmake +, flex +}: +let + pname = "nyan"; + version = "0.3"; +in +stdenv.mkDerivation +{ + inherit pname version; + + src = fetchFromGitHub { + owner = "SFTtech"; + repo = pname; + rev = "v${version}"; + hash = "sha256-bjz4aSS5RO+QuLd7kyblTPNcuQFhYK7sW1csNXHL4Qs="; + }; + + nativeBuildInputs = [ + clang + cmake + ]; + + buildInputs = [ + flex + ]; + + meta = with lib; { + description = "A data description language"; + longDescription = '' + Nyan stores hierarchical objects with key-value pairs in a database with the key idea that changes in a parent affect all children. We created nyan because there existed no suitable language to properly represent the enormous complexity of storing the data for openage. + ''; + homepage = "https://openage.sft.mx"; + license = licenses.lgpl3Plus; + platforms = platforms.unix; + mainProgram = "nyancat"; + }; +} + + diff --git a/nix/openage.nix b/nix/openage.nix new file mode 100644 index 0000000000..c26e51253a --- /dev/null +++ b/nix/openage.nix @@ -0,0 +1,113 @@ +# This file contains the definition of a nix expression that builds openage. +# A similar one exists from nyan, the configuration language. +# +# The expression in this file is a function (to be called by callPackage) +# that returns a derivation. callPackage is used inside the flake.nix file. +# +# To be compliant with nixpkgs, the dependencies can be passed via function +# arguments, similarly to how python3 or nyan is used here. The available +# packages that can be used when building/developing can be found among nixpkgs +# (https://search.nixos.org/) -as done with python3- or passed from the flake +# as done with nyan in the flake.nix file. +{ pkgs +, lib +, stdenv +, fetchFromGitHub +, nyan +, python3 +, ... +}: +let + pname = "openage"; + version = "0.5.3"; + + # Python libraries needed at build and run time. This function creates a + # python3 package configured with some packages (similar to a python venv). + # The current python3 version is used, which is python3.11 at the moment of + # writing. + # If more packages are needed, they can be added here. The available packages + # are listed at + # https://search.nixos.org/packages?channel=unstable&query=python311Packages + # For example, to add python311Packages.pybind11 to the dependencies, we could + # add to this list ps.pybind11 + pyEnv = python3.withPackages (ps: [ + ps.mako + ps.pillow + ps.numpy + ps.lz4 + ps.pygments + ps.cython + ps.pylint + ps.toml + ]); +in +stdenv.mkDerivation { + inherit pname version; + + # This uses the current checked-out repository as source code + src = ../.; + + # This fetches the code on github: one would use this when submitting the + # package to nixpkgs. + # src = fetchFromGitHub { + # owner = "SFTtech"; + # repo = pname; + # rev = "v${version}"; + # hash = "sha256-/ag4U7nnZbvkInnLOZ6cB2VWUb+n/HgM1wpo1y/mUHQ="; + # }; + + # Dependencies that are used only at build time + nativeBuildInputs = [ + # This is needed for qt applications + pkgs.qt6.wrapQtAppsHook + pkgs.toml11 + ]; + + # Dependencies that are used at build and run time + buildInputs = with pkgs; [ + nyan + pyEnv + + gcc + clang + cmake + gnumake + qt6.full + + eigen + libepoxy + libogg + libpng + dejavu_fonts + ftgl + fontconfig + harfbuzz + opusfile + libopus + qt6.qtdeclarative + qt6.qtmultimedia + ]; + + # openage requires access to both python dependencies and openage bindings + # Since nix places the binary somewhere in the nix store (/nix/store/blah), + # it needs to know where to find the python environment build above. This + # is done by setting the PYTHONPATH: the first part contains the path to + # the environment, the second part contains the path to the openage library + # build with this code. + postInstall = '' + wrapProgram $out/bin/openage --set PYTHONPATH "${pyEnv}/${pyEnv.sitePackages}:$out/lib/python3.11/site-packages/" + ''; + + # Metadata + meta = with lib; { + description = "Free (as in freedom) open source clone of the Age of Empires II engine"; + longDescription = '' + openage: a volunteer project to create a free engine clone of the Genie Engine used by Age of Empires, Age of Empires II (HD) and Star Wars: Galactic Battlegrounds, comparable to projects like OpenMW, OpenRA, OpenSAGE, OpenTTD and OpenRCT2. + + openage uses the original game assets (such as sounds and graphics), but (for obvious reasons) doesn't ship them + ''; + homepage = "https://openage.sft.mx"; + license = licenses.lgpl3Plus; + platforms = platforms.unix; + }; +} diff --git a/openage/CMakeLists.txt b/openage/CMakeLists.txt index a9233357dd..1b31e7110f 100644 --- a/openage/CMakeLists.txt +++ b/openage/CMakeLists.txt @@ -27,6 +27,7 @@ add_subdirectory(gamestate) add_subdirectory(log) add_subdirectory(main) add_subdirectory(nyan) +add_subdirectory(pathfinding) add_subdirectory(renderer) add_subdirectory(testing) add_subdirectory(util) diff --git a/openage/__main__.py b/openage/__main__.py index c002490739..60c1c1bb4f 100644 --- a/openage/__main__.py +++ b/openage/__main__.py @@ -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. # # pylint: disable=too-many-statements """ @@ -31,25 +31,15 @@ def print_version(): sys.exit(0) -def add_dll_search_paths(dll_paths): +def add_dll_search_paths(dll_paths: list[str]): """ This function adds DLL search paths. - This function does nothing if current OS is not Windows. """ + from .util.dll import DllDirectoryManager - def close_windows_dll_path_handles(dll_path_handles): - """ - This function calls close() method on each of the handles. - """ - for handle in dll_path_handles: - handle.close() + manager = DllDirectoryManager(dll_paths) - if sys.platform == 'win32' and dll_paths is not None: - import atexit - win_dll_path_handles = [] - for addtional_path in dll_paths: - win_dll_path_handles.append(os.add_dll_directory(addtional_path)) - atexit.register(close_windows_dll_path_handles, win_dll_path_handles) + return manager def main(argv=None): @@ -60,8 +50,11 @@ def main(argv=None): ) if sys.platform == 'win32': + from .util.dll import default_paths cli.add_argument( "--add-dll-search-path", action='append', dest='dll_paths', + # use path of current openage executable as default + default=default_paths(), help="(Windows only) provide additional DLL search path") cli.add_argument("--version", "-V", action='store_true', dest='print_version', @@ -99,15 +92,15 @@ def main(argv=None): # pylint: disable=reimported from .main.main import init_subparser - init_subparser(subparsers.add_parser( + main_cli = subparsers.add_parser( "main", - parents=[global_cli, cfg_cli])) + parents=[global_cli, cfg_cli]) + init_subparser(main_cli) from .game.main import init_subparser - game_cli = subparsers.add_parser( + init_subparser(subparsers.add_parser( "game", - parents=[global_cli, cfg_cli]) - init_subparser(game_cli) + parents=[global_cli, cfg_cli])) from .testing.main import init_subparser init_subparser(subparsers.add_parser( @@ -134,17 +127,21 @@ 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': - add_dll_search_paths(args.dll_paths) + dll_manager = add_dll_search_paths(args.dll_paths) + dll_manager.add_directories() if args.print_version: print_version() if not args.subcommand: - # the user didn't specify a subcommand. default to 'game'. - args = game_cli.parse_args(argv) + # the user didn't specify a subcommand. default to 'main'. + args = main_cli.parse_args(remaining_args) + + args.dll_manager = dll_manager # process the shared args set_loglevel(verbosity_to_level(args.verbose - args.quiet)) @@ -172,6 +169,11 @@ def main(argv=None): if __name__ == '__main__': + # Required for Windows executables (and apparently macOS too) + # https://docs.python.org/3/library/multiprocessing.html#multiprocessing.freeze_support + # https://pyinstaller.org/en/latest/common-issues-and-pitfalls.html#multi-processing + multiprocessing.freeze_support() + # openage is complicated and multithreaded; better not use fork. multiprocessing.set_start_method('spawn') 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/cabextract/cab.py b/openage/cabextract/cab.py index 3f04d45947..dde6d09aac 100644 --- a/openage/cabextract/cab.py +++ b/openage/cabextract/cab.py @@ -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. """ Provides CABFile, an extractor for the MSCAB format. @@ -18,7 +18,7 @@ from ..util.filelike.readonly import PosSavingReadOnlyFileLikeObject from ..util.filelike.stream import StreamFragment from ..util.files import read_guaranteed, read_nullterminated_string -from ..util.fslike.filecollection import FileCollection +from ..util.fslike.filecollection import FileCollection, FileEntry from ..util.math import INF from ..util.strings import try_decode from ..util.struct import NamedStruct, Flags @@ -216,6 +216,28 @@ def verify_checksum(self) -> Union[None, NoReturn]: raise ValueError("checksum error in MSCAB data block") +class CABEntry(FileEntry): + """ + Entry in a CAB file. + """ + + def __init__(self, fileobj: CFFile): + self.fileobj = fileobj + + def open_r(self): + return StreamFragment( + self.fileobj.folder.plain_stream, + self.fileobj.pos, + self.fileobj.size + ) + + def size(self) -> int: + return self.fileobj.size + + def mtime(self) -> float: + return self.fileobj.timestamp + + class CABFile(FileCollection): """ The actual file system-like CAB object. @@ -275,20 +297,9 @@ def __init__(self, cab: FileLikeObject, offset: int = 0): "CABFile has multiple entries with the same path: " + b'/'.join(fileobj.path).decode()) - def open_r(fileobj=fileobj): - """ Returns a opened ('rb') file-like object for fileobj. """ - return StreamFragment( - fileobj.folder.plain_stream, - fileobj.pos, - fileobj.size - ) - - self.add_fileentry(fileobj.path, ( - open_r, - None, - lambda fileobj=fileobj: fileobj.size, - lambda fileobj=fileobj: fileobj.timestamp - )) + file_entry = CABEntry(fileobj) + + self.add_fileentry(fileobj.path, file_entry) def __repr__(self): return "CABFile" diff --git a/openage/cabextract/cabchecksum.pyx b/openage/cabextract/cabchecksum.pyx index f869fc383b..cc5d418868 100644 --- a/openage/cabextract/cabchecksum.pyx +++ b/openage/cabextract/cabchecksum.pyx @@ -1,4 +1,4 @@ -# Copyright 2015-2016 the openage authors. See copying.md for legal info. +# Copyright 2015-2024 the openage authors. See copying.md for legal info. """ Implements the MSCAB checksum algorithm. @@ -74,7 +74,7 @@ def mscab_csum(bytes data): for i in range(count): result ^= data_ptr[i] - # we have so far ignored endianess issues. + # we have so far ignored endianness issues. # on a non-little endian system, the interpretation is wrong. # thus, interpret it as binary data and decode it as little endian. result = ( diff --git a/openage/convert/entity_object/conversion/aoc/genie_object_container.py b/openage/convert/entity_object/conversion/aoc/genie_object_container.py index c4a04376b9..00ce6cd79e 100644 --- a/openage/convert/entity_object/conversion/aoc/genie_object_container.py +++ b/openage/convert/entity_object/conversion/aoc/genie_object_container.py @@ -1,4 +1,4 @@ -# Copyright 2019-2023 the openage authors. See copying.md for legal info. +# Copyright 2019-2024 the openage authors. See copying.md for legal info. # # pylint: disable=too-many-instance-attributes,too-few-public-methods @@ -28,7 +28,7 @@ AgeUpgrade, BuildingLineUpgrade, BuildingUnlock, CivBonus, GenieTechEffectBundleGroup, \ InitiatedTech, StatUpgrade, UnitLineUpgrade, UnitUnlock from openage.convert.entity_object.conversion.aoc.genie_terrain import GenieTerrainObject, \ - GenieTerrainGroup + GenieTerrainGroup, GenieTerrainRestriction from openage.convert.entity_object.conversion.aoc.genie_unit import GenieUnitObject, \ GenieAmbientGroup, GenieBuildingLineGroup, GenieMonkGroup, GenieUnitLineGroup, \ GenieUnitTaskGroup, GenieUnitTransformGroup, GenieVariantGroup, GenieVillagerGroup, \ @@ -75,6 +75,7 @@ def __init__(self): self.genie_graphics: dict[int, GenieGraphic] = {} self.genie_sounds: dict[int, GenieSound] = {} self.genie_terrains: dict[int, GenieTerrainObject] = {} + self.genie_terrain_restrictions: dict[int, GenieTerrainRestriction] = {} # Phase 2: API-like objects # ConverterObjectGroup types (things that will become diff --git a/openage/convert/entity_object/conversion/aoc/genie_terrain.py b/openage/convert/entity_object/conversion/aoc/genie_terrain.py index a361c754db..55c76f9f75 100644 --- a/openage/convert/entity_object/conversion/aoc/genie_terrain.py +++ b/openage/convert/entity_object/conversion/aoc/genie_terrain.py @@ -1,4 +1,4 @@ -# Copyright 2019-2022 the openage authors. See copying.md for legal info. +# Copyright 2019-2024 the openage authors. See copying.md for legal info. """ Contains structures and API-like objects for terrain from AoC. @@ -94,3 +94,42 @@ def get_terrain(self) -> GenieTerrainObject: def __repr__(self): return f"GenieTerrainGroup<{self.get_id()}>" + + +class GenieTerrainRestriction(ConverterObject): + """ + Terrain restriction definition from a .dat file. + """ + + __slots__ = ('data',) + + def __init__( + self, + restriction_id: int, + full_data_set: GenieObjectContainer, + members: dict[str, ValueMember] = None + ): + """ + Creates a new Genie terrain restriction object. + + :param restriction_id: The index of the terrain restriction in the .dat file. + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + """ + super().__init__(restriction_id, members=members) + + self.data = full_data_set + + def is_accessible(self, terrain_index: int) -> bool: + """ + Checks if a terrain is accessible by this restriction. + + :param terrain_index: Index of the terrain. + """ + multiplier = self.members["accessible_dmgmultiplier"][terrain_index].value + + return multiplier > 0 + + def __repr__(self): + return f"GenieTerrainRestriction<{self.get_id()}>" diff --git a/openage/convert/entity_object/conversion/aoc/genie_unit.py b/openage/convert/entity_object/conversion/aoc/genie_unit.py index e719c5f24f..52ded9b659 100644 --- a/openage/convert/entity_object/conversion/aoc/genie_unit.py +++ b/openage/convert/entity_object/conversion/aoc/genie_unit.py @@ -479,6 +479,8 @@ def is_unique(self) -> bool: else: # AoE1 return False + else: + raise ValueError(f"Unknown group type for {repr(self)}") enabling_research_id = head_unit_connection["enabling_research"].value diff --git a/openage/convert/entity_object/conversion/ror/genie_unit.py b/openage/convert/entity_object/conversion/ror/genie_unit.py index 86e6b4986c..c2fe46267f 100644 --- a/openage/convert/entity_object/conversion/ror/genie_unit.py +++ b/openage/convert/entity_object/conversion/ror/genie_unit.py @@ -50,7 +50,7 @@ def __init__( def is_garrison(self, civ_id: int = -1) -> bool: """ - Only transport shis can garrison in RoR. + Only transport ships can garrison in RoR. :returns: True if the unit has the unload command (ID: 12). """ @@ -229,7 +229,7 @@ def __init__( def is_garrison(self, civ_id: int = -1) -> bool: """ - Only transport shis can garrison in RoR. + Only transport ships can garrison in RoR. :returns: True if the unit has the unload command (ID: 12). """ diff --git a/openage/convert/entity_object/export/formats/modpack_info.py b/openage/convert/entity_object/export/formats/modpack_info.py index 1a6017ce4e..0df5f220ce 100644 --- a/openage/convert/entity_object/export/formats/modpack_info.py +++ b/openage/convert/entity_object/export/formats/modpack_info.py @@ -1,4 +1,4 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. +# Copyright 2020-2024 the openage authors. See copying.md for legal info. # # pylint: disable=too-many-instance-attributes,too-many-arguments @@ -153,7 +153,7 @@ def add_dependency(self, modpack_id: str) -> None: def set_info( self, packagename: str, - version: str, + modpack_version: str, versionstr: str = None, repo: str = None, alias: str = None, @@ -168,9 +168,9 @@ def set_info( :param packagename: Name of the modpack. :type packagename: str - :param version: Internal version number. Must have semver format. - :type version: str - :param versionstr: Human-readable version number. + :param modpack_version: Internal version number. Must have semver format. + :type modpack_version: str + :param versionstr: Human-readable version number. Can be anything. :type versionstr: str :param repo: Name of the repo where the package is hosted. :type repo: str @@ -188,7 +188,7 @@ def set_info( :type licenses: list """ self.packagename = packagename - self.version = version + self.version = modpack_version if versionstr: self.extra_info["versionstr"] = versionstr diff --git a/openage/convert/entity_object/export/formats/terrain_metadata.py b/openage/convert/entity_object/export/formats/terrain_metadata.py index 956b44d11e..46ba96b259 100644 --- a/openage/convert/entity_object/export/formats/terrain_metadata.py +++ b/openage/convert/entity_object/export/formats/terrain_metadata.py @@ -158,7 +158,9 @@ def dump(self) -> str: output_str += "\n" # blendtable reference - output_str += f"blendtable {self.blendtable['table_id']} {self.blendtable['filename']}\n\n" + if self.blendtable: + output_str += (f"blendtable {self.blendtable['table_id']} " + "{self.blendtable['filename']}\n\n") # scale factor output_str += f"scalefactor {self.scalefactor}\n\n" @@ -185,7 +187,16 @@ def dump(self) -> str: # frame definitions for frame in self.frames: - output_str += f'frame {" ".join(str(param) for param in frame.values())}\n' + frame_attributes = list(frame.values()) + output_str += f'frame {" ".join(str(param) for param in frame_attributes[:4])}' + + if frame["priority"]: + output_str += f" priority={frame['priority']}" + + if frame["blend_mode"]: + output_str += f" blend_mode={frame['blend_mode']}" + + output_str += "\n" return output_str diff --git a/openage/convert/entity_object/export/metadata_export.py b/openage/convert/entity_object/export/metadata_export.py index a4d2fd35ed..d01e1d53f5 100644 --- a/openage/convert/entity_object/export/metadata_export.py +++ b/openage/convert/entity_object/export/metadata_export.py @@ -11,10 +11,14 @@ from ....util.observer import Observer from .formats.sprite_metadata import SpriteMetadata from .formats.texture_metadata import TextureMetadata +from .formats.terrain_metadata import TerrainMetadata if typing.TYPE_CHECKING: from openage.util.observer import Observable - from openage.convert.entity_object.export.formats.sprite_metadata import LayerMode + from openage.convert.entity_object.export.formats.sprite_metadata import LayerMode\ + as SpriteLayerMode + from openage.convert.entity_object.export.formats.terrain_metadata import LayerMode\ + as TerrainLayerMode class MetadataExport(Observer): @@ -50,7 +54,7 @@ def add_graphics_metadata( self, img_filename: str, tex_filename: str, - layer_mode: LayerMode, + layer_mode: SpriteLayerMode, layer_pos: int, frame_rate: float, replay_delay: float, @@ -62,12 +66,28 @@ def add_graphics_metadata( """ Add metadata from the GenieGraphic object. + :param img_filename: Filename of the exported PNG file. :param tex_filename: Filename of the .texture file. + :param layer_mode: Animation mode (off, once, loop). + :param layer_pos: Layer position. + :param frame_rate: Time spent on each frame. + :param replay_delay: Time delay before replaying the animation. + :param frame_count: Number of frames per angle in the animation. + :param angle_count: Number of angles in the animation. + :param mirror_mode: Mirroring mode (0, 1). If 1, angles above 180 degrees are mirrored. :param start_angle: Angle used for the first frame in the .texture file. """ - self.graphics_metadata[img_filename] = (tex_filename, layer_mode, layer_pos, frame_rate, - replay_delay, frame_count, angle_count, mirror_mode, - start_angle) + self.graphics_metadata[img_filename] = ( + tex_filename, + layer_mode, + layer_pos, + frame_rate, + replay_delay, + frame_count, + angle_count, + mirror_mode, + start_angle + ) def dump(self) -> str: """ @@ -191,3 +211,72 @@ def update(self, observable: Observable, message: dict = None): texture_metadata = message[self.imagefile] self.size = texture_metadata["size"] self.subtex_metadata = texture_metadata["subtex_metadata"] + + +class TerrainMetadataExport(MetadataExport): + """ + Export requests for texture definition files. + """ + + def __init__(self, targetdir, target_filename): + super().__init__(targetdir, target_filename) + + self.graphics_metadata: dict[int, tuple] = {} + self.subtex_count: dict[str, int] = {} + + def add_graphics_metadata( + self, + img_filename: str, + tex_filename: str, + layer_mode: TerrainLayerMode, + layer_pos: int, + frame_rate: float, + replay_delay: float, + frame_count: int, + ): + """ + Add metadata from the GenieGraphic object. + + :param img_filename: Filename of the exported PNG file. + :param tex_filename: Filename of the .texture file. + :param layer_mode: Animation mode (off, loop). + :param layer_pos: Layer position. + :param frame_rate: Time spent on each frame. + :param replay_delay: Time delay before replaying the animation. + :param frame_count: Number of frames in the animation. + """ + self.graphics_metadata[img_filename] = ( + tex_filename, + layer_mode, + layer_pos, + frame_rate, + replay_delay, + frame_count + ) + + def dump(self) -> str: + """ + Creates a human-readable string that can be written to a file. + """ + terrain_file = TerrainMetadata(self.targetdir, self.filename) + + tex_index = 0 + for _, metadata in self.graphics_metadata.items(): + tex_filename = metadata[0] + terrain_file.add_texture(tex_index, tex_filename) + terrain_file.add_layer(tex_index, *metadata[1:5]) + + frame_count = metadata[5] + + for frame_idx in range(frame_count): + subtex_index = frame_idx + terrain_file.add_frame( + frame_idx, + tex_index, + tex_index, + subtex_index + ) + + tex_index += 1 + + return terrain_file.dump() diff --git a/openage/convert/entity_object/export/texture.py b/openage/convert/entity_object/export/texture.py index 5039abfeb3..885f5dbfaa 100644 --- a/openage/convert/entity_object/export/texture.py +++ b/openage/convert/entity_object/export/texture.py @@ -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. """ Routines for texture generation etc """ @@ -14,7 +14,6 @@ from ....log import spam from ...value_object.read.media.blendomatic import BlendingMode from ...value_object.read.media.hardcoded.terrain_tile_size import TILE_HALFSIZE -from ...value_object.read.genie_structure import GenieStructure if typing.TYPE_CHECKING: from openage.convert.value_object.read.media.colortable import ColorTable @@ -65,17 +64,13 @@ def get_data(self) -> numpy.ndarray: return self.data -class Texture(GenieStructure): - image_format = "png" +class Texture: + """ + one sprite, as part of a texture atlas. - name_struct = "subtexture" - name_struct_file = "texture" - struct_description = ( - "one sprite, as part of a texture atlas.\n" - "\n" - "this struct stores information about positions and sizes\n" - "of sprites included in the 'big texture'." - ) + stores information about positions and sizes + of sprites included in the 'big texture'. + """ def __init__( self, @@ -180,18 +175,3 @@ def get_cache_params(self) -> tuple[tuple, tuple]: - PNG compression parameters (compression level + deflate params) """ return self.best_packer_hints, self.best_compr - - @classmethod - def get_data_format_members(cls, game_version) -> tuple: - """ - Return the members in this struct. - """ - data_format = ( - (True, "x", None, "int32_t"), - (True, "y", None, "int32_t"), - (True, "w", None, "int32_t"), - (True, "h", None, "int32_t"), - (True, "cx", None, "int32_t"), - (True, "cy", None, "int32_t"), - ) - return data_format diff --git a/openage/convert/main.py b/openage/convert/main.py index a0bd7a927b..cefa06d44e 100644 --- a/openage/convert/main.py +++ b/openage/convert/main.py @@ -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. # # pylint: disable=too-many-branches """ @@ -15,11 +15,13 @@ from ..util.fslike.wrapper import (DirectoryCreator, Synchronizer as AccessSynchronizer) from .service.debug_info import debug_cli_args, debug_game_version, debug_mounts -from .service.init.conversion_required import conversion_required +from .service.init.changelog import check_updates +from .service.init.modpack_search import enumerate_modpacks from .service.init.mount_asset_dirs import mount_asset_dirs from .service.init.version_detect import create_version_objects from .tool.interactive import interactive_browser -from .tool.subtool.acquire_sourcedir import acquire_conversion_source_dir, wanna_convert +from .tool.subtool.acquire_sourcedir import acquire_conversion_source_dir, wanna_convert, \ + wanna_check_updates from .tool.subtool.version_select import get_game_version if typing.TYPE_CHECKING: @@ -53,6 +55,10 @@ def convert_assets( if "compression_level" not in vars(args): args.compression_level = 1 + # Set worker count for multi-threading if it was not set + if "jobs" not in vars(args): + args.jobs = None + # Set verbosity for debug output if "debug_info" not in vars(args) or not args.debug_info: if args.devmode: @@ -64,7 +70,7 @@ def convert_assets( # add a dir for debug info debug_log_path = converted_path / "debug" / datetime.now().strftime("%Y-%m-%d-%H-%M-%S") debugdir = DirectoryCreator(debug_log_path).root - args.debugdir = AccessSynchronizer(debugdir).root + args.debugdir = debugdir # Create CLI args info debug_cli_args(args.debugdir, args.debug_info, args) @@ -93,9 +99,8 @@ def convert_assets( if not data_dir: return None - # make srcdir and targetdir safe for threaded conversion args.srcdir = AccessSynchronizer(data_dir).root - args.targetdir = AccessSynchronizer(targetdir).root + args.targetdir = targetdir # Create mountpoint info debug_mounts(args.debugdir, args.debug_info, args) @@ -235,6 +240,16 @@ def init_subparser(cli: ArgumentParser): "--export-api", action='store_true', help="Export the openage nyan API definition as a modpack") + cli.add_argument( + "--check-updates", action='store_true', + help="Check if the assets are up to date" + ) + + cli.add_argument( + "--no-prompts", action='store_false', dest='show_prompts', + help="Disable user prompts" + ) + def main(args, error): """ CLI entry point """ @@ -265,11 +280,16 @@ def main(args, error): from ..assets import get_asset_path outdir = get_asset_path(args.output_dir) - if args.force or wanna_convert() or conversion_required(outdir): + if args.force or (args.show_prompts and wanna_convert()): convert_assets(outdir, args, srcdir) - else: - print("assets are up to date; no conversion is required.") - print("override with --force.") + if args.check_updates or (args.show_prompts and wanna_check_updates()): + # check if the assets are up to date + modpack_dir = outdir / "converted" + available_modpacks = enumerate_modpacks(modpack_dir, exclude={"engine"}) + + game_info_dir = args.cfg_dir / "converter" / "games" + + check_updates(available_modpacks, game_info_dir) return 0 diff --git a/openage/convert/processor/conversion/aoc/ability_subprocessor.py b/openage/convert/processor/conversion/aoc/ability_subprocessor.py index 8e53371b2f..120541fe4c 100644 --- a/openage/convert/processor/conversion/aoc/ability_subprocessor.py +++ b/openage/convert/processor/conversion/aoc/ability_subprocessor.py @@ -1,4 +1,4 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. +# Copyright 2020-2024 the openage authors. See copying.md for legal info. # # pylint: disable=too-many-public-methods,too-many-lines,too-many-locals # pylint: disable=too-many-branches,too-many-statements,too-many-arguments @@ -302,6 +302,45 @@ def apply_continuous_effect_ability( return ability_forward_ref + @staticmethod + def activity_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Activity ability to a line. + + :param line: Unit/Building line that gets the ability. + :type line: ...dataformat.converter_object.ConverterObjectGroup + :returns: The forward reference for the ability. + :rtype: ...dataformat.forward_ref.ForwardRef + """ + current_unit_id = line.get_head_unit_id() + + dataset = line.data + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[current_unit_id][0] + + ability_ref = f"{game_entity_name}.Activity" + ability_raw_api_object = RawAPIObject(ability_ref, "Activity", dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.Activity") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # activity graph + if isinstance(line, GenieUnitLineGroup): + activity = dataset.pregen_nyan_objects["util.activity.types.Unit"].get_nyan_object() + + else: + activity = dataset.pregen_nyan_objects["util.activity.types.Default"].get_nyan_object() + + ability_raw_api_object.add_raw_member("graph", activity, "engine.ability.type.Activity") + + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref + @staticmethod def apply_discrete_effect_ability( line: GenieGameEntityGroup, @@ -614,7 +653,7 @@ def apply_discrete_effect_ability( return ability_forward_ref @staticmethod - def attribute_change_tracker_ability(line) -> ForwardRef: + def attribute_change_tracker_ability(line: GenieGameEntityGroup) -> ForwardRef: """ Adds the AttributeChangeTracker ability to a line. @@ -781,6 +820,65 @@ def collect_storage_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref + @ staticmethod + def collision_ability(line: GenieGameEntityGroup) -> ForwardRef: + """ + Adds the Collision ability to a line. + + :param line: Unit/Building line that gets the ability. + :type line: ...dataformat.converter_object.ConverterObjectGroup + :returns: The forward reference for the ability. + :rtype: ...dataformat.forward_ref.ForwardRef + """ + current_unit = line.get_head_unit() + current_unit_id = line.get_head_unit_id() + dataset = line.data + + name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) + + game_entity_name = name_lookup_dict[current_unit_id][0] + + ability_ref = f"{game_entity_name}.Collision" + ability_raw_api_object = RawAPIObject(ability_ref, "Collision", dataset.nyan_api_objects) + ability_raw_api_object.add_raw_parent("engine.ability.type.Collision") + ability_location = ForwardRef(line, game_entity_name) + ability_raw_api_object.set_location(ability_location) + + # Hitbox object + hitbox_name = f"{game_entity_name}.Collision.{game_entity_name}Hitbox" + hitbox_raw_api_object = RawAPIObject(hitbox_name, + f"{game_entity_name}Hitbox", + dataset.nyan_api_objects) + hitbox_raw_api_object.add_raw_parent("engine.util.hitbox.Hitbox") + hitbox_location = ForwardRef(line, ability_ref) + hitbox_raw_api_object.set_location(hitbox_location) + + radius_x = current_unit["radius_x"].value + radius_y = current_unit["radius_y"].value + radius_z = current_unit["radius_z"].value + + hitbox_raw_api_object.add_raw_member("radius_x", + radius_x, + "engine.util.hitbox.Hitbox") + hitbox_raw_api_object.add_raw_member("radius_y", + radius_y, + "engine.util.hitbox.Hitbox") + hitbox_raw_api_object.add_raw_member("radius_z", + radius_z, + "engine.util.hitbox.Hitbox") + + hitbox_forward_ref = ForwardRef(line, hitbox_name) + ability_raw_api_object.add_raw_member("hitbox", + hitbox_forward_ref, + "engine.ability.type.Collision") + + line.add_raw_api_object(hitbox_raw_api_object) + line.add_raw_api_object(ability_raw_api_object) + + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) + + return ability_forward_ref + @staticmethod def constructable_ability(line: GenieGameEntityGroup) -> ForwardRef: """ @@ -3976,65 +4074,6 @@ def herdable_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref - @ staticmethod - def hitbox_ability(line: GenieGameEntityGroup) -> ForwardRef: - """ - Adds the Hitbox ability to a line. - - :param line: Unit/Building line that gets the ability. - :type line: ...dataformat.converter_object.ConverterObjectGroup - :returns: The forward reference for the ability. - :rtype: ...dataformat.forward_ref.ForwardRef - """ - current_unit = line.get_head_unit() - current_unit_id = line.get_head_unit_id() - dataset = line.data - - name_lookup_dict = internal_name_lookups.get_entity_lookups(dataset.game_version) - - game_entity_name = name_lookup_dict[current_unit_id][0] - - ability_ref = f"{game_entity_name}.Hitbox" - ability_raw_api_object = RawAPIObject(ability_ref, "Hitbox", dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.Hitbox") - ability_location = ForwardRef(line, game_entity_name) - ability_raw_api_object.set_location(ability_location) - - # Hitbox object - hitbox_name = f"{game_entity_name}.Hitbox.{game_entity_name}Hitbox" - hitbox_raw_api_object = RawAPIObject(hitbox_name, - f"{game_entity_name}Hitbox", - dataset.nyan_api_objects) - hitbox_raw_api_object.add_raw_parent("engine.util.hitbox.Hitbox") - hitbox_location = ForwardRef(line, ability_ref) - hitbox_raw_api_object.set_location(hitbox_location) - - radius_x = current_unit["radius_x"].value - radius_y = current_unit["radius_y"].value - radius_z = current_unit["radius_z"].value - - hitbox_raw_api_object.add_raw_member("radius_x", - radius_x, - "engine.util.hitbox.Hitbox") - hitbox_raw_api_object.add_raw_member("radius_y", - radius_y, - "engine.util.hitbox.Hitbox") - hitbox_raw_api_object.add_raw_member("radius_z", - radius_z, - "engine.util.hitbox.Hitbox") - - hitbox_forward_ref = ForwardRef(line, hitbox_name) - ability_raw_api_object.add_raw_member("hitbox", - hitbox_forward_ref, - "engine.ability.type.Hitbox") - - line.add_raw_api_object(hitbox_raw_api_object) - line.add_raw_api_object(ability_raw_api_object) - - ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) - - return ability_forward_ref - @ staticmethod def idle_ability(line: GenieGameEntityGroup) -> ForwardRef: """ @@ -4487,6 +4526,19 @@ def move_ability(line: GenieGameEntityGroup) -> ForwardRef: ability_raw_api_object.add_raw_member("modes", move_modes, "engine.ability.type.Move") + # Path type + path_type = dataset.pregen_nyan_objects["util.path.types.Land"].get_nyan_object() + restrictions = current_unit["terrain_restriction"].value + if restrictions in (0x00, 0x0C, 0x0E, 0x17): + # air units + path_type = dataset.pregen_nyan_objects["util.path.types.Air"].get_nyan_object() + + elif restrictions in (0x03, 0x0D, 0x0F): + # ships + path_type = dataset.pregen_nyan_objects["util.path.types.Water"].get_nyan_object() + + ability_raw_api_object.add_raw_member("path_type", path_type, "engine.ability.type.Move") + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) return ability_forward_ref @@ -4580,6 +4632,10 @@ def move_projectile_ability(line: GenieGameEntityGroup, position: int = -1) -> F ] ability_raw_api_object.add_raw_member("modes", move_modes, "engine.ability.type.Move") + # Path type + path_type = dataset.pregen_nyan_objects["util.path.types.Air"].get_nyan_object() + ability_raw_api_object.add_raw_member("path_type", path_type, "engine.ability.type.Move") + ability_forward_ref = ForwardRef(line, ability_raw_api_object.get_id()) return ability_forward_ref @@ -4718,9 +4774,9 @@ def overlay_terrain_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref @ staticmethod - def passable_ability(line: GenieGameEntityGroup) -> ForwardRef: + def pathable_ability(line: GenieGameEntityGroup) -> ForwardRef: """ - Adds the Passable ability to a line. + Adds the Pathable ability to a line. :param line: Unit/Building line that gets the ability. :type line: ...dataformat.converter_object.ConverterObjectGroup @@ -4734,67 +4790,29 @@ def passable_ability(line: GenieGameEntityGroup) -> ForwardRef: game_entity_name = name_lookup_dict[current_unit_id][0] - ability_ref = f"{game_entity_name}.Passable" + ability_ref = f"{game_entity_name}.Pathable" ability_raw_api_object = RawAPIObject(ability_ref, - "Passable", + "Pathable", dataset.nyan_api_objects) - ability_raw_api_object.add_raw_parent("engine.ability.type.Passable") + ability_raw_api_object.add_raw_parent("engine.ability.type.Pathable") ability_location = ForwardRef(line, game_entity_name) ability_raw_api_object.set_location(ability_location) # Hitbox - hitbox_ref = f"{game_entity_name}.Hitbox.{game_entity_name}Hitbox" + hitbox_ref = f"{game_entity_name}.Collision.{game_entity_name}Hitbox" hitbox_forward_ref = ForwardRef(line, hitbox_ref) ability_raw_api_object.add_raw_member("hitbox", hitbox_forward_ref, - "engine.ability.type.Passable") - - # Passable mode - # ===================================================================================== - mode_name = f"{game_entity_name}.Passable.PassableMode" - mode_raw_api_object = RawAPIObject(mode_name, "PassableMode", dataset.nyan_api_objects) - mode_parent = "engine.util.passable_mode.type.Normal" - if isinstance(line, GenieStackBuildingGroup): - if line.is_gate(): - mode_parent = "engine.util.passable_mode.type.Gate" - - mode_raw_api_object.add_raw_parent(mode_parent) - mode_location = ForwardRef(line, ability_ref) - mode_raw_api_object.set_location(mode_location) - - # Allowed types - allowed_types = [ - dataset.pregen_nyan_objects["util.game_entity_type.types.Unit"].get_nyan_object(), - dataset.pregen_nyan_objects["util.game_entity_type.types.Building"].get_nyan_object(), - dataset.pregen_nyan_objects["util.game_entity_type.types.Projectile"].get_nyan_object() - ] - mode_raw_api_object.add_raw_member("allowed_types", - allowed_types, - "engine.util.passable_mode.PassableMode") + "engine.ability.type.Pathable") - # Blacklisted entities - mode_raw_api_object.add_raw_member("blacklisted_entities", - [], - "engine.util.passable_mode.PassableMode") - - if isinstance(line, GenieStackBuildingGroup): - if line.is_gate(): - # Let friendly and own units pass through gate - stances = [ - dataset.pregen_nyan_objects["util.diplomatic_stance.types.Friendly"].get_nyan_object( - ), - dataset.nyan_api_objects["engine.util.diplomatic_stance.type.Self"] - ] - mode_raw_api_object.add_raw_member("stances", - stances, - mode_parent) - - line.add_raw_api_object(mode_raw_api_object) - # ===================================================================================== - mode_forward_ref = ForwardRef(line, mode_name) - ability_raw_api_object.add_raw_member("mode", - mode_forward_ref, - "engine.ability.type.Passable") + # Costs + path_costs = { + dataset.pregen_nyan_objects["util.path.types.Land"].get_nyan_object(): 255, # impassable + dataset.pregen_nyan_objects["util.path.types.Water"].get_nyan_object(): 255, # impassable + } + ability_raw_api_object.add_raw_member("path_costs", + path_costs, + "engine.ability.type.Pathable") line.add_raw_api_object(ability_raw_api_object) diff --git a/openage/convert/processor/conversion/aoc/media_subprocessor.py b/openage/convert/processor/conversion/aoc/media_subprocessor.py index 360a13b4f7..90af7ad297 100644 --- a/openage/convert/processor/conversion/aoc/media_subprocessor.py +++ b/openage/convert/processor/conversion/aoc/media_subprocessor.py @@ -1,6 +1,6 @@ # Copyright 2019-2023 the openage authors. See copying.md for legal info. # -# pylint: disable=too-many-locals,too-few-public-methods +# pylint: disable=too-many-locals,too-few-public-methods,too-many-statements """ Convert media information to metadata definitions and export requests. Subroutine of the main AoC processor. @@ -10,10 +10,12 @@ from openage.convert.value_object.read.media_types import MediaType -from ....entity_object.export.formats.sprite_metadata import LayerMode +from ....entity_object.export.formats.sprite_metadata import LayerMode as SpriteLayerMode +from ....entity_object.export.formats.terrain_metadata import LayerMode as TerrainLayerMode from ....entity_object.export.media_export_request import MediaExportRequest from ....entity_object.export.metadata_export import SpriteMetadataExport from ....entity_object.export.metadata_export import TextureMetadataExport +from ....entity_object.export.metadata_export import TerrainMetadataExport from ....value_object.read.media_types import MediaType if typing.TYPE_CHECKING: @@ -82,13 +84,13 @@ def create_graphics_requests(full_data_set: GenieObjectContainer) -> None: # Add metadata from graphics to animation metadata sequence_type = graphic["sequence_type"].value if sequence_type == 0x00: - layer_mode = LayerMode.OFF + layer_mode = SpriteLayerMode.OFF elif sequence_type & 0x08: - layer_mode = LayerMode.ONCE + layer_mode = SpriteLayerMode.ONCE else: - layer_mode = LayerMode.LOOP + layer_mode = SpriteLayerMode.LOOP layer_pos = graphic["layer"].value frame_rate = round(graphic["frame_rate"].value, ndigits=6) @@ -132,6 +134,44 @@ def create_graphics_requests(full_data_set: GenieObjectContainer) -> None: target_filename) full_data_set.graphics_exports.update({slp_id: export_request}) + texture_meta_filename = f"{texture.get_filename()}.texture" + texture_meta_export = TextureMetadataExport(targetdir, + texture_meta_filename) + full_data_set.metadata_exports.append(texture_meta_export) + + # Add texture image filename to texture metadata + texture_meta_export.add_imagefile(target_filename) + texture_meta_export.update( + None, + { + f"{target_filename}": { + "size": (481, 481), # TODO: Get actual size = sqrt(slp_frame_count) + "subtex_metadata": [ + { + "x": 0, + "y": 0, + "w": 481, + "h": 481, + "cx": 0, + "cy": 0, + } + ] + }} + ) + + terrain_meta_filename = f"{texture.get_filename()}.terrain" + terrain_meta_export = TerrainMetadataExport(targetdir, + terrain_meta_filename) + full_data_set.metadata_exports.append(terrain_meta_export) + + terrain_meta_export.add_graphics_metadata(target_filename, + texture_meta_filename, + TerrainLayerMode.OFF, + 0, + 0.0, + 0.0, + 1) + @staticmethod def create_blend_requests(full_data_set: GenieObjectContainer) -> None: """ diff --git a/openage/convert/processor/conversion/aoc/modpack_subprocessor.py b/openage/convert/processor/conversion/aoc/modpack_subprocessor.py index 8d54d68fed..b842fc74c2 100644 --- a/openage/convert/processor/conversion/aoc/modpack_subprocessor.py +++ b/openage/convert/processor/conversion/aoc/modpack_subprocessor.py @@ -1,4 +1,4 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. +# Copyright 2020-2024 the openage authors. See copying.md for legal info. # # pylint: disable=too-many-locals,too-many-branches,too-few-public-methods,too-many-statements @@ -44,7 +44,10 @@ def _get_aoe2_base(cls, full_data_set: GenieObjectContainer) -> Modpack: mod_def = modpack.get_info() - mod_def.set_info("aoe2_base", "0.5", versionstr="1.0c", repo="openage") + targetmod_info = full_data_set.game_version.edition.target_modpacks["aoe2_base"] + version = targetmod_info["version"] + versionstr = targetmod_info["versionstr"] + mod_def.set_info("aoe2_base", version, versionstr=versionstr, repo="openage") mod_def.add_include("data/**") @@ -151,6 +154,11 @@ def _set_static_aliases(modpack: Modpack, import_tree: ImportTree) -> None: import_tree.add_alias(("engine", "ability", "property", "type"), "ability_prop") # Auxiliary objects + import_tree.add_alias( + ("engine", "util", "activity", "condition", "type"), "activity_condition" + ) + import_tree.add_alias(("engine", "util", "activity", "event", "type"), "activity_event") + import_tree.add_alias(("engine", "util", "activity", "node", "type"), "activity_node") import_tree.add_alias(("engine", "util", "accuracy"), "accuracy") import_tree.add_alias( ("engine", "util", "animation_override"), "animation_override" @@ -193,7 +201,7 @@ def _set_static_aliases(modpack: Modpack, import_tree: ImportTree) -> None: import_tree.add_alias(("engine", "util", "logic", "literal_scope", "type"), "literal_scope") import_tree.add_alias(("engine", "util", "patch"), "patch") import_tree.add_alias(("engine", "util", "patch", "property", "type"), "patch_prop") - import_tree.add_alias(("engine", "util", "passable_mode", "type"), "passable_mode") + import_tree.add_alias(("engine", "util", "path_type"), "path_type") import_tree.add_alias(("engine", "util", "payment_mode", "type"), "payment_mode") import_tree.add_alias(("engine", "util", "placement_mode", "type"), "placement_mode") import_tree.add_alias(("engine", "util", "price_mode", "type"), "price_mode") @@ -318,6 +326,10 @@ def _set_static_aliases(modpack: Modpack, import_tree: ImportTree) -> None: "garrison_empty"), "empty_garrison_condition" ) + import_tree.add_alias( + (modpack.name, "data", "util", "path_type", "types"), + prefix + "path_type" + ) import_tree.add_alias( (modpack.name, "data", "util", "resource", "market_trading"), prefix + "market_trading" diff --git a/openage/convert/processor/conversion/aoc/nyan_subprocessor.py b/openage/convert/processor/conversion/aoc/nyan_subprocessor.py index b519412ca3..7afbd38134 100644 --- a/openage/convert/processor/conversion/aoc/nyan_subprocessor.py +++ b/openage/convert/processor/conversion/aoc/nyan_subprocessor.py @@ -1,4 +1,4 @@ -# Copyright 2019-2023 the openage authors. See copying.md for legal info. +# Copyright 2019-2024 the openage authors. See copying.md for legal info. # # pylint: disable=too-many-lines,too-many-locals,too-many-statements,too-many-branches # @@ -14,7 +14,7 @@ from ....entity_object.conversion.aoc.genie_tech import UnitLineUpgrade from ....entity_object.conversion.aoc.genie_unit import GenieGarrisonMode, \ - GenieMonkGroup, GenieStackBuildingGroup + GenieMonkGroup from ....entity_object.conversion.aoc.genie_unit import GenieVillagerGroup from ....entity_object.conversion.combined_terrain import CombinedTerrain from ....entity_object.conversion.converter_object import RawAPIObject @@ -219,11 +219,12 @@ def unit_line_to_game_entity(unit_line: GenieUnitLineGroup) -> None: # ======================================================================= abilities_set = [] + abilities_set.append(AoCAbilitySubprocessor.activity_ability(unit_line)) abilities_set.append(AoCAbilitySubprocessor.death_ability(unit_line)) abilities_set.append(AoCAbilitySubprocessor.delete_ability(unit_line)) abilities_set.append(AoCAbilitySubprocessor.despawn_ability(unit_line)) abilities_set.append(AoCAbilitySubprocessor.idle_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.hitbox_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.collision_ability(unit_line)) abilities_set.append(AoCAbilitySubprocessor.live_ability(unit_line)) abilities_set.append(AoCAbilitySubprocessor.los_ability(unit_line)) abilities_set.append(AoCAbilitySubprocessor.move_ability(unit_line)) @@ -435,7 +436,7 @@ def building_line_to_game_entity(building_line: GenieBuildingLineGroup) -> None: abilities_set.append(AoCAbilitySubprocessor.delete_ability(building_line)) abilities_set.append(AoCAbilitySubprocessor.despawn_ability(building_line)) abilities_set.append(AoCAbilitySubprocessor.idle_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.hitbox_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.collision_ability(building_line)) abilities_set.append(AoCAbilitySubprocessor.live_ability(building_line)) abilities_set.append(AoCAbilitySubprocessor.los_ability(building_line)) abilities_set.append(AoCAbilitySubprocessor.named_ability(building_line)) @@ -449,9 +450,8 @@ def building_line_to_game_entity(building_line: GenieBuildingLineGroup) -> None: if building_line.is_creatable(): abilities_set.append(AoCAbilitySubprocessor.constructable_ability(building_line)) - if building_line.is_passable() or\ - (isinstance(building_line, GenieStackBuildingGroup) and building_line.is_gate()): - abilities_set.append(AoCAbilitySubprocessor.passable_ability(building_line)) + if not building_line.is_passable(): + abilities_set.append(AoCAbilitySubprocessor.pathable_ability(building_line)) if building_line.has_foundation(): if building_line.get_class_id() == 49: @@ -585,7 +585,7 @@ def ambient_group_to_game_entity(ambient_group: GenieAmbientGroup) -> None: if interaction_mode >= 0: abilities_set.append(AoCAbilitySubprocessor.death_ability(ambient_group)) - abilities_set.append(AoCAbilitySubprocessor.hitbox_ability(ambient_group)) + abilities_set.append(AoCAbilitySubprocessor.collision_ability(ambient_group)) abilities_set.append(AoCAbilitySubprocessor.idle_ability(ambient_group)) abilities_set.append(AoCAbilitySubprocessor.live_ability(ambient_group)) abilities_set.append(AoCAbilitySubprocessor.named_ability(ambient_group)) @@ -596,8 +596,8 @@ def ambient_group_to_game_entity(ambient_group: GenieAmbientGroup) -> None: if interaction_mode >= 2: abilities_set.extend(AoCAbilitySubprocessor.selectable_ability(ambient_group)) - if ambient_group.is_passable(): - abilities_set.append(AoCAbilitySubprocessor.passable_ability(ambient_group)) + if not ambient_group.is_passable(): + abilities_set.append(AoCAbilitySubprocessor.pathable_ability(ambient_group)) if ambient_group.is_harvestable(): abilities_set.append(AoCAbilitySubprocessor.harvestable_ability(ambient_group)) @@ -1033,6 +1033,36 @@ def terrain_group_to_terrain(terrain_group: GenieTerrainGroup) -> None: raw_api_object.add_raw_member("ambience", ambience, "engine.util.terrain.Terrain") + # ======================================================================= + # Path Costs + # ======================================================================= + path_costs = {} + restrictions = dataset.genie_terrain_restrictions + + # Land grid + path_type = dataset.pregen_nyan_objects["util.path.types.Land"].get_nyan_object() + land_restrictions = restrictions[0x07] + if land_restrictions.is_accessible(terrain_index): + path_costs[path_type] = 1 + + else: + path_costs[path_type] = 255 + + # Water grid + path_type = dataset.pregen_nyan_objects["util.path.types.Water"].get_nyan_object() + water_restrictions = restrictions[0x03] + if water_restrictions.is_accessible(terrain_index): + path_costs[path_type] = 1 + + else: + path_costs[path_type] = 255 + + # Air grid (default accessible) + path_type = dataset.pregen_nyan_objects["util.path.types.Air"].get_nyan_object() + path_costs[path_type] = 1 + + raw_api_object.add_raw_member("path_costs", path_costs, "engine.util.terrain.Terrain") + # ======================================================================= # Graphic # ======================================================================= diff --git a/openage/convert/processor/conversion/aoc/pregen_processor.py b/openage/convert/processor/conversion/aoc/pregen_processor.py index fe6151ea20..48eb8b97e7 100644 --- a/openage/convert/processor/conversion/aoc/pregen_processor.py +++ b/openage/convert/processor/conversion/aoc/pregen_processor.py @@ -1,4 +1,4 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. +# Copyright 2020-2024 the openage authors. See copying.md for legal info. # # pylint: disable=too-many-lines,too-many-locals,too-many-statements # @@ -35,6 +35,7 @@ def generate(cls, full_data_set: GenieObjectContainer) -> None: # Stores pregenerated raw API objects as a container pregen_converter_group = ConverterObjectGroup("pregen") + cls.generate_activities(full_data_set, pregen_converter_group) cls.generate_attributes(full_data_set, pregen_converter_group) cls.generate_diplomatic_stances(full_data_set, pregen_converter_group) cls.generate_team_property(full_data_set, pregen_converter_group) @@ -46,6 +47,7 @@ def generate(cls, full_data_set: GenieObjectContainer) -> None: cls.generate_misc_effect_objects(full_data_set, pregen_converter_group) cls.generate_modifiers(full_data_set, pregen_converter_group) cls.generate_terrain_types(full_data_set, pregen_converter_group) + cls.generate_path_types(full_data_set, pregen_converter_group) cls.generate_resources(full_data_set, pregen_converter_group) cls.generate_death_condition(full_data_set, pregen_converter_group) @@ -62,6 +64,297 @@ def generate(cls, full_data_set: GenieObjectContainer) -> None: raise RuntimeError(f"{repr(pregen_object)}: Pregenerated object is not ready " "for export. Member or object not initialized.") + @staticmethod + def generate_activities( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup + ) -> None: + """ + Generate the activities for game entity behaviour. + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + :type full_data_set: ...dataformat.aoc.genie_object_container.GenieObjectContainer + :param pregen_converter_group: GenieObjectGroup instance that stores + pregenerated API objects for referencing with + ForwardRef + :type pregen_converter_group: ...dataformat.aoc.genie_object_container.GenieObjectGroup + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + activity_parent = "engine.util.activity.Activity" + activity_location = "data/util/activity/" + + # Node types + start_parent = "engine.util.activity.node.type.Start" + end_parent = "engine.util.activity.node.type.End" + ability_parent = "engine.util.activity.node.type.Ability" + xor_parent = "engine.util.activity.node.type.XORGate" + xor_event_parent = "engine.util.activity.node.type.XOREventGate" + + # Condition types + condition_parent = "engine.util.activity.condition.Condition" + condition_queue_parent = "engine.util.activity.condition.type.CommandInQueue" + condition_next_move_parent = "engine.util.activity.condition.type.NextCommandMove" + + # ======================================================================= + # Default (Start -> Ability(Idle) -> End) + # ======================================================================= + default_ref_in_modpack = "util.activity.types.Default" + default_raw_api_object = RawAPIObject(default_ref_in_modpack, + "Default", api_objects, + activity_location) + default_raw_api_object.set_filename("types") + default_raw_api_object.add_raw_parent(activity_parent) + + start_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Default.Start") + default_raw_api_object.add_raw_member("start", start_forward_ref, + activity_parent) + + pregen_converter_group.add_raw_api_object(default_raw_api_object) + pregen_nyan_objects.update({default_ref_in_modpack: default_raw_api_object}) + + unit_forward_ref = ForwardRef(pregen_converter_group, default_ref_in_modpack) + + # Start + start_ref_in_modpack = "util.activity.types.Default.Start" + start_raw_api_object = RawAPIObject(start_ref_in_modpack, + "Start", api_objects) + start_raw_api_object.set_location(unit_forward_ref) + start_raw_api_object.add_raw_parent(start_parent) + + idle_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Default.Idle") + start_raw_api_object.add_raw_member("next", idle_forward_ref, + start_parent) + + pregen_converter_group.add_raw_api_object(start_raw_api_object) + pregen_nyan_objects.update({start_ref_in_modpack: start_raw_api_object}) + + # Idle + idle_ref_in_modpack = "util.activity.types.Default.Idle" + idle_raw_api_object = RawAPIObject(idle_ref_in_modpack, + "Idle", api_objects) + idle_raw_api_object.set_location(unit_forward_ref) + idle_raw_api_object.add_raw_parent(ability_parent) + + end_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Default.End") + idle_raw_api_object.add_raw_member("next", end_forward_ref, + ability_parent) + idle_raw_api_object.add_raw_member("ability", + api_objects["engine.ability.type.Idle"], + ability_parent) + + pregen_converter_group.add_raw_api_object(idle_raw_api_object) + pregen_nyan_objects.update({idle_ref_in_modpack: idle_raw_api_object}) + + # End + end_ref_in_modpack = "util.activity.types.Default.End" + end_raw_api_object = RawAPIObject(end_ref_in_modpack, + "End", api_objects) + end_raw_api_object.set_location(unit_forward_ref) + end_raw_api_object.add_raw_parent(end_parent) + + pregen_converter_group.add_raw_api_object(end_raw_api_object) + pregen_nyan_objects.update({end_ref_in_modpack: end_raw_api_object}) + + # ======================================================================= + # Units + # ======================================================================= + unit_ref_in_modpack = "util.activity.types.Unit" + unit_raw_api_object = RawAPIObject(unit_ref_in_modpack, + "Unit", api_objects, + activity_location) + unit_raw_api_object.set_filename("types") + unit_raw_api_object.add_raw_parent(activity_parent) + + start_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.Start") + unit_raw_api_object.add_raw_member("start", start_forward_ref, + activity_parent) + + pregen_converter_group.add_raw_api_object(unit_raw_api_object) + pregen_nyan_objects.update({unit_ref_in_modpack: unit_raw_api_object}) + + unit_forward_ref = ForwardRef(pregen_converter_group, unit_ref_in_modpack) + + # Start + start_ref_in_modpack = "util.activity.types.Unit.Start" + start_raw_api_object = RawAPIObject(start_ref_in_modpack, + "Start", api_objects) + start_raw_api_object.set_location(unit_forward_ref) + start_raw_api_object.add_raw_parent(start_parent) + + idle_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.Idle") + start_raw_api_object.add_raw_member("next", idle_forward_ref, + start_parent) + + pregen_converter_group.add_raw_api_object(start_raw_api_object) + pregen_nyan_objects.update({start_ref_in_modpack: start_raw_api_object}) + + # Idle + idle_ref_in_modpack = "util.activity.types.Unit.Idle" + idle_raw_api_object = RawAPIObject(idle_ref_in_modpack, + "Idle", api_objects) + idle_raw_api_object.set_location(unit_forward_ref) + idle_raw_api_object.add_raw_parent(ability_parent) + + queue_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.CheckQueue") + idle_raw_api_object.add_raw_member("next", queue_forward_ref, + ability_parent) + idle_raw_api_object.add_raw_member("ability", + api_objects["engine.ability.type.Idle"], + ability_parent) + + pregen_converter_group.add_raw_api_object(idle_raw_api_object) + pregen_nyan_objects.update({idle_ref_in_modpack: idle_raw_api_object}) + + # Check if command is in queue + queue_ref_in_modpack = "util.activity.types.Unit.CheckQueue" + queue_raw_api_object = RawAPIObject(queue_ref_in_modpack, + "CheckQueue", api_objects) + queue_raw_api_object.set_location(unit_forward_ref) + queue_raw_api_object.add_raw_parent(xor_parent) + + condition_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.CommandInQueue") + queue_raw_api_object.add_raw_member("next", + [condition_forward_ref], + xor_parent) + command_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.WaitForCommand") + queue_raw_api_object.add_raw_member("default", + command_forward_ref, + xor_parent) + + pregen_converter_group.add_raw_api_object(queue_raw_api_object) + pregen_nyan_objects.update({queue_ref_in_modpack: queue_raw_api_object}) + + # condition for command in queue + condition_ref_in_modpack = "util.activity.types.Unit.CommandInQueue" + condition_raw_api_object = RawAPIObject(condition_ref_in_modpack, + "CommandInQueue", api_objects) + condition_raw_api_object.set_location(queue_forward_ref) + condition_raw_api_object.add_raw_parent(condition_queue_parent) + + branch_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.BranchCommand") + condition_raw_api_object.add_raw_member("next", + branch_forward_ref, + condition_parent) + + pregen_converter_group.add_raw_api_object(condition_raw_api_object) + pregen_nyan_objects.update({condition_ref_in_modpack: condition_raw_api_object}) + + # Wait for Command + command_ref_in_modpack = "util.activity.types.Unit.WaitForCommand" + command_raw_api_object = RawAPIObject(command_ref_in_modpack, + "WaitForCommand", api_objects) + command_raw_api_object.set_location(unit_forward_ref) + command_raw_api_object.add_raw_parent(xor_event_parent) + + event_api_object = api_objects["engine.util.activity.event.type.CommandInQueue"] + branch_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.BranchCommand") + command_raw_api_object.add_raw_member("next", + {event_api_object: branch_forward_ref}, + xor_event_parent) + + pregen_converter_group.add_raw_api_object(command_raw_api_object) + pregen_nyan_objects.update({command_ref_in_modpack: command_raw_api_object}) + + # Branch on command type + branch_ref_in_modpack = "util.activity.types.Unit.BranchCommand" + branch_raw_api_object = RawAPIObject(branch_ref_in_modpack, + "BranchCommand", api_objects) + branch_raw_api_object.set_location(unit_forward_ref) + branch_raw_api_object.add_raw_parent(xor_parent) + + condition_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.NextCommandMove") + branch_raw_api_object.add_raw_member("next", + [condition_forward_ref], + xor_parent) + idle_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.Idle") + branch_raw_api_object.add_raw_member("default", + idle_forward_ref, + xor_parent) + + pregen_converter_group.add_raw_api_object(branch_raw_api_object) + pregen_nyan_objects.update({branch_ref_in_modpack: branch_raw_api_object}) + + # condition for branching to move + condition_ref_in_modpack = "util.activity.types.Unit.NextCommandMove" + condition_raw_api_object = RawAPIObject(condition_ref_in_modpack, + "NextCommandMove", api_objects) + condition_raw_api_object.set_location(branch_forward_ref) + condition_raw_api_object.add_raw_parent(condition_next_move_parent) + + move_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.Move") + condition_raw_api_object.add_raw_member("next", + move_forward_ref, + condition_parent) + + pregen_converter_group.add_raw_api_object(condition_raw_api_object) + pregen_nyan_objects.update({condition_ref_in_modpack: condition_raw_api_object}) + + # Move + move_ref_in_modpack = "util.activity.types.Unit.Move" + move_raw_api_object = RawAPIObject(move_ref_in_modpack, + "Move", api_objects) + move_raw_api_object.set_location(unit_forward_ref) + move_raw_api_object.add_raw_parent(ability_parent) + + wait_forward_ref = ForwardRef(pregen_converter_group, + "util.activity.types.Unit.Wait") + move_raw_api_object.add_raw_member("next", wait_forward_ref, + ability_parent) + move_raw_api_object.add_raw_member("ability", + api_objects["engine.ability.type.Move"], + ability_parent) + + pregen_converter_group.add_raw_api_object(move_raw_api_object) + pregen_nyan_objects.update({move_ref_in_modpack: move_raw_api_object}) + + # Wait (for Move or Command) + wait_ref_in_modpack = "util.activity.types.Unit.Wait" + wait_raw_api_object = RawAPIObject(wait_ref_in_modpack, + "Wait", api_objects) + wait_raw_api_object.set_location(unit_forward_ref) + wait_raw_api_object.add_raw_parent(xor_event_parent) + + wait_finish = api_objects["engine.util.activity.event.type.WaitAbility"] + wait_command = api_objects["engine.util.activity.event.type.CommandInQueue"] + wait_raw_api_object.add_raw_member("next", + { + wait_finish: idle_forward_ref, + # TODO: don't go back to move, go to xor gate that + # branches depending on command + wait_command: branch_forward_ref + }, + xor_event_parent) + + pregen_converter_group.add_raw_api_object(wait_raw_api_object) + pregen_nyan_objects.update({wait_ref_in_modpack: wait_raw_api_object}) + + # End + end_ref_in_modpack = "util.activity.types.Unit.End" + end_raw_api_object = RawAPIObject(end_ref_in_modpack, + "End", api_objects) + end_raw_api_object.set_location(unit_forward_ref) + end_raw_api_object.add_raw_parent(end_parent) + + pregen_converter_group.add_raw_api_object(end_raw_api_object) + pregen_nyan_objects.update({end_ref_in_modpack: end_raw_api_object}) + @staticmethod def generate_attributes( full_data_set: GenieObjectContainer, @@ -1627,6 +1920,71 @@ def generate_terrain_types( pregen_converter_group.add_raw_api_object(type_raw_api_object) pregen_nyan_objects.update({type_ref_in_modpack: type_raw_api_object}) + @staticmethod + def generate_path_types( + full_data_set: GenieObjectContainer, + pregen_converter_group: ConverterObjectGroup + ) -> None: + """ + Generate PathType objects. + + :param full_data_set: GenieObjectContainer instance that + contains all relevant data for the conversion + process. + :type full_data_set: ...dataformat.aoc.genie_object_container.GenieObjectContainer + :param pregen_converter_group: GenieObjectGroup instance that stores + pregenerated API objects for referencing with + ForwardRef + :type pregen_converter_group: ...dataformat.aoc.genie_object_container.GenieObjectGroup + """ + pregen_nyan_objects = full_data_set.pregen_nyan_objects + api_objects = full_data_set.nyan_api_objects + + path_type_parent = "engine.util.path_type.PathType" + path_types_location = "data/util/path_type/" + + # ======================================================================= + # Land + # ======================================================================= + path_type_ref_in_modpack = "util.path.types.Land" + path_type_raw_api_object = RawAPIObject(path_type_ref_in_modpack, + "Land", + api_objects, + path_types_location) + path_type_raw_api_object.set_filename("types") + path_type_raw_api_object.add_raw_parent(path_type_parent) + + pregen_converter_group.add_raw_api_object(path_type_raw_api_object) + pregen_nyan_objects.update({path_type_ref_in_modpack: path_type_raw_api_object}) + + # ======================================================================= + # Water + # ======================================================================= + path_type_ref_in_modpack = "util.path.types.Water" + path_type_raw_api_object = RawAPIObject(path_type_ref_in_modpack, + "Water", + api_objects, + path_types_location) + path_type_raw_api_object.set_filename("types") + path_type_raw_api_object.add_raw_parent(path_type_parent) + + pregen_converter_group.add_raw_api_object(path_type_raw_api_object) + pregen_nyan_objects.update({path_type_ref_in_modpack: path_type_raw_api_object}) + + # ======================================================================= + # Air + # ======================================================================= + path_type_ref_in_modpack = "util.path.types.Air" + path_type_raw_api_object = RawAPIObject(path_type_ref_in_modpack, + "Air", + api_objects, + path_types_location) + path_type_raw_api_object.set_filename("types") + path_type_raw_api_object.add_raw_parent(path_type_parent) + + pregen_converter_group.add_raw_api_object(path_type_raw_api_object) + pregen_nyan_objects.update({path_type_ref_in_modpack: path_type_raw_api_object}) + @staticmethod def generate_resources( full_data_set: GenieObjectContainer, diff --git a/openage/convert/processor/conversion/aoc/processor.py b/openage/convert/processor/conversion/aoc/processor.py index 6f03cc3421..d042e32065 100644 --- a/openage/convert/processor/conversion/aoc/processor.py +++ b/openage/convert/processor/conversion/aoc/processor.py @@ -1,4 +1,4 @@ -# Copyright 2019-2023 the openage authors. See copying.md for legal info. +# Copyright 2019-2024 the openage authors. See copying.md for legal info. # # pylint: disable=too-many-lines,too-many-branches,too-many-statements # pylint: disable=too-many-locals,too-many-public-methods @@ -25,8 +25,8 @@ from ....entity_object.conversion.aoc.genie_tech import GenieTechObject from ....entity_object.conversion.aoc.genie_tech import StatUpgrade, InitiatedTech, \ BuildingUnlock -from ....entity_object.conversion.aoc.genie_terrain import GenieTerrainGroup -from ....entity_object.conversion.aoc.genie_terrain import GenieTerrainObject +from ....entity_object.conversion.aoc.genie_terrain import GenieTerrainGroup, \ + GenieTerrainObject, GenieTerrainRestriction from ....entity_object.conversion.aoc.genie_unit import GenieAmbientGroup, \ GenieGarrisonMode from ....entity_object.conversion.aoc.genie_unit import GenieStackBuildingGroup, \ @@ -134,6 +134,7 @@ def _pre_processor( cls.extract_genie_graphics(gamespec, dataset) cls.extract_genie_sounds(gamespec, dataset) cls.extract_genie_terrains(gamespec, dataset) + cls.extract_genie_restrictions(gamespec, dataset) return dataset @@ -478,15 +479,35 @@ def extract_genie_terrains(gamespec: ArrayMember, full_data_set: GenieObjectCont # call hierarchy: wrapper[0]->terrains raw_terrains = gamespec[0]["terrains"].value - index = 0 - for raw_terrain in raw_terrains: + for index, raw_terrain in enumerate(raw_terrains): terrain_index = index terrain_members = raw_terrain.value terrain = GenieTerrainObject(terrain_index, full_data_set, members=terrain_members) full_data_set.genie_terrains.update({terrain.get_id(): terrain}) - index += 1 + @staticmethod + def extract_genie_restrictions( + gamespec: ArrayMember, + full_data_set: GenieObjectContainer + ) -> None: + """ + Extract terrain restrictions from the game data. + + :param gamespec: Gamedata from empires.dat file. + :type gamespec: class: ...dataformat.value_members.ArrayMember + """ + # call hierarchy: wrapper[0]->terrains + raw_restrictions = gamespec[0]["terrain_restrictions"].value + + for index, raw_restriction in enumerate(raw_restrictions): + restriction_index = index + restriction_members = raw_restriction.value + + restriction = GenieTerrainRestriction(restriction_index, + full_data_set, + members=restriction_members) + full_data_set.genie_terrain_restrictions.update({restriction.get_id(): restriction}) @staticmethod def create_unit_lines(full_data_set: GenieObjectContainer) -> None: @@ -1340,10 +1361,10 @@ def link_trade_posts(full_data_set: GenieObjectContainer) -> None: continue trade_post_id = command["unit_id"].value - break - # Notify buiding - full_data_set.building_lines[trade_post_id].add_trading_line(unit_line) + # Notify buiding + if trade_post_id in full_data_set.building_lines.keys(): + full_data_set.building_lines[trade_post_id].add_trading_line(unit_line) @staticmethod def link_repairables(full_data_set: GenieObjectContainer) -> None: diff --git a/openage/convert/processor/conversion/aoc_demo/modpack_subprocessor.py b/openage/convert/processor/conversion/aoc_demo/modpack_subprocessor.py index ffd171214e..0759ed9127 100644 --- a/openage/convert/processor/conversion/aoc_demo/modpack_subprocessor.py +++ b/openage/convert/processor/conversion/aoc_demo/modpack_subprocessor.py @@ -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. # # pylint: disable=too-few-public-methods @@ -40,7 +40,10 @@ def _get_demo_base(cls, full_data_set: GenieObjectContainer) -> Modpack: mod_def = modpack.get_info() - mod_def.set_info("trial_base", "0.5", versionstr="Trial", repo="openage") + targetmod_info = full_data_set.game_version.edition.target_modpacks["trial_base"] + version = targetmod_info["version"] + versionstr = targetmod_info["versionstr"] + mod_def.set_info("trial_base", version, versionstr=versionstr, repo="openage") mod_def.add_include("data/**") diff --git a/openage/convert/processor/conversion/de1/modpack_subprocessor.py b/openage/convert/processor/conversion/de1/modpack_subprocessor.py index 9cc3f5a95d..55c143ee83 100644 --- a/openage/convert/processor/conversion/de1/modpack_subprocessor.py +++ b/openage/convert/processor/conversion/de1/modpack_subprocessor.py @@ -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. # # pylint: disable=too-few-public-methods @@ -40,7 +40,10 @@ def _get_aoe1_base(cls, full_data_set: GenieObjectContainer) -> Modpack: mod_def = modpack.get_info() - mod_def.set_info("de1_base", "0.5", versionstr="1.0a", repo="openage") + targetmod_info = full_data_set.game_version.edition.target_modpacks["de1_base"] + version = targetmod_info["version"] + versionstr = targetmod_info["versionstr"] + mod_def.set_info("de1_base", version, versionstr=versionstr, repo="openage") mod_def.add_include("data/**") diff --git a/openage/convert/processor/conversion/de1/processor.py b/openage/convert/processor/conversion/de1/processor.py index c505c68467..cb3f54cc91 100644 --- a/openage/convert/processor/conversion/de1/processor.py +++ b/openage/convert/processor/conversion/de1/processor.py @@ -1,4 +1,4 @@ -# Copyright 2021-2023 the openage authors. See copying.md for legal info. +# Copyright 2021-2024 the openage authors. See copying.md for legal info. """ Convert data from DE1 to openage formats. @@ -107,6 +107,7 @@ def _pre_processor( cls.extract_genie_graphics(gamespec, dataset) RoRProcessor.extract_genie_sounds(gamespec, dataset) AoCProcessor.extract_genie_terrains(gamespec, dataset) + AoCProcessor.extract_genie_restrictions(gamespec, dataset) return dataset diff --git a/openage/convert/processor/conversion/de2/modpack_subprocessor.py b/openage/convert/processor/conversion/de2/modpack_subprocessor.py index 22d9d220e2..ab09c233a6 100644 --- a/openage/convert/processor/conversion/de2/modpack_subprocessor.py +++ b/openage/convert/processor/conversion/de2/modpack_subprocessor.py @@ -1,4 +1,4 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. +# Copyright 2020-2024 the openage authors. See copying.md for legal info. # # pylint: disable=too-few-public-methods @@ -40,7 +40,10 @@ def _get_aoe2_base(cls, full_data_set: GenieObjectContainer) -> Modpack: mod_def = modpack.get_info() - mod_def.set_info("de2_base", "0.5", versionstr="1.0c", repo="openage") + targetmod_info = full_data_set.game_version.edition.target_modpacks["de2_base"] + version = targetmod_info["version"] + versionstr = targetmod_info["versionstr"] + mod_def.set_info("de2_base", version, versionstr=versionstr, repo="openage") mod_def.add_include("data/**") diff --git a/openage/convert/processor/conversion/de2/nyan_subprocessor.py b/openage/convert/processor/conversion/de2/nyan_subprocessor.py index b8f40000ef..b688cc3e7c 100644 --- a/openage/convert/processor/conversion/de2/nyan_subprocessor.py +++ b/openage/convert/processor/conversion/de2/nyan_subprocessor.py @@ -1,4 +1,4 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. +# Copyright 2020-2024 the openage authors. See copying.md for legal info. # # pylint: disable=too-many-lines,too-many-locals,too-many-statements,too-many-branches # @@ -13,7 +13,7 @@ from ....entity_object.conversion.aoc.genie_tech import UnitLineUpgrade from ....entity_object.conversion.aoc.genie_unit import GenieVillagerGroup, \ - GenieGarrisonMode, GenieMonkGroup, GenieStackBuildingGroup + GenieGarrisonMode, GenieMonkGroup from ....entity_object.conversion.combined_terrain import CombinedTerrain from ....entity_object.conversion.converter_object import RawAPIObject from ....service.conversion import internal_name_lookups @@ -219,11 +219,12 @@ def unit_line_to_game_entity(unit_line: GenieUnitLineGroup) -> None: # ======================================================================= abilities_set = [] + abilities_set.append(AoCAbilitySubprocessor.activity_ability(unit_line)) abilities_set.append(AoCAbilitySubprocessor.death_ability(unit_line)) abilities_set.append(AoCAbilitySubprocessor.delete_ability(unit_line)) abilities_set.append(AoCAbilitySubprocessor.despawn_ability(unit_line)) abilities_set.append(AoCAbilitySubprocessor.idle_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.hitbox_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.collision_ability(unit_line)) abilities_set.append(AoCAbilitySubprocessor.live_ability(unit_line)) abilities_set.append(AoCAbilitySubprocessor.los_ability(unit_line)) abilities_set.append(AoCAbilitySubprocessor.move_ability(unit_line)) @@ -435,7 +436,7 @@ def building_line_to_game_entity(building_line: GenieBuildingLineGroup) -> None: abilities_set.append(AoCAbilitySubprocessor.delete_ability(building_line)) abilities_set.append(AoCAbilitySubprocessor.despawn_ability(building_line)) abilities_set.append(AoCAbilitySubprocessor.idle_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.hitbox_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.collision_ability(building_line)) abilities_set.append(AoCAbilitySubprocessor.live_ability(building_line)) abilities_set.append(AoCAbilitySubprocessor.los_ability(building_line)) abilities_set.append(AoCAbilitySubprocessor.named_ability(building_line)) @@ -445,13 +446,15 @@ def building_line_to_game_entity(building_line: GenieBuildingLineGroup) -> None: abilities_set.append(AoCAbilitySubprocessor.terrain_requirement_ability(building_line)) abilities_set.append(AoCAbilitySubprocessor.visibility_ability(building_line)) + if building_line.get_head_unit()["speed"].value > 0: + abilities_set.append(AoCAbilitySubprocessor.move_ability(building_line)) + # Config abilities if building_line.is_creatable(): abilities_set.append(AoCAbilitySubprocessor.constructable_ability(building_line)) - if building_line.is_passable() or\ - (isinstance(building_line, GenieStackBuildingGroup) and building_line.is_gate()): - abilities_set.append(AoCAbilitySubprocessor.passable_ability(building_line)) + if not building_line.is_passable(): + abilities_set.append(AoCAbilitySubprocessor.pathable_ability(building_line)) if building_line.has_foundation(): if building_line.get_class_id() == 49: @@ -783,6 +786,36 @@ def terrain_group_to_terrain(terrain_group: GenieTerrainGroup) -> None: raw_api_object.add_raw_member("ambience", ambience, "engine.util.terrain.Terrain") + # ======================================================================= + # Path Costs + # ======================================================================= + path_costs = {} + restrictions = dataset.genie_terrain_restrictions + + # Land grid + path_type = dataset.pregen_nyan_objects["util.path.types.Land"].get_nyan_object() + land_restrictions = restrictions[0x07] + if land_restrictions.is_accessible(terrain_index): + path_costs[path_type] = 1 + + else: + path_costs[path_type] = 255 + + # Water grid + path_type = dataset.pregen_nyan_objects["util.path.types.Water"].get_nyan_object() + water_restrictions = restrictions[0x03] + if water_restrictions.is_accessible(terrain_index): + path_costs[path_type] = 1 + + else: + path_costs[path_type] = 255 + + # Air grid (default accessible) + path_type = dataset.pregen_nyan_objects["util.path.types.Air"].get_nyan_object() + path_costs[path_type] = 1 + + raw_api_object.add_raw_member("path_costs", path_costs, "engine.util.terrain.Terrain") + # ======================================================================= # Graphic # ======================================================================= diff --git a/openage/convert/processor/conversion/de2/processor.py b/openage/convert/processor/conversion/de2/processor.py index eea3ebd346..19001b9087 100644 --- a/openage/convert/processor/conversion/de2/processor.py +++ b/openage/convert/processor/conversion/de2/processor.py @@ -1,4 +1,4 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. +# Copyright 2020-2024 the openage authors. See copying.md for legal info. # # pylint: disable=line-too-long,too-many-lines,too-many-branches,too-many-statements """ @@ -113,6 +113,7 @@ def _pre_processor( cls.extract_genie_graphics(gamespec, dataset) AoCProcessor.extract_genie_sounds(gamespec, dataset) AoCProcessor.extract_genie_terrains(gamespec, dataset) + AoCProcessor.extract_genie_restrictions(gamespec, dataset) return dataset @@ -311,7 +312,10 @@ def create_extra_building_lines(full_data_set: GenieObjectContainer) -> None: process. :type full_data_set: class: ...dataformat.aoc.genie_object_container.GenieObjectContainer """ - extra_units = (1734,) # Folwark + extra_units = ( + 1734, # Folwark + 1808, # Mule Cart + ) for unit_id in extra_units: building_line = GenieBuildingLineGroup(unit_id, full_data_set) diff --git a/openage/convert/processor/conversion/de2/tech_subprocessor.py b/openage/convert/processor/conversion/de2/tech_subprocessor.py index 2f2aaf9e76..7bce1d1163 100644 --- a/openage/convert/processor/conversion/de2/tech_subprocessor.py +++ b/openage/convert/processor/conversion/de2/tech_subprocessor.py @@ -1,4 +1,4 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. +# Copyright 2020-2024 the openage authors. See copying.md for legal info. # # pylint: disable=too-many-locals,too-many-branches @@ -63,11 +63,14 @@ class DE2TechSubprocessor: 57: AoCUpgradeAttributeSubprocessor.kidnap_storage_upgrade, 30: DE2UpgradeAttributeSubprocessor.herdable_capacity_upgrade, + 51: DE2UpgradeAttributeSubprocessor.bfg_unknown_51_upgrade, 59: DE2UpgradeAttributeSubprocessor.charge_attack_upgrade, 60: DE2UpgradeAttributeSubprocessor.charge_regen_upgrade, 61: DE2UpgradeAttributeSubprocessor.charge_event_upgrade, 62: DE2UpgradeAttributeSubprocessor.charge_type_upgrade, 63: AoCUpgradeAttributeSubprocessor.ignore_armor_upgrade, + 71: DE2UpgradeAttributeSubprocessor.bfg_unknown_71_upgrade, + 73: DE2UpgradeAttributeSubprocessor.bfg_unknown_73_upgrade, 100: AoCUpgradeAttributeSubprocessor.resource_cost_upgrade, 101: AoCUpgradeAttributeSubprocessor.creation_time_upgrade, 102: AoCUpgradeAttributeSubprocessor.min_projectiles_upgrade, @@ -99,6 +102,7 @@ class DE2TechSubprocessor: 46: AoCUpgradeResourceSubprocessor.tribute_inefficiency_upgrade, 47: AoCUpgradeResourceSubprocessor.gather_gold_efficiency_upgrade, 50: AoCUpgradeResourceSubprocessor.reveal_ally_upgrade, + 69: DE2UpgradeResourceSubprocessor.bfg_unknown_69_upgrade, 77: AoCUpgradeResourceSubprocessor.conversion_resistance_upgrade, 78: AoCUpgradeResourceSubprocessor.trade_penalty_upgrade, 79: AoCUpgradeResourceSubprocessor.gather_stone_efficiency_upgrade, @@ -137,6 +141,7 @@ class DE2TechSubprocessor: 214: DE2UpgradeResourceSubprocessor.free_kipchaks_upgrade, 216: DE2UpgradeResourceSubprocessor.sheep_food_amount_upgrade, 218: DE2UpgradeResourceSubprocessor.cuman_tc_upgrade, + 219: DE2UpgradeResourceSubprocessor.bfg_unknown_219_upgrade, 220: DE2UpgradeResourceSubprocessor.relic_food_production_upgrade, 234: DE2UpgradeResourceSubprocessor.first_crusade_upgrade, 236: DE2UpgradeResourceSubprocessor.burgundian_vineyards_upgrade, @@ -155,7 +160,15 @@ class DE2TechSubprocessor: 267: DE2UpgradeResourceSubprocessor.forager_wood_gather_upgrade, 268: DE2UpgradeResourceSubprocessor.resource_decay_upgrade, 269: DE2UpgradeResourceSubprocessor.tech_reward_upgrade, + 272: DE2UpgradeResourceSubprocessor.cliff_defense_upgrade, + 273: DE2UpgradeResourceSubprocessor.elevation_defense_upgrade, 274: DE2UpgradeResourceSubprocessor.chieftains_upgrade, + 280: DE2UpgradeResourceSubprocessor.conversion_range_upgrade, + 282: DE2UpgradeResourceSubprocessor.unknown_recharge_rate_upgrade, + 502: DE2UpgradeResourceSubprocessor.bfg_unknown_502_upgrade, + 507: DE2UpgradeResourceSubprocessor.bfg_unknown_507_upgrade, + 521: DE2UpgradeResourceSubprocessor.bfg_unknown_521_upgrade, + 551: DE2UpgradeResourceSubprocessor.bfg_unknown_551_upgrade, } @classmethod diff --git a/openage/convert/processor/conversion/de2/upgrade_attribute_subprocessor.py b/openage/convert/processor/conversion/de2/upgrade_attribute_subprocessor.py index f60ee8eb64..60a5a4f6ac 100644 --- a/openage/convert/processor/conversion/de2/upgrade_attribute_subprocessor.py +++ b/openage/convert/processor/conversion/de2/upgrade_attribute_subprocessor.py @@ -1,4 +1,4 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. +# Copyright 2020-2024 the openage authors. See copying.md for legal info. # # pylint: disable=too-few-public-methods # @@ -24,6 +24,90 @@ class DE2UpgradeAttributeSubprocessor: Creates raw API objects for attribute upgrade effects in DE2. """ + @staticmethod + def bfg_unknown_51_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False + ) -> list[ForwardRef]: + """ + Creates a patch for a BfG unknown attribute effect (ID: 51). + + :param converter_group: Tech/Civ that gets the patch. + :type converter_group: ...dataformat.converter_object.ConverterObjectGroup + :param line: Unit/Building line that has the ability. + :type line: ...dataformat.converter_object.ConverterObjectGroup + :param value: Value used for patching the member. + :type value: int, float + :param operator: Operator used for patching the member. + :type operator: MemberOperator + :returns: The forward references for the generated patches. + :rtype: list + """ + patches = [] + + # TODO: Implement + + return patches + + @staticmethod + def bfg_unknown_71_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False + ) -> list[ForwardRef]: + """ + Creates a patch for a BfG unknown attribute effect (ID: 71). + + :param converter_group: Tech/Civ that gets the patch. + :type converter_group: ...dataformat.converter_object.ConverterObjectGroup + :param line: Unit/Building line that has the ability. + :type line: ...dataformat.converter_object.ConverterObjectGroup + :param value: Value used for patching the member. + :type value: int, float + :param operator: Operator used for patching the member. + :type operator: MemberOperator + :returns: The forward references for the generated patches. + :rtype: list + """ + patches = [] + + # TODO: Implement + + return patches + + @staticmethod + def bfg_unknown_73_upgrade( + converter_group: ConverterObjectGroup, + line: GenieGameEntityGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False + ) -> list[ForwardRef]: + """ + Creates a patch for a BfG unknown attribute effect (ID: 73). + + :param converter_group: Tech/Civ that gets the patch. + :type converter_group: ...dataformat.converter_object.ConverterObjectGroup + :param line: Unit/Building line that has the ability. + :type line: ...dataformat.converter_object.ConverterObjectGroup + :param value: Value used for patching the member. + :type value: int, float + :param operator: Operator used for patching the member. + :type operator: MemberOperator + :returns: The forward references for the generated patches. + :rtype: list + """ + patches = [] + + # TODO: Implement + + return patches + @staticmethod def charge_attack_upgrade( converter_group: ConverterObjectGroup, diff --git a/openage/convert/processor/conversion/de2/upgrade_resource_subprocessor.py b/openage/convert/processor/conversion/de2/upgrade_resource_subprocessor.py index a394061fa1..9ccd9a68f2 100644 --- a/openage/convert/processor/conversion/de2/upgrade_resource_subprocessor.py +++ b/openage/convert/processor/conversion/de2/upgrade_resource_subprocessor.py @@ -1,4 +1,4 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. +# Copyright 2020-2024 the openage authors. See copying.md for legal info. # # pylint: disable=too-many-locals,too-many-lines,too-many-statements,too-many-public-methods,invalid-name # @@ -48,6 +48,156 @@ def bengali_conversion_resistance_upgrade( return patches + @staticmethod + def bfg_unknown_69_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False + ) -> list[ForwardRef]: + """ + Creates a patch for a BfG unknown resource effect (ID: 69). + + :param converter_group: Tech/Civ that gets the patch. + :type converter_group: ...dataformat.converter_object.ConverterObjectGroup + :param value: Value used for patching the member. + :type value: MemberOperator + :param operator: Operator used for patching the member. + :type operator: MemberOperator + :returns: The forward references for the generated patches. + :rtype: list + """ + patches = [] + + # TODO: Implement + + return patches + + @staticmethod + def bfg_unknown_219_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False + ) -> list[ForwardRef]: + """ + Creates a patch for a BfG unknown resource effect (ID: 219). + + :param converter_group: Tech/Civ that gets the patch. + :type converter_group: ...dataformat.converter_object.ConverterObjectGroup + :param value: Value used for patching the member. + :type value: MemberOperator + :param operator: Operator used for patching the member. + :type operator: MemberOperator + :returns: The forward references for the generated patches. + :rtype: list + """ + patches = [] + + # TODO: Implement + + return patches + + @staticmethod + def bfg_unknown_502_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False + ) -> list[ForwardRef]: + """ + Creates a patch for a BfG unknown resource effect (ID: 502). + + :param converter_group: Tech/Civ that gets the patch. + :type converter_group: ...dataformat.converter_object.ConverterObjectGroup + :param value: Value used for patching the member. + :type value: MemberOperator + :param operator: Operator used for patching the member. + :type operator: MemberOperator + :returns: The forward references for the generated patches. + :rtype: list + """ + patches = [] + + # TODO: Implement + + return patches + + @staticmethod + def bfg_unknown_507_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False + ) -> list[ForwardRef]: + """ + Creates a patch for a BfG unknown resource effect (ID: 507). + + :param converter_group: Tech/Civ that gets the patch. + :type converter_group: ...dataformat.converter_object.ConverterObjectGroup + :param value: Value used for patching the member. + :type value: MemberOperator + :param operator: Operator used for patching the member. + :type operator: MemberOperator + :returns: The forward references for the generated patches. + :rtype: list + """ + patches = [] + + # TODO: Implement + + return patches + + @staticmethod + def bfg_unknown_521_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False + ) -> list[ForwardRef]: + """ + Creates a patch for a BfG unknown resource effect (ID: 521). + + :param converter_group: Tech/Civ that gets the patch. + :type converter_group: ...dataformat.converter_object.ConverterObjectGroup + :param value: Value used for patching the member. + :type value: MemberOperator + :param operator: Operator used for patching the member. + :type operator: MemberOperator + :returns: The forward references for the generated patches. + :rtype: list + """ + patches = [] + + # TODO: Implement + + return patches + + @staticmethod + def bfg_unknown_551_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False + ) -> list[ForwardRef]: + """ + Creates a patch for a BfG unknown resource effect (ID: 551). + + :param converter_group: Tech/Civ that gets the patch. + :type converter_group: ...dataformat.converter_object.ConverterObjectGroup + :param value: Value used for patching the member. + :type value: MemberOperator + :param operator: Operator used for patching the member. + :type operator: MemberOperator + :returns: The forward references for the generated patches. + :rtype: list + """ + patches = [] + + # TODO: Implement + + return patches + @staticmethod def burgundian_vineyards_upgrade( converter_group: ConverterObjectGroup, @@ -123,6 +273,31 @@ def cliff_attack_upgrade( return patches + @staticmethod + def cliff_defense_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False + ) -> list[ForwardRef]: + """ + Creates a patch for the cliff defense multiplier effect (ID: 272). + + :param converter_group: Tech/Civ that gets the patch. + :type converter_group: ...dataformat.converter_object.ConverterObjectGroup + :param value: Value used for patching the member. + :type value: MemberOperator + :param operator: Operator used for patching the member. + :type operator: MemberOperator + :returns: The forward references for the generated patches. + :rtype: list + """ + patches = [] + + # TODO: Implement + + return patches + @staticmethod def conversion_min_adjustment_upgrade( converter_group: ConverterObjectGroup, @@ -258,6 +433,31 @@ def conversion_building_chance_upgrade( return patches + @staticmethod + def conversion_range_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False + ) -> list[ForwardRef]: + """ + Creates a patch for the conversion range modifer (ID: 280). + + :param converter_group: Tech/Civ that gets the patch. + :type converter_group: ...dataformat.converter_object.ConverterObjectGroup + :param value: Value used for patching the member. + :type value: MemberOperator + :param operator: Operator used for patching the member. + :type operator: MemberOperator + :returns: The forward references for the generated patches. + :rtype: list + """ + patches = [] + + # TODO: Implement + + return patches + @staticmethod def cuman_tc_upgrade( converter_group: ConverterObjectGroup, @@ -441,6 +641,31 @@ def elevation_attack_upgrade( return patches + @staticmethod + def elevation_defense_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False + ) -> list[ForwardRef]: + """ + Creates a patch for the elevation defense multiplier effect (ID: 273). + + :param converter_group: Tech/Civ that gets the patch. + :type converter_group: ...dataformat.converter_object.ConverterObjectGroup + :param value: Value used for patching the member. + :type value: MemberOperator + :param operator: Operator used for patching the member. + :type operator: MemberOperator + :returns: The forward references for the generated patches. + :rtype: list + """ + patches = [] + + # TODO: Implement + + return patches + @staticmethod def feitoria_gold_upgrade( converter_group: ConverterObjectGroup, @@ -851,6 +1076,31 @@ def trade_food_bonus_upgrade( return patches + @staticmethod + def unknown_recharge_rate_upgrade( + converter_group: ConverterObjectGroup, + value: typing.Union[int, float], + operator: MemberOperator, + team: bool = False + ) -> list[ForwardRef]: + """ + Creates a patch for the unknown recharge rate bonus effect (ID: 282). + + :param converter_group: Tech/Civ that gets the patch. + :type converter_group: ...dataformat.converter_object.ConverterObjectGroup + :param value: Value used for patching the member. + :type value: MemberOperator + :param operator: Operator used for patching the member. + :type operator: MemberOperator + :returns: The forward references for the generated patches. + :rtype: list + """ + patches = [] + + # TODO: Implement + + return patches + @staticmethod def workshop_food_gen_upgrade( converter_group: ConverterObjectGroup, diff --git a/openage/convert/processor/conversion/hd/media_subprocessor.py b/openage/convert/processor/conversion/hd/media_subprocessor.py index 935643efa0..62dc1dd719 100644 --- a/openage/convert/processor/conversion/hd/media_subprocessor.py +++ b/openage/convert/processor/conversion/hd/media_subprocessor.py @@ -1,6 +1,6 @@ # Copyright 2021-2023 the openage authors. See copying.md for legal info. # -# pylint: disable=too-many-locals +# pylint: disable=too-many-locals,too-many-statements """ Convert media information to metadata definitions and export @@ -9,9 +9,12 @@ from __future__ import annotations import typing -from ....entity_object.export.formats.sprite_metadata import LayerMode +from ....entity_object.export.formats.sprite_metadata import LayerMode as SpriteLayerMode +from ....entity_object.export.formats.terrain_metadata import LayerMode as TerrainLayerMode from ....entity_object.export.media_export_request import MediaExportRequest -from ....entity_object.export.metadata_export import SpriteMetadataExport, TextureMetadataExport +from ....entity_object.export.metadata_export import SpriteMetadataExport +from ....entity_object.export.metadata_export import TextureMetadataExport +from ....entity_object.export.metadata_export import TerrainMetadataExport from ....value_object.read.media_types import MediaType if typing.TYPE_CHECKING: @@ -77,13 +80,13 @@ def create_graphics_requests(full_data_set: GenieObjectContainer) -> None: # Add metadata from graphics to animation metadata sequence_type = graphic["sequence_type"].value if sequence_type == 0x00: - layer_mode = LayerMode.OFF + layer_mode = SpriteLayerMode.OFF elif sequence_type & 0x08: - layer_mode = LayerMode.ONCE + layer_mode = SpriteLayerMode.ONCE else: - layer_mode = LayerMode.LOOP + layer_mode = SpriteLayerMode.LOOP layer_pos = graphic["layer"].value frame_rate = round(graphic["frame_rate"].value, ndigits=6) @@ -128,6 +131,44 @@ def create_graphics_requests(full_data_set: GenieObjectContainer) -> None: target_filename) full_data_set.graphics_exports.update({slp_id: export_request}) + texture_meta_filename = f"{texture.get_filename()}.texture" + texture_meta_export = TextureMetadataExport(targetdir, + texture_meta_filename) + full_data_set.metadata_exports.append(texture_meta_export) + + # Add texture image filename to texture metadata + texture_meta_export.add_imagefile(target_filename) + texture_meta_export.update( + None, + { + f"{target_filename}": { + "size": (512, 512), + "subtex_metadata": [ + { + "x": 0, + "y": 0, + "w": 512, + "h": 512, + "cx": 0, + "cy": 0, + } + ] + }} + ) + + terrain_meta_filename = f"{texture.get_filename()}.terrain" + terrain_meta_export = TerrainMetadataExport(targetdir, + terrain_meta_filename) + full_data_set.metadata_exports.append(terrain_meta_export) + + terrain_meta_export.add_graphics_metadata(target_filename, + texture_meta_filename, + TerrainLayerMode.OFF, + 0, + 0.0, + 0.0, + 1) + @staticmethod def create_sound_requests(full_data_set: GenieObjectContainer) -> None: """ diff --git a/openage/convert/processor/conversion/hd/modpack_subprocessor.py b/openage/convert/processor/conversion/hd/modpack_subprocessor.py index 50e77ba711..536c451ae1 100644 --- a/openage/convert/processor/conversion/hd/modpack_subprocessor.py +++ b/openage/convert/processor/conversion/hd/modpack_subprocessor.py @@ -1,4 +1,4 @@ -# Copyright 2021-2023 the openage authors. See copying.md for legal info. +# Copyright 2021-2024 the openage authors. See copying.md for legal info. # # pylint: disable=too-few-public-methods @@ -40,7 +40,10 @@ def _get_aoe2_base(cls, full_data_set: GenieObjectContainer) -> Modpack: mod_def = modpack.get_info() - mod_def.set_info("hd_base", "0.5", versionstr="5.8", repo="openage") + targetmod_info = full_data_set.game_version.edition.target_modpacks["hd_base"] + version = targetmod_info["version"] + versionstr = targetmod_info["versionstr"] + mod_def.set_info("hd_base", version, versionstr=versionstr, repo="openage") mod_def.add_include("data/**") diff --git a/openage/convert/processor/conversion/hd/processor.py b/openage/convert/processor/conversion/hd/processor.py index 7379309f57..00835c7b1f 100644 --- a/openage/convert/processor/conversion/hd/processor.py +++ b/openage/convert/processor/conversion/hd/processor.py @@ -1,4 +1,4 @@ -# Copyright 2021-2023 the openage authors. See copying.md for legal info. +# Copyright 2021-2024 the openage authors. See copying.md for legal info. # # pylint: disable=too-few-public-methods @@ -107,6 +107,7 @@ def _pre_processor( AoCProcessor.extract_genie_graphics(gamespec, dataset) AoCProcessor.extract_genie_sounds(gamespec, dataset) AoCProcessor.extract_genie_terrains(gamespec, dataset) + AoCProcessor.extract_genie_restrictions(gamespec, dataset) return dataset diff --git a/openage/convert/processor/conversion/ror/modpack_subprocessor.py b/openage/convert/processor/conversion/ror/modpack_subprocessor.py index dfe485b003..b0022c601e 100644 --- a/openage/convert/processor/conversion/ror/modpack_subprocessor.py +++ b/openage/convert/processor/conversion/ror/modpack_subprocessor.py @@ -1,4 +1,4 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. +# Copyright 2020-2024 the openage authors. See copying.md for legal info. # # pylint: disable=too-few-public-methods @@ -41,7 +41,10 @@ def _get_aoe1_base(cls, full_data_set: GenieObjectContainer) -> Modpack: mod_def = modpack.get_info() - mod_def.set_info("aoe1_base", "0.5", versionstr="1.0a", repo="openage") + targetmod_info = full_data_set.game_version.edition.target_modpacks["aoe1_base"] + version = targetmod_info["version"] + versionstr = targetmod_info["versionstr"] + mod_def.set_info("aoe1_base", version, versionstr=versionstr, repo="openage") mod_def.add_include("data/**") diff --git a/openage/convert/processor/conversion/ror/nyan_subprocessor.py b/openage/convert/processor/conversion/ror/nyan_subprocessor.py index 75262402ae..c3f6b09706 100644 --- a/openage/convert/processor/conversion/ror/nyan_subprocessor.py +++ b/openage/convert/processor/conversion/ror/nyan_subprocessor.py @@ -1,4 +1,4 @@ -# Copyright 2020-2022 the openage authors. See copying.md for legal info. +# Copyright 2020-2024 the openage authors. See copying.md for legal info. # # pylint: disable=too-many-lines,too-many-locals,too-many-statements,too-many-branches # @@ -209,11 +209,12 @@ def unit_line_to_game_entity(unit_line): # ======================================================================= abilities_set = [] + abilities_set.append(AoCAbilitySubprocessor.activity_ability(unit_line)) abilities_set.append(AoCAbilitySubprocessor.death_ability(unit_line)) abilities_set.append(AoCAbilitySubprocessor.delete_ability(unit_line)) abilities_set.append(AoCAbilitySubprocessor.despawn_ability(unit_line)) abilities_set.append(AoCAbilitySubprocessor.idle_ability(unit_line)) - abilities_set.append(AoCAbilitySubprocessor.hitbox_ability(unit_line)) + abilities_set.append(AoCAbilitySubprocessor.collision_ability(unit_line)) abilities_set.append(AoCAbilitySubprocessor.live_ability(unit_line)) abilities_set.append(AoCAbilitySubprocessor.los_ability(unit_line)) abilities_set.append(AoCAbilitySubprocessor.move_ability(unit_line)) @@ -405,7 +406,7 @@ def building_line_to_game_entity(building_line): abilities_set.append(AoCAbilitySubprocessor.delete_ability(building_line)) abilities_set.append(AoCAbilitySubprocessor.despawn_ability(building_line)) abilities_set.append(AoCAbilitySubprocessor.idle_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.hitbox_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.collision_ability(building_line)) abilities_set.append(AoCAbilitySubprocessor.live_ability(building_line)) abilities_set.append(AoCAbilitySubprocessor.los_ability(building_line)) abilities_set.append(AoCAbilitySubprocessor.named_ability(building_line)) @@ -526,7 +527,7 @@ def ambient_group_to_game_entity(ambient_group): if interaction_mode >= 0: abilities_set.append(AoCAbilitySubprocessor.death_ability(ambient_group)) - abilities_set.append(AoCAbilitySubprocessor.hitbox_ability(ambient_group)) + abilities_set.append(AoCAbilitySubprocessor.collision_ability(ambient_group)) abilities_set.append(AoCAbilitySubprocessor.idle_ability(ambient_group)) abilities_set.append(AoCAbilitySubprocessor.live_ability(ambient_group)) abilities_set.append(AoCAbilitySubprocessor.named_ability(ambient_group)) @@ -537,8 +538,8 @@ def ambient_group_to_game_entity(ambient_group): if interaction_mode >= 2: abilities_set.extend(AoCAbilitySubprocessor.selectable_ability(ambient_group)) - if ambient_group.is_passable(): - abilities_set.append(AoCAbilitySubprocessor.passable_ability(ambient_group)) + if not ambient_group.is_passable(): + abilities_set.append(AoCAbilitySubprocessor.pathable_ability(ambient_group)) if ambient_group.is_harvestable(): abilities_set.append(AoCAbilitySubprocessor.harvestable_ability(ambient_group)) @@ -812,6 +813,36 @@ def terrain_group_to_terrain(terrain_group): raw_api_object.add_raw_member("ambience", ambience, "engine.util.terrain.Terrain") + # ======================================================================= + # Path Costs + # ======================================================================= + path_costs = {} + restrictions = dataset.genie_terrain_restrictions + + # Land grid + path_type = dataset.pregen_nyan_objects["util.path.types.Land"].get_nyan_object() + land_restrictions = restrictions[0x07] + if land_restrictions.is_accessible(terrain_index): + path_costs[path_type] = 1 + + else: + path_costs[path_type] = 255 + + # Water grid + path_type = dataset.pregen_nyan_objects["util.path.types.Water"].get_nyan_object() + water_restrictions = restrictions[0x03] + if water_restrictions.is_accessible(terrain_index): + path_costs[path_type] = 1 + + else: + path_costs[path_type] = 255 + + # Air grid (default accessible) + path_type = dataset.pregen_nyan_objects["util.path.types.Air"].get_nyan_object() + path_costs[path_type] = 1 + + raw_api_object.add_raw_member("path_costs", path_costs, "engine.util.terrain.Terrain") + # ======================================================================= # Graphic # ======================================================================= diff --git a/openage/convert/processor/conversion/ror/pregen_subprocessor.py b/openage/convert/processor/conversion/ror/pregen_subprocessor.py index 4bb4eb0ad1..890ad1250b 100644 --- a/openage/convert/processor/conversion/ror/pregen_subprocessor.py +++ b/openage/convert/processor/conversion/ror/pregen_subprocessor.py @@ -1,4 +1,4 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. +# Copyright 2020-2024 the openage authors. See copying.md for legal info. # # pylint: disable=too-many-locals @@ -32,6 +32,7 @@ def generate(cls, full_data_set: GenieObjectContainer) -> None: # Stores pregenerated raw API objects as a container pregen_converter_group = ConverterObjectGroup("pregen") + AoCPregenSubprocessor.generate_activities(full_data_set, pregen_converter_group) AoCPregenSubprocessor.generate_attributes(full_data_set, pregen_converter_group) AoCPregenSubprocessor.generate_diplomatic_stances(full_data_set, pregen_converter_group) AoCPregenSubprocessor.generate_entity_types(full_data_set, pregen_converter_group) @@ -41,6 +42,7 @@ def generate(cls, full_data_set: GenieObjectContainer) -> None: # TODO: # cls._generate_modifiers(gamedata, pregen_converter_group) AoCPregenSubprocessor.generate_terrain_types(full_data_set, pregen_converter_group) + AoCPregenSubprocessor.generate_path_types(full_data_set, pregen_converter_group) AoCPregenSubprocessor.generate_resources(full_data_set, pregen_converter_group) cls.generate_death_condition(full_data_set, pregen_converter_group) diff --git a/openage/convert/processor/conversion/ror/processor.py b/openage/convert/processor/conversion/ror/processor.py index e056d934b6..d6220753fa 100644 --- a/openage/convert/processor/conversion/ror/processor.py +++ b/openage/convert/processor/conversion/ror/processor.py @@ -1,4 +1,4 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. +# Copyright 2020-2024 the openage authors. See copying.md for legal info. # # pylint: disable=line-too-long,too-many-lines,too-many-branches,too-many-statements,too-many-locals """ @@ -116,6 +116,7 @@ def _pre_processor( AoCProcessor.extract_genie_graphics(gamespec, dataset) cls.extract_genie_sounds(gamespec, dataset) AoCProcessor.extract_genie_terrains(gamespec, dataset) + AoCProcessor.extract_genie_restrictions(gamespec, dataset) return dataset diff --git a/openage/convert/processor/conversion/swgbcc/ability_subprocessor.py b/openage/convert/processor/conversion/swgbcc/ability_subprocessor.py index aacdf8df6c..07ca29004a 100644 --- a/openage/convert/processor/conversion/swgbcc/ability_subprocessor.py +++ b/openage/convert/processor/conversion/swgbcc/ability_subprocessor.py @@ -1,4 +1,4 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. +# Copyright 2020-2024 the openage authors. See copying.md for legal info. # # pylint: disable=too-many-public-methods,too-many-lines,too-many-locals # pylint: disable=too-many-branches,too-many-statements,too-many-arguments @@ -1225,16 +1225,16 @@ def harvestable_ability(line: GenieGameEntityGroup) -> ForwardRef: return ability_forward_ref @staticmethod - def hitbox_ability(line: GenieGameEntityGroup) -> ForwardRef: + def collision_ability(line: GenieGameEntityGroup) -> ForwardRef: """ - Adds the Hitbox ability to a line. + Adds the Collision ability to a line. :param line: Unit/Building line that gets the ability. :type line: ...dataformat.converter_object.ConverterObjectGroup :returns: The forward reference for the ability. :rtype: ...dataformat.forward_ref.ForwardRef """ - ability_forward_ref = AoCAbilitySubprocessor.hitbox_ability(line) + ability_forward_ref = AoCAbilitySubprocessor.collision_ability(line) # TODO: Implement diffing of civ lines diff --git a/openage/convert/processor/conversion/swgbcc/modpack_subprocessor.py b/openage/convert/processor/conversion/swgbcc/modpack_subprocessor.py index cef278f9eb..262926b919 100644 --- a/openage/convert/processor/conversion/swgbcc/modpack_subprocessor.py +++ b/openage/convert/processor/conversion/swgbcc/modpack_subprocessor.py @@ -1,4 +1,4 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. +# Copyright 2020-2024 the openage authors. See copying.md for legal info. # # pylint: disable=too-few-public-methods @@ -40,7 +40,10 @@ def _get_swgb_base(cls, full_data_set: GenieObjectContainer) -> Modpack: mod_def = modpack.get_info() - mod_def.set_info("swgb_base", "0.5", versionstr="1.1-gog4", repo="openage") + targetmod_info = full_data_set.game_version.edition.target_modpacks["swgb_base"] + version = targetmod_info["version"] + versionstr = targetmod_info["versionstr"] + mod_def.set_info("swgb_base", version, versionstr=versionstr, repo="openage") mod_def.add_include("data/**") diff --git a/openage/convert/processor/conversion/swgbcc/nyan_subprocessor.py b/openage/convert/processor/conversion/swgbcc/nyan_subprocessor.py index b48b15d309..b014629619 100644 --- a/openage/convert/processor/conversion/swgbcc/nyan_subprocessor.py +++ b/openage/convert/processor/conversion/swgbcc/nyan_subprocessor.py @@ -1,4 +1,4 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. +# Copyright 2020-2024 the openage authors. See copying.md for legal info. # # pylint: disable=too-many-lines,too-many-locals,too-many-statements,too-many-branches # @@ -14,7 +14,7 @@ from ....entity_object.conversion.aoc.genie_tech import UnitLineUpgrade from ....entity_object.conversion.aoc.genie_unit import GenieVillagerGroup, \ - GenieStackBuildingGroup, GenieGarrisonMode, GenieMonkGroup + GenieGarrisonMode, GenieMonkGroup from ....entity_object.conversion.converter_object import RawAPIObject from ....service.conversion import internal_name_lookups from ....value_object.conversion.forward_ref import ForwardRef @@ -217,11 +217,12 @@ def unit_line_to_game_entity(unit_line: GenieUnitLineGroup) -> None: # ======================================================================= abilities_set = [] + abilities_set.append(AoCAbilitySubprocessor.activity_ability(unit_line)) abilities_set.append(AoCAbilitySubprocessor.death_ability(unit_line)) abilities_set.append(AoCAbilitySubprocessor.delete_ability(unit_line)) abilities_set.append(AoCAbilitySubprocessor.despawn_ability(unit_line)) abilities_set.append(SWGBCCAbilitySubprocessor.idle_ability(unit_line)) - abilities_set.append(SWGBCCAbilitySubprocessor.hitbox_ability(unit_line)) + abilities_set.append(SWGBCCAbilitySubprocessor.collision_ability(unit_line)) abilities_set.append(SWGBCCAbilitySubprocessor.live_ability(unit_line)) abilities_set.append(SWGBCCAbilitySubprocessor.los_ability(unit_line)) abilities_set.append(SWGBCCAbilitySubprocessor.move_ability(unit_line)) @@ -431,7 +432,7 @@ def building_line_to_game_entity(building_line: GenieBuildingLineGroup) -> None: abilities_set.append(AoCAbilitySubprocessor.delete_ability(building_line)) abilities_set.append(AoCAbilitySubprocessor.despawn_ability(building_line)) abilities_set.append(AoCAbilitySubprocessor.idle_ability(building_line)) - abilities_set.append(AoCAbilitySubprocessor.hitbox_ability(building_line)) + abilities_set.append(AoCAbilitySubprocessor.collision_ability(building_line)) abilities_set.append(AoCAbilitySubprocessor.live_ability(building_line)) abilities_set.append(AoCAbilitySubprocessor.los_ability(building_line)) abilities_set.append(AoCAbilitySubprocessor.named_ability(building_line)) @@ -445,9 +446,8 @@ def building_line_to_game_entity(building_line: GenieBuildingLineGroup) -> None: # if building_line.is_creatable(): # abilities_set.append(SWGBCCAbilitySubprocessor.constructable_ability(building_line)) - if building_line.is_passable() or\ - (isinstance(building_line, GenieStackBuildingGroup) and building_line.is_gate()): - abilities_set.append(AoCAbilitySubprocessor.passable_ability(building_line)) + if not building_line.is_passable(): + abilities_set.append(AoCAbilitySubprocessor.pathable_ability(building_line)) if building_line.has_foundation(): if building_line.get_class_id() == 7: @@ -582,7 +582,7 @@ def ambient_group_to_game_entity(ambient_group: GenieAmbientGroup) -> None: if interaction_mode >= 0: abilities_set.append(AoCAbilitySubprocessor.death_ability(ambient_group)) - abilities_set.append(AoCAbilitySubprocessor.hitbox_ability(ambient_group)) + abilities_set.append(AoCAbilitySubprocessor.collision_ability(ambient_group)) abilities_set.append(AoCAbilitySubprocessor.idle_ability(ambient_group)) abilities_set.append(AoCAbilitySubprocessor.live_ability(ambient_group)) abilities_set.append(AoCAbilitySubprocessor.named_ability(ambient_group)) @@ -593,8 +593,8 @@ def ambient_group_to_game_entity(ambient_group: GenieAmbientGroup) -> None: if interaction_mode >= 2: abilities_set.extend(AoCAbilitySubprocessor.selectable_ability(ambient_group)) - if ambient_group.is_passable(): - abilities_set.append(AoCAbilitySubprocessor.passable_ability(ambient_group)) + if not ambient_group.is_passable(): + abilities_set.append(AoCAbilitySubprocessor.pathable_ability(ambient_group)) if ambient_group.is_harvestable(): abilities_set.append(SWGBCCAbilitySubprocessor.harvestable_ability(ambient_group)) diff --git a/openage/convert/processor/conversion/swgbcc/pregen_subprocessor.py b/openage/convert/processor/conversion/swgbcc/pregen_subprocessor.py index be0d20e705..fb96a60698 100644 --- a/openage/convert/processor/conversion/swgbcc/pregen_subprocessor.py +++ b/openage/convert/processor/conversion/swgbcc/pregen_subprocessor.py @@ -1,4 +1,4 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. +# Copyright 2020-2024 the openage authors. See copying.md for legal info. # # pylint: disable=too-many-locals,too-many-statements # @@ -37,6 +37,7 @@ def generate(cls, full_data_set: GenieObjectContainer) -> None: # Stores pregenerated raw API objects as a container pregen_converter_group = ConverterObjectGroup("pregen") + AoCPregenSubprocessor.generate_activities(full_data_set, pregen_converter_group) AoCPregenSubprocessor.generate_attributes(full_data_set, pregen_converter_group) AoCPregenSubprocessor.generate_diplomatic_stances(full_data_set, pregen_converter_group) AoCPregenSubprocessor.generate_team_property(full_data_set, pregen_converter_group) @@ -48,6 +49,7 @@ def generate(cls, full_data_set: GenieObjectContainer) -> None: AoCPregenSubprocessor.generate_misc_effect_objects(full_data_set, pregen_converter_group) # cls._generate_modifiers(gamedata, pregen_converter_group) ?? # cls._generate_terrain_types(gamedata, pregen_converter_group) TODO: Create terrain types + AoCPregenSubprocessor.generate_path_types(full_data_set, pregen_converter_group) cls.generate_resources(full_data_set, pregen_converter_group) AoCPregenSubprocessor.generate_death_condition(full_data_set, pregen_converter_group) diff --git a/openage/convert/processor/conversion/swgbcc/processor.py b/openage/convert/processor/conversion/swgbcc/processor.py index 2fef37ffa2..8fbb9d97ae 100644 --- a/openage/convert/processor/conversion/swgbcc/processor.py +++ b/openage/convert/processor/conversion/swgbcc/processor.py @@ -1,4 +1,4 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. +# Copyright 2020-2024 the openage authors. See copying.md for legal info. # # pylint: disable=too-many-lines,too-many-branches,too-many-statements,too-many-locals # @@ -126,6 +126,7 @@ def _pre_processor( AoCProcessor.extract_genie_graphics(gamespec, dataset) AoCProcessor.extract_genie_sounds(gamespec, dataset) AoCProcessor.extract_genie_terrains(gamespec, dataset) + AoCProcessor.extract_genie_restrictions(gamespec, dataset) return dataset diff --git a/openage/convert/processor/export/media_exporter.py b/openage/convert/processor/export/media_exporter.py index 8984cbd4bf..ace7e1f83d 100644 --- a/openage/convert/processor/export/media_exporter.py +++ b/openage/convert/processor/export/media_exporter.py @@ -1,4 +1,4 @@ -# Copyright 2021-2023 the openage authors. See copying.md for legal info. +# Copyright 2021-2024 the openage authors. See copying.md for legal info. # # pylint: disable=too-many-arguments,too-many-locals """ @@ -9,7 +9,9 @@ import logging import os - +import multiprocessing +import queue +import sys from openage.convert.entity_object.export.texture import Texture from openage.convert.service import debug_info @@ -26,11 +28,14 @@ from openage.convert.value_object.read.media.colortable import ColorTable from openage.convert.value_object.init.game_version import GameVersion from openage.util.fslike.path import Path + from openage.util.dll import DllDirectoryManager class MediaExporter: """ Provides functions for converting media files and writing them to a targetdir. + + TODO: Avoid code duplication in the export functions. """ @staticmethod @@ -62,37 +67,70 @@ def export( for media_type in export_requests.keys(): cur_export_requests = export_requests[media_type] + # Function for reading the source file data + read_data_func = None + + # Multi-threaded function for exporting the source file data export_func = None + + # Optional function for handling data in the outqueue + handle_outqueue_func = None + kwargs = {} - if media_type is MediaType.TERRAIN: - # Game version and palettes - kwargs["game_version"] = args.game_version - kwargs["palettes"] = args.palettes - kwargs["compression_level"] = args.compression_level - export_func = MediaExporter._export_terrain - info("-- Exporting terrain files...") + if media_type is MediaType.BLEND: + read_data_func = MediaExporter._get_blend_data + export_func = _export_blend + itargs = (sourcedir, exportdir) + kwargs["blend_mode_count"] = args.blend_mode_count + info("-- Exporting blend files...") elif media_type is MediaType.GRAPHICS: - kwargs["palettes"] = args.palettes - kwargs["compression_level"] = args.compression_level + read_data_func = MediaExporter._get_graphics_data + export_func = _export_texture + handle_outqueue_func = MediaExporter._handle_graphics_outqueue + itargs = (args.palettes, args.compression_level) kwargs["cache_info"] = cache_info - export_func = MediaExporter._export_graphics info("-- Exporting graphics files...") elif media_type is MediaType.SOUNDS: - export_func = MediaExporter._export_sound + read_data_func = MediaExporter._get_sound_data + export_func = _export_sound + itargs = tuple() + kwargs["debugdir"] = args.debugdir + kwargs["loglevel"] = args.debug_info info("-- Exporting sound files...") - elif media_type is MediaType.BLEND: - kwargs["blend_mode_count"] = args.blend_mode_count - export_func = MediaExporter._export_blend - info("-- Exporting blend files...") + elif media_type is MediaType.TERRAIN: + read_data_func = MediaExporter._get_terrain_data + export_func = _export_terrain + itargs = (args.palettes, args.compression_level, args.game_version) + info("-- Exporting terrain files...") - total_count = len(cur_export_requests) - for count, request in enumerate(cur_export_requests, start = 1): - export_func(request, sourcedir, exportdir, **kwargs) - print(f"-- Files done: {format_progress(count, total_count)}", - end = "\r", flush = True) + if args.jobs == 1: + MediaExporter._export_singlethreaded( + cur_export_requests, + sourcedir, + exportdir, + read_data_func, + export_func, + handle_outqueue_func, + itargs, + kwargs + ) + + else: + MediaExporter._export_multithreaded( + cur_export_requests, + sourcedir, + exportdir, + read_data_func, + export_func, + handle_outqueue_func, + itargs, + kwargs, + args.jobs, + args.dll_manager, + ) if args.debug_info > 5: cachedata = {} @@ -119,293 +157,307 @@ def export( ) @staticmethod - def _export_blend( - export_request: MediaExportRequest, + def _export_singlethreaded( + requests: list[MediaExportRequest], sourcedir: Path, exportdir: Path, - blend_mode_count: int = None - ) -> None: + read_data_func: typing.Callable, + export_func: typing.Callable, + handle_outqueue_func: typing.Callable | None, + itargs: tuple, + kwargs: dict + ): """ - Convert and export a blending mode. + Export media files in a single thread. - :param export_request: Export request for a blending mask. + :param requests: Export requests for media files. :param sourcedir: Directory where all media assets are mounted. Source subfolder and source filename should be stored in the export request. :param exportdir: Directory the resulting file(s) will be exported to. Target subfolder and target filename should be stored in the export request. - :param blend_mode_count: Number of blending modes extracted from the source file. - :type export_request: MediaExportRequest + :param read_data_func: Function for reading the source file data. + :param export_func: Function for exporting media files. + :param handle_outqueue_func: Optional function for handling data in the outqueue. + :param itargs: Arguments for the export function. + :param kwargs: Keyword arguments for the export function. + :type requests: list[MediaExportRequest] :type sourcedir: Path :type exportdir: Path - :type blend_mode_count: int + :type read_data_func: typing.Callable + :type export_func: typing.Callable + :type handle_outqueue_func: typing.Callable + :type itargs: tuple + :type kwargs: dict """ - source_file = sourcedir.joinpath(export_request.source_filename) - - media_file = source_file.open("rb") - blend_data = Blendomatic(media_file, blend_mode_count) - - from .texture_merge import merge_frames - - textures = blend_data.get_textures() - for idx, texture in enumerate(textures): - merge_frames(texture) - MediaExporter.save_png( - texture, - exportdir[export_request.targetdir], - f"{export_request.target_filename}{idx}.png" + single_queue = queue.Queue() + for idx, request in enumerate(requests): + source_data = read_data_func(request, sourcedir, **kwargs) + if source_data is None: + continue + + target_path = exportdir[request.targetdir, request.target_filename] + + export_func( + idx, + source_data, + single_queue, + None, + request.source_filename, + target_path, + *itargs, + **kwargs ) if get_loglevel() <= logging.DEBUG: MediaExporter.log_fileinfo( - source_file, - exportdir[export_request.targetdir, - f"{export_request.target_filename}{idx}.png"] + sourcedir[request.get_type().value, request.source_filename], + exportdir[request.targetdir, request.target_filename] ) + MediaExporter._show_progress(idx + 1, len(requests)) + + if handle_outqueue_func: + handle_outqueue_func(single_queue, requests) + @staticmethod - def _export_graphics( - export_request: MediaExportRequest, + def _export_multithreaded( + requests: list[MediaExportRequest], sourcedir: Path, exportdir: Path, - palettes: dict[int, ColorTable], - compression_level: int, - cache_info: dict = None - ) -> None: + read_data_func: typing.Callable, + export_func: typing.Callable, + handle_outqueue_func: typing.Callable | None, + itargs: tuple, + kwargs: dict, + job_count: int = None, + dll_manager: DllDirectoryManager = None, + ): """ - Convert and export a graphics file. + Export media files in multiple threads. - :param export_request: Export request for a graphics file. + :param requests: Export requests for media files. :param sourcedir: Directory where all media assets are mounted. Source subfolder and source filename should be stored in the export request. :param exportdir: Directory the resulting file(s) will be exported to. Target subfolder and target filename should be stored in the export request. - :param palettes: Palettes used by the game. - :param compression_level: PNG compression level for the resulting image file. - :param cache_info: Media cache information with compression parameters from a previous run. - :type export_request: MediaExportRequest - :type sourcedir: Path - :type exportdir: Path - :type palettes: dict - :type compression_level: int - :type cache_info: tuple + :param read_data_func: Function for reading the source file data. + :param export_func: Function for exporting media files. + :param handle_outqueue_func: Optional function for handling data in the outqueue. + :param itargs: Arguments for the export function. + :param kwargs: Keyword arguments for the export function. + :param job_count: Number of worker processes to use. + :param dll_manager: Adds DLL search paths for the subrocesses (Windows-only). """ - source_file = sourcedir[ - export_request.get_type().value, - export_request.source_filename - ] - - try: - media_file = source_file.open("rb") - - except FileNotFoundError: - if source_file.suffix.lower() in (".smx", ".sld"): - # Rename extension to SMP and try again - other_filename = export_request.source_filename[:-3] + "smp" - source_file = sourcedir[ - export_request.get_type().value, - other_filename - ] - export_request.set_source_filename(other_filename) - - media_file = source_file.open("rb") - - if source_file.suffix.lower() == ".slp": - from ...value_object.read.media.slp import SLP - image = SLP(media_file.read()) - - elif source_file.suffix.lower() == ".smp": - from ...value_object.read.media.smp import SMP - image = SMP(media_file.read()) - - elif source_file.suffix.lower() == ".smx": - from ...value_object.read.media.smx import SMX - image = SMX(media_file.read()) - - elif source_file.suffix.lower() == ".sld": - from ...value_object.read.media.sld import SLD - image = SLD(media_file.read()) - - packer_cache = None - compr_cache = None - if cache_info: - cache_params = cache_info.get(export_request.source_filename, None) - - if cache_params: - packer_cache = cache_params["packer_settings"] - compression_level = cache_params["compr_settings"][0] - compr_cache = cache_params["compr_settings"][1:] - - from .texture_merge import merge_frames - - texture = Texture(image, palettes) - merge_frames(texture, cache=packer_cache) - MediaExporter.save_png( - texture, - exportdir[export_request.targetdir], - export_request.target_filename, - compression_level=compression_level, - cache=compr_cache - ) - metadata = {export_request.target_filename: texture.get_metadata()} - export_request.set_changed() - export_request.notify_observers(metadata) - export_request.clear_changed() - + worker_count = job_count + if worker_count is None: + # Small optimization that saves some time for small exports + worker_count = min(multiprocessing.cpu_count(), len(requests)) + + def error_callback(exception: Exception): + """ + Error callback for the worker pool. + """ + raise exception + + # Create a manager for sharing data between the workers and main process + with multiprocessing.Manager() as manager: + # Workers write the image metadata to this queue + # so that it can be forwarded to the export requests + # + # we cannot do this in a worker process directly + # because the export requests cannot be pickled + outqueue = manager.Queue() + + expected_size = len(requests) + + # Create a pool of workers + with multiprocessing.Pool(worker_count) as pool: + for idx, request in enumerate(requests): + # Feed the worker with the source file data (bytes) from the + # main process + # + # This is necessary because some image files are inside an + # archive and cannot be accessed asynchronously + source_data = read_data_func(request, sourcedir, **kwargs) + if source_data is None: + expected_size -= 1 + continue + + target_path = exportdir[request.targetdir, request.target_filename] + + # Start an export call in a worker process + # The call is asynchronous, so the next worker can be + # started immediately + pool.apply_async( + export_func, + args=( + idx, + source_data, + outqueue, + dll_manager, + request.source_filename, + target_path, + *itargs + ), + kwds=kwargs, + error_callback=error_callback + ) + + # Show progress + MediaExporter._show_progress(outqueue.qsize(), expected_size) + + # Close the pool since all workers have been started + pool.close() + + # Show progress for remaining workers + while outqueue.qsize() < expected_size: + MediaExporter._show_progress(outqueue.qsize(), expected_size) + + # Wait for all workers to finish + pool.join() + + if handle_outqueue_func: + handle_outqueue_func(outqueue, requests) + + # Log file information if get_loglevel() <= logging.DEBUG: - MediaExporter.log_fileinfo( - source_file, - exportdir[export_request.targetdir, export_request.target_filename] - ) + for request in requests: + MediaExporter.log_fileinfo( + sourcedir[request.get_type().value, request.source_filename], + exportdir[request.targetdir, request.target_filename] + ) @staticmethod - def _export_interface( - export_request: MediaExportRequest, + def _get_blend_data( + request: MediaExportRequest, sourcedir: Path, - **kwargs - ) -> None: + **kwargs # pylint: disable=unused-argument + ) -> bytes: """ - Convert and export a sprite file. + Get the raw file data of a blending mask. + + :param request: Export request for a blending mask. + :param sourcedir: Directory where all media assets are mounted. + :type request: MediaExportRequest + :type sourcedir: Path """ - # TODO: Implement + source_file = sourcedir[request.get_type().value, + request.source_filename] + + return source_file.open("rb").read() @staticmethod - def _export_palette( - export_request: MediaExportRequest, + def _get_graphics_data( + request: MediaExportRequest, sourcedir: Path, - **kwargs - ) -> None: + **kwargs # pylint: disable=unused-argument + ) -> bytes: """ - Convert and export a palette file. + Get the raw file data of a graphics file. + + :param request: Export request for a graphics file. + :param sourcedir: Directory where all media assets are mounted. + :type request: MediaExportRequest + :type sourcedir: Path """ - # TODO: Implement + source_file = sourcedir[request.get_type().value, + request.source_filename] + if not source_file.exists(): + if source_file.suffix.lower() in (".smx", ".sld"): + # Some DE2 graphics files have the wrong extension + # Fall back to the SMP (beta) extension + other_filename = request.source_filename[:-3] + "smp" + source_file = sourcedir[ + request.get_type().value, + other_filename + ] + request.set_source_filename(other_filename) + + return source_file.open("rb").read() @staticmethod - def _export_sound( - export_request: MediaExportRequest, + def _get_sound_data( + request: MediaExportRequest, sourcedir: Path, - exportdir: Path, - ) -> None: + **kwargs + ) -> bytes | None: """ - Convert and export a sound file. + Get the raw file data of a sound file. - :param export_request: Export request for a sound file. - :param sourcedir: Directory where all media assets are mounted. Source subfolder and - source filename should be stored in the export request. - :param exportdir: Directory the resulting file(s) will be exported to. Target subfolder - and target filename should be stored in the export request. - :type export_request: MediaExportRequest + :param request: Export request for a sound file. + :param sourcedir: Directory where all media assets are mounted. + :type request: MediaExportRequest :type sourcedir: Path - :type exportdir: Path """ - source_file = sourcedir[ - export_request.get_type().value, - export_request.source_filename - ] + source_file = sourcedir[request.get_type().value, + request.source_filename] - if source_file.is_file(): - with source_file.open_r() as infile: - media_file = infile.read() - - else: + if not source_file.is_file(): # TODO: Filter files that do not exist out sooner - return - - from ...service.export.opus.opusenc import encode - - soundata = encode(media_file) - - if isinstance(soundata, (str, int)): - raise RuntimeError(f"opusenc failed: {soundata}") - - export_file = exportdir[ - export_request.targetdir, - export_request.target_filename - ] + debug_info.debug_not_found_sounds(kwargs["debugdir"], + kwargs["loglevel"], + source_file) + return None - with export_file.open_w() as outfile: - outfile.write(soundata) - - if get_loglevel() <= logging.DEBUG: - MediaExporter.log_fileinfo( - source_file, - exportdir[export_request.targetdir, export_request.target_filename] - ) + return source_file.open("rb").read() @staticmethod - def _export_terrain( - export_request: MediaExportRequest, + def _get_terrain_data( + request: MediaExportRequest, sourcedir: Path, - exportdir: Path, - palettes: dict[int, ColorTable], - game_version: GameVersion, - compression_level: int - ) -> None: + **kwargs # pylint: disable=unused-argument + ) -> bytes: """ - Convert and export a terrain graphics file. + Get the raw file data of a terrain graphics file. - :param export_request: Export request for a terrain graphics file. - :param sourcedir: Directory where all media assets are mounted. Source subfolder and - source filename should be stored in the export request. - :param exportdir: Directory the resulting file(s) will be exported to. Target subfolder - and target filename should be stored in the export request. - :param game_version: Game edition and expansion info. - :param palettes: Palettes used by the game. - :param compression_level: PNG compression level for the resulting image file. - :type export_request: MediaExportRequest - :type sourcedir: Directory - :type exportdir: Directory - :type palettes: dict - :type game_version: GameVersion - :type compression_level: int + :param request: Export request for a terrain graphics file. + :param sourcedir: Directory where all media assets are mounted. + :type request: MediaExportRequest + :type sourcedir: Path """ - source_file = sourcedir[ - export_request.get_type().value, - export_request.source_filename - ] - - if source_file.suffix.lower() == ".slp": - from ...value_object.read.media.slp import SLP - media_file = source_file.open("rb") - image = SLP(media_file.read()) - - elif source_file.suffix.lower() == ".dds": - # TODO: Implement - pass + source_file = sourcedir[request.get_type().value, + request.source_filename] - elif source_file.suffix.lower() == ".png": - from shutil import copyfileobj - src_path = source_file.open('rb') - dst_path = exportdir[export_request.targetdir, - export_request.target_filename].open('wb') - copyfileobj(src_path, dst_path) - return + return source_file.open("rb").read() - else: - raise SyntaxError(f"Source file {source_file.name} has an unrecognized extension: " - f"{source_file.suffix.lower()}") + @staticmethod + def _handle_graphics_outqueue( + outqueue: multiprocessing.Queue, + requests: list[MediaExportRequest] + ): + """ + Collect the metadata from the workers and forward it to the + export requests. - if game_version.edition.game_id in ("AOC", "SWGB"): - from .terrain_merge import merge_terrain - texture = Texture(image, palettes) - merge_terrain(texture) + This must be called before the manager of the queue is shutdown! - else: - from .texture_merge import merge_frames - texture = Texture(image, palettes) - merge_frames(texture) + :param outqueue: Queue for passing metadata to the main process. + :param requests: Export requests for graphics files. + :type outqueue: multiprocessing.Queue + :type requests: list[MediaExportRequest] + """ + while not outqueue.empty(): + idx, metadata = outqueue.get() + update_data = {requests[idx].target_filename: metadata} + requests[idx].set_changed() + requests[idx].notify_observers(update_data) + requests[idx].clear_changed() - MediaExporter.save_png( - texture, - exportdir[export_request.targetdir], - export_request.target_filename, - compression_level, - ) + @staticmethod + def _show_progress( + current_size: int, + total_size: int, + ): + """ + Show the progress of the export process. - if get_loglevel() <= logging.DEBUG: - MediaExporter.log_fileinfo( - source_file, - exportdir[export_request.targetdir, export_request.target_filename] - ) + :param current_size: Number of files that have been exported. + :param total_size: Total number of files to export. + :type current_size: int + :type total_size: int + """ + print(f"-- Files done: {format_progress(current_size, total_size)}", + end = "\r", flush = True) @staticmethod def _get_media_cache( @@ -462,6 +514,14 @@ def _get_media_cache( from ...value_object.read.media.smx import SMX image = SMX(media_file.read()) + elif source_file.suffix.lower() == ".sld": + from ...value_object.read.media.sld import SLD + image = SLD(media_file.read()) + + else: + raise SyntaxError(f"Source file {source_file.name} has an unrecognized extension: " + f"{source_file.suffix.lower()}") + from .texture_merge import merge_frames texture = Texture(image, palettes) merge_frames(texture) @@ -546,17 +606,8 @@ def log_fileinfo( source_format = source_file.suffix[1:].upper() target_format = target_file.suffix[1:].upper() - source_path = source_file.resolve_native_path() - if source_path: - source_size = os.path.getsize(source_path) - - else: - with source_file.open('r') as src: - src.seek(0, os.SEEK_END) - source_size = src.tell() - - target_path = target_file.resolve_native_path() - target_size = os.path.getsize(target_path) + source_size = source_file.filesize + target_size = target_file.filesize log = ("Converted: " f"{source_file.name} " @@ -566,3 +617,264 @@ def log_fileinfo( f"{(target_size / source_size * 100) - 100:+.1f}%)") dbg(log) + + +def _export_blend( + request_id: int, + blendfile_data: bytes, + outqueue: multiprocessing.Queue, + dll_manager: DllDirectoryManager, + source_filename: str, # pylint: disable=unused-argument + targetdir: Path, + target_filename: str, + blend_mode_count: int = None +) -> None: + """ + Convert and export a blending mode. + + :param request_id: ID of the export request. + :param blendfile_data: Raw file data of the blending mask. + :param outqueue: Queue for passing metadata to the main process. + :param dll_manager: Adds DLL search paths for the subrocesses (Windows-only). + :param target_path: Path to the resulting image file. + :param blend_mode_count: Number of blending modes extracted from the source file. + """ + if sys.platform == "win32" and dll_manager is not None: + dll_manager.add_directories() + + blend_data = Blendomatic(blendfile_data, blend_mode_count) + + from .texture_merge import merge_frames + + textures = blend_data.get_textures() + for idx, texture in enumerate(textures): + merge_frames(texture) + _save_png( + texture, + targetdir.joinpath(f"{target_filename}_{idx}.png") + ) + + outqueue.put(request_id) + + +def _export_sound( + request_id: int, + sound_data: bytes, + outqueue: multiprocessing.Queue, + dll_manager: DllDirectoryManager, + source_filename: str, # pylint: disable=unused-argument + target_path: Path, + **kwargs # pylint: disable=unused-argument +) -> None: + """ + Convert and export a sound file. + + :param request_id: ID of the export request. + :param sound_data: Raw file data of the sound file. + :param outqueue: Queue for passing metadata to the main process. + :param dll_manager: Adds DLL search paths for the subrocesses (Windows-only). + :param target_path: Path to the resulting sound file. + """ + if sys.platform == "win32" and dll_manager is not None: + dll_manager.add_directories() + + from ...service.export.opus.opusenc import encode + encoded = encode(sound_data) + + if isinstance(encoded, (str, int)): + raise RuntimeError(f"opusenc failed: {encoded}") + + with target_path.open("wb") as outfile: + outfile.write(encoded) + + outqueue.put(request_id) + + +def _export_terrain( + request_id: int, + graphics_data: bytes, + outqueue: multiprocessing.Queue, + dll_manager: DllDirectoryManager, + source_filename: str, + target_path: Path, + palettes: dict[int, ColorTable], + compression_level: int, + game_version: GameVersion +) -> None: + """ + Convert and export a terrain graphics file. + + :param request_id: ID of the export request. + :param graphics_data: Raw file data of the graphics file. + :param outqueue: Queue for passing the image metadata to the main process. + :param dll_manager: Adds DLL search paths for the subrocesses (Windows-only). + :param source_filename: Filename of the source file. + :param target_path: Path to the resulting image file. + :param palettes: Palettes used by the game. + :param compression_level: PNG compression level for the resulting image file. + :param game_version: Game edition and expansion info. + """ + if sys.platform == "win32" and dll_manager is not None: + dll_manager.add_directories() + + file_ext = source_filename.split('.')[-1].lower() + if file_ext == "slp": + from ...value_object.read.media.slp import SLP + image = SLP(graphics_data) + + elif file_ext == "dds": + # TODO: Implement + pass + + elif file_ext == "png": + with target_path.open("wb") as imagefile: + imagefile.write(graphics_data) + + outqueue.put(request_id) + return + + else: + raise SyntaxError(f"Source file {source_filename} has an unrecognized extension: " + f"{file_ext}") + + if game_version.edition.game_id in ("AOC", "SWGB"): + from .terrain_merge import merge_terrain + texture = Texture(image, palettes) + merge_terrain(texture) + + else: + from .texture_merge import merge_frames + texture = Texture(image, palettes) + merge_frames(texture) + + _save_png( + texture, + target_path, + compression_level=compression_level + ) + + outqueue.put(request_id) + + +def _export_texture( + request_id: int, + graphics_data: bytes, + outqueue: multiprocessing.Queue, + dll_manager: DllDirectoryManager, + source_filename: str, + target_path: Path, + palettes: dict[int, ColorTable], + compression_level: int, + cache_info: dict = None +) -> None: + """ + Convert and export a graphics file to a PNG texture. + + :param request_id: ID of the export request. + :param graphics_data: Raw file data of the graphics file. + :param outqueue: Queue for passing the image metadata to the main process. + :param dll_manager: Adds DLL search paths for the subrocesses (Windows-only). + :param source_filename: Filename of the source file. + :param target_path: Path to the resulting image file. + :param palettes: Palettes used by the game. + :param compression_level: PNG compression level for the resulting image file. + :param cache_info: Media cache information with compression parameters from a previous run. + """ + if sys.platform == "win32" and dll_manager is not None: + dll_manager.add_directories() + + file_ext = source_filename.split('.')[-1].lower() + if file_ext == "slp": + from ...value_object.read.media.slp import SLP + image = SLP(graphics_data) + + elif file_ext == "smp": + from ...value_object.read.media.smp import SMP + image = SMP(graphics_data) + + elif file_ext == "smx": + from ...value_object.read.media.smx import SMX + image = SMX(graphics_data) + + elif file_ext == "sld": + from ...value_object.read.media.sld import SLD + image = SLD(graphics_data) + + else: + raise SyntaxError(f"Source file {source_filename} has an unrecognized extension: " + f"{file_ext}") + + packer_cache = None + compr_cache = None + if cache_info: + cache_params = cache_info.get(source_filename, None) + + if cache_params: + packer_cache = cache_params["packer_settings"] + compression_level = cache_params["compr_settings"][0] + compr_cache = cache_params["compr_settings"][1:] + + from .texture_merge import merge_frames + + texture = Texture(image, palettes) + merge_frames(texture, cache=packer_cache) + _save_png( + texture, + target_path, + compression_level=compression_level, + cache=compr_cache + ) + metadata = (request_id, texture.get_metadata().copy()) + outqueue.put(metadata) + + +def _save_png( + texture: Texture, + target_path: Path, + compression_level: int = 1, + cache: dict = None, + dry_run: bool = False +) -> None: + """ + Store the image data into the target directory path, + with given target_path="dir/out.png". + + :param texture: Texture with an image atlas. + :param target_path: Path to the resulting image file. + :param compression_level: PNG compression level used for the resulting image file. + :param dry_run: If True, create the PNG but don't save it as a file. + """ + from ...service.export.png import png_create + + compression_levels = { + 0: png_create.CompressionMethod.COMPR_NONE, + 1: png_create.CompressionMethod.COMPR_DEFAULT, + 2: png_create.CompressionMethod.COMPR_OPTI, + 3: png_create.CompressionMethod.COMPR_GREEDY, + 4: png_create.CompressionMethod.COMPR_AGGRESSIVE, + } + + if not dry_run: + ext = target_path.suffix.lower() + + # only allow png + if ext != ".png": + raise ValueError("Filename invalid, a texture must be saved" + f" as '*.png', not '*{ext}'") + + compression_method = compression_levels.get( + compression_level, + png_create.CompressionMethod.COMPR_DEFAULT + ) + png_data, compr_params = png_create.save( + texture.image_data.data, + compression_method, + cache + ) + + if not dry_run: + with target_path.open("wb") as imagefile: + imagefile.write(png_data) + + if compr_params: + texture.best_compr = (compression_level, *compr_params) 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/debug_info.py b/openage/convert/service/debug_info.py index 50ffd72bc1..39c8b92d3d 100644 --- a/openage/convert/service/debug_info.py +++ b/openage/convert/service/debug_info.py @@ -677,3 +677,47 @@ def debug_media_cache( with logfile.open("w") as log: log.write(logtext) + + +def debug_execution_time(debugdir: Directory, loglevel: int, stages_time: dict[str, float]) -> None: + """ + Create debug output for execution time for each stage + + :param debugdir: Output directory for the debug info. + :type debugdir: Directory + :param loglevel: Determines how detailed the output is. + :type loglevel: int + :param stages_time: Dict with execution time for each stage. + :type stages_time: dict + """ + if loglevel < 1: + return + + logfile = debugdir["execution_time"] + logtext = "".join(f"{k}: {v}\n" for k, v in stages_time.items()) + + with logfile.open("w") as log: + log.write(logtext) + + +def debug_not_found_sounds(debugdir: Directory, loglevel: int, sound: Path) -> None: + """ + Create debug output for sounds not found + + :param debugdir: Output directory for the debug info. + :type debugdir: Directory + :param loglevel: Determines how detailed the output is. + :type loglevel: int + :param sound: Sound object with path and name values. + :type sound: Path + """ + if loglevel < 6: + return + + logfile = debugdir.joinpath("export/not_found_sounds")[sound.stem] + + path = [part.decode() for part in sound.parts] + logtext = f"name: {sound.name}\npath: {'/'.join(path)}" + + with logfile.open("w") as log: + log.write(logtext) 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/service/init/CMakeLists.txt b/openage/convert/service/init/CMakeLists.txt index 3c7e404c69..1d09b906d9 100644 --- a/openage/convert/service/init/CMakeLists.txt +++ b/openage/convert/service/init/CMakeLists.txt @@ -2,7 +2,6 @@ add_py_modules( __init__.py api_export_required.py changelog.py - conversion_required.py modpack_search.py mount_asset_dirs.py version_detect.py diff --git a/openage/convert/service/init/api_export_required.py b/openage/convert/service/init/api_export_required.py index 61b8f5ba4d..8bb3a51850 100644 --- a/openage/convert/service/init/api_export_required.py +++ b/openage/convert/service/init/api_export_required.py @@ -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. """ Test whether the openage nyan API modpack is present. @@ -16,12 +16,12 @@ from openage.util.fslike.union import UnionPath -CURRENT_API_VERSION = "0.4.0" +CURRENT_API_VERSION = "0.5.0" def api_export_required(asset_dir: UnionPath) -> bool: """ - Returns true if the openage nyan API modpack cannot be found. + Returns true if the openage nyan API modpack cannot be found or is outdated. TODO: Remove once the API modpack is generated by default. diff --git a/openage/convert/service/init/changelog.py b/openage/convert/service/init/changelog.py index 0c63aac864..20877664e4 100644 --- a/openage/convert/service/init/changelog.py +++ b/openage/convert/service/init/changelog.py @@ -1,69 +1,49 @@ -# Copyright 2015-2022 the openage authors. See copying.md for legal info. +# Copyright 2024-2024 the openage authors. See copying.md for legal info. """ -Asset version change log - -used to determine whether assets that were converted by an earlier version of -openage are still up to date. +Check for updates in the openage converter modpacks. """ from __future__ import annotations import typing -from ....log import warn -from ....testing.testing import TestError - -# filename where to store the versioning information -ASSET_VERSION_FILENAME = "asset_version" - -# filename where to store the gamespec version hash -GAMESPEC_VERSION_FILENAME = "gamespec_version" - -# available components for reconversion -COMPONENTS = { - "graphics", - "sounds", - "metadata", - "interface", -} -# each line represents changes to the assets. -# the last line is the most recent change. -CHANGES = ( - {"graphics", "sounds"}, - {"sounds"}, - {"graphics"}, - {"interface"}, - {"interface"}, - {"metadata"}, - {"metadata"}, - {"graphics"}, -) +from itertools import chain -# the current version number equals the number of changes -ASSET_VERSION = len(CHANGES) - 1 +from openage.util.version import SemanticVersion +from ....log import info -def changes(asset_version: int) -> set: - """ - return all changed components since the passed version number. - """ - if asset_version >= len(CHANGES): - warn("asset version from the future: %d", asset_version) - warn("current version is: %d", ASSET_VERSION) - warn("leaving assets as they are.") - return set() +from ..init.version_detect import create_version_objects - changed_components = set() - # TODO: Reimplement with proper detection based on file hashing +if typing.TYPE_CHECKING: + from openage.util.fslike.union import UnionPath - return changed_components - -def test() -> typing.NoReturn: +def check_updates(available_modpacks: dict[str, str], game_info_dir: UnionPath): """ - verify only allowed versions are stored in the changes + Check if there are updates available for the openage converter modpacks. + + :param available_modpacks: Available modpacks and their versions. Modpack names are keys, + versions are values. + :param game_info_dir: The directory containing the game information. """ - for entry in CHANGES: - if entry > COMPONENTS: - invalid = entry - COMPONENTS - raise TestError(f"'{invalid}': invalid changelog entry") + game_editions, game_expansions = create_version_objects(game_info_dir) + for game_def in chain(game_editions, game_expansions): + for targetmod_name, targetmod_def in game_def.target_modpacks.items(): + if targetmod_name in available_modpacks: + converter_version = SemanticVersion(targetmod_def["version"]) + try: + modpack_version = SemanticVersion(available_modpacks[targetmod_name]) + + except ValueError: + # Some really old converted modpacks don't use semantic versioning + # these should always be updated + modpack_version = SemanticVersion("0.0.0") + + if converter_version > modpack_version: + info(("Modpack %s v%s is outdated: " + "newer version v%s is available"), + targetmod_name, modpack_version, converter_version) + + else: + info("Modpack %s v%s is up-to-date", targetmod_name, modpack_version) diff --git a/openage/convert/service/init/conversion_required.py b/openage/convert/service/init/conversion_required.py deleted file mode 100644 index 1c13a33142..0000000000 --- a/openage/convert/service/init/conversion_required.py +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. - -""" -Test whether there already are converted modpacks present. -""" -from __future__ import annotations -import typing - -from ....log import info - -from .modpack_search import enumerate_modpacks - -if typing.TYPE_CHECKING: - from openage.util.fslike.union import UnionPath - - -def conversion_required(asset_dir: UnionPath) -> bool: - """ - Check if an asset conversion is required to run the game. - - Asset conversions are required if: - - the modpack folder does not exist - - no modpacks inside the modpack folder exist - - the converted assets are outdated - - :param asset_dir: The asset directory to check. - :type asset_dir: UnionPath - :return: True if an asset conversion is required, else False. - """ - try: - # TODO: Do not use hardcoded path to "converted" directory. - # The mod directory should be its own configurable path - modpacks = enumerate_modpacks(asset_dir / "converted") - - except FileNotFoundError: - # modpack folder not created yet - modpacks = set() - - if not modpacks or \ - len(modpacks) == 1 and "engine" in modpacks: # engine is not usable on its own - info("No converted assets have been found") - return True - - # TODO: Check if modpack version of converted assets are up to date - # if not changes: - # dbg("Converted assets are up to date") - # return False - - # if asset_version >= 0 and asset_version != changelog.ASSET_VERSION: - # info("Found converted assets with version %d, " - # "but need version %d", asset_version, changelog.ASSET_VERSION) - - return False diff --git a/openage/convert/service/init/modpack_search.py b/openage/convert/service/init/modpack_search.py index ea8bf87c24..aa6a814004 100644 --- a/openage/convert/service/init/modpack_search.py +++ b/openage/convert/service/init/modpack_search.py @@ -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. """ Search for and enumerate openage modpacks. @@ -14,27 +14,35 @@ from openage.util.fslike.union import UnionPath -def enumerate_modpacks(modpacks_dir: UnionPath) -> set[str]: +def enumerate_modpacks(modpacks_dir: UnionPath, exclude: set[str] = None) -> dict[str, str]: """ Enumerate openage modpacks in a directory. :param asset_dir: The asset directory to search in. :type asset_dir: UnionPath - :returns: A list of modpack names that were found. - :rtype: set[str] + :param exclude: Modpack names to exclude from the enumeration. + :type exclude: set[str] + :returns: Modpacks that were found. Names are keys, versions are values. + :rtype: dict[str, str] """ if not modpacks_dir.exists(): info("openage modpack directory has not been created yet") raise FileNotFoundError("openage modpack directory not found") - modpacks: set[str] = set() + modpacks: dict[str] = {} for check_dir in modpacks_dir.iterdir(): if check_dir.is_dir(): try: modpack_info = get_modpack_info(check_dir) modpack_name = modpack_info["info"]["name"] - info("Found modpack %s", modpack_name) - modpacks.add(modpack_name) + modpack_version = modpack_info["info"]["version"] + info("Found modpack %s v%s", modpack_name, modpack_version) + + if exclude is not None and modpack_name in exclude: + dbg("Excluding modpack %s from enumeration", modpack_name) + continue + + modpacks.update({modpack_name: modpack_version}) except (FileNotFoundError, TypeError, toml.TomlDecodeError): dbg("No modpack found in directory: %s", check_dir) @@ -81,22 +89,26 @@ def get_modpack_info(modpack_dir: UnionPath) -> dict[str, typing.Any]: raise err -def query_modpack(proposals: set[str]) -> str: +def query_modpack(proposals: list[str]) -> str: """ Query interactively for a modpack from a selection of proposals. """ print("\nPlease select a modpack before starting.") - print("Insert the index of one of the proposals (Default = 0):") + print("Enter the index of one of the proposals (Default = 0):") proposals = sorted(proposals) for index, proposal in enumerate(proposals): print(f"({index}) {proposal}") user_selection = input("> ") - if user_selection.isdecimal() and int(user_selection) < len(proposals): - selection = proposals[int(user_selection)] + if user_selection == "": + selection = proposals[0] else: - selection = proposals[0] + while not (user_selection.isdecimal() and int(user_selection) < len(proposals)): + print(f"'{user_selection}' is not a valid index. Please try again.") + user_selection = input("> ") + + selection = proposals[int(user_selection)] return selection diff --git a/openage/convert/service/init/version_detect.py b/openage/convert/service/init/version_detect.py index 792ce9708b..58cec8925a 100644 --- a/openage/convert/service/init/version_detect.py +++ b/openage/convert/service/init/version_detect.py @@ -1,4 +1,4 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. +# Copyright 2020-2024 the openage authors. See copying.md for legal info. # # pylint: disable=too-many-arguments,too-many-locals,too-many-branches """ @@ -180,7 +180,7 @@ def create_game_obj( game_name = game_info['name'] game_id = game_info['game_edition_id'] support = game_info['support'] - modpacks = game_info['targetmod'] + modpacks = game_info['targetmods'] if not expansion: expansions = game_info['expansions'] diff --git a/openage/convert/service/read/gamedata.py b/openage/convert/service/read/gamedata.py index 522d959b8e..0d50c81d03 100644 --- a/openage/convert/service/read/gamedata.py +++ b/openage/convert/service/read/gamedata.py @@ -1,4 +1,4 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. +# Copyright 2020-2024 the openage authors. See copying.md for legal info. """ Module for reading .dat files. @@ -73,8 +73,8 @@ def load_gamespec( # pickle.load() can fail in many ways, we need to catch all. # pylint: disable=broad-except try: - gamespec = pickle.load(cachefile) info("using cached wrapper: %s", cachefile_name) + gamespec = pickle.load(cachefile) return gamespec except Exception: warn("could not use cached wrapper:") diff --git a/openage/convert/service/read/nyan_api_loader.py b/openage/convert/service/read/nyan_api_loader.py index 067c4d1065..9980b7fbf4 100644 --- a/openage/convert/service/read/nyan_api_loader.py +++ b/openage/convert/service/read/nyan_api_loader.py @@ -1,4 +1,4 @@ -# Copyright 2019-2023 the openage authors. See copying.md for legal info. +# Copyright 2019-2024 the openage authors. See copying.md for legal info. # # pylint: disable=line-too-long,too-many-lines,too-many-statements """ @@ -111,6 +111,13 @@ def _create_objects(api_objects: dict[str, NyanObject]) -> None: nyan_object.set_fqon(fqon) api_objects.update({fqon: nyan_object}) + # engine.ability.type.Activity + parents = [api_objects["engine.ability.Ability"]] + nyan_object = NyanObject("Activity", parents) + fqon = "engine.ability.type.Activity" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + # engine.ability.type.ApplyContinuousEffect parents = [api_objects["engine.ability.Ability"]] nyan_object = NyanObject("ApplyContinuousEffect", parents) @@ -146,6 +153,13 @@ def _create_objects(api_objects: dict[str, NyanObject]) -> None: nyan_object.set_fqon(fqon) api_objects.update({fqon: nyan_object}) + # engine.ability.type.Collision + parents = [api_objects["engine.ability.Ability"]] + nyan_object = NyanObject("Collision", parents) + fqon = "engine.ability.type.Collision" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + # engine.ability.type.Constructable parents = [api_objects["engine.ability.Ability"]] nyan_object = NyanObject("Constructable", parents) @@ -265,13 +279,6 @@ def _create_objects(api_objects: dict[str, NyanObject]) -> None: nyan_object.set_fqon(fqon) api_objects.update({fqon: nyan_object}) - # engine.ability.type.Hitbox - parents = [api_objects["engine.ability.Ability"]] - nyan_object = NyanObject("Hitbox", parents) - fqon = "engine.ability.type.Hitbox" - nyan_object.set_fqon(fqon) - api_objects.update({fqon: nyan_object}) - # engine.ability.type.Idle parents = [api_objects["engine.ability.Ability"]] nyan_object = NyanObject("Idle", parents) @@ -321,17 +328,17 @@ def _create_objects(api_objects: dict[str, NyanObject]) -> None: nyan_object.set_fqon(fqon) api_objects.update({fqon: nyan_object}) - # engine.ability.type.Passable + # engine.ability.type.PassiveTransformTo parents = [api_objects["engine.ability.Ability"]] - nyan_object = NyanObject("Passable", parents) - fqon = "engine.ability.type.Passable" + nyan_object = NyanObject("PassiveTransformTo", parents) + fqon = "engine.ability.type.PassiveTransformTo" nyan_object.set_fqon(fqon) api_objects.update({fqon: nyan_object}) - # engine.ability.type.PassiveTransformTo + # engine.ability.type.Pathable parents = [api_objects["engine.ability.Ability"]] - nyan_object = NyanObject("PassiveTransformTo", parents) - fqon = "engine.ability.type.PassiveTransformTo" + nyan_object = NyanObject("Pathable", parents) + fqon = "engine.ability.type.Pathable" nyan_object.set_fqon(fqon) api_objects.update({fqon: nyan_object}) @@ -518,6 +525,111 @@ def _create_objects(api_objects: dict[str, NyanObject]) -> None: nyan_object.set_fqon(fqon) api_objects.update({fqon: nyan_object}) + # engine.util.activity.Activity + parents = [api_objects["engine.root.Object"]] + nyan_object = NyanObject("Activity", parents) + fqon = "engine.util.activity.Activity" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + + # engine.util.activity.condition.Condition + parents = [api_objects["engine.root.Object"]] + nyan_object = NyanObject("Condition", parents) + fqon = "engine.util.activity.condition.Condition" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + + # engine.util.activity.condition.type.CommandInQueue + parents = [api_objects["engine.util.activity.condition.Condition"]] + nyan_object = NyanObject("CommandInQueue", parents) + fqon = "engine.util.activity.condition.type.CommandInQueue" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + + # engine.util.activity.condition.type.NextCommandIdle + parents = [api_objects["engine.util.activity.condition.Condition"]] + nyan_object = NyanObject("NextCommandIdle", parents) + fqon = "engine.util.activity.condition.type.NextCommandIdle" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + + # engine.util.activity.condition.type.NextCommandMove + parents = [api_objects["engine.util.activity.condition.Condition"]] + nyan_object = NyanObject("NextCommandMove", parents) + fqon = "engine.util.activity.condition.type.NextCommandMove" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + + # engine.util.activity.event.Event + parents = [api_objects["engine.root.Object"]] + nyan_object = NyanObject("Event", parents) + fqon = "engine.util.activity.event.Event" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + + # engine.util.activity.event.type.CommandInQueue + parents = [api_objects["engine.util.activity.event.Event"]] + nyan_object = NyanObject("CommandInQueue", parents) + fqon = "engine.util.activity.event.type.CommandInQueue" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + + # engine.util.activity.event.type.Wait + parents = [api_objects["engine.util.activity.event.Event"]] + nyan_object = NyanObject("Wait", parents) + fqon = "engine.util.activity.event.type.Wait" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + + # engine.util.activity.event.type.WaitAbility + parents = [api_objects["engine.util.activity.event.Event"]] + nyan_object = NyanObject("WaitAbility", parents) + fqon = "engine.util.activity.event.type.WaitAbility" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + + # engine.util.activity.node.Node + parents = [api_objects["engine.root.Object"]] + nyan_object = NyanObject("Node", parents) + fqon = "engine.util.activity.node.Node" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + + # engine.util.activity.node.type.Ability + parents = [api_objects["engine.util.activity.node.Node"]] + nyan_object = NyanObject("Ability", parents) + fqon = "engine.util.activity.node.type.Ability" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + + # engine.util.activity.node.type.End + parents = [api_objects["engine.util.activity.node.Node"]] + nyan_object = NyanObject("End", parents) + fqon = "engine.util.activity.node.type.End" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + + # engine.util.activity.node.type.Start + parents = [api_objects["engine.util.activity.node.Node"]] + nyan_object = NyanObject("Start", parents) + fqon = "engine.util.activity.node.type.Start" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + + # engine.util.activity.node.type.XOREventGate + parents = [api_objects["engine.util.activity.node.Node"]] + nyan_object = NyanObject("XOREventGate", parents) + fqon = "engine.util.activity.node.type.XOREventGate" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + + # engine.util.activity.node.type.XORGate + parents = [api_objects["engine.util.activity.node.Node"]] + nyan_object = NyanObject("XORGate", parents) + fqon = "engine.util.activity.node.type.XORGate" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + # engine.util.animation_override.AnimationOverride parents = [api_objects["engine.root.Object"]] nyan_object = NyanObject("AnimationOverride", parents) @@ -1260,27 +1372,6 @@ def _create_objects(api_objects: dict[str, NyanObject]) -> None: nyan_object.set_fqon(fqon) api_objects.update({fqon: nyan_object}) - # engine.util.passable_mode.PassableMode - parents = [api_objects["engine.root.Object"]] - nyan_object = NyanObject("PassableMode", parents) - fqon = "engine.util.passable_mode.PassableMode" - nyan_object.set_fqon(fqon) - api_objects.update({fqon: nyan_object}) - - # engine.util.passable_mode.type.Gate - parents = [api_objects["engine.util.passable_mode.PassableMode"]] - nyan_object = NyanObject("Gate", parents) - fqon = "engine.util.passable_mode.type.Gate" - nyan_object.set_fqon(fqon) - api_objects.update({fqon: nyan_object}) - - # engine.util.passable_mode.type.Normal - parents = [api_objects["engine.util.passable_mode.PassableMode"]] - nyan_object = NyanObject("Normal", parents) - fqon = "engine.util.passable_mode.type.Normal" - nyan_object.set_fqon(fqon) - api_objects.update({fqon: nyan_object}) - # engine.util.patch.NyanPatch parents = [api_objects["engine.root.Object"]] nyan_object = NyanObject("NyanPatch", parents) @@ -1309,6 +1400,13 @@ def _create_objects(api_objects: dict[str, NyanObject]) -> None: nyan_object.set_fqon(fqon) api_objects.update({fqon: nyan_object}) + # engine.util.path_type.PathType + parents = [api_objects["engine.root.Object"]] + nyan_object = NyanObject("PathType", parents) + fqon = "engine.util.path_type.PathType" + nyan_object.set_fqon(fqon) + api_objects.update({fqon: nyan_object}) + # engine.util.payment_mode.PaymentMode parents = [api_objects["engine.root.Object"]] nyan_object = NyanObject("PaymentMode", parents) @@ -2489,6 +2587,13 @@ def _insert_members(api_objects: dict[str, NyanObject]) -> None: member = NyanMember("transform_progress", member_type, None, None, 0) api_object.add_member(member) + # engine.ability.type.Activity + api_object = api_objects["engine.ability.type.Activity"] + + member_type = NyanMemberType(api_objects["engine.util.activity.Activity"]) + member = NyanMember("graph", member_type, None, None, 0) + api_object.add_member(member) + # engine.ability.type.ApplyContinuousEffect api_object = api_objects["engine.ability.type.ApplyContinuousEffect"] @@ -2561,6 +2666,13 @@ def _insert_members(api_objects: dict[str, NyanObject]) -> None: member = NyanMember("storage_elements", member_type, None, None, 0) api_object.add_member(member) + # engine.ability.type.Collision + api_object = api_objects["engine.ability.type.Collision"] + + member_type = NyanMemberType(api_objects["engine.util.hitbox.Hitbox"]) + member = NyanMember("hitbox", member_type, None, None, 0) + api_object.add_member(member) + # engine.ability.type.Constructable api_object = api_objects["engine.ability.type.Constructable"] @@ -2774,13 +2886,6 @@ def _insert_members(api_objects: dict[str, NyanObject]) -> None: member = NyanMember("mode", member_type, None, None, 0) api_object.add_member(member) - # engine.ability.type.Hitbox - api_object = api_objects["engine.ability.type.Hitbox"] - - member_type = NyanMemberType(api_objects["engine.util.hitbox.Hitbox"]) - member = NyanMember("hitbox", member_type, None, None, 0) - api_object.add_member(member) - # engine.ability.type.LineOfSight api_object = api_objects["engine.ability.type.LineOfSight"] @@ -2812,6 +2917,10 @@ def _insert_members(api_objects: dict[str, NyanObject]) -> None: member_type = NyanMemberType(MemberType.SET, (elem_type,)) member = NyanMember("modes", member_type, None, None, 0) api_object.add_member(member) + subtype = NyanMemberType(api_objects["engine.util.path_type.PathType"]) + member_type = NyanMemberType(MemberType.CHILDREN, (subtype,)) + member = NyanMember("path_type", member_type, None, None, 0) + api_object.add_member(member) # engine.ability.type.Named api_object = api_objects["engine.ability.type.Named"] @@ -2836,16 +2945,6 @@ def _insert_members(api_objects: dict[str, NyanObject]) -> None: member = NyanMember("terrain_overlay", member_type, None, None, 0) api_object.add_member(member) - # engine.ability.type.Passable - api_object = api_objects["engine.ability.type.Passable"] - - member_type = NyanMemberType(api_objects["engine.util.hitbox.Hitbox"]) - member = NyanMember("hitbox", member_type, None, None, 0) - api_object.add_member(member) - member_type = NyanMemberType(api_objects["engine.util.passable_mode.PassableMode"]) - member = NyanMember("mode", member_type, None, None, 0) - api_object.add_member(member) - # engine.ability.type.PassiveTransformTo api_object = api_objects["engine.ability.type.PassiveTransformTo"] @@ -2863,6 +2962,19 @@ def _insert_members(api_objects: dict[str, NyanObject]) -> None: member = NyanMember("transform_progress", member_type, None, None, 0) api_object.add_member(member) + # engine.ability.type.Pathable + api_object = api_objects["engine.ability.type.Pathable"] + + member_type = NyanMemberType(api_objects["engine.util.hitbox.Hitbox"]) + member = NyanMember("hitbox", member_type, None, None, 0) + api_object.add_member(member) + subtype = NyanMemberType(api_objects["engine.util.path_type.PathType"]) + key_type = NyanMemberType(MemberType.CHILDREN, (subtype,)) + elem_type = N_INT + member_type = NyanMemberType(MemberType.DICT, (key_type, elem_type)) + member = NyanMember("path_costs", member_type, None, None, 0) + api_object.add_member(member) + # engine.ability.type.ProductionQueue api_object = api_objects["engine.ability.type.ProductionQueue"] @@ -3154,6 +3266,64 @@ def _insert_members(api_objects: dict[str, NyanObject]) -> None: member = NyanMember("blacklisted_entities", member_type, None, None, 0) api_object.add_member(member) + # engine.util.activity.Activity + api_object = api_objects["engine.util.activity.Activity"] + + member_type = NyanMemberType(api_objects["engine.util.activity.node.type.Start"]) + member = NyanMember("start", member_type, None, None, 0) + api_object.add_member(member) + + # engine.util.activity.condition.Condition + api_object = api_objects["engine.util.activity.condition.Condition"] + + member_type = NyanMemberType(api_objects["engine.util.activity.node.Node"]) + member = NyanMember("next", member_type, None, None, 0) + api_object.add_member(member) + + # engine.util.activity.event.type.Wait + api_object = api_objects["engine.util.activity.event.type.Wait"] + + member = NyanMember("time", N_FLOAT, None, None, 0) + api_object.add_member(member) + + # engine.util.activity.node.type.Ability + api_object = api_objects["engine.util.activity.node.type.Ability"] + + member_type = NyanMemberType(api_objects["engine.util.activity.node.Node"]) + member = NyanMember("next", member_type, None, None, 0) + api_object.add_member(member) + subtype = NyanMemberType(api_objects["engine.ability.Ability"]) + member_type = NyanMemberType(MemberType.ABSTRACT, (subtype,)) + member = NyanMember("ability", member_type, None, None, 0) + api_object.add_member(member) + + # engine.util.activity.node.type.Start + api_object = api_objects["engine.util.activity.node.type.Start"] + + member_type = NyanMemberType(api_objects["engine.util.activity.node.Node"]) + member = NyanMember("next", member_type, None, None, 0) + api_object.add_member(member) + + # engine.util.activity.node.type.XOREventGate + api_object = api_objects["engine.util.activity.node.type.XOREventGate"] + + key_type = NyanMemberType(api_objects["engine.util.activity.event.Event"]) + value_type = NyanMemberType(api_objects["engine.util.activity.node.Node"]) + member_type = NyanMemberType(MemberType.DICT, (key_type, value_type)) + member = NyanMember("next", member_type, None, None, 0) + api_object.add_member(member) + + # engine.util.activity.node.type.XORGate + api_object = api_objects["engine.util.activity.node.type.XORGate"] + + elem_type = NyanMemberType(api_objects["engine.util.activity.condition.Condition"]) + member_type = NyanMemberType(MemberType.ORDEREDSET, (elem_type,)) + member = NyanMember("next", member_type, None, None, 0) + api_object.add_member(member) + member_type = NyanMemberType(api_objects["engine.util.activity.node.Node"]) + member = NyanMember("default", member_type, None, None, 0) + api_object.add_member(member) + # engine.util.animation_override.AnimationOverride api_object = api_objects["engine.util.animation_override.AnimationOverride"] @@ -3672,28 +3842,6 @@ def _insert_members(api_objects: dict[str, NyanObject]) -> None: member = NyanMember("range", N_FLOAT, None, None, 0) api_object.add_member(member) - # engine.util.passable_mode.PassableMode - api_object = api_objects["engine.util.passable_mode.PassableMode"] - - subtype = NyanMemberType(api_objects["engine.util.game_entity_type.GameEntityType"]) - elem_type = NyanMemberType(MemberType.CHILDREN, (subtype,)) - member_type = NyanMemberType(MemberType.SET, (elem_type,)) - member = NyanMember("allowed_types", member_type, None, None, 0) - api_object.add_member(member) - elem_type = NyanMemberType(api_objects["engine.util.game_entity.GameEntity"]) - member_type = NyanMemberType(MemberType.SET, (elem_type,)) - member = NyanMember("blacklisted_entities", member_type, None, None, 0) - api_object.add_member(member) - - # engine.util.passable_mode.type.Gate - api_object = api_objects["engine.util.passable_mode.type.Gate"] - - subtype = NyanMemberType(api_objects["engine.util.diplomatic_stance.DiplomaticStance"]) - elem_type = NyanMemberType(MemberType.CHILDREN, (subtype,)) - member_type = NyanMemberType(MemberType.SET, (elem_type,)) - member = NyanMember("stances", member_type, None, None, 0) - api_object.add_member(member) - # engine.util.patch.Patch api_object = api_objects["engine.util.patch.Patch"] @@ -4119,6 +4267,12 @@ def _insert_members(api_objects: dict[str, NyanObject]) -> None: member_type = NyanMemberType(MemberType.SET, (elem_type,)) member = NyanMember("ambience", member_type, None, None, 0) api_object.add_member(member) + subtype = NyanMemberType(api_objects["engine.util.path_type.PathType"]) + key_type = NyanMemberType(MemberType.CHILDREN, (subtype,)) + elem_type = N_INT + member_type = NyanMemberType(MemberType.DICT, (key_type, elem_type)) + member = NyanMember("path_costs", member_type, None, None, 0) + api_object.add_member(member) # engine.util.terrain.TerrainAmbient api_object = api_objects["engine.util.terrain.TerrainAmbient"] diff --git a/openage/convert/tool/api_export.py b/openage/convert/tool/api_export.py index e0d14e703e..b20752e330 100644 --- a/openage/convert/tool/api_export.py +++ b/openage/convert/tool/api_export.py @@ -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. """ Export tool for dumping the nyan API of the engine from the converter. @@ -12,8 +12,7 @@ from openage.nyan.import_tree import ImportTree from openage.util.fslike.directory import Directory from openage.util.fslike.union import Union, UnionPath -from openage.util.fslike.wrapper import (DirectoryCreator, - Synchronizer as AccessSynchronizer) +from openage.util.fslike.wrapper import DirectoryCreator from ...log import info @@ -50,7 +49,7 @@ def export_api(exportdir: UnionPath) -> None: info("Dumping info file...") targetdir = DirectoryCreator(exportdir).root - outdir = AccessSynchronizer(targetdir).root / "engine" + outdir = targetdir / "engine" # Modpack info file DataExporter.export([modpack.info], outdir) @@ -76,7 +75,7 @@ def create_modpack() -> Modpack: mod_def = modpack.get_info() - mod_def.set_info("engine", "0.4.0", repo="openage") + mod_def.set_info("engine", modpack_version="0.5.0", versionstr="0.5.0", repo="openage") mod_def.add_include("**") diff --git a/openage/convert/tool/driver.py b/openage/convert/tool/driver.py index a1b8bbfddf..61d53b0082 100644 --- a/openage/convert/tool/driver.py +++ b/openage/convert/tool/driver.py @@ -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. # # pylint: disable=too-many-return-statements @@ -8,14 +8,14 @@ """ from __future__ import annotations import typing +import timeit from ...log import info, dbg from ..processor.export.modpack_exporter import ModpackExporter from ..service.debug_info import debug_gamedata_format from ..service.debug_info import debug_string_resources, \ - debug_registered_graphics, debug_modpack -from ..service.init.changelog import (ASSET_VERSION) + debug_registered_graphics, debug_modpack, debug_execution_time from ..service.read.gamedata import get_gamespec from ..service.read.palette import get_palettes from ..service.read.register_media import get_existing_graphics @@ -39,8 +39,6 @@ def convert(args: Namespace) -> None: # clean args (set by convert_metadata for convert_media) del args.palettes - info(f"asset conversion complete; asset version: {ASSET_VERSION}", ) - def convert_metadata(args: Namespace) -> None: """ @@ -65,7 +63,11 @@ def convert_metadata(args: Namespace) -> None: if gamedata_path.exists(): gamedata_path.removerecursive() + # Record time taken for each stage + stages_time = {} + # Read .dat + stage_start = timeit.default_timer() debug_gamedata_format(args.debugdir, args.debug_info, args.game_version) gamespec = get_gamespec(args.srcdir, args.game_version, not args.flag("no_pickle_cache")) @@ -84,16 +86,40 @@ def convert_metadata(args: Namespace) -> None: existing_graphics = get_existing_graphics(args) debug_registered_graphics(args.debugdir, args.debug_info, existing_graphics) - # Convert + stage_end = timeit.default_timer() + info("Finished metadata read (%.2f seconds)", stage_end - stage_start) + stages_time.update({"read": stage_end - stage_start}) + + # nyan conversion + stage_start = timeit.default_timer() modpacks = args.converter.convert(gamespec, args, string_resources, existing_graphics) + stage_end = timeit.default_timer() + info("Finished data conversion (%.2f seconds)", stage_end - stage_start) + stages_time.update({"convert": stage_end - stage_start}) + + # Export modpacks + stage_start = timeit.default_timer() for modpack in modpacks: + mod_export_start = timeit.default_timer() ModpackExporter.export(modpack, args) debug_modpack(args.debugdir, args.debug_info, modpack) + mod_export_end = timeit.default_timer() + info("Finished export of modpack '%s' v%s (%.2f seconds)", + modpack.info.packagename, + modpack.info.version, + mod_export_end - mod_export_start) + + stage_end = timeit.default_timer() + info("Finished export (%.2f seconds)", stage_end - stage_start) + stages_time.update({"export": stage_end - stage_start}) + + debug_execution_time(args.debugdir, args.debug_info, stages_time) + # TODO: player palettes # player_palette = PlayerColorTable(palette) # data_formatter.add_data(player_palette.dump("player_palette")) diff --git a/openage/convert/tool/singlefile.py b/openage/convert/tool/singlefile.py index 78b67380b0..9d7f1b4583 100644 --- a/openage/convert/tool/singlefile.py +++ b/openage/convert/tool/singlefile.py @@ -1,10 +1,11 @@ -# Copyright 2015-2023 the openage authors. See copying.md for legal info. +# Copyright 2015-2024 the openage authors. See copying.md for legal info. """ Convert a single slp/wav file from some drs archive to a png/opus file. """ from __future__ import annotations +import sys from pathlib import Path @@ -59,6 +60,11 @@ def main(args, error): file_path = Path(args.filename) file_extension = file_path.suffix[1:].lower() + if sys.platform == "win32": + from openage.util.dll import DllDirectoryManager, default_paths + dll_manager = DllDirectoryManager(default_paths()) + dll_manager.add_directories() + if not (args.mode in ("sld", "drs-wav", "wav") or file_extension in ("sld", "wav")): if not args.palettes_path: raise RuntimeError("palettes-path needs to be specified for " diff --git a/openage/convert/tool/subtool/acquire_sourcedir.py b/openage/convert/tool/subtool/acquire_sourcedir.py index 5d9368e526..fd070b7825 100644 --- a/openage/convert/tool/subtool/acquire_sourcedir.py +++ b/openage/convert/tool/subtool/acquire_sourcedir.py @@ -1,4 +1,4 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. +# Copyright 2020-2024 the openage authors. See copying.md for legal info. # # pylint: disable=too-many-branches @@ -21,7 +21,6 @@ from urllib.request import urlopen from ....log import warn, info, dbg -from ....util.files import which from ....util.fslike.directory import CaseIgnoringDirectory, Directory if typing.TYPE_CHECKING: @@ -48,13 +47,15 @@ def expand_relative_path(path: str) -> AnyStr: return os.path.realpath(os.path.expandvars(os.path.expanduser(path))) -def wanna_convert() -> bool: +def prompt(msg: str, answer: typing.Union[bool, None] = None) -> bool: """ - Ask the user if assets should be converted. + Ask the user a yes/no question. + + :param msg: Message to display. + :param answer: Pre-determined answer (optional). """ - answer = None while answer is None: - print(" Do you want to convert assets? [Y/n]") + print(f" {msg} [Y/n]") user_selection = input("> ") if user_selection.lower() in {"yes", "y", ""}: @@ -66,88 +67,25 @@ def wanna_convert() -> bool: return answer -def wanna_download_trial() -> bool: +def wanna_convert(answer: typing.Union[bool, None] = None) -> bool: """ - Ask the user if the AoC trial should be downloaded. + Ask the user if assets should be converted. """ - answer = None - while answer is None: - print(" Do you want to download the AoC trial version? [Y/n]") - - user_selection = input("> ") - if user_selection.lower() in {"yes", "y", ""}: - answer = True - - elif user_selection.lower() in {"no", "n"}: - answer = False + return prompt("Do you want to convert assets?", answer=answer) - return answer - -def wanna_use_wine() -> bool: +def wanna_check_updates(answer: typing.Union[bool, None] = None) -> bool: """ - Ask the user if wine should be used. - Wine is not used if user has no wine installed. + Ask the user if they want to check for updates. """ - - # TODO: a possibility to call different wine binaries - # (e.g. wine-devel from wine upstream debian repos) - - if not which("wine"): - return False - - answer = None - long_prompt = True - while answer is None: - if long_prompt: - print( - " Should we call wine to determine an AOE installation? [Y/n]") - long_prompt = False - else: - # TODO: text-adventure here - print(" Don't know what you want. Use wine? [Y/n]") - - user_selection = input("> ") - if user_selection.lower() in {"yes", "y", ""}: - answer = True - - elif user_selection.lower() in {"no", "n"}: - answer = False - - return answer + return prompt("Do you want to check for updates?", answer=answer) -def set_custom_wineprefix() -> None: +def wanna_download_trial(answer: typing.Union[bool, None] = None) -> bool: """ - Allow the customization of the WINEPREFIX environment variable. + Ask the user if the AoC trial should be downloaded. """ - - print("The WINEPREFIX is a separate 'container' for windows " - "software installations.") - - current_wineprefix = os.environ.get("WINEPREFIX") - if current_wineprefix: - print(f"Currently: WINEPREFIX='{current_wineprefix}'") - - print("Enter a custom value or leave empty to keep it as-is:") - while True: - new_wineprefix = input("WINEPREFIX=") - - if not new_wineprefix: - break - - new_wineprefix = expand_relative_path(new_wineprefix) - - # test if it probably is a wineprefix - if (Path(new_wineprefix) / "drive_c").is_dir(): # pylint: disable=no-member - break - - print("This does not appear to be a valid WINEPREFIX.") - print("Enter a valid one, or leave it empty to skip.") - - # store the updated env variable for the wine subprocess - if new_wineprefix: - os.environ["WINEPREFIX"] = new_wineprefix + return prompt("Do you want to download the AoC trial version?", answer=answer) def query_source_dir(proposals: set[str]) -> AnyStr: diff --git a/openage/convert/value_object/conversion/de2/internal_nyan_names.py b/openage/convert/value_object/conversion/de2/internal_nyan_names.py index 6d1fe7f380..e4f3c995bd 100644 --- a/openage/convert/value_object/conversion/de2/internal_nyan_names.py +++ b/openage/convert/value_object/conversion/de2/internal_nyan_names.py @@ -1,4 +1,4 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. +# Copyright 2020-2024 the openage authors. See copying.md for legal info. # # pylint: disable=line-too-long @@ -51,6 +51,13 @@ 1790: ("Centurion", "centurion"), 1795: ("Dromon", "dromon"), + # TMR + 1800: ("CompositeBowman", "composite_bowman"), + 1803: ("Monaspa", "monaspa"), + 1811: ("WarriorPriest", "warrior_priest"), + 1813: ("Savar", "savar"), + 1817: ("QizilbashWarrior", "qizilbash_warrior"), + # TODO: These are upgrades 1737: ("EliteUrumiSwordsman", "elite_urumi_swordsman"), 1743: ("EliteChakramThrower", "elite_chakram_thrower"), @@ -60,6 +67,40 @@ 1761: ("EliteRRatha", "elite_rratha"), 1792: ("EliteCenturion", "elite_centurion"), 1793: ("Legionary", "legionary"), + + 1802: ("EliteCompositeBowman", "elite_composite_bowman"), + 1805: ("EliteMonaspa", "elite_monaspa"), + + # BfG + 2101: ("BfGUnkown_2101", "bfg_unkown_2101"), + 2102: ("BfGUnkown_2102", "bfg_unkown_2102"), + 2104: ("BfGUnkown_2104", "bfg_unkown_2104"), + 2105: ("BfGUnkown_2105", "bfg_unkown_2105"), + 2107: ("BfGUnkown_2107", "bfg_unkown_2107"), + 2108: ("BfGUnkown_2108", "bfg_unkown_2108"), + 2110: ("BfGUnkown_2110", "bfg_unkown_2110"), + 2111: ("BfGUnkown_2111", "bfg_unkown_2111"), + 2123: ("BfGUnkown_2123", "bfg_unkown_2123"), + 2124: ("BfGUnkown_2124", "bfg_unkown_2124"), + 2125: ("BfGUnkown_2125", "bfg_unkown_2125"), + 2126: ("BfGUnkown_2126", "bfg_unkown_2126"), + 2127: ("BfGUnkown_2127", "bfg_unkown_2127"), + 2128: ("BfGUnkown_2128", "bfg_unkown_2128"), + 2129: ("BfGUnkown_2129", "bfg_unkown_2129"), + 2130: ("BfGUnkown_2130", "bfg_unkown_2130"), + 2131: ("BfGUnkown_2131", "bfg_unkown_2131"), + 2132: ("BfGUnkown_2132", "bfg_unkown_2132"), + 2133: ("BfGUnkown_2133", "bfg_unkown_2133"), + 2134: ("BfGUnkown_2134", "bfg_unkown_2134"), + 2135: ("BfGUnkown_2135", "bfg_unkown_2135"), + 2138: ("BfGUnkown_2138", "bfg_unkown_2138"), + 2139: ("BfGUnkown_2139", "bfg_unkown_2139"), + 2140: ("BfGUnkown_2140", "bfg_unkown_2140"), + 2148: ("BfGUnkown_2148", "bfg_unkown_2148"), + 2149: ("BfGUnkown_2149", "bfg_unkown_2149"), + 2150: ("BfGUnkown_2150", "bfg_unkown_2150"), + 2151: ("BfGUnkown_2151", "bfg_unkown_2151"), + 2162: ("BfGUnkown_2162", "bfg_unkown_2162"), } # key: head unit id; value: (nyan object name, filename prefix) @@ -75,6 +116,13 @@ # DOI 1754: ("Caravanserai", "caravanserai"), + + # TMR + 1808: ("MuleCart", "mule_cart"), + + # BfG + 2119: ("BfGUnkown_2119", "bfg_unkown_2119"), + 2172: ("BfGUnkown_2172", "bfg_unkown_2172"), } # key: (head) unit id; value: (nyan object name, filename prefix) @@ -91,6 +139,8 @@ # key: head unit id; value: (nyan object name, filename prefix) # contains only new techs of DE2 TECH_GROUP_LOOKUPS = { + 46: ("Devotion", "devotion"), + 488: ("Kamandaran", "kamandaran"), 678: ("EliteKonnik", "elite_konnik"), 680: ("EliteKeshik", "elite_keshik"), @@ -139,11 +189,50 @@ 840: ("EliteGhulam", "elite_ghulam"), 843: ("EliteSHrivamshaRider", "elite_shrivamsha_rider"), + 875: ("Gambesons", "gambesons"), + # ROR 882: ("EliteCenturion", "elite_centurion"), 883: ("Ballistas", "ballistas"), 884: ("Comitatensis", "comitatensis"), 885: ("Legionary", "legionary"), + + # TMR + 918: ("EliteCompositeBowman", "elite_composite_bowman"), + 920: ("EliteMonaspa", "elite_monaspa"), + 921: ("Fereters", "fereters"), + 922: ("CilicianFleet", "cilician_fleet"), + 923: ("SvanTowers", "svan_towers"), + 924: ("AsnuariCavalry", "asnuari_cavalry"), + 929: ("FortifiedChurch", "fortified_church"), + 967: ("EliteQizilbashWarrior", "elite_qizilbash_warrior"), + + # BfG + 1110: ("BfGUnkown_1110", "bfg_unkown_1110"), + 1111: ("BfGUnkown_1111", "bfg_unkown_1111"), + 1112: ("BfGUnkown_1112", "bfg_unkown_1112"), + 1113: ("BfGUnkown_1113", "bfg_unkown_1113"), + 1120: ("BfGUnkown_1120", "bfg_unkown_1120"), + 1121: ("BfGUnkown_1121", "bfg_unkown_1121"), + 1122: ("BfGUnkown_1122", "bfg_unkown_1122"), + 1123: ("BfGUnkown_1123", "bfg_unkown_1123"), + 1130: ("BfGUnkown_1130", "bfg_unkown_1130"), + 1131: ("BfGUnkown_1131", "bfg_unkown_1131"), + 1132: ("BfGUnkown_1132", "bfg_unkown_1132"), + 1133: ("BfGUnkown_1133", "bfg_unkown_1133"), + 1161: ("BfGUnkown_1161", "bfg_unkown_1161"), + 1162: ("BfGUnkown_1162", "bfg_unkown_1162"), + 1165: ("BfGUnkown_1165", "bfg_unkown_1165"), + 1167: ("BfGUnkown_1167", "bfg_unkown_1167"), + 1173: ("BfGUnkown_1173", "bfg_unkown_1173"), + 1198: ("BfGUnkown_1198", "bfg_unkown_1198"), + 1202: ("BfGUnkown_1202", "bfg_unkown_1202"), + 1203: ("BfGUnkown_1203", "bfg_unkown_1203"), + 1204: ("BfGUnkown_1204", "bfg_unkown_1204"), + 1223: ("BfGUnkown_1223", "bfg_unkown_1223"), + 1224: ("BfGUnkown_1224", "bfg_unkown_1224"), + 1225: ("BfGUnkown_1225", "bfg_unkown_1225"), + 1226: ("BfGUnkown_1226", "bfg_unkown_1226"), } # key: civ index; value: (nyan object name, filename prefix) @@ -170,6 +259,15 @@ # ROR 43: ("Romans", "romans"), + + # TMR + 44: ("Armenians", "armenians"), + 45: ("Georgians", "georgians"), + + # BfG + 46: ("BfGUnkown_46", "bfg_unkown_46"), + 47: ("BfGUnkown_47", "bfg_unkown_47"), + 48: ("BfGUnkown_48", "bfg_unkown_48"), } # key: civ index; value: (civ ids, nyan object name, filename prefix) @@ -177,7 +275,7 @@ GRAPHICS_SET_LOOKUPS = { 0: ((0, 1, 2, 13, 14, 36), "WesternEuropean", "western_european"), 4: ((7, 37), "Byzantine", "byzantine"), - 6: ((19, 24, 43), "Mediterranean", "mediterranean"), + 6: ((19, 24, 43, 44, 45, 46, 47, 48), "Mediterranean", "mediterranean"), 7: ((20, 40, 41, 42), "Indian", "indian"), 8: ((22, 23, 32, 35, 38, 39), "EasternEuropean", "eastern_european"), 11: ((33, 34), "CentralAsian", "central_asian"), @@ -207,4 +305,6 @@ 37: "SiegeBallista", 38: "DE2Skirmisher", 39: "DE2CamelRider", + 40: "BfGUnkown_40", + 60: "BfGUnkown_60", } diff --git a/openage/convert/value_object/init/game_version.py b/openage/convert/value_object/init/game_version.py index e4364c3dde..2bffcbd3ed 100644 --- a/openage/convert/value_object/init/game_version.py +++ b/openage/convert/value_object/init/game_version.py @@ -1,4 +1,4 @@ -# Copyright 2020-2023 the openage authors. See copying.md for legal info. +# Copyright 2020-2024 the openage authors. See copying.md for legal info. # # pylint: disable=too-many-arguments @@ -35,7 +35,7 @@ def __init__( support: Support, game_version_info: list[tuple[list[str], dict[str, str]]], media_paths: list[tuple[str, list[str]]], - modpacks: list[str], + modpacks: dict[str, dict[str, str]], **flags ): """ diff --git a/openage/convert/value_object/read/genie_structure.py b/openage/convert/value_object/read/genie_structure.py index bfc6324f1c..dcfd0f3baf 100644 --- a/openage/convert/value_object/read/genie_structure.py +++ b/openage/convert/value_object/read/genie_structure.py @@ -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. # TODO pylint: disable=C,R @@ -456,30 +456,40 @@ def _read_primitive( array_members = [] allowed_member_type = None + if storage_type is StorageType.ARRAY_INT: + allowed_member_type = StorageType.INT_MEMBER + + elif storage_type is StorageType.ARRAY_FLOAT: + allowed_member_type = StorageType.FLOAT_MEMBER + + elif storage_type is StorageType.ARRAY_BOOL: + allowed_member_type = StorageType.BOOLEAN_MEMBER + + elif storage_type is StorageType.ARRAY_ID: + allowed_member_type = StorageType.ID_MEMBER + + elif storage_type is StorageType.ARRAY_STRING: + allowed_member_type = StorageType.STRING_MEMBER + for elem in result: if storage_type is StorageType.ARRAY_INT: gen_member = IntMember(var_name, elem) - allowed_member_type = StorageType.INT_MEMBER array_members.append(gen_member) elif storage_type is StorageType.ARRAY_FLOAT: gen_member = FloatMember(var_name, elem) - allowed_member_type = StorageType.FLOAT_MEMBER array_members.append(gen_member) elif storage_type is StorageType.ARRAY_BOOL: gen_member = BooleanMember(var_name, elem) - allowed_member_type = StorageType.BOOLEAN_MEMBER array_members.append(gen_member) elif storage_type is StorageType.ARRAY_ID: gen_member = IDMember(var_name, elem) - allowed_member_type = StorageType.ID_MEMBER array_members.append(gen_member) elif storage_type is StorageType.ARRAY_STRING: gen_member = StringMember(var_name, elem) - allowed_member_type = StorageType.STRING_MEMBER array_members.append(gen_member) else: diff --git a/openage/convert/value_object/read/media/datfile/lookup_dicts.py b/openage/convert/value_object/read/media/datfile/lookup_dicts.py index df0ba23d05..dfd2206612 100644 --- a/openage/convert/value_object/read/media/datfile/lookup_dicts.py +++ b/openage/convert/value_object/read/media/datfile/lookup_dicts.py @@ -1,4 +1,4 @@ -# Copyright 2021-2023 the openage authors. See copying.md for legal info. +# Copyright 2021-2024 the openage authors. See copying.md for legal info. """ Lookup dicts for the EnumLookupMember instances. @@ -279,11 +279,13 @@ 248: "DE2_UNKNOWN_248", 249: "DE2_UNKNOWN_249", 250: "DE2_UNKNOWN_250", + 501: "DE2_UNKNOWN_501", + 506: "DE2_UNKNOWN_506", } EFFECT_APPLY_TYPE = { # unused assignage: a = -1, b = -1, c = -1, d = 0 - -1: "DISABLED", + 255: "DISABLED", # if a != -1: a == unit_id, else b == unit_class_id; c = # attribute_id, d = new_value 0: "ATTRIBUTE_ABSSET", @@ -338,6 +340,11 @@ 102: "TECH_TOGGLE", # d == research_id 103: "TECH_TIME_MODIFY", # a == research_id, if c == 0: d==absval else d==relval + # unknown; used in DE2 BfG + 199: "UNKNOWN", + 200: "UNKNOWN", + 201: "UNKNOWN", + # attribute_id: # 0: hit points # 1: line of sight @@ -431,6 +438,7 @@ 149: "SHEAR", 150: "REGENERATION", 151: "FEITORIA", + 153: "RESOURCE_FOLLOW", 154: "LOOT", # Chieftains tech; looting on killing villagers, monks, trade carts 155: "BOOST_MOVE_AND_ATTACK", 768: "UNKNOWN_768", @@ -505,6 +513,8 @@ 37: "DE2_SIEGE_BALLISTA", 38: "DE2_SKIRMISHER", 39: "DE2_CAMEL_RIDER", + 40: "DE2_UNKNOWN_40", + 60: "DE2_UNKNOWN_60", } UNIT_CLASSES = { @@ -592,28 +602,28 @@ TERRAIN_RESTRICTIONS = { -0x01: "NONE", - 0x00: "ANY", - 0x01: "SHORELINE", - 0x02: "WATER", - 0x03: "WATER_SHIP_0x03", - 0x04: "FOUNDATION", + 0x00: "ANY", # projectiles + 0x01: "SHORELINE", # boar, deer, wolf + 0x02: "WATER", # unused in AoC + 0x03: "WATER_SHIP_0x03", # warships + 0x04: "FOUNDATION", # buildings 0x05: "NOWHERE", # can't place anywhere 0x06: "WATER_DOCK", # shallow water for dock placement - 0x07: "SOLID", - 0x08: "NO_ICE_0x08", + 0x07: "SOLID", # moving land units + 0x08: "NO_ICE_0x08", # resource piles (gold, stone, berries) 0x09: "SWGB_ONLY_WATER0", - 0x0A: "NO_ICE_0x0A", - 0x0B: "FOREST", - 0x0C: "UNKNOWN_0x0C", - 0x0D: "WATER_0x0D", # great fish - 0x0E: "UNKNOWN_0x0E", - 0x0F: "WATER_SHIP_0x0F", # transport ship + 0x0A: "NO_ICE_0x0A", # gate, palisades, walls + 0x0B: "FOREST", # trees + 0x0C: "UNKNOWN_0x0C", # projectile explosions on bridge? + 0x0D: "WATER_0x0D", # great fish, fishtrap, fishing ship + 0x0E: "UNKNOWN_0x0E", # projectile decay on bridge? + 0x0F: "WATER_SHIP_0x0F", # transport ship, longboat 0x10: "GRASS_SHORELINE", # for gates and walls 0x11: "WATER_ANY_0x11", - 0x12: "UNKNOWN_0x12", - 0x13: "FISH_NO_ICE", - 0x14: "WATER_ANY_0x14", - 0x15: "WATER_SHALLOW", + 0x12: "UNKNOWN_0x12", # projectile decay on bridge? + 0x13: "WATER_ICE", # small fish + 0x14: "NO_WATER", # siege units, trade cart + 0x15: "WATER_SHALLOW", # sea walls 0x16: "SWGB_GRASS_SHORE", 0x17: "SWGB_ANY", 0x18: "SWGB_ONLY_WATER1", @@ -752,7 +762,9 @@ 0x04: "CAVALRY", 0x07: "SWGB_NO_JEDI", 0x08: "MONK", + 0x09: "DE2_FORTIFIED_CHURCH", 0x0b: "NOCAVALRY", 0x0f: "ALL", 0x10: "SWGB_LIVESTOCK", + 0x40: "DE2_UNKNOWN_40", } diff --git a/openage/convert/value_object/read/media/datfile/tech.py b/openage/convert/value_object/read/media/datfile/tech.py index a6720d3c25..54c350dadd 100644 --- a/openage/convert/value_object/read/media/datfile/tech.py +++ b/openage/convert/value_object/read/media/datfile/tech.py @@ -1,4 +1,4 @@ -# Copyright 2013-2023 the openage authors. See copying.md for legal info. +# Copyright 2013-2024 the openage authors. See copying.md for legal info. # TODO pylint: disable=C,R from __future__ import annotations @@ -31,7 +31,7 @@ def get_data_format_members( """ data_format = [ (READ_GEN, "type_id", StorageType.ID_MEMBER, EnumLookupMember( - raw_type="int8_t", + raw_type="uint8_t", type_name="effect_apply_type", lookup_dict=EFFECT_APPLY_TYPE )), diff --git a/openage/convert/value_object/read/media/datfile/unit.py b/openage/convert/value_object/read/media/datfile/unit.py index c0cdc29172..ae0e7c5f99 100644 --- a/openage/convert/value_object/read/media/datfile/unit.py +++ b/openage/convert/value_object/read/media/datfile/unit.py @@ -1,4 +1,4 @@ -# Copyright 2013-2023 the openage authors. See copying.md for legal info. +# Copyright 2013-2024 the openage authors. See copying.md for legal info. # TODO pylint: disable=C,R,too-many-lines from __future__ import annotations @@ -695,7 +695,8 @@ def get_data_format_members( if game_version.edition.game_id == "AOE2DE": data_format.extend([ - (READ_GEN, "drop_sites", StorageType.ARRAY_ID, "int16_t[3]"), + (READ, "drop_sites_count", StorageType.INT_MEMBER, "int16_t"), + (READ_GEN, "drop_sites", StorageType.ARRAY_ID, "int16_t[drop_sites_count]"), ]) else: data_format.extend([ diff --git a/openage/convert/value_object/read/media/drs.py b/openage/convert/value_object/read/media/drs.py index 72bf6a0679..8e3410f614 100644 --- a/openage/convert/value_object/read/media/drs.py +++ b/openage/convert/value_object/read/media/drs.py @@ -1,4 +1,4 @@ -# Copyright 2013-2022 the openage authors. See copying.md for legal info. +# Copyright 2013-2024 the openage authors. See copying.md for legal info. """ Code for reading Genie .DRS archives. @@ -12,7 +12,7 @@ from .....log import spam, dbg from .....util.filelike.stream import StreamFragment -from .....util.fslike.filecollection import FileCollection +from .....util.fslike.filecollection import FileCollection, FileEntry from .....util.strings import decode_until_null from .....util.struct import NamedStruct @@ -87,6 +87,23 @@ class DRSFileInfo(NamedStruct): file_size = "i" +class DRSEntry(FileEntry): + """ + Entry in a DRS archive. + """ + + def __init__(self, fileobj: GuardedFile, offset: int, size: int): + self.fileobj = fileobj + self.offset = offset + self.entry_size = size + + def open_r(self): + return StreamFragment(self.fileobj, self.offset, self.entry_size) + + def size(self) -> int: + return self.entry_size + + class DRS(FileCollection): """ represents a file archive in DRS format. @@ -133,14 +150,9 @@ def __init__(self, fileobj: GuardedFile, game_version: GameVersion): self.tables.append(table_header) for filename, offset, size in self.read_tables(): - def open_r(offset=offset, size=size): - """ Returns a opened ('rb') file-like object for fileobj. """ - return StreamFragment(self.fileobj, offset, size) - - self.add_fileentry( - [filename.encode()], - (open_r, None, lambda size=size: size, None) - ) + file_entry = DRSEntry(self.fileobj, offset, size) + + self.add_fileentry([filename.encode()], file_entry) def read_tables(self) -> typing.Generator[tuple[str, str, str], None, None]: """ 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/cppinterface/exctranslate.pyx b/openage/cppinterface/exctranslate.pyx index 9c6be25d36..ea99fc7042 100644 --- a/openage/cppinterface/exctranslate.pyx +++ b/openage/cppinterface/exctranslate.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. """ Provides the raise_py_exception and describe_py_exception callbacks for @@ -10,21 +10,14 @@ from cpython.exc cimport ( PyErr_Occurred, PyErr_Fetch, PyErr_NormalizeException, - PyErr_SetObject, - PyErr_Restore -) -from cpython.pystate cimport ( - PyThreadState, - PyThreadState_Get + PyErr_SetObject ) -from libcpp.string cimport string from libcpp cimport bool as cppbool -from libopenage.log.level cimport level, err as lvl_err from libopenage.log.message cimport message from libopenage.error.error cimport Error -from libopenage.error.backtrace cimport Backtrace, backtrace_symbol +from libopenage.error.backtrace cimport backtrace_symbol from libopenage.pyinterface.functional cimport Func1 from libopenage.pyinterface.pyexception cimport ( PyException, @@ -36,26 +29,29 @@ from libopenage.pyinterface.exctranslate cimport ( set_exc_translation_funcs ) -import importlib from ..testing.testing import TestError -from ..log import err, info +from ..log import info cdef extern from "Python.h": int PyException_SetTraceback(PyObject *ex, PyObject *tb) -cdef extern from "traceback.h": - void _PyTraceback_Add(const char *funcname, const char *filename, int lineno) +# _PyTraceback_Add has been made private in Python 3.13 +# see https://github.com/python/cpython/pull/108453 +# TODO: Find another solution to add tracebacks +# cdef extern from "traceback.h": +# void _PyTraceback_Add(const char *funcname, const char *filename, int lineno) cdef void PyTraceback_Add(const char *functionname, const char *filename, int lineno) noexcept with gil: """ Add a new traceback stack frame. - Redirects to Python's internal _PyTraceback_Add function. - """ - # possible since 3.4.3 due to http://bugs.python.org/issue24436. - # the function will likely remain internal due to https://bugs.python.org/issue24743 - _PyTraceback_Add(functionname, filename, lineno) + Note: Currently does nothing, because _PyTraceback_Add is no longer + accessible since Python 3.13. + + TODO: Find another solution to add tracebacks. + """ + # _PyTraceback_Add(functionname, filename, lineno) cdef class CPPMessageObject: diff --git a/openage/game/main.py b/openage/game/main.py index 15fb0a3962..1ad1378426 100644 --- a/openage/game/main.py +++ b/openage/game/main.py @@ -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. # # pylint: disable=too-many-locals @@ -8,6 +8,7 @@ from __future__ import annotations import typing + from ..log import info if typing.TYPE_CHECKING: @@ -27,9 +28,27 @@ def init_subparser(cli: ArgumentParser) -> None: help="run without displaying graphics") cli.add_argument( - "--modpacks", nargs="+", required=True, + "--modpacks", nargs="+", required=True, type=str, help="list of modpacks to load") + cli.add_argument( + "--check-updates", action='store_true', + 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): """ @@ -43,9 +62,10 @@ def main(args, error): from .main_cpp import run_game from .. import config from ..assets import get_asset_path - from ..convert.main import conversion_required, convert_assets from ..convert.tool.api_export import export_api from ..convert.service.init.api_export_required import api_export_required + from ..convert.service.init.changelog import check_updates + from ..convert.service.init.modpack_search import enumerate_modpacks from ..cppinterface.setup import setup as cpp_interface_setup from ..cvar.location import get_config_path from ..util.fslike.union import Union @@ -77,16 +97,27 @@ def main(args, error): converted_path.mkdirs() export_api(converted_path) - # ensure that the assets have been converted - if conversion_required(asset_path): - convert_assets(asset_path, args) - - # modpacks - mods = [] + # ensure that modpacks are available + modpack_dir = asset_path / "converted" + available_modpacks = enumerate_modpacks(modpack_dir, exclude={"engine"}) for modpack in args.modpacks: - mods.append(modpack.encode("utf-8")) - - args.modpacks = mods + if modpack not in available_modpacks: + raise FileNotFoundError(f"Modpack '{modpack}' not found in {modpack_dir}") + + # check if the converted modpacks are up to date + if args.check_updates: + check_updates(available_modpacks, args.cfg_dir / "converter" / "games") + + # 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 685e5b6bc9..05266ec744 100644 --- a/openage/main/main.py +++ b/openage/main/main.py @@ -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. """ Main engine entry point for openage. @@ -25,9 +25,22 @@ def init_subparser(cli: ArgumentParser): help="run without displaying graphics") cli.add_argument( - "--modpacks", nargs="+", + "--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): """ @@ -42,8 +55,10 @@ def main(args, error): from .main_cpp import run_game from .. import config from ..assets import get_asset_path - from ..convert.main import conversion_required, convert_assets + from ..convert.main import convert_assets from ..convert.service.init.api_export_required import api_export_required + from ..convert.service.init.changelog import check_updates + from ..convert.service.init.modpack_search import enumerate_modpacks, query_modpack from ..convert.tool.api_export import export_api from ..convert.tool.subtool.acquire_sourcedir import wanna_convert from ..cppinterface.setup import setup as cpp_interface_setup @@ -77,22 +92,40 @@ def main(args, error): converted_path.mkdirs() export_api(converted_path) - # ensure that the assets have been converted - if wanna_convert() or conversion_required(asset_path): + # check if modpacks need to be converted + if wanna_convert(): convert_assets(asset_path, args) + available_modpacks = enumerate_modpacks(asset_path / "converted", exclude={"engine"}) + if len(available_modpacks) == 0: + info("No modpacks have been found") + if not args.modpacks: + info("Starting bare 'engine' mode") + args.modpacks = ["engine"] + + # check if the converted modpacks are up to date + check_updates(available_modpacks, args.cfg_dir / "converter" / "games") + # pass modpacks to engine if args.modpacks: - mods = [] + # ensure that specified modpacks are available for modpack in args.modpacks: - mods.append(modpack.encode("utf-8")) + if modpack not in available_modpacks: + raise FileNotFoundError( + f"Modpack '{modpack}' not found in {asset_path / 'converted'}") - args.modpacks = mods + args.modpacks = [modpack.encode("utf-8") for modpack in args.modpacks] else: - from ..convert.service.init.modpack_search import enumerate_modpacks, query_modpack - avail_modpacks = enumerate_modpacks(asset_path / "converted") - args.modpacks = [query_modpack(avail_modpacks).encode("utf-8")] + 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/openage/nyan/import_tree.py b/openage/nyan/import_tree.py index 8a3880ff8d..5e61feb55c 100644 --- a/openage/nyan/import_tree.py +++ b/openage/nyan/import_tree.py @@ -7,6 +7,8 @@ from enum import Enum import typing +from openage.log import warn + if typing.TYPE_CHECKING: from openage.convert.entity_object.export.formats.nyan_file import NyanFile from openage.nyan.nyan_structs import NyanObject @@ -161,7 +163,9 @@ def add_alias(self, fqon: tuple[str], alias: str) -> None: current_node = current_node.get_child(node_str) except KeyError: # as err: - # TODO: Do not silently fail + # TODO: Fail when the fqon is not found in the tree + warn(f"fqon '{'.'.join(fqon)}' " + "could not be found in import tree") return # raise KeyError(f"fqon '{'.'.join(fqon)}' " # "could not be found in import tree") from err diff --git a/openage/pathfinding/CMakeLists.txt b/openage/pathfinding/CMakeLists.txt new file mode 100644 index 0000000000..03ab1f8e49 --- /dev/null +++ b/openage/pathfinding/CMakeLists.txt @@ -0,0 +1,7 @@ +add_cython_modules( + tests.pyx +) + +add_py_modules( + __init__.py +) diff --git a/openage/pathfinding/__init__.py b/openage/pathfinding/__init__.py new file mode 100644 index 0000000000..8c497dfafb --- /dev/null +++ b/openage/pathfinding/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2024-2024 the openage authors. See copying.md for legal info. + +""" +openage pathfinding system +""" diff --git a/openage/pathfinding/tests.pyx b/openage/pathfinding/tests.pyx new file mode 100644 index 0000000000..f180cb786d --- /dev/null +++ b/openage/pathfinding/tests.pyx @@ -0,0 +1,49 @@ +# Copyright 2024-2024 the openage authors. See copying.md for legal info. + +""" +tests for the pathfinding system. +""" + +import argparse + +from libopenage.util.path cimport Path as Path_cpp +from libopenage.pyinterface.pyobject cimport PyObj +from cpython.ref cimport PyObject +from libopenage.pathfinding.demo.tests cimport path_demo as path_demo_c + +def path_demo(list argv): + """ + invokes the available pathfinding demos. + """ + + cmd = argparse.ArgumentParser( + prog='... path_demo', + description='Demo of the pathfinding system') + cmd.add_argument("test_id", type=int, help="id of the demo to run.") + cmd.add_argument("--asset-dir", + help="Use this as an additional asset directory.") + cmd.add_argument("--cfg-dir", + help="Use this as an additional config directory.") + + args = cmd.parse_args(argv) + + from ..cvar.location import get_config_path + from ..assets import get_asset_path + from ..util.fslike.union import Union + + # create virtual file system for data paths + root = Union().root + + # mount the assets folder union at "assets/" + root["assets"].mount(get_asset_path(args.asset_dir)) + + # mount the config folder at "cfg/" + root["cfg"].mount(get_config_path(args.cfg_dir)) + + cdef int demo_id = args.test_id + + cdef Path_cpp root_cpp = Path_cpp(PyObj(root.fsobj), + root.parts) + + with nogil: + path_demo_c(demo_id, root_cpp) diff --git a/openage/testing/testlist.py b/openage/testing/testlist.py index 939732bd0d..8227537697 100644 --- a/openage/testing/testlist.py +++ b/openage/testing/testlist.py @@ -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. """ Lists of all possible tests; enter your tests here. """ @@ -26,7 +26,6 @@ def tests_py(): yield "openage.assets.test" yield ("openage.cabextract.test.test", "test CAB archive extraction", lambda env: env["has_assets"]) - yield "openage.convert.service.init.changelog.test" yield "openage.cppinterface.exctranslate_tests.cpp_to_py" yield ("openage.cppinterface.exctranslate_tests.cpp_to_py_bounce", "translates the exception back and forth a few times") @@ -52,6 +51,8 @@ def demos_py(): "play pong on steroids through future prediction") yield ("openage.gamestate.tests.simulation_demo", "showcases the game simulation") + yield ("openage.pathfinding.tests.path_demo", + "showcases the pathfinding system") yield ("openage.renderer.tests.renderer_demo", "showcases the renderer") yield ("openage.renderer.tests.renderer_stresstest", @@ -85,6 +86,7 @@ def tests_cpp(): yield "openage::datastructure::tests::pairing_heap" yield "openage::job::tests::test_job_manager" yield "openage::path::tests::path_node", "pathfinding" + yield "openage::path::tests::flow_field", "pathfinding" yield "openage::pyinterface::tests::pyobject" yield "openage::pyinterface::tests::err_py_to_cpp" yield "openage::renderer::tests::font" @@ -99,7 +101,6 @@ def tests_cpp(): yield "openage::util::tests::vector" yield "openage::util::tests::siphash" yield "openage::util::tests::array_conversion" - yield "openage::input::legacy::tests::parse_event_string", "keybinds parsing" yield "openage::curve::tests::container" yield "openage::curve::tests::curve_types" yield "openage::event::tests::eventtrigger" diff --git a/openage/util/CMakeLists.txt b/openage/util/CMakeLists.txt index 3d58384dee..cc31682da6 100644 --- a/openage/util/CMakeLists.txt +++ b/openage/util/CMakeLists.txt @@ -3,6 +3,7 @@ add_py_modules( bytequeue.py context.py decorators.py + dll.py files.py fsprinting.py hash.py @@ -15,6 +16,7 @@ add_py_modules( struct.py system.py threading.py + version.py ) add_subdirectory(filelike) diff --git a/openage/util/dll.py b/openage/util/dll.py new file mode 100644 index 0000000000..a3d23be7f1 --- /dev/null +++ b/openage/util/dll.py @@ -0,0 +1,122 @@ +# Copyright 2024-2024 the openage authors. See copying.md for legal info. + +""" +Windows-specific loading of compiled Python modules and DLLs. +""" + +import inspect +import os +import sys + +# python.dll location +DEFAULT_PYTHON_DLL_DIR = os.path.dirname(sys.executable) + +# openage.dll locations (relative to this file) +DEFAULT_OPENAGE_DLL_DIRs = [ + "../../libopenage/Debug", + "../../libopenage/Release", + "../../libopenage/RelWithDebInfo", + "../../libopenage/MinSizeRel", +] + +# nyan.dll locations (relative to this file) +DEFAULT_NYAN_DLL_DIRS = [ + "../../../../nyan/build/nyan/Debug", + "../../../../nyan/build/nyan/Release", + "../../../../nyan/build/nyan/RelWithDebInfo", + "../../../../nyan/build/nyan/MinSizeRel", + "../../nyan-external/bin/nyan/Debug", + "../../nyan-external/bin/nyan/Release", + "../../nyan-external/bin/nyan/RelWithDebInfo", + "../../nyan-external/bin/nyan/MinSizeRel", +] + + +class DllDirectoryManager: + """ + Manages directories that should be added to/removed from Python's DLL search path. + + All dependent DLLs or compiled cython modules that are not in Python's default search path + mst be added manually at runtime. Basically, this applies to all openage-specific libraries. + """ + + def __init__(self, directory_paths: list[str]): + """ + Create a new DLL directory manager. + + :param directory_paths: Absolute paths to the directories that are added. + """ + # Directory paths + self.directories = directory_paths + + # Store handles for added directories + self.handles = [] + + def add_directories(self): + """ + Add the manager's directories to Python's DLL search path. + """ + for directory in self.directories: + handle = os.add_dll_directory(directory) + self.handles.append(handle) + + def remove_directories(self): + """ + Remove the manager's directories from Python's DLL search path. + """ + for handle in self.handles: + handle.close() + + self.handles = [] + + def __del__(self): + """ + Ensure that DLL paths are removed when the object is deleted. + """ + self.remove_directories() + + def __enter__(self): + """ + Enter a context guard. + """ + self.add_directories() + + def __exit__(self, exc_type, exc_value, traceback): + """ + Exit a context guard. + """ + self.remove_directories() + + def __getstate__(self): + """ + Change pickling behavior so that directory handles are not serialized. + """ + content = self.__dict__ + content["handles"] = [] + return content + + +def default_paths() -> list[str]: + """ + Create a list of default paths. + """ + directory_paths = [] + + # Add Python DLL search path + directory_paths.append(DEFAULT_PYTHON_DLL_DIR) + + file_dir = os.path.dirname(os.path.abspath(inspect.getsourcefile(lambda: 0))) + + # Add openage DLL search paths + for candidate in DEFAULT_OPENAGE_DLL_DIRs: + path = os.path.join(file_dir, candidate) + if os.path.exists(path): + directory_paths.append(path) + + # Add nyan DLL search paths + for candidate in DEFAULT_NYAN_DLL_DIRS: + path = os.path.join(file_dir, candidate) + if os.path.exists(path): + directory_paths.append(path) + + return directory_paths diff --git a/openage/util/fslike/filecollection.py b/openage/util/fslike/filecollection.py index 86df01533b..f77b034099 100644 --- a/openage/util/fslike/filecollection.py +++ b/openage/util/fslike/filecollection.py @@ -1,9 +1,11 @@ -# Copyright 2015-2022 the openage authors. See copying.md for legal info. +# Copyright 2015-2024 the openage authors. See copying.md for legal info. """ Provides Filecollection, a utility class for combining multiple file-like objects to a FSLikeObject. """ +from __future__ import annotations +import typing from collections import OrderedDict from io import UnsupportedOperation @@ -12,6 +14,9 @@ from .abstract import FSLikeObject from .path import Path +if typing.TYPE_CHECKING: + from openage.util.filelike.stream import StreamFragment + class FileCollection(FSLikeObject): """ @@ -59,14 +64,12 @@ def get_direntries(self, parts=None, create: bool = False) -> tuple[OrderedDict, return entries - def add_fileentry(self, parts, fileentry): + def add_fileentry(self, parts, fileentry: FileEntry): """ Adds a file entry (and parent directory entries, if needed). This method should not be called directly; instead, use the add_file method of Path objects that were obtained from this. - - fileentry must be open_r, open_w, size, mtime. """ if not parts: raise IsADirectoryError("FileCollection.root is a directory") @@ -79,11 +82,9 @@ def add_fileentry(self, parts, fileentry): entries[0][name] = fileentry - def get_fileentry(self, parts): + def get_fileentry(self, parts) -> FileEntry: """ Gets a file entry. Helper method for internal use. - - Returns open_r, open_w, size, mtime """ if not parts: raise IsADirectoryError( @@ -101,24 +102,30 @@ def get_fileentry(self, parts): return entries[0][name] - def open_r(self, parts) -> None: - open_r, _, _, _ = self.get_fileentry(parts) + def open_r(self, parts: list[bytes]) -> StreamFragment: + entry = self.get_fileentry(parts) + + open_r = entry.open_r() if open_r is None: raise UnsupportedOperation( "not readable: " + b"/".join(parts).decode(errors='replace')) - return open_r() + return open_r + + def open_w(self, parts: list[bytes]): + entry = self.get_fileentry(parts) - def open_w(self, parts) -> None: - _, open_w, _, _ = self.get_fileentry(parts) + open_w = entry.open_w() if open_w is None: raise UnsupportedOperation( "not writable: " + b"/".join(parts).decode(errors='replace')) + return open_w + def list(self, parts): fileentries, subdirs = self.get_direntries(parts) @@ -126,20 +133,14 @@ def list(self, parts): yield from fileentries def filesize(self, parts) -> int: - _, _, filesize, _ = self.get_fileentry(parts) + entry = self.get_fileentry(parts) - if filesize is None: - return None - - return filesize() + return entry.size() def mtime(self, parts) -> float: - _, _, _, mtime = self.get_fileentry(parts) - - if mtime is None: - return None + entry = self.get_fileentry(parts) - return mtime() + return entry.mtime() def mkdirs(self, parts) -> None: self.get_direntries(parts, create=True) @@ -248,3 +249,34 @@ def add_file_from_path(self, path: Path) -> None: open_w = None self.add_file(path.open_r, open_w, path.filesize, path.mtime) + + +class FileEntry: + """ + Entry in a file collection archive. + """ + # pylint: disable=no-self-use + + def open_r(self) -> StreamFragment: + """ + Returns a file-like object for reading. + """ + raise UnsupportedOperation("FileEntry.open_r") + + def open_w(self): + """ + Returns a file-like object for writing. + """ + raise UnsupportedOperation("FileEntry.open_w") + + def size(self) -> int: + """ + Returns the size of the entr<. + """ + raise UnsupportedOperation("FileEntry.size") + + def mtime(self) -> float: + """ + Returns the modification time of the entry. + """ + raise UnsupportedOperation("FileEntry.mtime") diff --git a/openage/util/fslike/path.py b/openage/util/fslike/path.py index f69479360e..ccdf8cc2b8 100644 --- a/openage/util/fslike/path.py +++ b/openage/util/fslike/path.py @@ -4,9 +4,12 @@ Provides Path, which is analogous to pathlib.Path, and the type of FSLikeObject.root. """ +from typing import NoReturn, Union from io import UnsupportedOperation, TextIOWrapper -from typing import NoReturn +import os +import pathlib +import tempfile class Path: @@ -32,7 +35,7 @@ class Path: # lower. # pylint: disable=too-many-public-methods - def __init__(self, fsobj, parts=None): + def __init__(self, fsobj, parts: Union[str, bytes, bytearray, list, tuple] = None): if isinstance(parts, str): parts = parts.encode() @@ -63,6 +66,9 @@ def __init__(self, fsobj, parts=None): self.fsobj = fsobj + # Set to True by create_temp_file or create_temp_dir + self.is_temp: bool = False + # use tuple instead of list to prevent accidential modification self.parts = tuple(result) @@ -143,6 +149,10 @@ def open_w(self): """ open with mode='wb' """ return self.fsobj.open_w(self.parts) + def open_a(self): + """ open with mode='ab' """ + return self.fsobj.open_a(self.parts) + def _get_native_path(self): """ return the native path (usable by your kernel) of this path, @@ -330,3 +340,33 @@ def mount(self, pathobj, priority=0) -> NoReturn: # pylint: disable=no-self-use,unused-argument # TODO: https://github.com/PyCQA/pylint/issues/2329 raise PermissionError("Do not call mount on Path instances!") + + @staticmethod + def get_temp_file(): + """ + Creates a temporary file. + """ + temp_fd, temp_file = tempfile.mkstemp() + + # Close the file descriptor to release resources + os.close(temp_fd) + + # Wrap the temporary file path in a Path object and return it + path = Path(pathlib.Path(temp_file)) + path.is_temp = True + + return path + + @staticmethod + def get_temp_dir(): + """ + Creates a temporary directory. + """ + # Create a temporary directory using tempfile.mkdtemp + temp_dir = tempfile.mkdtemp() + + # Wrap the temporary directory path in a Path object and return it + path = Path(pathlib.Path(temp_dir)) + path.is_temp = True + + return path diff --git a/openage/util/fslike/union.py b/openage/util/fslike/union.py index eb28129d01..7b2ca8a8f2 100644 --- a/openage/util/fslike/union.py +++ b/openage/util/fslike/union.py @@ -116,6 +116,14 @@ def open_w(self, parts): raise UnsupportedOperation( "not writable: " + b'/'.join(parts).decode(errors='replace')) + def open_a(self, parts): + for path in self.candidate_paths(parts): + if path.writable(): + return path.open_a() + + raise UnsupportedOperation( + "not appendable: " + b'/'.join(parts).decode(errors='replace')) + def resolve_r(self, parts): for path in self.candidate_paths(parts): if path.is_file() or path.is_dir(): diff --git a/openage/util/struct.py b/openage/util/struct.py index fa092669d5..c56d7ed023 100644 --- a/openage/util/struct.py +++ b/openage/util/struct.py @@ -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. """ Provides some classes designed to expand the functionality of struct.struct @@ -48,7 +48,7 @@ def __new__(mcs, name, bases, classdict, **kwds): raise SyntaxError("endianness has been given multiple times") if value not in "@=<>!": - raise SyntaxError("endianess: expected one of @=<>!") + raise SyntaxError("endianness: expected one of @=<>!") specstr = value continue @@ -95,7 +95,7 @@ class NamedStruct(metaclass=NamedStructMeta): Alternatively, attributes may be set to None; those are ignored, and may be set manually at some later point. - The first member must be 'endianess'. + The first member must be 'endianness'. Example: diff --git a/openage/util/version.py b/openage/util/version.py new file mode 100644 index 0000000000..712b04ec79 --- /dev/null +++ b/openage/util/version.py @@ -0,0 +1,84 @@ +# Copyright 2024-2024 the openage authors. See copying.md for legal info. + +""" +Handling of version information for openage. +""" +from __future__ import annotations + +import re + +SEMVER_REGEX = re.compile( + (r"^(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)" + r"(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)" + r"(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?" + r"(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$")) + + +class SemanticVersion: + """ + Semantic versioning information. + """ + + def __init__(self, version: str) -> None: + """ + Create a new semantic version object from a version string. + + :param version: The version string to parse. + """ + match = SEMVER_REGEX.match(version) + if not match: + raise ValueError(f"Invalid semantic version: {version}") + + self.major = int(match.group("major")) + self.minor = int(match.group("minor")) + self.patch = int(match.group("patch")) + self.prerelease = match.group("prerelease") + self.buildmetadata = match.group("buildmetadata") + + def __lt__(self, other: SemanticVersion) -> bool: + if self.major < other.major: + return True + if self.minor < other.minor: + return True + if self.patch < other.patch: + return True + + return False + + def __le__(self, other: SemanticVersion) -> bool: + if self.major <= other.major: + return True + + if self.minor <= other.minor: + return True + + if self.patch <= other.patch: + return True + + return False + + def __eq__(self, other: SemanticVersion) -> bool: + return (self.major == other.major and + self.minor == other.minor and + self.patch == other.patch) + + def __ne__(self, other: SemanticVersion) -> bool: + return not self.__eq__(other) + + def __gt__(self, other: SemanticVersion) -> bool: + return not self.__le__(other) + + def __ge__(self, other: SemanticVersion) -> bool: + return not self.__lt__(other) + + def __str__(self) -> str: + version = f"{self.major}.{self.minor}.{self.patch}" + if self.prerelease: + version += f"-{self.prerelease}" + if self.buildmetadata: + version += f"+{self.buildmetadata}" + + return version + + def __repr__(self) -> str: + return f"SemanticVersion('{str(self)}')" diff --git a/openage_version b/openage_version index cb0c939a93..a918a2aa18 100644 --- a/openage_version +++ b/openage_version @@ -1 +1 @@ -0.5.2 +0.6.0 diff --git a/packaging/docker/devenv/Dockerfile.ubuntu.2204 b/packaging/docker/devenv/Dockerfile.ubuntu.2404 similarity index 69% rename from packaging/docker/devenv/Dockerfile.ubuntu.2204 rename to packaging/docker/devenv/Dockerfile.ubuntu.2404 index 40b1dd3abc..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 \ @@ -20,8 +20,6 @@ RUN apt-get update && DEBIAN_FRONTEND="noninteractive" apt-get install -y sudo \ libopus-dev \ libopusfile-dev \ libpng-dev \ - libsdl2-dev \ - libsdl2-image-dev \ libtoml11-dev \ make \ ninja-build \ @@ -35,5 +33,11 @@ RUN apt-get update && DEBIAN_FRONTEND="noninteractive" apt-get install -y sudo \ python3-toml \ qml6-module-qtquick-controls \ qt6-declarative-dev \ + qt6-multimedia-dev \ + 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 diff --git a/shell.nix b/shell.nix index 5b466174ef..5d80e0669f 100644 --- a/shell.nix +++ b/shell.nix @@ -9,7 +9,7 @@ pkgs.mkShell { #pkgs.gdb pkgs.cmake pkgs.gnumake - pkgs.qt5.full + pkgs.qt6.full #pkgs.qtcreator pkgs.eigen @@ -27,13 +27,12 @@ pkgs.mkShell { pkgs.ftgl pkgs.fontconfig pkgs.harfbuzz - pkgs.SDL2 - pkgs.SDL2_image pkgs.opusfile pkgs.libopus pkgs.python39Packages.pylint pkgs.python39Packages.toml pkgs.libsForQt6.qt6.qtdeclarative pkgs.libsForQt6.qt6.qtquickcontrols + pkgs.libsForQt6.qt6.qtmultimedia ]; }