diff --git a/.github/workflows/ubuntu-touch.yml b/.github/workflows/ubuntu-touch.yml index cc925aca769..01a43d11c1d 100644 --- a/.github/workflows/ubuntu-touch.yml +++ b/.github/workflows/ubuntu-touch.yml @@ -67,7 +67,7 @@ jobs: ARCH: ${{ matrix.arch }} run: | ~/.local/bin/clickable build --verbose ${BUILD_TYPE} --arch ${ARCH} \ - --config mk/clickable/build-with-glew.json + --config mk/clickable/clickable-glew.yaml - name: Build (OpenGL with GLbinding) if: ${{ matrix.opengl == 'glbinding' }} @@ -76,7 +76,7 @@ jobs: ARCH: ${{ matrix.arch }} run: | ~/.local/bin/clickable build --verbose ${BUILD_TYPE} --arch ${ARCH} \ - --config mk/clickable/build-with-glbinding.json + --config mk/clickable/clickable-glbinding.yaml - name: Build (no OpenGL) if: ${{ matrix.opengl == 'sdl' }} @@ -92,12 +92,12 @@ jobs: path: build.clickable/*.click if-no-files-found: ignore - - name: Publish to Open Store - if: ${{ github.ref == 'refs/heads/master' && matrix.build_type == 'Release' && matrix.opengl == 'sdl' }} - env: - ARCH: ${{ matrix.arch }} - OPENSTORE_KEY: ${{ secrets.OPENSTORE_KEY }} - run: | - ~/.local/bin/clickable publish "* $(git log -1 --pretty=%B | \ - head -1)" --apikey ${OPENSTORE_KEY} \ - --arch ${ARCH} + #- name: Publish to Open Store + # if: ${{ github.ref == 'refs/heads/master' && matrix.build_type == 'Release' && matrix.opengl == 'sdl' }} + # env: + # ARCH: ${{ matrix.arch }} + # OPENSTORE_KEY: ${{ secrets.OPENSTORE_KEY }} + # run: | + # ~/.local/bin/clickable publish "* $(git log -1 --pretty=%B | \ + # head -1)" --apikey ${OPENSTORE_KEY} \ + # --arch ${ARCH} diff --git a/CODINGSTYLE.md b/CODINGSTYLE.md index e3c5bea51c6..868ae6000f3 100644 --- a/CODINGSTYLE.md +++ b/CODINGSTYLE.md @@ -2,10 +2,10 @@ ## Language -C++14 is the main langauge used for this project. GCC, Clang and MSVC are supported. +C++17 is the main langauge used for this project. GCC, Clang and MSVC are supported. For better backward compatibilty with older compiler, namely gcc5, -some C++14 features are restricted: +some C++17 features are restricted: * generic lambda functions are not allowed, e.g. `[](auto foo){}` * tuple constructors have to be explicit, e.g. `std::tuple{5, 6}`, not `{5, 6}` diff --git a/clickable.json b/clickable.json deleted file mode 100644 index 8aa9a5d5544..00000000000 --- a/clickable.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "builder": "cmake", - "build_dir": "build.clickable", - "dependencies_target": [ - "libogg-dev", - "libvorbis-dev", - "libopenal-dev", - "libsdl2-dev", - "libsdl2-image-dev", - "libfreetype6-dev", - "libcurl4-openssl-dev", - "libglew-dev", - "libharfbuzz-dev", - "libfribidi-dev", - "libglm-dev", - "zlib1g-dev" - ], - "install_lib": [ - "/usr/lib/${ARCH_TRIPLET}/libogg.so*", - "/usr/lib/${ARCH_TRIPLET}/libvorbis.so*", - "/usr/lib/${ARCH_TRIPLET}/libvorbisfile.so*", - "/usr/lib/${ARCH_TRIPLET}/libopenal.so*", - "/usr/lib/${ARCH_TRIPLET}/libSDL2.so*", - "/usr/lib/${ARCH_TRIPLET}/libSDL2-2.0.so*", - "/usr/lib/${ARCH_TRIPLET}/libSDL2_image.so*", - "/usr/lib/${ARCH_TRIPLET}/libSDL2_image-2.0.so*", - "/usr/lib/${ARCH_TRIPLET}/libfreetype.so*", - "/usr/lib/${ARCH_TRIPLET}/libcurl.so*", - "/usr/lib/${ARCH_TRIPLET}/libGLEW.so*", - "/usr/lib/${ARCH_TRIPLET}/libharfbuzz.so*", - "/usr/lib/${ARCH_TRIPLET}/libfribidi.so*", - "/usr/lib/${ARCH_TRIPLET}/libasound.so*", - "/usr/lib/${ARCH_TRIPLET}/libsndio.so*", - "/lib/${ARCH_TRIPLET}/libz.so*" - ], - "build_args": [ - "-DUBUNTU_TOUCH=ON", - "-DENABLE_OPENGL=OFF", - "-DWARNINGS=ON", - "-DWERROR=ON" - ] -} diff --git a/clickable.yaml b/clickable.yaml new file mode 100644 index 00000000000..0cafe4b9485 --- /dev/null +++ b/clickable.yaml @@ -0,0 +1,39 @@ +clickable_minimum_required: 7 +framework: "ubuntu-sdk-20.04" +builder: "cmake" +build_dir: "build.clickable" +dependencies_target: + - "libogg-dev" + - "libvorbis-dev" + - "libopenal-dev" + - "libsdl2-dev" + - "libsdl2-image-dev" + - "libfreetype6-dev" + - "libcurl4-openssl-dev" + - "libharfbuzz-dev" + - "libfribidi-dev" + - "libglm-dev" + - "zlib1g-dev" +install_lib: + - "/usr/lib/${ARCH_TRIPLET}/libogg.so*" + - "/usr/lib/${ARCH_TRIPLET}/libvorbis.so*" + - "/usr/lib/${ARCH_TRIPLET}/libvorbisfile.so*" + - "/usr/lib/${ARCH_TRIPLET}/libopenal.so*" + - "/usr/lib/${ARCH_TRIPLET}/libSDL2.so*" + - "/usr/lib/${ARCH_TRIPLET}/libSDL2-2.0.so*" + - "/usr/lib/${ARCH_TRIPLET}/libSDL2_image.so*" + - "/usr/lib/${ARCH_TRIPLET}/libSDL2_image-2.0.so*" + - "/usr/lib/${ARCH_TRIPLET}/libfreetype.so*" + - "/usr/lib/${ARCH_TRIPLET}/libcurl.so*" + - "/usr/lib/${ARCH_TRIPLET}/libharfbuzz.so*" + - "/usr/lib/${ARCH_TRIPLET}/libfribidi.so*" + - "/usr/lib/${ARCH_TRIPLET}/libasound.so*" + - "/usr/lib/${ARCH_TRIPLET}/libsndio.so*" + - "/lib/${ARCH_TRIPLET}/libz.so.1.2.11" +build_args: + - "-DUBUNTU_TOUCH=ON" + - "-DENABLE_OPENGL=OFF" + - "-DWARNINGS=ON" + - "-DWERROR=ON" + - "-DCLICK_ARCH=${ARCH}" + - "-DCLICK_FRAMEWORK=ubuntu-sdk-20.04" diff --git a/data/images/background/corrupted_forest/test b/data/images/background/corrupted_forest/test deleted file mode 100644 index b2888c437aa..00000000000 --- a/data/images/background/corrupted_forest/test +++ /dev/null @@ -1 +0,0 @@ -dgbyfjgfjduku diff --git a/data/images/creatures/dart/dart.sprite b/data/images/creatures/dart/dart.sprite index b98b7ef44ab..88c8f79114a 100644 --- a/data/images/creatures/dart/dart.sprite +++ b/data/images/creatures/dart/dart.sprite @@ -2,11 +2,23 @@ (action (name "flying-left") (hitbox 10 6 12 7) - (images "flying.png") - ) + (images "flying-left.png") + ) (action (name "flying-right") (hitbox 10 6 12 7) (mirror-action "flying-left") - ) ) + (action + (name "flying-down") + (hitbox 6 10 7 12) + (images "flying-down.png") + ) + ;; This will never show up since the editor uses the flipped down texture + ;; But it prevents warnings for missing actions + (action + (name "editor-up") + (hitbox 6 10 7 12) + (images "flying-down.png") + ) +) diff --git a/data/images/creatures/dart/flying-down.png b/data/images/creatures/dart/flying-down.png new file mode 100644 index 00000000000..e2452125de2 Binary files /dev/null and b/data/images/creatures/dart/flying-down.png differ diff --git a/data/images/creatures/dart/flying.png b/data/images/creatures/dart/flying-left.png similarity index 100% rename from data/images/creatures/dart/flying.png rename to data/images/creatures/dart/flying-left.png diff --git a/data/images/creatures/darttrap/darttrap.sprite b/data/images/creatures/darttrap/darttrap.sprite index e816e2bc33a..474638ef28b 100644 --- a/data/images/creatures/darttrap/darttrap.sprite +++ b/data/images/creatures/darttrap/darttrap.sprite @@ -1,39 +1,73 @@ (supertux-sprite (action - (hitbox 21 13 11 37) + (hitbox 21 13 11 54) (name "idle-left") - (loops 1) - (images "d3-.png" - "d3-.png" - "d3-.png" - "d3-.png" - "d4.png" - "d-idle.png") + (loops 1) + (images "left-3.png" + "left-3.png" + "left-3.png" + "left-3.png" + "left-4.png" + "left-idle.png" + ) + ) + (action + (hitbox 13 21 54 11) + (name "idle-down") + (loops 1) + (images "down-3.png" + "down-3.png" + "down-3.png" + "down-3.png" + "down-4.png" + "down-idle.png" + ) + ) + ;; This will never show up since the editor uses the flipped down texture + ;; But it prevents warnings for missing actions + (action + (hitbox 13 21 54 11) + (name "editor-up") + (images "down-1.png") ) (action - (hitbox 21 13 11 37) + (hitbox 21 13 11 54) (name "idle-right") - (loops 1) + (loops 1) (mirror-action "idle-left") ) (action - (hitbox 21 13 11 37) + (hitbox 21 13 11 54) (name "loading-left") - (fps 10) + (fps 10) + (images + "left-idle.png" + "left-1.png" + "left-2.png" + "left-3.png" + "left-3.png" + "left-3.png" + "left-3.png" + ) + ) + (action + (hitbox 13 21 54 11) + (name "loading-down") + (fps 10) (images - "d-idle.png" - "d1.png" - "d2.png" - "d3-.png" - "d3-.png" - "d3-.png" - "d3-.png" + "down-idle.png" + "down-1.png" + "down-2.png" + "down-3.png" + "down-3.png" + "down-3.png" + "down-3.png" ) ) (action - (hitbox 21 13 11 37) + (hitbox 21 13 11 54) (name "loading-right") - (fps 10) + (fps 10) (mirror-action "loading-left") ) ) diff --git a/data/images/creatures/darttrap/down-1.png b/data/images/creatures/darttrap/down-1.png new file mode 100644 index 00000000000..9e8e7f0008f Binary files /dev/null and b/data/images/creatures/darttrap/down-1.png differ diff --git a/data/images/creatures/darttrap/down-2.png b/data/images/creatures/darttrap/down-2.png new file mode 100644 index 00000000000..06fd8edec4d Binary files /dev/null and b/data/images/creatures/darttrap/down-2.png differ diff --git a/data/images/creatures/darttrap/down-3.png b/data/images/creatures/darttrap/down-3.png new file mode 100644 index 00000000000..b516cfa38a6 Binary files /dev/null and b/data/images/creatures/darttrap/down-3.png differ diff --git a/data/images/creatures/darttrap/down-4.png b/data/images/creatures/darttrap/down-4.png new file mode 100644 index 00000000000..976cd3282f5 Binary files /dev/null and b/data/images/creatures/darttrap/down-4.png differ diff --git a/data/images/creatures/darttrap/down-idle.png b/data/images/creatures/darttrap/down-idle.png new file mode 100644 index 00000000000..87588aeb743 Binary files /dev/null and b/data/images/creatures/darttrap/down-idle.png differ diff --git a/data/images/creatures/darttrap/d1.png b/data/images/creatures/darttrap/left-1.png similarity index 100% rename from data/images/creatures/darttrap/d1.png rename to data/images/creatures/darttrap/left-1.png diff --git a/data/images/creatures/darttrap/d2.png b/data/images/creatures/darttrap/left-2.png similarity index 100% rename from data/images/creatures/darttrap/d2.png rename to data/images/creatures/darttrap/left-2.png diff --git a/data/images/creatures/darttrap/d3-.png b/data/images/creatures/darttrap/left-3.png similarity index 100% rename from data/images/creatures/darttrap/d3-.png rename to data/images/creatures/darttrap/left-3.png diff --git a/data/images/creatures/darttrap/d4.png b/data/images/creatures/darttrap/left-4.png similarity index 100% rename from data/images/creatures/darttrap/d4.png rename to data/images/creatures/darttrap/left-4.png diff --git a/data/images/creatures/darttrap/d-idle.png b/data/images/creatures/darttrap/left-idle.png similarity index 100% rename from data/images/creatures/darttrap/d-idle.png rename to data/images/creatures/darttrap/left-idle.png diff --git a/data/images/creatures/mr_bomb/old_bomb/old_bomb.sprite b/data/images/creatures/mr_bomb/old_bomb/old_bomb.sprite index a1082df2752..c9911b2c7b9 100644 --- a/data/images/creatures/mr_bomb/old_bomb/old_bomb.sprite +++ b/data/images/creatures/mr_bomb/old_bomb/old_bomb.sprite @@ -20,12 +20,12 @@ (action (name "iced-left") - (hitbox 5 8 32 32) - (images "iced-left.png")) + (hitbox 14 19 32 32) + (images "left-0.png")) (action (name "iced-right") - (hitbox 5 8 32 32) + (hitbox 14 19 32 32) (mirror-action "iced-left")) (action diff --git a/data/images/creatures/tux/big/run-0.png b/data/images/creatures/tux/big/run-0.png new file mode 100644 index 00000000000..5a3d9243c8c Binary files /dev/null and b/data/images/creatures/tux/big/run-0.png differ diff --git a/data/images/creatures/tux/big/run-1.png b/data/images/creatures/tux/big/run-1.png new file mode 100644 index 00000000000..f9b8798a8a9 Binary files /dev/null and b/data/images/creatures/tux/big/run-1.png differ diff --git a/data/images/creatures/tux/big/run-2.png b/data/images/creatures/tux/big/run-2.png new file mode 100644 index 00000000000..b6c9bfe5dff Binary files /dev/null and b/data/images/creatures/tux/big/run-2.png differ diff --git a/data/images/creatures/tux/big/run-3.png b/data/images/creatures/tux/big/run-3.png new file mode 100644 index 00000000000..346cbf6e8ad Binary files /dev/null and b/data/images/creatures/tux/big/run-3.png differ diff --git a/data/images/creatures/tux/big/run-4.png b/data/images/creatures/tux/big/run-4.png new file mode 100644 index 00000000000..8bdc35c9012 Binary files /dev/null and b/data/images/creatures/tux/big/run-4.png differ diff --git a/data/images/creatures/tux/big/run-5.png b/data/images/creatures/tux/big/run-5.png new file mode 100644 index 00000000000..04f24702c64 Binary files /dev/null and b/data/images/creatures/tux/big/run-5.png differ diff --git a/data/images/creatures/tux/big/run-6.png b/data/images/creatures/tux/big/run-6.png new file mode 100644 index 00000000000..60741bd2ecf Binary files /dev/null and b/data/images/creatures/tux/big/run-6.png differ diff --git a/data/images/creatures/tux/big/run-7.png b/data/images/creatures/tux/big/run-7.png new file mode 100644 index 00000000000..59c25a04838 Binary files /dev/null and b/data/images/creatures/tux/big/run-7.png differ diff --git a/data/images/creatures/tux/big/run_transition-0.png b/data/images/creatures/tux/big/run_transition-0.png new file mode 100644 index 00000000000..77a2d1ee7c8 Binary files /dev/null and b/data/images/creatures/tux/big/run_transition-0.png differ diff --git a/data/images/creatures/tux/big/walk_transition-0.png b/data/images/creatures/tux/big/walk_transition-0.png new file mode 100644 index 00000000000..5c4fd704dbc Binary files /dev/null and b/data/images/creatures/tux/big/walk_transition-0.png differ diff --git a/data/images/creatures/tux/small/run-0.png b/data/images/creatures/tux/small/run-0.png new file mode 100644 index 00000000000..7425c0ea8ac Binary files /dev/null and b/data/images/creatures/tux/small/run-0.png differ diff --git a/data/images/creatures/tux/small/run-1.png b/data/images/creatures/tux/small/run-1.png new file mode 100644 index 00000000000..d7630a97d11 Binary files /dev/null and b/data/images/creatures/tux/small/run-1.png differ diff --git a/data/images/creatures/tux/small/run-2.png b/data/images/creatures/tux/small/run-2.png new file mode 100644 index 00000000000..e5f42a7d8e0 Binary files /dev/null and b/data/images/creatures/tux/small/run-2.png differ diff --git a/data/images/creatures/tux/small/run-3.png b/data/images/creatures/tux/small/run-3.png new file mode 100644 index 00000000000..c2dd8962c58 Binary files /dev/null and b/data/images/creatures/tux/small/run-3.png differ diff --git a/data/images/creatures/tux/small/run-4.png b/data/images/creatures/tux/small/run-4.png new file mode 100644 index 00000000000..0f71a8d808e Binary files /dev/null and b/data/images/creatures/tux/small/run-4.png differ diff --git a/data/images/creatures/tux/small/run-5.png b/data/images/creatures/tux/small/run-5.png new file mode 100644 index 00000000000..91095245067 Binary files /dev/null and b/data/images/creatures/tux/small/run-5.png differ diff --git a/data/images/creatures/tux/small/run-6.png b/data/images/creatures/tux/small/run-6.png new file mode 100644 index 00000000000..165eabb5602 Binary files /dev/null and b/data/images/creatures/tux/small/run-6.png differ diff --git a/data/images/creatures/tux/small/run-7.png b/data/images/creatures/tux/small/run-7.png new file mode 100644 index 00000000000..9c8393592d1 Binary files /dev/null and b/data/images/creatures/tux/small/run-7.png differ diff --git a/data/images/creatures/tux/small/run_transition-0.png b/data/images/creatures/tux/small/run_transition-0.png new file mode 100644 index 00000000000..45084c4b4d2 Binary files /dev/null and b/data/images/creatures/tux/small/run_transition-0.png differ diff --git a/data/images/creatures/tux/small/walk_transition-0.png b/data/images/creatures/tux/small/walk_transition-0.png new file mode 100644 index 00000000000..909c3af99a0 Binary files /dev/null and b/data/images/creatures/tux/small/walk_transition-0.png differ diff --git a/data/images/creatures/tux/tux.sprite b/data/images/creatures/tux/tux.sprite index c08c3e6a624..8bd0bc704ee 100644 --- a/data/images/creatures/tux/tux.sprite +++ b/data/images/creatures/tux/tux.sprite @@ -141,9 +141,11 @@ (action (name "small-walk-right") - (fps 15.0) + (fps 14.0) + (loop-frame 2) (hitbox 16 26 32 32) - (images "small/walk-0.png" + (images "small/walk_transition-0.png" + "small/walk-0.png" "small/walk-1.png" "small/walk-2.png" "small/walk-3.png" @@ -154,10 +156,33 @@ (action (name "small-walk-left") - (fps 15.0) + (fps 14.0) + (loop-frame 2) (hitbox 16 26 32 32) (mirror-action "small-walk-right")) + (action + (name "small-run-right") + (fps 15.0) + (loop-frame 2) + (hitbox 18 26 32 32) + (images "small/run_transition-0.png" + "small/run-0.png" + "small/run-1.png" + "small/run-2.png" + "small/run-3.png" + "small/run-4.png" + "small/run-5.png" + "small/run-6.png" + "small/run-7.png")) + + (action + (name "small-run-left") + (fps 15.0) + (loop-frame 2) + (hitbox 20 26 32 32) + (mirror-action "small-run-right")) + (action (name "small-jump-right") (fps 15.0) @@ -604,9 +629,11 @@ (action (name "big-walk-right") - (fps 16.0) + (fps 14.0) + (loop-frame 2) (hitbox 16 14 32 64) - (images "big/walk-0.png" + (images "big/walk_transition-0.png" + "big/walk-0.png" "big/walk-1.png" "big/walk-2.png" "big/walk-3.png" @@ -617,10 +644,33 @@ (action (name "big-walk-left") - (fps 16.0) + (fps 14.0) + (loop-frame 2) (hitbox 16 14 32 64) (mirror-action "big-walk-right")) + (action + (name "big-run-right") + (fps 15.0) + (loop-frame 2) + (hitbox 18 14 32 64) + (images "big/run_transition-0.png" + "big/run-0.png" + "big/run-1.png" + "big/run-2.png" + "big/run-3.png" + "big/run-4.png" + "big/run-5.png" + "big/run-6.png" + "big/run-7.png")) + + (action + (name "big-run-left") + (fps 15.0) + (loop-frame 2) + (hitbox 20 14 32 64) + (mirror-action "big-run-right")) + (action (name "big-jump-right") (fps 15.0) @@ -1075,9 +1125,11 @@ (action (name "fire-walk-right") - (fps 16.0) + (fps 14.0) + (loop-frame 2) (hitbox 16 14 32 64) - (images "big/walk-0.png" + (images "big/walk_transition-0.png" + "big/walk-0.png" "big/walk-1.png" "big/walk-2.png" "big/walk-3.png" @@ -1088,11 +1140,34 @@ (action (name "fire-walk-left") - (fps 16.0) + (fps 14.0) + (loop-frame 2) (hitbox 16 14 32 64) (mirror-action "fire-walk-right")) (action + (name "fire-run-right") + (fps 15.0) + (loop-frame 2) + (hitbox 18 14 32 64) + (images "big/run_transition-0.png" + "big/run-0.png" + "big/run-1.png" + "big/run-2.png" + "big/run-3.png" + "big/run-4.png" + "big/run-5.png" + "big/run-6.png" + "big/run-7.png")) + + (action + (name "fire-run-left") + (fps 15.0) + (loop-frame 2) + (hitbox 20 14 32 64) + (mirror-action "fire-run-right")) + + (action (name "fire-jump-right") (fps 15.0) (loops 1) @@ -1546,9 +1621,11 @@ (action (name "ice-walk-right") - (fps 16.0) + (fps 14.0) + (loop-frame 2) (hitbox 16 14 32 64) - (images "big/walk-0.png" + (images "big/walk_transition-0.png" + "big/walk-0.png" "big/walk-1.png" "big/walk-2.png" "big/walk-3.png" @@ -1559,11 +1636,34 @@ (action (name "ice-walk-left") - (fps 16.0) + (fps 14.0) + (loop-frame 2) (hitbox 16 14 32 64) (mirror-action "ice-walk-right")) (action + (name "ice-run-right") + (fps 15.0) + (loop-frame 2) + (hitbox 18 14 32 64) + (images "big/run_transition-0.png" + "big/run-0.png" + "big/run-1.png" + "big/run-2.png" + "big/run-3.png" + "big/run-4.png" + "big/run-5.png" + "big/run-6.png" + "big/run-7.png")) + + (action + (name "ice-run-left") + (fps 15.0) + (loop-frame 2) + (hitbox 20 14 32 64) + (mirror-action "ice-run-right")) + + (action (name "ice-jump-right") (fps 15.0) (loops 1) @@ -2017,9 +2117,11 @@ (action (name "earth-walk-right") - (fps 16.0) + (fps 14.0) + (loop-frame 2) (hitbox 16 14 32 64) - (images "big/walk-0.png" + (images "big/walk_transition-0.png" + "big/walk-0.png" "big/walk-1.png" "big/walk-2.png" "big/walk-3.png" @@ -2030,11 +2132,34 @@ (action (name "earth-walk-left") - (fps 16.0) + (fps 14.0) + (loop-frame 2) (hitbox 16 14 32 64) (mirror-action "earth-walk-right")) (action + (name "earth-run-right") + (fps 15.0) + (loop-frame 2) + (hitbox 18 14 32 64) + (images "big/run_transition-0.png" + "big/run-0.png" + "big/run-1.png" + "big/run-2.png" + "big/run-3.png" + "big/run-4.png" + "big/run-5.png" + "big/run-6.png" + "big/run-7.png")) + + (action + (name "earth-run-left") + (fps 15.0) + (loop-frame 2) + (hitbox 20 14 32 64) + (mirror-action "earth-run-right")) + + (action (name "earth-jump-right") (fps 15.0) (loops 1) @@ -2487,9 +2612,11 @@ (action (name "air-walk-right") - (fps 16.0) + (fps 14.0) + (loop-frame 2) (hitbox 16 14 32 64) - (images "big/walk-0.png" + (images "big/walk_transition-0.png" + "big/walk-0.png" "big/walk-1.png" "big/walk-2.png" "big/walk-3.png" @@ -2500,11 +2627,34 @@ (action (name "air-walk-left") - (fps 16.0) + (fps 14.0) + (loop-frame 2) (hitbox 16 14 32 64) (mirror-action "air-walk-right")) (action + (name "air-run-right") + (fps 15.0) + (loop-frame 2) + (hitbox 18 14 32 64) + (images "big/run_transition-0.png" + "big/run-0.png" + "big/run-1.png" + "big/run-2.png" + "big/run-3.png" + "big/run-4.png" + "big/run-5.png" + "big/run-6.png" + "big/run-7.png")) + + (action + (name "air-run-left") + (fps 15.0) + (loop-frame 2) + (hitbox 20 14 32 64) + (mirror-action "air-run-right")) + + (action (name "air-jump-right") (fps 15.0) (loops 1) @@ -2903,8 +3053,8 @@ "cutscene/credits-0.png")) (action - (name "credits-left") - (fps 10.0) - (hitbox 16 14 32 64) - (mirror-action "credits-right")) + (name "credits-left") + (fps 10.0) + (hitbox 16 14 32 64) + (mirror-action "credits-right")) ) diff --git a/data/images/engine/editor/objects.stoi b/data/images/engine/editor/objects.stoi index 5aaf6e284e4..0880d34252b 100644 --- a/data/images/engine/editor/objects.stoi +++ b/data/images/engine/editor/objects.stoi @@ -155,7 +155,7 @@ (icon "images/creatures/angrystone/charging-0.png")) (object (class "darttrap") - (icon "images/creatures/darttrap/d1.png")) + (icon "images/creatures/darttrap/left-1.png")) (object (class "dispenser") (icon "images/creatures/dispenser/cannon_left.png")) @@ -192,7 +192,7 @@ (icon "images/creatures/skydive/skydive1.png")) (object (class "dart") - (icon "images/creatures/dart/flying.png")) + (icon "images/creatures/dart/flying-left.png")) ) (objectgroup @@ -418,5 +418,8 @@ (object (class "tilemap") (icon "images/engine/editor/tilemap.png")) - ) + (object + (class "background") + (icon "images/engine/editor/background.png")) + ) ) diff --git a/data/images/engine/editor/select-mode3.png b/data/images/engine/editor/select-mode3.png new file mode 100644 index 00000000000..ac80da9ede9 Binary files /dev/null and b/data/images/engine/editor/select-mode3.png differ diff --git a/data/images/objects/bonus_block/box-invisible.png b/data/images/objects/bonus_block/box-invisible.png new file mode 100644 index 00000000000..e5c0f3a5ca4 Binary files /dev/null and b/data/images/objects/bonus_block/box-invisible.png differ diff --git a/data/images/objects/rublight/rublight.sprite b/data/images/objects/rublight/rublight.sprite index f5568b4104f..323f1057f14 100644 --- a/data/images/objects/rublight/rublight.sprite +++ b/data/images/objects/rublight/rublight.sprite @@ -1,22 +1,22 @@ -(supertux-sprite - (action - (name "default") - (fps 8) - (images "rublight-0.png" - "rublight-1.png" - "rublight-2.png" - "rublight-3.png" - "rublight-4.png" - "rublight-5.png" - "rublight-6.png" - "rublight-7.png" - "rublight-8.png" - "rublight-9.png" - "rublight-10.png" - "rublight-0.png" - "rublight-0.png" - "rublight-0.png" - "rublight-0.png" - "rublight-0.png") - ) -) +(supertux-sprite + (action + (name "active") + (fps 8) + (loops 1) + (images "rublight-0.png" + "rublight-1.png" + "rublight-2.png" + "rublight-3.png" + "rublight-4.png" + "rublight-5.png" + "rublight-6.png" + "rublight-7.png" + "rublight-8.png" + "rublight-9.png" + "rublight-10.png") + ) + (action + (name "inactive") + (images "rublight-0.png") + ) +) diff --git a/data/levels/world1/worldmap.stwm b/data/levels/world1/worldmap.stwm index a9c94ee78c8..22b00573d5e 100644 --- a/data/levels/world1/worldmap.stwm +++ b/data/levels/world1/worldmap.stwm @@ -2629,7 +2629,7 @@ state.ambient_b <- 1;") ) ) (tilemap - (solid #f) + (solid #t) (z-pos 2) (name "iv_secret") (width 100) diff --git a/data/scripts/default.nut b/data/scripts/default.nut index 7f674e51c47..082c35831cc 100644 --- a/data/scripts/default.nut +++ b/data/scripts/default.nut @@ -13,6 +13,8 @@ Level <- { set_respawn_pos=Level_set_respawn_pos, flip_vertically=Level_flip_vertically, toggle_pause=Level_toggle_pause, + pause_target_timer=Level_pause_target_timer, + resume_target_timer=Level_resume_target_timer, edit=Level_edit }; diff --git a/mk/clickable/BROKEN-build-with-glbinding.json b/mk/clickable/BROKEN-build-with-glbinding.json deleted file mode 100644 index 28f73c94c70..00000000000 --- a/mk/clickable/BROKEN-build-with-glbinding.json +++ /dev/null @@ -1,43 +0,0 @@ -// This build won't work, as glbinding isn't available on Ubuntu 16.04. -{ - "builder": "cmake", - "build_dir": "build.clickable", - "dependencies_target": [ - "libogg-dev", - "libvorbis-dev", - "libopenal-dev", - "libsdl2-dev", - "libsdl2-image-dev", - "libfreetype6-dev", - "libcurl4-openssl-dev", - "libglbinding-dev", - "libharfbuzz-dev", - "libfribidi-dev", - "libglm-dev", - "zlib1g-dev" - ], - "install_lib": [ - "/usr/lib/${ARCH_TRIPLET}/libogg.so*", - "/usr/lib/${ARCH_TRIPLET}/libvorbis.so*", - "/usr/lib/${ARCH_TRIPLET}/libvorbisfile.so*", - "/usr/lib/${ARCH_TRIPLET}/libopenal.so*", - "/usr/lib/${ARCH_TRIPLET}/libSDL2.so*", - "/usr/lib/${ARCH_TRIPLET}/libSDL2-2.0.so*", - "/usr/lib/${ARCH_TRIPLET}/libSDL2_image.so*", - "/usr/lib/${ARCH_TRIPLET}/libSDL2_image-2.0.so*", - "/usr/lib/${ARCH_TRIPLET}/libfreetype.so*", - "/usr/lib/${ARCH_TRIPLET}/libcurl.so*", - "/usr/lib/${ARCH_TRIPLET}/libglbinding.so*", - "/usr/lib/${ARCH_TRIPLET}/libharfbuzz.so*", - "/usr/lib/${ARCH_TRIPLET}/libfribidi.so*", - "/usr/lib/${ARCH_TRIPLET}/libasound.so*", - "/usr/lib/${ARCH_TRIPLET}/libsndio.so*", - "/lib/${ARCH_TRIPLET}/libz.so*" - ], - "build_args": [ - "-DUBUNTU_TOUCH=ON", - "-DGLBINDING_ENABLED=ON", - "-DWARNINGS=ON", - "-DWERROR=ON" - ] -} diff --git a/mk/clickable/build-with-glew.json b/mk/clickable/build-with-glew.json deleted file mode 100644 index a793e6f3d5d..00000000000 --- a/mk/clickable/build-with-glew.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "builder": "cmake", - "build_dir": "build.clickable", - "dependencies_target": [ - "libogg-dev", - "libvorbis-dev", - "libopenal-dev", - "libsdl2-dev", - "libsdl2-image-dev", - "libfreetype6-dev", - "libcurl4-openssl-dev", - "libglew-dev", - "libharfbuzz-dev", - "libfribidi-dev", - "libglm-dev", - "zlib1g-dev" - ], - "install_lib": [ - "/usr/lib/${ARCH_TRIPLET}/libogg.so*", - "/usr/lib/${ARCH_TRIPLET}/libvorbis.so*", - "/usr/lib/${ARCH_TRIPLET}/libvorbisfile.so*", - "/usr/lib/${ARCH_TRIPLET}/libopenal.so*", - "/usr/lib/${ARCH_TRIPLET}/libSDL2.so*", - "/usr/lib/${ARCH_TRIPLET}/libSDL2-2.0.so*", - "/usr/lib/${ARCH_TRIPLET}/libSDL2_image.so*", - "/usr/lib/${ARCH_TRIPLET}/libSDL2_image-2.0.so*", - "/usr/lib/${ARCH_TRIPLET}/libfreetype.so*", - "/usr/lib/${ARCH_TRIPLET}/libcurl.so*", - "/usr/lib/${ARCH_TRIPLET}/libGLEW.so*", - "/usr/lib/${ARCH_TRIPLET}/libharfbuzz.so*", - "/usr/lib/${ARCH_TRIPLET}/libfribidi.so*", - "/usr/lib/${ARCH_TRIPLET}/libasound.so*", - "/usr/lib/${ARCH_TRIPLET}/libsndio.so*", - "/lib/${ARCH_TRIPLET}/libz.so*" - ], - "build_args": [ - "-DUBUNTU_TOUCH=ON", - "-DWARNINGS=ON", - "-DWERROR=ON" - ] -} diff --git a/mk/clickable/clickable-glbinding.yaml b/mk/clickable/clickable-glbinding.yaml new file mode 100644 index 00000000000..4eb72183dc5 --- /dev/null +++ b/mk/clickable/clickable-glbinding.yaml @@ -0,0 +1,41 @@ +clickable_minimum_required: 7 +framework: "ubuntu-sdk-20.04" +builder: "cmake" +build_dir: "build.clickable" +dependencies_target: + - "libogg-dev" + - "libvorbis-dev" + - "libopenal-dev" + - "libsdl2-dev" + - "libsdl2-image-dev" + - "libfreetype6-dev" + - "libcurl4-openssl-dev" + - "libglbinding-dev" + - "libharfbuzz-dev" + - "libfribidi-dev" + - "libglm-dev" + - "zlib1g-dev" +install_lib: + - "/usr/lib/${ARCH_TRIPLET}/libogg.so*" + - "/usr/lib/${ARCH_TRIPLET}/libvorbis.so*" + - "/usr/lib/${ARCH_TRIPLET}/libvorbisfile.so*" + - "/usr/lib/${ARCH_TRIPLET}/libopenal.so*" + - "/usr/lib/${ARCH_TRIPLET}/libSDL2.so*" + - "/usr/lib/${ARCH_TRIPLET}/libSDL2-2.0.so*" + - "/usr/lib/${ARCH_TRIPLET}/libSDL2_image.so*" + - "/usr/lib/${ARCH_TRIPLET}/libSDL2_image-2.0.so*" + - "/usr/lib/${ARCH_TRIPLET}/libfreetype.so*" + - "/usr/lib/${ARCH_TRIPLET}/libcurl.so*" + - "/usr/lib/${ARCH_TRIPLET}/libglbinding.so*" + - "/usr/lib/${ARCH_TRIPLET}/libharfbuzz.so*" + - "/usr/lib/${ARCH_TRIPLET}/libfribidi.so*" + - "/usr/lib/${ARCH_TRIPLET}/libasound.so*" + - "/usr/lib/${ARCH_TRIPLET}/libsndio.so*" + - "/lib/${ARCH_TRIPLET}/libz.so.1.2.11" +build_args: + - "-DUBUNTU_TOUCH=ON" + - "-DGLBINDING_ENABLED=ON" + - "-DWARNINGS=ON" + - "-DWERROR=ON" + - "-DCLICK_ARCH=${ARCH}" + - "-DCLICK_FRAMEWORK=ubuntu-sdk-20.04" diff --git a/mk/clickable/clickable-glew.yaml b/mk/clickable/clickable-glew.yaml new file mode 100644 index 00000000000..a75c6b0a989 --- /dev/null +++ b/mk/clickable/clickable-glew.yaml @@ -0,0 +1,40 @@ +clickable_minimum_required: 7 +framework: "ubuntu-sdk-20.04" +builder: "cmake" +build_dir: "build.clickable" +dependencies_target: + - "libogg-dev" + - "libvorbis-dev" + - "libopenal-dev" + - "libsdl2-dev" + - "libsdl2-image-dev" + - "libfreetype6-dev" + - "libcurl4-openssl-dev" + - "libglew-dev" + - "libharfbuzz-dev" + - "libfribidi-dev" + - "libglm-dev" + - "zlib1g-dev" +install_lib: + - "/usr/lib/${ARCH_TRIPLET}/libogg.so*" + - "/usr/lib/${ARCH_TRIPLET}/libvorbis.so*" + - "/usr/lib/${ARCH_TRIPLET}/libvorbisfile.so*" + - "/usr/lib/${ARCH_TRIPLET}/libopenal.so*" + - "/usr/lib/${ARCH_TRIPLET}/libSDL2.so*" + - "/usr/lib/${ARCH_TRIPLET}/libSDL2-2.0.so*" + - "/usr/lib/${ARCH_TRIPLET}/libSDL2_image.so*" + - "/usr/lib/${ARCH_TRIPLET}/libSDL2_image-2.0.so*" + - "/usr/lib/${ARCH_TRIPLET}/libfreetype.so*" + - "/usr/lib/${ARCH_TRIPLET}/libcurl.so*" + - "/usr/lib/${ARCH_TRIPLET}/libGLEW.so*" + - "/usr/lib/${ARCH_TRIPLET}/libharfbuzz.so*" + - "/usr/lib/${ARCH_TRIPLET}/libfribidi.so*" + - "/usr/lib/${ARCH_TRIPLET}/libasound.so*" + - "/usr/lib/${ARCH_TRIPLET}/libsndio.so*" + - "/lib/${ARCH_TRIPLET}/libz.so.1.2.11" +build_args: + - "-DUBUNTU_TOUCH=ON" + - "-DWARNINGS=ON" + - "-DWERROR=ON" + - "-DCLICK_ARCH=${ARCH}" + - "-DCLICK_FRAMEWORK=ubuntu-sdk-20.04" diff --git a/mk/clickable/supertux2.apparmor b/mk/clickable/supertux2.apparmor index f5a7bc82791..e34364a5d08 100644 --- a/mk/clickable/supertux2.apparmor +++ b/mk/clickable/supertux2.apparmor @@ -3,5 +3,5 @@ "audio", "networking" ], - "policy_version": 16.04 + "policy_version": 20.04 } diff --git a/src/addon/addon_manager.cpp b/src/addon/addon_manager.cpp index 94802a60cf1..9127e11b14d 100644 --- a/src/addon/addon_manager.cpp +++ b/src/addon/addon_manager.cpp @@ -80,18 +80,14 @@ MD5 md5_from_archive(const std::string& filename) } } -static Addon& get_addon(const AddonManager::AddonList& list, const AddonId& id, +static Addon& get_addon(const AddonManager::AddonMap& list, const AddonId& id, bool installed) { - auto it = std::find_if(list.begin(), list.end(), - [&id](const std::unique_ptr& addon) - { - return addon->get_id() == id; - }); + auto it = list.find(id); if (it != list.end()) { - return **it; + return *(it->second); } else { @@ -100,15 +96,14 @@ static Addon& get_addon(const AddonManager::AddonList& list, const AddonId& id, } } -static std::vector get_addons(const AddonManager::AddonList& list) +static std::vector get_addons(const AddonManager::AddonMap& list) { // Use a map for storing sorted addon titles with their respective IDs. std::map sorted_titles; - std::for_each(list.begin(), list.end(), - [&](const std::unique_ptr& addon) - { - sorted_titles.insert({addon->get_title(), addon->get_id()}); - }); + for (const auto& [id, addon] : list) + { + sorted_titles.insert({addon->get_title(), id}); + } std::vector results; results.reserve(sorted_titles.size()); std::transform(sorted_titles.begin(), sorted_titles.end(), @@ -223,9 +218,9 @@ AddonManager::~AddonManager() { // sync enabled/disabled add-ons into the config for saving m_addon_config.clear(); - for (const auto& addon : m_installed_addons) + for (const auto& [id, addon] : m_installed_addons) { - m_addon_config.push_back({addon->get_id(), addon->is_enabled()}); + m_addon_config.push_back({id, addon->is_enabled()}); } // Delete the add-on cache directory, if it exists. @@ -307,17 +302,13 @@ TransferStatusListPtr AddonManager::request_install_addon(const AddonId& addon_id) { // remove addon if it already exists - auto it = std::find_if(m_installed_addons.begin(), m_installed_addons.end(), - [&addon_id](const std::unique_ptr& addon) - { - return addon->get_id() == addon_id; - }); + auto it = m_installed_addons.find(addon_id); if (it != m_installed_addons.end()) { log_debug << "reinstalling addon " << addon_id << std::endl; - if ((*it)->is_enabled()) + if (it->second->is_enabled()) { - disable_addon((*it)->get_id()); + disable_addon(it->first); } m_installed_addons.erase(it); } @@ -413,17 +404,13 @@ void AddonManager::install_addon(const AddonId& addon_id) { { // remove addon if it already exists - auto it = std::find_if(m_installed_addons.begin(), m_installed_addons.end(), - [&addon_id](const std::unique_ptr& addon) - { - return addon->get_id() == addon_id; - }); + auto it = m_installed_addons.find(addon_id); if (it != m_installed_addons.end()) { log_debug << "reinstalling addon " << addon_id << std::endl; - if ((*it)->is_enabled()) + if (it->second->is_enabled()) { - disable_addon((*it)->get_id()); + disable_addon(it->first); } m_installed_addons.erase(it); } @@ -489,11 +476,7 @@ AddonManager::uninstall_addon(const AddonId& addon_id) disable_addon(addon_id); } log_debug << "deleting file \"" << addon.get_install_filename() << "\"" << std::endl; - const auto it = std::find_if(m_installed_addons.begin(), m_installed_addons.end(), - [&addon](const std::unique_ptr& rhs) - { - return addon.get_id() == rhs->get_id(); - }); + const auto it = m_installed_addons.find(addon.get_id()); if (it != m_installed_addons.end()) { if (PHYSFS_delete(FileSystem::join(m_addon_directory, addon.get_filename()).c_str()) == 0) @@ -545,15 +528,13 @@ std::vector AddonManager::get_local_addon_screenshots(const AddonId& addon_id) { std::vector screenshots; - std::unique_ptr rc(PHYSFS_enumerateFiles(m_screenshots_cache_directory.c_str()), PHYSFS_freeList); - for (char** i = rc.get(); *i != nullptr; ++i) - { + physfsutil::enumerate_files(m_screenshots_cache_directory, [&screenshots, &addon_id, this](const std::string& filename) { // Push any files from the cache directory, starting with the ID of the add-on. - if (StringUtil::starts_with(*i, addon_id)) + if (StringUtil::starts_with(filename, addon_id)) { - screenshots.push_back(FileSystem::join(m_screenshots_cache_directory, *i)); + screenshots.push_back(FileSystem::join(m_screenshots_cache_directory, filename)); } - } + }); return screenshots; } @@ -570,10 +551,10 @@ AddonManager::enable_addon(const AddonId& addon_id) { if (addon.get_type() == Addon::RESOURCEPACK) { - for (const auto& installed_addon : m_installed_addons) + for (const auto& [id, addon] : m_installed_addons) { - if (installed_addon->get_type() == Addon::RESOURCEPACK && - installed_addon->is_enabled()) + if (addon->get_type() == Addon::RESOURCEPACK && + addon->is_enabled()) { throw std::runtime_error(_("Only one resource pack is allowed to be enabled at a time.")); } @@ -664,9 +645,9 @@ AddonManager::is_old_enabled_addon(const std::unique_ptr& addon) const bool AddonManager::is_old_addon_enabled() const { auto it = std::find_if(m_installed_addons.begin(), m_installed_addons.end(), - [this](const std::unique_ptr& addon) + [this](const auto& addon) { - return is_old_enabled_addon(addon); + return is_old_enabled_addon(addon.second); }); return it != m_installed_addons.end(); @@ -675,9 +656,9 @@ AddonManager::is_old_addon_enabled() const { void AddonManager::disable_old_addons() { - for (auto& addon : m_installed_addons) { + for (auto& [id, addon] : m_installed_addons) { if (is_old_enabled_addon(addon)) { - disable_addon(addon->get_id()); + disable_addon(id); } } } @@ -686,7 +667,7 @@ void AddonManager::mount_old_addons() { std::string mountpoint; - for (auto& addon : m_installed_addons) { + for (auto& [id, addon] : m_installed_addons) { if (is_old_enabled_addon(addon)) { if (PHYSFS_mount(addon->get_install_filename().c_str(), mountpoint.c_str(), !addon->overrides_data()) == 0) { @@ -700,7 +681,7 @@ AddonManager::mount_old_addons() void AddonManager::unmount_old_addons() { - for (auto& addon : m_installed_addons) { + for (auto& [id, addon] : m_installed_addons) { if (is_old_enabled_addon(addon)) { if (PHYSFS_unmount(addon->get_install_filename().c_str()) == 0) { @@ -715,7 +696,7 @@ bool AddonManager::is_from_old_addon(const std::string& filename) const { std::string real_path = PHYSFS_getRealDir(filename.c_str()); - for (auto& addon : m_installed_addons) { + for (auto& [id, addon] : m_installed_addons) { if (is_old_enabled_addon(addon) && addon->get_install_filename() == real_path) { return true; @@ -738,11 +719,11 @@ std::vector AddonManager::get_depending_addons(const std::string& id) const { std::vector addons; - for (auto& addon : m_installed_addons) + for (auto& [id, addon] : m_installed_addons) { const auto& dependencies = addon->get_dependencies(); if (std::find(dependencies.begin(), dependencies.end(), id) != dependencies.end()) - addons.push_back(addon->get_id()); + addons.push_back(id); } return addons; } @@ -753,28 +734,24 @@ AddonManager::scan_for_archives() const std::vector archives; // Search for archives and add them to the search path - std::unique_ptr - rc(PHYSFS_enumerateFiles(m_addon_directory.c_str()), - PHYSFS_freeList); - for (char** i = rc.get(); *i != nullptr; ++i) - { - const std::string fullpath = FileSystem::join(m_addon_directory, *i); + physfsutil::enumerate_files(m_addon_directory, [this, &archives](const std::string& filename) { + const std::string fullpath = FileSystem::join(m_addon_directory, filename); if (physfsutil::is_directory(fullpath)) { // ignore dot files (e.g. '.git/'), as well as the addon cache directory - if ((*i)[0] != '.' && fullpath != m_cache_directory) { + if (filename[0] != '.' && fullpath != m_cache_directory) { archives.push_back(fullpath); } } else { - if (StringUtil::has_suffix(StringUtil::tolower(*i), ".zip")) { + if (StringUtil::has_suffix(StringUtil::tolower(filename), ".zip")) { if (PHYSFS_exists(fullpath.c_str())) { archives.push_back(fullpath); } } } - } + }); return archives; } @@ -782,14 +759,11 @@ AddonManager::scan_for_archives() const std::string AddonManager::scan_for_info(const std::string& archive_os_path) const { - std::unique_ptr - rc2(PHYSFS_enumerateFiles("/"), - PHYSFS_freeList); - for (char** j = rc2.get(); *j != nullptr; ++j) - { - if (StringUtil::has_suffix(*j, ".nfo")) + std::string nfoFilename = std::string(); + physfsutil::enumerate_files("/", [archive_os_path, &nfoFilename](const std::string& file) { + if (StringUtil::has_suffix(file, ".nfo")) { - std::string nfo_filename = FileSystem::join("/", *j); + std::string nfo_filename = FileSystem::join("/", file); // make sure it's in the current archive_os_path const char* realdir = PHYSFS_getRealDir(nfo_filename.c_str()); @@ -801,13 +775,13 @@ AddonManager::scan_for_info(const std::string& archive_os_path) const { if (realdir == archive_os_path) { - return nfo_filename; + nfoFilename = nfo_filename; } } } - } + }); - return std::string(); + return nfoFilename; } void @@ -855,7 +829,7 @@ AddonManager::add_installed_archive(const std::string& archive, const std::strin // save addon title and author on stack before std::move const std::string addon_title = addon->get_title(); const std::string addon_author = addon->get_author(); - m_installed_addons.push_back(std::move(addon)); + m_installed_addons[addon_id] = std::move(addon); if(user_install) { try @@ -901,10 +875,10 @@ AddonManager::add_installed_addons() } } -AddonManager::AddonList +AddonManager::AddonMap AddonManager::parse_addon_infos(const std::string& filename) const { - AddonList m_addons; + AddonMap m_addons; try { @@ -929,7 +903,7 @@ AddonManager::parse_addon_infos(const std::string& filename) const try { std::unique_ptr addon = Addon::parse(addon_node.get_mapping()); - m_addons.push_back(std::move(addon)); + m_addons[addon->get_id()] = std::move(addon); } catch(const std::exception& e) { diff --git a/src/addon/addon_manager.hpp b/src/addon/addon_manager.hpp index 4a8ac7164c8..7e3e9a0d9e9 100644 --- a/src/addon/addon_manager.hpp +++ b/src/addon/addon_manager.hpp @@ -20,6 +20,7 @@ #include #include +#include #include #include "addon/downloader.hpp" @@ -35,7 +36,7 @@ typedef std::string AddonId; class AddonManager final : public Currenton { public: - using AddonList = std::vector >; + using AddonMap = std::map >; private: Downloader m_downloader; @@ -45,8 +46,8 @@ class AddonManager final : public Currenton std::string m_repository_url; std::vector& m_addon_config; - AddonList m_installed_addons; - AddonList m_repository_addons; + AddonMap m_installed_addons; + AddonMap m_repository_addons; bool m_initialized; bool m_has_been_updated; @@ -108,7 +109,7 @@ class AddonManager final : public Currenton std::vector scan_for_archives() const; void add_installed_addons(); - AddonList parse_addon_infos(const std::string& filename) const; + AddonMap parse_addon_infos(const std::string& filename) const; /** add \a archive, given as physfs path, to the list of installed archives */ diff --git a/src/addon/downloader.cpp b/src/addon/downloader.cpp index 083eb1cf497..fd9058c637a 100644 --- a/src/addon/downloader.cpp +++ b/src/addon/downloader.cpp @@ -407,7 +407,7 @@ Downloader::~Downloader() #ifndef EMSCRIPTEN for (auto& transfer : m_transfers) { - curl_multi_remove_handle(m_multi_handle, transfer->get_curl_handle()); + curl_multi_remove_handle(m_multi_handle, transfer.second->get_curl_handle()); } #endif m_transfers.clear(); @@ -487,21 +487,17 @@ Downloader::download(const std::string& url, const std::string& filename) void Downloader::abort(TransferId id) { - auto it = std::find_if(m_transfers.begin(), m_transfers.end(), - [&id](const std::unique_ptr& rhs) - { - return id == rhs->get_id(); - }); + auto it = m_transfers.find(id); if (it == m_transfers.end()) { log_warning << "transfer not found: " << id << std::endl; } else { - TransferStatusPtr status = (*it)->get_status(); + TransferStatusPtr status = (it->second)->get_status(); #ifndef EMSCRIPTEN - curl_multi_remove_handle(m_multi_handle, (*it)->get_curl_handle()); + curl_multi_remove_handle(m_multi_handle, it->second->get_curl_handle()); #endif m_transfers.erase(it); @@ -549,12 +545,12 @@ Downloader::update() curl_multi_remove_handle(m_multi_handle, msg->easy_handle); auto it = std::find_if(m_transfers.begin(), m_transfers.end(), - [&msg](const std::unique_ptr& rhs) { - return rhs->get_curl_handle() == msg->easy_handle; + [&msg](const auto& rhs) { + return rhs.second->get_curl_handle() == msg->easy_handle; }); assert(it != m_transfers.end()); - TransferStatusPtr status = (*it)->get_status(); - status->error_msg = (*it)->get_error_buffer(); + TransferStatusPtr status = it->second->get_status(); + status->error_msg = it->second->get_error_buffer(); m_transfers.erase(it); if (resultfromcurl == CURLE_OK) @@ -612,44 +608,37 @@ Downloader::request_download(const std::string& url, const std::string& outfile) #ifndef EMSCRIPTEN curl_multi_add_handle(m_multi_handle, transfer->get_curl_handle()); #endif - m_transfers.push_back(std::move(transfer)); - return m_transfers.back()->get_status(); + auto transferId = transfer->get_id(); + m_transfers[transferId] = std::move(transfer); + return m_transfers[transferId]->get_status(); } #ifdef EMSCRIPTEN void Downloader::onDownloadProgress(int id, int loaded, int total) { - auto it = std::find_if(m_transfers.begin(), m_transfers.end(), - [&id](const std::unique_ptr& rhs) - { - return id == rhs->get_id(); - }); + auto it = m_transfers.find(id); if (it == m_transfers.end()) { log_warning << "transfer not found: " << id << std::endl; } else { - (*it)->on_progress(static_cast(loaded), static_cast(total), 0.0, 0.0); + (it->second)->on_progress(static_cast(loaded), static_cast(total), 0.0, 0.0); } } void Downloader::onDownloadFinished(int id) { - auto it = std::find_if(m_transfers.begin(), m_transfers.end(), - [&id](const std::unique_ptr& rhs) - { - return id == rhs->get_id(); - }); + auto it = m_transfers.find(id); if (it == m_transfers.end()) { log_warning << "transfer not found: " << id << std::endl; } else { - for (const auto& callback : (*it)->get_status()->callbacks) + for (const auto& callback : it->second->get_status()->callbacks) { try { @@ -666,18 +655,14 @@ Downloader::onDownloadFinished(int id) void Downloader::onDownloadError(int id) { - auto it = std::find_if(m_transfers.begin(), m_transfers.end(), - [&id](const std::unique_ptr& rhs) - { - return id == rhs->get_id(); - }); + auto it = m_transfers.find(id); if (it == m_transfers.end()) { log_warning << "transfer not found: " << id << std::endl; } else { - for (const auto& callback : (*it)->get_status()->callbacks) + for (const auto& callback : it->second->get_status()->callbacks) { try { @@ -694,18 +679,14 @@ Downloader::onDownloadError(int id) void Downloader::onDownloadAborted(int id) { - auto it = std::find_if(m_transfers.begin(), m_transfers.end(), - [&id](const std::unique_ptr& rhs) - { - return id == rhs->get_id(); - }); + auto it = m_transfers.find(id); if (it == m_transfers.end()) { log_warning << "transfer not found: " << id << std::endl; } else { - for (const auto& callback : (*it)->get_status()->callbacks) + for (const auto& callback : it->second->get_status()->callbacks) { try { diff --git a/src/addon/downloader.hpp b/src/addon/downloader.hpp index 181af539560..5e6bfb8de54 100644 --- a/src/addon/downloader.hpp +++ b/src/addon/downloader.hpp @@ -24,6 +24,7 @@ #include #endif #include +#include #include #include @@ -120,7 +121,7 @@ class Downloader final #ifndef EMSCRIPTEN CURLM* m_multi_handle; #endif - std::vector > m_transfers; + std::map > m_transfers; int m_next_transfer_id; float m_last_update_time; diff --git a/src/audio/stream_sound_source.cpp b/src/audio/stream_sound_source.cpp index ee8a5e3cc59..2297a81c2d9 100644 --- a/src/audio/stream_sound_source.cpp +++ b/src/audio/stream_sound_source.cpp @@ -138,8 +138,8 @@ StreamSoundSource::fillBufferAndQueue(ALuint buffer) std::unique_ptr bufferdata(new char[STREAMFRAGMENTSIZE]); size_t bytesread = 0; do { - bytesread += m_file->read(bufferdata.get() + bytesread, - STREAMFRAGMENTSIZE - bytesread); + bytesread += m_file->read(static_cast(bufferdata.get()) + bytesread, + STREAMFRAGMENTSIZE - bytesread); // end of sound file if (bytesread < STREAMFRAGMENTSIZE) { if (m_looping) diff --git a/src/badguy/badguy.cpp b/src/badguy/badguy.cpp index a59fe916861..2f56b9b4b9c 100644 --- a/src/badguy/badguy.cpp +++ b/src/badguy/badguy.cpp @@ -111,7 +111,7 @@ BadGuy::BadGuy(const ReaderMapping& reader, const std::string& sprite_name_, int { std::string dir_str = "auto"; reader.get("direction", dir_str); - m_start_dir = str2dir( dir_str ); + m_start_dir = string_to_dir(dir_str); m_dir = m_start_dir; reader.get("dead-script", m_dead_script); @@ -290,12 +290,6 @@ BadGuy::update(float dt_sec) m_on_ground_flag = false; } -Direction -BadGuy::str2dir(const std::string& dir_str) const -{ - return string_to_dir(dir_str); -} - void BadGuy::initialize() { @@ -1085,7 +1079,7 @@ BadGuy::after_editor_set() } else { - std::string action_str = dir_to_string(m_dir); + std::string action_str = dir_to_string(m_start_dir); if (m_sprite->has_action("editor-" + action_str)) { set_action("editor-" + action_str); diff --git a/src/badguy/badguy.hpp b/src/badguy/badguy.hpp index 754aae527d3..8010bc5f2dc 100644 --- a/src/badguy/badguy.hpp +++ b/src/badguy/badguy.hpp @@ -209,9 +209,6 @@ class BadGuy : public MovingSprite, pixels. Minimum value for height is 1 pixel */ bool might_fall(int height = 1) const; - /** Get Direction from String. */ - Direction str2dir(const std::string& dir_str) const; - /** Update on_ground_flag judging by solid collision @c hit. This gets called from the base implementation of collision_solid, so call this when overriding collision_solid's default diff --git a/src/badguy/crusher.cpp b/src/badguy/crusher.cpp index 51d2ba15c16..7193dd7b8be 100644 --- a/src/badguy/crusher.cpp +++ b/src/badguy/crusher.cpp @@ -96,7 +96,7 @@ Crusher::on_type_change(int old_type) HitResponse Crusher::collision(GameObject& other, const CollisionHit& hit) { - auto player = dynamic_cast(&other); + auto* player = dynamic_cast(&other); // If the other object is the player, and the collision is at the // bottom of the crusher, hurt the player. @@ -110,12 +110,12 @@ Crusher::collision(GameObject& other, const CollisionHit& hit) return FORCE_MOVE; } - auto badguy = dynamic_cast(&other); + auto* badguy = dynamic_cast(&other); if (badguy && m_state == CRUSHING) { badguy->kill_fall(); } - auto heavy_coin = dynamic_cast(&other); + const auto* heavy_coin = dynamic_cast(&other); if (heavy_coin) { return ABORT_MOVE; } diff --git a/src/badguy/dart.cpp b/src/badguy/dart.cpp index 59afdf8cc4a..1dbc1194ebd 100644 --- a/src/badguy/dart.cpp +++ b/src/badguy/dart.cpp @@ -39,13 +39,14 @@ Dart::Dart(const ReaderMapping& reader) : SoundManager::current()->preload("sounds/stomp.wav"); } -Dart::Dart(const Vector& pos, Direction d, const BadGuy* parent_ = nullptr) : - BadGuy(pos, d, "images/creatures/dart/dart.sprite"), +Dart::Dart(const Vector& pos, Direction d, const BadGuy* parent_, const std::string& sprite, Flip flip) : + BadGuy(pos, d, sprite), parent(parent_), sound_source() { m_physic.enable_gravity(false); m_countMe = false; + m_flip = flip; SoundManager::current()->preload(DART_SOUND); SoundManager::current()->preload("sounds/darthit.wav"); SoundManager::current()->preload("sounds/stomp.wav"); @@ -64,8 +65,10 @@ Dart::updatePointers(const GameObject* from_object, GameObject* to_object) void Dart::initialize() { - m_physic.set_velocity_x(m_dir == Direction::LEFT ? -::DART_SPEED : ::DART_SPEED); - set_action("flying", m_dir); + m_physic.set_velocity_x(m_dir == Direction::LEFT ? -::DART_SPEED : m_dir == Direction::RIGHT ? ::DART_SPEED : 0); + m_physic.set_velocity_y(m_dir == Direction::UP ? -::DART_SPEED : m_dir == Direction::DOWN ? ::DART_SPEED : 0); + set_action("flying", m_dir == Direction::UP ? Direction::DOWN : m_dir); + if (m_dir == Direction::UP) m_flip = VERTICAL_FLIP; } void @@ -143,11 +146,29 @@ Dart::play_looping_sounds() } } +void +Dart::after_editor_set() +{ + BadGuy::after_editor_set(); + if ((m_dir == Direction::UP && m_flip == NO_FLIP) || (m_dir == Direction::DOWN && m_flip == VERTICAL_FLIP)) + FlipLevelTransformer::transform_flip(m_flip); +} + void Dart::on_flip(float height) { BadGuy::on_flip(height); FlipLevelTransformer::transform_flip(m_flip); + if (m_dir == Direction::UP) + { + m_dir = Direction::DOWN; + m_physic.set_velocity_y(::DART_SPEED); + } + else if (m_dir == Direction::DOWN) + { + m_dir = Direction::UP; + m_physic.set_velocity_y(-::DART_SPEED); + } } /* EOF */ diff --git a/src/badguy/dart.hpp b/src/badguy/dart.hpp index 49e1263f748..489d441977a 100644 --- a/src/badguy/dart.hpp +++ b/src/badguy/dart.hpp @@ -26,7 +26,7 @@ class Dart final : public BadGuy { public: Dart(const ReaderMapping& reader); - Dart(const Vector& pos, Direction d, const BadGuy* parent); + Dart(const Vector& pos, Direction d, const BadGuy* parent, const std::string& sprite = "images/creatures/dart/dart.sprite", Flip flip = NO_FLIP); virtual void initialize() override; virtual void activate() override; @@ -51,6 +51,7 @@ class Dart final : public BadGuy virtual void stop_looping_sounds() override; virtual void play_looping_sounds() override; + virtual void after_editor_set() override; virtual void on_flip(float height) override; protected: diff --git a/src/badguy/darttrap.cpp b/src/badguy/darttrap.cpp index 97c5692c6eb..4b886a90629 100644 --- a/src/badguy/darttrap.cpp +++ b/src/badguy/darttrap.cpp @@ -26,64 +26,62 @@ #include "util/log.hpp" #include "util/reader_mapping.hpp" -namespace { -const float MUZZLE_Y = 25; /**< [px] muzzle y-offset from top */ -} - DartTrap::DartTrap(const ReaderMapping& reader) : BadGuy(reader, "images/creatures/darttrap/darttrap.sprite", LAYER_TILES-1), - enabled(true), - initial_delay(), - fire_delay(), - ammo(), - state(IDLE), - fire_timer() + m_enabled(true), + m_initial_delay(), + m_fire_delay(), + m_ammo(), + m_dart_sprite("images/creatures/dart/dart.sprite"), + m_state(IDLE), + m_fire_timer() { - reader.get("enabled", enabled, true); - reader.get("initial-delay", initial_delay, 0.0f); - reader.get("fire-delay", fire_delay, 2.0f); - reader.get("ammo", ammo, -1); + reader.get("enabled", m_enabled, true); + reader.get("initial-delay", m_initial_delay, 0.0f); + reader.get("fire-delay", m_fire_delay, 2.0f); + reader.get("ammo", m_ammo, -1); + reader.get("dart-sprite", m_dart_sprite, "images/creatures/dart/dart.sprite"); m_countMe = false; SoundManager::current()->preload("sounds/dartfire.wav"); if (m_start_dir == Direction::AUTO) { log_warning << "Setting a DartTrap's direction to AUTO is no good idea" << std::endl; } - state = IDLE; + m_state = IDLE; set_colgroup_active(COLGROUP_DISABLED); if (!Editor::is_active()) { - if (initial_delay == 0) initial_delay = 0.1f; + if (m_initial_delay == 0) m_initial_delay = 0.1f; } } void DartTrap::initialize() { - set_action("idle", m_dir); + set_action("idle", m_dir == Direction::UP ? Direction::DOWN : m_dir); + if (m_dir == Direction::UP) m_flip = VERTICAL_FLIP; } void DartTrap::activate() { - fire_timer.start(initial_delay); + m_fire_timer.start(m_initial_delay); } HitResponse -DartTrap::collision_player(Player& , const CollisionHit& ) +DartTrap::collision_player(Player&, const CollisionHit& ) { return ABORT_MOVE; } void -DartTrap::active_update(float ) +DartTrap::active_update(float) { - if (!enabled) { - return; - } - switch (state) { + if (!m_enabled) return; + + switch (m_state) { case IDLE: - if ((ammo != 0) && (fire_timer.check())) { - if (ammo > 0) ammo--; + if ((m_ammo != 0) && (m_fire_timer.check())) { + if (m_ammo > 0) m_ammo--; load(); - fire_timer.start(fire_delay); + m_fire_timer.start(m_fire_delay); } break; @@ -101,25 +99,41 @@ DartTrap::active_update(float ) void DartTrap::load() { - state = LOADING; - set_action("loading", m_dir, 1); + m_state = LOADING; + set_action("loading", m_dir == Direction::UP ? Direction::DOWN : m_dir, 1); } void DartTrap::fire() { - float px = get_pos().x; - if (m_dir == Direction::RIGHT) px += 5; - float py = get_pos().y; - if (m_flip == NO_FLIP) - py += MUZZLE_Y; - else - py += (m_col.m_bbox.get_height() - MUZZLE_Y - 7.0f); - SoundManager::current()->play("sounds/dartfire.wav", get_pos()); - Sector::get().add(Vector(px, py), m_dir, this); - state = IDLE; - set_action("idle", m_dir); + Dart &dart = Sector::get().add(Vector(0.f, 0.f), m_dir, this, m_dart_sprite, m_flip); + + Vector pos; + switch (m_dir) + { + case Direction::RIGHT: + pos = Vector(get_pos().x, + get_pos().y + m_col.m_bbox.get_height() / 2 - dart.get_bbox().get_height() / 2); + break; + case Direction::UP: + pos = Vector(get_pos().x + m_col.m_bbox.get_width() / 2 - dart.get_bbox().get_width() / 2, + get_pos().y + m_col.m_bbox.get_height() - dart.get_bbox().get_height()); + break; + case Direction::DOWN: + pos = Vector(get_pos().x + m_col.m_bbox.get_width() / 2 - dart.get_bbox().get_width() / 2, + get_pos().y); + break; + default: + pos = Vector(get_pos().x + m_col.m_bbox.get_width() - dart.get_bbox().get_width(), + get_pos().y + m_col.m_bbox.get_height() / 2 - dart.get_bbox().get_height() / 2); + break; + } + + dart.set_pos(pos); + + m_state = IDLE; + set_action("idle", m_dir == Direction::UP ? Direction::DOWN : m_dir); } ObjectSettings @@ -127,21 +141,34 @@ DartTrap::get_settings() { ObjectSettings result = BadGuy::get_settings(); - result.add_float(_("Initial delay"), &initial_delay, "initial-delay"); - result.add_bool(_("Enabled"), &enabled, "enabled", true); - result.add_float(_("Fire delay"), &fire_delay, "fire-delay"); - result.add_int(_("Ammo"), &ammo, "ammo"); + result.add_float(_("Initial delay"), &m_initial_delay, "initial-delay"); + result.add_bool(_("Enabled"), &m_enabled, "enabled", true); + result.add_float(_("Fire delay"), &m_fire_delay, "fire-delay"); + result.add_int(_("Ammo"), &m_ammo, "ammo"); + result.add_sprite(_("Dart sprite"), &m_dart_sprite, "dart-sprite", "images/creatures/dart/dart.sprite"); - result.reorder({"initial-delay", "fire-delay", "ammo", "direction", "x", "y"}); + result.reorder({"initial-delay", "fire-delay", "ammo", "direction", "x", "y", "dart-sprite"}); return result; } +void +DartTrap::after_editor_set() +{ + BadGuy::after_editor_set(); + if ((m_dir == Direction::UP && m_flip == NO_FLIP) || (m_dir == Direction::DOWN && m_flip == VERTICAL_FLIP)) + FlipLevelTransformer::transform_flip(m_flip); +} + void DartTrap::on_flip(float height) { BadGuy::on_flip(height); FlipLevelTransformer::transform_flip(m_flip); + if (m_dir == Direction::UP) + m_dir = Direction::DOWN; + else if (m_dir == Direction::DOWN) + m_dir = Direction::UP; } /* EOF */ diff --git a/src/badguy/darttrap.hpp b/src/badguy/darttrap.hpp index cae9e83dcde..a5d31e4cf5e 100644 --- a/src/badguy/darttrap.hpp +++ b/src/badguy/darttrap.hpp @@ -37,6 +37,7 @@ class DartTrap final : public BadGuy virtual ObjectSettings get_settings() override; + virtual void after_editor_set() override; virtual void on_flip(float height) override; protected: @@ -44,17 +45,18 @@ class DartTrap final : public BadGuy IDLE, LOADING }; - void load(); /**< load a shot */ - void fire(); /**< fire a shot */ + void load(); + void fire(); private: - bool enabled; /** Is DartTrap enabled **/ - float initial_delay; /**< time to wait before firing first shot */ - float fire_delay; /**< reload time */ - int ammo; /**< ammo left (-1 means unlimited) */ + bool m_enabled; + float m_initial_delay; + float m_fire_delay; + int m_ammo; // ammo left (-1 means unlimited) + std::string m_dart_sprite; - State state; /**< current state */ - Timer fire_timer; /**< time until new shot is fired */ + State m_state; + Timer m_fire_timer; private: DartTrap(const DartTrap&) = delete; diff --git a/src/badguy/dispenser.cpp b/src/badguy/dispenser.cpp index 7e6ab3e18d4..d6d1241c442 100644 --- a/src/badguy/dispenser.cpp +++ b/src/badguy/dispenser.cpp @@ -208,7 +208,7 @@ Dispenser::launch_object() { throw std::runtime_error("Creating " + object->get_class_name() + " object failed."); } - auto moving_object = dynamic_cast(game_object.get()); + auto moving_object = static_cast(game_object.get()); Rectf object_bbox = moving_object->get_bbox(); Vector spawnpoint(0.0f, 0.0f); @@ -255,7 +255,7 @@ Dispenser::launch_object() if (obj_badguy) // The object is a badguy { - auto badguy = dynamic_cast(moving_object); + auto badguy = static_cast(moving_object); /* We don't want to count dispensed badguys in level stats */ badguy->m_countMe = false; diff --git a/src/badguy/fish_jumping.cpp b/src/badguy/fish_jumping.cpp index fa81a688324..58f08ca764b 100644 --- a/src/badguy/fish_jumping.cpp +++ b/src/badguy/fish_jumping.cpp @@ -81,7 +81,7 @@ FishJumping::collision_tile(uint32_t tile_attributes) if (!m_frozen) start_waiting(); m_col.set_movement(Vector(0, 0)); - SoundManager::current()->play("sounds/splash.ogg", get_pos()); + SoundManager::current()->play("sounds/splash.wav", get_pos()); } } if ((!(tile_attributes & Tile::WATER) || m_frozen) && (tile_attributes & Tile::HURTS)) { diff --git a/src/badguy/flame.cpp b/src/badguy/flame.cpp index edbb7541621..69b3527ecda 100644 --- a/src/badguy/flame.cpp +++ b/src/badguy/flame.cpp @@ -45,9 +45,6 @@ Flame::Flame(const ReaderMapping& reader, const std::string& sprite) : m_countMe = false; SoundManager::current()->preload(FLAME_SOUND); - reader.get("sprite", m_sprite_name, m_sprite_name.c_str()); - m_sprite = SpriteManager::current()->create(m_sprite_name); - set_colgroup_active(COLGROUP_TOUCHABLE); m_lightsprite->set_color(Color(0.21f, 0.13f, 0.08f)); diff --git a/src/badguy/goldbomb.cpp b/src/badguy/goldbomb.cpp index ccb78fc5313..72866d516c2 100644 --- a/src/badguy/goldbomb.cpp +++ b/src/badguy/goldbomb.cpp @@ -41,16 +41,6 @@ GoldBomb::GoldBomb(const ReaderMapping& reader) : //Prevent stutter when Tux jumps on Gold Bomb SoundManager::current()->preload("sounds/explosion.wav"); - //Check if we need another sprite - if ( !reader.get( "sprite", m_sprite_name ) ){ - return; - } - if (m_sprite_name.empty()) { - m_sprite_name = "images/creatures/gold_bomb/gold_bomb.sprite"; - return; - } - //Replace sprite - m_sprite = SpriteManager::current()->create( m_sprite_name ); m_exploding_sprite->set_action("default", 1); } diff --git a/src/badguy/haywire.cpp b/src/badguy/haywire.cpp index 2ebd7b798e7..cdcfaa954b5 100644 --- a/src/badguy/haywire.cpp +++ b/src/badguy/haywire.cpp @@ -56,17 +56,6 @@ Haywire::Haywire(const ReaderMapping& reader) : //Prevent stutter when Tux jumps on Mr Bomb SoundManager::current()->preload("sounds/explosion.wav"); - - //Check if we need another sprite - if ( !reader.get( "sprite", m_sprite_name ) ){ - return; - } - if (m_sprite_name.empty()) { - m_sprite_name = "images/creatures/haywire/haywire.sprite"; - return; - } - //Replace sprite - m_sprite = SpriteManager::current()->create( m_sprite_name ); } Direction diff --git a/src/badguy/kamikazesnowball.cpp b/src/badguy/kamikazesnowball.cpp index 307b0a2a3c1..ab3bb98f36d 100644 --- a/src/badguy/kamikazesnowball.cpp +++ b/src/badguy/kamikazesnowball.cpp @@ -17,8 +17,6 @@ #include "badguy/kamikazesnowball.hpp" #include "audio/sound_manager.hpp" -#include "sprite/sprite.hpp" -#include "sprite/sprite_manager.hpp" namespace{ static const float KAMIKAZE_SPEED = 200; @@ -26,8 +24,8 @@ namespace{ const std::string SPLAT_SOUND = "sounds/splat.wav"; } -KamikazeSnowball::KamikazeSnowball(const ReaderMapping& reader) : - BadGuy(reader, "images/creatures/snowball/kamikaze-snowball.sprite") +KamikazeSnowball::KamikazeSnowball(const ReaderMapping& reader, const std::string& sprite_name) : + BadGuy(reader, sprite_name) { SoundManager::current()->preload(SPLAT_SOUND); set_action (m_dir == Direction::LEFT ? "left" : "right", /* loops = */ -1); @@ -100,10 +98,8 @@ KamikazeSnowball::collision_player(Player& player, const CollisionHit& hit) } LeafShot::LeafShot(const ReaderMapping& reader) : - KamikazeSnowball(reader) + KamikazeSnowball(reader, "images/creatures/leafshot/leafshot.sprite") { - m_sprite_name = "images/creatures/leafshot/leafshot.sprite"; - m_sprite = SpriteManager::current()->create(m_sprite_name); } void diff --git a/src/badguy/kamikazesnowball.hpp b/src/badguy/kamikazesnowball.hpp index 864674c0359..33a3907f301 100644 --- a/src/badguy/kamikazesnowball.hpp +++ b/src/badguy/kamikazesnowball.hpp @@ -24,7 +24,8 @@ class KamikazeSnowball : public BadGuy { public: - KamikazeSnowball(const ReaderMapping& reader); + KamikazeSnowball(const ReaderMapping& reader, + const std::string& sprite_name = "images/creatures/snowball/kamikaze-snowball.sprite"); virtual void initialize() override; virtual void collision_solid(const CollisionHit& hit) override; diff --git a/src/badguy/mrbomb.cpp b/src/badguy/mrbomb.cpp index 255b5229b7b..03e309456ae 100644 --- a/src/badguy/mrbomb.cpp +++ b/src/badguy/mrbomb.cpp @@ -36,17 +36,6 @@ MrBomb::MrBomb(const ReaderMapping& reader) : //Prevent stutter when Tux jumps on Mr Bomb SoundManager::current()->preload("sounds/explosion.wav"); - - //Check if we need another sprite - if ( !reader.get( "sprite", m_sprite_name ) ){ - return; - } - if (m_sprite_name.empty()) { - m_sprite_name = "images/creatures/mr_bomb/mr_bomb.sprite"; - return; - } - //Replace sprite - m_sprite = SpriteManager::current()->create( m_sprite_name ); } HitResponse diff --git a/src/badguy/mriceblock.cpp b/src/badguy/mriceblock.cpp index aff55f08c53..d8792fb6c5e 100644 --- a/src/badguy/mriceblock.cpp +++ b/src/badguy/mriceblock.cpp @@ -30,8 +30,8 @@ namespace { const float NOKICK_TIME = 0.1f; } -MrIceBlock::MrIceBlock(const ReaderMapping& reader) : - WalkingBadguy(reader, "images/creatures/iceblock/iceblock.sprite", "left", "right"), +MrIceBlock::MrIceBlock(const ReaderMapping& reader, const std::string& sprite_name) : + WalkingBadguy(reader, sprite_name, "left", "right"), ice_state(ICESTATE_NORMAL), nokick_timer(), flat_timer(), diff --git a/src/badguy/mriceblock.hpp b/src/badguy/mriceblock.hpp index 53d6047e0aa..200e316bc31 100644 --- a/src/badguy/mriceblock.hpp +++ b/src/badguy/mriceblock.hpp @@ -22,7 +22,8 @@ class MrIceBlock : public WalkingBadguy { public: - MrIceBlock(const ReaderMapping& reader); + MrIceBlock(const ReaderMapping& reader, + const std::string& sprite_name = "images/creatures/iceblock/iceblock.sprite"); virtual void initialize() override; virtual HitResponse collision(GameObject& object, const CollisionHit& hit) override; diff --git a/src/badguy/owl.cpp b/src/badguy/owl.cpp index 5f9ff56383c..2eeb3de7320 100644 --- a/src/badguy/owl.cpp +++ b/src/badguy/owl.cpp @@ -110,7 +110,9 @@ Owl::active_update (float dt_sec) if (carried_object != nullptr) { if (!is_above_player ()) { Vector obj_pos = get_anchor_pos(m_col.m_bbox, ANCHOR_BOTTOM); - obj_pos.x -= 16.f; /* FIXME: Actually do use the half width of the carried object here. */ + auto obj = dynamic_cast(carried_object); + auto verticalOffset = obj != nullptr ? obj->get_bbox().get_width() / 2.f : 16.f; + obj_pos.x -= verticalOffset; obj_pos.y += 3.f; /* Move a little away from the hitbox (the body). Looks nicer. */ //To drop enemie before leave the screen diff --git a/src/badguy/short_fuse.cpp b/src/badguy/short_fuse.cpp index 3c17cb56808..165f1f920b3 100644 --- a/src/badguy/short_fuse.cpp +++ b/src/badguy/short_fuse.cpp @@ -31,17 +31,6 @@ ShortFuse::ShortFuse(const ReaderMapping& reader) : walk_speed = 100; max_drop_height = 16; - //Check if we need another sprite - if ( !reader.get( "sprite", m_sprite_name ) ){ - return; - } - if (m_sprite_name.empty()) { - m_sprite_name = "images/creatures/short_fuse/short_fuse.sprite"; - return; - } - //Replace sprite - m_sprite = SpriteManager::current()->create( m_sprite_name ); - SoundManager::current()->preload("sounds/firecracker.ogg"); } diff --git a/src/badguy/smartblock.cpp b/src/badguy/smartblock.cpp index f24265df905..5c6681e542c 100644 --- a/src/badguy/smartblock.cpp +++ b/src/badguy/smartblock.cpp @@ -16,19 +16,10 @@ #include "badguy/smartblock.hpp" -#include "sprite/sprite_manager.hpp" -#include "util/reader_mapping.hpp" - SmartBlock::SmartBlock(const ReaderMapping& reader) : - MrIceBlock(reader) + MrIceBlock(reader, "images/creatures/iceblock/smart_block.sprite") { max_drop_height = 16; - m_default_sprite_name = "images/creatures/iceblock/smart_block.sprite"; - - if (!reader.get("sprite", m_sprite_name)) { - m_sprite_name = m_default_sprite_name; - } - m_sprite = SpriteManager::current()->create(m_sprite_name); } /* EOF */ diff --git a/src/collision/collision_object.cpp b/src/collision/collision_object.cpp index 8dcfcc45950..25b7ab8a0e3 100644 --- a/src/collision/collision_object.cpp +++ b/src/collision/collision_object.cpp @@ -81,6 +81,7 @@ CollisionObject::clear_bottom_collision_list() void CollisionObject::propagate_movement(const Vector& movement) { for (CollisionObject* other_object : m_objects_hit_bottom) { + if (other_object->get_group() == COLGROUP_STATIC) continue; m_ground_movement_manager->register_movement(*this, *other_object, movement); other_object->propagate_movement(movement); } diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index c90d6cc3c05..9e3c4a746c9 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -333,6 +333,7 @@ Editor::test_level(const std::optional>& test_pos m_autosave_levelfile = FileSystem::join(directory, backup_filename); m_level->save(m_autosave_levelfile); m_time_since_last_save = 0.f; + m_leveltested = true; if (!m_level->is_worldmap()) { @@ -340,10 +341,8 @@ Editor::test_level(const std::optional>& test_pos } else { - GameManager::current()->start_worldmap(*current_world, "", m_autosave_levelfile); + GameManager::current()->start_worldmap(*current_world, m_autosave_levelfile, test_pos); } - - m_leveltested = true; } void @@ -405,13 +404,19 @@ Editor::update_keyboard(const Controller& controller) return; } - - if (!MenuManager::instance().has_dialog()) + if (MenuManager::instance().current_menu() == nullptr) { if (controller.pressed(Control::ESCAPE)) { esc_press(); return; } + if (controller.pressed(Control::DEBUG_MENU) && g_config->developer_mode) + { + m_enabled = false; + m_overlay_widget->delete_markers(); + MenuManager::instance().set_menu(MenuStorage::DEBUG_MENU); + return; + } if (controller.hold(Control::LEFT)) { scroll({ -m_scroll_speed, 0.0f }); } @@ -683,7 +688,6 @@ Editor::setup() m_enabled = true; m_toolbox_widget->update_mouse_icon(); } - } void @@ -742,8 +746,6 @@ Editor::event(const SDL_Event& ev) } } - - if (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_F6) { Compositor::s_render_lighting = !Compositor::s_render_lighting; return; diff --git a/src/editor/overlay_widget.cpp b/src/editor/overlay_widget.cpp index d41ffc01cc8..636eff6cbf3 100644 --- a/src/editor/overlay_widget.cpp +++ b/src/editor/overlay_widget.cpp @@ -27,7 +27,6 @@ #include "editor/tile_selection.hpp" #include "editor/tip.hpp" #include "editor/util.hpp" -#include "editor/worldmap_objects.hpp" #include "gui/menu.hpp" #include "gui/menu_manager.hpp" #include "math/bezier.hpp" @@ -44,6 +43,7 @@ #include "video/renderer.hpp" #include "video/video_system.hpp" #include "video/viewport.hpp" +#include "worldmap/worldmap_object.hpp" namespace { @@ -84,12 +84,14 @@ EditorOverlayWidget::~EditorOverlayWidget() void EditorOverlayWidget::update(float dt_sec) { - if (m_hovered_object && !m_hovered_object->is_valid()) { + if (m_hovered_object && !m_hovered_object->is_valid()) + { m_hovered_object = nullptr; m_object_tip = nullptr; } - if (m_selected_object && !m_selected_object->is_valid()) { + if (m_selected_object && !m_selected_object->is_valid()) + { delete_markers(); } } @@ -109,11 +111,11 @@ EditorOverlayWidget::delete_markers() { auto* sector = m_editor.get_sector(); - if (m_selected_object && m_selected_object->is_valid()) { + if (m_selected_object && m_selected_object->is_valid()) m_selected_object->editor_deselect(); - } - for (auto& marker : sector->get_objects_by_type()) { + for (auto& marker : sector->get_objects_by_type()) + { marker.remove_me(); } @@ -127,18 +129,24 @@ EditorOverlayWidget::drag_rect() const { int start_x, start_y, end_x, end_y; - if (m_drag_start.x < m_sector_pos.x) { + if (m_drag_start.x < m_sector_pos.x) + { start_x = static_cast(m_drag_start.x); end_x = static_cast(m_sector_pos.x); - } else { + } + else + { start_x = static_cast(m_sector_pos.x); end_x = static_cast(m_drag_start.x); } - if (m_drag_start.y < m_sector_pos.y) { + if (m_drag_start.y < m_sector_pos.y) + { start_y = static_cast(m_drag_start.y); end_y = static_cast(m_sector_pos.y); - } else { + } + else + { start_y = static_cast(m_sector_pos.y); end_y = static_cast(m_drag_start.y); } @@ -153,16 +161,7 @@ void EditorOverlayWidget::input_tile(const Vector& pos, uint32_t tile) { auto tilemap = m_editor.get_selected_tilemap(); - if (!tilemap) { - return; - } - - if ( pos.x < 0 || - pos.y < 0 || - pos.x >= static_cast(tilemap->get_width()) || - pos.y >= static_cast(tilemap->get_height())) { - return; - } + if (!tilemap || !is_position_inside_tilemap(tilemap, pos)) return; tilemap->save_state(); tilemap->change(static_cast(pos.x), static_cast(pos.y), tile); @@ -172,16 +171,7 @@ void EditorOverlayWidget::autotile(const Vector& pos, uint32_t tile) { auto tilemap = m_editor.get_selected_tilemap(); - if (!tilemap) { - return; - } - - if ( pos.x < 0 || - pos.y < 0 || - pos.x >= static_cast(tilemap->get_width()) || - pos.y >= static_cast(tilemap->get_height())) { - return; - } + if (!tilemap || !is_position_inside_tilemap(tilemap, pos)) return; tilemap->save_state(); tilemap->autotile(static_cast(pos.x), static_cast(pos.y), tile); @@ -211,16 +201,7 @@ EditorOverlayWidget::autotile_corner(const Vector& pos, uint32_t tile, TileMap::AutotileCornerOperation op) { auto tilemap = m_editor.get_selected_tilemap(); - if (!tilemap) { - return; - } - - if ( pos.x < 0 || - pos.y < 0 || - pos.x >= static_cast(tilemap->get_width()) || - pos.y >= static_cast(tilemap->get_height())) { - return; - } + if (!tilemap || !is_position_inside_tilemap(tilemap, pos)) return; tilemap->save_state(); tilemap->autotile_corner(static_cast(pos.x), static_cast(pos.y), tile, op); @@ -254,23 +235,33 @@ EditorOverlayWidget::put_tile(const Vector& target_tile) Vector hovered_corner = target_tile + Vector(0.5f, 0.5f); auto tiles = m_editor.get_tiles(); Vector add_tile(0.0f, 0.0f); - for (add_tile.x = static_cast(tiles->m_width) - 1.0f; add_tile.x >= 0.0f; add_tile.x--) { - for (add_tile.y = static_cast(tiles->m_height) - 1.0f; add_tile.y >= 0; add_tile.y--) { + for (add_tile.x = static_cast(tiles->m_width) - 1.0f; add_tile.x >= 0.0f; add_tile.x--) + { + for (add_tile.y = static_cast(tiles->m_height) - 1.0f; add_tile.y >= 0; add_tile.y--) + { uint32_t tile = tiles->pos(static_cast(add_tile.x), static_cast(add_tile.y)); auto tilemap = m_editor.get_selected_tilemap(); - if (g_config->editor_autotile_mode && ((tilemap && tilemap->get_autotileset(tile)) || tile == 0)) { - if (tile == 0) { + if (g_config->editor_autotile_mode && ((tilemap && tilemap->get_autotileset(tile)) || tile == 0)) + { + if (tile == 0) + { tilemap->autotile_erase(target_tile + add_tile, hovered_corner + add_tile); - } else if (tilemap->get_autotileset(tile)->is_corner()) { + } + else if (tilemap->get_autotileset(tile)->is_corner()) + { input_autotile_corner(hovered_corner + add_tile, tile, target_tile + add_tile); - } else { + } + else + { input_autotile(target_tile + add_tile, tile); } - } else { + } + else + { input_tile(target_tile + add_tile, tile); } @@ -283,15 +274,16 @@ namespace { // segment from pos1 to pos2 (similarly to a line drawing algorithm) std::vector rasterize_line_segment(Vector pos1, Vector pos2) { - if (pos1 == pos2) - return std::vector {pos1}; + if (pos1 == pos2) return std::vector {pos1}; // An integer position (x, y) contains all floating point vectors in // [x, x+1) x [y, y+1) std::vector positions; Vector diff = pos2 - pos1; - if (fabsf(diff.x) > fabsf(diff.y)) { + if (fabsf(diff.x) > fabsf(diff.y)) + { // Go along X, from left to right - if (diff.x < 0) { + if (diff.x < 0) + { Vector tmp = pos1; pos1 = pos2; pos2 = tmp; @@ -301,10 +293,12 @@ namespace { float y_step = diff.y / diff.x; // The x coordinate of the first vertical grid line right of pos1 float x_first_gridline = floorf(pos1.x + 1.0f); - for (float x = x_first_gridline; x < pos2.x; ++x) { + for (float x = x_first_gridline; x < pos2.x; ++x) + { // The y coordinate where our line intersects the vertical grid line float y = pos1.y + (x - pos1.x) * y_step; - if (floorf(y) != floorf(y_prev)) { + if (floorf(y) != floorf(y_prev)) + { // The current position is one horizontal grid line higher than // the previous one, // so add the position left to the current vertical grid line @@ -314,14 +308,18 @@ namespace { // Add the position right to the current vertical grid line positions.emplace_back(Vector(x + 0.5f, y)); } - if (x_first_gridline > pos2.x && floorf(pos2.y) != floorf(pos1.y)) { + if (x_first_gridline > pos2.x && floorf(pos2.y) != floorf(pos1.y)) + { // Special case: a single horizontal grid line is crossed with an acute // angle but no vertical grid line, so the for loop was skipped positions.emplace_back(pos2); } - } else { + } + else + { // Go along Y, from top to bottom - if (diff.y < 0) { + if (diff.y < 0) + { Vector tmp = pos1; pos1 = pos2; pos2 = tmp; @@ -330,20 +328,23 @@ namespace { float x_prev = pos1.x; float x_step = diff.x / diff.y; float y_first_gridline = floorf(pos1.y + 1.0f); - for (float y = y_first_gridline; y < pos2.y; ++y) { + for (float y = y_first_gridline; y < pos2.y; ++y) + { float x = pos1.x + (y - pos1.y) * x_step; - if (floorf(x) != floorf(x_prev)) { + if (floorf(x) != floorf(x_prev)) + { positions.emplace_back(Vector(x, y - 0.5f)); x_prev = x; } positions.emplace_back(Vector(x, y + 0.5f)); } - if (y_first_gridline > pos2.y && floorf(pos2.x) != floorf(pos1.x)) { + if (y_first_gridline > pos2.y && floorf(pos2.x) != floorf(pos1.x)) + { positions.emplace_back(pos2); } } return positions; - }; + } } // namespace void @@ -353,7 +354,8 @@ EditorOverlayWidget::put_next_tiles() int expired_ms = static_cast(std::chrono::duration_cast< std::chrono::milliseconds>(time_now - m_time_prev_put_tile).count()); m_time_prev_put_tile = time_now; - if (expired_ms > 70) { + if (expired_ms > 70) + { // Avoid drawing lines when the user has hold the left mouse button for some // time while not putting a tile put_tile(m_hovered_tile); @@ -363,7 +365,8 @@ EditorOverlayWidget::put_next_tiles() // Interpolate on a sub-grid with twice width and height because autotiling // needs to know the closest corner for (const Vector &pos : rasterize_line_segment(m_hovered_tile_prev * 2.0f, - m_hovered_tile * 2.0f)) { + m_hovered_tile * 2.0f)) + { put_tile(pos * 0.5f); } m_hovered_tile_prev = m_hovered_tile; @@ -382,9 +385,11 @@ EditorOverlayWidget::preview_rectangle() m_rectangle_preview->m_width = static_cast(dr.get_width()) + 1; m_rectangle_preview->m_height = static_cast(dr.get_height()) + 1; int y_ = sgn_y ? 0 : static_cast(-dr.get_height()); - for (int y = static_cast(dr.get_top()); y <= static_cast(dr.get_bottom()); y++, y_++) { + for (int y = static_cast(dr.get_top()); y <= static_cast(dr.get_bottom()); y++, y_++) + { int x_ = sgn_x ? 0 : static_cast(-dr.get_width()); - for (int x = static_cast(dr.get_left()); x <= static_cast(dr.get_right()); x++, x_++) { + for (int x = static_cast(dr.get_left()); x <= static_cast(dr.get_right()); x++, x_++) + { m_rectangle_preview->m_tiles.push_back(m_editor.get_tiles()->pos(x_, y_)); } } @@ -401,14 +406,15 @@ EditorOverlayWidget::draw_rectangle() bool sgn_y = m_drag_start.y < m_sector_pos.y; int x_ = sgn_x ? 0 : static_cast(-dr.get_width()); - for (int x = static_cast(dr.get_left()); x <= static_cast(dr.get_right()); x++, x_++) { + for (int x = static_cast(dr.get_left()); x <= static_cast(dr.get_right()); x++, x_++) + { int y_ = sgn_y ? 0 : static_cast(-dr.get_height()); - for (int y = static_cast(dr.get_top()); y <= static_cast(dr.get_bottom()); y++, y_++) { - if (g_config->editor_autotile_mode) { - input_autotile( Vector(static_cast(x), static_cast(y)), m_editor.get_tiles()->pos(x_, y_) ); - } else { - input_tile( Vector(static_cast(x), static_cast(y)), m_editor.get_tiles()->pos(x_, y_) ); - } + for (int y = static_cast(dr.get_top()); y <= static_cast(dr.get_bottom()); y++, y_++) + { + if (g_config->editor_autotile_mode) + input_autotile(Vector(static_cast(x), static_cast(y)), m_editor.get_tiles()->pos(x_, y_)); + else + input_tile(Vector(static_cast(x), static_cast(y)), m_editor.get_tiles()->pos(x_, y_)); } } } @@ -418,12 +424,15 @@ EditorOverlayWidget::check_tiles_for_fill(uint32_t replace_tile, uint32_t target_tile, uint32_t third_tile) const { - if (g_config->editor_autotile_mode) { + if (g_config->editor_autotile_mode) + { return m_editor.get_tileset()->get_autotileset_from_tile(replace_tile) == m_editor.get_tileset()->get_autotileset_from_tile(target_tile) && m_editor.get_tileset()->get_autotileset_from_tile(replace_tile) != m_editor.get_tileset()->get_autotileset_from_tile(third_tile); - } else { + } + else + { return replace_tile == target_tile && replace_tile != third_tile; } } @@ -433,14 +442,13 @@ EditorOverlayWidget::fill() { auto tiles = m_editor.get_tiles(); auto tilemap = m_editor.get_selected_tilemap(); - if (!tilemap) { - return; - } + if (!tilemap) return; // The tile that is going to be replaced: - Uint32 replace_tile = tilemap->get_tile_id(static_cast(m_hovered_tile.x), static_cast(m_hovered_tile.y)); + uint32_t replace_tile = tilemap->get_tile_id(static_cast(m_hovered_tile.x), static_cast(m_hovered_tile.y)); - if (replace_tile == tiles->pos(0, 0)) { + if (replace_tile == tiles->pos(0, 0)) + { // Replacing by the same tiles shouldn't do anything. return; } @@ -450,9 +458,11 @@ EditorOverlayWidget::fill() pos_stack.push_back(m_hovered_tile); // Passing recursively trough all tiles to be replaced... - while (pos_stack.size()) { + while (pos_stack.size()) + { - if (pos_stack.size() > 1000000) { + if (pos_stack.size() > 1000000) + { log_warning << "More than 1'000'000 tiles in stack to fill, STOP" << std::endl; return; } @@ -461,10 +471,7 @@ EditorOverlayWidget::fill() Vector tpos = pos - m_hovered_tile; // Tests for being inside tilemap: - if ( pos.x < 0 || - pos.y < 0 || - pos.x >= static_cast(tilemap->get_width()) || - pos.y >= static_cast(tilemap->get_height())) + if (!is_position_inside_tilemap(tilemap, pos)) { pos_stack.pop_back(); continue; @@ -477,10 +484,12 @@ EditorOverlayWidget::fill() // Going left... pos_ = pos + Vector(-1, 0); - if (pos_.x >= 0) { + if (pos_.x >= 0) + { if (check_tiles_for_fill(replace_tile, tilemap->get_tile_id(static_cast(pos_.x), static_cast(pos_.y)), - tiles->pos(static_cast(tpos.x - 1), static_cast(tpos.y)))) { + tiles->pos(static_cast(tpos.x - 1), static_cast(tpos.y)))) + { pos_stack.push_back( pos_ ); continue; } @@ -488,10 +497,12 @@ EditorOverlayWidget::fill() // Going right... pos_ = pos + Vector(1, 0); - if (pos_.x < static_cast(tilemap->get_width())) { + if (pos_.x < static_cast(tilemap->get_width())) + { if (check_tiles_for_fill(replace_tile, tilemap->get_tile_id(static_cast(pos_.x), static_cast(pos_.y)), - tiles->pos(static_cast(tpos.x + 1), static_cast(tpos.y)))) { + tiles->pos(static_cast(tpos.x + 1), static_cast(tpos.y)))) + { pos_stack.push_back( pos_ ); continue; } @@ -499,10 +510,12 @@ EditorOverlayWidget::fill() // Going up... pos_ = pos + Vector(0, -1); - if (pos_.y >= 0) { + if (pos_.y >= 0) + { if (check_tiles_for_fill(replace_tile, tilemap->get_tile_id(static_cast(pos_.x), static_cast(pos_.y)), - tiles->pos(static_cast(tpos.x), static_cast(tpos.y - 1)))) { + tiles->pos(static_cast(tpos.x), static_cast(tpos.y - 1)))) + { pos_stack.push_back( pos_ ); continue; } @@ -510,25 +523,51 @@ EditorOverlayWidget::fill() // Going down... pos_ = pos + Vector(0, 1); - if (pos_.y < static_cast(tilemap->get_height())) { + if (pos_.y < static_cast(tilemap->get_height())) + { if (check_tiles_for_fill(replace_tile, tilemap->get_tile_id(static_cast(pos_.x), static_cast(pos_.y)), - tiles->pos(static_cast(tpos.x), static_cast(tpos.y + 1)))) { + tiles->pos(static_cast(tpos.x), static_cast(tpos.y + 1)))) + { pos_stack.push_back( pos_ ); continue; } } // Autotile happens after directional detection (because of borders; see snow tileset) - if (g_config->editor_autotile_mode) { + if (g_config->editor_autotile_mode) input_autotile(pos, tiles->pos(static_cast(tpos.x), static_cast(tpos.y))); - } // When tiles on each side are already filled or occupied by another tiles, it ends. pos_stack.pop_back(); } } +void +EditorOverlayWidget::replace() +{ + auto tilemap = m_editor.get_selected_tilemap(); + uint32_t replace_tile = tilemap->get_tile_id(static_cast(m_hovered_tile.x), static_cast(m_hovered_tile.y)); + + // Don't do anything if the old and new tiles are the same tile. + if (m_editor.get_tiles()->m_width == 1 && m_editor.get_tiles()->m_height == 1 && replace_tile == m_editor.get_tiles()->pos(0, 0)) return; + + tilemap->save_state(); + for (int x = 0; x < tilemap->get_width(); ++x) + { + for (int y = 0; y < tilemap->get_height(); ++y) + { + if (tilemap->get_tile_id(x, y) == replace_tile) + { + tilemap->change(x, y, m_editor.get_tiles()->pos( + (x - static_cast(m_hovered_tile.x)) % m_editor.get_tiles()->m_width, + (y - static_cast(m_hovered_tile.y)) % m_editor.get_tiles()->m_height) + ); + } + } + } +} + void EditorOverlayWidget::hover_object() { @@ -543,8 +582,10 @@ EditorOverlayWidget::hover_object() for (auto& moving_object : m_editor.get_sector()->get_objects_by_type()) { Rectf bbox = moving_object.get_bbox(); - if (bbox.contains(m_sector_pos)) { - if (&moving_object != m_hovered_object) { + if (bbox.contains(m_sector_pos)) + { + if (&moving_object != m_hovered_object) + { // Ignore BezierMarkers if ctrl isn't pressed... (1/2) auto* bezier_marker = dynamic_cast(&moving_object); @@ -601,7 +642,8 @@ EditorOverlayWidget::edit_path(PathGameObject* path, GameObject* new_marked_obje if (!path) return; delete_markers(); - if (!path->is_valid()) { + if (!path->is_valid()) + { m_edited_path = nullptr; return; } @@ -628,15 +670,16 @@ EditorOverlayWidget::select_object() delete_markers(); if (!m_dragged_object || !m_dragged_object->is_valid()) return; - if (m_dragged_object->has_variable_size()) { + if (m_dragged_object->has_variable_size()) + { m_selected_object = m_dragged_object; m_dragged_object->editor_select(); return; } auto path_obj = dynamic_cast(m_dragged_object.get()); - if (path_obj && path_obj->get_path_gameobject()) - { + + if (path_obj && path_obj->get_path_gameobject()) { edit_path(path_obj->get_path_gameobject(), m_dragged_object.get()); } } @@ -644,7 +687,8 @@ EditorOverlayWidget::select_object() void EditorOverlayWidget::grab_object() { - if (m_hovered_object) { + if (m_hovered_object) + { if (!m_hovered_object->is_valid()) { m_hovered_object = nullptr; @@ -657,9 +701,8 @@ EditorOverlayWidget::grab_object() m_dragged_object->save_state(); auto* pm = dynamic_cast(m_hovered_object.get()); - if (!pm) { - select_object(); - } + if (!pm) select_object(); + m_last_node_marker = dynamic_cast(pm); } } @@ -672,7 +715,9 @@ EditorOverlayWidget::grab_object() m_edited_path->is_valid()) { // do nothing - } else { + } + else + { delete_markers(); } } @@ -681,8 +726,10 @@ EditorOverlayWidget::grab_object() void EditorOverlayWidget::clone_object() { - if (m_hovered_object && m_hovered_object->is_saveable()) { - if (!m_hovered_object->is_valid()) { + if (m_hovered_object && m_hovered_object->is_saveable()) + { + if (!m_hovered_object->is_valid()) + { m_hovered_object = nullptr; return; } @@ -729,20 +776,22 @@ EditorOverlayWidget::show_object_menu(GameObject& object) void EditorOverlayWidget::move_object() { - if (m_dragged_object) { - if (!m_dragged_object->is_valid()) { + if (m_dragged_object) + { + if (!m_dragged_object->is_valid()) + { m_dragged_object = nullptr; return; } Vector new_pos = m_sector_pos - m_obj_mouse_desync; - if (g_config->editor_snap_to_grid) { + if (g_config->editor_snap_to_grid) + { auto& snap_grid_size = snap_grid_sizes[g_config->editor_selected_snap_grid_size]; new_pos = glm::floor(new_pos / static_cast(snap_grid_size)) * static_cast(snap_grid_size); auto pm = dynamic_cast(m_dragged_object.get()); - if (pm) { + if (pm) new_pos -= pm->get_offset(); - } } // TODO: Temporarily disabled during ongoing discussion @@ -769,9 +818,11 @@ EditorOverlayWidget::rubber_object() if (!m_edited_path) { delete_markers(); } + if (m_dragged_object) { m_dragged_object->editor_delete(); } + m_last_node_marker = nullptr; } @@ -780,11 +831,13 @@ EditorOverlayWidget::rubber_rect() { delete_markers(); Rectf dr = drag_rect(); - for (auto& moving_object : m_editor.get_sector()->get_objects_by_type()) { + for (auto& moving_object : m_editor.get_sector()->get_objects_by_type()) + { Rectf bbox = moving_object.get_bbox(); if (dr.contains(bbox)) { moving_object.editor_delete(); } + } m_last_node_marker = nullptr; } @@ -796,7 +849,8 @@ EditorOverlayWidget::update_node_iterators() if (!m_edited_path->is_valid()) return; auto* sector = m_editor.get_sector(); - for (auto& moving_object : sector->get_objects_by_type()) { + for (auto& moving_object : sector->get_objects_by_type()) + { auto marker = dynamic_cast(&moving_object); if (marker) { marker->update_iterator(); @@ -858,12 +912,13 @@ EditorOverlayWidget::put_object() object->after_editor_set(); auto* mo = dynamic_cast (object.get()); - if (mo && !g_config->editor_snap_to_grid) { + if (mo && !g_config->editor_snap_to_grid) + { auto bbox = mo->get_bbox(); mo->move_to(mo->get_pos() - Vector(bbox.get_width() / 2, bbox.get_height() / 2)); } - auto* wo = dynamic_cast(object.get()); + auto* wo = dynamic_cast(object.get()); if (wo) { wo->move_to(wo->get_pos() / 32.0f); } @@ -875,8 +930,7 @@ EditorOverlayWidget::put_object() void EditorOverlayWidget::process_left_click() { - if (MenuManager::instance().has_dialog()) - return; + if (MenuManager::instance().has_dialog()) return; m_dragging = true; m_dragging_right = false; m_drag_start = m_sector_pos; @@ -900,6 +954,10 @@ EditorOverlayWidget::process_left_click() fill(); break; + case 3: + replace(); + break; + default: break; } @@ -920,11 +978,12 @@ EditorOverlayWidget::process_left_click() break; } - if (!m_editor.get_tileselect_object().empty()) { - if (!m_dragged_object) { - put_object(); - } - } else { + if (!m_editor.get_tileselect_object().empty()) + { + if (!m_dragged_object) put_object(); + } + else + { rubber_object(); } break; @@ -1001,9 +1060,7 @@ EditorOverlayWidget::update_tile_selection() Rectf select = tile_drag_rect(); auto tiles = m_editor.get_tiles(); auto tilemap = m_editor.get_selected_tilemap(); - if (!tilemap) { - return; - } + if (!tilemap) return; tiles->m_tiles.clear(); tiles->m_width = static_cast(select.get_width()); @@ -1011,11 +1068,16 @@ EditorOverlayWidget::update_tile_selection() int w = static_cast(tilemap->get_width()); int h = static_cast(tilemap->get_height()); - for (int y = static_cast(select.get_top()); y < static_cast(select.get_bottom()); y++) { - for (int x = static_cast(select.get_left()); x < static_cast(select.get_right()); x++) { - if ( x < 0 || y < 0 || x >= w || y >= h) { + for (int y = static_cast(select.get_top()); y < static_cast(select.get_bottom()); y++) + { + for (int x = static_cast(select.get_left()); x < static_cast(select.get_right()); x++) + { + if ( x < 0 || y < 0 || x >= w || y >= h) + { tiles->m_tiles.push_back(0); - } else { + } + else + { tiles->m_tiles.push_back(tilemap->get_tile_id(x, y)); } } @@ -1039,12 +1101,15 @@ EditorOverlayWidget::on_mouse_button_up(const SDL_MouseButtonEvent& button) } else if (m_editor.get_tileselect_input_type() == EditorToolboxWidget::InputType::OBJECT) { - if (m_dragging && m_dragged_object) + if (m_dragging && m_dragged_object) { m_dragged_object->check_state(); + } } } else if (button.button == SDL_BUTTON_MIDDLE) + { m_scrolling = false; + } m_dragging = false; @@ -1086,10 +1151,14 @@ EditorOverlayWidget::on_mouse_motion(const SDL_MouseMotionEvent& motion) switch (m_editor.get_tileselect_input_type()) { case EditorToolboxWidget::InputType::TILE: - if (m_dragging_right) { + if (m_dragging_right) + { update_tile_selection(); - } else { - switch (m_editor.get_tileselect_select_mode()) { + } + else + { + switch (m_editor.get_tileselect_select_mode()) + { case 0: put_next_tiles(); break; @@ -1103,11 +1172,15 @@ EditorOverlayWidget::on_mouse_motion(const SDL_MouseMotionEvent& motion) break; case EditorToolboxWidget::InputType::OBJECT: - if (m_editor.get_tileselect_object().empty()) { - if (m_editor.get_tileselect_select_mode() == 1) { + if (m_editor.get_tileselect_object().empty()) + { + if (m_editor.get_tileselect_select_mode() == 1) + { rubber_rect(); } - } else { + } + else + { move_object(); } break; @@ -1133,11 +1206,11 @@ bool EditorOverlayWidget::on_key_up(const SDL_KeyboardEvent& key) { auto sym = key.keysym.sym; - if (sym == SDLK_LSHIFT) - { + if (sym == SDLK_LSHIFT) { g_config->editor_snap_to_grid = !g_config->editor_snap_to_grid; } - if (sym == SDLK_LCTRL || sym == SDLK_RCTRL) { + if (sym == SDLK_LCTRL || sym == SDLK_RCTRL) + { if (action_pressed) { g_config->editor_autotile_mode = !g_config->editor_autotile_mode; @@ -1162,7 +1235,8 @@ EditorOverlayWidget::on_key_down(const SDL_KeyboardEvent& key) if (sym == SDLK_F7 || sym == SDLK_LSHIFT) { g_config->editor_snap_to_grid = !g_config->editor_snap_to_grid; } - if (sym == SDLK_F5 || ((sym == SDLK_LCTRL || sym == SDLK_RCTRL) && !action_pressed)) { + if (sym == SDLK_F5 || ((sym == SDLK_LCTRL || sym == SDLK_RCTRL) && !action_pressed)) + { g_config->editor_autotile_mode = !g_config->editor_autotile_mode; action_pressed = true; // Hovered objects depend on which keys are pressed @@ -1177,8 +1251,7 @@ EditorOverlayWidget::on_key_down(const SDL_KeyboardEvent& key) void EditorOverlayWidget::update_pos() { - if(m_editor.get_sector() == nullptr) - return; + if(m_editor.get_sector() == nullptr) return; m_sector_pos = m_mouse_pos + m_editor.get_sector()->get_camera().get_translation(); m_hovered_tile = sp_to_tp(m_sector_pos); @@ -1190,23 +1263,22 @@ EditorOverlayWidget::update_pos() void EditorOverlayWidget::draw_tile_tip(DrawingContext& context) { - if ( m_editor.get_tileselect_input_type() == EditorToolboxWidget::InputType::TILE ) { - + if (m_editor.get_tileselect_input_type() == EditorToolboxWidget::InputType::TILE) + { auto tilemap = m_editor.get_selected_tilemap(); - if (!tilemap) { - return; - } + if (!tilemap) return; - if (m_editor.get_tiles()->empty()) - return; + if (m_editor.get_tiles()->empty()) return; Vector screen_corner = context.get_cliprect().p2() + m_editor.get_sector()->get_camera().get_translation(); Vector drawn_tile = m_hovered_tile; // FIXME: Why is this initialised if it's going to be overwritten right below? auto tiles = m_editor.get_tiles(); - for (drawn_tile.x = static_cast(tiles->m_width) - 1.0f; drawn_tile.x >= 0.0f; drawn_tile.x--) { - for (drawn_tile.y = static_cast(tiles->m_height) - 1.0f; drawn_tile.y >= 0.0f; drawn_tile.y--) { + for (drawn_tile.x = static_cast(tiles->m_width) - 1.0f; drawn_tile.x >= 0.0f; drawn_tile.x--) + { + for (drawn_tile.y = static_cast(tiles->m_height) - 1.0f; drawn_tile.y >= 0.0f; drawn_tile.y--) + { Vector on_tile = m_hovered_tile + drawn_tile; if (on_tile.x < 0 || @@ -1214,18 +1286,19 @@ EditorOverlayWidget::draw_tile_tip(DrawingContext& context) on_tile.x >= static_cast(tilemap->get_width()) || on_tile.y >= static_cast(tilemap->get_height()) || on_tile.x >= ceilf(screen_corner.x / 32) || - on_tile.y >= ceilf(screen_corner.y / 32)) { + on_tile.y >= ceilf(screen_corner.y / 32)) + { continue; } uint32_t tile_id = tiles->pos(static_cast(drawn_tile.x), static_cast(drawn_tile.y)); draw_tile(context.color(), *m_editor.get_tileset(), tile_id, align_to_tilemap(on_tile) - m_editor.get_sector()->get_camera().get_translation(), LAYER_GUI-11, Color(1, 1, 1, 0.5)); - /*if (tile_id) { - const Tile* tg_tile = m_editor.get_tileset()->get( tile_id ); - tg_tile->draw(context.color(), tp_to_sp(on_tile) - m_editor.get_sector()->camera->get_translation(), - LAYER_GUI-11, Color(1, 1, 1, 0.5)); - }*/ + //if (tile_id) { + //const Tile* tg_tile = m_editor.get_tileset()->get( tile_id ); + //tg_tile->draw(context.color(), tp_to_sp(on_tile) - m_editor.get_sector()->camera->get_translation(), + // LAYER_GUI-11, Color(1, 1, 1, 0.5)); + //} } } } @@ -1235,12 +1308,9 @@ void EditorOverlayWidget::draw_rectangle_preview(DrawingContext& context) { auto tilemap = m_editor.get_selected_tilemap(); - if (!tilemap) { - return; - } + if (!tilemap) return; - if (m_rectangle_preview->empty()) - return; + if (m_rectangle_preview->empty()) return; Vector screen_corner = context.get_cliprect().p2() + m_editor.get_sector()->get_camera().get_translation(); @@ -1249,8 +1319,10 @@ EditorOverlayWidget::draw_rectangle_preview(DrawingContext& context) std::min(sp_to_tp(m_drag_start).y, m_hovered_tile.y)); auto tiles = m_rectangle_preview.get(); - for (drawn_tile.x = static_cast(tiles->m_width) - 1.0f; drawn_tile.x >= 0.0f; drawn_tile.x--) { - for (drawn_tile.y = static_cast(tiles->m_height) - 1.0f; drawn_tile.y >= 0.0f; drawn_tile.y--) { + for (drawn_tile.x = static_cast(tiles->m_width) - 1.0f; drawn_tile.x >= 0.0f; drawn_tile.x--) + { + for (drawn_tile.y = static_cast(tiles->m_height) - 1.0f; drawn_tile.y >= 0.0f; drawn_tile.y--) + { Vector on_tile = corner + drawn_tile; if (on_tile.x < 0 || @@ -1258,7 +1330,8 @@ EditorOverlayWidget::draw_rectangle_preview(DrawingContext& context) on_tile.x >= static_cast(tilemap->get_width()) || on_tile.y >= static_cast(tilemap->get_height()) || on_tile.x >= ceilf(screen_corner.x / 32) || - on_tile.y >= ceilf(screen_corner.y / 32)) { + on_tile.y >= ceilf(screen_corner.y / 32)) + { continue; } uint32_t tile_id = tiles->pos(static_cast(drawn_tile.x), static_cast(drawn_tile.y)); @@ -1274,8 +1347,7 @@ EditorOverlayWidget::draw_tile_grid(DrawingContext& context, int tile_size, bool draw_shadow) const { auto current_tm = m_editor.get_selected_tilemap(); - if (current_tm == nullptr) - return; + if (current_tm == nullptr) return; int tm_width = current_tm->get_width() * (32 / tile_size); int tm_height = current_tm->get_height() * (32 / tile_size); @@ -1296,12 +1368,14 @@ EditorOverlayWidget::draw_tile_grid(DrawingContext& context, int tile_size, { context.color().draw_line(from, to, col, current_tm->get_layer()); }; - if (draw_shadow) { + if (draw_shadow) + { Vector viewport_scale = VideoSystem::current()->get_viewport().get_scale(); const Color shadow_colour(0.0f, 0.0f, 0.0f, 0.05f); const Vector shadow_offset(1.0f / viewport_scale.x, 1.0f / viewport_scale.y); - for (int i = static_cast(start.x); i <= static_cast(end.x); i++) { + for (int i = static_cast(start.x); i <= static_cast(end.x); i++) + { line_start = tile_screen_pos(Vector(static_cast(i), 0.0f), tile_size) + shadow_offset; line_end = tile_screen_pos(Vector(static_cast(i), end.y), @@ -1309,7 +1383,8 @@ EditorOverlayWidget::draw_tile_grid(DrawingContext& context, int tile_size, draw_line(line_start, line_end, shadow_colour); } - for (int i = static_cast(start.y); i <= static_cast(end.y); i++) { + for (int i = static_cast(start.y); i <= static_cast(end.y); i++) + { line_start = tile_screen_pos(Vector(0.0f, static_cast(i)), tile_size) + shadow_offset; line_end = tile_screen_pos(Vector(end.x, static_cast(i)), @@ -1319,13 +1394,15 @@ EditorOverlayWidget::draw_tile_grid(DrawingContext& context, int tile_size, } const Color line_color(1.f, 1.f, 1.f, 0.2f); - for (int i = static_cast(start.x); i <= static_cast(end.x); i++) { + for (int i = static_cast(start.x); i <= static_cast(end.x); i++) + { line_start = tile_screen_pos(Vector(static_cast(i), 0.0f), tile_size); line_end = tile_screen_pos(Vector(static_cast(i), end.y), tile_size); draw_line(line_start, line_end, line_color); } - for (int i = static_cast(start.y); i <= static_cast(end.y); i++) { + for (int i = static_cast(start.y); i <= static_cast(end.y); i++) + { line_start = tile_screen_pos(Vector(0.0f, static_cast(i)), tile_size); line_end = tile_screen_pos(Vector(end.x, static_cast(i)), tile_size); draw_line(line_start, line_end, line_color); @@ -1335,11 +1412,10 @@ EditorOverlayWidget::draw_tile_grid(DrawingContext& context, int tile_size, void EditorOverlayWidget::draw_tilemap_border(DrawingContext& context) { - if ( !m_editor.get_selected_tilemap() ) return; + if (!m_editor.get_selected_tilemap()) return; auto current_tm = m_editor.get_selected_tilemap(); - if (!current_tm) - return; + if (!current_tm) return; Vector start = tile_screen_pos( Vector(0, 0) ); Vector end = tile_screen_pos( Vector(static_cast(current_tm->get_width()), @@ -1358,15 +1434,20 @@ EditorOverlayWidget::draw_path(DrawingContext& context) if (!m_selected_object->is_valid()) return; if (!m_edited_path->is_valid()) return; - for (auto i = m_edited_path->get_path().m_nodes.begin(); i != m_edited_path->get_path().m_nodes.end(); ++i) { + for (auto i = m_edited_path->get_path().m_nodes.begin(); i != m_edited_path->get_path().m_nodes.end(); ++i) + { auto j = i+1; Path::Node* node1 = &(*i); Path::Node* node2; - if (j == m_edited_path->get_path().m_nodes.end()) { - if (m_edited_path->get_path().m_mode == WalkMode::CIRCULAR) { + if (j == m_edited_path->get_path().m_nodes.end()) + { + if (m_edited_path->get_path().m_mode == WalkMode::CIRCULAR) + { //loop to the first node node2 = &(*m_edited_path->get_path().m_nodes.begin()); - } else { + } + else + { // Just draw the bezier lines auto cam_translation = m_editor.get_sector()->get_camera().get_translation(); context.color().draw_line(node1->position - cam_translation, @@ -1377,7 +1458,9 @@ EditorOverlayWidget::draw_path(DrawingContext& context) Color(0, 0, 1), LAYER_GUI - 21); continue; } - } else { + } + else + { node2 = &(*j); } auto cam_translation = m_editor.get_sector()->get_camera().get_translation(); @@ -1408,11 +1491,13 @@ EditorOverlayWidget::draw(DrawingContext& context) draw_rectangle_preview(context); draw_path(context); - if (g_config->editor_render_grid) { + if (g_config->editor_render_grid) + { draw_tile_grid(context, 32, true); draw_tilemap_border(context); auto snap_grid_size = snap_grid_sizes[g_config->editor_selected_snap_grid_size]; - if (snap_grid_size != 32) { + if (snap_grid_size != 32) + { draw_tile_grid(context, snap_grid_size, false); } } @@ -1422,7 +1507,8 @@ EditorOverlayWidget::draw(DrawingContext& context) } if (m_dragging && m_editor.get_tileselect_select_mode() == 1 - && !m_dragging_right) { + && !m_dragging_right) + { // Draw selection rectangle... auto cam_translation = m_editor.get_sector()->get_camera().get_translation(); Vector p0 = m_drag_start - cam_translation; @@ -1430,9 +1516,11 @@ EditorOverlayWidget::draw(DrawingContext& context) if (p0.x > p3.x) { std::swap(p0.x, p3.x); } + if (p0.y > p3.y) { std::swap(p0.y, p3.y); } + Vector p1 = Vector(p0.x, p3.y); Vector p2 = Vector(p3.x, p0.y); @@ -1449,27 +1537,39 @@ EditorOverlayWidget::draw(DrawingContext& context) Color(0.0f, 1.0f, 0.0f, 0.2f), 0.0f, LAYER_GUI-5); } - if (m_dragging && m_dragging_right) { + if (m_dragging && m_dragging_right) + { context.color().draw_filled_rect(selection_draw_rect(), Color(0.2f, 0.4f, 1.0f, 0.6f), 0.0f, LAYER_GUI-13); } - if (g_config->editor_autotile_help) { + if (g_config->editor_autotile_help) + { if (m_editor.get_tileset()->get_autotileset_from_tile(m_editor.get_tiles()->pos(0, 0)) != nullptr) { - if (g_config->editor_autotile_mode) { + if (g_config->editor_autotile_mode) + { context.color().draw_text(Resources::normal_font, _("Autotile mode is on"), Vector(144, 16), ALIGN_LEFT, LAYER_OBJECTS+1, EditorOverlayWidget::text_autotile_active_color); - } else { + } + else + { context.color().draw_text(Resources::normal_font, _("Hold Ctrl to enable autotile"), Vector(144, 16), ALIGN_LEFT, LAYER_OBJECTS+1, EditorOverlayWidget::text_autotile_available_color); } - } else if (g_config->editor_autotile_mode) { - if (m_editor.get_tiles()->pos(0, 0) == 0) { + } + else if (g_config->editor_autotile_mode) + { + if (m_editor.get_tiles()->pos(0, 0) == 0) + { context.color().draw_text(Resources::normal_font, _("Autotile erasing mode is on"), Vector(144, 16), ALIGN_LEFT, LAYER_OBJECTS+1, EditorOverlayWidget::text_autotile_active_color); - } else { + } + else + { context.color().draw_text(Resources::normal_font, _("Selected tile isn't autotileable"), Vector(144, 16), ALIGN_LEFT, LAYER_OBJECTS+1, EditorOverlayWidget::text_autotile_error_color); } - } else if (m_editor.get_tiles()->pos(0, 0) == 0) { + } + else if (m_editor.get_tiles()->pos(0, 0) == 0) + { context.color().draw_text(Resources::normal_font, _("Hold Ctrl to enable autotile erasing"), Vector(144, 16), ALIGN_LEFT, LAYER_OBJECTS+1, EditorOverlayWidget::text_autotile_available_color); } } @@ -1480,10 +1580,7 @@ Vector EditorOverlayWidget::tp_to_sp(const Vector& tp, int tile_size) const { auto tilemap = m_editor.get_selected_tilemap(); - if (!tilemap) - { - return Vector(0, 0); - } + if (!tilemap) return Vector(0, 0); Vector sp = tp * static_cast(tile_size); return sp + tilemap->get_offset(); @@ -1493,10 +1590,7 @@ Vector EditorOverlayWidget::sp_to_tp(const Vector& sp, int tile_size) const { auto tilemap = m_editor.get_selected_tilemap(); - if (!tilemap) - { - return Vector(0, 0); - } + if (!tilemap) return Vector(0, 0); Vector sp_ = sp - tilemap->get_offset(); return sp_ / static_cast(tile_size); @@ -1513,13 +1607,18 @@ Vector EditorOverlayWidget::align_to_tilemap(const Vector& sp, int tile_size) const { auto tilemap = m_editor.get_selected_tilemap(); - if (!tilemap) - { - return Vector(0, 0); - } + if (!tilemap) return Vector(0, 0); Vector sp_ = sp + tilemap->get_offset() / static_cast(tile_size); return glm::trunc(sp_) * static_cast(tile_size); } +bool +EditorOverlayWidget::is_position_inside_tilemap(const TileMap* tilemap, const Vector& pos) const +{ + return pos.x >= 0 && pos.y >= 0 && + pos.x < static_cast(tilemap->get_width()) && + pos.y < static_cast(tilemap->get_height()); +} + /* EOF */ diff --git a/src/editor/overlay_widget.hpp b/src/editor/overlay_widget.hpp index f657b8dda69..ac322d94cc8 100644 --- a/src/editor/overlay_widget.hpp +++ b/src/editor/overlay_widget.hpp @@ -83,6 +83,7 @@ class EditorOverlayWidget final : public Widget void preview_rectangle(); bool check_tiles_for_fill(uint32_t replace_tile, uint32_t target_tile, uint32_t third_tile) const; void fill(); + void replace(); void put_object(); void rubber_object(); @@ -111,6 +112,7 @@ class EditorOverlayWidget final : public Widget Vector sp_to_tp(const Vector& sp, int tile_size = 32) const; Vector tile_screen_pos(const Vector& tp, int tile_size = 32) const; Vector align_to_tilemap(const Vector& sp, int tile_size = 32) const; + bool is_position_inside_tilemap(const TileMap* tilemap, const Vector& pos) const; // in sector position Rectf drag_rect() const; diff --git a/src/editor/toolbox_widget.cpp b/src/editor/toolbox_widget.cpp index a5b38f8bb64..a6c9d50e00a 100644 --- a/src/editor/toolbox_widget.cpp +++ b/src/editor/toolbox_widget.cpp @@ -65,6 +65,7 @@ EditorToolboxWidget::EditorToolboxWidget(Editor& editor) : { m_select_mode->push_mode("images/engine/editor/select-mode1.png"); m_select_mode->push_mode("images/engine/editor/select-mode2.png"); + m_select_mode->push_mode("images/engine/editor/select-mode3.png"); m_move_mode->push_mode("images/engine/editor/move-mode1.png"); m_undo_mode->push_mode("images/engine/editor/redo.png"); //settings_mode->push_mode("images/engine/editor/settings-mode1.png"); @@ -448,8 +449,10 @@ EditorToolboxWidget::on_mouse_motion(const SDL_MouseMotionEvent& motion) try { obj_name = GameObjectFactory::instance().get_display_name(obj_class); } - catch (std::exception& err) { - log_warning << "Unable to find name for object with class \"" << obj_class << "\": " << err.what() << std::endl; + catch (std::exception&) { + // NOTE: Temporarily commented out, so hovering over node marker doesn't show a warning. + // When the node marker is moved as a tool, this should be uncommented. + // log_warning << "Unable to find name for object with class \"" << obj_class << "\": " << err.what() << std::endl; } m_object_tip = std::make_unique(obj_name); } diff --git a/src/editor/worldmap_objects.cpp b/src/editor/worldmap_objects.cpp deleted file mode 100644 index 62e898c40a3..00000000000 --- a/src/editor/worldmap_objects.cpp +++ /dev/null @@ -1,280 +0,0 @@ -// SuperTux -// Copyright (C) 2016 Hume2 -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -#include "editor/worldmap_objects.hpp" - -#include - -#include "editor/editor.hpp" -#include "sprite/sprite.hpp" -#include "sprite/sprite_manager.hpp" -#include "supertux/world.hpp" -#include "util/file_system.hpp" -#include "util/log.hpp" -#include "util/reader_mapping.hpp" -#include "util/writer.hpp" - -namespace worldmap_editor { - -WorldmapObject::WorldmapObject (const ReaderMapping& mapping, const std::string& default_sprite) : - MovingSprite(mapping, default_sprite), - m_tile_x(), - m_tile_y() -{ - m_col.m_bbox = Rectf(Vector(32 * m_col.m_bbox.get_left(), - 32 * m_col.m_bbox.get_top()), - Sizef(32.0f, 32.0f)); -} - -WorldmapObject::WorldmapObject (const ReaderMapping& mapping) : - MovingSprite(mapping), - m_tile_x(), - m_tile_y() -{ - m_col.m_bbox.set_left(32 * m_col.m_bbox.get_left()); - m_col.m_bbox.set_top(32 * m_col.m_bbox.get_top()); - m_col.m_bbox.set_size(32, 32); -} - -WorldmapObject::WorldmapObject (const Vector& pos, const std::string& default_sprite) : - MovingSprite(pos, default_sprite), - m_tile_x(), - m_tile_y() -{ - m_col.m_bbox.set_left(32 * m_col.m_bbox.get_left()); - m_col.m_bbox.set_top(32 * m_col.m_bbox.get_top()); - m_col.m_bbox.set_size(32, 32); -} - -ObjectSettings -WorldmapObject::get_settings() -{ - ObjectSettings result = MovingSprite::get_settings(); - - m_tile_x = static_cast(m_col.m_bbox.get_left()) / 32; - m_tile_y = static_cast(m_col.m_bbox.get_top()) / 32; - - result.remove("x"); - result.remove("y"); - - result.add_int(_("X"), &m_tile_x, "x", {}, OPTION_HIDDEN); - result.add_int(_("Y"), &m_tile_y, "y", {}, OPTION_HIDDEN); - - return result; -} - -void -WorldmapObject::move_to(const Vector& pos) -{ - set_pos(Vector(32.0f * static_cast(pos.x / 32), - 32.0f * static_cast(pos.y / 32))); -} - -LevelDot::LevelDot(const ReaderMapping& mapping) : - WorldmapObject(mapping, "images/worldmap/common/leveldot.sprite"), - m_level_filename(), - m_extro_script(), - m_auto_play(false), - m_title_color(1, 1, 1) -{ - mapping.get("extro-script", m_extro_script); - mapping.get("auto-play", m_auto_play); - if (!mapping.get("level", m_level_filename)) { - // Hack for backward compatibility with 0.5.x level - m_level_filename = std::move(m_name); - } - - std::vector vColor; - if (mapping.get("color", vColor)) { - m_title_color = Color(vColor); - } -} - -void -LevelDot::draw(DrawingContext& context) -{ - m_sprite->draw(context.color(), m_col.m_bbox.p1() + Vector(16, 16), m_layer); -} - -ObjectSettings -LevelDot::get_settings() -{ - ObjectSettings result = WorldmapObject::get_settings(); - - std::string basedir = (Editor::current() && Editor::current()->get_world()) ? - Editor::current()->get_world()->get_basedir() : std::string(); - - // FIXME: hack to make the basedir absolute, making - // World::get_basedir() itself absolute would be correct, but - // invalidate savefiles. - if (!basedir.empty() && basedir.front() != '/') { - basedir = "/" + basedir; - } - - result.add_level(_("Level"), &m_level_filename, "level", basedir); - result.add_script(_("Outro script"), &m_extro_script, "extro-script"); - result.add_bool(_("Auto play"), &m_auto_play, "auto-play", false); - //result.add_sprite(_("Sprite"), &m_sprite_name, "sprite"); - result.add_color(_("Title colour"), &m_title_color, "color", Color::WHITE); - - result.reorder({"name", "sprite", "x", "y"}); - - return result; -} - -Teleporter::Teleporter (const ReaderMapping& mapping) : - WorldmapObject(mapping, "images/worldmap/common/teleporterdot.sprite"), - m_worldmap(), - m_spawnpoint(), - m_message(), - m_automatic(), - m_change_worldmap() -{ - mapping.get("worldmap", m_worldmap); - mapping.get("spawnpoint", m_spawnpoint); - mapping.get("message", m_message); - - mapping.get("automatic", m_automatic); - - m_change_worldmap = m_worldmap.size() > 0; -} - -void -Teleporter::draw(DrawingContext& context) -{ - m_sprite->draw(context.color(), m_col.m_bbox.p1() + Vector(16, 16), m_layer); -} - -ObjectSettings -Teleporter::get_settings() -{ - ObjectSettings result = WorldmapObject::get_settings(); - - result.add_text(_("Spawnpoint"), &m_spawnpoint, "spawnpoint"); - result.add_translatable_text(_("Message"), &m_message, "message"); - result.add_bool(_("Automatic"), &m_automatic, "automatic", false); - // result.add_bool(_("Change worldmap"), &m_change_worldmap, "worldmap", true); - result.add_worldmap(_("Target worldmap"), &m_worldmap, "worldmap"); - //result.add_sprite(_("Sprite"), &m_sprite_name, "sprite"); - - result.reorder({"spawnpoint", "automatic", "message", "sprite", "x", "y"}); - - return result; -} - -WorldmapSpawnPoint::WorldmapSpawnPoint (const ReaderMapping& mapping) : - WorldmapObject(mapping, "images/worldmap/common/tux.png"), - m_dir(worldmap::Direction::NONE) -{ - mapping.get("name", m_name); - - std::string auto_dir_str; - if (mapping.get("auto-dir", auto_dir_str)) { - m_dir = worldmap::string_to_direction(auto_dir_str); - } -} - -WorldmapSpawnPoint::WorldmapSpawnPoint (const std::string& name_, const Vector& pos) : - WorldmapObject(pos, "images/worldmap/common/tux.png"), - m_dir(worldmap::Direction::NONE) -{ - m_name = name_; -} - -ObjectSettings -WorldmapSpawnPoint::get_settings() -{ - ObjectSettings result = WorldmapObject::get_settings(); - - result.add_worldmap_direction(_("Direction"), &m_dir, worldmap::Direction::NONE, "auto-dir"); - result.remove("sprite"); - - result.reorder({"auto-dir", "name", "x", "y"}); - - return result; -} - -SpriteChange::SpriteChange (const ReaderMapping& mapping) : - WorldmapObject(mapping, "images/engine/editor/spritechange.png"), - m_target_sprite(m_sprite_name), - m_stay_action(), - m_initial_stay_action(false), - m_stay_group(), - m_change_on_touch(true) -{ - // To make obvious where the sprite change is, let's use an universal 32×32 sprite - m_sprite = SpriteManager::current()->create("images/engine/editor/spritechange.png"); - - mapping.get("stay-action", m_stay_action); - mapping.get("initial-stay-action", m_initial_stay_action); - mapping.get("stay-group", m_stay_group); - - mapping.get("change-on-touch", m_change_on_touch); -} - -ObjectSettings -SpriteChange::get_settings() -{ - ObjectSettings result = WorldmapObject::get_settings(); - - //result.add_sprite(_("Sprite"), &m_target_sprite, "sprite"); - result.add_text(_("Stay action"), &m_stay_action, "stay-action"); - result.add_bool(_("Initial stay action"), &m_initial_stay_action, "initial-stay-action"); - result.add_text(_("Stay group"), &m_stay_group, "stay-group"); - result.add_bool(_("Change on touch"), &m_change_on_touch, "change-on-touch"); - - result.reorder({"change-on-touch", "initial-stay-action", "stay-group", "sprite", "x", "y"}); - - return result; -} - -SpecialTile::SpecialTile (const ReaderMapping& mapping) : - WorldmapObject(mapping, "images/worldmap/common/specialtile.png"), - m_map_message(), - m_script(), - m_passive_message(false), - m_invisible_tile(false), - m_apply_to_directions("north-east-south-west") -{ - mapping.get("map-message", m_map_message); - mapping.get("script", m_script); - - mapping.get("passive-message", m_passive_message); - mapping.get("invisible-tile", m_invisible_tile); - - mapping.get("apply-to-direction", m_apply_to_directions); -} - -ObjectSettings -SpecialTile::get_settings() -{ - ObjectSettings result = WorldmapObject::get_settings(); - - result.add_translatable_text(_("Message"), &m_map_message, "map-message"); - result.add_bool(_("Show message"), &m_passive_message, "passive-message", false); - result.add_script(_("Script"), &m_script, "script"); - result.add_bool(_("Invisible"), &m_invisible_tile, "invisible-tile", false); - result.add_text(_("Direction"), &m_apply_to_directions, "apply-to-direction", std::string("north-east-south-west")); - //result.add_sprite(_("Sprite"), &m_sprite_name, "sprite"); - - result.reorder({"map-message", "invisible-tile", "script", "passive-message", "apply-to-direction", "sprite", "x", "y"}); - - return result; -} - -} // namespace worldmap_editor - -/* EOF */ diff --git a/src/editor/worldmap_objects.hpp b/src/editor/worldmap_objects.hpp deleted file mode 100644 index 8d52daa3c72..00000000000 --- a/src/editor/worldmap_objects.hpp +++ /dev/null @@ -1,170 +0,0 @@ -// SuperTux -// Copyright (C) 2016 Hume2 -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -#ifndef HEADER_SUPERTUX_EDITOR_WORLDMAP_OBJECTS_HPP -#define HEADER_SUPERTUX_EDITOR_WORLDMAP_OBJECTS_HPP - -#include "object/moving_sprite.hpp" -#include "video/color.hpp" -#include "worldmap/direction.hpp" - -namespace worldmap_editor { - -class WorldmapObject : public MovingSprite -{ -public: - WorldmapObject(const ReaderMapping& mapping, const std::string& default_sprite); - WorldmapObject(const ReaderMapping& mapping); - WorldmapObject(const Vector& pos, const std::string& default_sprite); - - virtual ObjectSettings get_settings() override; - virtual HitResponse collision(GameObject& other, const CollisionHit& hit) override { return FORCE_MOVE; } - static std::string class_name() { return "worldmap-object"; } - virtual std::string get_class_name() const override { return class_name(); } - virtual void move_to(const Vector& pos) override; - -private: - // FIXME: purely used for saving, is not updated normally, don't use. - int m_tile_x; - int m_tile_y; - -private: - WorldmapObject(const WorldmapObject&) = delete; - WorldmapObject& operator=(const WorldmapObject&) = delete; -}; - -class LevelDot final : public WorldmapObject -{ -public: - LevelDot(const ReaderMapping& mapping); - - virtual void draw(DrawingContext& context) override; - - static std::string class_name() { return "level"; } - virtual std::string get_class_name() const override { return class_name(); } - static std::string display_name() { return _("Level"); } - virtual std::string get_display_name() const override { return display_name(); } - virtual ObjectSettings get_settings() override; - -private: - std::string m_level_filename; - std::string m_extro_script; - bool m_auto_play; - Color m_title_color; - -private: - LevelDot(const LevelDot&) = delete; - LevelDot& operator=(const LevelDot&) = delete; -}; - -class Teleporter final : public WorldmapObject -{ -public: - Teleporter(const ReaderMapping& mapping); - - virtual void draw(DrawingContext& context) override; - - static std::string class_name() { return "teleporter"; } - virtual std::string get_class_name() const override { return class_name(); } - static std::string display_name() { return _("Teleporter"); } - virtual std::string get_display_name() const override { return display_name(); } - virtual ObjectSettings get_settings() override; - -private: - std::string m_worldmap; - std::string m_spawnpoint; - std::string m_message; - bool m_automatic; - bool m_change_worldmap; - -private: - Teleporter(const Teleporter&) = delete; - Teleporter& operator=(const Teleporter&) = delete; -}; - -class WorldmapSpawnPoint final : public WorldmapObject -{ -public: - WorldmapSpawnPoint(const ReaderMapping& mapping); - WorldmapSpawnPoint(const std::string& name_, const Vector& pos); - - static std::string class_name() { return "worldmap-spawnpoint"; } - virtual std::string get_class_name() const override { return class_name(); } - static std::string display_name() { return _("Spawn point"); } - virtual std::string get_display_name() const override { return display_name(); } - - virtual ObjectSettings get_settings() override; - -private: - worldmap::Direction m_dir; - -private: - WorldmapSpawnPoint(const WorldmapSpawnPoint&) = delete; - WorldmapSpawnPoint& operator=(const WorldmapSpawnPoint&) = delete; -}; - -class SpriteChange final : public WorldmapObject -{ -public: - SpriteChange(const ReaderMapping& mapping); - - static std::string class_name() { return "sprite-change"; } - virtual std::string get_class_name() const override { return class_name(); } - static std::string display_name() { return _("Sprite Change"); } - virtual std::string get_display_name() const override { return display_name(); } - virtual ObjectSettings get_settings() override; - -private: - std::string m_target_sprite; - std::string m_stay_action; - bool m_initial_stay_action; - std::string m_stay_group; - bool m_change_on_touch; - -private: - SpriteChange(const SpriteChange&) = delete; - SpriteChange& operator=(const SpriteChange&) = delete; -}; - -class SpecialTile final : public WorldmapObject -{ -public: - SpecialTile(const ReaderMapping& mapping); - - static std::string class_name() { return "special-tile"; } - virtual std::string get_class_name() const override { return class_name(); } - static std::string display_name() { return _("Special tile"); } - virtual std::string get_display_name() const override { return display_name(); } - virtual ObjectSettings get_settings() override; - -private: - std::string m_map_message; - std::string m_script; - bool m_passive_message; - bool m_invisible_tile; - - std::string m_apply_to_directions; - -private: - SpecialTile(const SpecialTile&) = delete; - SpecialTile& operator=(const SpecialTile&) = delete; -}; - -} // namespace worldmap_editor - -#endif - -/* EOF */ diff --git a/src/gui/dialog.cpp b/src/gui/dialog.cpp index 5e177a87be4..59d954206bd 100644 --- a/src/gui/dialog.cpp +++ b/src/gui/dialog.cpp @@ -229,12 +229,12 @@ Dialog::draw(DrawingContext& context) for (int i = 0; i < static_cast(m_buttons.size()); ++i) { float segment_width = bg_rect.get_width() / static_cast(m_buttons.size()); - float button_width = segment_width; Vector pos(bg_rect.get_left() + segment_width/2.0f + static_cast(i) * segment_width, bg_rect.get_bottom() - 12); if (i == m_selected_button) { + float button_width = segment_width; float button_height = 24.0f; float blink = (sinf(g_real_time * math::PI * 1.0f)/2.0f + 0.5f) * 0.5f + 0.25f; context.color().draw_filled_rect(Rectf(Vector(pos.x - button_width/2, pos.y - button_height/2), diff --git a/src/gui/menu_filesystem.cpp b/src/gui/menu_filesystem.cpp index a37c70d6217..2fc4e864df5 100644 --- a/src/gui/menu_filesystem.cpp +++ b/src/gui/menu_filesystem.cpp @@ -71,31 +71,24 @@ FileSystemMenu::refresh_items() if (m_directory != "/") { m_directories.push_back(".."); } - - char** dir_files = PHYSFS_enumerateFiles(m_directory.c_str()); - if (dir_files) - { - for (const char* const* file = dir_files; *file != nullptr; ++file) + physfsutil::enumerate_files(m_directory, [this](const std::string& file) { + std::string filepath = FileSystem::join(m_directory, file); + if (physfsutil::is_directory(filepath)) { - std::string filepath = FileSystem::join(m_directory, *file); - if (physfsutil::is_directory(filepath)) - { - m_directories.push_back(*file); + m_directories.push_back(file); + } + else + { + if (AddonManager::current()->is_from_old_addon(filepath)) { + return; } - else - { - if (AddonManager::current()->is_from_old_addon(filepath)) { - continue; - } - if (has_right_suffix(*file)) - { - m_files.push_back(*file); - } + if (has_right_suffix(file)) + { + m_files.push_back(file); } } - PHYSFS_freeList(dir_files); - } + }); for (const auto& item : m_directories) { diff --git a/src/object/ambient_sound.cpp b/src/object/ambient_sound.cpp index d68d33ea36e..863980891a3 100644 --- a/src/object/ambient_sound.cpp +++ b/src/object/ambient_sound.cpp @@ -258,4 +258,16 @@ AmbientSound::draw(DrawingContext& context) } } +void +AmbientSound::stop_looping_sounds() +{ + stop_playing(); +} + +void +AmbientSound::play_looping_sounds() +{ + start_playing(); +} + /* EOF */ diff --git a/src/object/ambient_sound.hpp b/src/object/ambient_sound.hpp index 0c6a2fa7bd7..7e94aceb81f 100644 --- a/src/object/ambient_sound.hpp +++ b/src/object/ambient_sound.hpp @@ -82,6 +82,9 @@ class AmbientSound final : public MovingObject, virtual int get_layer() const override { return LAYER_OBJECTS; } + virtual void stop_looping_sounds() override; + virtual void play_looping_sounds() override; + protected: virtual void update(float dt_sec) override; virtual void start_playing(); diff --git a/src/object/block.cpp b/src/object/block.cpp index 7f726dfe100..cff5261efe8 100644 --- a/src/object/block.cpp +++ b/src/object/block.cpp @@ -26,8 +26,6 @@ #include "object/growup.hpp" #include "object/player.hpp" #include "object/sprite_particle.hpp" -#include "sprite/sprite.hpp" -#include "sprite/sprite_manager.hpp" #include "supertux/constants.hpp" #include "supertux/flip_level_transformer.hpp" #include "supertux/sector.hpp" @@ -38,16 +36,13 @@ static const float BOUNCY_BRICK_MAX_OFFSET = 8; static const float BOUNCY_BRICK_SPEED = 90; static const float BUMP_ROTATION_ANGLE = 10; -Block::Block(SpritePtr newsprite) : - m_sprite(std::move(newsprite)), - m_sprite_name(), - m_default_sprite_name(), +Block::Block(const Vector& pos, const std::string& sprite_file) : + MovingSprite(pos, sprite_file), m_bouncing(false), m_breaking(false), m_bounce_dir(0), m_bounce_offset(0), - m_original_y(-1), - m_flip(NO_FLIP) + m_original_y(-1) { m_col.m_bbox.set_size(32, 32.1f); set_group(COLGROUP_STATIC); @@ -56,28 +51,13 @@ Block::Block(SpritePtr newsprite) : } Block::Block(const ReaderMapping& mapping, const std::string& sprite_file) : - m_sprite(), - m_sprite_name(), - m_default_sprite_name(), + MovingSprite(mapping, sprite_file), m_bouncing(false), m_breaking(false), m_bounce_dir(0), m_bounce_offset(0), - m_original_y(-1), - m_flip(NO_FLIP) + m_original_y(-1) { - mapping.get("x", m_col.m_bbox.get_left()); - mapping.get("y", m_col.m_bbox.get_top()); - - std::string sf; - mapping.get("sprite", sf); - if (sf.empty() || !PHYSFS_exists(sf.c_str())) { - sf = sprite_file; - } - m_sprite = SpriteManager::current()->create(sf); - m_sprite_name = sf; - m_default_sprite_name = sprite_file; - m_col.m_bbox.set_size(32, 32.1f); set_group(COLGROUP_STATIC); SoundManager::current()->preload("sounds/upgrade.wav"); @@ -227,21 +207,6 @@ Block::break_me() remove_me(); } -ObjectSettings -Block::get_settings() -{ - ObjectSettings result = MovingObject::get_settings(); - - result.add_sprite(_("Sprite"), &m_sprite_name, "sprite", m_default_sprite_name); - - return result; -} - -void Block::after_editor_set() -{ - m_sprite = SpriteManager::current()->create(m_sprite_name); -} - void Block::on_flip(float height) { diff --git a/src/object/block.hpp b/src/object/block.hpp index 17fd1f74a68..8e647bf4852 100644 --- a/src/object/block.hpp +++ b/src/object/block.hpp @@ -17,31 +17,23 @@ #ifndef HEADER_SUPERTUX_OBJECT_BLOCK_HPP #define HEADER_SUPERTUX_OBJECT_BLOCK_HPP -#include "sprite/sprite.hpp" -#include "sprite/sprite_ptr.hpp" -#include "supertux/moving_object.hpp" -#include "video/flip.hpp" +#include "object/moving_sprite.hpp" class Player; class ReaderMapping; -class Block : public MovingObject +class Block : public MovingSprite { friend class FlipLevelTransformer; public: - Block(SpritePtr sprite); + Block(const Vector& pos, const std::string& sprite_file); Block(const ReaderMapping& mapping, const std::string& sprite_file); virtual HitResponse collision(GameObject& other, const CollisionHit& hit) override; virtual void update(float dt_sec) override; virtual void draw(DrawingContext& context) override; - virtual std::string get_default_sprite_name() const { return m_default_sprite_name; } - - virtual ObjectSettings get_settings() override; - virtual void after_editor_set() override; - virtual void on_flip(float height) override; virtual int get_layer() const override { return LAYER_OBJECTS + 1; } @@ -55,18 +47,12 @@ class Block : public MovingObject void break_me(); protected: - SpritePtr m_sprite; - std::string m_sprite_name; - std::string m_default_sprite_name; bool m_bouncing; bool m_breaking; float m_bounce_dir; float m_bounce_offset; float m_original_y; -private: - Flip m_flip; - private: Block(const Block&) = delete; Block& operator=(const Block&) = delete; diff --git a/src/object/bonus_block.cpp b/src/object/bonus_block.cpp index b0eb391da96..3ca508c22b3 100644 --- a/src/object/bonus_block.cpp +++ b/src/object/bonus_block.cpp @@ -36,7 +36,6 @@ #include "object/specialriser.hpp" #include "object/star.hpp" #include "object/trampoline.hpp" -#include "sprite/sprite_manager.hpp" #include "supertux/constants.hpp" #include "supertux/game_object_factory.hpp" #include "supertux/level.hpp" @@ -63,18 +62,16 @@ const float upgrade_sound_gain = 0.3f; } // namespace BonusBlock::BonusBlock(const Vector& pos, int tile_data) : - Block(SpriteManager::current()->create("images/objects/bonus_block/bonusblock.sprite")), + Block(pos, "images/objects/bonus_block/bonusblock.sprite"), m_contents(), m_object(), m_hit_counter(1), m_script(), m_lightsprite(), - m_custom_sx() + m_custom_sx(), + m_coin_sprite("images/objects/coin/coin.sprite") { - m_default_sprite_name = "images/objects/bonus_block/bonusblock.sprite"; - - m_col.m_bbox.set_pos(pos); - m_sprite->set_action("normal"); + set_action("normal"); m_contents = get_content_by_data(tile_data); preload_contents(tile_data); } @@ -86,10 +83,9 @@ BonusBlock::BonusBlock(const ReaderMapping& mapping) : m_hit_counter(1), m_script(), m_lightsprite(), - m_custom_sx() + m_custom_sx(), + m_coin_sprite("images/objects/coin/coin.sprite") { - m_default_sprite_name = "images/objects/bonus_block/bonusblock.sprite"; - auto iter = mapping.get_iter(); while (iter.next()) { const std::string& token = iter.get_key(); @@ -138,6 +134,8 @@ BonusBlock::BonusBlock(const ReaderMapping& mapping) : } } } + } else if (token == "coin-sprite") { + iter.get(m_coin_sprite); } else if (token == "custom-contents") { // handled elsewhere } else { @@ -168,7 +166,7 @@ BonusBlock::BonusBlock(const ReaderMapping& mapping) : SoundManager::current()->preload("sounds/switch.ogg"); m_lightsprite = Surface::from_file("/images/objects/lightmap_light/bonusblock_light.png"); if (m_contents == Content::LIGHT_ON) { - m_sprite->set_action("on"); + set_action("on"); } } } @@ -219,8 +217,9 @@ BonusBlock::get_settings() "1up", "custom", "script", "light", "light-on", "trampoline", "portabletrampoline", "rain", "explode", "rock", "potion"}, static_cast(Content::COIN), "contents"); result.add_sexp(_("Custom Content"), "custom-contents", m_custom_sx); + result.add_sprite(_("Coin sprite"), &m_coin_sprite, "coin-sprite", "images/objects/coin/coin.sprite"); - result.reorder({"script", "count", "contents", "sprite", "x", "y"}); + result.reorder({"script", "count", "contents", "coin-sprite", "sprite", "x", "y"}); return result; } @@ -289,7 +288,7 @@ BonusBlock::try_open(Player* player) switch (m_contents) { case Content::COIN: { - Sector::get().add(get_pos(), true); + Sector::get().add(get_pos(), true, m_coin_sprite); SoundManager::current()->play("sounds/coin.wav", get_pos()); player->get_status().add_coins(1, false); if (m_hit_counter != 0 && !m_parent_dispenser) @@ -349,9 +348,9 @@ BonusBlock::try_open(Player* player) case Content::LIGHT_ON: { if (m_sprite->get_action() == "on") - m_sprite->set_action("off"); + set_action("off"); else - m_sprite->set_action("on"); + set_action("on"); SoundManager::current()->play("sounds/switch.ogg", get_pos()); break; } @@ -379,13 +378,13 @@ BonusBlock::try_open(Player* player) } case Content::RAIN: { - Sector::get().add(get_pos(), true, !m_parent_dispenser); + Sector::get().add(get_pos(), true, !m_parent_dispenser, m_coin_sprite); play_upgrade_sound = true; break; } case Content::EXPLODE: { - Sector::get().add(get_pos() + Vector (0, -40), !m_parent_dispenser); + Sector::get().add(get_pos() + Vector (0, -40), !m_parent_dispenser, m_coin_sprite); play_upgrade_sound = true; break; } @@ -401,7 +400,7 @@ BonusBlock::try_open(Player* player) start_bounce(player); if (m_hit_counter <= 0 || m_contents == Content::LIGHT || m_contents == Content::LIGHT_ON) { //use 0 to allow infinite hits } else if (m_hit_counter == 1) { - m_sprite->set_action("empty"); + set_action("empty"); } else { m_hit_counter--; } @@ -529,7 +528,7 @@ BonusBlock::try_drop(Player *player) } case Content::EXPLODE: { - Sector::get().add(get_pos() + Vector (0, 40), !m_parent_dispenser); + Sector::get().add(get_pos() + Vector (0, 40), !m_parent_dispenser, m_coin_sprite); play_upgrade_sound = true; countdown = true; break; @@ -545,7 +544,7 @@ BonusBlock::try_drop(Player *player) if (countdown) { // only decrease hit counter if try_open was not called if (m_hit_counter == 1) { - m_sprite->set_action("empty"); + set_action("empty"); } else { m_hit_counter--; } diff --git a/src/object/bonus_block.hpp b/src/object/bonus_block.hpp index 9412e033f3d..28a8c143ba9 100644 --- a/src/object/bonus_block.hpp +++ b/src/object/bonus_block.hpp @@ -85,6 +85,7 @@ class BonusBlock final : public Block std::string m_script; SurfacePtr m_lightsprite; sexp::Value m_custom_sx; + std::string m_coin_sprite; private: BonusBlock(const BonusBlock&) = delete; diff --git a/src/object/brick.cpp b/src/object/brick.cpp index d6b7c54a53d..fc9b3829968 100644 --- a/src/object/brick.cpp +++ b/src/object/brick.cpp @@ -24,18 +24,15 @@ #include "object/explosion.hpp" #include "object/player.hpp" #include "object/portable.hpp" -#include "sprite/sprite.hpp" -#include "sprite/sprite_manager.hpp" #include "supertux/constants.hpp" #include "supertux/sector.hpp" #include "util/reader_mapping.hpp" -Brick::Brick(const Vector& pos, int data, const std::string& spriteName) : - Block(SpriteManager::current()->create(spriteName)), +Brick::Brick(const Vector& pos, int data, const std::string& sprite_name) : + Block(pos, sprite_name), m_breakable(false), m_coin_counter(0) { - m_col.m_bbox.set_pos(pos); if (data == 1) { m_coin_counter = 5; } else { @@ -43,8 +40,8 @@ Brick::Brick(const Vector& pos, int data, const std::string& spriteName) : } } -Brick::Brick(const ReaderMapping& mapping, const std::string& spriteName) : - Block(mapping, spriteName), +Brick::Brick(const ReaderMapping& mapping, const std::string& sprite_name) : + Block(mapping, sprite_name), m_breakable(), m_coin_counter(0) { @@ -67,10 +64,7 @@ HitResponse Brick::collision(GameObject& other, const CollisionHit& hit) { auto player = dynamic_cast (&other); - if (player) { - if (player->m_does_buttjump) try_break(player); - if (player->is_stone() && player->get_velocity().y >= 280) try_break(player); // stoneform breaks through bricks - } + if (player && player->m_does_buttjump) try_break(player); auto badguy = dynamic_cast (&other); if (badguy) { @@ -118,7 +112,7 @@ Brick::try_break(Player* player, bool slider) Player& player_one = *Sector::get().get_players()[0]; player_one.get_status().add_coins(1); if (m_coin_counter == 0) - m_sprite->set_action("empty"); + set_action("empty"); start_bounce(player); } else if (m_breakable) { if (player) { @@ -166,13 +160,7 @@ HitResponse HeavyBrick::collision(GameObject& other, const CollisionHit& hit) { auto player = dynamic_cast(&other); - if (player) - { - if (player->is_stone() && player->get_velocity().y >= 280) - try_break(player); - else if (player->m_does_buttjump) - ricochet(&other); - } + if (player && player->m_does_buttjump) ricochet(&other); auto crusher = dynamic_cast (&other); if (crusher) diff --git a/src/object/brick.hpp b/src/object/brick.hpp index bc427aef399..2a1b5353c89 100644 --- a/src/object/brick.hpp +++ b/src/object/brick.hpp @@ -17,14 +17,15 @@ #ifndef HEADER_SUPERTUX_OBJECT_BRICK_HPP #define HEADER_SUPERTUX_OBJECT_BRICK_HPP -#include "badguy/crusher.hpp" #include "object/block.hpp" +#include "badguy/crusher.hpp" + class Brick : public Block { public: - Brick(const Vector& pos, int data, const std::string& spriteName); - Brick(const ReaderMapping& mapping, const std::string& spriteName = "images/objects/bonus_block/brick.sprite"); + Brick(const Vector& pos, int data, const std::string& sprite_name); + Brick(const ReaderMapping& mapping, const std::string& sprite_name = "images/objects/bonus_block/brick.sprite"); virtual HitResponse collision(GameObject& other, const CollisionHit& hit) override; virtual ObjectSettings get_settings() override; diff --git a/src/object/bumper.cpp b/src/object/bumper.cpp index cd71ef39de7..0fd0caa3fd2 100644 --- a/src/object/bumper.cpp +++ b/src/object/bumper.cpp @@ -1,93 +1,103 @@ -// Copyright (C) 2020 Daniel Ward -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -#include "object/bumper.hpp" - -#include "audio/sound_manager.hpp" -#include "object/player.hpp" -#include "sprite/sprite.hpp" -#include "sprite/sprite_manager.hpp" -#include "supertux/flip_level_transformer.hpp" -#include "supertux/sector.hpp" -#include "util/reader_mapping.hpp" - -namespace { -const std::string TRAMPOLINE_SOUND = "sounds/trampoline.wav"; -const float BOUNCE_Y = -450.0f; -const float BOUNCE_X = 700.0f; -} - -Bumper::Bumper(const ReaderMapping& reader) : - MovingSprite(reader, "images/objects/trampoline/bumper.sprite", LAYER_OBJECTS, COLGROUP_MOVING), - physic(), - left() -{ - reader.get("left", left); - m_sprite->set_action(left ? "left-normal" : "right-normal"); - physic.enable_gravity(false); -} - -ObjectSettings -Bumper::get_settings() -{ - ObjectSettings result = MovingSprite::get_settings(); - - result.add_bool(_("Facing Left"), &left, "left", false); - - result.reorder({"left", "sprite", "x", "y"}); - - return result; -} - -void -Bumper::update(float dt_sec) -{ - if (m_sprite->animation_done()) - { - m_sprite->set_action(left ? "left-normal" : "right-normal"); - } - m_col.set_movement(physic.get_movement (dt_sec)); -} - -HitResponse -Bumper::collision(GameObject& other, const CollisionHit& hit) -{ - auto player = dynamic_cast (&other); - if (player) - { - float BOUNCE_DIR = left ? -BOUNCE_X : BOUNCE_X; - player->get_physic().set_velocity(0.f, player->is_swimming() ? 0.f : BOUNCE_Y); - player->sideways_push(BOUNCE_DIR); - SoundManager::current()->play(TRAMPOLINE_SOUND, get_pos()); - m_sprite->set_action((left ? "left-swinging" : "right-swinging"), 1); - } - - auto bumper = dynamic_cast (&other); - if (bumper) - { - physic.set_velocity_y(0); - return FORCE_MOVE; - } - return ABORT_MOVE; -} - -void -Bumper::on_flip(float height) -{ - MovingSprite::on_flip(height); - FlipLevelTransformer::transform_flip(m_flip); -} - -/* EOF */ +// Copyright (C) 2020 Daniel Ward +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "object/bumper.hpp" + +#include "audio/sound_manager.hpp" +#include "object/player.hpp" +#include "sprite/sprite.hpp" +#include "sprite/sprite_manager.hpp" +#include "supertux/flip_level_transformer.hpp" +#include "supertux/sector.hpp" +#include "util/reader_mapping.hpp" + +namespace { +const std::string TRAMPOLINE_SOUND = "sounds/trampoline.wav"; +const float BOUNCE_Y = -450.0f; +const float BOUNCE_X = 700.0f; +} + +Bumper::Bumper(const ReaderMapping& reader) : + MovingSprite(reader, "images/objects/trampoline/bumper.sprite", LAYER_OBJECTS, COLGROUP_MOVING), + m_physic(), + m_facing_left() +{ + reader.get("left", m_facing_left); + set_action(m_facing_left ? "left-normal" : "right-normal"); + m_physic.enable_gravity(false); +} + +ObjectSettings +Bumper::get_settings() +{ + ObjectSettings result = MovingSprite::get_settings(); + + result.add_bool(_("Facing Left"), &m_facing_left, "left", false); + result.reorder({"left", "sprite", "x", "y"}); + return result; +} + +void +Bumper::update(float dt_sec) +{ + if (m_sprite->animation_done()) + { + set_action(m_facing_left ? "left-normal" : "right-normal"); + } + m_col.set_movement(m_physic.get_movement (dt_sec)); +} + +HitResponse +Bumper::collision(GameObject& other, const CollisionHit& hit) +{ + auto player = dynamic_cast (&other); + if (player) + { + float BOUNCE_DIR = m_facing_left ? -BOUNCE_X : BOUNCE_X; + player->get_physic().set_velocity(0.f, player->is_swimming() ? 0.f : BOUNCE_Y); + player->sideways_push(BOUNCE_DIR); + SoundManager::current()->play(TRAMPOLINE_SOUND, get_pos()); + set_action((m_facing_left ? "left-swinging" : "right-swinging"), 1); + } + auto bumper = dynamic_cast (&other); + if (bumper) + { + m_physic.set_velocity_y(0); + return FORCE_MOVE; + } + return ABORT_MOVE; +} + +Physic& +Bumper::get_physic() +{ + return m_physic; +} + +void +Bumper::after_editor_set() +{ + MovingSprite::after_editor_set(); + set_action(m_facing_left ? "left-normal" : "right-normal"); +} + +void +Bumper::on_flip(float height) +{ + MovingSprite::on_flip(height); + FlipLevelTransformer::transform_flip(m_flip); +} + +/* EOF */ diff --git a/src/object/bumper.hpp b/src/object/bumper.hpp index 0d755131f26..65258cffac7 100644 --- a/src/object/bumper.hpp +++ b/src/object/bumper.hpp @@ -1,53 +1,56 @@ -// Copyright (C) 2020 Daniel Ward -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -#ifndef HEADER_SUPERTUX_OBJECT_BUMPER_HPP -#define HEADER_SUPERTUX_OBJECT_BUMPER_HPP - -#include "object/moving_sprite.hpp" -#include "supertux/physic.hpp" - -class Player; - -class Bumper final : public MovingSprite -{ -public: - Bumper(const ReaderMapping& reader); - - virtual ObjectSettings get_settings() override; - - virtual void update(float dt_sec) override; - virtual HitResponse collision(GameObject& other, const CollisionHit& hit) override; - - static std::string class_name() { return "bumper"; } - virtual std::string get_class_name() const override { return class_name(); } - static std::string display_name() { return _("Bumper"); } - virtual std::string get_display_name() const override { return display_name(); } - - virtual void on_flip(float height) override; - - Physic physic; - -private: - bool left; - -private: - Bumper(const Bumper&) = delete; - Bumper& operator=(const Bumper&) = delete; -}; - -#endif - -/* EOF */ +// Copyright (C) 2020 Daniel Ward +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef HEADER_SUPERTUX_OBJECT_BUMPER_HPP +#define HEADER_SUPERTUX_OBJECT_BUMPER_HPP + +#include "object/moving_sprite.hpp" + +#include "supertux/physic.hpp" + +class Player; + +class Bumper final : public MovingSprite +{ +public: + Bumper(const ReaderMapping& reader); + + virtual ObjectSettings get_settings() override; + + virtual void update(float dt_sec) override; + virtual HitResponse collision(GameObject& other, const CollisionHit& hit) override; + + static std::string class_name() { return "bumper"; } + virtual std::string get_class_name() const override { return class_name(); } + static std::string display_name() { return _("Bumper"); } + virtual std::string get_display_name() const override { return display_name(); } + + virtual void after_editor_set() override; + virtual void on_flip(float height) override; + + Physic& get_physic(); + +private: + Physic m_physic; + bool m_facing_left; + +private: + Bumper(const Bumper&) = delete; + Bumper& operator=(const Bumper&) = delete; +}; + +#endif + +/* EOF */ diff --git a/src/object/candle.cpp b/src/object/candle.cpp index 1483df84854..f415920016c 100644 --- a/src/object/candle.cpp +++ b/src/object/candle.cpp @@ -52,9 +52,9 @@ Candle::Candle(const ReaderMapping& mapping) : } if (burning) { - m_sprite->set_action("on"); + set_action("on"); } else { - m_sprite->set_action("off"); + set_action("off"); } } @@ -67,7 +67,7 @@ Candle::after_editor_set() candle_light_1->set_color(lightcolor); candle_light_2->set_color(lightcolor); - m_sprite->set_action(burning ? "on" : "off"); + set_action(burning ? "on" : "off"); } ObjectSettings @@ -136,9 +136,9 @@ Candle::set_burning(bool burning_) if (burning == burning_) return; burning = burning_; if (burning_) { - m_sprite->set_action("on"); + set_action("on"); } else { - m_sprite->set_action("off"); + set_action("off"); } //puff smoke for flickering light sources only if (flicker) puff_smoke(); diff --git a/src/object/coin.cpp b/src/object/coin.cpp index 5228e290a27..62a5cf97f14 100644 --- a/src/object/coin.cpp +++ b/src/object/coin.cpp @@ -28,8 +28,8 @@ #include "util/reader_mapping.hpp" #include "util/writer.hpp" -Coin::Coin(const Vector& pos, bool count_stats) : - MovingSprite(pos, "images/objects/coin/coin.sprite", LAYER_OBJECTS - 1, COLGROUP_TOUCHABLE), +Coin::Coin(const Vector& pos, bool count_stats, const std::string& sprite_path) : + MovingSprite(pos, sprite_path, LAYER_OBJECTS - 1, COLGROUP_TOUCHABLE), PathObject(), m_offset(0.0f, 0.0f), m_from_tilemap(false), @@ -210,8 +210,8 @@ Coin::collision(GameObject& other, const CollisionHit& ) } /* The following defines a coin subject to gravity */ -HeavyCoin::HeavyCoin(const Vector& pos, const Vector& init_velocity, bool count_stats) : - Coin(pos, count_stats), +HeavyCoin::HeavyCoin(const Vector& pos, const Vector& init_velocity, bool count_stats, const std::string& sprite_path) : + Coin(pos, count_stats, sprite_path), m_physic(), m_last_hit() { diff --git a/src/object/coin.hpp b/src/object/coin.hpp index dfdb4a1cbcc..f67d7539307 100644 --- a/src/object/coin.hpp +++ b/src/object/coin.hpp @@ -32,7 +32,8 @@ class Coin : public MovingSprite, friend class HeavyCoin; public: - Coin(const Vector& pos, bool count_stats = true); + Coin(const Vector& pos, bool count_stats = true, + const std::string& sprite_path = "images/objects/coin/coin.sprite"); Coin(const ReaderMapping& reader, bool count_stats = true); virtual void finish_construction() override; @@ -73,7 +74,8 @@ friend class HeavyCoin; class HeavyCoin final : public Coin { public: - HeavyCoin(const Vector& pos, const Vector& init_velocity, bool count_stats = true); + HeavyCoin(const Vector& pos, const Vector& init_velocity, bool count_stats = true, + const std::string& sprite_path = "images/objects/coin/coin.sprite"); HeavyCoin(const ReaderMapping& reader, bool count_stats = true); virtual void update(float dt_sec) override; diff --git a/src/object/coin_explode.cpp b/src/object/coin_explode.cpp index 8f88a4ea2ac..c465f549590 100644 --- a/src/object/coin_explode.cpp +++ b/src/object/coin_explode.cpp @@ -20,7 +20,8 @@ #include "object/coin.hpp" #include "supertux/sector.hpp" -CoinExplode::CoinExplode(const Vector& pos, bool count_stats) : +CoinExplode::CoinExplode(const Vector& pos, bool count_stats, const std::string& sprite) : + m_sprite(sprite), position(pos), m_count_stats(count_stats) { @@ -32,16 +33,16 @@ CoinExplode::update(float ) float mag = 100.0f; // madnitude that coins are to be thrown float rand = 30.0f; // max variation to be subtracted from magnitide - Sector::get().add(position, Vector(2.5, -4.5) * (mag - gameRandom.randf(rand)), m_count_stats); - Sector::get().add(position, Vector(2, -5) * (mag - gameRandom.randf(rand)), m_count_stats); - Sector::get().add(position, Vector(1.5, -5.5) * (mag - gameRandom.randf(rand)), m_count_stats); - Sector::get().add(position, Vector(1, -6) * (mag+gameRandom.randf(rand)), m_count_stats); - Sector::get().add(position, Vector(0.5, -6.5) * (mag - gameRandom.randf(rand)), m_count_stats); - Sector::get().add(position, Vector(-2.5, -4.5) * (mag - gameRandom.randf(rand)), m_count_stats); - Sector::get().add(position, Vector(-2, -5) * (mag - gameRandom.randf(rand)), m_count_stats); - Sector::get().add(position, Vector(-1.5, -5.5) * (mag - gameRandom.randf(rand)), m_count_stats); - Sector::get().add(position, Vector(-1, -6) * (mag+gameRandom.randf(rand)), m_count_stats); - Sector::get().add(position, Vector(-0.5, -6.5) * (mag - gameRandom.randf(rand)), m_count_stats); + Sector::get().add(position, Vector(2.5, -4.5) * (mag - gameRandom.randf(rand)), m_count_stats, m_sprite); + Sector::get().add(position, Vector(2, -5) * (mag - gameRandom.randf(rand)), m_count_stats, m_sprite); + Sector::get().add(position, Vector(1.5, -5.5) * (mag - gameRandom.randf(rand)), m_count_stats, m_sprite); + Sector::get().add(position, Vector(1, -6) * (mag+gameRandom.randf(rand)), m_count_stats, m_sprite); + Sector::get().add(position, Vector(0.5, -6.5) * (mag - gameRandom.randf(rand)), m_count_stats, m_sprite); + Sector::get().add(position, Vector(-2.5, -4.5) * (mag - gameRandom.randf(rand)), m_count_stats, m_sprite); + Sector::get().add(position, Vector(-2, -5) * (mag - gameRandom.randf(rand)), m_count_stats, m_sprite); + Sector::get().add(position, Vector(-1.5, -5.5) * (mag - gameRandom.randf(rand)), m_count_stats, m_sprite); + Sector::get().add(position, Vector(-1, -6) * (mag+gameRandom.randf(rand)), m_count_stats, m_sprite); + Sector::get().add(position, Vector(-0.5, -6.5) * (mag - gameRandom.randf(rand)), m_count_stats, m_sprite); remove_me(); } diff --git a/src/object/coin_explode.hpp b/src/object/coin_explode.hpp index 8b09ff43f01..b8ec2233e8d 100644 --- a/src/object/coin_explode.hpp +++ b/src/object/coin_explode.hpp @@ -23,7 +23,8 @@ class CoinExplode final : public GameObject { public: - CoinExplode(const Vector& pos, bool count_stats = true); + CoinExplode(const Vector& pos, bool count_stats = true, + const std::string& sprite_path = "images/objects/coin/coin.sprite"); virtual void update(float dt_sec) override; virtual void draw(DrawingContext& context) override; virtual bool is_saveable() const override { @@ -31,6 +32,7 @@ class CoinExplode final : public GameObject } private: + std::string m_sprite; Vector position; const bool m_count_stats; }; diff --git a/src/object/coin_rain.cpp b/src/object/coin_rain.cpp index b33e29450ab..92c3f81aab7 100644 --- a/src/object/coin_rain.cpp +++ b/src/object/coin_rain.cpp @@ -24,8 +24,9 @@ static const float DROP_TIME = .1f; // time duration between "drops" of coin rain -CoinRain::CoinRain(const Vector& pos, bool emerge, bool count_stats) : - sprite(SpriteManager::current()->create("images/objects/coin/coin.sprite")), +CoinRain::CoinRain(const Vector& pos, bool emerge, bool count_stats, const std::string& sprite_path) : + sprite(SpriteManager::current()->create(sprite_path)), + m_sprite_path(sprite_path), position(pos), emerge_distance(0), timer(), @@ -50,7 +51,7 @@ CoinRain::update(float dt_sec) else if (counter==0){ drop = gameRandom.rand(10); Sector::get().add(Vector(position.x + 32.0f * static_cast((drop < 5) ? -drop - 1 : drop - 4), -32.0f), - Vector(0, 0), m_count_stats); + Vector(0, 0), m_count_stats, m_sprite_path); counter++; timer.start(DROP_TIME); } // finally the remainder of the coins drop in a determined but appears to be a random order @@ -59,7 +60,7 @@ CoinRain::update(float dt_sec) drop += 7; if (drop >= 10) drop -=10; Sector::get().add(Vector(position.x + 32.0f * static_cast((drop < 5) ? -drop - 1 : drop - 4), -32.0f), - Vector(0, 0), m_count_stats); + Vector(0, 0), m_count_stats, m_sprite_path); counter++; timer.start(DROP_TIME); } else { diff --git a/src/object/coin_rain.hpp b/src/object/coin_rain.hpp index 7c76d3ed9c8..55a8ce5b222 100644 --- a/src/object/coin_rain.hpp +++ b/src/object/coin_rain.hpp @@ -25,7 +25,8 @@ class CoinRain final : public GameObject { public: - CoinRain(const Vector& pos, bool emerge=false, bool count_stats = true); + CoinRain(const Vector& pos, bool emerge=false, bool count_stats = true, + const std::string& sprite_path = "images/objects/coin/coin.sprite"); virtual void update(float dt_sec) override; virtual void draw(DrawingContext& context) override; virtual bool is_saveable() const override { @@ -34,6 +35,7 @@ class CoinRain final : public GameObject private: SpritePtr sprite; + std::string m_sprite_path; Vector position; float emerge_distance; Timer timer; diff --git a/src/object/conveyor_belt.cpp b/src/object/conveyor_belt.cpp index 5583626bf0a..97af510e8a3 100644 --- a/src/object/conveyor_belt.cpp +++ b/src/object/conveyor_belt.cpp @@ -24,15 +24,14 @@ #include "util/reader_mapping.hpp" ConveyorBelt::ConveyorBelt(const ReaderMapping &reader) : - MovingObject(reader), // TODO: sprite + MovingSprite(reader, "images/objects/conveyor_belt/conveyor.sprite"), ExposedObject(this), m_running(true), m_dir(Direction::LEFT), m_length(1), m_speed(1.0f), m_frame(0.0f), - m_frame_index(0), - m_sprite(SpriteManager::current()->create("images/objects/conveyor_belt/conveyor.sprite")) + m_frame_index(0) { set_group(COLGROUP_STATIC); reader.get("running", m_running); @@ -47,18 +46,16 @@ ConveyorBelt::ConveyorBelt(const ReaderMapping &reader) : if (m_length <= 0) m_length = 1; - m_col.m_bbox.set_size(32.0f * static_cast(m_length), 32.0f); - if (!m_running) - m_sprite->set_action("stopped"); + set_action("stopped"); else - m_sprite->set_action(m_dir); + set_action(m_dir); } ObjectSettings ConveyorBelt::get_settings() { - ObjectSettings result = MovingObject::get_settings(); + ObjectSettings result = MovingSprite::get_settings(); result.add_direction(_("Direction"), &m_dir, Direction::LEFT, "direction"); result.add_float(_("Speed"), &m_speed, "speed", 1.0f); @@ -117,26 +114,35 @@ ConveyorBelt::draw(DrawingContext &context) } } +void +ConveyorBelt::update_hitbox() +{ + m_col.m_bbox.set_size(m_sprite->get_current_hitbox_width() * static_cast(m_length), + m_sprite->get_current_hitbox_height()); +} + void ConveyorBelt::after_editor_set() { + MovingSprite::after_editor_set(); + if (m_length <= 0) m_length = 1; - m_col.m_bbox.set_size(32.0f * static_cast(m_length), 32.0f); + set_action(dir_to_string(m_dir)); } void ConveyorBelt::start() { m_running = true; - m_sprite->set_action(m_dir); + set_action(m_dir); } void ConveyorBelt::stop() { m_running = false; - m_sprite->set_action("stopped"); + set_action("stopped"); } void @@ -144,7 +150,7 @@ ConveyorBelt::move_left() { m_dir = Direction::LEFT; if (m_running) - m_sprite->set_action("left"); + set_action("left"); } void @@ -152,7 +158,7 @@ ConveyorBelt::move_right() { m_dir = Direction::RIGHT; if (m_running) - m_sprite->set_action("right"); + set_action("right"); } void diff --git a/src/object/conveyor_belt.hpp b/src/object/conveyor_belt.hpp index a8c0c742b76..c3228811b1e 100644 --- a/src/object/conveyor_belt.hpp +++ b/src/object/conveyor_belt.hpp @@ -17,17 +17,14 @@ #ifndef HEADER_SUPERTUX_OBJECT_CONVEYOR_BELT_HPP #define HEADER_SUPERTUX_OBJECT_CONVEYOR_BELT_HPP +#include "object/moving_sprite.hpp" #include "squirrel/exposed_object.hpp" -#include "supertux/moving_object.hpp" #include "scripting/conveyor_belt.hpp" #include "supertux/timer.hpp" -#include "video/layer.hpp" - -class Sprite; /** This class represents a platform that moves entities riding it. */ -class ConveyorBelt final : public MovingObject, +class ConveyorBelt final : public MovingSprite, public ExposedObject { public: @@ -66,6 +63,9 @@ class ConveyorBelt final : public MovingObject, /** Changes the shifting speed of the conveyor. */ void set_speed(float target_speed); +private: + void update_hitbox() override; + private: bool m_running; Direction m_dir; @@ -77,8 +77,6 @@ class ConveyorBelt final : public MovingObject, const float MAX_SPEED = 32.0f; - std::unique_ptr m_sprite; - private: ConveyorBelt(const ConveyorBelt&) = delete; ConveyorBelt& operator=(const ConveyorBelt&) = delete; diff --git a/src/object/custom_particle_system.cpp b/src/object/custom_particle_system.cpp index fb7dace1bb3..feb6cb204bd 100644 --- a/src/object/custom_particle_system.cpp +++ b/src/object/custom_particle_system.cpp @@ -741,7 +741,6 @@ CustomParticleSystem::update(float dt_sec) auto c = get_collision(particle, Vector(particle->speedX, particle->speedY) * dt_sec); float speed_angle = atanf(-particle->speedY / particle->speedX); - float face_angle = atanf(c.slope_normal.y / c.slope_normal.x); if (c.slope_normal.x == 0.f && c.slope_normal.y == 0.f) { auto cX = get_collision(particle, Vector(particle->speedX, 0) * dt_sec); if (cX.left != cX.right) @@ -750,6 +749,7 @@ CustomParticleSystem::update(float dt_sec) if (cY.top != cY.bottom) particle->speedY *= -1; } else { + float face_angle = atanf(c.slope_normal.y / c.slope_normal.x); float dest_angle = face_angle * 2.f - speed_angle; // Reflect the angle around face_angle float dX = cosf(dest_angle), dY = sinf(dest_angle); diff --git a/src/object/fallblock.cpp b/src/object/fallblock.cpp index a311e9c21de..4cf0c254012 100644 --- a/src/object/fallblock.cpp +++ b/src/object/fallblock.cpp @@ -1,174 +1,174 @@ -// Copyright (C) 2020 Daniel Ward -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -#include "object/fallblock.hpp" - -#include "audio/sound_manager.hpp" -#include "object/bumper.hpp" -#include "object/player.hpp" -#include "object/camera.hpp" -#include "sprite/sprite.hpp" -#include "supertux/flip_level_transformer.hpp" -#include "supertux/sector.hpp" -#include "supertux/tile.hpp" -#include "math/random.hpp" -#include "util/reader_mapping.hpp" - -FallBlock::FallBlock(const ReaderMapping& reader) : - MovingSprite(reader, "images/objects/fallblock/cave-4x4.sprite", LAYER_OBJECTS, COLGROUP_STATIC), - state(IDLE), - physic(), - timer() -{ - SoundManager::current()->preload("sounds/cracking.wav"); - SoundManager::current()->preload("sounds/thud.ogg"); - physic.enable_gravity(false); -} - -void -FallBlock::update(float dt_sec) -{ - switch (state) - { - case IDLE: - set_group(COLGROUP_STATIC); - if (found_victim_down()) - { - state = SHAKE; - SoundManager::current()->play("sounds/cracking.wav", get_pos()); - timer.start(0.5f); - } - break; - case SHAKE: - if (timer.check()) - { - state = FALL; - physic.reset(); - physic.enable_gravity(true); - } - break; - case FALL: - m_col.set_movement(physic.get_movement (dt_sec)); - set_group(COLGROUP_MOVING_STATIC); - break; - case LAND: - m_col.set_movement(physic.get_movement (dt_sec)); - set_group(COLGROUP_MOVING_STATIC); - break; - } - for (auto& bumper : Sector::get().get_objects_by_type()) - { - Rectf bumper_bbox = bumper.get_bbox(); - if ((bumper_bbox.get_left() < (m_col.m_bbox.get_right() + 8)) - && (bumper_bbox.get_right() > (m_col.m_bbox.get_left() - 8)) - && (bumper_bbox.get_bottom() > (m_col.m_bbox.get_top() - 8)) - && (bumper_bbox.get_top() < (m_col.m_bbox.get_bottom() + 8))) - { - switch (state) - { - case IDLE: - break; - case SHAKE: - break; - case FALL: - bumper.physic.enable_gravity(true); - break; - case LAND: - bumper.physic.enable_gravity(false); - bumper.physic.set_gravity_modifier(0.f); - bumper.physic.set_velocity_y(0.f); - bumper.physic.reset(); - break; - } - } - } -} - -HitResponse -FallBlock::collision(GameObject& other, const CollisionHit& hit) -{ - auto fallblock = dynamic_cast (&other); - if (fallblock && hit.bottom && (state == FALL || state == LAND)) - { - physic.set_velocity_y(0.0f); - return CONTINUE; - } - - auto player = dynamic_cast(&other); - if (state == IDLE && player && player->get_bbox().get_bottom() < m_col.m_bbox.get_top()) - { - state = SHAKE; - SoundManager::current()->play("sounds/cracking.wav", get_pos()); - timer.start(0.5f); - } - return FORCE_MOVE; -} - -void -FallBlock::collision_solid(const CollisionHit& hit) -{ - if (hit.top || hit.bottom || hit.crush) - { - physic.set_velocity(0.0f, 0.0f); - } - - if (state == FALL && hit.bottom) - { - Sector::get().get_camera().shake(0.125f, 0.0f, 10.0f); - SoundManager::current()->play("sounds/thud.ogg", get_pos()); - state = LAND; - } -} - -void -FallBlock::draw(DrawingContext& context) -{ - Vector pos = get_pos(); - // shaking - if (state == SHAKE) - { - pos.x += static_cast(graphicsRandom.rand(-8, 8)); - pos.y += static_cast(graphicsRandom.rand(-5, 5)); - } - MovingSprite::draw(context); -} - -bool -FallBlock::found_victim_down() const -{ - if (auto* player = Sector::get().get_nearest_player(m_col.m_bbox)) - { - const Rectf& player_bbox = player->get_bbox(); - Rectf crush_area_down = Rectf(m_col.m_bbox.get_left()+1, m_col.m_bbox.get_bottom(), - m_col.m_bbox.get_right()-1, std::max(m_col.m_bbox.get_bottom(),player_bbox.get_top()-1)); - if ((player_bbox.get_top() >= m_col.m_bbox.get_bottom()) - && (player_bbox.get_right() > (m_col.m_bbox.get_left() - 4)) - && (player_bbox.get_left() < (m_col.m_bbox.get_right() + 4)) - && (Sector::get().is_free_of_statics(crush_area_down, this, false))) - { - return true; - } - } - return false; -} - -void -FallBlock::on_flip(float height) -{ - MovingSprite::on_flip(height); - FlipLevelTransformer::transform_flip(m_flip); -} - -/* EOF */ +// Copyright (C) 2020 Daniel Ward +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "object/fallblock.hpp" + +#include "audio/sound_manager.hpp" +#include "object/bumper.hpp" +#include "object/player.hpp" +#include "object/camera.hpp" +#include "sprite/sprite.hpp" +#include "supertux/flip_level_transformer.hpp" +#include "supertux/sector.hpp" +#include "supertux/tile.hpp" +#include "math/random.hpp" +#include "util/reader_mapping.hpp" + +FallBlock::FallBlock(const ReaderMapping& reader) : + MovingSprite(reader, "images/objects/fallblock/cave-4x4.sprite", LAYER_OBJECTS, COLGROUP_STATIC), + m_state(IDLE), + m_physic(), + m_timer() +{ + SoundManager::current()->preload("sounds/cracking.wav"); + SoundManager::current()->preload("sounds/thud.ogg"); + m_physic.enable_gravity(false); +} + +void +FallBlock::update(float dt_sec) +{ + switch (m_state) + { + case IDLE: + set_group(COLGROUP_STATIC); + if (found_victim_down()) + { + m_state = SHAKE; + SoundManager::current()->play("sounds/cracking.wav", get_pos()); + m_timer.start(0.5f); + } + break; + case SHAKE: + if (m_timer.check()) + { + m_state = FALL; + m_physic.reset(); + m_physic.enable_gravity(true); + } + break; + case FALL: + m_col.set_movement(m_physic.get_movement (dt_sec)); + set_group(COLGROUP_MOVING_STATIC); + break; + case LAND: + m_col.set_movement(m_physic.get_movement (dt_sec)); + set_group(COLGROUP_MOVING_STATIC); + break; + } + for (auto& bumper : Sector::get().get_objects_by_type()) + { + Rectf bumper_bbox = bumper.get_bbox(); + if ((bumper_bbox.get_left() < (m_col.m_bbox.get_right() + 8)) + && (bumper_bbox.get_right() > (m_col.m_bbox.get_left() - 8)) + && (bumper_bbox.get_bottom() > (m_col.m_bbox.get_top() - 8)) + && (bumper_bbox.get_top() < (m_col.m_bbox.get_bottom() + 8))) + { + switch (m_state) + { + case IDLE: + break; + case SHAKE: + break; + case FALL: + bumper.get_physic().enable_gravity(true); + break; + case LAND: + bumper.get_physic().enable_gravity(false); + bumper.get_physic().set_gravity_modifier(0.f); + bumper.get_physic().set_velocity_y(0.f); + bumper.get_physic().reset(); + break; + } + } + } +} + +HitResponse +FallBlock::collision(GameObject& other, const CollisionHit& hit) +{ + auto fallblock = dynamic_cast (&other); + if (fallblock && hit.bottom && (m_state == FALL || m_state == LAND)) + { + m_physic.set_velocity_y(0.0f); + return CONTINUE; + } + + auto player = dynamic_cast(&other); + if (m_state == IDLE && player && player->get_bbox().get_bottom() < m_col.m_bbox.get_top()) + { + m_state = SHAKE; + SoundManager::current()->play("sounds/cracking.wav", get_pos()); + m_timer.start(0.5f); + } + return FORCE_MOVE; +} + +void +FallBlock::collision_solid(const CollisionHit& hit) +{ + if (hit.top || hit.bottom || hit.crush) + { + m_physic.set_velocity(0.0f, 0.0f); + } + + if (m_state == FALL && hit.bottom) + { + Sector::get().get_camera().shake(0.125f, 0.0f, 10.0f); + SoundManager::current()->play("sounds/thud.ogg", get_pos()); + m_state = LAND; + } +} + +void +FallBlock::draw(DrawingContext& context) +{ + Vector pos = get_pos(); + // shaking + if (m_state == SHAKE) + { + pos.x += static_cast(graphicsRandom.rand(-8, 8)); + pos.y += static_cast(graphicsRandom.rand(-5, 5)); + } + MovingSprite::draw(context); +} + +bool +FallBlock::found_victim_down() const +{ + if (auto* player = Sector::get().get_nearest_player(m_col.m_bbox)) + { + const Rectf& player_bbox = player->get_bbox(); + Rectf crush_area_down = Rectf(m_col.m_bbox.get_left()+1, m_col.m_bbox.get_bottom(), + m_col.m_bbox.get_right()-1, std::max(m_col.m_bbox.get_bottom(),player_bbox.get_top()-1)); + if ((player_bbox.get_top() >= m_col.m_bbox.get_bottom()) + && (player_bbox.get_right() > (m_col.m_bbox.get_left() - 4)) + && (player_bbox.get_left() < (m_col.m_bbox.get_right() + 4)) + && (Sector::get().is_free_of_statics(crush_area_down, this, false))) + { + return true; + } + } + return false; +} + +void +FallBlock::on_flip(float height) +{ + MovingSprite::on_flip(height); + FlipLevelTransformer::transform_flip(m_flip); +} + +/* EOF */ diff --git a/src/object/fallblock.hpp b/src/object/fallblock.hpp index b84b4222921..ab3b6c4a03e 100644 --- a/src/object/fallblock.hpp +++ b/src/object/fallblock.hpp @@ -1,69 +1,70 @@ -// Copyright (C) 2020 Daniel Ward -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -#ifndef HEADER_SUPERTUX_OBJECT_FALLBLOCK_HPP -#define HEADER_SUPERTUX_OBJECT_FALLBLOCK_HPP - -#include "object/moving_sprite.hpp" -#include "supertux/physic.hpp" -#include "supertux/timer.hpp" - -class Player; - -class FallBlock : public MovingSprite - -{ -public: - FallBlock(const ReaderMapping& reader); - - virtual void update(float dt_sec) override; - - virtual HitResponse collision(GameObject& other, const CollisionHit& hit) override; - virtual void collision_solid(const CollisionHit& hit) override; - - virtual void draw(DrawingContext& context) override; - - static std::string class_name() { return "fallblock"; } - virtual std::string get_class_name() const override { return class_name(); } - static std::string display_name() { return _("Falling Platform"); } - virtual std::string get_display_name() const override { return display_name(); } - - virtual void on_flip(float height) override; - -protected: - enum State - { - IDLE, - SHAKE, - FALL, - LAND - }; - -private: - State state; - - Physic physic; - Timer timer; - - bool found_victim_down() const; - -private: - FallBlock(const FallBlock&) = delete; - FallBlock& operator=(const FallBlock&) = delete; -}; - -#endif - -/* EOF */ +// Copyright (C) 2020 Daniel Ward +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef HEADER_SUPERTUX_OBJECT_FALLBLOCK_HPP +#define HEADER_SUPERTUX_OBJECT_FALLBLOCK_HPP + +#include "object/moving_sprite.hpp" + +#include "supertux/physic.hpp" +#include "supertux/timer.hpp" + +class Player; + +class FallBlock : public MovingSprite + +{ +public: + FallBlock(const ReaderMapping& reader); + + virtual void update(float dt_sec) override; + + virtual HitResponse collision(GameObject& other, const CollisionHit& hit) override; + virtual void collision_solid(const CollisionHit& hit) override; + + virtual void draw(DrawingContext& context) override; + + static std::string class_name() { return "fallblock"; } + virtual std::string get_class_name() const override { return class_name(); } + static std::string display_name() { return _("Falling Platform"); } + virtual std::string get_display_name() const override { return display_name(); } + + virtual void on_flip(float height) override; + +protected: + enum State + { + IDLE, + SHAKE, + FALL, + LAND + }; + +private: + State m_state; + + Physic m_physic; + Timer m_timer; + +private: + bool found_victim_down() const; + + FallBlock(const FallBlock&) = delete; + FallBlock& operator=(const FallBlock&) = delete; +}; + +#endif + +/* EOF */ diff --git a/src/object/firefly.cpp b/src/object/firefly.cpp index 21c2ccbe85f..b579e8bc9bb 100644 --- a/src/object/firefly.cpp +++ b/src/object/firefly.cpp @@ -39,19 +39,6 @@ Firefly::Firefly(const ReaderMapping& mapping) : activated(false), initial_position(get_pos()) { - if (!mapping.get( "sprite", m_sprite_name)){ - update_state(); - return; - } - if (m_sprite_name.empty()) { - m_sprite_name = "images/objects/resetpoints/default-resetpoint.sprite"; - update_state(); - return; - } - //Replace sprite - m_sprite = SpriteManager::current()->create( m_sprite_name ); - m_col.m_bbox.set_size(m_sprite->get_current_hitbox_width(), m_sprite->get_current_hitbox_height()); - if (m_sprite_name.find("torch", 0) != std::string::npos) { m_sprite_light = SpriteManager::current()->create("images/objects/lightmap_light/lightmap_light-small.sprite"); m_sprite_light->set_blend(Blend::ADD); @@ -101,11 +88,11 @@ Firefly::update_state() active_checkpoint_spawnpoint->sector == Sector::get().get_name() && active_checkpoint_spawnpoint->position == initial_position) // Is activated. { - m_sprite->set_action("ringing"); + set_action("ringing"); } else // Is deactivated. { - m_sprite->set_action("normal"); + set_action("normal"); } } @@ -142,7 +129,7 @@ Firefly::collision(GameObject& other, const CollisionHit& ) SoundManager::current()->play("sounds/savebell2.wav", get_pos()); } - m_sprite->set_action("ringing"); + set_action("ringing"); GameSession::current()->set_checkpoint_pos(Sector::get().get_name(), initial_position); } diff --git a/src/object/invisible_block.cpp b/src/object/invisible_block.cpp index 5d7774f5382..403c010b9e6 100644 --- a/src/object/invisible_block.cpp +++ b/src/object/invisible_block.cpp @@ -19,22 +19,19 @@ #include "audio/sound_manager.hpp" #include "editor/editor.hpp" #include "object/player.hpp" -#include "sprite/sprite.hpp" -#include "sprite/sprite_manager.hpp" #include "supertux/constants.hpp" InvisibleBlock::InvisibleBlock(const Vector& pos) : - Block(SpriteManager::current()->create("images/objects/bonus_block/invisibleblock.sprite")), - visible(false) + Block(pos, "images/objects/bonus_block/invisibleblock.sprite"), + visible(false) { - m_col.m_bbox.set_pos(pos); SoundManager::current()->preload("sounds/brick.wav"); - m_sprite->set_action("default-editor"); + set_action("default-editor"); } InvisibleBlock::InvisibleBlock(const ReaderMapping& mapping) : - Block(mapping, "images/objects/bonus_block/invisibleblock.sprite"), - visible(false) + Block(mapping, "images/objects/bonus_block/invisibleblock.sprite"), + visible(false) { SoundManager::current()->preload("sounds/brick.wav"); } @@ -77,7 +74,7 @@ InvisibleBlock::hit(Player& player) if (visible) return; - m_sprite->set_action("empty"); + set_action("empty"); start_bounce(&player); set_group(COLGROUP_STATIC); visible = true; diff --git a/src/object/ispy.cpp b/src/object/ispy.cpp index d6f795976c0..cd0fef1b0a1 100644 --- a/src/object/ispy.cpp +++ b/src/object/ispy.cpp @@ -128,10 +128,10 @@ Ispy::set_sprite_action(const std::string& action, int loops) { switch (m_dir) { - case Direction::DOWN: m_sprite->set_action(action + "-down", loops); break; - case Direction::UP: m_sprite->set_action(action + "-up", loops); break; - case Direction::LEFT: m_sprite->set_action(action + "-left", loops); break; - case Direction::RIGHT: m_sprite->set_action(action + "-right", loops); break; + case Direction::DOWN: set_action(action + "-down", loops); break; + case Direction::UP: set_action(action + "-up", loops); break; + case Direction::LEFT: set_action(action + "-left", loops); break; + case Direction::RIGHT: set_action(action + "-right", loops); break; default: break; } } @@ -143,12 +143,12 @@ Ispy::on_flip(float height) if (m_dir == Direction::UP) { m_dir = Direction::DOWN; - m_sprite->set_action("idle-down"); + set_action("idle-down"); } else if (m_dir == Direction::DOWN) { m_dir = Direction::UP; - m_sprite->set_action("idle-up"); + set_action("idle-up"); } } diff --git a/src/object/lantern.cpp b/src/object/lantern.cpp index f5f4907e6f6..9b979d0602e 100644 --- a/src/object/lantern.cpp +++ b/src/object/lantern.cpp @@ -79,10 +79,10 @@ Lantern::updateColor(){ lightsprite->set_color(lightcolor); //Turn lantern off if light is black if (lightcolor.red == 0 && lightcolor.green == 0 && lightcolor.blue == 0){ - m_sprite->set_action("off"); + set_action("off"); m_sprite->set_color(Color(1.0f, 1.0f, 1.0f)); } else { - m_sprite->set_action("normal"); + set_action("normal"); m_sprite->set_color(lightcolor); } } @@ -126,7 +126,7 @@ Lantern::grab(MovingObject& object, const Vector& pos, Direction dir) // if lantern is not lit, draw it as opened if (is_open()) { - m_sprite->set_action("off-open"); + set_action("off-open"); } } @@ -136,7 +136,7 @@ Lantern::ungrab(MovingObject& object, Direction dir) { // if lantern is not lit, it was drawn as opened while grabbed. Now draw it as closed again if (is_open()) { - m_sprite->set_action("off"); + set_action("off"); } Rock::ungrab(object, dir); diff --git a/src/object/lit_object.cpp b/src/object/lit_object.cpp index 607fd29cd86..eacd456c5a3 100644 --- a/src/object/lit_object.cpp +++ b/src/object/lit_object.cpp @@ -22,41 +22,29 @@ #include "util/reader_mapping.hpp" LitObject::LitObject(const ReaderMapping& reader) : - MovingObject(reader), + MovingSprite(reader, "images/objects/lightflower/lightflower1.sprite"), ExposedObject(this), m_light_offset(-6.f, -17.f), - m_sprite_name("images/objects/lightflower/lightflower1.sprite"), m_light_sprite_name("images/objects/lightflower/light/glow_light.sprite"), m_sprite_action("default"), m_light_sprite_action("default"), - m_sprite(), - m_light_sprite(), - m_layer(0), - m_flip(NO_FLIP) + m_light_sprite() { - reader.get("x", m_col.m_bbox.get_left()); - reader.get("y", m_col.m_bbox.get_top()); - reader.get("light-offset-x", m_light_offset.x); reader.get("light-offset-y", m_light_offset.y); - reader.get("sprite", m_sprite_name); reader.get("light-sprite", m_light_sprite_name); reader.get("layer", m_layer, 0); reader.get("action", m_sprite_action); reader.get("light-action", m_light_sprite_action); - m_sprite = SpriteManager::current()->create(m_sprite_name); m_light_sprite = SpriteManager::current()->create(m_light_sprite_name); m_light_sprite->set_blend(Blend::ADD); - m_sprite->set_action(m_sprite_action); + set_action(m_sprite_action); m_light_sprite->set_action(m_light_sprite_action); - m_col.m_bbox.set_size(static_cast(m_sprite->get_width()), - static_cast(m_sprite->get_height())); - set_group(COLGROUP_DISABLED); } @@ -75,9 +63,8 @@ LitObject::update(float) ObjectSettings LitObject::get_settings() { - ObjectSettings result = MovingObject::get_settings(); + ObjectSettings result = MovingSprite::get_settings(); - result.add_sprite(_("Sprite"), &m_sprite_name, "sprite", std::string("images/objects/lightflower/lightflower1.sprite")); result.add_sprite(_("Light sprite"), &m_light_sprite_name, "light-sprite", std::string("images/objects/lightflower/light/glow_light.sprite")); result.add_int(_("Layer"), &m_layer, "layer", 0); @@ -93,15 +80,13 @@ LitObject::get_settings() void LitObject::after_editor_set() { - m_sprite = SpriteManager::current()->create(m_sprite_name); + MovingSprite::after_editor_set(); + m_light_sprite = SpriteManager::current()->create(m_light_sprite_name); m_light_sprite->set_blend(Blend::ADD); - m_sprite->set_action(m_sprite_action); + set_action(m_sprite_action); m_light_sprite->set_action(m_light_sprite_action); - - m_col.m_bbox.set_size(static_cast(m_sprite->get_width()), - static_cast(m_sprite->get_height())); } void @@ -117,12 +102,6 @@ LitObject::get_action() const return m_sprite->get_action(); } -void -LitObject::set_action(const std::string& action) -{ - m_sprite->set_action(action); -} - const std::string& LitObject::get_light_action() const { diff --git a/src/object/lit_object.hpp b/src/object/lit_object.hpp index b22146e76d2..b44baf1b18c 100644 --- a/src/object/lit_object.hpp +++ b/src/object/lit_object.hpp @@ -17,17 +17,15 @@ #ifndef HEADER_SUPERTUX_OBJECT_LIT_OBJECT_HPP #define HEADER_SUPERTUX_OBJECT_LIT_OBJECT_HPP -#include "scripting/lit_object.hpp" -#include "sprite/sprite_ptr.hpp" +#include "object/moving_sprite.hpp" #include "squirrel/exposed_object.hpp" -#include "supertux/moving_object.hpp" -#include "video/flip.hpp" + +#include "scripting/lit_object.hpp" class ReaderMapping; -class LitObject final : - public MovingObject, - public ExposedObject +class LitObject final : public MovingSprite, + public ExposedObject { public: LitObject(const ReaderMapping& reader); @@ -35,8 +33,7 @@ class LitObject final : virtual void draw(DrawingContext& context) override; virtual void update(float) override; - virtual HitResponse collision(GameObject&, const CollisionHit&) override - { return ABORT_MOVE; } + virtual HitResponse collision(GameObject&, const CollisionHit&) override { return ABORT_MOVE; } static std::string class_name() { return "lit-object"; } virtual std::string get_class_name() const override { return class_name(); } @@ -51,20 +48,15 @@ class LitObject final : virtual void on_flip(float height) override; const std::string& get_action() const; - void set_action(const std::string& action); const std::string& get_light_action() const; void set_light_action(const std::string& action); private: Vector m_light_offset; - std::string m_sprite_name; std::string m_light_sprite_name; std::string m_sprite_action; std::string m_light_sprite_action; - SpritePtr m_sprite; SpritePtr m_light_sprite; - int m_layer; - Flip m_flip; private: LitObject(const LitObject&) = delete; diff --git a/src/object/magicblock.cpp b/src/object/magicblock.cpp index f88072905f1..66b92910051 100644 --- a/src/object/magicblock.cpp +++ b/src/object/magicblock.cpp @@ -176,11 +176,11 @@ MagicBlock::update(float dt_sec) if (m_is_solid) { m_solid_time+=dt_sec; m_color.alpha = ALPHA_SOLID; - m_sprite->set_action("solid"); + set_action("solid"); set_group(COLGROUP_STATIC); } else { m_color.alpha = ALPHA_NONSOLID; - m_sprite->set_action("normal"); + set_action("normal"); set_group(COLGROUP_DISABLED); } } diff --git a/src/object/moving_sprite.cpp b/src/object/moving_sprite.cpp index a34c5911628..2f0f7b9c53e 100644 --- a/src/object/moving_sprite.cpp +++ b/src/object/moving_sprite.cpp @@ -64,16 +64,13 @@ MovingSprite::MovingSprite(const ReaderMapping& reader, const std::string& sprit //Make the sprite go default when the sprite file is invalid if (m_sprite_name.empty() || !PHYSFS_exists(m_sprite_name.c_str())) { - m_sprite = SpriteManager::current()->create(m_default_sprite_name); + change_sprite(m_default_sprite_name); m_sprite_found = false; } - else + else if (!change_sprite(m_sprite_name)) // If sprite change fails, change back to default. { - if (!change_sprite(m_sprite_name)) // If sprite change fails, change back to default. - { - m_sprite = SpriteManager::current()->create(m_default_sprite_name); - m_sprite_found = false; - } + change_sprite(m_default_sprite_name); + m_sprite_found = false; } update_hitbox(); @@ -91,10 +88,8 @@ MovingSprite::MovingSprite(const ReaderMapping& reader, int layer_, CollisionGro { reader.get("x", m_col.m_bbox.get_left()); reader.get("y", m_col.m_bbox.get_top()); - if (!reader.get("sprite", m_sprite_name)) - throw std::runtime_error("no sprite name set"); + m_sprite_found = reader.get("sprite", m_sprite_name); - m_sprite_found = true; //m_default_sprite_name = m_sprite_name; m_sprite = SpriteManager::current()->create(m_sprite_name); update_hitbox(); @@ -182,20 +177,10 @@ MovingSprite::set_action(const std::string& action, int loops, AnchorPoint ancho bool MovingSprite::change_sprite(const std::string& new_sprite_name) { - SpritePtr new_sprite; - try - { - new_sprite = SpriteManager::current()->create(m_sprite_name); - } - catch (std::exception& err) - { - log_warning << "Sprite change failed: Sprite '" << new_sprite_name << "' cannot be loaded: " << err.what() << std::endl; - return false; - } - - m_sprite = std::move(new_sprite); + m_sprite = SpriteManager::current()->create(new_sprite_name); m_sprite_name = new_sprite_name; - return true; + + return SpriteManager::current()->last_load_successful(); } ObjectSettings @@ -216,7 +201,10 @@ MovingSprite::after_editor_set() MovingObject::after_editor_set(); std::string current_action = m_sprite->get_action(); - m_sprite = SpriteManager::current()->create(m_sprite_name); + if (!change_sprite(m_sprite_name)) // If sprite change fails, change back to default. + { + change_sprite(m_default_sprite_name); + } m_sprite->set_action(current_action); update_hitbox(); diff --git a/src/object/moving_sprite.hpp b/src/object/moving_sprite.hpp index fa3579d6d4e..97a56ee6aba 100644 --- a/src/object/moving_sprite.hpp +++ b/src/object/moving_sprite.hpp @@ -97,7 +97,7 @@ class MovingSprite : public MovingObject protected: /** Update hitbox, based on sprite. */ - void update_hitbox(); + virtual void update_hitbox(); protected: std::string m_sprite_name; diff --git a/src/object/path.hpp b/src/object/path.hpp index b00f0629b76..04bee8ee6dc 100644 --- a/src/object/path.hpp +++ b/src/object/path.hpp @@ -73,8 +73,6 @@ class Path final easing() {} - static std::string display_name() { return _("Path Node"); } - Path& get_parent() const { return *parent; } }; diff --git a/src/object/path_gameobject.cpp b/src/object/path_gameobject.cpp index 4ef36d849fd..fb99f82ddb0 100644 --- a/src/object/path_gameobject.cpp +++ b/src/object/path_gameobject.cpp @@ -24,6 +24,7 @@ #include "sprite/sprite.hpp" #include "sprite/sprite_manager.hpp" #include "supertux/debug.hpp" +#include "supertux/level.hpp" #include "supertux/sector.hpp" #include "util/log.hpp" #include "util/reader_mapping.hpp" @@ -215,11 +216,12 @@ PathGameObject::is_saveable() const if (!Sector::current()) return false; - const auto& path_objects = Sector::get().get_objects_by_type(); - - for (const auto& path_obj : path_objects) - if (path_obj.get_path_gameobject() == this) - return true; + for (const auto& sector : Level::current()->get_sectors()) + { + for (const auto& path_obj : sector->get_objects_by_type()) + if (path_obj.get_path_gameobject() == this) + return true; + } return false; } diff --git a/src/object/player.cpp b/src/object/player.cpp index 750c00021f9..57b3441db05 100644 --- a/src/object/player.cpp +++ b/src/object/player.cpp @@ -745,19 +745,30 @@ Player::update(float dt_sec) } } - if (m_does_buttjump) + if (m_does_buttjump || (m_stone && m_physic.get_velocity_y() > 30.f && !m_coyote_timer.started())) { Rectf downbox = get_bbox().grown(-1.f); - downbox.set_bottom(get_bbox().get_bottom() + 16.f); + downbox.set_top(get_bbox().get_bottom()); + downbox.set_bottom(downbox.get_bottom() + 16.f); for (auto& brick : Sector::get().get_objects_by_type()) { - if (downbox.contains(brick.get_bbox()) && brick.get_class_name() != "heavy-brick") { + // stoneform breaks through any kind of bricks + if (downbox.contains(brick.get_bbox()) && (m_stone || !dynamic_cast(&brick))) brick.try_break(this, is_big()); - } } for (auto& badguy : Sector::get().get_objects_by_type()) { - if (downbox.contains(badguy.get_bbox()) && badguy.is_snipable()) { + if (downbox.contains(badguy.get_bbox()) && badguy.is_snipable() && !badguy.is_grabbed()) badguy.kill_fall(); - } + } + } + + // break bricks above without stopping + if (m_stone && m_physic.get_velocity_y() < 30.f) + { + Rectf topbox = get_bbox().grown(-1.f); + topbox.set_top(get_bbox().get_top() - 16.f); + for (auto& brick : Sector::get().get_objects_by_type()) { + if (topbox.contains(brick.get_bbox())) + brick.try_break(this, is_big()); } } @@ -2044,8 +2055,16 @@ Player::draw(DrawingContext& context) m_sprite->set_action(sa_prefix+("-" + IDLE_STAGES[m_idle_stage])+sa_postfix, Sprite::LOOPS_CONTINUED); } } - else { + else + { + if (std::abs(m_physic.get_velocity_x()) >= MAX_RUN_XM-3) + { + m_sprite->set_action(sa_prefix+"-run"+sa_postfix); + } + else + { m_sprite->set_action(sa_prefix+"-walk"+sa_postfix); + } } } @@ -2321,7 +2340,7 @@ Player::kill(bool completely) } } - Sector::get().get_camera().shake(0.1f, m_dying ? 32.f : 0.f, m_dying ? 20.f : 10.f); + //Sector::get().get_camera().shake(0.1f, m_dying ? 32.f : 0.f, m_dying ? 20.f : 10.f); } void diff --git a/src/object/rublight.cpp b/src/object/rublight.cpp index 586d7172465..afe021e8326 100644 --- a/src/object/rublight.cpp +++ b/src/object/rublight.cpp @@ -33,11 +33,11 @@ RubLight::RubLight(const ReaderMapping& mapping) : stored_energy(0), light(SpriteManager::current()->create( "images/objects/lightmap_light/lightmap_light.sprite")), - color(1.0f, 0.5f, 0.3f), + color(1.f, 1.f, 1.f), fading_speed(5.0f), strength_multiplier(1.0f) { - m_sprite->set_action("normal"); + set_action("inactive"); std::vector vColor; if (mapping.get("color", vColor)) @@ -95,6 +95,7 @@ void RubLight::rub(float strength) { if (strength <= 0) return; + set_action("active"); strength *= strength_multiplier; stored_energy = std::max(stored_energy, strength); if (state == STATE_DARK) @@ -104,9 +105,14 @@ void RubLight::rub(float strength) void RubLight::update(float dt_sec) { + if (m_sprite->get_action() == "active" && m_sprite->animation_done()) + { + set_action("inactive"); + } + switch (state) { case STATE_DARK: - // Nothing to do + set_action("inactive"); break; case STATE_FADING: diff --git a/src/object/rusty_trampoline.cpp b/src/object/rusty_trampoline.cpp index a82bfd3a967..9b3ce246bb5 100644 --- a/src/object/rusty_trampoline.cpp +++ b/src/object/rusty_trampoline.cpp @@ -49,7 +49,7 @@ RustyTrampoline::update(float dt_sec) if (counter < 1) { remove_me(); } else { - m_sprite->set_action("normal"); + set_action("normal"); } } @@ -90,9 +90,9 @@ RustyTrampoline::collision(GameObject& other, const CollisionHit& hit) SoundManager::current()->play(BOUNCE_SOUND, get_pos()); counter--; if (counter > 0) { - m_sprite->set_action("swinging", 1); + set_action("swinging", 1); } else { - m_sprite->set_action("breaking", 1); + set_action("breaking", 1); } return FORCE_MOVE; @@ -109,9 +109,9 @@ RustyTrampoline::collision(GameObject& other, const CollisionHit& hit) SoundManager::current()->play(BOUNCE_SOUND, get_pos()); counter--; if (counter > 0) { - m_sprite->set_action("swinging", 1); + set_action("swinging", 1); } else { - m_sprite->set_action("breaking", 1); + set_action("breaking", 1); } return FORCE_MOVE; } @@ -132,9 +132,10 @@ RustyTrampoline::grab(MovingObject& object, const Vector& pos, Direction dir) { } void -RustyTrampoline::ungrab(MovingObject& object, Direction dir) { +RustyTrampoline::ungrab(MovingObject& object, Direction dir) +{ Rock::ungrab(object, dir); - m_sprite->set_action("breaking", 1); + set_action("breaking", 1); counter = 0; //remove in update() } diff --git a/src/object/shard.cpp b/src/object/shard.cpp index 68a6631fa66..228c31dda61 100644 --- a/src/object/shard.cpp +++ b/src/object/shard.cpp @@ -39,7 +39,7 @@ Shard::Shard(const Vector& pos, const Vector& velocity) : { m_physic.enable_gravity(true); m_physic.set_velocity(velocity); - m_sprite->set_action("default"); + set_action("default"); SoundManager::current()->preload("sounds/crystallo-shardhit.ogg"); } diff --git a/src/object/skull_tile.cpp b/src/object/skull_tile.cpp index 8d18faf9823..acf85cd0a3c 100644 --- a/src/object/skull_tile.cpp +++ b/src/object/skull_tile.cpp @@ -91,7 +91,7 @@ SkullTile::update(float dt_sec) } m_col.set_movement(physic.get_movement(dt_sec)); } else if (hit) { - m_sprite->set_action("mad", -1); + set_action("mad", -1); if (timer.check()) { falling = true; physic.enable_gravity(true); @@ -101,7 +101,7 @@ SkullTile::update(float dt_sec) timer.start(FALLTIME); } } else { - m_sprite->set_action("normal", -1); + set_action("normal", -1); timer.stop(); } hit = false; diff --git a/src/object/tilemap.cpp b/src/object/tilemap.cpp index e4e7b5c8d3e..d3a3c744a42 100644 --- a/src/object/tilemap.cpp +++ b/src/object/tilemap.cpp @@ -677,7 +677,9 @@ TileMap::get_tile_at(const Vector& pos) const void TileMap::change(int x, int y, uint32_t newtile) { - assert(x >= 0 && x < m_width && y >= 0 && y < m_height); + if(x < 0 || x >= m_width || y < 0 || y >= m_height) + return; + m_tiles[y*m_width + x] = newtile; } @@ -947,7 +949,7 @@ TileMap::update_effective_solid() { Sector::get().update_solid(this); } else if(worldmap::WorldMap::current() != nullptr && old != m_effective_solid) { - worldmap::WorldMap::current()->update_solid(this); + worldmap::WorldMapSector::current()->update_solid(this); } } diff --git a/src/object/torch.cpp b/src/object/torch.cpp index 0235316c9f6..2f88383fd3c 100644 --- a/src/object/torch.cpp +++ b/src/object/torch.cpp @@ -24,31 +24,20 @@ #include "util/reader_mapping.hpp" Torch::Torch(const ReaderMapping& reader) : - MovingObject(reader), + MovingSprite(reader, "images/objects/torch/torch1.sprite"), ExposedObject(this), m_light_color(1.f, 1.f, 1.f), - m_torch(), m_flame(SpriteManager::current()->create("images/objects/torch/flame.sprite")), m_flame_glow(SpriteManager::current()->create("images/objects/torch/flame_glow.sprite")), m_flame_light(SpriteManager::current()->create("images/objects/torch/flame_light.sprite")), - m_burning(true), - sprite_name("images/objects/torch/torch1.sprite"), - m_layer(0), - m_flip(NO_FLIP) + m_burning(true) { - reader.get("x", m_col.m_bbox.get_left()); - reader.get("y", m_col.m_bbox.get_top()); - - reader.get("sprite", sprite_name); reader.get("burning", m_burning, true); reader.get("layer", m_layer, 0); std::vector vColor; if (!reader.get("color", vColor)) vColor = { 1.f, 1.f, 1.f }; - m_torch = SpriteManager::current()->create(sprite_name); - m_col.m_bbox.set_size(static_cast(m_torch->get_width()), - static_cast(m_torch->get_height())); m_flame_glow->set_blend(Blend::ADD); m_flame_light->set_blend(Blend::ADD); if (vColor.size() >= 3) @@ -78,7 +67,7 @@ Torch::draw(DrawingContext& context) m_flame_glow->set_action(m_light_color.greyscale() >= 1.f ? "default" : "greyscale"); } - m_torch->draw(context.color(), get_pos(), m_layer - 1, m_flip); + m_sprite->draw(context.color(), get_pos(), m_layer - 1, m_flip); } void @@ -100,21 +89,22 @@ Torch::collision(GameObject& other, const CollisionHit& ) ObjectSettings Torch::get_settings() { - ObjectSettings result = MovingObject::get_settings(); + ObjectSettings result = MovingSprite::get_settings(); result.add_bool(_("Burning"), &m_burning, "burning", true); - result.add_sprite(_("Sprite"), &sprite_name, "sprite", std::string("images/objects/torch/torch1.sprite")); result.add_int(_("Layer"), &m_layer, "layer", 0); result.add_color(_("Color"), &m_light_color, "color", Color::WHITE); - result.reorder({"sprite", "layer", "color", "x", "y"}); + result.reorder({"burning", "sprite", "layer", "color", "x", "y"}); return result; } -void Torch::after_editor_set() +void +Torch::after_editor_set() { - m_torch = SpriteManager::current()->create(sprite_name); + MovingSprite::after_editor_set(); + m_flame->set_color(m_light_color); m_flame_glow->set_color(m_light_color); m_flame_light->set_color(m_light_color); diff --git a/src/object/torch.hpp b/src/object/torch.hpp index 6dd340b4c19..029542a1ea9 100644 --- a/src/object/torch.hpp +++ b/src/object/torch.hpp @@ -18,17 +18,15 @@ #ifndef HEADER_SUPERTUX_OBJECT_TORCH_HPP #define HEADER_SUPERTUX_OBJECT_TORCH_HPP +#include "object/moving_sprite.hpp" #include "squirrel/exposed_object.hpp" + #include "scripting/torch.hpp" -#include "sprite/sprite_ptr.hpp" -#include "supertux/moving_object.hpp" -#include "video/flip.hpp" class ReaderMapping; -class Torch final : - public MovingObject, - public ExposedObject +class Torch final : public MovingSprite, + public ExposedObject { public: Torch(const ReaderMapping& reader); @@ -59,14 +57,10 @@ class Torch final : private: Color m_light_color; - SpritePtr m_torch; SpritePtr m_flame; SpritePtr m_flame_glow; SpritePtr m_flame_light; bool m_burning; - std::string sprite_name; - int m_layer; /**< The layer (z-pos) of the torch */ - Flip m_flip; private: Torch(const Torch&) = delete; diff --git a/src/object/trampoline.cpp b/src/object/trampoline.cpp index 3d4e039e314..f0b318ed0dc 100644 --- a/src/object/trampoline.cpp +++ b/src/object/trampoline.cpp @@ -46,7 +46,7 @@ Trampoline::Trampoline(const ReaderMapping& mapping) : m_sprite_name = "images/objects/trampoline/trampoline_fix.sprite"; m_default_sprite_name = m_sprite_name; m_sprite = SpriteManager::current()->create(m_sprite_name); - m_sprite->set_action("normal"); + set_action("normal"); } } } @@ -59,7 +59,7 @@ Trampoline::Trampoline(const Vector& pos, bool port) : if (!port) { m_sprite_name = "images/objects/trampoline/trampoline_fix.sprite"; m_sprite = SpriteManager::current()->create(m_sprite_name); - m_sprite->set_action("normal"); + set_action("normal"); } } @@ -67,7 +67,7 @@ void Trampoline::update(float dt_sec) { if (m_sprite->animation_done()) { - m_sprite->set_action("normal"); + set_action("normal"); } Rock::update(dt_sec); @@ -111,7 +111,7 @@ Trampoline::collision(GameObject& other, const CollisionHit& hit) } player->get_physic().set_velocity_y(vy); SoundManager::current()->play(TRAMPOLINE_SOUND, get_pos()); - m_sprite->set_action("swinging", 1); + set_action("swinging", 1); return FORCE_MOVE; } } @@ -124,7 +124,7 @@ Trampoline::collision(GameObject& other, const CollisionHit& hit) vy = VY_INITIAL; walking_badguy->set_velocity_y(vy); SoundManager::current()->play(TRAMPOLINE_SOUND, get_pos()); - m_sprite->set_action("swinging", 1); + set_action("swinging", 1); return FORCE_MOVE; } } @@ -134,7 +134,8 @@ Trampoline::collision(GameObject& other, const CollisionHit& hit) } void -Trampoline::grab(MovingObject& object, const Vector& pos, Direction dir) { +Trampoline::grab(MovingObject& object, const Vector& pos, Direction dir) +{ m_sprite->set_animation_loops(0); Rock::grab(object, pos, dir); } diff --git a/src/object/unstable_tile.cpp b/src/object/unstable_tile.cpp index 1dbc157d100..d9460511a1f 100644 --- a/src/object/unstable_tile.cpp +++ b/src/object/unstable_tile.cpp @@ -41,7 +41,8 @@ UnstableTile::UnstableTile(const ReaderMapping& mapping) : m_alpha(1.f), m_original_pos(m_col.get_pos()) { - m_sprite->set_action("normal"); + set_action("normal"); + physic.set_gravity_modifier(.98f); physic.enable_gravity(false); } @@ -151,7 +152,7 @@ UnstableTile::revive() m_col.set_movement(Vector(0.0f, 0.0f)); m_revive_timer.stop(); m_respawn.reset(new FadeHelper(&m_alpha, FADE_IN_TIME, 1.f)); - m_sprite->set_action("normal"); + set_action("normal"); } void @@ -198,7 +199,7 @@ UnstableTile::update(float dt_sec) { if (m_revive_timer.check()) { - if (Sector::current() && Sector::get().is_free_of_movingstatics(m_col.m_bbox.grown(-1.f))) + if (Sector::get().is_free_of_movingstatics(Rectf(m_original_pos, get_bbox().get_size()).grown(-1.f))) { revive(); } diff --git a/src/object/water_drop.cpp b/src/object/water_drop.cpp index 36f5f7a33d4..e006048c116 100644 --- a/src/object/water_drop.cpp +++ b/src/object/water_drop.cpp @@ -49,7 +49,7 @@ WaterDrop::collision_solid(const CollisionHit& hit) wd_state = WDS_SPLASH; physic.enable_gravity(false); SoundManager::current()->play("sounds/splash.ogg", get_pos()); - m_sprite->set_action("splash", 1); + set_action("splash", 1); // spawn water particles for (int i = 50; i; i--) { diff --git a/src/object/weak_block.cpp b/src/object/weak_block.cpp index 3dd541c02c7..5a1bc01cf2d 100644 --- a/src/object/weak_block.cpp +++ b/src/object/weak_block.cpp @@ -36,14 +36,14 @@ WeakBlock::WeakBlock(const ReaderMapping& mapping) : linked(true), lightsprite(SpriteManager::current()->create("images/objects/lightmap_light/lightmap_light-small.sprite")) { - m_sprite->set_action("normal"); + set_action("normal"); //Check if this weakblock destroys adjacent weakblocks if (mapping.get("linked", linked)){ if (! linked){ m_default_sprite_name = "images/objects/weak_block/meltbox.sprite"; m_sprite_name = m_default_sprite_name; m_sprite = SpriteManager::current()->create(m_sprite_name); - m_sprite->set_action("normal"); + set_action("normal"); } } @@ -137,7 +137,7 @@ WeakBlock::update(float ) if (m_sprite->animation_done()) { state = STATE_DISINTEGRATING; - m_sprite->set_action("disintegrating", 1); + set_action("disintegrating", 1); spreadHit(); set_group(COLGROUP_DISABLED); lightsprite = SpriteManager::current()->create("images/objects/lightmap_light/lightmap_light-tiny.sprite"); @@ -173,7 +173,7 @@ WeakBlock::startBurning() { if (state != STATE_NORMAL) return; state = STATE_BURNING; - m_sprite->set_action("burning", 1); + set_action("burning", 1); // FIXME: Not hardcode these sounds? if (m_sprite_name == "images/objects/weak_block/meltbox.sprite") { SoundManager::current()->play("sounds/sizzle.ogg", get_pos()); diff --git a/src/physfs/physfs_file_system.cpp b/src/physfs/physfs_file_system.cpp index dbb8d20a00c..2356e086c97 100644 --- a/src/physfs/physfs_file_system.cpp +++ b/src/physfs/physfs_file_system.cpp @@ -19,6 +19,7 @@ #include #include "physfs/ifile_stream.hpp" +#include "physfs/util.hpp" PhysFSFileSystem::PhysFSFileSystem() { @@ -28,14 +29,9 @@ std::vector PhysFSFileSystem::open_directory(const std::string& pathname) { std::vector files; - - char** directory = PHYSFS_enumerateFiles(pathname.c_str()); - for (char** i = directory; *i != nullptr; ++i) - { - files.push_back(*i); - } - PHYSFS_freeList(directory); - + physfsutil::enumerate_files(pathname, [&files](const std::string& file) { + files.push_back(file); + }); return files; } diff --git a/src/physfs/util.cpp b/src/physfs/util.cpp index f237d041515..6c00ff23085 100644 --- a/src/physfs/util.cpp +++ b/src/physfs/util.cpp @@ -18,6 +18,7 @@ #include +#include "physfs/physfs_file_system.hpp" #include "util/file_system.hpp" namespace physfsutil { @@ -66,16 +67,12 @@ bool remove(const std::string& filename) void remove_content(const std::string& dir) { PHYSFS_UTIL_DIRECTORY_GUARD; - - char** files = PHYSFS_enumerateFiles(dir.c_str()); - for (const char* const* file = files; *file != nullptr; file++) - { - std::string path = FileSystem::join(dir, *file); + enumerate_files(dir, [&dir](const std::string& file) { + std::string path = FileSystem::join(dir, file); if (is_directory(path)) remove_with_content(path); PHYSFS_delete(path.c_str()); - } - PHYSFS_freeList(files); + }); } void remove_with_content(const std::string& dir) @@ -86,6 +83,23 @@ void remove_with_content(const std::string& dir) remove(dir); } +bool enumerate_files(const std::string& pathname, std::function callback) +{ + std::unique_ptr + files(PHYSFS_enumerateFiles(pathname.c_str()), + PHYSFS_freeList); + + if(files == nullptr) + return false; + + for (const char* const* filename = files.get(); *filename != nullptr; ++filename) + { + callback(*filename); + } + + return true; +} + } // namespace physfsutil /* EOF */ diff --git a/src/physfs/util.hpp b/src/physfs/util.hpp index 597b3151403..2e525b87402 100644 --- a/src/physfs/util.hpp +++ b/src/physfs/util.hpp @@ -17,6 +17,7 @@ #ifndef HEADER_SUPERTUX_PHYSFS_UTIL_HPP #define HEADER_SUPERTUX_PHYSFS_UTIL_HPP +#include #include namespace physfsutil { @@ -37,6 +38,9 @@ void remove_content(const std::string& dir); /** Removes directory with content */ void remove_with_content(const std::string& dir); +/** Open directory and call callback for each file */ +bool enumerate_files(const std::string& pathname, std::function callback); + } // namespace physfsutil #endif diff --git a/src/scripting/badguy.hpp b/src/scripting/badguy.hpp index cdb541b41ce..c33a0a7b197 100644 --- a/src/scripting/badguy.hpp +++ b/src/scripting/badguy.hpp @@ -39,8 +39,8 @@ class BadGuy { #ifndef SCRIPTING_API public: - BadGuy(UID uid) : - GameObject<::BadGuy>(uid) + BadGuy(const ::GameObject& object) : + GameObject<::BadGuy>(object) {} private: diff --git a/src/scripting/decal.cpp b/src/scripting/decal.cpp index 55cd5859bd3..472ea2221d2 100644 --- a/src/scripting/decal.cpp +++ b/src/scripting/decal.cpp @@ -47,6 +47,13 @@ Decal::fade_out(float time) object.fade_out(time); } +void +Decal::set_action(const std::string& action) +{ + SCRIPT_GUARD_VOID; + object.set_action(action); +} + } // namespace scripting /* EOF */ diff --git a/src/scripting/decal.hpp b/src/scripting/decal.hpp index cf65af140bc..d14a1a4693d 100644 --- a/src/scripting/decal.hpp +++ b/src/scripting/decal.hpp @@ -65,6 +65,11 @@ class Decal final * @param float $time */ void fade_out(float time); + /** + * Sets the action for the decal's sprite. + * @param string $action + */ + void set_action(const std::string& action); }; } // namespace scripting diff --git a/src/scripting/dispenser.hpp b/src/scripting/dispenser.hpp index f32cdadc586..c3c63456df7 100644 --- a/src/scripting/dispenser.hpp +++ b/src/scripting/dispenser.hpp @@ -38,10 +38,10 @@ class Dispenser final : public scripting::BadGuy { #ifndef SCRIPTING_API public: - Dispenser(UID uid) : - GameObject<::BadGuy>(uid), - GameObject<::Dispenser>(uid), - BadGuy(uid) + Dispenser(const ::GameObject& object) : + GameObject<::BadGuy>(object), + GameObject<::Dispenser>(object), + BadGuy(object) {} private: diff --git a/src/scripting/floating_image.cpp b/src/scripting/floating_image.cpp index 717c5be587a..1ce30c23fc0 100644 --- a/src/scripting/floating_image.cpp +++ b/src/scripting/floating_image.cpp @@ -24,7 +24,7 @@ namespace scripting { FloatingImage::FloatingImage(const std::string& spritefile) : - GameObject(get_game_object_manager().add<::FloatingImage>(spritefile).get_uid()) + GameObject(get_sector().add<::FloatingImage>(spritefile)) { } diff --git a/src/scripting/functions.cpp b/src/scripting/functions.cpp index 3a8d78835a0..5735882f15a 100644 --- a/src/scripting/functions.cpp +++ b/src/scripting/functions.cpp @@ -286,12 +286,12 @@ void debug_draw_editor_images(bool enable) void debug_worldmap_ghost(bool enable) { - auto worldmap = worldmap::WorldMap::current(); + auto worldmap_sector = worldmap::WorldMapSector::current(); - if (worldmap == nullptr) - throw std::runtime_error("Can't change ghost mode without active WorldMap"); + if (worldmap_sector == nullptr) + throw std::runtime_error("Can't change ghost mode without active WorldMapSector"); - auto& tux = worldmap->get_singleton_by_type(); + auto& tux = worldmap_sector->get_singleton_by_type(); tux.set_ghost_mode(enable); } diff --git a/src/scripting/game_object.cpp b/src/scripting/game_object.cpp index c9122fa8f16..05ff99ac6d4 100644 --- a/src/scripting/game_object.cpp +++ b/src/scripting/game_object.cpp @@ -1,5 +1,6 @@ // SuperTux // Copyright (C) 2018 Ingo Ruhnke +// 2023 Vankata453 // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -17,21 +18,15 @@ #include "scripting/game_object.hpp" #include "supertux/sector.hpp" -#include "worldmap/worldmap.hpp" namespace scripting { -::GameObjectManager& get_game_object_manager() +::Sector& get_sector() { - using namespace worldmap; - - if (::Sector::current() != nullptr) { + if (::Sector::current()) return ::Sector::get(); - } else if (::worldmap::WorldMap::current() != nullptr) { - return *::worldmap::WorldMap::current(); - } else { - throw std::runtime_error("Neither sector nor worldmap active"); - } + + throw std::runtime_error("Unable to perform scripting action: No active Sector."); } } // namespace scripting diff --git a/src/scripting/game_object.hpp b/src/scripting/game_object.hpp index fdf5f090f50..3c17b0342f6 100644 --- a/src/scripting/game_object.hpp +++ b/src/scripting/game_object.hpp @@ -1,5 +1,6 @@ // SuperTux // Copyright (C) 2018 Ingo Ruhnke +// 2023 Vankata453 // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -77,27 +78,33 @@ } \ auto& object = *object_ptr -class GameObjectManager; +class Sector; namespace scripting { -::GameObjectManager& get_game_object_manager(); +::Sector& get_sector(); template class GameObject { public: - GameObject(UID uid) : - m_uid(uid) + GameObject(const ::GameObject& object) : + m_uid(object.get_uid()), + m_parent(*object.get_parent()) {} T* get_object_ptr() const { - return get_game_object_manager().get_object_by_uid(m_uid); + return m_parent.get_object_by_uid(m_uid); } protected: UID m_uid; + ::GameObjectManager& m_parent; + +private: + GameObject(const GameObject&) = delete; + GameObject& operator=(const GameObject&) = delete; }; } // namespace scripting diff --git a/src/scripting/game_object_manager.hpp b/src/scripting/game_object_manager.hpp index 85b8fd3fc17..11ca2c5b5f0 100644 --- a/src/scripting/game_object_manager.hpp +++ b/src/scripting/game_object_manager.hpp @@ -25,9 +25,9 @@ class GameObjectManager; namespace scripting { /** - * @summary This class provides basic controlling functions for a sector. Applies for both worldmaps and in-level sectors. + * @summary This class provides basic controlling functions for a sector. Applies for both worldmap and in-level sectors. * @instances For in-level sectors, an instance under ""sector.settings"" is available from scripts and the console.${SRG_NEWPARAGRAPH} - For worldmaps, such instance is available under ""worldmap.settings"". + For worldmap sectors, such instance is available under ""worldmap.settings"". */ class GameObjectManager { diff --git a/src/scripting/level.cpp b/src/scripting/level.cpp index 184fe5532c2..f513ccc07f8 100644 --- a/src/scripting/level.cpp +++ b/src/scripting/level.cpp @@ -86,6 +86,20 @@ Level_edit(bool edit_mode) game_session.set_editmode(edit_mode); } +void +Level_pause_target_timer() +{ + SCRIPT_GUARD_GAMESESSION; + game_session.set_target_timer_paused(true); +} + +void +Level_resume_target_timer() +{ + SCRIPT_GUARD_GAMESESSION; + game_session.set_target_timer_paused(false); +} + } // namespace scripting /* EOF */ diff --git a/src/scripting/level.hpp b/src/scripting/level.hpp index 970631c8f59..a35e1dafc15 100644 --- a/src/scripting/level.hpp +++ b/src/scripting/level.hpp @@ -109,6 +109,16 @@ void Level_toggle_pause(); */ void Level_edit(bool edit_mode); +/** + * Pauses the target timer. + */ +void Level_pause_target_timer(); + +/** + * Resumes the target timer. + */ +void Level_resume_target_timer(); + #ifdef DOXYGEN_SCRIPTING } #endif diff --git a/src/scripting/willowisp.hpp b/src/scripting/willowisp.hpp index a4063c70258..f1b7d4d25cb 100644 --- a/src/scripting/willowisp.hpp +++ b/src/scripting/willowisp.hpp @@ -40,10 +40,10 @@ class WillOWisp final : public scripting::BadGuy { #ifndef SCRIPTING_API public: - WillOWisp(UID uid) : - GameObject<::BadGuy>(uid), - GameObject<::WillOWisp>(uid), - BadGuy(uid) + WillOWisp(const ::GameObject& object) : + GameObject<::BadGuy>(object), + GameObject<::WillOWisp>(object), + BadGuy(object) {} private: diff --git a/src/scripting/worldmap.cpp b/src/scripting/worldmap_sector.cpp similarity index 61% rename from src/scripting/worldmap.cpp rename to src/scripting/worldmap_sector.cpp index 68dcd61ceba..904a33b300e 100644 --- a/src/scripting/worldmap.cpp +++ b/src/scripting/worldmap_sector.cpp @@ -1,5 +1,6 @@ // SuperTux // Copyright (C) 2021 A. Semphris +// 2023 Vankata453 // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -14,30 +15,50 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "scripting/worldmap.hpp" +#include "scripting/worldmap_sector.hpp" #include "worldmap/worldmap.hpp" namespace scripting { -WorldMap::WorldMap(::worldmap::WorldMap* parent) : +WorldMapSector::WorldMapSector(::worldmap::WorldMapSector* parent) : GameObjectManager(parent), m_parent(parent) { } float -WorldMap::get_tux_x() const +WorldMapSector::get_tux_x() const { return m_parent->get_tux_pos().x; } float -WorldMap::get_tux_y() const +WorldMapSector::get_tux_y() const { return m_parent->get_tux_pos().y; } +void +WorldMapSector::set_sector(const std::string& sector) +{ + SCRIPT_GUARD_WORLDMAP; + worldmap.set_sector(sector); +} + +void +WorldMapSector::spawn(const std::string& sector, const std::string& spawnpoint) +{ + SCRIPT_GUARD_WORLDMAP; + worldmap.set_sector(sector, spawnpoint); +} + +void +WorldMapSector::move_to_spawnpoint(const std::string& spawnpoint) +{ + m_parent->move_to_spawnpoint(spawnpoint); +} + } // namespace scripting /* EOF */ diff --git a/src/scripting/worldmap_sector.hpp b/src/scripting/worldmap_sector.hpp new file mode 100644 index 00000000000..ac54cc1dc40 --- /dev/null +++ b/src/scripting/worldmap_sector.hpp @@ -0,0 +1,92 @@ +// SuperTux +// Copyright (C) 2021 A. Semphris +// 2023 Vankata453 +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef HEADER_SUPERTUX_SCRIPTING_WORLDMAP_SECTOR_HPP +#define HEADER_SUPERTUX_SCRIPTING_WORLDMAP_SECTOR_HPP + +#ifndef SCRIPTING_API + +#include + +#include "scripting/game_object_manager.hpp" + +namespace worldmap { + class WorldMapSector; +} + +/** Macro to allow quick and easy access to the parent worldmap. **/ +#define SCRIPT_GUARD_WORLDMAP \ + auto& worldmap = m_parent->get_worldmap() + +#endif + +namespace scripting { + +/** + * @summary This class provides additional controlling functions for a worldmap sector, other than the ones listed at ${SRG_REF_GameObjectManager}. + * @instances An instance under ""worldmap.settings"" is available from scripts and the console. + */ +class WorldMapSector final : public GameObjectManager +{ +#ifndef SCRIPTING_API +private: + ::worldmap::WorldMapSector* m_parent; + +public: + WorldMapSector(::worldmap::WorldMapSector* parent); + +private: + WorldMapSector(const WorldMapSector&) = delete; + WorldMapSector& operator=(const WorldMapSector&) = delete; +#endif + +public: + /** + * Gets Tux's X position on the worldmap. + */ + float get_tux_x() const; + /** + * Gets Tux's Y position on the worldmap. + */ + float get_tux_y() const; + + /** + * Changes the current sector of the worldmap to a specified new sector. + * @param string $sector + */ + void set_sector(const std::string& sector); + + /** + * Changes the current sector of the worldmap to a specified new sector, + moving Tux to the specified spawnpoint. + * @param string $sector + * @param string $spawnpoint + */ + void spawn(const std::string& sector, const std::string& spawnpoint); + + /** + * Moves Tux to a specified spawnpoint. + * @param string $spawnpoint + */ + void move_to_spawnpoint(const std::string& spawnpoint); +}; + +} // namespace scripting + +#endif + +/* EOF */ diff --git a/src/scripting/wrapper.cpp b/src/scripting/wrapper.cpp index 82b0ea0333e..14f9e9de1d8 100644 --- a/src/scripting/wrapper.cpp +++ b/src/scripting/wrapper.cpp @@ -1251,7 +1251,7 @@ static SQInteger Clouds_set_amount_wrapper(HSQUIRRELVM vm) static SQInteger ConveyorBelt_release_hook(SQUserPointer ptr, SQInteger ) { - auto _this = reinterpret_cast (ptr); + scripting::ConveyorBelt* _this = reinterpret_cast (ptr); delete _this; return 0; } @@ -1263,11 +1263,7 @@ static SQInteger ConveyorBelt_start_wrapper(HSQUIRRELVM vm) sq_throwerror(vm, _SC("'start' called without instance")); return SQ_ERROR; } - auto _this = reinterpret_cast (data); - - if (_this == nullptr) { - return SQ_ERROR; - } + scripting::ConveyorBelt* _this = reinterpret_cast (data); try { @@ -1292,11 +1288,7 @@ static SQInteger ConveyorBelt_stop_wrapper(HSQUIRRELVM vm) sq_throwerror(vm, _SC("'stop' called without instance")); return SQ_ERROR; } - auto _this = reinterpret_cast (data); - - if (_this == nullptr) { - return SQ_ERROR; - } + scripting::ConveyorBelt* _this = reinterpret_cast (data); try { @@ -1321,11 +1313,7 @@ static SQInteger ConveyorBelt_move_left_wrapper(HSQUIRRELVM vm) sq_throwerror(vm, _SC("'move_left' called without instance")); return SQ_ERROR; } - auto _this = reinterpret_cast (data); - - if (_this == nullptr) { - return SQ_ERROR; - } + scripting::ConveyorBelt* _this = reinterpret_cast (data); try { @@ -1350,11 +1338,7 @@ static SQInteger ConveyorBelt_move_right_wrapper(HSQUIRRELVM vm) sq_throwerror(vm, _SC("'move_right' called without instance")); return SQ_ERROR; } - auto _this = reinterpret_cast (data); - - if (_this == nullptr) { - return SQ_ERROR; - } + scripting::ConveyorBelt* _this = reinterpret_cast (data); try { @@ -1379,11 +1363,7 @@ static SQInteger ConveyorBelt_set_speed_wrapper(HSQUIRRELVM vm) sq_throwerror(vm, _SC("'set_speed' called without instance")); return SQ_ERROR; } - auto _this = reinterpret_cast (data); - - if (_this == nullptr) { - return SQ_ERROR; - } + scripting::ConveyorBelt* _this = reinterpret_cast (data); SQFloat arg0; if(SQ_FAILED(sq_getfloat(vm, 2, &arg0))) { @@ -1392,7 +1372,7 @@ static SQInteger ConveyorBelt_set_speed_wrapper(HSQUIRRELVM vm) } try { - _this->set_speed(static_cast (arg0)); + _this->set_speed(arg0); return 0; @@ -4940,6 +4920,36 @@ static SQInteger Decal_fade_out_wrapper(HSQUIRRELVM vm) } +static SQInteger Decal_set_action_wrapper(HSQUIRRELVM vm) +{ + SQUserPointer data; + if(SQ_FAILED(sq_getinstanceup(vm, 1, &data, nullptr, SQTrue)) || !data) { + sq_throwerror(vm, _SC("'set_action' called without instance")); + return SQ_ERROR; + } + scripting::Decal* _this = reinterpret_cast (data); + + const SQChar* arg0; + if(SQ_FAILED(sq_getstring(vm, 2, &arg0))) { + sq_throwerror(vm, _SC("Argument 1 not a string")); + return SQ_ERROR; + } + + try { + _this->set_action(arg0); + + return 0; + + } catch(std::exception& e) { + sq_throwerror(vm, e.what()); + return SQ_ERROR; + } catch(...) { + sq_throwerror(vm, _SC("Unexpected exception while executing function 'set_action'")); + return SQ_ERROR; + } + +} + static SQInteger Dispenser_release_hook(SQUserPointer ptr, SQInteger ) { scripting::Dispenser* _this = reinterpret_cast (ptr); @@ -11078,21 +11088,21 @@ static SQInteger Wind_stop_wrapper(HSQUIRRELVM vm) } -static SQInteger WorldMap_release_hook(SQUserPointer ptr, SQInteger ) +static SQInteger WorldMapSector_release_hook(SQUserPointer ptr, SQInteger ) { - scripting::WorldMap* _this = reinterpret_cast (ptr); + scripting::WorldMapSector* _this = reinterpret_cast (ptr); delete _this; return 0; } -static SQInteger WorldMap_get_tux_x_wrapper(HSQUIRRELVM vm) +static SQInteger WorldMapSector_get_tux_x_wrapper(HSQUIRRELVM vm) { SQUserPointer data; if(SQ_FAILED(sq_getinstanceup(vm, 1, &data, nullptr, SQTrue)) || !data) { sq_throwerror(vm, _SC("'get_tux_x' called without instance")); return SQ_ERROR; } - scripting::WorldMap* _this = reinterpret_cast (data); + scripting::WorldMapSector* _this = reinterpret_cast (data); try { @@ -11111,14 +11121,14 @@ static SQInteger WorldMap_get_tux_x_wrapper(HSQUIRRELVM vm) } -static SQInteger WorldMap_get_tux_y_wrapper(HSQUIRRELVM vm) +static SQInteger WorldMapSector_get_tux_y_wrapper(HSQUIRRELVM vm) { SQUserPointer data; if(SQ_FAILED(sq_getinstanceup(vm, 1, &data, nullptr, SQTrue)) || !data) { sq_throwerror(vm, _SC("'get_tux_y' called without instance")); return SQ_ERROR; } - scripting::WorldMap* _this = reinterpret_cast (data); + scripting::WorldMapSector* _this = reinterpret_cast (data); try { @@ -11137,6 +11147,101 @@ static SQInteger WorldMap_get_tux_y_wrapper(HSQUIRRELVM vm) } +static SQInteger WorldMapSector_set_sector_wrapper(HSQUIRRELVM vm) +{ + SQUserPointer data; + if(SQ_FAILED(sq_getinstanceup(vm, 1, &data, nullptr, SQTrue)) || !data) { + sq_throwerror(vm, _SC("'set_sector' called without instance")); + return SQ_ERROR; + } + scripting::WorldMapSector* _this = reinterpret_cast (data); + + const SQChar* arg0; + if(SQ_FAILED(sq_getstring(vm, 2, &arg0))) { + sq_throwerror(vm, _SC("Argument 1 not a string")); + return SQ_ERROR; + } + + try { + _this->set_sector(arg0); + + return 0; + + } catch(std::exception& e) { + sq_throwerror(vm, e.what()); + return SQ_ERROR; + } catch(...) { + sq_throwerror(vm, _SC("Unexpected exception while executing function 'set_sector'")); + return SQ_ERROR; + } + +} + +static SQInteger WorldMapSector_spawn_wrapper(HSQUIRRELVM vm) +{ + SQUserPointer data; + if(SQ_FAILED(sq_getinstanceup(vm, 1, &data, nullptr, SQTrue)) || !data) { + sq_throwerror(vm, _SC("'spawn' called without instance")); + return SQ_ERROR; + } + scripting::WorldMapSector* _this = reinterpret_cast (data); + + const SQChar* arg0; + if(SQ_FAILED(sq_getstring(vm, 2, &arg0))) { + sq_throwerror(vm, _SC("Argument 1 not a string")); + return SQ_ERROR; + } + const SQChar* arg1; + if(SQ_FAILED(sq_getstring(vm, 3, &arg1))) { + sq_throwerror(vm, _SC("Argument 2 not a string")); + return SQ_ERROR; + } + + try { + _this->spawn(arg0, arg1); + + return 0; + + } catch(std::exception& e) { + sq_throwerror(vm, e.what()); + return SQ_ERROR; + } catch(...) { + sq_throwerror(vm, _SC("Unexpected exception while executing function 'spawn'")); + return SQ_ERROR; + } + +} + +static SQInteger WorldMapSector_move_to_spawnpoint_wrapper(HSQUIRRELVM vm) +{ + SQUserPointer data; + if(SQ_FAILED(sq_getinstanceup(vm, 1, &data, nullptr, SQTrue)) || !data) { + sq_throwerror(vm, _SC("'move_to_spawnpoint' called without instance")); + return SQ_ERROR; + } + scripting::WorldMapSector* _this = reinterpret_cast (data); + + const SQChar* arg0; + if(SQ_FAILED(sq_getstring(vm, 2, &arg0))) { + sq_throwerror(vm, _SC("Argument 1 not a string")); + return SQ_ERROR; + } + + try { + _this->move_to_spawnpoint(arg0); + + return 0; + + } catch(std::exception& e) { + sq_throwerror(vm, e.what()); + return SQ_ERROR; + } catch(...) { + sq_throwerror(vm, _SC("Unexpected exception while executing function 'move_to_spawnpoint'")); + return SQ_ERROR; + } + +} + static SQInteger display_wrapper(HSQUIRRELVM vm) { return scripting::display(vm); @@ -12366,6 +12471,44 @@ static SQInteger Level_edit_wrapper(HSQUIRRELVM vm) } +static SQInteger Level_pause_target_timer_wrapper(HSQUIRRELVM vm) +{ + (void) vm; + + try { + scripting::Level_pause_target_timer(); + + return 0; + + } catch(std::exception& e) { + sq_throwerror(vm, e.what()); + return SQ_ERROR; + } catch(...) { + sq_throwerror(vm, _SC("Unexpected exception while executing function 'Level_pause_target_timer'")); + return SQ_ERROR; + } + +} + +static SQInteger Level_resume_target_timer_wrapper(HSQUIRRELVM vm) +{ + (void) vm; + + try { + scripting::Level_resume_target_timer(); + + return 0; + + } catch(std::exception& e) { + sq_throwerror(vm, e.what()); + return SQ_ERROR; + } catch(...) { + sq_throwerror(vm, _SC("Unexpected exception while executing function 'Level_resume_target_timer'")); + return SQ_ERROR; + } + +} + } // namespace wrapper void create_squirrel_instance(HSQUIRRELVM v, scripting::AmbientSound* object, bool setup_releasehook) { @@ -13173,27 +13316,27 @@ void create_squirrel_instance(HSQUIRRELVM v, scripting::Wind* object, bool setup sq_remove(v, -2); // remove root table } -void create_squirrel_instance(HSQUIRRELVM v, scripting::WorldMap* object, bool setup_releasehook) +void create_squirrel_instance(HSQUIRRELVM v, scripting::WorldMapSector* object, bool setup_releasehook) { using namespace wrapper; sq_pushroottable(v); - sq_pushstring(v, "WorldMap", -1); + sq_pushstring(v, "WorldMapSector", -1); if(SQ_FAILED(sq_get(v, -2))) { std::ostringstream msg; - msg << "Couldn't resolved squirrel type 'WorldMap'"; + msg << "Couldn't resolved squirrel type 'WorldMapSector'"; throw SquirrelError(v, msg.str()); } if(SQ_FAILED(sq_createinstance(v, -1)) || SQ_FAILED(sq_setinstanceup(v, -1, object))) { std::ostringstream msg; - msg << "Couldn't setup squirrel instance for object of type 'WorldMap'"; + msg << "Couldn't setup squirrel instance for object of type 'WorldMapSector'"; throw SquirrelError(v, msg.str()); } sq_remove(v, -2); // remove object name if(setup_releasehook) { - sq_setreleasehook(v, -1, WorldMap_release_hook); + sq_setreleasehook(v, -1, WorldMapSector_release_hook); } sq_remove(v, -2); // remove root table @@ -13642,6 +13785,20 @@ void register_supertux_wrapper(HSQUIRRELVM v) throw SquirrelError(v, "Couldn't register function 'Level_edit'"); } + sq_pushstring(v, "Level_pause_target_timer", -1); + sq_newclosure(v, &Level_pause_target_timer_wrapper, 0); + sq_setparamscheck(v, SQ_MATCHTYPEMASKSTRING, "."); + if(SQ_FAILED(sq_createslot(v, -3))) { + throw SquirrelError(v, "Couldn't register function 'Level_pause_target_timer'"); + } + + sq_pushstring(v, "Level_resume_target_timer", -1); + sq_newclosure(v, &Level_resume_target_timer_wrapper, 0); + sq_setparamscheck(v, SQ_MATCHTYPEMASKSTRING, "."); + if(SQ_FAILED(sq_createslot(v, -3))) { + throw SquirrelError(v, "Couldn't register function 'Level_resume_target_timer'"); + } + // Register class AmbientSound sq_pushstring(v, "AmbientSound", -1); if(sq_newclass(v, SQFalse) < 0) { @@ -14051,35 +14208,35 @@ void register_supertux_wrapper(HSQUIRRELVM v) } sq_pushstring(v, "start", -1); sq_newclosure(v, &ConveyorBelt_start_wrapper, 0); - sq_setparamscheck(v, SQ_MATCHTYPEMASKSTRING, "x|t"); + sq_setparamscheck(v, SQ_MATCHTYPEMASKSTRING, "."); if(SQ_FAILED(sq_createslot(v, -3))) { throw SquirrelError(v, "Couldn't register function 'start'"); } sq_pushstring(v, "stop", -1); sq_newclosure(v, &ConveyorBelt_stop_wrapper, 0); - sq_setparamscheck(v, SQ_MATCHTYPEMASKSTRING, "x|t"); + sq_setparamscheck(v, SQ_MATCHTYPEMASKSTRING, "."); if(SQ_FAILED(sq_createslot(v, -3))) { throw SquirrelError(v, "Couldn't register function 'stop'"); } sq_pushstring(v, "move_left", -1); sq_newclosure(v, &ConveyorBelt_move_left_wrapper, 0); - sq_setparamscheck(v, SQ_MATCHTYPEMASKSTRING, "x|t"); + sq_setparamscheck(v, SQ_MATCHTYPEMASKSTRING, "."); if(SQ_FAILED(sq_createslot(v, -3))) { throw SquirrelError(v, "Couldn't register function 'move_left'"); } sq_pushstring(v, "move_right", -1); sq_newclosure(v, &ConveyorBelt_move_right_wrapper, 0); - sq_setparamscheck(v, SQ_MATCHTYPEMASKSTRING, "x|t"); + sq_setparamscheck(v, SQ_MATCHTYPEMASKSTRING, "."); if(SQ_FAILED(sq_createslot(v, -3))) { throw SquirrelError(v, "Couldn't register function 'move_right'"); } sq_pushstring(v, "set_speed", -1); sq_newclosure(v, &ConveyorBelt_set_speed_wrapper, 0); - sq_setparamscheck(v, SQ_MATCHTYPEMASKSTRING, "x|tn"); + sq_setparamscheck(v, SQ_MATCHTYPEMASKSTRING, ".b|n"); if(SQ_FAILED(sq_createslot(v, -3))) { throw SquirrelError(v, "Couldn't register function 'set_speed'"); } @@ -14876,6 +15033,13 @@ void register_supertux_wrapper(HSQUIRRELVM v) throw SquirrelError(v, "Couldn't register function 'fade_out'"); } + sq_pushstring(v, "set_action", -1); + sq_newclosure(v, &Decal_set_action_wrapper, 0); + sq_setparamscheck(v, SQ_MATCHTYPEMASKSTRING, ".s"); + if(SQ_FAILED(sq_createslot(v, -3))) { + throw SquirrelError(v, "Couldn't register function 'set_action'"); + } + if(SQ_FAILED(sq_createslot(v, -3))) { throw SquirrelError(v, "Couldn't register class 'Decal'"); } @@ -15115,31 +15279,52 @@ void register_supertux_wrapper(HSQUIRRELVM v) throw SquirrelError(v, "Couldn't register class 'Sector'"); } - // Register class WorldMap - sq_pushstring(v, "WorldMap", -1); + // Register class WorldMapSector + sq_pushstring(v, "WorldMapSector", -1); sq_pushstring(v, "GameObjectManager", -1); sq_get(v, -3); if(sq_newclass(v, SQTrue) < 0) { std::ostringstream msg; - msg << "Couldn't create new class 'WorldMap'"; + msg << "Couldn't create new class 'WorldMapSector'"; throw SquirrelError(v, msg.str()); } sq_pushstring(v, "get_tux_x", -1); - sq_newclosure(v, &WorldMap_get_tux_x_wrapper, 0); + sq_newclosure(v, &WorldMapSector_get_tux_x_wrapper, 0); sq_setparamscheck(v, SQ_MATCHTYPEMASKSTRING, "."); if(SQ_FAILED(sq_createslot(v, -3))) { throw SquirrelError(v, "Couldn't register function 'get_tux_x'"); } sq_pushstring(v, "get_tux_y", -1); - sq_newclosure(v, &WorldMap_get_tux_y_wrapper, 0); + sq_newclosure(v, &WorldMapSector_get_tux_y_wrapper, 0); sq_setparamscheck(v, SQ_MATCHTYPEMASKSTRING, "."); if(SQ_FAILED(sq_createslot(v, -3))) { throw SquirrelError(v, "Couldn't register function 'get_tux_y'"); } + sq_pushstring(v, "set_sector", -1); + sq_newclosure(v, &WorldMapSector_set_sector_wrapper, 0); + sq_setparamscheck(v, SQ_MATCHTYPEMASKSTRING, ".s"); + if(SQ_FAILED(sq_createslot(v, -3))) { + throw SquirrelError(v, "Couldn't register function 'set_sector'"); + } + + sq_pushstring(v, "spawn", -1); + sq_newclosure(v, &WorldMapSector_spawn_wrapper, 0); + sq_setparamscheck(v, SQ_MATCHTYPEMASKSTRING, ".ss"); + if(SQ_FAILED(sq_createslot(v, -3))) { + throw SquirrelError(v, "Couldn't register function 'spawn'"); + } + + sq_pushstring(v, "move_to_spawnpoint", -1); + sq_newclosure(v, &WorldMapSector_move_to_spawnpoint_wrapper, 0); + sq_setparamscheck(v, SQ_MATCHTYPEMASKSTRING, ".s"); + if(SQ_FAILED(sq_createslot(v, -3))) { + throw SquirrelError(v, "Couldn't register function 'move_to_spawnpoint'"); + } + if(SQ_FAILED(sq_createslot(v, -3))) { - throw SquirrelError(v, "Couldn't register class 'WorldMap'"); + throw SquirrelError(v, "Couldn't register class 'WorldMapSector'"); } // Register class Gradient diff --git a/src/scripting/wrapper.hpp b/src/scripting/wrapper.hpp index 3abba741bfa..d2625106a44 100644 --- a/src/scripting/wrapper.hpp +++ b/src/scripting/wrapper.hpp @@ -74,8 +74,8 @@ class WillOWisp; void create_squirrel_instance(HSQUIRRELVM v, scripting::WillOWisp* object, bool setup_releasehook = false); class Wind; void create_squirrel_instance(HSQUIRRELVM v, scripting::Wind* object, bool setup_releasehook = false); -class WorldMap; -void create_squirrel_instance(HSQUIRRELVM v, scripting::WorldMap* object, bool setup_releasehook = false); +class WorldMapSector; +void create_squirrel_instance(HSQUIRRELVM v, scripting::WorldMapSector* object, bool setup_releasehook = false); } diff --git a/src/scripting/wrapper.interface.hpp b/src/scripting/wrapper.interface.hpp index 440a93853b3..0827d26202d 100644 --- a/src/scripting/wrapper.interface.hpp +++ b/src/scripting/wrapper.interface.hpp @@ -33,6 +33,6 @@ #include "scripting/torch.hpp" #include "scripting/willowisp.hpp" #include "scripting/wind.hpp" -#include "scripting/worldmap.hpp" +#include "scripting/worldmap_sector.hpp" /* EOF */ diff --git a/src/sprite/sprite_data.cpp b/src/sprite/sprite_data.cpp index debdcbafaaa..15935aecdff 100644 --- a/src/sprite/sprite_data.cpp +++ b/src/sprite/sprite_data.cpp @@ -27,6 +27,7 @@ #include "util/reader_mapping.hpp" #include "util/reader_object.hpp" #include "video/surface.hpp" +#include "video/texture_manager.hpp" SpriteData::Action::Action() : name(), @@ -61,6 +62,41 @@ SpriteData::SpriteData(const ReaderMapping& mapping) : throw std::runtime_error("Error: Sprite without actions."); } +SpriteData::SpriteData(const std::string& image) : + actions(), + name() +{ + auto surface = Surface::from_file(image); + if (!TextureManager::current()->last_load_successful()) + throw std::runtime_error("Cannot load image."); + + auto action = create_action_from_surface(surface); + action->name = "default"; + actions[action->name] = std::move(action); +} + +SpriteData::SpriteData() : + actions(), + name() +{ + auto surface = Surface::from_texture(TextureManager::current()->create_dummy_texture()); + auto action = create_action_from_surface(surface); + action->name = "default"; + actions[action->name] = std::move(action); +} + +std::unique_ptr +SpriteData::create_action_from_surface(SurfacePtr surface) +{ + auto action = std::make_unique(); + + action->hitbox_w = static_cast(surface->get_width()); + action->hitbox_h = static_cast(surface->get_height()); + action->surfaces.push_back(surface); + + return action; +} + void SpriteData::parse_action(const ReaderMapping& mapping) { diff --git a/src/sprite/sprite_data.hpp b/src/sprite/sprite_data.hpp index 3d2e140cf9f..30e6d0cfec6 100644 --- a/src/sprite/sprite_data.hpp +++ b/src/sprite/sprite_data.hpp @@ -27,9 +27,18 @@ class ReaderMapping; class SpriteData final { + friend class Sprite; + public: - /** cur has to be a pointer to data in the form of ((hitbox 5 10 0 0) ...) */ - SpriteData(const ReaderMapping& cur); + /** + * Sprite from data. + * `mapping` has to be a pointer to data in the form of "((hitbox 5 10 0 0) ...)". + */ + SpriteData(const ReaderMapping& mapping); + /** Single-image sprite */ + SpriteData(const std::string& image); + /** Dummy texture sprite */ + SpriteData(); const std::string& get_name() const { @@ -37,8 +46,6 @@ class SpriteData final } private: - friend class Sprite; - struct Action { Action(); @@ -77,12 +84,15 @@ class SpriteData final std::vector surfaces; }; - typedef std::map > Actions; + typedef std::map > Actions; + + static std::unique_ptr create_action_from_surface(SurfacePtr surface); void parse_action(const ReaderMapping& mapping); /** Get an action */ const Action* get_action(const std::string& act) const; +private: Actions actions; std::string name; }; diff --git a/src/sprite/sprite_manager.cpp b/src/sprite/sprite_manager.cpp index a1bf50d0b88..db21bd7b5f7 100644 --- a/src/sprite/sprite_manager.cpp +++ b/src/sprite/sprite_manager.cpp @@ -1,5 +1,6 @@ // SuperTux // Copyright (C) 2006 Matthias Braun +// 2023 Vankata453 // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -16,73 +17,93 @@ #include "sprite/sprite_manager.hpp" +#include +#include + #include "sprite/sprite.hpp" #include "util/file_system.hpp" +#include "util/log.hpp" #include "util/reader_document.hpp" #include "util/reader_mapping.hpp" #include "util/string_util.hpp" -#include +std::unique_ptr SpriteManager::s_dummy_sprite_data = nullptr; SpriteManager::SpriteManager() : - sprites() + m_sprites(), + m_load_successful(false) { + if (!s_dummy_sprite_data) + s_dummy_sprite_data.reset(new SpriteData()); } SpritePtr SpriteManager::create(const std::string& name) { - Sprites::iterator i = sprites.find(name); + Sprites::iterator i = m_sprites.find(name); SpriteData* data; - if (i == sprites.end()) { - // try loading the spritefile - data = load(name); - if (data == nullptr) { - std::stringstream msg; - msg << "Sprite '" << name << "' not found."; - throw std::runtime_error(msg.str()); + if (i == m_sprites.end()) + { + // Try loading the sprite file. + try + { + data = load(name); } - } else { + catch (const std::exception& err) + { + log_warning << "Error loading sprite '" << name << "', using dummy texture: " << err.what() << std::endl; + m_load_successful = false; + return SpritePtr(new Sprite(*s_dummy_sprite_data)); // Return a dummy sprite. + } + } + else + { data = i->second.get(); } + m_load_successful = true; return SpritePtr(new Sprite(*data)); } SpriteData* SpriteManager::load(const std::string& filename) { - ReaderDocument doc = [filename](){ - try { - if (StringUtil::has_suffix(filename, ".sprite")) { - return ReaderDocument::from_file(filename); - } else { - std::stringstream text; - text << "(supertux-sprite (action " - << "(name \"default\") " - << "(images \"" << FileSystem::basename(filename) << "\")))"; - return ReaderDocument::from_stream(text, filename); - } - } catch(const std::exception& e) { + std::unique_ptr sprite_data; + + if (StringUtil::has_suffix(filename, ".sprite")) + { + std::optional doc; + try + { + doc = ReaderDocument::from_file(filename); + } + catch (const std::exception& err) + { std::ostringstream msg; msg << "Parse error when trying to load sprite '" << filename - << "': " << e.what() << "\n"; + << "': " << err.what(); throw std::runtime_error(msg.str()); } - }(); + auto root = doc->get_root(); - auto root = doc.get_root(); - - if (root.get_name() != "supertux-sprite") { - std::ostringstream msg; - msg << "'" << filename << "' is not a supertux-sprite file"; - throw std::runtime_error(msg.str()); - } else { - auto data = std::make_unique(root.get_mapping()); - sprites[filename] = std::move(data); - - return sprites[filename].get(); + if (root.get_name() != "supertux-sprite") + { + std::ostringstream msg; + msg << "'" << filename << "' is not a supertux-sprite file"; + throw std::runtime_error(msg.str()); + } + else + { + sprite_data = std::make_unique(root.get_mapping()); + } } + else + { + sprite_data = std::make_unique(filename); + } + + m_sprites[filename] = std::move(sprite_data); + return m_sprites[filename].get(); } /* EOF */ diff --git a/src/sprite/sprite_manager.hpp b/src/sprite/sprite_manager.hpp index f6aed1a573f..381e04f96a8 100644 --- a/src/sprite/sprite_manager.hpp +++ b/src/sprite/sprite_manager.hpp @@ -1,5 +1,6 @@ // SuperTux // Copyright (C) 2006 Matthias Braun +// 2023 Vankata453 // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -17,29 +18,40 @@ #ifndef HEADER_SUPERTUX_SPRITE_SPRITE_MANAGER_HPP #define HEADER_SUPERTUX_SPRITE_SPRITE_MANAGER_HPP +#include "util/currenton.hpp" + #include #include #include #include "sprite/sprite_ptr.hpp" -#include "util/currenton.hpp" class SpriteData; class SpriteManager final : public Currenton { +private: + static std::unique_ptr s_dummy_sprite_data; + private: typedef std::map > Sprites; - Sprites sprites; + Sprites m_sprites; + bool m_load_successful; public: SpriteManager(); + bool last_load_successful() const { return m_load_successful; } + /** loads a sprite. */ SpritePtr create(const std::string& filename); private: SpriteData* load(const std::string& filename); + +private: + SpriteManager(const SpriteManager&) = delete; + SpriteManager& operator=(const SpriteManager&) = delete; }; #endif diff --git a/src/squirrel/exposed_object.hpp b/src/squirrel/exposed_object.hpp index b50517ebb1c..ad28bb8fd28 100644 --- a/src/squirrel/exposed_object.hpp +++ b/src/squirrel/exposed_object.hpp @@ -75,7 +75,7 @@ class ExposedObject : virtual public ScriptInterface log_debug << "Exposing " << m_parent->get_class_name() << " object " << name << std::endl; - auto object = std::make_unique(m_parent->get_uid()); + auto object = std::make_unique(*m_parent); expose_object(vm, table_idx, std::move(object), name); } diff --git a/src/supertux/colorscheme.cpp b/src/supertux/colorscheme.cpp index e64848bf4f9..490d2834bc3 100644 --- a/src/supertux/colorscheme.cpp +++ b/src/supertux/colorscheme.cpp @@ -23,6 +23,7 @@ #include "object/text_object.hpp" #include "supertux/levelintro.hpp" #include "supertux/player_status_hud.hpp" +#include "supertux/statistics.hpp" #include "supertux/textscroller_screen.hpp" #include "trigger/climbable.hpp" #include "trigger/secretarea_trigger.hpp" @@ -61,9 +62,9 @@ Color SecretAreaTrigger::text_color(1.f,1.f,0.6f); Color Climbable::text_color(1.f,1.f,0.6f); -Color worldmap::WorldMap::level_title_color(1.f,1.f,1.f); -Color worldmap::WorldMap::message_color(1.f,1.f,0.6f); -Color worldmap::WorldMap::teleporter_message_color(1.f,1.f,1.f); +Color worldmap::WorldMap::s_level_title_color(1.f,1.f,1.f); +Color worldmap::WorldMap::s_message_color(1.f,1.f,0.6f); +Color worldmap::WorldMap::s_teleporter_message_color(1.f,1.f,1.f); Color ColorScheme::Text::small_color(1.f,1.f,1.f); Color ColorScheme::Text::heading_color(1.f,1.f,0.6f); diff --git a/src/supertux/d_scope.cpp b/src/supertux/d_scope.cpp index e6cf7ba61b6..488a2bb3fab 100644 --- a/src/supertux/d_scope.cpp +++ b/src/supertux/d_scope.cpp @@ -17,7 +17,7 @@ #include "supertux/d_scope.hpp" DynamicScopedRef d_sector; -DynamicScopedRef d_worldmap; +DynamicScopedRef d_worldmap_sector; DynamicScopedRef d_gameobject_manager; /* EOF */ diff --git a/src/supertux/d_scope.hpp b/src/supertux/d_scope.hpp index d122d07696d..9c790ad2bb3 100644 --- a/src/supertux/d_scope.hpp +++ b/src/supertux/d_scope.hpp @@ -23,20 +23,20 @@ class Sector; extern DynamicScopedRef d_sector; namespace worldmap { -class WorldMap; +class WorldMapSector; } // namespace worldmap -extern DynamicScopedRef d_worldmap; +extern DynamicScopedRef d_worldmap_sector; class GameObjectManager; extern DynamicScopedRef d_gameobject_manager; #define BIND_SECTOR(x) \ - auto sector_guard = d_sector.bind(x); \ + auto sector_guard = d_sector.bind(x); \ auto gameobject_manager_guard = d_gameobject_manager.bind(x) -#define BIND_WORLDMAP(x) \ - auto worldmap_guard = d_worldmap.bind(x); \ +#define BIND_WORLDMAP_SECTOR(x) \ + auto worldmap_guard = d_worldmap_sector.bind(x); \ auto gameobject_manager_guard = d_gameobject_manager.bind(x) #endif diff --git a/src/supertux/game_manager.cpp b/src/supertux/game_manager.cpp index a145ac76c55..370fdfb143c 100644 --- a/src/supertux/game_manager.cpp +++ b/src/supertux/game_manager.cpp @@ -28,6 +28,7 @@ #include "util/reader.hpp" #include "util/reader_document.hpp" #include "util/reader_mapping.hpp" +#include "worldmap/tux.hpp" #include "worldmap/worldmap.hpp" #include "worldmap/worldmap_screen.hpp" @@ -52,7 +53,8 @@ GameManager::start_level(const World& world, const std::string& level_filename, } void -GameManager::start_worldmap(const World& world, const std::string& spawnpoint, const std::string& worldmap_filename) +GameManager::start_worldmap(const World& world, const std::string& worldmap_filename, + const std::string& sector, const std::string& spawnpoint) { try { @@ -74,7 +76,7 @@ GameManager::start_worldmap(const World& world, const std::string& spawnpoint, c filename = world.get_worldmap_filename(); } - auto worldmap = std::make_unique(filename, *m_savegame, spawnpoint); + auto worldmap = std::make_unique(filename, *m_savegame, sector, spawnpoint); auto worldmap_screen = std::make_unique(std::move(worldmap)); ScreenManager::current()->push_screen(std::move(worldmap_screen)); } @@ -84,6 +86,15 @@ GameManager::start_worldmap(const World& world, const std::string& spawnpoint, c } } +void +GameManager::start_worldmap(const World& world, const std::string& worldmap_filename, + const std::optional>& start_pos) +{ + start_worldmap(world, worldmap_filename, start_pos ? start_pos->first : ""); + if (start_pos) + worldmap::WorldMapSector::current()->get_tux().set_initial_pos(start_pos->second); +} + bool GameManager::load_next_worldmap() { diff --git a/src/supertux/game_manager.hpp b/src/supertux/game_manager.hpp index 9be381f48b7..edc4baf64b6 100644 --- a/src/supertux/game_manager.hpp +++ b/src/supertux/game_manager.hpp @@ -31,7 +31,10 @@ class GameManager final : public Currenton public: GameManager(); - void start_worldmap(const World& world, const std::string& spawnpoint = "", const std::string& worldmap_filename = ""); + void start_worldmap(const World& world, const std::string& worldmap_filename = "", + const std::string& sector = "", const std::string& spawnpoint = ""); + void start_worldmap(const World& world, const std::string& worldmap_filename, + const std::optional>& start_pos); void start_level(const World& world, const std::string& level_filename, const std::optional>& start_pos = std::nullopt); diff --git a/src/supertux/game_object.hpp b/src/supertux/game_object.hpp index 843e6202d53..06f339a858d 100644 --- a/src/supertux/game_object.hpp +++ b/src/supertux/game_object.hpp @@ -186,6 +186,8 @@ class GameObject together (e.g. platform on a path) */ virtual void editor_update() {} + GameObjectManager* get_parent() const { return m_parent; } + protected: /** Parse object type. **/ void parse_type(const ReaderMapping& reader); diff --git a/src/supertux/game_object_factory.cpp b/src/supertux/game_object_factory.cpp index f1c1a611fdd..7eb9bb1c3d5 100644 --- a/src/supertux/game_object_factory.cpp +++ b/src/supertux/game_object_factory.cpp @@ -73,7 +73,6 @@ #include "badguy/yeti.hpp" #include "badguy/yeti_stalactite.hpp" #include "badguy/zeekling.hpp" -#include "editor/worldmap_objects.hpp" #include "math/vector.hpp" #include "object/ambient_light.hpp" #include "object/ambient_sound.hpp" @@ -143,6 +142,11 @@ #include "trigger/text_area.hpp" #include "util/reader_document.hpp" #include "util/reader_mapping.hpp" +#include "worldmap/level_tile.hpp" +#include "worldmap/spawn_point.hpp" +#include "worldmap/special_tile.hpp" +#include "worldmap/sprite_change.hpp" +#include "worldmap/teleporter.hpp" GameObjectFactory& GameObjectFactory::instance() @@ -231,7 +235,7 @@ GameObjectFactory::init_factories() add_factory("ambient-light"); add_factory("ambient_sound"); // backward compatibilty add_factory("ambient-sound"); - add_factory("background"); + add_factory("background", OBJ_PARAM_WORLDMAP); add_factory("path"); add_factory("bicycle-platform"); add_factory("bonusblock", OBJ_PARAM_DISPENSABLE); @@ -245,7 +249,7 @@ GameObjectFactory::init_factories() add_factory("particles-custom"); add_factory("particles-custom-file"); add_factory("coin", OBJ_PARAM_DISPENSABLE); - add_factory("decal"); + add_factory("decal", OBJ_PARAM_WORLDMAP); add_factory("explosion", OBJ_PARAM_DISPENSABLE); add_factory("fallblock", OBJ_PARAM_DISPENSABLE); add_factory("firefly"); @@ -262,7 +266,6 @@ GameObjectFactory::init_factories() add_factory("leveltime"); add_factory("lit-object"); add_factory("magicblock"); - add_display_name("#node", Path::Node::display_name()); add_factory("particle-zone"); add_factory("platform"); add_factory("pneumatic-platform"); @@ -298,17 +301,22 @@ GameObjectFactory::init_factories() // editor stuff add_factory("spawnpoint"); - // worldmap editor objects - add_factory("level"); - add_factory("special-tile"); - add_factory("sprite-change"); - add_factory("teleporter"); - add_factory("worldmap-spawnpoint"); + // worldmap objects + add_factory("level", OBJ_PARAM_WORLDMAP); + add_factory("special-tile", OBJ_PARAM_WORLDMAP); + add_factory("sprite-change", OBJ_PARAM_WORLDMAP); + add_factory("teleporter", OBJ_PARAM_WORLDMAP); + add_factory("worldmap-spawnpoint", OBJ_PARAM_WORLDMAP); - add_factory("tilemap", TileMap::display_name(), [](const ReaderMapping& reader) { + add_factory("tilemap", { + [](const ReaderMapping& reader) { auto tileset = TileManager::current()->get_tileset(Level::current()->get_tileset()); return std::make_unique(tileset, reader); - }); + }, + []() { + return TileMap::display_name(); + } + }); } std::unique_ptr diff --git a/src/supertux/game_object_manager.hpp b/src/supertux/game_object_manager.hpp index 857c2546e6f..7e7e1a31e84 100644 --- a/src/supertux/game_object_manager.hpp +++ b/src/supertux/game_object_manager.hpp @@ -199,7 +199,7 @@ class GameObjectManager void redo(); /** Save object change in the undo stack with given data. - Used to save an object's previous state before a change had occured. */ + Used to save an object's previous state before a change had occurred. */ void save_object_change(GameObject& object, const std::string& data); /** Clear undo/redo stacks. */ diff --git a/src/supertux/game_session.cpp b/src/supertux/game_session.cpp index ed76530b3e5..83ac9159592 100644 --- a/src/supertux/game_session.cpp +++ b/src/supertux/game_session.cpp @@ -75,6 +75,7 @@ GameSession::GameSession(const std::string& levelfile_, Savegame& savegame, Stat m_max_ice_bullets_at_start(), m_active(false), m_end_seq_started(false), + m_pause_target_timer(false), m_current_cutscene_text(), m_endsequence_timer() { @@ -110,6 +111,7 @@ GameSession::reset_level() clear_respawn_points(); m_activated_checkpoint = nullptr; + m_pause_target_timer = false; } int @@ -535,7 +537,7 @@ GameSession::update(float dt_sec, const Controller& controller) assert(m_currentsector != nullptr); // Update the world if (!m_end_sequence || !m_end_sequence->is_running()) { - if (!m_level->m_is_in_cutscene) + if (!m_level->m_is_in_cutscene && !m_pause_target_timer) { m_play_time += dt_sec; m_level->m_stats.finish(m_play_time); @@ -630,9 +632,9 @@ GameSession::finish(bool win) } if (win) { - if (WorldMap::current()) + if (WorldMapSector::current()) { - WorldMap::current()->finished_level(m_level.get()); + WorldMapSector::current()->finished_level(m_level.get()); } if (LevelsetScreen::current()) @@ -795,17 +797,17 @@ GameSession::start_sequence(Player* caller, Sequence seq, const SequenceData* da m_endsequence_timer.stop(); - if (const auto& worldmap = worldmap::WorldMap::current()) + if (const auto& worldmap_sector = worldmap::WorldMapSector::current()) { if (data != nullptr) { if (!data->fade_tilemap.empty()) { - worldmap->set_initial_fade_tilemap(data->fade_tilemap, data->fade_type); + worldmap_sector->set_initial_fade_tilemap(data->fade_tilemap, data->fade_type); } if (!data->spawnpoint.empty()) { - worldmap->set_initial_spawnpoint(data->spawnpoint); + worldmap_sector->get_worldmap().set_initial_spawnpoint(data->spawnpoint); } } } @@ -824,11 +826,21 @@ GameSession::start_sequence(Player* caller, Sequence seq, const SequenceData* da } // Stop all clocks. - for (const auto& obj : m_currentsector->get_objects()) + for (LevelTime& lt : m_currentsector->get_objects_by_type()) { - auto lt = dynamic_cast(obj.get()); - if (lt) - lt->stop(); + lt.stop(); + } +} +void +GameSession::set_target_timer_paused(bool paused) +{ + m_pause_target_timer = paused; + for (LevelTime& lt : m_currentsector->get_objects_by_type()) + { + if(paused) + lt.stop(); + else + lt.start(); } } diff --git a/src/supertux/game_session.hpp b/src/supertux/game_session.hpp index da569a7b5f9..75ed63dd953 100644 --- a/src/supertux/game_session.hpp +++ b/src/supertux/game_session.hpp @@ -107,6 +107,7 @@ class GameSession final : public Screen, Level& get_current_level() const { return *m_level; } void start_sequence(Player* caller, Sequence seq, const SequenceData* data = nullptr); + void set_target_timer_paused(bool paused); /** * returns the "working directory" usually this is the directory where the @@ -187,6 +188,7 @@ class GameSession final : public Screen, bool m_active; /** Game active? **/ bool m_end_seq_started; + bool m_pause_target_timer; std::unique_ptr m_current_cutscene_text; diff --git a/src/supertux/gameconfig.cpp b/src/supertux/gameconfig.cpp index e8e0ff420a1..12993f675f5 100644 --- a/src/supertux/gameconfig.cpp +++ b/src/supertux/gameconfig.cpp @@ -525,7 +525,7 @@ bool Config::is_christmas() const { std::time_t time = std::time(nullptr); - std::tm* now = std::localtime(&time); + const std::tm* now = std::localtime(&time); /* Activate Christmas mode from Dec 6th until Dec 31st. */ return now->tm_mday >= 6 && now->tm_mon == 11; diff --git a/src/supertux/level.hpp b/src/supertux/level.hpp index 67ec6f6c4e8..6d77d8f0980 100644 --- a/src/supertux/level.hpp +++ b/src/supertux/level.hpp @@ -52,6 +52,7 @@ class Level final size_t get_sector_count() const; Sector* get_sector(size_t num) const; + const std::vector >& get_sectors() const { return m_sectors; } std::string get_tileset() const { return m_tileset; } diff --git a/src/supertux/levelset.cpp b/src/supertux/levelset.cpp index 420c572d93e..4fc2991e791 100644 --- a/src/supertux/levelset.cpp +++ b/src/supertux/levelset.cpp @@ -48,25 +48,17 @@ void Levelset::walk_directory(const std::string& directory, bool recursively) { bool is_basedir = (directory == m_basedir); - char** files = PHYSFS_enumerateFiles(directory.c_str()); - if (!files) - { - log_warning << "Couldn't read subset dir '" << directory << "'" << std::endl; - return; - } - - for (const char* const* filename = files; *filename != nullptr; ++filename) - { - auto filepath = FileSystem::join(directory.c_str(), *filename); + bool enumerateSuccess = physfsutil::enumerate_files(directory, [directory, is_basedir, recursively, this](const auto& filename) { + auto filepath = FileSystem::join(directory, filename); if (physfsutil::is_directory(filepath) && recursively) { walk_directory(filepath, true); } - if (StringUtil::has_suffix(*filename, ".stl")) + if (StringUtil::has_suffix(filename, ".stl")) { if (is_basedir) { - m_levels.push_back(*filename); + m_levels.push_back(filename); } else { @@ -75,8 +67,12 @@ Levelset::walk_directory(const std::string& directory, bool recursively) m_levels.push_back(filepath); } } + }); + + if (!enumerateSuccess) + { + log_warning << "Couldn't read subset dir '" << directory << "'" << std::endl; } - PHYSFS_freeList(files); } /* EOF */ diff --git a/src/supertux/menu/cheat_menu.cpp b/src/supertux/menu/cheat_menu.cpp index b5ff1c469b2..17c2461ba69 100644 --- a/src/supertux/menu/cheat_menu.cpp +++ b/src/supertux/menu/cheat_menu.cpp @@ -81,7 +81,6 @@ CheatMenu::menu_action(MenuItem& item) case MNID_FIRE: MenuManager::instance().push_menu(std::make_unique([](Player& player, int count){ - log_warning << player.get_id() << std::endl; player.set_bonus(FIRE_BONUS); player.get_status().max_fire_bullets[player.get_id()] = count; })); @@ -89,7 +88,6 @@ CheatMenu::menu_action(MenuItem& item) case MNID_ICE: MenuManager::instance().push_menu(std::make_unique([](Player& player, int count){ - log_warning << player.get_id() << std::endl; player.set_bonus(ICE_BONUS); player.get_status().max_ice_bullets[player.get_id()] = count; })); @@ -97,7 +95,6 @@ CheatMenu::menu_action(MenuItem& item) case MNID_AIR: MenuManager::instance().push_menu(std::make_unique([](Player& player, int count){ - log_warning << player.get_id() << std::endl; player.set_bonus(AIR_BONUS); player.get_status().max_air_time[player.get_id()] = count; })); @@ -105,7 +102,6 @@ CheatMenu::menu_action(MenuItem& item) case MNID_EARTH: MenuManager::instance().push_menu(std::make_unique([](Player& player, int count){ - log_warning << player.get_id() << std::endl; player.set_bonus(EARTH_BONUS); player.get_status().max_earth_time[player.get_id()] = count; })); diff --git a/src/supertux/menu/contrib_menu.cpp b/src/supertux/menu/contrib_menu.cpp index 70d15eaab03..c8af865d31c 100644 --- a/src/supertux/menu/contrib_menu.cpp +++ b/src/supertux/menu/contrib_menu.cpp @@ -39,44 +39,31 @@ ContribMenu::ContribMenu() : { // Generating contrib levels list by making use of Level Subset std::vector level_worlds; - - std::unique_ptr - files(PHYSFS_enumerateFiles("levels"), - PHYSFS_freeList); - for (const char* const* filename = files.get(); *filename != nullptr; ++filename) - { - std::string filepath = FileSystem::join("levels", *filename); + physfsutil::enumerate_files("levels", [&level_worlds](const std::string& filename) { + std::string filepath = FileSystem::join("levels", filename); if (physfsutil::is_directory(filepath)) { level_worlds.push_back(filepath); } - } + }); - std::unique_ptr - addons(PHYSFS_enumerateFiles("custom"), - PHYSFS_freeList); - for (const char* const* addondir = addons.get(); *addondir != nullptr; ++addondir) - { - std::string addonpath = FileSystem::join("custom", *addondir); + physfsutil::enumerate_files("custom", [&level_worlds](const std::string& addon_filename) { + std::string addonpath = FileSystem::join("custom", addon_filename); if (physfsutil::is_directory(addonpath)) { std::string addonlevelpath = FileSystem::join(addonpath.c_str(), "levels"); if (physfsutil::is_directory(addonlevelpath)) { - std::unique_ptr - addonfiles(PHYSFS_enumerateFiles(addonlevelpath.c_str()), - PHYSFS_freeList); - for (const char* const* filename = addonfiles.get(); *filename != nullptr; ++filename) - { - std::string filepath = FileSystem::join(addonlevelpath.c_str(), *filename); + physfsutil::enumerate_files(addonlevelpath, [addonlevelpath, &level_worlds](const std::string& filename) { + std::string filepath = FileSystem::join(addonlevelpath.c_str(), filename); if (physfsutil::is_directory(filepath)) { level_worlds.push_back(filepath); } - } + }); } } - } + }); add_label(_("Contrib Levels")); add_hl(); diff --git a/src/supertux/menu/debug_menu.cpp b/src/supertux/menu/debug_menu.cpp index 5af92dfe0cd..44c3401bc92 100644 --- a/src/supertux/menu/debug_menu.cpp +++ b/src/supertux/menu/debug_menu.cpp @@ -19,6 +19,7 @@ #include #include +#include "editor/editor.hpp" #include "gui/item_stringselect.hpp" #include "supertux/debug.hpp" #include "supertux/gameconfig.hpp" @@ -76,6 +77,14 @@ DebugMenu::DebugMenu() : add_back(_("Back")); } +DebugMenu::~DebugMenu() +{ + auto editor = Editor::current(); + + if (editor == nullptr) return; + editor->m_reactivate_request = true; +} + void DebugMenu::menu_action(MenuItem& item) { diff --git a/src/supertux/menu/debug_menu.hpp b/src/supertux/menu/debug_menu.hpp index 7b075050680..e8a98a90c7b 100644 --- a/src/supertux/menu/debug_menu.hpp +++ b/src/supertux/menu/debug_menu.hpp @@ -30,6 +30,7 @@ class DebugMenu final : public Menu public: DebugMenu(); + virtual ~DebugMenu() override; virtual void menu_action(MenuItem& item) override; diff --git a/src/supertux/menu/editor_levelset_select_menu.cpp b/src/supertux/menu/editor_levelset_select_menu.cpp index 778e85f0c1b..2066eb80f7d 100644 --- a/src/supertux/menu/editor_levelset_select_menu.cpp +++ b/src/supertux/menu/editor_levelset_select_menu.cpp @@ -55,21 +55,18 @@ void EditorLevelsetSelectMenu::initialize() { Editor::current()->m_deactivate_request = true; - // Generating contrib levels list by making use of Level Subset - std::vector level_worlds; m_contrib_worlds.clear(); - std::unique_ptr - files(PHYSFS_enumerateFiles("levels"), - PHYSFS_freeList); - for (const char* const* filename = files.get(); *filename != nullptr; ++filename) - { - std::string filepath = FileSystem::join("levels", *filename); + // Generating contrib levels list by making use of Level Subset + std::vector level_worlds; + + physfsutil::enumerate_files("levels", [&level_worlds](const auto& filename) { + std::string filepath = FileSystem::join("levels", filename); if (physfsutil::is_directory(filepath)) { level_worlds.push_back(filepath); } - } + }); add_label(_("Choose World")); add_hl(); diff --git a/src/supertux/menu/game_menu.cpp b/src/supertux/menu/game_menu.cpp index bb225b5c57c..2202621a286 100644 --- a/src/supertux/menu/game_menu.cpp +++ b/src/supertux/menu/game_menu.cpp @@ -40,8 +40,7 @@ GameMenu::GameMenu() : GameSession::current()->reset_checkpoint_button = true; }), - abort_callback ( [] { - MenuManager::instance().clear_menu_stack(); + abort_callback([] { GameSession::current()->abort_level(); }) { diff --git a/src/supertux/menu/profile_menu.cpp b/src/supertux/menu/profile_menu.cpp index 8eb0d115629..6d10d2098e3 100644 --- a/src/supertux/menu/profile_menu.cpp +++ b/src/supertux/menu/profile_menu.cpp @@ -24,6 +24,7 @@ #include "gui/dialog.hpp" #include "gui/menu_manager.hpp" #include "gui/menu_item.hpp" +#include "physfs/util.hpp" #include "supertux/gameconfig.hpp" #include "supertux/globals.hpp" #include "supertux/menu/profile_name_menu.hpp" @@ -222,13 +223,10 @@ namespace savegames_util { std::vector get_savegames() { std::vector savegames; - char **rc = PHYSFS_enumerateFiles("/"); - char **i; - for (i = rc; *i != nullptr; i++) - { - if (std::string(*i).substr(0, 7) == "profile") savegames.push_back(std::stoi(std::string(*i).substr(7))); - } - PHYSFS_freeList(rc); + physfsutil::enumerate_files("/", [&savegames](const std::string& filename) { + if (std::string(filename).substr(0, 7) == "profile") + savegames.push_back(std::stoi(std::string(filename).substr(7))); + }); std::sort(savegames.begin(), savegames.end()); return savegames; } @@ -236,14 +234,10 @@ namespace savegames_util { void delete_savegames(int idx, bool reset) { const auto& profile_path = "profile" + std::to_string(idx); - std::unique_ptr - files(PHYSFS_enumerateFiles(profile_path.c_str()), - PHYSFS_freeList); - for (const char* const* filename = files.get(); *filename != nullptr; ++filename) - { - std::string filepath = FileSystem::join(profile_path.c_str(), *filename); + physfsutil::enumerate_files(profile_path, [profile_path](const std::string& filename) { + std::string filepath = FileSystem::join(profile_path.c_str(), filename); PHYSFS_delete(filepath.c_str()); - } + }); if (!reset) PHYSFS_delete(profile_path.c_str()); } } // namespace savegames_util diff --git a/src/supertux/menu/profile_name_menu.cpp b/src/supertux/menu/profile_name_menu.cpp index fae36d24ed3..61fee2f331b 100644 --- a/src/supertux/menu/profile_name_menu.cpp +++ b/src/supertux/menu/profile_name_menu.cpp @@ -70,7 +70,7 @@ ProfileNameMenu::menu_action(MenuItem& item) if (!PHYSFS_mkdir(profile_path.c_str())) { log_warning << "Error creating folder for profile " << id << std::endl; - Dialog::show_message(_("An error occured while creating the profile.")); + Dialog::show_message(_("An error occurred while creating the profile.")); return; } diff --git a/src/supertux/menu/worldmap_cheat_menu.cpp b/src/supertux/menu/worldmap_cheat_menu.cpp index 31653fbc7a9..3bdc62d0646 100644 --- a/src/supertux/menu/worldmap_cheat_menu.cpp +++ b/src/supertux/menu/worldmap_cheat_menu.cpp @@ -29,8 +29,8 @@ WorldmapCheatMenu::WorldmapCheatMenu() { - auto worldmap = worldmap::WorldMap::current(); - auto& tux = worldmap->get_singleton_by_type(); + auto worldmap_sector = worldmap::WorldMapSector::current(); + auto& tux = worldmap_sector->get_singleton_by_type(); add_label(_("Cheats")); add_hl(); @@ -60,8 +60,9 @@ void WorldmapCheatMenu::menu_action(MenuItem& item) { auto worldmap = worldmap::WorldMap::current(); - auto& tux = worldmap->get_singleton_by_type(); - assert(worldmap); + auto worldmap_sector = &worldmap->get_sector(); + auto& tux = worldmap_sector->get_singleton_by_type(); + assert(worldmap_sector); PlayerStatus& status = worldmap->get_savegame().get_player_status(); @@ -114,7 +115,7 @@ WorldmapCheatMenu::menu_action(MenuItem& item) case MNID_FINISH_LEVEL: { - auto level_tile = worldmap->at_level(); + auto level_tile = worldmap_sector->at_object(); if (level_tile) { level_tile->set_solved(true); @@ -126,7 +127,7 @@ WorldmapCheatMenu::menu_action(MenuItem& item) case MNID_RESET_LEVEL: { - auto level_tile = worldmap->at_level(); + auto level_tile = worldmap_sector->at_object(); if (level_tile) { level_tile->set_solved(false); @@ -151,7 +152,7 @@ WorldmapCheatMenu::menu_action(MenuItem& item) return; case MNID_MOVE_TO_MAIN: - worldmap->move_to_spawnpoint("main"); + worldmap_sector->move_to_spawnpoint("main"); MenuManager::instance().clear_menu_stack(); break; } @@ -181,11 +182,11 @@ WorldmapCheatMenu::do_cheat(PlayerStatus& status, WorldmapLevelSelectMenu::WorldmapLevelSelectMenu() { - auto worldmap = worldmap::WorldMap::current(); + auto worldmap_sector = worldmap::WorldMapSector::current(); int id = 0; add_label(_("Select level")); add_hl(); - for (auto& level : worldmap->get_objects_by_type()) + for (auto& level : worldmap_sector->get_objects_by_type()) { add_entry(id, level.get_title()); id++; @@ -197,14 +198,14 @@ WorldmapLevelSelectMenu::WorldmapLevelSelectMenu() void WorldmapLevelSelectMenu::menu_action(MenuItem& item) { - auto worldmap = worldmap::WorldMap::current(); - auto& tux = worldmap->get_singleton_by_type(); + auto worldmap_sector = worldmap::WorldMapSector::current(); + auto& tux = worldmap_sector->get_singleton_by_type(); int id = 0; - for(const auto& tile : worldmap->get_objects_by_type()) + for(const auto& tile : worldmap_sector->get_objects_by_type()) { if(id == item.get_id()) { - tux.set_tile_pos(tile.get_pos()); + tux.set_tile_pos(tile.get_tile_pos()); break; } id++; diff --git a/src/supertux/object_factory.cpp b/src/supertux/object_factory.cpp index 6eb135dca78..058a4d819dd 100644 --- a/src/supertux/object_factory.cpp +++ b/src/supertux/object_factory.cpp @@ -26,9 +26,7 @@ ObjectFactory::ObjectFactory() : m_badguys_names(), m_badguys_params(), m_objects_names(), - m_objects_display_names(), m_objects_params(), - m_other_display_names(), m_adding_badguys(false) { } @@ -46,29 +44,24 @@ ObjectFactory::create(const std::string& name, const ReaderMapping& reader) cons } else { - return it->second(reader); + return it->second.create(reader); } } std::string ObjectFactory::get_display_name(const std::string& name) const { - auto it = std::find(m_objects_names.begin(), m_objects_names.end(), name); + auto it = factories.find(name); - if (it == m_objects_names.end()) + if (it == factories.end()) { - auto it_other_names = m_other_display_names.find(name); // Attempt to find display name in non-factory object names. - if (it_other_names == m_other_display_names.end()) - { - std::stringstream msg; - msg << "No display name for object '" << name << "' found."; - throw std::runtime_error(msg.str()); - } - return it_other_names->second; + std::stringstream msg; + msg << "No factory for object '" << name << "' found. Unable to get display name."; + throw std::runtime_error(msg.str()); } else { - return m_objects_display_names[std::distance(m_objects_names.begin(), it)]; + return it->second.get_display_name(); } } diff --git a/src/supertux/object_factory.hpp b/src/supertux/object_factory.hpp index f26848b9619..c9b28071ce7 100644 --- a/src/supertux/object_factory.hpp +++ b/src/supertux/object_factory.hpp @@ -27,21 +27,23 @@ #include "math/fwd.hpp" #include "supertux/direction.hpp" -class ReaderMapping; class GameObject; +class ReaderMapping; class ObjectFactory { private: - typedef std::function (const ReaderMapping&)> FactoryFunction; - typedef std::map Factories; + struct FactoryFunctions { + std::function (const ReaderMapping&)> create; + std::function get_display_name; + }; + typedef std::map Factories; + Factories factories; std::vector m_badguys_names; std::vector m_badguys_params; std::vector m_objects_names; - std::vector m_objects_display_names; std::vector m_objects_params; - std::map m_other_display_names; // Stores display names for non-factory objects. protected: bool m_adding_badguys; @@ -51,7 +53,8 @@ class ObjectFactory { OBJ_PARAM_NONE = 0, OBJ_PARAM_PORTABLE = 0b10000000, - OBJ_PARAM_DISPENSABLE = 0b00100000, + OBJ_PARAM_WORLDMAP = 0b01000000, + OBJ_PARAM_DISPENSABLE = 0b00100000 }; public: @@ -69,13 +72,7 @@ class ObjectFactory protected: ObjectFactory(); - void add_display_name(const char* class_name, const std::string& display_name) - { - assert(m_other_display_names.find(class_name) == m_other_display_names.end()); - m_other_display_names[class_name] = display_name; - } - - void add_factory(const char* name, const std::string& display_name, const FactoryFunction& func, uint8_t obj_params = 0) + void add_factory(const char* name, const FactoryFunctions functions, uint8_t obj_params = 0) { assert(factories.find(name) == factories.end()); if (m_adding_badguys) @@ -84,17 +81,20 @@ class ObjectFactory m_badguys_params.push_back(obj_params); } m_objects_names.push_back(name); - m_objects_display_names.push_back(display_name); m_objects_params.push_back(obj_params); - factories[name] = func; + factories[name] = std::move(functions); } template - void add_factory(const char* class_name, uint8_t obj_params = 0, const std::string& display_name = "") + void add_factory(const char* class_name, uint8_t obj_params = 0) { - add_factory(class_name, (display_name == "") ? C::display_name() : display_name, - [](const ReaderMapping& reader) { - return std::make_unique(reader); + add_factory(class_name, { + [](const ReaderMapping& reader) { + return std::make_unique(reader); + }, + []() { + return C::display_name(); + } }, obj_params); } }; diff --git a/src/supertux/player_status.cpp b/src/supertux/player_status.cpp index de21effb5cf..f42e17f05ef 100644 --- a/src/supertux/player_status.cpp +++ b/src/supertux/player_status.cpp @@ -213,30 +213,7 @@ PlayerStatus::read(const ReaderMapping& mapping) } auto map = iter.as_mapping(); - - std::string bonusname; - if (map.get("bonus", bonusname)) { - if (bonusname == "none") { - bonus[id] = NO_BONUS; - } else if (bonusname == "growup") { - bonus[id] = GROWUP_BONUS; - } else if (bonusname == "fireflower") { - bonus[id] = FIRE_BONUS; - } else if (bonusname == "iceflower") { - bonus[id] = ICE_BONUS; - } else if (bonusname == "airflower") { - bonus[id] = AIR_BONUS; - } else if (bonusname == "earthflower") { - bonus[id] = EARTH_BONUS; - } else { - log_warning << "Unknown bonus '" << bonusname << "' in savefile for player " << (id + 1) << std::endl; - bonus[id] = NO_BONUS; - } - } - map.get("fireflowers", max_fire_bullets[id]); - map.get("iceflowers", max_ice_bullets[id]); - map.get("airflowers", max_air_time[id]); - map.get("earthflowers", max_earth_time[id]); + parse_bonus_mapping(map, id); } } catch (const std::exception& e) @@ -245,34 +222,40 @@ PlayerStatus::read(const ReaderMapping& mapping) } } + parse_bonus_mapping(mapping, 0); + + mapping.get("coins", coins); + + mapping.get("worldmap-sprite", worldmap_sprite); + mapping.get("last-worldmap", last_worldmap); +} + +void +PlayerStatus::parse_bonus_mapping(const ReaderMapping& map, int id) +{ std::string bonusname; - if (mapping.get("bonus", bonusname)) { + if (map.get("bonus", bonusname)) { if (bonusname == "none") { - bonus[0] = NO_BONUS; + bonus[id] = NO_BONUS; } else if (bonusname == "growup") { - bonus[0] = GROWUP_BONUS; + bonus[id] = GROWUP_BONUS; } else if (bonusname == "fireflower") { - bonus[0] = FIRE_BONUS; + bonus[id] = FIRE_BONUS; } else if (bonusname == "iceflower") { - bonus[0] = ICE_BONUS; + bonus[id] = ICE_BONUS; } else if (bonusname == "airflower") { - bonus[0] = AIR_BONUS; + bonus[id] = AIR_BONUS; } else if (bonusname == "earthflower") { - bonus[0] = EARTH_BONUS; + bonus[id] = EARTH_BONUS; } else { - log_warning << "Unknown bonus '" << bonusname << "' in savefile" << std::endl; - bonus[0] = NO_BONUS; + log_warning << "Unknown bonus '" << bonusname << "' in savefile for player " << (id + 1) << std::endl; + bonus[id] = NO_BONUS; } } - mapping.get("fireflowers", max_fire_bullets[0]); - mapping.get("iceflowers", max_ice_bullets[0]); - mapping.get("airflowers", max_air_time[0]); - mapping.get("earthflowers", max_earth_time[0]); - - mapping.get("coins", coins); - - mapping.get("worldmap-sprite", worldmap_sprite); - mapping.get("last-worldmap", last_worldmap); + map.get("fireflowers", max_fire_bullets[id]); + map.get("iceflowers", max_ice_bullets[id]); + map.get("airflowers", max_air_time[id]); + map.get("earthflowers", max_earth_time[id]); } std::string diff --git a/src/supertux/player_status.hpp b/src/supertux/player_status.hpp index b705e3c2a5d..4be8f1bd832 100644 --- a/src/supertux/player_status.hpp +++ b/src/supertux/player_status.hpp @@ -56,6 +56,9 @@ class PlayerStatus final void add_player(); void remove_player(int player_id); +private: + void parse_bonus_mapping(const ReaderMapping& map, int id); + public: int m_num_players; diff --git a/src/supertux/sector.cpp b/src/supertux/sector.cpp index c54eae92b65..06e120b4e19 100644 --- a/src/supertux/sector.cpp +++ b/src/supertux/sector.cpp @@ -55,6 +55,7 @@ #include "supertux/resources.hpp" #include "supertux/savegame.hpp" #include "supertux/tile.hpp" +#include "supertux/tile_manager.hpp" #include "util/file_system.hpp" #include "util/writer.hpp" #include "video/video_system.hpp" @@ -68,15 +69,14 @@ PlayerStatus dummy_player_status(1); } // namespace + Sector::Sector(Level& parent) : + Base::Sector("sector"), m_level(parent), - m_name(), m_fully_constructed(false), - m_init_script(), m_foremost_layer(), - m_squirrel_environment(new SquirrelEnvironment(SquirrelVirtualMachine::current()->get_vm(), "sector")), - m_collision_system(new CollisionSystem(*this)), - m_gravity(10.0) + m_gravity(10.0f), + m_collision_system(new CollisionSystem(*this)) { Savegame* savegame = (Editor::current() && Editor::is_active()) ? Editor::current()->m_savegame.get() : @@ -136,14 +136,18 @@ Sector::finish_construction(bool editable) if (!editable) { convert_tiles2gameobject(); - bool has_background = std::any_of(get_objects().begin(), get_objects().end(), - [](const auto& obj) { - return (dynamic_cast(obj.get()) || - dynamic_cast(obj.get())); - }); - if (!has_background) { - auto& gradient = add(); - gradient.set_gradient(Color(0.3f, 0.4f, 0.75f), Color(1.f, 1.f, 1.f)); + if (!m_level.is_worldmap()) + { + bool has_background = std::any_of(get_objects().begin(), get_objects().end(), + [](const auto& obj) { + return (dynamic_cast(obj.get()) || + dynamic_cast(obj.get())); + }); + if (!has_background) + { + auto& gradient = add(); + gradient.set_gradient(Color(0.3f, 0.4f, 0.75f), Color(1.f, 1.f, 1.f)); + } } } @@ -152,7 +156,8 @@ Sector::finish_construction(bool editable) } if (!get_object_by_type()) { - log_warning << "sector '" << get_name() << "' does not contain a camera." << std::endl; + if (!m_level.is_worldmap()) + log_warning << "sector '" << get_name() << "' does not contain a camera." << std::endl; add("Camera"); } @@ -185,12 +190,6 @@ Sector::finish_construction(bool editable) m_fully_constructed = true; } -Level& -Sector::get_level() const -{ - return m_level; -} - void Sector::activate(const std::string& spawnpoint) { @@ -203,7 +202,8 @@ Sector::activate(const std::string& spawnpoint) } if (!sp) { - log_warning << "Spawnpoint '" << spawnpoint << "' not found." << std::endl; + if (!m_level.is_worldmap()) + log_warning << "Spawnpoint '" << spawnpoint << "' not found." << std::endl; if (spawnpoint != "main") { activate("main"); } else { @@ -347,6 +347,18 @@ Sector::get_foremost_layer() const return m_foremost_layer; } +TileSet* +Sector::get_tileset() const +{ + return TileManager::current()->get_tileset(m_level.get_tileset()); +} + +bool +Sector::in_worldmap() const +{ + return m_level.is_worldmap(); +} + void Sector::update(float dt_sec) { @@ -614,12 +626,6 @@ Sector::set_gravity(float gravity) m_gravity = gravity; } -float -Sector::get_gravity() const -{ - return m_gravity; -} - Player* Sector::get_nearest_player (const Vector& pos) const { @@ -776,12 +782,6 @@ Sector::convert_tiles2gameobject() } } -void -Sector::run_script(const std::string& script, const std::string& sourcename) -{ - m_squirrel_environment->run_script(script, sourcename); -} - Camera& Sector::get_camera() const { diff --git a/src/supertux/sector.hpp b/src/supertux/sector.hpp index 8ace57d7d87..aacbb49e6a7 100644 --- a/src/supertux/sector.hpp +++ b/src/supertux/sector.hpp @@ -17,15 +17,15 @@ #ifndef HEADER_SUPERTUX_SUPERTUX_SECTOR_HPP #define HEADER_SUPERTUX_SUPERTUX_SECTOR_HPP +#include "supertux/sector_base.hpp" + #include #include #include "math/anchor_point.hpp" #include "math/easing.hpp" #include "math/fwd.hpp" -#include "squirrel/squirrel_environment.hpp" #include "supertux/d_scope.hpp" -#include "supertux/game_object_manager.hpp" #include "supertux/tile.hpp" #include "video/color.hpp" @@ -49,7 +49,7 @@ class Writer; /** Represents one of (potentially) multiple, separate parts of a Level. Sectors contain GameObjects, e.g. Badguys and Players. */ -class Sector final : public GameObjectManager +class Sector final : public Base::Sector { public: friend class CollisionSystem; @@ -67,20 +67,19 @@ class Sector final : public GameObjectManager Sector(Level& parent); ~Sector() override; - /** Needs to be called after parsing to finish the construction of - the Sector before using it. */ - void finish_construction(bool editable); + void finish_construction(bool editable) override; - Level& get_level() const; + Level& get_level() const { return m_level; } + TileSet* get_tileset() const override; + bool in_worldmap() const override; /** activates this sector (change music, initialize player class, ...) */ void activate(const std::string& spawnpoint); void activate(const Vector& player_pos); void deactivate(); - void update(float dt_sec); - - void draw(DrawingContext& context); + void draw(DrawingContext& context) override; + void update(float dt_sec) override; void save(Writer &writer); @@ -90,9 +89,6 @@ class Sector final : public GameObjectManager /** continues the looping sounds in whole sector. */ void play_looping_sounds(); - void set_name(const std::string& name_) { m_name = name_; } - const std::string& get_name() const { return m_name; } - /** tests if a given rectangle is inside the sector (a rectangle that is on top of the sector is considered inside) */ bool inside(const Rectf& rectangle) const; @@ -138,13 +134,7 @@ class Sector final : public GameObjectManager /** set gravity throughout sector */ void set_gravity(float gravity); - float get_gravity() const; - - void set_init_script(const std::string& init_script) { - m_init_script = init_script; - } - - void run_script(const std::string& script, const std::string& sourcename); + float get_gravity() const { return m_gravity; } Camera& get_camera() const; std::vector get_players() const; @@ -163,22 +153,15 @@ class Sector final : public GameObjectManager void convert_tiles2gameobject(); private: - /** Parent level containing this sector */ - Level& m_level; - - std::string m_name; + Level& m_level; // Parent level bool m_fully_constructed; - - std::string m_init_script; - int m_foremost_layer; - std::unique_ptr m_squirrel_environment; - std::unique_ptr m_collision_system; - float m_gravity; + std::unique_ptr m_collision_system; + private: Sector(const Sector&) = delete; Sector& operator=(const Sector&) = delete; diff --git a/src/supertux/sector_base.cpp b/src/supertux/sector_base.cpp new file mode 100644 index 00000000000..306f7722ffd --- /dev/null +++ b/src/supertux/sector_base.cpp @@ -0,0 +1,38 @@ +// SuperTux +// Copyright (C) 2023 Vankata453 +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "supertux/sector_base.hpp" + +#include "util/log.hpp" + +namespace Base { + +Sector::Sector(const std::string& type) : + m_name(), + m_init_script(), + m_squirrel_environment(new SquirrelEnvironment(SquirrelVirtualMachine::current()->get_vm(), type)) +{ +} + +void +Sector::run_script(const std::string& script, const std::string& sourcename) +{ + m_squirrel_environment->run_script(script, sourcename); +} + +} // namespace Base + +/* EOF */ diff --git a/src/supertux/sector_base.hpp b/src/supertux/sector_base.hpp new file mode 100644 index 00000000000..20b47db63c6 --- /dev/null +++ b/src/supertux/sector_base.hpp @@ -0,0 +1,66 @@ +// SuperTux +// Copyright (C) 2023 Vankata453 +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef HEADER_SUPERTUX_SUPERTUX_SECTOR_BASE_HPP +#define HEADER_SUPERTUX_SUPERTUX_SECTOR_BASE_HPP + +#include "supertux/game_object_manager.hpp" + +#include "squirrel/squirrel_environment.hpp" + +class Level; +class TileSet; + +namespace Base { + +/** A base for sector classes. Contains main properties and functions. */ +class Sector : public GameObjectManager +{ +public: + Sector(const std::string& type); + + /** Needs to be called after parsing to finish the construction of + the Sector before using it. */ + virtual void finish_construction(bool editable) {} + + virtual void draw(DrawingContext& context) = 0; + virtual void update(float dt_sec) = 0; + + virtual TileSet* get_tileset() const = 0; + virtual bool in_worldmap() const = 0; + + void set_name(const std::string& name) { m_name = name; } + const std::string& get_name() const { return m_name; } + + void set_init_script(const std::string& init_script) { m_init_script = init_script; } + void run_script(const std::string& script, const std::string& sourcename); + +protected: + std::string m_name; + std::string m_init_script; + + std::unique_ptr m_squirrel_environment; + +private: + Sector(const Sector&) = delete; + Sector& operator=(const Sector&) = delete; +}; + +} // namespace Base + +#endif + +/* EOF */ diff --git a/src/supertux/sector_parser.cpp b/src/supertux/sector_parser.cpp index 17c1aba89f9..45229011c39 100644 --- a/src/supertux/sector_parser.cpp +++ b/src/supertux/sector_parser.cpp @@ -1,5 +1,6 @@ // SuperTux // Copyright (C) 2015 Ingo Ruhnke +// 2023 Vankata453 // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -23,7 +24,6 @@ #include "badguy/fish_jumping.hpp" #include "badguy/jumpy.hpp" #include "editor/editor.hpp" -#include "editor/worldmap_objects.hpp" #include "object/ambient_light.hpp" #include "object/background.hpp" #include "object/camera.hpp" @@ -39,9 +39,9 @@ #include "supertux/level.hpp" #include "supertux/sector.hpp" #include "supertux/tile.hpp" -#include "supertux/tile_manager.hpp" #include "util/reader_collection.hpp" #include "util/reader_mapping.hpp" +#include "worldmap/spawn_point.hpp" static const std::string DEFAULT_BG = "images/background/antarctic/arctis2.png"; @@ -75,43 +75,57 @@ SectorParser::from_nothing(Level& level) return sector; } -SectorParser::SectorParser(Sector& sector, bool editable) : +SectorParser::SectorParser(Base::Sector& sector, bool editable) : m_sector(sector), m_editable(editable) { } std::unique_ptr -SectorParser::parse_object(const std::string& name_, const ReaderMapping& reader) +SectorParser::parse_object(const std::string& name, const ReaderMapping& reader) { - if (name_ == "money") { // for compatibility with old maps - return std::make_unique(reader); - } else if (name_ == "fish") { //because the "fish" was renamed to "fish-jumping" - return std::make_unique(reader); - } else { - try { - return GameObjectFactory::instance().create(name_, reader); - } catch(std::exception& e) { - log_warning << e.what() << "" << std::endl; - return {}; - } + if (parse_object_additional(name, reader)) + return {}; // Object was parsed by additional rules, so cancel regular object parsing. + + try + { + return GameObjectFactory::instance().create(name, reader); + } + catch (std::exception& err) + { + log_warning << err.what() << std::endl; + return {}; } } +bool +SectorParser::parse_object_additional(const std::string& name, const ReaderMapping& reader) +{ + return false; // No additional object parsing rules, continue with regular object parsing. +} + void -SectorParser::parse(const ReaderMapping& sector) +SectorParser::parse(const ReaderMapping& reader) { - auto iter = sector.get_iter(); + auto iter = reader.get_iter(); while (iter.next()) { - if (iter.get_key() == "name") { + if (iter.get_key() == "name") + { std::string value; iter.get(value); m_sector.set_name(value); - } else if (iter.get_key() == "gravity") { + } + else if (iter.get_key() == "gravity") + { + auto sector = dynamic_cast(&m_sector); + if (!sector) continue; + float value; iter.get(value); - m_sector.set_gravity(value); - } else if (iter.get_key() == "music") { + sector->set_gravity(value); + } + else if (iter.get_key() == "music") + { const auto& sx = iter.get_sexp(); if (sx.is_array() && sx.as_array().size() == 2 && sx.as_array()[1].is_string()) { std::string value; @@ -120,18 +134,22 @@ SectorParser::parse(const ReaderMapping& sector) } else { m_sector.add(iter.as_mapping()); } - } else if (iter.get_key() == "init-script") { + } + else if (iter.get_key() == "init-script") + { std::string value; iter.get(value); m_sector.set_init_script(value); - } else if (iter.get_key() == "ambient-light") { + } + else if (iter.get_key() == "ambient-light") + { const auto& sx = iter.get_sexp(); if (sx.is_array() && sx.as_array().size() >= 3 && sx.as_array()[1].is_real() && sx.as_array()[2].is_real() && sx.as_array()[3].is_real()) { // for backward compatibilty std::vector vColor; - bool hasColor = sector.get("ambient-light", vColor); + bool hasColor = reader.get("ambient-light", vColor); if (vColor.size() < 3 || !hasColor) { log_warning << "(ambient-light) requires a color as argument" << std::endl; } else { @@ -141,11 +159,12 @@ SectorParser::parse(const ReaderMapping& sector) // modern format m_sector.add(iter.as_mapping()); } - } else { + } + else + { auto object = parse_object(iter.get_key(), iter.as_mapping()); - if (object) { + if (object) m_sector.add_object(std::move(object)); - } } } @@ -157,9 +176,13 @@ SectorParser::parse_old_format(const ReaderMapping& reader) { m_sector.set_name("main"); - float gravity; - if (reader.get("gravity", gravity)) - m_sector.set_gravity(gravity); + auto sector = dynamic_cast(&m_sector); + if (sector) + { + float gravity; + if (reader.get("gravity", gravity)) + sector->set_gravity(gravity); + } std::string backgroundimage; if (reader.get("background", backgroundimage) && (!backgroundimage.empty())) { @@ -234,8 +257,7 @@ SectorParser::parse_old_format(const ReaderMapping& reader) std::vector tiles; if (reader.get("interactive-tm", tiles) || reader.get("tilemap", tiles)) { - auto* tileset = TileManager::current()->get_tileset(m_sector.get_level().get_tileset()); - auto& tilemap = m_sector.add(tileset); + auto& tilemap = m_sector.add(m_sector.get_tileset()); tilemap.set(width, height, tiles, LAYER_TILES, true); // replace tile id 112 (old invisible tile) with 1311 (new invisible tile) @@ -251,15 +273,13 @@ SectorParser::parse_old_format(const ReaderMapping& reader) } if (reader.get("background-tm", tiles)) { - auto* tileset = TileManager::current()->get_tileset(m_sector.get_level().get_tileset()); - auto& tilemap = m_sector.add(tileset); + auto& tilemap = m_sector.add(m_sector.get_tileset()); tilemap.set(width, height, tiles, LAYER_BACKGROUNDTILES, false); if (height < 19) tilemap.resize(width, 19); } if (reader.get("foreground-tm", tiles)) { - auto* tileset = TileManager::current()->get_tileset(m_sector.get_level().get_tileset()); - auto& tilemap = m_sector.add(tileset); + auto& tilemap = m_sector.add(m_sector.get_tileset()); tilemap.set(width, height, tiles, LAYER_FOREGROUNDTILES, false); // fill additional space in foreground with tiles of ID 2035 (lightmap/black) @@ -313,20 +333,18 @@ SectorParser::parse_old_format(const ReaderMapping& reader) void SectorParser::create_sector() { - auto tileset = TileManager::current()->get_tileset(m_sector.get_level().get_tileset()); - bool worldmap = m_sector.get_level().is_worldmap(); - if (!worldmap) + if (!m_sector.in_worldmap()) { auto& background = m_sector.add(); background.set_image(DEFAULT_BG); background.set_speed(0.5); - auto& bkgrd = m_sector.add(tileset); + auto& bkgrd = m_sector.add(m_sector.get_tileset()); bkgrd.resize(100, 35); bkgrd.set_layer(-100); bkgrd.set_solid(false); - auto& frgrd = m_sector.add(tileset); + auto& frgrd = m_sector.add(m_sector.get_tileset()); frgrd.resize(100, 35); frgrd.set_layer(100); frgrd.set_solid(false); @@ -338,14 +356,14 @@ SectorParser::create_sector() } else { - auto& water = m_sector.add(tileset); + auto& water = m_sector.add(m_sector.get_tileset()); water.resize(100, 35, 1); water.set_layer(-100); water.set_solid(false); } - auto& intact = m_sector.add(tileset); - if (worldmap) { + auto& intact = m_sector.add(m_sector.get_tileset()); + if (m_sector.in_worldmap()) { intact.resize(100, 100, 0); } else { intact.resize(100, 35, 0); @@ -353,8 +371,8 @@ SectorParser::create_sector() intact.set_layer(0); intact.set_solid(true); - if (worldmap) { - m_sector.add("main", Vector(4, 4)); + if (m_sector.in_worldmap()) { + m_sector.add("main", Vector(4, 4)); } else { m_sector.add("main", Vector(64, 480)); } diff --git a/src/supertux/sector_parser.hpp b/src/supertux/sector_parser.hpp index a204ed95fd8..adbb6821942 100644 --- a/src/supertux/sector_parser.hpp +++ b/src/supertux/sector_parser.hpp @@ -1,5 +1,6 @@ // SuperTux // Copyright (C) 2015 Ingo Ruhnke +// 2023 Vankata453 // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -25,23 +26,33 @@ class Level; class ReaderMapping; class Sector; -class SectorParser final +namespace Base { + class Sector; +} + +class SectorParser { public: static std::unique_ptr from_reader(Level& level, const ReaderMapping& sector, bool editable); static std::unique_ptr from_reader_old_format(Level& level, const ReaderMapping& sector, bool editable); static std::unique_ptr from_nothing(Level& level); -private: - SectorParser(Sector& sector, bool editable); +protected: + SectorParser(Base::Sector& sector, bool editable); + virtual ~SectorParser() {} void parse_old_format(const ReaderMapping& reader); - void parse(const ReaderMapping& sector); + void parse(const ReaderMapping& reader); void create_sector(); - std::unique_ptr parse_object(const std::string& name_, const ReaderMapping& reader); -private: - Sector& m_sector; + std::unique_ptr parse_object(const std::string& name, const ReaderMapping& reader); + + /** Allows setting additional rules for parsing objects. + Return value indicates whether the regular object parsing process should be skipped. **/ + virtual bool parse_object_additional(const std::string& name, const ReaderMapping& reader); + +protected: + Base::Sector& m_sector; bool m_editable; private: diff --git a/src/trigger/climbable.cpp b/src/trigger/climbable.cpp index cc96cdcd056..c47ed435f1d 100644 --- a/src/trigger/climbable.cpp +++ b/src/trigger/climbable.cpp @@ -34,31 +34,14 @@ const float POSITION_FIX_AY = 50; // y-wise acceleration applied to player when } Climbable::Climbable(const ReaderMapping& reader) : + Trigger(reader), climbed_by(), trying_to_climb(), - message(), - new_size(0.0f, 0.0f) + message() { - reader.get("x", m_col.m_bbox.get_left()); - reader.get("y", m_col.m_bbox.get_top()); - float w = 32, h = 32; - reader.get("width", w); - reader.get("height", h); - m_col.m_bbox.set_size(w, h); - new_size.x = w; - new_size.y = h; reader.get("message", message); } -Climbable::Climbable(const Rectf& area) : - climbed_by(), - trying_to_climb(), - message(), - new_size(0.0f, 0.0f) -{ - m_col.m_bbox = area; -} - Climbable::~Climbable() { for (auto* player : climbed_by) @@ -71,13 +54,7 @@ Climbable::~Climbable() ObjectSettings Climbable::get_settings() { - new_size.x = m_col.m_bbox.get_width(); - new_size.y = m_col.m_bbox.get_height(); - - ObjectSettings result = TriggerBase::get_settings(); - - // result.add_float(_("Width"), &new_size.x, "width"); - // result.add_float(_("Height"), &new_size.y, "height"); + ObjectSettings result = Trigger::get_settings(); result.add_translatable_text(_("Message"), &message, "message"); @@ -86,15 +63,11 @@ Climbable::get_settings() return result; } -void -Climbable::after_editor_set() { - m_col.m_bbox.set_size(new_size.x, new_size.y); -} - void Climbable::update(float dt_sec) { - TriggerBase::update(dt_sec); + Trigger::update(dt_sec); + auto it = climbed_by.begin(); while (it != climbed_by.end()) { diff --git a/src/trigger/climbable.hpp b/src/trigger/climbable.hpp index fe35c4f1d7e..ad72a58945e 100644 --- a/src/trigger/climbable.hpp +++ b/src/trigger/climbable.hpp @@ -24,11 +24,8 @@ #include "supertux/timer.hpp" class Color; -class DrawingContext; -class Player; -class ReaderMapping; -class Climbable final : public TriggerBase +class Climbable final : public Trigger { private: struct ClimbPlayer @@ -42,7 +39,6 @@ class Climbable final : public TriggerBase public: Climbable(const ReaderMapping& reader); - Climbable(const Rectf& area); ~Climbable() override; static std::string class_name() { return "climbable"; } @@ -52,7 +48,6 @@ class Climbable final : public TriggerBase virtual bool has_variable_size() const override { return true; } virtual ObjectSettings get_settings() override; - virtual void after_editor_set() override; virtual void event(Player& player, EventType type) override; virtual void update(float dt_sec) override; @@ -67,8 +62,6 @@ class Climbable final : public TriggerBase std::string message; private: - Vector new_size; - Climbable(const Climbable&) = delete; Climbable& operator=(const Climbable&) = delete; }; diff --git a/src/trigger/door.cpp b/src/trigger/door.cpp index 2f5dc6df4c7..3b9e7f0f1f7 100644 --- a/src/trigger/door.cpp +++ b/src/trigger/door.cpp @@ -30,73 +30,32 @@ #include "util/reader_mapping.hpp" Door::Door(const ReaderMapping& mapping) : - TriggerBase(mapping), + SpritedTrigger(mapping, "images/objects/door/door.sprite"), state(CLOSED), target_sector(), target_spawnpoint(), script(), - sprite_name("images/objects/door/door.sprite"), - sprite(), lock_sprite(SpriteManager::current()->create("images/objects/door/door_lock.sprite")), stay_open_timer(), unlocking_timer(), lock_warn_timer(), - m_flip(NO_FLIP), m_locked(), lock_color(Color::WHITE) { - mapping.get("x", m_col.m_bbox.get_left()); - mapping.get("y", m_col.m_bbox.get_top()); mapping.get("sector", target_sector); mapping.get("spawnpoint", target_spawnpoint); - mapping.get("sprite", sprite_name); + mapping.get("script", script); mapping.get("locked", m_locked); state = m_locked ? DoorState::LOCKED : DoorState::CLOSED; - mapping.get("script", script); - - sprite = SpriteManager::current()->create(sprite_name); - sprite->set_action("closed"); - m_col.m_bbox.set_size(sprite->get_current_hitbox_width(), sprite->get_current_hitbox_height()); + set_action("closed"); std::vector vColor; - if (mapping.get("lock-color", vColor)) { + if (mapping.get("lock-color", vColor)) lock_color = Color(vColor); - } else - { lock_color = Color::WHITE; - } - lock_sprite->set_color(lock_color); - - SoundManager::current()->preload("sounds/door.wav"); - // TODO: Add proper sounds - SoundManager::current()->preload("sounds/locked.ogg"); - SoundManager::current()->preload("sounds/turnkey.ogg"); -} - -Door::Door(int x, int y, const std::string& sector, const std::string& spawnpoint) : - TriggerBase(), - state(CLOSED), - target_sector(sector), - target_spawnpoint(spawnpoint), - script(), - sprite_name("images/objects/door/door.sprite"), - sprite(SpriteManager::current()->create(sprite_name)), - lock_sprite(SpriteManager::current()->create("images/objects/door/door_lock.sprite")), - stay_open_timer(), - unlocking_timer(), - lock_warn_timer(), - m_flip(NO_FLIP), - lock_color() -{ - state = m_locked ? DoorState::LOCKED : DoorState::CLOSED; - m_col.m_bbox.set_pos(Vector(static_cast(x), static_cast(y))); - - sprite->set_action("closed"); - m_col.m_bbox.set_size(sprite->get_current_hitbox_width(), sprite->get_current_hitbox_height()); - lock_sprite->set_color(lock_color); SoundManager::current()->preload("sounds/door.wav"); @@ -108,9 +67,8 @@ Door::Door(int x, int y, const std::string& sector, const std::string& spawnpoin ObjectSettings Door::get_settings() { - ObjectSettings result = TriggerBase::get_settings(); + ObjectSettings result = SpritedTrigger::get_settings(); - result.add_sprite(_("Sprite"), &sprite_name, "sprite", std::string("images/objects/door/door.sprite")); result.add_script(_("Script"), &script, "script"); result.add_text(_("Sector"), &target_sector, "sector"); result.add_text(_("Spawn point"), &target_spawnpoint, "spawnpoint"); @@ -123,14 +81,11 @@ Door::get_settings() } void -Door::after_editor_set() { - sprite = SpriteManager::current()->create(sprite_name); - m_col.m_bbox.set_size(sprite->get_current_hitbox_width(), sprite->get_current_hitbox_height()); - lock_sprite->set_color(lock_color); -} - -Door::~Door() +Door::after_editor_set() { + SpritedTrigger::after_editor_set(); + + lock_sprite->set_color(lock_color); } void @@ -141,9 +96,9 @@ Door::update(float ) break; case OPENING: // if door has finished opening, start timer and keep door open - if (sprite->animation_done()) { + if (m_sprite->animation_done()) { state = OPEN; - sprite->set_action("open"); + set_action("open"); stay_open_timer.start(1.0); } break; @@ -151,14 +106,14 @@ Door::update(float ) // if door was open long enough, start closing it if (stay_open_timer.check()) { state = CLOSING; - sprite->set_action("closing", 1); + set_action("closing", 1); } break; case CLOSING: // if door has finished closing, keep it shut - if (sprite->animation_done()) { + if (m_sprite->animation_done()) { state = CLOSED; - sprite->set_action("closed"); + set_action("closed"); } break; case LOCKED: @@ -181,7 +136,7 @@ Door::update(float ) void Door::draw(DrawingContext& context) { - sprite->draw(context.color(), m_col.m_bbox.p1(), LAYER_BACKGROUNDTILES+1, m_flip); + m_sprite->draw(context.color(), m_col.m_bbox.p1(), LAYER_BACKGROUNDTILES+1, m_flip); if (state == DoorState::LOCKED || state == DoorState::UNLOCKING) { @@ -201,7 +156,7 @@ Door::event(Player& , EventType type) if (type == EVENT_ACTIVATE) { state = OPENING; SoundManager::current()->play("sounds/door.wav", get_pos()); - sprite->set_action("opening", 1); + set_action("opening", 1); ScreenManager::current()->set_screen_fade(std::make_unique(FadeToBlack::FADEOUT, 1.0f)); } break; @@ -236,7 +191,7 @@ Door::collision(GameObject& other, const CollisionHit& hit_) if (player) { state = CLOSING; - sprite->set_action("closing", 1); + set_action("closing", 1); if (!script.empty()) { Sector::get().run_script(script, "Door"); } diff --git a/src/trigger/door.hpp b/src/trigger/door.hpp index 884a4f24eae..7d9c542fcf7 100644 --- a/src/trigger/door.hpp +++ b/src/trigger/door.hpp @@ -17,19 +17,14 @@ #ifndef HEADER_SUPERTUX_TRIGGER_DOOR_HPP #define HEADER_SUPERTUX_TRIGGER_DOOR_HPP -#include "supertux/timer.hpp" #include "trigger/trigger_base.hpp" -#include "video/flip.hpp" -class Player; -class ReaderMapping; +#include "supertux/timer.hpp" -class Door final : public TriggerBase +class Door final : public SpritedTrigger { public: Door(const ReaderMapping& reader); - Door(int x, int y, const std::string& sector, const std::string& spawnpoint); - ~Door() override; static std::string class_name() { return "door"; } virtual std::string get_class_name() const override { return class_name(); } @@ -42,10 +37,14 @@ class Door final : public TriggerBase virtual void update(float dt_sec) override; virtual void draw(DrawingContext& context) override; virtual void event(Player& player, EventType type) override; + virtual HitResponse collision(GameObject& other, const CollisionHit& hit) override; + virtual void on_flip(float height) override; - virtual bool is_locked() const { return m_locked; } - virtual void unlock(); + + bool is_locked() const { return m_locked; } + void unlock(); + Color get_lock_color() const { return lock_color; } private: @@ -63,13 +62,10 @@ class Door final : public TriggerBase std::string target_sector; /**< target sector to teleport to */ std::string target_spawnpoint; /**< target spawnpoint to teleport to */ std::string script; - std::string sprite_name; - SpritePtr sprite; /**< "door" sprite to render */ SpritePtr lock_sprite; Timer stay_open_timer; /**< time until door will close again */ Timer unlocking_timer; Timer lock_warn_timer; - Flip m_flip; bool m_locked; Color lock_color; diff --git a/src/trigger/scripttrigger.cpp b/src/trigger/scripttrigger.cpp index 81d480d0ead..98683307a4c 100644 --- a/src/trigger/scripttrigger.cpp +++ b/src/trigger/scripttrigger.cpp @@ -24,26 +24,18 @@ #include "video/drawing_context.hpp" ScriptTrigger::ScriptTrigger(const ReaderMapping& reader) : - TriggerBase(reader), + Trigger(reader), triggerevent(), script(), - new_size(0.0f, 0.0f), must_activate(false), oneshot(false), runcount(0) { - if (m_col.m_bbox.get_width() == 0.f) - m_col.m_bbox.set_width(32.f); - - if (m_col.m_bbox.get_height() == 0.f) - m_col.m_bbox.set_height(32.f); - reader.get("script", script); reader.get("button", must_activate); reader.get("oneshot", oneshot); - if (script.empty()) { + if (script.empty() && !Editor::is_active()) log_warning << "No script set in script trigger" << std::endl; - } if (must_activate) triggerevent = EVENT_ACTIVATE; @@ -51,26 +43,10 @@ ScriptTrigger::ScriptTrigger(const ReaderMapping& reader) : triggerevent = EVENT_TOUCH; } -ScriptTrigger::ScriptTrigger(const Vector& pos, const std::string& script_) : - TriggerBase(), - triggerevent(EVENT_TOUCH), - script(script_), - new_size(0.0f, 0.0f), - must_activate(), - oneshot(false), - runcount(0) -{ - m_col.m_bbox.set_pos(pos); - m_col.m_bbox.set_size(32, 32); -} - ObjectSettings ScriptTrigger::get_settings() { - new_size.x = m_col.m_bbox.get_width(); - new_size.y = m_col.m_bbox.get_height(); - - ObjectSettings result = TriggerBase::get_settings(); + ObjectSettings result = Trigger::get_settings(); result.add_script(_("Script"), &script, "script"); result.add_bool(_("Button"), &must_activate, "button"); @@ -81,25 +57,11 @@ ScriptTrigger::get_settings() return result; } -void -ScriptTrigger::after_editor_set() { - //m_col.m_bbox.set_size(new_size.x, new_size.y); - if (must_activate) { - triggerevent = EVENT_ACTIVATE; - } else { - triggerevent = EVENT_TOUCH; - } -} - void ScriptTrigger::event(Player& , EventType type) { - if (type != triggerevent) - return; - - if (oneshot && runcount >= 1) { + if (type != triggerevent || (oneshot && runcount >= 1)) return; - } Sector::get().run_script(script, "ScriptTrigger"); runcount++; @@ -108,10 +70,9 @@ ScriptTrigger::event(Player& , EventType type) void ScriptTrigger::draw(DrawingContext& context) { - if (Editor::is_active() || g_debug.show_collision_rects) { + if (Editor::is_active() || g_debug.show_collision_rects) context.color().draw_filled_rect(m_col.m_bbox, Color(1.0f, 0.0f, 1.0f, 0.6f), 0.0f, LAYER_OBJECTS); - } } /* EOF */ diff --git a/src/trigger/scripttrigger.hpp b/src/trigger/scripttrigger.hpp index a5923593ebd..538990e8752 100644 --- a/src/trigger/scripttrigger.hpp +++ b/src/trigger/scripttrigger.hpp @@ -19,14 +19,10 @@ #include "trigger/trigger_base.hpp" -class ReaderMapping; -class Writer; - -class ScriptTrigger final : public TriggerBase +class ScriptTrigger final : public Trigger { public: ScriptTrigger(const ReaderMapping& reader); - ScriptTrigger(const Vector& pos, const std::string& script); static std::string class_name() { return "scripttrigger"; } virtual std::string get_class_name() const override { return class_name(); } @@ -35,17 +31,13 @@ class ScriptTrigger final : public TriggerBase virtual bool has_variable_size() const override { return true; } virtual ObjectSettings get_settings() override; - virtual void after_editor_set() override; virtual void event(Player& player, EventType type) override; virtual void draw(DrawingContext& context) override; - void write(Writer& writer); - private: EventType triggerevent; std::string script; - Vector new_size; bool must_activate; bool oneshot; int runcount; diff --git a/src/trigger/secretarea_trigger.cpp b/src/trigger/secretarea_trigger.cpp index c8c0ba3a796..857e0c009c6 100644 --- a/src/trigger/secretarea_trigger.cpp +++ b/src/trigger/secretarea_trigger.cpp @@ -32,48 +32,25 @@ static const float MESSAGE_TIME=3.5; SecretAreaTrigger::SecretAreaTrigger(const ReaderMapping& reader) : - TriggerBase(reader), + Trigger(reader), message_timer(), message_displayed(false), message(), fade_tilemap(), - script(), - new_size(0.0f, 0.0f) + script() { - reader.get("x", m_col.m_bbox.get_left()); - reader.get("y", m_col.m_bbox.get_top()); - float w,h; - reader.get("width", w, 32.0f); - reader.get("height", h, 32.0f); - m_col.m_bbox.set_size(w, h); - new_size.x = w; - new_size.y = h; reader.get("fade-tilemap", fade_tilemap); reader.get("message", message); - if (message.empty() && !Editor::is_active()) { - message = _("You found a secret area!"); - } reader.get("script", script); -} -SecretAreaTrigger::SecretAreaTrigger(const Rectf& area, const std::string& fade_tilemap_) : - message_timer(), - message_displayed(false), - message(_("You found a secret area!")), - fade_tilemap(fade_tilemap_), - script(), - new_size(0.0f, 0.0f) -{ - m_col.m_bbox = area; + if (message.empty() && !Editor::is_active()) + message = _("You found a secret area!"); } ObjectSettings SecretAreaTrigger::get_settings() { - new_size.x = m_col.m_bbox.get_width(); - new_size.y = m_col.m_bbox.get_height(); - - ObjectSettings result = TriggerBase::get_settings(); + ObjectSettings result = Trigger::get_settings(); result.add_text(_("Name"), &m_name); result.add_text(_("Fade tilemap"), &fade_tilemap, "fade-tilemap"); @@ -85,18 +62,6 @@ SecretAreaTrigger::get_settings() return result; } -void -SecretAreaTrigger::after_editor_set() -{ - m_col.m_bbox.set_size(new_size.x, new_size.y); -} - -std::string -SecretAreaTrigger::get_fade_tilemap_name() const -{ - return fade_tilemap; -} - void SecretAreaTrigger::draw(DrawingContext& context) { diff --git a/src/trigger/secretarea_trigger.hpp b/src/trigger/secretarea_trigger.hpp index c6d7b2c04b0..a32a237b5bb 100644 --- a/src/trigger/secretarea_trigger.hpp +++ b/src/trigger/secretarea_trigger.hpp @@ -22,15 +22,14 @@ #include "supertux/timer.hpp" class Color; -class DrawingContext; -class ReaderMapping; -class SecretAreaTrigger final : public TriggerBase +class SecretAreaTrigger final : public Trigger { +public: static Color text_color; + public: SecretAreaTrigger(const ReaderMapping& reader); - SecretAreaTrigger(const Rectf& area, const std::string& fade_tilemap = ""); static std::string class_name() { return "secretarea"; } virtual std::string get_class_name() const override { return class_name(); } @@ -39,12 +38,11 @@ class SecretAreaTrigger final : public TriggerBase virtual bool has_variable_size() const override { return true; } virtual ObjectSettings get_settings() override; - virtual void after_editor_set() override; virtual void event(Player& player, EventType type) override; virtual void draw(DrawingContext& context) override; - std::string get_fade_tilemap_name() const; + const std::string& get_fade_tilemap_name() const { return fade_tilemap; } private: Timer message_timer; @@ -52,7 +50,6 @@ class SecretAreaTrigger final : public TriggerBase std::string message; /**< message to display, default "You found a secret area!" */ std::string fade_tilemap; /**< tilemap to fade away when trigger is activated, or empty if you don't care */ std::string script; /**< optional script to run when trigger is activated */ - Vector new_size; private: SecretAreaTrigger(const SecretAreaTrigger&) = delete; diff --git a/src/trigger/sequence_trigger.cpp b/src/trigger/sequence_trigger.cpp index 95e42d372ad..299bf7f1c58 100644 --- a/src/trigger/sequence_trigger.cpp +++ b/src/trigger/sequence_trigger.cpp @@ -24,53 +24,26 @@ #include "video/drawing_context.hpp" SequenceTrigger::SequenceTrigger(const ReaderMapping& reader) : + Trigger(reader), triggerevent(EVENT_TOUCH), sequence(SEQ_ENDSEQUENCE), - new_size(0.0f, 0.0f), new_spawnpoint(), fade_tilemap(), fade() { - reader.get("x", m_col.m_bbox.get_left(), 0.0f); - reader.get("y", m_col.m_bbox.get_top(), 0.0f); - float w, h; - reader.get("width", w, 32.0f); - reader.get("height", h, 32.0f); - m_col.m_bbox.set_size(w, h); - new_size.x = w; - new_size.y = h; std::string sequence_name; - if (reader.get("sequence", sequence_name)) { + if (reader.get("sequence", sequence_name)) sequence = string_to_sequence(sequence_name); - } reader.get("new_spawnpoint", new_spawnpoint); reader.get("fade_tilemap", fade_tilemap); reader.get("fade", reinterpret_cast(fade)); } -SequenceTrigger::SequenceTrigger(const Vector& pos, const std::string& sequence_name) : - triggerevent(EVENT_TOUCH), - sequence(string_to_sequence(sequence_name)), - new_size(0.0f, 0.0f), - new_spawnpoint(), - fade_tilemap(), - fade() -{ - m_col.m_bbox.set_pos(pos); - m_col.m_bbox.set_size(32, 32); -} - ObjectSettings SequenceTrigger::get_settings() { - new_size.x = m_col.m_bbox.get_width(); - new_size.y = m_col.m_bbox.get_height(); - - ObjectSettings result = TriggerBase::get_settings(); - - //result.add_float(_("Width"), &new_size.x, "width"); - //result.add_float(_("Height"), &new_size.y, "height"); + ObjectSettings result = Trigger::get_settings(); result.add_enum(_("Sequence"), reinterpret_cast(&sequence), {_("end sequence"), _("stop Tux"), _("fireworks")}, @@ -88,19 +61,14 @@ SequenceTrigger::get_settings() return result; } -void -SequenceTrigger::after_editor_set() -{ - m_col.m_bbox.set_size(new_size.x, new_size.y); -} - void SequenceTrigger::event(Player& player, EventType type) { - if (type == triggerevent) { - auto data = SequenceData(new_spawnpoint, fade_tilemap, fade); - player.trigger_sequence(sequence, &data); - } + if (type != triggerevent) + return; + + auto data = SequenceData(new_spawnpoint, fade_tilemap, fade); + player.trigger_sequence(sequence, &data); } std::string @@ -112,10 +80,9 @@ SequenceTrigger::get_sequence_name() const void SequenceTrigger::draw(DrawingContext& context) { - if (Editor::is_active() || g_debug.show_collision_rects) { + if (Editor::is_active() || g_debug.show_collision_rects) context.color().draw_filled_rect(m_col.m_bbox, Color(1.0f, 0.0f, 0.0f, 0.6f), 0.0f, LAYER_OBJECTS); - } } /* EOF */ diff --git a/src/trigger/sequence_trigger.hpp b/src/trigger/sequence_trigger.hpp index cf60df6f56a..4de2fcb446b 100644 --- a/src/trigger/sequence_trigger.hpp +++ b/src/trigger/sequence_trigger.hpp @@ -17,17 +17,14 @@ #ifndef HEADER_SUPERTUX_TRIGGER_SEQUENCE_TRIGGER_HPP #define HEADER_SUPERTUX_TRIGGER_SEQUENCE_TRIGGER_HPP -#include "supertux/sequence.hpp" #include "trigger/trigger_base.hpp" -class Player; -class ReaderMapping; +#include "supertux/sequence.hpp" -class SequenceTrigger final : public TriggerBase +class SequenceTrigger final : public Trigger { public: SequenceTrigger(const ReaderMapping& reader); - SequenceTrigger(const Vector& pos, const std::string& sequence_name); static std::string class_name() { return "sequencetrigger"; } virtual std::string get_class_name() const override { return class_name(); } @@ -36,7 +33,6 @@ class SequenceTrigger final : public TriggerBase virtual bool has_variable_size() const override { return true; } virtual ObjectSettings get_settings() override; - virtual void after_editor_set() override; virtual void event(Player& player, EventType type) override; virtual void draw(DrawingContext& context) override; @@ -46,7 +42,6 @@ class SequenceTrigger final : public TriggerBase private: EventType triggerevent; Sequence sequence; - Vector new_size; std::string new_spawnpoint; std::string fade_tilemap; TilemapFadeType fade; diff --git a/src/trigger/switch.cpp b/src/trigger/switch.cpp index 23f958f1d38..4c0fba4ccb3 100644 --- a/src/trigger/switch.cpp +++ b/src/trigger/switch.cpp @@ -19,36 +19,26 @@ #include #include "audio/sound_manager.hpp" -#include "sprite/sprite.hpp" -#include "sprite/sprite_manager.hpp" #include "supertux/flip_level_transformer.hpp" #include "supertux/sector.hpp" #include "util/log.hpp" #include "util/reader_mapping.hpp" namespace { -const std::string SWITCH_SOUND = "sounds/switch.ogg"; -} + const std::string SWITCH_SOUND = "sounds/switch.ogg"; +} // namespace Switch::Switch(const ReaderMapping& reader) : - sprite_name(), - sprite(), + SpritedTrigger(reader, "images/objects/switch/left.sprite"), script(), off_script(), state(OFF), - bistable(), - m_flip(NO_FLIP) + bistable() { - if (!reader.get("x", m_col.m_bbox.get_left())) throw std::runtime_error("no x position set"); - if (!reader.get("y", m_col.m_bbox.get_top())) throw std::runtime_error("no y position set"); - if (!reader.get("sprite", sprite_name)) sprite_name = "images/objects/switch/left.sprite"; - sprite = SpriteManager::current()->create(sprite_name); - m_col.m_bbox.set_size(sprite->get_current_hitbox_width(), sprite->get_current_hitbox_height()); - reader.get("script", script); bistable = reader.get("off-script", off_script); - SoundManager::current()->preload( SWITCH_SOUND ); + SoundManager::current()->preload(SWITCH_SOUND); } Switch::~Switch() @@ -58,9 +48,8 @@ Switch::~Switch() ObjectSettings Switch::get_settings() { - ObjectSettings result = TriggerBase::get_settings(); + ObjectSettings result = SpritedTrigger::get_settings(); - result.add_sprite(_("Sprite"), &sprite_name, "sprite", std::string("images/objects/switch/left.sprite")); result.add_script(_("Turn on script"), &script, "script"); result.add_script(_("Turn off script"), &off_script, "off-script"); @@ -69,12 +58,6 @@ Switch::get_settings() return result; } -void -Switch::after_editor_set() { - sprite = SpriteManager::current()->create(sprite_name); - m_col.m_bbox.set_size(sprite->get_current_hitbox_width(), sprite->get_current_hitbox_height()); -} - void Switch::update(float ) { @@ -82,42 +65,36 @@ Switch::update(float ) case OFF: break; case TURN_ON: - if (sprite->animation_done()) { + if (m_sprite->animation_done()) { std::ostringstream location; location << "switch" << m_col.m_bbox.p1(); Sector::get().run_script(script, location.str()); - sprite->set_action("on", 1); + set_action("on", 1); state = ON; } break; case ON: - if (sprite->animation_done() && !bistable) { - sprite->set_action("turnoff", 1); + if (m_sprite->animation_done() && !bistable) { + set_action("turnoff", 1); state = TURN_OFF; } break; case TURN_OFF: - if (sprite->animation_done()) { + if (m_sprite->animation_done()) { if (bistable) { std::ostringstream location; location << "switch" << m_col.m_bbox.p1(); Sector::get().run_script(off_script, location.str()); } - sprite->set_action("off"); + set_action("off"); state = OFF; } break; } } -void -Switch::draw(DrawingContext& context) -{ - sprite->draw(context.color(), m_col.m_bbox.p1(), LAYER_TILES, m_flip); -} - void Switch::event(Player& , EventType type) { @@ -125,7 +102,7 @@ Switch::event(Player& , EventType type) switch (state) { case OFF: - sprite->set_action("turnon", 1); + set_action("turnon", 1); SoundManager::current()->play(SWITCH_SOUND, get_pos()); state = TURN_ON; break; @@ -133,7 +110,7 @@ Switch::event(Player& , EventType type) break; case ON: if (bistable) { - sprite->set_action("turnoff", 1); + set_action("turnoff", 1); SoundManager::current()->play(SWITCH_SOUND, get_pos()); state = TURN_OFF; } @@ -146,7 +123,7 @@ Switch::event(Player& , EventType type) void Switch::on_flip(float height) { - TriggerBase::on_flip(height); + SpritedTrigger::on_flip(height); FlipLevelTransformer::transform_flip(m_flip); } diff --git a/src/trigger/switch.hpp b/src/trigger/switch.hpp index af9ba21563a..19a5d728c33 100644 --- a/src/trigger/switch.hpp +++ b/src/trigger/switch.hpp @@ -17,14 +17,9 @@ #ifndef HEADER_SUPERTUX_TRIGGER_SWITCH_HPP #define HEADER_SUPERTUX_TRIGGER_SWITCH_HPP -#include - #include "trigger/trigger_base.hpp" -#include "video/flip.hpp" - -class ReaderMapping; -class Switch final : public TriggerBase +class Switch final : public SpritedTrigger { public: Switch(const ReaderMapping& reader); @@ -36,10 +31,8 @@ class Switch final : public TriggerBase virtual std::string get_display_name() const override { return display_name(); } virtual ObjectSettings get_settings() override; - virtual void after_editor_set() override; virtual void update(float dt_sec) override; - virtual void draw(DrawingContext& context) override; virtual void event(Player& player, EventType type) override; virtual void on_flip(float height) override; @@ -53,13 +46,10 @@ class Switch final : public TriggerBase }; private: - std::string sprite_name; - SpritePtr sprite; std::string script; std::string off_script; SwitchState state; bool bistable; - Flip m_flip; private: Switch(const Switch&) = delete; diff --git a/src/trigger/text_area.cpp b/src/trigger/text_area.cpp index 6f5968b791a..de904bc3dd4 100644 --- a/src/trigger/text_area.cpp +++ b/src/trigger/text_area.cpp @@ -27,7 +27,7 @@ #include "video/layer.hpp" TextArea::TextArea(const ReaderMapping& mapping) : - TriggerBase(mapping), + Trigger(mapping), m_once(false), m_items(), m_delay(4.0f), @@ -38,12 +38,6 @@ TextArea::TextArea(const ReaderMapping& mapping) : m_anchor(AnchorPoint::ANCHOR_MIDDLE), m_anchor_offset(0, 0) { - float w, h; - - mapping.get("x", m_col.m_bbox.get_left(), 0.0f); - mapping.get("y", m_col.m_bbox.get_top(), 0.0f); - mapping.get("width", w, 32.0f); - mapping.get("height", h, 32.0f); mapping.get("strings", m_items); mapping.get("delay", m_delay); mapping.get("once", m_once); @@ -54,22 +48,6 @@ TextArea::TextArea(const ReaderMapping& mapping) : std::string anchor; if (mapping.get("anchor-point", anchor)) m_anchor = string_to_anchor_point(anchor); - - m_col.m_bbox.set_size(w, h); -} - -TextArea::TextArea(const Vector& pos) : - m_once(false), - m_items(), - m_delay(4.0f), - m_fade_delay(1.0f), - m_current_text(0), - m_status(Status::NOT_STARTED), - m_timer(), - m_anchor(AnchorPoint::ANCHOR_MIDDLE) -{ - m_col.m_bbox.set_pos(pos); - m_col.m_bbox.set_size(32, 32); } void @@ -113,7 +91,7 @@ TextArea::event(Player& player, EventType type) void TextArea::update(float dt_sec) { - TriggerBase::update(dt_sec); + Trigger::update(dt_sec); if (m_timer.check()) { @@ -158,7 +136,7 @@ TextArea::update(float dt_sec) ObjectSettings TextArea::get_settings() { - ObjectSettings settings = TriggerBase::get_settings(); + ObjectSettings settings = Trigger::get_settings(); settings.add_bool(_("Once"), &m_once, "once"); settings.add_float(_("Text change time"), &m_delay, "delay"); diff --git a/src/trigger/text_area.hpp b/src/trigger/text_area.hpp index ae0a6061165..0c0b3d9df31 100644 --- a/src/trigger/text_area.hpp +++ b/src/trigger/text_area.hpp @@ -19,9 +19,10 @@ #define HEADER_SUPERTUX_TRIGGER_TEXT_AREA_HPP #include "trigger/trigger_base.hpp" + #include "supertux/timer.hpp" -class TextArea final : public TriggerBase +class TextArea final : public Trigger { private: enum class Status @@ -35,7 +36,6 @@ class TextArea final : public TriggerBase public: TextArea(const ReaderMapping& mapping); - TextArea(const Vector& pos); virtual void draw(DrawingContext& context) override; virtual void event(Player& player, EventType type) override; diff --git a/src/trigger/trigger_base.cpp b/src/trigger/trigger_base.cpp index 034b66274f0..9e0a3af2177 100644 --- a/src/trigger/trigger_base.cpp +++ b/src/trigger/trigger_base.cpp @@ -19,21 +19,10 @@ #include "object/player.hpp" #include "sprite/sprite.hpp" -TriggerBase::TriggerBase(const ReaderMapping& mapping) : - MovingObject(mapping), - m_sprite(), - m_hit(), - m_losetouch_listeners() -{ - set_group(COLGROUP_TOUCHABLE); -} - TriggerBase::TriggerBase() : - m_sprite(), m_hit(), m_losetouch_listeners() { - set_group(COLGROUP_TOUCHABLE); } TriggerBase::~TriggerBase() @@ -46,7 +35,7 @@ TriggerBase::~TriggerBase() } void -TriggerBase::update(float ) +TriggerBase::update() { for (unsigned i = 0; i < m_losetouch_listeners.size(); i++) { @@ -62,15 +51,6 @@ TriggerBase::update(float ) m_hit.clear(); } -void -TriggerBase::draw(DrawingContext& context) -{ - if (!m_sprite.get()) - return; - - m_sprite->draw(context.color(), get_pos(), LAYER_TILES+1); -} - HitResponse TriggerBase::collision(GameObject& other, const CollisionHit& ) { @@ -96,4 +76,23 @@ TriggerBase::object_removed(GameObject* object) m_losetouch_listeners.end()); } + +Trigger::Trigger(const ReaderMapping& reader) : + MovingObject(reader) +{ + set_group(COLGROUP_TOUCHABLE); + + if (m_col.m_bbox.get_width() == 0.f) + m_col.m_bbox.set_width(32.f); + + if (m_col.m_bbox.get_height() == 0.f) + m_col.m_bbox.set_height(32.f); +} + + +SpritedTrigger::SpritedTrigger(const ReaderMapping& reader, const std::string& sprite_name) : + MovingSprite(reader, sprite_name, LAYER_TILES + 1, COLGROUP_TOUCHABLE) +{ +} + /* EOF */ diff --git a/src/trigger/trigger_base.hpp b/src/trigger/trigger_base.hpp index 176e8426cf2..8a8941d9776 100644 --- a/src/trigger/trigger_base.hpp +++ b/src/trigger/trigger_base.hpp @@ -17,21 +17,18 @@ #ifndef HEADER_SUPERTUX_TRIGGER_TRIGGER_BASE_HPP #define HEADER_SUPERTUX_TRIGGER_TRIGGER_BASE_HPP -#include - +#include "object/moving_sprite.hpp" #include "supertux/moving_object.hpp" #include "supertux/object_remove_listener.hpp" -#include "sprite/sprite_ptr.hpp" -#include "video/layer.hpp" +#include class Player; /** This class is the base class for all objects you can interact with in some way. There are several interaction types defined like touch and activate */ -class TriggerBase : public MovingObject, - public ObjectRemoveListener +class TriggerBase : public ObjectRemoveListener { public: enum EventType { @@ -41,24 +38,20 @@ class TriggerBase : public MovingObject, }; public: - TriggerBase(const ReaderMapping& mapping); TriggerBase(); ~TriggerBase() override; - virtual void update(float dt_sec) override; - virtual void draw(DrawingContext& context) override; - virtual HitResponse collision(GameObject& other, const CollisionHit& hit) override; - /** Receive trigger events */ virtual void event(Player& player, EventType type) = 0; /** Called by GameObject destructor of an object in losetouch_listeners */ virtual void object_removed(GameObject* object) override; - virtual int get_layer() const override { return LAYER_TILES + 1; } +protected: + void update(); + HitResponse collision(GameObject& other, const CollisionHit& hit); private: - SpritePtr m_sprite; std::vector m_hit; /** Players that will be informed when we lose touch with them */ @@ -69,6 +62,50 @@ class TriggerBase : public MovingObject, TriggerBase& operator=(const TriggerBase&) = delete; }; + +class Trigger : public MovingObject, + public TriggerBase +{ +public: + Trigger(const ReaderMapping& reader); + + virtual void update(float) override + { + TriggerBase::update(); + } + virtual HitResponse collision(GameObject& other, const CollisionHit& hit) override + { + return TriggerBase::collision(other, hit); + } + + int get_layer() const override { return LAYER_TILES + 1; } + +private: + Trigger(const Trigger&) = delete; + Trigger& operator=(const Trigger&) = delete; +}; + + +class SpritedTrigger : public MovingSprite, + public TriggerBase +{ +public: + SpritedTrigger(const ReaderMapping& reader, const std::string& sprite_name); + + virtual void update(float) override + { + TriggerBase::update(); + } + virtual HitResponse collision(GameObject& other, const CollisionHit& hit) override + { + return TriggerBase::collision(other, hit); + } + +private: + SpritedTrigger(const SpritedTrigger&) = delete; + SpritedTrigger& operator=(const SpritedTrigger&) = delete; +}; + #endif /* EOF */ diff --git a/src/video/bitmap_font.cpp b/src/video/bitmap_font.cpp index c847591e83e..72c87f6c35b 100644 --- a/src/video/bitmap_font.cpp +++ b/src/video/bitmap_font.cpp @@ -23,6 +23,7 @@ #include #include "physfs/physfs_sdl.hpp" +#include "physfs/util.hpp" #include "util/file_system.hpp" #include "util/log.hpp" #include "util/reader_document.hpp" @@ -70,20 +71,18 @@ BitmapFont::BitmapFont(GlyphWidth glyph_width_, const std::string fontname = FileSystem::basename(filename); // scan for prefix-filename in addons search path - char **rc = PHYSFS_enumerateFiles(fontdir.c_str()); - for (char **i = rc; *i != nullptr; i++) { - std::string filename_(*i); - if ( filename_.rfind(fontname) != std::string::npos ) { + physfsutil::enumerate_files(fontdir, [fontdir, fontname, this](const std::string& file) { + std::string filepath = FileSystem::join(fontdir, file); + if (file.rfind(fontname) != std::string::npos) { try { - loadFontFile(fontdir + filename_); + loadFontFile(filepath); } catch(const std::exception& e) { log_fatal << "Couldn't load font file: " << e.what() << std::endl; } } - } - PHYSFS_freeList(rc); + }); } void diff --git a/src/video/drawing_context.cpp b/src/video/drawing_context.cpp index dcf619f3812..437f4fbdb32 100644 --- a/src/video/drawing_context.cpp +++ b/src/video/drawing_context.cpp @@ -114,13 +114,6 @@ DrawingContext::pop_transform() const Rect DrawingContext::get_viewport() const { - Rect tmp( - static_cast(static_cast(m_viewport.left) / transform().scale), - static_cast(static_cast(m_viewport.top) / transform().scale), - static_cast(static_cast(m_viewport.right) / transform().scale), - static_cast(static_cast(m_viewport.bottom) / transform().scale) - ); - return m_viewport; } diff --git a/src/video/sdl/sdl_painter.cpp b/src/video/sdl/sdl_painter.cpp index 062a5c81ce8..b8a2b76c4a1 100644 --- a/src/video/sdl/sdl_painter.cpp +++ b/src/video/sdl/sdl_painter.cpp @@ -278,19 +278,20 @@ SDLPainter::draw_gradient(const GradientRequest& request) for (int i = 0; i < n; ++i) { SDL_Rect rect; + if (direction == VERTICAL || direction == VERTICAL_SECTOR) { rect.x = static_cast(region.get_left()); - rect.y = static_cast(region.get_bottom() * static_cast(i) / static_cast(n)); - rect.w = static_cast(region.get_right()); - rect.h = static_cast((region.get_bottom() * static_cast(i+1) / static_cast(n)) - static_cast(rect.y)); + rect.y = static_cast(region.get_top() + (region.get_bottom() - region.get_top()) * static_cast(i) / static_cast(n)); + rect.w = static_cast(region.get_right() - region.get_left()); + rect.h = static_cast(ceilf((region.get_bottom() - region.get_top()) / static_cast(n))); } else { - rect.x = static_cast(region.get_right() * static_cast(i) / static_cast(n)); + rect.x = static_cast(region.get_left() + (region.get_right() - region.get_left()) * static_cast(i) / static_cast(n)); rect.y = static_cast(region.get_top()); - rect.w = static_cast((region.get_right() * static_cast(i+1) / static_cast(n)) - static_cast(rect.x)); - rect.h = static_cast(region.get_bottom()); + rect.w = static_cast(ceilf((region.get_right() - region.get_left()) / static_cast(n))); + rect.h = static_cast(region.get_bottom() - region.get_top()); } float p = static_cast(i+1) / static_cast(n); diff --git a/src/video/texture_manager.cpp b/src/video/texture_manager.cpp index 1b950bdc715..a9849659a16 100644 --- a/src/video/texture_manager.cpp +++ b/src/video/texture_manager.cpp @@ -75,9 +75,12 @@ GLenum string2filter(const std::string& text) } // namespace +const std::string TextureManager::s_dummy_texture = "images/engine/missing.png"; + TextureManager::TextureManager() : m_image_textures(), - m_surfaces() + m_surfaces(), + m_load_successful(false) { } @@ -256,6 +259,7 @@ TextureManager::reap_cache_entry(const Texture::Key& key) TexturePtr TextureManager::create_image_texture(const std::string& filename, const Rect& rect, const Sampler& sampler) { + m_load_successful = true; try { return create_image_texture_raw(filename, rect, sampler); @@ -263,6 +267,7 @@ TextureManager::create_image_texture(const std::string& filename, const Rect& re catch(const std::exception& err) { log_warning << "Couldn't load texture '" << filename << "' (now using dummy texture): " << err.what() << std::endl; + m_load_successful = false; return create_dummy_texture(); } } @@ -355,6 +360,7 @@ TextureManager::create_image_texture_raw(const std::string& filename, const Rect TexturePtr TextureManager::create_image_texture(const std::string& filename, const Sampler& sampler) { + m_load_successful = true; try { return create_image_texture_raw(filename, sampler); @@ -362,6 +368,7 @@ TextureManager::create_image_texture(const std::string& filename, const Sampler& catch (const std::exception& err) { log_warning << "Couldn't load texture '" << filename << "' (now using dummy texture): " << err.what() << std::endl; + m_load_successful = false; return create_dummy_texture(); } } @@ -387,26 +394,24 @@ TextureManager::create_image_texture_raw(const std::string& filename, const Samp TexturePtr TextureManager::create_dummy_texture() { - const std::string dummy_texture_fname = "images/engine/missing.png"; - // on error, try loading placeholder file try { - TexturePtr tex = create_image_texture_raw(dummy_texture_fname, Sampler()); + TexturePtr tex = create_image_texture_raw(s_dummy_texture, Sampler()); return tex; } catch (const std::exception& err) { // on error (when loading placeholder), try using empty surface, // when that fails to, just give up - SDLSurfacePtr image(SDL_CreateRGBSurface(0, 1024, 1024, 8, 0, 0, 0, 0)); + SDLSurfacePtr image(SDL_CreateRGBSurface(0, 128, 128, 8, 0, 0, 0, 0)); if (!image) { throw; } else { - log_warning << "Couldn't load texture '" << dummy_texture_fname << "' (now using empty one): " << err.what() << std::endl; + log_warning << "Couldn't load texture '" << s_dummy_texture << "' (now using empty one): " << err.what() << std::endl; TexturePtr texture = VideoSystem::current()->new_texture(*image); return texture; } diff --git a/src/video/texture_manager.hpp b/src/video/texture_manager.hpp index 1b7afb976c9..73c80930ee9 100644 --- a/src/video/texture_manager.hpp +++ b/src/video/texture_manager.hpp @@ -39,9 +39,11 @@ struct SDL_Surface; class TextureManager final : public Currenton { -public: friend class Texture; +private: + static const std::string s_dummy_texture; + public: TextureManager(); ~TextureManager() override; @@ -51,9 +53,12 @@ class TextureManager final : public Currenton TexturePtr get(const std::string& filename, const std::optional& rect, const Sampler& sampler = Sampler()); + TexturePtr create_dummy_texture(); void debug_print(std::ostream& out) const; + bool last_load_successful() const { return m_load_successful; } + private: const SDL_Surface& get_surface(const std::string& filename); void reap_cache_entry(const Texture::Key& key); @@ -67,11 +72,10 @@ class TextureManager final : public Currenton TexturePtr create_image_texture_raw(const std::string& filename, const Sampler& sampler); TexturePtr create_image_texture_raw(const std::string& filename, const Rect& rect, const Sampler& sampler); - TexturePtr create_dummy_texture(); - private: std::map > m_image_textures; std::map m_surfaces; + bool m_load_successful; private: TextureManager(const TextureManager&) = delete; diff --git a/src/worldmap/camera.cpp b/src/worldmap/camera.cpp index c3a6f3b18f3..0998dacdedf 100644 --- a/src/worldmap/camera.cpp +++ b/src/worldmap/camera.cpp @@ -82,8 +82,7 @@ Camera::pan() Vector Camera::get_camera_pos_for_tux() const { - auto& worldmap = *WorldMap::current(); - auto& tux = worldmap.get_singleton_by_type(); + auto& tux = WorldMapSector::current()->get_singleton_by_type(); Vector camera_offset_(0.0f, 0.0f); Vector tux_pos = tux.get_pos(); @@ -95,7 +94,7 @@ Camera::get_camera_pos_for_tux() const void Camera::clamp_camera_position(Vector& c) const { - auto& worldmap = *WorldMap::current(); + auto& worldmap_sector = *WorldMapSector::current(); if (c.x < 0) { c.x = 0; @@ -105,20 +104,20 @@ Camera::clamp_camera_position(Vector& c) const c.y = 0; } - if (c.x > worldmap.get_width() - static_cast(SCREEN_WIDTH)) { - c.x = worldmap.get_width() - static_cast(SCREEN_WIDTH); + if (c.x > worldmap_sector.get_width() - static_cast(SCREEN_WIDTH)) { + c.x = worldmap_sector.get_width() - static_cast(SCREEN_WIDTH); } - if (c.y > worldmap.get_height() - static_cast(SCREEN_HEIGHT)) { - c.y = worldmap.get_height() - static_cast(SCREEN_HEIGHT); + if (c.y > worldmap_sector.get_height() - static_cast(SCREEN_HEIGHT)) { + c.y = worldmap_sector.get_height() - static_cast(SCREEN_HEIGHT); } - if (worldmap.get_width() < static_cast(SCREEN_WIDTH)) { - c.x = (worldmap.get_width() - static_cast(SCREEN_WIDTH)) / 2.0f; + if (worldmap_sector.get_width() < static_cast(SCREEN_WIDTH)) { + c.x = (worldmap_sector.get_width() - static_cast(SCREEN_WIDTH)) / 2.0f; } - if (worldmap.get_height() < static_cast(SCREEN_HEIGHT)) { - c.y = (worldmap.get_height() - static_cast(SCREEN_HEIGHT)) / 2.0f; + if (worldmap_sector.get_height() < static_cast(SCREEN_HEIGHT)) { + c.y = (worldmap_sector.get_height() - static_cast(SCREEN_HEIGHT)) / 2.0f; } } diff --git a/src/worldmap/direction.cpp b/src/worldmap/direction.cpp index 8b5db588861..6b6036cb256 100644 --- a/src/worldmap/direction.cpp +++ b/src/worldmap/direction.cpp @@ -16,8 +16,6 @@ #include "worldmap/direction.hpp" -#include "editor/object_option.hpp" -#include "util/gettext.hpp" #include "util/log.hpp" namespace worldmap { @@ -40,8 +38,7 @@ Direction reverse_dir(Direction direction) return Direction::NONE; } -std::string -direction_to_string(Direction direction) +std::string direction_to_string(Direction direction) { switch (direction) { @@ -58,8 +55,7 @@ direction_to_string(Direction direction) } } -Direction -string_to_direction(const std::string& directory) +Direction string_to_direction(const std::string& directory) { if (directory == "west") return Direction::WEST; diff --git a/src/worldmap/direction.hpp b/src/worldmap/direction.hpp index e3f34f21579..9d91389f49d 100644 --- a/src/worldmap/direction.hpp +++ b/src/worldmap/direction.hpp @@ -17,11 +17,8 @@ #ifndef HEADER_SUPERTUX_WORLDMAP_DIRECTION_HPP #define HEADER_SUPERTUX_WORLDMAP_DIRECTION_HPP -#include #include -class ObjectOption; - namespace worldmap { enum class Direction { NONE, WEST, EAST, NORTH, SOUTH }; diff --git a/src/worldmap/level_tile.cpp b/src/worldmap/level_tile.cpp index 2fa039c8fbe..9bccae009a2 100644 --- a/src/worldmap/level_tile.cpp +++ b/src/worldmap/level_tile.cpp @@ -1,6 +1,7 @@ // SuperTux // Copyright (C) 2004 Ingo Ruhnke // Copyright (C) 2006 Christoph Sommer +// 2023 Vankata453 // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -19,19 +20,20 @@ #include -#include "sprite/sprite.hpp" -#include "sprite/sprite_manager.hpp" +#include "editor/editor.hpp" +#include "supertux/level_parser.hpp" #include "util/file_system.hpp" +#include "util/gettext.hpp" #include "util/log.hpp" +#include "util/reader_document.hpp" #include "util/reader_mapping.hpp" #include "worldmap/worldmap.hpp" namespace worldmap { -LevelTile::LevelTile(const std::string& basedir, const ReaderMapping& mapping) : - GameObject(mapping), - m_pos(0.0f, 0.0f), - m_basedir(basedir), +LevelTile::LevelTile(const ReaderMapping& mapping) : + WorldMapObject(mapping, "images/worldmap/common/leveldot.sprite"), + m_basedir(WorldMap::current() ? WorldMap::current()->get_levels_path() : ""), m_level_filename(), m_title(), m_auto_play(false), @@ -40,32 +42,25 @@ LevelTile::LevelTile(const std::string& basedir, const ReaderMapping& mapping) : m_solved(false), m_perfect(false), m_statistics(), - m_sprite(), - m_title_color(WorldMap::level_title_color) + m_title_color(WorldMap::s_level_title_color) { + if (m_basedir.empty() && Editor::current() && Editor::current()->get_world()) + m_basedir = Editor::current()->get_world()->get_basedir(); + if (!mapping.get("level", m_level_filename)) { // Hack for backward compatibility with 0.5.x level m_level_filename = m_name; } - mapping.get("x", m_pos.x); - mapping.get("y", m_pos.y); mapping.get("auto-play", m_auto_play); - - std::string spritefile = "images/worldmap/common/leveldot.sprite"; - mapping.get("sprite", spritefile); - m_sprite = SpriteManager::current()->create(spritefile); - mapping.get("extro-script", m_extro_script); std::vector vColor; - if (mapping.get("color", vColor)) { + if (mapping.get("color", vColor)) m_title_color = Color(vColor); - } - if (m_basedir == "./") { + if (m_basedir == "./") m_basedir = ""; - } if (!PHYSFS_exists(FileSystem::join(m_basedir, m_level_filename).c_str())) { @@ -73,6 +68,9 @@ LevelTile::LevelTile(const std::string& basedir, const ReaderMapping& mapping) : << "' does not exist and will not be added to the worldmap" << std::endl; return; } + + if (in_worldmap()) + load_level_information(); } LevelTile::~LevelTile() @@ -80,24 +78,58 @@ LevelTile::~LevelTile() } void -LevelTile::draw(DrawingContext& context) +LevelTile::load_level_information() { - m_sprite->draw(context.color(), m_pos * 32.0f + Vector(16, 16), LAYER_OBJECTS - 1); -} + /** Set default properties. */ + m_title = _(""); + m_target_time = 0.0f; -void -LevelTile::update(float ) -{ + if (!WorldMap::current()) + return; + + try + { + // Determine the level filename. + const std::string& levels_path = WorldMap::current()->get_levels_path(); + std::string filename = levels_path + get_level_filename(); + + if (levels_path == "./") + filename = get_level_filename(); + + try + { + auto doc = ReaderDocument::from_file(filename); + auto root = doc.get_root(); + + if (root.get_name() != "supertux-level") + throw std::runtime_error("'" + filename + "': file is not a supertux-level file."); + + auto mapping = root.get_mapping(); + + mapping.get("name", m_title); + mapping.get("target-time", m_target_time); + } + catch (std::exception& err) + { + std::stringstream out; + out << "Cannot read level info: " << err.what() << std::endl; + throw std::runtime_error(out.str()); + } + } + catch (std::exception& err) + { + log_warning << "Problem when reading level information: " << err.what() << std::endl; + return; + } } void LevelTile::update_sprite_action() { - if (!m_solved) { + if (!m_solved) m_sprite->set_action("default"); - } else { + else m_sprite->set_action((m_sprite->has_action("perfect") && m_perfect) ? "perfect" : "solved"); - } } void @@ -114,6 +146,28 @@ LevelTile::set_perfect(bool v) update_sprite_action(); } +ObjectSettings +LevelTile::get_settings() +{ + // FIXME: hack to make the basedir absolute, making + // World::get_basedir() itself absolute would be correct, but + // invalidate savefiles. + std::string basedir = m_basedir; + if (!basedir.empty() && basedir.front() != '/') + basedir = "/" + basedir; + + ObjectSettings result = WorldMapObject::get_settings(); + + result.add_level(_("Level"), &m_level_filename, "level", basedir); + result.add_script(_("Outro script"), &m_extro_script, "extro-script"); + result.add_bool(_("Auto play"), &m_auto_play, "auto-play", false); + result.add_color(_("Title colour"), &m_title_color, "color", Color::WHITE); + + result.reorder({"name", "sprite", "x", "y"}); + + return result; +} + } // namespace worldmap /* EOF */ diff --git a/src/worldmap/level_tile.hpp b/src/worldmap/level_tile.hpp index a602aab71b1..ced8fc3da2c 100644 --- a/src/worldmap/level_tile.hpp +++ b/src/worldmap/level_tile.hpp @@ -1,6 +1,7 @@ // SuperTux // Copyright (C) 2004 Ingo Ruhnke // Copyright (C) 2006 Christoph Sommer +// 2023 Vankata453 // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -18,25 +19,24 @@ #ifndef HEADER_SUPERTUX_WORLDMAP_LEVEL_TILE_HPP #define HEADER_SUPERTUX_WORLDMAP_LEVEL_TILE_HPP -#include "math/vector.hpp" -#include "sprite/sprite_ptr.hpp" -#include "supertux/game_object.hpp" -#include "supertux/statistics.hpp" +#include "worldmap/worldmap_object.hpp" -class ReaderMapping; +#include "supertux/statistics.hpp" namespace worldmap { -class LevelTile final : public GameObject +class LevelTile final : public WorldMapObject { - friend class WorldMapParser; - public: - LevelTile(const std::string& basedir, const ReaderMapping& mapping); + LevelTile(const ReaderMapping& mapping); ~LevelTile() override; - virtual void draw(DrawingContext& context) override; - virtual void update(float dt_sec) override; + static std::string class_name() { return "level"; } + virtual std::string get_class_name() const override { return class_name(); } + static std::string display_name() { return _("Level"); } + virtual std::string get_display_name() const override { return display_name(); } + + virtual ObjectSettings get_settings() override; void set_solved(bool v); bool is_solved() const { return m_solved; } @@ -49,8 +49,6 @@ class LevelTile final : public GameObject void update_sprite_action(); - Vector get_pos() const { return m_pos; } - std::string get_title() const { return m_title; } std::string get_level_filename() const { return m_level_filename; } std::string get_basedir() const { return m_basedir; } @@ -60,8 +58,9 @@ class LevelTile final : public GameObject bool is_auto_play() const { return m_auto_play; } private: - Vector m_pos; + void load_level_information(); +private: std::string m_basedir; std::string m_level_filename; std::string m_title; @@ -80,7 +79,6 @@ class LevelTile final : public GameObject Statistics m_statistics; - SpritePtr m_sprite; Color m_title_color; private: diff --git a/src/worldmap/spawn_point.cpp b/src/worldmap/spawn_point.cpp index ef566934834..a00828b4e77 100644 --- a/src/worldmap/spawn_point.cpp +++ b/src/worldmap/spawn_point.cpp @@ -1,5 +1,6 @@ // SuperTux - Worldmap Spawnpoint // Copyright (C) 2006 Matthias Braun +// 2023 Vankata453 // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -33,9 +34,8 @@ SpawnPoint::SpawnPoint(const ReaderMapping& mapping) : mapping.get("y", m_pos.y); std::string auto_dir_str; - if (mapping.get("auto-dir", auto_dir_str)) { + if (mapping.get("auto-dir", auto_dir_str)) m_auto_dir = string_to_direction(auto_dir_str); - } if (m_name.empty()) { @@ -50,6 +50,41 @@ SpawnPoint::SpawnPoint(const ReaderMapping& mapping) : } } + +SpawnPointObject::SpawnPointObject(const ReaderMapping& mapping) : + WorldMapObject(mapping, "images/worldmap/common/tux.png"), + m_dir(Direction::NONE) +{ + mapping.get("name", m_name); + + std::string auto_dir_str; + if (mapping.get("auto-dir", auto_dir_str)) + m_dir = string_to_direction(auto_dir_str); +} + +SpawnPointObject::SpawnPointObject(const std::string& name, const Vector& pos) : + WorldMapObject(pos, "images/worldmap/common/tux.png"), + m_dir(Direction::NONE) +{ + m_name = name; +} + +ObjectSettings +SpawnPointObject::get_settings() +{ + ObjectSettings result = WorldMapObject::get_settings(); + + result.remove("sprite"); + + result.add_worldmap_direction(_("Direction"), &m_dir, Direction::NONE, "auto-dir"); + + result.reorder({"auto-dir", "name", "x", "y"}); + + result.add_test_from_here(); + + return result; +} + } // namespace worldmap /* EOF */ diff --git a/src/worldmap/spawn_point.hpp b/src/worldmap/spawn_point.hpp index d3ef96de188..846b0798dc3 100644 --- a/src/worldmap/spawn_point.hpp +++ b/src/worldmap/spawn_point.hpp @@ -1,5 +1,6 @@ // SuperTux - Worldmap Spawnpoint // Copyright (C) 2006 Matthias Braun +// 2023 Vankata453 // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -17,13 +18,13 @@ #ifndef HEADER_SUPERTUX_WORLDMAP_SPAWN_POINT_HPP #define HEADER_SUPERTUX_WORLDMAP_SPAWN_POINT_HPP +#include "worldmap/worldmap_object.hpp" + #include #include "math/vector.hpp" #include "worldmap/direction.hpp" -class ReaderMapping; - namespace worldmap { class SpawnPoint final @@ -45,6 +46,28 @@ class SpawnPoint final SpawnPoint& operator=(const SpawnPoint&) = delete; }; + +class SpawnPointObject final : public WorldMapObject +{ +public: + SpawnPointObject(const ReaderMapping& mapping); + SpawnPointObject(const std::string& name, const Vector& pos); + + static std::string class_name() { return "worldmap-spawnpoint"; } + virtual std::string get_class_name() const override { return class_name(); } + static std::string display_name() { return _("Spawn point"); } + virtual std::string get_display_name() const override { return display_name(); } + + virtual ObjectSettings get_settings() override; + +private: + Direction m_dir; + +private: + SpawnPointObject(const SpawnPointObject&) = delete; + SpawnPointObject& operator=(const SpawnPointObject&) = delete; +}; + } // namespace worldmap #endif diff --git a/src/worldmap/special_tile.cpp b/src/worldmap/special_tile.cpp index fa15f39c0cd..34fbbdb7608 100644 --- a/src/worldmap/special_tile.cpp +++ b/src/worldmap/special_tile.cpp @@ -1,6 +1,7 @@ // SuperTux // Copyright (C) 2004 Ingo Ruhnke // Copyright (C) 2006 Christoph Sommer +// 2023 Vankata453 // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -17,70 +18,38 @@ #include "worldmap/special_tile.hpp" -#include "sprite/sprite.hpp" -#include "sprite/sprite_manager.hpp" -#include "util/log.hpp" #include "util/reader_mapping.hpp" namespace worldmap { SpecialTile::SpecialTile(const ReaderMapping& mapping) : - m_pos(0.0f, 0.0f), - m_sprite(), + WorldMapObject(mapping, "images/worldmap/common/specialtile.png"), m_map_message(), m_passive_message(false), m_script(), m_invisible(false), + m_apply_direction(), m_apply_action_north(true), m_apply_action_east(true), m_apply_action_south(true), m_apply_action_west(true) { - if (!mapping.get("x", m_pos.x)) { - log_warning << "X coordinate of special tile not set, defaulting to 0" << std::endl; - } - if (!mapping.get("y", m_pos.y)) { - log_warning << "Y coordinate of special tile not set, defaulting to 0" << std::endl; - } - if (!mapping.get("invisible-tile", m_invisible)) { - // Ignore attribute if it's not specified. Tile is visible. - } + mapping.get("invisible-tile", m_invisible); - if (!m_invisible) { - std::string spritefile = ""; - if (!mapping.get("sprite", spritefile)) { - log_warning << "No sprite specified for visible special tile." << std::endl; - } - m_sprite = SpriteManager::current()->create(spritefile); - } + if (in_worldmap() && !has_found_sprite()) // In worldmap and no valid sprite is specified, be invisible + m_invisible = true; - if (!mapping.get("map-message", m_map_message)) { - // Ignore attribute if it's not specified. No map message set. - } - if (!mapping.get("passive-message", m_passive_message)) { - // Ignore attribute if it's not specified. No passive message set. - } - if (!mapping.get("script", m_script)) { - // Ignore attribute if it's not specified. No script set. - } + mapping.get("map-message", m_map_message); + mapping.get("passive-message", m_passive_message); + mapping.get("script", m_script); - std::string apply_direction; - if (!mapping.get("apply-to-direction", apply_direction)) { - // Ignore attribute if it's not specified. Applies to all directions. - } - if (!apply_direction.empty()) { - m_apply_action_north = false; - m_apply_action_south = false; - m_apply_action_east = false; - m_apply_action_west = false; - if (apply_direction.find("north") != std::string::npos) - m_apply_action_north = true; - if (apply_direction.find("south") != std::string::npos) - m_apply_action_south = true; - if (apply_direction.find("east") != std::string::npos) - m_apply_action_east = true; - if (apply_direction.find("west") != std::string::npos) - m_apply_action_west = true; + mapping.get("apply-to-direction", m_apply_direction); + if (!m_apply_direction.empty()) + { + m_apply_action_north = m_apply_direction.find("north") != std::string::npos; + m_apply_action_south = m_apply_direction.find("south") != std::string::npos; + m_apply_action_east = m_apply_direction.find("east") != std::string::npos; + m_apply_action_west = m_apply_direction.find("west") != std::string::npos; } } @@ -89,17 +58,28 @@ SpecialTile::~SpecialTile() } void -SpecialTile::draw(DrawingContext& context) +SpecialTile::draw_worldmap(DrawingContext& context) { if (m_invisible) return; - m_sprite->draw(context.color(), m_pos*32.0f + Vector(16, 16), LAYER_OBJECTS - 1); + WorldMapObject::draw_worldmap(context); } -void -SpecialTile::update(float ) +ObjectSettings +SpecialTile::get_settings() { + ObjectSettings result = WorldMapObject::get_settings(); + + result.add_translatable_text(_("Message"), &m_map_message, "map-message"); + result.add_bool(_("Show message"), &m_passive_message, "passive-message", false); + result.add_script(_("Script"), &m_script, "script"); + result.add_bool(_("Invisible"), &m_invisible, "invisible-tile", false); + result.add_text(_("Direction"), &m_apply_direction, "apply-to-direction", std::string("north-east-south-west")); + + result.reorder({"map-message", "invisible-tile", "script", "passive-message", "apply-to-direction", "sprite", "x", "y"}); + + return result; } } diff --git a/src/worldmap/special_tile.hpp b/src/worldmap/special_tile.hpp index 9e85e00835a..84beca08a13 100644 --- a/src/worldmap/special_tile.hpp +++ b/src/worldmap/special_tile.hpp @@ -1,6 +1,7 @@ // SuperTux // Copyright (C) 2004 Ingo Ruhnke // Copyright (C) 2006 Christoph Sommer +// 2023 Vankata453 // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -18,26 +19,27 @@ #ifndef HEADER_SUPERTUX_WORLDMAP_SPECIAL_TILE_HPP #define HEADER_SUPERTUX_WORLDMAP_SPECIAL_TILE_HPP -#include - -#include "math/vector.hpp" -#include "sprite/sprite_ptr.hpp" -#include "supertux/game_object.hpp" +#include "worldmap/worldmap_object.hpp" -class ReaderMapping; +#include namespace worldmap { -class SpecialTile final : public GameObject +class SpecialTile final : public WorldMapObject { public: SpecialTile(const ReaderMapping& mapping); ~SpecialTile() override; - virtual void draw(DrawingContext& context) override; - virtual void update(float dt_sec) override; + static std::string class_name() { return "special-tile"; } + virtual std::string get_class_name() const override { return class_name(); } + static std::string display_name() { return _("Special Tile"); } + virtual std::string get_display_name() const override { return display_name(); } + + virtual void draw_worldmap(DrawingContext& context) override; + + virtual ObjectSettings get_settings() override; - Vector get_pos() const { return m_pos; } std::string get_map_message() const { return m_map_message; } bool is_passive_message() const { return m_passive_message; } std::string get_script() const { return m_script; } @@ -48,11 +50,6 @@ class SpecialTile final : public GameObject bool get_apply_action_west() const { return m_apply_action_west; } private: - Vector m_pos; - - /** Sprite to render instead of guessing what image to draw */ - SpritePtr m_sprite; - /** Message to show in the Map */ std::string m_map_message; bool m_passive_message; @@ -64,6 +61,7 @@ class SpecialTile final : public GameObject bool m_invisible; /** Only applies actions (ie. passive messages) when going to that direction */ + std::string m_apply_direction; bool m_apply_action_north; bool m_apply_action_east; bool m_apply_action_south; diff --git a/src/worldmap/sprite_change.cpp b/src/worldmap/sprite_change.cpp index b122b1172a0..8b96c355916 100644 --- a/src/worldmap/sprite_change.cpp +++ b/src/worldmap/sprite_change.cpp @@ -1,5 +1,6 @@ // SuperTux // Copyright (C) 2006 Matthias Braun +// 2023 Vankata453 // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -16,58 +17,38 @@ #include "worldmap/sprite_change.hpp" -#include "sprite/sprite.hpp" -#include "sprite/sprite_manager.hpp" #include "util/reader_mapping.hpp" -#include "video/drawing_context.hpp" +#include "worldmap/worldmap_sector.hpp" namespace worldmap { -std::list SpriteChange::s_all_sprite_changes; - SpriteChange::SpriteChange(const ReaderMapping& mapping) : - m_pos(0.0f, 0.0f), + WorldMapObject(mapping, "images/engine/editor/spritechange.png"), m_change_on_touch(false), - m_sprite(), - m_sprite_name(), m_stay_action(), m_stay_group(), m_in_stay_action(false) { - mapping.get("x", m_pos.x); - mapping.get("y", m_pos.y); mapping.get("change-on-touch", m_change_on_touch); - - if (!mapping.get("sprite", m_sprite_name)) m_sprite_name = ""; - m_sprite = SpriteManager::current()->create(m_sprite_name); - mapping.get("stay-action", m_stay_action); mapping.get("initial-stay-action", m_in_stay_action); - mapping.get("stay-group", m_stay_group); - - s_all_sprite_changes.push_back(this); } SpriteChange::~SpriteChange() { - s_all_sprite_changes.remove(this); } void -SpriteChange::draw(DrawingContext& context) +SpriteChange::draw_worldmap(DrawingContext& context) { - if (m_in_stay_action && !m_stay_action.empty()) { + if (m_in_stay_action && !m_stay_action.empty()) + { m_sprite->set_action(m_stay_action); - m_sprite->draw(context.color(), m_pos * 32.0f, LAYER_OBJECTS-1); + WorldMapObject::draw_worldmap(context); } } -void -SpriteChange::update(float ) -{ -} - bool SpriteChange::show_stay_action() const { @@ -86,14 +67,37 @@ SpriteChange::clear_stay_action(bool propagate) m_in_stay_action = false; // if we are in a stay_group, also clear all stay actions in this group - if (!m_stay_group.empty() && propagate) { - for (const auto& sc : s_all_sprite_changes) { - if (sc->m_stay_group != m_stay_group) continue; - sc->m_in_stay_action = false; + if (!m_stay_group.empty() && propagate) + { + for (SpriteChange& sc : WorldMapSector::current()->get_objects_by_type()) + { + if (sc.m_stay_group != m_stay_group) continue; + sc.m_in_stay_action = false; } } } +SpritePtr +SpriteChange::clone_sprite() const +{ + return m_sprite->clone(); +} + +ObjectSettings +SpriteChange::get_settings() +{ + ObjectSettings result = WorldMapObject::get_settings(); + + result.add_text(_("Stay action"), &m_stay_action, "stay-action"); + result.add_bool(_("Initial stay action"), &m_in_stay_action, "initial-stay-action"); + result.add_text(_("Stay group"), &m_stay_group, "stay-group"); + result.add_bool(_("Change on touch"), &m_change_on_touch, "change-on-touch"); + + result.reorder({"change-on-touch", "initial-stay-action", "stay-group", "sprite", "x", "y"}); + + return result; +} + } // namespace worldmap /* EOF */ diff --git a/src/worldmap/sprite_change.hpp b/src/worldmap/sprite_change.hpp index ff919ab6e05..e889b681066 100644 --- a/src/worldmap/sprite_change.hpp +++ b/src/worldmap/sprite_change.hpp @@ -1,5 +1,6 @@ // SuperTux // Copyright (C) 2006 Matthias Braun +// 2023 Vankata453 // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -17,61 +18,53 @@ #ifndef HEADER_SUPERTUX_WORLDMAP_SPRITE_CHANGE_HPP #define HEADER_SUPERTUX_WORLDMAP_SPRITE_CHANGE_HPP -#include -#include - -#include "math/vector.hpp" -#include "sprite/sprite_ptr.hpp" -#include "supertux/game_object.hpp" +#include "worldmap/worldmap_object.hpp" -class ReaderMapping; -class Sprite; +#include namespace worldmap { -class SpriteChange final : public GameObject +class SpriteChange final : public WorldMapObject { -private: - static std::list s_all_sprite_changes; - public: SpriteChange(const ReaderMapping& mapping); ~SpriteChange() override; - virtual void draw(DrawingContext& context) override; - virtual void update(float dt_sec) override; + static std::string class_name() { return "sprite-change"; } + virtual std::string get_class_name() const override { return class_name(); } + static std::string display_name() { return _("Sprite Change"); } + virtual std::string get_display_name() const override { return display_name(); } + + virtual void draw_worldmap(DrawingContext& context) override; + + virtual ObjectSettings get_settings() override; /** * Activates the SpriteChange's stay action, if applicable */ void set_stay_action(); - /** * Deactivates the SpriteChange's stay action, if applicable * @param propagate : Also change stay actions in the same stay group */ void clear_stay_action(bool propagate = true); - - /* + /** * Get the current value of in_stay_action */ - bool show_stay_action() const; + bool show_stay_action() const; - Vector get_pos() const { return m_pos; } + /** + * Clone the current sprite. + */ + SpritePtr clone_sprite() const; -private: - Vector m_pos; + bool change_on_touch() const { return m_change_on_touch; } -public: - /** should tuxs sprite change when the tile has been completely entered, +private: + /** should Tux's sprite change when the tile has been completely entered, or already when the tile was just touched */ bool m_change_on_touch; - /** sprite to change tux image to */ - SpritePtr m_sprite; - std::string m_sprite_name; - -private: /** stay action can be used for objects like boats or cars, if it is not empty then this sprite will be displayed when tux left the tile towards another SpriteChange object. */ @@ -81,7 +74,6 @@ class SpriteChange final : public GameObject its stay_action displayed. Leave empty if you don't care. */ std::string m_stay_group; -private: /** should the stayaction be displayed */ bool m_in_stay_action; diff --git a/src/worldmap/teleporter.cpp b/src/worldmap/teleporter.cpp index 92a1420210d..204181683b1 100644 --- a/src/worldmap/teleporter.cpp +++ b/src/worldmap/teleporter.cpp @@ -1,5 +1,6 @@ // SuperTux - Teleporter Worldmap Tile // Copyright (C) 2006 Christoph Sommer +// 2023 Vankata453 // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -16,53 +17,42 @@ #include "worldmap/teleporter.hpp" -#include "sprite/sprite.hpp" -#include "sprite/sprite_manager.hpp" #include "util/reader_mapping.hpp" namespace worldmap { Teleporter::Teleporter(const ReaderMapping& mapping) : - m_pos(0.0f, 0.0f), - m_sprite(), + WorldMapObject(mapping, "images/worldmap/common/teleporterdot.sprite"), m_worldmap(), + m_sector(), m_spawnpoint(), m_automatic(false), m_message() { - mapping.get("x", m_pos.x); - mapping.get("y", m_pos.y); - - std::string spritefile = ""; - if (mapping.get("sprite", spritefile)) { - m_sprite = SpriteManager::current()->create(spritefile); - } - - if (!mapping.get("worldmap", m_worldmap)) { - // worldmap parameter doesn't need to be set. Ignore. - } - if (!mapping.get("spawnpoint", m_spawnpoint)) { - // not set, use "main" spawnpoint. - } - if (!mapping.get("automatic", m_automatic)) { - // doesn't need to be set. Don't teleport automatically. - } - if (!mapping.get("message", m_message)) { - // Optional message not set. Ignore! - } + if (in_worldmap() && !has_found_sprite()) // In worldmap and no valid sprite is specified, remove it + m_sprite.reset(); + + mapping.get("worldmap", m_worldmap); + mapping.get("sector", m_sector); + mapping.get("spawnpoint", m_spawnpoint); + mapping.get("automatic", m_automatic); + mapping.get("message", m_message); } -void -Teleporter::draw(DrawingContext& context) +ObjectSettings +Teleporter::get_settings() { - if (m_sprite) { - m_sprite->draw(context.color(), m_pos * 32.0f + Vector(16, 16), LAYER_OBJECTS - 1); - } -} + ObjectSettings result = WorldMapObject::get_settings(); -void -Teleporter::update(float ) -{ + result.add_text(_("Sector"), &m_sector, "sector"); + result.add_text(_("Spawnpoint"), &m_spawnpoint, "spawnpoint"); + result.add_translatable_text(_("Message"), &m_message, "message"); + result.add_bool(_("Automatic"), &m_automatic, "automatic", false); + result.add_worldmap(_("Target worldmap"), &m_worldmap, "worldmap"); + + result.reorder({"sector", "spawnpoint", "automatic", "message", "sprite", "x", "y"}); + + return result; } } // namespace worldmap diff --git a/src/worldmap/teleporter.hpp b/src/worldmap/teleporter.hpp index 8a1f31914a8..b35b43e6aa4 100644 --- a/src/worldmap/teleporter.hpp +++ b/src/worldmap/teleporter.hpp @@ -1,5 +1,6 @@ // SuperTux - Teleporter Worldmap Tile // Copyright (C) 2006 Christoph Sommer +// 2023 Vankata453 // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -17,40 +18,37 @@ #ifndef HEADER_SUPERTUX_WORLDMAP_TELEPORTER_HPP #define HEADER_SUPERTUX_WORLDMAP_TELEPORTER_HPP -#include - -#include "math/vector.hpp" -#include "sprite/sprite_ptr.hpp" -#include "supertux/game_object.hpp" +#include "worldmap/worldmap_object.hpp" -class ReaderMapping; +#include namespace worldmap { -class Teleporter final : public GameObject +class Teleporter final : public WorldMapObject { public: Teleporter(const ReaderMapping& mapping); - virtual void draw(DrawingContext& context) override; - virtual void update(float dt_sec) override; + static std::string class_name() { return "teleporter"; } + virtual std::string get_class_name() const override { return class_name(); } + static std::string display_name() { return _("Teleporter"); } + virtual std::string get_display_name() const override { return display_name(); } + + virtual ObjectSettings get_settings() override; - Vector get_pos() const { return m_pos; } - std::string get_worldmap() const { return m_worldmap; } - std::string get_spawnpoint() const { return m_spawnpoint; } + const std::string& get_worldmap() const { return m_worldmap; } + const std::string& get_sector() const { return m_sector; } + const std::string& get_spawnpoint() const { return m_spawnpoint; } bool is_automatic() const { return m_automatic; } - std::string get_message() const { return m_message; } + const std::string& get_message() const { return m_message; } private: - /** Position (in tiles, not pixels) */ - Vector m_pos; - - /** Sprite to render, or 0 for no sprite */ - SpritePtr m_sprite; - /** Worldmap filename (relative to data root) to teleport to. Leave empty to use current word */ std::string m_worldmap; + /** Sector to teleport to. Leave empty to stay at the current sector **/ + std::string m_sector; + /** Spawnpoint to teleport to. Leave empty to use "main" or last one */ std::string m_spawnpoint; diff --git a/src/worldmap/tux.cpp b/src/worldmap/tux.cpp index bf6ec8b524d..383c5bd3297 100644 --- a/src/worldmap/tux.cpp +++ b/src/worldmap/tux.cpp @@ -27,9 +27,12 @@ #include "supertux/tile.hpp" #include "util/log.hpp" #include "worldmap/camera.hpp" +#include "worldmap/direction.hpp" #include "worldmap/level_tile.hpp" #include "worldmap/special_tile.hpp" #include "worldmap/sprite_change.hpp" +#include "worldmap/teleporter.hpp" +#include "worldmap/worldmap.hpp" namespace worldmap { @@ -43,7 +46,8 @@ Tux::Tux(WorldMap* worldmap) : m_controller(InputManager::current()->get_controller()), m_input_direction(Direction::NONE), m_direction(Direction::NONE), - m_tile_pos(0.0f, 0.0f), + m_initial_tile_pos(), + m_tile_pos(), m_offset(0), m_moving(false), m_ghost_mode(false) @@ -53,7 +57,7 @@ Tux::Tux(WorldMap* worldmap) : void Tux::draw(DrawingContext& context) { - if (m_worldmap->get_camera().is_panning()) return; + if (m_worldmap->get_sector().get_camera().is_panning()) return; std::string action = get_action_prefix_for_bonus(m_worldmap->get_savegame().get_player_status().bonus[0]); if (!action.empty()) @@ -85,7 +89,7 @@ Tux::draw(DrawingContext& context) log_debug << "Bonus type not handled in worldmap." << std::endl; m_sprite->set_action("large-stop"); } - m_sprite->draw(context.color(), get_pos(), LAYER_OBJECTS); + m_sprite->draw(context.color(), get_pos(), LAYER_OBJECTS + 1); } std::string @@ -196,13 +200,13 @@ Tux::try_start_walking() if (m_input_direction == Direction::NONE) return; - auto level = m_worldmap->at_level(); + auto level = m_worldmap->get_sector().at_object(); // We got a new direction, so lets start walking when possible Vector next_tile(0.0f, 0.0f); if ((!level || level->is_solved() || level->is_perfect() || (Editor::current() && Editor::current()->is_testing_level())) - && m_worldmap->path_ok(m_input_direction, m_tile_pos, &next_tile)) { + && m_worldmap->get_sector().path_ok(m_input_direction, m_tile_pos, &next_tile)) { m_tile_pos = next_tile; m_moving = true; m_direction = m_input_direction; @@ -210,7 +214,7 @@ Tux::try_start_walking() } else if (m_ghost_mode || (m_input_direction == m_back_direction)) { m_moving = true; m_direction = m_input_direction; - m_tile_pos = m_worldmap->get_next_tile(m_tile_pos, m_direction); + m_tile_pos = m_worldmap->get_sector().get_next_tile(m_tile_pos, m_direction); m_back_direction = reverse_dir(m_direction); } } @@ -228,11 +232,11 @@ Tux::can_walk(int tile_data, Direction dir) const void Tux::change_sprite(SpriteChange* sprite_change) { - //SpriteChange* sprite_change = m_worldmap->at_sprite_change(tile_pos); + //SpriteChange* sprite_change = m_worldmap->at_object(); if (sprite_change != nullptr) { - m_sprite = sprite_change->m_sprite->clone(); + m_sprite = sprite_change->clone_sprite(); sprite_change->clear_stay_action(); - m_worldmap->get_savegame().get_player_status().worldmap_sprite = sprite_change->m_sprite_name; + m_worldmap->get_savegame().get_player_status().worldmap_sprite = sprite_change->get_sprite_name(); } } @@ -251,11 +255,13 @@ Tux::try_continue_walking(float dt_sec) m_offset -= 32; - auto sprite_change = m_worldmap->at_sprite_change(m_tile_pos); + auto worldmap_sector = &m_worldmap->get_sector(); + + auto sprite_change = worldmap_sector->at_object(m_tile_pos); change_sprite(sprite_change); // if this is a special_tile with passive_message, display it - auto special_tile = m_worldmap->at_special_tile(); + auto special_tile = worldmap_sector->at_object(); if (special_tile) { // direction and the apply_action_ are opposites, since they "see" @@ -270,11 +276,11 @@ Tux::try_continue_walking(float dt_sec) } // check if we are at a Teleporter - auto teleporter = m_worldmap->at_teleporter(m_tile_pos); + auto teleporter = worldmap_sector->at_object(m_tile_pos); // stop if we reached a level, a WORLDMAP_STOP tile, a teleporter or a special tile without a passive_message - if ((m_worldmap->at_level()) || - (m_worldmap->tile_data_at(m_tile_pos) & Tile::WORLDMAP_STOP) || + if ((worldmap_sector->at_object()) || + (worldmap_sector->tile_data_at(m_tile_pos) & Tile::WORLDMAP_STOP) || (special_tile && !special_tile->is_passive_message() && special_tile->get_script().empty()) || (teleporter) || m_ghost_mode) @@ -287,7 +293,7 @@ Tux::try_continue_walking(float dt_sec) } // if user wants to change direction, try changing, else guess the direction in which to walk next - const int tile_data = m_worldmap->tile_data_at(m_tile_pos); + const int tile_data = worldmap_sector->tile_data_at(m_tile_pos); if ((m_direction != m_input_direction) && can_walk(tile_data, m_input_direction)) { m_direction = m_input_direction; m_back_direction = reverse_dir(m_direction); @@ -319,17 +325,17 @@ Tux::try_continue_walking(float dt_sec) return; Vector next_tile(0.0f, 0.0f); - if (!m_ghost_mode && !m_worldmap->path_ok(m_direction, m_tile_pos, &next_tile)) { + if (!m_ghost_mode && !worldmap_sector->path_ok(m_direction, m_tile_pos, &next_tile)) { log_debug << "Tilemap data is buggy" << std::endl; stop(); return; } - auto next_sprite = m_worldmap->at_sprite_change(next_tile); - if (next_sprite != nullptr && next_sprite->m_change_on_touch) { + auto next_sprite = worldmap_sector->at_object(next_tile); + if (next_sprite != nullptr && next_sprite->change_on_touch()) { change_sprite(next_sprite); } - //SpriteChange* last_sprite = m_worldmap->at_sprite_change(tile_pos); + //SpriteChange* last_sprite = m_worldmap->at_object(next_tile); if (sprite_change != nullptr && next_sprite != nullptr) { log_debug << "Old: " << m_tile_pos << " New: " << next_tile << std::endl; sprite_change->set_stay_action(); @@ -354,7 +360,7 @@ Tux::update_input_direction() void Tux::update(float dt_sec) { - if (m_worldmap->get_camera().is_panning()) return; + if (m_worldmap->get_sector().get_camera().is_panning()) return; update_input_direction(); if (m_moving) @@ -366,8 +372,12 @@ Tux::update(float dt_sec) void Tux::setup() { + // Set initial tile position, if provided + if (m_initial_tile_pos != Vector()) + m_tile_pos = m_initial_tile_pos; + // check if we already touch a SpriteChange object - auto sprite_change = m_worldmap->at_sprite_change(m_tile_pos); + auto sprite_change = m_worldmap->get_sector().at_object(m_tile_pos); change_sprite(sprite_change); } @@ -378,12 +388,17 @@ Tux::process_special_tile(SpecialTile* special_tile) return; } - if (special_tile->is_passive_message()) { + if (special_tile->is_passive_message()) m_worldmap->set_passive_message(special_tile->get_map_message(), map_message_TIME); - } else if (!special_tile->get_script().empty()) { - try { - m_worldmap->run_script(special_tile->get_script(), "specialtile"); - } catch(std::exception& e) { + + if (!special_tile->get_script().empty()) + { + try + { + m_worldmap->get_sector().run_script(special_tile->get_script(), "specialtile"); + } + catch(std::exception& e) + { log_warning << "Couldn't execute special tile script: " << e.what() << std::endl; } diff --git a/src/worldmap/tux.hpp b/src/worldmap/tux.hpp index d40c292993a..60031875974 100644 --- a/src/worldmap/tux.hpp +++ b/src/worldmap/tux.hpp @@ -18,10 +18,10 @@ #ifndef HEADER_SUPERTUX_WORLDMAP_TUX_HPP #define HEADER_SUPERTUX_WORLDMAP_TUX_HPP -#include "sprite/sprite_ptr.hpp" #include "supertux/game_object.hpp" + +#include "sprite/sprite_ptr.hpp" #include "supertux/player_status.hpp" -#include "worldmap/worldmap.hpp" class Controller; @@ -51,7 +51,8 @@ class Tux final : public GameObject Vector get_pos() const; Vector get_axis() const; Vector get_tile_pos() const { return m_tile_pos; } - void set_tile_pos(const Vector& p) { m_tile_pos = p; } + void set_initial_pos(const Vector& pos) { m_initial_tile_pos = pos / 32.f; } + void set_tile_pos(const Vector& pos) { m_tile_pos = pos; } void process_special_tile(SpecialTile* special_tile); @@ -75,6 +76,7 @@ class Tux final : public GameObject Direction m_input_direction; Direction m_direction; + Vector m_initial_tile_pos; Vector m_tile_pos; /** Length by which tux is away from its current tile, length is in input_direction direction */ diff --git a/src/worldmap/world_select.cpp b/src/worldmap/world_select.cpp index d489ff6df45..fa9b2492787 100644 --- a/src/worldmap/world_select.cpp +++ b/src/worldmap/world_select.cpp @@ -29,7 +29,6 @@ #include "video/compositor.hpp" #include "video/drawing_context.hpp" #include "video/surface.hpp" -#include "worldmap/worldmap_parser.hpp" #include "worldmap/worldmap.hpp" namespace worldmap { @@ -248,8 +247,7 @@ WorldSelect::update(float dt_sec, const Controller& controller) if (controller.pressed(Control::JUMP) && m_worlds[m_selected_world].unlocked) { m_enabled = false; ScreenManager::current()->pop_screen(std::make_unique(FadeToBlack::Direction::FADEOUT, 0.25f)); - worldmap::WorldMap::current()->change(m_worlds[m_selected_world].filename, "main"); - log_warning << m_worlds[m_selected_world].filename << std::endl; + worldmap::WorldMap::current()->change(m_worlds[m_selected_world].filename, "", "main"); return; } } diff --git a/src/worldmap/worldmap.cpp b/src/worldmap/worldmap.cpp index 213ee71f2c1..d8dd88aa761 100644 --- a/src/worldmap/worldmap.cpp +++ b/src/worldmap/worldmap.cpp @@ -1,6 +1,7 @@ // SuperTux - A Jump'n Run // Copyright (C) 2004 Ingo Ruhnke // Copyright (C) 2006 Christoph Sommer +// 2023 Vankata453 // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -17,291 +18,136 @@ #include "worldmap/worldmap.hpp" -#include - #include "audio/sound_manager.hpp" -#include "control/input_manager.hpp" #include "gui/menu_manager.hpp" -#include "object/ambient_light.hpp" -#include "object/decal.hpp" -#include "object/display_effect.hpp" -#include "object/music_object.hpp" -#include "object/tilemap.hpp" -#include "physfs/ifile_stream.hpp" -#include "physfs/physfs_file_system.hpp" -#include "scripting/worldmap.hpp" -#include "sprite/sprite.hpp" -#include "squirrel/squirrel_environment.hpp" -#include "supertux/d_scope.hpp" -#include "supertux/debug.hpp" +#include "physfs/util.hpp" #include "supertux/fadetoblack.hpp" #include "supertux/game_manager.hpp" -#include "supertux/game_session.hpp" #include "supertux/gameconfig.hpp" -#include "supertux/level.hpp" #include "supertux/menu/menu_storage.hpp" -#include "supertux/player_status_hud.hpp" -#include "supertux/resources.hpp" -#include "supertux/savegame.hpp" +#include "supertux/player_status.hpp" #include "supertux/screen_manager.hpp" -#include "supertux/shrinkfade.hpp" -#include "supertux/tile.hpp" #include "supertux/tile_manager.hpp" #include "util/file_system.hpp" +#include "util/log.hpp" #include "util/reader.hpp" #include "util/reader_document.hpp" #include "util/reader_mapping.hpp" -#include "video/compositor.hpp" -#include "video/video_system.hpp" -#include "video/video_system.hpp" -#include "video/viewport.hpp" -#include "worldmap/camera.hpp" +#include "video/drawing_context.hpp" +#include "worldmap/direction.hpp" #include "worldmap/level_tile.hpp" -#include "worldmap/spawn_point.hpp" -#include "worldmap/special_tile.hpp" -#include "worldmap/sprite_change.hpp" -#include "worldmap/teleporter.hpp" #include "worldmap/tux.hpp" #include "worldmap/world_select.hpp" -#include "worldmap/worldmap_parser.hpp" #include "worldmap/worldmap_screen.hpp" +#include "worldmap/worldmap_sector.hpp" +#include "worldmap/worldmap_sector_parser.hpp" #include "worldmap/worldmap_state.hpp" namespace worldmap { -WorldMap::WorldMap(const std::string& filename, Savegame& savegame, const std::string& force_spawnpoint_) : - m_squirrel_environment(new SquirrelEnvironment(SquirrelVirtualMachine::current()->get_vm(), "worldmap")), - m_camera(new Camera), - m_enter_level(false), - m_tux(), +WorldMap::WorldMap(const std::string& filename, Savegame& savegame, + const std::string& force_sector, const std::string& force_spawnpoint) : + m_sector(), + m_sectors(), + m_force_spawnpoint(force_spawnpoint), m_savegame(savegame), - m_tileset(nullptr), - m_name(""), - m_init_script(), - m_passive_message_timer(), + m_tileset(), + m_name(), + m_map_filename(physfsutil::realpath(filename)), + m_levels_path(FileSystem::dirname(m_map_filename)), + m_next_worldmap(), m_passive_message(), - m_map_filename(), - m_levels_path(), - m_spawn_points(), - m_force_spawnpoint(force_spawnpoint_), - m_main_is_default(true), - m_initial_fade_tilemap(), - m_fade_direction(), + m_passive_message_timer(), + m_enter_level(false), m_in_level(false), m_in_world_select(false) { - m_tux = &add(this); - add(m_savegame.get_player_status()); - SoundManager::current()->preload("sounds/warp.wav"); - BIND_WORLDMAP(*this); + /** Parse worldmap */ + register_translation_directory(m_map_filename); + auto doc = ReaderDocument::from_file(m_map_filename); + auto root = doc.get_root(); - // load worldmap objects - WorldMapParser parser(*this); - parser.load_worldmap(filename); -} + if (root.get_name() != "supertux-level") + throw std::runtime_error("file isn't a supertux-level file."); -WorldMap::~WorldMap() -{ - clear_objects(); - m_spawn_points.clear(); -} + auto mapping = root.get_mapping(); -void -WorldMap::finish_construction() -{ - if (!get_object_by_type()) { - add(Color::WHITE); - } + mapping.get("name", m_name); - if (!get_object_by_type()) { - add(); - } + std::string tileset_name; + if (mapping.get("tileset", tileset_name)) + m_tileset = TileManager::current()->get_tileset(tileset_name); + else + m_tileset = TileManager::current()->get_tileset("images/ice_world.strf"); - if (!get_object_by_type()) { - add("Effect"); + auto iter = mapping.get_iter(); + while (iter.next()) + { + if (iter.get_key() == "sector") + add_sector(WorldMapSectorParser::from_reader(*this, iter.as_mapping())); } - flush_game_objects(); + /** Force the initial sector, if provided */ + if (!force_sector.empty()) + set_sector(force_sector, "", false); } -bool -WorldMap::before_object_add(GameObject& object) -{ - m_squirrel_environment->try_expose(object); - return true; -} void -WorldMap::before_object_remove(GameObject& object) +WorldMap::setup() { - m_squirrel_environment->try_unexpose(object); -} + MenuManager::instance().clear_menu_stack(); -void -WorldMap::move_to_spawnpoint(const std::string& spawnpoint, bool pan, bool main_as_default) -{ - auto sp = get_spawnpoint_by_name(spawnpoint); - if (sp != nullptr) { - Vector p = sp->get_pos(); - m_tux->set_tile_pos(p); - m_tux->set_direction(sp->get_auto_dir()); - if (pan) { - m_camera->pan(); - } - return; - } + load_state(); + m_sector->setup(); - log_warning << "Spawnpoint '" << spawnpoint << "' not found." << std::endl; - if (spawnpoint != "main" && main_as_default) { - move_to_spawnpoint("main"); + /** Force the initial spawnpoint, if provided */ + if (!m_force_spawnpoint.empty()) + { + m_sector->move_to_spawnpoint(m_force_spawnpoint); + m_force_spawnpoint.clear(); } -} -void -WorldMap::change(const std::string& filename, const std::string& force_spawnpoint_) -{ - m_savegame.get_player_status().last_worldmap = filename; - ScreenManager::current()->pop_screen(); - ScreenManager::current()->push_screen(std::make_unique( - std::make_unique(filename, m_savegame, force_spawnpoint_))); + m_in_world_select = false; } void -WorldMap::on_escape_press() +WorldMap::leave() { - // Show or hide the menu - if (!MenuManager::instance().is_active()) - { - MenuManager::instance().set_menu(MenuStorage::WORLDMAP_MENU); - m_tux->set_direction(Direction::NONE); // stop tux movement when menu is called - } -} + save_state(); + m_sector->leave(); -Vector -WorldMap::get_next_tile(const Vector& pos, const Direction& direction) const -{ - auto position = pos; - switch (direction) { - case Direction::WEST: - position.x -= 1; - break; - case Direction::EAST: - position.x += 1; - break; - case Direction::NORTH: - position.y -= 1; - break; - case Direction::SOUTH: - position.y += 1; - break; - case Direction::NONE: - break; - } - return position; + GameManager::current()->load_next_worldmap(); } -bool -WorldMap::path_ok(const Direction& direction, const Vector& old_pos, Vector* new_pos) const + +void +WorldMap::draw(DrawingContext& context) { - *new_pos = get_next_tile(old_pos, direction); + m_sector->draw(context); - if (!(new_pos->x >= 0 && new_pos->x < get_tiles_width() - && new_pos->y >= 0 && new_pos->y < get_tiles_height())) - { // New position is outsite the tilemap - return false; - } - else - { // Check if the tile allows us to go to new_pos - int old_tile_data = tile_data_at(old_pos); - int new_tile_data = tile_data_at(*new_pos); - switch (direction) - { - case Direction::WEST: - return (old_tile_data & Tile::WORLDMAP_WEST - && new_tile_data & Tile::WORLDMAP_EAST); - - case Direction::EAST: - return (old_tile_data & Tile::WORLDMAP_EAST - && new_tile_data & Tile::WORLDMAP_WEST); - - case Direction::NORTH: - return (old_tile_data & Tile::WORLDMAP_NORTH - && new_tile_data & Tile::WORLDMAP_SOUTH); - - case Direction::SOUTH: - return (old_tile_data & Tile::WORLDMAP_SOUTH - && new_tile_data & Tile::WORLDMAP_NORTH); - - case Direction::NONE: - log_warning << "path_ok() can't walk if direction is NONE" << std::endl; - assert(false); - } - return false; - } + context.pop_transform(); } void -WorldMap::finished_level(Level* gamelevel) +WorldMap::update(float dt_sec) { - // TODO use Level* parameter here? - auto level = at_level(); - - if (level == nullptr) { - return; - } - - bool old_level_state = level->is_solved(); - level->set_solved(true); - - // deal with statistics - level->get_statistics().update(gamelevel->m_stats); - - if (level->get_statistics().completed(level->get_statistics(), level->get_target_time())) { - level->set_perfect(true); - } + if (m_in_world_select) return; - save_state(); + if (m_in_level) return; + if (MenuManager::instance().is_active()) return; - if (old_level_state != level->is_solved()) { - // Try to detect the next direction to which we should walk - // FIXME: Mostly a hack - Direction dir = Direction::NONE; - - int dirdata = available_directions_at(m_tux->get_tile_pos()); - // first, test for crossroads - if (dirdata == Tile::WORLDMAP_CNSE || - dirdata == Tile::WORLDMAP_CNSW || - dirdata == Tile::WORLDMAP_CNEW || - dirdata == Tile::WORLDMAP_CSEW || - dirdata == Tile::WORLDMAP_CNSEW) - dir = Direction::NONE; - else if (dirdata & Tile::WORLDMAP_NORTH - && m_tux->m_back_direction != Direction::NORTH) - dir = Direction::NORTH; - else if (dirdata & Tile::WORLDMAP_SOUTH - && m_tux->m_back_direction != Direction::SOUTH) - dir = Direction::SOUTH; - else if (dirdata & Tile::WORLDMAP_EAST - && m_tux->m_back_direction != Direction::EAST) - dir = Direction::EAST; - else if (dirdata & Tile::WORLDMAP_WEST - && m_tux->m_back_direction != Direction::WEST) - dir = Direction::WEST; - - if (dir != Direction::NONE) { - m_tux->set_direction(dir); - } + if (m_next_worldmap) // A worldmap is scheduled to be changed to. + { + m_savegame.get_player_status().last_worldmap = m_next_worldmap->m_map_filename; + ScreenManager::current()->pop_screen(); + ScreenManager::current()->push_screen(std::make_unique(std::move(m_next_worldmap))); + return; } - if (!level->get_extro_script().empty()) { - try { - run_script(level->get_extro_script(), "worldmap:extro_script"); - } catch(std::exception& e) { - log_warning << "Couldn't run level-extro-script: " << e.what() << std::endl; - } - } + m_sector->update(dt_sec); } void @@ -345,392 +191,145 @@ WorldMap::process_input(const Controller& controller) } } + void -WorldMap::update(float dt_sec) +WorldMap::on_escape_press() { - if (m_in_world_select) return; - - BIND_WORLDMAP(*this); - - if (m_in_level) return; - if (MenuManager::instance().is_active()) return; - - GameObjectManager::update(dt_sec); - - m_camera->update(dt_sec); - + // Show or hide the menu + if (!MenuManager::instance().is_active()) { - // check for teleporters - auto teleporter = at_teleporter(m_tux->get_tile_pos()); - if (teleporter && (teleporter->is_automatic() || (m_enter_level && (!m_tux->is_moving())))) { - m_enter_level = false; - if (!teleporter->get_worldmap().empty()) { - change(teleporter->get_worldmap(), teleporter->get_spawnpoint()); - } else { - // TODO: an animation, camera scrolling or a fading would be a nice touch - SoundManager::current()->play("sounds/warp.wav"); - m_tux->m_back_direction = Direction::NONE; - move_to_spawnpoint(teleporter->get_spawnpoint(), true); - } - } + MenuManager::instance().set_menu(MenuStorage::WORLDMAP_MENU); + m_sector->get_tux().set_direction(Direction::NONE); // stop tux movement when menu is called } +} - { - // check for auto-play levels - auto level = at_level(); - if (level && level->is_auto_play() && !level->is_solved() && !m_tux->is_moving()) { - m_enter_level = true; - // automatically mark these levels as solved in case player aborts - level->set_solved(true); - } - } +size_t +WorldMap::level_count() const +{ + size_t count = 0; + for (auto& sector : m_sectors) { - if (m_enter_level && !m_tux->is_moving()) - { - /* Check level action */ - auto level_ = at_level(); - if (!level_) { - //Respawn if player on a tile with no level and nowhere to go. - int tile_data = tile_data_at(m_tux->get_tile_pos()); - if (!( tile_data & ( Tile::WORLDMAP_NORTH | Tile::WORLDMAP_SOUTH | Tile::WORLDMAP_WEST | Tile::WORLDMAP_EAST ))){ - log_warning << "Player at illegal position " << m_tux->get_tile_pos().x << ", " << m_tux->get_tile_pos().y << " respawning." << std::endl; - move_to_spawnpoint("main"); - return; - } - log_warning << "No level to enter at: " << m_tux->get_tile_pos().x << ", " << m_tux->get_tile_pos().y << std::endl; - return; - } - - if (level_->get_pos() == m_tux->get_tile_pos()) { - try { - Vector shrinkpos = Vector(level_->get_pos().x * 32 + 16 - m_camera->get_offset().x, - level_->get_pos().y * 32 + 8 - m_camera->get_offset().y); - std::string levelfile = m_levels_path + level_->get_level_filename(); - - // update state and savegame - save_state(); - ScreenManager::current()->push_screen(std::make_unique(levelfile, m_savegame, &level_->get_statistics()), - std::make_unique(shrinkpos, 1.0f)); - - m_in_level = true; - } catch(std::exception& e) { - log_fatal << "Couldn't load level: " << e.what() << std::endl; - } - } - } - else - { - // tux->set_direction(input_direction); - } + count += sector->level_count(); } + return count; } -int -WorldMap::tile_data_at(const Vector& p) const +size_t +WorldMap::solved_level_count() const { - int dirs = 0; - - for (const auto& tilemap : get_solid_tilemaps()) { - const Tile& tile = tilemap->get_tile(static_cast(p.x), static_cast(p.y)); - int dirdata = tile.get_data(); - dirs |= dirdata; + size_t count = 0; + for (auto& sector : m_sectors) + { + count += sector->solved_level_count(); } - - return dirs; + return count; } -int -WorldMap::available_directions_at(const Vector& p) const -{ - return tile_data_at(p) & Tile::WORLDMAP_DIR_MASK; -} -LevelTile* -WorldMap::at_level() const +void +WorldMap::load_state() { - for (auto& level : get_objects_by_type()) { - if (level.get_pos() == m_tux->get_tile_pos()) - return &level; - } - - return nullptr; + WorldMapState state(*this); + state.load_state(); } -SpecialTile* -WorldMap::at_special_tile() const +void +WorldMap::save_state() { - for (auto& special_tile : get_objects_by_type()) { - if (special_tile.get_pos() == m_tux->get_tile_pos()) - return &special_tile; - } - - return nullptr; + WorldMapState state(*this); + state.save_state(); } -SpriteChange* -WorldMap::at_sprite_change(const Vector& pos) const -{ - for (auto& sprite_change : get_objects_by_type()) { - if (sprite_change.get_pos() == pos) - return &sprite_change; - } - return nullptr; -} - -Teleporter* -WorldMap::at_teleporter(const Vector& pos) const +void +WorldMap::change(const std::string& filename, const std::string& force_sector, + const std::string& force_spawnpoint) { - for (auto& teleporter : get_objects_by_type()) { - if (teleporter.get_pos() == pos) - return &teleporter; - } - - return nullptr; + // Schedule worldmap to be changed to next frame. + m_next_worldmap = std::make_unique(filename, m_savegame, force_sector, force_spawnpoint); } + void -WorldMap::draw(DrawingContext& context) +WorldMap::set_levels_solved(bool solved, bool perfect) { - BIND_WORLDMAP(*this); - - if (get_width() < static_cast(context.get_width()) || - get_height() < static_cast(context.get_height())) - { - context.color().draw_filled_rect(context.get_rect(), - Color(0.0f, 0.0f, 0.0f, 1.0f), LAYER_BACKGROUND0); - } - - context.push_transform(); - context.set_translation(m_camera->get_offset()); - - GameObjectManager::draw(context); - - if (g_debug.show_worldmap_path) + for (auto& level : m_sector->get_objects_by_type()) { - for (int x = 0; x < static_cast(get_tiles_width()); x++) { - for (int y = 0; y < static_cast(get_tiles_height()); y++) { - const int data = tile_data_at(Vector(static_cast(x), static_cast(y))); - const int px = x * 32; - const int py = y * 32; - const int W = 4; - const int layer = LAYER_FOREGROUND1 + 1000; - const Color color(1.0f, 0.0f, 1.0f, 0.5f); - if (data & Tile::WORLDMAP_NORTH) context.color().draw_filled_rect(Rect(px + 16-W, py , px + 16+W, py + 16-W), color, layer); - if (data & Tile::WORLDMAP_SOUTH) context.color().draw_filled_rect(Rect(px + 16-W, py + 16+W, px + 16+W, py + 32 ), color, layer); - if (data & Tile::WORLDMAP_EAST) context.color().draw_filled_rect(Rect(px + 16+W, py + 16-W, px + 32 , py + 16+W), color, layer); - if (data & Tile::WORLDMAP_WEST) context.color().draw_filled_rect(Rect(px , py + 16-W, px + 16-W, py + 16+W), color, layer); - if (data & Tile::WORLDMAP_DIR_MASK) context.color().draw_filled_rect(Rect(px + 16-W, py + 16-W, px + 16+W, py + 16+W), color, layer); - if (data & Tile::WORLDMAP_STOP) context.color().draw_filled_rect(Rect(px + 4 , py + 4 , px + 28 , py + 28 ), color, layer); - } - } + level.set_solved(solved); + level.set_perfect(perfect); } - - draw_status(context); - context.pop_transform(); } void -WorldMap::draw_status(DrawingContext& context) +WorldMap::set_passive_message(const std::string& message, float time) { - context.push_transform(); - context.set_translation(Vector(0, 0)); - - if (!m_tux->is_moving()) { - for (auto& level : get_objects_by_type()) { - if (level.get_pos() == m_tux->get_tile_pos()) { - context.color().draw_text(Resources::normal_font, level.get_title(), - Vector(static_cast(context.get_width()) / 2.0f, - static_cast(context.get_height()) - Resources::normal_font->get_height() - 10), - ALIGN_CENTER, LAYER_HUD, level.get_title_color()); - - if (g_config->developer_mode) { - context.color().draw_text(Resources::small_font, FileSystem::join(level.get_basedir(), level.get_level_filename()), - Vector(static_cast(context.get_width()) / 2.0f, - static_cast(context.get_height()) - Resources::normal_font->get_height() - 25), - ALIGN_CENTER, LAYER_HUD, level.get_title_color()); - } - - // if level is solved, draw level picture behind stats - /* - if (level.solved) { - if (const Surface* picture = level.get_picture()) { - Vector pos = Vector(context.get_width() - picture->get_width(), context.get_height() - picture->get_height()); - context.push_transform(); - context.set_alpha(0.5); - context.color().draw_surface(picture, pos, LAYER_FOREGROUND1-1); - context.pop_transform(); - } - } - */ - level.get_statistics().draw_worldmap_info(context, level.get_target_time()); - break; - } - } - - for (auto& special_tile : get_objects_by_type()) { - if (special_tile.get_pos() == m_tux->get_tile_pos()) { - /* Display an in-map message in the map, if any as been selected */ - if (!special_tile.get_map_message().empty() && !special_tile.is_passive_message()) - context.color().draw_text(Resources::normal_font, special_tile.get_map_message(), - Vector(static_cast(context.get_width()) / 2.0f, - static_cast(context.get_height()) - static_cast(Resources::normal_font->get_height()) - 60.0f), - ALIGN_CENTER, LAYER_FOREGROUND1, WorldMap::message_color); - break; - } - } - - // display teleporter messages - auto teleporter = at_teleporter(m_tux->get_tile_pos()); - if (teleporter && (!teleporter->get_message().empty())) { - Vector pos = Vector(static_cast(context.get_width()) / 2.0f, - static_cast(context.get_height()) - Resources::normal_font->get_height() - 30.0f); - context.color().draw_text(Resources::normal_font, teleporter->get_message(), pos, ALIGN_CENTER, LAYER_FOREGROUND1, WorldMap::teleporter_message_color); - } - } - - /* Display a passive message in the map, if needed */ - if (m_passive_message_timer.started()) - context.color().draw_text(Resources::normal_font, m_passive_message, - Vector(static_cast(context.get_width()) / 2.0f, - static_cast(context.get_height()) - Resources::normal_font->get_height() - 60.0f), - ALIGN_CENTER, LAYER_FOREGROUND1, WorldMap::message_color); - - context.pop_transform(); + m_passive_message = message; + m_passive_message_timer.start(time); } void -WorldMap::setup() +WorldMap::set_initial_spawnpoint(const std::string& spawnpoint) { - auto& music_object = get_singleton_by_type(); - music_object.play_music(MusicType::LEVEL_MUSIC); - - MenuManager::instance().clear_menu_stack(); - ScreenManager::current()->set_screen_fade(std::make_unique(FadeToBlack::FADEIN, 1.0f)); - - load_state(); + m_force_spawnpoint = spawnpoint; +} - // if force_spawnpoint was set, move Tux there, then clear force_spawnpoint - if (!m_force_spawnpoint.empty()) { - move_to_spawnpoint(m_force_spawnpoint, false, m_main_is_default); - m_force_spawnpoint = ""; - m_main_is_default = true; - } - // If we specified a fade tilemap, let's fade it: - if (!m_initial_fade_tilemap.empty()) +WorldMapSector* +WorldMap::get_sector(const std::string& name) const +{ + for (auto& sector : m_sectors) { - auto tilemap = get_object_by_name(m_initial_fade_tilemap); - if (tilemap != nullptr) - { - if (m_fade_direction == 0) - { - tilemap->fade(1.0, 1); - } - else - { - tilemap->fade(0.0, 1); - } - } - m_initial_fade_tilemap = ""; - } - - m_tux->setup(); - - // register worldmap_table as worldmap in scripting - m_squirrel_environment->expose_self(); - - m_squirrel_environment->expose("settings", std::make_unique(this)); - - //Run default.nut just before init script - try { - IFileStream in(m_levels_path + "default.nut"); - m_squirrel_environment->run_script(in, "WorldMap::default.nut"); - } catch(std::exception& ) { - // doesn't exist or erroneous; do nothing + if (sector->get_name() == name) + return sector.get(); } + return nullptr; +} - if (!m_init_script.empty()) { - m_squirrel_environment->run_script(m_init_script, "WorldMap::init"); - } - m_tux->process_special_tile( at_special_tile() ); +WorldMapSector* +WorldMap::get_sector(int index) const +{ + if (index < 0 || index > static_cast(m_sectors.size()) - 1) + return nullptr; - m_in_world_select = false; + return m_sectors.at(index).get(); } + void -WorldMap::leave() +WorldMap::add_sector(std::unique_ptr sector) { - // save state of world and player - save_state(); - - // remove worldmap_table from roottable - m_squirrel_environment->unexpose_self(); - - GameManager::current()->load_next_worldmap(); + m_sectors.push_back(std::move(sector)); } void -WorldMap::set_levels_solved(bool solved, bool perfect) +WorldMap::set_sector(const std::string& name, const std::string& spawnpoint, + bool perform_full_setup) { - for (auto& level : get_objects_by_type()) + if (m_sector) // There is a current sector. { - level.set_solved(solved); - level.set_perfect(perfect); + save_state(); + m_sector->leave(); } -} -size_t -WorldMap::level_count() const -{ - return get_object_count(); -} + m_sector = get_sector(name); -size_t -WorldMap::solved_level_count() const -{ - size_t count = 0; - for (auto& level : get_objects_by_type()) { - if (level.is_solved()) { - count++; - } + if (!m_sector) // The sector was not found, so no sector is assigned. + { + log_warning << "Sector '" << name << "' not found. Setting first sector." << std::endl; + m_sector = get_sector(0); // In that case, assign the first sector. } - return count; -} - -void -WorldMap::load_state() -{ - WorldMapState state(*this); - state.load_state(); -} - -void -WorldMap::save_state() -{ - WorldMapState state(*this); - state.save_state(); -} - -void -WorldMap::run_script(const std::string& script, const std::string& sourcename) -{ - m_squirrel_environment->run_script(script, sourcename); -} + m_sector->move_to_spawnpoint("main"); -void -WorldMap::set_passive_message(const std::string& message, float time) -{ - m_passive_message = message; - m_passive_message_timer.start(time); -} + // Set up the new sector. + if (perform_full_setup) + load_state(); + m_sector->setup(); -Vector -WorldMap::get_tux_pos() -{ - return m_tux->get_pos(); + // If a spawnpoint has been provided, move to it. + if (!spawnpoint.empty()) + m_sector->move_to_spawnpoint(spawnpoint); } } // namespace worldmap diff --git a/src/worldmap/worldmap.hpp b/src/worldmap/worldmap.hpp index 508bffde955..a5dd97c22f2 100644 --- a/src/worldmap/worldmap.hpp +++ b/src/worldmap/worldmap.hpp @@ -1,6 +1,7 @@ // SuperTux // Copyright (C) 2004 Ingo Ruhnke // Copyright (C) 2006 Christoph Sommer +// 2023 Vankata453 // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -18,189 +19,98 @@ #ifndef HEADER_SUPERTUX_WORLDMAP_WORLDMAP_HPP #define HEADER_SUPERTUX_WORLDMAP_WORLDMAP_HPP -#include +#include "util/currenton.hpp" -#include "math/vector.hpp" -#include "supertux/game_object_manager.hpp" -#include "squirrel/squirrel_environment.hpp" -#include "supertux/statistics.hpp" +#include "control/controller.hpp" +#include "supertux/savegame.hpp" #include "supertux/timer.hpp" -#include "util/currenton.hpp" -#include "worldmap/direction.hpp" -#include "worldmap/spawn_point.hpp" - -class Controller; -class Level; -class PlayerStatus; -class Savegame; -class Sprite; -class SquirrelEnvironment; -class TileMap; +#include "worldmap/worldmap_sector.hpp" + class TileSet; namespace worldmap { -class Camera; -class LevelTile; -class SpecialTile; -class SpriteChange; -class Teleporter; -class Tux; - -class WorldMap final : public GameObjectManager, - public Currenton +class WorldMap final : public Currenton { -public: - friend class WorldMapParser; + friend class WorldMapSector; friend class WorldMapState; - static Color level_title_color; - static Color message_color; - static Color teleporter_message_color; - public: - WorldMap(const std::string& filename, Savegame& savegame, const std::string& force_spawnpoint = ""); - ~WorldMap() override; + static Color s_level_title_color; + static Color s_message_color; + static Color s_teleporter_message_color; - void finish_construction(); +public: + WorldMap(const std::string& filename, Savegame& savegame, + const std::string& force_sector = "", const std::string& force_spawnpoint = ""); void setup(); void leave(); void draw(DrawingContext& context); void update(float dt_sec); - void process_input(const Controller& controller); - Vector get_next_tile(const Vector& pos, const Direction& direction) const; - - /** gets a bitfield of Tile::WORLDMAP_NORTH | Tile::WORLDMAP_WEST | - ... values, which indicates the directions Tux can move to when - at the given position. */ - int available_directions_at(const Vector& pos) const; - - /** returns a bitfield representing the union of all - Tile::WORLDMAP_XXX values of all solid tiles at the given - position */ - int tile_data_at(const Vector& pos) const; - size_t level_count() const; size_t solved_level_count() const; - /** gets called from the GameSession when a level has been successfully - finished */ - void finished_level(Level* level); - - Savegame& get_savegame() const { return m_savegame; } - - /** Get a spawnpoint by its name @param name The name of the - spawnpoint @return spawnpoint corresponding to that name */ - SpawnPoint* get_spawnpoint_by_name(const std::string& spawnpoint_name) const - { - for (const auto& sp : m_spawn_points) { - if (sp->get_name() == spawnpoint_name) { - return sp.get(); - } - } - return nullptr; - } - - LevelTile* at_level() const; - SpecialTile* at_special_tile() const; - SpriteChange* at_sprite_change(const Vector& pos) const; - Teleporter* at_teleporter(const Vector& pos) const; - - /** Check if it is possible to walk from \a pos into \a direction, - if possible, write the new position to \a new_pos */ - bool path_ok(const Direction& direction, const Vector& pos, Vector* new_pos) const; - - /** Save worldmap state to squirrel state table */ - void save_state(); - /** Load worldmap state from squirrel state table */ void load_state(); - const std::string& get_title() const { return m_name; } + /** Save worldmap state to squirrel state table */ + void save_state(); /** switch to another worldmap. filename is relative to data root path */ - void change(const std::string& filename, const std::string& force_spawnpoint=""); - - /** Moves Tux to the given spawnpoint - @param spawnpoint Name of the spawnpoint to move to - @param pan Pan the camera during to new spawnpoint - @param main_as_default Move Tux to main spawnpoint if specified spawnpoint can't be found */ - void move_to_spawnpoint(const std::string& spawnpoint, bool pan = false, bool main_as_default = true); + void change(const std::string& filename, const std::string& force_sector = "", + const std::string& force_spawnpoint = ""); /** Mark all levels as solved or unsolved */ void set_levels_solved(bool solved, bool perfect); - /** Sets the name of the tilemap that should fade when worldmap is set up. */ - void set_initial_fade_tilemap(const std::string& tilemap_name, int direction) - { - m_initial_fade_tilemap = tilemap_name; - m_fade_direction = direction; - } - - /** Sets the initial spawnpoint on worldmap setup */ - void set_initial_spawnpoint(const std::string& spawnpoint_name) - { - m_force_spawnpoint = spawnpoint_name; - - // If spawnpoint we specified can not be found, - // don't bother moving to the main spawnpoint. - m_main_is_default = false; - } - - void run_script(const std::string& script, const std::string& sourcename); - + /** Sets the passive message with specific time **/ void set_passive_message(const std::string& message, float time); - Camera& get_camera() const { return *m_camera; } + /** Sets the initial spawnpoint to be forced on next setup */ + void set_initial_spawnpoint(const std::string& spawnpoint); - Vector get_tux_pos(); + const std::string& get_title() const { return m_name; } + Savegame& get_savegame() const { return m_savegame; } + const std::string& get_levels_path() const { return m_levels_path; } -protected: - virtual bool before_object_add(GameObject& object) override; - virtual void before_object_remove(GameObject& object) override; + WorldMapSector* get_sector(const std::string& name) const; + WorldMapSector* get_sector(int index) const; -private: - void draw_status(DrawingContext& context); + void add_sector(std::unique_ptr sector); + WorldMapSector& get_sector() const { return *m_sector; } + void set_sector(const std::string& name, const std::string& spawnpoint = "", + bool perform_full_setup = true); - void load(const std::string& filename); +private: void on_escape_press(); private: - std::unique_ptr m_squirrel_environment; - std::unique_ptr m_camera; - - bool m_enter_level; + WorldMapSector* m_sector; /* The currently active sector. */ + std::vector > m_sectors; - Tux* m_tux; + std::string m_force_spawnpoint; Savegame& m_savegame; - TileSet* m_tileset; std::string m_name; - std::string m_init_script; - - /** Variables to deal with the passive map messages */ - Timer m_passive_message_timer; - std::string m_passive_message; - std::string m_map_filename; std::string m_levels_path; - std::vector > m_spawn_points; + /* A worldmap, scheduled to change to next frame. */ + std::unique_ptr m_next_worldmap; - std::string m_force_spawnpoint; /**< if set, spawnpoint will be forced to this value */ - bool m_main_is_default; - std::string m_initial_fade_tilemap; - int m_fade_direction; + /** Passive map message variables */ + std::string m_passive_message; + Timer m_passive_message_timer; + bool m_enter_level; bool m_in_level; - bool m_in_world_select; private: diff --git a/src/worldmap/worldmap_object.cpp b/src/worldmap/worldmap_object.cpp new file mode 100644 index 00000000000..87543d622cf --- /dev/null +++ b/src/worldmap/worldmap_object.cpp @@ -0,0 +1,135 @@ +// SuperTux +// Copyright (C) 2016 Hume2 +// 2023 Vankata453 +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "worldmap/worldmap_object.hpp" + +#include "editor/editor.hpp" +#include "worldmap/worldmap_sector.hpp" + +namespace worldmap { + +bool +WorldMapObject::in_worldmap() +{ + return !Editor::is_active(); +} + + +WorldMapObject::WorldMapObject(const ReaderMapping& mapping, const std::string& default_sprite) : + MovingSprite(mapping, default_sprite), + m_tile_x(), + m_tile_y() +{ + initialize(); +} + +WorldMapObject::WorldMapObject(const ReaderMapping& mapping) : + MovingSprite(mapping), + m_tile_x(), + m_tile_y() +{ + initialize(); +} + +WorldMapObject::WorldMapObject(const Vector& pos, const std::string& default_sprite) : + MovingSprite(pos, default_sprite), + m_tile_x(), + m_tile_y() +{ + initialize(); +} + +void +WorldMapObject::initialize() +{ + // Set sector position from provided tile position + set_pos(Vector(32.0f * m_col.m_bbox.get_left() + + (m_col.m_bbox.get_width() < 32.f ? (32.f - m_col.m_bbox.get_width()) / 2 : 0), + 32.0f * m_col.m_bbox.get_top() + + (m_col.m_bbox.get_width() < 32.f ? (32.f - m_col.m_bbox.get_height()) / 2 : 0))); + + update_pos(); + update_hitbox(); +} + +ObjectSettings +WorldMapObject::get_settings() +{ + ObjectSettings result = MovingSprite::get_settings(); + + result.remove("x"); + result.remove("y"); + + result.add_int(_("X"), &m_tile_x, "x", {}, OPTION_HIDDEN); + result.add_int(_("Y"), &m_tile_y, "y", {}, OPTION_HIDDEN); + + return result; +} + +void +WorldMapObject::draw(DrawingContext& context) +{ + if (in_worldmap()) + draw_worldmap(context); + else + draw_normal(context); +} + +void +WorldMapObject::draw_worldmap(DrawingContext& context) +{ + draw_normal(context); +} + +void +WorldMapObject::draw_normal(DrawingContext& context) +{ + if (!m_sprite) return; + + m_sprite->draw(context.color(), + m_col.m_bbox.p1() + Vector((m_col.m_bbox.get_width() < 32.f ? (32.f - m_col.m_bbox.get_width()) / 2 : 0), + (m_col.m_bbox.get_height() < 32.f ? (32.f - m_col.m_bbox.get_height()) / 2 : 0)), + m_layer); +} + +void +WorldMapObject::update(float) +{ + update_pos(); +} + +void +WorldMapObject::update_pos() +{ + m_tile_x = static_cast(m_col.m_bbox.get_left()) / 32; + m_tile_y = static_cast(m_col.m_bbox.get_top()) / 32; +} + +void +WorldMapObject::move_to(const Vector& pos) +{ + // Set sector position to the provided position, rounding it to be divisible by 32 + set_pos(Vector(32.0f * static_cast(pos.x / 32) + + (m_col.m_bbox.get_width() < 32.f ? (32.f - m_col.m_bbox.get_width()) / 2 : 0), + 32.0f * static_cast(pos.y / 32) + + (m_col.m_bbox.get_width() < 32.f ? (32.f - m_col.m_bbox.get_height()) / 2 : 0))); + update_pos(); +} + +} // namespace worldmap + +/* EOF */ diff --git a/src/worldmap/worldmap_object.hpp b/src/worldmap/worldmap_object.hpp new file mode 100644 index 00000000000..7d60963f188 --- /dev/null +++ b/src/worldmap/worldmap_object.hpp @@ -0,0 +1,75 @@ +// SuperTux +// Copyright (C) 2016 Hume2 +// 2023 Vankata453 +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef HEADER_SUPERTUX_WORLDMAP_WORLDMAP_OBJECT_HPP +#define HEADER_SUPERTUX_WORLDMAP_WORLDMAP_OBJECT_HPP + +#include "object/moving_sprite.hpp" + +#include "math/vector.hpp" + +class DrawingContext; +class ReaderMapping; + +namespace worldmap { + +class WorldMapObject : public MovingSprite +{ +protected: + static bool in_worldmap(); + +public: + WorldMapObject(const ReaderMapping& mapping, const std::string& default_sprite); + WorldMapObject(const ReaderMapping& mapping); + WorldMapObject(const Vector& pos, const std::string& default_sprite); + + static std::string class_name() { return "worldmap-object"; } + virtual std::string get_class_name() const override { return class_name(); } + + void draw(DrawingContext& context) override; + + /** Draws the object, when on a worldmap. */ + virtual void draw_worldmap(DrawingContext& context); + + void update(float) override; + + virtual HitResponse collision(GameObject& other, const CollisionHit& hit) override { return FORCE_MOVE; } + virtual ObjectSettings get_settings() override; + virtual void move_to(const Vector& pos) override; + + Vector get_tile_pos() const { return { m_tile_x, m_tile_y }; } + +private: + void initialize(); + + void draw_normal(DrawingContext& context); + void update_pos(); + +private: + int m_tile_x; + int m_tile_y; + +private: + WorldMapObject(const WorldMapObject&) = delete; + WorldMapObject& operator=(const WorldMapObject&) = delete; +}; + +} // namespace worldmap + +#endif + +/* EOF */ diff --git a/src/worldmap/worldmap_parser.cpp b/src/worldmap/worldmap_parser.cpp deleted file mode 100644 index 90bc074db69..00000000000 --- a/src/worldmap/worldmap_parser.cpp +++ /dev/null @@ -1,206 +0,0 @@ -// SuperTux -// Copyright (C) 2018 Ingo Ruhnke -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -#include "worldmap/worldmap_parser.hpp" - -#include -#include - -#include "object/ambient_light.hpp" -#include "object/background.hpp" -#include "object/decal.hpp" -#include "object/music_object.hpp" -#include "object/path_gameobject.hpp" -#include "object/tilemap.hpp" -#include "physfs/physfs_file_system.hpp" -#include "physfs/util.hpp" -#include "supertux/tile_manager.hpp" -#include "util/file_system.hpp" -#include "util/log.hpp" -#include "util/reader.hpp" -#include "util/reader_document.hpp" -#include "util/reader_mapping.hpp" -#include "util/reader_object.hpp" -#include "worldmap/level_tile.hpp" -#include "worldmap/spawn_point.hpp" -#include "worldmap/special_tile.hpp" -#include "worldmap/sprite_change.hpp" -#include "worldmap/teleporter.hpp" -#include "worldmap/tux.hpp" -#include "worldmap/worldmap.hpp" -#include "worldmap/worldmap_parser.hpp" -#include "worldmap/worldmap_screen.hpp" - -namespace worldmap { - -WorldMapParser::WorldMapParser(WorldMap& worldmap) : - m_worldmap(worldmap) -{ -} - -void -WorldMapParser::load_worldmap(const std::string& filename) -{ - m_worldmap.m_map_filename = physfsutil::realpath(filename); - m_worldmap.m_levels_path = FileSystem::dirname(m_worldmap.m_map_filename); - - try { - register_translation_directory(m_worldmap.m_map_filename); - auto doc = ReaderDocument::from_file(m_worldmap.m_map_filename); - auto root = doc.get_root(); - - if (root.get_name() != "supertux-level") - throw std::runtime_error("file isn't a supertux-level file."); - - auto level_ = root.get_mapping(); - - level_.get("name", m_worldmap.m_name); - - std::string tileset_name; - if (level_.get("tileset", tileset_name)) { - if (m_worldmap.m_tileset != nullptr) { - log_warning << "multiple tilesets specified in level_" << std::endl; - } else { - m_worldmap.m_tileset = TileManager::current()->get_tileset(tileset_name); - } - } - /* load default tileset */ - if (m_worldmap.m_tileset == nullptr) { - m_worldmap.m_tileset = TileManager::current()->get_tileset("images/ice_world.strf"); - } - - std::optional sector; - if (!level_.get("sector", sector)) { - throw std::runtime_error("No sector specified in worldmap file."); - } else { - auto iter = sector->get_iter(); - while (iter.next()) { - if (iter.get_key() == "tilemap") { - m_worldmap.add(m_worldmap.m_tileset, iter.as_mapping()); - } else if (iter.get_key() == "background") { - m_worldmap.add(iter.as_mapping()); - } else if (iter.get_key() == "music") { - const auto& sx = iter.get_sexp(); - if (sx.is_array() && sx.as_array().size() == 2 && sx.as_array()[1].is_string()) { - std::string value; - iter.get(value); - m_worldmap.add().set_music(value); - } else { - m_worldmap.add(iter.as_mapping()); - } - } else if (iter.get_key() == "init-script") { - iter.get(m_worldmap.m_init_script); - } else if (iter.get_key() == "worldmap-spawnpoint") { - auto sp = std::make_unique(iter.as_mapping()); - m_worldmap.m_spawn_points.push_back(std::move(sp)); - } else if (iter.get_key() == "level") { - auto& level = m_worldmap.add(m_worldmap.m_levels_path, iter.as_mapping()); - load_level_information(level); - } else if (iter.get_key() == "special-tile") { - m_worldmap.add(iter.as_mapping()); - } else if (iter.get_key() == "sprite-change") { - m_worldmap.add(iter.as_mapping()); - } else if (iter.get_key() == "teleporter") { - m_worldmap.add(iter.as_mapping()); - } else if (iter.get_key() == "decal") { - m_worldmap.add(iter.as_mapping()); - } else if (iter.get_key() == "path") { - m_worldmap.add(iter.as_mapping()); - } else if (iter.get_key() == "ambient-light") { - const auto& sx = iter.get_sexp(); - if (sx.is_array() && sx.as_array().size() >= 3 && - sx.as_array()[1].is_real() && sx.as_array()[2].is_real() && sx.as_array()[3].is_real()) - { - // for backward compatibilty - std::vector vColor; - bool hasColor = sector->get("ambient-light", vColor); - if (vColor.size() < 3 || !hasColor) { - log_warning << "(ambient-light) requires a color as argument" << std::endl; - } else { - m_worldmap.add(Color(vColor)); - } - } else { - // modern format - m_worldmap.add(iter.as_mapping()); - } - } else if (iter.get_key() == "name") { - // skip - } else { - log_warning << "Unknown token '" << iter.get_key() << "' in worldmap" << std::endl; - } - } - } - - m_worldmap.flush_game_objects(); - - if (m_worldmap.get_solid_tilemaps().empty()) - log_warning << "No solid tilemap specified" << std::endl; - - m_worldmap.move_to_spawnpoint("main"); - - } catch(std::exception& e) { - std::stringstream msg; - msg << "Problem when parsing worldmap '" << m_worldmap.m_map_filename << "': " << - e.what(); - throw std::runtime_error(msg.str()); - } - - m_worldmap.finish_construction(); -} - -void -WorldMapParser::load_level_information(LevelTile& level) -{ - /** get special_tile's title */ - level.m_title = _(""); - level.m_target_time = 0.0f; - - try { - std::string filename = m_worldmap.m_levels_path + level.get_level_filename(); - - if (m_worldmap.m_levels_path == "./") - filename = level.get_level_filename(); - - if (!PHYSFS_exists(filename.c_str())) - { - log_warning << "Level file '" << filename << "' does not exist. Skipping." << std::endl; - return; - } - if (physfsutil::is_directory(filename)) - { - log_warning << "Level file '" << filename << "' is a directory. Skipping." << std::endl; - return; - } - - register_translation_directory(filename); - auto doc = ReaderDocument::from_file(filename); - auto root = doc.get_root(); - if (root.get_name() != "supertux-level") { - return; - } else { - auto level_mapping = root.get_mapping(); - level_mapping.get("name", level.m_title); - level_mapping.get("target-time", level.m_target_time); - } - } catch(std::exception& e) { - log_warning << "Problem when reading level information: " << e.what() << std::endl; - return; - } -} - -} // namespace worldmap - -/* EOF */ diff --git a/src/worldmap/worldmap_sector.cpp b/src/worldmap/worldmap_sector.cpp new file mode 100644 index 00000000000..c1f140e2c35 --- /dev/null +++ b/src/worldmap/worldmap_sector.cpp @@ -0,0 +1,590 @@ +// SuperTux - A Jump'n Run +// Copyright (C) 2004 Ingo Ruhnke +// Copyright (C) 2006 Christoph Sommer +// 2023 Vankata453 +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "worldmap/worldmap_sector.hpp" + +#include "audio/sound_manager.hpp" +#include "object/ambient_light.hpp" +#include "object/display_effect.hpp" +#include "object/music_object.hpp" +#include "object/tilemap.hpp" +#include "physfs/ifile_stream.hpp" +#include "scripting/worldmap_sector.hpp" +#include "squirrel/squirrel_environment.hpp" +#include "supertux/d_scope.hpp" +#include "supertux/debug.hpp" +#include "supertux/fadetoblack.hpp" +#include "supertux/game_manager.hpp" +#include "supertux/game_session.hpp" +#include "supertux/gameconfig.hpp" +#include "supertux/level.hpp" +#include "supertux/player_status_hud.hpp" +#include "supertux/resources.hpp" +#include "supertux/screen_manager.hpp" +#include "supertux/shrinkfade.hpp" +#include "supertux/tile.hpp" +#include "util/file_system.hpp" +#include "worldmap/camera.hpp" +#include "worldmap/level_tile.hpp" +#include "worldmap/spawn_point.hpp" +#include "worldmap/special_tile.hpp" +#include "worldmap/teleporter.hpp" +#include "worldmap/worldmap.hpp" + +namespace worldmap { + +WorldMapSector* +WorldMapSector::current() +{ + if (!WorldMap::current()) + return nullptr; + + return &WorldMap::current()->get_sector(); +} + + +WorldMapSector::WorldMapSector(WorldMap& parent) : + Base::Sector("worldmap"), + m_parent(parent), + m_camera(new Camera), + m_tux(&add(&parent)), + m_spawnpoints(), + m_initial_fade_tilemap(), + m_fade_direction() +{ + BIND_WORLDMAP_SECTOR(*this); + + add(m_parent.get_savegame().get_player_status()); +} + +WorldMapSector::~WorldMapSector() +{ + m_spawnpoints.clear(); + + clear_objects(); +} + +void +WorldMapSector::finish_construction(bool) +{ + if (!get_object_by_type()) + add(Color::WHITE); + + if (!get_object_by_type()) + add(); + + if (!get_object_by_type()) + add("Effect"); + + flush_game_objects(); +} + + +void +WorldMapSector::setup() +{ + BIND_WORLDMAP_SECTOR(*this); + + auto& music_object = get_singleton_by_type(); + music_object.play_music(MusicType::LEVEL_MUSIC); + + ScreenManager::current()->set_screen_fade(std::make_unique(FadeToBlack::FADEIN, 1.0f)); + + // If we specified a fade tilemap, let's fade it: + if (!m_initial_fade_tilemap.empty()) + { + auto tilemap = get_object_by_name(m_initial_fade_tilemap); + if (tilemap != nullptr) + { + if (m_fade_direction == 0) + { + tilemap->fade(1.0, 1); + } + else + { + tilemap->fade(0.0, 1); + } + } + m_initial_fade_tilemap = ""; + } + + m_tux->setup(); + + // register worldmap_table as "worldmap" in scripting + m_squirrel_environment->expose_self(); + m_squirrel_environment->expose("settings", std::make_unique(this)); + + /** Perform scripting related actions. **/ + // Run default.nut just before init script + try + { + IFileStream in(m_parent.get_levels_path() + "default.nut"); + m_squirrel_environment->run_script(in, "WorldMapSector::default.nut"); + } + catch (...) + { + // doesn't exist or erroneous; do nothing + } + + if (!m_init_script.empty()) + m_squirrel_environment->run_script(m_init_script, "WorldMapSector::init"); +} + +void +WorldMapSector::leave() +{ + BIND_WORLDMAP_SECTOR(*this); + + // remove worldmap_table from roottable + m_squirrel_environment->unexpose_self(); +} + + +void +WorldMapSector::draw(DrawingContext& context) +{ + BIND_WORLDMAP_SECTOR(*this); + + if (get_width() < static_cast(context.get_width()) || + get_height() < static_cast(context.get_height())) + { + context.color().draw_filled_rect(context.get_rect(), + Color(0.0f, 0.0f, 0.0f, 1.0f), LAYER_BACKGROUND0); + } + + context.push_transform(); + context.set_translation(m_camera->get_offset()); + + GameObjectManager::draw(context); + + if (g_debug.show_worldmap_path) + { + for (int x = 0; x < static_cast(get_tiles_width()); x++) { + for (int y = 0; y < static_cast(get_tiles_height()); y++) { + const int data = tile_data_at(Vector(static_cast(x), static_cast(y))); + const int px = x * 32; + const int py = y * 32; + const int W = 4; + const int layer = LAYER_FOREGROUND1 + 1000; + const Color color(1.0f, 0.0f, 1.0f, 0.5f); + if (data & Tile::WORLDMAP_NORTH) context.color().draw_filled_rect(Rect(px + 16-W, py , px + 16+W, py + 16-W), color, layer); + if (data & Tile::WORLDMAP_SOUTH) context.color().draw_filled_rect(Rect(px + 16-W, py + 16+W, px + 16+W, py + 32 ), color, layer); + if (data & Tile::WORLDMAP_EAST) context.color().draw_filled_rect(Rect(px + 16+W, py + 16-W, px + 32 , py + 16+W), color, layer); + if (data & Tile::WORLDMAP_WEST) context.color().draw_filled_rect(Rect(px , py + 16-W, px + 16-W, py + 16+W), color, layer); + if (data & Tile::WORLDMAP_DIR_MASK) context.color().draw_filled_rect(Rect(px + 16-W, py + 16-W, px + 16+W, py + 16+W), color, layer); + if (data & Tile::WORLDMAP_STOP) context.color().draw_filled_rect(Rect(px + 4 , py + 4 , px + 28 , py + 28 ), color, layer); + } + } + } + + draw_status(context); +} + +void +WorldMapSector::draw_status(DrawingContext& context) +{ + context.push_transform(); + context.set_translation(Vector(0, 0)); + + if (!m_tux->is_moving()) { + LevelTile* level = at_object(); + if (level) + { + context.color().draw_text(Resources::normal_font, level->get_title(), + Vector(static_cast(context.get_width()) / 2.0f, + static_cast(context.get_height()) - Resources::normal_font->get_height() - 10), + ALIGN_CENTER, LAYER_HUD, level->get_title_color()); + + if (g_config->developer_mode) { + context.color().draw_text(Resources::small_font, FileSystem::join(level->get_basedir(), level->get_level_filename()), + Vector(static_cast(context.get_width()) / 2.0f, + static_cast(context.get_height()) - Resources::normal_font->get_height() - 25), + ALIGN_CENTER, LAYER_HUD, level->get_title_color()); + } + + // if level is solved, draw level picture behind stats + /* + if (level.solved) { + if (const Surface* picture = level->get_picture()) { + Vector pos = Vector(context.get_width() - picture->get_width(), context.get_height() - picture->get_height()); + context.push_transform(); + context.set_alpha(0.5); + context.color().draw_surface(picture, pos, LAYER_FOREGROUND1-1); + context.pop_transform(); + } + } + */ + level->get_statistics().draw_worldmap_info(context, level->get_target_time()); + } + + SpecialTile* special_tile = at_object(); + if (special_tile) + { + /* Display an in-map message in the map, if any as been selected */ + if (!special_tile->get_map_message().empty() && !special_tile->is_passive_message()) + context.color().draw_text(Resources::normal_font, special_tile->get_map_message(), + Vector(static_cast(context.get_width()) / 2.0f, + static_cast(context.get_height()) - static_cast(Resources::normal_font->get_height()) - 60.0f), + ALIGN_CENTER, LAYER_FOREGROUND1, WorldMap::s_message_color); + } + + // display teleporter messages + Teleporter* teleporter = at_object(); + if (teleporter && (!teleporter->get_message().empty())) + { + Vector pos = Vector(static_cast(context.get_width()) / 2.0f, + static_cast(context.get_height()) - Resources::normal_font->get_height() - 30.0f); + context.color().draw_text(Resources::normal_font, teleporter->get_message(), pos, ALIGN_CENTER, LAYER_FOREGROUND1, WorldMap::s_teleporter_message_color); + } + } + + /* Display a passive message on the map, if set */ + if (m_parent.m_passive_message_timer.started()) + context.color().draw_text(Resources::normal_font, m_parent.m_passive_message, + Vector(static_cast(context.get_width()) / 2.0f, + static_cast(context.get_height()) - Resources::normal_font->get_height() - 60.0f), + ALIGN_CENTER, LAYER_FOREGROUND1, WorldMap::s_message_color); + + context.pop_transform(); +} + +void +WorldMapSector::update(float dt_sec) +{ + BIND_WORLDMAP_SECTOR(*this); + + GameObjectManager::update(dt_sec); + + m_camera->update(dt_sec); + + { + // check for teleporters + auto teleporter = at_object(); + if (teleporter && (teleporter->is_automatic() || (m_parent.m_enter_level && (!m_tux->is_moving())))) { + m_parent.m_enter_level = false; + if (!teleporter->get_worldmap().empty()) + { + // Change worldmap. + m_parent.change(teleporter->get_worldmap(), teleporter->get_sector(), + teleporter->get_spawnpoint()); + } + else + { + // TODO: an animation, camera scrolling or a fading would be a nice touch + SoundManager::current()->play("sounds/warp.wav"); + m_tux->m_back_direction = Direction::NONE; + if (!teleporter->get_sector().empty()) + { + // A target sector is set, so teleport to it at the specified spawnpoint. + m_parent.set_sector(teleporter->get_sector(), teleporter->get_spawnpoint()); + } + else + { + // No target sector is set, so teleport at the specified spawnpoint in the current one. + move_to_spawnpoint(teleporter->get_spawnpoint(), true); + } + } + } + } + + { + // check for auto-play levels + auto level = at_object(); + if (level && level->is_auto_play() && !level->is_solved() && !m_tux->is_moving()) { + m_parent.m_enter_level = true; + // automatically mark these levels as solved in case player aborts + level->set_solved(true); + } + } + + { + if (m_parent.m_enter_level && !m_tux->is_moving()) + { + /* Check level action */ + auto level_ = at_object(); + if (!level_) { + //Respawn if player on a tile with no level and nowhere to go. + int tile_data = tile_data_at(m_tux->get_tile_pos()); + if (!( tile_data & ( Tile::WORLDMAP_NORTH | Tile::WORLDMAP_SOUTH | Tile::WORLDMAP_WEST | Tile::WORLDMAP_EAST ))){ + log_warning << "Player at illegal position " << m_tux->get_tile_pos().x << ", " << m_tux->get_tile_pos().y << " respawning." << std::endl; + move_to_spawnpoint("main"); + return; + } + log_warning << "No level to enter at: " << m_tux->get_tile_pos().x << ", " << m_tux->get_tile_pos().y << std::endl; + return; + } + + if (level_->get_tile_pos() == m_tux->get_tile_pos()) { + try { + Vector shrinkpos = Vector(level_->get_pos().x + 16 - m_camera->get_offset().x, + level_->get_pos().y + 8 - m_camera->get_offset().y); + std::string levelfile = m_parent.m_levels_path + level_->get_level_filename(); + + // update state and savegame + m_parent.save_state(); + ScreenManager::current()->push_screen(std::make_unique(levelfile, m_parent.m_savegame, &level_->get_statistics()), + std::make_unique(shrinkpos, 1.0f)); + + m_parent.m_in_level = true; + } catch(std::exception& e) { + log_fatal << "Couldn't load level: " << e.what() << std::endl; + } + } + } + else + { + // tux->set_direction(input_direction); + } + } +} + + +Vector +WorldMapSector::get_next_tile(const Vector& pos, const Direction& direction) const +{ + auto position = pos; + switch (direction) { + case Direction::WEST: + position.x -= 1; + break; + case Direction::EAST: + position.x += 1; + break; + case Direction::NORTH: + position.y -= 1; + break; + case Direction::SOUTH: + position.y += 1; + break; + case Direction::NONE: + break; + } + return position; +} + +int +WorldMapSector::available_directions_at(const Vector& p) const +{ + return tile_data_at(p) & Tile::WORLDMAP_DIR_MASK; +} + +int +WorldMapSector::tile_data_at(const Vector& p) const +{ + int dirs = 0; + + for (const auto& tilemap : get_solid_tilemaps()) { + const Tile& tile = tilemap->get_tile(static_cast(p.x), static_cast(p.y)); + int dirdata = tile.get_data(); + dirs |= dirdata; + } + + return dirs; +} + + +size_t +WorldMapSector::level_count() const +{ + return get_object_count(); +} + +size_t +WorldMapSector::solved_level_count() const +{ + size_t count = 0; + for (auto& level : get_objects_by_type()) { + if (level.is_solved()) { + count++; + } + } + + return count; +} + + +void +WorldMapSector::finished_level(Level* gamelevel) +{ + // TODO use Level* parameter here? + auto level = at_object(); + + if (level == nullptr) { + return; + } + + bool old_level_state = level->is_solved(); + level->set_solved(true); + + // deal with statistics + level->get_statistics().update(gamelevel->m_stats); + + if (level->get_statistics().completed(level->get_statistics(), level->get_target_time())) { + level->set_perfect(true); + } + + m_parent.save_state(); + + if (old_level_state != level->is_solved()) { + // Try to detect the next direction to which we should walk + // FIXME: Mostly a hack + Direction dir = Direction::NONE; + + int dirdata = available_directions_at(m_tux->get_tile_pos()); + // first, test for crossroads + if (dirdata == Tile::WORLDMAP_CNSE || + dirdata == Tile::WORLDMAP_CNSW || + dirdata == Tile::WORLDMAP_CNEW || + dirdata == Tile::WORLDMAP_CSEW || + dirdata == Tile::WORLDMAP_CNSEW) + dir = Direction::NONE; + else if (dirdata & Tile::WORLDMAP_NORTH + && m_tux->m_back_direction != Direction::NORTH) + dir = Direction::NORTH; + else if (dirdata & Tile::WORLDMAP_SOUTH + && m_tux->m_back_direction != Direction::SOUTH) + dir = Direction::SOUTH; + else if (dirdata & Tile::WORLDMAP_EAST + && m_tux->m_back_direction != Direction::EAST) + dir = Direction::EAST; + else if (dirdata & Tile::WORLDMAP_WEST + && m_tux->m_back_direction != Direction::WEST) + dir = Direction::WEST; + + if (dir != Direction::NONE) { + m_tux->set_direction(dir); + } + } + + if (!level->get_extro_script().empty()) { + try { + run_script(level->get_extro_script(), "WorldMapSector:extro_script"); + } catch(std::exception& e) { + log_warning << "Couldn't run level-extro-script: " << e.what() << std::endl; + } + } +} + +SpawnPoint* +WorldMapSector::get_spawnpoint_by_name(const std::string& spawnpoint_name) const +{ + for (const auto& sp : m_spawnpoints) + { + if (sp->get_name() == spawnpoint_name) + return sp.get(); + } + return nullptr; +} + +bool +WorldMapSector::path_ok(const Direction& direction, const Vector& old_pos, Vector* new_pos) const +{ + *new_pos = get_next_tile(old_pos, direction); + + if (!(new_pos->x >= 0 && new_pos->x < get_tiles_width() + && new_pos->y >= 0 && new_pos->y < get_tiles_height())) + { // New position is outsite the tilemap + return false; + } + else + { // Check if the tile allows us to go to new_pos + int old_tile_data = tile_data_at(old_pos); + int new_tile_data = tile_data_at(*new_pos); + switch (direction) + { + case Direction::WEST: + return (old_tile_data & Tile::WORLDMAP_WEST + && new_tile_data & Tile::WORLDMAP_EAST); + + case Direction::EAST: + return (old_tile_data & Tile::WORLDMAP_EAST + && new_tile_data & Tile::WORLDMAP_WEST); + + case Direction::NORTH: + return (old_tile_data & Tile::WORLDMAP_NORTH + && new_tile_data & Tile::WORLDMAP_SOUTH); + + case Direction::SOUTH: + return (old_tile_data & Tile::WORLDMAP_SOUTH + && new_tile_data & Tile::WORLDMAP_NORTH); + + case Direction::NONE: + log_warning << "path_ok() can't walk if direction is NONE" << std::endl; + assert(false); + } + return false; + } +} + +void +WorldMapSector::move_to_spawnpoint(const std::string& spawnpoint, bool pan) +{ + auto sp = get_spawnpoint_by_name(spawnpoint); + if (sp != nullptr) { + Vector p = sp->get_pos(); + m_tux->set_tile_pos(p); + m_tux->set_direction(sp->get_auto_dir()); + if (pan) { + m_camera->pan(); + } + return; + } + + log_warning << "Spawnpoint '" << spawnpoint << "' not found." << std::endl; + if (spawnpoint != "main") { + move_to_spawnpoint("main"); + } +} + +void +WorldMapSector::set_initial_fade_tilemap(const std::string& tilemap_name, int direction) +{ + m_initial_fade_tilemap = tilemap_name; + m_fade_direction = direction; +} + + +bool +WorldMapSector::before_object_add(GameObject& object) +{ + m_squirrel_environment->try_expose(object); + return true; +} + +void +WorldMapSector::before_object_remove(GameObject& object) +{ + m_squirrel_environment->try_unexpose(object); +} + + +TileSet* +WorldMapSector::get_tileset() const +{ + return m_parent.m_tileset; +} + +Vector +WorldMapSector::get_tux_pos() const +{ + return m_tux->get_pos(); +} + +} // namespace worldmap + +/* EOF */ diff --git a/src/worldmap/worldmap_sector.hpp b/src/worldmap/worldmap_sector.hpp new file mode 100644 index 00000000000..94019d0937a --- /dev/null +++ b/src/worldmap/worldmap_sector.hpp @@ -0,0 +1,141 @@ +// SuperTux +// Copyright (C) 2004 Ingo Ruhnke +// Copyright (C) 2006 Christoph Sommer +// 2023 Vankata453 +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef HEADER_SUPERTUX_WORLDMAP_WORLDMAP_SECTOR_HPP +#define HEADER_SUPERTUX_WORLDMAP_WORLDMAP_SECTOR_HPP + +#include "supertux/sector_base.hpp" + +#include "worldmap/tux.hpp" + +namespace worldmap { + +class Camera; +class SpawnPoint; +class WorldMap; + +/** Represents one of (potentially) multiple, separate parts of a WorldMap. + WorldMap variant of Sector, utilizing only its base features. */ +class WorldMapSector final : public Base::Sector +{ + friend class WorldMapSectorParser; + friend class WorldMapState; + +public: + static WorldMapSector* current(); + +public: + WorldMapSector(WorldMap& parent); + ~WorldMapSector() override; + + void finish_construction(bool) override; + + void setup(); + void leave(); + + void draw(DrawingContext& context) override; + void update(float dt_sec) override; + + Vector get_next_tile(const Vector& pos, const Direction& direction) const; + + /** gets a bitfield of Tile::WORLDMAP_NORTH | Tile::WORLDMAP_WEST | + ... values, which indicates the directions Tux can move to when + at the given position. */ + int available_directions_at(const Vector& pos) const; + + /** returns a bitfield representing the union of all + Tile::WORLDMAP_XXX values of all solid tiles at the given + position */ + int tile_data_at(const Vector& pos) const; + + size_t level_count() const; + size_t solved_level_count() const; + + /** gets called from the GameSession when a level has been successfully + finished */ + void finished_level(Level* level); + + /** Get a spawnpoint by its name @param name The name of the + spawnpoint @return spawnpoint corresponding to that name */ + SpawnPoint* get_spawnpoint_by_name(const std::string& spawnpoint_name) const; + + template + T* at_object() const + { + for (auto& obj : get_objects_by_type()) + if (obj.get_tile_pos() == m_tux->get_tile_pos()) + return &obj; + + return nullptr; + } + template + T* at_object(const Vector& pos) const + { + for (auto& obj : get_objects_by_type()) + if (obj.get_tile_pos() == pos) + return &obj; + + return nullptr; + } + + /** Check if it is possible to walk from \a pos into \a direction, + if possible, write the new position to \a new_pos */ + bool path_ok(const Direction& direction, const Vector& old_pos, Vector* new_pos) const; + + /** Moves Tux to the given spawnpoint + @param spawnpoint Name of the spawnpoint to move to + @param pan Pan the camera during to new spawnpoint */ + void move_to_spawnpoint(const std::string& spawnpoint, bool pan = false); + + /** Sets the name of the tilemap that should fade when worldmap is set up. */ + void set_initial_fade_tilemap(const std::string& tilemap_name, int direction); + + bool in_worldmap() const override { return true; } + + TileSet* get_tileset() const override; + WorldMap& get_worldmap() const { return m_parent; } + Camera& get_camera() const { return *m_camera; } + Tux& get_tux() const { return *m_tux; } + Vector get_tux_pos() const; + +protected: + void draw_status(DrawingContext& context); + + bool before_object_add(GameObject& object) override; + void before_object_remove(GameObject& object) override; + +private: + WorldMap& m_parent; + + std::unique_ptr m_camera; + Tux* m_tux; + std::vector > m_spawnpoints; + + std::string m_initial_fade_tilemap; + int m_fade_direction; + +private: + WorldMapSector(const WorldMapSector&) = delete; + WorldMapSector& operator=(const WorldMapSector&) = delete; +}; + +} // namespace worldmap + +#endif + +/* EOF */ diff --git a/src/worldmap/worldmap_sector_parser.cpp b/src/worldmap/worldmap_sector_parser.cpp new file mode 100644 index 00000000000..1c4f92673b3 --- /dev/null +++ b/src/worldmap/worldmap_sector_parser.cpp @@ -0,0 +1,69 @@ +// SuperTux +// Copyright (C) 2023 Vankata453 +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "worldmap/worldmap_sector_parser.hpp" + +#include "object/tilemap.hpp" +#include "supertux/d_scope.hpp" +#include "supertux/game_object_factory.hpp" +#include "worldmap/spawn_point.hpp" +#include "worldmap/worldmap_sector.hpp" + +namespace worldmap { + +std::unique_ptr +WorldMapSectorParser::from_reader(WorldMap& worldmap, const ReaderMapping& reader) +{ + auto sector = std::make_unique(worldmap); + BIND_WORLDMAP_SECTOR(*sector); + WorldMapSectorParser parser(*sector); + parser.parse(reader); + return sector; +} + + +WorldMapSectorParser::WorldMapSectorParser(WorldMapSector& sector) : + SectorParser(sector, false) +{ +} + +WorldMapSector& +WorldMapSectorParser::get_sector() const +{ + return static_cast(m_sector); +} + +bool +WorldMapSectorParser::parse_object_additional(const std::string& name, const ReaderMapping& reader) +{ + if (name == "worldmap-spawnpoint") // Custom rule for adding spawnpoints + { + get_sector().m_spawnpoints.push_back(std::make_unique(reader)); + return true; + } + else if (name == "tilemap") // Custom rule for adding tilemaps on worldmaps + { + get_sector().add(get_sector().get_tileset(), reader); + return true; + } + + // Proceed adding the object only if it's flagged as allowed for worldmaps + return !GameObjectFactory::instance().has_params(name, ObjectFactory::RegisteredObjectParam::OBJ_PARAM_WORLDMAP); +} + +} // namespace worldmap + +/* EOF */ diff --git a/src/worldmap/worldmap_parser.hpp b/src/worldmap/worldmap_sector_parser.hpp similarity index 53% rename from src/worldmap/worldmap_parser.hpp rename to src/worldmap/worldmap_sector_parser.hpp index 758f492632a..4460faf0bfd 100644 --- a/src/worldmap/worldmap_parser.hpp +++ b/src/worldmap/worldmap_sector_parser.hpp @@ -1,6 +1,5 @@ // SuperTux -// Copyright (C) 2004-2018 Ingo Ruhnke -// 2006 Christoph Sommer +// Copyright (C) 2023 Vankata453 // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -15,30 +14,32 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#ifndef HEADER_SUPERTUX_WORLDMAP_WORLDMAP_PARSER_HPP -#define HEADER_SUPERTUX_WORLDMAP_WORLDMAP_PARSER_HPP +#ifndef HEADER_SUPERTUX_WORLDMAP_WORLDMAP_SECTOR_PARSER_HPP +#define HEADER_SUPERTUX_WORLDMAP_WORLDMAP_SECTOR_PARSER_HPP -#include +#include "supertux/sector_parser.hpp" namespace worldmap { -class LevelTile; class WorldMap; +class WorldMapSector; -class WorldMapParser +class WorldMapSectorParser final : public SectorParser { public: - WorldMapParser(WorldMap& worldmap); + static std::unique_ptr from_reader(WorldMap& worldmap, const ReaderMapping& sector); - void load_worldmap(const std::string& filename); - void load_level_information(LevelTile& level); +private: + WorldMapSectorParser(WorldMapSector& sector); private: - WorldMap& m_worldmap; + WorldMapSector& get_sector() const; + + bool parse_object_additional(const std::string& name, const ReaderMapping& reader) override; private: - WorldMapParser(const WorldMapParser&) = delete; - WorldMapParser& operator=(const WorldMapParser&) = delete; + WorldMapSectorParser(const WorldMapSectorParser&) = delete; + WorldMapSectorParser& operator=(const WorldMapSectorParser&) = delete; }; } // namespace worldmap diff --git a/src/worldmap/worldmap_state.cpp b/src/worldmap/worldmap_state.cpp index f54ee502704..a9825f75995 100644 --- a/src/worldmap/worldmap_state.cpp +++ b/src/worldmap/worldmap_state.cpp @@ -1,6 +1,7 @@ // SuperTux // Copyright (C) 2004-2018 Ingo Ruhnke // 2006 Christoph Sommer +// 2023 Vankata453 // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -23,6 +24,7 @@ #include "supertux/savegame.hpp" #include "supertux/tile.hpp" #include "util/log.hpp" +#include "worldmap/direction.hpp" #include "worldmap/level_tile.hpp" #include "worldmap/sprite_change.hpp" #include "worldmap/tux.hpp" @@ -31,260 +33,367 @@ namespace worldmap { WorldMapState::WorldMapState(WorldMap& worldmap) : - m_worldmap(worldmap) + m_worldmap(worldmap), + m_position_was_reset(false) { } + void WorldMapState::load_state() { log_debug << "loading worldmap state" << std::endl; - SquirrelVM& vm = SquirrelVirtualMachine::current()->get_vm(); + WORLDMAP_STATE_SQUIRREL_VM_GUARD; SQInteger oldtop = sq_gettop(vm.get_vm()); try { - // get state table + /** Get state table. **/ sq_pushroottable(vm.get_vm()); vm.get_table_entry("state"); + + /** Get state table for all worldmaps. **/ vm.get_table_entry("worlds"); - // if a non-canonical entry is present, replace them with a canonical one - if (m_worldmap.m_map_filename != "/levels/world2/worldmap.stwm") { - std::string old_map_filename = m_worldmap.m_map_filename.substr(1); - if (vm.has_property(old_map_filename.c_str())) { - vm.rename_table_entry(old_map_filename.c_str(), m_worldmap.m_map_filename.c_str()); - } + // If a non-canonical entry is present, replace it with a canonical one. + const std::string old_map_filename = m_worldmap.m_map_filename.substr(1); + if (vm.has_property(old_map_filename.c_str())) + { + vm.rename_table_entry(old_map_filename.c_str(), m_worldmap.m_map_filename.c_str()); } + /** Get state table for the current worldmap. **/ vm.get_table_entry(m_worldmap.m_map_filename); - // load tux - vm.get_table_entry("tux"); + // Load the current sector. + if (vm.has_property("sector")) // Load the current sector, only if a "sector" property exists. + { + const std::string sector_name = vm.read_string("sector"); + if (!m_worldmap.m_sector) // If the worldmap doesn't have a current sector, try setting the new sector. + m_worldmap.set_sector(sector_name, "", false); + + WORLDMAP_STATE_SECTOR_GUARD; - Vector p(0.0f, 0.0f); - bool position_was_reset = false; - if (!vm.get_float("x", p.x) || !vm.get_float("y", p.y)) + /** Get state table for the current sector. **/ + vm.get_table_entry(sector.get_name().c_str()); + } + else // Sector property does not exist, which may indicate outdated save file. { - log_warning << "Player position not set, respawning." << std::endl; - m_worldmap.move_to_spawnpoint("main"); - position_was_reset = true; + if (!m_worldmap.m_sector) // If the worldmap doesn't have a current sector, try setting the main one. + m_worldmap.set_sector("main", "", false); } - std::string back_str = vm.read_string("back"); - m_worldmap.m_tux->m_back_direction = string_to_direction(back_str); - m_worldmap.m_tux->set_tile_pos(p); - - int tile_data = m_worldmap.tile_data_at(p); - if (!( tile_data & ( Tile::WORLDMAP_NORTH | Tile::WORLDMAP_SOUTH | Tile::WORLDMAP_WEST | Tile::WORLDMAP_EAST ))) { - log_warning << "Player at illegal position " << p.x << ", " << p.y << " respawning." << std::endl; - m_worldmap.move_to_spawnpoint("main"); - position_was_reset = true; + if (!m_worldmap.m_sector) + { + // Quit loading worldmap state, if there is still no current sector loaded. + throw std::runtime_error("No sector set."); } - sq_pop(vm.get_vm(), 1); + /** Load objects. **/ + load_tux(); + load_levels(); + load_tilemap_visibility(); + load_sprite_change_objects(); + } + catch (std::exception& err) + { + log_warning << "Not loading worldmap state: " << err.what() << std::endl; - // load levels - vm.get_table_entry("levels"); - for (auto& level : m_worldmap.get_objects_by_type()) { - sq_pushstring(vm.get_vm(), level.get_level_filename().c_str(), -1); - if (SQ_SUCCEEDED(sq_get(vm.get_vm(), -2))) - { - bool solved = false; - vm.get_bool("solved", solved); - level.set_solved(solved); + // Set default properties. + if (!m_worldmap.m_sector) + m_worldmap.set_sector("main", "", false); // If no current sector is present, set it to "main", or the default one. - bool perfect = false; - vm.get_bool("perfect", perfect); - level.set_perfect(perfect); + // Create a new initial save. + save_state(); + } + sq_settop(vm.get_vm(), oldtop); - level.update_sprite_action(); - level.get_statistics().unserialize_from_squirrel(vm); - sq_pop(vm.get_vm(), 1); - } + m_worldmap.m_in_level = false; +} + + +/** Load Tux **/ +void +WorldMapState::load_tux() +{ + WORLDMAP_STATE_SQUIRREL_VM_GUARD; + WORLDMAP_STATE_SECTOR_GUARD; + + vm.get_table_entry("tux"); + Vector p(0.0f, 0.0f); + if (!vm.get_float("x", p.x) || !vm.get_float("y", p.y)) + { + log_warning << "Player position not set, respawning." << std::endl; + sector.move_to_spawnpoint("main"); + m_position_was_reset = true; + } + std::string back_str = vm.read_string("back"); + sector.m_tux->m_back_direction = string_to_direction(back_str); + sector.m_tux->set_tile_pos(p); + + int tile_data = sector.tile_data_at(p); + if (!( tile_data & ( Tile::WORLDMAP_NORTH | Tile::WORLDMAP_SOUTH | Tile::WORLDMAP_WEST | Tile::WORLDMAP_EAST ))) { + log_warning << "Player at illegal position " << p.x << ", " << p.y << " respawning." << std::endl; + sector.move_to_spawnpoint("main"); + m_position_was_reset = true; + } + sq_pop(vm.get_vm(), 1); +} + +/** Load levels **/ +void +WorldMapState::load_levels() +{ + WORLDMAP_STATE_SQUIRREL_VM_GUARD; + WORLDMAP_STATE_SECTOR_GUARD; + + vm.get_or_create_table_entry("levels"); + for (auto& level : sector.get_objects_by_type()) { + sq_pushstring(vm.get_vm(), level.get_level_filename().c_str(), -1); + if (SQ_SUCCEEDED(sq_get(vm.get_vm(), -2))) + { + bool solved = false; + vm.get_bool("solved", solved); + level.set_solved(solved); + + bool perfect = false; + vm.get_bool("perfect", perfect); + level.set_perfect(perfect); + + level.update_sprite_action(); + level.get_statistics().unserialize_from_squirrel(vm); + sq_pop(vm.get_vm(), 1); } + } + sq_pop(vm.get_vm(), 1); +} - // leave levels table - sq_pop(vm.get_vm(), 1); +/** Load tilemap visibility **/ +void +WorldMapState::load_tilemap_visibility() +{ + WORLDMAP_STATE_SQUIRREL_VM_GUARD; + WORLDMAP_STATE_SECTOR_GUARD; - try { - if(!position_was_reset) + try + { + if (!m_position_was_reset) + { + vm.get_table_entry("tilemaps"); + sq_pushnull(vm.get_vm()); // Null-iterator + while (SQ_SUCCEEDED(sq_next(vm.get_vm(), -2))) { - vm.get_table_entry("tilemaps"); - sq_pushnull(vm.get_vm()); // Null-iterator - while (SQ_SUCCEEDED(sq_next(vm.get_vm(), -2))) + const char* key; // Name of specific tilemap table + if (SQ_SUCCEEDED(sq_getstring(vm.get_vm(), -2, &key))) { - const char* key; // Name of specific tilemap table - if (SQ_SUCCEEDED(sq_getstring(vm.get_vm(), -2, &key))) + auto tilemap = sector.get_object_by_name(key); + if (tilemap != nullptr) { - auto tilemap = m_worldmap.get_object_by_name(key); - if (tilemap != nullptr) + sq_pushnull(vm.get_vm()); // null iterator (inner); + while (SQ_SUCCEEDED(sq_next(vm.get_vm(), -2))) { - sq_pushnull(vm.get_vm()); // null iterator (inner); - while (SQ_SUCCEEDED(sq_next(vm.get_vm(), -2))) + const char* property_key; + if (SQ_SUCCEEDED(sq_getstring(vm.get_vm(), -2, &property_key))) { - const char* property_key; - if (SQ_SUCCEEDED(sq_getstring(vm.get_vm(), -2, &property_key))) + auto propKey = std::string(property_key); + if (propKey == "alpha") { - auto propKey = std::string(property_key); - if (propKey == "alpha") + float alpha_value = 1.0; + if (SQ_SUCCEEDED(sq_getfloat(vm.get_vm(), -1, &alpha_value))) { - float alpha_value = 1.0; - if (SQ_SUCCEEDED(sq_getfloat(vm.get_vm(), -1, &alpha_value))) - { - tilemap->set_alpha(alpha_value); - } + tilemap->set_alpha(alpha_value); } } - sq_pop(vm.get_vm(), 2); // Pop key/value from the stack } - sq_pop(vm.get_vm(), 1); // Pop null iterator + sq_pop(vm.get_vm(), 2); // Pop key/value from the stack } + sq_pop(vm.get_vm(), 1); // Pop null iterator } - sq_pop(vm.get_vm(), 2); // Pop key value pair from stack } - sq_pop(vm.get_vm(), 1); // Pop null - sq_pop(vm.get_vm(), 1); // leave tilemaps table + sq_pop(vm.get_vm(), 2); // Pop key value pair from stack } + sq_pop(vm.get_vm(), 1); // Pop null + sq_pop(vm.get_vm(), 1); // leave tilemaps table } - catch(const SquirrelError&) - { - // Failed to get tilemap entry. This could indicate - // that no savable tilemaps have been found. In any - // case: This is not severe at all. - } + } + catch(const SquirrelError&) + { + // Failed to get tilemap entry. This could indicate + // that no savable tilemaps have been found. In any + // case: This is not severe at all. + } +} + +/** Load sprite change objects **/ +void +WorldMapState::load_sprite_change_objects() +{ + WORLDMAP_STATE_SQUIRREL_VM_GUARD; + WORLDMAP_STATE_SECTOR_GUARD; - if (m_worldmap.get_object_count() > 0) + if (sector.get_object_count() > 0) + { + vm.get_table_entry("sprite-changes"); + for (auto& sc : sector.get_objects_by_type()) { - // load sprite change action: - vm.get_table_entry("sprite-changes"); - for (auto& sc : m_worldmap.get_objects_by_type()) - { - auto key = std::to_string(int(sc.get_pos().x)) + "_" + - std::to_string(int(sc.get_pos().y)); - sq_pushstring(vm.get_vm(), key.c_str(), -1); - if (SQ_SUCCEEDED(sq_get(vm.get_vm(), -2))) { - bool show_stay_action = false; - if (!vm.get_bool("show-stay-action", show_stay_action)) + auto key = std::to_string(int(sc.get_pos().x)) + "_" + + std::to_string(int(sc.get_pos().y)); + sq_pushstring(vm.get_vm(), key.c_str(), -1); + if (SQ_SUCCEEDED(sq_get(vm.get_vm(), -2))) { + bool show_stay_action = false; + if (!vm.get_bool("show-stay-action", show_stay_action)) + { + sc.clear_stay_action(/* propagate = */ false); + } + else + { + if (show_stay_action) { - sc.clear_stay_action(/* propagate = */ false); + sc.set_stay_action(); } else { - if (show_stay_action) - { - sc.set_stay_action(); - } - else - { - sc.clear_stay_action(/* propagate = */ false); - } + sc.clear_stay_action(/* propagate = */ false); } - sq_pop(vm.get_vm(), 1); } + sq_pop(vm.get_vm(), 1); } - - // Leave sprite changes table - sq_pop(vm.get_vm(), 1); } - - } catch(std::exception& e) { - log_debug << "Not loading worldmap state: " << e.what() << std::endl; - save_state(); // make new initial save - m_worldmap.move_to_spawnpoint("main"); // set tux to main spawnpoint + sq_pop(vm.get_vm(), 1); // Leave sprite change objects table. } - sq_settop(vm.get_vm(), oldtop); - - m_worldmap.m_in_level = false; } + void WorldMapState::save_state() const { + WorldMapSector& sector = m_worldmap.get_sector(); + SquirrelVM& vm = SquirrelVirtualMachine::current()->get_vm(); SQInteger oldtop = sq_gettop(vm.get_vm()); try { - // get state table + /** Get state table. **/ sq_pushroottable(vm.get_vm()); vm.get_table_entry("state"); + + /** Get or create state table for all worldmaps. **/ vm.get_or_create_table_entry("worlds"); - vm.delete_table_entry(m_worldmap.m_map_filename.c_str()); + /** Get or create state table for the current worldmap. **/ + vm.get_or_create_table_entry(m_worldmap.m_map_filename.c_str()); - // construct new table for this worldmap - vm.begin_table(m_worldmap.m_map_filename.c_str()); + // Save the current sector. + vm.store_string("sector", sector.get_name()); - // store tux - vm.begin_table("tux"); + /** Delete the table entry for the current sector and construct a new one. **/ + vm.delete_table_entry(sector.get_name().c_str()); + vm.begin_table(sector.get_name().c_str()); - vm.store_float("x", m_worldmap.m_tux->get_tile_pos().x); - vm.store_float("y", m_worldmap.m_tux->get_tile_pos().y); - vm.store_string("back", direction_to_string(m_worldmap.m_tux->m_back_direction)); + /** Save objects. **/ + save_tux(); + save_levels(); + save_tilemap_visibility(); + save_sprite_change_objects(); - vm.end_table("tux"); + /** Push the current sector into the current worldmap table. **/ + vm.end_table(sector.get_name().c_str()); + } + catch (std::exception& err) + { + log_warning << "Failed to save worldmap state: " << err.what() << std::endl; - // sprite change objects: - if (m_worldmap.get_object_count() > 0) - { - vm.begin_table("sprite-changes"); + sq_settop(vm.get_vm(), oldtop); + } - for (const auto& sc : m_worldmap.get_objects_by_type()) - { - auto key = std::to_string(int(sc.get_pos().x)) + "_" + - std::to_string(int(sc.get_pos().y)); - vm.begin_table(key.c_str()); - vm.store_bool("show-stay-action", sc.show_stay_action()); - vm.end_table(key.c_str()); - } + sq_settop(vm.get_vm(), oldtop); - vm.end_table("sprite-changes"); - } + m_worldmap.m_savegame.save(); +} - // tilemap visibility - sq_pushstring(vm.get_vm(), "tilemaps", -1); - sq_newtable(vm.get_vm()); - for (auto& tilemap : m_worldmap.get_objects_by_type<::TileMap>()) + +/** Save Tux **/ +void +WorldMapState::save_tux() const +{ + WORLDMAP_STATE_SQUIRREL_VM_GUARD; + WORLDMAP_STATE_SECTOR_GUARD; + + vm.begin_table("tux"); + vm.store_float("x", sector.m_tux->get_tile_pos().x); + vm.store_float("y", sector.m_tux->get_tile_pos().y); + vm.store_string("back", direction_to_string(sector.m_tux->m_back_direction)); + vm.end_table("tux"); +} + +/** Save levels **/ +void +WorldMapState::save_levels() const +{ + WORLDMAP_STATE_SQUIRREL_VM_GUARD; + WORLDMAP_STATE_SECTOR_GUARD; + + vm.begin_table("levels"); + for (const auto& level : sector.get_objects_by_type()) + { + vm.begin_table(level.get_level_filename().c_str()); + + vm.store_bool("solved", level.is_solved()); + vm.store_bool("perfect", level.is_perfect()); + + level.get_statistics().serialize_to_squirrel(vm); + vm.end_table(level.get_level_filename().c_str()); + } + vm.end_table("levels"); +} + +/** Save tilemap visibility **/ +void +WorldMapState::save_tilemap_visibility() const +{ + WORLDMAP_STATE_SQUIRREL_VM_GUARD; + WORLDMAP_STATE_SECTOR_GUARD; + + sq_pushstring(vm.get_vm(), "tilemaps", -1); + sq_newtable(vm.get_vm()); + for (auto& tilemap : sector.get_objects_by_type<::TileMap>()) + { + if (!tilemap.get_name().empty()) { - if (!tilemap.get_name().empty()) + sq_pushstring(vm.get_vm(), tilemap.get_name().c_str(), -1); + sq_newtable(vm.get_vm()); + vm.store_float("alpha", tilemap.get_alpha()); + if (SQ_FAILED(sq_createslot(vm.get_vm(), -3))) { - sq_pushstring(vm.get_vm(), tilemap.get_name().c_str(), -1); - sq_newtable(vm.get_vm()); - vm.store_float("alpha", tilemap.get_alpha()); - if (SQ_FAILED(sq_createslot(vm.get_vm(), -3))) - { - throw std::runtime_error("failed to create '" + m_worldmap.m_name + "' table entry"); - } + throw std::runtime_error("failed to create '" + m_worldmap.m_name + "' table entry"); } } - if (SQ_FAILED(sq_createslot(vm.get_vm(), -3))) - { - throw std::runtime_error("failed to create '" + m_worldmap.m_name + "' table entry"); - } + } + if (SQ_FAILED(sq_createslot(vm.get_vm(), -3))) + { + throw std::runtime_error("failed to create '" + m_worldmap.m_name + "' table entry"); + } +} - // levels... - vm.begin_table("levels"); +/** Save sprite change objects **/ +void +WorldMapState::save_sprite_change_objects() const +{ + WORLDMAP_STATE_SQUIRREL_VM_GUARD; + WORLDMAP_STATE_SECTOR_GUARD; - for (const auto& level : m_worldmap.get_objects_by_type()) + if (sector.get_object_count() > 0) + { + vm.begin_table("sprite-changes"); + for (const auto& sc : sector.get_objects_by_type()) { - vm.begin_table(level.get_level_filename().c_str()); - - vm.store_bool("solved", level.is_solved()); - vm.store_bool("perfect", level.is_perfect()); - - level.get_statistics().serialize_to_squirrel(vm); - vm.end_table(level.get_level_filename().c_str()); + auto key = std::to_string(int(sc.get_pos().x)) + "_" + + std::to_string(int(sc.get_pos().y)); + vm.begin_table(key.c_str()); + vm.store_bool("show-stay-action", sc.show_stay_action()); + vm.end_table(key.c_str()); } - vm.end_table("levels"); - - // push world into worlds table - vm.end_table(m_worldmap.m_map_filename.c_str()); - } catch(std::exception& ) { - sq_settop(vm.get_vm(), oldtop); + vm.end_table("sprite-changes"); } - - sq_settop(vm.get_vm(), oldtop); - - m_worldmap.m_savegame.save(); } } // namespace worldmap diff --git a/src/worldmap/worldmap_state.hpp b/src/worldmap/worldmap_state.hpp index 3635aec669a..edaaac205ca 100644 --- a/src/worldmap/worldmap_state.hpp +++ b/src/worldmap/worldmap_state.hpp @@ -1,6 +1,7 @@ // SuperTux // Copyright (C) 2004-2018 Ingo Ruhnke // 2006 Christoph Sommer +// 2023 Vankata453 // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -20,9 +21,18 @@ namespace worldmap { +/** Macro to allow quick and easy access to the current Squirrel VM. **/ +#define WORLDMAP_STATE_SQUIRREL_VM_GUARD \ + SquirrelVM& vm = SquirrelVirtualMachine::current()->get_vm() + +/** Macro to allow quick and easy access to the current WorldMapSector. **/ +#define WORLDMAP_STATE_SECTOR_GUARD \ + WorldMapSector& sector = m_worldmap.get_sector() + + class WorldMap; -class WorldMapState +class WorldMapState final { public: WorldMapState(WorldMap& worldmap); @@ -30,9 +40,23 @@ class WorldMapState void load_state(); void save_state() const; +private: + void load_tux(); + void load_levels(); + void load_tilemap_visibility(); + void load_sprite_change_objects(); + + void save_tux() const; + void save_levels() const; + void save_tilemap_visibility() const; + void save_sprite_change_objects() const; + private: WorldMap& m_worldmap; + /** Variables, related to loading. **/ + bool m_position_was_reset; + private: WorldMapState(const WorldMapState&) = delete; WorldMapState& operator=(const WorldMapState&) = delete;