diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 23e7985..22d5547 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -14,18 +14,6 @@ ## Screenshots (if appropriate): -## Types of changes - -- [ ] Chore (anything related to the building tools or the pipeline) -- [ ] Bug fix (non-breaking change which fixes an issue) -- [ ] New feature (non-breaking change which adds functionality) -- [ ] Refactor (A code change that neither fixes a bug nor adds a feature) -- [ ] Style (Changes that do not affect the meaning of the code) -- [ ] Perf (A code change that improves performance) -- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) -- [ ] Documentation (adding documentation of any kind to the project) -- [ ] Other - ## Checklist: diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 428ad05..af0304e 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -1,11 +1,11 @@ name: C++ CMake Build on: -# push: -# branches-ignore: -# - 'doc/**' -# - 'tmp/**' -# - 'experimental/**' + push: + branches-ignore: + - 'doc/**' + - 'tmp/**' + - 'experimental/**' pull_request: branches: - '**' @@ -89,6 +89,8 @@ jobs: # TODO : # add steps 'unit-tests' + # add steps 'fuzz' + # add steps 'golden-tests' # add steps 'benchmark' # add steps 'style & lint' # .. diff --git a/.travis.yml b/.travis.yml index be8a429..39699d5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,14 @@ dist: focal # directories: # - /home/travis/.conan +## FIXME : This the cache generate this error +## opengl/system: Already installed! +## ERROR: opengl/system: Error in package_info() method, line 78 +## self._fill_cppinfo_from_pkgconfig('gl') +## while calling '_fill_cppinfo_from_pkgconfig', line 21 +## if not pkg_config.provides: +## ConanException: pkg-config command ['pkg-config', '--print-provides', 'gl', '--print-errors'] failed with error: Command 'pkg-config --print-provides gl --print-errors' returned non-zero exit status 1. + matrix: include: - name: "Linux (GCC compiler)" @@ -20,49 +28,41 @@ matrix: - gcc-10 - g++-10 - cmake -# - doxygen -# - graphviz -# - binutils-dev - python3 - python3-pip - python3-setuptools - lcov -# - uuid-dev update: true after_script: - bash <(curl -s https://codecov.io/bash) - - name: "Linux (Clang compiler)" - os: linux - compiler: clang - env: OS=linux COMPILER=clang CLANG_VERSION=11 - addons: - apt: - sources: - - sourceline: 'ppa:ubuntu-toolchain-r/test' - packages: - - clang-11 - - cmake -# - doxygen -# - graphviz -# - binutils-dev - - python3 - - python3-pip - - python3-setuptools - - lcov -# - uuid-dev - update: true - - name: "MacOS" - os: osx - compiler: clang - env: OS=osx COMPILER=clang - osx_image: xcode12.2 + +# - name: "Linux (Clang compiler)" +# os: linux +# compiler: clang +# env: OS=linux COMPILER=clang CLANG_VERSION=11 +# addons: +# apt: +# sources: +# - sourceline: 'ppa:ubuntu-toolchain-r/test' +# packages: +# - clang-11 +# - cmake +# - python3 +# - python3-pip +# - python3-setuptools +# - lcov +# update: true + +# - name: "MacOS" +# os: osx +# compiler: clang +# env: OS=osx COMPILER=clang +# osx_image: xcode12.2 before_install: - if [ "$OS" != "osx" ] && [ "$COMPILER" = "gcc" ]; then export CC="gcc-${GCC_VERSION}" CXX="g++-${GCC_VERSION}"; fi - if [ "$OS" != "osx" ] && [ "$COMPILER" = "clang" ]; then export CC="clang-${CLANG_VERSION}" CXX="clang++-${CLANG_VERSION}"; fi - if [ "$OS" == "osx" ]; then brew update; fi -# - if [ "$OS" == "osx" ]; then brew install doxygen; fi -# - if [ "$OS" == "osx" ]; then brew install graphviz; fi - cmake --version - git config --global user.name "Travis CI" - git config --global user.email "travis@travis-ci.org" @@ -71,6 +71,7 @@ before_install: install: - ./tools/install.sh - conan --version + - cmake --version script: - ./tools/generate.sh -- -DENABLE_COVERAGE:BOOL=TRUE -DENABLE_TESTING:BOOL=TRUE diff --git a/CMakeLists.txt b/CMakeLists.txt index 6dcea0b..d064da8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,7 +31,6 @@ endif() add_library(project_warnings INTERFACE) include(cmake/CompilerWarnings.cmake) set_project_warnings(project_warnings) -add_compile_definitions(project_warnings PUBLIC GLM_FORCE_SILENT_WARNINGS) # Debugging flags include(cmake/Sanitizers.cmake) diff --git a/README.md b/README.md index f46d475..d5830bb 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Keimyung University - Mobile Game Development - 2020 Fall Semester -This project is a video game for PC. See the [Game Design Document](doc/GDD_ten_page.pdf) for more details. +This project is a video game for PC. See the [Game Design Document](doc/ThePURGE_GameDesignDocument.pdf) for more details. This repository contains the source code of the [game](src/Application) and his [engine](src/Engine). @@ -13,76 +13,84 @@ This repository contains the source code of the [game](src/Application) and his ```sh $> ./tools/launch.sh -- --help -ThePURGE 0.3.0 +ThePURGE 0.4.0 Usage: ./engine_main [OPTIONS] Options: - -h,--help Print this help message and exit - --version Print the version number and exit. - --config=data/config/app.ini - Read an ini file - --fullscreen BOOLEAN=1 Launch the window in fullscreen mode. - --window-width UINT=1024 Width of the window. - --window-height UINT=768 Height of the window. - --replay-path TEXT Path of the events to replay. - --replay-data TEXT Json events to replay. - --data TEXT=data/ Path of the data folder. - --output-folder TEXT=data/ Path of the generated output. + -h,--help Print this help message and exit + --version Print the version number and exit. + --config=data/config/app.ini Read an ini file + --fullscreen BOOLEAN=1 Launch the window in fullscreen mode. + --window-width UINT=1024 Width of the window. + --window-height UINT=768 Height of the window. + --replay-path TEXT Path of the events to replay. + --replay-data TEXT Json events to replay. + --data TEXT=data/ Path of the data folder. + --output-folder TEXT=../generated/ Path of the generated output. ``` -## Screenshots +## What it looks like -Application version 0.2.12 +See [the trailer](https://www.youtube.com/watch?v=GQxOHtLU4U0) of the game on youtube ! -![v0.2.12](./doc/screenshots/app_v0.2.12.gif) - -Application version 0.1.9 - -![v0.1.9](./doc/screenshots/app_v0.1.9.png) +See [older version](./doc/history) of the project. ## Installing ### Requirements +* All platforms + + * python>=3.0 + * Unix systems - * g++>=10 || clang>=12 - * cmake>=3.13 - * python>=3.0 + * g++>=10 || clang>=11 + * cmake>=3.13 + +* Windows -* Visual Studio Extension + * Visual Studio 2019>=16.7 && Linux CMake extension (see [the documentation](https://docs.microsoft.com/en-us/cpp/linux/cmake-linux-configure?view=vs-2019)) - * Visual Studio 2019>=16.7 - * Linux CMake extension (see [the documentation](https://docs.microsoft.com/en-us/cpp/linux/cmake-linux-configure?view=vs-2019)) - * python>=3.0 +### Command line -The build **require** an internet connection (download of dependencies if missing). +You can use this command line. + +```sh +# Shortcut command +$> sh -c "$(curl -fsSL https://raw.githubusercontent.com/Mathieu-Lala/game_project/develop/tools/install.sh)" + +# Or +$> sh -c "$(wget -O- https://raw.githubusercontent.com/Mathieu-Lala/game_project/develop/tools/install.sh)" +``` + +Or clone and inspect the install script. ```sh # Cloning the repository -$> git clone git@github.com:Mathieu-Lala/game_project.git +$> git clone https://github.com/Mathieu-Lala/game_project.git # Go in the project $> cd game_project # Installing the required environment (partial) -$> ./tools/install.sh +$> ./tools/install.sh --no-clone ``` ### Recommended environment * Unix systems - * [ccache](https://ccache.dev/) - * [cppcheck](http://cppcheck.sourceforge.net/) - * [clang-tidy](https://clang.llvm.org/extra/clang-tidy/) + * [ccache](https://ccache.dev/) + * [cppcheck](http://cppcheck.sourceforge.net/) + * [clang-tidy](https://clang.llvm.org/extra/clang-tidy/) * Windows - * [VS cppcheck extension](https://marketplace.visualstudio.com/items?itemName=Alexium.Cppcheckadd-in) - * [VS conan extension](https://marketplace.visualstudio.com/items?itemName=conan-io.conan-vs-extension) - * [VS GLSL extension](https://marketplace.visualstudio.com/items?itemName=DanielScherzer.GLSL) - * [VS clang-format extension](https://marketplace.visualstudio.com/items?itemName=xaver.clang-format) + * [VS cppcheck extension](https://marketplace.visualstudio.com/items?itemName=Alexium.Cppcheckadd-in) + * [VS conan extension](https://marketplace.visualstudio.com/items?itemName=conan-io.conan-vs-extension) + * [VS GLSL extension](https://marketplace.visualstudio.com/items?itemName=DanielScherzer.GLSL) + * [VS clang-format extension](https://marketplace.visualstudio.com/items?itemName=xaver.clang-format) ## Build and Run @@ -123,13 +131,17 @@ $> ./tools/install.sh +The build **require** an internet connection (download of dependencies if missing). + * Unix systems ```sh + # See the --help message + # Generate the cmake project $> ./tools/generate.sh - # Build all the target + # Build the target $> ./tools/build.sh # Run the executable @@ -138,14 +150,26 @@ $> ./tools/install.sh * Visual Studio -See the [Microsoft documentation](https://docs.microsoft.com/en-us/cpp/build/cmake-projects-in-visual-studio?view=msvc-160). + See [the documentation](https://docs.microsoft.com/en-us/cpp/build/cmake-projects-in-visual-studio?view=msvc-160) for further details. + + 1. Open the project folder with Visual Studio + 1. Project > Generate Cache for GameProject + 1. Build > Build All + 1. Select Startup Item > engine_main ### Package Manager -[conan](https://conan.io/) - [documentation](https://docs.conan.io/en/1.31/) +[Conan](https://conan.io/) is used for this project, see [the documentation](https://docs.conan.io/en/1.31/) + +The dependencies used : ![Dependencies](doc/conan_dependencies.png) +## Debugging + +When implementing a new feature (or just by curiousity), you might want to see what is happening behind the scene. +With a debug build you have access to extra information such as the hitbox, a console (with cheat codes), opengl rendering options ... + ## Testing [![codecov](https://codecov.io/gh/Mathieu-Lala/game_project/branch/develop/graph/badge.svg?token=E43G3XKG01)](https://codecov.io/gh/Mathieu-Lala/game_project) @@ -164,6 +188,7 @@ See the [Microsoft documentation](https://docs.microsoft.com/en-us/cpp/build/cma ## Acknowledgement * Jason Turner's [Weekly Game Project](https://github.com/lefticus/cpp_weekly_game_project) +* [learnopengl.com](http://learnopengl.com) ## Initial Authors diff --git a/VERSION b/VERSION index 0d91a54..1d0ba9e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.3.0 +0.4.0 diff --git a/cmake/StaticAnalyzers.cmake b/cmake/StaticAnalyzers.cmake index 14d33c6..7bf34ec 100644 --- a/cmake/StaticAnalyzers.cmake +++ b/cmake/StaticAnalyzers.cmake @@ -1,6 +1,6 @@ option(ENABLE_CPPCHECK "Enable static analysis with cppcheck" OFF) option(ENABLE_CLANG_TIDY "Enable static analysis with clang-tidy" OFF) -# option(ENABLE_INCLUDE_WHAT_YOU_USE "Enable static analysis with include-what-you-use" OFF) +option(ENABLE_INCLUDE_WHAT_YOU_USE "Enable static analysis with include-what-you-use" OFF) if(ENABLE_CPPCHECK) find_program(CPPCHECK cppcheck) @@ -22,11 +22,11 @@ if(ENABLE_CLANG_TIDY) endif() endif() -# if(ENABLE_INCLUDE_WHAT_YOU_USE) -# find_program(INCLUDE_WHAT_YOU_USE include-what-you-use) -# if(INCLUDE_WHAT_YOU_USE) -# set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE ${INCLUDE_WHAT_YOU_USE}) -# else() -# message(SEND_ERROR "include-what-you-use requested but executable not found") -# endif() -# endif() +if(ENABLE_INCLUDE_WHAT_YOU_USE) + find_program(INCLUDE_WHAT_YOU_USE include-what-you-use) + if(INCLUDE_WHAT_YOU_USE) + set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE ${INCLUDE_WHAT_YOU_USE}) + else() + message(SEND_ERROR "include-what-you-use requested but executable not found") + endif() +endif() diff --git a/conanfile.txt b/conanfile.txt index 1ea8e9f..9e7dee2 100644 --- a/conanfile.txt +++ b/conanfile.txt @@ -48,6 +48,8 @@ glfw:shared=False imgui:shared=False spdlog:header_only=True fmt:header_only=True +spdlog:no_exceptions=True + [generators] cmake diff --git a/data/_unused/enemy/zombie.json b/data/_unused/enemy/zombie.json deleted file mode 100644 index 538cb07..0000000 --- a/data/_unused/enemy/zombie.json +++ /dev/null @@ -1,118 +0,0 @@ -{ - "object": { - "file": "/assets/enemie/zombie_enrage.png", - "width": 300, - "height": 260, - "speed": 25, - "animations": { - "hold": [ - { - "x": 600, - "y": 520 - } - ], - "default": [ - { - "x": 0, - "y": 0 - }, - { - "x": 300, - "y": 0 - }, - { - "x": 600, - "y": 0 - }, - { - "x": 900, - "y": 0 - }, - { - "x": 1200, - "y": 0 - }, - { - "x": 0, - "y": 260 - }, - { - "x": 300, - "y": 260 - }, - { - "x": 600, - "y": 260 - }, - { - "x": 900, - "y": 260 - }, - { - "x": 1200, - "y": 260 - }, - { - "x": 0, - "y": 520 - }, - { - "x": 300, - "y": 520 - }, - { - "x": 600, - "y": 520 - }, - { - "x": 900, - "y": 520 - }, - { - "x": 1200, - "y": 520 - }, - { - "x": 0, - "y": 780 - }, - { - "x": 300, - "y": 780 - }, - { - "x": 600, - "y": 780 - }, - { - "x": 900, - "y": 780 - }, - { - "x": 1200, - "y": 780 - }, - { - "x": 0, - "y": 1040 - }, - { - "x": 300, - "y": 1040 - }, - { - "x": 600, - "y": 1040 - }, - { - "x": 900, - "y": 1040 - }, - { - "x": 1200, - "y": 1040 - } - ] - } - } -} diff --git a/data/_unused/enemy/zombie_enrage.gif b/data/_unused/enemy/zombie_enrage.gif deleted file mode 100644 index cf606f6..0000000 Binary files a/data/_unused/enemy/zombie_enrage.gif and /dev/null differ diff --git a/data/_unused/enemy/zombie_enrage.png b/data/_unused/enemy/zombie_enrage.png deleted file mode 100644 index e95f5f6..0000000 Binary files a/data/_unused/enemy/zombie_enrage.png and /dev/null differ diff --git a/data/_unused/sorcerer_attack/sorcerer_attack.data.json b/data/_unused/sorcerer_attack/sorcerer_attack.data.json deleted file mode 100644 index c376b64..0000000 --- a/data/_unused/sorcerer_attack/sorcerer_attack.data.json +++ /dev/null @@ -1,110 +0,0 @@ -{ - "object": { - "file": "/anims/spells/sorcerer_attack/sorcerer_attack.png", - "width": 377, - "height": 312, - "speed": 100, - "animations": { - "hold": [ - { - "x": 754, - "y": 624 - } - ], - "default": [ - { - "x": 0, - "y": 0 - }, - { - "x": 377, - "y": 0 - }, - { - "x": 754, - "y": 0 - }, - { - "x": 1131, - "y": 0 - }, - { - "x": 1508, - "y": 0 - }, - { - "x": 0, - "y": 312 - }, - { - "x": 377, - "y": 312 - }, - { - "x": 754, - "y": 312 - }, - { - "x": 1131, - "y": 312 - }, - { - "x": 1508, - "y": 312 - }, - { - "x": 0, - "y": 624 - }, - { - "x": 377, - "y": 624 - }, - { - "x": 754, - "y": 624 - }, - { - "x": 1131, - "y": 624 - }, - { - "x": 1508, - "y": 624 - }, - { - "x": 0, - "y": 936 - }, - { - "x": 377, - "y": 936 - }, - { - "x": 754, - "y": 936 - }, - { - "x": 1131, - "y": 936 - }, - { - "x": 1508, - "y": 936 - }, - { - "x": 0, - "y": 1248 - }, - { - "x": 377, - "y": 1248 - }, - { - "x": 754, - "y": 1248 - } - ] - } - } -} diff --git a/data/_unused/sorcerer_attack/sorcerer_attack.mp3 b/data/_unused/sorcerer_attack/sorcerer_attack.mp3 deleted file mode 100644 index 53fed12..0000000 Binary files a/data/_unused/sorcerer_attack/sorcerer_attack.mp3 and /dev/null differ diff --git a/data/_unused/sorcerer_attack/sorcerer_attack.png b/data/_unused/sorcerer_attack/sorcerer_attack.png deleted file mode 100644 index 2c00926..0000000 Binary files a/data/_unused/sorcerer_attack/sorcerer_attack.png and /dev/null differ diff --git a/data/animation/classes/archer/archer.data.json b/data/animation/classes/archer/archer.data.json new file mode 100644 index 0000000..f37813f --- /dev/null +++ b/data/animation/classes/archer/archer.data.json @@ -0,0 +1,130 @@ +{ + "object": { + "animations": { + "idle_left": { + "file": "/animation/classes/archer/archer_spritesheet.png", + "width": 47, + "height": 48, + "cooldown": 50, + "frames": [ + { + "x": 47, + "y": 0 + } + ] + }, + "run_left": { + "file": "/animation/classes/archer/archer_spritesheet.png", + "width": 47, + "height": 48, + "cooldown": 50, + "frames": [ + { + "x": 94, + "y": 48 + }, + { + "x": 47, + "y": 48 + }, + { + "x": 0, + "y": 48 + } + ] + }, + "idle_right": { + "file": "/animation/classes/archer/archer_spritesheet.png", + "width": 47, + "height": 48, + "cooldown": 50, + "frames": [ + { + "x": 47, + "y": 0 + } + ] + }, + "run_right": { + "file": "/animation/classes/archer/archer_spritesheet.png", + "width": 47, + "height": 48, + "cooldown": 50, + "frames": [ + { + "x": 0, + "y": 96 + }, + { + "x": 47, + "y": 96 + }, + { + "x": 94, + "y": 96 + } + ] + }, + "death": { + "file": "/animation/classes/archer/archer_spritesheet.png", + "width": 47, + "height": 48, + "cooldown": 50, + "frames": [ + { + "x": 0, + "y": 144 + }, + { + "x": 0, + "y": 144 + }, + { + "x": 0, + "y": 144 + } + ] + }, + "attack_left": { + "file": "/animation/classes/archer/archer_spritesheet.png", + "width": 47, + "height": 48, + "cooldown": 500, + "frames": [ + { + "x": 47, + "y": 144 + }, + { + "x": 47, + "y": 144 + }, + { + "x": 47, + "y": 144 + } + ] + }, + "attack_right": { + "file": "/animation/classes/archer/archer_spritesheet.png", + "width": 47, + "height": 48, + "cooldown": 500, + "frames": [ + { + "x": 94, + "y": 144 + }, + { + "x": 94, + "y": 144 + }, + { + "x": 94, + "y": 144 + } + ] + } + } + } +} diff --git a/data/animation/classes/archer/archer_spritesheet.png b/data/animation/classes/archer/archer_spritesheet.png new file mode 100644 index 0000000..838a62b Binary files /dev/null and b/data/animation/classes/archer/archer_spritesheet.png differ diff --git a/data/animation/classes/assassin/assassin.data.json b/data/animation/classes/assassin/assassin.data.json new file mode 100644 index 0000000..0fbbb3e --- /dev/null +++ b/data/animation/classes/assassin/assassin.data.json @@ -0,0 +1,130 @@ +{ + "object": { + "animations": { + "idle_left": { + "file": "/animation/classes/assassin/assassin_spritesheet.png", + "width": 35, + "height": 47, + "cooldown": 50, + "frames": [ + { + "x": 35, + "y": 0 + } + ] + }, + "run_left": { + "file": "/animation/classes/assassin/assassin_spritesheet.png", + "width": 35, + "height": 47, + "cooldown": 50, + "frames": [ + { + "x": 70, + "y": 47 + }, + { + "x": 35, + "y": 47 + }, + { + "x": 0, + "y": 47 + } + ] + }, + "idle_right": { + "file": "/animation/classes/assassin/assassin_spritesheet.png", + "width": 35, + "height": 47, + "cooldown": 50, + "frames": [ + { + "x": 35, + "y": 0 + } + ] + }, + "run_right": { + "file": "/animation/classes/assassin/assassin_spritesheet.png", + "width": 35, + "height": 47, + "cooldown": 50, + "frames": [ + { + "x": 0, + "y": 96 + }, + { + "x": 35, + "y": 96 + }, + { + "x": 70, + "y": 96 + } + ] + }, + "death": { + "file": "/animation/classes/assassin/assassin_spritesheet.png", + "width": 35, + "height": 47, + "cooldown": 50, + "frames": [ + { + "x": 0, + "y": 141 + }, + { + "x": 0, + "y": 141 + }, + { + "x": 0, + "y": 141 + } + ] + }, + "attack_left": { + "file": "/animation/classes/assassin/assassin_spritesheet.png", + "width": 35, + "height": 47, + "cooldown": 500, + "frames": [ + { + "x": 35, + "y": 141 + }, + { + "x": 35, + "y": 141 + }, + { + "x": 35, + "y": 141 + } + ] + }, + "attack_right": { + "file": "/animation/classes/assassin/assassin_spritesheet.png", + "width": 35, + "height": 47, + "cooldown": 500, + "frames": [ + { + "x": 70, + "y": 141 + }, + { + "x": 70, + "y": 141 + }, + { + "x": 70, + "y": 141 + } + ] + } + } + } +} diff --git a/data/animation/classes/assassin/assassin_spritesheet.png b/data/animation/classes/assassin/assassin_spritesheet.png new file mode 100644 index 0000000..1b46c54 Binary files /dev/null and b/data/animation/classes/assassin/assassin_spritesheet.png differ diff --git a/data/animation/classes/farmer/farmer.data.json b/data/animation/classes/farmer/farmer.data.json new file mode 100644 index 0000000..41f6a13 --- /dev/null +++ b/data/animation/classes/farmer/farmer.data.json @@ -0,0 +1,130 @@ +{ + "object": { + "animations": { + "idle_left": { + "file": "/animation/classes/farmer/farmer_spritesheet.png", + "width": 33, + "height": 45, + "cooldown": 50, + "frames": [ + { + "x": 33, + "y": 0 + } + ] + }, + "run_left": { + "file": "/animation/classes/farmer/farmer_spritesheet.png", + "width": 33, + "height": 45, + "cooldown": 50, + "frames": [ + { + "x": 66, + "y": 45 + }, + { + "x": 33, + "y": 45 + }, + { + "x": 0, + "y": 45 + } + ] + }, + "idle_right": { + "file": "/animation/classes/farmer/farmer_spritesheet.png", + "width": 33, + "height": 45, + "cooldown": 50, + "frames": [ + { + "x": 33, + "y": 0 + } + ] + }, + "run_right": { + "file": "/animation/classes/farmer/farmer_spritesheet.png", + "width": 33, + "height": 45, + "cooldown": 50, + "frames": [ + { + "x": 0, + "y": 90 + }, + { + "x": 33, + "y": 90 + }, + { + "x": 66, + "y": 90 + } + ] + }, + "death": { + "file": "/animation/classes/farmer/farmer_spritesheet.png", + "width": 33, + "height": 45, + "cooldown": 50, + "frames": [ + { + "x": 0, + "y": 135 + }, + { + "x": 0, + "y": 135 + }, + { + "x": 0, + "y": 135 + } + ] + }, + "attack_left": { + "file": "/animation/classes/farmer/farmer_spritesheet.png", + "width": 33, + "height": 45, + "cooldown": 500, + "frames": [ + { + "x": 33, + "y": 135 + }, + { + "x": 33, + "y": 135 + }, + { + "x": 33, + "y": 135 + } + ] + }, + "attack_right": { + "file": "/animation/classes/farmer/farmer_spritesheet.png", + "width": 33, + "height": 45, + "cooldown": 500, + "frames": [ + { + "x": 66, + "y": 135 + }, + { + "x": 66, + "y": 135 + }, + { + "x": 66, + "y": 135 + } + ] + } + } + } +} diff --git a/data/animation/classes/farmer/farmer_spritesheet.png b/data/animation/classes/farmer/farmer_spritesheet.png new file mode 100644 index 0000000..4120522 Binary files /dev/null and b/data/animation/classes/farmer/farmer_spritesheet.png differ diff --git a/data/animation/classes/gunner/gunner.data.json b/data/animation/classes/gunner/gunner.data.json new file mode 100644 index 0000000..faaf7bb --- /dev/null +++ b/data/animation/classes/gunner/gunner.data.json @@ -0,0 +1,130 @@ +{ + "object": { + "animations": { + "idle_left": { + "file": "/animation/classes/gunner/gunner_spritesheet.png", + "width": 38, + "height": 48, + "cooldown": 50, + "frames": [ + { + "x": 38, + "y": 0 + } + ] + }, + "run_left": { + "file": "/animation/classes/gunner/gunner_spritesheet.png", + "width": 38, + "height": 48, + "cooldown": 50, + "frames": [ + { + "x": 76, + "y": 48 + }, + { + "x": 38, + "y": 48 + }, + { + "x": 0, + "y": 48 + } + ] + }, + "idle_right": { + "file": "/animation/classes/gunner/gunner_spritesheet.png", + "width": 38, + "height": 48, + "cooldown": 50, + "frames": [ + { + "x": 38, + "y": 0 + } + ] + }, + "run_right": { + "file": "/animation/classes/gunner/gunner_spritesheet.png", + "width": 38, + "height": 48, + "cooldown": 50, + "frames": [ + { + "x": 0, + "y": 96 + }, + { + "x": 38, + "y": 96 + }, + { + "x": 76, + "y": 96 + } + ] + }, + "death": { + "file": "/animation/classes/gunner/gunner_spritesheet.png", + "width": 47, + "height": 47, + "cooldown": 50, + "frames": [ + { + "x": 0, + "y": 144 + }, + { + "x": 0, + "y": 144 + }, + { + "x": 0, + "y": 144 + } + ] + }, + "attack_left": { + "file": "/animation/classes/gunner/gunner_spritesheet.png", + "width": 38, + "height": 48, + "cooldown": 500, + "frames": [ + { + "x": 0, + "y": 0 + }, + { + "x": 0, + "y": 0 + }, + { + "x": 0, + "y": 0 + } + ] + }, + "attack_right": { + "file": "/animation/classes/gunner/gunner_spritesheet.png", + "width": 38, + "height": 48, + "cooldown": 500, + "frames": [ + { + "x": 76, + "y": 0 + }, + { + "x": 76, + "y": 0 + }, + { + "x": 76, + "y": 0 + } + ] + } + } + } +} diff --git a/data/animation/classes/gunner/gunner_spritesheet.png b/data/animation/classes/gunner/gunner_spritesheet.png new file mode 100644 index 0000000..6fbd23e Binary files /dev/null and b/data/animation/classes/gunner/gunner_spritesheet.png differ diff --git a/data/animation/classes/mage/mage.data.json b/data/animation/classes/mage/mage.data.json new file mode 100644 index 0000000..e5d6b25 --- /dev/null +++ b/data/animation/classes/mage/mage.data.json @@ -0,0 +1,130 @@ +{ + "object": { + "animations": { + "idle_left": { + "file": "/animation/classes/mage/mage_spritesheet.png", + "width": 47, + "height": 48, + "cooldown": 50, + "frames": [ + { + "x": 47, + "y": 0 + } + ] + }, + "run_left": { + "file": "/animation/classes/mage/mage_spritesheet.png", + "width": 47, + "height": 48, + "cooldown": 50, + "frames": [ + { + "x": 94, + "y": 48 + }, + { + "x": 47, + "y": 48 + }, + { + "x": 0, + "y": 48 + } + ] + }, + "idle_right": { + "file": "/animation/classes/mage/mage_spritesheet.png", + "width": 47, + "height": 48, + "cooldown": 50, + "frames": [ + { + "x": 47, + "y": 0 + } + ] + }, + "run_right": { + "file": "/animation/classes/mage/mage_spritesheet.png", + "width": 47, + "height": 48, + "cooldown": 50, + "frames": [ + { + "x": 0, + "y": 96 + }, + { + "x": 47, + "y": 96 + }, + { + "x": 94, + "y": 96 + } + ] + }, + "death": { + "file": "/animation/classes/mage/mage_spritesheet.png", + "width": 47, + "height": 48, + "cooldown": 50, + "frames": [ + { + "x": 0, + "y": 144 + }, + { + "x": 0, + "y": 144 + }, + { + "x": 0, + "y": 144 + } + ] + }, + "attack_left": { + "file": "/animation/classes/mage/mage_spritesheet.png", + "width": 47, + "height": 48, + "cooldown": 500, + "frames": [ + { + "x": 47, + "y": 144 + }, + { + "x": 47, + "y": 144 + }, + { + "x": 47, + "y": 144 + } + ] + }, + "attack_right": { + "file": "/animation/classes/mage/mage_spritesheet.png", + "width": 47, + "height": 48, + "cooldown": 500, + "frames": [ + { + "x": 94, + "y": 144 + }, + { + "x": 94, + "y": 144 + }, + { + "x": 94, + "y": 144 + } + ] + } + } + } +} diff --git a/data/animation/classes/mage/mage_spritesheet.png b/data/animation/classes/mage/mage_spritesheet.png new file mode 100644 index 0000000..f55192f Binary files /dev/null and b/data/animation/classes/mage/mage_spritesheet.png differ diff --git a/data/animation/classes/shooter/shooter.data.json b/data/animation/classes/shooter/shooter.data.json new file mode 100644 index 0000000..ff747f1 --- /dev/null +++ b/data/animation/classes/shooter/shooter.data.json @@ -0,0 +1,130 @@ +{ + "object": { + "animations": { + "idle_left": { + "file": "/animation/classes/shooter/shooter_spritesheet.png", + "width": 37, + "height": 48, + "cooldown": 50, + "frames": [ + { + "x": 37, + "y": 0 + } + ] + }, + "run_left": { + "file": "/animation/classes/shooter/shooter_spritesheet.png", + "width": 37, + "height": 48, + "cooldown": 50, + "frames": [ + { + "x": 74, + "y": 48 + }, + { + "x": 37, + "y": 48 + }, + { + "x": 0, + "y": 48 + } + ] + }, + "idle_right": { + "file": "/animation/classes/shooter/shooter_spritesheet.png", + "width": 37, + "height": 48, + "cooldown": 50, + "frames": [ + { + "x": 37, + "y": 0 + } + ] + }, + "run_right": { + "file": "/animation/classes/shooter/shooter_spritesheet.png", + "width": 37, + "height": 48, + "cooldown": 50, + "frames": [ + { + "x": 0, + "y": 96 + }, + { + "x": 37, + "y": 96 + }, + { + "x": 74, + "y": 96 + } + ] + }, + "death": { + "file": "/animation/classes/shooter/shooter_spritesheet.png", + "width": 41, + "height": 45, + "cooldown": 50, + "frames": [ + { + "x": 0, + "y": 144 + }, + { + "x": 0, + "y": 144 + }, + { + "x": 0, + "y": 144 + } + ] + }, + "attack_left": { + "file": "/animation/classes/shooter/shooter_spritesheet.png", + "width": 37, + "height": 48, + "cooldown": 500, + "frames": [ + { + "x": 0, + "y": 0 + }, + { + "x": 0, + "y": 0 + }, + { + "x": 0, + "y": 0 + } + ] + }, + "attack_right": { + "file": "/animation/classes/shooter/shooter_spritesheet.png", + "width": 37, + "height": 48, + "cooldown": 500, + "frames": [ + { + "x": 74, + "y": 0 + }, + { + "x": 74, + "y": 0 + }, + { + "x": 74, + "y": 0 + } + ] + } + } + } +} diff --git a/data/animation/classes/shooter/shooter_spritesheet.png b/data/animation/classes/shooter/shooter_spritesheet.png new file mode 100644 index 0000000..5aaaf3e Binary files /dev/null and b/data/animation/classes/shooter/shooter_spritesheet.png differ diff --git a/data/animation/classes/soldier/soldier.data.json b/data/animation/classes/soldier/soldier.data.json new file mode 100644 index 0000000..1535ea6 --- /dev/null +++ b/data/animation/classes/soldier/soldier.data.json @@ -0,0 +1,130 @@ +{ + "object": { + "animations": { + "idle_left": { + "file": "/animation/classes/soldier/soldier_spritesheet.png", + "width": 37, + "height": 48, + "cooldown": 50, + "frames": [ + { + "x": 37, + "y": 0 + } + ] + }, + "run_left": { + "file": "/animation/classes/soldier/soldier_spritesheet.png", + "width": 37, + "height": 48, + "cooldown": 50, + "frames": [ + { + "x": 74, + "y": 48 + }, + { + "x": 37, + "y": 48 + }, + { + "x": 0, + "y": 48 + } + ] + }, + "idle_right": { + "file": "/animation/classes/soldier/soldier_spritesheet.png", + "width": 37, + "height": 48, + "cooldown": 50, + "frames": [ + { + "x": 37, + "y": 0 + } + ] + }, + "run_right": { + "file": "/animation/classes/soldier/soldier_spritesheet.png", + "width": 37, + "height": 48, + "cooldown": 50, + "frames": [ + { + "x": 0, + "y": 96 + }, + { + "x": 37, + "y": 96 + }, + { + "x": 74, + "y": 96 + } + ] + }, + "death": { + "file": "/animation/classes/soldier/soldier_spritesheet.png", + "width": 37, + "height": 48, + "cooldown": 50, + "frames": [ + { + "x": 0, + "y": 144 + }, + { + "x": 0, + "y": 144 + }, + { + "x": 0, + "y": 144 + } + ] + }, + "attack_left": { + "file": "/animation/classes/soldier/soldier_spritesheet.png", + "width": 37, + "height": 48, + "cooldown": 500, + "frames": [ + { + "x": 37, + "y": 144 + }, + { + "x": 37, + "y": 144 + }, + { + "x": 37, + "y": 144 + } + ] + }, + "attack_right": { + "file": "/animation/classes/soldier/soldier_spritesheet.png", + "width": 37, + "height": 48, + "cooldown": 500, + "frames": [ + { + "x": 74, + "y": 144 + }, + { + "x": 74, + "y": 144 + }, + { + "x": 74, + "y": 144 + } + ] + } + } + } +} diff --git a/data/animation/classes/soldier/soldier_spritesheet.png b/data/animation/classes/soldier/soldier_spritesheet.png new file mode 100644 index 0000000..d644f82 Binary files /dev/null and b/data/animation/classes/soldier/soldier_spritesheet.png differ diff --git a/data/animation/classes/sorcerer/sorcerer.data.json b/data/animation/classes/sorcerer/sorcerer.data.json new file mode 100644 index 0000000..9207509 --- /dev/null +++ b/data/animation/classes/sorcerer/sorcerer.data.json @@ -0,0 +1,130 @@ +{ + "object": { + "animations": { + "idle_left": { + "file": "/animation/classes/sorcerer/sorcerer_spritesheet.png", + "width": 37, + "height": 47, + "cooldown": 50, + "frames": [ + { + "x": 37, + "y": 0 + } + ] + }, + "run_left": { + "file": "/animation/classes/sorcerer/sorcerer_spritesheet.png", + "width": 37, + "height": 47, + "cooldown": 50, + "frames": [ + { + "x": 74, + "y": 47 + }, + { + "x": 37, + "y": 47 + }, + { + "x": 0, + "y": 47 + } + ] + }, + "idle_right": { + "file": "/animation/classes/sorcerer/sorcerer_spritesheet.png", + "width": 37, + "height": 47, + "cooldown": 50, + "frames": [ + { + "x": 37, + "y": 0 + } + ] + }, + "run_right": { + "file": "/animation/classes/sorcerer/sorcerer_spritesheet.png", + "width": 37, + "height": 47, + "cooldown": 50, + "frames": [ + { + "x": 0, + "y": 94 + }, + { + "x": 37, + "y": 94 + }, + { + "x": 74, + "y": 94 + } + ] + }, + "death": { + "file": "/animation/classes/sorcerer/sorcerer_spritesheet.png", + "width": 37, + "height": 47, + "cooldown": 50, + "frames": [ + { + "x": 0, + "y": 141 + }, + { + "x": 0, + "y": 141 + }, + { + "x": 0, + "y": 141 + } + ] + }, + "attack_left": { + "file": "/animation/classes/sorcerer/sorcerer_spritesheet.png", + "width": 37, + "height": 47, + "cooldown": 500, + "frames": [ + { + "x": 37, + "y": 141 + }, + { + "x": 37, + "y": 141 + }, + { + "x": 37, + "y": 141 + } + ] + }, + "attack_right": { + "file": "/animation/classes/sorcerer/sorcerer_spritesheet.png", + "width": 37, + "height": 47, + "cooldown": 500, + "frames": [ + { + "x": 74, + "y": 141 + }, + { + "x": 74, + "y": 141 + }, + { + "x": 74, + "y": 141 + } + ] + } + } + } +} diff --git a/data/animation/classes/sorcerer/sorcerer_spritesheet.png b/data/animation/classes/sorcerer/sorcerer_spritesheet.png new file mode 100644 index 0000000..15b578c Binary files /dev/null and b/data/animation/classes/sorcerer/sorcerer_spritesheet.png differ diff --git a/data/animation/classes/tank/tank.data.json b/data/animation/classes/tank/tank.data.json new file mode 100644 index 0000000..dec80af --- /dev/null +++ b/data/animation/classes/tank/tank.data.json @@ -0,0 +1,130 @@ +{ + "object": { + "animations": { + "idle_left": { + "file": "/animation/classes/tank/tank_spritesheet.png", + "width": 40, + "height": 48, + "cooldown": 50, + "frames": [ + { + "x": 40, + "y": 0 + } + ] + }, + "run_left": { + "file": "/animation/classes/tank/tank_spritesheet.png", + "width": 40, + "height": 48, + "cooldown": 50, + "frames": [ + { + "x": 80, + "y": 48 + }, + { + "x": 40, + "y": 48 + }, + { + "x": 0, + "y": 48 + } + ] + }, + "idle_right": { + "file": "/animation/classes/tank/tank_spritesheet.png", + "width": 40, + "height": 48, + "cooldown": 50, + "frames": [ + { + "x": 40, + "y": 0 + } + ] + }, + "run_right": { + "file": "/animation/classes/tank/tank_spritesheet.png", + "width": 40, + "height": 48, + "cooldown": 50, + "frames": [ + { + "x": 0, + "y": 96 + }, + { + "x": 40, + "y": 96 + }, + { + "x": 80, + "y": 96 + } + ] + }, + "death": { + "file": "/animation/classes/tank/tank_spritesheet.png", + "width": 40, + "height": 48, + "cooldown": 50, + "frames": [ + { + "x": 0, + "y": 144 + }, + { + "x": 0, + "y": 144 + }, + { + "x": 0, + "y": 144 + } + ] + }, + "attack_left": { + "file": "/animation/classes/tank/tank_spritesheet.png", + "width": 40, + "height": 48, + "cooldown": 500, + "frames": [ + { + "x": 40, + "y": 144 + }, + { + "x": 40, + "y": 144 + }, + { + "x": 40, + "y": 144 + } + ] + }, + "attack_right": { + "file": "/animation/classes/tank/tank_spritesheet.png", + "width": 40, + "height": 48, + "cooldown": 500, + "frames": [ + { + "x": 80, + "y": 144 + }, + { + "x": 80, + "y": 144 + }, + { + "x": 80, + "y": 144 + } + ] + } + } + } +} diff --git a/data/animation/classes/tank/tank_spritesheet.png b/data/animation/classes/tank/tank_spritesheet.png new file mode 100644 index 0000000..d1e97d3 Binary files /dev/null and b/data/animation/classes/tank/tank_spritesheet.png differ diff --git a/data/animation/classes/warrior/warrior.data.json b/data/animation/classes/warrior/warrior.data.json new file mode 100644 index 0000000..f49be3b --- /dev/null +++ b/data/animation/classes/warrior/warrior.data.json @@ -0,0 +1,130 @@ +{ + "object": { + "animations": { + "idle_left": { + "file": "/animation/classes/warrior/warrior_spritesheet.png", + "width": 36, + "height": 48, + "cooldown": 50, + "frames": [ + { + "x": 36, + "y": 0 + } + ] + }, + "run_left": { + "file": "/animation/classes/warrior/warrior_spritesheet.png", + "width": 36, + "height": 48, + "cooldown": 50, + "frames": [ + { + "x": 72, + "y": 48 + }, + { + "x": 36, + "y": 48 + }, + { + "x": 0, + "y": 48 + } + ] + }, + "idle_right": { + "file": "/animation/classes/warrior/warrior_spritesheet.png", + "width": 36, + "height": 48, + "cooldown": 50, + "frames": [ + { + "x": 36, + "y": 0 + } + ] + }, + "run_right": { + "file": "/animation/classes/warrior/warrior_spritesheet.png", + "width": 36, + "height": 48, + "cooldown": 50, + "frames": [ + { + "x": 0, + "y": 96 + }, + { + "x": 36, + "y": 96 + }, + { + "x": 72, + "y": 96 + } + ] + }, + "death": { + "file": "/animation/classes/warrior/warrior_spritesheet.png", + "width": 36, + "height": 48, + "cooldown": 50, + "frames": [ + { + "x": 0, + "y": 144 + }, + { + "x": 0, + "y": 144 + }, + { + "x": 0, + "y": 144 + } + ] + }, + "attack_left": { + "file": "/animation/classes/warrior/warrior_spritesheet.png", + "width": 36, + "height": 48, + "cooldown": 500, + "frames": [ + { + "x": 36, + "y": 144 + }, + { + "x": 36, + "y": 144 + }, + { + "x": 36, + "y": 144 + } + ] + }, + "attack_right": { + "file": "/animation/classes/warrior/warrior_spritesheet.png", + "width": 36, + "height": 48, + "cooldown": 500, + "frames": [ + { + "x": 72, + "y": 144 + }, + { + "x": 72, + "y": 144 + }, + { + "x": 72, + "y": 144 + } + ] + } + } + } +} diff --git a/data/animation/classes/warrior/warrior_spritesheet.png b/data/animation/classes/warrior/warrior_spritesheet.png new file mode 100644 index 0000000..682f73f Binary files /dev/null and b/data/animation/classes/warrior/warrior_spritesheet.png differ diff --git a/data/animation/enemies/golem/golem.data.json b/data/animation/enemies/golem/golem.data.json new file mode 100644 index 0000000..79d6d38 --- /dev/null +++ b/data/animation/enemies/golem/golem.data.json @@ -0,0 +1,302 @@ +{ + "object": { + "animations": { + "idle_right": { + "file": "/animation/enemies/golem/spritesheet/golem_all.png", + "width": 46, + "height": 50, + "cooldown": 70, + "frames": [ + { + "x": 18, + "y": 20 + }, + { + "x": 100, + "y": 20 + }, + { + "x": 182, + "y": 20 + }, + { + "x": 264, + "y": 20 + }, + { + "x": 346, + "y": 20 + }, + { + "x": 428, + "y": 20 + }, + { + "x": 511, + "y": 20 + }, + { + "x": 592, + "y": 20 + }, + { + "x": 674, + "y": 20 + }, + { + "x": 756, + "y": 20 + }, + { + "x": 838, + "y": 20 + }, + { + "x": 920, + "y": 20 + }, + { + "x": 1083, + "y": 20 + } + ] + }, + "idle_left": { + "file": "/animation/enemies/golem/spritesheet/golem_all.png", + "width": 50, + "height": 53, + "cooldown": 70, + "frames": [ + { + "x": 18, + "y": 20 + }, + { + "x": 100, + "y": 20 + }, + { + "x": 182, + "y": 20 + }, + { + "x": 264, + "y": 20 + }, + { + "x": 346, + "y": 20 + }, + { + "x": 428, + "y": 20 + }, + { + "x": 511, + "y": 20 + }, + { + "x": 592, + "y": 20 + }, + { + "x": 674, + "y": 20 + }, + { + "x": 756, + "y": 20 + }, + { + "x": 838, + "y": 20 + }, + { + "x": 920, + "y": 20 + }, + { + "x": 1083, + "y": 20 + } + ] + }, + "run_right": { + "file": "/animation/enemies/golem/spritesheet/golem_all.png", + "width": 50, + "height": 46, + "cooldown": 50, + "frames": [ + { + "x": 23, + "y": 191 + }, + { + "x": 106, + "y": 191 + }, + { + "x": 187, + "y": 191 + }, + { + "x": 268, + "y": 191 + }, + { + "x": 348, + "y": 191 + }, + { + "x": 429, + "y": 191 + }, + { + "x": 512, + "y": 191 + }, + { + "x": 594, + "y": 191 + } + ] + }, + "run_left": { + "file": "/animation/enemies/golem/spritesheet/golem_all.png", + "width": 50, + "height": 46, + "cooldown": 50, + "frames": [ + { + "x": 1019, + "y": 355 + }, + { + "x": 937, + "y": 355 + }, + { + "x": 857, + "y": 355 + }, + { + "x": 777, + "y": 355 + }, + { + "x": 696, + "y": 355 + }, + { + "x": 613, + "y": 355 + } + ] + }, + "death": { + "file": "/animation/enemies/golem/spritesheet/golem_all.png", + "width": 46, + "height": 50, + "cooldown": 50, + "frames": [ + { + "x": 16, + "y": 438 + }, + { + "x": 98, + "y": 438 + }, + { + "x": 181, + "y": 438 + }, + { + "x": 264, + "y": 438 + }, + { + "x": 346, + "y": 438 + }, + { + "x": 428, + "y": 438 + }, + { + "x": 511, + "y": 438 + }, + { + "x": 592, + "y": 438 + } + ] + }, + "attack_left": { + "file": "/animation/enemies/golem/spritesheet/golem_all.png", + "width": 57, + "height": 52, + "cooldown": 50, + "frames": [ + { + "x": 1078, + "y": 191 + }, + { + "x": 991, + "y": 191 + }, + { + "x": 909, + "y": 191 + }, + { + "x": 827, + "y": 191 + }, + { + "x": 746, + "y": 191 + }, + { + "x": 670, + "y": 191 + } + ] + }, + "attack_right": { + "file": "/animation/enemies/golem/spritesheet/golem_all.png", + "width": 53, + "height": 73, + "cooldown": 50, + "frames": [ + { + "x": 507, + "y": 270 + }, + { + "x": 598, + "y": 270 + }, + { + "x": 680, + "y": 270 + }, + { + "x": 763, + "y": 270 + }, + { + "x": 845, + "y": 270 + }, + { + "x": 922, + "y": 270 + } + ] + } + } + } +} diff --git a/data/animation/enemies/golem/spritesheet/golem_all.png b/data/animation/enemies/golem/spritesheet/golem_all.png new file mode 100644 index 0000000..38187f7 Binary files /dev/null and b/data/animation/enemies/golem/spritesheet/golem_all.png differ diff --git a/data/animation/enemies/golem_turret/golem_turret.data.json b/data/animation/enemies/golem_turret/golem_turret.data.json new file mode 100644 index 0000000..71bebb6 --- /dev/null +++ b/data/animation/enemies/golem_turret/golem_turret.data.json @@ -0,0 +1,114 @@ +{ + "object": { + "animations": { + "idle_right": { + "file": "/animation/enemies/golem_turret/spritesheet/golem_turret.png", + "width": 65, + "height": 75, + "cooldown": 70, + "frames": [ + { + "x": 243, + "y": 152 + }, + { + "x": 316, + "y": 152 + }, + { + "x": 388, + "y": 152 + }, + { + "x": 316, + "y": 152 + } + ] + }, + "idle_left": { + "file": "/animation/enemies/golem_turret/spritesheet/golem_turret.png", + "width": 65, + "height": 75, + "cooldown": 70, + "frames": [ + { + "x": 627, + "y": 34 + }, + { + "x": 553, + "y": 34 + }, + { + "x": 481, + "y": 34 + }, + { + "x": 627, + "y": 34 + } + ] + }, + "run_right": { + "file": "/animation/enemies/golem_turret/spritesheet/golem_turret.png", + "width": 65, + "height": 75, + "cooldown": 70, + "frames": [ + { + "x": 243, + "y": 152 + }, + { + "x": 316, + "y": 152 + }, + { + "x": 388, + "y": 152 + }, + { + "x": 316, + "y": 152 + } + ] + }, + "run_left": { + "file": "/animation/enemies/golem_turret/spritesheet/golem_turret.png", + "width": 65, + "height": 75, + "cooldown": 70, + "frames": [ + { + "x": 627, + "y": 34 + }, + { + "x": 553, + "y": 34 + }, + { + "x": 481, + "y": 34 + }, + { + "x": 627, + "y": 34 + } + ] + }, + "death": { + "file": "/animation/enemies/golem_turret/spritesheet/golem_turret.png", + "width": 33, + "height": 32, + "cooldown": 70, + "frames": [ + { + "x": 627, + "y": 34 + } + ] + } + } + } +} diff --git a/data/animation/enemies/golem_turret/spritesheet/golem_turret.png b/data/animation/enemies/golem_turret/spritesheet/golem_turret.png new file mode 100644 index 0000000..02253e0 Binary files /dev/null and b/data/animation/enemies/golem_turret/spritesheet/golem_turret.png differ diff --git a/data/animation/enemies/monster_guy/monster_guy.data.json b/data/animation/enemies/monster_guy/monster_guy.data.json new file mode 100644 index 0000000..527d1d3 --- /dev/null +++ b/data/animation/enemies/monster_guy/monster_guy.data.json @@ -0,0 +1,202 @@ +{ + "object": { + "animations": { + "idle_right": { + "file": "/animation/enemies/monster_guy/spritesheet/monster_all.png", + "width": 28, + "height": 26, + "cooldown": 70, + "frames": [ + { + "x": 9, + "y": 2 + }, + { + "x": 76, + "y": 2 + }, + { + "x": 140, + "y": 2 + }, + { + "x": 206, + "y": 2 + }, + { + "x": 272, + "y": 2 + }, + { + "x": 337, + "y": 2 + } + ] + }, + "idle_left": { + "file": "/animation/enemies/monster_guy/spritesheet/monster_all.png", + "width": 30, + "height": 28, + "cooldown": 70, + "frames": [ + { + "x": 339, + "y": 33 + }, + { + "x": 274, + "y": 33 + }, + { + "x": 207, + "y": 33 + }, + { + "x": 140, + "y": 33 + }, + { + "x": 75, + "y": 33 + }, + { + "x": 9, + "y": 33 + } + ] + }, + "run_right": { + "file": "/animation/enemies/monster_guy/spritesheet/monster_all.png", + "width": 30, + "height": 28, + "cooldown": 70, + "frames": [ + { + "x": 11, + "y": 70 + }, + { + "x": 78, + "y": 70 + }, + { + "x": 144, + "y": 70 + }, + { + "x": 209, + "y": 70 + }, + { + "x": 275, + "y": 70 + }, + { + "x": 341, + "y": 70 + } + ] + }, + "run_left": { + "file": "/animation/enemies/monster_guy/spritesheet/monster_all.png", + "width": 30, + "height": 28, + "cooldown": 50, + "frames": [ + { + "x": 340, + "y": 104 + }, + { + "x": 277, + "y": 104 + }, + { + "x": 211, + "y": 104 + }, + { + "x": 144, + "y": 104 + }, + { + "x": 78, + "y": 104 + }, + { + "x": 12, + "y": 104 + } + ] + }, + "death": { + "file": "/animation/enemies/monster_guy/spritesheet/monster_all.png", + "width": 38, + "height": 37, + "cooldown": 70, + "frames": [ + { + "x": 9, + "y": 450 + } + ] + }, + "attack_left": { + "file": "/animation/enemies/monster_guy/spritesheet/monster_all.png", + "width": 30, + "height": 28, + "cooldown": 70, + "frames": [ + { + "x": 9, + "y": 138 + }, + { + "x": 73, + "y": 138 + }, + { + "x": 137, + "y": 138 + }, + { + "x": 202, + "y": 138 + }, + { + "x": 268, + "y": 138 + } + ] + }, + "attack_right": { + "file": "/animation/enemies/monster_guy/spritesheet/monster_all.png", + "width": 30, + "height": 28, + "cooldown": 50, + "frames": [ + { + "x": 268, + "y": 170 + }, + { + "x": 202, + "y": 170 + }, + { + "x": 138, + "y": 170 + }, + { + "x": 73, + "y": 170 + }, + { + "x": 7, + "y": 170 + } + ] + } + } + } +} diff --git a/data/animation/enemies/monster_guy/spritesheet/monster_all.png b/data/animation/enemies/monster_guy/spritesheet/monster_all.png new file mode 100644 index 0000000..a1c01a9 Binary files /dev/null and b/data/animation/enemies/monster_guy/spritesheet/monster_all.png differ diff --git a/data/animation/enemies/skeleton/Files/Skeleton Attack.ase b/data/animation/enemies/skeleton/Files/Skeleton Attack.ase new file mode 100644 index 0000000..5ed3283 Binary files /dev/null and b/data/animation/enemies/skeleton/Files/Skeleton Attack.ase differ diff --git a/data/animation/enemies/skeleton/Files/Skeleton Dead.ase b/data/animation/enemies/skeleton/Files/Skeleton Dead.ase new file mode 100644 index 0000000..1d90efa Binary files /dev/null and b/data/animation/enemies/skeleton/Files/Skeleton Dead.ase differ diff --git a/data/animation/enemies/skeleton/Files/Skeleton Hit.ase b/data/animation/enemies/skeleton/Files/Skeleton Hit.ase new file mode 100644 index 0000000..fea459c Binary files /dev/null and b/data/animation/enemies/skeleton/Files/Skeleton Hit.ase differ diff --git a/data/animation/enemies/skeleton/Files/Skeleton Idle.ase b/data/animation/enemies/skeleton/Files/Skeleton Idle.ase new file mode 100644 index 0000000..6d39ae8 Binary files /dev/null and b/data/animation/enemies/skeleton/Files/Skeleton Idle.ase differ diff --git a/data/animation/enemies/skeleton/Files/Skeleton React.ase b/data/animation/enemies/skeleton/Files/Skeleton React.ase new file mode 100644 index 0000000..289cde4 Binary files /dev/null and b/data/animation/enemies/skeleton/Files/Skeleton React.ase differ diff --git a/data/animation/enemies/skeleton/Files/Skeleton Walk.ase b/data/animation/enemies/skeleton/Files/Skeleton Walk.ase new file mode 100644 index 0000000..33c4811 Binary files /dev/null and b/data/animation/enemies/skeleton/Files/Skeleton Walk.ase differ diff --git a/data/animation/enemies/skeleton/GIFS/Skeleton Attack.gif b/data/animation/enemies/skeleton/GIFS/Skeleton Attack.gif new file mode 100644 index 0000000..6ee08c4 Binary files /dev/null and b/data/animation/enemies/skeleton/GIFS/Skeleton Attack.gif differ diff --git a/data/animation/enemies/skeleton/GIFS/Skeleton Dead.gif b/data/animation/enemies/skeleton/GIFS/Skeleton Dead.gif new file mode 100644 index 0000000..568b48a Binary files /dev/null and b/data/animation/enemies/skeleton/GIFS/Skeleton Dead.gif differ diff --git a/data/animation/enemies/skeleton/GIFS/Skeleton Hit.gif b/data/animation/enemies/skeleton/GIFS/Skeleton Hit.gif new file mode 100644 index 0000000..4553fb3 Binary files /dev/null and b/data/animation/enemies/skeleton/GIFS/Skeleton Hit.gif differ diff --git a/data/animation/enemies/skeleton/GIFS/Skeleton Idle.gif b/data/animation/enemies/skeleton/GIFS/Skeleton Idle.gif new file mode 100644 index 0000000..d440bdc Binary files /dev/null and b/data/animation/enemies/skeleton/GIFS/Skeleton Idle.gif differ diff --git a/data/animation/enemies/skeleton/GIFS/Skeleton React.gif b/data/animation/enemies/skeleton/GIFS/Skeleton React.gif new file mode 100644 index 0000000..9965819 Binary files /dev/null and b/data/animation/enemies/skeleton/GIFS/Skeleton React.gif differ diff --git a/data/animation/enemies/skeleton/GIFS/Skeleton Walk.gif b/data/animation/enemies/skeleton/GIFS/Skeleton Walk.gif new file mode 100644 index 0000000..d659a10 Binary files /dev/null and b/data/animation/enemies/skeleton/GIFS/Skeleton Walk.gif differ diff --git a/data/animation/enemies/skeleton/one-side/idle.png b/data/animation/enemies/skeleton/one-side/idle.png new file mode 100644 index 0000000..9137085 Binary files /dev/null and b/data/animation/enemies/skeleton/one-side/idle.png differ diff --git a/data/animation/enemies/skeleton/one-side/walk.png b/data/animation/enemies/skeleton/one-side/walk.png new file mode 100644 index 0000000..17dfd1f Binary files /dev/null and b/data/animation/enemies/skeleton/one-side/walk.png differ diff --git a/data/animation/enemies/skeleton/skeleton.data.json b/data/animation/enemies/skeleton/skeleton.data.json new file mode 100644 index 0000000..fe0a407 --- /dev/null +++ b/data/animation/enemies/skeleton/skeleton.data.json @@ -0,0 +1,450 @@ +{ + "object": { + "animations": { + "idle_right": { + "file": "/animation/enemies/skeleton/spritesheet/idle.png", + "width": 24, + "height": 32, + "cooldown": 70, + "frames": [ + { + "x": 0, + "y": 0 + }, + { + "x": 24, + "y": 0 + }, + { + "x": 48, + "y": 0 + }, + { + "x": 72, + "y": 0 + }, + { + "x": 96, + "y": 0 + }, + { + "x": 120, + "y": 0 + }, + { + "x": 144, + "y": 0 + }, + { + "x": 168, + "y": 0 + }, + { + "x": 192, + "y": 0 + }, + { + "x": 216, + "y": 0 + }, + { + "x": 240, + "y": 0 + } + ] + }, + "idle_left": { + "file": "/animation/enemies/skeleton/spritesheet/idle.png", + "width": 24, + "height": 32, + "cooldown": 70, + "frames": [ + { + "x": 0, + "y": 33 + }, + { + "x": 24, + "y": 33 + }, + { + "x": 48, + "y": 33 + }, + { + "x": 72, + "y": 33 + }, + { + "x": 96, + "y": 33 + }, + { + "x": 120, + "y": 33 + }, + { + "x": 144, + "y": 33 + }, + { + "x": 168, + "y": 33 + }, + { + "x": 192, + "y": 33 + }, + { + "x": 216, + "y": 33 + }, + { + "x": 240, + "y": 33 + } + ] + }, + "run_right": { + "file": "/animation/enemies/skeleton/spritesheet/walk.png", + "width": 22, + "height": 33, + "cooldown": 50, + "frames": [ + { + "x": 0, + "y": 0 + }, + { + "x": 22, + "y": 0 + }, + { + "x": 44, + "y": 0 + }, + { + "x": 66, + "y": 0 + }, + { + "x": 88, + "y": 0 + }, + { + "x": 110, + "y": 0 + }, + { + "x": 132, + "y": 0 + }, + { + "x": 154, + "y": 0 + }, + { + "x": 176, + "y": 0 + }, + { + "x": 198, + "y": 0 + }, + { + "x": 220, + "y": 0 + }, + { + "x": 242, + "y": 0 + }, + { + "x": 264, + "y": 0 + } + ] + }, + "run_left": { + "file": "/animation/enemies/skeleton/spritesheet/walk.png", + "width": 22, + "height": 33, + "cooldown": 50, + "frames": [ + { + "x": 0, + "y": 33 + }, + { + "x": 22, + "y": 33 + }, + { + "x": 44, + "y": 33 + }, + { + "x": 66, + "y": 33 + }, + { + "x": 88, + "y": 33 + }, + { + "x": 110, + "y": 33 + }, + { + "x": 132, + "y": 33 + }, + { + "x": 154, + "y": 33 + }, + { + "x": 176, + "y": 33 + }, + { + "x": 198, + "y": 33 + }, + { + "x": 220, + "y": 33 + }, + { + "x": 242, + "y": 33 + }, + { + "x": 264, + "y": 33 + } + ] + }, + "death": { + "file": "/animation/enemies/skeleton/spritesheet/dead.png", + "width": 33, + "height": 32, + "cooldown": 50, + "frames": [ + { + "x": 0, + "y": 0 + }, + { + "x": 33, + "y": 0 + }, + { + "x": 66, + "y": 0 + }, + { + "x": 99, + "y": 0 + }, + { + "x": 132, + "y": 0 + }, + { + "x": 165, + "y": 0 + }, + { + "x": 198, + "y": 0 + }, + { + "x": 231, + "y": 0 + }, + { + "x": 264, + "y": 0 + }, + { + "x": 297, + "y": 0 + }, + { + "x": 330, + "y": 0 + }, + { + "x": 363, + "y": 0 + }, + { + "x": 396, + "y": 0 + }, + { + "x": 429, + "y": 0 + }, + { + "x": 462, + "y": 0 + } + ] + }, + "attack_left": { + "file": "/animation/enemies/skeleton/spritesheet/attack_left.png", + "width": 36, + "height": 38, + "cooldown": 50, + "frames": [ + { + "x": 735, + "y": 0 + }, + { + "x": 692, + "y": 0 + }, + { + "x": 648, + "y": 0 + }, + { + "x": 603, + "y": 0 + }, + { + "x": 562, + "y": 0 + }, + { + "x": 521, + "y": 0 + }, + { + "x": 480, + "y": 0 + }, + { + "x": 434, + "y": 0 + }, + { + "x": 383, + "y": 0 + }, + { + "x": 343, + "y": 0 + }, + { + "x": 299, + "y": 0 + }, + { + "x": 256, + "y": 0 + }, + { + "x": 213, + "y": 0 + }, + { + "x": 172, + "y": 0 + }, + { + "x": 130, + "y": 0 + }, + { + "x": 91, + "y": 0 + }, + { + "x": 47, + "y": 0 + } + ] + }, + "attack_right": { + "file": "/animation/enemies/skeleton/spritesheet/attack.png", + "width": 36, + "height": 38, + "cooldown": 50, + "frames": [ + { + "x": 2, + "y": 0 + }, + { + "x": 45, + "y": 0 + }, + { + "x": 88, + "y": 0 + }, + { + "x": 133, + "y": 0 + }, + { + "x": 175, + "y": 0 + }, + { + "x": 215, + "y": 0 + }, + { + "x": 257, + "y": 0 + }, + { + "x": 303, + "y": 0 + }, + { + "x": 353, + "y": 0 + }, + { + "x": 394, + "y": 0 + }, + { + "x": 481, + "y": 0 + }, + { + "x": 525, + "y": 0 + }, + { + "x": 565, + "y": 0 + }, + { + "x": 607, + "y": 0 + }, + { + "x": 646, + "y": 0 + }, + { + "x": 690, + "y": 0 + }, + { + "x": 733, + "y": 0 + } + ] + } + } + } +} diff --git a/data/animation/enemies/skeleton/spritesheet/attack.png b/data/animation/enemies/skeleton/spritesheet/attack.png new file mode 100644 index 0000000..807443b Binary files /dev/null and b/data/animation/enemies/skeleton/spritesheet/attack.png differ diff --git a/data/animation/enemies/skeleton/spritesheet/attack_left.png b/data/animation/enemies/skeleton/spritesheet/attack_left.png new file mode 100644 index 0000000..dc43ac9 Binary files /dev/null and b/data/animation/enemies/skeleton/spritesheet/attack_left.png differ diff --git a/data/animation/enemies/skeleton/spritesheet/dead.png b/data/animation/enemies/skeleton/spritesheet/dead.png new file mode 100644 index 0000000..64f539d Binary files /dev/null and b/data/animation/enemies/skeleton/spritesheet/dead.png differ diff --git a/data/animation/enemies/skeleton/spritesheet/hit.png b/data/animation/enemies/skeleton/spritesheet/hit.png new file mode 100644 index 0000000..b9c6c08 Binary files /dev/null and b/data/animation/enemies/skeleton/spritesheet/hit.png differ diff --git a/data/animation/enemies/skeleton/spritesheet/idle.png b/data/animation/enemies/skeleton/spritesheet/idle.png new file mode 100644 index 0000000..e80541a Binary files /dev/null and b/data/animation/enemies/skeleton/spritesheet/idle.png differ diff --git a/data/animation/enemies/skeleton/spritesheet/react.png b/data/animation/enemies/skeleton/spritesheet/react.png new file mode 100644 index 0000000..dbd9943 Binary files /dev/null and b/data/animation/enemies/skeleton/spritesheet/react.png differ diff --git a/data/animation/enemies/skeleton/spritesheet/walk.png b/data/animation/enemies/skeleton/spritesheet/walk.png new file mode 100644 index 0000000..fc40748 Binary files /dev/null and b/data/animation/enemies/skeleton/spritesheet/walk.png differ diff --git a/data/animation/enemies/summoner/spritesheet/summoner.png b/data/animation/enemies/summoner/spritesheet/summoner.png new file mode 100644 index 0000000..f152c5f Binary files /dev/null and b/data/animation/enemies/summoner/spritesheet/summoner.png differ diff --git a/data/animation/enemies/summoner/summoner.data.json b/data/animation/enemies/summoner/summoner.data.json new file mode 100644 index 0000000..f091552 --- /dev/null +++ b/data/animation/enemies/summoner/summoner.data.json @@ -0,0 +1,222 @@ +{ + "object": { + "animations": { + "idle_right": { + "file": "/animation/enemies/summoner/spritesheet/summoner.png", + "width": 48, + "height": 55, + "cooldown": 70, + "frames": [ + { + "x": 51, + "y": 220 + }, + { + "x": 100, + "y": 220 + }, + { + "x": 162, + "y": 220 + }, + { + "x": 230, + "y": 220 + }, + { + "x": 298, + "y": 220 + }, + { + "x": 367, + "y": 220 + } + ] + }, + "idle_left": { + "file": "/animation/enemies/summoner/spritesheet/summoner.png", + "width": 48, + "height": 57, + "cooldown": 70, + "frames": [ + { + "x": 51, + "y": 220 + }, + { + "x": 100, + "y": 220 + }, + { + "x": 162, + "y": 220 + }, + { + "x": 230, + "y": 220 + }, + { + "x": 298, + "y": 220 + }, + { + "x": 367, + "y": 220 + } + ] + }, + "run_right": { + "file": "/animation/enemies/summoner/spritesheet/summoner.png", + "width": 45, + "height": 48, + "cooldown": 50, + "frames": [ + { + "x": 9, + "y": 402 + }, + { + "x": 58, + "y": 402 + }, + { + "x": 105, + "y": 402 + }, + { + "x": 150, + "y": 402 + }, + { + "x": 211, + "y": 402 + }, + { + "x": 275, + "y": 402 + } + ] + }, + "run_left": { + "file": "/animation/enemies/summoner/spritesheet/summoner.png", + "width": 45, + "height": 48, + "cooldown": 50, + "frames": [ + { + "x": 723, + "y": 411 + }, + { + "x": 677, + "y": 411 + }, + { + "x": 633, + "y": 411 + }, + { + "x": 574, + "y": 411 + }, + { + "x": 517, + "y": 411 + }, + { + "x": 461, + "y": 411 + } + ] + }, + "death": { + "file": "/animation/enemies/summoner/spritesheet/summoner.png", + "width": 50, + "height": 55, + "cooldown": 50, + "frames": [ + { + "x": 23, + "y": 1430 + }, + { + "x": 77, + "y": 1430 + }, + { + "x": 130, + "y": 1430 + }, + { + "x": 179, + "y": 1430 + }, + { + "x": 235, + "y": 1430 + }, + { + "x": 282, + "y": 1430 + } + ] + }, + "attack_left": { + "file": "/animation/enemies/summoner/spritesheet/summoner.png", + "width": 43, + "height": 60, + "cooldown": 50, + "frames": [ + { + "x": 202, + "y": 1520 + }, + { + "x": 242, + "y": 1520 + }, + { + "x": 286, + "y": 1520 + }, + { + "x": 340, + "y": 1520 + }, + { + "x": 383, + "y": 1520 + } + ] + }, + "attack_right": { + "file": "/animation/enemies/summoner/spritesheet/summoner.png", + "width": 43, + "height": 60, + "cooldown": 50, + "frames": [ + { + "x": 708, + "y": 1520 + }, + { + "x": 665, + "y": 1520 + }, + { + "x": 619, + "y": 1520 + }, + { + "x": 569, + "y": 1520 + }, + { + "x": 526, + "y": 1520 + } + ] + } + } + } +} diff --git a/data/animation/enemies/zombie/spritesheet/zombie_all.png b/data/animation/enemies/zombie/spritesheet/zombie_all.png new file mode 100644 index 0000000..306d922 Binary files /dev/null and b/data/animation/enemies/zombie/spritesheet/zombie_all.png differ diff --git a/data/animation/enemies/zombie/spritesheet/zombie_attack.png b/data/animation/enemies/zombie/spritesheet/zombie_attack.png new file mode 100644 index 0000000..d50527d Binary files /dev/null and b/data/animation/enemies/zombie/spritesheet/zombie_attack.png differ diff --git a/data/animation/enemies/zombie/spritesheet/zombie_death.png b/data/animation/enemies/zombie/spritesheet/zombie_death.png new file mode 100644 index 0000000..61765ad Binary files /dev/null and b/data/animation/enemies/zombie/spritesheet/zombie_death.png differ diff --git a/data/animation/enemies/zombie/spritesheet/zombie_idle.png b/data/animation/enemies/zombie/spritesheet/zombie_idle.png new file mode 100644 index 0000000..6ef23f4 Binary files /dev/null and b/data/animation/enemies/zombie/spritesheet/zombie_idle.png differ diff --git a/data/animation/enemies/zombie/spritesheet/zombie_walk.png b/data/animation/enemies/zombie/spritesheet/zombie_walk.png new file mode 100644 index 0000000..1a42639 Binary files /dev/null and b/data/animation/enemies/zombie/spritesheet/zombie_walk.png differ diff --git a/data/animation/enemies/zombie/zombie.data.json b/data/animation/enemies/zombie/zombie.data.json new file mode 100644 index 0000000..40750de --- /dev/null +++ b/data/animation/enemies/zombie/zombie.data.json @@ -0,0 +1,270 @@ +{ + "object": { + "animations": { + "idle_right": { + "file": "/animation/enemies/zombie/spritesheet/zombie_idle.png", + "width": 39, + "height": 70, + "cooldown": 100, + "frames": [ + { + "x": 2, + "y": 72 + }, + { + "x": 48, + "y": 72 + }, + { + "x": 93, + "y": 72 + }, + { + "x": 142, + "y": 72 + }, + { + "x": 190, + "y": 72 + }, + { + "x": 234, + "y": 72 + } + ] + }, + "idle_left": { + "file": "/animation/enemies/zombie/spritesheet/zombie_idle.png", + "width": 35, + "height": 75, + "cooldown": 100, + "frames": [ + { + "x": 232, + "y": 0 + }, + { + "x": 195, + "y": 0 + }, + { + "x": 140, + "y": 0 + }, + { + "x": 90, + "y": 0 + }, + { + "x": 44, + "y": 0 + }, + { + "x": 0, + "y": 0 + } + ] + }, + "run_right": { + "file": "/animation/enemies/zombie/spritesheet/zombie_walk.png", + "width": 35, + "height": 75, + "cooldown": 70, + "frames": [ + { + "x": 0, + "y": 75 + }, + { + "x": 54, + "y": 75 + }, + { + "x": 113, + "y": 75 + }, + { + "x": 171, + "y": 75 + }, + { + "x": 278, + "y": 75 + }, + { + "x": 278, + "y": 75 + }, + { + "x": 339, + "y": 75 + }, + { + "x": 388, + "y": 75 + }, + { + "x": 440, + "y": 75 + }, + { + "x": 491, + "y": 75 + }, + { + "x": 533, + "y": 75 + }, + { + "x": 570, + "y": 75 + } + ] + }, + "run_left": { + "file": "/animation/enemies/zombie/spritesheet/zombie_walk.png", + "width": 35, + "height": 75, + "cooldown": 70, + "frames": [ + { + "x": 573, + "y": 0 + }, + { + "x": 521, + "y": 0 + }, + { + "x": 460, + "y": 0 + }, + { + "x": 402, + "y": 0 + }, + { + "x": 351, + "y": 0 + }, + { + "x": 291, + "y": 0 + }, + { + "x": 239, + "y": 0 + }, + { + "x": 185, + "y": 0 + }, + { + "x": 143, + "y": 0 + }, + { + "x": 85, + "y": 0 + }, + { + "x": 44, + "y": 0 + }, + { + "x": 0, + "y": 0 + } + ] + }, + "death": { + "file": "/animation/enemies/zombie/spritesheet/zombie_death.png", + "width": 48, + "height": 70, + "cooldown": 100, + "frames": [ + { + "x": 12, + "y": 0 + }, + { + "x": 56, + "y": 0 + }, + { + "x": 110, + "y": 0 + }, + { + "x": 163, + "y": 0 + }, + { + "x": 216, + "y": 0 + }, + { + "x": 268, + "y": 0 + }, + { + "x": 319, + "y": 0 + }, + { + "x": 372, + "y": 0 + } + ] + }, + "attack_left": { + "file": "/animation/enemies/zombie/spritesheet/zombie_attack.png", + "width": 53, + "height": 76, + "cooldown": 50, + "frames": [ + { + "x": 266, + "y": 3 + }, + { + "x": 214, + "y": 3 + }, + { + "x": 141, + "y": 3 + }, + { + "x": 3, + "y": 3 + } + ] + }, + "attack_right": { + "file": "/animation/enemies/zombie/spritesheet/zombie_attack.png", + "width": 53, + "height": 73, + "cooldown": 50, + "frames": [ + { + "x": 272, + "y": 77 + }, + { + "x": 326, + "y": 77 + }, + { + "x": 396, + "y": 77 + }, + { + "x": 538, + "y": 77 + } + ] + } + } + } +} diff --git a/data/animation/map/rock/rock.data.json b/data/animation/map/rock/rock.data.json new file mode 100644 index 0000000..737379c --- /dev/null +++ b/data/animation/map/rock/rock.data.json @@ -0,0 +1,30 @@ +{ + "object": { + "animations": { + "idle_right": { + "file": "/animation/map/rock/rock.spritesheet.png", + "width": 900, + "height": 800, + "cooldown": 50, + "frames": [ + { + "x": 0, + "y": 0 + } + ] + }, + "idle_left": { + "file": "/animation/map/rock/rock.spritesheet.png", + "width": 900, + "height": 800, + "cooldown": 50, + "frames": [ + { + "x": 0, + "y": 0 + } + ] + } + } + } +} diff --git a/data/animation/map/rock/rock.spritesheet.png b/data/animation/map/rock/rock.spritesheet.png new file mode 100644 index 0000000..b9a970f Binary files /dev/null and b/data/animation/map/rock/rock.spritesheet.png differ diff --git a/data/animation/spells/after_shock/after_shock.data.json b/data/animation/spells/after_shock/after_shock.data.json new file mode 100644 index 0000000..7e559aa --- /dev/null +++ b/data/animation/spells/after_shock/after_shock.data.json @@ -0,0 +1,38 @@ +{ + "object": { + "animations": { + "default": { + "file": "/animation/spells/after_shock/after_shock.spritesheet.png", + "width": 256, + "height": 128, + "cooldown": 60, + "frames": [ + { + "x": 0, + "y": 256 + }, + { + "x": 256, + "y": 256 + }, + { + "x": 512, + "y": 256 + }, + { + "x": 768, + "y": 256 + }, + { + "x": 1024, + "y": 256 + }, + { + "x": 1280, + "y": 256 + } + ] + } + } + } +} diff --git a/data/animation/spells/after_shock/after_shock.spritesheet.png b/data/animation/spells/after_shock/after_shock.spritesheet.png new file mode 100644 index 0000000..2a211b6 Binary files /dev/null and b/data/animation/spells/after_shock/after_shock.spritesheet.png differ diff --git a/data/animation/spells/arrow/arrow.data.json b/data/animation/spells/arrow/arrow.data.json new file mode 100644 index 0000000..ec8dbe4 --- /dev/null +++ b/data/animation/spells/arrow/arrow.data.json @@ -0,0 +1,18 @@ +{ + "object": { + "animations": { + "default": { + "file": "/animation/spells/arrow/arrow.png", + "width": 412, + "height": 52, + "cooldown": 50, + "frames": [ + { + "x": 0, + "y": 0 + } + ] + } + } + } +} diff --git a/data/animation/spells/arrow/arrow.png b/data/animation/spells/arrow/arrow.png new file mode 100644 index 0000000..55ed56f Binary files /dev/null and b/data/animation/spells/arrow/arrow.png differ diff --git a/data/animation/spells/barrel/barrel.data.json b/data/animation/spells/barrel/barrel.data.json new file mode 100644 index 0000000..44a6fc8 --- /dev/null +++ b/data/animation/spells/barrel/barrel.data.json @@ -0,0 +1,38 @@ +{ + "object": { + "animations": { + "default": { + "file": "/animation/spells/barrel/barrel.spritesheet.png", + "width": 44, + "height": 42, + "cooldown": 50, + "frames": [ + { + "x": 0, + "y": 0 + }, + { + "x": 44, + "y": 0 + }, + { + "x": 88, + "y": 0 + }, + { + "x": 132, + "y": 0 + }, + { + "x": 176, + "y": 0 + }, + { + "x": 220, + "y": 0 + } + ] + } + } + } +} diff --git a/data/animation/spells/barrel/barrel.spritesheet.png b/data/animation/spells/barrel/barrel.spritesheet.png new file mode 100644 index 0000000..43bb0c2 Binary files /dev/null and b/data/animation/spells/barrel/barrel.spritesheet.png differ diff --git a/data/animation/spells/explosion/explosion.data.json b/data/animation/spells/explosion/explosion.data.json new file mode 100644 index 0000000..2570f79 --- /dev/null +++ b/data/animation/spells/explosion/explosion.data.json @@ -0,0 +1,106 @@ +{ + "object": { + "animations": { + "default": { + "file": "/animation/spells/explosion/explosion.spritesheet.png", + "width": 64, + "height": 64, + "cooldown": 20, + "frames": [ + { + "x": 0, + "y": 0 + }, + { + "x": 64, + "y": 0 + }, + { + "x": 128, + "y": 0 + }, + { + "x": 192, + "y": 0 + }, + { + "x": 256, + "y": 0 + }, + { + "x": 0, + "y": 64 + }, + { + "x": 64, + "y": 64 + }, + { + "x": 128, + "y": 64 + }, + { + "x": 192, + "y": 64 + }, + { + "x": 256, + "y": 64 + }, + { + "x": 0, + "y": 128 + }, + { + "x": 64, + "y": 128 + }, + { + "x": 128, + "y": 128 + }, + { + "x": 192, + "y": 128 + }, + { + "x": 256, + "y": 128 + }, + { + "x": 0, + "y": 192 + }, + { + "x": 64, + "y": 192 + }, + { + "x": 128, + "y": 192 + }, + { + "x": 192, + "y": 192 + }, + { + "x": 256, + "y": 192 + }, + { + "x": 0, + "y": 256 + }, + { + "x": 64, + "y": 256 + }, + { + "x": 128, + "y": 256 + } + ] + } + } + } +} diff --git a/data/animation/spells/explosion/explosion.spritesheet.png b/data/animation/spells/explosion/explosion.spritesheet.png new file mode 100644 index 0000000..2fc72a6 Binary files /dev/null and b/data/animation/spells/explosion/explosion.spritesheet.png differ diff --git a/data/animation/spells/fireball/fireball.data.json b/data/animation/spells/fireball/fireball.data.json new file mode 100644 index 0000000..625a1b1 --- /dev/null +++ b/data/animation/spells/fireball/fireball.data.json @@ -0,0 +1,34 @@ +{ + "object": { + "animations": { + "default": { + "file": "/animation/spells/fireball/fireball.spritesheet.png", + "width": 256, + "height": 256, + "cooldown": 60, + "frames": [ + { + "x": 0, + "y": 0 + }, + { + "x": 256, + "y": 0 + }, + { + "x": 512, + "y": 0 + }, + { + "x": 768, + "y": 0 + }, + { + "x": 1024, + "y": 0 + } + ] + } + } + } +} diff --git a/data/anims/spells/fireball/fireball.gif b/data/animation/spells/fireball/fireball.gif similarity index 100% rename from data/anims/spells/fireball/fireball.gif rename to data/animation/spells/fireball/fireball.gif diff --git a/data/anims/spells/fireball/fireball.spritesheet.png b/data/animation/spells/fireball/fireball.spritesheet.png similarity index 100% rename from data/anims/spells/fireball/fireball.spritesheet.png rename to data/animation/spells/fireball/fireball.spritesheet.png diff --git a/data/animation/spells/fireball_blue/fireball_blue.data.json b/data/animation/spells/fireball_blue/fireball_blue.data.json new file mode 100644 index 0000000..2dace36 --- /dev/null +++ b/data/animation/spells/fireball_blue/fireball_blue.data.json @@ -0,0 +1,34 @@ +{ + "object": { + "animations": { + "default": { + "file": "/animation/spells/fireball_blue/fireball_blue.spritesheet.png", + "width": 256, + "height": 256, + "cooldown": 60, + "frames": [ + { + "x": 0, + "y": 0 + }, + { + "x": 256, + "y": 0 + }, + { + "x": 512, + "y": 0 + }, + { + "x": 768, + "y": 0 + }, + { + "x": 1024, + "y": 0 + } + ] + } + } + } +} diff --git a/data/animation/spells/fireball_blue/fireball_blue.spritesheet.png b/data/animation/spells/fireball_blue/fireball_blue.spritesheet.png new file mode 100644 index 0000000..ce5f66f Binary files /dev/null and b/data/animation/spells/fireball_blue/fireball_blue.spritesheet.png differ diff --git a/data/animation/spells/firewall/firewall.data.json b/data/animation/spells/firewall/firewall.data.json new file mode 100644 index 0000000..ded204c --- /dev/null +++ b/data/animation/spells/firewall/firewall.data.json @@ -0,0 +1,514 @@ +{ + "object": { + "animations": { + "default": { + "file": "animation/spells/firewall/firewall.spritesheet.png", + "width": 400, + "height": 400, + "cooldown": 60, + "frames": [ + { + "x": 0, + "y": 0 + }, + { + "x": 400, + "y": 0 + }, + { + "x": 800, + "y": 0 + }, + { + "x": 1200, + "y": 0 + }, + { + "x": 1600, + "y": 0 + }, + { + "x": 0, + "y": 400 + }, + { + "x": 400, + "y": 400 + }, + { + "x": 800, + "y": 400 + }, + { + "x": 1200, + "y": 400 + }, + { + "x": 1600, + "y": 400 + }, + { + "x": 0, + "y": 800 + }, + { + "x": 400, + "y": 800 + }, + { + "x": 800, + "y": 800 + }, + { + "x": 1200, + "y": 800 + }, + { + "x": 1600, + "y": 800 + }, + { + "x": 0, + "y": 1200 + }, + { + "x": 400, + "y": 1200 + }, + { + "x": 800, + "y": 1200 + }, + { + "x": 1200, + "y": 1200 + }, + { + "x": 1600, + "y": 1200 + }, + { + "x": 0, + "y": 1600 + }, + { + "x": 400, + "y": 1600 + }, + { + "x": 800, + "y": 1600 + }, + { + "x": 1200, + "y": 1600 + }, + { + "x": 1600, + "y": 1600 + }, + { + "x": 0, + "y": 2000 + }, + { + "x": 400, + "y": 2000 + }, + { + "x": 800, + "y": 2000 + }, + { + "x": 1200, + "y": 2000 + }, + { + "x": 1600, + "y": 2000 + }, + { + "x": 0, + "y": 2400 + }, + { + "x": 400, + "y": 2400 + }, + { + "x": 800, + "y": 2400 + }, + { + "x": 1200, + "y": 2400 + }, + { + "x": 1600, + "y": 2400 + }, + { + "x": 0, + "y": 2800 + }, + { + "x": 400, + "y": 2800 + }, + { + "x": 800, + "y": 2800 + }, + { + "x": 1200, + "y": 2800 + }, + { + "x": 1600, + "y": 2800 + }, + { + "x": 0, + "y": 3200 + }, + { + "x": 400, + "y": 3200 + }, + { + "x": 800, + "y": 3200 + }, + { + "x": 1200, + "y": 3200 + }, + { + "x": 1600, + "y": 3200 + }, + { + "x": 0, + "y": 3600 + }, + { + "x": 400, + "y": 3600 + }, + { + "x": 800, + "y": 3600 + }, + { + "x": 1200, + "y": 3600 + }, + { + "x": 1600, + "y": 3600 + }, + { + "x": 0, + "y": 4000 + }, + { + "x": 400, + "y": 4000 + }, + { + "x": 800, + "y": 4000 + }, + { + "x": 1200, + "y": 4000 + }, + { + "x": 1600, + "y": 4000 + }, + { + "x": 0, + "y": 4400 + }, + { + "x": 400, + "y": 4400 + }, + { + "x": 800, + "y": 4400 + }, + { + "x": 1200, + "y": 4400 + }, + { + "x": 1600, + "y": 4400 + }, + { + "x": 0, + "y": 4800 + }, + { + "x": 400, + "y": 4800 + }, + { + "x": 800, + "y": 4800 + }, + { + "x": 1200, + "y": 4800 + }, + { + "x": 1600, + "y": 4800 + }, + { + "x": 0, + "y": 5200 + }, + { + "x": 400, + "y": 5200 + }, + { + "x": 800, + "y": 5200 + }, + { + "x": 1200, + "y": 5200 + }, + { + "x": 1600, + "y": 5200 + }, + { + "x": 0, + "y": 5600 + }, + { + "x": 400, + "y": 5600 + }, + { + "x": 800, + "y": 5600 + }, + { + "x": 1200, + "y": 5600 + }, + { + "x": 1600, + "y": 5600 + }, + { + "x": 0, + "y": 6000 + }, + { + "x": 400, + "y": 6000 + }, + { + "x": 800, + "y": 6000 + }, + { + "x": 1200, + "y": 6000 + }, + { + "x": 1600, + "y": 6000 + }, + { + "x": 0, + "y": 6400 + }, + { + "x": 400, + "y": 6400 + }, + { + "x": 800, + "y": 6400 + }, + { + "x": 1200, + "y": 6400 + }, + { + "x": 1600, + "y": 6400 + }, + { + "x": 0, + "y": 6800 + }, + { + "x": 400, + "y": 6800 + }, + { + "x": 800, + "y": 6800 + }, + { + "x": 1200, + "y": 6800 + }, + { + "x": 1600, + "y": 6800 + }, + { + "x": 0, + "y": 7200 + }, + { + "x": 400, + "y": 7200 + }, + { + "x": 800, + "y": 7200 + }, + { + "x": 1200, + "y": 7200 + }, + { + "x": 1600, + "y": 7200 + }, + { + "x": 0, + "y": 7600 + }, + { + "x": 400, + "y": 7600 + }, + { + "x": 800, + "y": 7600 + }, + { + "x": 1200, + "y": 7600 + }, + { + "x": 1600, + "y": 7600 + }, + { + "x": 0, + "y": 8000 + }, + { + "x": 400, + "y": 8000 + }, + { + "x": 800, + "y": 8000 + }, + { + "x": 1200, + "y": 8000 + }, + { + "x": 1600, + "y": 8000 + }, + { + "x": 0, + "y": 8400 + }, + { + "x": 400, + "y": 8400 + }, + { + "x": 800, + "y": 8400 + }, + { + "x": 1200, + "y": 8400 + }, + { + "x": 1600, + "y": 8400 + }, + { + "x": 0, + "y": 8800 + }, + { + "x": 400, + "y": 8800 + }, + { + "x": 800, + "y": 8800 + }, + { + "x": 1200, + "y": 8800 + }, + { + "x": 1600, + "y": 8800 + }, + { + "x": 0, + "y": 9200 + }, + { + "x": 400, + "y": 9200 + }, + { + "x": 800, + "y": 9200 + }, + { + "x": 1200, + "y": 9200 + }, + { + "x": 1600, + "y": 9200 + }, + { + "x": 0, + "y": 9600 + }, + { + "x": 400, + "y": 9600 + }, + { + "x": 800, + "y": 9600 + }, + { + "x": 1200, + "y": 9600 + }, + { + "x": 1600, + "y": 9600 + } + ] + } + } + } +} diff --git a/data/animation/spells/firewall/firewall.gif b/data/animation/spells/firewall/firewall.gif new file mode 100644 index 0000000..007876d Binary files /dev/null and b/data/animation/spells/firewall/firewall.gif differ diff --git a/data/animation/spells/firewall/firewall.spritesheet.png b/data/animation/spells/firewall/firewall.spritesheet.png new file mode 100644 index 0000000..7cf59f6 Binary files /dev/null and b/data/animation/spells/firewall/firewall.spritesheet.png differ diff --git a/data/animation/spells/rock/rock.data.json b/data/animation/spells/rock/rock.data.json new file mode 100644 index 0000000..70777d6 --- /dev/null +++ b/data/animation/spells/rock/rock.data.json @@ -0,0 +1,30 @@ +{ + "object": { + "animations": { + "default": { + "file": "/animation/spells/rock/rock.spritesheet.png", + "width": 31, + "height": 30, + "cooldown": 100, + "frames": [ + { + "x": 62, + "y": 100 + }, + { + "x": 97, + "y": 100 + }, + { + "x": 134, + "y": 100 + }, + { + "x": 169, + "y": 100 + } + ] + } + } + } +} diff --git a/data/animation/spells/rock/rock.spritesheet.png b/data/animation/spells/rock/rock.spritesheet.png new file mode 100644 index 0000000..02253e0 Binary files /dev/null and b/data/animation/spells/rock/rock.spritesheet.png differ diff --git a/data/animation/spells/shovel/shovel.data.json b/data/animation/spells/shovel/shovel.data.json new file mode 100644 index 0000000..6753121 --- /dev/null +++ b/data/animation/spells/shovel/shovel.data.json @@ -0,0 +1,34 @@ +{ + "object": { + "animations": { + "default": { + "file": "/animation/spells/shovel/shovel.spritesheet.png", + "width": 192, + "height": 192, + "cooldown": 50, + "frames": [ + { + "x": 0, + "y": 0 + }, + { + "x": 192, + "y": 0 + }, + { + "x": 384, + "y": 0 + }, + { + "x": 576, + "y": 0 + }, + { + "x": 768, + "y": 0 + } + ] + } + } + } +} diff --git a/data/animation/spells/shovel/shovel.spritesheet.png b/data/animation/spells/shovel/shovel.spritesheet.png new file mode 100644 index 0000000..511e26b Binary files /dev/null and b/data/animation/spells/shovel/shovel.spritesheet.png differ diff --git a/data/animation/spells/steam/steam.data.json b/data/animation/spells/steam/steam.data.json new file mode 100644 index 0000000..181128d --- /dev/null +++ b/data/animation/spells/steam/steam.data.json @@ -0,0 +1,30 @@ +{ + "object": { + "animations": { + "default": { + "file": "/animation/spells/steam/steam.spritesheet.png", + "width": 256, + "height": 256, + "cooldown": 50, + "frames": [ + { + "x": 0, + "y": 0 + }, + { + "x": 256, + "y": 0 + }, + { + "x": 512, + "y": 0 + }, + { + "x": 768, + "y": 0 + } + ] + } + } + } +} diff --git a/data/animation/spells/steam/steam.gif b/data/animation/spells/steam/steam.gif new file mode 100644 index 0000000..75cf4de Binary files /dev/null and b/data/animation/spells/steam/steam.gif differ diff --git a/data/animation/spells/steam/steam.spritesheet.png b/data/animation/spells/steam/steam.spritesheet.png new file mode 100644 index 0000000..9293e0c Binary files /dev/null and b/data/animation/spells/steam/steam.spritesheet.png differ diff --git a/data/animation/spells/sword/sword.data.json b/data/animation/spells/sword/sword.data.json new file mode 100644 index 0000000..87dcc61 --- /dev/null +++ b/data/animation/spells/sword/sword.data.json @@ -0,0 +1,34 @@ +{ + "object": { + "animations": { + "default": { + "file": "/animation/spells/sword/sword.spritesheet.png", + "width": 192, + "height": 192, + "cooldown": 50, + "frames": [ + { + "x": 0, + "y": 0 + }, + { + "x": 192, + "y": 0 + }, + { + "x": 384, + "y": 0 + }, + { + "x": 576, + "y": 0 + }, + { + "x": 768, + "y": 0 + } + ] + } + } + } +} diff --git a/data/animation/spells/sword/sword.spritesheet.png b/data/animation/spells/sword/sword.spritesheet.png new file mode 100644 index 0000000..0c5e816 Binary files /dev/null and b/data/animation/spells/sword/sword.spritesheet.png differ diff --git a/data/animation/spells/thunder/thunder.data.json b/data/animation/spells/thunder/thunder.data.json new file mode 100644 index 0000000..db6b69d --- /dev/null +++ b/data/animation/spells/thunder/thunder.data.json @@ -0,0 +1,50 @@ +{ + "object": { + "animations": { + "default": { + "file": "/animation/spells/thunder/thunder.spritesheet.png", + "width": 51, + "height": 104, + "cooldown": 100, + "frames": [ + { + "x": 0, + "y": 0 + }, + { + "x": 51, + "y": 0 + }, + { + "x": 102, + "y": 0 + }, + { + "x": 153, + "y": 0 + }, + { + "x": 204, + "y": 0 + }, + { + "x": 255, + "y": 0 + }, + { + "x": 306, + "y": 0 + }, + { + "x": 357, + "y": 0 + }, + { + "x": 408, + "y": 0 + } + ] + } + } + } +} diff --git a/data/animation/spells/thunder/thunder.spritesheet.png b/data/animation/spells/thunder/thunder.spritesheet.png new file mode 100644 index 0000000..d0fbbc9 Binary files /dev/null and b/data/animation/spells/thunder/thunder.spritesheet.png differ diff --git a/data/animation/spells/tornado_sword/tornado_sword.data.json b/data/animation/spells/tornado_sword/tornado_sword.data.json new file mode 100644 index 0000000..47a8863 --- /dev/null +++ b/data/animation/spells/tornado_sword/tornado_sword.data.json @@ -0,0 +1,86 @@ +{ + "object": { + "animations": { + "default": { + "file": "/animation/spells/tornado_sword/tornado_sword.spritesheet.png", + "width": 1024, + "height": 1024, + "cooldown": 50, + "frames": [ + { + "x": 0, + "y": 0 + }, + { + "x": 1024, + "y": 0 + }, + { + "x": 2048, + "y": 0 + }, + { + "x": 3072, + "y": 0 + }, + { + "x": 4096, + "y": 0 + }, + { + "x": 5120, + "y": 0 + }, + { + "x": 0, + "y": 1024 + }, + { + "x": 1024, + "y": 1024 + }, + { + "x": 2048, + "y": 1024 + }, + { + "x": 3072, + "y": 1024 + }, + { + "x": 4096, + "y": 1024 + }, + { + "x": 5120, + "y": 1024 + }, + { + "x": 0, + "y": 2048 + }, + { + "x": 1024, + "y": 2048 + }, + { + "x": 2048, + "y": 2048 + }, + { + "x": 3072, + "y": 2048 + }, + { + "x": 4096, + "y": 2048 + }, + { + "x": 5120, + "y": 2048 + } + ] + } + } + } +} diff --git a/data/animation/spells/tornado_sword/tornado_sword.spritesheet.png b/data/animation/spells/tornado_sword/tornado_sword.spritesheet.png new file mode 100644 index 0000000..31e9cbf Binary files /dev/null and b/data/animation/spells/tornado_sword/tornado_sword.spritesheet.png differ diff --git a/data/anims/bosses/onlyone/boss.data.json b/data/anims/bosses/onlyone/boss.data.json deleted file mode 100644 index 3d2670c..0000000 --- a/data/anims/bosses/onlyone/boss.data.json +++ /dev/null @@ -1,186 +0,0 @@ -{ - "object": { - "file": "anims/bosses/onlyone/boss_spritesheet.png", - "width": 470, - "height": 470, - "speed": 50, - "animations": { - "hold_right": [ - { - "x": 940, - "y": 940 - } - ], - "run_right": [ - { - "x": 0, - "y": 0 - }, - { - "x": 470, - "y": 0 - }, - { - "x": 940, - "y": 0 - }, - { - "x": 1410, - "y": 0 - }, - { - "x": 1880, - "y": 0 - }, - { - "x": 0, - "y": 470 - }, - { - "x": 470, - "y": 470 - }, - { - "x": 940, - "y": 470 - }, - { - "x": 1410, - "y": 470 - }, - { - "x": 1880, - "y": 470 - }, - { - "x": 0, - "y": 940 - }, - { - "x": 470, - "y": 940 - }, - { - "x": 940, - "y": 940 - }, - { - "x": 1410, - "y": 940 - }, - { - "x": 1880, - "y": 940 - }, - { - "x": 0, - "y": 1410 - }, - { - "x": 470, - "y": 1410 - }, - { - "x": 940, - "y": 1410 - }, - { - "x": 1410, - "y": 1410 - }, - { - "x": 1880, - "y": 1410 - } - ], - "hold_left": [ - { - "x": 940, - "y": 3290 - } - ], - "run_left": [ - { - "x": 0, - "y": 2350 - }, - { - "x": 470, - "y": 2350 - }, - { - "x": 940, - "y": 2350 - }, - { - "x": 1410, - "y": 2350 - }, - { - "x": 1880, - "y": 2350 - }, - { - "x": 0, - "y": 2820 - }, - { - "x": 470, - "y": 2820 - }, - { - "x": 940, - "y": 2820 - }, - { - "x": 1410, - "y": 2820 - }, - { - "x": 1880, - "y": 2820 - }, - { - "x": 0, - "y": 3290 - }, - { - "x": 470, - "y": 3290 - }, - { - "x": 940, - "y": 3290 - }, - { - "x": 1410, - "y": 3290 - }, - { - "x": 1880, - "y": 3290 - }, - { - "x": 0, - "y": 3760 - }, - { - "x": 470, - "y": 3760 - }, - { - "x": 940, - "y": 3760 - }, - { - "x": 1410, - "y": 3760 - }, - { - "x": 1880, - "y": 3760 - } - ] - } - } -} diff --git a/data/anims/bosses/onlyone/boss_spritesheet.png b/data/anims/bosses/onlyone/boss_spritesheet.png deleted file mode 100644 index 4bfe5c6..0000000 Binary files a/data/anims/bosses/onlyone/boss_spritesheet.png and /dev/null differ diff --git a/data/anims/classes/farmer/farmer.data.json b/data/anims/classes/farmer/farmer.data.json deleted file mode 100644 index a88bbdf..0000000 --- a/data/anims/classes/farmer/farmer.data.json +++ /dev/null @@ -1,194 +0,0 @@ -{ - "object": { - "file": "/anims/classes/farmer/farmer_spritesheet.png", - "width": 500, - "height": 500, - "speed": 50, - "animations": { - "hold_left": [ - { - "x": 1000, - "y": 1000 - } - ], - "run_left": [ - { - "x": 0, - "y": 0 - }, - { - "x": 500, - "y": 0 - }, - { - "x": 1000, - "y": 0 - }, - { - "x": 1500, - "y": 0 - }, - { - "x": 2000, - "y": 0 - }, - { - "x": 0, - "y": 500 - }, - { - "x": 500, - "y": 500 - }, - { - "x": 1000, - "y": 500 - }, - { - "x": 1500, - "y": 500 - }, - { - "x": 2000, - "y": 500 - }, - { - "x": 0, - "y": 1000 - }, - { - "x": 500, - "y": 1000 - }, - { - "x": 1000, - "y": 1000 - }, - { - "x": 1500, - "y": 1000 - }, - { - "x": 2000, - "y": 1000 - }, - { - "x": 0, - "y": 1500 - }, - { - "x": 500, - "y": 1500 - }, - { - "x": 1000, - "y": 1500 - }, - { - "x": 1500, - "y": 1500 - }, - { - "x": 2000, - "y": 1500 - }, - { - "x": 0, - "y": 2000 - } - ], - "hold_right": [ - { - "x": 1000, - "y": 3500 - } - ], - "run_right": [ - { - "x": 0, - "y": 2500 - }, - { - "x": 500, - "y": 2500 - }, - { - "x": 1000, - "y": 2500 - }, - { - "x": 1500, - "y": 2500 - }, - { - "x": 2000, - "y": 2500 - }, - { - "x": 0, - "y": 3000 - }, - { - "x": 500, - "y": 3000 - }, - { - "x": 1000, - "y": 3000 - }, - { - "x": 1500, - "y": 3000 - }, - { - "x": 2000, - "y": 3000 - }, - { - "x": 0, - "y": 3500 - }, - { - "x": 500, - "y": 3500 - }, - { - "x": 1000, - "y": 3500 - }, - { - "x": 1500, - "y": 3500 - }, - { - "x": 2000, - "y": 3500 - }, - { - "x": 0, - "y": 4000 - }, - { - "x": 500, - "y": 4000 - }, - { - "x": 1000, - "y": 4000 - }, - { - "x": 1500, - "y": 4000 - }, - { - "x": 2000, - "y": 4000 - }, - { - "x": 0, - "y": 4500 - } - ] - } - } -} diff --git a/data/anims/classes/farmer/farmer1.gif b/data/anims/classes/farmer/farmer1.gif deleted file mode 100644 index fa50638..0000000 Binary files a/data/anims/classes/farmer/farmer1.gif and /dev/null differ diff --git a/data/anims/classes/farmer/farmer_spritesheet.png b/data/anims/classes/farmer/farmer_spritesheet.png deleted file mode 100644 index 41f6814..0000000 Binary files a/data/anims/classes/farmer/farmer_spritesheet.png and /dev/null differ diff --git a/data/anims/classes/shooter/shooter.data.json b/data/anims/classes/shooter/shooter.data.json deleted file mode 100644 index 56f58eb..0000000 --- a/data/anims/classes/shooter/shooter.data.json +++ /dev/null @@ -1,130 +0,0 @@ -{ - "object": { - "file": "/anims/classes/shooter/shooter_spritesheet.png", - "width": 450, - "height": 575, - "speed": 50, - "animations": { - "hold_left": [ - { - "x": 900, - "y": 1150 - } - ], - "run_left": [ - { - "x": 0, - "y": 0 - }, - { - "x": 450, - "y": 0 - }, - { - "x": 900, - "y": 0 - }, - { - "x": 1350, - "y": 0 - }, - { - "x": 1800, - "y": 0 - }, - { - "x": 0, - "y": 575 - }, - { - "x": 450, - "y": 575 - }, - { - "x": 900, - "y": 575 - }, - { - "x": 1350, - "y": 575 - }, - { - "x": 1800, - "y": 575 - }, - { - "x": 0, - "y": 1150 - }, - { - "x": 450, - "y": 1150 - }, - { - "x": 900, - "y": 1150 - } - ], - "hold_right": [ - { - "x": 900, - "y": 2875 - } - ], - "run_right": [ - { - "x": 0, - "y": 1725 - }, - { - "x": 450, - "y": 1725 - }, - { - "x": 900, - "y": 1725 - }, - { - "x": 1350, - "y": 1725 - }, - { - "x": 1800, - "y": 1725 - }, - { - "x": 0, - "y": 2300 - }, - { - "x": 450, - "y": 2300 - }, - { - "x": 900, - "y": 2300 - }, - { - "x": 1350, - "y": 2300 - }, - { - "x": 1800, - "y": 2300 - }, - { - "x": 0, - "y": 2875 - }, - { - "x": 450, - "y": 2875 - }, - { - "x": 900, - "y": 2875 - } - ] - } - } -} diff --git a/data/anims/classes/shooter/shooter.gif b/data/anims/classes/shooter/shooter.gif deleted file mode 100644 index b93a8bc..0000000 Binary files a/data/anims/classes/shooter/shooter.gif and /dev/null differ diff --git a/data/anims/classes/shooter/shooter_spritesheet.png b/data/anims/classes/shooter/shooter_spritesheet.png deleted file mode 100644 index fc4d0cc..0000000 Binary files a/data/anims/classes/shooter/shooter_spritesheet.png and /dev/null differ diff --git a/data/anims/classes/soldier/soldier.data.json b/data/anims/classes/soldier/soldier.data.json deleted file mode 100644 index 1b1b1b4..0000000 --- a/data/anims/classes/soldier/soldier.data.json +++ /dev/null @@ -1,338 +0,0 @@ -{ - "object": { - "file": "/anims/classes/soldier/soldier_spritesheet.png", - "width": 300, - "height": 315, - "speed": 10, - "animations": { - "hold_right": [ - { - "x": 600, - "y": 630 - } - ], - "run_right": [ - { - "x": 0, - "y": 0 - }, - { - "x": 300, - "y": 0 - }, - { - "x": 600, - "y": 0 - }, - { - "x": 900, - "y": 0 - }, - { - "x": 1200, - "y": 0 - }, - { - "x": 0, - "y": 315 - }, - { - "x": 300, - "y": 315 - }, - { - "x": 600, - "y": 315 - }, - { - "x": 900, - "y": 315 - }, - { - "x": 1200, - "y": 315 - }, - { - "x": 0, - "y": 630 - }, - { - "x": 300, - "y": 630 - }, - { - "x": 600, - "y": 630 - }, - { - "x": 900, - "y": 630 - }, - { - "x": 1200, - "y": 630 - }, - { - "x": 0, - "y": 945 - }, - { - "x": 300, - "y": 945 - }, - { - "x": 600, - "y": 945 - }, - { - "x": 900, - "y": 945 - }, - { - "x": 1200, - "y": 945 - }, - { - "x": 0, - "y": 1260 - }, - { - "x": 300, - "y": 1260 - }, - { - "x": 600, - "y": 1260 - }, - { - "x": 900, - "y": 1260 - }, - { - "x": 1200, - "y": 1260 - }, - { - "x": 0, - "y": 1575 - }, - { - "x": 300, - "y": 1575 - }, - { - "x": 600, - "y": 1575 - }, - { - "x": 900, - "y": 1575 - }, - { - "x": 1200, - "y": 1575 - }, - { - "x": 0, - "y": 1890 - }, - { - "x": 300, - "y": 1890 - }, - { - "x": 600, - "y": 1890 - }, - { - "x": 900, - "y": 1890 - }, - { - "x": 1200, - "y": 1890 - }, - { - "x": 0, - "y": 2205 - }, - { - "x": 300, - "y": 2205 - }, - { - "x": 600, - "y": 2205 - }, - { - "x": 900, - "y": 2205 - } - ], - "hold_left": [ - { - "x": 600, - "y": 3150 - } - ], - "run_left": [ - { - "x": 0, - "y": 2520 - }, - { - "x": 300, - "y": 2520 - }, - { - "x": 600, - "y": 2520 - }, - { - "x": 900, - "y": 2520 - }, - { - "x": 1200, - "y": 2520 - }, - { - "x": 0, - "y": 2835 - }, - { - "x": 300, - "y": 2835 - }, - { - "x": 600, - "y": 2835 - }, - { - "x": 900, - "y": 2835 - }, - { - "x": 1200, - "y": 2835 - }, - { - "x": 0, - "y": 3150 - }, - { - "x": 300, - "y": 3150 - }, - { - "x": 600, - "y": 3150 - }, - { - "x": 900, - "y": 3150 - }, - { - "x": 1200, - "y": 3150 - }, - { - "x": 0, - "y": 3465 - }, - { - "x": 300, - "y": 3465 - }, - { - "x": 600, - "y": 3465 - }, - { - "x": 900, - "y": 3465 - }, - { - "x": 1200, - "y": 3465 - }, - { - "x": 0, - "y": 3780 - }, - { - "x": 300, - "y": 3780 - }, - { - "x": 600, - "y": 3780 - }, - { - "x": 900, - "y": 3780 - }, - { - "x": 1200, - "y": 3780 - }, - { - "x": 0, - "y": 4095 - }, - { - "x": 300, - "y": 4095 - }, - { - "x": 600, - "y": 4095 - }, - { - "x": 900, - "y": 4095 - }, - { - "x": 1200, - "y": 4095 - }, - { - "x": 0, - "y": 4410 - }, - { - "x": 300, - "y": 4410 - }, - { - "x": 600, - "y": 4410 - }, - { - "x": 900, - "y": 4410 - }, - { - "x": 1200, - "y": 4410 - }, - { - "x": 0, - "y": 4725 - }, - { - "x": 300, - "y": 4725 - }, - { - "x": 600, - "y": 4725 - }, - { - "x": 900, - "y": 4725 - } - ] - } - } -} diff --git a/data/anims/classes/soldier/soldier.gif b/data/anims/classes/soldier/soldier.gif deleted file mode 100644 index 553c1b5..0000000 Binary files a/data/anims/classes/soldier/soldier.gif and /dev/null differ diff --git a/data/anims/classes/soldier/soldier_spritesheet.png b/data/anims/classes/soldier/soldier_spritesheet.png deleted file mode 100644 index b40fa74..0000000 Binary files a/data/anims/classes/soldier/soldier_spritesheet.png and /dev/null differ diff --git a/data/anims/classes/sorcerer/sorcerer.data.json b/data/anims/classes/sorcerer/sorcerer.data.json deleted file mode 100644 index dbc3254..0000000 --- a/data/anims/classes/sorcerer/sorcerer.data.json +++ /dev/null @@ -1,138 +0,0 @@ -{ - "object": { - "file": "/anims/classes/sorcerer/sorcerer_spritesheet.png", - "width": 110, - "height": 170, - "speed": 25, - "animations": { - "hold_left": [ - { - "x": 220, - "y": 340 - } - ], - "run_left": [ - { - "x": 0, - "y": 0 - }, - { - "x": 110, - "y": 0 - }, - { - "x": 220, - "y": 0 - }, - { - "x": 330, - "y": 0 - }, - { - "x": 440, - "y": 0 - }, - { - "x": 0, - "y": 170 - }, - { - "x": 110, - "y": 170 - }, - { - "x": 220, - "y": 170 - }, - { - "x": 330, - "y": 170 - }, - { - "x": 440, - "y": 170 - }, - { - "x": 0, - "y": 340 - }, - { - "x": 110, - "y": 340 - }, - { - "x": 220, - "y": 340 - }, - { - "x": 330, - "y": 340 - } - ], - "hold_right": [ - { - "x": 220, - "y": 680 - } - ], - "run_right": [ - { - "x": 0, - "y": 510 - }, - { - "x": 110, - "y": 510 - }, - { - "x": 220, - "y": 510 - }, - { - "x": 330, - "y": 510 - }, - { - "x": 440, - "y": 510 - }, - { - "x": 0, - "y": 680 - }, - { - "x": 110, - "y": 680 - }, - { - "x": 220, - "y": 680 - }, - { - "x": 330, - "y": 680 - }, - { - "x": 440, - "y": 680 - }, - { - "x": 0, - "y": 850 - }, - { - "x": 110, - "y": 850 - }, - { - "x": 220, - "y": 850 - }, - { - "x": 330, - "y": 850 - } - ] - } - } -} diff --git a/data/anims/classes/sorcerer/sorcerer.gif b/data/anims/classes/sorcerer/sorcerer.gif deleted file mode 100644 index 929a8fa..0000000 Binary files a/data/anims/classes/sorcerer/sorcerer.gif and /dev/null differ diff --git a/data/anims/classes/sorcerer/sorcerer_spritesheet.png b/data/anims/classes/sorcerer/sorcerer_spritesheet.png deleted file mode 100644 index 1de3d29..0000000 Binary files a/data/anims/classes/sorcerer/sorcerer_spritesheet.png and /dev/null differ diff --git a/data/anims/enemies/boss/example.data.json b/data/anims/enemies/boss/example.data.json deleted file mode 100644 index b48e99b..0000000 --- a/data/anims/enemies/boss/example.data.json +++ /dev/null @@ -1,126 +0,0 @@ -{ - "object": { - "file": "anims/enemies/boss/example.spritesheet.png", - "width": 220, - "height": 165, - "speed": 50, - "animations": { - "chase": [ - { - "x": 440, - "y": 330 - } - ], - "default": [ - { - "x": 0, - "y": 0 - }, - { - "x": 220, - "y": 0 - }, - { - "x": 440, - "y": 0 - }, - { - "x": 660, - "y": 0 - }, - { - "x": 880, - "y": 0 - }, - { - "x": 0, - "y": 165 - }, - { - "x": 220, - "y": 165 - }, - { - "x": 440, - "y": 165 - }, - { - "x": 660, - "y": 165 - }, - { - "x": 880, - "y": 165 - }, - { - "x": 0, - "y": 330 - }, - { - "x": 220, - "y": 330 - }, - { - "x": 440, - "y": 330 - }, - { - "x": 660, - "y": 330 - }, - { - "x": 880, - "y": 330 - }, - { - "x": 0, - "y": 495 - }, - { - "x": 220, - "y": 495 - }, - { - "x": 440, - "y": 495 - }, - { - "x": 660, - "y": 495 - }, - { - "x": 880, - "y": 495 - }, - { - "x": 0, - "y": 660 - }, - { - "x": 220, - "y": 660 - }, - { - "x": 440, - "y": 660 - }, - { - "x": 660, - "y": 660 - }, - { - "x": 880, - "y": 660 - }, - { - "x": 0, - "y": 825 - }, - { - "x": 220, - "y": 825 - } - ] - } - } -} diff --git a/data/anims/enemies/boss/example.gif b/data/anims/enemies/boss/example.gif deleted file mode 100644 index a155e59..0000000 Binary files a/data/anims/enemies/boss/example.gif and /dev/null differ diff --git a/data/anims/enemies/boss/example.spritesheet.png b/data/anims/enemies/boss/example.spritesheet.png deleted file mode 100644 index a76c1c9..0000000 Binary files a/data/anims/enemies/boss/example.spritesheet.png and /dev/null differ diff --git a/data/anims/spells/farmer_attack/farmer_anim.data.json b/data/anims/spells/farmer_attack/farmer_anim.data.json deleted file mode 100644 index bdab83d..0000000 --- a/data/anims/spells/farmer_attack/farmer_anim.data.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "object": { - "file": "/anims/spells/farmer_attack/farmer_anim.png", - "width": 300, - "height": 300, - "speed": 50, - "animations": { - "hold": [ - { - "x": 600, - "y": 600 - } - ], - "default": [ - { - "x": 0, - "y": 0 - }, - { - "x": 300, - "y": 0 - }, - { - "x": 600, - "y": 0 - }, - { - "x": 900, - "y": 0 - }, - { - "x": 1200, - "y": 0 - }, - { - "x": 0, - "y": 300 - }, - { - "x": 600, - "y": 300 - } - ] - } - } -} diff --git a/data/anims/spells/farmer_attack/farmer_anim.png b/data/anims/spells/farmer_attack/farmer_anim.png deleted file mode 100644 index 2f93ad9..0000000 Binary files a/data/anims/spells/farmer_attack/farmer_anim.png and /dev/null differ diff --git a/data/anims/spells/fireball/fireball.data.json b/data/anims/spells/fireball/fireball.data.json deleted file mode 100644 index 192c739..0000000 --- a/data/anims/spells/fireball/fireball.data.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "object": { - "file": "/anims/spells/fireball/fireball.spritesheet.png", - "width": 256, - "height": 256, - "speed": 60, - "animations": { - "default": [ - { - "x": 0, - "y": 0 - }, - { - "x": 256, - "y": 0 - }, - { - "x": 512, - "y": 0 - }, - { - "x": 768, - "y": 0 - }, - { - "x": 1024, - "y": 0 - } - ] - } - } -} diff --git a/data/anims/spells/shooter_attack/shooter_attack.data.json b/data/anims/spells/shooter_attack/shooter_attack.data.json deleted file mode 100644 index 6b17038..0000000 --- a/data/anims/spells/shooter_attack/shooter_attack.data.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "object": { - "file": "/anims/spells/shooter_attack/shooter_attack.png", - "width": 466, - "height": 133, - "speed": 50, - "animations": { - "hold": [ - { - "x": 932, - "y": 266 - } - ], - "default": [ - { - "x": 0, - "y": 0 - }, - { - "x": 466, - "y": 0 - }, - { - "x": 932, - "y": 0 - }, - { - "x": 1398, - "y": 0 - }, - { - "x": 1864, - "y": 0 - }, - { - "x": 0, - "y": 133 - }, - { - "x": 466, - "y": 133 - }, - { - "x": 932, - "y": 133 - }, - { - "x": 1398, - "y": 133 - }, - { - "x": 1864, - "y": 133 - }, - { - "x": 0, - "y": 266 - }, - { - "x": 466, - "y": 266 - }, - { - "x": 932, - "y": 266 - }, - { - "x": 1398, - "y": 266 - } - ] - } - } -} diff --git a/data/anims/spells/shooter_attack/shooter_attack.png b/data/anims/spells/shooter_attack/shooter_attack.png deleted file mode 100644 index 67f82c9..0000000 Binary files a/data/anims/spells/shooter_attack/shooter_attack.png and /dev/null differ diff --git a/data/anims/spells/soldier_attack/soldier_attack.data.json b/data/anims/spells/soldier_attack/soldier_attack.data.json deleted file mode 100644 index 6cd39c7..0000000 --- a/data/anims/spells/soldier_attack/soldier_attack.data.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "object": { - "file": "/anims/spells/soldier_attack/soldier_attack.png", - "width": 480, - "height": 200, - "speed": 50, - "animations": { - "hold": [ - { - "x": 960, - "y": 0 - } - ], - "default": [ - { - "x": 0, - "y": 0 - }, - { - "x": 480, - "y": 0 - }, - { - "x": 960, - "y": 0 - }, - { - "x": 1440, - "y": 0 - }, - { - "x": 1920, - "y": 0 - } - ] - } - } -} diff --git a/data/anims/spells/soldier_attack/soldier_attack.png b/data/anims/spells/soldier_attack/soldier_attack.png deleted file mode 100644 index 4600c83..0000000 Binary files a/data/anims/spells/soldier_attack/soldier_attack.png and /dev/null differ diff --git a/data/config/app.ini b/data/config/app.ini index 53ad217..d4530b7 100644 --- a/data/config/app.ini +++ b/data/config/app.ini @@ -1,10 +1,19 @@ -; ThePURGE 0.2.18 +; ThePURGE 0.3.16 ; Print the version number and exit. version=false ; Launch the window in fullscreen mode. -fullscreen=true +fullscreen=false -; Path of the data folder +; Width of the window. +window-width=1920 + +; Height of the window. +window-height=1080 + +; Path of the data folder. data="data/" + +; Path of the generated output. +output-folder="../generated/" diff --git a/data/config/app.ini.last b/data/config/app.ini.last new file mode 100644 index 0000000..2f55a1e --- /dev/null +++ b/data/config/app.ini.last @@ -0,0 +1,22 @@ +; ThePURGE 0.3.22 + +; Print the version number and exit. +version=false + +; Launch the window in fullscreen mode. +fullscreen=false + +; Width of the window. +window-width=1920 + +; Height of the window. +window-height=1080 + +; Path of the events to replay. +replay-path="../generated/recoreded_events.json" + +; Path of the data folder. +data="data/" + +; Path of the generated output. +output-folder="../generated/" diff --git a/data/config/imgui.ini b/data/config/imgui.ini index 89bcc67..0d23447 100644 --- a/data/config/imgui.ini +++ b/data/config/imgui.ini @@ -1,5 +1,5 @@ [Window][Debug##Default] -Pos=-18, 0 +Pos=-18,0 Size=400,400 Collapsed=0 @@ -67,3 +67,19 @@ Collapsed=0 Pos=1363,455 Size=520,600 Collapsed=0 + +[Window][MainMenu] +Pos=0,0 +Size=1920,1055 +Collapsed=0 + +[Window][HUD] +Pos=0,0 +Size=1920,1055 +Collapsed=0 + +[Window][GameOver] +Pos=-5,-1 +Size=1930,1061 +Collapsed=0 + diff --git a/data/db/classes.json b/data/db/classes.json index 0f672e5..bbe66ad 100644 --- a/data/db/classes.json +++ b/data/db/classes.json @@ -1,13 +1,17 @@ { "farmer": { "starter": true, - "desc": "Farmer description", - "icon": "icons/farmer.png", - "assetGraph": "anims/classes/farmer/farmer.data.json", - "maxHealth": 10, - "damage": 1, + "icon": "img/icons/classes/farmer.png", + "assetGraph": "animation/classes/farmer/farmer.data.json", + "health": 10, + "speed": 0.5, + "cost": 0, + "hitbox": { + "x": 0.75, + "y": 1.4 + }, "spells": [ - 0 + "shovel" ], "children": [ "soldier", @@ -16,35 +20,146 @@ ] }, "soldier": { - "desc": "Soldier description", - "icon": "icons/soldier.png", - "assetGraph": "anims/classes/soldier/soldier.data.json", - "maxHealth": 20, - "damage": 5, + "icon": "img/icons/classes/soldier.png", + "assetGraph": "animation/classes/soldier/soldier.data.json", + "health": 5, + "speed": 0.5, + "cost": 1, + "hitbox": { + "x": 0.75, + "y": 1.4 + }, "spells": [ - 1 + "sword" ], - "children": [] + "children": [ + "assassin", + "warrior", + "tank" + ] }, "shooter": { - "desc": "Shooter description", - "icon": "icons/shooter.png", - "assetGraph": "anims/classes/shooter/shooter.data.json", - "maxHealth": 15, - "damage": 20, + "icon": "img/icons/classes/shooter.png", + "assetGraph": "animation/classes/shooter/shooter.data.json", + "health": 2, + "speed": 0.7, + "cost": 1, + "hitbox": { + "x": 0.75, + "y": 1.4 + }, "spells": [ - 3 + "arrow" ], - "children": [] + "children": [ + "archer", + "gunner" + ] }, "sorcerer": { - "desc": "Sorcerer description", - "icon": "icons/sorcerer.png", - "assetGraph": "anims/classes/sorcerer/sorcerer.data.json", - "maxHealth": 25, - "damage": 15, + "icon": "img/icons/classes/sorcerer.png", + "assetGraph": "animation/classes/sorcerer/sorcerer.data.json", + "health": 1, + "speed": 1.0, + "cost": 1, + "hitbox": { + "x": 0.75, + "y": 1.4 + }, + "spells": [ + "fireball" + ], + "children": [ + "mage" + ] + }, + "assassin": { + "icon": "img/icons/classes/assassin.png", + "assetGraph": "animation/classes/assassin/assassin.data.json", + "health": 5, + "speed": 1.5, + "cost": 2, + "hitbox": { + "x": 0.75, + "y": 1.4 + }, + "spells": [ + "shadow_jump" + ], + "children": [] + }, + "warrior": { + "icon": "img/icons/classes/warrior.png", + "assetGraph": "animation/classes/warrior/warrior.data.json", + "health": 8, + "speed": 0.7, + "cost": 2, + "hitbox": { + "x": 0.75, + "y": 1.4 + }, + "spells": [ + "tornado_sword" + ], + "children": [] + }, + "tank": { + "icon": "img/icons/classes/tank.png", + "assetGraph": "animation/classes/tank/tank.data.json", + "health": 10, + "speed": -1, + "cost": 2, + "hitbox": { + "x": 0.75, + "y": 1.4 + }, + "spells": [ + "after_shock" + ], + "children": [] + }, + "archer": { + "icon": "img/icons/classes/archer.png", + "assetGraph": "animation/classes/archer/archer.data.json", + "health": 2, + "speed": 0.8, + "cost": 2, + "hitbox": { + "x": 0.75, + "y": 1.4 + }, + "spells": [ + "triple_arrow" + ], + "children": [] + }, + "gunner": { + "icon": "img/icons/classes/gunner.png", + "assetGraph": "animation/classes/gunner/gunner.data.json", + "health": 3, + "speed": 0.7, + "cost": 2, + "hitbox": { + "x": 0.75, + "y": 1.4 + }, + "spells": [ + "throw_barrel" + ], + "children": [] + }, + "mage": { + "icon": "img/icons/classes/mage.png", + "assetGraph": "animation/classes/mage/mage.data.json", + "health": 3, + "speed": 1.0, + "cost": 2, + "hitbox": { + "x": 0.75, + "y": 1.4 + }, "spells": [ - 2 + "firewall" ], "children": [] } diff --git a/data/db/effects.json b/data/db/effects.json new file mode 100644 index 0000000..8fa27a3 --- /dev/null +++ b/data/db/effects.json @@ -0,0 +1,32 @@ +{ + "burn": { + "type": "dot", + "damage": 0.1, + "lifetime": 500, + "cooldown": 200 + }, + "sliced": { + "type": "dot", + "damage": 3, + "lifetime": 1000, + "cooldown": 550 + }, + "slow": { + "type": "dash", + "lifetime": 3000, + "cooldown": 2900, + "strength": 10 + }, + "explosion_effect": { + "type": "dot", + "damage": 8, + "lifetime": 300, + "cooldown": 230 + }, + "shadow_jump_effect": { + "type": "dash", + "lifetime": 80, + "cooldown": 50, + "strength": 0.1 + } +} diff --git a/data/db/enemies.json b/data/db/enemies.json new file mode 100644 index 0000000..70b21ef --- /dev/null +++ b/data/db/enemies.json @@ -0,0 +1,318 @@ +{ + "summoner": { + "asset": "animation/enemies/summoner/summoner.data.json", + "health": 6, + "speed": 4.0, + "view_range": 20, + "attack_range": 8, + "experience": 3, + "color": { + "r": 1, + "g": 1, + "b": 0.2 + }, + "hitbox": { + "x": 1.0, + "y": 1.0 + }, + "scale": { + "x": 0.8, + "y": 1.0 + }, + "spells": [ + "fireball_blue" + ] + }, + "zombie": { + "asset": "animation/enemies/zombie/zombie.data.json", + "health": 7, + "speed": 5.0, + "view_range": 15, + "attack_range": 8, + "experience": 3, + "color": { + "r": 1, + "g": 1, + "b": 0.2 + }, + "hitbox": { + "x": 1.0, + "y": 1.0 + }, + "scale": { + "x": 0.8, + "y": 1.0 + }, + "spells": [ + "shovel" + ] + }, + "golem": { + "asset": "animation/enemies/golem/golem.data.json", + "health": 15, + "speed": 2.5, + "view_range": 18, + "attack_range": 10, + "experience": 4, + "color": { + "r": 0.4, + "g": 0.4, + "b": 0.4 + }, + "hitbox": { + "x": 3.0, + "y": 3.0 + }, + "scale": { + "x": 2.5, + "y": 2.5 + }, + "spells": [ + "rock" + ] + }, + "skeleton": { + "asset": "animation/enemies/skeleton/skeleton.data.json", + "health": 2, + "speed": 5.0, + "view_range": 9, + "attack_range": 4, + "experience": 2, + "color": { + "r": 1, + "g": 1, + "b": 1 + }, + "hitbox": { + "x": 1.0, + "y": 1.0 + }, + "scale": { + "x": 0.8, + "y": 1.0 + }, + "spells": [ + "shovel" + ] + }, + "golem_turret": { + "asset": "animation/enemies/golem_turret/golem_turret.data.json", + "health": 1, + "speed": 0.0001, + "view_range": 6, + "attack_range": 10, + "experience": 2, + "color": { + "r": 1, + "g": 1, + "b": 1 + }, + "hitbox": { + "x": 0.8, + "y": 0.8 + }, + "scale": { + "x": 0.5, + "y": 0.5 + }, + "spells": [ + "small_rock" + ] + }, + "electric_skeleton": { + "asset": "animation/enemies/skeleton/skeleton.data.json", + "health": 3, + "speed": 5.0, + "view_range": 15, + "attack_range": 8, + "experience": 3, + "color": { + "r": 1, + "g": 1, + "b": 0.2 + }, + "hitbox": { + "x": 1.0, + "y": 1.0 + }, + "scale": { + "x": 0.8, + "y": 1.0 + }, + "spells": [ + "thunder" + ] + }, + "dark_skeleton": { + "asset": "animation/enemies/skeleton/skeleton.data.json", + "health": 20, + "speed": 7.0, + "view_range": 15, + "attack_range": 5, + "experience": 8, + "color": { + "r": 0.4, + "g": 0.4, + "b": 0.4 + }, + "hitbox": { + "x": 2.8, + "y": 2.8 + }, + "scale": { + "x": 2.2, + "y": 2.5 + }, + "spells": [ + "sword" + ], + "is_boss": true + }, + "monster_guy": { + "asset": "animation/enemies/monster_guy/monster_guy.data.json", + "health": 10, + "speed": 5.5, + "view_range": 15, + "attack_range": 8, + "experience": 10, + "color": { + "r": 1, + "g": 1, + "b": 1 + }, + "hitbox": { + "x": 1.1, + "y": 1.1 + }, + "scale": { + "x": 0.5, + "y": 0.5 + }, + "spells": [ + "small_rock", + "thunder" + ], + "is_boss": true + }, + "monster_guy2": { + "asset": "animation/enemies/monster_guy/monster_guy.data.json", + "health": 10, + "speed": 5.5, + "view_range": 15, + "attack_range": 8, + "experience": 10, + "color": { + "r": 0, + "g": 1, + "b": 0 + }, + "hitbox": { + "x": 1.1, + "y": 1.1 + }, + "scale": { + "x": 0.5, + "y": 0.5 + }, + "spells": [ + "small_rock", + "dash" + ], + "is_boss": true + }, + "ice_skeleton": { + "asset": "animation/enemies/skeleton/skeleton.data.json", + "health": 2, + "speed": 6.0, + "view_range": 12, + "attack_range": 2.2, + "experience": 4, + "color": { + "r": 0, + "g": 0.4, + "b": 0.8 + }, + "hitbox": { + "x": 1.0, + "y": 1.0 + }, + "scale": { + "x": 0.8, + "y": 1 + }, + "spells": [ + "freeze" + ] + }, + "fire_skeleton": { + "asset": "animation/enemies/skeleton/skeleton.data.json", + "health": 1, + "speed": 6.0, + "view_range": 8, + "attack_range": 2.2, + "experience": 4, + "color": { + "r": 1, + "g": 0.2, + "b": 0.2 + }, + "hitbox": { + "x": 1.0, + "y": 1.0 + }, + "scale": { + "x": 0.8, + "y": 1 + }, + "spells": [ + "explosion" + ] + }, + "rock_destructible": { + "asset": "animation/map/rock/rock.data.json", + "health": 2, + "speed": 0, + "view_range": 0, + "attack_range": 0, + "experience": 0, + "color": { + "r": 1, + "g": 1, + "b": 1 + }, + "hitbox": { + "x": 0.8, + "y": 0.8 + }, + "scale": { + "x": 0.8, + "y": 1 + }, + "spells": [] + }, + "rock_obstacle": { + "asset": "animation/map/rock/rock.data.json", + "health": 10000, + "speed": 0, + "view_range": 0, + "attack_range": 0, + "experience": 0, + "color": { + "r": 0.3, + "g": 0.3, + "b": 0.3 + }, + "hitbox": { + "x": 0.8, + "y": 0.8 + }, + "scale": { + "x": 0.8, + "y": 1 + }, + "spells": [], + "tag": [ + "wall" + ] + } +} diff --git a/data/db/spells.json b/data/db/spells.json new file mode 100644 index 0000000..de56fdb --- /dev/null +++ b/data/db/spells.json @@ -0,0 +1,430 @@ +{ + "shovel": { + "icon": "img/icons/spells/shovel.png", + "description": "A weak shovel blow. Everyone have to start somewhere...", + "cooldown": 800, + "damage": 1, + "scale": { + "x": 1.5, + "y": 1.5 + }, + "hitbox": { + "x": 0.2, + "y": 0.7 + }, + "lifetime": 200, + "audio_on_cast": "sounds/spells/shovel.wav", + "animation": "animation/spells/shovel/shovel.data.json", + "speed": 0, + "offset_to_source": { + "x": 0.5, + "y": 0.5 + }, + "type": [ + "projectile" + ] + }, + "sword": { + "icon": "img/icons/spells/sword.png", + "description": "A powerful sword blow, all soldiers know how to do it", + "cooldown": 500, + "damage": 3, + "scale": { + "x": 1.5, + "y": 1.5 + }, + "hitbox": { + "x": 0.3, + "y": 0.8 + }, + "lifetime": 200, + "audio_on_cast": "sounds/spells/sword.wav", + "animation": "animation/spells/sword/sword.data.json", + "speed": 0, + "offset_to_source": { + "x": 1.05, + "y": 1.05 + }, + "type": [ + "projectile" + ] + }, + "tornado_sword": { + "icon": "img/icons/spells/tornado_sword.png", + "description": "An impressive circular sword blow, destroying everything around you. Learned from a certain hero in green...", + "cooldown": 1500, + "damage": 0, + "scale": { + "x": 5.0, + "y": 5.0 + }, + "hitbox": { + "x": 5.0, + "y": 5.0 + }, + "lifetime": 1000, + "audio_on_cast": "sounds/spells/sword.wav", + "animation": "animation/spells/tornado_sword/tornado_sword.data.json", + "speed": 0, + "offset_to_source": { + "x": -2, + "y": -2 + }, + "type": [ + "aoe" + ], + "effect": [ + "sliced" + ] + }, + "fireball": { + "icon": "img/icons/spells/fireball.png", + "description": "Burn your enemies with a powerful fireball", + "cooldown": 1500, + "damage": 5, + "scale": { + "x": 2.0, + "y": 2.0 + }, + "hitbox": { + "x": 0.7, + "y": 0.7 + }, + "lifetime": 2000, + "audio_on_cast": "sounds/fire_cast.wav", + "animation": "animation/spells/fireball/fireball.data.json", + "speed": 7.0, + "offset_to_source": { + "x": 2, + "y": 2 + }, + "type": [ + "projectile" + ] + }, + "firewall": { + "icon": "img/icons/spells/firewall.png", + "description": "Imprison your opponents within this fire wall dealing continous damages", + "cooldown": 5000, + "damage": 0, + "scale": { + "x": 6.0, + "y": 6.0 + }, + "hitbox": { + "x": 5.0, + "y": 5.0 + }, + "lifetime": 3000, + "audio_on_cast": "sounds/fire_cast.wav", + "animation": "animation/spells/firewall/firewall.data.json", + "speed": 0, + "offset_to_source": { + "x": 5, + "y": 5 + }, + "type": [ + "aoe" + ], + "effect": [ + "burn" + ] + }, + "rock": { + "icon": "", + "description": "Creates a powerful rock ball dealing extremely high damages", + "cooldown": 2000, + "damage": 10, + "scale": { + "x": 1.0, + "y": 1.0 + }, + "hitbox": { + "x": 1.0, + "y": 1.0 + }, + "lifetime": 5000, + "audio_on_cast": "sounds/fire_cast.wav", + "animation": "animation/spells/rock/rock.data.json", + "speed": 3.0, + "offset_to_source": { + "x": 2, + "y": 2 + }, + "type": [ + "projectile" + ] + }, + "small_rock": { + "icon": "", + "description": "Creates a small rock ball dealing extremely high damages", + "cooldown": 1000, + "damage": 1, + "scale": { + "x": 0.3, + "y": 0.3 + }, + "hitbox": { + "x": 0.3, + "y": 0.3 + }, + "lifetime": 5000, + "audio_on_cast": "sounds/fire_cast.wav", + "animation": "animation/spells/rock/rock.data.json", + "speed": 5.0, + "offset_to_source": { + "x": 0.3, + "y": 0.3 + }, + "type": [ + "projectile" + ] + }, + "fireball_blue": { + "icon": "", + "description": "A fireball for advance mages. Hotter, more powerful, more impressive", + "cooldown": 2000, + "damage": 5, + "scale": { + "x": 2.5, + "y": 2.5 + }, + "hitbox": { + "x": 1.2, + "y": 1.2 + }, + "lifetime": 3000, + "audio_on_cast": "sounds/fire_cast.wav", + "animation": "animation/spells/fireball_blue/fireball_blue.data.json", + "speed": 5.0, + "offset_to_source": { + "x": 2, + "y": 2 + }, + "type": [ + "projectile" + ] + }, + "thunder": { + "icon": "", + "description": "Electrocute the ennemies with a raging thunder", + "cooldown": 2000, + "damage": 5, + "scale": { + "x": 2.5, + "y": 2.5 + }, + "hitbox": { + "x": 1.2, + "y": 1.2 + }, + "lifetime": 900, + "audio_on_cast": "sounds/spells/thunder.wav", + "animation": "animation/spells/thunder/thunder.data.json", + "speed": 0.1, + "offset_to_source": { + "x": 2, + "y": 2 + }, + "type": [ + "projectile" + ] + }, + "arrow": { + "icon": "img/icons/spells/arrow.png", + "description": "Shoots a basic arrow. Don't miss your target", + "cooldown": 1000, + "damage": 1.5, + "scale": { + "x": 1, + "y": 0.2 + }, + "hitbox": { + "x": 0.7, + "y": 0.3 + }, + "lifetime": 1000, + "audio_on_cast": "sounds/spells/shovel.wav", + "animation": "animation/spells/arrow/arrow.data.json", + "speed": 11.0, + "offset_to_source": { + "x": 1.5, + "y": 1.5 + }, + "type": [ + "projectile" + ] + }, + "triple_arrow": { + "icon": "img/icons/spells/triple_arrow.png", + "description": "Experienced archer's favorite move. Shooting three arrows in front of you", + "cooldown": 2000, + "damage": 2, + "scale": { + "x": 1, + "y": 0.2 + }, + "hitbox": { + "x": 0.7, + "y": 0.3 + }, + "lifetime": 1000, + "audio_on_cast": "sounds/spells/shovel.wav", + "animation": "animation/spells/arrow/arrow.data.json", + "speed": 8.0, + "offset_to_source": { + "x": 1.5, + "y": 1.5 + }, + "quantity": 3, + "angle": 0.2, + "type": [ + "projectile" + ] + }, + "after_shock": { + "icon": "img/icons/spells/freeze.png", + "description": "Create a shockwave slowing all the enemies nearby", + "cooldown": 2000, + "damage": 0, + "scale": { + "x": 6, + "y": 6 + }, + "hitbox": { + "x": 5, + "y": 5 + }, + "lifetime": 300, + "audio_on_cast": "sounds/spells/after_shock.wav", + "animation": "animation/spells/after_shock/after_shock.data.json", + "speed": 0, + "offset_to_source": { + "x": -1, + "y": -1 + }, + "type": [ + "aoe" + ], + "effect": [ + "slow" + ] + }, + "freeze": { + "icon": "", + "description": "A cold strike reducing the target's speed", + "cooldown": 2000, + "damage": 0, + "scale": { + "x": 3, + "y": 3 + }, + "hitbox": { + "x": 2.5, + "y": 2.5 + }, + "lifetime": 300, + "audio_on_cast": "sounds/spells/freeze.wav", + "animation": "animation/spells/after_shock/after_shock.data.json", + "speed": 0, + "offset_to_source": { + "x": -1, + "y": -1 + }, + "type": [ + "aoe" + ], + "effect": [ + "slow" + ] + }, + "explosion": { + "icon": "", + "description": "A very powerful and explosive attack. Don't hurt yourself with it", + "cooldown": 4000, + "damage": 0, + "scale": { + "x": 3.1, + "y": 3.1 + }, + "hitbox": { + "x": 3.5, + "y": 3.5 + }, + "lifetime": 460, + "audio_on_cast": "sounds/spells/explosion.wav", + "animation": "animation/spells/explosion/explosion.data.json", + "speed": 0, + "offset_to_source": { + "x": -1, + "y": -1 + }, + "type": [ + "aoe" + ], + "effect": [ + "explosion_effect" + ], + "target": [ + "all" + ] + }, + "shadow_jump": { + "icon": "img/icons/spells/shadow_jump.png", + "description": "Move like a shadow to change position quickly", + "cooldown": 2000, + "damage": 0, + "scale": { + "x": 3, + "y": 3 + }, + "hitbox": { + "x": 0, + "y": 0 + }, + "lifetime": 200, + "audio_on_cast": "sounds/spells/stick.wav", + "animation": "animation/spells/steam/steam.data.json", + "speed": 0, + "offset_to_source": { + "x": -1, + "y": -1 + }, + "type": [ + "aoe" + ], + "effect": [ + "shadow_jump_effect" + ], + "target": [ + "caster" + ] + }, + "throw_barrel": { + "icon": "img/icons/spells/throw_barrel.png", + "description": "Places an explosive barrel waiting to be shot at", + "cooldown": 2500, + "damage": 0, + "scale": { + "x": 1.2, + "y": 1.2 + }, + "hitbox": { + "x": 1, + "y": 1 + }, + "lifetime": 10000, + "audio_on_cast": "sounds/spells/stick.wav", + "animation": "animation/spells/barrel/barrel.data.json", + "speed": 0, + "offset_to_source": { + "x": 3, + "y": 3 + }, + "type": [ + "summoner", + "on_death" + ], + "on_death": "explosion" + } +} diff --git a/data/fonts/OldLondon.ttf b/data/fonts/OldLondon.ttf deleted file mode 100644 index f24a340..0000000 Binary files a/data/fonts/OldLondon.ttf and /dev/null differ diff --git a/data/fonts/OldLondonAlternate.ttf b/data/fonts/OldLondonAlternate.ttf deleted file mode 100644 index 6010ffd..0000000 Binary files a/data/fonts/OldLondonAlternate.ttf and /dev/null differ diff --git a/data/fonts/Olondon_.otf b/data/fonts/Olondon_.otf deleted file mode 100644 index 9a68b20..0000000 Binary files a/data/fonts/Olondon_.otf and /dev/null differ diff --git a/data/fonts/Olondona.otf b/data/fonts/Olondona.otf deleted file mode 100644 index 56161c8..0000000 Binary files a/data/fonts/Olondona.otf and /dev/null differ diff --git a/data/fonts/OpenSans.ttf b/data/fonts/OpenSans.ttf new file mode 100644 index 0000000..db43334 Binary files /dev/null and b/data/fonts/OpenSans.ttf differ diff --git a/data/fonts/kimberley_bl.ttf b/data/fonts/kimberley_bl.ttf new file mode 100644 index 0000000..09a597a Binary files /dev/null and b/data/fonts/kimberley_bl.ttf differ diff --git a/data/img/aim_sight.png b/data/img/aim_sight.png new file mode 100644 index 0000000..35d13c4 Binary files /dev/null and b/data/img/aim_sight.png differ diff --git a/data/textures/background.jpg b/data/img/background.jpg similarity index 100% rename from data/textures/background.jpg rename to data/img/background.jpg diff --git a/data/img/hud/LB.png b/data/img/hud/LB.png new file mode 100644 index 0000000..0a8659f Binary files /dev/null and b/data/img/hud/LB.png differ diff --git a/data/img/hud/LT.png b/data/img/hud/LT.png new file mode 100644 index 0000000..c9be819 Binary files /dev/null and b/data/img/hud/LT.png differ diff --git a/data/img/hud/RB.png b/data/img/hud/RB.png new file mode 100644 index 0000000..6ba4c90 Binary files /dev/null and b/data/img/hud/RB.png differ diff --git a/data/img/hud/RT.png b/data/img/hud/RT.png new file mode 100644 index 0000000..f19e1d2 Binary files /dev/null and b/data/img/hud/RT.png differ diff --git a/data/img/hud/UpgradeIcon.png b/data/img/hud/UpgradeIcon.png new file mode 100644 index 0000000..cb8e68b Binary files /dev/null and b/data/img/hud/UpgradeIcon.png differ diff --git a/data/img/hud/hud_static.png b/data/img/hud/hud_static.png new file mode 100644 index 0000000..95d0ffa Binary files /dev/null and b/data/img/hud/hud_static.png differ diff --git a/data/img/icons/classes/archer.png b/data/img/icons/classes/archer.png new file mode 100644 index 0000000..a7bebe9 Binary files /dev/null and b/data/img/icons/classes/archer.png differ diff --git a/data/img/icons/classes/assassin.png b/data/img/icons/classes/assassin.png new file mode 100644 index 0000000..7b7fa39 Binary files /dev/null and b/data/img/icons/classes/assassin.png differ diff --git a/data/img/icons/classes/farmer.png b/data/img/icons/classes/farmer.png new file mode 100644 index 0000000..bea98df Binary files /dev/null and b/data/img/icons/classes/farmer.png differ diff --git a/data/img/icons/classes/gunner.png b/data/img/icons/classes/gunner.png new file mode 100644 index 0000000..3a01a93 Binary files /dev/null and b/data/img/icons/classes/gunner.png differ diff --git a/data/img/icons/classes/mage.png b/data/img/icons/classes/mage.png new file mode 100644 index 0000000..36e96da Binary files /dev/null and b/data/img/icons/classes/mage.png differ diff --git a/data/img/icons/classes/shooter.png b/data/img/icons/classes/shooter.png new file mode 100644 index 0000000..9f8933a Binary files /dev/null and b/data/img/icons/classes/shooter.png differ diff --git a/data/img/icons/classes/soldier.png b/data/img/icons/classes/soldier.png new file mode 100644 index 0000000..ed73105 Binary files /dev/null and b/data/img/icons/classes/soldier.png differ diff --git a/data/img/icons/classes/sorcerer.png b/data/img/icons/classes/sorcerer.png new file mode 100644 index 0000000..e61b79c Binary files /dev/null and b/data/img/icons/classes/sorcerer.png differ diff --git a/data/img/icons/classes/tank.png b/data/img/icons/classes/tank.png new file mode 100644 index 0000000..ae8807c Binary files /dev/null and b/data/img/icons/classes/tank.png differ diff --git a/data/img/icons/classes/warrior.png b/data/img/icons/classes/warrior.png new file mode 100644 index 0000000..b7b2a37 Binary files /dev/null and b/data/img/icons/classes/warrior.png differ diff --git a/data/img/icons/spells/arrow.png b/data/img/icons/spells/arrow.png new file mode 100644 index 0000000..af95b35 Binary files /dev/null and b/data/img/icons/spells/arrow.png differ diff --git a/data/img/icons/spells/fireball.png b/data/img/icons/spells/fireball.png new file mode 100644 index 0000000..60a8c61 Binary files /dev/null and b/data/img/icons/spells/fireball.png differ diff --git a/data/img/icons/spells/firewall.png b/data/img/icons/spells/firewall.png new file mode 100644 index 0000000..394321d Binary files /dev/null and b/data/img/icons/spells/firewall.png differ diff --git a/data/img/icons/spells/freeze.png b/data/img/icons/spells/freeze.png new file mode 100644 index 0000000..5f91271 Binary files /dev/null and b/data/img/icons/spells/freeze.png differ diff --git a/data/img/icons/spells/shadow_jump.png b/data/img/icons/spells/shadow_jump.png new file mode 100644 index 0000000..302a081 Binary files /dev/null and b/data/img/icons/spells/shadow_jump.png differ diff --git a/data/img/icons/spells/shovel.png b/data/img/icons/spells/shovel.png new file mode 100644 index 0000000..1b96933 Binary files /dev/null and b/data/img/icons/spells/shovel.png differ diff --git a/data/img/icons/spells/sword.png b/data/img/icons/spells/sword.png new file mode 100644 index 0000000..9639b2b Binary files /dev/null and b/data/img/icons/spells/sword.png differ diff --git a/data/img/icons/spells/throw_barrel.png b/data/img/icons/spells/throw_barrel.png new file mode 100644 index 0000000..4032179 Binary files /dev/null and b/data/img/icons/spells/throw_barrel.png differ diff --git a/data/img/icons/spells/tornado_sword.png b/data/img/icons/spells/tornado_sword.png new file mode 100644 index 0000000..bc1ef22 Binary files /dev/null and b/data/img/icons/spells/tornado_sword.png differ diff --git a/data/img/icons/spells/triple_arrow.png b/data/img/icons/spells/triple_arrow.png new file mode 100644 index 0000000..3f763ab Binary files /dev/null and b/data/img/icons/spells/triple_arrow.png differ diff --git a/data/textures/key.png b/data/img/key.png similarity index 100% rename from data/textures/key.png rename to data/img/key.png diff --git a/data/textures/door.png b/data/img/map/door.png similarity index 100% rename from data/textures/door.png rename to data/img/map/door.png diff --git a/data/textures/corridor.jpg b/data/img/map/old/corridor.jpg similarity index 100% rename from data/textures/corridor.jpg rename to data/img/map/old/corridor.jpg diff --git a/data/textures/floor.jpg b/data/img/map/old/floor.jpg similarity index 100% rename from data/textures/floor.jpg rename to data/img/map/old/floor.jpg diff --git a/data/textures/floor_boss.jpg b/data/img/map/old/floor_boss.jpg similarity index 100% rename from data/textures/floor_boss.jpg rename to data/img/map/old/floor_boss.jpg diff --git a/data/img/map/old/lava.png b/data/img/map/old/lava.png new file mode 100644 index 0000000..c457e6a Binary files /dev/null and b/data/img/map/old/lava.png differ diff --git a/data/textures/spawn.png b/data/img/map/old/spawn.png similarity index 100% rename from data/textures/spawn.png rename to data/img/map/old/spawn.png diff --git a/data/textures/wall.jpg b/data/img/map/old/wall.jpg similarity index 100% rename from data/textures/wall.jpg rename to data/img/map/old/wall.jpg diff --git a/data/img/map/stone2_b.jpg b/data/img/map/stone2_b.jpg new file mode 100644 index 0000000..9e5f682 Binary files /dev/null and b/data/img/map/stone2_b.jpg differ diff --git a/data/img/map/stone3_b.jpg b/data/img/map/stone3_b.jpg new file mode 100644 index 0000000..3382ad2 Binary files /dev/null and b/data/img/map/stone3_b.jpg differ diff --git a/data/img/map/stone4_b.jpg b/data/img/map/stone4_b.jpg new file mode 100644 index 0000000..6fcb49f Binary files /dev/null and b/data/img/map/stone4_b.jpg differ diff --git a/data/img/map/stone5_b.jpg b/data/img/map/stone5_b.jpg new file mode 100644 index 0000000..f62eed4 Binary files /dev/null and b/data/img/map/stone5_b.jpg differ diff --git a/data/img/menu/_gimp_projects_saves/README.txt b/data/img/menu/_gimp_projects_saves/README.txt new file mode 100644 index 0000000..6399638 --- /dev/null +++ b/data/img/menu/_gimp_projects_saves/README.txt @@ -0,0 +1,2 @@ +These are all the useful gimp project files that were used to generate the menu images. +Made with gimp 2.10 \ No newline at end of file diff --git a/data/img/menu/_gimp_projects_saves/credits.xcf b/data/img/menu/_gimp_projects_saves/credits.xcf new file mode 100644 index 0000000..96a0206 Binary files /dev/null and b/data/img/menu/_gimp_projects_saves/credits.xcf differ diff --git a/data/img/menu/_gimp_projects_saves/howtoplay.xcf b/data/img/menu/_gimp_projects_saves/howtoplay.xcf new file mode 100644 index 0000000..43c37ad Binary files /dev/null and b/data/img/menu/_gimp_projects_saves/howtoplay.xcf differ diff --git a/data/img/menu/_gimp_projects_saves/mainmenu.xcf b/data/img/menu/_gimp_projects_saves/mainmenu.xcf new file mode 100644 index 0000000..745cf22 Binary files /dev/null and b/data/img/menu/_gimp_projects_saves/mainmenu.xcf differ diff --git a/data/img/menu/credits.png b/data/img/menu/credits.png new file mode 100644 index 0000000..bd3a704 Binary files /dev/null and b/data/img/menu/credits.png differ diff --git a/data/img/menu/game_over/background.png b/data/img/menu/game_over/background.png new file mode 100644 index 0000000..60c58f9 Binary files /dev/null and b/data/img/menu/game_over/background.png differ diff --git a/data/img/menu/game_over/btn_menu_selected.png b/data/img/menu/game_over/btn_menu_selected.png new file mode 100644 index 0000000..40a1eb1 Binary files /dev/null and b/data/img/menu/game_over/btn_menu_selected.png differ diff --git a/data/img/menu/game_over/btn_playagain_selected.png b/data/img/menu/game_over/btn_playagain_selected.png new file mode 100644 index 0000000..fce43d0 Binary files /dev/null and b/data/img/menu/game_over/btn_playagain_selected.png differ diff --git a/data/img/menu/how_to_play/controls.png b/data/img/menu/how_to_play/controls.png new file mode 100644 index 0000000..f77b3f5 Binary files /dev/null and b/data/img/menu/how_to_play/controls.png differ diff --git a/data/img/menu/how_to_play/howtoplay.png b/data/img/menu/how_to_play/howtoplay.png new file mode 100644 index 0000000..2ff2aec Binary files /dev/null and b/data/img/menu/how_to_play/howtoplay.png differ diff --git a/data/img/menu/main/btn_credits_selected.png b/data/img/menu/main/btn_credits_selected.png new file mode 100644 index 0000000..c2183d2 Binary files /dev/null and b/data/img/menu/main/btn_credits_selected.png differ diff --git a/data/img/menu/main/btn_exit_selected.png b/data/img/menu/main/btn_exit_selected.png new file mode 100644 index 0000000..8dbaa04 Binary files /dev/null and b/data/img/menu/main/btn_exit_selected.png differ diff --git a/data/img/menu/main/btn_howtoplay_selected.png b/data/img/menu/main/btn_howtoplay_selected.png new file mode 100644 index 0000000..db614c5 Binary files /dev/null and b/data/img/menu/main/btn_howtoplay_selected.png differ diff --git a/data/img/menu/main/btn_play_selected.png b/data/img/menu/main/btn_play_selected.png new file mode 100644 index 0000000..abdc505 Binary files /dev/null and b/data/img/menu/main/btn_play_selected.png differ diff --git a/data/img/menu/main/mainmenu.png b/data/img/menu/main/mainmenu.png new file mode 100644 index 0000000..d689591 Binary files /dev/null and b/data/img/menu/main/mainmenu.png differ diff --git a/data/img/menu/upgrade_panel/button/buy.png b/data/img/menu/upgrade_panel/button/buy.png new file mode 100644 index 0000000..053372c Binary files /dev/null and b/data/img/menu/upgrade_panel/button/buy.png differ diff --git a/data/img/menu/upgrade_panel/button/cant.png b/data/img/menu/upgrade_panel/button/cant.png new file mode 100644 index 0000000..91da4b5 Binary files /dev/null and b/data/img/menu/upgrade_panel/button/cant.png differ diff --git a/data/img/menu/upgrade_panel/button/owned.png b/data/img/menu/upgrade_panel/button/owned.png new file mode 100644 index 0000000..8951110 Binary files /dev/null and b/data/img/menu/upgrade_panel/button/owned.png differ diff --git a/data/img/menu/upgrade_panel/key_assignment_popup.png b/data/img/menu/upgrade_panel/key_assignment_popup.png new file mode 100644 index 0000000..2cd2776 Binary files /dev/null and b/data/img/menu/upgrade_panel/key_assignment_popup.png differ diff --git a/data/img/menu/upgrade_panel/static_background.png b/data/img/menu/upgrade_panel/static_background.png new file mode 100644 index 0000000..970d7af Binary files /dev/null and b/data/img/menu/upgrade_panel/static_background.png differ diff --git a/data/img/menu/upgrade_panel/tree/cursor.png b/data/img/menu/upgrade_panel/tree/cursor.png new file mode 100644 index 0000000..24c8f27 Binary files /dev/null and b/data/img/menu/upgrade_panel/tree/cursor.png differ diff --git a/data/img/menu/upgrade_panel/tree/frames/buyable.png b/data/img/menu/upgrade_panel/tree/frames/buyable.png new file mode 100644 index 0000000..543ad51 Binary files /dev/null and b/data/img/menu/upgrade_panel/tree/frames/buyable.png differ diff --git a/data/img/menu/upgrade_panel/tree/frames/owned.png b/data/img/menu/upgrade_panel/tree/frames/owned.png new file mode 100644 index 0000000..9170441 Binary files /dev/null and b/data/img/menu/upgrade_panel/tree/frames/owned.png differ diff --git a/data/img/menu/upgrade_panel/tree/frames/unavailable.png b/data/img/menu/upgrade_panel/tree/frames/unavailable.png new file mode 100644 index 0000000..c7caff0 Binary files /dev/null and b/data/img/menu/upgrade_panel/tree/frames/unavailable.png differ diff --git a/data/img/transparent.png b/data/img/transparent.png new file mode 100644 index 0000000..a30fb80 Binary files /dev/null and b/data/img/transparent.png differ diff --git a/data/shaders/colored.frag.glsl b/data/shaders/colored.frag.glsl index 305012f..ea4eb64 100644 --- a/data/shaders/colored.frag.glsl +++ b/data/shaders/colored.frag.glsl @@ -1,10 +1,10 @@ -#version 330 core +#version 450 core -in vec3 OutColor; +in vec4 OutColor; out vec4 FragColor; void main() { - FragColor = vec4(OutColor, 1.0f); + FragColor = OutColor; } diff --git a/data/shaders/colored.vert.glsl b/data/shaders/colored.vert.glsl index 1e3085c..02e8777 100644 --- a/data/shaders/colored.vert.glsl +++ b/data/shaders/colored.vert.glsl @@ -1,9 +1,9 @@ -#version 330 core +#version 450 core layout (location = 0) in vec3 aPos; -layout (location = 1) in vec3 aColor; +layout (location = 1) in vec4 aColor; -out vec3 OutColor; +out vec4 OutColor; uniform mat4 model; uniform mat4 viewProj; diff --git a/data/shaders/colored_textured.frag.glsl b/data/shaders/colored_textured.frag.glsl index 5fc57d1..386ed1b 100644 --- a/data/shaders/colored_textured.frag.glsl +++ b/data/shaders/colored_textured.frag.glsl @@ -1,6 +1,6 @@ -#version 330 core +#version 450 core -in vec3 OutColor; +in vec4 OutColor; in vec2 TexCoord; out vec4 FragColor; @@ -9,8 +9,8 @@ uniform sampler2D ourTexture; void main() { - vec4 color = texture(ourTexture, TexCoord) * vec4(OutColor, 1.0); - if (color.a < 0.1) + vec4 color = texture(ourTexture, TexCoord) * OutColor; + if (color.a < 0.05) discard; FragColor = color; } diff --git a/data/shaders/colored_textured.vert.glsl b/data/shaders/colored_textured.vert.glsl index f09ae61..f82e22a 100644 --- a/data/shaders/colored_textured.vert.glsl +++ b/data/shaders/colored_textured.vert.glsl @@ -1,10 +1,10 @@ -#version 330 core +#version 450 core layout(location = 0) in vec3 aPos; -layout(location = 1) in vec3 aColor; +layout(location = 1) in vec4 aColor; layout(location = 2) in vec2 aTexCoord; -out vec3 OutColor; +out vec4 OutColor; out vec2 TexCoord; uniform mat4 model; diff --git a/data/sounds/boss_death.wav b/data/sounds/death/boss_death.wav similarity index 100% rename from data/sounds/boss_death.wav rename to data/sounds/death/boss_death.wav diff --git a/data/sounds/death_01.wav b/data/sounds/death/death_01.wav similarity index 100% rename from data/sounds/death_01.wav rename to data/sounds/death/death_01.wav diff --git a/data/sounds/death_02.wav b/data/sounds/death/death_02.wav similarity index 100% rename from data/sounds/death_02.wav rename to data/sounds/death/death_02.wav diff --git a/data/sounds/hit.wav b/data/sounds/hit.wav deleted file mode 100644 index 98e880c..0000000 Binary files a/data/sounds/hit.wav and /dev/null differ diff --git a/data/sounds/key_pickup.wav b/data/sounds/key_pickup.wav new file mode 100644 index 0000000..6ca3e4e Binary files /dev/null and b/data/sounds/key_pickup.wav differ diff --git a/data/sounds/level_up.wav b/data/sounds/level_up.wav new file mode 100644 index 0000000..3281e09 Binary files /dev/null and b/data/sounds/level_up.wav differ diff --git a/data/sounds/menu/accept.wav b/data/sounds/menu/accept.wav new file mode 100644 index 0000000..e070b2d Binary files /dev/null and b/data/sounds/menu/accept.wav differ diff --git a/data/sounds/menu/back.wav b/data/sounds/menu/back.wav new file mode 100644 index 0000000..d80eaf0 Binary files /dev/null and b/data/sounds/menu/back.wav differ diff --git a/data/sounds/menu/background_music.wav b/data/sounds/menu/background_music.wav new file mode 100644 index 0000000..9cdb170 Binary files /dev/null and b/data/sounds/menu/background_music.wav differ diff --git a/data/sounds/menu/change.wav b/data/sounds/menu/change.wav new file mode 100644 index 0000000..e41b5a9 Binary files /dev/null and b/data/sounds/menu/change.wav differ diff --git a/data/sounds/menu/upgrade_panel/buy_class.wav b/data/sounds/menu/upgrade_panel/buy_class.wav new file mode 100644 index 0000000..07b59ed Binary files /dev/null and b/data/sounds/menu/upgrade_panel/buy_class.wav differ diff --git a/data/sounds/menu/upgrade_panel/error.wav b/data/sounds/menu/upgrade_panel/error.wav new file mode 100644 index 0000000..1615160 Binary files /dev/null and b/data/sounds/menu/upgrade_panel/error.wav differ diff --git a/data/sounds/menu/upgrade_panel/heal.wav b/data/sounds/menu/upgrade_panel/heal.wav new file mode 100644 index 0000000..7fe6819 Binary files /dev/null and b/data/sounds/menu/upgrade_panel/heal.wav differ diff --git a/data/sounds/menu/upgrade_panel/open_close.wav b/data/sounds/menu/upgrade_panel/open_close.wav new file mode 100644 index 0000000..a79d3b6 Binary files /dev/null and b/data/sounds/menu/upgrade_panel/open_close.wav differ diff --git a/data/sounds/menu/upgrade_panel/spell_assign.wav b/data/sounds/menu/upgrade_panel/spell_assign.wav new file mode 100644 index 0000000..93f3868 Binary files /dev/null and b/data/sounds/menu/upgrade_panel/spell_assign.wav differ diff --git a/data/sounds/menu/upgrade_panel/tree_select.wav b/data/sounds/menu/upgrade_panel/tree_select.wav new file mode 100644 index 0000000..166331f Binary files /dev/null and b/data/sounds/menu/upgrade_panel/tree_select.wav differ diff --git a/data/sounds/spells/after_shock.wav b/data/sounds/spells/after_shock.wav new file mode 100644 index 0000000..7e7bac0 Binary files /dev/null and b/data/sounds/spells/after_shock.wav differ diff --git a/data/sounds/spells/explosion.wav b/data/sounds/spells/explosion.wav new file mode 100644 index 0000000..5051760 Binary files /dev/null and b/data/sounds/spells/explosion.wav differ diff --git a/data/sounds/spells/freeze.wav b/data/sounds/spells/freeze.wav new file mode 100644 index 0000000..13f4a7d Binary files /dev/null and b/data/sounds/spells/freeze.wav differ diff --git a/data/sounds/spells/pan.wav b/data/sounds/spells/pan.wav new file mode 100644 index 0000000..87b7803 Binary files /dev/null and b/data/sounds/spells/pan.wav differ diff --git a/data/sounds/spells/shovel.wav b/data/sounds/spells/shovel.wav new file mode 100644 index 0000000..f201871 Binary files /dev/null and b/data/sounds/spells/shovel.wav differ diff --git a/data/sounds/spells/sword.wav b/data/sounds/spells/sword.wav new file mode 100644 index 0000000..b1694ff Binary files /dev/null and b/data/sounds/spells/sword.wav differ diff --git a/data/sounds/spells/thunder.wav b/data/sounds/spells/thunder.wav new file mode 100644 index 0000000..9dafbcd Binary files /dev/null and b/data/sounds/spells/thunder.wav differ diff --git a/data/textures/CPoint.PNG b/data/textures/CPoint.PNG deleted file mode 100644 index e5686ed..0000000 Binary files a/data/textures/CPoint.PNG and /dev/null differ diff --git a/data/textures/InfoHud.jpg b/data/textures/InfoHud.jpg deleted file mode 100644 index cf34997..0000000 Binary files a/data/textures/InfoHud.jpg and /dev/null differ diff --git a/data/textures/InfoHud.png b/data/textures/InfoHud.png deleted file mode 100644 index 05b0ad9..0000000 Binary files a/data/textures/InfoHud.png and /dev/null differ diff --git a/data/textures/Temporary/InfoPlayer.png b/data/textures/Temporary/InfoPlayer.png deleted file mode 100644 index ee22096..0000000 Binary files a/data/textures/Temporary/InfoPlayer.png and /dev/null differ diff --git a/data/textures/Temporary/archer_des.PNG b/data/textures/Temporary/archer_des.PNG deleted file mode 100644 index 830fdcd..0000000 Binary files a/data/textures/Temporary/archer_des.PNG and /dev/null differ diff --git a/data/textures/Temporary/farmer_des.PNG b/data/textures/Temporary/farmer_des.PNG deleted file mode 100644 index 35185cc..0000000 Binary files a/data/textures/Temporary/farmer_des.PNG and /dev/null differ diff --git a/data/textures/Temporary/gunner_des.PNG b/data/textures/Temporary/gunner_des.PNG deleted file mode 100644 index a691d25..0000000 Binary files a/data/textures/Temporary/gunner_des.PNG and /dev/null differ diff --git a/data/textures/Temporary/healer_des.PNG b/data/textures/Temporary/healer_des.PNG deleted file mode 100644 index cb2decf..0000000 Binary files a/data/textures/Temporary/healer_des.PNG and /dev/null differ diff --git a/data/textures/Temporary/mage_des.PNG b/data/textures/Temporary/mage_des.PNG deleted file mode 100644 index 2cd5412..0000000 Binary files a/data/textures/Temporary/mage_des.PNG and /dev/null differ diff --git a/data/textures/Temporary/plus.png b/data/textures/Temporary/plus.png deleted file mode 100644 index ed8cc2e..0000000 Binary files a/data/textures/Temporary/plus.png and /dev/null differ diff --git a/data/textures/Temporary/shooter_des.PNG b/data/textures/Temporary/shooter_des.PNG deleted file mode 100644 index 0ecb0ef..0000000 Binary files a/data/textures/Temporary/shooter_des.PNG and /dev/null differ diff --git a/data/textures/Temporary/soldier_des.PNG b/data/textures/Temporary/soldier_des.PNG deleted file mode 100644 index b9ab2af..0000000 Binary files a/data/textures/Temporary/soldier_des.PNG and /dev/null differ diff --git a/data/textures/Temporary/sorcerer_des.PNG b/data/textures/Temporary/sorcerer_des.PNG deleted file mode 100644 index d544752..0000000 Binary files a/data/textures/Temporary/sorcerer_des.PNG and /dev/null differ diff --git a/data/textures/Temporary/tank_des.PNG b/data/textures/Temporary/tank_des.PNG deleted file mode 100644 index a8c515a..0000000 Binary files a/data/textures/Temporary/tank_des.PNG and /dev/null differ diff --git a/data/textures/Temporary/test.jpg b/data/textures/Temporary/test.jpg deleted file mode 100644 index ac4bcd2..0000000 Binary files a/data/textures/Temporary/test.jpg and /dev/null differ diff --git a/data/textures/Temporary/test.png b/data/textures/Temporary/test.png deleted file mode 100644 index 5cd3877..0000000 Binary files a/data/textures/Temporary/test.png and /dev/null differ diff --git a/data/textures/Temporary/warrior_des.PNG b/data/textures/Temporary/warrior_des.PNG deleted file mode 100644 index 64bc3f5..0000000 Binary files a/data/textures/Temporary/warrior_des.PNG and /dev/null differ diff --git a/data/textures/controller/LB.png b/data/textures/controller/LB.png deleted file mode 100644 index ca75bbb..0000000 Binary files a/data/textures/controller/LB.png and /dev/null differ diff --git a/data/textures/controller/LT.png b/data/textures/controller/LT.png deleted file mode 100644 index c9a2a43..0000000 Binary files a/data/textures/controller/LT.png and /dev/null differ diff --git a/data/textures/controller/RB.png b/data/textures/controller/RB.png deleted file mode 100644 index 4d915cd..0000000 Binary files a/data/textures/controller/RB.png and /dev/null differ diff --git a/data/textures/controller/RT.png b/data/textures/controller/RT.png deleted file mode 100644 index d975f10..0000000 Binary files a/data/textures/controller/RT.png and /dev/null differ diff --git a/data/textures/enemy.png b/data/textures/enemy.png deleted file mode 100644 index b625084..0000000 Binary files a/data/textures/enemy.png and /dev/null differ diff --git a/data/textures/player.jpeg b/data/textures/player.jpeg deleted file mode 100644 index e1420aa..0000000 Binary files a/data/textures/player.jpeg and /dev/null differ diff --git a/data/textures/validate.png b/data/textures/validate.png deleted file mode 100644 index bbe1254..0000000 Binary files a/data/textures/validate.png and /dev/null differ diff --git a/doc/GDD_one_page.pdf b/doc/GDD_one_page.pdf deleted file mode 100644 index c62047f..0000000 Binary files a/doc/GDD_one_page.pdf and /dev/null differ diff --git a/doc/GDD_ten_page.pdf b/doc/ThePURGE_GameDesignDocument.pdf similarity index 100% rename from doc/GDD_ten_page.pdf rename to doc/ThePURGE_GameDesignDocument.pdf diff --git a/doc/history/README.md b/doc/history/README.md new file mode 100644 index 0000000..a2742ef --- /dev/null +++ b/doc/history/README.md @@ -0,0 +1,9 @@ +# Old version of the Application + +## Version 0.2.12 + +![v0.2.12](./app_v0.2.12.gif) + +## Version 0.1.9 + +![v0.1.9](./app_v0.1.9.png) diff --git a/doc/screenshots/app_v0.1.9.png b/doc/history/app_v0.1.9.png similarity index 100% rename from doc/screenshots/app_v0.1.9.png rename to doc/history/app_v0.1.9.png diff --git a/doc/screenshots/app_v0.2.12.gif b/doc/history/app_v0.2.12.gif similarity index 100% rename from doc/screenshots/app_v0.2.12.gif rename to doc/history/app_v0.2.12.gif diff --git a/doc/planned_delivery.txt b/doc/planned_delivery.txt deleted file mode 100644 index 98de4af..0000000 --- a/doc/planned_delivery.txt +++ /dev/null @@ -1,67 +0,0 @@ -[Alpha] - Gameplay : - Input joueur - -> controller - - Classes : - Farmer - -> system de stats - +3 classes de bases - -> interface de classes - -> system de changement de classes - -> system de competence - - Level : - Changement de terrain / system de clé - -> system de scene - -> system de game event - Generation - -> salles - -> couloir - -> escalier - -> zone de spawn (joueur & enemies) - - Enemies : - 1 types d Enemies - -> system de vision / agro - -> system de pathfinding - -> system d xp - 1 boss (drop a key) - - Interface : - hp joueur - sort - -> cooldown - -> icones - points de competence dispo - xp - menu level up / menu competence - audio - -[Beta] - -[Final Delivery] - Gameplay : - Multi-joueur - -> pre-game menu - -> menu (leave / options) (in-game) - -> Camera zoom - -> resurection - - Classes : - Implementer toutes les classes - -> tous les sorts - -> animations / particules - - Level : - Textures - (brouillard de guerre) - - Enemies : - all the enemies - - Interface : - Score / HighScore - Transition / animations - Stats (temps / kills ...) - menu (all clean) diff --git a/doc/state_of_art_idea.txt b/doc/state_of_art_idea.txt deleted file mode 100644 index a72155b..0000000 --- a/doc/state_of_art_idea.txt +++ /dev/null @@ -1,14 +0,0 @@ -Languages: - ? ChaiScript ? Lua ? - -Libaries: - Utility: - physics: ? units ? (https://github.com/mpusz/units) - -Static Analyzer: - include-what-you-use (ubuntu/debian only) - -Tests: - -Documentation: - Doxygen diff --git a/src/Application/CMakeLists.txt b/src/Application/CMakeLists.txt index fa3be9f..dbf82d6 100644 --- a/src/Application/CMakeLists.txt +++ b/src/Application/CMakeLists.txt @@ -2,16 +2,29 @@ add_library( ThePURGE STATIC # MODULE src/ThePURGE.cpp src/GameLogic.cpp - src/level/LevelTilemapBuilder.cpp - src/level/MapGenerator.cpp + src/GameLogic/collision.cpp + src/GameLogic/fight.cpp + src/stage/LevelTilemapBuilder.cpp + src/models/Stage.cpp + src/models/Spell.cpp + src/models/Class.cpp + src/models/Enemy.cpp + src/models/Effect.cpp + src/models/EndGameStats.cpp src/factory/EntityFactory.cpp src/factory/SpellFactory.cpp src/factory/ParticuleFactory.cpp - src/DataConfigLoader.cpp - src/widgets/console/DebugConsole.cpp - src/widgets/console/ConsoleCommands.cpp - src/models/SpellDatabase.cpp - src/models/ClassDatabase.cpp) + src/widgets/debug/console/DebugConsole.cpp + src/widgets/debug/console/ConsoleCommands.cpp + src/widgets/GameHUD.cpp + src/menu/AMenu.cpp + src/menu/MainMenu.cpp + src/menu/UpgradePanel.cpp + src/menu/GameOver.cpp + src/menu/Credits.cpp + src/menu/HowToPlay.cpp + src/widgets/helpers.cpp + src/widgets/Fonts.cpp) target_link_libraries(ThePURGE PRIVATE engine_core) # note : should be engine_api or something .. diff --git a/src/Application/include/DataConfigLoader.hpp b/src/Application/include/DataConfigLoader.hpp deleted file mode 100644 index 320738d..0000000 --- a/src/Application/include/DataConfigLoader.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#include -#include -#include - -#include - -#include "models/SpellDatabase.hpp" -#include "models/ClassDatabase.hpp" - -namespace game { - -struct /* [[deprecated]] */ DataConfigLoader { - static auto loadClassDatabase(const std::string_view path) -> classes::Database; -}; - -} // namespace game diff --git a/src/Application/include/GameLogic.hpp b/src/Application/include/GameLogic.hpp index 40ccf6e..a1ef548 100644 --- a/src/Application/include/GameLogic.hpp +++ b/src/Application/include/GameLogic.hpp @@ -9,20 +9,16 @@ #include -#include "component/all.hpp" -#include "level/MapGenerator.hpp" -#include "models/Class.hpp" +#include "models/Stage.hpp" namespace game { class ThePURGE; -enum class Direction { - UP, - LEFT, - DOWN, - RIGHT -}; +struct Class; +struct Spell; + +enum class Direction { UP, LEFT, DOWN, RIGHT }; class GameLogic { public: @@ -33,77 +29,86 @@ class GameLogic { // todo : should be a signals slots too ! auto addXp(entt::registry &, entt::entity player, std::uint32_t xp) -> void; - FloorGenParam m_map_generation_params; // note : should be private + Stage::Parameters m_map_generation_params; // note : should be private private: - std::uint32_t m_nextFloorSeed; - - static constexpr double kDoorInteractionRange = 3; + double m_gameTime; // seconds public: // signals - // note : should have only one signal : onUserMove, or something like that - entt::sigh joystickMovement; - entt::sigh movement; + entt::sigh onMovement; - entt::sigh onGameStarted; + entt::sigh onGameStart; - entt::sigh onPlayerBuyClass; + entt::sigh onPlayerPurchase; entt::sigh onPlayerLevelUp; - entt::sigh gameUpdated; - entt::sigh afterGameUpdated; + entt::sigh onEvent; + + entt::sigh onGameUpdate; + entt::sigh onGameUpdateAfter; + + entt::sigh onSpellCast; + entt::sigh onCollideWithSpell; - entt::sigh castSpell; + entt::sigh onDamageTaken; entt::sigh onEntityKilled; entt::sigh onFloorChange; private: // slots + decltype(onMovement)::sink_type sinkMovement{onMovement}; + auto slots_move(entt::registry &, entt::entity &, const Direction &, bool is_pressed) -> void; - decltype(movement)::sink_type sinkMovement{movement}; - auto move(entt::registry &, entt::entity &, const Direction &, bool is_pressed) -> void; + decltype(onGameStart)::sink_type sinkOnGameStarted{onGameStart}; + auto slots_game_start(entt::registry &) -> void; - decltype(joystickMovement)::sink_type sinkJoystickMovement{joystickMovement}; - auto joystickMove(entt::registry &world, entt::entity &player, const engine::d2::Acceleration &accel) -> void; + decltype(onPlayerPurchase)::sink_type sinkOnPlayerBuyClass{onPlayerPurchase}; + auto slots_apply_classes(entt::registry &, entt::entity, const Class &) -> void; - decltype(onGameStarted)::sink_type sinkOnGameStarted{onGameStarted}; - auto on_game_started(entt::registry &) -> void; + decltype(onPlayerLevelUp)::sink_type sinkOnPlayerLevelUp{onPlayerLevelUp}; + auto slots_level_up(entt::registry &, entt::entity) -> void; - decltype(onPlayerBuyClass)::sink_type sinkOnPlayerBuyClass{onPlayerBuyClass}; - auto apply_class_to_player(entt::registry &, entt::entity, const Class &) -> void; - auto on_class_bought(entt::registry &, entt::entity, const Class &) -> void; + decltype(onEvent)::sink_type sinkOnEvent{onEvent}; + auto slots_on_event(entt::registry &, const engine::Event &) -> void; - decltype(onPlayerLevelUp)::sink_type sinkOnPlayerLevelUp{onPlayerLevelUp}; - auto on_player_level_up(entt::registry &, entt::entity) -> void; - - decltype(gameUpdated)::sink_type sinkGameUpdated{gameUpdated}; - auto player_movement_update(entt::registry &, const engine::TimeElapsed &) -> void; - auto ai_pursue(entt::registry &, const engine::TimeElapsed &) -> void; - auto enemies_try_attack(entt::registry &, const engine::TimeElapsed &) -> void; - auto update_lifetime(entt::registry &, const engine::TimeElapsed &) -> void; - auto update_particule(entt::registry &, const engine::TimeElapsed &) -> void; - auto cooldown(entt::registry &, const engine::TimeElapsed &) -> void; - auto check_collision(entt::registry &, const engine::TimeElapsed &) -> void; - auto effect(entt::registry &, const engine::TimeElapsed &) -> void; - auto exit_door_interraction(entt::registry &, const engine::TimeElapsed &) -> void; - auto boss_anim_update(entt::registry &, const engine::TimeElapsed &) -> void; - auto player_anim_update(entt::registry &, const engine::TimeElapsed &) -> void; - - decltype(afterGameUpdated)::sink_type sinkAfterGameUpdated{afterGameUpdated}; - auto update_camera(entt::registry &, const engine::TimeElapsed &) -> void; - - decltype(castSpell)::sink_type sinkCastSpell{castSpell}; - auto cast_attack(entt::registry &, entt::entity, const glm::dvec2 &, Spell &) -> void; + decltype(onGameUpdate)::sink_type sinkGameUpdated{onGameUpdate}; // todo : cleaner + auto slots_update_game_time(entt::registry &, const engine::TimeElapsed &) -> void; + auto slots_check_animation_attack_status(entt::registry &, const engine::TimeElapsed &) -> void; + auto slots_update_player_movement(entt::registry &, const engine::TimeElapsed &) -> void; + auto slots_update_ai_movement(entt::registry &, const engine::TimeElapsed &) -> void; + auto slots_update_ai_attack(entt::registry &, const engine::TimeElapsed &) -> void; + auto slots_update_particle(entt::registry &, const engine::TimeElapsed &) -> void; + // note : should be in Core + auto slots_update_cooldown(entt::registry &, const engine::TimeElapsed &) -> void; + auto slots_check_collision(entt::registry &, const engine::TimeElapsed &) -> void; + auto slots_check_floor_change(entt::registry &, const engine::TimeElapsed &) -> void; + auto slots_update_effect(entt::registry &, const engine::TimeElapsed &) -> void; + + // todo : should not be a slots connected to onGameUpdate but a callback connected to replace + auto slots_update_animation_spritesheet(entt::registry &, const engine::TimeElapsed &) -> void; + + decltype(onGameUpdateAfter)::sink_type sinkAfterGameUpdated{onGameUpdateAfter}; // todo : cleaner + auto slots_update_camera(entt::registry &, const engine::TimeElapsed &) -> void; + auto slots_update_player_sigh(entt::registry &, const engine::TimeElapsed &) -> void; + + decltype(onSpellCast)::sink_type sinkCastSpell{onSpellCast}; + auto slots_cast_spell(entt::registry &, entt::entity, const glm::dvec2 &, Spell &) -> void; + + decltype(onCollideWithSpell)::sink_type sinkCollideSpell{onCollideWithSpell}; + auto slots_collide_with_spell(entt::registry &, entt::entity receiver, entt::entity sender, entt::entity spell) -> void; + + decltype(onDamageTaken)::sink_type sinkDamageTaken{onDamageTaken}; + auto slots_damage_taken(entt::registry &, entt::entity receiver, entt::entity sender, entt::entity spell) -> void; decltype(onEntityKilled)::sink_type sinkGetKilled{onEntityKilled}; - auto entity_killed(entt::registry &, entt::entity killed, entt::entity killer) -> void; + auto slots_kill_entity(entt::registry &, entt::entity killed, entt::entity killer) -> void; decltype(onFloorChange)::sink_type sinkOnFloorChange{onFloorChange}; - auto goToTheNextFloor(entt::registry &) -> void; + auto slots_change_floor(entt::registry &) -> void; }; } // namespace game diff --git a/src/Application/include/ThePURGE.hpp b/src/Application/include/ThePURGE.hpp index fdcf1fe..f34f410 100644 --- a/src/Application/include/ThePURGE.hpp +++ b/src/Application/include/ThePURGE.hpp @@ -8,28 +8,29 @@ #include #include -#include "component/all.hpp" +#include "menu/AMenu.hpp" -#include "widgets/console/DebugConsole.hpp" - -#include "level/MapGenerator.hpp" #include "GameLogic.hpp" -#include "models/ClassDatabase.hpp" +#include "models/Class.hpp" +#include "models/Spell.hpp" +#include "models/Enemy.hpp" +#include "models/Effect.hpp" + +#ifndef NDEBUG +# include "widgets/debug/console/DebugConsole.hpp" +#endif namespace game { class GameLogic; -class ThePURGE : public engine::api::Game { -public: - ThePURGE(); +struct SpellDatabase; +struct ClassDatabase; +struct EnemyDatabase; - enum class State { - LOADING, - IN_GAME, - IN_INVENTORY, - GAME_OVER, - }; +class ThePURGE : public engine::api::Game { +public: // api + ThePURGE() = default; auto onCreate(entt::registry &world) -> void final; @@ -39,43 +40,41 @@ class ThePURGE : public engine::api::Game { auto drawUserInterface(entt::registry &world) -> void final; - entt::entity player; // note : should not require to keep it like that - - auto setState(State new_state) noexcept { m_state = new_state; } + auto getBackgroundColor() const noexcept -> glm::vec4 final { return {0.0f, 0.0f, 0.0f, 0.0f}; } - // constexpr // note : C++20 but not supported by MSVC yet - auto getBackgroundColor() const noexcept -> glm::vec3 final - { - return m_state == State::GAME_OVER ? glm::vec3{0.35f, 0.45f, 0.50f} : glm::vec3{0.45f, 0.55f, 0.60f}; - } +public: + auto logics() const noexcept -> const std::unique_ptr & { return m_logics; } - auto getLogics() -> GameLogic & { return *m_logics; } - auto getMusic() -> std::shared_ptr { return m_dungeonMusic; } + auto dbSpells() noexcept -> SpellDatabase & { return m_db_spell; } + auto dbClasses() noexcept -> ClassDatabase & { return m_db_class; } + auto dbEnemies() noexcept -> EnemyDatabase & { return m_db_enemy; } + auto dbEffects() noexcept -> EffectDatabase & { return m_db_effects; } - auto getClassDatabase() -> const classes::Database & { return m_classDatabase; } auto getCamera() -> engine::Camera & { return m_camera; } + void setMenu(std::unique_ptr &&menu) { m_currentMenu = std::move(menu);} - auto logics() const noexcept -> const std::unique_ptr & { return m_logics; } - -private: - // auto goToNextFloor(entt::registry &world) -> void; - - auto displaySoundDebugGui() -> void; + auto getBackgroundMusic() -> std::shared_ptr { return m_background_music; } + void setBackgroundMusic(const std::string &path, float volume = 1) noexcept; - State m_state{State::LOADING}; + entt::entity player; // note : remove me - FloorGenParam m_map_generation_params; - std::uint32_t m_nextFloorSeed; +private: - engine::Camera m_camera; // note : should be in engine::Core +#ifndef NDEBUG + std::unique_ptr m_console; +#endif std::unique_ptr m_logics; - std::shared_ptr m_dungeonMusic; + SpellDatabase m_db_spell; + ClassDatabase m_db_class; + EnemyDatabase m_db_enemy; + EffectDatabase m_db_effects; - std::unique_ptr m_debugConsole; + engine::Camera m_camera; // note : should be in engine::Core + std::shared_ptr m_background_music; - classes::Database m_classDatabase; + std::unique_ptr m_currentMenu{ nullptr }; }; } // namespace game diff --git a/src/Application/include/component/AimSight.hpp b/src/Application/include/component/AimSight.hpp new file mode 100644 index 0000000..2a19c0e --- /dev/null +++ b/src/Application/include/component/AimSight.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include + +namespace game { + +// Reference to a player aiming sight +struct AimSight { + entt::entity entity; +}; + +} // namespace game diff --git a/src/Application/include/component/AimingDirection.hpp b/src/Application/include/component/AimingDirection.hpp new file mode 100644 index 0000000..337df9e --- /dev/null +++ b/src/Application/include/component/AimingDirection.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include + +namespace game { + +// note : Dictate the direction player is casting spells +// Probably just the input of the right joystick +struct AimingDirection { + glm::vec2 dir; // normalized and guaranteed to be set +}; + +} // namespace game diff --git a/src/Application/include/component/AttackCooldown.hpp b/src/Application/include/component/AttackCooldown.hpp deleted file mode 100644 index 5497ba0..0000000 --- a/src/Application/include/component/AttackCooldown.hpp +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include - -namespace game { - -// todo : see @Engine/component/cooldown -struct /*[[deprecated]]*/ AttackCooldown { - bool is_in_cooldown; - - std::chrono::milliseconds cooldown; - std::chrono::milliseconds remaining_cooldown; -}; - -} // namespace game diff --git a/src/Application/include/component/Classes.hpp b/src/Application/include/component/Classes.hpp index 7d1f70b..317e77e 100644 --- a/src/Application/include/component/Classes.hpp +++ b/src/Application/include/component/Classes.hpp @@ -5,8 +5,8 @@ namespace game { -struct Classes { - std::vector ids; +struct Classes { // todo : name very confusing + std::vector ids; }; } // namespace game diff --git a/src/Application/include/component/ControllerAxis.hpp b/src/Application/include/component/ControllerAxis.hpp index 1857605..9dca830 100644 --- a/src/Application/include/component/ControllerAxis.hpp +++ b/src/Application/include/component/ControllerAxis.hpp @@ -7,8 +7,14 @@ namespace game { // Controller axis current statuses struct ControllerAxis { + // If input magnitude is less than the deadzone, the input is considered to be zero + static constexpr auto kDeadzone = 0.1f; + glm::vec2 movement; glm::vec2 aiming; + + // virtual movement joystick corresponding to keyboard player movement. May not be normalized + glm::vec2 keyboard_movement; }; } // namespace game diff --git a/src/Application/include/component/Effect.hpp b/src/Application/include/component/Effect.hpp index e6d7387..7d53bfb 100644 --- a/src/Application/include/component/Effect.hpp +++ b/src/Application/include/component/Effect.hpp @@ -5,7 +5,7 @@ namespace game { -struct Effect { // todo : split me in severals components +struct /*[[deprecated]]*/ Effect { // todo : split me in severals components bool is_in_effect; bool is_in_cooldown; std::string effect_name; // todo : use enum diff --git a/src/Application/include/component/Experience.hpp b/src/Application/include/component/Experience.hpp new file mode 100644 index 0000000..d63dba2 --- /dev/null +++ b/src/Application/include/component/Experience.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include + +namespace game { + +struct Experience { + std::uint32_t xp; +}; + +} // namespace game diff --git a/src/Application/include/component/Facing.hpp b/src/Application/include/component/Facing.hpp deleted file mode 100644 index bd35c05..0000000 --- a/src/Application/include/component/Facing.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include - -namespace game { - -// note : Dictate the direction player is casting spells -// Probably just the input of the right joystick - -struct Facing { - glm::vec2 dir; // normalized -}; - -} // namespace game diff --git a/src/Application/include/component/KeyPicker.hpp b/src/Application/include/component/KeyPicker.hpp index 74bfab0..18ef344 100644 --- a/src/Application/include/component/KeyPicker.hpp +++ b/src/Application/include/component/KeyPicker.hpp @@ -1,7 +1,9 @@ #pragma once namespace game { + struct KeyPicker { bool hasKey = false; }; + } // namespace game diff --git a/src/Application/include/component/Particule.hpp b/src/Application/include/component/Particule.hpp index 623585a..df68855 100644 --- a/src/Application/include/component/Particule.hpp +++ b/src/Application/include/component/Particule.hpp @@ -5,8 +5,9 @@ namespace game { struct Particule { enum ID { HITMARKER, - - ID_MAX, + NEUTRAL, + POSITIVE, + ID_MAX }; ID id; diff --git a/src/Application/include/component/Speed.hpp b/src/Application/include/component/Speed.hpp new file mode 100644 index 0000000..f9a77a2 --- /dev/null +++ b/src/Application/include/component/Speed.hpp @@ -0,0 +1,9 @@ +#pragma once + +namespace game { + +struct Speed { + float speed; +}; + +} // namespace game \ No newline at end of file diff --git a/src/Application/include/component/SpellEffect.hpp b/src/Application/include/component/SpellEffect.hpp new file mode 100644 index 0000000..091c41b --- /dev/null +++ b/src/Application/include/component/SpellEffect.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +namespace game { + +struct SpellEffect { + + std::vector ref; + +}; + +} // namespace game diff --git a/src/Application/include/component/SpellSlots.hpp b/src/Application/include/component/SpellSlots.hpp index 7bb2f34..039b155 100644 --- a/src/Application/include/component/SpellSlots.hpp +++ b/src/Application/include/component/SpellSlots.hpp @@ -2,15 +2,19 @@ #include #include +//#include #include "models/Spell.hpp" namespace game { +struct Spell; + struct SpellSlots { std::array, 4ul> spells; - // inline void removeElem(int pos) { spells[pos] = {}; } + // note : should be a reference to the spell castable and not the real spell + //std::array, 4ul> spells; }; } // namespace game diff --git a/src/Application/include/component/SpellTarget.hpp b/src/Application/include/component/SpellTarget.hpp new file mode 100644 index 0000000..b7e8054 --- /dev/null +++ b/src/Application/include/component/SpellTarget.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include + +#include "models/Spell.hpp" + +namespace game { + +struct SpellTarget { + + decltype(SpellData::targets) ref; + +}; + +} // namespace game diff --git a/src/Application/include/component/StatsTracking.hpp b/src/Application/include/component/StatsTracking.hpp new file mode 100644 index 0000000..294219e --- /dev/null +++ b/src/Application/include/component/StatsTracking.hpp @@ -0,0 +1,10 @@ +#pragma once + +namespace game { + +// Stats to be displayed on gameover screen +struct StatsTracking { + int enemyKilled = 0; +}; + +} // namespace game diff --git a/src/Application/include/component/all.hpp b/src/Application/include/component/all.hpp index e79f432..688507d 100644 --- a/src/Application/include/component/all.hpp +++ b/src/Application/include/component/all.hpp @@ -5,18 +5,26 @@ #include #include #include +#include #include #include #include +#include +#include +#include "component/AimSight.hpp" +#include "component/AimingDirection.hpp" #include "component/AttackRange.hpp" #include "component/AttackDamage.hpp" -#include "component/AttackCooldown.hpp" -#include "component/Effect.hpp" +#include "component/SpellEffect.hpp" +#include "component/SpellTarget.hpp" +#include "component/Speed.hpp" +#include "component/StatsTracking.hpp" #include "component/ViewRange.hpp" #include "component/Health.hpp" #include "component/Level.hpp" +#include "component/Experience.hpp" #include "component/KeyPicker.hpp" @@ -24,7 +32,5 @@ #include "component/Classes.hpp" #include "component/SkillPoint.hpp" -#include "component/Facing.hpp" -#include "component/Lifetime.hpp" #include "component/Particule.hpp" #include "component/ControllerAxis.hpp" diff --git a/src/Application/include/factory/EntityFactory.hpp b/src/Application/include/factory/EntityFactory.hpp index dd94394..89ee569 100644 --- a/src/Application/include/factory/EntityFactory.hpp +++ b/src/Application/include/factory/EntityFactory.hpp @@ -1,78 +1,61 @@ #pragma once +#include #include #include #include -namespace game { - -struct EntityFactory { - enum ID { - ID_ERROR = 0, - - LAYER_DEBUG = 1, - DEBUG_TILE = LAYER_DEBUG * 10, - - LAYER_PLAYER = 2, - FARMER = LAYER_PLAYER * 10, - SOLDIER, - SHOOTER, - SORCERER, - TONK, - PLAYER, // should be named farmer - - KEY, // should be on a layer above +#include "models/Enemy.hpp" +namespace game { - LAYER_ENEMY = 3, - ENEMY = LAYER_ENEMY * 10, - BOSS, - +class ThePURGE; - LAYER_TERRAIN = 4, +struct EntityFactory { + enum class Layer : std::uint8_t { + LAYER_ERROR, + PARTICULE, + LAYER_PLAYER, + LAYER_ENEMY, + LAYER_DEBUG, + LAYER_TERRAIN, + LAYER_BACKGROUND, + MAX_LAYER, + }; - FLOOR_NORMAL = LAYER_TERRAIN * 10, + enum ID : std::uint8_t { + DEBUG_TILE, + AIMING_SIGHT, + PLAYER, + KEY, + FLOOR_NORMAL, FLOOR_SPAWN, FLOOR_BOSS, FLOOR_CORRIDOR, EXIT_DOOR, WALL, - - LAYER_BACKGROUND, BACKGROUND, - - MAX_ID, }; - static auto ID_from_string(const std::string_view in) noexcept -> ID - { - std::string compare{in}; - std::transform(compare.begin(), compare.end(), compare.begin(), [](auto c) { return static_cast(std::tolower(c)); }); - - for (const auto &i : magic_enum::enum_values()) { - auto enum_name = std::string{magic_enum::enum_name(i)}; - std::transform(enum_name.begin(), enum_name.end(), enum_name.begin(), [](auto c) { return static_cast(std::tolower(c)); }); - if (compare == enum_name) { return static_cast(i); } - } - return LAYER_DEBUG; - } - // note : should take a path to a json config instead ... ? // todo : normalize arguments , Args...&& ? template - static auto create(entt::registry &, const glm::vec2 &pos, const glm::vec2 &size /*, float rotation*/) -> entt::entity; + /*[[deprecated]]*/ static auto create(ThePURGE &, entt::registry &, const glm::vec2 &pos, const glm::vec2 &size) + -> entt::entity; - template + template constexpr static auto get_z_layer() noexcept -> double { - return static_cast(id); + return static_cast(layer); } + + static auto create(ThePURGE &, entt::registry &, const glm::vec2 &pos, const Enemy &) -> entt::entity; }; -#define DECL_SPEC(id) \ - template<> \ - auto EntityFactory::create( \ - entt::registry &, const glm::vec2 &, const glm::vec2 & /*, float rotation*/) \ +#define DECL_SPEC(id) \ + template<> \ + auto /*[[deprecated]]*/ EntityFactory::create( \ + ThePURGE &, entt::registry &, const glm::vec2 &, const glm::vec2 &) \ ->entt::entity DECL_SPEC(DEBUG_TILE); @@ -84,15 +67,10 @@ DECL_SPEC(FLOOR_CORRIDOR); DECL_SPEC(EXIT_DOOR); DECL_SPEC(WALL); -DECL_SPEC(BOSS); -DECL_SPEC(ENEMY); - +DECL_SPEC(AIMING_SIGHT); DECL_SPEC(PLAYER); DECL_SPEC(KEY); -DECL_SPEC(BACKGROUND); - - #undef DECL_SPEC } // namespace game diff --git a/src/Application/include/factory/ParticuleFactory.hpp b/src/Application/include/factory/ParticuleFactory.hpp index 2f9e8a4..02cdc79 100644 --- a/src/Application/include/factory/ParticuleFactory.hpp +++ b/src/Application/include/factory/ParticuleFactory.hpp @@ -2,6 +2,7 @@ #include #include +#include #include "component/Particule.hpp" @@ -9,14 +10,16 @@ namespace game { struct ParticuleFactory { template - static auto create(entt::registry &, const glm::vec2 &pos) -> void; + static auto create(entt::registry &, const glm::vec2 &pos, const glm::vec3 &) -> void; }; #define DECL_SPEC(id) \ template<> \ - auto ParticuleFactory::create(entt::registry &, const glm::vec2 &) -> void + auto ParticuleFactory::create(entt::registry &, const glm::vec2 &, const glm::vec3 &)->void +DECL_SPEC(POSITIVE); DECL_SPEC(HITMARKER); +DECL_SPEC(NEUTRAL); #undef DECL_SPEC diff --git a/src/Application/include/factory/SpellFactory.hpp b/src/Application/include/factory/SpellFactory.hpp index 0181b22..f670ee9 100644 --- a/src/Application/include/factory/SpellFactory.hpp +++ b/src/Application/include/factory/SpellFactory.hpp @@ -5,52 +5,12 @@ namespace game { -struct SpellFactory { - enum ID { - SHOVEL_ATTACK, - SWORD_ATTACK, - FIREBALL, - PIERCING_ARROW, - - ENEMY_ATTACK, - DEBUG_GIANT_FIREBALL, - }; - - template - static auto create(entt::registry &, entt::entity caster, const glm::dvec2 &direction) -> entt::entity; - - template - static auto create(ID spell, Args &&... args) - { -#define MAP_SPELL(id) \ - case ID::id: create(std::forward(args)...); break; - - switch (spell) { - MAP_SPELL(ENEMY_ATTACK); - MAP_SPELL(SHOVEL_ATTACK); - MAP_SPELL(SWORD_ATTACK); - MAP_SPELL(FIREBALL); - MAP_SPELL(PIERCING_ARROW); +struct SpellData; +struct SpellDatabase; - MAP_SPELL(DEBUG_GIANT_FIREBALL); - - default: assert(false && "unknown spell. Did you forget to map the enum value to factory function ?"); - } -#undef MAP_SPELL - } +struct SpellFactory { + static auto create(SpellDatabase &, entt::registry &, entt::entity caster, const glm::dvec2 &direction, const SpellData &) + -> entt::entity; }; -#define DECL_SPEC(id) \ - template<> \ - auto SpellFactory::create(entt::registry &, entt::entity, const glm::dvec2 &)->entt::entity - -DECL_SPEC(ENEMY_ATTACK); -DECL_SPEC(SHOVEL_ATTACK); -DECL_SPEC(SWORD_ATTACK); -DECL_SPEC(FIREBALL); -DECL_SPEC(DEBUG_GIANT_FIREBALL); -DECL_SPEC(PIERCING_ARROW); - -#undef DECL_SPEC - } // namespace game diff --git a/src/Application/include/level/MapData.hpp b/src/Application/include/level/MapData.hpp deleted file mode 100644 index 81612ee..0000000 --- a/src/Application/include/level/MapData.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#include - -namespace game { - -/** - In the range [x;x+w[ and [y;y+h[ - include walls -*/ -struct Room { - int x, y; - int w, h; -}; - - -struct MapData { - Room spawn{}; - std::vector regularRooms; - Room boss{}; - - std::uint32_t nextFloorSeed; -}; - -} // namespace game diff --git a/src/Application/include/level/MapGenerator.hpp b/src/Application/include/level/MapGenerator.hpp deleted file mode 100644 index 274aa29..0000000 --- a/src/Application/include/level/MapGenerator.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include -#include - -#include "level/MapData.hpp" - -namespace game { - -struct FloorGenParam { - int minRoomSize = 7; // exluding walls - int maxRoomSize = 15; // exluding walls - - std::size_t minRoomCount = 5; // including boss room - std::size_t maxRoomCount = 10; // including boss room - - int maxDungeonWidth = 50; - int maxDungeonHeight = 50; - - int minCorridorWidth = 3; // min 3 or player may not fit - int maxCorridorWidth = 4; - - - float mobDensity = 0.05f; // Average mob per tile -}; - -// note : avoid free function -auto generateFloor(entt::registry &, const FloorGenParam & = {}, std::optional seed = {}) - -> MapData; - -} // namespace game diff --git a/src/Application/include/menu/AMenu.hpp b/src/Application/include/menu/AMenu.hpp new file mode 100644 index 0000000..ea94837 --- /dev/null +++ b/src/Application/include/menu/AMenu.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include +#include +#include + +#include + +namespace game { + +class ThePURGE; + + +class AMenu { +public: + AMenu() = default; + virtual ~AMenu() = default; + + void onEvent(entt::registry &world, ThePURGE &game, const engine::Event &e); + void onDraw(entt::registry &world, ThePURGE &game); + +protected: + virtual void create(entt::registry &, ThePURGE &) {}; + virtual void event(entt::registry &, ThePURGE &, const engine::Event &) = 0; + virtual void draw(entt::registry &, ThePURGE &) = 0; + + + // + // Input helpers with most common interractions. + // Should only be read during the draw() function + // + bool up() const noexcept { return m_up; } + bool down() const noexcept { return m_down; } + bool right() const noexcept { return m_right; } + bool left() const noexcept { return m_left; } + // User closed menu + bool close() const noexcept { return m_close; } + // User selected / clicked on current option + bool select() const noexcept { return m_select; } + + + void forceUp(bool val) noexcept { m_up = val; } + void forceDown(bool val) noexcept { m_down = val; } + void forceLeft(bool val) noexcept { m_left = val; } + void forceRight(bool val) noexcept { m_right = val; } + + void forceSelect(bool val) noexcept { m_select = val; } + void forceClose(bool val) noexcept { m_close = val; } + + +private: + void resetInputs() noexcept; + +private: + static constexpr float kRecoveryThreshold = 0.3f; + static constexpr float kTriggerThreshold = 0.3f; + + glm::vec2 m_prevLeftJoystick{0.f, 0.f}; + + bool m_shouldResetInputs = false; + + bool m_createCalled = false; + + bool m_up = false; + bool m_down = false; + bool m_left = false; + bool m_right = false; + bool m_select = false; + bool m_close = false; + + bool m_recoveringUp = false; + bool m_recoveringDown = false; + bool m_recoveringRight = false; + bool m_recoveringLeft = false; +}; + +} // namespace game diff --git a/src/Application/include/menu/Credits.hpp b/src/Application/include/menu/Credits.hpp new file mode 100644 index 0000000..e163b86 --- /dev/null +++ b/src/Application/include/menu/Credits.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include + +#include "AMenu.hpp" + +namespace game::menu { + +class Credits : public AMenu { +public: + void create(entt::registry &world, ThePURGE &game) final; + void draw(entt::registry &world, ThePURGE &game) final; + void event(entt::registry &world, ThePURGE &game, const engine::Event &e) final; + +private: + std::uint32_t m_texture; +}; + +} // namespace game diff --git a/src/Application/include/menu/GameOver.hpp b/src/Application/include/menu/GameOver.hpp new file mode 100644 index 0000000..8ba5261 --- /dev/null +++ b/src/Application/include/menu/GameOver.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include + +#include "AMenu.hpp" +#include "widgets/helpers.hpp" +#include "models/EndGameStats.hpp" + +namespace game::menu { + +class GameOver : public AMenu { +public: + GameOver(const EndGameStats &stats) : m_stats(stats) {} + + void create(entt::registry &world, ThePURGE &game) final; + void draw(entt::registry &world, ThePURGE &game) final; + void event(entt::registry &world, ThePURGE &game, const engine::Event &e) final; + +private: + void drawGameStats(); + + auto clean_world(entt::registry &) -> void; + +private: + enum Button { + PLAY_AGAIN = 0, + MENU, + + MAX // last value, not a real button + }; + + std::uint32_t m_backgroundTexture; + std::vector m_buttons; + + int m_selected = Button::PLAY_AGAIN; + + EndGameStats m_stats; + + double m_timeElapsed = 0; +}; + +} // namespace game::menu diff --git a/src/Application/include/menu/HowToPlay.hpp b/src/Application/include/menu/HowToPlay.hpp new file mode 100644 index 0000000..f666afe --- /dev/null +++ b/src/Application/include/menu/HowToPlay.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include "AMenu.hpp" + +namespace game::menu { + +class HowToPlay : public AMenu { +public: + void create(entt::registry &world, ThePURGE &game) final; + void draw(entt::registry &world, ThePURGE &game) final; + void event(entt::registry &world, ThePURGE &game, const engine::Event &e) final; + +private: + enum class Tab { + HOW_TO_PLAY, + CONTROLS + }; + + std::uint32_t m_howToPlay; + std::uint32_t m_controls; + + Tab m_currentTab = Tab::HOW_TO_PLAY; +}; + +} // namespace game diff --git a/src/Application/include/menu/MainMenu.hpp b/src/Application/include/menu/MainMenu.hpp new file mode 100644 index 0000000..57feea3 --- /dev/null +++ b/src/Application/include/menu/MainMenu.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include "AMenu.hpp" +#include "widgets/helpers.hpp" + +namespace game::menu { + +class MainMenu : public AMenu { +public: + void create(entt::registry &world, ThePURGE &game) final; + void draw(entt::registry &world, ThePURGE &game) final; + void event(entt::registry &world, ThePURGE &game, const engine::Event &e) final; + +private: + enum Button { + PLAY = 0, + HOWTOPLAY, + CREDITS, + EXIT, + + MAX // last value, not a real button + }; + + + std::uint32_t m_backgroundTexture; + std::vector m_buttons; + + int m_selected = Button::PLAY; +}; + +} // namespace game diff --git a/src/Application/include/menu/UpgradePanel.hpp b/src/Application/include/menu/UpgradePanel.hpp new file mode 100644 index 0000000..6315ad1 --- /dev/null +++ b/src/Application/include/menu/UpgradePanel.hpp @@ -0,0 +1,78 @@ +#pragma once + +#include +#include + +#include "AMenu.hpp" +#include "widgets/helpers.hpp" + +namespace game::menu { + +class UpgradePanel : public AMenu { +public: + void create(entt::registry &world, ThePURGE &game) final; + void draw(entt::registry &world, ThePURGE &game) final; + void event(entt::registry &world, ThePURGE &game, const engine::Event &e) final; + +private: + struct ClassTreeNode { + int selfIndex; + + const Class *cl; + ImVec2 relPos; // Position of the center of the element relative to tree bounds + std::vector children; + }; + +private: + + void processInputs(entt::registry &world, ThePURGE &game); + void onBuyHp(entt::registry &world); + + void drawTree(entt::registry &world, ThePURGE &game) noexcept; + auto getTreeDrawPos(const ImVec2 &relPos, float elemSize) const noexcept -> ImVec2; + + void drawDetailPanel(entt::registry &world, ThePURGE &game) noexcept; + + void updateClassTree(entt::registry &world, ThePURGE &game); + auto generateTreeRec(ThePURGE &game, const Class *cl, int selfIndex = 0, int depth = 0) const noexcept -> ClassTreeNode; + auto findInTree(const Class *cl) const noexcept -> const ClassTreeNode *; + auto findParent(const Class *cl) const noexcept -> const ClassTreeNode *; + + auto isOwned(const Class *) const noexcept -> bool; + auto isPurchaseable(const Class *) const noexcept -> bool; + auto isUnavailable(const Class *) const noexcept -> bool; + + void printClassTreeDebug() const noexcept; +private: + GUITexture m_static_background; + GUITexture m_bind_popup; + entt::entity m_player; + + const SpellData * m_spellBeingAssigned; + + const ClassTreeNode *m_selection; + ImVec2 m_cursorCurrentPos; // in tree unit (@see `ClassTreeNode`) + ImVec2 m_cursorDestinationPos; + + // Classes by tier + std::vector> m_classes; + + std::vector m_owned; + std::vector m_purchaseable; + + ClassTreeNode m_root; +private: + static inline constexpr auto kHeightMargins = 100; + static inline constexpr auto kWidthMargins = 150; + + static inline const ImVec2 treeTopLeft{524 + kWidthMargins, 223 + kHeightMargins}; + static inline const ImVec2 treeSize{1344 - 2 * kWidthMargins, 835 - 2 * kHeightMargins}; + + static inline constexpr auto kIconSize = 100; + static inline constexpr auto kFrameSize = 250; + static inline constexpr auto kCursorSize = 200; + + static inline constexpr float kSelectionAnimationSpeed = 0.5f; +}; + +} // namespace game diff --git a/src/Application/include/models/Class.hpp b/src/Application/include/models/Class.hpp index d2dfc06..546413d 100644 --- a/src/Application/include/models/Class.hpp +++ b/src/Application/include/models/Class.hpp @@ -1,29 +1,42 @@ #pragma once - #include +#include #include +#include +#include + +#include #include "factory/SpellFactory.hpp" #include "factory/EntityFactory.hpp" namespace game { -struct Class { - EntityFactory::ID id; +struct Class { // todo : name very confusing std::string name; - std::string description; std::string iconPath; std::string assetGraphPath; bool is_starter = false; + int cost; + + float health; + float speed; + engine::d2::HitboxSolid hitbox; + + std::vector spells; + std::vector children; +}; + +struct ClassDatabase { + std::vector db; - std::vector spells; + auto fromFile(const std::string_view path) -> ClassDatabase &; - float maxHealth; - float damage; + auto getByName(const std::string_view name) -> const Class *; - std::vector children; + auto getStarterClass() -> const Class &; }; } // namespace game diff --git a/src/Application/include/models/ClassDatabase.hpp b/src/Application/include/models/ClassDatabase.hpp deleted file mode 100644 index 627f3fd..0000000 --- a/src/Application/include/models/ClassDatabase.hpp +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include -#include -#include - -#include - -#include "Class.hpp" - -namespace game { - -namespace classes { - -using Database = std::unordered_map; - -auto getByName(const Database &db, const std::string_view name) -> const Class *; - -inline auto getStarterClass(const Database &db) -> const Class & -{ - if (const auto found = std::find_if(db.begin(), db.end(), [](const auto &i) { return i.second.is_starter; }); - found != db.end()) { - return found->second; - } else - UNLIKELY { return db.begin()->second; } -} - -} // namespace classes - -} // namespace game diff --git a/src/Application/include/models/Effect.hpp b/src/Application/include/models/Effect.hpp new file mode 100644 index 0000000..8635826 --- /dev/null +++ b/src/Application/include/models/Effect.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include + +#include + +#include "models/utils.hpp" + +namespace game { + +struct Effect { + enum Type { + DOT, + DASH, + + TYPE_MAX, + }; + + Type type; + + float damage; + std::chrono::milliseconds lifetime; + std::chrono::milliseconds cooldown; + + float strength; +}; + +inline void to_json([[maybe_unused]] nlohmann::json &j, [[maybe_unused]] const Effect &effect) {} + +inline void from_json(const nlohmann::json &j, Effect &effect) +{ + effect.type = toEnum(j.at("type")).value_or(Effect::TYPE_MAX); + if (effect.type == Effect::DOT) { + effect.damage = j.at("damage"); + effect.cooldown = std::chrono::milliseconds{j.at("cooldown")}; + effect.lifetime = std::chrono::milliseconds{j.at("lifetime")}; + } else if (effect.type == Effect::DASH) { + effect.lifetime = std::chrono::milliseconds{j.at("lifetime")}; + effect.cooldown = std::chrono::milliseconds{j.at("cooldown")}; + effect.strength = j.at("strength"); + } +} + +struct EffectDatabase { + std::unordered_map db; + + auto fromFile(const std::string_view path) -> bool; +}; + +} // namespace game diff --git a/src/Application/include/models/EndGameStats.hpp b/src/Application/include/models/EndGameStats.hpp new file mode 100644 index 0000000..94c1a8d --- /dev/null +++ b/src/Application/include/models/EndGameStats.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include + +#include + +namespace game { + +struct EndGameStats { + EndGameStats(entt::registry &world, const entt::entity player, double gameTime); + + std::uint32_t finalLevel; + int enemyKilled; + double gameTimeInSeconds; +}; + +} // namespace game diff --git a/src/Application/include/models/Enemy.hpp b/src/Application/include/models/Enemy.hpp new file mode 100644 index 0000000..22f0d67 --- /dev/null +++ b/src/Application/include/models/Enemy.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +namespace game { + +struct Enemy { + + std::string asset; + + float health; + float speed; + + glm::vec4 color; + + engine::d2::HitboxSolid hitbox; + + engine::d2::Scale scale; + + std::vector spells; + + bool is_boss; + + float view_range; + float attack_range; + + std::uint32_t experience; + + std::vector tag; + +}; + +inline void to_json([[maybe_unused]] nlohmann::json &j, [[maybe_unused]] const Enemy &enemy) +{ + // clang-format off + // j = nlohmann::json{{ + + // }}; + // clang-format on +} + +inline void from_json(const nlohmann::json &j, Enemy &enemy) +{ + + enemy.asset = j.at("asset"); + enemy.health = j.at("health"); + enemy.speed = j.at("speed"); + enemy.color.r = j.at("color").at("r"); + enemy.color.g = j.at("color").at("g"); + enemy.color.b = j.at("color").at("b"); + enemy.color.a = j.at("color").value("a", 1.0f); + enemy.hitbox.width = j.at("hitbox").at("x"); + enemy.hitbox.height = j.at("hitbox").at("y"); + enemy.scale.x = j.at("scale").at("x"); + enemy.scale.y = j.at("scale").at("y"); + enemy.spells = j.at("spells").get>(); + enemy.is_boss = j.value("is_boss", false); + enemy.view_range = j.at("view_range"); + enemy.attack_range = j.at("attack_range"); + enemy.experience = j.at("experience"); + enemy.tag = j.value("tag", std::vector{}); +} + +struct EnemyDatabase { + std::unordered_map db; + + auto fromFile(const std::string_view path) -> bool; +}; + +} // namespace game diff --git a/src/Application/include/models/Spell.hpp b/src/Application/include/models/Spell.hpp index d965536..3aea85f 100644 --- a/src/Application/include/models/Spell.hpp +++ b/src/Application/include/models/Spell.hpp @@ -1,32 +1,93 @@ #pragma once #include +#include +#include +#include -#include "factory/SpellFactory.hpp" -#include "models/SpellDatabase.hpp" +#include +#include +#include #include +#include +#include + +#include "factory/SpellFactory.hpp" using namespace std::chrono_literals; namespace game { -struct Spell { - SpellFactory::ID id; +struct /*[[deprecated]]*/ Spell { + std::string_view id; engine::Cooldown cd; +}; + +struct SpellData { + std::string name; + std::string description; + std::string iconPath; + + std::chrono::milliseconds cooldown; + + float damage; + engine::d2::HitboxFloat hitbox; + double speed; + + std::chrono::milliseconds lifetime; + std::string audio_on_cast; + std::string animation; + engine::d2::Scale scale; + + double offset_to_source_x; + double offset_to_source_y; + + enum Type : std::uint8_t { + TYPE_ZERO = 0, + + PROJECTILE = 1 << 0, + AOE = 1 << 1, + SUMMONER = 1 << 2, + ON_DEATH = 1 << 3, + + TYPE_MAX, + }; + + std::bitset type; + + std::vector effects; + + + enum Target : std::uint8_t { + TARGET_ZERO = 0, + + ENEMY = 1 << 0, + CASTER = 1 << 1, + ALL = 1 << 2, + + TARGET_MAX, + }; + + std::bitset targets; + + int quantity; + double angle; + + std::string on_death; + +}; + +void to_json(nlohmann::json &j, const SpellData &spell); +void from_json(const nlohmann::json &j, SpellData &spell); + +struct SpellDatabase { + std::unordered_map db; + + auto fromFile(const std::string_view) -> bool; - [[nodiscard]] static auto create(SpellFactory::ID spell) -> Spell - { - return { - .id = spell, - .cd = { - .is_in_cooldown = false, - .cooldown = g_SpellDatabase.at(spell).cooldown, - .remaining_cooldown = 0ms, - }}; - } - -}; // namespace game + [[nodiscard]] auto instantiate(const std::string_view) -> std::optional; +}; } // namespace game diff --git a/src/Application/include/models/SpellDatabase.hpp b/src/Application/include/models/SpellDatabase.hpp deleted file mode 100644 index e5dcbe9..0000000 --- a/src/Application/include/models/SpellDatabase.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include -#include - -#include "factory/SpellFactory.hpp" - -namespace game { - -struct SpellData { - std::chrono::milliseconds cooldown; -}; - -using SpellDatabase = std::unordered_map; - -// TODO: Better way to provide the spell database -extern SpellDatabase g_SpellDatabase; - -// note : avoid free function -auto makeSpellDatabase() -> SpellDatabase; - -} // namespace game diff --git a/src/Application/include/models/Stage.hpp b/src/Application/include/models/Stage.hpp new file mode 100644 index 0000000..20e7a63 --- /dev/null +++ b/src/Application/include/models/Stage.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include +#include +#include +#include + +#include + +#include "stage/LevelTilemapBuilder.hpp" + +namespace game { + +class ThePURGE; + +struct Stage { + struct Room { + std::int32_t x, y, w, h; + + [[nodiscard]] constexpr bool is_valid(const TilemapBuilder &builder); + }; + + Room spawn{}; + Room boss{}; + std::vector regularRooms; + + struct Parameters { + int minRoomSize = 7; // exluding walls + int maxRoomSize = 15; // exluding walls + + std::size_t minRoomCount = 5; // including boss room + std::size_t maxRoomCount = 10; // including boss room + + int maxDungeonWidth = 50; + int maxDungeonHeight = 50; + + int minCorridorWidth = 3; // min 3 or player may not fit + int maxCorridorWidth = 4; + + std::unordered_map mobDensity{ + {"rock_destructible", 1.03f}, + {"rock_obstacle", 1.03f}, + {"skeleton", 1.01f}, + {"golem", 4.005f}, + {"zombie", 4.01f}, + {"summoner", 3.005f}, + {"electric_skeleton", 2.005f}, + {"ice_skeleton", 1.01f}, + {"golem_turret", 1.001f}, + {"fire_skeleton", 3.001f} + + }; + }; + + auto generate(ThePURGE &, entt::registry &, const Parameters &, std::optional seed = {}) -> Stage &; + + template + static auto randRange(T min, T max) + { + assert(max > min); + + const auto r = random_engine(); + return min + static_cast(r % static_cast(max - min)); + } + + std::uint32_t nextFloorSeed; + + auto clear(entt::registry &, bool kill_the_players) -> void; + +private: + static std::default_random_engine random_engine; + + auto create_floor(ThePURGE &, entt::registry &, const Parameters &); + auto populate_enemies(ThePURGE &, entt::registry &, const Parameters &); + + // todo : add parameter enemy type + auto spawn_mob(ThePURGE &, entt::registry &, const Parameters &, const Room &r) -> void; +}; + +} // namespace game diff --git a/src/Application/include/models/utils.hpp b/src/Application/include/models/utils.hpp new file mode 100644 index 0000000..dab83de --- /dev/null +++ b/src/Application/include/models/utils.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +#include + +namespace game { + +template +static auto toEnum(std::string in) noexcept -> std::optional +{ + const auto to_lower = [](auto str) { + std::transform(str.begin(), str.end(), str.begin(), [](auto c) { return static_cast(std::tolower(c)); }); + return str; + }; + + in = to_lower(in); + + for (const auto &i : magic_enum::enum_values()) { + if (in == to_lower(std::string{magic_enum::enum_name(i)})) { return static_cast(i); } + } + return {}; +} + +} // namespace game diff --git a/src/Application/include/screen/GameOverMenu.hpp b/src/Application/include/screen/GameOverMenu.hpp deleted file mode 100644 index e302e7f..0000000 --- a/src/Application/include/screen/GameOverMenu.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include - -#include "ThePURGE.hpp" - -namespace game { - -struct GameOverMenu { - static auto draw(ThePURGE &game, entt::registry &world) -> void - { - // todo : style because this is not a debug window - ImGui::Begin("Menu Game Over", nullptr, ImGuiWindowFlags_NoDecoration); - - if (ImGui::Button("Your are dead !")) { - for (const auto &i : world.view>()) { world.destroy(i); } - for (const auto &i : world.view>()) { world.destroy(i); } - for (const auto &i : world.view>()) { world.destroy(i); } - for (const auto &i : world.view>()) { world.destroy(i); } - for (const auto &i : world.view>()) { world.destroy(i); } - - game.setState(ThePURGE::State::LOADING); - } - - ImGui::End(); - } -}; - -} // namespace game diff --git a/src/Application/include/screen/MainMenu.hpp b/src/Application/include/screen/MainMenu.hpp deleted file mode 100644 index a316324..0000000 --- a/src/Application/include/screen/MainMenu.hpp +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include - -#include "ThePURGE.hpp" - -namespace game { - -struct MainMenu { - static auto draw(ThePURGE &game, entt::registry &world) -> void - { - // todo : load the resource in a coroutine here - - // todo : style because this is not a debug window - ImGui::Begin("Menu loading", nullptr, ImGuiWindowFlags_NoDecoration); - - if (ImGui::Button("Start the game")) { - game.logics()->onGameStarted.publish(world); - game.setState(ThePURGE::State::IN_GAME); - } - - ImGui::End(); - } -}; - -} // namespace game diff --git a/src/Application/include/level/LevelTilemapBuilder.hpp b/src/Application/include/stage/LevelTilemapBuilder.hpp similarity index 87% rename from src/Application/include/level/LevelTilemapBuilder.hpp rename to src/Application/include/stage/LevelTilemapBuilder.hpp index eccb4da..e505639 100644 --- a/src/Application/include/level/LevelTilemapBuilder.hpp +++ b/src/Application/include/stage/LevelTilemapBuilder.hpp @@ -10,6 +10,8 @@ namespace game { +class ThePURGE; + // note : avoid macro #define IS_FLOOR(tile) \ ((tile == game::TileEnum::FLOOR_SPAWN || tile == game::TileEnum::FLOOR_CORRIDOR \ @@ -34,8 +36,8 @@ enum class TileEnum : std::uint8_t { // note : should be merged with EntityFacto class TilemapBuilder { public: - explicit TilemapBuilder(glm::ivec2 &&size = {100, 100}) : - m_size(size), m_tiles(static_cast(m_size.x) * static_cast(m_size.y), TileEnum::NONE) + explicit TilemapBuilder(ThePURGE &game, glm::ivec2 &&size = {100, 100}) : + m_game(game), m_size(size), m_tiles(static_cast(m_size.x) * static_cast(m_size.y), TileEnum::NONE) { } @@ -64,6 +66,8 @@ class TilemapBuilder { auto getTileSize(int x, int y) const -> glm::ivec2; private: + ThePURGE &m_game; + const glm::ivec2 m_size; std::vector m_tiles; }; diff --git a/src/Application/include/widgets/DebugAudio.hpp b/src/Application/include/widgets/DebugAudio.hpp deleted file mode 100644 index 0f2ad99..0000000 --- a/src/Application/include/widgets/DebugAudio.hpp +++ /dev/null @@ -1,83 +0,0 @@ -#pragma once - -#include - -#include "ThePURGE.hpp" - -namespace game { - -namespace widget { - -struct DebugAudio { - static auto draw() - { - static auto holder = engine::Core::Holder{}; - - static std::vector> sounds; - - ImGui::Begin("Sound debug window"); - - if (ImGui::Button("Load Music")) { - try { - sounds.push_back(holder.instance->getAudioManager().getSound( - holder.instance->settings().data_folder + "/sounds/dungeon_music.wav")); - } catch (...) { - } - } - if (ImGui::Button("Load Hit sound")) { - try { - sounds.push_back(holder.instance->getAudioManager().getSound( - holder.instance->settings().data_folder + "/sounds/hit.wav")); - } catch (...) { - } - } - ImGui::Separator(); - - std::shared_ptr toRemove = nullptr; - - int loopId = 0; - for (const auto &s : sounds) { - ImGui::PushID(loopId++); - - ImGui::Text("Status :"); - ImGui::SameLine(); - - switch (s->getStatus()) { - case engine::SoundStatus::INITIAL: ImGui::TextColored(ImVec4(1, 1, 1, 1), "Initial"); break; - case engine::SoundStatus::PLAYING: ImGui::TextColored(ImVec4(0.2f, 1, 0.2f, 1), "Playing"); break; - case engine::SoundStatus::PAUSED: ImGui::TextColored(ImVec4(1, 1, 0.4f, 1), "Paused"); break; - case engine::SoundStatus::STOPPED: ImGui::TextColored(ImVec4(1, 0.2f, 0.2f, 1), "Stopped"); break; - } - - if (ImGui::Button("Play")) s->play(); - if (ImGui::Button("Sop")) s->stop(); - - auto speed = s->getSpeed(); - if (ImGui::SliderFloat("Speed", &speed, 0.5, 2)) s->setSpeed(speed); - - auto volume = s->getVolume(); - if (ImGui::SliderFloat("Volume", &volume, 0, 5)) s->setVolume(volume); - - auto loop = s->doesLoop(); - if (ImGui::Checkbox("Loop", &loop)) s->setLoop(loop); - - - if (ImGui::Button("Forget")) { - toRemove = s; - s->stop(); - } - - ImGui::PopID(); - - ImGui::Separator(); - } - - if (toRemove) sounds.erase(std::find(std::begin(sounds), std::end(sounds), toRemove)); - - ImGui::End(); - } -}; - -} // namespace widget - -} // namespace game diff --git a/src/Application/include/widgets/DebugCamera.hpp b/src/Application/include/widgets/DebugCamera.hpp deleted file mode 100644 index a12ee1f..0000000 --- a/src/Application/include/widgets/DebugCamera.hpp +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -#include - -#include "ThePURGE.hpp" - -namespace game { - -namespace widget { - -struct DebugCamera { - static auto draw(engine::Camera &cam) - { - ImGui::Begin("Camera"); - if (ImGui::Button("Reset")) cam = engine::Camera(); - - auto cameraPos = cam.getCenter(); - helper::ImGui::Text("Camera Position ({}, {})", cameraPos.x, cameraPos.y); - - bool pos_updated = false; - pos_updated |= ImGui::DragFloat("Camera X", &cameraPos.x); - pos_updated |= ImGui::DragFloat("Camera Y", &cameraPos.y); - - if (pos_updated) cam.setCenter(cameraPos); - - auto viewPortSize = cam.getViewportSize(); - const auto pos = cam.getCenter(); - - helper::ImGui::Text("Viewport size ({}, {})", viewPortSize.x, viewPortSize.y); - ImGui::Text("Viewport range :"); - helper::ImGui::Text(" left : {}", pos.x - viewPortSize.x / 2.0f); - helper::ImGui::Text(" right : {}", pos.x + viewPortSize.x / 2.0f); - helper::ImGui::Text(" top : {}", pos.y + viewPortSize.y / 2.0f); - helper::ImGui::Text(" bottom: {}", pos.y - viewPortSize.y / 2.0f); - - bool updated = false; - updated |= ImGui::DragFloat("Viewport width", &viewPortSize.x, 1.f, 2.f); - updated |= ImGui::DragFloat("Viewport height", &viewPortSize.y, 1.f, 2.f); - - if (updated) cam.setViewportSize(viewPortSize); - ImGui::End(); - } -}; - -} // namespace widget - -} // namespace game diff --git a/src/Application/include/widgets/Fonts.hpp b/src/Application/include/widgets/Fonts.hpp new file mode 100644 index 0000000..8781be9 --- /dev/null +++ b/src/Application/include/widgets/Fonts.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +namespace game { + +// All fonts can be static because they require no cleanup +struct Fonts { + static ImFont *imgui; + + static ImFont *kimberley_23; + static ImFont *kimberley_35; + static ImFont *kimberley_50; + static ImFont *kimberley_62; + static ImFont *opensans_44; + static ImFont *opensans_32; + + + // Must not be called between NewFrame() and EndFrame/Render() + static void loadFonts() noexcept; + +private: + static ImFont *load(const std::string &simplePath, float size) noexcept; +}; + +} diff --git a/src/Application/include/widgets/GameHUD.hpp b/src/Application/include/widgets/GameHUD.hpp new file mode 100644 index 0000000..5a2d315 --- /dev/null +++ b/src/Application/include/widgets/GameHUD.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include +#include "component/Health.hpp" +#include "component/Level.hpp" + +#include "ThePURGE.hpp" + +namespace game { + +struct GameHUD { + static void draw(ThePURGE &game, entt::registry &world); + +private: + static void drawHealthBar(const Health &health); + static void drawXpBar(const Level &level); + static void drawSpellCooldown(float spellX, float remaining); +}; + +} // namespace game diff --git a/src/Application/include/widgets/UserStatistics.hpp b/src/Application/include/widgets/UserStatistics.hpp deleted file mode 100644 index 2dbdee6..0000000 --- a/src/Application/include/widgets/UserStatistics.hpp +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once - -#include - -#include "ThePURGE.hpp" - -namespace game { - -struct UserStatistics { - static auto draw(ThePURGE &game, entt::registry &world) -> void - { - static GLuint texture = engine::DrawableFactory::createtexture("data/textures/InfoHud.png"); - - const auto infoHealth = world.get(game.player); - const auto HP = infoHealth.current / infoHealth.max; - // const auto Atk = world.get(game.player); - const auto level = world.get(game.player); - const auto XP = static_cast(level.current_xp) / static_cast(level.xp_require); - - ImGui::Begin( - "Info Player", - nullptr, - ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoResize - | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoMove); - ImVec2 size = ImGui::GetWindowSize(); - ImGui::Image(reinterpret_cast(static_cast((texture))), ImVec2(size.x - 30, size.y - 10)); - ImGui::SetCursorPos(ImVec2(ImGui::GetItemRectMin().x + 40, ImGui::GetItemRectMin().y + size.y / 7)); - ImGui::ProgressBar(HP, ImVec2(0.f, 0.f), fmt::format("{}/{}", infoHealth.current, infoHealth.max).data()); - ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); - ImGui::Text("HP"); - ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + 49, ImGui::GetCursorPosY())); - ImGui::ProgressBar(XP, ImVec2(0.0f, 0.0f)); - ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); - ImGui::Text("XP"); - ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + 49, ImGui::GetCursorPosY())); - helper::ImGui::Text("LEVEL {} ~~~ POINT {}", level.current_level, world.get(game.player).count); - // ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + 49, ImGui::GetCursorPosY())); - // ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + 49, ImGui::GetCursorPosY())); - // helper::ImGui::Text("Speed: {}", 1); - // ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + 49, ImGui::GetCursorPosY())); - // helper::ImGui::Text("Atk: {}", Atk.damage); - - for (const auto &i : world.get(game.player).spells) { - if (i.has_value()) { - ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + 49, ImGui::GetCursorPosY())); - ImGui::ProgressBar( - static_cast(i.value().cd.remaining_cooldown.count()) - / static_cast(i.value().cd.cooldown.count()), - ImVec2(0.f, 0.f), - fmt::format("{}/{}", i.value().cd.remaining_cooldown.count(), i.value().cd.cooldown.count()).data()); - } - } - if (world.get(game.player).hasKey) { - ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + 49, ImGui::GetCursorPosY())); - helper::ImGui::Text("You have the key"); - } - - ImGui::End(); - } -}; - -} // namespace game diff --git a/src/Application/include/widgets/DebugTerrainGeneration.hpp b/src/Application/include/widgets/debug/DebugTerrainGeneration.hpp similarity index 91% rename from src/Application/include/widgets/DebugTerrainGeneration.hpp rename to src/Application/include/widgets/debug/DebugTerrainGeneration.hpp index 7f37091..3d78ca0 100644 --- a/src/Application/include/widgets/DebugTerrainGeneration.hpp +++ b/src/Application/include/widgets/debug/DebugTerrainGeneration.hpp @@ -52,7 +52,9 @@ struct TerrainGeneration { 50); ImGui::Separator(); - ImGui::SliderFloat("Enemy per block", &game.logics()->m_map_generation_params.mobDensity, 0, 1); + for (const auto &[name, _] : game.dbEnemies().db) { + ImGui::SliderFloat("Enemy '{}' per block", &game.logics()->m_map_generation_params.mobDensity[name], 0, 1); + } ImGui::End(); } diff --git a/src/Application/include/widgets/console/ConsoleCommands.hpp b/src/Application/include/widgets/debug/console/ConsoleCommands.hpp similarity index 96% rename from src/Application/include/widgets/console/ConsoleCommands.hpp rename to src/Application/include/widgets/debug/console/ConsoleCommands.hpp index b56b1e9..dede029 100644 --- a/src/Application/include/widgets/console/ConsoleCommands.hpp +++ b/src/Application/include/widgets/debug/console/ConsoleCommands.hpp @@ -27,6 +27,7 @@ class CommandHandler { static handler_t cmd_setSpell; static handler_t cmd_addXp; static handler_t cmd_addLevel; + static handler_t cmd_addHealth; static handler_t cmd_setMusicVolume; static handler_t cmd_buyClass; static handler_t cmd_getClasses; diff --git a/src/Application/include/widgets/console/DebugConsole.hpp b/src/Application/include/widgets/debug/console/DebugConsole.hpp similarity index 100% rename from src/Application/include/widgets/console/DebugConsole.hpp rename to src/Application/include/widgets/debug/console/DebugConsole.hpp diff --git a/src/Application/include/widgets/helpers.hpp b/src/Application/include/widgets/helpers.hpp new file mode 100644 index 0000000..abe97dc --- /dev/null +++ b/src/Application/include/widgets/helpers.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +namespace game { + +struct GUITexture { + std::uint32_t id; + + // In a range [0; 1] relative to the window size + ImVec2 topleft; + ImVec2 size; +}; + +namespace helper { + +// path relative to the data directory +auto getTexture(const std::string &simplePath) -> std::uint32_t; +auto from1080p(float x, float y) noexcept -> ImVec2; + +auto frac2pixel(ImVec2 fraction) noexcept -> ImVec2; + +// topLeft and size in PIXELS +void drawTexture(std::uint32_t id, ImVec2 topLeft, ImVec2 size, ImVec4 tintColor = ImVec4(1, 1, 1, 1)) noexcept; + +void drawTexture(const GUITexture &t, ImVec4 tintColor = ImVec4(1, 1, 1, 1)) noexcept; + +void drawText(ImVec2 pos, const std::string &str, ImVec4 color = ImVec4(1, 1, 1, 1), ImFont *font = nullptr) noexcept; +void drawTextWrapped(ImVec2 pos, const std::string &str, float maxX, ImFont *font = nullptr) noexcept; + +} // namespace helper + +} // namespace game \ No newline at end of file diff --git a/src/Application/src/DataConfigLoader.cpp b/src/Application/src/DataConfigLoader.cpp deleted file mode 100644 index 424a43c..0000000 --- a/src/Application/src/DataConfigLoader.cpp +++ /dev/null @@ -1,83 +0,0 @@ -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "DataConfigLoader.hpp" -#include "component/all.hpp" -#include "factory/EntityFactory.hpp" - -using namespace std::chrono_literals; - -auto game::DataConfigLoader::loadClassDatabase(const std::string_view path) -> classes::Database -{ - spdlog::info("Loading class database file: '{}'", path.data()); - - std::ifstream file(path.data()); - - if (!file.is_open()) { spdlog::error("Can't open the given file"); } - nlohmann::json jsonData = nlohmann::json::parse(file); - - classes::Database database; - - for (const auto &[name, data] : jsonData.items()) { - std::vector spells; - // note : see std::transform - for (const auto &spell : data["spells"]) { spells.push_back(static_cast(spell.get())); } - - const auto currentID = EntityFactory::ID_from_string(name); - - database[currentID] = Class{ - .id = currentID, - .name = name, - .description = data["desc"].get(), - .iconPath = data["icon"], - .assetGraphPath = data["assetGraph"], - .is_starter = data.value("starter", false), - .spells = spells, - .maxHealth = data["maxHealth"].get(), - .damage = data["damage"].get(), - .children = {}, - }; - } - - for (auto &[id, dbClass] : database) { - for (const auto &child : jsonData[dbClass.name]["children"]) { - if (const auto c = classes::getByName(database, child.get()); c) { - dbClass.children.push_back(c->id); - } else - UNLIKELY { spdlog::warn("Unknown class '{}'. Ignoring", child); } - } - } - - for (const auto &[id, classes] : database) { - spdlog::info("[{}]", id); - - spdlog::info( - "id={} name={} description={} iconPath={} assetGraphPath={} is_starter={} spells={} maxHealth={} damage={} " - "children={}", - classes.id, - classes.name, - classes.description, - classes.iconPath, - classes.assetGraphPath, - classes.is_starter, - "",//classes.spells, - classes.maxHealth, - classes.damage, - ""//,classes.children - ); - } - - return database; -} diff --git a/src/Application/src/GameLogic.cpp b/src/Application/src/GameLogic.cpp index 39d74f0..09d73a3 100644 --- a/src/Application/src/GameLogic.cpp +++ b/src/Application/src/GameLogic.cpp @@ -6,206 +6,260 @@ #include #include #include -#include +#include #include #include + +#include "models/Spell.hpp" +#include "models/Class.hpp" + #include "GameLogic.hpp" -#include "screen/MainMenu.hpp" #include "ThePURGE.hpp" #include "factory/EntityFactory.hpp" #include "factory/SpellFactory.hpp" #include "factory/ParticuleFactory.hpp" -#include "models/ClassDatabase.hpp" +#include "component/all.hpp" + +#include "menu/UpgradePanel.hpp" +#include "menu/GameOver.hpp" using namespace std::chrono_literals; game::GameLogic::GameLogic(ThePURGE &game) : m_game{game}, m_nextFloorSeed(static_cast(std::time(nullptr))) { - sinkMovement.connect<&GameLogic::move>(*this); - // sinkJoystickMovement.connect<&GameLogic::joystickMove>(*this); + sinkMovement.connect<&GameLogic::slots_move>(*this); - sinkOnGameStarted.connect<&GameLogic::on_game_started>(*this); + sinkOnGameStarted.connect<&GameLogic::slots_game_start>(*this); - sinkOnPlayerBuyClass.connect<&GameLogic::apply_class_to_player>(*this); - sinkOnPlayerBuyClass.connect<&GameLogic::on_class_bought>(*this); - sinkOnPlayerLevelUp.connect<&GameLogic::on_player_level_up>(*this); + sinkOnPlayerBuyClass.connect<&GameLogic::slots_apply_classes>(*this); + sinkOnPlayerLevelUp.connect<&GameLogic::slots_level_up>(*this); - sinkGameUpdated.connect<&GameLogic::player_movement_update>(*this); - sinkGameUpdated.connect<&GameLogic::ai_pursue>(*this); - sinkGameUpdated.connect<&GameLogic::cooldown>(*this); - sinkGameUpdated.connect<&GameLogic::effect>(*this); - sinkGameUpdated.connect<&GameLogic::enemies_try_attack>(*this); - sinkGameUpdated.connect<&GameLogic::update_lifetime>(*this); - sinkGameUpdated.connect<&GameLogic::update_particule>(*this); - sinkGameUpdated.connect<&GameLogic::check_collision>(*this); - sinkGameUpdated.connect<&GameLogic::exit_door_interraction>(*this); - sinkGameUpdated.connect<&GameLogic::player_anim_update>(*this); - sinkGameUpdated.connect<&GameLogic::boss_anim_update>(*this); + sinkOnEvent.connect<&GameLogic::slots_on_event>(*this); - sinkAfterGameUpdated.connect<&GameLogic::update_camera>(*this); + sinkGameUpdated.connect<&GameLogic::slots_update_game_time>(*this); + sinkGameUpdated.connect<&GameLogic::slots_update_player_movement>(*this); + sinkGameUpdated.connect<&GameLogic::slots_update_ai_movement>(*this); + sinkGameUpdated.connect<&GameLogic::slots_update_ai_attack>(*this); + sinkGameUpdated.connect<&GameLogic::slots_update_particle>(*this); + sinkGameUpdated.connect<&GameLogic::slots_update_cooldown>(*this); + sinkGameUpdated.connect<&GameLogic::slots_update_effect>(*this); + sinkGameUpdated.connect<&GameLogic::slots_check_collision>(*this); + sinkGameUpdated.connect<&GameLogic::slots_check_floor_change>(*this); + sinkGameUpdated.connect<&GameLogic::slots_check_animation_attack_status>(*this); - sinkCastSpell.connect<&GameLogic::cast_attack>(*this); + sinkGameUpdated.connect<&GameLogic::slots_update_animation_spritesheet>(*this); - sinkGetKilled.connect<&GameLogic::entity_killed>(*this); - sinkOnFloorChange.connect<&GameLogic::goToTheNextFloor>(*this); -} + sinkAfterGameUpdated.connect<&GameLogic::slots_update_camera>(*this); + sinkAfterGameUpdated.connect<&GameLogic::slots_update_player_sigh>(*this); -auto game::GameLogic::move([[maybe_unused]] entt::registry &world, entt::entity &player, const Direction &dir, bool is_pressed) -> void -{ - switch (dir) { - case Direction::UP: world.get(player).movement.y = is_pressed ? -1.0f : 0.0f; break; - case Direction::DOWN: world.get(player).movement.y = is_pressed ? 1.0f : 0.0f; break; - case Direction::RIGHT: world.get(player).movement.x = is_pressed ? 1.0f : 0.0f; break; - case Direction::LEFT: world.get(player).movement.x = is_pressed ? -1.0f : 0.0f; break; - default: break; - } -} + sinkCastSpell.connect<&GameLogic::slots_cast_spell>(*this); + sinkCollideSpell.connect<&GameLogic::slots_collide_with_spell>(*this); -auto game::GameLogic::joystickMove(entt::registry &world, entt::entity &player, const engine::d2::Acceleration &accel) -> void -{ - world.get(player) = accel; + sinkGetKilled.connect<&GameLogic::slots_kill_entity>(*this); + sinkDamageTaken.connect<&GameLogic::slots_damage_taken>(*this); + + sinkOnFloorChange.connect<&GameLogic::slots_change_floor>(*this); } -auto game::GameLogic::on_game_started(entt::registry &world) -> void +auto game::GameLogic::slots_game_start(entt::registry &world) -> void { static auto holder = engine::Core::Holder{}; + m_gameTime = 0; + + // pos and size based of `FloorGenParam::maxDungeonWidth / Height` + // EntityFactory::create(m_game, world, glm::vec2(25, 25), glm::vec2(75, 75)); + { + const auto e = world.create(); + world.emplace( + e, 25.0, 25.0, EntityFactory::get_z_layer()); + world.emplace(e, 0.f); + world.emplace(e, 75.0, 75.0); + world.emplace(e, engine::DrawableFactory::rectangle()); + engine::DrawableFactory::fix_color(world, e, {0.15, 0.15, 0.15, 1}); + engine::DrawableFactory::fix_texture(world, e, holder.instance->settings().data_folder + "img/background.jpg", true); + } + holder.instance->getAudioManager() .getSound(holder.instance->settings().data_folder + "sounds/entrance_gong.wav") ->setVolume(0.2f) .play(); - m_game.getMusic()->play(); + m_game.setBackgroundMusic("sounds/dungeon_music.wav", 0.1f); + + + m_game.player = EntityFactory::create(m_game, world, {}, {}); + + const auto &starterClass = m_game.dbClasses().getStarterClass(); + slots_apply_classes(world, m_game.player, starterClass); + for (auto i = 0ul; const auto &spell : starterClass.spells) + world.get(m_game.player).spells[i++] = m_game.dbSpells().instantiate(spell); + + auto aimingSight = EntityFactory::create(m_game, world, {}, {}); - m_game.player = EntityFactory::create(world, {}, {}); - apply_class_to_player(world, m_game.player, classes::getStarterClass(m_game.getClassDatabase())); + engine::DrawableFactory::fix_color(world, aimingSight, {1.f, 0.2f, 0.2f, 0.8f}); + world.get(m_game.player).entity = aimingSight; // default camera value to see the generated terrain properly m_game.getCamera().setCenter(glm::vec2(13, 22)); m_game.getCamera().setViewportSize(glm::vec2(25, 17)); onFloorChange.publish(world); + + spdlog::info("Game is started ! Ready to play !"); } -auto game::GameLogic::apply_class_to_player(entt::registry &world, entt::entity player, const Class &newClass) -> void +auto game::GameLogic::slots_on_event(entt::registry &world, const engine::Event &e) -> void { static auto holder = engine::Core::Holder{}; - world.get(player).damage = newClass.damage; - - auto &health = world.get(player); - - health.current += newClass.maxHealth - health.max; - health.max = newClass.maxHealth; - world.get(player).ids.push_back(newClass.id); - - - // TODO: actual spell selection ? - - for (const auto &spell : newClass.spells) - for (auto &slot : world.get(player).spells) { - if (slot.has_value()) continue; - - slot = Spell::create(spell); - break; + const auto spell_map = [](T k) { + struct SpellMap { + int key; + std::size_t id; + }; + if constexpr (std::is_same::value) { + const auto map = std::to_array({{engine::Joystick::LS, 0}, {engine::Joystick::RS, 1}}); + return std::find_if(map.begin(), map.end(), [&k](auto &i) { return i.key == k; })->id; + + } else if constexpr (std::is_same::value) { + const auto map = std::to_array({{engine::Joystick::LST, 2}, {engine::Joystick::RST, 3}}); + return std::find_if(map.begin(), map.end(), [&k](auto &i) { return i.key == k; })->id; + + } else { // todo : should be engine::Keyboard::Key + const auto map = std::to_array( + {{GLFW_KEY_J, 0}, + {GLFW_KEY_1, 0}, + {GLFW_KEY_2, 2}, + {GLFW_KEY_K, 2}, + {GLFW_KEY_3, 3}, + {GLFW_KEY_L, 3}, + {GLFW_KEY_M, 1}, + {GLFW_KEY_4, 1}, + {GLFW_KEY_SEMICOLON, 1}}); + return std::find_if(map.begin(), map.end(), [&k](auto &i) { return i.key == k; })->id; } - - auto &sp = world.replace( - player, engine::Spritesheet::from_json(holder.instance->settings().data_folder + newClass.assetGraphPath)); - - // Doesn't really matter, will be overridden by correct one soon enough. Prevent segfault of accessing inexistant "default" animation - sp.current_animation = "hold_right"; - - engine::DrawableFactory::fix_texture(world, player, holder.instance->settings().data_folder + sp.file); - - - { // Logging - std::stringstream spellsId; - for (const auto &spell : newClass.spells) spellsId << spell << ", "; - - std::stringstream childrens; - for (const auto &child : newClass.children) childrens << m_game.getClassDatabase().at(child).name << ", "; - - spdlog::info( - "Applied class '{}' to player. Stats are now : \n" - "\tDamage : {:.3}\n" - "\tMax health : {:.3}\n" - "\tAdded spells {}\n" - "\tNew available classes : {}", - newClass.name, - newClass.damage, - newClass.maxHealth, - spellsId.str(), - childrens.str()); - } -} - -auto game::GameLogic::on_class_bought(entt::registry &world, entt::entity player, const Class &) -> void -{ - world.get(player).count--; -} - -auto game::GameLogic::on_player_level_up(entt::registry &world, entt::entity player) -> void -{ - world.get(player).count++; -} - -auto game::GameLogic::player_movement_update(entt::registry &world, [[maybe_unused]] const engine::TimeElapsed &dt) -> void -{ - constexpr float kSpeed = 10; + }; auto player = m_game.player; - auto &vel = world.get(player); - const auto &axis = world.get(player); - - vel.x = axis.movement.x * kSpeed; - vel.y = -axis.movement.y * kSpeed; + std::visit( + engine::overloaded{ + [&](const engine::Pressed &key) { + switch (key.source.key) { + case GLFW_KEY_DOWN: + case GLFW_KEY_S: onMovement.publish(world, player, Direction::DOWN, true); break; + case GLFW_KEY_RIGHT: + case GLFW_KEY_D: onMovement.publish(world, player, Direction::RIGHT, true); break; + case GLFW_KEY_LEFT: + case GLFW_KEY_Q: + case GLFW_KEY_A: onMovement.publish(world, player, Direction::LEFT, true); break; + case GLFW_KEY_UP: + case GLFW_KEY_Z: + case GLFW_KEY_W: onMovement.publish(world, player, Direction::UP, true); break; + case GLFW_KEY_P: { + m_game.setMenu(std::make_unique()); + } break; + + case GLFW_KEY_SEMICOLON: + case GLFW_KEY_1: + case GLFW_KEY_2: + case GLFW_KEY_3: + case GLFW_KEY_4: + case GLFW_KEY_J: + case GLFW_KEY_K: + case GLFW_KEY_L: + case GLFW_KEY_M: { + const auto id = spell_map(key.source.key); + + auto &spell = world.get(player).spells[id]; + if (!spell.has_value()) break; + + auto &aim = world.get(player).dir; + onSpellCast.publish(world, player, aim, spell.value()); + } break; + default: return; + } + }, + [&](const engine::Released &key) { + switch (key.source.key) { + case GLFW_KEY_DOWN: + case GLFW_KEY_S: onMovement.publish(world, player, Direction::DOWN, false); break; + case GLFW_KEY_RIGHT: + case GLFW_KEY_D: onMovement.publish(world, player, Direction::RIGHT, false); break; + case GLFW_KEY_LEFT: + case GLFW_KEY_Q: + case GLFW_KEY_A: onMovement.publish(world, player, Direction::LEFT, false); break; + case GLFW_KEY_UP: + case GLFW_KEY_Z: + case GLFW_KEY_W: onMovement.publish(world, player, Direction::UP, false); break; + default: return; + } + }, + [&](const engine::TimeElapsed &dt) { + onGameUpdate.publish(world, dt); + onGameUpdateAfter.publish(world, dt); + }, + [&](const engine::Moved &joy) { + switch (joy.source.axis) { + case engine::Joystick::LST: + case engine::Joystick::RST: { + const auto id = spell_map(joy.source.axis); + auto &spell = world.get(player).spells[id]; + if (!spell.has_value()) break; + auto &aim = world.get(player).dir; + onSpellCast.publish(world, player, aim, spell.value()); + } break; + case engine::Joystick::LSX: + case engine::Joystick::LSY: { + auto joystick = holder.instance->getJoystick(joy.source.id); + + const auto newVal = + glm::vec2{(*joystick)->axes[engine::Joystick::LSX], -(*joystick)->axes[engine::Joystick::LSY]}; + world.get(player).movement = + glm::length(newVal) > ControllerAxis::kDeadzone ? newVal : glm::vec2{0, 0}; + + } break; + case engine::Joystick::RSX: + case engine::Joystick::RSY: { + auto joystick = holder.instance->getJoystick(joy.source.id); + + const auto newVal = + glm::vec2{(*joystick)->axes[engine::Joystick::RSX], -(*joystick)->axes[engine::Joystick::RSY]}; + world.get(player).aiming = + glm::length(newVal) > ControllerAxis::kDeadzone ? newVal : glm::vec2{0, 0}; + + } break; + default: break; + } + }, + [&](const engine::Pressed &joy) { + switch (joy.source.button) { + case engine::Joystick::CENTER2: m_game.setMenu(std::make_unique()); break; + case engine::Joystick::LS: + case engine::Joystick::RS: { + const auto id = spell_map(joy.source.button); + auto &spell = world.get(player).spells[id]; + if (!spell.has_value()) break; + auto &aim = world.get(player).dir; + onSpellCast.publish(world, player, aim, spell.value()); + } break; + default: return; + } + }, + [&](auto) {}, + }, + e); } -auto game::GameLogic::ai_pursue(entt::registry &world, [[maybe_unused]] const engine::TimeElapsed &dt) -> void +auto game::GameLogic::slots_update_game_time(entt::registry &, [[maybe_unused]] const engine::TimeElapsed &dt) -> void { - const auto pursue = [&world](entt::entity entity, entt::entity target, engine::d2::Velocity &out) { - const auto &pos = world.get(entity); - const auto &view_range = world.get(entity); - - const auto &target_pos = world.get(target); - const auto diff = glm::vec2{target_pos.x - pos.x, target_pos.y - pos.y}; - - if (glm::length(diff) > view_range.range) return false; - - for (float i = 0.0f; i != 10.0f; i++) { - const auto in_between = - glm::vec2{static_cast(pos.x) + i * diff.x / 10.0f, static_cast(pos.y) + i * diff.y / 10.0f}; - for (const auto &wall : world.view>()) { - const auto &wall_pos = world.get(wall); - const auto &wall_hitbox = world.get(wall); - - if (engine::d2::overlapped( - engine::d2::HitboxSolid{0.01, 0.01}, - engine::d3::Position{in_between.x, in_between.y, 0.0f}, - wall_hitbox, - wall_pos)) { - return false; - } - } - } - - auto result = glm::normalize(diff)* 7.f; - out = {result.x, result.y} ; - - return true; - }; - - for (auto &i : world.view, engine::d3::Position, engine::d2::Velocity, game::ViewRange>()) { - auto &vel = world.get(i); - pursue(i, m_game.player, vel); - } + m_gameTime += static_cast(dt.elapsed.count()) / 1e9; } -auto game::GameLogic::cooldown(entt::registry &world, const engine::TimeElapsed &dt) -> void +auto game::GameLogic::slots_update_cooldown(entt::registry &world, const engine::TimeElapsed &dt) -> void { const auto elapsed = std::chrono::duration_cast(dt.elapsed).count(); @@ -220,148 +274,13 @@ auto game::GameLogic::cooldown(entt::registry &world, const engine::TimeElapsed } else { cd.remaining_cooldown = 0ms; cd.is_in_cooldown = false; - spdlog::warn("attack is up !"); } } }); } -auto game::GameLogic::effect(entt::registry &world, const engine::TimeElapsed &dt) -> void -{ - auto player_health = world.get(m_game.player); - - world.view().each([&](auto &effect) { - if (!effect.is_in_effect) return; - if (dt.elapsed < effect.remaining_time_effect) { - effect.remaining_time_effect -= std::chrono::duration_cast(dt.elapsed); - if (effect.effect_name == "stun") spdlog::warn("stun"); - if (effect.effect_name == "bleed") { - /* (1 * (dt * 0.001)) true calcul but didn't found how do this calcul each sec to do it yet so TODO*/ - player_health.current -= 0.01f; - } - } else { - effect.is_in_effect = false; - } - }); -} - -auto game::GameLogic::enemies_try_attack(entt::registry &world, [[maybe_unused]] const engine::TimeElapsed &dt) -> void -{ - for (auto enemy : world.view, engine::d3::Position, AttackRange>()) { - // TODO: Add brain to AI. current strategy : spam every spell towards the player - - for (auto &spell : world.get(enemy).spells) { - if (!spell.has_value()) continue; - - const auto &selfPosition = world.get(enemy); - const auto &targetPosition = world.get(m_game.player); - - const glm::vec2 diff = {targetPosition.x - selfPosition.x, targetPosition.y - selfPosition.y}; - - auto &attack_range = world.get(enemy); - - if (glm::length(diff) <= attack_range.range) { - castSpell.publish(world, enemy, {diff.x, diff.y}, spell.value()); - } - } - } -} - -auto game::GameLogic::check_collision(entt::registry &world, [[maybe_unused]] const engine::TimeElapsed &dt) -> void -{ - static auto holder = engine::Core::Holder{}; - - for (auto &spell : world.view>()) { - const auto &spell_pos = world.get(spell); - const auto &spell_box = world.get(spell); - - for (auto &wall : world.view>()) { - const auto &wall_pos = world.get(wall); - const auto &wall_box = world.get(wall); - - if (engine::d2::overlapped(spell_box, spell_pos, wall_box, wall_pos)) { - world.destroy(spell); - break; - } - } - } - - const auto apply_damage = [this, &world](auto &entity, auto &spell, auto &spell_hitbox, auto &spell_pos, auto &source) { - auto &entity_pos = world.get(entity); - auto &entity_hitbox = world.get(entity); - - if (engine::d2::overlapped(entity_hitbox, entity_pos, spell_hitbox, spell_pos)) { - auto &entity_health = world.get(entity); - auto &spell_damage = world.get(spell); - - // todo : publish player_took_damage instead - entity_health.current -= spell_damage.damage; - spdlog::warn("player took damage"); - - if (world.has>(entity)) { - holder.instance->setScreenshake(true, 300ms); - ParticuleFactory::create( - world, {(spell_pos.x + entity_pos.x) / 2.0, (entity_pos.y + spell_pos.y) / 2.0}); - } - - holder.instance->getAudioManager() - .getSound(holder.instance->settings().data_folder + "sounds/fire_hit.wav") - ->play(); - world.destroy(spell); - if (entity_health.current <= 0.0f) { onEntityKilled.publish(world, entity, source); } - } - }; - - for (auto &spell : world.view>()) { - const auto &source = world.get(spell).source; - if (!world.valid(source)) return; - - auto &spell_pos = world.get(spell); - const auto &hitbox = world.get(spell); - - if (world.has>(source)) { - for (auto &enemy : world.view>()) { - if (world.valid(spell)) apply_damage(enemy, spell, hitbox, spell_pos, source); - } - } else { - for (auto &player : world.view>()) { - if (world.valid(spell)) apply_damage(player, spell, hitbox, spell_pos, source); - } - } - } - - world.view().each( - [&](KeyPicker &keypicker, const engine::d2::HitboxSolid &pickerhitbox, const engine::d3::Position &pickerPos) { - if (keypicker.hasKey) return; - - for (auto &key : world.view>()) { - auto &keyHitbox = world.get(key); - auto &keyPos = world.get(key); - - if (engine::d2::overlapped(pickerhitbox, pickerPos, keyHitbox, keyPos)) { - keypicker.hasKey = true; - - world.destroy(key); - } - } - }); -} - -// note : this should be in Core -auto game::GameLogic::update_lifetime(entt::registry &world, const engine::TimeElapsed &dt) -> void -{ - for (auto &i : world.view()) { - auto &lifetime = world.get(i); - - if (dt.elapsed < lifetime.remaining_lifetime) { - lifetime.remaining_lifetime -= std::chrono::duration_cast(dt.elapsed); - } else { - world.destroy(i); - } - } -} - -auto game::GameLogic::update_particule(entt::registry &world, const engine::TimeElapsed &dt) -> void +auto game::GameLogic::slots_update_particle( + [[maybe_unused]] entt::registry &world, [[maybe_unused]] const engine::TimeElapsed &dt) -> void { const auto elapsed = std::chrono::duration_cast(dt.elapsed).count(); for (const auto &i : world.view()) { @@ -371,200 +290,147 @@ auto game::GameLogic::update_particule(entt::registry &world, const engine::Time const auto r = engine::Color::r(color); const auto g = std::clamp(engine::Color::g(color) + 0.0001f * static_cast(elapsed), 0.0f, 1.0f); const auto b = std::clamp(engine::Color::b(color) + 0.0001f * static_cast(elapsed), 0.0f, 1.0f); - engine::DrawableFactory::fix_color(world, i, {r, g, b}); + const auto a = engine::Color::a(color); + engine::DrawableFactory::fix_color(world, i, {r, g, b, a}); auto &vel = world.get(i); vel.x += ((std::rand() & 1) ? -1 : 1) * 0.005 * static_cast(elapsed); vel.y += ((std::rand() & 1) ? -1 : 1) * 0.005 * static_cast(elapsed); } break; + case Particule::POSITIVE: { + auto &color = world.get(i); + const auto r = engine::Color::r(color); + const auto g = std::clamp(engine::Color::g(color) + 0.0001f * static_cast(elapsed), 0.0f, 1.0f); + const auto b = std::clamp(engine::Color::b(color) + 0.0001f * static_cast(elapsed), 0.0f, 1.0f); + const auto a = engine::Color::a(color); + engine::DrawableFactory::fix_color(world, i, {r, g, b, a}); + + auto &vel = world.get(i); + vel.x -= ((std::rand() & 1) ? -1 : 1) * 0.005 * static_cast(elapsed); + vel.y -= ((std::rand() & 1) ? -1 : 1) * 0.005 * static_cast(elapsed); + } break; default: break; } } } -auto game::GameLogic::exit_door_interraction(entt::registry &world, const engine::TimeElapsed &) -> void +auto game::GameLogic::slots_check_floor_change(entt::registry &world, const engine::TimeElapsed &) -> void { - const auto &door = world.view>()[0]; - const auto &doorPosition = world.get(door); - - const auto doorUsageSystem = [&](const KeyPicker &keypicker, const engine::d3::Position &playerPos) { - if (!keypicker.hasKey) return; + world.view().each( + [door = world.view>().front(), &world, this](const auto &picker, const auto &pos) { + if (!picker.hasKey) return; - if (engine::d3::distance(doorPosition, playerPos) < kDoorInteractionRange) onFloorChange.publish(world); - }; - - world.view().each(doorUsageSystem); + constexpr auto kDoorInteractionRange = 2.0; + if (engine::d3::distance(world.get(door), pos) < kDoorInteractionRange) + onFloorChange.publish(world); + }); } -auto game::GameLogic::player_anim_update(entt::registry &world, const engine::TimeElapsed &) -> void +auto game::GameLogic::slots_check_animation_attack_status(entt::registry &world, const engine::TimeElapsed &) -> void { - const auto &vel = world.get(m_game.player); - const auto &facing = world.get(m_game.player); - auto &sp = world.get(m_game.player); - - bool isFacingLeft; - if (vel.x < 0) - isFacingLeft = true; - else if (vel.x > 0) - isFacingLeft = false; - else if (facing.dir.x < 0) - isFacingLeft = true; - else - isFacingLeft = false; - - std::string anim; - - auto isMoving = vel.x != 0 || vel.y != 0; - - if (isMoving) - if (isFacingLeft) - anim = "run_left"; - else - anim = "run_right"; - else if (isFacingLeft) - anim = "hold_left"; - else - anim = "hold_right"; - - if (sp.current_animation != anim) { - sp.current_animation = anim; - sp.current_frame = 0; - } + world.view(entt::exclude>).each([&](engine::Spritesheet &sprite) { + if (sprite.current_animation != "attack_left" && sprite.current_animation != "attack_right") return; + auto size = sprite.animations.at(sprite.current_animation).frames.size(); + if (sprite.current_frame == (size - 1)) sprite.attack_animation_finish = true; + }); } -auto game::GameLogic::update_camera(entt::registry &world, const engine::TimeElapsed &) -> void +auto game::GameLogic::slots_update_camera(entt::registry &world, const engine::TimeElapsed &) -> void { - static auto holder = engine::Core::Holder{}; - auto player = m_game.player; auto &pos = world.get(player); m_game.getCamera().setCenter({pos.x, pos.y}); - if (m_game.getCamera().isUpdated()) holder.instance->updateView(m_game.getCamera().getViewProjMatrix()); + if (m_game.getCamera().isUpdated()) { + engine::Core::Holder{}.instance->updateView(m_game.getCamera().getViewProjMatrix()); + } } -auto game::GameLogic::boss_anim_update(entt::registry &world, const engine::TimeElapsed &) -> void +// this function will try to update the spritesheet ate everyframe !!! BAD BAD BAD +auto game::GameLogic::slots_update_animation_spritesheet(entt::registry &world, const engine::TimeElapsed &) -> void { - // we keep it as static to be consister with previous frame on standstill - static bool isFacingLeft = false; - - auto queryRes = world.view>(); - - if (queryRes.size() == 0) // boss is dead - return; + for (const auto &i : world.view(entt::exclude>)) { + const auto &vel = world.get(i); + const auto &sp = world.get(i); - auto boss = queryRes.front(); + if ((world.has(i) && sp.current_animation == "death") || sp.attack_animation_finish == false) + continue; - const auto &vel = world.get(boss); - auto &sp = world.get(boss); - - isFacingLeft = vel.x < 0 || (isFacingLeft && vel.x == 0); - - std::string anim; + const auto aiming = [](entt::registry &w, const entt::entity &e) -> std::optional { + if (w.has(e)) { + return w.get(e).dir; + } else { + return {}; + } + }(world, i); + + const auto isFacingLeft = [](const auto &v, const auto &a) { + if (v.x < 0) + return true; + else if (v.x > 0) + return false; + else if (a.has_value() && a.value().x < 0) + return true; + else + return false; + }(vel, aiming); - auto isMoving = vel.x != 0 || vel.y != 0; + const auto isMoving = vel.x || vel.y; - if (isMoving) - if (isFacingLeft) - anim = "run_left"; - else - anim = "run_right"; - else if (isFacingLeft) - anim = "hold_left"; - else - anim = "hold_right"; + // all the entity will be looking on the right by default because they don t have a AimingDirection BAD BAD BAD + const auto animation = fmt::format("{}_{}", isMoving ? "run" : "idle", isFacingLeft ? "left" : "right"); - if (sp.current_animation != anim) { - spdlog::info("Changing anim from {} to {}. Vel is {}, {}", sp.current_animation, anim, vel.x, vel.y); - sp.current_animation = anim; - sp.current_frame = 0; + if (sp.current_animation != animation) { engine::DrawableFactory::fix_spritesheet(world, i, animation); } } } -auto game::GameLogic::entity_killed(entt::registry &world, entt::entity killed, entt::entity killer) -> void +auto game::GameLogic::slots_update_player_sigh(entt::registry &world, const engine::TimeElapsed &) -> void { - static auto holder = engine::Core::Holder{}; - - if (world.has>(killed)) { - holder.instance->getAudioManager() - .getSound(holder.instance->settings().data_folder + "sounds/player_death.wav") - ->play(); - - m_game.setState(ThePURGE::State::GAME_OVER); - } else if (world.has>(killed)) { - spdlog::warn("!! entity killed : dropping xp !!"); - - // TODO: actual random utilities - bool lazyDevCoinflip = static_cast(killed) % 2; - holder.instance->getAudioManager() - .getSound( - lazyDevCoinflip ? holder.instance->settings().data_folder + "sounds/death_01.wav" - : holder.instance->settings().data_folder + "sounds/death_02.wav") - ->play(); - - if (world.has>(killer)) { - // todo : move xp dropped as component or something - if (world.has>(killed)) - addXp(world, killer, 5); - else - addXp(world, killer, 1); - } + constexpr auto kSightMaxDistance = 1.5; + constexpr auto kSightScaleMultiplier = 0.75; - if (world.has>(killed)) { - auto pos = world.get(killed); - EntityFactory::create(world, {pos.x, pos.y}, {1.0, 1.0}); - holder.instance->getAudioManager() - .getSound(holder.instance->settings().data_folder + "sounds/boss_death.wav") - ->play(); - } - world.destroy(killed); - } -} + auto player = m_game.player; + auto sight = world.get(player).entity; + const auto &aimInput = world.get(player).aiming; + if (glm::length(aimInput)) { + auto &aim = world.get(player).dir; + auto newAim = glm::normalize(aimInput); -// todo : normalize direction -auto game::GameLogic::cast_attack(entt::registry &world, entt::entity caster, const glm::dvec2 &direction, Spell &spell) - -> void -{ - if (!spell.cd.is_in_cooldown) { - SpellFactory::create(spell.id, world, caster, glm::normalize(direction)); - spell.cd.remaining_cooldown = spell.cd.cooldown; - spell.cd.is_in_cooldown = true; + aim.x = newAim.x; + aim.y = newAim.y; } -} -auto game::GameLogic::goToTheNextFloor(entt::registry &world) -> void -{ - world.view>().each([&](auto &e) { world.destroy(e); }); - world.view>().each([&](auto &e) { world.destroy(e); }); - world.view>().each([&](auto &e) { world.destroy(e); }); - world.view>().each([&](auto &e) { world.destroy(e); }); - world.view().each([&](KeyPicker &kp) { kp.hasKey = false; }); - - auto data = generateFloor(world, m_map_generation_params, m_nextFloorSeed); - m_nextFloorSeed = data.nextFloorSeed; + const auto &playerPos = world.get(player); + auto &sightPos = world.get(sight); + auto &sightScale = world.get(sight); - auto allPlayers = world.view>(); + sightPos.x = playerPos.x + kSightMaxDistance * static_cast(aimInput.x); + sightPos.y = playerPos.y + kSightMaxDistance * static_cast(aimInput.y); - for (auto &player : allPlayers) { - auto &pos = world.get(player); - - pos.x = data.spawn.x + data.spawn.w * 0.5; - pos.y = data.spawn.y + data.spawn.h * 0.5; - } + const auto scale = static_cast(glm::length(aimInput)); + sightScale.x = scale * kSightScaleMultiplier; + sightScale.y = scale * kSightScaleMultiplier; } -auto game::GameLogic::addXp(entt::registry &world, entt::entity player, std::uint32_t xp) -> void +auto game::GameLogic::slots_change_floor(entt::registry &world) -> void { - auto &level = world.get(player); + Stage{}.clear(world, false); - level.current_xp += xp; + spdlog::info("Creating the terrain..."); + + // keep the stage instance somewhere + const auto data = Stage{}.generate(m_game, world, m_map_generation_params, m_nextFloorSeed); + m_nextFloorSeed = data.nextFloorSeed; - while (level.current_xp >= level.xp_require) { - level.current_xp -= level.xp_require; - level.xp_require = static_cast(std::ceil(level.xp_require * 1.2)); - level.current_level++; + spdlog::info("Terrain generation done, spawning players..."); - onPlayerLevelUp.publish(world, player); + for (const auto &player : world.view>()) { + world.emplace_or_replace( + player, + engine::d3::Position{ + data.spawn.x + data.spawn.w * 0.5, + data.spawn.y + data.spawn.h * 0.5, + EntityFactory::get_z_layer()}); } } diff --git a/src/Application/src/GameLogic/collision.cpp b/src/Application/src/GameLogic/collision.cpp new file mode 100644 index 0000000..8563625 --- /dev/null +++ b/src/Application/src/GameLogic/collision.cpp @@ -0,0 +1,174 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "models/Spell.hpp" +#include "models/Class.hpp" + +#include "GameLogic.hpp" +#include "ThePURGE.hpp" +#include "factory/EntityFactory.hpp" +#include "factory/SpellFactory.hpp" +#include "factory/ParticuleFactory.hpp" + +#include "component/all.hpp" + +#include "menu/UpgradePanel.hpp" +#include "menu/GameOver.hpp" + +using namespace std::chrono_literals; + +auto game::GameLogic::slots_move([[maybe_unused]] entt::registry &world, entt::entity &player, const Direction &dir, bool is_pressed) + -> void +{ + auto &movement = world.get(player).keyboard_movement; + + switch (dir) { + case Direction::UP: movement.y += is_pressed ? 1 : -1; break; + case Direction::DOWN: movement.y += is_pressed ? -1 : 1; break; + case Direction::RIGHT: movement.x += is_pressed ? 1 : -1; break; + case Direction::LEFT: movement.x += is_pressed ? -1 : 1; break; + default: break; + } + + if (glm::length(movement) < 0.01f) { + world.get(player).movement = glm::vec2(0.f, 0.f); + } else { + const auto &direction = glm::normalize(movement); + + world.get(player).aiming = direction; + world.get(player).movement = direction; + } +} + +auto game::GameLogic::slots_update_player_movement(entt::registry &world, [[maybe_unused]] const engine::TimeElapsed &dt) + -> void +{ + auto player = m_game.player; + + auto &vel = world.get(player); + const auto &axis = world.get(player); + const auto &spd = world.get(player).speed; + + vel.x = axis.movement.x * spd; + vel.y = axis.movement.y * spd; +} + +auto game::GameLogic::slots_update_ai_movement(entt::registry &world, [[maybe_unused]] const engine::TimeElapsed &dt) -> void +{ + const auto pursue = [&world](entt::entity entity, entt::entity target, engine::d2::Velocity &out) { + const auto &pos = world.get(entity); + const auto &view_range = world.get(entity); + + const auto &target_pos = world.get(target); + const auto diff = glm::vec2{target_pos.x - pos.x, target_pos.y - pos.y}; + + if (glm::length(diff) > view_range.range) return false; + + for (float i = 0.0f; i != 10.0f; i++) { + const auto in_between = + glm::vec2{static_cast(pos.x) + i * diff.x / 10.0f, static_cast(pos.y) + i * diff.y / 10.0f}; + for (const auto &wall : world.view>()) { + const auto &wall_pos = world.get(wall); + const auto &wall_hitbox = world.get(wall); + + if (engine::d2::overlapped( + engine::d2::HitboxSolid{0.01, 0.01}, + engine::d3::Position{in_between.x, in_between.y, 0.0f}, + wall_hitbox, + wall_pos)) { + return false; + } + } + } + + const auto &spd = world.get(entity).speed; + const auto result = glm::normalize(diff); + out = {result.x * spd, result.y * spd}; + + return true; + }; + + for (auto &i : world.view, Health>(entt::exclude>)) { + auto &vel = world.get(i); + pursue(i, m_game.player, vel); + } +} + +auto game::GameLogic::slots_check_collision(entt::registry &world, const engine::TimeElapsed &) -> void +{ + static auto holder = engine::Core::Holder{}; + + // note : this is check at each frame but we only want it when the spell (or the wall) move + for (auto &spell : world.view, entt::tag<"projectile"_hs>>()) { + const auto &spell_pos = world.get(spell); + const auto &spell_box = world.get(spell); + + for (const auto &wall : world.view, engine::d3::Position, engine::d2::HitboxSolid>()) { + if (!world.valid(spell)) { break; } + + const auto &wall_pos = world.get(wall); + const auto &wall_box = world.get(wall); + + if (engine::d2::overlapped(spell_box, spell_pos, wall_box, wall_pos)) { + world.destroy(spell); + break; + } + } + } + + for (const auto &spell : world.view, engine::d3::Position, engine::Source>()) { + const auto &caster = world.get(spell).source; + if (!world.valid(caster)) { + world.destroy(spell); + continue; + } + const auto &spell_pos = world.get(spell); + + for (const auto &receiver : world.view()) { + if (!world.valid(spell)) break; + if (spell == receiver) continue; + + const auto &receiver_pos = world.get(receiver); + const auto &receiver_hitbox = world.get(receiver); + + const auto solid_collide = + world.has(spell) + && engine::d2::overlapped( + receiver_pos, receiver_hitbox, spell_pos, world.get(spell)); + + const auto float_collide = + world.has(spell) + && engine::d2::overlapped( + receiver_pos, receiver_hitbox, spell_pos, world.get(spell)); + + if (solid_collide || float_collide) { onCollideWithSpell.publish(world, receiver, caster, spell); } + } + } + + world.view().each([&](auto &picker, + const auto &pickerhitbox, + const auto &pickerPos) { + if (picker.hasKey) return; + + for (const auto &key : world.view>()) { + if (engine::d2::overlapped( + pickerhitbox, pickerPos, world.get(key), world.get(key))) { + picker.hasKey = true; + holder.instance->getAudioManager() + .getSound(holder.instance->settings().data_folder + "sounds/key_pickup.wav") + ->play(); + world.destroy(key); + } + } + }); +} diff --git a/src/Application/src/GameLogic/fight.cpp b/src/Application/src/GameLogic/fight.cpp new file mode 100644 index 0000000..6f69870 --- /dev/null +++ b/src/Application/src/GameLogic/fight.cpp @@ -0,0 +1,370 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "models/Spell.hpp" +#include "models/Class.hpp" +#include "models/EndGameStats.hpp" + +#include "GameLogic.hpp" +#include "ThePURGE.hpp" +#include "factory/EntityFactory.hpp" +#include "factory/SpellFactory.hpp" +#include "factory/ParticuleFactory.hpp" + +#include "component/all.hpp" + +#include "menu/UpgradePanel.hpp" +#include "menu/GameOver.hpp" + +using namespace std::chrono_literals; + +auto game::GameLogic::slots_apply_classes(entt::registry &world, entt::entity player, const Class &newClass) -> void +{ + static auto holder = engine::Core::Holder{}; + + world.get(player).speed += newClass.speed; + world.get(player) = newClass.hitbox; + + auto &health = world.get(player); + + health.current += newClass.health; + if (health.current <= 0) health.current = 1; + health.max += newClass.health; + world.get(player).ids.push_back(newClass.name); + + world.replace( + player, engine::Spritesheet::from_json(holder.instance->settings().data_folder + newClass.assetGraphPath)); + + engine::DrawableFactory::fix_spritesheet(world, player, "idle_right"); + + world.get(player).count -= newClass.cost; +} + +auto game::GameLogic::slots_level_up(entt::registry &world, entt::entity entity) -> void +{ + static auto holder = engine::Core::Holder{}; + + world.get(entity).count++; + + auto &level = world.get(entity); + level.current_xp -= level.xp_require; + level.xp_require = static_cast(std::ceil(level.xp_require * 1.2)); + level.current_level++; + + const auto &pos = world.get(entity); + ParticuleFactory::create(world, {pos.x, pos.y}, {255.f, 255.f, 35.f}); + + holder.instance->getAudioManager().getSound(holder.instance->settings().data_folder + "sounds/level_up.wav")->play(); +} + +auto game::GameLogic::addXp(entt::registry &world, entt::entity player, std::uint32_t xp) -> void +{ + auto &level = world.get(player); + + level.current_xp += xp; + + while (level.current_xp >= level.xp_require) { onPlayerLevelUp.publish(world, player); } +} + +auto game::GameLogic::slots_update_effect(entt::registry &world, const engine::TimeElapsed &dt) -> void +{ + const auto elapsed = std::chrono::duration_cast(dt.elapsed).count(); + + world.view, engine::Cooldown>().each([&elapsed](auto &, auto &cd) { + if (!cd.is_in_cooldown) return; + + if (std::chrono::milliseconds{elapsed} <= cd.remaining_cooldown) { + cd.remaining_cooldown -= std::chrono::milliseconds{elapsed}; + } else { + cd.remaining_cooldown = 0ms; + cd.is_in_cooldown = false; + } + }); + + for (const auto &effect : world.view>()) { + auto &cd = world.get(effect); + if (cd.is_in_cooldown) continue; + cd.is_in_cooldown = true; + cd.remaining_cooldown = cd.cooldown; + + const auto &receiver = world.get(effect).source; + const auto &sender = world.get(effect).source; + if (!world.valid(receiver)) { + world.destroy(effect); + continue; + } + + switch (world.get(effect)) { + case Effect::Type::DOT: { + onDamageTaken.publish(world, receiver, sender, effect); + // world.destroy(effect); + + } break; + case Effect::Type::DASH: { + const auto &initial_speed = world.get_or_emplace>(receiver, world.get(receiver)).data; + world.replace(receiver, initial_speed); + world.remove_if_exists>(receiver); + + const auto &initial_color = + world.get_or_emplace>(receiver, world.get(receiver)).data; + engine::DrawableFactory::fix_color( + world, + receiver, + {engine::Color::r(initial_color), + engine::Color::g(initial_color), + engine::Color::b(initial_color), + engine::Color::a(initial_color)}); + world.remove_if_exists>(receiver); + + } break; + default: break; + } + } +} + +auto game::GameLogic::slots_collide_with_spell( + entt::registry &world, entt::entity receiver, entt::entity sender, entt::entity spell) -> void +{ + if (!world.valid(receiver) || !world.valid(sender) || !world.valid(spell)) return; + + const auto targets = world.get(spell).ref; + + const auto to_the_caster = targets[SpellData::Target::CASTER] && (receiver == sender); + const auto to_an_enemy = targets[SpellData::Target::ENEMY] + && (world.has>(receiver) ^ world.has>(sender)); + const auto to_all = targets[SpellData::Target::ALL]; + + if (to_the_caster || to_an_enemy || to_all) { + const auto effects = [&](const auto &ref) { + struct sPsE { + std::string_view tag; + const Effect *effect; + }; + std::vector out; + std::transform(ref.begin(), ref.end(), std::back_inserter(out), [&](const auto &id) { + return sPsE{id, &m_game.dbEffects().db.at(id)}; + }); + return out; + }(world.get(spell).ref); + + for (auto &[new_tag, i] : effects) { + const auto view = world.view, engine::Source, std::string>(); + const auto found = std::find_if(view.begin(), view.end(), [&world, &new_tag, &receiver](const auto &entity) { + const auto &source = world.get(entity); + const auto &tag = world.get(entity); + return tag == new_tag && source.source == receiver; + }); + + if (found != view.end()) { + world.emplace_or_replace(*found, i->lifetime); + + } else { + spdlog::info("create effect"); + + auto new_effect = world.create(); + world.emplace>(new_effect); + world.emplace(new_effect, receiver); + world.emplace(new_effect, sender); + world.emplace(new_effect, i->lifetime); + world.emplace(new_effect, true, i->cooldown, i->cooldown); + world.emplace(new_effect, new_tag); + + world.emplace(new_effect, i->type); + + if (i->type == Effect::DOT) { + world.emplace(new_effect, i->damage); + } else if (i->type == Effect::DASH) { + if (!world.has>(receiver)) { + if (!world.has>(receiver)) { + world.emplace>(receiver, world.get(receiver)); + } + world.replace(receiver, world.get(receiver).speed / i->strength); + + if (!world.has>(receiver)) { + const auto ¤t_color = world.get(receiver); + world.emplace>(receiver, current_color); + } + engine::DrawableFactory::fix_color(world, receiver, {0, 0, 1, 1}); + } + } + } + } + + if (world.has>(spell)) { + onDamageTaken.publish(world, receiver, sender, spell); + if (world.valid(spell)) { world.destroy(spell); } + } + } +} + +auto game::GameLogic::slots_damage_taken(entt::registry &world, entt::entity receiver, entt::entity sender, entt::entity spell) + -> void +{ + auto holder = engine::Core::Holder{}; + + if (!world.valid(receiver) || !world.has(receiver) || !world.valid(spell) || !world.valid(sender)) { + return; + } + + auto &entity_health = world.get(receiver); + entity_health.current -= world.has(spell) ? world.get(spell).damage : 0; + + const auto is_player = world.has>(receiver); + const auto is_wall = world.has>(receiver); + + if (is_player) { holder.instance->setScreenshake(true, 350ms); } + + if (!is_wall) { + const auto &entity_pos = world.get(receiver); + const auto &spell_pos = world.try_get(spell); + const auto particule_pos = + spell_pos ? glm::vec2{(spell_pos->x + entity_pos.x) / 2.0, (entity_pos.y + spell_pos->y) / 2.0} + : glm::vec2{entity_pos.x, entity_pos.y}; + ParticuleFactory::create( + world, particule_pos, is_player ? glm::vec3{255, 0, 0} : glm::vec3{0, 0, 0}); + + holder.instance->getAudioManager().getSound(holder.instance->settings().data_folder + "sounds/fire_hit.wav")->play(); + } + + if (entity_health.current <= 0.0f) { + onEntityKilled.publish(world, receiver, sender); + world.destroy(spell); + } else { + auto new_effect = world.create(); + world.emplace>(new_effect); + world.emplace(new_effect, receiver); + world.emplace(new_effect, sender); + world.emplace(new_effect, 200ms); + world.emplace(new_effect, true, 180ms, 180ms); + + world.emplace(new_effect, Effect::Type::DASH); + + if (!world.has>(receiver)) { + const auto ¤t_color = world.get(receiver); + world.emplace>(receiver, current_color); + } + engine::DrawableFactory::fix_color(world, receiver, {0.2, 0.2, 0.2, 1}); + } +} + +auto game::GameLogic::slots_cast_spell(entt::registry &world, entt::entity caster, const glm::dvec2 &direction, Spell &spell) + -> void +{ + if (!spell.cd.is_in_cooldown) { + const auto &vel = world.get(caster); + + const auto aiming = [](entt::registry &w, const entt::entity &e) -> std::optional { + if (w.has(e)) { + return w.get(e).dir; + } else { + return {}; + } + }(world, caster); + + const auto isFacingLeft = [](const auto &v, const auto &a) { + if (v.x < 0) + return true; + else if (v.x > 0) + return false; + else if (a.has_value() && a.value().x < 0) + return true; + else + return false; + }(vel, aiming); + + const auto animation = fmt::format("{}_{}", "attack", isFacingLeft ? "left" : "right"); + engine::DrawableFactory::fix_spritesheet(world, caster, animation); + world.get(caster).attack_animation_finish = false; + SpellFactory::create(m_game.dbSpells(), world, caster, glm::normalize(direction), m_game.dbSpells().db.at(std::string{spell.id})); + spell.cd.remaining_cooldown = spell.cd.cooldown; + spell.cd.is_in_cooldown = true; + } +} + +auto game::GameLogic::slots_update_ai_attack(entt::registry &world, [[maybe_unused]] const engine::TimeElapsed &dt) -> void +{ + for (const auto &enemy : world.view, engine::d3::Position, AttackRange, Health>()) { + // TODO: Add brain to AI. current strategy : spam every spell towards the player + + for (auto &spell : world.get(enemy).spells) { + if (!spell.has_value()) continue; + + const auto &selfPosition = world.get(enemy); + const auto &targetPosition = world.get(m_game.player); + + const auto diff = glm::dvec2{targetPosition.x - selfPosition.x, targetPosition.y - selfPosition.y}; + + const auto &attack_range = world.get(enemy); + + if (glm::length(diff) <= static_cast(attack_range.range)) { + onSpellCast.publish(world, enemy, diff, spell.value()); + } + } + } +} + +auto game::GameLogic::slots_kill_entity(entt::registry &world, entt::entity killed, entt::entity killer) -> void +{ + static auto holder = engine::Core::Holder{}; + + engine::DrawableFactory::fix_spritesheet(world, killed, "death"); + try { + const auto &animation = world.get(killed).animations["death"]; + + world.emplace_or_replace( + killed, std::chrono::milliseconds(animation.frames.size() * animation.cooldown)); + } catch (...) { + world.emplace_or_replace(killed, 0ms); + } + world.get(killed) = {0.0, 0.0}; + + world.remove_if_exists(killed); + world.remove_if_exists(killed); + world.remove_if_exists(killed); + + if (world.has>(killed)) { + + auto &spell_on_death = world.get(killed).spells[0]; + onSpellCast.publish(world, killed, glm::dvec2{0.0, 1.0}, spell_on_death.value()); + world.emplace_or_replace(killed, m_game.dbSpells().db.at(std::string{spell_on_death.value().id}).lifetime); + + } if (world.has>(killed)) { + holder.instance->getAudioManager() + .getSound(holder.instance->settings().data_folder + "sounds/player_death.wav") + ->play(); + + m_game.setMenu(std::make_unique(EndGameStats(world, killed, m_gameTime))); + + } else if (world.has>(killed)) { + // TODO: actual random utilities + bool lazyDevCoinflip = static_cast(killed) % 2; + holder.instance->getAudioManager() + .getSound( + lazyDevCoinflip ? holder.instance->settings().data_folder + "sounds/death/death_01.wav" + : holder.instance->settings().data_folder + "sounds/death/death_02.wav") + ->play(); + + if (world.has>(killer)) { + addXp(world, killer, world.get(killed).xp); + world.get(killer).enemyKilled++; + } + + if (world.has>(killed)) { + const auto &pos = world.get(killed); + EntityFactory::create(m_game, world, {pos.x, pos.y}, {1.0, 1.0}); + holder.instance->getAudioManager() + .getSound(holder.instance->settings().data_folder + "sounds/death/boss_death.wav") + ->play(); + } + } +} diff --git a/src/Application/src/ThePURGE.cpp b/src/Application/src/ThePURGE.cpp index c219260..a455b31 100644 --- a/src/Application/src/ThePURGE.cpp +++ b/src/Application/src/ThePURGE.cpp @@ -1,474 +1,99 @@ -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include #include -#include -#include +#include #include +#include -#include - -#include "level/LevelTilemapBuilder.hpp" -#include "level/MapGenerator.hpp" - -#include "factory/EntityFactory.hpp" +#include "models/Spell.hpp" +#include "models/Class.hpp" -#include "screen/MainMenu.hpp" -#include "screen/GameOverMenu.hpp" - -#include "widgets/UserStatistics.hpp" -#include "widgets/DebugTerrainGeneration.hpp" -#include "widgets/DebugCamera.hpp" -#include "widgets/DebugAudio.hpp" - -#include "GameLogic.hpp" #include "ThePURGE.hpp" -#include "DataConfigLoader.hpp" - -#include "component/Facing.hpp" +#include "widgets/GameHUD.hpp" +#include "widgets/debug/DebugTerrainGeneration.hpp" -using namespace std::chrono_literals; +#include "menu/MainMenu.hpp" -game::ThePURGE::ThePURGE() {} - -auto game::ThePURGE::onDestroy(entt::registry &) -> void {} +#include "widgets/Fonts.hpp" auto game::ThePURGE::onCreate([[maybe_unused]] entt::registry &world) -> void { static auto holder = engine::Core::Holder{}; - m_nextFloorSeed = static_cast(std::time(nullptr)); - m_logics = std::make_unique(*this); - m_debugConsole = std::make_unique(*this); + game::Fonts::loadFonts(); - m_dungeonMusic = - holder.instance->getAudioManager().getSound(holder.instance->settings().data_folder + "sounds/dungeon_music.wav"); - m_dungeonMusic->setVolume(0.1f).setLoop(true); +#ifndef NDEBUG + m_console = std::make_unique(*this); + m_console->info("Press TAB to autocomplete known commands.\nPress F1 to toggle this console"); +#endif - m_debugConsole->info("Press TAB to autocomplete known commands.\nPress F1 to toggle this console"); + // holder.instance->window()->setSize(glm::ivec2(1920, 1080)); - m_classDatabase = DataConfigLoader::loadClassDatabase(holder.instance->settings().data_folder + "db/classes.json"); + m_logics = std::make_unique(*this); + + const auto data_folder = holder.instance->settings().data_folder; + spdlog::trace("Loading the spells"); + m_db_spell.fromFile(data_folder + "db/spells.json"); + spdlog::trace("OK"); + spdlog::trace("Loading the classes"); + m_db_class.fromFile(data_folder + "db/classes.json"); + spdlog::trace("OK"); + spdlog::trace("Loading the enemies"); + m_db_enemy.fromFile(data_folder + "db/enemies.json"); + spdlog::trace("OK"); + spdlog::trace("Loading the effects"); + m_db_effects.fromFile(data_folder + "db/effects.json"); + spdlog::trace("OK"); + + setMenu(std::make_unique()); + setBackgroundMusic("sounds/menu/background_music.wav", 0.5f); + + spdlog::info("Starting the game"); + holder.instance->window()->setCursorVisible(false); +} - // pos and size based of `FloorGenParam::maxDungeonWidth / Height` - EntityFactory::create(world, glm::vec2(25, 25), glm::vec2(75, 75)); +auto game::ThePURGE::onDestroy(entt::registry &) -> void +{ + spdlog::info("Thanks for playing ThePURGE"); - setState(State::LOADING); + setMenu(nullptr); + m_logics.reset(nullptr); } auto game::ThePURGE::onUpdate(entt::registry &world, const engine::Event &e) -> void { static auto holder = engine::Core::Holder{}; - const auto spell_map = [](T k) - { - struct SpellMap { - int key; - std::size_t id; - }; - if constexpr (std::is_same::value) { - const auto map = std::to_array({{engine::Joystick::LS, 0}, {engine::Joystick::RS, 1}}); - return std::find_if(map.begin(), map.end(), [&k](auto &i) { return i.key == k; })->id; - - } else if constexpr (std::is_same::value) { - const auto map = std::to_array({{engine::Joystick::LST, 2}, {engine::Joystick::RST, 3}}); - return std::find_if(map.begin(), map.end(), [&k](auto &i) { return i.key == k; })->id; - - } else { // todo : should be engine::Keyboard::Key - const auto map = std::to_array({{GLFW_KEY_U, 0}, {GLFW_KEY_Y, 1}, {GLFW_KEY_T, 2}, {GLFW_KEY_R, 3}}); - return std::find_if(map.begin(), map.end(), [&k](auto &i) { return i.key == k; })->id; + if (m_currentMenu == nullptr) { + if (holder.instance->getEventMode() != engine::Core::EventMode::PAUSED) { + m_logics->onEvent.publish(world, e); } - }; - - if (m_state == State::IN_GAME) { - std::visit( - engine::overloaded{ - [&](const engine::Pressed &key) { - switch (key.source.key) { - case GLFW_KEY_UP: m_camera.move({0, 1}); break; - case GLFW_KEY_RIGHT: m_camera.move({1, 0}); break; - case GLFW_KEY_DOWN: m_camera.move({0, -1}); break; - case GLFW_KEY_LEFT: m_camera.move({-1, 0}); break; - - case GLFW_KEY_I: m_logics->movement.publish(world, player, Direction::UP, true); break; // go top - case GLFW_KEY_K: - m_logics->movement.publish(world, player, Direction::DOWN, true); - break; // go bottom - case GLFW_KEY_L: - m_logics->movement.publish(world, player, Direction::RIGHT, true); - break; // go right - case GLFW_KEY_J: m_logics->movement.publish(world, player, Direction::LEFT, true); break; // go left - case GLFW_KEY_P: setState(State::IN_INVENTORY); break; - - case GLFW_KEY_U: - case GLFW_KEY_Y: { - const auto id = spell_map(key.source.key); - - auto &spell = world.get(player).spells[id]; - if (!spell.has_value()) break; - - auto &vel = world.get(player); - m_logics->castSpell.publish(world, player, {vel.x, vel.y}, spell.value()); - } break; - default: return; - } - }, - [&](const engine::Released &key) { - switch (key.source.key) { - case GLFW_KEY_I: m_logics->movement.publish(world, player, Direction::UP, false); break; // go top - case GLFW_KEY_K: - m_logics->movement.publish(world, player, Direction::DOWN, false); - break; // go bottom - case GLFW_KEY_L: - m_logics->movement.publish(world, player, Direction::RIGHT, false); - break; // go right - case GLFW_KEY_J: - m_logics->movement.publish(world, player, Direction::LEFT, false); - break; // go left - default: return; - } - }, - [&](const engine::TimeElapsed &dt) { - m_logics->gameUpdated.publish(world, dt); - m_logics->afterGameUpdated.publish(world, dt); - }, - [&](const engine::Moved &joy) { - switch (joy.source.axis) { - case engine::Joystick::LST: - case engine::Joystick::RST: { - const auto id = spell_map(joy.source.axis); - auto &spell = world.get(player).spells[id]; - if (!spell.has_value()) break; - auto &vel = world.get(player); - m_logics->castSpell.publish(world, player, {vel.x, vel.y}, spell.value()); - } break; - case engine::Joystick::LSX: - case engine::Joystick::LSY: { - auto joystick = holder.instance->getJoystick(joy.source.id); - - auto &axis = world.get(player).movement; - - axis.x = (*joystick)->axes[engine::Joystick::LSX]; - axis.y = (*joystick)->axes[engine::Joystick::LSY]; - - } break; - case engine::Joystick::RSX: - case engine::Joystick::RSY: { - auto joystick = holder.instance->getJoystick(joy.source.id); - - auto &axis = world.get(player).aiming; - - axis.x = (*joystick)->axes[engine::Joystick::RSX]; - axis.y = (*joystick)->axes[engine::Joystick::RSY]; - - } break; - default: break; - } - }, - [&](const engine::Pressed &joy) { - switch (joy.source.button) { - case engine::Joystick::CENTER2: setState(State::IN_INVENTORY); break; - case engine::Joystick::LS: - case engine::Joystick::RS: { - const auto id = spell_map(joy.source.button); - auto &spell = world.get(player).spells[id]; - if (!spell.has_value()) break; - auto &vel = world.get(player); - m_logics->castSpell.publish(world, player, {vel.x, vel.y}, spell.value()); - } break; - default: return; - } - }, - [&](auto) {}, - }, - e); - } else if (m_state == State::IN_INVENTORY) { - std::visit( - engine::overloaded{ - [&](const engine::Pressed &key) { - switch (key.source.key) { - case GLFW_KEY_P: setState(State::IN_GAME); break; - default: break; - } - }, - [&](const engine::Pressed &joy) { - switch (joy.source.button) { - case engine::Joystick::CENTER2: setState(State::IN_GAME); break; - default: break; - } - }, - [&](auto) {}, - }, - e); - - } else if (m_state == State::GAME_OVER) { - std::visit( - engine::overloaded{ - [&](const engine::Pressed &joy) { - switch (joy.source.button) { - case engine::Joystick::CENTER2: { - for (const auto &i : world.view>()) { world.destroy(i); } - for (const auto &i : world.view>()) { world.destroy(i); } - for (const auto &i : world.view>()) { world.destroy(i); } - for (const auto &i : world.view>()) { world.destroy(i); } - for (const auto &i : world.view>()) { world.destroy(i); } - - setState(State::LOADING); - } break; - default: break; - } - }, - [&](auto) {}, - }, - e); - } else if (m_state == State::LOADING) { - std::visit( - engine::overloaded{ - [&](const engine::Pressed &joy) { - switch (joy.source.button) { - case engine::Joystick::CENTER2: { - logics()->onGameStarted.publish(world); - setState(ThePURGE::State::IN_GAME); - } break; - default: break; - } - }, - [&](auto) {}, - }, - e); - } + } else + m_currentMenu->onEvent(world, *this, e); } auto game::ThePURGE::drawUserInterface(entt::registry &world) -> void { - [[maybe_unused]] static auto holder = engine::Core::Holder{}; - #ifndef NDEBUG + static auto holder = engine::Core::Holder{}; + if (holder.instance->isShowingDebugInfo()) { - m_debugConsole->draw(); - ImGui::ShowDemoWindow(); + m_console->draw(); + widget::TerrainGeneration::draw(*this, world); } #endif - if (m_state == State::LOADING) { - MainMenu::draw(*this, world); - - } else if (m_state == State::IN_GAME) { - UserStatistics::draw(*this, world); - - -#ifndef NDEBUG - if (holder.instance->isShowingDebugInfo()) { - widget::TerrainGeneration::draw(*this, world); - widget::DebugCamera::draw(m_camera); - widget::DebugAudio::draw(); - - // displaySoundDebugGui(); - } -#endif - } else if (m_state == State::GAME_OVER) { - GameOverMenu::draw(*this, world); - - } else if (m_state == State::IN_INVENTORY) { // todo : cleaner - UserStatistics::draw(*this, world); - - const auto &boughtClasses = world.get(player).ids; - const auto skillPoints = world.get(player).count; - - // const auto comp = world.get(player); - static bool choosetrigger = false; - static auto test = classes::getStarterClass(m_classDatabase); - static std::optional selectedClass; - static std::string chosenTrig = ""; - static int spellmapped = 0; - static int infoAdd; - // clang-format off - static std::vector Texture = { - engine::DrawableFactory::createtexture("data/textures/InfoHud.png"), - engine::DrawableFactory::createtexture("data/textures/CPoint.PNG"), - engine::DrawableFactory::createtexture("data/textures/validate.png"), - engine::DrawableFactory::createtexture("data/textures/controller/LB.png"), - engine::DrawableFactory::createtexture("data/textures/controller/LT.png"), - engine::DrawableFactory::createtexture("data/textures/controller/RT.png"), - engine::DrawableFactory::createtexture("data/textures/controller/RB.png") - }; - // clang-format on - - ImVec2 size = ImVec2(1000.0f, 1000.0f); - ImGui::SetNextWindowPos(ImVec2(m_camera.getCenter().x + size.x / 2, 0)); - ImGui::SetNextWindowSize(ImVec2(size.x, size.y)); - ImGui::Begin( - "Evolution Panel", - nullptr, - ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoResize - | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoMove); - size = ImGui::GetWindowSize(); - ImGui::Image(reinterpret_cast(static_cast((Texture[0]))), ImVec2(size.x - 30, size.y - 10)); - ImGui::SetCursorPos(ImVec2(0, 200)); - ImVec2 next; - if (selectedClass.has_value()) { - ImVec2 icon = ImVec2(ImGui::GetCursorPosX() + size.x / 3 + 9, ImGui::GetCursorPosY()); - ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + size.x / 3 + 9, ImGui::GetCursorPosY())); - helper::ImGui::Text("Class Name: {}", selectedClass->name); - ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + size.x / 3, ImGui::GetCursorPosY())); - helper::ImGui::Text("Class description: {}", selectedClass->description); - if (infoAdd == 1) { - static std::size_t triggerValue = 5; - std::optional lastclass = world.get(player); - if (spellmapped % 2 != 0 && lastclass.value().ids.size() > 1 - && lastclass.value().ids[lastclass.value().ids.size() - 1] == selectedClass.value().id) { - ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + size.x / 3, ImGui::GetCursorPosY() + 50)); - ImGui::Text("Choose Your trigger :"); - ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + size.x / 3, ImGui::GetCursorPosY() + 10)); - auto &spell = world.get(player); - if (ImGui::ImageButton(reinterpret_cast(static_cast((Texture[3]))), ImVec2(50, 50))) { - chosenTrig = "LB"; - triggerValue = 0; - } - ImGui::SameLine(); - if (ImGui::ImageButton(reinterpret_cast(static_cast((Texture[4]))), ImVec2(50, 50))) { - chosenTrig = "LT"; - triggerValue = 2; - } - ImGui::SameLine(); - if (ImGui::ImageButton(reinterpret_cast(static_cast((Texture[5]))), ImVec2(50, 50))) { - chosenTrig = "RT"; - triggerValue = 3; - } - ImGui::SameLine(); - if (ImGui::ImageButton(reinterpret_cast(static_cast((Texture[6]))), ImVec2(50, 50))) { - chosenTrig = "RB"; - triggerValue = 1; - } - ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + size.x / 3, ImGui::GetCursorPosY() + 10)); - helper::ImGui::Text("You choosed the trigger: {}", chosenTrig.c_str()); - ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + size.x / 3, ImGui::GetCursorPosY())); - if (chosenTrig != "") { - if (ImGui::Button("Validate")) { - choosetrigger = false; - auto NewSpell = Spell::create(selectedClass.value().spells[0]); - for (auto i = 0ul; i < spell.spells.size(); i++) { - if (spell.spells[i].has_value() && NewSpell.id == spell.spells[i].value().id) { - spell.spells[i] = {}; - // spell.removeElem(i); - } - } - spell.spells[triggerValue] = NewSpell; - spellmapped++; - } - } - } else { - ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + size.x / 3, ImGui::GetCursorPosY())); - ImGui::Text("Already bought"); - } - next = ImVec2(ImGui::GetCursorPosX(), ImGui::GetCursorPosY()); - ImGui::SetCursorPos(ImVec2(icon.x - 50, icon.y)); - ImGui::Image(reinterpret_cast(static_cast((Texture[2]))), ImVec2(50, 50)); - } else if (infoAdd == 2) { - if (skillPoints > 0) { - if (choosetrigger == true) { - ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + size.x / 3, ImGui::GetCursorPosY())); - ImGui::Text("You haven't choose a trigger for the spell"); - } - ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + size.x / 3, ImGui::GetCursorPosY())); - if (ImGui::Button("Add class")) { - m_logics->onPlayerBuyClass.publish(world, player, selectedClass.value()); - infoAdd = 1; - if (choosetrigger == false) { - spellmapped++; - choosetrigger = true; - } else - spellmapped = spellmapped + 2; - } - } else { - ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + size.x / 3, ImGui::GetCursorPosY() + 10)); - helper::ImGui::Button("No skill point", ImVec4(0.5f, 0.5f, 0.5f, 0.5f)); - } - - } else { - ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + size.x / 3, ImGui::GetCursorPosY())); - ImGui::Text("You can't buy it yet"); - } - } - // Competences - ImGui::SetCursorPos(ImVec2(size.x / 2 - 17 * ImGui::GetFontSize() / 2, size.y / 2)); - helper::ImGui::Text("Point de comp: {}", skillPoints); // rendre dynamique le nombre de point de comp - ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); - ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX(), ImGui::GetCursorPosY() - 5)); - ImGui::Image(reinterpret_cast(static_cast((Texture[1]))), ImVec2(20, 20)); - - // Classes tree - float length = size.x / 2 - (12.0f + static_cast(test.name.length())) * ImGui::GetFontSize(); - ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + length, ImGui::GetCursorPosY() + 30)); - helper::ImGui::Text("class Name: {}", test.name); - - std::vector currentLine; - std::vector nextLine; - std::vector buyableClasses; - int tier = 0; - - nextLine.push_back(classes::getStarterClass(m_classDatabase).id); - - while (!nextLine.empty()) { - ++tier; - currentLine.clear(); - std::swap(currentLine, nextLine); - ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + size.x / 4, ImGui::GetCursorPosY() + 10)); - helper::ImGui::Text("Tier : {}", tier); - ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + size.x / 4, ImGui::GetCursorPosY() + 10)); - for (const auto currentId : currentLine) { - const auto ¤tClass = m_classDatabase[currentId]; - - bool bought = std::find(boughtClasses.begin(), boughtClasses.end(), currentId) != boughtClasses.end(); - bool buyable = std::find(buyableClasses.begin(), buyableClasses.end(), currentId) != buyableClasses.end(); - if (bought) { - buyableClasses.insert(buyableClasses.end(), currentClass.children.begin(), currentClass.children.end()); - if (helper::ImGui::Button(currentClass.name.c_str(), ImVec4(0, 1, 0, 0.5))) { - selectedClass = currentClass; - infoAdd = 1; - } - } else if (buyable) { - if (helper::ImGui::Button(currentClass.name.c_str(), ImVec4(1, 1, 0, 0.5))) { - selectedClass = currentClass; - infoAdd = 2; - } - } else { - if (helper::ImGui::Button(currentClass.name.c_str(), ImVec4(1, 0, 0, 0.5))) { - selectedClass = currentClass; - infoAdd = 3; - } - } - ImGui::SameLine(); - nextLine.insert(nextLine.end(), currentClass.children.begin(), currentClass.children.end()); - } - ImGui::Text(" "); // ImGui::NextLine() - } - ImGui::End(); - } + if (m_currentMenu == nullptr) + GameHUD::draw(*this, world); + else + m_currentMenu->onDraw(world, *this); } -/* -auto game::ThePURGE::goToNextFloor(entt::registry &world) -> void +void game::ThePURGE::setBackgroundMusic(const std::string &path, float volume) noexcept { - world.view>().each([&](auto &e) { world.destroy(e); }); - world.view>().each([&](auto &e) { world.destroy(e); }); - world.view>().each([&](auto &e) { world.destroy(e); }); - - const auto map = generateFloor(world, m_map_generation_params, m_nextFloorSeed); - m_nextFloorSeed = map.nextFloorSeed; + static auto holder = engine::Core::Holder{}; - auto &pos = world.get(player); + if (m_background_music) m_background_music->stop(); - pos.x = map.spawn.x + map.spawn.w * 0.5; - pos.y = map.spawn.y + map.spawn.h * 0.5; + m_background_music = holder.instance->getAudioManager().getSound(holder.instance->settings().data_folder + path); + m_background_music->setVolume(volume).setLoop(true).play(); } -*/ diff --git a/src/Application/src/factory/EntityFactory.cpp b/src/Application/src/factory/EntityFactory.cpp index dcf447e..8c1f109 100644 --- a/src/Application/src/factory/EntityFactory.cpp +++ b/src/Application/src/factory/EntityFactory.cpp @@ -1,263 +1,276 @@ #include -#include +#include #include #include #include +#include "models/Spell.hpp" + #include "component/all.hpp" #include "factory/EntityFactory.hpp" -#include "DataConfigLoader.hpp" + +#include "ThePURGE.hpp" + +auto game::EntityFactory::create([[maybe_unused]] ThePURGE &game, entt::registry &world, const glm::vec2 &pos, const Enemy &data) + -> entt::entity +{ + auto holder = engine::Core::Holder{}; + + const auto enemy = world.create(); + + world.emplace>(enemy); + + if (data.is_boss) world.emplace>(enemy); + + world.emplace(enemy, engine::DrawableFactory::rectangle()); + world.emplace(enemy, 0.f); + world.emplace(enemy, pos.x, pos.y, get_z_layer()); + + world.emplace(enemy, data.scale); + world.emplace(enemy, data.hitbox); + + engine::DrawableFactory::fix_color(world, enemy, glm::vec4{data.color}); + + world.emplace( + enemy, engine::Spritesheet::from_json(holder.instance->settings().data_folder + data.asset)); + + // note : this does not set animation as wished + engine::DrawableFactory::fix_spritesheet(world, enemy, (std::rand() & 1) ? "idle_right" : "idle_left"); + + world.emplace(enemy, 0.0, 0.0); + + world.emplace(enemy, data.view_range); + world.emplace(enemy, data.attack_range); + world.emplace(enemy, data.speed); + + world.emplace(enemy, data.health, data.health); + world.emplace(enemy, data.experience); + + auto &slots = world.emplace(enemy); + for (auto i = 0ul; i != data.spells.size(); i++) slots.spells[i] = game.dbSpells().instantiate(data.spells[i]); + + for (const auto &i : data.tag) { + if (i == "wall") world.emplace>(enemy); + } + +#ifndef NDEBUG + + auto hitbox_entity = world.create(); + world.emplace>(hitbox_entity); + world.emplace(hitbox_entity, enemy); + world.emplace(hitbox_entity, engine::DrawableFactory::rectangle()); + world.emplace(hitbox_entity, 0.f); + world.emplace(hitbox_entity, pos.x, pos.y, get_z_layer()); + world.emplace(hitbox_entity, data.hitbox.width, data.hitbox.height); + engine::DrawableFactory::fix_color(world, hitbox_entity, {1, 1, 1, 0.5}); + engine::DrawableFactory::fix_texture( + world, hitbox_entity, holder.instance->settings().data_folder + "img/transparent.png"); + +#endif + + return enemy; +} template<> -auto game::EntityFactory::create( - entt::registry &world, const glm::vec2 &pos, const glm::vec2 &size) -> entt::entity +auto game::EntityFactory::create( + ThePURGE &, entt::registry &world, [[maybe_unused]] const glm::vec2 &pos, [[maybe_unused]] const glm::vec2 &size) + -> entt::entity { - static auto holder = engine::Core::Holder{}; + auto player = world.create(); - const auto e = world.create(); - world.emplace(e, pos.x, pos.y, get_z_layer()); - world.emplace(e, 0.f); - world.emplace(e, size.x, size.y); - world.emplace(e, engine::DrawableFactory::rectangle()); - engine::DrawableFactory::fix_color(world, e, {1, 1, 1}); - engine::DrawableFactory::fix_texture(world, e, holder.instance->settings().data_folder + "textures/floor.jpg"); - world.emplace>(e); - return e; + world.emplace>(player); + world.emplace(player, 0.0, 0.0, EntityFactory::get_z_layer()); + world.emplace(player, 0.0, 0.0); + world.emplace(player, 0.f); + world.emplace(player, 0.0, 0.0); + world.emplace(player, 0.7, 1.2); + world.emplace(player, engine::DrawableFactory::rectangle()); + world.emplace(player, 0u, 0u, 10u); + world.emplace(player); + world.emplace(player); + world.emplace(player); + world.emplace(player, 0); + world.emplace(player, glm::vec2(0.f, 0.f), glm::vec2(0.f, 0.f)); + world.emplace(player); + world.emplace(player, glm::vec2(1.f, 0.f)); + world.emplace(player); + + engine::DrawableFactory::fix_color(world, player, {1.0f, 1.0f, 1.0f, 1.0f}); + + // class dependant, see `GameLogic::apply_class_to_player` + world.emplace(player); + world.emplace(player, 0.f, 0.f); + world.emplace(player, 0.f); + world.emplace(player, 5.f); + world.emplace(player, 1.0f, 1.0f); + // -- + + return player; } template<> -auto game::EntityFactory::create( - entt::registry &world, const glm::vec2 &pos, const glm::vec2 &size) -> entt::entity +auto game::EntityFactory::create( + ThePURGE &, entt::registry &world, const glm::vec2 &pos, const glm::vec2 &size) -> entt::entity { static auto holder = engine::Core::Holder{}; - const auto e = world.create(); - world.emplace(e, pos.x, pos.y, get_z_layer()); - world.emplace(e, 0.f); - world.emplace(e, size.x, size.y); - world.emplace(e, engine::DrawableFactory::rectangle()); - engine::DrawableFactory::fix_color(world, e, {1, 1, 1}); - engine::DrawableFactory::fix_texture(world, e, holder.instance->settings().data_folder + "textures/floor.jpg"); - world.emplace>(e); - return e; + auto key = world.create(); + world.emplace>(key); + world.emplace(key); + world.emplace(key, pos.x, pos.y, get_z_layer()); + world.emplace(key, 0.f); + world.emplace(key, size.x, size.y); + world.emplace(key, engine::DrawableFactory::rectangle()); + engine::DrawableFactory::fix_color(world, key, {1, 1, 0, 1}); + engine::DrawableFactory::fix_texture(world, key, holder.instance->settings().data_folder + "img/key.png"); + return key; } template<> -auto game::EntityFactory::create( - entt::registry &world, const glm::vec2 &pos, const glm::vec2 &size) -> entt::entity +auto game::EntityFactory::create( + ThePURGE &, entt::registry &world, [[maybe_unused]] const glm::vec2 &, [[maybe_unused]] const glm::vec2 &) + -> entt::entity { static auto holder = engine::Core::Holder{}; - const auto e = world.create(); - world.emplace(e, pos.x, pos.y, get_z_layer()); + auto e = world.create(); + + world.emplace>(e); + world.emplace(e, 0.0, 0.0, EntityFactory::get_z_layer()); world.emplace(e, 0.f); - world.emplace(e, size.x, size.y); + world.emplace(e, 0.0, 0.0); + world.emplace(e, engine::DrawableFactory::rectangle()); - engine::DrawableFactory::fix_color(world, e, {1, 1, 1}); - engine::DrawableFactory::fix_texture(world, e, holder.instance->settings().data_folder + "textures/floor_boss.jpg"); - world.emplace>(e); + + engine::DrawableFactory::fix_texture(world, e, holder.instance->settings().data_folder + "img/aim_sight.png"); + return e; } +struct TexturePath { + static constexpr auto floor_normal = "img/map/stone4_b.jpg"; + static constexpr auto floor_spawn = "img/map/stone2_b.jpg"; + static constexpr auto floor_boss = "img/map/stone5_b.jpg"; + static constexpr auto floor_corridor = "img/map/stone3_b.jpg"; +}; + + template<> -auto game::EntityFactory::create( - entt::registry &world, const glm::vec2 &pos, const glm::vec2 &size) -> entt::entity +auto game::EntityFactory::create( + ThePURGE &, entt::registry &world, const glm::vec2 &pos, const glm::vec2 &size) -> entt::entity { static auto holder = engine::Core::Holder{}; const auto e = world.create(); - world.emplace(e, pos.x, pos.y, get_z_layer()); + world.emplace(e, pos.x, pos.y, get_z_layer()); world.emplace(e, 0.f); world.emplace(e, size.x, size.y); world.emplace(e, engine::DrawableFactory::rectangle()); - engine::DrawableFactory::fix_color(world, e, {1, 1, 1}); - engine::DrawableFactory::fix_texture(world, e, holder.instance->settings().data_folder + "textures/corridor.jpg"); + engine::DrawableFactory::fix_color(world, e, {0.3, 0.3, 0.3, 1}); + engine::DrawableFactory::fix_texture( + world, e, holder.instance->settings().data_folder + TexturePath::floor_normal, true, {0.0f, 0.0f, size.x, size.y}); world.emplace>(e); return e; } template<> -auto game::EntityFactory::create( - entt::registry &world, const glm::vec2 &pos, const glm::vec2 &size) -> entt::entity +auto game::EntityFactory::create( + ThePURGE &, entt::registry &world, const glm::vec2 &pos, const glm::vec2 &size) -> entt::entity { static auto holder = engine::Core::Holder{}; const auto e = world.create(); - world.emplace(e, pos.x, pos.y, get_z_layer()); + world.emplace(e, pos.x, pos.y, get_z_layer()); world.emplace(e, 0.f); world.emplace(e, size.x, size.y); world.emplace(e, engine::DrawableFactory::rectangle()); - engine::DrawableFactory::fix_color(world, e, {1, 1, 1}); - engine::DrawableFactory::fix_texture(world, e, holder.instance->settings().data_folder + "textures/door.png"); - - world.emplace(e, size.x, size.y); + engine::DrawableFactory::fix_color(world, e, {0.3, 0.3, 0.3, 1}); + engine::DrawableFactory::fix_texture( + world, e, holder.instance->settings().data_folder + TexturePath::floor_spawn, true, {0.0f, 0.0f, size.x, size.y}); world.emplace>(e); - world.emplace>(e); return e; } template<> -auto game::EntityFactory::create(entt::registry &world, const glm::vec2 &pos, const glm::vec2 &size) - -> entt::entity +auto game::EntityFactory::create( + ThePURGE &, entt::registry &world, const glm::vec2 &pos, const glm::vec2 &size) -> entt::entity { + static auto holder = engine::Core::Holder{}; + const auto e = world.create(); - world.emplace(e, pos.x, pos.y, get_z_layer()); + world.emplace(e, pos.x, pos.y, get_z_layer()); world.emplace(e, 0.f); world.emplace(e, size.x, size.y); - // world.emplace(e, engine::DrawableFactory::rectangle()); - // engine::DrawableFactory::fix_color(world, e, {1, 1, 1}); - // engine::DrawableFactory::fix_texture(world, e, holder.instance->settings().data_folder + "textures/wall.jpg"); - world.emplace(e, size.x, size.y); + world.emplace(e, engine::DrawableFactory::rectangle()); + engine::DrawableFactory::fix_color(world, e, {0.3, 0.3, 0.3, 1}); + engine::DrawableFactory::fix_texture( + world, e, holder.instance->settings().data_folder + TexturePath::floor_boss, true, {0.0f, 0.0f, size.x, size.y}); world.emplace>(e); - world.emplace>(e); return e; } template<> -auto game::EntityFactory::create( - entt::registry &world, const glm::vec2 &pos, const glm::vec2 &size) -> entt::entity +auto game::EntityFactory::create( + ThePURGE &, entt::registry &world, const glm::vec2 &pos, const glm::vec2 &size) -> entt::entity { + static auto holder = engine::Core::Holder{}; + const auto e = world.create(); - world.emplace(e, pos.x, pos.y, get_z_layer()); + world.emplace(e, pos.x, pos.y, get_z_layer()); world.emplace(e, 0.f); world.emplace(e, size.x, size.y); world.emplace(e, engine::DrawableFactory::rectangle()); - engine::DrawableFactory::fix_color(world, e, {1, 1, 0}); + engine::DrawableFactory::fix_color(world, e, {0.3, 0.3, 0.3, 1}); + engine::DrawableFactory::fix_texture( + world, e, holder.instance->settings().data_folder + TexturePath::floor_corridor, true, {0.0f, 0.0f, size.x, size.y}); world.emplace>(e); return e; } template<> -auto game::EntityFactory::create(entt::registry &world, const glm::vec2 &pos, const glm::vec2 &size) - -> entt::entity +auto game::EntityFactory::create( + ThePURGE &, entt::registry &world, const glm::vec2 &pos, const glm::vec2 &size) -> entt::entity { static auto holder = engine::Core::Holder{}; const auto e = world.create(); - world.emplace>(e); - world.emplace(e, pos.x, pos.y, get_z_layer()); - world.emplace(e, 0.02 * (std::rand() & 1), 0.02 * (std::rand() & 1)); + world.emplace(e, pos.x, pos.y, get_z_layer()); world.emplace(e, 0.f); world.emplace(e, size.x, size.y); - world.emplace(e, 1.0, 1.0); world.emplace(e, engine::DrawableFactory::rectangle()); - engine::DrawableFactory::fix_color(world, e, {1, 0, 0}); - engine::DrawableFactory::fix_texture(world, e, holder.instance->settings().data_folder + "textures/enemy.png"); - world.emplace(e, 10.0f); - world.emplace(e, 3.0f); - world.emplace(e, 2.0f); - world.emplace(e, 1.0f, 1.0f); - - auto &slots = world.emplace(e); - slots.spells[0] = Spell::create(SpellFactory::ENEMY_ATTACK); + engine::DrawableFactory::fix_color(world, e, {1, 1, 1, 1}); + engine::DrawableFactory::fix_texture(world, e, holder.instance->settings().data_folder + "img/map/door.png"); + world.emplace(e, size.x, size.y); + world.emplace>(e); + world.emplace>(e); return e; } template<> -auto game::EntityFactory::create(entt::registry &world, const glm::vec2 &pos, const glm::vec2 &size) - -> entt::entity +auto game::EntityFactory::create( + ThePURGE &, entt::registry &world, const glm::vec2 &pos, const glm::vec2 &size) -> entt::entity { - static auto holder = engine::Core::Holder{}; - const auto e = world.create(); - world.emplace>(e); - world.emplace>(e); - world.emplace(e, pos.x, pos.y, get_z_layer()); - world.emplace(e, 0.0, 0.0); + world.emplace(e, pos.x, pos.y, get_z_layer()); world.emplace(e, 0.f); world.emplace(e, size.x, size.y); - world.emplace(e, 1.5, 3.0); - world.emplace(e, engine::DrawableFactory::rectangle()); - world.emplace(e, 10.0f); - world.emplace(e, 5.0f); - world.emplace(e, false, 2000ms, 0ms); - world.emplace(e, false, false, "bleed", 2000ms, 0ms, 5000ms, 0ms); - world.emplace(e, 1.0f); - world.emplace(e, 100.0f, 100.0f); - engine::DrawableFactory::fix_color(world, e, {0.95f, 0.95f, 0.95f}); - - // todo : add cache - auto &sp = world.emplace( - e, engine::Spritesheet::from_json(holder.instance->settings().data_folder + "anims/bosses/onlyone/boss.data.json")); - sp.current_animation = "hold_right"; - - engine::DrawableFactory::fix_texture(world, e, holder.instance->settings().data_folder + sp.file); - - auto &slots = world.emplace(e); - - // TODO: actual boss spells - slots.spells[0] = Spell::create(SpellFactory::ID::SWORD_ATTACK); - slots.spells[1] = Spell::create(SpellFactory::ID::PIERCING_ARROW); - + world.emplace(e, size.x, size.y); + world.emplace>(e); + world.emplace>(e); return e; } template<> -auto game::EntityFactory::create( - entt::registry &world, [[maybe_unused]] const glm::vec2 &pos, [[maybe_unused]] const glm::vec2 &size) -> entt::entity -{ - auto player = world.create(); - - world.emplace>(player); - world.emplace(player, 0.0, 0.0, EntityFactory::get_z_layer()); - world.emplace(player, 0.0, 0.0); - world.emplace(player, 0.f); - world.emplace(player, 0.0, 0.0); - world.emplace(player, 1.5, 2.5); - world.emplace(player, 0.75, 2.0); - world.emplace(player, engine::DrawableFactory::rectangle()); - world.emplace(player, 0u, 0u, 10u); - world.emplace(player); - world.emplace(player); - world.emplace(player); - world.emplace(player, 0); - world.emplace(player, glm::vec2(1.f, 0.f)); - world.emplace(player, glm::vec2(0.f, 0.f), glm::vec2(0.f, 0.f)); - - engine::DrawableFactory::fix_color(world, player, {1.0f, 1.0f, 1.0f}); - - // class dependant, see `GameLogic::apply_class_to_player` - world.emplace(player); - world.emplace(player, 1.f, 1.f); - world.emplace(player, 0.f); - // -- - - return player; -} - -template<> -auto game::EntityFactory::create(entt::registry &world, const glm::vec2 &pos, const glm::vec2 &size) - -> entt::entity -{ - static auto holder = engine::Core::Holder{}; - - auto key = world.create(); - world.emplace>(key); - world.emplace(key); - world.emplace(key, pos.x, pos.y, get_z_layer()); - world.emplace(key, 0.f); - world.emplace(key, size.x, size.y); - world.emplace(key, engine::DrawableFactory::rectangle()); - engine::DrawableFactory::fix_color(world, key, {1, 1, 0}); - engine::DrawableFactory::fix_texture(world, key, holder.instance->settings().data_folder + "textures/key.png"); - return key; -} - -template<> -auto game::EntityFactory::create( - entt::registry &world, const glm::vec2 &pos, const glm::vec2 &size) -> entt::entity +auto game::EntityFactory::create( + ThePURGE &, entt::registry &world, const glm::vec2 &pos, const glm::vec2 &size) -> entt::entity { - static auto holder = engine::Core::Holder{}; - - auto e = world.create(); - - world.emplace(e, pos.x, pos.y, get_z_layer()); + const auto e = world.create(); + world.emplace(e, pos.x, pos.y, get_z_layer()); world.emplace(e, 0.f); world.emplace(e, size.x, size.y); world.emplace(e, engine::DrawableFactory::rectangle()); - engine::DrawableFactory::fix_color(world, e, {0.15, 0.15, 0.15}); - engine::DrawableFactory::fix_texture(world, e, holder.instance->settings().data_folder + "textures/background.jpg"); - + engine::DrawableFactory::fix_color(world, e, {1, 1, 0, 1}); + world.emplace>(e); return e; } diff --git a/src/Application/src/factory/ParticuleFactory.cpp b/src/Application/src/factory/ParticuleFactory.cpp index 15962e7..d7d6db4 100644 --- a/src/Application/src/factory/ParticuleFactory.cpp +++ b/src/Application/src/factory/ParticuleFactory.cpp @@ -1,18 +1,26 @@ #include #include +#include #include -#include +#include #include #include +#include "models/Spell.hpp" + #include "component/all.hpp" #include "factory/ParticuleFactory.hpp" #include "factory/EntityFactory.hpp" +#include + +using namespace std::chrono_literals; template<> -auto game::ParticuleFactory::create(entt::registry &world, const glm::vec2 &pos) -> void +auto game::ParticuleFactory::create( + entt::registry &world, const glm::vec2 &pos, const glm::vec3 &color) -> void { + constexpr auto particule_count = 10.0f; constexpr auto speed = 2.0f; @@ -21,12 +29,66 @@ auto game::ParticuleFactory::create(entt::regist const auto particule_pos = pos + glm::vec2{std::cos(angle), std::sin(angle)}; auto e = world.create(); - world.emplace(e, particule_pos.x, particule_pos.y, EntityFactory::get_z_layer()); + world.emplace( + e, particule_pos.x, particule_pos.y, EntityFactory::get_z_layer()); world.emplace(e, 0.1, 0.1); + world.emplace(e, 0.f); world.emplace(e, (particule_pos.x - pos.x) * speed, (particule_pos.y - pos.y) * speed); world.emplace(e, engine::DrawableFactory::rectangle()); - world.emplace(e, 600ms); + world.emplace(e, 300ms); + world.emplace(e, Particule::HITMARKER); + engine::DrawableFactory::fix_color(world, e, {color.x / 255.0f, color.y / 255.0f, color.z / 255.0f, 1.0f}); + } +} + +template<> +auto game::ParticuleFactory::create( + entt::registry &world, const glm::vec2 &pos, const glm::vec3 &color) -> void +{ + constexpr auto particule_count = 10.0f; + constexpr auto speed = 2.0f; + + + for (float i = 0; i != particule_count; i++) { + const auto angle = i * 2 * std::numbers::pi_v / particule_count; + const auto particule_pos = pos + glm::vec2{std::cos(angle), std::sin(angle)}; + + auto e = world.create(); + world.emplace( + e, particule_pos.x, particule_pos.y, EntityFactory::get_z_layer()); + world.emplace(e, 0.1, 0.1); + world.emplace(e, 0.f); + world.emplace(e, -(particule_pos.x - pos.x) * speed, -(particule_pos.y - pos.y) * speed); + world.emplace(e, engine::DrawableFactory::rectangle()); + world.emplace(e, 300ms); + world.emplace(e, Particule::HITMARKER); + engine::DrawableFactory::fix_color(world, e, {color.x / 255.0f, color.y / 255.0f, color.z / 255.0f, 1.0f}); + } +} + +template<> +auto game::ParticuleFactory::create( + entt::registry &world, const glm::vec2 &pos, const glm::vec3 &color) -> void +{ + constexpr auto particule_count = 10.0f; + constexpr auto speed = 2.0f; + auto old_angle = 9 * 2 * std::numbers::pi_v / particule_count; + auto old_value = pos + glm::vec2{std::cos(old_angle), std::sin(old_angle)}; + + for (float i = 0; i != particule_count; i++) { + const auto angle = i * 2 * std::numbers::pi_v / particule_count; + const auto particule_pos = pos + glm::vec2{std::cos(angle), std::sin(angle)}; + auto e = world.create(); + world.emplace( + e, particule_pos.x, particule_pos.y, EntityFactory::get_z_layer()); + world.emplace(e, 0.1, 0.1); + world.emplace(e, 0.f); + world.emplace( + e, (old_value.x - particule_pos.x) * speed, (old_value.y - particule_pos.y) * speed); + world.emplace(e, engine::DrawableFactory::rectangle()); + world.emplace(e, 600ms); world.emplace(e, Particule::HITMARKER); - engine::DrawableFactory::fix_color(world, e, {255.0f / 255.0f, 92.0f / 255.0f, 103.0f / 255.0f}); + engine::DrawableFactory::fix_color(world, e, {color.x / 255.0f, color.y / 255.0f, color.z / 255.0f, 1.0f}); + old_value = particule_pos; } } diff --git a/src/Application/src/factory/SpellFactory.cpp b/src/Application/src/factory/SpellFactory.cpp index 3d672c5..a2bbf16 100644 --- a/src/Application/src/factory/SpellFactory.cpp +++ b/src/Application/src/factory/SpellFactory.cpp @@ -4,213 +4,90 @@ #include #include -#include +#include #include #include #include +#include "models/Spell.hpp" + #include "component/all.hpp" #include "factory/SpellFactory.hpp" using namespace std::chrono_literals; - -template<> -auto game::SpellFactory::create( - entt::registry &world, entt::entity caster, const glm::dvec2 &direction) -> entt::entity -{ - static auto holder = engine::Core::Holder{}; - - spdlog::info("Casting an enemy attack"); - const auto &caster_pos = world.get(caster); - const auto &attack_damage = world.get(caster); - - auto color = world.has>(caster) ? glm::vec3{0, 1, 0} : glm::vec3{1, 1, 1}; - - holder.instance->getAudioManager().getSound(holder.instance->settings().data_folder + "sounds/spells/stick.wav")->play(); - const auto spell = world.create(); - world.emplace>(spell); - world.emplace(spell, 200ms); - world.emplace(spell, attack_damage.damage); - world.emplace(spell, engine::DrawableFactory::rectangle()); - engine::DrawableFactory::fix_color(world, spell, std::move(color)); - auto &sp = world.emplace( - spell, - engine::Spritesheet::from_json( - holder.instance->settings().data_folder + "anims/spells/farmer_attack/farmer_anim.data.json")); - engine::DrawableFactory::fix_texture(world, spell, holder.instance->settings().data_folder + sp.file); - world.emplace(spell, caster_pos.x + direction.x, caster_pos.y + direction.y, -1.0); - world.emplace(spell, glm::acos(glm::dot({1.f, 0.f}, direction))); - world.emplace(spell, 1.0, 1.0); - world.emplace(spell, 0.2, 0.7); - world.emplace(spell, caster); - return spell; -} - -template<> -auto game::SpellFactory::create( - entt::registry &world, entt::entity caster, const glm::dvec2 &direction) -> entt::entity -{ - static auto holder = engine::Core::Holder{}; - - spdlog::info("Casting a shovel attack"); - const auto &caster_pos = world.get(caster); - const auto &attack_damage = world.get(caster); - - auto color = world.has>(caster) ? glm::vec3{0, 1, 0} : glm::vec3{1, 1, 1}; - - holder.instance->getAudioManager().getSound(holder.instance->settings().data_folder + "sounds/spells/stick.wav")->play(); - const auto spell = world.create(); - world.emplace>(spell); - world.emplace(spell, 200ms); - world.emplace(spell, attack_damage.damage); - world.emplace(spell, engine::DrawableFactory::rectangle()); - engine::DrawableFactory::fix_color(world, spell, std::move(color)); - auto &sp = world.emplace( - spell, - engine::Spritesheet::from_json( - holder.instance->settings().data_folder + "anims/spells/farmer_attack/farmer_anim.data.json")); - engine::DrawableFactory::fix_texture(world, spell, holder.instance->settings().data_folder + sp.file); - world.emplace(spell, caster_pos.x + direction.x, caster_pos.y + direction.y * 1.5, -1.0); - world.emplace(spell, glm::acos(glm::dot({1.f, 0.f}, direction))); - world.emplace(spell, 1.5, 1.5); - world.emplace(spell, 0.2, 0.7); - world.emplace(spell, caster); - return spell; -} - -template<> -auto game::SpellFactory::create( - entt::registry &world, entt::entity caster, const glm::dvec2 &direction) -> entt::entity -{ - static engine::Core::Holder holder{}; - - spdlog::info("Casting a sword attack"); - - const auto &caster_pos = world.get(caster); - const auto &attack_damage = world.get(caster); - - auto color = world.has>(caster) ? glm::vec3{0, 1, 0} : glm::vec3{1, 1, 1}; - - holder.instance->getAudioManager() - .getSound(holder.instance->settings().data_folder + "sounds/spells/soldier_attack.wav") - ->play(); - const auto spell = world.create(); - world.emplace>(spell); - world.emplace(spell, 200ms); - world.emplace(spell, attack_damage.damage); - world.emplace(spell, engine::DrawableFactory::rectangle()); - engine::DrawableFactory::fix_color(world, spell, std::move(color)); - auto &sp = world.emplace( - spell, - engine::Spritesheet::from_json( - holder.instance->settings().data_folder + "anims/spells/soldier_attack/soldier_attack.data.json")); - engine::DrawableFactory::fix_texture(world, spell, holder.instance->settings().data_folder + sp.file); - world.emplace(spell, caster_pos.x + direction.x, caster_pos.y + direction.y, -1.0); - world.emplace(spell, 0.f); - world.emplace(spell, 0.7, 0.7); - world.emplace(spell, 0.7, 0.7); - world.emplace(spell, caster); - return spell; -} - -template<> -auto game::SpellFactory::create(entt::registry &world, entt::entity caster, const glm::dvec2 &direction) +auto game::SpellFactory::create(SpellDatabase &db, entt::registry &world, entt::entity caster, const glm::dvec2 &direction, const SpellData &data) -> entt::entity { static auto holder = engine::Core::Holder{}; - spdlog::info("Casting fireball"); + spdlog::trace("Casting a spell {}", data.name); - holder.instance->getAudioManager().getSound(holder.instance->settings().data_folder + "sounds/fire_cast.wav")->play(); + holder.instance->getAudioManager().getSound(holder.instance->settings().data_folder + data.audio_on_cast)->play(); const auto &caster_pos = world.get(caster); - const auto &attack_damage = world.get(caster); - - // note : should be in db.json - static constexpr auto speed = 5.0; - - const auto spell = world.create(); - world.emplace>(spell); - world.emplace(spell, 2000ms); - world.emplace(spell, attack_damage.damage * 1.5f); - world.emplace(spell, engine::DrawableFactory::rectangle()); - engine::DrawableFactory::fix_color(world, spell, {1, 1, 1}); - auto &sp = world.emplace( - spell, - engine::Spritesheet::from_json(holder.instance->settings().data_folder + "anims/spells/fireball/fireball.data.json")); - engine::DrawableFactory::fix_texture(world, spell, holder.instance->settings().data_folder + sp.file); - - world.emplace(spell, caster_pos.x + direction.x, caster_pos.y + direction.y, -1.0); // note : why -1 - world.emplace(spell, direction.x * speed, direction.y * speed); - world.emplace(spell, glm::acos(glm::dot({1.f, 0.f}, direction))); - world.emplace(spell, 2.0, 2.0); - world.emplace(spell, 0.7, 0.7); - world.emplace(spell, caster); - return spell; -} - -template<> -auto game::SpellFactory::create(entt::registry &world, entt::entity caster, const glm::dvec2 &) - -> entt::entity -{ - static auto holder = engine::Core::Holder{}; - - spdlog::info("debug Giant fireball"); - - const auto spell = world.create(); - world.emplace>(spell); - world.emplace(spell, 2000ms); - world.emplace(spell, 999999.0f); - world.emplace(spell, engine::DrawableFactory::rectangle()); - engine::DrawableFactory::fix_color(world, spell, {1, 1, 1}); - auto &sp = world.emplace( - spell, - engine::Spritesheet::from_json(holder.instance->settings().data_folder + "anims/spells/fireball/fireball.data.json")); - engine::DrawableFactory::fix_texture(world, spell, holder.instance->settings().data_folder + sp.file); - - world.emplace(spell, 0.0, 0.0, -1.0); // note : why -1 - world.emplace(spell, 0.0, 0.0); - world.emplace(spell, 0.0); - world.emplace(spell, 200.0, 200.0); - world.emplace(spell, 200.0, 200.0); - world.emplace(spell, caster); - return spell; -} - - -template<> -auto game::SpellFactory::create( - entt::registry &world, entt::entity caster, const glm::dvec2 &direction) -> entt::entity -{ - static engine::Core::Holder holder{}; - - spdlog::info("Casting a piercing arrow"); - - holder.instance->getAudioManager().getSound(holder.instance->settings().data_folder + "sounds/spells/stick.wav")->play(); - - static constexpr auto speed = 6.0; - - const auto &caster_pos = world.get(caster); - const auto &attack_damage = world.get(caster); - - const auto spell = world.create(); - world.emplace>(spell); - world.emplace(spell, 1000ms); - world.emplace(spell, attack_damage.damage * 1.25f); - world.emplace(spell, engine::DrawableFactory::rectangle()); - engine::DrawableFactory::fix_color(world, spell, {1, 1, 1}); - auto &sp = world.emplace( - spell, - engine::Spritesheet::from_json( - holder.instance->settings().data_folder + "anims/spells/shooter_attack/shooter_attack.data.json")); - engine::DrawableFactory::fix_texture(world, spell, holder.instance->settings().data_folder + sp.file); - world.emplace(spell, caster_pos.x + direction.x, caster_pos.y + direction.y, -1.0); // note : why -1 - world.emplace(spell, direction.x * speed, direction.y * speed); - world.emplace(spell, glm::acos(glm::dot({1.f, 0.f}, direction))); - world.emplace(spell, 0.7, 0.7); - world.emplace(spell, 0.7, 0.7); - world.emplace(spell, caster); - return spell; + for (int i = 0; i != data.quantity; i++) { + const auto spell = world.create(); + world.emplace>(spell); + world.emplace(spell, engine::DrawableFactory::rectangle()); + engine::DrawableFactory::fix_color(world, spell, {1, 1, 1, 1}); // todo : add color in db ? + [[maybe_unused]] const auto &pos = world.emplace( + spell, + caster_pos.x + (data.scale.x / 3.0 + data.offset_to_source_x) * direction.x, + caster_pos.y + (data.scale.y / 3.0 + data.offset_to_source_y) * direction.y, + -1.0); + world.emplace( + spell, glm::acos(glm::dot({1.f, 0.f}, direction)) * (direction.y < 0 ? -1.0 : 1.0) + i * data.angle); + + world.emplace(spell, data.scale); + world.emplace(spell, data.damage); + world.emplace(spell, data.lifetime); + + world.emplace( + spell, engine::Spritesheet::from_json(holder.instance->settings().data_folder + data.animation)); + engine::DrawableFactory::fix_spritesheet(world, spell, "default"); + + const auto cross = glm::cross(glm::dvec3(1, 0, 0), glm::dvec3(0, 1, 0)); + const auto matrix_rotation = glm::rotate(glm::dmat4(1.0f), i * data.angle, cross); + const auto rotated = glm::dvec3(matrix_rotation * glm::dvec4(direction.x, direction.y, 0.0f, 1.0f)); + + world.emplace(spell, rotated.x * data.speed, rotated.y * data.speed); + + world.emplace(spell, data.effects); + world.emplace(spell, data.targets); + + world.emplace(spell, caster); + + if (data.type[SpellData::Type::PROJECTILE]) world.emplace>(spell); + if (data.type[SpellData::Type::AOE]) world.emplace>(spell); + if (data.type[SpellData::Type::ON_DEATH]) { + world.emplace>(spell); + world.emplace(spell).spells[0] = db.instantiate(data.on_death); + } + if (data.type[SpellData::Type::SUMMONER]) { + world.emplace(spell, 1.0f, 1.0f); + world.emplace(spell, data.hitbox.width, data.hitbox.height); + world.emplace(spell, static_cast(0)); + world.emplace>(spell); + } else { + world.emplace(spell, data.hitbox); + } + +#ifndef NDEBUG + auto hitbox_entity = world.create(); + world.emplace>(hitbox_entity); + world.emplace(hitbox_entity, spell); + world.emplace(hitbox_entity, engine::DrawableFactory::rectangle()); + world.emplace(hitbox_entity, 0.f); + world.emplace( + hitbox_entity, pos.x, pos.y, EntityFactory::get_z_layer()); + world.emplace(hitbox_entity, data.hitbox.width, data.hitbox.height); + engine::DrawableFactory::fix_color(world, hitbox_entity, {1, 1, 1, 0.5}); + engine::DrawableFactory::fix_texture( + world, hitbox_entity, holder.instance->settings().data_folder + "img/transparent.png"); +#endif + } + + return {}; } diff --git a/src/Application/src/level/MapGenerator.cpp b/src/Application/src/level/MapGenerator.cpp deleted file mode 100644 index f4938a9..0000000 --- a/src/Application/src/level/MapGenerator.cpp +++ /dev/null @@ -1,284 +0,0 @@ -#include "level/MapGenerator.hpp" -#include -#include -#include -#include -#include "level/LevelTilemapBuilder.hpp" -#include "factory/EntityFactory.hpp" -#include "level/MapData.hpp" - -// todo : move this in Core -static std::default_random_engine random_engine{}; - -template -static auto randRange(T min, T max) -{ - assert(max > min); - - const auto r = random_engine(); - return min + static_cast(r % static_cast(max - min)); -} - -constexpr static bool isRoomValid(const game::TilemapBuilder &builder, const game::Room &r) -{ - const auto x2 = r.x + r.w; - const auto y2 = r.y + r.h; - - for (auto x = r.x; x < x2; ++x) { - for (auto y = r.y; y < y2; ++y) { - if (builder.at(glm::ivec2{x, y}) != game::TileEnum::NONE) { return false; } - } - } - - return true; -} - -static auto generateRoom(const game::TilemapBuilder &builder, const game::FloorGenParam ¶ms) -> game::Room -{ - game::Room r; - - std::uint32_t tries = 0; - do { - if (tries++ > 10000) return {0, 0, 0, 0}; - - // Width including walls (hence the +2) - r.w = randRange(params.minRoomSize + 2, params.maxRoomSize + 2 + 1); - r.h = randRange(params.minRoomSize + 2, params.maxRoomSize + 2 + 1); - - r.x = randRange(0, params.maxDungeonWidth - r.w); - r.y = randRange(0, params.maxDungeonHeight - r.h); - } while (!isRoomValid(builder, r)); - - return r; -} - -static auto placeRoomFloor(game::TilemapBuilder &builder, const game::Room &r, game::TileEnum floorTile) -> void -{ - const auto x2 = r.x + r.w; - const auto y2 = r.y + r.h; - - for (auto y = r.y + 1; y < y2 - 1; ++y) { - for (auto x = r.x + 1; x < x2 - 1; ++x) { builder[glm::ivec2{x, y}] = floorTile; } - } -} - -// If gap is even, center will be chosen randomly between the two center tiles -static int getOnePossibleCenterOf(int a, int b) -{ - int result = (a + b) / 2; - - if ((a - b) % 2 == 0) result -= randRange(0, 1); - - return result; -} - -// If room size is even, center will be chosen randomly between the two center tiles -static auto getOnePossibleCenterOf(const game::Room &r) -> glm::ivec2 -{ - return {getOnePossibleCenterOf(r.x, r.x + r.w), getOnePossibleCenterOf(r.y, r.y + r.h)}; -} - -static void generatorCorridor( - game::TilemapBuilder &builder, const game::FloorGenParam ¶ms, const game::Room &r1, const game::Room &r2) -{ - auto start = getOnePossibleCenterOf(r1); - auto end = getOnePossibleCenterOf(r2); - - auto vertical = [&](glm::ivec2 pos, int width) -> glm::ivec2 { - const auto widthOffset = getOnePossibleCenterOf(0, width); - - const auto minX = pos.x - widthOffset; - const auto maxX = pos.x + (width - widthOffset); - - while (pos.y != end.y) { - for (auto x = minX; x < maxX; ++x) { - if (builder.at(glm::ivec2{x, pos.y}) == game::TileEnum::NONE) { - builder[glm::ivec2{x, pos.y}] = game::TileEnum::FLOOR_CORRIDOR; - } - } - - pos.y += pos.y < end.y ? 1 : -1; - } - - return pos; - }; - - auto horizontal = [&](glm::ivec2 pos, int width) -> glm::ivec2 { - const auto widthOffset = getOnePossibleCenterOf(0, width); - - const auto minY = pos.y - widthOffset; - const auto maxY = pos.y + (width - widthOffset); - - while (pos.x != end.x) { - for (auto y = minY; y < maxY; ++y) { - if (builder.at(glm::ivec2{pos.x, y}) == game::TileEnum::NONE) { - builder[glm::ivec2{pos.x, y}] = game::TileEnum::FLOOR_CORRIDOR; - } - } - - pos.x += pos.x < end.x ? 1 : -1; - } - - return pos; - }; - - // -2 for walls, and -1 to be safe about the random center not making wall go off bound - auto maxWidth = std::min(r1.h - 3, r2.w - 3); - auto width = - randRange(std::min(maxWidth, params.minCorridorWidth), std::min(maxWidth + 1, params.maxCorridorWidth + 1)); - - if (randRange(0, 1)) { - horizontal(vertical(start, width), width); - } else { - vertical(horizontal(start, width), width); - } -} - -static void placeWalls(game::TilemapBuilder &builder) -{ - // For each empty tile, if one it's neighbour is a floor, make it a wall - static constexpr auto neighbours = - std::to_array({{-1, -1}, {+0, -1}, {+1, -1}, {-1, +0}, {+1, +0}, {-1, +1}, {+0, +1}, {+1, +1}}); - - glm::ivec2 it; - - for (it.y = 0; it.y < builder.getSize().y; ++it.y) { - for (it.x = 0; it.x < builder.getSize().x; ++it.x) { - if (builder.at(glm::ivec2{it.x, it.y}) != game::TileEnum::NONE - && builder.at(glm::ivec2{it.x, it.y}) != game::TileEnum::RESERVED) - continue; - - for (const auto &n : neighbours) { - const auto checkPos = it + n; - - if (checkPos.x > 0 && checkPos.x < builder.getSize().x && checkPos.y > 0 - && checkPos.y < builder.getSize().y && IS_FLOOR(builder.at(glm::ivec2{checkPos.x, checkPos.y}))) { - builder[glm::ivec2{it.x, it.y}] = game::TileEnum::WALL; - break; - } - } - } - } -} - -static auto placeBossRoomExitDoor(game::TilemapBuilder &builder, game::Room &r) -> void -{ - // Strategy : - // Check room boundaries, find the boss room entrance, place a door at the central opposite of the room with the right orientation - // Reduce room size by one to make sure the door is not accessible by another room on the other side of the wall - - const auto x1 = r.x; - const auto x2 = r.x + r.w; - const auto y1 = r.y; - const auto y2 = r.y + r.h; - - // CHECK NORTH WALL - for (auto x = x1 + 1; x < x2 - 1; ++x) - if (IS_FLOOR(builder.at(glm::ivec2{x, y2 - 1}))) { - r.y += 1; - r.h -= 1; - - builder[glm::ivec2{x2 - 1 - (x - x1), r.y}] = game::TileEnum::EXIT_DOOR_FACING_NORTH; - return; - } - - // CHECK SOURTH WALL - for (auto x = x1 + 1; x < x2 - 1; ++x) - if (IS_FLOOR(builder.at(glm::ivec2{x, y1}))) { - r.h -= 1; - - builder[glm::ivec2{x2 - 1 - (x - x1), r.y + r.h - 1}] = game::TileEnum::EXIT_DOOR_FACING_SOUTH; - return; - } - - // CHECK WEST WALL - for (auto y = y1 + 1; y < y2 - 1; ++y) - if (IS_FLOOR(builder.at(glm::ivec2{x1, y}))) { - r.w -= 1; - - builder[glm::ivec2{r.x + r.w - 1, y2 - 1 - (y - y1)}] = game::TileEnum::EXIT_DOOR_FACING_WEST; - return; - } - - // CHECK EAST WALL - for (auto y = y1 + 1; y < y2 - 1; ++y) - if (IS_FLOOR(builder.at(glm::ivec2{x2 - 1, y}))) { - r.x += 1; - r.w -= 1; - - builder[glm::ivec2{r.x, y2 - 1 - (y - y1)}] = game::TileEnum::EXIT_DOOR_FACING_EAST; - return; - } - - assert(false && "Could not find boss room entrance, cannot place exit door"); -} - -static auto generateLevel(entt::registry &world, game::FloorGenParam params) -> game::MapData -{ - game::TilemapBuilder builder({params.maxDungeonWidth, params.maxDungeonHeight}); - - auto roomCount = randRange(params.minRoomCount, params.maxRoomCount); - - std::vector rooms; - rooms.reserve(roomCount); - - rooms.emplace_back(generateRoom(builder, params)); - placeRoomFloor(builder, rooms[0], game::TileEnum::RESERVED); - - for (std::size_t i = 0; i < roomCount - 1; ++i) { - auto room = generateRoom(builder, params); - if (room.w == 0 && room.h == 0) break; // Failed to place room - - // Reserve the space, so multiple rooms don't spawn at the same place - placeRoomFloor(builder, room, game::TileEnum::RESERVED); - rooms.emplace_back(room); - - generatorCorridor(builder, params, rooms[i], rooms[i + 1]); - } - - game::MapData result; - result.spawn = *rooms.begin(); - result.boss = *rooms.rbegin(); - - for (auto i = 1ul; i < rooms.size() - 1; ++i) result.regularRooms.push_back(rooms[i]); - - placeRoomFloor(builder, result.spawn, game::TileEnum::FLOOR_SPAWN); - for (const auto &r : result.regularRooms) placeRoomFloor(builder, r, game::TileEnum::FLOOR_NORMAL_ROOM); - - placeBossRoomExitDoor(builder, result.boss); - placeRoomFloor(builder, result.boss, game::TileEnum::FLOOR_BOSS_ROOM); - - placeWalls(builder); - - builder.build(world); - - return result; -} - -static void spawnMobsIn(entt::registry &world, game::FloorGenParam params, const game::Room &r) -{ - if (params.mobDensity == 0) return; - - for (auto x = r.x + 1; x < r.x + r.w - 1; ++x) { - for (auto y = r.y + 1; y < r.y + r.h - 1; ++y) { - if (randRange(0, static_cast(1.0f / params.mobDensity)) == 0) { - game::EntityFactory::create(world, glm::vec2{x + 0.5, y + 0.5}, {1.0, 1.0}); - } - } - } -} - -auto game::generateFloor(entt::registry &world, const game::FloorGenParam ¶ms, std::optional seed) -> MapData -{ - if (seed) random_engine.seed(seed.value()); // todo : move this in engine::Core - - auto data = generateLevel(world, params); - - for (auto &r : data.regularRooms) spawnMobsIn(world, params, r); - game::EntityFactory::create( - world, glm::vec2{data.boss.x + data.boss.w * 0.5, data.boss.y + data.boss.h * 0.5}, {3.0, 3.0}); - - data.nextFloorSeed = static_cast(random_engine()); - - return data; -} diff --git a/src/Application/src/menu/AMenu.cpp b/src/Application/src/menu/AMenu.cpp new file mode 100644 index 0000000..3c87f81 --- /dev/null +++ b/src/Application/src/menu/AMenu.cpp @@ -0,0 +1,108 @@ +#include +#include +#include +#include + +#include "models/Spell.hpp" + +#include "ThePURGE.hpp" +#include "menu/AMenu.hpp" + +void game::AMenu::onEvent(entt::registry &world, ThePURGE &game, const engine::Event &e) +{ + static auto holder = engine::Core::Holder{}; + + if (!m_createCalled) { + m_createCalled = true; + create(world, game); + } + + if (m_shouldResetInputs) { + resetInputs(); + m_shouldResetInputs = false; + } + + + std::visit( + engine::overloaded{ + [&](const engine::Pressed &key) { + switch (key.source.key) { + case GLFW_KEY_W: + case GLFW_KEY_Z: + case GLFW_KEY_UP: m_up = true; break; + case GLFW_KEY_D: + case GLFW_KEY_RIGHT: m_right = true; break; + case GLFW_KEY_S: + case GLFW_KEY_DOWN: m_down = true; break; + case GLFW_KEY_Q: + case GLFW_KEY_A: + case GLFW_KEY_LEFT: m_left = true; break; + case GLFW_KEY_ENTER: m_select = true; break; + case GLFW_KEY_ESCAPE: m_close = true; break; + default: break; + } + }, + [&](const engine::Pressed &joy) { + switch (joy.source.button) { + case engine::Joystick::UP: m_up = true; break; + case engine::Joystick::DOWN: m_down = true; break; + case engine::Joystick::LEFT: m_left = true; break; + case engine::Joystick::RIGHT: m_right = true; break; + case engine::Joystick::ACTION_BOTTOM: m_select = true; break; + default: break; + } + }, + [&](const engine::Moved &joy) { + switch (joy.source.axis) { + case engine::Joystick::LSX: + case engine::Joystick::LSY: { + auto joystick = holder.instance->getJoystick(joy.source.id); + + float x = (*joystick)->axes[engine::Joystick::LSX]; + float y = -(*joystick)->axes[engine::Joystick::LSY]; + + m_up = !m_recoveringUp && y > kTriggerThreshold; + m_recoveringUp = (!m_recoveringUp && m_up) || (m_recoveringUp && y > kRecoveryThreshold); + + m_down = !m_recoveringDown && y < -kTriggerThreshold; + m_recoveringDown = (!m_recoveringDown && m_down) || (m_recoveringDown && y < -kRecoveryThreshold); + + m_left = !m_recoveringLeft && x < -kTriggerThreshold; + m_recoveringLeft = (!m_recoveringLeft && m_left) || (m_recoveringLeft && x < -kRecoveryThreshold); + + m_right = !m_recoveringRight && x > kTriggerThreshold; + m_recoveringRight = (!m_recoveringRight && m_right) || (m_recoveringRight && x > kRecoveryThreshold); + } break; + default: break; + } + }, + [&](auto) {}, + }, + e); + + event(world, game, e); +} + +void game::AMenu::onDraw(entt::registry &world, ThePURGE &game) +{ + if (m_shouldResetInputs) resetInputs(); + + m_shouldResetInputs = true; + + if (!m_createCalled) { + m_createCalled = true; + create(world, game); + } + + draw(world, game); +} + +void game::AMenu::resetInputs() noexcept +{ + m_up = false; + m_down = false; + m_left = false; + m_right = false; + m_select = false; + m_close = false; +} diff --git a/src/Application/src/menu/Credits.cpp b/src/Application/src/menu/Credits.cpp new file mode 100644 index 0000000..289cd35 --- /dev/null +++ b/src/Application/src/menu/Credits.cpp @@ -0,0 +1,60 @@ +#include +#include +#include +#include + +#include "models/Spell.hpp" +#include "widgets/helpers.hpp" + +#include "menu/Credits.hpp" +#include "menu/MainMenu.hpp" +#include "ThePURGE.hpp" + +void game::menu::Credits::create(entt::registry &, ThePURGE &) +{ + static auto holder = engine::Core::Holder{}; + + const auto &dataFolder = holder.instance->settings().data_folder; + + m_texture = engine::helper::loadTexture(dataFolder + "img/menu/credits.png"); +} + +void game::menu::Credits::draw(entt::registry &, ThePURGE &game) +{ + static auto holder = engine::Core::Holder{}; + + ImGui::SetNextWindowPos(ImVec2(0, 0)); + ImGui::SetNextWindowSize(helper::frac2pixel({1.f, 1.f})); + ImGui::Begin("Credits", nullptr, ImGuiWindowFlags_NoDecoration); + helper::drawTexture(m_texture, ImVec2(0, 0), helper::frac2pixel(ImVec2(1, 1))); + ImGui::End(); + + + if (close()) { + holder.instance->getAudioManager().getSound(holder.instance->settings().data_folder + "sounds/menu/back.wav")->play(); + + game.setMenu(std::make_unique()); + } +} + +void game::menu::Credits::event(entt::registry &, ThePURGE &, const engine::Event &e) +{ + std::visit( + engine::overloaded{ + [&](const engine::Pressed &joy) { + switch (joy.source.button) { + case engine::Joystick::ACTION_RIGHT: forceClose(true); break; + case engine::Joystick::ACTION_BOTTOM: forceClose(true); break; + default: return; + } + }, + [&](const engine::Pressed &key) { + switch (key.source.key) { + case GLFW_KEY_ENTER: forceClose(true); break; + default: break; + } + }, + [&](auto) {}, + }, + e); +} diff --git a/src/Application/src/menu/GameOver.cpp b/src/Application/src/menu/GameOver.cpp new file mode 100644 index 0000000..4df0167 --- /dev/null +++ b/src/Application/src/menu/GameOver.cpp @@ -0,0 +1,142 @@ +#include +#include +#include +#include +#include + +#include "models/Spell.hpp" + +#include "component/all.hpp" + +#include "ThePURGE.hpp" +#include "menu/GameOver.hpp" +#include "menu/MainMenu.hpp" + +#include "widgets/Fonts.hpp" + +auto game::menu::GameOver::clean_world(entt::registry &world) -> void { Stage{}.clear(world, true); } + +void game::menu::GameOver::create(entt::registry &, ThePURGE &) +{ + static auto holder = engine::Core::Holder{}; + + const auto &dataFolder = holder.instance->settings().data_folder; + + + m_backgroundTexture = engine::helper::loadTexture(dataFolder + "img/menu/game_over/background.png"); + + // clang-format off + + // SAME ORDER AS `Button` ENUM + m_buttons.emplace_back(GUITexture{ + helper::getTexture("img/menu/game_over/btn_playagain_selected.png"), + helper::from1080p(701, 665), + helper::from1080p(515, 156) + }); + m_buttons.emplace_back(GUITexture{ + helper::getTexture("img/menu/game_over/btn_menu_selected.png"), + helper::from1080p(822, 884), + helper::from1080p(276, 149) + }); + // clang-format on +} + +void game::menu::GameOver::draw(entt::registry &world, ThePURGE &game) +{ + static auto holder = engine::Core::Holder{}; + + ImGui::SetNextWindowPos(ImVec2(-5, -1)); + const auto winSize = helper::frac2pixel({1.f, 1.f}); + ImGui::SetNextWindowSize(ImVec2(winSize.x + 10, winSize.y + 6)); + + ImGui::SetNextWindowBgAlpha(0.f); + + ImGui::Begin("GameOver", nullptr, ImGuiWindowFlags_NoDecoration); + + helper::drawTexture( + m_backgroundTexture, + ImVec2(0, 0), + helper::frac2pixel({1.f, 1.f}), + ImVec4(1, 1, 1, static_cast(std::clamp(m_timeElapsed, 0.0, 1.0)))); + + if (up() && m_selected > 0) { + holder.instance->getAudioManager().getSound(holder.instance->settings().data_folder + "sounds/menu/change.wav")->play(); + + m_selected--; + } + if (down() && m_selected < Button::MAX - 1) { + holder.instance->getAudioManager().getSound(holder.instance->settings().data_folder + "sounds/menu/change.wav")->play(); + + m_selected++; + } + + helper::drawTexture(m_buttons.at(static_cast(m_selected))); + + drawGameStats(); + + ImGui::End(); + + + if (close()) { + m_selected = Button::MENU; + forceSelect(true); + } + if (select()) { + holder.instance->getAudioManager().getSound(holder.instance->settings().data_folder + "sounds/menu/accept.wav")->play(); + clean_world(world); + + switch (m_selected) { + case Button::PLAY_AGAIN: + game.setMenu(nullptr); + game.logics()->onGameStart.publish(world); + return; + case Button::MENU: game.setMenu(std::make_unique()); return; + } + } +} + +void game::menu::GameOver::drawGameStats() +{ + const auto time = m_stats.gameTimeInSeconds; + + const int min = static_cast(time / 60); + const int sec = static_cast(time - min * 60); + const int ms = static_cast((time - min * 60 - sec) * 1000); + + helper::drawText( + helper::frac2pixel(helper::from1080p(1018, 321)), + fmt::format("{}m {}.{}s", min, sec, ms), + ImVec4(1, 1, 1, 1), + Fonts::kimberley_62); + + helper::drawText( + helper::frac2pixel(helper::from1080p(1018, 392)), + fmt::format("{}", m_stats.finalLevel), + ImVec4(1, 1, 1, 1), + Fonts::kimberley_62); + + helper::drawText( + helper::frac2pixel(helper::from1080p(1018, 463)), + fmt::format("{}", m_stats.enemyKilled), + ImVec4(1, 1, 1, 1), + Fonts::kimberley_62); +} + +void game::menu::GameOver::event(entt::registry &world, ThePURGE &game, const engine::Event &e) +{ + std::visit( + engine::overloaded{ + [&](const engine::Pressed &joy) { + switch (joy.source.button) { + case engine::Joystick::CENTER2: { + clean_world(world); + game.setMenu(std::make_unique()); + } break; + default: break; + } + }, + [&](const engine::TimeElapsed &dt) { m_timeElapsed += static_cast(dt.elapsed.count()) / 1e9; }, + [&](auto) {}, + }, + e); +} diff --git a/src/Application/src/menu/HowToPlay.cpp b/src/Application/src/menu/HowToPlay.cpp new file mode 100644 index 0000000..e238afb --- /dev/null +++ b/src/Application/src/menu/HowToPlay.cpp @@ -0,0 +1,78 @@ +#include +#include +#include +#include + +#include "models/Spell.hpp" +#include "widgets/helpers.hpp" + +#include "menu/HowToPlay.hpp" +#include "menu/MainMenu.hpp" +#include "ThePURGE.hpp" + +void game::menu::HowToPlay::create(entt::registry &, ThePURGE &) +{ + static auto holder = engine::Core::Holder{}; + + const auto &dataFolder = holder.instance->settings().data_folder; + + m_howToPlay = engine::helper::loadTexture(dataFolder + "img/menu/how_to_play/howtoplay.png"); + m_controls = engine::helper::loadTexture(dataFolder + "img/menu/how_to_play/controls.png"); +} + +void game::menu::HowToPlay::draw(entt::registry &, ThePURGE &game) +{ + static auto holder = engine::Core::Holder{}; + + ImGui::SetNextWindowPos(ImVec2(0, 0)); + ImGui::SetNextWindowSize(helper::frac2pixel({1.f, 1.f})); + ImGui::Begin("HowToPlay", nullptr, ImGuiWindowFlags_NoDecoration); + + if (right()) { + holder.instance->getAudioManager().getSound(holder.instance->settings().data_folder + "sounds/menu/change.wav")->play(); + + m_currentTab = Tab::CONTROLS; + } + if (left()) { + holder.instance->getAudioManager().getSound(holder.instance->settings().data_folder + "sounds/menu/change.wav")->play(); + + m_currentTab = Tab::HOW_TO_PLAY; + } + + switch (m_currentTab) { + case Tab::HOW_TO_PLAY: helper::drawTexture(m_howToPlay, ImVec2(0, 0), helper::frac2pixel(ImVec2(1, 1))); break; + case Tab::CONTROLS: helper::drawTexture(m_controls, ImVec2(0, 0), helper::frac2pixel(ImVec2(1, 1))); break; + } + + ImGui::End(); + + if (close()) { + holder.instance->getAudioManager().getSound(holder.instance->settings().data_folder + "sounds/menu/back.wav")->play(); + + game.setMenu(std::make_unique()); + } +} + +void game::menu::HowToPlay::event(entt::registry &, ThePURGE &, const engine::Event &e) +{ + std::visit( + engine::overloaded{ + [&](const engine::Pressed &joy) { + switch (joy.source.button) { + case engine::Joystick::ACTION_RIGHT: forceClose(true); break; + case engine::Joystick::ACTION_BOTTOM: forceClose(true); break; + case engine::Joystick::LS: forceLeft(true); break; + case engine::Joystick::RS: forceRight(true); break; + default: break; + } + }, + [&](const engine::Pressed &key) { + switch (key.source.key) { + case GLFW_KEY_ENTER: forceClose(true); break; + default: break; + } + }, + [&](auto) {}, + }, + e); +} diff --git a/src/Application/src/menu/MainMenu.cpp b/src/Application/src/menu/MainMenu.cpp new file mode 100644 index 0000000..cc070cd --- /dev/null +++ b/src/Application/src/menu/MainMenu.cpp @@ -0,0 +1,106 @@ +#include +#include +#include +#include + +#include "models/Spell.hpp" + +#include "menu/MainMenu.hpp" +#include "menu/Credits.hpp" +#include "menu/HowToPlay.hpp" +#include "ThePURGE.hpp" + +void game::menu::MainMenu::create(entt::registry &, ThePURGE &) +{ + static auto holder = engine::Core::Holder{}; + + const auto &dataFolder = holder.instance->settings().data_folder; + + + m_backgroundTexture = engine::helper::loadTexture(dataFolder + "img/menu/main/mainmenu.png"); + + // clang-format off + + // SAME ORDER AS `Button` ENUM + m_buttons.emplace_back(GUITexture{ + helper::getTexture("img/menu/main/btn_play_selected.png"), + helper::from1080p(1238, 259), + helper::from1080p(229, 155) + }); + m_buttons.emplace_back(GUITexture{ + helper::getTexture("img/menu/main/btn_howtoplay_selected.png"), + helper::from1080p(1062, 441), + helper::from1080p(590, 155) + }); + m_buttons.emplace_back(GUITexture{ + helper::getTexture("img/menu/main/btn_credits_selected.png"), + helper::from1080p(1195, 629), + helper::from1080p(345, 149) + }); + m_buttons.emplace_back(GUITexture{ + helper::getTexture("img/menu/main/btn_exit_selected.png"), + helper::from1080p(1263, 820), + helper::from1080p(195, 149) + }); + + // clang-format on +} + +void game::menu::MainMenu::draw(entt::registry &world, ThePURGE &game) +{ + static auto holder = engine::Core::Holder{}; + + ImGui::SetNextWindowPos(ImVec2(0, 0)); + ImGui::SetNextWindowSize(helper::frac2pixel({1.f, 1.f})); + + ImGui::Begin("MainMenu", nullptr, ImGuiWindowFlags_NoDecoration); + + helper::drawTexture(m_backgroundTexture, ImVec2(0, 0), helper::frac2pixel({1.f, 1.f})); + + if (up() && m_selected > 0) { + holder.instance->getAudioManager().getSound(holder.instance->settings().data_folder + "sounds/menu/change.wav")->play(); + + m_selected--; + } + if (down() && m_selected < Button::MAX - 1) { + holder.instance->getAudioManager().getSound(holder.instance->settings().data_folder + "sounds/menu/change.wav")->play(); + + m_selected++; + } + + helper::drawTexture(m_buttons.at(static_cast(m_selected))); + + ImGui::End(); + + if (select()) { + holder.instance->getAudioManager().getSound(holder.instance->settings().data_folder + "sounds/menu/accept.wav")->play(); + + switch (m_selected) { + case Button::PLAY: + game.logics()->onGameStart.publish(world); + game.setMenu(nullptr); + return; + case Button::HOWTOPLAY: game.setMenu(std::make_unique()); return; + case Button::CREDITS: game.setMenu(std::make_unique()); return; + case Button::EXIT: holder.instance->close(); break; + } + } +} + +void game::menu::MainMenu::event(entt::registry &world, ThePURGE &game, const engine::Event &e) +{ + std::visit( + engine::overloaded{ + [&](const engine::Pressed &joy) { + switch (joy.source.button) { + case engine::Joystick::CENTER2: { + game.logics()->onGameStart.publish(world); + game.setMenu(nullptr); + } break; + default: break; + } + }, + [&](auto) {}, + }, + e); +} diff --git a/src/Application/src/menu/UpgradePanel.cpp b/src/Application/src/menu/UpgradePanel.cpp new file mode 100644 index 0000000..fc29550 --- /dev/null +++ b/src/Application/src/menu/UpgradePanel.cpp @@ -0,0 +1,560 @@ +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "models/Spell.hpp" + +#include "component/all.hpp" + +#include "menu/UpgradePanel.hpp" +#include "widgets/Fonts.hpp" +#include "widgets/GameHUD.hpp" + +#include "ThePURGE.hpp" + +void game::menu::UpgradePanel::create(entt::registry &world, ThePURGE &game) +{ + static auto holder = engine::Core::Holder{}; + + holder.instance->setEventMode(engine::Core::EventMode::PAUSED); + + m_static_background = GUITexture{ + helper::getTexture("img/menu/upgrade_panel/static_background.png"), + ImVec2(0, 0), + ImVec2(1, 1), + }; + + m_bind_popup = GUITexture{ + helper::getTexture("img/menu/upgrade_panel/key_assignment_popup.png"), + ImVec2(0, 0), + ImVec2(1, 1), + }; + + + m_player = game.player; + + const auto &ownedClasses = world.get(m_player).ids; + + updateClassTree(world, game); + + m_selection = findInTree(game.dbClasses().getByName(ownedClasses.back())); + m_cursorDestinationPos = m_selection->relPos; + m_cursorCurrentPos = m_cursorDestinationPos; + + holder.instance->getAudioManager() + .getSound(holder.instance->settings().data_folder + "/sounds/menu/upgrade_panel/open_close.wav") + ->play(); +} + +void game::menu::UpgradePanel::draw(entt::registry &world, ThePURGE &game) +{ + processInputs(world, game); + + GameHUD::draw(game, world); + + ImGui::SetNextWindowPos(ImVec2(0, 0)); + ImGui::SetNextWindowSize(helper::frac2pixel({1.f, 1.f})); + ImGui::SetNextWindowBgAlpha(0.f); + ImGui::Begin("UpgradePanel", nullptr, ImGuiWindowFlags_NoDecoration); + + helper::drawTexture(m_static_background); + + drawTree(world, game); + drawDetailPanel(world, game); + + if (m_spellBeingAssigned) { + helper::drawTexture(m_bind_popup); + const auto spellIcon = helper::getTexture(m_spellBeingAssigned->iconPath); + + helper::drawTexture( + spellIcon, helper::frac2pixel(helper::from1080p(885, 446)), helper::frac2pixel(helper::from1080p(150, 150))); + } + + + ImGui::End(); +} + +void game::menu::UpgradePanel::drawDetailPanel(entt::registry &world, ThePURGE &game) noexcept +{ + const auto skillPoints = world.get(m_player).count; + const auto &selectedClassSpell = game.dbSpells().db.at(m_selection->cl->spells.front()); + + const GUITexture portrait{ + helper::getTexture(m_selection->cl->iconPath), + helper::from1080p(58, 226), + helper::from1080p(158, 179), + }; + const GUITexture spellPortrait{ + helper::getTexture(selectedClassSpell.iconPath), + helper::from1080p(64, 416), + helper::from1080p(100, 100), + }; + const GUITexture btn_buy{ + helper::getTexture("img/menu/upgrade_panel/button/buy.png"), + helper::from1080p(119, 855), + helper::from1080p(317, 197), + }; + const GUITexture btn_cant{ + helper::getTexture("img/menu/upgrade_panel/button/cant.png"), + helper::from1080p(119, 855), + helper::from1080p(317, 197), + }; + const GUITexture btn_alreadyowned{ + helper::getTexture("img/menu/upgrade_panel/button/owned.png"), + helper::from1080p(119, 855), + helper::from1080p(317, 197), + }; + + + helper::drawTexture(portrait); + helper::drawText( + helper::frac2pixel(helper::from1080p(232, 295)), m_selection->cl->name, ImVec4(1, 1, 1, 1), Fonts::kimberley_50); + + helper::drawTexture(spellPortrait); + helper::drawText( + helper::frac2pixel(helper::from1080p(232, 448)), selectedClassSpell.name, ImVec4(1, 1, 1, 1), Fonts::opensans_44); + + helper::drawTextWrapped( + helper::frac2pixel(helper::from1080p(67, 566)), selectedClassSpell.description, 510, Fonts::opensans_32); + + const ImVec4 bonusColor(0.16f, 07, 0, 1); + const ImVec4 malusColor(0.7f, 0, 0, 1); + + helper::drawText( + helper::frac2pixel(helper::from1080p(222, 747)), + fmt::format("{:+}", m_selection->cl->health), + m_selection->cl->health > 0 ? bonusColor : malusColor, + Fonts::kimberley_35); + helper::drawText( + helper::frac2pixel(helper::from1080p(222, 792)), + fmt::format("{:+}", m_selection->cl->speed), + m_selection->cl->speed > 0 ? bonusColor : malusColor, + Fonts::kimberley_35); + + + if (isOwned(m_selection->cl)) { + helper::drawTexture(btn_alreadyowned); + } else { + if (isPurchaseable(m_selection->cl) && skillPoints >= m_selection->cl->cost) + helper::drawTexture(btn_buy); + else + helper::drawTexture(btn_cant); + + helper::drawText( + helper::frac2pixel(helper::from1080p(269, 960)), + std::to_string(m_selection->cl->cost), + ImVec4(0, 0, 0, 1), + Fonts::kimberley_62); + } +} + +void game::menu::UpgradePanel::processInputs(entt::registry &world, ThePURGE &game) +{ + if (m_spellBeingAssigned) return; + + static engine::Core::Holder holder{}; + + const auto *previousSelection = m_selection; + + const auto *parent = findParent(m_selection->cl); + + if (up() && parent) { + m_selection = parent; + } else if (down() && !m_selection->children.empty()) { + for (const auto &child : m_selection->children) { + if (!isUnavailable(child.cl)) { + m_selection = &m_selection->children.front(); + break; + } + } + } else if (left() && parent) { + for (auto i = m_selection->selfIndex - 1; i > -1; --i) { + const auto *it = &parent->children.at(static_cast(i)); + if (!isUnavailable(it->cl)) { + m_selection = it; + break; + } + } + } else if (right() && parent) + for (auto i = static_cast(m_selection->selfIndex) + 1ul; i < parent->children.size(); ++i) { + const auto *it = &parent->children.at(i); + if (!isUnavailable(it->cl)) { + m_selection = it; + break; + } + } + + if (previousSelection != m_selection) { + holder.instance->getAudioManager() + .getSound(holder.instance->settings().data_folder + "/sounds/menu/upgrade_panel/tree_select.wav") + ->play(); + + m_cursorDestinationPos = m_selection->relPos; + } + + const auto sp = world.get(m_player).count; + if (select()) { + if (isPurchaseable(m_selection->cl) && sp >= m_selection->cl->cost) { + game.logics()->onPlayerPurchase.publish(world, m_player, *m_selection->cl); + holder.instance->getAudioManager() + .getSound(holder.instance->settings().data_folder + "/sounds/menu/upgrade_panel/buy_class.wav") + ->play(); + + m_spellBeingAssigned = &game.dbSpells().db.at(m_selection->cl->spells.front()); + updateClassTree(world, game); + m_selection = findInTree(game.dbClasses().getByName(world.get(m_player).ids.back())); + } else + holder.instance->getAudioManager() + .getSound(holder.instance->settings().data_folder + "/sounds/menu/upgrade_panel/error.wav") + ->play(); + } +} + +void game::menu::UpgradePanel::onBuyHp(entt::registry &world) +{ + static engine::Core::Holder holder{}; + + constexpr int kCost = 1; + constexpr float kHeal = 5; + + auto &sp = world.get(m_player).count; + auto &health = world.get(m_player); + + if (sp >= kCost && health.current < health.max) { + sp -= kCost; + health.current = std::min(health.current + kHeal, health.max); + holder.instance->getAudioManager() + .getSound(holder.instance->settings().data_folder + "/sounds/menu/upgrade_panel/heal.wav") + ->play(); + + } else + holder.instance->getAudioManager() + .getSound(holder.instance->settings().data_folder + "/sounds/menu/upgrade_panel/error.wav") + ->play(); +} + +void game::menu::UpgradePanel::drawTree(entt::registry &, ThePURGE &) noexcept +{ + std::vector todo = {&m_root}; + + while (!todo.empty()) { + const auto current = todo.back(); + todo.pop_back(); + + for (const auto &child : current->children) { + ImGui::GetWindowDrawList()->AddLine( + getTreeDrawPos(current->relPos, 0), + getTreeDrawPos(child.relPos, 0), + ImGui::ColorConvertFloat4ToU32(ImVec4(0, 0, 0, 1)), + 3.f); + todo.push_back(&child); + } + + std::uint32_t glowTexture; + + if (isOwned(current->cl)) + glowTexture = helper::getTexture("img/menu/upgrade_panel/tree/frames/owned.png"); + else if (isPurchaseable(current->cl)) + glowTexture = helper::getTexture("img/menu/upgrade_panel/tree/frames/buyable.png"); + else + glowTexture = helper::getTexture("img/menu/upgrade_panel/tree/frames/unavailable.png"); + + helper::drawTexture(glowTexture, getTreeDrawPos(current->relPos, kFrameSize), ImVec2(kFrameSize, kFrameSize)); + + const auto iconTexture = helper::getTexture(current->cl->iconPath); + helper::drawTexture(iconTexture, getTreeDrawPos(current->relPos, kIconSize), ImVec2(kIconSize, kIconSize)); + } + + m_cursorCurrentPos.x = std::lerp(m_cursorCurrentPos.x, m_cursorDestinationPos.x, kSelectionAnimationSpeed); + m_cursorCurrentPos.y = std::lerp(m_cursorCurrentPos.y, m_cursorDestinationPos.y, kSelectionAnimationSpeed); + + const auto cursorTexture = helper::getTexture("img/menu/upgrade_panel/tree/cursor.png"); + helper::drawTexture(cursorTexture, getTreeDrawPos(m_cursorCurrentPos, kCursorSize), ImVec2(kCursorSize, kCursorSize)); +} + +auto game::menu::UpgradePanel::getTreeDrawPos(const ImVec2 &relPos, float elemSize) const noexcept -> ImVec2 +{ + const ImVec2 centerAs1080p(treeTopLeft.x + treeSize.x * relPos.x, treeTopLeft.y + treeSize.y * relPos.y); + + return helper::frac2pixel(helper::from1080p(centerAs1080p.x - elemSize * 0.5f, centerAs1080p.y - elemSize * 0.5f)); +} + +void game::menu::UpgradePanel::updateClassTree(entt::registry &world, ThePURGE &game) +{ + m_owned.clear(); + m_classes.clear(); + m_purchaseable.clear(); + + for (const auto &name : world.get(m_player).ids) m_owned.push_back(game.dbClasses().getByName(name)); + + + m_classes.push_back({&game.dbClasses().getStarterClass()}); + + for (int i = 0;; ++i) { + const auto &lastRow = m_classes.back(); + std::vector thisRow; + + for (const auto *parent : lastRow) { + for (const auto &childName : parent->children) { + const auto *child = game.dbClasses().getByName(childName); + thisRow.push_back(child); + + if (std::find(std::begin(m_owned), std::end(m_owned), parent) != std::end(m_owned)) + m_purchaseable.push_back(child); + } + } + + if (thisRow.empty()) break; + + m_classes.push_back(std::move(thisRow)); + } + + // remove owned classes from purchaseable + m_purchaseable.erase( + std::remove_if( + std::begin(m_purchaseable), + std::end(m_purchaseable), + [this](const auto &c) { return std::find(std::begin(m_owned), std::end(m_owned), c) != std::end(m_owned); }), + std::end(m_purchaseable)); + + // printClassTreeDebug(); + + m_root = generateTreeRec(game, m_classes[0][0]); +} + +auto game::menu::UpgradePanel::generateTreeRec(ThePURGE &game, const Class *cl, int selfIndex, int depth) const noexcept + -> ClassTreeNode +{ + const auto yPos = static_cast(depth) / static_cast(m_classes.size() - 1); + + if (static_cast(depth) == m_classes.size() - 1) { + const auto &lastRow = m_classes.back(); + const auto iter = std::find(std::begin(lastRow), std::end(lastRow), cl); + auto idx = std::distance(std::begin(lastRow), iter); + + const auto xPos = static_cast(idx) / static_cast(lastRow.size() - 1); + + return ClassTreeNode{.selfIndex = selfIndex, .cl = cl, .relPos = ImVec2(xPos, yPos), .children = {}}; + } + + ClassTreeNode result; + + result.cl = cl; + result.selfIndex = selfIndex; + result.relPos.y = yPos; + result.relPos.x = 0; + + for (int idx = 0; const auto &childName : cl->children) { + result.children.push_back(generateTreeRec(game, game.dbClasses().getByName(childName), idx++, depth + 1)); + result.relPos.x += result.children.back().relPos.x / static_cast(cl->children.size()); + } + + return result; +} + +auto game::menu::UpgradePanel::findInTree(const Class *cl) const noexcept -> const ClassTreeNode * +{ + std::vector todo = {&m_root}; + + while (!todo.empty()) { + const auto current = todo.back(); + if (current->cl == cl) return current; + + todo.pop_back(); + + for (const auto &child : current->children) todo.push_back(&child); + } + + assert(!"The requested class does not exist in the tree"); + return nullptr; // guaranteed crash +} + +auto game::menu::UpgradePanel::findParent(const Class *cl) const noexcept -> const ClassTreeNode * +{ + std::vector todo = {&m_root}; + + while (!todo.empty()) { + const auto current = todo.back(); + + todo.pop_back(); + + for (const auto &child : current->children) { + if (child.cl == cl) return current; + + todo.push_back(&child); + } + } + + return nullptr; +} + +bool game::menu::UpgradePanel::isOwned(const Class *cl) const noexcept +{ + return std::find(std::begin(m_owned), std::end(m_owned), cl) != std::end(m_owned); +} + +auto game::menu::UpgradePanel::isPurchaseable(const Class *cl) const noexcept -> bool +{ + return std::find(std::begin(m_purchaseable), std::end(m_purchaseable), cl) != std::end(m_purchaseable); +} + +auto game::menu::UpgradePanel::isUnavailable(const Class *cl) const noexcept -> bool +{ + return !(isOwned(cl) || isPurchaseable(cl)); +} + +void game::menu::UpgradePanel::printClassTreeDebug() const noexcept +{ + spdlog::info("================ CLASS TREE DEBUG ================"); + spdlog::info("Tree :"); + + for (int i = 0; const auto &tier : m_classes) { + spdlog::info(" Tier {} :", ++i); + + for (const auto *cl : tier) spdlog::info("\t{}", cl->name); + } + + spdlog::info(""); + spdlog::info("Owned :"); + for (const auto *owned : m_owned) spdlog::info("\t{}", owned->name); + + spdlog::info(""); + spdlog::info("Purchaseable :"); + for (const auto *purchaseable : m_purchaseable) spdlog::info("\t{}", purchaseable->name); + + + spdlog::info("=================================================="); +} + + +void game::menu::UpgradePanel::event(entt::registry &world, ThePURGE &game, const engine::Event &e) +{ + static auto holder = engine::Core::Holder{}; + + if (m_spellBeingAssigned) { + const auto spell_map = [](T k) { + struct SpellMap { + int key; + std::size_t id; + }; + if constexpr (std::is_same::value) { + const auto map = std::to_array({{engine::Joystick::LS, 0}, {engine::Joystick::RS, 1}}); + return std::find_if(map.begin(), map.end(), [&k](auto &i) { return i.key == k; })->id; + + } else if constexpr (std::is_same::value) { + const auto map = std::to_array({{engine::Joystick::LST, 2}, {engine::Joystick::RST, 3}}); + return std::find_if(map.begin(), map.end(), [&k](auto &i) { return i.key == k; })->id; + + } else { // todo : should be engine::Keyboard::Key + const auto map = std::to_array( + {{GLFW_KEY_J, 0}, + {GLFW_KEY_1, 0}, + {GLFW_KEY_2, 2}, + {GLFW_KEY_K, 2}, + {GLFW_KEY_3, 3}, + {GLFW_KEY_L, 3}, + {GLFW_KEY_M, 1}, + {GLFW_KEY_4, 1}, + {GLFW_KEY_SEMICOLON, 1}}); + return std::find_if(map.begin(), map.end(), [&k](auto &i) { return i.key == k; })->id; + } + }; + + std::visit( + engine::overloaded{ + [&](const engine::Pressed &key) { + switch (key.source.key) { + case GLFW_KEY_SEMICOLON: + case GLFW_KEY_1: + case GLFW_KEY_2: + case GLFW_KEY_3: + case GLFW_KEY_4: + case GLFW_KEY_J: + case GLFW_KEY_K: + case GLFW_KEY_L: + case GLFW_KEY_M: { + const auto id = spell_map(key.source.key); + + world.get(m_player).spells[id] = + game.dbSpells().instantiate(m_spellBeingAssigned->name); + + m_spellBeingAssigned = nullptr; + } break; + default: break; + } + }, + [&](const engine::Moved &joy) { + switch (joy.source.axis) { + case engine::Joystick::LST: + case engine::Joystick::RST: { + const auto id = spell_map(joy.source.axis); + world.get(m_player).spells[id] = + game.dbSpells().instantiate(m_spellBeingAssigned->name); + m_spellBeingAssigned = nullptr; + } break; + default: break; + } + }, + [&](const engine::Pressed &joy) { + switch (joy.source.button) { + case engine::Joystick::LS: + case engine::Joystick::RS: { + const auto id = spell_map(joy.source.button); + world.get(m_player).spells[id] = + game.dbSpells().instantiate(m_spellBeingAssigned->name); + m_spellBeingAssigned = nullptr; + } break; + default: return; + } + }, + [&](auto) {}, + }, + e); + + if (!m_spellBeingAssigned) + holder.instance->getAudioManager() + .getSound(holder.instance->settings().data_folder + "/sounds/menu/upgrade_panel/spell_assign.wav") + ->play(); + return; + } + + std::visit( + engine::overloaded{ + [&](const engine::Pressed &key) { + switch (key.source.key) { + case GLFW_KEY_ESCAPE: + case GLFW_KEY_P: + holder.instance->getAudioManager() + .getSound(holder.instance->settings().data_folder + "/sounds/menu/upgrade_panel/open_close.wav") + ->play(); + holder.instance->setEventMode(engine::Core::EventMode::RECORD); + game.setMenu(nullptr); + break; + case GLFW_KEY_Y: onBuyHp(world); break; + default: break; + } + }, + [&](const engine::Pressed &joy) { + switch (joy.source.button) { + case engine::Joystick::CENTER2: + holder.instance->getAudioManager() + .getSound(holder.instance->settings().data_folder + "/sounds/menu/upgrade_panel/open_close.wav") + ->play(); + holder.instance->setEventMode(engine::Core::EventMode::RECORD); + game.setMenu(nullptr); + break; + case engine::Joystick::Buttons::ACTION_TOP: onBuyHp(world); break; + default: break; + } + }, + [&](auto) {}, + }, + e); +} diff --git a/src/Application/src/models/Class.cpp b/src/Application/src/models/Class.cpp new file mode 100644 index 0000000..75c1b85 --- /dev/null +++ b/src/Application/src/models/Class.cpp @@ -0,0 +1,101 @@ +#include +#include +#include + +#include +#include +#include + +#include + +#include "models/Class.hpp" + +auto game::ClassDatabase::getByName(const std::string_view name) -> const Class * +{ + if (const auto found = std::find_if(db.begin(), db.end(), [&name](const auto &i) { return i.name == name; }); + found != db.end()) { + return &(*found); + } else + UNLIKELY { return nullptr; } +} + +auto game::ClassDatabase::getStarterClass() -> const Class & +{ + if (const auto found = std::find_if(db.begin(), db.end(), [](const auto &i) { return i.is_starter; }); + found != db.end()) { + return *found; + } else + UNLIKELY { return *db.begin(); } +} + +using namespace std::chrono_literals; + +auto game::ClassDatabase::fromFile(const std::string_view path) -> ClassDatabase & +{ + spdlog::info("Loading class database file: '{}'", path.data()); + + std::ifstream file(path.data()); + if (!file.is_open()) { spdlog::error("Can't open the given file"); } + const auto jsonData = nlohmann::json::parse(file); + + for (const auto &[name, data] : jsonData.items()) { + Class c{ + .name = name, + .iconPath = data["icon"], + .assetGraphPath = data["assetGraph"], + .is_starter = data.value("starter", false), + .cost = data["cost"].get(), + .health = data["health"].get(), + .speed = data["speed"].get(), + .hitbox = engine::d2::HitboxSolid{data["hitbox"]["x"].get(), data["hitbox"]["y"].get()}, + .spells = data["spells"].get>(), + .children = {}, + }; + // Note creating the `Class` instance during assignment raises internal compiler error on MSVC + // note : is it still the case ? + // note : I don't know + db.emplace_back(c); + } + + for (auto &i : this->db) { + for (const auto &child : jsonData[i.name]["children"]) { + if (const auto c = getByName(child.get()); c) { + i.children.push_back(c->name); + } else + UNLIKELY { spdlog::warn("Unknown class '{}'. Ignoring", child); } + } + } + + for (const auto &classes : this->db) { + spdlog::info( + "\tname={}\n" + "\ticonPath={}\n" + "\tassetGraphPath={}\n" + "\tis_starter={}\n" + "\tspells={}\n" + "\thealth={}\n" + "\tspeed={}\n" + "\tcost={}\n" + "\thitbox={},{}\n" + "\tchildren={}\n", + classes.name, + classes.iconPath, + classes.assetGraphPath, + classes.is_starter, + std::accumulate( + std::begin(classes.spells), + std::end(classes.spells), + std::string{}, + [](auto out, auto &i) { return out + "/" + i; }), + classes.health, + classes.speed, + classes.cost, + classes.hitbox.width, + classes.hitbox.height, + std::accumulate(std::begin(classes.children), std::end(classes.children), std::string{}, [](auto out, auto &i) { + return out + "/" + i; + })); + } + + return *this; +} diff --git a/src/Application/src/models/ClassDatabase.cpp b/src/Application/src/models/ClassDatabase.cpp deleted file mode 100644 index 9d43d43..0000000 --- a/src/Application/src/models/ClassDatabase.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include -#include "models/ClassDatabase.hpp" - -auto game::classes::getByName(const Database &db, const std::string_view name) -> const Class * -{ - if (const auto found = std::find_if(db.begin(), db.end(), [&name](const auto &i) { return i.second.name == name; }); - found != db.end()) { - return &found->second; - } else - UNLIKELY { return nullptr; } -} diff --git a/src/Application/src/models/Effect.cpp b/src/Application/src/models/Effect.cpp new file mode 100644 index 0000000..eb65198 --- /dev/null +++ b/src/Application/src/models/Effect.cpp @@ -0,0 +1,19 @@ +#include + +#include + +#include "models/Effect.hpp" + +auto game::EffectDatabase::fromFile(const std::string_view path) -> bool +{ + std::ifstream file(path.data()); + if (!file.is_open()) { + spdlog::error("Can't open the given file"); + return false; + } + const auto jsonData = nlohmann::json::parse(file); + + this->db = jsonData.getdb)>>(); + + return true; +} diff --git a/src/Application/src/models/EndGameStats.cpp b/src/Application/src/models/EndGameStats.cpp new file mode 100644 index 0000000..e8f518b --- /dev/null +++ b/src/Application/src/models/EndGameStats.cpp @@ -0,0 +1,12 @@ +#include "models/EndGameStats.hpp" + +#include "component/all.hpp" + +game::EndGameStats::EndGameStats(entt::registry &world, const entt::entity player, double gameTime) +{ + finalLevel = world.get(player).current_level; + + gameTimeInSeconds = gameTime; + + enemyKilled = world.get(player).enemyKilled; +} diff --git a/src/Application/src/models/Enemy.cpp b/src/Application/src/models/Enemy.cpp new file mode 100644 index 0000000..990b8c5 --- /dev/null +++ b/src/Application/src/models/Enemy.cpp @@ -0,0 +1,19 @@ +#include + +#include + +#include "models/Enemy.hpp" + +bool game::EnemyDatabase::fromFile(const std::string_view path) +{ + std::ifstream file(path.data()); + if (!file.is_open()) { + spdlog::error("Can't open the given file"); + return false; + } + const auto jsonData = nlohmann::json::parse(file); + + this->db = jsonData.getdb)>>(); + + return true; +} diff --git a/src/Application/src/models/Spell.cpp b/src/Application/src/models/Spell.cpp new file mode 100644 index 0000000..dc1ca76 --- /dev/null +++ b/src/Application/src/models/Spell.cpp @@ -0,0 +1,123 @@ +#include +#include +#include + +#include + +#include + +#include "factory/SpellFactory.hpp" +#include "models/Spell.hpp" + +#include "models/utils.hpp" + +using namespace std::chrono_literals; + +void game::to_json(nlohmann::json &j, const SpellData &spell) +{ + // clang-format off + j = nlohmann::json({spell.name, { + "icon", spell.iconPath, + "description", spell.description, + "cooldown", spell.cooldown.count(), + "damage", spell.damage, + "hitbox", { + "x", spell.hitbox.width, + "y", spell.hitbox.height + }, + "scale", { + "x", spell.scale.x, + "y", spell.scale.y + }, + "lifetime", spell.lifetime.count(), + //"offset_to_source", { + // "x": spell.offset_to_source_x, + // "y": spell.offset_to_source_y + //} + "audio_on_cast", spell.audio_on_cast, + "animation", spell.animation, + "speed", spell.speed + }}); + // clang-format on +} + + +auto game::SpellDatabase::instantiate(const std::string_view spell) -> std::optional +{ + if (const auto found = std::find_if(std::begin(db), std::end(db), [&spell](auto &i) { return i.first == spell; }); + found != std::end(db)) { + return Spell{ + .id = found->first, + .cd = { + .is_in_cooldown = false, + .cooldown = found->second.cooldown, + .remaining_cooldown = 0ms, + }}; + + } else { + spdlog::error("SpellDatabase::instantiate: No such spell '{}'", spell.data()); + return {}; + } +} + +auto game::SpellDatabase::fromFile(const std::string_view path) -> bool +{ + std::ifstream file(path.data()); + if (!file.is_open()) { + spdlog::error("Can't open the given file"); + return false; + } + const auto jsonData = nlohmann::json::parse(file); + + for (const auto &[name, data] : jsonData.items()) { + SpellData spell; + + try { + spell.name = name; + spell.iconPath = data.at("icon"); + spell.description = data.at("description"); + spell.cooldown = std::chrono::milliseconds{data.at("cooldown")}; + spell.damage = data.at("damage"); + spell.hitbox.width = data.at("hitbox").at("x"); + spell.hitbox.height = data.at("hitbox").at("y"); + spell.scale.x = data.at("scale").at("x"); + spell.scale.y = data.at("scale").at("y"); + spell.lifetime = std::chrono::milliseconds{data.at("lifetime")}; + spell.audio_on_cast = data.at("audio_on_cast"); + spell.animation = data.at("animation"); + spell.speed = data.at("speed"); + spell.offset_to_source_x = data.at("offset_to_source").at("x"); + spell.offset_to_source_y = data.at("offset_to_source").at("y"); + spell.effects = data.value("effect", decltype(spell.effects){}); + spell.type = [](const auto &type) { + decltype(SpellData{}.type) out; + for (const auto &i : type) { + if (const auto id = toEnum(i); id.has_value()) { + out[static_cast(id.value())] = true; + spdlog::info("{}", magic_enum::enum_name(id.value()).data()); + } + } + return out; + }(data.at("type").get>()); + spell.targets = [](const std::vector &in) { + decltype(SpellData{}.targets) out; + for (const auto &i : in) { + if (const auto id = toEnum(i); id.has_value()) { + out[static_cast(id.value())] = true; + } + } + return out; + }(data.value("target", std::vector({"enemy"}))); + spell.quantity = data.value("quantity", 1); + spell.angle = data.value("angle", 0.0f); + spell.on_death = data.value("on_death", ""); + } catch (nlohmann::json::exception &e) { + spdlog::error("failed: {}", e.what()); + throw; // we probably don't want to continue + } + + this->db[name] = spell; + } + + return true; +} diff --git a/src/Application/src/models/SpellDatabase.cpp b/src/Application/src/models/SpellDatabase.cpp deleted file mode 100644 index c23839a..0000000 --- a/src/Application/src/models/SpellDatabase.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "models/SpellDatabase.hpp" - -using namespace std::chrono_literals; - -namespace game { - -SpellDatabase g_SpellDatabase = makeSpellDatabase(); - -auto makeSpellDatabase() -> SpellDatabase -{ - SpellDatabase db; - - db[SpellFactory::ID::SHOVEL_ATTACK] = {500ms}; - db[SpellFactory::ID::SWORD_ATTACK] = {500ms}; - db[SpellFactory::ID::FIREBALL] = {1250ms}; - db[SpellFactory::ID::ENEMY_ATTACK] = {1000ms}; - db[SpellFactory::ID::PIERCING_ARROW] = {1000ms}; - - return db; -} - -} // namespace game - diff --git a/src/Application/src/models/Stage.cpp b/src/Application/src/models/Stage.cpp new file mode 100644 index 0000000..0499d82 --- /dev/null +++ b/src/Application/src/models/Stage.cpp @@ -0,0 +1,324 @@ +#include "models/Stage.hpp" +#include "models/Spell.hpp" + +#include "component/KeyPicker.hpp" +#include "component/AimSight.hpp" + +#include "factory/EntityFactory.hpp" + +#include "ThePURGE.hpp" + +int levelStage = 1; + +// If gap is even, center will be chosen randomly between the two center tiles +static int getOnePossibleCenterOf(int a, int b) +{ + int result = (a + b) / 2; + + if ((a - b) % 2 == 0) result -= game::Stage::randRange(0, 1); + + return result; +} + +// If room size is even, center will be chosen randomly between the two center tiles +static auto getOnePossibleCenterOf(const game::Stage::Room &r) -> glm::ivec2 +{ + return {getOnePossibleCenterOf(r.x, r.x + r.w), getOnePossibleCenterOf(r.y, r.y + r.h)}; +} + +static void generatorCorridor( + game::TilemapBuilder &builder, + const game::Stage::Parameters ¶ms, + const game::Stage::Room &r1, + const game::Stage::Room &r2) +{ + auto start = getOnePossibleCenterOf(r1); + auto end = getOnePossibleCenterOf(r2); + + auto vertical = [&](glm::ivec2 pos, int width) -> glm::ivec2 { + const auto widthOffset = getOnePossibleCenterOf(0, width); + + const auto minX = pos.x - widthOffset; + const auto maxX = pos.x + (width - widthOffset); + + while (pos.y != end.y) { + for (auto x = minX; x < maxX; ++x) { + if (builder.at(glm::ivec2{x, pos.y}) == game::TileEnum::NONE) { + builder[glm::ivec2{x, pos.y}] = game::TileEnum::FLOOR_CORRIDOR; + } + } + + pos.y += pos.y < end.y ? 1 : -1; + } + + return pos; + }; + + auto horizontal = [&](glm::ivec2 pos, int width) -> glm::ivec2 { + const auto widthOffset = getOnePossibleCenterOf(0, width); + + const auto minY = pos.y - widthOffset; + const auto maxY = pos.y + (width - widthOffset); + + while (pos.x != end.x) { + for (auto y = minY; y < maxY; ++y) { + if (builder.at(glm::ivec2{pos.x, y}) == game::TileEnum::NONE) { + builder[glm::ivec2{pos.x, y}] = game::TileEnum::FLOOR_CORRIDOR; + } + } + + pos.x += pos.x < end.x ? 1 : -1; + } + + return pos; + }; + + // -2 for walls, and -1 to be safe about the random center not making wall go off bound + auto maxWidth = std::min(r1.h - 3, r2.w - 3); + auto width = game::Stage::randRange( + std::min(maxWidth, params.minCorridorWidth), std::min(maxWidth + 1, params.maxCorridorWidth + 1)); + + if (game::Stage::randRange(0, 1)) { + horizontal(vertical(start, width), width); + } else { + vertical(horizontal(start, width), width); + } +} + +static void placeWalls(game::TilemapBuilder &builder) +{ + // For each empty tile, if one it's neighbour is a floor, make it a wall + static constexpr auto neighbours = + std::to_array({{-1, -1}, {+0, -1}, {+1, -1}, {-1, +0}, {+1, +0}, {-1, +1}, {+0, +1}, {+1, +1}}); + + glm::ivec2 it; + + for (it.y = 0; it.y < builder.getSize().y; ++it.y) { + for (it.x = 0; it.x < builder.getSize().x; ++it.x) { + if (builder.at(glm::ivec2{it.x, it.y}) != game::TileEnum::NONE + && builder.at(glm::ivec2{it.x, it.y}) != game::TileEnum::RESERVED) + continue; + + for (const auto &n : neighbours) { + const auto checkPos = it + n; + + if (checkPos.x > 0 && checkPos.x < builder.getSize().x && checkPos.y > 0 + && checkPos.y < builder.getSize().y && IS_FLOOR(builder.at(glm::ivec2{checkPos.x, checkPos.y}))) { + builder[glm::ivec2{it.x, it.y}] = game::TileEnum::WALL; + break; + } + } + } + } +} + +static auto placeBossRoomExitDoor(game::TilemapBuilder &builder, game::Stage::Room &r) -> void +{ + // Strategy : + // Check room boundaries, find the boss room entrance, place a door at the central opposite of the room with the right orientation + // Reduce room size by one to make sure the door is not accessible by another room on the other side of the wall + + const auto x1 = r.x; + const auto x2 = r.x + r.w; + const auto y1 = r.y; + const auto y2 = r.y + r.h; + + // CHECK NORTH WALL + for (auto x = x1 + 1; x < x2 - 1; ++x) + if (IS_FLOOR(builder.at(glm::ivec2{x, y2 - 1}))) { + r.y += 1; + r.h -= 1; + + builder[glm::ivec2{x2 - 1 - (x - x1), r.y}] = game::TileEnum::EXIT_DOOR_FACING_NORTH; + return; + } + + // CHECK SOURTH WALL + for (auto x = x1 + 1; x < x2 - 1; ++x) + if (IS_FLOOR(builder.at(glm::ivec2{x, y1}))) { + r.h -= 1; + + builder[glm::ivec2{x2 - 1 - (x - x1), r.y + r.h - 1}] = game::TileEnum::EXIT_DOOR_FACING_SOUTH; + return; + } + + // CHECK WEST WALL + for (auto y = y1 + 1; y < y2 - 1; ++y) + if (IS_FLOOR(builder.at(glm::ivec2{x1, y}))) { + r.w -= 1; + + builder[glm::ivec2{r.x + r.w - 1, y2 - 1 - (y - y1)}] = game::TileEnum::EXIT_DOOR_FACING_WEST; + return; + } + + // CHECK EAST WALL + for (auto y = y1 + 1; y < y2 - 1; ++y) + if (IS_FLOOR(builder.at(glm::ivec2{x2 - 1, y}))) { + r.x += 1; + r.w -= 1; + + builder[glm::ivec2{r.x, y2 - 1 - (y - y1)}] = game::TileEnum::EXIT_DOOR_FACING_EAST; + return; + } + + assert(false && "Could not find boss room entrance, cannot place exit door"); +} + +///////// + +constexpr bool game::Stage::Room::is_valid(const TilemapBuilder &builder) +{ + const auto x2 = x + w; + const auto y2 = y + h; + + for (auto i = x; i < x2; ++i) { + for (auto j = y; j < y2; ++j) { + if (builder.at(glm::ivec2{i, j}) != game::TileEnum::NONE) { return false; } + } + } + return true; +} + +std::default_random_engine game::Stage::random_engine{}; + +auto game::Stage::create_floor(ThePURGE &game, entt::registry &world, const Parameters ¶ms) +{ + TilemapBuilder builder(game, {params.maxDungeonWidth, params.maxDungeonHeight}); + + const auto generate_room = [&builder](const auto &p) -> Room { + Room r; + + std::uint32_t tries = 0; + do { + if (tries++ > 10000) return {0, 0, 0, 0}; + + // Width including walls (hence the +2) + r.w = randRange(p.minRoomSize + 2, p.maxRoomSize + 2 + 1); + r.h = randRange(p.minRoomSize + 2, p.maxRoomSize + 2 + 1); + + r.x = randRange(0, p.maxDungeonWidth - r.w); + r.y = randRange(0, p.maxDungeonHeight - r.h); + } while (!r.is_valid(builder)); + + return r; + }; + + const auto set_room = [&builder](const Room &r, TileEnum tile) -> void { + const auto x2 = r.x + r.w; + const auto y2 = r.y + r.h; + + for (auto y = r.y + 1; y < y2 - 1; ++y) { + for (auto x = r.x + 1; x < x2 - 1; ++x) { builder[glm::ivec2{x, y}] = tile; } + } + }; + + auto roomCount = randRange(params.minRoomCount, params.maxRoomCount); + + std::vector rooms; + rooms.reserve(roomCount); + + rooms.emplace_back(generate_room(params)); + set_room(rooms[0], game::TileEnum::RESERVED); + + for (std::size_t i = 0; i < roomCount - 1; ++i) { + auto room = generate_room(params); + if (room.w == 0 && room.h == 0) break; // Failed to place room + + // Reserve the space, so multiple rooms don't spawn at the same place + set_room(room, game::TileEnum::RESERVED); + rooms.emplace_back(room); + + generatorCorridor(builder, params, rooms[i], rooms[i + 1]); + } + + this->spawn = *rooms.begin(); + this->boss = *rooms.rbegin(); + + // see std::copy + for (auto i = 1ul; i < rooms.size() - 1; ++i) this->regularRooms.push_back(rooms[i]); + + set_room(this->spawn, game::TileEnum::FLOOR_SPAWN); + for (const auto &r : this->regularRooms) set_room(r, game::TileEnum::FLOOR_NORMAL_ROOM); + + placeBossRoomExitDoor(builder, this->boss); + set_room(this->boss, game::TileEnum::FLOOR_BOSS_ROOM); + + placeWalls(builder); + + builder.build(world); +} + +auto game::Stage::spawn_mob(ThePURGE &game, entt::registry &world, const Parameters ¶ms, const Room &r) -> void +{ + for (const auto &[id, density] : params.mobDensity) { + const int isLevelAccepted = static_cast(density); + if (isLevelAccepted <= levelStage) { + float newDensity = density - static_cast(static_cast(density)); + if (newDensity == 0.0f) continue; + + for (auto x = r.x + 1; x < r.x + r.w - 1; ++x) { + for (auto y = r.y + 1; y < r.y + r.h - 1; ++y) { + if (randRange(0, static_cast(1.0f / (newDensity + (0.001f * static_cast(levelStage))))) + == 0) { + spdlog::warn("{}", id); + EntityFactory::create(game, world, glm::vec2{x + 0.5, y + 0.5}, game.dbEnemies().db.at(id)); + } + } + } + } + } +} + +auto game::Stage::populate_enemies(ThePURGE &game, entt::registry &world, const Parameters ¶ms) +{ + for (const auto &r : regularRooms) spawn_mob(game, world, params, r); + + decltype(game.dbEnemies().db) bosses; + std::copy_if(game.dbEnemies().db.begin(), game.dbEnemies().db.end(), std::inserter(bosses, bosses.end()), [](auto &i) { + return i.second.is_boss; + }); + + auto selected_one = bosses.begin(); + std::advance(selected_one, static_cast(std::rand()) % bosses.size()); + + spdlog::warn("Adding boss !"); + levelStage++; + + EntityFactory::create(game, world, glm::vec2{boss.x + boss.w * 0.5, boss.y + boss.h * 0.5}, selected_one->second); +} + +auto game::Stage::generate(ThePURGE &game, entt::registry &world, const Parameters ¶ms, std::optional seed) + -> Stage & +{ + this->regularRooms.clear(); + + if (seed) random_engine.seed(seed.value()); + + create_floor(game, world, params); + + spdlog::info("Map generated !"); + + populate_enemies(game, world, params); + + spdlog::info("Enemies spawned !"); + + nextFloorSeed = static_cast(random_engine()); + + return *this; +} + +auto game::Stage::clear(entt::registry &world, bool kill_the_players) -> void +{ + world.view>().each([&](auto &e) { world.destroy(e); }); + world.view>().each([&](auto &e) { world.destroy(e); }); + world.view>().each([&](auto &e) { world.destroy(e); }); + world.view>().each([&](auto &e) { world.destroy(e); }); + world.view>().each([&](auto &e) { world.destroy(e); }); + if (kill_the_players) { + levelStage = 1; + world.view>().each([&](auto &e) { world.destroy(e); }); + world.view>().each([&](auto &e) { world.destroy(e); }); + } else { + world.view().each([&](KeyPicker &kp) { kp.hasKey = false; }); + } +} diff --git a/src/Application/src/level/LevelTilemapBuilder.cpp b/src/Application/src/stage/LevelTilemapBuilder.cpp similarity index 71% rename from src/Application/src/level/LevelTilemapBuilder.cpp rename to src/Application/src/stage/LevelTilemapBuilder.cpp index 1bd63ea..83a88ca 100644 --- a/src/Application/src/level/LevelTilemapBuilder.cpp +++ b/src/Application/src/stage/LevelTilemapBuilder.cpp @@ -1,4 +1,6 @@ -#include "level/LevelTilemapBuilder.hpp" +#include + +#include "stage/LevelTilemapBuilder.hpp" #include "factory/EntityFactory.hpp" #include "Engine/component/Rotation.hpp" @@ -16,7 +18,7 @@ void game::TilemapBuilder::handleTileBuild(entt::registry &world, int x, int y) if (tile == TileEnum::NONE) return; auto size = getTileSize(x, y); - //auto size = glm::ivec2(1, 1); // uncomment this line to get proper textures, but massive FPS drops + // auto size = glm::ivec2(1, 1); // uncomment this line to get proper textures, but massive FPS drops for (auto clearY = y; clearY < y + size.y; ++clearY) { for (auto clearX = x; clearX < x + size.x; ++clearX) { @@ -29,41 +31,39 @@ void game::TilemapBuilder::handleTileBuild(entt::registry &world, int x, int y) tilePos += tileSize / 2.f; - constexpr auto kPI = 3.1415926535897; - switch (tile) { case TileEnum::FLOOR_NORMAL_ROOM: - EntityFactory::create(world, tilePos, tileSize); + EntityFactory::create(m_game, world, tilePos, tileSize); break; - case TileEnum::FLOOR_BOSS_ROOM: EntityFactory::create(world, tilePos, tileSize); break; + case TileEnum::FLOOR_BOSS_ROOM: EntityFactory::create(m_game, world, tilePos, tileSize); break; case TileEnum::FLOOR_CORRIDOR: - EntityFactory::create(world, tilePos, tileSize); + EntityFactory::create(m_game, world, tilePos, tileSize); break; - case TileEnum::FLOOR_SPAWN: EntityFactory::create(world, tilePos, tileSize); break; + case TileEnum::FLOOR_SPAWN: EntityFactory::create(m_game, world, tilePos, tileSize); break; case TileEnum::EXIT_DOOR_FACING_NORTH: { - auto e = EntityFactory::create(world, tilePos, tileSize); - world.get(e).angle = kPI; + auto e = EntityFactory::create(m_game, world, tilePos, tileSize); + world.get(e).angle = std::numbers::pi_v; break; } case TileEnum::EXIT_DOOR_FACING_EAST: { - auto e = EntityFactory::create(world, tilePos, tileSize); - world.get(e).angle = kPI / 2; + auto e = EntityFactory::create(m_game, world, tilePos, tileSize); + world.get(e).angle = std::numbers::pi_v / 2; break; } case TileEnum::EXIT_DOOR_FACING_SOUTH: { - auto e = EntityFactory::create(world, tilePos, tileSize); + auto e = EntityFactory::create(m_game, world, tilePos, tileSize); world.get(e).angle = 0; break; } case TileEnum::EXIT_DOOR_FACING_WEST: { - auto e = EntityFactory::create(world, tilePos, tileSize); - world.get(e).angle = 3 * kPI / 2; + auto e = EntityFactory::create(m_game, world, tilePos, tileSize); + world.get(e).angle = 3 * std::numbers::pi_v / 2; break; } - case TileEnum::DEBUG_TILE: EntityFactory::create(world, tilePos, tileSize); break; - case TileEnum::WALL: EntityFactory::create(world, tilePos, tileSize); break; + case TileEnum::DEBUG_TILE: EntityFactory::create(m_game, world, tilePos, tileSize); break; + case TileEnum::WALL: EntityFactory::create(m_game, world, tilePos, tileSize); break; default: break; } diff --git a/src/Application/src/widgets/Fonts.cpp b/src/Application/src/widgets/Fonts.cpp new file mode 100644 index 0000000..8973849 --- /dev/null +++ b/src/Application/src/widgets/Fonts.cpp @@ -0,0 +1,35 @@ +#include +#include +#include + +#include "widgets/Fonts.hpp" + +ImFont *game::Fonts::kimberley_23; +ImFont *game::Fonts::kimberley_35; +ImFont *game::Fonts::kimberley_50; +ImFont *game::Fonts::kimberley_62; +ImFont *game::Fonts::opensans_44; +ImFont *game::Fonts::opensans_32; +ImFont *game::Fonts::imgui; + +void game::Fonts::loadFonts() noexcept +{ + imgui = ImGui::GetIO().Fonts->AddFontDefault(); + + kimberley_23 = load("fonts/kimberley_bl.ttf", 23.f); + kimberley_35 = load("fonts/kimberley_bl.ttf", 35.f); + kimberley_50 = load("fonts/kimberley_bl.ttf", 50.f); + kimberley_62 = load("fonts/kimberley_bl.ttf", 62.f); + + opensans_44 = load("fonts/OpenSans.ttf", 44.f); + opensans_32 = load("fonts/OpenSans.ttf", 32.f); +} + +ImFont *game::Fonts::load(const std::string &simplePath, float size) noexcept +{ + static auto holder = engine::Core::Holder{}; + + ImGuiIO &io = ImGui::GetIO(); + + return io.Fonts->AddFontFromFileTTF((holder.instance->settings().data_folder + simplePath).c_str(), size); +} diff --git a/src/Application/src/widgets/GameHUD.cpp b/src/Application/src/widgets/GameHUD.cpp new file mode 100644 index 0000000..e7c5284 --- /dev/null +++ b/src/Application/src/widgets/GameHUD.cpp @@ -0,0 +1,183 @@ +#include + +#include +#include +#include +#include +#include + +#include "ThePURGE.hpp" + +#include "models/Spell.hpp" +#include "models/Class.hpp" + +#include "component/all.hpp" + +#include "widgets/GameHUD.hpp" +#include "widgets/Fonts.hpp" +#include "widgets/helpers.hpp" + +void game::GameHUD::draw(ThePURGE &game, entt::registry &world) +{ + const auto player = game.player; + + const auto *currentClass = game.dbClasses().getByName(world.get(player).ids.back()); + assert(currentClass != nullptr); + + const auto &health = world.get(player); + const auto &level = world.get(player); + const auto &skillPoints = world.get(player).count; + +// #pragma region Static textures + // clang-format off + + static GUITexture staticBackground = { + .id = helper::getTexture("img/hud/hud_static.png"), + .topleft = helper::from1080p(25, 16), + .size = helper::from1080p(339, 152) + }; + + static GUITexture LB = { + .id = helper::getTexture("img/hud/LB.png"), + .topleft = helper::from1080p(5, 160), + .size = helper::from1080p(30, 22) + }; + + static GUITexture LT = { + .id = helper::getTexture("img/hud/LT.png"), + .topleft = helper::from1080p(80, 160), + .size = helper::from1080p(30, 27) + }; + + static GUITexture RT = { + .id = helper::getTexture("img/hud/RT.png"), + .topleft = helper::from1080p(155, 160), + .size = helper::from1080p(30, 27) + }; + + static GUITexture RB = { + .id = helper::getTexture("img/hud/RB.png"), + .topleft = helper::from1080p(230, 160), + .size = helper::from1080p(30, 22) + }; + + static GUITexture UpgradeIcon = { + .id = helper::getTexture("img/hud/UpgradeIcon.png"), + .topleft = helper::from1080p(337, 125), + .size = helper::from1080p(26, 33) + }; + // clang-format on +// #pragma endregion Static textures + + GUITexture portrait = { + .id = helper::getTexture(currentClass->iconPath), + .topleft = helper::from1080p(28, 25), + .size = helper::from1080p(70, 80)}; + + + ImGui::SetNextWindowPos(ImVec2(0, 0)); + ImGui::SetNextWindowSize(helper::frac2pixel({1.f, 1.f})); + ImGui::SetNextWindowBgAlpha(0.f); + ImGui::Begin("HUD", nullptr, ImGuiWindowFlags_NoDecoration); + + helper::drawTexture(staticBackground); + + helper::drawTexture(portrait); + + + drawHealthBar(health); + drawXpBar(level); + + ImGui::PushFont(Fonts::kimberley_23); + { + ImGui::SetCursorPos(helper::frac2pixel(helper::from1080p(377, 20))); + ::helper::ImGui::Text("{}/{}", health.current, health.max); + + ImGui::SetCursorPos(helper::frac2pixel(helper::from1080p(377, 63))); + ::helper::ImGui::Text("{}/{}", level.current_xp, level.xp_require); + } + ImGui::PopFont(); + + + const auto &spells = world.get(player).spells; + + constexpr auto spellX = std::to_array({26.0f, 251.0f, 101.0f, 176.0f}); + + for (std::size_t i = 0; i < 4; ++i) { + if (!spells[i].has_value()) continue; + + GUITexture spell{ + .id = helper::getTexture(game.dbSpells().db.at(std::string(spells[i]->id)).iconPath), + .topleft = helper::from1080p(spellX[i], 119), + .size = helper::from1080p(48.0f, 48.0f)}; + + helper::drawTexture(spell); + + if (spells[i]->cd.is_in_cooldown) + drawSpellCooldown( + spellX[i], + static_cast(spells[i]->cd.remaining_cooldown.count()) + / static_cast(spells[i]->cd.cooldown.count())); + } + + helper::drawTexture(LB); + helper::drawTexture(LT); + helper::drawTexture(RT); + helper::drawTexture(RB); + + + if (skillPoints) { + helper::drawTexture(UpgradeIcon); + + ImGui::PushFont(Fonts::kimberley_23); + ImGui::SetCursorPos(helper::frac2pixel(helper::from1080p(371, 131))); + ::helper::ImGui::Text("{}", skillPoints); + ImGui::PopFont(); + } + + ImGui::End(); +} + +void game::GameHUD::drawHealthBar(const Health &health) +{ + const auto fraction = health.current / health.max; + + ImVec2 topLeft = helper::frac2pixel(helper::from1080p(137, 16)); + ImVec2 bottomRight = helper::frac2pixel(helper::from1080p(std::lerp(137, 364, fraction), 51)); + + ImVec4 color(0, 0, 0, 1); + if (fraction < 0.5f) { + // red + color.x = 1; + // green + color.y = std::lerp(0, 1, fraction * 2); + } else { + // red + color.x = std::lerp(1, 0, (fraction - 0.5f) * 2); + // green + color.y = 1; + } + + ImGui::GetWindowDrawList()->AddRectFilled(topLeft, bottomRight, ImGui::ColorConvertFloat4ToU32(color)); +} + +void game::GameHUD::drawXpBar(const Level &level) +{ + const auto fraction = static_cast(level.current_xp) / static_cast(level.xp_require); + + ImVec2 topLeft = helper::frac2pixel(helper::from1080p(137, 57)); + ImVec2 bottomRight = helper::frac2pixel(helper::from1080p(std::lerp(137, 364, fraction), 92)); + ImVec4 color(1.f, 1.f, 0.2f, 1); + + + ImGui::GetWindowDrawList()->AddRectFilled(topLeft, bottomRight, ImGui::ColorConvertFloat4ToU32(color)); +} + +void game::GameHUD::drawSpellCooldown(float spellX, float remaining) +{ + ImVec2 topLeft = helper::frac2pixel(helper::from1080p(spellX, std::lerp(167, 119, remaining))); + ImVec2 bottomRight = helper::frac2pixel(helper::from1080p(spellX + 48, 167)); + ImVec4 color(.3f, .3f, .3f, .75); + + ImGui::GetWindowDrawList()->AddRectFilled(topLeft, bottomRight, ImGui::ColorConvertFloat4ToU32(color)); +} diff --git a/src/Application/src/widgets/console/ConsoleCommands.cpp b/src/Application/src/widgets/debug/console/ConsoleCommands.cpp similarity index 69% rename from src/Application/src/widgets/console/ConsoleCommands.cpp rename to src/Application/src/widgets/debug/console/ConsoleCommands.cpp index 6e9235d..39a9d6b 100644 --- a/src/Application/src/widgets/console/ConsoleCommands.cpp +++ b/src/Application/src/widgets/debug/console/ConsoleCommands.cpp @@ -2,11 +2,13 @@ #include #include -#include "widgets/console/ConsoleCommands.hpp" -#include "widgets/console/DebugConsole.hpp" +#include "widgets/debug/console/ConsoleCommands.hpp" +#include "widgets/debug/console/DebugConsole.hpp" + +#include "models/Spell.hpp" #include "component/all.hpp" -#include "screen/MainMenu.hpp" + #include "ThePURGE.hpp" game::CommandHandler::CommandHandler() : @@ -19,7 +21,8 @@ game::CommandHandler::CommandHandler() : {"buyClass", cmd_buyClass}, {"getClasses", cmd_getClasses}, {"getClassInfo", cmd_getClassInfo}, - {"giantfireball", cmd_giantfireball}, + {"addHealth", cmd_addHealth}, + //{"giantfireball", cmd_giantfireball}, } { } @@ -45,11 +48,10 @@ game::CommandHandler::handler_t game::CommandHandler::cmd_kill = const auto what = lexicalCast(args[0]); if (what == "player") { - for (auto &e : world.view>()) - game.getLogics().onEntityKilled.publish(world, e, e); + for (auto &e : world.view>()) game.logics()->onEntityKilled.publish(world, e, e); } else if (what == "boss") { for (auto &e : world.view>()) - game.getLogics().onEntityKilled.publish(world, e, game.player); + game.logics()->onEntityKilled.publish(world, e, game.player); } else throw std::runtime_error(fmt::format("Invalid argument {}", what)); @@ -65,13 +67,13 @@ game::CommandHandler::handler_t game::CommandHandler::cmd_setSpell = if (args.size() != 2) throw std::runtime_error("Wrong argument count"); auto idx = lexicalCast(args[0]); - auto spellId = lexicalCast(args[1]); + auto spellId = args[1]; if (idx < 0 || idx > 4) throw std::runtime_error(fmt::format("Wrong index : {}", args[0])); auto player = game.player; auto &spellSlots = world.get(player); - spellSlots.spells[static_cast(idx)] = Spell::create(static_cast(spellId)); + spellSlots.spells[static_cast(idx)] = game.dbSpells().instantiate(spellId); } catch (const std::runtime_error &e) { throw std::runtime_error(fmt::format("{}\nusage: setSpell index spell_id", e.what())); @@ -87,7 +89,7 @@ game::CommandHandler::handler_t game::CommandHandler::cmd_addXp = auto player = game.player; - game.getLogics().addXp(world, player, amount); + game.logics()->addXp(world, player, amount); } catch (const std::runtime_error &e) { throw std::runtime_error(fmt::format("{}\nusage: addXp xp", e.what())); @@ -105,13 +107,33 @@ game::CommandHandler::handler_t game::CommandHandler::cmd_addLevel = auto &level = world.get(player); - while (amount--) game.getLogics().addXp(world, player, level.xp_require); + while (amount--) game.logics()->addXp(world, player, level.xp_require); } catch (const std::runtime_error &e) { throw std::runtime_error(fmt::format("{}\nusage: addLevel level", e.what())); } }; +game::CommandHandler::handler_t game::CommandHandler::cmd_addHealth = + [](entt::registry &world, ThePURGE &game, std::vector &&args, DebugConsole &) { + try { + if (args.size() != 1) throw std::runtime_error("Wrong argument count"); + + auto amount = lexicalCast(args[0]); + + auto player = game.player; + + auto &health = world.get(player); + + health.current += amount; + if (health.current > health.max) + health.current = health.max; + + } catch (const std::runtime_error &e) { + throw std::runtime_error(fmt::format("{}\nusage: addHealth amount", e.what())); + } + }; + game::CommandHandler::handler_t game::CommandHandler::cmd_setMusicVolume = []([[maybe_unused]] entt::registry &world, ThePURGE &game, std::vector &&args, DebugConsole &) { try { @@ -119,7 +141,7 @@ game::CommandHandler::handler_t game::CommandHandler::cmd_setMusicVolume = const auto volume = lexicalCast(args[0]); - game.getMusic()->setVolume(volume); + game.getBackgroundMusic()->setVolume(volume); } catch (const std::runtime_error &e) { throw std::runtime_error(fmt::format("{}\nusage: setMusicVolume volume\n\tvolume : [0; 2]", e.what())); @@ -127,19 +149,19 @@ game::CommandHandler::handler_t game::CommandHandler::cmd_setMusicVolume = }; game::CommandHandler::handler_t game::CommandHandler::cmd_buyClass = - []([[maybe_unused]] entt::registry &world, ThePURGE &game, std::vector &&args, DebugConsole &) { + [](entt::registry &world, ThePURGE &game, std::vector &&args, DebugConsole &) { try { if (args.size() != 1) throw std::runtime_error("Wrong argument count"); const auto className = lexicalCast(args[0]); const auto player = game.player; - if (const auto data = classes::getByName(game.getClassDatabase(), className); data) { - game.getLogics().onPlayerBuyClass.publish(world, player, *data); + if (const auto data = game.dbClasses().getByName(className); data) { + game.logics()->onPlayerPurchase.publish(world, player, *data); } else { // note : see std::accumulate std::stringstream names; - for (const auto &[_, i] : game.getClassDatabase()) { names << i.name << ", "; } + for (const auto &i : game.dbClasses().db) { names << i.name << ", "; } throw std::runtime_error(fmt::format("Available classes : [ {}]", names.str())); } @@ -149,16 +171,16 @@ game::CommandHandler::handler_t game::CommandHandler::cmd_buyClass = }; game::CommandHandler::handler_t game::CommandHandler::cmd_getClasses = - []([[maybe_unused]] entt::registry &world, ThePURGE &game, std::vector &&args, DebugConsole &console) { + [](entt::registry &world, ThePURGE &game, std::vector &&args, DebugConsole &console) { if (args.size() != 0) throw std::runtime_error("Wrong argument count"); const auto &classes = world.get(game.player).ids; - std::stringstream names; - - for (auto &id : classes) names << game.getClassDatabase().at(id).name << ", "; - - console.info("Player has {} classes : {}", classes.size(), names.str()); + console.info( + "Player has {} classes : {}", + classes.size(), + std::accumulate( + std::begin(classes), std::end(classes), std::string{}, [](auto out, auto &i) { return out + ", " + i; })); }; game::CommandHandler::handler_t game::CommandHandler::cmd_getClassInfo = @@ -168,22 +190,22 @@ game::CommandHandler::handler_t game::CommandHandler::cmd_getClassInfo = const auto className = lexicalCast(args[0]); - if (const auto data = classes::getByName(game.getClassDatabase(), className); !data) { - // note : see std::accumulate - std::stringstream names; - for (const auto &[_, i] : game.getClassDatabase()) names << i.name << ", "; - throw std::runtime_error(fmt::format("Available classes : [ {}]", names.str())); + if (const auto data = game.dbClasses().getByName(className); !data) { + throw std::runtime_error(fmt::format( + "Available classes : [ {}]", + std::accumulate( + std::begin(game.dbClasses().db), std::end(game.dbClasses().db), std::string{}, [](auto out, auto &i) { + return out + ", " + i.name; + }))); } else { std::stringstream spellNames; for (const auto &id : data->spells) spellNames << id << ", "; std::stringstream childrenesNames; - for (const auto &id : data->children) childrenesNames << game.getClassDatabase().at(id).name << ", "; + for (const auto &id : data->children) childrenesNames << game.dbClasses().getByName(id)->name << ", "; console.info( "Class {} :\n" - "\tid : {}\n" - "\tdescription : {}\n" "\ticon : {}\n" "\tgraph asset : {}\n" "\tspells : {}\n" @@ -191,13 +213,10 @@ game::CommandHandler::handler_t game::CommandHandler::cmd_getClassInfo = "\tdamage : {}\n" "\tchildren classes : {}", data->name, - data->id, - data->description, data->iconPath, data->assetGraphPath, spellNames.str(), - data->maxHealth, - data->damage, + data->health, childrenesNames.str()); } @@ -207,6 +226,6 @@ game::CommandHandler::handler_t game::CommandHandler::cmd_getClassInfo = }; game::CommandHandler::handler_t game::CommandHandler::cmd_giantfireball = - []([[maybe_unused]] entt::registry &world, ThePURGE &game, std::vector &&, DebugConsole &) { - SpellFactory::create(SpellFactory::DEBUG_GIANT_FIREBALL, world, game.player, glm::vec2(0, 0)); - }; \ No newline at end of file + [](entt::registry &, ThePURGE &, std::vector &&, DebugConsole &) { + // SpellFactory::create(SpellFactory::DEBUG_GIANT_FIREBALL, world, game.player, glm::vec2(0, 0)); + }; diff --git a/src/Application/src/widgets/console/DebugConsole.cpp b/src/Application/src/widgets/debug/console/DebugConsole.cpp similarity index 91% rename from src/Application/src/widgets/console/DebugConsole.cpp rename to src/Application/src/widgets/debug/console/DebugConsole.cpp index 23bc46e..8f62f81 100644 --- a/src/Application/src/widgets/console/DebugConsole.cpp +++ b/src/Application/src/widgets/debug/console/DebugConsole.cpp @@ -1,9 +1,9 @@ -#include "widgets/console/DebugConsole.hpp" +#include "widgets/debug/console/DebugConsole.hpp" #include #include #include -#include +#include #include game::DebugConsole::DebugConsole(ThePURGE &game) : m_game(game) diff --git a/src/Application/src/widgets/helpers.cpp b/src/Application/src/widgets/helpers.cpp new file mode 100644 index 0000000..3b69692 --- /dev/null +++ b/src/Application/src/widgets/helpers.cpp @@ -0,0 +1,56 @@ +#include +#include +#include +#include +#include + +#include "widgets/helpers.hpp" + +namespace game::helper { + +auto getTexture(const std::string &simplePath) -> std::uint32_t +{ + static auto holder = engine::Core::Holder{}; + + const auto &dataFolder = holder.instance->settings().data_folder; + + return engine::helper::loadTexture(dataFolder + simplePath); +} + +auto from1080p(float x, float y) noexcept -> ImVec2 { return ImVec2{x / 1920.0f, y / 1080.0f}; } + +auto frac2pixel(ImVec2 fraction) noexcept -> ImVec2 +{ + const auto winSize = engine::Core::Holder{}.instance->window()->getSize(); + return ImVec2(static_cast(winSize.x) * fraction.x, static_cast(winSize.y) * fraction.y); +} + +void drawTexture(std::uint32_t id, ImVec2 topLeft, ImVec2 size, ImVec4 tintColor) noexcept +{ + ImGui::SetCursorPos(topLeft); + ImGui::Image(reinterpret_cast(static_cast(id)), size, ImVec2(0, 0), ImVec2(1, 1), tintColor); +} + +void drawTexture(const GUITexture &t, ImVec4 tintColor) noexcept { drawTexture(t.id, frac2pixel(t.topleft), frac2pixel(t.size), tintColor); } + +void drawText(ImVec2 pos, const std::string &str, ImVec4 color, ImFont *font) noexcept +{ + if (font) ImGui::PushFont(font); + ImGui::SetCursorPos(pos); + ImGui::TextColored(color, "%s", str.c_str()); + if (font) ImGui::PopFont(); +} + +void drawTextWrapped(ImVec2 pos, const std::string &str, float maxX, ImFont *font) noexcept +{ + if (font) ImGui::PushFont(font); + ImGui::SetCursorPos(pos); + ImGui::PushTextWrapPos(maxX); + + ImGui::TextWrapped("%s", str.c_str()); + + ImGui::PopTextWrapPos(); + if (font) ImGui::PopFont(); +} + +} // namespace game::helper diff --git a/src/Engine/CMakeLists.txt b/src/Engine/CMakeLists.txt index 2b68bd6..af07dca 100644 --- a/src/Engine/CMakeLists.txt +++ b/src/Engine/CMakeLists.txt @@ -1,22 +1,31 @@ set(imgui_version "1.78") macro(download_imgui file) - if(NOT EXISTS "${CMAKE_BINARY_DIR}/download/imgui/${imgui_version}/${file}") + if(NOT EXISTS "${CMAKE_BINARY_DIR}/download/imgui/v${imgui_version}/${file}") message(STATUS "Downloading from https://github.com/ocornut/imgui/tree/v${imgui_version}/examples/${file}") file(DOWNLOAD "https://raw.githubusercontent.com/ocornut/imgui/v${imgui_version}/examples/${file}" - "${CMAKE_BINARY_DIR}/download/imgui/${imgui_version}/${file}") + "${CMAKE_BINARY_DIR}/download/imgui/v${imgui_version}/${file}") endif() endmacro() +## Note : These files exists in the conan package, so we don t want to download them like that + download_imgui(imgui_impl_glfw.cpp) download_imgui(imgui_impl_glfw.h) download_imgui(imgui_impl_opengl3.cpp) download_imgui(imgui_impl_opengl3.h) -add_library(glfw_imgui_impl STATIC ${CMAKE_BINARY_DIR}/download/imgui/${imgui_version}/imgui_impl_glfw.cpp - ${CMAKE_BINARY_DIR}/download/imgui/${imgui_version}/imgui_impl_opengl3.cpp) +add_library(glfw_imgui_impl STATIC ${CMAKE_BINARY_DIR}/download/imgui/v${imgui_version}/imgui_impl_glfw.cpp + ${CMAKE_BINARY_DIR}/download/imgui/v${imgui_version}/imgui_impl_opengl3.cpp) target_link_libraries(glfw_imgui_impl PUBLIC project_options CONAN_PKG::glew CONAN_PKG::glfw CONAN_PKG::imgui) target_compile_definitions(glfw_imgui_impl PUBLIC IMGUI_IMPL_OPENGL_LOADER_GLEW) +target_include_directories(glfw_imgui_impl PUBLIC ${CMAKE_BINARY_DIR}/download/imgui/v${imgui_version}) + +if(NOT EXISTS "${CMAKE_BINARY_DIR}/download/adamstark/v1.0.8/AudioFile.hpp") + message(STATUS "Downloading from https://github.com/adamstark/AudioFile/blob/1.0.8/AudioFile.h") + file(DOWNLOAD "https://raw.githubusercontent.com/adamstark/AudioFile/1.0.8/AudioFile.h" + "${CMAKE_BINARY_DIR}/download/adamstark/v1.0.8/AudioFile.hpp") +endif() configure_file(include/Engine/details/Version.hpp.in include/Engine/details/Version.hpp @ONLY) @@ -34,10 +43,11 @@ add_library( src/Engine/audio/AlErrorHandling.cpp src/Engine/audio/Sound.cpp src/Engine/audio/WavReader.cpp - src/Engine/audio/AudioFileBuffer.cpp) + src/Engine/audio/AudioFileBuffer.cpp + src/Engine/resources/Texture.cpp) -target_include_directories(engine_core PUBLIC include ${CMAKE_BINARY_DIR}/download/imgui/${imgui_version} - ${CMAKE_CURRENT_BINARY_DIR}/include) +target_include_directories(engine_core PUBLIC include ${CMAKE_CURRENT_BINARY_DIR}/include + ${CMAKE_BINARY_DIR}/download/adamstark/v1.0.8) target_link_libraries( engine_core PUBLIC project_options @@ -57,6 +67,7 @@ target_link_libraries( if(MSVC) target_compile_definitions(engine_core PUBLIC NOMINMAX) endif() +target_compile_definitions(engine_core PUBLIC GLM_FORCE_SILENT_WARNINGS) add_executable(engine_main src/Engine/main.cpp) target_link_libraries(engine_main PRIVATE engine_core ThePURGE) diff --git a/src/Engine/include/Engine/Core.hpp b/src/Engine/include/Engine/Core.hpp index 5f01de9..5ebc917 100644 --- a/src/Engine/include/Engine/Core.hpp +++ b/src/Engine/include/Engine/Core.hpp @@ -9,7 +9,9 @@ #include #include "Engine/resources/LoaderColor.hpp" +#include "Engine/resources/LoaderVBOTexture.hpp" #include "Engine/resources/LoaderTexture.hpp" + #include "Engine/Event/Event.hpp" #include "Engine/Settings.hpp" #include "Engine/audio/AudioManager.hpp" @@ -87,6 +89,7 @@ class Core { enum class EventMode { RECORD, PLAYBACK, + PAUSED, }; auto close() noexcept -> void { m_is_running = false; } @@ -102,6 +105,7 @@ class Core { auto window() noexcept -> std::unique_ptr & { return m_window; } [[nodiscard]] auto getEventMode() const noexcept { return m_eventMode; } + auto setEventMode(EventMode mode) noexcept { m_eventMode = mode; } [[nodiscard]] auto isRunning() const noexcept -> bool { return m_is_running; } @@ -150,8 +154,9 @@ class Core { std::unique_ptr m_joystickManager; - CacheColor m_colors; - CacheTexture m_textures; + entt::resource_cache m_colors; + entt::resource_cache m_vbo_textures; + entt::resource_cache m_textures; std::unique_ptr m_shader_colored; std::unique_ptr m_shader_colored_textured; @@ -171,19 +176,22 @@ class Core { template<> auto Core::getCache() noexcept -> entt::resource_cache &; +template<> +auto Core::getCache() noexcept -> entt::resource_cache &; + template<> auto Core::getCache() noexcept -> entt::resource_cache &; } // namespace engine #ifndef NDEBUG -# define IF_RECORD(...) \ - do { \ - if (engine::Core::Holder{}.instance->getEventMode() == engine::Core::EventMode::RECORD) { __VA_ARGS__; } \ +# define IF_NOT_PLAYBACK(...) \ + do { \ + if (engine::Core::Holder{}.instance->getEventMode() != engine::Core::EventMode::PLAYBACK) { __VA_ARGS__; } \ } while (0) #else -# define IF_RECORD(...) \ - do { \ - __VA_ARGS__; \ +# define IF_NOT_PLAYBACK(...) \ + do { \ + __VA_ARGS__; \ } while (0) #endif diff --git a/src/Engine/include/Engine/Event/Event.hpp b/src/Engine/include/Engine/Event/Event.hpp index df5bc64..ca1570f 100644 --- a/src/Engine/include/Engine/Event/Event.hpp +++ b/src/Engine/include/Engine/Event/Event.hpp @@ -108,6 +108,12 @@ struct MouseButton { Mouse mouse; }; +struct Character { + constexpr static std::string_view name{"Character"}; + constexpr static auto elements = std::to_array({"codepoint"}); + std::uint32_t codepoint; +}; + struct Joystick { constexpr static std::string_view name{"Joystick"}; constexpr static auto elements = std::to_array({"id", "axes", "buttons"}); @@ -195,6 +201,7 @@ using Event = std::variant< Pressed, Released, + Character, Moved, Pressed, diff --git a/src/Engine/include/Engine/Graphics/Shader.hpp b/src/Engine/include/Engine/Graphics/Shader.hpp index 37bcf4b..a7c132d 100644 --- a/src/Engine/include/Engine/Graphics/Shader.hpp +++ b/src/Engine/include/Engine/Graphics/Shader.hpp @@ -1,9 +1,10 @@ #pragma once +#include + #include -#include -#include +#include namespace engine { diff --git a/src/Engine/include/Engine/Graphics/Window.hpp b/src/Engine/include/Engine/Graphics/Window.hpp index 9a68feb..d72a797 100644 --- a/src/Engine/include/Engine/Graphics/Window.hpp +++ b/src/Engine/include/Engine/Graphics/Window.hpp @@ -60,6 +60,8 @@ class Window { bool screenshot(const std::string_view filename); + void setCursorVisible(bool visible) noexcept; + private: static Window *s_instance; @@ -83,6 +85,8 @@ class Window { static auto callback_eventMousePressed(GLFWwindow *window, int button, int action, int mods) -> void; static auto callback_eventMouseMoved(GLFWwindow *window, double x, double y) -> void; + + static auto callback_char(GLFWwindow *window, unsigned int codepoint) -> void; }; template<> @@ -97,4 +101,7 @@ auto Window::applyEvent(const Pressed &) -> void; template<> auto Window::applyEvent(const Released &) -> void; +template<> +auto Window::applyEvent(const Character &) -> void; + } // namespace engine diff --git a/src/Engine/include/Engine/Graphics/third_party.hpp b/src/Engine/include/Engine/Graphics/third_party.hpp index be41fda..b58e479 100644 --- a/src/Engine/include/Engine/Graphics/third_party.hpp +++ b/src/Engine/include/Engine/Graphics/third_party.hpp @@ -4,8 +4,8 @@ #define GLFW_INCLUDE_NONE #ifdef _WIN32 -# define GLFW_EXPOSE_NATIVE_WIN32 -# include +# define GLFW_EXPOSE_NATIVE_WIN32 +# include #endif #include @@ -13,3 +13,46 @@ #include #include #include + +#include + +namespace engine { + +inline constexpr auto GetGLErrorStr(GLenum err) +{ + switch (err) { + case GL_NO_ERROR: return "OPEN_GL: No error"; + case GL_INVALID_ENUM: return "OPEN_GL: Invalid enum"; + case GL_INVALID_VALUE: return "OPEN_GL: Invalid value"; + case GL_INVALID_OPERATION: return "OPEN_GL: Invalid operation"; + case GL_STACK_OVERFLOW: return "OPEN_GL: Stack overflow"; + case GL_STACK_UNDERFLOW: return "OPEN_GL: Stack underflow"; + case GL_OUT_OF_MEMORY: return "OPEN_GL: Out of memory"; + default: return "OPEN_GL: Unknown error"; + } +} + +} // namespace engine + +#ifndef NDEBUG + +# define CALL_OPEN_GL(call) \ + do { \ + call; \ + if (const auto err = glGetError(); GL_NO_ERROR != err) { \ + if constexpr (noexcept(__func__) == false) { \ + throw std::runtime_error(engine::GetGLErrorStr(err)); \ + } else { \ + spdlog::error("CALL_OPEN_GL: {}", engine::GetGLErrorStr(err)); \ + } \ + } \ + } while (0) + +#else + +# define CALL_OPEN_GL(call) \ + do { \ + call; \ + } while (0) + +#endif diff --git a/src/Engine/include/Engine/api/Game.hpp b/src/Engine/include/Engine/api/Game.hpp index 9852db3..9891a66 100644 --- a/src/Engine/include/Engine/api/Game.hpp +++ b/src/Engine/include/Engine/api/Game.hpp @@ -2,7 +2,7 @@ #include -#include +#include #include "Engine/Event/Event.hpp" @@ -15,12 +15,12 @@ class Game { virtual ~Game() = default; /** - * function called at the beginning of the game + * function called when the game is created */ virtual auto onCreate(entt::registry &) -> void = 0; /** - * function called at the end of the game + * function called at the game is destroyed */ virtual auto onDestroy(entt::registry &) -> void = 0; @@ -30,14 +30,14 @@ class Game { virtual auto onUpdate(entt::registry &, const Event &) -> void = 0; /** - * function called for the rendering of the GUI + * function called at every frame, should call the GUI rendering */ virtual auto drawUserInterface(entt::registry &) -> void = 0; /** * function called at every frame, return the clear color */ - virtual auto getBackgroundColor() const noexcept -> glm::vec3 = 0; + virtual auto getBackgroundColor() const noexcept -> glm::vec4 = 0; }; diff --git a/src/Engine/include/Engine/component/Color.hpp b/src/Engine/include/Engine/component/Color.hpp index 2907bf0..9e99989 100644 --- a/src/Engine/include/Engine/component/Color.hpp +++ b/src/Engine/include/Engine/component/Color.hpp @@ -3,12 +3,12 @@ #include #include -#include +#include namespace engine { struct Color { - std::array vertices{}; + std::array vertices{}; std::uint32_t VBO{0u}; static constexpr auto r(const Color &c) noexcept -> float { return c.vertices[0]; } @@ -17,8 +17,10 @@ struct Color { static constexpr auto b(const Color &c) noexcept -> float { return c.vertices[2]; } + static constexpr auto a(const Color &c) noexcept -> float { return c.vertices[3]; } + // note : you should not call this function yourself // see @DrawableFactory::fix_color - static auto ctor(glm::vec3 &&color) -> Color; + static auto ctor(glm::vec4 &&color) -> Color; // note : you should not call this function yourself // see @DrawableFactory::fix_color static auto dtor(Color *color) -> void; diff --git a/src/Engine/include/Engine/component/Copy.hpp b/src/Engine/include/Engine/component/Copy.hpp new file mode 100644 index 0000000..40ff208 --- /dev/null +++ b/src/Engine/include/Engine/component/Copy.hpp @@ -0,0 +1,10 @@ +#pragma once + +namespace engine { + +template +struct Copy { + T data; +}; + +} // namespace engine diff --git a/src/Engine/include/Engine/component/Hitbox.hpp b/src/Engine/include/Engine/component/Hitbox.hpp index 4d73a9c..163c106 100644 --- a/src/Engine/include/Engine/component/Hitbox.hpp +++ b/src/Engine/include/Engine/component/Hitbox.hpp @@ -61,6 +61,16 @@ template bx && ax < bw && ah > by && ay < bh; } +template +[[nodiscard]] constexpr auto overlapped( + const d3::PositionT &self_pos, + const HitboxT &self, + const d3::PositionT &other_pos, + const HitboxT &other) noexcept -> bool +{ + return overlapped(self, self_pos, other, other_pos); +} + template using Hitbox = HitboxT; diff --git a/src/Application/include/component/Lifetime.hpp b/src/Engine/include/Engine/component/Lifetime.hpp similarity index 72% rename from src/Application/include/component/Lifetime.hpp rename to src/Engine/include/Engine/component/Lifetime.hpp index 501ea5f..c249241 100644 --- a/src/Application/include/component/Lifetime.hpp +++ b/src/Engine/include/Engine/component/Lifetime.hpp @@ -2,10 +2,10 @@ #include -namespace game { +namespace engine { struct Lifetime { std::chrono::milliseconds remaining_lifetime; }; -} // namespace game +} // namespace engine diff --git a/src/Engine/include/Engine/component/Rotation.hpp b/src/Engine/include/Engine/component/Rotation.hpp index e1294a2..f608740 100644 --- a/src/Engine/include/Engine/component/Rotation.hpp +++ b/src/Engine/include/Engine/component/Rotation.hpp @@ -9,7 +9,7 @@ namespace d2 { template struct RotationT { // Radian - T angle; + T angle; }; using Rotation = RotationT; diff --git a/src/Engine/include/Engine/component/Source.hpp b/src/Engine/include/Engine/component/Source.hpp index 0df8c69..4c34fee 100644 --- a/src/Engine/include/Engine/component/Source.hpp +++ b/src/Engine/include/Engine/component/Source.hpp @@ -6,9 +6,11 @@ namespace engine { // note : there should be another way struct Source { - entt::entity source; +}; +struct SourceBis { + entt::entity source; }; } // namespace engine diff --git a/src/Engine/include/Engine/component/Spritesheet.hpp b/src/Engine/include/Engine/component/Spritesheet.hpp index 33299fe..6f17c84 100644 --- a/src/Engine/include/Engine/component/Spritesheet.hpp +++ b/src/Engine/include/Engine/component/Spritesheet.hpp @@ -16,47 +16,65 @@ namespace engine { struct Spritesheet { - std::string file; + struct Animation { + std::string file; + std::uint16_t width; + std::uint16_t height; - std::uint16_t width; - std::uint16_t height; + std::vector> frames; - std::unordered_map>> animations; + std::chrono::milliseconds cooldown; + }; + + std::unordered_map animations; + + Cooldown cooldown; - Cooldown speed; std::uint16_t current_frame{0}; std::string current_animation{"default"}; + bool attack_animation_finish{true}; + static auto from_json(const std::string_view file) -> Spritesheet; }; // todo : move in .cpp -inline void to_json(nlohmann::json &j, const engine::Spritesheet &sprite) +inline void to_json(nlohmann::json &j, const engine::Spritesheet::Animation &animation) { // clang-format off - j = nlohmann::json{{"object", { - "file", sprite.file, - "width", sprite.width, - "height", sprite.height, - "animations", sprite.animations, - "speed", sprite.speed.cooldown.count() - }}}; + j = nlohmann::json{{ + "file", animation.file, + "width", animation.width, + "height", animation.height, + "cooldown", animation.cooldown.count(), + "frames", animation.frames, + }}; // clang-format on } +inline void from_json(const nlohmann::json &j, engine::Spritesheet::Animation &animation) +{ + animation.file = j.at("file"); + animation.width = j.at("width"); + animation.height = j.at("height"); + animation.cooldown = std::chrono::milliseconds{j.at("cooldown")}; + animation.frames = j.at("frames").get>(); +} + +inline void to_json(nlohmann::json &j, const engine::Spritesheet &sprite) +{ + j = nlohmann::json{{"object", {"animations", sprite.animations}}}; +} + inline void from_json(const nlohmann::json &j, engine::Spritesheet &sprite) { using namespace std::chrono_literals; - sprite.file = j.at("object").at("file"); - sprite.width = j.at("object").at("width"); - sprite.height = j.at("object").at("height"); sprite.animations = j.at("object").at("animations").get>(); - std::uint64_t value = j.at("object").at("speed"); - sprite.speed.cooldown = std::chrono::milliseconds{value}; - sprite.speed.is_in_cooldown = false; - sprite.speed.remaining_cooldown = 0ms; + sprite.cooldown.is_in_cooldown = false; + sprite.cooldown.cooldown = 0ms; + sprite.cooldown.remaining_cooldown = 0ms; } inline auto Spritesheet::from_json(const std::string_view file) -> Spritesheet diff --git a/src/Engine/include/Engine/component/Texture.hpp b/src/Engine/include/Engine/component/VBOTexture.hpp similarity index 58% rename from src/Engine/include/Engine/component/Texture.hpp rename to src/Engine/include/Engine/component/VBOTexture.hpp index 2310db6..46b46fa 100644 --- a/src/Engine/include/Engine/component/Texture.hpp +++ b/src/Engine/include/Engine/component/VBOTexture.hpp @@ -6,7 +6,7 @@ namespace engine { -struct Texture { +struct VBOTexture { // clang-format off std::array vertices = { 0.0f, 1.0f, // top left @@ -17,17 +17,12 @@ struct Texture { // clang-format on std::uint32_t VBO; - std::uint32_t texture; - - std::int32_t width; - std::int32_t height; - std::int32_t channels; - std::uint8_t *px; + std::uint32_t id; bool mirrored; - static auto ctor(const std::string_view path, const std::array &) -> Texture; + static auto ctor(const std::string_view path, bool mirrored_repeated, const std::array &) -> VBOTexture; - static auto dtor(Texture *ptr) -> void; + static auto dtor(VBOTexture *ptr) -> void; }; } // namespace engine diff --git a/src/Engine/include/Engine/helpers/DrawableFactory.hpp b/src/Engine/include/Engine/helpers/DrawableFactory.hpp index d8b755f..afeb228 100644 --- a/src/Engine/include/Engine/helpers/DrawableFactory.hpp +++ b/src/Engine/include/Engine/helpers/DrawableFactory.hpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include @@ -16,39 +16,20 @@ namespace engine { struct Drawable; struct Color; -struct Texture; +struct VBOTexture; struct DrawableFactory { static auto rectangle() -> Drawable; - static auto fix_color(entt::registry &, entt::entity, glm::vec3 &&color) -> Color &; + static auto fix_color(entt::registry &, entt::entity, glm::vec4 &&color) -> Color &; static auto fix_texture( entt::registry &, entt::entity, const std::string_view filepath, - const std::array &clip = {0.0f, 0.0f, 1.0f, 1.0f}) -> Texture &; - - // todo : cleaner - static GLuint createtexture(const std::string &fullpath) - { - int w, h, channel; - auto image = stbi_load(fullpath.c_str(), &w, &h, &channel, 4); - - if (!image) { throw std::runtime_error("Failed to load image " + fullpath + " : " + stbi_failure_reason()); } - - GLuint id; - glGenTextures(1, &id); - glBindTexture(GL_TEXTURE_2D, id); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, image); - stbi_image_free(image); - - return id; - } + bool mirrored_repeated = false, + const std::array &clip = {0.0f, 0.0f, 1.0f, 1.0f}) -> VBOTexture &; + static auto fix_spritesheet(entt::registry &world, entt::entity entity, const std::string_view animation) -> void; }; } // namespace engine diff --git a/src/Engine/include/Engine/helpers/ImGui.hpp b/src/Engine/include/Engine/helpers/ImGui.hpp index 8db8a28..b7049dc 100644 --- a/src/Engine/include/Engine/helpers/ImGui.hpp +++ b/src/Engine/include/Engine/helpers/ImGui.hpp @@ -14,10 +14,10 @@ inline void Text(std::string_view format, Param &&... param) ::ImGui::TextUnformatted(fmt::format(format, std::forward(param)...).c_str()); } -inline bool Button(std::string id, ImVec4 selectedColor) { - bool pressed; +inline bool Button(std::string_view label, ImVec4 selectedColor) +{ ::ImGui::PushStyleColor(ImGuiCol_Button, selectedColor); - pressed =::ImGui::Button(id.c_str()); + const auto pressed = ::ImGui::Button(label.data()); ::ImGui::PopStyleColor(1); return pressed; } diff --git a/src/Engine/include/Engine/helpers/TextureLoader.hpp b/src/Engine/include/Engine/helpers/TextureLoader.hpp new file mode 100644 index 0000000..87699c5 --- /dev/null +++ b/src/Engine/include/Engine/helpers/TextureLoader.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +#include + +#include "Engine/resources/LoaderTexture.hpp" +#include "Engine/Core.hpp" + +namespace engine::helper { + +inline std::uint32_t loadTexture(const std::string &path, bool mirrored_repeated = false) +{ + static engine::Core::Holder holder{}; + + auto key = entt::hashed_string{fmt::format("resource/texture/identifier/{}_{}", path, mirrored_repeated).data()}; + if (const auto &resource = + holder.instance->getCache().load(key, path, mirrored_repeated); + resource) { + return resource->id; + } else { + spdlog::error("Could not load {}: ", path); + return 0; + } +} + +} // namespace engine::helper diff --git a/src/Engine/include/Engine/helpers/Warnings.hpp b/src/Engine/include/Engine/helpers/Warnings.hpp index 983ca93..9a76848 100644 --- a/src/Engine/include/Engine/helpers/Warnings.hpp +++ b/src/Engine/include/Engine/helpers/Warnings.hpp @@ -43,13 +43,19 @@ # define DISABLE_WARNING_MSVC_LEVEL_4 DISABLE_WARNING(4296) # define DISABLE_WARNING_CONSTANT_CONDITIONAL DISABLE_WARNING(4127) # define DISABLE_WARNING_NON_STD_EXTENSION DISABLE_WARNING(4201) +# define DISABLE_WARNING_OLD_CAST +# define DISABLE_WARNING_SIGN_CONVERSION +# define DISABLE_WARNING_USELESS_CAST #elif defined(__GNUC__) || defined(__clang__) -# define DISABLE_WARNING_UNREFERENCED_FORMAL_PARAMETER DISABLE_WARNING(-Wunused-parameter) -# define DISABLE_WARNING_UNREFERENCED_FUNCTION DISABLE_WARNING(-Wunused-function) +# define DISABLE_WARNING_UNREFERENCED_FORMAL_PARAMETER DISABLE_WARNING(-Wunused-parameter) +# define DISABLE_WARNING_UNREFERENCED_FUNCTION DISABLE_WARNING(-Wunused-function) # define DISABLE_WARNING_MSVC_LEVEL_4 # define DISABLE_WARNING_CONSTANT_CONDITIONAL # define DISABLE_WARNING_NON_STD_EXTENSION +# define DISABLE_WARNING_OLD_CAST DISABLE_WARNING(-Wold-style-cast) +# define DISABLE_WARNING_SIGN_CONVERSION DISABLE_WARNING(-Wsign-conversion) +# define DISABLE_WARNING_USELESS_CAST DISABLE_WARNING(-Wuseless-cast) #else # define DISABLE_WARNING_UNREFERENCED_FORMAL_PARAMETER @@ -57,5 +63,8 @@ # define DISABLE_WARNING_MSVC_LEVEL_4 # define DISABLE_WARNING_CONSTANT_CONDITIONAL # define DISABLE_WARNING_NON_STD_EXTENSION +# define DISABLE_WARNING_OLD_CAST +# define DISABLE_WARNING_SIGN_CONVERSION +# define DISABLE_WARNING_USELESS_CAST #endif diff --git a/src/Engine/include/Engine/resources/LoaderTexture.hpp b/src/Engine/include/Engine/resources/LoaderTexture.hpp index 993ccc1..b3d9121 100644 --- a/src/Engine/include/Engine/resources/LoaderTexture.hpp +++ b/src/Engine/include/Engine/resources/LoaderTexture.hpp @@ -4,6 +4,10 @@ #include +#include + +#include "Engine/resources/Texture.hpp" + namespace engine { struct Texture; @@ -12,7 +16,7 @@ struct LoaderTexture : entt::resource_loader { template auto load(Args &&... args) const -> std::shared_ptr { - return std::shared_ptr(new Texture{Texture::ctor(args...)}, Texture::dtor); + return std::shared_ptr(new Texture{Texture::ctor(std::forward(args)...)}, Texture::dtor); } }; diff --git a/src/Engine/include/Engine/resources/LoaderVBOTexture.hpp b/src/Engine/include/Engine/resources/LoaderVBOTexture.hpp new file mode 100644 index 0000000..badc42f --- /dev/null +++ b/src/Engine/include/Engine/resources/LoaderVBOTexture.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include + +#include + +namespace engine { + +struct VBOTexture; + +struct LoaderVBOTexture : entt::resource_loader { + template + auto load(Args &&... args) const -> std::shared_ptr + { + return std::shared_ptr(new VBOTexture{VBOTexture::ctor(args...)}, VBOTexture::dtor); + } +}; + +using CacheVBOTexture = entt::resource_cache; + +} // namespace engine diff --git a/src/Engine/include/Engine/resources/Texture.hpp b/src/Engine/include/Engine/resources/Texture.hpp new file mode 100644 index 0000000..5396901 --- /dev/null +++ b/src/Engine/include/Engine/resources/Texture.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +#include +#include + +#include "Engine/Graphics/third_party.hpp" + + +namespace engine { + +struct Texture { + GLuint id; + + std::int32_t width; + std::int32_t height; + std::int32_t channels; + std::uint8_t *px; + + static auto ctor(const std::string_view filepath, bool mirrored_repeated) -> Texture; + + static auto dtor(Texture *obj) -> void; +}; + +} // namespace engine diff --git a/src/Engine/src/Engine/Component.cpp b/src/Engine/src/Engine/Component.cpp index 2e4cd74..ba2c760 100644 --- a/src/Engine/src/Engine/Component.cpp +++ b/src/Engine/src/Engine/Component.cpp @@ -5,39 +5,42 @@ #include "Engine/component/Drawable.hpp" #include "Engine/component/Color.hpp" -#include "Engine/component/Texture.hpp" -#include "Engine/resources/LoaderTexture.hpp" +#include "Engine/component/VBOTexture.hpp" +#include "Engine/resources/LoaderVBOTexture.hpp" + +#include "Engine/Core.hpp" auto engine::Drawable::dtor(const Drawable &drawable) -> void { - ::glDeleteVertexArrays(1, &drawable.VAO); - ::glDeleteBuffers(1, &drawable.VBO); - ::glDeleteBuffers(1, &drawable.EBO); + CALL_OPEN_GL(::glDeleteVertexArrays(1, &drawable.VAO)); + CALL_OPEN_GL(::glDeleteBuffers(1, &drawable.VBO)); + CALL_OPEN_GL(::glDeleteBuffers(1, &drawable.EBO)); } -auto engine::Color::ctor(glm::vec3 &&color) -> Color +auto engine::Color::ctor(glm::vec4 &&color) -> Color { // clang-format off Color out = { .vertices = { - color.r, color.g, color.b, - color.r, color.g, color.b, - color.r, color.g, color.b, - color.r, color.g, color.b, + color.r, color.g, color.b, color.a, + color.r, color.g, color.b, color.a, + color.r, color.g, color.b, color.a, + color.r, color.g, color.b, color.a, }}; // clang-format on - ::glGenBuffers(1, &out.VBO); + CALL_OPEN_GL(::glGenBuffers(1, &out.VBO)); return out; } -auto engine::Color::dtor(Color *color) -> void { ::glDeleteBuffers(1, &color->VBO); } +auto engine::Color::dtor(Color *color) -> void { CALL_OPEN_GL(::glDeleteBuffers(1, &color->VBO)); } -auto engine::Texture::ctor(const std::string_view path, const std::array &clip) -> Texture +auto engine::VBOTexture::ctor(const std::string_view path, bool mirrored_repeated, const std::array &clip) + -> VBOTexture { // clang-format off - Texture out = { + VBOTexture out = { .vertices = { clip[0], clip[1] + clip[3], // top left clip[0] + clip[2], clip[1] + clip[3], // top right @@ -45,39 +48,26 @@ auto engine::Texture::ctor(const std::string_view path, const std::arraygetCache().load( + entt::hashed_string{fmt::format("resource/texture/identifier/{}_{}", path.data(), mirrored_repeated).data()}, + path, + mirrored_repeated); + if (!handle) { + spdlog::error("could not load texture in cache !"); + throw std::runtime_error("could not load texture in cache !"); + } else { + out.id = + entt::hashed_string{fmt::format("resource/texture/identifier/{}_{}", path.data(), mirrored_repeated).data()}; } - ::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, out.width, out.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, out.px); - ::glGenerateMipmap(GL_TEXTURE_2D); - return out; } -auto engine::Texture::dtor(Texture *ptr) -> void -{ - ::stbi_image_free(ptr->px); - ::glDeleteBuffers(1, &ptr->VBO); - ::glDeleteTextures(1, &ptr->texture); -} +auto engine::VBOTexture::dtor(VBOTexture *ptr) -> void { CALL_OPEN_GL(::glDeleteBuffers(1, &ptr->VBO)); } diff --git a/src/Engine/src/Engine/Core.cpp b/src/Engine/src/Engine/Core.cpp index 61b0454..0541771 100644 --- a/src/Engine/src/Engine/Core.cpp +++ b/src/Engine/src/Engine/Core.cpp @@ -18,9 +18,11 @@ #include "Engine/component/Velocity.hpp" #include "Engine/component/Acceleration.hpp" #include "Engine/component/Hitbox.hpp" +#include "Engine/component/Source.hpp" #include "Engine/component/Color.hpp" #include "Engine/component/Spritesheet.hpp" -#include "Engine/component/Texture.hpp" +#include "Engine/component/VBOTexture.hpp" +#include "Engine/component/Lifetime.hpp" #include "Engine/Event/Event.hpp" #include "Engine/Graphics/Shader.hpp" @@ -52,17 +54,17 @@ engine::Core::Core([[maybe_unused]] hidden_type &&) spdlog::error("Engine::Core GLFW An error occured '{}' 'code={}'\n", message, code); }); - spdlog::info("Engine::Core instanciated"); + spdlog::trace("Engine::Core instanciated"); if (::glfwInit() == GLFW_FALSE) { throw std::logic_error(fmt::format("Engine::Core initialization failed")); } - ::glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - ::glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + ::glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + ::glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5); ::glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); #ifdef __APPLE__ ::glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); #endif - spdlog::info("Engine::Core GLFW version: '{}'\n", ::glfwGetVersionString()); + spdlog::trace("Engine::Core GLFW version: '{}'\n", ::glfwGetVersionString()); IMGUI_CHECKVERSION(); @@ -71,12 +73,13 @@ engine::Core::Core([[maybe_unused]] hidden_type &&) engine::Core::~Core() { - ::glfwTerminate(); + m_textures.clear(); + m_vbo_textures.clear(); + m_colors.clear(); - // note : otherwise we have a final error message at the end + ::glfwTerminate(); ::glfwSetErrorCallback(nullptr); - - spdlog::info("Engine::Core destroyed"); + spdlog::trace("Engine::Core destroyed"); } auto engine::Core::setPendingEvents(std::vector &&events) -> void @@ -105,31 +108,20 @@ auto engine::Core::getNextEvent() -> Event ::glfwPollEvents(); switch (m_eventMode) { + case EventMode::PAUSED: case EventMode::RECORD: { m_joystickManager->poll(); - - // 1. poll the window event - // 2. poll the joysticks event - // 3. send elapsed time - auto event = - m_window->getNextEvent().value_or(m_joystickManager->getNextEvent().value_or(TimeElapsed{getElapsedTime()})); - - // TEMPORARY, BY YANIS. Allows for keyboard input to work. waiting for Mathieu to help do it a clean way - std::visit( - overloaded{ - [&](const auto &e) { m_window->applyEvent(e); }, - }, - event); - // ---- - return event; + if (const auto event = m_window->getNextEvent(); event.has_value()) return event.value(); + if (const auto event = m_joystickManager->getNextEvent(); event.has_value()) return event.value(); + return TimeElapsed{getElapsedTime()}; } break; case EventMode::PLAYBACK: { if (m_eventsPlayback.empty()) { - spdlog::warn("Engine::Window switching to record mode"); + spdlog::info("Engine::Window switching to record mode"); m_eventMode = EventMode::RECORD; return TimeElapsed{getElapsedTime()}; } - auto event = m_eventsPlayback.front(); + const auto event = m_eventsPlayback.front(); m_eventsPlayback.erase(m_eventsPlayback.begin()); std::visit( @@ -144,7 +136,12 @@ auto engine::Core::getNextEvent() -> Event [&](const Moved &m) { m_window->setCursorPosition({m.source.x, m.source.y}); }, - [&](const auto &e) { m_window->applyEvent(e); }, + [&](const Pressed &mouse) { m_window->applyEvent(mouse); }, + [&](const Released &mouse) { m_window->applyEvent(mouse); }, + // [&](const Pressed &key) { m_window->applyEvent(key); }, + // [&](const Released &key) { m_window->applyEvent(key); }, + // [&](const Character &character) { m_window->applyEvent(character); }, + [&](const auto &) {}, }, event); return event; @@ -160,7 +157,7 @@ auto engine::Core::main(int argc, char **argv) -> int #ifdef LOGLOGLOG // todo : setup properly logging auto logger = spdlog::basic_logger_mt("basic_logger", "logs/basic-log.txt"); - logger->info("logger created"); + logger->trace("logger created"); #endif Options opt{argc, argv}; @@ -171,7 +168,6 @@ auto engine::Core::main(int argc, char **argv) -> int opt.write_to_file(opt.settings.config_path + ".last"); - #ifndef NDEBUG if (!opt.options[Options::REPLAY_PATH]->empty()) { setPendingEventsFromFile(opt.settings.replay_path); @@ -210,10 +206,11 @@ auto engine::Core::main(int argc, char **argv) -> int const auto event = getNextEvent(); std::visit( - overloaded{[](TimeElapsed &prev, const TimeElapsed &next) { prev.elapsed += next.elapsed; }, - [&]([[maybe_unused]] const auto &, [[maybe_unused]] const std::monostate &) {}, - // event in playback mode will be saved twice = bad ! - [&]([[maybe_unused]] const auto &, const auto &next) { eventsProcessed.push_back(next); }}, + overloaded{ + [](TimeElapsed &prev, const TimeElapsed &next) { prev.elapsed += next.elapsed; }, + [&]([[maybe_unused]] const auto &, [[maybe_unused]] const std::monostate &) {}, + // event in playback mode will be saved twice = bad ! + [&]([[maybe_unused]] const auto &, const auto &next) { eventsProcessed.push_back(next); }}, eventsProcessed.back(), event); @@ -227,45 +224,53 @@ auto engine::Core::main(int argc, char **argv) -> int }; std::visit( - overloaded{[&]([[maybe_unused]] const OpenWindow &) { m_lastTick = std::chrono::steady_clock::now(); }, - [&]([[maybe_unused]] const CloseWindow &) { - m_window->close(); - this->close(); - }, - [&](const ResizeWindow &e) { - m_window->setSize({e.width, e.height}); - }, - [&]([[maybe_unused]] const TimeElapsed &) { timeElapsed = true; }, - [&]([[maybe_unused]] const Pressed &) { - // todo : abstract glfw keyboard - switch (const auto keyEvent = std::get>(event); keyEvent.source.key) { - case GLFW_KEY_ESCAPE: this->close(); break; + overloaded{ + [&]([[maybe_unused]] const OpenWindow &) { m_lastTick = std::chrono::steady_clock::now(); }, + [&]([[maybe_unused]] const CloseWindow &) { + m_window->close(); + this->close(); + }, + [&](const ResizeWindow &e) { + m_window->setSize({e.width, e.height}); + }, + [&]([[maybe_unused]] const TimeElapsed &) { timeElapsed = true; }, + [&](const Character &character) { m_window->applyEvent(character); }, + [&](const Released &key) { m_window->applyEvent(key); }, + [&](const Pressed &key) { + // todo : abstract glfw keyboard + switch (key.source.key) { #ifndef NDEBUG - case GLFW_KEY_F1: m_show_debug_info = !m_show_debug_info; break; + case GLFW_KEY_F1: + m_show_debug_info = !m_show_debug_info; + window()->setCursorVisible(m_show_debug_info); + break; + case GLFW_KEY_F2: + m_eventMode = m_eventMode == EventMode::PAUSED ? EventMode::RECORD : EventMode::PAUSED; + m_window->setCursorVisible(m_eventMode == EventMode::PAUSED); + break; + #endif - case GLFW_KEY_F11: m_window->setFullscreen(!m_window->isFullscreen()); break; - case GLFW_KEY_F12: { - std::filesystem::create_directories(m_settings.output_folder + "screenshot/"); - const auto file = fmt::format( - m_settings.output_folder + "screenshot/{}.png", time_to_string(std::time(nullptr))); - if (!m_window->screenshot(file)) { - spdlog::warn("failed to take a screenshot: {}", file); - } - } break; - default: break; - } - }, - [&](const Connected &j) { m_joystickManager->add(j.source); }, - [&](const Disconnected &j) { m_joystickManager->remove(j.source); }, - [&](const Moved &j) { m_joystickManager->update(j); }, - [&](const Pressed &j) { m_joystickManager->update(j); }, - [&](const Released &j) { m_joystickManager->update(j); }, - []([[maybe_unused]] const auto &) {}}, + case GLFW_KEY_F11: m_window->setFullscreen(!m_window->isFullscreen()); break; + case GLFW_KEY_F12: { + std::filesystem::create_directories(m_settings.output_folder + "screenshot/"); + const auto file = fmt::format( + m_settings.output_folder + "screenshot/{}.png", time_to_string(std::time(nullptr))); + if (!m_window->screenshot(file)) { spdlog::warn("failed to take a screenshot: {}", file); } + } break; + default: m_window->applyEvent(key); break; + } + }, + [&](const Connected &j) { m_joystickManager->add(j.source); }, + [&](const Disconnected &j) { m_joystickManager->remove(j.source); }, + [&](const Moved &j) { m_joystickManager->update(j); }, + [&](const Pressed &j) { m_joystickManager->update(j); }, + [&](const Released &j) { m_joystickManager->update(j); }, + []([[maybe_unused]] const auto &) {}}, event); if (timeElapsed) { this->tickOnce(std::get(event)); } - m_game->onUpdate(m_world, event); + if (!timeElapsed || (timeElapsed && m_eventMode != EventMode::PAUSED)) m_game->onUpdate(m_world, event); } m_world.view().each(engine::Drawable::dtor); @@ -279,7 +284,6 @@ auto engine::Core::main(int argc, char **argv) -> int f << serialized; #endif - // otherwise the singleton will be destroy after the main -> dead signal get().reset(nullptr); return 0; @@ -289,118 +293,136 @@ auto engine::Core::tickOnce(const TimeElapsed &t) -> void { const auto elapsed = std::chrono::duration_cast(t.elapsed).count(); - // should have only one entity cooldown - m_world.view, Cooldown>().each([this, elapsed](auto &, auto &cd) { - if (!cd.is_in_cooldown) return; + if (m_eventMode != EventMode::PAUSED) { + for (const auto &i : m_world.view()) { + auto &lifetime = m_world.get(i); - if (std::chrono::milliseconds{elapsed} < cd.remaining_cooldown) { - cd.remaining_cooldown -= std::chrono::milliseconds{elapsed}; - } else { - cd.remaining_cooldown = std::chrono::milliseconds(0); - cd.is_in_cooldown = false; - setScreenshake(false); - } - }); - - // check if the spritesheet need to update the texture - m_world.view().each([&elapsed](engine::Spritesheet &sprite) { - if (!sprite.speed.is_in_cooldown) return; - - if (std::chrono::milliseconds{elapsed} < sprite.speed.remaining_cooldown) { - sprite.speed.remaining_cooldown -= std::chrono::milliseconds{elapsed}; - } else { - sprite.speed.remaining_cooldown = std::chrono::milliseconds(0); - sprite.speed.is_in_cooldown = false; + if (t.elapsed < lifetime.remaining_lifetime) { + lifetime.remaining_lifetime -= std::chrono::duration_cast(t.elapsed); + } else { + m_world.destroy(i); + } } - }); + // should have only one entity cooldown + m_world.view, Cooldown>().each([this, elapsed](auto &, auto &cd) { + if (!cd.is_in_cooldown) return; - // update and reset the cooldown of the spritesheet - for (auto &i : m_world.view()) { - auto &sprite = m_world.get(i); - if (sprite.speed.is_in_cooldown) continue; - sprite.speed.is_in_cooldown = true; - sprite.speed.remaining_cooldown = sprite.speed.cooldown; - sprite.current_frame++; - sprite.current_frame %= static_cast(sprite.animations.at(sprite.current_animation).size()); - - auto &texture = m_world.get(i); - - DrawableFactory::fix_texture( - m_world, - i, - m_settings.data_folder + sprite.file, - {static_cast(sprite.animations[sprite.current_animation][sprite.current_frame].x) - / static_cast(texture.width), - static_cast(sprite.animations[sprite.current_animation][sprite.current_frame].y) - / static_cast(texture.height), - sprite.width / static_cast(texture.width), - sprite.height / static_cast(texture.height)}); - } - - m_world.view().each([](auto &vel, auto &acc) { - vel.x += acc.x; - vel.y += acc.y; - - // todo : add max velocity - }); - - // todo : exclude the d2::Hitbox on this system - // m_world.view().each( - // [&elapsed](auto &pos, auto &vel) { - // pos.x += vel.x * static_cast(elapsed) / 1000.0; - // pos.y += vel.y * static_cast(elapsed) / 1000.0; - // }); + if (std::chrono::milliseconds{elapsed} < cd.remaining_cooldown) { + cd.remaining_cooldown -= std::chrono::milliseconds{elapsed}; + } else { + cd.remaining_cooldown = std::chrono::milliseconds(0); + cd.is_in_cooldown = false; + setScreenshake(false); + } + }); - m_world.view(entt::exclude).each([&elapsed](auto &pos, auto &vel) { - pos.x += vel.x * static_cast(elapsed) / 1000.0; - pos.y += vel.y * static_cast(elapsed) / 1000.0; - }); + // check if the spritesheet need to update the texture + m_world.view().each([&elapsed](engine::Spritesheet &sprite) { + if (!sprite.cooldown.is_in_cooldown) return; - for (auto &moving : m_world.view()) { - auto &moving_pos = m_world.get(moving); - auto &moving_vel = m_world.get(moving); - auto &moving_hitbox = m_world.get(moving); - d2::Velocity actual_tick_velocity = moving_vel; + if (std::chrono::milliseconds{elapsed} < sprite.cooldown.remaining_cooldown) { + sprite.cooldown.remaining_cooldown -= std::chrono::milliseconds{elapsed}; + } else { + sprite.cooldown.remaining_cooldown = std::chrono::milliseconds(0); + sprite.cooldown.is_in_cooldown = false; + } + }); - const auto pred_pos = - d3::Position{moving_pos.x + moving_vel.x * static_cast(elapsed) / 1000.0, - moving_pos.y + moving_vel.y * static_cast(elapsed) / 1000.0, - moving_pos.z}; + // update and reset the cooldown of the spritesheet + for (auto &i : m_world.view()) { + auto &sprite = m_world.get(i); + if (sprite.cooldown.is_in_cooldown) continue; + sprite.cooldown.is_in_cooldown = true; + sprite.cooldown.remaining_cooldown = sprite.cooldown.cooldown; + sprite.current_frame++; + sprite.current_frame %= + static_cast(sprite.animations.at(sprite.current_animation).frames.size()); + + auto &vbo_texture = m_world.get(i); + auto &texture = *getCache().handle(vbo_texture.id); + + DrawableFactory::fix_texture( + m_world, + i, + m_settings.data_folder + sprite.animations.at(sprite.current_animation).file, + false, + {static_cast(sprite.animations.at(sprite.current_animation).frames.at(sprite.current_frame).x) + / static_cast(texture.width), + static_cast(sprite.animations.at(sprite.current_animation).frames.at(sprite.current_frame).y) + / static_cast(texture.height), + sprite.animations.at(sprite.current_animation).width / static_cast(texture.width), + sprite.animations.at(sprite.current_animation).height / static_cast(texture.height)}); + } - d3::Position other_pos; - d2::HitboxSolid other_hitbox; + m_world.view().each([](auto &vel, auto &acc) { + vel.x += acc.x; + vel.y += acc.y; - for (auto &others : m_world.view()) { - if (moving == others) continue; + // todo : add max velocity + }); - other_pos = m_world.get(others); - other_hitbox = m_world.get(others); + // todo : exclude the d2::Hitbox on this system + // m_world.view().each( + // [&elapsed](auto &pos, auto &vel) { + // pos.x += vel.x * static_cast(elapsed) / 1000.0; + // pos.y += vel.y * static_cast(elapsed) / 1000.0; + // }); - if (d2::overlapped(moving_hitbox, pred_pos, other_hitbox, other_pos)) { - auto xDiff = other_pos.x - pred_pos.x; - auto xLastDiff = other_pos.x - moving_pos.x; - auto xMinSpace = (moving_hitbox.width + other_hitbox.width) / 2; - if (std::abs(xDiff) < xMinSpace && std::abs(xLastDiff) >= xMinSpace) actual_tick_velocity.x = 0; + m_world.view(entt::exclude).each([&elapsed](auto &pos, auto &vel) { + pos.x += vel.x * static_cast(elapsed) / 1000.0; + pos.y += vel.y * static_cast(elapsed) / 1000.0; + }); - auto yDiff = other_pos.y - pred_pos.y; - auto yLastDiff = other_pos.y - moving_pos.y; - auto yMinSpace = (moving_hitbox.height + other_hitbox.height) / 2; - if (std::abs(yDiff) < yMinSpace && std::abs(yLastDiff) >= yMinSpace) actual_tick_velocity.y = 0; + for (auto &moving : m_world.view()) { + auto &moving_pos = m_world.get(moving); + auto &moving_vel = m_world.get(moving); + auto &moving_hitbox = m_world.get(moving); + d2::Velocity actual_tick_velocity = moving_vel; + + const auto pred_pos = d3::Position{ + moving_pos.x + moving_vel.x * static_cast(elapsed) / 1000.0, + moving_pos.y + moving_vel.y * static_cast(elapsed) / 1000.0, + moving_pos.z}; + + d3::Position other_pos; + d2::HitboxSolid other_hitbox; + + for (auto &others : m_world.view()) { + if (moving == others) continue; + + other_pos = m_world.get(others); + other_hitbox = m_world.get(others); + + if (d2::overlapped(moving_hitbox, pred_pos, other_hitbox, other_pos)) { + auto xDiff = other_pos.x - pred_pos.x; + auto xLastDiff = other_pos.x - moving_pos.x; + auto xMinSpace = (moving_hitbox.width + other_hitbox.width) / 2; + if (std::abs(xDiff) < xMinSpace && std::abs(xLastDiff) >= xMinSpace) actual_tick_velocity.x = 0; + + auto yDiff = other_pos.y - pred_pos.y; + auto yLastDiff = other_pos.y - moving_pos.y; + auto yMinSpace = (moving_hitbox.height + other_hitbox.height) / 2; + if (std::abs(yDiff) < yMinSpace && std::abs(yLastDiff) >= yMinSpace) actual_tick_velocity.y = 0; + } } - } - moving_pos.x += actual_tick_velocity.x * static_cast(elapsed) * 0.001; - moving_pos.y += actual_tick_velocity.y * static_cast(elapsed) * 0.001; + moving_pos.x += actual_tick_velocity.x * static_cast(elapsed) * 0.001; + moving_pos.y += actual_tick_velocity.y * static_cast(elapsed) * 0.001; + } } -#ifdef MODE_EPILEPTIC // change the color at every frame - for (const auto &i : m_world.view()) { - auto &color = m_world.get(i); - const auto r = std::clamp(Color::r(color) - 0.01f, 0.0f, 1.0f); - const auto g = std::clamp(Color::g(color) - 0.01f, 0.0f, 1.0f); - const auto b = std::clamp(Color::b(color) - 0.01f, 0.0f, 1.0f); - DrawableFactory::fix_color(m_world, i, {r ? r : 1.0f, g ? g : 1.0f, b ? b : 1.0f}); +#ifndef NDEBUG + for (auto &i : m_world.view, Source, d3::Position>()) { + auto &source = m_world.get(i); + if (!m_world.valid(source.source)) + m_world.destroy(i); + else { + const auto &pos_source = m_world.get(source.source); + auto &pos = m_world.get(i); + pos.x = pos_source.x; + pos.y = pos_source.y; + } } #endif @@ -409,6 +431,7 @@ auto engine::Core::tickOnce(const TimeElapsed &t) -> void #ifndef NDEBUG if (isShowingDebugInfo()) { + ImGui::ShowDemoWindow(); debugDrawJoystick(); debugDrawDisplayOptions(); } @@ -418,8 +441,8 @@ auto engine::Core::tickOnce(const TimeElapsed &t) -> void const auto background = m_game->getBackgroundColor(); - ::glClearColor(background.r, background.g, background.b, 1.00f); - ::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + CALL_OPEN_GL(::glClearColor(background.r, background.g, background.b, background.a)); + CALL_OPEN_GL(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); // todo : add rendering // texture and no color @@ -430,30 +453,36 @@ auto engine::Core::tickOnce(const TimeElapsed &t) -> void m_shader_colored->use(); m_shader_colored->setUniform("time", static_cast(tmp)); - m_world.view(entt::exclude) - .each([this](auto &drawable, [[maybe_unused]] auto &color, auto &pos, auto &scale, auto &rot) { + m_world.view(entt::exclude) + .each([this](auto entity, auto &drawable, [[maybe_unused]] auto &color, auto &pos, auto &scale) { + auto *rotationComponent = m_world.try_get(entity); + auto rotation = rotationComponent ? static_cast(rotationComponent->angle) : 0.f; + auto model = glm::mat4(1.0f); model = glm::translate(model, glm::vec3{pos.x, pos.y, pos.z}); - model = glm::rotate(model, static_cast(rot.angle), glm::vec3(0.f, 0.f, 1.f)); + model = glm::rotate(model, rotation, glm::vec3(0.f, 0.f, 1.f)); model = glm::scale(model, glm::vec3{scale.x, scale.y, 1.0f}); m_shader_colored->setUniform("model", model); - ::glBindVertexArray(drawable.VAO); - ::glDrawElements(m_displayMode, 3 * drawable.triangle_count, GL_UNSIGNED_INT, 0); + CALL_OPEN_GL(::glBindVertexArray(drawable.VAO)); + CALL_OPEN_GL(::glDrawElements(m_displayMode, 3 * drawable.triangle_count, GL_UNSIGNED_INT, 0)); }); m_shader_colored_textured->use(); m_shader_colored_textured->setUniform("time", static_cast(tmp)); - m_world.view().each( - [this](auto &drawable, [[maybe_unused]] auto &color, auto &texture, auto &pos, auto &scale, auto &rot) { + m_world.view().each( + [this](auto entity, auto &drawable, [[maybe_unused]] auto &color, auto &texture, auto &pos, auto &scale) { + auto *rotationComponent = m_world.try_get(entity); + auto rotation = rotationComponent ? static_cast(rotationComponent->angle) : 0.f; + auto model = glm::mat4(1.0f); model = glm::translate(model, glm::vec3{pos.x, pos.y, pos.z}); - model = glm::rotate(model, static_cast(rot.angle), glm::vec3(0.f, 0.f, 1.f)); + model = glm::rotate(model, rotation, glm::vec3(0.f, 0.f, 1.f)); model = glm::scale(model, glm::vec3{scale.x, scale.y, 1.0f}); m_shader_colored_textured->setUniform("model", model); m_shader_colored_textured->setUniform("mirrored", texture.mirrored); - ::glBindTexture(GL_TEXTURE_2D, texture.texture); - ::glBindVertexArray(drawable.VAO); - ::glDrawElements(m_displayMode, 3 * drawable.triangle_count, GL_UNSIGNED_INT, 0); + CALL_OPEN_GL(::glBindTexture(GL_TEXTURE_2D, getCache().handle(texture.id)->id)); + CALL_OPEN_GL(::glBindVertexArray(drawable.VAO)); + CALL_OPEN_GL(::glDrawElements(m_displayMode, 3 * drawable.triangle_count, GL_UNSIGNED_INT, 0)); }); }); } @@ -494,6 +523,12 @@ auto engine::Core::getCache() noexcept -> entt::resource_cache & return m_colors; } +template<> +auto engine::Core::getCache() noexcept -> entt::resource_cache & +{ + return m_vbo_textures; +} + template<> auto engine::Core::getCache() noexcept -> entt::resource_cache & { @@ -510,9 +545,14 @@ auto engine::Core::loadOpenGL() -> void auto engine::Core::getElapsedTime() noexcept -> std::chrono::nanoseconds { + static constexpr auto kMax = std::chrono::milliseconds(50); + const auto nextTick = std::chrono::steady_clock::now(); - const auto timeElapsed = nextTick - m_lastTick; - m_lastTick = nextTick; + auto timeElapsed = nextTick - m_lastTick; + + if (timeElapsed > kMax) timeElapsed = std::chrono::nanoseconds(kMax); + + m_lastTick += timeElapsed; return timeElapsed; } diff --git a/src/Engine/src/Engine/Graphics/Shader.cpp b/src/Engine/src/Engine/Graphics/Shader.cpp index badf147..667ca68 100644 --- a/src/Engine/src/Engine/Graphics/Shader.cpp +++ b/src/Engine/src/Engine/Graphics/Shader.cpp @@ -15,60 +15,61 @@ template struct shader_ { explicit shader_(const char *source) : ID{::glCreateShader(type)} { - ::glShaderSource(ID, 1, &source, nullptr); - ::glCompileShader(ID); + CALL_OPEN_GL(::glShaderSource(ID, 1, &source, nullptr)); + CALL_OPEN_GL(::glCompileShader(ID)); GLint success = 0; - ::glGetShaderiv(ID, GL_COMPILE_STATUS, &success); + CALL_OPEN_GL(::glGetShaderiv(ID, GL_COMPILE_STATUS, &success)); if (success == GL_FALSE) { GLint maxLength = 0; - ::glGetShaderiv(ID, GL_INFO_LOG_LENGTH, &maxLength); + CALL_OPEN_GL(::glGetShaderiv(ID, GL_INFO_LOG_LENGTH, &maxLength)); std::vector errorLog(static_cast(maxLength)); - ::glGetShaderInfoLog(ID, maxLength, &maxLength, errorLog.data()); + CALL_OPEN_GL(::glGetShaderInfoLog(ID, maxLength, &maxLength, errorLog.data())); spdlog::error("(Failed to compile shader, \nError : {}\n\n{}\n)", errorLog.data(), source); } } - ~shader_() { ::glDeleteShader(ID); } + ~shader_() { CALL_OPEN_GL(::glDeleteShader(ID)); } std::uint32_t ID; }; -engine::Shader::Shader(const std::string_view vCode, const std::string_view fCode) : ID{::glCreateProgram()} +engine::Shader::Shader(const std::string_view vCode, const std::string_view fCode) : + ID{::glCreateProgram()} { shader_ vertex{vCode.data()}; shader_ fragment{fCode.data()}; - ::glAttachShader(ID, vertex.ID); - ::glAttachShader(ID, fragment.ID); - ::glLinkProgram(ID); + CALL_OPEN_GL(::glAttachShader(ID, vertex.ID)); + CALL_OPEN_GL(::glAttachShader(ID, fragment.ID)); + CALL_OPEN_GL(::glLinkProgram(ID)); GLint success; - ::glGetProgramiv(ID, GL_LINK_STATUS, &success); + CALL_OPEN_GL(::glGetProgramiv(ID, GL_LINK_STATUS, &success)); if (const auto err = glGetError(); err != GL_NO_ERROR) { - spdlog::error("Error {} : {}", err, glewGetErrorString(err)); + spdlog::error("Error {} : {}", err, ::glewGetErrorString(err)); return; } if (success == GL_FALSE) { GLint maxLength = 0; - ::glGetProgramiv(ID, GL_INFO_LOG_LENGTH, &maxLength); + CALL_OPEN_GL(::glGetProgramiv(ID, GL_INFO_LOG_LENGTH, &maxLength)); std::vector errorLog(static_cast(maxLength)); - ::glGetProgramInfoLog(ID, maxLength, &maxLength, errorLog.data()); + CALL_OPEN_GL(::glGetProgramInfoLog(ID, maxLength, &maxLength, errorLog.data())); spdlog::error("(Failed to link shader program {}, \nError : {}\n", ID, errorLog.data()); } else { - spdlog::info("Successfully created shader program {}", ID); + spdlog::trace("Successfully created shader program {}", ID); } } engine::Shader::~Shader() { - ::glDeleteProgram(ID); - spdlog::info("Destroyed shader program {}", ID); + CALL_OPEN_GL(::glDeleteProgram(ID)); + spdlog::trace("Destroyed shader program {}", ID); } auto engine::Shader::fromFile(const std::string_view vFile, const std::string_view fFile) -> Shader @@ -78,25 +79,25 @@ auto engine::Shader::fromFile(const std::string_view vFile, const std::string_vi getFileContent(fFile).value_or(fmt::format("Cannot open file: {}", fFile))}; } -auto engine::Shader::use() -> void { ::glUseProgram(ID); } +auto engine::Shader::use() -> void { CALL_OPEN_GL(::glUseProgram(ID)); } template<> auto engine::Shader::setUniform(const std::string_view name, bool v) -> void { if (const auto location = ::glGetUniformLocation(ID, name.data()); location != -1) - ::glUniform1ui(location, v); + CALL_OPEN_GL(::glUniform1ui(location, v)); } template<> auto engine::Shader::setUniform(const std::string_view name, float v) -> void { if (const auto location = ::glGetUniformLocation(ID, name.data()); location != -1) - ::glUniform1f(location, v); + CALL_OPEN_GL(::glUniform1f(location, v)); } template<> auto engine::Shader::setUniform(const std::string_view name, glm::mat4 mat) -> void { if (const auto location = ::glGetUniformLocation(ID, name.data()); location != -1) - ::glUniformMatrix4fv(location, 1, GL_FALSE, glm::value_ptr(mat)); + CALL_OPEN_GL(::glUniformMatrix4fv(location, 1, GL_FALSE, glm::value_ptr(mat))); } diff --git a/src/Engine/src/Engine/Graphics/Window.cpp b/src/Engine/src/Engine/Graphics/Window.cpp index d4eaddd..dc24d1e 100644 --- a/src/Engine/src/Engine/Graphics/Window.cpp +++ b/src/Engine/src/Engine/Graphics/Window.cpp @@ -10,7 +10,7 @@ #include "Engine/audio/AudioManager.hpp" // note : should not require this header here #include "Engine/Settings.hpp" // note : should not require this header here #include "Engine/component/Color.hpp" -#include "Engine/component/Texture.hpp" +#include "Engine/component/VBOTexture.hpp" #include "Engine/Core.hpp" engine::Window *engine::Window::s_instance{nullptr}; @@ -21,7 +21,7 @@ engine::Window::Window(glm::ivec2 &&size, const std::string_view title, std::uin m_handle{::glfwCreateWindow(size.x, size.y, title.data(), nullptr, nullptr)}, m_ui_context{ImGui::CreateContext()} { if (m_handle == nullptr) { throw std::logic_error("Engine::Window initialization failed"); } - spdlog::info("Engine::Window instanciated"); + spdlog::trace("Engine::Window instanciated"); ::glfwGetWindowSize(m_handle, &m_size.x, &m_size.y); ::glfwGetWindowPos(m_handle, &m_pos.x, &m_pos.y); @@ -39,10 +39,10 @@ engine::Window::Window(glm::ivec2 &&size, const std::string_view title, std::uin // Vsync ::glfwSwapInterval(1); - ::glEnable(GL_DEPTH_TEST); + CALL_OPEN_GL(::glEnable(GL_DEPTH_TEST)); - ::glEnable(GL_BLEND); - ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + CALL_OPEN_GL(::glEnable(GL_BLEND)); + CALL_OPEN_GL(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); s_instance = this; @@ -52,18 +52,18 @@ engine::Window::Window(glm::ivec2 &&size, const std::string_view title, std::uin ::glfwSetKeyCallback(m_handle, callback_eventKeyBoard); ::glfwSetMouseButtonCallback(m_handle, callback_eventMousePressed); ::glfwSetCursorPosCallback(m_handle, callback_eventMouseMoved); - ::glfwSetCharCallback(m_handle, ImGui_ImplGlfw_CharCallback); + ::glfwSetCharCallback(m_handle, callback_char); - // todo : mouse wheel / request_focus ... + // todo : scroll / request_focus ... m_events.emplace_back(OpenWindow{}); if (property & FULLSCREEN) { - spdlog::info("Engine::Window Fullscreen"); + spdlog::trace("Engine::Window Fullscreen"); setFullscreen(true); } - ::glViewport(0, 0, m_size.x, m_size.y); + CALL_OPEN_GL(::glViewport(0, 0, m_size.x, m_size.y)); } engine::Window::~Window() @@ -75,7 +75,7 @@ engine::Window::~Window() if (m_handle != nullptr) { ::glfwDestroyWindow(m_handle); } - spdlog::info("Engine::Window destroyed"); + spdlog::trace("Engine::Window destroyed"); } auto engine::Window::draw(const std::function &drawer) -> void @@ -121,7 +121,7 @@ bool engine::Window::screenshot(const std::string_view filename) { GLint viewport[4]; - ::glGetIntegerv(GL_VIEWPORT, viewport); + CALL_OPEN_GL(::glGetIntegerv(GL_VIEWPORT, viewport)); const auto &x = viewport[0]; const auto &y = viewport[1]; const auto &width = viewport[2]; @@ -130,8 +130,8 @@ bool engine::Window::screenshot(const std::string_view filename) constexpr auto CHANNEL = 4ul; std::vector pixels(static_cast(width * height) * CHANNEL); - ::glPixelStorei(GL_PACK_ALIGNMENT, 1); - ::glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); + CALL_OPEN_GL(::glPixelStorei(GL_PACK_ALIGNMENT, 1)); + CALL_OPEN_GL(::glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data())); std::array pixel; for (auto j = 0; j < height / 2; ++j) @@ -139,14 +139,19 @@ bool engine::Window::screenshot(const std::string_view filename) const auto top = static_cast(i + j * width) * pixel.size(); const auto bottom = static_cast(i + (height - j - 1) * width) * pixel.size(); - std::memcpy(pixel.data(), pixels.data() + top, pixel.size()); - std::memcpy(pixels.data() + top, pixels.data() + bottom, pixel.size()); - std::memcpy(pixels.data() + bottom, pixel.data(), pixel.size()); + std::memcpy(pixel.data(), pixels.data() + top, pixel.size()); + std::memcpy(pixels.data() + top, pixels.data() + bottom, pixel.size()); + std::memcpy(pixels.data() + bottom, pixel.data(), pixel.size()); } return !!::stbi_write_png(filename.data(), width, height, 4, pixels.data(), 0); } +void engine::Window::setCursorVisible(bool visible) noexcept +{ + ::glfwSetInputMode(m_handle, GLFW_CURSOR, visible ? GLFW_CURSOR_NORMAL : GLFW_CURSOR_DISABLED); +} + auto engine::Window::isOpen() const -> bool { return ::glfwWindowShouldClose(m_handle) == GLFW_FALSE; } auto engine::Window::close() -> void { ::glfwSetWindowShouldClose(m_handle, GLFW_TRUE); } @@ -155,9 +160,11 @@ auto engine::Window::render() -> void { ::glfwSwapBuffers(m_handle); } auto engine::Window::setActive() -> void { ::glfwMakeContextCurrent(m_handle); } -auto engine::Window::setSize(glm::ivec2 &&size) -> void { +auto engine::Window::setSize(glm::ivec2 &&size) -> void +{ ::glfwSetWindowSize(m_handle, size.x, size.y); - ::glViewport(0, 0, size.x, size.y); + CALL_OPEN_GL(::glViewport(0, 0, size.x, size.y)); + m_size = size; } auto engine::Window::setPosition(glm::ivec2 &&pos) -> void { ::glfwSetWindowPos(m_handle, pos.x, pos.y); } @@ -167,23 +174,23 @@ auto engine::Window::setCursorPosition(glm::dvec2 &&pos) -> void { ::glfwSetCurs auto engine::Window::callback_eventClose([[maybe_unused]] GLFWwindow *window) -> void { - IF_RECORD(s_instance->m_events.emplace_back(CloseWindow{})); + IF_NOT_PLAYBACK(s_instance->m_events.emplace_back(CloseWindow{})); } auto engine::Window::callback_eventResized([[maybe_unused]] GLFWwindow *window, int w, int h) -> void { - IF_RECORD(s_instance->m_events.emplace_back(ResizeWindow{w, h})); + IF_NOT_PLAYBACK(s_instance->m_events.emplace_back(ResizeWindow{w, h})); } auto engine::Window::callback_eventMoved([[maybe_unused]] GLFWwindow *window, int x, int y) -> void { - IF_RECORD(s_instance->m_events.emplace_back(MoveWindow{x, y})); + IF_NOT_PLAYBACK(s_instance->m_events.emplace_back(MoveWindow{x, y})); } auto engine::Window::callback_eventKeyBoard([[maybe_unused]] GLFWwindow *window, int key, int scancode, int action, int mods) -> void { - IF_RECORD( + IF_NOT_PLAYBACK( // clang-format off Key k{ .alt = !!(mods & GLFW_MOD_ALT), // NOLINT @@ -206,7 +213,7 @@ auto engine::Window::callback_eventKeyBoard([[maybe_unused]] GLFWwindow *window, auto engine::Window::callback_eventMousePressed(GLFWwindow *window, int button, int action, [[maybe_unused]] int mods) -> void { - IF_RECORD( + IF_NOT_PLAYBACK( // NOLINTNEXTLINE double x = 0; double y = 0; // NOLINTNEXTLINE @@ -222,7 +229,12 @@ auto engine::Window::callback_eventMousePressed(GLFWwindow *window, int button, auto engine::Window::callback_eventMouseMoved([[maybe_unused]] GLFWwindow *window, double x, double y) -> void { - IF_RECORD(s_instance->m_events.emplace_back(Moved{x, y})); + IF_NOT_PLAYBACK(s_instance->m_events.emplace_back(Moved{x, y})); +} + +auto engine::Window::callback_char([[maybe_unused]] GLFWwindow *window, unsigned int codepoint) -> void +{ + IF_NOT_PLAYBACK(s_instance->m_events.emplace_back(Character{codepoint})); } using namespace engine; @@ -250,3 +262,9 @@ auto Window::applyEvent(const Released &k) -> void { ImGui_ImplGlfw_KeyCallback(m_handle, k.source.key, k.source.scancode, GLFW_RELEASE, 0 /* todo */); } + +template<> +auto Window::applyEvent(const Character &character) -> void +{ + ImGui_ImplGlfw_CharCallback(m_handle, character.codepoint); +} diff --git a/src/Engine/src/Engine/JoystickManager.cpp b/src/Engine/src/Engine/JoystickManager.cpp index db501c6..e4f615b 100644 --- a/src/Engine/src/Engine/JoystickManager.cpp +++ b/src/Engine/src/Engine/JoystickManager.cpp @@ -6,7 +6,7 @@ #include "Engine/audio/AudioManager.hpp" // note : should not require this header here #include "Engine/Settings.hpp" // note : should not require this header here #include "Engine/component/Color.hpp" -#include "Engine/component/Texture.hpp" +#include "Engine/component/VBOTexture.hpp" #include "Engine/Core.hpp" #include "Engine/helpers/macro.hpp" @@ -111,8 +111,24 @@ auto engine::JoystickManager::pollAxis() -> void const auto axes = ::glfwGetJoystickAxes(joy.id, &count); for (std::uint32_t i = 0; i != static_cast(count); i++) { if (std::abs(joy.axes[i] - axes[i]) >= 0.05f) { - this->m_events.emplace_back( - Moved({joy.id, magic_enum::enum_cast(i).value(), axes[i]})); + const auto axis = magic_enum::enum_cast(i).value(); + const auto found = std::find_if(m_events.begin(), m_events.end(), [&](auto &event) { + auto ret = false; + std::visit( + engine::overloaded{ + [&](const Moved &old) { + ret = old.source.id == joy.id && old.source.axis == axis; + }, + [&](auto) {}, + }, + event); + return ret; + }); + if (found == m_events.end()) { + this->m_events.emplace_back(Moved({joy.id, axis, axes[i]})); + } else { + *found = Moved{{ joy.id, axis, axes[i] }}; + } } } } diff --git a/src/Engine/src/Engine/audio/WavReader.exp.cpp b/src/Engine/src/Engine/audio/WavReader.exp.cpp new file mode 100644 index 0000000..7a540d3 --- /dev/null +++ b/src/Engine/src/Engine/audio/WavReader.exp.cpp @@ -0,0 +1,144 @@ +#include +#include +#include + +#include "Engine/helpers/Warnings.hpp" + +DISABLE_WARNING_PUSH +DISABLE_WARNING_OLD_CAST +DISABLE_WARNING_SIGN_CONVERSION +DISABLE_WARNING_USELESS_CAST +#include +DISABLE_WARNING_POP + +#include "Engine/audio/WavReader.hpp" + +#if 0 +static std::int32_t convert_to_int(char *buffer, std::size_t len) +{ + std::int32_t a = 0; + if constexpr (std::endian::native == std::endian::little) + std::memcpy(&a, buffer, len); + else + for (std::size_t i = 0; i < len; ++i) { reinterpret_cast(&a)[3 - i] = buffer[i]; } + return a; +} + +bool load_wav_file_header( + std::ifstream &file, std::uint8_t &channels, std::int32_t &sampleRate, std::uint8_t &bitsPerSample, ALsizei &size) +{ + char buffer[4]; + if (!file.is_open()) return false; + + // the RIFF + if (!file.read(buffer, 4)) { return false; } + if (std::strncmp(buffer, "RIFF", 4) != 0) { return false; } + + // the size of the file + if (!file.read(buffer, 4)) { return false; } + + // the WAVE + if (!file.read(buffer, 4)) { return false; } + if (std::strncmp(buffer, "WAVE", 4) != 0) { return false; } + + // "fmt/0" + if (!file.read(buffer, 4)) { return false; } + + // this is always 16, the size of the fmt data chunk + if (!file.read(buffer, 4)) { return false; } + + // PCM should be 1? + if (!file.read(buffer, 2)) { return false; } + + // the number of channels + if (!file.read(buffer, 2)) { return false; } + channels = static_cast(convert_to_int(buffer, 2)); + + // sample rate + if (!file.read(buffer, 4)) { return false; } + sampleRate = convert_to_int(buffer, 4); + + // (sampleRate * bitsPerSample * channels) / 8 + if (!file.read(buffer, 4)) { return false; } + + // ?? dafaq + if (!file.read(buffer, 2)) { return false; } + + // bitsPerSample + if (!file.read(buffer, 2)) { return false; } + bitsPerSample = static_cast(convert_to_int(buffer, 2)); + + // data chunk header "data" + if (!file.read(buffer, 4)) { return false; } + if (std::strncmp(buffer, "data", 4) != 0) { return false; } + + // size of data + if (!file.read(buffer, 4)) { return false; } + size = convert_to_int(buffer, 4); + + /* cannot be at the end of file */ + if (file.eof()) { return false; } + if (file.fail()) { return false; } + + return true; +} + +#endif +auto engine::load_wav( + const std::string_view filename, std::uint8_t &channels, std::int32_t &sampleRate, std::uint8_t &bitsPerSample, ALsizei &size) + -> char * +{ + spdlog::error("{}", filename.data()); + std::ifstream in(filename.data(), std::ios::binary); + if (!in.is_open()) { + spdlog::error("ERROR: Could not open '{}'", filename); + return nullptr; + } + + AudioFile audio; + audio.shouldLogErrorsToConsole(true); + if (!audio.load(filename.data())) { + spdlog::error("ERROR: Could not read '{}'", filename); + return nullptr; + } + + audio.printSummary(); + + channels = static_cast(audio.getNumChannels()); + sampleRate = static_cast(audio.getSampleRate()); + bitsPerSample = static_cast(audio.getBitDepth()); + size = static_cast(audio.samples.size() * audio.samples[0].size() * sizeof(float)); + + spdlog::error("channels: {}, sampleRate: {}, bitsPerSample: {}, size: {} ", channels, sampleRate, bitsPerSample, size); + + auto out = new char[size]; + std::size_t index = 0; + + for (auto &channels_in_audio : audio.samples) { + for (auto &sample : channels_in_audio) { + uint8_t bytes[sizeof(float)]; + DISABLE_WARNING_PUSH + DISABLE_WARNING_OLD_CAST + *(float *) (bytes) = sample; + DISABLE_WARNING_POP + out[index + 0] = static_cast(bytes[0]); + out[index + 1] = static_cast(bytes[1]); + out[index + 2] = static_cast(bytes[2]); + out[index + 3] = static_cast(bytes[3]); + //std::memcpy(out + index, bytes, 4); + index += 4; + } + } + + return out; + // if (!load_wav_file_header(in, channels, sampleRate, bitsPerSample, size)) { + // spdlog::error("ERROR: Could not load wav header of '{}'", filename); + // return nullptr; + // } + // + // auto data = new char[static_cast(size)]; + // + // in.read(data, size); + // + // return data; +} diff --git a/src/Engine/src/Engine/helpers/DrawableFactory.cpp b/src/Engine/src/Engine/helpers/DrawableFactory.cpp index 378f423..7c5f291 100644 --- a/src/Engine/src/Engine/helpers/DrawableFactory.cpp +++ b/src/Engine/src/Engine/helpers/DrawableFactory.cpp @@ -4,13 +4,14 @@ #include "Engine/Graphics/third_party.hpp" #include "Engine/component/Drawable.hpp" #include "Engine/component/Color.hpp" -#include "Engine/component/Texture.hpp" +#include "Engine/component/VBOTexture.hpp" #include "Engine/helpers/DrawableFactory.hpp" #include "Engine/Graphics/Shader.hpp" #include "Engine/Event/Event.hpp" // note : should not require this header here #include "Engine/audio/AudioManager.hpp" // note : should not require this header here #include "Engine/Settings.hpp" // note : should not require this header here #include "Engine/Core.hpp" +#include "Engine/component/Spritesheet.hpp" auto engine::DrawableFactory::rectangle() -> Drawable { @@ -30,46 +31,46 @@ auto engine::DrawableFactory::rectangle() -> Drawable Drawable out; out.triangle_count = 2; - ::glGenVertexArrays(1, &out.VAO); - ::glGenBuffers(1, &out.EBO); + CALL_OPEN_GL(::glGenVertexArrays(1, &out.VAO)); + CALL_OPEN_GL(::glGenBuffers(1, &out.EBO)); - ::glBindVertexArray(out.VAO); + CALL_OPEN_GL(::glBindVertexArray(out.VAO)); - ::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, out.EBO); - ::glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); + CALL_OPEN_GL(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, out.EBO)); + CALL_OPEN_GL(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW)); - ::glGenBuffers(1, &out.VBO); + CALL_OPEN_GL(::glGenBuffers(1, &out.VBO)); - ::glBindBuffer(GL_ARRAY_BUFFER, out.VBO); - ::glBufferData(GL_ARRAY_BUFFER, sizeof(vertices_pos), vertices_pos, GL_STATIC_DRAW); + CALL_OPEN_GL(::glBindBuffer(GL_ARRAY_BUFFER, out.VBO)); + CALL_OPEN_GL(::glBufferData(GL_ARRAY_BUFFER, sizeof(vertices_pos), vertices_pos, GL_STATIC_DRAW)); - ::glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), static_cast(0)); - ::glEnableVertexAttribArray(0); + CALL_OPEN_GL(::glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), static_cast(0))); + CALL_OPEN_GL(::glEnableVertexAttribArray(0)); return out; } -auto engine::DrawableFactory::fix_color(entt::registry &world, entt::entity e, glm::vec3 &&color) -> Color & +auto engine::DrawableFactory::fix_color(entt::registry &world, entt::entity e, glm::vec4 &&color) -> Color & { static Core::Holder holder{}; assert(world.has(e)); auto &drawable = world.get(e); - ::glBindVertexArray(drawable.VAO); + CALL_OPEN_GL(::glBindVertexArray(drawable.VAO)); auto handle = holder.instance->getCache().load( - entt::hashed_string{fmt::format("resource/color/identifier/{}_{}_{}", color.r, color.g, color.b).data()}, + entt::hashed_string{fmt::format("resource/color/identifier/{}_{}_{}_{}", color.r, color.g, color.b, color.a).data()}, std::move(color)); if (handle) { - ::glBindBuffer(GL_ARRAY_BUFFER, handle->VBO); - ::glBufferData( + CALL_OPEN_GL(::glBindBuffer(GL_ARRAY_BUFFER, handle->VBO)); + CALL_OPEN_GL(::glBufferData( GL_ARRAY_BUFFER, static_cast(handle->vertices.size() * sizeof(float)), handle->vertices.data(), - GL_STATIC_DRAW); + GL_STATIC_DRAW)); - ::glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), static_cast(0)); - ::glEnableVertexAttribArray(1); + CALL_OPEN_GL(::glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), static_cast(0))); + CALL_OPEN_GL(::glEnableVertexAttribArray(1)); return world.emplace_or_replace(e, *handle); } else { @@ -79,36 +80,65 @@ auto engine::DrawableFactory::fix_color(entt::registry &world, entt::entity e, g } auto engine::DrawableFactory::fix_texture( - entt::registry &world, entt::entity e, const std::string_view filepath, const std::array &clip) -> Texture & + entt::registry &world, + entt::entity e, + const std::string_view filepath, + bool mirrored_repeated, + const std::array &clip) -> VBOTexture & { static Core::Holder holder{}; assert(world.has(e)); auto &drawable = world.get(e); - ::glBindVertexArray(drawable.VAO); - - // note : could split the texture in two component : - // Texture = raw image from file - // ClippedTexture = VBO binded and clipped - auto handle = holder.instance->getCache().load( - entt::hashed_string{ - fmt::format("resource/texture/identifier/{}_{}_{}_{}_{}", filepath, clip[0], clip[1], clip[2], clip[3]).data()}, - filepath, - clip); - if (handle) { - ::glBindBuffer(GL_ARRAY_BUFFER, handle->VBO); - ::glBufferData( + CALL_OPEN_GL(::glBindVertexArray(drawable.VAO)); + + if (const auto handle = holder.instance->getCache().load( + entt::hashed_string{ + fmt::format("resource/texture/identifier/{}_{}_{}_{}_{}_{}", filepath, mirrored_repeated, clip[0], clip[1], clip[2], clip[3]).data()}, + filepath, + mirrored_repeated, + clip); + !handle) { + spdlog::error("could not load texture in cache : {}", filepath); + return *world.try_get(e); + } else { + CALL_OPEN_GL(::glBindBuffer(GL_ARRAY_BUFFER, handle->VBO)); + CALL_OPEN_GL(::glBufferData( GL_ARRAY_BUFFER, static_cast(handle->vertices.size() * sizeof(float)), handle->vertices.data(), - GL_STATIC_DRAW); + GL_STATIC_DRAW)); - ::glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), static_cast(0)); - ::glEnableVertexAttribArray(2); + CALL_OPEN_GL(::glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), static_cast(0))); + CALL_OPEN_GL(::glEnableVertexAttribArray(2)); - return world.emplace_or_replace(e, *handle); - } else { - spdlog::error("could not load texture in cache !"); - throw std::runtime_error("could not load texture in cache !"); + return world.emplace_or_replace(e, *handle); + } +} + +auto engine::DrawableFactory::fix_spritesheet(entt::registry &world, entt::entity entity, const std::string_view animation) + -> void +{ + using namespace std::chrono_literals; + + auto &sp = world.get(entity); + engine::Spritesheet::Animation *anim{nullptr}; + try { + anim = &sp.animations.at(animation.data()); + } catch (...) { + spdlog::error( + "could not find animation '{}' in {}", + animation, + std::accumulate(std::begin(sp.animations), std::end(sp.animations), std::string{}, [](auto old, auto &i) { + return old + "%" + i.first; + })); + return; } + + sp.current_animation = animation; + sp.cooldown.remaining_cooldown = 0ms; + sp.cooldown.is_in_cooldown = false; + sp.cooldown.cooldown = anim->cooldown; + engine::DrawableFactory::fix_texture(world, entity, Core::Holder{}.instance->settings().data_folder + anim->file); + sp.current_frame = 0; } diff --git a/src/Engine/src/Engine/main.cpp b/src/Engine/src/Engine/main.cpp index 00b49b3..bfaf5f7 100644 --- a/src/Engine/src/Engine/main.cpp +++ b/src/Engine/src/Engine/main.cpp @@ -1,29 +1,34 @@ -#include - #include // note : should not require this header here #include // note : should not require this header here #include // note : should not require this header here #include // note : should not require this header here #include // note : should not require this header here #include -#include +#include #include // tmp +#include "models/Spell.hpp" #include "ThePURGE.hpp" -int main(int argc, char **argv) -/*try*/ { + +#include + +int main(int argc, char **argv) try +{ +#ifndef NDEBUG + spdlog::set_level(spdlog::level::trace); +#else + spdlog::set_level(spdlog::level::warn); +#endif + auto holder = engine::Core::Holder::init(); holder.instance->game(); return holder.instance->main(argc, argv); - -}/* catch (const std::exception &e) { - spdlog::error("Caught exception at main level: {}", e.what()); - throw; - -} catch (const int code) { +} +catch (int code) +{ return code; -}*/ +} diff --git a/src/Engine/src/Engine/resources/Texture.cpp b/src/Engine/src/Engine/resources/Texture.cpp new file mode 100644 index 0000000..db4e2f5 --- /dev/null +++ b/src/Engine/src/Engine/resources/Texture.cpp @@ -0,0 +1,48 @@ +#include "Engine/resources/Texture.hpp" + +auto engine::Texture::ctor(const std::string_view filepath, bool mirrored_repeated) -> Texture +{ + Texture texture = { + .id = 0, + .width = 0, + .height = 0, + .channels = 0, + .px = nullptr, + }; + + texture.px = ::stbi_load(filepath.data(), &texture.width, &texture.height, &texture.channels, 4); + if (texture.px == nullptr) { + spdlog::error("Could not open texture '{}'. Texture will appear black", filepath.data()); + } + + CALL_OPEN_GL(::glGenTextures(1, &texture.id)); + CALL_OPEN_GL(::glBindTexture(GL_TEXTURE_2D, texture.id)); + + CALL_OPEN_GL(::glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, texture.width, texture.height)); + CALL_OPEN_GL( + ::glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture.width, texture.height, GL_RGBA, GL_UNSIGNED_BYTE, texture.px)); + CALL_OPEN_GL(::glGenerateMipmap(GL_TEXTURE_2D)); + + if (!mirrored_repeated) { + CALL_OPEN_GL(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); + CALL_OPEN_GL(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); + + CALL_OPEN_GL(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)); + CALL_OPEN_GL(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); + } else { + + CALL_OPEN_GL(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT));// GL_MIRRORED_REPEAT + CALL_OPEN_GL(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT));// GL_MIRRORED_REPEAT + + CALL_OPEN_GL(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)); + CALL_OPEN_GL(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); + } + CALL_OPEN_GL(::glBindTexture(GL_TEXTURE_2D, 0)); + return texture; +} + +auto engine::Texture::dtor(Texture *obj) -> void +{ + ::stbi_image_free(obj->px); + CALL_OPEN_GL(::glDeleteTextures(1, &obj->id)); +} diff --git a/tools/build.sh b/tools/build.sh index a4ce848..4de7e65 100755 --- a/tools/build.sh +++ b/tools/build.sh @@ -58,8 +58,8 @@ if [[ $(uname -a) =~ "Linux" ]]; then build_folder="$build_folder/$arch" fi -cmake --build $build_folder --config $build_type -j $(nproc --ignore=4) +cmake --build $build_folder --config $build_type # -j $(nproc --ignore=4) if [[ "$test" == "true" ]]; then - cd $build_folder && ctest -j $(nproc --ignore=4) + cd $build_folder && ctest # -j $(nproc --ignore=4) fi diff --git a/tools/generate.sh b/tools/generate.sh index 204cda4..cec3ec7 100755 --- a/tools/generate.sh +++ b/tools/generate.sh @@ -53,20 +53,22 @@ done argument="-DCMAKE_BUILD_TYPE=$build_type" case "$(uname)" in -"Linux") - argument="$argument -B build/$build_type/$arch" - ;; -*) # Windows - argument="$argument -B build/$build_type -A $arch" - ;; + "Linux") + argument="$argument -B build/$build_type/$arch" + ;; + *) # Windows + argument="$argument -B build/$build_type -A $arch" + ;; esac -#if [[ $(uname -a) =~ "Ubuntu" ]]; then -# export CC="gcc-10" -# export CXX="g++-10" -#fi +if [[ $(uname -a) =~ "Ubuntu" ]]; then + if [[ ! "$CXX" ]]; then + export CC="gcc-10" + export CXX="g++-10" + fi +fi export PATH="$PATH:$HOME/.local/bin" export CONAN_SYSREQUIRES_MODE=enabled -cmake $argument -j $(nproc --ignore=4) $extra_arg . +cmake $argument $extra_arg . diff --git a/tools/install.sh b/tools/install.sh index 61a01df..ac7681c 100755 --- a/tools/install.sh +++ b/tools/install.sh @@ -4,12 +4,15 @@ # This script will install the system requirements for this project # +clone=True + usage() { cat << EOF Usage: $0 Options: -h|--help Display this message. + --no-clone Does not clone. Assume executed in the repo. EOF exit 2 } @@ -23,25 +26,38 @@ case $key in usage $# shift ;; + --no-clone) + clone=False + shift + ;; *) shift ;; esac done +if [[ "$clone" == "True" ]]; then + git clone git@github.com:Mathieu-Lala/game_project.git + cd game_project +fi + if [[ $(uname -a) =~ "Ubuntu" ]]; then sudo apt update & > /dev/null - sudo apt install -y gcc-10 g++-10 pkg-config python3-pip + sudo apt install -y gcc-10 g++-10 pkg-config python3-pip ssh-askpass fi python3 -m pip install --upgrade pip setuptools --user -if [ -f requirements.txt ]; then pip install -r requirements.txt --user; fi export PATH="$PATH:$HOME/.local/bin" +if [ -f requirements.txt ]; then pip3 install -r requirements.txt --user; fi + # conan profile new game_project --detect -# if [[ $(uname -a) =~ "Ubuntu" ]]; then -# conan profile update settings.compiler.libcxx=libstdc++11 game_project -# conan profile update settings.compiler=gcc game_project -# conan profile update settings.compiler.version=10 game_project -# fi + +if [[ $(uname -a) =~ "Ubuntu" ]]; then + if [ ! "$CI" ]; then + conan profile update settings.compiler.libcxx=libstdc++11 default + conan profile update settings.compiler=gcc default + conan profile update settings.compiler.version=10 default + fi +fi