diff --git a/.github/workflows/build_docker.yml b/.github/workflows/build_docker.yml index b6ff87ffa..cf165e8da 100644 --- a/.github/workflows/build_docker.yml +++ b/.github/workflows/build_docker.yml @@ -10,8 +10,14 @@ jobs: strategy: matrix: include: - - platform: win - - platform: linux + - game_version: tr1 + platform: win + - game_version: tr1 + platform: linux + - game_version: tr2 + platform: win + - game_version: tr2 + platform: linux steps: - name: Login to Docker Hub uses: docker/login-action@v1 @@ -29,5 +35,5 @@ jobs: - name: Build Docker image (${{ matrix.platform }}) run: | - just image-${{ matrix.platform }} - just push-image-${{ matrix.platform }} + just ${{ matrix.game_version }}-image-${{ matrix.platform }} + just ${{ matrix.game_version }}-push-image-${{ matrix.platform }} diff --git a/.github/workflows/job_build_tr1.yml b/.github/workflows/job_build_tr1.yml index 15780ce8e..47dd2e6ef 100644 --- a/.github/workflows/job_build_tr1.yml +++ b/.github/workflows/job_build_tr1.yml @@ -3,10 +3,6 @@ name: Build TR1X and the installer on: workflow_call: inputs: - platform: - type: string - description: "Platform to build for" - required: true target: type: string description: "Target to build for" @@ -16,6 +12,15 @@ jobs: build: name: Build release assets runs-on: ubuntu-latest + strategy: + matrix: + include: + - platform: linux + just_target: tr1-package-linux ${{ inputs.target }} + - platform: win + just_target: tr1-package-win-all ${{ inputs.target }} + - platform: win-installer + just_target: tr1-package-win-installer ${{ inputs.target }} steps: - name: Install dependencies @@ -32,17 +37,13 @@ jobs: name: Prepare variables run: echo "version=$(just output-current-version 1)" >> $GITHUB_OUTPUT - - name: Download large assets - if: ${{ inputs.target == 'release' }} - run: just download-assets 1 - - - name: Package asset (${{ inputs.platform }}) - run: just tr1-package-${{ inputs.platform }} ${{ inputs.target }} + - name: Package asset (${{ matrix.platform }}) + run: just ${{ matrix.just_target }} - name: Upload the artifact uses: actions/upload-artifact@v4 with: - name: TR1X-${{ steps.vars.outputs.version }}-${{ inputs.platform }} + name: TR1X-${{ steps.vars.outputs.version }}-${{ matrix.platform }} path: | *.zip *.exe diff --git a/.github/workflows/job_build_tr2.yml b/.github/workflows/job_build_tr2.yml index 0dc810c1c..b83a2006d 100644 --- a/.github/workflows/job_build_tr2.yml +++ b/.github/workflows/job_build_tr2.yml @@ -1,12 +1,8 @@ -name: Build TR2X and the installer +name: Build TR2X on: workflow_call: inputs: - platform: - type: string - description: "Platform to build for" - required: true target: type: string description: "Target to build for" @@ -16,6 +12,13 @@ jobs: build: name: Build release assets runs-on: ubuntu-latest + strategy: + matrix: + include: + - platform: linux + just_target: tr2-package-linux ${{ inputs.target }} + - platform: win + just_target: tr2-package-win-all ${{ inputs.target }} steps: - name: Install dependencies @@ -32,17 +35,13 @@ jobs: name: Prepare variables run: echo "version=$(just output-current-version 2)" >> $GITHUB_OUTPUT - - name: Download large assets - if: ${{ inputs.target == 'release' }} - run: just download-assets 2 - - - name: Package asset (${{ inputs.platform }}) - run: just tr2-package-${{ inputs.platform }} ${{ inputs.target }} + - name: Package asset (${{ matrix.platform }}) + run: just ${{ matrix.just_target }} - name: Upload the artifact uses: actions/upload-artifact@v4 with: - name: TR2X-${{ steps.vars.outputs.version }}-${{ inputs.platform }} + name: TR2X-${{ steps.vars.outputs.version }}-${{ matrix.platform }} path: | *.zip *.exe diff --git a/.github/workflows/pr_builds.yml b/.github/workflows/pr_builds.yml index 16112e312..ba549eff8 100644 --- a/.github/workflows/pr_builds.yml +++ b/.github/workflows/pr_builds.yml @@ -11,52 +11,34 @@ on: - '!develop' jobs: - package_tr1_linux: - name: TR1 (Linux) + package_tr1_multiplatform: + name: Build TR1 uses: ./.github/workflows/job_build_tr1.yml with: - platform: linux - target: debug - secrets: inherit - - package_tr1_win: - name: TR1 (Windows) - uses: ./.github/workflows/job_build_tr1.yml - with: - platform: win-all - target: debug + target: 'debug' secrets: inherit package_tr1_mac: - name: TR1 (Mac) + name: Build TR1 if: vars.MACOS_ENABLE == 'true' uses: ./.github/workflows/job_build_tr1_macos.yml with: - target: debug + target: 'debug' let_mac_fail: true secrets: inherit - package_tr2_linux: - name: TR2 (Linux) + package_tr2_multiplatform: + name: Build TR2 uses: ./.github/workflows/job_build_tr2.yml with: - platform: linux - target: debug - secrets: inherit - - package_tr2_win: - name: TR2 (Windows) - uses: ./.github/workflows/job_build_tr2.yml - with: - platform: win-all - target: debug + target: 'debug' secrets: inherit package_tr2_mac: - name: TR2 (Mac) + name: Build TR2 if: vars.MACOS_ENABLE == 'true' uses: ./.github/workflows/job_build_tr2_macos.yml with: - target: debug + target: 'debug' let_mac_fail: true secrets: inherit diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 596638267..b6dbe4548 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -9,24 +9,16 @@ on: - develop jobs: - package_tr1_linux: - name: TR1 (Linux) + package_tr1_multiplatform: + name: Build TR1 + if: vars.PRERELEASE_ENABLE == 'true' uses: ./.github/workflows/job_build_tr1.yml with: - platform: linux - target: debug - secrets: inherit - - package_tr1_win: - name: TR1 (Windows) - uses: ./.github/workflows/job_build_tr1.yml - with: - platform: win-all - target: debug + target: 'debug' secrets: inherit package_tr1_mac: - name: TR1 (Mac) + name: Build TR1 if: | vars.PRERELEASE_ENABLE == 'true' && vars.MACOS_ENABLE == 'true' @@ -36,24 +28,16 @@ jobs: let_mac_fail: true secrets: inherit - package_tr2_linux: - name: TR2 (Linux) + package_tr2_multiplatform: + name: Build TR2 + if: vars.PRERELEASE_ENABLE == 'true' uses: ./.github/workflows/job_build_tr2.yml with: - platform: linux - target: debug - secrets: inherit - - package_tr2_win: - name: TR2 (Windows) - uses: ./.github/workflows/job_build_tr2.yml - with: - platform: win-all - target: debug + target: 'debug' secrets: inherit package_tr2_mac: - name: TR2 (Mac) + name: Build TR2 if: | vars.PRERELEASE_ENABLE == 'true' && vars.MACOS_ENABLE == 'true' @@ -64,14 +48,12 @@ jobs: secrets: inherit publish_prerelease: - if: always() && (vars.PRERELEASE_ENABLE == 'true') + if: vars.PRERELEASE_ENABLE == 'true' name: Create a prerelease needs: - - package_tr1_linux - - package_tr1_win + - package_tr1_multiplatform - package_tr1_mac - - package_tr2_linux - - package_tr2_win + - package_tr2_multiplatform - package_tr2_mac with: draft: false diff --git a/.github/workflows/release_tr1.yml b/.github/workflows/release_tr1.yml index 9881c9baa..0c3d24dfa 100644 --- a/.github/workflows/release_tr1.yml +++ b/.github/workflows/release_tr1.yml @@ -28,51 +28,30 @@ on: default: github.ref_name jobs: - package_tr1_linux: - name: Build TR1 (Linux) + package_multiplatform: + name: Build release assets if: vars.RELEASE_ENABLE == 'true' uses: ./.github/workflows/job_build_tr1.yml with: - platform: linux - target: release - secrets: inherit - - package_tr1_win: - name: Build TR1 (Windows) - if: vars.RELEASE_ENABLE == 'true' - uses: ./.github/workflows/job_build_tr1.yml - with: - platform: win-all - target: release - secrets: inherit - - package_tr1_win_installer: - name: Build TR1 (Windows installer) - if: vars.RELEASE_ENABLE == 'true' - uses: ./.github/workflows/job_build_tr1.yml - with: - platform: win-installer - target: release + target: "release" secrets: inherit package_mac: - name: Build TR1 (Mac) + name: "Build release assets (mac)" if: | vars.RELEASE_ENABLE == 'true' && vars.MACOS_ENABLE == 'true' uses: ./.github/workflows/job_build_tr1_macos.yml with: - target: release + target: "release" let_mac_fail: ${{ inputs.let_mac_fail == true || inputs.let_mac_fail == 'true' }} secrets: inherit publish_release: - if: always() && (vars.RELEASE_ENABLE == 'true') + if: vars.RELEASE_ENABLE == 'true' name: Create a GitHub release needs: - - package_tr1_linux - - package_tr1_win - - package_tr1_win_installer + - package_multiplatform - package_mac with: draft: ${{ inputs.draft || false }} diff --git a/.github/workflows/release_tr2.yml b/.github/workflows/release_tr2.yml index f4b3e0359..b82264f43 100644 --- a/.github/workflows/release_tr2.yml +++ b/.github/workflows/release_tr2.yml @@ -28,52 +28,30 @@ on: default: github.ref_name jobs: - package_tr2_linux: - name: Build TR2 (Linux) + package_multiplatform: + name: Build release assets if: vars.RELEASE_ENABLE == 'true' uses: ./.github/workflows/job_build_tr2.yml with: - platform: linux - target: release - secrets: inherit - - package_tr2_win: - name: Build TR2 (Windows) - if: vars.RELEASE_ENABLE == 'true' - uses: ./.github/workflows/job_build_tr2.yml - with: - platform: win-all - target: release - secrets: inherit - - package_tr2_win_installer: - name: Build TR2 (Windows installer) - if: vars.RELEASE_ENABLE == 'true' - uses: ./.github/workflows/job_build_tr2.yml - with: - platform: win-installer - target: release + target: "release" secrets: inherit package_mac: - name: Build TR2 (Mac) + name: "Build release assets (mac)" if: | vars.RELEASE_ENABLE == 'true' && vars.MACOS_ENABLE == 'true' uses: ./.github/workflows/job_build_tr2_macos.yml with: - target: release + target: "release" let_mac_fail: ${{ inputs.let_mac_fail == true || inputs.let_mac_fail == 'true' }} secrets: inherit publish_release: - if: always() && (vars.RELEASE_ENABLE == 'true') + if: vars.RELEASE_ENABLE == 'true' name: Create a GitHub release needs: - - package_tr2_linux - - package_tr2_win - - package_tr2_win_installer - - package_mac + - package_multiplatform with: draft: ${{ inputs.draft || false }} prerelease: ${{ inputs.draft || false }} diff --git a/.gitignore b/.gitignore index a7b8c9b27..ff2ce821f 100644 --- a/.gitignore +++ b/.gitignore @@ -35,14 +35,3 @@ Release/ **/subprojects/dwarfstack-*/ src/tr1/subprojects/dwarfstack.wrap src/tr2/subprojects/dwarfstack.wrap - -data/tr1/ship/data/images/ -data/tr2/ship/data/images/ -data/tr2/ship/data/level1.tr2 -data/tr2/ship/data/level2.tr2 -data/tr2/ship/data/level3.tr2 -data/tr2/ship/data/level4.tr2 -data/tr2/ship/data/level5.tr2 -data/tr2/ship/data/main_gm.sfx -data/tr2/ship/data/title_gm.tr2 -data/tr2/ship/music/ diff --git a/README.md b/README.md index a1ea02926..513b81016 100644 --- a/README.md +++ b/README.md @@ -123,9 +123,3 @@ Please refer to the [detailed documentation](docs/tr2/). further refined our identity by adopting the name TR1X. Meanwhile, TR2Main follows a completely separate and unique path, unconnected to our development work. - -## Credits - -- Endless GitHub contributors. -- TR1 title screen image by Kidd Bowyer. HD assets by goblan and posix. -- TR2 HD images by Arsunt. diff --git a/data/tr1/icon.psd b/data/tr1/icon.psd new file mode 100644 index 000000000..43c951f5b Binary files /dev/null and b/data/tr1/icon.psd differ diff --git a/data/tr1/images/atlantis.png b/data/tr1/images/atlantis.png new file mode 100644 index 000000000..2f53f6967 Binary files /dev/null and b/data/tr1/images/atlantis.png differ diff --git a/data/tr1/images/credits_1.png b/data/tr1/images/credits_1.png new file mode 100644 index 000000000..696826fa2 Binary files /dev/null and b/data/tr1/images/credits_1.png differ diff --git a/data/tr1/images/credits_2.png b/data/tr1/images/credits_2.png new file mode 100644 index 000000000..5acd9d811 Binary files /dev/null and b/data/tr1/images/credits_2.png differ diff --git a/data/tr1/images/credits_3.png b/data/tr1/images/credits_3.png new file mode 100644 index 000000000..4f14f177c Binary files /dev/null and b/data/tr1/images/credits_3.png differ diff --git a/data/tr1/images/credits_3_alt.png b/data/tr1/images/credits_3_alt.png new file mode 100644 index 000000000..fe37e730d Binary files /dev/null and b/data/tr1/images/credits_3_alt.png differ diff --git a/data/tr1/images/credits_ps1.png b/data/tr1/images/credits_ps1.png new file mode 100644 index 000000000..3f8e660a1 Binary files /dev/null and b/data/tr1/images/credits_ps1.png differ diff --git a/data/tr1/images/credits_ub.png b/data/tr1/images/credits_ub.png new file mode 100644 index 000000000..191137c8d Binary files /dev/null and b/data/tr1/images/credits_ub.png differ diff --git a/data/tr1/images/egypt.png b/data/tr1/images/egypt.png new file mode 100644 index 000000000..caee294e2 Binary files /dev/null and b/data/tr1/images/egypt.png differ diff --git a/data/tr1/images/eidos.png b/data/tr1/images/eidos.png new file mode 100644 index 000000000..a48e3375e Binary files /dev/null and b/data/tr1/images/eidos.png differ diff --git a/data/tr1/images/end.png b/data/tr1/images/end.png new file mode 100644 index 000000000..1e3870d86 Binary files /dev/null and b/data/tr1/images/end.png differ diff --git a/data/tr1/images/greece.png b/data/tr1/images/greece.png new file mode 100644 index 000000000..8734ede4f Binary files /dev/null and b/data/tr1/images/greece.png differ diff --git a/data/tr1/images/greece_saturn.png b/data/tr1/images/greece_saturn.png new file mode 100644 index 000000000..78bb31de5 Binary files /dev/null and b/data/tr1/images/greece_saturn.png differ diff --git a/data/tr1/images/gym.png b/data/tr1/images/gym.png new file mode 100644 index 000000000..3ad6f4752 Binary files /dev/null and b/data/tr1/images/gym.png differ diff --git a/data/tr1/images/install.png b/data/tr1/images/install.png new file mode 100644 index 000000000..98cce3b46 Binary files /dev/null and b/data/tr1/images/install.png differ diff --git a/data/tr1/images/peru.png b/data/tr1/images/peru.png new file mode 100644 index 000000000..4142b951c Binary files /dev/null and b/data/tr1/images/peru.png differ diff --git a/data/tr1/images/title.png b/data/tr1/images/title.png new file mode 100644 index 000000000..e11218272 Binary files /dev/null and b/data/tr1/images/title.png differ diff --git a/data/tr1/images/title_og_alt.png b/data/tr1/images/title_og_alt.png new file mode 100644 index 000000000..eee8de9fd Binary files /dev/null and b/data/tr1/images/title_og_alt.png differ diff --git a/data/tr1/images/title_ub.png b/data/tr1/images/title_ub.png new file mode 100644 index 000000000..55ece1bd9 Binary files /dev/null and b/data/tr1/images/title_ub.png differ diff --git a/data/tr1/installer icon.ai b/data/tr1/installer icon.ai new file mode 100755 index 000000000..6b2b1d3bf --- /dev/null +++ b/data/tr1/installer icon.ai @@ -0,0 +1,727 @@ +%PDF-1.5 % +1 0 obj <> endobj 2 0 obj <>stream + + + + + Adobe Illustrator CC 23.0 (Windows) + 2022-07-14T11:32:11+02:00 + 2022-07-14T11:43:29+02:00 + 2022-07-14T11:43:29+02:00 + + + + 248 + 256 + JPEG + /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgBAAD4AwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYqpXd3aWdtLdXk0dtawqXmnmYJGijqzMxAAHicVfP/5lf85YaTYGXTfI8S6leUKn VrgMtqh6fukPFpSPFuK/6wyBkzEXzpP+ZPn2XzKPMsmuXY1tT8F2JCpVTv6aoKIIz/Jx4+2QtlT6 A/LP/nLSzuBFpvnyEWs+yrrVshMTHxnhWrJ/rJUf5KjJiTExfRGn6jp+pWUV9p9zFeWc68obmB1k jdfFWUkHJsERirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdiryr 8zP+cifJfk1ZrSzYa3rcZ4GztnHpROQaCeb4gvT7Khm8QMiZMhF8o/mD+bXnbz3dF9avSLFW5QaX b1jtY/D4Knmw/mck++QJtmBSG8ifll5z88Xv1fQLBpYUYLcX8tY7WGv+/JSKV78Vq3gMQLUmn0JD /wA4eaD/AIY+rT63OfMVea3yIv1VSR/d+gfjZf8AK5g1326ZLgY8TwP8wPym87eRLrhrdkTZM3GD U7eslrJ4Ueg4k/yuAfbIkUyBtQ8ifmf5z8j3nr6BftFA7criwlrJay/68RI37clo3viDSkW+qvyz /wCcl/Jvmz0rDWCug62/wiKdwbaVun7qc0AJ/lengC2TEmBi9hyTF2KuxV2KuxV2KuxV2KuxV2Ku xV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV83/85aeevNej3GlaFpWoSWOnajbSy3qwHg8pD8ApkHx8 adVBoe9chIs4h826DoOta9cDTdGspr++mkThbwIXalGBY0+yorux2HfIM30h+Wf/ADiXawelqXny YXEooy6LbORED4TzLQv/AKqUH+UcmIsDJ9E6bpmnaZZRWGnW0VnZQLxhtoEWONB4KqgAZNgicVUr uztL21ltLyCO5tZlKTQTKskbqeqsjAgj54q+ffzM/wCcTtKv/V1LyPMunXZqzaTOSbZz1PpSbtEf Y1X/AFRkDFmJPmXzH5Y8weWtSfTNdsJtPvY9zFMtOQ6ckYVV1/ylJGQpm9+/5xK89ear/wAx3vla +1CS70W202S6tbeY8zC8c8MYEbn4gnGU/DWnhk4lhIPqPJsHYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq+Uf+cyv+Um8u/wDMFN/ydyubODv+cNf+Um8xf8wUX/J3GCzfVxIA qdgO+WMENNeou0fxHx7Zh5dYBtHcsxBCG4kaSvP4xv8AIH/azAOaRN3u2UETDfDpLt/lDMzFrOkm Bh3IpWVhVSCPEZnAgiw1vmb/AJzS/wCmO/7eX/YrkZs4MY/5w9/8mZqf/bFn/wCoq1wQ5plyfYOW NbsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVcSACSaAbknFXmvnL/nIb8r/ACuX hk1L9KXybGz00C4YHwaSqwrTuC9fbImQSIl8sfnT+bQ/MjW7O9j039G29hC0EKNL6ruGflyYhUA+ Qr88gTbYBTN/+cQ9Qhs/NeuGVuIksUAB2FRMvU9tsqyZ44xZ6p4DLk+ppJpJTVjUdQB0+j781+XP KfPkojSzKWSxVPqs/ZlUU+RP9cVX4qujkeNuSGh7+B+eWY8soHZBFvnD/nMW/juT5RRSDJF+kPU4 7j4vqtP1ZsMWoGT3hHAQ8n/J38zT+XXmqXW/0f8ApKO4tXspoPU9FgkkschdW4vuPR6EZeDSCLfV fk3/AJyQ/K/zKUge/OjXz7C21ICFSf8AJnBaH5VYE+GTEgwMS9QjkjkjWSNg8bgMjqagg7ggjqDk mLeKuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KvJvzM/5yP8k+TjLYWLDXddjJVrO2cCGJ h2mnoygjuq8mrsaZEyZCL5g89/nR+YXnqZoL+9aDT5DSPSLLlFAQdqMoJeX/AGZPtkDJmIqvlj8k POmshJrqJdJs2ofUuq+qQf5YR8Vf9bjmBl7Qxw5eouRDTyPkhPzP/L+z8m3WnWdtdSXklzC0k0sg VAWD8QEQVI+knJaTUnKCSKRmx8FMx/5xip+n9a8fqkf/ACcyjtP6R72Wn5l9JWepT25AU84v5D0+ jNVDIYt88Ykndrf21xsjUf8AkbY/RmXCYlycWUDFE5Ngh7m+t7YfvG+Lsg3ORlMR5sowMuST3eq3 E9VT93H4DqfmcxZ5iXKhiAfPP/OUn/TM/wDR9/2L5sOy/wCL4fpatT0eb/lf5JsvOGuXemXVxJai Kze5iliCn41ljQcgw3Wkh6EfPMzV6g4ogjfdrw4+I0m/mb8ifOWkh5rAJrFqtTW3qswA8YW3PyQt lWLtHHLn6Synp5Dlul3kn82fzC8hXXo6ZfSLbRtSbSLwNJb1ruDExBjPiUKnM+Mu5xzF9N/lp/zk 15O81NFp+tAaBrTkIiTPW1mY7D05iF4kn9l6eALZYJMDF7ICCKjpkmLsVdirsVdirsVdirsVdirs VdirsVdirsVS7zB5i0Ty7pM+r61eR2On2wrLPKdvZVAqzM3QKoJPbFXyN+bf/OS3mHzS02leWjJo 3l81RpFPG7uV6fvGU/u0P8in5k9MrMmwRYf5E/KHzH5pCXcg/R2kNv8AXJVJaQf8Ux7Fv9bYe+YG o1sMe3OTkY8Bl7nvHlryH5N8mWbXFvFHHJGtbjVLtlMlOhJkaiovstBmmy6jJlNH5BzYY4wYv5q/ P7y1ppe30WJtWulqPVqY7cH/AFyOT/QKHxzIw9mzlvL0hqnqQOW7xTzl531vzdfx3mqGNTCpSCKF OKIpNSNyzH6Tm3waeOIVFxJ5DI7p1+UXn+x8ma9PeX9tJcWd3EIJWiI5x/EHDhWoH+z0qMr1enOW NDmGWKfCX1JoHmXQvMNgt/o15HeWx2LIfiU9eLoaMjezDNDkxygakKcyMgeSZgkGoND45C2SMGrX gh9Plv8Az/tUpl3jypq8GNoNmLEkmpPUnKibbAEBrWu6Podg9/q13HZWidZZWpU0rxUdWY02VQTk oY5SNAWVJA5vmP8AOb8y9N8639hFptvJHZaX6wjuJSA0xm9Op4D7IHpbVNTXtm80emOIG+ZcLLkE uTE/KXm/WPKuqnUtKaMTvGYZFlTmjRsysVIqD9pB0IOX5sEckakxhMxNh7X5W/5yD0G+KW+vW7aZ cHb6xHWW3J8TQc0+4/PNTm7NkN4m3LhqQeezMte8o+TPO2npPcxxXayL/o+pWrL6gH+RKtaj2NR7 ZiY8+TEaG3k2yhGYeF+e/wAmPMHltZL2yrqmkLu00a0liH/FsYrsP5l28aZudPro5NjtJw8mAx3G 4Tv8pf8AnIvzN5MaHTNWL6z5bWii3dq3FuvT9xI37I/3223hxzYCVOMYvsDyp5t8v+a9Gh1jQrxL yxl25LsyOOsciH4kcV3B/VlgLWQm+KuxV2KuxV2KuxV2KuxV2KuxV2Ksd8+efvLnkjQZNZ1yf04g eFvbpQzTy0qI4lqKn8B1OAmkgW+I/wAxvzM82fmVr6S3gf6uHKaXo9vyZIgxoAqjd5G/aalT7Cgy qUupbIxej/lt+R1tYCLVfNMa3F9s0OmmjRRd6y9pG/yfsj37aTVdoE+mHLvc7Fp63kyfz5+a/l7y kjWiUvdXCjhYREAJUbGV9wg9uvtTfMfT6OWXflHvbMmYR97wHzH5x83+db8JdyvMpNYNPgBWFPcI PCv2mqffN1iwY8I2+bhmUshpMdG/Lh2pLqsvEdfq8Rqf9k/T7vvyrJq/5rlY9F/OZHP5P8uy2f1U WixqPsyptID48zUn6a5jjUTBu3JOmgRVMJ1zyPqenK8tsDd2ta8kHxqBX7S/xGZuPUxlz2Lg5dLK PLcJVofmDWtCv0v9IvJLO6T9uM0DDrxdT8LL7MKZdPHGYqQtxoyI5PePIn/OROnXvp2PmuMWNyfh XUYgTbsf+LE3aM+4qPlmpz9nEbw38nKhnB5vYo76yksxex3Eb2ZT1Rcq6mIpSvPmDx4071zW8Juu rfbybz5/zkLo2mepZeWUXVL4VU3jV+qof8mlGl+ii+5zYYOz5S3nsPtaJ5wOTwLzF5o17zFfG+1m 9kvJ9+HM0RAf2Y0FFQeyjNvjxRgKiKcaUieaM0LyXqmp8ZZB9VtDv6sg+Jh/kL1PzO2V5dRGPmW/ FppS8gzq08neXre0NsbVZ+X25ZfikJ8Q23H/AGNMwZaiZN258dNACqSHWPy4UhpdKloev1eY7f7F /wCv35fj1f8AOcfJov5qTaJ5l83+StRP1OWS0etZbST4oJe3xJ9lv9Yb+By/Jix5hvu4lyxnue9/ l/8AnDoXmgx2N2Bp2tNsLZzWOU/8Uue/+Sd/CvXNLqdDLHuN4uXjziW3VKfzI/JKw1hZdT8uolnq 27yWgokE5707RufHoe9OuW6XXmO094scunB3HN5N5J89ecfy18zPcWJe3njYR6lpdwGEUyrX4JU2 3FfhYbjtm8hMEWOTgyj0L7a/Lf8AMvy55+0IanpEnGaPit/YSEetbyEV4sO6n9lxs3zqBcDbURTL MKHYq7FXYq7FXYq7FXYq7FUl84+b9E8o+XrrXdZm9Gzthso3eSRtkijX9p2PT7zsCcBKgPhbz957 8z/mX5uF7cRu8krehpelwlnWGMn4Y0Hdm6u1Nz4CgFM5gCzybox6B7V+V35VWflW1S/1BUuNflWr ybMtuCKGOI+O/wATd+g268/q9YchofT97sMOHh3PNjH5o/nX9XebRPK8oM6kx3eqLuFPQpB4nxft 28cyNJoL9U/k15tR0i8r0DypqOuTG6nZo7VmLSXL1LyMTU8a/aJPUnNjlziGw5teHTme/R6Ppej6 fpcHo2cQjB+2/V2I7s3fNdPIZGy7THjjAUEbkGbTMqKWYhVG5Y7ADFUh1Dzv5fsyVExuZB+xAOX/ AAxov45fDTTPk489VCPmwPzFremapKZYNOFrNWpnD7v/AKygcf4++Z2LHKPM26/NljPkKSXL2hFr q2qrpzaYt5ONOdxK9kJG9EuP2jHXjX3pkeAXdbps1SEySE20DV9O02f1rnTxeSA/A7PTh8lIK1+e U5cZkNjTdiyRibItn2n+e/L92QrytayH9mcUH/BCq/ecwZ6aY83YQ1UD5J/HJHIgeNg6NurKagj2 IzHIcgG12KULqOmWOo25gvIVlj7V6qfFSNwclCZibDCcBIUXnHmPybe6SxurUtPZA19Qfbj8OdP+ JD8M2OHUCex5uszaYw3G4ei/lf8AnbJE0Oi+api8RolrqrmrKegWc91/y+3fxGDq9B/FD5fqZYdR 0kzz8yfyy03zhYG4t+FvrcSf6LefsyAbiOWnVT2PVfwOHpdWcRo/S3ZcQkPN4L5W8z+bPy185C9t la11GycxXtjLURzRn7UUgB+JGG4I9mGdDCYIscnXSjWxfc/5f+fND88eW7fXNIf4JPgubZj+8gmA BeJ/cV2PcbjLwbaiKZHhQ7FXYq7FXYq7FXYqsuJ4LeCS4uJFighVpJZXIVVRRVmYnYAAb4q+Gvz1 /Nu4/MDzMUs3dPLenM0emQGo9Q9GuXU/tSfsg/ZXbryrVI22RFPQvyc/LFPL9kmt6tD/ALnLpaxR uN7aJh9kDtIw+14dPGug12r4zwx+kfa7HBi4dzzY5+cv5sFmn8saBNRVPDUr6NupH2oIyO3Zz9Hj l+h0f8cvgGvPm6BgHk/yd+kON/fqRZA/uouhkIPU/wCT+vMzUajh2HNOm03FueT0dEREVEUKigBV UUAA6AAZrnZAN4pYvr3nvT9PLQWYF3dDYkH92p92HX5DMnFpjLc7BxMuqEdhuWBarr+q6o5N3OWT 9mFfhjHyUfxzPhijHk6/JllPmUvAJIAFSdgBljWzfy/+Sf5qa8ofT/Ld2ImAKzXSraIVP7StcGLk P9WuHhKOIM6sP+cQvzLnQPc3ul2df91vNM7jp/JCy/8ADYeBjxpp/wBCa+av+pgsf+Rc39MPAvGl t9/ziB+ZEKs9rqGl3YFeKCWeNz9DQ8f+GwcC8bCNf/Ir82NCUyXnly5lhFT6tnxvF4jqx+rmRlH+ sBgMSniDBHR43ZHUo6khlYUII6gg4GSN0zW9T0yTnZztGK1aPqjfNTtlc8cZcw2QyyjyLO9B8/2V 4Vg1AC0uDsJK/umPzP2fp+/MHLpSNxu5+LViW0tiywEEVG4PQ5iuY0QGBBFQdiD0IxV5/wCcfJgt lfUtNT9wKtcW4/Y7ll/yfEdvl0z9PqL9MnXanTV6osq/J382W02SLy7r0xOnyHjY3sjf3BOwjcn/ AHWex/Z+XSjXaPi9cefVrwZq2LPfza/LSHzTppv7CMLr1oh9Eig9dBv6Tnx/kJ7+xzC0Wr8M0fpL dmxcQsc3kf5PfmfqX5debVuZBI2k3LCDWrDcEoDTmqn/AHbEalfpXaudFGTrpB93adqFlqVhb6hY zLcWV3Gs1tOhqrxuOSsPmDlrUiMVdirsVdirsVdir51/5yu/NJ7Cxj8i6VNxur5BNrMiHdLcn93B UdDKRyb/ACadmyEiziHlv5F/l8uq6h/iTUY+Wn2D0so2G0twu/Kn8sfX/W+RzU9oanhHAOZczT4r Nlm350/mOdA079CaZLx1i+Q+rKp+K3gOxb2d+i+G58MxNBpeM8R+kfa3Z8vCKHN84ZvnAT7yv5qu dGmET1lsJGrLF3X/ACk9/bvlGbAJ+9yMGcwPk9Pj1GykshfLMptCvP1q0XiOtc1hgbrq7UTFX0ee eaPO1xfl7SwJhsd1aQbPKP4L7ff4ZsMOmEdzzdbn1RlsOTF4opJZEiiQySSEKiKCWZiaAADqTmU4 j3b8t/8AnFPzLraRah5tmbQtOejLZKoa+dT4q3wwf7IFvFckIMDJ9I+Tfyq8g+T0Q6Fo8MN0oob+ QetdN4/vpOTivcLQe2TAYksswodirsVdirsVY15v/LXyP5viKeYNIgvJSKLd09O4WnTjPHxkA9uV PbAQkF84/mP/AM4ma3piS6h5LuW1e0WrHTJ+KXar/kOOMcvyop8A2RMGQk8Buba5tbiS2uYnguIW KTQyKUdHU0ZWVqEEHqDkGbIfLHnO60tktromew6AdWjHint/k5jZtOJbjm5WDUmGx5PSYr6zlsxe RzIbUrz9atF4jqST0pmuMSDXV2YmCL6POfN3nCTUnazsmKaepozDYykdz/k+A+/Nhg0/DuebrdRq OLYcmL5lOI+gfyO/Mg6nar5Z1WWuoWqV0+ZzvNCo/uzXq8Y6eK/I10faGl4Txx5Hm5uny3sUl/Pn 8vlt5T5s02OkMzBdVjUbLI2yzU/yzs3vQ9zl3Z2pv0H4MdRi/iDMf+cT/wA0mEj+QdVmqrc59Cdz 0Iq81sPnvIv+y9s3MS4Mg+ncmwdirsVdirsVSnzZ5k0/yz5b1HX9Qalpp0LTOK0LkbJGtf2nchV9 ziVD4DL675/88tJO/qaprl0XlkoSqBjViB/JFGNh/KMxcuQQiZHo3wjZoPpi9utF8ieSy6rxsNKg CQxVAaR+irX+aRzufcnObiJZsnmXZkiEfc+Udb1i/wBZ1W61S/k9S6u3MkjdhXoq+CqNgPDOlxwE IiI5B1kpEmygsmh2Kqq3l0tq1qJWFs7B2hqeJYdDTI8Iu+qeI1XRHeW/LeteZdattG0a2a71C7bj FEvQDuzHoqqN2Y7AZIC2JL7O/J78hPL3kO3i1C9Can5pZay37CscBYfElsrfZA6cz8TewPHLQKay beqYUOxV2Ksf83ef/J/lCGCXzFqSWIuWKwIUklkegqSI4lkfiO7UpkJ5Ix5lydNo8uYkYxdfjqxj /oYn8nf+pg/6c77/AKoZX+Zh3uX/ACLqv5n2x/W7/oYn8nf+pg/6c77/AKoY/mYd6/yLqv5n2x/W z7TdRs9T0611Kxk9ayvYY7m1loy84pVDo3FgrCqsDQiuXA2LddOBhIxPMGkThYOxV5x+bX5IeWPz BtGuHUaf5iiWlrq0a7tQbR3CinqJ/wAMvY9QQRaQafFfnDyfr/lHXrjRNctjb3sG4I3jkjP2ZYn/ AGkamx+g0IIyohsBSxb+8WzeyWZhayMHeEH4Sw75HhF31Z8ZquihkmLsVRGn6headfQX1nKYbq2d ZYZV6hlNRkZREhR5JBo2+sPLOt6V558mpcSxK8N7E1vqFr1CSU4yJ496qetCDnNZccsOSu7k7KEh OL5q1zTdX8i+dXit5mivdLuEnsLsbEqpDwyDtuKVHjUZ0ODKMkBIOuyQ4TT7x/L/AM42XnHyfpnm G1oovYgZ4Qa+lOnwzR+PwuCB4ihzLBaCGQ4UOxV2KuxV82f85g+d2itNK8mWr0a5/wByOpAf77Ql LdD7M4diP8lchMs4hiH/ADjv5UCwXnme4T45CbSwJ7KKGVx8zRQfY5o+0824gPeXP0sOqUf85Beb 2vNYg8t20lbXTwJrwDo1y4+EH/UjP3sfDLezcFR4zzP3MdTOzTyPNo4rsVdiqK0vS9Q1XUbbTdOg a5vryRYbaBN2d3NABir7m/Jj8n9K/LzQQGCXHmK9RTqmoCvjyEMVfsxp/wAMfiPYC0Cmom3ouFDs Vdirzv8ANr85dE8hWJgTjfeYZ0raacDsgPSWcg1VPAdW7dyKM2YQ97s+zuzJ6g3ygOZ/U+O/Mvmb W/MusT6vrN011fTn4nbYKo6IijZVXsBmtlIyNl7fBghiiIwFAJXkW52Kvvn8tf8AyXPlX/tj2H/U LHm4xfSPc+c67+/n/Xl97I8m4rsVdirCPzY/KrQ/zC8vtZXYEGqW4ZtL1ICrQyGlQafajfiAy/SN wMBFpBp8I+YNB1Xy/rV5o2rQG21CxkMVxE3YjoQe6sKMrDYjcZUQ2Apfil2KuxV6f+Q3m86V5mOj XMnGx1eiRg9FuV/uz7cxVPc0zXdo4OKHEOcfucjTzo13sy/5yE8qC70a38x26f6RpxEN2R1NvI1F J/1JD/wxzE7MzVIwPVt1MLFo7/nD/wA7tBqWp+Tbl/3N2p1DTgegmjASdB/rx8W/2JzfwLr5B9TZ Ng7FXYq7FX5+fmp5ml85fmXrGp25M0d1dfV9PA/ahipDBTt8SqG+ZymR6tsQ+i7KCy8meR0jeht9 Gsy8pG3N0Us5HvI9fvzl5E5cn9Yu1FQj7nyVqWoXOo6hc3903O5upXmmbxZ2LH9edNGIiAB0dYTZ tD5JDsVdir6q/wCcUfysSz08+fNVhreXgaLREcbxwbpJPv8AtS7qv+TXs2WRDXIvovJMXYq7FXkP 51/nrbeTVk0LRQtx5ndAXZhWK0V1DK7gijuVNVTp3bbY42fPw7Dm7vsvsk5/XPbH975J1HUb7Ur6 e/v53ury5cyT3ErFndj1JJzXE3uXs4QEQIxFAIfAydirsVffP5a/+S58q/8AbHsP+oWPNxi+ke58 5139/P8Ary+9keTcV2KuxV2KvB/+cpvytTXPL3+MdNi/3LaLH/p6qN5rIGrMf8qCvL/V5eAyMgyi XyFlbY7FXYqvt55reeO4gcxzQsskUi9VZTVSPkRgIsUVBfXOl3Vl5z8jxSygehrFmY7hRvxdlKSA f6jg0+WcxMHFk/ql2gPHH3vmnyprF55H/MOx1CSqz6Jf8btF6lI3Mc6D/WTkudPCYIBHV1co9H6G wyxTRJNEweKRQ8bruCrCoI+Yy9pXYq7FWKfmt5hPl78uPMOro3CaCykS3cbUmmHown6JJFwHkkPi j8n9GGq/mBpiOtYbRmvJe/8AcDkn/JTjmv1uThxHz2crBG5B65/zkDrZsvJsWnI1JdUuFRh/xVD+ 8f8A4cIM1nZuO8l9zlamVRrvfOGb5wHYq7FWRfl75RuPN/nPSfLsNVF9OFuJFpVIEBeZxXaqxqxH vhAtBL9DbGytLCyt7GziWC0tY0gt4U2VI41Coq+yqKZa1K2KuxV2KvmD/nLPywLfXdI8yQpRL+Fr S6YdPVtzyQt7sj0/2OYGrjuC9b7O57hLGehv5/j7XgWYb0bJl/L3zEvkmbzlcw/VtHWWOC1aWoe4 aRqco1p9habt47Cu9LPDPDxdHE/OQ8XwhvL7mM5W5bsVffP5a/8AkufKv/bHsP8AqFjzcYvpHufO dd/fz/ry+9keTcV2KuxV2KrZI45Y2jkUPG4KujAFWUihBB6g4q/Pz83PJDeS/P8AquhopFksnr6a xqa2s3xxCp3JQHgT4qcqkN20Fh+BLsVdir6B/wCcdNbNxoGo6O7VewnE0QP++7gdB8njY/Tmj7Ux 1IS73N0stiHn/wCeejDTvP8AcTIvGLUoo7tadORBjf6S0ZJ+eZ3Z+TixDy2adRGpPrb8h/MJ178p /L127cp7e3+pTV3PK0YwAsfFkRW+nNnHk4cubPsKHYq8T/5y31c2f5Yw2Kn4tT1CGJ1/4riV5if+ DjTIy5Mo83jn/ONunB9S1rUiN4YYrZG/4zMXYf8AJFc0vaktohz9KNyUF/zkXqZn80afpwNY7O09 Qjwedzy/4WNcn2XCoE95Y6o708mzZuM7FXYq+j/+cOfK6zaprvmeZK/VYksLRiKjnMfUmI91VEHy bJwDCZfU2TYOxV2KuxV53+fvlka/+WGqqic7rTQNRttqkG3qZKfOEuMp1EbgXZ9j5/D1Ee6W3z/b Tyb8lv8AnHiXUPQ8x+coDFYbSWWjuCrzdw842Kx+CdW70H2sbBpr3k7ntTtnhvHiO/WXd7noX/OT MUcX5TyxRII4o7q1VEUAKqgkAADoBl2q+h1vYRvU/Avj7Na9s7FX3z+Wv/kufKv/AGx7D/qFjzcY vpHufOdd/fz/AK8vvZHk3FdirsVdirsVfNn/ADmR5WR7HQvNUSgSRSPpl23crIGmg+hSkv8AwWQm yiXy7kGx2KuxV6X/AM4/6mbXz0bQn4NQtZYuPi0dJgfoWNs1/aULx33FyNMakyj/AJyS04G20TUl FCjzW0h7nmFdPu4NmN2VLeQbNUOReh/84c6uZ/Jmt6UzVNjfrOo3qFuYlUD5coGObyDr5vf8mxdi r5p/5zPviLbypYg7O97O6+HAQqpp/s2yE2cEm/5xytQnlPUboijTXxSu+6xxRkfi5zn+1D6wPJ2O lHpLy/8AOe7Nz+ZGrmtViMMS96cIEBH/AAVc2OhjWIOPnPrLCczGl2KuxV9tf84taQth+UNjcAUb VLq6u3Heok+riv8AsbcZZHk1S5vW8kh2KuxV2KuIBFD0xV2KvJv+cnv/ACVc/wDzGW3/ABI5j6r6 Hc9g/wCMD3F8eZrHt3Yq++fy1/8AJc+Vf+2PYf8AULHm4xfSPc+c67+/n/Xl97I8m4rsVdirsVdi rzf/AJyK0gan+T+voFDS2qRXcRIrx9CZHcj/AJ58hgPJI5vhLKm12KuxVlH5X3htPzB0KUGnK6WG taf3wMVPp55jauN4pe5swmpB7Z/zkBaibyCJaVNteQy132qHj/5mZqezTWX4OXqR6UP/AM4Z3xTX fM1jXae1t5+Pj6MjrX/ktnRQdbN9U5Ng7FXyp/zmZM51/wAtQmnBLS4ceNXkUH/iAyE2cEZ+QMYT yArA7yXc7H5/Cv8AxrnOdpH978HZ6b6Xh35lSNJ5+15m6i8lXbwU8R+AzcaUfuo+5w8v1FjWZDW7 FXYq+/fyPtRbflL5WjFKNYpL8IoKykyH6fj3y0cmo82cYUOxV2KuxV2KuxV5N/zk9/5Kuf8A5jLb /iRzH1X0O57B/wAYHuL48zWPbuxV98/lr/5Lnyr/ANsew/6hY83GL6R7nznXf38/68vvZHk3Fdir sVdirsVY1+ZsAn/LfzVCQDz0i+A5CoDfVn4n6DviVD878pbnYq7FU38nyNH5u0SRftJqFqwr0qJl OVZx6Je4sofUH0Z+d0Yf8tNVYneNrZh8/rMa/wDG2aHQH98Pj9zn6j6CxP8A5w7mcfmLqsIpwfR5 XPjVLq3A/wCJnOlhzdZJ9fZY1uxV8rf85mQU13yzPy+3a3KcadOEiGtffnkJs4Ij/nH+Qv5B4kU9 O8mUe+yN/wAbZznaQ/e/B2Wm+l4j+ZkRi8/68pNa3kj/APBnl/HNxpD+6j7nEy/UWM5kNbaozGig sRuaCuBWsKvvn8ibwXn5ReV5QahbP0a0K/3EjRUoadOHXvlo5NR5s8wodirsVdirsVdiryb/AJye /wDJVz/8xlt/xI5j6r6Hc9g/4wPcXx5mse3dir75/LX/AMlz5V/7Y9h/1Cx5uMX0j3PnOu/v5/15 feyPJuK7FXYq7FXYqxf807kW35aea5ieJGk3oUgV+Jrd1Xb/AFiMBSH55ZU2r2hmVA7RsqMKqxBA I9jgsJorMKE48mRet5w0KKtPU1C1WvWlZlFcqzmscvcWWP6h730V+d8hT8tdUUCvqPbKfb/SI2/4 1zQ9nj98Pj9zn6j6Cxb/AJw7gr+YWrz8vsaRInGnXncwGtfbhnSwdZJ9eZY1uxV83f8AOZ1iz6Z5 Wvx9mCa7gY+8yROP+TJyE2cGO/8AOOF2G8s6pad4b31T8pYkX/mVmg7Uj6wfJ2OlOxeb/nbZm2/M fU2p8NwsEyfIwop/4ZTmfoJXiDj6gessn0jTtIfT7W4jsoFMsKOWWJATyUHwzFyTlZFl2eOEeEGg mXpR+mYwoCEEFRsN/lldttPELiFoLiWB/tROyN81NDm6BsW6IijT7N/5xR1kX35UR2ZI5aTe3NqF 78ZCLkH6TOctjyapc3smSYuxV2KuxV2KuxV5N/zk9/5Kuf8A5jLb/iRzH1X0O57B/wAYHuL48zWP buxV98/lr/5Lnyr/ANsew/6hY83GL6R7nznXf38/68vvZHk3FdirsVdirsVeW/8AOTGsppn5Qauh YLNqL29lBXuXlV3H/IqN8EuSY83w5lTa9s023Ntp1rbnrDDHGfmqgZpZmyS72AqIC27tNOMMktxb RSKilm5orbAV7g4xkehRKMa3DAvyptGvPzF0SMDdbgzGm39yjS/8aZsNZKsUvc6jCLmHsf8AzkJd CHyNDDX4rm+iSnsqSOT/AMKM1XZgvJ8HK1J9Lf8AzhjYltS80X9DSGG0gB7EyvK57dvSzooOtm+p Mmwdirxv/nK/RzfflQ94q1bSr63ui1N+LlrYivhWcZGXJlHm8N/5xv1IRa7q2mk0+tWyTqPEwPx/ VNmn7Uh6Qe4udpTuQ3/zkfpZi1zStUC/Bc2z27EfzQPy3+ib8Mey5+kx811Q3BQ3ke7+s+XLYE1a AtC3+xNR/wAKRg1MamXN0srgE+yhyHk3nWx+qeYrmgok5E6e/P7X/D1za6eVwDp9TGpl7L/zh95q jsvNWq+W534rq9utxagnrPaciyqPFopGb/Y5lQLizD60ybB2KuxV2KuxV2KvJv8AnJ7/AMlXP/zG W3/EjmPqvodz2D/jA9xfHmax7d2Kvvn8tf8AyXPlX/tj2H/ULHm4xfSPc+c67+/n/Xl97I8m4rsV dirsVdir5f8A+cx/NavdaF5UhkqYVfUr1BuOT1ht/pAEu3gRkJlnAPnry9Ym+1qztqVVpAX/ANRP ib8BlGWXDElvwx4pAPZc07u0o823f1Xy7eyVozx+kvzkPD9Ry3BG5hp1EqgUH/zjzpZufONzflax 2Fo5DeEkzBFH0pzy3tOdYwO8uu0w9Vp3/wA5JakC+iaYp3AmuZV+fFEP4PlXZUPqLPVHkHqP/OH+ jm28g6nqbrR9R1BlQ06xW8aKDX/Xd83cHAk93yTF2KpB+YHl7/EfknXNEC8pL6ymigB/39wJiP0S BTgKQ+CPI/mmTyr5nttY9EzLAJEltgeBdXRl4kkGnxEHp2zD1GHxIGLkY58JtFeefzC1/wA4XCPf lI7KBma1s4hRY67VYn4malNyaeFMjp9NHENuacmUy5pLpOu6npMvqWcpVSavE26N/rL/AB65bkxR lzXHllA7PQdC886ZqHGG5pZ3R24uf3bH/Jf+BzAy6aUeW4dji1UZbHYoP8x9LM1jDqMYq1seEpH8 j9D9DfryeknRrvYayFji7mIeU/Md95Z8y6br9if9J06dJ0WpAcKfjjYj9l1qrexzYAusIfoj5f1z T9e0Sx1nTn9Sy1CFLiBu/FxWjDsy9GHY5c1I/FXYq7FXYq7FXk3/ADk9/wCSrn/5jLb/AIkcx9V9 Duewf8YHuL48zWPbuxV98/lr/wCS58q/9sew/wCoWPNxi+ke585139/P+vL72R5NxXYq7FXYqh9R 1Cz06wudQvZRBZ2cTz3MzdEjjUs7H5AYq/PP8wfN915w85ap5iuAV+vTFoIm6xwIAkMe23wxqoPi d8qJttApNvy20stLcam4+FB6MJ9zu5+gUH05g6yfKLsNFj5yT7XfOelaXyiRvrV2NvRjOyn/AC26 D5dcx8WnlLyDkZdTGHmXn2teYtU1d63UlIQapAmyL9Hc+5zYY8UYcnW5c0p80w8k+fdd8n3sk+mF HhuOAu7WVapKErx3FGUrzNCD9+Q1GmjlFFGPIY8l35h+c283+Yf0r6Jto1gigigZuZQIOTDlQVHq O1NsdNg8KHCuXJxG327+Tnl0+Xvyx8u6Y6cJltEnuF7iW5JnkB91aQjM0cnGPNmWFDHfOn5g+UvJ en/XvMOoJaqwPoW4+OeUjtFEvxN8+g7kYCaSA+WfzN/5yh81+ZPW07y0H0HRnqhlVh9dlU/zSLtF X+WPf/KOQMmYi8h0TQtb1/Uo9O0ezm1C/mPwwQKXY77s1Oiiu7HYd8iyfSn5Zf8AOJltB6WpefJh cTbMuiWzkRr7TzLQv/qx0H+URkxBgZJz+ZP/ADin5a1lZL/yfIuialQk2T8mspDToAKvCT4rVf8A JwmKBJ8v+bfJPmnyjqR07zDp0tjcbmNnFY5FH7UUi1Rx/qn55WQzBW6b5pv7S3ezn/0vT5VMb28h 3CnY8G6r7dsonhBNjYuRDOQKO4SZuPI8SStfhJ2NPfLmh9Gf84qfmuljdHyHq8wW2u3Muhyudknb eS3qdgJPtJ/lVG5YZZEsJB9UZNg7FXYq7FXYq8m/5ye/8lXP/wAxlt/xI5j6r6Hc9g/4wPcXx5ms e3dir75/LX/yXPlX/tj2H/ULHm4xfSPc+c67+/n/AF5feyPJuK7FXYq7FXzV/wA5W/mskcA8gaTK DLJwm12VD9lRR4rb5ts7+3EdzkJFlEPl/INicz+Z74afFptiTaWcS8W4GkkhO7MzDxPYZSMIviO5 bjnPDwjYKflryr5i8zammmaDYS6heP8A7riWoUfzO5oqL/lMQMuaX03+Wf8AzidpGnejqXneZdTv BRl0mAkWqHrSV/haUjwFF/1hkxFgZIf8zP8AnEywu/W1LyLMLO4NXbRrliYGPWkMpq0fsr1HuoxM VEnzRr3l3XPL2pSaZrdjNp99F9uCdSpp2ZT0ZT2Zag9sgzer/lp/zk15u8rGGw1zlr2hiiqJW/0u FRtSOY15hf5ZPvXJCTExfU/kf8yPJ/naw+t+X79J3UA3Fm/wXMNdv3kR+Ib7chVT2JyYNsCKfn7r GtavrWoS6jq15LfX05rLcTuXc+1T0A7AbDKm17J+Vn/OL/mDzNDbax5lmOj6HOqywwx8XvJ42AKs o+JIlYHZmqf8nvkhFiZPqbyf5F8qeT9OFh5e06KyiIHqyKOU0pFfillarudzSp27UGTAYEp2J4SK iRafMZDxYd4+a0XetD/Ov3jHxod4+a0Uv17Q/LvmDTZNM1u1t9QsZftwTgMK9mU9VYdmG4x8WHeP mtF80/mZ/wA4pz2vral5Euhd2+7Po1zIomQdf3MxIWT2V6GndjlZnDvHzbAS+dp4JoJpIJkMc0TF JI2FGVlNCCPEHCClqKWWGVJYnaOWNg8ciEqyspqCCNwQcKvs78gPzyt/OunpoWuSrF5qs4wObFVF 7Go/vYxt+8UD94o/1htULZE21EU9lySHYq7FXYq8m/5ye/8AJVz/APMZbf8AEjmPqvodz2D/AIwP cXx5mse3dir75/LX/wAlz5V/7Y9h/wBQsebjF9I9z5zrv7+f9eX3sjybiuxV2KvK/wA8vzssPIOk tY6e6XHmm9RhaW4KsLdSKC4mXfYV+BSPiPtXATSQLfEd3d3V5dzXl3K891cO0s88hLO8jnkzMx3J JNTlTapYq+gPyy/5xR1vVRFqXnWV9IsGo6aZFQ3kgO/7wnksI9t27EKckIsTJ9O+WPKXlryrpa6b oNhFp9mm7LGPicgU5SSNV3b/ACmJOT5MCbTX1of51+8ZDxod4+a0XetD/Ov3jHxod4+a0Uk82eUP KPm3TTp3mCygv7ff0y9BJGT+1FIpDxn3U4nLA9R81AL5f/Mz/nF3W9DSfUfKVz+mtKQF2spCq3sQ G+1KLMKD9ni3+SeuVmcO8fNsBLxLS9V1PSr6K/0y6lsr6A8obmB2jkU9NmUg5JKFxV+jHkP/AJQb y7/2zLP/AKh0y0NJTa8lCRFf2n2+jvmPqsvDGhzLKItL81Dc7FXYql+sXfpQeip/eS9fZe/39Mpz ToU3YYWbfAXmT/lIdU/5i5/+TrZvMf0j3NMuZTjUvKTvoVnq1gvIm3RrqEdfs7yL/EfTlMM/qMT3 uTPT+gSHcxu0u7qzuobu0me3urd1lgniYo6OhqrKwoQQRscynEfWP5N/85OabrUcGh+dpo7DWAAk OrNxjtrkj/fvRYZD/wAAf8nYZYJNZi9/BBFRuD0OSYuxV2Koe/07T9QtzbX9rDeW5IYwzxrKlR0P FwRtgIB5soTlE3E0Ut/wP5L/AOrBpv8A0iQf80ZHw49wbvzeX+fL5l3+B/Jf/Vg03/pEg/5ox8OP cF/N5f58vmU3gggt4I4II1igiUJFEgCoiKKKqqNgANgBk2gkk2V+KHYq8O/OT/nJTR/LEc+i+VZI 9T8xbxy3QIe1tG6GpG0sq/yDYH7XTiYmVMhF8i6rquo6tqNxqWpXD3d/duZbi4lNWdj3P8B2ytsT bR/K01zpd5qlyDHawwSvAvQyOqEg/wCqD9+Y+TOBIRHO3Ix4CYmR5Ux/Mhx36a5c0oS/looiH7W7 H28PpzD1mWhwjmWcAg81ba7FXDCFSnX7vhbNbofidSX9lp/HKMs6oN2KF7vz4apHL6D8/wC3Ogcd zCh9juMVfov5EIHkXy8TsBpdnU/9G6ZbdC2pEzSmSQt26Ae2aXNk45W3AUsypLsVWu6ohdjRVFSc SaUC2MXNw9xM0r9WOw8B2Ga+cuI258I0KfDfmIj/ABDqZ7G7n/5ONnTY/pHucCXMvUvLX/KP6f8A 8YE/VmrzfWXcYPoHuY95o8ipcl7zSlEc53kttgrnxTsp9umZGHU1tJxs+lveLz+WGWGRopUaORDR 0YEEH3BzPBt15Fc3pP5a/n/568jiOzSUatoaUH6Lu2YhF8IJd2i+W6/5OTEmBi+kPJP/ADk1+Wvm OKKLULo+X9SYASW9+aQ8qb8LoD0+IPd+B9skJBgYl6raXlpeW6XNpPHc28grHNEyujDxDKSDkkKu KuxV2KrJ7iC3hee4kWGGMcpJZGCqoHcsaAYq8w85f85I/lf5bSSOHUP03fqPhtdNpMld6criohAr 1oxI8MBkEiJfOP5kf85G+evOUctjbsND0SQFXsbRyZJEP7M9wQrOO1FCqR1ByBkzEXlQBJAAqTsA MiyZt5X8hySFLzV0KRijR2h2ZveTwH+TmFm1PSLnYNLe8vky/XFVNA1BVAVFtZgqjYACM7DMTH9Y 97m5foPueNHNw6R+mckixoXboP8AOmWSkIiy0gJWzM7F2+0xqc0mSZlKy3gU1kEuxVZLKsUTSN9l QScBNC0gWaYreTPN60j/AGmBP4Zg8VytzgKFPg9SK0PQ7HOpdc3x2p1/lI70xV+hnlCankXy5GOp 0yyLfL6umY2ry0OEMYje0wzXNjsVdiqT61ejl9UQ7ijSfrA/jmNnn0cjBDqlWYzkvh3zB/x3tS/5 ip/+TjZ1OP6R7nWy5vUvK8kcnl6wKMGCwqrUNaMooR9GavMPWXcYD6AmmVNyWaz5c0vV46XUVJgK JcJtIPp7j2OW48socmrJhjPm828y+W5dEuI0adJo5gTERUPRafaXt18c2OHNxh1efD4Z5pNlzSj9 I1/XdGmM+kajdadMestpNJAx+ZjKnG0UzXTf+chPzi09BHD5kmlQU2uYoLkkDxeaN3/HDxFHCEw/ 6Gd/Ob/q9R/9Idr/ANU8PGV4QgdQ/wCciPzjvozHL5jliQ9reG3gI+TxRo/44OIrwhhes+ZfMeuS iXWdUu9SkH2Wu55JiPlzZqfRgtNJbilMdC0SfWL36rDIkbBS7M5/ZBANAOp3yvLkEBZbMWIzNB6T oXlHStJ4yKvr3Y63Eg3B/wAhei/r981uXPKfudpi08Ye9O8pb0v8wui6FqJdgoNtKoJNNyhAH0nL MX1D3teb6D7njWbh0j9KL6WrCMHZd2+eYmsy2eEMYDqhcwGx2KuxVJtYu+UggU/Cm7+7EfwzGzT3 pycMNrSib+6f/VP6sojzDeXwjnVOtbBZTtsR1+jFX0j+WH/OR9kul6doPmILYyWkMdrb6iAWgdYl Eaer+1G3ECp3XvVc1upwZLMo7tuPh5F7VB5hmuIUngljmhkUNHLHRlZTuCrA0IOaw5pDm5HgxX/p m98V+7B48l8GLf6ZvO/E/R/Q4fHkvgxQM0xZnllYCtWdjsB45SSSbbQKFPKvPf5++XND9Wz0QLrG prVeaH/RY2/ypB9ungn/AAQzOwaCU95bD7WmecDk+aL27lvLye7mp6tzI8snEUHJ2LGg+ZzeRFCn DJTDStY1jQblWQMiSqsjW8oISRHAZWofFTUMMrnjjMNuPLKB2ejaF5q0zV1Cxt6N1T4rZz8X+xP7 QzXZcEoe52eLPGfvTnKW94/5n1Y6prM9wprCp9OD/jGuwP09c2+HHwxp0ufJxyJSrLWp2KuxV2Ku xV2KuxVF6TqMmnalb3sfWFgWHip2YfSpyGSHECGeOfDIF7PBNHPDHNE3KOVQ6N4qwqDmnIo07sGx aW635l0vSIz9Yk53BHwWyULn5/yj3OWY8Mp8mrLnjDnzeb6zr+ra9OQwb0UDOlrECVVVBZmNOtFF STmxx4owDrMuaUyk4NDX9eXNL6o8if8AOSmm62sNjrsq6VqzAKbhwv1aV6bkOR+7JPZ9vAnNTqMG WJMh6h9rfDgOxeorq1+yhluKqRUEKhBB/wBjmv8AHk3+DFWGs3tOq/OmPjyR4MWxrV4D+wfmP7cP jyXwIpfNMAHmmcKBV5HY0AHUkk5TuS28nkXn7/nILQtLWWw8uKurX9Cpu6/6JGdxsw3l/wBjRf8A K7ZsdPoJHeWw+1onnA2D5szdOI+5PzL/AOcefJHnX1b2KP8AQ2uvVv0jaqOMjnvPD8Kyb9SOLf5W WGNtYk+UvzA/J/zv5EnZdZs/U012Cw6tbVktWJ+zV/hMbHpxcA+FcgRTMG0D5N/Mrzb5Qlppl1zs yayadcVkt2Pc8KgofdCD45jZtNDJzG7ZDIY8n0L5E/O3yr5o9O0uGGk6u9FFpOw9ORiaAQy7KxO3 wmjeAPXNRn0U4bjcOVDMCmfnf80/KflFGjvrj6xqVKpptvR5qkAjnvSMbg1bt0ByvBpZ5OXLvZTy CL5389fm/wCbPNpkt5Jf0fpLbDTrdiFZa7es+zSfgv8AkjNzg0kMe/M97iTymTD4dL1OexuL+C0m lsbQqLq7SN2iiMhogkkA4ryOwqd8ymtkv5a6x5N0zXVl80WH1u3aggnNXjgav2ngp+8H308DmLqo ZJR9BptxSiD6n0Rr/lPyf540eJ5ljuYWT/QtRtmX1EH/ABXIKinipqPbNFjzZMMv0OdKEZh4P5z/ ACc81+W3e5tEbU9MQ8lurZT6iAb1kiFWWniKj3GbnBroZNjsXDnglFj8fnTXBp01jLKJklQxrK/9 4oOxow67eOXnTxu0jUz4aSHL3HdirsVdirsVdirsVdirsVTqDzfrVvpUenW8gijjqBMB+84k141P Sle2UHBEyst41EhHhCfeT/yn82+apVunjax06Q8n1C6BBcHvGh+KT59PfKs+shj25nuRDDKW73vy t5F8peSdOklhVFcITeapdFeZUfaq5oqJ7DbxrmlzaieU/oc2GOMA8B/NPV/I2o63z8rWXoBS31u7 T93DMx7xw0+Gn821fDvm60cMkY+suDmlEn0sTbTNRWxiv3tZlsZ3aKG7MbiF5EFWRZKcSwruAczG pl3kb83vNvlIpbwyi/0oEV0+5JZVG1fSf7Ue3Snw/wCScxc+khk8i2QymL6G8jfmx5U83IkNtP8A VNVI+PTbghZKgVPpH7Mg2P2d6dQM0+fSTx89x3uXDKJKXnr83/KflJXt5Jfr+rLsNOtyCyn/AItf dY/p+L/JOHBo55N+QRPKIvnfzx+anmvzfI0d5P8AVtMr8Gm25KxbGoMneQ7ftfQBm4waWGPlz73F nkMlPyF+V3nPzzeiDQbFnt1YLcahNWO1ir/PJQ7/AOSoLe2ZQFtRNPqr8sv+ca/JnlL0r/VVXXtc WjetcIPq0Tdf3MBqKg/tvU9xxyYiwMnr+SYqdzbW11byW11Ek9vMpSWGVQ6Op2KsrVBB98VeBfmb /wA4o6Jqnq6l5KlXSb41ZtMlJNpIetI23aEk9t19lGQMWQk+YvM/lLzH5W1N9M1+wlsLxdwso+F1 /mjcVR191JGQIZ2hNL0nVtZ1COx0y1mvr+4akcEKtJIxPegr9JxS+jfyy/5xLJ9HUvPs1Bsy6Hav v8ridf8AiMZ/2XbJiLAyfRlh5d0HT9HGi2WnW8GkBGiNgkSCEo4owZKcW5ftV698mwfOf5uf84rM Gn1ryCKqayT6DI247n6q7f8AJtj8j0XIGLMSeG+XfN/nDyPqcsNu0lq8blbzS7pWCFhsRJE3Eq23 UUOYufTQyD1BuhkMeT3Hyf8Anf5V1wR2+ot+iNRagKTkegzf5E2wH+zp9OabP2fOG49Qc2Goieey N83/AJR+UPNDm94Gxv5BX65acQJOpBkSnB+v2tmPjkMGtyY9uY80zwxk8l8wfkF5z04tJpxi1a3H T0j6U1PExyGn0KxzZ4+0ccufpcaWmkOW7ANS0bV9Ll9LUrKezkrQLPG0ZPy5AVzNhkjLkbaDEjmg 8mh2KuxV2KuxVFafpWp6jN6On2k13N/vuCNpG39lByMpiPM0kAnkzzy/+RHnfU+Ml6kWk27blrhu UtPaJORr7MVzCydo448vU3R08jz2es+UPyX8o+XZEvJlbVNQj3E90B6aHxSIfCP9lyI8c1mfXznt yDlQwRj5u84fnP5S8v8AqW9vJ+ldSWo+r2xBjVh2kl3UfJan2xwaGc9zsFnnjHzeFea/P3mzzneJ BcyM0LuBa6XbBvT5E0UBBVpH32JqfDN1g0sMfLn3uFkymXN7F+Uv/OK97emHWPPga0s9ni0NDxnk HUfWGH92v+QPi8eOZYi0GT6Xl8teXptDGgy6bbPoojEI04xIYBGvRRHTiKdsnTB89fmb/wA4lxv6 upeQpuDbs2iXT1U+1vO3T/VkP+yyBizEnzfq+jazoWpyafqtpNp+oW5+OCZTG6nsRXt4EZAhmv0L y/rvmHU49N0aym1DUJz8EMKlm92Y9FUd2Y0HfEBX0r+WX/OJllbCLUvPcwu59mXRbZyIVPWk8woz nxVKD/KYZMRYGT6H0/TrDTrOKx0+2itLOBQkNtAixxoo7KqgAZNgiMVdirsVdiqVeZfKvl3zPpj6 Zr1hFqFm/wDuuVd1JFOUbijI3+UpBxpbQPkv8u/J/kuyNr5e06O05/31wavPLvX95K1XYeArQdhg ApJLI8KHYq7FWG/mD+Unkjz3b8dbsgL5V4wanb0juox2HOhDgfyuGHtgItINPmPz/wD84t+fPLzS XWhD/EWmLVh9XXjdoPBrckl/+eZYnwGQMWYk8+0Lzx528pXBtrS7nthC1JdOuAWjBrUqYpPsHxpQ 5jZdNDJ9QbYZZR5F6ZoP/OR8RCx6/pRVtuVxZNUf8ipCKf8ABnNdk7L/AJp+bkx1XeGdaf8Amp+X GsxemdVgQOKPBegwj5H1QEP0E5hS0eWHT5NwzQPVXl8h/ltrSeqmlWE6NuZbUKla9+UBXANRlh1P 496fDgeiVT/kX+XMpqljNBvWkdxKfo+NnywdoZe/7GB08EKf+cf/ACD6nP8A0wLWvp+sOPy+xy/H Jfyll8kfloomD8ivy6jNXs5phWtHuJQPl8BTAe0Mvf8AYn8vBNYfy9/LbR09ZtIsYkX/AHZdgSAU /wAqcvlR1OaXU/D9jPwoDop3v5mflvokXojVbUKn2YLIesK+AEAZR9OGOkyz6H4oOWA6sH17/nI6 yQNHoOlvM/7NxeMEUH/jHGWLD/ZjMzH2Wf4j8mmWq7g8y8w/mJ5280yfV7u9kaGY8U0+1Bjibkdl 4Ju/tyrmxxaXHj5Ddx55ZS5s18g/84yfmF5laO41SH/DultQma8U/WGH+RbVV6/8ZOGZQi0mT6d/ Lr8mPI3kOJX0u0+saoVpLq11SS4NeoQ0Cxr7IBXvXJgUwJtnWFDsVdirHvOf5f8AlLznp/1LzDp8 d2qg+jP9ieInvFKtHX5Voe4OAhIKr5R8keVvKGmjT/L2nxWMBp6rqKyysP2pZGq7n5n5YgKSnmFD sVdirsVdirsVdirsVdirsVdirsVdirH/ADV+X3kvzZD6XmDR7e/NOKzuvGdR4JMnGVfobAQkF415 n/5w78tXReXy5rNxprndba7RbmL/AFVZTFIo+fLImDLieX67/wA4rfmxprMbK3tdYiG4a0uFRqe6 3HoGvsK4OEp4gwm//LL8zNGkMlz5c1S3Kf7vS2mZBtX+9jDL0HjkTFIKCbXvPOmHg2o6nYkVXiZr iH3IpyXKjggecR8mYnLvW/4687f9TDqX/SZcf814Py+P+bH5BPiS7y2vmLzxqR9NdT1O9Oy8BPcS /a6CnI9cIwQHKI+SDOXejLL8ufzK1qX1Lfy9qt2z/wC72tp+Jr4yOoX8ctEe5iSzTQv+cWvzb1Jl N1Z22kRGnx3lwhNPHhb+u30EDJcJY8QeneWf+cOdDgKy+ZNcnvWFCbaxRbdPkZJPVZh8guEQRxvZ fKX5aeRfKSAaBo1vZygUN1xMlwfnPIXkp7cqZIBiSybCh2KuxV2KuxV2KuxV2KuxV2Kv/9k= + + + + application/vnd.adobe.illustrator + xmp.did:61e30391-89d4-d941-8455-8d44a928ac5f + uuid:717b9eac-e1fd-4b0c-ad7b-6573570e1b25 + xmp.did:ef0a0210-7abd-1e43-8593-6a0a8a6fa763 + default + + xmp.iid:8e3b6d1a-3d57-2246-a9cd-6e94b90fcfdd + xmp.did:8e3b6d1a-3d57-2246-a9cd-6e94b90fcfdd + xmp.did:ef0a0210-7abd-1e43-8593-6a0a8a6fa763 + default + + + + + saved + xmp.iid:ef0a0210-7abd-1e43-8593-6a0a8a6fa763 + 2022-07-14T11:29:30+02:00 + Adobe Illustrator CC 23.0 (Windows) + / + + + saved + xmp.iid:61e30391-89d4-d941-8455-8d44a928ac5f + 2022-07-14T11:32:11+02:00 + Adobe Illustrator CC 23.0 (Windows) + / + + + + 1 + True + False + + 620.000000 + 620.000000 + Pixels + + + + Cyan + Magenta + Yellow + Black + + + + + + Default Swatch Group + 0 + + + + Document + + + + + + + + + + + + + + + + + + + + + + + + + +endstream endobj 3 0 obj <> endobj 6 0 obj <>/Resources<>>>/Thumb 60 0 R/TrimBox[0.0 0.0 620.0 620.0]/Type/Page>> endobj 56 0 obj <>stream +H10 ݧjq@d{mn~{.VVdq z9$MQ "J8fYi6ӓќތ0gZ +endstream endobj 57 0 obj <> endobj 60 0 obj <>stream +8;Z]";%eC%#Xl<)"h@C#MX3fB_.9jdm)/u_7Zl;F:jIfi=ZDg^Lh>==\s& +*Z7^dOFr*:aG/b&Q3G;uM(cG]\R:i=Pr4-f0X!+8lLalKd=F$4T\ +'+6?D-hmP+/E-8!Z6?c8A\kY>:G%eB1>4Y*[Hr'GAUa?^rO7In7ckS_ +=gjPYW*o:;5Ru^Ags!#FUeC2391A~> +endstream endobj 61 0 obj [/Indexed/DeviceRGB 255 62 0 R] endobj 62 0 obj <>stream +8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 +b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` +E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn +6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( +l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~> +endstream endobj 59 0 obj <>/Font<>/ProcSet[/PDF/Text]>>/Subtype/Form>>stream +BT +0 0 0 rg +/GS0 gs +/T1_0 1 Tf +0 Tc 0 Tw 0 Ts 100 Tz 0 Tr 12 0 0 -12 -103.0967 -61.2773 Tm +[(T)7 (his is an A)11.9 (dobe\256 I)-10 (llustr)5.1 (a)4 (t)5.9 (or\256 F)25.9 (ile tha)4 (t w)4 (as)]TJ +0 -1.2 Td +[(sa)8 (v)10 (ed without PDF C)11 (on)4 (t)6 (en)4 (t)3 (.)]TJ +0 -1.2 Td +[(T)71 (o P)5 (lac)6 (e or open this \037le in other)]TJ +0 -1.2 Td +[(applica)4 (tions)10.9 (, it should be r)10 (e)-28 (-sa)8 (v)10 (ed fr)10 (om)]TJ +0 -1.2 Td +[(A)12 (dobe I)-10.1 (llustr)5 (a)4 (t)6 (or with the ")3 (C)3.1 (r)9.9 (ea)4 (t)6 (e PDF)]TJ +0 -1.2 Td +[(C)11 (ompa)4 (tible F)26 (ile" option tur)-4 (ned on. )41 (T)7 (his)]TJ +0 -1.2 Td +[(option is in the I)-10 (llustr)5 (a)4 (t)6 (or Na)4 (tiv)10 (e F)31 (or)-4 (ma)4.1 (t)]TJ +0 -1.2 Td +[(Options dialog bo)14 (x, which appears when)]TJ +0 -1.2 Td +[(sa)8 (ving an A)12 (dobe I)-10 (llustr)5 (a)4 (t)6.1 (or \037le using the)]TJ +0 -1.2 Td +[(S)-3 (a)8 (v)10 (e A)6 (s c)6.1 (ommand)10 (.)]TJ +ET + +endstream endobj 55 0 obj <> endobj 64 0 obj <> endobj 65 0 obj <> endobj 66 0 obj <>stream +H|TiPYjPfPƪTpdP9WdETZmFW4tXDECKQ@DnedËa\'fga̟"22_2c|6ylܰtaU\᥊z2 :Trr8?א?F+PO<2Y-& rd+lݴt,_|Z\~Jau2L-sPEFQJ%4T6 jJ3u{U LT3 HW)(\ BdS1Tp% KLX&DLgR)ܼ}G*e+e +A Å0-1 +Ö`v8ly ?af ;eaXn `L.Rn4bOq1#2$XRj4HnTcjF?&4㍸ 6R $}bxG CÔ1yF2I|;$DOih"Q-V7M tnO9. 'ďLsδN.9`#@?kAtՒuh/#UH]zKHM0S"}RQ5@ա E vBwJeWC ݋B G&f:A@q^'vwYU0J#)_K'7bL*R]7@_`>MK}RvGLueFsd 's4 >N(1Ÿ3߈'k10粶6Qk0ϭlM1͸y&u7ATQǀ6C-A!,yTh~/D p}7D51 e)@Xkǿ +,=Aj/ؘB +CԲuuk΃`4&ppBktvORBA]xǻOF4O5F,ZX_yrLân}(Ģ@By#rx6HK+̫gnreyZ~V{3}OȹaO:Bqz%Ck]fӱ 1iQVWĶ3`ۚQYWIo7m/`Baas4̪s;(7 ;lݞ_`ċ~)FDpt\@N]{RZl7)+J;&ꫪ.,l;rȟ&.(Ϯc>w$hk2R ~j*,b J{f8Tq}Bq/ߓVYt=?9[᝻!$Ƥo*F(-,22- ` Fg@PN&{(3αy-U+$YcWuVNInrT[uc]` n/rL Y0YY98Uףk*CjzrͰDxm iB"4FJl'3xjp?b_~ɭ}Y\.UNXmxK8JN[ZvtTA -FG;9%My2jz.n=s}pPh̑(@)oߟ#rSNgrncU̡a776&h8m=T #PޔPk'(uhXj2W) fI/,=SyNpीML AtH(BڅYlmf{ jI\~)PTJ`UǾp6FF{2Y1M]q4MnAYlV>lX t7E&Pe[6ǧb+F[ǀVQMQ"L!IL# 1-?=N~ܓۿH+,.(#6c;CmS宋rsgstP`a+0w3TJ-j\`2C?{^$/ƣP£0>7W!Feolj1CNB[B"b[KgD{c'%%~Ʌ jOJOeŮ<"N@ 9];zX򋬢]q^daVE(Jyyb_>@[<) ZQ"O;IBAB*n-co.1͒T^1Rei7+]<:z 9tlG$cpS('S9Y(T0u|~*QQf`S'/z/W<Բs=VZ(I,`72Q +Gy0\3Z=! YC!w^7ςMb y_3n& YU =o=S.;Y lseGŃ=/QTUBR~ڿ{M,LKJW!^Md\K3&4M =G ]hkƙ gzc+90 +endstream endobj 63 0 obj <> endobj 58 0 obj <> endobj 67 0 obj <> endobj 68 0 obj <>stream +%!PS-Adobe-3.0 +%%Creator: Adobe Illustrator(R) 17.0 +%%AI8_CreatorVersion: 23.0.1 +%%For: (rr-) () +%%Title: (installer icon.ai) +%%CreationDate: 7/14/2022 11:43 AM +%%Canvassize: 16383 +%%BoundingBox: 656 238 1242 842 +%%HiResBoundingBox: 656.764393457089 238.799999999999 1241.31560654291 841.120025677788 +%%DocumentProcessColors: Cyan Magenta Yellow Black +%AI5_FileFormat 13.0 +%AI12_BuildNumber: 540 +%AI3_ColorUsage: Color +%AI7_ImageSettings: 0 +%%RGBProcessColor: 0 0 0 ([Registration]) +%AI3_Cropmarks: 639.040000000001 229.960037252957 1259.04 849.960037252957 +%AI3_TemplateBox: 959.5 540.5 959.5 540.5 +%AI3_TileBox: 663.402213134767 131.224929831082 1234.72222045899 948.664922735745 +%AI3_DocumentPreview: None +%AI5_ArtSize: 14400 14400 +%AI5_RulerUnits: 6 +%AI9_ColorModel: 1 +%AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 +%AI5_TargetResolution: 800 +%AI5_NumLayers: 1 +%AI9_OpenToView: -9258.43684210526 7167.7052631579 0.109953703703704 2255 1454 18 0 0 78 121 0 0 0 1 1 1 1 1 0 1 +%AI5_OpenViewLayers: 7 +%%PageOrigin:936 516 +%AI7_GridSettings: 72 8 72 8 1 0 0.800000011920929 0.800000011920929 0.800000011920929 0.899999976158142 0.899999976158142 0.899999976158142 +%AI9_Flatten: 1 +%AI12_CMSettings: 00.MS +%%EndComments + +endstream endobj 69 0 obj <>stream +%%BoundingBox: 656 238 1242 842 +%%HiResBoundingBox: 656.764393457089 238.799999999999 1241.31560654291 841.120025677788 +%AI7_Thumbnail: 124 128 8 +%%BeginData: 20057 Hex Bytes +%0000330000660000990000CC0033000033330033660033990033CC0033FF +%0066000066330066660066990066CC0066FF009900009933009966009999 +%0099CC0099FF00CC0000CC3300CC6600CC9900CCCC00CCFF00FF3300FF66 +%00FF9900FFCC3300003300333300663300993300CC3300FF333300333333 +%3333663333993333CC3333FF3366003366333366663366993366CC3366FF +%3399003399333399663399993399CC3399FF33CC0033CC3333CC6633CC99 +%33CCCC33CCFF33FF0033FF3333FF6633FF9933FFCC33FFFF660000660033 +%6600666600996600CC6600FF6633006633336633666633996633CC6633FF +%6666006666336666666666996666CC6666FF669900669933669966669999 +%6699CC6699FF66CC0066CC3366CC6666CC9966CCCC66CCFF66FF0066FF33 +%66FF6666FF9966FFCC66FFFF9900009900339900669900999900CC9900FF +%9933009933339933669933999933CC9933FF996600996633996666996699 +%9966CC9966FF9999009999339999669999999999CC9999FF99CC0099CC33 +%99CC6699CC9999CCCC99CCFF99FF0099FF3399FF6699FF9999FFCC99FFFF +%CC0000CC0033CC0066CC0099CC00CCCC00FFCC3300CC3333CC3366CC3399 +%CC33CCCC33FFCC6600CC6633CC6666CC6699CC66CCCC66FFCC9900CC9933 +%CC9966CC9999CC99CCCC99FFCCCC00CCCC33CCCC66CCCC99CCCCCCCCCCFF +%CCFF00CCFF33CCFF66CCFF99CCFFCCCCFFFFFF0033FF0066FF0099FF00CC +%FF3300FF3333FF3366FF3399FF33CCFF33FFFF6600FF6633FF6666FF6699 +%FF66CCFF66FFFF9900FF9933FF9966FF9999FF99CCFF99FFFFCC00FFCC33 +%FFCC66FFCC99FFCCCCFFCCFFFFFF33FFFF66FFFF99FFFFCC110000001100 +%000011111111220000002200000022222222440000004400000044444444 +%550000005500000055555555770000007700000077777777880000008800 +%000088888888AA000000AA000000AAAAAAAABB000000BB000000BBBBBBBB +%DD000000DD000000DDDDDDDDEE000000EE000000EEEEEEEE0000000000FF +%00FF0000FFFFFF0000FF00FFFFFF00FFFFFF +%524C45FD3DFF27277DFD76FFA852F820F8F827A8FD73FF7D27F826F827F8 +%20F87DFD70FFA827F8F827F820F826F820F8277DFD6CFFA852F820F82720 +%27F826F827F827F82027A8FD69FF5220F826F826F8F8F87D7D27F820F826 +%F8F8F852A8FD65FF7D2620F827F827F82051FFFFFFA87DF820F8272026F8 +%2776FD62FF7D51F8F8F826F8F8F8277DFD06FFA827F8F826F820F8F8207D +%FD5FFF5226F827F827F820267DFD0AFF7D51F826F8272026F852A8FD5BFF +%A8F8F8F8272020F82652FD0EFF7D20F8F827F820F8F87DFD5AFF7D26F827 +%F827F852A8FD11FF5226F8272027F87DFD5AFFA8F826F82027A7FD0AFFCF +%FD09FFA851F826F8F852FD5AFFA827F827F8A8FD0AFFCFCFCFFD0AFF27F8 +%27F87DFD5AFFA8F826F8207DFD09FFC9CFA7CFCFFD09FFF826F8F852FD5A +%FFA826F827F8A8FD07FFCFC9CFCFCFCECFCFCFFD07FF27F827F87DFD5AFF +%A8F820F8207DFD05FFA8CFA7CFA7CFA7CFA7CFA7CFA8FD05FFF820F8F852 +%FD5AFFA826F827F8A8FD04FFCEFD05CFC9FD05CFCECFCFFD04FF27F827F8 +%7DFD5AFF7DF820F8207DFFA8CFC9CFA7CFC9CFC9CFA7CFA7CFC9CFA7CFC9 +%CFCFFF2020F8F852FD58FF7C4BF8272026F8A7CFCFCECFCFCFCECFCFCFC9 +%CFCFCFCECFCFCFCECFCECFA827F827F827277CA7FD52FF7D51F8F8F820F8 +%26F8F875CEA7CFA7CFA7CFA7CFA7CFA7CFA7CFA7CFA7CFA7CFA7A7F820F8 +%20F820F8F8277DA8FD4DFF7D2720F827F827F8272027F8A0C8C9C9CFCFCF +%CEFD0DCFC9C9A627F827F827F827F826F82752FD4AFF7627F820F820F826 +%2651F826F8207BC9A6C8A6CFCECFA7CFC9CFA7CFC9CFA7CFC9C9A6C9A6A6 +%F826F8F82651F8F8F826F8F8F852A8FD45FF7D2020F827F82626517BC97C +%27F827F8A0C8C9A6C9A6CECFCFCECFCFCFCECFCFCFA7C9A6C9A6C9A627F8 +%27F876C8A65127F827F826F82752FD42FF7D27F8F8F820F82751A6A6C8A0 +%A0F820F8207BC9A0C9A6C8A0C9A7CFA7CFA7CFA7CFA6C8A0C9A6C9A6C9F8 +%20F8F851C8A0C8A07C2620F820F8F8F87DFD3FFF5226F827F827267CA0C9 +%C8C8A6CE7C27F827F8A0C8C9C8C9A6C9C8C9A7CFCFCFC9CEC8C9A6C9C8C9 +%A6CEA627F827F875C8C9A6C8C8C97C51F8272026F852A8FD3BFFA826F8F8 +%26F82651C8A6C8A0C8A6A04B27F820F82075CEA6C9A6C9A6C9A6C8A6CEA7 +%C9A6C9A6C9A6C9A6C9C8A6F820F826205175C8A6C8A0C8A67C26F8F820F8 +%267DFD39FF7DF8262020F851A0C9A0C9C8C87551F820F8272027F82775C9 +%C8C9C8C9A6C9C8C9A6C9C8C9A6C9C8C9A6CEC97C26272027F827F826267C +%A0C9A0C9C8C85127F827F82051FD36FFA851F820F8F8207CA0C8A0C8A07C +%26F8F826F8F8F826F820F8F8207CA6C9A6C9A0C9A6C9A0C9A6C9A0C9A6C9 +%7B4BF8F8F826F820F820F8F8F82751A6A0C8A0C87B4BF820F8F820A8FD33 +%FFA827F827F82751C8C8C9C8C97C51F826F827F8274B4BF827F8272026F8 +%51A0CEC8C9C8C9A6C9C8C9A6CEC9C95127F827F827F82726512627F827F8 +%2026A0C8C9A0C9C87CF8272020F8A8FD31FF7D20F827F82675C8A0C8A6A6 +%4B20F826F8F82051FD047520F8F826F820F82651A6C8C9A6C9A6C9A6C9A6 +%7CF8F8F826F820F8274A7575754B27F826F8F8F84B7BC8A0C8C8A026F8F8 +%20F87DFD2FFF7D26F827F84BA0C9A0C9C87C2020F826F8274B7575767575 +%75764B27F8272027F820267CA6CEC8C9C8CE7C27F8272027F827F84B759A +%7575759A75752627F827F82751C8A6C8C8C84B26F826F87DFD2DFF7DF8F8 +%26F84BA0C8A0C8A051F8F8F820204B6F7575756F7575756F754B4BF820F8 +%20F820F82775C9C8A026F8F820F826F8F8207575756F7575756F7575754A +%27F820F8F826A0A0C8A0C84BF8F820F852FD2BFF7D20F827F851A6C9A6C9 +%A04BF827F8274B75759A7575759A7575759A759A75752627F8272027F827 +%5176F8272027F827F8274B9A7575759A7575759A757575A0755120272026 +%20A0C8C8A6C97527F827F852FD29FF7D20F820F875A6C8A0C97520F826F8 +%264B99FD10752620F826F820F820F820F826F820F8274BFD11752626F820 +%F875A6C8A0C97520F826F876FD27FFA826F827F875A6C9A6C85127F827F8 +%4B757575767575757675757576759975754B27F8272027F8272027F82720 +%27F8272027F827264B4BFD047576757575767575757675754B27F826F851 +%A6C8A0C97526F827F87DFD25FFA827F820F851A6C8A0C84BF8F820F84BFD +%04756F7575756FFD05752626F820F820F826F820F826F820F826F820F826 +%F820F826F8F8F84B4BFD05756F7575756F7575754B27F820F84BA0C8A0C8 +%75F8F820F87DFD24FF4BF827F851C8C9A6C95120F8272075759A7575759A +%7575759A759A4B4B2027F8272027F8272027F8272027F827F827F8272027 +%F8272027F827F8272675759A759A7575759A757575A07527F827F84BA0C9 +%A6C97527F826F8FD23FF52F827F84BA0C8A0C84BF8F8F820FD0D752620F8 +%26F820F826F820F820F8F8F820F820F826FD05F820F826F820F826F820F8 +%274BFD0C7527F826F827A0C8A0C84BF8F8F826FD21FF7DF827F827A0C8A0 +%C95120F82726757576757575767575759A754BF827F827F8272027F820F8 +%272752527D7DA8A8A87DA87D7D524B2020F826F827F8272027F827267575 +%9A757575767575759A754BF827F84BA0C9A6C8272620F852FD1FFFA8F820 +%F8207BC8A0C851F8F820F87575756F7575756F7575754A26F820F826F820 +%F8F8F827517DA8FD0DFFAFFF7D522020F820F826F820F820F84B6F757575 +%6F7575756F757527F826F851A0C8A0A62020F8F87DFD1EFF2726202075C9 +%A6C97B27F827F8274B9A759A7575759A75752627F827F8272027F82752A8 +%FD16FFA8762020F827F8272027F827759A759A7575759A757520272027F8 +%76C8C8C8A0F827F827A8FD1CFF76F8F8F827C8A0C8A026F826F820F82626 +%FD077520F8F826F826F8F8F852A8FD1BFF5227F820F826F820F8264BFD07 +%75F820F826F820F87CA6A6A675F820F852FD1BFFA827F82620A6C8C8A64B +%F827F8272027F827267575767575F8272027F827F82051FD1FFFA87DF820 +%F8272027F8274B7575767575F8272027F8272026F8A6C8C8A651F827F8A8 +%FD1AFF52F826F875A0C8A651F826F820F826F820F8202675754BF820F826 +%F820F8277DFD22FFA827F8F826F820F82026757551F8F8F826F820F826F8 +%F826C8A0C87B26F8F820FD19FFA8F827F84BA6C9C8A0F8272027F8272027 +%F82720272651F827F8272026F876FD26FF7D27F827F82720272675F827F8 +%272027F8272027F82775C9A0C95120F8207DFD18FF27F8F8267BC8A0C826 +%F8F826F820F826F820F826F820F826F820F820F87DFD28FFA827F826F826 +%F820F826F820F826F820F826F820F827A0C8A0C820F8F827A8FD16FF7D20 +%F8204BC9A6C951202027F8272027F8272027F8272027F8272020F8A8FD12 +%FFCAFD07FFCAFD0FFFA852F827F827F827F8272027F8272027F8272027F8 +%51C8C8C87CF827F87DFD16FF4BF826F8A0A0C8A026F826F820F826F820F8 +%26F820F826F820F8F8F8A8FD12FF6F68446868684468686876FD0FFFA851 +%F820F826F820F826F820F826F820F826F820F87CA6C8A027F820F8FD15FF +%A8F827F851C8C9C875F8272027F8272027F8272027F8272027F82620FD13 +%FF6FFD096844A1FD11FF51F8272027F8272027F8272027F8272027F82727 +%C9A6C97526F8207DFD14FF51F8F826A0C8A0A0F820F826F820F826F820F8 +%26F820F826F8F8F8A8FD11FFCA68684468446844684468446876FD12FF27 +%F827F820F826F820F826F820F826F820F82075C8A0C826F8F827FD13FFA8 +%27F8204BC9A6C951202027F8272027F8272027F8272027F827F8A8FD11FF +%CAFD0C6844A1FD12FFA827F8272027F8272027F8272027F8272027F84BA6 +%C8C87CF827F8A8FD12FF7DF826F87CA0C8A026F826F820F826F820F826F8 +%20F826F820F87DFD11FFA144684468446844684468446844689AFD13FF7D +%F8F826F820F826F820F826F820F826F820F87CA6C89F27F8F827FD12FF27 +%27F84BA6C9C876F8272027F8272027F8272027F8272027F852FD11FFA144 +%FD0D6844A1FD14FF7C202027F8272027F8272027F8272027F8274BC9A6C9 +%5120F827A8FD10FFA8F8F8F851C8A0C826F8F826F820F826F820F826F820 +%F826F826A8FD10FF9A446844684468446844684468446844689AFD15FF27 +%20F820F826F820F826F820F826F820F8267BC8A0A0F820F87DFD10FF7DF8 +%26F8A6C8C97527F827F8272027F8272027F8272027F8207DFD10FF9344FD +%0F6844A1FD15FFA8F8272027F8272027F8272027F8272027F875C8C8A04B +%F82027FD10FF2620F84BA0C8A651F826F820F826F820F826F820F826F8F8 +%27FD10FF6F44684468446844684468446844684468446876FD16FF52F826 +%F820F826F820F826F820F826F8F820C8A0C84BF8F820A8FD0EFFA827F820 +%51C9A6C826272027F8272027F8272027F8272027F8A8FD10FF6FFD116844 +%A1FD16FFA827F827F8272027F8272027F8272027F8277CC9C8A0F827F8A1 +%FD0EFF7DF826F8A0A0C875F8F826F820F826F820F826F820F826F827FD11 +%FFA144684468446844684468446844684468446876FD17FF52F8F820F826 +%F820F826F820F826F820F875A6C8A027F8F851FD0EFF5220F827A6C8A675 +%F827F8272027F8272027F8272027F8207DFD11FFCAFD0868A1FD086844A1 +%FD17FFA8F8272027F8272027F8272027F827202626C9A6C84B26F827FD0D +%FFA827F8F826C8A0A62620F820F826F820F826F820F826F8F8F8FD13FF6F +%684468446868CAA86844684468446844689AFD18FF52F826F820F826F820 +%F826F820F826F8207BC8A675F820F8A8FD0DFF2027F87CC8C9A027F827F8 +%272027F8272027F8272027F87DFD13FFA1FD04686FFFFFCAFD086844A1FD +%18FF7D262027F8272027F8272027F8272027F8A0C8C97C27F8207DFD0CFF +%7D20F82075C8A67CF826F820F826F820F826F820F826F8F87DFD13FFA868 +%44686FFFFFFFA16844684468446844689AFD19FFF826F820F826F820F826 +%F820F826F8F84BC8A0A6F826F876FD0CFF7DF82720A6A6C951F8F8272027 +%F8272027F8272027F82627FD15FF6F6876FD04FFCA44FD076844A1FD19FF +%52F8272027F8272027F8272027F827F851A0C9A64BF82651FD0CFF52F8F8 +%27A0C8A051F820F826F820F826F820F826F820F852FD15FFA176FD05FFA1 +%68446844684468446876FD19FF7DF8F826F820F826F820F826F820F82020 +%A6A0C84BF8F827FD0CFF52F8274BC8A6C92727F8272027F8272027F82720 +%27F8207DFD1CFFCAFD086844A1FD19FFA8F8272027F8272027F8272027F8 +%27F827A0C9C875F82726FD0CFF27F8F851A6C8A027F820F826F820F826F8 +%20F826F820F8A8FD1CFFA168446844684468446876FD19FFA827F826F820 +%F826F820F826F820F826F8A0A0C875F8F827A8FD0BFF27F8F851C9A6A6F8 +%272027F8272027F8272027F827F827A8FD1CFFCAFD086844A1FD1AFF2720 +%F8272027F8272027F8272027F82775C9A6A0F826F8A8FD0AFFA8F820F875 +%A0C87B20F826F820F826F820F826F820F82020FD1DFFA168446844684468 +%44689AFD1AFF52F820F826F820F826F820F826F820F875A6C87526F8F87D +%FD0AFFA827F82675C9C8A0F8272027F8272027F8272027F827F852FD1DFF +%CAFD086844A1FD1AFF7620F8272027F8272027F8272027F82751C9A6A620 +%27F8A8FD0AFFA8F826F87CA6C875F8F826F820F826F820F826F820F82027 +%FD1DFFA16844684468446844689AFD1AFF7DF820F826F820F826F820F826 +%F820F851A6C89F27F8F87CFD0AFFA826F8277CC8C87CF827F8272027F827 +%2027F8272027F852FD1DFFCA44FD076844A1FD1AFF7D202027F8272027F8 +%272027F82720204BC9A6C82027F87DFD0AFFA8F820F8A0A6C85120F820F8 +%26F820F826F820F826F8F852FD1DFFA168446844684468446876FD1AFF7D +%F826F820F826F820F826F820F826F851A0C8A026F8F852FD0AFFA826F827 +%7CC9C87CF827F8272027F8272027F8272027F87DFD1DFFCAFD086844A1FD +%1AFF7D262027F8272027F8272027F82720204BC9A6C82027F87DFD0AFFA8 +%F820F87CA6C87520F820F826F820F826F820F826F8F827FD1DFFA1684468 +%44684468446876FD1AFF7DF826F820F826F820F826F820F826F851A0C8A0 +%26F8F876FD0AFFA827F82075C9C8A0F8272027F827F827F827F827F827F8 +%52FD1DFFCAFD086844A1FD1AFF7CF8F8272027F827F827F8272027F82051 +%C9A6A62026F8A7FD0AFFA8F820F875A0C87520F826F8F8F826F820F827F8 +%20F82026FD1DFFA16844684468446844689AFD1AFF52F820F826F827F826 +%F820F820F820F875A6C87B26F8F87DFD0AFFCF27F82051C9C8A0F827204B +%4B754B75759A7527F827F827FD1DFFCAFD086844A1FD1AFF4B20F827F84B +%757675754B754B27F82775C9A6A0F827F8A8FD0BFF2026F851A6C8A026F8 +%264B99FD0675F820F826F8A8FD1CFFA16844684468446844689AFD19FFA8 +%27F820F8264AFD07752020F87CA6C87520F820A8FD0BFF51F8274BC8A6C8 +%2626F8FD04757675757527F827F8207DFD1CFFCA44FD076844A1FD1AFFF8 +%272027F8FD04757675754B27F8277BC9C876F82720FD0CFF4BF8F827A0C8 +%A04BF8F8267575756F7575752620F820F87CFD1CFFA16844684468446844 +%6876FD19FF7DF8F826F8264B7575756F75754BF820F8A0A0C84BF8F827A8 +%FD0BFF7DF82726C8A6C95120F84B7575759A75757551F827F82727FD1CFF +%CAFD086844A1FD19FF7DF8272027269A7575759A759A4B27F84BA6C9A651 +%F8264BFD0CFF7DF8F8267BC8A67CF820F8FD07754B26F820F826A8FD1BFF +%A168446844684468446876FD19FF27F8F826F84BFD07754BF8F84BC8A0C8 +%2620F852FD0CFFA8F826F87CC8C87B27F8274B9A75757576757520272027 +%F87DFD1BFFCAFD086844A1FD18FFA827F827F8274B997576757575762626 +%F87CC8C8A027F8F87DFD0CFFA827F8F84BC8A0A62020F84BFD04756F7575 +%4BF826F8F827FD1BFFA16844684468446844689AFD18FF52F826F820F875 +%75756F7575754B26F8207BC8A67BF820F8A8FD0DFF4B20F851A6C9A04BF8 +%272075759A759A759A4B272027F827A8FD1AFFCAFD086844A1FD18FF2727 +%2027F84B7575759A759A754BF82720A6C8C95127F827A8FD0DFF7DF820F8 +%A67527F826F820F84B4BFD05752026F820F852FD1AFFA168446844684468 +%44689AFD17FF7DF8F826F8F84AFD05752626F826F820F87CA027F82027FD +%0EFF7D26F8272627F827F8272027F8274B757576754BF8272026F8FD1AFF +%CA44FD076844A1FD17FF4BF827F8272675759A75512027F827F8272026F8 +%4B2027F87DFD0FFFF820F8F8F826F8F8F826F820F820F84B6F994B20F826 +%F8F851FD19FFA168446844684468446876FD16FF7DF826F820F84B75754A +%27F8F8F826F8F8F826F820F826F820A8FD0EFF7D27F827F827F8202752F8 +%20F8272027F84B4BA04B272027F827A8FD18FFCAFD086844A1FD16FF2727 +%2027F82775752627F827F827F8202751F826F8272027F87DFD0CFFA827F8 +%F827F820F82652FFFFA827F8F826F820F8262675F826F826F851FD18FFA1 +%68446844684468446876FD15FF52F8F826F8202651F8F8F826F820F8277D +%FFA87DF8F8F827F820F8277DFD08FFA852F8262027F826F852A8FD05FF7D +%27F8272027F8272027F8272027F87DFD17FFCAFD086844A1FD14FFA826F8 +%27F8272027F8272027F820F87CA8FD05FF5220F8272027F82027A8FD05FF +%5220F820F826F8F826A8FD08FFA876F8F8F820F826F820F826F820F8A8FD +%16FFA16844684468446844689AFD14FF27F826F820F826F820F826F8F827 +%A8FD08FF7D27F820F826F8F8F852A8FFA12620F827F827F8277DFD0DFF52 +%20F8272027F8272027F82627FD16FFCAFD086844A1FD13FF52F8272027F8 +%272027F827F84B7DFD0CFFA72720F8272027F8277DF8F8F827F8F8F876A8 +%FD0FFF7D27F820F820F826F820F8F827FD15FFA16844684468446844689A +%FD12FF7DF820F826F820F827F8F8F87DFD10FF5220F826F820F8F8272027 +%F82627A8FD13FF7D20272027F8272027F82052FD14FFCA44686868446868 +%6844A1FD11FF7DF8272027F8272027F82652FD13FFA852F8272027F8F826 +%F8F852FD07FFA8CFCFCFA8FFCFCFA8FFCFCFA8FFCFCA2620F820F826F820 +%F8F852FD13FFA168446844684468444476FD10FF7DF820F826F820F826F8 +%207DFD07FFA8CFCFCFA8FFCFCFA8CFCFCFA8FFCF7DF826F820272027F8A8 +%FD06FFCFCFCECFCECFCECFCECFCECFCECFCECFC951F827F8272027F82720 +%F852FD13FFA1C3A1A1A1CAA1A1A1CAFD0FFF7DF826F8272027F827202626 +%FD07FFCFCFCECFCECFCECFCECFCECFCECFCFCFA727F827F8F826F8F87DFD +%06FFCFA7CFC9CFA7CFC9CFA7CFC9CFA7CFC9C92620F8F8264BF820F826F8 +%F827FD2AFF52F826F820F826204BF826F827A8FD05FFCFCFA7CFC9CFA7CF +%C9CFA7CFC9CFA7CFC9A6F826F82027F827F8A1FD06FFCECFCECFCFCFCECF +%CFCFCEFD04CFCEC851F827F84B754BF827F827202026A8FD26FFA852F826 +%F827202620754B26F82726FD06FFCFCFCFCECFCFCFCECFCFCFCECFCFCFC9 +%CEA627F82720F820F8F87DFD05FFA7CFA7CFA7CFA7CFA7CFA7CFA7CFC9CE +%A0C927F8F8202675754BF820F826F8F8F87DFD24FF7D27F826F820F820F8 +%75754BF820F827A8FD04FFCFA7CFA7CFA7CFA7CFA7CFA7CFA7CFC9C9A6A7 +%F820F82627F827F8A7FD04FFFD10CFA7C9C951F827F84B759A7551F82720 +%27F827F84B7DFD21FF5220F8272027F827267575A04B27F82726FD05FFFD +%10CFA6CEA627F82720F820F8207DFFFFFFCFCFA7CFC9CFA7CFC9CFA7CFC9 +%CFA7CFA7C8A6C94BF8F82626FD04754BF826F820F826F8F827A8FD1CFFA8 +%52F8F8F826F826F8F820FD04754BF820F827A8FFFFFFCACFA7CFC9CFA7CF +%C9CFA7CFC9CFA7CFA7C8A6A7F820F827272026F8A8FFFFFFCFCECFCECFCF +%CFCECFCFCFCECFCFCFC9C9C8C9C851F827F84B7575757675752027F827F8 +%27F820F8527DFD18FFA85227F826F8272027F8274B75757675994A27F826 +%20FFFFFFFD04CFCECFCFCFCECFCFCFCECFCFCFC9C9C8C9A027F827F8F826 +%F8F87DFFFFFFA7CFA7CFA7CFA7CFA7CFA7CFA7CFC9C9A0C9A6C926F8F8F8 +%207575756F7575752620F820F826F820F8F826527DFD12FFA85227F8F8F8 +%26F820F826F8264A7575756F75754BF826F827A8FFFFCFA7CFA7CFA7CFA7 +%CFA7CFA7CFA7CFC9C9A0C9A6A6F820F820272027F8A8FFFFFD10CFA6C9C8 +%C9C851F827F84B7575759A757575A04B27F8272027F8272026F82727527C +%A8A8FD09FF7D7D5227F820F827F8272027F827F84B75A07575759A759A4B +%27202620FFFFFFCEFD0FCFA6C9C8C9A027F827F8F827F8F87DFFCFCFA7CF +%C9CFA7CFC9CFA7CFC9CFA7CFA7C8A6C9A6C92620F8F826FD0A754BF826F8 +%20F826F820F826F8F8F827F8FD05272627FD05F820F826F820F826F8F820 +%FD0A754BF827F827A8FFC9CFA7CFC9CFA7CFC9CFA7CFC9CFA7CFA6C9A6C9 +%A6A6F826F82027F827F8A1FFCFCFCFCECFCFCFCECFCFCFCECFCFCFA7C9C8 +%C9A6C9C851F827F84B7576757575767575757675754A27F827F8272027F8 +%272026F827F820F826F826F827F827F8272027F8272027F8274B75757675 +%757576757575A04B26F82726FFFD04CFCECFCFCFCECFCFCFCECFCFCFA7C9 +%C8C9A6C9A627F82720F826F8207DFFA7CFA7CFA7CFA7CFA7CFA7CFA7CFA7 +%C9A0C9A6C9A6C927F8F82020FD05756F7575756F757575F826F820F826F8 +%20F826F820F826F820F826F820F826F820F826F820F826F8F8269975756F +%7575756FFD04754BF820F827A8CFA7CFC9CFA7CFA7CFA7CFA7CFA7CFA7C9 +%A0C9A6C9A6A7F820F82627F827F8A7FD10CFA6C9C8C9A6CEC951F8272027 +%20FD04759A7575759A759A26272027F8272027F8272027F8272027F82720 +%27F8272027F8272027F8272027F8277576759A7575759A759A754BF827F8 +%2727FD05CFCEFD0ACFCEA6C9C8C9A6CEA627F82720F820F82027C9C9CEA7 +%C9A6C9A7C9A6C9A7C9A7CEA6C9A6C9A6C9C8A02020F826F820F84BFD0875 +%6F26F826F820F826F820F826F820F826F820F826F820F826F820F826F820 +%F826F820F84BFD08754B26F826F820F82676CEC8C9A6C9A6C9A7C9A6C9A7 +%C9A7CEA6C9A6C9A6C9A67CF820F826272027F827267CA6CEC8C9A6C9A6C9 +%A6C9A6C9A6C9C8C9C8CEA051F8272027F827F827F84B7599757675757551 +%F827F8272027F8272027F8272027F8272027F8272027F8272027F8272027 +%F827202620FD04757675752627F826F8272027F8274BA6C8C9C8C9A6C9A6 +%C9A6C9A6C9A6C9C8C9C8CE7C4BF8272027FD04F820F826F8277BC9A6C9A0 +%C9A6C9A0C9A6C9A0C9C8A65126F820F826F8F82627F820F82626FD0575F8 +%26F820F826F820F826F820F826F820F826F820F826F820F826F820F826F8 +%20F826F8F84A7575756F51F8F8F820F827F820F820F8F8F851A0C9A6C9A0 +%C9A6C9A0C9A6C9A0C9C8A62720F820F826F8F8CA4B20F8272027F82751A7 +%C9CEC8C9A6C9C8C9A6CEC9A02627F827F827F82751C9A051F827F8272075 +%75A04B272027F8272027F8272027F8272027F8272027F8272027F8272027 +%F8272027F8272027F827759A4B4BF827F82726A0A67C2627F8272027F827 +%7CC9C8C9C8C9A6C9C8C9A6CEA676F8272027F827F8277DFFFF7D27F820F8 +%26F8F8F875A0CEA6C9A6C9C8C97C4BF8F8F826F820F8277BC8A0C8A675F8 +%F8F820F8274B26F826F820F826F820F826F820F826F820F826F820F826F8 +%20F826F820F826F820F826F820F84B2620F826F8204BC8A6C8C8A62020F8 +%26F820F8F826A0A6C9A6C9A6C9C8C97527F820F827F8F8F87DFD06FF7DF8 +%202027F827F82775C9C9C9C8C95126F8272027F820F827F84BA0C9A0C9C8 +%A64B27F826F827F827F8272027F8272027F8272027F8272027F8272027F8 +%272027F8272027F8272027F8272027F827F820F851A0C9A6C9C8A64B27F8 +%26F8262027F826F851A0CEC8C9C8A62720F8272027F82652FD09FFCA5120 +%F820F826F8F826A0A675F8F8F820F820F8275226F820F82051C8A0C8A0C8 +%7B51F8F8F826F820F826F820F826F820F826F820F826F820F826F820F826 +%F820F826F820F826F820F826F820F8F8267CA0C8A0C8A07CF8F8F820F852 +%27F8F820F826F82051C9A051F8F8F820F820F8277DFD0CFFA852F8262027 +%F827F827F8272027F826F87DFFFFA827F827F82026A0C8C9A6C9C8A02727 +%F827F8272027F8272027F8272027F8272027F8272027F8272027F8272027 +%F8272027F8272026F8277BC9C8C9A6C9A051F827F826F8A7FFFF7D27F827 +%2027F8272627F8272027F82027A1FD10FF7D20F8F826F826F820F826F8F8 +%27A8FD04FFA852F820F8F8F851A0C8A0C8A6C87551F8F8F826F820F826F8 +%20F826F820F826F820F826F820F826F820F826F820F826F8F8F8262675A0 +%C8A0C8A6C85126F826F8F827A8FD04FFA852F820F826F826F820F826F820 +%52FD14FF5227F826F8272026F84BA1FD08FFA72020F827F82751A6C8C9A6 +%C9A6A6514BF826F826F8272027F8272027F8272027F8272027F8272027F8 +%27F820F82726757BC9C8C8A0C9A67C2626F827F82676FD08FFA82720F827 +%F8272020F852A8FD16FFA852FD05F87DFD0CFF4BF8F820F8F820759FC8A6 +%C8A0C8A0A051512020FD05F826F820F826F820F826F820F820F8F8F82626 +%5175C8A6C8A0C8A6A67527F8F8F820F827A1FD0BFF7D27F820F8F827A8FD +%1AFFA8272752FD0FFFA84BF8262027F82751A0A6C9C8C8A6C9C8C87C7C51 +%5126272027F827F826F827F8272027275151A0A0C8C8C9A6C9C8C9A07626 +%27F827F820277DFD0FFF7DF8277DFD1EFFA8FD12FF7D26F8F820F8F8F84B +%51A0A0C8A0C8A0C8A6C8A0A6A0A07B7C757C757C75A07BA0A0C8A6C8A0C8 +%A0C8A6C8A07B2627F8F8F826F82652FD12FFA8A8FD34FF7D27F820F827F8 +%20F85151A0A0C9C8C9A6C9A6C8A6C9C8C8A6C9C8C8A6C9C8C8A6C9C8C9A6 +%C8A07C4B27F820F827F8F8207DA8FD4AFFA8A12720F820F820F8F8F82626 +%7575A0A0C8A0C8A0C8A6C8A6C8A6C8A0C8A0A67B7C51512026F8F8F826F8 +%F8F8277CFD50FFA87D2726F827F827F827F827F82727514B767576517C51 +%7651754B4B2627F820F827F827F820F82752A8FD55FF7D522726F8F8F826 +%F820F820FD0BF820F820F826F820F8F8F82752A7A8FD5AFFA8A1524BF827 +%F8F8F826F826F8272027F8272026F826F8F8F82727527DA8FD63FF7D7D52 +%522727F820F826F820F826262727767CA8A8FD32FFFF +%%EndData + +endstream endobj 70 0 obj <>stream +%AI12_CompressedDatax}g{2y+ fN4J) jn}#ٲ$Kd_Xh5}A?a9"IXϖ|l沐Q?[e*.f62a? N,N1itsL?u (| Ed(@e!D(hܣ6f? ƂjM%.,$,5zlFBx08|I GH8DIAPH4 j[қMn5_1%IҦ_?w2p8Ӏ8B%3fwaЀn{67 ߣT[0(MHuM 𧋇̬֋>3oGq?"H EAT aAՠ99D8hC|S0|v"AL1$h~ +ũx,H10T0R"ibHp4ă'sJ<4G d?G`r@Yr߯F!(_RZ4̘6[w[$1 j 9 *{ :Ma9ʭ 6H-_iS ح9k>}>waY֕'%maZSo}f׊?+#/J;tk 'Jܯ~@MAYKBy z +^f-,5-ζÁ4/0h'}0+"Hؐ޶p`a$A5 |RLHao'`S?XecL,L,j.@x.`>CXk1XͧFpNm4W@gK>Qp J}gg oiaMXCD-G ~ )P:oئWht`"_niQ6W54Y-IM#[E.Xפg!!+_uFX2g_3̧G0 +_D8FAt%&cXrx B+E$E(̽P\IԉŨ` ->!/a"#q +XH^@S@~no_[?  U;#a &a`(HZ'Q2 e*|hB0HK`X[ث,ðQ8#Eo|) W71?B$oKDgEy ]- aZCč5AE n Ah<~FBQXgHc^: 7E b z_4#b=9?3 Zr03TzD"$З-`{ZfD3̀Aތ鰭 G{3g=SN,5eHݢQ$c$iu+cX8$IJ\1cI\}j32b$(  Ĭ$)*xqHZX6Q 4d0 +~¸+brs=rR&')F:XEIx"?DH.ֹ $2ޅC`S4D ϣ`r U%* j5 z| f3-GɗsACvސkj? ֫Xd~k+ o^09 7f`NMVc |{0U>Ġ{mu, +]>ǎɼ ͟Ӭ3P#2ݒ!SýT 1}^{LZ?ENejOmec5zX>|Zmpx1cUؐ( +GXpK[p\+Nh΄^̗"t9Id:14ddc .r|u%i?>4\Jo/\1)0O4p8=wnW*ܫ_:X<AS;xթ; =OXC#a`AAMt`kaWUy.S{*߮M3ё<- +-?u +4 Zd#s0E^80i0cpآ[Y4H9g,܀ ЂG`NЙѝHZ/ ,nT#:=\-y- AD4 L8e6f f: kTSpfb, 0 d'-E؍+f -(KU?PJ~#i[] sd-/5 Ɋ SNiIr)xL@ԩ 0=cQ!ґKUH:}d* ԃ4 8q0c:`e J O+˰c,.F1]v@Vcq33J^xZLnk.`j'e#q[ӱ^j&k6lH4RqKYRnO;>tᦾWA*qYb@#sZ֔DemEx**NÉ +. FІ+fD4Ka?O9[THlZrXGR53\ ihwOqoyol)-&z[zܴ#r!bK}&Qpܧ0Z,P 2&{ {w&MjeV\M'Ct=7N ~cUhxl,VMRN )q഼G^#KHTv8}U vE獁h"AAAMbp^ ?[mϴJڕ/ <J{5lU1~/Ou©NWQ&J,ʼF>bz|݂&p#ow5/.@4b-Μi1Y"UVsdkDu`Wp^+)dh6:窲{h g=S渧L/,Ie|u3]#$>EN,0qNTPcnm=M91w(~m_V +OK#YD#m޼ss+ľĽc`E}&\5F l%,J oFy;l` ) 8hh\ |]I%J$Gcjހ |%ぇɈąh䳥=Nb&D4 tkI%Ne>z4{*07kU:._B-zE)"5lIW Qs48xC(wG"S2bF2Բ Z"I&| ʁUonn`}m4-vpNf_|q fD %iy信!cu|mBwr,t_tg`WvhuFЃD|v`gW[svpsɮm#}ww;[{KT$䠔G7hD@U1_]@Ξ01߼|{BrU&M)S=9`~{pTB$Ψk@y"1Z 8U2O:-& ~Jtۊ'u_Xz}~J?Q}y~@ȼ$fyT`?5jFba&Pa"nz_\Eq-gC,Azl;՝Ox[+xmvyC@u;ZV,v] k0tgebOjF.ժPGٗe=|c-ئ$ˠ1\%l$9()X([\}妛i|( 1Vb$K9j c\mMع{GgA9po|lc'ʚ6zNнK#U)k$Z_"[aq>4cntlMd;Na tp~5h\ަrwsKxu ._٦d@:.Xr#rƟ*|?hK'ob g8SkxPn.J1R]`{ -jtuX^/vm Yv{N<@b8#~~eO4Z<ȗtf;v+i[#j:p=sX{)\׽JLfv٭58Y\zqGW&Wt5CqjZH&d.~[lOX&*r/7 +>;0j`?E_a4?ј?ԻHSNհh sٱY_*7=3TeHhn =W<& +wZjs}K:ӌ/:~_^Fo/$3:jD`E??nT$$>hnyiVkp-ۊ縘Kke5Yv;ҨVm+=NL25(१ XYO%Y/{.:׊kmcjs_7AQxZ QeM=%l/.h`w#lv^+t@?hd:;# +DA 3WX*}glx7t2IQ@BHb}B&{*@ZvTCs:/Vax P. z92}C)|9u`8QE X[V,q?77z -2CP7gZ=0l3|{WЋTjh79<׷oe3;6D#pg"AQK0Vi"%&YtH{d}ia=Dnǟo@vBe BsYG>ceX@~ʵ*r&Upw&104^ Gа3jxg~-5]R] /Prx-0-.*J:ᨮϮ6`S["ٺ|bwq!+mp#Wt^rds 0K:@%7CZ:w`=H=\W_;Q*;!1#΁Q2^xXLw9Gg({C0R7 f^ - wL?bDd`}Zl44?_bћ\97yC<|vwwsaYӊB ,資?Cf`ZgjL%[鍫u eZڕOxz6;x^hΏÑw1~&cJAW jWONp>ӥ:=ϣdГ\>Z%w|bD41  ~}dzk-lbm슽fp8[z!4 '4||`r0zNC-Ъ(ې}vmghؾ*y'>85hF"w ~* sBJ^&6"Rd#Io.y+I`'Q ++;<ޕ )A| +.n}Q,WB5ikkEߜ?t]۫Uaq.4<{î c?[\m+ڱq6IX`3\ $)G(@q_C$fi&a7rЃ|M#_2Ͻ*zw7JBY=")QÅAդ 4&#)oq~At3B,oOŞB񑿵~byU\$Z ]lGp޸%`.C$lXYKxSāy@0Uk{YxǯГ}+ØiL[:+uyTeG%ʛI@ͲǀX0u*Ob7;LkU;jcxdƸ)6z-$:zC[ mNhˈ=_ɲ䭍 llRwPc,&+uN~Ks@O9_e=/z4Ojܸ?]Z2D^ @&DlWĐLq25U`d鐭ؽtؖ;л՗~3x_]?ELzLײj)I2I +!ث!jou/G}FѫՅ(VAmcZa-00jȗal_LǀfKdlaZcLR FWp¬t[*3 ֎,\2L~ZiS[dgDFP"Vs5[l8 +sR-"9l@ކ9<PCCRb3"h'fu!(lW@2p`]$wӥ#NZs"󮯑*1DIXcrxբHx8b$lD_+f&**h2<×T2|N +vO#-SN;K9ڗ 0bDCIxs|Jέ#teeTkJ.T8bU@Fi1i^AL݃@b+?r=%QG?!~*la~Q~tFsB3Cc؆<ҍn1XTwiL"O;0^`.*ݒ2A[8h(1LAI'$B%*C(}`  3/%b8ݨ%<7 ( G j3dݐp? A+ѥZ\.E\cTҠ,%Wdxx, i3Цi#92AqxhhpGܲ\}@Z gr&VtT?pX$zh\@ ۚe d^|f9á.30;*^ vч"ZC*s=^sbQt:UtnC#W TYvQ'=,sђ0Qt=d7x Lv-,pH>s@ jH PWUif#hk՚;݀X`p8Պ4lzBMiJ&d +8mW~l G d=Y1cyB}s8gBy I/9-JuL9 aÙI5Gg7R67hGsIF + 1˭qCNS5˝p|CnDKzܗ\7||D""X<%D4F5G &Rvz8W ^.93 p|+JRLR~ +="c&/* 4WKx#ՍF*LPuJ?9lң*yi=4-v3l3~CvFb7 %#:shd 1ޗ$Fx=֑l'3ñ E VQɥVcl D&kMNg099DNqX4L#1 !̒@Wmslf|sha!Ã/CL%h[#e +#hgb2nrTkAK7iYcȉ4F5>in!c#Z4Yt$sS+e-B.B#׸J%RY{{QPGxI.~ٙhrwTa#÷HN֓ + |!HFuݣeA ,y hcՐ@;\ԡhRU!"mOl>7F، ]["`QӶgJ_f6]GH#0{`̺,ZvKL0K9ѲoU!e-t~ {#c_&}\>vHmWw064/ 2yf0yF1'B3!_HNn8xOӿ9xTLdVe =ER˦"LMp+<@[ҟO½e {f oT( +oUYVÚ\"+XXffI-nNYou{\ؚs=6.? :ROD1_{u_{ FqqTͮ67n2 D5*)`>!yR%8`+Qvo@ia^mZ̧+OBuvZ=os*KG f$ʢ3Fk6uZGib^IFoP"3ƚ<8쫵Vf۝{C +Ј"qcW_qmQld5z^\Ó/5%۪q[ Q| [Յs%E"/[x3RMmž>9 + +59 +kPc'0/X5 7G&֎5;DHn$Sku1g) &U֍#&uaEˑK D1NUv??5~>*ZXQQ1s3ic}^/㱠&nŷDXz.sQXuDa{[ӟB4HC.7AХ[FX)GYJ܉ Ur/uy8{ŔO/Z Oć1]?;S " u6}YPO_WOᠬ V,''x^}sO඾%OUG>qx*#Z}Zu^Sf1+:z @'ºOoPtԫD^V>z_2Z-usN-c쮄2QvN8O= 4O*4^ɝk0qoxSY[Q4qNa9bxω6tb7y+vC8g3n?hk9 +^܎^Cs*a(sN9|@^H-@~& M"U<9C~[`z=p=w^OX=Ij5b5w- EjbK7(~=u4pj7Iv C.:X7 ]Cˈ/2.V@kJ+dZÅEGkSMHn #B+t-&XV.Vd";E+4Ry<~ F n4mt +GE;>"kpǩ,G# s. j[c8=sYOZ|?RktM]ɵ҉`e7D*^z%/@AQGqZy}xWd\]&!R_5y$Uv'%Xwn@G)g .aٰR 9U hiw^qCT"O}w +«?3 (b}a5S4&Э;Bh7#4Ո^RPѰHwh |"y`*}K\p,c$H^T-~{ ZȥB Q 3R0R=>CIx*"mu +u-x|lVS<>B':jOaF\qoF%:k:{pE?/9P|ythM^CbK A*Mu :k뙶wM2mt .kyB6ss(zMfR  4f@+}H#p6mIzǻGبCrjY3ULV3!ԇ,tK70mIc`74luf0-JƋ"R,5MIE6pҜd B M +VRaU\k)bnmonuV>-}[O>9s(8C#wZ*=-nSX`]C=fbI.s2ɵD[K٭eT6em˹XiĊa}#37$U )J \DI>`"?$jDyZ-d3 `V3T}T@«T덂y, +V|!֝,|nJl>7-(]#8=> ȅV)n:L{~lVeY@#lcq+7uܲf5;уCMk6 +ܐ;J0fз 1XLicT(W[@ I,$!7;df݀pf֒ %/XYF{i 4sXKAsTT 2N4$| ԏE@U T6m\@ʧxf.rɸoJP;++\ToՕ[/:Bs(-蘞nq(U>7pA GPfP! =QԂ"P@Ppyd&>c:heoO^,d\~T'F)ԥ7FĘɦv\*y$nQE&cLM54Zͣ$xL50 +nZL 2n9h +76$4-?[iHmۅ#sG4r[XHشwqs@$bsbB(N`hcz#͇!A=i^9w +[_GMNY_rAH~h+fGs4 زc-k?8[x{?F8*\\U:N436S;t߅"} 47 -,9QhXnZipڱ K-0Ĩ[l-de*!CRˍ K5'f. 6'L0L~ z` + Z/ .۪\,]꓄-y{Ŋ \*|twޗN;W9q-J?E9q-B^g\ǗK/3;S.\g\МϸLQxp9q-Z|vZ>88[y`9"Zkl0h >J$H5ni89 XbIVQߺW@J!WƦs-H*E@JT"΄3SK2c81*PH~9 WfyI73\~P]7S@r0]&rc\U3÷e&q¶w+IJ1/Af1Fp`~B }dVh'\_Fc#[P.`A"0R0.3y֊P?f쎣(3Ie~αjJAň42b6&%J򨧆I=&G ,JtL;&nBh&{ SQs$L W"i RFlQwp\bsשL3c01;7D+\zdฬr9P Q6Q.t['X1.jXlgHi1+Q(0$SE JѼ<+}2-:fWN +^xi:;Q..d"F1ov:Oc\_E*D[G]^u nɎGC=v:g'+ gE'*9E,r"YXv"Y"YEU$wеy٫Q%o$ȣOC[a0ޣZ-Eyss<0s*Sa(Ta.ʓ%Y0,FvV_~S\(6V/;>l@hXQu&G9n9!{,~kE?tNwޣL z/n6q{Sl +֑+6nmt|3H6sl^P_\BT0]2sIʯb?^` OIJet(HT5~&֞q_`U^lʲ;x;pᣢVݑ~]B/bDv~wQߪ +\VH؏6*l|nc-PnJeLZ^nNXاEq{>-K\0OWا;C׽O68Ov>aS[WRاUv¾ir +>EaID-p>}9Aaw➶ONZاe1iͰn\>ez6;37>/:ua)n>OCU) @+,>EaVU)fj,E4><]اU']NTاUէu_ا@^r>># j}>*d}Z}LaVUKݍ +CRWaOf>vGػu攁U1"G`2Xi>&-~XwEKGr}FPdp?zSz ؤ¸Z'meS1LسO0blt]vpZd` OhO.GG_fzfMѽG]'2TJ?E0/ei~am;I^(֛p0wGț׮k 1QMuMgbYvQvZtīwJRH*,i*lyW4m*B5 IIаrR4$rvOSy>хt V3CDpwHڷxCML^Sؤ%'R60)u6 +vq՞? <41c,Gj\Ɔ͆rB̽J(G!x'8ƀdjs m +H.;HP̪()(Er"yԸzϽcӔ40?LS{DS}H? h_[T_? ?tjW)s#4Tվ? +[[Kwvt;ޟh@ q S@Wl^Y[KeVo4̄ܿ>1ԬVbckB*UN^53+#ՏAiF~dD=BOXhKf ߓ:Qb]ƸJxUTH%i +QGTFvȣݝ(( GwceϺ۶{/nmS4ډ.lPFn 1aXwJ05?9 var"8(Bۄ|\6pѷ޸^<>#ʑ:ϳ K&+ |I*64%.{okU東Q%̮ uE­W٣5קŕ׍}"XKV2s=+6%#*.]ΛNf"$K令8ʒCW5ZGr5Vz*ִ<^tL +͢|jWƕvgl颬i0G`$zD'8 ͗_C<aGc1Wgtܐt I3όu6RS84{$n)8w&ڋU_90]m٨{$n)H%. &&nXg UZ9`e }rnj7ZI=feP8uG:t5喫ibT0M.3 8{mIlNO;xZߝ>;9 $t3vm(::㤢hԢNrutx` Yk_{ E=}K_B?YQko0bd!K4gX3*aTUS%z= T쌎{1e/x*~R[[x{%_&E_ױ3T$ }0Gʹh/I4;f5O4UǻEzshI宗 {`I~XI"n=f$QTZV%f1pVZW[N٫$sϒDM.ѨG4J*ILQKB9 + ڤxl + 8eh4'B6ﻬPI +qvG^V( +e^V2pI1Qw0N:CSP(ӼPr3r.<9P fZ_xh\xvSЅ'< C"]x׺ {ٞeDjDPgpmqP%!!&ν.<4 J + <Cz\xHbGЇ^xh|ۡǝ_zۡr慇x]o|ۡ[*(4׾ѝi1Vnu yql .&Kv:.MԑFՏиT_qa0A{ᡢM)/<4ssԅ޽Tf1[LYdvmԂ8$Du"i^^r:,_Nz\֔FPo`9U }6_kH?ou7-,EM}-h/8 85){^"zc'o]s=)Gy?xAy oE"o|LHзz\\s^e5){v\~+|zo tD?#SP((_F:y +eZXŸa Nĥea8bI\RbF+J>y=KrwjvZ?G&Y}o#YS+hE lN)Jd7Aô2Y޼ !QNX" ,.wҟ!; )Qh85]GNU'x +b rlx?1r:ܔh NCc+"_pw6"@_`O +yv~,>{e"(G 8YT=s#,=pe, FX*v#Ik?H "<+KPY" 0[=S9T5~xD)'|.t7X|{k.^ -w, ZS(1l8w +B+*U{u.9yԱ,SY'џrZ|,0`wŠeÚϜ¡ǚ҉=ΜBiJ2Ln9ew<ąS@Z9]ZD}ugKYB8`2 z? +?"e#D_M3}d/ި) +Ɋ.j6RGJk$W~sT'8=ި&9I9/3@P/X$\AlS{շ3_yst|< +ԃ N=huChzh a;]ݸ}O44F.Nm]tj*0|бfzL={ +q{mv71/]ڗ8hXš%vj>\i:{=<,0es*='Ƚπ{j207g3jF8Plk^b+s&ݼcr_tcY\rt,l1"sF>8`lv8CNG2M=BA9;PXP&gp{%)|~oM|>3H$ >lb;ޑk^M7jL9ߊ/u QL\m!N,*PehN8>|Xw@OaC:ǣc=5x,>4`xW}-G2bw6w37ƢSzը2?ȱu8sS4r+[L\qartnƓ +F]B(dc@.Pw/ntAlrE.8[Kl@,JPL^N4iY`Yiz) g4om&0}\zd7/w2B4ŭ7ɫE W wgWtQz0ҮwMy WwWS}s ?7+Wv(=L<=jrDkYW[d|M{쟇J!*\ tH@:I#%,扐z.OP@Ly1bF]"$fK D"a$д&U$5y+3$g]w3?M~.3G9xZ&H2Oq`Y:JN/$BشІ==f $5H5/7*D":iBl ^ +97rF!礗XߺT67w!["P0k +C 5OkJ"Iatج㏑XDRB->{dR9ÈLGҮqqߤVϕqN8 6_K YUiqHQ嶴Nhr @==8PnN+Q)Gn'$f$e & LiXaZF=N Q?<$OH$CߵlJ-J%6R@# &'b>0t[]T}mN"T3|uTbeV'W}H/Q= ny-UU܌j=_ds@wB.-) +'<|**BeKS?74[*:4pe Ү'{E ]NKmS?Juv.\Sib!f"b5WjG:lҵ!_jZ޺qQf +ZNkE_ʹ,CsC67uG +RX|j9<2tiJ@BhUdhAͯZ@Tb<=\hJ!H8aڻuPǒlfߗʾBiCBC=|wfd;^P}NlFO#Ghzn]n{pi|$K2Rzp]gz_~lFu6RĄvk웣ӥ<E)mglpGnY8ЗGb둱pywirvâ2=ƇmmiYSE\#ucCun.ܟ)6GyV[+3po'W޵&[/lLYdu&(kP&PJkP&Pʹ (M.;l/oD[ c-uq+22 ]z>?hCK/(cnv L՝ł63nt_~NHboXIoD~oaO홟fŰrNH|i-#~BV&q@dž/2 Wqt:Y̕NRj>9AG:~ Ok* ?V)^AQ(2߃f(E7}nU˵};'6>2'Wd0EC|PB)H r,|ٸDK ݯFg(}  +;%xJyD0\tW }75QPNZƦNZzH'- Zt"vUJ~6r:ڜ 64d՗~h.v6Z,xU9书?5y6z׿4~qx9 GR,Wl.h_G, +H%N؜|_4x4,w6 +1eDޅYxod&I4%%%^ o[NmWvǎEՇꝂ5+s* UcxtJ:|rz{xKd2HNoVUU6䘷,*"|t=bbӫo_:\9i zYyGMݐnxnzfn]<4Z7XnMN7@yȹݹ,]lo3~o"8%5#|aZ&sycxJZǒJb8~c+y,Oi##($w+kʏn>ꋡؠ|>51 J_ߪ$G*~: +0&?ܽA*_;IYp d*]Xs'[3JN½|.u;b;NRikf@æv*<_&Msz(yS2cd +=zckBsԚl ?0p0aN$=\*RFe$c +Z[p|OCL}E[;-X'sJmo0i_j;,DV:JgͅyRjctp}mouAT3 d ã'o,scxѥ>o|wx m]rlho6bBR‚]87&nRf!]Z\w-u]ZP0KM1`e@ᚮ,8NR}㘖MnC;$6 Ӂp8ABEGP6nY7\v(@̅ iEG +h(QB Q:4tigoZq\jåbZ+mכ%$]U˞)28W)T|t~NheR)A2P2' Kê,O:%#@+bB62z+PڶǸ7ڳY-3mRvKWʥG|BCor8:R3$k؝$f=i D+ I :La$!X.K +H Zd. J"ni Qlaa40,Nq#Yrt'jo @ ~P+ma]T0i FW#3$X8g$Nt`ʰ`cX2I2iąIPdH Mڶm0Ri al!q +BbI%TX- "]T@z$6O;ˆ 8( 7Kp+oZ"}P 0ytV +D -,&IPdH@Ƙ ׎cpWK#9qk)@$YB a:_ -h"|E'Hqxx>΄&L B:( XÁq-Xf#~c:$=d&A#q2[跮EK#aU>JPx0SO,0/F4bpan6'x$]ۉs2A$F^[l x !<&ᶅuѭ$)0Lp@tX'@<QJ HL +8m^;#€c'u83ԓ K8!L "= -úE'HW*- mgH &U_ {# )F=HHBq@{!8dcNF$(.Af[S@ m )<'APqBcFz#d#]`SF2m1!\Cܙpt-kUZAhE_nZl900VzW6K`*^Z6^X-6hߵ4a_&ɫo[}?'~RivSq5hP]*Q * ݬcD%bD%|)ޏmm|eݥ(;-&U.5JTWS(jϛSmChKj5Yqyv隭kk;``:;emvʗtŇ8w6FVpkAUgHV=5T:8lPt W"';&FjkT~CuD5D\|^Siu`,S^o#STZ8cTڟvPjPV=\XJT"FTzVhk5lU~1s2~:^`cv,shqS:cP$D5D\|v?NS%*#*ª{U~ !{)H!=ܫ\vveaxQ _gOjS﹥Cןros6ls۾3x;{fԦws}sd#?0>c?-lyf84%dgL=JslºjPiuJ__TT".>w7TJĈJ :)syس +eS93Y*}%8[77OgNp:23}t&C熯7}8ma3iw(|Q3 +_ή$>5jUċӃEzJG_Ǩ[mJTJŇcD%bD׸zpZ'Ekt{G7t^p]ߚ^Jd~[߱xB1s2d*~ woZ d0ۅCi&&FYы ˬuz3F(V,'!K ev}߸BS,V\0txՏ._YNw5w34sk~I+ÓR>ۿz :.Wlqɧ?62qy\W?~sޭJ+;y%^#vCk+aԿm/7v5E#!:.I+9,蕝F4O{.:_t:в'YWGWir_yAysi~sfuв'=S_i?sJ3G[f} =(z#Y7hKmOz/vz(~]KƟ.3 Zo?I}E!ew6jDgR?C_߀^~?~dolT/hgTct/*MAotLfs?m~{^VK"Fk*&si[\oKz2Ppojꣽ? J}I2G +0Zm.M˰KؖL),̔:t3npv1ɵ;IFʴ sw).,CLpCбu?x)ڔ G3G9aSW'2]5wu6昘6 CsBT./uJ +uqG2Khh$RQO`T3S蘻0̎hkYzRi)+eҝ4dS5CqhBHmr0 b +;&@he/NJF܁h(V4D*ʡ(kL)-wC%8yWGI~dGEC%GWk]@w&lALXBk&'R ҟ1FK `ʠL%TGs[Bh `%2mh {v4 0&(p};5i0UԻHeT4YY\C*'2CtMb T' ! c]j+OL@Mik߀YT!Y^> E|ⷱۘ"\xLU9eSlިN쵦Ԑ#G | !Οmrdm~A~AG8Ia[ `8Z\*8&$L4v6e>t +(80 ,0vTgTC)7oY0Ax)b)mB7LCb5737q2&E11g.ga7/G +# yt9f +Q;IQ|Y <7އZJc6lF w`ε1]0zS})Lt  DUr :|%cu: fu.Cq Cf(ʎF?6ۗ4l C%u adCebya$;n\tɣk-%N$'@ +$DA|_RelZkK&}Mi#~X^a7 A}0喠5*So9T\k:zm&M0nQ)xWx_B9 +d0آ@bnއ}%"+ jiѝȆ0C#% 5p4P@mEB 2CFZ2! ."nzB7Kn;zuvпM +ϕ='q;UnWޚth򈰉02 ߤbY"u*ΆQ{7=笍Ț`>b&@ CUƴZ2[*r~=ïo:^Gˍm;M;̒,Y(. 5JܖDctFxR|ff ++p;C <%3?6D&%Y:@,qd7A-a)%f,lLoC='H@_!"ɀ@pڈKk.(ްw9WWkwRφ?)WHY4 1hΆh60IW&6X3Xr1nT7n6}?eʜ"麶4M f,f%J3&&75!Ƃ[8y]@;0 +UuɄ bXIR(6V}zvA2T+WwRy˷HjFYwRPappqc- +endstream endobj 71 0 obj <> endobj xref +0 72 +0000000004 65535 f +0000000016 00000 n +0000000076 00000 n +0000032391 00000 n +0000000005 00000 f +0000000007 00000 f +0000032442 00000 n +0000000008 00000 f +0000000009 00000 f +0000000010 00000 f +0000000011 00000 f +0000000012 00000 f +0000000013 00000 f +0000000014 00000 f +0000000015 00000 f +0000000016 00000 f +0000000017 00000 f +0000000018 00000 f +0000000019 00000 f +0000000020 00000 f +0000000021 00000 f +0000000022 00000 f +0000000023 00000 f +0000000024 00000 f +0000000025 00000 f +0000000026 00000 f +0000000027 00000 f +0000000028 00000 f +0000000029 00000 f +0000000030 00000 f +0000000031 00000 f +0000000032 00000 f +0000000033 00000 f +0000000034 00000 f +0000000035 00000 f +0000000036 00000 f +0000000037 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000035173 00000 n +0000032792 00000 n +0000032996 00000 n +0000039463 00000 n +0000034024 00000 n +0000033061 00000 n +0000033462 00000 n +0000033510 00000 n +0000039350 00000 n +0000035688 00000 n +0000035772 00000 n +0000036154 00000 n +0000039537 00000 n +0000039711 00000 n +0000040999 00000 n +0000061299 00000 n +0000092851 00000 n +trailer +<<3C78A8342581C9459377D8EF17C1EC5D>]>> +startxref +92990 +%%EOF diff --git a/data/tr1/logo-dark-theme.png b/data/tr1/logo-dark-theme.png new file mode 100644 index 000000000..6ddef13c2 Binary files /dev/null and b/data/tr1/logo-dark-theme.png differ diff --git a/data/tr1/logo-light-theme.png b/data/tr1/logo-light-theme.png new file mode 100755 index 000000000..9ba6c079c Binary files /dev/null and b/data/tr1/logo-light-theme.png differ diff --git a/data/tr1/logo.ai b/data/tr1/logo.ai new file mode 100755 index 000000000..2cc0ab4a7 --- /dev/null +++ b/data/tr1/logo.ai @@ -0,0 +1,1929 @@ +%PDF-1.6 % +1 0 obj <>/OCGs[26 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <>stream + + + + + application/pdf + + + logo + + + 2023-09-25T22:02:55+02:00 + 2023-09-25T22:02:55+02:00 + 2023-09-25T22:02:55+02:00 + Adobe Illustrator 27.9 (Windows) + + + + 256 + 148 + JPEG + /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgAlAEAAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9Qyyu7+nHhQ19Ubu2+Nr Tf1Q/wA34Y2tO+qH+b8MbWnfVD/N+GNrTvqh/m/DG1p31Q/zfhja076of5vwxtad9UP834Y2tO+q H+b8MbWnfVD/ADfhja076of5vwxtad9UP834Y2tO+qH+b8MbWmvqjdmxtad6dwm4NR7Gv4HFVyXO 9JBQ+ONKrgg7jAl2Kqck6Iabk+GKqX1iVvsL/HDSHf6Ufb7hirfp3P8AN+OKtcbod6/SMVd61wv2 lqPcYquF2vdSPljS239aj8DjSbbFzGSBvvgpVXFXYq7FXYqhrWnNgftYSgInAl2KuxV2KuxV2Kux V2KuxV2KuxV2KuxV2KuxVZJErjfr2OKqMLtG/pt07YUInAlaY0Y1IqcVXYqomZhNwNAvj9GFCp6s f8w+/AlsOh6MD9OKtSOEQt93zxVRt4uVXcVr0woVfRi/lGBKnPAvCqChHX5YUL4JOaUP2h1wJVMV dirsVUJYW5c4+vcYULfWuB1X7wcVd68/8n4HFVSCVpOVQNqdMCVXFVCWd0fioBwoa9ef+T8DirvX n/k/A4q715/5PwOKtxTu78WAGKq+BKlPK0YFKb+OKqfrz/yfgcKG/Xn/AJPwOKtfWJh1Tb5HFVWK dXNKUbwwJVMVQ90v2X79MIQVdG5ID4jAlzkhCR1A2xVD8LiTqaD32woWSRemwBNQe+Kq31WPxP4Y 2tNpbqrBgTt2OC0qdwS0oQ7KO/z74UIlQFAA6DAl2KuxVCEiKY8dx3GFCKVlYVBqMCW8VdirsVdi rj0OKoe0/b+j+OEoCIwJQ8n+9K/RhQiMCXYq7FUPH/vS304UIjAlQu+i/ThCCrL9kfLAlzOi/aNM VUzcxDpU40tqUQMk3MCgG+FCKwJUbo/uwPE4QgqkQpEvywJXYq7FWmRGpyFadMVbxV2KqVxHySo6 r+rEK63k5JQ/aXFVXFVsjhELd+3zxVQgiDgs+9en9cKGgWgkod1OKooEEVHQ4EuxV2KuxVx6HFUP aft/R/HCUBEYEoeT/elfowoRGBLsVdiqHj/3pb6cKERgSoXfRfpwhBVeXGPl4DAlDxRmVi7nbChX EEQ/ZwJXgACg2GKuxVCyH1ZQq9B3/XhQihttgS7FXEgCp2GKqbXMQ71+WNKs+tr/ACnDSLbF0ncE Y0tqiyI32TX2wJUGhlWQmPoflhQ3S7/zpirRiuHID9PHbFUR8KL4KMCVLlHMGTuOhwqtt3KsY2+j 54oRGBLsVdirj0OKoe0/b+j+OEoCIwJQ8n+9K/RhQiMCXYq7FUPH/vS304UIjAlQu+i/ThCCqMpa EgdSMCVO1cUKHrWowlAV8CXYqh7iV6lAKDx8cKFSGIItepPfAlUxVZLMsY8WPQYqhXaRgGb7J6eG FCIjhh4hgOVe5xSqcE/lH3YFULkIqgAAE98IQVog5IGRqt3GKrknZTxk+/viqIBBFR0wJWyPwQtS tMVQ4WWY8mNF/wA+mFC/1IYhRPiPf/bxVq4WoWVfpxVWjfmgb78CV2KuxVx6HFUPaft/R/HCUBEY EoeT/elfowoRGBLsVdiqHj/3pb6cKERgSoXfRfpwhBVl+yPlgSozQ/tp17gYULoZ+fwt9r9eKVXA q2SNXWh69jiqhG7xSem3Q4UInAlTmRGoWNOwOKqKhwTCwqD09vfChXhjMa0Jr3wJbMsY6sMVaKxS eDfI4qovC8Z5RnYffhQ5pY5I/j2cdKYq1by0PA9D09sSqKwJUJEmdytaJ27DChoLbx/aPJsVXq6z KygUwJWWrEFkPzwlARGBLsVcemKoa1NGZT1P8MJQETgShmPK6FOxH4YUInAl2KuxVDKeN0a9z+vC hE4Eoe7IJVe+EIKIAoAMCXYqoTwV+NPtdxhQ3DPy+FvtfrxSrYFQ1yQZFA64QhE4Eqc/Ex0Y0qdj irkAijqxrTqcVUf3s7bbKPuwoVBapTcknG1pRlQJIFQknFVWGYk8H69jiqy4jCMGA+E9vfFVszxt x4ChGKomF+cYPfocCWp4y4FGoB1riqkI7dftPy+X9mFCpHLDyCoKV9sCVM/BdexP68KETgS7FXYq oTQnlzQ0PfthQp+rMfh5D8MVVoYeHxHdjgSq4q7FXYqpTwhxyBowxVREsw+Hl+o4UKkUB5c3NT1A 64qr4EuxV2KqM8IPxghT3r0wqo+tN9nl9O368UK0MBB5uant3xVWwJWPUyItKr1J+WKqV0xqq/Th CCqpwVQoI298CV+KqMcLCQu5qe2FWrpOjjr0OIQVz/vLevelfpGBKjG8QjIYfEa70woVLQ7MPpxK hVlVWjIY0HjgSoBLYdXJwoXo1sGHHr26/wAcVWXO0it/nscQqJwJdiqySVUG/XsMVUKSznfZfwwo Xm0WmzGuNrTULsj+k30YqiMCXYq7FUM5aWXgPsj/ADrhQv8AqqU6mvjja0pqzwPxbdcVRQIYVG4O BLsVaZgoqdgMVQ3xzv4KPwwoVPqsdOpr442mlkTNHL6bdDihE4ErGH7xTXrUUxVRuh8ansRhCCu+ qqejY2tK4FAB4YEtK6sSAakdcVU7kgRU8TiFLk2tt/A4VUYfRofU69sUL7T7TYlQrSceB5fZ70wJ UB9V9/xwoXottyBU7123OKrbvqv04hSiMCVCW4oeKbnxwoajtyTyk6+H9cbVEAACg6YEuxVDSf70 r9GFCJwJdirsVQ0JCTMrd9sKETgStdFdaH6DiqHVngfi26nChEl0Cc6/D44Eob4538EGFCJVVVaD pgS3iqGYh7leO4BG/wAsKETgSpzvx47VNdsVdNH6ibdRuMVU4Zgo4PtTocKER1wJQrgwyhh9k9v4 YUOYtPIANlGKqlwwWMKO+30DEJWGJFg5N9rt9OKF9qtEJ8T+rEqF0zqqbjkCaUwJUgbVuqlfvwoX pFDyDK1SN6VxVZcbyqvy/E4qrysVjYjrTAlStUHEv36YSgK+BLsVdiqHk/3pX6MKERgS7FXYqpTw 8/iX7Q/HCq2Gb9h+vY4oV8CVrorrQ/QcVQ3oSc+H7PWvbChFKqqtB0wJbxVDzTFj6ab16kYUKkMI QVO7HrgSqYqsWRGkK9x0xVfiqySFH3Ox8Riqj9WlH2W2+kYbQ2LViau33Y2tK6IqCijAlCy8hLWQ bdqeGFDcr+qyonTFUSqhVCjoMCVKaV0b7NU71woWg2z9RxP3YqqRwKjcga7bYEqQ+O5r2H8MKEQy 8lK+OBKGjkMLFHG2FCo10gHw7nGk263dnLljXp/HEoVsCUPJ/vSv0YUIjAl2KuxV2KqU0PP4l+1+ vCq2GY14P17E4oV8CXYq7FUPNMWPpx712JGFCpDCEFTux6nAlUxV2KqE0JrzTr1IGFDcVwDs+x8e 2NJWozPcEg/CP1DFC5ZWMxTbiK4pckrmYo3TemKFiExTFWOx74q6abn8CCvviqrDDwFT9o9cCVTF XYqpvbxt0HE+2KuoIYTvUj9eKrLVdi577DCUBXwJaZFb7Qriqz0YhuF/jiqnaft/R/HCUBEYEoeT /elfowoRGBLsVdirsVaZgoqdgMVQpBnkqBQeOFCLAoAPDAl2KtMOSkeOKoUcoJNxUHv7YUIpWDCo 3BwJbxV2KuxVTkgR9+h8Riql6M8Zqm/y/twoWKZkctxNT4g4q798ZOYUhvl/XFV4t5XNXNPxOKqo WKEVO1e/fAl31iH+b8DirvrEP834HFVQEEAjocVdiqGnYu4jXt+vChEKoVQo6DAlvFWmZVFWNBiq wzxU+1iqnaft/R/HCUBEYEoaUgXAJ6CmFCt68X82BK8EEVBqMVdirTMFFTsBiqGJed6DZBhQiURU Wg6YEt4qp+unPh+PauKqmKtOiutDiqGBeB6HdThQiVYMKjcHAlsioxVS9bg/CT6G9sVVQQRUGoxV 2KuxV2KuxVCsTNNQfZH6sKFb6tF4fjgtLTW0fE0FD2xtVls5BMbdR0wlCpPLwWg+0emBK23ioObd T0wlCtgS4kAEnoN8VQqK07lmNFGFCr9Wi9/vwWlfHEqV49+uKrsVWPCjtU9cVWm2i98NrSkpaGXi TVTihFYEoaQtLLwH2RhQiERUXiMCW8VUJpiT6abk7EjChr6qeHX4/Dtja03DMa8H69ATiqvgS06K 60bpiqHjLRS8D9k4UInAlZJEj/a7YqhnRonAVtzhQv8AWnX7S1+YxVsXfiv440tqkU3qV2pTAlq4 cqlB1bauIV1ugVOXdsSqrirsVQ9yvFhINif14QhqGIu3qPuO3viqJwJdirTiqMB1IxVQtXFCh61q MJQERgS7FXYq7FXYqhpiHmVV3pthQicCUNGRHOwbatRXChE4EqE0xJ4JuTsSMKF0MIQVO7H8MCVX FVKaEPuNm/Xiq2GYg8H2I6E4UK+BKGciS4Xj0FBX5b4UInAl2KqZhBlElfoxVUxV1AeoxVD2wIdq jbCUK0iB0Kn6MCVG3cqxjb6PnhKERgS7FUK1ZpqD7I/VhQigABQdMCXYq7FXYqoy24Y8lNDhtCz0 bj+f8TirfpXP8/4nFXelc/z/AInFXelc/wA/4nFWvRuDsW2+ZxVVigVN+reOBKpiqnLCsm/RvHFV L0J+nLb5nChVigVN+reOBKpirsVdiqnLCsm/RvHFVL6tL05CnzOG0K0UKxjxPc4Er8VUplmJBQ9O 2FVMXEi7Ov8ADGkLxdRnqCMaTa76xD/N+BwK76xD/N+BxVabqMdAThpbUWZpJOSrQ+2+KFT1Ln+X 8MVaZ7kggqaH2xVVgj4Jv9o9cCVTFXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXGlN+nviqi31bvT6P7MKrD9V9/xxQ4fVff8AHFV6/Vu1Pp/txSrClNuntgV2KuxV2Kux V2KuxV//2Q== + + + + uuid:edd2effc-90f8-4921-9f87-5c443560c5f6 + xmp.did:0fb69b92-9296-f643-8766-fd53738d52f9 + uuid:5D20892493BFDB11914A8590D31508C8 + proof:pdf + + uuid:87d7cc42-d24f-554d-97c7-b76979e61089 + xmp.did:68099ade-e32b-42d3-94ec-ef546c954f1c + uuid:5D20892493BFDB11914A8590D31508C8 + proof:pdf + + + + + saved + xmp.iid:0fb69b92-9296-f643-8766-fd53738d52f9 + 2023-09-25T20:39:44+02:00 + Adobe Illustrator 27.9 (Windows) + / + + + + Document + Print + AIRobin + False + True + 1 + + 500.000000 + 280.000000 + Pixels + + + + Cyan + Magenta + Yellow + + + + + + Default Swatch Group + 0 + + + + White + RGB + PROCESS + 255 + 255 + 255 + + + Black + RGB + PROCESS + 35 + 31 + 32 + + + R=240 G=243 B=245 + PROCESS + 100.000000 + RGB + 240 + 243 + 245 + + + + + + Grays + 1 + + + + C=0 M=0 Y=0 K=100 + RGB + PROCESS + 35 + 31 + 32 + + + + + + + Adobe PDF library 17.00 + + + + + + + + + + + + + + + + + + + + + + + + + +endstream endobj 3 0 obj <> endobj 5 0 obj <>/ExtGState<>/Properties<>/XObject<>>>/Thumb 34 0 R/TrimBox[0.0 0.0 500.0 280.0]/Type/Page/PieceInfo<>>> endobj 28 0 obj <>stream +HtVˎ%5 W*qrfÀF,Ͱ9vv#PK8v8o?݇;m_7&q&c=^JnL7m+UbUmRҔ_8yv-}ڕjiRP"u y#C+I+ZjlF[kފN/0ohB \j7n)I܅j7A[XjQ:5](FXu$F> c.:W`{qVcE5<#R#[j|E:0 +GK^yhepǵZ :L($;וwcPF bMY$1M=lO øj`5,A{m=.)u|)8y)8Z?vWh yʼn/8$W sG;mX/H@v*!?I.Ae_D/= kTY:Wf$2ep=1:ưz|@-"C>Ճb"ܴX22q\3UẊ[~|NִĢ T<9tX|AcCcOߥAGҜvɯgLOO6\ +endstream endobj 29 0 obj <> endobj 34 0 obj <>stream +8;X^=5o.?\#Xn-2(L25&YEN$lU,*;!Uh1AS%$c3+>KWG@qFo`/G#c9OH)C+/G4\H3 +5*DXT[ccoHM;1+qUDUP-OnC@$LN9bA[,4W;?T8da$?F+FF,edHIh2KH/P@8Pmr%ds +[,-5?BCR$`1V5ph(sVt/UiKnqH^of[ +endstream endobj 9 0 obj <> endobj 11 0 obj <> endobj 12 0 obj <>stream +%!PS-Adobe-3.0 +%%Creator: Adobe Illustrator(R) 24.0 +%%AI8_CreatorVersion: 27.9.0 +%%For: (Marcin Kurczewski) () +%%Title: (logo.ai) +%%CreationDate: 9/25/2023 10:02 PM +%%Canvassize: 16383 +%%BoundingBox: 619 -327 1108 -47 +%%HiResBoundingBox: 619.99564826068 -326.542200908796 1107.63695635689 -47.6737990912034 +%%DocumentProcessColors: Cyan Magenta Yellow +%AI5_FileFormat 14.0 +%AI12_BuildNumber: 80 +%AI3_ColorUsage: Color +%AI7_ImageSettings: 0 +%%RGBProcessColor: 0.943904638290405 0.953717112541199 0.961130082607269 (R=240 G=243 B=245) +%%+ 0 0 0 ([Registration]) +%AI3_Cropmarks: 613.816302308787 -327.108 1113.81630230879 -47.1080000000002 +%AI3_TemplateBox: 298.5 -421.5 298.5 -421.5 +%AI3_TileBox: 455.111416826001 -472.745786865235 1272.55140973066 98.5742204589842 +%AI3_DocumentPreview: None +%AI5_ArtSize: 14400 14400 +%AI5_RulerUnits: 6 +%AI24_LargeCanvasScale: 1 +%AI9_ColorModel: 1 +%AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 +%AI5_TargetResolution: 800 +%AI5_NumLayers: 1 +%AI17_Begin_Content_if_version_gt:24 4 +%AI10_OpenToVie: 107.557309503033 306.532287769785 1.93055555555556 0 8752.6724174167 7952.96411510791 2950 1902 18 1 0 68 181 0 0 0 1 1 0 1 1 0 0 +%AI17_Alternate_Content +%AI9_OpenToView: 107.557309503033 306.532287769785 1.93055555555556 2950 1902 18 1 0 68 181 0 0 0 1 1 0 1 1 0 0 +%AI17_End_Versioned_Content +%AI5_OpenViewLayers: 7 +%AI17_Begin_Content_if_version_gt:24 4 +%AI17_Alternate_Content +%AI17_End_Versioned_Content +%%PageOrigin:-8 -817 +%AI7_GridSettings: 72 8 72 8 1 0 0.800000011920929 0.800000011920929 0.800000011920929 0.899999976158142 0.899999976158142 0.899999976158142 +%AI9_Flatten: 1 +%AI12_CMSettings: 00.MS +%%EndComments + +endstream endobj 13 0 obj <>stream +%AI24_ZStandard_Data(/X|oq +,Ldj-YwgQF\7NSWo: 3 -2ݖUzN8缯 JCQIf ]p@$0H\m!%QSX"Kh8Xޓ$E%wM) |q +ZѦGueHܬ +\.evV~SFDU7b K0}@$.a8"B`%M gKq ' ͘0zV +aD*(. sEMݻmW ! %:[|7s2Uͩ!gû}vd+fl^f: ppFvtMDXqHys)Ciϐ, _ʞ]*pH$ qT02evܽȊUf̙84g9Y!29ojәR[eZDê !mUU!VPӃ63S&e/viT%tT%ve䡕!?k)ь7FIܨXbqL"q\҆q+!pD$É=+JZ U=; =5[p uYLx !1WэPX<#H2?/' c a 0<*ː2kQM/;TVxӮuTFx6VFfXߵ.1TvpXCCa8Vm$0A Z\QCaWTֈ;ppqXq4 +H|app Z(yTPbKO:<,CLº^T;t7gr{Vn8 +E&faF@:j8 la5 !3p;Cc@VZ &1 b@a83,FpHrĈCh%R ܪ0I1yX%ˆR / "a8PYцCBa8a8󠣎;8@PEH(\|q,3!18ELD3*W%Hx" š0a8dCqȅy0JIL P$WP+QÑg +#a XDNafu.Jx 6eHStٍu9:[q?`9$dBVKı3 ЎUs%d9n 'n8UӆbCd8 MYGŤc OV M5oLPmƄYO=Vr]Rkʐ̹[!^d+=K{*5ˊtz),CN&ѽs3,CNq0qu1P a- XM""18qq4;+AU`2A +"v;5B')yd"I.nqpHXG,9栃~ Db"PP$ÁpxP`AdȔ !RFShwsR֝Xģ_p`߆# uqW|1(Ì3jq#a(a8AGv<|0@Cq,A8DHG@$ +1 JTh0xH D"P$Q8EHWCP$ +B1 Z cq,BX,PQ)LAQ,D"H #hp%(1 "@ 8"8D!8qȣt,q781Gw6gfӒr 4H8=;áNQp0 8C#a # ߶m8 %p袊&(uӪD`!U) Ixc4aH 0<sa8̱0P #a$ 000 0nذFiFgaeAc,'n8$qnV8a6l>ex4#|:31 T`xGc"a!ᠢ5j5).q;8_^Ύnna +KjgfecRT2ʨWVpZP :ctnljhp@b8X@(P@(H pH +, A.X@ 8Al XX B`*` + &t`bA&  P`$P.P P@,AEP  .D L` !,TAB$`8 J0B $ +D@ !  L@ $PPl`$$.X04P, hPH8ABp(LLP8 & .L@A(Xp +p + A,,„ :Ѐ,! +.P`*H$ dPA +(@aB8@AT@`@AQA"Pf"xP$\AEקNTL*1`a\\@aB,L@B(PA@„  00%\@ D&Rs"P_OHtXi2;BcLjcٖ"H + x0t <(Lxp * PA$6@Ё8P 2J4Xh  +8 $h!$d`\! (` :̝.X0T + @„H8߽l X~RYDjU4:{Y*ftDT`B % H xxc5bF`$`P0aB*Ё @pJ\% ːZDIi}ҹyv/C.8PrPp H x @ >A@ `a 4x&8@ \ !  A.B * 8@B +(p :P!QA}ĒWrc {4,9DT] ˇ *0T`8xH\p +G&`@ +GfC"HH8P +"@Rs2@'H!A &@(2A40T`8Xbp` )` +"X ,TXd (T xT`8L"T *\`hAB $X@TP@X$|F,@TӔI"+ӣAC.iGZ𬐝{8eiL"V/,YrwFU~nyX. +-i;UPlsXE[w%v2xJ4⋵NF5"M6..Kͭx)#*Vxt+sBf+g4ҘHAʺyN"b+M|*IG*?W-ߕ~{?2y|/6DjFDNfwܼ3Zֆhxs,tkf#JMF冈_L\JNdJ꘦L:g{$]_Րe9iioߊս[n7^Wn#37w#Bx8wO'|Ky.OEs6GsَXi-'"֤;<MPN;fϥL* $XBu)cn!cM$A*c7 %ƟkJb"3fh.?;syb$}~TXIbd8v s)IN[]) !{G7iٔ"A*|"JF*4vudmY +)KcMڵwmYjG>$denL9ui-6`: ҟ_6R#S٘2&)O=g*YTDJVZE)Ԑ4Y̺Q!WuKz{"YNo5Eί+tNudu"<»BN~WuDjܕ%3 ѯXJrDtQzή$4q^~Fvr뤡)[IpZEhkie٣^ĭ+RZθ̽ZږIWCμ+AQڲ|JG}LX fUKUWŨİT!ٕd}OPP'}喪dIʎFs#՜b;OeW6ܢ&= +x%tӮ`Q+}j t螰J,fyb`0s!!Z{Wӕ +9g7e&m2J*gEX.gIBڣs c49TܙSȏJ'vR:W.Ug/'K"YvRVy%%Y=v+G:8%]4V Gzxfґ^5H b37kOJUYamD?b!bˎXMdZ#DN}tSԕ]MG-XG4WBuBBf3Ւuve4&$c13KaK˔deK"[eMKS|&J`9mkdK dhx1 TrZǖ>:aɊOk]ͲM*d9n*yՆ5])#VsoƖC"i_7:&iQ +fTtN3e2>D6nǶ2[5aez|O"yH%M2?Ry +HN_}lef)^fڷ%A"ўGFأ&dSrе$!ZMh$[H'ehstb!ˣ]EoNG)gj<%,n7+L3BmY׊^WeB+z^J@2xCly!<2)D#RtCO#,W#h'}Js=6e^ +خrf*u2j +ȕ e_#V}1+kݟC}bb1bɫwJX5[g)1SMU=߮՛,YUHRUՌ% Q< .R%4Hepgi8В>?_Jk$߻-jꡗ4rNTeJXDϒnW:V +ܔ<":o!K##ȏ:/tTVvh&q0d&֡{"ĬnyeW͋Kpp>SJ#4 ~Kٜn4xr)dA[q1oNL#+sDx4G)s#; gtԫ!`Iw])Y):Ubͭi;Ey94e_EElRR#tixM9#b gJY F~1#b{y!;rIEh!ӑZo)"=tG u+xrt'o_ɹz򦴕#J+xBGJ OQNIW|HVieL%xP@ +< <Ȁ„DpR1=SkQAe_ +U:jlLo3鐍>75NxM |Y"XBy%,g$OҧplĤ] #i+@⮰DPgDDp @$pP n: W<: 0vO<+2y8 3 )DUEU2_U2gCl<4+Tr+yh1"bѝӛ|]%Uv +.fgr¹fYkbk2|9<#o{.Hn/ģقSJ|rXhzyx_`JZzhACBBJϫB+2*q!e窱,UYf2Q TB]X4txdURdyd^23k3vhso#:":clHGGǒ\y♻bY{4,K4Su3Ű[NQoBbmi7$,Ӗ?×M"%/ =thSN٘5KYO9k#U0MfbEXף:MuoY( ] +۱)f5]&D1{MvtS'wtr,3 ϮdsYⱱRY%}<^%I^y^ЉlO /ݴ)#IjMsDKYi"MX Xgs<4%&$XG9IS^Ϧݹڪ|˕Yˬ,6o4g|QY9,HDS$,$yJ%fF:;P͘gΪlvÛRn<3nSgc#Jg"UZj9SD$i +MiuBRȲfNB5MDz~$ckt*JSL?b? Tj39_̱DX29*)x̷n-VDpʒgduۭdxXɝrYx YXs,^}"ڌ/cS'Cw7x}go/",|3hJW;N +ãezSfXEcNq_, >Nu,,9N#'Z֘vnԲM=26IQmj6b*=i+sNG^I2Vr"UlB׎ֽ\ Ufޑn⍉V>G.sGDYIehT3QwrHl*H/D}隶>tbjO9 Z4>eI:;*U]9 ԑ\:2kTbޙ12#Qoس]4j<H؜3Q>Xb$HSoegYa!"-mO3,*J۞ǹDֳyzǿ7+uķ!!Dyټli콌 9zO5ߋ{}h#UܜXFtOߙ\wK2rT7͓DOMΪ$XV*:*c'si+{E_cWjsyZư&SY/M*VoK|TV߆rߐvSc?/];r f:b#n!#1bjUg6o!өv%^V1f_9E6{yLČWF)nn6cfʙljct҆ds˚x.'7Z^Tŗvv㙧],/] dvdi|ZK=ٗ4hDl +gŌ#?*O !%w:x9Ϛ唥o3>eL3T z&S*?^]FGI%W4>uzԕ"Ĭ_TW)y">;{^p^D]7O~h3Ϟ7Dr~|cZTdHhHwCn{fdtHdWbF= #ލeϨXu(Tp… +ؠɀ ,`bA @[k +e4iBHy0+*9L- +aŪS?h/xF9CfoB U>Ecˋ>WNJ)+CE|Asy!*^BlL^VgDUW)GE.e)|/UNNO-$#rI)u]b)\f}Wŷ MM$3̈́gLRW} ү4Gs8Id=?2,e[եkXiwU_.NEIط%=j9c6~^blաntWfًfViE>+]1iNw+^ۧYIh3_o,98uާp>hje$6;Hן '<ӛAOWW(Pij躬U%h%婗"sU^%|$QQ1YMI'VPI%QVSD*Yԟ<1Ny6E%/'VVI &) Kɚ:mw$[dRN͔#M\2 WICe4<{Nig3^/^Q].DrxiO]7SC宪ʴ󊚭ݰ4vuh/Ԗ;3ˌGd2;a/=5uzu3z|d5ׅNdIl%=UfW$TfIW Ká{ywc/s;F[Wu{n|mʖD*1Uiut镯JXcgu|[ב{=u/wMVpjCk2}d*OW;^{ܣfgveku#ϽXo޺%[~V:WV(p&>V'XcEHw>{!U!{fEFם3%/#OVcXu;:a]aq̅Vǎ8Zҳ̨̐^f6H/Azoٍt2{e.N?~ѝ2faNe#YFeB2>W9©/*f~y1fʘ^$2o&ײL2*r7IL:SbNDD*vOQ_Mdt7&V_C8^B*9Uykdc|妲|7)+*l]vVŬlUd]e2acBb]^sZ1a_j +ns>WA( X0`  $P4@  +  6@L# \;?Ӌ7"±+=$eNZɹV~9e kJ묲veeB]zyelhvhDC^5o~c)_JW~##7dX$R+VaUt+~b+tdIYwFwe9uhL" +O'ģڼC,fYq-v<Տ$V94GSOg!!SE.&цMUIWr,W,BcRYyJe7b*JF8ڼ*?*t{mϙfhŐty>55|$Lf7"$V٩~䕶&2YInJϫ_,M3Y~BUt̺It=mx=Ļg'fuuedNIʻJFga%R~C'qM*o.L;3]Ig3nNo쌙>2I|"çy5W2VS$vvҥ6FWke9rI꣟(ONgeɹZaݜD +L^YJf!0ωxv3dezR|ĖLE-mhffR-hB3b3f?xAJ>8F8vVIxjoJ4t *sևcovH*i>S9G9h.Jo;yT 347Rf>[?ʶ擕hj6R;GGawkyZ witdWYNe4+{~^_L!grjav^慎<|4ÓQp||_V*sXdj*jNJK̺G,TGRoJu48HՎ~t$+TsdI;Q~jvD!w8eC,gևuTU9~? |2+,8h9#Bt̥֬*eXfw=H[C.Uo2{$hԺ,0[h?17{E4lwhF76NO,Y?^bE5yrXN;CxC;XyC2΋pyƺJk1{ˡҬ=ڊܾ-+ʷP]V6z?J5Q5yG{XʗA\>.5488Hw4&K:Cu;Yһ\3xUʹfeS󩧦2/Y~H5O]#qeSʼT?*iCr̪ә.;+v,XF&)ͫ%+gksmW0+RK1%Y5yYIWJ%5wAY9zZrnUM=3HMa֦Uإlʫ K-tNsexI+s(")̋d9|ֲJ{`1[I6-˘8/[t[%V!UvGssVZ.+[J @b!I(Gq1@, =Zf܉ţJ o֣!9߬nt ߬Pbq)&_*7+;Y7- +uP 8\e'i*-J՗pVlQ.ZAN8F5Cz +[~Z',%1u` Z]8~ ,{G gKp ~TVKU>f0B$pVK^[f.r"-P]ݑb#<.&Sع5kii8kqgY߬́|g Ql$~c &E>l򣐇F !קoCֲ";l^q~8)'yîp@A;{ (x#aݬ3*VY3]5`7lX$2uެ’&X/nRPTLN0NwcAk2kYCPGIVvO%ژ;j5 t,*GW buYGWՋgŭ5xG aoZ5oAڏ9߬Wx,Ki8(ɝ|5T/7#, gb䩹?eYжm"2ud_ч-¥χMYZ2VH pslMN,t#'@܅N@p֥+El\ `[,|M* ޟ JsGa'X"gA؉,ƈ#eot +,ϭ}s$%2΢?PB?Ddqj +Y<%~- DPir֖O8Pd!qۦ[_AS$g]̳Y@#\TEIYVY[fĩ ;7UOg,D&gL& S/L~Vt.Rx=0Gf%".vջ8Hk JS,gzBgl|qAb,Vw2BJU78(HثG1 FɱϬ-ȣ~؟EŖ/ޝY bQ(8tG8tϰ!_yXW|='A2JY9LȵkHxT%Vک#5GغQ":A4NhT'~s(j '̏$լKyjji`r=d p2S2ǓIߌbH~ + w\ӥꅔa([ ;뺒"b P"nKrDN! X3`Ce!<+[}#QM(zkp1m$qbh:DŽ) v< _(jԈ՗.hep:,ݏBtiiD7? ㋞GЯNghg K2K5n#$`<1!HU&F-zcztwig#]Snh*>hBd-S`S'w?Ŷ^1:DgF[ʊٜ> A)6E* #L@G +2)|,Ȗdrs4E'u,έM:,ŠT2/2JsHi,vq/b)ϥƬ7`ij6}pتqw}.0!MT ZEpo >9biZ<8HaTȹc[W'pҾރ?X%`S/ydKKJPBx|9IJr&L3إ(6X\ C*94h'2ӝB̷"-+}\xr5X]ЃVrXH}Za(u Ւf;m LY*γVߚlÄ_x䰽uۡO`&Kk2q#уkC0~=Bzc j>% +d4jfFp3N =m]#Y6 +6a&:@]?n,@/7JlG(O;5}ůq8BDݚ<%;٬=dQFM\ϧ8B!e<FHq:lcftpn^C\ [ +P KŤt ,YdY<c:#/ kpo26Dib S,%(CtI,A凖䋍/FADC~4$6SaE7N*ƲRn"_.,CT1D0?i +@p:2W)򔖛8gA kqPӞ2N"4#ًOjjY5u:BcoO#_cP S8t3M3ll:5W"2ˁwJ19ÍAݛɅň%ȶkȈ^#[XT)W#^G"H|jrFQڕHGlpK 0-&9jDhOr :Ujz|0 8!贸k'vJeuХ nP8<1|n=OX,ho<@}(xYƝl.Gz~w5 ~^!R8ZBt}pYbK;[a ^*Ḁb,tUȾc-RX]F}4.B캱\ϋXsG6;7lTkSv<+'8oaVɳ j#QW{ph].AJ/\4A*"46x0ç6nsɁ]*E!|\l4}bv s&*)ClJvN n:}SM=2Vbϯ2DjFt V55Zm +ff+˵qr ^QKvMox: cPwx >u|Sxjqtٔ |H5 aFKga+܃AZ =Z+)IW@#>CƝf,0nYDa=7j `B#fx|0Q5H/#-\$ω)Oi(N*%0h+ʫGXa~) +$*2!nf٦Int&F|caRH{ d~ΞO)qu|=km+sҨ<6Q\8{ kh<+϶Z{B~ <[0Af PY# )!*1zKvNC f*Ѭk\WD:4G }b\C{U8"Lٍv.E3JH^n6Mwoȼ|1@>i1ڢ&9+’.qVeDd͒ 1E `̈́緭"HL%Y> EAl.a#'^QaXMjxSObu^yndfp=/K倃AʚQ䠊\@_Bi+Ƚ] ,ZP3-x~ a4E2Y +{15R/QLr}wX;pGVCكIQqEL!<46G z2Q_:W+%6Y{T cwB3?}JgWl!Pґcޢshpc_zw6zn U[|bG$C@ e?B0ESG鶹P3q>KR =5ߥ)zr0S +I|)˘"(sI!_#iY֡cղA!Ӭg +6WSmOS0Y^fQ 2 +$d l*6DZaU[z ~MPn3"&7ɴ71P}2^F5,?z/抁XbiYUd>'!Qq>KlSӗ-E\O!c!,UVC e۽ +@{l&h(rWm Yjȸ^ *7p"rH|*s)c \MGqʺt!$±1ſw[xL*ڱ2tS7XiJ*yJL#E.c]lI5><0NGbi]r]e[A&<ɺ6&r31KY~l :T?gWImFL,S]npK{y-'~ e%͵r5 vLݞbR*ec3oF v.+"Up߷* 1b6}r~d"sVyuѠTQ:^.pO >\ެw (YUPTFՅXX׿ˆwQ֐X@>_Cl?LF2͂`%ѱU.j"kl;wubUd@ġi.DE! /jPRp%KK򾔘&V)J$/3>+oS +4DAdp4 ?xLosg@\^} kz2%f$k 3NΠHq_\K`x\ @D7<>( \KFwЄ[;ͱKM56('o-Tɯez{?v],Y:LVj֣(iCG=e; :"ロQO-\3X&\NyeZ S%9}3|w]Ph/.8(ҏ)-3r4[R1'>w;NFyԎݞ<|i"*ܐGE٘JPRdtk+)ql&Fq<8p +7P`hrv2H ˻6Nh!FiDᬁ"ke: Ivݫe{lsPJW*!"WgFߧ@h ?ތ:`[9q b)ù+WE[o2)&xdDFa.WrbP#2c# ָdz!Q-Ĩl鐎*#9w\֛S'\tnXu4߈_\SEaΓ$ONI/le'e=I +##"ys$XAw.eK:$&&#KqAgAohԁ￑!+|9񩞠"F@6d$o,]^nnݒ 籸p./#as)%] #e"aO^ ֊Œap|xVbVv5(_p߆>yP׾_õދ㋚JF^!h5au!*Ê^~+® BS/bYS8d<hTw~qpf\3`; 2M" [7wȩBcaYfMޤEa_^OT#ޣ] +BJ2u! y'ݤ=͉}1!+*Ģ\qG}msDQ նfwf 6@ ;:bc+4v{rx;DyҏN¹G5--|'zP<]m%[\8S[L19t9d"`vOQ=#$x +U ݖj:@sF#ٟ2.Y)O56>E/ +0EFoUQ[}SLB ~|GV[qA>T vl ,#QsElrr_fGgV#ZSp5 6Q0<dE5 0ZhG:#Üoшmn/7L}wCpN>jc!ktPxĥieN-64Hӷ7Ph|Y!k +=4De*cٳ(D7+"A#Pe}LQt4 0D(}I<>_r%v#/`D0~Ai9m֥Z R  *;Z6^z@>?9դC@Z~z?X+*gև(s߃tw*bX˭\ZIf *SCS4-TLBvaNmpvu BPDjoL^W9ՕVa{r9+f7]a0"Q۝뎦NdK5ÀP߱-bpTn-ݒX$HN/%YiB~ +;<^OuC9ؑBy~f kDc^d8n*XZߊV`,a ~f^8 lg qbY@_-Qu17-bq/[rP$0S\3VKpN9Ntye&oj s&A+MufnX%p;3ۨ-ځ;x7q{a/Vjhz*\)醈=J!P$pIDpdi0fN%`ڭf][XXܐ|9r +w:ɚ3{ {.^!z40KLH;/h[y # Wdtg@Wg+3s@:ҿDLөhOM 24f_ڭn(m#E]eaˑpHHy +Io=]pu&?<>@olA;_6heW ^ol>(\p)DC@n."w}>FFߎm?0@GWr-A G/T +*5gRKQ3bF#1j:K2zfwEETDsT(U&O6{6^LlW+*Scg[m`݇` @b[1FoԪʌ wEjzWAQK5gs+0/Vokt|_qW{zr8, #zr֙*cE0->jKzXSQTbaUs:y#$iP3Gν<-ƻt.Eț{k8kj#*2{~ݴ`;2.ʣ𷎰4D Y +(:˛`޻FqxWmzr:!K *NS[bJV"@(: d2߉e!JDxL:THeP24wnb$088= q:aTXQl,BQxr+;z0{<}GI7]1>r+-?jTYbT*l}hT9^ɂO?CS@L_~`{uѴԉDsas!O C'4HOcyela*#5wU0/FFYhfkqQV" Θ\/rypp+\]*)j͌T r@+qMrH>8^(Obz^u+, -аQ9.pd&t`DZA[}d]!!'10hUMQ}\pF=MdžhMQjK\\De.H >Mu۶x `|ٝ|Ri0MQ\(j)[X0'&Mtp~u`TVSn+qh@.8ຳet +9nZJc ndjXH􆀖rh2Ҭ@iצ[WqѽYHe +{2]瀷obbd%cIcA:%&+ɾV*&fM8_!ܨpB/Tre?YiFY&~8 +Fׁ\Ɖ+{h >$ cQ}q]|%֙w7%q_FOҶ&X_{jDc%{Pa'J>tn1`otV"C2У{6}Jqj=%(/G`ܓKOZ4Ź ,B-.%Kt$eZ8(N,.nÓ[ιl;qW1a3yDqR0- +A(`v b+(ĒF2"趪G*oJ!]ܳjg&_k ұpEp_\Sqa? BL$ĹFNe&%KcLgڤq ZT!?l޴g\0"AРivj)ͯuW4[;HRSܐș^U7@PX" ŏSQ((fzDD]Wi٦*6ā\ J [~qMJujUw2~M֐&kR)KpwSࢷ/-QUK"w/(BVh)+8XD`LoeNL"_⣽go0)$il 2ogi$Eu8S6V3|@ Qfi$8qby:P29='[$cl1tj93$4^֘N#<}Z:@inK6s0]`M@iC!f@fV=/E0[()Mkꞝnkk0y Za'-MQ`54w"57s\46u?Dcr$p-7{o='+н'V<4+>:f,2Q]eĶUέVuU_ɺ E^Z\VW`(~OPN( TuN s&tɀ! Koщj2hs׼riOK$Ra#1A#V2P{fDYJl^Ca - F7n0tD/쎭z_#JUЊWf T`Wu.l."LLi V׬}Y[8Ji<W==ԏY2䩄=RGIT5!5`:;N_H`JGSz;|Vik/qe/$$yr3_oʁRg4Y4o%l1&g\ƆR6&T4=@zbOʚɐDCLERǐ7 CȄ@M磢Y]6cR:+&$hx`j+%Ѳ;Qgȱ:9""dq Et07X°g~pЄ $ĕ&`ϙ2S[C_}V.'6XFbh񶺇 QWY|8:e&.҅gzMA]9U۽_9 kLocY9,3olM=u&F*j礶+Ք=)b 6JśE\5T=-wE2HOvAبG[םZX6}tԧz +BZFw9+H(1[\q+IqAP7J¶ƪMŽcBXSW❆X ɡ&FzmhoK#賓xiMf!3/SGAkq?z'yQ!w"pV<!@mnV'+?_?B,oo _)1Q4cʒ\4%7TUƃ6*[H~޽X=@IF-4Lө ͑_:|4G|"HEp:dLhh%7~%/aCAKԕ3S >3+_C~H(J/`ntH5\Pʯwh(]ՐuZI΢-)GHπVcxf~9rz~C >.\LUg(=,~9"7px$ѐm.tW!WCp)YL%^ٌ;jO ^@;vDPSժ [gmRMbs$#exxH1} V"Qk !zcB7D1|ggK!Fu p"^"p/ޒ\ H56Y6m#d(Pj]Sq{=\uvx"b5l9ԶO ޙ מ[{Qdr-I=cnm zb1X14]{Xpԣ&`P1( p_<ש?a=! wrGI(N'%.Vt^3/#FwԵo7i\L oZzNeSTdR0QF(ea#U ,@sY0 n?lir;VF&.w&,W;Vk+#~ +?g2y?tsFK|32!֯>W7+8+D8FB$'?OW<s`(`65F9Ո8,a7( @J݃#! j3*ڟ.b=w4Au~TN@gEdĜ'#QLU# ߏhc~0M?JmJ4(#"{J]#I6SfRJ/œh}" +ojtpWA7Q9(R"κK&|"4N984ΫTN[n3l)5HzvYfhcLHZ-$iC`)C Y̮rsg[SKOW8BE5 +P;:(hl4j:A +-S!Uk c|F50㢮 9ꒈ2YHF%2ۏXm1Ka}^̳w j;4Uڤ}ż#Ϋ+ ˺n֦:VQVޒ/Ӣh lh2|^/s$ +qMB-Ai`#t>:n0`P$A<\F=v쐄-#Mݳz,z=6YԸ|wYEEx^P ^%˧2R ^&,ymџֱQ");؅$:`?ʅۚۈp($( GH~Tw!XrL.\!nGg*Z[yl9B + _1ac#HGFoq K)*b6o9P/AHЄ鍧H(S=vPh8 $($R 3~%?xhR4r?.%0EkI,ө,%'27MmⲞ?|Mh^%A~B+z*f[nLI[$X̛Q鳄]CbJ]41ۄ푊+uAN*ns:*j7{$UX+((@i*%o*ȉ@ hd),_c(^@qȠ IUs}aʸx^jfXpAj_xe.fW@s3) +g$N(zV߰c6" :eie"NR2iyhM@qTƍBф`h'!yT+|aA] m8H]+ +gn<~3K,&!w:c%o,ګaf R6 .\H[g)8Ĵ91d*V V哅$d$YRbH)nU0cr>Sƕ;4ƮS8ԳE4Yy`vh7BP9!@An#p[zfM?0JĂSJwϒN{-*DJy=k 'buq=dqy(%Q 6+D@hHn}q)DaBR$kʱ$*2pkg?LO*6Dl}b@_zn-uJMF;xG#l(?rUd䨸aTT/`iD lV+ Z_ĩ\l!N.x4 Fdv'e1}ba 's !?&K [y66TwJ[IM>3jMhWXT\Y "j_ jݰX=0Ql b +.>Fuw?8h  @ee4Cה3H-L->_I'`n zRo H.{oh}ڍbc¸f R 4M.#;Xo aj G| DnۍK/ Bb6(< +)^\ghޚn!!L P 5er?|BÕ(Qx(T,/Ujb>8ʣe%qXOzM!x3"t8Ly\}C9bPW?^Xڅ"0% ['/f3oى}S 6?v02<[-:R>Y ~rzە9)yRJR"j1gBOAXڊF}W-#oXc15ҏX7 -?'c?<],/<`J$oG,LpXܒ=صơ䬁eaX/+X!kB`Y}ET T||rw%*vA2u} v!UW -gYqkR ]NDKSn,6"-4wV,i9]Z T:N{ߏW\󉱼0MRҰ㧮4+e3ilsrBYo]vACӽ,gar8 AgKηR135?-DoVS,o;k:95.xުzrzNϯ|9Ϟ}ؓDɡ'~T+JyUA +H6O7h5H%;ZM~'mPYAUL2FW_B)"@_*Tp!r:&QFᗳ;%$Oz%p6{Xf$wk9]<o~:l3Dp .)r.ת^+a7s:8NP{#YFNi\=gR !ʹJ ˷>bmv\PgaS7ZkDS}RiՏq1G& T=N|=FAQa#{ +V7lY\o7G6JCVB5&U߀0Ȧ7ٔ3:-W +3z˩ +;SJGH2<-D 0<i=.oca%h +%[(CC)"zcDd/;hD +k>CYw"n.6i +M+Mirgj*~H[2DCb[]qs?! `h,'&tמk +̄65-Ԑx9F;SNdΎtS=z=)s&3:>>DzٞB`Ro,EQW*}Ҡ~-6K9:U^/nܨxD( :,DTR{1/*UU?lcPpD-/C(QVEz~R@9~fL,0 r=q-v!By}ʶ2~>gPSL]yMOs_uN yOķIj='m .e|Hyrj]j2aCeRbTRfAb0+jXkJHhdZGxY$@@);&qΞv#n\۱NmHx1W͍YI _VBOGV!ne!މUƆ~s&T?5Gx8V/cUs:RG#;c:@+ 1>7T+<%;ރr⾛VW ;C 9eGpxݷk^X7ºgۀxM?mi-+]`6[\˙i +c>bB4]bEWD&U'@0VagA09+JV'+aK麅;\QOdzla冁DJňs'@:rTpT=F75u2|2AUxĐRܦD+17Ǝk .91Ce3ΙA*͹)w\7O:*^vsVB. g[8H-m}緍 +&(Ͼ[aT`Q!]俿W.sݡ ޫH㿌uG]F]ww393KxjJ rJߩPk|?h[ǞP&7&y5I\E dVItWUFqVwʰV<*R̽&VW`Y3XDv˜;π 77]6Qu /pN5{됧HWZqK~Qg^bf7yMKfX ŏ{K)vNڮ^*p53vqv[~#0egnrHSG?yt'o Yh +9)i@2H'A)}@[$mLhA Ċ +8 >6(b[PzZ7qeV$xJ"K̦azȨx%п$6n93=ѽԫ|?t..!}uhp~Sfh*rZEV2)&U;L}8X@y~t^4!H0u +lWJ)Ԏsez"/2)E5yDw73.+52/PV^9xnk%dgM䝍hU}_]o p4YOdwF+ehvJr콤6?O ?A S$#.%-G_NR9֢VZߋxٵK"s9?%o߃B!̈́kK7.П wz֟^o/>roof?G}Ooa rp$|SRY㦿luވg7YDbm`4)5MΛ(wyVb?ߑiM* +Y?KU&B& R{sF6YcNpp9t]zؽ:"Fav7#=r :e zqo呟F#A]ex +0:@P̊&T ;K]z*?\]wE4'9K^ AJwefnƃh%Id +NHRJR(jt*ϨgT܋_4.JO0=]LЫ;j/zO¾?dWfBVhK]Z*;q'2ɕ2 K}ҐWveKJE.eaQy:<<7McCHEHoaMz7O&b.qy:}3|EȞd%=%W͕+4#G;x~8?x8gF&pcpcl*-J?BJTEV\3;|?¹C辄i;{K8GxҾC3l-!JJ<^# +KtkBǐ< ' aOC6>=D2|'"(kDӞ K +K"3*-A"_d6/hR_eEcJfpZsAeOM+4 qyZW{ݑ*ͨ&, +MJԡ:QOO0aY]عXTZJ9x&z֜l9wZXN甪FJiN|KWoR=ž3lj+2Uk|k՚W]uUZ"^+g!qfcUG^U\WqWk$^?r2ԮK%#]bqwE~p2{mUS_睅mb$T4TC [s}{1ÚD;~+K/)y"HᏤ2WI=.I&DIC"Av?ZOb~˓/OO|中>ñ8/rn)eZL' eU + u Cuq. ߫>qcl71"Ff]b9="Lijtni11 8?'ê4dA&|dgECyL"*H SA y71-8M^_"Isd麨#yp5y(CJ)Cmd\O1%-&aRtSGǕ#M59TO\_PqA$C|`F7N!QD#1?C<\ HGѦhfhȇF)O#MɳhCg̠NnVѕ=B䍃,HG_&DQR̄|(p.DVT>'waEhu&Zsh;IƧR Rlޙݘ5p2B"jFL*4 N;R/h^Mg(+̫97{k>17t]1̳$RI'(U#(MF&UlWkKYKi 1$VG.a1ĥD |fA~yK!B(M IyH'.-a(2ogwHiL G#݋bHњJqhcNqJh* ^}fJ 'r_ XYy3(FFVf BkDרUiVL5H^*RXi*fefdx͌҂F,Q? rňi5 +9g1$NT ՆK(2ʈ/cǫt}~d,-qj2f8RLh7tTF.NvAb޲0Ejj%4P9N)̰R6T" +Y%# .:u3mVw~Y\y [UQffj 3(aN,N}]̹ NPnܬX2- lp8naABD(AuBd恈EDj(De,IY&KQKs {E?D(C_EڅrC2-W!YMK㉤>z3FD|Wa=sy.X;,*[CީT,?Xփ}5.Ҿj98j g=hG׮nD-Rx{HW2q3-gR|4bqҥ>3$W?d+SP홙_tc=r׹Is(q!{*{֗sxc]ctUjd$ɬB ""GJ Xh$""¤~QZa-81H냸o!5MMOHes"EQ~5Ch%3!!_xF [bceyϒDV椾L*1>Ò0{AJxIѤ,&_;yFa,&jS?.YӸA~p~rKE+3*U(gFDJDR!kȉ)sacOgwޤ&UӞ*5o[%M>Prqt~ufE˭c#\T5-JiVfzXe=oIJV9eER +6TMF$= !ʷQU-e{*b`8^~e,c/eh)̓ʪG,PA 9J<$E %uTҚDz9:Jm#&[Î~[RIQio_!Qe.V''pJZ5"1OoOJ:rRHMTM<#i†-Ǹ!S4#k$8HI`ɪLkwyg3)Cc6M;S{iD>-ȍ>yQ65 c]"'; 3Hc]z3lts]wqV<P#ɹG҄RcFQ'~*% QZ8Yig:[_s:ujd&,$&ihnAC/Y1*/U.z ye0xiS}~B''=LOԊ1E11_Q܏2(:1N''M#e3WRt:dt\:Hi5H+m? +!kWT-"Gi+6szQJLFz|E9j\FgfV]czE;L*SPmSɓY9YgR]BjCroS峒Ͱ5jI&ZobtϹ!Q&Hyt-QzdEI㕌HU*WYƐ,:Xu4];'%fX]OR2rIq uA7҉OCz+65Ԡ Y/QMzW%fEӇ#ty~1JBJ?VIyqz +9޼Ч ])EF4DNhxך^gDCQSԂ.>._"mzT'ެE.sd,] :>~WnhgFFZyGN91\ M"G R ƹ.34(]"1t/*EKnLHXuLPJʴ`Z j6R$#]=Fj6sOt1#%8bf toIGygF!sVG7G|&9ňv)e/#PQBIΚIS5ͫEbl.9 U n.1&hfpU¹5p.[nLgͬfܼ6LSGyƈ`tݎ +3C[5%TPNd"-DMP9U˂e,|1VDCSlgMR- kIJJ rj=I ٕΕm) +z42ֈXCSFVriZ*aj{PVE,DQQ8)W&?:z6U'+= )Q5WhM(R;İRZ=d*)Qb5ۯO;4Bdb*RHTp1,!y8%_9CcdD}jTb#ʺ'GaTaq0#JoYY 22Xlc$vCycաWG?gw"e Wv+wsZ:fka +fKqxI֌Lh4j(e26cHh?arV?~#~ "jB#Yky4\})bj(+&JqFċ%DLTj&+3e +!IFHPDCr2c$M)C| SVyg(<1\eKz2VjfgK*9#슯, z8"1C: bjaO~[UHEd"( [[NUg1gb[lž4HQdt%eubEIeKE3(|ċ',ዣUĉ"nQt\B p"ʡpPeIC8KkݑG5e%"31!Dx3$D84^![j!y!Qʢ"\JsWy^s!%!<)u]02ӹ @BN_P/4%Ca8Bc{bAA9tࢦAT *A5AP3 e CV,s^y 3U*RXC |spIV!ljSuAKkD*t(f %0/[A"eJBj/TAGNμ_pC7_E&VS:$ j=5 \ p\T,_pEx/3/h/p]m/nA݂ٔDu0hjY}*nn? b=ᷝރ.-p[컷i-+p -P*PYv=@nn2n݂yC Qܪ0T֊] ᪯rJ'e& V8f=ugnaPm 6vƯ6{L!72khH{w9[ I,$xD7z +͜NzdPZn-Н:iquNbT͂<Z$. :ƒbݹ>/$| 9[vfX.DqYd 1<+XAY@} *2" +"hkQ+i[Y^e#VbwiZ@i!s8mLr/0hLG4+yS>XtJ+鐼I5TTc;NO):z:RJ9}YFJA~qϩ%9)(n pJ wP|`$l.]R0!-t4Xk]qi_5Ieb/8hDq(hѹQ] c/5)GlkȊ[q=\cpZDuk`OD[Wd&dxʕ"C(2]9GbU2؋o`'p[˯cՔvBeŎ(儡@71t\+w84Y&ä7 ZmH^,(H%̚`%LLefx5WJ~̦R0XO)tKB`ꀃ#唺Hp6͖@LX'F+A =!L7R?QYO +ulS$)0ZmkgI@j |t1GKnϤv8ԹG}7l},`nSh#@rR tjl5D+F NcB3"X>#7m%Z͙aB\Թɽ@rv"8w JbPvge"x\!υDގ3e1d0!@ȧ[imfˢz*3І>Śh7sl5 }]O[!'q+yzL(=Z~.Ԃ) *s + Ӱmy^A$c +XF`# =yZu}`y9;2dT!% (#NZ| ] P Hp 䭍Օ?l2,>[ g`H"Ӹ-O'Om xf˽>: ("5|[|f32)?8$|2*C}`m'֤JG)S1r{>[F>P)[t Y1 O/a'<DySB[Rkz[C@j*뀴!=!왨žcpqiҁ`nQ} )gKo.h.ce<3'#9tVSKkO؅osԕ4)z ¤Ri + e*{ +4;"錅.p`=9&6FڡFmmyq٠Mt.F".RI8e.q/FߧvQ8w6ľZWd>E$OeC' .+PX%* gurVprtlp@Qx0Ru@sfI7_pAJ.`a@`+_6 =#dZ.=ɊN~zW}kj 2yOj  ϙ@e=^* + ]D8ېDVj+(8ڍpe:EC\0ŰZ6BIeYQv*("AW75C>V>`JA"{4~6Q7k0q HD-ja4lXMtp[pzcpaxP9j]ϳ]RN^ F,w$_uÞ=(/uVpǫ?( +:@9O=)vYs|wnRI8/țx =ڐA,:qOOԨ;9]E%8, !K" +5͓tuʴ17K@p,=DJd0T$qY3=d.:9Zp`7PSҸ"ѱ09* cfHR*&g~Z|d+2GlDF 4oE*6I+ٲK#lJ ZeOx9 `FoF8c'er HH'S\#$8żQ݀5,N%}%pBUvԈH7CXˍ/[q/>!40V)D@_PG41ԸVʹ=zEF:7oo@WH5\ =Ps7[@ZxixiG~MuW/$ʆ8G6'Rs7@s w9)'7&}^|{3@nSJ` l!&X+px8e2@y +He帊pTk{pxZ?,)V %X7D XT70*4Qж? 7ͷ2/n% \fC3ThZ3̇JEr ,BMp@:pdV9SQ`vSőU}t:b %P5(-U ް6Ўp$-7%!EiG0N7pI_z, +|S4>t`)<7p8Z5xī_Rok5^p2P>C$BNB&.7yU| C܀٠  +>2kSnc'v]{hWnxu1`bK&[aRS/ARLAyI]0pM_0.5wR5^<`K-\yqU%E8q*nΣ֋B~x!E*8[*+q';ب +PGOi&v 9q#0ԘVKUk5j~$[ϒhORHmy[ R'씊ARXFh2'0eiO w YOM@R-Dnк+f+i|ľ>-جL@߇-K1Ȗ:W PЭD<#%05$y$ v[$@XtO$ @L*#P6kTWVʦ3fnO4 ""1R=%Pi:tHR^QFhˣ_0/Ҝ6H'.SL&Pa\uklE}9+Ynv8! W'yCA@ ڶڮqE˼x +pPsQ4G.@!yhy=0̐,"Fv R1aSG=@B+%b*9D$M7r$$/;@\nxf:Æ:ۊ\A Zn(CBL9I:.k{51V&F*7qQ(&}pjJJΰ`Xj@o \Xͺ?CPOZII+4m~MЧM- ]5|Oj(Edi . UF8!g^.>ʢ4PܴZ3|"L$ggj1w P} 1aJe4aR2#Y+[e'Mlj*6I@ݐ;WY0FyeRJ݆Mݑ*^heu+I^v`2`['7eA2@ff1l oK3PB%lseI*0IBG/~3՟pv#6?3 V R zG}O-\!Q$so4ɇXQQ{VK" 7B E$٣Ψ!wL '|Zŋ1&ص, *MˤO *-"V:'GV3*:=bGM[/ щ9JpxE/5!1+zpw8X +^+|0}W9pjGT78Fw1жs0+`1 +05[5+4xB`z#v^pѮzF'\Zf$?F+EYVP_⠨|UuH*:0E.Ork5U;Fb?\YU@{ˬʲTb- +8zK4L I&M +(pT\㶲UHb#਍` +j@yGW Xqnjڇ]'yhNٜHܱ*@ ̈́bjY(ĥfYh"jЦml$ +Eq\ + MC&3u׷g&Rgٞ[(rU~bvZ t/WѬGrU lh"~fCO())_ +0 +rDӀU@_\xxb *~۟U@lļvn%+w8|cʍ)%* +(`Uځؠ[T@n̤Zݛ[]! 4)mͅsgNp"Y8&jsr.&Q@ +Jǰ8 d ؿG} J\_ψi8J}+tIuUڃBRh>lΏ0}OY/Q(,YOdS#8cB ޷1bQ=^}E$XCU yhHԇp9cFi.YJqj}ũ] +B:`f dⲋ7h~[{/~Kٛr@ $'|r~  !FPe ` g* ( ͑kԭ '@sP b4.'`()mQ^R6yh?8l&+Yg%QђH2 +7kdf*j\"מ'(p:׫g6#TS?J`i66@uD@ӚcF Ub,u{]-JN|տDa{Qt@3xS?RH;H].vEa}>\9uImR/6Y6lmn AKQ?,-m̸QFpf!gш_̈́ }>}<1jZ䐢36_/ +gZȿ: H]i1Ook}I@>4`&B5(JHE`N00զsG}cYZ1ey6)"G}LVЅ< (x* B|@!B3&椠{@^s=!ޣ.|JGgyh"o?E[x#;޼E(Z>B8-kn9T nY/KREܒБKQhᏦHBV~ Q!eKߘ:JVɟ' z~< ?Qa?) Z{EA-_ 4v<0e*ʩsGKz%$0'˩pOtԑ4`P$CW?}"#E£@DjT3wȿ+ ”DH[h +W+#xcGW)y>SRe5 yOŨb9<%BIvvJ8o\c>_ؕ-;sP|]A IvOݬgM/>( Y_gbLٿ;U^GY+Tم? c󷆊0nl-r3 Q?X+? qe1UY0: /]8v_Pt\ѿLi ̆)I^T3oƄG?AH٨lS"ή=O|0 `y{/(Upv;n7~4J [*=WmcԕܣLKݝ"?u<~4ZRa+H@KH? 2$N k6 +_VzJc  +' +ސ(.}bNʧ0x1tǎ!c +f- \%HZT7_L aOXwy.Oibt[ EväM~q Mp||~ox/tg5owG /Rы]'ypKFUEx:la?_ Bf$%tyof8x*]GaL'3WN""܁s~txA0m۟+f`̉ %iۿ(m.v3fh?>;i^+[iLd@$@D +b+G9ؐeZĕv[?sfxOJ|} I0PT[`*o7dǐ~7 J&qӟe1W;0;FrGmFMKA{dut0J +E?v{q,[*HU'/^{{OϏK86;/xB,!sI9U֐Jo;ji{^z2?I01vn0?Q;;ѡX\ 3Ub)"h귔|7g6h,i#X}s' cKII/ZdXv}ϪW>uW߈ I_p Ҍu(tDڡiߥ1zn+/cE *kL~n : +;l;,sE~.9mD{bFtT?yr㮾UDϻGRʎE ,>!Uq8Ie("࠹{lxHN Z_]H96n3AZ3 /`Mr\O{cwos֭e'l߃??E&+L;U3whM;dΛ +r,ENb߿Y B1]")0YZIf&GKFe8pCpʲ`n2Zac}m| "HO\gP"~gMwlh#ZGTJ2P4Ug:g^pM՟^0u<%G> >3Ba=s,';t4DQŴ_]2)Uo|JQFUQTA!ҳm2l'8IUncQ^)N4a?& pcءrs(M.^]T<^13DىD{QR M: :o&T@$Ee~~߅t3eeZe9zP_?RS!5-RjQ8> ìC@q>D4A7?,uo2DqT|3@q\%Z`Fq,df>15= YHc6mo|W(<;O_>"GQur-N5ֱܧ=pt jyє*^o}݊6o? +z Qvx_>zDixPYPϧWTY@yVoZ'*L Ԉs|Ѻhoi}\NpgcG Sv.._0?^+'rOm-Ρ4%š FJ l-MOJ~ +^,5#g(/&0F;G7,DV Gq|eFSw/r_46.4:o@%~jgEm Y^G ڀ108.i:N7~Zo8P!Fķ7CX)*(i:i|Aߣ;@6#)sŨ| `>%{͢ВV7_qV}!~yHkSI}w.g{MeW{T8/]Ê:ս [OB#og wr?rcu\)̽-~;l 87&^qoޙB؊p6G|.y +Y@;(4-= Qo!4ؾ~S׆tۃwxu"o-^W] E_Vo#q(]_&@fPw])VG +w~9/<9EϺiaAɱ\aۢ(?!Ph HPdVB[ymHk4q;ZUѽZ''p@ a:l#mM ; 6ZcZwm_٥|9ENU +7Ն܇#Xq(lzw|?k{q ֯gVu*4jd7=ij{+%z-pm|p- Q|X3gl1|E>fk{ދ&e-}K8d8UZuñ-ѷ'1=ͦ[ fӟJyeatnc,:^/2+Rck{z`VEZŬ3W rVN_T0tbbrϜ6z4ӷ/QJ!/*Ki{Y CR۳uryھ@Q3m3pة3RB~c-e&@9~4.Mm4Mm_.g^P;cZwW۟*Yo%cՂ9pٌ$~dax?cZt yRF6頮 .PN'~xz+ȌT26!iSb)rau{h3ŪsAZy藒G// ؼ]1~4n]ybQn.o$=|RBX(l6kb23 +EJ!3x#u* ,lZҪ+pJ'n3/efWX[R0F{gJ_.t<;^Lt) +à.nHcONöHQ6 SzHy*8cl7 +c([ 2aU;* ʬyLVÞRy :>{W2E+ 1O"L X#UUgCD~ҿ:_W֢:/^Mt2\B$pI>\pCǘ*r {$q>%'w~ǞawkV$y!feuˊ;dsHgu.]΃܂VTP[U:m[2x$%e `l`@ +(Y<7)ݕ`O)eW:@zc!ْ\ k7\[y}iuZ]rԣSա +iuy۩HAJ@&S9ђҾȋRHx`Iu~F*ɏK$)6@I8)Uk3ZykmS(}{RÞkH)%^`TT)%e̷g$+ )tJu?JU^7T.ُRb`B:^oGx&8/u&`eQ +~ኆk`?9쵕s-HJ+hڒI fࣄbl:)$3vRʢTERH)슣Jv8#jQFCC6GpBc#W,Nwql` ))<{(}K_CL*"9,7&+l,7J>Tv`W1pPEބRO-|DEX]EЇW}%[2DQP!)[eh^OUw*}9y}[v46TvkȠAxspJsS͓[.ML{ݽdhKLO8P;H"ns~ے֫& +'YI}νgx$  єpp@{k*w7TU{Ǥi N{AN@S]{;P2T%27 kz&NQ'ɤ d)nΣl6}Z#ĕL) A̯xTݝF*8ĹHMDJ7AKR4I5k=( PzMx`\Njڴ7D~ayEy+Ab 4BZ7D"Mn)G  j\qC`\{9:# tϞ :2ţ(b$ ж~n*ؚw7 9WSQBpd"pY~]m~|+VBM-/ Kbq;. E[8,"n򋀓^.T5:.gm'&~5DYdXAǁ,ASY;Rw6H"fC-H$zMPZ?H!cm=/?DOi)<أ +Gh۞ +˶ )fߞCFTQTRFbVP]C}WuM466YJd4φYA}y*5eALMDhY0pLj| w׉t̢JRPB^V3'>^n8g4uyANTdѾ\ I5Tp#}v`4 ش&1cdbu 16ӑla$֞ ΐ.|FcP8Mc4!Pԇɀ5Ϊm5y8ٌ,bfLՂGb*kAm.ţ;-lv]`!;r\@ޒhOMgaS`>Ð]\S |p|M4T >YP'w F +(ltw CH<1lЃ6m74OG *ize*:_Y_zϯ!뛌8 + ,Ps1+= Gc }6?ؔBN\ Pt, ͦ} m0i>^ސnd!JCۏT>dDg StEĞN4 b4uЗd{A7 +:= dW(z͋4~LݥMł{AmrdYn8b. +69`=2GM¾9p/A(u谠( {cnpU 8QYZYo:‰) 0[T =btMO44CG>[ܤ~vIQڞџľ2>(m_ [ /4B%vw7UX͓E Q#!T*UCR͛/h h7GDh;p2o2DBg +'Be^ !B3Z+(BM~>B $WP68e TuyM\)Ub}nBtcg;Z !xE 7 +yniqWU)M ;&`?E-E[UNeބ$9?EgiF's_>otWhj$ DWSS[ʛN lCZGֳ"d4df* +KЙ~7!p,Bm kڼ='A/>w`R= cqJjM{cUfdIN&䏫ƞ&\("^n_KehЈR[<T:@/HBm6>KCH' HD+?,P-O{QB@n"&V /ݔ bj{:.ΌBﯴYwSB +\r9iB6+[2j0rr.10ȆM302n1~Uu”{t[A7O*¶S %:VmT?&W8HFT)A6u-"t[TUiO +/ee1"+P??Dl | !`6j5\gݻMscVL> +ĿiDD&R%o(JP%QBD q7%RӠj#-l&~zz<'n{;LMك8 1ƄudB#ҿeBHۑ qʄ 0]΄ ox Y 6BvpY489'/@~S-NNR?C'< H_rN(S4S"[JaK-0>lsi &I@q7FF|>s\8GfǘApsFeQBE4"jr!c4wTf` J󎺰E#VBNNf @O9oMW%le9 +͠Ƙzk,`51JO  9ZXư>Az+8tԘC1hibSB<ׅZ)[U3cARVI$ EU;$Q)P((P(w Ig3L y, TLx0o9qk $bzw?-'#z/(rmul2ptUȱD`{LTMpzS '~L +&)QRRDSF[E 9wcJЄmԡKgQ% +SGK) ֌Қ:ASyXb"htmlIc nu+abjAVˆsƑ n AS cdrl7}I{1 TDYZBLut4=fL𺡓 +K=j}bxQ0yrSR *T[1!yQ2X%RNGծ)BL![Sq bO)'1*$տa"\56IPĄLhZ/u?:i5԰IUw\**cB=&^GS5KI)ǒÑ$&X1q<2#T "Kl90XMﰥ*2D:JKr` +Oq`Kq-/)U -c8>p6;8P`)U9Mn4&`e@:a*q0gK\LߧȆlp07L_LJ8y *ޥ׵ҫ2+@uAeIU.Z1l|.Kz _O<\2;1B /Z"Nj*C]l'<\!+xOg'ʹ.~v"KD- QAWe2){_!OG#uߨH(sNc e 9Ν2ik<,ns&S< #$;ֹw + ɰwvԉӬ` #)$N،WN]^zڧQ/>wxQb:&p%UDT<^EX' ~0)GAމ-G각iK5ΔvRzJKi釕O 259TۜK@T+t+Lbd(]bJ|tRi1.͋K55e'+K>^DT0 ?Atp'0&>Y2ge>/Ih>stream +`[Ȋ 1#Q#9MZUdW k`e31 3&U}O5(:++KQOzH0pjVuъEU^ӜQXHJ5Xܭlr%ͨ~fͨ*W:h2iFAs٘j)j\HgaեPkggKG ?Q;1N8? Q<}h-*wrttl:Gjݚ$+fVa1 z(s&9qU]NFj~a7guR`5 +|{I(:wօU +5 +8PUkp.T2|bF< q(޿BLIQA0*lPQG=/(J݅5ڱϢab+!ҍb]t0`cꪜT^bAXa4 BG4FOߜeƥ=0rL#̦!i/FD/WuP_yXAuQu Mիu-niG]hW檞6BԻ+6Dێ}S$[C,`GmQ= + JWɡva(]7 mRYEwF yE~S{Y), y?*UpAs !{a1xY-tUϛr%٣E0m>INfяuI%s) Se/G{"oXv R:-`>\M(,d!"56B3Hh&#"K]Fb.:dYѱHXsIRƄV8$n)aTcWdTYmm,`c5" R^mʭoʩ9Ş#k1c-FjS"lX-7iW$Peh| \%, W9f/bY +6Up{Y*%wЕ9*1qN\#GWp ]v5Ç@:\7+eW*tO#wfȢGv!_rD{maXa( X,*0(9L1%_e4ϐRLy`WoW|13!-fݭv`/ظRfIckyC2{*N*jZ1U.I9E4Qe=2o*[!@R$xQe];&8zlnvC(tKQE'mE I+au +KHdt*7ط/RA>?-4J-xﺂг<qM6U1WJI RW{T:իnU] +(fzHL#ϫTU)ȯگ6'xrrU3 +bu +,!|$G^\@ý]6x,%0,QŊS$1e\\`%1ti!rO\^ѸWhD]St +E2wc8hf1A+n~IΏxIŠ];d3e^dI]?HV_'$Vb߆Q> 5 cAlCYF8>rAYcHe"6CgXW4u>=ÅcdMD&ne>!Y('e)l](NV%G,:c(x(O: `m6]xv#E\Ydgy1}X,gM^0TϰLY1g'LcwV2{ٽǪ;|ߔhDAw_1!ʰ偫wVqNRn.Lg|1Y!#Awhusd;Nm +/Ck+B9$*óȣN61Qay,Oq=j8Dgw.:,'Y #x7)Ugy| 4l@"IXYxB%,S? i9r?8:Zg,ccؼAgIiYԲLLo+ɳ` *8, 7G%|.g=뉑م<˛`_NRbdAնZi0N`*ςmGR1|gQk䊞?>,UT5vR˳p̃q'N+I[沠. rXy%EbgjAu*_ʳ.̀Xx%yP; 2X7/3 J_gMNֲ2:k1vY'QN}Ig3pAj;Mr%(̏}Y&Mų8=pȢ04&tQ]~E?dzx# MgJ6x4ςcm[04۔2V1 +Q&H$ guf Kr‡哎5?ʳʐpҕg!ơrSImy,O_]DzDiNwwW\MLoS؇hS-qԀDA;c, x?hOv[ ehAF΂vLG){Rlu؝UHQH͝{n`5Y<jcHRxYGwcC!'Ԇw.#4tC^; g:lJEE/ +3wη!SSe0O?lfbgh6Av5а*Ui}&X5IgytWzy=Lyv_ 8ą>tV1̾7ɥ&[X4,B)ޭPʨVZb|MX.+=uVR(+^ dU@2Rp#kβ#YN-uRֵ3+㳣42pQgShHU*_B/ $Dβژr묳:yj Y}f:BNUYdK!uMG:k`9kuA o,6=a>6sVĻğeÜ.ߜ!bYsOQ,uN"$ɼCH΢k1<'*Nz/38 U8~Jzh*Beg؁dg}6;G>ݲKߟ +GCMO+EgQF&CcݾY,UR:C +後$jaaA&71i}LTMn%Ί 0O{kmWYN0Cp=~o\qg>xt@(6e8 \@U0Y|ZȣrV1{" loz) :ysR>QwYE&AYO&&aWcUeRlz +wMaxsX1ˆˀVY)V9 +AIIľ^_ o +(XI7/UV LRu\9q fe,3+Q_|g!*/vF-κMq!*Ok/#1r%YQCYw˺F8빥"vgAċ2Q5 Wdٜ́g;xy g-ᬷUabg+EyݽfE<'tbڑp%]EjVQ<3Qŗ[xDT O,/RGLrJAeIi5Zה0M ֬O +PԌQxv;["-ҁ S,~O'qnw⓻m͚75U6(4_G@Y(3KZ/dWjs jJlTRMי5-B_YT Gp$<֬C]jmfwJVn(tɀfقz0LSͺ;f\m QIx‡9:EYP=2M?L\rQHrV0!L2H-磘%ܨ,Q Ϡct^6_dG|Rq~z,WZ;yjR +LRe֒f9}775ˍ%{fݗSRPa<,{=\$*4 R5|Y|L'0fq02=.-Q4+a LhVP Ѭr]n>Z`lѬ +6Av/Euratz3 UMcFH+k(6(4 +j$.Ci;0Ej4뫁Q\Dptp>GΞ) Yfa=oY5-hV4 s0:+8,Z@4T*,/ՑG0nۣYx*Ѭ+GD?eL/*$ (뽞rMiV@d{ʒmJiIPs%Y8Ne0c3kg2GH ͸'SҬFΏ4l-Y4kvּ.Tb.e%9l98uxfe\g 謅DTDn,QhN'Cf'%f pt6@?Zd:|Ga4 {Mi Ԭ $\}2W ÑY:ڤUҺK$jhLAȓ`f )~Y Ҭ|Iӯ?4{cj(KҚ,dQYp,cmz8Qi֌Bо6JU^Ռ/Y\όȺbz$V)D'!,cBK.V&nqZ5Q [UږAuN̘^g׬5i LO+(JKp7CfٴD,iXdY+ugcqU{m1XߨY[ig~3VtR5K"jHߘ/CwS!84AGRe-f|)jB6"'VY͢v;xf9^;wY+jq\,?V3Y L9]ݳ),֖xgzY,8o?4Y{OHf!5kւA{YG~J!Y_WѶУ5\hˈ6Viܟ[Hn?Yݚui.w +JfEOP5gL-RKjYL.o!&B͢|B 4OTtOhC)D% +i_WpTf啰Ziz`1ͪnrfi[;ʛ%%G $&zլ{''jj .,b<Y.WfY#56!fZaR557#YoiV.vEfZ 5V?NMt*9!%"%MVO$@%(/[T3PmJqSxa>3V2{)A<d +UK|:IHYáKD}赔)|4w>aL=@(j O SUo\nfƂPl#gDc}k$d>4o381"aEc/`ZF,P+? %\-ÇM$D9 ˰YLq⿜i T}9Csj1k})nm< 0|q # *T" d}w-'mo!E)l8.&$nG`8 AЉC`pZ 2~C8lC`o``D *;FpDRMDžL|3&qUBNlN  ȃzQ +H70};JRσM'fC +# aGHf23gY5GmY:rV.rl`L-6,$_; WXC&g4֟hyB!vG`y|·F\Fi\. W!Z!_u3 xe[g肄4 5eDhϡTàx5ģT +وe ![:&dϲ\0CXX)#|6Xe;`0pN_a CiT- A U$T63E +2WHN|eؒa-IQ--oL +,/ %?%/ɳuSSehQJxX=܏3xp?`3QPϨ# 2c(]6(y!p+AԄHCQQ4ERahEeDևrI(.B/b;|9N>OğS\ }6~H QG Li7F 6&/1@ |Rbfel9Œ!$sx~0./˕-B#B +T,\,*HH0)KU8,0ar:.)c]?FghRA^ɠHQyg[ 2 Ը`Y ' iǏ\)lZ Ƿ:~8ꆡt?<8*)4e z0%PBf/ٙ0c,Ƭ ΂㸰0?dZg4ӴOAs8C;DڐESA6B,RF| >f Q }Z wK"'tuOO [:PBP%,!ͧ-E;B!.Dń!^"ѩ\\gE&qCXҘd(Ӧ7,DIݛPVѫ6bE"z=:geabJgies/c_FH#W֢9gspp8p n*^ + BZ8\dD\&!:jhLd2irKRy4lZ-oLmR =)I2j@߷46XO3D4ƏOQAr~pNƼG GiAQ7Di-HÞWҤ-ƎΓCPf(f\TPH㙔V9 $_ +O @"x<,#Vu@mhXBDj~m' rvG{j8& ^Ґ@4l@ t3`M`{=|'i`,EټB8ԫ'Հ4> ЍE4/HC@]~AO%Si趱H9?«*tw'LxiA9` +)4;$24X/Ei9i<*»:aҰ)F2*R;!cSh>j2XlDiΉ"Pr|œ>#PxEC^*h'~~&2Xw25AG[AߵDg)4|st h̑%Rk3ҰW]JX@ xLI9x0Ƌ"An-p37aʞ1!֎ ~ ܾc5WYܖǨA\Og_$'!HATӆE=ܡ5M,4feZ?׿#2^w0OAaPiX]х 2+tL4~"8jҘWӻ;TAoj$KQpM$VJk#jCvB܉y%JZ] iēdMB +] &ʋBtʫ/ޑEG +.pgO=!Ъu(=QH\4֤"j1h`\vE!{м%ee4djn nWwP`L=TҠ/8h!qR9J@^7踁-CBU&C=Mτ44ѠBJ4 Br AU#EVҨ3T#\Syba!^Ih%Ѡc3soZd ,tq՗#:vS$^,2l97! |RL! +/.nfA!)sf;pQ}mB4 +sº?=jԌGwT̠ZsnϏXY?1ETq40QHA44i &4LQHt’-a1gRCE2 ' Ҙo84#癲9]Z%eB5hzgH#`A ]v(R}t+! >e|ӐLc4DDO! n*lC~ +,RzvÁC}x(Y! .(-,bd2Vhy$ۡZ峤 i`Ҿ#Ҙ f%7Sp4ۮ&"C=pp)d# P +v/0v0Kp=dž^q6AU&hCo4 ihiSsww\졵9ǘDWKe;:N(f? )i[fª^9 J0aj0"Hc/qcA/̍!U{ق4y Jh$dC4Rdq?s UkqSoX<) MAS gkl?H#4zWx4X!:HÍ)2e]8&;oy+(oЬX/Ait~D;&;3)d5"x>i#݀{Ƃ;;eEڱA1f2'Dϖ!^&ẄM5!)`<qv祏e}P&NyS,g|ѭ_0 :AQ.4BR('vm~2! KoBM(Jo4W\H# n<64<$7D@ \o(bHHWA35Zzм{IYH5DlfWO-A^BQ{}[3 iyr7 PlI744vC |! $BvFn@G7BԉGHk.}73QHj)aˤo@#a4>•6 +zPlb{Hu iR,fB;3ZRe ݳuoT +e0HQ]DSqu2\ B$%! SO}1iB]A&!>HM /d P͖10͹a" 'A$42*hAe#&h `}^Q [B3H/$+cDm2:d鱡")KL!UHA3ѕsyq,?z +jh\84HŸuk%VxBNA0)ލc Pib3ؽ5鑠x?Z#h05os}2!a`T ^HI6_JəGB3я>؅1w1ST:RPw?sCWM/86sm|yHGohPI~]~o(?D7d&4>| 4QO8WGGcN B`يoh5_nGa[ ©#?Rɱh[`A7@ÒTqKᝬiE]C Z iá} g~qWu|97A|: ؆ l El+FV*OnTn2ot + ^mi8i/+lSU1xKKܴ +H\|N I4Ӱ`:3/C6 M;CA"p@814({}$оVLV*)Ʃy1iP8yf^.HiN ވ$SSUc8}x@2OchiP s%;р$\D)4?jSEXXSϢ*ShHRbGczsy^Qphk +Hc.EJ3lbت( Rh%"3pG@A%Sd?Qlb88&igPr+ϲ'h<9iHr1T^ -l6瑥xGC`"-? ?|:z4jA"<@`螺 \jр΄)l +h|&Hm`U43_Wmdt GCвV%[j8 tN Zٴ6  )[œb0dz4hP,l/eқG a<"5;\d6e0uzE)T/1\enB-E78^lf@GAR%|4~Uܢh hpGIA80h2~eh[ڐ/ Wy+"ErZj.v!b}o Prw\wGhJ¿X ؐb(VXX[hX}G#Ԕ+gIufw$rq `ψI*jP|4ާFe0ۯG vGc&CMf6.w2T$=419jl ۣ˓IE8bY)ƕ P$c$X@ǫ3:I2l|ңW\5  J:`50 +l}ף1-]O9Yj}5~eh>.CJ]Ь skp8WL`iScYJLBV_@8 Ymz4Vj"r*qr ߣ/q H%]Ѱ\L9οjœ +$"d꜐24[Ii"exA17 u 8+|b|@D'\nU~u*:1JLaArr<]ipL?pdG㧋yE5Mx +XI2[ᦇ%-~9Fn|&eiOuI X'ekD '[)&?BhhC G.uB ":@1yD+\5Ӥ{׊YR2\-УqN:)O&R몰: 9O\d6h T +;GįGgfѸrAE] $ROR!ޣBKCiir>i@ʒRh.Xx,FDOjJ +ф&>bC)_*2>>SezFĔx=yty4sl}4IP$0Lx$2ƦՊ!F5iG0ɎI)"f4#K2xNa`Qx4lp!_ + 6E)-)(@_R|_0]klxJn5dZy`T cEHƣ4/F*J&ˌG \Y31<F\XPkfT(FZ@!9 ֟Gf$,qzؘlh hTD;T; c#8QyD%Zn-G#/y']4d'`5yT2\|3.'T؎Ρn}Ñ:~88Bt4VbTtr㣏|X`j[JvYYpcg)iɐ C j?qTd0F~gr͸ ?ۀG#z%tZh=NG#.ߍ +$!~ոp}w4pGxR#pJʕ;eM"/p<ܷ9VGzLR6?ϊ ŎC5e^F p 79#]UdpX +TdXwஃ]7ג?3zvBPGr`.(x xϣﹺ <_ pojޔIvGFI I\LQPw48sa NE`hW~xAh?0b/` s2c YɎ(1Gp0-٨ + woшBJP^1ј7TP P\:*Ůׯ~Z'9v4{v Ձ~tCZ0 +;_Ǖ*\g~*C 9oG㗀;CndG#%w{hOihk40@*.׳|G@hB^'ˆ+hЋB˳ievCpupH"hDZGlrɊjt8u^nRԣ:µpO.H-YC!=S іpILP_փ$xugk1aŖ0SyRhoRT +1v=rXH1Y֬z4 c?w 믜@ţPQbjRa|]@ 8dyBј) IvE +L)<!z:<?J Lސ~GLnv%ۊrA16R:;(R1F|v,GCp^V+Ў|TZJꍆդig 7y9bC^>-@Z T"W7k|+ (')N\nz)I Py /_vy9KP:EOz![>k1B_SZі5XA&{Mhoz ua CGGh{z/fE]Zk%8({]3]ШST8̑);m4ʤaU繣8vRԕЅd;NR!!P\5}'I|TxKع5r۶Af1F@w3Vhl-P-ÿ2 Pt `qhR 5 \DN|>rhm l4G!t @;>q{$e,+ pu;f1 +-ռv߰j} +nAu6Kȗ[`|Wɰ(Ϡˮ[dCۂ =Ot*iغyw !Z{a)6bca8=P,E.z9(b[eL86gβ`w6+uZ~:/_؊s7ATӂ~hALw |5~ͽ,83>`7 OPYL]kYX`e䴍BKNNl2U?Ҙ*sS۵h@Ehh'0A9 J8?hY^Es1+ {ƕ/X7@7 ~%uܨ Z +}p4hL7@4PLx!2ؠ,cr:X966USXK8J`6K/;iGvV’FO\sG#,+H t0EwWT[W_5C2z +FPxܠ +g#\4$]h勮W\\!X7<@D@F}рO.+skgS΅61Yc$ʈ CZ/uEԵ-֞^7ǵlTBʀfvfÛNgxތZmk-1jQ{TQڐImbF?4ǿe^h Jt*n-$2m G-[mH!|y~8^ѰSe>|yw k09̕ v4ZlbDv4榟[zF;'lªVtqrYM֌B~j01=ۄv4^vS`icX)F[e_./zHN*&|E8jVϋ%??P}P)=$~G#V<t6K˨N^`hx  cz\wGlGcێtqRY q + V~QV 'jJ]@va + CAGC]c6%$]v RH"E]Aq,>^MZa5c'l|}+%D8VO?=Ӧ$nQlWxdKOm;i]o lGc~wжAC +/1fQB;qg"xoG# $'; gpT`h< tX)M|ŞZcyjn|mGcʥ@Ď֖@Beh Z%h/WZԣ2zS8nj ԣz g; E/HVXehPGCE LOS 87yĥhĝfͤVfA`@9b˲yb؞ԌM3a'17X1s',hF>4X͎Xh%4r46B +( 46U~n3Ga}-\@;/C3.rl%(xMt\g@j\:\qΨ&gI1˟BͰc0݉ AM3D3x;3fc*3C2X-pD@p-c-,\8m^yePe@q,#eABV( +xeRJH1Wq^0eXMi' +s2=KPM>s|Gɨ 8ddHFzdxFFZJB,V81U 0dVP2.y1l 㕚׍}F661c/= Pe1F81ݍ`Kj1h6n嶌1v1PTc1jHNfcsG/V\;`(%+ƥV14bSUcy)E)<*-Nc"ctg!F\LAvP$bI>3xaac0Xt0h1.w, hHǨO˖0aXˆڃAcc 'u ΂ #Q9OZ  KOئc3WtL1""E`P +l#0Ac ;A-ay1s1Q Ff:6VHzcY8L@nG#TcDVcdm#d=d"KF3٘dPA'  0$ʢɕ-,,0ղ2z.^&L 3 S޲̪>ef%*RgP"49:`FiX+0M,f A83-g y0: Cȩm\vwTx#8B`V=W泩3.q/!FF` 3~9Gs| `$KHd6_`5h"Em.! L8+tzŨŭc۫a'Y:aMQ [yEӱ).,`^A5 +.۝CϛZլj1쫍7+559+2DⒺ1k05]*44㡵_}xʽex~ Z[j-F^d8^ѯZWP8^ޚk7z +)Z2 +] FWj^S"Wkq +_ +.݊/lB5B +E?l֊fJy +cZQ"f[ +d6͔w["Ze+N/d\O;Hl#(UD 0:ViE*`m@Wv͎U!mU\E>Ud *s[!T=Tvkũ@n!4ח_Y݈g `T'dGEȨ976E *tXh۠b}MPf`?~U v74=vS[xˬS@ީ)ټ!wbn!-oH4B3f@L!/|(p]MMz,{\h~R<,T +(R=)<ѤH2(j"Rf5cXplmz*8A(F*B(k(q1Eā(zEQ(PP;՗XHÒD +IdLD!ƵQ6CQ85v-C!2Kq` +An(iB19(f7}džYr' +_{>ݏr6[fd,Wl9E!t9b?_.; t=hSsd +n0 ʳu}甆՞I\SιNE ":ܹs"v4 w,z\s +{Hj@AgAQTCAaPt1F!fƠt֠V`Pҍę.7X%Pn)&uj ʩcuAElA<Ч@@L +' +%V=B2Q'$'Xi>1}u&'neO_]z"5N> :xbY߉ey I*:ƭ EU7g넞ȇCT'`uM:SխnbubTu q :Yʙ:네IiD:#uxu ;M tIvBE uub[РNpv"&uWs8{N:u; +;A9ux׉k ;/;qՌn/;!R]sbRs9Ju>oW(Ĝh]u"U7Z澜pf T%=*# sFB_UWnA b:Ώ]F.p2uw):vSĎ׍"٘Y-;lvNGh| #%e9a²ciogHN9rr;;7'؜Phל!ݏûS;/"xxG>МOלPh(N /!Z1CKnjp +rsbDI+&L޻)P~9#4wO- Da1⾛.'ijrSKNR<}}& Ֆ#b 'N;/D߅߽3M(@߄I&'Pfr7O}gpq~wtK&P(DMM H翍 ~3\&X@!&=xԘx8pi6Q>fxH a9,:KISޡIWP5sE8z-E_"Bo'BÜMM&r]$]=!}D#FnXDz҈c# ԡ#DDTK}ws=sa*apH!C^"-{k<{m 5"YG) Gwpn:!{ +!M!P/$ +ѩwt'Ć*B$| _ + !!0B,#JsCD@]o* p(Bntm>s| N*c/jzȣ bO! x{|}%ؗ(ڗ A4j0D>ZU;p8@GV~_?qI DqH9ab ~Iao})#~lDĘV JJ&LRX  lX%,~ Wt0/ @`U̕K?'~7akKds ~_kx-w-x=OC~{rs?8K'{|pO?guo V0gS0'>Ll_@P)BE?*ft 2?ؼ?*3 lX@C@|6aX[  Ti%ӍT@=PTA"h?j>C= +Vp׸LP`s]f+  Cz1h};c0a>4:Εi+{`z?dCԤ|e\v8(9vxP|Gv]u OUW2H0)^֡ WVnTu 0urCNVL).tw! E.E}G9nn@[cK:jN J`]SuYx}eMIE)G7 &BJAA"; +EQicjA(2cP#Q 5q=i, vuCNAg8dRmh ^O9(n; l[ z+,Df 6Šnx->1XەCOXEᓃ ΃<='7s\   7l?Zcp.cp nL +1^sàxx wI Ne: Uph`A @Lnxi+.q08 N,t̃a"[eNG[04nGYίH85n(Tz&p$Z>lq`vH! 7OcBv!"$-߆q0hmTa6$!vjs0hl83 ;lH56#6Y lpnuF]3â!# fa"k0|j/ ^UK +M KI Y5ԠᝆRiT2 F6 cZ^nfiЕK-l@÷F phkB& gxggu\p +  +`H/r5\„E` B)HBAq@ xЧ 6f|<@} f]̠BN, 8_} oV u ]f/^`H ͣ $x]pY6X.M߅! ]pg]0}K[|օ󂺪. *ƺ`:l̺<7وlt낥\h4u^ ^w]R] -]xO.KXJJv9C]Kt 4OP;:MtE,g.v<]W.hGG ƪOb]P S .ţ }uy@', k/[\pr2s!=\@`\A#Ԓoƅ .LoA`oz D Wv IoB+E[ [8sp-dΜDTL-|iap fH-YY 3 . W Œ0…,БcP~bO o2 ;_`%`!" |yw.Xwނ\AkB4 + +\0 +-' . +O_ee +/\0^qp,ngxUWUp.TaQ@tAI3I} +RW$TX1*D% A4[mr?.X)\PZ8p +&z:SmR0Y +7RHB)IW`>Rx&? +XulQ8(H́RL@悘(a +ιPBaLBa +A6Bm.?Aj?[ KONy‘`ЪJ.;-ߙ;s ]B.\C!]Q%$;^ \F.nDj,H:2@A!&P +"<@*ЀHX.pp T@:! (0  !8 Dpp !< *x`BEEE4;[X ;t_8,M-@"P t8Ϫ("Ry/V*hU7ʢR2\Mdeٽ9'\8TJ\b6bLTr +GL˕.''/K':9]h(b>jѵ~{cz'J=Ӈm$7$bX8d2켪h IxJt_wcg~\6+Ӓzn|W޻N{+ݚ%3W3u8!U{.=,C)FUJmZdͫ!2٥^HZ{Uva߷_$ԞVyS^WHe`5J7wE a[:W\JŦ_W3">D9^B)֌2$:3Kt1Q8G2Dfqfߏ=4s36:tqL3J~=RԍiҸ:^5UϢS' *qd*֭L+ +UYNU5LSXuHV%E#k/;aE uS6Su6,%ɕ>[ 9,Rt_ +-zxEoa#L8xhe3vG|n![ sqn* ؃$[ܽ赨7T;'U紕ܓݲ-ԧ!ەb3S a +4FJQň*טz#ǮPB%ۖ}nZ[kQJ}tn>Ɩ痑+K.ew8/SDxo3a1ëՋfS{8cyMϔ76yEB궩:V|dA3)/SY4O1;3/t[0˜({VL3Cɦ3.FLP}LѝHu%9Mͭ<G&VtL4HcVs[CZHOnWWqћgSx>Wzd8P)XUg5}p| 鈌:$W|cRj;=T'o5)Ě] xSkMLF?}cucΘh[EO*WRw졲o<%a}5^cvOϵqIb5MeHv'2d쾪vd/ G"21)lΥ؝LUsۧOͩ˹:Gk8ʋS$ӿ-ZՇ#nuXf!q.Gu"ATF2JO:uut$6m3Sq4%{,^y̼yQj|xz+۳ ϦGlٜU26ayTd&4vlb`- Q2*Ag}Apz+.aѪ`l* BIb,|A#\%B򉬍rōEG +ۢs'|,2b'cP`j]@hyޱD_E:m10W_=Z=&10&?/&x î7M 1dD@osmK?t{*Pąj8T5{N)!S YOȡf&C7TO5by0*}Dv%ʪg"ey*0+=ٟFq g֩P^pI98E.hZ/bN{(?L!M%VՐ9:P;j-hC7$u=i0Vx?r] +?u =*TBi\nMfʛyG+*WݤsA 'e-uMU'IJzj ~ό ReP IX-IT'4.ya$ :W<{Ϥf<(T(,ݔ;0)Y/t@tR2ĭQr?IRm|>"tY"6) HR*RC}(:INߚEJߘ=I -R|%(H)q"tؠ1,Z<ە",YP&%~NvB2(58 63tj=dR~G)c'r]-('5SFn d؏z*GܓY#H3@ +e<Ѱ>ͱOc15jb.\'4g?OԜvz8\2' +-đ}s-a*G )3f<,>ts .M4JvF7:} IgmTZ$9\Xli\h\qBs @E zHԹTB(o’Apb#R{:.;#NIKqpC TQRxAV+'4NˠJbtj5ľ3 DY*6; #봇v{Fj!GV>ѩeRmG$+IQͩ`L)B V#qA&1$q"Qv3;Jjc9M@2%|Pefh}R+C&hd}S6ß g-LIt (;\4.02Q-~#$9O44}5g%<ev 3!StiP*<-K$Wr1ܤm+v Z%A( }ff͏0p(b! 7,#^`8kWoMerG+ g>;u!rM{M\ YDnPp]<<QSn+DKTļYKM=?w׃QO0~z>Ϥ }#9%Z:8BcuAe>h%ߚ.'zLkH'}̛@yαnJ=:K6I>DA-:Xh +ᏋnJ +ވ0yx7^ eYj<дݟv'`PSٯ:{ t52[ 7kjwSQx9G-wunjhəzJJkWIifV,תCb۠ڊBO&6˰tJ*?/'MɷO] (1[aI |O,hgsGʋ +$Ϯa㋦G" 52&o_6MZz"etrLɣx`/ ÀF*˸<\0*QaƏh"؋i hFYH򭳹9#}ybDl =8͞q\0[KT<CF: u0ܞ$?dDCU4bE%Л"4꓊Aii\5ފI}k:JheEĥ + N6!/ kJ4&W]7JOxxjO{K7]b92eP|vI?)sXdLT{-A_p$\x:܅&TyଋRojs~/nmo3Mi3OԍU.δa` aj#E/ }ם Vhgފ~y3<>:aҖ-)%FC$"@|^xQvzΰ"%h:w8\UyG㻾ˢ&qzCibj~X便tFA|,$Yə560>\-VJ]vKN +aF~c[G`>ZzV8y5vKSn QjHM ]rzKW|;}>({~I0_SpBCi?T(>z!^"#vdQw@By+Wyt L-uHӋCt֖ +Y`A?R?[g\ V)=VgXb|fFJ*) h 2=MC~qQn54*h{N#Qvۉ~⟜ pVjx7X*%Uoz#b[} |?,?]V%:`e/|A̓GJIy9;iYG.`R#1>L#ϣ0nTFr]q/2Nݓ; l^9 +a ^v:4 ^VF~\tMA씡?`qrIM(΅)Ydc g;iA;JZ o\A휞2`5qe" @b@4pdT"4zQ”52R-VeLn`Z 2 +ʁcB`n pcv.a;Eۈo| q?Ou:S1T(۠Qe ` +{\{=eTgԌy7/ f}r"l+C牍iQaw˼k=pDvyHz-ҩu{tϑ@)lZxrvJM1Ⱥa؈o/gEN" IgnR}Bnjkm` s!VE + 5㫞`5ms +Hf jI)@EAd:xM@12u\8HSsg,>h};\0..[F۽Zs4v׵J#PЪb 僇占E޸+64qšz{3i$wZ™{CgB$} H#P @ +#Z=)LӠǿ O-=oU5;B]o\4M=5eoǸc͡ZmzRWL*ҋFzBrQwщpx?EC; PW?1 +2pn3qE3PZ 4 ?MS¸qg}Of>QW3~#bǷE1O6Och0u ƅ7tEK#p=.^N[ںfX.ςV[+ a[J^͌i:ho#%AKvN,VGC#t() +?Xz]לm?=h)}kTOpe `tĹ؅܀+aIoЫUeQ]3P*@2"*Qr_pB`FO|dɏ9gϘ{b>"s]/ |S|}A] P X#UBT\8) j\EmAe ⿬ޠA|m~ ] +=+klf(]å^̧V7XkCcװqɕػ7(9_f_ިbe*7:o%,ch'x4YƾP4Emʭ = 9#nZX5SЃHh<9[ط{##xw@rD* YSZ\=6y/qae0;FԀÿDd] $nGhޣ:w`+x@i"rH>J"`SUDdSُ#o%mc9Tyۖ$[i@eS'âJQI9Ȝ]F) º}X<8׬BP{`\8$ Z5&,h FO 6t`"$$5gUvt nMa7VDo "ŝؐu2$m$ 2M.-P|\b\4eVg"R$ZT([|DI}4 䳃j#F(dt@M֢mƱ "ͭnl>z)+I`> Y&]7Y4.D1R#T(KxsUjIPc[Kn4(QNOwDLXF'%߄k$EIL.]j0 c {uD֨oR@hBS=.vho\bpr]sߴB&hS(l&M.UѽwUzjI*S4¼S}s |>hKѓ" ) dygQtR$E&fJ9@8jmur.r"sߴ&3%bjXjiQ{5cÍ>-t23Wy`QH +Qa~kYTvz)tka/S@wQe D%tsnY0DPʻR:`j] +U.8 nL^j @v*y)'d;*Aֺ\Zi:/To,сϷ z *(h#1AHh}BH}!9tdDrCBXM~!MٖwWe7Z ?wu @+{^PK$g+ w:1yqpOdu9$Ev,읤%iǔ'ebL[0λwzrk$?%"q@rP!sK/}ج f~5[e=0ŻOAGߟ"5搌HC3Msh:fpǺu~*)QsƠ4 +Y0%:=L|jI ?Y8G_ݏSTԍ\Ac40?OjcAiǢ_=zfc6>vC9Z( (OBiSc= uW%#Ëp~cj|4֫1<\ؚㄋ:Tw,&G1{i15)|ɠ;vV%Hc_DSQZJi;A]+ֺm[{>~f{l?fHۏ8?g4܏e* Z dJ~"叝>j"Xi;L2@?TcԠ͔Y;@&U>҈\FeߏS[ic ~}:I 򱲃5mQ \3dI"AB(llɴW&7Awep()DSܷʎ}+Dȯ,/c.Jc _Fa6g5fq[flfd ,Yrjf&%,;Ľf FL]EGqY0#a,gzg@ciHqԯ4֓Yוw[7YTzK: !#1Md;@'Z[-KVp&͎cCK}9F舏6\qSv'F$ż&.;v~iL&xJ_ku nJcN]p4VJM+[UZi$^c>}.+ L5nWi<\* Qv=Ey4[@hkǬ`E)1ng8q!u8ݪ4Zܐ EN^(9Aݬ4G'^COV9*r0V/ BӘeNCX6Lsyͭh\69T{.Ws4蒬-Wa%k@Si tKTs҈-:P꺜2Mimox8uu34TuJ.M)@S":) uO3RNQi8}ZhUiuJc֭Y뚪4zT19F34N:CYh鶧4lmMVGI:H)6N(KiE ::m^J#%/`) tSyNGcxŒRj'+SJcqw袼S!ӻ 1zgJCbzdw ȆNiUMX8lJҸ1whxEJ]O%9\mшxT%MlB ^#15*a88]]jgޥҢ4<<(r.S>e%{N>i\)xT̗@MF 0!J(4r, .?^݇LY㝎cQTJg( s?^hyσ0)}/+;AޘE(F8%S^ ^y4f0/y}N4759b$PFR^͖!!6Hz3zvToVZqQ[=`7meEc4~_ջz)4NӢHL_zYϓ,1zOg}o[Lilzzs=bÀ_82z҈Vv[oSL{f6^Q'Q9mK-" ,' }N<A$T4´INM65<# w]I#P:7h0iF% ᢉj3{ ̞VL\Dӿ%=JN% _RpO%&c/Wb_nCFᨱ%În1i$hϝc"u7=%+h(!0i^cg.iä'a{m}{Lj{wy@3L=72kOư>y |BeY׿f|#x|n&x>xoV;(7i(rҀ{L|i9l''-3O#I>EPvF({Mܿٷ ڗ'J \7 +sT}`( a |{|3{QќƊYWO#uI;Hd~;h7/T8Ʉh~wkI,`PIJyȯ\ʯG=?A.q`72.~~Bi\?]o~`WŸMWY35NO +du%nzXJeAi,'e4NX~`i?iH$[_>84 :` -L(q]!znҘwc`lVW2B'pV 48a= 9ARA(u*cD+(e2xg҈E4Y ҠR'"dd5HuH J&t`Td"hD,²A 2g/e}`|h28C +|"an Yi i#2+ +1h$.1g *ȇ.T >T i24>"5^1dbd S6"}+4T L *4x;AĆ4Y>=a i;g"UADS9">34 ill(^:-4p1  _DuʐsQ40 Z9"u`+i>9a HL K ,H#BhAn3@}aap6Hæ`c1 + 08_jiIr[(ɓ+zJLe/ጃ#w@i +=.(.X`; +/ i]8D[Vߨ .B%QH[[.C!6uAr tф4؃0bsJt5M)q4LV28Dwt/0M(u]x4#]p&5Q2]H'stbH# '~! !jt^Bi$8HܞJ! u){V!i^.8IC UH#OH. +i+P@u[M im_ 54v&C0k]i|d m]W l? +^*m"x@Xe䞻 =  netJ%IH^"s{l%j+l)uA>is\s/ NIdl?G< N]ÏjeЅam]` +HɠHcU p炷O LIEQARo 7]0^iM6HÕw$H\< t)1 B$@P⮴x4ڃ DAG=M>AW]b '$(f ԕEi.=]Ge~4Gd2Q^0 ?v_ш:Fi` E9S w^ oygb//$w<&^PvA +Ɉ z4eVq}4l A3^P ql|4G~HL?== ;. U HLFZFo݇>. > +(.7 +/#ךwI |4؆ Qϒ3񂁏wGcm@]  M=S #=`}48ExGYtid`pMBJ-^񒲷."=[K/X3.aQ54=D=6`2 I{48 *A Vy0x/0Jh g V-ZxC-_dPMd0أ`tL4Eh 0_fٽ W< BZ<펆[%.\`8?!x4k@IGZ[ʫLU1ףYc AO&b*hQo֣?/o!DDbt G_XE +j<{ّ꿠qw4J)*{J-hU-w4<% (P) N"\zZ8zȜsp4<R; n4*Fc, C @MoˍƚUheh:BZ64{X` 6>l4FYeU7ͧ-m4nA%L7)? =.FRD#n4,+ h,EQEE ڔ FƤwOuH"A~g(r4ZX <I F8/ u;0 x4D;-GCă^4@`bB+_hGx#Wtw40̎M ѐ'!;tuM`P`(v4 2kIN08Oc!;Cs0h#  GщJGv=Y7" 8 :{hPc]Gí0/NhdK8qiE0[<ʞ;;>ǣa {&QthG#tL,?_uxtcG]Tȳl@jA eAsc0~pvQRt4x\/n7G<:ʘs9EP["!A4^Ɓf9im+󱣡m4*Ԧ !nwL(;<6!S*8)hMYGh Hq]G< D=laХt4U3OA>Jh,FTb0ku4ɨ,0rc]qh)oS+;̱n2ai>ݎj00`ycJd8Ԇ)CMꚃ02ЎV?/5 j=IJ)SJxLa`i:hT`KkZMpTS(P8, S Y`* ihYL?pc PUiEΪ׈wUjh6BXvTœ15ij,)H f\l(1D<~Hŷ푻ĥ\u[i`*zc花yzјDJ/lЉSUX *֋LڋfoT^Q"UR&1t#XPa-* +.H@.5%eGf,:LJ# G!BD!Ӓil[%FEpp^XtIWн.uakZivz%u,4_o45E+?ÉO&z)|2,j@N$E͟i)m&6c%mc)Z/4)JRo/ޢfwOI .nMHhĠf!5/KLTV*u0sGg^zE*E2Aڽ+hQ^ͬ+%E뉦Zk!lLS\ >g/&n59*Xn{aM(4TT\'bJ)4>ZDu\XDN@&<1'T$8( +a J?B$,İfDUl +[m;naCP30R{R}Ty>]ʌso\4՘BNUL +xSS gFɼ2݊<Ǜ35> #(ԌU[07d?C ]DP?ӴSЭ"‹+@YAx` 9 +5Vp$J1=X0S>lbsDT?Nȋ\4M9ѥ}86raO٦#cB匴IfJeL_{&UdZ8%u:S>cj^Dr̤9XKJ\''Jfg銲SI#BGg + +k^ǐL,Y㉨ɵքCǥFU7eEJqGCŃk\I"3CH%-j'e{Z~YlI竪GO",iMHmb9QUYͼ vnop"E/}T#w82APHkTHQMJbL(>9MD!.($0SiLˤtbH)BjT1R[B*GU-8(ƂX^idF%AZh3ĕ,C AdC #Z=-0|C AAMP8`eWݘ$sF ${&l™4!YF_~%E(Q?n92䈤NAd~" I͓*b-ҢdebQxbp$ȱ\ EzT,괡pF@Źj,d%jB5CիT%KsžfZ/O4Cڿ8 $Z% x)mL׉CFI 12XQM{0ojbbbWVjFɂ^.ג)XF1d*OsEfV8-EiM /+WMFXsPet!ȩ =mO*5JXE[ ޶3 -ymB%%aK:A"TgU) ^'MOc%dc=9:~kf}7=>KXEf +qSHA2"QpnI7'Asb,88P`@ \()"8xA`jXk8Oф޾L%^0zO9x*nTm|jgTDє -$)R!zl4 2w wq+o`a$F5Uj$A=!1<`2wlĠFZl,AI i1fʎ:Cޢb~Eg(3 <PEI D-GSG}vQ?X7 (R{֜ueJ!WELy7!Q\f&>QB:sӘ )!1 Er *$]#sT(!mSPz`>0Ŗ0: +qNd#ٻQrCi*WTN'xVزg1q^d* Q.#hqdxQE=P*F\#_ blHڰ}k"cS^XS\d0lg`qnuWUSnPYjVP[UߺhfJ;5d3aE=Wi^I oLQ,4垤DsmQmk$H&O={< ':8hn^%NB] qcHS'9JU:#BS!,!%?r6-!vxH4b+V0J m)RSN%* NIVJ@ F/a^û-f4}0huכ +b/=1ɃK S 7\rS.x{$V R4L^Sf d0 ! J)iP",XȃȬAF$P(MR2㰘y \ gЉ0( +iqВ"1X0Ȍ)< +̴U d0#)i&h`a&"L= +B2J3 + U D` 4@2S; C! 34h!K)Ø4 &՛ PM$XHADJ@2k7U@f""P㣈8Amĝ +Ñ0< +WB1TX͸J ^^8(D$lD4fzl\AT*nPf&̄cUVmXUuTQ{!"8gSf*zhnf%T|Zule"n"$%-G,T 1sb8rb8⪁KIX@iSt5(:?HiֆZ/kI-^uub峽`E B0 o6Z0O`K'hiq&:MbB,T0ZOmy1%!d`C\ÖX  x9^t  VW0~i@T?~ONʴS* r9KV^fC |ByP#tBV6L.'DiY׆֔ kK[=fuZ;4} +'\(`qab[VØN[];M*eZK\q(f+Hpoeǽn'ψ*y +mO3&<&j,뤣}渽25MBѣ[Q2öq:Rwx +#vL^bD̪U<o%Eae GjJ>ʨ@JT]TSFkMF`ɱ^ ([Х-:=b~ ]&.Ú$ рy@uBj(S2L +`BBa7za9M0|=@l&}vg\X1F/ҏO{CϷ +:35ڗZNt^!O pU!'Dib*Oui"gzcrr3HDy "0EjA%-s b3]!s':jOLVĴ/ㅮ(O7~2ↁЙ1> + h>1/~NXRm]KkPP}e@g< [lPR540dt>e렃Q c5_; 8=} f2R@HSFI52|!0ZW^ʓu+p{MK +gh@5u:;!4P9xT,{ +%m?I`ļ4'1֥QT4'G&nP6Q*`;ET͕of4GEJ%U@" I_^mж ÍkIdSܲ>D6WiVvA3&Ai=S HX}ݧ'54]j{VYNQ0OB: +pQt +kt-9|tV\bS#]R0LݙgT5}>Gt3GE~cؿ0tʡ\;@ӆM֚}ޒ .d%,(;ʢ潎@P@%^¸chK PNYeAL~BWG~NP[;3 [">\@5$L.|%q3 +) WaEx/^jan@k%:2>[zt|3:Zu{3~=Ǜ לmRz ٰ^b]̀nnӈ,VQ}QMK;ʣ-T +E27O[|yooUIKr˯ CyʞnHmb~2M09F'n)--Ss#g sG'xK%WjQbuvŰ)µuUQ12!ŧy"R'1k֚\ X +Ԝ (Mc!oPyCՄ1}x`B J[j(G]CWx^",WۺkοQzsC TCYU|^1[eN?saJG pҠٯZŒlY=ZX/|[}z߾/trXe-oUXe8>zZ5#ƵO0eq]U. 9m/ԛX5.nR)h @JaN/F"dg Y* -_1 p/Agz4J^lkʗI-؂r]sSЕ"wQLH!(Hzah!PSpFr5yXI=,1VѸ/<&h>z`*p0W84Qo pV<sQɗ1zlҷOzg3x|!Lt{&i7:ЁȰp Lm>l2 &#<\?9{ z*୵(j7(vJ${ٴ¹Og_9>xe_ry5-дʂ̨tNXߏ |N:,^'g Stw W)/g!فƙz cWl|1r> +[ͲՒ|6Ca+lc1d۳:cKCbc~ vޠ%DR~ٝ;{ePi JeM .=MVQ^L/߫ͅĖu~= Ŗ141$(r\ t˻dU"A@2i VA)4pdf,DK Y'{FY 8~%J\]wGb t@& ̇rc܌T,>8D7&/#ZTC "0֚,@nC55Hrr78@20+(`g# @J2D54?G-0%q)$ <2[($;%#K#}e%GI P|pmA@ҮLCK2cE&ǡT@@^f1fЊ%%'Md5֘a=]*v0ND + qDϯ,,1* ~~bI:c-;^Nb]a+$Mr`!x@eD_KEv,<7uUeّRdi;-:w*[c jct{v:Un&>0I`. +t-.Y5!A& xf CǙ*/gc_N+5_|t٧t.d=) KIKHm{*{\:Ȱ̜^Cҷfhd(fnQ@6~:ޚ /xȊwxͣ|KΙNr;>.@Ne\ Ѱz]T:0+ZPdsIH<<& :{ @17 Ֆs.Iy%_558"gIŋdč1^P &8CG F޼OӾxrR~ڙ<~uJ +EۄY~CqMU/_OȎAMLIo>A㙺T枀66 (`5]hr҇o1$azҤfҦЩ{   g9fgK^y,r̴HI{J/(k9GAؘ#J(1̘bu +pPŀxXbjV"b.Mds|0 x&ynHCGX?` |vWJC +\WHJA]Qy+OB\a;5~\ܔp8SuWT)K t+)uʶbEr[9JFZɩ +),QŊoH/(pʊW ൊ`0֓mHNYDì[ҫIӫ]]O\@q+TE/VA+|>EaeU{$7U񆨇*ȹĢè +5*.q-T7@T/9]Sj._RwMB p2L)FK< 9* + 4fQ^F@}HCIM(ޒA:sJOٟ(|B]Vܓ)k<ѤQ^j'4S:Yגӡ'GRjvo6>֔r3rc=2ݐFb1cK`bߨupdX4$6Sbъ( _LmW?e"`pE1.@|+H^4bO>*a20rq# $ +  ,9`tksb fX0! +`i' +`ـ0ȃZ(DH >wAtȧS`ِ @ 3+ߛVcMq";H2jPN[Е⠰$,}K u}gF/8|o@o?:R3}T{ 78{Wǘ|Vꎯ闱Ӛ\`R@klD^($;uέ6Z#Byq +LHz1.P^׍jD4[DcL:m^%}y8Es\ryS^UiS}'*lbt>~rGTS50c_;h ' hܰ7Ӣ3ˠyaޞL:$bOY{չ/c: 9&TBK/_F4@q_(WAt {GS __lS1lV10u5&@ɣ%v7\ׅèI%4E38Kﺠ*umkNnỎu !h1[ .zBs>L-b؇ue7ƒR1q~=&Џ嵺nn:@2T 3z,lϔR'ZGSڋ¨tsh=Q=>!AQ2/)B@u-Gyb]ÄӄWe:}ƍM,8a|wN1F< +pt-⚐WlG +U,׸.Tt\̵ xD.w>ٌJI/:6eej#=A~Ŀ$xgpI7*XCSEO)/0Tj]cё W  +:HsThCP9e/qUH$8QO_?~)D5q9GFV5k6$sg8J)" ˑW4Dbt\S'd< y3CN %f*{ʰadS02޷sWp  'L9r>hje4AA+ϲ܏ߝ76;wS-Ek8[J?ܻӲ驼'Q(/k**FQ~&_JO?CKwyAE):Tv9oMR;rKrzP3gSY+s5 xXl~^̗ߥwp*9sŒAo=Xx2 +tPh] +q@ hL"4kߺFƾq|PTX5z0T0դ ֙JtYʈF)޴|3хkPtcxcЦЕ7%1~tqsjEBՆBڼ>zCi< [4s~GeW&6=i#N",+2Vl5i.ub#NՔq'&,_MJl^y@.5goj9U0PCf^ s˾ +HKL.1ߡN[6b*7R6;Nq0b1UG:| q?3[I|̭r/j,iL񣒜=odӼD>FlOqz7? *ܹ36W]JԄ/0W8 +Z;Ix=lC茭%D3+65l;"=QQcYox\<Z7$yI|I=9wNЄ0^յs9^:UoI8b5cf~yw{#@"XaRkvIV ctƺJ+^_EiB}нDNpGrX+'q N %}M]'ʁE8x OWGC NCu7WB~E>28iO\RPl~B]J8ң dQD Tآ|@p,OF +\#%Y=-;M!gEO yv5 1n-:жݷpT&Z7.6tXxR$)#+0RVB/4GFBOS8_?q*=jL +endstream endobj 15 0 obj <>stream +]"0W{WdǛK<=.4,N'dr -3Yᣬ.|5f_\nM{914#9r#)$ E !PcMnaN[JrS31wB 4Lf@3z1$0"ΏͳQ>)xHS_o~޻ɜ͊334`fkC;]tS-<] M!ϳ L5>A,MwA%wl߂ VRIe_K-檝{uEj-+K13>;)zV5toNy9d+\dxCۂA0viu?*'ۻX(N242{Q}KsHy',YyvI(q3eQAz./|H1d&`L7yّ '^OUvl^=#J6h{!)=>5֔^+8ɻk՜@~^خlح4~{bc$ g3:p#pYzlbmW$yZ s@WvpO&E@oဝ{~ÍΫ)|6m:kR9aY7;hVo/cؖn3g TYFF&Lj,41.an& +Y B5:/Y]|QI=ₙF9Klַw5|NSY(\j:N>2奋g&E:9QX($vrc:P^H@QCҨ8K$qIPd4#}oʼTD~f(C du 膚PDUɩ 8MNI(<:٪'oz3~寇(mX!IVN!Ka$]H(KqBP7>JE`R(4zէ5-@~ :p%RP+uVՊ0p36ݏߪ/JυI.y's eZF ZVgbKvBw,@R6nEC@`ƤqŧB Qŕ UnC4w~~/ 4h#w _iWOZO_Փ"nsz{F>n+i>a| p!ӕ'm2i~ +$2uǍ.ȍ,Q/?IH-'[[V˛]y}p;IhyW_ŗQ+~\ p7kuHup%agE%#4c"[p+Oqo b @d7rߐH#}frW^qXG>c"f<G|r0MlmZA:([՞RX>q(bTvյ/]'6b8?HY9CёGS7sPHenb R_I.3#AO.ԩBsO42\np`֙_e}J#)^ i!JDME_)yn_,\j["΋T4R†7*^Lw9(7ddžj):mmcv HA뾅 uʌg61= ,OxkTw1c(CmtܠCB9Rp4W(m % mqC t,D~<;*uhr^cQ!}W0ҁs0aw^TѢ[1@=y"y`U,0w)đ< fY%ƪ3vd!mŒ,_{.;_LVDnF5 vhw^HRj{zՌ l 91dYOjfuRDiרFsl,wf֜/Yfd +2H! +LHgL%hIRH"m‹i*>5Jig00~zgU頵+MuWa:rZIrO\7d$RK3R{i"$y'Z_fٍ4/$\G *v^&P?ܲJo'4dڬkZ@27ȡ|[t`̣8+8W~q=^8A ui SzTUuOy7rw? wD%©ᅴ<\qQfa0S}hX3`ӛ"sDgD~+cU\ +c`pZy91GUD`[!TZai,[qLv}=q1R^鋍EgD@Gey8mBDx +lYw=uQ报9ҺTOJ + +I@Ht1tEE9ȼx BrF*_|P@I((C,9:n7^9#\?Fvv Y +v[ErUkK N|0'o;A +z,5`~h(R2jT$u*i>^ \{#LGA0mlUOt:;Zww%M뇍iu +*NEplU +4,ge:LLfx +3tTwo=]]6u*T4LaʨQts*`3;)Ɗ1]:" `,^6D/큱XB 4xuS{@wمezosTD<]![VKe*$xlEdŀ3^JK$|K-;:bQDwy.e{-CI( SYri^Z(٢"nw9T2}[A8޵^e4qq*3݄YVԫ^X+{xjt ^)l8~<72V{c{jmkm_`WNR{ݢ 5o3].cNlyS:Gj +[W/3@El5$QAR)Sƀ= #.^'q£w]A|14)pi]CKO?O_M2Ov~++ &;~Ep}eN&_ vS$GgG)P@ +NJLAF 5NvmW_OOz?: +D!nbI27ھ`F$V?E{+iq^~=͗T/),wn_|lu)}#؞E+kڇ5JU5&<۷7%@L>c{&bh1?3ruCv;KؼBgEh=Pp]{{x ﱑaWd/$e\Z?N!m"`})H"ALw۫ExH Ey}yOh1Afod]Z!͎kuuZ/8%aW +a"DlJn}er'rbJiMDVڢ98XXKI_}`\*B1wt0Tٺ ¡mP\(m +!cr6()yo. +ɁƸQ ((JYUHL ~7u ɥRE(ҝdRgC\ mxk+poLnep4E&@!).MXM|*hfJ #U^ +p^ X`[i&ep8*ƎyϏ>"3y4*ɜ'xu&-SrPndg$=CD&`:,z|$]'#Z:ܦb6+\ihE)maMc`{[ +HGw* @$!wϙ$=i [ #xW/˲k8iU +?$"T/*2ɷYBngȈdU +-Q)b-gC8Hfk捷H88Z輸 XL Jh\Ie7|&9GEa֠;+%lWײʅɵVAt|`,q\̒i/8Aj:YUіoOo"DM:͸vjitƉnj+ xK`W30Φ4\%!H'5SbL%_:\YpHG^T/_kxg`*%ՌUk c%{G0iJUiXrL" +mPљ! 8h~YEAx#ۅl`g?`dSh +6FP1t2 +J^*?uN))5q=y談c;ݡ1ZSr?d8KzE @f숹zAG'gZYcrU:D0HYhte1[ d *a6CRxfˋT-\O`IqeK6 + T^H&0wAcPuO|8pܠPM/hOy5Mb(-=AwSH8J}cfWZ1(0ppqyDܦ%i-6LT1vZ89C8In ;"$ +wF7{B4ouYRkQvX[;,k;| )p0o;!Eg 墟 +a+UQ(Q}R'|nUVֽ==*a-sl!\ +^4WEYY.nR +W@ +{=_E⮊euU Ǭ";=t8hhO7\)~"j0/TB5OpM*,|s?RQ.!#Bj$ {ѓ(^,Nct[b},}!(_8x71Sm5vzAbytU6<nXzٵgU^/x/CNVk/E Y,j E\cK<(Fdvh5MhE 3A$# +{`1ad0caK!؞ +T~߃ PGҗr\P ;$Uq}Xtl`4AU `c![-ܩGZhUd.pܮ1b"[+veRE}bH *L-Mtz۽=*dOurꁷG,s1}&^E lJ5V["ɻVFYi|ډ8*S4$7syV36 +A;_IX ]᪤s_#*V)a[csu.< Z(rcT#+#?RY u]$;ńiXo]wۙ +5>@Nݘ"$>ż} 3R9iqjyS% 'Fĸ?K +ʊͶ/^Lx/R="Ԙ/.gx\T<\0#*R +.(O׬:& ImlWM`.Û+r[4 BӘ" +CE.AW^<ø=1{py1bpgcEm /I166nb ٔ{V(yJy tásC6q=EȭZUj8ыz1 U7`]҉o< 11J7=ҁ򆿿( +Yl{ l.7nz6Z):圥5H]ET[ZVp3He(N6ŋdʢp_) :2LLN<#zh>#OňrŐ]L'F23!1{Rȗ&kI%kZ!>l֓Ӂ%'كr@vh>܄!nQKFѣEXEkċ!i<Ц%& Y(E-H=:_32tːi +WTBΚD3/M3)G25_{8*TeP[TxZJπw}ig}Ţ@OpK:1K>ʕLYY^u"IQyڧ2+E(Nwí?1$X5p8{hSQu Q⹱Tn]:ME84'$5`=5G-|%g'X5\zdgPPЀFSDҒ Lp,5Ffd %¸?Jc&t:YD+XcBIOP2>Y2,6oN|0Sr89KOڀ^Mg0}V0#AB NU>I^%Vn7Kev;<.KB[?"=So%|7vet *a A]dyZTISiX)Ə 5b$t H{ ȣh]_MVl_ '9'gtПB'7kKt`ӣApf?$uQh!>&khUfGEWͺQT,PoV1$bqFs7v 4rCwФ,Rx &g-{f$l +hUwdȾ/+@UpbTeGrĖ~HV&{ʧyHVّ$ȁ'iT>1Yb>X+ݑ5nshΰYWmT̈́nd?˖_H*ȍr%Ap#kᴴHzͧbWJDݐ_Bv;3V87N^11FJ5y|f2kmY%DI "(ow*mhǐBofeLO6sYjՏ˷wBg-mT7Y"E¶R[VUk?4N2l4>U \mTw̓:زS,A7ՍC \/=,]AFL漙bެ4oe<O-lC2lNR}x4~tA TT%De+솶t ;kVS!Gt"\BXH5m x9+-WӰNĉa[T~wGP`l"n_곆ܪcRSt +?+6':'>H:aߓ?ҳ5uǛ# 0h'De\Wi1 3VqS3K'= jJX8S =R*馼G 3c +؊A4~y`lj]lsP~ U dQiVV`EMw 5BU17 fP?}0׫u7KT{ 6Til†C I{ +W_s7lXe]a;ֲ傡ǘX~8V$nR@70o8<G1Lbhu_D|f `-g@~SWxIHy0&IV<,⁝(VǏFg'lYw 0!(Z!FYb`@m0ҠʨBV$Sc- $\F'1X7i"KK`I,TL~g6e'q"8IZ0+`,GG9T|=ͨM_w("pye +27˖4t 9odH)OJ,ik2Eqa<1*('ldlMAu*MPn]IXR b]뮵L(NrS$5=,RG$" +rj"!eJd]%wAሟ#E~ J3 hr~+-H8ã:*hɓֆxgi-B#'YTwjn.M@˗ '^%f'҇Y8qW^.C_POI<^EɠMv[!0Ӓb0_ڲU(&<|*۪(1̭͓*$%.=HqBV<%vsVzPBb S@R9EU {6~eh_NSyQU.aW*I͎d;5Nxb2i/gQb}D"Cib +[{s080 RӞy0+D_N͠NpPP\Nl<з'HnPt_ +?G`g$tuD=P?Rlb6Sg[z lpaů4LVZX +H:"8Y +~cZN)ɴY4G&E+ +j"`!]G!Dmk.uaUA#N9{I٦kҐc돴 fDE tǎMHZ5jq>p#/Jk>rN/v("$$7+CVH'f"/q"!^pt,٫9HCkȳ rz.u{gK c'}X# + +W!Z*¡\"Y*ͽ3r) '6G~rRO$CՃ?G![bǃOOk0ɣb{..Z@F@ ݙgBmmlF7n;CPء&n8"L4[/XGgIEQ+*wigQyq8ENS1!! N$<0MNzVZƏqn%/ LݺsUVQP +*:h 7 VOC>/g +otb}.&G8U0__s{Pd:N+El[뺮뺮먊(((8ZE``5*:s9;;Hqjor%9?Y8Zb)uL30a .A,JZ.3~vW cczQ;zxNJ y335͒?+ c%8R%)-:+OdF0LV2T`9dp3Vb*x'[ՙ`lLBEP~ح;=s3R\4NZ(l^i&4m3U$I _(.{ƣ)D%)V`(;FʼnƖ.gjOrhC(~u&avX{R3VnԘX"CVFCբJL;@< Eo  (t8lJ;SK'1ℕ%VdDs=򠂅%'fOg\7Z/eb ̶^YL6E(e{qɠGUTNGo"0CkI(A/;Brt?*پbXY>R3 4`^lnXR,.;=P&sTN2R\;^f)*ض7^g ϙ1EJ-Nrɵ0A|6v-<+(t&-.&*,.=͜ixq%Ɗ E+-N,w6fUin| %-GSx'lq'$%vbqb;(AsISX~0QؙZ^IJLOQ2T]9˫gzy-E*N4Zj33ʉFɆʉ +%p4&IdOTJMqkfGqIOPi%@i(QfQ=n%fbFW` ]&PpޑDGh,I]uiWML$ǸݾఔJih5+GQ-z+._fz.ɫ$wA}cU3+dÛzU*%`sSmp+t9`d'nJFa'ydI!'TO*vŘT ǤuRX + u:E~PrJggyڢ&/5- \FKK¢cdxĪ=عbfJVI7==Ռ-hMef;[PL7[tXӘh/Skn| +4",ܐ??,!dT$>~Eu'pXS7S\ǹlps5]Z϶W 1I8^l*gOLlJui3KP۞K]of3,VDI4x&Y]S{yi{ST2qjpJ+:1u% +&v֧wEvf}g[36S _i'(gZs۪ɕ,\RWkҢMPKU(F=U#|Xux-ӗ]U +#'7;]-155^EnUrk9C$L?iyBWA/+6}~f0WGY|FS-gGy9Ÿ$I ]ؒGY}5 Un ZKCM~%VdS 1v%(X2["%f'Gޑ ULM n53-^J'~xͦ GSP0j^`*V3rULRiٶ}e$ xgMJi./_25ڌ j.,ozڞhϮfrway[b%DMU tTF00<,zIJ KpjM ''} I=~vtӓT}OLqK&4BPH=&N +:W 摆=EW׿uCS]('=9)Aͫtu"_k) Պ}A6BLbUQ, +yA&SzOlTRoDP?+cEd>rh4WW Id?C6TA&jG=}9zGX,{G"1)&;(R:=v$qaastjC鬪3d$RRG|:ZhŎs yWuĊ>R\9Sb0Ző$ia G1*w䧝U lgG4(B 8zܞz)wz$yG%sdp~OOE6,pκd3&χiˢv/|cMWY_=róJ}r?,fG?vbC/%%v|գJohܮԐNrr3C"U0(Z+Z?=4)Hhؐ>e/;%I\tj8rЀpC i%!r< |$MOТ8zJǔr":YWIy҂>ZvX5TbGnrKlOPnr~ԃģ `S?Gw1V gz[賣b% = +է>mPyD~~Z3j?[b^qCώ_{bKO b "g5,*y#KSL5^uԫ"e$紆NN +rnؐMrV0Y6T?|6TRG)G#!;6ٱGn~3#4@d8A *ZUg<OыFtAOqPȇ. '8G"9? 'JFcs!V|VP'?c䑀+YA&ls yAxHrFtԆB cG^IN]T|ԟG.GEVE!! jd7GHD9KNc$2~H`R7G'!?贎srf8?$TyͻzeEK&Y@x5x-UC*~J'GNKvt>0EǤwUp$@j2I,EvBCTI? $I;`G&fWG5)dņv|]^ҫ]6!Z%&6?zФ'??}EK&OPQ C;( WD$⧣_sC"D-bK +GhCܐGM +1#ZRv44#AϪ\EDEءwA׬# +KOJ E;2 \29S:$MNigCcOEL`) `H~rzAmXQMN+XVl%'^V o;jxQQ|EHKu0)$C\yÊI^q.WwHd}ðK28rEĉE!ǃIbZ!dB&!G/;FRRd?g #cFn"QNum ;$)EdpE7yC&sD d"C`A` $3 Tn(?l(;%W?7~apxB='EV;M,@9$R c"x$N=rVphZA=M!'&;6\6]z$OOKQX/ǧ5QLrzEQN0wEz/&"6Q#vD]R 2QaovVWRbSxW\gCUĊI*+;bA*^1iY~.Yt;*zG*'eEnE٬KΫ^K)>bǟ~z#mv|Kb\3 .aZA puOGyMwMCSׂB.vxzɒ?d hC& #'9A9! +P7$2Ffʟ{! )z<@hsTCi^;iD)vH.zI !c"p?z(|>dSVQ.|bܦ(/CءI"<ݧL̡h#$ܑ#M]#IOD`dydtzsbI)K9Bf'V9(LSbx+"thfjh?=:jI!8B~zb4YMޖүO ޥX.eSCQءCDI$1)&=9 +5q+t,?`D-&GXŞ4翧V4&𳳒Ójk&YyzMvM*D`.SElU}u&u4fOC)U=tkE"#)~ +$T$IP? IH!QP ]m8$ +PvH7! 2"xK EF1)$@^Hx4BKAͤI~翂8)r?҇ Ȉy|_A\/V*8IL +993'Ղ硢K\/V/E%1Ap#T fҴjP`Cv)2?@R7̘~I㧣:KOR F ғxdOGu Qrv5KI DL +[PwEdQOpR.&dQffg]~5xٯWC/.;7LɸϪm&τMXP bZ1D"R_ +GcS?#KȡXe;=~d~rt_M+/?7ώUrDFJ,^vl1$&} B_CE. ȣ7AAd89$Z]O$ XN~\yL+ o[+?C|ܥԒf#&g'A-l9';,8Jr#(̀zFiDX1&<vIRZA)D VKN +KfKN>]xbE8lEXL_,&Ԏ K~K̊G?f_E\5Ҽ]rrCYQ+pmJ' FƷ`QG=u9W0ܔRr[N +O _gy;@ny'|%L(rԼbWr_J)`O]j?:J!+ӐJ.UpjGlG hDǒG^&9*K6Wb{ 26T?A6eLUȴ~PǤ|kC1fp&=H}";>b*WőU~Lb$= +J#}lߚȢ4 OoJESBV-!>_ȑՎRןf7;:Rţ0d7=/mN[z͢ m MOAOS̊Ej`ɩ~xUM^'IoS1jbI=5e~\o~Lw]vu;*G^Pr@I鷾͕\}6~ +rG$~W²u|zI6Yȣ Vɦü'{KV Ռ~VԂG=^k.6KEO}a!kR7""(gD)Lk >I`p6A+Cդr$7IuԊR*ѦpGP$/Gd٣wBȐ$G oȄڰGE,Ȅ+A vҎ-v|uĂi(?A\kSW=~VujZ%{J_YRC9 iQd?T膣/<$hE/6֏`0?A5yI!s(jGrE(6דQGrV/)/dba$|:W_ExbA&j GC5ի,&T2?-{noD a a/CG!:2 +Sث 92`0eX<":O +&]٧6yzI8I?%T!~>֏Q$IpC"P~vTI $~KP˩`ڏndϊL, t)#KI 2 }QFOA-dOWT/?;+.'&OEd@;#t3#'>0ϐ8#Ci^ZmXъܐ:pVg,$m`i I2bG"iDŽ)YV)%备It$Ɏ6O+EeOCٙc $X(ŊRՆZ35+8PXA7PNvXP 9MOIx4t+S8$0a`hsf|q-n Kzzx4cEfyݻ]hGud$iեΗydߝIzd`SڮL OM(;<-CKv1l+K }${&;uH" ܐ7=I}$3 M~N.㞒ޑ;P=#T !pVfϏJi(Cvӳr b6dZGxali69 +rңzJ̤|׫ NzZ1YXc.;|~jQA,9^*_XM䰤~ BRxK}%٧DQWC8tѤr^5+4{E%YuӯU[E,ȉV_v`7+*"'??'y *f&W0xRj-/W-if-*sjjI0]$PsU:C!(dG?=b /m+?kjFutzœ 퇟e3􀝠s@G@<:BQ &PI,KQTy=H x' GS4AS)hD> 4ː("x":iiI2CMc$ʲRDx(n+96Rx8,K#L,KxxWPyhA8Cpˏ&x@$AX"4ːv#ƣA=HL!~%yX#9F2,A<JqA,KPJQ'H$ң$yJ7Yfޯ*AM4KRD%9"$ÓtQp GQ8#i!(iyJQGQ$1)œO#E$$")$$$ +pA $!IJp>DcY(CQ$b!xPŰ `(C E`X I_if)(BQ)&x)"ɑ$IGtyI2sh IBR(0 OthIb9r,Ia("#x!R%)hfI"4 A)2ODhF$2M4KPdy&8Na7N'ߖ'U&ߒ(zY6S|؉Ѳc鰮dU/9J2a7EGfU2DȋB[hu%+dͧ}tbb;ɧfKȡv%y,\8"cgQ0~U4"09%HMzUD? $ 㡓ڭ읉ޡl 1)1Tv+J^5Zj2V Xh(Jd9l3/]5!^~^Hܧ;Knzj-~#&}d՛?tV/[QQ 61TMTǣhv(Z(+6ܪ]tGC_k##08RvF7^Y9+]vLVZ@P1 +K0NT3ҋXbbd&A*OK"a?X|e(G16Ic g_Eva+'7A'~!](T;$VTrpX^j`ygw08bX +]wu$8zx*f wUEհ=9hIbK 27.46Na*JOɨwqS ڣg*%(;&N4moW^{͗s+lJdC16K8սXòGI+t%{UIXV\fWW6AvyV\6`͔'701S–!eb*WsHѭhܪQ؝PtJ>Y9:f7I$YŢ +nyݰ\C%N 2䡦$ -MH_ ,ب*0 a:PD ,Rdr=MsbV+:mq*%*MbE?rhN0*:%392ŨtXdjeXy`AÃo/8^f[UcMrOQJKQI2$O??(.CvIs~e+ETx$Af mYJ%|*Zɥ>yTğ"LhC!3DYdB%&Uz5GE +A_CC&WR Nu\&F׻a.KT.Kwzu&-쌘EϏyS\ާX4Y]$QA<`jĠ&&gG.bE(1;Ud=[ Vv +jzp32N\*١jL\La6Cq~!~j5GG1I**ZEU\eCr¦Bar4-5*2*1K5l@dUefBecޮZ%N6o͙r|Tg]T*9yېwGs0.'3G9N<@iGKp8i~WER;jLf39(KFC~S2+)(WE%3%+vҊebh I\SdzI"҂B49٧V +"Wj{ +B84 񳋍e$rl:ˊIɛ윸BC$qGw4#s !%>"Ƌ)djmf²]X5šo @7IXKYfΚWTLm?aG]`k_m=5#"Sh R3= +ݔ*l h8q2+a)x~_vC&?駄XZw64tz<져bw+#-i `SApPG oE-4|R@B8Dfºw'\*>Se23}EOqkl( Q3eaIJpXkl[ncEƂK 3^=բ +W +ww@cc4WAyKpe#[]Q)CNzz4 gܿgrr*oPx%(RI+f6U;+醝Sh[nE%v<"EH<|ud c{|O;QqzbpGeP$#l:( xQ&6#eliV[qEU`^ӏ,"ͮ$SюYjXq~cp$1.r(|%Tjf8b*_I ?1ɢ9+*J*ʊ&$<:0Vxk["%efwCvwDCр엣Tt/E'h!9-Nb(v<gKLs Wg`z ^WG %g;ޚinR?JXt0ftwkU  *LR^i.U.it(ؾPcg:A)y|X`0L'(jGĵ~CdeC0ișnaK/w[^w'-{dzYLN5k$9{}: +N^ +KƊ E5/JT3=i/=8)3}Q +yCjѪMwe8[]" U0es~F[~!%P9b>Iӻ NKtvΥj?e†()7f,YĒ|dEqH2觖Y.m8e"" ֌MO$ˁEE1NZر^L*/Uu'h ]ffh6M7G\j( pnG=&-.lt,Ph}l)ZomkҪ{zVze*S^J>[Jж'lLoE,0]EXDŢ·edJ]D̐1ӓBKS*.XPvNO~evWIHDGC,ɑO$-z.ޕ\:U +e[o"谹 +FDU[\nlZh aŋm"SXݒOؽ\䔨^=Y^]wc3U+Evzn ㅆ&3YZ3 ?L?q ]v(TV I_YXS G&qUI-v\d?4S+٪N1vę孊:$9?F(VIMUΪ[(fgM+oc[4Ql_}( ʎmS;5MfTIWV]tUü?>I=_᫿28zF! `G!|,^w5#YXbE^Z҇,uNXv,g_7ˎHLrf`oeʪ[5zzǚ.W6nwťn#$ߊ`C+:çXTi>c:ΗizۢO}^p0,%SJ^Qݷ\FcvjrY$)mJFUVq U UԊJV_a{/J\8TVFvAQd\IR[T5L0̋h\j9A en13|{?v,{EvUgr}+$[#A&C4 :9#NQ^XH߂sH$v)f;c#Q^`>Gs,u?A?cqZfE5RM_(@E#&YS09mDILbb[ѕ9(iJ0FTDg=*;(CɡᕳJΚӔ\40$=IlgOh_W䨢SBU[=q=+Kh8"AFneOvvTG#'+oqX!S[}j6\ZI>yY<jM%&AiI#,{fX޶l&=dgBTtʯ$Oty;ѷvI]:M)NaYZt_]K7udlGY͏[6|JjC O;5.ۤɩOСB Vz_dީEyͼj$.X*fz\=4h*>y;  ߊ!ML~3 bRqdE#%#H9]T3+8֥^ .sIި_G3LZ PqⰤC · +W^_5W$(L`7bz*tk.K,6=y +͏j('9}E$2ᮇWRߊ +m=z\ϟ%ZGA cH4U\TGG1H"ٟeDWsHBߒX^NjQC!qHU\~nǨ4ťp}V,՚Q=#{&u;$D~κaW] k-o]"bȔhqBF S\q_L ,:IoAxTElSgT~JNOȚO'+ +qE5z?۞j]ۛt6f2k%9WbewItdū,$!Umbz{>W< +[:SJJ]u +BV3J%%F3%q.ؽBߎ6Vt>? (>OJ4R`*v+|-xYV G3ģx6Ee$E)r2r@::zholWX[ɫRKC8RHTQ* +9˪/a Ab uNz~DNG Yh?K_ɥ-#3w+ʡ0[Eaj%ڐO?6AȐ\U&8]I3Db: \V{(T)`i*oi~v)6}K+v^$JYI#YuOR*I\WhZ?eN? +׽L-~l~#e}GC1ϊ3-9)*:=&LOBvLSl! zg8OSC~wK[Zg))^v%'|WI-i!)ݦ7ҺsVR,pFI9CN~*ZDJGiwBH!$L2"'!}L}P:}DJ[x{%7Y`|pF3 $#dÿiU1өN5QPt㎂L헗dhupL'N܏3BIb#Sojcگ8>csDg )xv"S^WTllvOCqC!qV2Za%An)b1MN}jH"C`gEeX~NAk,G_L4|MX!fo&Ͻ*z?gBCxJwrbc\~[z{Ve 5kb~VQ2˯,' XG) ڝlݮxp` o6Ŏ ?"f$qZo׽pݛTrcd6Ϧf7~S*h7Vt"$*/|H~[PO\WZ*S=>Qw{S.^Kż=DhI(KQhütۦ9N;m{+XQ}-S1"&C("Zhw|YP1(2'vmU2>CC~[򴻻TAD 6><@ V``vtdSX+Xn($aZ"4a F@((a6Pv #A2h 2 "6PFkΊYKfQj ll K@B*H +F#4A DA JЃ@ Fh4XȤXӞc#4B\&@|p+< `@)4! BXBx#@ T` 3R82sr+q+)*37U0fl@ Hx+L +VB#8p <@>` 4  5\h` &UV,^f7nA 8 '(`'B px !@ +FP ! 5,l^Xؼhi̟O^| "x (4 [n +L! +E x@1$؀6P0@:jȰh, +Zfti)T$̠+A`&` @' N 0 pPC/Pr2VUw)g @q# ^(4 ZȂ`">p!*8(pa1PNu,z(} GLn6$ t ":\ԘRMjߎ&WᬌoUtI{`\K6a2rk h`)< +R`Dp^`c̋ 0dNN`[JWr>y)e9OVrءB`H30 ~! tl"@htr@+6Wmo+g3$esј9a1t0? \ + 4hNhRh'WI{'rTJLiR tZaԀ|!?A LOax++.b5(v }wIgs4p8A E(v +qÌ)NVdZl0lLjX9ᢻL)LU35`I6X `P`h"vp1!f @z ;px&d{nV6.UfVzax-,blp +<0Ap"r0AB q@F!$drT]**wDmp(I^: +.ENt$~fWf%+֝Y>iexOXLg(xjȕ%9u*I9bGKnݩǬ5VZ(t)\4u+ٵ4wj(XbUN9X@@*8ETsXy̟9WRLrZ{;erE + ̥$bZCS;[.= -峢eݵ- +^*ѭL>XQ+qiE!5J*}(:?㕻3$#MP65֫,$Z.3R^f+_ϐ+_cxjG7]lvrZ\)OhG('* ŕv@jp}`]^< +f棨jξ IKaݩ Xtڰ6=0=}(v d)>Qպ?.)q[_9%yJHicx;ݙ-gG]h([6;a;`{K/w}ds_CmQP٥<a-zRI w1!ɍ ֫4(*p'=fa+)8DACraRN5R1Zf5\PKOq]\_gRp)zFDz)y|;wGJhT,}E +gk3/E~&YLդ({ػalOo'M`x*?7;Հ%k? WS.X! yhJo?wvbq;5K1x9 JeN"]R9X0i8؝.K )PͯWbvrw $bSPDƊUܺ՟mҲ]\+.߆::aֿ9^n/J\11ioYd| Gqc,J-v(ͺOT,b{a[%6ah%&;Պ08C#LzJ.ֻm3N`5ɢwy'U1UPVkTZު Xd{z.PL8 %FKmkwTCYTQ&U|Y_SLB_ P?̨IbDpsb•ڒKn?ͪɒYP;dr6\p056UyDI,dP^K⚩'UL{NTĢXMʴWZjNُUQ/iE'Z E5ݝ)%!+"HfPz6ՊOgI<:qwԠ) *ol,go@SrtCM/ ߙb)ߒx| ۲(x4Xj,EƳ9ޝɡfٔgd=04!XxUL`(n 4Ub f&u}y#Qrz fLdf|Mnes}z+Qk٘[Ê\*/dO&NZ޿Jlޑg`&):?eJlf,D3yWOpњ bXqn%&h-`j 2"CY͸~d5pgb]r?[~L +Kqb. +"3J(p&U#SX+L+OSN\:S}CLquN9fz*E/%STL um n&=sNgE,'h|H"Ċhn5lvحV +n2D4;Psb--`p6,NPX)ym걖7>bRXyBP"T5;[WB+U#ŵ"_dz)wIXtI. +7qZ‚glXbn?ϥrcnh^bj6Yv¢5~S[8YWČ +dVlg]d7jb'gBay*DLHMvs +3^l,5o]vV%¸U]z^? +]w< I~P(LbAK0bVVhNZ'/)'("ǍZ +$eff1ӛ*JℭZ W,WYl+xGFa` cn< +ڡl CoHш݈吹ɵHJ/&]CpҺMX5UP%N.&© P#E5O]8fp1\h%+{%V߰ԊTM? go5tD뀁tEx +6Q-{UuYÃN'r(O^%ySL03c(.OC_lv؞ր")zK KdBwJaٰ۬ 2$BW ZB7fpI V3(6<Y̍Oq/%"nESChqab  \dI`4 +=W5S֋vrdVbelRd-Tf.N^59ogVtK3Tp/Maov\\`9ElػmGjl0z]z${`8, +/E2LPld%@ԠNVs \I>eⰆ/ E/$hS*` VC˫bz]ə~@nQBKyE@עDeK|ڰEKZpXqÉͭO3MjH啷v矸'*9e2BS08`ljrbs|pCܯMX/UV|`Iǂw¡M08 FU.cKX5+OCA+fPFnrtwU\z) "cQXjv){ֳ3dLtpv^eLEwq+:wm*4^8^.p + Z8Q\t&xʼn +p 1XtZddzdI6t`-.NprCOh0 M& 49ä&x,Vx)1=L2TI,w,|%5RT6TX-JP'n׭;'Lemz2S~b |&Y)[^gUTSɭXb: +\EBWfMI - Ư0%%Bcf\ + U.(C5  %%+SYr|X!ߙ!jPqZ)x.3_Ҳmz+J uНvpɼfZWڥf&UN*l}a8P-I^զFaJ;pϓ,zvwc͘M 16jt=H:j[$fr%PYӡҒaҙ$၅ +m::YL3Z`@H)FJLZoQVsLj֝ژK&iF)U*75*,0V*.aL(;Z`3|5h^dDp1zTUX]D,ȤuPOTY8"qOǂkG1/g錓 /T^R{jC7:¿"YR3Jj~1EƷl&S"̴ݓSzLԥ%׽QH% +^W?\(8#wCdPA!:P + +X? !I [ZzKiL1FijAn~l:!(dMMq*l: 'Ovf˖Nr +iJ~ ݊BGFq< RYa:lFq{ݝq_ADb^чrn+.߂41={Z,v: JNbu}m~|eA7Y1.?s[J&Q&OT4ƫ1Loyu"ŊZ ,hM7y&wezv{70bWQrJYŶR;6_Nc}R Һo; Q$P Jy,9V*|(ΏQi! k겒_rGqh"E/eR@M0)0\Ʒ:-R$r泮RӁſ3> +H$i/?::SUQu\z}Sʢ䄄wEQYEWO8b'WG(f!;#Ώ nSMG9տ[hzokC&Jǝ/~;u%J6[yjA+.I?Q\zT9íMɦͯctC%-~[4 ܦ.lxGK6WYZ#㱆"?nM/ꯤsv}d-?W]Z8 /15)?ȑAɭx`GG\18&ɇ + dO37\.1lD6`CF ɮ_b&)1 TLr/%F ͚ef.^E:R4|WFJRT)f"ŜȨXxb:VumIΈ3nΔ~׷ˏS=Ú_:^*eAANyh~]ttfjK⼡g>cs\i|QTs\**|0&;*|H;vt7^urj[iu.zme-"4/Lp),R^0sC\tzbʪ Y̛# +z83[t_R4xBܧh-&ˏo41WSãn.&-)XZl^iIVSh5[t(6ROe_O5窺oXVj_yov fY{=XaaCeZ_&In= Wff+-߶͚'] wZ&.:7bt, +`=`|0AkFobMZz\+39BfUmW%{P^ˊ] 7p01XG`jeܕ˭vKіe{wµP\L`P^p `>hnRT\XJjoRe-;#`gZ ̅ +"bz@ Tp2 `Z.e*w}&I^QkyXK`Qx A @ (H JB@ DPSղt{i>)ԯG^[B' b|`'p 2%8Cb>< B:6ͥV ek,zf:A!/ PP&A dZ28Z-0X+-dV V,oOZvq6\.EO.:@HX h`C"?`^[qENkYwW}R)1?~a528B4(Ԡب +V4@B+3̫ڡ*QnUs~a7PJ%pp$Xapp t #x(L,0`OVP[IWKWM 0`9  ApXAbt@:@A@"p pt0&p,pxa,v ecR&8R#4p @l0 h +` ?3IU Y2mE 0," f @D ` zp/ ` PA |PvXCC053XX'A*+Az +E 5t@. 2FPE`c'w wn:yMUŎ =p-B n8 |A lL7&0p/{:gzW)UA4"0  %3#v=[#%H< gzBOԟV.TAI @!A _ +vFpBd8AA M]UӴĻr%=. 8f`Ch Ā!`a9.0"R`@LeO9byK "r<0 &(=T*ATpB*.t/t0Iʱ8jyhP6,Vb̜lXT !\PEcg=I-5W"4[4@0nP"@VA1z+nTf^)1,o-Leԃa7>255e\%,hС@< `%PPtJ8"*9~maj[fcU&)>7=uWj1_߿]iY *?gN+-u9ac ܮkt5݋ D'_Ŷ4uUH +Z]K벊,:sRL/*I^*r0dPxvV%ן禍0v>tힿ͖T^1h.\~O&E~W'^/Ὁ/7iG!l Y*^-(LRX1Ej&UI?ئQ,Y9m׸ch*Vi~3r@:{ ?BLLЀ8,z% h^[d4Ǵ9s8S.C}=SR4**;__3 &9H҇c[&MRL~';Nհ<~裄$#":h5eW_Tnh[\)d۬^IQ6&U5;VIR?8陥RZ%vu41ъIv FwvW?&OT3-a..%V, +o_<$cQI O~[/*fEC;VAkd;k.*,o|coNCNS5K2a^e2$!{%Xb ^V%rz>oc(X/Z=%y^vzyF# 2'<a|-]ӓ4 +j_Βݓ_˜5Yym$긆HFJI_~(}nWrܬ*<%,)K~fc|}"m~7bɖT{ ,uJOf.nu:j^Z/ s<"?#h%PTKtu Qyߩz-k<]b} ѭdt^NQZ_9}v]QWy&~W Zגk~,!Vr4 .el3'ECYOf>ikke8~>G/ wO+$ya(>}c&+XzSriinaRf osfMX%W[ .Yv1VOlȟ")(]ot4%ӵ\#VL>G9JlLn>\|+6V^SWL^0_6]K[6kM㴢642d1chWl4'4;m%U-oJ1kfQqZ\Z;-hyJ:BwxAd]Y0[F +˥-*4kȳ!Z[+[iq_lJYYڻ%Sc6$)> Bp@2~vb;TՂpq$O @`yCTe(Ɓ /pP7Xb:\`+t<E+Xr,9٪[[kh?̋(~HdE6.IFm)vO\K폱XRSD vS(.V؈١tMIJf|Q__>]yk b:-*2B]jɝȥd<{dv$sj=ѨIFg{=%h6UR-) + EY7L5JÑC pXPYl&`Ly44A 00I U]lb8;$jp+ @ A4S#%CzKURW +(Éެƃ,Vvlr~gG?:N48lPeȞ1 +,kNdA̘!4Wt+>Ȉ9dP:eVah E +fJnjFq8Q7QsiW^} rp1sd_SuSRsPQR^pٔj(AN'<ǺUn)K3~FnU}Y5ɦl씤[اH52KXyLwQ"R{{L& wCȡtWe)Z I bؽee%y,Ph1n3~$Hq%ydTjC[H0aPyxw-D.%؝ZR46ӬWG&uڮQ0pG <9$Ў đC; +iC$Gâ$#OFC+KO.;-iVťm_ebx +"3xVYvP~~"Ԋ+6`n)( 4:_&S:+-p[n"[ѸfѤ@y` +wnnIȤ@9 ټ@.ՕԓQ,4<h)t!C 'Pce&w:Q2Vdb8^ĺS= Z׃ 7S,'Rȁ @6` *Qc rp1^)*62<Bmx,}z+k)[uڴ E8T-|=qTCݩ:J łcN:N^ˇmR`B8*z6>6fIHXzM35UUVN3Ԋkgi-0%V` +J2䵿uƷ+"ġgU9K ġ;::ŦjNm2r<6鬍os%dn6FsK,[u+g{eV.;K2.e`g?H"w&Z9`ph%xm&ǰ6jLr)6Y).Fl\fz[dZR:Rg̷CQ.LAw`NoTy%PÉ,IfդIn'VmTp*;:fd.Y7~/:?Jm/T6`C +Pdd9ͭD+@=`@ .d "pfبɀTdQEˉ .泴؞c=PrZEV:dn<`f'SU`5L\}n۔C)xRč+'PaF 4&.Pb244E22A!.رVGqXQy&:Bq/JV694TwIwU%8F*qn0PZ2\l(L^/)mpۏ8C4CS";39"ae/-1AJ$*`%T_ޖrݺǪEq?ˏ,Geh} )NXXq";Ki ȑb 4ɆL:25b0 )Jf(l&G^gG?x; + Z`4TZ4X^B *0LX*r*)0hZj䄈тlr)Q[<~^:|]s"ncKߖP6]te5|mGb8X9 be66Åh +2nWe)*{5t!GJ!h6(;X##N/יJ*>ȊNel4\xُb2afSW*0۔KRv^uSy @ix1*"^3H2'x-FMWknl„ b4(:Eͧ%3GT{^.|IP;AI$]ꎱpHRlX6'1ZåITQV +^+JR?d 5a*{%MV9mWZ E >.sش^<cpac_j3^L`5fn d()v*}nuy,i7WMR֗^ZnЮcLh -WS⛿`YccK@!  U$XXX5g2t"+qg?ȠNV*NVH.$t`-Xhp@`w#6v;ݻ[C%eª_kPn~X1Y64b  +O0dĆsEH<@vJTrV׻:bpA-\RX.};,P#/]fL1oQqYbC$¢B嶯mZ uhhώ1)E\Pp=XR '1C ەqS;lٍV;YaRk{}~b\zY ,RЃ4ٝpHaąC:C+:GHv"OnrR:oGR+kE0mgsׯKq"E}\ 0a)Lhp +O]p)./J  'x5\f}Ko2Ŀ$Oj_C|(.SWSםΦwØ^ša`8 +ˮ$1" \ )SZɊV0zAbal=|wUs ӑI>Sszz<‘7BjazԬW`EFƳ:b^;Ndv&RP`6dL0 \84p@.3X^-D\%9E=ޏzL|H ;-]GDqdMw[V8Nw@QA`κYlL1M5&'ph?MsPx)e0 WDˣ_I--=EErUIXЇL +əjJ_I,2 +l͎oA.<%}Pq=ܚIz$6䱂Z|K-w8!Ʀ":kbgXƎ/Ce:ˎF\ +1$q\ˇ*vtA] ~UJ(x+*;̰LT}BBK),4Wf{,<憢bn[&heM5j^ }bӷ" lͤGCegSTzxUSℕÊP d̔^p MVM.HG]Џ Dj<ÒWN-JVRK+BȘi.˫!E%=0Ac[aҩh+5۩fsUUV +FYɶR;44,P@8RxgC_ŝ)?Y.-a!( 4V/b|x(7t5,Prج@QS["E +KxZPDU3>Xr+8.NvPl0K;jJxhr„;;:2"-iM;=$|Vd OS2I$%) }|g,OYviW$ݬAU#e$;ŭz,"y p< 04S OU짏S$L7ҷK s蹀H2#%˿ Z"L\&_#7 Ԑ ʼn.8+dRl/\ʆӬ*zE KIJOV,\0_.YEFpq&+0miFm=lRK)ɆYdTZ-LV9`nJ?w:÷!,$$D r$Fjr$U):DS+~ު(ZC,,؆,J`ty},h2d,tMW(zh2[RRMdOJ(`wcj}5gQt:Ԣ'%(TlFKMER;RyCЀ8 -_a7{~JWSR?VLr& +mڢDu"ktX'lL^6s)2vK1X 6/2; h2p(bzh@q=Y@J*8Q Jl(Gә bz1ަ(,!,\BVsL 棸[##GiH# _E }lս[Rcu!Pß~e^fUIL-gNnʧ t*DY4KXk`ߖ-[ך3f!  \䐢k&re$(ǐLP4+<Q2g"j;Mfէ8p5)EIZjWPz~WE +'?NsgG?kr, +ϻTmEL*y{I$DŽĠHg! ǟ ʼn(\I+|)$ Zㄮ7l|X#6#Cu* +1=lʪV`~zP x9͕,fZ$GG='y_G%)7_٦$uSݵuy,50aJ-==qjB{Nb;wm\ +åvǏ;9!#СTOz~ej%/)[ vb+!S؝bjJ>gt t~lk$!!qV$"*h5VyU2E~Bdol~ ]z>|ϻ Xg% +DEKKq{ZC`C3-\p)ɑ S-iG<Q1zS=YR =3 XTv gmS:p`ѹWZu>ˢ .i 9BnY?u3s[N5pp$5dֻ|ZV^TC ̘ +/o (hid}`0 M9$ަԒPNʮ)l&Sh)! )u@#EJ گfk @4C+MEJLW5̈́ǡgCG1EjXVlOa\ 3Ez3? +VHpJ02D%vgX_ d'ڝHdBC" +y]͢uiÐ)YS>I;;ٞ Fb-Ѓ!'R)R^rӼҥ*8:jh;'IA1 v׏Tg^[wW;T^yJr(" Tp9񶿖4Ie +&d#1tpvTqvІC + OI쿁ec6Yn+(RC#+f䎾$D汣A-~VJX; +kF nŕy`C [}(x!jAIoE՝ȥd %zy"3#2%ZRozVK).E 1LqNgOX lD 3Zl!hĜء`9{ A2B"QRD DrWL'u+(X8V{5 !K6[CT+2# jVtR=w4a\9.U.I?=:*1ѳ[Gz1_pגU9y?68M8T[CYiA%Tԧ"D.Itj#aVT +*U|ҢnhRVAmzP]Nj&|==! cZu < _K`6y%V&3Vqrz)6EpNe¿-VG-jI:ܼlUȴ~Ng;^ablvs׍^/1 d\7$?IlS+nN7w*CŖv^=_xCAؔ`jhťn4ɋ ~ jA +hˋϕ|S GTGE/v4t]'HhA&rMٝ&OЫ>w%g!WԂ#z<~&&urSKjy8>MPLםc&"VQ>=g>П^x%);K$n}#KԪVbwŒ*>[q3tkaF9*+zd'90*>yTԢqiǔ+*$%S\ay 2!FokW;;JɤDl5~#Bjl.O-H(~S1*&b[,O[Ն`Ijg O[zōNcpa7A%%w9LKb g_3:FT˔\p3=+ҝ}HT-/{J˓J=vPZ7 ׳cJAvD:SL+/uPbCa~+WЊo=Koɛ'ǯ(zW^UR G_2HEuܳ jU()9..S2+25 XL7pVٕT׷'f.,1ŷ<*tUUA-Y,"X0!W`U͢U08 贅/iCuj"EO?LgYΦ^g:RdMgc^v̋ni1<-#z$U;nggjG)"؏&mŠkf*~ rQSKNHWihKOi$9 + +6WU;Gj&[ FMwkb</&(e6Uʧ")!BJ'Vc~N8A1%<̏:jwSN} bRz^%!2] +OQrj{v"_fU joeKIio_}+%&~"؝tzG&6] ~"l&g~:$Em5pn_K糴lI7яp``20۶5Da/\j6w~\UN2j|5Y ?]u`H= g,XO} +o;¢]VtP'w䧢x>$ +ffJdLAdV2Yd _ⷧ6dɉ6 an~ 52: bETL +ӑ F!ib|K& ]¸,R>(E3{#OwQ0z~V(7\cLhv=U\H^v^v]~k.]yrC>F9@XZ1UՊro~UĊXm*EUu??ߊ +!2"%5؝jXaalE.K8B$2b;;:Bb6SL=zTpg鯢^ Gh y %Uɗ,Xnd$Dr%X0c 366=A'ĕ`*ZtPtNJ]ɢ-WKo[$VT6o[zuD"G(@/At>KZmppv/{fvWA84I)+$r50W WGTYT\#hC,] ((-v?O;~HlV:,efԢ`Pe(a}g ؾW) p13e?+:#4=kώt"Y1-&v#Oξ씶4}E9_ުpԧvƅlS~ԫ!Q j^G: vVEfgr`Q-vZG^pR%vj8ynx~6\?ct~22QnX$"HJ,x+HB~ hxk˪vIPNh $)mK6Ok@nE\ *!!ةEN=v|v[S$)+23zx?;հE֢6quǨ=G?A1IGV^:jF{twܚEnSV(8+6Sί$t&+:]ÏZ]~`0䖧/,E5ϴjǎ!^ W$,8D +endstream endobj 16 0 obj <>stream +  +.A-ٜgI _^fR0:敧6=(/u*ٔF13ŭTBAݪ* gc+OI3]DzZdoLW)I!2(+*Yhʊ~y2<}'I*zzzf:}t5wWE-\c)"hs5=W|g<~bc!SL|ť.?$~z? Q ?O'0EOfԛ+nF38Fҏߎ:o>+h0$V>(NH, +yAWN +,oeE$~BHb}VMN8 + +թ%ݑ4m= 7ҧ-_wTTl)'EPn0Y 4 bȨd&۬Qf)dc^QHnm#&}Q6eYU\dǏ;%hR%lqvI!cvz'eͷѡjl~ Nͧ~)8\Ѧw**wAyU5KgQQ\&?]1JP߂A (+>=8i况;e!-2>nfGIا=tSě&%}`UҫF_ْK֌-}( f.AH%qZn +u;~zIVW^'XbG/IZZwIvզK&ePJn3ƕd[l2dE +QʪnY9=l cntt"urkXż[^ ! dwØLn&ƫ,+HB[Lk6I^5xbb]ZsJ*5{V,]r lHlOQL.\0绯,*U " ~P-)7~'%ٻ> 'Mm(EEin,-\ǫ0zI74!6e|۳#m u5DI8FYxz_]rӿ`̇k%[`| .Q'e٫kV}WKJrEEzҤD‚kfKKvivHrW#=X^i(M\sϗ*=)M~dSݮǐϊxL[Ɏ=vZz܁l:[n$2J C7 /cwZI3یÑ=9$]JJ&}7Q v^ih/I`V_UT䰡Lݧ: SKey"M;5ҊƷ:>.W8׻IB3WRpJ)6s6tJOGI 7[T3Om$+8)~fwa^Aզ;/IP^Mݮ^IMA|*&VKoѧ=ԺP׳30d~W*^I.-{íԙ^r?Aj +LϝfPLWaϏKse'[oC}Oy@JR Ѹ{G NJnǣilrCC@Ѩ RdxIUmr_yrE9i.JwO3Kpo?xo] Uml&XIDŽQ`VKDq{j;uǻ +6QlF|Wj[ f5j2Fv&Ȅ~~j¦1./9[V\P¦ǂw3Y,;GR``^I~E; +J$ƿ+l!26ZiԫZ+)In8Zcr%&5!;By4u#Cg% l>_k#Y$+^PhG.IPJINmhS!X$\T)DX4}E#T+IXNzBHLo[}٧6Oק9xԙj[yW]9 *"ޞ\E)*ZBlvSuˊFQHiP,*U\fyKl䦤b&,lY4쿎D\q6d&5Ϗfp'RJX~φ4>/\v9Sfr[#ŕC +^ .̯!J>SJ+*xFi oI/<ٱ^OewbA]YJYC +*VQ]Vtʖx0,ZE(0gKQ’[\KlFqgh%NVed6E9..F׾nӻEYSrrqhy5E + ^Ke5*QF󷠘CKƉJ}vh:ʢw=]tTv +$M?24BRĒQYޢ$#ZYbKZWI4H!X ^dN#Xf򶫹]dK&imoy_%ౖx9vɑKϡl/Һٕlק Shku+ceƪϙ.WqնMM~t 4+'':0FY0&A9ߎիLO.WVu̬d3mݪۉwsʢcZu + +~q*a;McpLnOYX.Oc9EC_33fM^R*_f +Vt',ۧ;qdwkku>?LvE9&'v'ل滛\`C54P^-wU%)K+6\lx)fm'zfag,p\ 2,he;#UXLGaڢMS !^h8tru%V2̕ZSboi`9IV%XDzDԏsԼ";(TBC|E|f|ܚU< $ QY|ikWZb?{H t7GVIHGC]0ť0蘂@X\)v Lgiq+99$d,zfƷءNT5ϊ\j/}$2͕U\Z3~UsU픘Ðܾ]zW%efsGRPq [br4ǫMYQ&E:LWST3ˊ,J ,2;q" ])GˋJƊ+難ԒLo<<opPő j(1/4bswG52ja3pFK-Nr/n&CdhJ9H愅Ikf񮎂z?[CK +lfi孌q逸E2Z׷Zo5}]RM?ME´}SzG,IdMkOE$5JG*^uXI,g&;a(I6VJ&uÚI),D%Uݢub)U>{w:d`^8o]sxzI?; Jlb lf w:f֗ZRL΁&jѲCZDaeR^ҋ~a^8XV%^y&7VRejrWׯ᤮N>c{,z wQm^\w +/A +  +릩Aφ),|F +섭;ZnW'=??,JX6bj(z?e0ՐQQGE|6ibf{I]ˮ4iJnz #OR6Oq +_[h'ZI>Q44tX%;*ӑMvt=˟i9pxl<(x撇/Ϗ-/]V4NgTĚ+%g关عV-JC:LY)/|Oh(WMPo0ɭd뻢e;C9pW[XL `S+:]l(wyD:nxUwOPˌo.m0Åڍ +U5o[٢Kڮ8^cʫDg,-7i3%*EtvE5Z^ϚLfֳ-) eC34ܐ)C (VV)'Ece;$yA&h8CƄggpd7?|{O)+%'f5Xp"'z1 *NbX=mɠ%عrYaeOc`~mPAY\ɢ (Pp^^G]\*va'`^`  8I+WY\,aj&_b+Vdha*STuMk c%E"+XD`Oin'WA} SnJFNJPjF_u+#aZ\xOeY}v*J~.YP(nL7_K-/P@24ut;4]얱%wC:XX5hd|ͭbw™`N3FY3XK\bzg9Z%vQV>SJ)(~:IrRG<:JMU[L^.p)mChNz`S;.A=3kj&kh?v׆',Vd.l\4s=!Ynw2 ST\ IF_#g2OZݵ* +MF ,0de'/ ݑς\sXrwoĔN;x 3v3]m-1LRar %¤VUrA)zd:83@e lr63fp:B"R^f&8A4UC#NXA8KgWJmb#e5QPQ >KPh# 6҉Ouŕ^$Aq074LK,0/J*;͝`; .u{DzU)06UM`bd hq2 NxxWgf:َܳ+0+1VG5d5șvܲϒlC9il%MSj(5.c8 zX)ZG4 %%! 2rŞfZ$y+z؆ˮg{z_N0LP3NMK*&u&z/IT-%¼rXzʝ}]l!j8A+,?(1;QKAEfZ/^Unڿʎ&_pL4P\)r'ܾdwASĎ8?vq7PV=jHQ@QtW%6EI*vdP&*܉u]Nu5TT0 +-fv9T8صlZr{+LpAec5C{B6x]n322&!ŵ⦟*rVMJFM+5䂇JaѻU`j@-fƊ+%CH డn>\#FgEw_f<*LNNEc%ƺ˗Y^ K.EQX7ݪmr U U.<JVdr`lEh */z^> .#fe0OE3w+ϚbQFjo/4ܾׯV82"?E)Eol&CǮgQL,x?;)&Z]Sef m$(& .1jr@p`:2'F9^`8`d;`f:^h0.O? t+.lr`؀D ڏW]a`9 CsՐK,ay6څΧ,~f2ENբѡRN5\hV]ˏɓ<|Bfϸ^֛K&:1QּBVR/_w,nA-PX';V֠b@ VЕbbEY }q42気kv*gOt 7m}+/;)67dvHq2KC-6+9Yi 6#6'.x(XG ׿C7XT;vpyNPLGm=d%#UTɩiOPLOgP\8x`@~3,Vh8@K 29Tx5+~;j^XC,lg;`J*tק.e%%}ܥGLfEn~dXP5&}7Xb=%nWK~vV9߽/th+ŷ\=Q(+5؝PRk&wfLJ+Ky)R.OUSԬ#&#%3t}ʉM\V3^trdh6z-G|Yq&@'*E0z1ND@v)%صBDk[N,OŇ.(n&UOYp۟B+)H<`f; +[e%v+<"dU\CEfp'*Eřvu&IT„ Y ՉU_ & DXv%gںMD4(Րzi)Ư;' \i<$hhE/LLQՂ(1 #28I)(rnCIZpdgAT/C63~FOU/:+KOKGCH&v b톘*>[9c `@ -z?,(/v&ӜI'Č/+cX o#izAYM'ճеLק/5VZ*&{uW4%*-)WÊif WY8SEeT4@A@?3@ "<;|6Ί,A@ Z@1Ck(a~g /U)|lTa)b^2TOs %ו):7SůSNtDmEAw?p4?56QoI$rd' cw-<.֓.@L¢us=ԛ ŖNoMxF 'o3asš|̈́"KM &llr[hyN>fB'e62-[OXy;z Oɛ',Ygr&{Plɣb9ℷLn +?9WϽO(qrF'7b֛+gM(XzXL~hƓ=y KOS'Z2yT'?]0mM߄%O s7,=y$V.ɍ@Og’& ; <&oLx@&o?a:|^VďJ>fRb26̏nMȠGy- E> cO< SQ;u㴖8oziS rԑl (!u o yFZxۅru;M1H xt`- >ʤkׇfkUc6t7qYȋւ0;BF+aA69Ovi*\?XPOdK~|>G +#fVi<$HLXNu +כ>Eɜ +_NXʣtd'OReLd<ȶ=%5KGjFW}T 'G @OWp!'C iB7hZpLRp(kčy.ɝ<& ѡ')rDbB nxu5-VGu4w1b@,.n68g8q{QSݦR:[=!}Gdy#e,1[ 3!!r U,J"hk4hNV50b}*|E S:tWq"B 'Ctw ۽zB94~ud(UI_-٠#atjDR8G>-`hv+Lw 'uE=Օ7?/ =۠Ak+>(d>ޢ&`5PI~;ZϭQՉKV@w>UӴxZJEp~#X $]Ι63EBߙE 9ugQFNnD TN!%athHUQ7bwl_ N=I{`Ԓ~hcdOEdpyj9ѡüYfsFcr|0brĤ'R>Jޯ%_o8\ Q(X8?) hCmSԇw3&i>u( nK;eҠ@f>%e5K +B**CѤ_{|Qt{22(1.k86a50]kD:_t$Iԃ5t]3QN:U 1ƭ]tTN$ҼA,eq;@ȿ*:rR;Hs,BHC}6=gV2]DF*XL H$(}bR\|0h, UNCTQ~rD(N?@]Me l͙(E ~q"5Y! 2r qtҤ&jʢ`%fcb&N9pLL5.&A{L;2ͣϖJ4~"nZQ ީI wbFwfY#r-/:hHۨ]9˫)2X?׼QlGSO"C"GV5fA}U/%u{»;Ӓ:et)v|Fjd(xoNC~Kͪ2u}LB$*e[`^ƀoz+r7X孶V ARc$?њ ]=asQmXF1 IO_ww-AmN2if$>.n"EE* ĤB؋ +X*)Π\Q=V; pQ[OOβ߃HSϯ-@ۂPf.)xS^XpّLr:GB}ljqۘd " `wn0<~~. 2u>uérL[tIwjR.WQPҒDUH^V?&Y 2}zs͒eFessV+2WT[$@ɴ ES|J[ulTOךhmRfG <|7NTLXm.g]  '&XbQS*) HeZVE̋A%j^ +=y<ʵhS?qˁ}mBQ5 ∼yPN& Bl^an 97u%#q~r)'| aL4/D 4+-:H.qPpF;~[X}_ymrO.Uw QԤqU-_Aor 9YW(*=cb`*-8Zf͐4dkB O3:+|lscLUOy#}N#*m)݇A +YwVM-B bjRy!aˀstq&ެ Y'K(5@+fhfMJz{8Dx+bPS>˹sYO"$E6p (✘ш{ &EHqA+UOsQTFlA.^ e +F=+hA_vIg؄Nwo`$ 2.ר=Q=[shT"uyqAqa5ci˕p$; +X_l(jpoO"2ddʢ\RUg`#˙)=h&@脥Qrh00a GQ!n/. 3)̃AMz^@q02=ĕR +4aLtӝ$TvdxOAneWMt+n<,U G Hdf3 kwGwYXKE7j ɇ$M.8\aɵ F*DG=pcNVf LlH̸ +8'["Νo 6cb mѮiE72E ,`tj`.9 +̊wOt'Q +,}׭Q`IߐB9cuאdoWluIatcW~Ȍ{ԒDZNSa``-Kt DEɶZzŽ| WM[zJ99ΓBy0hxS0,H*` Z9Y-MFv"f# +.sf1[BŃ:3s ND5h@F3KGjKQu5> +bNu2kJlZyXa-a6 7 VyG!qsTV蕨sA<{7,ivC,pK#1יRiedZ[3Bq,`8xT>27eSx^ 7ʻ(DlWOڨ:uK4jh>zb] mИDN SWՃ2,Dk5Xj.pA? orN,/С.?)z߆^ FmFS S*2g p_NmH˙OͿXų8%HUd +wDv&2?@9XI4fq$#7x~I:CC2w rH2s pmIB(!Ӗlhj $O:V=|Ai8#t'K?ib@I^`D;kQ+ ܤ|E NѸ5a]a|}PzO }DV, s %*$OqLp ǰ<҃$GIgBs 8\-TK[x#8č"kd-T#b;IR(H7&Hx+ERZV\(! HeX4\$jH 5"EZw#n< 9C:w7w-Ђlor@/C?*(C<`ŠvRa(k9t2q]z +31>FzluS2 +U(Q( icd(YJPRp2|QӀrhH¯}.?5ԝԀI$'}hVis>}o*^`&si?7cCXh\ r1yip 6_.`̒cLFŭǂ~ +&j>Pʸ07 [2X`Zs-mYvɄO,xлkaT$pxW.A@vPPK7?,誃_L󚙣Y['ʢ:-KzhPܱh #;d^K.aȠ]BJdd4HGq|Op$ +#qK |Y:| 6Q,"v)M`io +ͰGtdc(,LG{\!ʸjAt( Zc8Xp.e ;Wo@ ڂMҀPr{G*!#ឩ3 +|V:)Ub\|Oܲ'vxTa0XkJ4,Yam{?c$&t/~S^ |& ໽@ӬFSɶ5:RbLyB*FW%ڂaP z)Pe5BW0$'{jX8EqV3[$߲Ӕ LSO{A2>Lm$6hYNbB/ EXF6Eu<T ,fL6qfZs&sW1hv abyP'A__>߽_1qwT4.^j(WDڌP0fEJc^6NTt0bgb7'so,_zf : lƚK&k:pe-Sj&k.[Mw## x>YlD 7M [e9Noi Sml7VrxE', i&ݩ Kc릃v+4;)KP5)D h>*vJ F['ܾ>&~})wFN > +D4@%jnZ]w]zp^P[€u1r=o3vQGO,Q.*/Z{[]_DhL'K|)O|tFzT6nǹ? M;=`2X_&tLL\obtBpIuzx%WFH{C8?9vvx8!qI;kciC=wՓzAnDu|ݣ+E¬.fRi VF)5#EEBig s,i ( +}~Ԋ1Sp[h=~rm`ig(4T2}I 0cV5@ʾG3Us.3p02No^dzuZR'R_Et' + +bz|VOу|D@UuzcE[*S&Y̦ `)Rڔ'cv#00u*M)k'D'Gטg-QՒ\m1$`90+А&Id 8g<2~%)e5[&J=<4  +Gp(g4![E69dATC=t.=&P@ؘ2adfpfaQcZCh P0IXP4@6{aa}QюPCQ岢:pf $d8LЫ +;ɳ%#M +pP--Q[gcV}KpXXTlU+[|Ns+^99+YʘY}>nJa=:G|O:⸹zNJ8C ѐ],fKJϋѨaaa)bRc=jHڐ@{ I[8d$O63ON@ @ƒ +$<(<`P@6@8@ "Dxo\0@ƒ $9( d@OP + @p @x D ,DPHH(A+ 0!",P8HT`A "DHHg A(˪h ʔuMv ѩQaIz[Fgƺr)%(AŃd8@Hp | D(xEѣ!^{BApP$%:8'>ۛLmTw7^Th`C(_\hЀ|{󋕣[燤q,v^'os/6*h@0b @`!'""̰ƪu *i,xo || 㬕8C<: F&7^4jPNr;!9ޔpcNUcmH|2zuXxT LjlzhAӑzEHjE#clYvŧ]ٴ%QlwЖ; yvCm]AϥjQq gTf&"AyGeu5:l Ysa۬WZ=|U/z_6'^QI8]g<|M~jPS)5]^CAo%7}N{o6}wic!MTwPj]9jM|Qra]dM8}YMlHe5Ƨƨo3,bQ߻̗(۩ڣr:Y?svh6;BS,af6f[{ُwqyd |fh \fDn4GCY1d Axԉʮ%rWj`@?n{Efz[ul'W]cw\.c)-Q.HyJډO7(O#ҿwr5 N SYYrh"9u;oK\Ϭ ?,+iMp@^1Y&b3]J|p@q>üI\tD=.c^"j\2w 5!SlJDBz$U󀏎FLA MzrٚU\VKN c6UΥT8k09HE+h pi:4+vZԉ`vnOgC3/oH`GUFe@]qB6qd9x6&4LP(|r>5)Y9N*˥Cd}`E_~6!- m%.l]^IhDIqĺ[qi_s{=Y+_]"f+Eҩם f ?v2W6,ѫƻđ ?FvV}ix|sMB@lmn bK1[T?U8QUat_grXv?~ ̎ ;9O!cŠ +B9!UҐ7EN;tpz%(,9`+uZ ?Q؀9ܶV(g덏#'xgB\uVK8jwcAJ|Iٵ8t !xsX:,a1n +c- ?t fMZ˭@5 Ch|;y +k0+VEVbj-&ϩu}ۤQ+6PtMViݫ4oM14UԃVc\fUIϜ/ƌ5&0cI!f}@[ݜѥGm ɳ[l m8>;M6ȱ !t +*1JXMM}2޶\t˳9OĦdž;c +kP:' ERIsyV5=Re>ұt" +0VA >foVkjsg>Ts֓ʫw~li7#:\-2?PURW/w&c[cIg?> OwgDdX <;?9$ pƪ D0RD Pm_?:hꓙ0: qQBQ;Xh| DDQnc4bDl]Qe͆_sWޠdרa$U yb!c +r9^j(l }]~3==Q, X\lƛ@޵J/;an‚P/~@KbY8')` 87B  ς17h+0;Żj")ZX4 p󌒂mѪF[_gZq cepDQl?p>qdZp]CZǦXjG^uG꫁Ê%#-{~aq>pOQ,MyɸQ9׉}PӞB Qfg#?y\n@TX0L@+> eP_o`yi3veFiLwk~GX^, \+#z ٘rOD$F2,6(2BP)*X[48.q4í?nW$ϒdޘ,"H8`zh3N?kZ\@XzgF轏rRT|0vs-U#45tDGy tЛQf)mTt7K(=<*IOT]2C9:MV(XoI@T6p()" +Hfń4 :xfZ3Mbv@e>{noy<hj̆Dz&KURC*=@`Qç(ʃ"?Kt7"lM3=:shoMFߊD&;0CR̎2Q%K +]wJ cކ3Tvs-B=J(،W^HȓyZ22s,mj ʹ^tvZ`z[:R#];w)lv7?;C>u!ҮcݨNh;\tT:vvtr`C .3gy`#p`*\ !ckj#amYp<-ږlᡀNqtcҿrL\J jZ_Y_+gԊ#6ZVnbff(A[sYoM{X`3n8/ (%DKr@u&V[$_FX`) X2 BsoL0L~O5vVi_:i=h_k4 + :4r^i0W6V(ĭ7+ZX0LW#o +A]/$`BQ{X&/0\b1le~:(E mZ)."|p hɴfOƺ/9eSAp!'5UE]q_xpꗅsA[P)}tTGeYю/%沄g^ʲ_)qъۖOUjv5V 6qWl+dy!/؍q^a[,M.LrA=!ڊz +Q<ݠ+8'GKrUi:J* r>r=~V'3zj.W ,{뵹"DKuTE?%g +ю_ΰF`x%PKcCN +;Wi8Ib2%l|q!c\^LP5$ґP{A%ڒ LZc$0n$dk$ f^< bI3Fs;0"l; w,c"6@E|+ =@ZUh6E (%UrH)cꪞ78nS#H^ +!2޲bm=(30e%zHLj#aHBؐ1HŸN! K,Qg:"a1q`E,JK!MԺg?%X?B<vg-@˴0O~IS)˳[ob<R~mb`ژcpEk'7) 䛔_PBޖ41F>J'S ^-"~#Qe })Am <1NbcQA%mZ=c ;3eNmg:yc#0^TN,R3m(['M*>>Q@H ԼA! YS=:? '(u\PP۞[\Em,n7^n?i&V`.57-*57 X")TjT10洵16}D<643߄61 +9lra< 셶(7UB5iYXQ2˨勖/k"IZQ9R߳1_Ss"wLQ+k+L'Fz4u&PRpg-.f6y? 骠{/OtH)yvmrУ:+?hg%W0;9IǤDBr(*D̈́1)BQHtq*F! -/;rBiP[uſi\!W ̞HEȹ eF?]a, j8'ͷʚGuQХ-#N +/E;ʬfsY𺠳MTVq ]/>oH'qU5"Em܇>ΟzrQ&8YVoԴ/k,7R11)X&Д zŕ-ЗOdL0pzJ[Pd-ce+{!0U\]JmCj)ϭ5<ƱSFwM^N5FI?}/M\!Ky_^5-, c/_yFX\9V])|t*Zݝg0'}^soW!`|& .[uFڭh5&U( ;ƪfSEҪ'P#pzVrc#̰Wؕ/hj-9jV%V,j3o .ͪiʕ'ctxūQ̴'U/ +uFX?Ϻ ^zxjiI\iyvκaiz )g/:^C9Tk/),p:-CN[rF`Z5qѥs4k6>VS2q8vn;7_5[Us9ļ'MՉVAj _INtXH 8foDA5iK214\ fN3R䴇$:XRFf sNr(l2xgddѷ,Jjʱ.L5έ&T-2%?Lu͸ߛ;.M1Dir +m,5XX'dIԌk <d`^綜07Pof䊘 J ]'8@Zsf. +'|*PWy l.48 VPE^ c筩gқ>N,z$ +R%ѝ ,b,hGz`/}^gD›y^Ux#μFC['nWL8[EV@ _fp.o;։|cm;w廥c)CRr)"RezG@طnSb,1yLEl'K%DFeDEb#FuTr&HM}ʛ] >rFVg;Ҳtь\2˫.NԬY%F )Xgf+\cܲy%+ffEU͇\ygwТGh"d[G5Lu6Os2j.'/ăXdb6S"4)W$LDB cPND9|w__96F}[DO%QS8&B2eGX+usʬccP^ S+%u3O +K]8Hͩpk-FtYI^ӿ!o&GaD}TaOnID4%=7j/]tL͸M !FI"VZ치ym؏ǰhP:WBkqM(s{*!!c)`o>H0 =d̤#6f/׵ Hq +9$N/4'TeTx 1]z`XWi!]# J(0 CD'(c4J' a~7Přn|qCƴL'΄PK G#H%7ElA2 0\C')1}ĦuSNF%it^NajABd+8D7y8(SgqA4Oot%%Ko1^+fV;@D::*3ĂD"?ELs8:*Ia/ iM#CL,ӂ lk(zQ86F] ߙ/JamD-@SʹRb!/FCveTU@RI~cS*;mN/ޑI݅BdO'>U"͆d\{H[b*g) 𸧴ȥĽ,w!,]G,E|Clu'oߋvPϘx^[76-GV$i~,O%-,bKr0L,a tE +iU"*%9>o\ +!P$su;C_Rd=5<@qJRsnz<&SSaPrggOOOU㠮lcJӅ;)V%%|t89YS#:o24JfLCWt]Y:QY^1J9К5f,-drk<S\՘"r:!ՐӁ]Ȃ 1](}ጊ#\j/a)]CANXJZ-nJ+sn\*M>%Ș~4\UnOk2T ˼|$͊i;0^9y@,'dgc|et`7c] إU +!6 jR=:?<5z\ͪw\+!RE.AR3S4ǻ$ړD7&!wO8.ᕬ8ٳA n?ٟ?^WA_y"=MR/&%!PbTx(v&sjKԌFCB i΋%UyHnj ?7 E6WP.a|͐FIN&45yQ:sLRtg$p,qdYɃkfpl1y_3 +Y֌0-7]_3(\#1]L*/فfIa3\`n 'o&Ei_7.l5yEUŗ\شWgkဵåC hsQ ɳXЪjRFB|5Z.<9?ms#[( ŵZg>jbJFy'oRTtA#r^ha%\u Il*!4o{8c1ÁtrC`!oMeG*[Uh + Y+#ƒ"nۚb$O_8;|{*RȺA$R:D yMk4hX+_(5q(n';;^;9PDu7, /s~aY+$S .V{zDs(srvf"sRQ9|jDC& LM9դ{R3iga*Cлɣ8AH̱GCyg4PI]5x+ Rv}.>nr_h2A^1Go׸*^q'>}kfuʍby8"UOLZ(#/t+*CxBgFB}iAidl蠄{BI(;/ﭳfÐ zΛF/Rz8Dk6gZ'T+Ζ_^XJS\yeixxcAo2mEɣD<4(NanK KkLN:z;vos<(EjlK.+L .glhSw씜䈯B+,#jC};v,629j ~GNS͟\rʴ2Ƚ\ wc +0N3ԵJ2 `NDW6 4ܻtNcQ@c EG?]M8a# +0܍L2.Xk >R^*.;$MU RIs1Hfͅ r926I'LN>20()PM`4zA#*DuW؊Ldr_>wdF@-[ 4cҖ$ﱍ0iĐ'^ Ai6+gYݹBϙz|;ṃwd;Ci>V>#G"q b} H0"!vPaMJ-: WXP$ANt]XQ +Ib2 1;+&l*Wj;dܬoU' H#>|L?ֆ,2+,t+e<ẔUB;;䂔KZ,s\$|rCP t`=l$/ci% *ZC(hE|550!1INsΣ^E4Sh6-D3/iM|Yҹ̛(6/Z23X)P˱'aX]5z|>FМ!QB 4΂fY9ݥ9@UFop\q3b$VJs$;Rcb $;&%;dМ/%:6=L( }΁+I6:F5Im%E&>gf3!*U":2yup#AAznŴz8S. zRr8w$eU1%ޮ3)s\v!iL^J XiW)T4 /4"U~} EKLF6XAt X&\IMHs0J2vf4A[ bN(I壆hMq$$LIvJ",iD˃*煱+*P="J=^X[cS.$9/Za"HlAnaiyxwzn4{bwFECcQ53#;Y kHu?s "s _9F1h.X$"fmb(L +Ps\j + NP<ZiEΞj>A0Qq>sCnj Qʪ /(\95($%nugfmcpI2З*_P ~%M^'>IA~&) ypSgCȈy5ur'9o˵1E +૏"[pJn + +)$%jnZ;8nC:+wgl0PLK@6 `Rۺo+ѡ + X̾DOf,8vG@@LZ}W:%EӸρw(3(q%Bc)޲'0%LrzJv& GͱCHF_D%~0[ Oq |=}vJ42cġk^ї@B=M#Wg̈$QuZb/!cBN Mtlk P7ItšVE|0nZyf%^ ;-I&b@ +&`H#JOgp~i5RKKnymdκ40"-G2&*N94nռ q(IbfA7T4AoI= % %NMZz,8w) C7h K)mNZ& +iK< XV žz4G.flrfܘ.>6CB@g|`z$ BrSPd_2>:O]CI5#cEŚq Bs8L 9쮡9-#-Kޣ-#dſ6-͖ Q$k;L an}y>+@j8Wl@ +m)[ ^V*%X9E /"VC\@1D5\X{ܑ: 2q' +O.D/wVFzP  < :}af(ɳ!ђ` m:NIzp;(5L*֐IBz2vPs.FKڊ(;JcL h'Xћ-u{F"$-)%CRWwLcHo%^eHխSOj("0`(OQ8( 8/0,U .gcB5[0-v7s:ͼ-/dE?5E95&ʆJsG?>58}up(Qy +ǰx""NiƫiR#f/MYH\-Cn2G8m8<Ap7ڲyF(ވy[= HE:&]bKZ,mY 3t8(u0[vn4X9_4q]C/6/'V &9x`]nP $c>zlƏ]},]II`¦x7v* x4*`3ċyEﹰVRоeWEo i=Nǜ(C(#, tPhy-fs8Aa'Ϊal!qwK2k.:r&Υ:rU0uya.@. %Hrn?P:<ljaF[eىi)̉RGk74[5@\Ê=4KY$Wc8,~YzUR#Lϔ#M%A]Ս#KwUC!\ ]C}7Q~G;A MLLaqOY13N$.FЌj#V:Jjt\@~rWH(uL{LSS:$-Wa$\#:"8oCՆ@o;eU;2rzA,oZX% [)=A 1,S}^Z`0D)G#躵}NƄQ!=W]D-:qjjgMvhrS۹6eZ<[ +Eǔ0ǚWPTp`$d@/ɓ*ǡ|1ѱ n  +vDEa/. o#^dXP-%yl.)e:{(G:Sѝe; .ik>^t9Cl] uJyd9@јΓv0쒉ըd-xޠ -W;!-i<*nwAB1ͪru -{@;+u6Th̜3Q/ {mf8`ߠ3l8CH{/$-9UqCdvgV}ݮU]ۢɍt>McP1wFMdņnP mQ<4U ۽~଀vhll{kM~P m(ȃ<2 /ȱ8(d1UP=of9"xC޲AHS$GaIlf1MK_D&;Y k|{Hb-> l+\`N)0Uv*>OLӃ+JBhGd(ݜr)t}sU>U)wasDC7~3%r"Bŋp1{qR{%N8v? r$(tk;92yL6\Չ -j>IHv9mID(FQ4EFiwPCgЍًS 3CL* ;" &EHUlTAPOZj")CJ9|kSLtBL?@mc5scxo 3ƍ`97N Er88;Gm{o7(X}D^a獴!' }%Ü̈JA>Uu z݅){r-#Q i0lA{PUV˻5/=sby3 /Lv&" +$ I9 ZcJбMϗ:8\*?lS7(n& bm$ArZO.)LU ~bT RW97 ؛q6i<"a, <(3 =|k @.. +N@[ņY*  *3O_cNQ~Zcf֜j|Bu|LF밳Wvɚ~ɟǼߔS$?.iZe@Nw4m&Pl\x!K|+fO/i%-ǻob!mD fǑ)GIu=kUؠˊWҦ ",2՘h,3Ĭ +/H<d 2W x|O[UN*炞;M!_dM&jbP{&IG_]4qBn0%nǖl:!M@prʳҬFY{Z.ֵ舌 5 FϾ]/u"VDOKdH%)ϨEJ@@n}0 \;$Tt(G-fiaK7\zn,oNP.ɑc`L1iBNG쬱@](ƫhB3t5 mhlz-jg_J*s-$ѠîU!kfa"p1LTG1$ +2n)FsȦ }@hc"Xjs 9n6CQ]J rH0ĥ)ɢ8ӷJibO[L3LX7!#H!{"U a +t,[sKhxb1: +z^=hЭW\WsVw֬^yZ,,gsDHW/.5"%! +Y2Keg=uݣqs*N)+/;aqL + bNY0`cuyՏI˾ >ѐs,Kc`)֘?-$XQ]\=\V|\ [-A+KTWLuo\XF. O-9,Eb *2Sz GԮ?]G  2C j+Eei}*zPi/"*әഅcCk@/fV]NsXڎ`@`;{R.FvEtsi< UϚsv Z-c35s>^o(YW:1k|jW:Dx 57{qQaxrplP( !WX<5t|Yͺfjجpkb)v<$}b{]Ezs&Qt^,;coSe~(¨~-,馻r5Yz^p#̳/+IR<ᑿbʁtrj.S8]%SA,PI>rCs ,;PxUqUhx|}yEUM-#O?gIBq=؉CE؀#j"^ n-%$`" + e8 W` 2^Vt6$y6()TBg9!ږ( C/@t͍d{lJSkeG-8P+yo%)jeX1)BSq5H' Oew u<uq~cČGj}]bv#:O0> gxOXk_<Ȋ ˊPP~%rMDm B@},^Kys peh5[.d4DwK/{!"MI KU "L[^ 5߻"679T2S}CeZZyO[(+- "[ PhN̪Y>\_8a 'IK냓dO(&خr "'lc +F# Be75.X d+냬Aa-w@4(Į9K`uѦFQhT'V1egS7H1=#T>ZmIے߽jbp(hVw:=iiXG:_[6"c^:.^3M)f}58$đ('  +vOK$VA_ ;كNr dAxeS uZOËfmOdY7&u)5Nx 3t]'JC39N(rG$u?C 4Þ!4:a-Ca*$-賧$Hh%1t#[X"yIR?`,r!քC%)BtnC18 +Wۊe~κ\S+(@A8C`iaO bƭ:29 ++!>WćڷG|UWMgA>RJ<;)k ?VO ,Ɉr%}P>{ 6( #~)} ْx  D !fs#-/$#~ )IЧ˂h(SB`#Wd> =AlTH݆-4"1w4 +.˯6ˋ|2)=p饸nv^n337Qf]'|,%5hj΁^[R9~P& S[Ivi2iX#jOúFfxphJiNv}__j7<{o$i}>`^bZZSPwp"#Ai2.S p|a݉FzLUq !ftś97iMeb 3*7$&i>kAq2䦃cBM՟a5)1 Mw9 ('?nf!97JѼ%οOkr±-]Xs5oR#>ׯAXU>e6;}l%1Swx( N7g-{c +8OH1X'~޹B-#HL=GK.bFzbvƢ>MpHLkKhAbE&gŸKYV9zn°h!2Q`M4܃%WJ26t/ɣ։jtnOj׬x(N<zJQڍcFMlؘ-bJNS4וJ`qK+JB1b.h1zЎdv~^E x"I$(Zх*-ޞs7WF}{ +Kt FqDb:E_BON4(xnUs3䲛)n֍7vdp@D}'s~z{)mMlOXP&lB qFnsܣqQY1v\M)G*nAHMM +e͍p8;;/11XvRMJd%< ?5OL+X}'?q粈gݷ|[B?tyj5}&N?Mq&8쮴edz&Dy`] BDHmxYF  +, +:\vRcg D@I-4m?X C"CvۜuV-k T 絴1y +ק5%k0ۦ:)vVm~Mum+s)jaX=z̶nk.3*c-kSMkNȖMFZfUӮ~>= XMKïH5uWsUwZҺ k ޴ec¹7XD6kKʹCRVignkX4[cK[=ݗzN$k. ʢeg$W9+:95VE +m>Oehy^e11XKB?q&~*޴m4Kml(8.rq %ɽVΚdUQ)`el;^5h?ݗ0Vek0^gYMmUήZ1: 횮ڊf[%fl)?u9l kWU^cXǫγ4ʆrR]c +C(^U}8<1".Ѭk\[-ͽ3W5k77^5SɳLżP3Zgu~cWlmik۹EG>(g + W<%*V&PUd,\| +ol[<Gri/<l*ͪHp + +vdz9Vtϴ5Z!mNr +w!elpi JmYL۲SX$fЊYh`=:,ekV +UŮ,c- HA?4mƵZx.ͱ*j1XYd\jc̢eU[e5Cn1 ({gUDz߽O  +kM_d\s޵n4溡'Eb@P޿&PsT󎒚C]d׉U1{I/kj,q嵡Rb7E5O  +$tuWˬ~tc`-jCݪ,VmndפbjliqﺑBhK kR7"^gqHkMuO$Ѳ +6Yܝ朗܇(I$Yv? NnzTocG<=Ix?Na?9B:sq@H&4uf7}9v&=z7}ק6S޿6{_4iO~]7ǒ6G/.'*fMS'f6ͳߓ'7Ŏj~?M>򑟺ܛI>9 OYሒ(yړcK{9Ӈ/r͑Ѯs4ы| h?ڏ.qK'͑$HF,IS7P&nr^M^.Qyတg/6It$99rcHq$qD?:~]X~8PrK ҇Xfi~sDM$G-?ڇIMv2'PR2dIԝ /З#Y }I'GOR2UWLZ+D+_%,5*Rg),`H^ e"8_fHLl(V8W` +@F07 PϚ'9SO020X~dQ|DfiP+F$<$@k8ƟKKdav6b$RѺVԜ:hTmJCPE>Pۅ6 ?),,t JH fF0ę\/Lb9d/jYb xChJ`|IfP RpwS@@rˌtpqϡ*3o@HPɁg8wq>d4^doOk]jVD4K|-hE 窠;T v2 gu>4ADːq)Df d +Bݒ6܉t/jȝ6iu`˸@C-e 4I3(`KInn+J"hPVr3KTR4d) H(j,@MY + ($`K^41o]J1nʑ~Y}mѶVvmimV;rmbo@A^xUۻP=~6Rb)%TϦ.ffOt"6ć~mY8*d (q"m Lخ}rl͈AL]F9r߆j}D9!laLܷ +(+⣳o$3܁߀@ mc7lQ؎ۇ½wYS Se?ޭ.^֩@ +ogd4=,{43od +"OY!I! ƝK]z% +g]PBq`bʧ ӌ}d[|b3XFhXo:VXu(;]By:AAZ +6p1%j# ƦQ88pw8FAlg%㹀Lt:46E:e`0ڛp7 + + +*J7a +dU2 {@p 7 ƥrH"OIiT1IޯXAs {| {?6 bo@#9[XrrCA`A`Xm(7 |u,A8 K+rLr|D_H<_,b\-4!8f{ ]v OxM1$Zȸa p_ޛAo@0A{oMtR{tRdـi<_yp62{+t-Ԅ{o!N*{w[k{džwߌ{C߀q{{bؓ9`lU,`}0,:OB I36O{SJH 5,?YW#Bw7{e<7Dih.[i۪W0@-}mavnw5Z*)"J˶ +Ьm_&H9P={c61%Ƙ}?Hݍc56VSԘUXng3PL75,igH? O;2yxuqN,իӭ5V^-F˪vv_V{Vl]]{įfeZegvjծzEUeiUe6d=ӫmkU^mDm%utV[Sb:mde|if4sչcَ k-k]6XDo(ڲ%Vk+q?˪ޕeV?yƦ,5y3mf8wW42vW2.>*v{˦TZU;KYbRMͱ 򏜳8g2ιQ#Y`zmYlui2EIop2=TJkzƴr;v~C %+3Us/,+xUX<1jlMX{+SIYظ5;xޅ{qiW^IZ dfmY6^g]Yn³_3 ѼK0ŸgsOrӫe4ʿ 4 v44`pf7 ≮UREil.001ǺtdI4p%a教XP0F1PbZ- `0Zɀda##zr]D +rk oА`W7`RdZ ~HU%46'c)ǗeaȀ7yM)hZq QĮPN: 00^ ,ǗBM!/Y]][ t2DÄi,߀fx{1s 3pwz( VRtaQ3MR|`&Dzgd@=_OI1&:=0ubS>BIqo@(^Q$1Fy`H$4Kˣ`m8?} -NP!E̍ɼ<5rnZa,%_@RC8@ {!p/f/I36؟(fcl Fr^9%H'RLY߁ rbP3bC7aoL<;OJayJz +C.z@ֈ lIJhfl3 _d YQZ0q7 $ۢ0vɱH)16:Rl\$D#WoC>ob`,``u*Ra)`uO|d/#E?$*'p]#:`.I hH!~\"#bip2+vcB1I)vI po\(Xm% zH`73"s|%58`\߀0ÿt'8jZy ` ##b`@(V͠A#a#dYhYfCI@FFazPcwfgR^}e|jYX*e1Qee8VDT)zS[SZ, b;Y],@eŜe 2e+] 1 $I = gVKDQԟ`Xlg9gbp;˯MaX&0=?I>sf[8mZSyw*f6& q@6^Ĝx+h-mbܺ+,yD흭kq,bLaYmi*2,wb> 's&^Z 3qvi e",*n(]28w{\x}B$)䌹xŨ8WS7)o@la."1ۛ;&euE xW.W"9[n՞@7gR,(ܬ=8Ȧr tD8N#`lZ*+jC)T|kBvT&i>H۵2$猫\6jrn:%7 f)#qLb"6;$ )Db* \0\wSn3*`M%{=y]N'uZ6CB.5-OitjGbk"p`e+s\!XȴNQ\raxlVS˲2 0p%jG n-MDhq΁ԉVa,U@C}"C\$ +0ŧѤ4prt*J8hsJ8D&4,o`EU6J(G4Nɼ}  io@hh佘shv/ю%و +͋.y!]C@*)l5GI+l]lQ|Bſ U|&<3kuPu%uPHG&%:el,86ഹ)Mcblj"\l* .Y4iX2|Rl23$3cg9Üp+|K!0܂P6tQ,ʅ[(T"!ELl2XP-s)x<% ~(J!SqUbw*pME(T-Ef +K~ +M(MߤLPcp,00DCV HpiJ0 G "Ei,H0"l*-"t% !N@:U&`oD,$@IIv o@@L+GFq8p0NmV*`PuQ? ۤÞGd+i53gS.zEdLv? ߀n7-$@ >ɔD_};,vmP&!bq<O]r.~,9n\8%>0Շ^rxȋ T; ۙ8&8ݘ8?%n[rm<;[&v@!PLi ĄRWAp2ptL*婩i`on677mfKB";#2Lh4592ksy.To0cG["WDP)b\A.ywqņ_  s\8:Is0ʔ f_ t0\T1^K1F!{Rt*jbl8 2xRLg1;`XXP$yaX*(J,%{!(8k4*iYL PDqe!>z3:24-u9c4_+~^cTD=ձ2 BP,ACU&EJE8y8y 2 Ls⥮$w3ˠD QAYFAQ@LiLT_cwt*Ǩ k" F~+:7 ȱL̞y߀DuNÌN^uȉ2DZWN23V,"+eW^7 UfRzʢWH;Z>#Ehĸ06\jk|B%,a|HFnY2hD~N4" DT 6 +I}8"0 L'FĐÌWHjD@_z<a +~Hao@p0].\W̶z&Ӌ>ڕB$aP@yxlQs~tqpaƓ~W(G7fO--l'${^,rNQ]G" +4ZTМrCq_4*i*+)ύ(pL uIVCwbiqLWC6L&6&RNpipXn`)bEHLR@L%jGZ@.\C:)ݓxa}(Â9o@8xJ1W"|crG4h}t}0JFK8Ђĉ^ !hI@sМ =hK\#87"B## r`sZ4N2@$HX  +@ + !RH'5`SGB FihPEXW&ɍfL+MRwi0ޒ;3^ٝ(}:"IX r]a YW-G^<: |ՠ_KJ`4y’q8KBA/x涩@zI&c˒+dHi?w4%H%Umy}%Beq@êʋ=VwͯX~8(1Pѩe&+O㿶m-粒|Ӭ{ 3<>1}Y{lJa}uMB8͊и2!XtE?O?y(g-u#}fLna6.uLv~܆9oIXXJy+Yuu䈥} I %XV{GG8 5i)9FGqDnc$>a%! +4250F2~-LD,Bs= +!!v3 !~b["BTek̝>O;"z^OKC 1ÞބbO>-εL 8Y3Wbg[yb cOe;ղXs]J%Gm\}x SA+է.0fxLm1}iFcEA1q 8-{6890iS>G%Hz* H_aML>F`NmAng:OU<:a9҈_C;cpl\CڜEzT_x +`!m̡9c_L9ōڽQeռ@8ɨPPR}5Qs:M"z`Ny E*ÖIB$gHnyu/Nwy"'ˈprkɛ{8N oot1+$I<("7xWvFAd6Ӏ+(wB1.(_~C+M_4s$tgƔ 6-wßpFgmjcm'Kr(iF^[xP0,i_,W)NYVej +H\o<3R@|\-l:,&\0q!?,0!pJ`DpW .`}[gJv$ս8P/7 _nZ>V!:Y25]sC:sk)|ثC[0^L1h3L;*zDWm]ݺIE9yEp`i4kHx 9(C2DP]D=0ݠ&(%TGʊsB$gcْE H>3Akk<' uR+,ߌ`t 5) gR03݈ +(ԗkl.t9?`.?tr(q0spB~q.=-HijJ߂<3&pIHCSS&!\'/kwƸ(j͜~蚙p,)a1-pЮy )jɥ?CB5YCFU*O)r Ĺ!>_aO],9uɚ XdY7GXt2N:ub +t!7Te芄"*b-DO$S1T4T a\Aa +b1t7G%A3cb?S.ǩ_UB!p]Xh6Z?@>  :"Mk /-Roc.ɽ!rQ?2 pDޢ'GCe?T'(tBvc!j| 0bcZ)uOX 6 vagRbP6wlƭykQHyGg$!EO|Nud܌ԚLgi~_ߢQlt($Ԯo+SHQ X!kZ_.8X@k^a3?댥[̠\_TDژ)b-#$_Ld)OPLU58L+?Kpjfy,f:%Hux hR ^*餈%PvpjR̝U*Shm&'0+lU*] ڼg"d .V1{Dm~CE8 L[]20ܾ׭`t("W1Vmߵ8B0:5Zs}eG1Nji R%Ἵw WLa $*UFv3J7x 䅿X8. hk_׻;Yp0Ls[ȓ#ZQU}iDQʭpI]M bݹwBf.ӏڈjJXYF$<nϜρN"84\<8ES +o I j4oc2ZO2N(3+9 r}LSxXS'Yf@e]2zzLY_".%$5"_w# B3iABۆ}Mޑ]ElS<7i8eSkL 6j!JG&cXYՊ,p '1tZ|1jlO[˪>7* 864"C>/-KrU}u~Z>zmƴ~?SG*Eran]4p?ϖ|c23%II>9]%( *y&GegPYF\[,:cxNڎZQΤƦ:. ],l&zhfKsCl'z|4ga2lCPV.} *{ÈtPxM9Nh#h5h$OXʯz'h"zdk#`, +y4a'Z{7%!HoԱ!XW;aU":p\QoqgnP +_rգ mUhu[AF+4c4;i`lB÷pJBj8~ҕM*^ٯm- yB0:׽oaH^}u P6x'(g Up!c6#|Ѫ:reAUjZ*f̉6)*dÞlº[d5 q2H_|u3307}/TTA|n$F8F)Hg G~K>\95Hhj[\u9`87Z^׷PvW`l'L8=#+4@=W jHr7,= +gNW_H/~9C؛M[ؤm\@EwJG8C ,8pN pW2bjIBQi]CLO|c8u/,G ,z|:!|%MBHO>0U%YN! +>{uaFBa,܂m +#>Bئ'!NZ8n@;M F HEiie5EHhpX{z5NլeV82x5#lXnD96a]s 2S59lw8H'dnG#iG[ C?fCSz<9(Q0N!t"{'II h~_[ _) ff4:8 Q3J>&RYp wŦ3H`s_ii69/e:쓲]J4B|c}9R9* </eD /u:QWۿ%w49 f5W0l@meM0!{?@s[o0 G.Ա'޼AjU. 1Wq\TIysXFyqiTB8@ Ĭ+ȐZjǭ\;r2)C2?smľH˟{1檹Ev RfќʫvmWx)yK6esxD'y-Ϙ֮F D`|Րw ٪]תl&4ה7qsZU&k$r\K'Z\*3>6ˡEw.depvռJ9LqB*ȇ!_,>_8D߻6ھ UњSyՙk._8?,ѽ ! $u<ɦQJftjK&M E FD{C l`'(%XT:oP<'l$C& v${fChj ,eE\?k'-[Cyy( PG/wךNyJ"DMEҌ(\Ua Ch73\Ҍ}fdO3L_}xh"|lf}l&TU~}V6qTk<ܘ[t7|oK vH컿d;$_CH6:^Y) 2.)Wo~G0>d= S*Xff{VhTx4sU] ,ny T򥍅Mt1r$p5lcm^ay@6A`?nܿnSc ).e=#ĭI'~;ii9>YADl0.eHS<Ѯ`\N:J}(U Pu8> ѝQzF3{媺f<0UZc ?_3 p0-b4)NLg=i@A/Yܦ>Ĉ_%t9 ($0Ky#2 svOZWQ/5uӄFM}x @3k*$qٯ򦅧_0'h|haK(є~TOҊ8[Dh*;H(`lk><&He +yQ'8_su*U#m׶ƣz:0) PhC0zZQ`VwiLug;rE#u7Ҍ]$|tJݹoIݹQ̤ \Ȼ[1@O +@+y>]E$8^\.PdA%_5_4u%G:nu`NeE80٥W0OWʻ6\[WFAdtu8Ϛ,N2g)"9=z ߍT1h o/s27C=r 2~*ţMd7c8LH ষP^βt;t~杓l^>VT{37DyZrz^y %+ +-#)[7p0) cI M~Lݎ+pRK U^ j  R|1iu8k$6TKd%1C%7Kʍ&pZbHX 3,P.C?vrP’%؏RsiKrY+f2&MP eWAu?z_`"S.,,*D#!zG;M$ F=ۡtG`m3>|N5~J mžn IQ"9@3yJi]15뺐o6Uj>:AVc^+KsuDt*2 /g D%fvx;j&a0&KQb#P[I?]A(;vTޢ8< F3,iI4|cjeb&@xu ϶٩ Z6)7]~A,KK jV " DnE?pZ'嫼 +%4jNEPm3c~D'ھ]HF,Ie,|ƕ'=YG3kY=?1J & a, +@RJv2J_i׃xRt [D cJ.PDQ*fJll^lMZ#A~T$\,kwqC3jw12LP~m0'6jKfel՛Ÿ ySX{\<.gc}nj@ .\~qzFDXv@w![$up'&b- 2.gڀJ7.ChG~6Ǽ + >8S) FC?P X4͝]b7jZW /y#J,A8 +;:5lé:SA|r3<3IbZwݢ`.ԌCovLNT(O>=趻kE?8rO'O 55x `#*-롴,I-S¢P14ya&e6[Pn1KWTpΤ΅B~I7h{rh51q}:xFΰ +mz_t(q[nޣtw96-d+Cݞw Zi#7UވE~q +VXdSRPvO| 3aAҬ zƊOD#K*_ U(l6 W +endstream endobj 17 0 obj <>stream +-PJ}'fOP}f6Kj%LJЄ";hE-㻇5ZoDv]t3TcR¨6f_$藰(>nIԫH-zoLKZJдTNGf ;<+ >P=f,N 42;$ ;3`ne~Wxmp$b> %|ˆu"`1jd<*w9PziԙZ!ڕ;YD%s-LqV˦zF[0s*ZadM4("""A+c ?蚼B&h6rN +G#x!H!o;``ZځYshq ?6Ekb#,Lfv0|{pu43 ẫcčZse4Wȸh J^lkBqLXDֹ7ke"_g\}HwqP&iiH&{0`sCLv!R-3A:b65'c +8Ss9`^$ƥ.%t.20 <'6\ +'W<ۈ?2v*\%QMnsgx3EcOE""f$1 6ɍ"V/<Y"ji Ԓq x7 d6P;->#,! +˰g a7.]Vvq +ώӥbIsUlnci48ZY8@p륭S3[lo2WVf.qw&mO&WtZ 4_J:vnn D]>re[6F)+jnp2B2٠T K;/.+bB8T5PQJK0,\*uA4:}4*V#6KCÂq {.}/* qw:30: C2%gV! +3a2>iWdSP/DFV?wKDwd fL]JQ7["Ng8Jn\~*L5( <pX!s߉0N#hFx^W~V֜>^ձ]70;kaqM `tˬa +Lh3 +W2,zE0e {CW8!awAi޳$OT2fcxm6꺌k$AChqEC[V(P7kEsmHDa +xPɿc53aԊ1=>7hAGZu͹QR96epyF ٓMFԸ#H RPSi6b*@PQAFkGY( ]W>?&/OY@αU#=%?T[*S^]٧dC9˕6qh|Mhvèq+bt8UxFXo/.mw3?@" Fi6Uqr(a!Q8E+r =m5^&IH'8 B 8<^7rtbk(*'<3@$)h.B$}00\FhP\/, "TZERv4,r?)3%4HMN 㘾Io2UYY7Ե:"hHX(^'] Z7BտL#(X([3CWO )ωu,7ӧZWk%]/9Ơ2p`.<- AϢւF-0L^Uǡ!D:Z%dF'd;`c#Rtk0 - +EJ]*YfB  Ҷ0@)oA{މقXO" @(:qɚC3$Gэ*\>m?{֩?4r5@ +gH=q驓qjeRq'҂o?yNjů½V>H8tk})8jy +N̍ ]1C% 1Dlt-ݖ&hT +n,=-xj'&<4mKK"d6mĖ j̛~9^b7ejd"$ azO?2Ɉ=r^Q) V`<9|m B/\r*dXHAa=p;]}Y"eK ?m@sYKr%rh<9ͶD˥Mƌ !Ɍ NFtFL;3]p"=!qz(t]3[XG O U .يԥK"$@Ì4.SEytG و+a1AldOﱱw^(c2 r  +#w$(Y5H^x|݈~_9c>-5.:!uj: ăKʉ<αildy+MeiyD@öf:d:rjL$* :Qea{F, ?ϭ$oo9,\b0b"Ggrr +'ހ Kd̈́3.C҄29%K15 ̓Yi°Wn j3GD؛Kq[ԨV,}{৚{- Yim:%KPEmUnǀW0p%@yd1#77RBJDHV#_wKEF2']Jn~҈ I L(<ɈH1 + glwH`$Uy&EnloqxhCp/h#"h'rʮlz{uZϊzoZ,~ OA;7O̶s>K>ЂXU%D2W`2x.fb'|p#KYjF+2Yy’4>$; s[?Qٹd$ 8UP94G%Xum"?$2"& "B>a1NaTI%afx֙tcTp7miu2R=4ģiS/UErӗQN.<\mf˙07c"mMpvA5C цR&3P?*vB%,k$ p>fn3= *#Ep@T*EL߇t*+KNjpBS1 !?w thK~ I39@ \jז< be>Xvw@ge[|'"UhDм{mN 2! \#̙f2EᏸfEMtAFkF7Rvr"iVT|Tgi_vk {3/V] 1MAkE4V PO =9zLA@'UG;ڍL6&~Z&ItH?l?,ٺa74yX@(y7^?v#J k0=4yM^41h1bx0RIU c]d(IӨK-8:R{au," z~m;E +8FnG!9qFjH\!4Іdn3Nǡ`Tp.^;\UcH#)@2 4a63* 8@CK22u@1p IBٶqF+\4TT< |m,l" ᾡ .z~"Ü + R@ ᔎ0rL u\qe<\"3vFL@eXXcSi/WG2e#cl$SWAАW7m )@M^,q3@s2-FTǂh,qYbd!-/”e&1>)j.ӸLh\.>6B.<^Tfc#p`X"ViT\:ņNi#G OcC<Rs0\Hf"Uy\GD8I {*@ \Tp?u~ xHb4pyةA !L8N17FdzDB#"2Mf`3NX* P\(^.<dLym3"`bj@pzsr@*!Q3Ђr104`ՓtџxG'LGׂepW@=Ugvt[Q.jfy7=inRi`h @A(U!lryxĒyo2-#Uɒ" +PdI!;rʂ gp 2Ft2@5\\$t4%e&4 a:8WD Sl4 +LLihDghqC&MaCtgB06y\HĆFUP!@ДFy1YJ݌6!Q:c`0cDbŪ9| dWB& AFб|' ehHD(d,/̄|#%RlBt\PJF*tx(<DG&J83Vd.xXQo2}n;ʹIMRJ޶Qd3#Q1Y1cWJ,4o%FVeNQC`Ls6+;D$֙ {FrK 4"L*m(jؽƪ\201԰U\u )?]- +5*!Pp= +Ţ+f i#s ՞#HTNAŧMmP*N2S\.crџvU)jPRc92e@oϟ~}NO_o{ߗvCs9_%AjV4[LmŶ}zw׊eJ}.4@-b}`D0"ikPuHU\. :.T>|ztk\~ygiNZ{9JǸs+qqL|'\?ߊsƶz7ksʌ~ln^9si.u]m>o[i1~z|1mM>-4ԱV){V^ٛ>Lk;9J)4B34լ+4f^4i{--:Ch6ϟv^i 64'B8TZ_[Bgc9xҷ~zqYSOj^BfgWK4U3 S>4{vukdz/گL=};KRJT(zҐ 2Dg=|dH(qJD]X4 ! R@tqևd9]D'F;GӠD'CMZ A +|ZQ)M$8wO@hQ|3K +& >=LpU!%@@RU>Fхp34h\ +FG(l `VFc34ՀT *2RfYL0ӷhA hNc)a&or 6Qh8!?Rsџv^[6`+H(G$XQ2(Qpe +# +@G zu`drbXGadkHV. +FKԑ`wN܉;q'ĝwN܉;q'k;`HwN܉;q'ĝwN܉;q'āN܉;q'ĝiwĹ>HӳΞgIN,R FLeg\p`2\.F-Z iblZ MGiri40k,5z>|'4c*[siӯSœ~fnj/RƸvӊom4P.1{i9ߗ;Aff_cۯΌV;zs3Nwڞ!M1sS>[zY1nl8cjicl\=ۋY|z1]]? ]sm[)oxgKy1~y~?-Jjzڛ +qsKqvAZbv9qN=㶴:;[KnfYqKyJOٷޖvb)_mwb>[;ۖnϜljSin뤔Zu^忿g\o>_>?ݛNwS?9_y/ԳԽ[JK'O/{Ͷ}\JoS*i_{lRk%\64nq|Ү>=S)N-^?m-OK)vkoSvn{OLҧb;JLsZҮq|ܴRZO%[[{im=eWvZmwv:;J/uLϳ^.-⚱|-q˶M'LgS[RO+ŐL_Jim<3~Mo߮_}j.u=;ۮ'$[LS9 :ӻbzksd[Jwf[=qnoo1rۚk֜%ŏ;O}Nk[s.mi>3d6-i9W{8C&vk[:ˊsv+>_[+/- oͲ;6ι~؟i9Cc~o *+}uNi)ΐYmg[kdjmŕfYi~:۱2gȝykiޯ7ڧ|Bbfhd#00rh BZ&D2. Eqh + PCAQ e!uFFVkS +ZC27QETti/ۚIѨ/܅Go_@ڈH`xB!f +v8I146oܧ<.F4Υ0ЗrXy-Ƹ+H.zoyKX-?ol!??%]͉턑R3˵eZ_V.:!La߷A-nƴ|!-o:Ne“.3}"["].EAK@|LX#vg͌#?)\E.]" \cEnTW.VG1]xC R. \_p.FxFs>X.׷\L2 ڕsn}nI+6lPzܝ03A0ю}н0,+uʯrr?iJ PO {,p=XkT'9`76+ E$e>BU6]S:ºQ26eVne1v]@ثB_' xK[~x?rn INDca`Fk vFfd4 JdEtpX{IV-! J.>[)`E.!AIKW FB~ QK5̳!mz\&LyPŜ`:H@7wWRBɇ!“QR)N}=-^qݻHsbyg .6OH)"P>Daκ ڀ^b_N \zNQvxJXrP7Db$ $cs1d.VpнE0U(Ϗ72IqӻCQW.q?G-`7n!\A⠅1}||҅>閿8r~}sܲaP ڝ10>] ^֨hun';:)dkf!H.b~HMvofs7~P4rzIzּFJQE#X(t=MAG,K:5 UڠG=]9eT̸wNvL ۝kFJ?b+؇Tɒ[,ăKGOI".2Om06`~&@$ aAH$5aUQ} #$;zaEqdWZk>wp@Hw|=Tp%]lr9*m/U3, Q>KSbqwՕP,Yi$"JaaipZL5gfC00ЋZw%]§/i-yA1Bde_)SGU#ڗbt[H[Ce\P!&pNG2xC_c/ݟaG](8MBݡܛc^UzV&T 71W5*S&6 p򓉍c~ށleP*eӾkiWa%_A2[g@^V)GpV:aQB_zNF1kZM +q>{V@< OZLٸKJ /*d[6]:p.r@ תDu$_ԷpnuMzd`Fl7Md+kA"ͯv":طHC`*?Eۣq(zj4mf^^ȝc&rtAmOp!}\ޗRf«&ΰU0_1"a7K;_zt~?4 y_|lPҷ6A)L ͎[?ܬLp:njY26\ij= K*"KB^N;fPe`Sk{D~`iǂc;2 MJM&NI!8̅]fm Y37ƭ+TpY$].T#?^̰Ǟ,"7OjF[m&|W)HoҒ)M_$O0ԅoB\}wMU=Ls*(?}_ck(7ӟ׵HQ\ OvsAvQqs+.T. i'>7kkNQRΑԯB> tbw)a,}IWhFw[$2aZfWx! >ô_R=Tbeq?Tm!N|VŴr~sfxg68~^#B$C!OS[ۧ SGv3O>eAwF078~<5}r\{"1a4Ƞ7=L\ +<3*,!zZIP J,rL#ͻ:4ތu#Xݩv{/G'78K- d/N&bs9ҡffp^:5؈@Wo]3!@7Lf5ݘ_E|u$É-9G_'=yblkцݟn%ɗk>p߬gqIӎ3Btxyq, D~[Qbĺwl)_`Kc_5N 2ق6#/O}ȑN"vM,rZ!ވ zKtG.""5twѢG`` U-zV2CxƁ!K6(\D 6_NmFlVWo]Cy3Tkby47cʌ z.l;kYE@5jx T)" Sc3^yZBl;0v a<Χ`!l°<{4{6^qsLgUV"Ev?Ĕ9_ .t`ĎyOL+Oi(^ǵX{o$1<:մ~G}V R0nuvdG]4Xاyz@/3y?O +c\ަ}Q? Loܥ~I 0o,j$r%z*Njvx,csOy)e`E1GE#y-'Ld ũMn9)Q3}o JoܚYz=TH,tYX +ޒ h 䝍EF4Ub-#؎¿ip%!<5G<'|F8<8ݛT}8 .ֺv\Ajwu?Rq kvXM~DK0R#цp,$Fڧ@aԦ#Cgwz,*d\Op9 +좎LDӶu4PE"o874UÇ]cf@!д8pSBqVӷXГj>)b5U9BU¤RԀ*IY\QTlN?8z*qN"mMu@Vx{"pLAWHm__H@$g8g9#Xa$h-̺ $wn9CA!) !0H}͙"ǿCxbBΟY\*ݥ@pNFWH$RTV2<1'(a RyEgS/c`A}!pe!iެɔy f]kCQ̶x*+dOPWO*"uUZhƵGP]$b7 ,ߵT)*Mpy*h2x*|dt:J(v),N՜Dե ۰HX}O{5⴫/Q>Q>y>kd;/[{Qv"ao} $ml}R,g#҇gy3 }U67,>Q~ Fu0s<#F7)K1F`q2Naq0acň'&6RD4ԡ"oӆ(3ඈ00ϐG2g~~|"_栱|NJE.QmS}\Y8a\7@:. öDqޣJ[u&s39Emу{dz9ϡ[뒼lOm{.Bˑtf -w^3`׾vbfPOu(Wq7ZdujyƇnآj zg:xrP9@ey  F|i:QK4ٔobfQz{v`Çp,W*M]Hi}j!bڒZR1 g3kqC`GU`KOo WH򊫑^RE OSikU|[.9',KęXL`,QO aW!tpm.tn{~ IVz;?>;wUDչ|vR(NKdvjff8d_= V44Rc_jMA+kQFOQ\] 9:ٹf̄_{D=UPH+֪ϼu9𜪮G6\.R$.TJU@%ϦB "e(>fpv27(^ܷN*˿j:3aҖ2դgtp/,摡ΰe5!mOnMH֬? cU0(L)hL}u5Tĉ6]dx{BBJԐdWL jQ۔;z+ p4" Q׺ްA-xm3"Rh@TB |2'w ^%G-Fc5]s:>intnXDXh|} 'YR" ے)H vjm(2B`|@HU(ɈOҔĥ `Uоz\A]izM%A$$'A۳ 7Bd##]X r* h&eNȁ +WFJ/vk +3d'2]Q +*VI%]hXb7$݃ Jmֱ6j8T!fCY2fRCV.I qmF^MTYѬs{RfB +%퓉$;$Ge7 ,>mX|K:TZp ǑtQ-L⿶-z\Z w%Sqj}biaYҾnf~4%Ãjuk.u]ȷ4}.)I x:y-eωyh_{D;$>LzAC8-M~X_+`Yx4dbrϾ!5 čG c2ӭ9DM[|u)kX|L?D_T"Vuڞ9"<8xͷ%.V(k%EZr^9XY5,lծ5"- *A cb+?;|飗 ]'HR Q7Wf"Zh LOnwhժ4%K"vyIn6M( + Fcs֏]MQt]c 9(ū>zBqlȠQ la2|23LF?J)7+ؽB5esY m8Dt'^^*͕R3WԨOMS1 &>DEzI8\Lt ‹`Xs+ķ\ $ k\z=) | +.jrPХ$]2'6D TII5+7$ul6Xi>{lI,O3wDC"P ƴRW#]2L7#j RݍN{ A N")A"[FflSf +s&PG( *% \TmuT?DoX]3B?.Cg%_+C 4Lyt7P9a +4E0LsVߝp*d5 qbꁣJ + Du_@+59AGy8&vGMToS?eOchIz|rpztD%;~M*< $KEFYZ;w}(LwgOdbQM9^JΖ4s)qȃ¶=ʽ&Ʊ" Yƥ?y`1US~(s)6% Qq1Iz5'>hG$gzY4>D@|wގTV?]]a揖~8$>񻧟C8,8dB̾.p`A"Q.R0{; 3U h܍`s<@!Ӝİ:Y' ~axLbLKEGgm y+3uHr/me聀F~|*5Ff~v7]]gg4؃%Xvןmx:2W~L [6?mE㇢ߜ] #=)^*&"eILrڐp4TȀ]!v8LEr=ob˕yu2;ūt#D i1P-BI Bb7i[C1RV#]S~dwj$z'͜ ݡocL'YK6iI`XT +#Zq=v/r7 +& *q5@4+"7x+EVeo73bΉ;2v Y{m7 H -Y,@'H<<4;`Qݫ$Qi Ico,Y@9UkU\ߢABɝ8+*H%D`/@ojcۻnji_<6@V_.ZE‰;W÷h[x}hο f -hGod72xo8o{ |;J`PzjB+ +{e2>Ggf_M0} h`ZSSse^w\zC:V\t ) _rcIƲR E#]!j"K%EČ*ݼ__ar":dBR$iH _I&YID!%LB#ݙbdLBh«'InI;a'H9 kN8vN7- [{% ;LCv;Z}VMj"vAM[j *o؅*"<#CZBgUQ*Q0-yQcȯ&ĞKvsc*8yKV!gK&Jc2l +++k'+a'(X[h. FM;ʛr+C^CS(t;J =XzC Y޸qJaa:3"JƗWrO0åV;o;iLe|tT\`ʂJ,k/4]W>>V +V*sarV5$a4Sj(-Z<\WlPe $3rPWmHE(6T*nw&HwWB(DoP7 +kZ#MRɘe("*f@*)ģlUvl+V6";zz4J$ +uc${c6تTI2I'ׅc\B^qSY^FȀ~F$m4  _xbNxe[R(MuCe[6u{@t{p:!LuM7Zg|5b͓Hx䌶x675E""qHYA6Y^_(P^*@T\p۞Pq*{_{~W(ml`ʜZxB)|hP*[VyIxKŬiA%&)ޓ7@m0 @c5luiT*wC ZT&WFn wєzNMd/թ2awήU5ν8r\jCbO4h"}>{ejD!=Lm{ y׭<{>J +EF@WhIpZ[rPp=ԧNnL(R %,ġr`->$* +endstream endobj 36 0 obj [/Indexed/DeviceRGB 255 37 0 R] endobj 37 0 obj <>stream +8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 +b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` +E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn +6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( +l[$6Nn+Z_Nq0]s7hs]`XX$6Ra!<<'!!!*'!!rrmPX()~> +endstream endobj 33 0 obj <>/ExtGState<>>>/Subtype/Form>>stream +/CS0 CS 0.944 0.954 0.961 SCN +8 w 10 M 0 j 0 J []0 d +/GS0 gs +q 1 0 0 1 442.843 238.3426 cm +0 0 m +-15.683 -27.165 l +-47.178 -27.091 l +-62.99 0.148 l +-47.306 27.312 l +-15.811 27.238 l +0 0 l +h +S +Q +q 1 0 0 1 204.6208 143.1162 cm +0 0 m +15.811 -27.238 l +47.306 -27.312 l +62.99 -0.148 l +47.178 27.091 l +15.683 27.165 l +0 0 l +h +S +Q +q 1 0 0 1 431.5853 11.4572 cm +0 0 m +15.683 27.165 l +-0.128 54.403 l +-31.623 54.477 l +-47.306 27.312 l +-31.495 0.074 l +0 0 l +h +S +Q +q 1 0 0 1 294.4866 173.1122 cm +0 0 m +-18.457 -31.969 -6.844 -73.227 25.938 -92.154 c +58.72 -111.081 100.257 -100.508 118.714 -68.54 c +134.884 -40.533 127.975 -5.396 103.972 15.609 c +100.577 18.58 96.839 21.269 92.777 23.614 c +59.995 42.541 18.457 31.969 0 0 c +h +S +Q +q 1 0 0 1 325.0405 155.4719 cm +0 0 m +-8.956 -15.513 -3.321 -35.534 12.586 -44.718 c +28.494 -53.902 48.65 -48.772 57.607 -33.259 c +65.453 -19.669 62.1 -2.618 50.453 7.574 c +48.805 9.016 46.992 10.321 45.02 11.459 c +29.113 20.643 8.956 15.513 0 0 c +h +S +Q +q 1 0 0 1 359.4413 148.5375 cm +0 0 m +10.619 18.393 l +S +Q +q 1 0 0 1 371.1792 110.679 cm +0 0 m +-11.352 18.443 l +S +Q +q 1 0 0 1 342.4347 139.1691 cm +0 0 m +-21.649 0.62 l +S +Q +q 1 0 0 1 220.4379 115.8778 cm +0 0 m +7.407 -37.351 30.63 -71.733 66.706 -92.562 c +102.577 -113.272 143.701 -116.285 179.606 -104.266 c +S +Q +q 1 0 0 1 393.8267 266.3006 cm +0 0 m +-59.824 19.518 -126.482 -3.692 -158.449 -59.062 c +-165.272 -70.879 -170.031 -83.333 -172.849 -96.021 c +S +Q +q 1 0 0 1 447.0169 39.0557 cm +0 0 m +9.78 8.879 18.356 19.374 25.294 31.39 c +57.566 87.287 43.776 157.415 -4.13 199.337 c +S +Q +q 1 0 0 1 430.1418 189.9215 cm +0 0 m +25.677 17.19 l +S +Q +q 1 0 0 1 436.5098 178.6974 cm +0 0 m +27.842 13.447 l +S +Q +q 1 0 0 1 441.2536 166.7061 cm +0 0 m +29.466 9.443 l +S +Q +q 1 0 0 1 444.2809 154.1808 cm +0 0 m +30.516 5.255 l +S +Q +q 1 0 0 1 445.5329 141.3655 cm +0 0 m +30.973 0.965 l +S +Q +q 1 0 0 1 444.985 128.5094 cm +0 0 m +30.826 -3.344 l +S +Q +q 1 0 0 1 442.648 115.863 cm +0 0 m +30.08 -7.588 l +S +Q +q 1 0 0 1 438.5674 103.6722 cm +0 0 m +28.748 -11.684 l +S +Q +q 1 0 0 1 310.6307 118.0083 cm +0 0 m +-14.554 -7.029 l +S +Q +q 1 0 0 1 305.914 137.5234 cm +0 0 m +-16.191 -0.504 l +S +Q +q 1 0 0 1 309.5551 157.2273 cm +0 0 m +-15.028 6.108 l +S +Q +q 1 0 0 1 321.4745 174.222 cm +0 0 m +-11.039 11.886 l +S +Q +q 1 0 0 1 307.1458 127.8552 cm +0 0 m +-15.731 -3.713 l +S +Q +q 1 0 0 1 306.7646 147.9286 cm +0 0 m +-15.916 3.012 l +S +Q +q 1 0 0 1 314.5842 166.3773 cm +0 0 m +-13.35 9.216 l +S +Q +q 1 0 0 1 329.8993 180.3893 cm +0 0 m +-8.204 13.994 l +S +Q +q 1 0 0 1 374.852 95.6871 cm +0 0 m +7.07 -14.523 l +S +Q +q 1 0 0 1 362.037 91.5891 cm +0 0 m +2.788 -15.933 l +S +Q +q 1 0 0 1 348.6091 91.181 cm +0 0 m +-1.71 -16.108 l +S +Q +q 1 0 0 1 335.6083 94.4943 cm +0 0 m +-6.075 -15.036 l +S +Q +q 1 0 0 1 392.4974 167.2558 cm +0 0 m +13.016 9.582 l +S +Q +q 1 0 0 1 398.8436 155.4035 cm +0 0 m +15.177 5.629 l +S +Q +q 1 0 0 1 401.6691 142.2796 cm +0 0 m +16.161 1.24 l +S +Q +q 1 0 0 1 400.755 128.9009 cm +0 0 m +15.894 -3.245 l +S +Q +q 1 0 0 1 372.1512 201.8157 cm +0 0 m +-4.128 -13.097 l +-0.415 -14.227 3.224 -15.79 6.723 -17.809 c +32.064 -32.44 40.444 -65.366 25.441 -91.352 c +24.105 -93.666 22.627 -95.844 21.027 -97.888 c +32.486 -107.014 l +S +Q +q 1 0 0 1 275.2839 193.4857 cm +0 0 m +-23.488 16.337 l +S +Q +q 1 0 0 1 368.392 246.075 cm +0 0 m +-43.231 6.616 -87.586 -12.632 -110.29 -51.956 c +-112.506 -55.795 -114.445 -59.719 -116.13 -63.702 c +S +Q +q 1 0 0 1 333.0875 258.7783 cm +0 0 m +4.777 -27.605 l +S +Q +q 1 0 0 1 287.4663 64.8066 cm +0 0 m +5.07 -4.447 10.635 -8.486 16.709 -11.992 c +S +Q +q 1 0 0 1 304.1755 52.8143 cm +0 0 m +6.074 -3.507 12.354 -6.308 18.74 -8.475 c +S +Q +q 1 0 0 1 317.9897 29.2883 cm +0 0 m +4.926 15.051 l +S +Q +q 1 0 0 1 361.752 21.8364 cm +0 0 m +-1.159 17.144 l +-13.691 16.574 -26.468 18.307 -38.836 22.503 c +S +Q +q 1 0 0 1 276.8943 53.0148 cm +0 0 m +10.572 11.792 l +S +Q +q 1 0 0 1 294.3333 35.7671 cm +0 0 m +9.842 17.047 l +S +Q +q 1 0 0 1 248.5597 87.188 cm +0 0 m +15.426 7.568 l +21.199 -3.57 29.088 -13.768 38.907 -22.381 c +S +Q + +endstream endobj 38 0 obj <> endobj 31 0 obj <> endobj 30 0 obj [/ICCBased 39 0 R] endobj 39 0 obj <>stream +HyTSwoɞc [5laQIBHADED2mtFOE.c}08׎8GNg9w߽'0 ֠Jb  + 2y.-;!KZ ^i"L0- @8(r;q7Ly&Qq4j|9 +V)gB0iW8#8wթ8_٥ʨQQj@&A)/g>'Kt;\ ӥ$պFZUn(4T%)뫔0C&Zi8bxEB;Pӓ̹A om?W= +x-[0}y)7ta>jT7@tܛ`q2ʀ&6ZLĄ?_yxg)˔zçLU*uSkSeO4?׸c. R ߁-25 S>ӣVd`rn~Y&+`;A4 A9=-tl`;~p Gp| [`L`< "A YA+Cb(R,*T2B- +ꇆnQt}MA0alSx k&^>0|>_',G!"F$H:R!zFQd?r 9\A&G rQ hE]a4zBgE#H *B=0HIpp0MxJ$D1D, VĭKĻYdE"EI2EBGt4MzNr!YK ?%_&#(0J:EAiQ(()ӔWT6U@P+!~mD eԴ!hӦh/']B/ҏӿ?a0nhF!X8܌kc&5S6lIa2cKMA!E#ƒdV(kel }}Cq9 +N')].uJr + wG xR^[oƜchg`>b$*~ :Eb~,m,-ݖ,Y¬*6X[ݱF=3뭷Y~dó ti zf6~`{v.Ng#{}}jc1X6fm;'_9 r:8q:˜O:ϸ8uJqnv=MmR 4 +n3ܣkGݯz=[==<=GTB(/S,]6*-W:#7*e^YDY}UjAyT`#D="b{ų+ʯ:!kJ4Gmt}uC%K7YVfFY .=b?SƕƩȺy چ k5%4m7lqlioZlG+Zz͹mzy]?uuw|"űNwW&e֥ﺱ*|j5kyݭǯg^ykEklD_p߶7Dmo꿻1ml{Mś nLl<9O[$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! +zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Km +endstream endobj 26 0 obj <> endobj 40 0 obj [/View/Design] endobj 41 0 obj <>>> endobj 32 0 obj <> endobj 42 0 obj <> endobj 43 0 obj [0.0] endobj 44 0 obj <>/XObject<>>>/Subtype/Form>>stream +0 g +/GS0 gs +199.999 285.819 304.583 -286.226 re +f +q +0 Tc 0 Tw 0 Ts 100 Tz 0 Tr 8 w 10 M 0 j 0 J []0 d +0 TL/Fm0 Do +Q + +endstream endobj 45 0 obj <>stream +H63Ԋ $Z7 +endstream endobj 46 0 obj <> endobj 47 0 obj <>/ExtGState<>>>/Subtype/Form>>stream +/CS0 cs 1 1 1 scn +/CS0 CS 1 1 1 SCN +40 w 10 M 0 j 0 J []0 d +/GS0 gs +26.533 -58.744 214.446 374.503 re +B +q 1 0 0 1 166.3662 14.3408 cm +0 0 m +-4.582 0 -9.164 1.738 -12.674 5.219 c +-19.733 12.219 -19.781 23.615 -12.781 30.674 c +19.746 63.476 l +-13.056 96.003 l +-20.114 103.003 -20.162 114.399 -13.162 121.459 c +-6.163 128.518 5.233 128.564 12.293 121.565 c +45.095 89.039 l +77.622 121.84 l +84.621 128.899 96.019 128.947 103.077 121.947 c +110.137 114.947 110.185 103.551 103.185 96.492 c +70.657 63.69 l +103.459 31.163 l +110.518 24.163 110.565 12.767 103.565 5.707 c +96.566 -1.352 85.17 -1.398 78.11 5.601 c +45.309 38.127 l +12.781 5.326 l +9.263 1.777 4.632 0 0 0 c +f +Q +0 G +8 w +q 1 0 0 1 166.3662 14.3408 cm +0 0 m +-4.582 0 -9.164 1.738 -12.674 5.219 c +-19.733 12.219 -19.781 23.615 -12.781 30.674 c +19.746 63.476 l +-13.056 96.003 l +-20.114 103.003 -20.162 114.399 -13.162 121.459 c +-6.163 128.518 5.233 128.564 12.293 121.565 c +45.095 89.039 l +77.622 121.84 l +84.621 128.899 96.019 128.947 103.077 121.947 c +110.137 114.947 110.185 103.551 103.185 96.492 c +70.657 63.69 l +103.459 31.163 l +110.518 24.163 110.565 12.767 103.565 5.707 c +96.566 -1.352 85.17 -1.398 78.11 5.601 c +45.309 38.127 l +12.781 5.326 l +9.263 1.777 4.632 0 0 0 c +h +S +Q +/CS0 CS 1 1 1 SCN +60 w +q 1 0 0 1 166.3662 14.3408 cm +0 0 m +-4.582 0 -9.164 1.738 -12.674 5.219 c +-19.733 12.219 -19.781 23.615 -12.781 30.674 c +19.746 63.476 l +-13.056 96.003 l +-20.114 103.003 -20.162 114.399 -13.162 121.459 c +-6.163 128.518 5.233 128.564 12.293 121.565 c +45.095 89.039 l +77.622 121.84 l +84.621 128.899 96.019 128.947 103.077 121.947 c +110.137 114.947 110.185 103.551 103.185 96.492 c +70.657 63.69 l +103.459 31.163 l +110.518 24.163 110.565 12.767 103.565 5.707 c +96.566 -1.352 85.17 -1.398 78.11 5.601 c +45.309 38.127 l +12.781 5.326 l +9.263 1.777 4.632 0 0 0 c +h +S +Q +q 1 0 0 1 257.9834 192.3545 cm +0 0 m +13.972 9.191 21.286 23.716 21.286 37.704 c +21.286 50.305 15.486 62.267 5.375 70.523 c +-4.686 78.738 -18.538 83.08 -34.686 83.08 c +-229.804 83.08 l +-239.745 83.08 -247.804 75.021 -247.804 65.08 c +-247.804 55.139 -239.745 47.08 -229.804 47.08 c +-202.603 47.08 l +-202.603 -25.345 l +-202.603 -35.286 -194.543 -43.345 -184.603 -43.345 c +-174.661 -43.345 -166.603 -35.286 -166.603 -25.345 c +-166.603 47.08 l +-108.077 47.08 l +-108.077 -25.322 l +-108.077 -35.264 -100.019 -43.322 -90.077 -43.322 c +-80.137 -43.322 -72.077 -35.264 -72.077 -25.322 c +-72.077 47.08 l +-34.686 47.08 l +-24.373 47.08 -19.426 44.298 -17.396 42.639 c +-15.616 41.187 -14.714 39.526 -14.714 37.704 c +-14.714 33.352 -20.137 26.447 -34.686 26.447 c +-42.025 26.447 -48.63 21.991 -51.377 15.186 c +-54.124 8.379 -52.465 0.587 -47.182 -4.509 c +-12.171 -38.278 l +-8.678 -41.647 -4.175 -43.322 0.323 -43.322 c +5.039 -43.322 9.749 -41.48 13.281 -37.818 c +20.183 -30.663 19.977 -19.269 12.821 -12.367 c +h +f +Q +0 G +8 w +q 1 0 0 1 257.9834 192.3545 cm +0 0 m +13.972 9.191 21.286 23.716 21.286 37.704 c +21.286 50.305 15.486 62.267 5.375 70.523 c +-4.686 78.738 -18.538 83.08 -34.686 83.08 c +-229.804 83.08 l +-239.745 83.08 -247.804 75.021 -247.804 65.08 c +-247.804 55.139 -239.745 47.08 -229.804 47.08 c +-202.603 47.08 l +-202.603 -25.345 l +-202.603 -35.286 -194.543 -43.345 -184.603 -43.345 c +-174.661 -43.345 -166.603 -35.286 -166.603 -25.345 c +-166.603 47.08 l +-108.077 47.08 l +-108.077 -25.322 l +-108.077 -35.264 -100.019 -43.322 -90.077 -43.322 c +-80.137 -43.322 -72.077 -35.264 -72.077 -25.322 c +-72.077 47.08 l +-34.686 47.08 l +-24.373 47.08 -19.426 44.298 -17.396 42.639 c +-15.616 41.187 -14.714 39.526 -14.714 37.704 c +-14.714 33.352 -20.137 26.447 -34.686 26.447 c +-42.025 26.447 -48.63 21.991 -51.377 15.186 c +-54.124 8.379 -52.465 0.587 -47.182 -4.509 c +-12.171 -38.278 l +-8.678 -41.647 -4.175 -43.322 0.323 -43.322 c +5.039 -43.322 9.749 -41.48 13.281 -37.818 c +20.183 -30.663 19.977 -19.269 12.821 -12.367 c +0 0 l +h +S +Q +/CS0 CS 1 1 1 SCN +60 w +q 1 0 0 1 257.9834 192.3545 cm +0 0 m +13.972 9.191 21.286 23.716 21.286 37.704 c +21.286 50.305 15.486 62.267 5.375 70.523 c +-4.686 78.738 -18.538 83.08 -34.686 83.08 c +-229.804 83.08 l +-239.745 83.08 -247.804 75.021 -247.804 65.08 c +-247.804 55.139 -239.745 47.08 -229.804 47.08 c +-202.603 47.08 l +-202.603 -25.345 l +-202.603 -35.286 -194.543 -43.345 -184.603 -43.345 c +-174.661 -43.345 -166.603 -35.286 -166.603 -25.345 c +-166.603 47.08 l +-108.077 47.08 l +-108.077 -25.322 l +-108.077 -35.264 -100.019 -43.322 -90.077 -43.322 c +-80.137 -43.322 -72.077 -35.264 -72.077 -25.322 c +-72.077 47.08 l +-34.686 47.08 l +-24.373 47.08 -19.426 44.298 -17.396 42.639 c +-15.616 41.187 -14.714 39.526 -14.714 37.704 c +-14.714 33.352 -20.137 26.447 -34.686 26.447 c +-42.025 26.447 -48.63 21.991 -51.377 15.186 c +-54.124 8.379 -52.465 0.587 -47.182 -4.509 c +-12.171 -38.278 l +-8.678 -41.647 -4.175 -43.322 0.323 -43.322 c +5.039 -43.322 9.749 -41.48 13.281 -37.818 c +20.183 -30.663 19.977 -19.269 12.821 -12.367 c +0 0 l +h +S +Q + +endstream endobj 48 0 obj <> endobj 35 0 obj [/ICCBased 39 0 R] endobj 27 0 obj [26 0 R] endobj 49 0 obj <> endobj xref +0 50 +0000000004 65535 f +0000000016 00000 n +0000000147 00000 n +0000015116 00000 n +0000000000 00000 f +0000015167 00000 n +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000017159 00000 n +0000000000 00000 f +0000017232 00000 n +0000017472 00000 n +0000019205 00000 n +0000084794 00000 n +0000150383 00000 n +0000215972 00000 n +0000281561 00000 n +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000314469 00000 n +0000320614 00000 n +0000015606 00000 n +0000016665 00000 n +0000311776 00000 n +0000311663 00000 n +0000314656 00000 n +0000307484 00000 n +0000016727 00000 n +0000320579 00000 n +0000306909 00000 n +0000306957 00000 n +0000311600 00000 n +0000311811 00000 n +0000314540 00000 n +0000314571 00000 n +0000314771 00000 n +0000314844 00000 n +0000314866 00000 n +0000315193 00000 n +0000315344 00000 n +0000315421 00000 n +0000320507 00000 n +0000320639 00000 n +trailer +<<52C01D6FCF06EF4AB4FAD4A3944F4582>]>> +startxref +320821 +%%EOF diff --git a/data/tr1/logo.png b/data/tr1/logo.png deleted file mode 100644 index 0d6c7cd52..000000000 Binary files a/data/tr1/logo.png and /dev/null differ diff --git a/data/tr1/ship/cfg/TR1X_gameflow.json5 b/data/tr1/ship/cfg/TR1X_gameflow.json5 index aa7898d49..b338a778e 100644 --- a/data/tr1/ship/cfg/TR1X_gameflow.json5 +++ b/data/tr1/ship/cfg/TR1X_gameflow.json5 @@ -8,10 +8,12 @@ "savegame_fmt_legacy": "saveati.%d", "savegame_fmt_bson": "save_tr1_%02d.dat", "demo_delay": 16, + "water_color": [0.45, 1.0, 1.0], + "draw_distance_fade": 22.0, + "draw_distance_max": 30.0, "injections": [ "data/injections/backpack.bin", "data/injections/braid.bin", - "data/injections/bubbles.bin", "data/injections/lara_animations.bin", "data/injections/purple_crystal.bin", "data/injections/uzi_sfx.bin", @@ -28,7 +30,7 @@ "music_track": 2, "inherit_injections": false, "sequence": [ - {"type": "display_picture", "path": "data/images/eidos.webp", "legal": true, "display_time": 1, "fade_in_time": 1.0, "fade_out_time": 1.0}, + {"type": "display_picture", "path": "data/images/eidos.webp", "display_time": 1, "fade_in_time": 1.0, "fade_out_time": 1.0}, {"type": "play_fmv", "fmv_id": 0}, {"type": "play_fmv", "fmv_id": 1}, {"type": "play_fmv", "fmv_id": 2}, @@ -57,7 +59,6 @@ "injections": [ "data/injections/lara_gym_guns.bin", "data/injections/braid.bin", - "data/injections/bubbles.bin", "data/injections/gym_textures.bin", "data/injections/lara_animations.bin", "data/injections/uzi_sfx.bin", @@ -150,7 +151,6 @@ "injections": [ "data/injections/folly_fd.bin", "data/injections/folly_itemrots.bin", - "data/injections/folly_pickup_meshes.bin", "data/injections/folly_textures.bin", ], }, @@ -246,7 +246,6 @@ "data/injections/khamoon_fd.bin", "data/injections/khamoon_mummy.bin", "data/injections/khamoon_textures.bin", - "data/injections/panther_sfx.bin", ], }, @@ -266,7 +265,6 @@ "data/injections/obelisk_meshfixes.bin", "data/injections/obelisk_skybox.bin", "data/injections/obelisk_textures.bin", - "data/injections/panther_sfx.bin", ], }, @@ -308,7 +306,6 @@ "data/injections/mines_meshfixes.bin", "data/injections/mines_pushblocks.bin", "data/injections/mines_textures.bin", - "data/injections/skate_kid_sfx.bin", ], "item_drops": [ {"enemy_num": 17, "object_ids": [86]}, @@ -484,8 +481,8 @@ { "path": "data/cut4.phd", "music_track": 22, - "fog_start": 12.0, - "fog_end": 18.0, + "draw_distance_fade": 12.0, + "draw_distance_max": 18.0, "lara_type": "player_1", "inherit_injections": false, "injections": [ @@ -506,8 +503,8 @@ // FMVs "fmvs": [ - {"path": "fmv/core.avi", "legal": true}, - {"path": "fmv/escape.avi", "legal": true}, + {"path": "fmv/core.avi"}, + {"path": "fmv/escape.avi"}, {"path": "fmv/cafe.avi"}, {"path": "fmv/mansion.avi"}, {"path": "fmv/snow.avi"}, diff --git a/data/tr1/ship/cfg/TR1X_gameflow_demo_pc.json5 b/data/tr1/ship/cfg/TR1X_gameflow_demo_pc.json5 index 743012b96..cbe21e47a 100644 --- a/data/tr1/ship/cfg/TR1X_gameflow_demo_pc.json5 +++ b/data/tr1/ship/cfg/TR1X_gameflow_demo_pc.json5 @@ -9,10 +9,12 @@ "savegame_fmt_legacy": "save_demo_pc.%d", "savegame_fmt_bson": "save_demo_pc_%02d.dat", "demo_delay": 16, + "water_color": [0.45, 1.0, 1.0], + "draw_distance_fade": 22.0, + "draw_distance_max": 30.0, "injections": [ "data/injections/backpack.bin", "data/injections/braid.bin", - "data/injections/bubbles.bin", "data/injections/lara_animations.bin", "data/injections/lara_jumping.bin", "data/injections/uzi_sfx.bin", diff --git a/data/tr1/ship/cfg/TR1X_gameflow_level.json5 b/data/tr1/ship/cfg/TR1X_gameflow_level.json5 index 035285740..95a236920 100644 --- a/data/tr1/ship/cfg/TR1X_gameflow_level.json5 +++ b/data/tr1/ship/cfg/TR1X_gameflow_level.json5 @@ -5,10 +5,12 @@ "savegame_fmt_legacy": "save_tmp.%d", "savegame_fmt_bson": "save_tmp_%02d.dat", "demo_delay": 0, + "water_color": [0.45, 1.0, 1.0], + "draw_distance_fade": 22.0, + "draw_distance_max": 30.0, "injections": [ "data/injections/backpack.bin", "data/injections/braid.bin", - "data/injections/bubbles.bin", "data/injections/lara_animations.bin", "data/injections/lara_jumping.bin", "data/injections/uzi_sfx.bin", diff --git a/data/tr1/ship/cfg/TR1X_gameflow_ub.json5 b/data/tr1/ship/cfg/TR1X_gameflow_ub.json5 index fab10e0ff..9817d0510 100644 --- a/data/tr1/ship/cfg/TR1X_gameflow_ub.json5 +++ b/data/tr1/ship/cfg/TR1X_gameflow_ub.json5 @@ -8,10 +8,12 @@ "savegame_fmt_legacy": "saveuba.%d", "savegame_fmt_bson": "save_trub_%02d.dat", "demo_delay": 16, + "water_color": [0.45, 1.0, 1.0], + "draw_distance_fade": 22, + "draw_distance_max": 30, "injections": [ "data/injections/backpack.bin", "data/injections/braid.bin", - "data/injections/bubbles.bin", "data/injections/lara_animations.bin", "data/injections/uzi_sfx.bin", "data/injections/explosion.bin", @@ -34,7 +36,7 @@ "data/injections/pda_model.bin", ], "sequence": [ - {"type": "display_picture", "path": "data/images/eidos.webp", "legal": true, "display_time": 1, "fade_in_time": 1.0, "fade_out_time": 1.0}, + {"type": "display_picture", "path": "data/images/eidos.webp", "display_time": 1, "fade_in_time": 1.0, "fade_out_time": 1.0}, {"type": "play_fmv", "fmv_id": 0}, {"type": "play_fmv", "fmv_id": 1}, {"type": "exit_to_title"}, @@ -42,7 +44,7 @@ }, "levels": [ - // Level 1: Return to Egypt + // Level 0: Return to Egypt { "path": "data/egypt.phd", "music_track": 59, @@ -56,12 +58,11 @@ "data/injections/egypt_fd.bin", "data/injections/egypt_meshfixes.bin", "data/injections/egypt_textures.bin", - "data/injections/panther_sfx.bin", ], "unobtainable_kills": 1, }, - // Level 2: Temple of the Cat + // Level 1: Temple of the Cat { "path": "data/cat.phd", "music_track": 59, @@ -76,12 +77,11 @@ "data/injections/cat_itemrots.bin", "data/injections/cat_meshfixes.bin", "data/injections/cat_textures.bin", - "data/injections/panther_sfx.bin", ], "unobtainable_pickups": 1, }, - // Level 3: Atlantean Stronghold + // Level 2: Atlantean Stronghold { "path": "data/end.phd", "music_track": 60, @@ -98,7 +98,7 @@ "unobtainable_kills": 1, }, - // Level 4: The Hive + // Level 3: The Hive { "path": "data/end2.phd", "music_track": 60, @@ -123,8 +123,7 @@ {"type": "dummy"}, - // Level 6: Current Position - // This level is necessary to read TombATI's save files. + // Level 5: Current Position { "path": "data/current.phd", "type": "current", @@ -135,7 +134,7 @@ ], "fmvs": [ - {"path": "fmv/core.avi", "legal": true}, - {"path": "fmv/escape.avi", "legal": true}, + {"path": "fmv/core.avi"}, + {"path": "fmv/escape.avi"}, ], } diff --git a/data/tr1/ship/cfg/TR1X_strings.json5 b/data/tr1/ship/cfg/TR1X_strings.json5 index b49fdf18d..8391245aa 100644 --- a/data/tr1/ship/cfg/TR1X_strings.json5 +++ b/data/tr1/ship/cfg/TR1X_strings.json5 @@ -271,7 +271,7 @@ "door_8": {"name": "Door 8"}, "trapdoor_1": {"name": "Trapdoor 1"}, "trapdoor_2": {"name": "Trapdoor 2"}, - "trapdoor_3": {"name": "Trapdoor 3"}, + "big_trapdoor": {"name": "Big Trapdoor"}, "bridge_flat": {"name": "Bridge Flat"}, "bridge_tilt_1": {"name": "Bridge Tilt 1"}, "bridge_tilt_2": {"name": "Bridge Tilt 2"}, @@ -341,40 +341,35 @@ }, "game_strings": { - "CONTROLS_BACKEND_CONTROLLER": "Controller", - "CONTROLS_BACKEND_KEYBOARD": "Keyboard", - "CONTROLS_CUSTOMIZE": "Customize Controls", - "CONTROLS_CUSTOM_1": "User Keys 1", - "CONTROLS_CUSTOM_2": "User Keys 2", - "CONTROLS_CUSTOM_3": "User Keys 3", - "CONTROLS_DEFAULT_KEYS": "Default Keys", - "CONTROLS_RESET_DEFAULTS": "Reset All: Hold %s", - "CONTROLS_UNBIND": "Unbind: Hold %s", + "CONTROL_BACKEND_CONTROLLER": "Controller", + "CONTROL_BACKEND_KEYBOARD": "Keyboard", + "CONTROL_CUSTOMIZE": "Customize Controls", + "CONTROL_CUSTOM_1": "User Keys 1", + "CONTROL_CUSTOM_2": "User Keys 2", + "CONTROL_CUSTOM_3": "User Keys 3", + "CONTROL_DEFAULT_KEYS": "Default Keys", + "CONTROL_RESET_DEFAULTS": "Reset All: Hold %s", + "CONTROL_UNBIND": "Unbind: Hold %s", "DETAIL_BILINEAR": "Bilinear", "DETAIL_BRIGHTNESS": "Brightness", + "DETAIL_DECIMAL_FMT": "%d", "DETAIL_FBO_FILTER": "FBO filter", "DETAIL_FLOAT_FMT": "%.1f", - "DETAIL_FOG_END": "Fog end", - "DETAIL_FOG_START": "Fog start", "DETAIL_FPS": "FPS", - "DETAIL_INTEGER_FMT": "%d", + "DETAIL_PRETTY_PIXELS": "Pretty pixels", "DETAIL_REFLECTIONS": "Reflections", "DETAIL_RENDER_MODE": "Render mode", "DETAIL_RENDER_MODE_FBO": "Framebuffer", "DETAIL_RENDER_MODE_LEGACY": "Window size", "DETAIL_RESOLUTION": "Resolution", "DETAIL_RESOLUTION_FMT": "%dx%d", + "DETAIL_SELECT_DETAIL": "Select Detail", "DETAIL_STRING_FMT": "%s", "DETAIL_TEXTURE_FILTER": "Texture filter", - "DETAIL_TITLE": "Graphic Options", "DETAIL_TRAPEZOID_FILTER": "Trapezoid filter", "DETAIL_UI_BAR_SCALE": "UI bar scale", - "DETAIL_UI_SCROLL_WRAPAROUND": "UI scroll wrap", "DETAIL_UI_TEXT_SCALE": "UI text scale", "DETAIL_VSYNC": "VSync", - "DETAIL_WATER_COLOR_B": "Water color (B)", - "DETAIL_WATER_COLOR_G": "Water color (G)", - "DETAIL_WATER_COLOR_R": "Water color (R)", "HEADING_GAME_OVER": "GAME OVER", "HEADING_INVENTORY": "INVENTORY", "HEADING_ITEMS": "ITEMS", @@ -428,11 +423,7 @@ "OSD_AMBIGUOUS_INPUT_2": "Ambiguous input: %s and %s", "OSD_AMBIGUOUS_INPUT_3": "Ambiguous input: %s, %s, ...", "OSD_COMMAND_BAD_INVOCATION": "Invalid invocation: %s", - "OSD_COMMAND_BOOL": "on, off", - "OSD_COMMAND_DECIMAL": "[decimal]", - "OSD_COMMAND_INTEGER": "[integer]", "OSD_COMMAND_UNAVAILABLE": "This command is not currently available", - "OSD_COMMAND_VALID_VALUES": "Valid values: %s", "OSD_COMPLETE_LEVEL": "Level complete!", "OSD_CONFIG_OPTION_GET": "%s is currently set to %s", "OSD_CONFIG_OPTION_SET": "%s changed to %s", @@ -535,18 +526,13 @@ "PHOTO_MODE_ROTATE_PROMPT": "Rotate camera", "PHOTO_MODE_SNAP_PROMPT": "Take picture", "PHOTO_MODE_TITLE": "Photo Mode", - "SOUND_DIALOG_MUSIC": "\\{icon sound} Sound", - "SOUND_DIALOG_SOUND": "\\{icon music} Music", - "SOUND_DIALOG_TITLE": "Set Volumes", - "STATS_AMMO": "AMMO HITS/USED", + "SOUND_SET_VOLUMES": "Set Volumes", "STATS_BASIC_FMT": "%d", "STATS_BONUS_STATISTICS": "Bonus Statistics", "STATS_DEATHS": "DEATHS", "STATS_DETAIL_FMT": "%d of %d", - "STATS_DISTANCE_TRAVELLED": "DISTANCE TRAVELLED", "STATS_FINAL_STATISTICS": "Final Statistics", "STATS_KILLS": "KILLS", - "STATS_MEDIPACKS_USED": "HEALTH PACKS USED", "STATS_PICKUPS": "PICKUPS", "STATS_SECRETS": "SECRETS", "STATS_TIME_TAKEN": "TIME TAKEN", diff --git a/data/tr1/ship/cfg/TR1X_strings_ub.json5 b/data/tr1/ship/cfg/TR1X_strings_ub.json5 index 36e1134cb..1a704f6cd 100644 --- a/data/tr1/ship/cfg/TR1X_strings_ub.json5 +++ b/data/tr1/ship/cfg/TR1X_strings_ub.json5 @@ -1,6 +1,6 @@ { "levels": [ - // Level 1: Return to Egypt + // Level 0: Return to Egypt { "title": "Return to Egypt", "objects": { @@ -8,7 +8,7 @@ }, }, - // Level 2: Temple of the Cat + // Level 1: Temple of the Cat { "title": "Temple of the Cat", "objects": { @@ -16,22 +16,22 @@ }, }, - // Level 3: Atlantean Stronghold + // Level 2: Atlantean Stronghold { "title": "Atlantean Stronghold", }, - // Level 4: The Hive + // Level 3: The Hive { "title": "The Hive", }, - // Level 5: Title + // Level 4: Title { "title": "Title", }, - // Level 6: Current Position + // Level 5: Current Position { "title": "Current Position", }, diff --git a/data/tr1/ship/data/images/atlantis.webp b/data/tr1/ship/data/images/atlantis.webp new file mode 100644 index 000000000..1387e1f12 Binary files /dev/null and b/data/tr1/ship/data/images/atlantis.webp differ diff --git a/data/tr1/ship/data/images/credits_1.webp b/data/tr1/ship/data/images/credits_1.webp new file mode 100644 index 000000000..174e94bb2 Binary files /dev/null and b/data/tr1/ship/data/images/credits_1.webp differ diff --git a/data/tr1/ship/data/images/credits_2.webp b/data/tr1/ship/data/images/credits_2.webp new file mode 100644 index 000000000..67f5a1b70 Binary files /dev/null and b/data/tr1/ship/data/images/credits_2.webp differ diff --git a/data/tr1/ship/data/images/credits_3.webp b/data/tr1/ship/data/images/credits_3.webp new file mode 100644 index 000000000..c10913e67 Binary files /dev/null and b/data/tr1/ship/data/images/credits_3.webp differ diff --git a/data/tr1/ship/data/images/credits_3_alt.webp b/data/tr1/ship/data/images/credits_3_alt.webp new file mode 100644 index 000000000..868dde66d Binary files /dev/null and b/data/tr1/ship/data/images/credits_3_alt.webp differ diff --git a/data/tr1/ship/data/images/credits_ps1.webp b/data/tr1/ship/data/images/credits_ps1.webp new file mode 100644 index 000000000..c7d81d416 Binary files /dev/null and b/data/tr1/ship/data/images/credits_ps1.webp differ diff --git a/data/tr1/ship/data/images/credits_ub.webp b/data/tr1/ship/data/images/credits_ub.webp new file mode 100644 index 000000000..69cc8824e Binary files /dev/null and b/data/tr1/ship/data/images/credits_ub.webp differ diff --git a/data/tr1/ship/data/images/egypt.webp b/data/tr1/ship/data/images/egypt.webp new file mode 100644 index 000000000..f3b2c66ce Binary files /dev/null and b/data/tr1/ship/data/images/egypt.webp differ diff --git a/data/tr1/ship/data/images/eidos.webp b/data/tr1/ship/data/images/eidos.webp new file mode 100644 index 000000000..5439e9103 Binary files /dev/null and b/data/tr1/ship/data/images/eidos.webp differ diff --git a/data/tr1/ship/data/images/end.webp b/data/tr1/ship/data/images/end.webp new file mode 100644 index 000000000..e5b858de9 Binary files /dev/null and b/data/tr1/ship/data/images/end.webp differ diff --git a/data/tr1/ship/data/images/greece.webp b/data/tr1/ship/data/images/greece.webp new file mode 100644 index 000000000..060bd0291 Binary files /dev/null and b/data/tr1/ship/data/images/greece.webp differ diff --git a/data/tr1/ship/data/images/greece_saturn.webp b/data/tr1/ship/data/images/greece_saturn.webp new file mode 100644 index 000000000..618bc6565 Binary files /dev/null and b/data/tr1/ship/data/images/greece_saturn.webp differ diff --git a/data/tr1/ship/data/images/gym.webp b/data/tr1/ship/data/images/gym.webp new file mode 100644 index 000000000..9e026fcc7 Binary files /dev/null and b/data/tr1/ship/data/images/gym.webp differ diff --git a/data/tr1/ship/data/images/install.webp b/data/tr1/ship/data/images/install.webp new file mode 100644 index 000000000..f79c61dfc Binary files /dev/null and b/data/tr1/ship/data/images/install.webp differ diff --git a/data/tr1/ship/data/images/peru.webp b/data/tr1/ship/data/images/peru.webp new file mode 100644 index 000000000..4a3f79fc3 Binary files /dev/null and b/data/tr1/ship/data/images/peru.webp differ diff --git a/data/tr1/ship/data/images/title.webp b/data/tr1/ship/data/images/title.webp new file mode 100644 index 000000000..a94f8a959 Binary files /dev/null and b/data/tr1/ship/data/images/title.webp differ diff --git a/data/tr1/ship/data/images/title_og_alt.webp b/data/tr1/ship/data/images/title_og_alt.webp new file mode 100644 index 000000000..723256902 Binary files /dev/null and b/data/tr1/ship/data/images/title_og_alt.webp differ diff --git a/data/tr1/ship/data/images/title_ub.webp b/data/tr1/ship/data/images/title_ub.webp new file mode 100644 index 000000000..7e269f718 Binary files /dev/null and b/data/tr1/ship/data/images/title_ub.webp differ diff --git a/data/tr1/ship/data/injections/bubbles.bin b/data/tr1/ship/data/injections/bubbles.bin deleted file mode 100644 index c399a73f8..000000000 Binary files a/data/tr1/ship/data/injections/bubbles.bin and /dev/null differ diff --git a/data/tr1/ship/data/injections/folly_pickup_meshes.bin b/data/tr1/ship/data/injections/folly_pickup_meshes.bin deleted file mode 100644 index 044f6717e..000000000 Binary files a/data/tr1/ship/data/injections/folly_pickup_meshes.bin and /dev/null differ diff --git a/data/tr1/ship/data/injections/panther_sfx.bin b/data/tr1/ship/data/injections/panther_sfx.bin deleted file mode 100644 index 199c2e833..000000000 Binary files a/data/tr1/ship/data/injections/panther_sfx.bin and /dev/null differ diff --git a/data/tr1/ship/data/injections/skate_kid_sfx.bin b/data/tr1/ship/data/injections/skate_kid_sfx.bin deleted file mode 100644 index 6227d7485..000000000 Binary files a/data/tr1/ship/data/injections/skate_kid_sfx.bin and /dev/null differ diff --git a/data/tr1/ship/shaders/2d.glsl b/data/tr1/ship/shaders/2d.glsl index 681e574c3..b8b2438d2 100644 --- a/data/tr1/ship/shaders/2d.glsl +++ b/data/tr1/ship/shaders/2d.glsl @@ -1,18 +1,25 @@ #ifdef VERTEX +// Vertex shader + +#ifdef OGL33C + out vec2 vertTexCoords; + out vec2 vertCoords; +#else + varying vec2 vertTexCoords; + varying vec2 vertCoords; +#endif layout(location = 0) in vec2 inPosition; layout(location = 1) in vec2 inTexCoords; -out vec2 vertTexCoords; -out vec2 vertCoords; - void main(void) { gl_Position = vec4(inPosition * vec2(2.0, -2.0) + vec2(-1.0, 1.0), 0.0, 1.0); vertCoords = inPosition; vertTexCoords = inTexCoords; } -#elif defined(FRAGMENT) +#else +// Fragment shader #define EFFECT_NONE 0 #define EFFECT_VIGNETTE 1 @@ -26,29 +33,42 @@ uniform bool tintEnabled; uniform vec3 tintColor; uniform int effect; -in vec2 vertTexCoords; -in vec2 vertCoords; -out vec4 outColor; +#ifdef OGL33C + #define OUTCOLOR outColor + #define TEXTURE2D texture + #define TEXTURE1D texture + + in vec2 vertTexCoords; + in vec2 vertCoords; + out vec4 outColor; +#else + #define OUTCOLOR gl_FragColor + #define TEXTURE2D texture2D + #define TEXTURE1D texture1D + + varying vec2 vertTexCoords; + varying vec2 vertCoords; +#endif void main(void) { vec2 uv = vertTexCoords; if (alphaEnabled) { - float alpha = texture(texAlpha, uv).r; + float alpha = TEXTURE2D(texAlpha, uv).r; if (alpha < 0.5) { discard; } } if (paletteEnabled) { - float paletteIndex = texture(texMain, uv).r; - outColor = texture(texPalette, paletteIndex); + float paletteIndex = TEXTURE2D(texMain, uv).r; + OUTCOLOR = TEXTURE1D(texPalette, paletteIndex); } else { - outColor = texture(texMain, uv); + OUTCOLOR = TEXTURE2D(texMain, uv); } if (tintEnabled) { - outColor.rgb *= tintColor.rgb; + OUTCOLOR.rgb *= tintColor.rgb; } if (effect == EFFECT_VIGNETTE) { @@ -56,7 +76,7 @@ void main(void) { float y_dist = vertCoords.y - 0.5; float light = 256 - sqrt(x_dist * x_dist + y_dist * y_dist ) * 300.0; light = clamp(light, 0, 255) / 255; - outColor *= vec4(light, light, light, 1); + OUTCOLOR *= vec4(light, light, light, 1); } } -#endif +#endif // VERTEX diff --git a/data/tr1/ship/shaders/3d.glsl b/data/tr1/ship/shaders/3d.glsl index 54e8b6a89..75314792a 100644 --- a/data/tr1/ship/shaders/3d.glsl +++ b/data/tr1/ship/shaders/3d.glsl @@ -1,4 +1,5 @@ #ifdef VERTEX +// Vertex shader layout(location = 0) in vec3 inPosition; layout(location = 1) in vec4 inTexCoords; @@ -6,13 +7,20 @@ layout(location = 2) in float inTexZ; layout(location = 3) in vec4 inColor; uniform mat4 matProjection; +uniform mat4 matModelView; -out vec4 vertColor; -out vec4 vertTexCoords; -out float vertTexZ; +#ifdef OGL33C + out vec4 vertColor; + out vec4 vertTexCoords; + out float vertTexZ; +#else + varying vec4 vertColor; + varying vec4 vertTexCoords; + varying float vertTexZ; +#endif void main(void) { - gl_Position = matProjection * vec4(inPosition, 1); + gl_Position = matProjection * matModelView * vec4(inPosition, 1); vertColor = inColor / 255.0; vertTexCoords = inTexCoords; vertTexCoords.xy *= vertTexCoords.zw; @@ -20,7 +28,8 @@ void main(void) { vertTexZ = inTexZ; } -#elif defined(FRAGMENT) +#else +// Fragment shader uniform sampler2D tex0; uniform bool texturingEnabled; @@ -29,28 +38,52 @@ uniform bool alphaPointDiscard; uniform float alphaThreshold; uniform float brightnessMultiplier; -in vec4 vertColor; -in vec4 vertTexCoords; -in float vertTexZ; -out vec4 outColor; +#ifdef OGL33C + #define OUTCOLOR outColor + #define TEXTURESIZE textureSize + #define TEXTURE texture + #define TEXELFETCH texelFetch + + in vec4 vertColor; + in vec4 vertTexCoords; + in float vertTexZ; + out vec4 OUTCOLOR; +#else + #define OUTCOLOR gl_FragColor + #define TEXTURESIZE textureSize2D + #define TEXELFETCH texelFetch2D + #define TEXTURE texture2D + + varying vec4 vertColor; + varying vec4 vertTexCoords; + varying float vertTexZ; +#endif void main(void) { - outColor = vertColor; + OUTCOLOR = vertColor; vec2 texCoords = vertTexCoords.xy; texCoords.xy /= vertTexCoords.zw; if (texturingEnabled) { - if (alphaPointDiscard && smoothingEnabled && discardTranslucent(tex0, texCoords)) { - discard; +#if defined(GL_EXT_gpu_shader4) || defined(OGL33C) + if (alphaPointDiscard && smoothingEnabled) { + // do not use smoothing for chroma key + ivec2 size = TEXTURESIZE(tex0, 0); + ivec2 texCoordsNN = ivec2(texCoords.xy * size.xy) % size.xy; + vec4 texel = TEXELFETCH(tex0, texCoordsNN, 0); + if (texel.a == 0.0) { + discard; + } } +#endif - vec4 texColor = texture(tex0, texCoords.xy); + vec4 texColor = TEXTURE(tex0, texCoords.xy); if (alphaThreshold >= 0.0 && texColor.a <= alphaThreshold) { discard; } - outColor = vec4(outColor.rgb * texColor.rgb * brightnessMultiplier, texColor.a); + OUTCOLOR = vec4(OUTCOLOR.rgb * texColor.rgb * brightnessMultiplier, texColor.a); } } -#endif +#endif // VERTEX diff --git a/data/tr1/ship/shaders/common.glsl b/data/tr1/ship/shaders/common.glsl deleted file mode 100644 index 0cf4d9e7d..000000000 --- a/data/tr1/ship/shaders/common.glsl +++ /dev/null @@ -1,65 +0,0 @@ -#define WALL_L 1024 -#define SHADE_NEUTRAL 0x1000 -#define SHADE_MAX 0x1FFF - -#define VERT_NO_CAUSTICS 0x01 -#define VERT_FLAT_SHADED 0x02 -#define VERT_REFLECTIVE 0x04 -#define VERT_NO_LIGHTING 0x08 -#define VERT_SPRITE 0x10 // flag for billboarded sprites in mesh shader - -#define WIBBLE_SIZE 32 -#define MAX_WIBBLE 2 -#define PI 3.1415926538 - -vec3 waterWibble(vec4 position, vec2 viewportSize, int time) -{ - // get screen coordinates - vec3 ndc = position.xyz / position.w; //perspective divide/normalize - vec2 viewportCoord = ndc.xy * 0.5 + 0.5; //ndc is -1 to 1 in GL. scale for 0 to 1 - vec2 viewportPixelCoord = viewportCoord * viewportSize; - - viewportPixelCoord.x += sin((float(time) + viewportPixelCoord.y) * 2.0 * PI / WIBBLE_SIZE) * MAX_WIBBLE; - viewportPixelCoord.y += sin((float(time) + viewportPixelCoord.x) * 2.0 * PI / WIBBLE_SIZE) * MAX_WIBBLE; - - // reverse transform - viewportCoord = viewportPixelCoord / viewportSize; - ndc.xy = (viewportCoord - 0.5) * 2.0; - return ndc * position.w; -} - -float shadeFog(float shade, float depth, vec2 fog) -{ - float fogBegin = fog.x; - float fogEnd = fog.y; - if (depth < fogBegin) { - return shade + 0.0; - } else if (depth >= fogEnd) { - return shade + float(SHADE_MAX); - } else { - return shade + (depth - fogBegin) * SHADE_MAX / (fogEnd - fogBegin); - } -} - -vec3 applyShade(vec3 color, float shade) -{ - return color * (2.0 - (shade / SHADE_NEUTRAL)); -} - -bool discardTranslucent(sampler2D tex, vec2 uv) -{ - // do not use smoothing for chroma key - ivec2 size = textureSize(tex, 0); - ivec2 texCoordsNN = ivec2(uv.xy * size.xy) % size.xy; - vec4 texel = texelFetch(tex, texCoordsNN, 0); - return texel.a == 0.0; -} - -bool discardTranslucent(sampler2DArray tex, vec3 uvw) -{ - // do not use smoothing for chroma key - ivec2 size = textureSize(tex, 0).xy; - ivec3 texCoordsNN = ivec3(ivec2(uvw.xy * size.xy) % size.xy, uvw.z); - vec4 texel = texelFetch(tex, texCoordsNN, 0); - return texel.a == 0.0; -} diff --git a/data/tr1/ship/shaders/fbo.glsl b/data/tr1/ship/shaders/fbo.glsl index ca9b714be..ebc393978 100644 --- a/data/tr1/ship/shaders/fbo.glsl +++ b/data/tr1/ship/shaders/fbo.glsl @@ -1,22 +1,38 @@ #ifdef VERTEX +// Vertex shader layout(location = 0) in vec2 inPosition; -out vec2 vertTexCoords; +#ifdef OGL33C + out vec2 vertTexCoords; +#else + varying vec2 vertTexCoords; +#endif void main(void) { vertTexCoords = inPosition; gl_Position = vec4(vertTexCoords * vec2(2.0, 2.0) + vec2(-1.0, -1.0), 0.0, 1.0); } -#elif defined(FRAGMENT) +#else +// Fragment shader uniform sampler2D tex0; -in vec2 vertTexCoords; -out vec4 outColor; +#ifdef OGL33C + #define OUTCOLOR outColor + #define TEXTURE texture + + in vec2 vertTexCoords; + out vec4 OUTCOLOR; +#else + #define OUTCOLOR gl_FragColor + #define TEXTURE texture2D + + varying vec2 vertTexCoords; +#endif void main(void) { - outColor = texture(tex0, vertTexCoords); + OUTCOLOR = TEXTURE(tex0, vertTexCoords); } -#endif +#endif // VERTEX diff --git a/data/tr1/ship/shaders/meshes.glsl b/data/tr1/ship/shaders/meshes.glsl deleted file mode 100644 index ea17d37d5..000000000 --- a/data/tr1/ship/shaders/meshes.glsl +++ /dev/null @@ -1,122 +0,0 @@ -#ifdef VERTEX - -uniform int uTime; -uniform vec2 uViewportSize; -uniform mat4 uMatProjection; -uniform mat4 uMatModelView; -uniform bool uTrapezoidFilterEnabled; -uniform bool uWibbleEffect; - -layout(location = 0) in vec3 inPosition; -layout(location = 1) in vec3 inNormal; -layout(location = 2) in vec3 inUVW; -layout(location = 3) in vec4 inTextureSize; -layout(location = 4) in vec2 inTrapezoidRatios; -layout(location = 5) in int inFlags; -layout(location = 6) in vec4 inColor; -layout(location = 7) in float inShade; - -out vec4 gWorldPos; -out vec3 gNormal; -flat out int gFlags; -flat out int gTexLayer; -out vec2 gTexUV; -flat out vec4 gAtlasSize; -out vec2 gTrapezoidRatios; -out float gShade; -out vec4 gColor; - -void main(void) { - // billboard sprites if flagged, else standard vertex transform - vec4 eyePos = uMatModelView * vec4(inPosition.xyz, 1.0); - if ((inFlags & VERT_SPRITE) != 0) { - // inNormal.xy carries sprite displacement for billboarding - eyePos.xy += inNormal.xy; - } - gWorldPos = eyePos; - gNormal = inNormal; - gl_Position = uMatProjection * eyePos; - - // apply water wibble effect only to non-sprite vertices - if (uWibbleEffect && (inFlags & VERT_NO_CAUSTICS) == 0 && (inFlags & VERT_SPRITE) == 0) { - gl_Position.xyz = - waterWibble(gl_Position, uViewportSize, uTime); - } - - gFlags = inFlags; - gAtlasSize = inTextureSize; - gTexUV = inUVW.xy; - gTexLayer = int(inUVW.z); - gTrapezoidRatios = inTrapezoidRatios; - if (uTrapezoidFilterEnabled) { - gTexUV *= inTrapezoidRatios; - } - gShade = inShade; - gColor = inColor; -} - -#elif defined(FRAGMENT) - -uniform int uTime; -uniform sampler2DArray uTexAtlas; -uniform sampler2D uTexEnvMap; -uniform bool uSmoothingEnabled; -uniform bool uAlphaDiscardEnabled; -uniform bool uTrapezoidFilterEnabled; -uniform bool uReflectionsEnabled; -uniform float uAlphaThreshold; -uniform float uBrightnessMultiplier; -uniform vec3 uGlobalTint; -uniform vec2 uFog; // x = fog start, y = fog end -uniform bool uWaterEffect; - -in vec4 gWorldPos; -in vec3 gNormal; -flat in int gFlags; -flat in int gTexLayer; -in vec2 gTexUV; -flat in vec4 gAtlasSize; -in float gShade; -in vec4 gColor; -in vec2 gTrapezoidRatios; -out vec4 outColor; - -vec2 clampTexAtlas(vec2 uv, vec4 atlasSize) -{ - float epsilon = 0.5 / 256.0; - return clamp(uv, atlasSize.xy + epsilon, atlasSize.zw - epsilon); -} - -void main(void) { - vec4 texColor = gColor; - vec3 texCoords = vec3(gTexUV.x, gTexUV.y, gTexLayer); - if (uTrapezoidFilterEnabled) { - texCoords.xy /= gTrapezoidRatios; - } - texCoords.xy = clampTexAtlas(texCoords.xy, gAtlasSize); - - if ((gFlags & VERT_FLAT_SHADED) == 0 && texCoords.z >= 0) { - if (uAlphaDiscardEnabled && uSmoothingEnabled && discardTranslucent(uTexAtlas, texCoords)) { - discard; - } - - texColor = texture(uTexAtlas, texCoords); - if (uAlphaThreshold >= 0.0 && texColor.a <= uAlphaThreshold) { - discard; - } - } - if ((gFlags & VERT_REFLECTIVE) != 0 && uReflectionsEnabled) { - texColor *= texture(uTexEnvMap, (normalize(gNormal) * 0.5 + 0.5).xy) * 2; - } - - if ((gFlags & VERT_NO_LIGHTING) == 0) { - float shade = gShade; - shade = shadeFog(shade, gWorldPos.z, uFog); - texColor.rgb = applyShade(texColor.rgb, shade); - texColor.rgb *= uGlobalTint; - } - - texColor.rgb *= uBrightnessMultiplier; - outColor = vec4(texColor.rgb, gColor.a); -} -#endif diff --git a/data/tr2/logo-dark-theme.png b/data/tr2/logo-dark-theme.png new file mode 100644 index 000000000..16c9fbc76 Binary files /dev/null and b/data/tr2/logo-dark-theme.png differ diff --git a/data/tr2/logo-light-theme.png b/data/tr2/logo-light-theme.png new file mode 100644 index 000000000..fe050d2ed Binary files /dev/null and b/data/tr2/logo-light-theme.png differ diff --git a/data/tr2/logo.ai b/data/tr2/logo.ai new file mode 100644 index 000000000..3791eaea4 --- /dev/null +++ b/data/tr2/logo.ai @@ -0,0 +1,1489 @@ +%PDF-1.6 % +1 0 obj <>/OCGs[24 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <>stream + + + + + application/pdf + + + logo + + + 2023-09-25T22:02:23+02:00 + 2023-09-25T22:02:23+02:00 + 2023-09-25T22:02:23+02:00 + Adobe Illustrator 27.9 (Windows) + + + + 256 + 104 + JPEG + /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgAaAEAAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4qoDnMxoeKDChv6sP5 jja076sP5jja076sP5jja076sP5jja076sP5jja076sP5jja076sP5jja076sP5jja076sP5jja0 76sP5jja076t4ORja01xuE3Dch4YqvjmD7HZvDAlUxVa8ioKn6BiqkGnk+z8K4UN/V2P2nJxtad9 W/yzja01wnTdW5DwOKr4pg+xFG8MCqjEAEnoMUodVkm+JmovYYUNkSQmoPJO4xVWR1ZajpgS3iqn LLw2G7noMULBbs27seWG1ptGeOQRsag9DiqtgS7FXEVBHjiqhA4SqNsQcJQFbkviMCWwwPQ4q7FW uS+IxV3JfEYq7kviMVbBB6HFXYq1yXxGKu5L4jFW+SnuMVdiqlPHUc12Yb4Qgr435IG+/AlSjHqy F2+yOgwoV8CXEgddsVcCD0NcVdiqjcLSkg2I64QgqjfHFt+0NsCVlu448Dsw7YSgKvXAlQZWhbku 6HqMKFz3ChRx3Y9BjS26KIj433c/hiqrgSoE+pOvHovU4UK+BLsVdiq14kfqN/HFVn1aP3w2ilio EuAB0p/DFURgShxGrzOD2woX/Vo/fG1p31aP3xtaWxqFuCo6AYqr4EodY1eZwexwoX/Vo/fG1ppr eIDqR742tOt2Y8lrUDocSoVsCVCCpjcD6PuwlAWxy+mvEqa1xVf9ZH8pxpbWsxlcVBCDrirduP3j FfsYlQr4EqdwQIiPHCEFdGKIo9sCVksXL4l2cYUNxS8vhbZx1GBVTrilYkKKxYfR7YrS/FVB3aRv Tj6dzhQqxxqi0H0nAldirsVdirsVdiqif96h8v4YUK2BKjH/AH74UK2BLsVUV/3qb5fwGFCtgSox f30mFDpGd5PTU0A6nFXC1TuScbWlVVVRRRQYErZnCIT3OwxVqBOMYr1O+JUKmKtM6qKsaYqo/HMf 5Y8KFZVCig6YEud1QVOKqKK0r822QdBhQr4EuxVQuOIYEbSe2EIKsnLiOX2u+BLeKqVwX47fZ/aw hBXQhOA4/T41wJC/FXYq7FXYq7FXYqon/eofL+GFCtgSox/374UK2BLsVUV/3qb5fwGFCtgSoxf3 0mFDVfTnPLo3fFVfAlxrQ03PbFUMn7yX94dx0XChE4EqckwU8V+JvDCi1qwsx5Smp/lxWlYAAUHT AlZJMqbdW8MVUFZHblK3yXfChXWaI7Bv4YEr8VU5ZeAoN2PQYq1FEQeb7ufwwoVcCVqyozFQdxiq 7FVBlaJuafZ7jChWR1dajAlvFXYq7FXYq7FVE/71D5fwwoVsCVGP+/fChWwJdiqiv+9TfL+AwoVs CVGL++k/z74UKrorrQ4EqKu0TcH3XscKFfAlSnjBHMbMu9cKC4SH0OfemKut0AQN1Y98SoVcCXYq t9KP+UYq70o/5Riq14I2GwoexGNopbDIfSYncrhV0CV/eNux6YlQrYEqEkjO3px/ScKG2twFHA/G O/jja0uil5fC2zjFVTAlQI9KZeP2X7YUK+BLsVdirsVdiqjMrK4kUVp1GFDvrSU2Br4Y0ttwI27t 1btiVCrgS7FVGUMkglUVHcYUONytNga+GNLbcCMAWb7TYlQq4EtOiuKHFVAM8J4tunY4UNvNzHBA anritqixgRcD4b4EqSSGL4HG3YjChf8AWY/fGlt31mP3xpbd9Zj98aW3fWY/fGltprgEUQEsemNL a6OKkRU9W64FU0kMXwONuxGFW2laX4IwQO5xVVjjVFoPpOBK7FVOWLl8S7OMKFq3FBRwQwxpbaXl LIHIoi9MVV8CXYq7FXYq7FXYq6grWm+KuxV2KuxV2KuoOtMVdirsVcSBudsVWGWLoWGKrlKkfDSn tireKuIB64q1xXwGKu4r4DFXcV8BiruK+AxVsADoKYq7FXEA9cVcAB0xV2KuxV2KuIB64q7FXYq7 FXFgOpA+eKrfUj/mH3jFXepH/MPvxVdirsVdirsVdirRZR1IGKtGWMftDFVpni/m/XjS2ta4B2QV Y4aRax0pRpiST0UYq2ptj1FPnXFW/RH2omofnitNrOynjKKHxxW1aopWu3jgS0GU9CD8sVbxV2Ku xV2KuxV2KuxV2KuxV2KuxV2KuxV2KqEv7yZU7DrhQv8Aq8Xh+ONrSlIsStxVSW9jircbzqvEIT4V xVsvc+AH+fzxVvjcn9oDFXelMesn3YqtMSVo0u+KrhbRUrUkY2tNpFARUCtPniqz1IQdo6jxxVUj kib7Ox8OmBW5Yw607jocUqQdR8Ey7j9rChqIAzVjrxHXFVd1Rlo3TAlQ9OEH4pKjsBhQ5GjSaqn4 KYqrevF/NgTbvXi/mxW3evF/Nitu9eL+bFbd68X82K2714v5sVt3rxfzYrbvXi/mxW3evF/Nitu9 eL+bFbd68X82K2714v5sVt3rxfzYrbvWi/mxW1ODaRw32sJQFfAlRTa5avcbfhhQ3IZkfkPiXwxV YFWZySGGKrkSRH4g1Q/hirXGWInj8SntirUaKzEGMgeNTiq4RyRuOHxIeoxVzQsrcojTxGKtB5+g Sh8cVXvCHFacX8RitLow4Qc+uBK4qD1FfniqHrKZDGCF+W2FC4WwO7sScbWl4giH7OBNO9GL+UYq 70Yv5RirvRi/lGKu9GL+UYq70Yv5RirvRi/lGKu9GL+UYq70Yv5RirvRi/lGKu9GL+UYq70Yv5Ri rvRi/lGKu9GL+UYqsk9BP2QW8MKGoYSW5sKeC4q6b4JFkHTocVb+tR+B/DGltTkkRm5KSDTw/txV ckMjLUuV9sVXejL/AL8P44rTvRl/34fxxWnelN/vzFXelP8A78xV3pz/AM+Ku9O4/nH+f0Yq7hc/ zj/P6MVdS6HcH7sVd6sy/aSvyxVtbmM9ajGltqVQ4DxmrL4YquSdGHxGjd8aW13qx/zDAl3qx/zD FXerH/MMVd6sf8wxV3qx/wAwxV3qx/zDFXerH/MMVd6sf8wxV3qx/wAwxV3qx/zDFWvWi/mxVo3E Q7k/RhpFrfrBP2EJxpbdxuH6ngMVXJAib9T4nAmlTFXYq6g8MVdQeGKuxV2KuxV2KuxV2KuxV2Ku xV2KtMit1AOKqTW9DWM8ThtFLGSStWQN7j+zFWjGzbCPj77/AMcVVlt4wBUVPc742tO9CL+X8Tgt NO9CL+X8Tja070Iv5fxONrTvQi/l/E42tO9CL+X8Tja070Iv5fxONrTvQi/l/E42tO9CL+X8Tja0 36MX8uK0uEaDoo+7FW8VdirsVdir/9k= + + + + uuid:9d823f4c-4b4b-4823-ae38-c33bf13c4295 + xmp.did:c75b5b7c-47a6-ef43-8012-54e10b5ca555 + uuid:5D20892493BFDB11914A8590D31508C8 + proof:pdf + + uuid:85b06774-401c-479c-8001-7d3b0b129933 + xmp.did:0fb69b92-9296-f643-8766-fd53738d52f9 + uuid:5D20892493BFDB11914A8590D31508C8 + default + + + + + saved + xmp.iid:0fb69b92-9296-f643-8766-fd53738d52f9 + 2023-09-25T20:39:44+02:00 + Adobe Illustrator 27.9 (Windows) + / + + + saved + xmp.iid:c75b5b7c-47a6-ef43-8012-54e10b5ca555 + 2023-09-25T21:59:35+02:00 + Adobe Illustrator 27.9 (Windows) + / + + + + Document + Print + AIRobin + False + False + 1 + + 625.000000 + 250.000000 + Pixels + + + + Cyan + Magenta + Yellow + + + + + + Default Swatch Group + 0 + + + + C=4 M=2 Y=1 K=0 1 + RGB + PROCESS + 241 + 244 + 246 + + + White + RGB + PROCESS + 255 + 255 + 255 + + + Black + RGB + PROCESS + 35 + 31 + 32 + + + + + + Grays + 1 + + + + C=0 M=0 Y=0 K=100 + RGB + PROCESS + 35 + 31 + 32 + + + + + + + Adobe PDF library 17.00 + + + + + + + + + + + + + + + + + + + + + + + + + +endstream endobj 3 0 obj <> endobj 5 0 obj <>/ExtGState<>/Properties<>>>/Thumb 29 0 R/TrimBox[0.0 0.0 625.0 250.0]/Type/Page/PieceInfo<>>> endobj 26 0 obj <>stream +HtIEuKgF[ BxF$#nGjETdd  K +}?Э~Kx1!UcGd~3ˡWNr'ӡWKm?/+^CpE+[ 'gVbͥE^=SkaRR̙Rq]X.(ks : 0cS)Yt#Gm1抩,#vfg\'pKc[5ơ}e}.!z6l-S42 BѦ"uˏ݋+V["{#oD8GKY-ĚaL`Oy]ku1aEeq@1Gbl𰭸F~B YpYh 92[88閰JbHcŐMԬ(Cqg$e?)dVܰSMX~нF(oɹrf7{[`J@$DzVB|e5fAvg/)Xodw: K^Ҩ/<)j`6i&'O%6-7Q +ц;rmumF'+- +M99zj[:4Ro]vNnu} y4μu9WZN\qiy0-'43_|~8= +endstream endobj 29 0 obj <>stream +8;ZDm9+HIp$q0Q6Qe??qY%%kUBPmQE#ReZi85U4q[)TUZBT@eEa4i\9f/'&[RRDHl +NjX&<7Nq4?7_a+8n'pa#`t4f+\/0!^a_X1)b@]V*V[/!!!$!rr<$!s8N0$XN0%'~> +endstream endobj 8 0 obj <> endobj 9 0 obj <> endobj 10 0 obj <>stream +%!PS-Adobe-3.0 +%%Creator: Adobe Illustrator(R) 24.0 +%%AI8_CreatorVersion: 27.9.0 +%%For: (Marcin Kurczewski) () +%%Title: (logo.ai) +%%CreationDate: 9/25/2023 10:02 PM +%%Canvassize: 16383 +%%BoundingBox: 668 -724 1280 -478 +%%HiResBoundingBox: 668.8522925662 -723.82957172635 1279.61121764318 -478.292207216795 +%%DocumentProcessColors: Cyan Magenta Yellow +%AI5_FileFormat 14.0 +%AI12_BuildNumber: 80 +%AI3_ColorUsage: Color +%AI7_ImageSettings: 0 +%%RGBProcessColor: 0 0 0 ([Registration]) +%AI3_Cropmarks: 661.731755104687 -726.060889471573 1286.73175510469 -476.060889471573 +%AI3_TemplateBox: 298.5 -421.5 298.5 -421.5 +%AI3_TileBox: 565.526869621899 -886.698676336808 1382.96686252656 -315.378669012589 +%AI3_DocumentPreview: None +%AI5_ArtSize: 14400 14400 +%AI5_RulerUnits: 6 +%AI24_LargeCanvasScale: 1 +%AI9_ColorModel: 1 +%AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 +%AI5_TargetResolution: 800 +%AI5_NumLayers: 1 +%AI17_Begin_Content_if_version_gt:24 4 +%AI10_OpenToVie: 656.148966433662 -393.435617140419 4.59 0 8865.48883571471 8369.3179700816 2950 1902 18 1 0 68 181 0 0 0 1 1 0 1 1 0 0 +%AI17_Alternate_Content +%AI9_OpenToView: 656.148966433662 -393.435617140419 4.59 2950 1902 18 1 0 68 181 0 0 0 1 1 0 1 1 0 0 +%AI17_End_Versioned_Content +%AI5_OpenViewLayers: 7 +%AI17_Begin_Content_if_version_gt:24 4 +%AI17_Alternate_Content +%AI17_End_Versioned_Content +%%PageOrigin:-8 -817 +%AI7_GridSettings: 72 8 72 8 1 0 0.800000011920929 0.800000011920929 0.800000011920929 0.899999976158142 0.899999976158142 0.899999976158142 +%AI9_Flatten: 1 +%AI12_CMSettings: 00.MS +%%EndComments + +endstream endobj 11 0 obj <>stream +%AI24_ZStandard_Data(/XgY ,M6dN>L1{#SJ{I{ +{| $ q +qWXDb CAͦÁH@ :=+1Y +~aϊL^(.oÆ"OqQ +*R\ҳ{aLeƴg~9/#\$]bAiՇ" Eo 욆"@4I D6BFMaELQ{4G0EPµ kM$H6狃HWx,mzDYW$ͪ9er^fg7eDTE91~y %PhWL0 +iHp8Cw #qd8^4htՑ2S|!YZ{(_pb({vR ͑06D%a׳Uv㌎ ]u/bբ.sf'*2YN$sqfΛtgVV7Ѱ4sdkV*C + mgLVuK^Ҩ6KlJbC+C~,SoQ" +D$LDa8$ʮ!pa8բ0'0NpMqx QpB(n)/PwnGE)y9qELsBaGQQeR2v-ej +ozڵJ޲ 1QfCcE.:7b8pX\Qc8ʇp HLQJТ®wP<~i Oa7b "QGeH!Cғ3 zySnKp0D&@XNT  t5 0q(|^% ;CcZXVZ &1 b@xa%H @Y$"}f.C<*1i Áʮᰠ* D<g8o8gVL32m=D)NQ… 0f +ÍtqG}15JTp"H= B/EzV5d6f8粈hFCp2(A +f8(pȎ1^:) ["")#r1I0X2,U pH5ƊL$:hfu.Jx 6eHStٍu9:[q?`9$dBVKı3 ЎUs%df=Ϊ pـ#S3E%c H LGp"[ ;'8 R(ʆ)"q, @phb8B@(aEp EBqGaD"a8$dpH ^pHY-hA Ápw=+Z$DE) áLAXFoet! 'R-XBBA ZT5+G%J1n7:kHtEа# \up‰9n7 iP -p "3Iϼx2즚Jhf6{c,E+9YzyeH9 ɜeM'ӈ\q^ګV9YVKarew0U)?΂GV|^!CTh£vK2%!͔!o;8fP0  +$ Tݦ &QHE*P*Nq )F1Eb0D"@$#a$O8L`%*A JLb @$ @GaD 6ExxQDqCX#q 0>0qu1P #-*l8$2h0 qP$09qq4;+Nc bR`2A +"v;5B')yd"I.nqpHXGp?#U#C* B a8ppÁ + )AB?,4ʐsJ2e:_arxSK. 1ͩ)P8P@p0 {ASTU`߆VW-,˨(E-j쬆fvူ)La ~)nqSjzmY5zD]tp0<,"&*p&fi䑈L"9&Co8+da5H 8Xsta=8đ8! +aCDq8D@,D%,q&0 ' 'đ@$ Eb)Ja +S +TŠ+ š@( +b-ja[t\x8EbX,8~:F0 hQ\÷ᐮcEf$D[f8vD`Y58 4%$Wp(~`ᠨp0@p4fѰHu@(WV0J7F30(cH 8 07l8 4fXd1+x#q ~ +FN\.E*244l^ZVRN.p +L&ͶZ5S\wq<ޝ0vDe땅պ2 ! N'SґQbc|<98΍M 06`8`p"C +2 ,lA XPAB PH$`&D@6 : (@R | L "t@Ba < +$ +  h +"@p h +(@`RA"\BHH.XA"@0X8A +*\pDpHX`*\ (,H +\"@!h ,@b +<@`„ +T`T   +@ +DB *@ HPP! +"B +$a$,@L 2@ <0 $D ! H + `(H@B 8 aa & 0Á(3@<( *@`QG.aI%($A @L $D ,dA$ "\,<@_A +D&Rs"AL>L(fJ&Crts+v3;`X* aAx0tʂ +<DxPAB $D`x@p@:` xPB &*ULvTDXE}X%H!^U&AP`88d\DĈĂL82 L8(p0`8$@xHCjXPBB„DP&d0A 4P`8TRAp@\@ . Px"H + xP`8L"L &T@h„ +( (p S A +HI8J*[u,uJ=zirI&"tK{:txhЦKڑV!l&<+dNjw%U K+Qz?*3r9Tx̆lISᝯBG$fg}>5a[DşL,bm,0TUƥ:Ye5oW4yY^ʓ]n{eNȌwL0f[dwnSn'JO㕺3tw'sOi#ߋ (-7!. &و|SQ!2#{s.}7"sߎ|~t·ԎT4gs4Wi툕fr"rhM3ʣzac\ʤOr5+TGޙ2&b)=O1V~۹Pof$&*)sj8rTsEEnXJD4heUBO YOŬrU1^]*AjVSBd]HX'g#+twUWXN[*]Xb9CJh室.IMDnKաJOBg~z]n[>,NzZnRd Ut\1kV>V_=UJz"Eܫm$x5̻24-ʧIttW ^`VX>TUޕb1*1,!bHv%Yߓ)9I_/YR\%H5gs4تSٕ.IO魂%D4`I>-ݴ+XrJڂ%4',˻Y>.L*D%bHVU:%xyteBGnNMIǬ̯R*reF/ֲYlR2xr1CMN*U%wfwT!2G%u^)+I"VzܔPf2C䆊yZJfsxu|Sr8 \ zܓIX%K5h*bK9UN =3Xqj& }3Gy,t R\: "d 먤;gЖbѕ24tPK +eASeZ,oSqzWd{MU\S&$3%3O'Q+6 hUJh*q#VVw+nR#6eC\eS6]ksrHl +\u>*=*3ٗ%d;)ɼ,#Ғ.+#=< HMxUzĪPIٰ{Bw1щ^ƨˎXMdZ#DN}tSԕ]MG-XG4WBuBBf3Ւuve4&$c13KaK˔deK"[eMKS|&J`9mkdK f1RܫFNIx7R_fJvTjޑXXϾ2ң3}K呼ֲ-.b3OXY\e&"iύ+9"X7Y!:>3f=HMgʝ)wsKG6C ;"jS^^G9,Yi-Y+YI%2]S#GڍC7ڰ3~ĊurH$uUF$R7zY}|?4X㲑JUiTч^_c\> ض2]fK4&;LI$RBD´TG0o^)k,>"Lb!?ptIy#ٔi4t-IDlȷVli5G RO#*y%T83R4:YZ*N)Ϗmk5UfyR5bK~jRi2%r,J|gIv7+yN+FnJyʥ_GTZ"?"xӱ;SYڙ¢鎲h4!fX牖Ҳ~_5/.L](4-Y"ds#R֦ o)wDǼ9_0ͦ̍ ~,dDzXfăstD$tԫ!`Iw])Y):Ubͭi;Ey94e_EElRR#titSΈ z™RvVB_̈;k^Ȏa|i){Ht[zxWWC}<97֯Y=ySJ<#QVɁ)krݓ<+:w:Z@K(0 axA +<@B\x`F& b +$LP +(sSĻȊDM |Y"XBy%,g$Oҧnnc#&讆7ba >7$yXt7X?7﵉Zsy:ߝ,T V<3O<ݼB%g[#"9}nUXnJp*am}f-'kf ]55XYjp۞iR3` xj\8V)ڮy^W8XZC_*.ꃆFWz)VdTBBUYYh5ݫ.14d2Zi +{%IvMtiT^~'Kz:#eg8<sV/*֖fnyC)3V<v96bNK{&]U3LI)˞Д)_W*D-Չ,k政*Ty,I9FǪtY>#`K)6C%KM%sOk⫌|):bE +,xFV +KǞKH%aI+gzѝ8e9en='2=q+,W^iRE|]"Y*m\ OSXVt{nͷ7#;+uY# h5'+֮8eKgelĮX6ʘ\Ty^HkHU=7''w+W4r)6d%͟6vds]`"R'&ogugRiFQ3yCWN+ʵ9 5s֗Jp9n.1`+d"f24j^OIts_w6S+fOQyZn) )5|,,}#)fyj\8;ͼ0iR)Z7R?J*즡ۦgoa%fJ6i߳r"2aN~SD},!$C+rӆwXgߦZdJVyźŸI6YP&]gK. +MB"CDCTv3o3#C:$3B칈n-|FŒ#aB +*L\pq H@ +@*"֚B$'n}ڧy^=1:JJ/{ˡBs{j5=޸Ţi}6Ep;!oq>iB|jO<#ϕ"ıqPy*c}{t.~/[3S<#*9Y$Ek{/vQKY5KE|շS˥'-\Dd]x͛m^9;sXD/>c Lxt!yא_*JS|4DsK}ЉR5,Biz[BOߣ3kCω fɖ\FweahfZj_D|zɹ}U6*y2J8Y} +烦YfObԏ}`q;4|%x]UbJ S/E"m!窼J.3<|Ib*N|?+>ěK9T?ybf;iS ˝*m&Jl'^N⭬ dLJS*5w5y4; +We{Ouj26pbcyŰe\+-{XrҴ{XICrmrVR薼^u*<{2x5flf7**v_!2 'kNXw|fa^a^.f.<,9u8uaf.;Ygp[I2zl:Y;Hepެq}^]܎eu{n|mʖD*1Uiut镯JX232VN5i²F*6ʛ(e?$l-bmm5?[nln6ټlx;tto.#͍m>tmќnvݮW->Wi>U\}~6W3vDWUl^Gוc,5^Xu©=[wgeʬ^<_ؚ{jrΚٕ{sяyşɪLVR*/gw2tL5rP3n'O"HD'3i%=.?1/$EvLWMTt6=3 (C:ɎkRyu7fڙhLJ:itsx%}cgٕIe>e$X|` 1.}$1\+IKR$etGyru:+K +T' +L^YJf!0ωxv3dezR|ĖLE-mhffR-hB3b3f?xAJ>8F8vVIxjoJ4*sևcovH*i>S9G9h.&BIG&hW%:* ]C"7 ȩu5"y+l6d"O&*sgDu92Kh=|YXdj*j|PZbU>:W.7)ggehlVӕ]eAKaf ?d.U v-cOz,&\~eItѨuYzaV=~Xcofi؜zhnm+#TY&~98IJ R&k,尜Fw,ˇvY/d25u峕vcCY5{]}ձ[uWo Nm*t/5j:k]Q +_q>s$ јL. ӪdIVslUm*皕}3O'Χʼdeu# >d I(GqT$hPHY-'{t2 pv +'gvÇf51}QPe=r7O|)b=X@\{M8iFɿYa nd8Ķ~;!z7+ +7)=}5Kܰ㊡KbA8U8*͚W]MJ geE,L[g=)DɍB)sYy?e~2J +ȉ@FP8M^d$,?ף|ĥ΂NъQ;XѐY""Lr=Lep@^羥^gr 4Op⥖[#FSch7P!MR4駏ߚ?j|n9K7&?; 4 +-vC؛8 ݧ78w,:bJ8 -)R)SEfчyð[ԛEjdD7XfܜWVofqij=Cfg]&YY&~|*a'@ +DKgkx]Yyr*:&^kBь5!Os:UYZR!o<]8kP]VwGw +B8u5F2>v5*TfIY-hs+K$#%Q!Y/gmGK^8L CjpqTYy9,qָFęԞH,A3z D ڪX48B sE8nSIpE_\ A|;Β?N<,kTȎhJ鼌}Y,P,/5cj>'ިyrhƱ^n gb딤Ym88kZz' ^8+^X|1 xLzI8R"'Ί5D7LXXI<ϋDp\,QwB 1&ٌeI7i<~Qg +/>5!SPKZ>TZɑ {t5Keq+i +]!f(/ J`(J0ҡ,"['V`"Lon[d@ڽ^@Ad$gm&zɣX-RY$ZC3BjV4裲3Cc78[S,YzzIغ.@ӟ2 +zJn,Bj:XUah˱UiLg%{{=63 qVybmVv:-q%Dʗoh8,HҫdB.YT𣏕Ec+=drQ(|qi`A!EyĶ&k.=ߒ 'S +||G€$u *;5 !γ>{Qm ֛ +1Zv1 vod4aj]_ L{A)M",8"c2詢Yd9/zo\;{;9VtB|(VFA$u%9ɄsCXȣ5(=5~Īo?|Lk |Ha%+HӲ^*Dt={>N!.o(MQkPYZ#Xei$Wm̥Q'?^s=ir;[F +GNw.p ~rW \`!˓/+QgJ;]s|Hss*w˦RnDx.ck- *FT,x%|= wüA>`mskdS- +ǨLUڀO:/2~Yrrnc/1IadjO -]=hm5W)*٨h29%F-nxQF3ѥI~rlxհNd7У\r(@,(t9Y[!{I +hC.Ym0&)y[ڤ \H90f"]Z# =]T/߸Oo̽&fLjXC+ìF#0E~x⚤1?>$o|uMJ"p1& +cv8Vv9%p\P Ed_`Y0o*đm&? "~҇qJdLd|B_[>ieU{Бu!}AC A-#/VH(A 喣Ɗh Ycűp6U9Tn#v,ܤ4(QJO V&NJ uO 2d$l(^şU2o槲>щ)F:6ePVxo8AN +5x\Sn W&% w~;+ ~@LW4- kDc K~*vѾ׍U& 'w߉b8EjŶض,WF6_Cfxz8WKC=Qv=pP)AA%5J-ܐ:a$>bEF-@-pcâ[Nc Sw&t=Wt1VUgs\5`wlIQz~ YB-S:k AuA4 + jeM ==cE1l.#.YXYF@B)$yy4 TdD%Ss,A@κSMd0U ) ˳Kw6@Qp +͂1ȺdWd:!! iSݛ#m$4ʔԈЈ**B]բ(23+|eQw ׀~q94gҏ |@@>vsQ%ais?FwCSJ-q( X$``6@$jA2p7ˆwzlYK/{=*i`  /YD%Rs)8d.R艞(ߔu;$}Q)ukaT~_tIe׈0X[*⁒i=趩c\2+)g)gqy{EDQ5S^\/v$H{ctDɸ5OMiO8kPZ~P+EȯO @:fa ӈzc]`!- arQOQDۑn^ɵT8Kʳ_qc5)8#yUzaϸ۠4T˜:|J̋yT62X|#3Đ/~QM(KuOOWRbi< +]0iF[ h`ܬLvK +@' +:3uI /F;Lx'xs8?ʻws Q/4>,%lg>r[]@Cge VzQY- ͒܏Ǯ)/VGBD3 / FZ& ef +X̨&VKJboӝpArW-[q\(XF殤B|^NSzg]bKcK f_sxK,V\b,9lDߨk=q_II.v- 2^>S ;A( ;ҭBS2gixNٜT/x̢R&}Qb) #.K, | h撜`R50"NU窣kgVd'`=)Y5-ڥ1wڤ_f>8^zx7dEWuNVcƪ8\A-:gWl'ck)PքK oRCڀlnPlFξ\G 6(wB'㝘5(&45Ze% KRM;7#,ZaɃ, vE7Z3A,S0W8>ˤ#xc^E!Px +6dyl4 +?RHB5oqSXe--Dr]=ҭ({m"F𩲌/V'#9DoH&t$#VɪEq>y#FJ<0hPT3+HK7iO>*7K I#+3Bn?XΎˠ@iWX$h()%f%043JKuee)bM1՛xL!O6tKU4e +[Gu6IudM `\hv8,8sRUXafl&Hw:sW,&T MryoEYx!k}![3)fYWM(€hbEHO$u7bϳ݃l/?-–^'y|hH}VS {"`d֢؁A4(c"e.Az8eHL74} O~5pܫSgy=,"eCyF3(4 :%޴`7W֤=v#Pjf#1D\Iq ޼=3fqSYeƣZC9awue[uD[,WSp7-.Lw:O0g혦VdJ`;E&P:1:0 䦠;qn#z9ի ~@xB%X$`^?|n!@>+ +hnp~HZ K.KRp6щ +ʒ7.Vj Q\4oPΉ~$}vxJQ*+;ۡJ7G|605AaGpUk,wfӾ|Jk|G(pcP-Nj@b7`ʼnoprbZؔ3)m,,wVaK1OI:\4[k(D&1FH긕oi`q|>Ɋ>x\d|~˛؈З{ +9l0NI:Iܿk I-ͳ"_\a +/:~al;78t +⮒P{짤YAj-jx,z:k?^: EPO ~˛ ~rj>bFҩ͌( y ` k_O;9~VXJ^ce0r;lZXo/HI<'$ƲDyfVP KߏdY*K{YZ^0_"k5r +9,)-0.9TOO)D5pv JV.U\Hd*.@Fo< t7nIA0؉`~'l+iv? +Zk~ǜ:y:1@ H& +[ zy l&fu'*pi{9 4]_P$3"HIQ\5CBLhʢۊrd;J!/iFs6ڷzQ<W-}{8U(CIT-;>J&]P`!6=w-> zF`.^𡇁v(uږz O\ǮM}?Rg=5iVif[`-T5edc\5X6QCSH7QD,8ar NTSb߯Toڞ<ھbe[#2]qLT Bp62RG?4 1%#,qFRV2TB}moï@!s6%'t|Jv9Mb#WyivP"0~gF2&gzJ/*0BSȢbVf©s/nE@:A+OP_bfۿJȹO*~ClABzGqΧh:J%9eBe$S +lΓ\5-u7$ˏCzq]bEHixD]T1mINԇ@n4b{őv&cñRwC +3U\8}P[ZnƤkh+|;Wөi5'4VSN5A@uw~H.T>cB91DWF'kNw]VDrI(Z.ú$\ʑ(֠9ѓЅĎe< 'L+]:VAX|c  1S>ճ)(0+5t>x./NQP"2 Pj-!b%=2T {h Ro9.|rfCXJ/lo`:%m ޟCBۚ "FhQXk3WF ͢UqLlg'PMq818U/,AVaش}0fX)֣r>0I(z_\@e_.4)0KbDaq0sgp]mD>/NGXQ%(Dw_Rd[ʪH3\n{{uSc~}효LAJԟ?%շsǪ#38@$9rIbk U!$t0G ;j?gF\):+Uè;{R#Bcz O\RE(h@.p ΞA +bW&V`r^DnnxwFʩ0@$fDΫ;QuKiߚ& 1-0p nNlX \bNe 28Px?ڢ+,8@2ǵx9qR"s{vΫIzFĢa\gv' C:+e3o+8#X؁GO`AiO8p\vK t@ +W?JrL΍:-Yx^QrnY6gHn'.*#Cir(gOgşHnuJf 踷d+x;9o,3lv|N` r|+M0бq6@xP駺\Np?[f.΂(Gdz,Ɣ9d!i熁dV\(xp n\ +;1t*rf-87zׇ +O,<$)տŀ:!=22>K/RTGs`/UYwWǎA2?lRcCT,'NYB7&>Dcy 雋983oNQ3w&1ǻDNNAjXTaj'(_W2|k]c0r;yjaQZ̘\<2>pTC-X'\7p`Z<'4-ac$]%cVv(SNELx?Pz̴VYVJ3eXĢ .h +v˸!5Mꝯ5q*N<+c譄y%;Ϛ>d/T3L]>TbI,ٍ`y +m g\n#: B*onz.TI%iP YajQshO''bڤFqFja+%zdŋ >P# #y`L@ww ],?ҡR9(jp2i3dzUZRRQ$UW%| .Hg *YZ7$,g +ZRV lUI +k[+/ĄۢXQ@o$gV+m9jFiҳr騫7尪^=NJ%рj*Eݦ dV'gpp$h[cqiz0ge`u#wxV\eEjhBzϓjfP}ڝrtD7G\h0QW]3BG)ʼ]â7(V|(.KV_yR/lԞEA20D_b4y[9TR|YY㘆tp +d'j4 R~ϨmPpG['Ose'Y58ߓA+U8SAV=R |?NAov +ZDG%7΂D Fp}%l +Bh)UEp9I?Jḿ' R"2"lR;Yx_ն&LOs üu&׾!ʤ|" "tOKČ hs~]}V3Ux2v&&X5-s$Zh:UBSOW$J# VIP%2>$7^/NjNE;o9r.F/\$Pׅ27`KE=r7/@j9eAdPǖ;&c^"ѻp}=IJ?m͠qH%ͦI@>tVtl)޹ z\F{4k +C-j51eZ*wu2J{$p&{M4 Mh0w EN>?0*?{mL0!0_}X\J+y1Le!l8."P@" 9 J:.ai;r`sh+)ʄ n,Tr@K%M' Ә jڭ|CO;d]"1>yJP +4t@ίyFSܔhy$}E=HY_PZr$\0% +M}-AK)# +^o&_7 tBo;$R'ӎh7 +Z+VIWEW t颿W6Q3|@,>L3qi$+BCr´qpqm~yQ2=?}bC!4޻[y۞F=spZ;nF|i"*2okmQQvAzƮjezz Wbd 1)i/gzNV![+I\K6JUL}Z' +^ke_=Pd(냑3.uW{U_[(l~@8!O`Ɂ:cU¸,EYR5`@d߫΅fZ 9ۅ~/3WT L8|ə1]R&nl9{+ʶWuj%/Rs ҉"M7]#3) [c?xFn{|7_Wr-@b$K asܧG%΍0b2I5ɕMw^ftc٫LOoEg\ًB+|+k@ hU7 D//%"Gn|R旆nXA +ŋADu0_E~W̕TwHZ)c%D cl1qDlPXnbP|`C!&hh@eb+1%6ɱhR%u d5Y蟥SL^F_M71{X!fwxpRW,U>$0r߲RlyysbƊumV{K~'l-s)[bII$ߝRRLI{ԙ+XK YNy%A]Os_$\ҳ WL_]>86fWm8F'M!fE ean/Spoe8TByOaQ_o)85-շRپd:jtqXԦ_TTSj+Rҋ;oێb GIɎ}~<SH-1JXVWq2#X' d0z=" eDNWb #aw4wm/U H_C6 3GA#;B꟡ "kz +ߟE*AjCDEC;2߁efBqyMnIl3_o2y-o;N#w[ۋD!\+jիg7#,͑X2~UtyT ]ӹNjR2xr$iIybˍܝ#Gn$AU~QưNz6υfwz9 ^],!J)Ӏn/Z ߫ɷBryӇH ~ /J˺p?JB."E.2Y.!o $xXAʑ+Ij?W|ȼ| +)>ۢ\ ]5d{ΐ|[^bq +Ѫn3|x)KN2Ny]Nޕ{)jB3e}cFZX*-C.wz/ ў˟o +.ոY 2$PmҥHh: apN`>g; ఺n ݂usӸG{3H#VK-ږPvzR~Ş6;jv4qwhQ<=/|з^aȰ͋"Srhts zB%˃^8P>zV̒k@I2nŲMh5|M˧R>kR{,{<9TɌ2H|ipTG{QhlɯdpҦu``nzHy1h~>׀pqd غc +oMIkIZN6E|Mtgt3(("`bɏt)fLL6Ud @bܤ:9}7ΉJ"[h8Em.%"=b yL'Q*|imG;‹kRk&gdeExxpZl/Yc j/0,$d6'i]'&L4:;Җ{-ppnKПgsEX~MWF'Bp>Lk-̃ZO)vxQc}& E}"'ho,"J}EQ8hF >9Q\mj_p/I/dyt4jpbxX't=Lz݈L{-3pD)r WԣY„֐AO69"荸+|vÞ ylRy{p?/+ƥ o9DYOP{B3\g\g !e{X  $m `x{,XtC wY"_(@QoorGJ':␕9f, :d_~Ugz̦ ~P0}aY؄#5"!]<I BH|1CN͚(\9 \7sU" wtr.*A`b!q LLWwf +/>' {\=@'bUlb(f?c*Q !Dz9q?$|[~~rxIЏp/L9c-v̉AY[8q7PG?[$!]+AFAKߔzWIfJ#G '+@ WDvktFܸϑG?Z{w:Gu)^C-qI7|67z>[g~m܇מAb0qS>^XMd4/NQɯ,"$]L oK9 .ͯ+R/h`|@pq^#YB`lbYi6!V=xt0[Oǒ + `HpiFHz52m=+Y9~8]p#čȍ"ltIʍ ^5Jc-]^jT5=C6 Hl<a+3`W/@vu4Y_ 6D㈹S"dh{Y2l{D@6!nYl޴8Qb}HcYw:D꣝e{.h›xȹ3 ċ\ '^ȩ~lC νeZY.CK]lG mf(}Z2 1A{& `6@pڢ $WZ 0sJ }y蕹̎04=OQ qtGSNw~V$ ` N4- Ҹ qa5JNpxI7Za$ 0z|腇x(sPnz:!&KlRO w2n!ъJqh\rcx6olՒUjY9YRۈ4$L;skPAeԩXs[N֎%H^V'E"+at2r}bcbz]~3zc [(("r-@coLjf鳂)& 3NY2p}IUI⿡^P^՘6s!6|`VSc~  DA! ٝ|=r S!Lj(2b$Tz6SEQy,&Շyh]ƳCF ?yYU* +ߗ:XDDk+5pלs7ϑ ETx@KEچ?;Y4gl)}ϸH~~?ً²Eܘ=X̎gOw$%g ,A\TpY3hVJmCJn1q (\6€[O9~ߚ?;rO}ĿlgX6wlToydp>2.$hRZdԚn0\An\M:=kzBU,Ȇr[94o$b^¸n3./Vtu[xȎfZ:˘p/L~<4kDG@69XIDQ(Dhe +I"z.q"uazn/QA" gACI:Qn%b.@ޕDE1gy0@QEQ-L?>q| Q>NLmQEt b)^@~;YzhY,&8Yެ`C^< I6J(А>ZY,5:M G>9HB3^ MWG]gLJ~Qs (jiGնQNN:.Ue~=b2V-Ly 4aCTLHoh~k4Er$`֟^/kijmo Ei˞T&1ry;޿_V zߌr Re +<*\} vzA`ր+ ;_0qR5$KV,6#-fk co zK$V{1[P^('sh!p3I]sMZ94Pʘ$#)Dg>\;!\+-]A~j0c Q Krz7a"‚2Y^bQ&լ·RC;4cȊ*'97畜ʴČC#ġO-q |%Ϸ.=ҼF,o QiVs_\sL8Mr 8WVQ_?d oPTG.A9Ĩ#ɭVDEo2X/;!E%/ۇ)ˇzV*&xi֤ڴ֐]Ɔ*γle( )MBŮ@q=OzB1(ړAQ765 /~Bf)ŻbYPܐ#ݛ#ߝS\<"E﫽W"i`)k"V<}R9MvT 3K5~~8,ȪYE0;is &]ې: d֬8#tUt6uÆYktgPUcn. B^$W 䏹USri[ŧHkT2eZgDx/##1 xh[ss3O[h )9b $fx%nl}HgADg확W I32:,K73jVkU4= RhA4k^4c(})5U<9_6K ̩ iU&!6Ҁg:Sfa[#Rè!!OЭ;}H{GeH 0'W2h7􈈐_BK'nG6 Ǖd*c`4+#}lj E%t} F +}-HOS}aNLFnZnޞ;2b;zKLsaA- UBnۦp;S^ڐy;@:TMLlE]VK|1Z[ܷGY^'Ӵ?4zMu`9ޡvsXreJC_%xM R CտP0+hB'Vf>Ouai/` Z pVHG.R0`[Z,UPbp{(o$|ɪUUl[ `]X$8SZ-k 7D[H-_x<^69&9Si*' ZlMIZ~q4HݎV?:^R!V4L=ɩduZFgnWT^Bއ 'W";Ye!/\(9fc{93W8|^#&Ew)(%$;&t[ew29\AMx-t3rbƦ؆{7<=lGX/S 5ÆPJvUKI=;lЙ+{DQ ~7_C̾>u{wWj=:dbh۾k!f9k,nˆ-QQG~ %= tz(A[$2KEag?E a;Ff }7 J_rDff,F@`k#Dc(i,Ɗ[ 6WwCYy28نg(u,>(5rоL CU5[4 3m[q;EGz{Na~k SÈb=2>+TPq`)F=i)8&`2i։?qF=TVM\v11𭪰7zlM0h&p?J@ŒaWрJ Ine_.P[? 146xo4kԸ!nG\{`٦WhrBNpF6>Gw$yP9ч*=W[;t>Տ9ޱBrm2?U(:I۸^Qɫ +֡\Jm1"af?N4S6PJnP~ **P@'61k*Әd B2u}}uȨ8zMہ~!95P[QTfS'U z\l6 XgQ%c4 eA% tՅJh7aK9a@{GZӣ|pʕO^?sukC1~Ilpwv^]m޸?pQ "$!F>;-JWH0zo劣Z~Bl_7gq?oaRӴ9*Pϊ] XT_\2iI7$)qOI~M5`,?$A^^cZB1]ǰ?($,Wp*m=-o>^n#O\lQM IksЍVB1X4 W@=LI̸^0Uh-:鑝#HތD#zFs`W&R53( *#Q{ COEEiz %&]֯qB"XSҨ@EL՜,it[I~(rpdx_ԥg\?ZϪS{o]Sr Sߓٔ_a:aK3kq k\:c} @,y[^nt4):Pk ;dHa&ף-B(KI_&vSܥƵQ)j].#2n_,{e[jsi@rON&r-WJҕ4 n[:uc4_"j|Eu hJ<ЇY*o<WQ,*~57Ґ(<iLiFe]hE"70WKiGhs/L igեTR L1*3H恰rthC-(Ϻ\dvVde_]Ik&5:#TL[jIk> (2^@o3lٵ`zVpsflDž߇xysNrgN<'EADT(3zUCitJb+kO?;7m0?/=z]&VhW% nh$IzJ$)%y zaSINqd]RLF"/JǬJyFgu?Z{gmn(-D-e>~)i:/u%ZKiiz!7ሦEX'i]M)RB)yE=tIjvw]yG[NnΛS(F?Ķh% ʇĠM$< ѳ*!̈'ď"$(+iP,rD>܋_4.JO0=]LЫ;j/zO¾?<8喾!Sg&)K HjR5]ZKyHLt$(7+ #AetpUH0J k yfY=R\{xƒMHRW8 G#%{>M~!̩DCa7!1RiOQ<Ky~L 5lB"qTED"BcG>Hk! +$Yn&s=HOvWtx{ׇqX.qy:}3|EȞd%=%W͕+4D#G;x~8?x8ͨqLc>T3_EGHi!jܾȊk}G84}ݗp19}u} 'o[w(~E?D)R'QXKqDA}nP!ihBFPq$HӜ/bDeM#֗`?ssxaI_cP]zF%׽PdfXM(4+qSfFk>?ewab=>Ri)❚虊sZs❖#VS9QRu>Tg [ƧJ i|gZUW]tx"Y%jH\!wŢlR}HԕIe]K +wq]uqG&.eh*u#; I1zHƩ~r*!-㾽aM~͔g<_$ҋpRGRWI$Jc-Sf5Š 2*N6'9p,΋[Jh<B CBC vբ&kqP]K+*nܘ.>[-MtqG21ѹmGlhbl'lfj"H #3>+˖`QAR_j rLɻ؍iui`LtT0 IQQK(d"둊!_Lmh$i ˣhS4hnC'·&Bx!ͳtfP'|EJ!sPj6!(d&tffCaffs!:*>9 (F[h3њCFIJ5>b _b3|Ƭ9_EQ4bBmmVYptɐ*xDn:SF_E7\ f^͹yݻ^y]_승e%H:AA5AAl22bC/ˬGW])H%[H\JNDg䗷)DlDf8HKF:qitCyy;ԸFJHfju_8I^LMC$|UB$8}e$C;v"GSB4GSh v4SJ^> JPdkD025C\Z!:hKVء5>y!:FHJgAj*WrJS6,3S$+kf$4`>g4-FLQ?3!qR65(~XGQFDhx=^Hc}#d>g>voS7ӎƙbBvߥ6pw xX\)P8P(O)pJaeH4IU*ipѩiꤤ"n(2ǬZnxY!٫%LF%# +n䢬˳gw*s{zD(}*CS."7+#"@6d a([lr`q@%J@*y bzuzJ%"'(b%gRRGn. +|WvaLUxdHV5ĢM|T*g&iHѩp Uc$60b:bjr%N1|DTz4h153ェuWV:Yůؐ:bOeD;բXk,'?EfHQ]*E%`Xl yRòVv`YָH+-=Zzhuf_Zg_>H!]mkt!ϴKҴIJΐl\\)LuCgfR~џҍ1_>'iϡ\ƅT_en[_Ή_SAvUBZHIvU$N .rh~)͏2h~b vQG1j( 4RR"6="5_>ΉEe Kdz K̄ӇPzbl>EEQ=KFGGV(Yt_Z|H5C9GY!BV4A|dkؑ}K8)*;0$ʢc +DNxBZIkQPİ"ƠUp9umIZI'CNjɘIg$PPѝ7d +fdDG)4l>Y5i-N? u#e"b,٦igabz}#G1t٧ۃ6/ʦ!wlK$z|qc7c֠Wo~arr1*|"g*}b39HPjU_!X$a]6J+}'3;+Lg+R}~_'٢bc?ZN,ф%ф= M-hh%WJQ'f[žUUE/#2u!ϞL/mtZDǔiZ8><+]&QE'F$tO!G6g<^BE+2)u踈uV!"j:=>>Vz~B֒ZDVlm"EҋrԸR9*Ʀd7rw6U1'yҢ]62:PJrPPBIIIHJ< r&l⽔^jUE$Wu411Ux8B.p0BJL&2tՙd68i]F#B'`P*r|ҵD~m]%W2"aWݪd^fdCxbeIl4ot4УIcu5>uJ]$1ԑ_H'> R67dF7 E^Q }MBҏR(GX +)X++C')x~B&tdMFФ9M!okS3l!)j}/u=oVj"92E.FQ+`gd43##epм#'WhsьUW.C&#D62as\dF<'jr3#4:8$ʥ]}xZќK͉`EzaG# =XУqf/RnpoWO&HEb\\ o^._:iR* c,I1e"a3QBGk#D+)ӂi1B%HtIW>Ō,ukH:GygF!sVG7G|&9ňv)e/#PQBIΚMOZ^|QڼZQ/f*9 U n.1&hfpU¹5p.ͭC7n&ֳfV3ڌ^n^&֩P#F<cďyH nGiIZFEND2Th*e2 eI+ȡ)&HB CK\^y8Da%1U)mP\{[%%CV9 +Y$ vBs[JCT6SHP=kD,)G#+4-05=("p(ȫ[Q}]=h +C(VZ9IHIꭹED3ݘnz@EEڄEؑ5$G5Zr^q&SHNY~}ڡ'Sq~B:ca +.s&%S[HLG8WE>9" +Qz˒U%hw*Oab$GhE,4%r=9(O2ǝ,VZi+CX#o/mE$5V +fdƋ7ZQ;j5#U\KA +dߦj(%ũ"V4#4G#՗!,FbB:7XiDY()>JtIf.;Sr +m$P|1 O4$Wp(3VMd +R?2eW?6CIe/[jЋL1b"CXo@iGYAEArΉhA VjfgK*9#슯, z8"1C: bʞȶ3DlQYѷĝԯ#LCbÝ^S&r)dN̸h"l5r)H"bvexe1|qt8Y-'UxAD"Qgg+ ,}+^]^bZjǣsI-[AY Iz br|厕 bQ!Via' qy([Qۣ +CoU=X1E8x1GkݪRmWU"VL˒ƙ4r%IOT)Qb뒤-⍡lrQh9JLFVCX(nK$]"T.Gcӊ8Y!xp"iL̼Rٙ]>OJMŦ2 +<nHΩpgʹ'Nk])?HsEmzтKG2iJKO*Vqœ^"ntAESt/ iZ$nfit8aivG"X]Eȉ~rl_$E-٥M6mutM7ӕ[GvבQkE)\NJFP9B-$HX ]&10Eu3,+))^-!+[èYNC7t\B n"ʡpPeIC_paƁ\Ύl^}拠EE/hNJ`e2Na~DD8a5A2F""(cϾ \-""aw^̅qab r  +CW !_p4,dS't/$").ك` +A +7>rp`w0)O#<"{  TY@Cq`A c@Ș&JL,Q?2#([WR'hAN ?3 +P7#ihUI_H)Jq $̴_0ZGbكS.AJd :z f<|,{AxSǃ5R/cvp<`W + ~A2 ,ځ2|`ax0fP㌗Du!"x`œ*M85`B} `po\|6sP r'r 2\r 9808A;ā0P-`pg "p`oM`0u#7ع66hڀmZ0ld408l _Av A5hf2As[ ˀAsՒ@>P a>WԛJj6s))x)ąbC-ΰ KoX;V3Y13(~3h\ `9(K  'z ^q LS*sXȋ RM 6`p% v/@VȾ78^8d(0ƿ s/uP) T) nl0GWX!&;@ +[=h(yș8 y.8 AD̙v\ `8XRp\`q)pD[`XYb0Ho0H` ƝQ=w ģCT:ETlLL%Ax: [,o { 6vنߐ@0PIVɌ+_a&j݂gĻ(sFu -H^m{vaP9?kݞ1n [Z وg@8a{ζp$`pWT=E!'J5qHt=)T/vR! %2ٲp({H} BIFo.BZ}ܚpA~z1 ڂiΞ· ,ǘw& ?j0;!wSQNPkctWxqWM0o]zH5H!^B $RC>aT&&. S2NLKpBeKK +]\ 6|+`-JI6rAixV: =p1[*vH Qgܟ'$ȥؒ@w%a|I  r GThx#ie= 𔦃{p:ܣ?+m}Gznk7oLZjD2F~U>gamE[#HGZgTMJϹh5@v.`Ġ6@D\q4S|sob_%X/Q[F:D*32)}Ak !o!${v= bƛMR6^ EYh ޣPK9 u攔i'gBpyJ'J!`X2 p<wa/rd h.@cy 5@V@AЂw +AH +xe=Gl)7:I`@g?_C&,^k.KVנ~ W˕g\WH'́ x&(EF-ҤĪ4Ýi%z%A=0U¡ XZz 2B4&yu%x,-8gƇ"Mž^~z`hЄs6 +lI 5sR߬ I÷:`/ YAyu@-+8.<&p9`}L52.yHSzbqUIt^tqGqἄۺ++}_7iXш˄(?fFj@81WZa{\4SpvN& +ȉz=I`BdG8PaSG}wT^ {RWNiӵ}R?Ԙp_7ptYEsnj8@R zBY JP>$(%w +&q3Z^v_~䱁7%%J> :@3[vHUeRai`Og)0m5m5@3}5ЬDPHf H;WH 2X͸h .ȇ O6l;% D>$g&*rO~Qwp*20@3S%׀ThO'!)'|I.@у1),'~b@|q5Op_dPLw%i[ Bao&?js;3 +*h  y n43wJ XN.PN-h[i@gi4[xZȡ=4ꬁ'+R{0U@MR. bXKy&B jL&(x^vh%>\ 5@4X})6\prMݟA˧2$^2xp6 iiPfv+q +i^; ]wh@T> @ 7>8')@:DMbz8\WӀ#hrr:= D r'.yyd̀$(mlf #s(S# bf4F 5 |a: p=41,*ւm"VV. qem V<2!mTJ^0OxV,zߺ.U60fƀy-Z)'1 -LtmOH:@  `@/Gy#Wn0QY0@ WW?%ḵG4/m!$bEEZDDg@s BEٻh'Gg:LMb=1[+ICɩ|q+`)4 zqHK +g w0/Ԡ}W9CT'8FDˡ5s74"+`{P4_5 +85xB +zwv?@]n +%DzQgw&wW[ +X_uPoU + +ʌַb̓AU@)5U\YU@rTc-O`4K0D y$ +vT +mU0Fp`2 +(j6[OFa +:MTP_U 4'jZo= +6t3HO MDRZ4_"+Ȏ pA}*`aDܸT2YQ<~[\Lr"k* 2ǯ] +?FfduU"S  Z +h6ZBm^Ӣ?JWw5 [S,,8Y +8e*U%^ +Zb {ץVΥU9r*1ʠ +Z;<*Z*iu0SJEWvAs +J"hqN9F8kssA.) +H|  +oجI< p&dޥ #P H>ׄFJxJdV ПJ>+yeIv4}S.[\^OcaBg P=@|ݶ$foV~#(e<a3$hZ µԭ'yhoZs |@c(R'`B )lQ_RuOюq&G+Ym%@EH.kdfS +"=='(HwWB:6#N?H`p Ҷ6a7Ġz]PR3,~]tֱ쏬~^N. ^^i4_:཭[~y9Y<=ӄ{]&[a $}VAGᲥ.6r*>?s1u9,oNMTV-xVP_iVCXSOڝW&m10ONG,t2됋N3=בTVھ3^Q @Z;{Bå',))A OT?N=uE_\wy//mZT$L<ǫN̙ `j1ϤaO@r;lv޸ݡqLق̩\}qhv/ Tt74:mYIowar(LX? ˍG_I(:#/V8ٍ¨̪M:;4W k|#g)6)$$sI-2=G C:H4,}I$U+&˵p"TF\ͳI)1U]ArAك"4%kzQ4HC"'nzsAeٟ% njjvDݤƆUG4n /(מAQc1-^ "[_~k"oqAOaIO$glSW" 9-~`*0l$4=(ELH>$v[FFV{F`C&r "SSQB_ *e~EO\͎?J wyLCً&aOxiG +ZW"!WДA':ISk_W[k2iK t=CI?4?bJO\ִy+Vy2}P$'u&dQ]XJYow w'#f=3Y\zg@g:c*hVI ):'`"4,¨3ek;c嚝a=bL)g!zrb@~R> mBUNGvlf q{i6$R MS}bW 足o, +Iu2,V/=I~țzKM2Ζխ_CZlHwfA~Kt‹XS?^2V'S##{ |"\yWooi @|\i C Kh"#z>퀲AF8举s[!k9' h?4.ь_Q v1Qy"vlZ+nSWODXzHXXx |}i󕪞݂5͆mCWV aGMɟ4.BI ; FToCmh)6G5!+ɢ1|^D\*pÝe{»+ )l83l`ޱoe?Yɸ-}c]˶~nE¤cVy!;>5Q7N.//|Gc$o37, MA Yv(B/ + +{V<I@gbC(+g L[jX=B_(\W`a I #F`Q`{AJ'nΜQFgcoijMU*0\aB&3⏚4c%ߖ(J;WLɶ +l)!Lm!\ðTG' +¯ ?q~U_~]ʚl1rnaaPhЈH#'¼$ڑTDaWwɦ?zOԩ)ҊJ12Ѕ<>|sLk|}#U2qI{Yv߿=]<>+o DHtu%*mZvӗk0[͉#Ȁ"ݒ#s/9dJhM]\ (QH'J쟾Y󅣭HN8.z-@f%?]KFMi͍p9Dszd3~Cia ~#} kvuCb}gefh.K;ie^78UZ:c+a\ꓪB;7?<PW~\fư x,D 8( ۈⶢ_+yҿ@.7 +J-#SU@ 6P!6!$<D&F/4a}z[_9u&& aC䯃,Dj({CX&$X &3VW7?G(d'=\*鬚׮k0?KaP !8? i2aG›b6 [Rv/n8*j[XD 6ǒ0#-oK2 3UBM[ d1_s[.0Z+?TJƎ/7쨺qw-ߔ$폚'p9XȰ>ojb:_l@and|ZOݚb +zLLb ʖiN[RǧPY@iVOZV +o +(кi~N$faΌ0>`v(Sa+ƂrOm-ZǡIK|š ֪ +кlSCO1|j9|C5|PGNb@^L0F&,#D~UrhF*P1z[u8R4B+m]ho& +Rl6X~,jăXOSA8.~4~d~m^j-]eJ oC#}[ws.48i|Ag;T$3/TCd <%%S&ƴj__}QUR?tȼ7GQ`WQ~FG`ɇ!o$Cl=簷{gMev{W/{x{aet/Czգ_ ݇e 9C1[~nx qrn*&^H!~zi(6W| ~|F`YZ2wJ +y Wom!1|QgC\ۻ޻o-/a!uzTDqoxGa ]&η@fePX5\]*yGz>!ߺi>6BIsaע_s +!Q_1 7}wPd+)m4y{rVT7^ KX8f`+ ;c۾_ql{B +jM`5mhBnwWv\iS%d #pmEfd/3,#E;<>t„VibFmlpc^ +QmjmPH1kX\ݳ R( _/S!= +C?ыm^x!'Gq7}\xv߀܌mϳRq.V@ +PߗBھUΨRab։ݫ? ~eseR° +f?':]*-Wm"Rquҟ?mOp(Ъ% Qߣb3tYZ:9ufuj{@5}븥>z(o^q^Q3j{ޅ߳WfU7&Nk]ʁWC Zg/7Njc%Q_q<0T 㶸JA {[-)@8b"~BNvkzV'~+³++nd+:y*=[1lZN։`7eo;(fk($Ã6NJE%u^чѭ~SRfjNt<(h;qVSl=X{R 3ԫ8J A #\!ݗ-Ct&Z.FNJɤ<&x?!B;d[@CI/\:HM%lܕHk)U&Q頑 Kmhz(uq'ZYGBd+(OX:=/y0HEChgPDӖRSTH}Dd:ugrL8PzO2*qgaϛ9tߎERj7̑`0;f*g\e*M! K\ҎtWdǤ9(4b?4d3})WRyq;bHN[sO6/3CҎޠk K#Uje/pU[Vqƍa`_Cޝ5zޑ~w[ WZ+vW\-xB՗Wx?OZDjL@k[xmҕ&8-~لڟoeY3!i^ < 2BrgL#8ހc1\(M//A z{SNb4!`N~ \Ҷ@?dS=;Pg J$2;–ksIq' +&7PDm :pt&TKܔȠp W [S*NAExAML"כ Р\)  Ҵk*]!@tۄFe +צev +J7$4pt4rzSZ&2讪 U tU`ֹkщGQ t!F*s%.VqPA2ӎM2NbIiHq:#!9ঐsU9%4[L&7a0%JcAlƷ ߂m;:"i7%Rhk(2mQ~pk:54Frcڔ\ ]#úlqchН)cT]g.YuD_'. WxB%1Zً'#=jA(\84iA22Ulo2WM +hAh%ܗgEAh `qf;ɦ8E>*ڴf)%QJ-8 lQW=/g +6,* W:; w (k/nAC4 p'0@$}tUghxoU0/hȞ.EdS +$$pŦ.Ґ~V7@lCQd`GA(Cns3(Z'Ĺ®-H%pZb%}U:U V9j(N0L~$EٍcSd=5Tv *(MD +ɢ|O~φK +NcfeQMn/,9Oj|MNy#a4 ZPRʁZEz xF6f_sq 9h[ u6%8tg 9D @Y6-kCB@aĻ ¦R(B♇xo7ֆvihAWJ R{ޭ4MeŌ..hH] G>𗴋'anڠI>Dj:;gAuH3dAǣW$6FYDF A 5ậ5IқrR,D)|h{A3glJCC6m"6Ճ*SӉqOrV^< ]:s̃ʍyЯyM<&=6faҬQ-Ώ?Kp +X (nfdM( zxl0,Hƃd1nj{l?HW)@:')1@7mJen`vx=EH +1:(60uIٷ& ܴPnR?;[a᦬ kƔ<>}:AB}!jYedb5DVq-tkޔEdj@cޔV&BySIoXw.j>+g,V.2&<'^rA.:PrMJF(.U =+BgNkM,=>͛rQTEHG*BB+ !ڶ "<}EtN$>5]z2m7i*BE7K[9D;"tBcgTWax;W5Mcܲ2=BhhJ5.Ʌ!Gguu!*v +nj,*YM025D[+Z;_M !e)BdX NODv,j,Uju/5@"C@[ ! !e +@K&x+KMf0B3WN\#Xj,@$B^CAZ`!y&1܎8ZI ^Yz!hyݧRBʀw0"̥璉L@H@慐{+ohax7y5B|Z⺛܆] J>ca9B`#R;%$ClؔAEwSьq c }PkT:R1K'3˨jcrI|9 C+䣥 C讖/B򀄎Rt .KZ*KMlSy!b7AMPߞxP3%TFLNJR!2F`'BفH5ebvT֚`JERB8fhĆ7+#XXJzUDt K'!xM)w6L(<'D}[jNڃbBYbH2!s&D|dBu$hʐ2|A` +YgB#nX΄ J׼N,A}FBOY._~OI\ Jx7+z-JHTkjPTIХ8 ;w]Pta(cO.OO'w"`0Wbve=pwN^TE +Ņ M\EER.#($5Nq~nw}k;$12HD>C=$?]sϴ g$#Xߠb@|@aos` #FlӁ"s#k|9x.V82/BAMsGc2B%5"fc5y4wflE*<$di%d Oe/J"r;"<z*kc6 ";* ICu@-|ǰb1߷~Zҕ'cʨӹvIf($Qy\*d0IOPVGqڂAI"(e +@<7dR qfA*ٺ@~:/PQ S)MyH^au'l"u֔HWXpƫ$[=qռJ: -Wo]v+%Ifl J\H-eJOwJg$|gi +TU͖ml"+Jttd%1q3,prSZ +BRXč۷AB'PА +HBR/ +y%ǧ4=rnH)7H +X*Iې޲r俨T[`i.a|[,LYiIe~ RNT(t'Fzo7L)*iU}XI$*AmܵW*d;}I2 t 3ɤ!%L!;Æ)r6\_ +SX+HfSڭ ֦vi~cj]R +S02A$Dfʓ`d/(H3(!L;4Ȕ7 )!MFLQH~"|H da\s?X -PJλIk?L9{_^|jFTXTj0U͈UVlNe/7I14fV`*No(&D71^g1`r032oM~jSHS1cǻ=n~̴*c"_+fjL}\-㊩)|$EZVLl>^K=/(z6uwӘHaHmYs1UÉ'qd,I7,9c>:$1I RgfQZeLG適uHM;E|5)-p$E„*(-.KKe".epnڤp&)'oSaRlLbANTRGK[ +d;F@L`&Q~#gXy'sT{)b& SaU.dRzL4beϽTbdYBR_L2AbXo1aS-nH7pPŴͪFV ] ,Sf"-ػC8쯂)Hn)a1)nbYbJ Tҹ6eyp+n@b2&CwN;:0b Ku>S"LH;nǠv0=߀:թLRu*<\JZ<1paŤpl07~xNE^.8Op蹘 TƸئ`1u݄x gTr/}r1В-I1fɘ $2)&WW= c"_d0pk!gi}?>f*띩"2xTsClʗX4r5ML挧Q"g5cS2.̩@˲_Mue*#*&kL`@dT INQi󀲡+?u.׏(@ɐ{jdedr[n!Sa5(5~m2O!|]iKmħ4_֔hoO",][IPRs88,R6conRꓥ+ +dЬa._>k6)jj +endstream endobj 12 0 obj <>stream +U%Yz! "'9&+y

T5 NQ:ż*B=GqNQ{!Lғ;$S0JgAeb#?y"{T]1kcp xVf$ nWWm€ +~Rył$n`8&3'˨KviVrGpM@T_.Ww\2[},vI`9nh\oGao*14 HbZw ӎ:>5UMRǘ/ + '{aȲGb%#U/J%_YiRUrRďnge:tY9xh{kcֳưhJ2(5TTFOr -JMX^@gџ ̕ԦŠsL㟪vg +Aj@#IʁX6Sb)WPL\#䗥Zr\]y,PS3Ti?*5b,i,W/| HuS*ը*rgF;bVoW$K_>,4LU*l=gyWAD HU4_TTzj*fTً8/0E*dMc'kqRCe:N*n+.IuFJ=3~AD!T+Ld]L%qlfvAMq +Ojo`U~{.fWx@TDMB0 G $oVO +u[}*?AQjaauu@MJV +L#(SO^m'=שr:ޜWh-/P:Vt1 +[#[W 8rfAPJئJ~51~=)b+ f +=y`U b%GV@\0RU+=aztlVZb)ðGW\ MQ9ys%`#K!m%7Jt+g9:̽ZJq +4ܯ@ɼXu,k- ?uq$r-6u} Yu|%X%GTiNV21Vxe= ! 8Д*k]YçmHnRNpC_Ub!?/ZVEfS/BqҬf ^xf_TY/(\uu֭L6$ϕ.k uօ(բ`Y:FkRiSLWۆgQ? <2YJYe^1g,w-| @:SHZЭ +oS唇jY@5CӮ ®gV肠΢  (Y8Βf:|׶~^u_7d}H[tuu8` Mko#{}2³ +p-@8E'?xvgqut +ς78_ZĻ'C`ljc/dgOI3r=$U!x,lQ05/R-Y5bU1L !YMWKSd,#KHc|,y* g5yP_=WΪ +V ۃY!*`kY>:qwVQSEBu5Nb4fxo<+ߤ5<6E0`7ֱH'D)|!349(gI:x9Vޞ@ƝH[1|r\owO-̖urW.A^2r kޓ-c:L!N,(J,,E\MsC-PsgPT;P{C# +'tg90 +,?*wm6 rg)i #MG(rB@æ8{,QDT"-E};򀚺Il\z-;BFJ:f8dg9'.PLKP Yh:5U,tɺ|NgոYfͳ[/R= !.9XIgoMi.?Ng]A@:r,e)nSg]Y8f,4S Lud&nl]3I 2;I_u:@w|ꬤai qф,[ 䥗!'d;Pc04H͂aH!m@NsY-ԹiP@מ-"hjs`W3g%AK/.YeqnTYtyՂ05Bn خkZrBA5(ظsۼIe(e@.`E1TAwBe%+L7 ;ql7BeIÉa $6[@d<FW4qpDh8_mr@xq`غܗ5Y xq7+O94M +Y(@E1 +8 & .)Ϊ ޏ;n{.Yľ4L~&κY-o ;,j08/WyTXDY^%Kn +i'z0_$[t8ijbLY 4*B! ݂8\+"?OWYjTj~Pqc:%.N{7R.RM)KwLUdIC#J1*"Z*8v}WC ,0JK1gIt \ł +}g!<⬿qqV"UШ%2G} Y#*1^~o?;ꚇX pVm2*}2Op8k栊qU(+g. +01#Y|x_ho YXlS66jэ$Bfݽ&zf' +>ð=Ͳ֤URI{'j6XC@ 'r"y@kf5 e,)D-鋬YpM>+7R1H좶fŒLhmլ6 pe;'w:BgkLƪxYEaMɰB`5F5&#`%Ԭxj:>ѣ7ZPf!JfB65KP1Y s_j1uVb28Ue^+ +vfS$MKÒF,H=qp>ݯf4R>hkA6R/k4xzH9^,%@!QuiۢYW ۤYd3d>;JD2ODc5]0#Y[at4LwtY'H1s2+f'' =&]j2o4 b |iVd.bGg4댵}Md5 l焔90$w1cK5oY3JYRCMҨp^4_ZM|UzjgawTia3팊OJc,vUC\sIS.4_R<#YD1 {h>yVT5-YjVA9r!Ii"===YM!SmëfOjވ4T2M]SerUȋcRU˛f+_ŧYL4Kzc ~xi[ri3 +RfݱԋcfeYf  i1,D"Ĉf9 5Wn0B0sfك6^4[Y4I75K^p 5 2Ca9&LzDE Af5ȣ@y-i)'YTiG0apR,G@lft;a+7 tIcUzLSQ#U*F\-I´x⇧!*AYP4y4\n -$;FLOF0RMr73f9ԊnPıfe:/f!#M⍿,8D ?5+<5T>~eVfՁ X֊(PY@FQjF4n/5bc^ 27E8:]M^4⏿鬻i s3YN%kf1)j۠8-ÏJd/BD8m\CCndNmAF;@Li,<|ņM!|Yeh6 +4h_=fa5p$궑hDhٓ!c,Ma4-9Ufj2PQ4  Oڋf}d,: h<p;{4+estdf ZjzSDenGa:mb@ A[ .iȜRi ̼2r S}c8znYWl*32ONR ͂k G3+9@"2j<^:?Di2d?!YhV3Ft"`Bi#*YbG>F֢oG_ Vf9j2((g@l0fl^sS..t՟%5@ W؁ø0c'\iV!N iT.([ 4k'!f Sd|(ӒҪ` '*f2*a5W)%/!cfiSb5,r`kr`iiֽ_J)ҬRiVXb`)Y犈/lgGmUm1R9Y[óT}(9j"f_ .YrYt)C$u\X7`.Bf즱]15 xMAa"Zb秾_?9g@vYWb҂|5X훿YH2NPS #Izϊ?Y>(wɥ+Y,ܨuoo[{aX@5,;#C䩭Y+]Wǣk\h4֠#ʳ?fR dZ +wkVO߯S^r?a@ *B5ϘXlId5f9L i,AC=͂ @VQԧYy]2\,Sv|JܦY.4Ku| U\?50Yn<-D^ʣl],MYamAr^VŸg͂M戚/lfXjVͬ@C͊`h OqOXfoNmF=g N`604+jf7LaaC7͊iyVxuh)(1J \ȝO0ݑPL0ks@4~_{f}kVMno* :0 +QkVfnB A _SWd5(K ՈCjYEΫDG'pܚ%Z84X^kV5 t:֬zY qC) oRY3شUk\|SZa1fΞJ5+{0^|i=q֬wLi%k5 s Gf~Z*[u=٩{PJ {Y@Sm8}Z@pQ6pÕRHͪvU5PAլ*&+6keͲ/ri,>kDY؀`2-\ +?ci8լHeͪ%if n)B5 ŢSdlYr,+-jV"M5 fLlĞԴ X>p/hY}=(EmhrQ,@/JG˵ ;Y _bi5=Ⱦ=^L.g 3F0e>N7l;gгo s2cg/w)9T +vm8X]LC,0oeEH U3 {ϡ4a'd b\Ab#,L0$B5)G?s\1T:xsH=(<ў&ѧ +Qf/:%LA x6ND)*f"mph>R!Q {+z-eo +͝O{&@&BT$AGQcx +tL($ ~xuc443̘mb9#s]ۥ f>a|EiE\? C/ ѠE2bI%>BCs~ Aaa e0eX{,8_4{g|¾!A9C 5>N~6~>pCGVc{ *~»㷐"}6DCp#`8 !ЉC`pZ ]0 :v8{$)&# +u@0pRBo{Z!J EMfLD)qUBNlN  ȃzQ +H70};J2σM'fC +aGHf23gY)Hߣ6,Ub9C+AW6JPxMnW ++m!3BO\jyCG;#Q<>CC#X.]L#SEKj9ChV~2W`y Ä)j 1͢>~MgD7s(0( +UxJA2l䃶?KDYvf ?eoUF|Gs3 'n;c7._PrhiZϥy4B'-HA6qž,Ie*/[pף+ahgFr_׊x; Iz4dWm,4 (,eQwѣa{ܟơ*Gc%`h=084s,PhXi@Ndk<;IrE|Hew_:|9*~~Ě`IP,"Hx|4pov,:B ].ۥJ"=?? RyGC$:q3}=!wSCiT *ЁfyM! +4T {1 @񰇮d"r zQJe +hJi)FxSŊ "(GLar/H1gvËqRLFN SƓ Z*  h/8l"l AH=65fuLG8(x)E!'qQ4䥿6itH!u/SS^1# +%] /R:HC:G7 N1XJ! 8S+ {U t $ aXZA/nY\A& }<8vGH1VnVy a1bO \ĄAyCbqEҘhei~.(im:hPA<"GA@|Ý + 2H<=]h4f]gm*Q3ܛ]E;ܡ +|K.%yL-*;\'o +4&\  Q N+Q\H#\%{B6hRZp4$XMR^}qtD=>*w=hl qVcyBuHC4h`\vE!{н%Ln ~wP`4=tҠ/8h!sR9JHF7C BU.C>M4tѠt4. A`?A9E +'EDV҈2n)<1a!yq/IZhxIE4[d`tqj#Mv%^N7l㏏A>;! 2)u.r0N||K8Z}m4 +SaerԌGxw|*bl Y7uG[ d[ldBرBL(tvxx44& 4,QHMt’Ґ13l(ۡE2l i̯o4#LYVPJ%߱tG C5hz`H#` N\v(R}t+! >e|ӐTc4bDOm! &-my@~/RzvCUp(>i!0._) ,Fad2Vi ۡZ ii܆L0f%?Sp4۾֡C8`)SdC\(Đ)`E.E( nwnv\%gKe2KA'ɐv@x!;5we*ہ=s({U=~_X8 YʥdS䠻BBG H&VC:E/{ ɚTJ\x! }D/7 P{$4)/+%9= \m/`^Hc%(KLFK +x4Puq.gbv:h"@U$**HK^^HAXtT,1u4~4Ņ F"\=pk֚3 8dv*?Α.d'H"ca@Ai7i0{J7bA%mXT +] +P9i463a#I E)ܷM\QH8\d;~nN%qny5\1vkE.XJd (o'ۡ:,Mp&н[+,ЊȢd˺0TK[/QI9 ̝%1ˊ食b *ӿ ALV S @Oh@)F}6(H6TagӆAxyx# ϸ  z BXJ|LiW3FB:D#T`1,hs_]s:8pIioʞMl p=[{  U<Ǭ=EҰ@P5_ <AL1L.49|x}KAM}f%n|:J#7☩/4919H#$ 4enEN$Ҏ4L 5% +Ҡ.ń4~]R"_8}g4~zC]?Xgꒃk4q[dKp". iL&:a?Lttihtkb;]2&^q4f]mUXУ-+#N18Sb2׭X]0 +ŠQ.YB!Z('Tg?yBkv[ BI(J~4Wfm-k8ɥǖ4,Kie‚7Dp Go(bHH-=PҀ+IPH#35K ݖ2m! {}-Bo=.Ƅ4ϵvC |! \BvZ.n@G ! ډGHk$}c73(av)M7caB% J6 +riDCnN$ LCx 8 iBO=f{X9-M{&sĘ2J!@(1ߣM)q.nUWזSmS_4nHf>^1N'Ql*TU&F[k5G GllzB{pw˫8?W29{7zڧ[Je{ #0dl> i\pb +`{C7*R +i (JTd"z8e{YuЭux i|);B-!3sY̪o4 +ϲ6¨."(hbu:HC) C2! sD!z.+S ]f|A&\4^ifKoi0 RMFhGiQWTÒ j"f74i]Yzmi%"o}n)4TViE iӫdĘg) ߅[fCt77éy iЂ,U'Bs@'8`lCgn?^0\OyqnH·xHHN( 9d1xO\9!3Ђ4lr=FC4[7h2\ʮq![mF`R75E]<(u}V/*!@, +z4pIeeoְ롭?-z2r AfTjU揆 !5@^X}_%P;^ +='5"Mnt/yWbp,{{h'HJd,/&7t5⬬x{4#b hX ֳKNnHsOC)\aݭ"sFHpQrL|4Ν] ē.M/Q¥{4Pxա|4|뜶qwټwhGc$(c\ G) T>YF|4&vč'Mk*,ډ68SX7cVŽ( n#3SHBf>[B.*FXs+g51`S_$+\>s|0-np[>^6ۈ}4ji3殲p!nQ ~Z~4_!һQhJ1 g 8Vj]9t90gv˪&ޏFJGct^ZoBp5+fB8#-(h&&H<ˎ=4uE8<IP6Wb/ZVRhF󀔙d y$'1h`!V?]y3#FeEdz61;{i8tl.w.1!Gn- G$M"heS.hXxސ6pјG=^#9!2 GYKg+:"ڿ|m4GȦh<`BJ &qfeN|D K +RA:HC /ͪg Ou Afj5 42ren~4YarXpe~4JsGC|Z> űJ.½iMGMб^nԢ> 6(lLΰH4Phx$ YhWfQמ6 +WsJSH*,c aEK|4lDCTlG34H3@c:BVU5~4z+*49CCYTFN88aAs8??u KSekN@HoMb_ae1Mn:c|"]ٿv4T龶 oq^K폹qa %h\/\303Z@Ex4v5x46L`38L`hȡ8Dlӎ0upEF:d=k(0䓿ТGƨl SYh@p%\ MTGOQG#^5FMD6oq+5fy]AE!O,I6 I[V78nrwLbȥ|4=:}xhgـ|4ϳ +sҠ,CoP<Jo%R..#B`pH^ G P=peJqJӾXXNzăhDx!`!6H) cCyxG_W- Kؿ3% }4Gh`PeAe|G!/+bWJ2D&6cO 7N+^>\J6MX`PZ|w7GhJ@,ê 0b1(VXX[hX}`G#Ҕ+gufw$ra`ϖI*r |4ƧFep2G +,wJ5OB>lw< = dc릻91&"IuP^KˬLmJ,52ՙNz}6 !~U3VMy4\6yҢXMGHL0t1khLKWzSNW +2m +DƖ~rHCrmhj~-/ Hg{go]`7Nz? +._4?/"Q(V[]ytN E$m92#Ђ@vP}4$\Lo;4qbd`e 6hD@L XSZ"N)CΡ8<邾Jr2ՄBx+0r{4xh`Q SO@۱zAB)tmiZ-jG'0 |4Nk,ؤi Yᄋ㣑ѓdSóh DlQ z?GcVأ,VFo3e -{7z [y4 SzI: *bṛ?kѤl#%QʰϩxiRr=CSLee?&K#Av%ţU$L&6Eg:'m<[:"B (e} WY0£PohL>~VBiyh&;5_hYvG3p)UzF%bl$*Od@БthuN& t7&= n]"Åɨaԭo:VG'cp-FP!^n#Xh]7 nlbvkD!d=ߑoP2)._ ,@NWghtWVk4)h໱Yg$Ϲ:.'c>hT1:?Kjv_"CWrDWd"GcJhT}S +0vGUڱjFҴ,:׋pdW&ǡtp ؛I u =z5 +R40gJE=)toh^}_L6aqo2y4+vcAGcޫ]<ۛ> =d(I=ݢ0r9_[/;CCx4F޻qRlo0bGM=v4Vdc<AFX+QJK؝FcM@Z^(=Lա72= yq9,FCf=}" + }p-kdk1VUАFC’;. 3J^͋0ZJxP!gQSL oq#ShdI:_Gq檭,+aA/ v:/A)CPC(fIjB!NbN[$?sk4mɠf1g0 0KGZPGrW%~"dbh6`v$g h?whCu 6>Qze,+t x0a1w`Ƽvٰj} nAuiA.@|$+Ϡ}^dCJ _O+iج͹u !Z{(6bca8=PLE.z8Pb-LX6o`wS} P:GjHS]~?xĹ;QӂBhۢmb;`~!aXor @ eFc{4 mÓPDZƯ, G03'h9m/?x!bU# ij{j&UuT s LJ͞YLh̜.pdܖuh nhۀ6BPF?t {ȟhf8giRCc$PCf%oeP-FAnptcfk,-܎F]8+XG hRh$J+DO010h4=pZHTG#pw4~(sO 4 3bNe*'>3VZaF((+uU +5؅ɞ' $Mˊmv} 4w8NZ!m7ϻ \eY|X jPYɥO"tKb$P''zM%HܢآȖ8v +ި (A܎F}; `\_#J7͢Ć1tGNA:ގF&L.Nv4ϓpFxByS9>Za.SĠ(G Z[JѸA85K +_ݮG+tq?1};GŴ^v4^1‘Рh|:\͘zo0po6 K;U; +ŘICV[L ~Ŧe;*X) 4fNdcnb4O4'Y4pB 1f}h б^CC Jh8h,4Hh)3c |Sg>o JpՑvL_Bt6gH ƘP0VG0"ls􁱟 ,Y#6rY0 >ɹT!0P0`39:,FЄ/7FZdbC+}uDCRWlV)M_kh=HӸ4$j;v[eC/޴vN;MԦPƒn6q <@Jj ХvT`S@76 ?ZO.jQP??R iE)fJk)6 RMIT\O!cnZVGxq,$,rRz4&@]9_iD +D mqYk/+5"m/._8bȨй$"`&^v/֏m/ً I/tZ^ drb&2n^XZ0j/7qxqPM-] zw"k'.:&.Dn* ?E[]0J" X')TM\8Xjl1RK\$5 WO @rZjシŴ|YWjBP: X& \-R@6&Jv #J~n&[Zmhjmjhfj#vb4~-JfkQԱjFxEEPըrkj[cj˯P13$-h-B'ۂ&gj{FQ΂tb5ABa҇YX(d, SndUadqud!yXc8uVj:"l&2WRlXZMsYWstaMQ/8XtE]amh,ntB"#e@4qƚb֊EQjbQXXP ScD,  ରX m\2XkYU1X"ŴkgWGdg_ +R{i +IɆ^g +lsfkmTG.|5MxC,{5ل5kCXīW08"mLP]+8^]wsPxY3WjC8JjmXK *';@hDkx 3E96נZ:^nY[i2^!ETU9eaUk߼Z8㮑WWEkoZm\WAikg[<͵\Búĺ׊+>ZB'2ۊlmŊEB?l +VV4;icZ!Rv~̊dœHwre!+ r2r$Cal*R͉Wam]E[ yxA"Xjf{jˇUT6b;fǪ¶.:* +Jwpxع-"- }3p#TnɗfX3݈g pT'nGEʨ972E *XtkYhޠb}MPfh?~v7"=pS[xS@ީ)ڼqoblu"!-o@4ߎ@X7f@L!/|(p]MMp,{\hwR</T:QR=)IE")F jcε(8:aĠ2ȭAJ3]$oK.E]LR݆As|)|H ([ +ɸmuS8OȢ~n}IO잸=~uU:t'J5'.}'t'R[?j'N'A"B7jVg{VU0v"K|:vꚥ0N0XUw.Vu:Sյ}0Su +If꘮0^'鍨d˟NuӽmI w%u9׉` u:؉x u3©ìI]Npx-[ ©W_'ೌ+} o!8؉3`鄷F'zNĐ +3z%YyJɜ@5/"{DUhT330'0R]܄(y.0'=T7sV,^]uUmquvp2Sumu9$v7vz8NǜHig 52'Ff >-ao4'̱N #{;ID +w͉N|ݹ9!Dq意~ޕp\l;|VsBaBC!6'v +FUIS|SVt+Kys7={w-ZpNWйջ r9$>%׫,]62!_8Dw!pbU&].M;%Mx7x41UȫMhG^nhD Ll<̈́ЋX .^2!S&ҙg(L:$2L@yEGZLzĄ7 zZg/wLdp k/a_b^K/q-D +z1@+QTe%"[-j%60==%(r%x)@{!&,%m ~glBS"UBU +%*aIGS₦SŔPORTKgJ bgPO)5O""T8F= N¹&1o$D<&1%%qHQ8WIQLWH+EˏD$eա#  Q]rI#rH$H ɟRt_|"#m,"n=iD둆iP"x# @D}繞CpCteUO!`  =kSJ"\d BV.Cz!Rts𽷳 +!(;bEϐ !$|EJ؆/ !!_χ!Q" _i>}V8!6|e:6vc[+DNtA1AH5} Q_'ɂpw=K +BtK + A4}4"uqBh@!Q~;?Jx=I BaoDXH O ua1 ~Oc{z)#~xߘfT!KN~>{R = xJ<~W(tDh3ph*#5iu@??Я5c7gh'=OC;PKU!Ĭ_A}[$G6ڰHX@faLÝ7B4p2]J! {z%` +X wsc~%x@ +d4 T? 'I@X[ s^Ni )@}D}d6Rn"$ɇIqU+d1OP3{`g&JUN,旇F\yh!< <旇l+WMpKvGv4\u OB2H0)^/֡ V2Tu:0rC NDL=t=ay=En'ecs%<)N79ħ9<4? `U48pFK9pɁ;Ɂ bA Jl B2xp8lf!EšrBTs8D`89 Qlb.% >2n nA\Z2؟`؋dYݠU0r`1BR%` I'|VDL td͒" & $ʹ3с4( uÁ,0x s l^"=Q0XASc1(A`g)& %u\GLryt霟r:h1( b(  7DJx`Z V1 1%ĠnRq47+!* l ~/ap2 04 'pA ڂIat\ྃ6F5ٳ+LZNK`K% X2Z2`pCjAq\n  $c!uJ qCs0XO' \/ m6nE݆k6$ &_ ޣ ` gyj6Y65!ႰpP7k>޸w:m AlFk A2e~ ~5A(Dnj8$5la=/wNCpua04@ ֖ + -K\j1Ne(Z5hjD_J+xv?C>Ý=f3$:h3t 383 x E䋵 \B V/"e e wA2aauXv (2 `œLJt̐ #2#C "}em?=t]0Z0 Fb6 +h `tA(*-_ Mv ?oIz|'d2V UQ @4!bxR^02 .( 炙K0$  M0\t`Ћx\ [ Ku.:.u<8#L:ȥrlO pdKmY-._x({/t. ɀ]p. Y}]Y_`5_ \d5/O`_^ezӀ,]0&#A 0Hzw /\. ]B-낉u. +u k 8D[x2pP N]p@…$GH U,v[taE8jŮt!LhQ^$W.NpaQvX~f&?r@t"  U; kot] |`5˰,=Bs ʅC-\p2.l0pH[ cd--DN-\0BvjABN J \0_@TЂX,D3wY * ,<GȂ H q'yXx0X fW:悕+Dl$+tҭp[V"rAP)xVxq*+fP?X*WAv=W+ + uPJu*O> +٧`I +R!**PTHՎSAO!炖u +,)L Ma38]3VL!B<° &#QPGacp3UTd$E[:d Bh܅-$nP8$悦(8'̐O؞0\zR'wDmN0k n' TƧY `1W. .\]Vv%Bu'p'Er'${[\HwܨûlН`k+wYg`t>vK.'g ,@< ~*f@Ci86Lo]E9nj G`,8Ap P yM낮4aoh q̄UJpd8Lt &~ \/ r./!Uo8]K%9 c}%n]Pyu.A^%[^p%X'-0pt$^PKSv\9'%@H K_h%.]Yn]p% Ȏ t G0WIٹ]0%/.}7%94k-gg "0:wݓ )_WJ)˕vE..p%(EVB]DN ZMĕILtwK{`Mw_pr%>ɿ`AWy2.WN═^ a`W _ <_ {%` )`PɈ.v╠Վ+tС _TjWJ@$&GY9  Q0 +60HN&c[ s_[%&lݬ$/e%l*Jؙ:*AA.=%t [Y? +wPP"t$$\G %a6݌(誕TzPJ1T,o:JBx= * +t+h u$LN0( .0R$ \u!!T$`!@{GpAގ0 &!A?Ct96`{oumk[E4# ~FFz}ʷ!fJ #(&6 +8!лtwz'igJIݗ*M)! =qYذ赋bӜL9B<8X@ @,!  d A$$@B^F+Oz7CQ6tr BzȆ&J e;Nnhp bHA (48Bdl)fßSd2~S%4}~S7ȼqN(Ұi|kf3UXԣ^>Eޓ;:SL"h2o@>߁dl Ul'VXGu7ytR:訵UDʫhD7D:;>,ڐذ)MM)> #>1Ԝv*LۧݼV>+0C}^"2Xlue"B&L@Dx=&T\#" ` tB +D9^B)֌2$:3Kt1Q8G2Dfqfߏ=4s36:tqL3J~=RԍiҸ:^5UϢS' *qd*֭L+ +UYNU5LSXuHV%E#k/;aE uSF s):e o^J-sNu)Eb =idȢ&͂z4i>-Ȅ8CEetABws-^ZTY->m,B=ґcWU(Pm>tRot|wao7~cKɈȕSZƲEt)|"S?]؆<V/^M]dA + +RA4~8S4)N2?SzMD< +EۦʯXIͤ\hNAvd,z<̼`t4`1QtCg͇Mw3&şb\t 8݉TWq$mbELDCoکn>iu9׍5|u7+}N1ewGڜ z\Ꮈ1}VۉЇi=G*ܐCr87&5#Cu2F\APY7z,tmW8V77>^dr%U}=*L S"WuTLzLUƦz9m%dq^"dQ ~fA4@cnXʮDeP2}tړ౪s` @SU.j<~b@sË \O1:5.ad +(%.īe}7tc{Ū|۬ Fiiu"<1O[D;J.kt]Va{2Ei)b`~h:礊!ndQ -El: n|jҏ1l&ܸIiC5gehZ8p&'eRɡ'O\ܐMw2sZNn9d +I8Q+_q(f~s,P^_(kȌCj^ -yP *.ӤɑCK+VP Č\JcͰ2i1B|XA4f㡯NQ& @)1q; +zUB%ܷf\F`@"Ked%XQ){4}F+*{D;""=q#K\#Ӕj;ОȊh6g,3͛+"~ZdxꞾ^S +U4['Qa/nm~SYm_Liܔ*! ̥#3c~M(Q֩t瘟UӔwJA%ZĖ-r Ӫa-w` (;R)ߵCGndq(Q 4zp(>.(3Fn dM{*W(FQoS6UClָ3 DMooLWkjU+'Εf(sg >+DxvKuVx2D?#D`b m)]BA#c>mFj ;CQZOITJhlBO0(גgIJE3ge]s#o if3)ffFy`|8KHN2蒾 |2ժLaKV#k̈́%ZYezod#Ƈ\޸jQ,!cXj M衄;'F?`6m9|ܼ-ԋ# "p3 +Mh:I NsIz +(b Qui{u yp#2ɪsn8ޒmqʓ?N|X ʑ=*ﰔQ3'(ʛJ `ّ) Yu% B.7"xy>vZ' +heYF'ly;Tޏk&sX0v;Q]A:# FI{GSh8ϯP(d';CxbO}T, `dwzURܒ&\ G9j􍆱K3(K+{Rj$ĻB&ude7V B-05WI.>Cf-#JLY"ߪ@Z$jU蚵N9 |/(g>m>z]waCcLʺUl q[ +J1i5*o(C(]LG7 8^`$J/LJl4G$`)xF0Ir0墈@6bA[\TkrbKp;8xq8V:ގqg2I0(fm=hg˧[X]Ex6zktJ]ӑ56Ϧ_f/fFi):dKLFiǪ̃ڱ^S㾻vÑt}㮟,Akǩ^,%:=U_ ue11%##5\yUd\؅E%D +4Jp +X3VF5}֧D^Oq݇b23~/ƅ?2 &Wݙc`4I5[]\,5]0"!lY @DiWiԳhNJdԺb#2 xPچD#xk}/UH12QȞ%p~ilx[ 7^gݥ.C}R4"pmMLОз0öe +UalZz6y)E7T7txzQy:TYF7BdcB| ?lC/aF +<ɤ Q\Bl~}B;c>z}h` 6q:_ E)j P*( %~YXpq +hUN1J~bC QXl!NhMis=dw;׼5iF P"v'@JiB$ަMVY4Y})\__ޝc)]g|Ȇm̈́ 1t~)}™I*\CP_@?{fs?}xĜ] O|s Pre ,Bn$$0s +=`,P;``:mě͓!ǮK}6 DF3r,Ћr PƌQ7w:'kVGOCrSob1f= {0{" yYq`A@&V‚HfKBhS_c!yHܫܝG@rƥVB pP@FQY"AFS*4z0d\o~k4WS*,_W *LPjemo`x+vߝf + _*ۀ[C j&o5(ygPhd|J Ć3`@W:DJUdl ЈtbNzǒn?OcG-9ލ?A]h4HPLTt3 w=9 cvh˨ Zd08fO9a+]Ѽ 4vf̣]}m֥=_iX 2r@HjДj:+u~>YUy|m8r57Sָ(dȄ/< +Kt 1ozy- l- W8NpP.Uf6-!r֙HHE,*ԙF#UYyaN8 ugW/u+x<}V?Hua6!u& +P|zYX< %jch +IO(.4Xc!Ttګ?ȴ"se5s. +uϝjA1lsP`j2԰lc +66݅ZIY(k@+k[b'TaKr Rb&C-\iUiA40F t45P4C +P 4BP 3hB0i%A=;J ZeG:C hNp+_2yN+zx&~UFxڑW{2_O¾bHW6IFT (ywyPHX:QJWY_1ta_8 ehε}୺ZuK pCG -M/3)LY MjrdLq %JՀH=7'$L$GlF[GDG$aE#QmrraFee/YA_=K\AiiVyf<|g8>H+c= C[>d$.V"9KФ~g? p-hHaI#26RIG0b+P\cA -ho">1GjvWӨlWE |oϽX7E,bH=JǏK `i)ʹ?;_)y&=3,O}mdYm|>U8䉾闧,ZxY)|+h2'Xb?pENA<勔̤&%Iϛ)u߯/_#4SxG>"@22+OR#"PR0 ru +~S3J@!DX hi@U¶F/6LW\X%}8Εz,a`i饂R1({=Gᘝma +gqG67n}:l> ߱:ez;cuQh;  + $h鬟"%=M@yaVk$AȌaD;;'olaZڿoȮY Z-j*Nl~Ϧ[V ܴ߃ h^InS)l+) t(3$<.PҐDWzWxX<Ħ&nWDĎU +k}C6{Y2"><~ +J$D!rLF.n&~,ҭ\ ]kFzfkJ(FC^j +=*|y3,) +*ͿP:-0uRڱ9w|wZg+$ g# B( {+@ +GS:L_oA(7>m{$jZ>vrtotqЬ9j4aX&wVRCbzb,vf%*gZH|&ą/܁ .=&45Ա>B] Q + +# CkmX`b%wK .`4܉ +%?2:{tC;GcdԼ1մ:^/-~*,.wއ~#Q00i1HYȊ+ZL V}ꔆbRCQY| 7? Uop M|` ?/Qg}- ZG j7Â/bd/0_ANd]׼Þ]-1MyG^JWL tt5#bWR~,A(ͫ.oÈeydJ"%G-xtZ"ptpĉ)5 yw(`E9L;}{I_zmy5 )-aq@}NoN1n0KRB!RTHIFJkQ!y?Y#>u6~2O5s&bӮl4J̪k{YʆO1p4q1DIF_F2kJiِDʍiͣ˚q (%}`h8⪫#=c퓶3h3c>sȵ{Vn.DR*d,[=6Y0pp-A ~f N)j)gѕ lְ8` +}QZiґH 5:MZ^A;\1īĕƠgvwH~h`!|O^n MS" @JD+Ve`8|2pG#jo%Du}ns7 lQ\`;iC πҴrsUiEg9.mN@-I;,_eS'oI,..Q "FiB9c#na\ AusLk&D-ւ^q‚M>=\W!@ Z堮;lPv| }Q[dtN_O߉]hQ/Z`)3߈XIОd\ #`wJ+C=ml!b@Jن&\Uh/kDzʓE Q@u=젍>>>\#k{Z4QZ,Erk;$3>J)mA숩O}~IMv/Q71iqi(NGhJH݇o5zI{,%E}4HJYӧ{P}$MN w9d@cM(X#)oN@)vBm0V$/71$y-\"DM T8T,|Nu#5@<=\׳N[V-+Dz +FkIU;3fС+c~as/3^0^b @]  \) 5,!{-lPWCq}ΒٙiCFD;q@,Pvs"JO3WT!pF = +w\3l,aAsS.1/vS0ghu5>9VmLbΦD+`4շkz[tO[r$%/A3@\njo`mTw/Q"b!|r8it~ty4(%ʶ20(-%5=M `{w$mƑnJ&g=\s{2E5(RMnnj#uחL+.a^ '.E 8GW5tAG@Gqdө$ʤ(pG7SWZ%:+gO`}Ew&Z@StjY^Cw 0H+6AB_4V!Ү'!4 xN#߃u7ɈICܝ$O뛌qĭܬ"r<4 )ҫo-!s_jm!ZQw: !PF. ȃ>>N-E*yUQ +`v*4ڒR!]|scN|C:іv').B\-ݧ&RFV O91(׭/Ľ6`p ~k9Mk pحlZ:j'v(ie@ff&ԕ":v?*5 @Xq^[u Ee]:Vw5ro`:qX [_ +|7]@4](Z +U4@Y.0ÈmdM:>m* j{_MWbcb#V&?:l N~moW[ @̆OBĂ$U-Y)|v91c5NiLD\`fwqeN;1;ۘޘ|.R׿#U/ڠMW MX1}m|pOu9~g>X,;m8f79őƙV̘apnqCh~mvӥBF7( ~Q+{2{]m},ǤUm ? ){!Jca@xzJ B>OAdFc|phԱ-qb gnCcc;Ҷ1c`R5t;֗B sXFPȥ4ᝀßq,^+hJzͅXv-X; S?Fsotq) X>c=ƃֆ dyҰ`?1ȄǬV2]V?f(y  dǾcAM1cѩ1r@zF~L؏4nK>~~Xhڏ+Jd%=>6MC7 ;f6"ӞEzƴ=9(Y4ޒɰL&Mnw2IeN)K)uhɼvگYv*CfeΏ˒˦s!^{a&Y!̲Bˬ!63÷LF}^j6SSG #PG)koXI4oipXg&g}RL\g-{jݒݭ2A'=Uiptsh@* .JCJJA*Rd.AD ::*.A[tSgFҽeM!H;6\Cit!Q"D]+#udFTViPcgA +^r 3m%($pHiBpݏ躀tc) y\|l )X4':>loؤy뼺uuE4;їuM<*벑) g|J(Qi4uc*YG_!:ذK@>uuJ㱫 -;dǍ+c\VE.Li4zV'U) zgoJ(c]{u2qvõ]Jzg$) dS7?P) kYشŽ.) Rn;yoas Wr{֑h(KR7JS'Ҹc묋J#k]Ou&qbO6κ*k "S-wΪw]JaN}AބmOb 3vY ͮgWj.cvTjWCۉ4_@ղq4IS+vKR [Rww9x'ʻޕ44vsZ4}g +rwJC`JcbXR)Ҍ]O+}]THR_p pV > 0חXU u(V5H6M5J* +DE ywXv8x7xR4X(N(_mOҘS5Dtg__AZ;|@i(nEFFi| (̟<ZϱLZ!(Bx`~F?{xbWB\) ?=JipEG J^E^-IɣДGW^M<) K-e^i( }<+|@} +GԹѣGҫҸɦORԎ (@Q~7Ci~.oX4 !DiB',9JcclD <֋NdmAv:y6zlJ +|q덠FlKJoY/a͉#c} ̩Q\"1(I|Nqϧldr|T4!ڤn4^ԔI#pizB2Ӥ(N1${14AnI6{8h}4{'1JI#|A籗hyJ+VxT̞wV2mՒb\z@ICA{xv՗4@{p?{Н]/i8F.=%Q嫵ƙ4D>gq]Q05e(2&/송Pdmo_@h4&od6ܤ!QdQ|Cn444G>' >lC*הm/m#ePJoF* JsTTQ}P> ?T9yy4UȾ̯ǜ_,Gwf;l̏؝_ 'iS7[T~@icdZ7E? fk.MoF~J*!w7<{:N(؟( B.I-OTҨVP쓆_j(@_H`P*ն4 iiSܫ4c 5d>4 74W(`D )βor+dbHòQ@0MCd<:j7a0A߆CfeBK< .+?fh4 1& ȰiדA]ii 40 ocE  #~3_6#_+0H|@B.)[+X!JȇAG"!(.تAϷT!n#X$Շ,Ɠ*{س ªbxQ. J BpN +h~ld]P vA~uA'kr i`>~BZZPF!Y V!6$ưWѐF|$51R/$LRV +B]0 !1AÅ.H XҘP/IbA*1 %.^H%! m\il-zgEp_l F +iTf[=^HwAq󨓫u! :BE TH#AHC]m.ᰲ`킾 >i+q, b ^y]Q_42TT]V<PJJJ7.8пDU]Ga`{1]K4^8 8ѝ},BnE~4L.hݠLa.xß |F,O]hLt4)wHG4 +HFT5Ԛ 6N̘ j]*"Ұ(H#g4B T5h۾Ud%R!Y )H#sg]` e4/YpL/A6`Y4<@+Ay Hc1? i(dHCW(VA纠N ^ڶF FQ> BiuAH ~]r(Џb#hRҸx VGC%}0^hFM s . +e Q|ܿ.)~3MX ]hPb1{4ѐ= >v]0qR|zÀF~`A! ~!)/hݤFjwx n?xAX ;pd +1i玈]0DUnGż qh{4~Gd&4R%r4|4Zr=9q7!*@k&1-qhT/Dq9bbfN>b죡 r~42 K [O/XN +H=M5 K>&#I1_P0ˑ8ok + Z Gz4BA=?G= + F`0 + L$ > |4ߣA T&hy^chE*; u&ի. GoʀAJ&g vѸC;Lw:v1)WZhh_\)%£Me#uQIң19`MuP#0hׂh 'Bha`P`8 j9 u bq1wHƣq 6$ YU]x [DK!;Gw4@F֦ +Tq#9ukח=|4G`HxPP  =j |) t``A ^y4^( GK?C@M Sl`@hPo~79jSTdr Uh(*G.t8cOV@h t7maQ@, 1O!`7 TUvFml45߄Ik4┄W$ pF0XeFC?m4$L`FyM2`F)081Z0` & Z?,/a6E$_q apWaPS7E-gJ\ RTiN&0j a7n48qՌ +YLpy IGԴq 'N6K!?x4;GCF1( QĔl6Mhdu4aY@T|;0bzeGÂ0h`pMIhĈCQ# + :G0d;[ .NƢ ' xA aWb"x4NP"A;< +% h +$^׎x逖cҁ qG$ VWE⎆qЎF\l`0E,:jNWFq.ԤYQ+f`cpB u ]q<˵"ݷ Mx:ü4UNHBv4B@%./"Q,}ŴeGcJo40Loh輣 ,~(u?,ʹ#|F~%*NBS  LGx O׶hׇsˇܥMDQnYS)9`FꞂr @(ҶR +-P*5P"}T֎ƥmê"aF0݃]lH *&4E< _Pl/d%v;xh:R衽A)`jTP} +Q9X\UZKGCT4;G?cZ?#Pјkh FQB;i+3_pZ0}r(ŕ(´g +p]sq0u4v1#[XG4(x) 1ANq^F2P 26T!RT@Pc5lRSbs+hi}X]Spb9y" ȧ,j!ETB"lB1RGDt֌-Uam;WzyY_F`/R_ +23+QqFBSIIojYau(W[xw&'#3Z>ad244[y fG࠼Ie'kJ)B&Aݜn^̓`YPa% iJ4[y\ 38̙N]V>442#2HmMPUI?Z>+9iyD3;>JU "qt8zm1؊h_! Eٴ6&(& {ef:,h:!Rp-8hMoC;WdaHNR6K +qdT< U]=Ab +W$Rsu\ m;Ƞޒ&11\⾺]"nvd8$B+{&X{2-QD>:{oPA1֓㷆k +ݗ|].JUdP'1EqI+#JNu Gxy=oNT4pT4R/ah5QPO@EGrL%d 0KuJgi:2#Q@AVy*˛ +:f\#Wk]cuS<O U, 1qĴ8]R\Cln-4&q7K/CxHԇz+98XO/L%, cBťH.(rH| )U1YLdATLwAc'S*)R1I|$2EsgEІ]0k_x2H dD ӄa;KdwKBrKRSOڪg5U E6sWA! +LM\Xg}CEd4垤DsNԨ._7& E?l 8?H/pHHrRMy;] )vKL24!!!OL*UOUOb"`۲ȇ$ڗV/!bpY{ p2 I5\aM/?;FU7eEJqGCŃk\I"3CH%-j'e{Z~YlI竪GO",iMHmb9QUYͼ vnop"E/}T#w82APHkTHQMJbL(>9MD!.($0SiLˤtbH)BjT1R[B*GU-8(ƂXoi3 CQbNq% GT (^F EPa5* 2 zysdvtjr BS 2SKafJB]0:#)V2PaW${k&+ n͂;Ш-&@%kC,[}lBe)^0jFa-.3a^WULv T+KX= +kV>0@ x(Pm"vC#vZ3TC+D'MhH)^!!)|xX$~gLe $7LH.!ʘ1T'<JAP "VUtdR5SDS'8DT^HH걑c.Ҙ2ȴޥ&ލ ni9.9ifώpCX!2R⪼ȕep9)نx yR)]:W)1|j#/`6i & +o9AI$pLj(=/8:8[iYYgc{; +\ā kۚuڳA^G>'9wb=A-. {:/% v_Dq2)*O!գ2lCn s>:!hd;Z|".Бt~ݠ gKzv|z?M=ehF*bl܀s7 +,pWK$1 v>[,gݴLM{Ezwi$y5;\TȌ6P C$h.E]L6WtQA#1aOz 3*HHG槒iO/ +a tTݖ46F<_F7qku]#(g,YӄjB{,' m ]g|"XF# 1dB~/5Vt'?ϖar kCvD3Læ -0vAB2dQ~p*=⌷ѸN;mT@??X,P`n$:bN<ı #tZ(m4u{dܚ>o0gWi^׆Ƈ7`k+[hC)N&nM\meu.vYlCUJN-=c@ {AC36?֢{^& 2xH+ITU +t5wCDC8dGd1#o+^36T~'9m.&*4IAsjOdas>E a{ ADDWcF*ցF>=Q>o)u*EΔBv}~ EnS~3 +9ULk(Ϝf .ˋ mgf1;s36{gҲA?ov'޶Ky\(?ޫSy甈>M:GqP桢T24t LX0z6A]>d#} >D! *lU`oh4QIӭ]jp.IC"U@ eBRеuHVX/0>&f|vq6yfAp 1wSUz0cx(`>oZ-Fyq^Ig:1LVDt +Aƣsґrft^jUc3L"hhXhH <0^oX%OΝL[! ++IGʪԧl/ O[dp;d,'edJ)F.q/kKUORCHXĻ}͓#gcD uv# +t`1AstĄwSEM'q7 %!}I%,X()W5"ys +k(7^D,H/ X5ߘ D{e:ݳ3,_-.q+S0׮;AP$_=տ0#fD|r&WRXF(Xqp*]Y Ρ$NG#zYIT0 QkP֠ #u=ۜP U7XȻpC[O+f_l0OQp^_MrԗEЧ[$GWcUMl?fH0ڲfJ.wM$6ŗ!alΜ53F_&{W + +`yP.ɬ. ,tUI.+ !B1>YꞳ uE7ʶep)Tf64}ŁEKȊz3:$ӧlθ=:E.>4 )ԥkfB[@+ ե5'0vÍbA/J +Y_ VFןOLT3(z?c-; ǟo}l=Gzpڭ("nFFT8Bk~.@ݶ2qJWXnpZO0l?rM^3.4}L.8Y +U4*Er jS:fNHB$r +9?p7q=Rg:.<3I.\Dn6JXi\}UZ'<P C2iVa* sW1)sIe@v3(]o+/ѫΈ.,B^ogb]=GomvWs<כ2KBH݇wh!z-]3E/jU6:  8E#$ f㔋xZpވS陸ff|KUSpubBi{5m?4 +meKr9`T3\?F q&0EkTeC0/xZ DXO0N`F-qDd@;G4L1 .hPAZչRL)f=fcBH +.(f "81&>B 0GBK`M[0єDP|_z{0U갇Ӭs'#)¯Nj|Z.$hۼ> h+|ڏ8{G\D y5@_HHL +ßC cTWSqG#)<0?5N҉GYCӋ5W{s!B[-DG!RDR:hK^hO O*`@mIPюyT-_F[18`mȍvѾ`oeR4L)2No"hZkUechՔQ-ZͷH-iԛ\o6fĎRoRZo0桜괿9j iVϲԄ%ή⺕ ޯK5.JM}7pR;"ZIMBe"PU +J9.lX,03tA]4AF3 +At Td-|5S֮X<n6 UCq/OW4 %]\ UGҋȆJ D97s?.*7 N~:Ppxwo{xbeLDC'Uhn'"Av!PxY5@ IE<#Bw EJ#^shFYAl-?8kQpaRx0UM5IY;du##$#oQw CE>ӽG['*)4xz`D{0SrF@ARJCipeY< 9҃6;zyEg F*(Jvh; tK$T)aav'CH C͕uuy4T}_{˴]KxwAx' n7a1t۲jV2-lqYfjjT֢AN}94;Y$1;bBoXNp]ȾzQ:|R%?q7RnT:!o0(CԔ@0K 0]yuzĬIpvg;gYley[B{ٖ,ŨQ(u U 'C ʇKH#T :| +4yZ8Vh(pw"w +0|G +jls*F+ĠHcp!K,i[ /ͅ]gPp#痕dܿ[m9W\ak)Gb@@1x4XgGguc4D5ОՅނBi5]نq Dd'HKD=A>pDQ$О t $9dw.l7;Mz9My_딜yy.1˒g4;Xꙛ@QHltzC ]҈niRNzH2KDBn#kW#a(h! iIw@^ց}u=Ou|pP"]RݐTLtit;udJw'F)UWwzPQX uеbpp9^k)Qk&kLk10_BGW +c3YlNJpaȈ w L\pIpg۝ZDgAXYpƻ dQRBLs)&nѩ wuHlKWT?!~+S惜Iov0j7:Kf`fƠ'~Q_/3ًHKu, 0.]>a(lGzNXMxaL _ !`#a(\XAaVr΀r$+ǚ=(RvCaJ)ni: O * ϾςӅQ:D>4',  +AMa,4 w 4{BNC'[S`&9U~pXBn-M2fw (P5 ФoH +#k⯛Dh/~ /?eYH"JG?>8::6b @l/Lu0h5"z%SQo?f(z $OV$bdKDP?AdG?ѐ>?PJ?b~r}J+cM1Е5_;&Z_b9h繎_^@b&Glev-ڵ44cOD;β5ߪc2v8=AdpHܮev&2v& :p܏xmm>Q$5y^US#N[uNg dnJu^0DoD(jԢ s,>|pss10͖& U<?ylDq~#Ϳ8t-^ .=y}jdg݋%=-$A-ٯ .aJ">jG:T-ŪM/ڷv˽d%3ꖗ(QC{.u~-kYtcǛkmb,e@#y昕vDzlO吐` 98Q9WSyX F@9A%*$[<zvAVT#}v̈QBϸh ޻n6vKK]f(W#欵0˒l8qZK!f4.5\ę_B lvnf P;y^.˔rBH} znzuXY۠46у!sql^Mؘ>M{"oS|KyNyQ @/[{tVJ`l'<띗lm)`@Yc6{˿'༫7@t)z^D 39Jj50h(,QjM @[QgD 65&; +9Μ|Bn YPcjF#}p hܤ=`97CFpߔPV3 T#X`ˏHVa4֟`CPp/ac53QL,{jTHTy 1f\y7$Df҄" l- /u- 3JI?M׵L&}USrҔ95?m!NNozԶ=Cө\g0eivJF?N"6s;m!`D*f@O^T^kԖV.\Pˡ#ϢL@kABAo([Nyq5X z^W ^|aCa:S(1KM8p8^y35U10P]i-u>EW'cÖ`Z0PRJ$k[h“8̉ͼH4]×6WSq*?g qI(eZ{M*!.Md-dBZZ}K2™gqgӱ͞b_Lׂ{9@zb3kÞg`i\E#)\{=|22{?A%d/c#3J)"3D }$#[ǐMA痣f(lb+TG4m{]dD&B9,t1%Gr(I +26CŠݕenAx*pECtƑp۶K6(, 0fuU [M?S@~X=8omgJ,j<3DcQ0mA2~,zL 9'\f(5<>HS!l4XVTw%nLYRGƄ6 +d]~Jlߦwa"͟m7\phew~*CҀ~Ltf9c3g `fſ:ckf>m+ѥNV}y +b&nUqtb-UEq@Ue OUNQF#n,Xqԥ؇2b?̤);QȨ-/$**\׸FM$(50??Hi|"BʾF."~&:6efA;"F.n{iK*wʄ lǷ˭lۋ#PЕ?Q'eG ܮۼ +-ȰĽ +2Ik6Ep { /9Ϋ2w]ݯ}9%M@*+Y)e7L^N*NPi:f?gsPʍL'햻opn2̖T7{J(T I,}_'x"5Beh;;͚s)r|Ǵoh]J ƴ߀1K1K_tH:.<"'tV)zʪ{%:j!0:-m$:?P10Q5Xy$ AJu)(pŧ)c&R咀XZ 63仚c6d<4hg4ծC=-/xhHOl_eɖ"jٸ; IS f*~NȂMNY-.LߞrOGGp2/Nx2Jt疃Np=gY:=da`ziucτBlS{ce-ø24)QըuI,@3YO@:O1-s|c$cܶ‚1DI _̈ Bps)"ܕŰ09^ѱkk]cA #pʞ@Xm%5#E & - bVՏ@II+kZJEs%F j&S$Ɖ^S:e%7͓- \~DTmm0=Cv( 2-jpEkV x- DP 0DB\=_0Lu4iw9PaWڙ' em3]4ңfs0bmi-BZO{zLH$ҹtwu%fM?LH0t%;7ڌ^ -h'8zc StdXlќn)֡/Y~=z䋶_ Y=kW zZq%lܕ9VQb'pdMWNrXCFp[wT5ͷhkG8hݭV|ez%dW[I8)sx<wbj2S4x 7oΦ|qcǀkWhn Txxb99 KIŌf!u/bq03l\CLPot?F%htĝrF!u9Dw&(G?F\;/  e{& L³[SFzfD +dKb0( @ois">{vtrФ_"}u6"@CXa/B|^ +;euQL :!:t/č"hͬ)$me BA^WuUְV3 +Kn8BN}¦ZrJy)aUYEjOѩtot +Jj1)>}<5wBc4:CmZfmu:e,PF=rfEjYW:weQ=j +5])^przȮ5Te"eh߅SBVhr᧢ipsyn& /mI2f^GJ:M|L@E6f0ў I4$s*~ḨDt{Vx6&q~CٲRNCq:l]bY +endstream endobj 13 0 obj <>stream +xdF_ӏHxzH4 ($-vi GL)|wg.ścK;\F@ˆPi觎dLdǽݩ,۷C~ gp^E5N{rӹ#y=D!\=lAplQ>m=nnyjGhA_;&Kf٪8Ѡ ݝ3\;F4B3/4k:&M.V]ٜxXH/dpl:IXJ+-:Anٟ52NH)~&uaݰ`Y}rޑӈٖSfs7T!|=gT(E)'?ì +.@ G :#WK"6BJ& a-LXZd(LFR%q䄉-< +s/!3n@7>AJi xxT.֪NE$祃)CBڜொ63Q7tnV?dVtSp&7 +P'|G35dbnX9ܨ pIȳi81Vqr`Vzİ#B_ Yb ڦRTUF(^#$E<̦VD񪒞.ʖVhX2`1有fѴޯ5H;i SUQ@tFsN3r2ЫvYT ZߤCɹW>IE)I,^@i`4;pR$6h#[͑1±WfVd.8Ϟ"[qٗ +ӥEBL(uIo/jMz,Z4KV6)ݒTMGRQaB>Gz1 @'>%I-U%w΃G ^ӄ^>Ma< qԡv߇ D-:c3. ]*ZPBuPѠd!{Cnn#o}=HBYE<㠚0UAh*&Z4*=h 3|Dm>?bokx?Y>g-jധ1ljiZ/o ~j1wsp!N뵼%E[ ݔ\8'2N7Ϡ#$~ը0&4.dJ9<@-SkyۮF75=^%"(;*fb;4m8'CԭmQާl^+Qh|ۓ\YҒ K4Z%RVоU>Mb+Ul$M"n?| 97f-ٌD#䑰 +_Q<~@{-܅o[i&dVKh +E{'ˆ\v ]қq㺍CdZag0l>7dް5Kދ,lr MzL.8 ۱9Da+V^M7p804(R& <2؋) |rĶR݁[,T6,w%60um)dl`-Q̷>Є Qvzs"%/{kiS\ELGRp4y8p$[:3T]+Cwli(r^/a#2 YJaӌ#5_' YqIvb_M1mѣƏ?þVi2dШN󗬎zb #=h%hjW'fShT*3|ot>0'?OEp٨XM8`NohG/BA#q Q3$$h +0%m@} q^FXÃi)14ev2XlTZ\qƛCNpG+q),.4C~bJ6Nj*mĄ} v9.THK+Mh;5<u_>(qy]A0b ݍm[݁FQ Vũa1a,ꮕSBK 2:vSg>`HK{ZpŲ.HsL/xxJNh?4րO5 ӧDh`tʾ`F{ZcN@y}/%;g@}W4px&zcO뵶di~Z/|geSs\êO{: +on 1Ş>\㔽){Y%$inm<}Z5Fseͯi\D`yG +(78 NjB=;#o}eX]"ih >H(7u"(rxHRbh߀犫\<-A2H^4@FWrׁ LD0y hC .aneC>4!/M+}.l+ӣhZ!~C924 +BQRNJ)R+S(bCbF6I4Ks0+V"D a{bQpϧ7 +ٺ^O F)>Ga'x`tr~5fQJ0X!;nʒ(S֋(EJ}2zOBMQTH(cOGߺsFE<>JBeIrw7ŒptoxnwhB .*z>lѐ?,4!exv6m9cR^VudT̸gsaKߦ4hVJ2#l$(130bWC;kW̬-U"Akaz[pNM;*}i`%CDlO9)}#52#bGLmzbZ'c ꅤIiiªZ=[\A*f S.+~jw;p$&!j Faj>bTx0okǃ/^ΩlKp~lIJC=C GHjW'J5#J{8_󏩥 S1bR,HMhf,Ut1i^0z bX؁ؕ>4x@+H"Fh5+EH?{bv!bLɳRGRpפ-Ui]3ʞթ(epDY52G(KPv;l7xiLjn81yiIU^5`]_PL?Jk@ _ -Y(KKX.{7.|Ԇm* өJ1lh*]#QAi ;PNO8e4.5Bi_Ec}<1R08C U˕pi< 5ʄ G +F#}"([BZERGfL?K>@]|~%XPZiWfx\rl!Kd SrXhDzyKqȞ =GBȺa!FxTQDAZ:.`x4_t>idv wWyq5Co?ױ<ōBEXsO2kXRX&g!EjI8@)a+F':w$N vMʮ/Cd3tb/y$MsT ${u#r dETpZX>w+kc$CvN, ]R,:-pv$t} ᮨp5X7 7&ɵh-nKFKz70䵲' zPK +qYJXuL}$zԖk4*b)APUJ$VR -(+XoOJ#~0$r]y_ki~xTL0k~C$gxQ,]b&OT+AJ~3U8+Whcy諄G({aT}m5$؍d@zY5RÒdp ./K\ѥI>}T,{6A0ZDZU"(N1r)> J(QO3"v$Tx  qꐗ٤1GeiOMCEZJMr3Nxx+g۶L +vt*cb#: =`ie,uuL7BS;t6}4;LR*c$`_Prr Ə \6Aqcڬ -Ew1$}BOE78~O AőI B@yړ暿YENL`VRjp {]O\Dn.?&\F +ZJW@Gu5TZZ }5"N$]F~GŹ\܀r}֑^šdM™Lg.Taڬ)uz |)b6^8h$1R l.G5/ +Dh::xBPL5_5A; {C=BD"sGthA BjuDβg^&/̄KZ)琐Twf#41?N9&f&\̺ h՟셛Q +~ԒD-+;05в")Ƃ71 ˼y5OEM񠹦.](A%.>U[ %&]%Xs4roNoǩ7yJސK`Ga!1:C; G3K>r̦2?(+fE7fA54[ a4$->YHwʪהqEִ{M9~X⦩5]Ol^SXI(V0 ).RtkP}*Ftpdwun^S?mɪ1U5Pg6UNjY m|M; + ѦJRD^Mq~V97w}(Oo*؍${jyS`y5ʛ"ef3) /8yS/8>7:]-FN +o:ܐ-;'n灛XinzuȑsA")ѦMP*qS|=;mF F\7eЦ㹥yhS[JZ&RGw;[tחxDg/2.[6.RA܆oؕJ:SMw";JAtrp*M~JZUߝ>րI'fjo^WbPtt⨻t(N}=iKkbñܒZ=J,gS,u&k̘œu{ciԤ Qظۼ&54( Sgk1WG+H,wA3tBQ˟tޙKv-]\ZEդivظPfT9X%s/1!}g'Ru(]gS ly;[Ʃ~IԿRZLO=wA)xHIj 2Fè@(7]CT@yac+!hjNn~+p(p`} > O Jq-F z,k|_,֠`{=["Ss}UufQs>!pHKS Im͔Vz}Nfa[LgbZLKCSFU*bޟj)7hb6.{۴7GRHJkZ&$ƛEUOdK +l!9)w^(O.p5^ZIRNUЇSjJ)RVR,rCB)ITɀ$?GPLS_l, J-hQ}_Sa>`ܺZcspJȶPEv`0dUy¼uSb6FCaF*`v#1HG!! M'gJsW +]@.y-ݭYqg px>?jhRvKvh)[GYP.hB>OwM$œu%orbbD.c[G[o#!^"kъ" f|x+NnG'pEcͮ4j#Di!mۭh6; -U"_-u+-U$v'ūq/uC/ɮ QOI=#4<sS3UX5Y%14ye]|Aʆ r(O +3c?̶0CQFcʾWX☎Q/x-ƫktʘkGGBöG+QsJ5l0>AOLnv_0T*l="*:SfAJoz1;[;FuQ0R2J Vu!`AɦLZ ^B'iJva*XBaʹfОNcvKeX__YʃR}o|V?89QKB핒FV b4~ATH3J188{(oل~c{Td/MBPTهuQDBe\p#5/ml['CJ5qY=$@TQ ^?ҡ&6*bD]niʡS-‘ÛnɎ8ٮ5hi-by$\4}”[fqюBjuJi RBLyj乑%ZO +̛ %)bnٞ"˂^{K7/Ʈ 2bh:3f!ػb nlS8(\ceSLK?A]\LՕ>-"c]ˋ/EjQQpu>dx1@h>~pä`Pw*Hxn$ZxTAܩC.5B"tneU-gk Ǖ*w I2;dF -:c('?fz m lS0ylg(ƪ)`%*C3g` c^ '_( v9t.̗qaRߛ,#!^Ej_>*ctѹBϡvtF7hޙO$]k. J蒰 + N|3j mCVJi8f,,q/-;vuu.=KV SrAI] ƸS9v܍"47C,7LT"a>'au:*W=<*ͦX!Veʏo2gulqYW3OoLJ15njV#Mory1҄vtrћ;ua +k.3x^$͈_̕l%+`!<PBBe1>]D%K. ]H]Wnjh,mnJ9/v@1^uдN36K~jL[i GXWg`+Q}%VTD(BNPV2$NXMbT8Dr@J+yYjX)&][h;%uhtR0suxI% J3yG!a@\$S +JJN.$4njBu6X$f*LS^j*ϔ#·"]#Jkk䐚in(b]a,sU7͙5p +(鬪Pڳap%z8h>Cbg(Ϝ1 ˖pe0`+2$gyZw|a zo;+Rf&;4@E5}KMR\ĀaIKTr񖵯xW%xzbxɦ ""cCBdh ʌdH:{_e}zIKa^ԿX$$`-d.T + +NÕ<1Bp8C=H#pp둛R,|qzD'F1Wd)f됤*.kS51hsOj( f#N:)\iÐwCL~sȜmYh|%[8U0<<wwwwwwt'IwRT"-bC꺮뺮뺮((((⸮u[X k69;SIɕ*+$%[m[ +Z_E5#aZ8IFA%$WGz_8&bgjar6Qm/qr+Ԝ#]=͡zLP4Z\'n%¢_Z5Md^ÅF#kjnj``;+OćA܏YX +[~BVV^]y+ Z)&)}Yy^n Vtjp$"7:uXuPIj, +ZӢ[y]H( ōi:eH Ԑ,?O\)-=%%ؕlfbyKLz~_Jg^ , jnI-'8NNs6(2"#&6ڥm 1͠ɩ6m?WF*r#Y3(9 GQRY+JJ0PV0<糺Q"vvww6ZuVdqC{2ۭj>xipaЙXVL,Ǖ:)t/v(ºWPmWM. o+*t629OV-z)z^yW/WO GGkG=y娋.S0ثeգO_^8ۈR$b6;}:9ű)m_KFnߺjW~ 9A8`j+-oQJ +}aͲMN +͜U+* +_8LA5JJ61ߡhiWOP2SV#liWKLo ,םҢSZ5 `Z )1,J,R5+_SXIvdǫWYcMXl_ÛgƯ&%_Sjf7I`5)љnȍxXQ"ťBG9R\GaȿZhr?4\3ȡvx%$^[u3))֯~-Da-Ȣ5nYE면\iE$(-\fJy/[WZef UJz=1)i٢ OT ))xƯ=[ϕ&(zֳ0ōZ=\\/;Z Ð`O?5ڮ(zvX&v7LW(~;ZdJ0زɔܒeZv ˎݦ(-b ӢcX 7a#փkOR4MWI7zOLj6[ْCz+:o%4}Mh(?Ir">S ~Jb +{: ?(MGYq"> fR.^v#"(ɈcZzh>fH?!HxE qA&ZG1!~ UC!H )Ɉ7v4tH$ 3b/?( drdTʢ\uJRI; |H `!$B,zb~>>PP ՜a7C+E^~Vχ#ڏ aA&у AH#{ rC*{HR"(IF#4Po@D%-,gGL!--Ϛ(SQ6Я~7IBS#ЩRPt#BFNSz(<دGP $NN 8BU[Cl#u}kQ=nY_vtqD'IzUYg(u(&_Ü"^ nh̏G,R%#"+*цJ#3ç^CأI^JBy#(vC g pO "1 +H$l;"p?%tԓCF$$ QX -xEQ"-oGGA3b!0P`*h3S .5t$K` z׿;U[wIA!p5V?ԥGG?Gj.QN^QSC+LqEQd:3">O&9qAo( ԠH)@l##h O(?sS$5PŊ# AfCMiOXz$K)TOC;z CN0@h"DPSf(Վ~ CoGC*I+K~XrI@8h]hу|sbvj0=#|{)K.QFKi>H}, +A;# 6("Eb { GjENoCJ~)Qqd?;)$x׷7G$LOCvj߰E>tӃZC5tJqث\Ӥqd/I3:;$5DjcWp0W?8~UalWWK*N!*8OKښ|:' +M`T4|rYI+{$qfngg9 Ð}ldp6pĂ^PTJF_J%ESbGPx q?H 3(b E~>JJZHD73|E!-[͑[_")2}QQ,Q;$Nc ǣ}!3@P;ZE*<߿~oDxO=;nE7=Q򒃗$鉿hcJ?0y\G!zL96Qb)g?9Dd~Q\Q6l~zjdG=$_z^'߮Ў=zJmxZE4z+OG^ }jV&kA&L*:a ~gG~&Vov`2g o~PԮp1&o=E +bUMSV69.}I'?=}$}vCddZt?_bA%WEq٩mKCrcBdH>vԂ?d pC'I C/9I9 +`_8$B#2{Q';HzJP٦`b=Q$%E_C0q[g5fZoWFg.IJmCv2NX<^ OEdR&=^z$e+0 oU09RE(VH=+'L^IH":fnUp  hH [Ʉd17Amr\ 3u&SI֏G`crޞQ$5#g Gn#)ApXC )X"shA ,D!ц>T7۷d@acR$19$LN %X|EoD!Ɏz-:mh{\j:ȞQoG=$ zN7Ls3Tc&?si?(gy䩇WA.*) ŢA(@ I@Jeۯ_ojC<$i=$ψ3BG98R<Xt٧ "SaW+$NK%,(BiqqGi^XIzEW Hb2%~r?CqȄ%d ߮ĊT#v) a)b5;YO +*vg*V'+$񨎙ɉnI ` hr5 K"g"Р8=G췢ih7C7~

fqE9? h7?E"x>zĚOSGc+=Kbg)Ef`A%꒳$a#FyӳvjxC&C#=",bSՊ@p7?SЮF; CWIRPؽ+W"+29?+# EdzI94ц&d#$e0,D''Ih0$y%ILgqCSǛ?;ϑp]jݚ>%)nd1}5WBx6T&k]Ҫe{#''wCz${$I%1Aͧ6 W^j=zя)a<+VMu?A1r=N )a=vs9 qr ~nokFiHFi>uԃ?;-!nvڐ0& ,q}\z +ʢ3$)FEРH@IF"\2A xIr⻯ENĞ |$-wH9`ȿz1-j +~\H} +KN zD,NNr`cvHT.$OV/A,Ȅ1$Ԡ+䂪.V]yz64bGGrJٻ9*_RrcX GאLjĂUԣ Z ?bRWNnrӚC +cbaN/nOS{(jQJ* eWd1'XTz==^^&EbxYNkIm?HǶ,K+^I1hжzSlgm;LfEh_C($FrHbbD켞 cmwMz<>P?#nF;MOxC&TOPȏzyA&JL :Y Oωz4!釄~zp{{[z) K&On X~Lx&@͎P~JGJC%Uԛ( ?Cq04_Nrv$/;]ɦ-Ģ{$I}egQ>rRψ3B퐐U;$WSzsHRd -"Sjc8ꧡCbOA%E9ٙ?W?? +WP𓳧<5*3 cJ`+KN^rxsXC^3XE iK*~N/ގvl}+^EY-TR2  +jN^h;Q[߲3BW Sȉ d2,Ԃ,$릇D-=%)^>J04]jVW;C#DN0vs#C^v3JAiC&D7Aa {c#|ɡ٩KJn%YG6! b:A# &Iy9^v\qxMW,VzvHz iDOdy,8E8F IjEɑŏ$ !H``]I2JP+(فhK&MǒE\Fo>]Gd0v^V3 *fuL*?;?ĊpZiZ7 jn" VKJfq36Bcf^N6{͑XhbWN~~`9+],̫/13u٦.M/xVJsh6% 6RsښɓX[WCL!{07j&Wû_|^Ԋ8١Ñdy D)IjE!h@od'=68{"HDDnq%WO*SoؒILg䂨7Őltf%5< O9B Ǒ'(! M21BA3{mɨkFe7{zǻ0;#P\FL^OisC\a%< QH끸"KWoIr4v;*? ϟJ^.m{G",3((:7bQ]LfrĂl;#l۞򴶳2\mvF( +V*&4mnz\3IQX=,U{-RVBMH +Ys~,+&l>euԅ{ɩ.,çy4J]5KP(Nkd߂F0(G9lvFFg_"[YݹU.e6*M)(?=%ka+_?fJuONi9{rbѮ_ixcӳv|~ +~JC|UK-!Dpç!񩧣 $Kl?RbrerE* xBjR Ƣ39UzAAX1ZE0;5+o\fvXZ +*Ƶ`R s$Gbm#4>R#ـ±;,YЉ.GmH$sda2b\0??1z9߈TqXRC3d9)X%ӑb&lQMlRee,_]V#\O/F64 iᨗ|?*{q6!CZ(&IdY3Y~\~J FL5lTNyF]L f1ѿd.?q[M/Q߇z#"WEzQ=%HM\(z$7;vd6ɊC$A +Zz'7ĢA=bWCnygGCziA%fQ?=^vJGIN2nȽK qђ{է&m:9Ӣ]\W\KLEt…VS cXQ"5dbCD~psgO=$I"vĊFH crzÿp)'8u~wyޑEy$KdfEp&) GVt$afYy$I?MGEDZIΌ_KJp-WKH&;%{f* OazELR }РMzVX4[$UԆWO~~$ *#GErH[\WAX$>VψS>i~~~x󫡑^GC,|gK7JC9IRY.e9JA=Q^zVO qѨ~ra.{SIÒKl&;ّ͇c"£$k92+}*ythJavrRo f4; ި"5)⯡,rO9.;܍Β_Z rX3|Uf=爩&{#v^x+)#GG?OydI'.UqbɡM7U.(KT}Bq;[r׾F1n q<גߍj)&gBVQ~JxUfܬmHKgQt2x ^ۤ[$6 [yt8@eFJo'%5Bz⸗i{a *x'':x!, w~:Cgyy1I i~镤ix8@6թ$(f74MI) XY聊!9(gaa Ǐ4M O3$cy>%I$K$K AEC2$Eð>7a/-8,0<3-ɒ,E42 GCh((iz!zg8dIahz&&y^9^x~W~^eIHz# b>$sHP$$AP$AA ɐ,A/I Ep@Q3oERܿDCC2<3?,=sDTD1D>D=T?G/p Apq1GGeQΉ^)4INY'hz mSmJT-^WMN,WYsiT]ڨ$GSTz8!IbӓCώYLOBCzG?bt`IЁvdu/9-5P (`Z`\@Sf~*ӔF_Dϑ[hXH^2ETfl8^f)j!G=.VjԎZ|4&=YȽvnfre`Txg` NbXTe +go$9W1OAUl:$KlT]һ(8vW32 +7]v()0^т4;GO2>o3ԊDL XBc^3QFvægjDO+`OCL2-1XCl:(l`4)9)%(|z~<XQ IZtJvcCq+FQLz7uAٻ>`}n8ZIQosHq$E u̅5ZZWSe/\-9*,Ee_׽G;tHǕ_ɆJb -3Gj[X tX "A7"#rBe_D,Pf.Xj]Y󨯞}zbqepw; &CU#gIը٤KQINs&|ԆXLJ_wK5%/ޡr(kSݻ]K{ +:I$#JRvOOA fHO\r'zEn䲢QeD.3Unmt\Vף^^D&FٍZLѤƸl!N vVLc~XOV +#5Z ++\Y* +iAؠCWqE4|8"flBȀ)d+|O9.ee/+?=blȋ6cz_\.a 1[MaZgi>[[l!*Xw%EștJ8Z)8jRj$wƟ11u-D (Pd&+\EI&fmd1OlGUWC&84de' yae kn@ 8N(I\eV70|6(7P@piQd>̺NN+gkgc<]m/0埨l>.KEJ0d+NZ.Vn.CͭnU}.ânyWE̒Qmxih_=e?è,rp>;m`1LL4RVF̟.6O,2xlU4)"EH\U.դ6A}+x4yꇂ3hHgEX$#j{f&) a#FCEIoW#KN1%#W?(z-UrHѫl`it pd20@p\5VNty8!d "3"6{䰤),"|1d +rFrkh=   35׾âܩF̭o?=wwZGAH qKkޮhZҼv%(Lu ͤbOJ4aAkjvh"nDťhsANY;xOĊ)F$,'?՝9bdD@IAsXU8"$(-FX/NT0PV6Ӌ +\F 7bp&u؎ºeLRߎA= r(9m gYQv\P8^p7Xj))͇a- +K@7Kq+pXwꭡXj( V74SoA'D34QY5~Ͷ6Wk{ˢ}F2Vq˒KSB7=.p)UmWzQTΧH<~aBFmȷLrz% +WCl)lgoᤊZOA\#J-a}" ]zvpoAnJ2^KM'p7ZX4XV6F.G +Tm@a]` jv2]&;7B4r(YfG-HS]^[}z8.^~MvR3"542 ב`+vOD%UJ@1t$pi?;Nt"gra:A%ֻSYg6[`~ik=|KxgE$;eZ7K*2F4CMzBca3-A8ҬO$Jw݇K9d~Ra5*"Xtkn荑Kv?l69PA RXZ%()`b7^`.TX;Wr*JVQ͚Ζ䔠fD|e,1%frh GlFַYn׎HV\b>?ACvt o_`$0'=3!]z$\Z:^D9hvZG+JNmĴ~)?Wq;,%'*}e+#SX3aUv6CjD-}ݫφ*P̟#-ߧ3ŴEͧς)6ewlٟ p$CЊBLkhgEcrԎGL'uļPV7Lo]xۊHN,rCCue$xd٩ &%f]v]y_^*F|H(u6>~E 5iᬪFI-hr>gֻ3ӊ +d]vҐUٲ=!I oIm8$'d\z"Ί#I 7Bdĕ58XInW͗O2Z;\q$}E/&Jq WtA~8㖽f(,Mw[cd̘pbNE غUldg?Y ɕ`RZ CVϽ U/$ؙ?`rEyU9&O_ yB_WWTv[{+c+`z1ܬIN*2U0K>)?nle$n~X2 Ie55-[րQiAOAC덐Fc Y 5\K.cGA/;}(٨kNUQ- izEխAխ +\?О\yS娻׏楿rV}kh䂲!GJꅬ^%_Thoy$$lcY#>F12#vlH,X=#Ho8zYu'WD{kCe h+{-* ~ | 5x +./G?\E _wd*hLt$jUGE +`$l&;0H͵LELzvIbzp[T CEtu lXg)S*`t&_Ay̰uj.19T "w?T+%#Wק3TC!`ܶPͦXb;'l GC"TiUoA~3RΈTdC:` 6\yiIJ5wS$z@Q5֬d8tQ h@B`'D B(Rh"`)ffPp4gקW?.,lfcgɭub;E 8| B@)H ^ ?(TZ.cEOH%:SQnǜ`X l`&8A +XMp#58F D&D;`8KuvY'fcnj`HXBp!0A f +8 "GR`1CMz(4Jt vrC PC↌-N`B%e^0OCmqޖ:pXe;|AjTpIA¥ńsf*9X2=p&P!D&t [vЂ, T 4pĠQڊ͚)z 6C"v !?؁x*Ta +X!@ H',;A@X.j^rR-L;~^[D82p!?8B ! +RxB0%(E)` +S0vt &,Ѐ.R\4%X?\2GY*Ph@jD Jp EЄ/t! C\ 8< H'`TjZv%щǙ?j4y"' N0"Q!ta X@ +r0xVH`tì%(4YA5b F@%$ OhB H"Cx P <Ԉ-tB̊-vmhhW#r(Da ZB%(?h|+Da J0n00p vء,lZШYN& U"~Fe#Ŋz)A-Xa +; h7r0h A0 +mf#[&Ty'0=)A%(V(/>Î4d@1#b/MT~Б x` X&< +$$?` >P + L Sa#yA|*:I*JV,ZlJ0B-(A GxB.` +R`Bp!I hl`CL`pIL_Ú^ oSh!pt MP0+PA +H($< F N HA@p5Z̨V7ݺU msM u` @A1| W"4>pB!, +DP\`:h…fY`|͏UR͗ilb%% YP# aHBtACp`C4 l@Rb{x5MqBjaMŀf 0! +QB@#A1`V0 h +B Hrxq L  wY4wR| h0'( YMȐ*L?l#aR`xA.Cl5K2; M2+C :]Bz`ܠ#x|! T 9A +6@ nF +.BF.RZ9Đ9A&` +OP& I0Є$,B R HtP21&9- +hA2_!롆M. +c+ /NP*Eݫwmu|N~VR15†LX%Ѐ"` +Q)4!*|A`p 1fPP^%j_pKPfkT_lcE jHCxv @ D)5KB#f?C<z7WwZ5hP N@Bx A5(5*3˄fҹ|Sygo#:ukسt``7h:`!x@7|1KH1%,2/8/cD)t.$*燣 +%j@%p!`@4hh@aqb7Rasb -VBVʩF}ҩȗIXiE +< BA p `v@( <Ѐ La,`Aa)yJouK1%,-dnxA @PCh2a`#ؑ .fARB`)UfkS#6e!ra1BЅ,X +I0n "@xB쀁L -'6VkDNvnfBFq3xVI1 r~LŕGp lmHƣ01;.ܬqQbbI)%`ESgſk; SNI ~Lq7\Z#Vb涃 +dj-RX" ^q3joWHtfA8^e=FMMA\ɡdn(9P~00_Ҳm>K"j%o%oD jVMH5i(v*^ȬlWG]YZ+xfzw-䕣tC1; )\p(F_<[E m4~!:Ua'@TL-l&1lTE#0`PX;LQ%&GF/-RvU. W_z٨r5Bp, [E: $<jKp" XʶL?u ~i/.濽+eşg?瀱0ivp4mmWS1xrQqB.X%G  6rG/HcKH.MPKn;xJL*&;IqTZ  mGQ),eV:ˏUʫު.av9OzUU~ԭl}AUMqC0~>zWga  IC&XRGСH).[܏{ mq"EW|b_?3oCnCJ "NrP|]IΕdپ\w`={A8!EIZK[Iip'YVEunČ*+>%6Zn-֯+l65;8 &W`!hj]|U@!&6TCmSas+W4#ejp* ݋o?]rg< őXLlŏ^C0V^,R`jJJgL+oQʝj[`ZËF +L# +hIrWVa#nU0]Uݹ !> rwCEiZ6_[K}P AlDH1v+ TE 12(t*ՍhŌEb}^79^@.VZ(+ KMLED %+ ٘P^> ?]6IQ[t6zSrHn"h cςRv3:{#)-e6z K,ڡ-_sy~+/5b(*jqJZ6uY̆[ W6UVuφKi0R^+v*(* V @åvmƄۗ.:hqؕRH%T$'0XhZhjoaÿaʊ + +i0ƫ*dq%,r(+._;J;8-CĐaQ*!^EI4>YkJ + ) +֯<ώf',4--++-T0$Ƽ_6>G,,E 6 v*~ʬ Z8*0A2dON(|u=+ɿ<Gc;8<#op@n@Pkn4(p>f~X +ɄԢ\I NE$%y4U#S|]^ElD+EfB;䚜m"żhd 2#UCkf<RQC%hi阙8YН^XL*a6`!T pCF(CUlTj$ -  Wi00=4#HNU[Epc`7DK b8NJnEx9 ݊fesD6jglTb.Vd/P\'pҭgC4Vn:UQmPA퐉8aSoƫ=ؑX|%$r,@XQϴua/v''rz!lL(*,/1%ygU|֍}*t)h~ a&(T-lQa;^^;lRJp&]w)jnh@7NP `@H$eCE t`W+fS]khťW(YE"2[8bjzbjsЀx +idu2/?1,Kт!ŭE Jl?k@PĎ*2efLdWJK.cHŕ◞LJk?QEJbH)EEt.[3':T 0#FGzi,,gk#+:8`2 !\@Iء^rJJxW2Zt\fb  +0\Z)p=piP3E [+Ge<Ѐ!lLb4\5 jpAR\,NV5Zv"W)AYUhC%uce6ri9lOTmڧ'W902^T#WØٿ&;K3a`^Q4Yz'L\58U lOq؍^F:~3 a)942)r7Tl';Ù2bZZy4!34)G %7n$FA7@av%&/ɄuLK3Pi\&(ڏ]LGQ8\`*-7]s+8Y~ vq;Slr[^d= *.;F* OO/y,-Ev=]Zjf2PPsS]hRJ-]a .9S"Gq1s[Qb\)D1.Vb,vQL򮵗r*(u쓓|#uMe \Z+gy)[֧*,$5(UR^  TMMOa_\tI5b|тj]؋kqb MD`I +p:fh6Z^5VZ.v)WO%&P`pC`!EN46HWr-2h^rld`^9sIVf,k\5M Bۡy y8hl(=w&\O==nW;9J*-?[~hf.(9E;1 XVZ#-Y8 JK&Ƴ4XT'h:++KPt5b`nl(Anx_C,S[Qb#SI5LGOt  *+RoHE9NV(|c44H,+U%Xuږ%E~}٦LGMRr߂NX8 d_Z6JJU^mJ+gQcbSݪM0%6aV]2F 3x5S+O,vhc6TpGlCRɕ\tեκg?䠠,)z*'7e^XNI 7]Z GWBr[T2jΑ\U]`LgOU"d5)%UTJ)h#TK;Fq +סt}J>9]HhؐU21݅ҢUL /Q5ϙY8O?9ކmja56J8PT)h)$3 Ƌ 0IVY**ƫ%?|ri˺MRVW ]un䏇*,/thHIB7 lp6TP[x333~݊soMڮ2*XLqWRߊJkhV~rexU-KJ4_uÊk7rMnOGzk0lhW.#_b=.]Y).԰yt]}a/-ƅͪ V9iφ&xo5zҤqƐVPΒ%'}Ff.ݥtUGJYήܰ,kk8d"El2-zEmFjG)dݩ4[Fmg(6A>`xzW]4Vx*qmoawza[~Y5< ɭ~+EʱmnOC$71%a:[_jr&?BwR+f<~8Gq4靈-|;6; Ԋ^LWoybrw(%UPN +ŌIc)4TײKeifMV$qɩna.t+Z!'?CѬi;VJ&MRvU7_q^`aWSw-u#դ؄Co\dS(aZ7M5UojFc}ٍ@?&=d^%=R4տﶌnğF,0tyFQx;]U&7,# [uY#-ޭ֎39c0~fUtVC:Q~z&V4 .g+zKQ`E,W9%9 N0VMJ]v̏JrfRp>56ӊjQǥhWOe_ӊD.~TGtͻ4{Ni]ٙeY pf8~^{ORģ@~;q+/!x[4;ҍ;&-+Mśdu7*(gwMp~G/|maE,W3UCӨSiR(cuxүoYjU"Zƌ--ۆllP<%*[Gt0263n0DBNlX${M)1 +\f$5`Hfc HK򔒥^$V"B3CF#erH'iF z8"~q%]GK|E8%`T|v*^t))3I5YےlXF_fBA+9ѐNI$Y:^V&eo֌B|*UN*gc#wU4⒝}.w:uOQ.>2bm}}wqhYۙQ7*0~a/: dpW +x-W4ݟdz84kgKq X Rlk( 2?]궽Q` r.xk/Gɕu 0[ ffC p 6C'@ NvT5cb^;KP+s-+̷c&Vd\f"#6P! jP!Vh F79^f3o`p\N56@df ATz 8@` @'MSWKzp" n \H0pR, r@TD7XdC%F*SS6sUzFI +4;` thd|<`#萂@ p|@1*0؀_-7,E"sRJ%ʾ/Y%% hx:D `%\M7:Bjr(2VV +Y(!z:7]'X–L(%x# ?# +VC +C:Pa8ˍgSv}JB|T\ZTt5.0A H@)HE@ #L&$(b9`Pض/J~*86 +=ApP/YaԸ +`&P"xP<5:0!<ʹPjp*2h*V!Zp;@ p&HH@8v``(EدR{(zѾm^%ٱRЃX Z +  @% n ( V@ 0Uf2d[g>iz7h/)vT@ xAnA 8! +^ np(*XAnJG0VYRJ0pf0@C|,6jiQCa#Vs^~u邥:f_dz|X tC%0!vP$p! (`A>! P |c)BGcC#͸*캎ϷZ?ŪaCz$(@ p "P"4`->T GJFT[-k!mʭ.{r4w5pj !@GBd 8 D8p r`bʋ NhaZypm~b TmC d7x riBIx@UO7)$"E`E6dA& "Rj63n-l@xm1+m;a(?ώjRcc&@\Ё0! `tp@@-7x8dž⤩#Hv>B $h6}R +&bs$|+R,q=Ї<%VԡP쌔I8Jr4`"l:Ȩ(1'bJ ꖂ`'VVOd)۝OPFKXE\r0*Z&AR"pf@5;DLI #vJ۾ z0f*Jұc C*vcIn/ 6OtbԪzuvUi8|P5*dP^%%gÍ3ۍU,UTrh)G$)?$Yۊ*&0 81%+%MQ3JeN+VɦO/OzmN 4v\-vB\lٮ%FkET;aGsyY5KHb+rh Hhuqt_I|FȴJ|q'fx>HʤP6[J(n8I!CӍ`UyY%'βDJ!*APl~״nSY봚~ $ Aj8CHĞ퀤#2ASA"ԧ:_YwkuQӰ,**("P NIsגYR$"EK E7(I۝=SSrzgy-ȸJJyf㘅c.F$KoQx_^-۹Z$LZ|<+ݳS{nU4ըE}]Aϋ>Ĥh>_EbBjuW?чGl/J[XEYNbY+aĥ[ڟlXeQюW҆Lġ.Λ|WDE 4-^d1>R$GPw}ZvOQSg􄌠2$"sR\ffR +:J}ѭliO5:q5;ODb;}'$QQn0Tii|X$̅as$wra5 #qJHLls\;#bwFM;̾z 1!|淒S%~3zEW_|!&B&jHC]6U A3NSb(҈tiQQD " m; ; 7+?]J`'pQ|ͱԞ ge7>JH2r-`,)z)p#~EjB_5^/8[~H +ds"S}VzB2a7lxNrxlO(m5SJOsmzhHf,^pт1ը*{  Tϫuu/xŤq"E #}#$>&`~LקHW>mzHHuѓIJ}}۲gKJ̰H44kϚ,-jz}=Kd%?A+װQ5}ȑ$i?+J00^Y3_VEAQU]^u_fQ|TκY} ؔt&?u*j >E|?$=lz$\a'r-IgQX;+gi JklK+,|XH<ԏ$"!EFkh}~zvye\p2i~rrFC1hzWo$=x +wONZ1jmU3שivD2~FNoA^R^>NMs*[FA?Fm(]erJx"l~@jH=;*dNCԏǩIO<7"c!y$@|jT^ ,Ď "inOUҨ(.85gӇ<-s q7 +_$Iͦ굏$ReF>^SxV?l?:ר嶲U\s_EY\G%y>_H +YΖ)-OZ^SsIrԌds2vLYjtf5!IJ!5+!Ύ45W ?(v^ؤ1j23RMN5kzY q}iһϰ<cvYM:th}xW_yk׽5sŨeh&VQ)1č5RиF{Iv]ܖٔVMM(iF]|< m9WkM\~M7&YiD;05OSSpv{=ͧ OkڊKX$zJUʩN=p˂!RxdU#ݚӕ,՝+/%}DIդIIFI9jŕަ&Al|VMu(JRO׳/oLnVl^lǮ5l3bH>d(G|6nYeuDq]}sSja9[&6?%wtݫ{OѮɡzhOEo6_hTŊVr9zI)H{ٜ؄5Xj-,gq:q{\Tfm)>z#mh&A.5&4&)sԠjݲf*>+=ɪ} \gbFT2{Qât6eQ10|ʪn:u{Êz%Y\&$~4%/4,^k +W [  +_$95yP,_ءJF1^̑1xH(+(l9SWS.(G}82 5RXF6f[uԛ-^lHk+oaf>dC LˋEnĢW)\ .s3Ri~HDUiNkfQZ; $S;! +T-RZ:H!Ka)pWC(tNǜI3_vYI4ogx֨j +*ַ(Ld(Pp։VW]ST.UT(~u~?|wXذfdt2ҽ㓝HC 38~fYcەj*گ"iP:c6K1ڷ!ކKL(ٜ[jǽ*d'7?-$mIZ0R`FL,fr|p4'8( ?ԒQD˿Q8"z(gE|3H>_d.XhTL,>L/ySY2&7MVFIʶ [[x*DGj8@C knB,ܢ~cq@dQd%g@|@q` +܈A!e\wM 쬏mp ̯uHrⶢok>vL'n* + ƵP5 `#Ɔ +LwwWPkNv֋ -Xt$~> "x8ijnbGLM)uϠA# ,`OĆAc"j8톕m1C& >a0Q,RPC-3R^'d}E)(F<6OP6˗VV< OX;ɵTj_IvXar`x٘l0IL(1Wl .Ƅ+,Nl,y-|nLrnK(T~Lʢm䢰v5Q_N+pN*|z:a5YN\}l4rE /ᮎޏ|T仠g.A#Wp JPMb'_Ps E V`R}_WINa-1]fƯS %(D1Y伢7@p&=/J2D4OI~T^~VG+KHDdƳ3J^^L-PbKeHG4rG#7bl+ 8NXK_IsWHfn g#.TZ;U}#ū̝).-4T`))A;jt6l;ۢH~vXÍ}@:^l&?(*1Zf"@>p T.c0Zbdh}G;poٌ %Uz7GZĎ&$x@;Vi+v+5&BhB h v@BDX p@@ 0|1cĖc8;p.T3mJV;+B/nPw +GSY]۩VX9w(䄣l5,SmR3@qR[l" bX~&r=͎HBܧ>3E "v8*,{FrTxjTxXfXZ| +wy)kNJE*NZ'.޵U'ѢH!G=*OmD=%SI>Szblok&*7"*_kk[,_36޺"o&ȅ&0JR316L@^44A@4n͈حVb<q M k6^&`#&Eoa205AF~ȱ2SVJ٩t|pт _yDЂ$vq5  P,@Æ )fQչ 7ܧhe$8r#%*Ji0LT6SDRhSX&2i4RcfR`n*>J46VMey եGEPO Q8Х4Q^];<Fe# AJ$:aGV+wMP w[:~IOLOgf Z*PZH0LpS]3ŦJKI>tEO%# +WKR6(6bp)/_fy[TH ORTv]ɏ +QUK?C& wk:W^Kd'2SNuߐ)lYu+)4f-;=6lP`s%ȏ&C6LQ,JX0?Yp|!4|dէnGE'AF LSo%*;MЭJ3U!xW]ʴj9kߡh'v!*^fb} CƵmFWAzqcD<ŋ Vw8a)"pFuԙ;׻'wI8ćdgODX?"ǙOpW׭@‚ 6&+~܉Fˋ:PHu;#ܗξFapz&=> vTMY$iA3+/4΂-kG*18ۺU c0jJ+ō+,I9qC,J\ wK [Z(t=VTtp6VДdy'+ cm鱽mE#/\rءL-uzC\ry̍|{! JHovRbd?I&mP ]TQP)tɅJLFƤ*jJZ2nlzU'GwUJ'\9J)ꍆc8 +ZIG̭ƊL$7Oo>/6KL ,Tc4bCc₹fn&}q?&(A+b"gra٨%bnxZ\6*pB0ɎU.U`L^^Q'8E5F.dT%&EUQ/?GđfCg^9$H+(uU4)7 hk 2@Yfb<9K-Rrذq"N*3RV9ҋ/j ER*e M(9pq.6\rOE%xlHj PWt*U\% jV ]7 +m~&g2'?oXiP@L2&Gw|f@aF̬DT$?$3&kZ6v-!TPfN^J-ކ GMI1b"`cf(+v)&YnQn;Ltj.GYiu+y~p#T.p4D=! +ЙtZgOzҳŏ=zP3ڲSL^kVZ0W^D 81(å$Vzsɸt>s \hVEͶlV5%WD"i}UvlАAAq BJAѯzDA(~޵7`)QQE9$2tOƿ7VhISKVzaSKdR*ƉjnO4*DboXB %dɨkFYV oONR'l(IN *[y0Il?X, +W4ZJހ ̌2KN3,OYuX9=5\mi\^T, ID%PEV @6˕f)+7d`~mwO9rEZaElDJnXLOi'l>)U2Vfh?E$vcxT>lg{Qپ|u;-3dr(?.ը +gܩ|HNA0[Pn~)I ig/#Kӑʢgz^-K(pUSllg\ }d=#M2fx"ċTT`>d~?l_ExS ͥw۹ jxӒ~q(p(WUT}\ + U_p9 GΌdr7BҋHZZU6ʷIsbm + +Kɫu(FK,E+*yIЏW$C\n?CկGC<d(1OehVq$'9Ta80Aj̜(r+:dj4TZ1>]tP!eߪ'Ԑ#T㉉]rcI,X j~1?R8w_\zK790NV.kWQ\`n&?#="UELIn#+9!#ȝ0id^φv gh#04*2(~R;A1"KЙdhjnF!tHz%fnIBC';6R뤟W|bnߎE~?F!Ѻ]yA H# k&ozb;ƌ6h\P7,:$`=49؋WR7+ok?Kcxhɵl6bsK oya9BE EN_ 3DZvTT[~S ^#Y: ',( M_]r!$3==~PY.(\Tg?W$Y06(n>Y2*TfBРE WT lh-7^ka"$9bU⧧)y%upEy]U}beRY9bp<BF +,VI+5CKƋm +l|8E1?~ˢBdӎ#rL-/ƫ1-ƕ|T4A +XPxF1$5|o?o!(E)W"QtJ(1*Ou~BXC?uWGD? *ĕR|zG{բ'?C;pV|(/Q&9TX&Mo8;%UϯqtI\bl_#al0Yt(|.48-;-)/U'} aŋYYl7A7*jC]F@G؊O=ՌecfVTzH6hѩ|؝v%FLzkhD!1jG^_?Mv,JVZdŧ(qpLP-Tpb01g|kr=q"W_zȤT&Al~&{x~XOSV\PsHG"GZ=>,:I2n$W>*K*C|552=ًngF|ݨ”ܕ[? $#RM';)Vם٨+H?;jeL}sCːTRר練fbnԒX_E-lgB(g~jrԂy)!^~X-rf7[E)%%+ +R6\1';|, +K-觟, [ d-$}+J# Og[^/->%~ *+)+QIɭJṪS5ʳ`kO~$Cg +ڐNu%/嫎r" =rY85pu8OV$Bzc}yͼo2eU_E' bOۖ#+d#&e9ڲ=gGClGL(WG߇Ʒ.EU\-~ՓE 7IdF;lHyJA9ӎkiv8a&krՌ*P1pBySPy`ȮNJq^Ys~H6ѤvOAڔ*6}T4@Nˑ*b9#/dIh^чǻ(>ʑJQ=&damg$,2Q$6فإd`rlzvַYmP2RE0jטmGӌ +"1R=pl.K7b= ^ߊ +dEWfj&I/ה|\V8~N_lO=v'xYe/;=J&}TjE*Y4"-z-ی"qou#D~uf>Mث\P`QsC KFŦ\O/;)ۆiV2 OvRI1/%!ArG ϐӂ_L7]G+6uGH"#hç;SD'2]f?koE89R ncAf^.]Xj!FqlwcsVfF=E9Ŧ7+5vRtPF7~cM_G^5 ZVD2W(`7Arۺ͙8N}z7-OmYON#o˯ ,hUkzڢWמl$0DL/!(٬IMVW:$S,O]P&$G6{ڰ=aVOI>iv\Їnzy$#lb#8&'`^^$#zTD^w>Y}vwg_AY޲Yag˪hK- }ՌX>j1o?$+Hf,Ðz;Rfj JMʋŵeGVٷ͇_^}ϊRH%ÄŢA~f:ˢS$*a%)1*Lʨ(v4M?HSx w'7L_OPF[9!_ ~,9|loU4*)_l rzVLͧwS8zRZVfuoFr._xBƫ(aL]٪)&[bֳPToIqɳ؁EhQo~Dfw]iE#7D U+r+ga3H+WMrIjAX]ޮ7zeګ-/miϒ~5_-_O$_,oq|+!QFM,wO9z KPrTH.ĆcSԌt5&VA H[/J_H=vc%IJ~X +nZ=IңbWcկ@eR3&IgGA2(R#,D~*RI:̢~z4fX .j%gX'*׽}\6?qbUiXv + !ѺQoIU,VP)^6Ah _ 21,[ƥd?=)ǩ( +O)4-ۅm4JAyIJݪ,oa9OcMx75=OwOay[5DCzNv)~.G79~~`zUrC|$Qh[Βǰ[端:e j3.05),bW]rjj;bf/.\-le +JpwSwUF0^isaL%իpObgpiͮYVN_[RujIIn +ǐS|Bƿ~$ ]1,~\ ,դɨ-;K ?u+is'ƿ ȒI-dp09ݪ_M[7C<;N1VJh~<գ3t&-OYUgW |77zUsbܰa|`WCIu=Kb=! }$j_l1W=y*L/ŧ$XVlS%2M!$5nSoVޞvUq%EjM?D[я 7wYNvrPiG\ }3ǯ-'y75"^֧%Z$%HJkgMHI}4k/boi^IFaPۗ̂_Xp3WgʢM*gWw;4eŗr__6W=,#ѷӍ3Ѭ;5$WyE $%nf=]PMKI6ϑYR~ }0qT$JNwԍRZIi>Nu:s[rAr%ejWFL v`P M^5$#C7wIVOwO~S(mj(êḊS^^G1<Ť o>lrCKtV׿8Y)&wWSǹ +4}aŨ]@oDWgz&vCQmi1Kp"lv-fI)ې_վ՚$܎bQP6>stream +nVes;j44LWYr=Mz +ا'v/)YFΤb֣`X滿lTw*&'[>gþXpu,%l1}׏-*Q*UP qIȍ( +eqVX2_0U'5Gs2KIͲ]5h1rwRD厠P؎Cٵӭ"t`y0+%妜^(ƕ藾Gƅ QChYa+ڨEN #''=b,WAn[vNǗ 9^zD@_ wy,|uh>M竟NLJߎ,lΏSYSƻwdզJNw_+ +,r%Нh=;Lw둓}^㤥p5.*{v8%"7rev)=ynѥ~~t-[pFlPhR(KbC4e赡BE //ܳӌ"h+b&%(] rFrF[Z9 ,* s#oQ宍twr?ֿj=Q2"lwWzj? (Rbhߎd'e5`FӜ3*|sҽz7Xn]UPIE9A9nߊ.k^ jQ}u/ Ԑ5R\:;ǘ/L&MP n`f[)jY)8/81lp6TV13Fl˯-=ՍJLl_Yٱ=}+>Kn<Ҫ[I΍ڔ,r7rK[SLsj6TT WY?^Q4{",!%|ZQR',~Vn~H#ɟf{_9QlGǧޞvCzC־$f +ii@Љ`8Z`3ucˮrH$g[R6w;^h%0: s<ōdl]klv픠¬n +v'-d5)_k:$2]-†J eMپU,ϗ^E5DQW  ,-ΫKȣ L~MsxXy|Ƴ6S)0053DXp%,ە=DUcWrq9 JǙJna'Yl]r5*hvX JM$=H .+/w&z>plgE.q9}k~*ڌ=UTâYV5 +F$ +&W3)U}b淹m#Alws%.C%wq2/% EVbf9!bX_2jW2H#%MdV{>m#&棔Y+8G'+ h0.8`h H05$A}*\x참$OC)Y]5 ]h|Ֆli6='}K5gBEvR)ɊL Oi8ZrC,-2#(hh?=+zDAQpVrFSXӼe4|~~#vJTncr_=+TY(vB^]Rwn`v1}=i4:Keyʃ l*ƕnM0HjE}!cԀpiɊClE#;(pÉ"bN0O+0;lHkG91Gˆ +kwIU`e%WEm6Zq#H +Fpg_G܈ݙxſxm +Y0IpSnr5\A ' ^CYYUV9 淸q;3=^zbч~ZnRV,2S9fcfeOC?b8M~`i`x)ڄ4dvPLJJ' 5z𘘏ei.U923F3J&\ǩSWEwmzcES斒W3٧"㑢M\wG?5|QշUoE#xM+okfU\ +wnj )gg>KE1;*&%llwY/?$Ɏl~NX;ʪGh +GQ(֌Ws \ (A~r_T2Vd15[K .UR ikoEI E)nԲhSp)n%/&+]Fn^y]xOǑ)3AêQN1K+?d}VYKqja,FIEu|Y3糺M0ǡGP cH,'=MJR neHmț jv~Wi(t8Z\66Lݭ + |+waX5) OcPT^~VEC-FR)OV1RhEbkf} +Eunul20$x$φvئfq2r&͇U3ɲѓ\F  +B1i\8N_MG$4)R?~5,Np|#WC+*ֻ?H9hn8ht59TjD--=#c ubmNf(8:ͤTf/2^FBQ5ݝWf'68U׃q)9ƷCXeE]). noC8\M5+~'o]zn4^|٠Za`{.oE *ֻd_QHݙdV*!3hI 6I.1>"KC o?GMaZ`hPy<5`j6S΂tBGVggC&x%E泹1NT.|YLO]Jvڝiv6[RHtd uq'n$+/)l~F淬p%-1=_%+ڝX-T\5mBgn&z+:fj6)2=-eZSTtFUF*~{IbZE}*B-u2)7^P ^z^5*C3h6A.##fr#gC;8@ikݩo]F5r#׌[= #'n;^Q%EbfNjκH(r,5Eסfwvݖ׍QT8 WI)&h$HpA$fL1bhfILGa)WM(E'9`Pm }Ibj ,npg#MFlU0 0@ FV)l= ?54Ej(V^=F ޱrurdzC +5+7}QP)l6>6\*̈́feH6 +ZMxǝ $Ii1DJ Lbtɤ5J*Хjn(Խ3՝ؾSK٥*A&(EHE s'JM~iKKa'\C&\\ +\IGY7jmt+2cWz[^{Mmºg} drƻ7t&,OuszCoUH +I>Yͤ i&b 2z[Ξ4;ƋnWaL1NN4PH x2\J5o\P q/0NC3<;VEPrYVq*VqۃhaFI. +8qBǂ+,{ŅH;P@qY^ͭT#g,kKL2l@`/pRT?nPIHI()Lpjmr&(~6J#8IL+\(r+.u,\I)'WoX%BǪݑVHu&*v(C?,.9MvA(bMϡ\R䊐A3Xqr39\Z,l; +YgbltbRQ{"()(9jM!&P&.d(Kk&EP 5aSsK؆LFLJtzVq# +] l02};Uo_^ _I :dH2NR6RT;dRXv.u5CW+.݆K-BXcP>an(2,^.JR52^Jgb:.&%lMGK|JJO9'$F!C)lULH ]$QN ?Agd)k$)WZ/0Nxy56̠D`WMyሉ툑這F-4"q(^q|EC +Fo0bhf[Ϧjc^wً^`붉/rMoy5h.UR.v)IȄFcriɚoSSnq$erUC`z¢YH/JN,(V\PX8*UbboXA5@F,LXF20beôXUC6dpԽ+(b֕5_ōZC fzPF"wJY2\j1Xp/[p>ؠ(9M!`HlCKX/.;IioefbJJF1:_ݥ *W֭4(!#"{&V[tp7 n[AkڢIM^˫0ԃ[Ydd*dF]ȧN{vXqp1$%¢[Tn2jcnztbd{gF +Ewy$ջ2߆ayWRkf> +Of!2l:Vthf$0@xIa+ll啷H*Q#FƧZT ‚i2ܙ^-iNI6VZ h %o6JQ(U%I>[x#qB)sKA!Qr!VlprYsŅ!GMJIʞ0ڄS)EMFJfIɸQ+זWK&PYHY%%$-\OXwͭcbbh + *.Vrk=U؍d9ڲO gOC,p$WCT+rѥ*&SnxzCLsdPP*d|E7ˣ5͐K_ {qi)p'Rf`E ZIɦ (r+ ŎS`F + ]zU/EfC@#3Pf5ø6|+8r\\~ĊJ|ǰh +Oɥ^:d՟z1+F|+ɨyLQ6b\x6/m3YF: +iE?iVmoy4d&vfUQ/!N@ig~3Ű f@25N~`R3IN +O˗lcEC L +k*Wć+,Nf7) n+py 1C6Ȭvh6?EELrI)>I9+c#P*P%4H@ E#BѼ.>Bb 1F  !| ~5I߇{b~BϨ' /UDmM]Ӏ}dt)RZ[*ğ?Oޞ7^Nuziݡۏ"SNRmZ{w;*)QN~6.58&Ds%HDeȮy"O4W*QY'Ds%HDeȮy"O4W*QY'Ds%HDeȮy"O4W*QY'Ds%HDeȮy"O4W*QY'Ds%HDeȮy"O4W*QY'Ds%HDeȮy"O4W*QY'Ds%HDeȮy"O4W*QY'Ds%Hh~Gb. t4n}>VfEoXH$ǯ@$\'X`Sǽ@jߴ:OyTw]" sLrW- ֓ۺV6sLGiWe_Gl8ue3͹^hAPp}}T315XI<-04p`,"?%WYy`sX& -YWc#ʐmHA]B5 A1]#7 a9umFV&I%d4T<̑UD9[YO`kU",š{\C8^SP)9ۻ e$7ČºvN@E?z*WxQ3)BZ Z\8hӼ(0IhE-VD`U她F6@tsTMRV4W?![ G]`?6>=pXGAo̍2)7@?qؤp]G_Ź m'.H؟g(۰`o[xigԲ!>A _ {3%otM>R*HDj@:f/msKDy~y#LGA.zQ?˜Bu֯[l͓q)A+nYl\UD)юB^n3M3L>&zV<@Sp_} Q +c 6!N(|~x3YZ(.6N_h +YU@<l1+`Q*~\DxۆeŎ9"JO}hr_#D XF Aq)(b$K1f|9h?>5ΑS! H܄m'Ie&v4t*̆x8D;v܏o)or& <1q(o˷ &Z6]N8(7k*gnҔ!V'tƹ EFѥ@,SI@@iͱs9*P~#:FFsE(]>[TB0u)3 sGdMCJ [|?NQF&'7O,<ڷQk9oQOO^CSUΒQ+me1NVd=Y\h?YɗfA%&_[ɬ,4EXCŒ 5ʔlq~{8@Ցemie +<67J$jWH -Su_5hrm>ڦA( _))W:dLi^Ï gpY֓^-F9@qdf kmc.5Q95?I;EF)|(unqz`(4}=C*38] :$rx(A1IX M%<#޻#4ۂ1ENwr zs-ΧI;&1Fi=V #G:'[S%V f G-ZKof2sTZU.iWld_ D/ XQ4 DG᳜z6hலOJpPJ4ZߧuBy(< +Ex0ЏC!!h +pƸjUiHҸ ZM]NWfI85c{h:g vJ0խ=Pn>.O&hn22@*J 2hYrv(U/cK!ZŞ1,8M5j " 0HLXH;*&4N3pQm̡!mA %ii09DH2A~Φ] 2Nl+aRᘰϷ sNnC"H3:q-mاB /sʰ2LB&xqԯJ F4p33Dr9^74s)QV+Ty7mgKăyᔁ:[4l(ӐSpM[`mNET j]q_`X S'%{i&F&hKρ5q"]ZTÞU4N/ȹ9Y|?1 YCp-:}4?4J*;*++]DGQ22]!~99Aʒ +T&=0$2:)g|/=㳎3sm߾u1..d\׾v5т'/AP%MHNlޙ7<\ŖYg$jd!Jo>O,0EܔFiQgjUr(+M|6 +ٷ7JWN43x6ݟ*ڸUZU;p;RhJ4j% pi@N~x.lYoʳ^E?OK* + TfoG+Y,Sk.3p umyV^\! (bg`>8e-4yIqdטYT.1wFiqq܆FZ/ZE02hu`4;AҮ4G-Qy 2-DSj-Qfbd9 +k!RX{SG#6 :XXIEs1!kP8ko!mbv^=LR4cȰ*(*ފD"$KaT. S`duq!MF'N'NF5g"ǀPϦ voHm`hՕ@%ܳrHHB"~meo@!z(48fЅ~3HLx𡠉poi9J +h~Eo6NVYLkf|8zC3@l$]X$MA$6 U +ElmCDe{"Ƒ$`E( W/Ͽ7]˓<$ұGl;R\GK52PFY>nջAp̒ux K9<*6oxo/aK(]Oo{ȤD8 fX*X;gU . +W6稥\9 e@ Nv +% J$Ow"z[ai"@Nl`D}Y#'0Df;Si `Kx3@|:-n^38,xoW8,\x%4XETϝdkx,DK0_ zAѿ9{JfJӮF j5=~L1%b5}Gk\i[H[Rx aDdC)- 2o2t+5]PA@2CwIA\kR$#k,\bio"$ JP!rGVf{ oKxLJHW'BL泅M +UM)$7d!,̌\xZ+JVfb3Q3j\px6} 7wF}CXkLǤJfo(n9B{|o9oX ɞSA\J2AV|_QۓD0ӧm7ee<@I=2x cïM^@fi"&i>^|Ћ,]pj"~ߢ?S@ +^}hi&#7d )܎u%y3PeP^/@S< n9wS7`Ûl33t3PgJWZn*&,d%P`ҚT_L F/\8PL;"tFwzPb *ko1:O"15dg_=ī Lx*lŒ|]Aҫ#H!~$l>@|D VWe!?S7ZAGNڽfE e΍FCtnuk +R{" nr{ po˾Y {ت.Ȥ/S٢Tu@@ /p) 7"ӏEƑZ^1f*).` sjZ6djmHĨ<ۧio9Ykʋ׀ N^Cʌ;^w蝥y*FFjVVN˼>mС5QYi%Sq;Nͩm/0]Ԏ+bw+O2IэGO H k-#|VW%TRE +FQ fi#|BIkT^={Fbt{3d6j$d˒iL[XΤncqTĐO `ȍגD.v̘jR2m-[ +2@ +•hMؒ)(`5/yAJ蜥C.DI]tW^^5.zu<5Vv 5:ǹ;xS8MjTQ{|XCuTC5&?eKbxiZ՜3{9HPjNc&H#w XtUv[rl-&תci yNڄ3KѴk*Vh&8L'FFG-P:Gtޠ(jg6|{֚!>GR*#*= +PPr^PfqGk-.YM:1`%+23Yp` xchpNUVϐK}uચΪxь0=v5Q&@">Z$:!''_۷IU.Kȁ@6m*?fޭm| s(UP W,_} B<J~I+v)si)X +:c6WrWgj@uN!*pS">#DzSDJ*׈_g4: ͸o@[Z)kרk WCCd3"\u6 L̖(.e\'*K%mQn.^#"C4q2ia/l9;ͪvpAT"lʼP4MF9Tdzy"ʉy̐kKNynv vY&h$Zhls#{lI>/vy;lw2 5s@RaMV0864 QQXT2ޱ9n)*}]E@)"ĐrY~ŭTe :Av/%]łK,wi&xW;مlQ;#9ϗ5n4d*>AT"zT tBm +EP t@J5HEouR)`[`Nލ;n5Д.UˋjHjNM;臭VE=IFF8TH +(1/x=+Ҝ_,TDIkNPp;,Ft')_~W'Hzxh4DQ:n%8 H2W;OD4o4X ѾBk!-Py'H~MOv4]P1OQ A/UNzZnR>"R N5a]aPzO }DD+ Ê"2/,ё3O$ER4Q(Kr$[$i yh!:h +iE9[F3i!IC6 jDYxi 5QyߌvNC$x3C6ř8k3Vm l,4cEGZhBI(9ၮ8R#*U<ӏׂa75PɼP#(by&y] 'L(3pH ڤ}훩C|K&2Ͱvyqپ81[+?A9a~5CQb(nB妿lC-U jε`*ۨ|XBSOOIOݧڃOOϲtX8<\'q).RB~GSKZˌz9qp< ep^@Yͦrkjj-vCk1hf--ern6fSw^%RIImJrHN#R!TDi/͐}X C@9h"g44Ь}~"x o34%e⒢"j3\ _dZť&}dhI)Qp]don+%vs$6^\h!sJ\\`f8'ڬznvTl%`,[ ̌ۊJJIJID2ko'EeEE@aT*)0(0Zk)y)f[J j43YRsNNJPTZp._FSyQy!968Ңr"4[䖒23(ʭ6;Yq)/B4Z6+y LmBS9Qѭ2*tr4c86>r_n(K&-Ee%O@VNn' JIͬdN[r;Q)QUr%sBSSn*rrr!o.08\ɬ~GNF'Q-wpmғJNV\bTPn-~ zz|_XiSi[nbYDEEARj5VXj$SRR̈H$ "(T4@` PCA@ 4 1Hm93H6/(v'Q<-8:ʵd)&V|9 Z412 !6ad3X*M; 듢Xf4]\xfqfHW dyx԰s3)fpl0X7|pR+)KU0_v +'^MIEĐdmI6LzbQ.7+KH/^ރnEYpήlڀ eջpǣdHftW^F3?Yެ?3 B S=NkJ…;d.ϓ6.a +"$rd4Gp|Od Џ /eԛ.|DS<ٛ+DNq"M4ܓ }AV.w ܁f?eW-o{h9n8$:\b'8vL#="!7iGp)w!dw*;+sR TGـt +Fp9=Im]/q-678Ua0Xk(:tM +Z/b0=T6Gry$E #D۳_dbr4|a sCi1j<ٶvJEOU`]aP3=Iy5RM8OUq!'D=˨X0M >m⤦Gc4Mb+i@Y|pmb,xM(ˈh3!b- {\MtFDtf$Tj4S4/o.^11-lI!=)޸)H*bQ׶=)RlFaD( eb\.C*aȑZiO@iS-־ Db&꓅S8У.tBh! Sf1:b2ϩLEz&)).;UV$D 64TGjkC`ywg'7K/GrJ4)hAGm1ҶxE*_[؆ÂŲ'bR_ٲ8Ւ11Ņ勍_ƍu4*RGRסߞ WM)ÂF`RQg3fcV>~qJm!QS# qLca/H5RH('ِ,OHwd()sB!Zc_zшāyftlA1ǹ&Ҽxߖ6ɑR{#tx,A)\{HnGWVw}65f"k( +((r(UQx|GQ ;4 +ácCQH(;Pd R`f8(iaCEa!GZdQ? +p(nPlRy(W2a! yeJ ԬImaǍ 8DuqC+IfkHлMbI&ԽʴzXԴV9ɏ]DLy {*{]G +)YbP,O?n.uSbyg95r*K,DjLPl[25'i-3\59m5[!H$VfH2n)STS|Cn z˶]Q<2kC˫aClᅽTr|nl&@A8U wCGz<Ӑ{c0! +Gp(g'ݕU+i^=W4~z á0{wKZBdLAAc`&  $4# "P}JDqhXzwE}3^c'㮙*m oq>fǁN{.uJ*UoJ8ݱih~QڪWw6$b=:,Tm<QN&[C5I6I]<iWV49>v&eW|ڕM^vmnؐgy=/GT($J.?VaSzLaZ|,p(f/v{K5Gmn򧺃RQ{m:k3w &4їՔˆtPZVaAj|Jjv9#٦p gc1QK~ QL{qy,s9J51z,Y +d@@B 4Ѐ +8@$ @tC`p(F #ȫPn:myIj[ŐlʝcÂkؐ!D pkAPP~LԺ!gǂI:)nfD6A03ݝD:@udM_k%:j]ӮiTҦM)ܧY&.JK3 E9EhiڹT?Tu]= +Lg:WejCJ1t [g9X{4"ujWx+zMͅ? 1 [̈́Ź^qq]E#ٺV9~eM=ƩLWeZiK۲|DH9GܛY^^3l?naL $h \f@l2,˜14Y:v*r +N{!nVW(yg3 R МjCf,xF/9d32+Dtwu!Bn;Ja: +#+D]5?uǬ}hv![SϘ-~s9?DYdaQ`1 / +dT̻ :gE% ~%xhmȽEo< 0*Ǝ'tBgبS%XO3N9Q&tIܾ}D5#i*ޏ+3wWM6o0ĄFe2%xhMȞ) +@Wg#0 B ,ۻvQnX Ӳ匀0 A"hxTH@9Z>E#awخ4Gùt(? r~l7F(USI9sMF.ߏk8=j$a7\YߤL&9$iU}Dq5i ̰r|VHy*>[}@#=Po㡊,v"io(aEeQ9{q>pѥRH( -=<5: +Ly^ѨNPPžfB7o$3|$TRgo T0M+"Щ_蹛;AgH@ҠL{ ,^ Onp@e(VjKl6*)K% K?AkE7R|jas Vr:,\ 7EY!>W\DY0C_Pǩ BɀD@7V@(64!&+YSbJ ">';gv'dD0 S D  ! hݜj&jŔ.fɆDW} +.;#2`'"wu3e*52ڷUֲNc-!s m2^T~i &?ѿV(g&:0k29D6Q/@ {$]>b8bZy +2o՘Y%WTzHwO +E&fNF2kTUE_A.:qG߶niuNV*mK(Pvm-Q1X@ lj! +lMtL]Θq V$ 'v4R:u-ukU+Ժx9">(V/8Wǂ"M.Z!˴HtAzZa"Z+*Ras~Di8i̤Hh$e1 +k}xRg_+)֊rk]Z]=fxvCv6WP%fj/p_.|c˃ص6y rz[̙Z9$ae&/`@/(8 +O ) $<$jg9(kӹ'kŦI-Z&tZ筘Cx *k,"V"[ \s3Vmu` +Fc8ޝ'M>`A ްyЂ^ PQL*>$u<0nؚ B\D3s Ov(- N$d7 **Q}D&/G|"1#<sa+B*E`3<2~Āwޙ\-O IH0pVyi2,s@'&=^%᭭w宭<0mLfkA<~Nrp(xfTb̘5N;Lcn<Q&ٶHA+WΨW?j#̬sW̑M !̽1DL Է)¥2\`qb3I;OO9ޚlDx6EDï66 vi7pU{{#:Ly%a^)Ρ q_ٔ,5ݴšN#yC0'Gf t_b$t8'HXa$>IZ]\U٫Ij$:f=EBEEžsE\C |Z$HHTtժg +:HxCЇ_e7 6+o@F j#b1 +ʴ jv5RHL{#A" c \C@`$|M-f,ٔz9"% +m"yRO _R#'FWK4^4H*y"^8╸\ >Nj`cސ=1EYYy̓4/Wi_Dwú <Ţ˪EZcr+I >)!~ڈ sv<2ff* $gh3b2@(bW 1Xϲ"F0en.}J0B#sLC硷;B76JRt_B <,ګXgKQ fKx2bcAdnj4b0( n<`wZnm{gTz,'*.N%7_I1ϱx㵃H\ D1-iENqoF7.*M,;y_ͅ90%P늘aLzKP8*](Ap)di?URw%/Rl("osqڛ9 5yIJ#{ SQ#NKC.Ud{g }o: PDj 1L:Ŕ)^$SC%!')$רP +[#PiY7cy=VH H4A2θ+p0F ^ǬHA3JLkw:>dO?G6& >с|w̥G<ϼKKzr>/UjB"՚-yν`MSkK#VWԐ1ls90Oo5 27ݸu,B@ _+HÆQTGTPbk&L?`|A+$pԚ:p CSuKz%(Z'8v֘b0GI1ђf#(0%Oj"tjG&[5/3.\xA"678K~d,p$ a lbaWK +!S $E䭃*SwbEJv~@-V0ލmۏs+ќ*).UaQ!jjK312:YzŢIU߿ f +Z s<"N'#ulQ:؋x0] ~eO|Aa#eVD;`1r +:g@‘E>Ӟ2Dw.a(4,h +1V_kOcIa8@~1Hs4lN/^Z. ̋Zѓ"kv"w +a,88񂇞 Nd9HV'4Z<6Q,')w" hNq9B<*V_ a8Xq꿜u4IH3"x.'&2< @59sJrFm 44<1nZ0EAM_GDV +P{|VV$, hPP"zUyz=B +5ڹ Cqc-L.G$wD +^?ڢd$8CnT9Z(RFS ~Lo`QЗV0GC HHpPO֕\G\I7d')_ IICNV ?6 x 54Ja-r!֗שV?HcZ} Lu*'Nd[cnkXYYŠb>*k xr[1oHeqRJESif;D:έ Mļj5B"jDr&7[,Z{F9q2S(C7xs +X +j2q20}t2Ȅ-xMqcBT1tbV..bܴي'wZTLDjCP3ke 0R e_ql\fKOZ)9R:2\bqΤ #)4jklqXa}qp S=Udj>u֡φL!8O VC#elpjլf8KV}z#rVM jSU/(- ܪLjҞĪ\G`qV} +ט9(XRЫjɱ+7v9AXVT,*RՌ"ވ#nTKN+ᣮ%U^zK[1+jh]+zi^crkV!N&ұ (#m t(Q_lJ6^-SyG=)~%±ndL`Z6%`QUoCսჹm6.~,ذ2Զt#ե)6El-\|Ejyu$W$8 $bXmju1sv5O: X٪d:Ff(ˠSUj:x@Ud1ϸkOV}U3AwU i݄D֫3UK>[CތtAqvhjU +< EN M}ޔVM,ئz#32O2:e38ʎZCh^aqXyHjz15jyqw&Lٽ +ԅӔ:S\ ]XFuߥB#u =u2_"#K|ΌA*lORXw]߶ t?TzF*\92J=ЀUTmK#!0am#x0YmHdOr h МvIL(?>dDMJR=[#Ж'aۋj", 6hn4lΌ9pdC%Rc,5#I7nqDG#;Ԥ׋%ɞoE_ܚ0s+Jά3:qK̀pxapI;}鎖]JGAR[^3T>YFz#ˎl#8k%U| + {W4)Ck5 %S=e)QL(F dvjbE%j{R*oއl>AU=d_2yоB8u([U1 JSOF&fI#ďGDo%U&aТi!%Q%B{ ;ye4]Es0YUCkp[|bK6z,}Z,/׮,&'t!<ٷljY ( ?<,a?!f(FN'vsL~~r^K$W5̫n+b"U1. 9ir`&RCm)p;(eݰ6. e,-4c|EF׺zdY2 'hX$\ +Bng|CՉK"ɰj-Jx@X9ۺK̺kY"GԺnm'q_ |;rJ.>xDd&{/lxZK/'smjóQe %TAwPr 'q4+ |܍_a,^S\]>sznstd\N~xEBK VfI4fd<Q@Ԥ1ljΒ;Ǫ͊)Y*ذ2v[̺QE~86h%r99} JV3fĢb)̓D`H_s}Y]h]/3@,6 .:jEi9NU,5e{*yYEZe>[Dw.Puˀ"-R([rq2$h[v #Eb1X+N! t!28D73HGIMO(q @WYW_E]͔d0p)rՖ 6SIObaU) +y-ɒWmPB%,<1(tjsFo }"L k^D|r +\Ɓ1:K'^bb'֛<($-&抁T! ]Gt]ʗ`>>IWÊpN#֏gyXbSVd)چ3 x +AcC|㉌ѹ-#A7@]>辤Iqw hW>TVC> +sTG]u9 7]~%hܱ C,\(+12 9YJsȮsJ/sUG)ַZ+Yb4آޏߨ$zuIFmM0XoH?kgrz7^WԢnxl"C{RݜU:>޼@4!ZNsedt<ڋs:u$h1-<᠊&+:(ҟ{wXH1%B}HXq*a 0]T@0j yFcgwHH>vӕAZPH'"`ߑ;{yLǯE5 K\,P$w +tˀ9鿤(ɥ=+ e0۷hGKK>Zd(H~`/E'~?{<$`xۅ,8w n&`v!rYF(0d7"E ;ts JK ]}ZsޕQ~^Ar;4T,{%SjrsQ_vф3J{[!npCC,B]LS\Zb7J=M9ESi.VdV?*LOrLZf7eeIjР|@;#-5 I|Rlcy]ela  7і2g2ɉ$&_G'{Xyv@J`2t#ߑtm,ڶǠ~)9v1ԏr"h߉=aP7MPC.?ґ54YrTkr|u sJAMX)LY(rFa\.0 +ddè4}D3 +M>*նeG.V] 5r[3u?25tn1sI7jE `au!IAb)J"m0tlE@ꮯPr [C:groM\Aj䁫a?qb +Ömc{ NTV͖OCk @a)Ap'Y5Ӡ"cu LGwB!¬ %l44z.`l%W:Xu@~P8Mhp3HW#A8;"LG?҈ׄ&WbCZ}VK!461H4"i͇ yW[p>+FM,L=;F sedװkv1^lb.A9SҔ{_s@6k Kq$Mߔ'9ucS 3иju`&. poBE('%އ/''2fY~J:O<Ϭ|P9.2uu$òJu몺": c8pK͹8 =@qt L +SŐtTS}CIERrHqez>B{t +!]p')cqo$ JP1k$@ӑn rc#X ?p'V4֗Vꋧe#)<#xy&<B"ݸAt|6 *G0;gA=AA:m/zg&vڿ'c=R8$,_[., D]8~DZZXHr3&9ٍ>z~ܑBSvF[{r}O|1S2aV|>G6ԦXТmdZ4+,%!TKØNDjUZ&5Hv7qIoeH{8֤ F64#0N /օ~0*"Ve 1<\]-V(`EzOih9Fw[NC [vd5yZ?d8X */5jL TM` B$  4N^ u"&W7JyY#-BeݓVȓw[$τEz>>+$̯<|TVw#@'#y͸._8DIDr|N@Yx@ +E?RІX} ؙBKaBeg5KP f4T2A1A[paiX?Z%h 2lfSfME: +W"7NldI&?:ߏUeR4bE ˓*w!0@^bT:m2<) +,5,Msf7=z2*ɕѯh?k -Y*v!{WE}Mq$$+{' +ZY"+h^H +4@2hTtO`ݹ*=x;߈jlAD &;ߓ[ UG"v^sDAY)~ ѥ2eN!oq.\f`ʅf#=Hl]f9,Hs7B>3IMcHg(pSȡ.4z yW⺋NR}Ks?;H˦̭>Mv4Rhm# ei;1)`9J͊b=XSCO=w{߬or%mPdK ]iC. X9aG>vs@uȁ򺒩ʽ>0!uv*<=!۠`ɍp\HxoY+Sb@wSV:12k}ch&蝤U-|)˕Jʑu(h o\KL3c3 +Aꌰ=h +=3sH.Ok=VGdb$\ANMh V{:BS*<8@SyVRcDw08!ALpFFM]b@ET6" +JM8gHR7خEMDM"-*~/E 1qG{z˖;\< Xq<ŖIn6)*ۋ=H3~gu&K$Mɋᩓ9rF*pF$|▆[3.#(#{D`PK'uK(І9@|F1TpO]CG^Ds,CX!t04^ I?\kw+^ّ}k%IQKD(=UB +ۗiT;) +ZaxrgT6YΔZrx AP7h1- +1q|koEK/2uF괎SCλ"`QjP}!ttyr0O=N?l2DN)um E{p) vIߍm E&/Swq{[~m[xH_ݮSJy.ӕPͨ1(u{"w[MPsԉ##MY@bZ,v`B8@ϋ}"%I˒@lxwZP*<p$[j%fGd-򚑛Д6sEQȶTyGх,IM7D̽E}`%7CQs-=PBؐېYD̒"lܽ{Fq:aރL:vJ`+B+Ĉ#({ ++A k[t(mv͑SQ,G$c@L~TYZ^b LH$Hx0mJ^e˓#:4E^^jlESY0i ihnH3* ;O`hQۉe.NS7խ6 āҙ \LI,o8b8++灌\$H:Nd#kՊj +6\]- SPsO99.[h 66EE|$[w{t%4$! 6MpXhD:@!;Z mM[rwP tӮtTE1櫢fGz qI:<P7j.wʉ%( zWh(Dy+r +┡' Cd V` + i:ÿ:ig)#u˓k,ٟjI +NsNr!q e[\4CShANg~;YFn@v<}/cXd|ggcV 5PGRnWc`DbrP6J7%[hz@A܍dڈ>gk]#$ s#pzeq`Ӎ1G +~Am}c$ay }zpDxTY-c^\L"8 +-z@=0۸G gEuJV}ӖCEDdos@w_d:S3 ~Tɤnt4<>S@yPY v\FVO@$va !cqLE +\nfDK1'Z~ߐ0*6G,_\b%0\Z%Z?7kJCRhXy[qXv5'Q84183ΰޣ;r$ll(Qbm|,a~|Ҭ8W4NFjW \< 硠9&E vESmw-Va +.IF +zDD*QA*J>ᡩY gG;p,.0 +BK`3e~w&zێ}$t{<"LZ?I뤺TĴ-QhѨ +4$nmxEQ;K%H"h'Y; +>{Alw"!I)r OږbK!#n +B?Jw EOH=ydJ Q0bX"AyXƛ#ƭMҕUHBŘce/4Oڄ#^9#c|oorrgö*WNya58rv[N4l2oH:h|iCY}$^xvO>Ԣ%h8O3zM\u@ErD!Q`IUBv<YlG&;")eh]IBeR<Ή}"ݘnToK7T3D%6(AO8C#G [$9-WM8':P]|ˊ-ОJA@U{g=1}3!z3>N"ۣۤ$;"Ⱦ1<WjXy@Qx?.MPsPʅғ*07O xO9ay8,?p+UfN#BʥuJLPd~ ɼޜtW ?.iZF^;7ӛĩ$ LٮXxᑥAifC}ʙ^H/,z-F 2{aO YaliȤ䫪I\,b {:7RP9ײe/ww*\,8!Í H0 ]HN8)&+T"k҂xo}2CW*0%)V$" +?ŷikU)Ha`@~QS2Eg4@ 7$8G+;X@7fj*tЌHd#'zQ͑-|*1YDtsjsڴ掙0&]Ry0#f̴")2ɈѻC9d5\hf6UU8@Ķ_QJ^d:>Y?q4a/)(Dwe.\[: z|#q%4|ii]3'!Z 8HyѱKi5 Y:rKӘ3J$^!;Q9qX,rFPTќ@4t*X9**b4x`#!x?0>Z4pfG$+AY&'zO1TpwDYETFH\ 1sB麁u-yR&@c;ԙA2ALrlƍe]󇞠'S~x%"qBkQ4+V1UeB5\]j'dd&AXmK0Ġ`up{ +èN&8<玓a狘McǺJyf J XP`t3ArQcEGuu%pk!vDn.KmV/*]^ZEr.beHi?\2Vd>NF"^*bNH 3\/|.AGjG?XkI6% JْgׇʗT6%9礆Q3N 3'v`-J^^amt @"=- 6eXY]JħACƊ(2ǵNGSʘbzkL-UJœR}gy" +ʄ]O+rN4ROg$画Oj$L`9&թTBMl: "-ˤ@fl9c#!%5#nڌ~f=AϊpA0fqj*F(~{8repJi*΀ң.(bI%lh?Z#̲%|αŽĴj0;w`Njk2"qQXW؊%hdBY9a˼ +$4Rkcš@@FV҆,B #I +U$v"嵸;O3kMc!6`K*AFS. ņ2݀edϹg,QȃSp7'fxr8Z fp®s,セ`OHHGԆ-PЀ{KD\KEt|'|U2Ō3X$ m~ YFN;xdb STaoV6N͙iGfM)hFQW&hU 9![AI1;(K"|Q;ʼn6zY!a9y fs଍s oLl ΜSy$vYoS]v;r\rS]0DaBnQ2Dy^nĝd1EBËwaN B- Yő%Ǣ/",j ⌀(}xTZ)jrH:lPUF=!\nڧy! +xLzT8e'G)s]Ƶ + Jh TZ.oF V)IRNC3Jݰua't?^rӆa^ .xhWƑp_q1n)M͝֎%,~H0ɏ4@9, CVQYISP("s3n<.>N"eJ͐#Uď0RԮ D#U\C}ñMK^S4E8]m%Mrt^=|)v ;S`4gxmi;\8PYAuZ%h:G =A؛_hU7d;A5kNi~tDxF'oE&Fuζ\ur$E5\)ŚE*!ף GwwTӱ%xEF>>몝R>U*Sn"QܱuZI> Q~vHi>SfsBn8kXr- OKX+":B^xH"Llp3 +nX[P]jXpX3Hxh2E&͚[UF[ާq2`@*{!'k5u~0d#Gi=-1WV9^3*G@E M|`ͩ A1^TO>q>lUW_=%"{@~*BUPqPM7Z_4PT, ق8PNn vh%rgq O>6A^]YiAh鬱i%=5=]2 Ϛ}ԔT,~ZGzins:Yݥś$}n%,djAyXiҔq&I9y)<1cQӝF.bzٷA|<58ړbgсgm1p=d+pM$\SX\KE)m]"bh0 RǪyuՊYǩoV,1M~SP/>7$o1O9jo+6N)ݐ.J!wG}Ls5`M:9>ן% " +zU4rBX=hn[ϩ[%WzH9:ĽXc; XQm8RzB~v +XM+uVXا;x`b&+𚞓pK#ͻ|ռ!V쯘s|S7r y閎'\ËF XS~4{j%H- 7&MKJ1Cbvƒ&bmp`qltUdAQB 69 ty^Ҟy0N +=Y CB*5XD_i`)܃Id'Σ + +M]by(\xS݆+xRG:=cFmí`J3N+W XɁe\ZJ^&=hAd]SgwF`"`YɭT-AO7}ùQgpOAJ]:|Ѡ>ɁHFG>8֖ioM̨> ^ZtUܜvvt;J1hPܓ@zbXɠѺo«] 5aP}>7Px B3M9RCFjbkPSŶӱy۝qT:RR +f'6$8߭%߉+t-}Fg<-]H3gƭ3g#z?0a;ÂXѽY{"wj( RZ/ .#H`ػN܃ ?(DD`ÿ|2T'"N񞜝9QSbf@ Syyp\՘n=3MbZTfbZi釓dXc,URl-1fLi}[/oöԷŴ) W5:bzQdK_۶kYb[Mݵ约uTp;ܡl+KMk㸵5V*2'24sʪkkdd[9tuUh,m,KoKf5f~LquMϔEQlL}5#e^,cV}7 47e8FWZޜSTJ?[qRe;ZM߽ ~tD0g#tݕ^f//e隚f/sv/o@(5-uLs*ޠ` hoY]edjQ (xOUP#,GKop^~a@lqyW7ZCi-n~Sm^MEsckgkGsUeJKK;y#ei4ſC7?P.{ѳIJk;E4yI_ w,{?7I7ý"ΪMJvƢ& +hs*86W +fQu^i˪*+-}s;6E4E0y䖏i»w\PQw]c^t^n^۾A0ucSל-FGFAYL7?}Ý֚H-fWvdiǀ0iL +Ejf]Lr*rzrE,Ki4LNHW5ê:r䅫!9G(!6xEkOq9݌me*Zp2461VS̪؝# ƲJCCY1 ct{+7IqIqe)\ɐ$3YnzNhf,G/N$/=r3RdI~MOr#'rGQMs ɿ;'Y^<#(p{d'{G9/K/&/.9?r)fn￷(C9 7,?ؿGwX`6MsC2$7X$9X$}'G4H;{~4m4%(?8#^neI7h#_"{A/p̤Xb)Nރfap='/~f>""8 |ﲸy{q@ȕy\ K_[.w'Ew,=9 "m 9h~`_9?fo/;HqE/~,E2="E2{${]$("I#??)d.`MN=f=cM,Gp@8Ǟoq"YQ"(7 KqɑhaEL?\@8?\fG`.'{XYͱ99N2wq8IIER,(KPsIeY,җ=̾dI0o҇Q 4'Ar$}9 Hr s8]9.7RL}55V]Q(zɱ2ss9-#;Mr_?d;,.ľ ;Eqb9h"O9(@$GS4Ms9h} 7ͽЛ}oޗ{b!W&@[)ā [t +X$I<ĝ85j`f`L!N7, da }\@WK#6 +jjWjcFAK1Xcv@[7DtUI.<-Ύj1Å|i!,$}c!K-Ec㶬ۙ)d(@ؓBr@ +$Q-xPP#&\xq>ķi mJ +u 1FIIǒ`AHDJ #H ,"QDLC"@Dũ@Oj AQQ)>z RD9D p 4"'yzy:TqaQB-"g#4-Jra3@@L]la8aD%7 pʑ[h\AB΁ +^* +ApJŨKZK4D֧rZqe +,V/ &$PY c,ML0U:L9i$c_-~o@HpfK-Mw +Oe1hs /ȑG8pЃb0N/#!ԓ4LG%XiO0@;1`>R6Qzdqq2]Ut8D +1{,e0T-I.KW ހ0`hp`,ɬT),XHIhi!Z< Aހ֢Akp j O9,txkjg)E P)~.9nӆ wI.C Rΰ`2;UF+@O1 x|ٺZkT0 JgA5 'X=1Zkjh}iJZ7*aB BkMʊ@Ak]Ph]O*+DM @: HhVچФ,hIzIk߃֚sH/kKo@(tZ / p?Dƶ[,PF5cA)mD!@4%zhbnDHVeFkZYz%s*^U-9:SW4zT2@ OWW( l1왹ª뛺6,]V c)OWVQij̦3ǽ-ⷒkL˶ Q,Jpk_e['@_ec蛲*}5:c$SvĪ1^z']sW}epz! ű 6.(T8 &mUͬc56γ4e𶹡fVEk5VY6]+ 30Ue3YNmqN_4pj;)ji USt Eߓ-5tԞf,Za^bV}G)9=2眊ñk)jmEY@k3Ǒ! +"ݙ2eEAx\Nij5=n0*Fej)MekT9VU3V1@m%Fe.MgkKL54*fJe!| d?p9]Ljy'Ir+9Qd%@tsiv,(xWɜ"CV6CPugZ^w9on6ci-2so2yMN%޹N۷],.gpZlr* pUAƬ1 +jua}y6#vMhkǩiScrp11]\ S]sm dh"0@cn<8NMC ̠xy9ml92ܴvոi%6]iHƪl21Њ|ird%F7kjRdC(n]S*n;i X]rlj0+R&*[SH<Øi2[sYBJQ& ۗˏ +Y9($ʤxǀ\ uNBW&h- |&d~#xo@* nY",znD[w)\ a1l8{Ol~S9Bb f30n*ހp&b%f^9>2z1!ظD"k#OfQhS7t^(fkhMr^REJK)SH} XrPb`'a]w2,,=:aiCˉ rG0x@x? +lh\Һ3f +d8ͯZO:"7 (:h5tH-M5IZhӥԑWp@sb0`%pP*V˜-:qIS&*'|3"3"B`Vx`:k3 #%bql/S^ q>]%9TCж$ v؋ +YO_;[Wᡖqo@c<*x;EX}"of`An$K_Z1%b%0D]们}툥& o@HOs*˸y%1[O-κPt*nSMoko늆Y5l699M"#eόEiX{{W[ (WWYtvSxuY g,jm82E++k5 ̱t vcdi̪aN0ZKvC2 뺯뒞,eUZzQ4y7MCXYnpX{s5[[,|]]paqUWZ8{mjZC8w0ڛb5[ tM[h-{\ʪ+-ỲuB q^,n1a涸MC[@Ndo-thi˜@, h9&s c9sY+U/7 H`Ҽ!7 +)ozP΀K`:k]4r ]IMCFCƲx&LEd+3 PrKn$7GV.)lErtFJ" P U@\]zj[ Պ=B7 –nIEz'K" $jSiGD04e/hbeE`JT'RZQށW hʀ7 &bAR O EPOeH-D$09U'w!rb%ƄBSʕ&2U"o~R#?<)"LIZhǚpFG F'j +]$ +E@.2Pfl5 i*:-|lF%ax"[*>zyL4-yR..)<4el'S3NWc8Ź-sjpj+#`l ]6ܛw4 xZp45p,eP41mH $2CT4 Y0J F ^*% E Nu41ىc)OⲼXr;iUhJm5TWp-܌=dЈuɥA'+ސd;AppUW $%0|V*:";Do@آX>7O9-߱#Yet5!CWWL+_"{Ey=5ڰ=ywEqF9ܶ0Ec> +q ߓb{RKm( 'g@-PQ(v59~@s:("k;0+R tN5ܠ˰T\FNor1uRe㰜w9E6gDqg=}me_y r|2Ej8.:[ -pLV ,pk@5(P~XK) %h Js&P +wrnnSٹiB M]xq= G&xux':NN!>\G8Nn8?͹Tyө,F0%+O _Xqa 1.pqYJ(!aAKA\.Ż>E*Kgε΅E'6R& VJ#ݻZN)MP9[2~`L`%v/ `>zADKCwB٧)7[Cn^W7 ۑhfVivʎ*+9Fu]]PHMg3'0OGx6[!`+ժxx*\ޗ++P]b8mN]+wV, ?=C,baB]Sצ4m +u>?3NQ'4I1%&Վstt{&=&;ʨ3~Nu:|I')xdwlp' po@%qIO O}S%KjXFiHLf֙ĒOL8dNU@1Z#2m +\X6Vpqa+daj_ +Bo@`0 +Q/ +pr$R4|{^Rh Ap2@C)6][uP+D!^fq c]k!JcT +-k#$N|z4{5nQЧ٫MTC. !~%?8ɧM7[CB&#E +R0PZ:V?%XD>[(8VI?, @zA<{ؔFE +Jtj32x%v`K9r=DEA`@>6)==׀@LPSTgISf;fj%Zc>H$|(w 0PaE,: LJZ}wc'(X]aSŽ0/ $:qXSZ̎PCpYSՒM-Ǣk1W yPܗC8 ((V88_=YRC04 &͇(.F78t]I A0XceJ"!$.I#Yz +qG˃Eրvļ]Rހ:1ԣi2u  6&&+ 4Q' z%Bm%3"AU%N0-!aD\* `mK8i օ@mB}maS}Xwڴ-9 nA 9B%(mh_YeoG-z^a`%F!It!Xlac/{ulcEPɩZހ/MQνn%zBfW7NjHFx&DqAJ:nDAySדښd3DJ (yB/ 77 Bq/ GFzJPqIlw-maD fp, t蹠B## cXң{`8V4>"ıX 8!bdQH!8Aw6FHJI`RBg> 5Z>9@(vyK/$x :(*×K3 \Qvǁ !;p+{TQ3\P%ߦ08*aMg` +w%rp 8y13I|w gZB\h {N3O>]kw*r#Hn˲xH + Q]t [R8'Jn[)$tO""Q6sXWCE\a;Wt=-mKHފek%8EilBk -Bҡ]ܰDqIzJ;5H#0ocMDԸy:`4DelClT!Ƃ<uOD6ª!ƽ<&vf E#`GfX"9ė /` r0H% PޏMM6 ?Q䞈 +WQ)$CLvj6!Vܞa_B#ifbo¡?!VH%X؅\Zm!,f ٖx@˜W)NMYL.5L&R ))~/U0*8L'Pk1Ѯ2JSn EUB/a_(r%;Z|j;HiTY1+2+єEPMˆZ!2i~Ɉu`wjV`ٺV3FM625m"XS6;3,. DfȚֲN5|KqԧP79VT0R܋xPu8j2R|΄.OFe7\i⚷=:*J֑S([.A,;p[ ڒ +f_Ђ1xO3'h\ϩ~njy%#)x$V  +QOEl5HR*wH-H*, 0!iAmta09 K@K|~giGE NSx rjl`5cvC&^x3|V8zq,(+֟yӁOX^_(B ^ Jm,O Yx&Ccnc +w@~oC mvM>_&Tw{LQ@$e"KLSuUKE%k,r^ [(((Po2nH +$wwͬLc:H}|gS2` +S*La1`PƼ.#%xx9ߴC + +d Zh%m ұza#nuO ;Aеn(>̙Ǣo S*jZGpq=Gb-θQ TwTbQ +:1c|<U(cv 9*~DS<iG 3,GL +(bj\$NRw604FSn8cx͋- +཰r`VLgBM| ++NF+ QF +AU(ƒs~ Se,UtG-IK`rew>8Ց#gD"y9*n@7 o* +YgI2`&VANeMӷ]RYt\5˿`Oii;.ꩯh *B8n$>ƨ98:h(^7WI.Ҋ(j]p+wâ!dH*,nW$opQ݂E6Ww~JϹ… EoU\6ٔ͑9e^/?KJK!-B ӁiD0ޚ.cA]P6-o^hHwL3t\H u4Ỳ,Q?sk 4RǓ#!Csofm0t=sx99B츨t& qg|Skw~s5y;s6,~Y{V^t^(6~/΀'өYC]iW2@ohi`;!Lخndp9 ѱdPRTٜn݅c[:hL]Ƣ(va٘~) =wnMb)=|1R|)q%D9Giz/ 4S ]J' B^![!(!/ԛU$ŠtP/4+4qէp 4/tQ6nmFP3A#-U"xWPSj9 QPR|26cy!D׉xru2--|7Nʒ\ˑc"@x-qE'[~(d䶦6pʖ6d,7*R#8N1±-A&%CKXfkԛ䣊 ݱ<=.y/DvbpXhW?$>f~ Ml3瞷PXg'lQ>y4LZY$37%ۑr8[2m_/~H"&e7tÚF h/ NL-Ŋ &'PFi# tz5K8U]84cX/%9'<#0Oܮ?Yw]v!}ˮ<(ü2NVQ,U2"40hՋLQț5/ke44qyXǨ#+Ιb=|٣[ 18V 4͍m{DGi S)yY:tt}VtǸRpy3Թ[2PbV5"AC4,//^s˟6Ĕ>TOaf҆X8$ToFIT4PMkf1Es(ކ#k'vZBy'/@r~5dB猢> pQ&fvXCy}ݤrn` 8 A$ff;Bu,E!{~:8͖s%BN>{1u+)7{hg!dN7@>Δ2h^½u0,I +(^c?>3h3S`,JYa >u~m9w"2W'qo6 Ɂe.[HbVFl ^:" ́64~'޶Ugީ +\_ n~1Ӕ^CV 'P.@.Z.ck>cm +! 7(t,A)nþdBrd8S<s/6[̱ڋA3M2iy2ST!HmoDA%p,ɭ p Py1bP `C<6kzFzQ {9(Z=ߡ:8b8p{8m!%')bF?H^6Op|Afe͙t fSgU %`iT$Yg;d y?7!Ds[&ֲlot֞T?TSv `<,r WasQ\j G榝w[s_Bc#8ƐmYc hx|W6I7_YAŨ6@`p?|NwȜ:sCoTv1魹ޞ Ej,qg"Z 麫 +Y$CG +\[>C88؛*1ˠwcgM-RhQaĄ7xfw-RPR@6utMQy ہ[QDխ6sh u3@ :lB^$iT'ȿ@\F0ޙ-0Lkp`T+  yf#270ODdY$^ͨ$ɥ-UmVպ͝9h]6GΡ/b9onE~eK ` ^jU^SwoeERu5:g2q%SLd +-j)BE%5 ˿AμϵO%x([.GKjɼq:Ò3iΈԮ-ڬ,h7pVOȒY$lzszϫ,A&뉮q 4б7ʷKс%d}lO*/+9<%ʩl !J料 \||x7Id}{‡NB]5NȈqZی9@cʍT2r'@y.T eÉ"ҁJ9H+Swm3[ASwV="[}Z&5a]6-)Y|R D tuWRWM룓S֙r:_g#ʲ\IEc6ey!@>]!!i.r"j:4'' oƋnA9E{#N"DђѡJmZ;RqEcB4`p +i h.sÂs'Z(kIm&܍PԐZ-*!aΆ&"u#X6ļrԗ)R,eE .cJh@YnMlM"Xkq]pak}'<$%*U,/&F $'AN,)BIl$ +!>txOAhfN INUCvD7O^>pO{f4@ ߺHvQ*6Y Uj*}[]]?eFK:I볭M&JV!l͍{ޢClp@f5 C7VsJ +5  ԕK }x[>TJs|q}#n*dye +tt a' Al ~qbmy>w U2[HMMpq9u[ǣ}KxI*ѩ3>if])a_0>iQw~4-06n 6]hNY-;D%f= +$j3ԏk<+D.}%aqkЂDn5pHlQ/b^<8#,${gJAq~<-!Q6>M!> hзj $&t,?cz\v7Zj;{n[,F Wv> +$$:q!> 3AhQֈzzSJ Z擩,L8l8ﴸu@=Mt?,+3FcZ\]!HX Yt2X";yf3uCl&uT t p>~:qEf sȻZUбF٤?Rg-jbP,Vzq *2O&"[( Z%|HX۩kxU@>hLN梎co~`s%xr\ULuts+q%3)*Kg2V5dQ@({CGEGp7 e#Jy3}Uh͎:蘯;(~lɑ\Oev"_ RO au%ehsB'R{NtBd`9Z弘l70*=&' %lS^ߔ_b 5:[j {+WmDW]笔 +gA32 xbYwN̫6^;ufk*uf R)VκԒM /ZeVo )P]Q8HwMqRk&Oۧ- S0$>\DhGjdHvA^)~*M)xR>2ɕь5'6.'yP=2'xYA~x^aXɹRّ~DZVt0/T2Fwp^ri{k渡XN6$ХUn6 J+r9v e=W!^`׫)dM>*9z1?0'aj?A;>cb6 7wEL@o(Ԍ!9זNƮ_ ! PCē/[2"M?E MY8\L˳ź̬//rStRew_ou f{S(x F/R.#T,P"0U⇳UZ^HMbEKL^:!?a1r0V]`q bl] vyvb&BgmqEc8g9I)MHi +hˍҗSqpJ<38SݙhqD>j nnmY0aY94~ԐUOKS}'SIV  +۳Fiqۘ:Cgn)::ϭH/qڇr`$*(:ǘk1rO^Og0z)jj?`#fŢL IHSyqPna4yAHMm.ɗ~]TvrtPt睫 &iQFcӠg3z) q4*ӫq%鲿i{Q}%Z~Yti{y qh*/囮`qVh/Q: 4 Y Ҽi7(&""XAO6MA.zh ftEѳ>>=. %e/W'C*)glL 6ȼ* H*5`hO0}Wԭo7JOǩ~K\Е!EO ʛU(]<ۥ"Op) LK_N$~_n򟋣x8ޕ~dqON |nqL֦6=>Qs3!ϷQ=5n0 2C(!nx?ovb …&jcg3\wb ޕ1/86o4Vuur3[AE$]G[`xu M)"Lle.ruQh-K/. px&Te-:T"^"fY착ǩ.4Vs㍶ ZSbE/oXԚ&Ik>m?kxPa7錕 3Y"J!|NH5)FnzDzgD.MV#)I -:jkQ +&22JRH<.[̙6~('ڍMҶdP[5 XfΡ5I]7MYn3ݍ|zsRJn)WX=jԭąsnv26+}}Q: +C.5; nrԊ:h#M,I#էQGdrf<I P싗[KDsTs ]4(#@g`WwF/)&u$GFcY67Sz8볢9hEt\LeoH$ =ߩ3p^aE. Қa 6F` +1Ph({x`|RQ^'QL!c%]eݡCAaur4x6K;OL5e//^5N0 .QPJ5} nA #x]%v<W_2/qB%VdaM[~DWjV(&(kĝ5R5q &%$_k| J,IoYS_B"~ڧ??4ju遆P=ÒzW-FOV~X3*HLʀ ʍ%h{b?V UL]<2ZOQr9ЭN#^F1 +&>y@*aE뛟 Y&y(Bi=ILDצ!Q@eUDtXW6-O붘ݕ>G8; qDE#bhaF̽#I t%<\]*1^N|) E\d6 pЙz.f֌Yec ^FQ0-2O]mR!ijĩ~;N!7Փ1Xѫ6:;A#նD­B'roиra + +_VP]0 LtlBR6E/dYi~1V:7/2mldm>u'̫dlG)+A^1_eZ(]K^۸.+b:tGk`,uv]a|фF!~1ÂWqIx.i1Q[ +Ǽ$=iC0◲v(TQ5uGtva{'IJYJ6nU= |wcõVIWD<&g/U쟜@ȎM9We?,)q2m$Ad{YKa߉`B;`#a&_->ι*ʻ\7GFq-_Y2άdžC*>|¾Vn K ~@ :bC5#NX:%Qkl7Sp7nc(:6ہ֟ oCVO@TkPӦ\ + hts zUb*+ƈ),3Knt-e *lYDl!: gs >1 Rl㒟4唘=]yDb*lf0ag.?5Z LT_U~@H(SG˙#1 aQɧ,d +Gc_ +r4fcC1NiDy9>s-)Er[<: r'&z+cd.1vQ%mr_<Ŷ衠W;?jm,3D.q^$F,+ϭ;?KsdvOr{g~_,O6d#P?E`eόDjxUatQJdY +M<#4! +endstream endobj 15 0 obj <>stream +19/&wG/!۾فe*m::*X"g Z 3C^6ao܄?nÍݚr[2eG|IAOc{ChP3;IuDJo>Lg5~I Vˏt,kp y'P"1 9מҒj6?žP + (7?XJAP#"niqcȷuQT7FbTD.KFy ÿ +/(>k`!qR|'͝G + / +3B&z\Z9"s@BqفKیE:{FS D5RJ(p!2NY%?lwJB|BEЕ!sH:þ֊&J_ٴ+We$V)f)ՁS'n[V +="W%`ƢŮ=%MwRFvwF=GFSS ]@L(NKy\/kYL}+V/9fod;;gp+9R_([  +Z,!BK@9jx$C"M0νehim2 s]̧Άyh~K@yT6`IDyUrXb^R^@ Qeâ*K +gώ%ҩ|mL&X&LFL%ra) M`3,m=BIi,`WZ}SfHt*,zXE,e931L\Q9iC+{8ai_:kD4>G2fpAykL2m ȭV<` L/ˁ8Odɭ$έ0QJTOᏖg5~~wCS-`zԊ΁P9ËWZ6醭3G #*"cZ*⇏`,81iGlaC"iq¤ZKP,#]pc*"p[3"q[ С-RDkiN|;TTxZl'f,ly +ѶK +ͨ"RYTp6P-i~OIdt#Y(lHOuF@Jd.3WqC J,G?>"Ū)Hp):\ϒNA$=A]86Ú`ֆ KfxUtm(f-'\Oˊ lb44Wd"0o=# K #0ǐ 'j}ꌄ4-X +._.VA+!jv¨TKq#"d!:W} ix+dK S5E@T'dLH8!{yo˜8C:IM7]{LL`*u +r l{z'=^]&̨оϜ+vF%mPvd]xfiX%WI[_`%"栓f~>!0{"roY$D^afI!C_IuaDWM"oA L~J3J)i9i%0B._60h[;l.@К\w-8lek]ntFHrot}M0^߶%1Ok4~]'?x: -~FHKn"ڲR-/{a^# ]+'^C']s@F|XxD4P w~k''L*mHsBAVpPԿaHDR!9O`!FD + m᳉A)PQ{Ju>`-D]W}}{w:TnvHߥg\|~(v̰͌| +cscdBA6!r1g)nJ3aʈeciڰJlX<~DPoE{ŠJI:Hz9Q;Tf͋3EAtP ྒv#r1%u=5CXsF/ A@EN>T߂eǩrr7;U6/蔢ŷfsUЃ.Ƙ&5&ÙM<-Wٺ*ܢiu7vDţJixڌTQ=  +&5rGؾ zurŁC.Vr?}u1餓Qq-rnSԀ# Ͻ)}x)Zw(#B|tH5( i+v Z)M_W\km2 !ї%wd0A + fW+P"͌6fi;g'6~S:S|& ɸq[v'g.^71něr)id.AI \z#CF@ù=.}ئ>XJ.@ǦVGۉ%:d>).6CVJvK5;Xհ pJSgm(),KNȼ_r3{c?ܵmL23yp-tc>Q$с.x5M,bs7J~S^r39I  Pʉj7=U!#'~i7_l?2mӘ54_` MVi +G%9k j>#pOW55= ,=JvTLE V##ZOr,}i[nfѳGWk9 Պ4`UaJJZRt<TOĿ;Hd920"pA\r:7$4ajV6ǒY20¶j9;Qꈸ2˸tyw/:a$,Tt'-Q{&=^-X@GNsl|>4[bYJBk -$jPYl/Z#_jphw8K| PEKσMB`9%rn=Rstn5^)Z9۟ +wD@rndDRXGo EkbOAb8U˛ dp/KqEÏkӧ&/3z?RiЪE#&ٛRA+عrXvGoɠRZEtv\t[ň[PvbZC|ZfbFpMnOTݑTա<-iꡈNʛyoME VuTrz+`n&%<.;T>QPJh2OSCš[NeE1)re,3N5A28"VX@} ư @XHG=?F+X +&PDOL)WZ78A27Q_0?h"Ejf6u[eq^O@=FwbknƋ,?kjK^~aXXFGK@\z{ss<}U캌 ʏrbQ;%)@4r@'tJP-;{OUrz4|Si`g-c&L@ b91)Y9ThsW*,&BN;O<ʆ$HCzkKQ?0=těG;cw5?X$ :~RPrn aőalAEԜp`hKf h6[)N/\RZHv#om~45{r9( +PBӆ)KwDozD\=ޢwD$Α +m`*; eRx:JZ_8º/ߊ~Z^ZnڼAn/*/ 7ROd g{J*f,$cn|xĖ(މUX*X}U\ffj2f@mBɺS!(l*>o*V,q k!JBF{&~39P@`a!PB!gdXy(aQޜt {xq@rPIq3Po86mhHA.qH$kʎebZm\N T` sUN!s^"SqݣFF*myw8qmfRP- Q 48 Dȱ3m ,!'=(%Bt&U;hf9iB*މQ-QY,VV,0JBجhEadxCM=)3QQАƍCADnB-N@&)3œ(N fk@9NGAYHo&4Dl6p7fI!_RA6Ti.7cA89BeAКOヲX,1ޘ2QrDf+|'oB E6.BIȦQ`*Sa_ +&VF۸]sBh۸Yͫmܸ(U0oB+XĤ6N6RH0%Jf !fPEL:o@L&aQ.v (+Cr{Q?jgv,+ u(e);d +Zu cub`'ЀL> +vYDՇibB)QBIlb> ̄ψ*2:RASp/m?Ȁ؝*P*A\HHI0e O1615q)j&j)̍'N˻l4G pI0OR*0Qj630P +<N*T +p^*{0df6nA$B){d&.%-R`e&h_ڸTHHС3[1Pid'Bg00yx .2TH*-\T}q #]!aZ&`U$R: 2ueuv4t/c8c@k32iygAPXxo2J0RLxiB0!a`c!jE<T۸ {_g}U8D]+HA 11P"P ) ]&lI.`;\efB$*#lj]X9KHȘSP5q-r5"q3^K <+@;@$1"J&? DQG&Sx}MkFL&;xPrZ Erppƃ%"pw\Y3+L$X xg 0Z oW`@$6Μ<{| ^'K>~#@ˉ+/s +FH){W؜'!2OMBC~5 Qiu/P|T +D"N +IaYPX #'FxHgayl!Y(ef+_ UrJD@B _(@ +'OT1R!'TK(崄JPTl+HqDgƉQpZ!@LBV32G@Rz<( $`8i!4ֶV}12I`C%!tIxS&H WQ012 $R7Y1$AP14#-_C|6Uː*` I)1 a7H:Qu ;Z#/'S,zFNT#e! m,٘`MfY=n&! -;{6g8gCATb?ke`/QƁqqYV x7ڎÄBolNDYd28pG:ދ{E]Ԇ6:HP?Z/).#jtqD8wp G (5dhTR m&JD"ơBÂE&ÌF[vck;j딝 +RqLDE"*cH  ]R!6Kc?fvWXFMɨ0VS=LBGA 4`pi&L4^7 cvYM` +`ԅLq0dFf €GY&2$@tpY>' ƩH3n)\m?7oDFG#̋ᲸZQMbas_u +.}-)7J@LKCbX.A+H~%ayXފO=wtYFmap/R!?|@xH䔩 R㱌0l,9BR؃(%-Q1^0=-/+>cHC~لc4 +t̎iH*̘.&BHVDu4j^<.Aa@$BtYBnz + I(J@d% hu0@~b钟XNwtFSJ R@uR).DMBQ BI0ecE$>Yc|woƄK:@;YG2 I""bmqKmq7fF@*CeT0RSg!B$G_&3:L~KW\`QeJ^5H@Rɘ4`4h:RŽD؂gF<`'28"](aW‘Qf%h 4{L48FѨzˌR/CPz .Ma9PxH`Qx'Vw@𘄌-a&.jkz/kks:o-{ÅZ[EzQ,vqv7Z7LgOqԊ+~8c\7}7Op+T[/zmc֚u"7N/ޔ}+~z[,:mƗ]_cmfwŻmo5gzE }flo.okVgQǟū/E쪿 _kz[i^>"u$/H@tm' +h& <EFØ=#|G wXyzWRl-cUrؗ\2e $1"rb, R9,^ djL,hhUx0#"MЙ *VB@-e 03ȅ=FHi4I2)ȇXQp0QA#M rSWB(D +lm<0섊6EJ!E#%* u)<ldRQyF6M<ꁆ钖T(ftm(J-Xh}̋9i6>2:q@lb2"+HgXz%Q,!8R 90R/Z߈@fZPaBj#ـy00:y@JBL0cY +x e@]4F0@tC,,qaIVH JThDaf赐4:xЦh&SPX +9)bɆH܈o.ffH0 8ٝ*vuF*t$Z4IP(}B8))prp/ R,=Pڀg[K< +IȄ$۸Ϫq,Aeo1s53AFH@g#ҙCI@K۸۸P6B3Z+ƍ$REeG (J}0hKӛ\1qz6.Sn!ay&& *MLP`E801AC?)gʎ*7g.,]jfFF8;(*/1gS/i$LMo,4J@~1xm*" LbX`JXEb+Z{RR\)YBvXwb[_|3xjg|^[]׍5⚳ƹJmyni_ʹ*nqx/]޼H3B]9SMj\oՋk5jO);q/g;W8?v|+WTocJUi/檭 fRE{yk\Y/roivo/4;wW)_1cfzk\oMiv}i,V[V͗ޫf|wzug{޵ps~~1ޏt/lq8zvq + _8|5{m: ]zt.f>"uUn뜩cNgѫƮ;pquu1o-,pc{fzӚ{{[yWL^T\9ETzŭ),(ۺ}~JvVT~>^yؾu.t:W}녦׵~}\0_9U*ntۋ+.ֽۿz Tt}ÅV[\i]|]V7w㜫h3Kzu/tco꽠xgVVwvR֮).(^޺L{VK7*~i_|ԊKY;pqŝ~Mu[,nWZ}W׿\7NE17\썯u{5~L~wz߫ŕϿL?oji; ]7_VBk7ݸ^5/o?N-~M[{w{TV}]{[R׻.Zةaچŷ ]p+u WM]_JRx W*4lョ5W"^ YٟZ|k:zx]iqxcQ׭w/ںgn6ߌhoܥR}ZݙjKEƩ[{޶޽EWzhMmZkiմ]3/USOo~zݢMbhziv7N/jkWqϷ7NUojڬ7zӍ<7N~7n3x:tߧ{U]lW1k7Nڼ1Z{[YqjiTXbN)343S00 I%4iP(F26"@ +@ +b uZAwq"Bfn%)[⡛OHWͬ_Mkݍ{!;5QKEK+m NQj if ou Rc垧n-fDk:?]!,RnN!wiqulTx&(G޶=Ta>Ru!xB7{GaJivܝE#1nD#9m{n|B☥s'ˏ=Ba,pTnq}O➍k䜂-.C?::IU]7 +FZ8bU h B>a?;}Tqᅰ%lXfj֪EDr!|<Ikz`UmuwvKKb6$.iyBʊܟ V;J)Hzsqp>j:ҽ~$V+r=jdUWjM`'^r A_Lڢ'_rs> o{l۩M li 4}RL@1[- i#My+on3VR|r6!*anF7#o^:jQzㅨĚ%Is k?.[邢J!xH# cjRvVAnalO̓ ݓ,hSxlE* 5(Ձr@8`7/ &;KFG9g:w&76F}}Cs?Gbt*Sa@9> FyMGL[=MiqC3$kR^?K&jLB 0URlM_{o1ɪ]K<ߢ($64WD,qN`2ݵD!c`~nQ"g*k!ţJ]y)ƪ gN<ۑm)Sdlh+R髐؀ӡd;;F6^dr^.]%0lLIS53 5K7UGǁK eT@B9RNA x\,13^G#_SI Gƻ/d/[&E *k\T!auu>H}j;A5oL7BS5֤_{xۤ%a#ѪveȄo# +pCpjDb@[Nw~륉?f ;JF3;G(>!8SY}oMkt6_v݊e"㽐#2l{}hLan|;.}moQ $|V464@6U4}3@zPAaf=Y䃣Q*h¸zVO[ZB ,`{C_ }+z{*PUz6*5 +DF2oהQP2ʃIk@2]=*K$ۿL;@&'H<@)_~Ne WueɿH+0:E!C췌 `x `q(_(u)5ڠ@%22{^^9gYF08#Im@q2Uо<X;+(>}ݦ cΘȠ^4O+bbFʎ5f((9Q!S4 b`sBPfFo()r\8BQ'چܷ;0 fD*k16`x_Ug?2VK!WS0V ]4FwbH. )F>6%bWSj cp*uX׎ g<qem%*=&s RG`& + +?`t +Y^,5+LԖf"Z%H}e(Τo +4 +ƮG=2вWkZpң:cTs8>_HuV' +2UI$J}8T4&3 ޴߉lPꚭz<J#9u tfiFMei9zIDP_0$Od&wߗACIW ܡЉX+owJ1r҄@ g`cM)U:7 +H. /S-FTuq/]!Uۗ|n8 +K OR{%(2K'Ԗ!j Ѯv" +4ݕÄ^Fu,I'Sԅ4ZQY䮭[l@awHۣRcЗZ;0#RΦv|z2q1b 1%5ŌTYu$0dI6pRm羇0THzͫ=?Aa {aut*IDՎ"m/ww\ӑ̜ (W(3`MOؐNhV"ޘ%24碜6%شS4&|RM<DwI~3EP'~E!"Mm`E<9I,A +xS3H'n\kqʦo;^% 5wƑ-9S=Ƭ(!S_8/>;"u'^rb͊7@3t)'{7VI 1oQ!$K9M +"!IZZՁg)nj]7طKUw F ߃dQv ^P_o$^\͚#˽ˆos:P1w n|`C= +$FⳢUQ4 +{؝l|2N"#16! +jAnz#. k/YTm$h˾U+*27Ew*Tmd%S-БXjgn΄!c'GTj֢2*2K01p TvU*5 -H| jdVKs_f]lcND#aiBERw]X7 KqluHvXk=.~pLDl FIGװHGj&iNW V\ +пTleFc>N͔ +6]S~uELjWA +lR)Cy'U5|WPId;"zB&#9x"L `BXsnR@tA:77k?hZ\l=:=?%}D=yI>ϊۨeΕcC,[MܾBr=vl3I\p^~UU^OPhD=i +qa57/s?#^Y6epnSxd *258rRߵ-ENH)j L T+ʅ_2eU8pTiVy3psUۥvDglV'X#Ws6")mg.2 WVKA2o$TH ]aBxԗ0peMPa񦌕)_< hhD }K]8ئLov\l޺R*TXp}ywLbA EciUb:u0P>fnLXMWi)SkBd{|" =j}ؤГtZ TM}Gg +[Wb?NQ5cå1.oE4H0|!įI쩤wqD s0yr@~:ns+oF}%,/3HoeWnRr'{qҵ&5ֱBf̱8i6]b=}੖! +r;^T\Ғ]F.rZٲ +||EY$|)EA;fna7퐓7 [ ØŢoXb 7f*蟒mzlcwFѴfZfeˊf$)v6,ɼVlNeY'ijly kYv$_j+ ,kI=VwYExnYSԌ2P, 7ɸq@dްT3 6 ǵ3XF^+ p!v), (pF_Is %iHʫyfOp S?*E>j2$gj>ɷ%Xa4&?7Mx JrqLNTx=:IlUF] zj+ C/:l^+S;e()ͨsj}(9V*ƞWPvpŔG[4y7E 訔ڈm {cr!3bXX/R.T|6,?W?1*˒C!\0zy +<FNN|_he;#־ٿ`{]h(QY~b=00tJ*!?^+k "6'*W,¨S'DC| +*Aj͍ ]az_^ #)zUaf!p:Q4JRIL31bt/[%<dLk1&V"ĸWrULKS:kOg2&BAtF-p7SxWO*lu!62Xro$u@O$iL:`|E084./~PkS$Dž%6!w]%%nxlu9Q7'meq5Vkеpxg8@SiIU/Z+ZywPJѐ4 +cYw{&ID"5W$&Z5&xmJ)3~b2̲B}j[Y\W";!194*_ hckk^q&rZϸ)A02L.g pOAmuT'Rn Ub|HN8Jl7bvH! E|@ُ6JG):>.*6vE3o-}F0ӒdXнF>ڻй y:x D ȻZgJʗ5)Dwiv]GFwh_Btv_'ډN<13p@͞p-0;ha=ӣG\n(S .-@7% Oݵit_:0NB܌t׃b5&)61CX._ݕп*`E.d6z)"MPWR\Kd fgɍY ޅLI4V$suJ৓qw;IBe5SpP7HP@I?ӣ&ˈ 8B7}>\4zh. +n+}!{pCP"nFEI軅 <1($RMUH`-LH("SSBEf$iQ=80-^l; +f$uwnuZ=UxwqI,z5%4[`SeK[]+}C3ajF=ojNjӝA;ЗBn6,Yb^bx6t,41Z6DcÖW7ĺ_/@G ˥Ϋҝ`y{x5#}[ MHB"W H~1Ӭ;B62Tw!a 5$s?Իj(oi297>KԘn2Gݨ!!\tY:bl ɷM&or(0l + ͌N3,s"i?̰d ⳤl:؆4Sa4[P1V[q!@UVJ$mX8ykBjV6y_0&($0-pk҂Ɇ/U|+zo ' jia.Bs)кez-U~\HjC+DB+NmzBAڳ ۂ4Y l um0ıejo.lT*GEѪ91lDHG",(is_v Z}&G'`] +؀KoJKқJ +Rw%ɽ n.; V_٤ Qm{w0190^,?zJn & I|m/ 7*Ye ?^ +V@O!.޼G`@ 7Ԉ&uܼ(4hDSȒ_z Pg6%tᰫJeB7GɅ/rY+Eĵ9{j"J| +4pn8\ x5QpgTh* ЃP5Ǖ edPR&S+vuܻ1o)}n$<_j/qMp7eMҾ+j%SWۄΕ7#y-60> hDa Dq֚Aar'"FRu +)q䦣PmwW=]5[Dx74ͪ ېM4j@sFuj*ENK?0Bc`]ܛ %3zWGH} Af~naQ2f0/-jkNptlP;`O~B%(1E0)`P\*`pz]?0W%i7(279!ԫ2&9t>Izg 9^`1UFfV˶Amo(Y]BiXpV8ȄIg"7Ҥ+H1)?^v:&fi¥ԁt48FI[\a*~2hRaozPiD# +tOAl+/ OɎeI%.:h^e4sf B]D\w3!!Əz) +tUpJ~fmor=*Cx]췒VR#R:*TC9;7%vpfji@&sϐ)NXs/VYk[yCIr#㒻d\.CF]b2A/ q[2#=r,)D/HRlׂ.8|DvK~_o%N~57tEdcFM6H6=+,{( +r/D;fW[;l ,XxfMLj +endstream endobj 30 0 obj [/Indexed/DeviceRGB 255 31 0 R] endobj 31 0 obj <>stream +8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 +b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` +E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn +6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( +l[$6Nn+Z_Nq0]s7hs]`XX$6Ra!<<'!!!*'!!rrmPX()~> +endstream endobj 24 0 obj <> endobj 32 0 obj [/View/Design] endobj 33 0 obj <>>> endobj 28 0 obj <> endobj 27 0 obj [/ICCBased 34 0 R] endobj 34 0 obj <>stream +HyTSwoɞc [5laQIBHADED2mtFOE.c}08׎8GNg9w߽'0 ֠Jb  + 2y.-;!KZ ^i"L0- @8(r;q7Ly&Qq4j|9 +V)gB0iW8#8wթ8_٥ʨQQj@&A)/g>'Kt;\ ӥ$պFZUn(4T%)뫔0C&Zi8bxEB;Pӓ̹A om?W= +x-[0}y)7ta>jT7@tܛ`q2ʀ&6ZLĄ?_yxg)˔zçLU*uSkSeO4?׸c. R ߁-25 S>ӣVd`rn~Y&+`;A4 A9=-tl`;~p Gp| [`L`< "A YA+Cb(R,*T2B- +ꇆnQt}MA0alSx k&^>0|>_',G!"F$H:R!zFQd?r 9\A&G rQ hE]a4zBgE#H *B=0HIpp0MxJ$D1D, VĭKĻYdE"EI2EBGt4MzNr!YK ?%_&#(0J:EAiQ(()ӔWT6U@P+!~mD eԴ!hӦh/']B/ҏӿ?a0nhF!X8܌kc&5S6lIa2cKMA!E#ƒdV(kel }}Cq9 +N')].uJr + wG xR^[oƜchg`>b$*~ :Eb~,m,-ݖ,Y¬*6X[ݱF=3뭷Y~dó ti zf6~`{v.Ng#{}}jc1X6fm;'_9 r:8q:˜O:ϸ8uJqnv=MmR 4 +n3ܣkGݯz=[==<=GTB(/S,]6*-W:#7*e^YDY}UjAyT`#D="b{ų+ʯ:!kJ4Gmt}uC%K7YVfFY .=b?SƕƩȺy چ k5%4m7lqlioZlG+Zz͹mzy]?uuw|"űNwW&e֥ﺱ*|j5kyݭǯg^ykEklD_p߶7Dmo꿻1ml{Mś nLl<9O[$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! +zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Km +endstream endobj 25 0 obj [24 0 R] endobj 35 0 obj <> endobj xref +0 36 +0000000004 65535 f +0000000016 00000 n +0000000147 00000 n +0000013733 00000 n +0000000000 00000 f +0000013784 00000 n +0000000000 00000 f +0000000000 00000 f +0000016323 00000 n +0000016395 00000 n +0000016634 00000 n +0000018275 00000 n +0000083864 00000 n +0000149453 00000 n +0000215042 00000 n +0000280631 00000 n +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000304029 00000 n +0000307022 00000 n +0000014175 00000 n +0000304329 00000 n +0000304216 00000 n +0000015941 00000 n +0000303454 00000 n +0000303502 00000 n +0000304100 00000 n +0000304131 00000 n +0000304364 00000 n +0000307047 00000 n +trailer +<<8E9FFD9F61D2424E89A75D8D75A99DA7>]>> +startxref +307229 +%%EOF diff --git a/data/tr2/logo.png b/data/tr2/logo.png deleted file mode 100644 index cc49cc56a..000000000 Binary files a/data/tr2/logo.png and /dev/null differ diff --git a/data/tr2/ship/cfg/TR2X_gameflow.json5 b/data/tr2/ship/cfg/TR2X_gameflow.json5 index 2649e668d..9bcb0cb42 100644 --- a/data/tr2/ship/cfg/TR2X_gameflow.json5 +++ b/data/tr2/ship/cfg/TR2X_gameflow.json5 @@ -2,9 +2,8 @@ // NOTE: bad changes to this file may result in crashes. // Lines starting with double slashes are comments and are ignored. - "main_menu_picture": "data/images/title_eu.webp", + "main_menu_picture": "data/title.pcx", "savegame_fmt_legacy": "savegame.%d", - "savegame_fmt_bson": "save_tr2_%02d.dat", "cmd_init": {"action": "exit_to_title"}, "cmd_title": {"action": "noop"}, @@ -28,7 +27,7 @@ "path": "data/title.tr2", "music_track": 64, "sequence": [ - {"type": "display_picture", "path": "data/images/legal_eu.webp", "legal": true}, + {"type": "display_picture", "path": "data/legal.pcx"}, {"type": "play_fmv", "fmv_id": 0}, {"type": "play_fmv", "fmv_id": 1}, {"type": "exit_to_title"}, @@ -47,6 +46,7 @@ "path": "data/assault.tr2", "music_track": -1, "sequence": [ + {"type": "set_secret_count", "count": 0}, {"type": "loop_game"}, {"type": "level_stats"}, ], @@ -85,7 +85,6 @@ {"type": "level_complete"}, ], "injections": [ - "data/injections/boat_bits.bin", "data/injections/common_pickup_meshes.bin", ], }, @@ -217,7 +216,6 @@ ], "injections": [ "data/injections/living_deck_goon_sfx.bin", - "data/injections/living_fd.bin", "data/injections/living_pickup_meshes.bin", "data/injections/seaweed_collision.bin", ], @@ -235,7 +233,6 @@ {"type": "level_complete"}, ], "injections": [ - "data/injections/deck_fd.bin", "data/injections/deck_itemrots.bin", "data/injections/deck_pickup_meshes.bin", "data/injections/living_deck_goon_sfx.bin", @@ -312,7 +309,6 @@ ], "injections": [ "data/injections/common_pickup_meshes.bin", - "data/injections/guardian_death_commands.bin", "data/injections/palace_fd.bin", "data/injections/palace_itemrots.bin", ], @@ -362,6 +358,7 @@ "path": "data/xian.tr2", "music_track": 59, "sequence": [ + {"type": "set_secret_count", "count": 0}, {"type": "loop_game"}, {"type": "play_music", "music_track": 41}, {"type": "level_stats"}, @@ -378,6 +375,7 @@ "path": "data/house.tr2", "music_track": -1, "sequence": [ + {"type": "set_secret_count", "count": 0}, {"type": "give_item", "object_id": "key_1"}, {"type": "set_lara_start_anim", "anim": 9}, {"type": "remove_weapons"}, @@ -387,18 +385,17 @@ {"type": "loop_game"}, {"type": "play_music", "music_track": 52}, {"type": "level_complete"}, - {"type": "display_picture", "path": "data/images/credit01.webp", "display_time": 15, "fade_in_time": 0.5, "fade_out_time": 0.5}, - {"type": "display_picture", "path": "data/images/credit02.webp", "display_time": 15, "fade_in_time": 0.5, "fade_out_time": 0.5}, - {"type": "display_picture", "path": "data/images/credit03.webp", "display_time": 15, "fade_in_time": 0.5, "fade_out_time": 0.5}, - {"type": "display_picture", "path": "data/images/credit04.webp", "display_time": 15, "fade_in_time": 0.5, "fade_out_time": 0.5}, - {"type": "display_picture", "path": "data/images/credit05.webp", "display_time": 15, "fade_in_time": 0.5, "fade_out_time": 0.5}, - {"type": "display_picture", "path": "data/images/credit06.webp", "display_time": 15, "fade_in_time": 0.5, "fade_out_time": 0.5}, - {"type": "display_picture", "path": "data/images/credit07.webp", "display_time": 15, "fade_in_time": 0.5, "fade_out_time": 0.5}, - {"type": "display_picture", "path": "data/images/credit08.webp", "display_time": 15, "fade_in_time": 0.5, "fade_out_time": 0.5}, - {"type": "total_stats", "background_path": "data/images/end.webp"}, + {"type": "display_picture", "path": "data/credit01.pcx", "display_time": 15, "fade_in_time": 0.5, "fade_out_time": 0.5}, + {"type": "display_picture", "path": "data/credit02.pcx", "display_time": 15, "fade_in_time": 0.5, "fade_out_time": 0.5}, + {"type": "display_picture", "path": "data/credit03.pcx", "display_time": 15, "fade_in_time": 0.5, "fade_out_time": 0.5}, + {"type": "display_picture", "path": "data/credit04.pcx", "display_time": 15, "fade_in_time": 0.5, "fade_out_time": 0.5}, + {"type": "display_picture", "path": "data/credit05.pcx", "display_time": 15, "fade_in_time": 0.5, "fade_out_time": 0.5}, + {"type": "display_picture", "path": "data/credit06.pcx", "display_time": 15, "fade_in_time": 0.5, "fade_out_time": 0.5}, + {"type": "display_picture", "path": "data/credit07.pcx", "display_time": 15, "fade_in_time": 0.5, "fade_out_time": 0.5}, + {"type": "display_picture", "path": "data/credit08.pcx", "display_time": 15, "fade_in_time": 0.5, "fade_out_time": 0.5}, + {"type": "total_stats", "background_path": "data/end.pcx"}, ], "injections": [ - "data/injections/explosion.bin", "data/injections/house_itemrots.bin", ], }, @@ -491,14 +488,13 @@ {"type": "loop_game"}, ], "injections": [ - "data/injections/cut4_textures.bin", "data/injections/photo.bin", ], }, ], "fmvs": [ - {"path": "fmv/LOGO.RPL", "legal": true}, + {"path": "fmv/LOGO.RPL"}, {"path": "fmv/ANCIENT.RPL"}, {"path": "fmv/MODERN.RPL"}, {"path": "fmv/LANDING.RPL"}, diff --git a/data/tr2/ship/cfg/TR2X_gameflow_gm.json5 b/data/tr2/ship/cfg/TR2X_gameflow_gm.json5 deleted file mode 100644 index e5a2b9b07..000000000 --- a/data/tr2/ship/cfg/TR2X_gameflow_gm.json5 +++ /dev/null @@ -1,157 +0,0 @@ -{ - // NOTE: bad changes to this file may result in crashes. - // Lines starting with double slashes are comments and are ignored. - - "main_menu_picture": "data/images/title_eu_gm.webp", - "savegame_fmt_legacy": "savegame_gm.%d", - "savegame_fmt_bson": "save_trgm_%02d.dat", - - "cmd_init": {"action": "exit_to_title"}, - "cmd_title": {"action": "noop"}, - "cmd_death_in_demo": {"action": "exit_to_title"}, - "cmd_death_in_game": {"action": "noop"}, - "cmd_demo_interrupt": {"action": "exit_to_title"}, - "cmd_demo_end": {"action": "exit_to_title"}, - - "cheat_keys": true, - "load_save_disabled": false, - "play_any_level": false, - "lockout_option_ring": false, - "gym_enabled": false, - "demo_version": false, - "single_level": -1, - - "demo_delay": 30, - "secret_track": 47, - - "title": { - "path": "data/title_gm.tr2", - "music_track": 64, - "sequence": [ - {"type": "display_picture", "path": "data/images/legal_eu_gm.webp", "legal": true}, - {"type": "exit_to_title"}, - ], - }, - - "sfx_path": "data/main_gm.sfx", - "injections": [ - "data/injections/font.bin", - ], - - "levels": [ - // 0. Lara's Home - { - "type": "gym", - "path": "data/assault.tr2", - "music_track": -1, - "sequence": [ - {"type": "loop_game"}, - {"type": "level_stats"}, - ], - }, - - // 1. The Cold War - { - "path": "data/level1.tr2", - "music_track": 33, - "sequence": [ - {"type": "loop_game"}, - {"type": "play_music", "music_track": 41}, - {"type": "level_stats"}, - {"type": "level_complete"}, - ], - "injections": [ - "data/injections/common_pickup_meshes.bin", - "data/injections/shark_sfx.bin", - ], - }, - - // 2. Fool's Gold - { - "path": "data/level2.tr2", - "music_track": 58, - "sequence": [ - {"type": "loop_game"}, - {"type": "play_music", "music_track": 41}, - {"type": "level_stats"}, - {"type": "level_complete"}, - ], - "injections": [ - "data/injections/fools_pickup_meshes.bin", - ], - }, - - // 3. Furnace of the Gods - { - "path": "data/level3.tr2", - "music_track": 59, - "sequence": [ - {"type": "loop_game"}, - {"type": "play_music", "music_track": 41}, - {"type": "level_stats"}, - {"type": "level_complete"}, - ], - "injections": [ - "data/injections/furnace_pickup_meshes.bin", - ], - }, - - // 4. Kingdom - { - "path": "data/level4.tr2", - "music_track": 31, - "sequence": [ - {"type": "give_item", "object_id": "puzzle_1"}, - {"type": "loop_game"}, - {"type": "play_music", "music_track": 52}, - {"type": "display_picture", "path": "data/images/credit00_gm.webp", "display_time": 15, "fade_in_time": 0.5, "fade_out_time": 0.5}, - {"type": "display_picture", "path": "data/images/credit01.webp", "display_time": 15, "fade_in_time": 0.5, "fade_out_time": 0.5}, - {"type": "display_picture", "path": "data/images/credit02.webp", "display_time": 15, "fade_in_time": 0.5, "fade_out_time": 0.5}, - {"type": "display_picture", "path": "data/images/credit03.webp", "display_time": 15, "fade_in_time": 0.5, "fade_out_time": 0.5}, - {"type": "display_picture", "path": "data/images/credit04.webp", "display_time": 15, "fade_in_time": 0.5, "fade_out_time": 0.5}, - {"type": "display_picture", "path": "data/images/credit05.webp", "display_time": 15, "fade_in_time": 0.5, "fade_out_time": 0.5}, - {"type": "display_picture", "path": "data/images/credit06.webp", "display_time": 15, "fade_in_time": 0.5, "fade_out_time": 0.5}, - {"type": "display_picture", "path": "data/images/credit07_gm.webp", "display_time": 15, "fade_in_time": 0.5, "fade_out_time": 0.5}, - {"type": "display_picture", "path": "data/images/credit08.webp", "display_time": 15, "fade_in_time": 0.5, "fade_out_time": 0.5}, - {"type": "total_stats", "background_path": "data/images/end.webp"}, - {"type": "level_complete"}, - ], - "injections": [ - "data/injections/common_pickup_meshes.bin", - "data/injections/guardian_death_commands.bin", - ], - }, - - // 5. Nightmare in Vegas - { - "path": "data/level5.tr2", - "type": "bonus", - "music_track": 34, - "sequence": [ - {"type": "remove_weapons"}, - {"type": "remove_ammo"}, - {"type": "remove_flares"}, - {"type": "remove_medipacks"}, - {"type": "give_item", "object_id": "pistols"}, - {"type": "loop_game"}, - {"type": "play_music", "music_track": 41}, - {"type": "level_stats"}, - {"type": "level_complete"}, - ], - "injections": [ - "data/injections/common_pickup_meshes.bin", - "data/injections/guardian_death_commands.bin", - "data/injections/vegas_fd.bin", - ], - }, - ], - - "demos": [ - ], - - "cutscenes": [ - ], - - "fmvs": [ - ], -} diff --git a/data/tr2/ship/cfg/TR2X_gameflow_level.json5 b/data/tr2/ship/cfg/TR2X_gameflow_level.json5 index 5eaf8eab2..9ec655703 100644 --- a/data/tr2/ship/cfg/TR2X_gameflow_level.json5 +++ b/data/tr2/ship/cfg/TR2X_gameflow_level.json5 @@ -1,9 +1,8 @@ { // This file is used to enable the -l argument support. - "main_menu_picture": "data/images/title_eu.webp", - "savegame_fmt_legacy": "savegame_custom.%d", - "savegame_fmt_bson": "save_tr2_custom_%02d.dat", + "main_menu_picture": "data/title.pcx", + "savegame_fmt_legacy": "savegame.%d", "cmd_init": {"action": "exit_to_title"}, "cmd_title": {"action": "noop"}, @@ -33,6 +32,7 @@ "path": "PLACEHOLDER", "music_track": -1, "sequence": [ + {"type": "set_secret_count", "count": 0}, {"type": "loop_game"}, {"type": "level_stats"}, ], diff --git a/data/tr2/ship/cfg/TR2X_strings.json5 b/data/tr2/ship/cfg/TR2X_strings.json5 index 5d08c0f49..c1592b253 100644 --- a/data/tr2/ship/cfg/TR2X_strings.json5 +++ b/data/tr2/ship/cfg/TR2X_strings.json5 @@ -141,7 +141,7 @@ { "title": "Ice Palace", "objects": { - "tiger": {"name": "White Tiger"}, + "tiger": {"name": "Snow Leopard"}, "key_2": {"name": "Gong Hammer"}, "pickup_2": {"name": "Talion"}, "puzzle_1": {"name": "Tibetan Mask"}, @@ -464,44 +464,13 @@ }, "game_strings": { - "CONTROLS_BACKEND_CONTROLLER": "Controller", - "CONTROLS_BACKEND_KEYBOARD": "Keyboard", - "CONTROLS_CUSTOMIZE": "Customize Controls", - "CONTROLS_CUSTOM_1": "User Keys 1", - "CONTROLS_CUSTOM_2": "User Keys 2", - "CONTROLS_CUSTOM_3": "User Keys 3", - "CONTROLS_DEFAULT_KEYS": "Default Keys", - "DETAIL_ASPECT_MODE": "Aspect mode", - "DETAIL_ASPECT_MODE_16_9": "16:9", - "DETAIL_ASPECT_MODE_4_3": "4:3", - "DETAIL_ASPECT_MODE_ANY": "Any", - "DETAIL_BILINEAR": "Bilinear", - "DETAIL_DEPTH_BUFFER": "Z-Buffer", - "DETAIL_FLOAT_FMT": "%.1f", - "DETAIL_FOG_END": "Fog end", - "DETAIL_FOG_START": "Fog start", - "DETAIL_FOV": "Field of view", - "DETAIL_FPS": "FPS", - "DETAIL_INTEGER_FMT": "%d", - "DETAIL_LIGHTING_CONTRAST": "Lighting contrast", - "DETAIL_LIGHTING_CONTRAST_HIGH": "High", - "DETAIL_LIGHTING_CONTRAST_LOW": "Low", - "DETAIL_LIGHTING_CONTRAST_MEDIUM": "Medium", - "DETAIL_RENDER_MODE": "Render mode", - "DETAIL_RENDER_MODE_HARDWARE": "Hardware", - "DETAIL_RENDER_MODE_SOFTWARE": "Software", - "DETAIL_SCALER": "Scaler", - "DETAIL_SIZER": "Sizer", - "DETAIL_TEXTURE_FILTER": "Texture filter", - "DETAIL_TITLE": "Graphic Options", - "DETAIL_TRAPEZOID_FILTER": "Trapezoid filter", - "DETAIL_UI_BAR_SCALE": "UI bar scale", - "DETAIL_UI_SCROLL_WRAPAROUND": "UI scroll wrap", - "DETAIL_UI_TEXT_SCALE": "UI text scale", - "DETAIL_USE_PSX_FOV": "Use PSX FOV", - "DETAIL_WATER_COLOR_B": "Water color (B)", - "DETAIL_WATER_COLOR_G": "Water color (G)", - "DETAIL_WATER_COLOR_R": "Water color (R)", + "CONTROL_BACKEND_CONTROLLER": "Controller", + "CONTROL_BACKEND_KEYBOARD": "Keyboard", + "CONTROL_CUSTOMIZE": "Customize Controls", + "CONTROL_CUSTOM_1": "User Keys 1", + "CONTROL_CUSTOM_2": "User Keys 2", + "CONTROL_CUSTOM_3": "User Keys 3", + "CONTROL_DEFAULT_KEYS": "Default Keys", "HEADING_GAME_OVER": "GAME OVER", "HEADING_INVENTORY": "INVENTORY", "HEADING_ITEMS": "ITEMS", @@ -536,7 +505,7 @@ "KEYMAP_USE_FLARE": "Flare", "KEYMAP_WALK": "Walk", "MISC_DEMO_MODE": "Demo Mode", - "MISC_EMPTY_SLOT_FMT": "- EMPTY SLOT -", + "MISC_EMPTY_SLOT": "- EMPTY SLOT -", "MISC_EXIT": "Exit", "MISC_NONE": "None", "MISC_OFF": "Off", @@ -547,11 +516,7 @@ "OSD_BILINEAR_FILTER_OFF": "Bilinear filter: off", "OSD_BILINEAR_FILTER_ON": "Bilinear filter: on", "OSD_COMMAND_BAD_INVOCATION": "Invalid invocation: %s", - "OSD_COMMAND_BOOL": "on, off", - "OSD_COMMAND_DECIMAL": "[decimal]", - "OSD_COMMAND_INTEGER": "[integer]", "OSD_COMMAND_UNAVAILABLE": "This command is not currently available", - "OSD_COMMAND_VALID_VALUES": "Valid values: %s", "OSD_COMPLETE_LEVEL": "Level complete!", "OSD_CONFIG_OPTION_GET": "%s is currently set to %s", "OSD_CONFIG_OPTION_SET": "%s changed to %s", @@ -626,22 +591,13 @@ "OSD_UNKNOWN_COMMAND": "Unknown command: %s", "OSD_WIREFRAME_MODE_OFF": "Wireframe mode: off", "OSD_WIREFRAME_MODE_ON": "Wireframe mode: on", - "PAGINATION_NAV": "%d / %d", "PASSPORT_EXIT_DEMO": "Exit Demo", "PASSPORT_EXIT_GAME": "Exit Game", "PASSPORT_EXIT_TO_TITLE": "Exit to Title", - "PASSPORT_LEGACY_SELECT_LEVEL_1": "Legacy saves do not", - "PASSPORT_LEGACY_SELECT_LEVEL_2": "support this feature.", "PASSPORT_LOAD_GAME": "Load Game", - "PASSPORT_MODE_NEW_GAME": "New Game", - "PASSPORT_MODE_NEW_GAME_JP": "Japanese NG", - "PASSPORT_MODE_NEW_GAME_JP_PLUS": "Japanese NG+", - "PASSPORT_MODE_NEW_GAME_PLUS": "New Game+", "PASSPORT_NEW_GAME": "New Game", "PASSPORT_SAVE_GAME": "Save Game", "PASSPORT_SELECT_LEVEL": "Select Level", - "PASSPORT_SELECT_MODE": "Select Mode", - "PASSPORT_STORY_SO_FAR": "Story so far...", "PAUSE_ARE_YOU_SURE": "Are you sure?", "PAUSE_CONTINUE": "Continue", "PAUSE_EXIT_TO_TITLE": "Exit to title?", @@ -659,16 +615,13 @@ "PHOTO_MODE_ROTATE_PROMPT": "Rotate camera", "PHOTO_MODE_SNAP_PROMPT": "Take picture", "PHOTO_MODE_TITLE": "Photo Mode", - "SOUND_DIALOG_MUSIC": "\\{icon sound} Sound", - "SOUND_DIALOG_SOUND": "\\{icon music} Music", - "SOUND_DIALOG_TITLE": "Set Volumes", + "SOUND_SET_VOLUMES": "Set Volumes", "STATS_AMMO_HITS": "Hits", "STATS_AMMO_USED": "Ammo Used", "STATS_ASSAULT_FINISH": "Finish", "STATS_ASSAULT_NO_TIMES_SET": "No Times Set", "STATS_ASSAULT_TITLE": "BEST TIMES", "STATS_BASIC_FMT": "%d", - "STATS_BONUS_STATISTICS": "Bonus Statistics", "STATS_DETAIL_FMT": "%d of %d", "STATS_DISTANCE_TRAVELLED": "Distance Travelled", "STATS_FINAL_STATISTICS": "Final Statistics", diff --git a/data/tr2/ship/cfg/TR2X_strings_gm.json5 b/data/tr2/ship/cfg/TR2X_strings_gm.json5 deleted file mode 100644 index 255fb1a46..000000000 --- a/data/tr2/ship/cfg/TR2X_strings_gm.json5 +++ /dev/null @@ -1,61 +0,0 @@ -{ - // For usage, refer to the documentation here: - // https://github.com/LostArtefacts/TRX/blob/stable/docs/GAME_STRINGS.md - - "levels": [ - // 0. Lara's Home - { - "title": "Lara's Home", - }, - - // 1. The Cold War - { - "title": "The Cold War", - "objects": { - "tiger": {"name": "Snow Leopard"}, - "key_1": {"name": "Guardroom Key"}, - "key_2": {"name": "Shaft 'B' Key"}, - }, - }, - - // 2. Fool's Gold - { - "title": "Fool's Gold", - "objects": { - "key_1": {"name": "CardKey 1"}, - "key_4": {"name": "CardKey 2"}, - "puzzle_1": {"name": "Circuit Board"}, - }, - }, - - // 3. Furnace of the Gods - { - "title": "Furnace of the Gods", - "objects": { - "big_spider": {"name": "Polar Bear"}, - "spider": {"name": "Wolf"}, - "puzzle_1": {"name": "Mask of Tornarsuk"}, - "puzzle_2": {"name": "Gold Nugget"}, - }, - }, - - // 4. Kingdom - { - "title": "Kingdom", - "objects": { - "tiger": {"name": "Snow Leopard"}, - "puzzle_1": {"name": "Mask of Tornarsuk"}, - }, - }, - - // 5. Nightmare in Vegas - { - "title": "Nightmare in Vegas", - "objects": { - "key_1": {"name": "Hotel Key"}, - "puzzle_1": {"name": "Elevator Junction"}, - "puzzle_2": {"name": "Door Circuit"}, - }, - }, - ], -} diff --git a/data/tr2/ship/data/injections/barkhang_pickup_meshes.bin b/data/tr2/ship/data/injections/barkhang_pickup_meshes.bin index 6258d8387..99284528c 100644 Binary files a/data/tr2/ship/data/injections/barkhang_pickup_meshes.bin and b/data/tr2/ship/data/injections/barkhang_pickup_meshes.bin differ diff --git a/data/tr2/ship/data/injections/boat_bits.bin b/data/tr2/ship/data/injections/boat_bits.bin deleted file mode 100644 index dbf348b22..000000000 Binary files a/data/tr2/ship/data/injections/boat_bits.bin and /dev/null differ diff --git a/data/tr2/ship/data/injections/common_pickup_meshes.bin b/data/tr2/ship/data/injections/common_pickup_meshes.bin index 7f89d0265..08b219154 100644 Binary files a/data/tr2/ship/data/injections/common_pickup_meshes.bin and b/data/tr2/ship/data/injections/common_pickup_meshes.bin differ diff --git a/data/tr2/ship/data/injections/cut4_textures.bin b/data/tr2/ship/data/injections/cut4_textures.bin deleted file mode 100644 index 372e9444f..000000000 Binary files a/data/tr2/ship/data/injections/cut4_textures.bin and /dev/null differ diff --git a/data/tr2/ship/data/injections/deck_fd.bin b/data/tr2/ship/data/injections/deck_fd.bin deleted file mode 100644 index 4f56521cb..000000000 Binary files a/data/tr2/ship/data/injections/deck_fd.bin and /dev/null differ diff --git a/data/tr2/ship/data/injections/deck_pickup_meshes.bin b/data/tr2/ship/data/injections/deck_pickup_meshes.bin index 08792874c..81bf404b3 100644 Binary files a/data/tr2/ship/data/injections/deck_pickup_meshes.bin and b/data/tr2/ship/data/injections/deck_pickup_meshes.bin differ diff --git a/data/tr2/ship/data/injections/diving_pickup_meshes.bin b/data/tr2/ship/data/injections/diving_pickup_meshes.bin index f53d8423b..91cddc638 100644 Binary files a/data/tr2/ship/data/injections/diving_pickup_meshes.bin and b/data/tr2/ship/data/injections/diving_pickup_meshes.bin differ diff --git a/data/tr2/ship/data/injections/explosion.bin b/data/tr2/ship/data/injections/explosion.bin deleted file mode 100644 index 9972c25aa..000000000 Binary files a/data/tr2/ship/data/injections/explosion.bin and /dev/null differ diff --git a/data/tr2/ship/data/injections/floating_fd.bin b/data/tr2/ship/data/injections/floating_fd.bin index 044a0d4e8..d1f98768a 100644 Binary files a/data/tr2/ship/data/injections/floating_fd.bin and b/data/tr2/ship/data/injections/floating_fd.bin differ diff --git a/data/tr2/ship/data/injections/floating_pickup_meshes.bin b/data/tr2/ship/data/injections/floating_pickup_meshes.bin index f38dd3d5d..d994fd149 100644 Binary files a/data/tr2/ship/data/injections/floating_pickup_meshes.bin and b/data/tr2/ship/data/injections/floating_pickup_meshes.bin differ diff --git a/data/tr2/ship/data/injections/font.bin b/data/tr2/ship/data/injections/font.bin index cc0e17b37..f7127feae 100644 Binary files a/data/tr2/ship/data/injections/font.bin and b/data/tr2/ship/data/injections/font.bin differ diff --git a/data/tr2/ship/data/injections/fools_pickup_meshes.bin b/data/tr2/ship/data/injections/fools_pickup_meshes.bin deleted file mode 100644 index ef8f32db2..000000000 Binary files a/data/tr2/ship/data/injections/fools_pickup_meshes.bin and /dev/null differ diff --git a/data/tr2/ship/data/injections/furnace_pickup_meshes.bin b/data/tr2/ship/data/injections/furnace_pickup_meshes.bin deleted file mode 100644 index 7e99c9691..000000000 Binary files a/data/tr2/ship/data/injections/furnace_pickup_meshes.bin and /dev/null differ diff --git a/data/tr2/ship/data/injections/guardian_death_commands.bin b/data/tr2/ship/data/injections/guardian_death_commands.bin deleted file mode 100644 index 70801064d..000000000 Binary files a/data/tr2/ship/data/injections/guardian_death_commands.bin and /dev/null differ diff --git a/data/tr2/ship/data/injections/living_fd.bin b/data/tr2/ship/data/injections/living_fd.bin deleted file mode 100644 index d23df437d..000000000 Binary files a/data/tr2/ship/data/injections/living_fd.bin and /dev/null differ diff --git a/data/tr2/ship/data/injections/living_pickup_meshes.bin b/data/tr2/ship/data/injections/living_pickup_meshes.bin index 97c03c924..07146c093 100644 Binary files a/data/tr2/ship/data/injections/living_pickup_meshes.bin and b/data/tr2/ship/data/injections/living_pickup_meshes.bin differ diff --git a/data/tr2/ship/data/injections/rig_pickup_meshes.bin b/data/tr2/ship/data/injections/rig_pickup_meshes.bin index fc7a2bf89..9c07f6e76 100644 Binary files a/data/tr2/ship/data/injections/rig_pickup_meshes.bin and b/data/tr2/ship/data/injections/rig_pickup_meshes.bin differ diff --git a/data/tr2/ship/data/injections/shark_sfx.bin b/data/tr2/ship/data/injections/shark_sfx.bin deleted file mode 100644 index cea287175..000000000 Binary files a/data/tr2/ship/data/injections/shark_sfx.bin and /dev/null differ diff --git a/data/tr2/ship/data/injections/vegas_fd.bin b/data/tr2/ship/data/injections/vegas_fd.bin deleted file mode 100644 index ee24306c2..000000000 Binary files a/data/tr2/ship/data/injections/vegas_fd.bin and /dev/null differ diff --git a/data/tr2/ship/data/injections/wreck_pickup_meshes.bin b/data/tr2/ship/data/injections/wreck_pickup_meshes.bin index 25327bd94..75aef0525 100644 Binary files a/data/tr2/ship/data/injections/wreck_pickup_meshes.bin and b/data/tr2/ship/data/injections/wreck_pickup_meshes.bin differ diff --git a/data/tr2/ship/data/injections/xian_fd.bin b/data/tr2/ship/data/injections/xian_fd.bin index 258aac7dc..d98701465 100644 Binary files a/data/tr2/ship/data/injections/xian_fd.bin and b/data/tr2/ship/data/injections/xian_fd.bin differ diff --git a/data/tr2/ship/data/injections/xian_pickup_meshes.bin b/data/tr2/ship/data/injections/xian_pickup_meshes.bin index 2b47b6493..70029abf7 100644 Binary files a/data/tr2/ship/data/injections/xian_pickup_meshes.bin and b/data/tr2/ship/data/injections/xian_pickup_meshes.bin differ diff --git a/data/tr2/ship/music/57.mp3 b/data/tr2/ship/music/57.mp3 new file mode 100644 index 000000000..ad9e2b673 Binary files /dev/null and b/data/tr2/ship/music/57.mp3 differ diff --git a/data/tr2/ship/shaders/2d.glsl b/data/tr2/ship/shaders/2d.glsl index 681e574c3..b8b2438d2 100644 --- a/data/tr2/ship/shaders/2d.glsl +++ b/data/tr2/ship/shaders/2d.glsl @@ -1,18 +1,25 @@ #ifdef VERTEX +// Vertex shader + +#ifdef OGL33C + out vec2 vertTexCoords; + out vec2 vertCoords; +#else + varying vec2 vertTexCoords; + varying vec2 vertCoords; +#endif layout(location = 0) in vec2 inPosition; layout(location = 1) in vec2 inTexCoords; -out vec2 vertTexCoords; -out vec2 vertCoords; - void main(void) { gl_Position = vec4(inPosition * vec2(2.0, -2.0) + vec2(-1.0, 1.0), 0.0, 1.0); vertCoords = inPosition; vertTexCoords = inTexCoords; } -#elif defined(FRAGMENT) +#else +// Fragment shader #define EFFECT_NONE 0 #define EFFECT_VIGNETTE 1 @@ -26,29 +33,42 @@ uniform bool tintEnabled; uniform vec3 tintColor; uniform int effect; -in vec2 vertTexCoords; -in vec2 vertCoords; -out vec4 outColor; +#ifdef OGL33C + #define OUTCOLOR outColor + #define TEXTURE2D texture + #define TEXTURE1D texture + + in vec2 vertTexCoords; + in vec2 vertCoords; + out vec4 outColor; +#else + #define OUTCOLOR gl_FragColor + #define TEXTURE2D texture2D + #define TEXTURE1D texture1D + + varying vec2 vertTexCoords; + varying vec2 vertCoords; +#endif void main(void) { vec2 uv = vertTexCoords; if (alphaEnabled) { - float alpha = texture(texAlpha, uv).r; + float alpha = TEXTURE2D(texAlpha, uv).r; if (alpha < 0.5) { discard; } } if (paletteEnabled) { - float paletteIndex = texture(texMain, uv).r; - outColor = texture(texPalette, paletteIndex); + float paletteIndex = TEXTURE2D(texMain, uv).r; + OUTCOLOR = TEXTURE1D(texPalette, paletteIndex); } else { - outColor = texture(texMain, uv); + OUTCOLOR = TEXTURE2D(texMain, uv); } if (tintEnabled) { - outColor.rgb *= tintColor.rgb; + OUTCOLOR.rgb *= tintColor.rgb; } if (effect == EFFECT_VIGNETTE) { @@ -56,7 +76,7 @@ void main(void) { float y_dist = vertCoords.y - 0.5; float light = 256 - sqrt(x_dist * x_dist + y_dist * y_dist ) * 300.0; light = clamp(light, 0, 255) / 255; - outColor *= vec4(light, light, light, 1); + OUTCOLOR *= vec4(light, light, light, 1); } } -#endif +#endif // VERTEX diff --git a/data/tr2/ship/shaders/3d.glsl b/data/tr2/ship/shaders/3d.glsl index 54e8b6a89..75314792a 100644 --- a/data/tr2/ship/shaders/3d.glsl +++ b/data/tr2/ship/shaders/3d.glsl @@ -1,4 +1,5 @@ #ifdef VERTEX +// Vertex shader layout(location = 0) in vec3 inPosition; layout(location = 1) in vec4 inTexCoords; @@ -6,13 +7,20 @@ layout(location = 2) in float inTexZ; layout(location = 3) in vec4 inColor; uniform mat4 matProjection; +uniform mat4 matModelView; -out vec4 vertColor; -out vec4 vertTexCoords; -out float vertTexZ; +#ifdef OGL33C + out vec4 vertColor; + out vec4 vertTexCoords; + out float vertTexZ; +#else + varying vec4 vertColor; + varying vec4 vertTexCoords; + varying float vertTexZ; +#endif void main(void) { - gl_Position = matProjection * vec4(inPosition, 1); + gl_Position = matProjection * matModelView * vec4(inPosition, 1); vertColor = inColor / 255.0; vertTexCoords = inTexCoords; vertTexCoords.xy *= vertTexCoords.zw; @@ -20,7 +28,8 @@ void main(void) { vertTexZ = inTexZ; } -#elif defined(FRAGMENT) +#else +// Fragment shader uniform sampler2D tex0; uniform bool texturingEnabled; @@ -29,28 +38,52 @@ uniform bool alphaPointDiscard; uniform float alphaThreshold; uniform float brightnessMultiplier; -in vec4 vertColor; -in vec4 vertTexCoords; -in float vertTexZ; -out vec4 outColor; +#ifdef OGL33C + #define OUTCOLOR outColor + #define TEXTURESIZE textureSize + #define TEXTURE texture + #define TEXELFETCH texelFetch + + in vec4 vertColor; + in vec4 vertTexCoords; + in float vertTexZ; + out vec4 OUTCOLOR; +#else + #define OUTCOLOR gl_FragColor + #define TEXTURESIZE textureSize2D + #define TEXELFETCH texelFetch2D + #define TEXTURE texture2D + + varying vec4 vertColor; + varying vec4 vertTexCoords; + varying float vertTexZ; +#endif void main(void) { - outColor = vertColor; + OUTCOLOR = vertColor; vec2 texCoords = vertTexCoords.xy; texCoords.xy /= vertTexCoords.zw; if (texturingEnabled) { - if (alphaPointDiscard && smoothingEnabled && discardTranslucent(tex0, texCoords)) { - discard; +#if defined(GL_EXT_gpu_shader4) || defined(OGL33C) + if (alphaPointDiscard && smoothingEnabled) { + // do not use smoothing for chroma key + ivec2 size = TEXTURESIZE(tex0, 0); + ivec2 texCoordsNN = ivec2(texCoords.xy * size.xy) % size.xy; + vec4 texel = TEXELFETCH(tex0, texCoordsNN, 0); + if (texel.a == 0.0) { + discard; + } } +#endif - vec4 texColor = texture(tex0, texCoords.xy); + vec4 texColor = TEXTURE(tex0, texCoords.xy); if (alphaThreshold >= 0.0 && texColor.a <= alphaThreshold) { discard; } - outColor = vec4(outColor.rgb * texColor.rgb * brightnessMultiplier, texColor.a); + OUTCOLOR = vec4(OUTCOLOR.rgb * texColor.rgb * brightnessMultiplier, texColor.a); } } -#endif +#endif // VERTEX diff --git a/data/tr2/ship/shaders/common.glsl b/data/tr2/ship/shaders/common.glsl deleted file mode 100644 index 4aac9ff56..000000000 --- a/data/tr2/ship/shaders/common.glsl +++ /dev/null @@ -1,27 +0,0 @@ -#define PI 3.1415926538 - -vec3 waterWibble(vec4 position, vec2 viewportSize, float wibbleOffset) -{ - // get screen coordinates - vec3 ndc = position.xyz / position.w; //perspective divide/normalize - vec2 viewportCoord = ndc.xy * 0.5 + 0.5; //ndc is -1 to 1 in GL. scale for 0 to 1 - vec2 viewportPixelCoord = viewportCoord * viewportSize; - - float amplitude = 2.0; - viewportPixelCoord.x += sin((wibbleOffset + viewportPixelCoord.y) * 2.0 * PI / 32) * amplitude; - viewportPixelCoord.y += sin((wibbleOffset + viewportPixelCoord.x) * 2.0 * PI / 32) * amplitude; - - // reverse transform - viewportCoord = viewportPixelCoord / viewportSize; - ndc.xy = (viewportCoord - 0.5) * 2.0; - return ndc * position.w; -} - -bool discardTranslucent(sampler2D tex, vec2 uv) -{ - // do not use smoothing for chroma key - ivec2 size = textureSize(tex, 0); - ivec2 texCoordsNN = ivec2(uv.xy * size.xy) % size.xy; - vec4 texel = texelFetch(tex, texCoordsNN, 0); - return texel.a == 0.0; -} diff --git a/data/tr2/ship/shaders/fade.glsl b/data/tr2/ship/shaders/fade.glsl index 1ce77503f..43578c8a4 100644 --- a/data/tr2/ship/shaders/fade.glsl +++ b/data/tr2/ship/shaders/fade.glsl @@ -1,4 +1,5 @@ #ifdef VERTEX +// Vertex shader layout(location = 0) in vec2 inPosition; @@ -6,12 +7,19 @@ void main(void) { gl_Position = vec4(inPosition * vec2(2.0, -2.0) + vec2(-1.0, 1.0), 0.0, 1.0); } -#elif defined(FRAGMENT) +#else +// Fragment shader uniform float opacity; -out vec4 outColor; + +#ifdef OGL33C + #define OUTCOLOR outColor + out vec4 outColor; +#else + #define OUTCOLOR gl_FragColor +#endif void main(void) { - outColor = vec4(0, 0, 0, opacity); + OUTCOLOR = vec4(0, 0, 0, opacity); } -#endif +#endif // VERTEX diff --git a/data/tr2/ship/shaders/fbo.glsl b/data/tr2/ship/shaders/fbo.glsl index ca9b714be..ebc393978 100644 --- a/data/tr2/ship/shaders/fbo.glsl +++ b/data/tr2/ship/shaders/fbo.glsl @@ -1,22 +1,38 @@ #ifdef VERTEX +// Vertex shader layout(location = 0) in vec2 inPosition; -out vec2 vertTexCoords; +#ifdef OGL33C + out vec2 vertTexCoords; +#else + varying vec2 vertTexCoords; +#endif void main(void) { vertTexCoords = inPosition; gl_Position = vec4(vertTexCoords * vec2(2.0, 2.0) + vec2(-1.0, -1.0), 0.0, 1.0); } -#elif defined(FRAGMENT) +#else +// Fragment shader uniform sampler2D tex0; -in vec2 vertTexCoords; -out vec4 outColor; +#ifdef OGL33C + #define OUTCOLOR outColor + #define TEXTURE texture + + in vec2 vertTexCoords; + out vec4 OUTCOLOR; +#else + #define OUTCOLOR gl_FragColor + #define TEXTURE texture2D + + varying vec2 vertTexCoords; +#endif void main(void) { - outColor = texture(tex0, vertTexCoords); + OUTCOLOR = TEXTURE(tex0, vertTexCoords); } -#endif +#endif // VERTEX diff --git a/docs/COMMAND_LINE.md b/docs/COMMAND_LINE.md deleted file mode 100644 index 9fca2d295..000000000 --- a/docs/COMMAND_LINE.md +++ /dev/null @@ -1,20 +0,0 @@ -# Command line options - -Currently the following command line interface options are available: - -`-g/--gold` (legacy: `-gold`): - Runs the Unfinished Business or the Golden Mask expansion pack, depending - on the game. - -`--demo-pc` (TR1X only, legacy: `-demo_pc`): - Runs the PC demo level. - -`-l/--level `: - Runs the game immediately launching it into the specified level. - The path should be absolute. Internally, this option uses - `TR*X_gameflow_level.json5` as a template instructing it how to run the - game. - -`-s/--save `: - Runs the game immediately loading a specific save slot. The first save - starts at `num=1`. This option can be combined with `-l/--level`. diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index efcdc6a13..7ea40e63f 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -266,63 +266,30 @@ request number, but it's important to carefully review the body field, as it often includes unwanted content. +### Branching model + +We have two branches: `develop` and `stable`. `develop` is where all changes +about to be published in the next release land. `stable` is the latest release. +We avoid creating merge commits between these two – they should always point to +the same HEAD when applicable. This means that any hotfixes that need to be +released ahead of unpublished work in `develop` are merged directly to +`stable`, and `develop` needs to be then rebased on top of the now-patched +`stable`. + + ### Tooling Internal tools are typically coded in a reasonably recent version of Python, while avoiding the use of bash, shell, and similar languages. -### Branching model - -We have two branches: `develop` and `stable`. `develop` is where all changes -about to be published in the next release land. `stable` is the latest release. - - ### Releasing a new version -New version releases are published automatically whenever a new tag is pushed -to the `stable` branch with the help of GitHub actions. -The general workflow is this: +New version releases happen automatically whenever a new tag is pushed to the +`stable` branch with the help of GitHub actions. In general this is accompanied +with a special commit `docs: release X.Y.Z` that also adjusts the changelog. +See git history for details. -```console -TR_VERSION=... -RELEASE_VERSION=... - -# Switch to the stable branch. -git checkout stable - -# Merge `develop` into it. -git merge develop - -# Create a special commit `docs: release X.Y.Z` marking the release in the -# relevant changelog file. Then tag it with `tr1-X.Y.Z` or `tr2-X.Y.Z`. -# You can do that by hand, or run the command below: -tools/release commit ${TR_VERSION} ${RELEASE_VERSION} - -# Review the changelog content. - -# Switch back to develop. -git checkout develop - -# Merge stable using fast-forward. -git merge --ff stable - -# Review both branches and changes. If everything is okay, push to GitHub. -# You can do this by hand: git push origin develop stable tr1-X.Y.Z, or: -# tools/release push ${TR_VERSION} ${RELEASE_VERSION} -``` - -### Hotfixes - -Hotfix releases are a bit different as we try to not include non-bugfix changes -in them. Here instead of merging `develop` to `stable` we cherry-pick relevant -changes, resolving conflicts along the way. - -### Versioning - -We increase the major version for significant releases based on judgment, -typically defaulting to increasing the minor version. Hotfixes increase the -patch version. ## Glossary diff --git a/docs/GAME_FLOW.md b/docs/GAME_FLOW.md index 2d48e919f..53ad7d3c2 100644 --- a/docs/GAME_FLOW.md +++ b/docs/GAME_FLOW.md @@ -34,8 +34,8 @@ remains distinct for each game. "savegame_fmt_bson": "save_tr1_%02d.dat", "demo_delay": 16, "water_color": [0.45, 1.0, 1.0], -"fog_start": 22.0, -"fog_end": 30.0, +"draw_distance_fade": 22.0, +"draw_distance_max": 30.0, "injections": [ "data/global_injection1.bin", "data/global_injection2.bin", @@ -98,14 +98,14 @@ remains distinct for each game. - fog_start + draw_distance_fade - Double + Double* The distance (in tiles) at which objects and the world start to fade into blackness.

    -
  • The default value in OG TR1 is hardcoded to 12.
  • +
  • The default hardcoded value in TR1 is 12.
  • The default (disabled) value in TombATI is 72.
@@ -113,13 +113,13 @@ remains distinct for each game. - fog_end + draw_distance_max - Double + Double* The distance (in tiles) at which objects and the world are clipped away.
    -
  • The default value in OG TR1 is hardcoded to 20.
  • +
  • The default hardcoded value in TR1 is 20.
  • The default (disabled) value in TombATI is 80.
@@ -196,9 +196,11 @@ remains distinct for each game. Float array - Water color (R, G, B) or `#RRGGBB`. 1.0 or `FF` means pass-through, 0.0 - or `00` means completely black color. - See this table for reference values. + Water color (R, G, B). 1.0 means pass-through, 0.0 means no value at all. +
    +
  • [0.6, 0.7, 1.0] is the original DOS version filter.
  • +
  • [0.45, 1.0, 1.0] is the default TombATI filter.
  • +
@@ -313,39 +315,6 @@ remains distinct for each game. The path to the sound effects (.sfx) file to use in the game. - - - - fog_start - - Double - - The distance (in tiles) at which objects and the world start to fade into - blackness. The default value in OG TR2 is hardcoded to 12. - - - - - - fog_end - - Double - - The distance (in tiles) at which objects and the world are clipped away. - The default value in OG TR2 is hardcoded to 20. - - - - - water_color - - Float array or hex string - - Water color (R, G, B) or `#RRGGBB`. 1.0 or `FF` means pass-through, 0.0 - or `00` means completely black color. - See this table for reference values. - - ## Game flow commands @@ -455,8 +424,8 @@ Following are each of the properties available within a level. "music_track": 57, "lara_type": 0, "water_color": [0.7, 0.5, 0.85], - "fog_start": 34.0, - "fog_end": 50.0, + "draw_distance_fade": 34.0, + "draw_distance_max": 50.0, "unobtainable_pickups": 1, "unobtainable_kills": 1, "inherit_injections": false, @@ -548,7 +517,7 @@ Following are each of the properties available within a level. The ambient music track ID. - fog_start + draw_distance_fade¹ Double Can be customized per level. See above @@ -556,7 +525,7 @@ Following are each of the properties available within a level. - fog_end + draw_distance_max¹ Double Can be customized per level. See above @@ -690,18 +659,7 @@ default game flow for examples. path String - - Displays the specified picture for a fixed time. - Files that are needed to function only with a specific aspect ratio can - be placed in a directory adjacent to the main image, named according to - the aspect ratio – for example, 4x3/title.png or 16x10/title.png. The - game won't attempt to match these precisely; instead, it will select the - file with the aspect ratio closest to the game's viewport. The main image - designated by path is presumed to have a 16:9 aspect ratio - for this purpose, and as such there's no need for 16x9-specific - directory.
- This logic applies to all images. - + Displays the specified picture for a fixed time. display_time @@ -889,6 +847,16 @@ default game flow for examples. a level. + + set_secret_count² + value + Integer + + Sets the current level secret count to this number. In Tomb Raider II + this is mainly used to mark certain levels, such as Dragon's Lair, as + having no secrets. + + set_lara_start_anim² value @@ -1226,27 +1194,17 @@ best practice to remove the references to maintain a clean game flow file. Following is a summary of what each of the default injection files that are provided with the game achieves. +#### TR1 + - - - - - - - - - - - - - - - - - - - - - - - - @@ -1305,28 +1231,16 @@ provided with the game achieves. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1489,23 +1355,17 @@ provided with the game achieves. - - - - - -
Injection fileUsage Purpose
- *_cameras.bin - TR1 - Injects positional adjustments for cameras that can otherwise cause visual - issues, such as in Temple of the Cat. -
*_fd.bin TR1, TR2 Injects fixes for floor data issues in the original levels. Refer to the README for a full list of fixes. @@ -1256,47 +1214,15 @@ provided with the game achieves. *_itemrots.bin TR1, TR2 Injects rotations on pickup items so they make more visual sense when using the 3D pickups option.
- *_meshfixes.bin - TR1 - Injects miscellaneous mesh adjustments for objects, such as in Obelisk of - Khamoon to avoid z-fighting. -
- *_pickup_meshes.bin - TR1, TR2 - Injects mesh edits to change the scale of various pickup models, such as - the keys in St. Francis' Folly or the Prayer Wheel in Barkhang Monastry. -
- *_sfx.bin - TR1, TR2 - Injects various SFX fixes or additions, such as the PSX Uzi SFX in TR1, or - fixing the silent enemies in TR2's water levels. -
*_skybox.bin TR1 Injects a predefined skybox model into specific levels. *_textures.bin TR1 Injects fixes for texture issues in the original levels, such as gaps in the walls or wrongly colored models. Refer to the README for a full list of fixes.
- boat_bits.bin - TR2 - Injects a model in slot `O_BOAT_BITS` (221) which is used to show the boat - exploding when it crosses mines. -
backpack.bin TR1 Injects mesh edits for Lara's backback, such that it becomes shallower. This is only applied when the braid is enabled, to avoid the braid @@ -1343,7 +1257,6 @@ provided with the game achieves. braid.bin TR1 Injects a braid when the option for it is enabled. This also edits Lara's head meshes (object 0 and object 4) to make the braid fit better. A golden @@ -1367,21 +1280,10 @@ provided with the game achieves. braid_valley.bin
- bubbles.bin - TR1 - Injects replacement sprite textures for Lara's underwater bubble sprites, - which are cut off in OG. -
cistern_plants.bin TR1 This disables the animation on sprite ID 193 in The Cistern and Tomb of Tihocan. @@ -1391,7 +1293,6 @@ provided with the game achieves. khamoon_mummy.bin TR1 Injects the mummy in room 25 of City of Khamoon, which is present in the PS1 version but not the PC. @@ -1401,55 +1302,23 @@ provided with the game achieves. lara_animations.bin TR1 Injects several animations, state changes and commands for Lara, such as responsive jumping, jump-twist, somersault, underwater roll, and wading.
- lara_gym_guns.bin - TR1 - Injects all of Lara's weapons and weapon animations in TR1's gym level. -
explosion.bin TR1 Injects explosion sprites for certain console commands.
- font.bin - TR1, TR2 - Injects replacement font sprites to support more characters than OG. -
- guardian_death_commands.bin - TR2 - Injects an animation command for the bird guardian to end the level on the - final frame of its death animation. The original hard-coded end-level - behaviour is removed in TR2X. -
mines_pushblocks.bin TR1 Injects animation command data for pushblock types 2, 3 and 4 to restore the missing scraping SFX when pulling these blocks. @@ -1459,7 +1328,6 @@ provided with the game achieves. pickup_aid.bin TR1 Injects a sprite sequence similar to the Midas twinkle effect, which is used when the option for pickup aids is enabled. Custom levels should @@ -1470,7 +1338,6 @@ provided with the game achieves. photo.bin TR1, TR2 Injects camera shutter sound effect for the photo mode, needed only for the cutscene levels. @@ -1480,7 +1347,6 @@ provided with the game achieves. purple_crystal.bin TR1 Injects a replacement savegame crystal model to match the PS1 style. scion_collision.bin TR1 Increases the collision radius on the (targetable) Scion such that it can be shot with the shotgun.
- seaweed_collision.bin - TR2 - Fixes the seaweed in Living Quarters blocking Lara from exiting the water. -
+#### TR2 + +TBD + ## FMVs The FMVs section of the document defines how to play video content. This is an @@ -1572,65 +1432,3 @@ keyboard or controller layouts defined. If you do not have any requirement to enforce settings, you can omit the `enforced_config` section from your game flow altogether. - -## Water colors - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
GameColor (hex)Color (array)Usage
TR1 #99B2FF[0.6, 0.7, 1.0]original DOS version color
#72FFFF[0.45, 1.0, 1.0]default TombATI color
TR2 #80DFFF[0.5, 0.875, 1.0]default PC hardware renderer color
#AAAAFF[0.66, 0.66, 1.0]default PC software renderer color
#CCFF80[0.8, 1.0, 0.5]Venice, Bartoli's Hideout and Opera House (PS1)
#CCFF99[0.8, 1.0, 0.6]Temple of Xian (PS1)
#CCFFCC[0.8, 1.0, 0.8]Floating Islands and Dragon's Lair (PS1)
#B2E5E5[0.7, 0.9, 0.9]The Great Wall and Tibetan Foothills (PS1)
#80FFFF[0.5, 1.0, 1.0]All other PS1 levels
diff --git a/docs/MIGRATING.md b/docs/MIGRATING.md index 1c9f2deb8..0b7232906 100644 --- a/docs/MIGRATING.md +++ b/docs/MIGRATING.md @@ -2,18 +2,7 @@ ## TR1X -### Version 4.9 to 4.10 - -1. **Update fog configuration** - If you wish to force your fog settings on player: - - Rename `draw_distance_fade` to `fog_start` - - Rename `draw_distance_max` to `fog_end` - - If you wish to give the player agency to change the fog: - - Remove `draw_distance_fade` and `draw_distance_max` - - -### Version 4.7 to 4.8 +### Version 4.7 to TR1X 4.8 1. **Rename basic keys** - Replace `file` key with `path` for every level. diff --git a/docs/tr1/CHANGELOG.md b/docs/tr1/CHANGELOG.md index 1b54ffcd9..2bd28cfe4 100644 --- a/docs/tr1/CHANGELOG.md +++ b/docs/tr1/CHANGELOG.md @@ -1,75 +1,15 @@ -## [Unreleased](https://github.com/LostArtefacts/TRX/compare/tr1-4.9...develop) - ××××-××-×× -- added an ability to customize the fog distances (#634) -- added an ability to customize the water color [see the reference](/docs/GAME_FLOW.md#water-color-table) (#1532) -- added support for a hex water color notation (eg. `#80FFFF`) in the game flow file -- added support for antitriggers, like TR2+ (#2580) -- added support for aspect ratio-specific images (#1840) -- added an option to wraparound when scrolling UI dialogs, such as save/load (#2834) -- added aliases to CLI options (`-gold` becomes `-g/--gold`, `-demo_pc` becomes `--demo-pc`) -- added a `--help` CLI option (may not output anything on Windows machines – OS bug) -- changed the `draw_distance_fade` and `draw_distance_max` to `fog_start` and `fog_end` -- changed `Select Detail` dialog title to `Graphic Options` -- changed the number of static mesh slots from 50 to 256 (#2734) -- changed the "enable EIDOS logo" option to disable the Core Design and Bink Video Codec FMVs as well; renamed to "enable legal" (#2741) -- changed sprite pickups to respect the water tint if placed underwater (#2673) -- changed save to take priority over load when both inputs are held on the same frame, in line with OG (#2833) -- changed the sound dialog appearance (repositioned and added text labels) -- changed The Unfinished Business strings to default to the OG strings file for the main tables (#2847) -- changed the dev console to no longer add duplicate entries to the history -- fixed the bilinear filter to not readjust the UVs (#2258) -- fixed disabling the cutscenes causing the game to exit (#2743, regression from 4.8) -- fixed anisotropy filter causing black lines on certain GPUs (#902) -- fixed mesh faces not being drawn under some circumstances (#2452, #2438) -- fixed objects disappearing too early around screen edges (#2005) -- fixed the trapezoid filter being toggled if Alt-F4 (either left or right) is used to close the game (#2690) -- fixed enemies in one-click high water appearing with a water tint, and not making any animation sounds (#2753) -- fixed the scale of the four keys in St. Francis' Folly (#2652) -- fixed the panther at times not making a sound when it dies, and restored Skate Kid's death SFX (#2647) -- fixed pushblocks being rotated when Lara grabs them, most noticeable if asymmetric textures have been used (#2776) -- fixed Lara becoming clamped if she picks up an item under a steeply sloped ceiling (#2879) -- fixed a crash when 3D pickups are disabled and Lara crosses a trigger to look at a pickup item (#2711, regression from 4.8) -- fixed trapezoid filter warping on faces close to the camera (#2629, regression from 4.9) -- fixed Mac builds crashing upon start (regression from 4.9) -- fixed sprites rendering black if no shade value is assigned in the level (#2701, regression from 4.9) -- fixed being stuck on the Restart Level page if using save crystals and F5 is pressed when no saves are present (#2700, regression from 4.8.2) -- fixed being stuck on the Exit to Title page if using save crystals and a new save is made when there were previously none, and then F5 is pressed (#2700, regression from 4.9) -- fixed the sprite UVs to restore the right and bottom edge pixels (#2672, regression from 4.8) -- fixed sprites missing the fog effect (regression from 4.9) -- fixed the camera going out of bounds in 60fps near specific invalid floor data (known as no-space) (#2764, regression from 4.9) -- fixed wrong PS1-style title bar color for the end of the level stats dialog (regression from 4.9) -- fixed Story So Far showing up even when there's nothing to play (#2611, regression from 2.10) -- fixed Story So Far not playing the opening FMV, `cafe.rpl` (#2779, regression from 2.10) -- fixed Lara at times ending up in incorrect rooms when using the teleport cheat (#2486, regression from 3.0) -- fixed the `/pos` console command reporting the base room number when Lara is actually in a flipped room (#2487, regression from 3.0) -- fixed clicks in audio sounds (#2846, regression from 2.0) -- fixed Lara being killed if she enters the void in a level that uses the `disable_floor` sequence in the game flow (#2874, regression from 4.9) -- fixed game crashing if the music folder was not present (#2887, regression from 4.9) -- fixed game crashing on unknown sequencer events -- improved bubble appearance (#2672) -- improved rendering performance -- improved pause exit dialog - it can now be canceled with escape -- improved the `/set` console command to display available options if given an unknown argument -- removed the pretty pixels options (it's now always enabled, #2258) - -## [4.9](https://github.com/LostArtefacts/TRX/compare/tr1-4.8.3...tr1-4.9) - 2025-03-31 +## [Unreleased](https://github.com/LostArtefacts/TRX/compare/tr1-4.8.3...develop) - ××××-××-×× - added quadrilateral interpolation (#354) - added `/flood` and `/drain` console commands - added support for `-l`/`--level` argument to play a single level - added support for `-s`/`--save` argument to immediately start a saved game - added support for custom levels to use `disable_floor` in the gameflow, similar to TR2's Floating Islands (#2541) - added drawing of object mesh spheres to the `/debug` console command -- added TR2+ stats if the full stat detail mode option is enabled (#2561): - - ammo hits / used - - health packs used - - distance travelled -- added a TR2+ style bordered stat box to the end of level stats if the full stat detail mode option is enabled (#2658) - changed the Controls screen to hide the reset and unbind texts when changing a key (#2103) - changed injections to a new file format with a smaller footprint and improved applicability tests (#1967) - changed the `/pos` command to show `Demo` and `Cutscene` instead of `Level` when relevant - changed the `/pos` command to show demo and cutscene numbers starting at 1, in line with `/play` - changed the `/play` and `/pos` commands to always treat the gym level as the level 0 – even if it's not included -- changed sprites to respect the water tint if placed underwater (#2093) -- changed the optional `Deaths` stat to be placed last in the stats menu - fixed delays when scanning available save games (#2610, #1335, regression from <3.0) - fixed several instances of the camera going out of bounds (#1034) - fixed issues with stacked, floating and flipmap pushblocks in custom levels @@ -85,8 +25,6 @@ - fixed the `/pos` command not showing demo and cutscene titles - fixed the embedded bats fix causing problems inside rooms with trapdoors (regression from 4.6) - fixed cutscene music looping (#2591, regression from 4.8) -- fixed saves created before version 2.15 causing a crash on load (#2654, regression from 4.8) -- fixed the console opening when remapping its key (#2641) - removed perspective filter toggle (it had no effect; repurposed to trapezoid interpolation toggle) - improved camera mode navigation: - improved support for pivoting @@ -103,7 +41,7 @@ ## [4.8.2](https://github.com/LostArtefacts/TRX/compare/tr1-4.8.1...tr1-4.8.2) - 2025-02-15 - changed default FPS value to 60 (#2501) - changed passport to be more responsive to player inputs (#1328) -- fixed Story So Far not skipping over levels (#2506, regression from 4.8) +- fixed story so far not skipping over levels (#2506, regression from 4.8) - fixed resolving paths (especially to music files) on case-sensitive filesystems (#1934, #2504) - improved memory usage by shedding ca. 100-110 MB on average @@ -761,7 +699,7 @@ - added a .NET-based installer - added the option to make Lara revert to pistols on new level start (#557) - added the PS1 style UI (#517) -- added the "Story So far..." option in the select level menu to view cutscenes and FMVs (#201) +- added the "Story so far..." option in the select level menu to view cutscenes and FMVs (#201) ## [2.9.1](https://github.com/LostArtefacts/TRX/compare/tr1-2.9...tr1-2.9.1) - 2022-06-03 - fixed crash on centaur hatch (#579, regression from 2.9) diff --git a/docs/tr1/README.md b/docs/tr1/README.md index e49c78f4e..648be642f 100644 --- a/docs/tr1/README.md +++ b/docs/tr1/README.md @@ -1,5 +1,6 @@

-TR1X logo +TR1X logo +TR1X logo

## Windows / Linux @@ -34,7 +35,7 @@ We hope that eventually these alerts will go away as the popularity of the proje - Or the more manual link: https://archive.org/details/tomb-raider-i-unfinished-business-pc-eng-full-version_20201225 2. For TombATI users this means copying the `data`, `fmv` and `music` directories. 5. To play the game, run `TR1X.exe`. -6. To play the Unfinished Business expansion pack, run `TR1X.exe -gold`. +6. To play the Unfinished Expansion pack, run `TR1X.exe -gold`. If you install everything correctly, your game directory should look more or less like this (click to expand): @@ -47,22 +48,24 @@ less like this (click to expand): │   ├── TR1X.json5 * │   ├── TR1X_gameflow.json5 │   ├── TR1X_gameflow_demo_pc.json5 -│   ├── TR1X_gameflow_level.json5 -│   ├── TR1X_gameflow_ub.json5 -│   ├── TR1X_strings.json5 -│   ├── TR1X_strings_demo_pc.json5 -│   ├── TR1X_strings_level.json5 -│   └── TR1X_strings_ub.json5 +│   └── TR1X_gameflow_ub.json5 ├── data │   ├── cat.phd +│   ├── cred0.pcx +│   ├── cred1.pcx +│   ├── cred2.pcx +│   ├── cred3.pcx │   ├── cut1.phd │   ├── cut2.phd │   ├── cut3.phd │   ├── cut4.phd │   ├── egypt.phd +│   ├── eidospc.pcx │   ├── end2.phd +│   ├── end.pcx │   ├── end.phd │   ├── gym.phd +│   ├── install.pcx │   ├── level10a.phd │   ├── level10b.phd │   ├── level10c.phd @@ -78,8 +81,10 @@ less like this (click to expand): │   ├── level8a.phd │   ├── level8b.phd │   ├── level8c.phd -│   ├── title.phd -│   ├── images +│   ├── titleh.pcx +│   ├── titleh_ub.pcx +│   │── title.phd +│   │── images │   │ ├── atlantis.webp │   │ ├── credits_1.webp │   │ ├── credits_2.webp @@ -96,17 +101,7 @@ less like this (click to expand): │   │ ├── peru.webp │   │ ├── title.webp │   │ ├── title_og_alt.webp -│   │ ├── title_ub.webp -│ │ └── og -│ │ ├── cred0.pcx -│ │ ├── cred1.pcx -│ │ ├── cred2.pcx -│ │ ├── cred3.pcx -│   │ ├── eidospc.pcx -│   │ ├── end.pcx -│   │ ├── install.pcx -│   │ ├── titleh.pcx -│   │ └── titleh_ub.pcx +│   │ └── title_ub.webp │   └── injections │   ├── atlantis_fd.bin │   ├── atlantis_textures.bin @@ -187,9 +182,7 @@ less like this (click to expand): ├── shaders │   ├── 2d.glsl │   ├── 3d.glsl -│   ├── common.glsl -│   ├── fbo.glsl -│   └── sprites.glsl +│   └── fbo.glsl ├── TR1X.exe └── TR1X_ConfigTool.exe @@ -232,14 +225,21 @@ less like this (click to expand): │   └── TR1X_gameflow_ub.json5 ├── data │   ├── cat.phd + │   ├── cred0.pcx + │   ├── cred1.pcx + │   ├── cred2.pcx + │   ├── cred3.pcx │   ├── cut1.phd │   ├── cut2.phd │   ├── cut3.phd │   ├── cut4.phd │   ├── egypt.phd + │   ├── eidospc.pcx │   ├── end2.phd + │   ├── end.pcx │   ├── end.phd │   ├── gym.phd + │   ├── install.pcx │   ├── level10a.phd │   ├── level10b.phd │   ├── level10c.phd @@ -255,6 +255,8 @@ less like this (click to expand): │   ├── level8a.phd │   ├── level8b.phd │   ├── level8c.phd + │   ├── titleh.pcx + │   ├── titleh_ub.pcx │   │── title.phd │   │── images │   │ ├── atlantis.webp @@ -273,17 +275,7 @@ less like this (click to expand): │   │ ├── peru.webp │   │ ├── title.webp │   │ ├── title_og_alt.webp - │   │ ├── title_ub.webp - │ │ └── og - │ │ ├── cred0.pcx - │ │ ├── cred1.pcx - │ │ ├── cred2.pcx - │ │ ├── cred3.pcx - │   │ ├── eidospc.pcx - │   │ ├── end.pcx - │   │ ├── install.pcx - │   │ ├── titleh.pcx - │   │ └── titleh_ub.pcx + │   │ └── title_ub.webp │   └── injections │   ├── atlantis_fd.bin │   ├── atlantis_textures.bin @@ -365,22 +357,10 @@ less like this (click to expand): └── shaders    ├── 2d.glsl    ├── 3d.glsl -    ├── common.glsl -    ├── fbo.glsl -    └── sprites.glsl +    └── fbo.glsl -## Dev snapshots - -To ease the load on our infrastructure, the binary assets such as images and music files are not included in pre-releases and pull request preview builds - they only ship with the full release builds. -However, you can easily download them manually from these urls: - -- https://lostartefacts.dev/aux/tr1x/main.zip (main assets) -- https://lostartefacts.dev/aux/tr1x/music.zip (music files) -- https://lostartefacts.dev/aux/tr1x/trub-music.zip (TR: Unfinished Business expansion pack with fan-patched music) -- https://lostartefacts.dev/aux/tr1x/trub-vanilla.zip (TR: Unfinished Business expansion pack in its original form) - ## Improvements over original game Not all options are turned on by default. Refer to `TR1X_ConfigTool.exe` for details. @@ -484,8 +464,6 @@ Not all options are turned on by default. Refer to `TR1X_ConfigTool.exe` for det - fixed a potential softlock when killing the Torso boss in Great Pyramid - fixed being able to shoot the scion multiple times if save/load is used while it blows up - fixed the game crashing if a cinematic is triggered but the level contains no cinematic frames -- fixed pushblocks being rotated when Lara grabs them, most noticeable if asymmetric textures have been used -- fixed Lara becoming clamped if she picks up an item under a steeply sloped ceiling #### Cheats - added a fly cheat @@ -525,11 +503,6 @@ Not all options are turned on by default. Refer to `TR1X_ConfigTool.exe` for det - added optional deaths counter - added optional total pickups and kills per level - added unobtainable pickups, kills, and secrets stats support in the gameflow -- added optional TR2+ stats: - - ammo hits / used - - health packs used - - distance travelled -- added an optional TR2+ style bordered stat box to the end of level stats #### Visuals - added quadrilateral texture correction @@ -618,8 +591,6 @@ Not all options are turned on by default. Refer to `TR1X_ConfigTool.exe` for det - **Atlantean Stronghold**: converted track 20 in room 4, track 19 in room 13, track 11 in room 17, track 15 in room 20, and track 12 in room 25 to one shot - **The Hive**: converted track 9 in room 8, track 6 in room 18, track 12 in room 30, track 18 in room 31, track 3 in room 32, and track 20 in room 35 to one shot - fixed being unable to load a level that contains no sound effect data -- fixed the panther at times not making a sound when it dies -- restored Skate Kid's death SFX #### Mods - added developer console (accessible with `/`, see [COMMANDS.md](COMMANDS.md) for details) @@ -633,7 +604,6 @@ Not all options are turned on by default. Refer to `TR1X_ConfigTool.exe` for det - added per-level customizable water color (with customizable blue component) - added per-level customizable fog distance - added deadly water feature from TR2+ -- added support for antitriggers, like TR2+ #### Miscellaneous - added Linux builds @@ -650,7 +620,6 @@ Not all options are turned on by default. Refer to `TR1X_ConfigTool.exe` for det - expanded maximum texture pages from 32 to 128 - expanded maximum vertices of a single drawable object from 1500 to unlimited - expanded the number of visible enemies from 8 to 32 -- expanded the number of static mesh slots from 50 to 256 - ported audio decoding library to ffmpeg - ported video decoding library to ffmpeg - ported image decoding library to ffmpeg diff --git a/docs/tr2/CHANGELOG.md b/docs/tr2/CHANGELOG.md index 9b8593c9f..03fab6e53 100644 --- a/docs/tr2/CHANGELOG.md +++ b/docs/tr2/CHANGELOG.md @@ -1,98 +1,4 @@ -## [Unreleased](https://github.com/LostArtefacts/TRX/compare/tr2-1.0.1...develop) - ××××-××-×× -- added aliases to CLI options (`-gold` becomes `-g/--gold`) -- added a `--help` CLI option (may not output anything on Windows machines – OS bug) -- added explosion sprites to Home Sweet Home (#1569) -- changed the sound dialog appearance (repositioned, added text labels and arrows) -- changed the installer to always allow downloading music files (#2891) -- changed the dev console to no longer add duplicate entries to the history -- fixed Lara being killed if she enters the void in a level that uses the `disable_floor` sequence in the game flow (#2874, regression from 0.10) -- fixed flame emitter 23 in room 6 not being deactivated when the lever in room 1 is used (#2851) -- fixed Lara snapping to face forwards if she has a slight angle and action is pressed after using an airlock door (#2215) -- fixed the game crashing on unknown sequencer events -- improved the `/set` console command to display available options if given an unknown argument - -## [1.0.2](https://github.com/LostArtefacts/TRX/compare/tr2-1.0.1...tr2-1.0.2) - 2025-04-26 -- changed The Golden Mask strings to default to the OG strings file for the main tables (#2847) -- fixed Lara voiding if she stops on a tile with a closing door, and the door isn't on a portal (#2848) -- fixed guns carried by enemies not being converted to ammo if Lara has picked up the same gun elsewhere in the same level (#2856) -- fixed button mashing triggering load instead of save on a specific passport animation frame (#2863, regression from 1.0) -- fixed guns carried by enemies not being converted to ammo if Lara starts the level with the gun and the game has later been reloaded (#2850, regression from 1.0) -- fixed 1920x1080 screenshots in 16:9 aspect mode being saved as 1919x1080 (#2845, regression from 0.8) -- fixed clicks in audio sounds (#2846, regression from 0.2) - -## [1.0.1](https://github.com/LostArtefacts/TRX/compare/tr2-1.0...tr2-1.0.1) - 2025-04-24 -- added an option to wraparound when scrolling UI dialogs, such as save/load (#2834) -- changed save to take priority over load when both inputs are held on the same frame, in line with OG (#2833) -- fixed the selected keyboard/controller layout not being saved (#2830, regression from 1.0) -- fixed toggling the PSX FOV option not having an immediate effect (#2831, regression from 1.0) -- fixed changing the aspect ratio not updating the current background image (#2832, regression from 1.0) -- improved graphic settings dialog sizing (#2841) - -## [1.0](https://github.com/LostArtefacts/TRX/compare/tr2-0.10...tr2-1.0) - 2025-04-23 -- added support for The Golden Mask (#1621) -- added ability to turn off legal screen and FMVs (#2740) -- added ability to turn off ingame cutscenes (#2127) -- added HD images from TR2Main (with Arsunt's consent) -- added sunglasses for graphic options (#1615) -- added control over the fog distances for players and level builders (#1622) -- added control over the water color for players and level builders [see the reference](/docs/GAME_FLOW.md#water-color-table) (#1619) -- added an installer for Windows (#2681) -- added the bonus level game flow type, which allows for levels to be unlocked if all main game secrets are found (#2668) -- added the ability for custom levels to have up to two of each secret type per level (#2674) -- added BSON savegame support, removing the limits imposed by the OG 8KB file size, so allowing for storing more data and offering improved feature support (legacy save files can still be read, similar to TR1) (#2662) -- added NG+, Japanese, and Japanese NG+ game mode options to the New Game page in the passport (#2731) -- added the ability for spike walls to be reset (antitriggered) -- added the current music track and timestamp to the savegame so they now persist on load (#2579) -- added waterfalls to the savegame so that they now persist on load (#2686) -- added support for aspect ratio-specific images (#1840) -- added a guard to ensure the game always starts on a visible screen even after unplugging displays (#2819) -- changed savegame files to be stored in the `saves` directory (#2087) -- changed the default fog distance to 22 tiles cutting off at 30 tiles to match TR1X (#1622) -- changed the number of static mesh slots from 50 to 256 (#2734) -- changed the maximum number of items (moveables) per level from 256 to 10240 (1024 remains the limit for triggered items) (#1794) -- changed the maximum number of visible enemies from 5 to 32 (#1624) -- changed the maximum number of effects (flames, embers, exploding parts etc) from 100 to 1000 (#1581) -- changed default pitch of the save/load dialog ingame - it's now higher. -- fixed the inability to completely mute the sounds, even at sound volume 0 (#2722) -- fixed the final two levels not allowing for secrets to be counted in the statistics (#1582) -- fixed assault course best times not being retained between game relaunches (#1578) -- fixed flares disappearing on the ground when the z buffer is enabled (#1595) -- fixed Lara's holsters being empty if a game flow level removes all weapons but also re-adds the pistols (#2677) -- fixed the console opening when remapping its key (#2641) -- fixed the boat when it explodes after crossing mines, where Lara's hips would appear rather than exploded boat parts (#1605) -- fixed Lara's hips appearing on Bartoli in the Temple of Xian cutscene (#2558) -- fixed collision issues with drawbridges, trapdoors, and bridges when stacked over each other, over slopes, and near the ground (#2752) -- fixed the lift to work in any cardinal direction in custom levels, not just South (#2100) -- fixed the springboard not responding correctly when Lara drives across one on a skidoo (#1903) -- fixed the drawbridge producing dynamic light when open (#2294) -- fixed the scale of several pickup models in The Golden Mask (#2652) -- fixed the shark in The Cold War not making any sounds when biting Lara (#2678) -- fixed the bird monster not having a shadow (#2060) -- fixed the in-game cinematic camera at times yielding invalid positions (and hence views) in custom levels (#2754) -- fixed a softlock in Temple of Xian if the main chamber key is missed (#2042) -- fixed a potential softlock in Floating Islands if returning towards the level start from the gold secret (#2590) -- fixed a potential softlock in Nightmare in Vegas where the bird monster could remain inactive, or the flip map not set (#1851) -- fixed invalid portals in The Deck between rooms 17 and 104, which could result in Lara seeing enemies in disconnected rooms (#2393) -- fixed pushblocks being rotated when Lara grabs them, most noticeable if asymmetric textures have been used (#2776) -- fixed the boat briefly having an underwater hue when Lara first climbs on (#2787) -- fixed destroyed gondolas appearing embedded in the ground after loading a save (#1612) -- fixed a crash in custom levels with large rooms (#2749) -- fixed the viewport not always in sync with the window (#2820) -- fixed inability to move the window to another screen (#2820) -- fixed flares flipped to the right when thrown (regression from 0.10) -- fixed the camera going out of bounds in 60fps near specific invalid floor data (known as no-space) (#2764, regression from 0.10) -- fixed sprites rendering black if no shade value is assigned in the level (#2701, regression from 0.8) -- fixed some 3D pickup items rendering black in software mode (#2792, regression from 0.10) -- fixed Lara at times ending up in incorrect rooms when using the teleport cheat (#2486, regression from 0.3) -- fixed the `/pos` console command reporting the base room number when Lara is actually in a flipped room (#2487, regression from 0.3) -- fixed a crash if an image was missing -- fixed a crash on level load if an animation has no frames (#2746, regression from 0.8) -- fixed flares missing the flicker effect in 60 FPS (#2806, regression from 0.10) -- improved performance when moving the window around -- improved pause exit dialog - it can now be canceled with escape -- removed the need to specify in the game flow levels that have no secrets (secrets will be automatically counted) (#1582) -- removed the hard-coded end-level behaviour of the bird guardian for custom levels (#1583) -- removed the FPS and aspect mode options from the config tool (now available in-game in the graphics options) +## [Unreleased](https://github.com/LostArtefacts/TRX/compare/tr2-0.10...develop) - ××××-××-×× ## [0.10](https://github.com/LostArtefacts/TRX/compare/tr2-0.9.2...tr2-0.10) - 2025-03-18 - added support for 60 FPS rendering @@ -132,7 +38,6 @@ - fixed the ammo counter not showing in demos if NG+ is set (#2574, regression from 0.9) - fixed being able to play with Lara invisible after using the explosion cheat then the fly cheat (#2584, regression from 0.9) - fixed the `/pos` command not showing demo and cutscene titles -- fixed the distance travelled stat displaying the wrong value when over 1000m (#2659) - improved camera mode navigation: - improved support for pivoting - improved roll support @@ -384,7 +289,6 @@ - fixed harpoon bolts damaging inactive enemies (#1804) - fixed enemies that are run over by the skidoo not being counted in the statistics (#1772) - fixed sound settings resuming the music (#1707) -- fixed being able to use hotkeys in the end-level statistics screen - fixed the inventory ring spinout animation sometimes running too fast (#1704, regression from 0.3) - fixed new saves not displaying the save count in the passport (#1591) - fixed certain erroneous `/play` invocations resulting in duplicated error messages diff --git a/docs/tr2/README.md b/docs/tr2/README.md index fd4246446..4a2dd5efc 100644 --- a/docs/tr2/README.md +++ b/docs/tr2/README.md @@ -1,5 +1,6 @@

-TR2X logo +TR2X logo +TR2X logo

TR2X is finished with the decompilation and is now able to run without the @@ -8,13 +9,6 @@ game with new enhancements and features. ## Windows / Linux -### Installing (simplified) - -1. Head over to GitHub releases: https://github.com/LostArtefacts/TRX/releases -2. Download the TR2X installer. Your browser may complain that the .exe is unsafe, but it's OK to ignore this alert. -3. Mark the installer EXE as safe to run by right-clicking on the .exe, going to properties and clicking "Unblock". -4. Run the installer and proceed with the steps. - ### Installing (manual) 1. Head over to GitHub releases: https://github.com/LostArtefacts/TRX/releases @@ -22,129 +16,7 @@ game with new enhancements and features. 3. Extract the TR2X zip file into a directory of your choice. Make sure you choose to overwrite existing directories and files. 4. (First time installation) Put your original game files into the target directory. -5. Optionally you can also install the Golden Mask expansion pack files. Extract the contents of the following zip - into the target directory. - https://lostartefacts.dev/aux/tr2x/trgm.zip 5. To play the game, run `TR2X.exe`. -6. To play the Golden Mask expansion pack, run `TR2X.exe -gold`. - -If you install everything correctly, your game directory should look more or -less like this (click to expand): - -
-

* Will not be present until the game has been launched.

-
-.
-├── cfg
-│   ├── TR2X.json5 *
-│   ├── TR2X_gameflow.json5
-│   ├── TR2X_gameflow_gm.json5
-│   ├── TR2X_gameflow_level.json5
-│   ├── TR2X_strings.json5
-│   ├── TR2X_strings_gm.json5
-│   └── TR2X_strings_level.json5
-├── data
-│   ├── assault.tr2
-│   ├── boat.tr2
-│   ├── catacomb.tr2
-│   ├── cut1.tr2
-│   ├── cut2.tr2
-│   ├── cut3.tr2
-│   ├── cut4.tr2
-│   ├── deck.tr2
-│   ├── emprtomb.tr2
-│   ├── floating.tr2
-│   ├── house.tr2
-│   ├── icecave.tr2
-│   ├── keel.tr2
-│   ├── level1.tr2
-│   ├── level2.tr2
-│   ├── level3.tr2
-│   ├── level4.tr2
-│   ├── level5.tr2
-│   ├── living.tr2
-│   ├── main.sfx
-│   ├── main_gm.sfx
-│   ├── monastry.tr2
-│   ├── opera.tr2
-│   ├── platform.tr2
-│   ├── rig.tr2
-│   ├── skidoo.tr2
-│   ├── title.tr2
-│   ├── title_gm.tr2
-│   ├── unwater.tr2
-│   ├── venice.tr2
-│   ├── wall.tr2
-│   ├── xian.tr2
-│   ├── images
-│   │   ├── credit00_gm.png
-│   │   ├── credit01.png
-│   │   ├── credit02.png
-│   │   ├── credit03.png
-│   │   ├── credit04.png
-│   │   ├── credit05.png
-│   │   ├── credit06.png
-│   │   ├── credit07.png
-│   │   ├── credit07_gm.png
-│   │   ├── credit08.png
-│   │   ├── end.png
-│   │   ├── legal.png
-│   │   ├── title_eu.png
-│   │   ├── title_eu_gm.png
-│   │   ├── title_us.png
-│   │   ├── title_us_gm.png
-│   │   └── og
-│   │       ├── credit00_gm.pcx
-│   │       ├── credit01.pcx
-│   │       ├── credit02.pcx
-│   │       ├── credit03.pcx
-│   │       ├── credit04.pcx
-│   │       ├── credit05.pcx
-│   │       ├── credit06.pcx
-│   │       ├── credit07.pcx
-│   │       ├── credit07_gm.pcx
-│   │       ├── credit08.pcx
-│   │       ├── credit09.pcx
-│   │       ├── end.pcx
-│   │       ├── legal.pcx
-│   │       ├── title.pcx
-│   │       ├── title_eu_gm.pcx
-│   │       └── title_us_gm.pcx
-│   └── injections
-│       ├── barkhang_itemrots.bin
-│       ├── barkhang_pickup_meshes.bin
-│       ├── catacombs_fd.bin
-│       └── etc...
-├── fmv
-│   ├── ancient.rpl
-│   ├── crash.rpl
-│   ├── end.rpl
-│   ├── jeep.rpl
-│   ├── landing.rpl
-│   ├── logo.rpl
-│   ├── modern.rpl
-│   └── ms.rpl
-├── music
-│   ├── 2.mp3
-│   ├── 3.mp3
-│   └── etc...
-├── shaders
-│   ├── 2d.glsl
-│   ├── 3d.glsl
-│   ├── common.glsl
-│   ├── fade.glsl
-│   └── fbo.glsl
-├── TR2X.exe
-└── TR2X_ConfigTool.exe
-
-
- -### Configuring - -To configure TR2X, run the `TR2X_ConfigTool.exe` application. All the -configuration is explained in this tool. Alternatively, after running the game -at least once, you can edit `TR2X.json5` manually in a text editor such -as Notepad. ## macOS @@ -156,22 +28,12 @@ as Notepad. 4. Find TR2X in your Applications folder. Right-click it and click "Show Package Contents". 5. Copy your Tomb Raider 2 game data files into `Contents/Resources`. -## Dev snapshots - -To ease the load on our infrastructure, the binary assets such as images and music files are not included in pre-releases and pull request preview builds - they only ship with the full release builds. -However, you can easily download them manually from these urls: - -- https://lostartefacts.dev/aux/tr2x/main.zip (main assets) -- https://lostartefacts.dev/aux/tr2x/music.zip (music files) -- https://lostartefacts.dev/aux/tr2x/trgm.zip (TR: The Golden Mask expansion pack) - ## Improvements over original game #### UI - added support for more accented characters - added fade effects to displayed images - added a wireframe mode -- added sunglasses for graphic options - improved support for windowed mode #### Gameplay @@ -186,12 +48,8 @@ However, you can easily download them manually from these urls: - added support for 60 FPS rendering - added a pause screen - added a photo mode feature -- added combined support for The Golden Mask -- added NG+, Japanese, and Japanese NG+ game mode options to the New Game page in the passport -- added waterfalls to the savegame so that they now persist on load - changed inventory to pause the music rather than muting it - fixed killing the T-Rex with a grenade launcher crashing the game -- fixed assault course best times not being retained between game relaunches - fixed secret rewards not displaying shotgun ammo - fixed numeric keys interfering with the demos - fixed the ammo counter being hidden while a demo plays in NG+ @@ -221,18 +79,14 @@ However, you can easily download them manually from these urls: - fixed the following floor data issues: - **Opera House**: fixed the trigger under item 203 to trigger it rather than item 204 - **Wreck of the Maria Doria**: fixed room 98 not having water - - **Living Quarters** - fixed flame emitter 23 in room 6 not being deactivated when the lever in room 1 is used - - **The Deck**: fixed invalid portals between rooms 17 and 104, which could result in Lara seeing enemies in disconnected rooms - **Tibetan Foothills**: added missing triggers for the drawbridge in room 96 (after the flipmap) - **Catacombs of the Talion**: changed some music triggers to pads near the first yeti, and added missing triggers and ladder in room 116 (after the flipmap) - **Ice Palace**: fixed door 143's position to resolve the invisible wall in front of it, and added an extra pickup trigger beside the Gong Hammer in room 29 - - **Temple of Xian**: fixed missing death tiles in room 91; adding trigger workarounds to avoid a softlock after (missing) the final key - - **Floating Islands**: fixed door 72's position to resolve the invisible wall in front of it; added extra zipline reset triggers to avoid softlock - - **Nightmare in Vegas**: added additional triggers for the bird monster and final flip map to avoid softlock + - **Temple of Xian**: fixed missing death tiles in room 91 + - **Floating Islands**: fixed door 72's position to resolve the invisible wall in front of it - fixed the game crashing if a cinematic is triggered but the level contains no cinematic frames - fixed smashed windows blocking enemy pathing after loading a save - fixed Lara getting stuck in a T-pose after jumping/falling and then dying before reaching fast fall speed -- fixed collision issues with drawbridges, trapdoors, and bridges when stacked over each other, over slopes, and near the ground - fixed several issues with pushblocks: - fixed an invisible wall above stacked pushblocks if near a ceiling portal - fixed floor height issues with pushblocks poised to fall in various scenarios @@ -253,12 +107,6 @@ However, you can easily download them manually from these urls: - increased Barkhang Monastery rooftops key size - increased Temple of Xian dragon seal size, and fixed inventory rotation - fixed Floating Islands mystic plaque inventory rotation -- fixed pushblocks being rotated when Lara grabs them, most noticeable if asymmetric textures have been used -- fixed being able to use hotkeys in the end-level statistics screen -- fixed guns carried by enemies not being converted to ammo if Lara has picked up the same gun elsewhere in the same level -- fixed destroyed gondolas appearing embedded in the ground after loading a save -- fixed Lara voiding if she stops on a tile with a closing door, and the door isn't on a portal -- fixed Lara snapping to face forwards if she has a slight angle and action is pressed after using an airlock door - improved the animation of Lara's braid #### Cheats @@ -280,7 +128,6 @@ However, you can easily download them manually from these urls: #### Statistics - fixed the dragon counting as more than one kill if allowed to revive - fixed enemies that are run over by the skidoo not being counted in the statistics -- fixed the final two levels not allowing for secrets to be counted in the statistics #### Visuals - added quadrilateral texture correction @@ -293,7 +140,6 @@ However, you can easily download them manually from these urls: - added optional fade effects to the hardware renderer - added text information when changing rendering options at runtime - added support for animated sprites -- added the ability for custom levels to have up to two of each secret type per level - changed the hardware renderer to always use 16-bit textures - changed the software renderer to use the picture's palette for the background pictures - changed fullscreen behavior to use windowed desktop mode @@ -304,23 +150,17 @@ However, you can easily download them manually from these urls: - fixed TGA screenshots crashing the game - fixed the camera being cut off after using the gong hammer in Ice Palace - fixed Lara's underwater hue being retained when re-entering a boat -- fixed the boat briefly having an underwater hue when Lara first climbs on - fixed distant rooms sometimes not appearing, causing the skybox to be visible when it shouldn't - fixed rendering problems on certain Intel GPUs - fixed bubbles spawning from flares if Lara is in shallow water - fixed the inventory up arrow at times overlapping the health bar - fixed blood spawning on Lara from gunshots using incorrect positioning data -- fixed the drawbridge producing dynamic light when open -- fixed the boat when it explodes after crossing mines, where Lara's hips would appear rather than exploded boat parts -- fixed Lara's hips appearing on Bartoli in the Temple of Xian cutscene -- fixed the bird monster not having a shadow - improved FMV mode behavior - stopped switching screen resolutions - improved vertex movement when looking through water portals - improved support for non-4:3 aspect ratios #### Audio - added an option to control how music is played while underwater rather than simply muting it -- added the current music track and timestamp to the savegame so they now persist on load - fixed music not playing with certain game versions - fixed the audio not being in sync when Lara strikes the gong in Ice Palace - fixed sound settings resuming the music @@ -333,30 +173,19 @@ However, you can easily download them manually from these urls: #### Mods - added developer console (accessible with `/`, see [COMMANDS.md](COMMANDS.md) for details) - added ability to disable FMVs -- added per-level customizable fog distance -- added the ability for spike walls to be reset (antitriggered) -- fixed the lift to work in any cardinal direction in custom levels, not just South -- fixed the springboard not responding correctly when Lara drives across one on a skidoo -- removed the hard-coded end-level behaviour of the bird guardian for custom levels #### Miscellaneous - added Linux builds - added macOS builds - added .jpeg/.png screenshots -- added BSON savegame support, removing the limits imposed by the OG 8KB file size, so allowing for storing more data and offering improved feature support - added ability to skip FMVs with both the Action key - added ability to skip end credits with the Action and Escape keys - added the ability to specify per-level SFX files rather than enforcing the default (main.sfx) on all levels -- added the ability to define bonus levels in the game flow, which unlock when all main game secrets are found - added -l/--level and -s/--save command line arguments - expanded internal game memory limit from 7.5 MB to unlimited (within system memory cap) - expanded maximum object textures from 2048 to unlimited (within game's overall memory cap) - expanded maximum sprite textures from 512 to unlimited (within game's overall memory cap) - expanded maximum texture pages from 32 to 128 -- expanded the number of static mesh slots from 50 to 256 -- expanded maximum number of items (moveables) from 256 to 10240 (1024 remains the limit for triggered items) -- expanded maximum number of visible enemies from 5 to 32 -- expanded the maximum number of effects (flames, embers, exploding parts etc) from 100 to 1000 - ported audio decoding library to ffmpeg - ported video decoding library to ffmpeg - ported input backend to SDL diff --git a/justfile b/justfile index ec8069c42..485fc5b7f 100644 --- a/justfile +++ b/justfile @@ -38,19 +38,44 @@ _docker_run *args: -v {{CWD}}:/app/ \ {{args}} -image-win force="1": (_docker_build "tools/shared/docker/game-win/Dockerfile" "rrdash/trx-win" force) -image-linux force="1": (_docker_build "tools/shared/docker/game-linux/Dockerfile" "rrdash/trx-linux" force) -image-win-config force="1": (_docker_build "tools/shared/docker/config/Dockerfile" "rrdash/trx-config" force) -image-win-installer force="1": (_docker_build "tools/shared/docker/installer/Dockerfile" "rrdash/trx-installer" force) -push-image-linux: (image-linux "0") (_docker_push "rrdash/trx-linux") -push-image-win: (image-win "0") (_docker_push "rrdash/trx-win") +tr1-image-linux force="1": (_docker_build "tools/tr1/docker/game-linux/Dockerfile" "rrdash/tr1x-linux" force) +tr1-image-win force="1": (_docker_build "tools/tr1/docker/game-win/Dockerfile" "rrdash/tr1x" force) +tr1-image-win-config force="1": (_docker_build "tools/tr1/docker/config/Dockerfile" "rrdash/tr1x-config" force) +tr1-image-win-installer force="1": (_docker_build "tools/tr1/docker/installer/Dockerfile" "rrdash/tr1x-installer" force) -import "justfile.tr1" -import "justfile.tr2" +tr1-push-image-linux: (tr1-image-linux "0") (_docker_push "rrdash/tr1x-linux") +tr1-push-image-win: (tr1-image-win "0") (_docker_push "rrdash/tr1x") -download-assets tr_version='all': - tools/download_assets {{tr_version}} +tr1-build-linux target='debug': (tr1-image-linux "0") (_docker_run "-e" "TARGET="+target "rrdash/tr1x-linux") +tr1-build-win target='debug': (tr1-image-win "0") (_docker_run "-e" "TARGET="+target "rrdash/tr1x") +tr1-build-win-config: (tr1-image-win-config "0") (_docker_run "rrdash/tr1x-config") +tr1-build-win-installer: (tr1-image-win-installer "0") (_docker_run "rrdash/tr1x-installer") + +tr1-package-linux target='release': (tr1-build-linux target) (_docker_run "rrdash/tr1x-linux" "package") +tr1-package-win target='release': (tr1-build-win target) (_docker_run "rrdash/tr1x" "package") +tr1-package-win-all target='release': (tr1-build-win target) (tr1-build-win-config) (_docker_run "rrdash/tr1x" "package") +tr1-package-win-installer target='release': (tr1-build-win target) (tr1-build-win-config) (_docker_run "rrdash/tr1x" "package" "-o" "tools/tr1/installer/Installer/Resources/release.zip") (tr1-build-win-installer) + #!/bin/sh + git checkout "tools/tr1/installer/Installer/Resources/release.zip" + exe_name=TR1X-$(tools/get_version 1)-Installer.exe + cp tools/tr1/installer/out/TR1X_Installer.exe "${exe_name}" + echo "Created ${exe_name}" + +tr2-image-linux force="1": (_docker_build "tools/tr2/docker/game-linux/Dockerfile" "rrdash/tr2x-linux" force) +tr2-image-win force="1": (_docker_build "tools/tr2/docker/game-win/Dockerfile" "rrdash/tr2x" force) +tr2-image-win-config force="1": (_docker_build "tools/tr2/docker/config/Dockerfile" "rrdash/tr2x-config" force) + +tr2-push-image-linux: (tr2-image-linux "0") (_docker_push "rrdash/tr2x-linux") +tr2-push-image-win: (tr2-image-win "0") (_docker_push "rrdash/tr2x") + +tr2-build-linux target='debug': (tr2-image-linux "0") (_docker_run "-e" "TARGET="+target "rrdash/tr2x-linux") +tr2-build-win target='debug': (tr2-image-win "0") (_docker_run "-e" "TARGET="+target "rrdash/tr2x") +tr2-build-win-config: (tr2-image-win-config "0") (_docker_run "rrdash/tr2x-config") + +tr2-package-linux target='release': (tr2-build-linux target) (_docker_run "rrdash/tr2x-linux" "package") +tr2-package-win target='release': (tr2-build-win target) (_docker_run "rrdash/tr2x" "package") +tr2-package-win-all target='release': (tr2-build-win target) (tr2-build-win-config) (_docker_run "rrdash/tr2x" "package") output-release-name tr_version: tools/output_release_name {{tr_version}} @@ -66,13 +91,10 @@ clean: -find tools/ -type f \( -ipath '*/out/*' -or -ipath '*/bin/*' -or -ipath '*/obj/*' \) -delete -find . -mindepth 1 -empty -type d -delete -[group('lint')] lint-imports: tools/sort_imports -[group('lint')] lint-format: pre-commit run -a -[group('lint')] lint: (lint-imports) (lint-format) diff --git a/justfile.tr1 b/justfile.tr1 deleted file mode 100644 index a0f58d403..000000000 --- a/justfile.tr1 +++ /dev/null @@ -1,27 +0,0 @@ -[group('tr1')] -tr1-build-linux target='debug': (image-linux "0") (_docker_run "rrdash/trx-linux" "build" "--tr-version" "1" "--target" target) -[group('tr1')] -tr1-build-win target='debug': (image-win "0") (_docker_run "rrdash/trx-win" "build" "--tr-version" "1" "--target" target) -[group('tr1')] -tr1-build-win-config: (image-win-config "0") (_docker_run "rrdash/trx-config" "1") -[group('tr1')] -tr1-build-win-installer: (image-win-installer "0") (_docker_run "rrdash/trx-installer" "1") - -[group('tr1')] -tr1-package-linux target='release': (tr1-build-linux target) (_docker_run "rrdash/trx-linux" "package" "--tr-version" "1") -[group('tr1')] -tr1-package-win target='release': (tr1-build-win target) (_docker_run "rrdash/trx-win" "package" "--tr-version" "1") -[group('tr1')] -tr1-package-win-all target='release': (tr1-build-win target) (tr1-build-win-config) (_docker_run "rrdash/trx-win" "package" "--tr-version" "1") - -[group('tr1')] -tr1-package-win-installer target='release': \ - (tr1-build-win target) \ - (tr1-build-win-config) \ - (_docker_run "rrdash/trx-win" "package" "--tr-version" "1" "-o" "tools/installer/TR1X_Installer/Resources/release.zip") \ - (tr1-build-win-installer) - #!/bin/sh - git checkout "tools/installer/TR1X_Installer/Resources/release.zip" - exe_name=TR1X-$(tools/get_version 1)-Installer.exe - cp tools/installer/out/TR1X_Installer.exe "${exe_name}" - echo "Created ${exe_name}" diff --git a/justfile.tr2 b/justfile.tr2 deleted file mode 100644 index 6b28dff3f..000000000 --- a/justfile.tr2 +++ /dev/null @@ -1,25 +0,0 @@ -[group('tr2')] -tr2-build-linux target='debug': (image-linux "0") (_docker_run "rrdash/trx-linux" "build" "--tr-version" "2" "--target" target) -[group('tr2')] -tr2-build-win target='debug': (image-win "0") (_docker_run "rrdash/trx-win" "build" "--tr-version" "2" "--target" target) -[group('tr2')] -tr2-build-win-config: (image-win-config "0") (_docker_run "rrdash/trx-config" "2") -[group('tr2')] -tr2-build-win-installer: (image-win-installer "0") (_docker_run "rrdash/trx-installer" "2") - -[group('tr2')] -tr2-package-linux target='release': (tr2-build-linux target) (_docker_run "rrdash/trx-linux" "package" "--tr-version" "2") -[group('tr2')] -tr2-package-win target='release': (tr2-build-win target) (_docker_run "rrdash/trx-win" "package" "--tr-version" "2") -[group('tr2')] -tr2-package-win-all target='release': (tr2-build-win target) (tr2-build-win-config) (_docker_run "rrdash/trx-win" "package" "--tr-version" "2") -[group('tr2')] -tr2-package-win-installer target='release': \ - (tr2-build-win target) \ - (tr2-build-win-config) \ - (_docker_run "rrdash/trx-win" "package" "--tr-version" "2" "-o" "tools/installer/TR2X_Installer/Resources/release.zip") (tr2-build-win-installer) - #!/bin/sh - git checkout "tools/installer/TR2X_Installer/Resources/release.zip" - exe_name=TR2X-$(tools/get_version 2)-Installer.exe - cp tools/installer/out/TR2X_Installer.exe "${exe_name}" - echo "Created ${exe_name}" diff --git a/src/libtrx/benchmark.c b/src/libtrx/benchmark.c index f3226749e..cdc1e0bb6 100644 --- a/src/libtrx/benchmark.c +++ b/src/libtrx/benchmark.c @@ -6,7 +6,7 @@ static void M_Log( BENCHMARK *const b, const char *file, int32_t line, const char *func, - Uint64 current, const char *message, const bool closing) + Uint64 current, const char *message) { const Uint64 freq = SDL_GetPerformanceFrequency(); const double elapsed_start = @@ -14,14 +14,7 @@ static void M_Log( const double elapsed_last = (double)(current - b->last) * 1000.0 / (double)freq; - if (closing) { - if (message == nullptr) { - Log_Message(file, line, func, "took %.02f ms", elapsed_start); - } else { - Log_Message( - file, line, func, "%s: took %.02f ms", message, elapsed_start); - } - } else { + if (b->last != b->start) { if (message == nullptr) { Log_Message( file, line, func, "took %.02f ms (%.02f ms)", elapsed_start, @@ -31,6 +24,14 @@ static void M_Log( file, line, func, "%s: took %.02f ms (%.02f ms)", message, elapsed_start, elapsed_last); } + } else { + if (message == nullptr) { + Log_Message(file, line, func, "took %.02f ms", elapsed_start); + } else { + Log_Message( + file, line, func, "%s: took %.02f ms (%.02f ms)", message, + elapsed_start); + } } } @@ -48,7 +49,7 @@ void Benchmark_Tick_Impl( const char *const func, const char *const message) { const Uint64 current = SDL_GetPerformanceCounter(); - M_Log(b, file, line, func, current, message, false); + M_Log(b, file, line, func, current, message); b->last = current; } @@ -56,6 +57,5 @@ void Benchmark_End_Impl( BENCHMARK *b, const char *const file, const int32_t line, const char *const func, const char *const message) { - const Uint64 current = SDL_GetPerformanceCounter(); - M_Log(b, file, line, func, current, message, true); + Benchmark_Tick_Impl(b, file, line, func, message); } diff --git a/src/libtrx/config/file.c b/src/libtrx/config/file.c index 29a83e785..c9fc5c3d9 100644 --- a/src/libtrx/config/file.c +++ b/src/libtrx/config/file.c @@ -1,6 +1,5 @@ #include "config/file.h" -#include "colors.h" #include "debug.h" #include "filesystem.h" #include "game/console/history.h" @@ -8,7 +7,6 @@ #include "memory.h" #include "strings.h" -#include #include #define EMPTY_ROOT "{}" @@ -246,32 +244,6 @@ void ConfigFile_LoadOptions(JSON_OBJECT *root_obj, const CONFIG_OPTION *options) *(int *)opt->target = ConfigFile_ReadEnum( root_obj, M_ResolveOptionName(opt->name), *(int *)opt->default_value, opt->param); - break; - - case COT_RGB888: { - RGB_888 *const target = (RGB_888 *)opt->target; - JSON_VALUE *const value = - JSON_ObjectGetValue(root_obj, M_ResolveOptionName(opt->name)); - bool success = false; - if (value != nullptr && value->type == JSON_TYPE_NUMBER) { - const uint32_t rgb_value = - JSON_ValueGetInt(value, JSON_INVALID_NUMBER); - ASSERT(rgb_value != JSON_INVALID_NUMBER); - target->r = (rgb_value >> 0) & 0xFF; - target->g = (rgb_value >> 8) & 0xFF; - target->b = (rgb_value >> 16) & 0xFF; - success = true; - } else if (value != nullptr && value->type == JSON_TYPE_STRING) { - const char *str_value = - JSON_ValueGetString(value, JSON_INVALID_STRING); - ASSERT(str_value != JSON_INVALID_STRING); - success = String_ParseRGB888(str_value, target); - } - if (!success) { - *(RGB_888 *)opt->target = *(RGB_888 *)opt->default_value; - } - break; - } } opt++; } @@ -310,15 +282,6 @@ void ConfigFile_DumpOptions(JSON_OBJECT *root_obj, const CONFIG_OPTION *options) root_obj, M_ResolveOptionName(opt->name), *(int *)opt->target, (const char *)opt->param); break; - - case COT_RGB888: { - const RGB_888 *const color = (RGB_888 *)opt->target; - char tmp[10]; - sprintf(tmp, "#%02X%02X%02X", color->r, color->g, color->b); - JSON_ObjectAppendString( - root_obj, M_ResolveOptionName(opt->name), tmp); - break; - } } opt++; } @@ -340,49 +303,3 @@ void ConfigFile_WriteEnum( { JSON_ObjectAppendString(obj, name, EnumMap_ToString(enum_name, value)); } - -bool ConfigFile_LoadAssaultStats( - JSON_OBJECT *const root_obj, ASSAULT_STATS *const assault_stats) -{ - JSON_OBJECT *const stats_obj = - JSON_ObjectGetObject(root_obj, "assault_stats"); - if (stats_obj == nullptr) { - return false; - } - JSON_ARRAY *const entries_arr = JSON_ObjectGetArray(stats_obj, "entries"); - if (entries_arr != nullptr) { - for (size_t i = 0; i < entries_arr->length && i < MAX_ASSAULT_TIMES; - i++) { - JSON_OBJECT *const entry_obj = JSON_ArrayGetObject(entries_arr, i); - if (entry_obj != nullptr) { - assault_stats->entries[i].time = JSON_ObjectGetInt( - entry_obj, "time", assault_stats->entries[i].time); - assault_stats->entries[i].attempt_num = JSON_ObjectGetInt( - entry_obj, "attempt_num", - assault_stats->entries[i].attempt_num); - } - } - } - assault_stats->total_attempts = JSON_ObjectGetInt( - stats_obj, "total_attempts", assault_stats->total_attempts); - return true; -} - -bool ConfigFile_DumpAssaultStats( - JSON_OBJECT *const root_obj, const ASSAULT_STATS *const assault_stats) -{ - JSON_OBJECT *const stats_obj = JSON_ObjectNew(); - JSON_ARRAY *const entries_arr = JSON_ArrayNew(); - for (int i = 0; i < MAX_ASSAULT_TIMES; i++) { - JSON_OBJECT *const entry_obj = JSON_ObjectNew(); - JSON_ObjectAppendInt(entry_obj, "time", assault_stats->entries[i].time); - JSON_ObjectAppendInt( - entry_obj, "attempt_num", assault_stats->entries[i].attempt_num); - JSON_ArrayAppendObject(entries_arr, entry_obj); - } - JSON_ObjectAppendArray(stats_obj, "entries", entries_arr); - JSON_ObjectAppendInt( - stats_obj, "total_attempts", assault_stats->total_attempts); - JSON_ObjectAppendObject(root_obj, "assault_stats", stats_obj); - return true; -} diff --git a/src/libtrx/config/file.h b/src/libtrx/config/file.h index b6f0819ea..7da1e873f 100644 --- a/src/libtrx/config/file.h +++ b/src/libtrx/config/file.h @@ -2,7 +2,6 @@ #include "config/option.h" #include "enum_map.h" -#include "game/gym.h" #include "json.h" #include @@ -26,8 +25,3 @@ int ConfigFile_ReadEnum( const char *enum_name); void ConfigFile_WriteEnum( JSON_OBJECT *obj, const char *name, int value, const char *enum_name); - -bool ConfigFile_LoadAssaultStats( - JSON_OBJECT *root_obj, ASSAULT_STATS *assault_stats); -bool ConfigFile_DumpAssaultStats( - JSON_OBJECT *root_obj, const ASSAULT_STATS *assault_stats); diff --git a/src/libtrx/config/map.c b/src/libtrx/config/map.c index ca43ff0d6..25c1c9e16 100644 --- a/src/libtrx/config/map.c +++ b/src/libtrx/config/map.c @@ -1,4 +1,3 @@ -#include "colors.h" #include "config/option.h" #include "config/types.h" #include "config/vars.h" @@ -40,13 +39,6 @@ .default_value = &(int32_t) { default_value_ }, \ .param = ENUM_MAP_NAME(enum_map) }, -#define CFG_RGB888(parent, target_, default_r, default_g, default_b) \ - { .name = QUOTE(target_), \ - .type = COT_RGB888, \ - .target = &parent.target_, \ - .default_value = &(RGB_888) { default_r, default_g, default_b }, \ - .param = nullptr }, - static const CONFIG_OPTION m_ConfigOptionMap[] = { #include "map.def" {}, // sentinel diff --git a/src/libtrx/config/map_tr1.def b/src/libtrx/config/map_tr1.def index 0d3219eeb..7348f00ad 100644 --- a/src/libtrx/config/map_tr1.def +++ b/src/libtrx/config/map_tr1.def @@ -3,12 +3,9 @@ CFG_BOOL(g_Config, gameplay.disable_medpacks, false) CFG_BOOL(g_Config, gameplay.disable_magnums, false) CFG_BOOL(g_Config, gameplay.disable_uzis, false) CFG_BOOL(g_Config, gameplay.disable_shotgun, false) -CFG_ENUM(g_Config, gameplay.stat_detail_mode, SDM_FULL, STAT_DETAIL_MODE) +CFG_BOOL(g_Config, gameplay.enable_detailed_stats, true) CFG_BOOL(g_Config, gameplay.enable_deaths_counter, true) CFG_BOOL(g_Config, gameplay.enable_enhanced_look, true) -CFG_INT32(g_Config, visuals.fog_start, 22) -CFG_INT32(g_Config, visuals.fog_end, 30) -CFG_RGB888(g_Config, visuals.water_color, 0x72, 0xFF, 0xFF) CFG_BOOL(g_Config, visuals.enable_gun_lighting, true) CFG_BOOL(g_Config, visuals.enable_shotgun_flash, true) CFG_BOOL(g_Config, gameplay.fix_shotgun_targeting, true) @@ -42,9 +39,9 @@ CFG_INT32(g_Config, rendering.resolution_width, -1) CFG_INT32(g_Config, rendering.resolution_height, -1) CFG_BOOL(g_Config, gameplay.enable_demo, true) CFG_BOOL(g_Config, gameplay.enable_fmv, true) -CFG_BOOL(g_Config, gameplay.enable_legal, true) +CFG_BOOL(g_Config, gameplay.enable_eidos_logo, true) CFG_BOOL(g_Config, gameplay.enable_loading_screens, false) -CFG_BOOL(g_Config, gameplay.enable_cutscenes, true) +CFG_BOOL(g_Config, gameplay.enable_cine, true) CFG_BOOL(g_Config, audio.enable_music_in_menu, true) CFG_BOOL(g_Config, audio.enable_music_in_inventory, true) CFG_ENUM(g_Config, audio.underwater_music_mode, UMM_FULL, UNDERWATER_MUSIC_MODE) @@ -108,6 +105,7 @@ CFG_BOOL(g_Config, rendering.enable_wireframe, false) CFG_DOUBLE(g_Config, rendering.wireframe_width, 2.5) CFG_BOOL(g_Config, rendering.enable_trapezoid_filter, true) CFG_BOOL(g_Config, rendering.enable_vsync, true) +CFG_BOOL(g_Config, rendering.pretty_pixels, true) CFG_BOOL(g_Config, visuals.enable_reflections, true) CFG_INT32(g_Config, audio.music_volume, 8) CFG_INT32(g_Config, audio.sound_volume, 8) @@ -122,7 +120,6 @@ CFG_BOOL(g_Config, visuals.enable_skybox, true) CFG_BOOL(g_Config, visuals.enable_ps1_crystals, true) CFG_BOOL(g_Config, ui.enable_game_ui, true) CFG_BOOL(g_Config, ui.enable_photo_mode_ui, true) -CFG_BOOL(g_Config, ui.enable_wraparound, true) CFG_BOOL(g_Config, gameplay.enable_item_examining, true) CFG_BOOL(g_Config, gameplay.enable_auto_item_selection, true) CFG_BOOL(g_Config, gameplay.enable_pickup_aids, false) diff --git a/src/libtrx/config/map_tr2.def b/src/libtrx/config/map_tr2.def index bdb48896f..e06f170df 100644 --- a/src/libtrx/config/map_tr2.def +++ b/src/libtrx/config/map_tr2.def @@ -7,13 +7,9 @@ CFG_BOOL(g_Config, gameplay.fix_pickup_drift_glitch, false) CFG_BOOL(g_Config, gameplay.fix_floor_data_issues, true) CFG_BOOL(g_Config, gameplay.fix_flare_throw_priority, true) CFG_BOOL(g_Config, gameplay.fix_walk_run_jump, true) -CFG_BOOL(g_Config, gameplay.fix_bear_ai, true) -CFG_BOOL(g_Config, gameplay.fix_bridge_collision, true) CFG_BOOL(g_Config, gameplay.enable_cheats, false) CFG_BOOL(g_Config, gameplay.enable_console, true) CFG_BOOL(g_Config, gameplay.enable_fmv, true) -CFG_BOOL(g_Config, gameplay.enable_legal, true) -CFG_BOOL(g_Config, gameplay.enable_cutscenes, true) CFG_BOOL(g_Config, input.enable_tr3_sidesteps, true) CFG_BOOL(g_Config, input.enable_responsive_passport, true) CFG_BOOL(g_Config, gameplay.enable_auto_item_selection, true) @@ -23,16 +19,11 @@ CFG_BOOL(g_Config, visuals.enable_gun_lighting, true) CFG_BOOL(g_Config, visuals.enable_fade_effects, true) CFG_BOOL(g_Config, visuals.enable_exit_fade_effects, true) CFG_BOOL(g_Config, visuals.fix_item_rots, true) -CFG_BOOL(g_Config, visuals.fix_texture_issues, true) CFG_INT32(g_Config, visuals.fov, 80) -CFG_BOOL(g_Config, visuals.use_psx_fov, true) -CFG_INT32(g_Config, visuals.fog_start, 22) -CFG_INT32(g_Config, visuals.fog_end, 30) -CFG_RGB888(g_Config, visuals.water_color, 0x80, 0xDF, 0xFF) +CFG_BOOL(g_Config, visuals.use_pcx_fov, true) CFG_DOUBLE(g_Config, ui.text_scale, 1.0) CFG_DOUBLE(g_Config, ui.bar_scale, 1.0) CFG_BOOL(g_Config, ui.enable_photo_mode_ui, true) -CFG_BOOL(g_Config, ui.enable_wraparound, true) CFG_INT32(g_Config, rendering.fps, 60) CFG_ENUM(g_Config, rendering.screenshot_format, SCREENSHOT_FORMAT_JPEG, SCREENSHOT_FORMAT) CFG_ENUM(g_Config, rendering.render_mode, RM_HARDWARE, RENDER_MODE) @@ -61,6 +52,3 @@ CFG_INT32(g_Config, audio.sound_volume, 10) CFG_INT32(g_Config, audio.music_volume, 10) CFG_BOOL(g_Config, audio.enable_lara_mic, false) CFG_ENUM(g_Config, audio.underwater_music_mode, UMM_FULL, UNDERWATER_MUSIC_MODE) -CFG_ENUM(g_Config, audio.music_load_condition, MUSIC_LOAD_NON_AMBIENT, MUSIC_LOAD_CONDITION) -CFG_BOOL(g_Config, gameplay.enable_game_modes, true) -CFG_BOOL(g_Config, profile.new_game_plus_unlock, false) diff --git a/src/libtrx/config/priv_tr1.c b/src/libtrx/config/priv_tr1.c index 34b6eac6f..db4833581 100644 --- a/src/libtrx/config/priv_tr1.c +++ b/src/libtrx/config/priv_tr1.c @@ -117,10 +117,6 @@ static void M_LoadLegacyOptions(JSON_OBJECT *const parent_obj) READ_FALLBACK_INT(g_Config.window.height, "window_height"); READ_FALLBACK_INT(g_Config.input.keyboard_layout, "layout"); READ_FALLBACK_INT(g_Config.input.controller_layout, "cntlr_layout"); - - // ..4.9 - READ_FALLBACK_BOOL(g_Config.gameplay.enable_cutscenes, "enable_cine"); - READ_FALLBACK_BOOL(g_Config.gameplay.enable_legal, "enable_eidos_logo"); } static void M_DumpKeyboardLayout( @@ -178,9 +174,6 @@ static void M_DumpControllerLayout( void Config_LoadFromJSON(JSON_OBJECT *root_obj) { ConfigFile_LoadOptions(root_obj, Config_GetOptionMap()); - if (Gym_HasAssaultStats()) { - ConfigFile_LoadAssaultStats(root_obj, &g_Config.profile.assault_stats); - } for (INPUT_LAYOUT layout = INPUT_LAYOUT_CUSTOM_1; layout < INPUT_LAYOUT_NUMBER_OF; layout++) { @@ -189,15 +182,13 @@ void Config_LoadFromJSON(JSON_OBJECT *root_obj) } M_LoadLegacyOptions(root_obj); + g_Config.loaded = true; } void Config_DumpToJSON(JSON_OBJECT *root_obj) { ConfigFile_DumpOptions(root_obj, Config_GetOptionMap()); - if (Gym_HasAssaultStats()) { - ConfigFile_DumpAssaultStats(root_obj, &g_Config.profile.assault_stats); - } for (INPUT_LAYOUT layout = INPUT_LAYOUT_CUSTOM_1; layout < INPUT_LAYOUT_NUMBER_OF; layout++) { @@ -213,8 +204,6 @@ void Config_DumpToJSON(JSON_OBJECT *root_obj) void Config_Sanitize(void) { CLAMP(g_Config.gameplay.start_lara_hitpoints, 1, LARA_MAX_HITPOINTS); - CLAMP(g_Config.visuals.fog_start, 1, 100); - CLAMP(g_Config.visuals.fog_end, 1, 100); CLAMP(g_Config.visuals.fov_value, 30, 150); CLAMP(g_Config.gameplay.camera_speed, 1, 10); CLAMP(g_Config.audio.music_volume, 0, 10); diff --git a/src/libtrx/config/priv_tr2.c b/src/libtrx/config/priv_tr2.c index c5c9eea96..19953ccaf 100644 --- a/src/libtrx/config/priv_tr2.c +++ b/src/libtrx/config/priv_tr2.c @@ -4,7 +4,6 @@ #include "config/vars.h" #include "debug.h" #include "game/clock.h" -#include "game/gym.h" #include "game/input.h" #include "log.h" #include "utils.h" @@ -17,7 +16,6 @@ static void M_LoadInputLayout( static void M_DumpInputConfig(JSON_OBJECT *root_obj); static void M_DumpInputLayout( JSON_OBJECT *parent_obj, INPUT_BACKEND backend, INPUT_LAYOUT layout); -static void M_LoadLegacyOptions(JSON_OBJECT *const parent_obj); static void M_LoadInputConfig(JSON_OBJECT *const root_obj) { @@ -100,23 +98,10 @@ static void M_DumpInputLayout( } } -static void M_LoadLegacyOptions(JSON_OBJECT *const parent_obj) -{ -#define READ_FALLBACK_BOOL(target, key) \ - target = JSON_ObjectGetBool(parent_obj, key, target) - - // ..0.10 - READ_FALLBACK_BOOL(g_Config.visuals.use_psx_fov, "use_pcx_fov"); -} - void Config_LoadFromJSON(JSON_OBJECT *root_obj) { ConfigFile_LoadOptions(root_obj, Config_GetOptionMap()); - if (Gym_HasAssaultStats()) { - ConfigFile_LoadAssaultStats(root_obj, &g_Config.profile.assault_stats); - } M_LoadInputConfig(root_obj); - M_LoadLegacyOptions(root_obj); g_Config.loaded = true; g_SavedConfig = g_Config; } @@ -124,9 +109,6 @@ void Config_LoadFromJSON(JSON_OBJECT *root_obj) void Config_DumpToJSON(JSON_OBJECT *root_obj) { ConfigFile_DumpOptions(root_obj, Config_GetOptionMap()); - if (Gym_HasAssaultStats()) { - ConfigFile_DumpAssaultStats(root_obj, &g_Config.profile.assault_stats); - } M_DumpInputConfig(root_obj); } @@ -155,8 +137,6 @@ void Config_Sanitize(void) g_Config.rendering.fps = 30; } - CLAMP(g_Config.visuals.fog_start, 1, 100); - CLAMP(g_Config.visuals.fog_end, 1, 100); CLAMP(g_Config.visuals.fov, 30, 150); CLAMP(g_Config.ui.bar_scale, 0.5, 2.0); CLAMP(g_Config.ui.text_scale, 0.5, 2.0); diff --git a/src/libtrx/engine/audio_sample.c b/src/libtrx/engine/audio_sample.c index 5dee0147b..0d3a4afb8 100644 --- a/src/libtrx/engine/audio_sample.c +++ b/src/libtrx/engine/audio_sample.c @@ -1,6 +1,5 @@ #include "audio_internal.h" -#include "benchmark.h" #include "debug.h" #include "log.h" #include "memory.h" @@ -22,21 +21,12 @@ #include #include #include - -typedef struct { - struct { - int32_t format; - AVChannelLayout ch_layout; - int32_t sample_rate; - } src, dst; - SwrContext *ctx; - size_t working_buffer_size; - uint8_t *working_buffer; -} M_SWR_CONTEXT; +#include typedef struct { char *original_data; size_t original_size; + float *sample_data; int32_t channels; int32_t num_samples; @@ -60,8 +50,8 @@ typedef struct { } AUDIO_SAMPLE_SOUND; typedef struct { - const uint8_t *data; - const uint8_t *ptr; + const char *data; + const char *ptr; int32_t size; int32_t remaining; } AUDIO_AV_BUFFER; @@ -74,7 +64,7 @@ static double M_DecibelToMultiplier(double db_gain); static bool M_RecalculateChannelVolumes(int32_t sound_id); static int32_t M_ReadAVBuffer(void *opaque, uint8_t *dst, int32_t dst_size); static int64_t M_SeekAVBuffer(void *opaque, int64_t offset, int32_t whence); -static bool M_ConvertSample(const int32_t sample_id); +static bool M_Convert(const int32_t sample_id); static double M_DecibelToMultiplier(double db_gain) { @@ -145,98 +135,20 @@ static int64_t M_SeekAVBuffer(void *opaque, int64_t offset, int32_t whence) return src->ptr - src->data; } -static int32_t M_OutputAudioFrame( - M_SWR_CONTEXT *const swr, AVFrame *const frame) +static bool M_Convert(const int32_t sample_id) { - // Determine the maximum number of output samples this call can produce, - // based on the current delay already inside the resampler plus the new - // input. Using av_rescale_rnd() keeps everything in integer domain and - // avoids cumulative rounding errors. - const int64_t delay = swr_get_delay(swr->ctx, swr->src.sample_rate); - const int32_t out_samples = (int32_t)av_rescale_rnd( - delay + frame->nb_samples, swr->dst.sample_rate, swr->src.sample_rate, - AV_ROUND_UP); - if (out_samples <= 0) { - return 0; // nothing to do - } + ASSERT(sample_id >= 0 && sample_id < m_LoadedSamplesCount); - uint8_t *out_buffer = nullptr; - if (av_samples_alloc( - &out_buffer, nullptr, swr->dst.ch_layout.nb_channels, out_samples, - swr->dst.format, 1) - < 0) { - return AVERROR(ENOMEM); - } - - // Convert – we do *not* drain the resampler here. - const int32_t converted = swr_convert( - swr->ctx, &out_buffer, out_samples, (const uint8_t **)frame->data, - frame->nb_samples); - - if (converted < 0) { - av_freep(&out_buffer); - return converted; // propagate error - } - - if (converted > 0) { - const int32_t out_buffer_size = av_samples_get_buffer_size( - nullptr, swr->dst.ch_layout.nb_channels, converted, swr->dst.format, - 1); - if (out_buffer_size > 0) { - swr->working_buffer = Memory_Realloc( - swr->working_buffer, - swr->working_buffer_size + out_buffer_size); - memcpy( - swr->working_buffer + swr->working_buffer_size, out_buffer, - out_buffer_size); - swr->working_buffer_size += out_buffer_size; - } - } - - av_freep(&out_buffer); - return 0; -} - -static int32_t M_DecodePacket( - AVCodecContext *const dec, const AVPacket *const pkt, AVFrame *frame, - M_SWR_CONTEXT *const swr) -{ - // Submit the packet to the decoder - int32_t ret = avcodec_send_packet(dec, pkt); - if (ret < 0) { - LOG_ERROR( - "Error submitting a packet for decoding (%s)\n", av_err2str(ret)); - return ret; - } - - // Get all the available frames from the decoder - while (ret >= 0) { - ret = avcodec_receive_frame(dec, frame); - if (ret < 0) { - // those two return values are special and mean there is no output - // frame available, but there were no errors during decoding - if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) { - return 0; - } - LOG_ERROR( - "Error receiving a frame for decoding (%s)\n", av_err2str(ret)); - return ret; - } - - ret = M_OutputAudioFrame(swr, frame); - av_frame_unref(frame); - } - - return ret; -} - -static bool M_ConvertRawData( - const uint8_t *const original_data, const int32_t original_size, - const int32_t dst_sample_rate, const int32_t dst_format, - const int32_t dst_channel_count, uint8_t **const out_sample_data, - size_t *const out_size, size_t *const out_sample_count) -{ bool result = false; + AUDIO_SAMPLE *const sample = &m_LoadedSamples[sample_id]; + + if (sample->sample_data != nullptr) { + return true; + } + + const clock_t time_start = clock(); + size_t working_buffer_size = 0; + float *working_buffer = nullptr; struct { size_t read_buffer_size; @@ -258,20 +170,28 @@ static bool M_ConvertRawData( .frame = nullptr, }; - M_SWR_CONTEXT swr = {}; + struct { + struct { + int32_t format; + AVChannelLayout ch_layout; + int32_t sample_rate; + } src, dst; + SwrContext *ctx; + } swr = {}; + int32_t error_code; - uint8_t *const read_buffer = av_malloc(av.read_buffer_size); - if (read_buffer == nullptr) { + unsigned char *read_buffer = av_malloc(av.read_buffer_size); + if (!read_buffer) { error_code = AVERROR(ENOMEM); goto cleanup; } AUDIO_AV_BUFFER av_buf = { - .data = original_data, - .ptr = original_data, - .size = original_size, - .remaining = original_size, + .data = sample->original_data, + .ptr = sample->original_data, + .size = sample->original_size, + .remaining = sample->original_size, }; av.avio_context = avio_alloc_context( @@ -280,7 +200,8 @@ static bool M_ConvertRawData( av.format_ctx = avformat_alloc_context(); av.format_ctx->pb = av.avio_context; - error_code = avformat_open_input(&av.format_ctx, "mem:", nullptr, nullptr); + error_code = + avformat_open_input(&av.format_ctx, "dummy_filename", nullptr, nullptr); if (error_code != 0) { goto cleanup; } @@ -298,19 +219,19 @@ static bool M_ConvertRawData( break; } } - if (av.stream == nullptr) { + if (!av.stream) { error_code = AVERROR_STREAM_NOT_FOUND; goto cleanup; } av.codec = avcodec_find_decoder(av.stream->codecpar->codec_id); - if (av.codec == nullptr) { + if (!av.codec) { error_code = AVERROR_DEMUXER_NOT_FOUND; goto cleanup; } av.codec_ctx = avcodec_alloc_context3(av.codec); - if (av.codec_ctx == nullptr) { + if (!av.codec_ctx) { error_code = AVERROR(ENOMEM); goto cleanup; } @@ -327,134 +248,166 @@ static bool M_ConvertRawData( } av.packet = av_packet_alloc(); - if (av.packet == nullptr) { + if (!av.packet) { error_code = AVERROR(ENOMEM); goto cleanup; } av.frame = av_frame_alloc(); - if (av.frame == nullptr) { + if (!av.frame) { error_code = AVERROR(ENOMEM); goto cleanup; } - swr.src.sample_rate = av.codec_ctx->sample_rate; - swr.src.ch_layout = av.codec_ctx->ch_layout; - swr.src.format = av.codec_ctx->sample_fmt; - swr.dst.sample_rate = AUDIO_WORKING_RATE; - av_channel_layout_default(&swr.dst.ch_layout, dst_channel_count); - swr.dst.format = Audio_GetAVAudioFormat(AUDIO_WORKING_FORMAT); - swr_alloc_set_opts2( - &swr.ctx, &swr.dst.ch_layout, swr.dst.format, swr.dst.sample_rate, - &swr.src.ch_layout, swr.src.format, swr.src.sample_rate, 0, 0); - if (swr.ctx == nullptr) { - av_packet_unref(av.packet); - error_code = AVERROR(ENOMEM); - goto cleanup; - } - - error_code = swr_init(swr.ctx); - if (error_code != 0) { - av_packet_unref(av.packet); - goto cleanup; - } - - while ((error_code = av_read_frame(av.format_ctx, av.packet)) >= 0) { - M_DecodePacket(av.codec_ctx, av.packet, av.frame, &swr); - av_packet_unref(av.packet); - if (error_code < 0) { + while (1) { + error_code = av_read_frame(av.format_ctx, av.packet); + if (error_code == AVERROR_EOF) { + av_packet_unref(av.packet); + error_code = 0; break; } + + if (error_code < 0) { + av_packet_unref(av.packet); + goto cleanup; + } + + error_code = avcodec_send_packet(av.codec_ctx, av.packet); + if (error_code < 0) { + av_packet_unref(av.packet); + goto cleanup; + } + + if (swr.ctx == nullptr) { + swr.src.sample_rate = av.codec_ctx->sample_rate; + swr.src.ch_layout = av.codec_ctx->ch_layout; + swr.src.format = av.codec_ctx->sample_fmt; + swr.dst.sample_rate = AUDIO_WORKING_RATE; + av_channel_layout_default(&swr.dst.ch_layout, 1); + swr.dst.format = Audio_GetAVAudioFormat(AUDIO_WORKING_FORMAT); + swr_alloc_set_opts2( + &swr.ctx, &swr.dst.ch_layout, swr.dst.format, + swr.dst.sample_rate, &swr.src.ch_layout, swr.src.format, + swr.src.sample_rate, 0, 0); + if (swr.ctx == nullptr) { + av_packet_unref(av.packet); + error_code = AVERROR(ENOMEM); + goto cleanup; + } + + error_code = swr_init(swr.ctx); + if (error_code != 0) { + av_packet_unref(av.packet); + goto cleanup; + } + } + + while (1) { + error_code = avcodec_receive_frame(av.codec_ctx, av.frame); + if (error_code == AVERROR(EAGAIN)) { + av_frame_unref(av.frame); + break; + } + + if (error_code < 0) { + av_packet_unref(av.packet); + av_frame_unref(av.frame); + goto cleanup; + } + + uint8_t *out_buffer = nullptr; + const int32_t out_samples = + swr_get_out_samples(swr.ctx, av.frame->nb_samples); + av_samples_alloc( + &out_buffer, nullptr, swr.dst.ch_layout.nb_channels, + out_samples, swr.dst.format, 1); + int32_t resampled_size = swr_convert( + swr.ctx, &out_buffer, out_samples, + (const uint8_t **)av.frame->data, av.frame->nb_samples); + while (resampled_size > 0) { + int32_t out_buffer_size = av_samples_get_buffer_size( + nullptr, swr.dst.ch_layout.nb_channels, resampled_size, + swr.dst.format, 1); + + if (out_buffer_size > 0) { + working_buffer = Memory_Realloc( + working_buffer, working_buffer_size + out_buffer_size); + if (out_buffer) { + memcpy( + (uint8_t *)working_buffer + working_buffer_size, + out_buffer, out_buffer_size); + } + working_buffer_size += out_buffer_size; + } + + resampled_size = + swr_convert(swr.ctx, &out_buffer, out_samples, nullptr, 0); + } + + av_freep(&out_buffer); + av_frame_unref(av.frame); + } + + av_packet_unref(av.packet); } - if (av.codec_ctx != nullptr) { - M_DecodePacket(av.codec_ctx, nullptr, av.frame, &swr); - } - - if (error_code == AVERROR_EOF) { - error_code = 0; - } else if (error_code < 0) { - goto cleanup; - } - - if (out_size != nullptr) { - *out_size = swr.working_buffer_size; - } - if (out_sample_count != nullptr) { - *out_sample_count = (int32_t)swr.working_buffer_size - / av_get_bytes_per_sample(swr.dst.format) - / swr.dst.ch_layout.nb_channels; - } - if (out_sample_data != nullptr) { - *out_sample_data = swr.working_buffer; - } else { - Memory_FreePointer(&swr.working_buffer); - } + int32_t sample_format_bytes = av_get_bytes_per_sample(swr.dst.format); + sample->num_samples = working_buffer_size / sample_format_bytes + / swr.dst.ch_layout.nb_channels; + sample->channels = swr.src.ch_layout.nb_channels; + sample->sample_data = working_buffer; result = true; + const clock_t time_end = clock(); + const double time_delta = + (((double)(time_end - time_start)) / CLOCKS_PER_SEC) * 1000.0f; + LOG_DEBUG( + "Sample %d decoded (%.0f ms)", sample_id, sample->original_size, + time_delta); + cleanup: if (error_code != 0) { - LOG_ERROR("Error while decoding sample: %s", av_err2str(error_code)); - } - - if (!result) { - if (out_size != nullptr) { - *out_size = 0; - } - if (out_sample_count != nullptr) { - *out_sample_count = 0; - } - if (out_sample_data != nullptr) { - *out_sample_data = nullptr; - } - Memory_FreePointer(&swr.working_buffer); + LOG_ERROR( + "Error while opening sample ID %d: %s", sample_id, + av_err2str(error_code)); } if (swr.ctx) { swr_free(&swr.ctx); } + if (av.frame) { av_frame_free(&av.frame); } + if (av.packet) { av_packet_free(&av.packet); } + av.codec = nullptr; + + if (!result) { + sample->sample_data = nullptr; + sample->original_data = nullptr; + sample->original_size = 0; + sample->num_samples = 0; + sample->channels = 0; + Memory_FreePointer(&working_buffer); + } + if (av.codec_ctx) { avcodec_free_context(&av.codec_ctx); } + if (av.format_ctx) { avformat_close_input(&av.format_ctx); } + if (av.avio_context) { av_freep(&av.avio_context->buffer); avio_context_free(&av.avio_context); } - return result; -} -static bool M_ConvertSample(const int32_t sample_id) -{ - ASSERT(sample_id >= 0 && sample_id < m_LoadedSamplesCount); - AUDIO_SAMPLE *const sample = &m_LoadedSamples[sample_id]; - if (sample->sample_data != nullptr) { - return true; - } - - size_t num_samples; - BENCHMARK benchmark = Benchmark_Start(); - - const bool result = M_ConvertRawData( - (uint8_t *)sample->original_data, sample->original_size, - AUDIO_WORKING_RATE, Audio_GetAVAudioFormat(AUDIO_WORKING_FORMAT), 1, - (uint8_t **)&sample->sample_data, nullptr, &num_samples); - - char buffer[80]; - sprintf(buffer, "sample %d decoded", sample_id); - Benchmark_End(&benchmark, buffer); - - sample->channels = 1; - sample->num_samples = num_samples; return result; } @@ -600,7 +553,7 @@ int32_t Audio_Sample_Play( continue; } - M_ConvertSample(sample_id); + M_Convert(sample_id); sound->is_used = true; sound->is_playing = true; diff --git a/src/libtrx/engine/audio_stream.c b/src/libtrx/engine/audio_stream.c index ba80da5f7..bca11ca75 100644 --- a/src/libtrx/engine/audio_stream.c +++ b/src/libtrx/engine/audio_stream.c @@ -84,32 +84,17 @@ static void M_SeekToStart(AUDIO_STREAM_SOUND *stream) ASSERT(stream != nullptr); stream->timestamp = stream->start_at; - int32_t error_code; if (stream->start_at <= 0.0) { // reset to start of file avio_seek(stream->av.format_ctx->pb, 0, SEEK_SET); - error_code = avformat_seek_file( + avformat_seek_file( stream->av.format_ctx, -1, 0, 0, 0, AVSEEK_FLAG_FRAME); } else { // seek to specific timestamp - AVFormatContext *const fmt = stream->av.format_ctx; - if (fmt->pb != nullptr && (fmt->pb->seekable & AVIO_SEEKABLE_NORMAL)) { - const int64_t ts = (int64_t)(stream->start_at * AV_TIME_BASE); - error_code = avformat_seek_file( - fmt, stream->av.stream->index, INT64_MIN, ts, INT64_MAX, - AVSEEK_FLAG_BACKWARD); - } else { - // fallback to stream-based seek - const double time_base_sec = av_q2d(stream->av.stream->time_base); - error_code = av_seek_frame( - fmt, stream->av.stream->index, - (int64_t)(stream->start_at / time_base_sec), AVSEEK_FLAG_ANY); - } - } - if (error_code < 0) { - LOG_ERROR( - "seek failed for timestamp %f: %s", stream->timestamp, - av_err2str(error_code)); + const double time_base_sec = av_q2d(stream->av.stream->time_base); + av_seek_frame( + stream->av.format_ctx, 0, stream->start_at / time_base_sec, + AVSEEK_FLAG_ANY); } } @@ -272,10 +257,6 @@ static bool M_InitialiseFromPath(int32_t sound_id, const char *file_path) int32_t error_code; char *full_path = File_GetFullPath(file_path); - if (full_path == nullptr) { - error_code = AVERROR(ENOENT); - goto cleanup; - } AUDIO_STREAM_SOUND *stream = &m_Streams[sound_id]; @@ -699,51 +680,26 @@ double Audio_Stream_GetDuration(int32_t sound_id) return duration; } -bool Audio_Stream_SeekTimestamp(const int32_t sound_id, const double timestamp) +bool Audio_Stream_SeekTimestamp(int32_t sound_id, double timestamp) { if (!g_AudioDeviceID || sound_id < 0 || sound_id >= AUDIO_MAX_ACTIVE_STREAMS) { return false; } - AUDIO_STREAM_SOUND *const stream = &m_Streams[sound_id]; - stream->start_at = timestamp; - if (!stream->is_used) { - return false; - } - ASSERT(stream->av.format_ctx != nullptr); - ASSERT(stream->av.codec_ctx != nullptr); - ASSERT(stream->av.stream != nullptr); - - SDL_LockAudioDevice(g_AudioDeviceID); - - const double time_base_sec = av_q2d(stream->av.stream->time_base); - if (time_base_sec <= 0.0) { - LOG_ERROR( - "Audio_Stream_SeekTimestamp: invalid time_base %f", time_base_sec); + if (m_Streams[sound_id].is_playing) { + SDL_LockAudioDevice(g_AudioDeviceID); + AUDIO_STREAM_SOUND *stream = &m_Streams[sound_id]; + const double time_base_sec = av_q2d(stream->av.stream->time_base); + av_seek_frame( + stream->av.format_ctx, 0, timestamp / time_base_sec, + AVSEEK_FLAG_ANY); + avcodec_flush_buffers(stream->av.codec_ctx); SDL_UnlockAudioDevice(g_AudioDeviceID); - return false; + return true; } - const int32_t stream_index = stream->av.stream->index; - const int64_t seek_target = (int64_t)(timestamp / time_base_sec); - const int32_t error_code = av_seek_frame( - stream->av.format_ctx, stream_index, seek_target, AVSEEK_FLAG_ANY); - if (error_code < 0) { - LOG_ERROR( - "seek failed for timestamp %f: %s", timestamp, - av_err2str(error_code)); - } - - avcodec_flush_buffers(stream->av.codec_ctx); - if (stream->sdl.stream) { - SDL_AudioStreamFlush(stream->sdl.stream); - } - - stream->timestamp = timestamp; - - SDL_UnlockAudioDevice(g_AudioDeviceID); - return true; + return false; } bool Audio_Stream_SetStartTimestamp(int32_t sound_id, double timestamp) diff --git a/src/libtrx/engine/image.c b/src/libtrx/engine/image.c index f209f8627..9449a1a8c 100644 --- a/src/libtrx/engine/image.c +++ b/src/libtrx/engine/image.c @@ -59,12 +59,7 @@ static bool M_Init(const char *const path, IMAGE_READER_CONTEXT *const ctx) ctx->packet = nullptr; char *full_path = File_GetFullPath(path); - int32_t error_code = 0; - if (full_path == nullptr) { - error_code = AVERROR(ENOENT); - goto finish; - } - error_code = + int32_t error_code = avformat_open_input(&ctx->format_ctx, full_path, nullptr, nullptr); Memory_FreePointer(&full_path); diff --git a/src/libtrx/enum_map.c b/src/libtrx/enum_map.c index 88cf557f3..b140bfc30 100644 --- a/src/libtrx/enum_map.c +++ b/src/libtrx/enum_map.c @@ -121,25 +121,3 @@ void EnumMap_Shutdown(void) } } } - -VECTOR *EnumMap_ListValues(const char *const enum_name) -{ - if (enum_name == nullptr) { - return nullptr; - } - - // Compare the prefix to find the matching enum values. - const size_t prefix_len = strlen(enum_name) + 1; - - VECTOR *const results = Vector_Create(sizeof(char *)); - M_INVERSE_ENTRY *entry; - M_INVERSE_ENTRY *tmp; - HASH_ITER(hh, m_InverseMap, entry, tmp) - { - if (strncmp(entry->key, enum_name, prefix_len - 1) == 0 - && entry->key[prefix_len - 1] == '|') { - Vector_Add(results, &entry->str_value); - } - } - return results; -} diff --git a/src/libtrx/filesystem.c b/src/libtrx/filesystem.c index 4ce3ab763..906032745 100644 --- a/src/libtrx/filesystem.c +++ b/src/libtrx/filesystem.c @@ -283,56 +283,56 @@ MYFILE *File_Open(const char *path, FILE_OPEN_MODE mode) return file; } -bool File_ReadData(MYFILE *const file, void *const data, const size_t size) +void File_ReadData(MYFILE *const file, void *const data, const size_t size) { - return fread(data, size, 1, file->fp) == 1; + fread(data, size, 1, file->fp); } -bool File_ReadItems( +void File_ReadItems( MYFILE *const file, void *data, const size_t count, const size_t item_size) { - return fread(data, item_size, count, file->fp) == count; + fread(data, item_size, count, file->fp); } int8_t File_ReadS8(MYFILE *const file) { int8_t result; - File_ReadData(file, &result, sizeof(result)); + fread(&result, sizeof(result), 1, file->fp); return result; } int16_t File_ReadS16(MYFILE *const file) { int16_t result; - File_ReadData(file, &result, sizeof(result)); + fread(&result, sizeof(result), 1, file->fp); return result; } int32_t File_ReadS32(MYFILE *const file) { int32_t result; - File_ReadData(file, &result, sizeof(result)); + fread(&result, sizeof(result), 1, file->fp); return result; } uint8_t File_ReadU8(MYFILE *const file) { uint8_t result; - File_ReadData(file, &result, sizeof(result)); + fread(&result, sizeof(result), 1, file->fp); return result; } uint16_t File_ReadU16(MYFILE *const file) { uint16_t result; - File_ReadData(file, &result, sizeof(result)); + fread(&result, sizeof(result), 1, file->fp); return result; } uint32_t File_ReadU32(MYFILE *const file) { uint32_t result; - File_ReadData(file, &result, sizeof(result)); + fread(&result, sizeof(result), 1, file->fp); return result; } diff --git a/src/libtrx/game/anims/common.c b/src/libtrx/game/anims/common.c index 10a673c65..042d8c187 100644 --- a/src/libtrx/game/anims/common.c +++ b/src/libtrx/game/anims/common.c @@ -7,7 +7,6 @@ static ANIM *m_Anims = nullptr; static ANIM_CHANGE *m_Changes = nullptr; static ANIM_RANGE *m_Ranges = nullptr; static ANIM_BONE *m_Bones = nullptr; -static ANIM m_NullAnim = {}; void Anim_InitialiseAnims(const int32_t num_anims) { @@ -38,7 +37,7 @@ int32_t Anim_GetTotalCount(void) ANIM *Anim_GetAnim(const int32_t anim_idx) { - return anim_idx == NO_ANIM ? &m_NullAnim : &m_Anims[anim_idx]; + return &m_Anims[anim_idx]; } ANIM_CHANGE *Anim_GetChange(const int32_t change_idx) diff --git a/src/libtrx/game/anims/frames.c b/src/libtrx/game/anims/frames.c index f7aa6b45c..fbb67706c 100644 --- a/src/libtrx/game/anims/frames.c +++ b/src/libtrx/game/anims/frames.c @@ -41,10 +41,6 @@ static int32_t M_GetAnimFrameCount( uint32_t next_ofs = anim_idx == Anim_GetTotalCount() - 1 ? (unsigned)(sizeof(int16_t) * frame_data_length) : Anim_GetAnim(anim_idx + 1)->frame_ofs; - if (anim->frame_size == 0) { - ASSERT(next_ofs - anim->frame_ofs == 0); - return 0; - } return (next_ofs - anim->frame_ofs) / (int32_t)(sizeof(int16_t) * anim->frame_size); #endif @@ -205,7 +201,7 @@ void Anim_LoadFrames(const int16_t *data, const int32_t data_length) // so ensure everything that's loaded is configured as such. for (int32_t i = 0; i < O_NUMBER_OF; i++) { OBJECT *const obj = Object_Get(i); - if (obj->loaded && obj->mesh_count >= 0 && obj->anim_idx == NO_ANIM + if (obj->loaded && obj->mesh_count >= 0 && obj->anim_idx == -1 && obj->frame_base == nullptr) { obj->frame_base = M_FindFrameBase(obj->frame_ofs); } diff --git a/src/libtrx/game/camera/common.c b/src/libtrx/game/camera/common.c index 700c77d96..798388ccc 100644 --- a/src/libtrx/game/camera/common.c +++ b/src/libtrx/game/camera/common.c @@ -50,10 +50,8 @@ void Camera_ClampInterpResult(void) CLAMP(pos->z, box->left, box->right); finish: - const int32_t floor = - Room_GetHeightEx(sector, pos->x, pos->y, pos->z, true); - const int32_t ceiling = - Room_GetCeilingEx(sector, pos->x, pos->y, pos->z, true); + const int32_t floor = Room_GetHeight(sector, pos->x, pos->y, pos->z); + const int32_t ceiling = Room_GetCeiling(sector, pos->x, pos->y, pos->z); if (floor != NO_HEIGHT && ceiling != NO_HEIGHT) { CLAMP(pos->y, ceiling - shift, floor - shift); } diff --git a/src/libtrx/game/collision.c b/src/libtrx/game/collision.c index e459289a3..701bffbf1 100644 --- a/src/libtrx/game/collision.c +++ b/src/libtrx/game/collision.c @@ -14,8 +14,12 @@ static bool M_IsOnWalkable( const SECTOR *const sector, const int32_t x, const int32_t y, const int32_t z, const int32_t room_height) { +#if TR_VERSION == 1 return g_Config.gameplay.fix_bridge_collision && Room_IsOnWalkable(sector, x, y, z, room_height); +#elif TR_VERSION >= 2 + return false; +#endif } int32_t Collide_GetSpheres( @@ -65,7 +69,18 @@ int32_t Collide_GetSpheres( Matrix_TranslateRel32(bone->pos); Matrix_Rot16(frame->mesh_rots[i]); - Object_ApplyExtraRotation(&extra_rotation, bone->rot, false); + + if (extra_rotation != nullptr) { + if (bone->rot_y) { + Matrix_RotY(*extra_rotation++); + } + if (bone->rot_x) { + Matrix_RotX(*extra_rotation++); + } + if (bone->rot_z) { + Matrix_RotZ(*extra_rotation++); + } + } mesh = Object_GetMesh(obj->mesh_idx + i); Matrix_Push(); @@ -143,7 +158,18 @@ void Collide_GetJointAbsPosition( Matrix_TranslateRel32(bone->pos); Matrix_Rot16(frame->mesh_rots[i + 1]); - Object_ApplyExtraRotation(&extra_rotation, bone->rot, false); + + if (extra_rotation != nullptr) { + if (bone->rot_y) { + Matrix_RotY(*extra_rotation++); + } + if (bone->rot_x) { + Matrix_RotX(*extra_rotation++); + } + if (bone->rot_z) { + Matrix_RotZ(*extra_rotation++); + } + } } Matrix_TranslateRel32(*out_vec); @@ -265,7 +291,11 @@ void Collide_GetCollisionInfo( coll->side_front.ceiling = ceiling; coll->side_front.type = Room_GetHeightType(); +#if TR_VERSION == 1 is_on_walkable = M_IsOnWalkable(sector, x, y_top, z, room_height); +#elif TR_VERSION >= 2 + is_on_walkable = false; +#endif if (!is_on_walkable) { if (coll->slopes_are_walls && coll->side_front.type == HT_BIG_SLOPE && coll->side_front.floor < 0) { diff --git a/src/libtrx/game/console/cmd/config.c b/src/libtrx/game/console/cmd/config.c index bd2f31bca..2979bfe95 100644 --- a/src/libtrx/game/console/cmd/config.c +++ b/src/libtrx/game/console/cmd/config.c @@ -1,6 +1,5 @@ #include "game/console/cmd/config.h" -#include "colors.h" #include "config.h" #include "debug.h" #include "enum_map.h" @@ -14,7 +13,6 @@ static const char *M_Resolve(const char *option_name); static bool M_SameKey(const char *key1, const char *key2); -static char *M_GetAvailableOptions(const CONFIG_OPTION *option); static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *ctx); @@ -77,56 +75,6 @@ cleanup: return result; } -// Return a comma-delimited list of valid values for the option. -// Caller must free the result via Memory_FreePointer. -static char *M_GetAvailableOptions(const CONFIG_OPTION *const option) -{ - if (option == nullptr) { - return nullptr; - } - - switch (option->type) { - case COT_BOOL: - return Memory_DupStr(GS(OSD_COMMAND_BOOL)); - - case COT_INT32: - return Memory_DupStr(GS(OSD_COMMAND_INTEGER)); - - case COT_DOUBLE: - case COT_FLOAT: - return Memory_DupStr(GS(OSD_COMMAND_DECIMAL)); - - case COT_ENUM: { - const char *enum_name = (const char *)option->param; - VECTOR *const values = EnumMap_ListValues(enum_name); - if (values == nullptr) { - return nullptr; - } - // Join vector items into a comma-separated string - size_t total_len = 1; - const char *const sep = ", "; - for (int32_t i = 0; i < values->count; i++) { - const char *const s = *(char **)Vector_Get(values, i); - total_len += strlen(s) + (i + 1 < values->count ? strlen(sep) : 0); - } - char *const result = Memory_Alloc(total_len); - char *ptr = result; - for (int32_t i = 0; i < values->count; i++) { - const char *const s = *(char **)Vector_Get(values, i); - strcat(ptr, s); - if (i + 1 < values->count) { - strcat(ptr, sep); - } - } - Vector_Free(values); - return result; - } - - default: - return nullptr; - } -} - char *Console_Cmd_Config_NormalizeKey(const char *key) { // TODO: Once we support arbitrary glyphs, this conversion should @@ -168,13 +116,6 @@ bool Console_Cmd_Config_GetCurrentValue( target, target_size, "%s", EnumMap_ToString(option->param, *(int32_t *)option->target)); break; - case COT_RGB888: { - const RGB_888 *color = option->target; - snprintf( - target, target_size, "%02hhx%02hhx%02hhx", color->r, color->g, - color->b); - break; - } } return true; } @@ -234,18 +175,6 @@ bool Console_Cmd_Config_SetCurrentValue( } break; } - - case COT_RGB888: { - uint8_t r, g, b; - if (sscanf(new_value, "%02hhx%02hhx%02hhx", &r, &g, &b) == 3) { - RGB_888 *const color = (RGB_888 *)option->target; - color->r = r; - color->g = g; - color->b = b; - return true; - } - break; - } } return false; @@ -312,16 +241,18 @@ COMMAND_RESULT Console_Cmd_Config_Helper( char *normalized_name = Console_Cmd_Config_NormalizeKey(option->name); + COMMAND_RESULT result = CR_BAD_INVOCATION; if (new_value == nullptr || String_IsEmpty(new_value)) { char cur_value[128]; if (Console_Cmd_Config_GetCurrentValue(option, cur_value, 128)) { Console_Log(GS(OSD_CONFIG_OPTION_GET), normalized_name, cur_value); - return CR_SUCCESS; + result = CR_SUCCESS; + } else { + result = CR_FAILURE; } - return CR_FAILURE; + return result; } - COMMAND_RESULT result; if (Console_Cmd_Config_SetCurrentValue(option, new_value)) { Config_Write(); @@ -329,15 +260,6 @@ COMMAND_RESULT Console_Cmd_Config_Helper( ASSERT(Console_Cmd_Config_GetCurrentValue(option, final_value, 128)); Console_Log(GS(OSD_CONFIG_OPTION_SET), normalized_name, final_value); result = CR_SUCCESS; - } else { - // Report bad invocation on the provided new value - Console_Log(GS(OSD_COMMAND_BAD_INVOCATION), new_value); - char *available_options = M_GetAvailableOptions(option); - if (available_options != nullptr) { - Console_Log(GS(OSD_COMMAND_VALID_VALUES), available_options); - Memory_FreePointer(&available_options); - } - result = CR_FAILURE; } cleanup: diff --git a/src/libtrx/game/console/cmd/pos.c b/src/libtrx/game/console/cmd/pos.c index 0f150fbc5..34cfbb2fd 100644 --- a/src/libtrx/game/console/cmd/pos.c +++ b/src/libtrx/game/console/cmd/pos.c @@ -6,7 +6,6 @@ #include "game/game_string.h" #include "game/lara/common.h" #include "game/objects/common.h" -#include "game/rooms.h" #include "memory.h" #include "strings.h" @@ -46,15 +45,10 @@ static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *const ctx) String_Format(level_type_fmt, current_level->num + reindex); const ITEM *const lara_item = Lara_GetItem(); - int16_t room_num = lara_item->room_num; - const ROOM *const room = Room_Get(room_num); - if (Room_GetFlipStatus() && room->flipped_room != NO_ROOM_NEG) { - room_num = room->flipped_room; - } char *details = lara_item == nullptr ? String_Format("%s", GS(OSD_POS_LARA_MISSING)) : String_Format( - GS(OSD_POS_LARA_POS_FMT), room_num, + GS(OSD_POS_LARA_POS_FMT), lara_item->room_num, lara_item->pos.x / (float)WALL_L, lara_item->pos.y / (float)WALL_L, lara_item->pos.z / (float)WALL_L, diff --git a/src/libtrx/game/console/cmd/teleport.c b/src/libtrx/game/console/cmd/teleport.c index d8479f7b3..02dbe97b8 100644 --- a/src/libtrx/game/console/cmd/teleport.c +++ b/src/libtrx/game/console/cmd/teleport.c @@ -156,7 +156,7 @@ static COMMAND_RESULT M_TeleportToXYZ(float x, const float y, float z) z += 0.5f; } - if (!Lara_Cheat_Teleport(x * WALL_L, y * WALL_L, z * WALL_L, NO_ROOM)) { + if (!Lara_Cheat_Teleport(x * WALL_L, y * WALL_L, z * WALL_L)) { Console_Log(GS(OSD_POS_SET_POS_FAIL), x, y, z); return CR_FAILURE; } @@ -185,7 +185,7 @@ static COMMAND_RESULT M_TeleportToRoom(const int16_t room_num) int32_t x = x1 + Random_GetControl() * (x2 - x1) / 0x7FFF; int32_t y = y1; int32_t z = z1 + Random_GetControl() * (z2 - z1) / 0x7FFF; - if (Lara_Cheat_Teleport(x, y, z, room_num)) { + if (Lara_Cheat_Teleport(x, y, z)) { success = true; break; } @@ -219,8 +219,8 @@ static COMMAND_RESULT M_TeleportToObject(const char *const user_input) } if (Lara_Cheat_Teleport( - best_item->pos.x, best_item->pos.y - STEP_L / 4, best_item->pos.z, - best_item->room_num)) { + best_item->pos.x, best_item->pos.y - STEP_L / 4, + best_item->pos.z)) { Console_Log(GS(OSD_POS_SET_ITEM), obj_name); } else { Console_Log(GS(OSD_POS_SET_ITEM_FAIL), obj_name); diff --git a/src/libtrx/game/console/common.c b/src/libtrx/game/console/common.c index 70454aa7a..e3b6052a0 100644 --- a/src/libtrx/game/console/common.c +++ b/src/libtrx/game/console/common.c @@ -4,7 +4,7 @@ #include "debug.h" #include "game/console/registry.h" #include "game/game_string.h" -#include "game/ui.h" +#include "game/ui/widgets/console.h" #include "log.h" #include "memory.h" #include "strings.h" @@ -14,18 +14,20 @@ #include static bool m_IsOpened = false; -static UI_CONSOLE_STATE m_UIState = {}; +static UI_WIDGET *m_Console; void Console_Init(void) { - UI_Console_Init(&m_UIState); - + m_Console = UI_Console_Create(); Console_History_Init(); } void Console_Shutdown(void) { - UI_Console_Free(&m_UIState); + if (m_Console != nullptr) { + m_Console->free(m_Console); + m_Console = nullptr; + } Console_History_Shutdown(); Console_Registry_Shutdown(); @@ -36,21 +38,16 @@ void Console_Shutdown(void) void Console_Open(void) { if (m_IsOpened) { - return; + UI_Console_HandleClose(m_Console); } m_IsOpened = true; - UI_FireEvent( - (EVENT) { .name = "console_open", .sender = nullptr, .data = nullptr }); + UI_Console_HandleOpen(m_Console); } void Console_Close(void) { - if (!m_IsOpened) { - return; - } + UI_Console_HandleClose(m_Console); m_IsOpened = false; - UI_FireEvent((EVENT) { - .name = "console_close", .sender = nullptr, .data = nullptr }); } bool Console_IsOpened(void) @@ -58,6 +55,21 @@ bool Console_IsOpened(void) return m_IsOpened; } +void Console_ScrollLogs(void) +{ + UI_Console_ScrollLogs(m_Console); +} + +int32_t Console_GetVisibleLogCount(void) +{ + return UI_Console_GetVisibleLogCount(m_Console); +} + +int32_t Console_GetMaxLogCount(void) +{ + return UI_Console_GetMaxLogCount(m_Console); +} + void Console_Log(const char *fmt, ...) { ASSERT(fmt != nullptr); @@ -74,12 +86,7 @@ void Console_Log(const char *fmt, ...) va_end(va); LOG_INFO("%s", text); - - UI_FireEvent((EVENT) { - .name = "console_log", - .sender = nullptr, - .data = text, - }); + UI_Console_HandleLog(m_Console, text); Memory_FreePointer(&text); } @@ -128,12 +135,17 @@ COMMAND_RESULT Console_Eval(const char *const cmdline) return result; } -void Console_Control(void) -{ - UI_Console_Control(&m_UIState); -} - void Console_Draw(void) { - UI_Console(&m_UIState); + if (m_Console == nullptr) { + return; + } + + Console_ScrollLogs(); + + if (Console_IsOpened() || Console_GetVisibleLogCount() > 0) { + Console_DrawBackdrop(); + } + + m_Console->draw(m_Console); } diff --git a/src/libtrx/game/console/history.c b/src/libtrx/game/console/history.c index 07dbb7af1..8a5921078 100644 --- a/src/libtrx/game/console/history.c +++ b/src/libtrx/game/console/history.c @@ -5,8 +5,6 @@ #include "utils.h" #include "vector.h" -#include - #define MAX_HISTORY_ENTRIES 30 VECTOR *m_History = nullptr; @@ -88,21 +86,12 @@ void Console_History_Clear(void) void Console_History_Append(const char *const prompt) { - for (int32_t i = m_History->count - 1; i >= 0; i--) { - char *const entry = *(char **)Vector_Get(m_History, i); - if (strcmp(entry, prompt) == 0) { - Memory_Free(entry); - Vector_RemoveAt(m_History, i); - } - } - if (m_History->count == MAX_HISTORY_ENTRIES) { - char *const oldest = *(char **)Vector_Get(m_History, 0); - Memory_Free(oldest); + char *const prompt = *(char **)Vector_Get(m_History, 0); + Memory_Free(prompt); Vector_RemoveAt(m_History, 0); } - - char *const prompt_copy = Memory_DupStr(prompt); + char *prompt_copy = Memory_DupStr(prompt); Vector_Add(m_History, &prompt_copy); } diff --git a/src/libtrx/game/creature/common.c b/src/libtrx/game/creature/common.c deleted file mode 100644 index eedaa4e25..000000000 --- a/src/libtrx/game/creature/common.c +++ /dev/null @@ -1,1120 +0,0 @@ -#include "game/camera/vars.h" -#include "game/creature.h" -#include "game/lara/common.h" -#include "game/los.h" -#include "game/objects/vars.h" -#include "game/pathing.h" -#include "game/random.h" -#include "game/rooms.h" -#include "log.h" -#include "utils.h" - -#define M_FLOAT_SPEED 32 -#define M_MAX_DISTANCE (WALL_L * 30) -#define M_ATTACK_RANGE SQUARE(WALL_L * 3) // = 0x900000 = 9437184 -#define M_ESCAPE_CHANCE 2048 -#define M_RECOVER_CHANCE 256 -#define M_TARGET_TOLERANCE 0x400000 -#define M_MAX_TILT (3 * DEG_1) // = 546 -#define M_MAX_HEAD_CHANGE (5 * DEG_1) // = 910 -#if TR_VERSION == 1 - #define M_HEAD_ARC FRONT_ARC -#elif TR_VERSION >= 2 - #define M_HEAD_ARC 0x3000 // = 12288 -#endif -#define M_MAX_X_ROT (20 * DEG_1) // = 3640 - -static bool m_AlliesHostile = false; - -static ITEM *M_ChooseEnemy(const ITEM *item); -static bool M_SwitchToWater( - int16_t item_num, const int32_t *wh, const HYBRID_INFO *info); -static bool M_SwitchToLand( - int16_t item_num, const int32_t *wh, const HYBRID_INFO *info); -static bool M_TestSwitchOrKill(int16_t item_num, GAME_OBJECT_ID target_id); - -#if TR_VERSION == 1 -extern void Carrier_TestItemDrops(int16_t item_num); -#endif - -static void M_GetBaddieTarget(const int16_t item_num, const bool goody) -{ - ITEM *const lara_item = Lara_GetItem(); - ITEM *const item = Item_Get(item_num); - CREATURE *const creature = item->data; - - ITEM *best_item = nullptr; - int32_t best_distance = INT32_MAX; - for (int32_t i = 0; i < LOT_SLOT_COUNT; i++) { - const int16_t target_item_num = LOT_GetBaddieSlot(i)->item_num; - if (target_item_num == NO_ITEM || target_item_num == item_num) { - continue; - } - - ITEM *const target = Item_Get(target_item_num); - const GAME_OBJECT_ID obj_id = target->object_id; -#if TR_VERSION == 2 - if (goody && obj_id != O_BANDIT_1 && obj_id != O_BANDIT_2) { - continue; - } else if (!goody && obj_id != O_MONK_1 && obj_id != O_MONK_2) { - continue; - } -#endif - - const int32_t dx = (target->pos.x - item->pos.x) >> 6; - const int32_t dy = (target->pos.y - item->pos.y) >> 6; - const int32_t dz = (target->pos.z - item->pos.z) >> 6; - const int32_t distance = SQUARE(dx) + SQUARE(dy) + SQUARE(dz); - if (distance < best_distance) { - best_item = target; - best_distance = distance; - } - } - - if (best_item == nullptr) { - if (!goody || Creature_AreAlliesHostile()) { - creature->enemy = lara_item; - } else { - creature->enemy = nullptr; - } - return; - } - - if (!goody || Creature_AreAlliesHostile()) { - const int32_t dx = (lara_item->pos.x - item->pos.x) >> 6; - const int32_t dy = (lara_item->pos.y - item->pos.y) >> 6; - const int32_t dz = (lara_item->pos.z - item->pos.z) >> 6; - const int32_t distance = SQUARE(dx) + SQUARE(dy) + SQUARE(dz); - if (distance < best_distance) { - best_item = lara_item; - best_distance = distance; - } - } - - const ITEM *const target = creature->enemy; - if (target == nullptr || target->status != IS_ACTIVE) { - creature->enemy = best_item; - } else { - const int32_t dx = (target->pos.x - item->pos.x) >> 6; - const int32_t dy = (target->pos.y - item->pos.y) >> 6; - const int32_t dz = (target->pos.z - item->pos.z) >> 6; - const int32_t distance = SQUARE(dz) + SQUARE(dy) + SQUARE(dx); - if (distance < best_distance + M_TARGET_TOLERANCE) { - creature->enemy = best_item; - } - } -} - -static ITEM *M_ChooseEnemy(const ITEM *const item) -{ - CREATURE *const creature = item->data; - switch (item->object_id) { -#if TR_VERSION == 2 - case O_BANDIT_1: - case O_BANDIT_2: - M_GetBaddieTarget(creature->item_num, false); - break; - - case O_MONK_1: - case O_MONK_2: - M_GetBaddieTarget(creature->item_num, true); - break; -#endif - - default: - creature->enemy = Lara_GetItem(); - break; - } - - if (creature->enemy != nullptr) { - return creature->enemy; - } - return Lara_GetItem(); -} - -static bool M_SwitchToWater( - const int16_t item_num, const int32_t *const wh, - const HYBRID_INFO *const info) -{ - if (*wh == NO_HEIGHT) { - return false; - } - - ITEM *const item = Item_Get(item_num); - - if (item->hit_points <= 0) { - // Dead land creatures should remain in their pose permanently. - return false; - } - - // The land creature is alive and the room has been flooded. Switch to the - // water creature. - if (!M_TestSwitchOrKill(item_num, info->water.id)) { - return false; - } - - item->object_id = info->water.id; - Item_SwitchToAnim(item, info->water.active_anim, 0); - item->current_anim_state = Item_GetAnim(item)->current_anim_state; - item->goal_anim_state = item->current_anim_state; - item->pos.y = *wh; - - return true; -} - -static bool M_SwitchToLand( - const int16_t item_num, const int32_t *const wh, - const HYBRID_INFO *const info) -{ - if (*wh != NO_HEIGHT) { - return false; - } - - if (!M_TestSwitchOrKill(item_num, info->land.id)) { - return false; - } - - ITEM *const item = Item_Get(item_num); - - // Switch to the land creature regardless of death state. - item->object_id = info->land.id; - item->rot.x = 0; - - if (item->hit_points > 0) { - Item_SwitchToAnim(item, info->land.active_anim, 0); - item->current_anim_state = Item_GetAnim(item)->current_anim_state; - item->goal_anim_state = item->current_anim_state; - - } else { - Item_SwitchToAnim(item, info->land.death_anim, -1); - item->current_anim_state = info->land.death_state; - item->goal_anim_state = item->current_anim_state; - - int16_t room_num = item->room_num; - const SECTOR *const sector = - Room_GetSector(item->pos.x, item->pos.y, item->pos.z, &room_num); - item->floor = - Room_GetHeight(sector, item->pos.x, item->pos.y, item->pos.z); - item->pos.y = item->floor; - - if (item->room_num != room_num) { - Item_NewRoom(item_num, room_num); - } - } - - return true; -} - -static bool M_TestSwitchOrKill( - const int16_t item_num, const GAME_OBJECT_ID target_id) -{ - if (Object_Get(target_id)->loaded) { - return true; - } - - LOG_WARNING( - "Object %d is not loaded; item %d cannot be converted.", target_id, - item_num); - Item_Kill(item_num); - return false; -} - -void Creature_Initialise(const int16_t item_num) -{ - ITEM *const item = Item_Get(item_num); - item->rot.y += (Random_GetControl() - DEG_90) >> 1; - item->collidable = 1; - item->data = nullptr; -} - -bool Creature_Activate(const int16_t item_num) -{ - ITEM *const item = Item_Get(item_num); - if (item->status != IS_INVISIBLE) { - return true; - } - - if (!LOT_EnableBaddieAI(item_num, false)) { - return false; - } - - item->status = IS_ACTIVE; - return true; -} - -void Creature_AIInfo(ITEM *const item, AI_INFO *const info) -{ - CREATURE *const creature = item->data; - if (creature == nullptr) { - return; - } - - ITEM *const enemy = M_ChooseEnemy(item); - const int16_t *const zone = Box_GetLotZone(&creature->lot); - - { - const ROOM *const room = Room_Get(item->room_num); - item->box_num = - Room_GetWorldSector(room, item->pos.x, item->pos.z)->box; - info->zone_num = zone[item->box_num]; - } - - { - const ROOM *const room = Room_Get(enemy->room_num); - enemy->box_num = - Room_GetWorldSector(room, enemy->pos.x, enemy->pos.z)->box; - info->enemy_zone_num = zone[enemy->box_num]; - } - - if (((Box_GetBox(enemy->box_num)->overlap_index & creature->lot.block_mask) - != 0) - || (creature->lot.node[item->box_num].search_num - == (creature->lot.search_num | BOX_BLOCKED_SEARCH))) { - info->enemy_zone_num |= BOX_BLOCKED; - } - - const OBJECT *const obj = Object_Get(item->object_id); - const int32_t z = enemy->pos.z - - ((obj->pivot_length * Math_Cos(item->rot.y)) >> W2V_SHIFT) - - item->pos.z; - const int32_t x = enemy->pos.x - - ((obj->pivot_length * Math_Sin(item->rot.y)) >> W2V_SHIFT) - - item->pos.x; - const int16_t angle = Math_Atan(z, x); - - if (creature->enemy == nullptr) { - info->distance = 0x7FFFFFFF; - } else if (ABS(x) > M_MAX_DISTANCE || ABS(z) > M_MAX_DISTANCE) { - info->distance = SQUARE(M_MAX_DISTANCE); - } else { - info->distance = SQUARE(x) + SQUARE(z); - } - - info->angle = angle - item->rot.y; - info->enemy_facing = angle - enemy->rot.y + DEG_180; - info->ahead = info->angle > -FRONT_ARC && info->angle < FRONT_ARC; - info->bite = info->ahead && ABS(enemy->pos.y - item->pos.y) <= STEP_L - && (TR_VERSION == 1 || enemy->hit_points > 0); -} - -bool Creature_EnsureHabitat( - const int16_t item_num, int32_t *const wh, const HYBRID_INFO *const info) -{ - // Test the environment for a hybrid creature. Record the water height and - // return whether or not a type conversion has taken place. - const ITEM *const item = Item_Get(item_num); - *wh = Room_GetWaterHeight( - item->pos.x, item->pos.y, item->pos.z, item->room_num); - - return item->object_id == info->land.id - ? M_SwitchToWater(item_num, wh, info) - : M_SwitchToLand(item_num, wh, info); -} - -void Creature_Mood( - const ITEM *const item, const AI_INFO *const info, const bool violent) -{ - CREATURE *const creature = item->data; - if (creature == nullptr) { - return; - } - - LOT_INFO *const lot = &creature->lot; - const ITEM *enemy = TR_VERSION >= 2 ? creature->enemy : Lara_GetItem(); - if (lot->node[item->box_num].search_num - == (lot->search_num | BOX_BLOCKED_SEARCH)) { - lot->required_box = NO_BOX; - } - - if (creature->mood != MOOD_ATTACK && lot->required_box != NO_BOX - && !Box_ValidBox(item, info->zone_num, lot->target_box)) { - if (info->zone_num == info->enemy_zone_num) { - creature->mood = MOOD_BORED; - } - lot->required_box = NO_BOX; - } - - const MOOD_TYPE mood = creature->mood; - if (enemy == nullptr) { - creature->mood = MOOD_BORED; - enemy = Lara_GetItem(); - } else if (enemy->hit_points <= 0) { - creature->mood = MOOD_BORED; - } else if (violent) { - switch (mood) { - case MOOD_BORED: - case MOOD_STALK: - if (info->zone_num == info->enemy_zone_num) { - creature->mood = MOOD_ATTACK; - } else if (item->hit_status) { - creature->mood = MOOD_ESCAPE; - } - break; - - case MOOD_ATTACK: - if (info->zone_num != info->enemy_zone_num) { - creature->mood = MOOD_BORED; - } - break; - - case MOOD_ESCAPE: - if (info->zone_num == info->enemy_zone_num) { - creature->mood = MOOD_ATTACK; - } - break; - } - } else { - switch (mood) { - case MOOD_BORED: - case MOOD_STALK: - if (item->hit_status - && (Random_GetControl() < M_ESCAPE_CHANCE - || info->zone_num != info->enemy_zone_num)) { - creature->mood = MOOD_ESCAPE; - } else if (info->zone_num == info->enemy_zone_num) { - if (info->distance < M_ATTACK_RANGE - || (creature->mood == MOOD_STALK - && lot->required_box == NO_BOX)) { - creature->mood = MOOD_ATTACK; - } else { - creature->mood = MOOD_STALK; - } - } - break; - - case MOOD_ATTACK: - if (item->hit_status - && (Random_GetControl() < M_ESCAPE_CHANCE - || info->zone_num != info->enemy_zone_num)) { - creature->mood = MOOD_ESCAPE; - } else if (info->zone_num != info->enemy_zone_num) { - creature->mood = MOOD_BORED; - } - break; - - case MOOD_ESCAPE: - if (info->zone_num == info->enemy_zone_num - && Random_GetControl() < M_RECOVER_CHANCE) { - creature->mood = MOOD_STALK; - } - break; - } - } - - if (mood != creature->mood) { - if (mood == MOOD_ATTACK) { - Box_TargetBox(lot, lot->target_box); - } - lot->required_box = NO_BOX; - } - - switch (creature->mood) { - case MOOD_BORED: { - const int16_t box_num = - lot->node[lot->zone_count * Random_GetControl() / 0x7FFF].box_num; - if (Box_ValidBox(item, info->zone_num, box_num)) { - if (Box_StalkBox(item, enemy, box_num) - && (TR_VERSION == 1 - || (creature->enemy != nullptr && enemy->hit_points > 0))) { - Box_TargetBox(lot, box_num); - creature->mood = MOOD_STALK; - } else if (lot->required_box == NO_BOX) { - Box_TargetBox(lot, box_num); - } - } - break; - } - - case MOOD_ATTACK: - if (TR_VERSION >= 2 - || Random_GetControl() < Object_Get(item->object_id)->smartness) { - lot->target = enemy->pos; - lot->required_box = enemy->box_num; - if (lot->fly != 0 - && Lara_GetLaraInfo()->water_status == LWS_ABOVE_WATER) { - lot->target.y += Item_GetBestFrame(enemy)->bounds.min.y; - } - } - break; - - case MOOD_ESCAPE: { - const int16_t box_num = - lot->node[lot->zone_count * Random_GetControl() / 0x7FFF].box_num; - if (Box_ValidBox(item, info->zone_num, box_num) - && lot->required_box == NO_BOX) { - if (Box_EscapeBox(item, enemy, box_num)) { - Box_TargetBox(lot, box_num); - } else if ( - info->zone_num == info->enemy_zone_num - && Box_StalkBox(item, enemy, box_num)) { - Box_TargetBox(lot, box_num); - creature->mood = MOOD_STALK; - } - } - break; - } - - case MOOD_STALK: { - if (lot->required_box == NO_BOX - || !Box_StalkBox(item, enemy, lot->required_box)) { - const int16_t box_num = - lot->node[lot->zone_count * Random_GetControl() / 0x7FFF] - .box_num; - if (Box_ValidBox(item, info->zone_num, box_num)) { - if (Box_StalkBox(item, enemy, box_num)) { - Box_TargetBox(lot, box_num); - } else if (lot->required_box == NO_BOX) { - Box_TargetBox(lot, box_num); - if (info->zone_num != info->enemy_zone_num) { - creature->mood = MOOD_BORED; - } - } - } - } - break; - } - } - - if (lot->target_box == NO_BOX) { - Box_TargetBox(lot, item->box_num); - } - Box_CalculateTarget(&creature->target, item, lot); -} - -int16_t Creature_Turn(ITEM *const item, int16_t max_turn) -{ - const CREATURE *const creature = item->data; - if (creature == nullptr || item->speed == 0 || max_turn == 0) { - return 0; - } - - const int32_t dx = creature->target.x - item->pos.x; - const int32_t dz = creature->target.z - item->pos.z; - - int16_t angle = Math_Atan(dz, dx) - item->rot.y; - if (angle > FRONT_ARC || angle < -FRONT_ARC) { - const int32_t range = (item->speed << 14) / max_turn; - if (SQUARE(dx) + SQUARE(dz) < SQUARE(range)) { - max_turn /= 2; - } - } - - CLAMP(angle, -max_turn, max_turn); - item->rot.y += angle; - return angle; -} - -void Creature_Tilt(ITEM *const item, int16_t angle) -{ - angle = angle * 4 - item->rot.z; - CLAMP(angle, -M_MAX_TILT, M_MAX_TILT); - item->rot.z += angle; -} - -void Creature_Head(ITEM *const item, const int16_t required) -{ - CREATURE *const creature = item->data; - if (creature == nullptr) { - return; - } - - int16_t change = required - creature->head_rotation; - CLAMP(change, -M_MAX_HEAD_CHANGE, M_MAX_HEAD_CHANGE); - - creature->head_rotation += change; - CLAMP(creature->head_rotation, -M_HEAD_ARC, M_HEAD_ARC); -} - -void Creature_Neck(ITEM *const item, const int16_t required) -{ - CREATURE *const creature = item->data; - if (creature == nullptr) { - return; - } - - int16_t change = required - creature->neck_rotation; - CLAMP(change, -M_MAX_HEAD_CHANGE, M_MAX_HEAD_CHANGE); - - creature->neck_rotation += change; - CLAMP(creature->neck_rotation, -M_HEAD_ARC, M_HEAD_ARC); -} - -void Creature_Float(const int16_t item_num) -{ - ITEM *const item = Item_Get(item_num); - - item->hit_points = DONT_TARGET; - item->rot.x = 0; - - const int32_t wh = Room_GetWaterHeight( - item->pos.x, item->pos.y, item->pos.z, item->room_num); - if (item->pos.y > wh) { - item->pos.y -= M_FLOAT_SPEED; - } else if (item->pos.y < wh) { - item->pos.y = wh; - } - - Item_Animate(item); - - int16_t room_num = item->room_num; - const SECTOR *const sector = - Room_GetSector(item->pos.x, item->pos.y, item->pos.z, &room_num); - item->floor = Room_GetHeight(sector, item->pos.x, item->pos.y, item->pos.z); - if (room_num != item->room_num) { - Item_NewRoom(item_num, room_num); - } -} - -void Creature_Underwater(ITEM *const item, const int32_t depth) -{ - const int32_t wh = Room_GetWaterHeight( - item->pos.x, item->pos.y, item->pos.z, item->room_num); - if (item->pos.y >= wh + depth) { - return; - } - - item->pos.y = wh + depth; - if (item->rot.x > 2 * DEG_1) { - item->rot.x -= 2 * DEG_1; - } else { - CLAMPG(item->rot.x, 0); - } -} - -bool Creature_CanTargetEnemy(const ITEM *const item, const AI_INFO *const info) -{ - const CREATURE *const creature = item->data; - const ITEM *const enemy = - creature->enemy != nullptr ? creature->enemy : Lara_GetItem(); - if (!info->ahead || info->distance >= CREATURE_SHOOT_RANGE) { - return false; - } - - GAME_VECTOR start; - start.pos.x = item->pos.x; - start.pos.y = item->pos.y - STEP_L * 3; - start.pos.z = item->pos.z; - start.room_num = item->room_num; - - GAME_VECTOR target; - target.pos.x = enemy->pos.x; - target.pos.y = enemy->pos.y - STEP_L * 3; - target.pos.z = enemy->pos.z; - target.room_num = enemy->room_num; - - return LOS_Check(&start, &target); -} - -bool Creature_CheckBaddieOverlap(const int16_t item_num) -{ - const ITEM *item = Item_Get(item_num); - - const int32_t x = item->pos.x; - const int32_t y = item->pos.y; - const int32_t z = item->pos.z; - const int32_t radius = SQUARE(Object_Get(item->object_id)->radius); - - int16_t link = Room_Get(item->room_num)->item_num; - while (link != NO_ITEM && link != item_num) { - item = Item_Get(link); - if (item != Lara_GetItem() && item->status == IS_ACTIVE - && item->speed != 0) { - const XYZ_32 delta = { - item->pos.x - x, - item->pos.y - y, - item->pos.z - z, - }; - const int32_t distance = - SQUARE(delta.x) + SQUARE(delta.y) + SQUARE(delta.z); - if (distance < radius) { - return true; - } - } - - link = item->next_item; - } - - return false; -} - -void Creature_Collision( - const int16_t item_num, ITEM *const lara_item, COLL_INFO *const coll) -{ - ITEM *const item = Item_Get(item_num); - const LARA_INFO *const lara = Lara_GetLaraInfo(); - if (!Lara_TestBoundsCollide(item, coll->radius)) { - return; - } - if (!Collide_TestCollision(item, lara_item)) { - return; - } - - if (!coll->enable_baddie_push) { - return; - } - - if (TR_VERSION >= 2 - && (lara->water_status == LWS_UNDERWATER - || lara->water_status == LWS_SURFACE)) { - return; - } - - Lara_Push( - item, coll, - (TR_VERSION >= 2 || item->hit_points > 0) ? coll->enable_hit : false, - false); -} - -bool Creature_Animate( - const int16_t item_num, const int16_t angle, const int16_t tilt) -{ - ITEM *const item = Item_Get(item_num); - const CREATURE *const creature = item->data; - const OBJECT *const obj = Object_Get(item->object_id); - if (creature == nullptr) { - return false; - } - - const LOT_INFO *const lot = &creature->lot; - const XYZ_32 old = item->pos; - - const int16_t *const zone = Box_GetLotZone(lot); - - if (TR_VERSION >= 2 && !Object_IsType(item->object_id, g_WaterObjects)) { - int16_t room_num = item->room_num; - Room_GetSector(item->pos.x, item->pos.y, item->pos.z, &room_num); - if (room_num != item->room_num) { - Item_NewRoom(item_num, room_num); - } - } - - Item_Animate(item); - if (item->status == IS_DEACTIVATED) { - Creature_Die(item_num, false); - return false; - } - - const BOUNDS_16 *const bounds = Item_GetBoundsAccurate(item); - int32_t y = item->pos.y + bounds->min.y; - - int16_t room_num = item->room_num; - Room_GetSector(old.x, y, old.z, &room_num); - const SECTOR *sector = - Room_GetSector(item->pos.x, y, item->pos.z, &room_num); - int32_t height = Box_GetBox(sector->box)->height; - int16_t next_box = lot->node[sector->box].exit_box; - int32_t next_height = - next_box != NO_BOX ? Box_GetBox(next_box)->height : height; - - const int32_t box_height = Box_GetBox(item->box_num)->height; - if (sector->box == NO_BOX || zone[item->box_num] != zone[sector->box] - || box_height - height > lot->step || box_height - height < lot->drop) { - const int32_t pos_x = item->pos.x >> WALL_SHIFT; - const int32_t shift_x = old.x >> WALL_SHIFT; - const int32_t shift_z = old.z >> WALL_SHIFT; - - if (pos_x < shift_x) { - item->pos.x = old.x & (~(WALL_L - 1)); - } else if (pos_x > shift_x) { - item->pos.x = old.x | (WALL_L - 1); - } - - if (pos_x < shift_z) { - item->pos.z = old.z & (~(WALL_L - 1)); - } else if (pos_x > shift_z) { - item->pos.z = old.z | (WALL_L - 1); - } - - sector = Room_GetSector(item->pos.x, y, item->pos.z, &room_num); - height = Box_GetBox(sector->box)->height; - next_box = lot->node[sector->box].exit_box; - next_height = - next_box != NO_BOX ? Box_GetBox(next_box)->height : height; - } - - const int32_t x = item->pos.x; - const int32_t z = item->pos.z; - const int32_t pos_x = x & (WALL_L - 1); - const int32_t pos_z = z & (WALL_L - 1); - int32_t shift_x = 0; - int32_t shift_z = 0; - const int32_t radius = obj->radius; - - if (pos_z < radius) { - if (Box_BadFloor( - x, y, z - radius, height, next_height, room_num, lot)) { - shift_z = radius - pos_z; - } - - if (pos_x < radius) { - if (Box_BadFloor( - x - radius, y, z, height, next_height, room_num, lot)) { - shift_x = radius - pos_x; - } else if ( - !shift_z - && Box_BadFloor( - x - radius, y, z - radius, height, next_height, room_num, - lot)) { - if (item->rot.y > -DEG_135 && item->rot.y < DEG_45) { - shift_z = radius - pos_z; - } else { - shift_x = radius - pos_x; - } - } - } else if (pos_x > WALL_L - radius) { - if (Box_BadFloor( - x + radius, y, z, height, next_height, room_num, lot)) { - shift_x = WALL_L - radius - pos_x; - } else if ( - !shift_z - && Box_BadFloor( - x + radius, y, z - radius, height, next_height, room_num, - lot)) { - if (item->rot.y > -DEG_45 && item->rot.y < DEG_135) { - shift_z = radius - pos_z; - } else { - shift_x = WALL_L - radius - pos_x; - } - } - } - } else if (pos_z > WALL_L - radius) { - if (Box_BadFloor( - x, y, z + radius, height, next_height, room_num, lot)) { - shift_z = WALL_L - radius - pos_z; - } - - if (pos_x < radius) { - if (Box_BadFloor( - x - radius, y, z, height, next_height, room_num, lot)) { - shift_x = radius - pos_x; - } else if ( - !shift_z - && Box_BadFloor( - x - radius, y, z + radius, height, next_height, room_num, - lot)) { - if (item->rot.y > -DEG_45 && item->rot.y < DEG_135) { - shift_x = radius - pos_x; - } else { - shift_z = WALL_L - radius - pos_z; - } - } - } else if (pos_x > WALL_L - radius) { - if (Box_BadFloor( - x + radius, y, z, height, next_height, room_num, lot)) { - shift_x = WALL_L - radius - pos_x; - } else if ( - !shift_z - && Box_BadFloor( - x + radius, y, z + radius, height, next_height, room_num, - lot)) { - if (item->rot.y > -DEG_135 && item->rot.y < DEG_45) { - shift_x = WALL_L - radius - pos_x; - } else { - shift_z = WALL_L - radius - pos_z; - } - } - } - } else if (pos_x < radius) { - if (Box_BadFloor( - x - radius, y, z, height, next_height, room_num, lot)) { - shift_x = radius - pos_x; - } - } else if (pos_x > WALL_L - radius) { - if (Box_BadFloor( - x + radius, y, z, height, next_height, room_num, lot)) { - shift_x = WALL_L - radius - pos_x; - } - } - - item->pos.x += shift_x; - item->pos.z += shift_z; - - if (shift_x || shift_z) { - sector = Room_GetSector(item->pos.x, y, item->pos.z, &room_num); - item->rot.y += angle; - Creature_Tilt(item, tilt * 2); - } - - if (Creature_CheckBaddieOverlap(item_num)) { - item->pos = old; - return true; - } - - if (lot->fly) { - int32_t dy = creature->target.y - item->pos.y; - CLAMP(dy, -lot->fly, lot->fly); - - height = Room_GetHeight(sector, item->pos.x, y, item->pos.z); - if (item->pos.y + dy <= height) { - const int32_t ceiling = - Room_GetCeiling(sector, item->pos.x, y, item->pos.z); - int32_t min_y = bounds->min.y; - switch (item->object_id) { -#if TR_VERSION == 1 - case O_ALLIGATOR: - min_y = 0; - break; -#elif TR_VERSION == 2 - case O_SHARK: - min_y = 128; - break; -#endif - default: - break; - } - if (item->pos.y + min_y + dy < ceiling) { - if (item->pos.y + min_y < ceiling) { - item->pos.x = old.x; - item->pos.z = old.z; - dy = lot->fly; - } else { - dy = 0; - } - } - } else if (item->pos.y <= height) { - item->pos.y = height; - dy = 0; - } else { - item->pos.x = old.x; - item->pos.z = old.z; - dy = -lot->fly; - } - - item->pos.y += dy; - sector = Room_GetSector(item->pos.x, y, item->pos.z, &room_num); - item->floor = Room_GetHeight(sector, item->pos.x, y, item->pos.z); - - int16_t angle = item->speed ? Math_Atan(item->speed, -dy) : 0; - if (TR_VERSION >= 2) { - CLAMP(angle, -M_MAX_X_ROT, M_MAX_X_ROT); - } - - if (angle < item->rot.x - DEG_1) { - item->rot.x -= DEG_1; - } else if (angle > item->rot.x + DEG_1) { - item->rot.x += DEG_1; - } else { - item->rot.x = angle; - } - } else { - const SECTOR *const sector = - Room_GetSector(item->pos.x, item->pos.y, item->pos.z, &room_num); - item->floor = - Room_GetHeight(sector, item->pos.x, item->pos.y, item->pos.z); - - if (item->pos.y > item->floor) { - item->pos.y = item->floor; - } else if (item->floor - item->pos.y > STEP_L / 4) { - item->pos.y += STEP_L / 4; - } else if (item->pos.y < item->floor) { - item->pos.y = item->floor; - } - item->rot.x = 0; - } - - if (!Object_IsType(item->object_id, g_WaterObjects)) { - // Get the room just above the enemy so that if it is in one-click high - // water, its effects behave still as though in a dry room. - Room_GetSector( - item->pos.x, item->pos.y - (STEP_L * 2), item->pos.z, &room_num); - if (TR_VERSION >= 2 - && (Room_Get(room_num)->flags & RF_UNDERWATER) != 0) { - item->hit_points = 0; - } - } - - if (item->room_num != room_num) { - Item_NewRoom(item_num, room_num); - } - return true; -} - -void Creature_SpecialKill( - ITEM *const item, const int32_t kill_anim, const int32_t kill_state, - const int32_t lara_kill_state) -{ - LARA_INFO *const lara = Lara_GetLaraInfo(); - ITEM *const lara_item = Lara_GetItem(); - - Item_SwitchToAnim(item, kill_anim, 0); - item->current_anim_state = kill_state; - -#if TR_VERSION == 2 - Item_SwitchToObjAnim(lara_item, LA_EXTRA_BREATH, 0, O_LARA_EXTRA); - lara_item->current_anim_state = LA_EXTRA_BREATH; -#endif - lara_item->goal_anim_state = lara_kill_state; - lara_item->pos = item->pos; - lara_item->rot = item->rot; - lara_item->fall_speed = 0; - lara_item->gravity = 0; - lara_item->speed = 0; - - int16_t room_num = item->room_num; - if (room_num != lara_item->room_num) { - Item_NewRoom(lara->item_num, room_num); - } - - Item_Animate(lara_item); - - lara_item->goal_anim_state = lara_kill_state; - lara_item->current_anim_state = lara_kill_state; -#if TR_VERSION == 2 - lara->extra_anim = 1; -#endif - lara->gun_status = LGS_HANDS_BUSY; - lara->gun_type = LGT_UNARMED; - lara->hit_direction = -1; - lara->air = -1; - - g_Camera.pos.room_num = lara_item->room_num; -} - -void Creature_Die(const int16_t item_num, const bool explode) -{ - ITEM *const item = Item_Get(item_num); - - switch (item->object_id) { -#if TR_VERSION == 2 - case O_DRAGON_FRONT: - item->hit_points = 0; - return; - - case O_SKIDOO_DRIVER: - if (explode) { - Item_Explode(item_num, -1, 0); - } - item->hit_points = DONT_TARGET; - const int16_t vehicle_item_num = (int16_t)(intptr_t)item->data; - ITEM *const vehicle_item = Item_Get(vehicle_item_num); - vehicle_item->hit_points = 0; - return; -#endif - - default: - break; - } - - item->collidable = 0; - item->hit_points = DONT_TARGET; - if (explode) { - Item_Explode(item_num, -1, 0); - Item_Kill(item_num); - } else { - Item_RemoveActive(item_num); - } - - const OBJECT *const obj = Object_Get(item->object_id); - if (obj->intelligent) { - LOT_DisableBaddieAI(item_num); - } - item->flags |= IF_ONE_SHOT; - -#if TR_VERSION >= 2 - if (item->killed) { - item->next_active = Item_GetPrevActive(); - Item_SetPrevActive(item_num); - } - - if (obj->intelligent) { - int16_t pickup_num = item->carried_item; - while (pickup_num != NO_ITEM) { - ITEM *const pickup = Item_Get(pickup_num); - pickup->pos = item->pos; - Item_NewRoom(pickup_num, item->room_num); - pickup_num = pickup->carried_item; - } - } -#else - Carrier_TestItemDrops(item_num); -#endif -} - -int32_t Creature_Vault( - const int16_t item_num, const int16_t angle, int32_t vault, - const int32_t shift) -{ - ITEM *const item = Item_Get(item_num); - const int16_t room_num = item->room_num; - const XYZ_32 old = item->pos; - - Creature_Animate(item_num, angle, 0); - - if (item->floor > old.y + STEP_L * 7 / 2) { - vault = -4; - } else if (item->pos.y > old.y - STEP_L * 3 / 2) { - return 0; - } else if (item->pos.y > old.y - STEP_L * 5 / 2) { - vault = 2; - } else if (item->pos.y > old.y - STEP_L * 7 / 2) { - vault = 3; - } else { - vault = 4; - } - - const int32_t old_x_sector = old.x >> WALL_SHIFT; - const int32_t old_z_sector = old.z >> WALL_SHIFT; - const int32_t x_sector = item->pos.x >> WALL_SHIFT; - const int32_t z_sector = item->pos.z >> WALL_SHIFT; - if (old_z_sector == z_sector) { - if (old_x_sector == x_sector) { - return 0; - } - - if (old_x_sector >= x_sector) { - item->rot.y = -DEG_90; - item->pos.x = (old_x_sector << WALL_SHIFT) + shift; - } else { - item->rot.y = DEG_90; - item->pos.x = (x_sector << WALL_SHIFT) - shift; - } - } else if (old_x_sector == x_sector) { - if (old_z_sector >= z_sector) { - item->rot.y = -DEG_180; - item->pos.z = (old_z_sector << WALL_SHIFT) + shift; - } else { - item->rot.y = 0; - item->pos.z = (z_sector << WALL_SHIFT) - shift; - } - } - - item->floor = old.y; - item->pos.y = old.y; - - if (room_num != item->room_num) { - Item_NewRoom(item_num, room_num); - } - return vault; -} - -bool Creature_AreAlliesHostile(void) -{ - return m_AlliesHostile; -} - -void Creature_SetAlliesHostile(bool enable) -{ - m_AlliesHostile = enable; -} - -bool Creature_IsHostile(const ITEM *const item) -{ - return Object_IsType(item->object_id, g_EnemyObjects) - || (Creature_AreAlliesHostile() && Creature_IsAlly(item)); -} - -bool Creature_IsAlly(const ITEM *const item) -{ - return Object_IsType(item->object_id, g_AllyObjects); -} - -int16_t Creature_Effect( - const ITEM *const item, const BITE *const bite, - int16_t (*const spawn)( - int32_t x, int32_t y, int32_t z, int16_t speed, int16_t y_rot, - int16_t room_num)) -{ - XYZ_32 pos = bite->pos; - Collide_GetJointAbsPosition(item, &pos, bite->mesh_num); - return spawn(pos.x, pos.y, pos.z, item->speed, item->rot.y, item->room_num); -} diff --git a/src/libtrx/game/game.c b/src/libtrx/game/game.c index d7fd1c07d..c8c54120b 100644 --- a/src/libtrx/game/game.c +++ b/src/libtrx/game/game.c @@ -6,7 +6,6 @@ static bool m_IsPlaying = false; static const GF_LEVEL *m_CurrentLevel = nullptr; -static GAME_BONUS_FLAG m_BonusFlag = GBF_NONE; void Game_SetIsPlaying(const bool is_playing) { @@ -50,18 +49,3 @@ bool Game_IsPlayable(void) return true; } - -GAME_BONUS_FLAG Game_GetBonusFlag(void) -{ - return m_BonusFlag; -} - -void Game_SetBonusFlag(const GAME_BONUS_FLAG flag) -{ - m_BonusFlag = flag; -} - -bool Game_IsBonusFlagSet(const GAME_BONUS_FLAG flag) -{ - return (m_BonusFlag & flag) != 0; -} diff --git a/src/libtrx/game/game_flow/common.c b/src/libtrx/game/game_flow/common.c index ab17d7a1d..cc5b08003 100644 --- a/src/libtrx/game/game_flow/common.c +++ b/src/libtrx/game/game_flow/common.c @@ -91,8 +91,9 @@ void GF_Shutdown(void) Memory_FreePointer(&gf->main_menu_background_path); Memory_FreePointer(&gf->savegame_fmt_legacy); +#if TR_VERSION == 1 Memory_FreePointer(&gf->savegame_fmt_bson); -#if TR_VERSION == 2 +#else Memory_FreePointer(&gf->settings.sfx_path); #endif } diff --git a/src/libtrx/game/game_flow/reader.c b/src/libtrx/game/game_flow/reader.c index 507e13e64..8ebdfd4d5 100644 --- a/src/libtrx/game/game_flow/reader.c +++ b/src/libtrx/game/game_flow/reader.c @@ -11,7 +11,6 @@ #include "json.h" #include "log.h" #include "memory.h" -#include "strings.h" #include @@ -72,7 +71,6 @@ static void M_LoadFMV( void *user_arg); static void M_LoadFMVs(JSON_OBJECT *obj, GAME_FLOW *gf); static void M_LoadGlobalInjections(JSON_OBJECT *obj, GAME_FLOW *gf); -static void M_LoadCommonSettings(JSON_OBJECT *obj, GF_LEVEL_SETTINGS *settings); static void M_LoadCommonRoot(JSON_OBJECT *obj, GAME_FLOW *gf); static void M_LoadRoot(JSON_OBJECT *obj, GAME_FLOW *gf); @@ -82,58 +80,6 @@ static void M_LoadRoot(JSON_OBJECT *obj, GAME_FLOW *gf); #include "./reader_tr2.def.c" #endif -static void M_LoadCommonSettings( - JSON_OBJECT *const obj, GF_LEVEL_SETTINGS *const settings) -{ - { - const double value = - JSON_ObjectGetDouble(obj, "fog_start", JSON_INVALID_NUMBER); - if (value != JSON_INVALID_NUMBER) { - settings->fog_start.is_present = true; - settings->fog_start.value = value; - } - } - - { - const double value = - JSON_ObjectGetDouble(obj, "fog_end", JSON_INVALID_NUMBER); - if (value != JSON_INVALID_NUMBER) { - settings->fog_end.is_present = true; - settings->fog_end.value = value; - } - } - - { - JSON_VALUE *const tmp_value = JSON_ObjectGetValue(obj, "water_color"); - if (tmp_value != nullptr && tmp_value->type == JSON_TYPE_ARRAY) { - const JSON_ARRAY *const tmp_arr = JSON_ValueAsArray(tmp_value); - const RGB_F color = { - JSON_ArrayGetDouble(tmp_arr, 0, -1.0), - JSON_ArrayGetDouble(tmp_arr, 1, -1.0), - JSON_ArrayGetDouble(tmp_arr, 2, -1.0), - }; - if (color.r >= 0.0 && color.g >= 0.0 && color.b >= 0.0) { - settings->water_color.is_present = true; - settings->water_color.value = (RGB_888) { - color.r * 255.0f, - color.g * 255.0f, - color.b * 255.0f, - }; - } - } else if ( - tmp_value != nullptr && tmp_value->type == JSON_TYPE_STRING) { - const char *tmp_str = - JSON_ValueGetString(tmp_value, JSON_INVALID_STRING); - ASSERT(tmp_str != JSON_INVALID_STRING); - RGB_888 tmp_color; - if (String_ParseRGB888(tmp_str, &tmp_color)) { - settings->water_color.is_present = true; - settings->water_color.value = tmp_color; - } - } - } -} - static void M_LoadCommonRoot(JSON_OBJECT *const obj, GAME_FLOW *const gf) { const char *tmp_s = @@ -149,12 +95,6 @@ static void M_LoadCommonRoot(JSON_OBJECT *const obj, GAME_FLOW *const gf) Shell_ExitSystem("'savegame_fmt_legacy' must be a string"); } gf->savegame_fmt_legacy = Memory_DupStr(tmp_s); - - tmp_s = JSON_ObjectGetString(obj, "savegame_fmt_bson", JSON_INVALID_STRING); - if (tmp_s == JSON_INVALID_STRING) { - Shell_ExitSystem("'savegame_fmt_bson' must be a string"); - } - gf->savegame_fmt_bson = Memory_DupStr(tmp_s); } static DECLARE_SEQUENCE_EVENT_HANDLER_FUNC(M_HandleIntEvent) @@ -169,23 +109,23 @@ static DECLARE_SEQUENCE_EVENT_HANDLER_FUNC(M_HandleIntEvent) static DECLARE_SEQUENCE_EVENT_HANDLER_FUNC(M_HandlePictureEvent) { const char *const path = JSON_ObjectGetString(event_obj, "path", nullptr); + if (path == nullptr) { + Shell_ExitSystem("Missing picture path"); + return -1; + } if (event != nullptr) { GF_DISPLAY_PICTURE_DATA *const event_data = extra_data; event_data->path = (char *)extra_data + sizeof(GF_DISPLAY_PICTURE_DATA); - event_data->is_legal = JSON_ObjectGetBool(event_obj, "legal", false); event_data->display_time = JSON_ObjectGetDouble(event_obj, "display_time", 5.0); event_data->fade_in_time = JSON_ObjectGetDouble(event_obj, "fade_in_time", 1.0); event_data->fade_out_time = JSON_ObjectGetDouble(event_obj, "fade_out_time", 1.0 / 3.0); - if (path != nullptr) { - strcpy(event_data->path, path); - } + strcpy(event_data->path, path); event->data = event_data; } - return sizeof(GF_DISPLAY_PICTURE_DATA) - + (path == nullptr ? 0 : strlen(path) + 1); + return sizeof(GF_DISPLAY_PICTURE_DATA) + strlen(path) + 1; } static DECLARE_SEQUENCE_EVENT_HANDLER_FUNC(M_HandleTotalStatsEvent) @@ -193,10 +133,8 @@ static DECLARE_SEQUENCE_EVENT_HANDLER_FUNC(M_HandleTotalStatsEvent) const char *const path = JSON_ObjectGetString(event_obj, "background_path", nullptr); if (path == nullptr) { - if (event != nullptr) { - event->data = nullptr; - } - return 0; + Shell_ExitSystem("Missing picture path"); + return -1; } if (event != nullptr) { char *const event_data = extra_data; @@ -281,10 +219,6 @@ static size_t M_LoadSequenceEvent( const char *const type_str = JSON_ObjectGetString(event_obj, "type", ""); const GF_SEQUENCE_EVENT_TYPE type = ENUM_MAP_GET(GF_SEQUENCE_EVENT_TYPE, type_str, -1); - if (type == (GF_SEQUENCE_EVENT_TYPE)-1) { - Shell_ExitSystemFmt( - "Unknown game flow sequence event type: '%s'", type_str); - } const M_SEQUENCE_EVENT_HANDLER *handler = M_GetSequenceEventHandlers(); while (handler->event_type != (GF_SEQUENCE_EVENT_TYPE)-1 @@ -292,6 +226,11 @@ static size_t M_LoadSequenceEvent( handler++; } + if (handler->event_type != type) { + Shell_ExitSystemFmt( + "Unknown game flow sequence event type: '%s'", type); + } + int32_t extra_data_size = 0; if (handler->handler_func != nullptr) { extra_data_size = handler->handler_func( @@ -513,8 +452,7 @@ static void M_LoadTitleLevel(JSON_OBJECT *obj, GAME_FLOW *const gf) JSON_OBJECT *title_obj = JSON_ObjectGetObject(obj, "title"); if (title_obj != nullptr) { gf->title_level = Memory_Alloc(sizeof(GF_LEVEL)); - M_LoadLevel( - title_obj, gf, gf->title_level, 0, (void *)(intptr_t)GFL_TITLE); + M_LoadLevel(title_obj, gf, gf->title_level, 0, GFL_TITLE); } } @@ -527,7 +465,6 @@ static void M_LoadFMV( Shell_ExitSystemFmt("Missing FMV path"); } fmv->path = Memory_DupStr(path); - fmv->is_legal = JSON_ObjectGetBool(obj, "legal", false); } static void M_LoadFMVs(JSON_OBJECT *const obj, GAME_FLOW *const gf) diff --git a/src/libtrx/game/game_flow/reader_tr1.def.c b/src/libtrx/game/game_flow/reader_tr1.def.c index 983e13032..76786a3c7 100644 --- a/src/libtrx/game/game_flow/reader_tr1.def.c +++ b/src/libtrx/game/game_flow/reader_tr1.def.c @@ -7,6 +7,12 @@ static DECLARE_SEQUENCE_EVENT_HANDLER_FUNC(M_HandleMeshSwapEvent); static void M_LoadLevelItemDrops( JSON_OBJECT *obj, const GAME_FLOW *gf, GF_LEVEL *level); +static GF_LEVEL_SETTINGS m_DefaultSettings = { + .water_color = { .r = 0.6, .g = 0.7, .b = 1.0 }, + .draw_distance_fade = 12.0f, + .draw_distance_max = 20.0f, +}; + static M_SEQUENCE_EVENT_HANDLER m_SequenceEventHandlers[] = { // clang-format off // Events without arguments @@ -98,7 +104,33 @@ static DECLARE_SEQUENCE_EVENT_HANDLER_FUNC(M_HandleMeshSwapEvent) static void M_LoadSettings( JSON_OBJECT *const obj, GF_LEVEL_SETTINGS *const settings) { - M_LoadCommonSettings(obj, settings); + { + const double value = JSON_ObjectGetDouble( + obj, "draw_distance_fade", JSON_INVALID_NUMBER); + if (value != JSON_INVALID_NUMBER) { + settings->draw_distance_fade = value; + } + } + + { + const double value = + JSON_ObjectGetDouble(obj, "draw_distance_max", JSON_INVALID_NUMBER); + if (value != JSON_INVALID_NUMBER) { + settings->draw_distance_max = value; + } + } + + { + JSON_ARRAY *const tmp_arr = JSON_ObjectGetArray(obj, "water_color"); + if (tmp_arr != nullptr) { + settings->water_color.r = + JSON_ArrayGetDouble(tmp_arr, 0, settings->water_color.r); + settings->water_color.g = + JSON_ArrayGetDouble(tmp_arr, 1, settings->water_color.g); + settings->water_color.b = + JSON_ArrayGetDouble(tmp_arr, 2, settings->water_color.b); + } + } } static void M_LoadLevelGameSpecifics( @@ -196,15 +228,23 @@ static void M_LoadLevelItemDrops( static void M_LoadRoot(JSON_OBJECT *const obj, GAME_FLOW *const gf) { + const char *tmp_s; double tmp_d; JSON_ARRAY *tmp_arr; + tmp_s = JSON_ObjectGetString(obj, "savegame_fmt_bson", JSON_INVALID_STRING); + if (tmp_s == JSON_INVALID_STRING) { + Shell_ExitSystem("'savegame_fmt_bson' must be a string"); + } + gf->savegame_fmt_bson = Memory_DupStr(tmp_s); + tmp_d = JSON_ObjectGetDouble(obj, "demo_delay", -1.0); if (tmp_d < 0.0) { Shell_ExitSystem("'demo_delay' must be a positive number"); } gf->demo_delay = tmp_d; + gf->settings = m_DefaultSettings; M_LoadSettings(obj, &gf->settings); gf->enable_tr2_item_drops = diff --git a/src/libtrx/game/game_flow/reader_tr2.def.c b/src/libtrx/game/game_flow/reader_tr2.def.c index 88ffbbf0b..db2a5be22 100644 --- a/src/libtrx/game/game_flow/reader_tr2.def.c +++ b/src/libtrx/game/game_flow/reader_tr2.def.c @@ -25,6 +25,7 @@ static M_SEQUENCE_EVENT_HANDLER m_SequenceEventHandlers[] = { { GFS_EXIT_TO_TITLE, nullptr, nullptr }, // Events with integer arguments + { GFS_SET_NUM_SECRETS, M_HandleIntEvent, "count" }, { GFS_SET_CAMERA_ANGLE, M_HandleIntEvent, "angle" }, { GFS_SET_START_ANIM, M_HandleIntEvent, "anim" }, { GFS_LOOP_GAME, M_HandleIntEvent, "level_id" }, @@ -47,8 +48,6 @@ static M_SEQUENCE_EVENT_HANDLER m_SequenceEventHandlers[] = { static void M_LoadSettings( JSON_OBJECT *const obj, GF_LEVEL_SETTINGS *const settings) { - M_LoadCommonSettings(obj, settings); - { const char *tmp_s = JSON_ObjectGetString(obj, "sfx_path", JSON_INVALID_STRING); diff --git a/src/libtrx/game/game_flow/sequencer.c b/src/libtrx/game/game_flow/sequencer.c index 15eb39360..a2fdc74c3 100644 --- a/src/libtrx/game/game_flow/sequencer.c +++ b/src/libtrx/game/game_flow/sequencer.c @@ -81,6 +81,10 @@ GF_COMMAND GF_InterpretSequence( const GF_SEQUENCE *const sequence = &level->sequence; for (int32_t i = 0; i < sequence->length; i++) { const GF_SEQUENCE_EVENT *const event = &sequence->events[i]; + if (GF_ShouldSkipSequenceEvent(level, event)) { + continue; + } + if (M_PostponeEvent(event)) { continue; } diff --git a/src/libtrx/game/game_flow/sequencer_events.c b/src/libtrx/game/game_flow/sequencer_events.c index 159441465..77744cdd4 100644 --- a/src/libtrx/game/game_flow/sequencer_events.c +++ b/src/libtrx/game/game_flow/sequencer_events.c @@ -37,7 +37,7 @@ static DECLARE_GF_EVENT_HANDLER(M_HandlePlayCutscene) { GF_COMMAND gf_cmd = { .action = GF_NOOP }; const int16_t cutscene_num = (int16_t)(intptr_t)event->data; - if (seq_ctx != GFSC_SAVED && g_Config.gameplay.enable_cutscenes) { + if (seq_ctx != GFSC_SAVED) { gf_cmd = GF_DoCutsceneSequence(cutscene_num); if (gf_cmd.action == GF_LEVEL_COMPLETE) { gf_cmd.action = GF_NOOP; @@ -50,18 +50,13 @@ static DECLARE_GF_EVENT_HANDLER(M_HandlePlayFMV) { GF_COMMAND gf_cmd = { .action = GF_NOOP }; const int16_t fmv_id = (int16_t)(intptr_t)event->data; - if (seq_ctx == GFSC_SAVED) { - return gf_cmd; + if (seq_ctx != GFSC_SAVED) { + if (fmv_id < 0 || fmv_id >= g_GameFlow.fmv_count) { + LOG_ERROR("Invalid FMV number: %d", fmv_id); + } else { + FMV_Play(g_GameFlow.fmvs[fmv_id].path); + } } - if (fmv_id < 0 || fmv_id >= g_GameFlow.fmv_count) { - LOG_ERROR("Invalid FMV number: %d", fmv_id); - return gf_cmd; - } - const GF_FMV *const fmv = &g_GameFlow.fmvs[fmv_id]; - if (fmv->is_legal && !g_Config.gameplay.enable_legal) { - return gf_cmd; - } - FMV_Play(fmv->path); return gf_cmd; } @@ -78,11 +73,14 @@ static DECLARE_GF_EVENT_HANDLER(M_HandlePicture) return gf_cmd; } - GF_DISPLAY_PICTURE_DATA *data = event->data; - if (data->is_legal && !g_Config.gameplay.enable_legal) { +#if TR_VERSION == 1 + if (Game_GetCurrentLevel() == nullptr + && !g_Config.gameplay.enable_eidos_logo) { return gf_cmd; } +#endif + GF_DISPLAY_PICTURE_DATA *data = event->data; PHASE *const phase = Phase_Picture_Create((PHASE_PICTURE_ARGS) { .file_name = data->path, .display_time = data->display_time, @@ -101,19 +99,12 @@ static DECLARE_GF_EVENT_HANDLER(M_HandleLevelStats) if (seq_ctx != GFSC_NORMAL) { return gf_cmd; } - -#if TR_VERSION == 1 - const bool use_bare_style = g_Config.gameplay.stat_detail_mode != SDM_FULL; -#else - const bool use_bare_style = false; -#endif - PHASE *const phase = Phase_Stats_Create((PHASE_STATS_ARGS) { .background_type = (TR_VERSION == 1 || Game_IsInGym()) ? BK_TRANSPARENT : BK_OBJECT, .level_num = -1, .show_final_stats = false, - .use_bare_style = use_bare_style, + .use_bare_style = TR_VERSION == 1, }); gf_cmd = PhaseExecutor_Run(phase); Phase_Stats_Destroy(phase); diff --git a/src/libtrx/game/game_flow/sequencer_priv.h b/src/libtrx/game/game_flow/sequencer_priv.h index 9ac810c1b..fc02aff74 100644 --- a/src/libtrx/game/game_flow/sequencer_priv.h +++ b/src/libtrx/game/game_flow/sequencer_priv.h @@ -3,6 +3,8 @@ extern void GF_PreSequenceHook(GF_SEQUENCE_CONTEXT seq_ctx, void *seq_ctx_arg); extern GF_SEQUENCE_CONTEXT GF_SwitchSequenceContext( const GF_SEQUENCE_EVENT *event, GF_SEQUENCE_CONTEXT seq_ctx); +extern bool GF_ShouldSkipSequenceEvent( + const GF_LEVEL *level, const GF_SEQUENCE_EVENT *event); // Defer execution of certain events to run it at various stages of // GFS_LOOP_GAME. diff --git a/src/libtrx/game/game_string_table/common.c b/src/libtrx/game/game_string_table/common.c index 3d31c1738..d3f8327dc 100644 --- a/src/libtrx/game/game_string_table/common.c +++ b/src/libtrx/game/game_string_table/common.c @@ -1,11 +1,9 @@ #include "debug.h" -#include "filesystem.h" #include "game/game_flow.h" #include "game/game_string.h" #include "game/game_string_table.h" #include "game/game_string_table/priv.h" #include "game/objects/names.h" -#include "game/shell.h" #include "log.h" #include "memory.h" @@ -13,6 +11,8 @@ typedef void (*M_LOAD_STRING_FUNC)(const char *, const char *); +GS_FILE g_GST_File = {}; + static struct { GAME_OBJECT_ID target_object_id; GAME_OBJECT_ID source_object_id; @@ -27,8 +27,6 @@ static struct { { .target_object_id = NO_OBJECT }, }; -static VECTOR *m_GST_Layers = nullptr; - static void M_Apply(const GS_TABLE *table); static void M_ApplyLevelTitles( const GS_FILE *gs_file, GF_LEVEL_TABLE_TYPE level_table_type); @@ -92,19 +90,17 @@ static void M_ApplyLevelTitles( GF_GetLevelTable(level_table_type); const GS_LEVEL_TABLE *const gs_level_table = &gs_file->level_tables[level_table_type]; - if (gs_level_table->count == 0) { - return; - } - ASSERT(gs_level_table->count == level_table->count); for (int32_t i = 0; i < level_table->count; i++) { GF_SetLevelTitle( &level_table->levels[i], gs_level_table->entries[i].title); } } -static void M_ApplyLayer( - const GF_LEVEL *const level, const GS_FILE *const gs_file) +void GameStringTable_Apply(const GF_LEVEL *const level) { + const GS_FILE *const gs_file = &g_GST_File; + + Object_ResetNames(); M_Apply(&gs_file->global); for (int32_t i = 0; i < GFLT_NUMBER_OF; i++) { @@ -124,50 +120,16 @@ static void M_ApplyLayer( } } - if (gs_level_table != nullptr && gs_level_table->count != 0) { + if (gs_level_table != nullptr) { ASSERT(level->num >= 0); ASSERT(level->num < gs_level_table->count); M_Apply(&gs_level_table->entries[level->num].table); } } -} - -void GameStringTable_Apply(const GF_LEVEL *const level) -{ - Object_ResetNames(); - ASSERT(m_GST_Layers != nullptr); - for (int32_t i = 0; i < m_GST_Layers->count; i++) { - const GS_FILE *const gs_file = *(GS_FILE **)Vector_Get(m_GST_Layers, i); - M_ApplyLayer(level, gs_file); - } M_DoObjectAliases(); } -void GameStringTable_Init(void) -{ - m_GST_Layers = Vector_Create(sizeof(GS_FILE *)); -} - void GameStringTable_Shutdown(void) { - if (m_GST_Layers != nullptr) { - for (int32_t i = 0; i < m_GST_Layers->count; i++) { - GS_FILE *const gs_file = *(GS_FILE **)Vector_Get(m_GST_Layers, i); - GS_File_Free(gs_file); - } - Vector_Free(m_GST_Layers); - m_GST_Layers = nullptr; - } -} - -void GameStringTable_Load(const char *const path, const bool load_levels) -{ - char *data = nullptr; - if (!File_Load(path, &data, nullptr)) { - Shell_ExitSystemFmt("failed to open strings file (path: %d)", path); - } - GS_FILE *gs_file = GS_File_CreateFromString(data, load_levels); - ASSERT(m_GST_Layers != nullptr); - Vector_Add(m_GST_Layers, &gs_file); - Memory_FreePointer(&data); + GS_File_Free(&g_GST_File); } diff --git a/src/libtrx/game/game_string_table/priv.c b/src/libtrx/game/game_string_table/priv.c index a9bada043..0b62cc41b 100644 --- a/src/libtrx/game/game_string_table/priv.c +++ b/src/libtrx/game/game_string_table/priv.c @@ -52,5 +52,4 @@ void GS_File_Free(GS_FILE *const gs_file) for (int32_t i = 0; i < GFLT_NUMBER_OF; i++) { M_FreeLevelsTable(&gs_file->level_tables[i]); } - Memory_Free(gs_file); } diff --git a/src/libtrx/game/game_string_table/priv.h b/src/libtrx/game/game_string_table/priv.h index 9533c5989..00d69b7f0 100644 --- a/src/libtrx/game/game_string_table/priv.h +++ b/src/libtrx/game/game_string_table/priv.h @@ -1,7 +1,6 @@ #pragma once #include "game/game_flow/enum.h" -#include "vector.h" #include @@ -36,7 +35,7 @@ typedef struct { GS_LEVEL_TABLE level_tables[GFLT_NUMBER_OF]; } GS_FILE; -void GS_Table_Free(GS_TABLE *gs_table); +extern GS_FILE g_GST_File; -GS_FILE *GS_File_CreateFromString(const char *data, bool load_levels); +void GS_Table_Free(GS_TABLE *gs_table); void GS_File_Free(GS_FILE *gs_file); diff --git a/src/libtrx/game/game_string_table/reader.c b/src/libtrx/game/game_string_table/reader.c index 5ded75216..4cec681d4 100644 --- a/src/libtrx/game/game_string_table/reader.c +++ b/src/libtrx/game/game_string_table/reader.c @@ -1,3 +1,4 @@ +#include "filesystem.h" #include "game/game_flow.h" #include "game/game_string_table.h" #include "game/game_string_table/priv.h" @@ -129,14 +130,24 @@ static void M_LoadLevelsFromJSON( } } -GS_FILE *GS_File_CreateFromString( - const char *const data, const bool load_levels) +void GameStringTable_LoadFromFile(const char *const path) { - GS_FILE *const gs_file = Memory_Alloc(sizeof(GS_FILE)); + char *data = nullptr; + if (!File_Load(path, &data, nullptr)) { + Shell_ExitSystemFmt("failed to open strings file (path: %d)", path); + } + GameStringTable_LoadFromString(data); + Memory_FreePointer(&data); +} + +void GameStringTable_LoadFromString(const char *const data) +{ + GameStringTable_Shutdown(); + + JSON_VALUE *root = nullptr; JSON_PARSE_RESULT parse_result; - - JSON_VALUE *root = JSON_ParseEx( + root = JSON_ParseEx( data, strlen(data), JSON_PARSE_FLAGS_ALLOW_JSON5, nullptr, nullptr, &parse_result); if (root == nullptr) { @@ -146,16 +157,15 @@ GS_FILE *GS_File_CreateFromString( parse_result.error_line_no, parse_result.error_row_no, data); } + GS_FILE *const gs_file = &g_GST_File; JSON_OBJECT *root_obj = JSON_ValueAsObject(root); M_LoadTableFromJSON(root_obj, &gs_file->global); - if (load_levels) { - M_LoadLevelsFromJSON(root_obj, gs_file, "levels", GFLT_MAIN); - M_LoadLevelsFromJSON(root_obj, gs_file, "demos", GFLT_DEMOS); - M_LoadLevelsFromJSON(root_obj, gs_file, "cutscenes", GFLT_CUTSCENES); - } + M_LoadLevelsFromJSON(root_obj, gs_file, "levels", GFLT_MAIN); + M_LoadLevelsFromJSON(root_obj, gs_file, "demos", GFLT_DEMOS); + M_LoadLevelsFromJSON(root_obj, gs_file, "cutscenes", GFLT_CUTSCENES); + if (root != nullptr) { JSON_ValueFree(root); root = nullptr; } - return gs_file; } diff --git a/src/libtrx/game/gym.c b/src/libtrx/game/gym.c deleted file mode 100644 index e51035d44..000000000 --- a/src/libtrx/game/gym.c +++ /dev/null @@ -1,137 +0,0 @@ -#include "game/gym.h" - -#include "config.h" -#include "game/const.h" -#include "game/game.h" -#include "game/game_flow.h" -#include "game/music.h" -#include "game/savegame.h" -#include "game/stats.h" - -#define NO_TIME (-1) - -static bool m_IsInventoryOpenEnabled = true; -static bool m_IsAssaultTimerDisplay = false; -static bool m_IsAssaultTimerActive = false; - -static int32_t M_GetBestTime(void); -static bool M_StoreAssaultTime(uint32_t time); - -static int32_t M_GetBestTime(void) -{ - const ASSAULT_STATS *const assault = &g_Config.profile.assault_stats; - return assault->total_attempts > 0 ? (int32_t)assault->entries[0].time - : NO_TIME; -} - -static bool M_StoreAssaultTime(const uint32_t time) -{ - ASSAULT_STATS *const assault = &g_Config.profile.assault_stats; - int32_t insert_idx = -1; - for (int32_t i = 0; i < MAX_ASSAULT_TIMES; i++) { - if (assault->entries[i].time == 0 || time < assault->entries[i].time) { - insert_idx = i; - break; - } - } - if (insert_idx == -1) { - return false; - } - - for (int32_t i = MAX_ASSAULT_TIMES - 1; i > insert_idx; i--) { - assault->entries[i] = assault->entries[i - 1]; - } - - assault->total_attempts++; - assault->entries[insert_idx].time = time; - assault->entries[insert_idx].attempt_num = assault->total_attempts; - Config_Write(); - return true; -} - -void Gym_SetInventoryOpenEnabled(const bool enabled) -{ - m_IsInventoryOpenEnabled = enabled; -} - -bool Gym_IsInventoryOpenEnabled(void) -{ - return m_IsInventoryOpenEnabled; -} - -bool Gym_IsAssaultTimerDisplay(void) -{ - return m_IsAssaultTimerDisplay; -} - -bool Gym_IsAssaultTimerActive(void) -{ - return m_IsAssaultTimerActive; -} - -ASSAULT_STATS Gym_GetAssaultStats(void) -{ - return g_Config.profile.assault_stats; -} - -void Gym_ResetAssault(void) -{ - m_IsAssaultTimerActive = false; - m_IsAssaultTimerDisplay = false; -} - -void Gym_StartAssault(void) -{ - RESUME_INFO *const resume = Savegame_GetCurrentInfo(Game_GetCurrentLevel()); - resume->stats.timer = 0; - m_IsAssaultTimerActive = true; - m_IsAssaultTimerDisplay = true; - Stats_StartTimer(); -} - -void Gym_StopAssault(void) -{ - m_IsAssaultTimerActive = false; - m_IsAssaultTimerDisplay = true; -} - -void Gym_FinishAssault(void) -{ - if (!m_IsAssaultTimerActive) { - return; - } - - const int32_t current_best_time = M_GetBestTime(); - ASSAULT_STATS *const assault = &g_Config.profile.assault_stats; - const RESUME_INFO *const resume = - Savegame_GetCurrentInfo(Game_GetCurrentLevel()); - M_StoreAssaultTime(resume->stats.timer); - - if (current_best_time <= 0) { - if (resume->stats.timer < 100 * LOGIC_FPS) { - // "Gosh! That was my best time yet!" - Music_Play(MX_GYM_HINT_15, MPM_ALWAYS); - } else { - // "Congratulations! You did it! But perhaps I could've been - // faster." - Music_Play(MX_GYM_HINT_17, MPM_ALWAYS); - } - } else if (resume->stats.timer < (uint32_t)current_best_time) { - // "Gosh! That was my best time yet!" - Music_Play(MX_GYM_HINT_15, MPM_ALWAYS); - } else if ( - resume->stats.timer < (uint32_t)current_best_time + 5 * LOGIC_FPS) { - // "Almost. Perhaps another try and I might beat it." - Music_Play(MX_GYM_HINT_16, MPM_ALWAYS); - } else { - // "Great. But nowhere near my best time." - Music_Play(MX_GYM_HINT_14, MPM_ALWAYS); - } - - m_IsAssaultTimerActive = false; -} - -bool Gym_HasAssaultStats(void) -{ - return TR_VERSION >= 2; -} diff --git a/src/libtrx/game/inject/common.c b/src/libtrx/game/inject/common.c index d66c016b3..d7fa374e2 100644 --- a/src/libtrx/game/inject/common.c +++ b/src/libtrx/game/inject/common.c @@ -180,8 +180,6 @@ static bool M_IsRelevant(const INJECTION_FILE_TYPE type) return g_Config.gameplay.fix_floor_data_issues; case IFT_ITEM_POSITION: return g_Config.visuals.fix_item_rots; - case IFT_TEXTURE_FIX: - return g_Config.visuals.fix_texture_issues; #if TR_VERSION == 1 case IFT_LARA_ANIMS: return true; @@ -189,6 +187,8 @@ static bool M_IsRelevant(const INJECTION_FILE_TYPE type) return g_Config.visuals.enable_braid; case IFT_UZI_SFX: return g_Config.audio.enable_ps_uzi_sfx; + case IFT_TEXTURE_FIX: + return g_Config.visuals.fix_texture_issues; case IFT_PS1_ENEMY: return g_Config.gameplay.restore_ps1_enemies; case IFT_DISABLE_ANIM_SPRITE: diff --git a/src/libtrx/game/inject/data/anims.c b/src/libtrx/game/inject/data/anims.c index 79429b6ec..a161af22e 100644 --- a/src/libtrx/game/inject/data/anims.c +++ b/src/libtrx/game/inject/data/anims.c @@ -1,11 +1,9 @@ #include "debug.h" #include "game/anims.h" #include "game/inject.h" -#include "game/objects.h" #include "memory.h" static void M_HandleAnimData(INJECTION_CHUNK chunk); -static void M_CommandEdits(const INJECTION *injection, int32_t data_count); static void M_HandleAnimData(const INJECTION_CHUNK chunk) { @@ -92,28 +90,4 @@ static void M_HandleAnimData(const INJECTION_CHUNK chunk) } } -static void M_CommandEdits( - const INJECTION *const injection, const int32_t data_count) -{ - const LEVEL_INFO cached_info = Inject_GetCachedInfo(); - int16_t cmd_idx = cached_info.anims.command_count; - for (int32_t i = 0; i < data_count; i++) { - const GAME_OBJECT_ID obj_id = VFile_ReadS32(injection->fp); - const int32_t anim_idx = VFile_ReadS32(injection->fp); - const int32_t num_raw_cmds = VFile_ReadS32(injection->fp); - const int32_t num_anim_cmds = VFile_ReadS32(injection->fp); - - const OBJECT *const obj = Object_Get(obj_id); - if (!obj->loaded) { - continue; - } - - ANIM *const anim = Object_GetAnim(obj, anim_idx); - anim->command_idx = cmd_idx; - anim->num_commands = num_anim_cmds; - cmd_idx += num_raw_cmds; - } -} - REGISTER_INJECTOR(ICT_ANIMATION_DATA, M_HandleAnimData) -REGISTER_INJECT_EDITOR(IDT_ANIM_CMD_EDITS, M_CommandEdits) diff --git a/src/libtrx/game/inject/data/textures.c b/src/libtrx/game/inject/data/textures.c index f970ad9d9..218bb32a9 100644 --- a/src/libtrx/game/inject/data/textures.c +++ b/src/libtrx/game/inject/data/textures.c @@ -147,7 +147,7 @@ static void M_HandleSpriteSequences( obj->mesh_count = num_meshes; obj->mesh_idx = mesh_idx + level_info->textures.sprite_count; obj->loaded = true; - } else if (obj_id - O_NUMBER_OF < MAX_STATIC_OBJECTS_2D) { + } else if (obj_id - O_NUMBER_OF < MAX_STATIC_OBJECTS) { STATIC_OBJECT_2D *const obj = Object_Get2DStatic(obj_id - O_NUMBER_OF); obj->frame_count = ABS(num_meshes); diff --git a/src/libtrx/game/inject/editors/meshes.c b/src/libtrx/game/inject/editors/meshes.c index a26fc7d7d..9544fae4b 100644 --- a/src/libtrx/game/inject/editors/meshes.c +++ b/src/libtrx/game/inject/editors/meshes.c @@ -104,7 +104,7 @@ static void M_ApplyMeshEdit(const MESH_EDIT *const edit) } mesh = Object_GetMesh(obj->mesh_idx + edit->mesh_idx); - } else if (edit->object_id - O_NUMBER_OF < MAX_STATIC_OBJECTS_3D) { + } else if (edit->object_id - O_NUMBER_OF < MAX_STATIC_OBJECTS) { const STATIC_OBJECT_3D *const obj = Object_Get3DStatic(edit->object_id - O_NUMBER_OF); mesh = Object_GetMesh(obj->mesh_idx); diff --git a/src/libtrx/game/inject/editors/rooms.c b/src/libtrx/game/inject/editors/rooms.c index 2e4f02f80..b2a62b421 100644 --- a/src/libtrx/game/inject/editors/rooms.c +++ b/src/libtrx/game/inject/editors/rooms.c @@ -299,7 +299,7 @@ static void M_RoomPortalEdits( const ROOM *const room = Room_Get(base_room); PORTAL *portal = nullptr; for (int32_t j = 0; j < room->portals->count; j++) { - const PORTAL room_portal = room->portals->portal[j]; + PORTAL room_portal = room->portals->portal[j]; if (room_portal.room_num == link_room && j == portal_index) { portal = &room->portals->portal[j]; break; @@ -314,23 +314,10 @@ static void M_RoomPortalEdits( continue; } - bool empty_portal = true; for (int32_t j = 0; j < 4; j++) { portal->vertex[j].x += VFile_ReadS16(injection->fp); portal->vertex[j].y += VFile_ReadS16(injection->fp); portal->vertex[j].z += VFile_ReadS16(injection->fp); - empty_portal &= portal->vertex[j].x == 0 && portal->vertex[j].y == 0 - && portal->vertex[j].z == 0; - } - - // An injection that has reset all vertices such that the portal size is - // now 0, should be interpreted as a command to ignore that portal. - if (empty_portal) { - for (int32_t j = portal_index + 1; j < room->portals->count; j++) { - room->portals->portal[j - 1] = room->portals->portal[j]; - } - room->portals->portal[room->portals->count - 1] = *portal; - room->portals->count--; } } } diff --git a/src/libtrx/game/input/backends/keyboard.c b/src/libtrx/game/input/backends/keyboard.c index 682d1fa13..440b93eb8 100644 --- a/src/libtrx/game/input/backends/keyboard.c +++ b/src/libtrx/game/input/backends/keyboard.c @@ -299,8 +299,7 @@ static bool M_Key(const INPUT_LAYOUT layout, const INPUT_ROLE role) return false; } #ifdef _WIN32 - if (scancode == SDL_SCANCODE_F4 - && (KEY_DOWN(SDL_SCANCODE_LALT) || KEY_DOWN(SDL_SCANCODE_RALT))) { + if (scancode == SDL_SCANCODE_F4 && KEY_DOWN(SDL_SCANCODE_LALT)) { return false; } #endif diff --git a/src/libtrx/game/input/common.c b/src/libtrx/game/input/common.c index e4202f48f..16ae6e33e 100644 --- a/src/libtrx/game/input/common.c +++ b/src/libtrx/game/input/common.c @@ -53,10 +53,10 @@ static bool m_IsRoleHardcoded[INPUT_ROLE_NUMBER_OF] = { }; static const GAME_STRING_ID m_LayoutMap[INPUT_LAYOUT_NUMBER_OF] = { - [INPUT_LAYOUT_DEFAULT] = GS_ID(CONTROLS_DEFAULT_KEYS), - [INPUT_LAYOUT_CUSTOM_1] = GS_ID(CONTROLS_CUSTOM_1), - [INPUT_LAYOUT_CUSTOM_2] = GS_ID(CONTROLS_CUSTOM_2), - [INPUT_LAYOUT_CUSTOM_3] = GS_ID(CONTROLS_CUSTOM_3), + [INPUT_LAYOUT_DEFAULT] = GS_ID(CONTROL_DEFAULT_KEYS), + [INPUT_LAYOUT_CUSTOM_1] = GS_ID(CONTROL_CUSTOM_1), + [INPUT_LAYOUT_CUSTOM_2] = GS_ID(CONTROL_CUSTOM_2), + [INPUT_LAYOUT_CUSTOM_3] = GS_ID(CONTROL_CUSTOM_3), }; static INPUT_BACKEND_IMPL *M_GetBackend(INPUT_BACKEND backend); diff --git a/src/libtrx/game/inventory.c b/src/libtrx/game/inventory.c index b3fe43546..0d552545e 100644 --- a/src/libtrx/game/inventory.c +++ b/src/libtrx/game/inventory.c @@ -4,8 +4,6 @@ #include "game/objects/vars.h" #include "utils.h" -INVENTORY_MODE g_Inv_Mode = INV_TITLE_MODE; - bool Inv_AddItemNTimes(const GAME_OBJECT_ID obj_id, const int32_t qty) { bool result = false; diff --git a/src/libtrx/game/items.c b/src/libtrx/game/items.c index f92b67299..ac9ee6329 100644 --- a/src/libtrx/game/items.c +++ b/src/libtrx/game/items.c @@ -219,11 +219,15 @@ int32_t Item_GlobalReplace( { int32_t changed = 0; - for (int32_t item_num = 0; item_num < m_MaxUsedItemCount; item_num++) { - ITEM *const item = &m_Items[item_num]; - if (item->object_id == src_obj_id) { - item->object_id = dst_obj_id; - changed++; + for (int32_t i = 0; i < Room_GetCount(); i++) { + int16_t item_num = Room_Get(i)->item_num; + while (item_num != NO_ITEM) { + ITEM *const item = &m_Items[item_num]; + if (item->object_id == src_obj_id) { + item->object_id = dst_obj_id; + changed++; + } + item_num = item->next_item; } } @@ -297,11 +301,7 @@ void Item_SwitchToObjAnim( const GAME_OBJECT_ID obj_id) { const OBJECT *const obj = Object_Get(obj_id); - if (obj->anim_idx == NO_ANIM) { - item->anim_num = NO_ANIM; - } else { - item->anim_num = obj->anim_idx + anim_idx; - } + item->anim_num = obj->anim_idx + anim_idx; const ANIM *const anim = Item_GetAnim(item); if (frame < 0) { @@ -506,57 +506,3 @@ void Item_PlayAnimSFX( Sound_Effect(data->effect_num, &item->pos, play_mode); } - -bool Item_TestBoundsCollide( - const ITEM *const src_item, const ITEM *const dst_item, - const int32_t radius) -{ - const BOUNDS_16 *const src_bounds = &Item_GetBestFrame(src_item)->bounds; - const BOUNDS_16 *const dst_bounds = &Item_GetBestFrame(dst_item)->bounds; - - if (src_item->pos.y + src_bounds->min.y - >= dst_item->pos.y + dst_bounds->max.y - || src_item->pos.y + src_bounds->max.y - <= dst_item->pos.y + dst_bounds->min.y) { - return false; - } - - const int32_t c = Math_Cos(src_item->rot.y); - const int32_t s = Math_Sin(src_item->rot.y); - const int32_t dx = dst_item->pos.x - src_item->pos.x; - const int32_t dz = dst_item->pos.z - src_item->pos.z; - const int32_t rx = (c * dx - s * dz) >> W2V_SHIFT; - const int32_t rz = (c * dz + s * dx) >> W2V_SHIFT; - - // clang-format off - return ( - rx >= src_bounds->min.x - radius && - rx <= src_bounds->max.x + radius && - rz >= src_bounds->min.z - radius && - rz <= src_bounds->max.z + radius); - // clang-format on -} - -bool Item_IsTriggerActive(ITEM *const item) -{ - const bool ok = !(item->flags & IF_REVERSE); - - if ((item->flags & IF_CODE_BITS) != IF_CODE_BITS) { - return !ok; - } - - if (!item->timer) { - return ok; - } - - if (item->timer == -1) { - return !ok; - } - - item->timer--; - if (item->timer == 0) { - item->timer = -1; - } - - return ok; -} diff --git a/src/libtrx/game/lara/common.c b/src/libtrx/game/lara/common.c index ad6136f5a..57501bc07 100644 --- a/src/libtrx/game/lara/common.c +++ b/src/libtrx/game/lara/common.c @@ -2,9 +2,7 @@ #include "game/const.h" #include "game/item_actions.h" -#include "game/lara/const.h" -#include "game/matrix.h" -#include "game/rooms.h" +#include "game/rooms/const.h" void Lara_Animate(ITEM *const item) { @@ -148,98 +146,3 @@ const ANIM_FRAME *Lara_GetHitFrame(const ITEM *const item) const ANIM *const anim = Object_GetAnim(obj, anim_idx); return &anim->frame_ptr[lara->hit_frame]; } - -void Lara_TakeDamage(const int16_t damage, const bool hit_status) -{ - Item_TakeDamage(Lara_GetItem(), damage, hit_status); -} - -bool Lara_TestBoundsCollide(const ITEM *const item, const int32_t radius) -{ - return Item_TestBoundsCollide(item, Lara_GetItem(), radius); -} - -bool Lara_TestPosition( - const ITEM *const item, const OBJECT_BOUNDS *const bounds) -{ - const ITEM *const lara = Lara_GetItem(); - const XYZ_16 rot = { - .x = lara->rot.x - item->rot.x, - .y = lara->rot.y - item->rot.y, - .z = lara->rot.z - item->rot.z, - }; - const XYZ_32 dist = { - .x = lara->pos.x - item->pos.x, - .y = lara->pos.y - item->pos.y, - .z = lara->pos.z - item->pos.z, - }; - - // clang-format off - if (rot.x < bounds->rot.min.x || - rot.x > bounds->rot.max.x || - rot.y < bounds->rot.min.y || - rot.y > bounds->rot.max.y || - rot.z < bounds->rot.min.z || - rot.z > bounds->rot.max.z - ) { - return false; - } - // clang-format on - - Matrix_PushUnit(); - Matrix_Rot16(item->rot); - const MATRIX *const m = g_MatrixPtr; - const XYZ_32 shift = { - .x = (dist.x * m->_00 + dist.y * m->_10 + dist.z * m->_20) >> W2V_SHIFT, - .y = (dist.x * m->_01 + dist.y * m->_11 + dist.z * m->_21) >> W2V_SHIFT, - .z = (dist.x * m->_02 + dist.y * m->_12 + dist.z * m->_22) >> W2V_SHIFT, - }; - Matrix_Pop(); - - // clang-format off - return ( - shift.x >= bounds->shift.min.x && - shift.x <= bounds->shift.max.x && - shift.y >= bounds->shift.min.y && - shift.y <= bounds->shift.max.y && - shift.z >= bounds->shift.min.z && - shift.z <= bounds->shift.max.z - ); - // clang-format on -} - -void Lara_AlignPosition(const ITEM *const item, const XYZ_32 *const vec) -{ - ITEM *const lara = Lara_GetItem(); - lara->rot = item->rot; - Matrix_PushUnit(); - Matrix_Rot16(item->rot); - const MATRIX *const m = g_MatrixPtr; - const XYZ_32 shift = { - .x = (vec->x * m->_00 + vec->y * m->_01 + vec->z * m->_02) >> W2V_SHIFT, - .y = (vec->x * m->_10 + vec->y * m->_11 + vec->z * m->_12) >> W2V_SHIFT, - .z = (vec->x * m->_20 + vec->y * m->_21 + vec->z * m->_22) >> W2V_SHIFT, - }; - Matrix_Pop(); - - const XYZ_32 new_pos = { - .x = item->pos.x + shift.x, - .y = item->pos.y + shift.y, - .z = item->pos.z + shift.z, - }; - - int16_t room_num = lara->room_num; - const SECTOR *const sector = - Room_GetSector(new_pos.x, new_pos.y, new_pos.z, &room_num); - const int32_t height = - Room_GetHeight(sector, new_pos.x, new_pos.y, new_pos.z); - const int32_t ceiling = - Room_GetCeiling(sector, new_pos.x, new_pos.y, new_pos.z); - - if (ABS(height - lara->pos.y) > STEP_L - || ABS(ceiling - lara->pos.y) < LARA_HEIGHT) { - return; - } - - lara->pos = new_pos; -} diff --git a/src/libtrx/game/lara/misc.c b/src/libtrx/game/lara/misc.c index acf9a9b74..a1343b225 100644 --- a/src/libtrx/game/lara/misc.c +++ b/src/libtrx/game/lara/misc.c @@ -1,6 +1,5 @@ #include "game/lara/misc.h" -#include "game/items.h" #include "game/lara/common.h" #include "game/lara/const.h" #include "game/rooms.h" diff --git a/src/libtrx/game/level/common.c b/src/libtrx/game/level/common.c index cc54110f1..cc12c4b3c 100644 --- a/src/libtrx/game/level/common.c +++ b/src/libtrx/game/level/common.c @@ -16,7 +16,6 @@ #include "game/rooms.h" #include "game/shell.h" #include "game/sound.h" -#include "game/stats.h" #include "game/viewport.h" #include "log.h" #include "memory.h" @@ -297,7 +296,6 @@ static void M_ReadObjectMesh(OBJECT_MESH *const mesh, VFILE *const file) VFile_Skip(file, sizeof(int16_t)); mesh->enable_reflections = false; - mesh->disable_lighting = false; { mesh->num_vertices = VFile_ReadS16(file); @@ -774,9 +772,9 @@ void Level_AppendAnimBones( const int32_t flags = VFile_ReadS32(file); bone->matrix_pop = (flags & 1) != 0; bone->matrix_push = (flags & 2) != 0; - bone->rot.x = false; - bone->rot.y = false; - bone->rot.z = false; + bone->rot_x = false; + bone->rot_y = false; + bone->rot_z = false; M_ReadPosition(&bone->pos, file); } } @@ -842,10 +840,10 @@ void Level_ReadStaticObjects(VFILE *const file) LOG_INFO("static objects: %d", num_objects); for (int32_t i = 0; i < num_objects; i++) { const int32_t static_id = VFile_ReadS32(file); - if (static_id < 0 || static_id >= MAX_STATIC_OBJECTS_3D) { + if (static_id < 0 || static_id >= MAX_STATIC_OBJECTS) { Shell_ExitSystemFmt( "Invalid static ID: %d (max=%d)", static_id, - MAX_STATIC_OBJECTS_3D - 1); + MAX_STATIC_OBJECTS); } STATIC_OBJECT_3D *const obj = Object_Get3DStatic(static_id); @@ -881,7 +879,6 @@ void Level_AppendObjectTextures( { for (int32_t i = 0; i < num_textures; i++) { OBJECT_TEXTURE *const texture = Output_GetObjectTexture(base_idx + i); - texture->uv_count = 4; // Default to 4 vertices texture->draw_type = VFile_ReadU16(file); texture->tex_page = VFile_ReadU16(file) + base_page_idx; for (int32_t j = 0; j < 4; j++) { @@ -935,9 +932,8 @@ void Level_ReadSpriteSequences(VFILE *const file) OBJECT *const obj = Object_Get(object_id); obj->mesh_count = num_meshes; obj->mesh_idx = mesh_idx; - obj->anim_idx = NO_ANIM; obj->loaded = true; - } else if (object_id - O_NUMBER_OF < MAX_STATIC_OBJECTS_2D) { + } else if (object_id - O_NUMBER_OF < MAX_STATIC_OBJECTS) { STATIC_OBJECT_2D *const obj = Object_Get2DStatic(object_id - O_NUMBER_OF); obj->frame_count = ABS(num_meshes); @@ -1115,7 +1111,6 @@ void Level_ReadItems(VFILE *const file) } finish: - Stats_ObserveItemsLoad(); Benchmark_End(&benchmark, nullptr); } @@ -1190,26 +1185,6 @@ finish: void Level_LoadTextures(void) { - for (int32_t room_num = 0; room_num < Room_GetCount(); room_num++) { - const ROOM *const room = Room_Get(room_num); - for (int32_t j = 0; j < room->mesh.num_face3s; j++) { - const FACE3 *const face = &room->mesh.face3s[j]; - OBJECT_TEXTURE *const texture = - Output_GetObjectTexture(face->texture_idx); - texture->uv_count = 3; - } - } - - for (int32_t i = 0; i < Object_GetMeshCount(); i++) { - const OBJECT_MESH *const mesh = Object_GetMesh(i); - for (int32_t j = 0; j < mesh->num_tex_face3s; j++) { - const FACE3 *const face = &mesh->tex_face3s[j]; - OBJECT_TEXTURE *const texture = - Output_GetObjectTexture(face->texture_idx); - texture->uv_count = 3; - } - } - for (int32_t room_num = 0; room_num < Room_GetCount(); room_num++) { ROOM *const room = Room_Get(room_num); for (int32_t j = 0; j < room->mesh.num_face4s; j++) { diff --git a/src/libtrx/game/level/settings.c b/src/libtrx/game/level/settings.c deleted file mode 100644 index 7a666dca8..000000000 --- a/src/libtrx/game/level/settings.c +++ /dev/null @@ -1,31 +0,0 @@ -#include "game/level/settings.h" - -#include "config.h" -#include "game/game_flow.h" - -RGB_888 Level_GetWaterColor(void) -{ - const GF_LEVEL *const level = GF_GetCurrentLevel(); - if (level != nullptr && level->settings.water_color.is_present) { - return level->settings.water_color.value; - } - return g_Config.visuals.water_color; -} - -float Level_GetFogStart(void) -{ - const GF_LEVEL *const level = GF_GetCurrentLevel(); - if (level != nullptr && level->settings.fog_start.is_present) { - return level->settings.fog_start.value; - } - return g_Config.visuals.fog_start; -} - -float Level_GetFogEnd(void) -{ - const GF_LEVEL *const level = GF_GetCurrentLevel(); - if (level != nullptr && level->settings.fog_end.is_present) { - return level->settings.fog_end.value; - } - return g_Config.visuals.fog_end; -} diff --git a/src/libtrx/game/music.c b/src/libtrx/game/music.c index 19d259fdb..c3e60a6e6 100644 --- a/src/libtrx/game/music.c +++ b/src/libtrx/game/music.c @@ -2,16 +2,6 @@ static uint16_t m_MusicTrackFlags[MAX_MUSIC_TRACKS] = {}; -int32_t Music_GetMinVolume(void) -{ - return 0; -} - -int32_t Music_GetMaxVolume(void) -{ - return 10; -} - void Music_ResetTrackFlags(void) { for (int32_t i = 0; i < MAX_MUSIC_TRACKS; i++) { diff --git a/src/libtrx/game/objects/common.c b/src/libtrx/game/objects/common.c index 146485ede..54a7425d1 100644 --- a/src/libtrx/game/objects/common.c +++ b/src/libtrx/game/objects/common.c @@ -6,27 +6,13 @@ #include "game/game_buf.h" #include "game/matrix.h" #include "game/output.h" -#include "game/output/objects.h" static OBJECT m_Objects[O_NUMBER_OF] = {}; -static STATIC_OBJECT_3D m_StaticObjects3D[MAX_STATIC_OBJECTS_3D] = {}; -static STATIC_OBJECT_2D m_StaticObjects2D[MAX_STATIC_OBJECTS_2D] = {}; +static STATIC_OBJECT_3D m_StaticObjects3D[MAX_STATIC_OBJECTS] = {}; +static STATIC_OBJECT_2D m_StaticObjects2D[MAX_STATIC_OBJECTS] = {}; static OBJECT_MESH **m_MeshPointers = nullptr; static int32_t m_MeshCount = 0; -void Object_Reset(void) -{ - for (int32_t i = 0; i < O_NUMBER_OF; i++) { - m_Objects[i].loaded = false; - } - for (int32_t i = 0; i < MAX_STATIC_OBJECTS_3D; i++) { - m_StaticObjects3D[i].loaded = false; - } - for (int32_t i = 0; i < MAX_STATIC_OBJECTS_2D; i++) { - m_StaticObjects2D[i].loaded = false; - } -} - OBJECT *Object_Get(const GAME_OBJECT_ID obj_id) { return &m_Objects[obj_id]; @@ -98,16 +84,6 @@ OBJECT_MESH *Object_GetMesh(const int32_t index) return m_MeshPointers[index]; } -int32_t Object_GetMeshIndex(const OBJECT_MESH *const mesh) -{ - for (int32_t i = 0; i < m_MeshCount; i++) { - if (mesh == m_MeshPointers[i]) { - return i; - } - } - return -1; -} - int32_t Object_GetMeshCount(void) { return m_MeshCount; @@ -146,12 +122,6 @@ void Object_SwapMesh( m_MeshPointers[obj1->mesh_idx + mesh_num] = m_MeshPointers[obj2->mesh_idx + mesh_num]; m_MeshPointers[obj2->mesh_idx + mesh_num] = temp; - -#if TR_VERSION == 1 - Output_Meshes_ObserveObjectMeshSwap( - m_MeshPointers[obj1->mesh_idx + mesh_num], - m_MeshPointers[obj2->mesh_idx + mesh_num]); -#endif } ANIM *Object_GetAnim(const OBJECT *const obj, const int32_t anim_idx) @@ -185,7 +155,6 @@ void Object_DrawInterpolatedObject( Matrix_TranslateRel16_ID(frame1->offset, frame2->offset); Matrix_Rot16_ID( frame1->mesh_rots[mesh_idx], frame2->mesh_rots[mesh_idx]); - Object_ApplyExtraRotation(&extra_rotation, obj->base_rot, true); } else { const ANIM_BONE *const bone = Object_GetBone(obj, mesh_idx - 1); if (bone->matrix_pop) { @@ -198,7 +167,17 @@ void Object_DrawInterpolatedObject( Matrix_TranslateRel32_I(bone->pos); Matrix_Rot16_ID( frame1->mesh_rots[mesh_idx], frame2->mesh_rots[mesh_idx]); - Object_ApplyExtraRotation(&extra_rotation, bone->rot, true); + if (extra_rotation != nullptr) { + if (bone->rot_y) { + Matrix_RotY_I(*extra_rotation++); + } + if (bone->rot_x) { + Matrix_RotX_I(*extra_rotation++); + } + if (bone->rot_z) { + Matrix_RotZ_I(*extra_rotation++); + } + } } if (meshes & (1 << mesh_idx)) { @@ -210,8 +189,6 @@ void Object_DrawInterpolatedObject( if (mesh_idx == 0) { Matrix_TranslateRel16(frame1->offset); Matrix_Rot16(frame1->mesh_rots[mesh_idx]); - Object_ApplyExtraRotation( - &extra_rotation, obj->base_rot, false); } else { const ANIM_BONE *const bone = Object_GetBone(obj, mesh_idx - 1); if (bone->matrix_pop) { @@ -223,7 +200,17 @@ void Object_DrawInterpolatedObject( Matrix_TranslateRel32(bone->pos); Matrix_Rot16(frame1->mesh_rots[mesh_idx]); - Object_ApplyExtraRotation(&extra_rotation, bone->rot, false); + if (extra_rotation != nullptr) { + if (bone->rot_y) { + Matrix_RotY(*extra_rotation++); + } + if (bone->rot_x) { + Matrix_RotX(*extra_rotation++); + } + if (bone->rot_z) { + Matrix_RotZ(*extra_rotation++); + } + } } if (meshes & (1 << mesh_idx)) { @@ -234,29 +221,3 @@ void Object_DrawInterpolatedObject( Matrix_Pop(); } - -void Object_ApplyExtraRotation( - const int16_t **extra_rotation, const XYZ_BOOL rot_flags, - const bool interpolated) -{ - const int16_t *rot_ptr = *extra_rotation; - if (rot_ptr == nullptr) { - return; - } - -#define APPLY_ROTATION(axis_, flag_) \ - if (rot_flags.flag_) { \ - if (interpolated) { \ - Matrix_Rot##axis_##_I(*rot_ptr++); \ - } else { \ - Matrix_Rot##axis_(*rot_ptr++); \ - } \ - } - - APPLY_ROTATION(Y, y); - APPLY_ROTATION(X, x); - APPLY_ROTATION(Z, z); - -#undef APPLY_ROTATION - *extra_rotation = rot_ptr; -} diff --git a/src/libtrx/game/objects/traps/movable_block.c b/src/libtrx/game/objects/traps/movable_block.c index 155bbd485..f463bb51a 100644 --- a/src/libtrx/game/objects/traps/movable_block.c +++ b/src/libtrx/game/objects/traps/movable_block.c @@ -7,11 +7,6 @@ #include -typedef struct { - int16_t counter_rot[3]; - int16_t original_rot; -} M_PRIV; - static int32_t m_BlockCount = 0; static VECTOR *m_UnsortedBlocks = nullptr; static int16_t *m_SortedBlocks = nullptr; @@ -59,24 +54,6 @@ void MovableBlock_Initialise(const int16_t item_num) m_UnsortedBlocks = Vector_Create(sizeof(int16_t)); } Vector_Add(m_UnsortedBlocks, (void *)&item_num); - - // Ensure the block is snapped to the grid, otherwise the snapping occurs - // during collision tests and can appear jarring. Additional angles are - // stored to preserve item appearance in spite of control angle changes. - ITEM *const item = Item_Get(item_num); - M_PRIV *const data = GameBuf_Alloc(sizeof(M_PRIV), GBUF_ITEM_DATA); - item->data = data; - data->original_rot = - (((item->rot.y + DEG_180) / DEG_90) * DEG_90) - DEG_180; - MovableBlock_UpdateRotation(item, data->original_rot); -} - -// TODO: make private -void MovableBlock_UpdateRotation(ITEM *const item, const int16_t rot_y) -{ - item->rot.y = rot_y; - M_PRIV *const data = (M_PRIV *)item->data; - data->counter_rot[0] = data->original_rot - rot_y; } void MovableBlock_SetupFloor(void) diff --git a/src/libtrx/game/output/background.c b/src/libtrx/game/output/background.c deleted file mode 100644 index 4a8a680c4..000000000 --- a/src/libtrx/game/output/background.c +++ /dev/null @@ -1,222 +0,0 @@ -#include "game/output/background.h" - -#include "debug.h" -#include "filesystem.h" -#include "game/output/common.h" -#include "game/viewport.h" -#include "log.h" -#include "memory.h" -#include "strings.h" -#include "utils.h" -#include "vector.h" - -#include -#include -#include - -#define M_RELATIVE_ERROR(a, b) ABS((a) - (b)) / (b) - -typedef struct { - char *file_name; - float diff; -} M_CANDIDATE; - -static char *m_LastPath = nullptr; - -static IMAGE *M_CreateImageFromPath(const char *path); -static float M_GetScreenAspectRatio(void); -static int M_CompareCandidates(const void *a, const void *b); -static VECTOR *M_ScanCandidates(const char *path, size_t *dir_len_out); -static bool M_TryLoadCandidates( - const char *path, VECTOR *candidates, size_t dir_len); -static void M_FreeCandidates(VECTOR *candidates); - -static IMAGE *M_CreateImageFromPath(const char *const path) -{ - if (TR_VERSION == 1) { - return Image_CreateFromFileInto( - path, Viewport_GetWidth(), Viewport_GetHeight(), IMAGE_FIT_SMART); - } else { - return Image_CreateFromFile(path); - } -} - -static float M_GetScreenAspectRatio(void) -{ - return Viewport_GetWidth() / (float)Viewport_GetHeight(); -} - -static int M_CompareCandidates(const void *const a, const void *const b) -{ - const M_CANDIDATE *const c1 = a; - const M_CANDIDATE *const c2 = b; - if (c1->diff < c2->diff) { - return -1; - } - if (c1->diff > c2->diff) { - return 1; - } - return 0; -} - -static VECTOR *M_ScanCandidates( - const char *const path, size_t *const dir_len_out) -{ - VECTOR *candidates = nullptr; - - const char *last_slash = strrchr(path, '/'); - const char *last_backslash = strrchr(path, '\\'); - const char *last_sep = - last_slash > last_backslash ? last_slash : last_backslash; - size_t dir_len; - char *dir_path; - if (last_sep != nullptr) { - dir_len = last_sep - path; - dir_path = String_Format("%.*s", (int)dir_len, path); - } else { - dir_len = 0; - dir_path = Memory_DupStr("."); - } - - const char *file_name = last_sep ? last_sep + 1 : path; - const char *ext_ptr = strrchr(file_name, '.'); - const float screen_ratio = M_GetScreenAspectRatio(); - - void *const dir_handle = File_OpenDirectory(dir_path); - if (dir_handle == nullptr) { - goto finish; - } - - candidates = Vector_Create(sizeof(M_CANDIDATE)); - const char *entry; - while ((entry = File_ReadDirectory(dir_handle)) != nullptr) { - // Match the file itself, and assume it's of 16:9 aspect ratio. - if (String_Equivalent(entry, file_name)) { - const float ratio = 16.0f / 9.0f; - Vector_Add( - candidates, - &(M_CANDIDATE) { - .file_name = Memory_DupStr(file_name), - .diff = M_RELATIVE_ERROR(ratio, screen_ratio), - }); - } - - // Match directories with pattern: x - int32_t w = 0, h = 0; - if (sscanf(entry, "%dx%d", &w, &h) == 2) { - const float ratio = w / (float)h; - Vector_Add( - candidates, - &(M_CANDIDATE) { - .file_name = String_Format("%s/%s", entry, file_name), - .diff = M_RELATIVE_ERROR(ratio, screen_ratio), - }); - } - } - File_CloseDirectory(dir_handle); - -finish: - *dir_len_out = dir_len; - Memory_FreePointer(&dir_path); - return candidates; -} - -static bool M_TryLoadCandidates( - const char *const path, VECTOR *const candidates, const size_t dir_len) -{ - for (int32_t i = 0; i < candidates->count; i++) { - const M_CANDIDATE *candidate = Vector_Get(candidates, i); - char *full_path; - if (dir_len > 0) { - full_path = String_Format( - "%.*s/%s", (int32_t)dir_len, path, candidate->file_name); - } else { - full_path = String_Format("%s", candidate->file_name); - } - IMAGE *const image = M_CreateImageFromPath(full_path); - Memory_FreePointer(&full_path); - if (image != nullptr) { - Output_LoadBackgroundFromImage(image); - Image_Free(image); - return true; - } - } - return false; -} - -static void M_FreeCandidates(VECTOR *const candidates) -{ - if (candidates == nullptr) { - return; - } - for (int32_t i = 0; i < candidates->count; i++) { - M_CANDIDATE *const candidate = Vector_Get(candidates, i); - Memory_Free(candidate->file_name); - } - Vector_Free(candidates); -} - -bool Output_LoadBackgroundFromFile(const char *const path) -{ - LOG_INFO("Loading image %s", path); - size_t dir_len = 0; - bool result = false; - - // Try aspect-ratio specific directories. - VECTOR *candidates = M_ScanCandidates(path, &dir_len); - if (candidates != nullptr) { - M_CANDIDATE *raw_candidates = Vector_GetData(candidates); - qsort( - raw_candidates, candidates->count, sizeof(M_CANDIDATE), - M_CompareCandidates); - for (int32_t i = 0; i < candidates->count; i++) { - const M_CANDIDATE *const candidate = Vector_Get(candidates, i); - LOG_INFO( - "Found candidate %s (diff=%.02f)", candidate->file_name, - candidate->diff); - } - if (M_TryLoadCandidates(path, candidates, dir_len)) { - result = true; - } - } - - if (!result) { - // Fallback to the main image. - IMAGE *const image = M_CreateImageFromPath(path); - if (image != nullptr) { - result = true; - Output_LoadBackgroundFromImage(image); - Image_Free(image); - } - } - - M_FreeCandidates(candidates); - if (result) { - char *prev = m_LastPath; - m_LastPath = Memory_DupStr(path); - Memory_FreePointer(&prev); - } - return result; -} - -void Output_ReloadBackgroundImage(void) -{ - if (m_LastPath == nullptr) { - return; - } - - char *prev = Memory_DupStr(m_LastPath); - Output_UnloadBackground(); - Output_LoadBackgroundFromFile(prev); - Memory_FreePointer(&prev); -} - -char *Output_GetLastBackgroundPath(void) -{ - return m_LastPath; -} - -void Output_ClearLastBackgroundPath(void) -{ - Memory_FreePointer(&m_LastPath); -} diff --git a/src/libtrx/game/output/common.c b/src/libtrx/game/output/common.c index c8a835fce..7a6c9ed01 100644 --- a/src/libtrx/game/output/common.c +++ b/src/libtrx/game/output/common.c @@ -10,7 +10,6 @@ typedef struct { int32_t shade; } COMMON_LIGHT; -static int32_t m_FogStart = 0; static int32_t m_DynamicLightCount = 0; static LIGHT m_DynamicLights[MAX_DYNAMIC_LIGHTS] = {}; @@ -52,7 +51,7 @@ static void M_CalculateBrightestLight( } #endif - const int32_t ambient = TR_VERSION == 1 ? (SHADE_MAX - room->ambient) : 0; + const int32_t ambient = TR_VERSION == 1 ? (0x1FFF - room->ambient) : 0; for (int32_t i = 0; i < room->num_lights; i++) { const LIGHT *const light = &room->lights[i]; const int32_t dx = pos.x - light->pos.x; @@ -112,7 +111,7 @@ void Output_CalculateLight(const XYZ_32 pos, const int16_t room_num) adder = (adder + dynamic_adder) / 2; if (TR_VERSION == 1 && (room->num_lights > 0 || dynamic_adder > 0)) { - adder += (SHADE_MAX - room->ambient) / 2; + adder += (0x1FFF - room->ambient) / 2; } // TODO: use m_LsAdder and m_LsDivider once ported @@ -123,7 +122,7 @@ void Output_CalculateLight(const XYZ_32 pos, const int16_t room_num) global_divider = 0; } else { #if TR_VERSION == 1 - global_adder = SHADE_MAX - adder; + global_adder = 0x1FFF - adder; const int32_t divider = brightest_light.shade == adder ? adder : brightest_light.shade - adder; @@ -141,7 +140,7 @@ void Output_CalculateLight(const XYZ_32 pos, const int16_t room_num) const int32_t depth = g_MatrixPtr->_23 >> W2V_SHIFT; global_adder += Output_CalcFogShade(depth); - CLAMPG(global_adder, SHADE_MAX); + CLAMPG(global_adder, 0x1FFF); Output_SetLightAdder(global_adder); Output_SetLightDivider(global_divider); @@ -150,10 +149,10 @@ void Output_CalculateLight(const XYZ_32 pos, const int16_t room_num) void Output_CalculateStaticLight(const int16_t adder) { // TODO: use m_LsAdder - int32_t global_adder = adder - SHADE_NEUTRAL; + int32_t global_adder = adder - 0x1000; const int32_t depth = g_MatrixPtr->_23 >> W2V_SHIFT; global_adder += Output_CalcFogShade(depth); - CLAMPG(global_adder, SHADE_MAX); + CLAMPG(global_adder, 0x1FFF); Output_SetLightAdder(global_adder); } @@ -295,13 +294,3 @@ void Output_AddDynamicLight( light->shade.value_1 = intensity; light->falloff.value_1 = falloff; } - -int32_t Output_GetFogStart(void) -{ - return MIN(m_FogStart, Output_GetFogEnd()); -} - -void Output_SetFogStart(const int32_t dist) -{ - m_FogStart = dist; -} diff --git a/src/libtrx/game/output/textures.c b/src/libtrx/game/output/textures.c index 01b78f7ac..e35c55486 100644 --- a/src/libtrx/game/output/textures.c +++ b/src/libtrx/game/output/textures.c @@ -20,7 +20,6 @@ static LIGHT_MAP m_LightMap[32]; static SHADE_MAP m_ShadeMap[256]; static int32_t m_ObjectTextureCount = 0; -static int32_t m_SpriteTextureCount = 0; static OBJECT_TEXTURE *m_ObjectTextures = nullptr; static SPRITE_TEXTURE *m_SpriteTextures = nullptr; static ANIMATED_TEXTURE_RANGE *m_AnimTextureRanges = nullptr; @@ -73,7 +72,6 @@ void Output_InitialiseObjectTextures(const int32_t num_textures) void Output_InitialiseSpriteTextures(const int32_t num_textures) { - m_SpriteTextureCount = num_textures; m_SpriteTextures = num_textures == 0 ? nullptr : GameBuf_Alloc( @@ -146,11 +144,6 @@ int32_t Output_GetObjectTextureCount(void) return m_ObjectTextureCount; } -int32_t Output_GetSpriteTextureCount(void) -{ - return m_SpriteTextureCount; -} - OBJECT_TEXTURE *Output_GetObjectTexture(const int32_t texture_idx) { if (m_ObjectTextures == nullptr) { @@ -216,7 +209,7 @@ void Output_CycleAnimatedTextures(void) m_ObjectTextures[range->textures[i]] = temp; } - for (int32_t i = 0; i < MAX_STATIC_OBJECTS_2D; i++) { + for (int32_t i = 0; i < MAX_STATIC_OBJECTS; i++) { const STATIC_OBJECT_2D *const obj = Object_Get2DStatic(i); if (!obj->loaded || obj->frame_count == 1) { continue; diff --git a/src/libtrx/game/phase/executor.c b/src/libtrx/game/phase/executor.c index 8afd3f9cd..3c990cf95 100644 --- a/src/libtrx/game/phase/executor.c +++ b/src/libtrx/game/phase/executor.c @@ -1,6 +1,5 @@ #include "game/phase/executor.h" -#include "benchmark.h" #include "config.h" #include "game/clock.h" #include "game/console/common.h" @@ -12,10 +11,7 @@ #include "game/savegame.h" #include "game/shell.h" #include "game/text.h" -#include "game/ui.h" -#include "gfx/gl/track.h" -#define DEBUG_OPTIM 0 #define MAX_PHASES 10 static bool m_Exiting; @@ -29,8 +25,6 @@ static int32_t M_Wait(PHASE *phase); static PHASE_CONTROL M_Control(PHASE *const phase, const int32_t nframes) { - Console_Control(); - const GF_COMMAND gf_override_cmd = GF_GetOverrideCommand(); if (gf_override_cmd.action != GF_NOOP) { const GF_COMMAND gf_cmd = gf_override_cmd; @@ -67,30 +61,16 @@ static PHASE_CONTROL M_Control(PHASE *const phase, const int32_t nframes) static void M_Draw(PHASE *const phase) { Output_BeginScene(); - UI_BeginScene(); -#if DEBUG_OPTIM - BENCHMARK benchmark = Benchmark_Start(); -#endif if (phase != nullptr && phase->draw != nullptr) { phase->draw(phase); } Console_Draw(); Text_Draw(); - UI_EndScene(); Output_DrawPolyList(); Fader_Draw(&m_ExitFader); Output_EndScene(); - -#if DEBUG_OPTIM - char buffer[80]; - const GFX_METRICS metrics = GFX_Track_GetMetrics(); - sprintf( - buffer, "%.03f KB T:%d U:%d", metrics.buffer_total_bytes / 1024.0f, - metrics.buffer_transfer_count, metrics.uniform_changes); - Benchmark_End(&benchmark, buffer); -#endif } static int32_t M_Wait(PHASE *const phase) diff --git a/src/libtrx/game/phase/phase_pause.c b/src/libtrx/game/phase/phase_pause.c index 94799b89a..f6151268b 100644 --- a/src/libtrx/game/phase/phase_pause.c +++ b/src/libtrx/game/phase/phase_pause.c @@ -11,7 +11,8 @@ #include "game/shell.h" #include "game/sound.h" #include "game/text.h" -#include "game/ui.h" +#include "game/ui/common.h" +#include "game/ui/widgets/requester.h" #include "memory.h" #include @@ -22,15 +23,14 @@ typedef enum { STATE_FADE_IN, STATE_WAIT, STATE_ASK, + STATE_CONFIRM, STATE_FADE_OUT, } STATE; typedef struct { STATE state; - struct { - bool is_ready; - UI_PAUSE_STATE state; - } ui; + bool is_ui_ready; + UI_WIDGET *ui; TEXTSTRING *mode_text; GF_ACTION action; FADER back_fader; @@ -43,6 +43,8 @@ static void M_ReturnToGame(M_PRIV *p); static void M_ExitToTitle(M_PRIV *p); static void M_CreateText(M_PRIV *p); static void M_RemoveText(M_PRIV *p); +static int32_t M_DisplayRequester( + M_PRIV *p, const char *header, const char *option1, const char *option2); static PHASE_CONTROL M_Start(PHASE *phase); static void M_End(PHASE *phase); @@ -58,7 +60,10 @@ static void M_FadeIn(M_PRIV *const p) static void M_FadeOut(M_PRIV *const p) { M_RemoveText(p); - p->ui.is_ready = false; + if (p->ui != nullptr) { + p->ui->free(p->ui); + p->ui = nullptr; + } if (p->action == GF_NOOP) { Fader_Init(&p->back_fader, FADER_ANY, FADER_TRANSPARENT, FADE_TIME); } else { @@ -103,13 +108,42 @@ static void M_RemoveText(M_PRIV *const p) p->mode_text = nullptr; } +static int32_t M_DisplayRequester( + M_PRIV *const p, const char *header, const char *option1, + const char *option2) +{ + if (!p->is_ui_ready) { + if (p->ui == nullptr) { + p->ui = UI_Requester_Create((UI_REQUESTER_SETTINGS) { + .is_selectable = true, + .width = 160, + .visible_rows = 2, + }); + } + UI_Requester_ClearRows(p->ui); + UI_Requester_SetTitle(p->ui, header); + UI_Requester_AddRowC(p->ui, option1, nullptr); + UI_Requester_AddRowC(p->ui, option2, nullptr); + p->ui->set_position( + p->ui, (UI_GetCanvasWidth() - p->ui->get_width(p->ui)) / 2, + (UI_GetCanvasHeight() - p->ui->get_height(p->ui)) - 50); + p->is_ui_ready = true; + } + + const int32_t choice = UI_Requester_GetSelectedRow(p->ui); + if (choice >= 0) { + p->is_ui_ready = false; + } + return choice; +} + static PHASE_CONTROL M_Start(PHASE *const phase) { M_PRIV *const p = phase->priv; - p->ui.is_ready = false; - UI_Pause_Init(&p->ui.state); M_PauseGame(p); + + p->is_ui_ready = false; return (PHASE_CONTROL) { .action = PHASE_ACTION_CONTINUE }; } @@ -117,7 +151,10 @@ static void M_End(PHASE *const phase) { M_PRIV *const p = phase->priv; M_RemoveText(p); - UI_Pause_Free(&p->ui.state); + if (p->ui != nullptr) { + p->ui->free(p->ui); + p->ui = nullptr; + } } static PHASE_CONTROL M_Control(PHASE *const phase, int32_t const num_frames) @@ -127,8 +164,8 @@ static PHASE_CONTROL M_Control(PHASE *const phase, int32_t const num_frames) Input_Update(); Shell_ProcessInput(); - if (p->ui.is_ready) { - UI_Pause_Control(&p->ui.state); + if (p->ui != nullptr) { + p->ui->control(p->ui); } switch (p->state) { @@ -153,23 +190,30 @@ static PHASE_CONTROL M_Control(PHASE *const phase, int32_t const num_frames) break; case STATE_ASK: { - const UI_PAUSE_EXIT_CHOICE choice = UI_Pause_Control(&p->ui.state); - switch (choice) { - case UI_PAUSE_RESUME_PAUSE: - p->state = STATE_WAIT; - return (PHASE_CONTROL) { .action = PHASE_ACTION_NO_WAIT }; - case UI_PAUSE_EXIT_TO_GAME: + const int32_t choice = M_DisplayRequester( + p, GS(PAUSE_EXIT_TO_TITLE), GS(PAUSE_CONTINUE), GS(PAUSE_QUIT)); + if (choice == 0) { M_ReturnToGame(p); return (PHASE_CONTROL) { .action = PHASE_ACTION_NO_WAIT }; - case UI_PAUSE_EXIT_TO_TITLE: - M_ExitToTitle(p); + } else if (choice == 1) { + p->state = STATE_CONFIRM; return (PHASE_CONTROL) { .action = PHASE_ACTION_NO_WAIT }; - default: - break; } break; } + case STATE_CONFIRM: { + const int32_t choice = M_DisplayRequester( + p, GS(PAUSE_ARE_YOU_SURE), GS(PAUSE_YES), GS(PAUSE_NO)); + if (choice == 0) { + M_ExitToTitle(p); + return (PHASE_CONTROL) { .action = PHASE_ACTION_NO_WAIT }; + } else if (choice == 1) { + M_ReturnToGame(p); + return (PHASE_CONTROL) { .action = PHASE_ACTION_NO_WAIT }; + } + break; + case STATE_FADE_OUT: if (!Fader_IsActive(&p->back_fader)) { return (PHASE_CONTROL) { @@ -179,6 +223,7 @@ static PHASE_CONTROL M_Control(PHASE *const phase, int32_t const num_frames) } break; } + } return (PHASE_CONTROL) { .action = PHASE_ACTION_CONTINUE }; } @@ -192,8 +237,8 @@ static void M_Draw(PHASE *const phase) Interpolation_Enable(); Fader_Draw(&p->back_fader); - if (p->state == STATE_ASK) { - UI_Pause(&p->ui.state); + if (p->ui != nullptr) { + p->ui->draw(p->ui); } Output_DrawPolyList(); } diff --git a/src/libtrx/game/phase/phase_photo_mode.c b/src/libtrx/game/phase/phase_photo_mode.c index d031a7b92..258f53d99 100644 --- a/src/libtrx/game/phase/phase_photo_mode.c +++ b/src/libtrx/game/phase/phase_photo_mode.c @@ -12,10 +12,12 @@ #include "game/overlay.h" #include "game/shell.h" #include "game/sound.h" -#include "game/ui.h" +#include "game/ui/common.h" +#include "game/ui/widgets/photo_mode.h" #include "memory.h" typedef struct { + UI_WIDGET *ui; bool taking_screenshot; bool show_fps_counter; } M_PRIV; @@ -38,6 +40,7 @@ static PHASE_CONTROL M_Start(PHASE *const phase) Music_Pause(); Sound_PauseAll(); + p->ui = UI_PhotoMode_Create(); if (!g_Config.ui.enable_photo_mode_ui) { Console_Log( GS(OSD_PHOTO_MODE_LAUNCHED), @@ -53,6 +56,9 @@ static void M_End(PHASE *const phase) M_PRIV *const p = phase->priv; Camera_ExitPhotoMode(); + p->ui->free(p->ui); + p->ui = nullptr; + #if TR_VERSION == 1 g_Config.rendering.enable_fps_counter = p->show_fps_counter; #endif @@ -86,6 +92,7 @@ static PHASE_CONTROL M_Control(PHASE *const phase, int32_t num_frames) Output_EndScene(); Sound_Effect(SFX_MENU_LARA_HOME, nullptr, SPM_ALWAYS); } else { + p->ui->control(p->ui); Camera_Update(); } @@ -99,7 +106,7 @@ static void M_Draw(PHASE *const phase) Output_DrawPolyList(); if (!p->taking_screenshot) { - UI_PhotoMode(); + p->ui->draw(p->ui); } Output_DrawPolyList(); } diff --git a/src/libtrx/game/phase/phase_stats.c b/src/libtrx/game/phase/phase_stats.c index 8cda06426..0fde57e95 100644 --- a/src/libtrx/game/phase/phase_stats.c +++ b/src/libtrx/game/phase/phase_stats.c @@ -9,7 +9,7 @@ #include "game/interpolation.h" #include "game/shell.h" #include "game/text.h" -#include "game/ui.h" +#include "game/ui/widgets/stats_dialog.h" #include "memory.h" typedef enum { @@ -24,8 +24,7 @@ typedef struct { STATE state; FADER back_fader; FADER top_fader; - bool ui_active; - UI_STATS_DIALOG_STATE ui_state; + UI_WIDGET *ui; } M_PRIV; static bool M_IsFading(M_PRIV *p); @@ -66,11 +65,8 @@ static PHASE_CONTROL M_Start(PHASE *const phase) M_PRIV *const p = phase->priv; if (p->args.background_type == BK_IMAGE) { - if (p->args.background_path == nullptr) { - LOG_WARNING("Trying to load empty background image"); - } else { - Output_LoadBackgroundFromFile(p->args.background_path); - } + ASSERT(p->args.background_path != nullptr); + Output_LoadBackgroundFromFile(p->args.background_path); } else if (p->args.background_type == BK_OBJECT) { Output_LoadBackgroundFromObject(); } else { @@ -87,19 +83,14 @@ static PHASE_CONTROL M_Start(PHASE *const phase) M_FadeIn(p); } - p->ui_active = true; - UI_StatsDialog_Init( - &p->ui_state, - (UI_STATS_DIALOG_ARGS) { - .mode = p->args.show_final_stats ? UI_STATS_DIALOG_MODE_FINAL - : UI_STATS_DIALOG_MODE_LEVEL, - .style = p->args.use_bare_style - ? UI_STATS_DIALOG_STYLE_BARE - : UI_STATS_DIALOG_STYLE_BORDERED, - .level_num = p->args.level_num != -1 - ? p->args.level_num - : Game_GetCurrentLevel()->num, - }); + p->ui = UI_StatsDialog_Create((UI_STATS_DIALOG_ARGS) { + .mode = p->args.show_final_stats ? UI_STATS_DIALOG_MODE_FINAL + : UI_STATS_DIALOG_MODE_LEVEL, + .style = p->args.use_bare_style ? UI_STATS_DIALOG_STYLE_BARE + : UI_STATS_DIALOG_STYLE_BORDERED, + .level_num = p->args.level_num != -1 ? p->args.level_num + : Game_GetCurrentLevel()->num, + }); } return (PHASE_CONTROL) { .action = PHASE_ACTION_CONTINUE }; @@ -108,11 +99,10 @@ static PHASE_CONTROL M_Start(PHASE *const phase) static void M_End(PHASE *const phase) { M_PRIV *const p = phase->priv; - if (p->ui_active) { - p->ui_active = false; - UI_StatsDialog_Free(&p->ui_state); - } Output_UnloadBackground(); + if (p->ui != nullptr) { + p->ui->free(p->ui); + } } static PHASE_CONTROL M_Control(PHASE *const phase, int32_t num_frames) @@ -150,6 +140,9 @@ static PHASE_CONTROL M_Control(PHASE *const phase, int32_t num_frames) }; } + if (p->ui != nullptr) { + p->ui->control(p->ui); + } return (PHASE_CONTROL) { .action = PHASE_ACTION_CONTINUE }; } @@ -166,11 +159,13 @@ static void M_Draw(PHASE *const phase) } Fader_Draw(&p->back_fader); - UI_BeginFade(&p->top_fader, true); - if (p->ui_active) { - UI_StatsDialog(&p->ui_state); + if (p->ui != nullptr) { + p->ui->draw(p->ui); } - UI_EndFade(); + Text_Draw(); + Output_DrawPolyList(); + + Fader_Draw(&p->top_fader); } PHASE *Phase_Stats_Create(const PHASE_STATS_ARGS args) diff --git a/src/libtrx/game/rooms/common.c b/src/libtrx/game/rooms/common.c index 0dc525142..d593a1d3c 100644 --- a/src/libtrx/game/rooms/common.c +++ b/src/libtrx/game/rooms/common.c @@ -7,7 +7,6 @@ #include "game/items.h" #include "game/objects/common.h" #include "game/objects/traps/movable_block.h" -#include "game/output.h" #include "game/rooms/const.h" #include "game/rooms/enum.h" #include "game/sound/common.h" @@ -43,10 +42,9 @@ static const int16_t *M_ReadTrigger( const int16_t *data, int16_t fd_entry, SECTOR *sector); static void M_AddFlipItems(const ROOM *room); static void M_RemoveFlipItems(const ROOM *room); -static int16_t M_GetFloorTiltHeight( - const SECTOR *sector, int32_t x, int32_t z, bool fix_tilts); +static int16_t M_GetFloorTiltHeight(const SECTOR *sector, int32_t x, int32_t z); static int16_t M_GetCeilingTiltHeight( - const SECTOR *sector, int32_t x, int32_t z, bool fix_tilts); + const SECTOR *sector, int32_t x, int32_t z); static const int16_t *M_ReadTrigger( const int16_t *data, const int16_t fd_entry, SECTOR *const sector) @@ -156,11 +154,10 @@ static void M_RemoveFlipItems(const ROOM *const room) } static int16_t M_GetFloorTiltHeight( - const SECTOR *const sector, const int32_t x, const int32_t z, - const bool fix_tilts) + const SECTOR *const sector, const int32_t x, const int32_t z) { int16_t height = sector->floor.height; - if (sector->floor.tilt == 0 || (height == NO_HEIGHT && fix_tilts)) { + if (sector->floor.tilt == 0) { return height; } @@ -191,11 +188,10 @@ static int16_t M_GetFloorTiltHeight( } static int16_t M_GetCeilingTiltHeight( - const SECTOR *sector, const int32_t x, const int32_t z, - const bool fix_tilts) + const SECTOR *sector, const int32_t x, const int32_t z) { int16_t height = sector->ceiling.height; - if (sector->ceiling.tilt == 0 || (height == NO_HEIGHT && fix_tilts)) { + if (sector->ceiling.tilt == 0) { return height; } @@ -242,14 +238,6 @@ ROOM *Room_Get(const int32_t room_num) return &m_Rooms[room_num]; } -int32_t Room_GetNumber(const ROOM *const room) -{ - if (room == nullptr) { - return NO_ROOM_NEG; - } - return room - m_Rooms; -} - void Room_InitialiseFlipStatus(void) { for (int32_t i = 0; i < Room_GetCount(); i++) { @@ -298,8 +286,6 @@ void Room_FlipMap(void) room->effect_num = flipped->effect_num; M_AddFlipItems(room); - Output_ObserveRoomFlip(flipped); - Output_ObserveRoomFlip(room); } MovableBlock_HandleFlipMap(RFS_FLIPPED); @@ -467,17 +453,6 @@ int32_t Room_FindByPos(const int32_t x, const int32_t y, const int32_t z) return NO_ROOM_NEG; } -int32_t Room_GetFlippedBaseRoom(const int32_t room_num) -{ - for (int32_t i = 0; i < Room_GetCount(); i++) { - const ROOM *const room = Room_Get(i); - if (room->flipped_room == room_num) { - return i; - } - } - return NO_ROOM; -} - BOUNDS_32 Room_GetWorldBounds(void) { BOUNDS_32 bounds = { @@ -550,7 +525,7 @@ void Room_SetAbyssHeight(const int16_t height) CLAMPG(m_AbyssMaxHeight, MAX_HEIGHT - STEP_L); } -bool Room_IsAbyssHeight(const int32_t height) +bool Room_IsAbyssHeight(const int16_t height) { return m_AbyssMinHeight != 0 && height >= m_AbyssMinHeight; } @@ -561,15 +536,7 @@ HEIGHT_TYPE Room_GetHeightType(void) } int16_t Room_GetHeight( - const SECTOR *const sector, const int32_t x, const int32_t y, - const int32_t z) -{ - return Room_GetHeightEx(sector, x, y, z, false); -} - -int16_t Room_GetHeightEx( - const SECTOR *sector, const int32_t x, const int32_t y, const int32_t z, - const bool fix_tilts) + const SECTOR *sector, const int32_t x, const int32_t y, const int32_t z) { m_HeightType = HT_WALL; @@ -579,7 +546,7 @@ int16_t Room_GetHeightEx( if (Room_IsAbyssHeight(height)) { height = m_AbyssMaxHeight; } else { - height = M_GetFloorTiltHeight(pit_sector, x, z, fix_tilts); + height = M_GetFloorTiltHeight(pit_sector, x, z); } if (pit_sector->trigger == nullptr) { @@ -605,16 +572,9 @@ int16_t Room_GetHeightEx( int16_t Room_GetCeiling( const SECTOR *const sector, const int32_t x, const int32_t y, const int32_t z) -{ - return Room_GetCeilingEx(sector, x, y, z, false); -} - -int16_t Room_GetCeilingEx( - const SECTOR *const sector, const int32_t x, const int32_t y, - const int32_t z, const bool fix_tilts) { const SECTOR *const sky_sector = Room_GetSkySector(sector, x, z); - int16_t height = M_GetCeilingTiltHeight(sky_sector, x, z, fix_tilts); + int16_t height = M_GetCeilingTiltHeight(sky_sector, x, z); const SECTOR *const pit_sector = Room_GetPitSector(sector, x, z); if (pit_sector->trigger == nullptr) { diff --git a/src/libtrx/game/savegame.c b/src/libtrx/game/savegame.c new file mode 100644 index 000000000..e6b29488c --- /dev/null +++ b/src/libtrx/game/savegame.c @@ -0,0 +1,22 @@ +#include "game/savegame.h" + +#include "log.h" + +static int32_t m_BoundSlot = -1; + +void Savegame_BindSlot(const int32_t slot_num) +{ + m_BoundSlot = slot_num; + LOG_DEBUG("Binding save slot %d", slot_num); +} + +void Savegame_UnbindSlot(void) +{ + LOG_DEBUG("Resetting the save slot"); + m_BoundSlot = -1; +} + +int32_t Savegame_GetBoundSlot(void) +{ + return m_BoundSlot; +} diff --git a/src/libtrx/game/savegame/common.c b/src/libtrx/game/savegame/common.c deleted file mode 100644 index 298f6914f..000000000 --- a/src/libtrx/game/savegame/common.c +++ /dev/null @@ -1,641 +0,0 @@ -#include "benchmark.h" -#include "config.h" -#include "debug.h" -#include "enum_map.h" -#include "game/game.h" -#include "game/game_flow.h" -#include "game/gun/const.h" -#include "game/inventory.h" -#include "game/lara.h" -#include "game/objects.h" -#include "game/objects/traps/movable_block.h" -#include "game/pathing/lot.h" -#include "game/savegame.h" -#include "memory.h" - -#define MAX_STRATEGIES 2 -#define SAVES_DIR "saves" - -static SAVEGAME_VERSION m_InitialVersion = VERSION_LEGACY; -static SAVEGAME_INFO *m_SavegameInfo = nullptr; -static RESUME_INFO *m_ResumeInfo = nullptr; -static STATS_COMMON *m_DefaultStats = nullptr; -static int32_t m_SaveSlots = 0; -static int32_t m_SavedGames = 0; -static int32_t m_SaveCounter = 0; -static int32_t m_MostRecentlyUsedSlot = -1; -static int32_t m_MostRecentlyCreatedSlot = -1; -static int32_t m_BoundSlot = -1; - -static int32_t m_StrategyCount = 0; -static SAVEGAME_STRATEGY m_Strategies[MAX_STRATEGIES]; - -static void M_ClearSlots(void); -static bool M_FillSlot( - SAVEGAME_STRATEGY strategy, int32_t slot_num, const char *path); -static void M_ScanSavedGamesDir(const char *dir_path); -static void M_LoadPreprocess(void); -static void M_LoadPostprocess(void); - -static void M_ClearSlots(void) -{ - if (m_SavegameInfo == nullptr) { - return; - } - - for (int32_t i = 0; i < m_SaveSlots; i++) { - SAVEGAME_INFO *const savegame_info = &m_SavegameInfo[i]; - savegame_info->format = SAVEGAME_FORMAT_INVALID; - savegame_info->counter = -1; - savegame_info->level_num = -1; - Memory_FreePointer(&savegame_info->full_path); - Memory_FreePointer(&savegame_info->level_title); - } -} - -static bool M_FillSlot( - const SAVEGAME_STRATEGY strategy, const int32_t slot_num, - const char *const path) -{ - SAVEGAME_INFO *const savegame_info = &m_SavegameInfo[slot_num]; - if (strategy.format <= savegame_info->format) { - return true; - } - - bool result = false; - MYFILE *const fp = File_Open(path, FILE_OPEN_READ); - if (fp != nullptr) { - if (strategy.fill_info_func(fp, savegame_info)) { - savegame_info->format = strategy.format; - Memory_FreePointer(&savegame_info->full_path); - savegame_info->full_path = Memory_DupStr(path); - result = true; - } - File_Close(fp); - } - return result; -} - -static void M_ScanSavedGamesDir(const char *const dir_path) -{ - void *const dir_handle = File_OpenDirectory(dir_path); - if (dir_handle == nullptr) { - return; - } - - while (true) { - const char *const file_name = File_ReadDirectory(dir_handle); - if (file_name == nullptr) { - break; - } - if (strcmp(file_name, ".") == 0 || strcmp(file_name, "..") == 0) { - continue; - } - - for (int32_t i = 0; i < m_StrategyCount; i++) { - const SAVEGAME_STRATEGY strategy = m_Strategies[i]; - if (!strategy.allow_load) { - continue; - } - - int32_t slot = -1; - const int32_t parsed = - sscanf(file_name, strategy.get_save_file_pattern_func(), &slot); - if (parsed == 1 && slot >= 0 && slot < m_SaveSlots) { - char *file_path = String_Format("%s/%s", dir_path, file_name); - M_FillSlot(strategy, slot, file_path); - Memory_FreePointer(&file_path); - } - } - } - - File_CloseDirectory(dir_handle); -} - -static void M_LoadPreprocess(void) -{ - Savegame_InitCurrentInfo(); -} - -static void M_LoadPostprocess(void) -{ - // TODO: tidy this; skidoo drivers currently require handle_save_func to be - // called immediately on load within the strategies. - for (int32_t i = 0; i < Item_GetLevelCount(); i++) { - ITEM *const item = Item_Get(i); - const OBJECT *const obj = Object_Get(item->object_id); - - if (obj->save_position && obj->shadow_size) { - int16_t room_num = item->room_num; - const SECTOR *const sector = Room_GetSector( - item->pos.x, item->pos.y, item->pos.z, &room_num); - item->floor = - Room_GetHeight(sector, item->pos.x, item->pos.y, item->pos.z); - } - - if (obj->save_flags != 0) { - item->flags &= 0xFF00; - } -#if TR_VERSION == 1 - if (obj->handle_save_func != nullptr) { - obj->handle_save_func(item, SAVEGAME_STAGE_AFTER_LOAD); - } -#endif - } - - MovableBlock_SetupFloor(); - - LARA_INFO *const lara = Lara_GetLaraInfo(); -#if TR_VERSION == 1 - if (Game_GetBonusFlag() != GBF_NONE) { - g_Config.profile.new_game_plus_unlock = true; - } - LOT_ClearLOT(&lara->lot); -#else - if (lara->burn) { - lara->burn = 0; - Lara_CatchFire(); - } -#endif -} - -SAVEGAME_VERSION Savegame_GetInitialVersion(void) -{ - return m_InitialVersion; -} - -void Savegame_SetInitialVersion(const SAVEGAME_VERSION version) -{ - m_InitialVersion = version; -} - -void Savegame_BindSlot(const int32_t slot_num) -{ - m_BoundSlot = slot_num; - m_MostRecentlyUsedSlot = slot_num; - LOG_DEBUG("Binding save slot %d", slot_num); -} - -int32_t Savegame_GetMostRecentlyUsedSlot(void) -{ - return m_MostRecentlyUsedSlot; -} - -void Savegame_UnbindSlot(void) -{ - LOG_DEBUG("Resetting the save slot"); - m_BoundSlot = -1; -} - -int32_t Savegame_GetBoundSlot(void) -{ - return m_BoundSlot; -} - -int32_t Savegame_GetLevelNumber(const int32_t slot_num) -{ - return m_SavegameInfo[slot_num].level_num; -} - -bool Savegame_IsSlotFree(const int32_t slot_num) -{ - return m_SavegameInfo[slot_num].level_num == -1; -} - -int32_t Savegame_GetCounter(void) -{ - return m_SaveCounter; -} - -int32_t Savegame_GetTotalCount(void) -{ - return m_SavedGames; -} - -int32_t Savegame_GetMostRecentlyCreatedSlot(void) -{ - return m_MostRecentlyCreatedSlot; -} - -bool Savegame_RestartAvailable(const int32_t slot_num) -{ -#if TR_VERSION == 1 - if (slot_num == -1) { - return true; - } - - const SAVEGAME_INFO *const savegame_info = &m_SavegameInfo[slot_num]; - return savegame_info->features.restart; -#else - return false; -#endif -} - -void Savegame_RegisterStrategy(const SAVEGAME_STRATEGY strategy) -{ - ASSERT(m_StrategyCount < MAX_STRATEGIES); - m_Strategies[m_StrategyCount] = strategy; - m_StrategyCount++; -} - -void Savegame_Init(void) -{ - m_ResumeInfo = Memory_Alloc( - sizeof(RESUME_INFO) - * (GF_GetLevelTable(GFLT_MAIN)->count - + GF_GetLevelTable(GFLT_DEMOS)->count)); - - m_SaveSlots = Savegame_GetSlotCount(); - m_SavegameInfo = Memory_Alloc(sizeof(SAVEGAME_INFO) * m_SaveSlots); - - const GF_LEVEL_TABLE *const level_table = GF_GetLevelTable(GFLT_DEMOS); - for (int32_t i = 0; i < level_table->count; i++) { - RESUME_INFO *const resume_info = - Savegame_GetCurrentInfo(&level_table->levels[i]); - resume_info->flags.available = 1; - resume_info->flags.has_pistols = 1; - resume_info->pistol_ammo = 1000; - resume_info->gun_status = LGS_ARMLESS; - resume_info->equipped_gun_type = LGT_PISTOLS; -#if TR_VERSION == 1 - resume_info->holsters_gun_type = LGT_PISTOLS; - resume_info->back_gun_type = LGT_UNARMED; - resume_info->lara_hitpoints = LARA_MAX_HITPOINTS; -#endif - } -} - -bool Savegame_IsInitialised(void) -{ - return m_SavegameInfo != nullptr; -} - -void Savegame_Shutdown(void) -{ - M_ClearSlots(); - Memory_FreePointer(&m_ResumeInfo); - Memory_FreePointer(&m_SavegameInfo); - Memory_FreePointer(&m_DefaultStats); -} - -RESUME_INFO *Savegame_GetCurrentInfo(const GF_LEVEL *const level) -{ - ASSERT(m_ResumeInfo != nullptr); - ASSERT(level != nullptr); - if (GF_GetLevelTableType(level->type) == GFLT_MAIN) { - return &m_ResumeInfo[level->num]; - } else if (level->type == GFL_DEMO) { - return &m_ResumeInfo[GF_GetLevelTable(GFLT_MAIN)->count]; - } - LOG_WARNING( - "Warning: unable to get resume info for level %d (type=%s)", level->num, - ENUM_MAP_TO_STRING(GF_LEVEL_TYPE, level->type)); - return nullptr; -} - -void Savegame_SetCurrentInfo(const int32_t current_slot, const int32_t src_slot) -{ - m_ResumeInfo[current_slot] = m_ResumeInfo[src_slot]; -} - -const SAVEGAME_INFO *Savegame_GetSavegameInfo(const int32_t slot_num) -{ - return &m_SavegameInfo[slot_num]; -} - -void Savegame_InitCurrentInfo(void) -{ - const GF_LEVEL_TABLE *const level_table = GF_GetLevelTable(GFLT_MAIN); - for (int32_t i = 0; i < level_table->count; i++) { - const GF_LEVEL *const level = &level_table->levels[i]; - Savegame_ResetCurrentInfo(level); - Savegame_ApplyLogicToCurrentInfo(level); - Savegame_GetCurrentInfo(level)->flags.available = 0; - } - - if (GF_GetGymLevel() != nullptr) { - Savegame_GetCurrentInfo(GF_GetGymLevel())->flags.available = 1; - } - if (GF_GetFirstLevel() != nullptr) { - Savegame_GetCurrentInfo(GF_GetFirstLevel())->flags.available = 1; - } -} - -void Savegame_ResetCurrentInfo(const GF_LEVEL *const level) -{ - LOG_INFO("Resetting resume info for level #%d", level->num); - RESUME_INFO *const current = Savegame_GetCurrentInfo(level); - memset(current, 0, sizeof(RESUME_INFO)); -} - -void Savegame_CarryCurrentInfoToNextLevel( - const GF_LEVEL *const src_level, const GF_LEVEL *const dst_level) -{ - LOG_INFO( - "Copying resume info from level #%d to level #%d", src_level->num, - dst_level->num); - RESUME_INFO *const src_resume = Savegame_GetCurrentInfo(src_level); - RESUME_INFO *const dst_resume = Savegame_GetCurrentInfo(dst_level); - memcpy(dst_resume, src_resume, sizeof(RESUME_INFO)); -} - -void Savegame_PersistGameToCurrentInfo(const GF_LEVEL *const level) -{ - RESUME_INFO *const resume = Savegame_GetCurrentInfo(level); - LARA_INFO *const lara = Lara_GetLaraInfo(); - -#if TR_VERSION == 1 - resume->lara_hitpoints = Lara_GetItem()->hit_points; -#endif - resume->flags.available = 1; - resume->small_medipacks = Inv_RequestItem(O_SMALL_MEDIPACK_ITEM); - resume->large_medipacks = Inv_RequestItem(O_LARGE_MEDIPACK_ITEM); - - resume->pistol_ammo = 1000; - if (Inv_RequestItem(O_PISTOL_ITEM)) { - resume->flags.has_pistols = 1; - } else { - resume->flags.has_pistols = 0; - } - - if (Inv_RequestItem(O_SHOTGUN_ITEM)) { - resume->flags.has_shotgun = 1; - resume->shotgun_ammo = lara->shotgun_ammo.ammo; - } else { - resume->flags.has_shotgun = 0; - resume->shotgun_ammo = - Inv_RequestItem(O_SHOTGUN_AMMO_ITEM) * SHOTGUN_AMMO_QTY; - } - - if (Inv_RequestItem(O_MAGNUM_ITEM)) { - resume->flags.has_magnums = 1; - resume->magnum_ammo = lara->magnum_ammo.ammo; - } else { - resume->flags.has_magnums = 0; - resume->magnum_ammo = - Inv_RequestItem(O_MAGNUM_AMMO_ITEM) * MAGNUM_AMMO_QTY; - } - - if (Inv_RequestItem(O_UZI_ITEM)) { - resume->flags.has_uzis = 1; - resume->uzi_ammo = lara->uzi_ammo.ammo; - } else { - resume->flags.has_uzis = 0; - resume->uzi_ammo = Inv_RequestItem(O_UZI_AMMO_ITEM) * UZI_AMMO_QTY; - } - -#if TR_VERSION == 1 - resume->num_scions = Inv_RequestItem(O_SCION_ITEM_1); - - resume->equipped_gun_type = lara->gun_type; - resume->holsters_gun_type = lara->holsters_gun_type; - resume->back_gun_type = lara->back_gun_type; - if (lara->gun_status == LGS_READY) { - resume->gun_status = LGS_READY; - } else { - resume->gun_status = LGS_ARMLESS; - } -#elif TR_VERSION == 2 - if (Inv_RequestItem(O_M16_ITEM)) { - resume->flags.has_m16 = 1; - resume->m16_ammo = lara->m16_ammo.ammo; - } else { - resume->flags.has_m16 = 0; - resume->m16_ammo = Inv_RequestItem(O_M16_AMMO_ITEM) * M16_AMMO_QTY; - } - - if (Inv_RequestItem(O_HARPOON_ITEM)) { - resume->flags.has_harpoon = 1; - resume->harpoon_ammo = lara->harpoon_ammo.ammo; - } else { - resume->flags.has_harpoon = 0; - resume->harpoon_ammo = - Inv_RequestItem(O_HARPOON_AMMO_ITEM) * HARPOON_AMMO_QTY; - } - - if (Inv_RequestItem(O_GRENADE_ITEM)) { - resume->flags.has_grenade = 1; - resume->grenade_ammo = lara->grenade_ammo.ammo; - } else { - resume->flags.has_grenade = 0; - resume->grenade_ammo = - Inv_RequestItem(O_GRENADE_AMMO_ITEM) * GRENADE_AMMO_QTY; - } - - resume->flares = Inv_RequestItem(O_FLARE_ITEM); - if (lara->gun_type == LGT_FLARE) { - resume->equipped_gun_type = lara->last_gun_type; - } else { - resume->equipped_gun_type = lara->gun_type; - } - resume->gun_status = LGS_ARMLESS; -#endif -} - -void Savegame_ProcessItemsBeforeSave(void) -{ - for (int32_t i = 0; i < Item_GetLevelCount(); i++) { - ITEM *const item = Item_Get(i); - const OBJECT *const obj = Object_Get(item->object_id); - if (obj->handle_save_func != nullptr) { - obj->handle_save_func(item, SAVEGAME_STAGE_BEFORE_SAVE); - } - } -} - -void Savegame_ProcessItemsBeforeLoad(void) -{ - for (int32_t i = 0; i < Item_GetLevelCount(); i++) { - ITEM *const item = Item_Get(i); - const OBJECT *const obj = Object_Get(item->object_id); - if (obj->handle_save_func != nullptr) { - obj->handle_save_func(item, SAVEGAME_STAGE_BEFORE_LOAD); - } - } -} - -void Savegame_SetDefaultStats( - const GF_LEVEL *const level, const STATS_COMMON stats) -{ - if (m_DefaultStats == nullptr) { - m_DefaultStats = Memory_Alloc( - sizeof(STATS_COMMON) * GF_GetLevelTable(GFLT_MAIN)->count); - } - m_DefaultStats[level->num] = stats; -} - -STATS_COMMON Savegame_GetDefaultStats(const GF_LEVEL *const level) -{ - if (m_DefaultStats == nullptr - || (level->type != GFL_NORMAL && level->type != GFL_BONUS)) { - return (STATS_COMMON) {}; - } - return m_DefaultStats[level->num]; -} - -void Savegame_ScanSavedGames(void) -{ - BENCHMARK benchmark = Benchmark_Start(); - M_ClearSlots(); - - m_SaveCounter = 0; - m_SavedGames = 0; - m_MostRecentlyCreatedSlot = -1; - - M_ScanSavedGamesDir(SAVES_DIR); - M_ScanSavedGamesDir("."); - - for (int32_t i = 0; i < m_SaveSlots; i++) { - SAVEGAME_INFO *const savegame_info = &m_SavegameInfo[i]; - if (savegame_info->level_title != nullptr) { - if (savegame_info->counter > m_SaveCounter) { - m_SaveCounter = savegame_info->counter; - m_MostRecentlyCreatedSlot = i; - } - m_SavedGames++; - } - } - - Benchmark_End(&benchmark, nullptr); -} - -bool Savegame_Save(const int32_t slot_idx) -{ - bool result = false; - Savegame_BindSlot(slot_idx); - - File_CreateDirectory(SAVES_DIR); - - const GF_LEVEL *const current_level = Game_GetCurrentLevel(); - const char *const level_title = current_level->title; - - Savegame_PersistGameToCurrentInfo(current_level); - -#if TR_VERSION == 1 - const GF_LEVEL_TABLE *const level_table = GF_GetLevelTable(GFLT_MAIN); - for (int32_t i = 0; i < level_table->count; i++) { - const GF_LEVEL *const level = &level_table->levels[i]; - if (level->type == GFL_CURRENT) { - Savegame_SetCurrentInfo(i, current_level->num); - } - } -#endif - - SAVEGAME_INFO *const savegame_info = &m_SavegameInfo[slot_idx]; - const bool was_slot_empty = savegame_info->full_path == nullptr; - - m_SaveCounter++; - for (int32_t i = 0; i < m_StrategyCount; i++) { - const SAVEGAME_STRATEGY strategy = m_Strategies[i]; - if (!strategy.allow_save || strategy.save_to_file_func == nullptr) { - continue; - } - - char *file_name = - String_Format(strategy.get_save_file_pattern_func(), slot_idx); - char *full_path = String_Format("%s/%s", SAVES_DIR, file_name); - MYFILE *const fp = File_Open(full_path, FILE_OPEN_WRITE); - if (fp != nullptr) { - strategy.save_to_file_func(fp, savegame_info); - savegame_info->format = strategy.format; - Memory_FreePointer(&savegame_info->full_path); - savegame_info->full_path = Memory_DupStr(File_GetPath(fp)); - savegame_info->counter = m_SaveCounter; - savegame_info->level_num = current_level->num; - savegame_info->level_title = - level_title != nullptr ? Memory_DupStr(level_title) : nullptr; - File_Close(fp); - result = true; - } - - Memory_FreePointer(&file_name); - Memory_FreePointer(&full_path); - } - - if (result) { - m_MostRecentlyCreatedSlot = slot_idx; - if (was_slot_empty) { - m_SavedGames++; - } - Savegame_HighlightNewestSlot(); - } else { - m_SaveCounter--; - } - - return result; -} - -bool Savegame_Load(const int32_t slot_idx) -{ - const SAVEGAME_INFO *const savegame_info = &m_SavegameInfo[slot_idx]; - ASSERT(savegame_info->format != 0); - - M_LoadPreprocess(); - - bool result = false; - for (int32_t i = 0; i < m_StrategyCount; i++) { - const SAVEGAME_STRATEGY strategy = m_Strategies[i]; - if (strategy.format != savegame_info->format) { - continue; - } - - MYFILE *const fp = File_Open(savegame_info->full_path, FILE_OPEN_READ); - if (fp != nullptr) { - result = strategy.load_from_file_func(fp); - File_Close(fp); - } - break; - } - - M_LoadPostprocess(); - m_InitialVersion = m_SavegameInfo[slot_idx].initial_version; - return result; -} - -bool Savegame_UpdateDeathCounters( - const int32_t slot_num, const int32_t death_count) -{ - ASSERT(slot_num >= 0); - const SAVEGAME_INFO *const savegame_info = &m_SavegameInfo[slot_num]; - ASSERT(savegame_info->format != SAVEGAME_FORMAT_INVALID); - - bool ret = false; - for (int32_t i = 0; i < m_StrategyCount; i++) { - const SAVEGAME_STRATEGY strategy = m_Strategies[i]; - if (savegame_info->format == strategy.format - && strategy.update_death_counters_func != nullptr) { - MYFILE *const fp = - File_Open(savegame_info->full_path, FILE_OPEN_READ_WRITE); - if (fp != nullptr) { - ret = strategy.update_death_counters_func(fp, death_count); - File_Close(fp); - } - break; - } - } - return ret; -} - -bool Savegame_LoadOnlyResumeInfo(int32_t slot_num) -{ - const SAVEGAME_INFO *const savegame_info = &m_SavegameInfo[slot_num]; - ASSERT(savegame_info->format != SAVEGAME_FORMAT_INVALID); - - bool ret = false; - for (int32_t i = 0; i < m_StrategyCount; i++) { - const SAVEGAME_STRATEGY strategy = m_Strategies[i]; - if (savegame_info->format == strategy.format - && strategy.load_only_resume_info_func != nullptr) { - MYFILE *const fp = - File_Open(savegame_info->full_path, FILE_OPEN_READ); - if (fp != nullptr) { - ret = strategy.load_only_resume_info_func(fp); - File_Close(fp); - } - break; - } - } - - Savegame_SetInitialVersion(m_SavegameInfo[slot_num].initial_version); - return ret; -} diff --git a/src/libtrx/game/shell/common.c b/src/libtrx/game/shell/common.c index 391ae69f9..2e180264e 100644 --- a/src/libtrx/game/shell/common.c +++ b/src/libtrx/game/shell/common.c @@ -130,42 +130,31 @@ void Shell_ExitSystemFmt(const char *fmt, ...) Memory_FreePointer(&message); } -bool Shell_IsFullscreen(void) +int32_t Shell_GetCurrentDisplayWidth(void) { - SDL_Window *const window = Shell_GetWindow(); - ASSERT(window != nullptr); - const Uint32 flags = SDL_GetWindowFlags(window); - return (flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0; -} - -SHELL_SIZE Shell_GetWindowSize(void) -{ - SDL_Window *const window = Shell_GetWindow(); - SHELL_SIZE result = { .w = -1, .h = -1 }; - if (window != nullptr) { - SDL_GetWindowSize(window, &result.w, &result.h); - } - return result; -} - -SHELL_SIZE Shell_GetCurrentSize(void) -{ - return Shell_IsFullscreen() ? Shell_GetCurrentDisplaySize() - : Shell_GetWindowSize(); -} - -SHELL_SIZE Shell_GetCurrentDisplaySize(void) -{ - int32_t display_idx = 0; - SDL_Window *const window = Shell_GetWindow(); - if (window != nullptr) { - display_idx = SDL_GetWindowDisplayIndex(window); - } SDL_DisplayMode dm; - if (SDL_GetCurrentDisplayMode(display_idx, &dm) == 0) { - return (SHELL_SIZE) { .w = dm.w, .h = dm.h }; + SDL_GetCurrentDisplayMode(0, &dm); + return dm.w; +} + +int32_t Shell_GetCurrentDisplayHeight(void) +{ + SDL_DisplayMode dm; + SDL_GetCurrentDisplayMode(0, &dm); + return dm.h; +} + +void Shell_GetWindowSize(int32_t *const out_width, int32_t *const out_height) +{ + ASSERT(out_width != nullptr); + ASSERT(out_height != nullptr); + SDL_Window *const window = Shell_GetWindow(); + if (window == nullptr) { + *out_width = -1; + *out_height = -1; + } else { + SDL_GetWindowSize(window, out_width, out_height); } - return (SHELL_SIZE) { .w = -1, .h = -1 }; } void Shell_ScheduleExit(void) diff --git a/src/libtrx/game/shell/main.c b/src/libtrx/game/shell/main.c index 54a5a0653..1a29106ab 100644 --- a/src/libtrx/game/shell/main.c +++ b/src/libtrx/game/shell/main.c @@ -6,18 +6,28 @@ #include +static int m_ArgCount = 0; +static const char **m_ArgStrings = nullptr; + +void Shell_GetCommandLine(int *arg_count, const char ***args) +{ + *arg_count = m_ArgCount; + *args = m_ArgStrings; +} + int main(int argc, char *argv[]) { - if (!Shell_ParseArgs(argc, (const char **)argv)) { - return 0; - } - char *log_path = File_GetFullPath(PROJECT_NAME ".log"); Log_Init(log_path); Memory_FreePointer(&log_path); + LOG_INFO("Game directory: %s", File_GetGameDirectory()); + + m_ArgCount = argc; + m_ArgStrings = (const char **)argv; + Shell_Setup(); - int32_t exit_code = Shell_Main(); - Shell_Terminate(exit_code); - return exit_code; + Shell_Main(); + Shell_Terminate(0); + return 0; } diff --git a/src/libtrx/game/text.c b/src/libtrx/game/text.c index 5d280ebcc..28bfdedec 100644 --- a/src/libtrx/game/text.c +++ b/src/libtrx/game/text.c @@ -11,14 +11,7 @@ typedef struct { GLYPH_INFO *glyph; UT_hash_handle hh; -} M_GLYPH_MAP_ENTRY; - -typedef struct { - char *text; - const GLYPH_INFO **glyphs; - size_t glyph_count; - UT_hash_handle hh; -} M_TEXT_MAP_ENTRY; +} M_HASH_ENTRY; static TEXTSTRING m_TextStrings[TEXT_MAX_STRINGS] = {}; @@ -35,8 +28,7 @@ static GLYPH_INFO m_Glyphs[] = { static size_t m_GlyphLookupKeyCap = 0; static char *m_GlyphLookupKey = nullptr; -static M_GLYPH_MAP_ENTRY *m_GlyphMap = nullptr; -static M_TEXT_MAP_ENTRY *m_TextMap = nullptr; +static M_HASH_ENTRY *m_GlyphMap = nullptr; static size_t M_GetGlyphSize(const char *ptr); @@ -74,8 +66,7 @@ void Text_Init(void) // table for faster text-to-glyph resolution. for (GLYPH_INFO *glyph_ptr = m_Glyphs; glyph_ptr->text != nullptr; glyph_ptr++) { - M_GLYPH_MAP_ENTRY *const hash_entry = - Memory_Alloc(sizeof(M_GLYPH_MAP_ENTRY)); + M_HASH_ENTRY *const hash_entry = Memory_Alloc(sizeof(M_HASH_ENTRY)); hash_entry->glyph = glyph_ptr; HASH_ADD_KEYPTR( hh, m_GlyphMap, glyph_ptr->text, strlen(glyph_ptr->text), @@ -91,29 +82,14 @@ void Text_Shutdown(void) for (int32_t i = 0; i < TEXT_MAX_STRINGS; i++) { TEXTSTRING *const text = &m_TextStrings[i]; Memory_FreePointer(&text->content); - text->content_cap = 0; Memory_FreePointer(&text->glyphs); - text->glyphs_cap = 0; } + M_HASH_ENTRY *current, *tmp; + HASH_ITER(hh, m_GlyphMap, current, tmp) { - M_GLYPH_MAP_ENTRY *current, *tmp; - HASH_ITER(hh, m_GlyphMap, current, tmp) - { - HASH_DEL(m_GlyphMap, current); - Memory_Free(current); - } - } - - { - M_TEXT_MAP_ENTRY *current, *tmp; - HASH_ITER(hh, m_TextMap, current, tmp) - { - Memory_FreePointer(¤t->text); - Memory_FreePointer(¤t->glyphs); - HASH_DEL(m_TextMap, current); - Memory_FreePointer(¤t); - } + HASH_DEL(m_GlyphMap, current); + Memory_Free(current); } Memory_FreePointer(&m_GlyphLookupKey); @@ -158,12 +134,8 @@ TEXTSTRING *Text_Create(int16_t x, int16_t y, const char *const content) } TEXTSTRING *text = &m_TextStrings[free_idx]; - if (text->content != nullptr) { - text->content[0] = '\0'; - } - if (text->glyphs != nullptr) { - text->glyphs[0] = nullptr; - } + text->content = nullptr; + text->glyphs = nullptr; text->scale.h = TEXT_BASE_SCALE; text->scale.v = TEXT_BASE_SCALE; text->pos.x = x; @@ -192,18 +164,24 @@ void Text_Remove(TEXTSTRING *const text) } if (text->flags.active) { text->flags.active = 0; - if (text->content != nullptr) { - text->content[0] = '\0'; - } - if (text->glyphs != nullptr) { - text->glyphs[0] = nullptr; - } + Memory_FreePointer(&text->content); + Memory_FreePointer(&text->glyphs); } } -static const GLYPH_INFO **M_Decompose( - const char *const content, size_t *out_glyph_count) +void Text_ChangeText(TEXTSTRING *const text, const char *const content) { + if (text == nullptr) { + return; + } + + ASSERT(content != nullptr); + Memory_FreePointer(&text->content); + Memory_FreePointer(&text->glyphs); + if (!text->flags.active) { + return; + } + // Count number of characters size_t glyph_count = 0; const char *content_ptr = content; @@ -213,11 +191,12 @@ static const GLYPH_INFO **M_Decompose( glyph_count++; } + text->content = Memory_DupStr(content); + text->glyphs = Memory_Alloc((glyph_count + 1) * sizeof(GLYPH_INFO *)); + // Assign glyphs using hash table - const GLYPH_INFO **glyphs = - Memory_Alloc((glyph_count + 1) * sizeof(GLYPH_INFO *)); content_ptr = content; - const GLYPH_INFO **glyph_ptr = glyphs; + const GLYPH_INFO **glyph_ptr = text->glyphs; while (*content_ptr != '\0') { const size_t glyph_size = M_GetGlyphSize(content_ptr); if (m_GlyphLookupKeyCap <= glyph_size) { @@ -228,7 +207,7 @@ static const GLYPH_INFO **M_Decompose( strncpy(m_GlyphLookupKey, content_ptr, glyph_size); m_GlyphLookupKey[glyph_size] = '\0'; - M_GLYPH_MAP_ENTRY *entry; + M_HASH_ENTRY *entry; HASH_FIND_STR(m_GlyphMap, m_GlyphLookupKey, entry); if (entry != nullptr) { @@ -240,67 +219,8 @@ static const GLYPH_INFO **M_Decompose( content_ptr += glyph_size; } - if (out_glyph_count != nullptr) { - *out_glyph_count = glyph_count; - } - // guard *glyph_ptr++ = nullptr; - return glyphs; -} - -static const GLYPH_INFO **M_DecomposeWithCache( - const char *const content, size_t *out_glyph_count) -{ - M_TEXT_MAP_ENTRY *entry; - HASH_FIND_STR(m_TextMap, content, entry); - if (entry == nullptr) { - entry = Memory_Alloc(sizeof(M_TEXT_MAP_ENTRY)); - entry->text = Memory_DupStr(content); - entry->glyphs = M_Decompose(content, &entry->glyph_count); - HASH_ADD_STR(m_TextMap, text, entry); - } - if (out_glyph_count != nullptr) { - *out_glyph_count = entry->glyph_count; - } - return entry->glyphs; -} - -void Text_ChangeText(TEXTSTRING *const text, const char *const content) -{ - if (text == nullptr) { - return; - } - - ASSERT(content != nullptr); - if (text->content != nullptr) { - text->content[0] = '\0'; - } - if (text->glyphs != nullptr) { - text->glyphs[0] = nullptr; - } - if (!text->flags.active) { - return; - } - - const size_t content_size = strlen(content) + 1; - if (content_size >= text->content_cap) { - text->content_cap = content_size; - text->content = Memory_Realloc(text->content, content_size); - } - strcpy(text->content, content); - - size_t glyph_count; - const GLYPH_INFO **glyphs = - M_DecomposeWithCache(text->content, &glyph_count); - const size_t glyphs_size = (glyph_count + 1) * sizeof(GLYPH_INFO *); - if (glyphs_size >= text->glyphs_cap) { - text->glyphs_cap = glyphs_size; - text->glyphs = Memory_Realloc(text->glyphs, glyphs_size); - } - text->glyphs[0] = nullptr; - memcpy(text->glyphs, glyphs, glyphs_size); - // Memory_FreePointer(&glyphs); } void Text_SetPos(TEXTSTRING *const text, int16_t x, int16_t y) @@ -435,7 +355,11 @@ void Text_SetMultiline(TEXTSTRING *const text, const bool enable) int32_t Text_GetWidth(const TEXTSTRING *const text) { - if (text == nullptr || text->glyphs == nullptr) { + if (text == nullptr) { + return 0; + } + + if (text->glyphs == nullptr) { return 0; } @@ -471,5 +395,5 @@ int32_t Text_GetHeight(const TEXTSTRING *const text) height += TEXT_HEIGHT_FIXED; } } - return height * text->scale.v / (float)TEXT_BASE_SCALE; + return height * text->scale.v / TEXT_BASE_SCALE; } diff --git a/src/libtrx/game/ui/common.c b/src/libtrx/game/ui/common.c index 159eb7190..84b0175d9 100644 --- a/src/libtrx/game/ui/common.c +++ b/src/libtrx/game/ui/common.c @@ -1,176 +1,17 @@ #include "game/ui/common.h" #include "config.h" -#include "debug.h" #include "game/console/common.h" #include "game/game_string.h" -#include "game/scaler.h" -#include "game/ui/elements/anchor.h" -#include "game/ui/events.h" -#include "game/viewport.h" -#include "memory.h" - -#include -#include - -static struct { - MEMORY_ARENA_ALLOCATOR alloc; - - int32_t node_count; - int32_t node_capacity; - UI_NODE *nodes; - - UI_NODE *root; // The top-level container - UI_NODE *current; // The current container into which we attach nodes -} m_Priv = { - .alloc = { - .default_chunk_size = 1024 * 4, - }, -}; - -static UI_INPUT M_TranslateInput(uint32_t system_keycode); -static void M_MeasureNode(UI_NODE *node); -static void M_LayoutNode(UI_NODE *node, float x, float y, float w, float h); -static void M_DrawNode(const UI_NODE *node); - -static UI_INPUT M_TranslateInput(const uint32_t system_keycode) -{ - // clang-format off - switch (system_keycode) { - case SDLK_UP: return UI_KEY_UP; - case SDLK_DOWN: return UI_KEY_DOWN; - case SDLK_LEFT: return UI_KEY_LEFT; - case SDLK_RIGHT: return UI_KEY_RIGHT; - case SDLK_HOME: return UI_KEY_HOME; - case SDLK_END: return UI_KEY_END; - case SDLK_BACKSPACE: return UI_KEY_BACK; - case SDLK_RETURN: return UI_KEY_RETURN; - case SDLK_ESCAPE: return UI_KEY_ESCAPE; - } - // clang-format on - return -1; -} - -// Depth-first measure pass -static void M_MeasureNode(UI_NODE *const node) -{ - if (node == nullptr || node->ops == nullptr - || node->ops->measure == nullptr) { - return; - } - - // Recurse to children - UI_NODE *child = node->first_child; - while (child != nullptr) { - M_MeasureNode(child); - child = child->next_sibling; - } - - node->ops->measure(node); -} - -// Depth-first layout pass -static void M_LayoutNode( - UI_NODE *const node, const float x, const float y, const float w, - const float h) -{ - if (node == nullptr || node->ops == nullptr - || node->ops->layout == nullptr) { - return; - } - - node->ops->layout(node, x, y, w, h); - // Recursing to children is a responsibility of the layout function. -} - -// Depth-first draw pass -static void M_DrawNode(const UI_NODE *const node) -{ - if (node == nullptr || node->ops == nullptr || node->ops->draw == nullptr) { - return; - } - - node->ops->draw(node); - // Recursing to children is a responsibility of the draw function. -} - -// Allocate a new node -UI_NODE *UI_AllocNode( - const UI_WIDGET_OPS *const ops, const size_t additional_size) -{ - const size_t size = sizeof(UI_NODE) + additional_size; - UI_NODE *const node = Memory_ArenaAlloc(&m_Priv.alloc, size); - memset(node, 0, size); - node->ops = ops; - node->data = (char *)node + sizeof(UI_NODE); - m_Priv.node_count++; - return node; -} - -// Attach child to parent's child list -void UI_AddChild(UI_NODE *const child) -{ - // Special case - the root widget - if (m_Priv.root == nullptr) { - m_Priv.root = child; - return; - } - - UI_NODE *const parent = m_Priv.current; - if (parent == nullptr || child == nullptr) { - return; - } - child->parent = parent; - if (parent->first_child == nullptr) { - parent->first_child = child; - } else { - parent->last_child->next_sibling = child; - } - parent->last_child = child; -} - -void UI_PushCurrent(UI_NODE *const child) -{ - m_Priv.current = child; -} - -void UI_PopCurrent(void) -{ - ASSERT(m_Priv.current != nullptr); - m_Priv.current = m_Priv.current->parent; - if (m_Priv.current == nullptr) { - m_Priv.root = nullptr; - } -} - -// Scene management -void UI_BeginScene(void) -{ - Memory_ArenaReset(&m_Priv.alloc); - m_Priv.node_count = 0; - - // Make a root node. - UI_BeginAnchor(0.5f, 0.5f); -} - -void UI_EndScene(void) -{ - M_MeasureNode(m_Priv.root); - M_LayoutNode(m_Priv.root, 0, 0, UI_GetCanvasWidth(), UI_GetCanvasHeight()); - M_DrawNode(m_Priv.root); - UI_EndAnchor(); - ASSERT(m_Priv.root == nullptr); -} void UI_Init(void) { - UI_InitEvents(); + UI_Events_Init(); } void UI_Shutdown(void) { - Memory_ArenaFree(&m_Priv.alloc); - UI_ShutdownEvents(); + UI_Events_Shutdown(); } void UI_ToggleState(bool *const config_setting) @@ -180,46 +21,42 @@ void UI_ToggleState(bool *const config_setting) Console_Log(*config_setting ? GS(OSD_UI_ON) : GS(OSD_UI_OFF)); } +void UI_HandleLayoutChange(void) +{ + const EVENT event = { + .name = "layout_change", + .sender = nullptr, + .data = nullptr, + }; + UI_Events_Fire(&event); +} + void UI_HandleKeyDown(const uint32_t key) { - UI_FireEvent((EVENT) { + const EVENT event = { .name = "key_down", .sender = nullptr, - .data = (void *)M_TranslateInput(key), - }); + .data = (void *)UI_TranslateInput(key), + }; + UI_Events_Fire(&event); } void UI_HandleKeyUp(const uint32_t key) { - UI_FireEvent((EVENT) { + const EVENT event = { .name = "key_up", .sender = nullptr, - .data = (void *)M_TranslateInput(key), - }); + .data = (void *)UI_TranslateInput(key), + }; + UI_Events_Fire(&event); } void UI_HandleTextEdit(const char *const text) { - UI_FireEvent((EVENT) { - .name = "text_edit", .sender = nullptr, .data = (void *)text }); -} - -int32_t UI_GetCanvasWidth(void) -{ - return Scaler_CalcInverse(Viewport_GetWidth(), SCALER_TARGET_GENERIC); -} - -int32_t UI_GetCanvasHeight(void) -{ - return Scaler_CalcInverse(Viewport_GetHeight(), SCALER_TARGET_GENERIC); -} - -float UI_ScaleX(const float x) -{ - return Scaler_Calc(x, SCALER_TARGET_GENERIC); -} - -float UI_ScaleY(const float y) -{ - return Scaler_Calc(y, SCALER_TARGET_GENERIC); + const EVENT event = { + .name = "text_edit", + .sender = nullptr, + .data = (void *)text, + }; + UI_Events_Fire(&event); } diff --git a/src/libtrx/game/ui/dialogs/base_passport.c b/src/libtrx/game/ui/dialogs/base_passport.c deleted file mode 100644 index 082d7b62d..000000000 --- a/src/libtrx/game/ui/dialogs/base_passport.c +++ /dev/null @@ -1,59 +0,0 @@ -#include "game/ui/dialogs/base_passport.h" - -#include "game/inventory.h" -#include "game/scaler.h" -#include "game/ui/elements/modal.h" -#include "game/ui/elements/requester.h" -#include "game/ui/elements/resize.h" -#include "game/viewport.h" - -static int32_t M_GetVisibleRows(void); - -static int32_t M_GetVisibleRows(void) -{ - if (TR_VERSION == 2) { - return 10; - } else { - const int32_t res_h = - Scaler_CalcInverse(Viewport_GetHeight(), SCALER_TARGET_TEXT); - if (res_h <= 240) { - return 5; - } else if (res_h <= 384) { - return 7; - } else if (res_h <= 480) { - return 10; - } else { - return 12; - } - } -} - -void UI_BasePassportDialog_Init( - UI_REQUESTER_STATE *const req, const size_t max_rows) -{ - UI_Requester_Init(req, M_GetVisibleRows(), max_rows, true); - req->row_pad = 4.0f; - req->row_spacing = TR_VERSION == 1 ? 2.0f : 3.0f; - req->show_arrows = TR_VERSION == 1; - req->reserve_space = true; -} - -void UI_BasePassportDialog_Control(UI_REQUESTER_STATE *const req) -{ - UI_Requester_SetVisibleRows(req, M_GetVisibleRows()); -} - -void UI_BeginBasePassportDialog(void) -{ - const float modal_y = TR_VERSION == 1 - ? (g_Inv_Mode == INV_TITLE_MODE ? 0.72f : 0.55f) - : (g_Inv_Mode == INV_TITLE_MODE ? 0.8f : 0.65f); - UI_BeginModal(0.5f, modal_y); - UI_BeginResize(300.0f, -1.0f); -} - -void UI_EndBasePassportDialog(void) -{ - UI_EndResize(); - UI_EndModal(); -} diff --git a/src/libtrx/game/ui/dialogs/controls.c b/src/libtrx/game/ui/dialogs/controls.c deleted file mode 100644 index eea608c4a..000000000 --- a/src/libtrx/game/ui/dialogs/controls.c +++ /dev/null @@ -1,88 +0,0 @@ -#include "game/ui/dialogs/controls.h" - -#include "config.h" -#include "game/game_string.h" -#include "game/input.h" -#include "game/ui/dialogs/controls_backend.h" -#include "game/ui/dialogs/controls_editor.h" -#include "game/ui/elements/requester.h" - -typedef enum { - M_PHASE_BACKEND, - M_PHASE_EDITOR, -} M_PHASE; - -void UI_Controls_Init(UI_CONTROLS_STATE *const s) -{ - s->events = EventManager_Create(); - s->phase = M_PHASE_BACKEND; - s->backend = INPUT_BACKEND_KEYBOARD; - s->active_layout = g_Config.input.keyboard_layout; - UI_ControlsBackend_Init(&s->backend_state); - UI_ControlsEditor_Init(&s->editor_state, s->events); -} - -void UI_Controls_Free(UI_CONTROLS_STATE *const s) -{ - UI_ControlsEditor_Free(&s->editor_state); - UI_ControlsBackend_Free(&s->backend_state); - EventManager_Free(s->events); - s->events = nullptr; -} - -bool UI_Controls_Control(UI_CONTROLS_STATE *const s) -{ - switch (s->phase) { - case M_PHASE_BACKEND: { - const int32_t choice = UI_ControlsBackend_Control(&s->backend_state); - switch (choice) { - case UI_REQUESTER_NO_CHOICE: - return false; - case UI_REQUESTER_CANCEL: - return true; - case INPUT_BACKEND_KEYBOARD: - s->backend = choice; - s->phase = M_PHASE_EDITOR; - UI_ControlsEditor_Reinit( - &s->editor_state, choice, g_Config.input.keyboard_layout); - break; - case INPUT_BACKEND_CONTROLLER: - s->backend = choice; - s->phase = M_PHASE_EDITOR; - UI_ControlsEditor_Reinit( - &s->editor_state, choice, g_Config.input.controller_layout); - break; - } - break; - } - - case M_PHASE_EDITOR: { - const UI_CONTROLS_CHOICE choice = - UI_ControlsEditor_Control(&s->editor_state); - switch (choice) { - case UI_CONTROLS_CHOICE_NOOP: - break; - case UI_CONTROLS_CHOICE_GO_BACK: - s->phase = M_PHASE_BACKEND; - break; - case UI_CONTROLS_CHOICE_EXIT: - return true; - } - break; - } - } - - return false; -} - -void UI_Controls(UI_CONTROLS_STATE *const s) -{ - switch (s->phase) { - case M_PHASE_BACKEND: - UI_ControlsBackend(&s->backend_state); - break; - case M_PHASE_EDITOR: - UI_ControlsEditor(&s->editor_state); - break; - } -} diff --git a/src/libtrx/game/ui/dialogs/controls_backend.c b/src/libtrx/game/ui/dialogs/controls_backend.c deleted file mode 100644 index 58b6f68a9..000000000 --- a/src/libtrx/game/ui/dialogs/controls_backend.c +++ /dev/null @@ -1,57 +0,0 @@ -#include "game/ui/dialogs/controls_backend.h" - -#include "game/game_string.h" -#include "game/input.h" -#include "game/ui/elements/anchor.h" -#include "game/ui/elements/label.h" -#include "game/ui/elements/modal.h" -#include "game/ui/elements/requester.h" - -static const GAME_STRING_ID m_Options[] = { - GS_ID(CONTROLS_BACKEND_KEYBOARD), - GS_ID(CONTROLS_BACKEND_CONTROLLER), - nullptr, -}; - -void UI_ControlsBackend_Init(UI_CONTROLS_BACKEND_STATE *const s) -{ - int32_t count = 0; - for (count = 0; m_Options[count] != nullptr; count++) { } - UI_Requester_Init(&s->req, count, count, true); -} - -void UI_ControlsBackend_Free(UI_CONTROLS_BACKEND_STATE *const s) -{ - UI_Requester_Free(&s->req); -} - -int32_t UI_ControlsBackend_Control(UI_CONTROLS_BACKEND_STATE *const s) -{ - const int32_t choice = UI_Requester_Control(&s->req); - switch (choice) { - case 0: - return INPUT_BACKEND_KEYBOARD; - case 1: - return INPUT_BACKEND_CONTROLLER; - default: - return choice; - } -} - -void UI_ControlsBackend(UI_CONTROLS_BACKEND_STATE *const s) -{ - UI_BeginModal(0.5f, 2.0f / 3.0f); - UI_BeginRequester(&s->req, GS(CONTROLS_CUSTOMIZE)); - - for (int32_t i = UI_Requester_GetFirstRow(&s->req); - i < UI_Requester_GetLastRow(&s->req); i++) { - UI_BeginRequesterRow(&s->req, i); - UI_BeginAnchor(0.5f, 0.5f); - UI_Label(GameString_Get(m_Options[i])); - UI_EndAnchor(); - UI_EndRequesterRow(&s->req, i); - } - - UI_EndRequester(&s->req); - UI_EndModal(); -} diff --git a/src/libtrx/game/ui/dialogs/controls_editor.c b/src/libtrx/game/ui/dialogs/controls_editor.c deleted file mode 100644 index 1bf98943a..000000000 --- a/src/libtrx/game/ui/dialogs/controls_editor.c +++ /dev/null @@ -1,375 +0,0 @@ -#include "game/ui/dialogs/controls_editor.h" - -#include "config.h" -#include "game/const.h" -#include "game/game_string.h" -#include "game/input.h" -#include "game/shell.h" -#include "game/ui/elements/anchor.h" -#include "game/ui/elements/frame.h" -#include "game/ui/elements/label.h" -#include "game/ui/elements/modal.h" -#include "game/ui/elements/pad.h" -#include "game/ui/elements/requester.h" -#include "game/ui/elements/spacer.h" -#include "game/ui/elements/stack.h" -#include "game/ui/elements/window.h" -#include "utils.h" - -typedef enum { - M_PHASE_NAVIGATE_LAYOUT, - M_PHASE_NAVIGATE_INPUTS, - M_PHASE_NAVIGATE_INPUTS_DEBOUNCE, - M_PHASE_LISTEN, - M_PHASE_LISTEN_DEBOUNCE, - M_PHASE_EXIT, -} M_PHASE; - -static const INPUT_ROLE m_LeftRoles[] = { - // clang-format off - INPUT_ROLE_UP, - INPUT_ROLE_DOWN, - INPUT_ROLE_LEFT, - INPUT_ROLE_RIGHT, - INPUT_ROLE_STEP_L, - INPUT_ROLE_STEP_R, - INPUT_ROLE_SLOW, - INPUT_ROLE_ENTER_CONSOLE, - INPUT_ROLE_PAUSE, - INPUT_ROLE_TOGGLE_PHOTO_MODE, - INPUT_ROLE_TOGGLE_UI, - // INPUT_ROLE_CAMERA_RESET, // same as look, no need to configure - INPUT_ROLE_CAMERA_UP, - INPUT_ROLE_CAMERA_DOWN, - INPUT_ROLE_CAMERA_LEFT, - INPUT_ROLE_CAMERA_RIGHT, - INPUT_ROLE_CAMERA_FORWARD, - INPUT_ROLE_CAMERA_BACK, - (INPUT_ROLE)-1, - // clang-format on -}; - -static const INPUT_ROLE m_RightRoles_CheatsOff[] = { - // clang-format off - INPUT_ROLE_JUMP, - INPUT_ROLE_ACTION, - INPUT_ROLE_DRAW, -#if TR_VERSION == 2 - INPUT_ROLE_USE_FLARE, -#endif - INPUT_ROLE_LOOK, - INPUT_ROLE_ROLL, - INPUT_ROLE_OPTION, - (INPUT_ROLE)-1, - // clang-format on -}; - -static const INPUT_ROLE m_RightRoles_CheatsOn[] = { - // clang-format off - INPUT_ROLE_JUMP, - INPUT_ROLE_ACTION, - INPUT_ROLE_DRAW, -#if TR_VERSION == 2 - INPUT_ROLE_USE_FLARE, -#endif - INPUT_ROLE_LOOK, - INPUT_ROLE_ROLL, - INPUT_ROLE_OPTION, - INPUT_ROLE_FLY_CHEAT, - INPUT_ROLE_ITEM_CHEAT, - INPUT_ROLE_LEVEL_SKIP_CHEAT, - INPUT_ROLE_TURBO_CHEAT, - (INPUT_ROLE)-1, - // clang-format on -}; - -static const INPUT_ROLE *m_RightRoles = nullptr; - -static INPUT_ROLE M_GetInputRole(int32_t col, int32_t row); -static int32_t M_GetInputRoleCount(int32_t col); -static void M_CycleLayout(UI_CONTROLS_EDITOR_STATE *s, int32_t dir); -static UI_CONTROLS_CHOICE M_NavigateLayout(UI_CONTROLS_EDITOR_STATE *s); -static UI_CONTROLS_CHOICE M_NavigateInputs(UI_CONTROLS_EDITOR_STATE *s); -static UI_CONTROLS_CHOICE M_NavigateInputsDebounce(UI_CONTROLS_EDITOR_STATE *s); -static UI_CONTROLS_CHOICE M_Listen(UI_CONTROLS_EDITOR_STATE *s); -static UI_CONTROLS_CHOICE M_ListenDebounce(UI_CONTROLS_EDITOR_STATE *s); - -static void M_Title(const UI_CONTROLS_EDITOR_STATE *s); -static void M_InputChoice(UI_CONTROLS_EDITOR_STATE *s, INPUT_ROLE role); -static void M_InputLabel(const UI_CONTROLS_EDITOR_STATE *s, INPUT_ROLE role); -static void M_Column(UI_CONTROLS_EDITOR_STATE *s, const INPUT_ROLE *roles); - -static INPUT_ROLE M_GetInputRole(const int32_t col, const int32_t row) -{ - if (col == 0) { - return m_LeftRoles[row]; - } else { - return m_RightRoles[row]; - } -} - -static int32_t M_GetInputRoleCount(const int32_t col) -{ - int32_t row = 0; - while (M_GetInputRole(col, row) != (INPUT_ROLE)-1) { - row++; - } - return row; -} - -static void M_CycleLayout(UI_CONTROLS_EDITOR_STATE *const s, const int32_t dir) -{ - s->active_layout += dir; - s->active_layout += INPUT_LAYOUT_NUMBER_OF; - s->active_layout %= INPUT_LAYOUT_NUMBER_OF; - - const EVENT event = { - .name = "layout_change", - .sender = nullptr, - .data = nullptr, - }; - EventManager_Fire(s->events, &event); -} - -static UI_CONTROLS_CHOICE M_NavigateLayout(UI_CONTROLS_EDITOR_STATE *const s) -{ - if (g_InputDB.menu_confirm) { - return UI_CONTROLS_CHOICE_EXIT; - } else if (g_InputDB.menu_back) { - return UI_CONTROLS_CHOICE_GO_BACK; - } else if (g_InputDB.menu_left) { - M_CycleLayout(s, -1); - } else if (g_InputDB.menu_right) { - M_CycleLayout(s, 1); - } else if (g_InputDB.menu_down && s->active_layout != 0) { - s->phase = M_PHASE_NAVIGATE_INPUTS; - s->active_col = 0; - s->active_row = 0; - } else if (g_InputDB.menu_up && s->active_layout != 0) { - s->phase = M_PHASE_NAVIGATE_INPUTS; - s->active_col = 1; - s->active_row = M_GetInputRoleCount(1) - 1; - } else { - return UI_CONTROLS_CHOICE_NOOP; - } - s->active_role = M_GetInputRole(s->active_col, s->active_row); - return UI_CONTROLS_CHOICE_NOOP; -} - -static UI_CONTROLS_CHOICE M_NavigateInputs(UI_CONTROLS_EDITOR_STATE *const s) -{ - if (g_InputDB.menu_confirm) { - s->phase = M_PHASE_NAVIGATE_INPUTS_DEBOUNCE; - } else if (g_InputDB.menu_back) { - return UI_CONTROLS_CHOICE_GO_BACK; - } else if (g_InputDB.menu_left || g_InputDB.menu_right) { - s->active_col ^= 1; - CLAMP(s->active_row, 0, M_GetInputRoleCount(s->active_col) - 1); - } else if (g_InputDB.menu_up) { - s->active_row--; - if (s->active_row < 0) { - if (s->active_col == 0) { - s->phase = M_PHASE_NAVIGATE_LAYOUT; - } else { - s->active_col = 0; - s->active_row = M_GetInputRoleCount(0) - 1; - } - } - } else if (g_InputDB.menu_down) { - s->active_row++; - if (s->active_row >= M_GetInputRoleCount(s->active_col)) { - if (s->active_col == 0) { - s->active_col = 1; - s->active_row = 0; - } else { - s->phase = M_PHASE_NAVIGATE_LAYOUT; - } - } - } else { - return UI_CONTROLS_CHOICE_NOOP; - } - s->active_role = M_GetInputRole(s->active_col, s->active_row); - return UI_CONTROLS_CHOICE_NOOP; -} - -static UI_CONTROLS_CHOICE M_NavigateInputsDebounce( - UI_CONTROLS_EDITOR_STATE *const s) -{ - Shell_ProcessEvents(); - Input_Update(); - if (g_Input.any) { - return UI_CONTROLS_CHOICE_NOOP; - } - Input_EnterListenMode(); - s->phase = M_PHASE_LISTEN; - return UI_CONTROLS_CHOICE_NOOP; -} - -static UI_CONTROLS_CHOICE M_Listen(UI_CONTROLS_EDITOR_STATE *const s) -{ - if (!Input_ReadAndAssignRole( - s->backend, s->active_layout, s->active_role)) { - return UI_CONTROLS_CHOICE_NOOP; - } - - Input_ExitListenMode(); - - const EVENT event = { - .name = "key_change", - .sender = nullptr, - .data = nullptr, - }; - EventManager_Fire(s->events, &event); - - s->phase = M_PHASE_LISTEN_DEBOUNCE; - return UI_CONTROLS_CHOICE_NOOP; -} - -static UI_CONTROLS_CHOICE M_ListenDebounce(UI_CONTROLS_EDITOR_STATE *const s) -{ - if (!g_Input.any) { - s->phase = M_PHASE_NAVIGATE_INPUTS; - } - return UI_CONTROLS_CHOICE_NOOP; -} - -static void M_Title(const UI_CONTROLS_EDITOR_STATE *const s) -{ - UI_BeginAnchor(0.5f, 0.5f); - if (s->phase == M_PHASE_NAVIGATE_LAYOUT) { - UI_BeginFrame(UI_FRAME_SELECTED_OPTION); - } - UI_BeginPad(2.0f, 1.0f); - UI_Label(Input_GetLayoutName(s->active_layout)); - UI_EndPad(); - if (s->phase == M_PHASE_NAVIGATE_LAYOUT) { - UI_EndFrame(); - } - UI_EndAnchor(); -} - -static void M_InputLabel( - const UI_CONTROLS_EDITOR_STATE *const s, const INPUT_ROLE role) -{ - const bool is_selected = s->active_role == role - && (s->phase == M_PHASE_NAVIGATE_INPUTS - || s->phase == M_PHASE_NAVIGATE_INPUTS_DEBOUNCE); - if (is_selected) { - UI_BeginFrame(UI_FRAME_SELECTED_OPTION); - } - UI_Label(Input_GetRoleName(role)); - if (is_selected) { - UI_EndFrame(); - } -} - -static void M_InputChoice( - UI_CONTROLS_EDITOR_STATE *const s, const INPUT_ROLE role) -{ - const bool is_flashing = - Input_IsKeyConflicted(s->backend, s->active_layout, role); - const bool is_selected = - s->active_role == role && s->phase == M_PHASE_LISTEN; - - if (is_flashing) { - UI_BeginFlash(&s->flash); - } - if (is_selected) { - UI_BeginFrame(UI_FRAME_SELECTED_OPTION); - } - UI_Label(Input_GetKeyName(s->backend, s->active_layout, role)); - if (is_selected) { - UI_EndFrame(); - } - if (is_flashing) { - UI_EndFlash(); - } -} - -static void M_Column( - UI_CONTROLS_EDITOR_STATE *const s, const INPUT_ROLE *const roles) -{ - UI_BeginStack(UI_STACK_HORIZONTAL); - UI_BeginStack(UI_STACK_VERTICAL); - for (const INPUT_ROLE *role = roles; *role != (INPUT_ROLE)-1; role++) { - M_InputChoice(s, *role); - } - UI_EndStack(); - UI_Spacer(10.0f, 0.0f); - UI_BeginStack(UI_STACK_VERTICAL); - for (const INPUT_ROLE *role = roles; *role != (INPUT_ROLE)-1; role++) { - M_InputLabel(s, *role); - } - UI_EndStack(); - UI_EndStack(); -} - -void UI_ControlsEditor_Init( - UI_CONTROLS_EDITOR_STATE *const s, EVENT_MANAGER *events) -{ - m_RightRoles = g_Config.gameplay.enable_cheats ? m_RightRoles_CheatsOn - : m_RightRoles_CheatsOff; - s->events = events; - UI_Flash_Init(&s->flash, LOGIC_FPS * 2 / 3); -} - -void UI_ControlsEditor_Free(UI_CONTROLS_EDITOR_STATE *const s) -{ - UI_Flash_Free(&s->flash); -} - -void UI_ControlsEditor_Reinit( - UI_CONTROLS_EDITOR_STATE *s, INPUT_BACKEND backend, int32_t layout) -{ - s->backend = backend; - s->active_layout = layout; - s->active_row = 0; - s->active_col = 0; - s->active_role = M_GetInputRole(s->active_col, s->active_row); - s->phase = M_PHASE_NAVIGATE_LAYOUT; -} - -UI_CONTROLS_CHOICE UI_ControlsEditor_Control(UI_CONTROLS_EDITOR_STATE *const s) -{ - UI_Flash_Control(&s->flash); - switch (s->phase) { - case M_PHASE_NAVIGATE_LAYOUT: - return M_NavigateLayout(s); - case M_PHASE_NAVIGATE_INPUTS: - return M_NavigateInputs(s); - case M_PHASE_NAVIGATE_INPUTS_DEBOUNCE: - return M_NavigateInputsDebounce(s); - case M_PHASE_LISTEN: - return M_Listen(s); - case M_PHASE_LISTEN_DEBOUNCE: - return M_ListenDebounce(s); - default: - return UI_CONTROLS_CHOICE_NOOP; - } -} - -void UI_ControlsEditor(UI_CONTROLS_EDITOR_STATE *const s) -{ - UI_BeginModal(0.5f, 0.5f); - UI_BeginWindow(); - UI_WindowTitle(GS(CONTROLS_CUSTOMIZE)); - UI_BeginWindowBody(); - - UI_BeginStackEx((UI_STACK_SETTINGS) { - .orientation = UI_STACK_VERTICAL, - .align = { .h = UI_STACK_H_ALIGN_SPAN }, - }); - M_Title(s); - UI_Spacer(0.0f, 5.0f); - - UI_BeginStack(UI_STACK_HORIZONTAL); - M_Column(s, m_LeftRoles); - UI_Spacer(10.0f, 0.0f); - M_Column(s, m_RightRoles); - UI_EndStack(); - - UI_EndStack(); - UI_EndWindowBody(); - UI_EndWindow(); - UI_EndModal(); -} diff --git a/src/libtrx/game/ui/dialogs/examine_item.c b/src/libtrx/game/ui/dialogs/examine_item.c deleted file mode 100644 index 19256eed6..000000000 --- a/src/libtrx/game/ui/dialogs/examine_item.c +++ /dev/null @@ -1,144 +0,0 @@ -#include "game/ui/dialogs/examine_item.h" - -#include "game/game_string.h" -#include "game/input.h" -#include "game/sound.h" -#include "game/text.h" -#include "game/ui/elements/anchor.h" -#include "game/ui/elements/frame.h" -#include "game/ui/elements/label.h" -#include "game/ui/elements/modal.h" -#include "game/ui/elements/pad.h" -#include "game/ui/elements/resize.h" -#include "game/ui/elements/spacer.h" -#include "game/ui/elements/stack.h" -#include "memory.h" -#include "strings.h" -#include "utils.h" - -#include - -#define TITLE_MARGIN 5.0f -#define WINDOW_MARGIN 10.0f -#define DIALOG_PADDING 8.0f -#define PADDING_SCALED (3.5f * (DIALOG_PADDING + WINDOW_MARGIN)) - -static bool M_SelectPage(UI_EXAMINE_ITEM_STATE *state, int32_t new_page); - -static bool M_SelectPage( - UI_EXAMINE_ITEM_STATE *const state, const int32_t new_page) -{ - if (new_page == state->current_page || new_page < 0 - || new_page >= state->page_content->count) { - return false; - } - state->current_page = new_page; - return true; -} - -void UI_ExamineItem_Init( - UI_EXAMINE_ITEM_STATE *const state, const char *const title, - const char *const text, size_t max_lines) -{ - state->current_page = 0; - state->title = String_ToUpper(title); - - const char *wrapped = - String_WordWrap(text, Text_GetMaxLineLength() - PADDING_SCALED); - state->page_content = String_Paginate(wrapped, max_lines); - state->is_empty = String_IsEmpty(text); - Memory_FreePointer(&wrapped); - - // Compute actual maximum number of lines on any single page, which can be - // less than the line cap. - size_t max_vis_lines = 0; - for (int32_t i = 0; i < state->page_content->count; i++) { - size_t page_lines = 1; - const char *c = *(char **)Vector_Get(state->page_content, i); - while (*c != '\0') { - page_lines += *c++ == '\n'; - } - CLAMPL(max_vis_lines, page_lines); - } - state->max_lines = max_vis_lines; -} - -void UI_ExamineItem_Control(UI_EXAMINE_ITEM_STATE *const state) -{ - const int32_t page_shift = - g_InputDB.menu_left ? -1 : (g_InputDB.menu_right ? 1 : 0); - if (M_SelectPage(state, state->current_page + page_shift)) { - Sound_Effect(SFX_MENU_PASSPORT, nullptr, SPM_ALWAYS); - } -} - -void UI_ExamineItem_Free(UI_EXAMINE_ITEM_STATE *const state) -{ - Memory_FreePointer(&state->title); - for (int32_t i = state->page_content->count - 1; i >= 0; i--) { - char *const page = *(char **)Vector_Get(state->page_content, i); - Memory_Free(page); - } - Vector_Free(state->page_content); -} - -void UI_ExamineItem(UI_EXAMINE_ITEM_STATE *const state) -{ - if (state->is_empty) { - return; - } - - UI_BeginModal(0.5f, 0.5f); - UI_BeginFrame(UI_FRAME_DIALOG_BACKGROUND); - UI_BeginPad(DIALOG_PADDING, DIALOG_PADDING); - UI_BeginStackEx((UI_STACK_SETTINGS) { - .orientation = UI_STACK_VERTICAL, - .align = { .h = UI_STACK_H_ALIGN_SPAN }, - }); - - UI_BeginAnchor(0.5f, 0.5f); - UI_Label(state->title); - UI_EndAnchor(); - UI_Spacer(TITLE_MARGIN, TITLE_MARGIN); - - for (int32_t i = 0; i < state->page_content->count; i++) { - if (i != state->current_page) { - UI_BeginResize(-1.0f, 0.0f); - } else if (state->page_content->count == 1) { - UI_BeginResize(-1.0f, -1.0f); - } else { - UI_BeginResize(-1.0f, TEXT_HEIGHT_FIXED * state->max_lines); - } - UI_Label(*(char **)Vector_Get(state->page_content, i)); - UI_EndResize(); - } - - if (state->page_content->count > 1) { - UI_Spacer(TITLE_MARGIN, TITLE_MARGIN * 3); - - UI_BeginAnchor(1.0f, 0.5f); - UI_BeginStack(UI_STACK_HORIZONTAL); - - if (state->current_page > 0) { - UI_Label("\\{button left} "); - } - - char page_indicator[100]; - sprintf( - page_indicator, GS(PAGINATION_NAV), state->current_page + 1, - state->page_content->count); - UI_Label(page_indicator); - - if (state->current_page < state->page_content->count - 1) { - UI_Label(" \\{button right}"); - } - - UI_EndStack(); - UI_EndAnchor(); - } - - UI_EndStack(); - UI_EndPad(); - UI_EndFrame(); - UI_EndModal(); -} diff --git a/src/libtrx/game/ui/dialogs/new_game.c b/src/libtrx/game/ui/dialogs/new_game.c deleted file mode 100644 index 694be289b..000000000 --- a/src/libtrx/game/ui/dialogs/new_game.c +++ /dev/null @@ -1,55 +0,0 @@ -#include "game/ui/dialogs/new_game.h" - -#include "game/game_string.h" -#include "game/ui/elements/anchor.h" -#include "game/ui/elements/frame.h" -#include "game/ui/elements/label.h" -#include "game/ui/elements/modal.h" -#include "game/ui/elements/pad.h" -#include "game/ui/elements/requester.h" - -static const GAME_STRING_ID m_Options[] = { - GS_ID(PASSPORT_MODE_NEW_GAME), - GS_ID(PASSPORT_MODE_NEW_GAME_PLUS), - GS_ID(PASSPORT_MODE_NEW_GAME_JP), - GS_ID(PASSPORT_MODE_NEW_GAME_JP_PLUS), - nullptr, -}; - -void UI_NewGame_Init(UI_NEW_GAME_STATE *const s) -{ - int32_t option_count = 0; - for (int32_t i = 0; m_Options[i] != nullptr; i++) { - option_count++; - } - - UI_Requester_Init(&s->req, option_count, option_count, true); -} - -void UI_NewGame_Free(UI_NEW_GAME_STATE *const s) -{ - UI_Requester_Free(&s->req); -} - -int32_t UI_NewGame_Control(UI_NEW_GAME_STATE *const s) -{ - return UI_Requester_Control(&s->req); -} - -void UI_NewGame(UI_NEW_GAME_STATE *const s) -{ - UI_BeginModal(0.5f, 2.0f / 3.0f); - UI_BeginRequester(&s->req, GS(PASSPORT_SELECT_MODE)); - - for (int32_t i = UI_Requester_GetFirstRow(&s->req); - i < UI_Requester_GetLastRow(&s->req); i++) { - UI_BeginRequesterRow(&s->req, i); - UI_BeginAnchor(0.5f, 0.5f); - UI_Label(GameString_Get(m_Options[i])); - UI_EndAnchor(); - UI_EndRequesterRow(&s->req, i); - } - - UI_EndRequester(&s->req); - UI_EndModal(); -} diff --git a/src/libtrx/game/ui/dialogs/pause.c b/src/libtrx/game/ui/dialogs/pause.c deleted file mode 100644 index b329ca1b1..000000000 --- a/src/libtrx/game/ui/dialogs/pause.c +++ /dev/null @@ -1,72 +0,0 @@ -#include "game/ui/dialogs/pause.h" - -#include "game/game_string.h" -#include "game/ui/elements/anchor.h" -#include "game/ui/elements/frame.h" -#include "game/ui/elements/label.h" -#include "game/ui/elements/modal.h" -#include "game/ui/elements/pad.h" -#include "game/ui/elements/requester.h" - -static const GAME_STRING_ID m_Options[2][2] = { - { GS_ID(PAUSE_CONTINUE), GS_ID(PAUSE_QUIT) }, - { GS_ID(PAUSE_YES), GS_ID(PAUSE_NO) }, -}; - -void UI_Pause_Init(UI_PAUSE_STATE *const s) -{ - s->phase = 0; - UI_Requester_Init(&s->req, 2, 2, true); -} - -void UI_Pause_Free(UI_PAUSE_STATE *const s) -{ - UI_Requester_Free(&s->req); -} - -UI_PAUSE_EXIT_CHOICE UI_Pause_Control(UI_PAUSE_STATE *const s) -{ - const int32_t choice = UI_Requester_Control(&s->req); - if (s->phase == 0) { - if (choice == UI_REQUESTER_CANCEL) { - return UI_PAUSE_RESUME_PAUSE; - } else if (choice == 0) { - return UI_PAUSE_EXIT_TO_GAME; - } else if (choice == 1) { - s->phase = 1; - UI_Requester_Free(&s->req); - UI_Requester_Init(&s->req, 2, 2, true); - } - } else { - if (choice == UI_REQUESTER_CANCEL) { - s->phase = 0; - } else if (choice == 0) { - return UI_PAUSE_EXIT_TO_TITLE; - } else if (choice == 1) { - return UI_PAUSE_EXIT_TO_GAME; - } - } - return UI_PAUSE_NOOP; -} - -void UI_Pause(UI_PAUSE_STATE *const s) -{ - UI_BeginModal(0.5f, 1.0f); - UI_BeginPad(50.0f, 50.0f); - UI_BeginRequester( - &s->req, - s->phase == 0 ? GS(PAUSE_EXIT_TO_TITLE) : GS(PAUSE_ARE_YOU_SURE)); - - for (int32_t i = UI_Requester_GetFirstRow(&s->req); - i < UI_Requester_GetLastRow(&s->req); i++) { - UI_BeginRequesterRow(&s->req, i); - UI_BeginAnchor(0.5f, 0.5f); - UI_Label(GameString_Get(m_Options[s->phase][i])); - UI_EndAnchor(); - UI_EndRequesterRow(&s->req, i); - } - - UI_EndRequester(&s->req); - UI_EndPad(); - UI_EndModal(); -} diff --git a/src/libtrx/game/ui/dialogs/photo_mode.c b/src/libtrx/game/ui/dialogs/photo_mode.c deleted file mode 100644 index 903fe6a11..000000000 --- a/src/libtrx/game/ui/dialogs/photo_mode.c +++ /dev/null @@ -1,111 +0,0 @@ -#include "game/ui/dialogs/photo_mode.h" - -#include "config.h" -#include "game/game_string.h" -#include "game/input.h" -#include "game/ui/elements/frame.h" -#include "game/ui/elements/label.h" -#include "game/ui/elements/modal.h" -#include "game/ui/elements/pad.h" -#include "game/ui/elements/spacer.h" -#include "game/ui/elements/stack.h" - -#include - -void UI_PhotoMode(void) -{ - if (!g_Config.ui.enable_photo_mode_ui) { - return; - } - - char tmp[50]; - - UI_BeginModal(0.0f, 0.0f); - UI_BeginPad(8.0f, 8.0f); - UI_BeginFrame(UI_FRAME_DIALOG_BACKGROUND); - UI_BeginPad(8.0, 6.0); - - UI_BeginStackEx((UI_STACK_SETTINGS) { - .orientation = UI_STACK_VERTICAL, - .spacing = { .v = 8.0f }, - }); - UI_Label(GS(PHOTO_MODE_TITLE)); - - UI_BeginStack(UI_STACK_HORIZONTAL); - - // Inputs column - UI_BeginStack(UI_STACK_VERTICAL); - sprintf( - tmp, "%s%s%s%s%s%s: ", - Input_GetKeyName( - INPUT_BACKEND_KEYBOARD, g_Config.input.keyboard_layout, - INPUT_ROLE_CAMERA_UP), - Input_GetKeyName( - INPUT_BACKEND_KEYBOARD, g_Config.input.keyboard_layout, - INPUT_ROLE_CAMERA_DOWN), - Input_GetKeyName( - INPUT_BACKEND_KEYBOARD, g_Config.input.keyboard_layout, - INPUT_ROLE_CAMERA_FORWARD), - Input_GetKeyName( - INPUT_BACKEND_KEYBOARD, g_Config.input.keyboard_layout, - INPUT_ROLE_CAMERA_BACK), - Input_GetKeyName( - INPUT_BACKEND_KEYBOARD, g_Config.input.keyboard_layout, - INPUT_ROLE_CAMERA_LEFT), - Input_GetKeyName( - INPUT_BACKEND_KEYBOARD, g_Config.input.keyboard_layout, - INPUT_ROLE_CAMERA_RIGHT)); - UI_Label(tmp); - UI_Label( - "\\{button left} \\{button up} " - "\\{button down} \\{button right} : "); - sprintf(tmp, "%s: ", GS(PHOTO_MODE_ROLL_ROLE)); - UI_Label(tmp); - sprintf(tmp, "%s: ", GS(KEYMAP_ROLL)); - UI_Label(tmp); - sprintf(tmp, "%s: ", GS(PHOTO_MODE_FOV_ROLE)); - UI_Label(tmp); - sprintf(tmp, "%s: ", GS(KEYMAP_LOOK)); - UI_Label(tmp); - sprintf( - tmp, "%s: ", - Input_GetKeyName( - INPUT_BACKEND_KEYBOARD, g_Config.input.keyboard_layout, - INPUT_ROLE_TOGGLE_UI)); - UI_Label(tmp); - sprintf(tmp, "%s: ", GS(KEYMAP_ACTION)); - UI_Label(tmp); - sprintf( - tmp, "%s/%s: ", - Input_GetKeyName( - INPUT_BACKEND_KEYBOARD, g_Config.input.keyboard_layout, - INPUT_ROLE_TOGGLE_PHOTO_MODE), - Input_GetKeyName( - INPUT_BACKEND_KEYBOARD, g_Config.input.keyboard_layout, - INPUT_ROLE_OPTION)); - UI_Label(tmp); - UI_EndStack(); - - UI_Spacer(4.0f, 0.0f); - - // Behaviors column - UI_BeginStack(UI_STACK_VERTICAL); - UI_Label(GS(PHOTO_MODE_MOVE_PROMPT)); - UI_Label(GS(PHOTO_MODE_ROTATE_PROMPT)); - UI_Label(GS(PHOTO_MODE_ROLL_PROMPT)); - UI_Label(GS(PHOTO_MODE_ROTATE90_PROMPT)); - UI_Label(GS(PHOTO_MODE_FOV_PROMPT)); - UI_Label(GS(PHOTO_MODE_RESET_PROMPT)); - UI_Label(GS(MISC_TOGGLE_HELP)); - UI_Label(GS(PHOTO_MODE_SNAP_PROMPT)); - UI_Label(GS(MISC_EXIT)); - UI_EndStack(); - - UI_EndStack(); - - UI_EndStack(); - UI_EndPad(); - UI_EndFrame(); - UI_EndPad(); - UI_EndModal(); -} diff --git a/src/libtrx/game/ui/dialogs/play_any_level.c b/src/libtrx/game/ui/dialogs/play_any_level.c deleted file mode 100644 index d21061f49..000000000 --- a/src/libtrx/game/ui/dialogs/play_any_level.c +++ /dev/null @@ -1,87 +0,0 @@ -#include "game/ui/dialogs/play_any_level.h" - -#include "game/game_flow.h" -#include "game/game_string.h" -#include "game/ui/common.h" -#include "game/ui/dialogs/base_passport.h" -#include "game/ui/elements/anchor.h" -#include "game/ui/elements/hide.h" -#include "game/ui/elements/label.h" -#include "game/ui/elements/offset.h" -#include "game/ui/elements/requester.h" -#include "game/ui/elements/spacer.h" -#include "game/ui/elements/stack.h" -#include "memory.h" -#include "vector.h" - -typedef struct { - int32_t level_num; - const char *text; -} M_ROW; - -typedef struct UI_PLAY_ANY_LEVEL_DIALOG_STATE { - VECTOR *rows; - UI_REQUESTER_STATE req; -} UI_PLAY_ANY_LEVEL_DIALOG_STATE; - -UI_PLAY_ANY_LEVEL_DIALOG_STATE *UI_PlayAnyLevelDialog_Init(void) -{ - UI_PLAY_ANY_LEVEL_DIALOG_STATE *const s = - Memory_Alloc(sizeof(UI_PLAY_ANY_LEVEL_DIALOG_STATE)); - s->rows = Vector_Create(sizeof(M_ROW)); - - const GF_LEVEL_TABLE *const level_table = GF_GetLevelTable(GFLT_MAIN); - for (int32_t i = 0; i < level_table->count; i++) { - if (level_table->levels[i].type != GFL_GYM) { - const M_ROW row = { - .level_num = i, - .text = level_table->levels[i].title, - }; - Vector_Add(s->rows, &row); - } - } - - UI_BasePassportDialog_Init(&s->req, s->rows->count); - return s; -} - -void UI_PlayAnyLevelDialog_Free(UI_PLAY_ANY_LEVEL_DIALOG_STATE *const s) -{ - Vector_Free(s->rows); - UI_Requester_Free(&s->req); - Memory_Free(s); -} - -int32_t UI_PlayAnyLevelDialog_Control(UI_PLAY_ANY_LEVEL_DIALOG_STATE *const s) -{ - UI_BasePassportDialog_Control(&s->req); - const int32_t choice = UI_Requester_Control(&s->req); - switch (choice) { - case UI_REQUESTER_NO_CHOICE: - return UI_PLAY_ANY_LEVEL_CHOICE_NO_CHOICE; - case UI_REQUESTER_CANCEL: - return UI_PLAY_ANY_LEVEL_CHOICE_CANCEL; - default: - return ((M_ROW *)Vector_Get(s->rows, choice))->level_num; - } -} - -void UI_PlayAnyLevelDialog(UI_PLAY_ANY_LEVEL_DIALOG_STATE *const s) -{ - UI_BeginBasePassportDialog(); - UI_BeginRequester(&s->req, GS(PASSPORT_SELECT_LEVEL)); - - for (int32_t i = 0; i < s->rows->count; i++) { - if (UI_Requester_IsRowVisible(&s->req, i)) { - const M_ROW *const row = Vector_Get(s->rows, i); - UI_BeginRequesterRow(&s->req, i); - UI_BeginAnchor(0.5f, 0.5f); - UI_Label(row->text); - UI_EndAnchor(); - UI_EndRequesterRow(&s->req, i); - } - } - - UI_EndRequester(&s->req); - UI_EndBasePassportDialog(); -} diff --git a/src/libtrx/game/ui/dialogs/save_slot.c b/src/libtrx/game/ui/dialogs/save_slot.c deleted file mode 100644 index f20eb0d9e..000000000 --- a/src/libtrx/game/ui/dialogs/save_slot.c +++ /dev/null @@ -1,184 +0,0 @@ -#include "game/ui/dialogs/save_slot.h" - -#include "game/game_string.h" -#include "game/input.h" -#include "game/savegame.h" -#include "game/scaler.h" -#include "game/ui/common.h" -#include "game/ui/dialogs/base_passport.h" -#include "game/ui/elements/anchor.h" -#include "game/ui/elements/hide.h" -#include "game/ui/elements/label.h" -#include "game/ui/elements/offset.h" -#include "game/ui/elements/requester.h" -#include "game/ui/elements/spacer.h" -#include "game/ui/elements/stack.h" -#include "game/viewport.h" -#include "memory.h" -#include "utils.h" - -#include - -typedef struct UI_SAVE_SLOT_DIALOG_STATE { - UI_SAVE_SLOT_DIALOG_TYPE type; - UI_REQUESTER_STATE req; -} UI_SAVE_SLOT_DIALOG_STATE; - -static bool M_ShowDetails(const UI_SAVE_SLOT_DIALOG_STATE *s, int32_t slot_idx); -static void M_NonEmptySlot( - const UI_SAVE_SLOT_DIALOG_STATE *s, int32_t slot_idx, - const SAVEGAME_INFO *info); -static void M_EmptySlot(const UI_SAVE_SLOT_DIALOG_STATE *s, int32_t slot_idx); - -static bool M_ShowDetails( - const UI_SAVE_SLOT_DIALOG_STATE *const s, const int32_t slot_idx) -{ - if (s->type != UI_SAVE_SLOT_DIALOG_LOAD_GAME) { - return false; - } - if (!UI_Requester_IsRowSelected(&s->req, slot_idx)) { - return false; - } - if (TR_VERSION == 2) { - return false; - } - return !Savegame_IsSlotFree(slot_idx); -} - -static void M_NonEmptySlot( - const UI_SAVE_SLOT_DIALOG_STATE *const s, const int32_t slot_idx, - const SAVEGAME_INFO *const info) -{ - const bool show_details = M_ShowDetails(s, slot_idx); - - if (show_details) { - UI_BeginStackEx((UI_STACK_SETTINGS) { - .orientation = UI_STACK_HORIZONTAL, - .align = { .h = UI_STACK_H_ALIGN_DISTRIBUTE }, - }); - // Balance both sides so that the row text appears centered - UI_BeginHide(true); - UI_Label("\\{button right}"); - UI_EndHide(); - if (TR_VERSION == 1) { - UI_BeginStack(UI_STACK_HORIZONTAL); - } - } else { - if (TR_VERSION == 1) { - UI_BeginAnchor(0.5f, 0.5f); - UI_BeginStack(UI_STACK_HORIZONTAL); - } else { - UI_BeginStackEx((UI_STACK_SETTINGS) { - .orientation = UI_STACK_HORIZONTAL, - .align = { .h = UI_STACK_H_ALIGN_DISTRIBUTE }, - }); - } - } - - // Level title with the save counter - UI_Label(info->level_title); - if (info->counter > 0) { - UI_Spacer(8.0f, 0.0f); - char buf[16]; - sprintf(buf, "%d", info->counter); - UI_Label(buf); - } - - if (show_details) { - if (TR_VERSION == 1) { - UI_EndStack(); - } - UI_BeginOffset(0.0f, -1.0f); - UI_Label("\\{button right}"); - UI_EndOffset(); - UI_EndStack(); - } else { - UI_EndStack(); - if (TR_VERSION == 1) { - UI_EndAnchor(); - } - } -} - -static void M_EmptySlot( - const UI_SAVE_SLOT_DIALOG_STATE *const s, const int32_t slot_idx) -{ - char buf[16]; - sprintf(buf, GS(MISC_EMPTY_SLOT_FMT), slot_idx + 1); - UI_BeginAnchor(0.5f, 0.5f); - UI_Label(buf); - UI_EndAnchor(); -} - -UI_SAVE_SLOT_DIALOG_STATE *UI_SaveSlotDialog_Init( - const UI_SAVE_SLOT_DIALOG_TYPE type, const int32_t save_slot) -{ - UI_SAVE_SLOT_DIALOG_STATE *const s = - Memory_Alloc(sizeof(UI_SAVE_SLOT_DIALOG_STATE)); - s->type = type; - - UI_BasePassportDialog_Init(&s->req, Savegame_GetSlotCount()); - s->req.sel_row = save_slot; - CLAMP(s->req.sel_row, 0, s->req.max_rows); - return s; -} - -void UI_SaveSlotDialog_Free(UI_SAVE_SLOT_DIALOG_STATE *const s) -{ - UI_Requester_Free(&s->req); -} - -UI_SAVE_SLOT_DIALOG_CHOICE UI_SaveSlotDialog_Control( - UI_SAVE_SLOT_DIALOG_STATE *const s) -{ - UI_BasePassportDialog_Control(&s->req); - const int32_t sel_row = UI_Requester_GetCurrentRow(&s->req); - if (M_ShowDetails(s, sel_row) && g_InputDB.menu_right) { - return (UI_SAVE_SLOT_DIALOG_CHOICE) { - .action = UI_SAVE_SLOT_DIALOG_DETAILS, - .slot_num = sel_row, - }; - } - const int32_t choice = UI_Requester_Control(&s->req); - if (choice == UI_REQUESTER_CANCEL) { - return (UI_SAVE_SLOT_DIALOG_CHOICE) { - .action = UI_SAVE_SLOT_DIALOG_CANCEL, - }; - } else if ( - choice != UI_REQUESTER_NO_CHOICE - && (s->type == UI_SAVE_SLOT_DIALOG_SAVE_GAME - || !Savegame_IsSlotFree(choice))) { - return (UI_SAVE_SLOT_DIALOG_CHOICE) { - .action = UI_SAVE_SLOT_DIALOG_CONFIRM, - .slot_num = sel_row, - }; - } - return (UI_SAVE_SLOT_DIALOG_CHOICE) { - .action = UI_SAVE_SLOT_DIALOG_NO_CHOICE, - }; -} - -void UI_SaveSlotDialog(const UI_SAVE_SLOT_DIALOG_STATE *const s) -{ - UI_BeginBasePassportDialog(); - const char *const title = (s->type == UI_SAVE_SLOT_DIALOG_SAVE_GAME) - ? GS(PASSPORT_SAVE_GAME) - : GS(PASSPORT_LOAD_GAME); - UI_BeginRequester(&s->req, title); - - const int32_t first = UI_Requester_GetFirstRow(&s->req); - const int32_t last = UI_Requester_GetLastRow(&s->req); - for (int32_t i = first; i < last; ++i) { - UI_BeginRequesterRow(&s->req, i); - const SAVEGAME_INFO *const info = Savegame_GetSavegameInfo(i); - if (info != nullptr && info->level_title != nullptr) { - M_NonEmptySlot(s, i, info); - } else { - M_EmptySlot(s, i); - } - UI_EndRequesterRow(&s->req, i); - } - - UI_EndRequester(&s->req); - UI_EndBasePassportDialog(); -} diff --git a/src/libtrx/game/ui/dialogs/select_level.c b/src/libtrx/game/ui/dialogs/select_level.c deleted file mode 100644 index f2db94fc9..000000000 --- a/src/libtrx/game/ui/dialogs/select_level.c +++ /dev/null @@ -1,140 +0,0 @@ -#include "game/ui/dialogs/select_level.h" - -#include "debug.h" -#include "game/game_flow.h" -#include "game/game_string.h" -#include "game/inventory.h" -#include "game/savegame.h" -#include "game/ui/common.h" -#include "game/ui/dialogs/base_passport.h" -#include "game/ui/elements/anchor.h" -#include "game/ui/elements/hide.h" -#include "game/ui/elements/label.h" -#include "game/ui/elements/offset.h" -#include "game/ui/elements/requester.h" -#include "game/ui/elements/spacer.h" -#include "game/ui/elements/stack.h" -#include "memory.h" -#include "vector.h" - -typedef enum { - M_ROW_ROLE_PLAY_LEVEL, - M_ROW_ROLE_STORY_SO_FAR, -} M_ROW_ROLE; - -typedef struct { - const char *const text; - M_ROW_ROLE role; -} M_ROW; - -typedef struct UI_SELECT_LEVEL_DIALOG_STATE { - int32_t save_slot; - bool is_active; - VECTOR *rows; - UI_REQUESTER_STATE req; -} UI_SELECT_LEVEL_DIALOG_STATE; - -UI_SELECT_LEVEL_DIALOG_STATE *UI_SelectLevelDialog_Init(const int32_t save_slot) -{ - UI_SELECT_LEVEL_DIALOG_STATE *const s = - Memory_Alloc(sizeof(UI_SELECT_LEVEL_DIALOG_STATE)); - s->save_slot = save_slot; - s->rows = Vector_Create(sizeof(M_ROW)); - - const SAVEGAME_INFO *const info = Savegame_GetSavegameInfo(save_slot); - ASSERT(info != nullptr); - s->is_active = info->features.select_level; - - if (s->is_active) { - const GF_LEVEL_TABLE *const level_table = GF_GetLevelTable(GFLT_MAIN); - for (int32_t i = 0; i <= info->level_num && i < level_table->count; - i++) { - if (level_table->levels[i].type != GFL_GYM) { - Vector_Add( - s->rows, - &(M_ROW) { - .text = level_table->levels[i].title, - .role = M_ROW_ROLE_PLAY_LEVEL, - }); - } - } - - if (g_Inv_Mode == INV_TITLE_MODE && GF_HasAvailableStory(save_slot)) { - Vector_Add( - s->rows, - &(M_ROW) { - .text = GS(PASSPORT_STORY_SO_FAR), - .role = M_ROW_ROLE_STORY_SO_FAR, - }); - } - } - - UI_BasePassportDialog_Init(&s->req, s->rows->count); - return s; -} - -void UI_SelectLevelDialog_Free(UI_SELECT_LEVEL_DIALOG_STATE *const s) -{ - Vector_Free(s->rows); - UI_Requester_Free(&s->req); - Memory_Free(s); -} - -int32_t UI_SelectLevelDialog_Control(UI_SELECT_LEVEL_DIALOG_STATE *const s) -{ - UI_BasePassportDialog_Control(&s->req); - const int32_t choice = UI_Requester_Control(&s->req); - if (choice < 0 || !s->is_active) { - return UI_SELECT_LEVEL_CHOICE_NOOP; - } - const M_ROW *const row = Vector_Get(s->rows, choice); - if (row->role == M_ROW_ROLE_STORY_SO_FAR) { - return UI_SELECT_LEVEL_CHOICE_PLAY_STORY_SO_FAR; - } - return choice; -} - -void UI_SelectLevelDialog(UI_SELECT_LEVEL_DIALOG_STATE *const s) -{ - UI_BeginBasePassportDialog(); - UI_BeginRequester(&s->req, GS(PASSPORT_SELECT_LEVEL)); - - const SAVEGAME_INFO *info = Savegame_GetSavegameInfo(s->save_slot); - if (!s->is_active) { - UI_BeginAnchor(0.5f, 0.5f); - UI_Label(GS(PASSPORT_LEGACY_SELECT_LEVEL_1)); - UI_EndAnchor(); - UI_BeginAnchor(0.5f, 0.5f); - UI_Label(GS(PASSPORT_LEGACY_SELECT_LEVEL_2)); - UI_EndAnchor(); - } else { - for (int32_t i = 0; i < s->rows->count; i++) { - if (UI_Requester_IsRowVisible(&s->req, i)) { - const M_ROW *const row = Vector_Get(s->rows, i); - UI_BeginRequesterRow(&s->req, i); - if (UI_Requester_IsRowSelected(&s->req, i)) { - UI_BeginStackEx((UI_STACK_SETTINGS) { - .orientation = UI_STACK_HORIZONTAL, - .align = { .h = UI_STACK_H_ALIGN_DISTRIBUTE } }); - UI_BeginOffset(0.0f, -1.0f); - UI_Label("\\{button left}"); - UI_EndOffset(); - UI_Label(row->text); - // balance both sides so that the row text appears centered - UI_BeginHide(true); - UI_Label("\\{button left}"); - UI_EndHide(); - UI_EndStack(); - } else { - UI_BeginAnchor(0.5f, 0.5f); - UI_Label(row->text); - UI_EndAnchor(); - } - UI_EndRequesterRow(&s->req, i); - } - } - } - - UI_EndRequester(&s->req); - UI_EndBasePassportDialog(); -} diff --git a/src/libtrx/game/ui/dialogs/sound_settings.c b/src/libtrx/game/ui/dialogs/sound_settings.c deleted file mode 100644 index b5f382308..000000000 --- a/src/libtrx/game/ui/dialogs/sound_settings.c +++ /dev/null @@ -1,176 +0,0 @@ -#include "game/ui/dialogs/sound_settings.h" - -#include "config.h" -#include "game/game_string.h" -#include "game/input.h" -#include "game/music.h" -#include "game/sound.h" -#include "game/ui/elements/anchor.h" -#include "game/ui/elements/hide.h" -#include "game/ui/elements/label.h" -#include "game/ui/elements/modal.h" -#include "game/ui/elements/requester.h" -#include "game/ui/elements/resize.h" -#include "game/ui/elements/spacer.h" -#include "game/ui/elements/stack.h" -#include "memory.h" -#include "strings.h" -#include "utils.h" - -typedef struct UI_SOUND_SETTINGS_STATE { - UI_REQUESTER_STATE req; -} UI_SOUND_SETTINGS_STATE; - -typedef enum { - M_ROW_MUSIC = 0, - M_ROW_SOUND = 1, - M_ROW_COUNT = 2, -} M_ROW; - -static const GAME_STRING_ID m_Labels[M_ROW_COUNT] = { - GS_ID(SOUND_DIALOG_SOUND), - GS_ID(SOUND_DIALOG_MUSIC), -}; - -static char *M_FormatRowValue(int32_t row); -static bool M_CanChange(int32_t row, int32_t dir); -static bool M_RequestChange(int32_t row, int32_t dir); - -static char *M_FormatRowValue(const int32_t row) -{ - switch (row) { - case M_ROW_MUSIC: - return String_Format("%2d", g_Config.audio.music_volume); - case M_ROW_SOUND: - return String_Format("%2d", g_Config.audio.sound_volume); - default: - return nullptr; - } -} - -static bool M_CanChange(const int32_t row, const int32_t dir) -{ - switch (row) { - case M_ROW_MUSIC: - if (dir < 0) { - return g_Config.audio.music_volume > Music_GetMinVolume(); - } else if (dir > 0) { - return g_Config.audio.music_volume < Music_GetMaxVolume(); - } - break; - case M_ROW_SOUND: - if (dir < 0) { - return g_Config.audio.sound_volume > Sound_GetMinVolume(); - } else if (dir > 0) { - return g_Config.audio.sound_volume < Sound_GetMaxVolume(); - } - break; - } - return false; -} - -static bool M_RequestChange(const int32_t row, const int32_t dir) -{ - if (!M_CanChange(row, dir)) { - return false; - } - switch (row) { - case M_ROW_MUSIC: - g_Config.audio.music_volume += dir; - Music_SetVolume(g_Config.audio.music_volume); - break; - case M_ROW_SOUND: - g_Config.audio.sound_volume += dir; - Sound_SetMasterVolume(g_Config.audio.sound_volume); - break; - default: - return false; - } - Config_Write(); - Sound_Effect(SFX_MENU_PASSPORT, nullptr, SPM_ALWAYS); - return true; -} - -UI_SOUND_SETTINGS_STATE *UI_SoundSettings_Init(void) -{ - UI_SOUND_SETTINGS_STATE *s = Memory_Alloc(sizeof(UI_SOUND_SETTINGS_STATE)); - UI_Requester_Init(&s->req, M_ROW_COUNT, M_ROW_COUNT, true); - s->req.row_pad = 2.0f; - return s; -} - -void UI_SoundSettings_Free(UI_SOUND_SETTINGS_STATE *const s) -{ - UI_Requester_Free(&s->req); - Memory_Free(s); -} - -bool UI_SoundSettings_Control(UI_SOUND_SETTINGS_STATE *const s) -{ - const int32_t choice = UI_Requester_Control(&s->req); - if (choice == UI_REQUESTER_CANCEL) { - return true; - } - const int32_t sel = UI_Requester_GetCurrentRow(&s->req); - if (g_InputDB.menu_left && sel >= 0) { - M_RequestChange(sel, -1); - } else if (g_InputDB.menu_right && sel >= 0) { - M_RequestChange(sel, +1); - } - return false; -} - -void UI_SoundSettings(UI_SOUND_SETTINGS_STATE *const s) -{ - const int32_t sel = UI_Requester_GetCurrentRow(&s->req); - UI_BeginModal(0.5f, 0.6f); - UI_BeginRequester(&s->req, GS(SOUND_DIALOG_TITLE)); - - // Measure the maximum width of the value label to prevent the entire - // dialog from changing its size as the player changes the sound levels. - float value_w = -1.0f; - UI_Label_Measure("10", &value_w, nullptr); - - for (int32_t i = 0; i < s->req.max_rows; ++i) { - if (!UI_Requester_IsRowVisible(&s->req, i)) { - UI_BeginResize(-1.0f, 0.0f); - } else { - UI_BeginResize(-1.0f, -1.0f); - } - UI_BeginRequesterRow(&s->req, i); - - UI_BeginStackEx((UI_STACK_SETTINGS) { - .orientation = UI_STACK_HORIZONTAL, - .align = { .h = UI_STACK_H_ALIGN_DISTRIBUTE }, - }); - UI_Label(GameString_Get(m_Labels[i])); - UI_Spacer(20.0f, 0.0f); - - UI_BeginStackEx((UI_STACK_SETTINGS) { - .orientation = UI_STACK_HORIZONTAL, - .align = { .h = UI_STACK_H_ALIGN_DISTRIBUTE }, - .spacing = { .h = 5.0f }, - }); - UI_BeginHide(i != sel || !M_CanChange(i, -1)); - UI_Label("\\{button left}"); - UI_EndHide(); - - UI_BeginResize(value_w, -1.0f); - UI_BeginAnchor(0.5f, 0.5f); - UI_Label(M_FormatRowValue(i)); - UI_EndAnchor(); - UI_EndResize(); - - UI_BeginHide(i != sel || !M_CanChange(i, +1)); - UI_Label("\\{button right}"); - UI_EndHide(); - UI_EndStack(); - UI_EndStack(); - - UI_EndRequesterRow(&s->req, i); - UI_EndResize(); - } - - UI_EndRequester(&s->req); - UI_EndModal(); -} diff --git a/src/libtrx/game/ui/dialogs/stats.c b/src/libtrx/game/ui/dialogs/stats.c deleted file mode 100644 index 84982c734..000000000 --- a/src/libtrx/game/ui/dialogs/stats.c +++ /dev/null @@ -1,28 +0,0 @@ -#include "game/ui/dialogs/stats.h" - -#include "game/gym.h" - -void UI_StatsDialog_Init( - UI_STATS_DIALOG_STATE *const s, const UI_STATS_DIALOG_ARGS args) -{ - const ASSAULT_STATS stats = Gym_GetAssaultStats(); - int32_t max_assault_times = 0; - for (int i = 0; i < MAX_ASSAULT_TIMES; i++) { - if (stats.entries[i].time != 0) { - max_assault_times++; - } - } - UI_Requester_Init(&s->assault_req, 7, max_assault_times, false); - s->assault_req.reserve_space = true; - s->args = args; -} - -void UI_StatsDialog_Free(UI_STATS_DIALOG_STATE *const s) -{ - UI_Requester_Free(&s->assault_req); -} - -int32_t UI_StatsDialog_Control(UI_STATS_DIALOG_STATE *const s) -{ - return UI_Requester_Control(&s->assault_req); -} diff --git a/src/libtrx/game/ui/elements/anchor.c b/src/libtrx/game/ui/elements/anchor.c deleted file mode 100644 index 57170dab6..000000000 --- a/src/libtrx/game/ui/elements/anchor.c +++ /dev/null @@ -1,56 +0,0 @@ -#include "game/ui/elements/anchor.h" - -#include "game/text.h" -#include "game/ui/helpers.h" - -typedef struct { - float x; - float y; -} M_DATA; - -static void M_Measure(UI_NODE *node); -static void M_Layout(UI_NODE *node, float x, float y, float w, float h); - -static const UI_WIDGET_OPS m_Ops = { - .measure = UI_MeasureWrapper, - .layout = M_Layout, - .draw = UI_DrawWrapper, -}; - -static void M_Measure(UI_NODE *const node) -{ - node->measure_w = UI_GetCanvasWidth(); - node->measure_h = UI_GetCanvasHeight() - TEXT_HEIGHT_FIXED; -} - -static void M_Layout( - UI_NODE *const node, const float x, const float y, const float w, - const float h) -{ - UI_LayoutBasic(node, x, y, w, h); - const M_DATA *const data = node->data; - UI_NODE *child = node->first_child; - while (child != nullptr) { - const float cw = child->measure_w; - const float ch = child->measure_h; - const float cx = x + (w - cw) * data->x; - const float cy = y + (h - ch) * data->y; - child->ops->layout(child, cx, cy, cw, ch); - child = child->next_sibling; - } -} - -void UI_BeginAnchor(const float x, const float y) -{ - UI_NODE *const node = UI_AllocNode(&m_Ops, sizeof(M_DATA)); - M_DATA *const data = node->data; - data->x = x; - data->y = y; - UI_AddChild(node); - UI_PushCurrent(node); -} - -void UI_EndAnchor(void) -{ - UI_PopCurrent(); -} diff --git a/src/libtrx/game/ui/elements/fade.c b/src/libtrx/game/ui/elements/fade.c deleted file mode 100644 index 3e04dd5c6..000000000 --- a/src/libtrx/game/ui/elements/fade.c +++ /dev/null @@ -1,45 +0,0 @@ -#include "game/ui/elements/fade.h" - -#include "game/output.h" -#include "game/ui/helpers.h" - -typedef struct { - FADER *fader; - bool is_on_top; -} M_DATA; - -static void M_Draw(const UI_NODE *node); - -static const UI_WIDGET_OPS m_Ops = { - .measure = UI_MeasureWrapper, - .layout = UI_LayoutWrapper, - .draw = M_Draw, -}; - -static void M_Draw(const UI_NODE *const node) -{ - const M_DATA *const data = node->data; - if (data->is_on_top) { - UI_DrawWrapper(node); - Output_DrawPolyList(); // flush geometry - Fader_Draw(data->fader); - } else { - Fader_Draw(data->fader); - UI_DrawWrapper(node); - } -} - -void UI_BeginFade(FADER *const fader, bool is_on_top) -{ - UI_NODE *const node = UI_AllocNode(&m_Ops, sizeof(M_DATA)); - M_DATA *const data = node->data; - data->fader = fader; - data->is_on_top = is_on_top; - UI_AddChild(node); - UI_PushCurrent(node); -} - -void UI_EndFade(void) -{ - UI_PopCurrent(); -} diff --git a/src/libtrx/game/ui/elements/fixed.c b/src/libtrx/game/ui/elements/fixed.c deleted file mode 100644 index d20208771..000000000 --- a/src/libtrx/game/ui/elements/fixed.c +++ /dev/null @@ -1,67 +0,0 @@ -#include "game/ui/elements/fixed.h" - -#include "game/ui/helpers.h" -#include "utils.h" - -typedef struct { - float x; - float y; -} M_DATA; - -static void M_Measure(UI_NODE *node); -static void M_Layout(UI_NODE *node, float x, float y, float w, float h); -static void M_Draw(const UI_NODE *node); - -static const UI_WIDGET_OPS m_Ops = { - .measure = M_Measure, - .layout = M_Layout, - .draw = M_Draw, -}; - -static void M_Measure(UI_NODE *const node) -{ - node->measure_w = 0.0f; - node->measure_h = 0.0f; -} - -static void M_Layout(UI_NODE *const node, float x, float y, float w, float h) -{ - const M_DATA *const data = node->data; - w = 0; - h = 0; - const UI_NODE *child = node->first_child; - while (child != nullptr) { - w = MAX(w, child->measure_w); - h = MAX(h, child->measure_h); - child = child->next_sibling; - } - x -= w * data->x; - y -= h * data->y; - UI_LayoutWrapper(node, x, y, w, h); -} - -static void M_Draw(const UI_NODE *const node) -{ - const UI_NODE *child = node->first_child; - while (child != nullptr) { - if (child->ops->draw != nullptr) { - child->ops->draw(child); - } - child = child->next_sibling; - } -} - -void UI_BeginFixed(const float x, const float y) -{ - UI_NODE *const node = UI_AllocNode(&m_Ops, sizeof(M_DATA)); - M_DATA *const data = node->data; - data->x = x; - data->y = y; - UI_AddChild(node); - UI_PushCurrent(node); -} - -void UI_EndFixed(void) -{ - UI_PopCurrent(); -} diff --git a/src/libtrx/game/ui/elements/flash.c b/src/libtrx/game/ui/elements/flash.c deleted file mode 100644 index e4b52d92a..000000000 --- a/src/libtrx/game/ui/elements/flash.c +++ /dev/null @@ -1,43 +0,0 @@ -#include "game/ui/elements/flash.h" - -#include "game/ui/elements/hide.h" -#include "game/ui/helpers.h" - -static const UI_WIDGET_OPS m_Ops = { - .measure = UI_MeasureWrapper, - .layout = UI_LayoutWrapper, - .draw = UI_DrawWrapper, -}; - -void UI_Flash_Init(UI_FLASH_STATE *const s, const int32_t rate) -{ - s->count = 0; - s->rate = rate; -} - -void UI_Flash_Free(UI_FLASH_STATE *const s) -{ -} - -void UI_Flash_Control(UI_FLASH_STATE *const s) -{ - s->count -= 2; - if (s->count < -s->rate) { - s->count = s->rate; - } -} - -void UI_BeginFlash(UI_FLASH_STATE *const s) -{ - UI_NODE *const node = UI_AllocNode(&m_Ops, sizeof(UI_FLASH_STATE *)); - *(UI_FLASH_STATE **)node->data = s; - UI_AddChild(node); - UI_PushCurrent(node); - UI_BeginHide(s->count >= 0); -} - -void UI_EndFlash(void) -{ - UI_EndHide(); - UI_PopCurrent(); -} diff --git a/src/libtrx/game/ui/elements/frame.c b/src/libtrx/game/ui/elements/frame.c deleted file mode 100644 index 2b78d163f..000000000 --- a/src/libtrx/game/ui/elements/frame.c +++ /dev/null @@ -1,81 +0,0 @@ -#include "game/ui/elements/frame.h" - -#include "config.h" -#include "game/output.h" -#include "game/text.h" -#include "game/ui/helpers.h" - -typedef struct { - UI_STYLE ui_style; - TEXT_STYLE text_style; - int32_t outline_z; - int32_t background_z; -} M_DATA; - -static void M_Draw(const UI_NODE *node); - -static const UI_WIDGET_OPS m_Ops = { - .measure = UI_MeasureWrapper, - .layout = UI_LayoutWrapper, - .draw = M_Draw, -}; - -static void M_Draw(const UI_NODE *node) -{ - const M_DATA *const data = node->data; - if (data->background_z >= 0) { - Output_DrawTextBackground( - data->ui_style, UI_ScaleX(node->x), UI_ScaleY(node->y), - UI_ScaleX(node->w), UI_ScaleY(node->h), data->background_z, - data->text_style); - } - if (data->outline_z >= 0) { - Output_DrawTextOutline( - data->ui_style, UI_ScaleX(node->x), UI_ScaleY(node->y), - UI_ScaleX(node->w), UI_ScaleY(node->h), data->outline_z, - data->text_style); - } - UI_DrawWrapper(node); -} - -void UI_BeginFrame(UI_FRAME_STYLE style) -{ - UI_NODE *const node = UI_AllocNode(&m_Ops, sizeof(M_DATA)); - M_DATA *const data = node->data; - - data->ui_style = UI_STYLE_PC; -#if TR_VERSION == 1 - data->ui_style = g_Config.ui.menu_style; -#endif - - switch (style) { - case UI_FRAME_DIALOG_BACKGROUND: - data->outline_z = 160; - data->background_z = 160; - data->text_style = TS_BACKGROUND; - break; - case UI_FRAME_DIALOG_HEADING: - data->outline_z = 80; - data->background_z = 80; - data->text_style = TS_HEADING; - break; - case UI_FRAME_SELECTED_OPTION: - data->outline_z = 80; - data->background_z = 80; - data->text_style = TS_REQUESTED; - break; - case UI_FRAME_OUTLINE_ONLY: - data->outline_z = 80; - data->background_z = -1; - data->text_style = TS_REQUESTED; - break; - } - - UI_AddChild(node); - UI_PushCurrent(node); -} - -void UI_EndFrame(void) -{ - UI_PopCurrent(); -} diff --git a/src/libtrx/game/ui/elements/hide.c b/src/libtrx/game/ui/elements/hide.c deleted file mode 100644 index 2e03be1cc..000000000 --- a/src/libtrx/game/ui/elements/hide.c +++ /dev/null @@ -1,32 +0,0 @@ -#include "game/ui/elements/hide.h" - -#include "game/ui/helpers.h" - -static void M_Draw(const UI_NODE *node); - -static const UI_WIDGET_OPS m_Ops = { - .measure = UI_MeasureWrapper, - .layout = UI_LayoutWrapper, - .draw = M_Draw, -}; - -static void M_Draw(const UI_NODE *const node) -{ - const bool draw_children = *(bool *)node->data; - if (draw_children) { - UI_DrawWrapper(node); - } -} - -void UI_BeginHide(const bool hide_children) -{ - UI_NODE *const node = UI_AllocNode(&m_Ops, sizeof(bool)); - *(bool *)node->data = !hide_children; - UI_AddChild(node); - UI_PushCurrent(node); -} - -void UI_EndHide(void) -{ - UI_PopCurrent(); -} diff --git a/src/libtrx/game/ui/elements/label.c b/src/libtrx/game/ui/elements/label.c deleted file mode 100644 index edf01ff24..000000000 --- a/src/libtrx/game/ui/elements/label.c +++ /dev/null @@ -1,100 +0,0 @@ -#include "game/ui/elements/label.h" - -#include "config.h" -#include "game/text.h" -#include "game/ui/helpers.h" - -#include - -typedef struct { - UI_LABEL_SETTINGS settings; - char *text; -} M_DATA; - -static void M_Measure(UI_NODE *node); -static void M_Draw(const UI_NODE *node); - -static UI_LABEL_SETTINGS m_DefaultSettings = { .scale = 1.0f }; -static const UI_WIDGET_OPS m_Ops = { - .measure = M_Measure, - .layout = UI_LayoutBasic, - .draw = M_Draw, -}; - -static TEXTSTRING *M_CreateText( - const float x, const float y, const char *text, - const UI_LABEL_SETTINGS settings); - -static TEXTSTRING *M_CreateText( - const float x, const float y, const char *text, - const UI_LABEL_SETTINGS settings) -{ - TEXTSTRING *const textstring = Text_Create(x, y, text); - Text_SetPos( - textstring, x / g_Config.ui.text_scale, - y / g_Config.ui.text_scale + TEXT_HEIGHT_FIXED - 1); - Text_SetMultiline(textstring, true); - Text_SetScale( - textstring, settings.scale * TEXT_BASE_SCALE, - settings.scale * TEXT_BASE_SCALE); - return textstring; -} - -static void M_Measure(UI_NODE *const node) -{ - M_DATA *const data = node->data; - UI_Label_MeasureEx( - data->text, &node->measure_w, &node->measure_h, data->settings); -} - -static void M_Draw(const UI_NODE *const node) -{ - M_DATA *const data = node->data; - TEXTSTRING *const textstring = - M_CreateText(node->x, node->y, data->text, data->settings); - if (data->settings.z != 0) { - textstring->pos.z = data->settings.z; - } - Text_DrawText(textstring); - Text_Remove(textstring); - UI_DrawWrapper(node); -} - -void UI_Label(const char *const text) -{ - UI_LabelEx(text, m_DefaultSettings); -} - -void UI_LabelEx(const char *text, const UI_LABEL_SETTINGS settings) -{ - if (text == nullptr) { - text = "(null)"; // quality of life for UI development - } - UI_NODE *const node = - UI_AllocNode(&m_Ops, sizeof(M_DATA) + strlen(text) + 1); - M_DATA *const data = node->data; - data->settings = settings; - data->text = (char *)node->data + sizeof(M_DATA); - strcpy(data->text, text); - UI_AddChild(node); -} - -void UI_Label_Measure( - const char *const text, float *const out_w, float *const out_h) -{ - UI_Label_MeasureEx(text, out_w, out_h, m_DefaultSettings); -} - -void UI_Label_MeasureEx( - const char *const text, float *const out_w, float *const out_h, - const UI_LABEL_SETTINGS settings) -{ - TEXTSTRING *const textstring = M_CreateText(0, 0, text, settings); - if (out_w != nullptr) { - *out_w = Text_GetWidth(textstring) * g_Config.ui.text_scale; - } - if (out_h != nullptr) { - *out_h = Text_GetHeight(textstring) * g_Config.ui.text_scale; - } - Text_Remove(textstring); -} diff --git a/src/libtrx/game/ui/elements/modal.c b/src/libtrx/game/ui/elements/modal.c deleted file mode 100644 index f74030b87..000000000 --- a/src/libtrx/game/ui/elements/modal.c +++ /dev/null @@ -1,33 +0,0 @@ -#include "game/ui/elements/modal.h" - -#include "game/text.h" -#include "game/ui/elements/anchor.h" -#include "game/ui/helpers.h" - -static void M_Measure(UI_NODE *node); - -static const UI_WIDGET_OPS m_Ops = { - .measure = M_Measure, - .layout = UI_LayoutWrapper, - .draw = UI_DrawWrapper, -}; - -static void M_Measure(UI_NODE *const node) -{ - node->measure_w = UI_GetCanvasWidth(); - node->measure_h = UI_GetCanvasHeight(); -} - -void UI_BeginModal(const float x, const float y) -{ - UI_NODE *const node = UI_AllocNode(&m_Ops, 0); - UI_AddChild(node); - UI_PushCurrent(node); - UI_BeginAnchor(x, y); -} - -void UI_EndModal(void) -{ - UI_EndAnchor(); - UI_PopCurrent(); -} diff --git a/src/libtrx/game/ui/elements/offset.c b/src/libtrx/game/ui/elements/offset.c deleted file mode 100644 index 80275e1be..000000000 --- a/src/libtrx/game/ui/elements/offset.c +++ /dev/null @@ -1,13 +0,0 @@ -#include "game/ui/elements/offset.h" - -#include "game/ui/elements/pad.h" - -void UI_BeginOffset(const float x, const float y) -{ - UI_BeginPadEx(x, -x, y, -y); -} - -void UI_EndOffset(void) -{ - UI_EndPad(); -} diff --git a/src/libtrx/game/ui/elements/pad.c b/src/libtrx/game/ui/elements/pad.c deleted file mode 100644 index eec083e22..000000000 --- a/src/libtrx/game/ui/elements/pad.c +++ /dev/null @@ -1,65 +0,0 @@ -#include "game/ui/elements/pad.h" - -#include "config.h" -#include "game/ui/helpers.h" - -typedef struct { - float t; - float r; - float d; - float l; -} M_DATA; - -static void M_Measure(UI_NODE *node); -static void M_Layout(UI_NODE *node, float x, float y, float w, float h); - -static const UI_WIDGET_OPS m_Ops = { - .measure = M_Measure, - .layout = M_Layout, - .draw = UI_DrawWrapper, -}; - -static void M_Measure(UI_NODE *const node) -{ - UI_MeasureWrapper(node); - const M_DATA *const data = node->data; - node->measure_w += data->l + data->r; - node->measure_h += data->t + data->d; -} - -static void M_Layout( - UI_NODE *const node, const float x, const float y, const float w, - const float h) -{ - UI_LayoutBasic(node, x, y, w, h); - const M_DATA *const data = node->data; - UI_NODE *child = node->first_child; - while (child != nullptr) { - child->ops->layout( - child, x + data->l, y + data->t, w - data->l - data->r, - h - data->t - data->d); - child = child->next_sibling; - } -} - -void UI_BeginPad(const float x, const float y) -{ - UI_BeginPadEx(x, x, y, y); -} - -void UI_BeginPadEx(const float l, const float r, const float t, const float d) -{ - UI_NODE *const node = UI_AllocNode(&m_Ops, sizeof(M_DATA)); - M_DATA *const data = node->data; - data->t = t * g_Config.ui.text_scale; - data->r = r * g_Config.ui.text_scale; - data->d = d * g_Config.ui.text_scale; - data->l = l * g_Config.ui.text_scale; - UI_AddChild(node); - UI_PushCurrent(node); -} - -void UI_EndPad(void) -{ - UI_PopCurrent(); -} diff --git a/src/libtrx/game/ui/elements/prompt.c b/src/libtrx/game/ui/elements/prompt.c deleted file mode 100644 index 6a79cee6a..000000000 --- a/src/libtrx/game/ui/elements/prompt.c +++ /dev/null @@ -1,253 +0,0 @@ -#include "game/ui/elements/prompt.h" - -#include "game/const.h" -#include "game/input.h" -#include "game/ui/common.h" -#include "game/ui/elements/flash.h" -#include "game/ui/elements/label.h" -#include "game/ui/events.h" -#include "game/ui/helpers.h" -#include "log.h" -#include "memory.h" -#include "strings.h" -#include "utils.h" - -#include - -typedef struct { - UI_PROMPT_STATE *state; -} M_DATA; - -static const char m_ValidPromptChars[] = - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-: "; - -static void M_Layout(UI_NODE *node, float x, float y, float w, float h); - -static const UI_WIDGET_OPS m_Ops = { - .measure = UI_MeasureWrapper, - .layout = M_Layout, - .draw = UI_DrawWrapper, -}; - -static void M_MoveCaretLeft(UI_PROMPT_STATE *s); -static void M_MoveCaretRight(UI_PROMPT_STATE *s); -static void M_MoveCaretStart(UI_PROMPT_STATE *s); -static void M_MoveCaretEnd(UI_PROMPT_STATE *s); -static void M_DeleteCharBack(UI_PROMPT_STATE *s); -static void M_Confirm(UI_PROMPT_STATE *s); -static void M_Cancel(UI_PROMPT_STATE *s); -static void M_Clear(UI_PROMPT_STATE *s); - -static void M_HandleKeyDown(const EVENT *event, void *user_data); -static void M_HandleTextEdit(const EVENT *event, void *user_data); - -static void M_Layout( - UI_NODE *const node, const float x, const float y, const float w, - const float h) -{ - UI_LayoutBasic(node, x, y, w, h); - const M_DATA *const data = node->data; - const UI_PROMPT_STATE *const s = data->state; - UI_NODE *const prompt = node->first_child; - UI_NODE *const caret = prompt->next_sibling; - prompt->ops->layout(prompt, x, y, w, h); - - const char old = s->current_text[s->caret_pos]; - s->current_text[s->caret_pos] = '\0'; - float caret_pos; - UI_Label_Measure(s->current_text, &caret_pos, nullptr); - s->current_text[s->caret_pos] = old; - - caret->ops->layout(caret, x + caret_pos, y, w, h); -} - -static void M_MoveCaretLeft(UI_PROMPT_STATE *const s) -{ - if (s->caret_pos > 0) { - s->caret_pos--; - } -} - -static void M_MoveCaretRight(UI_PROMPT_STATE *const s) -{ - if (s->caret_pos < (int32_t)strlen(s->current_text)) { - s->caret_pos++; - } -} - -static void M_MoveCaretStart(UI_PROMPT_STATE *const s) -{ - s->caret_pos = 0; -} - -static void M_MoveCaretEnd(UI_PROMPT_STATE *const s) -{ - s->caret_pos = strlen(s->current_text); -} - -static void M_DeleteCharBack(UI_PROMPT_STATE *const s) -{ - if (s->caret_pos <= 0) { - return; - } - - memmove( - s->current_text + s->caret_pos - 1, s->current_text + s->caret_pos, - strlen(s->current_text) + 1 - s->caret_pos); - s->caret_pos--; -} - -static void M_Confirm(UI_PROMPT_STATE *const s) -{ - if (String_IsEmpty(s->current_text)) { - M_Cancel(s); - return; - } - UI_FireEvent((EVENT) { - .name = "confirm", - .sender = s, - .data = s->current_text, - }); - M_Clear(s); -} - -static void M_Cancel(UI_PROMPT_STATE *const s) -{ - UI_FireEvent((EVENT) { - .name = "cancel", - .sender = s, - .data = s->current_text, - }); - M_Clear(s); -} - -static void M_Clear(UI_PROMPT_STATE *const s) -{ - strcpy(s->current_text, ""); - s->caret_pos = 0; -} - -static void M_HandleKeyDown(const EVENT *const event, void *const user_data) -{ - const UI_INPUT key = (UI_INPUT)(uintptr_t)event->data; - UI_PROMPT_STATE *const s = user_data; - - if (!s->is_focused) { - return; - } - - // clang-format off - switch (key) { - case UI_KEY_LEFT: M_MoveCaretLeft(s); break; - case UI_KEY_RIGHT: M_MoveCaretRight(s); break; - case UI_KEY_HOME: M_MoveCaretStart(s); break; - case UI_KEY_END: M_MoveCaretEnd(s); break; - case UI_KEY_BACK: M_DeleteCharBack(s); break; - case UI_KEY_RETURN: M_Confirm(s); break; - case UI_KEY_ESCAPE: M_Cancel(s); break; - default: break; - } - // clang-format on -} - -static void M_HandleTextEdit(const EVENT *const event, void *const user_data) -{ - const char *insert_string = event->data; - const size_t insert_length = strlen(insert_string); - UI_PROMPT_STATE *const s = user_data; - - if (!s->is_focused) { - return; - } - - if (strlen(insert_string) != 1 - || strstr(m_ValidPromptChars, insert_string) == nullptr) { - return; - } - - const size_t available_space = - s->current_text_capacity - strlen(s->current_text); - if (insert_length >= available_space) { - s->current_text_capacity *= 2; - s->current_text = - Memory_Realloc(s->current_text, s->current_text_capacity); - } - - memmove( - s->current_text + s->caret_pos + insert_length, - s->current_text + s->caret_pos, - strlen(s->current_text) + 1 - s->caret_pos); - memcpy(s->current_text + s->caret_pos, insert_string, insert_length); - - s->caret_pos += insert_length; -} - -void UI_Prompt_Init(UI_PROMPT_STATE *const s) -{ - s->is_focused = false; - s->current_text_capacity = 30; - s->current_text = Memory_Alloc(s->current_text_capacity); - s->listener1 = UI_Subscribe("key_down", nullptr, M_HandleKeyDown, s); - s->listener2 = UI_Subscribe("text_edit", nullptr, M_HandleTextEdit, s); - UI_Flash_Init(&s->flash, LOGIC_FPS * 2 / 3); -} - -void UI_Prompt_Free(UI_PROMPT_STATE *const s) -{ - UI_Unsubscribe(s->listener1); - UI_Unsubscribe(s->listener2); - UI_Flash_Free(&s->flash); - Memory_FreePointer(&s->current_text); -} - -void UI_Prompt_Control(UI_PROMPT_STATE *const s) -{ - UI_Flash_Control(&s->flash); -} - -void UI_Prompt(UI_PROMPT_STATE *const s) -{ - UI_NODE *const node = UI_AllocNode(&m_Ops, sizeof(M_DATA)); - M_DATA *const data = node->data; - data->state = s; - UI_AddChild(node); - UI_PushCurrent(node); - UI_LabelEx( - s->current_text != nullptr ? s->current_text : "", - (UI_LABEL_SETTINGS) { .scale = 1.0f, .z = 16 }); - if (s->is_focused) { - UI_BeginFlash(&s->flash); - } - UI_LabelEx( - "\\{button left}", (UI_LABEL_SETTINGS) { .scale = 1.0f, .z = 8 }); - if (s->is_focused) { - UI_EndFlash(); - } - UI_PopCurrent(); -} - -void UI_Prompt_SetFocus(UI_PROMPT_STATE *const s, const bool is_focused) -{ - if (s->is_focused == is_focused) { - return; - } - s->is_focused = is_focused; - s->flash.count = 0; - if (is_focused) { - Input_EnterListenMode(); - } else { - Input_ExitListenMode(); - } -} - -void UI_Prompt_Clear(UI_PROMPT_STATE *const s) -{ - M_Clear(s); -} - -void UI_Prompt_ChangeText(UI_PROMPT_STATE *const s, const char *const new_text) -{ - Memory_FreePointer(&s->current_text); - s->current_text = Memory_DupStr(new_text); - s->caret_pos = strlen(new_text); -} diff --git a/src/libtrx/game/ui/elements/requester.c b/src/libtrx/game/ui/elements/requester.c deleted file mode 100644 index 20103043b..000000000 --- a/src/libtrx/game/ui/elements/requester.c +++ /dev/null @@ -1,216 +0,0 @@ -#include "game/ui/elements/requester.h" - -#include "config.h" -#include "game/input.h" -#include "game/text.h" -#include "game/ui/elements/anchor.h" -#include "game/ui/elements/fixed.h" -#include "game/ui/elements/frame.h" -#include "game/ui/elements/hide.h" -#include "game/ui/elements/label.h" -#include "game/ui/elements/offset.h" -#include "game/ui/elements/pad.h" -#include "game/ui/elements/resize.h" -#include "game/ui/elements/spacer.h" -#include "game/ui/elements/stack.h" -#include "game/ui/elements/window.h" -#include "utils.h" - -static void M_UpArrow(const UI_REQUESTER_STATE *s); -static void M_DownArrow(const UI_REQUESTER_STATE *s); - -static void M_UpArrow(const UI_REQUESTER_STATE *const s) -{ - UI_BeginHide(s->vis_row == 0); - UI_Spacer(0.0f, TR_VERSION == 2 ? 6.0f : 4.0f); - UI_BeginAnchor(0.5f, 0.5f); - UI_BeginFixed(0.5f, TR_VERSION == 2 ? 1.25f : 1.5f); - UI_LabelEx("\\{arrow up}", (UI_LABEL_SETTINGS) { .scale = 0.7 }); - UI_EndFixed(); - UI_EndAnchor(); - UI_EndHide(); -} - -static void M_DownArrow(const UI_REQUESTER_STATE *const s) -{ - UI_BeginHide(s->vis_row + s->vis_rows >= s->max_rows); - UI_BeginAnchor(0.5f, 0.5f); - UI_BeginFixed(0.5f, 0.0f); - UI_LabelEx("\\{arrow down}", (UI_LABEL_SETTINGS) { .scale = 0.7 }); - UI_EndFixed(); - UI_EndAnchor(); - UI_EndHide(); - UI_Spacer(0.0f, TR_VERSION == 2 ? 6.0f : 4.0f); -} - -void UI_Requester_Init( - UI_REQUESTER_STATE *const s, const int32_t vis_rows, const int32_t max_rows, - const bool is_selectable) -{ - s->vis_row = 0; - s->sel_row = 0; - s->vis_rows = vis_rows; - s->max_rows = max_rows; - s->is_selectable = is_selectable; - s->row_pad = 20.0f; - s->row_spacing = 3.0f; - s->show_arrows = false; - s->reserve_space = false; -} - -void UI_Requester_Free(UI_REQUESTER_STATE *const s) -{ -} - -int32_t UI_Requester_Control(UI_REQUESTER_STATE *const s) -{ - if (s->is_selectable) { - if (g_InputDB.menu_down) { - if (s->sel_row + 1 < s->max_rows) { - s->sel_row++; - } else if (g_Config.ui.enable_wraparound) { - s->sel_row = 0; - } - } else if (g_InputDB.menu_up) { - if (s->sel_row > 0) { - s->sel_row--; - } else if (g_Config.ui.enable_wraparound) { - s->sel_row = s->max_rows - 1; - } - } - CLAMP(s->vis_row, s->sel_row - s->vis_rows + 1, s->sel_row); - } else { - if (g_InputDB.menu_down) { - if (s->vis_row + 1 <= s->max_rows - s->vis_rows) { - s->vis_row++; - } else if (g_Config.ui.enable_wraparound) { - s->vis_row = 0; - } - } else if (g_InputDB.menu_up) { - if (s->vis_row > 0) { - s->vis_row--; - } else if (g_Config.ui.enable_wraparound) { - s->vis_row = s->max_rows - 1; - } - } - } - - CLAMPG(s->vis_row, s->max_rows - s->vis_rows); - CLAMPL(s->vis_row, 0); - - if (s->is_selectable) { - if (g_InputDB.menu_back) { - return UI_REQUESTER_CANCEL; - } - if (g_InputDB.menu_confirm) { - return s->sel_row; - } - } - return UI_REQUESTER_NO_CHOICE; -} - -void UI_Requester_SetMaxRows(UI_REQUESTER_STATE *const s, const size_t max_rows) -{ - s->max_rows = max_rows; -} - -void UI_Requester_SetVisibleRows( - UI_REQUESTER_STATE *const s, const size_t visible_rows) -{ - s->vis_rows = visible_rows; - CLAMPL(s->vis_rows, 0); - if (s->sel_row != -1) { - CLAMP(s->vis_row, s->sel_row - s->vis_rows + 1, s->sel_row); - } - CLAMP(s->vis_row, 0, s->max_rows - s->vis_rows); -} - -int32_t UI_Requester_GetFirstRow(const UI_REQUESTER_STATE *const s) -{ - return s->vis_row; -} - -int32_t UI_Requester_GetLastRow(const UI_REQUESTER_STATE *const s) -{ - return MIN(s->vis_row + s->vis_rows, s->max_rows); -} - -int32_t UI_Requester_GetCurrentRow(const UI_REQUESTER_STATE *s) -{ - return s->sel_row; -} - -bool UI_Requester_IsRowVisible( - const UI_REQUESTER_STATE *const s, const int32_t i) -{ - return i >= UI_Requester_GetFirstRow(s) && i < UI_Requester_GetLastRow(s); -} - -bool UI_Requester_IsRowSelected( - const UI_REQUESTER_STATE *const s, const int32_t i) -{ - return i == s->sel_row; -} - -void UI_BeginRequester( - const UI_REQUESTER_STATE *const s, const char *const title) -{ - UI_BeginWindow(); - UI_WindowTitle(title); - UI_BeginWindowBody(); - - UI_BeginStackEx((UI_STACK_SETTINGS) { - .orientation = UI_STACK_VERTICAL, - .align = { .h = UI_STACK_H_ALIGN_SPAN }, - }); - - if (s->show_arrows) { - M_UpArrow(s); - } - if (s->reserve_space) { - UI_BeginResize( - -1.0f, - s->vis_rows * TEXT_HEIGHT_FIXED - + (s->vis_rows - 1) * s->row_spacing); - } - - UI_BeginStackEx((UI_STACK_SETTINGS) { - .orientation = UI_STACK_VERTICAL, - .align = { .h = UI_STACK_H_ALIGN_SPAN }, - .spacing = { .v = s->row_spacing }, - }); -} - -void UI_EndRequester(const UI_REQUESTER_STATE *const s) -{ - UI_EndStack(); - - if (s->reserve_space) { - UI_EndResize(); - } - if (s->show_arrows) { - M_DownArrow(s); - } - - UI_EndStack(); - UI_EndWindowBody(); - UI_EndWindow(); -} - -void UI_BeginRequesterRow(const UI_REQUESTER_STATE *const s, const int32_t i) -{ - UI_BeginPad(0.0f, TR_VERSION == 1 ? -1.0f : 0.0f); - if (UI_Requester_IsRowSelected(s, i)) { - UI_BeginFrame(UI_FRAME_SELECTED_OPTION); - } - UI_BeginPad(s->row_pad, TR_VERSION == 1 ? 1.0f : 0.0f); -} - -void UI_EndRequesterRow(const UI_REQUESTER_STATE *const s, const int32_t i) -{ - UI_EndPad(); - if (UI_Requester_IsRowSelected(s, i)) { - UI_EndFrame(); - } - UI_EndPad(); -} diff --git a/src/libtrx/game/ui/elements/resize.c b/src/libtrx/game/ui/elements/resize.c deleted file mode 100644 index 3a43dc3cb..000000000 --- a/src/libtrx/game/ui/elements/resize.c +++ /dev/null @@ -1,53 +0,0 @@ -#include "game/ui/elements/resize.h" - -#include "config.h" -#include "game/ui/helpers.h" - -typedef struct { - float x; - float y; -} M_DATA; - -static void M_Measure(UI_NODE *node); -static void M_Draw(const UI_NODE *node); - -static const UI_WIDGET_OPS m_Ops = { - .measure = M_Measure, - .layout = UI_LayoutWrapper, - .draw = M_Draw, -}; - -static void M_Measure(UI_NODE *const node) -{ - UI_MeasureWrapper(node); - const M_DATA *const data = node->data; - if (data->x >= 0.0f) { - node->measure_w = data->x; - } - if (data->y >= 0.0f) { - node->measure_h = data->y; - } -} - -void M_Draw(const UI_NODE *const node) -{ - if (node->measure_w <= 0.0f || node->measure_h <= 0.0f) { - return; - } - UI_DrawWrapper(node); -} - -void UI_BeginResize(const float x, const float y) -{ - UI_NODE *const node = UI_AllocNode(&m_Ops, sizeof(M_DATA)); - M_DATA *const data = node->data; - data->x = x * g_Config.ui.text_scale; - data->y = y * g_Config.ui.text_scale; - UI_AddChild(node); - UI_PushCurrent(node); -} - -void UI_EndResize(void) -{ - UI_PopCurrent(); -} diff --git a/src/libtrx/game/ui/elements/spacer.c b/src/libtrx/game/ui/elements/spacer.c deleted file mode 100644 index dd4160e14..000000000 --- a/src/libtrx/game/ui/elements/spacer.c +++ /dev/null @@ -1,25 +0,0 @@ -#include "game/ui/elements/spacer.h" - -#include "config.h" -#include "game/ui/helpers.h" - -static void M_Measure(UI_NODE *node); - -static const UI_WIDGET_OPS m_Ops = { - .measure = M_Measure, - .layout = UI_LayoutBasic, - .draw = UI_DrawWrapper, -}; - -static void M_Measure(UI_NODE *const node) -{ - // already done in the constructor -} - -void UI_Spacer(const float w, const float h) -{ - UI_NODE *const node = UI_AllocNode(&m_Ops, 0); - node->measure_w = w * g_Config.ui.text_scale; - node->measure_h = h * g_Config.ui.text_scale; - UI_AddChild(node); -} diff --git a/src/libtrx/game/ui/elements/stack.c b/src/libtrx/game/ui/elements/stack.c deleted file mode 100644 index 6638b775b..000000000 --- a/src/libtrx/game/ui/elements/stack.c +++ /dev/null @@ -1,246 +0,0 @@ -#include "game/ui/elements/stack.h" - -#include "config.h" -#include "debug.h" -#include "game/ui/helpers.h" -#include "utils.h" - -#include -#include - -typedef struct { - UI_STACK_SETTINGS settings; -} M_DATA; - -static float M_CalcChildW(const UI_NODE *node, const UI_NODE *child); -static float M_CalcChildH(const UI_NODE *node, const UI_NODE *child); -static float M_CalcStartX(const UI_NODE *node, const UI_NODE *child); -static float M_CalcStartY(const UI_NODE *node, const UI_NODE *child); -static void M_Measure(UI_NODE *node); -static void M_Layout(UI_NODE *node, float x, float y, float w, float h); - -static const UI_WIDGET_OPS m_Ops = { - .measure = M_Measure, - .layout = M_Layout, - .draw = UI_DrawWrapper, -}; - -static float M_CalcChildW(const UI_NODE *const node, const UI_NODE *const child) -{ - M_DATA *const data = node->data; - if (data->settings.align.h == UI_STACK_H_ALIGN_SPAN) { - return MAX(child->measure_w, node->w); - } - return child->measure_w; -} - -static float M_CalcChildH(const UI_NODE *const node, const UI_NODE *const child) -{ - M_DATA *const data = node->data; - if (data->settings.align.v == UI_STACK_V_ALIGN_SPAN) { - return MAX(child->measure_h, node->h); - } - return child->measure_h; -} - -static float M_CalcStartX(const UI_NODE *const node, const UI_NODE *const child) -{ - M_DATA *const data = node->data; - switch (data->settings.align.h) { - case UI_STACK_H_ALIGN_SPAN: - case UI_STACK_H_ALIGN_LEFT: - return node->x; - case UI_STACK_H_ALIGN_CENTER: - return node->x + (node->w - child->measure_w) * 0.5f; - case UI_STACK_H_ALIGN_RIGHT: - return node->x + node->w - child->measure_w; - case UI_STACK_H_ALIGN_DISTRIBUTE: - ASSERT_FAIL(); - } - return 0.0f; -} - -static float M_CalcStartY(const UI_NODE *const node, const UI_NODE *const child) -{ - M_DATA *const data = node->data; - switch (data->settings.align.v) { - case UI_STACK_V_ALIGN_SPAN: - case UI_STACK_V_ALIGN_TOP: - return node->y; - case UI_STACK_V_ALIGN_CENTER: - return node->y + (node->h - child->measure_h) * 0.5f; - case UI_STACK_V_ALIGN_BOTTOM: - return node->y + node->h - child->measure_h; - case UI_STACK_V_ALIGN_DISTRIBUTE: - ASSERT_FAIL(); - } - return 0.0f; -} - -static void M_Measure(UI_NODE *const node) -{ - node->measure_w = 0.0f; - node->measure_h = 0.0f; - UI_NODE *child = node->first_child; - M_DATA *const data = node->data; - const float scale = g_Config.ui.text_scale; - while (child != nullptr) { - if (data->settings.orientation == UI_STACK_VERTICAL) { - node->measure_w = MAX(node->measure_w, child->measure_w); - node->measure_h += child->measure_h; - if (child->next_sibling != nullptr) { - node->measure_h += data->settings.spacing.v * scale; - } - } else { - node->measure_h = MAX(node->measure_h, child->measure_h); - node->measure_w += child->measure_w; - if (child->next_sibling != nullptr) { - node->measure_w += data->settings.spacing.h * scale; - } - } - child = child->next_sibling; - } -} - -static void M_Layout( - UI_NODE *const node, const float x, const float y, const float w, - const float h) -{ - UI_LayoutBasic(node, x, y, w, h); - M_DATA *const data = node->data; - - // Count children and compute the total size they occupy on the main axis - // including the base spacing from the settings. - int32_t child_count = 0; - float total_child_main_size = 0.0f; - UI_NODE *child = node->first_child; - while (child != nullptr) { - switch (data->settings.orientation) { - case UI_STACK_HORIZONTAL: - total_child_main_size += child->measure_w; - break; - case UI_STACK_VERTICAL: - total_child_main_size += child->measure_h; - break; - } - child_count++; - child = child->next_sibling; - } - - // If there is at least one gap between children, compute the normal - // (configured) total spacing on the main axis. If only 1 child or 0 - // children, there's no gap to distribute leftover space into. - const int32_t gaps = (child_count > 1) ? (child_count - 1) : 0; - const float scale = g_Config.ui.text_scale; - float base_spacing = 0.0f; - switch (data->settings.orientation) { - case UI_STACK_HORIZONTAL: - base_spacing = data->settings.spacing.h * gaps * scale; - break; - case UI_STACK_VERTICAL: - base_spacing = data->settings.spacing.v * gaps * scale; - break; - } - - // The space that the children + base spacing absolutely need - const float needed_size = total_child_main_size + base_spacing; - - // The leftover that we can distribute among the (child_count - 1) internal - // gaps. - float leftover = 0.0f; - float extra_per_gap = 0.0f; - switch (data->settings.orientation) { - case UI_STACK_HORIZONTAL: - leftover = w - needed_size; - break; - case UI_STACK_VERTICAL: - leftover = h - needed_size; - break; - } - - if ((data->settings.orientation == UI_STACK_HORIZONTAL - && data->settings.align.h == UI_STACK_H_ALIGN_DISTRIBUTE) - || (data->settings.orientation == UI_STACK_VERTICAL - && data->settings.align.v == UI_STACK_V_ALIGN_DISTRIBUTE)) { - if (gaps > 0 && leftover > 0.0f) { - extra_per_gap = leftover / (float)gaps; - } - } - - // Now we actually lay out the children - float cx = x; - float cy = y; - child = node->first_child; - while (child != nullptr) { - const float cw = M_CalcChildW(node, child); - const float ch = M_CalcChildH(node, child); - - switch (data->settings.orientation) { - case UI_STACK_HORIZONTAL: - // For horizontal: vertical alignment is determined by M_CalcStartY - cy = M_CalcStartY(node, child); - - // Lay out the child - child->ops->layout(child, cx, cy, cw, ch); - - // Advance cx for the next child - cx += cw; - // Add normal spacing + any extra leftover that we are distributing - if (child->next_sibling != nullptr) { - cx += data->settings.spacing.h * scale + extra_per_gap; - } - break; - - case UI_STACK_VERTICAL: - cx = M_CalcStartX(node, child); - - child->ops->layout(child, cx, cy, cw, ch); - - cy += ch; - if (child->next_sibling != nullptr) { - cy += data->settings.spacing.v * scale + extra_per_gap; - } - break; - } - - child = child->next_sibling; - } -} - -UI_NODE *UI_CreateStack(const UI_STACK_SETTINGS settings) -{ - UI_NODE *const node = UI_AllocNode(&m_Ops, sizeof(M_DATA)); - if (node == nullptr) { - return nullptr; - } - M_DATA *const data = node->data; - data->settings = settings; - return node; -} - -void UI_BeginStack(const UI_STACK_ORIENTATION orientation) -{ - UI_BeginStackEx((UI_STACK_SETTINGS) { - .orientation = orientation, - .align = { - .h = UI_STACK_H_ALIGN_LEFT, - .v = UI_STACK_V_ALIGN_TOP, - }, - .spacing = { - .h = 0.0f, - .v = 0.0f, - }, - }); -} - -void UI_BeginStackEx(const UI_STACK_SETTINGS settings) -{ - UI_NODE *const child = UI_CreateStack(settings); - UI_AddChild(child); - UI_PushCurrent(child); -} - -void UI_EndStack(void) -{ - UI_PopCurrent(); -} diff --git a/src/libtrx/game/ui/elements/window.c b/src/libtrx/game/ui/elements/window.c deleted file mode 100644 index 985d60b61..000000000 --- a/src/libtrx/game/ui/elements/window.c +++ /dev/null @@ -1,50 +0,0 @@ -#include "game/ui/elements/window.h" - -#include "game/ui/elements/anchor.h" -#include "game/ui/elements/frame.h" -#include "game/ui/elements/label.h" -#include "game/ui/elements/modal.h" -#include "game/ui/elements/pad.h" -#include "game/ui/elements/stack.h" - -void UI_BeginWindow(void) -{ - const float outer_pad = 2.0f; - const float title_spacing = 3.0f; - UI_BeginFrame(UI_FRAME_DIALOG_BACKGROUND); - UI_BeginPad(outer_pad, outer_pad); - UI_BeginStackEx((UI_STACK_SETTINGS) { - .orientation = UI_STACK_VERTICAL, - .align = { .h = UI_STACK_H_ALIGN_SPAN }, - .spacing = { .v = title_spacing }, - }); -} - -void UI_EndWindow(void) -{ - UI_EndStack(); - UI_EndPad(); - UI_EndFrame(); -} - -void UI_BeginWindowBody(void) -{ - const float body_pad = TR_VERSION == 2 ? 4.0f : 8.0f; - UI_BeginPad(body_pad, body_pad); -} - -void UI_EndWindowBody(void) -{ - UI_EndPad(); -} - -void UI_WindowTitle(const char *const title) -{ - UI_BeginFrame(UI_FRAME_DIALOG_HEADING); - UI_BeginPad(10.0f, TR_VERSION == 2 ? 1.0f : 2.0f); - UI_BeginAnchor(0.5f, 0.5f); - UI_Label(title); - UI_EndAnchor(); - UI_EndPad(); - UI_EndFrame(); -} diff --git a/src/libtrx/game/ui/events.c b/src/libtrx/game/ui/events.c index 54d87be14..13ea6de17 100644 --- a/src/libtrx/game/ui/events.c +++ b/src/libtrx/game/ui/events.c @@ -6,19 +6,35 @@ static EVENT_MANAGER *m_EventManager = nullptr; -void UI_InitEvents(void) +static void M_HandleConfigChange(const EVENT *event, void *data); + +static void M_HandleConfigChange(const EVENT *const event, void *const data) { - m_EventManager = EventManager_Create(); + if (m_EventManager != nullptr) { + const EVENT new_event = { + .name = "canvas_resize", + .sender = nullptr, + .data = nullptr, + }; + EventManager_Fire(m_EventManager, &new_event); + UI_HandleLayoutChange(); + } } -void UI_ShutdownEvents(void) +void UI_Events_Init(void) +{ + m_EventManager = EventManager_Create(); + Config_SubscribeChanges(M_HandleConfigChange, nullptr); +} + +void UI_Events_Shutdown(void) { EventManager_Free(m_EventManager); m_EventManager = nullptr; } -int32_t UI_Subscribe( - const char *const event_name, const void *const sender, +int32_t UI_Events_Subscribe( + const char *const event_name, const UI_WIDGET *const sender, const EVENT_LISTENER listener, void *const user_data) { ASSERT(m_EventManager != nullptr); @@ -26,16 +42,16 @@ int32_t UI_Subscribe( m_EventManager, event_name, sender, listener, user_data); } -void UI_Unsubscribe(const int32_t listener_id) +void UI_Events_Unsubscribe(const int32_t listener_id) { if (m_EventManager != nullptr) { EventManager_Unsubscribe(m_EventManager, listener_id); } } -void UI_FireEvent(const EVENT event) +void UI_Events_Fire(const EVENT *const event) { if (m_EventManager != nullptr) { - EventManager_Fire(m_EventManager, &event); + EventManager_Fire(m_EventManager, event); } } diff --git a/src/libtrx/game/ui/helpers.c b/src/libtrx/game/ui/helpers.c deleted file mode 100644 index e20741d36..000000000 --- a/src/libtrx/game/ui/helpers.c +++ /dev/null @@ -1,50 +0,0 @@ -#include "game/ui/helpers.h" - -#include "utils.h" - -void UI_MeasureWrapper(UI_NODE *const node) -{ - node->measure_w = 0.0f; - node->measure_h = 0.0f; - const UI_NODE *child = node->first_child; - while (child != nullptr) { - node->measure_w = MAX(node->measure_w, child->measure_w); - node->measure_h = MAX(node->measure_h, child->measure_h); - child = child->next_sibling; - } -} - -void UI_LayoutBasic( - UI_NODE *const node, const float x, const float y, const float w, - const float h) -{ - node->x = x; - node->y = y; - node->w = w; - node->h = h; -} - -void UI_LayoutWrapper( - UI_NODE *const node, const float x, const float y, const float w, - const float h) -{ - UI_LayoutBasic(node, x, y, w, h); - UI_NODE *child = node->first_child; - while (child != nullptr) { - if (child->ops->layout != nullptr) { - child->ops->layout(child, x, y, w, h); - } - child = child->next_sibling; - } -} - -void UI_DrawWrapper(const UI_NODE *const node) -{ - const UI_NODE *child = node->first_child; - while (child != nullptr) { - if (child->ops->draw != nullptr) { - child->ops->draw(child); - } - child = child->next_sibling; - } -} diff --git a/src/libtrx/game/ui/helpers.h b/src/libtrx/game/ui/helpers.h deleted file mode 100644 index 88d8eccc6..000000000 --- a/src/libtrx/game/ui/helpers.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include "game/ui/common.h" - -// Repetitive widget ops strategies -void UI_MeasureWrapper(UI_NODE *node); -void UI_LayoutBasic(UI_NODE *node, float x, float y, float w, float h); -void UI_LayoutWrapper(UI_NODE *node, float x, float y, float w, float h); -void UI_DrawWrapper(const UI_NODE *node); diff --git a/src/libtrx/game/ui/hud/console.c b/src/libtrx/game/ui/hud/console.c deleted file mode 100644 index 805ba39c6..000000000 --- a/src/libtrx/game/ui/hud/console.c +++ /dev/null @@ -1,168 +0,0 @@ -#include "game/ui/hud/console.h" - -#include "game/console.h" -#include "game/text.h" -#include "game/ui/elements/modal.h" -#include "game/ui/elements/pad.h" -#include "game/ui/elements/prompt.h" -#include "game/ui/elements/spacer.h" -#include "game/ui/elements/stack.h" -#include "game/ui/events.h" -#include "game/ui/helpers.h" -#include "game/ui/hud/console_logs.h" -#include "utils.h" - -static void M_Draw(const UI_NODE *node); - -static const UI_WIDGET_OPS m_Ops = { - .measure = UI_MeasureWrapper, - .layout = UI_LayoutWrapper, - .draw = M_Draw, -}; - -static void M_MoveHistoryUp(UI_CONSOLE_STATE *s); -static void M_MoveHistoryDown(UI_CONSOLE_STATE *s); -static void M_HandleOpen(const EVENT *event, void *user_data); -static void M_HandleClose(const EVENT *event, void *user_data); -static void M_HandleCancel(const EVENT *event, void *user_data); -static void M_HandleConfirm(const EVENT *event, void *user_data); - -static void M_MoveHistoryUp(UI_CONSOLE_STATE *const s) -{ - s->history_idx--; - CLAMP(s->history_idx, 0, Console_History_GetLength()); - const char *const new_prompt = Console_History_Get(s->history_idx); - UI_Prompt_ChangeText(&s->prompt, new_prompt == nullptr ? "" : new_prompt); -} - -static void M_MoveHistoryDown(UI_CONSOLE_STATE *const s) -{ - s->history_idx++; - CLAMP(s->history_idx, 0, Console_History_GetLength()); - const char *const new_prompt = Console_History_Get(s->history_idx); - UI_Prompt_ChangeText(&s->prompt, new_prompt == nullptr ? "" : new_prompt); -} - -static void M_HandleKeyDown(const EVENT *const event, void *const user_data) -{ - if (!Console_IsOpened()) { - return; - } - - UI_CONSOLE_STATE *const s = user_data; - const UI_INPUT key = (UI_INPUT)(uintptr_t)event->data; - - // clang-format off - switch (key) { - case UI_KEY_UP: M_MoveHistoryUp(s); break; - case UI_KEY_DOWN: M_MoveHistoryDown(s); break; - default: break; - } - // clang-format on -} - -static void M_HandleOpen(const EVENT *event, void *user_data) -{ - UI_CONSOLE_STATE *const s = user_data; - UI_Prompt_SetFocus(&s->prompt, true); - s->history_idx = Console_History_GetLength(); -} - -static void M_HandleClose(const EVENT *event, void *user_data) -{ - UI_CONSOLE_STATE *const s = user_data; - UI_Prompt_SetFocus(&s->prompt, false); - UI_Prompt_Clear(&s->prompt); -} - -static void M_HandleCancel(const EVENT *const event, void *const data) -{ - Console_Close(); -} - -static void M_HandleConfirm(const EVENT *event, void *user_data) -{ - UI_CONSOLE_STATE *const s = user_data; - const char *text = event->data; - Console_History_Append(text); - Console_Eval(text); - Console_Close(); - s->history_idx = Console_History_GetLength(); -} - -static void M_Draw(const UI_NODE *node) -{ - UI_CONSOLE_STATE *const s = *(UI_CONSOLE_STATE **)node->data; - UI_DrawWrapper(node); - if (Console_IsOpened() || s->logs.vis_lines > 0) { - Console_DrawBackdrop(); - } -} - -void UI_Console_Init(UI_CONSOLE_STATE *const s) -{ - UI_Prompt_Init(&s->prompt); - UI_ConsoleLogs_Init(&s->logs); - - struct { - const char *event_name; - const void *sender; - EVENT_LISTENER handler; - } listeners[] = { - { "console_open", nullptr, M_HandleOpen }, - { "console_close", nullptr, M_HandleClose }, - { "cancel", &s->prompt, M_HandleCancel }, - { "confirm", &s->prompt, M_HandleConfirm }, - { "key_down", nullptr, M_HandleKeyDown }, - { 0 }, - }; - for (int32_t i = 0; listeners[i].event_name != nullptr; i++) { - s->listeners[i] = UI_Subscribe( - listeners[i].event_name, listeners[i].sender, listeners[i].handler, - s); - } - - s->history_idx = -1; -} - -void UI_Console_Free(UI_CONSOLE_STATE *const s) -{ - UI_ConsoleLogs_Free(&s->logs); - UI_Prompt_Free(&s->prompt); - for (int32_t i = 0; i < 5; i++) { - UI_Unsubscribe(s->listeners[i]); - } -} - -void UI_Console_Control(UI_CONSOLE_STATE *const s) -{ - UI_Prompt_Control(&s->prompt); -} - -void UI_Console(UI_CONSOLE_STATE *const s) -{ - UI_Prompt_SetFocus(&s->prompt, Console_IsOpened()); - - UI_NODE *const node = UI_AllocNode(&m_Ops, sizeof(UI_CONSOLE_STATE *)); - *(UI_CONSOLE_STATE **)node->data = s; - UI_AddChild(node); - UI_PushCurrent(node); - - UI_BeginModal(0.0f, 1.0f); - UI_BeginPad(5.0f, 5.0f); - UI_BeginStack(UI_STACK_VERTICAL); - - UI_ConsoleLogs(&s->logs); - UI_Spacer(0.0f, 8.0f); - if (Console_IsOpened()) { - UI_Prompt(&s->prompt); - } else { - UI_Spacer(0.0f, TEXT_HEIGHT_FIXED); - } - - UI_EndStack(); - UI_EndModal(); - UI_EndPad(); - - UI_PopCurrent(); -} diff --git a/src/libtrx/game/ui/hud/console_logs.c b/src/libtrx/game/ui/hud/console_logs.c deleted file mode 100644 index 278ca3659..000000000 --- a/src/libtrx/game/ui/hud/console_logs.c +++ /dev/null @@ -1,100 +0,0 @@ -#include "game/ui/hud/console_logs.h" - -#include "debug.h" -#include "game/clock.h" -#include "game/text.h" -#include "game/ui/elements/label.h" -#include "game/ui/elements/stack.h" -#include "game/ui/events.h" -#include "memory.h" -#include "strings.h" - -#include - -#define M_LOG_SCALE 0.8f -#define M_MAX_LOG_LINES 20 -#define M_DELAY_PER_CHAR 0.2 - -static void M_ScrollLogs(UI_CONSOLE_LOGS *s); -static void M_UpdateLogCount(UI_CONSOLE_LOGS *s); -static void M_HandleLog(const EVENT *event, void *user_data); - -static void M_ScrollLogs(UI_CONSOLE_LOGS *const s) -{ - int32_t i = s->max_lines - 1; - while (i >= 0 && !s->logs[i].expire_at) { - i--; - } - - bool need_layout = false; - while (i >= 0 && s->logs[i].expire_at - && Clock_GetRealTime() >= s->logs[i].expire_at) { - s->logs[i].expire_at = 0.0; - Memory_FreePointer(&s->logs[i].text); - need_layout = true; - i--; - } - - if (need_layout) { - M_UpdateLogCount(s); - } -} -static void M_UpdateLogCount(UI_CONSOLE_LOGS *const s) -{ - s->vis_lines = 0; - for (int32_t i = s->max_lines - 1; i >= 0; i--) { - if (s->logs[i].expire_at != 0.0) { - s->vis_lines = i + 1; - break; - } - } -} - -static void M_HandleLog(const EVENT *const event, void *const user_data) -{ - const char *text = event->data; - UI_CONSOLE_LOGS *const s = user_data; - Memory_FreePointer(&s->logs[s->max_lines - 1].text); - for (int32_t i = s->max_lines - 1; i > 0; i--) { - s->logs[i] = s->logs[i - 1]; - } - - s->logs[0].expire_at = - Clock_GetRealTime() + strlen(text) * M_DELAY_PER_CHAR; - s->logs[0].text = String_WordWrap(text, Text_GetMaxLineLength()); - M_UpdateLogCount(s); -} - -void UI_ConsoleLogs_Init(UI_CONSOLE_LOGS *const s) -{ - if (s->max_lines <= 0) { - s->max_lines = M_MAX_LOG_LINES; - } - s->logs = Memory_Alloc(s->max_lines * sizeof(UI_CONSOLE_LOG_LINE)); - s->vis_lines = 0; - s->listener_id = UI_Subscribe("console_log", nullptr, M_HandleLog, s); -} - -void UI_ConsoleLogs_Free(UI_CONSOLE_LOGS *const s) -{ - Memory_FreePointer(&s->logs); - UI_Unsubscribe(s->listener_id); -} - -void UI_ConsoleLogs(UI_CONSOLE_LOGS *const s) -{ - ASSERT(s != nullptr); - M_ScrollLogs(s); - UI_BeginStackEx((UI_STACK_SETTINGS) { - .orientation = UI_STACK_VERTICAL, - .align = { - .h = UI_STACK_H_ALIGN_LEFT, - .v = UI_STACK_V_ALIGN_CENTER, - }, - }); - for (int32_t i = s->vis_lines - 1; i >= 0; i--) { - UI_LabelEx( - s->logs[i].text, (UI_LABEL_SETTINGS) { .scale = M_LOG_SCALE }); - } - UI_EndStack(); -} diff --git a/src/libtrx/game/ui/widgets/console.c b/src/libtrx/game/ui/widgets/console.c new file mode 100644 index 000000000..e69153dc1 --- /dev/null +++ b/src/libtrx/game/ui/widgets/console.c @@ -0,0 +1,310 @@ +#include "game/ui/widgets/console.h" + +#include "game/clock.h" +#include "game/console/common.h" +#include "game/console/history.h" +#include "game/text.h" +#include "game/ui/common.h" +#include "game/ui/events.h" +#include "game/ui/widgets/label.h" +#include "game/ui/widgets/prompt.h" +#include "game/ui/widgets/spacer.h" +#include "game/ui/widgets/stack.h" +#include "memory.h" +#include "strings.h" +#include "utils.h" + +#include + +#define WINDOW_MARGIN 5 +#define MAX_LOG_LINES 20 +#define MAX_LISTENERS 4 +#define LOG_MARGIN 10 +#define LOG_SCALE 0.8 +#define DELAY_PER_CHAR 0.2 + +typedef struct { + UI_WIDGET_VTABLE vtable; + UI_WIDGET *container; + UI_WIDGET *prompt; + UI_WIDGET *spacer; + char *log_lines; + int32_t logs_on_screen; + int32_t history_idx; + + int32_t listeners[MAX_LISTENERS]; + struct { + double expire_at; + UI_WIDGET *label; + } logs[MAX_LOG_LINES]; +} UI_CONSOLE; + +static void M_MoveHistoryUp(UI_CONSOLE *self); +static void M_MoveHistoryDown(UI_CONSOLE *self); +static void M_DoLayout(UI_CONSOLE *self); +static void M_HandlePromptCancel(const EVENT *event, void *data); +static void M_HandlePromptConfirm(const EVENT *event, void *data); +static void M_HandleCanvasResize(const EVENT *event, void *data); +static void M_UpdateLogCount(UI_CONSOLE *self); + +static int32_t M_GetWidth(const UI_CONSOLE *self); +static int32_t M_GetHeight(const UI_CONSOLE *self); +static void M_SetPosition(UI_CONSOLE *self, int32_t x, int32_t y); +static void M_Control(UI_CONSOLE *self); +static void M_Draw(UI_CONSOLE *self); +static void M_Free(UI_CONSOLE *self); + +static void M_MoveHistoryUp(UI_CONSOLE *const self) +{ + self->history_idx--; + CLAMP(self->history_idx, 0, Console_History_GetLength()); + const char *const new_prompt = Console_History_Get(self->history_idx); + if (new_prompt == nullptr) { + UI_Prompt_ChangeText(self->prompt, ""); + } else { + UI_Prompt_ChangeText(self->prompt, new_prompt); + } +} + +static void M_MoveHistoryDown(UI_CONSOLE *const self) +{ + self->history_idx++; + CLAMP(self->history_idx, 0, Console_History_GetLength()); + const char *const new_prompt = Console_History_Get(self->history_idx); + if (new_prompt == nullptr) { + UI_Prompt_ChangeText(self->prompt, ""); + } else { + UI_Prompt_ChangeText(self->prompt, new_prompt); + } +} + +static void M_DoLayout(UI_CONSOLE *const self) +{ + UI_Stack_SetSize(self->container, M_GetWidth(self), M_GetHeight(self)); + M_SetPosition(self, WINDOW_MARGIN, WINDOW_MARGIN); +} + +static void M_HandlePromptCancel(const EVENT *const event, void *const data) +{ + Console_Close(); +} + +static void M_HandlePromptConfirm(const EVENT *const event, void *const data) +{ + UI_CONSOLE *const self = (UI_CONSOLE *)data; + const char *text = event->data; + Console_History_Append(text); + Console_Eval(text); + Console_Close(); + + self->history_idx = Console_History_GetLength(); +} + +static void M_HandleCanvasResize(const EVENT *event, void *data) +{ + UI_CONSOLE *const self = (UI_CONSOLE *)data; + M_DoLayout(self); +} + +static void M_HandleKeyDown(const EVENT *const event, void *const user_data) +{ + if (!Console_IsOpened()) { + return; + } + + UI_CONSOLE *const self = user_data; + const UI_INPUT key = (UI_INPUT)(uintptr_t)event->data; + + // clang-format off + switch (key) { + case UI_KEY_UP: M_MoveHistoryUp(self); break; + case UI_KEY_DOWN: M_MoveHistoryDown(self); break; + default: break; + } + // clang-format on +} + +static void M_UpdateLogCount(UI_CONSOLE *const self) +{ + self->logs_on_screen = 0; + for (int32_t i = MAX_LOG_LINES - 1; i >= 0; i--) { + if (self->logs[i].expire_at != 0.0) { + self->logs_on_screen = i + 1; + break; + } + } +} + +static int32_t M_GetWidth(const UI_CONSOLE *const self) +{ + if (self->vtable.is_hidden) { + return 0; + } + return UI_GetCanvasWidth() - 2 * WINDOW_MARGIN; +} + +static int32_t M_GetHeight(const UI_CONSOLE *const self) +{ + if (self->vtable.is_hidden) { + return 0; + } + return UI_GetCanvasHeight() - 2 * WINDOW_MARGIN; +} + +static void M_SetPosition(UI_CONSOLE *const self, int32_t x, int32_t y) +{ + self->container->set_position(self->container, x, y); +} + +static void M_Control(UI_CONSOLE *const self) +{ + if (self->container->control != nullptr) { + self->container->control(self->container); + } +} + +static void M_Draw(UI_CONSOLE *const self) +{ + if (self->vtable.is_hidden) { + return; + } + + if (self->container->draw != nullptr) { + self->container->draw(self->container); + } +} + +static void M_Free(UI_CONSOLE *const self) +{ + self->spacer->free(self->spacer); + self->prompt->free(self->prompt); + self->container->free(self->container); + for (int32_t i = 0; i < MAX_LOG_LINES; i++) { + self->logs[i].label->free(self->logs[i].label); + } + for (int32_t i = 0; i < MAX_LISTENERS; i++) { + UI_Events_Unsubscribe(self->listeners[i]); + } + Memory_Free(self); +} + +UI_WIDGET *UI_Console_Create(void) +{ + UI_CONSOLE *const self = Memory_Alloc(sizeof(UI_CONSOLE)); + self->vtable = (UI_WIDGET_VTABLE) { + .control = (UI_WIDGET_CONTROL)M_Control, + .draw = (UI_WIDGET_DRAW)M_Draw, + .get_width = (UI_WIDGET_GET_WIDTH)M_GetWidth, + .get_height = (UI_WIDGET_GET_HEIGHT)M_GetHeight, + .set_position = (UI_WIDGET_SET_POSITION)M_SetPosition, + .free = (UI_WIDGET_FREE)M_Free, + }; + + self->container = UI_Stack_Create( + UI_STACK_LAYOUT_VERTICAL, M_GetWidth(self), M_GetHeight(self)); + UI_Stack_SetVAlign(self->container, UI_STACK_V_ALIGN_BOTTOM); + + for (int32_t i = MAX_LOG_LINES - 1; i >= 0; i--) { + self->logs[i].label = + UI_Label_Create("", UI_LABEL_AUTO_SIZE, UI_LABEL_AUTO_SIZE); + UI_Label_SetScale(self->logs[i].label, LOG_SCALE); + UI_Stack_AddChild(self->container, self->logs[i].label); + } + + self->spacer = UI_Spacer_Create(LOG_MARGIN, LOG_MARGIN); + UI_Stack_AddChild(self->container, self->spacer); + + self->prompt = UI_Prompt_Create(UI_LABEL_AUTO_SIZE, UI_LABEL_AUTO_SIZE); + UI_Stack_AddChild(self->container, self->prompt); + + int32_t i = 0; + self->listeners[i++] = UI_Events_Subscribe( + "confirm", self->prompt, M_HandlePromptConfirm, self); + self->listeners[i++] = UI_Events_Subscribe( + "cancel", self->prompt, M_HandlePromptCancel, nullptr); + self->listeners[i++] = UI_Events_Subscribe( + "canvas_resize", nullptr, M_HandleCanvasResize, self); + self->listeners[i++] = + UI_Events_Subscribe("key_down", nullptr, M_HandleKeyDown, self); + + M_DoLayout(self); + return (UI_WIDGET *)self; +} + +void UI_Console_HandleOpen(UI_WIDGET *const widget) +{ + UI_CONSOLE *const self = (UI_CONSOLE *)widget; + UI_Prompt_SetFocus(self->prompt, true); + self->history_idx = Console_History_GetLength(); +} + +void UI_Console_HandleClose(UI_WIDGET *const widget) +{ + UI_CONSOLE *const self = (UI_CONSOLE *)widget; + UI_Prompt_SetFocus(self->prompt, false); + UI_Prompt_Clear(self->prompt); +} + +void UI_Console_HandleLog(UI_WIDGET *const widget, const char *const text) +{ + UI_CONSOLE *const self = (UI_CONSOLE *)widget; + + int32_t dst_idx = -1; + for (int32_t i = MAX_LOG_LINES - 1; i > 0; i--) { + if (self->logs[i].label == nullptr) { + continue; + } + UI_Label_ChangeText( + self->logs[i].label, UI_Label_GetText(self->logs[i - 1].label)); + self->logs[i].expire_at = self->logs[i - 1].expire_at; + } + + if (self->logs[0].label == nullptr) { + return; + } + + self->logs[0].expire_at = + Clock_GetRealTime() + strlen(text) * DELAY_PER_CHAR; + + char *wrapped = String_WordWrap(text, Text_GetMaxLineLength()); + UI_Label_ChangeText(self->logs[0].label, wrapped); + Memory_FreePointer(&wrapped); + + M_DoLayout(self); + M_UpdateLogCount(self); +} + +void UI_Console_ScrollLogs(UI_WIDGET *const widget) +{ + UI_CONSOLE *const self = (UI_CONSOLE *)widget; + + int32_t i = MAX_LOG_LINES - 1; + while (i >= 0 && !self->logs[i].expire_at) { + i--; + } + + bool need_layout = false; + while (i >= 0 && self->logs[i].expire_at + && Clock_GetRealTime() >= self->logs[i].expire_at) { + self->logs[i].expire_at = 0.0; + UI_Label_ChangeText(self->logs[i].label, ""); + need_layout = true; + i--; + } + + if (need_layout) { + M_UpdateLogCount(self); + M_DoLayout(self); + } +} + +int32_t UI_Console_GetVisibleLogCount(UI_WIDGET *const widget) +{ + UI_CONSOLE *const self = (UI_CONSOLE *)widget; + return self->logs_on_screen; +} + +int32_t UI_Console_GetMaxLogCount(UI_WIDGET *const widget) +{ + return MAX_LOG_LINES; +} diff --git a/src/libtrx/game/ui/widgets/frame.c b/src/libtrx/game/ui/widgets/frame.c new file mode 100644 index 000000000..c8bc2f366 --- /dev/null +++ b/src/libtrx/game/ui/widgets/frame.c @@ -0,0 +1,99 @@ +#include "game/text.h" +#include "game/ui/widgets/spacer.h" +#include "memory.h" + +typedef struct { + UI_WIDGET_VTABLE vtable; + UI_WIDGET *root; + int32_t margin; + int32_t padding; + bool is_frame_visible; + TEXTSTRING *frame; +} UI_FRAME; + +static int32_t M_GetWidth(const UI_FRAME *self); +static int32_t M_GetHeight(const UI_FRAME *self); +static void M_SetPosition(UI_FRAME *self, int32_t x, int32_t y); +static void M_Draw(UI_FRAME *self); +static void M_Free(UI_FRAME *self); + +static int32_t M_GetWidth(const UI_FRAME *const self) +{ + if (self->vtable.is_hidden) { + return 0; + } + return self->root->get_width(self->root) + self->margin * 2 + + self->padding * 2; +} + +static int32_t M_GetHeight(const UI_FRAME *const self) +{ + if (self->vtable.is_hidden) { + return 0; + } + return self->root->get_height(self->root) + self->margin * 2 + + self->padding * 2; +} + +static void M_SetPosition( + UI_FRAME *const self, const int32_t x, const int32_t y) +{ + const int32_t w = M_GetWidth(self); + const int32_t h = M_GetHeight(self); + self->root->set_position( + self->root, x + self->margin + self->padding, + y + self->margin + self->padding); + Text_SetPos( + self->frame, x + self->margin, y + self->margin + TEXT_HEIGHT_FIXED); + self->frame->pos.z = 24; + Text_AddBackground( + self->frame, w - self->margin * 2, h - self->margin * 2, + (w - self->margin * 2) / 2, 0, TS_REQUESTED); + Text_AddOutline(self->frame, TS_REQUESTED); +} + +static void M_Draw(UI_FRAME *const self) +{ + if (self->vtable.is_hidden) { + return; + } + self->root->draw(self->root); + if (self->frame != nullptr && self->is_frame_visible) { + Text_DrawText(self->frame); + } +} + +static void M_Free(UI_FRAME *const self) +{ + Text_Remove(self->frame); + Memory_Free(self); +} + +UI_WIDGET *UI_Frame_Create( + UI_WIDGET *const root, const int32_t margin, const int32_t padding) +{ + UI_FRAME *const self = Memory_Alloc(sizeof(UI_FRAME)); + self->vtable = (UI_WIDGET_VTABLE) { + .control = nullptr, + .draw = (UI_WIDGET_DRAW)M_Draw, + .get_width = (UI_WIDGET_GET_WIDTH)M_GetWidth, + .get_height = (UI_WIDGET_GET_HEIGHT)M_GetHeight, + .set_position = (UI_WIDGET_SET_POSITION)M_SetPosition, + .free = (UI_WIDGET_FREE)M_Free, + }; + self->root = root; + self->margin = margin; + self->padding = padding; + self->is_frame_visible = true; + self->frame = Text_Create(0, 0, ""); + self->frame->pos.z = -400; + self->frame->flags.manual_draw = 1; + return (UI_WIDGET *)self; +} + +void UI_Frame_SetFrameVisible( + UI_WIDGET *const widget, const bool is_frame_visible) +{ + UI_FRAME *const self = (UI_FRAME *)widget; + self->is_frame_visible = is_frame_visible; +} diff --git a/src/libtrx/game/ui/widgets/label.c b/src/libtrx/game/ui/widgets/label.c new file mode 100644 index 000000000..2740b6834 --- /dev/null +++ b/src/libtrx/game/ui/widgets/label.c @@ -0,0 +1,164 @@ +#include "game/ui/widgets/label.h" + +#include "game/text.h" +#include "memory.h" + +typedef struct { + UI_WIDGET_VTABLE vtable; + TEXTSTRING *text; + int32_t width; + int32_t height; + bool has_frame; +} UI_LABEL; + +static int32_t M_GetWidth(const UI_LABEL *self); +static int32_t M_GetHeight(const UI_LABEL *self); +static void M_SetPosition(UI_LABEL *self, int32_t x, int32_t y); +static void M_Draw(UI_LABEL *self); +static void M_Free(UI_LABEL *self); + +static int32_t M_GetWidth(const UI_LABEL *const self) +{ + if (self->vtable.is_hidden) { + return 0; + } + if (self->width != UI_LABEL_AUTO_SIZE) { + return self->width; + } + return Text_GetWidth(self->text); +} + +static int32_t M_GetHeight(const UI_LABEL *const self) +{ + if (self->vtable.is_hidden) { + return 0; + } + if (self->height != UI_LABEL_AUTO_SIZE) { + return self->height; + } + return Text_GetHeight(self->text); +} + +static void M_SetPosition( + UI_LABEL *const self, const int32_t x, const int32_t y) +{ + Text_SetPos(self->text, x, y + TEXT_HEIGHT_FIXED); +} + +static void M_Draw(UI_LABEL *const self) +{ + if (self->vtable.is_hidden) { + return; + } + Text_DrawText(self->text); +} + +static void M_Free(UI_LABEL *const self) +{ + Text_Remove(self->text); + Memory_Free(self); +} + +UI_WIDGET *UI_Label_Create( + const char *const text, const int32_t width, const int32_t height) +{ + UI_LABEL *self = Memory_Alloc(sizeof(UI_LABEL)); + self->vtable = (UI_WIDGET_VTABLE) { + .get_width = (UI_WIDGET_GET_WIDTH)M_GetWidth, + .get_height = (UI_WIDGET_GET_HEIGHT)M_GetHeight, + .set_position = (UI_WIDGET_SET_POSITION)M_SetPosition, + .control = nullptr, + .draw = (UI_WIDGET_DRAW)M_Draw, + .free = (UI_WIDGET_FREE)M_Free, + }; + + self->width = width; + self->height = height; + self->has_frame = false; + + self->text = Text_Create(0, 0, text); + self->text->pos.z = 16; + Text_SetMultiline(self->text, true); + self->text->flags.manual_draw = 1; + + return (UI_WIDGET *)self; +} + +void UI_Label_ChangeText(UI_WIDGET *const widget, const char *const text) +{ + UI_LABEL *const self = (UI_LABEL *)widget; + Text_ChangeText(self->text, text); +} + +const char *UI_Label_GetText(UI_WIDGET *const widget) +{ + UI_LABEL *const self = (UI_LABEL *)widget; + return self->text->content; +} + +void UI_Label_SetSize( + UI_WIDGET *const widget, const int32_t width, const int32_t height) +{ + UI_LABEL *const self = (UI_LABEL *)widget; + self->width = width; + self->height = height; +} + +void UI_Label_SetVisible(UI_WIDGET *const widget, const bool visible) +{ + UI_LABEL *const self = (UI_LABEL *)widget; + Text_Hide(self->text, !visible); +} + +void UI_Label_AddFrame(UI_WIDGET *const widget) +{ + UI_LABEL *const self = (UI_LABEL *)widget; + if (!self->has_frame) { + self->text->pos.z = 0; + Text_AddBackground(self->text, 0, 0, 0, 0, TS_REQUESTED); + Text_AddOutline(self->text, TS_REQUESTED); + self->has_frame = true; + } +} + +void UI_Label_RemoveFrame(UI_WIDGET *const widget) +{ + UI_LABEL *const self = (UI_LABEL *)widget; + if (self->has_frame) { + Text_RemoveBackground(self->text); + Text_RemoveOutline(self->text); + self->text->pos.z = 16; + self->has_frame = false; + } +} + +void UI_Label_Flash( + UI_WIDGET *const widget, const bool enable, const int32_t rate) +{ + UI_LABEL *const self = (UI_LABEL *)widget; + Text_Flash(self->text, enable, rate); +} + +void UI_Label_SetScale(UI_WIDGET *const widget, const float scale) +{ + UI_LABEL *const self = (UI_LABEL *)widget; + Text_SetScale(self->text, TEXT_BASE_SCALE * scale, TEXT_BASE_SCALE * scale); +} + +void UI_Label_SetZIndex(UI_WIDGET *const widget, const int32_t z_index) +{ + UI_LABEL *const self = (UI_LABEL *)widget; + self->text->pos.z = z_index; +} + +int32_t UI_Label_MeasureTextWidth(UI_WIDGET *const widget) +{ + UI_LABEL *const self = (UI_LABEL *)widget; + return Text_GetWidth(self->text); +} + +int32_t UI_Label_MeasureTextHeight(UI_WIDGET *const widget) +{ + UI_LABEL *const self = (UI_LABEL *)widget; + return Text_GetHeight(self->text); +} diff --git a/src/libtrx/game/ui/widgets/photo_mode.c b/src/libtrx/game/ui/widgets/photo_mode.c new file mode 100644 index 000000000..c88137456 --- /dev/null +++ b/src/libtrx/game/ui/widgets/photo_mode.c @@ -0,0 +1,221 @@ +#include "game/ui/widgets/photo_mode.h" + +#include "config.h" +#include "game/game_string.h" +#include "game/input.h" +#include "game/text.h" +#include "game/ui/common.h" +#include "game/ui/widgets/label.h" +#include "game/ui/widgets/spacer.h" +#include "game/ui/widgets/stack.h" +#include "game/ui/widgets/window.h" +#include "memory.h" + +#include + +#define TITLE_MARGIN 5 +#define WINDOW_MARGIN 10 +#define DIALOG_PADDING 5 + +typedef struct { + UI_WIDGET_VTABLE vtable; + UI_WIDGET *window; + UI_WIDGET *title; + UI_WIDGET *outer_stack; + UI_WIDGET *inner_stack; + UI_WIDGET *left_stack; + UI_WIDGET *right_stack; + UI_WIDGET *spacer; + bool shown; + int32_t label_count; + UI_WIDGET **left_labels; + UI_WIDGET **right_labels; +} UI_PHOTO_MODE; + +static int32_t M_GetWidth(const UI_PHOTO_MODE *self); +static int32_t M_GetHeight(const UI_PHOTO_MODE *self); +static void M_SetPosition(UI_PHOTO_MODE *self, int32_t x, int32_t y); +static void M_Control(UI_PHOTO_MODE *self); +static void M_Draw(UI_PHOTO_MODE *self); +static void M_Free(UI_PHOTO_MODE *self); + +static int32_t M_GetWidth(const UI_PHOTO_MODE *const self) +{ + return UI_GetCanvasWidth() - 2 * WINDOW_MARGIN; +} + +static int32_t M_GetHeight(const UI_PHOTO_MODE *const self) +{ + return UI_GetCanvasHeight() - 2 * WINDOW_MARGIN; +} + +static void M_SetPosition(UI_PHOTO_MODE *const self, int32_t x, int32_t y) +{ + self->window->set_position(self->window, x, y); +} + +static void M_Control(UI_PHOTO_MODE *const self) +{ + if (self->window->control != nullptr) { + self->window->control(self->window); + } + + self->shown = g_Config.ui.enable_photo_mode_ui; +} + +static void M_Draw(UI_PHOTO_MODE *const self) +{ + if (self->shown && self->window->draw != nullptr) { + self->window->draw(self->window); + } +} + +static void M_Free(UI_PHOTO_MODE *const self) +{ + for (int32_t i = 0; i < self->label_count; i++) { + self->left_labels[i]->free(self->left_labels[i]); + self->right_labels[i]->free(self->right_labels[i]); + } + self->spacer->free(self->spacer); + self->title->free(self->title); + self->outer_stack->free(self->outer_stack); + self->inner_stack->free(self->inner_stack); + self->left_stack->free(self->left_stack); + self->right_stack->free(self->right_stack); + self->window->free(self->window); + Memory_Free(self->left_labels); + Memory_Free(self->right_labels); + Memory_Free(self); +} + +UI_WIDGET *UI_PhotoMode_Create(void) +{ + UI_PHOTO_MODE *const self = Memory_Alloc(sizeof(UI_PHOTO_MODE)); + self->vtable = (UI_WIDGET_VTABLE) { + .control = (UI_WIDGET_CONTROL)M_Control, + .draw = (UI_WIDGET_DRAW)M_Draw, + .get_width = (UI_WIDGET_GET_WIDTH)M_GetWidth, + .get_height = (UI_WIDGET_GET_HEIGHT)M_GetHeight, + .set_position = (UI_WIDGET_SET_POSITION)M_SetPosition, + .free = (UI_WIDGET_FREE)M_Free, + }; + + self->outer_stack = UI_Stack_Create( + UI_STACK_LAYOUT_VERTICAL, UI_STACK_AUTO_SIZE, UI_STACK_AUTO_SIZE); + self->inner_stack = UI_Stack_Create( + UI_STACK_LAYOUT_HORIZONTAL, UI_STACK_AUTO_SIZE, UI_STACK_AUTO_SIZE); + self->left_stack = UI_Stack_Create( + UI_STACK_LAYOUT_VERTICAL, UI_STACK_AUTO_SIZE, UI_STACK_AUTO_SIZE); + self->right_stack = UI_Stack_Create( + UI_STACK_LAYOUT_VERTICAL, UI_STACK_AUTO_SIZE, UI_STACK_AUTO_SIZE); + + const char *const title = GS(PHOTO_MODE_TITLE); + self->title = UI_Label_Create(title, UI_LABEL_AUTO_SIZE, TEXT_HEIGHT_FIXED); + UI_Stack_AddChild(self->outer_stack, self->title); + + self->spacer = UI_Spacer_Create(TITLE_MARGIN, TITLE_MARGIN); + UI_Stack_AddChild(self->outer_stack, self->spacer); + UI_Stack_AddChild(self->outer_stack, self->inner_stack); + UI_Stack_AddChild(self->inner_stack, self->left_stack); + UI_Stack_AddChild(self->inner_stack, self->right_stack); + + char move_role[50]; + sprintf( + move_role, "%s%s%s%s%s%s: ", + Input_GetKeyName( + INPUT_BACKEND_KEYBOARD, g_Config.input.keyboard_layout, + INPUT_ROLE_CAMERA_UP), + Input_GetKeyName( + INPUT_BACKEND_KEYBOARD, g_Config.input.keyboard_layout, + INPUT_ROLE_CAMERA_DOWN), + Input_GetKeyName( + INPUT_BACKEND_KEYBOARD, g_Config.input.keyboard_layout, + INPUT_ROLE_CAMERA_FORWARD), + Input_GetKeyName( + INPUT_BACKEND_KEYBOARD, g_Config.input.keyboard_layout, + INPUT_ROLE_CAMERA_BACK), + Input_GetKeyName( + INPUT_BACKEND_KEYBOARD, g_Config.input.keyboard_layout, + INPUT_ROLE_CAMERA_LEFT), + Input_GetKeyName( + INPUT_BACKEND_KEYBOARD, g_Config.input.keyboard_layout, + INPUT_ROLE_CAMERA_RIGHT)); + + const char *const rot_role = + "\\{button left} \\{button up} " + "\\{button down} \\{button right} : "; + + char z_roll_role[100]; + sprintf(z_roll_role, "%s: ", GS(PHOTO_MODE_ROLL_ROLE)); + + char roll_role[100]; + sprintf(roll_role, "%s: ", GS(KEYMAP_ROLL)); + + char fov_role[100]; + sprintf(fov_role, "%s: ", GS(PHOTO_MODE_FOV_ROLE)); + + char reset_role[100]; + sprintf(reset_role, "%s: ", GS(KEYMAP_LOOK)); + + char help_role[20]; + sprintf( + help_role, "%s: ", + Input_GetKeyName( + INPUT_BACKEND_KEYBOARD, g_Config.input.keyboard_layout, + INPUT_ROLE_TOGGLE_UI)); + + char action_role[100]; + sprintf(action_role, "%s: ", GS(KEYMAP_ACTION)); + + char exit_role[100]; + sprintf( + exit_role, "%s/%s: ", + Input_GetKeyName( + INPUT_BACKEND_KEYBOARD, g_Config.input.keyboard_layout, + INPUT_ROLE_TOGGLE_PHOTO_MODE), + Input_GetKeyName( + INPUT_BACKEND_KEYBOARD, g_Config.input.keyboard_layout, + INPUT_ROLE_OPTION)); + + const char *const inputs[] = { + move_role, rot_role, z_roll_role, roll_role, fov_role, + reset_role, help_role, action_role, exit_role, nullptr, + }; + + const char *const roles[] = { + GS(PHOTO_MODE_MOVE_PROMPT), + GS(PHOTO_MODE_ROTATE_PROMPT), + GS(PHOTO_MODE_ROLL_PROMPT), + GS(PHOTO_MODE_ROTATE90_PROMPT), + GS(PHOTO_MODE_FOV_PROMPT), + GS(PHOTO_MODE_RESET_PROMPT), + GS(MISC_TOGGLE_HELP), + GS(PHOTO_MODE_SNAP_PROMPT), + GS(MISC_EXIT), + nullptr, + }; + + self->shown = true; + self->label_count = 0; + while (inputs[self->label_count] != nullptr) { + self->label_count++; + } + + self->left_labels = Memory_Alloc(sizeof(UI_WIDGET *) * self->label_count); + self->right_labels = Memory_Alloc(sizeof(UI_WIDGET *) * self->label_count); + for (int32_t i = 0; i < self->label_count; i++) { + self->left_labels[i] = + UI_Label_Create(inputs[i], UI_LABEL_AUTO_SIZE, TEXT_HEIGHT_FIXED); + UI_Stack_AddChild(self->left_stack, self->left_labels[i]); + self->right_labels[i] = + UI_Label_Create(roles[i], UI_LABEL_AUTO_SIZE, TEXT_HEIGHT_FIXED); + UI_Stack_AddChild(self->right_stack, self->right_labels[i]); + } + + self->window = UI_Window_Create( + self->outer_stack, DIALOG_PADDING, DIALOG_PADDING, DIALOG_PADDING * 2, + DIALOG_PADDING); + + M_SetPosition(self, WINDOW_MARGIN, WINDOW_MARGIN); + return (UI_WIDGET *)self; +} diff --git a/src/libtrx/game/ui/widgets/prompt.c b/src/libtrx/game/ui/widgets/prompt.c new file mode 100644 index 000000000..8dadf4fa0 --- /dev/null +++ b/src/libtrx/game/ui/widgets/prompt.c @@ -0,0 +1,327 @@ +#include "game/ui/widgets/prompt.h" + +#include "game/const.h" +#include "game/input.h" +#include "game/ui/common.h" +#include "game/ui/events.h" +#include "game/ui/widgets/label.h" +#include "memory.h" +#include "strings.h" + +#include + +static const char m_ValidPromptChars[] = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-: "; + +typedef struct { + UI_WIDGET_VTABLE vtable; + UI_WIDGET *label; + UI_WIDGET *caret; + + int32_t listener1; + int32_t listener2; + + struct { + int32_t x; + int32_t y; + } pos; + bool is_focused; + int32_t current_text_capacity; + char *current_text; + int32_t caret_pos; +} UI_PROMPT; + +static void M_UpdatePromptLabel(UI_PROMPT *self); +static void M_UpdateCaretLabel(UI_PROMPT *self); +static void M_MoveCaretLeft(UI_PROMPT *self); +static void M_MoveCaretRight(UI_PROMPT *self); +static void M_MoveCaretStart(UI_PROMPT *self); +static void M_MoveCaretEnd(UI_PROMPT *self); +static void M_DeleteCharBack(UI_PROMPT *self); +static void M_Confirm(UI_PROMPT *self); +static void M_Cancel(UI_PROMPT *self); +static void M_Clear(UI_PROMPT *self); + +static int32_t M_GetWidth(const UI_PROMPT *self); +static int32_t M_GetHeight(const UI_PROMPT *self); +static void M_SetPosition(UI_PROMPT *self, int32_t x, int32_t y); +static void M_Control(UI_PROMPT *self); +static void M_Draw(UI_PROMPT *self); +static void M_Free(UI_PROMPT *self); +static void M_HandleKeyDown(const EVENT *event, void *user_data); +static void M_HandleTextEdit(const EVENT *event, void *user_data); + +static void M_UpdatePromptLabel(UI_PROMPT *const self) +{ + UI_Label_ChangeText(self->label, self->current_text); +} + +static void M_UpdateCaretLabel(UI_PROMPT *const self) +{ + const char old = self->current_text[self->caret_pos]; + self->current_text[self->caret_pos] = '\0'; + UI_Label_ChangeText(self->label, self->current_text); + const int32_t width = UI_Label_MeasureTextWidth(self->label); + self->current_text[self->caret_pos] = old; + UI_Label_ChangeText(self->label, self->current_text); + + self->caret->set_position(self->caret, self->pos.x + width, self->pos.y); +} + +static int32_t M_GetWidth(const UI_PROMPT *const self) +{ + if (self->vtable.is_hidden) { + return 0; + } + return self->label->get_width(self->label); +} + +static int32_t M_GetHeight(const UI_PROMPT *const self) +{ + if (self->vtable.is_hidden) { + return 0; + } + return self->label->get_height(self->label); +} + +static void M_SetPosition( + UI_PROMPT *const self, const int32_t x, const int32_t y) +{ + self->pos.x = x; + self->pos.y = y; + self->label->set_position(self->label, x, y); + M_UpdateCaretLabel(self); +} + +static void M_Control(UI_PROMPT *const self) +{ + if (self->label->control != nullptr) { + self->label->control(self->label); + } + if (self->caret->control != nullptr) { + self->caret->control(self->caret); + } +} + +static void M_Draw(UI_PROMPT *const self) +{ + if (self->vtable.is_hidden) { + return; + } + if (self->label->draw != nullptr) { + self->label->draw(self->label); + } + if (self->caret->draw != nullptr) { + self->caret->draw(self->caret); + } +} + +static void M_Free(UI_PROMPT *const self) +{ + self->label->free(self->label); + self->caret->free(self->caret); + UI_Events_Unsubscribe(self->listener1); + UI_Events_Unsubscribe(self->listener2); + Memory_FreePointer(&self->current_text); + Memory_Free(self); +} + +static void M_MoveCaretLeft(UI_PROMPT *const self) +{ + if (self->caret_pos > 0) { + self->caret_pos--; + M_UpdateCaretLabel(self); + } +} + +static void M_MoveCaretRight(UI_PROMPT *const self) +{ + if (self->caret_pos < (int32_t)strlen(self->current_text)) { + self->caret_pos++; + M_UpdateCaretLabel(self); + } +} + +static void M_MoveCaretStart(UI_PROMPT *const self) +{ + self->caret_pos = 0; + M_UpdateCaretLabel(self); +} + +static void M_MoveCaretEnd(UI_PROMPT *const self) +{ + self->caret_pos = strlen(self->current_text); + M_UpdateCaretLabel(self); +} + +static void M_DeleteCharBack(UI_PROMPT *const self) +{ + if (self->caret_pos <= 0) { + return; + } + + memmove( + self->current_text + self->caret_pos - 1, + self->current_text + self->caret_pos, + strlen(self->current_text) + 1 - self->caret_pos); + + self->caret_pos--; + M_UpdatePromptLabel(self); + M_UpdateCaretLabel(self); +} + +static void M_Confirm(UI_PROMPT *const self) +{ + if (String_IsEmpty(self->current_text)) { + M_Cancel(self); + return; + } + const EVENT event = { + .name = "confirm", + .sender = self, + .data = self->current_text, + }; + UI_Events_Fire(&event); + M_Clear(self); + M_UpdateCaretLabel(self); +} + +static void M_Cancel(UI_PROMPT *const self) +{ + const EVENT event = { + .name = "cancel", + .sender = self, + .data = self->current_text, + }; + UI_Events_Fire(&event); + M_Clear(self); +} + +static void M_Clear(UI_PROMPT *const self) +{ + strcpy(self->current_text, ""); + self->caret_pos = 0; + M_UpdatePromptLabel(self); + M_UpdateCaretLabel(self); +} + +static void M_HandleKeyDown(const EVENT *const event, void *const user_data) +{ + const UI_INPUT key = (UI_INPUT)(uintptr_t)event->data; + UI_PROMPT *const self = user_data; + + if (!self->is_focused) { + return; + } + + // clang-format off + switch (key) { + case UI_KEY_LEFT: M_MoveCaretLeft(self); break; + case UI_KEY_RIGHT: M_MoveCaretRight(self); break; + case UI_KEY_HOME: M_MoveCaretStart(self); break; + case UI_KEY_END: M_MoveCaretEnd(self); break; + case UI_KEY_BACK: M_DeleteCharBack(self); break; + case UI_KEY_RETURN: M_Confirm(self); break; + case UI_KEY_ESCAPE: M_Cancel(self); break; + default: break; + } + // clang-format on +} + +static void M_HandleTextEdit(const EVENT *const event, void *const user_data) +{ + const char *insert_string = event->data; + const size_t insert_length = strlen(insert_string); + UI_PROMPT *const self = user_data; + + if (!self->is_focused) { + return; + } + + if (strlen(insert_string) != 1 + || !strstr(m_ValidPromptChars, insert_string)) { + return; + } + + const size_t available_space = + self->current_text_capacity - strlen(self->current_text); + if (insert_length >= available_space) { + self->current_text_capacity *= 2; + self->current_text = + Memory_Realloc(self->current_text, self->current_text_capacity); + } + + memmove( + self->current_text + self->caret_pos + insert_length, + self->current_text + self->caret_pos, + strlen(self->current_text) + 1 - self->caret_pos); + memcpy(self->current_text + self->caret_pos, insert_string, insert_length); + + self->caret_pos += insert_length; + M_UpdatePromptLabel(self); + M_UpdateCaretLabel(self); +} + +UI_WIDGET *UI_Prompt_Create(const int32_t width, const int32_t height) +{ + UI_PROMPT *const self = Memory_Alloc(sizeof(UI_PROMPT)); + self->vtable = (UI_WIDGET_VTABLE) { + .control = (UI_WIDGET_CONTROL)M_Control, + .draw = (UI_WIDGET_DRAW)M_Draw, + .get_width = (UI_WIDGET_GET_WIDTH)M_GetWidth, + .get_height = (UI_WIDGET_GET_HEIGHT)M_GetHeight, + .set_position = (UI_WIDGET_SET_POSITION)M_SetPosition, + .free = (UI_WIDGET_FREE)M_Free, + }; + + self->current_text_capacity = 1; + self->current_text = Memory_Alloc(self->current_text_capacity); + self->label = UI_Label_Create(self->current_text, width, height); + self->caret = UI_Label_Create("", width, height); + UI_Label_SetZIndex(self->label, 16); + UI_Label_SetZIndex(self->caret, 8); + self->is_focused = false; + + self->listener1 = + UI_Events_Subscribe("key_down", nullptr, M_HandleKeyDown, self); + self->listener2 = + UI_Events_Subscribe("text_edit", nullptr, M_HandleTextEdit, self); + + return (UI_WIDGET *)self; +} + +void UI_Prompt_SetSize( + UI_WIDGET *const widget, const int32_t width, const int32_t height) +{ + UI_PROMPT *const self = (UI_PROMPT *)widget; + UI_Label_SetSize(self->label, width, height); +} + +void UI_Prompt_SetFocus(UI_WIDGET *const widget, const bool is_focused) +{ + UI_PROMPT *const self = (UI_PROMPT *)widget; + self->is_focused = is_focused; + if (is_focused) { + Input_EnterListenMode(); + UI_Label_ChangeText(self->caret, "\\{button left}"); + UI_Label_Flash(self->caret, 1, LOGIC_FPS * 2 / 3); + } else { + Input_ExitListenMode(); + UI_Label_ChangeText(self->caret, ""); + } +} + +void UI_Prompt_Clear(UI_WIDGET *const widget) +{ + UI_PROMPT *const self = (UI_PROMPT *)widget; + M_Clear(self); +} + +void UI_Prompt_ChangeText(UI_WIDGET *widget, const char *new_text) +{ + UI_PROMPT *const self = (UI_PROMPT *)widget; + Memory_FreePointer(&self->current_text); + self->current_text = Memory_DupStr(new_text); + self->caret_pos = strlen(new_text); + M_UpdateCaretLabel(self); +} diff --git a/src/libtrx/game/ui/widgets/requester.c b/src/libtrx/game/ui/widgets/requester.c new file mode 100644 index 000000000..b519c4bb9 --- /dev/null +++ b/src/libtrx/game/ui/widgets/requester.c @@ -0,0 +1,321 @@ +#include "game/ui/widgets/requester.h" + +#include "game/game_string.h" +#include "game/input.h" +#include "game/text.h" +#include "game/ui/common.h" +#include "game/ui/widgets/frame.h" +#include "game/ui/widgets/label.h" +#include "game/ui/widgets/stack.h" +#include "game/ui/widgets/window.h" +#include "log.h" +#include "memory.h" + +#include + +typedef struct { + void *user_data; + UI_WIDGET *frame; + UI_WIDGET *stack; + UI_WIDGET *left_label; + UI_WIDGET *right_label; +} M_ROW; + +typedef struct { + UI_WIDGET_VTABLE vtable; + UI_WIDGET *window; + UI_WIDGET *outer_stack; + UI_REQUESTER_SETTINGS settings; + + bool is_confirmed; + int32_t selected_row_offset; + int32_t visible_row_offset; + int32_t row_count; + M_ROW *rows; + + int32_t selection_margin; + int32_t selection_padding; + + int32_t listener; +} UI_REQUESTER; + +static void M_ClearRows(UI_REQUESTER *self); +static M_ROW *M_AddRow( + UI_REQUESTER *self, const char *left_text, const char *right_text, + void *user_data); +static void M_DoLayout(UI_REQUESTER *self); +static void M_HandleCanvasResize(const EVENT *event, void *data); + +static int32_t M_GetWidth(const UI_REQUESTER *self); +static int32_t M_GetHeight(const UI_REQUESTER *self); +static void M_SetPosition(UI_REQUESTER *self, int32_t x, int32_t y); +static void M_Control(UI_REQUESTER *self); +static void M_Draw(UI_REQUESTER *self); +static void M_Free(UI_REQUESTER *self); + +static void M_ClearRows(UI_REQUESTER *const self) +{ + for (int32_t i = 0; i < self->row_count; i++) { + self->rows[i].left_label->free(self->rows[i].left_label); + if (self->rows[i].right_label != nullptr) { + self->rows[i].right_label->free(self->rows[i].right_label); + } + self->rows[i].frame->free(self->rows[i].frame); + self->rows[i].stack->free(self->rows[i].stack); + } + UI_Stack_ClearChildren(self->outer_stack); + self->visible_row_offset = 0; + self->row_count = 0; + self->selected_row_offset = -1; + self->is_confirmed = false; +} + +static M_ROW *M_AddRow( + UI_REQUESTER *const self, const char *const left_text, + const char *const right_text, void *const user_data) +{ + self->row_count++; + self->rows = Memory_Realloc(self->rows, sizeof(M_ROW) * self->row_count); + M_ROW *const row = &self->rows[self->row_count - 1]; + + row->stack = UI_Stack_Create( + UI_STACK_LAYOUT_HORIZONTAL, self->settings.width, UI_STACK_AUTO_SIZE); + UI_Stack_SetHAlign(row->stack, UI_STACK_H_ALIGN_DISTRIBUTE); + + row->frame = UI_Frame_Create(row->stack, 0, 0); + UI_Frame_SetFrameVisible(row->frame, false); + + row->left_label = UI_Label_Create( + left_text, UI_LABEL_AUTO_SIZE, self->settings.row_height); + UI_Stack_AddChild(row->stack, row->left_label); + + if (right_text != nullptr) { + row->right_label = UI_Label_Create( + right_text, UI_LABEL_AUTO_SIZE, self->settings.row_height); + UI_Stack_AddChild(row->stack, row->right_label); + } else { + row->right_label = nullptr; + UI_Stack_SetHAlign(row->stack, UI_STACK_H_ALIGN_CENTER); + } + + row->user_data = user_data; + + for (int32_t y = 0; y < self->row_count; y++) { + self->rows[y].stack->is_hidden = y < self->visible_row_offset + || y >= self->visible_row_offset + self->settings.visible_rows; + } + if (self->settings.is_selectable && self->selected_row_offset == -1) { + self->selected_row_offset = 0; + UI_Frame_SetFrameVisible( + self->rows[self->selected_row_offset].frame, true); + } + + UI_Stack_AddChild(self->outer_stack, row->frame); + return row; +} + +static void M_DoLayout(UI_REQUESTER *const self) +{ + UI_HandleLayoutChange(); +} + +static void M_HandleCanvasResize(const EVENT *event, void *data) +{ + UI_REQUESTER *const self = (UI_REQUESTER *)data; + M_DoLayout(self); +} + +static int32_t M_GetWidth(const UI_REQUESTER *const self) +{ + return self->window->get_width(self->window); +} + +static int32_t M_GetHeight(const UI_REQUESTER *const self) +{ + return self->window->get_height(self->window); +} + +static void M_SetPosition( + UI_REQUESTER *const self, const int32_t x, const int32_t y) +{ + self->window->set_position(self->window, x, y); +} + +static void M_Control(UI_REQUESTER *const self) +{ + if (self->window->control != nullptr) { + self->window->control(self->window); + } + + bool update = false; + if (g_InputDB.menu_down) { + if (self->visible_row_offset + self->settings.visible_rows + < self->row_count) { + self->rows[self->visible_row_offset].stack->is_hidden = true; + self->rows[self->visible_row_offset + self->settings.visible_rows] + .stack->is_hidden = false; + self->visible_row_offset++; + update = true; + } + } else if (g_InputDB.menu_up) { + if (self->visible_row_offset > 0) { + self->rows + [self->visible_row_offset + self->settings.visible_rows - 1] + .stack->is_hidden = true; + self->rows[self->visible_row_offset - 1].stack->is_hidden = false; + self->visible_row_offset--; + update = true; + } + } + + if (self->settings.is_selectable) { + if (g_InputDB.menu_down + && self->selected_row_offset + 1 < self->row_count) { + if (self->selected_row_offset != -1) { + UI_Frame_SetFrameVisible( + self->rows[self->selected_row_offset].frame, false); + } + self->selected_row_offset++; + UI_Frame_SetFrameVisible( + self->rows[self->selected_row_offset].frame, true); + update = true; + } else if (g_InputDB.menu_up && self->selected_row_offset > 0) { + if (self->selected_row_offset != -1) { + UI_Frame_SetFrameVisible( + self->rows[self->selected_row_offset].frame, false); + } + self->selected_row_offset--; + UI_Frame_SetFrameVisible( + self->rows[self->selected_row_offset].frame, true); + update = true; + } + if (g_InputDB.menu_confirm) { + self->is_confirmed = true; + } + } + + if (update) { + M_DoLayout(self); + } +} + +static void M_Draw(UI_REQUESTER *const self) +{ + if (self->window->draw != nullptr) { + self->window->draw(self->window); + } +} + +static void M_Free(UI_REQUESTER *const self) +{ + M_ClearRows(self); + self->outer_stack->free(self->outer_stack); + self->window->free(self->window); + UI_Events_Unsubscribe(self->listener); + Memory_Free(self); +} + +UI_WIDGET *UI_Requester_Create(UI_REQUESTER_SETTINGS settings) +{ + UI_REQUESTER *const self = Memory_Alloc(sizeof(UI_REQUESTER)); + if (settings.row_height == 0) { + settings.row_height = 18; + } + if (settings.width == 0) { + settings.width = UI_STACK_AUTO_SIZE; + } + + self->vtable = (UI_WIDGET_VTABLE) { + .get_width = (UI_WIDGET_GET_WIDTH)M_GetWidth, + .get_height = (UI_WIDGET_GET_HEIGHT)M_GetHeight, + .set_position = (UI_WIDGET_SET_POSITION)M_SetPosition, + .control = (UI_WIDGET_CONTROL)M_Control, + .draw = (UI_WIDGET_DRAW)M_Draw, + .free = (UI_WIDGET_FREE)M_Free, + }; + + self->settings = settings; + self->outer_stack = UI_Stack_Create( + UI_STACK_LAYOUT_VERTICAL, UI_STACK_AUTO_SIZE, + settings.row_height * self->settings.visible_rows); + UI_Stack_SetHAlign(self->outer_stack, UI_STACK_H_ALIGN_CENTER); + + self->window = UI_Window_Create(self->outer_stack, 8, 8, 8, 8); + + self->selected_row_offset = -1; + self->listener = UI_Events_Subscribe( + "canvas_resize", nullptr, M_HandleCanvasResize, self); + + M_DoLayout(self); + return (UI_WIDGET *)self; +} + +int32_t UI_Requester_GetSelectedRow(UI_WIDGET *const widget) +{ + UI_REQUESTER *const self = (UI_REQUESTER *)widget; + return self->is_confirmed ? self->selected_row_offset : -1; +} + +void UI_Requester_SetTitle(UI_WIDGET *const widget, const char *const title) +{ + UI_REQUESTER *const self = (UI_REQUESTER *)widget; + UI_Window_SetTitle(self->window, title); + M_DoLayout(self); +} + +void UI_Requester_ClearRows(UI_WIDGET *const widget) +{ + UI_REQUESTER *const self = (UI_REQUESTER *)widget; + M_ClearRows(self); + M_DoLayout(self); +} + +void UI_Requester_AddRowLR( + UI_WIDGET *const widget, const char *const text_l, const char *const text_r, + void *const user_data) +{ + UI_REQUESTER *const self = (UI_REQUESTER *)widget; + M_AddRow(self, text_l, text_r, user_data); + M_DoLayout(self); +} + +void UI_Requester_AddRowC( + UI_WIDGET *const widget, const char *const text, void *const user_data) +{ + UI_REQUESTER *const self = (UI_REQUESTER *)widget; + M_AddRow(self, text, nullptr, user_data); + M_DoLayout(self); +} + +void *UI_Requester_GetRowUserData(UI_WIDGET *const widget, const int32_t idx) +{ + UI_REQUESTER *const self = (UI_REQUESTER *)widget; + if (idx >= self->row_count || idx < 0) { + return nullptr; + } + return self->rows[idx].user_data; +} + +int32_t UI_Requester_GetRowCount(UI_WIDGET *const widget) +{ + UI_REQUESTER *const self = (UI_REQUESTER *)widget; + return self->row_count; +} + +void UI_Requester_ChangeRowLR( + UI_WIDGET *widget, int32_t idx, const char *text_l, const char *text_r, + void *user_data) +{ + UI_REQUESTER *const self = (UI_REQUESTER *)widget; + if (idx >= self->row_count || idx < 0) { + return; + } + if (self->rows[idx].left_label != nullptr && text_l != nullptr) { + UI_Label_ChangeText(self->rows[idx].left_label, text_l); + } + if (self->rows[idx].right_label != nullptr && text_r != nullptr) { + UI_Label_ChangeText(self->rows[idx].right_label, text_r); + } + self->rows[idx].user_data = user_data; + M_DoLayout(self); +} diff --git a/src/libtrx/game/ui/widgets/spacer.c b/src/libtrx/game/ui/widgets/spacer.c new file mode 100644 index 000000000..0c06f853d --- /dev/null +++ b/src/libtrx/game/ui/widgets/spacer.c @@ -0,0 +1,65 @@ +#include "game/ui/widgets/spacer.h" + +#include "memory.h" + +typedef struct { + UI_WIDGET_VTABLE vtable; + int32_t width; + int32_t height; +} UI_SPACER; + +static int32_t M_GetWidth(const UI_SPACER *self); +static int32_t M_GetHeight(const UI_SPACER *self); +static void M_SetPosition(UI_SPACER *self, int32_t x, int32_t y); +static void M_Control(UI_SPACER *self); +static void M_Free(UI_SPACER *self); + +static int32_t M_GetWidth(const UI_SPACER *const self) +{ + if (self->vtable.is_hidden) { + return 0; + } + return self->width; +} + +static int32_t M_GetHeight(const UI_SPACER *const self) +{ + if (self->vtable.is_hidden) { + return 0; + } + return self->height; +} + +static void M_SetPosition( + UI_SPACER *const self, const int32_t x, const int32_t y) +{ +} + +static void M_Free(UI_SPACER *const self) +{ + Memory_Free(self); +} + +UI_WIDGET *UI_Spacer_Create(const int32_t width, const int32_t height) +{ + UI_SPACER *const self = Memory_Alloc(sizeof(UI_SPACER)); + self->vtable = (UI_WIDGET_VTABLE) { + .control = nullptr, + .draw = nullptr, + .get_width = (UI_WIDGET_GET_WIDTH)M_GetWidth, + .get_height = (UI_WIDGET_GET_HEIGHT)M_GetHeight, + .set_position = (UI_WIDGET_SET_POSITION)M_SetPosition, + .free = (UI_WIDGET_FREE)M_Free, + }; + self->width = width; + self->height = height; + return (UI_WIDGET *)self; +} + +void UI_Spacer_SetSize( + UI_WIDGET *const widget, const int32_t width, const int32_t height) +{ + UI_SPACER *const self = (UI_SPACER *)widget; + self->width = width; + self->height = height; +} diff --git a/src/libtrx/game/ui/widgets/stack.c b/src/libtrx/game/ui/widgets/stack.c new file mode 100644 index 000000000..99a537a9b --- /dev/null +++ b/src/libtrx/game/ui/widgets/stack.c @@ -0,0 +1,314 @@ +#include "game/ui/widgets/stack.h" + +#include "memory.h" +#include "utils.h" +#include "vector.h" + +typedef struct { + UI_WIDGET_VTABLE vtable; + + struct { + UI_STACK_H_ALIGN h; + UI_STACK_V_ALIGN v; + } align; + int32_t width; + int32_t height; + int32_t x; + int32_t y; + UI_STACK_LAYOUT layout; + VECTOR *children; +} UI_STACK; + +static int32_t M_GetChildrenWidth(const UI_STACK *self); +static int32_t M_GetChildrenHeight(const UI_STACK *self); +static int32_t M_GetHeight(const UI_STACK *self); +static int32_t M_GetWidth(const UI_STACK *self); +static void M_SetPosition(UI_STACK *self, int32_t x, int32_t y); +static void M_Control(UI_STACK *self); +static void M_Draw(UI_STACK *self); +static void M_Free(UI_STACK *self); + +static int32_t M_GetChildrenWidth(const UI_STACK *const self) +{ + int32_t result = 0; + for (int32_t i = 0; i < self->children->count; i++) { + const UI_WIDGET *const child = + *(UI_WIDGET **)Vector_Get(self->children, i); + switch (self->layout) { + case UI_STACK_LAYOUT_HORIZONTAL: + result += child->get_width(child); + break; + case UI_STACK_LAYOUT_VERTICAL: + result = MAX(result, child->get_width(child)); + break; + } + } + return result; +} + +static int32_t M_GetChildrenHeight(const UI_STACK *const self) +{ + int32_t result = 0; + for (int32_t i = 0; i < self->children->count; i++) { + const UI_WIDGET *const child = + *(UI_WIDGET **)Vector_Get(self->children, i); + switch (self->layout) { + case UI_STACK_LAYOUT_HORIZONTAL: + result = MAX(result, child->get_height(child)); + break; + case UI_STACK_LAYOUT_VERTICAL: + result += child->get_height(child); + break; + } + } + return result; +} + +static int32_t M_GetWidth(const UI_STACK *const self) +{ + if (self->vtable.is_hidden) { + return 0; + } + if (self->width != UI_STACK_AUTO_SIZE) { + return self->width; + } + return M_GetChildrenWidth(self); +} + +static int32_t M_GetHeight(const UI_STACK *const self) +{ + if (self->vtable.is_hidden) { + return 0; + } + if (self->height != UI_STACK_AUTO_SIZE) { + return self->height; + } + return M_GetChildrenHeight(self); +} + +static void M_SetPosition( + UI_STACK *const self, const int32_t x, const int32_t y) +{ + self->x = x; + self->y = y; + UI_Stack_DoLayout((UI_WIDGET *)self); +} + +static void M_Control(UI_STACK *const self) +{ + for (int32_t i = 0; i < self->children->count; i++) { + UI_WIDGET *const child = *(UI_WIDGET **)Vector_Get(self->children, i); + if (child->control != nullptr) { + child->control(child); + } + } +} + +static void M_Draw(UI_STACK *const self) +{ + if (self->vtable.is_hidden) { + return; + } + for (int32_t i = 0; i < self->children->count; i++) { + UI_WIDGET *const child = *(UI_WIDGET **)Vector_Get(self->children, i); + if (child->draw != nullptr) { + child->draw(child); + } + } +} + +static void M_Free(UI_STACK *const self) +{ + Vector_Free(self->children); + Memory_Free(self); +} + +void UI_Stack_ClearChildren(UI_WIDGET *const widget) +{ + UI_STACK *const self = (UI_STACK *)widget; + Vector_Clear(self->children); +} + +void UI_Stack_AddChild(UI_WIDGET *const widget, UI_WIDGET *const child) +{ + UI_STACK *const self = (UI_STACK *)widget; + Vector_Add(self->children, (void *)&child); +} + +UI_WIDGET *UI_Stack_Create( + const UI_STACK_LAYOUT layout, const int32_t width, const int32_t height) +{ + UI_STACK *const self = Memory_Alloc(sizeof(UI_STACK)); + self->vtable = (UI_WIDGET_VTABLE) { + .control = (UI_WIDGET_CONTROL)M_Control, + .draw = (UI_WIDGET_DRAW)M_Draw, + .get_width = (UI_WIDGET_GET_WIDTH)M_GetWidth, + .get_height = (UI_WIDGET_GET_HEIGHT)M_GetHeight, + .set_position = (UI_WIDGET_SET_POSITION)M_SetPosition, + .free = (UI_WIDGET_FREE)M_Free, + }; + + self->align.h = UI_STACK_H_ALIGN_LEFT; + self->align.v = UI_STACK_V_ALIGN_TOP; + self->width = width; + self->height = height; + self->layout = layout; + self->children = Vector_Create(sizeof(UI_WIDGET *)); + return (UI_WIDGET *)self; +} + +void UI_Stack_SetHAlign(UI_WIDGET *const widget, const UI_STACK_H_ALIGN align) +{ + UI_STACK *const self = (UI_STACK *)widget; + self->align.h = align; +} + +void UI_Stack_SetVAlign(UI_WIDGET *const widget, const UI_STACK_V_ALIGN align) +{ + UI_STACK *const self = (UI_STACK *)widget; + self->align.v = align; +} + +void UI_Stack_SetSize( + UI_WIDGET *const widget, const int32_t width, const int32_t height) +{ + UI_STACK *const self = (UI_STACK *)widget; + self->width = width; + self->height = height; + UI_Stack_DoLayout(widget); +} + +void UI_Stack_DoLayout(UI_WIDGET *const widget) +{ + UI_STACK *const self = (UI_STACK *)widget; + const int32_t self_width = M_GetWidth(self); + const int32_t self_height = M_GetHeight(self); + const int32_t children_width = M_GetChildrenWidth(self); + const int32_t children_height = M_GetChildrenHeight(self); + + // calculate main axis placement + int32_t x = -999; + int32_t y = -999; + + int32_t spacing_h = 0; + int32_t remainder_h = 0; + if (self->children->count > 1 && self->layout == UI_STACK_LAYOUT_HORIZONTAL + && self->align.h == UI_STACK_H_ALIGN_DISTRIBUTE + && self_width > children_width) { + spacing_h = (self_width - children_width) / (self->children->count - 1); + remainder_h = + (self_width - children_width) % (self->children->count - 1); + } + + int32_t spacing_v = 0; + int32_t remainder_v = 0; + if (self->children->count > 1 && self->layout == UI_STACK_LAYOUT_VERTICAL + && self->align.v == UI_STACK_V_ALIGN_DISTRIBUTE + && self_height > children_height) { + spacing_v = + (self_height - children_height) / (self->children->count - 1); + remainder_v = + (self_height - children_height) % (self->children->count - 1); + } + + switch (self->layout) { + case UI_STACK_LAYOUT_HORIZONTAL: + switch (self->align.h) { + case UI_STACK_H_ALIGN_LEFT: + x = self->x; + break; + case UI_STACK_H_ALIGN_CENTER: + x = self->x + (self_width - children_width) / 2; + break; + case UI_STACK_H_ALIGN_RIGHT: + x = self->x + self_width - children_width; + break; + case UI_STACK_H_ALIGN_DISTRIBUTE: + if (self->children->count == 1) { + x = self->x + (self_width - children_width) / 2; + } else { + x = self->x; + } + break; + } + break; + + case UI_STACK_LAYOUT_VERTICAL: + switch (self->align.v) { + case UI_STACK_V_ALIGN_TOP: + y = self->y; + break; + case UI_STACK_V_ALIGN_CENTER: + y = self->y + (self_height - children_height) / 2; + break; + case UI_STACK_V_ALIGN_BOTTOM: + y = self->y + self_height - children_height; + break; + case UI_STACK_V_ALIGN_DISTRIBUTE: + y = self->y; + break; + } + break; + } + + for (int32_t i = 0; i < self->children->count; i++) { + UI_WIDGET *const child = *(UI_WIDGET **)Vector_Get(self->children, i); + const int32_t child_width = child->get_width(child); + const int32_t child_height = child->get_height(child); + + // calculate other axis placement + switch (self->layout) { + case UI_STACK_LAYOUT_HORIZONTAL: + switch (self->align.v) { + case UI_STACK_V_ALIGN_TOP: + y = self->y; + break; + case UI_STACK_V_ALIGN_CENTER: + y = self->y + (self_height - child_height) / 2; + break; + case UI_STACK_V_ALIGN_BOTTOM: + y = self->y + self_height - child_height; + break; + default: + break; + } + break; + + case UI_STACK_LAYOUT_VERTICAL: + switch (self->align.h) { + case UI_STACK_H_ALIGN_LEFT: + x = self->x; + break; + case UI_STACK_H_ALIGN_CENTER: + x = self->x + (self_width - child_width) / 2; + break; + case UI_STACK_H_ALIGN_RIGHT: + x = self->x + self_width - child_width; + break; + default: + break; + } + break; + } + + child->set_position(child, x, y); + + // calculate main axis offset + switch (self->layout) { + case UI_STACK_LAYOUT_HORIZONTAL: + x += child_width + spacing_h; + if (remainder_h > 0) { + x += 1; + remainder_h--; + } + break; + case UI_STACK_LAYOUT_VERTICAL: + y += child_height + spacing_v; + if (remainder_v > 0) { + y += 1; + remainder_v--; + } + break; + } + } +} diff --git a/src/libtrx/game/ui/widgets/window.c b/src/libtrx/game/ui/widgets/window.c new file mode 100644 index 000000000..12b6db4e7 --- /dev/null +++ b/src/libtrx/game/ui/widgets/window.c @@ -0,0 +1,177 @@ +#include "game/ui/widgets/window.h" + +#include "game/text.h" +#include "game/ui/widgets/label.h" +#include "memory.h" +#include "utils.h" + +typedef struct { + UI_WIDGET_VTABLE vtable; + TEXTSTRING *frame; + TEXTSTRING *title_frame; + UI_WIDGET *root; + UI_WIDGET *title_label; + struct { + int32_t left; + int32_t right; + int32_t top; + int32_t bottom; + } border; + int32_t title_margin; + int32_t title_padding; +} UI_WINDOW; + +static int32_t M_GetWidth(const UI_WINDOW *self); +static int32_t M_GetHeight(const UI_WINDOW *self); +static void M_SetPosition(UI_WINDOW *self, int32_t x, int32_t y); +static void M_Control(UI_WINDOW *self); +static void M_Draw(UI_WINDOW *self); +static void M_Free(UI_WINDOW *self); + +static int32_t M_GetWidth(const UI_WINDOW *const self) +{ + if (self->vtable.is_hidden) { + return 0; + } + const int32_t title_width = self->title_label != nullptr + ? self->title_label->get_width(self->title_label) + + 2 * self->title_margin + 2 * self->title_padding + : 0; + const int32_t root_width = self->root->get_width(self->root) + + self->border.left + self->border.right; + return MAX(title_width, root_width); +} + +static int32_t M_GetHeight(const UI_WINDOW *const self) +{ + if (self->vtable.is_hidden) { + return 0; + } + const int32_t title_height = self->title_label != nullptr + ? self->title_label->get_height(self->title_label) + + 2 * self->title_margin + 2 * self->title_padding + : 0; + const int32_t root_height = self->root->get_height(self->root) + + self->border.top + self->border.bottom; + return title_height + root_height; +} + +static void M_SetPosition( + UI_WINDOW *const self, const int32_t x, const int32_t y) +{ + const int32_t w = M_GetWidth(self); + const int32_t h = M_GetHeight(self); + + if (self->title_label != nullptr) { + self->title_label->set_position( + self->title_label, + x + (w - self->title_label->get_width(self->title_label)) / 2, + y + self->title_margin + self->title_padding); + + self->root->set_position( + self->root, x + (w - self->root->get_width(self->root)) / 2, + y + self->title_label->get_height(self->title_label) + + self->title_margin * 2 + self->title_padding * 2 + + self->border.top); + + Text_SetPos( + self->title_frame, x + self->title_margin, + y + TEXT_HEIGHT_FIXED + self->title_margin); + Text_AddBackground( + self->title_frame, w - self->title_margin * 2, + TEXT_HEIGHT_FIXED + self->title_padding * 2, + (w - self->title_margin * 2) / 2, 0, TS_REQUESTED); + Text_AddOutline(self->title_frame, TS_REQUESTED); + } else { + self->root->set_position( + self->root, x + self->border.left, y + self->border.top); + } + + Text_SetPos(self->frame, x, y + TEXT_HEIGHT_FIXED); + Text_AddBackground(self->frame, w, h, w / 2, 0, TS_BACKGROUND); + Text_AddOutline(self->frame, TS_BACKGROUND); +} + +static void M_Control(UI_WINDOW *const self) +{ + if (self->root->control != nullptr) { + self->root->control(self->root); + } +} + +static void M_Draw(UI_WINDOW *const self) +{ + if (self->vtable.is_hidden) { + return; + } + Text_DrawText(self->frame); + if (self->root->draw != nullptr) { + self->root->draw(self->root); + } + if (self->title_label != nullptr) { + self->title_label->draw(self->title_label); + Text_DrawText(self->title_frame); + } +} + +static void M_Free(UI_WINDOW *const self) +{ + if (self->title_label != nullptr) { + self->title_label->free(self->title_label); + Text_Remove(self->title_frame); + } + Text_Remove(self->frame); + Memory_Free(self); +} + +UI_WIDGET *UI_Window_Create( + UI_WIDGET *const root, const int32_t border_top, const int32_t border_right, + const int32_t border_bottom, const int32_t border_left) +{ + UI_WINDOW *const self = Memory_Alloc(sizeof(UI_WINDOW)); + self->vtable = (UI_WIDGET_VTABLE) { + .get_width = (UI_WIDGET_GET_WIDTH)M_GetWidth, + .get_height = (UI_WIDGET_GET_HEIGHT)M_GetHeight, + .set_position = (UI_WIDGET_SET_POSITION)M_SetPosition, + .control = (UI_WIDGET_CONTROL)M_Control, + .draw = (UI_WIDGET_DRAW)M_Draw, + .free = (UI_WIDGET_FREE)M_Free, + }; + + self->root = root; + self->border.top = border_top; + self->border.right = border_right; + self->border.bottom = border_bottom; + self->border.left = border_left; + self->title_margin = 2; + self->title_padding = 2; + + self->frame = Text_Create(0, 0, ""); + self->frame->pos.z = 32; + self->frame->flags.manual_draw = 1; + + return (UI_WIDGET *)self; +} + +void UI_Window_SetTitle(UI_WIDGET *const widget, const char *const text) +{ + UI_WINDOW *const self = (UI_WINDOW *)widget; + if (self->title_label != nullptr) { + self->title_label->free(self->title_label); + self->title_label = nullptr; + Text_Remove(self->title_frame); + } + if (text != nullptr) { + self->title_label = + UI_Label_Create(text, UI_LABEL_AUTO_SIZE, UI_LABEL_AUTO_SIZE); + self->title_frame = Text_Create(0, 0, ""); + self->title_frame->pos.z = 16; + self->title_frame->flags.manual_draw = 1; + } +} + +void UI_Window_SetRootWidget(UI_WIDGET *const widget, UI_WIDGET *const root) +{ + UI_WINDOW *const self = (UI_WINDOW *)widget; + self->root = root; +} diff --git a/src/libtrx/gfx/3d/3d_renderer.c b/src/libtrx/gfx/3d/3d_renderer.c index 9388b390b..2e2d60cc7 100644 --- a/src/libtrx/gfx/3d/3d_renderer.c +++ b/src/libtrx/gfx/3d/3d_renderer.c @@ -22,6 +22,7 @@ struct GFX_3D_RENDERER { // shader variable locations GLint loc_mat_projection; + GLint loc_mat_model_view; GLint loc_texturing_enabled; GLint loc_smoothing_enabled; GLint loc_alpha_point_discard; @@ -141,6 +142,8 @@ GFX_3D_RENDERER *GFX_3D_Renderer_Create(void) renderer->loc_mat_projection = GFX_GL_Program_UniformLocation(&renderer->program, "matProjection"); + renderer->loc_mat_model_view = + GFX_GL_Program_UniformLocation(&renderer->program, "matModelView"); renderer->loc_texturing_enabled = GFX_GL_Program_UniformLocation(&renderer->program, "texturingEnabled"); renderer->loc_smoothing_enabled = @@ -154,6 +157,15 @@ GFX_3D_RENDERER *GFX_3D_Renderer_Create(void) GFX_GL_Program_Bind(&renderer->program); + GLfloat model_view[4][4] = { + { +1.0f, +0.0f, +0.0f, +0.0f }, + { +0.0f, +1.0f, +0.0f, +0.0f }, + { +0.0f, +0.0f, +1.0f, +0.0f }, + { +0.0f, +0.0f, +0.0f, +1.0f }, + }; + GFX_GL_Program_UniformMatrix4fv( + &renderer->program, renderer->loc_mat_model_view, 1, GL_FALSE, + &model_view[0][0]); GFX_GL_Program_Uniform1f( &renderer->program, renderer->loc_brightness_multiplier, 1.0); M_ApplyUniforms(renderer); @@ -177,30 +189,21 @@ void GFX_3D_Renderer_RenderBegin(GFX_3D_RENDERER *const renderer) { ASSERT(renderer != nullptr); + renderer->vertex_stream.rendered_count = 0; + renderer->vertex_stream.transferred = 0; + renderer->program.uniform_updates = 0; + GFX_GL_Program_Bind(&renderer->program); GFX_3D_VertexStream_Bind(&renderer->vertex_stream); GFX_GL_Sampler_Bind(&renderer->sampler, 0); M_RestoreTexture(renderer); M_ApplyUniforms(renderer); - GFX_3D_Renderer_SetProjectionMatrix(renderer); - - glDepthFunc(GL_LEQUAL); - glDepthMask(GL_TRUE); - glEnable(GL_DEPTH_TEST); - glEnable(GL_BLEND); - GFX_GL_CheckError(); -} - -void GFX_3D_Renderer_SetProjectionMatrix(GFX_3D_RENDERER *renderer) -{ - GFX_GL_Program_Bind(&renderer->program); const float left = 0.0f; const float top = 0.0f; const float right = GFX_Context_GetDisplayWidth(); const float bottom = GFX_Context_GetDisplayHeight(); - GLfloat projection[4][4] = { { 2.0f / (right - left), 0.0f, 0.0f, 0.0f }, { 0.0f, 2.0f / (top - bottom), 0.0f, 0.0f }, @@ -212,6 +215,12 @@ void GFX_3D_Renderer_SetProjectionMatrix(GFX_3D_RENDERER *renderer) GFX_GL_Program_UniformMatrix4fv( &renderer->program, renderer->loc_mat_projection, 1, GL_FALSE, &projection[0][0]); + + glDepthFunc(GL_LEQUAL); + glDepthMask(GL_TRUE); + glEnable(GL_DEPTH_TEST); + glEnable(GL_BLEND); + GFX_GL_CheckError(); } void GFX_3D_Renderer_Flush(GFX_3D_RENDERER *const renderer) @@ -224,6 +233,12 @@ void GFX_3D_Renderer_RenderEnd(GFX_3D_RENDERER *const renderer) { ASSERT(renderer != nullptr); M_Flush(renderer); +#ifdef DEBUG_OPTIM + LOG_DEBUG( + "vertices: %d, bytes: %d, uniforms: %d", + renderer->vertex_stream.rendered_count, + renderer->vertex_stream.transferred, renderer->program.uniform_updates); +#endif } void GFX_3D_Renderer_ClearDepth(GFX_3D_RENDERER *const renderer) diff --git a/src/libtrx/gfx/3d/vertex_stream.c b/src/libtrx/gfx/3d/vertex_stream.c index 93d7e9a90..5bdf91aa5 100644 --- a/src/libtrx/gfx/3d/vertex_stream.c +++ b/src/libtrx/gfx/3d/vertex_stream.c @@ -37,6 +37,8 @@ void GFX_3D_VertexStream_Init(GFX_3D_VERTEX_STREAM *const vertex_stream) vertex_stream->prim_type = GFX_3D_PRIM_TRI; vertex_stream->buffer_size = M_PREALLOC_VERTEX_COUNT * sizeof(GFX_3D_VERTEX); + vertex_stream->rendered_count = 0; + vertex_stream->transferred = 0; vertex_stream->pending_vertices.count = 0; vertex_stream->pending_vertices.capacity = M_PREALLOC_VERTEX_COUNT; vertex_stream->pending_vertices.data = Memory_Alloc( @@ -164,16 +166,19 @@ void GFX_3D_VertexStream_RenderPending( GFX_GL_Buffer_Data( &vertex_stream->buffer, buffer_size, nullptr, GL_STREAM_DRAW); vertex_stream->buffer_size = buffer_size; + vertex_stream->transferred += buffer_size; } GFX_GL_Buffer_SubData( &vertex_stream->buffer, 0, buffer_size, vertex_stream->pending_vertices.data); + vertex_stream->transferred += buffer_size; glDrawArrays( GL_PRIM_MODES[vertex_stream->prim_type], 0, vertex_stream->pending_vertices.count); GFX_GL_CheckError(); + vertex_stream->rendered_count += vertex_stream->pending_vertices.count; vertex_stream->pending_vertices.count = 0; } diff --git a/src/libtrx/gfx/context.c b/src/libtrx/gfx/context.c index aafdf8f05..68ab2695b 100644 --- a/src/libtrx/gfx/context.c +++ b/src/libtrx/gfx/context.c @@ -36,9 +36,7 @@ typedef struct { static GFX_CONTEXT m_Context = {}; static bool M_IsExtensionSupported(const char *name); -static GLvoid GLAPIENTRY M_GLDebug( - GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, - const GLchar *message, const void *user_param); +static void M_CheckExtensionSupport(const char *name); static bool M_IsExtensionSupported(const char *name) { @@ -58,15 +56,10 @@ static bool M_IsExtensionSupported(const char *name) return false; } -static GLvoid GLAPIENTRY M_GLDebug( - const GLenum source, const GLenum type, const GLuint id, - const GLenum severity, const GLsizei length, const GLchar *const message, - const void *const user_param) +static void M_CheckExtensionSupport(const char *name) { - if (severity == GL_DEBUG_SEVERITY_NOTIFICATION) { - return; - } - LOG_INFO("%d %s", source, message); + LOG_INFO( + "%s supported: %s", name, M_IsExtensionSupported(name) ? "yes" : "no"); } void GFX_Context_SwitchToWindowViewport(void) @@ -139,6 +132,12 @@ bool GFX_Context_Attach(void *window_handle, GFX_GL_BACKEND backend) "Can't activate OpenGL context: %s", SDL_GetError()); } + // Instruct GLEW to load non-Core Profile extensions with OpenGL 2.1, + // as we rely on `GL_ARB_explicit_attrib_location` and + // `GL_EXT_gpu_shader4`. + if (m_Context.config.backend == GFX_GL_21) { + glewExperimental = GL_TRUE; // Global state + } if (glewInit() != GLEW_OK) { Shell_ExitSystem("Can't initialize GLEW for OpenGL extension loading"); } @@ -154,17 +153,18 @@ bool GFX_Context_Attach(void *window_handle, GFX_GL_BACKEND backend) GFX_GL_CheckError(); } + // Check the availability of non-Core Profile extensions for OpenGL 2.1 + if (m_Context.config.backend == GFX_GL_21) { + M_CheckExtensionSupport("GL_ARB_explicit_attrib_location"); + M_CheckExtensionSupport("GL_EXT_gpu_shader4"); + } + glClearColor(0, 0, 0, 0); glClearDepth(1); GFX_GL_CheckError(); // VSync defaults to on unless user disabled it in runtime json SDL_GL_SetSwapInterval(1); - -#if DEBUG && !defined(__APPLE__) - glDebugMessageCallback(M_GLDebug, nullptr); - glEnable(GL_DEBUG_OUTPUT); -#endif return true; } @@ -277,16 +277,6 @@ void *GFX_Context_GetWindowHandle(void) return m_Context.window_handle; } -int32_t GFX_Context_GetWindowWidth(void) -{ - return m_Context.window_width; -} - -int32_t GFX_Context_GetWindowHeight(void) -{ - return m_Context.window_height; -} - int32_t GFX_Context_GetDisplayWidth(void) { return m_Context.display_width; diff --git a/src/libtrx/gfx/gl/buffer.c b/src/libtrx/gfx/gl/buffer.c index 7244a62b0..a0d2c1a84 100644 --- a/src/libtrx/gfx/gl/buffer.c +++ b/src/libtrx/gfx/gl/buffer.c @@ -1,7 +1,6 @@ #include "gfx/gl/buffer.h" #include "debug.h" -#include "gfx/gl/track.h" #include "gfx/gl/utils.h" void GFX_GL_Buffer_Init(GFX_GL_BUFFER *buf, GLenum target) @@ -36,7 +35,7 @@ void GFX_GL_Buffer_Data( { ASSERT(buf != nullptr); ASSERT(buf->initialized); - GFX_TRACK_DATA(glBufferData, buf->target, size, data, usage); + glBufferData(buf->target, size, data, usage); GFX_GL_CheckError(); } @@ -45,7 +44,7 @@ void GFX_GL_Buffer_SubData( { ASSERT(buf != nullptr); ASSERT(buf->initialized); - GFX_TRACK_SUBDATA(glBufferSubData, buf->target, offset, size, data); + glBufferSubData(buf->target, offset, size, data); GFX_GL_CheckError(); } diff --git a/src/libtrx/gfx/gl/program.c b/src/libtrx/gfx/gl/program.c index 71950aa77..e79b8421a 100644 --- a/src/libtrx/gfx/gl/program.c +++ b/src/libtrx/gfx/gl/program.c @@ -3,7 +3,6 @@ #include "debug.h" #include "filesystem.h" #include "game/shell.h" -#include "gfx/gl/track.h" #include "gfx/gl/utils.h" #include "log.h" #include "memory.h" @@ -32,7 +31,7 @@ void GFX_GL_Program_Close(GFX_GL_PROGRAM *program) } } -void GFX_GL_Program_Bind(const GFX_GL_PROGRAM *const program) +void GFX_GL_Program_Bind(GFX_GL_PROGRAM *program) { ASSERT(program != nullptr); glUseProgram(program->id); @@ -44,53 +43,45 @@ char *GFX_GL_Program_PreprocessShader( { ASSERT(content != nullptr); - char *common = nullptr; - File_Load("shaders/common.glsl", &common, nullptr); - const char *version_ogl21 = "#version 120\n" "#extension GL_ARB_explicit_attrib_location: enable\n" "#extension GL_EXT_gpu_shader4: enable\n"; const char *version_ogl33c = "#version 330 core\n"; const char *define_vertex = "#define VERTEX\n"; - const char *define_fragment = "#define FRAGMENT\n"; const char *define_ogl33c = "#define OGL33C\n"; size_t bufsize = strlen(content) + 1; - if (common != nullptr) { - bufsize += strlen(common); - } if (backend == GFX_GL_33C) { bufsize += strlen(version_ogl33c); bufsize += strlen(define_ogl33c); + } else { + bufsize += strlen(version_ogl21); } if (type == GL_VERTEX_SHADER) { bufsize += strlen(define_vertex); - } else if (type == GL_FRAGMENT_SHADER) { - bufsize += strlen(define_fragment); } char *processed_content = Memory_Alloc(bufsize); + if (!processed_content) { + return nullptr; + } + processed_content[0] = '\0'; + if (backend == GFX_GL_33C) { strcpy(processed_content, version_ogl33c); strcat(processed_content, define_ogl33c); - } - - if (common != nullptr) { - strcat(processed_content, common); + } else { + strcpy(processed_content, version_ogl21); } if (type == GL_VERTEX_SHADER) { strcat(processed_content, define_vertex); - } else if (type == GL_FRAGMENT_SHADER) { - strcat(processed_content, define_fragment); } strcat(processed_content, content); - - Memory_FreePointer(&common); return processed_content; } @@ -198,8 +189,9 @@ void GFX_GL_Program_Uniform3f( GFX_GL_PROGRAM *program, GLint loc, GLfloat v0, GLfloat v1, GLfloat v2) { ASSERT(program != nullptr); - GFX_TRACK_UNIFORM(glUniform3f, loc, v0, v1, v2); + glUniform3f(loc, v0, v1, v2); GFX_GL_CheckError(); + program->uniform_updates++; } void GFX_GL_Program_Uniform4f( @@ -207,30 +199,25 @@ void GFX_GL_Program_Uniform4f( GLfloat v3) { ASSERT(program != nullptr); - GFX_TRACK_UNIFORM(glUniform4f, loc, v0, v1, v2, v3); + glUniform4f(loc, v0, v1, v2, v3); GFX_GL_CheckError(); + program->uniform_updates++; } void GFX_GL_Program_Uniform1i(GFX_GL_PROGRAM *program, GLint loc, GLint v0) { ASSERT(program != nullptr); - GFX_TRACK_UNIFORM(glUniform1i, loc, v0); + glUniform1i(loc, v0); GFX_GL_CheckError(); + program->uniform_updates++; } void GFX_GL_Program_Uniform1f(GFX_GL_PROGRAM *program, GLint loc, GLfloat v0) { ASSERT(program != nullptr); - GFX_TRACK_UNIFORM(glUniform1f, loc, v0); - GFX_GL_CheckError(); -} - -void GFX_GL_Program_Uniform2f( - GFX_GL_PROGRAM *program, GLint loc, GLfloat v0, GLfloat v1) -{ - ASSERT(program != nullptr); - GFX_TRACK_UNIFORM(glUniform2f, loc, v0, v1); + glUniform1f(loc, v0); GFX_GL_CheckError(); + program->uniform_updates++; } void GFX_GL_Program_UniformMatrix4fv( @@ -238,6 +225,7 @@ void GFX_GL_Program_UniformMatrix4fv( const GLfloat *value) { ASSERT(program != nullptr); - GFX_TRACK_UNIFORM(glUniformMatrix4fv, loc, count, transpose, value); + glUniformMatrix4fv(loc, count, transpose, value); GFX_GL_CheckError(); + program->uniform_updates++; } diff --git a/src/libtrx/gfx/gl/track.c b/src/libtrx/gfx/gl/track.c deleted file mode 100644 index 272e90de8..000000000 --- a/src/libtrx/gfx/gl/track.c +++ /dev/null @@ -1,13 +0,0 @@ -#include "gfx/gl/track.h" - -GFX_METRICS g_GFX_Metrics; - -void GFX_Track_Reset(void) -{ - g_GFX_Metrics = (GFX_METRICS) { 0 }; -} - -GFX_METRICS GFX_Track_GetMetrics(void) -{ - return g_GFX_Metrics; -} diff --git a/src/libtrx/gfx/gl/vertex_array.c b/src/libtrx/gfx/gl/vertex_array.c index 7914173ef..ba1d2c042 100644 --- a/src/libtrx/gfx/gl/vertex_array.c +++ b/src/libtrx/gfx/gl/vertex_array.c @@ -44,16 +44,3 @@ void GFX_GL_VertexArray_Attribute( index, size, type, normalized, stride, (void *)(intptr_t)offset); GFX_GL_CheckError(); } - -void GFX_GL_VertexArray_IAttribute( - GFX_GL_VERTEX_ARRAY *array, GLuint index, GLint size, GLenum type, - GLsizei stride, GLsizei offset) -{ - ASSERT(array != nullptr); - ASSERT(array->initialized); - glEnableVertexAttribArray(index); - GFX_GL_CheckError(); - - glVertexAttribIPointer(index, size, type, stride, (void *)(intptr_t)offset); - GFX_GL_CheckError(); -} diff --git a/src/libtrx/gfx/renderers/legacy_renderer.c b/src/libtrx/gfx/renderers/legacy_renderer.c index 4553465c4..14c4a71e3 100644 --- a/src/libtrx/gfx/renderers/legacy_renderer.c +++ b/src/libtrx/gfx/renderers/legacy_renderer.c @@ -8,8 +8,6 @@ #include static void M_SwapBuffers(GFX_RENDERER *renderer); -static void M_GetScale( - const GFX_RENDERER *renderer, float *out_scale_x, float *out_scale_y); static void M_SwapBuffers(GFX_RENDERER *renderer) { diff --git a/src/libtrx/include/libtrx/colors.h b/src/libtrx/include/libtrx/colors.h deleted file mode 100644 index b52af4227..000000000 --- a/src/libtrx/include/libtrx/colors.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include - -typedef struct { - float r; - float g; - float b; -} RGB_F; - -typedef struct { - uint8_t r; - uint8_t g; - uint8_t b; -} RGB_888; - -typedef struct { - uint8_t r; - uint8_t g; - uint8_t b; - uint8_t a; -} RGBA_8888; diff --git a/src/libtrx/include/libtrx/config/const.h b/src/libtrx/include/libtrx/config/const.h deleted file mode 100644 index a10d9c40e..000000000 --- a/src/libtrx/include/libtrx/config/const.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -#define MAX_ASSAULT_TIMES 10 diff --git a/src/libtrx/include/libtrx/config/option.h b/src/libtrx/include/libtrx/config/option.h index c5a32928c..12d3c461b 100644 --- a/src/libtrx/include/libtrx/config/option.h +++ b/src/libtrx/include/libtrx/config/option.h @@ -1,12 +1,11 @@ #pragma once typedef enum { - COT_BOOL, - COT_INT32, - COT_FLOAT, - COT_DOUBLE, - COT_ENUM, - COT_RGB888, + COT_BOOL = 0, + COT_INT32 = 1, + COT_FLOAT = 2, + COT_DOUBLE = 3, + COT_ENUM = 4, } CONFIG_OPTION_TYPE; typedef struct { diff --git a/src/libtrx/include/libtrx/config/types.h b/src/libtrx/include/libtrx/config/types.h index a10346e43..185abd51c 100644 --- a/src/libtrx/include/libtrx/config/types.h +++ b/src/libtrx/include/libtrx/config/types.h @@ -1,28 +1,5 @@ #pragma once -#include "./const.h" - -#include - -typedef enum { - MUSIC_LOAD_NEVER, - MUSIC_LOAD_NON_AMBIENT, - MUSIC_LOAD_ALWAYS, -} MUSIC_LOAD_CONDITION; - -typedef enum { - UI_STYLE_PS1, - UI_STYLE_PC, -} UI_STYLE; - -typedef struct { - struct { - uint32_t time; - uint32_t attempt_num; - } entries[MAX_ASSAULT_TIMES]; - uint32_t total_attempts; -} ASSAULT_STATS; - #if TR_VERSION == 1 #include "./types_tr1.h" #elif TR_VERSION == 2 diff --git a/src/libtrx/include/libtrx/config/types_tr1.h b/src/libtrx/include/libtrx/config/types_tr1.h index 81a463c41..8194aad53 100644 --- a/src/libtrx/include/libtrx/config/types_tr1.h +++ b/src/libtrx/include/libtrx/config/types_tr1.h @@ -1,11 +1,11 @@ #pragma once -#include "../game/gym.h" -#include "../game/output/types.h" #include "../game/sound/enum.h" #include "../gfx/common.h" #include "../screenshot.h" +#include + #define CONFIG_MIN_BRIGHTNESS 0.1f #define CONFIG_MAX_BRIGHTNESS 2.0f #define CONFIG_MIN_TEXT_SCALE 0.5 @@ -46,6 +46,12 @@ typedef enum { BC_PURPLE, } BAR_COLOR; +typedef enum { + MUSIC_LOAD_NEVER, + MUSIC_LOAD_NON_AMBIENT, + MUSIC_LOAD_ALWAYS, +} MUSIC_LOAD_CONDITION; + typedef enum { TLM_FULL, TLM_SEMI, @@ -53,10 +59,9 @@ typedef enum { } TARGET_LOCK_MODE; typedef enum { - SDM_MINIMAL, - SDM_DETAILED, - SDM_FULL, -} STAT_DETAIL_MODE; + UI_STYLE_PS1, + UI_STYLE_PC, +} UI_STYLE; typedef struct { bool loaded; @@ -88,16 +93,11 @@ typedef struct { bool fix_animated_sprites; bool fix_texture_issues; bool enable_ps1_crystals; - - RGB_888 water_color; - int32_t fog_start; - int32_t fog_end; } visuals; struct { bool enable_game_ui; bool enable_photo_mode_ui; - bool enable_wraparound; double text_scale; double bar_scale; UI_STYLE menu_style; @@ -141,14 +141,14 @@ typedef struct { bool enable_cheats; bool enable_console; bool enable_fmv; - bool enable_legal; bool enable_compass_stats; bool enable_total_stats; bool enable_timer_in_inventory; bool enable_demo; + bool enable_eidos_logo; bool enable_loading_screens; - bool enable_cutscenes; - STAT_DETAIL_MODE stat_detail_mode; + bool enable_cine; + bool enable_detailed_stats; bool enable_walk_to_items; bool enable_enhanced_saves; bool enable_jump_twists; @@ -210,11 +210,11 @@ typedef struct { bool enable_vsync; bool enable_fps_counter; float anisotropy_filter; + bool pretty_pixels; SCREENSHOT_FORMAT screenshot_format; } rendering; struct { bool new_game_plus_unlock; - ASSAULT_STATS assault_stats; } profile; } CONFIG; diff --git a/src/libtrx/include/libtrx/config/types_tr2.h b/src/libtrx/include/libtrx/config/types_tr2.h index ebdfe073b..0de245fe9 100644 --- a/src/libtrx/include/libtrx/config/types_tr2.h +++ b/src/libtrx/include/libtrx/config/types_tr2.h @@ -1,12 +1,12 @@ #pragma once -#include "../colors.h" -#include "../game/gym.h" #include "../game/input.h" #include "../game/sound/enum.h" #include "../gfx/common.h" #include "../screenshot.h" +#include + typedef enum { LIGHTING_CONTRAST_LOW, LIGHTING_CONTRAST_MEDIUM, @@ -48,18 +48,12 @@ typedef struct { bool enable_fade_effects; bool enable_exit_fade_effects; bool fix_item_rots; - bool fix_texture_issues; int32_t fov; - bool use_psx_fov; - - RGB_888 water_color; - int32_t fog_start; - int32_t fog_end; + bool use_pcx_fov; } visuals; struct { bool enable_photo_mode_ui; - bool enable_wraparound; double text_scale; double bar_scale; } ui; @@ -69,7 +63,6 @@ typedef struct { int32_t music_volume; bool enable_lara_mic; UNDERWATER_MUSIC_MODE underwater_music_mode; - MUSIC_LOAD_CONDITION music_load_condition; } audio; struct { @@ -82,16 +75,11 @@ typedef struct { bool fix_floor_data_issues; bool fix_flare_throw_priority; bool fix_walk_run_jump; - bool fix_bear_ai; - bool fix_bridge_collision; bool enable_cheats; bool enable_console; bool enable_fmv; - bool enable_legal; - bool enable_cutscenes; bool enable_auto_item_selection; int32_t turbo_speed; - bool enable_game_modes; } gameplay; struct { @@ -121,9 +109,4 @@ typedef struct { int32_t scaler; float sizer; } rendering; - - struct { - bool new_game_plus_unlock; - ASSAULT_STATS assault_stats; - } profile; } CONFIG; diff --git a/src/libtrx/include/libtrx/enum_map.h b/src/libtrx/include/libtrx/enum_map.h index 12382e49f..d0fecff85 100644 --- a/src/libtrx/include/libtrx/enum_map.h +++ b/src/libtrx/include/libtrx/enum_map.h @@ -1,4 +1,4 @@ -#include "vector.h" +#include #define ENUM_MAP_DEFINE(enum_name, enum_value, str_value) \ EnumMap_Define(ENUM_MAP_NAME(enum_name), enum_value, str_value); @@ -18,13 +18,6 @@ extern void EnumMap_Init(void); void EnumMap_Shutdown(void); -// Returns a vector of valid string values for the given enum_name. -// -// The returned vector must be freed via Vector_Free(). The string pointers -// within the vector are owned by the enum map and should not be freed by the -// caller. Returns nullptr if the enum_name is not valid. -VECTOR *EnumMap_ListValues(const char *enum_name); - void EnumMap_Define( const char *enum_name, int32_t enum_value, const char *str_value); int32_t EnumMap_Get( diff --git a/src/libtrx/include/libtrx/filesystem.h b/src/libtrx/include/libtrx/filesystem.h index 584166af6..20a789db5 100644 --- a/src/libtrx/include/libtrx/filesystem.h +++ b/src/libtrx/include/libtrx/filesystem.h @@ -39,8 +39,8 @@ char *File_GuessExtension(const char *path, const char **extensions); MYFILE *File_Open(const char *path, FILE_OPEN_MODE mode); -bool File_ReadData(MYFILE *file, void *data, size_t size); -bool File_ReadItems(MYFILE *file, void *data, size_t count, size_t item_size); +void File_ReadData(MYFILE *file, void *data, size_t size); +void File_ReadItems(MYFILE *file, void *data, size_t count, size_t item_size); int8_t File_ReadS8(MYFILE *file); int16_t File_ReadS16(MYFILE *file); int32_t File_ReadS32(MYFILE *file); diff --git a/src/libtrx/include/libtrx/game/anims/common.h b/src/libtrx/include/libtrx/game/anims/common.h index 103ea87ba..07e9e7ac8 100644 --- a/src/libtrx/include/libtrx/game/anims/common.h +++ b/src/libtrx/include/libtrx/game/anims/common.h @@ -2,8 +2,6 @@ #include "./types.h" -#define NO_ANIM (-1) - void Anim_InitialiseAnims(int32_t num_anims); void Anim_InitialiseChanges(int32_t num_changes); void Anim_InitialiseRanges(int32_t num_ranges); diff --git a/src/libtrx/include/libtrx/game/anims/types.h b/src/libtrx/include/libtrx/game/anims/types.h index 1fa49f180..c80e665ab 100644 --- a/src/libtrx/include/libtrx/game/anims/types.h +++ b/src/libtrx/include/libtrx/game/anims/types.h @@ -35,7 +35,9 @@ typedef struct { typedef struct { bool matrix_pop; bool matrix_push; - XYZ_BOOL rot; + bool rot_x; + bool rot_y; + bool rot_z; XYZ_32 pos; } ANIM_BONE; diff --git a/src/libtrx/include/libtrx/game/console.h b/src/libtrx/include/libtrx/game/console.h deleted file mode 100644 index c07b4c616..000000000 --- a/src/libtrx/include/libtrx/game/console.h +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once - -#include "console/common.h" -#include "console/history.h" diff --git a/src/libtrx/include/libtrx/game/console/common.h b/src/libtrx/include/libtrx/game/console/common.h index 263291a24..b870cb5ff 100644 --- a/src/libtrx/include/libtrx/game/console/common.h +++ b/src/libtrx/include/libtrx/game/console/common.h @@ -11,9 +11,12 @@ void Console_Open(void); void Console_Close(void); bool Console_IsOpened(void); +void Console_ScrollLogs(void); +int32_t Console_GetVisibleLogCount(void); +int32_t Console_GetMaxLogCount(void); + void Console_Log(const char *fmt, ...); COMMAND_RESULT Console_Eval(const char *cmdline); -void Console_Control(void); void Console_Draw(void); extern void Console_DrawBackdrop(void); diff --git a/src/libtrx/include/libtrx/game/const.h b/src/libtrx/include/libtrx/game/const.h index 2928947b5..5b82588fd 100644 --- a/src/libtrx/include/libtrx/game/const.h +++ b/src/libtrx/include/libtrx/game/const.h @@ -14,5 +14,4 @@ #define GRAVITY 6 #define FAST_FALL_SPEED 128 -#define MAX_STATIC_OBJECTS_2D 50 -#define MAX_STATIC_OBJECTS_3D 256 +#define MAX_STATIC_OBJECTS 50 diff --git a/src/libtrx/include/libtrx/game/creature.h b/src/libtrx/include/libtrx/game/creature.h index 06e47896a..0b6c593e1 100644 --- a/src/libtrx/include/libtrx/game/creature.h +++ b/src/libtrx/include/libtrx/game/creature.h @@ -1,6 +1,34 @@ #pragma once -#include "./creature/common.h" -#include "./creature/const.h" -#include "./creature/enum.h" -#include "./creature/types.h" +#include "items.h" +#include "math.h" +#include "pathing.h" + +#include + +typedef enum { + MOOD_BORED = 0, + MOOD_ATTACK = 1, + MOOD_ESCAPE = 2, + MOOD_STALK = 3, +} MOOD_TYPE; + +typedef struct { + int16_t head_rotation; + int16_t neck_rotation; + int16_t maximum_turn; + int16_t flags; + int16_t item_num; + MOOD_TYPE mood; + LOT_INFO lot; + XYZ_32 target; +#if TR_VERSION == 2 + ITEM *enemy; +#endif +} CREATURE; + +#define CREATURE_STALK_DIST (3 * WALL_L) // = 3072 +#define CREATURE_ESCAPE_DIST (5 * WALL_L) // = 5120 +#define CREATURE_TARGET_DIST (4 * WALL_L) // = 4096 + +bool Creature_IsHostile(const ITEM *item); diff --git a/src/libtrx/include/libtrx/game/creature/common.h b/src/libtrx/include/libtrx/game/creature/common.h deleted file mode 100644 index 9d9270839..000000000 --- a/src/libtrx/include/libtrx/game/creature/common.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include "../collision.h" -#include "./types.h" - -void Creature_Initialise(int16_t item_num); -bool Creature_Activate(int16_t item_num); -void Creature_AIInfo(ITEM *item, AI_INFO *info); -bool Creature_EnsureHabitat( - int16_t item_num, int32_t *wh, const HYBRID_INFO *info); -void Creature_Mood(const ITEM *item, const AI_INFO *info, bool violent); - -int16_t Creature_Turn(ITEM *item, int16_t max_turn); -void Creature_Tilt(ITEM *item, int16_t angle); -void Creature_Head(ITEM *item, int16_t required); -void Creature_Neck(ITEM *item, int16_t required); - -void Creature_Float(int16_t item_num); -void Creature_Underwater(ITEM *item, int32_t depth); - -bool Creature_CanTargetEnemy(const ITEM *item, const AI_INFO *info); -bool Creature_CheckBaddieOverlap(int16_t item_num); -void Creature_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); -bool Creature_Animate(int16_t item_num, int16_t angle, int16_t tilt); - -void Creature_SpecialKill( - ITEM *item, int32_t kill_anim, int32_t kill_state, int32_t lara_kill_state); -void Creature_Die(int16_t item_num, bool explode); -int32_t Creature_Vault( - int16_t item_num, int16_t angle, int32_t vault, int32_t shift); - -bool Creature_AreAlliesHostile(void); -void Creature_SetAlliesHostile(bool enable); -bool Creature_IsHostile(const ITEM *item); -bool Creature_IsAlly(const ITEM *item); - -int16_t Creature_Effect( - const ITEM *item, const BITE *bite, - int16_t (*spawn)( - int32_t x, int32_t y, int32_t z, int16_t speed, int16_t y_rot, - int16_t room_num)); diff --git a/src/libtrx/include/libtrx/game/creature/const.h b/src/libtrx/include/libtrx/game/creature/const.h deleted file mode 100644 index c9052d0ff..000000000 --- a/src/libtrx/include/libtrx/game/creature/const.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include "../../utils.h" - -#define FRONT_ARC DEG_90 -#define UNIT_SHADOW 256 - -#define CREATURE_STALK_DIST (3 * WALL_L) // = 3072 -#define CREATURE_ESCAPE_DIST (5 * WALL_L) // = 5120 -#define CREATURE_TARGET_DIST (4 * WALL_L) // = 4096 - -#define CREATURE_MISS_CHANCE 0x2000 -#if TR_VERSION >= 2 - #define CREATURE_SHOOT_RANGE SQUARE(WALL_L * 8) // = 0x4000000 = 67108864 -#else - #define CREATURE_SHOOT_RANGE SQUARE(WALL_L * 7) // = 51380224 -#endif diff --git a/src/libtrx/include/libtrx/game/creature/enum.h b/src/libtrx/include/libtrx/game/creature/enum.h deleted file mode 100644 index 71ef482ea..000000000 --- a/src/libtrx/include/libtrx/game/creature/enum.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -typedef enum { - MOOD_BORED = 0, - MOOD_ATTACK = 1, - MOOD_ESCAPE = 2, - MOOD_STALK = 3, -} MOOD_TYPE; diff --git a/src/libtrx/include/libtrx/game/creature/types.h b/src/libtrx/include/libtrx/game/creature/types.h deleted file mode 100644 index 097b1241c..000000000 --- a/src/libtrx/include/libtrx/game/creature/types.h +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once - -#include "../items.h" -#include "../pathing/types.h" -#include "./enum.h" - -typedef struct { - int16_t head_rotation; - int16_t neck_rotation; - int16_t maximum_turn; - int16_t flags; - int16_t item_num; - MOOD_TYPE mood; - LOT_INFO lot; - XYZ_32 target; - ITEM *enemy; -} CREATURE; - -typedef struct { - int16_t zone_num; - // TODO: merge - union { - int16_t enemy_zone; - int16_t enemy_zone_num; - }; - int32_t distance; - int32_t ahead; - int32_t bite; - int16_t angle; - int16_t enemy_facing; -} AI_INFO; - -typedef struct { - XYZ_32 pos; - int32_t mesh_num; -} BITE; - -typedef struct { - struct { - GAME_OBJECT_ID id; - int16_t active_anim; - int16_t death_anim; - int16_t death_state; - } land; - struct { - GAME_OBJECT_ID id; - int16_t active_anim; - } water; -} HYBRID_INFO; diff --git a/src/libtrx/include/libtrx/game/effects/const.h b/src/libtrx/include/libtrx/game/effects/const.h index a5501b2be..5abc401fd 100644 --- a/src/libtrx/include/libtrx/game/effects/const.h +++ b/src/libtrx/include/libtrx/game/effects/const.h @@ -1,4 +1,3 @@ #pragma once #define NO_EFFECT (-1) -#define MAX_EFFECTS 1000 diff --git a/src/libtrx/include/libtrx/game/enum_map.def b/src/libtrx/include/libtrx/game/enum_map.def index 2811c315f..48b3ac199 100644 --- a/src/libtrx/include/libtrx/game/enum_map.def +++ b/src/libtrx/include/libtrx/game/enum_map.def @@ -50,10 +50,7 @@ ENUM_MAP_DEFINE(GAME_BUFFER, GBUF_CREATURE_DATA, "Creature data") ENUM_MAP_DEFINE(GAME_BUFFER, GBUF_CREATURE_LOT, "Creature pathfinding") ENUM_MAP_DEFINE(GAME_BUFFER, GBUF_SAMPLE_INFOS, "Sample information") ENUM_MAP_DEFINE(GAME_BUFFER, GBUF_SAMPLES, "Samples") - -ENUM_MAP_DEFINE(MUSIC_LOAD_CONDITION, MUSIC_LOAD_NEVER, "never") -ENUM_MAP_DEFINE(MUSIC_LOAD_CONDITION, MUSIC_LOAD_NON_AMBIENT, "non-ambient") -ENUM_MAP_DEFINE(MUSIC_LOAD_CONDITION, MUSIC_LOAD_ALWAYS, "always") +ENUM_MAP_DEFINE(GAME_BUFFER, GBUF_VERTEX_BUFFER, "Vertex buffer") ENUM_MAP_DEFINE(UNDERWATER_MUSIC_MODE, UMM_FULL, "full") ENUM_MAP_DEFINE(UNDERWATER_MUSIC_MODE, UMM_QUIET, "quiet") @@ -90,6 +87,7 @@ ENUM_MAP_DEFINE(GF_SEQUENCE_EVENT_TYPE, GFS_SETUP_BACON_LARA, "setup_bacon_lara #elif TR_VERSION == 2 ENUM_MAP_DEFINE(GF_SEQUENCE_EVENT_TYPE, GFS_REMOVE_FLARES, "remove_flares") ENUM_MAP_DEFINE(GF_SEQUENCE_EVENT_TYPE, GFS_ENABLE_SUNSET, "enable_sunset") +ENUM_MAP_DEFINE(GF_SEQUENCE_EVENT_TYPE, GFS_SET_NUM_SECRETS, "set_secret_count") ENUM_MAP_DEFINE(GF_SEQUENCE_EVENT_TYPE, GFS_SET_START_ANIM, "set_lara_start_anim") ENUM_MAP_DEFINE(GF_SEQUENCE_EVENT_TYPE, GFS_ADD_SECRET_REWARD, "add_secret_reward") #endif diff --git a/src/libtrx/include/libtrx/game/game.h b/src/libtrx/include/libtrx/game/game.h index 81e986a67..6eaa7234f 100644 --- a/src/libtrx/include/libtrx/game/game.h +++ b/src/libtrx/include/libtrx/game/game.h @@ -1,6 +1,5 @@ #pragma once -#include "./game/enum.h" #include "./game_flow/types.h" void Game_SetIsPlaying(bool is_playing); @@ -12,10 +11,6 @@ void Game_SetCurrentLevel(const GF_LEVEL *level); bool Game_IsInGym(void); bool Game_IsPlayable(void); -GAME_BONUS_FLAG Game_GetBonusFlag(void); -void Game_SetBonusFlag(GAME_BONUS_FLAG flag); -bool Game_IsBonusFlagSet(GAME_BONUS_FLAG flag); - extern bool Game_Start(const GF_LEVEL *level, GF_SEQUENCE_CONTEXT seq_ctx); extern void Game_End(void); extern void Game_Suspend(void); diff --git a/src/libtrx/include/libtrx/game/game/enum.h b/src/libtrx/include/libtrx/game/game/enum.h deleted file mode 100644 index b47a83286..000000000 --- a/src/libtrx/include/libtrx/game/game/enum.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -typedef enum { - // clang-format off - GBF_NONE = 0, - GBF_NGPLUS = 1 << 0, - GBF_JAPANESE = 1 << 1, - // clang=format on -} GAME_BONUS_FLAG; diff --git a/src/libtrx/include/libtrx/game/game_buf.h b/src/libtrx/include/libtrx/game/game_buf.h index 8cc9b14af..38b0b3f55 100644 --- a/src/libtrx/include/libtrx/game/game_buf.h +++ b/src/libtrx/include/libtrx/game/game_buf.h @@ -47,6 +47,7 @@ typedef enum { GBUF_SAMPLE_INFOS, GBUF_SAMPLES, GBUF_DEMO_BUFFER, + GBUF_VERTEX_BUFFER, GBUF_NUM_MALLOC_TYPES, // clang-format on } GAME_BUFFER; diff --git a/src/libtrx/include/libtrx/game/game_flow/common.h b/src/libtrx/include/libtrx/game/game_flow/common.h index d63a1302b..3077dc803 100644 --- a/src/libtrx/include/libtrx/game/game_flow/common.h +++ b/src/libtrx/include/libtrx/game/game_flow/common.h @@ -22,7 +22,3 @@ const GF_LEVEL *GF_GetLevelBefore(const GF_LEVEL *level); void GF_SetCurrentLevel(const GF_LEVEL *level); void GF_SetLevelTitle(GF_LEVEL *level, const char *title); - -// Returns true if any story cutscenes or FMVs occur before gameplay in any -// main level up to the level in the specified save slot. -bool GF_HasAvailableStory(int32_t slot_num); diff --git a/src/libtrx/include/libtrx/game/game_flow/enum.h b/src/libtrx/include/libtrx/game/game_flow/enum.h index ad7693122..4c51c5963 100644 --- a/src/libtrx/include/libtrx/game/game_flow/enum.h +++ b/src/libtrx/include/libtrx/game/game_flow/enum.h @@ -82,6 +82,7 @@ typedef enum { #elif TR_VERSION == 2 GFS_REMOVE_FLARES, GFS_SET_START_ANIM, + GFS_SET_NUM_SECRETS, GFS_ENABLE_SUNSET, GFS_ADD_SECRET_REWARD, #endif diff --git a/src/libtrx/include/libtrx/game/game_flow/types.h b/src/libtrx/include/libtrx/game/game_flow/types.h index cb4412b1b..1524ab571 100644 --- a/src/libtrx/include/libtrx/game/game_flow/types.h +++ b/src/libtrx/include/libtrx/game/game_flow/types.h @@ -29,7 +29,6 @@ typedef struct { typedef struct { char *path; - bool is_legal; float display_time; float fade_in_time; float fade_out_time; @@ -76,19 +75,14 @@ typedef struct { typedef struct { const char *path; - bool is_legal; } GF_FMV; typedef struct { - struct { - bool is_present; - float value; - } fog_start, fog_end; - struct { - bool is_present; - RGB_888 value; - } water_color; -#if TR_VERSION == 2 +#if TR_VERSION == 1 + RGB_F water_color; + float draw_distance_fade; + float draw_distance_max; +#elif TR_VERSION == 2 char *sfx_path; #endif } GF_LEVEL_SETTINGS; @@ -148,13 +142,13 @@ typedef struct { GF_FMV *fmvs; }; +#if TR_VERSION == 1 // savegame settings struct { char *savegame_fmt_legacy; char *savegame_fmt_bson; }; -#if TR_VERSION == 1 // global settings struct { float demo_delay; @@ -174,6 +168,11 @@ typedef struct { GF_COMMAND cmd_demo_end; }; + // savegame settings + struct { + char *savegame_fmt_legacy; + }; + // global settings struct { float demo_delay; diff --git a/src/libtrx/include/libtrx/game/game_string.def b/src/libtrx/include/libtrx/game/game_string.def index 7ce161412..c6fc6f3ff 100644 --- a/src/libtrx/include/libtrx/game/game_string.def +++ b/src/libtrx/include/libtrx/game/game_string.def @@ -34,10 +34,6 @@ GS_DEFINE(OSD_PLAY_MUSIC_TRACK, "Playing music track %d") GS_DEFINE(OSD_INVALID_MUSIC_TRACK, "Invalid music track") GS_DEFINE(OSD_UNKNOWN_COMMAND, "Unknown command: %s") GS_DEFINE(OSD_COMMAND_BAD_INVOCATION, "Invalid invocation: %s") -GS_DEFINE(OSD_COMMAND_VALID_VALUES, "Valid values: %s") -GS_DEFINE(OSD_COMMAND_BOOL, "on, off") -GS_DEFINE(OSD_COMMAND_INTEGER, "[integer]") -GS_DEFINE(OSD_COMMAND_DECIMAL, "[decimal]") GS_DEFINE(OSD_COMMAND_UNAVAILABLE, "This command is not currently available") GS_DEFINE(OSD_INVALID_ROOM, "Invalid room: %d. Valid rooms are 0-%d") GS_DEFINE(OSD_POS_SET_POS, "Teleported to position: %.3f %.3f %.3f") @@ -65,13 +61,13 @@ GS_DEFINE(OSD_AMBIGUOUS_INPUT_2, "Ambiguous input: %s and %s") GS_DEFINE(OSD_AMBIGUOUS_INPUT_3, "Ambiguous input: %s, %s, ...") GS_DEFINE(OSD_UI_ON, "UI enabled") GS_DEFINE(OSD_UI_OFF, "UI disabled") -GS_DEFINE(CONTROLS_DEFAULT_KEYS, "Default Keys") -GS_DEFINE(CONTROLS_CUSTOM_1, "User Keys 1") -GS_DEFINE(CONTROLS_CUSTOM_2, "User Keys 2") -GS_DEFINE(CONTROLS_CUSTOM_3, "User Keys 3") -GS_DEFINE(CONTROLS_BACKEND_KEYBOARD, "Keyboard") -GS_DEFINE(CONTROLS_BACKEND_CONTROLLER, "Controller") -GS_DEFINE(CONTROLS_CUSTOMIZE, "Customize Controls") +GS_DEFINE(CONTROL_DEFAULT_KEYS, "Default Keys") +GS_DEFINE(CONTROL_CUSTOM_1, "User Keys 1") +GS_DEFINE(CONTROL_CUSTOM_2, "User Keys 2") +GS_DEFINE(CONTROL_CUSTOM_3, "User Keys 3") +GS_DEFINE(CONTROL_BACKEND_KEYBOARD, "Keyboard") +GS_DEFINE(CONTROL_BACKEND_CONTROLLER, "Controller") +GS_DEFINE(CONTROL_CUSTOMIZE, "Customize Controls") GS_DEFINE(KEYMAP_RUN, "Run") GS_DEFINE(KEYMAP_BACK, "Back") GS_DEFINE(KEYMAP_LEFT, "Left") @@ -128,34 +124,6 @@ GS_DEFINE(PASSPORT_SAVE_GAME, "Save Game") GS_DEFINE(PASSPORT_NEW_GAME, "New Game") GS_DEFINE(PASSPORT_EXIT_GAME, "Exit Game") GS_DEFINE(PASSPORT_EXIT_TO_TITLE, "Exit to Title") -GS_DEFINE(PASSPORT_SELECT_MODE, "Select Mode") -GS_DEFINE(PASSPORT_MODE_NEW_GAME, "New Game") -GS_DEFINE(PASSPORT_MODE_NEW_GAME_PLUS, "New Game+") -GS_DEFINE(PASSPORT_MODE_NEW_GAME_JP, "Japanese NG") -GS_DEFINE(PASSPORT_MODE_NEW_GAME_JP_PLUS, "Japanese NG+") -GS_DEFINE(PASSPORT_STORY_SO_FAR, "Story so far...") -GS_DEFINE(PASSPORT_LEGACY_SELECT_LEVEL_1, "Legacy saves do not") -GS_DEFINE(PASSPORT_LEGACY_SELECT_LEVEL_2, "support this feature.") -GS_DEFINE(SOUND_DIALOG_TITLE, "Set Volumes") -GS_DEFINE(SOUND_DIALOG_SOUND, "\\{icon music} Music") -GS_DEFINE(SOUND_DIALOG_MUSIC, "\\{icon sound} Sound") +GS_DEFINE(SOUND_SET_VOLUMES, "Set Volumes") GS_DEFINE(OSD_TRAPEZOID_FILTER_ON, "Trapezoid filter enabled") GS_DEFINE(OSD_TRAPEZOID_FILTER_OFF, "Trapezoid filter disabled") -GS_DEFINE(DETAIL_INTEGER_FMT, "%d") -GS_DEFINE(DETAIL_FLOAT_FMT, "%.1f") -GS_DEFINE(DETAIL_TITLE, "Graphic Options") -GS_DEFINE(DETAIL_FPS, "FPS") -GS_DEFINE(DETAIL_FOG_START, "Fog start") -GS_DEFINE(DETAIL_FOG_END, "Fog end") -GS_DEFINE(DETAIL_WATER_COLOR_R, "Water color (R)") -GS_DEFINE(DETAIL_WATER_COLOR_G, "Water color (G)") -GS_DEFINE(DETAIL_WATER_COLOR_B, "Water color (B)") -GS_DEFINE(DETAIL_TEXTURE_FILTER, "Texture filter") -GS_DEFINE(DETAIL_BILINEAR, "Bilinear") -GS_DEFINE(DETAIL_TRAPEZOID_FILTER, "Trapezoid filter") -GS_DEFINE(DETAIL_RENDER_MODE, "Render mode") -GS_DEFINE(DETAIL_UI_TEXT_SCALE, "UI text scale") -GS_DEFINE(DETAIL_UI_BAR_SCALE, "UI bar scale") -GS_DEFINE(DETAIL_UI_SCROLL_WRAPAROUND, "UI scroll wrap") -GS_DEFINE(PAGINATION_NAV, "%d / %d") -GS_DEFINE(MISC_EMPTY_SLOT_FMT, "- EMPTY SLOT -") diff --git a/src/libtrx/include/libtrx/game/game_string_table.h b/src/libtrx/include/libtrx/game/game_string_table.h index cc33f3fe4..b14a99c97 100644 --- a/src/libtrx/include/libtrx/game/game_string_table.h +++ b/src/libtrx/include/libtrx/game/game_string_table.h @@ -2,8 +2,7 @@ #include -void GameStringTable_Init(void); -void GameStringTable_Shutdown(void); - -void GameStringTable_Load(const char *path, bool load_levels); +void GameStringTable_LoadFromFile(const char *path); +void GameStringTable_LoadFromString(const char *data); void GameStringTable_Apply(const GF_LEVEL *level); +void GameStringTable_Shutdown(void); diff --git a/src/libtrx/include/libtrx/game/gun.h b/src/libtrx/include/libtrx/game/gun.h index 6cca02787..a9b31b98c 100644 --- a/src/libtrx/include/libtrx/game/gun.h +++ b/src/libtrx/include/libtrx/game/gun.h @@ -1,5 +1,3 @@ #pragma once -#include "./gun/const.h" - void Gun_AddDynamicLight(void); diff --git a/src/libtrx/include/libtrx/game/gun/const.h b/src/libtrx/include/libtrx/game/gun/const.h deleted file mode 100644 index ca4340222..000000000 --- a/src/libtrx/include/libtrx/game/gun/const.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#define SHOTGUN_PELLET_SCATTER (DEG_1 * 20) // = 3640 - -#define GUN_AMMO_CLIP 16 -#define GUN_AMMO_QTY (GUN_AMMO_CLIP * 2) // = 32 - -#define SHOTGUN_SHELL_COUNT 2 -#define SHOTGUN_AMMO_CLIP 6 -#define SHOTGUN_AMMO_QTY (SHOTGUN_AMMO_CLIP * SHOTGUN_SHELL_COUNT) // = 12 - -#if TR_VERSION == 1 - #define MAGNUM_AMMO_CLIP 25 - #define MAGNUM_AMMO_QTY (MAGNUM_AMMO_CLIP * 2) // = 50 - - #define UZI_AMMO_CLIP 50 - #define UZI_AMMO_QTY (UZI_AMMO_CLIP * 2) // = 100 -#elif TR_VERSION == 2 - #define MAGNUM_AMMO_CLIP 20 - #define MAGNUM_AMMO_QTY (MAGNUM_AMMO_CLIP * 2) // = 40 - - #define UZI_AMMO_CLIP 40 - #define UZI_AMMO_QTY (UZI_AMMO_CLIP * 2) // = 80 - - #define M16_AMMO_CLIP 40 - #define M16_AMMO_QTY M16_AMMO_CLIP // = 40 - - #define HARPOON_AMMO_CLIP 3 - #define HARPOON_AMMO_QTY HARPOON_AMMO_CLIP // = 3 - #define HARPOON_BOLT_SPEED 150 - #define HARPOON_RECOIL 4 - - #define GRENADE_AMMO_CLIP 1 - #define GRENADE_AMMO_QTY (GRENADE_AMMO_CLIP * 2) // = 2 - #define GRENADE_SPEED 200 - - #define FLARE_AMMO_QTY 6 - #define FLARE_AMMO_JAPANESE_QTY 8 -#endif diff --git a/src/libtrx/include/libtrx/game/gym.h b/src/libtrx/include/libtrx/game/gym.h deleted file mode 100644 index 2a6140693..000000000 --- a/src/libtrx/include/libtrx/game/gym.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include "../config/types.h" - -#include - -extern bool Gym_IsAccessible(void); -void Gym_SetInventoryOpenEnabled(bool enabled); -bool Gym_IsInventoryOpenEnabled(void); - -bool Gym_HasAssaultStats(void); -bool Gym_IsAssaultTimerDisplay(void); -bool Gym_IsAssaultTimerActive(void); -ASSAULT_STATS Gym_GetAssaultStats(void); -void Gym_SetAssaultStats(ASSAULT_STATS stats); - -void Gym_ResetAssault(void); -void Gym_StartAssault(void); -void Gym_StopAssault(void); -void Gym_FinishAssault(void); diff --git a/src/libtrx/include/libtrx/game/inject/enum.h b/src/libtrx/include/libtrx/game/inject/enum.h index 5590d0aca..7c6ee589f 100644 --- a/src/libtrx/include/libtrx/game/inject/enum.h +++ b/src/libtrx/include/libtrx/game/inject/enum.h @@ -66,8 +66,7 @@ typedef enum { IDT_CAMERA_EDITS = 24, IDT_FRAME_EDITS = 25, IDT_OBJECT_3D_EDITS = 26, - IDT_ANIM_CMD_EDITS = 27, - IDT_NUMBER_OF = 28, + IDT_NUMBER_OF = 27, } INJECTION_DATA_TYPE; typedef enum { diff --git a/src/libtrx/include/libtrx/game/input/common.h b/src/libtrx/include/libtrx/game/input/common.h index 01ad55caa..9e1ec9b8a 100644 --- a/src/libtrx/include/libtrx/game/input/common.h +++ b/src/libtrx/include/libtrx/game/input/common.h @@ -101,5 +101,3 @@ bool Input_AssignToJSONObject( INPUT_ROLE role); INPUT_STATE Input_GetDebounced(const INPUT_STATE input); - -extern const char *Input_GetRoleName(INPUT_ROLE role); diff --git a/src/libtrx/include/libtrx/game/inventory.h b/src/libtrx/include/libtrx/game/inventory.h index 1d8a42c8a..06e21eb48 100644 --- a/src/libtrx/include/libtrx/game/inventory.h +++ b/src/libtrx/include/libtrx/game/inventory.h @@ -6,8 +6,6 @@ #include -extern INVENTORY_MODE g_Inv_Mode; - bool Inv_AddItemNTimes(GAME_OBJECT_ID obj_id, int32_t qty); void Inv_AddAmmo(AMMO_INFO *weapon_ammo, int32_t qty); GAME_OBJECT_ID Inv_GetItemOption(GAME_OBJECT_ID obj_id); diff --git a/src/libtrx/include/libtrx/game/items/common.h b/src/libtrx/include/libtrx/game/items/common.h index 20cbad1d4..fc1f45921 100644 --- a/src/libtrx/include/libtrx/game/items/common.h +++ b/src/libtrx/include/libtrx/game/items/common.h @@ -50,8 +50,3 @@ void Item_Translate(ITEM *item, int32_t x, int32_t y, int32_t z); void Item_Animate(ITEM *item); void Item_PlayAnimSFX(const ITEM *item, const ANIM_COMMAND_EFFECT_DATA *data); - -bool Item_TestBoundsCollide( - const ITEM *src_item, const ITEM *dst_item, int32_t radius); -extern const BOUNDS_16 *Item_GetBoundsAccurate(const ITEM *item); -bool Item_IsTriggerActive(ITEM *item); diff --git a/src/libtrx/include/libtrx/game/items/const.h b/src/libtrx/include/libtrx/game/items/const.h index 20c0c2978..fb1431048 100644 --- a/src/libtrx/include/libtrx/game/items/const.h +++ b/src/libtrx/include/libtrx/game/items/const.h @@ -1,4 +1,8 @@ #pragma once #define NO_ITEM (-1) -#define MAX_ITEMS 10240 +#if TR_VERSION == 1 + #define MAX_ITEMS 10240 +#else + #define MAX_ITEMS 256 +#endif diff --git a/src/libtrx/include/libtrx/game/lara/cheat.h b/src/libtrx/include/libtrx/game/lara/cheat.h index a1f31ab29..4973cf864 100644 --- a/src/libtrx/include/libtrx/game/lara/cheat.h +++ b/src/libtrx/include/libtrx/game/lara/cheat.h @@ -9,5 +9,4 @@ extern bool Lara_Cheat_EnterFlyMode(void); extern bool Lara_Cheat_ExitFlyMode(void); extern bool Lara_Cheat_KillEnemy(int16_t item_num); extern void Lara_Cheat_EndLevel(void); -extern bool Lara_Cheat_Teleport( - int32_t x, int32_t y, int32_t z, int16_t room_num); +extern bool Lara_Cheat_Teleport(int32_t x, int32_t y, int32_t z); diff --git a/src/libtrx/include/libtrx/game/lara/common.h b/src/libtrx/include/libtrx/game/lara/common.h index 82e2f7357..e8c3b56d0 100644 --- a/src/libtrx/include/libtrx/game/lara/common.h +++ b/src/libtrx/include/libtrx/game/lara/common.h @@ -1,6 +1,5 @@ #pragma once -#include "../collision.h" #include "../items.h" #include "types.h" @@ -11,9 +10,3 @@ void Lara_SwapSingleMesh(LARA_MESH mesh, GAME_OBJECT_ID obj_id); OBJECT_MESH *Lara_GetMesh(LARA_MESH mesh); void Lara_SetMesh(LARA_MESH mesh, OBJECT_MESH *mesh_ptr); const ANIM_FRAME *Lara_GetHitFrame(const ITEM *item); -void Lara_TakeDamage(int16_t damage, bool hit_status); - -bool Lara_TestBoundsCollide(const ITEM *item, int32_t radius); -void Lara_Push(const ITEM *item, COLL_INFO *coll, bool hit_on, bool big_push); -bool Lara_TestPosition(const ITEM *item, const OBJECT_BOUNDS *bounds); -void Lara_AlignPosition(const ITEM *item, const XYZ_32 *vec); diff --git a/src/libtrx/include/libtrx/game/lara/misc.h b/src/libtrx/include/libtrx/game/lara/misc.h index 051374e7c..16dc3b960 100644 --- a/src/libtrx/include/libtrx/game/lara/misc.h +++ b/src/libtrx/include/libtrx/game/lara/misc.h @@ -7,4 +7,3 @@ void Lara_Extinguish(void); int16_t Lara_FloorFront(const ITEM *item, int16_t ang, int32_t dist); void Lara_GetCollisionInfo(const ITEM *item, COLL_INFO *coll); -extern void Lara_CatchFire(void); diff --git a/src/libtrx/include/libtrx/game/lara/types.h b/src/libtrx/include/libtrx/game/lara/types.h index 581386dd0..e23b874ba 100644 --- a/src/libtrx/include/libtrx/game/lara/types.h +++ b/src/libtrx/include/libtrx/game/lara/types.h @@ -46,7 +46,6 @@ typedef struct { int16_t death_timer; int16_t current_active; int32_t water_surface_dist; - XYZ_32 last_pos; int16_t hit_effect_count; EFFECT *hit_effect; int32_t mesh_effects; @@ -59,10 +58,10 @@ typedef struct { XYZ_16 torso_rot; LARA_ARM left_arm; LARA_ARM right_arm; - AMMO_INFO pistol_ammo; - AMMO_INFO magnum_ammo; - AMMO_INFO uzi_ammo; - AMMO_INFO shotgun_ammo; + AMMO_INFO pistols; + AMMO_INFO magnums; + AMMO_INFO uzis; + AMMO_INFO shotgun; LOT_INFO lot; struct { int32_t item_num; diff --git a/src/libtrx/include/libtrx/game/level.h b/src/libtrx/include/libtrx/game/level.h index 6d1109cab..9173213d0 100644 --- a/src/libtrx/include/libtrx/game/level.h +++ b/src/libtrx/include/libtrx/game/level.h @@ -1,5 +1,4 @@ #pragma once #include "level/common.h" -#include "level/settings.h" #include "level/types.h" diff --git a/src/libtrx/include/libtrx/game/level/settings.h b/src/libtrx/include/libtrx/game/level/settings.h deleted file mode 100644 index bd2f424d4..000000000 --- a/src/libtrx/include/libtrx/game/level/settings.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include "../../colors.h" - -extern RGB_888 Level_GetWaterColor(void); -extern float Level_GetFogStart(void); -extern float Level_GetFogEnd(void); diff --git a/src/libtrx/include/libtrx/game/los.h b/src/libtrx/include/libtrx/game/los.h deleted file mode 100644 index 91b83d02d..000000000 --- a/src/libtrx/include/libtrx/game/los.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#include "./types.h" - -bool LOS_Check(const GAME_VECTOR *start, GAME_VECTOR *target); diff --git a/src/libtrx/include/libtrx/game/math/types.h b/src/libtrx/include/libtrx/game/math/types.h index 3374c04d1..29a479678 100644 --- a/src/libtrx/include/libtrx/game/math/types.h +++ b/src/libtrx/include/libtrx/game/math/types.h @@ -26,12 +26,6 @@ typedef struct { int16_t z; } XYZ_16; -typedef struct { - bool x; - bool y; - bool z; -} XYZ_BOOL; - typedef struct { float x, y, z; } XYZ_F; diff --git a/src/libtrx/include/libtrx/game/music/common.h b/src/libtrx/include/libtrx/game/music/common.h index 1e6ee1f85..68c3c3838 100644 --- a/src/libtrx/include/libtrx/game/music/common.h +++ b/src/libtrx/include/libtrx/game/music/common.h @@ -31,15 +31,3 @@ extern void Music_Unpause(void); void Music_ResetTrackFlags(void); uint16_t Music_GetTrackFlags(int32_t track_idx); void Music_SetTrackFlags(int32_t track, uint16_t flags); - -// Gets the minimum possible game volume. -extern int32_t Music_GetMinVolume(void); - -// Gets the maximum possible game volume. -extern int32_t Music_GetMaxVolume(void); - -// Gets the game volume. -extern int32_t Music_GetVolume(void); - -// Sets the game volume. -extern void Music_SetVolume(int32_t volume); diff --git a/src/libtrx/include/libtrx/game/objects/common.h b/src/libtrx/include/libtrx/game/objects/common.h index 18de887cd..ceeee3db0 100644 --- a/src/libtrx/include/libtrx/game/objects/common.h +++ b/src/libtrx/include/libtrx/game/objects/common.h @@ -7,7 +7,6 @@ #include "ids.h" #include "types.h" -void Object_Reset(void); OBJECT *Object_Get(GAME_OBJECT_ID obj_id); STATIC_OBJECT_3D *Object_Get3DStatic(int32_t static_id); STATIC_OBJECT_2D *Object_Get2DStatic(int32_t static_id); @@ -25,7 +24,6 @@ void Object_StoreMesh(OBJECT_MESH *mesh); int32_t Object_GetMeshCount(void); OBJECT_MESH *Object_FindMesh(int32_t data_offset); -int32_t Object_GetMeshIndex(const OBJECT_MESH *mesh); int32_t Object_GetMeshOffset(const OBJECT_MESH *mesh); void Object_SetMeshOffset(OBJECT_MESH *mesh, int32_t data_offset); @@ -36,16 +34,12 @@ void Object_SwapMesh( ANIM *Object_GetAnim(const OBJECT *obj, int32_t anim_idx); ANIM_BONE *Object_GetBone(const OBJECT *obj, int32_t bone_idx); -extern void Object_DrawUnclippedItem(const ITEM *item); extern void Object_DrawMesh(int32_t mesh_idx, int32_t clip, bool interpolated); void Object_DrawInterpolatedObject( const OBJECT *obj, uint32_t meshes, const int16_t *extra_rotation, const ANIM_FRAME *frame1, const ANIM_FRAME *frame2, int32_t frac, int32_t rate); -void Object_ApplyExtraRotation( - const int16_t **extra_rotation, const XYZ_BOOL rot_flags, - bool interpolated); #define REGISTER_OBJECT(object_id, setup_func_) \ __attribute__((constructor)) static void M_RegisterObject##object_id(void) \ diff --git a/src/libtrx/include/libtrx/game/objects/creatures/bear.h b/src/libtrx/include/libtrx/game/objects/creatures/bear.h deleted file mode 100644 index 54ae50bf2..000000000 --- a/src/libtrx/include/libtrx/game/objects/creatures/bear.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#include "../types.h" - -void Bear_Setup(OBJECT *obj); diff --git a/src/libtrx/include/libtrx/game/objects/creatures/wolf.h b/src/libtrx/include/libtrx/game/objects/creatures/wolf.h deleted file mode 100644 index 8ce381ab6..000000000 --- a/src/libtrx/include/libtrx/game/objects/creatures/wolf.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#include "../types.h" - -void Wolf_Setup(OBJECT *obj); diff --git a/src/libtrx/include/libtrx/game/objects/general/door.h b/src/libtrx/include/libtrx/game/objects/general/door.h deleted file mode 100644 index 845c171cd..000000000 --- a/src/libtrx/include/libtrx/game/objects/general/door.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include "../../collision.h" - -typedef enum { - DOOR_STATE_CLOSED = 0, - DOOR_STATE_OPEN = 1, -} DOOR_STATE; - -extern void Door_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); diff --git a/src/libtrx/include/libtrx/game/objects/ids_tr1.def b/src/libtrx/include/libtrx/game/objects/ids_tr1.def index 32f07eff2..4186e2d56 100644 --- a/src/libtrx/include/libtrx/game/objects/ids_tr1.def +++ b/src/libtrx/include/libtrx/game/objects/ids_tr1.def @@ -63,9 +63,9 @@ OBJ_ID_DEFINE(O_DOOR_TYPE_5, 61) OBJ_ID_DEFINE(O_DOOR_TYPE_6, 62) OBJ_ID_DEFINE(O_DOOR_TYPE_7, 63) OBJ_ID_DEFINE(O_DOOR_TYPE_8, 64) -OBJ_ID_DEFINE(O_TRAPDOOR_TYPE_1, 65) -OBJ_ID_DEFINE(O_TRAPDOOR_TYPE_2, 66) -OBJ_ID_DEFINE(O_TRAPDOOR_TYPE_3, 67) +OBJ_ID_DEFINE(O_TRAPDOOR_1, 65) +OBJ_ID_DEFINE(O_TRAPDOOR_2, 66) +OBJ_ID_DEFINE(O_BIGTRAPDOOR, 67) OBJ_ID_DEFINE(O_BRIDGE_FLAT, 68) OBJ_ID_DEFINE(O_BRIDGE_TILT_1, 69) OBJ_ID_DEFINE(O_BRIDGE_TILT_2, 70) @@ -87,12 +87,12 @@ OBJ_ID_DEFINE(O_SHOTGUN_ITEM, 85) OBJ_ID_DEFINE(O_MAGNUM_ITEM, 86) OBJ_ID_DEFINE(O_UZI_ITEM, 87) OBJ_ID_DEFINE(O_PISTOL_AMMO_ITEM, 88) -OBJ_ID_DEFINE(O_SHOTGUN_AMMO_ITEM, 89) -OBJ_ID_DEFINE(O_MAGNUM_AMMO_ITEM, 90) +OBJ_ID_DEFINE(O_SG_AMMO_ITEM, 89) +OBJ_ID_DEFINE(O_MAG_AMMO_ITEM, 90) OBJ_ID_DEFINE(O_UZI_AMMO_ITEM, 91) OBJ_ID_DEFINE(O_EXPLOSIVE_ITEM, 92) -OBJ_ID_DEFINE(O_SMALL_MEDIPACK_ITEM, 93) -OBJ_ID_DEFINE(O_LARGE_MEDIPACK_ITEM, 94) +OBJ_ID_DEFINE(O_MEDI_ITEM, 93) +OBJ_ID_DEFINE(O_BIGMEDI_ITEM, 94) OBJ_ID_DEFINE(O_DETAIL_OPTION, 95) OBJ_ID_DEFINE(O_SOUND_OPTION, 96) OBJ_ID_DEFINE(O_CONTROL_OPTION, 97) @@ -102,12 +102,12 @@ OBJ_ID_DEFINE(O_SHOTGUN_OPTION, 100) OBJ_ID_DEFINE(O_MAGNUM_OPTION, 101) OBJ_ID_DEFINE(O_UZI_OPTION, 102) OBJ_ID_DEFINE(O_PISTOL_AMMO_OPTION, 103) -OBJ_ID_DEFINE(O_SHOTGUN_AMMO_OPTION, 104) -OBJ_ID_DEFINE(O_MAGNUM_AMMO_OPTION, 105) +OBJ_ID_DEFINE(O_SG_AMMO_OPTION, 104) +OBJ_ID_DEFINE(O_MAG_AMMO_OPTION, 105) OBJ_ID_DEFINE(O_UZI_AMMO_OPTION, 106) OBJ_ID_DEFINE(O_EXPLOSIVE_OPTION, 107) -OBJ_ID_DEFINE(O_SMALL_MEDIPACK_OPTION, 108) -OBJ_ID_DEFINE(O_LARGE_MEDIPACK_OPTION, 109) +OBJ_ID_DEFINE(O_MEDI_OPTION, 108) +OBJ_ID_DEFINE(O_BIGMEDI_OPTION, 109) OBJ_ID_DEFINE(O_PUZZLE_ITEM_1, 110) OBJ_ID_DEFINE(O_PUZZLE_ITEM_2, 111) OBJ_ID_DEFINE(O_PUZZLE_ITEM_3, 112) diff --git a/src/libtrx/include/libtrx/game/objects/ids_tr2.def b/src/libtrx/include/libtrx/game/objects/ids_tr2.def index 766ec2852..cf9ad0998 100644 --- a/src/libtrx/include/libtrx/game/objects/ids_tr2.def +++ b/src/libtrx/include/libtrx/game/objects/ids_tr2.def @@ -263,17 +263,3 @@ OBJ_ID_DEFINE(O_ASSAULT_DIGITS, 261) OBJ_ID_DEFINE(O_FINAL_LEVEL_COUNTER, 262) OBJ_ID_DEFINE(O_CUT_SHOTGUN, 263) OBJ_ID_DEFINE(O_EARTHQUAKE, 264) - -// Conflicting objects definitions for TR2: The Golden Mask. In a situation -// when two different objects share the same IDs, the objects register -// themselves as usual through the C constructor mechanism, and fight for the -// slot, which is okay. The conflict is supposed to be resolved by an external -// party – in terms of the Golden Mask, this currently happens in Shell_Main(), -// that manually calls the setup routine overriding whatever information was in -// the slot. -OBJ_ID_DEFINE(O_MONK_3, O_MONK_1) -OBJ_ID_DEFINE(O_BEAR, O_BIG_SPIDER) -OBJ_ID_DEFINE(O_WOLF, O_SPIDER) - -// Force the O_NUMBER_OF to be valid -OBJ_ID_DEFINE(O_LAST, 264) diff --git a/src/libtrx/include/libtrx/game/objects/names_tr1.def b/src/libtrx/include/libtrx/game/objects/names_tr1.def index 4d9edc8d1..786cd737f 100644 --- a/src/libtrx/include/libtrx/game/objects/names_tr1.def +++ b/src/libtrx/include/libtrx/game/objects/names_tr1.def @@ -1,13 +1,13 @@ // pickups on the ground -OBJ_NAME_DEFINE(O_SMALL_MEDIPACK_ITEM, "small_medipack", "Small Medi Pack") -OBJ_NAME_DEFINE(O_LARGE_MEDIPACK_ITEM, "large_medipack", "Large Medi Pack") +OBJ_NAME_DEFINE(O_MEDI_ITEM, "small_medipack", "Small Medi Pack") +OBJ_NAME_DEFINE(O_BIGMEDI_ITEM, "large_medipack", "Large Medi Pack") OBJ_NAME_DEFINE(O_PISTOL_ITEM, "pistols", "Pistols") OBJ_NAME_DEFINE(O_SHOTGUN_ITEM, "shotgun", "Shotgun") OBJ_NAME_DEFINE(O_MAGNUM_ITEM, "magnums", "Magnums") OBJ_NAME_DEFINE(O_UZI_ITEM, "uzis", "Uzis") OBJ_NAME_DEFINE(O_PISTOL_AMMO_ITEM, "pistol_ammo", "Pistol Clips") -OBJ_NAME_DEFINE(O_SHOTGUN_AMMO_ITEM, "shotgun_ammo", "Shotgun Shells") -OBJ_NAME_DEFINE(O_MAGNUM_AMMO_ITEM, "magnums_ammo", "Magnum Clips") +OBJ_NAME_DEFINE(O_SG_AMMO_ITEM, "shotgun_ammo", "Shotgun Shells") +OBJ_NAME_DEFINE(O_MAG_AMMO_ITEM, "magnums_ammo", "Magnum Clips") OBJ_NAME_DEFINE(O_UZI_AMMO_ITEM, "uzis_ammo", "Uzi Clips") OBJ_NAME_DEFINE(O_EXPLOSIVE_ITEM, "grenade", "Grenade") OBJ_NAME_DEFINE(O_KEY_ITEM_1, "key_1", "Key 1") @@ -36,30 +36,30 @@ OBJ_NAME_DEFINE(O_SOUND_OPTION, "sound", "Sound") OBJ_NAME_DEFINE(O_GAMMA_OPTION, "gamma", "Gamma") OBJ_NAME_DEFINE(O_MAP_CLOSED, "map", "Map") -OBJ_ALIAS_DEFINE(O_PISTOL_OPTION, O_PISTOL_ITEM) -OBJ_ALIAS_DEFINE(O_PISTOL_AMMO_OPTION, O_PISTOL_ITEM) -OBJ_ALIAS_DEFINE(O_SHOTGUN_OPTION, O_SHOTGUN_ITEM) -OBJ_ALIAS_DEFINE(O_MAGNUM_OPTION, O_MAGNUM_ITEM) -OBJ_ALIAS_DEFINE(O_UZI_OPTION, O_UZI_ITEM) -OBJ_ALIAS_DEFINE(O_SHOTGUN_AMMO_OPTION, O_SHOTGUN_AMMO_ITEM) -OBJ_ALIAS_DEFINE(O_MAGNUM_AMMO_OPTION, O_MAGNUM_AMMO_ITEM) -OBJ_ALIAS_DEFINE(O_UZI_AMMO_OPTION, O_UZI_AMMO_ITEM) -OBJ_ALIAS_DEFINE(O_EXPLOSIVE_OPTION, O_EXPLOSIVE_ITEM) -OBJ_ALIAS_DEFINE(O_SMALL_MEDIPACK_OPTION, O_SMALL_MEDIPACK_ITEM) -OBJ_ALIAS_DEFINE(O_LARGE_MEDIPACK_OPTION, O_LARGE_MEDIPACK_ITEM) -OBJ_ALIAS_DEFINE(O_PUZZLE_OPTION_1, O_PUZZLE_ITEM_1) -OBJ_ALIAS_DEFINE(O_PUZZLE_OPTION_2, O_PUZZLE_ITEM_2) -OBJ_ALIAS_DEFINE(O_PUZZLE_OPTION_3, O_PUZZLE_ITEM_3) -OBJ_ALIAS_DEFINE(O_PUZZLE_OPTION_4, O_PUZZLE_ITEM_4) -OBJ_ALIAS_DEFINE(O_KEY_OPTION_1, O_KEY_ITEM_1) -OBJ_ALIAS_DEFINE(O_KEY_OPTION_2, O_KEY_ITEM_2) -OBJ_ALIAS_DEFINE(O_KEY_OPTION_3, O_KEY_ITEM_3) -OBJ_ALIAS_DEFINE(O_KEY_OPTION_4, O_KEY_ITEM_4) -OBJ_ALIAS_DEFINE(O_PICKUP_OPTION_1, O_PICKUP_ITEM_1) -OBJ_ALIAS_DEFINE(O_PICKUP_OPTION_2, O_PICKUP_ITEM_2) -OBJ_ALIAS_DEFINE(O_LEADBAR_OPTION, O_LEADBAR_ITEM) -OBJ_ALIAS_DEFINE(O_SCION_OPTION, O_SCION_ITEM_1) -OBJ_ALIAS_DEFINE(O_PASSPORT_CLOSED, O_PASSPORT_OPTION) +OBJ_ALIAS_DEFINE(O_PISTOL_OPTION, O_PISTOL_ITEM) +OBJ_ALIAS_DEFINE(O_PISTOL_AMMO_OPTION, O_PISTOL_ITEM) +OBJ_ALIAS_DEFINE(O_SHOTGUN_OPTION, O_SHOTGUN_ITEM) +OBJ_ALIAS_DEFINE(O_MAGNUM_OPTION, O_MAGNUM_ITEM) +OBJ_ALIAS_DEFINE(O_UZI_OPTION, O_UZI_ITEM) +OBJ_ALIAS_DEFINE(O_SG_AMMO_OPTION, O_SG_AMMO_ITEM) +OBJ_ALIAS_DEFINE(O_MAG_AMMO_OPTION, O_MAG_AMMO_ITEM) +OBJ_ALIAS_DEFINE(O_UZI_AMMO_OPTION, O_UZI_AMMO_ITEM) +OBJ_ALIAS_DEFINE(O_EXPLOSIVE_OPTION, O_EXPLOSIVE_ITEM) +OBJ_ALIAS_DEFINE(O_MEDI_OPTION, O_MEDI_ITEM) +OBJ_ALIAS_DEFINE(O_BIGMEDI_OPTION, O_BIGMEDI_ITEM) +OBJ_ALIAS_DEFINE(O_PUZZLE_OPTION_1, O_PUZZLE_ITEM_1) +OBJ_ALIAS_DEFINE(O_PUZZLE_OPTION_2, O_PUZZLE_ITEM_2) +OBJ_ALIAS_DEFINE(O_PUZZLE_OPTION_3, O_PUZZLE_ITEM_3) +OBJ_ALIAS_DEFINE(O_PUZZLE_OPTION_4, O_PUZZLE_ITEM_4) +OBJ_ALIAS_DEFINE(O_KEY_OPTION_1, O_KEY_ITEM_1) +OBJ_ALIAS_DEFINE(O_KEY_OPTION_2, O_KEY_ITEM_2) +OBJ_ALIAS_DEFINE(O_KEY_OPTION_3, O_KEY_ITEM_3) +OBJ_ALIAS_DEFINE(O_KEY_OPTION_4, O_KEY_ITEM_4) +OBJ_ALIAS_DEFINE(O_PICKUP_OPTION_1, O_PICKUP_ITEM_1) +OBJ_ALIAS_DEFINE(O_PICKUP_OPTION_2, O_PICKUP_ITEM_2) +OBJ_ALIAS_DEFINE(O_LEADBAR_OPTION, O_LEADBAR_ITEM) +OBJ_ALIAS_DEFINE(O_SCION_OPTION, O_SCION_ITEM_1) +OBJ_ALIAS_DEFINE(O_PASSPORT_CLOSED, O_PASSPORT_OPTION) // general objects OBJ_NAME_DEFINE(O_LARA, "lara", "Lara") @@ -127,9 +127,9 @@ OBJ_NAME_DEFINE(O_DOOR_TYPE_5, "door_5", "Door 5") OBJ_NAME_DEFINE(O_DOOR_TYPE_6, "door_6", "Door 6") OBJ_NAME_DEFINE(O_DOOR_TYPE_7, "door_7", "Door 7") OBJ_NAME_DEFINE(O_DOOR_TYPE_8, "door_8", "Door 8") -OBJ_NAME_DEFINE(O_TRAPDOOR_TYPE_1, "trapdoor_1", "Trapdoor 1") -OBJ_NAME_DEFINE(O_TRAPDOOR_TYPE_2, "trapdoor_2", "Trapdoor 2") -OBJ_NAME_DEFINE(O_TRAPDOOR_TYPE_3, "trapdoor_3", "Trapdoor 3") +OBJ_NAME_DEFINE(O_TRAPDOOR_1, "trapdoor_1", "Trapdoor 1") +OBJ_NAME_DEFINE(O_TRAPDOOR_2, "trapdoor_2", "Trapdoor 2") +OBJ_NAME_DEFINE(O_BIGTRAPDOOR, "big_trapdoor", "Big Trapdoor") OBJ_NAME_DEFINE(O_BRIDGE_FLAT, "bridge_flat", "Bridge Flat") OBJ_NAME_DEFINE(O_BRIDGE_TILT_1, "bridge_tilt_1", "Bridge Tilt 1") OBJ_NAME_DEFINE(O_BRIDGE_TILT_2, "bridge_tilt_2", "Bridge Tilt 2") diff --git a/src/libtrx/include/libtrx/game/objects/traps/movable_block.h b/src/libtrx/include/libtrx/game/objects/traps/movable_block.h index 4f1815955..bdafcfbca 100644 --- a/src/libtrx/include/libtrx/game/objects/traps/movable_block.h +++ b/src/libtrx/include/libtrx/game/objects/traps/movable_block.h @@ -3,6 +3,5 @@ #include "../../rooms.h" void MovableBlock_Initialise(int16_t item_num); -void MovableBlock_UpdateRotation(ITEM *item, int16_t rot_y); void MovableBlock_SetupFloor(void); void MovableBlock_HandleFlipMap(ROOM_FLIP_STATUS flip_status); diff --git a/src/libtrx/include/libtrx/game/objects/types.h b/src/libtrx/include/libtrx/game/objects/types.h index c22f3c549..27d0c555b 100644 --- a/src/libtrx/include/libtrx/game/objects/types.h +++ b/src/libtrx/include/libtrx/game/objects/types.h @@ -4,7 +4,7 @@ #include "../collision.h" #include "../items/types.h" #include "../rooms/enum.h" -#include "../savegame/enum.h" +#include "../savegame.h" #include "../types.h" #include @@ -33,17 +33,17 @@ typedef struct { FACE3 *tex_face3s; FACE4 *flat_face4s; FACE3 *flat_face3s; - bool enable_reflections; - bool disable_lighting; } OBJECT_MESH; +#if TR_VERSION == 1 typedef struct { struct { XYZ_16 min; XYZ_16 max; } shift, rot; } OBJECT_BOUNDS; +#endif typedef struct OBJECT { int16_t mesh_count; @@ -64,8 +64,8 @@ typedef struct OBJECT { void (*activate_func)(ITEM *item); void (*handle_flip_func)(ITEM *item, ROOM_FLIP_STATUS flip_status); void (*handle_save_func)(ITEM *item, SAVEGAME_STAGE stage); - const OBJECT_BOUNDS *(*bounds_func)(void); #if TR_VERSION == 1 + const OBJECT_BOUNDS *(*bounds_func)(void); bool (*is_usable_func)(int16_t item_num); #endif @@ -76,7 +76,6 @@ typedef struct OBJECT { int16_t shadow_size; int16_t smartness; bool enable_interpolation; - XYZ_BOOL base_rot; union { uint16_t flags; diff --git a/src/libtrx/include/libtrx/game/objects/vars.h b/src/libtrx/include/libtrx/game/objects/vars.h index 85d5df60c..88c3d555a 100644 --- a/src/libtrx/include/libtrx/game/objects/vars.h +++ b/src/libtrx/include/libtrx/game/objects/vars.h @@ -10,7 +10,6 @@ extern const GAME_OBJECT_ID g_PickupObjects[]; extern const GAME_OBJECT_ID g_AnimObjects[]; extern const GAME_OBJECT_ID g_NullObjects[]; extern const GAME_OBJECT_ID g_InvObjects[]; -extern const GAME_OBJECT_ID g_WaterSpriteObjects[]; extern const GAME_OBJECT_PAIR g_ItemToInvObjectMap[]; extern const GAME_OBJECT_PAIR g_KeyItemToReceptacleMap[]; diff --git a/src/libtrx/include/libtrx/game/output.h b/src/libtrx/include/libtrx/game/output.h index 4322b2d0a..a8aea7e30 100644 --- a/src/libtrx/include/libtrx/game/output.h +++ b/src/libtrx/include/libtrx/game/output.h @@ -1,8 +1,6 @@ #pragma once -#include "./output/background.h" #include "./output/common.h" #include "./output/const.h" -#include "./output/draw.h" #include "./output/textures.h" #include "./output/types.h" diff --git a/src/libtrx/include/libtrx/game/output/background.h b/src/libtrx/include/libtrx/game/output/background.h deleted file mode 100644 index 7c028ec32..000000000 --- a/src/libtrx/include/libtrx/game/output/background.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include "../../engine/image.h" - -bool Output_LoadBackgroundFromFile(const char *path); -void Output_ReloadBackgroundImage(void); - -extern bool Output_LoadBackgroundFromImage(const IMAGE *image); -extern void Output_LoadBackgroundFromObject(void); -extern void Output_UnloadBackground(void); - -extern void Output_DrawBackground(void); - -// TODO: make these functions private once output module is consolidated -char *Output_GetLastBackgroundPath(void); -void Output_ClearLastBackgroundPath(void); diff --git a/src/libtrx/include/libtrx/game/output/common.h b/src/libtrx/include/libtrx/game/output/common.h index 07d985b72..cff22d600 100644 --- a/src/libtrx/include/libtrx/game/output/common.h +++ b/src/libtrx/include/libtrx/game/output/common.h @@ -2,15 +2,16 @@ #include "../rooms.h" -extern void Output_ObserveLevelLoad(void); -extern void Output_ObserveLevelUnload(void); -extern void Output_ObserveRoomFlip(const ROOM *room); - extern bool Output_MakeScreenshot(const char *path); extern void Output_BeginScene(void); extern void Output_EndScene(void); +extern bool Output_LoadBackgroundFromFile(const char *file_name); +extern void Output_LoadBackgroundFromObject(void); +extern void Output_UnloadBackground(void); + extern void Output_DrawBlackRectangle(int32_t opacity); +extern void Output_DrawBackground(void); extern void Output_DrawPolyList(void); extern void Output_SetupBelowWater(bool is_underwater); @@ -35,8 +36,3 @@ void Output_LightRoom(ROOM *room); void Output_ResetDynamicLights(void); void Output_AddDynamicLight(XYZ_32 pos, int32_t intensity, int32_t falloff); - -int32_t Output_GetFogStart(void); -void Output_SetFogStart(int32_t dist); -extern int32_t Output_GetFogEnd(void); -extern void Output_SetFogEnd(int32_t dist); diff --git a/src/libtrx/include/libtrx/game/output/const.h b/src/libtrx/include/libtrx/game/output/const.h index c14ff2e53..022460bae 100644 --- a/src/libtrx/include/libtrx/game/output/const.h +++ b/src/libtrx/include/libtrx/game/output/const.h @@ -6,9 +6,4 @@ #define TEXTURE_PAGE_HEIGHT 256 #define TEXTURE_PAGE_SIZE (TEXTURE_PAGE_WIDTH * TEXTURE_PAGE_HEIGHT) -#define SHADE_CAUSTICS 0x300 -#define SHADE_MAX 0x1FFF -#define SHADE_LOW 0x1400 -#define SHADE_NEUTRAL 0x1000 - #define WIBBLE_SIZE 32 diff --git a/src/libtrx/include/libtrx/game/output/draw.h b/src/libtrx/include/libtrx/game/output/draw.h deleted file mode 100644 index 048bb7bba..000000000 --- a/src/libtrx/include/libtrx/game/output/draw.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include "../../config/types.h" - -#include - -typedef enum { - TS_HEADING = 0, - TS_BACKGROUND = 1, - TS_REQUESTED = 2, -} TEXT_STYLE; - -extern void Output_DrawTextBackground( - UI_STYLE ui_style, int32_t sx, int32_t sy, int32_t w, int32_t h, int32_t z, - TEXT_STYLE text_style); -extern void Output_DrawTextOutline( - UI_STYLE ui_style, int32_t sx, int32_t sy, int32_t w, int32_t h, int32_t z, - TEXT_STYLE text_style); diff --git a/src/libtrx/include/libtrx/game/output/objects.h b/src/libtrx/include/libtrx/game/output/objects.h deleted file mode 100644 index d32007c2a..000000000 --- a/src/libtrx/include/libtrx/game/output/objects.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include "../objects/types.h" - -#if TR_VERSION == 1 -extern void Output_Meshes_ObserveObjectMeshSwap( - const OBJECT_MESH *mesh_1, const OBJECT_MESH *mesh_2); -extern void Output_Meshes_ObserveObjectMeshUpdate(const OBJECT_MESH *mesh); -#endif diff --git a/src/libtrx/include/libtrx/game/output/textures.h b/src/libtrx/include/libtrx/game/output/textures.h index d9ad3409f..2f7aa1aa8 100644 --- a/src/libtrx/include/libtrx/game/output/textures.h +++ b/src/libtrx/include/libtrx/game/output/textures.h @@ -18,7 +18,6 @@ RGB_888 Output_GetPaletteColor16(uint16_t idx); LIGHT_MAP *Output_GetLightMap(uint8_t idx); SHADE_MAP *Output_GetShadeMap(uint8_t idx); int32_t Output_GetObjectTextureCount(void); -int32_t Output_GetSpriteTextureCount(void); OBJECT_TEXTURE *Output_GetObjectTexture(int32_t texture_idx); SPRITE_TEXTURE *Output_GetSpriteTexture(int32_t texture_idx); ANIMATED_TEXTURE_RANGE *Output_GetAnimatedTextureRange(int32_t range_idx); diff --git a/src/libtrx/include/libtrx/game/output/types.h b/src/libtrx/include/libtrx/game/output/types.h index 40fa91bbb..8ac1e32fc 100644 --- a/src/libtrx/include/libtrx/game/output/types.h +++ b/src/libtrx/include/libtrx/game/output/types.h @@ -1,7 +1,5 @@ #pragma once -#include "../../colors.h" - #include typedef enum { @@ -38,7 +36,6 @@ typedef struct { typedef struct { uint16_t draw_type; uint16_t tex_page; - int32_t uv_count; TEXTURE_UV uv[4]; #if TR_VERSION == 2 TEXTURE_UV uv_backup[4]; @@ -62,6 +59,25 @@ typedef struct ANIMATED_TEXTURE_RANGE { struct ANIMATED_TEXTURE_RANGE *next_range; } ANIMATED_TEXTURE_RANGE; +typedef struct { + float r; + float g; + float b; +} RGB_F; + +typedef struct { + uint8_t r; + uint8_t g; + uint8_t b; +} RGB_888; + +typedef struct { + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; +} RGBA_8888; + typedef struct { uint8_t index[256]; } LIGHT_MAP; diff --git a/src/libtrx/include/libtrx/game/pathing.h b/src/libtrx/include/libtrx/game/pathing.h index 34a1dd2cf..47976cec5 100644 --- a/src/libtrx/include/libtrx/game/pathing.h +++ b/src/libtrx/include/libtrx/game/pathing.h @@ -2,5 +2,4 @@ #include "pathing/box.h" #include "pathing/const.h" -#include "pathing/lot.h" #include "pathing/types.h" diff --git a/src/libtrx/include/libtrx/game/pathing/const.h b/src/libtrx/include/libtrx/game/pathing/const.h index b2d5661ec..8d39823a3 100644 --- a/src/libtrx/include/libtrx/game/pathing/const.h +++ b/src/libtrx/include/libtrx/game/pathing/const.h @@ -4,7 +4,6 @@ #define NO_BOX (-1) #define BOX_ZONE(num) (((num) / STEP_L) - 1) -#define LOT_SLOT_COUNT 32 #if TR_VERSION == 1 #define MAX_ZONES 2 #else diff --git a/src/libtrx/include/libtrx/game/pathing/lot.h b/src/libtrx/include/libtrx/game/pathing/lot.h deleted file mode 100644 index 262bdaafa..000000000 --- a/src/libtrx/include/libtrx/game/pathing/lot.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -#include "../creature/types.h" - -extern bool LOT_EnableBaddieAI(int16_t item_num, bool always); -extern void LOT_DisableBaddieAI(int16_t item_num); -extern CREATURE *LOT_GetBaddieSlot(int32_t i); -extern void LOT_ClearLOT(LOT_INFO *LOT); diff --git a/src/libtrx/include/libtrx/game/rooms/common.h b/src/libtrx/include/libtrx/game/rooms/common.h index 19539f1ce..9d39d2b99 100644 --- a/src/libtrx/include/libtrx/game/rooms/common.h +++ b/src/libtrx/include/libtrx/game/rooms/common.h @@ -6,7 +6,6 @@ void Room_InitialiseRooms(int32_t num_rooms); int32_t Room_GetCount(void); ROOM *Room_Get(int32_t room_num); -int32_t Room_GetNumber(const ROOM *room); void Room_InitialiseFlipStatus(void); void Room_FlipMap(void); @@ -31,7 +30,6 @@ void Room_PopulateSectorData( int16_t Room_GetIndexFromPos(int32_t x, int32_t y, int32_t z); int32_t Room_FindByPos(int32_t x, int32_t y, int32_t z); -int32_t Room_GetFlippedBaseRoom(int32_t room_num); BOUNDS_32 Room_GetWorldBounds(void); extern SECTOR *Room_GetSector( @@ -43,16 +41,10 @@ SECTOR *Room_GetPitSector(const SECTOR *sector, int32_t x, int32_t z); SECTOR *Room_GetSkySector(const SECTOR *sector, int32_t x, int32_t z); void Room_SetAbyssHeight(int16_t height); -bool Room_IsAbyssHeight(int32_t height); +bool Room_IsAbyssHeight(int16_t height); HEIGHT_TYPE Room_GetHeightType(void); int16_t Room_GetHeight(const SECTOR *sector, int32_t x, int32_t y, int32_t z); -int16_t Room_GetHeightEx( - const SECTOR *sector, int32_t x, int32_t y, int32_t z, bool fix_tilts); -extern int32_t Room_GetWaterHeight( - int32_t x, int32_t y, int32_t z, int16_t room_num); int16_t Room_GetCeiling(const SECTOR *sector, int32_t x, int32_t y, int32_t z); -int16_t Room_GetCeilingEx( - const SECTOR *sector, int32_t x, int32_t y, int32_t z, bool fix_tilts); extern int16_t Room_GetTiltType( const SECTOR *sector, int32_t x, int32_t y, int32_t z); extern int32_t Room_FindGridShift(int32_t src, int32_t dst); diff --git a/src/libtrx/include/libtrx/game/rooms/enum.h b/src/libtrx/include/libtrx/game/rooms/enum.h index ad7abda5f..8dcbab259 100644 --- a/src/libtrx/include/libtrx/game/rooms/enum.h +++ b/src/libtrx/include/libtrx/game/rooms/enum.h @@ -67,7 +67,9 @@ typedef enum { TT_ANTIPAD = 6, TT_COMBAT = 7, TT_DUMMY = 8, +#if TR_VERSION == 2 TT_ANTITRIGGER = 9, +#endif } TRIGGER_TYPE; #if TR_VERSION == 2 diff --git a/src/libtrx/include/libtrx/game/savegame.h b/src/libtrx/include/libtrx/game/savegame.h index ac83fd6bd..61f6305ea 100644 --- a/src/libtrx/include/libtrx/game/savegame.h +++ b/src/libtrx/include/libtrx/game/savegame.h @@ -1,6 +1,25 @@ #pragma once -#include "./savegame/common.h" -#include "./savegame/const.h" -#include "./savegame/enum.h" -#include "./savegame/types.h" +#include + +typedef enum { + SAVEGAME_STAGE_BEFORE_LOAD, + SAVEGAME_STAGE_AFTER_LOAD, + SAVEGAME_STAGE_BEFORE_SAVE, +} SAVEGAME_STAGE; + +// Remembers the slot used when the player starts a loaded game. +// Persists across level reloads. +void Savegame_BindSlot(int32_t slot_num); + +// Removes the binding of the current slot. Used when the player exits to +// title, issues a command like `/play` etc. +void Savegame_UnbindSlot(void); + +// Returns the currently bound slot number. If there is none, returns -1. +int32_t Savegame_GetBoundSlot(void); + +extern int32_t Savegame_GetSlotCount(void); +extern bool Savegame_IsSlotFree(int32_t slot_num); +extern bool Savegame_Load(int32_t slot_num); +extern bool Savegame_Save(int32_t slot_num); diff --git a/src/libtrx/include/libtrx/game/savegame/bson.h b/src/libtrx/include/libtrx/game/savegame/bson.h deleted file mode 100644 index 1545d85d4..000000000 --- a/src/libtrx/include/libtrx/game/savegame/bson.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include - -#pragma pack(push, 1) -typedef struct { - uint32_t magic; - int16_t initial_version; - uint16_t version; - int32_t compressed_size; - int32_t uncompressed_size; -} SAVEGAME_BSON_HEADER; - -typedef struct { - uint32_t flags; - int32_t counter; - int32_t level_num; - int32_t title_size; -} SAVEGAME_BSON_EXTENDED_HEADER; -#pragma pack(pop) diff --git a/src/libtrx/include/libtrx/game/savegame/common.h b/src/libtrx/include/libtrx/game/savegame/common.h deleted file mode 100644 index 77965f637..000000000 --- a/src/libtrx/include/libtrx/game/savegame/common.h +++ /dev/null @@ -1,72 +0,0 @@ -#pragma once - -#include "../game_flow/types.h" -#include "./types.h" - -// Loading a saved game is divided into two phases. First, the game reads the -// savegame file contents to look for the level number. The rest of the save -// data is stored in a special buffer in the g_GameInfo. Then the engine -// continues to execute the normal game flow and loads the specified level. -// Second phase occurs after everything finishes loading, e.g. items, -// creatures, triggers etc., and is what actually sets Lara's health, creatures -// status, triggers, inventory etc. - -void Savegame_RegisterStrategy(SAVEGAME_STRATEGY strategy); -void Savegame_Init(void); -void Savegame_Shutdown(void); -bool Savegame_IsInitialised(void); -void Savegame_ScanSavedGames(void); - -SAVEGAME_VERSION Savegame_GetInitialVersion(void); -void Savegame_SetInitialVersion(SAVEGAME_VERSION version); -int32_t Savegame_GetCounter(void); -int32_t Savegame_GetTotalCount(void); -int32_t Savegame_GetLevelNumber(int32_t slot_num); -bool Savegame_IsSlotFree(int32_t slot_num); -bool Savegame_RestartAvailable(int32_t slot_num); - -// Remembers the slot used when the player starts a loaded game. -// Persists across level reloads. -void Savegame_BindSlot(int32_t slot_num); - -// Removes the binding of the current slot. Used when the player exits to -// title, issues a command like `/play` etc. -void Savegame_UnbindSlot(void); - -// Returns the currently bound slot number. If there is none, returns -1. -int32_t Savegame_GetBoundSlot(void); - -// Returns the most recently created slot number. If there is none, returns -1. -int32_t Savegame_GetMostRecentlyCreatedSlot(void); - -// Returns the most recently created slot number. If there is none, returns -1. -int32_t Savegame_GetMostRecentlyUsedSlot(void); - -void Savegame_ProcessItemsBeforeLoad(void); -void Savegame_ProcessItemsBeforeSave(void); -bool Savegame_Load(int32_t slot_num); -bool Savegame_Save(int32_t slot_num); -bool Savegame_UpdateDeathCounters(int32_t slot_num, int32_t death_count); -bool Savegame_LoadOnlyResumeInfo(int32_t slot_num); - -void Savegame_InitCurrentInfo(void); -void Savegame_SetCurrentInfo(int32_t current_slot, int32_t src_slot); -RESUME_INFO *Savegame_GetCurrentInfo(const GF_LEVEL *level); -const SAVEGAME_INFO *Savegame_GetSavegameInfo(int32_t slot_num); -void Savegame_ResetCurrentInfo(const GF_LEVEL *level); -void Savegame_CarryCurrentInfoToNextLevel( - const GF_LEVEL *src_level, const GF_LEVEL *dst_level); -void Savegame_PersistGameToCurrentInfo(const GF_LEVEL *level); - -void Savegame_SetDefaultStats(const GF_LEVEL *level, STATS_COMMON stats); -STATS_COMMON Savegame_GetDefaultStats(const GF_LEVEL *level); - -extern int32_t Savegame_GetSlotCount(void); -extern void Savegame_HighlightNewestSlot(void); -extern void Savegame_ApplyLogicToCurrentInfo(const GF_LEVEL *level); - -#define REGISTER_SAVEGAME_STRATEGY(strategy_) \ - __attribute__((__constructor__)) static void M_Register(void) \ - { \ - Savegame_RegisterStrategy(strategy_); \ - } diff --git a/src/libtrx/include/libtrx/game/savegame/const.h b/src/libtrx/include/libtrx/game/savegame/const.h deleted file mode 100644 index a4222ce4e..000000000 --- a/src/libtrx/include/libtrx/game/savegame/const.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -#define SAVEGAME_CURRENT_VERSION 8 diff --git a/src/libtrx/include/libtrx/game/savegame/enum.h b/src/libtrx/include/libtrx/game/savegame/enum.h deleted file mode 100644 index a4110eccb..000000000 --- a/src/libtrx/include/libtrx/game/savegame/enum.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -typedef enum { - SAVEGAME_STAGE_BEFORE_LOAD, - SAVEGAME_STAGE_AFTER_LOAD, - SAVEGAME_STAGE_BEFORE_SAVE, -} SAVEGAME_STAGE; - -typedef enum { - SAVEGAME_FORMAT_INVALID = 0, - SAVEGAME_FORMAT_LEGACY = 1, - SAVEGAME_FORMAT_BSON = 2, -} SAVEGAME_FORMAT; - -typedef enum { - VERSION_LEGACY = -1, - VERSION_0 = 0, - VERSION_1 = 1, - VERSION_2 = 2, - VERSION_3 = 3, - VERSION_4 = 4, - VERSION_5 = 5, - VERSION_6 = 6, - - // Added extra footer after the compressed BSON structure for quicker - // access to essential data, such as the level counter and the level title, - // without the need to parse the entire BSON document. - // Added TR2+ stats ammo hits/used, health packs used, distance travelled. - VERSION_7 = 7, - - // Added the current TRX version string. - VERSION_8 = 8, -} SAVEGAME_VERSION; diff --git a/src/libtrx/include/libtrx/game/savegame/types.h b/src/libtrx/include/libtrx/game/savegame/types.h deleted file mode 100644 index a74e0db54..000000000 --- a/src/libtrx/include/libtrx/game/savegame/types.h +++ /dev/null @@ -1,73 +0,0 @@ -#pragma once - -#include "../../filesystem.h" -#include "../lara/enum.h" -#include "../stats/types.h" - -typedef struct { - LEVEL_STATS stats; - uint8_t small_medipacks; - uint8_t large_medipacks; - uint16_t pistol_ammo; - uint16_t magnum_ammo; - uint16_t uzi_ammo; - uint16_t shotgun_ammo; - LARA_GUN_STATE gun_status; - LARA_GUN_TYPE equipped_gun_type; -#if TR_VERSION == 1 - LARA_GUN_TYPE holsters_gun_type; - LARA_GUN_TYPE back_gun_type; - uint8_t num_scions; - int32_t lara_hitpoints; -#elif TR_VERSION == 2 - uint16_t m16_ammo; - uint16_t grenade_ammo; - uint16_t harpoon_ammo; - uint8_t flares; -#endif - - union { - uint16_t all; - struct { - uint16_t available : 1; - uint16_t has_pistols : 1; - uint16_t has_magnums : 1; - uint16_t has_uzis : 1; - uint16_t has_shotgun : 1; -#if TR_VERSION == 1 - uint16_t costume : 1; - uint16_t pad : 10; -#elif TR_VERSION == 2 - uint16_t has_m16 : 1; - uint16_t has_grenade : 1; - uint16_t has_harpoon : 1; - uint16_t pad : 8; -#endif - }; - } flags; -} RESUME_INFO; - -typedef struct { - SAVEGAME_FORMAT format; - char *full_path; - int32_t counter; - int32_t level_num; - char *level_title; - int16_t initial_version; - struct { - bool restart; - bool select_level; - } features; -} SAVEGAME_INFO; - -typedef struct { - bool allow_load; - bool allow_save; - SAVEGAME_FORMAT format; - const char *(*get_save_file_pattern_func)(void); - bool (*fill_info_func)(MYFILE *fp, SAVEGAME_INFO *info); - bool (*load_from_file_func)(MYFILE *fp); - bool (*load_only_resume_info_func)(MYFILE *fp); - void (*save_to_file_func)(MYFILE *fp, SAVEGAME_INFO *savegame_info); - bool (*update_death_counters_func)(MYFILE *fp, int32_t death_count); -} SAVEGAME_STRATEGY; diff --git a/src/libtrx/include/libtrx/game/shell.h b/src/libtrx/include/libtrx/game/shell.h index 1ed37747f..99b922576 100644 --- a/src/libtrx/include/libtrx/game/shell.h +++ b/src/libtrx/include/libtrx/game/shell.h @@ -3,28 +3,22 @@ #include #include -typedef struct { - int32_t w; - int32_t h; -} SHELL_SIZE; - extern void Shell_Shutdown(void); extern SDL_Window *Shell_GetWindow(void); -extern bool Shell_ParseArgs(int32_t arg_count, const char **args); void Shell_Setup(void); -extern int32_t Shell_Main(void); +extern void Shell_Main(void); void Shell_Terminate(int32_t exit_code); void Shell_ExitSystem(const char *message); void Shell_ExitSystemFmt(const char *fmt, ...); +void Shell_GetCommandLine(int *arg_count, const char ***args); void Shell_ScheduleExit(void); bool Shell_IsExiting(void); -bool Shell_IsFullscreen(void); -SHELL_SIZE Shell_GetWindowSize(void); -SHELL_SIZE Shell_GetCurrentSize(void); -SHELL_SIZE Shell_GetCurrentDisplaySize(void); +int32_t Shell_GetCurrentDisplayWidth(void); +int32_t Shell_GetCurrentDisplayHeight(void); +void Shell_GetWindowSize(int32_t *out_width, int32_t *out_height); extern const char *Shell_GetConfigPath(void); extern const char *Shell_GetGameFlowPath(void); diff --git a/src/libtrx/include/libtrx/game/sound/common.h b/src/libtrx/include/libtrx/game/sound/common.h index 144a322d8..374320fe5 100644 --- a/src/libtrx/include/libtrx/game/sound/common.h +++ b/src/libtrx/include/libtrx/game/sound/common.h @@ -14,11 +14,6 @@ int16_t *Sound_GetSampleLUT(void); SAMPLE_INFO *Sound_GetSampleInfo(SOUND_EFFECT_ID sfx_num); SAMPLE_INFO *Sound_GetSampleInfoByIdx(int32_t info_idx); -extern int32_t Sound_GetMinVolume(void); -extern int32_t Sound_GetMaxVolume(void); -extern int32_t Sound_GetMasterVolume(void); -extern void Sound_SetMasterVolume(int32_t volume); - void Sound_ResetSources(void); void Sound_PauseAll(void); void Sound_UnpauseAll(void); diff --git a/src/libtrx/include/libtrx/game/spawn.h b/src/libtrx/include/libtrx/game/spawn.h deleted file mode 100644 index 022f1491c..000000000 --- a/src/libtrx/include/libtrx/game/spawn.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include - -extern int16_t Spawn_Blood( - int32_t x, int32_t y, int32_t z, int16_t speed, int16_t y_rot, - int16_t room_num); diff --git a/src/libtrx/include/libtrx/game/stats.h b/src/libtrx/include/libtrx/game/stats.h deleted file mode 100644 index e0ceb05c4..000000000 --- a/src/libtrx/include/libtrx/game/stats.h +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once - -#include "stats/common.h" -#include "stats/types.h" diff --git a/src/libtrx/include/libtrx/game/stats/common.h b/src/libtrx/include/libtrx/game/stats/common.h deleted file mode 100644 index 0e83bca63..000000000 --- a/src/libtrx/include/libtrx/game/stats/common.h +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once - -extern void Stats_StartTimer(void); -extern void Stats_ObserveItemsLoad(void); diff --git a/src/libtrx/include/libtrx/game/text.h b/src/libtrx/include/libtrx/game/text.h index 7385805eb..d4355aa72 100644 --- a/src/libtrx/include/libtrx/game/text.h +++ b/src/libtrx/include/libtrx/game/text.h @@ -1,8 +1,5 @@ #pragma once -#include "./output/draw.h" - -#include #include // TODO: rename this @@ -34,6 +31,12 @@ typedef struct { } combine_with; } GLYPH_INFO; +typedef enum { + TS_HEADING = 0, + TS_BACKGROUND = 1, + TS_REQUESTED = 2, +} TEXT_STYLE; + typedef struct { union { uint32_t all; @@ -91,10 +94,8 @@ typedef struct { TEXT_STYLE style; } outline; - size_t content_cap; char *content; - size_t glyphs_cap; const GLYPH_INFO **glyphs; } TEXTSTRING; diff --git a/src/libtrx/include/libtrx/game/ui.h b/src/libtrx/include/libtrx/game/ui.h deleted file mode 100644 index fd8a7a341..000000000 --- a/src/libtrx/include/libtrx/game/ui.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include "./ui/common.h" -#include "./ui/dialogs/controls.h" -#include "./ui/dialogs/controls_backend.h" -#include "./ui/dialogs/examine_item.h" -#include "./ui/dialogs/new_game.h" -#include "./ui/dialogs/pause.h" -#include "./ui/dialogs/photo_mode.h" -#include "./ui/dialogs/play_any_level.h" -#include "./ui/dialogs/save_slot.h" -#include "./ui/dialogs/select_level.h" -#include "./ui/dialogs/sound_settings.h" -#include "./ui/dialogs/stats.h" -#include "./ui/elements/anchor.h" -#include "./ui/elements/fade.h" -#include "./ui/elements/flash.h" -#include "./ui/elements/frame.h" -#include "./ui/elements/label.h" -#include "./ui/elements/modal.h" -#include "./ui/elements/pad.h" -#include "./ui/elements/requester.h" -#include "./ui/elements/resize.h" -#include "./ui/elements/spacer.h" -#include "./ui/elements/stack.h" -#include "./ui/events.h" -#include "./ui/hud/console.h" -#include "./ui/hud/console_logs.h" diff --git a/src/libtrx/include/libtrx/game/ui/common.h b/src/libtrx/include/libtrx/game/ui/common.h index 015dfc5f3..e56325eb9 100644 --- a/src/libtrx/include/libtrx/game/ui/common.h +++ b/src/libtrx/include/libtrx/game/ui/common.h @@ -1,7 +1,6 @@ #pragma once -#include -#include +#include "./events.h" typedef enum { UI_KEY_UP, @@ -15,56 +14,6 @@ typedef enum { UI_KEY_ESCAPE, } UI_INPUT; -// Forward declaration of the node and its vtable. -struct UI_NODE; -typedef struct { - void (*measure)(struct UI_NODE *node); - void (*layout)(struct UI_NODE *node, float x, float y, float w, float h); - void (*draw)(const struct UI_NODE *node); -} UI_WIDGET_OPS; - -// Node structure that forms the UI tree -typedef struct UI_NODE { - // Common operations on a widget - const UI_WIDGET_OPS *ops; - - // Final layout rectangle - float x; - float y; - float w; - float h; - - // Needed size from measure pass - float measure_w; - float measure_h; - - // Link to parent and siblings to form a tree - struct UI_NODE *parent; - struct UI_NODE *first_child; - struct UI_NODE *last_child; - struct UI_NODE *next_sibling; - - // Widget-specific data - void *data; -} UI_NODE; - -// Dimensions in virtual pixels of the screen area -// (640x480 for any 4:3 resolution on 1.00 text scaling) -int32_t UI_GetCanvasWidth(void); -int32_t UI_GetCanvasHeight(void); -float UI_ScaleX(float x); -float UI_ScaleY(float y); - -// Public API for scene management -void UI_BeginScene(void); -void UI_EndScene(void); - -// Helpers to add children, etc. -UI_NODE *UI_AllocNode(const UI_WIDGET_OPS *ops, size_t additional_size); -void UI_AddChild(UI_NODE *child); -void UI_PushCurrent(UI_NODE *child); -void UI_PopCurrent(void); - void UI_Init(void); void UI_Shutdown(void); void UI_ToggleState(bool *config_setting); @@ -72,3 +21,8 @@ void UI_ToggleState(bool *config_setting); void UI_HandleKeyDown(uint32_t key); void UI_HandleKeyUp(uint32_t key); void UI_HandleTextEdit(const char *text); +void UI_HandleLayoutChange(void); + +extern int32_t UI_GetCanvasWidth(void); +extern int32_t UI_GetCanvasHeight(void); +extern UI_INPUT UI_TranslateInput(uint32_t system_keycode); diff --git a/src/libtrx/include/libtrx/game/ui/dialogs/base_passport.h b/src/libtrx/include/libtrx/game/ui/dialogs/base_passport.h deleted file mode 100644 index ccf1dcb05..000000000 --- a/src/libtrx/include/libtrx/game/ui/dialogs/base_passport.h +++ /dev/null @@ -1,16 +0,0 @@ -// Base passport dialog functions. -// Does not implement a function on its own, and is used mostly for placement -// and sizing of the larger dialogs such as load/save game. - -#pragma once - -#include "../common.h" -#include "../elements/requester.h" - -// state functions -void UI_BasePassportDialog_Init(UI_REQUESTER_STATE *req, size_t max_rows); -void UI_BasePassportDialog_Control(UI_REQUESTER_STATE *req); - -// draw functions -void UI_BeginBasePassportDialog(void); -void UI_EndBasePassportDialog(void); diff --git a/src/libtrx/include/libtrx/game/ui/dialogs/controls.h b/src/libtrx/include/libtrx/game/ui/dialogs/controls.h deleted file mode 100644 index f119f47eb..000000000 --- a/src/libtrx/include/libtrx/game/ui/dialogs/controls.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -// A controls editor dialog. - -#include "../../../event_manager.h" -#include "../../input.h" -#include "../common.h" -#include "./controls_backend.h" -#include "./controls_editor.h" - -typedef struct { - int32_t phase; - INPUT_BACKEND backend; - int32_t active_layout; - - EVENT_MANAGER *events; - UI_CONTROLS_BACKEND_STATE backend_state; - UI_CONTROLS_EDITOR_STATE editor_state; -} UI_CONTROLS_STATE; - -// state functions -void UI_Controls_Init(UI_CONTROLS_STATE *s); -void UI_Controls_Free(UI_CONTROLS_STATE *s); -bool UI_Controls_Control(UI_CONTROLS_STATE *s); - -// draw functions -void UI_Controls(UI_CONTROLS_STATE *s); diff --git a/src/libtrx/include/libtrx/game/ui/dialogs/controls_backend.h b/src/libtrx/include/libtrx/game/ui/dialogs/controls_backend.h deleted file mode 100644 index 454c29813..000000000 --- a/src/libtrx/include/libtrx/game/ui/dialogs/controls_backend.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -// A control backend (keyboard/controller) choice dialog. - -#include "../common.h" -#include "../elements/requester.h" - -typedef struct { - UI_REQUESTER_STATE req; -} UI_CONTROLS_BACKEND_STATE; - -// state functions -void UI_ControlsBackend_Init(UI_CONTROLS_BACKEND_STATE *s); -void UI_ControlsBackend_Free(UI_CONTROLS_BACKEND_STATE *s); -int32_t UI_ControlsBackend_Control(UI_CONTROLS_BACKEND_STATE *s); - -// draw functions -void UI_ControlsBackend(UI_CONTROLS_BACKEND_STATE *s); diff --git a/src/libtrx/include/libtrx/game/ui/dialogs/controls_editor.h b/src/libtrx/include/libtrx/game/ui/dialogs/controls_editor.h deleted file mode 100644 index 6ce2f8826..000000000 --- a/src/libtrx/include/libtrx/game/ui/dialogs/controls_editor.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -// A controls remapper dialog. - -#include "../../../event_manager.h" -#include "../../input.h" -#include "../common.h" -#include "../elements/flash.h" -#include "../elements/requester.h" - -typedef struct { - int32_t phase; - INPUT_BACKEND backend; - int32_t active_layout; - INPUT_ROLE active_role; - int32_t active_col; - int32_t active_row; - UI_FLASH_STATE flash; - EVENT_MANAGER *events; -} UI_CONTROLS_EDITOR_STATE; - -typedef enum { - UI_CONTROLS_CHOICE_EXIT, - UI_CONTROLS_CHOICE_GO_BACK, - UI_CONTROLS_CHOICE_NOOP, -} UI_CONTROLS_CHOICE; - -// state functions -void UI_ControlsEditor_Init(UI_CONTROLS_EDITOR_STATE *s, EVENT_MANAGER *events); -void UI_ControlsEditor_Free(UI_CONTROLS_EDITOR_STATE *s); -void UI_ControlsEditor_Reinit( - UI_CONTROLS_EDITOR_STATE *s, INPUT_BACKEND backend, int32_t layout); -UI_CONTROLS_CHOICE UI_ControlsEditor_Control(UI_CONTROLS_EDITOR_STATE *s); - -// draw functions -void UI_ControlsEditor(UI_CONTROLS_EDITOR_STATE *s); diff --git a/src/libtrx/include/libtrx/game/ui/dialogs/examine_item.h b/src/libtrx/include/libtrx/game/ui/dialogs/examine_item.h deleted file mode 100644 index f543ab718..000000000 --- a/src/libtrx/include/libtrx/game/ui/dialogs/examine_item.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include "../../../vector.h" -#include "../common.h" - -// A widget to cycle through several pages of a text content. - -typedef struct { - char *title; - size_t max_lines; - int32_t current_page; - VECTOR *page_content; - bool is_empty; -} UI_EXAMINE_ITEM_STATE; - -void UI_ExamineItem_Init( - UI_EXAMINE_ITEM_STATE *state, const char *title, const char *text, - size_t max_lines); -void UI_ExamineItem_Control(UI_EXAMINE_ITEM_STATE *state); -void UI_ExamineItem_Free(UI_EXAMINE_ITEM_STATE *state); - -void UI_ExamineItem(UI_EXAMINE_ITEM_STATE *state); diff --git a/src/libtrx/include/libtrx/game/ui/dialogs/new_game.h b/src/libtrx/include/libtrx/game/ui/dialogs/new_game.h deleted file mode 100644 index 67d408b17..000000000 --- a/src/libtrx/include/libtrx/game/ui/dialogs/new_game.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -// A new game mode selector dialog. - -#include "../common.h" -#include "../elements/requester.h" - -typedef struct { - UI_REQUESTER_STATE req; -} UI_NEW_GAME_STATE; - -// state functions -void UI_NewGame_Init(UI_NEW_GAME_STATE *s); -int32_t UI_NewGame_Control(UI_NEW_GAME_STATE *s); -void UI_NewGame_Free(UI_NEW_GAME_STATE *s); - -// draw functions -void UI_NewGame(UI_NEW_GAME_STATE *s); diff --git a/src/libtrx/include/libtrx/game/ui/dialogs/pause.h b/src/libtrx/include/libtrx/game/ui/dialogs/pause.h deleted file mode 100644 index 1bb7be76e..000000000 --- a/src/libtrx/include/libtrx/game/ui/dialogs/pause.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -// A pause exit confirmation dialog. - -#include "../common.h" -#include "../elements/requester.h" - -typedef struct { - int32_t phase; - UI_REQUESTER_STATE req; -} UI_PAUSE_STATE; - -typedef enum { - UI_PAUSE_NOOP, - UI_PAUSE_RESUME_PAUSE, - UI_PAUSE_EXIT_TO_GAME, - UI_PAUSE_EXIT_TO_TITLE, -} UI_PAUSE_EXIT_CHOICE; - -// state functions -void UI_Pause_Init(UI_PAUSE_STATE *s); -UI_PAUSE_EXIT_CHOICE UI_Pause_Control(UI_PAUSE_STATE *s); -void UI_Pause_Free(UI_PAUSE_STATE *s); - -// draw functions -void UI_Pause(UI_PAUSE_STATE *s); diff --git a/src/libtrx/include/libtrx/game/ui/dialogs/photo_mode.h b/src/libtrx/include/libtrx/game/ui/dialogs/photo_mode.h deleted file mode 100644 index eafbf1df0..000000000 --- a/src/libtrx/include/libtrx/game/ui/dialogs/photo_mode.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include "../common.h" - -// A photo mode tutorial dialog. - -void UI_PhotoMode(void); diff --git a/src/libtrx/include/libtrx/game/ui/dialogs/play_any_level.h b/src/libtrx/include/libtrx/game/ui/dialogs/play_any_level.h deleted file mode 100644 index 4783bf820..000000000 --- a/src/libtrx/include/libtrx/game/ui/dialogs/play_any_level.h +++ /dev/null @@ -1,20 +0,0 @@ -// UI dialog for selecting a level within a save slot - -#pragma once - -#include "../common.h" - -#include - -#define UI_PLAY_ANY_LEVEL_CHOICE_NO_CHOICE UI_REQUESTER_NO_CHOICE -#define UI_PLAY_ANY_LEVEL_CHOICE_CANCEL UI_REQUESTER_CANCEL - -typedef struct UI_PLAY_ANY_LEVEL_DIALOG_STATE UI_PLAY_ANY_LEVEL_DIALOG_STATE; - -struct UI_PLAY_ANY_LEVEL_DIALOG_STATE *UI_PlayAnyLevelDialog_Init(void); - -void UI_PlayAnyLevelDialog_Free(struct UI_PLAY_ANY_LEVEL_DIALOG_STATE *s); - -int32_t UI_PlayAnyLevelDialog_Control(struct UI_PLAY_ANY_LEVEL_DIALOG_STATE *s); - -void UI_PlayAnyLevelDialog(struct UI_PLAY_ANY_LEVEL_DIALOG_STATE *s); diff --git a/src/libtrx/include/libtrx/game/ui/dialogs/save_slot.h b/src/libtrx/include/libtrx/game/ui/dialogs/save_slot.h deleted file mode 100644 index 7b13241d9..000000000 --- a/src/libtrx/include/libtrx/game/ui/dialogs/save_slot.h +++ /dev/null @@ -1,34 +0,0 @@ -// UI dialog for selecting a save slot (load or save game) - -#pragma once - -#include "../common.h" - -typedef enum { - UI_SAVE_SLOT_DIALOG_LOAD_GAME, - UI_SAVE_SLOT_DIALOG_SAVE_GAME, -} UI_SAVE_SLOT_DIALOG_TYPE; - -typedef enum { - UI_SAVE_SLOT_DIALOG_NO_CHOICE, - UI_SAVE_SLOT_DIALOG_CANCEL, - UI_SAVE_SLOT_DIALOG_CONFIRM, - UI_SAVE_SLOT_DIALOG_DETAILS, -} UI_SAVE_SLOT_DIALOG_ACTION; - -typedef struct { - UI_SAVE_SLOT_DIALOG_ACTION action; - int32_t slot_num; -} UI_SAVE_SLOT_DIALOG_CHOICE; - -typedef struct UI_SAVE_SLOT_DIALOG_STATE UI_SAVE_SLOT_DIALOG_STATE; - -// state functions -struct UI_SAVE_SLOT_DIALOG_STATE *UI_SaveSlotDialog_Init( - UI_SAVE_SLOT_DIALOG_TYPE type, int32_t save_slot); -void UI_SaveSlotDialog_Free(struct UI_SAVE_SLOT_DIALOG_STATE *s); -UI_SAVE_SLOT_DIALOG_CHOICE UI_SaveSlotDialog_Control( - struct UI_SAVE_SLOT_DIALOG_STATE *s); - -// draw functions -void UI_SaveSlotDialog(const struct UI_SAVE_SLOT_DIALOG_STATE *s); diff --git a/src/libtrx/include/libtrx/game/ui/dialogs/select_level.h b/src/libtrx/include/libtrx/game/ui/dialogs/select_level.h deleted file mode 100644 index 21cd97066..000000000 --- a/src/libtrx/include/libtrx/game/ui/dialogs/select_level.h +++ /dev/null @@ -1,22 +0,0 @@ -// UI dialog for selecting a level within a save slot - -#pragma once - -#include "../common.h" - -#include - -#define UI_SELECT_LEVEL_CHOICE_NOOP -1 -#define UI_SELECT_LEVEL_CHOICE_CANCEL -2 -#define UI_SELECT_LEVEL_CHOICE_PLAY_STORY_SO_FAR -3 - -typedef struct UI_SELECT_LEVEL_DIALOG_STATE UI_SELECT_LEVEL_DIALOG_STATE; - -struct UI_SELECT_LEVEL_DIALOG_STATE *UI_SelectLevelDialog_Init( - int32_t save_slot); - -void UI_SelectLevelDialog_Free(struct UI_SELECT_LEVEL_DIALOG_STATE *s); - -int32_t UI_SelectLevelDialog_Control(struct UI_SELECT_LEVEL_DIALOG_STATE *s); - -void UI_SelectLevelDialog(struct UI_SELECT_LEVEL_DIALOG_STATE *s); diff --git a/src/libtrx/include/libtrx/game/ui/dialogs/sound_settings.h b/src/libtrx/include/libtrx/game/ui/dialogs/sound_settings.h deleted file mode 100644 index d3bbd128d..000000000 --- a/src/libtrx/include/libtrx/game/ui/dialogs/sound_settings.h +++ /dev/null @@ -1,21 +0,0 @@ -// UI dialog for adjusting music and sound volumes -#pragma once - -#include "../common.h" - -typedef struct UI_SOUND_SETTINGS_STATE UI_SOUND_SETTINGS_STATE; - -// state functions -// Initialize the sound settings dialog state -UI_SOUND_SETTINGS_STATE *UI_SoundSettings_Init(void); - -// Free resources used by the sound settings dialog -void UI_SoundSettings_Free(UI_SOUND_SETTINGS_STATE *s); - -// Handle input/control for the sound settings dialog -// Returns true if the dialog should be closed -bool UI_SoundSettings_Control(UI_SOUND_SETTINGS_STATE *s); - -// draw functions -// Render the sound settings dialog -void UI_SoundSettings(UI_SOUND_SETTINGS_STATE *s); diff --git a/src/libtrx/include/libtrx/game/ui/elements/anchor.h b/src/libtrx/include/libtrx/game/ui/elements/anchor.h deleted file mode 100644 index e843d5390..000000000 --- a/src/libtrx/include/libtrx/game/ui/elements/anchor.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include "../common.h" - -// Used to align a top-level widget to the screen center or to the screen edges. -// Uses ratio inputs. - -void UI_BeginAnchor(const float x, const float y); -void UI_EndAnchor(void); diff --git a/src/libtrx/include/libtrx/game/ui/elements/fade.h b/src/libtrx/include/libtrx/game/ui/elements/fade.h deleted file mode 100644 index cf1de65fe..000000000 --- a/src/libtrx/include/libtrx/game/ui/elements/fade.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include "../../fader.h" -#include "../common.h" - -#include - -// Draw a fade on top or behind children. - -void UI_BeginFade(FADER *fader, bool is_on_top); -void UI_EndFade(void); diff --git a/src/libtrx/include/libtrx/game/ui/elements/fixed.h b/src/libtrx/include/libtrx/game/ui/elements/fixed.h deleted file mode 100644 index f6ab5d9b0..000000000 --- a/src/libtrx/include/libtrx/game/ui/elements/fixed.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include "../common.h" - -// A transformer to detach the children from the parent layout. -// x, y specify the anchor point - 0,0 mean top left corner, 1,1 mean bottom -// right corner. - -void UI_BeginFixed(float x, float y); -void UI_EndFixed(void); diff --git a/src/libtrx/include/libtrx/game/ui/elements/flash.h b/src/libtrx/include/libtrx/game/ui/elements/flash.h deleted file mode 100644 index 1254fdf7f..000000000 --- a/src/libtrx/include/libtrx/game/ui/elements/flash.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include "../common.h" - -#include - -// Make the child widget invisible in the specified interval. - -typedef struct { - int32_t rate; - int32_t count; -} UI_FLASH_STATE; - -// state functions -void UI_Flash_Init(UI_FLASH_STATE *s, int32_t rate); -void UI_Flash_Free(UI_FLASH_STATE *s); -void UI_Flash_Control(UI_FLASH_STATE *s); - -// draw functions -void UI_BeginFlash(UI_FLASH_STATE *s); -void UI_EndFlash(void); diff --git a/src/libtrx/include/libtrx/game/ui/elements/frame.h b/src/libtrx/include/libtrx/game/ui/elements/frame.h deleted file mode 100644 index 1be4f0fdd..000000000 --- a/src/libtrx/include/libtrx/game/ui/elements/frame.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include "../common.h" - -// A frame around the child widget. - -typedef enum { - UI_FRAME_DIALOG_BACKGROUND, - UI_FRAME_DIALOG_HEADING, - UI_FRAME_SELECTED_OPTION, - UI_FRAME_OUTLINE_ONLY, -} UI_FRAME_STYLE; - -void UI_BeginFrame(UI_FRAME_STYLE style); -void UI_EndFrame(void); diff --git a/src/libtrx/include/libtrx/game/ui/elements/hide.h b/src/libtrx/include/libtrx/game/ui/elements/hide.h deleted file mode 100644 index f7c3ad1c9..000000000 --- a/src/libtrx/include/libtrx/game/ui/elements/hide.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include "../common.h" - -// Make the child widget invisible if the given flag is true. -// The children still take up their size. - -void UI_BeginHide(bool hide_children); -void UI_EndHide(void); diff --git a/src/libtrx/include/libtrx/game/ui/elements/label.h b/src/libtrx/include/libtrx/game/ui/elements/label.h deleted file mode 100644 index 32ce8122e..000000000 --- a/src/libtrx/include/libtrx/game/ui/elements/label.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include "../common.h" - -#include - -// Basic text widget. - -typedef struct { - float scale; - int32_t z; -} UI_LABEL_SETTINGS; - -void UI_Label(const char *text); -void UI_LabelEx(const char *text, UI_LABEL_SETTINGS settings); - -void UI_Label_Measure(const char *text, float *out_w, float *out_h); -void UI_Label_MeasureEx( - const char *text, float *out_w, float *out_h, UI_LABEL_SETTINGS settings); diff --git a/src/libtrx/include/libtrx/game/ui/elements/modal.h b/src/libtrx/include/libtrx/game/ui/elements/modal.h deleted file mode 100644 index 8d1f81a7b..000000000 --- a/src/libtrx/include/libtrx/game/ui/elements/modal.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include "../common.h" - -// A widget that resizes the children to the canvas size -// and places it at a specific proportional spot. - -void UI_BeginModal(float x, float y); -void UI_EndModal(void); diff --git a/src/libtrx/include/libtrx/game/ui/elements/offset.h b/src/libtrx/include/libtrx/game/ui/elements/offset.h deleted file mode 100644 index bd8710545..000000000 --- a/src/libtrx/include/libtrx/game/ui/elements/offset.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include "../common.h" - -// A transformer to move the child element in a certain direction. -// Does not affect the occupied space. - -void UI_BeginOffset(float x, float y); -void UI_EndOffset(void); diff --git a/src/libtrx/include/libtrx/game/ui/elements/pad.h b/src/libtrx/include/libtrx/game/ui/elements/pad.h deleted file mode 100644 index 7deff9aee..000000000 --- a/src/libtrx/include/libtrx/game/ui/elements/pad.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include "../common.h" - -// An invisible border in pixel units around the child widget. - -void UI_BeginPad(float x, float y); -void UI_BeginPadEx(float l, float r, float t, float d); -void UI_EndPad(void); diff --git a/src/libtrx/include/libtrx/game/ui/elements/prompt.h b/src/libtrx/include/libtrx/game/ui/elements/prompt.h deleted file mode 100644 index 61670c00e..000000000 --- a/src/libtrx/include/libtrx/game/ui/elements/prompt.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include "../common.h" -#include "./flash.h" - -#include - -// A text edit widget that collects text input from the player. -// Needs to be in focus to work, otherwise is inactive. - -typedef struct { - bool is_focused; - int32_t caret_pos; - int32_t current_text_capacity; - char *current_text; - - int32_t listener1; - int32_t listener2; - - UI_FLASH_STATE flash; -} UI_PROMPT_STATE; - -// state functions -void UI_Prompt_Init(UI_PROMPT_STATE *s); -void UI_Prompt_Free(UI_PROMPT_STATE *s); -void UI_Prompt_Control(UI_PROMPT_STATE *s); -void UI_Prompt_Clear(UI_PROMPT_STATE *s); -void UI_Prompt_SetFocus(UI_PROMPT_STATE *s, bool is_focused); -void UI_Prompt_ChangeText(UI_PROMPT_STATE *s, const char *new_text); - -// draw functions -void UI_Prompt(UI_PROMPT_STATE *s); diff --git a/src/libtrx/include/libtrx/game/ui/elements/requester.h b/src/libtrx/include/libtrx/game/ui/elements/requester.h deleted file mode 100644 index 0763dccd5..000000000 --- a/src/libtrx/include/libtrx/game/ui/elements/requester.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -// A window to select a single option from a list of predefined choices. - -#include "../common.h" - -#include - -#define UI_REQUESTER_CANCEL -2 -#define UI_REQUESTER_NO_CHOICE -1 - -typedef struct { - bool is_selectable; - int32_t vis_rows; - int32_t max_rows; - int32_t vis_row; - int32_t sel_row; - float row_pad; - float row_spacing; - bool show_arrows; - bool reserve_space; -} UI_REQUESTER_STATE; - -// state functions -void UI_Requester_Init( - UI_REQUESTER_STATE *s, int32_t vis_rows, int32_t max_rows, - bool is_selectable); -void UI_Requester_Free(UI_REQUESTER_STATE *s); -int32_t UI_Requester_Control(UI_REQUESTER_STATE *s); -void UI_Requester_SetMaxRows(UI_REQUESTER_STATE *s, size_t max_rows); -void UI_Requester_SetVisibleRows(UI_REQUESTER_STATE *s, size_t visible_rows); -int32_t UI_Requester_GetFirstRow(const UI_REQUESTER_STATE *s); -int32_t UI_Requester_GetLastRow(const UI_REQUESTER_STATE *s); -int32_t UI_Requester_GetCurrentRow(const UI_REQUESTER_STATE *s); -bool UI_Requester_IsRowVisible(const UI_REQUESTER_STATE *s, int32_t i); -bool UI_Requester_IsRowSelected(const UI_REQUESTER_STATE *s, int32_t i); - -// draw functions -void UI_BeginRequester(const UI_REQUESTER_STATE *s, const char *title); -void UI_EndRequester(const UI_REQUESTER_STATE *s); - -void UI_BeginRequesterRow(const UI_REQUESTER_STATE *s, int32_t i); -void UI_EndRequesterRow(const UI_REQUESTER_STATE *s, int32_t i); diff --git a/src/libtrx/include/libtrx/game/ui/elements/resize.h b/src/libtrx/include/libtrx/game/ui/elements/resize.h deleted file mode 100644 index a15efe57c..000000000 --- a/src/libtrx/include/libtrx/game/ui/elements/resize.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include "../common.h" - -// Resize child widget to a specified size in pixels. -// A negative size means to use the child's size. -// A zero value means to hide the child, but participate in the layout pass. - -void UI_BeginResize(float x, float y); -void UI_EndResize(void); diff --git a/src/libtrx/include/libtrx/game/ui/elements/spacer.h b/src/libtrx/include/libtrx/game/ui/elements/spacer.h deleted file mode 100644 index bd76a436e..000000000 --- a/src/libtrx/include/libtrx/game/ui/elements/spacer.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include "../common.h" - -// An invisible widget that occupies certain space in pixels. - -void UI_Spacer(float w, float h); diff --git a/src/libtrx/include/libtrx/game/ui/elements/stack.h b/src/libtrx/include/libtrx/game/ui/elements/stack.h deleted file mode 100644 index a7fb9336f..000000000 --- a/src/libtrx/include/libtrx/game/ui/elements/stack.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -#include "../common.h" - -#include - -// Stack several widgets vertically or horizontally. - -typedef enum { - UI_STACK_VERTICAL, - UI_STACK_HORIZONTAL, -} UI_STACK_ORIENTATION; - -typedef enum { - UI_STACK_H_ALIGN_LEFT, - UI_STACK_H_ALIGN_CENTER, - UI_STACK_H_ALIGN_RIGHT, - UI_STACK_H_ALIGN_SPAN, - UI_STACK_H_ALIGN_DISTRIBUTE, -} UI_STACK_H_ALIGN; - -typedef enum { - UI_STACK_V_ALIGN_TOP, - UI_STACK_V_ALIGN_CENTER, - UI_STACK_V_ALIGN_BOTTOM, - UI_STACK_V_ALIGN_SPAN, - UI_STACK_V_ALIGN_DISTRIBUTE, -} UI_STACK_V_ALIGN; - -typedef struct { - UI_STACK_ORIENTATION orientation; - struct { - UI_STACK_H_ALIGN h; - UI_STACK_V_ALIGN v; - } align; - struct { - float h; - float v; - } spacing; -} UI_STACK_SETTINGS; - -void UI_BeginStack(UI_STACK_ORIENTATION orientation); -void UI_BeginStackEx(UI_STACK_SETTINGS settings); -void UI_EndStack(void); diff --git a/src/libtrx/include/libtrx/game/ui/elements/window.h b/src/libtrx/include/libtrx/game/ui/elements/window.h deleted file mode 100644 index 561f7b996..000000000 --- a/src/libtrx/include/libtrx/game/ui/elements/window.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -void UI_BeginWindow(void); -void UI_EndWindow(void); -void UI_BeginWindowBody(void); -void UI_EndWindowBody(void); -void UI_WindowTitle(const char *title); diff --git a/src/libtrx/include/libtrx/game/ui/events.h b/src/libtrx/include/libtrx/game/ui/events.h index 31c235332..28c699e35 100644 --- a/src/libtrx/include/libtrx/game/ui/events.h +++ b/src/libtrx/include/libtrx/game/ui/events.h @@ -1,16 +1,17 @@ #pragma once #include "../../event_manager.h" +#include "./widgets/base.h" typedef void (*EVENT_LISTENER)(const EVENT *, void *user_data); -void UI_InitEvents(void); -void UI_ShutdownEvents(void); +void UI_Events_Init(void); +void UI_Events_Shutdown(void); -int32_t UI_Subscribe( - const char *event_name, const void *sender, EVENT_LISTENER listener, +int32_t UI_Events_Subscribe( + const char *event_name, const UI_WIDGET *sender, EVENT_LISTENER listener, void *user_data); -void UI_Unsubscribe(int32_t listener_id); +void UI_Events_Unsubscribe(int32_t listener_id); -void UI_FireEvent(EVENT event); +void UI_Events_Fire(const EVENT *event); diff --git a/src/libtrx/include/libtrx/game/ui/hud/console.h b/src/libtrx/include/libtrx/game/ui/hud/console.h deleted file mode 100644 index 97241df44..000000000 --- a/src/libtrx/include/libtrx/game/ui/hud/console.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include "../elements/prompt.h" -#include "./console_logs.h" - -// Dev console display widget. - -typedef struct { - UI_CONSOLE_LOGS logs; - UI_PROMPT_STATE prompt; - - int32_t listeners[5]; - int32_t history_idx; -} UI_CONSOLE_STATE; - -// state functions -void UI_Console_Init(UI_CONSOLE_STATE *s); -void UI_Console_Free(UI_CONSOLE_STATE *s); -void UI_Console_Control(UI_CONSOLE_STATE *s); - -// draw functions -void UI_Console(UI_CONSOLE_STATE *s); diff --git a/src/libtrx/include/libtrx/game/ui/hud/console_logs.h b/src/libtrx/include/libtrx/game/ui/hud/console_logs.h deleted file mode 100644 index 0f97938e6..000000000 --- a/src/libtrx/include/libtrx/game/ui/hud/console_logs.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include -#include - -// Scrollback for the dev console. - -typedef struct { - char *text; - double expire_at; -} UI_CONSOLE_LOG_LINE; - -typedef struct { - size_t max_lines; - size_t vis_lines; - UI_CONSOLE_LOG_LINE *logs; - int32_t listener_id; -} UI_CONSOLE_LOGS; - -// state functions -void UI_ConsoleLogs_Init(UI_CONSOLE_LOGS *s); -void UI_ConsoleLogs_Free(UI_CONSOLE_LOGS *s); -void UI_ConsoleLogs(UI_CONSOLE_LOGS *s); diff --git a/src/libtrx/include/libtrx/game/ui/widgets/base.h b/src/libtrx/include/libtrx/game/ui/widgets/base.h new file mode 100644 index 000000000..2c8fae478 --- /dev/null +++ b/src/libtrx/include/libtrx/game/ui/widgets/base.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +struct UI_WIDGET; + +typedef void (*UI_WIDGET_CONTROL)(struct UI_WIDGET *self); +typedef void (*UI_WIDGET_DRAW)(struct UI_WIDGET *self); +typedef int32_t (*UI_WIDGET_GET_WIDTH)(const struct UI_WIDGET *self); +typedef int32_t (*UI_WIDGET_GET_HEIGHT)(const struct UI_WIDGET *self); +typedef void (*UI_WIDGET_SET_POSITION)( + struct UI_WIDGET *self, int32_t x, int32_t y); +typedef void (*UI_WIDGET_FREE)(struct UI_WIDGET *self); + +typedef struct UI_WIDGET { + UI_WIDGET_CONTROL control; + UI_WIDGET_DRAW draw; + UI_WIDGET_GET_WIDTH get_width; + UI_WIDGET_GET_HEIGHT get_height; + UI_WIDGET_SET_POSITION set_position; + UI_WIDGET_FREE free; + bool is_hidden; +} UI_WIDGET; + +typedef UI_WIDGET UI_WIDGET_VTABLE; diff --git a/src/libtrx/include/libtrx/game/ui/widgets/console.h b/src/libtrx/include/libtrx/game/ui/widgets/console.h new file mode 100644 index 000000000..26ddacdfb --- /dev/null +++ b/src/libtrx/include/libtrx/game/ui/widgets/console.h @@ -0,0 +1,12 @@ +#pragma once + +#include "./base.h" + +UI_WIDGET *UI_Console_Create(void); + +void UI_Console_HandleOpen(UI_WIDGET *widget); +void UI_Console_HandleClose(UI_WIDGET *widget); +void UI_Console_HandleLog(UI_WIDGET *widget, const char *text); +void UI_Console_ScrollLogs(UI_WIDGET *widget); +int32_t UI_Console_GetVisibleLogCount(UI_WIDGET *widget); +int32_t UI_Console_GetMaxLogCount(UI_WIDGET *widget); diff --git a/src/libtrx/include/libtrx/game/ui/widgets/frame.h b/src/libtrx/include/libtrx/game/ui/widgets/frame.h new file mode 100644 index 000000000..81e8cb4e2 --- /dev/null +++ b/src/libtrx/include/libtrx/game/ui/widgets/frame.h @@ -0,0 +1,6 @@ +#pragma once + +#include "./base.h" + +UI_WIDGET *UI_Frame_Create(UI_WIDGET *root, int32_t margin, int32_t padding); +void UI_Frame_SetFrameVisible(UI_WIDGET *widget, bool is_frame_visible); diff --git a/src/libtrx/include/libtrx/game/ui/widgets/label.h b/src/libtrx/include/libtrx/game/ui/widgets/label.h new file mode 100644 index 000000000..fdf2c7bdf --- /dev/null +++ b/src/libtrx/include/libtrx/game/ui/widgets/label.h @@ -0,0 +1,20 @@ +#pragma once + +#include "./base.h" + +#define UI_LABEL_AUTO_SIZE (-1) + +UI_WIDGET *UI_Label_Create(const char *text, int32_t width, int32_t height); + +void UI_Label_ChangeText(UI_WIDGET *widget, const char *text); +const char *UI_Label_GetText(UI_WIDGET *widget); +void UI_Label_SetSize(UI_WIDGET *widget, int32_t width, int32_t height); +void UI_Label_SetVisible(UI_WIDGET *widget, bool visible); + +void UI_Label_AddFrame(UI_WIDGET *widget); +void UI_Label_RemoveFrame(UI_WIDGET *widget); +void UI_Label_Flash(UI_WIDGET *widget, bool enable, int32_t rate); +void UI_Label_SetScale(UI_WIDGET *widget, float scale); +void UI_Label_SetZIndex(UI_WIDGET *widget, int32_t z_index); +int32_t UI_Label_MeasureTextWidth(UI_WIDGET *widget); +int32_t UI_Label_MeasureTextHeight(UI_WIDGET *widget); diff --git a/src/libtrx/include/libtrx/game/ui/widgets/photo_mode.h b/src/libtrx/include/libtrx/game/ui/widgets/photo_mode.h new file mode 100644 index 000000000..bb8babe65 --- /dev/null +++ b/src/libtrx/include/libtrx/game/ui/widgets/photo_mode.h @@ -0,0 +1,5 @@ +#pragma once + +#include "./base.h" + +UI_WIDGET *UI_PhotoMode_Create(void); diff --git a/src/libtrx/include/libtrx/game/ui/widgets/prompt.h b/src/libtrx/include/libtrx/game/ui/widgets/prompt.h new file mode 100644 index 000000000..91e51d97d --- /dev/null +++ b/src/libtrx/include/libtrx/game/ui/widgets/prompt.h @@ -0,0 +1,10 @@ +#pragma once + +#include "./base.h" + +UI_WIDGET *UI_Prompt_Create(int32_t width, int32_t height); +void UI_Prompt_SetSize(UI_WIDGET *widget, int32_t width, int32_t height); + +void UI_Prompt_SetFocus(UI_WIDGET *widget, bool is_focused); +void UI_Prompt_Clear(UI_WIDGET *widget); +void UI_Prompt_ChangeText(UI_WIDGET *widget, const char *new_text); diff --git a/src/libtrx/include/libtrx/game/ui/widgets/requester.h b/src/libtrx/include/libtrx/game/ui/widgets/requester.h new file mode 100644 index 000000000..c30848b83 --- /dev/null +++ b/src/libtrx/include/libtrx/game/ui/widgets/requester.h @@ -0,0 +1,25 @@ +#pragma once + +#include "./base.h" + +typedef struct { + bool is_selectable; + int32_t visible_rows; + int32_t width; + int32_t row_height; +} UI_REQUESTER_SETTINGS; + +UI_WIDGET *UI_Requester_Create(UI_REQUESTER_SETTINGS settings); +int32_t UI_Requester_GetSelectedRow(UI_WIDGET *requester); +void UI_Requester_ClearRows(UI_WIDGET *requester); +void UI_Requester_SetTitle(UI_WIDGET *requester, const char *title); +void UI_Requester_AddRowLR( + UI_WIDGET *requester, const char *text_l, const char *text_r, + void *user_data); +void UI_Requester_AddRowC( + UI_WIDGET *requester, const char *text, void *user_data); +void *UI_Requester_GetRowUserData(UI_WIDGET *widget, int32_t idx); +int32_t UI_Requester_GetRowCount(UI_WIDGET *widget); +void UI_Requester_ChangeRowLR( + UI_WIDGET *widget, int32_t idx, const char *text_l, const char *text_r, + void *user_data); diff --git a/src/libtrx/include/libtrx/game/ui/widgets/spacer.h b/src/libtrx/include/libtrx/game/ui/widgets/spacer.h new file mode 100644 index 000000000..9c20e210d --- /dev/null +++ b/src/libtrx/include/libtrx/game/ui/widgets/spacer.h @@ -0,0 +1,6 @@ +#pragma once + +#include "./base.h" + +UI_WIDGET *UI_Spacer_Create(int32_t width, int32_t height); +void UI_Spacer_SetSize(UI_WIDGET *widget, int32_t width, int32_t height); diff --git a/src/libtrx/include/libtrx/game/ui/widgets/stack.h b/src/libtrx/include/libtrx/game/ui/widgets/stack.h new file mode 100644 index 000000000..1f1d675c7 --- /dev/null +++ b/src/libtrx/include/libtrx/game/ui/widgets/stack.h @@ -0,0 +1,33 @@ +#pragma once + +#include "./base.h" + +#define UI_STACK_AUTO_SIZE (-1) + +typedef enum { + UI_STACK_LAYOUT_HORIZONTAL, + UI_STACK_LAYOUT_VERTICAL, +} UI_STACK_LAYOUT; + +typedef enum { + UI_STACK_H_ALIGN_LEFT, + UI_STACK_H_ALIGN_CENTER, + UI_STACK_H_ALIGN_RIGHT, + UI_STACK_H_ALIGN_DISTRIBUTE, +} UI_STACK_H_ALIGN; + +typedef enum { + UI_STACK_V_ALIGN_TOP, + UI_STACK_V_ALIGN_CENTER, + UI_STACK_V_ALIGN_BOTTOM, + UI_STACK_V_ALIGN_DISTRIBUTE, +} UI_STACK_V_ALIGN; + +UI_WIDGET *UI_Stack_Create( + UI_STACK_LAYOUT layout, int32_t width, int32_t height); +void UI_Stack_SetHAlign(UI_WIDGET *self, UI_STACK_H_ALIGN align); +void UI_Stack_SetVAlign(UI_WIDGET *self, UI_STACK_V_ALIGN align); +void UI_Stack_ClearChildren(UI_WIDGET *self); +void UI_Stack_AddChild(UI_WIDGET *self, UI_WIDGET *child); +void UI_Stack_SetSize(UI_WIDGET *widget, int32_t width, int32_t height); +void UI_Stack_DoLayout(UI_WIDGET *self); diff --git a/src/libtrx/include/libtrx/game/ui/dialogs/stats.h b/src/libtrx/include/libtrx/game/ui/widgets/stats_dialog.h similarity index 50% rename from src/libtrx/include/libtrx/game/ui/dialogs/stats.h rename to src/libtrx/include/libtrx/game/ui/widgets/stats_dialog.h index 408310c45..62c1d58b0 100644 --- a/src/libtrx/include/libtrx/game/ui/dialogs/stats.h +++ b/src/libtrx/include/libtrx/game/ui/widgets/stats_dialog.h @@ -1,7 +1,6 @@ #pragma once -#include "../common.h" -#include "../elements/requester.h" +#include "./base.h" typedef enum { UI_STATS_DIALOG_MODE_LEVEL, @@ -22,13 +21,4 @@ typedef struct { int32_t level_num; } UI_STATS_DIALOG_ARGS; -typedef struct { - UI_STATS_DIALOG_ARGS args; - UI_REQUESTER_STATE assault_req; -} UI_STATS_DIALOG_STATE; - -void UI_StatsDialog_Init(UI_STATS_DIALOG_STATE *s, UI_STATS_DIALOG_ARGS args); -void UI_StatsDialog_Free(UI_STATS_DIALOG_STATE *s); -int32_t UI_StatsDialog_Control(UI_STATS_DIALOG_STATE *s); - -extern void UI_StatsDialog(UI_STATS_DIALOG_STATE *s); +UI_WIDGET *UI_StatsDialog_Create(UI_STATS_DIALOG_ARGS args); diff --git a/src/libtrx/include/libtrx/game/ui/widgets/window.h b/src/libtrx/include/libtrx/game/ui/widgets/window.h new file mode 100644 index 000000000..33f896433 --- /dev/null +++ b/src/libtrx/include/libtrx/game/ui/widgets/window.h @@ -0,0 +1,10 @@ +#pragma once + +#include "./base.h" + +UI_WIDGET *UI_Window_Create( + UI_WIDGET *root, int32_t border_top, int32_t border_right, + int32_t border_bottom, int32_t border_left); + +void UI_Window_SetTitle(UI_WIDGET *widget, const char *text); +void UI_Window_SetRootWidget(UI_WIDGET *widget, UI_WIDGET *root); diff --git a/src/libtrx/include/libtrx/game/viewport.h b/src/libtrx/include/libtrx/game/viewport.h index 4f9694117..cd234ea38 100644 --- a/src/libtrx/include/libtrx/game/viewport.h +++ b/src/libtrx/include/libtrx/game/viewport.h @@ -11,7 +11,5 @@ extern int16_t Viewport_GetFOV(bool resolve_user_fov); extern void Viewport_AlterFOV(int16_t view_angle); #endif -extern int32_t Viewport_GetWidth(void); -extern int32_t Viewport_GetHeight(void); extern int32_t Viewport_GetMaxX(void); extern int32_t Viewport_GetMaxY(void); diff --git a/src/libtrx/include/libtrx/gfx/3d/3d_renderer.h b/src/libtrx/include/libtrx/gfx/3d/3d_renderer.h index 52760b6aa..368dadd2e 100644 --- a/src/libtrx/include/libtrx/gfx/3d/3d_renderer.h +++ b/src/libtrx/include/libtrx/gfx/3d/3d_renderer.h @@ -71,5 +71,3 @@ void GFX_3D_Renderer_SetAlphaPointDiscard( void GFX_3D_Renderer_SetAlphaThreshold(GFX_3D_RENDERER *renderer, float value); void GFX_3D_Renderer_SetBrightnessMultiplier( GFX_3D_RENDERER *renderer, float value); - -void GFX_3D_Renderer_SetProjectionMatrix(GFX_3D_RENDERER *renderer); diff --git a/src/libtrx/include/libtrx/gfx/3d/vertex_stream.h b/src/libtrx/include/libtrx/gfx/3d/vertex_stream.h index e1eb230c5..53f43a751 100644 --- a/src/libtrx/include/libtrx/gfx/3d/vertex_stream.h +++ b/src/libtrx/include/libtrx/gfx/3d/vertex_stream.h @@ -34,6 +34,8 @@ typedef struct { size_t count; size_t capacity; } pending_vertices; + size_t rendered_count; + size_t transferred; } GFX_3D_VERTEX_STREAM; void GFX_3D_VertexStream_Init(GFX_3D_VERTEX_STREAM *vertex_stream); diff --git a/src/libtrx/include/libtrx/gfx/common.h b/src/libtrx/include/libtrx/gfx/common.h index 0becb25fe..7ced70725 100644 --- a/src/libtrx/include/libtrx/gfx/common.h +++ b/src/libtrx/include/libtrx/gfx/common.h @@ -15,5 +15,6 @@ typedef enum { typedef enum { GFX_GL_INVALID_BACKEND, + GFX_GL_21, GFX_GL_33C, } GFX_GL_BACKEND; diff --git a/src/libtrx/include/libtrx/gfx/context.h b/src/libtrx/include/libtrx/gfx/context.h index ad94e83aa..a5bc6e9f0 100644 --- a/src/libtrx/include/libtrx/gfx/context.h +++ b/src/libtrx/include/libtrx/gfx/context.h @@ -21,8 +21,6 @@ void GFX_Context_SetDisplaySize(int32_t width, int32_t height); void GFX_Context_SetRenderingMode(GFX_RENDER_MODE target_mode); void *GFX_Context_GetWindowHandle(void); -int32_t GFX_Context_GetWindowWidth(void); -int32_t GFX_Context_GetWindowHeight(void); int32_t GFX_Context_GetDisplayWidth(void); int32_t GFX_Context_GetDisplayHeight(void); diff --git a/src/libtrx/include/libtrx/gfx/gl/program.h b/src/libtrx/include/libtrx/gfx/gl/program.h index 0bbfadc44..dac362ad0 100644 --- a/src/libtrx/include/libtrx/gfx/gl/program.h +++ b/src/libtrx/include/libtrx/gfx/gl/program.h @@ -7,12 +7,13 @@ typedef struct { bool initialized; GLuint id; + int32_t uniform_updates; } GFX_GL_PROGRAM; bool GFX_GL_Program_Init(GFX_GL_PROGRAM *program); void GFX_GL_Program_Close(GFX_GL_PROGRAM *program); -void GFX_GL_Program_Bind(const GFX_GL_PROGRAM *program); +void GFX_GL_Program_Bind(GFX_GL_PROGRAM *program); char *GFX_GL_Program_PreprocessShader( const char *content, GLenum type, GFX_GL_BACKEND backend); void GFX_GL_Program_AttachShader( @@ -29,8 +30,6 @@ void GFX_GL_Program_Uniform4f( GLfloat v3); void GFX_GL_Program_Uniform1i(GFX_GL_PROGRAM *program, GLint loc, GLint v0); void GFX_GL_Program_Uniform1f(GFX_GL_PROGRAM *program, GLint loc, GLfloat v0); -void GFX_GL_Program_Uniform2f( - GFX_GL_PROGRAM *program, GLint loc, GLfloat v0, GLfloat v1); void GFX_GL_Program_UniformMatrix4fv( GFX_GL_PROGRAM *program, GLint loc, GLsizei count, GLboolean transpose, const GLfloat *value); diff --git a/src/libtrx/include/libtrx/gfx/gl/track.h b/src/libtrx/include/libtrx/gfx/gl/track.h deleted file mode 100644 index 0c46e0d98..000000000 --- a/src/libtrx/include/libtrx/gfx/gl/track.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include - -typedef struct { - int32_t buffer_transfer_count; - int32_t buffer_total_bytes; - int32_t uniform_changes; -} GFX_METRICS; - -extern GFX_METRICS g_GFX_Metrics; - -#define GFX_TRACK_UNIFORM(fn, ...) \ - do { \ - g_GFX_Metrics.uniform_changes++; \ - fn(__VA_ARGS__); \ - } while (0); - -#define GFX_TRACK_DATA(fn, a, b, c, d) \ - do { \ - g_GFX_Metrics.buffer_total_bytes += b; \ - g_GFX_Metrics.buffer_transfer_count++; \ - fn(a, b, c, d); \ - } while (0); - -#define GFX_TRACK_SUBDATA(fn, a, b, c, d) \ - do { \ - g_GFX_Metrics.buffer_total_bytes += c; \ - g_GFX_Metrics.buffer_transfer_count++; \ - fn(a, b, c, d); \ - } while (0); - -void GFX_Track_Reset(void); -GFX_METRICS GFX_Track_GetMetrics(void); diff --git a/src/libtrx/include/libtrx/gfx/gl/utils.h b/src/libtrx/include/libtrx/gfx/gl/utils.h index 1dc4fe111..b1aabec7a 100644 --- a/src/libtrx/include/libtrx/gfx/gl/utils.h +++ b/src/libtrx/include/libtrx/gfx/gl/utils.h @@ -1,7 +1,6 @@ #pragma once #include "../../log.h" -#include "track.h" #include diff --git a/src/libtrx/include/libtrx/gfx/gl/vertex_array.h b/src/libtrx/include/libtrx/gfx/gl/vertex_array.h index 048fabe61..adba0518b 100644 --- a/src/libtrx/include/libtrx/gfx/gl/vertex_array.h +++ b/src/libtrx/include/libtrx/gfx/gl/vertex_array.h @@ -13,6 +13,3 @@ void GFX_GL_VertexArray_Bind(GFX_GL_VERTEX_ARRAY *array); void GFX_GL_VertexArray_Attribute( GFX_GL_VERTEX_ARRAY *array, GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLsizei offset); -void GFX_GL_VertexArray_IAttribute( - GFX_GL_VERTEX_ARRAY *array, GLuint index, GLint size, GLenum type, - GLsizei stride, GLsizei offset); diff --git a/src/libtrx/include/libtrx/strings/common.h b/src/libtrx/include/libtrx/strings/common.h index 01e65d892..159f0d1a4 100644 --- a/src/libtrx/include/libtrx/strings/common.h +++ b/src/libtrx/include/libtrx/strings/common.h @@ -1,6 +1,5 @@ #pragma once -#include "../colors.h" #include "../vector.h" #include @@ -15,7 +14,6 @@ bool String_IsEmpty(const char *value); bool String_ParseBool(const char *value, bool *target); bool String_ParseInteger(const char *value, int32_t *target); bool String_ParseDecimal(const char *value, float *target); -bool String_ParseRGB888(const char *value, RGB_888 *target); char *String_ToUpper(const char *text); diff --git a/src/libtrx/include/libtrx/vector.h b/src/libtrx/include/libtrx/vector.h index ab4747ed3..334d2f6ae 100644 --- a/src/libtrx/include/libtrx/vector.h +++ b/src/libtrx/include/libtrx/vector.h @@ -15,7 +15,6 @@ typedef struct { VECTOR *Vector_Create(size_t item_size); VECTOR *Vector_CreateAtCapacity(size_t item_size, int32_t capacity); -void Vector_EnsureCapacity(VECTOR *vector, int32_t capacity); void Vector_Free(VECTOR *vector); int32_t Vector_IndexOf(const VECTOR *vector, const void *item); @@ -23,9 +22,8 @@ int32_t Vector_LastIndexOf(const VECTOR *vector, const void *item); bool Vector_Contains(const VECTOR *vector, const void *item); void *Vector_Get(VECTOR *vector, int32_t index); -void *Vector_GetData(VECTOR *vector); -void Vector_Add(VECTOR *vector, const void *item); -void Vector_Insert(VECTOR *vector, int32_t index, const void *item); +void Vector_Add(VECTOR *vector, void *item); +void Vector_Insert(VECTOR *vector, int32_t index, void *item); void Vector_Swap(VECTOR *vector, int32_t index1, int32_t index2); bool Vector_Remove(VECTOR *vector, const void *item); diff --git a/src/libtrx/meson.build b/src/libtrx/meson.build index d93000c31..224994bc0 100644 --- a/src/libtrx/meson.build +++ b/src/libtrx/meson.build @@ -33,7 +33,6 @@ build_opts = [ '-DPCRE2_STATIC', '-DPCRE2_CODE_UNIT_WIDTH=8', '-DTR_VERSION=' + tr_version.to_string(), - '-DDEBUG=' + (get_option('buildtype') == 'debug' ? '1' : '0'), ] set_variable('defines', ['-DTR_VERSION=' + tr_version.to_string()]) @@ -120,7 +119,6 @@ sources = [ 'game/console/common.c', 'game/console/history.c', 'game/console/registry.c', - 'game/creature/common.c', 'game/demo/common.c', 'game/fader.c', 'game/game.c', @@ -136,7 +134,6 @@ sources = [ 'game/game_string_table/priv.c', 'game/game_string_table/reader.c', 'game/gun/common.c', - 'game/gym.c', 'game/inject/common.c', 'game/inject/data/anims.c', 'game/inject/data/meshes.c', @@ -164,25 +161,14 @@ sources = [ 'game/lara/misc.c', 'game/level/common.c', 'game/level/faces.c', - 'game/level/settings.c', 'game/math/trig.c', 'game/math/util.c', 'game/matrix.c', 'game/music.c', 'game/objects/common.c', - 'game/objects/creatures/bear.c', - 'game/objects/creatures/wolf.c', - 'game/objects/general/bridge_common.c', - 'game/objects/general/bridge_flat.c', - 'game/objects/general/bridge_tilt1.c', - 'game/objects/general/bridge_tilt2.c', - 'game/objects/general/door.c', - 'game/objects/general/drawbridge.c', - 'game/objects/general/trapdoor.c', 'game/objects/names.c', 'game/objects/traps/movable_block.c', 'game/objects/vars.c', - 'game/output/background.c', 'game/output/common.c', 'game/output/textures.c', 'game/packer.c', @@ -199,46 +185,22 @@ sources = [ 'game/random.c', 'game/rooms/common.c', 'game/rooms/draw.c', - 'game/savegame/common.c', - 'game/scaler.c', + 'game/savegame.c', 'game/shell/common.c', 'game/shell/main.c', 'game/sound.c', 'game/text.c', 'game/ui/common.c', - 'game/ui/dialogs/base_passport.c', - 'game/ui/dialogs/controls.c', - 'game/ui/dialogs/controls_backend.c', - 'game/ui/dialogs/controls_editor.c', - 'game/ui/dialogs/examine_item.c', - 'game/ui/dialogs/new_game.c', - 'game/ui/dialogs/pause.c', - 'game/ui/dialogs/photo_mode.c', - 'game/ui/dialogs/play_any_level.c', - 'game/ui/dialogs/save_slot.c', - 'game/ui/dialogs/select_level.c', - 'game/ui/dialogs/sound_settings.c', - 'game/ui/dialogs/stats.c', - 'game/ui/elements/anchor.c', - 'game/ui/elements/fade.c', - 'game/ui/elements/fixed.c', - 'game/ui/elements/flash.c', - 'game/ui/elements/frame.c', - 'game/ui/elements/hide.c', - 'game/ui/elements/label.c', - 'game/ui/elements/modal.c', - 'game/ui/elements/offset.c', - 'game/ui/elements/pad.c', - 'game/ui/elements/prompt.c', - 'game/ui/elements/requester.c', - 'game/ui/elements/resize.c', - 'game/ui/elements/spacer.c', - 'game/ui/elements/stack.c', - 'game/ui/elements/window.c', 'game/ui/events.c', - 'game/ui/helpers.c', - 'game/ui/hud/console.c', - 'game/ui/hud/console_logs.c', + 'game/ui/widgets/console.c', + 'game/ui/widgets/frame.c', + 'game/ui/widgets/label.c', + 'game/ui/widgets/photo_mode.c', + 'game/ui/widgets/prompt.c', + 'game/ui/widgets/requester.c', + 'game/ui/widgets/spacer.c', + 'game/ui/widgets/stack.c', + 'game/ui/widgets/window.c', 'gfx/2d/2d_renderer.c', 'gfx/2d/2d_surface.c', 'gfx/3d/3d_renderer.c', @@ -249,7 +211,6 @@ sources = [ 'gfx/gl/program.c', 'gfx/gl/sampler.c', 'gfx/gl/texture.c', - 'gfx/gl/track.c', 'gfx/gl/utils.c', 'gfx/gl/vertex_array.c', 'gfx/renderers/fbo_renderer.c', diff --git a/src/libtrx/strings/common.c b/src/libtrx/strings/common.c index a3fad4f90..353f89f5f 100644 --- a/src/libtrx/strings/common.c +++ b/src/libtrx/strings/common.c @@ -174,16 +174,6 @@ bool String_ParseDecimal(const char *const value, float *const target) return true; } -bool String_ParseRGB888(const char *value, RGB_888 *const target) -{ - if (value[0] == '#') { - value++; - } - return sscanf( - value, "%02hhX%02hhX%02hhX", &target->r, &target->g, &target->b) - == 3; -} - char *String_ToUpper(const char *text) { if (text == nullptr) { @@ -317,7 +307,7 @@ char *String_Format(const char *const fmt, ...) va_list args; va_start(args, fmt); - int len = vsnprintf(nullptr, 0, fmt, args); + int len = vsnprintf(NULL, 0, fmt, args); if (len < 0) { va_end(args); return nullptr; diff --git a/src/libtrx/vector.c b/src/libtrx/vector.c index d4b31bf4e..21c8ce53d 100644 --- a/src/libtrx/vector.c +++ b/src/libtrx/vector.c @@ -42,16 +42,6 @@ VECTOR *Vector_CreateAtCapacity(const size_t item_size, const int32_t capacity) return vector; } -void Vector_EnsureCapacity(VECTOR *const vector, const int32_t capacity) -{ - if (vector->capacity >= capacity) { - return; - } - vector->capacity = capacity; - P(vector).items = - Memory_Realloc(P(vector).items, vector->item_size * capacity); -} - void Vector_Free(VECTOR *vector) { Memory_FreePointer(&P(vector).items); @@ -96,19 +86,13 @@ void *Vector_Get(VECTOR *const vector, const int32_t index) return (void *)(items + index * vector->item_size); } -void *Vector_GetData(VECTOR *const vector) -{ - return P(vector).items; -} - -void Vector_Add(VECTOR *const vector, const void *const item) +void Vector_Add(VECTOR *const vector, void *const item) { M_EnsureCapacity(vector, 1); Vector_Insert(vector, vector->count, item); } -void Vector_Insert( - VECTOR *const vector, const int32_t index, const void *const item) +void Vector_Insert(VECTOR *const vector, const int32_t index, void *const item) { ASSERT(index >= 0 && index <= vector->count); M_EnsureCapacity(vector, 1); diff --git a/src/libtrx/include/libtrx/version.h b/src/libtrx/version.h similarity index 100% rename from src/libtrx/include/libtrx/version.h rename to src/libtrx/version.h diff --git a/src/tr1/game/console/common.c b/src/tr1/game/console/common.c index 9f3b2e4c5..f2b978358 100644 --- a/src/tr1/game/console/common.c +++ b/src/tr1/game/console/common.c @@ -11,7 +11,7 @@ void Console_DrawBackdrop(void) int32_t sw = Viewport_GetWidth(); int32_t sh = Screen_GetRenderScale( // not entirely accurate, but good enough - TEXT_HEIGHT_FIXED * 1.0 + 7 * TEXT_HEIGHT_FIXED * 0.8, RSR_TEXT); + TEXT_HEIGHT * 1.0 + 10 * TEXT_HEIGHT * 0.8, RSR_TEXT); int32_t sy = Viewport_GetHeight() - sh; RGBA_8888 top = { 0, 0, 0, 0 }; diff --git a/src/tr1/game/creature.c b/src/tr1/game/creature.c index d5ee8ea3a..abf606c1e 100644 --- a/src/tr1/game/creature.c +++ b/src/tr1/game/creature.c @@ -1,27 +1,684 @@ #include "game/creature.h" +#include "game/box.h" +#include "game/carrier.h" #include "game/effects.h" +#include "game/items.h" #include "game/lara/common.h" +#include "game/los.h" +#include "game/lot.h" +#include "game/objects/common.h" #include "game/objects/vars.h" #include "game/random.h" +#include "game/room.h" #include "game/spawn.h" +#include "global/vars.h" + +#include +#include +#include + +#define MAX_CREATURE_DISTANCE (WALL_L * 30) + +static bool M_SwitchToWater( + int16_t item_num, const int32_t *wh, const HYBRID_INFO *info); +static bool M_SwitchToLand( + int16_t item_num, const int32_t *wh, const HYBRID_INFO *info); +static bool M_TestSwitchOrKill(int16_t item_num, GAME_OBJECT_ID target_id); + +void Creature_Initialise(int16_t item_num) +{ + ITEM *const item = Item_Get(item_num); + + item->rot.y += (PHD_ANGLE)((Random_GetControl() - DEG_90) >> 1); + item->collidable = 1; + item->data = nullptr; +} + +void Creature_AIInfo(ITEM *item, AI_INFO *info) +{ + CREATURE *creature = item->data; + if (!creature) { + return; + } + + const int16_t *const zone = Box_GetLotZone(&creature->lot); + + const ROOM *room = Room_Get(item->room_num); + item->box_num = Room_GetWorldSector(room, item->pos.x, item->pos.z)->box; + info->zone_num = zone[item->box_num]; + + room = Room_Get(g_LaraItem->room_num); + g_LaraItem->box_num = + Room_GetWorldSector(room, g_LaraItem->pos.x, g_LaraItem->pos.z)->box; + info->enemy_zone = zone[g_LaraItem->box_num]; + + if (Box_GetBox(g_LaraItem->box_num)->overlap_index + & creature->lot.block_mask) { + info->enemy_zone |= BOX_BLOCKED; + } else if ( + creature->lot.node[item->box_num].search_num + == (creature->lot.search_num | BOX_BLOCKED_SEARCH)) { + info->enemy_zone |= BOX_BLOCKED; + } + + const OBJECT *const obj = Object_Get(item->object_id); + int32_t z = g_LaraItem->pos.z + - ((Math_Cos(item->rot.y) * obj->pivot_length) >> W2V_SHIFT) + - item->pos.z; + int32_t x = g_LaraItem->pos.x + - ((Math_Sin(item->rot.y) * obj->pivot_length) >> W2V_SHIFT) + - item->pos.x; + + PHD_ANGLE angle = Math_Atan(z, x); + info->distance = SQUARE(x) + SQUARE(z); + if (ABS(x) > MAX_CREATURE_DISTANCE || ABS(z) > MAX_CREATURE_DISTANCE) { + info->distance = SQUARE(MAX_CREATURE_DISTANCE); + } + info->angle = angle - item->rot.y; + info->enemy_facing = angle - g_LaraItem->rot.y + DEG_180; + info->ahead = info->angle > -FRONT_ARC && info->angle < FRONT_ARC; + info->bite = info->ahead && (g_LaraItem->pos.y > item->pos.y - STEP_L) + && (g_LaraItem->pos.y < item->pos.y + STEP_L); +} + +void Creature_Mood(ITEM *item, AI_INFO *info, bool violent) +{ + CREATURE *creature = item->data; + if (!creature) { + return; + } + + LOT_INFO *lot = &creature->lot; + const ITEM *const enemy = g_LaraItem; + if (lot->node[item->box_num].search_num + == (lot->search_num | BOX_BLOCKED_SEARCH)) { + lot->required_box = NO_BOX; + } + + if (creature->mood != MOOD_ATTACK && lot->required_box != NO_BOX + && !Box_ValidBox(item, info->zone_num, lot->target_box)) { + if (info->zone_num == info->enemy_zone) { + creature->mood = MOOD_BORED; + } + lot->required_box = NO_BOX; + } + + MOOD_TYPE mood = creature->mood; + + if (enemy->hit_points <= 0) { + creature->mood = MOOD_BORED; + } else if (violent) { + switch (mood) { + case MOOD_ATTACK: + if (info->zone_num != info->enemy_zone) { + creature->mood = MOOD_BORED; + } + break; + + case MOOD_BORED: + case MOOD_STALK: + if (info->zone_num == info->enemy_zone) { + creature->mood = MOOD_ATTACK; + } else if (item->hit_status) { + creature->mood = MOOD_ESCAPE; + } + break; + + case MOOD_ESCAPE: + if (info->zone_num == info->enemy_zone) { + creature->mood = MOOD_ATTACK; + } + break; + } + } else { + switch (mood) { + case MOOD_ATTACK: + if (item->hit_status + && (Random_GetControl() < ESCAPE_CHANCE + || info->zone_num != info->enemy_zone)) { + creature->mood = MOOD_ESCAPE; + } else if (info->zone_num != info->enemy_zone) { + creature->mood = MOOD_BORED; + } + break; + + case MOOD_BORED: + case MOOD_STALK: + if (item->hit_status + && (Random_GetControl() < ESCAPE_CHANCE + || info->zone_num != info->enemy_zone)) { + creature->mood = MOOD_ESCAPE; + } else if (info->zone_num == info->enemy_zone) { + if (info->distance < ATTACK_RANGE + || (creature->mood == MOOD_STALK + && lot->required_box == NO_BOX)) { + creature->mood = MOOD_ATTACK; + } else { + creature->mood = MOOD_STALK; + } + } + break; + + case MOOD_ESCAPE: + if (info->zone_num == info->enemy_zone + && Random_GetControl() < RECOVER_CHANCE) { + creature->mood = MOOD_STALK; + } + break; + } + } + + if (mood != creature->mood) { + if (mood == MOOD_ATTACK) { + Box_TargetBox(lot, lot->target_box); + } + lot->required_box = NO_BOX; + } + + switch (creature->mood) { + case MOOD_ATTACK: + if (Random_GetControl() < Object_Get(item->object_id)->smartness) { + lot->target.x = enemy->pos.x; + lot->target.y = enemy->pos.y; + lot->target.z = enemy->pos.z; + lot->required_box = enemy->box_num; + if (lot->fly && g_Lara.water_status == LWS_ABOVE_WATER) { + const ANIM_FRAME *const frame = Item_GetBestFrame(enemy); + lot->target.y += frame->bounds.min.y; + } + } + break; + + case MOOD_BORED: { + int box_num = + lot->node[Random_GetControl() * lot->zone_count / 0x7FFF].box_num; + if (Box_ValidBox(item, info->zone_num, box_num)) { + if (Box_StalkBox(item, enemy, box_num)) { + Box_TargetBox(lot, box_num); + creature->mood = MOOD_STALK; + } else if (lot->required_box == NO_BOX) { + Box_TargetBox(lot, box_num); + } + } + break; + } + + case MOOD_STALK: { + if (lot->required_box == NO_BOX + || !Box_StalkBox(item, enemy, lot->required_box)) { + int box_num = + lot->node[Random_GetControl() * lot->zone_count / 0x7FFF] + .box_num; + if (Box_ValidBox(item, info->zone_num, box_num)) { + if (Box_StalkBox(item, enemy, box_num)) { + Box_TargetBox(lot, box_num); + } else if (lot->required_box == NO_BOX) { + Box_TargetBox(lot, box_num); + if (info->zone_num != info->enemy_zone) { + creature->mood = MOOD_BORED; + } + } + } + } + break; + } + + case MOOD_ESCAPE: { + int box_num = + lot->node[Random_GetControl() * lot->zone_count / 0x7FFF].box_num; + if (Box_ValidBox(item, info->zone_num, box_num) + && lot->required_box == NO_BOX) { + if (Box_EscapeBox(item, enemy, box_num)) { + Box_TargetBox(lot, box_num); + } else if ( + info->zone_num == info->enemy_zone + && Box_StalkBox(item, enemy, box_num)) { + Box_TargetBox(lot, box_num); + creature->mood = MOOD_STALK; + } + } + break; + } + } + + if (lot->target_box == NO_BOX) { + Box_TargetBox(lot, item->box_num); + } + + Box_CalculateTarget(&creature->target, item, &creature->lot); +} + +int16_t Creature_Turn(ITEM *item, int16_t maximum_turn) +{ + CREATURE *creature = item->data; + if (!creature) { + return 0; + } + + if (!item->speed || !maximum_turn) { + return 0; + } + + int32_t x = creature->target.x - item->pos.x; + int32_t z = creature->target.z - item->pos.z; + int16_t angle = Math_Atan(z, x) - item->rot.y; + int32_t range = (item->speed << 14) / maximum_turn; + + if (angle > FRONT_ARC || angle < -FRONT_ARC) { + if (SQUARE(x) + SQUARE(z) < SQUARE(range)) { + maximum_turn >>= 1; + } + } + + if (angle > maximum_turn) { + angle = maximum_turn; + } else if (angle < -maximum_turn) { + angle = -maximum_turn; + } + + item->rot.y += angle; + + return angle; +} + +void Creature_Tilt(ITEM *item, int16_t angle) +{ + angle = angle * 4 - item->rot.z; + if (angle < -MAX_TILT) { + angle = -MAX_TILT; + } else if (angle > MAX_TILT) { + angle = MAX_TILT; + } + item->rot.z += angle; +} + +void Creature_Head(ITEM *item, int16_t required) +{ + CREATURE *creature = item->data; + if (!creature) { + return; + } + + int16_t change = required - creature->head_rotation; + if (change > MAX_HEAD_CHANGE) { + change = MAX_HEAD_CHANGE; + } else if (change < -MAX_HEAD_CHANGE) { + change = -MAX_HEAD_CHANGE; + } + + creature->head_rotation += change; + + if (creature->head_rotation > FRONT_ARC) { + creature->head_rotation = FRONT_ARC; + } else if (creature->head_rotation < -FRONT_ARC) { + creature->head_rotation = -FRONT_ARC; + } +} + +int16_t Creature_Effect( + ITEM *item, BITE *bite, + int16_t (*spawn)( + int32_t x, int32_t y, int32_t z, int16_t speed, int16_t yrot, + int16_t room_num)) +{ + XYZ_32 pos = { + .x = bite->x, + .y = bite->y, + .z = bite->z, + }; + Collide_GetJointAbsPosition(item, &pos, bite->mesh_num); + return spawn(pos.x, pos.y, pos.z, item->speed, item->rot.y, item->room_num); +} + +bool Creature_CheckBaddieOverlap(int16_t item_num) +{ + const ITEM *item = Item_Get(item_num); + + int32_t x = item->pos.x; + int32_t y = item->pos.y; + int32_t z = item->pos.z; + const int32_t radius = SQUARE(Object_Get(item->object_id)->radius); + + int16_t link = Room_Get(item->room_num)->item_num; + do { + item = Item_Get(link); + + if (link == item_num) { + return false; + } + + if (item != g_LaraItem && item->status == IS_ACTIVE + && item->speed != 0) { + int32_t distance = SQUARE(item->pos.x - x) + SQUARE(item->pos.y - y) + + SQUARE(item->pos.z - z); + if (distance < radius) { + return true; + } + } + + link = item->next_item; + } while (link != NO_ITEM); + + return false; +} + +void Creature_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll) +{ + ITEM *const item = Item_Get(item_num); + + if (!Lara_TestBoundsCollide(item, coll->radius)) { + return; + } + if (!Collide_TestCollision(item, lara_item)) { + return; + } + + if (coll->enable_baddie_push) { + if (item->hit_points <= 0) { + Lara_Push(item, coll, 0, 0); + } else { + Lara_Push(item, coll, coll->enable_hit, 0); + } + } +} + +bool Creature_Animate(int16_t item_num, int16_t angle, int16_t tilt) +{ + ITEM *const item = Item_Get(item_num); + CREATURE *creature = item->data; + if (!creature) { + return false; + } + LOT_INFO *lot = &creature->lot; + + XYZ_32 old = { + .x = item->pos.x, + .y = item->pos.y, + .z = item->pos.z, + }; + + const int32_t box_height = Box_GetBox(item->box_num)->height; + + const int16_t *const zone = Box_GetLotZone(lot); + + Item_Animate(item); + if (item->status == IS_DEACTIVATED) { + item->collidable = 0; + item->hit_points = DONT_TARGET; + LOT_DisableBaddieAI(item_num); + Item_RemoveActive(item_num); + Carrier_TestItemDrops(item_num); + return false; + } + + const BOUNDS_16 *const bounds = Item_GetBoundsAccurate(item); + int32_t y = item->pos.y + bounds->min.y; + + int16_t room_num = item->room_num; + const SECTOR *sector = + Room_GetSector(item->pos.x, y, item->pos.z, &room_num); + int32_t height = Box_GetBox(sector->box)->height; + int16_t next_box = lot->node[sector->box].exit_box; + int32_t next_height; + if (next_box != NO_BOX) { + next_height = Box_GetBox(next_box)->height; + } else { + next_height = height; + } + + int32_t pos_x; + int32_t pos_z; + int32_t shift_x; + int32_t shift_z; + if (sector->box == NO_BOX || zone[item->box_num] != zone[sector->box] + || box_height - height > lot->step || box_height - height < lot->drop) { + pos_x = item->pos.x >> WALL_SHIFT; + + shift_x = old.x >> WALL_SHIFT; + shift_z = old.z >> WALL_SHIFT; + + if (pos_x < shift_x) { + item->pos.x = old.x & (~(WALL_L - 1)); + } else if (pos_x > shift_x) { + item->pos.x = old.x | (WALL_L - 1); + } + + if (pos_x < shift_z) { + item->pos.z = old.z & (~(WALL_L - 1)); + } else if (pos_x > shift_z) { + item->pos.z = old.z | (WALL_L - 1); + } + + sector = Room_GetSector(item->pos.x, y, item->pos.z, &room_num); + height = Box_GetBox(sector->box)->height; + next_box = lot->node[sector->box].exit_box; + if (next_box != NO_BOX) { + next_height = Box_GetBox(next_box)->height; + } else { + next_height = height; + } + } + + int32_t x = item->pos.x; + int32_t z = item->pos.z; + + pos_x = x & (WALL_L - 1); + pos_z = z & (WALL_L - 1); + shift_x = 0; + shift_z = 0; + + const int32_t radius = Object_Get(item->object_id)->radius; + + if (pos_z < radius) { + if (Box_BadFloor( + x, y, z - radius, height, next_height, room_num, lot)) { + shift_z = radius - pos_z; + } + + if (pos_x < radius) { + if (Box_BadFloor( + x - radius, y, z, height, next_height, room_num, lot)) { + shift_x = radius - pos_x; + } else if ( + !shift_z + && Box_BadFloor( + x - radius, y, z - radius, height, next_height, room_num, + lot)) { + if (item->rot.y > -DEG_135 && item->rot.y < DEG_45) { + shift_z = radius - pos_z; + } else { + shift_x = radius - pos_x; + } + } + } else if (pos_x > WALL_L - radius) { + if (Box_BadFloor( + x + radius, y, z, height, next_height, room_num, lot)) { + shift_x = WALL_L - radius - pos_x; + } else if ( + !shift_z + && Box_BadFloor( + x + radius, y, z - radius, height, next_height, room_num, + lot)) { + if (item->rot.y > -DEG_45 && item->rot.y < DEG_135) { + shift_z = radius - pos_z; + } else { + shift_x = WALL_L - radius - pos_x; + } + } + } + } else if (pos_z > WALL_L - radius) { + if (Box_BadFloor( + x, y, z + radius, height, next_height, room_num, lot)) { + shift_z = WALL_L - radius - pos_z; + } + + if (pos_x < radius) { + if (Box_BadFloor( + x - radius, y, z, height, next_height, room_num, lot)) { + shift_x = radius - pos_x; + } else if ( + !shift_z + && Box_BadFloor( + x - radius, y, z + radius, height, next_height, room_num, + lot)) { + if (item->rot.y > -DEG_45 && item->rot.y < DEG_135) { + shift_x = radius - pos_x; + } else { + shift_z = WALL_L - radius - pos_z; + } + } + } else if (pos_x > WALL_L - radius) { + if (Box_BadFloor( + x + radius, y, z, height, next_height, room_num, lot)) { + shift_x = WALL_L - radius - pos_x; + } else if ( + !shift_z + && Box_BadFloor( + x + radius, y, z + radius, height, next_height, room_num, + lot)) { + if (item->rot.y > -DEG_135 && item->rot.y < DEG_45) { + shift_x = WALL_L - radius - pos_x; + } else { + shift_z = WALL_L - radius - pos_z; + } + } + } + } else if (pos_x < radius) { + if (Box_BadFloor( + x - radius, y, z, height, next_height, room_num, lot)) { + shift_x = radius - pos_x; + } + } else if (pos_x > WALL_L - radius) { + if (Box_BadFloor( + x + radius, y, z, height, next_height, room_num, lot)) { + shift_x = WALL_L - radius - pos_x; + } + } + + item->pos.x += shift_x; + item->pos.z += shift_z; + + if (shift_x || shift_z) { + sector = Room_GetSector(item->pos.x, y, item->pos.z, &room_num); + + item->rot.y += angle; + Creature_Tilt(item, tilt * 2); + } + + if (Creature_CheckBaddieOverlap(item_num)) { + item->pos.x = old.x; + item->pos.y = old.y; + item->pos.z = old.z; + return true; + } + + if (lot->fly) { + int32_t dy = creature->target.y - item->pos.y; + if (dy > lot->fly) { + dy = lot->fly; + } else if (dy < -lot->fly) { + dy = -lot->fly; + } + + height = Room_GetHeight(sector, item->pos.x, y, item->pos.z); + if (item->pos.y + dy > height) { + if (item->pos.y > height) { + item->pos.x = old.x; + item->pos.z = old.z; + dy = -lot->fly; + } else { + dy = 0; + item->pos.y = height; + } + } else { + int32_t ceiling = + Room_GetCeiling(sector, item->pos.x, y, item->pos.z); + + int32_t min_y = item->object_id == O_ALLIGATOR ? 0 : bounds->min.y; + if (item->pos.y + min_y + dy < ceiling) { + if (item->pos.y + min_y < ceiling) { + item->pos.x = old.x; + item->pos.z = old.z; + dy = lot->fly; + } else { + dy = 0; + } + } + } + + item->pos.y += dy; + sector = Room_GetSector(item->pos.x, y, item->pos.z, &room_num); + item->floor = Room_GetHeight(sector, item->pos.x, y, item->pos.z); + + angle = item->speed ? Math_Atan(item->speed, -dy) : 0; + if (angle < item->rot.x - DEG_1) { + item->rot.x -= DEG_1; + } else if (angle > item->rot.x + DEG_1) { + item->rot.x += DEG_1; + } else { + item->rot.x = angle; + } + } else { + sector = + Room_GetSector(item->pos.x, item->pos.y, item->pos.z, &room_num); + item->floor = + Room_GetHeight(sector, item->pos.x, item->pos.y, item->pos.z); + + if (item->pos.y > item->floor) { + item->pos.y = item->floor; + } else if (item->floor - item->pos.y > STEP_L / 4) { + item->pos.y += STEP_L / 4; + } else if (item->pos.y < item->floor) { + item->pos.y = item->floor; + } + + item->rot.x = 0; + } + + if (item->room_num != room_num) { + Item_NewRoom(item_num, room_num); + } + + return true; +} + +bool Creature_CanTargetEnemy(ITEM *item, AI_INFO *info) +{ + if (!info->ahead || info->distance >= CREATURE_SHOOT_RANGE) { + return false; + } + + GAME_VECTOR start; + start.x = item->pos.x; + start.y = item->pos.y - STEP_L * 3; + start.z = item->pos.z; + start.room_num = item->room_num; + + GAME_VECTOR target; + target.x = g_LaraItem->pos.x; + target.y = g_LaraItem->pos.y - STEP_L * 3; + target.z = g_LaraItem->pos.z; + + return LOS_Check(&start, &target); +} bool Creature_ShootAtLara( ITEM *item, int32_t distance, BITE *gun, int16_t extra_rotation, int16_t damage) { - bool is_hit; + bool hit; if (distance > CREATURE_SHOOT_RANGE) { - is_hit = false; + hit = false; } else { - is_hit = Random_GetControl() + hit = Random_GetControl() < ((CREATURE_SHOOT_RANGE - distance) / (CREATURE_SHOOT_RANGE / 0x7FFF) - CREATURE_MISS_CHANCE); } int16_t effect_num; - if (is_hit) { + if (hit) { effect_num = Creature_Effect(item, gun, Spawn_GunShotHit); } else { effect_num = Creature_Effect(item, gun, Spawn_GunShotMiss); @@ -31,11 +688,25 @@ bool Creature_ShootAtLara( Effect_Get(effect_num)->rot.y += extra_rotation; } - if (is_hit) { + if (hit) { Lara_TakeDamage(damage, true); } - return is_hit; + return hit; +} + +bool Creature_EnsureHabitat( + const int16_t item_num, int32_t *const wh, const HYBRID_INFO *const info) +{ + // Test the environment for a hybrid creature. Record the water height and + // return whether or not a type conversion has taken place. + const ITEM *const item = Item_Get(item_num); + *wh = Room_GetWaterHeight( + item->pos.x, item->pos.y, item->pos.z, item->room_num); + + return item->object_id == info->land.id + ? M_SwitchToWater(item_num, wh, info) + : M_SwitchToLand(item_num, wh, info); } bool Creature_IsBoss(const int16_t item_num) @@ -43,3 +714,100 @@ bool Creature_IsBoss(const int16_t item_num) const ITEM *const item = Item_Get(item_num); return Object_IsType(item->object_id, g_BossObjects); } + +static bool M_SwitchToWater( + const int16_t item_num, const int32_t *const wh, + const HYBRID_INFO *const info) +{ + if (*wh == NO_HEIGHT) { + return false; + } + + ITEM *const item = Item_Get(item_num); + + if (item->hit_points <= 0) { + // Dead land creatures should remain in their pose permanently. + return false; + } + + // The land creature is alive and the room has been flooded. Switch to the + // water creature. + if (!M_TestSwitchOrKill(item_num, info->water.id)) { + return false; + } + + item->object_id = info->water.id; + Item_SwitchToAnim(item, info->water.active_anim, 0); + item->current_anim_state = Item_GetAnim(item)->current_anim_state; + item->goal_anim_state = item->current_anim_state; + item->pos.y = *wh; + + return true; +} + +static bool M_SwitchToLand( + const int16_t item_num, const int32_t *const wh, + const HYBRID_INFO *const info) +{ + if (*wh != NO_HEIGHT) { + return false; + } + + if (!M_TestSwitchOrKill(item_num, info->land.id)) { + return false; + } + + ITEM *const item = Item_Get(item_num); + + // Switch to the land creature regardless of death state. + item->object_id = info->land.id; + item->rot.x = 0; + + if (item->hit_points > 0) { + Item_SwitchToAnim(item, info->land.active_anim, 0); + item->current_anim_state = Item_GetAnim(item)->current_anim_state; + item->goal_anim_state = item->current_anim_state; + + } else { + Item_SwitchToAnim(item, info->land.death_anim, -1); + item->current_anim_state = info->land.death_state; + item->goal_anim_state = item->current_anim_state; + + int16_t room_num = item->room_num; + const SECTOR *const sector = + Room_GetSector(item->pos.x, item->pos.y, item->pos.z, &room_num); + item->floor = + Room_GetHeight(sector, item->pos.x, item->pos.y, item->pos.z); + item->pos.y = item->floor; + + if (item->room_num != room_num) { + Item_NewRoom(item_num, room_num); + } + } + + return true; +} + +static bool M_TestSwitchOrKill( + const int16_t item_num, const GAME_OBJECT_ID target_id) +{ + if (Object_Get(target_id)->loaded) { + return true; + } + + LOG_WARNING( + "Object %d is not loaded; item %d cannot be converted.", target_id, + item_num); + Item_Kill(item_num); + return false; +} + +bool Creature_IsHostile(const ITEM *const item) +{ + return Object_IsType(item->object_id, g_EnemyObjects); +} + +bool Creature_IsAlly(const ITEM *const item) +{ + return Object_IsType(item->object_id, g_AllyObjects); +} diff --git a/src/tr1/game/creature.h b/src/tr1/game/creature.h index 68c2c50cb..53a07b190 100644 --- a/src/tr1/game/creature.h +++ b/src/tr1/game/creature.h @@ -1,8 +1,45 @@ #pragma once -#include +#include "global/const.h" +#include "global/types.h" -bool Creature_IsBoss(int16_t item_num); +#include +#include + +#define CREATURE_SHOOT_RANGE SQUARE(WALL_L * 7) // = 51380224 +#define CREATURE_MISS_CHANCE 0x2000 + +typedef struct { + struct { + GAME_OBJECT_ID id; + int16_t active_anim; + int16_t death_anim; + int16_t death_state; + } land; + struct { + GAME_OBJECT_ID id; + int16_t active_anim; + } water; +} HYBRID_INFO; + +void Creature_Initialise(int16_t item_num); +void Creature_AIInfo(ITEM *item, AI_INFO *info); +void Creature_Mood(ITEM *item, AI_INFO *info, bool violent); +int16_t Creature_Turn(ITEM *item, int16_t maximum_turn); +void Creature_Tilt(ITEM *item, int16_t angle); +void Creature_Head(ITEM *item, int16_t required); +int16_t Creature_Effect( + ITEM *item, BITE *bite, + int16_t (*spawn)( + int32_t x, int32_t y, int32_t z, int16_t speed, int16_t yrot, + int16_t room_num)); +bool Creature_CheckBaddieOverlap(int16_t item_num); +void Creature_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +bool Creature_Animate(int16_t item_num, int16_t angle, int16_t tilt); +bool Creature_CanTargetEnemy(ITEM *item, AI_INFO *info); bool Creature_ShootAtLara( ITEM *item, int32_t distance, BITE *gun, int16_t extra_rotation, int16_t damage); +bool Creature_EnsureHabitat( + int16_t item_num, int32_t *wh, const HYBRID_INFO *info); +bool Creature_IsBoss(int16_t item_num); diff --git a/src/tr1/game/effects.c b/src/tr1/game/effects.c index ac0ff9d67..9c9446577 100644 --- a/src/tr1/game/effects.c +++ b/src/tr1/game/effects.c @@ -1,6 +1,5 @@ #include "game/effects.h" -#include "game/objects/vars.h" #include "game/output.h" #include "game/room.h" #include "global/const.h" @@ -16,15 +15,15 @@ static int16_t m_NextEffectFree = NO_EFFECT; void Effect_InitialiseArray(void) { - m_Effects = GameBuf_Alloc(MAX_EFFECTS * sizeof(EFFECT), GBUF_EFFECTS); + m_Effects = GameBuf_Alloc(NUM_EFFECTS * sizeof(EFFECT), GBUF_EFFECTS); m_NextEffectActive = NO_EFFECT; m_NextEffectFree = 0; - for (int32_t i = 0; i < MAX_EFFECTS - 1; i++) { + for (int i = 0; i < NUM_EFFECTS - 1; i++) { m_Effects[i].next_draw = i + 1; m_Effects[i].next_free = i + 1; } - m_Effects[MAX_EFFECTS - 1].next_draw = NO_EFFECT; - m_Effects[MAX_EFFECTS - 1].next_free = NO_EFFECT; + m_Effects[NUM_EFFECTS - 1].next_draw = NO_EFFECT; + m_Effects[NUM_EFFECTS - 1].next_free = NO_EFFECT; } void Effect_Control(void) @@ -145,14 +144,10 @@ void Effect_Draw(const int16_t effect_num) } if (obj->mesh_count < 0) { - const RGB_F tint = - Object_IsType(effect->object_id, g_WaterSpriteObjects) - ? (RGB_F) { 1.0f, 1.0f, 1.0f } - : Output_GetTint(); Output_DrawSprite( effect->interp.result.pos.x, effect->interp.result.pos.y, effect->interp.result.pos.z, obj->mesh_idx - effect->frame_num, - SHADE_NEUTRAL, tint); + 4096); } else { Matrix_Push(); Matrix_TranslateAbs32(effect->interp.result.pos); diff --git a/src/tr1/game/fmv.c b/src/tr1/game/fmv.c index 595b7e863..7474c6bf3 100644 --- a/src/tr1/game/fmv.c +++ b/src/tr1/game/fmv.c @@ -2,11 +2,11 @@ #include "game/input.h" #include "game/music.h" -#include "game/output.h" #include "game/screen.h" #include "game/shell.h" #include "game/sound.h" #include "global/types.h" +#include "specific/s_output.h" #include "specific/s_shell.h" #include @@ -58,12 +58,13 @@ static void M_ClearSurface(void *const surface, void *const user_data) static void M_RenderBegin(void *surface, void *const user_data) { - Output_BeginScene(); + S_Output_RenderBegin(); } static void M_RenderEnd(void *surface, void *const user_data) { - Output_EndScene(); + S_Output_RenderEnd(); + S_Output_FlipScreen(); } static void *M_LockSurface(void *const surface, void *const user_data) @@ -129,7 +130,7 @@ static bool M_Play(const char *const file_path) Audio_Init(); GFX_2D_Renderer_Destroy(renderer_2d); - Output_ApplyRenderSettings(); + S_Output_ApplyRenderSettings(); return true; } diff --git a/src/tr1/game/game/game.c b/src/tr1/game/game/game.c index 85b1e2dd8..e8e4371d9 100644 --- a/src/tr1/game/game/game.c +++ b/src/tr1/game/game/game.c @@ -23,7 +23,7 @@ #include #include #include -#include +#include #define FRAME_BUFFER(key) \ do { \ @@ -48,11 +48,10 @@ void Game_ProcessInput(void) } } - if (g_InputDB.use_small_medi && Inv_RequestItem(O_SMALL_MEDIPACK_OPTION)) { - Lara_UseItem(O_SMALL_MEDIPACK_OPTION); - } else if ( - g_InputDB.use_big_medi && Inv_RequestItem(O_LARGE_MEDIPACK_OPTION)) { - Lara_UseItem(O_LARGE_MEDIPACK_OPTION); + if (g_InputDB.use_small_medi && Inv_RequestItem(O_MEDI_OPTION)) { + Lara_UseItem(O_MEDI_OPTION); + } else if (g_InputDB.use_big_medi && Inv_RequestItem(O_BIGMEDI_OPTION)) { + Lara_UseItem(O_BIGMEDI_OPTION); } if (g_Config.input.enable_buffering && Game_IsPlaying()) { @@ -142,10 +141,10 @@ GF_COMMAND Game_Control(const bool demo_mode) if (g_Camera.type == CAM_CINEMATIC) { g_OverlayFlag = 0; } else if (g_OverlayFlag > 0) { - if (g_Input.save) { - g_OverlayFlag = -2; - } else if (g_Input.load) { + if (g_Input.load) { g_OverlayFlag = -1; + } else if (g_Input.save) { + g_OverlayFlag = -2; } else { g_OverlayFlag = 0; } diff --git a/src/tr1/game/game/game_draw.c b/src/tr1/game/game/game_draw.c index 63530b1fa..c09917042 100644 --- a/src/tr1/game/game/game_draw.c +++ b/src/tr1/game/game/game_draw.c @@ -4,7 +4,6 @@ #include "game/lara/draw.h" #include "game/lara/hair.h" #include "game/output.h" -#include "game/output/textures.h" #include "game/overlay.h" #include "game/room_draw.h" #include "game/viewport.h" @@ -25,7 +24,7 @@ void Game_Draw(bool draw_overlay) Room_DrawAllRooms(g_Camera.interp.room_num, g_Camera.target.room_num); if (g_Config.visuals.enable_reflections) { - Output_Textures_UpdateEnvironmentMap(); + Output_FillEnvironmentMap(); } if (Room_Get(g_LaraItem->room_num)->flags & RF_UNDERWATER) { diff --git a/src/tr1/game/game_flow/sequencer.h b/src/tr1/game/game_flow/sequencer.h index ce83afff5..6f45e734f 100644 --- a/src/tr1/game/game_flow/sequencer.h +++ b/src/tr1/game/game_flow/sequencer.h @@ -6,5 +6,4 @@ void GF_InitSequencer(void); GF_COMMAND GF_DoLevelSequence( const GF_LEVEL *start_level, GF_SEQUENCE_CONTEXT seq_ctx); - GF_COMMAND GF_PlayAvailableStory(int32_t slot_num); diff --git a/src/tr1/game/game_flow/sequencer_events.c b/src/tr1/game/game_flow/sequencer_events.c index 9d786ed98..373643335 100644 --- a/src/tr1/game/game_flow/sequencer_events.c +++ b/src/tr1/game/game_flow/sequencer_events.c @@ -88,7 +88,8 @@ static DECLARE_GF_EVENT_HANDLER(M_HandlePlayLevel) // select level feature Savegame_InitCurrentInfo(); if (level->num > GF_GetFirstLevel()->num) { - Savegame_LoadOnlyResumeInfo(g_GameInfo.select_save_slot); + Savegame_LoadOnlyResumeInfo( + g_GameInfo.select_save_slot, &g_GameInfo); const GF_LEVEL *tmp_level = level; while (tmp_level != nullptr) { Savegame_ResetCurrentInfo(tmp_level); @@ -218,7 +219,7 @@ static DECLARE_GF_EVENT_HANDLER(M_HandleLevelComplete) g_Config.profile.new_game_plus_unlock = true; Config_Write(); } - const bool bonus_level_unlock = Stats_CheckAllSecretsCollected(GFL_NORMAL); + g_GameInfo.bonus_level_unlock = Stats_CheckAllSecretsCollected(GFL_NORMAL); // play specific level if (g_GameInfo.select_level_num != -1) { @@ -241,7 +242,7 @@ static DECLARE_GF_EVENT_HANDLER(M_HandleLevelComplete) Savegame_CarryCurrentInfoToNextLevel(current_level, next_level); Savegame_ApplyLogicToCurrentInfo(next_level); - if (next_level->type == GFL_BONUS && !bonus_level_unlock) { + if (next_level->type == GFL_BONUS && !g_GameInfo.bonus_level_unlock) { return (GF_COMMAND) { .action = GF_EXIT_TO_TITLE }; } return (GF_COMMAND) { @@ -306,7 +307,7 @@ static DECLARE_GF_EVENT_HANDLER(M_HandleAddItem) static DECLARE_GF_EVENT_HANDLER(M_HandleRemoveWeapons) { if (seq_ctx != GFSC_STORY && seq_ctx != GFSC_SAVED - && !Game_IsBonusFlagSet(GBF_NGPLUS)) { + && !(g_GameInfo.bonus_flag & GBF_NGPLUS)) { g_GameInfo.remove_guns = true; } return (GF_COMMAND) { .action = GF_NOOP }; @@ -315,7 +316,7 @@ static DECLARE_GF_EVENT_HANDLER(M_HandleRemoveWeapons) static DECLARE_GF_EVENT_HANDLER(M_HandleRemoveAmmo) { if (seq_ctx != GFSC_STORY && seq_ctx != GFSC_SAVED - && !Game_IsBonusFlagSet(GBF_NGPLUS)) { + && !(g_GameInfo.bonus_flag & GBF_NGPLUS)) { g_GameInfo.remove_ammo = true; } return (GF_COMMAND) { .action = GF_NOOP }; @@ -368,7 +369,7 @@ void GF_PreSequenceHook( g_GameInfo.remove_ammo = false; g_GameInfo.remove_medipacks = false; if (seq_ctx == GFSC_SAVED) { - Game_SetBonusFlag(GBF_NONE); + g_GameInfo.bonus_flag = false; } } @@ -388,6 +389,25 @@ GF_SEQUENCE_CONTEXT GF_SwitchSequenceContext( } } +bool GF_ShouldSkipSequenceEvent( + const GF_LEVEL *const level, const GF_SEQUENCE_EVENT *const event) +{ + // Skip cinematic levels + if (!g_Config.gameplay.enable_cine && level->type == GFL_CUTSCENE) { + switch (event->type) { + case GFS_EXIT_TO_TITLE: + case GFS_LEVEL_COMPLETE: + case GFS_PLAY_FMV: + case GFS_LEVEL_STATS: + case GFS_TOTAL_STATS: + return false; + default: + return true; + } + } + return false; +} + GF_EVENT_QUEUE_TYPE GF_ShouldDeferSequenceEvent( const GF_SEQUENCE_EVENT_TYPE event_type) { diff --git a/src/tr1/game/game_flow/sequencer_misc.c b/src/tr1/game/game_flow/sequencer_misc.c index a3d7068ab..b43cfb0aa 100644 --- a/src/tr1/game/game_flow/sequencer_misc.c +++ b/src/tr1/game/game_flow/sequencer_misc.c @@ -6,10 +6,8 @@ #include "game/level.h" #include "game/savegame.h" -#include #include #include -#include GF_COMMAND GF_RunTitle(void) { @@ -25,12 +23,6 @@ GF_COMMAND GF_RunTitle(void) GF_COMMAND GF_PlayAvailableStory(const int32_t slot_num) { const int32_t savegame_level = Savegame_GetLevelNumber(slot_num); - const bool prev_enable_legal = g_Config.gameplay.enable_legal; - g_Config.gameplay.enable_legal = false; - - // Play intro FMVs and cutscenes - GF_DoFrontendSequence(); - const GF_LEVEL_TABLE *const level_table = GF_GetLevelTable(GFLT_MAIN); for (int32_t i = 0; i <= MIN(savegame_level, level_table->count); i++) { const GF_LEVEL *const level = GF_GetLevel(GFLT_MAIN, i); @@ -44,64 +36,9 @@ GF_COMMAND GF_PlayAvailableStory(const int32_t slot_num) break; } } - - g_Config.gameplay.enable_legal = prev_enable_legal; return (GF_COMMAND) { .action = GF_EXIT_TO_TITLE }; } -bool GF_HasAvailableStory(const int32_t slot_num) -{ - const int32_t savegame_level = Savegame_GetLevelNumber(slot_num); - - // Check intro FMVs and cutscenes in frontend sequence (skip legal FMVs) - const GF_LEVEL *const title_level = GF_GetTitleLevel(); - if (title_level != nullptr) { - const GF_SEQUENCE *const seq = &title_level->sequence; - for (int32_t j = 0; j < seq->length; j++) { - const GF_SEQUENCE_EVENT *const ev = &seq->events[j]; - if (ev->type == GFS_PLAY_CUTSCENE) { - return true; - } - if (ev->type == GFS_PLAY_FMV) { - const int32_t fmv_id = (int32_t)(intptr_t)ev->data; - const GF_FMV *const fmv = &g_GameFlow.fmvs[fmv_id]; - if (!fmv->is_legal) { - return true; - } - } - } - } - - // Check for any cutscenes or FMVs up until the save point - const GF_LEVEL_TABLE *const level_table = GF_GetLevelTable(GFLT_MAIN); - const int32_t max_level = MIN(savegame_level, level_table->count); - for (int32_t i = 0; i <= max_level; i++) { - const GF_LEVEL *const level = GF_GetLevel(GFLT_MAIN, i); - if (level->type == GFL_GYM) { - continue; - } - const GF_SEQUENCE *const seq = &level->sequence; - for (int32_t j = 0; j < seq->length; j++) { - const GF_SEQUENCE_EVENT *const ev = &seq->events[j]; - // Stop checking after the saved level - if (ev->type == GFS_LOOP_GAME) { - break; - } - if (ev->type == GFS_PLAY_CUTSCENE) { - return true; - } - if (ev->type == GFS_PLAY_FMV) { - const int32_t fmv_id = (int32_t)(intptr_t)ev->data; - const GF_FMV *const fmv = &g_GameFlow.fmvs[fmv_id]; - if (!fmv->is_legal) { - return true; - } - } - } - } - return false; -} - GF_COMMAND GF_DoLevelSequence( const GF_LEVEL *const start_level, const GF_SEQUENCE_CONTEXT seq_ctx) { diff --git a/src/tr1/game/game_string.c b/src/tr1/game/game_string.c index d30284e31..56bf3122b 100644 --- a/src/tr1/game/game_string.c +++ b/src/tr1/game/game_string.c @@ -4,9 +4,9 @@ void GameString_Init(void) { -#include -// force order #include "game_string.def" + +#include } void GameString_Shutdown(void) diff --git a/src/tr1/game/game_string.def b/src/tr1/game/game_string.def index 4b0cffd37..f170e6898 100644 --- a/src/tr1/game/game_string.def +++ b/src/tr1/game/game_string.def @@ -1,15 +1,34 @@ GS_DEFINE(PASSPORT_RESTART_LEVEL, "Restart Level") +GS_DEFINE(PASSPORT_STORY_SO_FAR, "Story so far...") +GS_DEFINE(PASSPORT_LEGACY_SELECT_LEVEL_1, "Legacy saves do not") +GS_DEFINE(PASSPORT_LEGACY_SELECT_LEVEL_2, "support this feature.") +GS_DEFINE(PASSPORT_SELECT_MODE, "Select Mode") +GS_DEFINE(PASSPORT_MODE_NEW_GAME, "New Game") +GS_DEFINE(PASSPORT_MODE_NEW_GAME_PLUS, "New Game+") +GS_DEFINE(PASSPORT_MODE_NEW_GAME_JP, "Japanese NG") +GS_DEFINE(PASSPORT_MODE_NEW_GAME_JP_PLUS, "Japanese NG+") +GS_DEFINE(DETAIL_SELECT_DETAIL, "Select Detail") +GS_DEFINE(DETAIL_FPS, "FPS") +GS_DEFINE(DETAIL_TRAPEZOID_FILTER, "Trapezoid filter") +GS_DEFINE(DETAIL_PRETTY_PIXELS, "Pretty pixels") GS_DEFINE(DETAIL_REFLECTIONS, "Reflections") +GS_DEFINE(DETAIL_BILINEAR, "Bilinear") +GS_DEFINE(DETAIL_TEXTURE_FILTER, "Texture filter") GS_DEFINE(DETAIL_FBO_FILTER, "FBO filter") GS_DEFINE(DETAIL_VSYNC, "VSync") GS_DEFINE(DETAIL_BRIGHTNESS, "Brightness") +GS_DEFINE(DETAIL_UI_TEXT_SCALE, "UI text scale") +GS_DEFINE(DETAIL_UI_BAR_SCALE, "UI bar scale") +GS_DEFINE(DETAIL_RENDER_MODE, "Render mode") GS_DEFINE(DETAIL_RENDER_MODE_LEGACY, "Window size") GS_DEFINE(DETAIL_RENDER_MODE_FBO, "Framebuffer") GS_DEFINE(DETAIL_RESOLUTION, "Resolution") +GS_DEFINE(DETAIL_DECIMAL_FMT, "%d") GS_DEFINE(DETAIL_STRING_FMT, "%s") +GS_DEFINE(DETAIL_FLOAT_FMT, "%.1f") GS_DEFINE(DETAIL_RESOLUTION_FMT, "%dx%d") -GS_DEFINE(CONTROLS_RESET_DEFAULTS, "Reset All: Hold %s") -GS_DEFINE(CONTROLS_UNBIND, "Unbind: Hold %s") +GS_DEFINE(CONTROL_RESET_DEFAULTS, "Reset All: Hold %s") +GS_DEFINE(CONTROL_UNBIND, "Unbind: Hold %s") GS_DEFINE(KEYMAP_CHANGE_TARGET, "Change Target") GS_DEFINE(KEYMAP_EQUIP_PISTOLS, "Equip Pistols") GS_DEFINE(KEYMAP_EQUIP_SHOTGUN, "Equip Shotgun") @@ -27,9 +46,7 @@ GS_DEFINE(STATS_SECRETS, "SECRETS") GS_DEFINE(STATS_DEATHS, "DEATHS") GS_DEFINE(STATS_TIME_TAKEN, "TIME TAKEN") GS_DEFINE(STATS_BONUS_STATISTICS, "Bonus Statistics") -GS_DEFINE(STATS_AMMO, "AMMO HITS/USED") -GS_DEFINE(STATS_DISTANCE_TRAVELLED, "DISTANCE TRAVELLED") -GS_DEFINE(STATS_MEDIPACKS_USED, "HEALTH PACKS USED") +GS_DEFINE(MISC_EMPTY_SLOT_FMT, "- EMPTY SLOT %d -") GS_DEFINE(OSD_FLY_MODE_ON, "Fly mode enabled") GS_DEFINE(OSD_FLY_MODE_OFF, "Fly mode disabled") GS_DEFINE(OSD_GIVE_ITEM_ALL_KEYS, "Surprise! Every key item Lara needs is now in her backpack.") @@ -46,4 +63,4 @@ GS_DEFINE(OSD_DOOR_CLOSE, "Close Sesame!") GS_DEFINE(OSD_DOOR_OPEN_FAIL, "No doors in Lara's proximity") GS_DEFINE(ITEM_EXAMINE_ROLE, "\\{button empty} %s: Examine") GS_DEFINE(ITEM_USE_ROLE, "\\{button empty} %s: Use") -GS_DEFINE(MISC_EMPTY_SLOT_FMT, "- EMPTY SLOT %d -") +GS_DEFINE(PAGINATION_NAV, "%d / %d") diff --git a/src/tr1/game/gun/gun.c b/src/tr1/game/gun/gun.c index ac82f2ea1..623328f7d 100644 --- a/src/tr1/game/gun/gun.c +++ b/src/tr1/game/gun/gun.c @@ -137,7 +137,7 @@ void Gun_Control(void) switch (g_Lara.gun_type) { case LGT_PISTOLS: - if (g_Lara.pistol_ammo.ammo && g_Input.action) { + if (g_Lara.pistols.ammo && g_Input.action) { Lara_SwapSingleMesh(LM_HEAD, O_UZI_ANIM); } if (g_Camera.type != CAM_CINEMATIC && g_Camera.type != CAM_LOOK) { @@ -147,7 +147,7 @@ void Gun_Control(void) break; case LGT_MAGNUMS: - if (g_Lara.magnum_ammo.ammo && g_Input.action) { + if (g_Lara.magnums.ammo && g_Input.action) { Lara_SwapSingleMesh(LM_HEAD, O_UZI_ANIM); } if (g_Camera.type != CAM_CINEMATIC && g_Camera.type != CAM_LOOK) { @@ -157,7 +157,7 @@ void Gun_Control(void) break; case LGT_UZIS: - if (g_Lara.uzi_ammo.ammo && g_Input.action) { + if (g_Lara.uzis.ammo && g_Input.action) { Lara_SwapSingleMesh(LM_HEAD, O_UZI_ANIM); } if (g_Camera.type != CAM_CINEMATIC && g_Camera.type != CAM_LOOK) { @@ -167,7 +167,7 @@ void Gun_Control(void) break; case LGT_SHOTGUN: - if (g_Lara.shotgun_ammo.ammo && g_Input.action) { + if (g_Lara.shotgun.ammo && g_Input.action) { Lara_SwapSingleMesh(LM_HEAD, O_UZI_ANIM); } if (g_Camera.type != CAM_CINEMATIC && g_Camera.type != CAM_LOOK) { diff --git a/src/tr1/game/gun/gun_misc.c b/src/tr1/game/gun/gun_misc.c index 596df9a5a..5a92a460f 100644 --- a/src/tr1/game/gun/gun_misc.c +++ b/src/tr1/game/gun/gun_misc.c @@ -9,7 +9,6 @@ #include "game/savegame.h" #include "game/sound.h" #include "game/spawn.h" -#include "game/stats.h" #include "global/const.h" #include "global/vars.h" @@ -49,8 +48,8 @@ #define SHOTGUN_RARM_XMIN (-65 * DEG_1) #define SHOTGUN_RARM_XMAX (+65 * DEG_1) -static ITEM *m_TargetList[LOT_SLOT_COUNT]; -static ITEM *m_LastTargetList[LOT_SLOT_COUNT]; +static ITEM *m_TargetList[NUM_SLOTS]; +static ITEM *m_LastTargetList[NUM_SLOTS]; WEAPON_INFO g_Weapons[NUM_WEAPONS] = { // null @@ -269,7 +268,7 @@ void Gun_GetNewTarget(WEAPON_INFO *winfo) } if (num_targets > 0) { - for (int slot = 0; slot < LOT_SLOT_COUNT; slot++) { + for (int slot = 0; slot < NUM_SLOTS; slot++) { if (!m_TargetList[slot]) { g_Lara.target = nullptr; } @@ -288,7 +287,7 @@ void Gun_GetNewTarget(WEAPON_INFO *winfo) } if (g_Lara.target != m_LastTargetList[0]) { - for (int slot = LOT_SLOT_COUNT - 1; slot > 0; slot--) { + for (int slot = NUM_SLOTS - 1; slot > 0; slot--) { m_LastTargetList[slot] = m_LastTargetList[slot - 1]; } m_LastTargetList[0] = g_Lara.target; @@ -302,12 +301,12 @@ void Gun_ChangeTarget(WEAPON_INFO *winfo) g_Lara.target = nullptr; bool found_new_target = false; - for (int new_target = 0; new_target < LOT_SLOT_COUNT; new_target++) { + for (int new_target = 0; new_target < NUM_SLOTS; new_target++) { if (!m_TargetList[new_target]) { break; } - for (int last_target = 0; last_target < LOT_SLOT_COUNT; last_target++) { + for (int last_target = 0; last_target < NUM_SLOTS; last_target++) { if (!m_LastTargetList[last_target]) { found_new_target = true; break; @@ -325,8 +324,7 @@ void Gun_ChangeTarget(WEAPON_INFO *winfo) } if (g_Lara.target != m_LastTargetList[0]) { - for (int last_target = LOT_SLOT_COUNT - 1; last_target > 0; - last_target--) { + for (int last_target = NUM_SLOTS - 1; last_target > 0; last_target--) { m_LastTargetList[last_target] = m_LastTargetList[last_target - 1]; } m_LastTargetList[0] = g_Lara.target; @@ -395,28 +393,28 @@ int32_t Gun_FireWeapon( AMMO_INFO *ammo; switch (weapon_type) { case LGT_MAGNUMS: - ammo = &g_Lara.magnum_ammo; - if (Game_IsBonusFlagSet(GBF_NGPLUS)) { + ammo = &g_Lara.magnums; + if (g_GameInfo.bonus_flag & GBF_NGPLUS) { ammo->ammo = 1000; } break; case LGT_UZIS: - ammo = &g_Lara.uzi_ammo; - if (Game_IsBonusFlagSet(GBF_NGPLUS)) { + ammo = &g_Lara.uzis; + if (g_GameInfo.bonus_flag & GBF_NGPLUS) { ammo->ammo = 1000; } break; case LGT_SHOTGUN: - ammo = &g_Lara.shotgun_ammo; - if (Game_IsBonusFlagSet(GBF_NGPLUS)) { + ammo = &g_Lara.shotgun; + if (g_GameInfo.bonus_flag & GBF_NGPLUS) { ammo->ammo = 1000; } break; default: - ammo = &g_Lara.pistol_ammo; + ammo = &g_Lara.pistols; ammo->ammo = 1000; break; } @@ -433,7 +431,6 @@ int32_t Gun_FireWeapon( } ammo->ammo--; - Stats_AddAmmoUsed(); const XYZ_32 view_pos = { .x = src->pos.x, @@ -473,13 +470,12 @@ int32_t Gun_FireWeapon( GAME_VECTOR vdest; if (best >= 0) { ammo->hit++; - Stats_AddAmmoHits(); vdest.x = vsrc.x + ((bestdist * g_MatrixPtr->_20) >> W2V_SHIFT); vdest.y = vsrc.y + ((bestdist * g_MatrixPtr->_21) >> W2V_SHIFT); vdest.z = vsrc.z + ((bestdist * g_MatrixPtr->_22) >> W2V_SHIFT); Gun_HitTarget( target, &vdest, - winfo->damage * (Game_IsBonusFlagSet(GBF_JAPANESE) ? 2 : 1)); + winfo->damage * (g_GameInfo.bonus_flag & GBF_JAPANESE ? 2 : 1)); return 1; } @@ -495,7 +491,7 @@ int32_t Gun_FireWeapon( void Gun_HitTarget(ITEM *item, GAME_VECTOR *hitpos, int16_t damage) { if (item->hit_points > 0 && item->hit_points <= damage) { - Stats_AddKill(); + Savegame_GetCurrentInfo(Game_GetCurrentLevel())->stats.kill_count++; if (g_Config.gameplay.target_mode == TLM_SEMI) { g_Lara.target = nullptr; } diff --git a/src/tr1/game/gun/gun_rifle.c b/src/tr1/game/gun/gun_rifle.c index 474b335f4..b38a81612 100644 --- a/src/tr1/game/gun/gun_rifle.c +++ b/src/tr1/game/gun/gun_rifle.c @@ -255,11 +255,9 @@ void Gun_Rifle_Fire(const LARA_GUN_TYPE weapon_type) for (int i = 0; i < SHOTGUN_AMMO_CLIP; i++) { dangles[0] = angles[0] - + (int32_t)((Random_GetControl() - 16384) * SHOTGUN_PELLET_SCATTER) - / 65536; + + (int)((Random_GetControl() - 16384) * PELLET_SCATTER) / 65536; dangles[1] = angles[1] - + (int32_t)((Random_GetControl() - 16384) * SHOTGUN_PELLET_SCATTER) - / 65536; + + (int)((Random_GetControl() - 16384) * PELLET_SCATTER) / 65536; if (Gun_FireWeapon(weapon_type, g_Lara.target, g_LaraItem, dangles)) { fired = true; } diff --git a/src/tr1/game/input.c b/src/tr1/game/input.c index bb0417b1b..cec011580 100644 --- a/src/tr1/game/input.c +++ b/src/tr1/game/input.c @@ -129,9 +129,3 @@ void Input_Update(void) g_InputDB.any = 0; } } - -const char *Input_GetRoleName(const INPUT_ROLE role) -{ - // TODO: implement me - return nullptr; -} diff --git a/src/tr1/game/inventory.c b/src/tr1/game/inventory.c index 1674f8c3e..79bd0df81 100644 --- a/src/tr1/game/inventory.c +++ b/src/tr1/game/inventory.c @@ -52,50 +52,50 @@ bool Inv_AddItem(const GAME_OBJECT_ID obj_id) case O_SHOTGUN_ITEM: case O_SHOTGUN_OPTION: - for (int32_t i = Inv_RequestItem(O_SHOTGUN_AMMO_ITEM); i > 0; i--) { - Inv_RemoveItem(O_SHOTGUN_AMMO_ITEM); - Inv_AddAmmo(&g_Lara.shotgun_ammo, SHOTGUN_AMMO_QTY); + for (int32_t i = Inv_RequestItem(O_SG_AMMO_ITEM); i > 0; i--) { + Inv_RemoveItem(O_SG_AMMO_ITEM); + Inv_AddAmmo(&g_Lara.shotgun, SHOTGUN_AMMO_QTY); } - Inv_AddAmmo(&g_Lara.shotgun_ammo, SHOTGUN_AMMO_QTY); + Inv_AddAmmo(&g_Lara.shotgun, SHOTGUN_AMMO_QTY); Inv_InsertItem(&g_InvRing_Item_Shotgun); - Item_GlobalReplace(O_SHOTGUN_ITEM, O_SHOTGUN_AMMO_ITEM); + Item_GlobalReplace(O_SHOTGUN_ITEM, O_SG_AMMO_ITEM); return false; case O_MAGNUM_ITEM: case O_MAGNUM_OPTION: - for (int32_t i = Inv_RequestItem(O_MAGNUM_AMMO_ITEM); i > 0; i--) { - Inv_RemoveItem(O_MAGNUM_AMMO_ITEM); - Inv_AddAmmo(&g_Lara.magnum_ammo, MAGNUM_AMMO_QTY); + for (int32_t i = Inv_RequestItem(O_MAG_AMMO_ITEM); i > 0; i--) { + Inv_RemoveItem(O_MAG_AMMO_ITEM); + Inv_AddAmmo(&g_Lara.magnums, MAGNUM_AMMO_QTY); } - Inv_AddAmmo(&g_Lara.magnum_ammo, MAGNUM_AMMO_QTY); + Inv_AddAmmo(&g_Lara.magnums, MAGNUM_AMMO_QTY); Inv_InsertItem(&g_InvRing_Item_Magnum); - Item_GlobalReplace(O_MAGNUM_ITEM, O_MAGNUM_AMMO_ITEM); + Item_GlobalReplace(O_MAGNUM_ITEM, O_MAG_AMMO_ITEM); return false; case O_UZI_ITEM: case O_UZI_OPTION: for (int32_t i = Inv_RequestItem(O_UZI_AMMO_ITEM); i > 0; i--) { Inv_RemoveItem(O_UZI_AMMO_ITEM); - Inv_AddAmmo(&g_Lara.uzi_ammo, UZI_AMMO_QTY); + Inv_AddAmmo(&g_Lara.uzis, UZI_AMMO_QTY); } - Inv_AddAmmo(&g_Lara.uzi_ammo, UZI_AMMO_QTY); + Inv_AddAmmo(&g_Lara.uzis, UZI_AMMO_QTY); Inv_InsertItem(&g_InvRing_Item_Uzi); Item_GlobalReplace(O_UZI_ITEM, O_UZI_AMMO_ITEM); return false; - case O_SHOTGUN_AMMO_ITEM: - case O_SHOTGUN_AMMO_OPTION: + case O_SG_AMMO_ITEM: + case O_SG_AMMO_OPTION: if (Inv_RequestItem(O_SHOTGUN_ITEM)) { - Inv_AddAmmo(&g_Lara.shotgun_ammo, SHOTGUN_AMMO_QTY); + Inv_AddAmmo(&g_Lara.shotgun, SHOTGUN_AMMO_QTY); } else { Inv_InsertItem(&g_InvRing_Item_ShotgunAmmo); } return false; - case O_MAGNUM_AMMO_ITEM: - case O_MAGNUM_AMMO_OPTION: + case O_MAG_AMMO_ITEM: + case O_MAG_AMMO_OPTION: if (Inv_RequestItem(O_MAGNUM_ITEM)) { - Inv_AddAmmo(&g_Lara.magnum_ammo, MAGNUM_AMMO_QTY); + Inv_AddAmmo(&g_Lara.magnums, MAGNUM_AMMO_QTY); } else { Inv_InsertItem(&g_InvRing_Item_MagnumAmmo); } @@ -104,19 +104,19 @@ bool Inv_AddItem(const GAME_OBJECT_ID obj_id) case O_UZI_AMMO_ITEM: case O_UZI_AMMO_OPTION: if (Inv_RequestItem(O_UZI_ITEM)) { - Inv_AddAmmo(&g_Lara.uzi_ammo, UZI_AMMO_QTY); + Inv_AddAmmo(&g_Lara.uzis, UZI_AMMO_QTY); } else { Inv_InsertItem(&g_InvRing_Item_UziAmmo); } return false; - case O_SMALL_MEDIPACK_ITEM: - case O_SMALL_MEDIPACK_OPTION: + case O_MEDI_ITEM: + case O_MEDI_OPTION: Inv_InsertItem(&g_InvRing_Item_Medi); return true; - case O_LARGE_MEDIPACK_ITEM: - case O_LARGE_MEDIPACK_OPTION: + case O_BIGMEDI_ITEM: + case O_BIGMEDI_OPTION: Inv_InsertItem(&g_InvRing_Item_BigMedi); return true; diff --git a/src/tr1/game/inventory_ring/control.c b/src/tr1/game/inventory_ring/control.c index bb5803ccb..b48227895 100644 --- a/src/tr1/game/inventory_ring/control.c +++ b/src/tr1/game/inventory_ring/control.c @@ -23,7 +23,6 @@ #include "global/vars.h" #include -#include #include #include #include @@ -97,7 +96,7 @@ static void M_RemoveExamineOverlay(void) static void M_ShowAmmoQuantity(const char *const fmt, const int32_t qty) { - if (!Game_IsBonusFlagSet(GBF_NGPLUS)) { + if (!(g_GameInfo.bonus_flag & GBF_NGPLUS)) { InvRing_ShowItemQuantity(fmt, qty); } } @@ -123,29 +122,28 @@ static void M_RingNotActive(const INVENTORY_ITEM *const inv_item) switch (inv_item->object_id) { case O_SHOTGUN_OPTION: - M_ShowAmmoQuantity( - "%5d A", g_Lara.shotgun_ammo.ammo / SHOTGUN_AMMO_CLIP); + M_ShowAmmoQuantity("%5d A", g_Lara.shotgun.ammo / SHOTGUN_AMMO_CLIP); break; case O_MAGNUM_OPTION: - M_ShowAmmoQuantity("%5d B", g_Lara.magnum_ammo.ammo); + M_ShowAmmoQuantity("%5d B", g_Lara.magnums.ammo); break; case O_UZI_OPTION: - M_ShowAmmoQuantity("%5d C", g_Lara.uzi_ammo.ammo); + M_ShowAmmoQuantity("%5d C", g_Lara.uzis.ammo); break; - case O_SHOTGUN_AMMO_OPTION: - InvRing_ShowItemQuantity("%d", qty * SHOTGUN_SHELL_COUNT); + case O_SG_AMMO_OPTION: + InvRing_ShowItemQuantity("%d", qty * NUM_SG_SHELLS); break; - case O_MAGNUM_AMMO_OPTION: + case O_MAG_AMMO_OPTION: case O_UZI_AMMO_OPTION: InvRing_ShowItemQuantity("%d", qty * 2); break; - case O_SMALL_MEDIPACK_OPTION: - case O_LARGE_MEDIPACK_OPTION: + case O_MEDI_OPTION: + case O_BIGMEDI_OPTION: Overlay_BarSetHealthTimer(40); if (qty > 1) { InvRing_ShowItemQuantity("%d", qty); @@ -176,8 +174,8 @@ static void M_RingNotActive(const INVENTORY_ITEM *const inv_item) break; } - if (inv_item->object_id == O_SMALL_MEDIPACK_OPTION - || inv_item->object_id == O_LARGE_MEDIPACK_OPTION) { + if (inv_item->object_id == O_MEDI_OPTION + || inv_item->object_id == O_BIGMEDI_OPTION) { if (g_Config.ui.healthbar_location == BL_TOP_LEFT) { InvRing_HideArrow(INV_RING_ARROW_TL, true); } else if (g_Config.ui.healthbar_location == BL_TOP_RIGHT) { @@ -317,8 +315,8 @@ static GF_COMMAND M_Finish(INV_RING *const ring, const bool apply_changes) case O_SHOTGUN_OPTION: case O_MAGNUM_OPTION: case O_UZI_OPTION: - case O_SMALL_MEDIPACK_OPTION: - case O_LARGE_MEDIPACK_OPTION: + case O_MEDI_OPTION: + case O_BIGMEDI_OPTION: case O_KEY_OPTION_1: case O_KEY_OPTION_2: case O_KEY_OPTION_3: @@ -694,6 +692,7 @@ static GF_COMMAND M_Control(INV_RING *const ring) InvRing_MotionSetup(ring, RNG_CLOSING_ITEM, RNG_DESELECT, 0); g_Input = (INPUT_STATE) {}; g_InputDB = (INPUT_STATE) {}; + if (ring->mode == INV_LOAD_MODE || ring->mode == INV_SAVE_MODE || ring->mode == INV_SAVE_CRYSTAL_MODE) { InvRing_MotionSetup( @@ -712,7 +711,7 @@ static GF_COMMAND M_Control(INV_RING *const ring) } if (ring->mode == INV_TITLE_MODE - && (inv_item->object_id == O_DETAIL_OPTION + && ((inv_item->object_id == O_DETAIL_OPTION) || inv_item->object_id == O_SOUND_OPTION || inv_item->object_id == O_CONTROL_OPTION || inv_item->object_id == O_GAMMA_OPTION)) { @@ -865,6 +864,7 @@ INV_RING *InvRing_Open(const INVENTORY_MODE mode) g_InvRing_Source[RT_OPTION].count = TITLE_RING_OBJECTS; InvRing_ShowVersionText(); Savegame_ScanSavedGames(); + Savegame_FillAvailableSaves(&g_SavegameRequester); } else { g_InvRing_Source[RT_OPTION].count = OPTION_RING_OBJECTS; InvRing_RemoveVersionText(); @@ -929,7 +929,7 @@ INV_RING *InvRing_Open(const INVENTORY_MODE mode) break; } - g_Inv_Mode = mode; + g_InvMode = mode; Interpolation_Remember(); if (g_Config.gameplay.enable_timer_in_inventory) { Stats_StartTimer(); diff --git a/src/tr1/game/inventory_ring/draw.c b/src/tr1/game/inventory_ring/draw.c index 5c2ab63c3..f7ab338f3 100644 --- a/src/tr1/game/inventory_ring/draw.c +++ b/src/tr1/game/inventory_ring/draw.c @@ -70,9 +70,9 @@ static void M_DrawItem( { if (ring->motion.status != RNG_FADING_OUT && ring->motion.status != RNG_DONE && inv_item == ring->list[ring->current_object] && !ring->rotating) { - Output_SetLightAdder(SHADE_NEUTRAL); + Output_SetLightAdder(HIGH_LIGHT); } else { - Output_SetLightAdder(SHADE_LOW); + Output_SetLightAdder(LOW_LIGHT); } Matrix_TranslateRel(0, inv_item->y_trans, inv_item->z_trans); @@ -81,6 +81,7 @@ static void M_DrawItem( OBJECT *const obj = Object_Get(inv_item->object_id); if (obj->mesh_count < 0) { + Output_DrawSpriteRel(0, 0, 0, obj->mesh_idx, 4096); return; } @@ -90,7 +91,7 @@ static void M_DrawItem( const int32_t frac = M_GetFrames(ring, inv_item, &frame1, &frame2, &rate); if (inv_item->object_id == O_COMPASS_OPTION) { const int16_t extra_rotation[1] = { Option_Compass_GetNeedleAngle() }; - Object_GetBone(obj, 0)->rot.y = true; + Object_GetBone(obj, 0)->rot_y = true; Object_DrawInterpolatedObject( obj, inv_item->meshes_drawn, extra_rotation, frame1, frame2, frac, rate); @@ -168,8 +169,8 @@ void InvRing_Draw(INV_RING *const ring) || ring->motion.status == RNG_CLOSING_ITEM)) { const INVENTORY_ITEM *inv_item = ring->list[ring->current_object]; switch (inv_item->object_id) { - case O_SMALL_MEDIPACK_OPTION: - case O_LARGE_MEDIPACK_OPTION: + case O_MEDI_OPTION: + case O_BIGMEDI_OPTION: if (g_Config.ui.enable_game_ui) { Overlay_BarDrawHealth(); } diff --git a/src/tr1/game/inventory_ring/vars.c b/src/tr1/game/inventory_ring/vars.c index f0850ec5e..eba55d339 100644 --- a/src/tr1/game/inventory_ring/vars.c +++ b/src/tr1/game/inventory_ring/vars.c @@ -80,7 +80,7 @@ INVENTORY_ITEM g_InvRing_Item_Compass = { }; INVENTORY_ITEM g_InvRing_Item_Medi = { - .object_id = O_SMALL_MEDIPACK_OPTION, + .object_id = O_MEDI_OPTION, .frames_total = 26, .current_frame = 0, .goal_frame = 0, @@ -105,7 +105,7 @@ INVENTORY_ITEM g_InvRing_Item_Medi = { }; INVENTORY_ITEM g_InvRing_Item_BigMedi = { - .object_id = O_LARGE_MEDIPACK_OPTION, + .object_id = O_BIGMEDI_OPTION, .frames_total = 20, .current_frame = 0, .goal_frame = 0, @@ -580,7 +580,7 @@ INVENTORY_ITEM g_InvRing_Item_PistolAmmo = { }; INVENTORY_ITEM g_InvRing_Item_ShotgunAmmo = { - .object_id = O_SHOTGUN_AMMO_OPTION, + .object_id = O_SG_AMMO_OPTION, .frames_total = 1, .current_frame = 0, .goal_frame = 0, @@ -605,7 +605,7 @@ INVENTORY_ITEM g_InvRing_Item_ShotgunAmmo = { }; INVENTORY_ITEM g_InvRing_Item_MagnumAmmo = { - .object_id = O_MAGNUM_AMMO_OPTION, + .object_id = O_MAG_AMMO_OPTION, .frames_total = 1, .current_frame = 0, .goal_frame = 0, diff --git a/src/tr1/game/items.c b/src/tr1/game/items.c index fdb607cbb..c2473382a 100644 --- a/src/tr1/game/items.c +++ b/src/tr1/game/items.c @@ -11,7 +11,6 @@ #include "global/vars.h" #include -#include #include #include #include @@ -28,7 +27,6 @@ } \ } while (0) -static BOUNDS_16 m_NullBounds = {}; static BOUNDS_16 m_InterpolatedBounds = {}; void Item_Control(void) @@ -94,7 +92,7 @@ void Item_Initialise(int16_t item_num) Room_GetWorldSector(room, item->pos.x, item->pos.z); item->floor = sector->floor.height; - if (Game_IsBonusFlagSet(GBF_NGPLUS)) { + if (g_GameInfo.bonus_flag & GBF_NGPLUS) { item->hit_points *= 2; } if (obj->initialise_func) { @@ -150,7 +148,7 @@ int16_t Item_Spawn(const ITEM *const item, const GAME_OBJECT_ID obj_id) spawn->rot = item->rot; Item_Initialise(spawn_num); spawn->status = IS_INACTIVE; - spawn->shade.value_1 = SHADE_NEUTRAL; + spawn->shade.value_1 = HIGH_LIGHT; } return spawn_num; } @@ -196,6 +194,94 @@ bool Item_Test3DRange(int32_t x, int32_t y, int32_t z, int32_t range) && (SQUARE(x) + SQUARE(y) + SQUARE(z) < SQUARE(range)); } +bool Item_TestBoundsCollide(ITEM *src_item, ITEM *dst_item, int32_t radius) +{ + const BOUNDS_16 *const src_bounds = &Item_GetBestFrame(src_item)->bounds; + const BOUNDS_16 *const dst_bounds = &Item_GetBestFrame(dst_item)->bounds; + if (dst_item->pos.y + dst_bounds->max.y + <= src_item->pos.y + src_bounds->min.y + || dst_item->pos.y + dst_bounds->min.y + >= src_item->pos.y + src_bounds->max.y) { + return false; + } + + const int32_t c = Math_Cos(dst_item->rot.y); + const int32_t s = Math_Sin(dst_item->rot.y); + const int32_t x = src_item->pos.x - dst_item->pos.x; + const int32_t z = src_item->pos.z - dst_item->pos.z; + const int32_t rx = (c * x - s * z) >> W2V_SHIFT; + const int32_t rz = (c * z + s * x) >> W2V_SHIFT; + const int32_t min_x = dst_bounds->min.x - radius; + const int32_t max_x = dst_bounds->max.x + radius; + const int32_t min_z = dst_bounds->min.z - radius; + const int32_t max_z = dst_bounds->max.z + radius; + return rx >= min_x && rx <= max_x && rz >= min_z && rz <= max_z; +} + +bool Item_TestPosition( + const ITEM *const src_item, const ITEM *const dst_item, + const OBJECT_BOUNDS *const bounds) +{ + const XYZ_16 rot = { + .x = src_item->rot.x - dst_item->rot.x, + .y = src_item->rot.y - dst_item->rot.y, + .z = src_item->rot.z - dst_item->rot.z, + }; + if (rot.x < bounds->rot.min.x || rot.x > bounds->rot.max.x + || rot.y < bounds->rot.min.y || rot.y > bounds->rot.max.y + || rot.z < bounds->rot.min.z || rot.z > bounds->rot.max.z) { + return false; + } + + const XYZ_32 dist = { + .x = src_item->pos.x - dst_item->pos.x, + .y = src_item->pos.y - dst_item->pos.y, + .z = src_item->pos.z - dst_item->pos.z, + }; + + Matrix_PushUnit(); + Matrix_Rot16(dst_item->rot); + MATRIX *mptr = g_MatrixPtr; + const XYZ_32 shift = { + .x = (mptr->_00 * dist.x + mptr->_10 * dist.y + mptr->_20 * dist.z) + >> W2V_SHIFT, + .y = (mptr->_01 * dist.x + mptr->_11 * dist.y + mptr->_21 * dist.z) + >> W2V_SHIFT, + .z = (mptr->_02 * dist.x + mptr->_12 * dist.y + mptr->_22 * dist.z) + >> W2V_SHIFT, + }; + Matrix_Pop(); + + if (shift.x < bounds->shift.min.x || shift.x > bounds->shift.max.x + || shift.y < bounds->shift.min.y || shift.y > bounds->shift.max.y + || shift.z < bounds->shift.min.z || shift.z > bounds->shift.max.z) { + return false; + } + + return true; +} + +void Item_AlignPosition(ITEM *src_item, ITEM *dst_item, XYZ_32 *vec) +{ + src_item->rot.x = dst_item->rot.x; + src_item->rot.y = dst_item->rot.y; + src_item->rot.z = dst_item->rot.z; + + Matrix_PushUnit(); + Matrix_Rot16(dst_item->rot); + MATRIX *mptr = g_MatrixPtr; + src_item->pos.x = dst_item->pos.x + + ((mptr->_00 * vec->x + mptr->_01 * vec->y + mptr->_02 * vec->z) + >> W2V_SHIFT); + src_item->pos.y = dst_item->pos.y + + ((mptr->_10 * vec->x + mptr->_11 * vec->y + mptr->_12 * vec->z) + >> W2V_SHIFT); + src_item->pos.z = dst_item->pos.z + + ((mptr->_20 * vec->x + mptr->_21 * vec->y + mptr->_22 * vec->z) + >> W2V_SHIFT); + Matrix_Pop(); +} + bool Item_MovePosition( ITEM *item, const ITEM *ref_item, const XYZ_32 *vec, int32_t velocity) { @@ -297,29 +383,55 @@ void Item_ShiftCol(ITEM *item, COLL_INFO *coll) coll->shift.z = 0; } +bool Item_IsTriggerActive(ITEM *item) +{ + bool ok = !(item->flags & IF_REVERSE); + + if ((item->flags & IF_CODE_BITS) != IF_CODE_BITS) { + return !ok; + } + + if (!item->timer) { + return ok; + } + + if (item->timer == -1) { + return !ok; + } + + item->timer--; + + if (!item->timer) { + item->timer = -1; + } + + return ok; +} + ANIM_FRAME *Item_GetBestFrame(const ITEM *item) { - ANIM_FRAME *frames[2]; + ANIM_FRAME *frmptr[2]; int32_t rate; - const int32_t frac = Item_GetFrames(item, frames, &rate); - return frames[(frac > rate / 2) ? 1 : 0]; + int32_t frac = Item_GetFrames(item, frmptr, &rate); + if (frac <= rate / 2) { + return frmptr[0]; + } else { + return frmptr[1]; + } } const BOUNDS_16 *Item_GetBoundsAccurate(const ITEM *item) { int32_t rate; - ANIM_FRAME *frames[2]; - const int32_t frac = Item_GetFrames(item, frames, &rate); - if (frames[0] == nullptr) { - return &m_NullBounds; + ANIM_FRAME *frmptr[2]; + + int32_t frac = Item_GetFrames(item, frmptr, &rate); + if (!frac) { + return &frmptr[0]->bounds; } - if (frac == 0) { - return &frames[0]->bounds; - } - - const BOUNDS_16 *const a = &frames[0]->bounds; - const BOUNDS_16 *const b = &frames[1]->bounds; + const BOUNDS_16 *const a = &frmptr[0]->bounds; + const BOUNDS_16 *const b = &frmptr[1]->bounds; BOUNDS_16 *const result = &m_InterpolatedBounds; result->min.x = a->min.x + (((b->min.x - a->min.x) * frac) / rate); @@ -331,13 +443,9 @@ const BOUNDS_16 *Item_GetBoundsAccurate(const ITEM *item) return result; } -int32_t Item_GetFrames(const ITEM *item, ANIM_FRAME *frames[], int32_t *rate) +int32_t Item_GetFrames(const ITEM *item, ANIM_FRAME *frmptr[], int32_t *rate) { const ANIM *const anim = Item_GetAnim(item); - if (anim->frame_ptr == nullptr) { - frames[0] = nullptr; - return 0; - } const int32_t cur_frame_num = item->frame_num - anim->frame_base; const int32_t last_frame_num = anim->frame_end - anim->frame_base; @@ -345,8 +453,8 @@ int32_t Item_GetFrames(const ITEM *item, ANIM_FRAME *frames[], int32_t *rate) const int32_t first_key_frame_num = cur_frame_num / key_frame_span; const int32_t second_key_frame_num = first_key_frame_num + 1; - frames[0] = &anim->frame_ptr[first_key_frame_num]; - frames[1] = &anim->frame_ptr[second_key_frame_num]; + frmptr[0] = &anim->frame_ptr[first_key_frame_num]; + frmptr[1] = &anim->frame_ptr[second_key_frame_num]; const int32_t key_frame_shift = cur_frame_num % key_frame_span; const int32_t numerator = key_frame_shift; @@ -395,6 +503,12 @@ int32_t Item_Explode(int16_t item_num, int32_t mesh_bits, int16_t damage) Matrix_TranslateRel16(frame->offset); Matrix_Rot16(frame->mesh_rots[0]); +#if 0 + // XXX: present in OG, removed by GLrage on the grounds that it sometimes + // crashes. + int16_t *extra_rotation = (int16_t*)item->data; +#endif + int32_t bit = 1; if ((bit & mesh_bits) && (bit & item->mesh_bits)) { int16_t effect_num = Effect_Create(item->room_num); @@ -432,10 +546,17 @@ int32_t Item_Explode(int16_t item_num, int32_t mesh_bits, int16_t damage) Matrix_Rot16(frame->mesh_rots[i]); #if 0 - // XXX: present in OG, removed by GLrage on the grounds that it - // sometimes crashes. - const int16_t *extra_rotation = (int16_t *)item->data; - Object_ApplyExtraRotation(&extra_rotation, bone->rot, false); + if (extra_rotation) { + if (bone->rot_y) { + Matrix_RotY(*extra_rotation++); + } + if (bone->rot_x) { + Matrix_RotX(*extra_rotation++); + } + if (bone->rot_z) { + Matrix_RotZ(*extra_rotation++); + } + } #endif bit <<= 1; diff --git a/src/tr1/game/items.h b/src/tr1/game/items.h index c94893dfc..b143055ad 100644 --- a/src/tr1/game/items.h +++ b/src/tr1/game/items.h @@ -11,11 +11,18 @@ int16_t Item_Spawn(const ITEM *item, GAME_OBJECT_ID obj_id); bool Item_IsNearItem(const ITEM *item, const XYZ_32 *pos, int32_t distance); bool Item_Test3DRange(int32_t x, int32_t y, int32_t z, int32_t range); +bool Item_TestBoundsCollide(ITEM *src_item, ITEM *dst_item, int32_t radius); +bool Item_TestPosition( + const ITEM *src_item, const ITEM *dst_item, const OBJECT_BOUNDS *bounds); +void Item_AlignPosition(ITEM *src_item, ITEM *dst_item, XYZ_32 *vec); bool Item_MovePosition( ITEM *src_item, const ITEM *dst_item, const XYZ_32 *vec, int32_t velocity); void Item_ShiftCol(ITEM *item, COLL_INFO *coll); int32_t Item_GetDistance(const ITEM *item, const XYZ_32 *target); +bool Item_IsTriggerActive(ITEM *item); + +const BOUNDS_16 *Item_GetBoundsAccurate(const ITEM *item); int32_t Item_GetFrames(const ITEM *item, ANIM_FRAME *frmptr[], int32_t *rate); void Item_TakeDamage(ITEM *item, int16_t damage, bool hit_status); diff --git a/src/tr1/game/lara/cheat.c b/src/tr1/game/lara/cheat.c index cca271687..b656e36e0 100644 --- a/src/tr1/game/lara/cheat.c +++ b/src/tr1/game/lara/cheat.c @@ -107,9 +107,9 @@ void Lara_Cheat_Control(void) Inv_AddItem(O_SHOTGUN_ITEM); Inv_AddItem(O_MAGNUM_ITEM); Inv_AddItem(O_UZI_ITEM); - g_Lara.shotgun_ammo.ammo = 500; - g_Lara.magnum_ammo.ammo = 500; - g_Lara.uzi_ammo.ammo = 5000; + g_Lara.shotgun.ammo = 500; + g_Lara.magnums.ammo = 500; + g_Lara.uzis.ammo = 5000; Sound_Effect(SFX_LARA_HOLSTER, nullptr, SPM_ALWAYS); } else if (as == LS_SWAN_DIVE) { Item_Explode(g_Lara.item_num, -1, 1); @@ -241,9 +241,9 @@ bool Lara_Cheat_GiveAllGuns(void) Inv_AddItem(O_MAGNUM_ITEM); Inv_AddItem(O_UZI_ITEM); Inv_AddItem(O_SHOTGUN_ITEM); - g_Lara.shotgun_ammo.ammo = Game_IsBonusFlagSet(GBF_NGPLUS) ? 10001 : 300; - g_Lara.magnum_ammo.ammo = Game_IsBonusFlagSet(GBF_NGPLUS) ? 10001 : 1000; - g_Lara.uzi_ammo.ammo = Game_IsBonusFlagSet(GBF_NGPLUS) ? 10001 : 2000; + g_Lara.shotgun.ammo = g_GameInfo.bonus_flag & GBF_NGPLUS ? 10001 : 300; + g_Lara.magnums.ammo = g_GameInfo.bonus_flag & GBF_NGPLUS ? 10001 : 1000; + g_Lara.uzis.ammo = g_GameInfo.bonus_flag & GBF_NGPLUS ? 10001 : 2000; Sound_Effect(SFX_LARA_RELOAD, nullptr, SPM_ALWAYS); Console_Log(GS(OSD_GIVE_ITEM_ALL_GUNS)); @@ -261,24 +261,24 @@ bool Lara_Cheat_GiveAllItems(void) if (!Inv_RequestItem(O_SHOTGUN_ITEM)) { Inv_AddItem(O_SHOTGUN_ITEM); } - g_Lara.shotgun_ammo.ammo = Game_IsBonusFlagSet(GBF_NGPLUS) ? 10001 : 300; + g_Lara.shotgun.ammo = g_GameInfo.bonus_flag & GBF_NGPLUS ? 10001 : 300; if (!Inv_RequestItem(O_MAGNUM_ITEM)) { Inv_AddItem(O_MAGNUM_ITEM); } - g_Lara.magnum_ammo.ammo = Game_IsBonusFlagSet(GBF_NGPLUS) ? 10001 : 1000; + g_Lara.magnums.ammo = g_GameInfo.bonus_flag & GBF_NGPLUS ? 10001 : 1000; if (!Inv_RequestItem(O_UZI_ITEM)) { Inv_AddItem(O_UZI_ITEM); } - g_Lara.uzi_ammo.ammo = Game_IsBonusFlagSet(GBF_NGPLUS) ? 10001 : 2000; + g_Lara.uzis.ammo = g_GameInfo.bonus_flag & GBF_NGPLUS ? 10001 : 2000; for (int i = 0; i < 10; i++) { - if (Inv_RequestItem(O_SMALL_MEDIPACK_ITEM) < 240) { - Inv_AddItem(O_SMALL_MEDIPACK_ITEM); + if (Inv_RequestItem(O_MEDI_ITEM) < 240) { + Inv_AddItem(O_MEDI_ITEM); } - if (Inv_RequestItem(O_LARGE_MEDIPACK_ITEM) < 240) { - Inv_AddItem(O_LARGE_MEDIPACK_ITEM); + if (Inv_RequestItem(O_BIGMEDI_ITEM) < 240) { + Inv_AddItem(O_BIGMEDI_ITEM); } } @@ -383,23 +383,13 @@ bool Lara_Cheat_KillEnemy(const int16_t item_num) return true; } -bool Lara_Cheat_Teleport(int32_t x, int32_t y, int32_t z, int16_t room_num) +bool Lara_Cheat_Teleport(int32_t x, int32_t y, int32_t z) { - if (room_num == NO_ROOM) { - room_num = Room_GetIndexFromPos(x, y, z); - } + int16_t room_num = Room_GetIndexFromPos(x, y, z); if (room_num == NO_ROOM) { return false; } - const ROOM *const room = Room_Get(room_num); - if (room->flip_status == RFS_FLIPPED && Room_GetFlipStatus()) { - room_num = Room_GetFlippedBaseRoom(room_num); - if (room_num == NO_ROOM) { - return false; - } - } - const SECTOR *sector = Room_GetSector(x, y, z, &room_num); int16_t height = Room_GetHeight(sector, x, y, z); @@ -421,9 +411,12 @@ bool Lara_Cheat_Teleport(int32_t x, int32_t y, int32_t z, int16_t room_num) .y = y, .z = ROUND_TO_SECTOR(z + dz * unit) + WALL_L / 2, }; + room_num = Room_GetIndexFromPos(point.x, point.y, point.z); + if (room_num == NO_ROOM) { + continue; + } sector = Room_GetSector(point.x, point.y, point.z, &room_num); - height = - Room_GetHeightEx(sector, point.x, point.y, point.z, true); + height = Room_GetHeight(sector, point.x, point.y, point.z); if (height == NO_HEIGHT) { continue; } @@ -450,8 +443,12 @@ bool Lara_Cheat_Teleport(int32_t x, int32_t y, int32_t z, int16_t room_num) } } + room_num = Room_GetIndexFromPos(x, y, z); + if (room_num == NO_ROOM) { + return false; + } sector = Room_GetSector(x, y, z, &room_num); - height = Room_GetHeightEx(sector, x, y, z, true); + height = Room_GetHeight(sector, x, y, z); if (height == NO_HEIGHT) { return false; } diff --git a/src/tr1/game/lara/cheat.h b/src/tr1/game/lara/cheat.h index 6214b5832..ab2e5050f 100644 --- a/src/tr1/game/lara/cheat.h +++ b/src/tr1/game/lara/cheat.h @@ -11,3 +11,4 @@ bool Lara_Cheat_GiveAllGuns(void); bool Lara_Cheat_GiveAllItems(void); bool Lara_Cheat_OpenNearestDoor(void); bool Lara_Cheat_KillEnemy(int16_t item_num); +bool Lara_Cheat_Teleport(int32_t x, int32_t y, int32_t z); diff --git a/src/tr1/game/lara/common.c b/src/tr1/game/lara/common.c index 6596d42fc..faeb7b494 100644 --- a/src/tr1/game/lara/common.c +++ b/src/tr1/game/lara/common.c @@ -19,7 +19,6 @@ #include "game/savegame.h" #include "game/sound.h" #include "game/spawn.h" -#include "game/stats.h" #include "global/const.h" #include "global/vars.h" @@ -273,7 +272,11 @@ void Lara_Control(void) item->hit_points = -1; if (!g_Lara.death_timer) { Music_Stop(); - Stats_AddDeath(); + g_GameInfo.death_count++; + if (Savegame_GetBoundSlot() != -1) { + Savegame_UpdateDeathCounters( + Savegame_GetBoundSlot(), &g_GameInfo); + } } g_Lara.death_timer++; // make sure the enemy healthbar is no longer rendered. If g_Lara later @@ -330,9 +333,6 @@ void Lara_Control(void) default: break; } - - Stats_AddDistanceTravelled(item->pos, g_Lara.last_pos); - g_Lara.last_pos = item->pos; } void Lara_SwapMeshExtra(void) @@ -392,30 +392,28 @@ void Lara_UseItem(const GAME_OBJECT_ID obj_id) } break; - case O_SMALL_MEDIPACK_ITEM: - case O_SMALL_MEDIPACK_OPTION: + case O_MEDI_ITEM: + case O_MEDI_OPTION: if (g_LaraItem->hit_points <= 0 || g_LaraItem->hit_points >= LARA_MAX_HITPOINTS) { return; } g_LaraItem->hit_points += LARA_MAX_HITPOINTS / 2; CLAMPG(g_LaraItem->hit_points, LARA_MAX_HITPOINTS); - Inv_RemoveItem(O_SMALL_MEDIPACK_ITEM); + Inv_RemoveItem(O_MEDI_ITEM); Sound_Effect(SFX_MENU_MEDI, nullptr, SPM_ALWAYS); - Stats_AddMedipacksUsed(.5); break; - case O_LARGE_MEDIPACK_ITEM: - case O_LARGE_MEDIPACK_OPTION: + case O_BIGMEDI_ITEM: + case O_BIGMEDI_OPTION: if (g_LaraItem->hit_points <= 0 || g_LaraItem->hit_points >= LARA_MAX_HITPOINTS) { return; } g_LaraItem->hit_points = g_LaraItem->hit_points + LARA_MAX_HITPOINTS; CLAMPG(g_LaraItem->hit_points, LARA_MAX_HITPOINTS); - Inv_RemoveItem(O_LARGE_MEDIPACK_ITEM); + Inv_RemoveItem(O_BIGMEDI_ITEM); Sound_Effect(SFX_MENU_MEDI, nullptr, SPM_ALWAYS); - Stats_AddMedipacksUsed(1); break; case O_KEY_ITEM_1: @@ -499,7 +497,6 @@ void Lara_Initialise(const GF_LEVEL *const level) g_Lara.hit_direction = 0; g_Lara.death_timer = 0; g_Lara.target = nullptr; - g_Lara.last_pos = g_LaraItem->pos; g_Lara.hit_effect = nullptr; g_Lara.hit_effect_count = 0; g_Lara.turn_rate = 0; @@ -541,14 +538,14 @@ void Lara_InitialiseInventory(const GF_LEVEL *const level) RESUME_INFO *const resume = Savegame_GetCurrentInfo(level); - g_Lara.pistol_ammo.ammo = 1000; + g_Lara.pistols.ammo = 1000; if (resume != nullptr) { if (g_GameInfo.remove_guns) { - resume->flags.has_pistols = 0; - resume->flags.has_shotgun = 0; - resume->flags.has_magnums = 0; - resume->flags.has_uzis = 0; + resume->flags.got_pistols = 0; + resume->flags.got_shotgun = 0; + resume->flags.got_magnums = 0; + resume->flags.got_uzis = 0; resume->equipped_gun_type = LGT_UNARMED; resume->holsters_gun_type = LGT_UNARMED; resume->back_gun_type = LGT_UNARMED; @@ -566,60 +563,60 @@ void Lara_InitialiseInventory(const GF_LEVEL *const level) } if (g_GameInfo.remove_medipacks) { - resume->small_medipacks = 0; - resume->large_medipacks = 0; + resume->num_medis = 0; + resume->num_big_medis = 0; } - if (resume->flags.has_pistols) { + if (resume->flags.got_pistols) { Inv_AddItem(O_PISTOL_ITEM); } - if (resume->flags.has_magnums) { + if (resume->flags.got_magnums) { Inv_AddItem(O_MAGNUM_ITEM); - g_Lara.magnum_ammo.ammo = resume->magnum_ammo; - Item_GlobalReplace(O_MAGNUM_ITEM, O_MAGNUM_AMMO_ITEM); + g_Lara.magnums.ammo = resume->magnum_ammo; + Item_GlobalReplace(O_MAGNUM_ITEM, O_MAG_AMMO_ITEM); } else { int32_t ammo = resume->magnum_ammo / MAGNUM_AMMO_QTY; for (int i = 0; i < ammo; i++) { - Inv_AddItem(O_MAGNUM_AMMO_ITEM); + Inv_AddItem(O_MAG_AMMO_ITEM); } - g_Lara.magnum_ammo.ammo = 0; + g_Lara.magnums.ammo = 0; } - if (resume->flags.has_uzis) { + if (resume->flags.got_uzis) { Inv_AddItem(O_UZI_ITEM); - g_Lara.uzi_ammo.ammo = resume->uzi_ammo; + g_Lara.uzis.ammo = resume->uzi_ammo; Item_GlobalReplace(O_UZI_ITEM, O_UZI_AMMO_ITEM); } else { int32_t ammo = resume->uzi_ammo / UZI_AMMO_QTY; for (int i = 0; i < ammo; i++) { Inv_AddItem(O_UZI_AMMO_ITEM); } - g_Lara.uzi_ammo.ammo = 0; + g_Lara.uzis.ammo = 0; } - if (resume->flags.has_shotgun) { + if (resume->flags.got_shotgun) { Inv_AddItem(O_SHOTGUN_ITEM); - g_Lara.shotgun_ammo.ammo = resume->shotgun_ammo; - Item_GlobalReplace(O_SHOTGUN_ITEM, O_SHOTGUN_AMMO_ITEM); + g_Lara.shotgun.ammo = resume->shotgun_ammo; + Item_GlobalReplace(O_SHOTGUN_ITEM, O_SG_AMMO_ITEM); } else { int32_t ammo = resume->shotgun_ammo / SHOTGUN_AMMO_QTY; for (int i = 0; i < ammo; i++) { - Inv_AddItem(O_SHOTGUN_AMMO_ITEM); + Inv_AddItem(O_SG_AMMO_ITEM); } - g_Lara.shotgun_ammo.ammo = 0; + g_Lara.shotgun.ammo = 0; } for (int i = 0; i < resume->num_scions; i++) { Inv_AddItem(O_SCION_ITEM_1); } - for (int i = 0; i < resume->small_medipacks; i++) { - Inv_AddItem(O_SMALL_MEDIPACK_ITEM); + for (int i = 0; i < resume->num_medis; i++) { + Inv_AddItem(O_MEDI_ITEM); } - for (int i = 0; i < resume->large_medipacks; i++) { - Inv_AddItem(O_LARGE_MEDIPACK_ITEM); + for (int i = 0; i < resume->num_big_medis; i++) { + Inv_AddItem(O_BIGMEDI_ITEM); } g_Lara.gun_status = resume->gun_status; @@ -701,6 +698,21 @@ bool Lara_IsNearItem(const XYZ_32 *pos, int32_t distance) return Item_IsNearItem(g_LaraItem, pos, distance); } +bool Lara_TestBoundsCollide(ITEM *item, int32_t radius) +{ + return Item_TestBoundsCollide(g_LaraItem, item, radius); +} + +bool Lara_TestPosition(const ITEM *item, const OBJECT_BOUNDS *const bounds) +{ + return Item_TestPosition(g_LaraItem, item, bounds); +} + +void Lara_AlignPosition(ITEM *item, XYZ_32 *vec) +{ + Item_AlignPosition(g_LaraItem, item, vec); +} + bool Lara_MovePosition(ITEM *item, XYZ_32 *vec) { int32_t velocity = g_Config.gameplay.enable_walk_to_items @@ -711,13 +723,11 @@ bool Lara_MovePosition(ITEM *item, XYZ_32 *vec) return Item_MovePosition(g_LaraItem, item, vec, velocity); } -void Lara_Push( - const ITEM *const item, COLL_INFO *const coll, const bool hit_on, - const bool big_push) +void Lara_Push(ITEM *item, COLL_INFO *coll, bool hit_on, bool big_push) { - ITEM *const target_item = Lara_GetItem(); - int32_t x = target_item->pos.x - item->pos.x; - int32_t z = target_item->pos.z - item->pos.z; + ITEM *const lara_item = g_LaraItem; + int32_t x = lara_item->pos.x - item->pos.x; + int32_t z = lara_item->pos.z - item->pos.z; const int32_t c = Math_Cos(item->rot.y); const int32_t s = Math_Sin(item->rot.y); int32_t rx = (c * x - s * z) >> W2V_SHIFT; @@ -755,8 +765,8 @@ void Lara_Push( int32_t ax = (c * rx + s * rz) >> W2V_SHIFT; int32_t az = (c * rz - s * rx) >> W2V_SHIFT; - target_item->pos.x = item->pos.x + ax; - target_item->pos.z = item->pos.z + az; + lara_item->pos.x = item->pos.x + ax; + lara_item->pos.z = item->pos.z + az; rx = (bounds->min.x + bounds->max.x) / 2; rz = (bounds->min.z + bounds->max.z) / 2; @@ -764,10 +774,10 @@ void Lara_Push( z -= (c * rz - s * rx) >> W2V_SHIFT; if (hit_on) { - PHD_ANGLE hitang = target_item->rot.y - (DEG_180 + Math_Atan(z, x)); + PHD_ANGLE hitang = lara_item->rot.y - (DEG_180 + Math_Atan(z, x)); g_Lara.hit_direction = (hitang + DEG_45) / DEG_90; if (!g_Lara.hit_frame) { - Sound_Effect(SFX_LARA_BODYSL, &target_item->pos, SPM_NORMAL); + Sound_Effect(SFX_LARA_BODYSL, &lara_item->pos, SPM_NORMAL); } g_Lara.hit_frame++; @@ -782,20 +792,20 @@ void Lara_Push( int16_t old_facing = coll->facing; coll->facing = Math_Atan( - target_item->pos.z - coll->old.z, target_item->pos.x - coll->old.x); + lara_item->pos.z - coll->old.z, lara_item->pos.x - coll->old.x); Collide_GetCollisionInfo( - coll, target_item->pos.x, target_item->pos.y, target_item->pos.z, - target_item->room_num, LARA_HEIGHT); + coll, lara_item->pos.x, lara_item->pos.y, lara_item->pos.z, + lara_item->room_num, LARA_HEIGHT); coll->facing = old_facing; if (coll->coll_type != COLL_NONE) { - target_item->pos.x = coll->old.x; - target_item->pos.z = coll->old.z; + lara_item->pos.x = coll->old.x; + lara_item->pos.z = coll->old.z; } else { - coll->old.x = target_item->pos.x; - coll->old.y = target_item->pos.y; - coll->old.z = target_item->pos.z; - Item_UpdateRoom(target_item, -10); + coll->old.x = lara_item->pos.x; + coll->old.y = lara_item->pos.y; + coll->old.z = lara_item->pos.z; + Item_UpdateRoom(item, -10); } if (g_Lara.interact_target.is_moving @@ -805,3 +815,8 @@ void Lara_Push( } } } + +void Lara_TakeDamage(int16_t damage, bool hit_status) +{ + Item_TakeDamage(g_LaraItem, damage, hit_status); +} diff --git a/src/tr1/game/lara/common.h b/src/tr1/game/lara/common.h index 620b0c31e..9ef933546 100644 --- a/src/tr1/game/lara/common.h +++ b/src/tr1/game/lara/common.h @@ -26,6 +26,12 @@ void Lara_SwapMeshExtra(void); bool Lara_IsNearItem(const XYZ_32 *pos, int32_t distance); void Lara_UseItem(GAME_OBJECT_ID obj_id); +bool Lara_TestBoundsCollide(ITEM *item, int32_t radius); +bool Lara_TestPosition(const ITEM *item, const OBJECT_BOUNDS *bounds); +void Lara_AlignPosition(ITEM *item, XYZ_32 *vec); bool Lara_MovePosition(ITEM *item, XYZ_32 *vec); +void Lara_Push(ITEM *item, COLL_INFO *coll, bool hit_on, bool big_push); + +void Lara_TakeDamage(int16_t damage, bool hit_status); void Lara_RevertToPistolsIfNeeded(void); diff --git a/src/tr1/game/lara/misc.h b/src/tr1/game/lara/misc.h index 6b71bfa7e..9e918e723 100644 --- a/src/tr1/game/lara/misc.h +++ b/src/tr1/game/lara/misc.h @@ -23,3 +23,4 @@ int32_t Lara_GetWaterDepth(int32_t x, int32_t y, int32_t z, int16_t room_num); void Lara_TestWaterDepth(ITEM *item, const COLL_INFO *coll); bool Lara_TestWaterStepOut(ITEM *item, const COLL_INFO *coll); bool Lara_TestWaterClimbOut(ITEM *item, COLL_INFO *coll); +void Lara_CatchFire(void); diff --git a/src/tr1/game/level.c b/src/tr1/game/level.c index 4000c3edc..d11b6dfe8 100644 --- a/src/tr1/game/level.c +++ b/src/tr1/game/level.c @@ -4,6 +4,7 @@ #include "game/carrier.h" #include "game/effects.h" #include "game/game.h" +#include "game/game_flow.h" #include "game/inventory_ring/vars.h" #include "game/items.h" #include "game/lara/common.h" @@ -53,6 +54,7 @@ static LEVEL_LAYOUT M_GuessLayout(VFILE *file); static void M_LoadFromFile(const GF_LEVEL *level); static void M_CompleteSetup(const GF_LEVEL *level); static void M_MarkWaterEdgeVertices(void); +static size_t M_CalculateMaxVertices(void); static bool M_TryLayout(VFILE *const file, const LEVEL_LAYOUT layout) { @@ -217,6 +219,7 @@ static void M_LoadFromFile(const GF_LEVEL *const level) Level_ReadPathingData(file); Level_ReadAnimatedTextureRanges(file); Level_ReadItems(file); + Stats_ObserveItemsLoad(); Level_ReadLightMap(file); if (layout != LEVEL_LAYOUT_TR1_DEMO_PC) { @@ -259,11 +262,15 @@ static void M_CompleteSetup(const GF_LEVEL *const level) // Configure enemies who carry and drop items Carrier_InitialiseLevel(level); + const size_t max_vertices = M_CalculateMaxVertices(); + LOG_INFO("Maximum vertices: %d", max_vertices); + Output_ReserveVertexBuffer(max_vertices); + Level_LoadTextures(); Level_LoadTexturePages(); Level_LoadPalettes(); Level_LoadFaces(); - Output_ObserveLevelLoad(); + Output_DownloadTextures(); // Initialise the sound effects. LEVEL_INFO *const info = Level_GetInfo(); @@ -314,6 +321,41 @@ static void M_MarkWaterEdgeVertices(void) Benchmark_End(&benchmark, nullptr); } +static size_t M_CalculateMaxVertices(void) +{ + BENCHMARK benchmark = Benchmark_Start(); + int32_t max_vertices = 0; + for (int32_t i = 0; i < O_NUMBER_OF; i++) { + const OBJECT *const obj = Object_Get(i); + if (!obj->loaded) { + continue; + } + + for (int32_t j = 0; j < obj->mesh_count; j++) { + const OBJECT_MESH *const mesh = Object_GetMesh(obj->mesh_idx + j); + max_vertices = MAX(max_vertices, mesh->num_vertices); + } + } + + for (int32_t i = 0; i < MAX_STATIC_OBJECTS; i++) { + const STATIC_OBJECT_3D *obj = Object_Get3DStatic(i); + if (!obj->loaded) { + continue; + } + + const OBJECT_MESH *const mesh = Object_GetMesh(obj->mesh_idx); + max_vertices = MAX(max_vertices, mesh->num_vertices); + } + + for (int32_t i = 0; i < Room_GetCount(); i++) { + const ROOM *const room = Room_Get(i); + max_vertices = MAX(max_vertices, room->mesh.num_vertices); + } + + Benchmark_End(&benchmark, nullptr); + return max_vertices; +} + void Level_Load(const GF_LEVEL *const level) { LOG_INFO("%d (%s)", level->num, level->path); @@ -326,17 +368,15 @@ void Level_Load(const GF_LEVEL *const level) Inject_Cleanup(); + Output_SetWaterColor(&level->settings.water_color); + Output_SetDrawDistFade(level->settings.draw_distance_fade * WALL_L); + Output_SetDrawDistMax(level->settings.draw_distance_max * WALL_L); Output_SetSkyboxEnabled( g_Config.visuals.enable_skybox && Object_Get(O_SKYBOX)->loaded); Benchmark_End(&benchmark, nullptr); } -void Level_Unload(void) -{ - Output_ObserveLevelUnload(); -} - bool Level_Initialise( const GF_LEVEL *const level, const GF_SEQUENCE_CONTEXT seq_ctx) { @@ -356,10 +396,6 @@ bool Level_Initialise( resume->stats.secret_count = 0; resume->stats.pickup_count = 0; resume->stats.kill_count = 0; - resume->stats.ammo_hits = 0; - resume->stats.ammo_used = 0; - resume->stats.medipacks_used = 0; - resume->stats.distance_travelled = 0; } g_LevelComplete = false; @@ -372,12 +408,19 @@ bool Level_Initialise( Music_ResetTrackFlags(); - Object_Reset(); + /* Clear Object Loaded flags */ + for (int32_t i = 0; i < O_NUMBER_OF; i++) { + Object_Get(i)->loaded = false; + } + for (int32_t i = 0; i < MAX_STATIC_OBJECTS; i++) { + Object_Get2DStatic(i)->loaded = false; + Object_Get3DStatic(i)->loaded = false; + } + Camera_Reset(); Pierre_Reset(); Lara_InitialiseLoad(NO_ITEM); - Level_Unload(); Level_Load(level); GameStringTable_Apply(level); diff --git a/src/tr1/game/level.h b/src/tr1/game/level.h index 00a5b0856..986c528f1 100644 --- a/src/tr1/game/level.h +++ b/src/tr1/game/level.h @@ -2,7 +2,5 @@ #include "game/game_flow/types.h" -#include - bool Level_Initialise(const GF_LEVEL *level, GF_SEQUENCE_CONTEXT seq_ctx); void Level_Load(const GF_LEVEL *level); diff --git a/src/tr1/game/los.h b/src/tr1/game/los.h index 96041ee31..0a5981260 100644 --- a/src/tr1/game/los.h +++ b/src/tr1/game/los.h @@ -1,3 +1,5 @@ #pragma once -#include +#include "global/types.h" + +bool LOS_Check(const GAME_VECTOR *start, GAME_VECTOR *target); diff --git a/src/tr1/game/lot.c b/src/tr1/game/lot.c index 16691919a..770d11391 100644 --- a/src/tr1/game/lot.c +++ b/src/tr1/game/lot.c @@ -16,8 +16,8 @@ static CREATURE *m_BaddieSlots = nullptr; void LOT_InitialiseArray(void) { m_BaddieSlots = - GameBuf_Alloc(LOT_SLOT_COUNT * sizeof(CREATURE), GBUF_CREATURE_DATA); - for (int i = 0; i < LOT_SLOT_COUNT; i++) { + GameBuf_Alloc(NUM_SLOTS * sizeof(CREATURE), GBUF_CREATURE_DATA); + for (int i = 0; i < NUM_SLOTS; i++) { CREATURE *creature = &m_BaddieSlots[i]; creature->item_num = NO_ITEM; creature->lot.node = @@ -26,11 +26,6 @@ void LOT_InitialiseArray(void) m_SlotsUsed = 0; } -CREATURE *LOT_GetBaddieSlot(const int32_t i) -{ - return &m_BaddieSlots[i]; -} - void LOT_DisableBaddieAI(int16_t item_num) { ITEM *const item = Item_Get(item_num); @@ -42,14 +37,14 @@ void LOT_DisableBaddieAI(int16_t item_num) } } -bool LOT_EnableBaddieAI(const int16_t item_num, const bool always) +bool LOT_EnableBaddieAI(int16_t item_num, int32_t always) { if (Item_Get(item_num)->data != nullptr) { return true; } - if (m_SlotsUsed < LOT_SLOT_COUNT) { - for (int32_t slot = 0; slot < LOT_SLOT_COUNT; slot++) { + if (m_SlotsUsed < NUM_SLOTS) { + for (int32_t slot = 0; slot < NUM_SLOTS; slot++) { CREATURE *creature = &m_BaddieSlots[slot]; if (creature->item_num == NO_ITEM) { LOT_InitialiseSlot(item_num, slot); @@ -69,7 +64,7 @@ bool LOT_EnableBaddieAI(const int16_t item_num, const bool always) } int32_t worst_slot = -1; - for (int32_t slot = 0; slot < LOT_SLOT_COUNT; slot++) { + for (int32_t slot = 0; slot < NUM_SLOTS; slot++) { CREATURE *creature = &m_BaddieSlots[slot]; const ITEM *const item = Item_Get(creature->item_num); int32_t x = (item->pos.x - g_Camera.pos.x) >> 8; diff --git a/src/tr1/game/lot.h b/src/tr1/game/lot.h index 8fbc00b89..35be3b086 100644 --- a/src/tr1/game/lot.h +++ b/src/tr1/game/lot.h @@ -2,11 +2,12 @@ #include "global/types.h" -#include - #include void LOT_InitialiseArray(void); +void LOT_DisableBaddieAI(int16_t item_num); +bool LOT_EnableBaddieAI(int16_t item_num, int32_t always); void LOT_InitialiseSlot(int16_t item_num, int32_t slot); void LOT_CreateZone(ITEM *item); void LOT_InitialiseLOT(LOT_INFO *LOT); +void LOT_ClearLOT(LOT_INFO *LOT); diff --git a/src/tr1/game/music.c b/src/tr1/game/music.c index 6a0467c8a..c4700d9f1 100644 --- a/src/tr1/game/music.c +++ b/src/tr1/game/music.c @@ -182,12 +182,12 @@ void Music_Unmute(void) M_SyncVolume(m_AudioStreamID); } -int32_t Music_GetVolume(void) +int16_t Music_GetVolume(void) { return m_Volume; } -void Music_SetVolume(int32_t volume) +void Music_SetVolume(int16_t volume) { if (volume != m_Volume) { m_Volume = volume; @@ -195,6 +195,16 @@ void Music_SetVolume(int32_t volume) } } +int16_t Music_GetMinVolume(void) +{ + return 0; +} + +int16_t Music_GetMaxVolume(void) +{ + return 10; +} + void Music_Pause(void) { if (m_AudioStreamID < 0) { diff --git a/src/tr1/game/music.h b/src/tr1/game/music.h index 32957af65..97de0c9ad 100644 --- a/src/tr1/game/music.h +++ b/src/tr1/game/music.h @@ -19,6 +19,18 @@ void Music_Mute(void); // Unmutes the game music. Doesn't change the music volume. void Music_Unmute(void); +// Gets the game volume. +int16_t Music_GetVolume(void); + +// Sets the game volume. Value can be 0-10. +void Music_SetVolume(int16_t volume); + +// Gets the minimum possible game volume. +int16_t Music_GetMinVolume(void); + +// Gets the maximum possible game volume. +int16_t Music_GetMaxVolume(void); + // Returns the currently playing track. Includes looped music. MUSIC_TRACK_ID Music_GetCurrentPlayingTrack(void); diff --git a/src/tr1/game/objects/common.c b/src/tr1/game/objects/common.c index 9e45ab52e..e51bc1872 100644 --- a/src/tr1/game/objects/common.c +++ b/src/tr1/game/objects/common.c @@ -5,7 +5,6 @@ #include "game/lara/common.h" #include "game/objects/vars.h" #include "game/output.h" -#include "game/output/meshes/objects.h" #include "game/room.h" #include "game/viewport.h" #include "global/const.h" @@ -73,12 +72,11 @@ void Object_DrawDummyItem(const ITEM *const item) void Object_DrawSpriteItem(const ITEM *const item) { - const RGB_F tint = Output_GetTint(); Output_DrawSprite( item->interp.result.pos.x, item->interp.result.pos.y, item->interp.result.pos.z, Object_Get(item->object_id)->mesh_idx - item->frame_num, - item->shade.value_1 < 0 ? SHADE_NEUTRAL : item->shade.value_1, tint); + item->shade.value_1); } void Object_DrawPickupItem(const ITEM *const item) @@ -141,7 +139,7 @@ void Object_DrawPickupItem(const ITEM *const item) case O_SHOTGUN_OPTION: case O_MAGNUM_OPTION: case O_UZI_OPTION: - case O_MAGNUM_AMMO_OPTION: + case O_MAG_AMMO_OPTION: case O_UZI_AMMO_OPTION: case O_EXPLOSIVE_OPTION: case O_LEADBAR_OPTION: @@ -151,9 +149,9 @@ void Object_DrawPickupItem(const ITEM *const item) // Ignore the sprite and just position based upon the anim. offset = item->pos.y + (min_y - anim_y) / 2; break; - case O_SMALL_MEDIPACK_OPTION: - case O_LARGE_MEDIPACK_OPTION: - case O_SHOTGUN_AMMO_OPTION: + case O_MEDI_OPTION: + case O_BIGMEDI_OPTION: + case O_SG_AMMO_OPTION: case O_PUZZLE_OPTION_1: case O_PUZZLE_OPTION_2: case O_PUZZLE_OPTION_3: @@ -280,19 +278,22 @@ void Object_SetMeshReflective( OBJECT_MESH *const mesh = Object_GetMesh(obj->mesh_idx + mesh_idx); mesh->enable_reflections = enabled; + for (int32_t i = 0; i < mesh->num_tex_face4s; i++) { mesh->tex_face4s[i].enable_reflections = enabled; } + for (int32_t i = 0; i < mesh->num_tex_face3s; i++) { mesh->tex_face3s[i].enable_reflections = enabled; } + for (int32_t i = 0; i < mesh->num_flat_face4s; i++) { mesh->flat_face4s[i].enable_reflections = enabled; } + for (int32_t i = 0; i < mesh->num_flat_face3s; i++) { mesh->flat_face3s[i].enable_reflections = enabled; } - Output_Meshes_ObserveObjectMeshUpdate(mesh); } void Object_SetReflective(const GAME_OBJECT_ID obj_id, const bool enabled) diff --git a/src/tr1/game/objects/common.h b/src/tr1/game/objects/common.h index 5b9da9fdc..dd39f97e8 100644 --- a/src/tr1/game/objects/common.h +++ b/src/tr1/game/objects/common.h @@ -12,6 +12,7 @@ void Object_DrawDummyItem(const ITEM *item); void Object_DrawSpriteItem(const ITEM *item); void Object_DrawPickupItem(const ITEM *item); void Object_DrawAnimatingItem(const ITEM *item); +void Object_DrawUnclippedItem(const ITEM *item); void Object_SetMeshReflective( GAME_OBJECT_ID obj_id, int32_t mesh_idx, bool enabled); void Object_SetReflective(GAME_OBJECT_ID obj_id, bool enabled); diff --git a/src/tr1/game/objects/creatures/ape.c b/src/tr1/game/objects/creatures/ape.c index 90d82754b..63728f441 100644 --- a/src/tr1/game/objects/creatures/ape.c +++ b/src/tr1/game/objects/creatures/ape.c @@ -44,7 +44,7 @@ typedef enum { APE_STATE_VAULT = 11, } APE_STATE; -static BITE m_ApeBite = { .pos = { 0, -19, 75 }, .mesh_num = 15 }; +static BITE m_ApeBite = { 0, -19, 75, 15 }; static bool M_Vault(int16_t item_num, int16_t angle); static void M_Setup(OBJECT *obj); @@ -127,7 +127,7 @@ static void M_Setup(OBJECT *const obj) obj->save_anim = 1; obj->save_flags = 1; - Object_GetBone(obj, 13)->rot.y = true; + Object_GetBone(obj, 13)->rot_y = true; } static void M_Control(const int16_t item_num) diff --git a/src/tr1/game/objects/creatures/baldy.c b/src/tr1/game/objects/creatures/baldy.c index fa1f18d60..756d9810a 100644 --- a/src/tr1/game/objects/creatures/baldy.c +++ b/src/tr1/game/objects/creatures/baldy.c @@ -26,7 +26,7 @@ typedef enum { BALDY_STATE_SHOOT = 6, } BALDY_STATE; -static BITE m_BaldyGun = { .pos = { -20, 440, 20 }, .mesh_num = 9 }; +static BITE m_BaldyGun = { -20, 440, 20, 9 }; static void M_Setup(OBJECT *obj); static void M_Initialise(int16_t item_num); @@ -52,7 +52,7 @@ static void M_Setup(OBJECT *const obj) obj->save_anim = 1; obj->save_flags = 1; - Object_GetBone(obj, 0)->rot.y = true; + Object_GetBone(obj, 0)->rot_y = true; } static void M_Initialise(const int16_t item_num) diff --git a/src/tr1/game/objects/creatures/bat.c b/src/tr1/game/objects/creatures/bat.c index f3cea22da..fcbb953b2 100644 --- a/src/tr1/game/objects/creatures/bat.c +++ b/src/tr1/game/objects/creatures/bat.c @@ -25,7 +25,7 @@ typedef enum { BAT_STATE_DEATH = 5, } BAT_STATE; -static BITE m_BatBite = { .pos = { 0, 16, 45 }, .mesh_num = 4 }; +static BITE m_BatBite = { 0, 16, 45, 4 }; static void M_FixEmbeddedPosition(int16_t item_num); static void M_Setup(OBJECT *obj); diff --git a/src/libtrx/game/objects/creatures/bear.c b/src/tr1/game/objects/creatures/bear.c similarity index 82% rename from src/libtrx/game/objects/creatures/bear.c rename to src/tr1/game/objects/creatures/bear.c index 6489c1d9c..157f21e57 100644 --- a/src/libtrx/game/objects/creatures/bear.c +++ b/src/tr1/game/objects/creatures/bear.c @@ -1,70 +1,90 @@ -#include "game/objects/creatures/bear.h" - -#include "config.h" -#include "game/const.h" #include "game/creature.h" +#include "game/items.h" #include "game/lara/common.h" +#include "game/lot.h" #include "game/random.h" #include "game/spawn.h" -#include "utils.h" +#include "global/const.h" +#include "global/types.h" +#include "global/vars.h" + +#include +#include -// clang-format off #define BEAR_CHARGE_DAMAGE 3 -#define BEAR_SLAM_DAMAGE 200 +#define BEAR_SLAM_DAMAGE 200 #define BEAR_ATTACK_DAMAGE 200 -#define BEAR_PAT_DAMAGE 400 -#define BEAR_TOUCH 0x2406C -#define BEAR_ROAR_CHANCE 80 -#define BEAR_REAR_CHANCE 768 -#define BEAR_DROP_CHANCE 1536 -#define BEAR_REAR_RANGE SQUARE(WALL_L * 2) // = 4194304 -#define BEAR_ATTACK_RANGE SQUARE(WALL_L) // = 1048576 -#define BEAR_PAT_RANGE SQUARE(600) // = 360000 +#define BEAR_PAT_DAMAGE 400 +#define BEAR_TOUCH 0x2406C +#define BEAR_ROAR_CHANCE 80 +#define BEAR_REAR_CHANCE 768 +#define BEAR_DROP_CHANCE 1536 +#define BEAR_REAR_RANGE SQUARE(WALL_L * 2) // = 4194304 +#define BEAR_ATTACK_RANGE SQUARE(WALL_L) // = 1048576 +#define BEAR_PAT_RANGE SQUARE(600) // = 360000 #define BEAR_FIX_PAT_RANGE SQUARE(300) // = 90000 -#define BEAR_RUN_TURN (5 * DEG_1) // = 910 -#define BEAR_WALK_TURN (2 * DEG_1) // = 364 -#define BEAR_EAT_RANGE SQUARE(WALL_L * 3 / 4) // = 589824 -#if TR_VERSION == 1 -#define BEAR_HITPOINTS 20 -#else -#define BEAR_HITPOINTS 30 -#endif -#define BEAR_RADIUS (WALL_L / 3) // = 341 -#define BEAR_SMARTNESS 0x4000 -// clang-format on +#define BEAR_RUN_TURN (5 * DEG_1) // = 910 +#define BEAR_WALK_TURN (2 * DEG_1) // = 364 +#define BEAR_EAT_RANGE SQUARE(WALL_L * 3 / 4) // = 589824 +#define BEAR_HITPOINTS 20 +#define BEAR_RADIUS (WALL_L / 3) // = 341 +#define BEAR_SMARTNESS 0x4000 typedef enum { - // clang-format off - BEAR_STATE_STROLL = 0, - BEAR_STATE_STOP = 1, - BEAR_STATE_WALK = 2, - BEAR_STATE_RUN = 3, - BEAR_STATE_REAR = 4, - BEAR_STATE_ROAR = 5, + BEAR_STATE_STROLL = 0, + BEAR_STATE_STOP = 1, + BEAR_STATE_WALK = 2, + BEAR_STATE_RUN = 3, + BEAR_STATE_REAR = 4, + BEAR_STATE_ROAR = 5, BEAR_STATE_ATTACK_1 = 6, BEAR_STATE_ATTACK_2 = 7, - BEAR_STATE_EAT = 8, - BEAR_STATE_DEATH = 9, - // clang-format on + BEAR_STATE_EAT = 8, + BEAR_STATE_DEATH = 9, } BEAR_STATE; -static BITE m_BearHeadBite = { .pos = { 0, 96, 335 }, .mesh_num = 14 }; +static BITE m_BearHeadBite = { 0, 96, 335, 14 }; +static void M_Setup(OBJECT *obj); static void M_Control(int16_t item_num); +static void M_Setup(OBJECT *const obj) +{ + if (!obj->loaded) { + return; + } + obj->initialise_func = Creature_Initialise; + obj->control_func = M_Control; + obj->collision_func = Creature_Collision; + obj->shadow_size = UNIT_SHADOW / 2; + obj->hit_points = BEAR_HITPOINTS; + obj->radius = BEAR_RADIUS; + obj->smartness = BEAR_SMARTNESS; + obj->intelligent = 1; + obj->save_position = 1; + obj->save_hitpoints = 1; + obj->save_anim = 1; + obj->save_flags = 1; + + Object_GetBone(obj, 13)->rot_y = true; +} + static void M_Control(const int16_t item_num) { - if (!Creature_Activate(item_num)) { - return; - } - ITEM *const item = Item_Get(item_num); OBJECT *const obj = Object_Get(item->object_id); obj->pivot_length = g_Config.gameplay.fix_bear_ai ? 0 : 500; - CREATURE *const bear = (CREATURE *)item->data; + if (item->status == IS_INVISIBLE) { + if (!LOT_EnableBaddieAI(item_num, 0)) { + return; + } + item->status = IS_ACTIVE; + } + + CREATURE *bear = (CREATURE *)item->data; int16_t head = 0; - int16_t angle = 0; + PHD_ANGLE angle = 0; if (item->hit_points <= 0) { angle = Creature_Turn(item, DEG_1); @@ -108,7 +128,7 @@ static void M_Control(const int16_t item_num) angle = Creature_Turn(item, bear->maximum_turn); - const bool dead_enemy = Lara_GetItem()->hit_points <= 0; + int dead_enemy = g_LaraItem->hit_points <= 0; if (item->hit_status) { bear->flags = 1; } @@ -225,25 +245,4 @@ static void M_Control(const int16_t item_num) Creature_Animate(item_num, angle, 0); } -void Bear_Setup(OBJECT *const obj) -{ - if (!obj->loaded) { - return; - } - obj->initialise_func = Creature_Initialise; - obj->control_func = M_Control; - obj->collision_func = Creature_Collision; - obj->shadow_size = UNIT_SHADOW / 2; - obj->hit_points = BEAR_HITPOINTS; - obj->radius = BEAR_RADIUS; - obj->smartness = BEAR_SMARTNESS; - obj->intelligent = 1; - obj->save_position = 1; - obj->save_hitpoints = 1; - obj->save_anim = 1; - obj->save_flags = 1; - - Object_GetBone(obj, 13)->rot.y = true; -} - -REGISTER_OBJECT(O_BEAR, Bear_Setup) +REGISTER_OBJECT(O_BEAR, M_Setup) diff --git a/src/tr1/game/objects/creatures/centaur.c b/src/tr1/game/objects/creatures/centaur.c index 89ba04f1f..b3f15e1d8 100644 --- a/src/tr1/game/objects/creatures/centaur.c +++ b/src/tr1/game/objects/creatures/centaur.c @@ -32,8 +32,8 @@ typedef enum { CENTAUR_STATE_WARNING = 6, } CENTAUR_STATE; -static BITE m_CentaurRocket = { .pos = { 11, 415, 41 }, .mesh_num = 13 }; -static BITE m_CentaurRear = { .pos = { 50, 30, 0 }, .mesh_num = 5 }; +static BITE m_CentaurRocket = { 11, 415, 41, 13 }; +static BITE m_CentaurRear = { 50, 30, 0, 5 }; static void M_Setup(OBJECT *obj); static void M_Control(int16_t item_num); @@ -57,8 +57,8 @@ static void M_Setup(OBJECT *const obj) obj->save_anim = 1; obj->save_flags = 1; - Object_GetBone(obj, 10)->rot.x = true; - Object_GetBone(obj, 10)->rot.y = true; + Object_GetBone(obj, 10)->rot_x = true; + Object_GetBone(obj, 10)->rot_y = true; } static void M_Control(const int16_t item_num) diff --git a/src/tr1/game/objects/creatures/cowboy.c b/src/tr1/game/objects/creatures/cowboy.c index d6030d5dd..7c158662b 100644 --- a/src/tr1/game/objects/creatures/cowboy.c +++ b/src/tr1/game/objects/creatures/cowboy.c @@ -28,8 +28,8 @@ typedef enum { COWBOY_STATE_SHOOT = 6, } COWBOY_STATE; -static BITE m_CowboyGun1 = { .pos = { 1, 200, 41 }, .mesh_num = 5 }; -static BITE m_CowboyGun2 = { .pos = { -2, 200, 40 }, .mesh_num = 8 }; +static BITE m_CowboyGun1 = { 1, 200, 41, 5 }; +static BITE m_CowboyGun2 = { -2, 200, 40, 8 }; static void M_Setup(OBJECT *obj); static void M_HandleSave(ITEM *item, SAVEGAME_STAGE stage); @@ -54,7 +54,7 @@ static void M_Setup(OBJECT *const obj) obj->save_anim = 1; obj->save_flags = 1; - Object_GetBone(obj, 0)->rot.y = true; + Object_GetBone(obj, 0)->rot_y = true; } static void M_HandleSave(ITEM *const item, const SAVEGAME_STAGE stage) diff --git a/src/tr1/game/objects/creatures/crocodile.c b/src/tr1/game/objects/creatures/crocodile.c index 138949361..6da609a15 100644 --- a/src/tr1/game/objects/creatures/crocodile.c +++ b/src/tr1/game/objects/creatures/crocodile.c @@ -50,7 +50,7 @@ typedef enum { ALLIGATOR_STATE_DEATH = 3, } ALLIGATOR_STATE; -static BITE m_CrocodileBite = { .pos = { 5, -21, 467 }, .mesh_num = 9 }; +static BITE m_CrocodileBite = { 5, -21, 467, 9 }; static void M_SetupCrocodile(OBJECT *obj); static void M_SetupAlligator(OBJECT *obj); @@ -77,7 +77,7 @@ static void M_SetupBase(OBJECT *const obj) obj->save_hitpoints = 1; obj->save_anim = 1; obj->save_flags = 1; - Object_GetBone(obj, 7)->rot.y = true; + Object_GetBone(obj, 7)->rot_y = true; } static void M_SetupCrocodile(OBJECT *const obj) diff --git a/src/tr1/game/objects/creatures/larson.c b/src/tr1/game/objects/creatures/larson.c index 47084bc1f..6cb37430d 100644 --- a/src/tr1/game/objects/creatures/larson.c +++ b/src/tr1/game/objects/creatures/larson.c @@ -29,7 +29,7 @@ typedef enum { LARSON_STATE_SHOOT = 7, } LARSON_STATE; -static BITE m_LarsonGun = { .pos = { -60, 170, 0 }, .mesh_num = 14 }; +static BITE m_LarsonGun = { -60, 170, 0, 14 }; static void M_Setup(OBJECT *obj); static void M_HandleSave(ITEM *item, SAVEGAME_STAGE stage); @@ -54,7 +54,7 @@ static void M_Setup(OBJECT *const obj) obj->save_anim = 1; obj->save_flags = 1; - Object_GetBone(obj, 6)->rot.y = true; + Object_GetBone(obj, 6)->rot_y = true; } static void M_HandleSave(ITEM *const item, const SAVEGAME_STAGE stage) diff --git a/src/tr1/game/objects/creatures/lion.c b/src/tr1/game/objects/creatures/lion.c index d28516082..41325fe4d 100644 --- a/src/tr1/game/objects/creatures/lion.c +++ b/src/tr1/game/objects/creatures/lion.c @@ -41,7 +41,7 @@ typedef enum { LION_STATE_ATTACK_2 = 7, } LION_STATE; -static BITE m_LionBite = { .pos = { -2, -10, 132 }, .mesh_num = 21 }; +static BITE m_LionBite = { -2, -10, 132, 21 }; static void M_SetupBase(OBJECT *obj); static void M_SetupLion(OBJECT *obj); @@ -62,7 +62,7 @@ static void M_SetupBase(OBJECT *const obj) obj->save_anim = 1; obj->save_flags = 1; - Object_GetBone(obj, 19)->rot.y = true; + Object_GetBone(obj, 19)->rot_y = true; } static void M_SetupLion(OBJECT *const obj) diff --git a/src/tr1/game/objects/creatures/mummy.c b/src/tr1/game/objects/creatures/mummy.c index 05a5f0b71..d1904fd05 100644 --- a/src/tr1/game/objects/creatures/mummy.c +++ b/src/tr1/game/objects/creatures/mummy.c @@ -4,7 +4,6 @@ #include "game/items.h" #include "game/objects/common.h" #include "game/savegame.h" -#include "game/stats.h" #include "global/const.h" #include "global/vars.h" @@ -37,7 +36,7 @@ static void M_Setup(OBJECT *const obj) obj->save_hitpoints = 1; obj->save_anim = 1; - Object_GetBone(obj, 2)->rot.y = true; + Object_GetBone(obj, 2)->rot_y = true; } static void M_Initialise(const int16_t item_num) @@ -72,7 +71,7 @@ static void M_Control(const int16_t item_num) if (item->status == IS_DEACTIVATED) { // Count kill if Lara touches mummy and it falls. if (item->hit_points > 0) { - Stats_AddKill(); + Savegame_GetCurrentInfo(Game_GetCurrentLevel())->stats.kill_count++; } Item_RemoveActive(item_num); if (item->hit_points != DONT_TARGET) { diff --git a/src/tr1/game/objects/creatures/mutant.c b/src/tr1/game/objects/creatures/mutant.c index 3b12c474c..c22bc3190 100644 --- a/src/tr1/game/objects/creatures/mutant.c +++ b/src/tr1/game/objects/creatures/mutant.c @@ -55,9 +55,9 @@ typedef enum { } MUTANT_STATE; static bool m_EnableExplosions = true; -static BITE m_WarriorBite = { .pos = { -27, 98, 0 }, .mesh_num = 10 }; -static BITE m_WarriorRocket = { .pos = { 51, 213, 0 }, .mesh_num = 14 }; -static BITE m_WarriorShard = { .pos = { -35, 269, 0 }, .mesh_num = 9 }; +static BITE m_WarriorBite = { -27, 98, 0, 10 }; +static BITE m_WarriorRocket = { 51, 213, 0, 14 }; +static BITE m_WarriorShard = { -35, 269, 0, 9 }; static void M_Setup(OBJECT *obj); static void M_Setup2(OBJECT *obj); @@ -83,8 +83,8 @@ static void M_Setup(OBJECT *const obj) obj->save_hitpoints = 1; obj->save_anim = 1; obj->save_flags = 1; - Object_GetBone(obj, 0)->rot.y = true; - Object_GetBone(obj, 2)->rot.y = true; + Object_GetBone(obj, 0)->rot_y = true; + Object_GetBone(obj, 2)->rot_y = true; } static void M_Setup2(OBJECT *const obj) diff --git a/src/tr1/game/objects/creatures/natla.c b/src/tr1/game/objects/creatures/natla.c index bf0d2c490..2bb873089 100644 --- a/src/tr1/game/objects/creatures/natla.c +++ b/src/tr1/game/objects/creatures/natla.c @@ -40,7 +40,7 @@ typedef enum { NATLA_STATE_DEATH = 9, } NATLA_STATE; -static BITE m_NatlaGun = { .pos = { 5, 220, 7 }, .mesh_num = 4 }; +static BITE m_NatlaGun = { 5, 220, 7, 4 }; static void M_Setup(OBJECT *obj); static void M_Control(int16_t item_num); @@ -63,8 +63,8 @@ static void M_Setup(OBJECT *const obj) obj->save_anim = 1; obj->save_flags = 1; - Object_GetBone(obj, 2)->rot.x = true; - Object_GetBone(obj, 2)->rot.z = true; + Object_GetBone(obj, 2)->rot_x = true; + Object_GetBone(obj, 2)->rot_z = true; } static void M_Control(const int16_t item_num) diff --git a/src/tr1/game/objects/creatures/pierre.c b/src/tr1/game/objects/creatures/pierre.c index 70b91d819..7dd4f0120 100644 --- a/src/tr1/game/objects/creatures/pierre.c +++ b/src/tr1/game/objects/creatures/pierre.c @@ -38,8 +38,8 @@ typedef enum { PIERRE_STATE_SHOOT = 7, } PIERRE_STATE; -static BITE m_PierreGun1 = { .pos = { 60, 200, 0 }, .mesh_num = 11 }; -static BITE m_PierreGun2 = { .pos = { -57, 200, 0 }, .mesh_num = 14 }; +static BITE m_PierreGun1 = { 60, 200, 0, 11 }; +static BITE m_PierreGun2 = { -57, 200, 0, 14 }; static int16_t m_PierreItemNum = NO_ITEM; static void M_Setup(OBJECT *obj); @@ -65,7 +65,7 @@ static void M_Setup(OBJECT *const obj) obj->save_anim = 1; obj->save_flags = 1; - Object_GetBone(obj, 6)->rot.y = true; + Object_GetBone(obj, 6)->rot_y = true; } static void M_HandleSave(ITEM *const item, const SAVEGAME_STAGE stage) diff --git a/src/tr1/game/objects/creatures/raptor.c b/src/tr1/game/objects/creatures/raptor.c index 54d8f85ea..fd3845a7a 100644 --- a/src/tr1/game/objects/creatures/raptor.c +++ b/src/tr1/game/objects/creatures/raptor.c @@ -36,7 +36,7 @@ typedef enum { RAPTOR_STATE_ATTACK_3 = 8, } RAPTOR_STATE; -static BITE m_RaptorBite = { .pos = { 0, 66, 318 }, .mesh_num = 22 }; +static BITE m_RaptorBite = { 0, 66, 318, 22 }; static void M_Setup(OBJECT *obj); static void M_Control(int16_t item_num); @@ -60,7 +60,7 @@ static void M_Setup(OBJECT *const obj) obj->save_anim = 1; obj->save_flags = 1; - Object_GetBone(obj, 21)->rot.y = true; + Object_GetBone(obj, 21)->rot_y = true; } static void M_Control(const int16_t item_num) diff --git a/src/tr1/game/objects/creatures/rat.c b/src/tr1/game/objects/creatures/rat.c index 5c68c0d0a..b1e059132 100644 --- a/src/tr1/game/objects/creatures/rat.c +++ b/src/tr1/game/objects/creatures/rat.c @@ -44,7 +44,7 @@ typedef enum { VOLE_STATE_DEATH = 3, } VOLE_STATE; -static BITE m_RatBite = { .pos = { 0, -11, 108 }, .mesh_num = 3 }; +static BITE m_RatBite = { 0, -11, 108, 3 }; static void M_SetupBase(OBJECT *obj); static void M_SetupRat(OBJECT *obj); @@ -75,7 +75,7 @@ static void M_SetupBase(OBJECT *const obj) obj->save_hitpoints = 1; obj->save_anim = 1; obj->save_flags = 1; - Object_GetBone(obj, 1)->rot.y = true; + Object_GetBone(obj, 1)->rot_y = true; } static void M_SetupRat(OBJECT *const obj) diff --git a/src/tr1/game/objects/creatures/skate_kid.c b/src/tr1/game/objects/creatures/skate_kid.c index bef2fff60..8c8d098ba 100644 --- a/src/tr1/game/objects/creatures/skate_kid.c +++ b/src/tr1/game/objects/creatures/skate_kid.c @@ -34,8 +34,8 @@ typedef enum { SKATE_KID_STATE_DEATH = 5, } SKATE_KID_STATE; -static BITE m_KidGun1 = { .pos = { 0, 150, 34 }, .mesh_num = 7 }; -static BITE m_KidGun2 = { .pos = { 0, 150, 37 }, .mesh_num = 4 }; +static BITE m_KidGun1 = { 0, 150, 34, 7 }; +static BITE m_KidGun2 = { 0, 150, 37, 4 }; static void M_Setup(OBJECT *obj); static void M_Initialise(int16_t item_num); @@ -61,7 +61,7 @@ static void M_Setup(OBJECT *const obj) obj->save_anim = 1; obj->save_flags = 1; - Object_GetBone(obj, 0)->rot.y = true; + Object_GetBone(obj, 0)->rot_y = true; if (!Object_Get(O_SKATEBOARD)->loaded) { LOG_WARNING( diff --git a/src/tr1/game/objects/creatures/torso.c b/src/tr1/game/objects/creatures/torso.c index 597c3a647..2a26e7663 100644 --- a/src/tr1/game/objects/creatures/torso.c +++ b/src/tr1/game/objects/creatures/torso.c @@ -70,7 +70,7 @@ static void M_Setup(OBJECT *const obj) obj->save_anim = 1; obj->save_flags = 1; - Object_GetBone(obj, 1)->rot.y = true; + Object_GetBone(obj, 1)->rot_y = true; } static void M_Control(const int16_t item_num) diff --git a/src/tr1/game/objects/creatures/trex.c b/src/tr1/game/objects/creatures/trex.c index cf1af5ab8..c253b09f1 100644 --- a/src/tr1/game/objects/creatures/trex.c +++ b/src/tr1/game/objects/creatures/trex.c @@ -63,8 +63,8 @@ static void M_Setup(OBJECT *const obj) obj->save_anim = 1; obj->save_flags = 1; - Object_GetBone(obj, 10)->rot.y = true; - Object_GetBone(obj, 11)->rot.y = true; + Object_GetBone(obj, 10)->rot_y = true; + Object_GetBone(obj, 11)->rot_y = true; } static void M_Collision( diff --git a/src/libtrx/game/objects/creatures/wolf.c b/src/tr1/game/objects/creatures/wolf.c similarity index 79% rename from src/libtrx/game/objects/creatures/wolf.c rename to src/tr1/game/objects/creatures/wolf.c index 7d1baa62b..b168d4df7 100644 --- a/src/libtrx/game/objects/creatures/wolf.c +++ b/src/tr1/game/objects/creatures/wolf.c @@ -1,62 +1,76 @@ -#include "game/objects/creatures/wolf.h" - -#include "game/const.h" #include "game/creature.h" +#include "game/items.h" #include "game/lara/common.h" +#include "game/lot.h" #include "game/random.h" #include "game/spawn.h" -#include "utils.h" +#include "global/const.h" +#include "global/vars.h" -// clang-format off -#define WOLF_SLEEP_FRAME 96 -#define WOLF_BITE_DAMAGE 100 +#include + +#define WOLF_SLEEP_FRAME 96 +#define WOLF_BITE_DAMAGE 100 #define WOLF_POUNCE_DAMAGE 50 -#define WOLF_WALK_TURN (2 * DEG_1) // = 364 -#define WOLF_RUN_TURN (5 * DEG_1) // = 910 -#define WOLF_STALK_TURN (2 * DEG_1) // = 364 -#define WOLF_ATTACK_RANGE SQUARE(WALL_L * 3 / 2) // = 2359296 -#define WOLF_STALK_RANGE SQUARE(WALL_L * 3) // = 9437184 -#define WOLF_BITE_RANGE SQUARE(345) // = 119025 -#define WOLF_WAKE_CHANCE 32 -#define WOLF_SLEEP_CHANCE 32 -#define WOLF_HOWL_CHANCE 384 -#define WOLF_TOUCH 0x774F -#if TR_VERSION == 1 -#define WOLF_HITPOINTS 6 -#else -#define WOLF_HITPOINTS 10 -#endif -#define WOLF_RADIUS (WALL_L / 3) // = 341 -#define WOLF_SMARTNESS 0x2000 -// clang-format on +#define WOLF_DIE_ANIM 20 +#define WOLF_WALK_TURN (2 * DEG_1) // = 364 +#define WOLF_RUN_TURN (5 * DEG_1) // = 910 +#define WOLF_STALK_TURN (2 * DEG_1) // = 364 +#define WOLF_ATTACK_RANGE SQUARE(WALL_L * 3 / 2) // = 2359296 +#define WOLF_STALK_RANGE SQUARE(WALL_L * 3) // = 9437184 +#define WOLF_BITE_RANGE SQUARE(345) // = 119025 +#define WOLF_WAKE_CHANCE 32 +#define WOLF_SLEEP_CHANCE 32 +#define WOLF_HOWL_CHANCE 384 +#define WOLF_TOUCH 0x774F +#define WOLF_HITPOINTS 6 +#define WOLF_RADIUS (WALL_L / 3) // = 341 +#define WOLF_SMARTNESS 0x2000 typedef enum { - // clang-format off - WOLF_STATE_EMPTY = 0, - WOLF_STATE_STOP = 1, - WOLF_STATE_WALK = 2, - WOLF_STATE_RUN = 3, - WOLF_STATE_JUMP = 4, - WOLF_STATE_STALK = 5, - WOLF_STATE_ATTACK = 6, - WOLF_STATE_HOWL = 7, - WOLF_STATE_SLEEP = 8, - WOLF_STATE_CROUCH = 9, + WOLF_STATE_EMPTY = 0, + WOLF_STATE_STOP = 1, + WOLF_STATE_WALK = 2, + WOLF_STATE_RUN = 3, + WOLF_STATE_JUMP = 4, + WOLF_STATE_STALK = 5, + WOLF_STATE_ATTACK = 6, + WOLF_STATE_HOWL = 7, + WOLF_STATE_SLEEP = 8, + WOLF_STATE_CROUCH = 9, WOLF_STATE_FAST_TURN = 10, - WOLF_STATE_DEATH = 11, - WOLF_STATE_BITE = 12, - // clang-format on + WOLF_STATE_DEATH = 11, + WOLF_STATE_BITE = 12, } WOLF_STATE; -typedef enum { - WOLF_ANIM_DEATH = 20, -} WOLF_ANIM; - -static BITE m_WolfJawBite = { .pos = { 0, -14, 174 }, .mesh_num = 6 }; +static BITE m_WolfJawBite = { 0, -14, 174, 6 }; +static void M_Setup(OBJECT *obj); static void M_Initialise(int16_t item_num); static void M_Control(int16_t item_num); +static void M_Setup(OBJECT *const obj) +{ + if (!obj->loaded) { + return; + } + obj->initialise_func = M_Initialise; + obj->control_func = M_Control; + obj->collision_func = Creature_Collision; + obj->shadow_size = UNIT_SHADOW / 2; + obj->hit_points = WOLF_HITPOINTS; + obj->pivot_length = 375; + obj->radius = WOLF_RADIUS; + obj->smartness = WOLF_SMARTNESS; + obj->intelligent = 1; + obj->save_position = 1; + obj->save_hitpoints = 1; + obj->save_anim = 1; + obj->save_flags = 1; + + Object_GetBone(obj, 2)->rot_y = true; +} + static void M_Initialise(const int16_t item_num) { Item_Get(item_num)->frame_num = WOLF_SLEEP_FRAME; @@ -65,12 +79,16 @@ static void M_Initialise(const int16_t item_num) static void M_Control(const int16_t item_num) { - if (!Creature_Activate(item_num)) { - return; + ITEM *const item = Item_Get(item_num); + + if (item->status == IS_INVISIBLE) { + if (!LOT_EnableBaddieAI(item_num, 0)) { + return; + } + item->status = IS_ACTIVE; } - ITEM *const item = Item_Get(item_num); - CREATURE *const wolf = (CREATURE *)item->data; + CREATURE *wolf = item->data; int16_t head = 0; int16_t angle = 0; int16_t tilt = 0; @@ -79,7 +97,7 @@ static void M_Control(const int16_t item_num) if (item->current_anim_state != WOLF_STATE_DEATH) { item->current_anim_state = WOLF_STATE_DEATH; Item_SwitchToAnim( - item, WOLF_ANIM_DEATH + (int16_t)(Random_GetControl() / 11000), + item, WOLF_DIE_ANIM + (int16_t)(Random_GetControl() / 11000), 0); } } else { @@ -212,26 +230,4 @@ static void M_Control(const int16_t item_num) Creature_Animate(item_num, angle, tilt); } -void Wolf_Setup(OBJECT *const obj) -{ - if (!obj->loaded) { - return; - } - obj->initialise_func = M_Initialise; - obj->control_func = M_Control; - obj->collision_func = Creature_Collision; - obj->shadow_size = UNIT_SHADOW / 2; - obj->hit_points = WOLF_HITPOINTS; - obj->pivot_length = 375; - obj->radius = WOLF_RADIUS; - obj->smartness = WOLF_SMARTNESS; - obj->intelligent = 1; - obj->save_position = 1; - obj->save_hitpoints = 1; - obj->save_anim = 1; - obj->save_flags = 1; - - Object_GetBone(obj, 2)->rot.y = true; -} - -REGISTER_OBJECT(O_WOLF, Wolf_Setup) +REGISTER_OBJECT(O_WOLF, M_Setup) diff --git a/src/libtrx/game/objects/general/bridge_common.c b/src/tr1/game/objects/general/bridge_common.c similarity index 86% rename from src/libtrx/game/objects/general/bridge_common.c rename to src/tr1/game/objects/general/bridge_common.c index 332caf1d8..6d22c4096 100644 --- a/src/libtrx/game/objects/general/bridge_common.c +++ b/src/tr1/game/objects/general/bridge_common.c @@ -1,16 +1,18 @@ #include "game/objects/general/bridge_common.h" -#include "config.h" -#include "game/rooms.h" -#include "utils.h" +#include "game/items.h" +#include "game/room.h" +#include "global/const.h" -bool Bridge_IsSameSector( - const int32_t x, const int32_t z, const ITEM *const item) +#include +#include + +bool Bridge_IsSameSector(int32_t x, int32_t z, const ITEM *item) { - const int32_t sector_x = x / WALL_L; - const int32_t sector_z = z / WALL_L; - const int32_t item_sector_x = item->pos.x / WALL_L; - const int32_t item_sector_z = item->pos.z / WALL_L; + int32_t sector_x = x / WALL_L; + int32_t sector_z = z / WALL_L; + int32_t item_sector_x = item->pos.x / WALL_L; + int32_t item_sector_z = item->pos.z / WALL_L; return sector_x == item_sector_x && sector_z == item_sector_z; } @@ -72,9 +74,9 @@ void Bridge_FixEmbeddedPosition(int16_t item_num) // and moves them up. ITEM *const item = Item_Get(item_num); - const int32_t x = item->pos.x; - const int32_t y = item->pos.y; - const int32_t z = item->pos.z; + int32_t x = item->pos.x; + int32_t y = item->pos.y; + int32_t z = item->pos.z; int16_t room_num = item->room_num; const BOUNDS_16 *const bounds = Item_GetBoundsAccurate(item); diff --git a/src/libtrx/include/libtrx/game/objects/general/bridge_common.h b/src/tr1/game/objects/general/bridge_common.h similarity index 88% rename from src/libtrx/include/libtrx/game/objects/general/bridge_common.h rename to src/tr1/game/objects/general/bridge_common.h index 880dea9ac..6952e70b7 100644 --- a/src/libtrx/include/libtrx/game/objects/general/bridge_common.h +++ b/src/tr1/game/objects/general/bridge_common.h @@ -1,6 +1,6 @@ #pragma once -#include "../../items.h" +#include "global/types.h" bool Bridge_IsSameSector(int32_t x, int32_t z, const ITEM *item); int32_t Bridge_GetOffset(const ITEM *item, int32_t x, int32_t y, int32_t z); diff --git a/src/libtrx/game/objects/general/bridge_flat.c b/src/tr1/game/objects/general/bridge_flat.c similarity index 95% rename from src/libtrx/game/objects/general/bridge_flat.c rename to src/tr1/game/objects/general/bridge_flat.c index f80424ce5..2044e9b70 100644 --- a/src/libtrx/game/objects/general/bridge_flat.c +++ b/src/tr1/game/objects/general/bridge_flat.c @@ -1,8 +1,7 @@ -#include "config.h" -#include "game/const.h" -#include "game/objects.h" #include "game/objects/general/bridge_common.h" +#include + static int16_t M_GetFloorHeight( const ITEM *item, int32_t x, int32_t y, int32_t z, int16_t height); static int16_t M_GetCeilingHeight( diff --git a/src/libtrx/game/objects/general/bridge_tilt1.c b/src/tr1/game/objects/general/bridge_tilt1.c similarity index 96% rename from src/libtrx/game/objects/general/bridge_tilt1.c rename to src/tr1/game/objects/general/bridge_tilt1.c index 77df0826f..2d8c84d32 100644 --- a/src/libtrx/game/objects/general/bridge_tilt1.c +++ b/src/tr1/game/objects/general/bridge_tilt1.c @@ -1,8 +1,7 @@ -#include "config.h" -#include "game/const.h" -#include "game/objects.h" #include "game/objects/general/bridge_common.h" +#include + static int16_t M_GetFloorHeight( const ITEM *item, int32_t x, int32_t y, int32_t z, int16_t height); static int16_t M_GetCeilingHeight( diff --git a/src/libtrx/game/objects/general/bridge_tilt2.c b/src/tr1/game/objects/general/bridge_tilt2.c similarity index 96% rename from src/libtrx/game/objects/general/bridge_tilt2.c rename to src/tr1/game/objects/general/bridge_tilt2.c index c071e80a6..58a5b651f 100644 --- a/src/libtrx/game/objects/general/bridge_tilt2.c +++ b/src/tr1/game/objects/general/bridge_tilt2.c @@ -1,8 +1,7 @@ -#include "config.h" -#include "game/const.h" -#include "game/objects.h" #include "game/objects/general/bridge_common.h" +#include + static int16_t M_GetFloorHeight( const ITEM *item, int32_t x, int32_t y, int32_t z, int16_t height); static int16_t M_GetCeilingHeight( diff --git a/src/libtrx/game/objects/general/door.c b/src/tr1/game/objects/general/door.c similarity index 74% rename from src/libtrx/game/objects/general/door.c rename to src/tr1/game/objects/general/door.c index e81c4ca39..81e359f13 100644 --- a/src/libtrx/game/objects/general/door.c +++ b/src/tr1/game/objects/general/door.c @@ -1,16 +1,15 @@ #include "game/objects/general/door.h" -#include "game/game_buf.h" +#include "game/box.h" +#include "game/items.h" #include "game/lara/common.h" #include "game/objects/common.h" -#include "game/pathing.h" -#include "game/rooms.h" +#include "game/room.h" +#include "global/vars.h" -typedef struct { - SECTOR *sector; - SECTOR old_sector; - int16_t box_num; -} DOORPOS_DATA; +#include +#include +#include typedef struct { DOORPOS_DATA d1; @@ -24,6 +23,7 @@ static SECTOR *M_GetRoomRelSector( static void M_InitialisePortal( const ROOM *room, const ITEM *item, int32_t sector_dx, int32_t sector_dz, DOORPOS_DATA *door_pos); + static bool M_LaraDoorCollision(const SECTOR *sector); static void M_Check(DOORPOS_DATA *d); static void M_Shut(DOORPOS_DATA *d); @@ -43,17 +43,38 @@ static SECTOR *M_GetRoomRelSector( return Room_GetUnitSector(room, sector.x, sector.z); } +static void M_InitialisePortal( + const ROOM *const room, const ITEM *const item, const int32_t sector_dx, + const int32_t sector_dz, DOORPOS_DATA *const door_pos) +{ + door_pos->sector = M_GetRoomRelSector(room, item, sector_dx, sector_dz); + + const SECTOR *sector = door_pos->sector; + + const int16_t room_num = sector->portal_room.wall; + if (room_num != NO_ROOM) { + sector = + M_GetRoomRelSector(Room_Get(room_num), item, sector_dx, sector_dz); + } + + int16_t box_num = sector->box; + if (!(Box_GetBox(box_num)->overlap_index & BOX_BLOCKABLE)) { + box_num = NO_BOX; + } + door_pos->block = box_num; + door_pos->old_sector = *door_pos->sector; +} + static bool M_LaraDoorCollision(const SECTOR *const sector) { // Check if Lara is on the same tile as the invisible block. - const ITEM *const lara = Lara_GetItem(); - if (lara == nullptr) { + if (g_LaraItem == nullptr) { return false; } - int16_t room_num = lara->room_num; - const SECTOR *const lara_sector = - Room_GetSector(lara->pos.x, lara->pos.y, lara->pos.z, &room_num); + int16_t room_num = g_LaraItem->room_num; + const SECTOR *const lara_sector = Room_GetSector( + g_LaraItem->pos.x, g_LaraItem->pos.y, g_LaraItem->pos.z, &room_num); return lara_sector == sector; } @@ -70,22 +91,22 @@ static void M_Check(DOORPOS_DATA *const d) static void M_Shut(DOORPOS_DATA *const d) { + // Change the level geometry so that the door tile is impassable. SECTOR *const sector = d->sector; - if (d->sector == nullptr) { + if (sector == nullptr) { return; } - sector->idx = 0; sector->box = NO_BOX; - sector->ceiling.height = NO_HEIGHT; sector->floor.height = NO_HEIGHT; + sector->ceiling.height = NO_HEIGHT; sector->floor.tilt = 0; sector->ceiling.tilt = 0; - sector->portal_room.sky = NO_ROOM_NEG; - sector->portal_room.pit = NO_ROOM_NEG; + sector->portal_room.sky = NO_ROOM; + sector->portal_room.pit = NO_ROOM; sector->portal_room.wall = NO_ROOM; - const int16_t box_num = d->box_num; + const int16_t box_num = d->block; if (box_num != NO_BOX) { Box_GetBox(box_num)->overlap_index |= BOX_BLOCKED; } @@ -93,55 +114,34 @@ static void M_Shut(DOORPOS_DATA *const d) static void M_Open(DOORPOS_DATA *const d) { - if (d->sector == nullptr) { + // Restore the level geometry so that the door tile is passable. + SECTOR *const sector = d->sector; + if (!sector) { return; } - *d->sector = d->old_sector; + *sector = d->old_sector; - const int16_t box_num = d->box_num; + const int16_t box_num = d->block; if (box_num != NO_BOX) { Box_GetBox(box_num)->overlap_index &= ~BOX_BLOCKED; } } -static void M_InitialisePortal( - const ROOM *const room, const ITEM *const item, const int32_t sector_dx, - const int32_t sector_dz, DOORPOS_DATA *const door_pos) -{ - door_pos->sector = M_GetRoomRelSector(room, item, sector_dx, sector_dz); - - const SECTOR *sector = door_pos->sector; - - const int16_t room_num = door_pos->sector->portal_room.wall; - if (room_num != NO_ROOM) { - sector = - M_GetRoomRelSector(Room_Get(room_num), item, sector_dx, sector_dz); - } - - int16_t box_num = sector->box; - const BOX_INFO *const box = Box_GetBox(box_num); - if ((box->overlap_index & BOX_BLOCKABLE) == 0) { - box_num = NO_BOX; - } - door_pos->box_num = box_num; - door_pos->old_sector = *door_pos->sector; -} - static void M_Setup(OBJECT *const obj) { obj->initialise_func = M_Initialise; obj->control_func = M_Control; obj->draw_func = Object_DrawUnclippedItem; obj->collision_func = Door_Collision; - obj->save_flags = 1; obj->save_anim = 1; + obj->save_flags = 1; } static void M_Initialise(const int16_t item_num) { ITEM *const item = Item_Get(item_num); - DOOR_DATA *door = GameBuf_Alloc(sizeof(DOOR_DATA), GBUF_ITEM_DATA); + DOOR_DATA *const door = GameBuf_Alloc(sizeof(DOOR_DATA), GBUF_ITEM_DATA); item->data = door; int32_t dx = 0; @@ -160,7 +160,7 @@ static void M_Initialise(const int16_t item_num) const ROOM *room = Room_Get(room_num); M_InitialisePortal(room, item, dx, dz, &door->d1); - if (room->flipped_room == NO_ROOM_NEG) { + if (room->flipped_room == -1) { door->d1flip.sector = nullptr; } else { room = Room_Get(room->flipped_room); @@ -174,29 +174,30 @@ static void M_Initialise(const int16_t item_num) if (room_num == NO_ROOM) { door->d2.sector = nullptr; door->d2flip.sector = nullptr; - } else { - room = Room_Get(room_num); - M_InitialisePortal(room, item, 0, 0, &door->d2); - if (room->flipped_room == NO_ROOM_NEG) { - door->d2flip.sector = nullptr; - } else { - room = Room_Get(room->flipped_room); - M_InitialisePortal(room, item, 0, 0, &door->d2flip); - } - - M_Shut(&door->d2); - M_Shut(&door->d2flip); - - const int16_t prev_room = item->room_num; - Item_NewRoom(item_num, room_num); - item->room_num = prev_room; + return; } + + room = Room_Get(room_num); + M_InitialisePortal(room, item, 0, 0, &door->d2); + if (room->flipped_room == -1) { + door->d2flip.sector = nullptr; + } else { + room = Room_Get(room->flipped_room); + M_InitialisePortal(room, item, 0, 0, &door->d2flip); + } + + M_Shut(&door->d2); + M_Shut(&door->d2flip); + + const int16_t prev_room = item->room_num; + Item_NewRoom(item_num, room_num); + item->room_num = prev_room; } static void M_Control(const int16_t item_num) { ITEM *const item = Item_Get(item_num); - DOOR_DATA *const door = item->data; + DOOR_DATA *door = item->data; if (Item_IsTriggerActive(item)) { if (item->current_anim_state == DOOR_STATE_CLOSED) { @@ -225,25 +226,21 @@ static void M_Control(const int16_t item_num) Item_Animate(item); } -void Door_Collision( - const int16_t item_num, ITEM *const lara_item, COLL_INFO *const coll) +void Door_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll) { ITEM *const item = Item_Get(item_num); - - if (!Item_TestBoundsCollide(item, lara_item, coll->radius)) { + if (!Lara_TestBoundsCollide(item, coll->radius)) { return; } - if (!Collide_TestCollision(item, lara_item)) { return; } - if (coll->enable_baddie_push) { - Lara_Push( - item, coll, - coll->enable_hit - && item->current_anim_state != item->goal_anim_state, - true); + if (item->current_anim_state != item->goal_anim_state) { + Lara_Push(item, coll, coll->enable_hit, true); + } else { + Lara_Push(item, coll, false, true); + } } } diff --git a/src/tr1/game/objects/general/door.h b/src/tr1/game/objects/general/door.h new file mode 100644 index 000000000..c1b09abfa --- /dev/null +++ b/src/tr1/game/objects/general/door.h @@ -0,0 +1,10 @@ +#pragma once + +#include "global/types.h" + +typedef enum { + DOOR_STATE_CLOSED, + DOOR_STATE_OPEN, +} DOOR_STATE; + +void Door_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); diff --git a/src/libtrx/game/objects/general/drawbridge.c b/src/tr1/game/objects/general/drawbridge.c similarity index 97% rename from src/libtrx/game/objects/general/drawbridge.c rename to src/tr1/game/objects/general/drawbridge.c index 882213c49..94b45c92c 100644 --- a/src/libtrx/game/objects/general/drawbridge.c +++ b/src/tr1/game/objects/general/drawbridge.c @@ -1,8 +1,8 @@ -#include "config.h" #include "game/items.h" -#include "game/objects.h" #include "game/objects/general/door.h" -#include "game/rooms.h" +#include "game/room.h" + +#include typedef enum { DRAWBRIDGE_STATE_CLOSED = DOOR_STATE_CLOSED, diff --git a/src/tr1/game/objects/general/pickup.c b/src/tr1/game/objects/general/pickup.c index d9a959f84..3172fd0ad 100644 --- a/src/tr1/game/objects/general/pickup.c +++ b/src/tr1/game/objects/general/pickup.c @@ -9,7 +9,6 @@ #include "game/overlay.h" #include "game/random.h" #include "game/savegame.h" -#include "game/stats.h" #include "global/const.h" #include "global/vars.h" @@ -112,7 +111,7 @@ static void M_GetItem(int16_t item_num, ITEM *item, ITEM *lara_item) Item_RemoveDrawn(item_num); Item_RemoveActive(item_num); - Stats_AddPickup(); + Savegame_GetCurrentInfo(Game_GetCurrentLevel())->stats.pickup_count++; g_Lara.interact_target.is_moving = false; } @@ -407,11 +406,11 @@ REGISTER_OBJECT(O_SHOTGUN_ITEM, M_Setup) REGISTER_OBJECT(O_MAGNUM_ITEM, M_Setup) REGISTER_OBJECT(O_UZI_ITEM, M_Setup) REGISTER_OBJECT(O_PISTOL_AMMO_ITEM, M_Setup) -REGISTER_OBJECT(O_SHOTGUN_AMMO_ITEM, M_Setup) -REGISTER_OBJECT(O_MAGNUM_AMMO_ITEM, M_Setup) +REGISTER_OBJECT(O_SG_AMMO_ITEM, M_Setup) +REGISTER_OBJECT(O_MAG_AMMO_ITEM, M_Setup) REGISTER_OBJECT(O_UZI_AMMO_ITEM, M_Setup) REGISTER_OBJECT(O_EXPLOSIVE_ITEM, M_Setup) -REGISTER_OBJECT(O_SMALL_MEDIPACK_ITEM, M_Setup) -REGISTER_OBJECT(O_LARGE_MEDIPACK_ITEM, M_Setup) +REGISTER_OBJECT(O_MEDI_ITEM, M_Setup) +REGISTER_OBJECT(O_BIGMEDI_ITEM, M_Setup) REGISTER_OBJECT(O_LEADBAR_ITEM, M_Setup) REGISTER_OBJECT(O_SCION_ITEM_2, M_Setup) diff --git a/src/tr1/game/objects/general/scion1.c b/src/tr1/game/objects/general/scion1.c index db3111c4c..24fa620e7 100644 --- a/src/tr1/game/objects/general/scion1.c +++ b/src/tr1/game/objects/general/scion1.c @@ -11,7 +11,6 @@ #include "game/objects/common.h" #include "game/overlay.h" #include "game/savegame.h" -#include "game/stats.h" #include "global/vars.h" #define EXTRA_ANIM_PEDESTAL_SCION 0 @@ -81,7 +80,8 @@ static void M_Collision( Inv_AddItem(item->object_id); item->status = IS_INVISIBLE; Item_RemoveDrawn(item_num); - Stats_AddPickup(); + Savegame_GetCurrentInfo(Game_GetCurrentLevel()) + ->stats.pickup_count++; } } else if ( g_Input.action && g_Lara.gun_status == LGS_ARMLESS diff --git a/src/tr1/game/objects/general/trapdoor.c b/src/tr1/game/objects/general/trapdoor.c new file mode 100644 index 000000000..a3d5728f0 --- /dev/null +++ b/src/tr1/game/objects/general/trapdoor.c @@ -0,0 +1,113 @@ +#include "game/items.h" +#include "global/const.h" + +#include + +typedef enum { + TRAPDOOR_STATE_CLOSED, + TRAPDOOR_STATE_OPEN, +} TRAPDOOR_STATE; + +static bool M_IsItemOnTop(const ITEM *item, int32_t x, int32_t z); +static void M_Setup(OBJECT *obj); +static void M_Control(int16_t item_num); +static int16_t M_GetFloorHeight( + const ITEM *item, int32_t x, int32_t y, int32_t z, int16_t height); +static int16_t M_GetCeilingHeight( + const ITEM *item, int32_t x, int32_t y, int32_t z, int16_t height); + +static bool M_IsItemOnTop(const ITEM *item, int32_t x, int32_t z) +{ + const BOUNDS_16 *const orig_bounds = &Item_GetBestFrame(item)->bounds; + if (orig_bounds == nullptr) { + return false; + } + + BOUNDS_16 fixed_bounds = {}; + + // Bounds need to change in order to account for 2 sector trapdoors + // and the trapdoor angle. + if (item->rot.y == 0) { + fixed_bounds.min.x = orig_bounds->min.x; + fixed_bounds.max.x = orig_bounds->max.x; + fixed_bounds.min.z = orig_bounds->min.z; + fixed_bounds.max.z = orig_bounds->max.z; + } else if (item->rot.y == DEG_90) { + fixed_bounds.min.x = orig_bounds->min.z; + fixed_bounds.max.x = orig_bounds->max.z; + fixed_bounds.min.z = -orig_bounds->max.x; + fixed_bounds.max.z = -orig_bounds->min.x; + } else if (item->rot.y == -DEG_180) { + fixed_bounds.min.x = -orig_bounds->max.x; + fixed_bounds.max.x = -orig_bounds->min.x; + fixed_bounds.min.z = -orig_bounds->max.z; + fixed_bounds.max.z = -orig_bounds->min.z; + } else if (item->rot.y == -DEG_90) { + fixed_bounds.min.x = -orig_bounds->max.z; + fixed_bounds.max.x = -orig_bounds->min.z; + fixed_bounds.min.z = orig_bounds->min.x; + fixed_bounds.max.z = orig_bounds->max.x; + } + + if (x <= item->pos.x + fixed_bounds.max.x + && x >= item->pos.x + fixed_bounds.min.x + && z <= item->pos.z + fixed_bounds.max.z + && z >= item->pos.z + fixed_bounds.min.z) { + return true; + } + + return false; +} + +static void M_Setup(OBJECT *const obj) +{ + obj->control_func = M_Control; + obj->floor_height_func = M_GetFloorHeight; + obj->ceiling_height_func = M_GetCeilingHeight; + obj->save_anim = 1; + obj->save_flags = 1; +} + +static void M_Control(const int16_t item_num) +{ + ITEM *const item = Item_Get(item_num); + if (Item_IsTriggerActive(item)) { + if (item->current_anim_state == TRAPDOOR_STATE_CLOSED) { + item->goal_anim_state = TRAPDOOR_STATE_OPEN; + } + } else if (item->current_anim_state == TRAPDOOR_STATE_OPEN) { + item->goal_anim_state = TRAPDOOR_STATE_CLOSED; + } + Item_Animate(item); +} + +static int16_t M_GetFloorHeight( + const ITEM *item, const int32_t x, const int32_t y, const int32_t z, + const int16_t height) +{ + if (!M_IsItemOnTop(item, x, z)) { + return height; + } + if (item->current_anim_state == TRAPDOOR_STATE_OPEN || y > item->pos.y + || item->pos.y >= height) { + return height; + } + return item->pos.y; +} + +static int16_t M_GetCeilingHeight( + const ITEM *item, const int32_t x, const int32_t y, const int32_t z, + const int16_t height) +{ + if (!M_IsItemOnTop(item, x, z)) { + return height; + } + if (item->current_anim_state == TRAPDOOR_STATE_OPEN || y <= item->pos.y + || item->pos.y <= height) { + return height; + } + return item->pos.y + STEP_L; +} + +REGISTER_OBJECT(O_TRAPDOOR_1, M_Setup) +REGISTER_OBJECT(O_TRAPDOOR_2, M_Setup) diff --git a/src/tr1/game/objects/setup.c b/src/tr1/game/objects/setup.c index e0226796a..71bf60f72 100644 --- a/src/tr1/game/objects/setup.c +++ b/src/tr1/game/objects/setup.c @@ -27,17 +27,6 @@ static void M_SetupLaraExtra(void) obj->control_func = Lara_ControlExtra; } -static void M_SetupSkybox(void) -{ - const OBJECT *const obj = Object_Get(O_SKYBOX); - if (obj->loaded) { - for (int32_t i = 0; i < obj->mesh_count; i++) { - OBJECT_MESH *const obj_mesh = Object_GetMesh(obj->mesh_idx + i); - obj_mesh->disable_lighting = true; - } - } -} - static void M_DisableObject(const GAME_OBJECT_ID obj_id) { OBJECT *const obj = Object_Get(obj_id); @@ -78,18 +67,17 @@ void Object_SetupAllObjects(void) M_SetupLara(); M_SetupLaraExtra(); - M_SetupSkybox(); Lara_Hair_Initialise(); if (g_Config.gameplay.disable_medpacks) { - M_DisableObject(O_SMALL_MEDIPACK_ITEM); - M_DisableObject(O_LARGE_MEDIPACK_ITEM); + M_DisableObject(O_MEDI_ITEM); + M_DisableObject(O_BIGMEDI_ITEM); } if (g_Config.gameplay.disable_magnums) { M_DisableObject(O_MAGNUM_ITEM); - M_DisableObject(O_MAGNUM_AMMO_ITEM); + M_DisableObject(O_MAG_AMMO_ITEM); } if (g_Config.gameplay.disable_uzis) { @@ -99,6 +87,6 @@ void Object_SetupAllObjects(void) if (g_Config.gameplay.disable_shotgun) { M_DisableObject(O_SHOTGUN_ITEM); - M_DisableObject(O_SHOTGUN_AMMO_ITEM); + M_DisableObject(O_SG_AMMO_ITEM); } } diff --git a/src/tr1/game/objects/traps/lightning_emitter.c b/src/tr1/game/objects/traps/lightning_emitter.c index 345299355..81aed7588 100644 --- a/src/tr1/game/objects/traps/lightning_emitter.c +++ b/src/tr1/game/objects/traps/lightning_emitter.c @@ -231,12 +231,11 @@ static void M_Draw(const ITEM *const item) if (i > 0) { Output_DrawLightningSegment( - (XYZ_32) { x1, y1 + l->wibble[i - 1].y, z1 }, - (XYZ_32) { x2, y2, z2 }, Viewport_GetWidth() / 6); + x1, y1 + l->wibble[i - 1].y, z1, x2, y2, z2, + Viewport_GetWidth() / 6); } else { Output_DrawLightningSegment( - (XYZ_32) { x1, y1, z1 }, (XYZ_32) { x2, y2, z2 }, - Viewport_GetWidth() / 6); + x1, y1, z1, x2, y2, z2, Viewport_GetWidth() / 6); } x1 = x2; @@ -286,12 +285,11 @@ static void M_Draw(const ITEM *const item) if (k > 0) { Output_DrawLightningSegment( - (XYZ_32) { x1, y1 + l->shoot[i][k - 1].y, z1 }, - (XYZ_32) { x2, y2, z2 }, Viewport_GetWidth() / 16); + x1, y1 + l->shoot[i][k - 1].y, z1, x2, y2, z2, + Viewport_GetWidth() / 16); } else { Output_DrawLightningSegment( - (XYZ_32) { x1, y1, z1 }, (XYZ_32) { x2, y2, z2 }, - Viewport_GetWidth() / 16); + x1, y1, z1, x2, y2, z2, Viewport_GetWidth() / 16); } x1 = x2; diff --git a/src/tr1/game/objects/traps/movable_block.c b/src/tr1/game/objects/traps/movable_block.c index de6dd5391..528abb3ca 100644 --- a/src/tr1/game/objects/traps/movable_block.c +++ b/src/tr1/game/objects/traps/movable_block.c @@ -278,7 +278,6 @@ static void M_Setup(OBJECT *const obj) obj->save_position = 1; obj->save_anim = 1; obj->save_flags = 1; - obj->base_rot.y = true; obj->bounds_func = M_Bounds; } @@ -291,7 +290,6 @@ static void M_HandleSave(ITEM *const item, const SAVEGAME_STAGE stage) item->status = IS_INACTIVE; } item->priv = item->status == IS_ACTIVE ? (void *)true : (void *)false; - MovableBlock_UpdateRotation(item, item->rot.y); } } @@ -369,16 +367,16 @@ static void M_Collision( switch (quadrant) { case DIR_NORTH: - MovableBlock_UpdateRotation(item, 0); + item->rot.y = 0; break; case DIR_EAST: - MovableBlock_UpdateRotation(item, DEG_90); + item->rot.y = DEG_90; break; case DIR_SOUTH: - MovableBlock_UpdateRotation(item, -DEG_180); + item->rot.y = -DEG_180; break; case DIR_WEST: - MovableBlock_UpdateRotation(item, -DEG_90); + item->rot.y = -DEG_90; break; default: break; diff --git a/src/tr1/game/objects/traps/teeth_trap.c b/src/tr1/game/objects/traps/teeth_trap.c index fdc4ba087..119dcbe68 100644 --- a/src/tr1/game/objects/traps/teeth_trap.c +++ b/src/tr1/game/objects/traps/teeth_trap.c @@ -12,12 +12,12 @@ typedef enum { TEETH_TRAP_STATE_NASTY = 1, } TEETH_TRAP_STATE; -static BITE m_Teeth1A = { .pos = { -23, 0, -1718 }, .mesh_num = 0 }; -static BITE m_Teeth1B = { .pos = { 71, 0, -1718 }, .mesh_num = 1 }; -static BITE m_Teeth2A = { .pos = { -23, 10, -1718 }, .mesh_num = 0 }; -static BITE m_Teeth2B = { .pos = { 71, 10, -1718 }, .mesh_num = 1 }; -static BITE m_Teeth3A = { .pos = { -23, -10, -1718 }, .mesh_num = 0 }; -static BITE m_Teeth3B = { .pos = { 71, -10, -1718 }, .mesh_num = 1 }; +static BITE m_Teeth1A = { -23, 0, -1718, 0 }; +static BITE m_Teeth1B = { 71, 0, -1718, 1 }; +static BITE m_Teeth2A = { -23, 10, -1718, 0 }; +static BITE m_Teeth2B = { 71, 10, -1718, 1 }; +static BITE m_Teeth3A = { -23, -10, -1718, 0 }; +static BITE m_Teeth3B = { 71, -10, -1718, 1 }; static void M_BiteEffect(ITEM *item, BITE *bite); static void M_Setup(OBJECT *obj); @@ -25,7 +25,11 @@ static void M_Control(int16_t item_num); static void M_BiteEffect(ITEM *item, BITE *bite) { - XYZ_32 pos = bite->pos; + XYZ_32 pos = { + .x = bite->x, + .y = bite->y, + .z = bite->z, + }; Collide_GetJointAbsPosition(item, &pos, bite->mesh_num); Spawn_Blood(pos.x, pos.y, pos.z, item->speed, item->rot.y, item->room_num); } diff --git a/src/tr1/game/objects/vars.c b/src/tr1/game/objects/vars.c index dd2b85e40..12083270f 100644 --- a/src/tr1/game/objects/vars.c +++ b/src/tr1/game/objects/vars.c @@ -83,11 +83,11 @@ const GAME_OBJECT_ID g_PickupObjects[] = { O_MAGNUM_ITEM, O_UZI_ITEM, O_PISTOL_AMMO_ITEM, - O_SHOTGUN_AMMO_ITEM, - O_MAGNUM_AMMO_ITEM, + O_SG_AMMO_ITEM, + O_MAG_AMMO_ITEM, O_UZI_AMMO_ITEM, - O_SMALL_MEDIPACK_ITEM, - O_LARGE_MEDIPACK_ITEM, + O_MEDI_ITEM, + O_BIGMEDI_ITEM, O_EXPLOSIVE_ITEM, O_PUZZLE_ITEM_1, O_PUZZLE_ITEM_2, @@ -132,9 +132,9 @@ const GAME_OBJECT_ID g_DoorObjects[] = { const GAME_OBJECT_ID g_TrapdoorObjects[] = { // clang-format off - O_TRAPDOOR_TYPE_1, - O_TRAPDOOR_TYPE_2, - O_TRAPDOOR_TYPE_3, + O_TRAPDOOR_1, + O_TRAPDOOR_2, + O_BIGTRAPDOOR, O_DRAWBRIDGE, NO_OBJECT, // clang-format on @@ -187,12 +187,12 @@ const GAME_OBJECT_ID g_InvObjects[] = { O_SHOTGUN_OPTION, O_MAGNUM_OPTION, O_UZI_OPTION, - O_SHOTGUN_AMMO_OPTION, - O_MAGNUM_AMMO_OPTION, + O_SG_AMMO_OPTION, + O_MAG_AMMO_OPTION, O_UZI_AMMO_OPTION, O_EXPLOSIVE_OPTION, - O_SMALL_MEDIPACK_OPTION, - O_LARGE_MEDIPACK_OPTION, + O_MEDI_OPTION, + O_BIGMEDI_OPTION, O_PUZZLE_OPTION_1, O_PUZZLE_OPTION_2, O_PUZZLE_OPTION_3, @@ -216,22 +216,11 @@ const GAME_OBJECT_ID g_InvObjects[] = { // clang-format on }; -const GAME_OBJECT_ID g_WaterSpriteObjects[] = { - // clang-format off - O_WATERFALL, - O_SPLASH_1, - O_SPLASH_2, - O_BUBBLES_1, - O_BUBBLES_2, - NO_OBJECT, - // clang-format on -}; - const GAME_OBJECT_PAIR g_GunAmmoObjectMap[] = { // clang-format off { O_PISTOL_ITEM, O_PISTOL_AMMO_ITEM }, - { O_SHOTGUN_ITEM, O_SHOTGUN_AMMO_ITEM }, - { O_MAGNUM_ITEM, O_MAGNUM_AMMO_ITEM }, + { O_SHOTGUN_ITEM, O_SG_AMMO_ITEM }, + { O_MAGNUM_ITEM, O_MAG_AMMO_ITEM }, { O_UZI_ITEM, O_UZI_AMMO_ITEM }, { NO_OBJECT, NO_OBJECT }, // clang-format on @@ -244,12 +233,12 @@ const GAME_OBJECT_PAIR g_ItemToInvObjectMap[] = { { O_MAGNUM_ITEM, O_MAGNUM_OPTION }, { O_UZI_ITEM, O_UZI_OPTION }, { O_PISTOL_AMMO_ITEM, O_PISTOL_AMMO_OPTION }, - { O_SHOTGUN_AMMO_ITEM, O_SHOTGUN_AMMO_OPTION }, - { O_MAGNUM_AMMO_ITEM, O_MAGNUM_AMMO_OPTION }, + { O_SG_AMMO_ITEM, O_SG_AMMO_OPTION }, + { O_MAG_AMMO_ITEM, O_MAG_AMMO_OPTION }, { O_UZI_AMMO_ITEM, O_UZI_AMMO_OPTION }, { O_EXPLOSIVE_ITEM, O_EXPLOSIVE_OPTION }, - { O_SMALL_MEDIPACK_ITEM, O_SMALL_MEDIPACK_OPTION }, - { O_LARGE_MEDIPACK_ITEM, O_LARGE_MEDIPACK_OPTION }, + { O_MEDI_ITEM, O_MEDI_OPTION }, + { O_BIGMEDI_ITEM, O_BIGMEDI_OPTION }, { O_PUZZLE_ITEM_1, O_PUZZLE_OPTION_1 }, { O_PUZZLE_ITEM_2, O_PUZZLE_OPTION_2 }, { O_PUZZLE_ITEM_3, O_PUZZLE_OPTION_3 }, diff --git a/src/tr1/game/option/option.c b/src/tr1/game/option/option.c index ca778692f..ca2d2604e 100644 --- a/src/tr1/game/option/option.c +++ b/src/tr1/game/option/option.c @@ -104,16 +104,16 @@ void Option_Control(INVENTORY_ITEM *inv_item, const bool is_busy) case O_MAGNUM_OPTION: case O_UZI_OPTION: case O_EXPLOSIVE_OPTION: - case O_SMALL_MEDIPACK_OPTION: - case O_LARGE_MEDIPACK_OPTION: + case O_MEDI_OPTION: + case O_BIGMEDI_OPTION: if (!is_busy) { g_InputDB.menu_confirm = 1; } break; case O_PISTOL_AMMO_OPTION: - case O_SHOTGUN_AMMO_OPTION: - case O_MAGNUM_AMMO_OPTION: + case O_SG_AMMO_OPTION: + case O_MAG_AMMO_OPTION: case O_UZI_AMMO_OPTION: break; @@ -145,13 +145,9 @@ void Option_Control(INVENTORY_ITEM *inv_item, const bool is_busy) } } -void Option_Draw(INVENTORY_ITEM *const inv_item) +void Option_Draw(INVENTORY_ITEM *inv_item) { switch (inv_item->object_id) { - case O_PASSPORT_OPTION: - Option_Passport_Draw(inv_item); - break; - case O_CONTROL_OPTION: switch (m_ControlMode) { case CM_KEYBOARD: @@ -168,9 +164,6 @@ void Option_Draw(INVENTORY_ITEM *const inv_item) case O_COMPASS_OPTION: Option_Compass_Draw(); break; - case O_SOUND_OPTION: - Option_Sound_Draw(inv_item); - break; case O_PICKUP_OPTION_1: case O_PICKUP_OPTION_2: diff --git a/src/tr1/game/option/option_compass.c b/src/tr1/game/option/option_compass.c index ed1be4085..d38e3c010 100644 --- a/src/tr1/game/option/option_compass.c +++ b/src/tr1/game/option/option_compass.c @@ -5,80 +5,74 @@ #include "game/game_string.h" #include "game/input.h" #include "game/text.h" +#include "game/ui/widgets/stats_dialog.h" #include "global/vars.h" #include -#include #include #include -typedef struct { - struct { - bool is_ready; - UI_STATS_DIALOG_STATE state; - } ui; -} M_PRIV; - -static M_PRIV m_Priv = {}; - +static UI_WIDGET *m_Dialog = nullptr; static int16_t m_CompassNeedle = 0; static int16_t m_CompassSpeed = 0; -static void M_Init(M_PRIV *p); -static void M_Shutdown(M_PRIV *p); +static void M_Init(void); +static void M_Shutdown(void); -static void M_Init(M_PRIV *const p) +static void M_Init(void) { - p->ui.is_ready = true; - UI_StatsDialog_Init( - &p->ui.state, - (UI_STATS_DIALOG_ARGS) { - .mode = UI_STATS_DIALOG_MODE_LEVEL, - .style = UI_STATS_DIALOG_STYLE_BORDERED, - .level_num = Game_GetCurrentLevel()->num, - }); + m_Dialog = UI_StatsDialog_Create((UI_STATS_DIALOG_ARGS) { + .mode = UI_STATS_DIALOG_MODE_LEVEL, + .style = UI_STATS_DIALOG_STYLE_BORDERED, + .level_num = Game_GetCurrentLevel()->num, + }); } -static void M_Shutdown(M_PRIV *const p) +static void M_Shutdown(void) { - if (p->ui.is_ready) { - p->ui.is_ready = false; - UI_StatsDialog_Free(&p->ui.state); + if (m_Dialog != nullptr) { + m_Dialog->free(m_Dialog); + m_Dialog = nullptr; } } void Option_Compass_Control(INVENTORY_ITEM *const inv_item, const bool is_busy) { - M_PRIV *const p = &m_Priv; if (is_busy) { return; } - if (!p->ui.is_ready && g_Config.gameplay.enable_compass_stats) { - M_Init(p); + if (g_Config.gameplay.enable_compass_stats) { + char buf[100]; + char time_buf[100]; + + if (m_Dialog == nullptr) { + M_Init(); + } + + if (m_Dialog != nullptr) { + m_Dialog->control(m_Dialog); + } } - UI_StatsDialog_Control(&p->ui.state); if (g_InputDB.menu_confirm || g_InputDB.menu_back) { - M_Shutdown(p); - inv_item->anim_direction = 1; + M_Shutdown(); inv_item->goal_frame = inv_item->frames_total - 1; + inv_item->anim_direction = 1; } } void Option_Compass_Draw(void) { - M_PRIV *const p = &m_Priv; - if (p->ui.is_ready) { - UI_StatsDialog(&p->ui.state); + if (m_Dialog != nullptr) { + m_Dialog->draw(m_Dialog); } } void Option_Compass_Shutdown(void) { - M_PRIV *const p = &m_Priv; - M_Shutdown(p); + M_Shutdown(); } void Option_Compass_UpdateNeedle(const INVENTORY_ITEM *const inv_item) diff --git a/src/tr1/game/option/option_controls.c b/src/tr1/game/option/option_controls.c index fdc60db28..9bf459e4b 100644 --- a/src/tr1/game/option/option_controls.c +++ b/src/tr1/game/option/option_controls.c @@ -295,10 +295,10 @@ static void M_InitText(INPUT_BACKEND backend, INPUT_LAYOUT layout) Text_AddOutline(m_Text[TEXT_TITLE_BORDER], TS_BACKGROUND); sprintf( - m_ResetGS, GS(CONTROLS_RESET_DEFAULTS), + m_ResetGS, GS(CONTROL_RESET_DEFAULTS), Input_GetKeyName(backend, layout, INPUT_ROLE_RESET_BINDINGS)); sprintf( - m_UnbindGS, GS(CONTROLS_UNBIND), + m_UnbindGS, GS(CONTROL_UNBIND), Input_GetKeyName(backend, layout, INPUT_ROLE_UNBIND_KEY)); m_Text[TEXT_RESET] = @@ -340,11 +340,11 @@ static void M_UpdateText(INPUT_BACKEND backend, INPUT_LAYOUT layout) } sprintf( - m_ResetGS, GS(CONTROLS_RESET_DEFAULTS), + m_ResetGS, GS(CONTROL_RESET_DEFAULTS), Input_GetKeyName(backend, layout, INPUT_ROLE_RESET_BINDINGS)); Text_ChangeText(m_Text[TEXT_RESET], m_ResetGS); sprintf( - m_UnbindGS, GS(CONTROLS_UNBIND), + m_UnbindGS, GS(CONTROL_UNBIND), Input_GetKeyName(backend, layout, INPUT_ROLE_UNBIND_KEY)); Text_ChangeText(m_Text[TEXT_UNBIND], m_UnbindGS); @@ -507,10 +507,10 @@ static void M_ProgressBar( const TEXTSTRING *const txt, BAR_INFO *const bar, const int32_t timer) { int32_t width = Text_GetWidth(txt); - int32_t height = TEXT_HEIGHT_FIXED * 2 / 3; + int32_t height = TEXT_HEIGHT; int32_t x = txt->pos.x; - int32_t y = txt->pos.y - height; + int32_t y = txt->pos.y - TEXT_HEIGHT; if (txt->flags.centre_h) { x += (Screen_GetResWidthDownscaled(RSR_TEXT) - width) / 2; @@ -620,11 +620,6 @@ static void M_CheckUnbindKey(INPUT_BACKEND backend, INPUT_LAYOUT layout) m_Text[TEXT_UNBIND], &m_ProgressBars[M_PROGERSS_BAR_UNBIND], progress); } -bool Option_Controls_IsKeyChangeMode(void) -{ - return m_KeyMode == KM_CHANGE || m_KeyMode == KM_CHANGEKEYUP; -} - CONTROL_MODE Option_Controls_Control( INVENTORY_ITEM *inv_item, const bool is_busy, INPUT_BACKEND backend) { diff --git a/src/tr1/game/option/option_controls.h b/src/tr1/game/option/option_controls.h index b0a9725a4..0fddb7d4a 100644 --- a/src/tr1/game/option/option_controls.h +++ b/src/tr1/game/option/option_controls.h @@ -10,7 +10,6 @@ typedef enum { CM_CONTROLLER, } CONTROL_MODE; -bool Option_Controls_IsKeyChangeMode(void); CONTROL_MODE Option_Controls_Control( INVENTORY_ITEM *inv_item, bool is_busy, INPUT_BACKEND backend); void Option_Controls_Draw(INVENTORY_ITEM *inv_item, INPUT_BACKEND backend); diff --git a/src/tr1/game/option/option_controls_pick.c b/src/tr1/game/option/option_controls_pick.c index 746debb0d..6b887371b 100644 --- a/src/tr1/game/option/option_controls_pick.c +++ b/src/tr1/game/option/option_controls_pick.c @@ -29,13 +29,13 @@ static void M_InitText(void) Text_AddBackground(m_Text[TEXT_TITLE_BORDER], 180, 85, 0, 0, TS_BACKGROUND); Text_AddOutline(m_Text[TEXT_TITLE_BORDER], TS_BACKGROUND); - m_Text[TEXT_TITLE] = Text_Create(0, -30, GS(CONTROLS_CUSTOMIZE)); + m_Text[TEXT_TITLE] = Text_Create(0, -30, GS(CONTROL_CUSTOMIZE)); Text_AddBackground(m_Text[TEXT_TITLE], 176, 0, 0, 0, TS_HEADING); Text_AddOutline(m_Text[TEXT_TITLE], TS_HEADING); - m_Text[TEXT_KEYBOARD] = Text_Create(0, 0, GS(CONTROLS_BACKEND_KEYBOARD)); + m_Text[TEXT_KEYBOARD] = Text_Create(0, 0, GS(CONTROL_BACKEND_KEYBOARD)); m_Text[TEXT_CONTROLLER] = - Text_Create(0, 25, GS(CONTROLS_BACKEND_CONTROLLER)); + Text_Create(0, 25, GS(CONTROL_BACKEND_CONTROLLER)); Text_AddBackground(m_Text[g_OptionSelected], 128, 0, 0, 0, TS_REQUESTED); Text_AddOutline(m_Text[g_OptionSelected], TS_REQUESTED); diff --git a/src/tr1/game/option/option_examine.c b/src/tr1/game/option/option_examine.c index 924ab593e..cba8e27af 100644 --- a/src/tr1/game/option/option_examine.c +++ b/src/tr1/game/option/option_examine.c @@ -1,38 +1,21 @@ #include "game/option/option_examine.h" #include "game/input.h" +#include "game/ui/widgets/paginator.h" #include -#include +#include #define MAX_LINES 10 -typedef struct { - struct { - bool is_ready; - UI_EXAMINE_ITEM_STATE state; - } ui; -} M_PRIV; +static UI_WIDGET *m_PaginatorUI = nullptr; -static M_PRIV m_Priv = {}; +static void M_End(void); -static void M_Init(M_PRIV *p, GAME_OBJECT_ID obj_id); -static void M_Shutdown(M_PRIV *p); - -static void M_Init(M_PRIV *const p, const GAME_OBJECT_ID obj_id) +static void M_End(void) { - p->ui.is_ready = true; - UI_ExamineItem_Init( - &p->ui.state, Object_GetName(obj_id), Object_GetDescription(obj_id), - MAX_LINES); -} - -static void M_Shutdown(M_PRIV *const p) -{ - if (p->ui.is_ready) { - UI_ExamineItem_Free(&p->ui.state); - p->ui.is_ready = false; - } + m_PaginatorUI->free(m_PaginatorUI); + m_PaginatorUI = nullptr; } bool Option_Examine_CanExamine(const GAME_OBJECT_ID obj_id) @@ -42,37 +25,37 @@ bool Option_Examine_CanExamine(const GAME_OBJECT_ID obj_id) bool Option_Examine_IsActive(void) { - const M_PRIV *const p = &m_Priv; - return p->ui.is_ready; + return m_PaginatorUI != nullptr; } void Option_Examine_Control(const GAME_OBJECT_ID obj_id, const bool is_busy) { - M_PRIV *const p = &m_Priv; if (is_busy) { return; } - if (!p->ui.is_ready) { - M_Init(p, obj_id); + if (m_PaginatorUI == nullptr) { + m_PaginatorUI = UI_Paginator_Create( + Object_GetName(obj_id), Object_GetDescription(obj_id), MAX_LINES); } - UI_ExamineItem_Control(&p->ui.state); + + m_PaginatorUI->control(m_PaginatorUI); if (g_InputDB.menu_back || g_InputDB.menu_confirm) { - M_Shutdown(p); + M_End(); } } void Option_Examine_Draw(void) { - M_PRIV *const p = &m_Priv; - if (p->ui.is_ready) { - UI_ExamineItem(&p->ui.state); + if (m_PaginatorUI != nullptr) { + m_PaginatorUI->draw(m_PaginatorUI); } } void Option_Examine_Shutdown(void) { - M_PRIV *const p = &m_Priv; - M_Shutdown(p); + if (m_PaginatorUI != nullptr) { + M_End(); + } } diff --git a/src/tr1/game/option/option_graphics.c b/src/tr1/game/option/option_graphics.c index beac9659d..32eefce96 100644 --- a/src/tr1/game/option/option_graphics.c +++ b/src/tr1/game/option/option_graphics.c @@ -24,7 +24,6 @@ #define LEFT_ARROW_OFFSET (-20) #define RIGHT_ARROW_OFFSET_MIN 35 #define RIGHT_ARROW_OFFSET_MAX 85 -#define COLOR_SHIFT 10 typedef enum { TEXT_TITLE, @@ -39,21 +38,16 @@ typedef enum { typedef enum { OPTION_FPS, - OPTION_FOG_START, - OPTION_FOG_END, - OPTION_WATER_COLOR_R, - OPTION_WATER_COLOR_G, - OPTION_WATER_COLOR_B, OPTION_TEXTURE_FILTER, OPTION_FBO_FILTER, OPTION_VSYNC, OPTION_BRIGHTNESS, OPTION_UI_TEXT_SCALE, OPTION_UI_BAR_SCALE, - OPTION_UI_SCROLL_WRAPAROUND, OPTION_RENDER_MODE, OPTION_RESOLUTION, OPTION_TRAPEZOID_FILTER, + OPTION_PRETTY_PIXELS, OPTION_REFLECTIONS, OPTION_NUMBER_OF, OPTION_MIN = OPTION_FPS, @@ -79,15 +73,7 @@ typedef struct { } GRAPHICS_MENU; static const GRAPHICS_OPTION_ROW m_GfxOptionRows[] = { - { OPTION_FPS, GS_ID(DETAIL_FPS), GS_ID(DETAIL_INTEGER_FMT) }, - { OPTION_FOG_START, GS_ID(DETAIL_FOG_START), GS_ID(DETAIL_INTEGER_FMT) }, - { OPTION_FOG_END, GS_ID(DETAIL_FOG_END), GS_ID(DETAIL_INTEGER_FMT) }, - { OPTION_WATER_COLOR_R, GS_ID(DETAIL_WATER_COLOR_R), - GS_ID(DETAIL_INTEGER_FMT) }, - { OPTION_WATER_COLOR_G, GS_ID(DETAIL_WATER_COLOR_G), - GS_ID(DETAIL_INTEGER_FMT) }, - { OPTION_WATER_COLOR_B, GS_ID(DETAIL_WATER_COLOR_B), - GS_ID(DETAIL_INTEGER_FMT) }, + { OPTION_FPS, GS_ID(DETAIL_FPS), GS_ID(DETAIL_DECIMAL_FMT) }, { OPTION_TEXTURE_FILTER, GS_ID(DETAIL_TEXTURE_FILTER), GS_ID(MISC_OFF) }, { OPTION_FBO_FILTER, GS_ID(DETAIL_FBO_FILTER), GS_ID(MISC_OFF) }, { OPTION_VSYNC, GS_ID(DETAIL_VSYNC), GS_ID(MISC_ON) }, @@ -96,12 +82,11 @@ static const GRAPHICS_OPTION_ROW m_GfxOptionRows[] = { GS_ID(DETAIL_FLOAT_FMT) }, { OPTION_UI_BAR_SCALE, GS_ID(DETAIL_UI_BAR_SCALE), GS_ID(DETAIL_FLOAT_FMT) }, - { OPTION_UI_SCROLL_WRAPAROUND, GS_ID(DETAIL_UI_SCROLL_WRAPAROUND), - GS_ID(MISC_ON) }, { OPTION_RENDER_MODE, GS_ID(DETAIL_RENDER_MODE), GS_ID(DETAIL_STRING_FMT) }, { OPTION_RESOLUTION, GS_ID(DETAIL_RESOLUTION), GS_ID(DETAIL_RESOLUTION_FMT) }, { OPTION_TRAPEZOID_FILTER, GS_ID(DETAIL_TRAPEZOID_FILTER), GS_ID(MISC_ON) }, + { OPTION_PRETTY_PIXELS, GS_ID(DETAIL_PRETTY_PIXELS), GS_ID(MISC_ON) }, { OPTION_REFLECTIONS, GS_ID(DETAIL_REFLECTIONS), GS_ID(MISC_ON) }, // end { OPTION_NUMBER_OF, 0, 0 }, @@ -180,8 +165,6 @@ static void M_MenuUp(void) } m_GraphicsMenu.cur_option--; M_UpdateText(); - } else if (g_Config.ui.enable_wraparound) { - M_Reinitialize(m_GfxOptionRows[OPTION_NUMBER_OF - 1].option_name); } } @@ -196,8 +179,6 @@ static void M_MenuDown(void) } m_GraphicsMenu.cur_option++; M_UpdateText(); - } else if (g_Config.ui.enable_wraparound) { - M_Reinitialize(m_GfxOptionRows[0].option_name); } } @@ -210,7 +191,7 @@ static void M_InitText(void) Text_CentreH(m_Text[TEXT_TITLE_BORDER], 1); Text_CentreV(m_Text[TEXT_TITLE_BORDER], 1); - m_Text[TEXT_TITLE] = Text_Create(0, TOP_Y, GS(DETAIL_TITLE)); + m_Text[TEXT_TITLE] = Text_Create(0, TOP_Y, GS(DETAIL_SELECT_DETAIL)); Text_CentreH(m_Text[TEXT_TITLE], 1); Text_CentreV(m_Text[TEXT_TITLE], 1); Text_AddBackground(m_Text[TEXT_TITLE], ROW_WIDTH - 4, 0, 0, 0, TS_HEADING); @@ -286,26 +267,6 @@ static void M_UpdateArrows( m_HideArrowLeft = g_Config.rendering.fps == 30; m_HideArrowRight = g_Config.rendering.fps == 60; break; - case OPTION_FOG_START: - m_HideArrowLeft = g_Config.visuals.fog_start <= 1; - m_HideArrowRight = g_Config.visuals.fog_start >= 100; - break; - case OPTION_FOG_END: - m_HideArrowLeft = g_Config.visuals.fog_end <= 1; - m_HideArrowRight = g_Config.visuals.fog_end >= 100; - break; - case OPTION_WATER_COLOR_R: - m_HideArrowLeft = g_Config.visuals.water_color.r <= 0; - m_HideArrowRight = g_Config.visuals.water_color.r >= 255; - break; - case OPTION_WATER_COLOR_G: - m_HideArrowLeft = g_Config.visuals.water_color.g <= 0; - m_HideArrowRight = g_Config.visuals.water_color.g >= 255; - break; - case OPTION_WATER_COLOR_B: - m_HideArrowLeft = g_Config.visuals.water_color.b <= 0; - m_HideArrowRight = g_Config.visuals.water_color.b >= 255; - break; case OPTION_TEXTURE_FILTER: m_HideArrowLeft = g_Config.rendering.texture_filter == GFX_TF_FIRST; m_HideArrowRight = g_Config.rendering.texture_filter == GFX_TF_LAST; @@ -330,10 +291,6 @@ static void M_UpdateArrows( m_HideArrowLeft = g_Config.ui.bar_scale <= CONFIG_MIN_BAR_SCALE; m_HideArrowRight = g_Config.ui.bar_scale >= CONFIG_MAX_BAR_SCALE; break; - case OPTION_UI_SCROLL_WRAPAROUND: - m_HideArrowLeft = !g_Config.ui.enable_wraparound; - m_HideArrowRight = g_Config.ui.enable_wraparound; - break; case OPTION_RENDER_MODE: local_right_arrow_offset = RIGHT_ARROW_OFFSET_MAX; m_HideArrowLeft = false; @@ -348,6 +305,10 @@ static void M_UpdateArrows( m_HideArrowLeft = !g_Config.rendering.enable_trapezoid_filter; m_HideArrowRight = g_Config.rendering.enable_trapezoid_filter; break; + case OPTION_PRETTY_PIXELS: + m_HideArrowLeft = !g_Config.rendering.pretty_pixels; + m_HideArrowRight = g_Config.rendering.pretty_pixels; + break; case OPTION_REFLECTIONS: m_HideArrowLeft = !g_Config.visuals.enable_reflections; m_HideArrowRight = g_Config.visuals.enable_reflections; @@ -426,32 +387,7 @@ static void M_ChangeTextOption( switch (row->option_name) { case OPTION_FPS: - sprintf(buf, GS(DETAIL_INTEGER_FMT), g_Config.rendering.fps); - Text_ChangeText(value_text, buf); - break; - - case OPTION_FOG_START: - sprintf(buf, GS(DETAIL_INTEGER_FMT), g_Config.visuals.fog_start); - Text_ChangeText(value_text, buf); - break; - - case OPTION_FOG_END: - sprintf(buf, GS(DETAIL_INTEGER_FMT), g_Config.visuals.fog_end); - Text_ChangeText(value_text, buf); - break; - - case OPTION_WATER_COLOR_R: - sprintf(buf, GS(DETAIL_INTEGER_FMT), g_Config.visuals.water_color.r); - Text_ChangeText(value_text, buf); - break; - - case OPTION_WATER_COLOR_G: - sprintf(buf, GS(DETAIL_INTEGER_FMT), g_Config.visuals.water_color.g); - Text_ChangeText(value_text, buf); - break; - - case OPTION_WATER_COLOR_B: - sprintf(buf, GS(DETAIL_INTEGER_FMT), g_Config.visuals.water_color.b); + sprintf(buf, GS(DETAIL_DECIMAL_FMT), g_Config.rendering.fps); Text_ChangeText(value_text, buf); break; @@ -494,11 +430,6 @@ static void M_ChangeTextOption( Text_ChangeText(value_text, buf); break; - case OPTION_UI_SCROLL_WRAPAROUND: - bool is_enabled = g_Config.ui.enable_wraparound; - Text_ChangeText(value_text, is_enabled ? GS(MISC_ON) : GS(MISC_OFF)); - break; - case OPTION_RENDER_MODE: sprintf( buf, GS(DETAIL_STRING_FMT), @@ -521,6 +452,12 @@ static void M_ChangeTextOption( break; } + case OPTION_PRETTY_PIXELS: { + bool is_enabled = g_Config.rendering.pretty_pixels; + Text_ChangeText(value_text, is_enabled ? GS(MISC_ON) : GS(MISC_OFF)); + break; + } + case OPTION_REFLECTIONS: { bool is_enabled = g_Config.visuals.enable_reflections; Text_ChangeText(value_text, is_enabled ? GS(MISC_ON) : GS(MISC_OFF)); @@ -560,7 +497,6 @@ void Option_Graphics_Control(INVENTORY_ITEM *inv_item, const bool is_busy) int32_t reset = -1; - const int32_t color_shift = g_Input.slow ? 1 : COLOR_SHIFT; if (g_InputDB.menu_right) { switch (m_GraphicsMenu.cur_option->option_name) { case OPTION_FPS: @@ -568,43 +504,6 @@ void Option_Graphics_Control(INVENTORY_ITEM *inv_item, const bool is_busy) reset = OPTION_FPS; break; - case OPTION_FOG_START: - g_Config.visuals.fog_start++; - reset = OPTION_FOG_START; - break; - - case OPTION_FOG_END: - g_Config.visuals.fog_end++; - reset = OPTION_FOG_END; - break; - - case OPTION_WATER_COLOR_R: - if (g_Config.visuals.water_color.r < 255 - color_shift) { - g_Config.visuals.water_color.r += color_shift; - } else { - g_Config.visuals.water_color.r = 255; - } - reset = OPTION_WATER_COLOR_R; - break; - - case OPTION_WATER_COLOR_G: - if (g_Config.visuals.water_color.g < 255 - color_shift) { - g_Config.visuals.water_color.g += color_shift; - } else { - g_Config.visuals.water_color.g = 255; - } - reset = OPTION_WATER_COLOR_G; - break; - - case OPTION_WATER_COLOR_B: - if (g_Config.visuals.water_color.b < 255 - color_shift) { - g_Config.visuals.water_color.b += color_shift; - } else { - g_Config.visuals.water_color.b = 255; - } - reset = OPTION_WATER_COLOR_B; - break; - case OPTION_TEXTURE_FILTER: if (g_Config.rendering.texture_filter != GFX_TF_LAST) { g_Config.rendering.texture_filter++; @@ -652,13 +551,6 @@ void Option_Graphics_Control(INVENTORY_ITEM *inv_item, const bool is_busy) reset = OPTION_UI_BAR_SCALE; break; - case OPTION_UI_SCROLL_WRAPAROUND: - if (!g_Config.ui.enable_wraparound) { - g_Config.ui.enable_wraparound = true; - reset = OPTION_UI_SCROLL_WRAPAROUND; - } - break; - case OPTION_RENDER_MODE: if (g_Config.rendering.render_mode == GFX_RM_LEGACY) { g_Config.rendering.render_mode = GFX_RM_FRAMEBUFFER; @@ -684,6 +576,13 @@ void Option_Graphics_Control(INVENTORY_ITEM *inv_item, const bool is_busy) } break; + case OPTION_PRETTY_PIXELS: + if (!g_Config.rendering.pretty_pixels) { + g_Config.rendering.pretty_pixels = true; + reset = OPTION_PRETTY_PIXELS; + } + break; + case OPTION_REFLECTIONS: if (!g_Config.visuals.enable_reflections) { g_Config.visuals.enable_reflections = true; @@ -704,44 +603,6 @@ void Option_Graphics_Control(INVENTORY_ITEM *inv_item, const bool is_busy) reset = OPTION_FPS; break; - case OPTION_FOG_START: - g_Config.visuals.fog_start--; - reset = OPTION_FOG_START; - break; - - case OPTION_FOG_END: - g_Config.visuals.fog_end--; - reset = OPTION_FOG_END; - break; - - case OPTION_WATER_COLOR_R: - if (g_Config.visuals.water_color.r >= color_shift) { - g_Config.visuals.water_color.r -= color_shift; - } else { - g_Config.visuals.water_color.r = 0; - } - reset = OPTION_WATER_COLOR_R; - break; - - case OPTION_WATER_COLOR_G: - if (g_Config.visuals.water_color.g >= color_shift) { - g_Config.visuals.water_color.g -= color_shift; - } else { - g_Config.visuals.water_color.g = 0; - } - reset = OPTION_WATER_COLOR_G; - break; - - case OPTION_WATER_COLOR_B: - if (g_Config.visuals.water_color.b >= color_shift) { - g_Config.visuals.water_color.b -= color_shift; - } else { - g_Config.visuals.water_color.b = 0; - } - CLAMP(g_Config.visuals.water_color.b, 0, 255); - reset = OPTION_WATER_COLOR_B; - break; - case OPTION_TEXTURE_FILTER: if (g_Config.rendering.texture_filter != GFX_TF_FIRST) { g_Config.rendering.texture_filter--; @@ -789,13 +650,6 @@ void Option_Graphics_Control(INVENTORY_ITEM *inv_item, const bool is_busy) reset = OPTION_UI_BAR_SCALE; break; - case OPTION_UI_SCROLL_WRAPAROUND: - if (g_Config.ui.enable_wraparound) { - g_Config.ui.enable_wraparound = false; - reset = OPTION_UI_SCROLL_WRAPAROUND; - } - break; - case OPTION_RENDER_MODE: if (g_Config.rendering.render_mode == GFX_RM_LEGACY) { g_Config.rendering.render_mode = GFX_RM_FRAMEBUFFER; @@ -821,6 +675,13 @@ void Option_Graphics_Control(INVENTORY_ITEM *inv_item, const bool is_busy) } break; + case OPTION_PRETTY_PIXELS: + if (g_Config.rendering.pretty_pixels) { + g_Config.rendering.pretty_pixels = false; + reset = OPTION_PRETTY_PIXELS; + } + break; + case OPTION_REFLECTIONS: if (g_Config.visuals.enable_reflections) { g_Config.visuals.enable_reflections = false; diff --git a/src/tr1/game/option/option_passport.c b/src/tr1/game/option/option_passport.c index baee61364..551594970 100644 --- a/src/tr1/game/option/option_passport.c +++ b/src/tr1/game/option/option_passport.c @@ -4,8 +4,8 @@ #include "game/game_flow.h" #include "game/game_string.h" #include "game/input.h" -#include "game/inventory.h" #include "game/inventory_ring.h" +#include "game/requester.h" #include "game/savegame.h" #include "game/screen.h" #include "game/sound.h" @@ -14,7 +14,6 @@ #include "global/vars.h" #include -#include #include #include @@ -27,6 +26,8 @@ typedef enum { TEXT_PAGE_NAME = 0, TEXT_LEFT_ARROW = 1, TEXT_RIGHT_ARROW = 2, + TEXT_LEVEL_ARROW_RIGHT = 3, + TEXT_LEVEL_ARROW_LEFT = 4, TEXT_NUMBER_OF = 5, } PASSPORT_TEXT; @@ -48,17 +49,6 @@ static struct { M_PAGE pages[PAGE_COUNT]; M_PAGE_NUMBER current_page; M_PAGE_NUMBER active_page; - bool is_ready; - struct { - bool is_ready; - UI_NEW_GAME_STATE state; - } new_game; - struct { - UI_SELECT_LEVEL_DIALOG_STATE *state; - } select_level; - struct { - UI_SAVE_SLOT_DIALOG_STATE *state; - } save_slot; } m_State = { .current_page = PAGE_1, .active_page = -1, @@ -73,6 +63,54 @@ static struct { static bool m_IsTextInit = false; static TEXTSTRING *m_Text[TEXT_NUMBER_OF] = {}; +static REQUEST_INFO m_NewGameRequester = { + .items_used = 0, + .max_items = MAX_GAME_MODES, + .requested = 0, + .vis_lines = MAX_GAME_MODES, + .line_offset = 0, + .line_old_offset = 0, + .pix_width = 162, + .line_height = TEXT_HEIGHT + 7, + .is_blockable = false, + .x = 0, + .y = 0, + .heading_text = nullptr, + .items = nullptr, +}; + +static REQUEST_INFO m_SelectLevelRequester = { + .items_used = 0, + .max_items = 2, + .requested = 0, + .vis_lines = -1, + .line_offset = 0, + .line_old_offset = 0, + .pix_width = 292, + .line_height = TEXT_HEIGHT + 7, + .is_blockable = false, + .x = 0, + .y = -32, + .heading_text = nullptr, + .items = nullptr, +}; + +REQUEST_INFO g_SavegameRequester = { + .items_used = 0, + .max_items = 1, + .requested = 0, + .vis_lines = -1, + .line_offset = 0, + .line_old_offset = 0, + .pix_width = 292, + .line_height = TEXT_HEIGHT + 7, + .is_blockable = false, + .x = 0, + .y = -32, + .heading_text = nullptr, + .items = nullptr, +}; + static void M_InitRequesters(void); static void M_InitText(void); static void M_RemoveAllText(void); @@ -82,7 +120,9 @@ static void M_ChangePageTextContent(const char *text); static void M_SetPage(int32_t page, PASSPORT_MODE role, bool available); static void M_DeterminePages(void); static void M_InitSaveRequester(int16_t page_num); +static void M_RestoreSaveRequester(void); static void M_InitSelectLevelRequester(void); +static void M_InitNewGameRequester(void); static void M_ShowSaves(PASSPORT_MODE pending_mode); static void M_ShowSelectLevel(void); static void M_LoadGame(void); @@ -95,16 +135,15 @@ static void M_FlipLeft(INVENTORY_ITEM *inv_item); static void M_ShowPage(INVENTORY_ITEM *inv_item); static void M_HandleFlipInputs(void); -static void M_InitRequesters(void) +void M_InitRequesters(void) { - UI_NewGame_Init(&m_State.new_game.state); -} - -static void M_FreeRequesters(void) -{ - UI_NewGame_Free(&m_State.new_game.state); - m_State.new_game.is_ready = false; - m_State.is_ready = false; + Requester_Shutdown(&m_SelectLevelRequester); + Requester_Shutdown(&m_NewGameRequester); + Requester_Shutdown(&g_SavegameRequester); + Requester_Init(&g_SavegameRequester, Savegame_GetSlotCount()); + Requester_Init( + &m_SelectLevelRequester, g_GameFlow.level_tables[GFLT_MAIN].count + 1); + Requester_Init(&m_NewGameRequester, MAX_GAME_MODES); } static void M_InitText(void) @@ -115,6 +154,12 @@ static void M_InitText(void) m_Text[TEXT_RIGHT_ARROW] = Text_Create(70, -15, "\\{button right}"); Text_Hide(m_Text[TEXT_RIGHT_ARROW], true); + m_Text[TEXT_LEVEL_ARROW_LEFT] = Text_Create(0, 0, "\\{button left}"); + Text_Hide(m_Text[TEXT_LEVEL_ARROW_LEFT], true); + + m_Text[TEXT_LEVEL_ARROW_RIGHT] = Text_Create(0, 0, "\\{button right}"); + Text_Hide(m_Text[TEXT_LEVEL_ARROW_RIGHT], true); + m_Text[TEXT_PAGE_NAME] = Text_Create(0, -16, ""); for (int i = 0; i < TEXT_NUMBER_OF; i++) { @@ -129,15 +174,9 @@ static void M_RemoveAllText(void) Text_Remove(m_Text[i]); m_Text[i] = nullptr; } - if (m_State.select_level.state != nullptr) { - UI_SelectLevelDialog_Free(m_State.select_level.state); - m_State.select_level.state = nullptr; - } - if (m_State.save_slot.state != nullptr) { - UI_SaveSlotDialog_Free(m_State.save_slot.state); - m_State.save_slot.state = nullptr; - } - M_FreeRequesters(); + Requester_Shutdown(&m_SelectLevelRequester); + Requester_Shutdown(&m_NewGameRequester); + Requester_ClearTextstrings(&g_SavegameRequester); } static void M_Close(INVENTORY_ITEM *inv_item) @@ -190,10 +229,9 @@ static void M_SetPage( static void M_DeterminePages(void) { - const bool has_saves = - Savegame_GetTotalCount() > 0 && Savegame_GetSlotCount() > 0; + const bool has_saves = g_SavedGamesCount > 0 && Savegame_GetSlotCount() > 0; - switch (g_Inv_Mode) { + switch (g_InvMode) { case INV_TITLE_MODE: m_State.mode = PASSPORT_MODE_BROWSE; M_SetPage(PAGE_1, PASSPORT_MODE_LOAD_GAME, has_saves); @@ -221,7 +259,7 @@ static void M_DeterminePages(void) M_SetPage(PAGE_2, PASSPORT_MODE_UNAVAILABLE, false); M_SetPage(PAGE_3, PASSPORT_MODE_UNAVAILABLE, false); if (m_State.mode == PASSPORT_MODE_RESTART) { - m_State.new_game.is_ready = true; + M_InitNewGameRequester(); } else { M_InitSaveRequester(PAGE_1); } @@ -277,76 +315,134 @@ static void M_DeterminePages(void) } } -static void M_InitSaveRequester(const int16_t page_num) +static void M_InitSaveRequester(int16_t page_num) { - int32_t save_slot = g_GameInfo.select_save_slot; - if (save_slot == -1) { - save_slot = Savegame_GetMostRecentlyUsedSlot(); + REQUEST_INFO *req = &g_SavegameRequester; + Requester_ClearTextstrings(req); + Requester_SetHeading( + req, + page_num == PAGE_1 ? GS(PASSPORT_LOAD_GAME) : GS(PASSPORT_SAVE_GAME)); + + if (Screen_GetResHeightDownscaled(RSR_TEXT) <= 240) { + req->vis_lines = 5; + } else if (Screen_GetResHeightDownscaled(RSR_TEXT) <= 384) { + req->vis_lines = 7; + } else if (Screen_GetResHeightDownscaled(RSR_TEXT) <= 480) { + req->vis_lines = 10; + } else { + req->vis_lines = 12; } - if (save_slot == -1) { - save_slot = Savegame_GetMostRecentlyCreatedSlot(); + req->vis_lines = MIN(req->max_items, req->vis_lines); + + // Title screen passport is at a different pitch. + if (g_InvMode == INV_TITLE_MODE) { + req->y = (-Screen_GetResHeightDownscaled(RSR_TEXT) / 2) + + (req->line_height * req->vis_lines); + } else { + req->y = (-Screen_GetResHeightDownscaled(RSR_TEXT) / 1.73) + + (req->line_height * req->vis_lines); } - const UI_SAVE_SLOT_DIALOG_TYPE dialog_type = page_num == PAGE_1 - ? UI_SAVE_SLOT_DIALOG_LOAD_GAME - : UI_SAVE_SLOT_DIALOG_SAVE_GAME; - m_State.save_slot.state = UI_SaveSlotDialog_Init(dialog_type, save_slot); + Savegame_FillAvailableSaves(req); +} + +static void M_RestoreSaveRequester(void) +{ + CLAMP(g_SavegameRequester.requested, 0, g_SavegameRequester.items_used - 1); } static void M_InitSelectLevelRequester(void) { - m_State.select_level.state = - UI_SelectLevelDialog_Init(g_GameInfo.select_save_slot); + REQUEST_INFO *req = &m_SelectLevelRequester; + req->is_blockable = true; + Requester_ClearTextstrings(req); + Requester_SetHeading(req, GS(PASSPORT_SELECT_LEVEL)); + + if (Screen_GetResHeightDownscaled(RSR_TEXT) <= 240) { + req->vis_lines = 5; + } else if (Screen_GetResHeightDownscaled(RSR_TEXT) <= 384) { + req->vis_lines = 7; + } else if (Screen_GetResHeightDownscaled(RSR_TEXT) <= 480) { + req->vis_lines = 10; + } else { + req->vis_lines = 12; + } + + // Title screen passport is at a different pitch. + if (g_InvMode == INV_TITLE_MODE) { + req->y = (-Screen_GetResHeightDownscaled(RSR_TEXT) / 2) + + (req->line_height * req->vis_lines); + } else { + req->y = (-Screen_GetResHeightDownscaled(RSR_TEXT) / 1.73) + + (req->line_height * req->vis_lines); + } + + Savegame_FillAvailableLevels(req); } -static void M_ShowSaves(const PASSPORT_MODE pending_mode) +static void M_InitNewGameRequester(void) { - const UI_SAVE_SLOT_DIALOG_CHOICE choice = - UI_SaveSlotDialog_Control(m_State.save_slot.state); - switch (choice.action) { - case UI_SAVE_SLOT_DIALOG_NO_CHOICE: + REQUEST_INFO *req = &m_NewGameRequester; + Requester_ClearTextstrings(req); + Requester_SetHeading(req, GS(PASSPORT_SELECT_MODE)); + Requester_AddItem(req, false, "%s", GS(PASSPORT_MODE_NEW_GAME)); + Requester_AddItem(req, false, "%s", GS(PASSPORT_MODE_NEW_GAME_PLUS)); + Requester_AddItem(req, false, "%s", GS(PASSPORT_MODE_NEW_GAME_JP)); + Requester_AddItem(req, false, "%s", GS(PASSPORT_MODE_NEW_GAME_JP_PLUS)); + req->vis_lines = MAX_GAME_MODES; + + req->line_offset = 0; + req->requested = 0; + + // Title screen passport is at a different pitch. + if (g_InvMode == INV_TITLE_MODE) { + req->y = (-Screen_GetResHeightDownscaled(RSR_TEXT) / 2.4) + + (req->line_height * req->vis_lines + 1); + } else { + req->y = (-Screen_GetResHeightDownscaled(RSR_TEXT) / 2) + + (req->line_height * req->vis_lines); + } +} + +static void M_ShowSaves(PASSPORT_MODE pending_mode) +{ + int32_t select = Requester_Display(&g_SavegameRequester); + if (select == 0) { g_Input = (INPUT_STATE) {}; g_InputDB = (INPUT_STATE) {}; - break; - - case UI_SAVE_SLOT_DIALOG_CANCEL: - if (g_Inv_Mode != INV_SAVE_MODE && g_Inv_Mode != INV_SAVE_CRYSTAL_MODE - && g_Inv_Mode != INV_LOAD_MODE) { - m_State.mode = PASSPORT_MODE_BROWSE; - g_Input = (INPUT_STATE) {}; - g_InputDB = (INPUT_STATE) {}; - } else { - m_State.mode = PASSPORT_MODE_BROWSE; - } - break; - - case UI_SAVE_SLOT_DIALOG_DETAILS: - g_GameInfo.select_save_slot = choice.slot_num; - M_InitSelectLevelRequester(); - m_State.mode = PASSPORT_MODE_SELECT_LEVEL; - g_Input = (INPUT_STATE) {}; - g_InputDB = (INPUT_STATE) {}; - M_ShowSelectLevel(); - break; - - case UI_SAVE_SLOT_DIALOG_CONFIRM: + } else if (select > 0) { m_State.mode = PASSPORT_MODE_BROWSE; - g_GameInfo.select_save_slot = choice.slot_num; + g_GameInfo.select_save_slot = select - 1; g_GameInfo.passport_selection = pending_mode; - break; + } else if ( + g_InvMode != INV_SAVE_MODE && g_InvMode != INV_SAVE_CRYSTAL_MODE + && g_InvMode != INV_LOAD_MODE) { + m_State.mode = PASSPORT_MODE_BROWSE; + g_Input = (INPUT_STATE) {}; + g_InputDB = (INPUT_STATE) {}; + } else { + m_State.mode = PASSPORT_MODE_BROWSE; } } static void M_ShowSelectLevel(void) { - const int32_t choice = - UI_SelectLevelDialog_Control(m_State.select_level.state); - if (choice == UI_SELECT_LEVEL_CHOICE_PLAY_STORY_SO_FAR) { - g_GameInfo.passport_selection = PASSPORT_MODE_STORY_SO_FAR; - } else if (choice != UI_SELECT_LEVEL_CHOICE_NOOP) { - g_GameInfo.select_level_num = choice + GF_GetFirstLevel()->num; - g_GameInfo.passport_selection = PASSPORT_MODE_SELECT_LEVEL; - Savegame_BindSlot(g_GameInfo.select_save_slot); + int32_t select = Requester_Display(&m_SelectLevelRequester); + if (select) { + if (select - 1 + GF_GetFirstLevel()->num + == Savegame_GetLevelNumber(g_GameInfo.select_save_slot) + 1) { + g_GameInfo.passport_selection = PASSPORT_MODE_STORY_SO_FAR; + } else if (select > 0) { + g_GameInfo.select_level_num = select - 1 + GF_GetFirstLevel()->num; + g_GameInfo.passport_selection = PASSPORT_MODE_SELECT_LEVEL; + Savegame_BindSlot(g_GameInfo.select_save_slot); + } else if ( + g_InvMode != INV_SAVE_MODE && g_InvMode != INV_SAVE_CRYSTAL_MODE + && g_InvMode != INV_LOAD_MODE) { + g_Input = (INPUT_STATE) {}; + g_InputDB = (INPUT_STATE) {}; + } + m_State.mode = PASSPORT_MODE_BROWSE; } else { g_Input = (INPUT_STATE) {}; g_InputDB = (INPUT_STATE) {}; @@ -356,6 +452,8 @@ static void M_ShowSelectLevel(void) static void M_LoadGame(void) { M_ChangePageTextContent(GS(PASSPORT_LOAD_GAME)); + g_SavegameRequester.is_blockable = true; + if (m_State.mode == PASSPORT_MODE_BROWSE) { if (g_InputDB.menu_confirm) { M_InitSaveRequester(m_State.active_page); @@ -364,7 +462,44 @@ static void M_LoadGame(void) m_State.mode = PASSPORT_MODE_LOAD_GAME; } } else if (m_State.mode == PASSPORT_MODE_LOAD_GAME) { - M_ShowSaves(PASSPORT_MODE_LOAD_GAME); + M_RestoreSaveRequester(); + if (!g_SavegameRequester.items[g_SavegameRequester.requested].is_blocked + || !g_SavegameRequester.is_blockable) { + if (g_InputDB.menu_right) { + g_GameInfo.select_save_slot = g_SavegameRequester.requested; + Text_Hide(m_Text[TEXT_LEVEL_ARROW_RIGHT], true); + Requester_ClearTextstrings(&g_SavegameRequester); + M_InitSelectLevelRequester(); + m_State.mode = PASSPORT_MODE_SELECT_LEVEL; + g_Input = (INPUT_STATE) {}; + g_InputDB = (INPUT_STATE) {}; + M_ShowSelectLevel(); + } else { + M_ShowSaves(PASSPORT_MODE_LOAD_GAME); + if (m_State.mode == PASSPORT_MODE_LOAD_GAME) { + const REQUESTER_ITEM *const row_item = + &g_SavegameRequester.items + [g_SavegameRequester.requested + - g_SavegameRequester.line_offset]; + if (row_item->content != nullptr) { + Text_SetPos( + m_Text[TEXT_LEVEL_ARROW_RIGHT], 130, + row_item->content->pos.y); + Text_Hide(m_Text[TEXT_LEVEL_ARROW_RIGHT], false); + } + } else { + Text_Hide(m_Text[TEXT_LEVEL_ARROW_RIGHT], true); + } + } + } else { + M_ShowSaves(PASSPORT_MODE_LOAD_GAME); + Text_Hide(m_Text[TEXT_LEVEL_ARROW_RIGHT], true); + } + + if (g_SavegameRequester.items[g_SavegameRequester.requested].is_blocked + && g_SavegameRequester.is_blockable) { + Text_Hide(m_Text[TEXT_LEVEL_ARROW_RIGHT], true); + } } else if (m_State.mode == PASSPORT_MODE_SELECT_LEVEL) { M_SelectLevel(); } @@ -373,6 +508,8 @@ static void M_LoadGame(void) static void M_SelectLevel(void) { if (g_InputDB.menu_left) { + Text_Hide(m_Text[TEXT_LEVEL_ARROW_LEFT], true); + Requester_ClearTextstrings(&m_SelectLevelRequester); M_InitSaveRequester(m_State.active_page); m_State.mode = PASSPORT_MODE_LOAD_GAME; g_Input = (INPUT_STATE) {}; @@ -380,12 +517,31 @@ static void M_SelectLevel(void) M_ShowSaves(PASSPORT_MODE_LOAD_GAME); } else { M_ShowSelectLevel(); + if (m_State.mode == PASSPORT_MODE_SELECT_LEVEL) { + const TEXTSTRING *const sel_item = + m_SelectLevelRequester + .items + [m_SelectLevelRequester.requested + - m_SelectLevelRequester.line_offset] + .content; + if (sel_item != nullptr) { + Text_SetPos( + m_Text[TEXT_LEVEL_ARROW_LEFT], -130, sel_item->pos.y); + Text_Hide(m_Text[TEXT_LEVEL_ARROW_LEFT], false); + } else { + Text_Hide(m_Text[TEXT_LEVEL_ARROW_LEFT], true); + } + } else { + Text_Hide(m_Text[TEXT_LEVEL_ARROW_LEFT], true); + } } } static void M_SaveGame(void) { M_ChangePageTextContent(GS(PASSPORT_SAVE_GAME)); + g_SavegameRequester.is_blockable = false; + if (m_State.mode == PASSPORT_MODE_BROWSE) { if (g_InputDB.menu_confirm) { M_InitSaveRequester(m_State.active_page); @@ -394,6 +550,7 @@ static void M_SaveGame(void) m_State.mode = PASSPORT_MODE_SAVE_GAME; } } else if (m_State.mode == PASSPORT_MODE_SAVE_GAME) { + M_RestoreSaveRequester(); M_ShowSaves(PASSPORT_MODE_SAVE_GAME); } } @@ -401,48 +558,54 @@ static void M_SaveGame(void) static void M_NewGame(void) { M_ChangePageTextContent(GS(PASSPORT_NEW_GAME)); + if (m_State.mode == PASSPORT_MODE_BROWSE) { if (g_InputDB.menu_confirm && (g_Config.gameplay.enable_game_modes || g_Config.profile.new_game_plus_unlock)) { + M_InitNewGameRequester(); g_Input = (INPUT_STATE) {}; g_InputDB = (INPUT_STATE) {}; m_State.mode = PASSPORT_MODE_NEW_GAME; - m_State.new_game.is_ready = true; } else { - Savegame_SetInitialVersion(SAVEGAME_CURRENT_VERSION); + g_GameInfo.save_initial_version = SAVEGAME_CURRENT_VERSION; + g_GameInfo.bonus_level_unlock = false; g_GameInfo.passport_selection = PASSPORT_MODE_NEW_GAME; } } else if (m_State.mode == PASSPORT_MODE_NEW_GAME) { - const int32_t choice = UI_NewGame_Control(&m_State.new_game.state); - if (choice == UI_REQUESTER_NO_CHOICE) { - g_Input = (INPUT_STATE) {}; - g_InputDB = (INPUT_STATE) {}; - } else if (choice == UI_REQUESTER_CANCEL) { - m_State.new_game.is_ready = false; - m_State.mode = PASSPORT_MODE_BROWSE; - g_Input = (INPUT_STATE) {}; - g_InputDB = (INPUT_STATE) {}; - } else { - switch (choice) { - case 0: - Game_SetBonusFlag(GBF_NONE); - break; - case 1: - Game_SetBonusFlag(GBF_NGPLUS); - break; - case 2: - Game_SetBonusFlag(GBF_JAPANESE); - break; - case 3: - Game_SetBonusFlag(GBF_JAPANESE | GBF_NGPLUS); - break; - default: - Game_SetBonusFlag(GBF_NONE); - break; + int32_t select = Requester_Display(&m_NewGameRequester); + if (select) { + if (select > 0) { + switch (select - 1) { + case 0: + g_GameInfo.bonus_flag = 0; + break; + case 1: + g_GameInfo.bonus_flag = GBF_NGPLUS; + break; + case 2: + g_GameInfo.bonus_flag = GBF_JAPANESE; + break; + case 3: + g_GameInfo.bonus_flag = GBF_JAPANESE | GBF_NGPLUS; + break; + default: + g_GameInfo.bonus_flag = 0; + break; + } + g_GameInfo.bonus_level_unlock = false; + g_GameInfo.passport_selection = PASSPORT_MODE_NEW_GAME; + g_GameInfo.save_initial_version = SAVEGAME_CURRENT_VERSION; + } else if ( + g_InvMode != INV_SAVE_MODE && g_InvMode != INV_SAVE_CRYSTAL_MODE + && g_InvMode != INV_LOAD_MODE) { + g_Input = (INPUT_STATE) {}; + g_InputDB = (INPUT_STATE) {}; } - g_GameInfo.passport_selection = PASSPORT_MODE_NEW_GAME; - Savegame_SetInitialVersion(SAVEGAME_CURRENT_VERSION); + m_State.mode = PASSPORT_MODE_BROWSE; + } else { + g_Input = (INPUT_STATE) {}; + g_InputDB = (INPUT_STATE) {}; } } } @@ -566,16 +729,14 @@ void Option_Passport_Control(INVENTORY_ITEM *inv_item, const bool is_busy) } else if (m_State.current_page > m_State.active_page) { M_FlipLeft(inv_item); } else { - m_State.is_ready = true; M_SyncArrowsVisibility(); M_ShowPage(inv_item); if (g_InputDB.menu_confirm) { M_Close(inv_item); m_State.active_page = -1; } else if (g_InputDB.menu_back) { - if (g_Inv_Mode != INV_DEATH_MODE - && (m_State.mode == PASSPORT_MODE_BROWSE - || m_State.mode == PASSPORT_MODE_RESTART)) { + if (g_InvMode != INV_DEATH_MODE + && m_State.mode == PASSPORT_MODE_BROWSE) { M_Close(inv_item); m_State.active_page = -1; } else { @@ -588,33 +749,6 @@ void Option_Passport_Control(INVENTORY_ITEM *inv_item, const bool is_busy) } } -void Option_Passport_Draw(INVENTORY_ITEM *const item) -{ - switch (m_State.mode) { - case PASSPORT_MODE_NEW_GAME: - if (m_State.new_game.is_ready) { - UI_NewGame(&m_State.new_game.state); - } - break; - - case PASSPORT_MODE_SELECT_LEVEL: - if (m_State.select_level.state != nullptr) { - UI_SelectLevelDialog(m_State.select_level.state); - } - break; - - case PASSPORT_MODE_LOAD_GAME: - case PASSPORT_MODE_SAVE_GAME: - if (m_State.is_ready && m_State.save_slot.state != nullptr) { - UI_SaveSlotDialog(m_State.save_slot.state); - } - break; - - default: - break; - } -} - void Option_Passport_Shutdown(void) { M_RemoveAllText(); diff --git a/src/tr1/game/option/option_passport.h b/src/tr1/game/option/option_passport.h index 7f94dffbd..8161fac1f 100644 --- a/src/tr1/game/option/option_passport.h +++ b/src/tr1/game/option/option_passport.h @@ -3,5 +3,4 @@ #include void Option_Passport_Control(INVENTORY_ITEM *inv_item, bool is_busy); -void Option_Passport_Draw(INVENTORY_ITEM *inv_item); void Option_Passport_Shutdown(void); diff --git a/src/tr1/game/option/option_sound.c b/src/tr1/game/option/option_sound.c index 0777a6d6f..0af6b0a3c 100644 --- a/src/tr1/game/option/option_sound.c +++ b/src/tr1/game/option/option_sound.c @@ -1,48 +1,170 @@ #include "game/option/option_sound.h" +#include "game/game_string.h" +#include "game/input.h" +#include "game/music.h" +#include "game/sound.h" +#include "game/text.h" +#include "global/vars.h" + #include -#include -typedef struct { - UI_SOUND_SETTINGS_STATE *ui; -} M_PRIV; +#include -static M_PRIV m_Priv = {}; +typedef enum { + TEXT_MUSIC_VOLUME = 0, + TEXT_SOUND_VOLUME = 1, + TEXT_TITLE = 2, + TEXT_TITLE_BORDER = 3, + TEXT_LEFT_ARROW = 4, + TEXT_RIGHT_ARROW = 5, + TEXT_NUMBER_OF = 6, + TEXT_OPTION_MIN = TEXT_MUSIC_VOLUME, + TEXT_OPTION_MAX = TEXT_SOUND_VOLUME, +} SOUND_TEXT; -static void M_Init(M_PRIV *const p) +static TEXTSTRING *m_Text[TEXT_NUMBER_OF] = {}; + +static void M_InitText(void); + +static void M_InitText(void) { - p->ui = UI_SoundSettings_Init(); -} + char buf[20]; -static void M_Shutdown(M_PRIV *const p) -{ - if (p->ui != nullptr) { - UI_SoundSettings_Free(p->ui); - p->ui = nullptr; + m_Text[TEXT_LEFT_ARROW] = Text_Create(-45, 0, "\\{button left}"); + m_Text[TEXT_RIGHT_ARROW] = Text_Create(40, 0, "\\{button right}"); + + m_Text[TEXT_TITLE_BORDER] = Text_Create(0, -32, " "); + m_Text[TEXT_TITLE] = Text_Create(0, -30, GS(SOUND_SET_VOLUMES)); + + if (g_Config.audio.music_volume > 10) { + g_Config.audio.music_volume = 10; + } + sprintf(buf, "\\{icon music} %2d", g_Config.audio.music_volume); + m_Text[TEXT_MUSIC_VOLUME] = Text_Create(0, 0, buf); + + if (g_Config.audio.sound_volume > 10) { + g_Config.audio.sound_volume = 10; + } + sprintf(buf, "\\{icon sound} %2d", g_Config.audio.sound_volume); + m_Text[TEXT_SOUND_VOLUME] = Text_Create(0, 25, buf); + + Text_AddBackground(m_Text[g_OptionSelected], 128, 0, 0, 0, TS_REQUESTED); + Text_AddOutline(m_Text[g_OptionSelected], TS_REQUESTED); + Text_AddBackground(m_Text[TEXT_TITLE], 136, 0, 0, 0, TS_HEADING); + Text_AddOutline(m_Text[TEXT_TITLE], TS_HEADING); + Text_AddBackground(m_Text[TEXT_TITLE_BORDER], 140, 85, 0, 0, TS_BACKGROUND); + Text_AddOutline(m_Text[TEXT_TITLE_BORDER], TS_BACKGROUND); + + for (int i = 0; i < TEXT_NUMBER_OF; i++) { + Text_CentreH(m_Text[i], 1); + Text_CentreV(m_Text[i], 1); } } -void Option_Sound_Control(INVENTORY_ITEM *const inv_item, const bool is_busy) +void Option_Sound_Control(INVENTORY_ITEM *inv_item, const bool is_busy) { - M_PRIV *const p = &m_Priv; if (is_busy) { return; } - if (p->ui == nullptr) { - M_Init(p); - } - UI_SoundSettings_Control(p->ui); -} -void Option_Sound_Draw(INVENTORY_ITEM *const inv_item) -{ - M_PRIV *const p = &m_Priv; - if (p->ui != nullptr) { - UI_SoundSettings(p->ui); + char buf[20]; + + if (!m_Text[TEXT_MUSIC_VOLUME]) { + M_InitText(); + } + + if (g_InputDB.menu_up && g_OptionSelected > TEXT_OPTION_MIN) { + Text_RemoveOutline(m_Text[g_OptionSelected]); + Text_RemoveBackground(m_Text[g_OptionSelected]); + --g_OptionSelected; + Text_AddBackground( + m_Text[g_OptionSelected], 128, 0, 0, 0, TS_REQUESTED); + Text_AddOutline(m_Text[g_OptionSelected], TS_REQUESTED); + Text_SetPos(m_Text[TEXT_LEFT_ARROW], -45, 0); + Text_SetPos(m_Text[TEXT_RIGHT_ARROW], 40, 0); + } + + if (g_InputDB.menu_down && g_OptionSelected < TEXT_OPTION_MAX) { + Text_RemoveOutline(m_Text[g_OptionSelected]); + Text_RemoveBackground(m_Text[g_OptionSelected]); + ++g_OptionSelected; + Text_AddBackground( + m_Text[g_OptionSelected], 128, 0, 0, 0, TS_REQUESTED); + Text_AddOutline(m_Text[g_OptionSelected], TS_REQUESTED); + Text_SetPos(m_Text[TEXT_LEFT_ARROW], -45, 25); + Text_SetPos(m_Text[TEXT_RIGHT_ARROW], 40, 25); + } + + switch (g_OptionSelected) { + case TEXT_MUSIC_VOLUME: + if (g_InputDB.menu_left + && g_Config.audio.music_volume > Music_GetMinVolume()) { + g_Config.audio.music_volume--; + Config_Write(); + Music_SetVolume(g_Config.audio.music_volume); + Sound_Effect(SFX_MENU_PASSPORT, nullptr, SPM_ALWAYS); + sprintf(buf, "\\{icon music} %2d", g_Config.audio.music_volume); + Text_ChangeText(m_Text[TEXT_MUSIC_VOLUME], buf); + } else if ( + g_InputDB.menu_right + && g_Config.audio.music_volume < Music_GetMaxVolume()) { + g_Config.audio.music_volume++; + Config_Write(); + Music_SetVolume(g_Config.audio.music_volume); + Sound_Effect(SFX_MENU_PASSPORT, nullptr, SPM_ALWAYS); + sprintf(buf, "\\{icon music} %2d", g_Config.audio.music_volume); + Text_ChangeText(m_Text[TEXT_MUSIC_VOLUME], buf); + } + + Text_Hide( + m_Text[TEXT_LEFT_ARROW], + g_Config.audio.music_volume == Music_GetMinVolume()); + Text_Hide( + m_Text[TEXT_RIGHT_ARROW], + g_Config.audio.music_volume == Music_GetMaxVolume()); + + break; + + case TEXT_SOUND_VOLUME: + if (g_InputDB.menu_left + && g_Config.audio.sound_volume > Sound_GetMinVolume()) { + g_Config.audio.sound_volume--; + Config_Write(); + Sound_SetMasterVolume(g_Config.audio.sound_volume); + Sound_Effect(SFX_MENU_PASSPORT, nullptr, SPM_ALWAYS); + sprintf(buf, "\\{icon sound} %2d", g_Config.audio.sound_volume); + Text_ChangeText(m_Text[TEXT_SOUND_VOLUME], buf); + } else if ( + g_InputDB.menu_right + && g_Config.audio.sound_volume < Sound_GetMaxVolume()) { + g_Config.audio.sound_volume++; + Config_Write(); + Sound_SetMasterVolume(g_Config.audio.sound_volume); + Sound_Effect(SFX_MENU_PASSPORT, nullptr, SPM_ALWAYS); + sprintf(buf, "\\{icon sound} %2d", g_Config.audio.sound_volume); + Text_ChangeText(m_Text[TEXT_SOUND_VOLUME], buf); + } + + Text_Hide( + m_Text[TEXT_LEFT_ARROW], + g_Config.audio.sound_volume == Sound_GetMinVolume()); + Text_Hide( + m_Text[TEXT_RIGHT_ARROW], + g_Config.audio.sound_volume == Sound_GetMaxVolume()); + + break; + } + + if (g_InputDB.menu_confirm || g_InputDB.menu_back) { + Option_Sound_Shutdown(); } } void Option_Sound_Shutdown(void) { - M_Shutdown(&m_Priv); + for (int i = 0; i < TEXT_NUMBER_OF; i++) { + Text_Remove(m_Text[i]); + m_Text[i] = nullptr; + } } diff --git a/src/tr1/game/option/option_sound.h b/src/tr1/game/option/option_sound.h index ee5f63ba7..9128617d5 100644 --- a/src/tr1/game/option/option_sound.h +++ b/src/tr1/game/option/option_sound.h @@ -3,5 +3,4 @@ #include void Option_Sound_Control(INVENTORY_ITEM *inv_item, bool is_busy); -void Option_Sound_Draw(INVENTORY_ITEM *inv_item); void Option_Sound_Shutdown(void); diff --git a/src/tr1/game/output.c b/src/tr1/game/output.c index c63c73c94..d9a13cc91 100644 --- a/src/tr1/game/output.c +++ b/src/tr1/game/output.c @@ -1,515 +1,625 @@ #include "game/output.h" -#include "game/level.h" -#include "game/output/meshes/common.h" -#include "game/output/meshes/rooms.h" -#include "game/output/sprites.h" -#include "game/output/textures.h" +#include "game/clock.h" #include "game/overlay.h" +#include "game/random.h" +#include "game/room.h" #include "game/shell.h" #include "game/viewport.h" +#include "global/const.h" +#include "global/types.h" #include "global/vars.h" +#include "specific/s_output.h" #include #include +#include #include +#include +#include +#include +#include #include +#include + +#include +#include #define MAX_LIGHTNINGS 64 -#define MAP_DEPTH(zv) (g_FltResZBuf - g_FltResZ * (1.0 / (double)(zv))) -#define TEXT_OUTLINE_THICKNESS 2 - -typedef enum { - MC_PURPLE_C, - MC_PURPLE_E, - MC_BROWN_C, - MC_BROWN_E, - MC_GREY_C, - MC_GREY_E, - MC_GREY_TL, - MC_GREY_TR, - MC_GREY_BL, - MC_GREY_BR, - MC_BLACK, - MC_GOLD_LIGHT, - MC_GOLD_DARK, - MC_NUMBER_OF, -} MENU_COLOR; +#define PHD_IONE (PHD_ONE / 4) typedef struct { - int32_t x; - int32_t y; - int32_t w; - int32_t h; -} QUAD_INFO; - -typedef struct { - XYZ_32 pos_0; - XYZ_32 pos_1; - int32_t thickness; + struct { + XYZ_32 pos; + int32_t thickness; + } edges[2]; } LIGHTNING; -static bool m_Initialized = false; +typedef struct { + int16_t poly_count; + int16_t vertex_count; + XYZ_16 vertices[32]; +} SHADOW_INFO; + +static int32_t m_LsAdder = 0; +static int32_t m_LsDivider = 0; +static bool m_IsSkyboxEnabled = false; +static bool m_IsWibbleEffect = false; +static bool m_IsWaterEffect = false; +static bool m_IsShadeEffect = false; +static int m_OverlayCurAlpha = 0; +static int m_OverlayDstAlpha = 0; +static int m_BackdropCurAlpha = 0; +static int m_BackdropDstAlpha = 0; + +static int32_t m_WibbleOffset = 0; +static int32_t m_AnimatedTexturesOffset = 0; +static CLOCK_TIMER m_WibbleTimer = { .type = CLOCK_TIMER_SIM }; +static CLOCK_TIMER m_AnimatedTexturesTimer = { .type = CLOCK_TIMER_SIM }; +static CLOCK_TIMER m_FadeTimer = { .type = CLOCK_TIMER_SIM }; +static int32_t m_WibbleTable[WIBBLE_SIZE] = {}; +static int32_t m_ShadeTable[WIBBLE_SIZE] = {}; +static int32_t m_RandTable[WIBBLE_SIZE] = {}; + +static PHD_VBUF *m_VBuf = nullptr; +static TEXTURE_UV *m_EnvMapUV = nullptr; +static int32_t m_DrawDistFade = 0; +static int32_t m_DrawDistMax = 0; +static RGB_F m_WaterColor = {}; +static XYZ_32 m_LsVectorView = {}; + static int32_t m_LightningCount = 0; static LIGHTNING m_LightningTable[MAX_LIGHTNINGS]; -static int32_t m_TextureMap[GFX_MAX_TEXTURES] = { GFX_NO_TEXTURE }; - -static GFX_2D_RENDERER *m_Renderer2D = nullptr; -static GFX_3D_RENDERER *m_Renderer3D = nullptr; -static bool m_IsTextureMode = false; -static int32_t m_SelectedTexture = -1; - -static int32_t m_SurfaceWidth = 0; -static int32_t m_SurfaceHeight = 0; -static GFX_2D_SURFACE *m_PictureSurface = nullptr; -static GFX_2D_SURFACE *m_TextureSurfaces[GFX_MAX_TEXTURES] = { nullptr }; +static char *m_BackdropImagePath = nullptr; static const char *m_ImageExtensions[] = { ".png", ".jpg", ".jpeg", ".pcx", nullptr, }; -static RGBA_8888 m_MenuColorMap[MC_NUMBER_OF] = { - { 70, 30, 107, 230 }, // MC_PURPLE_C - { 70, 30, 107, 0 }, // MC_PURPLE_E - { 91, 46, 9, 255 }, // MC_BROWN_C - { 91, 46, 9, 0 }, // MC_BROWN_E - { 197, 197, 197, 255 }, // MC_GREY_C - { 45, 45, 45, 255 }, // MC_GREY_E - { 96, 96, 96, 255 }, // MC_GREY_TL - { 32, 32, 32, 255 }, // MC_GREY_TR - { 63, 63, 63, 255 }, // MC_GREY_BL - { 0, 0, 0, 255 }, // MC_GREY_BR - { 0, 0, 0, 255 }, // MC_BLACK - { 232, 192, 112, 255 }, // MC_GOLD_LIGHT - { 140, 112, 56, 255 }, // MC_GOLD_DARK -}; +static void M_DrawSphere(const XYZ_32 pos, const int32_t radius); +static void M_DrawFlatFace3s(const FACE3 *faces, int32_t count); +static void M_DrawFlatFace4s(const FACE4 *faces, int32_t count); +static void M_DrawTexturedFace3s(const FACE3 *faces, int32_t count); +static void M_DrawTexturedFace4s(const FACE4 *faces, int32_t count); +static void M_DrawObjectFace3EnvMap(const FACE3 *faces, int32_t count); +static void M_DrawObjectFace4EnvMap(const FACE4 *faces, int32_t count); +static void M_DrawRoomSprites(const ROOM_MESH *mesh); +static uint16_t M_CalcVertex(PHD_VBUF *vbuf, const XYZ_16 pos); +static void M_CalcVertexWibble(PHD_VBUF *vbuf); +static bool M_CalcObjectVertices(const XYZ_16 *vertices, int16_t count); +static void M_CalcVerticeLight(const OBJECT_MESH *mesh); +static bool M_CalcVerticeEnvMap(const OBJECT_MESH *mesh); +static void M_CalcSkyboxLight(const OBJECT_MESH *mesh); +static void M_CalcRoomVertices(const ROOM_MESH *mesh); +static void M_CalcRoomVerticesWibble(const ROOM_MESH *mesh); +static void M_CalcWibbleTable(void); -static RGBA_8888 M_GetMenuColor(MENU_COLOR color); -static void M_SelectTexture(int32_t texture_num); -static void M_EnableDepthWrites(void); -static void M_DisableDepthWrites(void); -static void M_EnableDepthTest(void); -static void M_DisableDepthTest(void); -static void M_EnableTextureMode(void); -static void M_DisableTextureMode(void); -static void M_DownloadBackdropSurface(const IMAGE *image); -static void M_DownloadTextures(int32_t pages); -static void M_ReleaseTextures(void); -static void M_ReleaseSurfaces(void); -static void M_Flush(void); -static void M_FlipScreen(void); - -static void M_DrawTriangleFan( - const GFX_3D_VERTEX *vertices, int32_t vertex_count); -static void M_DrawTriangleStrip( - const GFX_3D_VERTEX *vertices, int32_t vertex_count); -static void M_Draw2DQuad( - int32_t x1, int32_t y1, int32_t x2, int32_t y2, RGBA_8888 tl, RGBA_8888 tr, - RGBA_8888 bl, RGBA_8888 br); -static void M_DrawLightningSegment(const LIGHTNING *const lightning); -static void M_DrawSprite( - int16_t x1, int16_t y1, int16_t x2, int32_t y2, int32_t z, - int32_t sprite_idx, int16_t shade); - -static RGBA_8888 M_GetMenuColor(MENU_COLOR color) +static void M_DrawSphere(const XYZ_32 pos, const int32_t radius) { - return m_MenuColorMap[color]; -} + bool wireframe_state = GFX_Context_GetWireframeMode(); + GFX_Context_SetWireframeMode(true); -static void M_SelectTexture(const int32_t texture_num) -{ - if (texture_num == m_SelectedTexture) { - return; + RGBA_8888 color = { .r = 255, .g = 255, .b = 255, .a = 128 }; + if (wireframe_state) { + color = (RGBA_8888) { .r = 0, .g = 0, .b = 0, .a = 128 }; } - if (m_TextureMap[texture_num] == GFX_NO_TEXTURE) { - LOG_ERROR("ERROR: Attempt to select unloaded texture"); - return; - } - GFX_3D_Renderer_SelectTexture(m_Renderer3D, m_TextureMap[texture_num]); - m_SelectedTexture = texture_num; -} -static void M_EnableDepthWrites(void) -{ - GFX_3D_Renderer_SetDepthWritesEnabled(m_Renderer3D, true); -} + S_Output_DisableTextureMode(); + S_Output_SetBlendingMode(GFX_BLEND_MODE_NORMAL); -static void M_DisableDepthWrites(void) -{ - GFX_3D_Renderer_SetDepthWritesEnabled(m_Renderer3D, false); -} + // More subdivisions means smoother spheres. + const int32_t subdivisions = 12; + PHD_VBUF vertices[(subdivisions + 1) * (subdivisions + 1)]; + int32_t index = 0; -static void M_EnableDepthTest(void) -{ - GFX_3D_Renderer_SetDepthTestEnabled(m_Renderer3D, true); -} + for (int32_t i = 0; i <= subdivisions; i++) { + const float theta = (M_PI * i) / subdivisions; // Latitude angle + const float sin_theta = sinf(theta); + const float cos_theta = cosf(theta); -static void M_DisableDepthTest(void) -{ - GFX_3D_Renderer_SetDepthTestEnabled(m_Renderer3D, false); -} + for (int32_t j = 0; j <= subdivisions; j++) { + const float phi = (2 * M_PI * j) / subdivisions; // Longitude angle + const float sin_phi = sinf(phi); + const float cos_phi = cosf(phi); -static void M_EnableTextureMode(void) -{ - if (!m_IsTextureMode) { - m_IsTextureMode = true; - GFX_3D_Renderer_SetTexturingEnabled(m_Renderer3D, m_IsTextureMode); - } -} - -static void M_DisableTextureMode(void) -{ - if (m_IsTextureMode) { - m_IsTextureMode = false; - GFX_3D_Renderer_SetTexturingEnabled(m_Renderer3D, m_IsTextureMode); - } -} - -static void M_DownloadBackdropSurface(const IMAGE *const image) -{ - GFX_2D_Surface_Free(m_PictureSurface); - m_PictureSurface = nullptr; - if (image == nullptr) { - return; - } - m_PictureSurface = GFX_2D_Surface_CreateFromImage(image); - GFX_2D_Renderer_Upload( - m_Renderer2D, &m_PictureSurface->desc, m_PictureSurface->buffer); -} - -static void M_DownloadTextures(const int32_t pages) -{ - if (pages > GFX_MAX_TEXTURES) { - Shell_ExitSystem("Attempt to download more than texture page limit"); - } - M_ReleaseTextures(); - - for (int32_t i = 0; i < pages; i++) { - if (m_TextureSurfaces[i] == nullptr) { - const GFX_2D_SURFACE_DESC surface_desc = { - .width = TEXTURE_PAGE_WIDTH, - .height = TEXTURE_PAGE_HEIGHT, + // Convert spherical coordinates to 3D points. + XYZ_16 vertex_pos = { + .x = pos.x + radius * cos_phi * sin_theta, + .y = pos.y + radius * cos_theta, + .z = pos.z + radius * sin_phi * sin_theta, }; - m_TextureSurfaces[i] = GFX_2D_Surface_Create(&surface_desc); - } - GFX_2D_SURFACE *const surface = m_TextureSurfaces[i]; - RGBA_8888 *const output_ptr = (RGBA_8888 *)surface->buffer; - const RGBA_8888 *const input_ptr = Output_GetTexturePage32(i); - memcpy( - output_ptr, input_ptr, - surface->desc.width * surface->desc.height * sizeof(RGBA_8888)); - m_TextureMap[i] = GFX_3D_Renderer_RegisterTexturePage( - m_Renderer3D, output_ptr, surface->desc.width, - surface->desc.height); - } -} -static void M_ReleaseTextures(void) -{ - if (m_Renderer3D == nullptr) { - return; - } - for (int32_t i = 0; i < GFX_MAX_TEXTURES; i++) { - if (m_TextureMap[i] != GFX_NO_TEXTURE) { - GFX_3D_Renderer_UnregisterTexturePage( - m_Renderer3D, m_TextureMap[i]); - m_TextureMap[i] = GFX_NO_TEXTURE; + M_CalcVertex(&vertices[index], vertex_pos); + vertices[index].g = HIGH_LIGHT; + index++; } } - m_SelectedTexture = -1; -} -static void M_ReleaseSurfaces(void) -{ - for (int32_t i = 0; i < GFX_MAX_TEXTURES; i++) { - if (m_TextureSurfaces[i] != nullptr) { - GFX_2D_Surface_Free(m_TextureSurfaces[i]); - m_TextureSurfaces[i] = nullptr; + for (int32_t i = 0; i < subdivisions; i++) { + for (int32_t j = 0; j < subdivisions; j++) { + const int32_t index_0 = i * (subdivisions + 1) + j; + const int32_t index_1 = (i + 1) * (subdivisions + 1) + j; + const int32_t index_2 = (i + 1) * (subdivisions + 1) + (j + 1); + const int32_t index_3 = i * (subdivisions + 1) + (j + 1); + S_Output_DrawFlatTriangle( + &vertices[index_0], &vertices[index_1], &vertices[index_2], + color); + S_Output_DrawFlatTriangle( + &vertices[index_0], &vertices[index_2], &vertices[index_3], + color); } } - if (m_PictureSurface != nullptr) { - GFX_2D_Surface_Free(m_PictureSurface); - m_PictureSurface = nullptr; + + S_Output_SetBlendingMode(GFX_BLEND_MODE_OFF); + GFX_Context_SetWireframeMode(wireframe_state); +} + +static void M_DrawFlatFace3s(const FACE3 *const faces, const int32_t count) +{ + S_Output_DisableTextureMode(); + + for (int32_t i = 0; i < count; i++) { + const FACE3 *const face = &faces[i]; + PHD_VBUF *const vns[3] = { + &m_VBuf[face->vertices[0]], + &m_VBuf[face->vertices[1]], + &m_VBuf[face->vertices[2]], + }; + + const RGBA_8888 color = + Output_RGB2RGBA(Output_GetPaletteColor8(face->palette_idx)); + S_Output_DrawFlatTriangle(vns[0], vns[1], vns[2], color); } } -static void M_Flush(void) +static void M_DrawFlatFace4s(const FACE4 *const faces, const int32_t count) { - GFX_3D_Renderer_Flush(m_Renderer3D); -} + S_Output_DisableTextureMode(); -static void M_FlipScreen(void) -{ - GFX_Context_SwapBuffers(); - m_SelectedTexture = -1; -} + for (int32_t i = 0; i < count; i++) { + const FACE4 *const face = &faces[i]; + PHD_VBUF *const vns[4] = { + &m_VBuf[face->vertices[0]], + &m_VBuf[face->vertices[1]], + &m_VBuf[face->vertices[2]], + &m_VBuf[face->vertices[3]], + }; -static void M_DrawBackdropSurface(void) -{ -} - -static void M_DrawTriangleFan( - const GFX_3D_VERTEX *const vertices, const int32_t vertex_count) -{ - GFX_3D_Renderer_RenderPrimFan(m_Renderer3D, vertices, vertex_count); -} - -static void M_DrawTriangleStrip( - const GFX_3D_VERTEX *const vertices, const int32_t vertex_count) -{ - GFX_3D_Renderer_RenderPrimStrip(m_Renderer3D, vertices, vertex_count); -} - -static void M_Draw2DQuad( - const int32_t x1, const int32_t y1, const int32_t x2, const int32_t y2, - const RGBA_8888 tl, const RGBA_8888 tr, const RGBA_8888 bl, - const RGBA_8888 br) -{ - int32_t vertex_count = 4; - GFX_3D_VERTEX vertices[vertex_count]; - -#define SET(vtx_idx, x_, y_, z_, color) \ - vertices[vtx_idx].x = x_, vertices[vtx_idx].y = y_, \ - vertices[vtx_idx].z = 1.0f, vertices[vtx_idx].r = color.r; \ - vertices[vtx_idx].g = color.g; \ - vertices[vtx_idx].b = color.b; \ - vertices[vtx_idx].a = color.a; - SET(0, x1, y1, 1.0f, tl); - SET(1, x2, y1, 1.0f, tr); - SET(2, x2, y2, 1.0f, br); - SET(3, x1, y2, 1.0f, bl); -#undef SET - - M_DisableTextureMode(); - GFX_3D_Renderer_SetBlendingMode(m_Renderer3D, GFX_BLEND_MODE_NORMAL); - M_DrawTriangleFan(vertices, vertex_count); - GFX_3D_Renderer_SetBlendingMode(m_Renderer3D, GFX_BLEND_MODE_OFF); -} - -static void M_DrawLightningSegment(const LIGHTNING *const lightning) -{ - const int32_t vertex_count = 8; - const RGB_F blue = { 0.0f, 0.0f, 255.0f }; - const RGB_F white = { 255.0f, 255.0f, 255.0f }; - GFX_3D_VERTEX vertices[vertex_count]; - - const XYZ_32 wp0 = lightning->pos_0; - const XYZ_32 wp1 = lightning->pos_1; - if (wp0.z < Output_GetNearZ() || wp0.z > Output_GetFarZ() - || wp1.z < Output_GetNearZ() || wp1.z > Output_GetFarZ()) { - return; + const RGBA_8888 color = + Output_RGB2RGBA(Output_GetPaletteColor8(face->palette_idx)); + S_Output_DrawFlatTriangle(vns[0], vns[1], vns[2], color); + S_Output_DrawFlatTriangle(vns[2], vns[3], vns[0], color); } +} - const int32_t vcx = Viewport_GetCenterX(); - const int32_t vcy = Viewport_GetCenterY(); - const float zp0 = wp0.z / g_PhdPersp; - const float zp1 = wp1.z / g_PhdPersp; - const XYZ_32 p0 = { vcx + wp0.x / zp0, vcy + wp0.y / zp0, wp0.z }; - const XYZ_32 p1 = { vcx + wp1.x / zp1, vcy + wp1.y / zp1, wp1.z }; - const int32_t t1 = (lightning->thickness << W2V_SHIFT) / zp0; - const int32_t t2 = (lightning->thickness << W2V_SHIFT) / zp1; +static void M_DrawTexturedFace3s(const FACE3 *const faces, const int32_t count) +{ + S_Output_EnableTextureMode(); -#define SET(vtx_idx, x_, y_, z_, color) \ - vertices[vtx_idx].x = x_; \ - vertices[vtx_idx].y = y_; \ - vertices[vtx_idx].z = MAP_DEPTH(z_); \ - vertices[vtx_idx].r = color.r; \ - vertices[vtx_idx].g = color.g; \ - vertices[vtx_idx].b = color.b; \ - vertices[vtx_idx].a = 128.0f; + for (int32_t i = 0; i < count; i++) { + const FACE3 *const face = &faces[i]; + PHD_VBUF *const vns[3] = { + &m_VBuf[face->vertices[0]], + &m_VBuf[face->vertices[1]], + &m_VBuf[face->vertices[2]], + }; + + OBJECT_TEXTURE *const tex = Output_GetObjectTexture(face->texture_idx); + for (int32_t j = 0; j < 3; j++) { + vns[j]->u = tex->uv[j].u; + vns[j]->v = tex->uv[j].v; + vns[j]->z = 1.0f; + vns[j]->w = 1.0f; + } + + S_Output_DrawTexturedTriangle( + vns[0], vns[1], vns[2], tex->tex_page, tex->draw_type); + } +} + +static void M_DrawTexturedFace4s(const FACE4 *const faces, const int32_t count) +{ + S_Output_EnableTextureMode(); + + for (int32_t i = 0; i < count; i++) { + const FACE4 *const face = &faces[i]; + PHD_VBUF *const vns[4] = { + &m_VBuf[face->vertices[0]], + &m_VBuf[face->vertices[1]], + &m_VBuf[face->vertices[2]], + &m_VBuf[face->vertices[3]], + }; + + OBJECT_TEXTURE *const tex = Output_GetObjectTexture(face->texture_idx); + for (int32_t j = 0; j < 4; j++) { + vns[j]->u = tex->uv[j].u; + vns[j]->v = tex->uv[j].v; + if (g_Config.rendering.enable_trapezoid_filter) { + vns[j]->z = face->texture_zw[j].z; + vns[j]->w = face->texture_zw[j].w; + } else { + vns[j]->z = 1.0f; + vns[j]->w = 1.0f; + } + } + + S_Output_DrawTexturedQuad( + vns[0], vns[1], vns[2], vns[3], tex->tex_page, tex->draw_type); + } +} + +static void M_DrawObjectFace3EnvMap( + const FACE3 *const faces, const int32_t count) +{ + for (int32_t i = 0; i < count; i++) { + const FACE3 *const face = &faces[i]; + PHD_VBUF *vns[3] = { + &m_VBuf[face->vertices[0]], + &m_VBuf[face->vertices[1]], + &m_VBuf[face->vertices[2]], + }; + + for (int32_t j = 0; j < 3; j++) { + vns[j]->u = m_EnvMapUV[face->vertices[j]].u; + vns[j]->v = m_EnvMapUV[face->vertices[j]].v; + } + + if (face->enable_reflections) { + S_Output_DrawEnvMapTriangle(vns[0], vns[1], vns[2]); + } + } +} + +static void M_DrawObjectFace4EnvMap( + const FACE4 *const faces, const int32_t count) +{ + for (int32_t i = 0; i < count; i++) { + const FACE4 *const face = &faces[i]; + PHD_VBUF *vns[4] = { + &m_VBuf[face->vertices[0]], + &m_VBuf[face->vertices[1]], + &m_VBuf[face->vertices[2]], + &m_VBuf[face->vertices[3]], + }; + + for (int32_t j = 0; j < 4; j++) { + vns[j]->u = m_EnvMapUV[face->vertices[j]].u; + vns[j]->v = m_EnvMapUV[face->vertices[j]].v; + } + + if (face->enable_reflections) { + S_Output_DrawEnvMapQuad(vns[0], vns[1], vns[2], vns[3]); + } + } +} + +static void M_DrawRoomSprites(const ROOM_MESH *const mesh) +{ + for (int i = 0; i < mesh->num_sprites; i++) { + const ROOM_SPRITE *room_sprite = &mesh->sprites[i]; + const PHD_VBUF *const vbuf = &m_VBuf[room_sprite->vertex]; + if (vbuf->clip < 0) { + continue; + } + + const int32_t zv = vbuf->zv; + const SPRITE_TEXTURE *const sprite = + Output_GetSpriteTexture(room_sprite->texture); + const int32_t zp = (zv / g_PhdPersp); + const int32_t x0 = + Viewport_GetCenterX() + (vbuf->xv + (sprite->x0 << W2V_SHIFT)) / zp; + const int32_t y0 = + Viewport_GetCenterY() + (vbuf->yv + (sprite->y0 << W2V_SHIFT)) / zp; + const int32_t x1 = + Viewport_GetCenterX() + (vbuf->xv + (sprite->x1 << W2V_SHIFT)) / zp; + const int32_t y1 = + Viewport_GetCenterY() + (vbuf->yv + (sprite->y1 << W2V_SHIFT)) / zp; + if (x1 >= g_PhdLeft && y1 >= g_PhdTop && x0 < g_PhdRight + && y0 < g_PhdBottom) { + S_Output_DrawSprite( + x0, y0, x1, y1, zv, room_sprite->texture, vbuf->g); + } + } +} + +static uint16_t M_CalcVertex(PHD_VBUF *const vbuf, const XYZ_16 pos) +{ // clang-format off - SET(0, p0.x, p0.y, p0.z, blue); - SET(1, p0.x + t1 / 2, p0.y, p0.z, white); - SET(2, p1.x + t2 / 2, p1.y, p1.z, white); - SET(3, p1.x, p1.y, p1.z, blue); - SET(4, p0.x + t1 / 2, p0.y, p0.z, white); - SET(5, p0.x + t1, p0.y, p0.z, blue); - SET(6, p1.x + t2, p1.y, p1.z, blue); - SET(7, p1.x + t2 / 2, p1.y, p1.z, white); + double xv = + g_MatrixPtr->_00 * pos.x + + g_MatrixPtr->_01 * pos.y + + g_MatrixPtr->_02 * pos.z + + g_MatrixPtr->_03; + double yv = + g_MatrixPtr->_10 * pos.x + + g_MatrixPtr->_11 * pos.y + + g_MatrixPtr->_12 * pos.z + + g_MatrixPtr->_13; + double zv = + g_MatrixPtr->_20 * pos.x + + g_MatrixPtr->_21 * pos.y + + g_MatrixPtr->_22 * pos.z + + g_MatrixPtr->_23; // clang-format on -#undef SET - M_DisableTextureMode(); - GFX_3D_Renderer_SetBlendingMode(m_Renderer3D, GFX_BLEND_MODE_NORMAL); - M_DrawTriangleFan(&vertices[0], 4); - M_DrawTriangleFan(&vertices[4], 4); - GFX_3D_Renderer_SetBlendingMode(m_Renderer3D, GFX_BLEND_MODE_OFF); + vbuf->xv = xv; + vbuf->yv = yv; + vbuf->zv = zv; + + uint16_t clip_flags; + if (zv < Output_GetNearZ()) { + clip_flags = 0x8000; + } else { + clip_flags = 0; + + double persp = g_PhdPersp / zv; + double xs = Viewport_GetCenterX() + xv * persp; + double ys = Viewport_GetCenterY() + yv * persp; + + if (xs < g_PhdLeft) { + clip_flags |= 1; + } else if (xs > g_PhdRight) { + clip_flags |= 2; + } + + if (ys < g_PhdTop) { + clip_flags |= 4; + } else if (ys > g_PhdBottom) { + clip_flags |= 8; + } + + vbuf->xs = xs; + vbuf->ys = ys; + } + vbuf->clip = clip_flags; + return clip_flags; } -static void M_DrawSprite( - const int16_t x1, const int16_t y1, const int16_t x2, const int32_t y2, - const int32_t z, const int32_t sprite_idx, const int16_t shade) +static void M_CalcVertexWibble(PHD_VBUF *const vbuf) { - const int32_t vertex_count = 4; - GFX_3D_VERTEX vertices[vertex_count]; + double xs = vbuf->xs; + double ys = vbuf->ys; + xs += m_WibbleTable[(m_WibbleOffset + (int)ys) & (WIBBLE_SIZE - 1)]; + ys += m_WibbleTable[(m_WibbleOffset + (int)xs) & (WIBBLE_SIZE - 1)]; - const float multiplier = g_Config.visuals.brightness / 16.0f; - const SPRITE_TEXTURE *const sprite = Output_GetSpriteTexture(sprite_idx); - float vshade = (SHADE_MAX - shade) * multiplier; - CLAMPG(vshade, 255.0f); + int16_t clip_flags = vbuf->clip & ~15; + if (xs < g_PhdLeft) { + clip_flags |= 1; + } else if (xs > g_PhdRight) { + clip_flags |= 2; + } - const float u0 = (sprite->offset & 0xFF) / 256.0f; - const float v0 = (sprite->offset >> 8) / 256.0f; - const float u1 = (sprite->width >> 8) / 256.0f + u0; - const float v1 = (sprite->height >> 8) / 256.0f + v0; - const float vz = MAP_DEPTH(z); - const float rhw = 1.0f / z; + if (ys < g_PhdTop) { + clip_flags |= 4; + } else if (ys > g_PhdBottom) { + clip_flags |= 8; + } -#define SET(vtx_idx, x_, y_, z_, u_, v_) \ - vertices[vtx_idx].x = x_; \ - vertices[vtx_idx].y = y_; \ - vertices[vtx_idx].z = z_; \ - vertices[vtx_idx].s = u_; \ - vertices[vtx_idx].t = v_; \ - vertices[vtx_idx].tex_coord[2] = 1.0; \ - vertices[vtx_idx].tex_coord[3] = 1.0; \ - vertices[vtx_idx].w = rhw; \ - vertices[vtx_idx].r = vshade; \ - vertices[vtx_idx].g = vshade; \ - vertices[vtx_idx].b = vshade; + vbuf->xs = xs; + vbuf->ys = ys; + vbuf->clip = clip_flags; +} - SET(0, x1, y1, vz, u0, v0); - SET(1, x2, y1, vz, u1, v0); - SET(2, x2, y2, vz, u1, v1); - SET(3, x1, y2, vz, u0, v1); -#undef SET +static bool M_CalcObjectVertices( + const XYZ_16 *const vertices, const int16_t count) +{ + uint16_t total_clip = 0xFFFF; + for (int i = 0; i < count; i++) { + total_clip &= M_CalcVertex(&m_VBuf[i], vertices[i]); + } - if (m_TextureMap[sprite->tex_page] != GFX_NO_TEXTURE) { - M_EnableTextureMode(); - M_SelectTexture(sprite->tex_page); - M_DrawTriangleFan(vertices, vertex_count); - } else { - M_DisableTextureMode(); - M_DrawTriangleFan(vertices, vertex_count); + return total_clip == 0; +} + +static void M_CalcVerticeLight(const OBJECT_MESH *const mesh) +{ + if (mesh->num_lights <= 0) { + for (int32_t i = 0; i < -mesh->num_lights; i++) { + int16_t shade = m_LsAdder + mesh->lighting.lights[i]; + CLAMP(shade, 0, 0x1FFF); + m_VBuf[i].g = shade; + } + + return; + } + + if (m_LsDivider == 0) { + int16_t shade = m_LsAdder; + CLAMP(shade, 0, 0x1FFF); + for (int32_t i = 0; i < mesh->num_lights; i++) { + m_VBuf[i].g = shade; + } + + return; + } + + // clang-format off + const int32_t xv = ( + g_MatrixPtr->_00 * m_LsVectorView.x + + g_MatrixPtr->_10 * m_LsVectorView.y + + g_MatrixPtr->_20 * m_LsVectorView.z + ) / m_LsDivider; + + const int32_t yv = ( + g_MatrixPtr->_01 * m_LsVectorView.x + + g_MatrixPtr->_11 * m_LsVectorView.y + + g_MatrixPtr->_21 * m_LsVectorView.z + ) / m_LsDivider; + + const int32_t zv = ( + g_MatrixPtr->_02 * m_LsVectorView.x + + g_MatrixPtr->_12 * m_LsVectorView.y + + g_MatrixPtr->_22 * m_LsVectorView.z + ) / m_LsDivider; + // clang-format on + + for (int32_t i = 0; i < mesh->num_lights; i++) { + const XYZ_16 *const normal = &mesh->lighting.normals[i]; + int16_t shade = m_LsAdder + + ((normal->x * xv + normal->y * yv + normal->z * zv) >> 16); + CLAMP(shade, 0, 0x1FFF); + m_VBuf[i].g = shade; + } +} + +static bool M_CalcVerticeEnvMap(const OBJECT_MESH *mesh) +{ + if (mesh->num_lights <= 0) { + return false; + } + + for (int32_t i = 0; i < mesh->num_lights; ++i) { + // make sure that reflection will be drawn after normal poly + m_VBuf[i].zv -= (double)((1 << W2V_SHIFT) / 2); + + // set lighting that depends only from fog distance + m_VBuf[i].g = 0x1000; + + const int32_t depth = g_MatrixPtr->_23 >> W2V_SHIFT; + m_VBuf[i].g += Output_CalcFogShade(depth); + + // reflection can be darker but not brighter + CLAMP(m_VBuf[i].g, 0x1000, 0x1FFF); + + // rotate normal vectors for X/Y, no translation + const XYZ_16 *const normal = &mesh->lighting.normals[i]; + + // clang-format off + int32_t x = ( + g_MatrixPtr->_00 * normal->x + + g_MatrixPtr->_01 * normal->y + + g_MatrixPtr->_02 * normal->z + ) >> W2V_SHIFT; + + int32_t y = ( + g_MatrixPtr->_10 * normal->x + + g_MatrixPtr->_11 * normal->y + + g_MatrixPtr->_12 * normal->z + ) >> W2V_SHIFT; + // clang-format on + + CLAMP(x, -PHD_ONE, PHD_IONE); + CLAMP(y, -PHD_ONE, PHD_IONE); + m_EnvMapUV[i].u = PHD_ONE / PHD_IONE * (x + PHD_IONE) / 2; + m_EnvMapUV[i].v = PHD_ONE - PHD_ONE / PHD_IONE * (y + PHD_IONE) / 2; + } + + return true; +} + +static void M_CalcSkyboxLight(const OBJECT_MESH *const mesh) +{ + for (int32_t i = 0; i < ABS(mesh->num_lights); i++) { + m_VBuf[i].g = 0xFFF; + } +} + +static void M_CalcRoomVertices(const ROOM_MESH *const mesh) +{ + for (int32_t i = 0; i < mesh->num_vertices; i++) { + PHD_VBUF *const vbuf = &m_VBuf[i]; + const ROOM_VERTEX *const vertex = &mesh->vertices[i]; + + M_CalcVertex(vbuf, vertex->pos); + + vbuf->g = vertex->light_adder; + if (vbuf->zv >= Output_GetNearZ()) { + const int32_t depth = ((int32_t)vbuf->zv) >> W2V_SHIFT; + if (depth > Output_GetDrawDistMax()) { + vbuf->g = MAX_LIGHTING; + if (!m_IsSkyboxEnabled) { + vbuf->clip |= 16; + } + } else { + vbuf->g += Output_CalcFogShade(depth); + if (!m_IsWaterEffect) { + CLAMPG(vbuf->g, MAX_LIGHTING); + } + } + } + + if (m_IsWaterEffect) { + vbuf->g += m_ShadeTable[( + ((uint8_t)m_WibbleOffset + + (uint8_t)m_RandTable[(mesh->num_vertices - i) % WIBBLE_SIZE]) + % WIBBLE_SIZE)]; + CLAMP(vbuf->g, 0, 0x1FFF); + } + } +} + +static void M_CalcRoomVerticesWibble(const ROOM_MESH *const mesh) +{ + for (int32_t i = 0; i < mesh->num_vertices; i++) { + if (mesh->vertices[i].flags & NO_VERT_MOVE) { + continue; + } + M_CalcVertexWibble(&m_VBuf[i]); + } +} + +static void M_CalcWibbleTable(void) +{ + for (int i = 0; i < WIBBLE_SIZE; i++) { + PHD_ANGLE angle = (i * DEG_360) / WIBBLE_SIZE; + m_WibbleTable[i] = Math_Sin(angle) * MAX_WIBBLE >> W2V_SHIFT; + m_ShadeTable[i] = Math_Sin(angle) * MAX_SHADE >> W2V_SHIFT; + m_RandTable[i] = (Random_GetDraw() >> 5) - 0x01FF; } } bool Output_Init(void) { - if (m_Initialized) { - return true; - } - m_Initialized = true; - - for (int32_t i = 0; i < GFX_MAX_TEXTURES; i++) { - m_TextureMap[i] = GFX_NO_TEXTURE; - m_TextureSurfaces[i] = nullptr; - } - - m_Renderer2D = GFX_2D_Renderer_Create(); - m_Renderer3D = GFX_3D_Renderer_Create(); - - Output_ApplyRenderSettings(); - GFX_3D_Renderer_SetPrimType(m_Renderer3D, GFX_3D_PRIM_TRI); - GFX_3D_Renderer_SetAlphaThreshold(m_Renderer3D, 0.0); - GFX_3D_Renderer_SetAlphaPointDiscard(m_Renderer3D, true); - - Output_Textures_Init(); - Output_Meshes_Init(); - Output_Sprites_Init(); - return true; + M_CalcWibbleTable(); + return S_Output_Init(); } void Output_Shutdown(void) { - if (!m_Initialized) { - return; - } - m_Initialized = false; - - Output_Meshes_Shutdown(); - Output_Sprites_Shutdown(); - Output_Textures_Shutdown(); - - M_ReleaseTextures(); - M_ReleaseSurfaces(); - - if (m_Renderer2D != nullptr) { - GFX_2D_Renderer_Destroy(m_Renderer2D); - m_Renderer2D = nullptr; - } - if (m_Renderer3D != nullptr) { - GFX_3D_Renderer_Destroy(m_Renderer3D); - m_Renderer3D = nullptr; - } - GFX_Context_Detach(); - Output_ClearLastBackgroundPath(); + S_Output_Shutdown(); + Memory_FreePointer(&m_BackdropImagePath); } -void Output_SetWindowSize(int32_t width, int32_t height) +void Output_ReserveVertexBuffer(const size_t size) { - GFX_Context_SetWindowSize(width, height); + m_VBuf = GameBuf_Alloc(size * sizeof(PHD_VBUF), GBUF_VERTEX_BUFFER); + m_EnvMapUV = GameBuf_Alloc(size * sizeof(TEXTURE_UV), GBUF_VERTEX_BUFFER); } -void Output_ApplyLevelSettings(void) +void Output_SetWindowSize(int width, int height) { - Output_SetWaterColor(Level_GetWaterColor()); - Output_SetFogStart(Level_GetFogStart() * WALL_L); - Output_SetFogEnd(Level_GetFogEnd() * WALL_L); + S_Output_SetWindowSize(width, height); } void Output_ApplyRenderSettings(void) { - Output_Textures_ApplyRenderSettings(); - Output_ApplyLevelSettings(); - - if (m_Renderer3D == nullptr) { - return; - } - - if (m_PictureSurface != nullptr - && (Screen_GetResWidth() != m_SurfaceWidth - || Screen_GetResHeight() != m_SurfaceHeight)) { - GFX_2D_Surface_Free(m_PictureSurface); - m_PictureSurface = nullptr; - } - - m_SurfaceWidth = Screen_GetResWidth(); - m_SurfaceHeight = Screen_GetResHeight(); - - GFX_Context_SetVSync(g_Config.rendering.enable_vsync); - GFX_Context_SetDisplayFilter(g_Config.rendering.fbo_filter); - GFX_Context_SetDisplaySize(m_SurfaceWidth, m_SurfaceHeight); - GFX_Context_SetRenderingMode(g_Config.rendering.render_mode); - GFX_Context_SetWireframeMode(g_Config.rendering.enable_wireframe); - GFX_Context_SetLineWidth(g_Config.rendering.wireframe_width); - GFX_3D_Renderer_SetAnisotropyFilter( - m_Renderer3D, g_Config.rendering.anisotropy_filter); - - const char *const last_path = Output_GetLastBackgroundPath(); - if (last_path != nullptr) { - Output_LoadBackgroundFromFile(last_path); + S_Output_ApplyRenderSettings(); + if (m_BackdropImagePath) { + Output_LoadBackgroundFromFile(m_BackdropImagePath); } } -void Output_ObserveLevelLoad(void) +void Output_DownloadTextures(void) { - M_DownloadTextures(Output_GetTexturePageCount()); - Output_Textures_ObserveLevelLoad(); - Output_Sprites_ObserveLevelLoad(); - Output_Meshes_ObserveLevelLoad(); - - Output_ApplyLevelSettings(); + S_Output_DownloadTextures(Output_GetTexturePageCount()); } -void Output_ObserveLevelUnload(void) +void Output_DrawBlack(void) { - Output_Meshes_ObserveLevelUnload(); -} - -void Output_ObserveRoomFlip(const ROOM *room) -{ - Output_Meshes_ObserveRoomFlip(room); + Output_DrawBlackRectangle(255); } void Output_FlushTranslucentObjects(void) { - Output_RememberState(); // draw transparent lightnings as last in the 3D geometry pipeline for (int32_t i = 0; i < m_LightningCount; i++) { - M_DrawLightningSegment(&m_LightningTable[i]); + const LIGHTNING *const lightning = &m_LightningTable[i]; + S_Output_DrawLightningSegment( + lightning->edges[0].pos.x, lightning->edges[0].pos.y, + lightning->edges[0].pos.z, lightning->edges[0].thickness, + lightning->edges[1].pos.x, lightning->edges[1].pos.y, + lightning->edges[1].pos.z, lightning->edges[1].thickness); } - Output_RestoreState(); } void Output_BeginScene(void) @@ -517,283 +627,515 @@ void Output_BeginScene(void) Output_ApplyFOV(); Text_DrawReset(); - Output_RememberState(); - Output_Sprites_RenderBegin(); - Output_Meshes_RenderBegin(); - - GFX_Context_Clear(); - GFX_Track_Reset(); - GFX_3D_Renderer_RenderBegin(m_Renderer3D); - GFX_3D_Renderer_SetTextureFilter( - m_Renderer3D, g_Config.rendering.texture_filter); - GFX_3D_Renderer_SetBlendingMode(m_Renderer3D, GFX_BLEND_MODE_OFF); + S_Output_RenderBegin(); m_LightningCount = 0; } void Output_EndScene(void) { - M_DisableDepthTest(); - Output_ClearDepthBuffer(); + S_Output_DisableDepthTest(); + S_Output_ClearDepthBuffer(); Overlay_DrawFPSInfo(); - M_EnableDepthTest(); - GFX_3D_Renderer_RenderEnd(m_Renderer3D); - M_FlipScreen(); + S_Output_EnableDepthTest(); + S_Output_RenderEnd(); + S_Output_FlipScreen(); Shell_ProcessEvents(); g_FPSCounter++; } void Output_ClearDepthBuffer(void) { - GFX_3D_Renderer_ClearDepth(m_Renderer3D); + S_Output_ClearDepthBuffer(); +} + +void Output_DrawObjectMesh(const OBJECT_MESH *const mesh, const int32_t clip) +{ + if (!M_CalcObjectVertices(mesh->vertices, mesh->num_vertices)) { + return; + } + + M_CalcVerticeLight(mesh); + M_DrawTexturedFace4s(mesh->tex_face4s, mesh->num_tex_face4s); + M_DrawTexturedFace3s(mesh->tex_face3s, mesh->num_tex_face3s); + M_DrawFlatFace4s(mesh->flat_face4s, mesh->num_flat_face4s); + M_DrawFlatFace3s(mesh->flat_face3s, mesh->num_flat_face3s); + + if (mesh->enable_reflections && g_Config.visuals.enable_reflections) { + if (!M_CalcVerticeEnvMap(mesh)) { + return; + } + + M_DrawObjectFace4EnvMap(mesh->tex_face4s, mesh->num_tex_face4s); + M_DrawObjectFace3EnvMap(mesh->tex_face3s, mesh->num_tex_face3s); + M_DrawObjectFace4EnvMap(mesh->flat_face4s, mesh->num_flat_face4s); + M_DrawObjectFace3EnvMap(mesh->flat_face3s, mesh->num_flat_face3s); + } + + if (g_Config.rendering.enable_debug_spheres) { + M_DrawSphere( + (XYZ_32) { + .x = mesh->center.x, + .y = mesh->center.y, + .z = mesh->center.z, + }, + mesh->radius); + } +} + +void Output_DrawObjectMesh_I(const OBJECT_MESH *const mesh, const int32_t clip) +{ + Matrix_Push(); + Matrix_Interpolate(); + Output_DrawObjectMesh(mesh, clip); + Matrix_Pop(); +} + +void Output_SetSkyboxEnabled(const bool enabled) +{ + m_IsSkyboxEnabled = enabled; +} + +bool Output_IsSkyboxEnabled(void) +{ + return m_IsSkyboxEnabled; +} + +void Output_DrawSkybox(const OBJECT_MESH *const mesh) +{ + g_PhdLeft = Viewport_GetMinX(); + g_PhdTop = Viewport_GetMinY(); + g_PhdRight = Viewport_GetMaxX(); + g_PhdBottom = Viewport_GetMaxY(); + + if (!M_CalcObjectVertices(mesh->vertices, mesh->num_vertices)) { + return; + } + + S_Output_DisableDepthTest(); + M_CalcSkyboxLight(mesh); + M_DrawTexturedFace4s(mesh->tex_face4s, mesh->num_tex_face4s); + M_DrawTexturedFace3s(mesh->tex_face3s, mesh->num_tex_face3s); + M_DrawFlatFace4s(mesh->flat_face4s, mesh->num_flat_face4s); + M_DrawFlatFace3s(mesh->flat_face3s, mesh->num_flat_face3s); + S_Output_EnableDepthTest(); +} + +void Output_DrawRoom(const ROOM_MESH *const mesh) +{ + M_CalcRoomVertices(mesh); + + if (m_IsWibbleEffect) { + S_Output_DisableDepthWrites(); + M_DrawTexturedFace4s(mesh->face4s, mesh->num_face4s); + M_DrawTexturedFace3s(mesh->face3s, mesh->num_face3s); + S_Output_EnableDepthWrites(); + M_CalcRoomVerticesWibble(mesh); + } + + M_DrawTexturedFace4s(mesh->face4s, mesh->num_face4s); + M_DrawTexturedFace3s(mesh->face3s, mesh->num_face3s); + M_DrawRoomSprites(mesh); +} + +void Output_DrawRoomPortals(const ROOM *const room) +{ + S_Output_DisableDepthTest(); + const RGBA_8888 portal_color = { 0, 0, 255, 255 }; + for (int32_t i = 0; i < room->portals->count; i++) { + const PORTAL *const portal = &room->portals->portal[i]; + const XYZ_32 vertices[4] = { + { portal->vertex[0].x, portal->vertex[0].y, portal->vertex[0].z }, + { portal->vertex[1].x, portal->vertex[1].y, portal->vertex[1].z }, + { portal->vertex[2].x, portal->vertex[2].y, portal->vertex[2].z }, + { portal->vertex[3].x, portal->vertex[3].y, portal->vertex[3].z }, + }; + Output_Draw3DFrame(vertices, portal_color); + } + S_Output_EnableDepthTest(); +} + +void Output_DrawRoomTriggers(const ROOM *const room) +{ +#define DRAW_TRI(a, b, c, color) \ + do { \ + S_Output_DrawFlatTriangle(a, b, c, color); \ + S_Output_DrawFlatTriangle(c, b, a, color); \ + } while (0) +#define DRAW_QUAD(a, b, c, d, color) \ + do { \ + DRAW_TRI(a, b, d, color); \ + DRAW_TRI(b, c, d, color); \ + } while (0) + + const RGBA_8888 color = { .r = 255, .g = 0, .b = 255, .a = 128 }; + const XZ_16 offsets[4] = { { 0, 0 }, { 0, 1 }, { 1, 1 }, { 1, 0 } }; + + m_IsWaterEffect = false; + m_IsShadeEffect = false; + S_Output_DisableTextureMode(); + S_Output_DisableDepthWrites(); + S_Output_SetBlendingMode(GFX_BLEND_MODE_NORMAL); + for (int32_t z = 0; z < room->size.z; z++) { + for (int32_t x = 0; x < room->size.x; x++) { + const SECTOR *sector = Room_GetUnitSector(room, x, z); + if (sector->trigger == nullptr) { + continue; + } + PHD_VBUF vns[4]; + for (int32_t i = 0; i < 4; i++) { + XYZ_16 vertex_pos = { + .x = (x + offsets[i].x) * WALL_L, + .z = (z + offsets[i].z) * WALL_L, + }; + XYZ_32 world_pos = { + .x = room->pos.x + x * WALL_L + offsets[i].x * (WALL_L - 1), + .z = room->pos.z + z * WALL_L + offsets[i].z * (WALL_L - 1), + .y = room->pos.y, + }; + + int16_t room_num = room - Room_Get(0); + sector = Room_GetSector( + world_pos.x, world_pos.y, world_pos.z, &room_num); + vertex_pos.y = + Room_GetHeight( + sector, world_pos.x, world_pos.y, world_pos.z) + + (m_IsWaterEffect ? -16 : -2); + + M_CalcVertex(&vns[i], vertex_pos); + vns[i].g = HIGH_LIGHT; + vns[i].zv -= + (double)((1 << W2V_SHIFT) / 2); // reduce z fighting + } + + DRAW_QUAD(&vns[0], &vns[1], &vns[2], &vns[3], color); + } + } + + S_Output_SetBlendingMode(GFX_BLEND_MODE_OFF); + S_Output_EnableDepthWrites(); + +#undef DRAW_TRI +#undef DRAW_QUAD +} + +void Output_DrawShadow( + const int16_t size, const BOUNDS_16 *const bounds, const ITEM *const item) +{ + if (!item->enable_shadow) { + return; + } + + SHADOW_INFO shadow = {}; + shadow.vertex_count = g_Config.visuals.enable_round_shadow ? 32 : 8; + + int32_t x0 = bounds->min.x; + int32_t x1 = bounds->max.x; + int32_t z0 = bounds->min.z; + int32_t z1 = bounds->max.z; + + int32_t x_mid = (x0 + x1) / 2; + int32_t z_mid = (z0 + z1) / 2; + + int32_t x_add = (x1 - x0) * size / 1024; + int32_t z_add = (z1 - z0) * size / 1024; + + for (int32_t i = 0; i < shadow.vertex_count; i++) { + int32_t angle = (DEG_180 + i * DEG_360) / shadow.vertex_count; + shadow.vertices[i].x = x_mid + (x_add * 2) * Math_Sin(angle) / DEG_90; + shadow.vertices[i].z = z_mid + (z_add * 2) * Math_Cos(angle) / DEG_90; + shadow.vertices[i].y = 0; + } + + Matrix_Push(); + Matrix_TranslateAbs( + item->interp.result.pos.x, item->floor, item->interp.result.pos.z); + Matrix_RotY(item->rot.y); + + if (M_CalcObjectVertices(shadow.vertices, shadow.vertex_count)) { + int16_t clip_and = 1; + int16_t clip_positive = 1; + int16_t clip_or = 0; + for (int32_t i = 0; i < shadow.vertex_count; i++) { + clip_and &= m_VBuf[i].clip; + clip_positive &= m_VBuf[i].clip >= 0; + clip_or |= m_VBuf[i].clip; + } + PHD_VBUF *vn1 = &m_VBuf[0]; + PHD_VBUF *vn2 = &m_VBuf[g_Config.visuals.enable_round_shadow ? 4 : 1]; + PHD_VBUF *vn3 = &m_VBuf[g_Config.visuals.enable_round_shadow ? 8 : 2]; + + int32_t c1 = (vn3->xs - vn2->xs) * (vn1->ys - vn2->ys); + int32_t c2 = (vn1->xs - vn2->xs) * (vn3->ys - vn2->ys); + bool visible = (int32_t)(c1 - c2) >= 0; + + if (!clip_and && clip_positive && visible) { + S_Output_DrawShadow( + &m_VBuf[0], clip_or ? 1 : 0, shadow.vertex_count); + } + } + + Matrix_Pop(); +} + +int32_t Output_GetDrawDistMin(void) +{ + return 20; +} + +int32_t Output_GetDrawDistFade(void) +{ + return m_DrawDistFade; +} + +int32_t Output_GetDrawDistMax(void) +{ + return m_DrawDistMax; +} + +void Output_SetDrawDistFade(int32_t dist) +{ + m_DrawDistFade = dist; +} + +void Output_SetDrawDistMax(int32_t dist) +{ + m_DrawDistMax = dist; + + const double near_z = Output_GetNearZ(); + const double far_z = Output_GetFarZ(); + const double res_z = 0.99 * near_z * far_z / (far_z - near_z); + g_FltResZ = res_z; + g_FltResZBuf = 0.005 + res_z / near_z; +} + +void Output_SetWaterColor(const RGB_F *color) +{ + m_WaterColor.r = color->r; + m_WaterColor.g = color->g; + m_WaterColor.b = color->b; +} + +void Output_SetLightAdder(const int32_t adder) +{ + m_LsAdder = adder; +} + +int32_t Output_GetLightAdder(void) +{ + return m_LsAdder; +} + +void Output_SetLightDivider(const int32_t divider) +{ + m_LsDivider = divider; +} + +int32_t Output_GetNearZ(void) +{ + return Output_GetDrawDistMin() << W2V_SHIFT; +} + +int32_t Output_GetFarZ(void) +{ + return Output_GetDrawDistMax() << W2V_SHIFT; +} + +void Output_DrawSprite( + int32_t x, int32_t y, int32_t z, int16_t sprnum, int16_t shade) +{ + x -= g_W2VMatrix._03; + y -= g_W2VMatrix._13; + z -= g_W2VMatrix._23; + + if (x < -Output_GetDrawDistMax() || x > Output_GetDrawDistMax()) { + return; + } + + if (y < -Output_GetDrawDistMax() || y > Output_GetDrawDistMax()) { + return; + } + + if (z < -Output_GetDrawDistMax() || z > Output_GetDrawDistMax()) { + return; + } + + int32_t zv = + g_W2VMatrix._20 * x + g_W2VMatrix._21 * y + g_W2VMatrix._22 * z; + if (zv < Output_GetNearZ() || zv > Output_GetFarZ()) { + return; + } + + int32_t xv = + g_W2VMatrix._00 * x + g_W2VMatrix._01 * y + g_W2VMatrix._02 * z; + int32_t yv = + g_W2VMatrix._10 * x + g_W2VMatrix._11 * y + g_W2VMatrix._12 * z; + int32_t zp = zv / g_PhdPersp; + + const SPRITE_TEXTURE *const sprite = Output_GetSpriteTexture(sprnum); + const int32_t x0 = + Viewport_GetCenterX() + (xv + (sprite->x0 << W2V_SHIFT)) / zp; + const int32_t y0 = + Viewport_GetCenterY() + (yv + (sprite->y0 << W2V_SHIFT)) / zp; + const int32_t x1 = + Viewport_GetCenterX() + (xv + (sprite->x1 << W2V_SHIFT)) / zp; + const int32_t y1 = + Viewport_GetCenterY() + (yv + (sprite->y1 << W2V_SHIFT)) / zp; + if (x1 >= Viewport_GetMinX() && y1 >= Viewport_GetMinY() + && x0 <= Viewport_GetMaxX() && y0 <= Viewport_GetMaxY()) { + int32_t depth = zv >> W2V_SHIFT; + shade += Output_CalcFogShade(depth); + CLAMPG(shade, 0x1FFF); + S_Output_DrawSprite(x0, y0, x1, y1, zv, sprnum, shade); + } } void Output_DrawScreenFlatQuad( - const int32_t sx, const int32_t sy, const int32_t w, const int32_t h, - const RGBA_8888 color) + int32_t sx, int32_t sy, int32_t w, int32_t h, RGBA_8888 color) { - M_Draw2DQuad(sx, sy, sx + w, sy + h, color, color, color, color); + S_Output_Draw2DQuad(sx, sy, sx + w, sy + h, color, color, color, color); } void Output_DrawScreenGradientQuad( - const int32_t sx, const int32_t sy, const int32_t w, const int32_t h, - const RGBA_8888 tl, const RGBA_8888 tr, const RGBA_8888 bl, - const RGBA_8888 br) + int32_t sx, int32_t sy, int32_t w, int32_t h, RGBA_8888 tl, RGBA_8888 tr, + RGBA_8888 bl, RGBA_8888 br) { - M_Draw2DQuad(sx, sy, sx + w, sy + h, tl, tr, bl, br); + S_Output_Draw2DQuad(sx, sy, sx + w, sy + h, tl, tr, bl, br); } -void Output_DrawScreenFrame( - int32_t sx, int32_t sy, const int32_t w, const int32_t h, - const RGBA_8888 col_dark, const RGBA_8888 col_light, - const int32_t thickness_i) +void Output_Draw3DLine( + const XYZ_32 pos_0, const XYZ_32 pos_1, const RGBA_8888 color) { - const float scale = Viewport_GetHeight() / 480.0; - const float thickness = thickness_i * scale / 2.0f; - sx -= scale; - sy -= scale; + PHD_VBUF vbuf[2]; + uint16_t total_clip = 0xFFFF; + total_clip &= M_CalcVertex( + &vbuf[0], (XYZ_16) { .x = pos_0.x, .y = pos_0.y, .z = pos_0.z }); + total_clip &= M_CalcVertex( + &vbuf[1], (XYZ_16) { .x = pos_1.x, .y = pos_1.y, .z = pos_1.z }); + S_Output_Draw3DLine(&vbuf[0], &vbuf[1], color); +} -#define SB_NUM_VERTS_DARK 12 -#define SB_NUM_VERTS_LIGHT 10 - GFX_3D_VERTEX vertices[SB_NUM_VERTS_DARK + SB_NUM_VERTS_LIGHT]; - GFX_3D_VERTEX *const dark_vertices = vertices; - GFX_3D_VERTEX *const light_vertices = vertices + SB_NUM_VERTS_DARK; - const float sxf = sx + thickness; - const float syf = sy + thickness; - const float hf = h; - const float wf = w; +void Output_Draw3DFrame(const XYZ_32 vert[4], const RGBA_8888 color) +{ + Output_Draw3DLine(vert[0], vert[1], color); + Output_Draw3DLine(vert[1], vert[2], color); + Output_Draw3DLine(vert[2], vert[3], color); + Output_Draw3DLine(vert[3], vert[0], color); +} -#define SET(i, x_, y_) \ - vertices[i].x = x_; \ - vertices[i].y = y_; - // clang-format off - // Top Left Dark edge - SET(0, sxf, syf + hf - thickness); - SET(1, sxf + thickness, syf + hf - thickness); - SET(2, sxf, syf); - SET(3, sxf + thickness, syf + thickness); - SET(4, sxf + wf - thickness, syf); - SET(5, sxf + wf - thickness, syf + thickness); - // Bottom Right Dark set - SET(6, sxf + wf + thickness, syf - thickness); - SET(7, sxf + wf, syf - thickness); - SET(8, sxf + wf + thickness, syf + hf + thickness); - SET(9, sxf + wf, syf + hf); - SET(10, sxf - thickness, syf + hf + thickness); - SET(11, sxf - thickness, syf + hf); - // Light box - SET(12, sxf - thickness, syf + hf); - SET(13, sxf - thickness + thickness, syf + hf - thickness); - SET(14, sxf - thickness, syf - thickness); - SET(15, sxf, syf); - SET(16, sxf + wf, syf - thickness); - SET(17, sxf + wf - thickness, syf); - SET(18, sxf + wf, syf + hf); - SET(19, sxf + wf - thickness, syf + hf - thickness); - SET(20, sxf - thickness, syf + hf); - SET(21, sxf - thickness + thickness, syf + hf - thickness); - // clang-format on -#undef SET - - for (int32_t i = 0; i < SB_NUM_VERTS_DARK + SB_NUM_VERTS_LIGHT; i++) { - vertices[i].z = 1.0f; - vertices[i].s = 0.0f; - vertices[i].t = 0.0f; - vertices[i].w = 0.0f; - } - for (int32_t i = 0; i < SB_NUM_VERTS_DARK; i++) { - dark_vertices[i].r = col_dark.r; - dark_vertices[i].g = col_dark.g; - dark_vertices[i].b = col_dark.b; - dark_vertices[i].a = col_dark.a; - } - for (int32_t i = 0; i < SB_NUM_VERTS_LIGHT; i++) { - light_vertices[i].r = col_light.r; - light_vertices[i].g = col_light.g; - light_vertices[i].b = col_light.b; - light_vertices[i].a = col_light.a; - } - M_DisableTextureMode(); - M_DrawTriangleStrip(vertices, SB_NUM_VERTS_DARK + SB_NUM_VERTS_LIGHT); +void Output_DrawScreenBox( + int32_t sx, int32_t sy, int32_t w, int32_t h, RGBA_8888 colDark, + RGBA_8888 colLight, int32_t thickness) +{ + float scale = Viewport_GetHeight() / 480.0; + S_Output_ScreenBox( + sx - scale, sy - scale, w, h, colDark, colLight, + thickness * scale / 2.0f); } void Output_DrawGradientScreenBox( - int32_t sx, int32_t sy, int32_t w, int32_t h, const RGBA_8888 tl, - const RGBA_8888 tr, const RGBA_8888 bl, const RGBA_8888 br, - const int32_t thickness_i) + int32_t sx, int32_t sy, int32_t w, int32_t h, RGBA_8888 tl, RGBA_8888 tr, + RGBA_8888 bl, RGBA_8888 br, int32_t thickness) { - const float scale = Viewport_GetHeight() / 480.0; - const float thickness = thickness_i * scale / 2.0f; - sx -= scale; - sy -= scale; - w += scale; - h += scale; - - // 0 2 - // * & - // 1 3 - // - // 7 5 - // # @ - // 6 4 - GFX_3D_VERTEX vertices[10]; - -#define SET(i, x_, y_, color) \ - vertices[i].x = x_; \ - vertices[i].y = y_; \ - vertices[i].r = color.r; \ - vertices[i].g = color.g; \ - vertices[i].b = color.b; \ - vertices[i].a = color.a; - // clang-format off - SET(0, sx - thickness, sy - thickness, tl); - SET(1, sx + thickness, sy + thickness, tl); - SET(2, sx + w + thickness, sy - thickness, tr); - SET(3, sx + w - thickness, sy + thickness, tr); - SET(4, sx + w + thickness, sy + h + thickness, br); - SET(5, sx + w - thickness, sy + h - thickness, br); - SET(6, sx - thickness, sy + h + thickness, bl); - SET(7, sx + thickness, sy + h - thickness, bl); - SET(8, sx - thickness, sy - thickness, tl); - SET(9, sx + thickness, sy + thickness, tl); - // clang-format on -#undef SET - - for (int32_t i = 0; i < 10; i++) { - vertices[i].z = 1.0f; - vertices[i].s = 0.0f; - vertices[i].t = 0.0f; - vertices[i].w = 0.0f; - } - M_DisableTextureMode(); - M_DrawTriangleStrip(vertices, 10); + float scale = Viewport_GetHeight() / 480.0; + S_Output_4ColourTextBox( + sx - scale, sy - scale, w + scale, h + scale, tl, tr, bl, br, + thickness * scale / 2.0f); } void Output_DrawCentreGradientScreenBox( - int32_t sx, int32_t sy, int32_t w, int32_t h, const RGBA_8888 edge, - const RGBA_8888 center, const int32_t thickness_i) + int32_t sx, int32_t sy, int32_t w, int32_t h, RGBA_8888 edge, + RGBA_8888 center, int32_t thickness) { - const float scale = Viewport_GetHeight() / 480.0; - const float thickness = thickness_i * scale / 2.0f; - sx -= scale; - sy -= scale; - w += scale; - h += scale; - - // 0 2 4 - // * & - // 1 3 5 - // - // 14 15 7 6 - // - // 13 10 9 - // # @ - // 12 11 8 - - const int32_t half_w = w / 2; - const int32_t half_h = h / 2; - GFX_3D_VERTEX vertices[18]; - -#define SET(i, x_, y_, color) \ - vertices[i].x = x_; \ - vertices[i].y = y_; \ - vertices[i].r = color.r; \ - vertices[i].g = color.g; \ - vertices[i].b = color.b; \ - vertices[i].a = color.a; - // clang-format off - SET(0, sx - thickness, sy - thickness, edge); - SET(1, sx + thickness, sy + thickness, edge); - SET(2, sx + half_w, sy - thickness, center); - SET(3, sx + half_w, sy + thickness, center); - SET(4, sx + w + thickness, sy - thickness, edge); - SET(5, sx + w - thickness, sy + thickness, edge); - SET(6, sx + w + thickness, sy + half_h, center); - SET(7, sx + w - thickness, sy + half_h, center); - SET(8, sx + w + thickness, sy + h + thickness, edge); - SET(9, sx + w - thickness, sy + h - thickness, edge); - SET(10, sx + half_w, sy + h + thickness, center); - SET(11, sx + half_w, sy + h - thickness, center); - SET(12, sx - thickness, sy + h + thickness, edge); - SET(13, sx + thickness, sy + h - thickness, edge); - SET(14, sx - thickness, sy + half_h, center); - SET(15, sx + thickness, sy + half_h, center); - SET(16, sx - thickness, sy - thickness, edge); - SET(17, sx + thickness, sy + thickness, edge); - // clang-format on -#undef SET - - for (int32_t i = 0; i < 18; i++) { - vertices[i].z = 1.0f; - vertices[i].s = 0.0f; - vertices[i].t = 0.0f; - vertices[i].w = 0.0f; - } - M_DisableTextureMode(); - M_DrawTriangleStrip(vertices, 18); + float scale = Viewport_GetHeight() / 480.0; + S_Output_2ToneColourTextBox( + sx - scale, sy - scale, w + scale, h + scale, edge, center, + thickness * scale / 2.0f); } -void Output_DrawScreenFBox( - const int32_t sx, const int32_t sy, const int32_t w, const int32_t h) +void Output_DrawScreenFBox(int32_t sx, int32_t sy, int32_t w, int32_t h) { RGBA_8888 color = { 0, 0, 0, 128 }; - M_Draw2DQuad(sx, sy, sx + w, sy + h, color, color, color, color); + S_Output_Draw2DQuad(sx, sy, sx + w, sy + h, color, color, color, color); } void Output_DrawScreenSprite( - const int32_t sx, const int32_t sy, const int32_t z, const int32_t scale_h, - const int32_t scale_v, const int32_t sprite_idx, const int16_t shade, - const uint16_t flags, const int32_t page) + int32_t sx, int32_t sy, int32_t z, int32_t scale_h, int32_t scale_v, + int32_t sprnum, int16_t shade, uint16_t flags, int32_t page) { - const SPRITE_TEXTURE *const sprite = Output_GetSpriteTexture(sprite_idx); + const SPRITE_TEXTURE *const sprite = Output_GetSpriteTexture(sprnum); const int32_t x0 = sx + (scale_h * sprite->x0 / PHD_ONE); const int32_t x1 = sx + (scale_h * sprite->x1 / PHD_ONE); const int32_t y0 = sy + (scale_v * sprite->y0 / PHD_ONE); const int32_t y1 = sy + (scale_v * sprite->y1 / PHD_ONE); if (x1 >= 0 && y1 >= 0 && x0 < Viewport_GetWidth() && y0 < Viewport_GetHeight()) { - M_DrawSprite(x0, y0, x1, y1, Output_GetNearZ() + 200, sprite_idx, 0); + S_Output_DrawSprite(x0, y0, x1, y1, Output_GetNearZ() + 200, sprnum, 0); + } +} + +void Output_DrawSpriteRel( + int32_t x, int32_t y, int32_t z, int16_t sprnum, int16_t shade) +{ + int32_t zv = g_MatrixPtr->_20 * x + g_MatrixPtr->_21 * y + + g_MatrixPtr->_22 * z + g_MatrixPtr->_23; + if (zv < Output_GetNearZ() || zv > Output_GetFarZ()) { + return; + } + + int32_t xv = g_MatrixPtr->_00 * x + g_MatrixPtr->_01 * y + + g_MatrixPtr->_02 * z + g_MatrixPtr->_03; + int32_t yv = g_MatrixPtr->_10 * x + g_MatrixPtr->_11 * y + + g_MatrixPtr->_12 * z + g_MatrixPtr->_13; + int32_t zp = zv / g_PhdPersp; + + const SPRITE_TEXTURE *const sprite = Output_GetSpriteTexture(sprnum); + const int32_t x0 = + Viewport_GetCenterX() + (xv + (sprite->x0 << W2V_SHIFT)) / zp; + const int32_t y0 = + Viewport_GetCenterY() + (yv + (sprite->y0 << W2V_SHIFT)) / zp; + const int32_t x1 = + Viewport_GetCenterX() + (xv + (sprite->y0 << W2V_SHIFT)) / zp; + const int32_t y1 = + Viewport_GetCenterY() + (yv + (sprite->y1 << W2V_SHIFT)) / zp; + if (x1 >= Viewport_GetMinX() && y1 >= Viewport_GetMinY() + && x0 <= Viewport_GetMaxX() && y0 <= Viewport_GetMaxY()) { + int32_t depth = zv >> W2V_SHIFT; + shade += Output_CalcFogShade(depth); + CLAMPG(shade, 0x1FFF); + S_Output_DrawSprite(x0, y0, x1, y1, zv, sprnum, shade); } } void Output_DrawUISprite( - const int32_t x, const int32_t y, const int32_t scale, - const int16_t sprite_idx, const int16_t shade) + int32_t x, int32_t y, int32_t scale, int16_t sprnum, int16_t shade) { - const SPRITE_TEXTURE *const sprite = Output_GetSpriteTexture(sprite_idx); + const SPRITE_TEXTURE *const sprite = Output_GetSpriteTexture(sprnum); const int32_t x0 = x + (scale * sprite->x0 >> 16); const int32_t x1 = x + (scale * sprite->x1 >> 16); const int32_t y0 = y + (scale * sprite->y0 >> 16); const int32_t y1 = y + (scale * sprite->y1 >> 16); if (x1 >= Viewport_GetMinX() && y1 >= Viewport_GetMinY() && x0 <= Viewport_GetMaxX() && y0 <= Viewport_GetMaxY()) { - M_DrawSprite( - x0, y0, x1, y1, Output_GetNearZ() + 200, sprite_idx, shade); + S_Output_DrawSprite( + x0, y0, x1, y1, Output_GetNearZ() + 200, sprnum, shade); } } -bool Output_LoadBackgroundFromImage(const IMAGE *const image) +bool Output_LoadBackgroundFromFile(const char *const path) { - M_DownloadBackdropSurface(image); + ASSERT(path != nullptr); + const char *old_path = m_BackdropImagePath; + m_BackdropImagePath = File_GuessExtension(path, m_ImageExtensions); + Memory_FreePointer(&old_path); + + IMAGE *const img = Image_CreateFromFileInto( + m_BackdropImagePath, Viewport_GetWidth(), Viewport_GetHeight(), + IMAGE_FIT_SMART); + if (img == nullptr) { + return false; + } + S_Output_DownloadBackdropSurface(img); + Image_Free(img); return true; } @@ -805,48 +1147,109 @@ void Output_LoadBackgroundFromObject(void) void Output_UnloadBackground(void) { - M_DownloadBackdropSurface(nullptr); - Output_ClearLastBackgroundPath(); + S_Output_DownloadBackdropSurface(nullptr); + Memory_FreePointer(&m_BackdropImagePath); } void Output_DrawLightningSegment( - const XYZ_32 pos_0, const XYZ_32 pos_1, const int32_t thickness) + int32_t x1, int32_t y1, const int32_t z1, int32_t x2, int32_t y2, + const int32_t z2, const int32_t width) { if (m_LightningCount >= MAX_LIGHTNINGS) { return; } + + if (z1 < Output_GetNearZ() || z1 > Output_GetFarZ() + || z2 < Output_GetNearZ() || z2 > Output_GetFarZ()) { + return; + } + + x1 = Viewport_GetCenterX() + x1 / (z1 / g_PhdPersp); + y1 = Viewport_GetCenterY() + y1 / (z1 / g_PhdPersp); + x2 = Viewport_GetCenterX() + x2 / (z2 / g_PhdPersp); + y2 = Viewport_GetCenterY() + y2 / (z2 / g_PhdPersp); + const int32_t thickness1 = (width << W2V_SHIFT) / (z1 / g_PhdPersp); + const int32_t thickness2 = (width << W2V_SHIFT) / (z2 / g_PhdPersp); + LIGHTNING *const lightning = &m_LightningTable[m_LightningCount]; - lightning->pos_0 = pos_0; - lightning->pos_1 = pos_1; - lightning->thickness = thickness; + lightning->edges[0].pos.x = x1; + lightning->edges[0].pos.y = y1; + lightning->edges[0].pos.z = z1; + lightning->edges[0].thickness = thickness1; + lightning->edges[1].pos.x = x2; + lightning->edges[1].pos.y = y2; + lightning->edges[1].pos.z = z2; + lightning->edges[1].thickness = thickness2; m_LightningCount++; } -void Output_DrawBlackRectangle(const int32_t opacity) +void Output_SetupBelowWater(bool underwater) { - const int32_t sx = 0; - const int32_t sy = 0; - const int32_t sw = Viewport_GetWidth(); - const int32_t sh = Viewport_GetHeight(); - const RGBA_8888 background = { 0, 0, 0, opacity }; - M_DisableDepthTest(); - Output_ClearDepthBuffer(); + m_IsWaterEffect = true; + m_IsWibbleEffect = !underwater; + m_IsShadeEffect = true; +} + +void Output_SetupAboveWater(bool underwater) +{ + m_IsWaterEffect = false; + m_IsWibbleEffect = underwater; + m_IsShadeEffect = underwater; +} + +void Output_AnimateTextures(const int32_t num_frames) +{ + m_WibbleOffset = (m_WibbleOffset + num_frames) % WIBBLE_SIZE; + m_AnimatedTexturesOffset += num_frames; + while (m_AnimatedTexturesOffset > 5) { + Output_CycleAnimatedTextures(); + m_AnimatedTexturesOffset -= 5; + } +} + +void Output_RotateLight(int16_t pitch, int16_t yaw) +{ + int32_t cp = Math_Cos(pitch); + int32_t sp = Math_Sin(pitch); + int32_t cy = Math_Cos(yaw); + int32_t sy = Math_Sin(yaw); + int32_t ls_x = TRIGMULT2(cp, sy); + int32_t ls_y = -sp; + int32_t ls_z = TRIGMULT2(cp, cy); + m_LsVectorView.x = (g_W2VMatrix._00 * ls_x + g_W2VMatrix._01 * ls_y + + g_W2VMatrix._02 * ls_z) + >> W2V_SHIFT; + m_LsVectorView.y = (g_W2VMatrix._10 * ls_x + g_W2VMatrix._11 * ls_y + + g_W2VMatrix._12 * ls_z) + >> W2V_SHIFT; + m_LsVectorView.z = (g_W2VMatrix._20 * ls_x + g_W2VMatrix._21 * ls_y + + g_W2VMatrix._22 * ls_z) + >> W2V_SHIFT; +} + +void Output_DrawBlackRectangle(int32_t opacity) +{ + int32_t sx = 0; + int32_t sy = 0; + int32_t sw = Viewport_GetWidth(); + int32_t sh = Viewport_GetHeight(); + + RGBA_8888 background = { 0, 0, 0, opacity }; + S_Output_DisableDepthTest(); + S_Output_ClearDepthBuffer(); Output_DrawScreenFlatQuad(sx, sy, sw, sh, background); - M_EnableDepthTest(); + S_Output_EnableDepthTest(); } void Output_DrawBackground(void) { - if (m_PictureSurface == nullptr) { - return; - } - GFX_2D_Renderer_Render(m_Renderer2D); + // already handled in S_Output_RenderBegin } void Output_DrawPolyList(void) { // force flush the vertex stream - Output_ClearDepthBuffer(); + S_Output_ClearDepthBuffer(); } void Output_ApplyFOV(void) @@ -875,73 +1278,138 @@ void Output_ApplyFOV(void) } } -void Output_DrawTextBackground( - const UI_STYLE ui_style, const int32_t sx, const int32_t sy, int32_t w, - int32_t h, const int32_t z, const TEXT_STYLE text_style) +void Output_ApplyTint(float *r, float *g, float *b) { - if (ui_style == UI_STYLE_PC) { - Output_DrawScreenFBox(sx, sy, w, h); - return; - } - - // Make sure height and width divisible by 2. - w = 2 * ((w + 1) / 2); - h = 2 * ((h + 1) / 2); - Output_DrawScreenFBox(sx - 1, sy - 1, w + 1, h + 1); - - QUAD_INFO gradient_quads[4] = { - { sx, sy, w / 2, h / 2 }, - { sx + w, sy, -w / 2, h / 2 }, - { sx, sy + h, w / 2, -h / 2 }, - { sx + w, sy + h, -w / 2, -h / 2 }, - }; - - if (text_style == TS_HEADING) { - for (int i = 0; i < 4; i++) { - Output_DrawScreenGradientQuad( - gradient_quads[i].x, gradient_quads[i].y, gradient_quads[i].w, - gradient_quads[i].h, M_GetMenuColor(MC_BROWN_E), - M_GetMenuColor(MC_BROWN_E), M_GetMenuColor(MC_BROWN_E), - M_GetMenuColor(MC_BROWN_C)); - } - } else if (text_style == TS_REQUESTED) { - for (int i = 0; i < 4; i++) { - Output_DrawScreenGradientQuad( - gradient_quads[i].x, gradient_quads[i].y, gradient_quads[i].w, - gradient_quads[i].h, M_GetMenuColor(MC_PURPLE_E), - M_GetMenuColor(MC_PURPLE_E), M_GetMenuColor(MC_PURPLE_E), - M_GetMenuColor(MC_PURPLE_C)); - } + if (m_IsShadeEffect) { + *r *= m_WaterColor.r; + *g *= m_WaterColor.g; + *b *= m_WaterColor.b; } } -void Output_DrawTextOutline( - const UI_STYLE ui_style, const int32_t sx, const int32_t sy, int32_t w, - int32_t h, const int32_t z, const TEXT_STYLE text_style) +bool Output_MakeScreenshot(const char *const path) { - if (ui_style == UI_STYLE_PC) { - Output_DrawScreenFrame( - sx, sy, w, h, M_GetMenuColor(MC_GOLD_DARK), - M_GetMenuColor(MC_GOLD_LIGHT), TEXT_OUTLINE_THICKNESS); - return; + GFX_Context_ScheduleScreenshot(path); + return true; +} + +int Output_GetObjectBounds(const BOUNDS_16 *const bounds) +{ + if (g_MatrixPtr->_23 >= Output_GetFarZ()) { + return 0; } - if (text_style == TS_HEADING) { - Output_DrawGradientScreenBox( - sx, sy, w, h, M_GetMenuColor(MC_BLACK), M_GetMenuColor(MC_BLACK), - M_GetMenuColor(MC_BLACK), M_GetMenuColor(MC_BLACK), - TEXT_OUTLINE_THICKNESS); - } else if (text_style == TS_BACKGROUND) { - Output_DrawGradientScreenBox( - sx, sy, w, h, M_GetMenuColor(MC_GREY_TL), - M_GetMenuColor(MC_GREY_TR), M_GetMenuColor(MC_GREY_BL), - M_GetMenuColor(MC_GREY_BR), TEXT_OUTLINE_THICKNESS); - } else if (text_style == TS_REQUESTED) { - // Make sure height and width divisible by 2. - w = 2 * ((w + 1) / 2); - h = 2 * ((h + 1) / 2); - Output_DrawCentreGradientScreenBox( - sx, sy, w, h, M_GetMenuColor(MC_GREY_E), M_GetMenuColor(MC_GREY_C), - TEXT_OUTLINE_THICKNESS); + int32_t x_min = bounds->min.x; + int32_t x_max = bounds->max.x; + int32_t y_min = bounds->min.y; + int32_t y_max = bounds->max.y; + int32_t z_min = bounds->min.z; + int32_t z_max = bounds->max.z; + + XYZ_32 vtx[8]; + vtx[0].x = x_min; + vtx[0].y = y_min; + vtx[0].z = z_min; + vtx[1].x = x_max; + vtx[1].y = y_min; + vtx[1].z = z_min; + vtx[2].x = x_max; + vtx[2].y = y_max; + vtx[2].z = z_min; + vtx[3].x = x_min; + vtx[3].y = y_max; + vtx[3].z = z_min; + vtx[4].x = x_min; + vtx[4].y = y_min; + vtx[4].z = z_max; + vtx[5].x = x_max; + vtx[5].y = y_min; + vtx[5].z = z_max; + vtx[6].x = x_max; + vtx[6].y = y_max; + vtx[6].z = z_max; + vtx[7].x = x_min; + vtx[7].y = y_max; + vtx[7].z = z_max; + + int num_z = 0; + x_min = 0x3FFFFFFF; + y_min = 0x3FFFFFFF; + x_max = -0x3FFFFFFF; + y_max = -0x3FFFFFFF; + + for (int i = 0; i < 8; i++) { + int32_t zv = g_MatrixPtr->_20 * vtx[i].x + g_MatrixPtr->_21 * vtx[i].y + + g_MatrixPtr->_22 * vtx[i].z + g_MatrixPtr->_23; + + if (zv > Output_GetNearZ() && zv < Output_GetFarZ()) { + ++num_z; + int32_t zp = zv / g_PhdPersp; + int32_t xv = + (g_MatrixPtr->_00 * vtx[i].x + g_MatrixPtr->_01 * vtx[i].y + + g_MatrixPtr->_02 * vtx[i].z + g_MatrixPtr->_03) + / zp; + int32_t yv = + (g_MatrixPtr->_10 * vtx[i].x + g_MatrixPtr->_11 * vtx[i].y + + g_MatrixPtr->_12 * vtx[i].z + g_MatrixPtr->_13) + / zp; + + if (x_min > xv) { + x_min = xv; + } else if (x_max < xv) { + x_max = xv; + } + + if (y_min > yv) { + y_min = yv; + } else if (y_max < yv) { + y_max = yv; + } + } } + + x_min += Viewport_GetCenterX(); + x_max += Viewport_GetCenterX(); + y_min += Viewport_GetCenterY(); + y_max += Viewport_GetCenterY(); + + if (!num_z || x_min > g_PhdRight || y_min > g_PhdBottom || x_max < g_PhdLeft + || y_max < g_PhdTop) { + return 0; // out of screen + } + + if (num_z < 8 || x_min < 0 || y_min < 0 || x_max > Viewport_GetMaxX() + || y_max > Viewport_GetMaxY()) { + return -1; // clipped + } + + return 1; // fully on screen +} + +int32_t Output_CalcFogShade(const int32_t depth) +{ + int32_t fog_begin = Output_GetDrawDistFade(); + int32_t fog_end = Output_GetDrawDistMax(); + + if (depth < fog_begin) { + return 0; + } + if (depth >= fog_end) { + return 0x1FFF; + } + + return (depth - fog_begin) * 0x1FFF / (fog_end - fog_begin); +} + +int32_t Output_GetRoomLightShade(const ROOM_LIGHT_MODE mode) +{ + // TODO: remove + ASSERT_FAIL(); + return 0; +} + +void Output_LightRoomVertices(const ROOM *room) +{ + // TODO: remove + ASSERT_FAIL(); } diff --git a/src/tr1/game/output.h b/src/tr1/game/output.h index 6a1bdd517..00dc25375 100644 --- a/src/tr1/game/output.h +++ b/src/tr1/game/output.h @@ -3,38 +3,49 @@ #include "global/types.h" #include -#include #include #include bool Output_Init(void); void Output_Shutdown(void); +void Output_ReserveVertexBuffer(size_t size); -void Output_SetWindowSize(int32_t width, int32_t height); -void Output_ApplyLevelSettings(void); +void Output_SetWindowSize(int width, int height); void Output_ApplyRenderSettings(void); -void Output_ObserveFOVChange(void); +void Output_DownloadTextures(void); int32_t Output_GetNearZ(void); int32_t Output_GetFarZ(void); -void Output_SetWaterColor(const RGB_888 color); +int32_t Output_GetDrawDistMin(void); +int32_t Output_GetDrawDistFade(void); +int32_t Output_GetDrawDistMax(void); +void Output_SetDrawDistFade(int32_t dist); +void Output_SetDrawDistMax(int32_t dist); +void Output_SetWaterColor(const RGB_F *color); void Output_BeginScene(void); void Output_EndScene(void); +void Output_DrawBlack(void); void Output_ClearDepthBuffer(void); -void Output_SetSkyboxEnabled(bool enabled); -bool Output_IsSkyboxEnabled(void); -void Output_DrawSkybox(const OBJECT_MESH *mesh); void Output_DrawObjectMesh(const OBJECT_MESH *mesh, int32_t clip); void Output_DrawObjectMesh_I(const OBJECT_MESH *mesh, int32_t clip); -void Output_DrawRoomMesh(ROOM *mesh); + +void Output_SetSkyboxEnabled(bool enabled); +bool Output_IsSkyboxEnabled(void); +void Output_DrawSkybox(const OBJECT_MESH *mesh); + +void Output_DrawRoom(const ROOM_MESH *mesh); void Output_DrawRoomPortals(const ROOM *room); void Output_DrawRoomTriggers(const ROOM *room); void Output_DrawShadow(int16_t size, const BOUNDS_16 *bounds, const ITEM *item); -void Output_DrawLightningSegment(XYZ_32 pos_0, XYZ_32 pos_1, int32_t thickness); +void Output_DrawLightningSegment( + int32_t x1, int32_t y1, int32_t z1, int32_t x2, int32_t y2, int32_t z2, + int32_t width); +void Output_Draw3DLine(XYZ_32 pos_0, XYZ_32 pos_1, RGBA_8888 color); +void Output_Draw3DFrame(const XYZ_32 vert[4], RGBA_8888 color); void Output_FlushTranslucentObjects(void); void Output_DrawScreenFlatQuad( @@ -44,7 +55,7 @@ void Output_DrawScreenTranslucentQuad( void Output_DrawScreenGradientQuad( int32_t sx, int32_t sy, int32_t w, int32_t h, RGBA_8888 tl, RGBA_8888 tr, RGBA_8888 bl, RGBA_8888 br); -void Output_DrawScreenFrame( +void Output_DrawScreenBox( int32_t sx, int32_t sy, int32_t w, int32_t h, RGBA_8888 colDark, RGBA_8888 colLight, int32_t thickness); void Output_DrawGradientScreenBox( @@ -56,10 +67,12 @@ void Output_DrawCentreGradientScreenBox( void Output_DrawScreenFBox(int32_t sx, int32_t sy, int32_t w, int32_t h); void Output_DrawSprite( - int32_t x, int32_t y, int32_t z, int16_t sprnum, int16_t shade, RGB_F tint); + int32_t x, int32_t y, int32_t z, int16_t sprnum, int16_t shade); void Output_DrawScreenSprite( int32_t sx, int32_t sy, int32_t z, int32_t scale_h, int32_t scale_v, int32_t sprnum, int16_t shade, uint16_t flags, int32_t page); +void Output_DrawSpriteRel( + int32_t x, int32_t y, int32_t z, int16_t sprnum, int16_t shade); void Output_DrawUISprite( int32_t x, int32_t y, int32_t scale, int16_t sprnum, int16_t shade); @@ -68,20 +81,7 @@ void Output_SetupAboveWater(bool underwater); void Output_AnimateTextures(int32_t num_frames); void Output_ApplyFOV(void); -RGB_F Output_GetTint(void); +void Output_ApplyTint(float *r, float *g, float *b); +void Output_FillEnvironmentMap(void); bool Output_MakeScreenshot(const char *path); - -bool Output_GetWaterEffect(void); -bool Output_GetWibbleEffect(void); -int32_t Output_GetTime(void); -void Output_GetProjectionMatrix(GLfloat output[][4]); -void Output_EnableScissor(float x, float y, float w, float h); -void Output_DisableScissor(void); - -// TODO: these functions are poor in their design and should be not needed -void Output_RememberState(void); -void Output_RestoreState(void); - -int32_t Output_GetLightDivider(void); -XYZ_32 Output_GetLightVectorView(void); diff --git a/src/tr1/game/output/draw_misc.c b/src/tr1/game/output/draw_misc.c deleted file mode 100644 index 739880a8a..000000000 --- a/src/tr1/game/output/draw_misc.c +++ /dev/null @@ -1,302 +0,0 @@ -#include "game/output.h" -#include "game/output/meshes/common.h" -#include "game/output/meshes/dynamic.h" -#include "game/output/meshes/objects.h" -#include "game/output/meshes/rooms.h" -#include "game/output/sprites.h" -#include "game/output/utils.h" - -#include -#include - -#include - -static struct { - GLint bound_polygon_mode[2]; -} m_CachedState; - -static bool m_IsSkyboxEnabled = false; -static void M_DrawSphere(XYZ_32 pos, int32_t radius); - -static void M_DrawSphere(const XYZ_32 pos, const int32_t radius) -{ - // More subdivisions means smoother spheres. - const int32_t subdivisions = 12; - const int32_t position_count = SQUARE(subdivisions + 1); - XYZ_F positions[position_count]; - int32_t index = 0; - - for (int32_t i = 0; i <= subdivisions; i++) { - const float theta = (M_PI * i) / subdivisions; // Latitude angle - const float sin_theta = sinf(theta); - const float cos_theta = cosf(theta); - - for (int32_t j = 0; j <= subdivisions; j++) { - const float phi = (2 * M_PI * j) / subdivisions; // Longitude angle - const float sin_phi = sinf(phi); - const float cos_phi = cosf(phi); - - // Convert spherical coordinates to 3D points. - positions[index] = (XYZ_F) { - .x = pos.x + radius * cos_phi * sin_theta, - .y = pos.y + radius * cos_theta, - .z = pos.z + radius * sin_phi * sin_theta, - }; - index++; - } - } - - const int32_t vertex_count = - subdivisions * subdivisions * OUTPUT_QUAD_VERTICES; - OUTPUT_MESH_VERTEX vertices[vertex_count]; - OUTPUT_MESH_VERTEX *out_vertex = vertices; - for (int32_t i = 0; i < subdivisions; i++) { - for (int32_t j = 0; j < subdivisions; j++) { - const int32_t indices[4] = { - i * (subdivisions + 1) + j, - (i + 1) * (subdivisions + 1) + j, - (i + 1) * (subdivisions + 1) + (j + 1), - i * (subdivisions + 1) + (j + 1), - }; - for (int32_t k = 0; k < OUTPUT_QUAD_VERTICES; k++) { - out_vertex->pos = positions[indices[OUTPUT_QUAD_TO_FAN(k)]]; - out_vertex++; - } - } - } - - const RGBA_8888 color_black = { 0, 0, 0, 128 }; - const RGBA_8888 color_white = { 255, 255, 255, 128 }; - const bool wireframe_state = GFX_Context_GetWireframeMode(); - for (int32_t i = 0; i < vertex_count; i++) { - vertices[i].flags = - VERT_FLAT_SHADED | VERT_NO_LIGHTING | VERT_NO_CAUSTICS; - vertices[i].color = wireframe_state ? color_black : color_white; - } - - glGetIntegerv(GL_POLYGON_MODE, &m_CachedState.bound_polygon_mode[0]); - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - Output_Shader_UploadMatrix(Output_Meshes_GetShader(), g_MatrixPtr); - Output_Meshes_DrawTriangles(vertex_count, vertices); - glBlendFunc(GL_ONE, GL_ZERO); - glPolygonMode(GL_FRONT_AND_BACK, m_CachedState.bound_polygon_mode[0]); -} - -void Output_SetSkyboxEnabled(const bool enabled) -{ - m_IsSkyboxEnabled = enabled; -} - -bool Output_IsSkyboxEnabled(void) -{ - return m_IsSkyboxEnabled; -} - -void Output_DrawSkybox(const OBJECT_MESH *const mesh) -{ - Output_RememberState(); - glDisable(GL_DEPTH_TEST); - Output_Meshes_RenderObjectMesh(g_MatrixPtr, Output_GetTint(), mesh); - glEnable(GL_DEPTH_TEST); - Output_RestoreState(); -} - -void Output_DrawObjectMesh(const OBJECT_MESH *const mesh, const int32_t clip) -{ - Output_RememberState(); - Output_Meshes_RenderObjectMesh(g_MatrixPtr, Output_GetTint(), mesh); - Output_RestoreState(); - - if (g_Config.rendering.enable_debug_spheres) { - M_DrawSphere( - (XYZ_32) { mesh->center.x, mesh->center.y, mesh->center.z }, - mesh->radius); - } -} - -void Output_DrawObjectMesh_I(const OBJECT_MESH *const mesh, const int32_t clip) -{ - Matrix_Push(); - Matrix_Interpolate(); - Output_DrawObjectMesh(mesh, clip); - Matrix_Pop(); -} - -void Output_DrawRoomMesh(ROOM *const room) -{ - Output_RememberState(); - Output_LightRoom(room); - Output_EnableScissor( - room->bound_left, room->bound_bottom, - room->bound_right - room->bound_left, - room->bound_bottom - room->bound_top); - Output_Meshes_RenderRoomMesh(g_MatrixPtr, Output_GetTint(), room); - Output_DisableScissor(); - Output_RestoreState(); -} - -void Output_DrawRoomPortals(const ROOM *const room) -{ - const int32_t vertex_count = room->portals->count * 8; - OUTPUT_MESH_VERTEX vertices[vertex_count]; - OUTPUT_MESH_VERTEX *out_vertex = vertices; - const RGBA_8888 color = { 0, 0, 255, 255 }; - for (int32_t i = 0; i < room->portals->count; i++) { - const PORTAL *const portal = &room->portals->portal[i]; - const XYZ_F positions[4] = { - { portal->vertex[0].x, portal->vertex[0].y, portal->vertex[0].z }, - { portal->vertex[1].x, portal->vertex[1].y, portal->vertex[1].z }, - { portal->vertex[2].x, portal->vertex[2].y, portal->vertex[2].z }, - { portal->vertex[3].x, portal->vertex[3].y, portal->vertex[3].z }, - }; - const int32_t indices[8] = { 0, 1, 1, 2, 2, 3, 3, 0 }; - for (int32_t j = 0; j < 8; j++) { - out_vertex->pos = positions[indices[j]]; - out_vertex++; - } - } - for (int32_t i = 0; i < vertex_count; i++) { - vertices[i].flags = - VERT_FLAT_SHADED | VERT_NO_LIGHTING | VERT_NO_CAUSTICS; - vertices[i].color = color; - } - - glDisable(GL_DEPTH_TEST); - glGetIntegerv(GL_POLYGON_MODE, &m_CachedState.bound_polygon_mode[0]); - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - Output_Shader_UploadMatrix(Output_Meshes_GetShader(), g_MatrixPtr); - Output_Meshes_DrawPrimitives(GL_LINES, vertex_count, vertices); - glBlendFunc(GL_ONE, GL_ZERO); - glPolygonMode(GL_FRONT_AND_BACK, m_CachedState.bound_polygon_mode[0]); - glEnable(GL_DEPTH_TEST); -} - -void Output_DrawRoomTriggers(const ROOM *const room) -{ - const RGBA_8888 color = { .r = 255, .g = 0, .b = 255, .a = 128 }; - const XZ_16 offsets[4] = { { 0, 0 }, { 0, 1 }, { 1, 1 }, { 1, 0 } }; - - int32_t vertex_count = 0; - for (int32_t z = 0; z < room->size.z; z++) { - for (int32_t x = 0; x < room->size.x; x++) { - const SECTOR *sector = Room_GetUnitSector(room, x, z); - if (sector->trigger == nullptr) { - continue; - } - vertex_count += OUTPUT_QUAD_VERTICES; - } - } - - OUTPUT_MESH_VERTEX *vertices = - Memory_Alloc(vertex_count * sizeof(OUTPUT_MESH_VERTEX)); - OUTPUT_MESH_VERTEX *out_vertex = vertices; - - for (int32_t z = 0; z < room->size.z; z++) { - for (int32_t x = 0; x < room->size.x; x++) { - const SECTOR *sector = Room_GetUnitSector(room, x, z); - if (sector->trigger == nullptr) { - continue; - } - for (int32_t i = 0; i < OUTPUT_QUAD_VERTICES; i++) { - const int32_t j = OUTPUT_QUAD_TO_FAN(i); - XYZ_16 vertex_pos = { - .x = (x + offsets[j].x) * WALL_L, - .z = (z + offsets[j].z) * WALL_L, - }; - XYZ_32 world_pos = { - .x = room->pos.x + x * WALL_L + offsets[j].x * (WALL_L - 1), - .z = room->pos.z + z * WALL_L + offsets[j].z * (WALL_L - 1), - .y = room->pos.y, - }; - - int16_t room_num = room - Room_Get(0); - sector = Room_GetSector( - world_pos.x, world_pos.y, world_pos.z, &room_num); - vertex_pos.y = - Room_GetHeight( - sector, world_pos.x, world_pos.y, world_pos.z) - + (Output_GetWaterEffect() ? -16 : -2); - - out_vertex->pos = - (XYZ_F) { vertex_pos.x, vertex_pos.y, vertex_pos.z }; - out_vertex->flags = VERT_FLAT_SHADED | VERT_NO_LIGHTING; - out_vertex->color = color; - out_vertex++; - } - } - } - - glDepthMask(GL_FALSE); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - Output_Shader_UploadMatrix(Output_Meshes_GetShader(), g_MatrixPtr); - Output_Meshes_DrawTriangles(vertex_count, vertices); - glBlendFunc(GL_ONE, GL_ZERO); - glDepthMask(GL_TRUE); - Memory_FreePointer(&vertices); -} - -void Output_DrawShadow( - const int16_t size, const BOUNDS_16 *const bounds, const ITEM *const item) -{ - if (!item->enable_shadow) { - return; - } - - const int32_t vertex_count = g_Config.visuals.enable_round_shadow ? 32 : 8; - const int32_t x0 = bounds->min.x; - const int32_t x1 = bounds->max.x; - const int32_t z0 = bounds->min.z; - const int32_t z1 = bounds->max.z; - const int32_t x_mid = (x0 + x1) / 2; - const int32_t z_mid = (z0 + z1) / 2; - const int32_t x_add = (x1 - x0) * size / 1024; - const int32_t z_add = (z1 - z0) * size / 1024; - - Matrix_Push(); - Matrix_TranslateAbs( - item->interp.result.pos.x, item->floor, item->interp.result.pos.z); - Matrix_RotY(item->rot.y); - - OUTPUT_MESH_VERTEX vertices[vertex_count * 3]; - for (int32_t i = 0; i < vertex_count; i++) { - for (int32_t j = 0; j < 2; j++) { - const int32_t angle = (DEG_180 + (i + j) * DEG_360) / vertex_count; - vertices[i * 3 + j].pos.x = - x_mid + ((x_add * 2) * Math_Sin(angle) >> W2V_SHIFT); - vertices[i * 3 + j].pos.z = - z_mid + ((z_add * 2) * Math_Cos(angle) >> W2V_SHIFT); - } - vertices[i * 3 + 2].pos.x = x_mid; - vertices[i * 3 + 2].pos.z = z_mid; - for (int32_t j = 0; j < 3; j++) { - vertices[i * 3 + j].pos.y = -5; - vertices[i * 3 + j].flags = - VERT_FLAT_SHADED | VERT_NO_LIGHTING | VERT_NO_CAUSTICS; - vertices[i * 3 + j].color = (RGBA_8888) { 0, 0, 0, 128 }; - } - } - - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - Output_Shader_UploadMatrix(Output_Meshes_GetShader(), g_MatrixPtr); - Output_Meshes_DrawTriangles(vertex_count * 3, vertices); - glBlendFunc(GL_ONE, GL_ZERO); - - Matrix_Pop(); -} - -void Output_DrawSprite( - const int32_t x, const int32_t y, const int32_t z, const int16_t sprite_idx, - const int16_t shade, const RGB_F tint) -{ - Matrix_Push(); - Matrix_TranslateAbs(x, y, z); - Output_Sprites_RenderSingleSprite( - g_MatrixPtr, (XYZ_32) { 0, 0, 0 }, sprite_idx, shade, tint); - Matrix_Pop(); -} diff --git a/src/tr1/game/output/func.c b/src/tr1/game/output/func.c deleted file mode 100644 index 85ee749ff..000000000 --- a/src/tr1/game/output/func.c +++ /dev/null @@ -1,147 +0,0 @@ -#include "game/output.h" -#include "game/output/meshes/common.h" -#include "game/output/sprites.h" -#include "game/viewport.h" -#include "global/vars.h" - -#include -#include - -#include - -static struct { - GLint bound_program; - GLint bound_vao; - GLint bound_vbo; - GLint bound_active_texture; - GLint bound_texture; - GLint bound_polygon_mode[2]; -} m_CachedState; - -void Output_ObserveFOVChange(void) -{ - Output_Meshes_UploadProjectionMatrix(); - Output_Sprites_UploadProjectionMatrix(); -} - -bool Output_MakeScreenshot(const char *const path) -{ - GFX_Context_ScheduleScreenshot(path); - return true; -} - -int32_t Output_GetObjectBounds(const BOUNDS_16 *const bounds) -{ - // TODO: remove - return 1; -} - -int32_t Output_CalcFogShade(const int32_t depth) -{ - // TODO: done in the shader - return 0; -} - -int32_t Output_GetRoomLightShade(const ROOM_LIGHT_MODE mode) -{ - // TODO: remove - ASSERT_FAIL(); - return 0; -} - -void Output_LightRoomVertices(const ROOM *const room) -{ - // TODO: remove - ASSERT_FAIL(); -} - -void Output_GetProjectionMatrix(GLfloat output[][4]) -{ - const float left = 0.0f; - const float top = 0.0f; - const float right = Viewport_GetWidth(); - const float bottom = Viewport_GetHeight(); - const float near = Output_GetNearZ() / (float)(1 << W2V_SHIFT); - const float far = Output_GetFarZ() / (float)(1 << W2V_SHIFT); - const float aspect = (float)right / (float)bottom; - const float fov = Viewport_GetFOV() * M_PI / (float)DEG_180; - const float f = 1.0f / tan(fov / 2.0f); - - output[0][0] = f / aspect; - output[0][1] = 0.0f; - output[0][2] = 0.0f; - output[0][3] = 0.0f; - - output[1][0] = 0.0f; - output[1][1] = -f; - output[1][2] = 0.0f; - output[1][3] = 0.0f; - - output[2][0] = 0.0f; - output[2][1] = 0.0f; - output[2][2] = g_FltResZBuf; - output[2][3] = -g_FltResZ / (float)(1 << W2V_SHIFT); - - output[3][0] = 0.0f; - output[3][1] = 0.0f; - output[3][2] = 1.0f; - output[3][3] = 0.0f; -} - -void Output_EnableScissor( - const float x, const float y, const float w, const float h) -{ - // Causes the rendering pipeline to discard every pixel outside of the - // specified window. The window is in game framebuffer viewport's - // coordinates; to make it work properly, we need to translate it to the - // SDL window coordinates first. - - const int32_t border = 2; // to deal with precision issues - - struct { - GLint x, y, w, h; - } game_viewport = { - .x = Viewport_GetMinX(), - .y = Viewport_GetMinY(), - .w = Viewport_GetWidth(), - .h = Viewport_GetHeight(), - }, gl_viewport, scissor; - glGetIntegerv(GL_VIEWPORT, &gl_viewport.x); - - const float scale_x = gl_viewport.w / (float)game_viewport.w; - const float scale_y = gl_viewport.h / (float)game_viewport.h; - scissor.x = gl_viewport.x + (x * scale_x) - border; - scissor.y = gl_viewport.y + (game_viewport.h - y) * scale_y - border; - scissor.w = w * scale_x + border * 2; - scissor.h = h * scale_y + border * 2; - - glEnable(GL_SCISSOR_TEST); - glScissor(scissor.x, scissor.y, scissor.w, scissor.h); -} - -void Output_DisableScissor(void) -{ - glDisable(GL_SCISSOR_TEST); -} - -void Output_RememberState(void) -{ - glGetIntegerv(GL_CURRENT_PROGRAM, &m_CachedState.bound_program); - glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &m_CachedState.bound_vao); - glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &m_CachedState.bound_vbo); - glGetIntegerv(GL_ACTIVE_TEXTURE, &m_CachedState.bound_active_texture); - glGetIntegerv(GL_TEXTURE_BINDING_2D, &m_CachedState.bound_texture); - glGetIntegerv(GL_POLYGON_MODE, &m_CachedState.bound_polygon_mode[0]); - GFX_GL_CheckError(); -} - -void Output_RestoreState(void) -{ - glUseProgram(m_CachedState.bound_program); - glBindVertexArray(m_CachedState.bound_vao); - glBindBuffer(GL_ARRAY_BUFFER, m_CachedState.bound_vbo); - glActiveTexture(m_CachedState.bound_active_texture); - glBindTexture(GL_TEXTURE_2D, m_CachedState.bound_texture); - glPolygonMode(GL_FRONT_AND_BACK, m_CachedState.bound_polygon_mode[0]); - GFX_GL_CheckError(); -} diff --git a/src/tr1/game/output/meshes/common.c b/src/tr1/game/output/meshes/common.c deleted file mode 100644 index e51178d36..000000000 --- a/src/tr1/game/output/meshes/common.c +++ /dev/null @@ -1,51 +0,0 @@ -#include "game/output/meshes/common.h" - -#include "game/output/meshes/dynamic.h" -#include "game/output/meshes/objects.h" -#include "game/output/meshes/rooms.h" - -static OUTPUT_SHADER *m_Shader = nullptr; - -void Output_Meshes_Init(void) -{ - m_Shader = Output_Shader_Create("shaders/meshes.glsl"); - Output_Meshes_InitDynamic(); - Output_Meshes_InitRooms(); - Output_Meshes_InitObjects(); -} - -void Output_Meshes_Shutdown(void) -{ - Output_Meshes_ShutdownObjects(); - Output_Meshes_ShutdownRooms(); - Output_Meshes_ShutdownDynamic(); - Output_Shader_Free(m_Shader); - m_Shader = nullptr; -} - -void Output_Meshes_ObserveLevelLoad(void) -{ - Output_Meshes_ObserveLevelLoadObjects(); - Output_Meshes_ObserveLevelLoadRooms(); -} - -void Output_Meshes_ObserveLevelUnload(void) -{ - Output_Meshes_ObserveLevelUnloadObjects(); -} - -void Output_Meshes_RenderBegin(void) -{ - Output_Shader_UploadCommonUniforms(m_Shader); - Output_Shader_UploadProjectionMatrix(m_Shader); -} - -OUTPUT_SHADER *Output_Meshes_GetShader(void) -{ - return m_Shader; -} - -void Output_Meshes_UploadProjectionMatrix(void) -{ - Output_Shader_UploadProjectionMatrix(m_Shader); -} diff --git a/src/tr1/game/output/meshes/common.h b/src/tr1/game/output/meshes/common.h deleted file mode 100644 index 332401060..000000000 --- a/src/tr1/game/output/meshes/common.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include "game/output/shader.h" -#include "game/output/textures.h" - -// clang-format off -#define VERT_NO_CAUSTICS 0b0000'0001 // = 0x01 -#define VERT_FLAT_SHADED 0b0000'0010 // = 0x02 -#define VERT_REFLECTIVE 0b0000'0100 // = 0x04 -#define VERT_NO_LIGHTING 0b0000'1000 // = 0x08 -#define VERT_SPRITE 0b0001'0000 // = 0x10 -// clang-format on - -typedef struct { - // attribute 2 - OUTPUT_UVW uvw; - // attribute 3 - OUTPUT_TEXTURE_SIZE texture_size; - // attribute 4 - float trapezoid_ratio[2]; -} OUTPUT_MESH_TEXTURE; - -void Output_Meshes_Init(void); -void Output_Meshes_Shutdown(void); -void Output_Meshes_UploadProjectionMatrix(void); -void Output_Meshes_ObserveLevelLoad(void); -void Output_Meshes_ObserveLevelUnload(void); -void Output_Meshes_RenderBegin(void); -OUTPUT_SHADER *Output_Meshes_GetShader(void); diff --git a/src/tr1/game/output/meshes/dynamic.c b/src/tr1/game/output/meshes/dynamic.c deleted file mode 100644 index ca57a8874..000000000 --- a/src/tr1/game/output/meshes/dynamic.c +++ /dev/null @@ -1,96 +0,0 @@ -#include "game/output/meshes/dynamic.h" - -#include "game/output/meshes/common.h" - -#include -#include - -#include - -static struct { - GLuint vao; - GLuint vbo; -} m_Priv; - -static OUTPUT_SHADER *m_Shader = nullptr; - -void Output_Meshes_InitDynamic(void) -{ - m_Shader = Output_Meshes_GetShader(); - - glGenVertexArrays(1, &m_Priv.vao); - glBindVertexArray(m_Priv.vao); - - glGenBuffers(1, &m_Priv.vbo); - glBindBuffer(GL_ARRAY_BUFFER, m_Priv.vbo); - glEnableVertexAttribArray(0); - glVertexAttribPointer( - 0, 3, GL_FLOAT, GL_FALSE, sizeof(OUTPUT_MESH_VERTEX), - (void *)(intptr_t)offsetof(OUTPUT_MESH_VERTEX, pos)); - - glEnableVertexAttribArray(1); - glVertexAttribPointer( - 1, 3, GL_FLOAT, GL_FALSE, sizeof(OUTPUT_MESH_VERTEX), - (void *)(intptr_t)offsetof(OUTPUT_MESH_VERTEX, normal)); - - glEnableVertexAttribArray(2); - glVertexAttribPointer( - 2, 3, GL_FLOAT, GL_FALSE, sizeof(OUTPUT_MESH_VERTEX), - (void *)(intptr_t)offsetof(OUTPUT_MESH_VERTEX, uvw)); - - glEnableVertexAttribArray(3); - glVertexAttribPointer( - 3, 4, GL_FLOAT, GL_FALSE, sizeof(OUTPUT_MESH_VERTEX), - (void *)(intptr_t)offsetof(OUTPUT_MESH_VERTEX, texture_size)); - - glEnableVertexAttribArray(4); - glVertexAttribPointer( - 4, 2, GL_FLOAT, GL_FALSE, sizeof(OUTPUT_MESH_VERTEX), - (void *)(intptr_t)offsetof(OUTPUT_MESH_VERTEX, trapezoid_ratio)); - - glEnableVertexAttribArray(5); - glVertexAttribIPointer( - 5, 1, GL_UNSIGNED_SHORT, sizeof(OUTPUT_MESH_VERTEX), - (void *)(intptr_t)offsetof(OUTPUT_MESH_VERTEX, flags)); - - glEnableVertexAttribArray(6); - glVertexAttribPointer( - 6, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(OUTPUT_MESH_VERTEX), - (void *)(intptr_t)offsetof(OUTPUT_MESH_VERTEX, color)); - - glEnableVertexAttribArray(7); - glVertexAttribPointer( - 7, 1, GL_UNSIGNED_SHORT, GL_FALSE, sizeof(OUTPUT_MESH_VERTEX), - (void *)(intptr_t)offsetof(OUTPUT_MESH_VERTEX, shade)); -} - -void Output_Meshes_ShutdownDynamic(void) -{ - if (m_Priv.vao != 0) { - glDeleteVertexArrays(1, &m_Priv.vao); - m_Priv.vao = 0; - } - if (m_Priv.vbo != 0) { - glDeleteBuffers(1, &m_Priv.vbo); - m_Priv.vbo = 0; - } -} - -void Output_Meshes_DrawPrimitives( - const GLuint prim_type, const int32_t count, - const OUTPUT_MESH_VERTEX *const vertices) -{ - Output_Shader_Bind(m_Shader); - glBindBuffer(GL_ARRAY_BUFFER, m_Priv.vbo); - GFX_TRACK_DATA( - glBufferData, GL_ARRAY_BUFFER, count * sizeof(OUTPUT_MESH_VERTEX), - vertices, GL_STATIC_DRAW); - glBindVertexArray(m_Priv.vao); - glDrawArrays(prim_type, 0, count); -} - -void Output_Meshes_DrawTriangles( - const int32_t count, const OUTPUT_MESH_VERTEX *const vertices) -{ - Output_Meshes_DrawPrimitives(GL_TRIANGLES, count, vertices); -} diff --git a/src/tr1/game/output/meshes/dynamic.h b/src/tr1/game/output/meshes/dynamic.h deleted file mode 100644 index a258fd334..000000000 --- a/src/tr1/game/output/meshes/dynamic.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include "game/output/shader.h" -#include "game/output/textures.h" - -#include -#include -#include - -#pragma pack(push, 1) -typedef struct { - // clang-format off - XYZ_F pos; // attribute 0 - XYZ_F normal; // attribute 1 - OUTPUT_UVW uvw; // attribute 2 - OUTPUT_TEXTURE_SIZE texture_size; // attribute 3 - float trapezoid_ratio[2]; // attribute 4 - uint16_t flags; // attribute 5 - RGBA_8888 color; // attribute 6 - int16_t shade; // attribute 7 - // clang-format on -} OUTPUT_MESH_VERTEX; -#pragma pack(pop) - -void Output_Meshes_InitDynamic(void); -void Output_Meshes_ShutdownDynamic(void); - -void Output_Meshes_DrawPrimitives( - GLuint prim_type, int32_t count, const OUTPUT_MESH_VERTEX *vertices); -void Output_Meshes_DrawTriangles( - int32_t count, const OUTPUT_MESH_VERTEX *vertices); diff --git a/src/tr1/game/output/meshes/objects.c b/src/tr1/game/output/meshes/objects.c deleted file mode 100644 index 18ca7c173..000000000 --- a/src/tr1/game/output/meshes/objects.c +++ /dev/null @@ -1,586 +0,0 @@ -#include "game/output/meshes/objects.h" - -#include "game/output.h" -#include "game/output/meshes/common.h" -#include "game/output/textures.h" -#include "game/output/utils.h" -#include "game/output/vertex_range.h" - -#include -#include -#include -#include -#include - -#pragma pack(push, 1) -typedef struct { - // attribute 0 - XYZ_F pos; - // attribute 1 - XYZ_F normal; - // attribute 5 - uint16_t flags; - // attribute 6 - RGBA_8888 color; - // clang-format on -} M_MESH_VERTEX; - -// attribute 7 -typedef int16_t M_MESH_SHADE; -#pragma pack(pop) - -typedef struct { - int32_t vertex_start; - int32_t vertex_count; - VECTOR *animated_vertices; -} M_BATCH; - -static struct { - size_t vertex_count; - GLuint vao; - GLuint geom_vbo; - GLuint tex_vbo; - GLuint shade_vbo; - M_MESH_VERTEX *geom_vbo_data; - OUTPUT_MESH_TEXTURE *tex_vbo_data; - M_MESH_SHADE *shade_vbo_data; - int32_t *light_idx_map; - size_t batch_count; - M_BATCH *batches; -} m_Priv; - -static OUTPUT_SHADER *m_Shader = nullptr; - -static M_BATCH *M_GetBatch(const OBJECT_MESH *const mesh) -{ - const int16_t mesh_num = Object_GetMeshIndex(mesh); - return &m_Priv.batches[mesh_num]; -} - -static uint16_t M_GetFlags(const OBJECT_MESH *const mesh, const bool flat) -{ - uint16_t flags = 0; - if (flat) { - flags |= VERT_FLAT_SHADED; - } - if (mesh->enable_reflections) { - flags |= VERT_REFLECTIVE; - } - if (mesh->disable_lighting) { - flags |= VERT_NO_LIGHTING; - } - return flags; -} - -static void M_FillTexture( - OUTPUT_MESH_TEXTURE *const out_texture, const int32_t uvw_idx, - const float trapezoid_ratio_x, const float trapezoid_ratio_y) -{ - out_texture->uvw = Output_Textures_GetUVW(uvw_idx); - out_texture->texture_size = Output_Textures_GetAtlasSize(uvw_idx / 4); - out_texture->trapezoid_ratio[0] = trapezoid_ratio_x; - out_texture->trapezoid_ratio[1] = trapezoid_ratio_y; -} - -static void M_FillVertex( - M_MESH_VERTEX *out_vertex, const int32_t uvw_idx, - const OBJECT_MESH *const mesh, const int32_t vertex_idx, - const int32_t palette_idx) -{ - const XYZ_16 pos = mesh->vertices[vertex_idx]; - const XYZ_16 normal = mesh->lighting.normals[vertex_idx]; - out_vertex->pos = (XYZ_F) { .x = pos.x, .y = pos.y, .z = pos.z }; - out_vertex->normal = - (XYZ_F) { .x = normal.x, .y = -normal.y, .z = normal.z }; - out_vertex->flags = M_GetFlags(mesh, palette_idx >= 0); - if (palette_idx >= 0) { - out_vertex->color = - Output_RGB2RGBA(Output_GetPaletteColor8(palette_idx)); - } else { - out_vertex->color = (RGBA_8888) { 255, 255, 255, 255 }; - } -} - -static void M_FillFace4( - const OBJECT_MESH *const mesh, M_MESH_VERTEX **out_vertex, - OUTPUT_MESH_TEXTURE **out_texture, const FACE4 *const face, const bool flat) -{ - for (int32_t i = 0; i < OUTPUT_QUAD_VERTICES; i++) { - const int32_t j = OUTPUT_QUAD_TO_FAN(i); - const int32_t uvw_idx = face->texture_idx * 4 + j; - M_FillVertex( - *out_vertex, uvw_idx, mesh, face->vertices[j], - flat ? face->palette_idx : -1); - M_FillTexture( - *out_texture, uvw_idx, face->texture_zw[j].z, - face->texture_zw[j].w); - (*out_vertex)++; - (*out_texture)++; - } -} - -static void M_FillFace3( - const OBJECT_MESH *const mesh, M_MESH_VERTEX **out_vertex, - OUTPUT_MESH_TEXTURE **out_texture, const FACE3 *const face, const bool flat) -{ - const M_BATCH *const batch = M_GetBatch(mesh); - for (int32_t i = 0; i < OUTPUT_TRI_VERTICES; i++) { - const int32_t j = OUTPUT_TRI_TO_FAN(i); - const int32_t uvw_idx = face->texture_idx * 4 + j; - M_FillVertex( - *out_vertex, uvw_idx, mesh, face->vertices[j], - flat ? face->palette_idx : -1); - M_FillTexture(*out_texture, uvw_idx, 1.0f, 1.0f); - (*out_vertex)++; - (*out_texture)++; - } -} - -static void M_UpdateGeometry(const OBJECT_MESH *const mesh) -{ - const M_BATCH *const batch = M_GetBatch(mesh); - M_MESH_VERTEX *out_vertex = &m_Priv.geom_vbo_data[batch->vertex_start]; - OUTPUT_MESH_TEXTURE *out_texture = - &m_Priv.tex_vbo_data[batch->vertex_start]; - - for (int32_t i = 0; i < mesh->num_tex_face4s; i++) { - M_FillFace4( - mesh, &out_vertex, &out_texture, &mesh->tex_face4s[i], false); - } - for (int32_t i = 0; i < mesh->num_tex_face3s; i++) { - M_FillFace3( - mesh, &out_vertex, &out_texture, &mesh->tex_face3s[i], false); - } - for (int32_t i = 0; i < mesh->num_flat_face4s; i++) { - M_FillFace4( - mesh, &out_vertex, &out_texture, &mesh->flat_face4s[i], true); - } - for (int32_t i = 0; i < mesh->num_flat_face3s; i++) { - M_FillFace3( - mesh, &out_vertex, &out_texture, &mesh->flat_face3s[i], true); - } - -#define SET(v, j) light_idx_map[v] = face->vertices[j]; - int32_t *const light_idx_map = &m_Priv.light_idx_map[batch->vertex_start]; - int32_t v = 0; - for (int32_t i = 0; i < mesh->num_tex_face4s; i++) { - const FACE4 *const face = &mesh->tex_face4s[i]; - for (int32_t j = 0; j < OUTPUT_QUAD_VERTICES; j++) { - SET(v, OUTPUT_QUAD_TO_FAN(j)); - v++; - } - } - for (int32_t i = 0; i < mesh->num_tex_face3s; i++) { - const FACE3 *const face = &mesh->tex_face3s[i]; - for (int32_t j = 0; j < OUTPUT_TRI_VERTICES; j++) { - SET(v, OUTPUT_TRI_TO_FAN(j)); - v++; - } - } - for (int32_t i = 0; i < mesh->num_flat_face4s; i++) { - const FACE4 *const face = &mesh->flat_face4s[i]; - for (int32_t j = 0; j < OUTPUT_QUAD_VERTICES; j++) { - SET(v, OUTPUT_QUAD_TO_FAN(j)); - v++; - } - } - for (int32_t i = 0; i < mesh->num_flat_face3s; i++) { - const FACE3 *const face = &mesh->flat_face3s[i]; - for (int32_t j = 0; j < OUTPUT_TRI_VERTICES; j++) { - SET(v, OUTPUT_TRI_TO_FAN(j)); - v++; - } - } -#undef SET -} - -static void M_UpdateShades( - const MATRIX *const matrix, const OBJECT_MESH *const mesh) -{ - M_BATCH *const batch = M_GetBatch(mesh); - M_MESH_SHADE *const out_shades = - &m_Priv.shade_vbo_data[batch->vertex_start]; - int32_t *const light_idx_map = &m_Priv.light_idx_map[batch->vertex_start]; - - const int32_t ls_adder = Output_GetLightAdder(); - const int32_t ls_divider = Output_GetLightDivider(); - const XYZ_32 ls_vector_view = Output_GetLightVectorView(); - - if (mesh->num_lights <= 0) { - for (int32_t i = 0; i < batch->vertex_count; i++) { - const int32_t j = light_idx_map[i]; - int16_t shade = ls_adder + mesh->lighting.lights[j]; - CLAMP(shade, 0, SHADE_MAX); - out_shades[i] = shade; - } - } else if (ls_divider == 0) { - int16_t shade = ls_adder; - CLAMP(shade, 0, SHADE_MAX); - for (int32_t i = 0; i < batch->vertex_count; i++) { - out_shades[i] = shade; - } - } else { - // clang-format off - const int32_t xv = ( - matrix->_00 * ls_vector_view.x + - matrix->_10 * ls_vector_view.y + - matrix->_20 * ls_vector_view.z - ) / ls_divider; - - const int32_t yv = ( - matrix->_01 * ls_vector_view.x + - matrix->_11 * ls_vector_view.y + - matrix->_21 * ls_vector_view.z - ) / ls_divider; - - const int32_t zv = ( - matrix->_02 * ls_vector_view.x + - matrix->_12 * ls_vector_view.y + - matrix->_22 * ls_vector_view.z - ) / ls_divider; - // clang-format on - - for (int32_t i = 0; i < batch->vertex_count; i++) { - const int32_t j = light_idx_map[i]; - const XYZ_16 *const normal = &mesh->lighting.normals[j]; - int16_t shade = ls_adder - + ((normal->x * xv + normal->y * yv + normal->z * zv) >> 16); - CLAMP(shade, 0, SHADE_MAX); - out_shades[i] = shade; - } - } -} - -static void M_UpdateVertices(void) -{ - for (int32_t i = 0; i < Object_GetMeshCount(); i++) { - M_UpdateGeometry(Object_GetMesh(i)); - } - - glBindBuffer(GL_ARRAY_BUFFER, m_Priv.geom_vbo); - GFX_TRACK_DATA( - glBufferData, GL_ARRAY_BUFFER, - m_Priv.vertex_count * sizeof(M_MESH_VERTEX), m_Priv.geom_vbo_data, - GL_STATIC_DRAW); - - glBindBuffer(GL_ARRAY_BUFFER, m_Priv.tex_vbo); - GFX_TRACK_DATA( - glBufferData, GL_ARRAY_BUFFER, - m_Priv.vertex_count * sizeof(OUTPUT_MESH_TEXTURE), m_Priv.tex_vbo_data, - GL_DYNAMIC_DRAW); - - glBindBuffer(GL_ARRAY_BUFFER, m_Priv.shade_vbo); - GFX_TRACK_DATA( - glBufferData, GL_ARRAY_BUFFER, - m_Priv.vertex_count * sizeof(M_MESH_SHADE), m_Priv.shade_vbo_data, - GL_DYNAMIC_DRAW); // shades are always dynamic -} - -static void M_PrepareBuffers(void) -{ - m_Priv.batch_count = Object_GetMeshCount(); - m_Priv.batches = - Memory_Realloc(m_Priv.batches, sizeof(M_BATCH) * m_Priv.batch_count); - - int32_t last_vertex = 0; - for (int32_t i = 0; i < Object_GetMeshCount(); i++) { - const OBJECT_MESH *const obj_mesh = Object_GetMesh(i); - M_BATCH *const batch = &m_Priv.batches[i]; - batch->vertex_start = last_vertex; - batch->vertex_count = 0; - batch->vertex_count += obj_mesh->num_tex_face4s * OUTPUT_QUAD_VERTICES; - batch->vertex_count += obj_mesh->num_tex_face3s * OUTPUT_TRI_VERTICES; - batch->vertex_count += obj_mesh->num_flat_face4s * OUTPUT_QUAD_VERTICES; - batch->vertex_count += obj_mesh->num_flat_face3s * OUTPUT_TRI_VERTICES; - last_vertex += batch->vertex_count; - } - m_Priv.vertex_count = last_vertex; - - int32_t current_vertex = 0; - for (int32_t i = 0; i < Object_GetMeshCount(); i++) { - const OBJECT_MESH *const obj_mesh = Object_GetMesh(i); - M_BATCH *const batch = &m_Priv.batches[i]; - batch->animated_vertices = Vector_Create(sizeof(OUTPUT_VERTEX_RANGE)); - for (int32_t j = 0; j < obj_mesh->num_tex_face4s; j++) { - const FACE4 *const face = &obj_mesh->tex_face4s[j]; - if (Output_Textures_IsObjectTextureAnimated(face->texture_idx)) { - Vector_Add( - batch->animated_vertices, - &(OUTPUT_VERTEX_RANGE) { - .vertex_start = current_vertex, - .vertex_count = OUTPUT_QUAD_VERTICES, - }); - } - current_vertex += OUTPUT_QUAD_VERTICES; - } - for (int32_t j = 0; j < obj_mesh->num_tex_face3s; j++) { - const FACE3 *const face = &obj_mesh->tex_face3s[j]; - if (Output_Textures_IsObjectTextureAnimated(face->texture_idx)) { - Vector_Add( - batch->animated_vertices, - &(OUTPUT_VERTEX_RANGE) { - .vertex_start = current_vertex, - .vertex_count = OUTPUT_TRI_VERTICES, - }); - } - current_vertex += OUTPUT_TRI_VERTICES; - } - current_vertex += obj_mesh->num_flat_face4s * OUTPUT_QUAD_VERTICES; - current_vertex += obj_mesh->num_flat_face3s * OUTPUT_TRI_VERTICES; - Output_GlueVertexRanges(batch->animated_vertices); - } - - m_Priv.geom_vbo_data = - Memory_Alloc(m_Priv.vertex_count * sizeof(M_MESH_VERTEX)); - m_Priv.tex_vbo_data = - Memory_Alloc(m_Priv.vertex_count * sizeof(OUTPUT_MESH_TEXTURE)); - m_Priv.shade_vbo_data = - Memory_Alloc(m_Priv.vertex_count * sizeof(M_MESH_SHADE)); - m_Priv.light_idx_map = Memory_Alloc(m_Priv.vertex_count * sizeof(int32_t)); - - glGenVertexArrays(1, &m_Priv.vao); - glGenBuffers(1, &m_Priv.geom_vbo); - glGenBuffers(1, &m_Priv.tex_vbo); - glGenBuffers(1, &m_Priv.shade_vbo); - - glBindVertexArray(m_Priv.vao); - - glBindBuffer(GL_ARRAY_BUFFER, m_Priv.geom_vbo); - // attribute 0: position - glEnableVertexAttribArray(0); - glVertexAttribPointer( - 0, 3, GL_FLOAT, GL_FALSE, sizeof(M_MESH_VERTEX), - (void *)(intptr_t)offsetof(M_MESH_VERTEX, pos)); - - // attribute 1: normal - glEnableVertexAttribArray(1); - glVertexAttribPointer( - 1, 3, GL_FLOAT, GL_FALSE, sizeof(M_MESH_VERTEX), - (void *)(intptr_t)offsetof(M_MESH_VERTEX, normal)); - - // attribute 5: flags - glEnableVertexAttribArray(5); - glVertexAttribIPointer( - 5, 1, GL_UNSIGNED_SHORT, sizeof(M_MESH_VERTEX), - (void *)(intptr_t)offsetof(M_MESH_VERTEX, flags)); - - // attribute 6: color - glEnableVertexAttribArray(6); - glVertexAttribPointer( - 6, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(M_MESH_VERTEX), - (void *)(intptr_t)offsetof(M_MESH_VERTEX, color)); - - glBindBuffer(GL_ARRAY_BUFFER, m_Priv.tex_vbo); - // attribute 2: uvw - glEnableVertexAttribArray(2); - glVertexAttribPointer( - 2, 3, GL_FLOAT, GL_FALSE, sizeof(OUTPUT_MESH_TEXTURE), - (void *)(intptr_t)offsetof(OUTPUT_MESH_TEXTURE, uvw)); - - // attribute 3: texture size - glEnableVertexAttribArray(3); - glVertexAttribPointer( - 3, 4, GL_FLOAT, GL_FALSE, sizeof(OUTPUT_MESH_TEXTURE), - (void *)(intptr_t)offsetof(OUTPUT_MESH_TEXTURE, texture_size)); - - // attribute 4: trapezoid ratios - glEnableVertexAttribArray(4); - glVertexAttribPointer( - 4, 2, GL_FLOAT, GL_FALSE, sizeof(OUTPUT_MESH_TEXTURE), - (void *)(intptr_t)offsetof(OUTPUT_MESH_TEXTURE, trapezoid_ratio)); - - glBindBuffer(GL_ARRAY_BUFFER, m_Priv.shade_vbo); - - // attribute 7: shade - glEnableVertexAttribArray(7); - glVertexAttribPointer( - 7, 1, GL_UNSIGNED_SHORT, GL_FALSE, sizeof(M_MESH_SHADE), 0); - - M_UpdateVertices(); -} - -static void M_FreeBuffers(void) -{ - glBindVertexArray(0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - if (m_Priv.vao != 0) { - glDeleteVertexArrays(1, &m_Priv.vao); - m_Priv.vao = 0; - } - if (m_Priv.geom_vbo != 0) { - glDeleteBuffers(1, &m_Priv.geom_vbo); - m_Priv.geom_vbo = 0; - } - if (m_Priv.tex_vbo != 0) { - glDeleteBuffers(1, &m_Priv.tex_vbo); - m_Priv.tex_vbo = 0; - } - if (m_Priv.shade_vbo != 0) { - glDeleteBuffers(1, &m_Priv.shade_vbo); - m_Priv.shade_vbo = 0; - } - Memory_FreePointer(&m_Priv.geom_vbo_data); - Memory_FreePointer(&m_Priv.tex_vbo_data); - Memory_FreePointer(&m_Priv.shade_vbo_data); - Memory_FreePointer(&m_Priv.light_idx_map); - for (int32_t i = 0; i < (int32_t)m_Priv.batch_count; i++) { - Vector_Free(m_Priv.batches[i].animated_vertices); - } - Memory_FreePointer(&m_Priv.batches); -} - -void Output_Meshes_InitObjects(void) -{ - m_Shader = Output_Meshes_GetShader(); -} - -void Output_Meshes_ShutdownObjects(void) -{ - M_FreeBuffers(); -} - -void Output_Meshes_ObserveLevelLoadObjects(void) -{ - M_PrepareBuffers(); -} - -void Output_Meshes_ObserveLevelUnloadObjects(void) -{ - M_FreeBuffers(); -} - -static void M_FillAnimatedTextures(const OBJECT_MESH *const obj_mesh) -{ - const M_BATCH *const batch = M_GetBatch(obj_mesh); - OUTPUT_MESH_TEXTURE *out_texture = - &m_Priv.tex_vbo_data[batch->vertex_start]; - for (int32_t j = 0; j < obj_mesh->num_tex_face4s; j++) { - const FACE4 *const face = &obj_mesh->tex_face4s[j]; - if (!Output_Textures_IsObjectTextureAnimated(face->texture_idx)) { - out_texture += OUTPUT_QUAD_VERTICES; - continue; - } - for (int32_t k = 0; k < OUTPUT_QUAD_VERTICES; k++) { - const int32_t l = OUTPUT_QUAD_TO_FAN(k); - const int32_t uvw_idx = face->texture_idx * 4 + l; - M_FillTexture( - out_texture, uvw_idx, face->texture_zw[l].z, - face->texture_zw[l].w); - out_texture++; - } - } - for (int32_t j = 0; j < obj_mesh->num_tex_face3s; j++) { - const FACE3 *const face = &obj_mesh->tex_face3s[j]; - if (!Output_Textures_IsObjectTextureAnimated(face->texture_idx)) { - out_texture += OUTPUT_TRI_VERTICES; - continue; - } - for (int32_t k = 0; k < OUTPUT_TRI_VERTICES; k++) { - const int32_t l = OUTPUT_TRI_TO_FAN(k); - const int32_t uvw_idx = face->texture_idx * 4 + l; - M_FillTexture(out_texture, uvw_idx, 1.0f, 1.0f); - out_texture++; - } - } -} - -static void M_UpdateAnimedTextures(const OBJECT_MESH *const obj_mesh) -{ - const M_BATCH *const batch = M_GetBatch(obj_mesh); - glBindBuffer(GL_ARRAY_BUFFER, m_Priv.tex_vbo); - for (int32_t i = 0; i < batch->animated_vertices->count; i++) { - const OUTPUT_VERTEX_RANGE *const range = - Vector_Get(batch->animated_vertices, i); - GFX_TRACK_DATA( - glBufferSubData, GL_ARRAY_BUFFER, - range->vertex_start * sizeof(OUTPUT_MESH_TEXTURE), - range->vertex_count * sizeof(OUTPUT_MESH_TEXTURE), - &m_Priv.tex_vbo_data[range->vertex_start]); - } -} - -void Output_Meshes_ObserveTextureAnimationObjects(void) -{ - for (int32_t i = 0; i < Object_GetMeshCount(); i++) { - const OBJECT_MESH *const obj_mesh = Object_GetMesh(i); - M_FillAnimatedTextures(obj_mesh); - M_UpdateAnimedTextures(obj_mesh); - } -} - -void Output_Meshes_ObserveObjectMeshSwap( - const OBJECT_MESH *const mesh_1, const OBJECT_MESH *const mesh_2) -{ - if (m_Priv.geom_vbo_data == nullptr) { - return; - } - - M_BATCH *const batch_1 = M_GetBatch(mesh_1); - M_BATCH *const batch_2 = M_GetBatch(mesh_2); - const M_BATCH batch_tmp = *batch_1; - *batch_1 = *batch_2; - *batch_2 = batch_tmp; - Output_Meshes_ObserveObjectMeshUpdate(mesh_1); - Output_Meshes_ObserveObjectMeshUpdate(mesh_2); -} - -void Output_Meshes_ObserveObjectMeshUpdate(const OBJECT_MESH *const mesh) -{ - if (m_Priv.geom_vbo_data == nullptr) { - return; - } - - const M_BATCH *const batch = M_GetBatch(mesh); - M_UpdateGeometry(mesh); - glBindBuffer(GL_ARRAY_BUFFER, m_Priv.geom_vbo); - GFX_TRACK_SUBDATA( - glBufferSubData, GL_ARRAY_BUFFER, - batch->vertex_start * sizeof(M_MESH_VERTEX), - batch->vertex_count * sizeof(M_MESH_VERTEX), - &m_Priv.geom_vbo_data[batch->vertex_start]); - GFX_GL_CheckError(); - - glBindBuffer(GL_ARRAY_BUFFER, m_Priv.tex_vbo); - GFX_TRACK_SUBDATA( - glBufferSubData, GL_ARRAY_BUFFER, - batch->vertex_start * sizeof(OUTPUT_MESH_TEXTURE), - batch->vertex_count * sizeof(OUTPUT_MESH_TEXTURE), - &m_Priv.tex_vbo_data[batch->vertex_start]); - GFX_GL_CheckError(); -} - -void Output_Meshes_RenderObjectMesh( - const MATRIX *const matrix, const RGB_F tint, const OBJECT_MESH *const mesh) -{ - const M_BATCH *const batch = M_GetBatch(mesh); - - M_UpdateShades(matrix, mesh); - - Output_Shader_UploadMatrix(m_Shader, matrix); - Output_Shader_UploadTint(m_Shader, tint); - GFX_GL_CheckError(); - - glBindBuffer(GL_ARRAY_BUFFER, m_Priv.shade_vbo); - GFX_TRACK_SUBDATA( - glBufferSubData, GL_ARRAY_BUFFER, - batch->vertex_start * sizeof(M_MESH_SHADE), - batch->vertex_count * sizeof(M_MESH_SHADE), - &m_Priv.shade_vbo_data[batch->vertex_start]); - GFX_GL_CheckError(); - - glEnable(GL_CULL_FACE); - glBindVertexArray(m_Priv.vao); - GFX_GL_CheckError(); - - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D_ARRAY, Output_Textures_GetAtlasTexture()); - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, Output_Textures_GetEnvMapTexture()); - GFX_GL_CheckError(); - - Output_Shader_UploadWibbleEffect(m_Shader, false); - glDrawArrays(GL_TRIANGLES, batch->vertex_start, batch->vertex_count); - - glDisable(GL_CULL_FACE); - GFX_GL_CheckError(); -} diff --git a/src/tr1/game/output/meshes/objects.h b/src/tr1/game/output/meshes/objects.h deleted file mode 100644 index afb53e0f9..000000000 --- a/src/tr1/game/output/meshes/objects.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -void Output_Meshes_InitObjects(void); -void Output_Meshes_ShutdownObjects(void); -void Output_Meshes_ObserveLevelLoadObjects(void); -void Output_Meshes_ObserveLevelUnloadObjects(void); -void Output_Meshes_ObserveTextureAnimationObjects(void); - -void Output_Meshes_RenderObjectMesh( - const MATRIX *matrix, RGB_F tint, const OBJECT_MESH *mesh); diff --git a/src/tr1/game/output/meshes/rooms.c b/src/tr1/game/output/meshes/rooms.c deleted file mode 100644 index 5ebf749a4..000000000 --- a/src/tr1/game/output/meshes/rooms.c +++ /dev/null @@ -1,468 +0,0 @@ -#include "game/output/meshes/rooms.h" - -#include "game/output.h" -#include "game/output/meshes/common.h" -#include "game/output/textures.h" -#include "game/output/utils.h" -#include "game/output/vertex_range.h" -#include "game/random.h" -#include "game/room.h" - -#include -#include -#include - -#pragma pack(push, 1) -typedef struct { - // attribute 0 - XYZ_F pos; - // attribute 5 - uint16_t flags; -} M_MESH_VERTEX; - -// attribute 7 -typedef int16_t M_MESH_SHADE; -#pragma pack(pop) - -typedef struct { - int32_t vertex_start; - int32_t vertex_count; - int16_t *caustics; - VECTOR *animated_vertices; -} M_BATCH; - -static struct { - size_t vertex_count; - GLuint vao; - GLuint geom_vbo; - GLuint tex_vbo; - GLuint shade_vbo; - M_MESH_VERTEX *geom_vbo_data; - OUTPUT_MESH_TEXTURE *tex_vbo_data; - M_MESH_SHADE *shade_vbo_data; - size_t batch_count; - M_BATCH *batches; -} m_Priv; - -static int32_t m_ShadeTable[WIBBLE_SIZE] = {}; -static int32_t m_CausticsTable[WIBBLE_SIZE] = {}; -static OUTPUT_SHADER *m_Shader = nullptr; - -static M_MESH_SHADE M_ShadeCaustics(M_MESH_SHADE source, uint8_t caustic) -{ - if (Output_GetWaterEffect()) { - source += - m_ShadeTable[((uint8_t)Output_GetTime() + caustic) % WIBBLE_SIZE]; - CLAMP(source, 0, SHADE_MAX); - } else { - CLAMPG(source, SHADE_MAX); - } - return source; -} - -static M_BATCH *M_GetBatch(const ROOM *const room) -{ - // Room data gets swapped when flipping, but the VBOs do not. So a room 2 - // that gets flipped to room 17 ends up getting the data from room 2, - // whereas the VBO needs to take data from room 17. - const int16_t room_num = - Room_GetFlipStatus() && room->flipped_room != NO_ROOM_NEG - ? room->flipped_room - : Room_GetNumber(room); - return &m_Priv.batches[room_num]; -} - -static void M_FillVertex( - M_MESH_VERTEX *const out_vertex, const XYZ_16 pos, const uint16_t flags) -{ - out_vertex->pos = (XYZ_F) { .x = pos.x, .y = pos.y, .z = pos.z }; - out_vertex->flags = flags & NO_VERT_MOVE ? VERT_NO_CAUSTICS : 0; -} - -static void M_FillTexture( - OUTPUT_MESH_TEXTURE *const out_texture, const int32_t uvw_idx, - const float trapezoid_ratio_x, const float trapezoid_ratio_y) -{ - out_texture->uvw = Output_Textures_GetUVW(uvw_idx); - out_texture->texture_size = Output_Textures_GetAtlasSize(uvw_idx / 4); - out_texture->trapezoid_ratio[0] = trapezoid_ratio_x; - out_texture->trapezoid_ratio[1] = trapezoid_ratio_y; -} - -static void M_FillRoomFace4( - const ROOM *const room, M_MESH_VERTEX **out_vertex, - OUTPUT_MESH_TEXTURE **out_texture, const FACE4 *const face) -{ - for (int32_t i = 0; i < OUTPUT_QUAD_VERTICES; i++) { - const int32_t j = OUTPUT_QUAD_TO_FAN(i); - const int32_t uvw_idx = face->texture_idx * 4 + j; - const ROOM_VERTEX *const room_vertex = - &room->mesh.vertices[face->vertices[j]]; - M_FillVertex(*out_vertex, room_vertex->pos, room_vertex->flags); - M_FillTexture( - *out_texture, uvw_idx, face->texture_zw[j].z, - face->texture_zw[j].w); - (*out_vertex)++; - (*out_texture)++; - } -} - -static void M_FillRoomFace3( - const ROOM *const room, M_MESH_VERTEX **out_vertex, - OUTPUT_MESH_TEXTURE **out_texture, const FACE3 *const face) -{ - for (int32_t i = 0; i < OUTPUT_TRI_VERTICES; i++) { - const int32_t j = OUTPUT_TRI_TO_FAN(i); - const int32_t uvw_idx = face->texture_idx * 4 + j; - const ROOM_VERTEX *const room_vertex = - &room->mesh.vertices[face->vertices[j]]; - M_FillVertex(*out_vertex, room_vertex->pos, room_vertex->flags); - M_FillTexture(*out_texture, uvw_idx, 1.0f, 1.0f); - (*out_vertex)++; - (*out_texture)++; - } -} - -static void M_UpdateRoomGeometry(const ROOM *const room) -{ - M_BATCH *const batch = &m_Priv.batches[Room_GetNumber(room)]; - M_MESH_VERTEX *out_vertex = &m_Priv.geom_vbo_data[batch->vertex_start]; - OUTPUT_MESH_TEXTURE *out_texture = - &m_Priv.tex_vbo_data[batch->vertex_start]; - for (int32_t i = 0; i < room->mesh.num_face4s; i++) { - M_FillRoomFace4(room, &out_vertex, &out_texture, &room->mesh.face4s[i]); - } - for (int32_t i = 0; i < room->mesh.num_face3s; i++) { - M_FillRoomFace3(room, &out_vertex, &out_texture, &room->mesh.face3s[i]); - } -} - -static void M_UpdateRoomShades(const ROOM *const room) -{ - M_BATCH *const batch = M_GetBatch(room); - M_MESH_SHADE *out_shade = &m_Priv.shade_vbo_data[batch->vertex_start]; - for (int32_t i = 0; i < room->mesh.num_face4s; i++) { - const FACE4 *const face = &room->mesh.face4s[i]; - for (int32_t j = 0; j < OUTPUT_QUAD_VERTICES; j++) { - const int32_t k = OUTPUT_QUAD_TO_FAN(j); - *out_shade = room->mesh.vertices[face->vertices[k]].light_adder; - *out_shade = - M_ShadeCaustics(*out_shade, batch->caustics[face->vertices[k]]); - *out_shade++; - } - } - for (int32_t i = 0; i < room->mesh.num_face3s; i++) { - const FACE3 *const face = &room->mesh.face3s[i]; - for (int32_t j = 0; j < OUTPUT_TRI_VERTICES; j++) { - const int32_t k = OUTPUT_TRI_TO_FAN(j); - *out_shade = room->mesh.vertices[face->vertices[k]].light_adder; - *out_shade = - M_ShadeCaustics(*out_shade, batch->caustics[face->vertices[k]]); - out_shade++; - } - } -} - -static void M_UpdateVertices(void) -{ - for (int32_t i = 0; i < Room_GetCount(); i++) { - const ROOM *const room = Room_Get(i); - M_UpdateRoomGeometry(room); - } - - glBindBuffer(GL_ARRAY_BUFFER, m_Priv.geom_vbo); - GFX_TRACK_DATA( - glBufferData, GL_ARRAY_BUFFER, - m_Priv.vertex_count * sizeof(M_MESH_VERTEX), m_Priv.geom_vbo_data, - GL_STATIC_DRAW); - - glBindBuffer(GL_ARRAY_BUFFER, m_Priv.tex_vbo); - GFX_TRACK_DATA( - glBufferData, GL_ARRAY_BUFFER, - m_Priv.vertex_count * sizeof(OUTPUT_MESH_TEXTURE), m_Priv.tex_vbo_data, - GL_DYNAMIC_DRAW); // allow animating textures - - glBindBuffer(GL_ARRAY_BUFFER, m_Priv.shade_vbo); - GFX_TRACK_DATA( - glBufferData, GL_ARRAY_BUFFER, - m_Priv.vertex_count * sizeof(M_MESH_SHADE), m_Priv.shade_vbo_data, - GL_DYNAMIC_DRAW); // shades are always dynamic -} - -static void M_PrepareBuffers(void) -{ - m_Priv.batch_count = Room_GetCount(); - Memory_FreePointer(&m_Priv.batches); - m_Priv.batches = Memory_Alloc(sizeof(M_BATCH) * m_Priv.batch_count); - - int32_t last_vertex = 0; - for (int32_t i = 0; i < Room_GetCount(); i++) { - const ROOM *const room = Room_Get(i); - M_BATCH *const batch = &m_Priv.batches[i]; - batch->caustics = - Memory_Alloc(room->mesh.num_vertices * sizeof(int16_t)); - batch->vertex_start = last_vertex; - batch->vertex_count = 0; - batch->vertex_count += room->mesh.num_face4s * OUTPUT_QUAD_VERTICES; - batch->vertex_count += room->mesh.num_face3s * OUTPUT_TRI_VERTICES; - last_vertex += batch->vertex_count; - - for (int32_t j = 0; j < room->mesh.num_vertices; j++) { - batch->caustics[j] = - m_CausticsTable[(room->mesh.num_vertices - j) % WIBBLE_SIZE]; - } - } - m_Priv.vertex_count = last_vertex; - - int32_t current_vertex = 0; - for (int32_t i = 0; i < Room_GetCount(); i++) { - const ROOM *const room = Room_Get(i); - M_BATCH *const batch = M_GetBatch(room); - batch->animated_vertices = Vector_Create(sizeof(OUTPUT_VERTEX_RANGE)); - for (int32_t j = 0; j < room->mesh.num_face4s; j++) { - const FACE4 *const face = &room->mesh.face4s[j]; - if (Output_Textures_IsObjectTextureAnimated(face->texture_idx)) { - Vector_Add( - batch->animated_vertices, - &(OUTPUT_VERTEX_RANGE) { - .vertex_start = current_vertex, - .vertex_count = OUTPUT_QUAD_VERTICES, - }); - } - current_vertex += OUTPUT_QUAD_VERTICES; - } - for (int32_t j = 0; j < room->mesh.num_face3s; j++) { - const FACE3 *const face = &room->mesh.face3s[j]; - if (Output_Textures_IsObjectTextureAnimated(face->texture_idx)) { - Vector_Add( - batch->animated_vertices, - &(OUTPUT_VERTEX_RANGE) { - .vertex_start = current_vertex, - .vertex_count = OUTPUT_TRI_VERTICES, - }); - } - current_vertex += OUTPUT_TRI_VERTICES; - } - Output_GlueVertexRanges(batch->animated_vertices); - } - - m_Priv.geom_vbo_data = - Memory_Alloc(m_Priv.vertex_count * sizeof(M_MESH_VERTEX)); - m_Priv.tex_vbo_data = - Memory_Alloc(m_Priv.vertex_count * sizeof(OUTPUT_MESH_TEXTURE)); - m_Priv.shade_vbo_data = - Memory_Alloc(m_Priv.vertex_count * sizeof(M_MESH_SHADE)); - - glGenVertexArrays(1, &m_Priv.vao); - glGenBuffers(1, &m_Priv.geom_vbo); - glGenBuffers(1, &m_Priv.tex_vbo); - glGenBuffers(1, &m_Priv.shade_vbo); - - glBindVertexArray(m_Priv.vao); - - glBindBuffer(GL_ARRAY_BUFFER, m_Priv.geom_vbo); - // attribute 0: position - glEnableVertexAttribArray(0); - glVertexAttribPointer( - 0, 3, GL_FLOAT, GL_FALSE, sizeof(M_MESH_VERTEX), - (void *)(intptr_t)offsetof(M_MESH_VERTEX, pos)); - - // attribute 1: normal (ignore) - - // attribute 5: flags - glEnableVertexAttribArray(5); - glVertexAttribIPointer( - 5, 1, GL_UNSIGNED_SHORT, sizeof(M_MESH_VERTEX), - (void *)(intptr_t)offsetof(M_MESH_VERTEX, flags)); - - // attribute 6: mesh color (ignore) - - glBindBuffer(GL_ARRAY_BUFFER, m_Priv.tex_vbo); - // attribute 2: uvw - glEnableVertexAttribArray(2); - glVertexAttribPointer( - 2, 3, GL_FLOAT, GL_FALSE, sizeof(OUTPUT_MESH_TEXTURE), - (void *)(intptr_t)offsetof(OUTPUT_MESH_TEXTURE, uvw)); - - // attribute 3: texture size - glEnableVertexAttribArray(3); - glVertexAttribPointer( - 3, 4, GL_FLOAT, GL_FALSE, sizeof(OUTPUT_MESH_TEXTURE), - (void *)(intptr_t)offsetof(OUTPUT_MESH_TEXTURE, texture_size)); - - // attribute 4: trapezoid ratios - glEnableVertexAttribArray(4); - glVertexAttribPointer( - 4, 2, GL_FLOAT, GL_FALSE, sizeof(OUTPUT_MESH_TEXTURE), - (void *)(intptr_t)offsetof(OUTPUT_MESH_TEXTURE, trapezoid_ratio)); - - glBindBuffer(GL_ARRAY_BUFFER, m_Priv.shade_vbo); - - // attribute 7 (shade) - glEnableVertexAttribArray(7); - glVertexAttribPointer( - 7, 1, GL_UNSIGNED_SHORT, GL_FALSE, sizeof(M_MESH_SHADE), 0); - - M_UpdateVertices(); -} - -static void M_FreeBuffers(void) -{ - glBindVertexArray(0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - if (m_Priv.vao != 0) { - glDeleteVertexArrays(1, &m_Priv.vao); - m_Priv.vao = 0; - } - if (m_Priv.geom_vbo != 0) { - glDeleteBuffers(1, &m_Priv.geom_vbo); - m_Priv.geom_vbo = 0; - } - if (m_Priv.tex_vbo != 0) { - glDeleteBuffers(1, &m_Priv.tex_vbo); - m_Priv.tex_vbo = 0; - } - if (m_Priv.shade_vbo != 0) { - glDeleteBuffers(1, &m_Priv.shade_vbo); - m_Priv.shade_vbo = 0; - } - Memory_FreePointer(&m_Priv.geom_vbo_data); - Memory_FreePointer(&m_Priv.tex_vbo_data); - Memory_FreePointer(&m_Priv.shade_vbo_data); - for (int32_t i = 0; i < (int32_t)m_Priv.batch_count; i++) { - Vector_Free(m_Priv.batches[i].animated_vertices); - Memory_FreePointer(&m_Priv.batches[i].caustics); - } - Memory_FreePointer(&m_Priv.batches); -} - -void Output_Meshes_InitRooms(void) -{ - m_Shader = Output_Meshes_GetShader(); - for (int32_t i = 0; i < WIBBLE_SIZE; i++) { - const int16_t angle = (i * DEG_360) / WIBBLE_SIZE; - m_ShadeTable[i] = Math_Sin(angle) * SHADE_CAUSTICS >> W2V_SHIFT; - m_CausticsTable[i] = (Random_GetDraw() >> 5) - 0x01FF; - } -} - -void Output_Meshes_ShutdownRooms(void) -{ - M_FreeBuffers(); -} - -void Output_Meshes_ObserveLevelLoadRooms(void) -{ - M_FreeBuffers(); - M_PrepareBuffers(); -} - -static void M_FillAnimatedTextures(const ROOM *const room) -{ - const M_BATCH *const batch = M_GetBatch(room); - OUTPUT_MESH_TEXTURE *out_texture = - &m_Priv.tex_vbo_data[batch->vertex_start]; - for (int32_t j = 0; j < room->mesh.num_face4s; j++) { - const FACE4 *const face = &room->mesh.face4s[j]; - if (!Output_Textures_IsObjectTextureAnimated(face->texture_idx)) { - out_texture += OUTPUT_QUAD_VERTICES; - continue; - } - for (int32_t k = 0; k < OUTPUT_QUAD_VERTICES; k++) { - const int32_t l = OUTPUT_QUAD_TO_FAN(k); - const int32_t uvw_idx = face->texture_idx * 4 + l; - M_FillTexture( - out_texture, uvw_idx, face->texture_zw[l].z, - face->texture_zw[l].w); - out_texture++; - } - } - for (int32_t j = 0; j < room->mesh.num_face3s; j++) { - const FACE3 *const face = &room->mesh.face3s[j]; - if (!Output_Textures_IsObjectTextureAnimated(face->texture_idx)) { - out_texture += OUTPUT_TRI_VERTICES; - continue; - } - for (int32_t k = 0; k < OUTPUT_TRI_VERTICES; k++) { - const int32_t l = OUTPUT_TRI_TO_FAN(k); - const int32_t uvw_idx = face->texture_idx * 4 + l; - M_FillTexture(out_texture, uvw_idx, 1.0f, 1.0f); - out_texture++; - } - } -} - -static void M_UpdateAnimatedTextures(const ROOM *const room) -{ - const M_BATCH *const batch = M_GetBatch(room); - glBindBuffer(GL_ARRAY_BUFFER, m_Priv.tex_vbo); - for (int32_t i = 0; i < batch->animated_vertices->count; i++) { - const OUTPUT_VERTEX_RANGE *const range = - Vector_Get(batch->animated_vertices, i); - GFX_TRACK_DATA( - glBufferSubData, GL_ARRAY_BUFFER, - range->vertex_start * sizeof(OUTPUT_MESH_TEXTURE), - range->vertex_count * sizeof(OUTPUT_MESH_TEXTURE), - &m_Priv.tex_vbo_data[range->vertex_start]); - } -} - -void Output_Meshes_ObserveTextureAnimationRooms(void) -{ - for (int32_t i = 0; i < Room_GetCount(); i++) { - const ROOM *const room = Room_Get(i); - M_FillAnimatedTextures(room); - M_UpdateAnimatedTextures(room); - } -} - -void Output_Meshes_ObserveRoomFlip(const ROOM *const room) -{ - M_FillAnimatedTextures(room); - M_UpdateAnimatedTextures(room); -} - -void Output_Meshes_RenderRoomMesh( - const MATRIX *const matrix, const RGB_F tint, const ROOM *const room) -{ - const M_BATCH *const batch = M_GetBatch(room); - - M_UpdateRoomShades(room); - - Output_Shader_UploadMatrix(m_Shader, matrix); - Output_Shader_UploadTint(m_Shader, tint); - GFX_GL_CheckError(); - - glBindBuffer(GL_ARRAY_BUFFER, m_Priv.shade_vbo); - GFX_TRACK_SUBDATA( - glBufferSubData, GL_ARRAY_BUFFER, - batch->vertex_start * sizeof(M_MESH_SHADE), - batch->vertex_count * sizeof(M_MESH_SHADE), - &m_Priv.shade_vbo_data[batch->vertex_start]); - GFX_GL_CheckError(); - - glEnable(GL_CULL_FACE); - glBindVertexArray(m_Priv.vao); - GFX_GL_CheckError(); - - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D_ARRAY, Output_Textures_GetAtlasTexture()); - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, Output_Textures_GetEnvMapTexture()); - GFX_GL_CheckError(); - - if (Output_GetWibbleEffect()) { - Output_Shader_UploadWibbleEffect(m_Shader, false); - glDepthMask(GL_FALSE); - glDrawArrays(GL_TRIANGLES, batch->vertex_start, batch->vertex_count); - glDepthMask(GL_TRUE); - Output_Shader_UploadWibbleEffect(m_Shader, true); - glDrawArrays(GL_TRIANGLES, batch->vertex_start, batch->vertex_count); - } else { - Output_Shader_UploadWibbleEffect(m_Shader, false); - glDrawArrays(GL_TRIANGLES, batch->vertex_start, batch->vertex_count); - } - - glDisable(GL_CULL_FACE); - GFX_GL_CheckError(); -} diff --git a/src/tr1/game/output/meshes/rooms.h b/src/tr1/game/output/meshes/rooms.h deleted file mode 100644 index 0c822ac3e..000000000 --- a/src/tr1/game/output/meshes/rooms.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include -#include -#include - -void Output_Meshes_InitRooms(void); -void Output_Meshes_ShutdownRooms(void); -void Output_Meshes_ObserveLevelLoadRooms(void); -void Output_Meshes_ObserveTextureAnimationRooms(void); -void Output_Meshes_ObserveRoomFlip(const ROOM *room); - -void Output_Meshes_RenderRoomMesh( - const MATRIX *matrix, RGB_F tint, const ROOM *room); diff --git a/src/tr1/game/output/shader.c b/src/tr1/game/output/shader.c deleted file mode 100644 index 947b42ca9..000000000 --- a/src/tr1/game/output/shader.c +++ /dev/null @@ -1,187 +0,0 @@ -#include "game/output/shader.h" - -#include "game/output.h" -#include "game/viewport.h" -#include "global/vars.h" - -#include -#include -#include - -typedef enum { - M_UNIFORM_TIME, - M_UNIFORM_TEX_ATLAS, - M_UNIFORM_TEX_ENV_MAP, - M_UNIFORM_SMOOTHING_ENABLED, - M_UNIFORM_ALPHA_DISCARD_ENABLED, - M_UNIFORM_ALPHA_THRESHOLD, - M_UNIFORM_TRAPEZOID_FILTER_ENABLED, - M_UNIFORM_REFLECTIONS_ENABLED, - M_UNIFORM_BRIGHTNESS_MULTIPLIER, - M_UNIFORM_GLOBAL_TINT, - M_UNIFORM_FOG, - M_UNIFORM_VIEWPORT_SIZE, - M_UNIFORM_PROJECTION_MATRIX, - M_UNIFORM_MODEL_MATRIX, - M_UNIFORM_WIBBLE_EFFECT, - M_UNIFORM_WATER_EFFECT, - M_UNIFORM_NUMBER_OF, -} M_UNIFORM; - -struct OUTPUT_SHADER { - GFX_GL_PROGRAM program; - GLint uniforms[M_UNIFORM_NUMBER_OF]; -}; - -OUTPUT_SHADER *Output_Shader_Create(const char *const path) -{ - OUTPUT_SHADER *const shader = Memory_Alloc(sizeof(OUTPUT_SHADER)); - - GFX_GL_Program_Init(&shader->program); - GFX_GL_Program_AttachShader( - &shader->program, GL_VERTEX_SHADER, path, - GFX_Context_GetConfig()->backend); - GFX_GL_Program_AttachShader( - &shader->program, GL_FRAGMENT_SHADER, path, - GFX_Context_GetConfig()->backend); - GFX_GL_Program_FragmentData(&shader->program, "outColor"); - GFX_GL_Program_Link(&shader->program); - - const char *const uniform_names[] = { - [M_UNIFORM_TIME] = "uTime", - [M_UNIFORM_TEX_ATLAS] = "uTexAtlas", - [M_UNIFORM_TEX_ENV_MAP] = "uTexEnvMap", - [M_UNIFORM_SMOOTHING_ENABLED] = "uSmoothingEnabled", - [M_UNIFORM_ALPHA_DISCARD_ENABLED] = "uAlphaDiscardEnabled", - [M_UNIFORM_ALPHA_THRESHOLD] = "uAlphaThreshold", - [M_UNIFORM_TRAPEZOID_FILTER_ENABLED] = "uTrapezoidFilterEnabled", - [M_UNIFORM_REFLECTIONS_ENABLED] = "uReflectionsEnabled", - [M_UNIFORM_BRIGHTNESS_MULTIPLIER] = "uBrightnessMultiplier", - [M_UNIFORM_GLOBAL_TINT] = "uGlobalTint", - [M_UNIFORM_FOG] = "uFog", - [M_UNIFORM_VIEWPORT_SIZE] = "uViewportSize", - [M_UNIFORM_PROJECTION_MATRIX] = "uMatProjection", - [M_UNIFORM_MODEL_MATRIX] = "uMatModelView", - [M_UNIFORM_WIBBLE_EFFECT] = "uWibbleEffect", - [M_UNIFORM_WATER_EFFECT] = "uWaterEffect", - }; - for (int32_t i = 0; i < M_UNIFORM_NUMBER_OF; i++) { - shader->uniforms[i] = - GFX_GL_Program_UniformLocation(&shader->program, uniform_names[i]); - GFX_GL_CheckError(); - } - - GFX_GL_Program_Bind(&shader->program); - glUniform1i(shader->uniforms[M_UNIFORM_TEX_ATLAS], 0); - glUniform1i(shader->uniforms[M_UNIFORM_TEX_ENV_MAP], 1); - return shader; -} - -void Output_Shader_Free(OUTPUT_SHADER *const shader) -{ - GFX_GL_Program_Close(&shader->program); - Memory_Free(shader); -} - -void Output_Shader_Bind(const OUTPUT_SHADER *const shader) -{ - GFX_GL_Program_Bind(&shader->program); -} - -void Output_Shader_UploadCommonUniforms(const OUTPUT_SHADER *const shader) -{ - GFX_GL_Program_Bind(&shader->program); - - GFX_TRACK_UNIFORM( - glUniform1f, shader->uniforms[M_UNIFORM_SMOOTHING_ENABLED], - g_Config.rendering.texture_filter); - GFX_TRACK_UNIFORM( - glUniform1f, shader->uniforms[M_UNIFORM_ALPHA_THRESHOLD], - g_Config.rendering.enable_wireframe ? -1.0f : 0.0f); - GFX_TRACK_UNIFORM( - glUniform1i, shader->uniforms[M_UNIFORM_ALPHA_DISCARD_ENABLED], - !g_Config.rendering.enable_wireframe); - GFX_TRACK_UNIFORM( - glUniform1i, shader->uniforms[M_UNIFORM_TRAPEZOID_FILTER_ENABLED], - g_Config.rendering.enable_trapezoid_filter); - GFX_TRACK_UNIFORM( - glUniform1i, shader->uniforms[M_UNIFORM_REFLECTIONS_ENABLED], - g_Config.visuals.enable_reflections); - GFX_TRACK_UNIFORM( - glUniform1f, shader->uniforms[M_UNIFORM_BRIGHTNESS_MULTIPLIER], - g_Config.visuals.brightness); - GFX_TRACK_UNIFORM( - glUniform2f, shader->uniforms[M_UNIFORM_VIEWPORT_SIZE], - GFX_Context_GetDisplayWidth(), GFX_Context_GetDisplayHeight()); - GFX_TRACK_UNIFORM( - glUniform2f, shader->uniforms[M_UNIFORM_FOG], Output_GetFogStart(), - Output_GetFogEnd()); - GFX_TRACK_UNIFORM( - glUniform1i, shader->uniforms[M_UNIFORM_TIME], Output_GetTime()); -} - -void Output_Shader_UploadMatrix( - const OUTPUT_SHADER *const shader, const MATRIX *const source) -{ - GLfloat target[4][4]; - target[0][0] = source->_00 / (float)(1 << W2V_SHIFT); - target[0][1] = source->_01 / (float)(1 << W2V_SHIFT); - target[0][2] = source->_02 / (float)(1 << W2V_SHIFT); - target[0][3] = source->_03 / (float)(1 << W2V_SHIFT); - - target[1][0] = source->_10 / (float)(1 << W2V_SHIFT); - target[1][1] = source->_11 / (float)(1 << W2V_SHIFT); - target[1][2] = source->_12 / (float)(1 << W2V_SHIFT); - target[1][3] = source->_13 / (float)(1 << W2V_SHIFT); - - target[2][0] = source->_20 / (float)(1 << W2V_SHIFT); - target[2][1] = source->_21 / (float)(1 << W2V_SHIFT); - target[2][2] = source->_22 / (float)(1 << W2V_SHIFT); - target[2][3] = source->_23 / (float)(1 << W2V_SHIFT); - - target[3][0] = 0.0; - target[3][1] = 0.0; - target[3][2] = 0.0; - target[3][3] = 1.0; - - GFX_GL_Program_Bind(&shader->program); - GFX_TRACK_UNIFORM( - glUniformMatrix4fv, shader->uniforms[M_UNIFORM_MODEL_MATRIX], 1, - GL_TRUE, &target[0][0]); -} - -void Output_Shader_UploadProjectionMatrix(const OUTPUT_SHADER *const shader) -{ - GFX_GL_Program_Bind(&shader->program); - - GLfloat projection[4][4]; - Output_GetProjectionMatrix(projection); - GFX_TRACK_UNIFORM( - glUniformMatrix4fv, shader->uniforms[M_UNIFORM_PROJECTION_MATRIX], 1, - GL_TRUE, &projection[0][0]); -} - -void Output_Shader_UploadWibbleEffect( - const OUTPUT_SHADER *const shader, const bool is_enabled) -{ - GFX_GL_Program_Bind(&shader->program); - GFX_TRACK_UNIFORM( - glUniform1i, shader->uniforms[M_UNIFORM_WIBBLE_EFFECT], is_enabled); -} - -void Output_Shader_UploadWaterEffect( - const OUTPUT_SHADER *const shader, const bool is_enabled) -{ - GFX_GL_Program_Bind(&shader->program); - GFX_TRACK_UNIFORM( - glUniform1i, shader->uniforms[M_UNIFORM_WATER_EFFECT], is_enabled); -} - -void Output_Shader_UploadTint( - const OUTPUT_SHADER *const shader, const RGB_F tint) -{ - GFX_GL_Program_Bind(&shader->program); - GFX_TRACK_UNIFORM( - glUniform3f, shader->uniforms[M_UNIFORM_GLOBAL_TINT], tint.r, tint.g, - tint.b); -} diff --git a/src/tr1/game/output/shader.h b/src/tr1/game/output/shader.h deleted file mode 100644 index ea9384873..000000000 --- a/src/tr1/game/output/shader.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include -#include - -typedef struct OUTPUT_SHADER OUTPUT_SHADER; - -OUTPUT_SHADER *Output_Shader_Create(const char *path); -void Output_Shader_Free(OUTPUT_SHADER *shader); -void Output_Shader_Bind(const OUTPUT_SHADER *shader); -void Output_Shader_UploadCommonUniforms(const OUTPUT_SHADER *shader); -void Output_Shader_UploadProjectionMatrix(const OUTPUT_SHADER *shader); - -// TODO: these functions are poor design. -void Output_Shader_UploadMatrix( - const OUTPUT_SHADER *shader, const MATRIX *source); -void Output_Shader_UploadWibbleEffect( - const OUTPUT_SHADER *shader, bool is_enabled); -void Output_Shader_UploadWaterEffect( - const OUTPUT_SHADER *shader, bool is_enabled); -void Output_Shader_UploadTint(const OUTPUT_SHADER *shader, RGB_F tint); diff --git a/src/tr1/game/output/sprites.c b/src/tr1/game/output/sprites.c deleted file mode 100644 index b8c524ebd..000000000 --- a/src/tr1/game/output/sprites.c +++ /dev/null @@ -1,487 +0,0 @@ -#include "game/output/sprites.h" - -#include "game/output.h" -#include "game/output/meshes/common.h" -#include "game/output/shader.h" -#include "game/output/textures.h" -#include "game/output/utils.h" -#include "game/output/vertex_range.h" -#include "game/room.h" - -#include -#include -#include -#include -#include -#include - -#pragma pack(push, 1) -typedef struct { - // attribute 0 - XYZ_F pos; - - // attribute 1 - struct { - float x, y; - } displacement; - - // attribute 2 - OUTPUT_UVW uvw; -} M_SPRITE_VERTEX; - -typedef uint16_t M_SPRITE_SHADE; -#pragma pack(pop) - -typedef struct { - MATRIX matrix; - XYZ_32 pos; - int32_t sprite_idx; - RGB_F tint; - M_SPRITE_SHADE shade; -} M_DYNAMIC_SPRITE; - -typedef struct { - GLuint vao; - GLuint geom_vbo; - GLuint shade_vbo; - size_t vertex_capacity; - size_t vertex_count; - GLenum usage; - M_SPRITE_VERTEX *geom_vbo_data; - M_SPRITE_SHADE *shade_vbo_data; -} M_SPRITE_BUFFER; - -typedef struct { - int32_t quad_start; - int32_t quad_count; -} M_ROOM_BATCH; - -static OUTPUT_SHADER *m_Shader = nullptr; - -static struct { - M_SPRITE_BUFFER sprite_buf; - size_t room_batch_count; - M_ROOM_BATCH *room_batches; - VECTOR *animated_vertices; -} m_LevelData = {}; - -static struct { - VECTOR *source; - M_SPRITE_BUFFER sprite_buf; -} m_Dynamic = {}; - -static void M_MakeQuad( - M_SPRITE_VERTEX out_quad[4], const int32_t sprite_idx, const XYZ_16 pos) -{ - const SPRITE_TEXTURE *const sprite = Output_GetSpriteTexture(sprite_idx); - - for (int32_t k = 0; k < 4; k++) { - out_quad[k].pos = (XYZ_F) { .x = pos.x, .y = pos.y, .z = pos.z }; - out_quad[k].uvw = Output_Textures_GetUVW( - Output_Textures_GetSpritesUVWsBase() + sprite_idx * 4 + k); - } - - out_quad[0].displacement.x = sprite->x0; - out_quad[0].displacement.y = sprite->y0; - out_quad[1].displacement.x = sprite->x1; - out_quad[1].displacement.y = sprite->y0; - out_quad[2].displacement.x = sprite->x1; - out_quad[2].displacement.y = sprite->y1; - out_quad[3].displacement.x = sprite->x0; - out_quad[3].displacement.y = sprite->y1; -} - -static M_ROOM_BATCH *M_GetRoomBatch(const ROOM *const room) -{ - // Room data gets swapped when flipping, but the VBOs do not. So a room 2 - // that gets flipped to room 17 ends up getting the data from room 2, - // whereas the VBO needs to take data from room 17. - const int16_t room_num = - Room_GetFlipStatus() && room->flipped_room != NO_ROOM_NEG - ? room->flipped_room - : Room_GetNumber(room); - return &m_LevelData.room_batches[room_num]; -} - -static void M_BufferReallocGPU(M_SPRITE_BUFFER *const buffer) -{ - // This triggers a reallocation on the GPU and should be used sparingly, - // even when swapping the entire buffer data. - glBindBuffer(GL_ARRAY_BUFFER, buffer->geom_vbo); - GFX_TRACK_DATA( - glBufferData, GL_ARRAY_BUFFER, - buffer->vertex_capacity * sizeof(M_SPRITE_VERTEX), - buffer->geom_vbo_data, buffer->usage); - - glBindBuffer(GL_ARRAY_BUFFER, buffer->shade_vbo); - GFX_TRACK_DATA( - glBufferData, GL_ARRAY_BUFFER, - buffer->vertex_capacity * sizeof(M_SPRITE_SHADE), - buffer->shade_vbo_data, GL_DYNAMIC_DRAW); // shades are always dynamic -} - -static void M_PrepareBuffer( - M_SPRITE_BUFFER *const buffer, const size_t capacity, const GLenum usage) -{ - buffer->vertex_capacity = capacity; - buffer->vertex_count = 0; - buffer->usage = usage; - - buffer->geom_vbo_data = Memory_Alloc(capacity * sizeof(M_SPRITE_VERTEX)); - buffer->shade_vbo_data = Memory_Alloc(capacity * sizeof(M_SPRITE_SHADE)); - - glGenVertexArrays(1, &buffer->vao); - glGenBuffers(1, &buffer->geom_vbo); - glGenBuffers(1, &buffer->shade_vbo); - - M_BufferReallocGPU(buffer); - - glBindVertexArray(buffer->vao); - - glBindBuffer(GL_ARRAY_BUFFER, buffer->geom_vbo); - - // attribute 0: position - glEnableVertexAttribArray(0); - glVertexAttribPointer( - 0, 3, GL_FLOAT, GL_FALSE, sizeof(M_SPRITE_VERTEX), - (void *)(intptr_t)offsetof(M_SPRITE_VERTEX, pos)); - - // attribute 1: normal (becomes sprite corner displacement) - glEnableVertexAttribArray(1); - glVertexAttribPointer( - 1, 2, GL_FLOAT, GL_FALSE, sizeof(M_SPRITE_VERTEX), - (void *)(intptr_t)offsetof(M_SPRITE_VERTEX, displacement)); - - // attribute 2: uvw - glEnableVertexAttribArray(2); - glVertexAttribPointer( - 2, 3, GL_FLOAT, GL_FALSE, sizeof(M_SPRITE_VERTEX), - (void *)(intptr_t)offsetof(M_SPRITE_VERTEX, uvw)); - - // attribute 3: texture size - glDisableVertexAttribArray(3); - glVertexAttrib4f(3, 0.0f, 0.0f, 1.0f, 1.0f); - - // attribute 4: trapezoid ratios - glDisableVertexAttribArray(4); - glVertexAttrib2f(4, 1.0f, 1.0f); - - // attribute 5: flags - glDisableVertexAttribArray(5); - glVertexAttribI1i(5, VERT_SPRITE); - - // attribute 6: mesh color (ignore) - glDisableVertexAttribArray(6); - glVertexAttrib4f(6, 1.0f, 1.0f, 1.0f, 1.0f); - - glBindBuffer(GL_ARRAY_BUFFER, buffer->shade_vbo); - - // attribute 7 (shade) - glEnableVertexAttribArray(7); - glVertexAttribPointer( - 7, 1, GL_UNSIGNED_SHORT, GL_FALSE, sizeof(M_SPRITE_SHADE), 0); -} - -static void M_FreeBuffer(M_SPRITE_BUFFER *const buffer) -{ - if (buffer->vao != 0) { - glDeleteVertexArrays(1, &buffer->vao); - buffer->vao = 0; - } - if (buffer->geom_vbo != 0) { - glDeleteBuffers(1, &buffer->geom_vbo); - buffer->geom_vbo = 0; - } - if (buffer->shade_vbo != 0) { - glDeleteBuffers(1, &buffer->shade_vbo); - buffer->shade_vbo = 0; - } - Memory_FreePointer(&buffer->geom_vbo_data); - Memory_FreePointer(&buffer->shade_vbo_data); -} - -static void M_PushToBuffer( - M_SPRITE_BUFFER *const buffer, const XYZ_32 pos, const int32_t sprite_idx, - const uint16_t shade) -{ -} - -static void M_FlushBuffer(M_SPRITE_BUFFER *const buffer) -{ - buffer->vertex_count = 0; -} - -static void M_DrawBuffer( - M_SPRITE_BUFFER *const buffer, const int32_t start, const int32_t count) -{ - glBindVertexArray(buffer->vao); - GFX_GL_CheckError(); - - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D_ARRAY, Output_Textures_GetAtlasTexture()); - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, Output_Textures_GetEnvMapTexture()); - GFX_GL_CheckError(); - - glDrawArrays(GL_TRIANGLES, start, count); - GFX_GL_CheckError(); -} - -static void M_PrepareLevelBatches(void) -{ - m_LevelData.room_batches = - Memory_Alloc(m_LevelData.room_batch_count * sizeof(M_ROOM_BATCH)); - - int32_t current_quad = 0; - for (int32_t i = 0; i < Room_GetCount(); i++) { - const ROOM *const room = Room_Get(i); - M_ROOM_BATCH *const room_batch = &m_LevelData.room_batches[i]; - room_batch->quad_start = current_quad; - room_batch->quad_count = room->mesh.num_sprites; - current_quad += room->mesh.num_sprites; - } - - Vector_Clear(m_LevelData.animated_vertices); - int32_t current_vertex = 0; - for (int32_t i = 0; i < Room_GetCount(); i++) { - const ROOM *const room = Room_Get(i); - for (int32_t j = 0; j < room->mesh.num_sprites; j++) { - const ROOM_SPRITE *const room_sprite = &room->mesh.sprites[j]; - const int16_t sprite_idx = room_sprite->texture; - if (Output_Textures_IsSpriteTextureAnimated(sprite_idx)) { - Vector_Add( - m_LevelData.animated_vertices, - &(OUTPUT_VERTEX_RANGE) { - .vertex_start = current_vertex, - .vertex_count = OUTPUT_QUAD_VERTICES, - }); - } - current_vertex += OUTPUT_QUAD_VERTICES; - } - } - Output_GlueVertexRanges(m_LevelData.animated_vertices); -} - -static void M_FillRoomGeometry(const ROOM *const room) -{ - int32_t current_vertex = 0; - for (int32_t i = 0; i < Room_GetCount(); i++) { - const ROOM *const room = Room_Get(i); - for (int32_t j = 0; j < room->mesh.num_sprites; j++) { - const ROOM_SPRITE *const room_sprite = &room->mesh.sprites[j]; - const ROOM_VERTEX *const mesh_vertex = - &room->mesh.vertices[room_sprite->vertex]; - const int16_t sprite_idx = room_sprite->texture; - const XYZ_16 pos = mesh_vertex->pos; - - M_SPRITE_VERTEX quad[4]; - M_MakeQuad(quad, sprite_idx, pos); - - for (int32_t k = 0; k < OUTPUT_QUAD_VERTICES; k++) { - m_LevelData.sprite_buf.geom_vbo_data[current_vertex] = - quad[OUTPUT_QUAD_TO_FAN(k)]; - current_vertex++; - } - } - } -} - -static void M_UpdateRoomShades(const ROOM *const room) -{ - const M_ROOM_BATCH *const room_batch = M_GetRoomBatch(room); - int32_t current_vertex = room_batch->quad_start * OUTPUT_QUAD_VERTICES; - for (int32_t j = 0; j < room->mesh.num_sprites; j++) { - const ROOM_SPRITE *const room_sprite = &room->mesh.sprites[j]; - for (int32_t k = 0; k < OUTPUT_QUAD_VERTICES; k++) { - m_LevelData.sprite_buf.shade_vbo_data[current_vertex] = - room->mesh.vertices[room_sprite->vertex].light_adder; - current_vertex++; - } - } -} - -static void M_FreeBuffers(void) -{ - glBindVertexArray(0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - M_FreeBuffer(&m_LevelData.sprite_buf); - M_FreeBuffer(&m_Dynamic.sprite_buf); - Memory_FreePointer(&m_LevelData.room_batches); -} - -static void M_PrepareLevelBuffers(void) -{ - m_LevelData.room_batch_count = Room_GetCount(); - - int32_t num_quads = 0; - for (int32_t i = 0; i < Room_GetCount(); i++) { - num_quads += Room_Get(i)->mesh.num_sprites; - } - M_PrepareBuffer(&m_Dynamic.sprite_buf, 500, GL_DYNAMIC_DRAW); - M_PrepareBuffer( - &m_LevelData.sprite_buf, num_quads * OUTPUT_QUAD_VERTICES, - GL_DYNAMIC_DRAW); - - m_LevelData.sprite_buf.vertex_count = num_quads * OUTPUT_QUAD_VERTICES; - for (int32_t i = 0; i < Room_GetCount(); i++) { - const ROOM *const room = Room_Get(i); - M_FillRoomGeometry(room); - } - M_BufferReallocGPU(&m_LevelData.sprite_buf); -} - -void Output_Sprites_Init(void) -{ - m_Shader = Output_Meshes_GetShader(); - m_LevelData.animated_vertices = Vector_Create(sizeof(OUTPUT_VERTEX_RANGE)); - m_Dynamic.source = Vector_CreateAtCapacity(sizeof(M_DYNAMIC_SPRITE), 50); -} - -void Output_Sprites_Shutdown(void) -{ - Vector_Free(m_LevelData.animated_vertices); - Vector_Free(m_Dynamic.source); - M_FreeBuffers(); - m_Shader = nullptr; -} - -void Output_Sprites_ObserveLevelLoad(void) -{ - M_FreeBuffers(); - M_PrepareLevelBuffers(); - M_PrepareLevelBatches(); -} - -void Output_Sprites_ObserveTextureAnimation(void) -{ - int32_t current_vertex = 0; - for (int32_t i = 0; i < Room_GetCount(); i++) { - const ROOM *const room = Room_Get(i); - for (int32_t j = 0; j < room->mesh.num_sprites; j++) { - const ROOM_SPRITE *const room_sprite = &room->mesh.sprites[j]; - const int16_t sprite_idx = room_sprite->texture; - if (!Output_Textures_IsSpriteTextureAnimated(sprite_idx)) { - current_vertex += OUTPUT_QUAD_VERTICES; - continue; - } - for (int32_t k = 0; k < OUTPUT_QUAD_VERTICES; k++) { - m_LevelData.sprite_buf.geom_vbo_data[current_vertex].uvw = - Output_Textures_GetUVW( - Output_Textures_GetSpritesUVWsBase() + sprite_idx * 4 - + OUTPUT_QUAD_TO_FAN(k)); - current_vertex++; - } - } - } - glBindBuffer(GL_ARRAY_BUFFER, m_LevelData.sprite_buf.geom_vbo); - for (int32_t i = 0; i < m_LevelData.animated_vertices->count; i++) { - const OUTPUT_VERTEX_RANGE *const range = - Vector_Get(m_LevelData.animated_vertices, i); - GFX_TRACK_DATA( - glBufferSubData, GL_ARRAY_BUFFER, - range->vertex_start * sizeof(M_SPRITE_VERTEX), - range->vertex_count * sizeof(M_SPRITE_VERTEX), - &m_LevelData.sprite_buf.geom_vbo_data[range->vertex_start]); - } -} - -void Output_Sprites_RenderRoomSprites( - const MATRIX *const matrix, const RGB_F tint, const ROOM *const room) -{ - M_UpdateRoomShades(room); - - const M_ROOM_BATCH *const room_batch = M_GetRoomBatch(room); - glBindBuffer(GL_ARRAY_BUFFER, m_LevelData.sprite_buf.shade_vbo); - GFX_GL_CheckError(); - GFX_TRACK_SUBDATA( - glBufferSubData, GL_ARRAY_BUFFER, - room_batch->quad_start * OUTPUT_QUAD_VERTICES * sizeof(M_SPRITE_SHADE), - room_batch->quad_count * OUTPUT_QUAD_VERTICES * sizeof(M_SPRITE_SHADE), - &m_LevelData.sprite_buf - .shade_vbo_data[room_batch->quad_start * OUTPUT_QUAD_VERTICES]); - GFX_GL_CheckError(); - - Output_Shader_UploadWibbleEffect(m_Shader, Output_GetWibbleEffect()); - Output_Shader_UploadMatrix(m_Shader, matrix); - Output_Shader_UploadTint(m_Shader, tint); - GFX_GL_CheckError(); - - const M_ROOM_BATCH *const batch = M_GetRoomBatch(room); - M_DrawBuffer( - &m_LevelData.sprite_buf, batch->quad_start * OUTPUT_QUAD_VERTICES, - batch->quad_count * OUTPUT_QUAD_VERTICES); -} - -void Output_Sprites_RenderSingleSprite( - const MATRIX *const matrix, const XYZ_32 pos, const int32_t sprite_idx, - const uint16_t shade, const RGB_F tint) -{ - const M_DYNAMIC_SPRITE sprite = { - .matrix = *matrix, - .pos = pos, - .sprite_idx = sprite_idx, - .tint = tint, - .shade = shade, - }; - Vector_Add(m_Dynamic.source, &sprite); -} - -void Output_Sprites_RenderBegin(void) -{ - Output_Shader_UploadCommonUniforms(m_Shader); - Output_Shader_UploadProjectionMatrix(m_Shader); -} - -bool Output_Sprites_Flush(void) -{ - if (m_Dynamic.source->count == 0) { - return false; - } - - M_SPRITE_BUFFER *const buffer = &m_Dynamic.sprite_buf; - if ((size_t)m_Dynamic.source->count * OUTPUT_QUAD_VERTICES - > buffer->vertex_capacity) { - buffer->vertex_capacity = - (m_Dynamic.source->count + 50) * OUTPUT_QUAD_VERTICES; - buffer->geom_vbo_data = Memory_Realloc( - buffer->geom_vbo_data, - buffer->vertex_capacity * sizeof(M_SPRITE_VERTEX)); - buffer->shade_vbo_data = Memory_Realloc( - buffer->shade_vbo_data, - buffer->vertex_capacity * sizeof(M_SPRITE_SHADE)); - } - - for (int32_t i = 0; i < m_Dynamic.source->count; i++) { - const M_DYNAMIC_SPRITE *const sprite = Vector_Get(m_Dynamic.source, i); - M_SPRITE_VERTEX quad[4]; - M_MakeQuad( - quad, sprite->sprite_idx, - (XYZ_16) { sprite->pos.x, sprite->pos.y, sprite->pos.z }); - for (int32_t i = 0; i < OUTPUT_QUAD_VERTICES; i++) { - buffer->geom_vbo_data[buffer->vertex_count] = - quad[OUTPUT_QUAD_TO_FAN(i)]; - buffer->shade_vbo_data[buffer->vertex_count] = sprite->shade; - buffer->vertex_count++; - } - } - M_BufferReallocGPU(buffer); - - Output_Shader_UploadWibbleEffect(m_Shader, Output_GetWibbleEffect()); - for (int32_t i = 0; i < m_Dynamic.source->count; i++) { - const M_DYNAMIC_SPRITE *const sprite = Vector_Get(m_Dynamic.source, i); - Output_Shader_UploadTint(m_Shader, sprite->tint); - Output_Shader_UploadMatrix(m_Shader, &sprite->matrix); - M_DrawBuffer( - &m_Dynamic.sprite_buf, i * OUTPUT_QUAD_VERTICES, - OUTPUT_QUAD_VERTICES); - } - - Vector_Clear(m_Dynamic.source); - M_FlushBuffer(&m_Dynamic.sprite_buf); - return true; -} - -void Output_Sprites_UploadProjectionMatrix(void) -{ - Output_Shader_UploadProjectionMatrix(m_Shader); -} diff --git a/src/tr1/game/output/sprites.h b/src/tr1/game/output/sprites.h deleted file mode 100644 index d2838b37a..000000000 --- a/src/tr1/game/output/sprites.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -void Output_Sprites_Init(void); -void Output_Sprites_Shutdown(void); -void Output_Sprites_ObserveLevelLoad(void); -void Output_Sprites_ObserveTextureAnimation(void); -void Output_Sprites_UploadProjectionMatrix(void); - -void Output_Sprites_RenderBegin(void); -void Output_Sprites_RenderRoomSprites( - const MATRIX *matrix, RGB_F tint, const ROOM *room); -void Output_Sprites_RenderSingleSprite( - const MATRIX *matrix, XYZ_32 pos, int32_t sprite_idx, uint16_t shade, - RGB_F tint); -bool Output_Sprites_Flush(void); diff --git a/src/tr1/game/output/state.c b/src/tr1/game/output/state.c deleted file mode 100644 index e0dada05f..000000000 --- a/src/tr1/game/output/state.c +++ /dev/null @@ -1,155 +0,0 @@ -#include "game/output.h" -#include "game/output/meshes/common.h" -#include "game/output/meshes/objects.h" -#include "game/output/meshes/rooms.h" -#include "game/output/sprites.h" -#include "game/output/textures.h" -#include "global/vars.h" - -static int32_t m_Time = 0; -static int32_t m_AnimatedTexturesOffset = 0; - -static int32_t m_FogEnd = 0; -static RGB_F m_WaterColor = {}; - -static int32_t m_LsAdder = 0; -static int32_t m_LsDivider = 0; -static XYZ_32 m_LsVectorView = {}; - -static bool m_IsWibbleEffect = false; -static bool m_IsWaterEffect = false; -static bool m_IsShadeEffect = false; - -int32_t Output_GetFogEnd(void) -{ - return m_FogEnd; -} - -void Output_SetFogEnd(const int32_t dist) -{ - m_FogEnd = dist; - - const double near_z = Output_GetNearZ(); - const double far_z = Output_GetFarZ(); - const double res_z = 0.99 * near_z * far_z / (far_z - near_z); - g_FltResZ = res_z; - g_FltResZBuf = 0.005 + res_z / near_z; -} - -int32_t Output_GetNearZ(void) -{ - return 20 << W2V_SHIFT; -} - -int32_t Output_GetFarZ(void) -{ - return Output_GetFogEnd() << W2V_SHIFT; -} - -void Output_SetupBelowWater(const bool underwater) -{ - m_IsWaterEffect = true; - m_IsWibbleEffect = !underwater; - m_IsShadeEffect = true; - Output_RememberState(); - Output_Shader_UploadWaterEffect(Output_Meshes_GetShader(), m_IsWaterEffect); - Output_RestoreState(); -} - -void Output_SetupAboveWater(const bool underwater) -{ - m_IsWaterEffect = false; - m_IsWibbleEffect = underwater; - m_IsShadeEffect = underwater; - Output_RememberState(); - Output_Shader_UploadWaterEffect(Output_Meshes_GetShader(), m_IsWaterEffect); - Output_RestoreState(); -} - -bool Output_GetWaterEffect(void) -{ - return m_IsWaterEffect; -} - -bool Output_GetWibbleEffect(void) -{ - return m_IsWibbleEffect; -} - -void Output_SetWaterColor(const RGB_888 color) -{ - m_WaterColor.r = color.r / 255.0f; - m_WaterColor.g = color.g / 255.0f; - m_WaterColor.b = color.b / 255.0f; -} - -RGB_F Output_GetTint(void) -{ - if (m_IsShadeEffect) { - return m_WaterColor; - } - return (RGB_F) { 1.0f, 1.0f, 1.0f }; -} - -void Output_SetLightAdder(const int32_t adder) -{ - m_LsAdder = adder; -} - -int32_t Output_GetLightAdder(void) -{ - return m_LsAdder; -} - -void Output_SetLightDivider(const int32_t divider) -{ - m_LsDivider = divider; -} - -int32_t Output_GetLightDivider(void) -{ - return m_LsDivider; -} - -XYZ_32 Output_GetLightVectorView(void) -{ - return m_LsVectorView; -} - -void Output_RotateLight(const int16_t pitch, const int16_t yaw) -{ - const int32_t cp = Math_Cos(pitch); - const int32_t sp = Math_Sin(pitch); - const int32_t cy = Math_Cos(yaw); - const int32_t sy = Math_Sin(yaw); - const int32_t x = TRIGMULT2(cp, sy); - const int32_t y = -sp; - const int32_t z = TRIGMULT2(cp, cy); - const MATRIX *const m = &g_W2VMatrix; - m_LsVectorView.x = (m->_00 * x + m->_01 * y + m->_02 * z) >> W2V_SHIFT; - m_LsVectorView.y = (m->_10 * x + m->_11 * y + m->_12 * z) >> W2V_SHIFT; - m_LsVectorView.z = (m->_20 * x + m->_21 * y + m->_22 * z) >> W2V_SHIFT; -} - -int32_t Output_GetTime(void) -{ - return m_Time; -} - -void Output_AnimateTextures(const int32_t num_frames) -{ - m_Time += num_frames; - m_AnimatedTexturesOffset += num_frames; - bool update = false; - while (m_AnimatedTexturesOffset > 5) { - Output_CycleAnimatedTextures(); - update = true; - m_AnimatedTexturesOffset -= 5; - } - if (update) { - Output_Textures_CycleAnimations(); - Output_Sprites_ObserveTextureAnimation(); - Output_Meshes_ObserveTextureAnimationRooms(); - Output_Meshes_ObserveTextureAnimationObjects(); - } -} diff --git a/src/tr1/game/output/textures.c b/src/tr1/game/output/textures.c deleted file mode 100644 index 7944db03f..000000000 --- a/src/tr1/game/output/textures.c +++ /dev/null @@ -1,427 +0,0 @@ -#include "game/output/textures.h" - -#include "game/output.h" -#include "game/output/vertex_range.h" - -#include -#include -#include - -#include - -typedef struct { - OUTPUT_UVW corners[4]; -} M_UVW_PACK; - -static struct { - VECTOR *objects; - VECTOR *sprites; -} m_AnimationRanges; - -static struct { - GLuint tex_atlas; - GLuint tex_env_map; - - struct { - int32_t count; - int32_t count_objects; - int32_t count_sprites; - M_UVW_PACK *data; - M_UVW_PACK *data_objects; - M_UVW_PACK *data_sprites; - - bool *animated_objects; - bool *animated_sprites; - } uvws; - - struct { - OUTPUT_TEXTURE_SIZE *data; - OUTPUT_TEXTURE_SIZE *data_objects; - OUTPUT_TEXTURE_SIZE *data_sprites; - } atlas_sizes; -} m_Priv = {}; - -static void M_PrepareObjectAnimationRanges(void) -{ - size_t required_size = 0; - for (const ANIMATED_TEXTURE_RANGE *src_range = - Output_GetAnimatedTextureRange(0); - src_range != nullptr; src_range = src_range->next_range) { - required_size += src_range->num_textures; - } - - Vector_Clear(m_AnimationRanges.objects); - Vector_EnsureCapacity(m_AnimationRanges.objects, required_size); - - for (const ANIMATED_TEXTURE_RANGE *src_range = - Output_GetAnimatedTextureRange(0); - src_range != nullptr; src_range = src_range->next_range) { - for (int32_t i = 0; i < src_range->num_textures; i++) { - Vector_Add( - m_AnimationRanges.objects, - &(OUTPUT_VERTEX_RANGE) { - .vertex_start = src_range->textures[i], - .vertex_count = 1, - }); - } - } - Output_GlueVertexRanges(m_AnimationRanges.objects); -} - -static void M_PrepareSpriteAnimationRanges(void) -{ - size_t required_size = 0; - for (int32_t i = 0; i < MAX_STATIC_OBJECTS_2D; i++) { - const STATIC_OBJECT_2D *const obj = Object_Get2DStatic(i); - if (!obj->loaded || obj->frame_count == 1) { - continue; - } - required_size++; - } - - Vector_Clear(m_AnimationRanges.sprites); - Vector_EnsureCapacity(m_AnimationRanges.sprites, required_size); - - for (int32_t i = 0; i < MAX_STATIC_OBJECTS_2D; i++) { - const STATIC_OBJECT_2D *const obj = Object_Get2DStatic(i); - if (!obj->loaded || obj->frame_count == 1) { - continue; - } - Vector_Add( - m_AnimationRanges.sprites, - &(OUTPUT_VERTEX_RANGE) { - .vertex_start = obj->texture_idx, - .vertex_count = obj->frame_count, - }); - } - Output_GlueVertexRanges(m_AnimationRanges.sprites); -} - -static void M_PrepareAnimationRanges(void) -{ - M_PrepareObjectAnimationRanges(); - M_PrepareSpriteAnimationRanges(); - - for (int32_t i = 0; i < Output_GetObjectTextureCount(); i++) { - m_Priv.uvws.animated_objects[i] = false; - for (int32_t j = 0; j < m_AnimationRanges.objects->count; j++) { - const OUTPUT_VERTEX_RANGE *const dst_range = - Vector_Get(m_AnimationRanges.objects, j); - const int32_t range_start = dst_range->vertex_start; - const int32_t range_end = range_start + dst_range->vertex_count; - if (i >= range_start && i < range_end) { - m_Priv.uvws.animated_objects[i] = true; - break; - } - } - } - - for (int32_t i = 0; i < Output_GetSpriteTextureCount(); i++) { - m_Priv.uvws.animated_sprites[i] = false; - for (int32_t j = 0; j < m_AnimationRanges.sprites->count; j++) { - const OUTPUT_VERTEX_RANGE *const dst_range = - Vector_Get(m_AnimationRanges.sprites, j); - const int32_t range_start = dst_range->vertex_start; - const int32_t range_end = range_start + dst_range->vertex_count; - if (i >= range_start && i < range_end) { - m_Priv.uvws.animated_sprites[i] = true; - break; - } - } - } -} - -static void M_FillAtlasObjectSize(const int32_t i) -{ - OUTPUT_TEXTURE_SIZE *const size = &m_Priv.atlas_sizes.data_objects[i]; - const OBJECT_TEXTURE *const texture = Output_GetObjectTexture(i); - size->x0 = texture->uv[0].u; - size->y0 = texture->uv[0].v; - size->x1 = texture->uv[0].u; - size->y1 = texture->uv[0].v; - for (int32_t j = 1; j < texture->uv_count; j++) { - size->x0 = MIN(size->x0, texture->uv[j].u); - size->y0 = MIN(size->y0, texture->uv[j].v); - size->x1 = MAX(size->x1, texture->uv[j].u); - size->y1 = MAX(size->y1, texture->uv[j].v); - } - size->x0 /= 65535.0; - size->y0 /= 65535.0; - size->x1 /= 65535.0; - size->y1 /= 65535.0; -} - -static void M_FillAtlasSpriteSize(const int32_t i) -{ - OUTPUT_TEXTURE_SIZE *const size = &m_Priv.atlas_sizes.data_sprites[i]; - const SPRITE_TEXTURE *const sprite = Output_GetSpriteTexture(i); - const float adj = 0.1 / 256.0f; - const float u0 = (sprite->offset & 0xFF) / 256.0f + adj; - const float v0 = (sprite->offset >> 8) / 256.0f + adj; - const float u1 = u0 + sprite->width / 65536.0f - 2 * adj; - const float v1 = v0 + sprite->height / 65536.0f - 2 * adj; - size->x0 = u0; - size->y0 = v0; - size->x1 = u1; - size->y1 = v1; -} - -static void M_FillObjectUVW(const int32_t i) -{ - const OBJECT_TEXTURE *const texture = Output_GetObjectTexture(i); - OUTPUT_UVW *const corners = m_Priv.uvws.data_objects[i].corners; - for (int32_t j = 0; j < 4; j++) { - corners[j].u = texture->uv[j].u / 65535.0f; - corners[j].v = texture->uv[j].v / 65535.0f; - corners[j].w = texture->tex_page; - } -} - -static void M_FillSpriteUVW(const int32_t i) -{ - const SPRITE_TEXTURE *const sprite = Output_GetSpriteTexture(i); - const float adj = 0.1 / 256.0f; - const float u0 = (sprite->offset & 0xFF) / 256.0f + adj; - const float v0 = (sprite->offset >> 8) / 256.0f + adj; - const float u1 = u0 + sprite->width / 65536.0f - 2 * adj; - const float v1 = v0 + sprite->height / 65536.0f - 2 * adj; - OUTPUT_UVW *const corners = m_Priv.uvws.data_sprites[i].corners; - // clang-format off - corners[0].u = u0; corners[0].v = v0; corners[0].w = sprite->tex_page; - corners[1].u = u1; corners[1].v = v0; corners[1].w = sprite->tex_page; - corners[2].u = u1; corners[2].v = v1; corners[2].w = sprite->tex_page; - corners[3].u = u0; corners[3].v = v1; corners[3].w = sprite->tex_page; - // clang-format on -} - -static void M_FillObjectUVWs(void) -{ - for (int32_t i = 0; i < Output_GetObjectTextureCount(); i++) { - M_FillObjectUVW(i); - } -} - -static void M_FillSpriteUVWs(void) -{ - for (int32_t i = 0; i < Output_GetSpriteTextureCount(); i++) { - M_FillSpriteUVW(i); - } -} - -static void M_UpdateObjectAnimatedUVWs(VECTOR *const source) -{ - for (int32_t i = 0; i < source->count; i++) { - const OUTPUT_VERTEX_RANGE *const range = Vector_Get(source, i); - for (int32_t j = 0; j < range->vertex_count; j++) { - M_FillObjectUVW(range->vertex_start + j); - M_FillAtlasObjectSize(range->vertex_start + j); - } - } -} - -static void M_UpdateSpriteAnimatedUVWs(VECTOR *const source) -{ - for (int32_t i = 0; i < source->count; i++) { - const OUTPUT_VERTEX_RANGE *const range = Vector_Get(source, i); - for (int32_t j = 0; j < range->vertex_count; j++) { - M_FillSpriteUVW(range->vertex_start + j); - M_FillAtlasSpriteSize(range->vertex_start + j); - } - } -} - -static void M_PrepareUVWs(void) -{ - m_Priv.uvws.count_objects = Output_GetObjectTextureCount(); - m_Priv.uvws.count_sprites = Output_GetSpriteTextureCount(); - m_Priv.uvws.count = m_Priv.uvws.count_objects + m_Priv.uvws.count_sprites; - m_Priv.uvws.data = Memory_Alloc(m_Priv.uvws.count * sizeof(M_UVW_PACK)); - m_Priv.uvws.data_objects = m_Priv.uvws.data; - m_Priv.uvws.data_sprites = m_Priv.uvws.data + m_Priv.uvws.count_objects; - m_Priv.uvws.animated_objects = - Memory_Alloc(m_Priv.uvws.count_objects * sizeof(bool)); - m_Priv.uvws.animated_sprites = - Memory_Alloc(m_Priv.uvws.count_sprites * sizeof(bool)); - M_FillObjectUVWs(); - M_FillSpriteUVWs(); -} - -static void M_PrepareEnvMap(void) -{ - glGenTextures(1, &m_Priv.tex_env_map); - glBindTexture(GL_TEXTURE_2D, m_Priv.tex_env_map); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - GFX_GL_CheckError(); -} - -static void M_PrepareAtlasSizes(void) -{ - const int32_t count_objects = Output_GetObjectTextureCount(); - const int32_t count_sprites = Output_GetSpriteTextureCount(); - const int32_t count = count_objects + count_sprites; - m_Priv.atlas_sizes.data = Memory_Realloc( - m_Priv.atlas_sizes.data, count * sizeof(OUTPUT_TEXTURE_SIZE)); - m_Priv.atlas_sizes.data_objects = m_Priv.atlas_sizes.data; - m_Priv.atlas_sizes.data_sprites = m_Priv.atlas_sizes.data + count_objects; - for (int32_t i = 0; i < count_objects; i++) { - M_FillAtlasObjectSize(i); - } - for (int32_t i = 0; i < count_sprites; i++) { - M_FillAtlasSpriteSize(i); - } -} - -static void M_UploadAtlas(void) -{ - glGenTextures(1, &m_Priv.tex_atlas); - glBindTexture(GL_TEXTURE_2D_ARRAY, m_Priv.tex_atlas); - glTexStorage3D( - GL_TEXTURE_2D_ARRAY, - 1, // number of mipmaps - GL_RGBA8, TEXTURE_PAGE_WIDTH, TEXTURE_PAGE_HEIGHT, - Output_GetTexturePageCount()); - GFX_GL_CheckError(); - - glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - GFX_GL_CheckError(); - - for (int32_t i = 0; i < Output_GetTexturePageCount(); i++) { - const RGBA_8888 *const input_ptr = Output_GetTexturePage32(i); - - glTexSubImage3D( - GL_TEXTURE_2D_ARRAY, - 0, // mipmap level - 0, // x offset - 0, // y offset - i, // z offset - TEXTURE_PAGE_WIDTH, TEXTURE_PAGE_HEIGHT, - 1, // depth - GL_RGBA, GL_UNSIGNED_BYTE, input_ptr); - } - GFX_GL_CheckError(); - - M_PrepareAtlasSizes(); - - GFX_GL_CheckError(); -} - -static void M_FreeLevelData(void) -{ - glBindTexture(GL_TEXTURE_BUFFER, 0); - glBindBuffer(GL_TEXTURE_BUFFER, 0); - if (m_Priv.tex_atlas != 0) { - glDeleteTextures(1, &m_Priv.tex_atlas); - m_Priv.tex_atlas = 0; - } - Memory_FreePointer(&m_Priv.uvws.data); - Memory_FreePointer(&m_Priv.uvws.animated_objects); - Memory_FreePointer(&m_Priv.uvws.animated_sprites); - Memory_FreePointer(&m_Priv.atlas_sizes.data); -} - -void Output_Textures_Init(void) -{ - M_PrepareEnvMap(); - m_AnimationRanges.objects = Vector_Create(sizeof(OUTPUT_VERTEX_RANGE)); - m_AnimationRanges.sprites = Vector_Create(sizeof(OUTPUT_VERTEX_RANGE)); -} - -void Output_Textures_Shutdown(void) -{ - Vector_Free(m_AnimationRanges.objects); - Vector_Free(m_AnimationRanges.sprites); - M_FreeLevelData(); - if (m_Priv.tex_env_map != 0) { - glDeleteTextures(1, &m_Priv.tex_env_map); - m_Priv.tex_env_map = 0; - } -} - -void Output_Textures_ObserveLevelLoad(void) -{ - M_FreeLevelData(); - M_PrepareUVWs(); - M_PrepareAnimationRanges(); - M_UploadAtlas(); -} - -void Output_Textures_UpdateEnvironmentMap(void) -{ - GLint viewport[4]; - glGetIntegerv(GL_VIEWPORT, viewport); - GFX_GL_CheckError(); - - const GLint vp_x = viewport[0]; - const GLint vp_y = viewport[1]; - const GLint vp_w = viewport[2]; - const GLint vp_h = viewport[3]; - - const int32_t side = MIN(vp_w, vp_h); - const int32_t x = vp_x + (vp_w - side) / 2; - const int32_t y = vp_y + (vp_h - side) / 2; - const int32_t w = side; - const int32_t h = side; - - glBindTexture(GL_TEXTURE_2D, m_Priv.tex_env_map); - glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, x, y, w, h, 0); - GFX_GL_CheckError(); -} - -void Output_Textures_CycleAnimations(void) -{ - if (m_Priv.uvws.count != 0) { - M_UpdateSpriteAnimatedUVWs(m_AnimationRanges.sprites); - M_UpdateObjectAnimatedUVWs(m_AnimationRanges.objects); - } -} - -GLuint Output_Textures_GetAtlasTexture(void) -{ - return m_Priv.tex_atlas; -} - -GLuint Output_Textures_GetEnvMapTexture(void) -{ - return m_Priv.tex_env_map; -} - -int32_t Output_Textures_GetSpritesUVWsBase(void) -{ - const size_t num = sizeof(M_UVW_PACK) / sizeof(OUTPUT_UVW); - ASSERT(num == 4); - return m_Priv.uvws.count_objects * num; -} - -OUTPUT_UVW Output_Textures_GetUVW(const int32_t uvw_idx) -{ - return m_Priv.uvws.data[uvw_idx / 4].corners[uvw_idx % 4]; -} - -OUTPUT_TEXTURE_SIZE Output_Textures_GetAtlasSize(const int32_t uvw_idx) -{ - return m_Priv.atlas_sizes.data[uvw_idx]; -} - -bool Output_Textures_IsObjectTextureAnimated(const int32_t texture_idx) -{ - return m_Priv.uvws.animated_objects[texture_idx]; -} - -bool Output_Textures_IsSpriteTextureAnimated(const int32_t uvw_idx) -{ - return m_Priv.uvws.animated_sprites[uvw_idx]; -} - -void Output_Textures_ApplyRenderSettings(void) -{ - // re-adjust UVs when the bilinear filter is toggled. - if (m_Priv.uvws.count != 0) { - M_FillObjectUVWs(); - } -} diff --git a/src/tr1/game/output/textures.h b/src/tr1/game/output/textures.h deleted file mode 100644 index 3bb18c77a..000000000 --- a/src/tr1/game/output/textures.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include - -#pragma pack(push, 1) -typedef struct { - float x0; - float y0; - float x1; - float y1; -} OUTPUT_TEXTURE_SIZE; - -typedef struct { - float u; - float v; - float w; -} OUTPUT_UVW; -#pragma pack(pop) - -void Output_Textures_Init(void); -void Output_Textures_Shutdown(void); -void Output_Textures_ObserveLevelLoad(void); -void Output_Textures_UpdateEnvironmentMap(void); -void Output_Textures_CycleAnimations(void); -void Output_Textures_ApplyRenderSettings(void); -GLuint Output_Textures_GetAtlasTexture(void); -GLuint Output_Textures_GetEnvMapTexture(void); -int32_t Output_Textures_GetSpritesUVWsBase(void); - -OUTPUT_UVW Output_Textures_GetUVW(int32_t uvw_idx); -OUTPUT_TEXTURE_SIZE Output_Textures_GetAtlasSize(int32_t uvw_idx); -bool Output_Textures_IsObjectTextureAnimated(int32_t texture_idx); -bool Output_Textures_IsSpriteTextureAnimated(int32_t sprite_idx); diff --git a/src/tr1/game/output/utils.h b/src/tr1/game/output/utils.h deleted file mode 100644 index 790208024..000000000 --- a/src/tr1/game/output/utils.h +++ /dev/null @@ -1,5 +0,0 @@ -#define OUTPUT_QUAD_VERTICES 6 -#define OUTPUT_TRI_VERTICES 3 - -#define OUTPUT_QUAD_TO_FAN(i) ((int32_t[]) { 0, 2, 1, 0, 3, 2 }[i]) -#define OUTPUT_TRI_TO_FAN(i) ((int32_t[]) { 0, 2, 1 }[i]) diff --git a/src/tr1/game/output/vertex_range.c b/src/tr1/game/output/vertex_range.c deleted file mode 100644 index f9ba9010e..000000000 --- a/src/tr1/game/output/vertex_range.c +++ /dev/null @@ -1,59 +0,0 @@ -#include "game/output/vertex_range.h" - -#include - -#include - -static int M_CompareRanges(const void *const a, const void *const b) -{ - const OUTPUT_VERTEX_RANGE *const range_a = (OUTPUT_VERTEX_RANGE *)a; - const OUTPUT_VERTEX_RANGE *const range_b = (OUTPUT_VERTEX_RANGE *)b; - return range_a->vertex_start - range_b->vertex_start; -} - -void Output_GlueVertexRanges(VECTOR *const target) -{ - ASSERT(target != nullptr); - if (target->count == 0) { - return; - } - - OUTPUT_VERTEX_RANGE *const ranges = - (OUTPUT_VERTEX_RANGE *)Vector_Get(target, 0); - - qsort(ranges, target->count, sizeof(OUTPUT_VERTEX_RANGE), M_CompareRanges); - - // Initialize a new index to store the merged ranges - int32_t new_range_count = 0; - - // Iterate over sorted ranges and merge them - for (int32_t i = 0; i < target->count; i++) { - if (new_range_count == 0) { - // First range - just copy it - ranges[new_range_count] = ranges[i]; - new_range_count++; - } else { - // Check if the previous range can be merged with the current one - OUTPUT_VERTEX_RANGE *const last_range = - &ranges[new_range_count - 1]; - const int32_t last_start = last_range->vertex_start; - const int32_t last_end = - last_range->vertex_start + last_range->vertex_count; - const int32_t current_start = ranges[i].vertex_start; - const int32_t current_end = - ranges[i].vertex_start + ranges[i].vertex_count; - - if (current_start >= last_start && current_start <= last_end) { - last_range->vertex_count = - current_end - last_range->vertex_start; - } else if (current_end >= last_start && current_end <= last_end) { - last_range->vertex_start = ranges[i].vertex_start; - } else { - ranges[new_range_count++] = ranges[i]; - } - } - } - - // Update the range vertex_count with the new number of merged ranges - target->count = new_range_count; -} diff --git a/src/tr1/game/output/vertex_range.h b/src/tr1/game/output/vertex_range.h deleted file mode 100644 index 2788ceca1..000000000 --- a/src/tr1/game/output/vertex_range.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include - -typedef struct { - int32_t vertex_start; - int32_t vertex_count; -} OUTPUT_VERTEX_RANGE; - -void Output_GlueVertexRanges(VECTOR *vertex_range); diff --git a/src/tr1/game/overlay.c b/src/tr1/game/overlay.c index 97d31063f..aa7f8b54a 100644 --- a/src/tr1/game/overlay.c +++ b/src/tr1/game/overlay.c @@ -7,8 +7,6 @@ #include "game/inventory.h" #include "game/items.h" #include "game/output.h" -#include "game/output/meshes/common.h" -#include "game/output/shader.h" #include "game/screen.h" #include "game/text.h" #include "game/viewport.h" @@ -17,7 +15,6 @@ #include "global/vars.h" #include -#include #include #include @@ -242,14 +239,14 @@ static void M_BarGetLocation( } if (g_GameInfo.showing_demo && bar_info->location == BL_BOTTOM_CENTER) { - *y -= M_GetBarToTextScale() * (TEXT_HEIGHT_FIXED + bar_spacing); + *y -= M_GetBarToTextScale() * (TEXT_HEIGHT + bar_spacing); } else if ( g_GameInfo.inv_ring_shown && GF_GetCurrentLevel() != nullptr && GF_GetCurrentLevel()->type == GFL_TITLE && (bar_info->location == BL_TOP_CENTER || bar_info->location == BL_BOTTOM_CENTER)) { *y = screen_margin_v + m_BarOffsetY[bar_info->location] - + M_GetBarToTextScale() * (TEXT_HEIGHT_FIXED + bar_spacing); + + M_GetBarToTextScale() * (TEXT_HEIGHT + bar_spacing); } m_BarOffsetY[bar_info->location] += *height + bar_spacing; @@ -379,7 +376,6 @@ static void M_DrawPickup3D(DISPLAY_PICKUP *pu) int16_t old_fov = Viewport_GetFOV(); Viewport_SetFOV(PICKUPS_FOV * DEG_1); Viewport_Init(vp_x1, vp_y1, vp_x2 - vp_x1, vp_y2 - vp_y1); - glViewport(vp_x1, -vp_y1, screen_width, screen_height); Output_ApplyFOV(); Matrix_PushUnit(); @@ -389,7 +385,7 @@ static void M_DrawPickup3D(DISPLAY_PICKUP *pu) Matrix_RotY(pu->rot_y); Output_SetLightDivider(0x6000); - Output_SetLightAdder(SHADE_LOW); + Output_SetLightAdder(LOW_LIGHT); Output_RotateLight(0, 0); const OBJECT *const obj = Object_Get(Inv_GetItemOption(pu->object_id)); @@ -426,7 +422,6 @@ static void M_DrawPickup3D(DISPLAY_PICKUP *pu) Matrix_Pop(); Viewport_Init(0, 0, Screen_GetResWidth(), Screen_GetResHeight()); Viewport_SetFOV(old_fov); - glViewport(0, 0, Screen_GetResWidth(), Screen_GetResHeight()); } static void M_DrawPickups3D(void) @@ -501,7 +496,7 @@ static void M_DrawPickupsSprites(void) Viewport_GetHeight() - sprite_height - sprite_height * pu->grid_y; const int32_t scale = Screen_GetRenderScaleGLRage(12288); const int16_t sprite_num = Object_Get(pu->object_id)->mesh_idx; - Output_DrawUISprite(x, y, scale, sprite_num, SHADE_NEUTRAL); + Output_DrawUISprite(x, y, scale, sprite_num, 4096); } } @@ -585,7 +580,7 @@ static void M_BarDrawEnemy(void) const OBJECT *const obj = Object_Get(g_Lara.target->object_id); m_EnemyBar.value = g_Lara.target->hit_points; m_EnemyBar.max_value = - obj->hit_points * (Game_IsBonusFlagSet(GBF_NGPLUS) ? 2 : 1); + obj->hit_points * ((g_GameInfo.bonus_flag & GBF_NGPLUS) ? 2 : 1); CLAMP(m_EnemyBar.value, 0, m_EnemyBar.max_value); Overlay_BarDraw(&m_EnemyBar, RSR_BAR); @@ -621,7 +616,8 @@ static void M_DrawAmmoInfo(void) double scale_ammo_to_bar = g_Config.ui.bar_scale / g_Config.ui.text_scale; - if (g_Lara.gun_status != LGS_READY || Game_IsBonusFlagSet(GBF_NGPLUS)) { + if (g_Lara.gun_status != LGS_READY + || (g_GameInfo.bonus_flag & GBF_NGPLUS)) { M_RemoveAmmoText(); return; } @@ -631,14 +627,13 @@ static void M_DrawAmmoInfo(void) case LGT_PISTOLS: return; case LGT_SHOTGUN: - sprintf( - ammo_string, "%6d A", g_Lara.shotgun_ammo.ammo / SHOTGUN_AMMO_CLIP); + sprintf(ammo_string, "%6d A", g_Lara.shotgun.ammo / SHOTGUN_AMMO_CLIP); break; case LGT_UZIS: - sprintf(ammo_string, "%6d C", g_Lara.uzi_ammo.ammo); + sprintf(ammo_string, "%6d C", g_Lara.uzis.ammo); break; case LGT_MAGNUMS: - sprintf(ammo_string, "%6d B", g_Lara.magnum_ammo.ammo); + sprintf(ammo_string, "%6d B", g_Lara.magnums.ammo); break; default: return; diff --git a/src/tr1/game/requester.c b/src/tr1/game/requester.c new file mode 100644 index 000000000..92a35168c --- /dev/null +++ b/src/tr1/game/requester.c @@ -0,0 +1,266 @@ +#include "game/requester.h" + +#include "game/input.h" +#include "game/screen.h" +#include "game/text.h" +#include "global/const.h" +#include "global/types.h" + +#include + +#include +#include +#include + +#define BOX_BORDER 2 +#define BOX_PADDING 10 + +static void M_SetItem( + REQUEST_INFO *req, const int32_t idx, const bool is_blocked, + const char *const fmt, va_list va); + +static void M_SetItem( + REQUEST_INFO *req, const int32_t idx, const bool is_blocked, + const char *const fmt, va_list va) +{ + if (req->items[idx].content_text) { + Memory_FreePointer(&req->items[idx].content_text); + } + + va_list va_dup; + va_copy(va_dup, va); + const size_t out_size = vsnprintf(nullptr, 0, fmt, va) + 1; + req->items[idx].content_text = Memory_Alloc(sizeof(char) * out_size); + vsnprintf(req->items[idx].content_text, out_size, fmt, va_dup); + req->items[idx].is_blocked = is_blocked; + va_end(va_dup); +} + +void Requester_Init(REQUEST_INFO *req, const uint16_t max_items) +{ + req->max_items = max_items; + req->items = Memory_Alloc(sizeof(REQUESTER_ITEM) * max_items); + Requester_ClearTextstrings(req); +} + +void Requester_Shutdown(REQUEST_INFO *req) +{ + Requester_ClearTextstrings(req); + + Memory_FreePointer(&req->heading_text); + if (req->items != nullptr) { + for (int i = 0; i < req->max_items; i++) { + Memory_FreePointer(&req->items[i].content_text); + } + } + Memory_FreePointer(&req->items); +} + +void Requester_ClearTextstrings(REQUEST_INFO *req) +{ + Text_Remove(req->heading); + req->heading = nullptr; + Text_Remove(req->background); + req->background = nullptr; + Text_Remove(req->moreup); + req->moreup = nullptr; + Text_Remove(req->moredown); + req->moredown = nullptr; + + if (req->items != nullptr) { + for (int i = 0; i < req->max_items; i++) { + Text_Remove(req->items[i].content); + req->items[i].content = nullptr; + } + } + + req->items_used = 0; +} + +int32_t Requester_Display(REQUEST_INFO *req) +{ + int32_t edge_y = req->y; + int32_t lines_height = req->vis_lines * req->line_height; + int32_t box_width = req->pix_width; + int32_t box_height = + req->line_height + lines_height + BOX_PADDING * 2 + BOX_BORDER * 2; + + int32_t line_one_off = edge_y - lines_height - BOX_PADDING; + int32_t box_y = line_one_off - req->line_height - BOX_PADDING - BOX_BORDER; + int32_t line_qty = req->vis_lines; + if (req->items_used < req->vis_lines) { + line_qty = req->items_used; + } + + if (!req->background) { + req->background = Text_Create(req->x, box_y, " "); + Text_CentreH(req->background, 1); + Text_AlignBottom(req->background, 1); + Text_AddBackground( + req->background, box_width, box_height, 0, 0, TS_BACKGROUND); + Text_AddOutline(req->background, TS_BACKGROUND); + } + + if (!req->heading) { + req->heading = Text_Create( + req->x, line_one_off - req->line_height - BOX_PADDING, + req->heading_text); + Text_CentreH(req->heading, 1); + Text_AlignBottom(req->heading, 1); + Text_AddBackground( + req->heading, req->pix_width - 2 * BOX_BORDER, 0, 0, 0, TS_HEADING); + Text_AddOutline(req->heading, TS_HEADING); + } + + if (g_InputDB.menu_down) { + if (req->requested < req->items_used - 1) { + req->requested++; + } + req->line_old_offset = req->line_offset; + if (req->requested > req->line_offset + req->vis_lines - 1) { + req->line_offset++; + } + } + + if (g_InputDB.menu_up) { + if (req->requested) { + req->requested--; + } + req->line_old_offset = req->line_offset; + if (req->requested < req->line_offset) { + req->line_offset--; + } + } + + if (req->line_offset > 0) { + if (!req->moreup) { + req->moreup = Text_Create( + req->x, line_one_off - req->line_height + 2, "\\{arrow up}"); + Text_SetScale( + req->moreup, TEXT_BASE_SCALE * 2 / 3, TEXT_BASE_SCALE * 2 / 3); + Text_CentreH(req->moreup, 1); + Text_AlignBottom(req->moreup, 1); + } + } else { + Text_Remove(req->moreup); + req->moreup = nullptr; + } + + if (req->items_used > req->vis_lines + req->line_offset) { + if (!req->moredown) { + req->moredown = Text_Create(req->x, edge_y - 12, "\\{arrow down}"); + Text_SetScale( + req->moredown, TEXT_BASE_SCALE * 2 / 3, + TEXT_BASE_SCALE * 2 / 3); + Text_CentreH(req->moredown, 1); + Text_AlignBottom(req->moredown, 1); + } + } else { + Text_Remove(req->moredown); + req->moredown = nullptr; + } + + for (int32_t i = 0; i < line_qty; i++) { + REQUESTER_ITEM *const item = &req->items[i]; + + const int32_t j = req->line_offset + i; + if (j < 0 || j >= req->items_used) { + Text_RemoveBackground(item->content); + Text_RemoveOutline(item->content); + continue; + } + REQUESTER_ITEM *const row_item = &req->items[j]; + + if (item->content == nullptr && row_item->content_text != nullptr) { + item->content = Text_Create( + 0, line_one_off + req->line_height * i, row_item->content_text); + Text_CentreH(item->content, 1); + Text_AlignBottom(item->content, 1); + } + if (req->line_offset + i == req->requested) { + Text_AddBackground( + item->content, req->pix_width - BOX_PADDING - 1 * BOX_BORDER, 0, + 0, 0, TS_REQUESTED); + Text_AddOutline(item->content, TS_REQUESTED); + } else { + Text_RemoveBackground(item->content); + Text_RemoveOutline(item->content); + } + } + + if (req->line_offset != req->line_old_offset) { + for (int i = 0; i < line_qty; i++) { + REQUESTER_ITEM *const item = &req->items[i]; + if (item->content != nullptr) { + Text_ChangeText( + item->content, + req->items[req->line_offset + i].content_text); + } + } + } + + if (g_InputDB.menu_confirm) { + if (req->is_blockable && req->items[req->requested].is_blocked) { + g_Input = (INPUT_STATE) {}; + return 0; + } else { + Requester_ClearTextstrings(req); + return req->requested + 1; + } + } else if (g_InputDB.menu_back) { + Requester_ClearTextstrings(req); + return -1; + } + + return 0; +} + +void Requester_SetHeading(REQUEST_INFO *req, const char *string) +{ + Text_Remove(req->heading); + req->heading = nullptr; + if (req->heading_text) { + Memory_FreePointer(&req->heading_text); + } + + const size_t out_size = snprintf(nullptr, 0, "%s", string) + 1; + req->heading_text = Memory_Alloc(sizeof(char) * out_size); + snprintf(req->heading_text, out_size, "%s", string); +} + +void Requester_ChangeItem( + REQUEST_INFO *req, const int32_t idx, const bool is_blocked, + const char *const fmt, ...) +{ + if (idx < 0 || idx >= req->max_items || !fmt) { + return; + } + + va_list va; + va_start(va, fmt); + M_SetItem(req, idx, is_blocked, fmt, va); + va_end(va); +} + +void Requester_AddItem( + REQUEST_INFO *req, const bool is_blocked, const char *const fmt, ...) +{ + if (req->items_used >= req->max_items || !fmt) { + return; + } + + va_list va; + va_start(va, fmt); + M_SetItem(req, req->items_used, is_blocked, fmt, va); + va_end(va); + req->items_used++; +} + +void Requester_SetSize(REQUEST_INFO *req, int32_t max_lines, int16_t y) +{ + req->y = y; + req->vis_lines = Screen_GetResHeightDownscaled(RSR_TEXT) / 2 / MAX_REQLINES; + if (req->vis_lines > max_lines) { + req->vis_lines = max_lines; + } +} diff --git a/src/tr1/game/requester.h b/src/tr1/game/requester.h new file mode 100644 index 000000000..ad0c2a30e --- /dev/null +++ b/src/tr1/game/requester.h @@ -0,0 +1,16 @@ +#pragma once + +#include "global/types.h" + +#include + +void Requester_Init(REQUEST_INFO *req, uint16_t num_items); +void Requester_Shutdown(REQUEST_INFO *req); +void Requester_ClearTextstrings(REQUEST_INFO *req); +int32_t Requester_Display(REQUEST_INFO *req); +void Requester_SetHeading(REQUEST_INFO *req, const char *string); +void Requester_ChangeItem( + REQUEST_INFO *req, int32_t idx, bool is_blocked, const char *fmt, ...); +void Requester_AddItem( + REQUEST_INFO *req, bool is_blocked, const char *fmt, ...); +void Requester_SetSize(REQUEST_INFO *req, int32_t max_lines, int16_t y); diff --git a/src/tr1/game/room.c b/src/tr1/game/room.c index 696f06afd..d4741cec8 100644 --- a/src/tr1/game/room.c +++ b/src/tr1/game/room.c @@ -14,7 +14,6 @@ #include "game/savegame.h" #include "game/shell.h" #include "game/sound.h" -#include "game/stats.h" #include "global/vars.h" #include @@ -26,8 +25,7 @@ static bool M_TestLava(const ITEM *const item); static void M_TriggerMusicTrack(int16_t track, const TRIGGER *const trigger) { - if (track == MX_UNUSED_0 - && (trigger->type == TT_ANTIPAD || trigger->type == TT_ANTITRIGGER)) { + if (track == MX_UNUSED_0 && trigger->type == TT_ANTIPAD) { Music_Stop(); return; } @@ -93,7 +91,7 @@ static void M_TriggerMusicTrack(int16_t track, const TRIGGER *const trigger) if (trigger->type == TT_SWITCH) { flags ^= trigger->mask; - } else if (trigger->type == TT_ANTIPAD || trigger->type == TT_ANTITRIGGER) { + } else if (trigger->type == TT_ANTIPAD) { flags &= -1 - trigger->mask; } else if (trigger->mask) { flags |= trigger->mask; @@ -221,7 +219,7 @@ SECTOR *Room_GetSector(int32_t x, int32_t y, int32_t z, int16_t *room_num) return sector; } -int32_t Room_GetWaterHeight(int32_t x, int32_t y, int32_t z, int16_t room_num) +int16_t Room_GetWaterHeight(int32_t x, int32_t y, int32_t z, int16_t room_num) { const ROOM *room = Room_Get(room_num); @@ -405,6 +403,9 @@ void Room_TestSectorTrigger(const ITEM *const item, const SECTOR *const sector) } } else { switch (trigger->type) { + case TT_TRIGGER: + break; + case TT_SWITCH: { if (!Switch_Trigger(trigger->item_index, trigger->timer)) { return; @@ -444,9 +445,6 @@ void Room_TestSectorTrigger(const ITEM *const item, const SECTOR *const sector) return; } break; - - default: - break; } } @@ -467,9 +465,7 @@ void Room_TestSectorTrigger(const ITEM *const item, const SECTOR *const sector) if (trigger->type == TT_SWITCH) { item->flags ^= trigger->mask; - } else if ( - trigger->type == TT_ANTIPAD - || trigger->type == TT_ANTITRIGGER) { + } else if (trigger->type == TT_ANTIPAD) { item->flags &= -1 - trigger->mask; } else if (trigger->mask) { item->flags |= trigger->mask; @@ -630,9 +626,14 @@ void Room_TestSectorTrigger(const ITEM *const item, const SECTOR *const sector) case TO_SECRET: { const int16_t secret_num = 1 << (int16_t)(intptr_t)cmd->parameter; - if (Stats_AddSecret(secret_num)) { - Music_Play(MX_SECRET, MPM_ALWAYS); + RESUME_INFO *resume_info = + Savegame_GetCurrentInfo(Game_GetCurrentLevel()); + if (resume_info->stats.secret_flags & secret_num) { + break; } + resume_info->stats.secret_flags |= secret_num; + resume_info->stats.secret_count++; + Music_Play(MX_SECRET, MPM_ALWAYS); break; } } diff --git a/src/tr1/game/room.h b/src/tr1/game/room.h index 984a796f6..d203dddfa 100644 --- a/src/tr1/game/room.h +++ b/src/tr1/game/room.h @@ -8,6 +8,7 @@ #include void Room_GetNewRoom(int32_t x, int32_t y, int32_t z, int16_t room_num); +int16_t Room_GetWaterHeight(int32_t x, int32_t y, int32_t z, int16_t room_num); void Room_TestTriggers(const ITEM *item); void Room_TestSectorTrigger(const ITEM *item, const SECTOR *sector); diff --git a/src/tr1/game/room_draw.c b/src/tr1/game/room_draw.c index be517a6a3..8caa8ba73 100644 --- a/src/tr1/game/room_draw.c +++ b/src/tr1/game/room_draw.c @@ -5,7 +5,6 @@ #include "game/items.h" #include "game/lara/draw.h" #include "game/output.h" -#include "game/output/sprites.h" #include "game/shell.h" #include "game/viewport.h" #include "global/const.h" @@ -280,7 +279,8 @@ void Room_DrawSingleRoom(int16_t room_num) g_PhdTop = room->bound_top; g_PhdBottom = room->bound_bottom; - Output_DrawRoomMesh(room); + Output_LightRoom(room); + Output_DrawRoom(&room->mesh); int16_t item_num = room->item_num; while (item_num != NO_ITEM) { @@ -321,11 +321,6 @@ void Room_DrawSingleRoom(int16_t room_num) if (g_Config.rendering.enable_debug_portals) { Output_DrawRoomPortals(room); } - - Output_RememberState(); - Output_Sprites_RenderRoomSprites(g_MatrixPtr, Output_GetTint(), room); - Output_Sprites_Flush(); - Output_RestoreState(); Matrix_Pop(); room->bound_left = Viewport_GetMaxX(); diff --git a/src/tr1/game/savegame.h b/src/tr1/game/savegame.h index 18cbe44f9..19aa39070 100644 --- a/src/tr1/game/savegame.h +++ b/src/tr1/game/savegame.h @@ -1,3 +1,84 @@ #pragma once +#include "global/types.h" + #include + +#include + +// Loading a saved game is divided into two phases. First, the game reads the +// savegame file contents to look for the level number. The rest of the save +// data is stored in a special buffer in the g_GameInfo. Then the engine +// continues to execute the normal game flow and loads the specified level. +// Second phase occurs after everything finishes loading, e.g. items, +// creatures, triggers etc., and is what actually sets Lara's health, creatures +// status, triggers, inventory etc. + +#define SAVEGAME_CURRENT_VERSION 7 + +typedef enum { + VERSION_LEGACY = -1, + VERSION_0 = 0, + VERSION_1 = 1, + VERSION_2 = 2, + VERSION_3 = 3, + VERSION_4 = 4, + VERSION_5 = 5, + VERSION_6 = 6, + + VERSION_7 = 7, + // Added extra footer after the compressed BSON structure for quicker + // access to essential data, such as the level counter and the level title, + // without the need to parse the entire BSON document. +} SAVEGAME_VERSION; + +typedef enum { + SAVEGAME_FORMAT_LEGACY = 1, + SAVEGAME_FORMAT_BSON = 2, +} SAVEGAME_FORMAT; + +typedef struct { + SAVEGAME_FORMAT format; + char *full_path; + int32_t counter; + int32_t level_num; + char *level_title; + int16_t initial_version; + struct { + bool restart; + bool select_level; + } features; +} SAVEGAME_INFO; + +void Savegame_Init(void); +void Savegame_Shutdown(void); +bool Savegame_IsInitialised(void); + +void Savegame_InitCurrentInfo(void); + +int32_t Savegame_GetLevelNumber(int32_t slot_num); + +bool Savegame_Load(int32_t slot_num); +bool Savegame_Save(int32_t slot_num); +bool Savegame_UpdateDeathCounters(int32_t slot_num, GAME_INFO *game_info); +bool Savegame_LoadOnlyResumeInfo(int32_t slot_num, GAME_INFO *game_info); + +void Savegame_ScanSavedGames(void); +void Savegame_FillAvailableSaves(REQUEST_INFO *req); +void Savegame_FillAvailableLevels(REQUEST_INFO *req); +void Savegame_HighlightNewestSlot(void); +bool Savegame_RestartAvailable(int32_t slot_num); + +RESUME_INFO *Savegame_GetCurrentInfo(const GF_LEVEL *level); + +void Savegame_ApplyLogicToCurrentInfo(const GF_LEVEL *level); +void Savegame_ResetCurrentInfo(const GF_LEVEL *level); +void Savegame_CarryCurrentInfoToNextLevel( + const GF_LEVEL *src_level, const GF_LEVEL *dst_level); + +// Persist Lara's inventory to the current info. +// Used to carry over Lara's inventory between levels. +void Savegame_PersistGameToCurrentInfo(const GF_LEVEL *level); + +void Savegame_ProcessItemsBeforeLoad(void); +void Savegame_ProcessItemsBeforeSave(void); diff --git a/src/tr1/game/savegame/savegame.c b/src/tr1/game/savegame/savegame.c index 5dde9f215..938f08d0d 100644 --- a/src/tr1/game/savegame/savegame.c +++ b/src/tr1/game/savegame/savegame.c @@ -3,20 +3,293 @@ #include "game/game.h" #include "game/game_flow.h" #include "game/game_string.h" +#include "game/inventory.h" +#include "game/items.h" +#include "game/lot.h" +#include "game/objects/vars.h" +#include "game/requester.h" +#include "game/room.h" +#include "game/savegame/savegame_bson.h" +#include "game/savegame/savegame_legacy.h" +#include "global/const.h" +#include "global/types.h" #include "global/vars.h" +#include #include #include -#include +#include +#include +#include +#include +#include -int32_t Savegame_GetSlotCount(void) +#include +#include +#include + +#define SAVES_DIR "saves" + +typedef struct { + bool allow_load; + bool allow_save; + SAVEGAME_FORMAT format; + const char *(*get_save_filename_pattern)(void); + bool (*fill_info)(MYFILE *fp, SAVEGAME_INFO *info); + bool (*load_from_file)(MYFILE *fp, GAME_INFO *game_info); + bool (*load_only_resume_info)(MYFILE *fp, GAME_INFO *game_info); + void (*save_to_file)(MYFILE *fp, GAME_INFO *game_info); + bool (*update_death_counters)(MYFILE *fp, GAME_INFO *game_info); +} SAVEGAME_STRATEGY; + +static int32_t m_SaveSlots = 0; +static int16_t m_NewestSlot = -1; +static SAVEGAME_INFO *m_SavegameInfo = nullptr; + +static const SAVEGAME_STRATEGY m_Strategies[] = { + { + .allow_load = true, + .allow_save = true, + .format = SAVEGAME_FORMAT_BSON, + .get_save_filename_pattern = Savegame_BSON_GetSaveFilePattern, + .fill_info = Savegame_BSON_FillInfo, + .load_from_file = Savegame_BSON_LoadFromFile, + .load_only_resume_info = Savegame_BSON_LoadOnlyResumeInfo, + .save_to_file = Savegame_BSON_SaveToFile, + .update_death_counters = Savegame_BSON_UpdateDeathCounters, + }, + { + .allow_load = true, + .allow_save = false, + .format = SAVEGAME_FORMAT_LEGACY, + .get_save_filename_pattern = Savegame_Legacy_GetSaveFilePattern, + .fill_info = Savegame_Legacy_FillInfo, + .load_from_file = Savegame_Legacy_LoadFromFile, + .load_only_resume_info = Savegame_Legacy_LoadOnlyResumeInfo, + .save_to_file = nullptr, + .update_death_counters = Savegame_Legacy_UpdateDeathCounters, + }, + { 0 }, +}; + +static void M_ClearSlots(void); +static bool M_FillSlot( + const SAVEGAME_STRATEGY *strategy, int32_t slot_num, const char *path); +static void M_ScanSavedGamesDir(const char *dir_path); +static void M_LoadPreprocess(void); +static void M_LoadPostprocess(void); + +static void M_ClearSlots(void) { - return g_Config.gameplay.maximum_save_slots; + if (m_SavegameInfo == nullptr) { + return; + } + + for (int32_t i = 0; i < m_SaveSlots; i++) { + SAVEGAME_INFO *const savegame_info = &m_SavegameInfo[i]; + savegame_info->format = 0; + savegame_info->counter = -1; + savegame_info->level_num = -1; + Memory_FreePointer(&savegame_info->full_path); + Memory_FreePointer(&savegame_info->level_title); + } } -void Savegame_HighlightNewestSlot(void) +static bool M_FillSlot( + const SAVEGAME_STRATEGY *const strategy, const int32_t slot_num, + const char *const path) { - g_GameInfo.select_save_slot = Savegame_GetMostRecentlyCreatedSlot(); + SAVEGAME_INFO *const savegame_info = &m_SavegameInfo[slot_num]; + if (strategy->format <= savegame_info->format) { + // do not override already filled slots or for less preferred strategies + return true; + } + + bool result = false; + MYFILE *const fp = File_Open(path, FILE_OPEN_READ); + if (fp != nullptr) { + if (strategy->fill_info(fp, savegame_info)) { + savegame_info->format = strategy->format; + Memory_FreePointer(&savegame_info->full_path); + savegame_info->full_path = Memory_DupStr(path); + result = true; + } + File_Close(fp); + } + return result; +} + +static void M_ScanSavedGamesDir(const char *const dir_path) +{ + void *dir_handle = File_OpenDirectory(dir_path); + if (dir_handle == nullptr) { + return; + } + + while (true) { + const char *file_name = File_ReadDirectory(dir_handle); + if (file_name == nullptr) { + break; + } + if (strcmp(file_name, ".") == 0 || strcmp(file_name, "..") == 0) { + continue; + } + + for (const SAVEGAME_STRATEGY *strategy = m_Strategies; strategy->format; + strategy++) { + if (!strategy->allow_load) { + continue; + } + + int32_t slot = -1; + int32_t parsed = + sscanf(file_name, strategy->get_save_filename_pattern(), &slot); + if (parsed == 1 && slot >= 0 && slot < m_SaveSlots) { + char *file_path = String_Format("%s/%s", dir_path, file_name); + M_FillSlot(strategy, slot, file_path); + Memory_FreePointer(&file_path); + } + } + } + + File_CloseDirectory(dir_handle); +} + +static void M_LoadPreprocess(void) +{ + Savegame_InitCurrentInfo(); +} + +static void M_LoadPostprocess(void) +{ + for (int32_t i = 0; i < Item_GetLevelCount(); i++) { + ITEM *const item = Item_Get(i); + const OBJECT *const obj = Object_Get(item->object_id); + + if (obj->save_position && obj->shadow_size) { + int16_t room_num = item->room_num; + const SECTOR *const sector = Room_GetSector( + item->pos.x, item->pos.y, item->pos.z, &room_num); + item->floor = + Room_GetHeight(sector, item->pos.x, item->pos.y, item->pos.z); + } + + if (obj->save_flags != 0) { + item->flags &= 0xFF00; + } + + if (obj->handle_save_func != nullptr) { + obj->handle_save_func(item, SAVEGAME_STAGE_AFTER_LOAD); + } + } + + MovableBlock_SetupFloor(); + + if (g_GameInfo.bonus_flag) { + g_Config.profile.new_game_plus_unlock = true; + } + + LOT_ClearLOT(&g_Lara.lot); +} + +void Savegame_Init(void) +{ + g_GameInfo.current = Memory_Alloc( + sizeof(RESUME_INFO) + * (GF_GetLevelTable(GFLT_MAIN)->count + + (GF_GetLevelTable(GFLT_DEMOS)->count >= 0 ? 1 : 0))); + m_SaveSlots = g_Config.gameplay.maximum_save_slots; + m_SavegameInfo = Memory_Alloc(sizeof(SAVEGAME_INFO) * m_SaveSlots); + + const GF_LEVEL_TABLE *const level_table = GF_GetLevelTable(GFLT_DEMOS); + for (int32_t i = 0; i < level_table->count; i++) { + RESUME_INFO *const resume_info = + Savegame_GetCurrentInfo(&level_table->levels[i]); + resume_info->flags.available = 1; + resume_info->flags.costume = 0; + resume_info->num_medis = 0; + resume_info->num_big_medis = 0; + resume_info->num_scions = 0; + resume_info->flags.got_pistols = 1; + resume_info->flags.got_shotgun = 0; + resume_info->flags.got_magnums = 0; + resume_info->flags.got_uzis = 0; + resume_info->pistol_ammo = 1000; + resume_info->shotgun_ammo = 0; + resume_info->magnum_ammo = 0; + resume_info->uzi_ammo = 0; + resume_info->gun_status = LGS_ARMLESS; + resume_info->equipped_gun_type = LGT_PISTOLS; + resume_info->holsters_gun_type = LGT_PISTOLS; + resume_info->back_gun_type = LGT_UNARMED; + resume_info->lara_hitpoints = LARA_MAX_HITPOINTS; + } +} + +RESUME_INFO *Savegame_GetCurrentInfo(const GF_LEVEL *const level) +{ + ASSERT(level != nullptr); + if (GF_GetLevelTableType(level->type) == GFLT_MAIN) { + return &g_GameInfo.current[level->num]; + } else if (level->type == GFL_DEMO) { + return &g_GameInfo.current[GF_GetLevelTable(GFLT_MAIN)->count]; + } + LOG_WARNING( + "Warning: unable to get resume info for level %d (type=%s)", level->num, + ENUM_MAP_TO_STRING(GF_LEVEL_TYPE, level->type)); + return nullptr; +} + +void Savegame_Shutdown(void) +{ + M_ClearSlots(); + Memory_FreePointer(&m_SavegameInfo); + Memory_FreePointer(&g_GameInfo.current); +} + +bool Savegame_IsInitialised(void) +{ + return m_SavegameInfo != nullptr; +} + +void Savegame_ProcessItemsBeforeLoad(void) +{ + for (int32_t i = 0; i < Item_GetLevelCount(); i++) { + ITEM *const item = Item_Get(i); + const OBJECT *const obj = Object_Get(item->object_id); + if (obj->handle_save_func != nullptr) { + obj->handle_save_func(item, SAVEGAME_STAGE_BEFORE_LOAD); + } + } +} + +void Savegame_ProcessItemsBeforeSave(void) +{ + for (int32_t i = 0; i < Item_GetLevelCount(); i++) { + ITEM *const item = Item_Get(i); + const OBJECT *const obj = Object_Get(item->object_id); + if (obj->handle_save_func != nullptr) { + obj->handle_save_func(item, SAVEGAME_STAGE_BEFORE_SAVE); + } + } +} + +void Savegame_InitCurrentInfo(void) +{ + g_GameInfo.death_count = 0; + const GF_LEVEL_TABLE *const level_table = GF_GetLevelTable(GFLT_MAIN); + for (int32_t i = 0; i < level_table->count; i++) { + const GF_LEVEL *const level = &level_table->levels[i]; + Savegame_ResetCurrentInfo(level); + Savegame_ApplyLogicToCurrentInfo(level); + Savegame_GetCurrentInfo(level)->flags.available = 0; + } + if (GF_GetGymLevel() != nullptr) { + Savegame_GetCurrentInfo(GF_GetGymLevel())->flags.available = 1; + } + if (GF_GetFirstLevel() != nullptr) { + Savegame_GetCurrentInfo(GF_GetFirstLevel())->flags.available = 1; + } } void Savegame_ApplyLogicToCurrentInfo(const GF_LEVEL *const level) @@ -32,17 +305,17 @@ void Savegame_ApplyLogicToCurrentInfo(const GF_LEVEL *const level) if (level == GF_GetGymLevel()) { current->flags.available = 1; current->flags.costume = 1; - current->small_medipacks = 0; - current->large_medipacks = 0; + current->num_medis = 0; + current->num_big_medis = 0; current->num_scions = 0; current->pistol_ammo = 0; current->shotgun_ammo = 0; current->magnum_ammo = 0; current->uzi_ammo = 0; - current->flags.has_pistols = 0; - current->flags.has_shotgun = 0; - current->flags.has_magnums = 0; - current->flags.has_uzis = 0; + current->flags.got_pistols = 0; + current->flags.got_shotgun = 0; + current->flags.got_magnums = 0; + current->flags.got_uzis = 0; current->equipped_gun_type = LGT_UNARMED; current->holsters_gun_type = LGT_UNARMED; current->back_gun_type = LGT_UNARMED; @@ -52,28 +325,28 @@ void Savegame_ApplyLogicToCurrentInfo(const GF_LEVEL *const level) if (level == GF_GetFirstLevel()) { current->flags.available = 1; current->flags.costume = 0; - current->small_medipacks = 0; - current->large_medipacks = 0; + current->num_medis = 0; + current->num_big_medis = 0; current->num_scions = 0; current->pistol_ammo = 1000; current->shotgun_ammo = 0; current->magnum_ammo = 0; current->uzi_ammo = 0; - current->flags.has_pistols = 1; - current->flags.has_shotgun = 0; - current->flags.has_magnums = 0; - current->flags.has_uzis = 0; + current->flags.got_pistols = 1; + current->flags.got_shotgun = 0; + current->flags.got_magnums = 0; + current->flags.got_uzis = 0; current->equipped_gun_type = LGT_PISTOLS; current->holsters_gun_type = LGT_PISTOLS; current->back_gun_type = LGT_UNARMED; current->gun_status = LGS_ARMLESS; } - if (Game_IsBonusFlagSet(GBF_NGPLUS) && level != GF_GetGymLevel()) { - current->flags.has_pistols = 1; - current->flags.has_shotgun = 1; - current->flags.has_magnums = 1; - current->flags.has_uzis = 1; + if ((g_GameInfo.bonus_flag & GBF_NGPLUS) && level != GF_GetGymLevel()) { + current->flags.got_pistols = 1; + current->flags.got_shotgun = 1; + current->flags.got_magnums = 1; + current->flags.got_uzis = 1; current->shotgun_ammo = 1234; current->magnum_ammo = 1234; current->uzi_ammo = 1234; @@ -92,11 +365,11 @@ void Savegame_ApplyLogicToCurrentInfo(const GF_LEVEL *const level) current->holsters_gun_type = current->equipped_gun_type; break; case LGT_SHOTGUN: - if (current->flags.has_pistols) { + if (current->flags.got_pistols) { current->holsters_gun_type = LGT_PISTOLS; - } else if (current->flags.has_magnums) { + } else if (current->flags.got_magnums) { current->holsters_gun_type = LGT_MAGNUMS; - } else if (current->flags.has_uzis) { + } else if (current->flags.got_uzis) { current->holsters_gun_type = LGT_UZIS; } else { current->holsters_gun_type = LGT_UNARMED; @@ -108,10 +381,338 @@ void Savegame_ApplyLogicToCurrentInfo(const GF_LEVEL *const level) } } if (current->back_gun_type == LGT_UNKNOWN) { - if (current->flags.has_shotgun) { + if (current->flags.got_shotgun) { current->back_gun_type = LGT_SHOTGUN; } else { current->back_gun_type = LGT_UNARMED; } } } + +void Savegame_ResetCurrentInfo(const GF_LEVEL *const level) +{ + LOG_INFO("Resetting resume info for level #%d", level->num); + RESUME_INFO *const current = Savegame_GetCurrentInfo(level); + memset(current, 0, sizeof(RESUME_INFO)); +} + +void Savegame_CarryCurrentInfoToNextLevel( + const GF_LEVEL *const src_level, const GF_LEVEL *const dst_level) +{ + LOG_INFO( + "Copying resume info from level #%d to level #%d", src_level->num, + dst_level->num); + RESUME_INFO *const src_resume = Savegame_GetCurrentInfo(src_level); + RESUME_INFO *const dst_resume = Savegame_GetCurrentInfo(dst_level); + memcpy(dst_resume, src_resume, sizeof(RESUME_INFO)); +} + +void Savegame_PersistGameToCurrentInfo(const GF_LEVEL *const level) +{ + ASSERT(level != nullptr); + RESUME_INFO *current = Savegame_GetCurrentInfo(level); + + current->lara_hitpoints = g_LaraItem->hit_points; + current->flags.available = 1; + + current->pistol_ammo = 1000; + if (Inv_RequestItem(O_PISTOL_ITEM)) { + current->flags.got_pistols = 1; + } else { + current->flags.got_pistols = 0; + } + + if (Inv_RequestItem(O_MAGNUM_ITEM)) { + current->magnum_ammo = g_Lara.magnums.ammo; + current->flags.got_magnums = 1; + } else { + current->magnum_ammo = + Inv_RequestItem(O_MAG_AMMO_ITEM) * MAGNUM_AMMO_QTY; + current->flags.got_magnums = 0; + } + + if (Inv_RequestItem(O_UZI_ITEM)) { + current->uzi_ammo = g_Lara.uzis.ammo; + current->flags.got_uzis = 1; + } else { + current->uzi_ammo = Inv_RequestItem(O_UZI_AMMO_ITEM) * UZI_AMMO_QTY; + current->flags.got_uzis = 0; + } + + if (Inv_RequestItem(O_SHOTGUN_ITEM)) { + current->shotgun_ammo = g_Lara.shotgun.ammo; + current->flags.got_shotgun = 1; + } else { + current->shotgun_ammo = + Inv_RequestItem(O_SG_AMMO_ITEM) * SHOTGUN_AMMO_QTY; + current->flags.got_shotgun = 0; + } + + current->num_medis = Inv_RequestItem(O_MEDI_ITEM); + current->num_big_medis = Inv_RequestItem(O_BIGMEDI_ITEM); + current->num_scions = Inv_RequestItem(O_SCION_ITEM_1); + + current->equipped_gun_type = g_Lara.gun_type; + current->holsters_gun_type = g_Lara.holsters_gun_type; + current->back_gun_type = g_Lara.back_gun_type; + if (g_Lara.gun_status == LGS_READY) { + current->gun_status = LGS_READY; + } else { + current->gun_status = LGS_ARMLESS; + } +} + +int32_t Savegame_GetLevelNumber(const int32_t slot_num) +{ + return m_SavegameInfo[slot_num].level_num; +} + +int32_t Savegame_GetSlotCount(void) +{ + return m_SaveSlots; +} + +bool Savegame_IsSlotFree(const int32_t slot_num) +{ + return m_SavegameInfo[slot_num].level_num == -1; +} + +bool Savegame_Load(const int32_t slot_num) +{ + GAME_INFO *const game_info = &g_GameInfo; + SAVEGAME_INFO *savegame_info = &m_SavegameInfo[slot_num]; + ASSERT(savegame_info->format != 0); + + M_LoadPreprocess(); + + bool ret = false; + const SAVEGAME_STRATEGY *strategy = m_Strategies; + while (strategy->format) { + if (savegame_info->format == strategy->format) { + MYFILE *fp = File_Open(savegame_info->full_path, FILE_OPEN_READ); + if (fp) { + ret = strategy->load_from_file(fp, game_info); + File_Close(fp); + } + break; + } + strategy++; + } + + if (ret) { + M_LoadPostprocess(); + } + + g_GameInfo.save_initial_version = m_SavegameInfo[slot_num].initial_version; + + return ret; +} + +bool Savegame_Save(const int32_t slot_num) +{ + GAME_INFO *const game_info = &g_GameInfo; + bool ret = false; + Savegame_BindSlot(slot_num); + + File_CreateDirectory(SAVES_DIR); + + const GF_LEVEL *const current_level = Game_GetCurrentLevel(); + Savegame_PersistGameToCurrentInfo(current_level); + + const GF_LEVEL_TABLE *const level_table = GF_GetLevelTable(GFLT_MAIN); + for (int32_t i = 0; i < level_table->count; i++) { + const GF_LEVEL *const level = &level_table->levels[i]; + if (level->type == GFL_CURRENT) { + game_info->current[i] = game_info->current[current_level->num]; + } + } + const char *const level_title = Game_GetCurrentLevel()->title; + + SAVEGAME_INFO *savegame_info = &m_SavegameInfo[slot_num]; + const bool was_slot_empty = savegame_info->full_path == nullptr; + const SAVEGAME_STRATEGY *strategy = m_Strategies; + while (strategy->format != 0) { + if (strategy->allow_save && strategy->save_to_file != nullptr) { + char *filename = + String_Format(strategy->get_save_filename_pattern(), slot_num); + char *full_path = String_Format("%s/%s", SAVES_DIR, filename); + + MYFILE *const fp = File_Open(full_path, FILE_OPEN_WRITE); + if (fp != nullptr) { + g_SaveCounter++; + strategy->save_to_file(fp, game_info); + savegame_info->format = strategy->format; + Memory_FreePointer(&savegame_info->full_path); + savegame_info->full_path = Memory_DupStr(File_GetPath(fp)); + savegame_info->counter = g_SaveCounter; + savegame_info->level_num = current_level->num; + savegame_info->level_title = level_title != nullptr + ? Memory_DupStr(level_title) + : nullptr; + File_Close(fp); + ret = true; + } + + Memory_FreePointer(&filename); + Memory_FreePointer(&full_path); + } + strategy++; + } + + if (ret) { + m_NewestSlot = slot_num; + if (was_slot_empty) { + g_SavedGamesCount++; + } + Savegame_HighlightNewestSlot(); + } + + return ret; +} + +bool Savegame_UpdateDeathCounters(int32_t slot_num, GAME_INFO *game_info) +{ + ASSERT(game_info != nullptr); + ASSERT(slot_num >= 0); + SAVEGAME_INFO *savegame_info = &m_SavegameInfo[slot_num]; + ASSERT(savegame_info->format != 0); + + bool ret = false; + const SAVEGAME_STRATEGY *strategy = &m_Strategies[0]; + while (strategy->format) { + if (savegame_info->format == strategy->format) { + MYFILE *fp = + File_Open(savegame_info->full_path, FILE_OPEN_READ_WRITE); + if (fp) { + ret = strategy->update_death_counters(fp, game_info); + File_Close(fp); + } else + break; + } + strategy++; + } + return ret; +} + +bool Savegame_LoadOnlyResumeInfo(int32_t slot_num, GAME_INFO *game_info) +{ + ASSERT(game_info != nullptr); + SAVEGAME_INFO *savegame_info = &m_SavegameInfo[slot_num]; + ASSERT(savegame_info->format != 0); + + bool ret = false; + const SAVEGAME_STRATEGY *strategy = &m_Strategies[0]; + while (strategy->format) { + if (savegame_info->format == strategy->format) { + MYFILE *fp = File_Open(savegame_info->full_path, FILE_OPEN_READ); + if (fp) { + ret = strategy->load_only_resume_info(fp, game_info); + File_Close(fp); + } + break; + } + strategy++; + } + + g_GameInfo.save_initial_version = m_SavegameInfo[slot_num].initial_version; + + return ret; +} + +void Savegame_ScanSavedGames(void) +{ + BENCHMARK benchmark = Benchmark_Start(); + M_ClearSlots(); + + g_SaveCounter = 0; + g_SavedGamesCount = 0; + m_NewestSlot = -1; + + M_ScanSavedGamesDir(SAVES_DIR); + M_ScanSavedGamesDir("."); + + for (int32_t i = 0; i < m_SaveSlots; i++) { + SAVEGAME_INFO *savegame_info = &m_SavegameInfo[i]; + if (savegame_info->level_title != nullptr) { + if (savegame_info->counter > g_SaveCounter) { + g_SaveCounter = savegame_info->counter; + m_NewestSlot = i; + } + g_SavedGamesCount++; + } + } + Benchmark_End(&benchmark, nullptr); +} + +void Savegame_FillAvailableSaves(REQUEST_INFO *req) +{ + Requester_Shutdown(req); + Requester_Init(req, Savegame_GetSlotCount()); + + for (int i = 0; i < req->max_items; i++) { + SAVEGAME_INFO *savegame_info = &m_SavegameInfo[i]; + + if (savegame_info->level_title != nullptr) { + Requester_AddItem( + req, false, "%s %d", savegame_info->level_title, + savegame_info->counter); + } else { + Requester_AddItem(req, true, GS(MISC_EMPTY_SLOT_FMT), i + 1); + } + } + + if (req->requested >= req->vis_lines) { + req->line_offset = req->requested - req->vis_lines + 1; + } else if (req->requested < req->line_offset) { + req->line_offset = req->requested; + } +} + +void Savegame_FillAvailableLevels(REQUEST_INFO *req) +{ + ASSERT(req != nullptr); + const int32_t slot_num = g_GameInfo.select_save_slot; + if (slot_num == -1) { + return; + } + + const SAVEGAME_INFO *const savegame_info = &m_SavegameInfo[slot_num]; + if (!savegame_info->features.select_level) { + Requester_AddItem(req, true, "%s", GS(PASSPORT_LEGACY_SELECT_LEVEL_1)); + Requester_AddItem(req, true, "%s", GS(PASSPORT_LEGACY_SELECT_LEVEL_2)); + req->requested = 0; + req->line_offset = 0; + return; + } + + const GF_LEVEL_TABLE *const level_table = GF_GetLevelTable(GFLT_MAIN); + for (int32_t i = 0; i <= MIN(savegame_info->level_num, level_table->count); + i++) { + const GF_LEVEL *const level = GF_GetLevel(GFLT_MAIN, i); + if (level->type != GFL_GYM) { + Requester_AddItem(req, false, "%s", level->title); + } + } + + if (g_InvMode == INV_TITLE_MODE) { + Requester_AddItem(req, false, "%s", GS(PASSPORT_STORY_SO_FAR)); + } + + req->requested = 0; + req->line_offset = 0; +} + +void Savegame_HighlightNewestSlot(void) +{ + g_SavegameRequester.requested = MAX(0, m_NewestSlot); +} + +bool Savegame_RestartAvailable(int32_t slot_num) +{ + if (slot_num == -1) { + return true; + } + + SAVEGAME_INFO *savegame_info = &m_SavegameInfo[slot_num]; + return savegame_info->features.restart; +} diff --git a/src/tr1/game/savegame/savegame_bson.c b/src/tr1/game/savegame/savegame_bson.c index cca545fbe..608370b4a 100644 --- a/src/tr1/game/savegame/savegame_bson.c +++ b/src/tr1/game/savegame/savegame_bson.c @@ -1,3 +1,5 @@ +#include "game/savegame/savegame_bson.h" + #include "game/camera.h" #include "game/carrier.h" #include "game/effects.h" @@ -9,7 +11,6 @@ #include "game/lot.h" #include "game/music.h" #include "game/room.h" -#include "game/savegame.h" #include "game/shell.h" #include "game/stats.h" #include "global/const.h" @@ -18,12 +19,10 @@ #include #include #include -#include #include #include #include #include -#include #include #include @@ -32,19 +31,39 @@ #define SAVEGAME_BSON_MAGIC MKTAG('T', '1', 'M', 'B') +#pragma pack(push, 1) +typedef struct { + uint32_t magic; + int16_t initial_version; + uint16_t version; + int32_t compressed_size; + int32_t uncompressed_size; +} SAVEGAME_BSON_HEADER; + +typedef struct { + uint32_t flags; + int32_t counter; + int32_t level_num; + size_t title_size; +} SAVEGAME_BSON_EXTENDED_HEADER; +#pragma pack(pop) + typedef struct { int16_t count; - int16_t id_map[MAX_EFFECTS]; + int16_t id_map[NUM_EFFECTS]; } SAVEGAME_BSON_FX_ORDER; static void M_SaveRaw(MYFILE *fp, JSON_VALUE *root, int32_t version); static JSON_VALUE *M_ParseFromBuffer( const char *buffer, size_t buffer_size, int32_t *version_out); static JSON_VALUE *M_ParseFromFile(MYFILE *fp, int32_t *version_out); -static bool M_LoadResumeInfo(JSON_ARRAY *levels_arr, uint16_t header_version); -static bool M_LoadDiscontinuedStartInfo(JSON_ARRAY *start_arr); -static bool M_LoadDiscontinuedEndInfo(JSON_ARRAY *end_arr); -static bool M_LoadMisc(JSON_OBJECT *misc_obj, uint16_t header_version); +static bool M_LoadResumeInfo(JSON_ARRAY *levels_arr, RESUME_INFO *resume_info); +static bool M_LoadDiscontinuedStartInfo( + JSON_ARRAY *start_arr, GAME_INFO *game_info); +static bool M_LoadDiscontinuedEndInfo( + JSON_ARRAY *end_arr, GAME_INFO *game_info); +static bool M_LoadMisc( + JSON_OBJECT *misc_obj, GAME_INFO *game_info, uint16_t header_version); static bool M_LoadInventory(JSON_OBJECT *inv_obj); static bool M_LoadFlipmaps(JSON_OBJECT *flipmap_obj); static bool M_LoadCameras(JSON_ARRAY *cameras_arr); @@ -57,8 +76,8 @@ static bool M_LoadLara( JSON_OBJECT *lara_obj, LARA_INFO *lara, uint16_t header_version); static bool M_LoadCurrentMusic(JSON_OBJECT *music_obj); static bool M_LoadMusicTrackFlags(JSON_ARRAY *music_track_arr); -static JSON_ARRAY *M_DumpResumeInfo(void); -static JSON_OBJECT *M_DumpMisc(void); +static JSON_ARRAY *M_DumpResumeInfo(RESUME_INFO *game_info); +static JSON_OBJECT *M_DumpMisc(GAME_INFO *game_info); static JSON_OBJECT *M_DumpInventory(void); static JSON_OBJECT *M_DumpFlipmaps(void); static JSON_ARRAY *M_DumpCameras(void); @@ -75,25 +94,6 @@ static void M_GetFXOrder(SAVEGAME_BSON_FX_ORDER *order); static bool M_IsValidItemObject( GAME_OBJECT_ID saved_obj_id, GAME_OBJECT_ID current_obj_id); -static const char *M_GetSaveFilePattern(void); -static bool M_FillInfo(MYFILE *fp, SAVEGAME_INFO *savegame_info); -static bool M_LoadFromFile(MYFILE *fp); -static bool M_LoadOnlyResumeInfo(MYFILE *fp); -static void M_SaveToFile(MYFILE *fp, SAVEGAME_INFO *savegame_info); -static bool M_UpdateDeathCounters(MYFILE *fp, int32_t death_count); - -static SAVEGAME_STRATEGY m_Strategy = { - .allow_load = true, - .allow_save = true, - .format = SAVEGAME_FORMAT_BSON, - .get_save_file_pattern_func = M_GetSaveFilePattern, - .fill_info_func = M_FillInfo, - .load_from_file_func = M_LoadFromFile, - .load_only_resume_info_func = M_LoadOnlyResumeInfo, - .save_to_file_func = M_SaveToFile, - .update_death_counters_func = M_UpdateDeathCounters, -}; - static void M_SaveRaw(MYFILE *fp, JSON_VALUE *root, int32_t version) { size_t uncompressed_size; @@ -110,21 +110,22 @@ static void M_SaveRaw(MYFILE *fp, JSON_VALUE *root, int32_t version) Memory_FreePointer(&uncompressed); - const SAVEGAME_BSON_HEADER header = { + SAVEGAME_BSON_HEADER header = { .magic = SAVEGAME_BSON_MAGIC, - .initial_version = Savegame_GetInitialVersion(), + .initial_version = g_GameInfo.save_initial_version, .version = version, .compressed_size = compressed_size, .uncompressed_size = uncompressed_size, }; + File_WriteData(fp, &header, sizeof(header)); File_WriteData(fp, compressed, compressed_size); const GF_LEVEL *const level = Game_GetCurrentLevel(); - const SAVEGAME_BSON_EXTENDED_HEADER extra_header = { - .flags = Game_GetBonusFlag(), - .counter = Savegame_GetCounter(), + SAVEGAME_BSON_EXTENDED_HEADER extra_header = { + .flags = g_GameInfo.bonus_flag, + .counter = g_SaveCounter, .level_num = level->num, .title_size = strlen(level->title), }; @@ -137,7 +138,7 @@ static void M_SaveRaw(MYFILE *fp, JSON_VALUE *root, int32_t version) static void M_GetFXOrder(SAVEGAME_BSON_FX_ORDER *order) { order->count = 0; - for (int32_t i = 0; i < MAX_EFFECTS; i++) { + for (int i = 0; i < NUM_EFFECTS; i++) { order->id_map[i] = -1; } @@ -164,8 +165,8 @@ static bool M_IsValidItemObject( case O_PUZZLE_DONE_4: return initial_obj_id == O_PUZZLE_HOLE_4; // pickups case O_PISTOL_AMMO_ITEM: return initial_obj_id == O_PISTOL_ANIM; - case O_SHOTGUN_AMMO_ITEM: return initial_obj_id == O_SHOTGUN_ITEM; - case O_MAGNUM_AMMO_ITEM: return initial_obj_id == O_MAGNUM_ITEM; + case O_SG_AMMO_ITEM: return initial_obj_id == O_SHOTGUN_ITEM; + case O_MAG_AMMO_ITEM: return initial_obj_id == O_MAGNUM_ITEM; case O_UZI_AMMO_ITEM: return initial_obj_id == O_UZI_ITEM; // dual-state animals case O_ALLIGATOR: return initial_obj_id == O_CROCODILE; @@ -221,9 +222,9 @@ static JSON_VALUE *M_ParseFromFile(MYFILE *fp, int32_t *version_out) return ret; } -static bool M_LoadResumeInfo( - JSON_ARRAY *const resume_arr, const uint16_t header_version) +static bool M_LoadResumeInfo(JSON_ARRAY *resume_arr, RESUME_INFO *resume_info) { + ASSERT(resume_info != nullptr); if (!resume_arr) { LOG_ERROR("Malformed save: invalid or missing resume array"); return false; @@ -240,9 +241,7 @@ static bool M_LoadResumeInfo( LOG_ERROR("Malformed save: invalid resume info"); return false; } - - const GF_LEVEL *const level = GF_GetLevel(GFLT_MAIN, i); - RESUME_INFO *const resume = Savegame_GetCurrentInfo(level); + RESUME_INFO *resume = &resume_info[i]; resume->lara_hitpoints = JSON_ObjectGetInt( resume_obj, "lara_hitpoints", g_Config.gameplay.start_lara_hitpoints); @@ -250,8 +249,8 @@ static bool M_LoadResumeInfo( resume->magnum_ammo = JSON_ObjectGetInt(resume_obj, "magnum_ammo", 0); resume->uzi_ammo = JSON_ObjectGetInt(resume_obj, "uzi_ammo", 0); resume->shotgun_ammo = JSON_ObjectGetInt(resume_obj, "shotgun_ammo", 0); - resume->small_medipacks = JSON_ObjectGetInt(resume_obj, "num_medis", 0); - resume->large_medipacks = + resume->num_medis = JSON_ObjectGetInt(resume_obj, "num_medis", 0); + resume->num_big_medis = JSON_ObjectGetInt(resume_obj, "num_big_medis", 0); resume->num_scions = JSON_ObjectGetInt(resume_obj, "num_scions", 0); resume->gun_status = JSON_ObjectGetInt(resume_obj, "gun_status", 0); @@ -263,12 +262,12 @@ static bool M_LoadResumeInfo( JSON_ObjectGetInt(resume_obj, "back_gun_type", LGT_UNKNOWN); resume->flags.available = JSON_ObjectGetBool(resume_obj, "available", 0); - resume->flags.has_pistols = + resume->flags.got_pistols = JSON_ObjectGetBool(resume_obj, "got_pistols", 0); - resume->flags.has_magnums = + resume->flags.got_magnums = JSON_ObjectGetBool(resume_obj, "got_magnums", 0); - resume->flags.has_uzis = JSON_ObjectGetBool(resume_obj, "got_uzis", 0); - resume->flags.has_shotgun = + resume->flags.got_uzis = JSON_ObjectGetBool(resume_obj, "got_uzis", 0); + resume->flags.got_shotgun = JSON_ObjectGetBool(resume_obj, "got_shotgun", 0); resume->flags.costume = JSON_ObjectGetBool(resume_obj, "costume", 0); @@ -287,25 +286,17 @@ static bool M_LoadResumeInfo( resume_obj, "max_kills", resume->stats.max_kill_count); resume->stats.max_pickup_count = JSON_ObjectGetInt( resume_obj, "max_pickups", resume->stats.max_pickup_count); - if (header_version >= VERSION_7) { - resume->stats.ammo_hits = JSON_ObjectGetInt( - resume_obj, "ammo_hits", resume->stats.ammo_hits); - resume->stats.ammo_used = JSON_ObjectGetInt( - resume_obj, "ammo_used", resume->stats.ammo_used); - resume->stats.medipacks_used = JSON_ObjectGetDouble( - resume_obj, "medipacks_used", resume->stats.medipacks_used); - resume->stats.distance_travelled = JSON_ObjectGetInt( - resume_obj, "distance_travelled", - resume->stats.distance_travelled); - } } return true; } -static bool M_LoadDiscontinuedStartInfo(JSON_ARRAY *const start_arr) +static bool M_LoadDiscontinuedStartInfo( + JSON_ARRAY *start_arr, GAME_INFO *game_info) { // This function solely exists for backward compatibility with 2.6 and 2.7 // saves. + ASSERT(game_info != nullptr); + ASSERT(game_info->current != nullptr); if (!start_arr) { LOG_ERROR( "Malformed save: invalid or missing discontinued start array"); @@ -323,8 +314,7 @@ static bool M_LoadDiscontinuedStartInfo(JSON_ARRAY *const start_arr) LOG_ERROR("Malformed save: invalid discontinued start info"); return false; } - const GF_LEVEL *const level = GF_GetLevel(GFLT_MAIN, i); - RESUME_INFO *const start = Savegame_GetCurrentInfo(level); + RESUME_INFO *start = &game_info->current[i]; start->lara_hitpoints = JSON_ObjectGetInt( start_obj, "lara_hitpoints", g_Config.gameplay.start_lara_hitpoints); @@ -332,9 +322,8 @@ static bool M_LoadDiscontinuedStartInfo(JSON_ARRAY *const start_arr) start->magnum_ammo = JSON_ObjectGetInt(start_obj, "magnum_ammo", 0); start->uzi_ammo = JSON_ObjectGetInt(start_obj, "uzi_ammo", 0); start->shotgun_ammo = JSON_ObjectGetInt(start_obj, "shotgun_ammo", 0); - start->small_medipacks = JSON_ObjectGetInt(start_obj, "num_medis", 0); - start->large_medipacks = - JSON_ObjectGetInt(start_obj, "num_big_medis", 0); + start->num_medis = JSON_ObjectGetInt(start_obj, "num_medis", 0); + start->num_big_medis = JSON_ObjectGetInt(start_obj, "num_big_medis", 0); start->num_scions = JSON_ObjectGetInt(start_obj, "num_scions", 0); start->gun_status = JSON_ObjectGetInt(start_obj, "gun_status", 0); start->equipped_gun_type = @@ -342,22 +331,24 @@ static bool M_LoadDiscontinuedStartInfo(JSON_ARRAY *const start_arr) start->holsters_gun_type = LGT_UNKNOWN; start->back_gun_type = LGT_UNKNOWN; start->flags.available = JSON_ObjectGetBool(start_obj, "available", 0); - start->flags.has_pistols = + start->flags.got_pistols = JSON_ObjectGetBool(start_obj, "got_pistols", 0); - start->flags.has_magnums = + start->flags.got_magnums = JSON_ObjectGetBool(start_obj, "got_magnums", 0); - start->flags.has_uzis = JSON_ObjectGetBool(start_obj, "got_uzis", 0); - start->flags.has_shotgun = + start->flags.got_uzis = JSON_ObjectGetBool(start_obj, "got_uzis", 0); + start->flags.got_shotgun = JSON_ObjectGetBool(start_obj, "got_shotgun", 0); start->flags.costume = JSON_ObjectGetBool(start_obj, "costume", 0); } return true; } -static bool M_LoadDiscontinuedEndInfo(JSON_ARRAY *end_arr) +static bool M_LoadDiscontinuedEndInfo(JSON_ARRAY *end_arr, GAME_INFO *game_info) { // This function solely exists for backward compatibility with 2.6 and 2.7 // saves. + ASSERT(game_info != nullptr); + ASSERT(game_info->current != nullptr); if (!end_arr) { LOG_ERROR("Malformed save: invalid or missing resume info array"); return false; @@ -374,10 +365,7 @@ static bool M_LoadDiscontinuedEndInfo(JSON_ARRAY *end_arr) LOG_ERROR("Malformed save: invalid resume info"); return false; } - - const GF_LEVEL *const level = GF_GetLevel(GFLT_MAIN, i); - RESUME_INFO *const resume = Savegame_GetCurrentInfo(level); - LEVEL_STATS *const end = &resume->stats; + LEVEL_STATS *end = &game_info->current[i].stats; end->timer = JSON_ObjectGetInt(end_obj, "timer", end->timer); end->secret_flags = JSON_ObjectGetInt(end_obj, "secrets", end->secret_flags); @@ -396,19 +384,18 @@ static bool M_LoadDiscontinuedEndInfo(JSON_ARRAY *end_arr) } static bool M_LoadMisc( - JSON_OBJECT *const misc_obj, const uint16_t header_version) + JSON_OBJECT *misc_obj, GAME_INFO *game_info, uint16_t header_version) { + ASSERT(game_info != nullptr); if (!misc_obj) { LOG_ERROR("Malformed save: invalid or missing misc info"); return false; } - const int32_t bonus_flag = JSON_ObjectGetInt(misc_obj, "bonus_flag", 0); - Game_SetBonusFlag(bonus_flag); + game_info->bonus_flag = JSON_ObjectGetInt(misc_obj, "bonus_flag", 0); if (header_version >= VERSION_4) { - const GF_LEVEL *const current_level = Game_GetCurrentLevel(); - RESUME_INFO *const resume = Savegame_GetCurrentInfo(current_level); - resume->stats.death_count = - JSON_ObjectGetInt(misc_obj, "death_count", -1); + game_info->bonus_level_unlock = + JSON_ObjectGetBool(misc_obj, "bonus_level_unlock", 0); + game_info->death_count = JSON_ObjectGetInt(misc_obj, "death_count", -1); } return true; } @@ -555,12 +542,6 @@ static bool M_LoadItems(JSON_ARRAY *items_arr, uint16_t header_version) JSON_ObjectGetInt(item_obj, "anim_num", item->anim_num); item->frame_num = JSON_ObjectGetInt(item_obj, "frame_num", item->frame_num); - - // Prevent issues with pre-injection saves and Lara's enhanced - // animation set. - if (item->object_id == O_LARA && item->anim_num < obj->anim_idx) { - item->anim_num += obj->anim_idx; - } } if (obj->save_hitpoints) { @@ -677,12 +658,12 @@ static bool M_LoadEffects(JSON_ARRAY *fx_arr) return false; } - if ((signed)fx_arr->length >= MAX_EFFECTS) { + if ((signed)fx_arr->length >= NUM_EFFECTS) { LOG_WARNING( "Malformed save: expected a max of %d effect, got %d. effect over " "the " "maximum will not be created.", - MAX_EFFECTS - 1, fx_arr->length); + NUM_EFFECTS - 1, fx_arr->length); } for (int i = 0; i < (signed)fx_arr->length; i++) { @@ -873,21 +854,21 @@ static bool M_LoadLara( } if (!M_LoadAmmo( - JSON_ObjectGetObject(lara_obj, "pistols"), &lara->pistol_ammo)) { + JSON_ObjectGetObject(lara_obj, "pistols"), &lara->pistols)) { return false; } if (!M_LoadAmmo( - JSON_ObjectGetObject(lara_obj, "magnums"), &lara->magnum_ammo)) { + JSON_ObjectGetObject(lara_obj, "magnums"), &lara->magnums)) { return false; } - if (!M_LoadAmmo(JSON_ObjectGetObject(lara_obj, "uzis"), &lara->uzi_ammo)) { + if (!M_LoadAmmo(JSON_ObjectGetObject(lara_obj, "uzis"), &lara->uzis)) { return false; } if (!M_LoadAmmo( - JSON_ObjectGetObject(lara_obj, "shotgun"), &lara->shotgun_ammo)) { + JSON_ObjectGetObject(lara_obj, "shotgun"), &lara->shotgun)) { return false; } @@ -907,15 +888,6 @@ static bool M_LoadLara( lara->interact_target.is_moving); } - if (header_version >= VERSION_7) { - lara->last_pos.x = - JSON_ObjectGetInt(lara_obj, "last_pos.x", lara->last_pos.x); - lara->last_pos.y = - JSON_ObjectGetInt(lara_obj, "last_pos.y", lara->last_pos.y); - lara->last_pos.z = - JSON_ObjectGetInt(lara_obj, "last_pos.z", lara->last_pos.z); - } - return true; } @@ -978,12 +950,12 @@ static bool M_LoadMusicTrackFlags(JSON_ARRAY *music_track_arr) return true; } -static JSON_ARRAY *M_DumpResumeInfo(void) +static JSON_ARRAY *M_DumpResumeInfo(RESUME_INFO *resume_info) { JSON_ARRAY *resume_arr = JSON_ArrayNew(); + ASSERT(resume_info != nullptr); for (int i = 0; i < GF_GetLevelTable(GFLT_MAIN)->count; i++) { - const GF_LEVEL *const level = GF_GetLevel(GFLT_MAIN, i); - const RESUME_INFO *const resume = Savegame_GetCurrentInfo(level); + RESUME_INFO *resume = &resume_info[i]; JSON_OBJECT *resume_obj = JSON_ObjectNew(); JSON_ObjectAppendInt( resume_obj, "lara_hitpoints", resume->lara_hitpoints); @@ -991,9 +963,9 @@ static JSON_ARRAY *M_DumpResumeInfo(void) JSON_ObjectAppendInt(resume_obj, "magnum_ammo", resume->magnum_ammo); JSON_ObjectAppendInt(resume_obj, "uzi_ammo", resume->uzi_ammo); JSON_ObjectAppendInt(resume_obj, "shotgun_ammo", resume->shotgun_ammo); - JSON_ObjectAppendInt(resume_obj, "num_medis", resume->small_medipacks); + JSON_ObjectAppendInt(resume_obj, "num_medis", resume->num_medis); JSON_ObjectAppendInt( - resume_obj, "num_big_medis", resume->large_medipacks); + resume_obj, "num_big_medis", resume->num_big_medis); JSON_ObjectAppendInt(resume_obj, "num_scions", resume->num_scions); JSON_ObjectAppendInt(resume_obj, "gun_status", resume->gun_status); JSON_ObjectAppendInt(resume_obj, "gun_type", resume->equipped_gun_type); @@ -1003,12 +975,12 @@ static JSON_ARRAY *M_DumpResumeInfo(void) resume_obj, "back_gun_type", resume->back_gun_type); JSON_ObjectAppendBool(resume_obj, "available", resume->flags.available); JSON_ObjectAppendBool( - resume_obj, "got_pistols", resume->flags.has_pistols); + resume_obj, "got_pistols", resume->flags.got_pistols); JSON_ObjectAppendBool( - resume_obj, "got_magnums", resume->flags.has_magnums); - JSON_ObjectAppendBool(resume_obj, "got_uzis", resume->flags.has_uzis); + resume_obj, "got_magnums", resume->flags.got_magnums); + JSON_ObjectAppendBool(resume_obj, "got_uzis", resume->flags.got_uzis); JSON_ObjectAppendBool( - resume_obj, "got_shotgun", resume->flags.has_shotgun); + resume_obj, "got_shotgun", resume->flags.got_shotgun); JSON_ObjectAppendBool(resume_obj, "costume", resume->flags.costume); JSON_ObjectAppendInt(resume_obj, "timer", resume->stats.timer); JSON_ObjectAppendInt(resume_obj, "kills", resume->stats.kill_count); @@ -1021,25 +993,18 @@ static JSON_ARRAY *M_DumpResumeInfo(void) JSON_ObjectAppendInt( resume_obj, "max_pickups", resume->stats.max_pickup_count); JSON_ArrayAppendObject(resume_arr, resume_obj); - JSON_ObjectAppendInt(resume_obj, "ammo_hits", resume->stats.ammo_hits); - JSON_ObjectAppendInt(resume_obj, "ammo_used", resume->stats.ammo_used); - JSON_ObjectAppendDouble( - resume_obj, "medipacks_used", resume->stats.medipacks_used); - JSON_ObjectAppendInt( - resume_obj, "distance_travelled", resume->stats.distance_travelled); } return resume_arr; } -static JSON_OBJECT *M_DumpMisc(void) +static JSON_OBJECT *M_DumpMisc(GAME_INFO *game_info) { + ASSERT(game_info != nullptr); JSON_OBJECT *misc_obj = JSON_ObjectNew(); - JSON_ObjectAppendString(misc_obj, "game_version", g_TRXVersion); - JSON_ObjectAppendInt(misc_obj, "bonus_flag", Game_GetBonusFlag()); - - const GF_LEVEL *const level = Game_GetCurrentLevel(); - const RESUME_INFO *const resume = Savegame_GetCurrentInfo(level); - JSON_ObjectAppendInt(misc_obj, "death_count", resume->stats.death_count); + JSON_ObjectAppendInt(misc_obj, "bonus_flag", game_info->bonus_flag); + JSON_ObjectAppendBool( + misc_obj, "bonus_level_unlock", game_info->bonus_level_unlock); + JSON_ObjectAppendInt(misc_obj, "death_count", game_info->death_count); return misc_obj; } @@ -1301,13 +1266,10 @@ static JSON_OBJECT *M_DumpLara(LARA_INFO *lara) JSON_ObjectAppendObject(lara_obj, "left_arm", M_DumpArm(&lara->left_arm)); JSON_ObjectAppendObject(lara_obj, "right_arm", M_DumpArm(&lara->right_arm)); - JSON_ObjectAppendObject( - lara_obj, "pistols", M_DumpAmmo(&lara->pistol_ammo)); - JSON_ObjectAppendObject( - lara_obj, "magnums", M_DumpAmmo(&lara->magnum_ammo)); - JSON_ObjectAppendObject(lara_obj, "uzis", M_DumpAmmo(&lara->uzi_ammo)); - JSON_ObjectAppendObject( - lara_obj, "shotgun", M_DumpAmmo(&lara->shotgun_ammo)); + JSON_ObjectAppendObject(lara_obj, "pistols", M_DumpAmmo(&lara->pistols)); + JSON_ObjectAppendObject(lara_obj, "magnums", M_DumpAmmo(&lara->magnums)); + JSON_ObjectAppendObject(lara_obj, "uzis", M_DumpAmmo(&lara->uzis)); + JSON_ObjectAppendObject(lara_obj, "shotgun", M_DumpAmmo(&lara->shotgun)); JSON_ObjectAppendObject(lara_obj, "lot", M_DumpLOT(&lara->lot)); JSON_ObjectAppendInt( @@ -1318,10 +1280,6 @@ static JSON_OBJECT *M_DumpLara(LARA_INFO *lara) JSON_ObjectAppendBool( lara_obj, "interact_target.is_moving", lara->interact_target.is_moving); - JSON_ObjectAppendInt(lara_obj, "last_pos.x", lara->last_pos.x); - JSON_ObjectAppendInt(lara_obj, "last_pos.y", lara->last_pos.y); - JSON_ObjectAppendInt(lara_obj, "last_pos.z", lara->last_pos.z); - return lara_obj; } @@ -1347,12 +1305,12 @@ static JSON_ARRAY *M_DumpMusicTrackFlags(void) return music_track_arr; } -static const char *M_GetSaveFilePattern(void) +const char *Savegame_BSON_GetSaveFilePattern(void) { return g_GameFlow.savegame_fmt_bson; } -static bool M_FillInfo(MYFILE *const fp, SAVEGAME_INFO *const info) +bool Savegame_BSON_FillInfo(MYFILE *fp, SAVEGAME_INFO *info) { bool ret = false; SAVEGAME_BSON_HEADER header; @@ -1393,12 +1351,19 @@ static bool M_FillInfo(MYFILE *const fp, SAVEGAME_INFO *const info) return ret; } -static bool M_LoadFromFile(MYFILE *const fp) +bool Savegame_BSON_LoadFromFile(MYFILE *fp, GAME_INFO *game_info) { + ASSERT(game_info != nullptr); + bool ret = false; - int32_t version; - JSON_VALUE *root = M_ParseFromFile(fp, &version); + // Read savegame version + SAVEGAME_BSON_HEADER header; + File_Seek(fp, 0, FILE_SEEK_SET); + File_ReadData(fp, &header, sizeof(SAVEGAME_BSON_HEADER)); + File_Seek(fp, 0, FILE_SEEK_SET); + + JSON_VALUE *root = M_ParseFromFile(fp, nullptr); JSON_OBJECT *root_obj = JSON_ValueAsObject(root); if (!root_obj) { LOG_ERROR("Malformed save: cannot parse BSON data"); @@ -1406,22 +1371,25 @@ static bool M_LoadFromFile(MYFILE *const fp) } if (!M_LoadResumeInfo( - JSON_ObjectGetArray(root_obj, "current_info"), version)) { + JSON_ObjectGetArray(root_obj, "current_info"), + game_info->current)) { LOG_WARNING( "Failed to load RESUME_INFO current properly. " "Checking if save is legacy."); // Check for 2.6 and 2.7 legacy start and end info. if (!M_LoadDiscontinuedStartInfo( - JSON_ObjectGetArray(root_obj, "start_info"))) { + JSON_ObjectGetArray(root_obj, "start_info"), game_info)) { goto cleanup; } if (!M_LoadDiscontinuedEndInfo( - JSON_ObjectGetArray(root_obj, "end_info"))) { + JSON_ObjectGetArray(root_obj, "end_info"), game_info)) { goto cleanup; } } - if (!M_LoadMisc(JSON_ObjectGetObject(root_obj, "misc"), version)) { + if (!M_LoadMisc( + JSON_ObjectGetObject(root_obj, "misc"), game_info, + header.version)) { goto cleanup; } @@ -1439,21 +1407,22 @@ static bool M_LoadFromFile(MYFILE *const fp) Savegame_ProcessItemsBeforeLoad(); - if (!M_LoadItems(JSON_ObjectGetArray(root_obj, "items"), version)) { + if (!M_LoadItems(JSON_ObjectGetArray(root_obj, "items"), header.version)) { goto cleanup; } - if (version >= VERSION_3) { + if (header.version >= VERSION_3) { if (!M_LoadEffects(JSON_ObjectGetArray(root_obj, "fx"))) { goto cleanup; } } - if (!M_LoadLara(JSON_ObjectGetObject(root_obj, "lara"), &g_Lara, version)) { + if (!M_LoadLara( + JSON_ObjectGetObject(root_obj, "lara"), &g_Lara, header.version)) { goto cleanup; } - if (version >= VERSION_3) { + if (header.version >= VERSION_3) { if (!M_LoadCurrentMusic(JSON_ObjectGetObject(root_obj, "music"))) { goto cleanup; } @@ -1471,12 +1440,12 @@ cleanup: return ret; } -static bool M_LoadOnlyResumeInfo(MYFILE *const fp) +bool Savegame_BSON_LoadOnlyResumeInfo(MYFILE *fp, GAME_INFO *game_info) { - bool ret = false; + ASSERT(game_info != nullptr); - int32_t version; - JSON_VALUE *root = M_ParseFromFile(fp, &version); + bool ret = false; + JSON_VALUE *root = M_ParseFromFile(fp, nullptr); JSON_OBJECT *root_obj = JSON_ValueAsObject(root); if (!root_obj) { LOG_ERROR("Malformed save: cannot parse BSON data"); @@ -1484,17 +1453,18 @@ static bool M_LoadOnlyResumeInfo(MYFILE *const fp) } if (!M_LoadResumeInfo( - JSON_ObjectGetArray(root_obj, "current_info"), version)) { + JSON_ObjectGetArray(root_obj, "current_info"), + game_info->current)) { LOG_WARNING( "Failed to load RESUME_INFO current properly. Checking if " "save is legacy."); // Check for 2.6 and 2.7 legacy start and end info. if (!M_LoadDiscontinuedStartInfo( - JSON_ObjectGetArray(root_obj, "start_info"))) { + JSON_ObjectGetArray(root_obj, "start_info"), game_info)) { goto cleanup; } if (!M_LoadDiscontinuedEndInfo( - JSON_ObjectGetArray(root_obj, "end_info"))) { + JSON_ObjectGetArray(root_obj, "end_info"), game_info)) { goto cleanup; } } @@ -1506,17 +1476,20 @@ cleanup: return ret; } -static void M_SaveToFile(MYFILE *const fp, SAVEGAME_INFO *const savegame_info) +void Savegame_BSON_SaveToFile(MYFILE *fp, GAME_INFO *game_info) { + ASSERT(game_info != nullptr); + const GF_LEVEL *const current_level = Game_GetCurrentLevel(); JSON_OBJECT *root_obj = JSON_ObjectNew(); JSON_ObjectAppendString(root_obj, "level_title", current_level->title); - JSON_ObjectAppendInt(root_obj, "save_counter", Savegame_GetCounter()); + JSON_ObjectAppendInt(root_obj, "save_counter", g_SaveCounter); JSON_ObjectAppendInt(root_obj, "level_num", current_level->num); - JSON_ObjectAppendObject(root_obj, "misc", M_DumpMisc()); - JSON_ObjectAppendArray(root_obj, "current_info", M_DumpResumeInfo()); + JSON_ObjectAppendObject(root_obj, "misc", M_DumpMisc(game_info)); + JSON_ObjectAppendArray( + root_obj, "current_info", M_DumpResumeInfo(game_info->current)); JSON_ObjectAppendObject(root_obj, "inventory", M_DumpInventory()); JSON_ObjectAppendObject(root_obj, "flipmap", M_DumpFlipmaps()); JSON_ObjectAppendArray(root_obj, "cameras", M_DumpCameras()); @@ -1530,11 +1503,9 @@ static void M_SaveToFile(MYFILE *const fp, SAVEGAME_INFO *const savegame_info) JSON_VALUE *root = JSON_ValueFromObject(root_obj); M_SaveRaw(fp, root, SAVEGAME_CURRENT_VERSION); JSON_ValueFree(root); - - savegame_info->features.restart = true; } -static bool M_UpdateDeathCounters(MYFILE *const fp, const int32_t death_count) +bool Savegame_BSON_UpdateDeathCounters(MYFILE *fp, GAME_INFO *game_info) { bool result = false; int32_t version; @@ -1551,7 +1522,7 @@ static bool M_UpdateDeathCounters(MYFILE *const fp, const int32_t death_count) goto cleanup; } JSON_ObjectEvictKey(misc_obj, "death_count"); - JSON_ObjectAppendInt(misc_obj, "death_count", death_count); + JSON_ObjectAppendInt(misc_obj, "death_count", game_info->death_count); File_Seek(fp, 0, FILE_SEEK_SET); M_SaveRaw(fp, root, version); @@ -1561,5 +1532,3 @@ cleanup: JSON_ValueFree(root); return result; } - -REGISTER_SAVEGAME_STRATEGY(m_Strategy) diff --git a/src/tr1/game/savegame/savegame_bson.h b/src/tr1/game/savegame/savegame_bson.h new file mode 100644 index 000000000..9e3d93118 --- /dev/null +++ b/src/tr1/game/savegame/savegame_bson.h @@ -0,0 +1,17 @@ +#pragma once + +#include "game/savegame.h" +#include "global/types.h" + +#include + +#include + +// TR1X implementation of savegames. + +const char *Savegame_BSON_GetSaveFilePattern(void); +bool Savegame_BSON_FillInfo(MYFILE *fp, SAVEGAME_INFO *info); +bool Savegame_BSON_LoadFromFile(MYFILE *fp, GAME_INFO *game_info); +bool Savegame_BSON_LoadOnlyResumeInfo(MYFILE *fp, GAME_INFO *game_info); +void Savegame_BSON_SaveToFile(MYFILE *fp, GAME_INFO *game_info); +bool Savegame_BSON_UpdateDeathCounters(MYFILE *fp, GAME_INFO *game_info); diff --git a/src/tr1/game/savegame/savegame_legacy.c b/src/tr1/game/savegame/savegame_legacy.c index 34db1bd17..76a7bf522 100644 --- a/src/tr1/game/savegame/savegame_legacy.c +++ b/src/tr1/game/savegame/savegame_legacy.c @@ -1,3 +1,5 @@ +#include "game/savegame/savegame_legacy.h" + #include "game/camera.h" #include "game/carrier.h" #include "game/effects.h" @@ -9,7 +11,6 @@ #include "game/level.h" #include "game/lot.h" #include "game/room.h" -#include "game/savegame.h" #include "game/shell.h" #include "game/stats.h" #include "global/const.h" @@ -82,26 +83,7 @@ static void M_ReadAmmoInfo(AMMO_INFO *ammo_info); static void M_ReadLara(LARA_INFO *lara); static void M_ReadLOT(LOT_INFO *lot); static void M_SetCurrentPosition(int32_t level_num); -static void M_ReadResumeInfo(MYFILE *fp); - -static const char *M_GetSaveFilePattern(void); -static bool M_FillInfo(MYFILE *fp, SAVEGAME_INFO *savegame_info); -static bool M_LoadFromFile(MYFILE *fp); -static bool M_LoadOnlyResumeInfo(MYFILE *fp); - -static SAVEGAME_STRATEGY m_Strategy = { - // clang-format off - .allow_load = true, - .allow_save = false, - .format = SAVEGAME_FORMAT_LEGACY, - .get_save_file_pattern_func = M_GetSaveFilePattern, - .fill_info_func = M_FillInfo, - .load_from_file_func = M_LoadFromFile, - .load_only_resume_info_func = M_LoadOnlyResumeInfo, - .save_to_file_func = nullptr, - .update_death_counters_func = nullptr, - // clang-format on -}; +static void M_ReadResumeInfo(MYFILE *fp, GAME_INFO *game_info); static bool M_ItemHasSaveFlags(const OBJECT *const obj, ITEM *const item) { @@ -293,10 +275,10 @@ static void M_ReadLara(LARA_INFO *const lara) M_ReadArm(&lara->left_arm); M_ReadArm(&lara->right_arm); - M_ReadAmmoInfo(&lara->pistol_ammo); - M_ReadAmmoInfo(&lara->magnum_ammo); - M_ReadAmmoInfo(&lara->uzi_ammo); - M_ReadAmmoInfo(&lara->shotgun_ammo); + M_ReadAmmoInfo(&lara->pistols); + M_ReadAmmoInfo(&lara->magnums); + M_ReadAmmoInfo(&lara->uzis); + M_ReadAmmoInfo(&lara->shotgun); M_ReadLOT(&lara->lot); } @@ -343,13 +325,14 @@ static void M_SetCurrentPosition(const int32_t level_num) for (int32_t i = 0; i < level_table->count; i++) { const GF_LEVEL *const level = &level_table->levels[i]; if (level->type == GFL_CURRENT) { - Savegame_SetCurrentInfo(current_level->num, i); + g_GameInfo.current[current_level->num] = g_GameInfo.current[i]; } } } -static void M_ReadResumeInfo(MYFILE *const fp) +static void M_ReadResumeInfo(MYFILE *const fp, GAME_INFO *const game_info) { + ASSERT(game_info->current != nullptr); const GF_LEVEL_TABLE *const level_table = GF_GetLevelTable(GFLT_MAIN); for (int32_t i = 0; i < level_table->count; i++) { const GF_LEVEL *const level = &level_table->levels[i]; @@ -358,8 +341,8 @@ static void M_ReadResumeInfo(MYFILE *const fp) current->magnum_ammo = M_ReadU16(); current->uzi_ammo = M_ReadU16(); current->shotgun_ammo = M_ReadU16(); - current->small_medipacks = M_ReadU8(); - current->large_medipacks = M_ReadU8(); + current->num_medis = M_ReadU8(); + current->num_big_medis = M_ReadU8(); current->num_scions = M_ReadU8(); current->gun_status = M_ReadS8(); current->equipped_gun_type = M_ReadS8(); @@ -368,10 +351,10 @@ static void M_ReadResumeInfo(MYFILE *const fp) const uint16_t flags = M_ReadU16(); current->flags.available = flags & 1 ? 1 : 0; - current->flags.has_pistols = flags & 2 ? 1 : 0; - current->flags.has_magnums = flags & 4 ? 1 : 0; - current->flags.has_uzis = flags & 8 ? 1 : 0; - current->flags.has_shotgun = flags & 16 ? 1 : 0; + current->flags.got_pistols = flags & 2 ? 1 : 0; + current->flags.got_magnums = flags & 4 ? 1 : 0; + current->flags.got_uzis = flags & 8 ? 1 : 0; + current->flags.got_shotgun = flags & 16 ? 1 : 0; current->flags.costume = flags & 32 ? 1 : 0; // Gym and first level have special starting items. if (level == GF_GetFirstLevel() || level == GF_GetGymLevel()) { @@ -392,19 +375,16 @@ static void M_ReadResumeInfo(MYFILE *const fp) resume_info->stats.secret_flags = temp_secret_flags; Stats_UpdateSecrets(&resume_info->stats); resume_info->stats.pickup_count = M_ReadU8(); - const bool is_ng_plus = M_ReadU8() != 0; - if (is_ng_plus) { - Game_SetBonusFlag(GBF_NGPLUS); - } - resume_info->stats.death_count = -1; + game_info->bonus_flag = M_ReadU8(); + game_info->death_count = -1; } -static const char *M_GetSaveFilePattern(void) +const char *Savegame_Legacy_GetSaveFilePattern(void) { return g_GameFlow.savegame_fmt_legacy; } -static bool M_FillInfo(MYFILE *const fp, SAVEGAME_INFO *const info) +bool Savegame_Legacy_FillInfo(MYFILE *const fp, SAVEGAME_INFO *const info) { File_Seek(fp, 0, SEEK_SET); @@ -442,8 +422,10 @@ static bool M_FillInfo(MYFILE *const fp, SAVEGAME_INFO *const info) return true; } -static bool M_LoadFromFile(MYFILE *const fp) +bool Savegame_Legacy_LoadFromFile(MYFILE *const fp, GAME_INFO *const game_info) { + ASSERT(game_info != nullptr); + char *buffer = Memory_Alloc(File_Size(fp)); File_Seek(fp, 0, FILE_SEEK_SET); File_ReadData(fp, buffer, File_Size(fp)); @@ -457,7 +439,7 @@ static bool M_LoadFromFile(MYFILE *const fp) M_Skip(SAVEGAME_LEGACY_TITLE_SIZE); // level title M_Skip(sizeof(int32_t)); // save counter - M_ReadResumeInfo(fp); + M_ReadResumeInfo(fp, game_info); g_Lara.holsters_gun_type = LGT_UNKNOWN; g_Lara.back_gun_type = LGT_UNKNOWN; @@ -575,8 +557,10 @@ static bool M_LoadFromFile(MYFILE *const fp) return true; } -static bool M_LoadOnlyResumeInfo(MYFILE *const fp) +bool Savegame_Legacy_LoadOnlyResumeInfo(MYFILE *fp, GAME_INFO *game_info) { + ASSERT(game_info != nullptr); + char *buffer = Memory_Alloc(File_Size(fp)); File_Seek(fp, 0, FILE_SEEK_SET); File_ReadData(fp, buffer, File_Size(fp)); @@ -584,10 +568,14 @@ static bool M_LoadOnlyResumeInfo(MYFILE *const fp) M_Skip(SAVEGAME_LEGACY_TITLE_SIZE); // level title M_Skip(sizeof(int32_t)); // save counter - M_ReadResumeInfo(fp); + M_ReadResumeInfo(fp, game_info); Memory_FreePointer(&buffer); return true; } -REGISTER_SAVEGAME_STRATEGY(m_Strategy) +bool Savegame_Legacy_UpdateDeathCounters( + MYFILE *const fp, GAME_INFO *const game_info) +{ + return false; +} diff --git a/src/tr1/game/savegame/savegame_legacy.h b/src/tr1/game/savegame/savegame_legacy.h new file mode 100644 index 000000000..ebcfcbf89 --- /dev/null +++ b/src/tr1/game/savegame/savegame_legacy.h @@ -0,0 +1,16 @@ +#pragma once + +#include "game/savegame.h" +#include "global/types.h" + +#include + +#include + +// TombATI implementation of savegames. + +const char *Savegame_Legacy_GetSaveFilePattern(void); +bool Savegame_Legacy_FillInfo(MYFILE *fp, SAVEGAME_INFO *info); +bool Savegame_Legacy_LoadFromFile(MYFILE *fp, GAME_INFO *game_info); +bool Savegame_Legacy_LoadOnlyResumeInfo(MYFILE *fp, GAME_INFO *game_info); +bool Savegame_Legacy_UpdateDeathCounters(MYFILE *fp, GAME_INFO *game_info); diff --git a/src/tr1/game/screen.c b/src/tr1/game/screen.c index d0ee41efd..581ac77af 100644 --- a/src/tr1/game/screen.c +++ b/src/tr1/game/screen.c @@ -71,10 +71,9 @@ void Screen_Init(void) } // set the first resolution size to desktop size - const SHELL_SIZE display_size = Shell_GetCurrentDisplaySize(); res = &m_Resolutions[0]; - res->width = display_size.w; - res->height = display_size.h; + res->width = Shell_GetCurrentDisplayWidth(); + res->height = Shell_GetCurrentDisplayHeight(); // select matching resolution from config if (g_Config.rendering.resolution_width > 0 @@ -132,7 +131,7 @@ int32_t Screen_GetRenderScale(int32_t unit, RENDER_SCALE_REF ref) } else if (ref == RSR_BAR) { return M_GetRenderScaleBase(unit, 640, 480, g_Config.ui.bar_scale); } else { - return M_GetRenderScaleBase(unit, 640, 480, 1.0f); + return M_GetRenderScaleBase(unit, 640, 480, 0); } } diff --git a/src/tr1/game/screen.h b/src/tr1/game/screen.h index 280e9d493..ffbe25b2f 100644 --- a/src/tr1/game/screen.h +++ b/src/tr1/game/screen.h @@ -3,9 +3,8 @@ #include typedef enum { - RSR_TEXT, - RSR_BAR, - RSR_GENERIC, + RSR_TEXT = 0, + RSR_BAR = 1, } RENDER_SCALE_REF; void Screen_Init(void); diff --git a/src/tr1/game/shell.c b/src/tr1/game/shell.c index da1fdea06..728f7456f 100644 --- a/src/tr1/game/shell.c +++ b/src/tr1/game/shell.c @@ -26,7 +26,7 @@ #include #include #include -#include +#include #include #include @@ -81,18 +81,37 @@ static SHELL_ARGS m_Args = { static const char *m_CurrentGameFlowPath; -static void M_ShowHelp(void); +static void M_ParseArgs(SHELL_ARGS *out_args); static void M_LoadConfig(void); static void M_HandleConfigChange(const EVENT *event, void *data); -static void M_ShowHelp(void) +static void M_ParseArgs(SHELL_ARGS *const out_args) { - puts("Currently available options:"); - puts(""); - puts("-g/--gold: launch The Unfinished Business expansion pack."); - puts(" --demo-pc: launch the PC demo level file."); - puts("-l/--level : launch a specific level file."); - puts("-s/--save : launch from a specific save slot (starts at 1)."); + const char **args = nullptr; + int32_t arg_count = 0; + Shell_GetCommandLine(&arg_count, &args); + + out_args->mod = M_MOD_OG; + + for (int32_t i = 0; i < arg_count; i++) { + if (!strcmp(args[i], "-gold")) { + out_args->mod = M_MOD_UB; + } + if (!strcmp(args[i], "-demo_pc")) { + out_args->mod = M_MOD_DEMO_PC; + } + if ((!strcmp(args[i], "-l") || !strcmp(args[i], "--level")) + && i + 1 < arg_count) { + out_args->level_to_play = args[i + 1]; + out_args->mod = M_MOD_CUSTOM_LEVEL; + } + if ((!strcmp(args[i], "-s") || !strcmp(args[i], "--save")) + && i + 1 < arg_count) { + if (String_ParseInteger(args[i + 1], &out_args->save_to_load)) { + out_args->save_to_load--; + } + } + } } static void M_HandleConfigChange(const EVENT *const event, void *const data) @@ -113,6 +132,7 @@ static void M_HandleConfigChange(const EVENT *const event, void *const data) Savegame_Shutdown(); Savegame_Init(); Savegame_ScanSavedGames(); + Savegame_FillAvailableSaves(&g_SavegameRequester); Savegame_HighlightNewestSlot(); } @@ -133,8 +153,6 @@ void Shell_Shutdown(void) Console_Shutdown(); GameBuf_Shutdown(); Savegame_Shutdown(); - - GameStringTable_Shutdown(); GF_Shutdown(); Output_Shutdown(); @@ -157,40 +175,10 @@ const char *Shell_GetGameFlowPath(void) return m_ModPaths[m_Args.mod].game_flow_path; } -bool Shell_ParseArgs(const int32_t arg_count, const char **args) +void Shell_Main(void) { - SHELL_ARGS *const out_args = &m_Args; - out_args->mod = M_MOD_OG; + M_ParseArgs(&m_Args); - for (int32_t i = 0; i < arg_count; i++) { - if (!strcmp(args[i], "-h") || !strcmp(args[i], "--help")) { - M_ShowHelp(); - return false; - } - if (!strcmp(args[i], "-g") || !strcmp(args[i], "--gold") - || !strcmp(args[i], "-gold")) { - out_args->mod = M_MOD_UB; - } - if (!strcmp(args[i], "--demo-pc") || !strcmp(args[i], "-demo_pc")) { - out_args->mod = M_MOD_DEMO_PC; - } - if ((!strcmp(args[i], "-l") || !strcmp(args[i], "--level")) - && i + 1 < arg_count) { - out_args->level_to_play = args[i + 1]; - out_args->mod = M_MOD_CUSTOM_LEVEL; - } - if ((!strcmp(args[i], "-s") || !strcmp(args[i], "--save")) - && i + 1 < arg_count) { - if (String_ParseInteger(args[i + 1], &out_args->save_to_load)) { - out_args->save_to_load--; - } - } - } - return true; -} - -int32_t Shell_Main(void) -{ GameString_Init(); EnumMap_Init(); Config_Init(); @@ -213,21 +201,18 @@ int32_t Shell_Main(void) if (!Output_Init()) { Shell_ExitSystem("Could not initialise video system"); - return 1; + return; } Screen_Init(); GF_Init(); GF_LoadFromFile(m_ModPaths[m_Args.mod].game_flow_path); - GameStringTable_Init(); - if (m_Args.mod != M_MOD_OG) { - GameStringTable_Load(m_ModPaths[M_MOD_OG].game_strings_path, false); - } - GameStringTable_Load(m_ModPaths[m_Args.mod].game_strings_path, true); + GameStringTable_LoadFromFile(m_ModPaths[m_Args.mod].game_strings_path); GameStringTable_Apply(nullptr); Savegame_Init(); Savegame_ScanSavedGames(); + Savegame_FillAvailableSaves(&g_SavegameRequester); Savegame_HighlightNewestSlot(); GameBuf_Init(); Console_Init(); @@ -329,7 +314,6 @@ int32_t Shell_Main(void) if (m_Args.level_to_play != nullptr) { Memory_FreePointer(&g_GameFlow.level_tables[GFLT_MAIN].levels[0].path); } - return 0; } void Shell_ProcessInput(void) diff --git a/src/tr1/game/sound.c b/src/tr1/game/sound.c index 7728f2465..3f479d368 100644 --- a/src/tr1/game/sound.c +++ b/src/tr1/game/sound.c @@ -528,6 +528,18 @@ void Sound_StopAll(void) Audio_Sample_CloseAll(); } +void Sound_SetMasterVolume(int8_t volume) +{ + int8_t raw_volume = volume ? 6 * volume + 3 : 0; + m_MasterVolumeDefault = raw_volume & 0x3F; + m_MasterVolume = raw_volume & 0x3F; +} + +int8_t Sound_GetMasterVolume(void) +{ + return (m_MasterVolume - 3) / 6; +} + int32_t Sound_GetMinVolume(void) { return 0; @@ -538,18 +550,6 @@ int32_t Sound_GetMaxVolume(void) return 10; } -void Sound_SetMasterVolume(int32_t volume) -{ - int8_t raw_volume = volume ? 6 * volume + 3 : 0; - m_MasterVolumeDefault = raw_volume & 0x3F; - m_MasterVolume = raw_volume & 0x3F; -} - -int32_t Sound_GetMasterVolume(void) -{ - return (m_MasterVolume - 3) / 6; -} - void Sound_ResetAmbient(void) { M_ResetAmbientLoudness(); diff --git a/src/tr1/game/sound.h b/src/tr1/game/sound.h index 9a403a6c6..4cd892222 100644 --- a/src/tr1/game/sound.h +++ b/src/tr1/game/sound.h @@ -13,6 +13,10 @@ bool Sound_StopEffect(SOUND_EFFECT_ID sfx_num, const XYZ_32 *pos); void Sound_UpdateEffects(void); void Sound_ResetEffects(void); void Sound_StopAmbientSounds(void); +int8_t Sound_GetMasterVolume(void); +void Sound_SetMasterVolume(int8_t volume); +int32_t Sound_GetMinVolume(void); +int32_t Sound_GetMaxVolume(void); void Sound_LoadSamples( size_t num_samples, const char **sample_pointers, size_t *sizes); int32_t Sound_GetMaxSamples(void); diff --git a/src/tr1/game/spawn.c b/src/tr1/game/spawn.c index 8fe008f11..dfa547c23 100644 --- a/src/tr1/game/spawn.c +++ b/src/tr1/game/spawn.c @@ -131,7 +131,7 @@ int16_t Spawn_RocketGun( effect->object_id = O_MISSILE_3; effect->frame_num = 0; effect->speed = ROCKET_SPEED; - effect->shade = SHADE_NEUTRAL; + effect->shade = HIGH_LIGHT; M_ShootAtLara(effect); } return effect_num; @@ -154,7 +154,7 @@ int16_t Spawn_GunShot( effect->counter = 3; effect->frame_num = 0; effect->object_id = O_GUN_FLASH; - effect->shade = SHADE_NEUTRAL; + effect->shade = HIGH_LIGHT; } return effect_num; } diff --git a/src/tr1/game/spawn.h b/src/tr1/game/spawn.h index 31e84f497..1ad01c34e 100644 --- a/src/tr1/game/spawn.h +++ b/src/tr1/game/spawn.h @@ -2,12 +2,14 @@ #include "global/types.h" -#include - void Spawn_Splash(ITEM *item); void Spawn_Bubble(const XYZ_32 *pos, int16_t room_num); +int16_t Spawn_Blood( + int32_t x, int32_t y, int32_t z, int16_t speed, int16_t direction, + int16_t room_num); + int16_t Spawn_ShardGun( int32_t x, int32_t y, int32_t z, int16_t speed, int16_t y_rot, int16_t room_num); diff --git a/src/tr1/game/stats.h b/src/tr1/game/stats.h index d36f32fa5..12d2f11d9 100644 --- a/src/tr1/game/stats.h +++ b/src/tr1/game/stats.h @@ -1,5 +1,4 @@ #pragma once #include "game/stats/common.h" - -#include +#include "game/stats/types.h" diff --git a/src/tr1/game/stats/common.c b/src/tr1/game/stats/common.c index 1af9562d1..6224f0829 100644 --- a/src/tr1/game/stats/common.c +++ b/src/tr1/game/stats/common.c @@ -13,11 +13,11 @@ #include #include -#include #include #include +#define MAX_TEXTSTRINGS 10 #define USE_REAL_CLOCK 0 static int32_t m_CachedItemCount = 0; @@ -143,10 +143,6 @@ void Stats_ComputeFinal(GF_LEVEL_TYPE level_type, FINAL_STATS *final_stats) final_stats->max_kill_count += level_stats->max_kill_count; final_stats->max_secret_count += level_stats->max_secret_count; final_stats->max_pickup_count += level_stats->max_pickup_count; - final_stats->ammo_hits += level_stats->ammo_hits; - final_stats->ammo_used += level_stats->ammo_used; - final_stats->medipacks_used += level_stats->medipacks_used; - final_stats->distance_travelled += level_stats->distance_travelled; } } @@ -271,71 +267,3 @@ void Stats_UpdateSecrets(LEVEL_STATS *const stats) stats->secret_count += (stats->secret_flags & (1 << i)) ? 1 : 0; } } - -void Stats_AddKill(void) -{ - RESUME_INFO *const current_info = - Savegame_GetCurrentInfo(Game_GetCurrentLevel()); - current_info->stats.kill_count++; -} - -bool Stats_AddSecret(const int16_t secret_number) -{ - RESUME_INFO *const current_info = - Savegame_GetCurrentInfo(Game_GetCurrentLevel()); - if (current_info->stats.secret_flags & secret_number) { - return false; - } - current_info->stats.secret_flags |= secret_number; - current_info->stats.secret_count++; - return true; -} - -void Stats_AddPickup(void) -{ - RESUME_INFO *const current_info = - Savegame_GetCurrentInfo(Game_GetCurrentLevel()); - current_info->stats.pickup_count++; -} - -void Stats_AddAmmoHits(void) -{ - RESUME_INFO *const current_info = - Savegame_GetCurrentInfo(Game_GetCurrentLevel()); - current_info->stats.ammo_hits++; -} - -void Stats_AddAmmoUsed(void) -{ - RESUME_INFO *const current_info = - Savegame_GetCurrentInfo(Game_GetCurrentLevel()); - current_info->stats.ammo_used++; -} - -void Stats_AddMedipacksUsed(const double medipack_value) -{ - RESUME_INFO *const current_info = - Savegame_GetCurrentInfo(Game_GetCurrentLevel()); - current_info->stats.medipacks_used += medipack_value; -} - -void Stats_AddDistanceTravelled(const XYZ_32 pos, const XYZ_32 last_pos) -{ - RESUME_INFO *const current_info = - Savegame_GetCurrentInfo(Game_GetCurrentLevel()); - current_info->stats.distance_travelled += Math_Sqrt( - SQUARE(pos.z - last_pos.z) + SQUARE(pos.y - last_pos.y) - + SQUARE(pos.x - last_pos.x)); -} - -void Stats_AddDeath(void) -{ - const GF_LEVEL *const current_level = Game_GetCurrentLevel(); - RESUME_INFO *const current_info = Savegame_GetCurrentInfo(current_level); - current_info->stats.death_count++; - const int32_t save_slot = Savegame_GetBoundSlot(); - if (save_slot != -1) { - Savegame_UpdateDeathCounters( - save_slot, current_info->stats.death_count); - } -} diff --git a/src/tr1/game/stats/common.h b/src/tr1/game/stats/common.h index 13c63a954..8bb351ae1 100644 --- a/src/tr1/game/stats/common.h +++ b/src/tr1/game/stats/common.h @@ -1,10 +1,10 @@ #pragma once +#include "game/stats/types.h" #include "global/types.h" -#include - void Stats_ObserveRoomsLoad(void); +void Stats_ObserveItemsLoad(void); void Stats_CalculateStats(void); int32_t Stats_GetPickups(void); int32_t Stats_GetKillables(void); @@ -12,14 +12,6 @@ int32_t Stats_GetSecrets(void); void Stats_ComputeFinal(GF_LEVEL_TYPE level_type, FINAL_STATS *stats); bool Stats_CheckAllSecretsCollected(GF_LEVEL_TYPE level_type); +void Stats_StartTimer(void); void Stats_UpdateTimer(void); void Stats_UpdateSecrets(LEVEL_STATS *stats); - -void Stats_AddKill(void); -bool Stats_AddSecret(int16_t secret_number); -void Stats_AddPickup(void); -void Stats_AddAmmoHits(void); -void Stats_AddAmmoUsed(void); -void Stats_AddMedipacksUsed(double medipack_value); -void Stats_AddDistanceTravelled(XYZ_32 pos, XYZ_32 last_pos); -void Stats_AddDeath(void); diff --git a/src/libtrx/include/libtrx/game/stats/types.h b/src/tr1/game/stats/types.h similarity index 63% rename from src/libtrx/include/libtrx/game/stats/types.h rename to src/tr1/game/stats/types.h index 64a82be99..a0fe6c34a 100644 --- a/src/libtrx/include/libtrx/game/stats/types.h +++ b/src/tr1/game/stats/types.h @@ -5,18 +5,11 @@ typedef struct STATS_COMMON { uint32_t timer; uint32_t kill_count; - uint32_t ammo_used; - uint32_t ammo_hits; - uint32_t distance_travelled; - uint16_t max_secret_count; - double medipacks_used; -#if TR_VERSION == 1 uint16_t secret_count; uint16_t pickup_count; uint32_t max_kill_count; + uint16_t max_secret_count; uint16_t max_pickup_count; - int32_t death_count; -#endif } STATS_COMMON; typedef struct { @@ -26,8 +19,4 @@ typedef struct { typedef struct { struct STATS_COMMON; -#if TR_VERSION == 2 - int32_t found_secrets; - int32_t total_secrets; -#endif } FINAL_STATS; diff --git a/src/tr1/game/text.c b/src/tr1/game/text.c index 4227d1815..7b9aa7e7f 100644 --- a/src/tr1/game/text.c +++ b/src/tr1/game/text.c @@ -7,9 +7,111 @@ #include -#define TEXT_BOX_OFFSET_X 2 -#define TEXT_BOX_OFFSET_Y1 0 -#define TEXT_BOX_OFFSET_Y2 3 +#define TEXT_BOX_OFFSET 2 + +static RGBA_8888 m_MenuColorMap[MC_NUMBER_OF] = { + { 70, 30, 107, 230 }, // MC_PURPLE_C + { 70, 30, 107, 0 }, // MC_PURPLE_E + { 91, 46, 9, 255 }, // MC_BROWN_C + { 91, 46, 9, 0 }, // MC_BROWN_E + { 197, 197, 197, 255 }, // MC_GREY_C + { 45, 45, 45, 255 }, // MC_GREY_E + { 96, 96, 96, 255 }, // MC_GREY_TL + { 32, 32, 32, 255 }, // MC_GREY_TR + { 63, 63, 63, 255 }, // MC_GREY_BL + { 0, 0, 0, 255 }, // MC_GREY_BR + { 0, 0, 0, 255 }, // MC_BLACK + { 232, 192, 112, 255 }, // MC_GOLD_LIGHT + { 140, 112, 56, 255 }, // MC_GOLD_DARK +}; + +typedef struct { + int32_t x; + int32_t y; + int32_t w; + int32_t h; +} QUAD_INFO; + +static void M_DrawTextBackground( + UI_STYLE ui_style, int32_t sx, int32_t sy, int32_t w, int32_t h, + TEXT_STYLE text_style); +static void M_DrawTextOutline( + UI_STYLE ui_style, int32_t sx, int32_t sy, int32_t w, int32_t h, + TEXT_STYLE text_style); + +static void M_DrawTextBackground( + const UI_STYLE ui_style, const int32_t sx, const int32_t sy, int32_t w, + int32_t h, const TEXT_STYLE text_style) +{ + if (ui_style == UI_STYLE_PC) { + Output_DrawScreenFBox(sx, sy, w, h); + return; + } + + // Make sure height and width divisible by 2. + w = 2 * ((w + 1) / 2); + h = 2 * ((h + 1) / 2); + Output_DrawScreenFBox(sx - 1, sy - 1, w + 1, h + 1); + + QUAD_INFO gradient_quads[4] = { { sx, sy, w / 2, h / 2 }, + { sx + w, sy, -w / 2, h / 2 }, + { sx, sy + h, w / 2, -h / 2 }, + { sx + w, sy + h, -w / 2, -h / 2 } }; + + if (text_style == TS_HEADING) { + for (int i = 0; i < 4; i++) { + Output_DrawScreenGradientQuad( + gradient_quads[i].x, gradient_quads[i].y, gradient_quads[i].w, + gradient_quads[i].h, Text_GetMenuColor(MC_BROWN_E), + Text_GetMenuColor(MC_BROWN_E), Text_GetMenuColor(MC_BROWN_E), + Text_GetMenuColor(MC_BROWN_C)); + } + } else if (text_style == TS_REQUESTED) { + for (int i = 0; i < 4; i++) { + Output_DrawScreenGradientQuad( + gradient_quads[i].x, gradient_quads[i].y, gradient_quads[i].w, + gradient_quads[i].h, Text_GetMenuColor(MC_PURPLE_E), + Text_GetMenuColor(MC_PURPLE_E), Text_GetMenuColor(MC_PURPLE_E), + Text_GetMenuColor(MC_PURPLE_C)); + } + } +} + +static void M_DrawTextOutline( + const UI_STYLE ui_style, const int32_t sx, const int32_t sy, int32_t w, + int32_t h, const TEXT_STYLE text_style) +{ + if (ui_style == UI_STYLE_PC) { + Output_DrawScreenBox( + sx, sy, w, h, Text_GetMenuColor(MC_GOLD_DARK), + Text_GetMenuColor(MC_GOLD_LIGHT), TEXT_OUTLINE_THICKNESS); + return; + } + + if (text_style == TS_HEADING) { + Output_DrawGradientScreenBox( + sx, sy, w, h, Text_GetMenuColor(MC_BLACK), + Text_GetMenuColor(MC_BLACK), Text_GetMenuColor(MC_BLACK), + Text_GetMenuColor(MC_BLACK), TEXT_OUTLINE_THICKNESS); + } else if (text_style == TS_BACKGROUND) { + Output_DrawGradientScreenBox( + sx, sy, w, h, Text_GetMenuColor(MC_GREY_TL), + Text_GetMenuColor(MC_GREY_TR), Text_GetMenuColor(MC_GREY_BL), + Text_GetMenuColor(MC_GREY_BR), TEXT_OUTLINE_THICKNESS); + } else if (text_style == TS_REQUESTED) { + // Make sure height and width divisible by 2. + w = 2 * ((w + 1) / 2); + h = 2 * ((h + 1) / 2); + Output_DrawCentreGradientScreenBox( + sx, sy, w, h, Text_GetMenuColor(MC_GREY_E), + Text_GetMenuColor(MC_GREY_C), TEXT_OUTLINE_THICKNESS); + } +} + +RGBA_8888 Text_GetMenuColor(MENU_COLOR color) +{ + return m_MenuColorMap[color]; +} void Text_DrawText(TEXTSTRING *const text) { @@ -52,14 +154,14 @@ void Text_DrawText(TEXTSTRING *const text) y += Screen_GetResHeightDownscaled(RSR_TEXT); } - int32_t bxpos = text->background.offset.x + x - TEXT_BOX_OFFSET_X; + int32_t bxpos = text->background.offset.x + x - TEXT_BOX_OFFSET; int32_t bypos = - text->background.offset.y + y + TEXT_BOX_OFFSET_Y1 - TEXT_HEIGHT_FIXED; + text->background.offset.y + y - TEXT_BOX_OFFSET * 2 - TEXT_HEIGHT; int32_t sx; int32_t sy; - int32_t sh = Screen_GetRenderScale(text->scale.h, RSR_TEXT); - int32_t sv = Screen_GetRenderScale(text->scale.v, RSR_TEXT); + int32_t sh; + int32_t sv; const int32_t start_x = x; const GLYPH_INFO **glyph_ptr = text->glyphs; @@ -77,6 +179,8 @@ void Text_DrawText(TEXTSTRING *const text) sx = Screen_GetRenderScale(x, RSR_TEXT); sy = Screen_GetRenderScale(y, RSR_TEXT); + sh = Screen_GetRenderScale(text->scale.h, RSR_TEXT); + sv = Screen_GetRenderScale(text->scale.v, RSR_TEXT); if (glyph->role == GLYPH_COMPOUND) { const int32_t csx = sx @@ -112,14 +216,14 @@ void Text_DrawText(TEXTSTRING *const text) if (text->background.size.x) { bxpos += text_width / 2; bxpos -= text->background.size.x / 2; - bwidth = text->background.size.x + TEXT_BOX_OFFSET_X * 2; + bwidth = text->background.size.x + TEXT_BOX_OFFSET * 2; } else { - bwidth = text_width + TEXT_BOX_OFFSET_X * 2; + bwidth = text_width + TEXT_BOX_OFFSET * 2; } if (text->background.size.y) { bheight = text->background.size.y; } else { - bheight = TEXT_HEIGHT_FIXED + TEXT_BOX_OFFSET_Y2; + bheight = TEXT_HEIGHT + 7; } } @@ -128,8 +232,9 @@ void Text_DrawText(TEXTSTRING *const text) sy = Screen_GetRenderScale(bypos, RSR_TEXT); sh = Screen_GetRenderScale(bwidth, RSR_TEXT); sv = Screen_GetRenderScale(bheight, RSR_TEXT); - Output_DrawTextBackground( - g_Config.ui.menu_style, sx, sy, sh, sv, 0, text->background.style); + + M_DrawTextBackground( + g_Config.ui.menu_style, sx, sy, sh, sv, text->background.style); } if (text->flags.outline) { @@ -137,12 +242,13 @@ void Text_DrawText(TEXTSTRING *const text) sy = Screen_GetRenderScale(bypos, RSR_TEXT); sh = Screen_GetRenderScale(bwidth, RSR_TEXT); sv = Screen_GetRenderScale(bheight, RSR_TEXT); - Output_DrawTextOutline( - g_Config.ui.menu_style, sx, sy, sh, sv, 0, text->outline.style); + + M_DrawTextOutline( + g_Config.ui.menu_style, sx, sy, sh, sv, text->outline.style); } } int32_t Text_GetMaxLineLength(void) { - return Screen_GetResWidthDownscaled(RSR_TEXT) / (TEXT_HEIGHT_FIXED * 0.6); + return Screen_GetResWidthDownscaled(RSR_TEXT) / (TEXT_HEIGHT * 0.75); } diff --git a/src/tr1/game/text.h b/src/tr1/game/text.h index b398cad9d..418e844cd 100644 --- a/src/tr1/game/text.h +++ b/src/tr1/game/text.h @@ -1,5 +1,13 @@ #pragma once +#include "global/types.h" + #include +#include + +#define TEXT_HEIGHT 11 // TODO: Get rid of this +#define TEXT_OUTLINE_THICKNESS 2 + +RGBA_8888 Text_GetMenuColor(MENU_COLOR color); void Text_DrawText(TEXTSTRING *text); diff --git a/src/tr1/game/ui/common.c b/src/tr1/game/ui/common.c new file mode 100644 index 000000000..0712f2cd5 --- /dev/null +++ b/src/tr1/game/ui/common.c @@ -0,0 +1,34 @@ +#include "game/screen.h" + +#include +#include + +#include + +int32_t UI_GetCanvasWidth(void) +{ + return Screen_GetResWidthDownscaled(RSR_TEXT); +} + +int32_t UI_GetCanvasHeight(void) +{ + return Screen_GetResHeightDownscaled(RSR_TEXT); +} + +UI_INPUT UI_TranslateInput(uint32_t system_keycode) +{ + // clang-format off + switch (system_keycode) { + case SDLK_UP: return UI_KEY_UP; + case SDLK_DOWN: return UI_KEY_DOWN; + case SDLK_LEFT: return UI_KEY_LEFT; + case SDLK_RIGHT: return UI_KEY_RIGHT; + case SDLK_HOME: return UI_KEY_HOME; + case SDLK_END: return UI_KEY_END; + case SDLK_BACKSPACE: return UI_KEY_BACK; + case SDLK_RETURN: return UI_KEY_RETURN; + case SDLK_ESCAPE: return UI_KEY_ESCAPE; + } + // clang-format on + return -1; +} diff --git a/src/tr1/game/ui/dialogs/stats.c b/src/tr1/game/ui/dialogs/stats.c deleted file mode 100644 index 802514177..000000000 --- a/src/tr1/game/ui/dialogs/stats.c +++ /dev/null @@ -1,268 +0,0 @@ -#include "game/ui/dialogs/stats.h" - -#include "game/game_flow.h" -#include "game/game_string.h" -#include "game/savegame.h" -#include "game/stats.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -typedef enum { - M_ROW_KILLS, - M_ROW_PICKUPS, - M_ROW_SECRETS, - M_ROW_DEATHS, - M_ROW_TIMER, - M_ROW_AMMO, - M_ROW_MEDIPACKS_USED, - M_ROW_DISTANCE_TRAVELLED, -} M_ROW_ROLE; - -static void M_FormatTime(char *out, int32_t total_frames); -static void M_FormatDistance(char *const out, int32_t distance); -static void M_Row( - const UI_STATS_DIALOG_STATE *s, const char *key, const char *value); -static void M_RowFromRole( - const UI_STATS_DIALOG_STATE *s, M_ROW_ROLE role, const STATS_COMMON *stats); -static void M_CommonRows( - const UI_STATS_DIALOG_STATE *s, const STATS_COMMON *stats); -static void M_LevelStatsRows(const UI_STATS_DIALOG_STATE *s); -static void M_FinalStatsRows(const UI_STATS_DIALOG_STATE *s); -static const char *M_GetDialogTitle(const UI_STATS_DIALOG_STATE *s); -static void M_BeginDialog(const UI_STATS_DIALOG_STATE *s); -static void M_EndDialog(const UI_STATS_DIALOG_STATE *s); - -static void M_FormatTime(char *const out, const int32_t total_frames) -{ - const int32_t total_seconds = total_frames / LOGIC_FPS; - const int32_t hours = total_seconds / 3600; - const int32_t minutes = (total_seconds / 60) % 60; - const int32_t seconds = total_seconds % 60; - if (hours != 0) { - sprintf(out, "%d:%02d:%02d", hours, minutes, seconds); - } else { - sprintf(out, "%d:%02d", minutes, seconds); - } -} - -static void M_FormatDistance(char *const out, int32_t distance) -{ - distance /= 445; - if (distance < 1000) { - sprintf(out, "%dm", distance); - } else { - sprintf(out, "%d.%02dkm", distance / 1000, (distance % 1000) / 10); - } -} - -static void M_Row( - const UI_STATS_DIALOG_STATE *const s, const char *const key, - const char *const value) -{ - if (s->args.style == UI_STATS_DIALOG_STYLE_BARE) { - UI_BeginStack(UI_STACK_HORIZONTAL); - UI_Label(key); - UI_Label(" "); - UI_Label(value); - UI_EndStack(); - } else { - UI_BeginStackEx((UI_STACK_SETTINGS) { - .orientation = UI_STACK_HORIZONTAL, - .spacing = { .h = 30.0f }, - .align = { .h = UI_STACK_H_ALIGN_DISTRIBUTE }, - }); - UI_Label(key); - UI_Label(value); - UI_EndStack(); - } -} - -static void M_RowFromRole( - const UI_STATS_DIALOG_STATE *const s, const M_ROW_ROLE role, - const STATS_COMMON *const stats) -{ - char buf[50]; - const char *const num_fmt = - g_Config.gameplay.stat_detail_mode == SDM_MINIMAL - ? GS(STATS_BASIC_FMT) - : GS(STATS_DETAIL_FMT); - - switch (role) { - case M_ROW_KILLS: - sprintf(buf, num_fmt, stats->kill_count, stats->max_kill_count); - M_Row(s, GS(STATS_KILLS), buf); - break; - - case M_ROW_PICKUPS: - sprintf(buf, num_fmt, stats->pickup_count, stats->max_pickup_count); - M_Row(s, GS(STATS_PICKUPS), buf); - break; - - case M_ROW_SECRETS: - sprintf( - buf, GS(STATS_DETAIL_FMT), stats->secret_count, - stats->max_secret_count); - M_Row(s, GS(STATS_SECRETS), buf); - break; - - case M_ROW_DEATHS: - sprintf(buf, GS(STATS_BASIC_FMT), stats->death_count); - M_Row(s, GS(STATS_DEATHS), buf); - break; - - case M_ROW_TIMER: - M_FormatTime(buf, stats->timer); - M_Row(s, GS(STATS_TIME_TAKEN), buf); - break; - - case M_ROW_AMMO: - sprintf(buf, GS(PAGINATION_NAV), stats->ammo_hits, stats->ammo_used); - M_Row(s, GS(STATS_AMMO), buf); - break; - - case M_ROW_MEDIPACKS_USED: - sprintf(buf, GS(DETAIL_FLOAT_FMT), stats->medipacks_used); - M_Row(s, GS(STATS_MEDIPACKS_USED), buf); - break; - - case M_ROW_DISTANCE_TRAVELLED: - M_FormatDistance(buf, stats->distance_travelled); - M_Row(s, GS(STATS_DISTANCE_TRAVELLED), buf); - break; - - default: - break; - } -} - -static void M_CommonRows( - const UI_STATS_DIALOG_STATE *const s, const STATS_COMMON *const stats) -{ - if (g_Config.gameplay.stat_detail_mode == SDM_MINIMAL) { - M_RowFromRole(s, M_ROW_KILLS, stats); - M_RowFromRole(s, M_ROW_PICKUPS, stats); - M_RowFromRole(s, M_ROW_SECRETS, stats); - M_RowFromRole(s, M_ROW_TIMER, stats); - } else { - M_RowFromRole(s, M_ROW_TIMER, stats); - M_RowFromRole(s, M_ROW_SECRETS, stats); - M_RowFromRole(s, M_ROW_PICKUPS, stats); - M_RowFromRole(s, M_ROW_KILLS, stats); - if (g_Config.gameplay.stat_detail_mode == SDM_FULL) { - M_RowFromRole(s, M_ROW_AMMO, stats); - M_RowFromRole(s, M_ROW_MEDIPACKS_USED, stats); - M_RowFromRole(s, M_ROW_DISTANCE_TRAVELLED, stats); - } - } - - if (g_Config.gameplay.enable_deaths_counter && stats->death_count >= 0) { - // Always use sum of all levels for the deaths. - // Deaths get stored in the resume info for the level they happen - // on, so if the player dies in Vilcabamba and reloads Caves, they - // should still see an incremented death counter. - M_RowFromRole(s, M_ROW_DEATHS, stats); - } -} - -static void M_LevelStatsRows(const UI_STATS_DIALOG_STATE *const s) -{ - const GF_LEVEL *const current_level = - GF_GetLevel(GFLT_MAIN, s->args.level_num); - const RESUME_INFO *const current_info = - Savegame_GetCurrentInfo(current_level); - const STATS_COMMON *const stats = (STATS_COMMON *)¤t_info->stats; - M_CommonRows(s, stats); -} - -static void M_FinalStatsRows(const UI_STATS_DIALOG_STATE *const s) -{ - FINAL_STATS final_stats; - const GF_LEVEL_TYPE level_type = - GF_GetLevel(GFLT_MAIN, s->args.level_num)->type; - Stats_ComputeFinal(level_type, &final_stats); - M_CommonRows(s, (STATS_COMMON *)&final_stats); -} - -static const char *M_GetDialogTitle(const UI_STATS_DIALOG_STATE *const s) -{ - switch (s->args.mode) { - case UI_STATS_DIALOG_MODE_LEVEL: - return GF_GetLevel(GFLT_MAIN, s->args.level_num)->title; - - case UI_STATS_DIALOG_MODE_FINAL: { - const GF_LEVEL_TYPE level_type = - GF_GetLevel(GFLT_MAIN, s->args.level_num)->type; - if (level_type == GFL_BONUS) { - return GS(STATS_BONUS_STATISTICS); - } - return GS(STATS_FINAL_STATISTICS); - } - } - - return nullptr; -} - -static void M_BeginDialog(const UI_STATS_DIALOG_STATE *const s) -{ - const char *const title = M_GetDialogTitle(s); - UI_BeginModal(0.5f, 0.5f); - if (s->args.style == UI_STATS_DIALOG_STYLE_BARE) { - UI_BeginStackEx((UI_STACK_SETTINGS) { - .orientation = UI_STACK_VERTICAL, - .spacing = { .v = 11.0f }, - .align = { .h = UI_STACK_H_ALIGN_CENTER }, - }); - if (title != nullptr) { - UI_Label(title); - } - } else { - UI_BeginWindow(); - if (title != nullptr) { - UI_WindowTitle(title); - } - UI_BeginWindowBody(); - UI_BeginStackEx((UI_STACK_SETTINGS) { - .orientation = UI_STACK_VERTICAL, - .spacing = { .v = 4.0f }, - .align = { .h = UI_STACK_H_ALIGN_SPAN }, - }); - } -} - -static void M_EndDialog(const UI_STATS_DIALOG_STATE *const s) -{ - if (s->args.style == UI_STATS_DIALOG_STYLE_BARE) { - UI_EndStack(); - } else { - UI_EndStack(); - UI_EndWindowBody(); - UI_EndWindow(); - } - UI_EndModal(); -} - -void UI_StatsDialog(UI_STATS_DIALOG_STATE *const s) -{ - M_BeginDialog(s); - - switch (s->args.mode) { - case UI_STATS_DIALOG_MODE_LEVEL: - M_LevelStatsRows(s); - break; - case UI_STATS_DIALOG_MODE_FINAL: - M_FinalStatsRows(s); - break; - } - - M_EndDialog(s); -} diff --git a/src/tr1/game/ui/dialogs/stats.h b/src/tr1/game/ui/dialogs/stats.h deleted file mode 100644 index 00c84b406..000000000 --- a/src/tr1/game/ui/dialogs/stats.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -#include diff --git a/src/tr1/game/ui/widgets/paginator.c b/src/tr1/game/ui/widgets/paginator.c new file mode 100644 index 000000000..320d91da8 --- /dev/null +++ b/src/tr1/game/ui/widgets/paginator.c @@ -0,0 +1,232 @@ +#include "game/ui/widgets/paginator.h" + +#include "game/game_string.h" +#include "game/input.h" +#include "game/screen.h" +#include "game/sound.h" +#include "game/text.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#define TITLE_MARGIN 5 +#define WINDOW_MARGIN 10 +#define DIALOG_PADDING 5 +#define PADDING_SCALED (3.5 * (DIALOG_PADDING + WINDOW_MARGIN)) + +typedef struct { + UI_WIDGET_VTABLE vtable; + UI_WIDGET *window; + UI_WIDGET *outer_stack; + UI_WIDGET *bottom_stack; + UI_WIDGET *title; + UI_WIDGET *top_spacer; + UI_WIDGET *text; + UI_WIDGET *bottom_spacer; + UI_WIDGET *left_arrow; + UI_WIDGET *right_arrow; + UI_WIDGET *right_arrow_spacer; + UI_WIDGET *page_label; + int32_t current_page; + VECTOR *page_content; + bool shown; +} UI_PAGINATOR; + +static void M_DoLayout(UI_PAGINATOR *self); +static int32_t M_GetWidth(const UI_PAGINATOR *self); +static int32_t M_GetHeight(const UI_PAGINATOR *self); +static void M_SetPosition(UI_PAGINATOR *self, int32_t x, int32_t y); +static bool M_SelectPage(UI_PAGINATOR *const self, int32_t new_page); +static void M_Control(UI_PAGINATOR *self); +static void M_Draw(UI_PAGINATOR *self); +static void M_Free(UI_PAGINATOR *self); + +static void M_DoLayout(UI_PAGINATOR *const self) +{ + M_SetPosition( + self, (Screen_GetResWidthDownscaled(RSR_TEXT) - M_GetWidth(self)) / 2.0, + (Screen_GetResHeightDownscaled(RSR_TEXT) - M_GetHeight(self)) / 2.0); +} + +static int32_t M_GetWidth(const UI_PAGINATOR *const self) +{ + return self->window->get_width(self->window); +} + +static int32_t M_GetHeight(const UI_PAGINATOR *const self) +{ + return self->window->get_height(self->window); +} + +static void M_SetPosition(UI_PAGINATOR *const self, int32_t x, int32_t y) +{ + self->window->set_position(self->window, x, y); +} + +static bool M_SelectPage(UI_PAGINATOR *const self, const int32_t new_page) +{ + if (new_page == self->current_page || new_page < 0 + || new_page >= self->page_content->count) { + return false; + } + + self->current_page = new_page; + UI_Label_ChangeText( + self->text, + *(char **)Vector_Get(self->page_content, self->current_page)); + + char page_indicator[100]; + sprintf( + page_indicator, GS(PAGINATION_NAV), self->current_page + 1, + self->page_content->count); + UI_Label_ChangeText(self->page_label, page_indicator); + UI_Label_SetVisible(self->left_arrow, self->current_page > 0); + UI_Label_SetVisible( + self->right_arrow, self->current_page < self->page_content->count - 1); + + return true; +} + +static void M_Control(UI_PAGINATOR *const self) +{ + const int32_t page_shift = + g_InputDB.menu_left ? -1 : (g_InputDB.menu_right ? 1 : 0); + if (M_SelectPage(self, self->current_page + page_shift)) { + Sound_Effect(SFX_MENU_PASSPORT, nullptr, SPM_ALWAYS); + } + + if (self->window->control != nullptr) { + self->window->control(self->window); + } +} + +static void M_Draw(UI_PAGINATOR *const self) +{ + if (self->shown && self->window->draw != nullptr) { + self->window->draw(self->window); + } +} + +static void M_Free(UI_PAGINATOR *const self) +{ + for (int32_t i = self->page_content->count - 1; i >= 0; i--) { + char *const page = *(char **)Vector_Get(self->page_content, i); + Memory_Free(page); + } + Vector_Free(self->page_content); + + self->text->free(self->text); + self->top_spacer->free(self->top_spacer); + self->title->free(self->title); + if (self->bottom_stack != nullptr) { + self->bottom_spacer->free(self->bottom_spacer); + self->left_arrow->free(self->left_arrow); + self->right_arrow->free(self->right_arrow); + self->right_arrow_spacer->free(self->right_arrow_spacer); + self->page_label->free(self->page_label); + self->bottom_stack->free(self->bottom_stack); + } + self->outer_stack->free(self->outer_stack); + self->window->free(self->window); + Memory_Free(self); +} + +UI_WIDGET *UI_Paginator_Create( + const char *const title, const char *const text, const int32_t max_lines) +{ + UI_PAGINATOR *const self = Memory_Alloc(sizeof(UI_PAGINATOR)); + self->vtable = (UI_WIDGET_VTABLE) { + .control = (UI_WIDGET_CONTROL)M_Control, + .draw = (UI_WIDGET_DRAW)M_Draw, + .get_width = (UI_WIDGET_GET_WIDTH)M_GetWidth, + .get_height = (UI_WIDGET_GET_HEIGHT)M_GetHeight, + .set_position = (UI_WIDGET_SET_POSITION)M_SetPosition, + .free = (UI_WIDGET_FREE)M_Free, + }; + + self->shown = !String_IsEmpty(text); + + self->outer_stack = UI_Stack_Create( + UI_STACK_LAYOUT_VERTICAL, UI_STACK_AUTO_SIZE, UI_STACK_AUTO_SIZE); + + const char *upper_title = String_ToUpper(title); + self->title = + UI_Label_Create(upper_title, UI_LABEL_AUTO_SIZE, TEXT_HEIGHT_FIXED); + Memory_FreePointer(&upper_title); + UI_Stack_AddChild(self->outer_stack, self->title); + + self->top_spacer = UI_Spacer_Create(TITLE_MARGIN, TITLE_MARGIN); + UI_Stack_AddChild(self->outer_stack, self->top_spacer); + + const char *wrapped = + String_WordWrap(text, Text_GetMaxLineLength() - PADDING_SCALED); + self->page_content = String_Paginate(wrapped, max_lines); + self->current_page = 0; + Memory_FreePointer(&wrapped); + + self->text = UI_Label_Create( + *(char **)Vector_Get(self->page_content, 0), UI_LABEL_AUTO_SIZE, + UI_LABEL_AUTO_SIZE); + UI_Stack_AddChild(self->outer_stack, self->text); + + if (self->page_content->count > 1) { + self->bottom_spacer = UI_Spacer_Create(TITLE_MARGIN, TITLE_MARGIN * 3); + UI_Stack_AddChild(self->outer_stack, self->bottom_spacer); + + self->bottom_stack = UI_Stack_Create( + UI_STACK_LAYOUT_HORIZONTAL, UI_STACK_AUTO_SIZE, UI_STACK_AUTO_SIZE); + UI_Stack_AddChild(self->outer_stack, self->bottom_stack); + + self->left_arrow = + UI_Label_Create("\\{button left}", 22, TEXT_HEIGHT_FIXED); + self->right_arrow = + UI_Label_Create("\\{button right}", 16, TEXT_HEIGHT_FIXED); + self->right_arrow_spacer = UI_Spacer_Create(6, TEXT_HEIGHT_FIXED); + self->page_label = + UI_Label_Create("", UI_LABEL_AUTO_SIZE, UI_LABEL_AUTO_SIZE); + UI_Stack_AddChild(self->bottom_stack, self->left_arrow); + UI_Stack_AddChild(self->bottom_stack, self->page_label); + UI_Stack_AddChild(self->bottom_stack, self->right_arrow_spacer); + UI_Stack_AddChild(self->bottom_stack, self->right_arrow); + + UI_Stack_SetHAlign(self->bottom_stack, UI_STACK_H_ALIGN_RIGHT); + } + + self->window = UI_Window_Create( + self->outer_stack, DIALOG_PADDING, DIALOG_PADDING, DIALOG_PADDING * 2, + DIALOG_PADDING); + + // Ensure minimum width for page navigation as text content may be empty. + int32_t max_width = + MAX(self->page_content->count == 1 ? 0 : 100, + UI_Label_MeasureTextWidth(self->title)); + int32_t max_nav_width = 0; + int32_t max_height = 0; + for (int32_t i = 0; i < self->page_content->count; i++) { + M_SelectPage(self, i); + max_width = MAX(max_width, UI_Label_MeasureTextWidth(self->text)); + max_height = MAX(max_height, UI_Label_MeasureTextHeight(self->text)); + if (self->bottom_stack != nullptr) { + max_nav_width = + MAX(max_nav_width, UI_Label_MeasureTextWidth(self->page_label)); + } + } + + UI_Label_SetSize(self->text, max_width, max_height); + if (self->bottom_stack != nullptr) { + UI_Stack_SetSize(self->bottom_stack, max_width, UI_STACK_AUTO_SIZE); + UI_Label_SetSize(self->page_label, max_nav_width, UI_LABEL_AUTO_SIZE); + } + + M_SelectPage(self, 0); + M_DoLayout(self); + + return (UI_WIDGET *)self; +} diff --git a/src/tr1/game/ui/widgets/paginator.h b/src/tr1/game/ui/widgets/paginator.h new file mode 100644 index 000000000..3dbbf2d2b --- /dev/null +++ b/src/tr1/game/ui/widgets/paginator.h @@ -0,0 +1,6 @@ +#pragma once + +#include + +UI_WIDGET *UI_Paginator_Create( + const char *title, const char *text, int32_t max_lines); diff --git a/src/tr1/game/ui/widgets/stats_dialog.c b/src/tr1/game/ui/widgets/stats_dialog.c new file mode 100644 index 000000000..31905b3cc --- /dev/null +++ b/src/tr1/game/ui/widgets/stats_dialog.c @@ -0,0 +1,341 @@ +#include "game/ui/widgets/stats_dialog.h" + +#include "game/game_flow.h" +#include "game/game_string.h" +#include "game/input.h" +#include "game/stats.h" +#include "global/vars.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#define ROW_HEIGHT_BARE 30 +#define ROW_HEIGHT_BORDERED 18 + +typedef enum { + M_ROW_KILLS, + M_ROW_PICKUPS, + M_ROW_SECRETS, + M_ROW_DEATHS, + M_ROW_TIMER, +} M_ROW_ROLE; + +typedef struct { + M_ROW_ROLE role; + UI_WIDGET *stack; + UI_WIDGET *key_label; + UI_WIDGET *value_label; +} M_ROW; + +typedef struct { + UI_WIDGET_VTABLE vtable; + UI_STATS_DIALOG_ARGS args; + GF_LEVEL_TYPE level_type; + int32_t listener; + + int32_t row_count; + UI_WIDGET *title; + UI_WIDGET *stack; + UI_WIDGET *window; + UI_WIDGET *root; // just a pointer to either stack or window + M_ROW *rows; +} UI_STATS_DIALOG; + +static void M_FormatTime(char *out, int32_t total_frames); +static const char *M_GetDialogTitle(UI_STATS_DIALOG *self); +static void M_AddRow( + UI_STATS_DIALOG *self, M_ROW_ROLE role, const char *key, const char *value); +static void M_AddRowFromRole( + UI_STATS_DIALOG *self, M_ROW_ROLE role, const STATS_COMMON *stats, + const GAME_INFO *game_info); +static void M_AddCommonRows( + UI_STATS_DIALOG *self, const STATS_COMMON *stats, + const GAME_INFO *game_info); +static void M_AddLevelStatsRows(UI_STATS_DIALOG *self); +static void M_AddFinalStatsRows(UI_STATS_DIALOG *self); +static void M_UpdateTimerRow(UI_STATS_DIALOG *self); +static void M_DoLayout(UI_STATS_DIALOG *self); +static void M_HandleLayoutUpdate(const EVENT *event, void *data); + +static int32_t M_GetWidth(const UI_STATS_DIALOG *self); +static int32_t M_GetHeight(const UI_STATS_DIALOG *self); +static void M_SetPosition(UI_STATS_DIALOG *self, int32_t x, int32_t y); +static void M_Control(UI_STATS_DIALOG *self); +static void M_Draw(UI_STATS_DIALOG *self); +static void M_Free(UI_STATS_DIALOG *self); + +static void M_FormatTime(char *const out, const int32_t total_frames) +{ + const int32_t total_seconds = total_frames / LOGIC_FPS; + const int32_t hours = total_seconds / 3600; + const int32_t minutes = (total_seconds / 60) % 60; + const int32_t seconds = total_seconds % 60; + if (hours != 0) { + sprintf(out, "%d:%02d:%02d", hours, minutes, seconds); + } else { + sprintf(out, "%d:%02d", minutes, seconds); + } +} + +static const char *M_GetDialogTitle(UI_STATS_DIALOG *const self) +{ + switch (self->args.mode) { + case UI_STATS_DIALOG_MODE_LEVEL: + return GF_GetLevel(GFLT_MAIN, self->args.level_num)->title; + + case UI_STATS_DIALOG_MODE_FINAL: + return self->level_type == GFL_BONUS ? GS(STATS_BONUS_STATISTICS) + : GS(STATS_FINAL_STATISTICS); + } + + return nullptr; +} + +static void M_AddRow( + UI_STATS_DIALOG *const self, const M_ROW_ROLE role, const char *const key, + const char *const value) +{ + self->row_count++; + self->rows = Memory_Realloc(self->rows, sizeof(M_ROW) * self->row_count); + M_ROW *const row = &self->rows[self->row_count - 1]; + row->role = role; + + // create a stack + int32_t row_height; + if (self->args.style == UI_STATS_DIALOG_STYLE_BARE) { + row_height = ROW_HEIGHT_BARE; + row->stack = UI_Stack_Create( + UI_STACK_LAYOUT_HORIZONTAL, UI_STACK_AUTO_SIZE, row_height); + } else { + row_height = ROW_HEIGHT_BORDERED; + row->stack = + UI_Stack_Create(UI_STACK_LAYOUT_HORIZONTAL, 200, row_height); + UI_Stack_SetHAlign(row->stack, UI_STACK_H_ALIGN_DISTRIBUTE); + } + + // create a key label; append space for the bare style + char key2[strlen(key) + 2]; + sprintf( + key2, self->args.style == UI_STATS_DIALOG_STYLE_BARE ? "%s " : "%s", + key); + row->key_label = UI_Label_Create(key2, UI_LABEL_AUTO_SIZE, row_height); + + // create a value label + row->value_label = UI_Label_Create(value, UI_LABEL_AUTO_SIZE, row_height); + + UI_Stack_AddChild(row->stack, row->key_label); + UI_Stack_AddChild(row->stack, row->value_label); + UI_Stack_AddChild(self->stack, row->stack); +} + +static void M_AddRowFromRole( + UI_STATS_DIALOG *const self, const M_ROW_ROLE role, + const STATS_COMMON *const stats, const GAME_INFO *const game_info) +{ + char buf[50]; + const char *const num_fmt = g_Config.gameplay.enable_detailed_stats + ? GS(STATS_DETAIL_FMT) + : GS(STATS_BASIC_FMT); + + switch (role) { + case M_ROW_KILLS: + sprintf(buf, num_fmt, stats->kill_count, stats->max_kill_count); + M_AddRow(self, role, GS(STATS_KILLS), buf); + break; + + case M_ROW_PICKUPS: + sprintf(buf, num_fmt, stats->pickup_count, stats->max_pickup_count); + M_AddRow(self, role, GS(STATS_PICKUPS), buf); + break; + + case M_ROW_SECRETS: + sprintf( + buf, GS(STATS_DETAIL_FMT), stats->secret_count, + stats->max_secret_count); + M_AddRow(self, role, GS(STATS_SECRETS), buf); + break; + + case M_ROW_DEATHS: + sprintf(buf, "%d", game_info->death_count); + M_AddRow(self, role, GS(STATS_DEATHS), buf); + break; + + case M_ROW_TIMER: + M_FormatTime(buf, stats->timer); + M_AddRow(self, role, GS(STATS_TIME_TAKEN), buf); + break; + + default: + break; + } +} + +static void M_AddCommonRows( + UI_STATS_DIALOG *const self, const STATS_COMMON *const stats, + const GAME_INFO *const game_info) +{ + M_AddRowFromRole(self, M_ROW_KILLS, stats, game_info); + M_AddRowFromRole(self, M_ROW_PICKUPS, stats, game_info); + M_AddRowFromRole(self, M_ROW_SECRETS, stats, game_info); + if (g_Config.gameplay.enable_deaths_counter + && game_info->death_count >= 0) { + // Always use sum of all levels for the deaths. + // Deaths get stored in the resume info for the level they happen on, + // so if the player dies in Vilcabamba and reloads Caves, they should + // still see an incremented death counter. + M_AddRowFromRole(self, M_ROW_DEATHS, stats, game_info); + } + M_AddRowFromRole(self, M_ROW_TIMER, stats, game_info); +} + +static void M_AddLevelStatsRows(UI_STATS_DIALOG *const self) +{ + const STATS_COMMON *stats = + (STATS_COMMON *)&g_GameInfo.current[self->args.level_num].stats; + M_AddCommonRows(self, stats, &g_GameInfo); +} + +static void M_AddFinalStatsRows(UI_STATS_DIALOG *const self) +{ + FINAL_STATS final_stats; + Stats_ComputeFinal(self->level_type, &final_stats); + M_AddCommonRows(self, (STATS_COMMON *)&final_stats, &g_GameInfo); +} + +static void M_UpdateTimerRow(UI_STATS_DIALOG *const self) +{ + if (self->args.mode != UI_STATS_DIALOG_MODE_LEVEL) { + return; + } + + for (int32_t i = 0; i < self->row_count; i++) { + if (self->rows[i].role != M_ROW_TIMER) { + continue; + } + char buf[50]; + M_FormatTime(buf, g_GameInfo.current[self->args.level_num].stats.timer); + UI_Label_ChangeText(self->rows[i].value_label, buf); + return; + } +} + +static void M_DoLayout(UI_STATS_DIALOG *const self) +{ + M_SetPosition( + self, (UI_GetCanvasWidth() - M_GetWidth(self)) / 2, + (UI_GetCanvasHeight() - M_GetHeight(self)) / 2 + 25); +} + +static void M_HandleLayoutUpdate(const EVENT *event, void *data) +{ + UI_STATS_DIALOG *const self = (UI_STATS_DIALOG *)data; + M_DoLayout(self); +} + +static int32_t M_GetWidth(const UI_STATS_DIALOG *const self) +{ + return self->root->get_width(self->root); +} + +static int32_t M_GetHeight(const UI_STATS_DIALOG *const self) +{ + return self->root->get_height(self->root); +} + +static void M_SetPosition( + UI_STATS_DIALOG *const self, const int32_t x, const int32_t y) +{ + self->root->set_position(self->root, x, y); +} + +static void M_Control(UI_STATS_DIALOG *const self) +{ + if (self->root->control != nullptr) { + self->root->control(self->root); + } + M_UpdateTimerRow(self); +} + +static void M_Draw(UI_STATS_DIALOG *const self) +{ + if (self->root->draw != nullptr) { + self->root->draw(self->root); + } +} + +static void M_Free(UI_STATS_DIALOG *const self) +{ + if (self->title != nullptr) { + self->title->free(self->title); + } + for (int32_t i = 0; i < self->row_count; i++) { + self->rows[i].key_label->free(self->rows[i].key_label); + self->rows[i].value_label->free(self->rows[i].value_label); + self->rows[i].stack->free(self->rows[i].stack); + } + if (self->window != nullptr) { + self->window->free(self->window); + } + self->stack->free(self->stack); + UI_Events_Unsubscribe(self->listener); + Memory_Free(self); +} + +UI_WIDGET *UI_StatsDialog_Create(const UI_STATS_DIALOG_ARGS args) +{ + UI_STATS_DIALOG *const self = Memory_Alloc(sizeof(UI_STATS_DIALOG)); + self->vtable = (UI_WIDGET_VTABLE) { + .get_width = (UI_WIDGET_GET_WIDTH)M_GetWidth, + .get_height = (UI_WIDGET_GET_HEIGHT)M_GetHeight, + .set_position = (UI_WIDGET_SET_POSITION)M_SetPosition, + .control = (UI_WIDGET_CONTROL)M_Control, + .draw = (UI_WIDGET_DRAW)M_Draw, + .free = (UI_WIDGET_FREE)M_Free, + }; + + self->args = args; + self->level_type = GF_GetLevel(GFLT_MAIN, self->args.level_num)->type; + + self->row_count = 0; + self->rows = nullptr; + self->stack = UI_Stack_Create( + UI_STACK_LAYOUT_VERTICAL, UI_STACK_AUTO_SIZE, UI_STACK_AUTO_SIZE); + UI_Stack_SetHAlign(self->stack, UI_STACK_H_ALIGN_CENTER); + + self->listener = UI_Events_Subscribe( + "layout_update", nullptr, M_HandleLayoutUpdate, self); + + const char *title = M_GetDialogTitle(self); + switch (self->args.style) { + case UI_STATS_DIALOG_STYLE_BARE: + if (title != nullptr) { + self->title = + UI_Label_Create(title, UI_LABEL_AUTO_SIZE, ROW_HEIGHT_BARE); + UI_Stack_AddChild(self->stack, self->title); + } + self->root = self->stack; + break; + + case UI_STATS_DIALOG_STYLE_BORDERED: + self->window = UI_Window_Create(self->stack, 8, 8, 8, 8); + UI_Window_SetTitle(self->window, title); + self->root = self->window; + break; + } + + if (self->args.mode == UI_STATS_DIALOG_MODE_LEVEL) { + M_AddLevelStatsRows(self); + } else if (self->args.mode == UI_STATS_DIALOG_MODE_FINAL) { + M_AddFinalStatsRows(self); + } + + M_DoLayout(self); + return (UI_WIDGET *)self; +} diff --git a/src/tr1/game/ui/widgets/stats_dialog.h b/src/tr1/game/ui/widgets/stats_dialog.h new file mode 100644 index 000000000..96afd51a2 --- /dev/null +++ b/src/tr1/game/ui/widgets/stats_dialog.h @@ -0,0 +1,3 @@ +#pragma once + +#include diff --git a/src/tr1/game/viewport.c b/src/tr1/game/viewport.c index 98e3e2cb4..b71c18503 100644 --- a/src/tr1/game/viewport.c +++ b/src/tr1/game/viewport.c @@ -1,6 +1,5 @@ #include "game/viewport.h" -#include "game/output.h" #include "game/screen.h" #include "global/const.h" #include "global/vars.h" @@ -22,8 +21,8 @@ void Viewport_Init(int32_t x, int32_t y, int32_t width, int32_t height) { m_MinX = x; m_MinY = y; - m_MaxX = x + width; - m_MaxY = y + height; + m_MaxX = x + width - 1; + m_MaxY = y + height - 1; m_CenterX = (m_MinX + m_MaxX) / 2; m_CenterY = (m_MinY + m_MaxY) / 2; m_Width = width; @@ -88,5 +87,4 @@ int16_t Viewport_GetUserFOV(void) void Viewport_SetFOV(int16_t fov) { m_CurrentFOV = fov; - Output_ObserveFOVChange(); } diff --git a/src/tr1/global/const.h b/src/tr1/global/const.h index 8f8a0c3b5..98ab344ba 100644 --- a/src/tr1/global/const.h +++ b/src/tr1/global/const.h @@ -6,7 +6,7 @@ #define PHD_ONE 0x10000 #define MAX_REQLINES 18 -#define LOT_SLOT_COUNT 32 +#define NUM_SLOTS 32 #define MAX_SECRETS 16 #define LARA_MAX_HITPOINTS 1000 #define LARA_MAX_AIR 1800 @@ -41,6 +41,17 @@ #define DAMAGE_START 140 #define DAMAGE_LENGTH 14 #define NO_CAMERA (-1) +#define PELLET_SCATTER (20 * DEG_1) +#define NUM_SG_SHELLS 2 +#define GUN_AMMO_CLIP 16 +#define MAGNUM_AMMO_CLIP 25 +#define UZI_AMMO_CLIP 50 +#define SHOTGUN_AMMO_CLIP 6 +#define GUN_AMMO_QTY (GUN_AMMO_CLIP * 2) +#define MAGNUM_AMMO_QTY (MAGNUM_AMMO_CLIP * 2) +#define UZI_AMMO_QTY (UZI_AMMO_CLIP * 2) +#define SHOTGUN_AMMO_QTY (SHOTGUN_AMMO_CLIP * NUM_SG_SHELLS) +#define NUM_EFFECTS 1000 #define DEATH_WAIT (10 * LOGIC_FPS) #define DEATH_WAIT_MIN (2 * LOGIC_FPS) #define MAX_HEAD_ROTATION (50 * DEG_1) // = 9100 @@ -55,6 +66,9 @@ #define MIN_HEAD_TILT_SURF (-40 * DEG_1) // = -7280 #define DIVE_WAIT 10 #define STEPUP_HEIGHT ((STEP_L * 3) / 2) // = 384 +#define FRONT_ARC DEG_90 +#define MAX_HEAD_CHANGE (DEG_1 * 5) // = 910 +#define MAX_TILT (DEG_1 * 3) // = 546 #define CAM_A_HANG 0 #define CAM_E_HANG (-60 * DEG_1) // = -10920 #define CAM_WADE_ELEVATION (-22 * DEG_1) // = -4004 @@ -65,12 +79,18 @@ #define COMBAT_DISTANCE (WALL_L * 5 / 2) // = 2560 #define MAX_ELEVATION (85 * DEG_1) // = 15470 #define DEFAULT_RADIUS 10 +#define UNIT_SHADOW 256 #define NO_BAD_POS (-NO_HEIGHT) #define NO_BAD_NEG NO_HEIGHT #define BAD_JUMP_CEILING ((STEP_L * 3) / 4) // = 192 #define MAX_WIBBLE 2 +#define MAX_SHADE 0x300 +#define MAX_LIGHTING 0x1FFF #define NO_VERT_MOVE 0x2000 #define NO_BOX (-1) +#define ATTACK_RANGE SQUARE(WALL_L * 3) // = 9437184 +#define ESCAPE_CHANCE 2048 +#define RECOVER_CHANCE 256 #define PASSPORT_FOV 65 #define PICKUPS_FOV 65 @@ -87,6 +107,8 @@ #define OPTION_RING_OBJECTS 4 #define TITLE_RING_OBJECTS 5 #define CAMERA_2_RING 598 +#define LOW_LIGHT 0x1400 // = 5120 +#define HIGH_LIGHT 0x1000 // = 4096 #if _MSC_VER > 0x500 #define strdup _strdup // fixes error about POSIX function diff --git a/src/tr1/global/enum_map.def b/src/tr1/global/enum_map.def index 4dc74c1d0..23cecc0e2 100644 --- a/src/tr1/global/enum_map.def +++ b/src/tr1/global/enum_map.def @@ -30,6 +30,6 @@ ENUM_MAP_DEFINE(TARGET_LOCK_MODE, TLM_FULL, "full-lock") ENUM_MAP_DEFINE(TARGET_LOCK_MODE, TLM_SEMI, "semi-lock") ENUM_MAP_DEFINE(TARGET_LOCK_MODE, TLM_NONE, "no-lock") -ENUM_MAP_DEFINE(STAT_DETAIL_MODE, SDM_MINIMAL, "minimal") -ENUM_MAP_DEFINE(STAT_DETAIL_MODE, SDM_DETAILED, "detailed") -ENUM_MAP_DEFINE(STAT_DETAIL_MODE, SDM_FULL, "full") +ENUM_MAP_DEFINE(MUSIC_LOAD_CONDITION, MUSIC_LOAD_NEVER, "never") +ENUM_MAP_DEFINE(MUSIC_LOAD_CONDITION, MUSIC_LOAD_NON_AMBIENT, "non-ambient") +ENUM_MAP_DEFINE(MUSIC_LOAD_CONDITION, MUSIC_LOAD_ALWAYS, "always") diff --git a/src/tr1/global/types.h b/src/tr1/global/types.h index 6b7384d57..aa663a6c5 100644 --- a/src/tr1/global/types.h +++ b/src/tr1/global/types.h @@ -1,5 +1,6 @@ #pragma once +#include "game/stats/types.h" #include "global/const.h" #include @@ -89,6 +90,11 @@ typedef enum { BT_PROGRESS = 3, } BAR_TYPE; +typedef enum { + GBF_NGPLUS = 1 << 0, + GBF_JAPANESE = 1 << 1, +} GAME_BONUS_FLAG; + typedef enum { PASSPORT_MODE_BROWSE = 0, PASSPORT_MODE_LOAD_GAME = 1, @@ -124,6 +130,45 @@ typedef struct { } PHD_VBUF; typedef struct { + SECTOR *sector; + SECTOR old_sector; + int16_t block; +} DOORPOS_DATA; + +typedef struct { + int32_t lara_hitpoints; + uint16_t pistol_ammo; + uint16_t magnum_ammo; + uint16_t uzi_ammo; + uint16_t shotgun_ammo; + uint8_t num_medis; + uint8_t num_big_medis; + uint8_t num_scions; + int8_t gun_status; + LARA_GUN_TYPE equipped_gun_type; + LARA_GUN_TYPE holsters_gun_type; + LARA_GUN_TYPE back_gun_type; + union { + uint16_t all; + struct { + uint16_t available : 1; + uint16_t got_pistols : 1; + uint16_t got_magnums : 1; + uint16_t got_uzis : 1; + uint16_t got_shotgun : 1; + uint16_t costume : 1; + }; + } flags; + LEVEL_STATS stats; +} RESUME_INFO; + +typedef struct { + RESUME_INFO *current; + int32_t death_count; + + uint8_t bonus_flag; + bool bonus_level_unlock; + int16_t save_initial_version; PASSPORT_MODE passport_selection; int32_t select_save_slot; int32_t select_level_num; @@ -141,6 +186,23 @@ typedef struct { bool ask_for_save; } GAME_INFO; +typedef enum { + MC_PURPLE_C, + MC_PURPLE_E, + MC_BROWN_C, + MC_BROWN_E, + MC_GREY_C, + MC_GREY_E, + MC_GREY_TL, + MC_GREY_TR, + MC_GREY_BL, + MC_GREY_BR, + MC_BLACK, + MC_GOLD_LIGHT, + MC_GOLD_DARK, + MC_NUMBER_OF, +} MENU_COLOR; + typedef struct { int32_t xv; int32_t yv; @@ -160,3 +222,46 @@ typedef struct { int16_t flash_time; int16_t sample_num; } WEAPON_INFO; + +typedef struct { + int32_t x; + int32_t y; + int32_t z; + int32_t mesh_num; +} BITE; + +typedef struct { + int16_t zone_num; + int16_t enemy_zone; + int32_t distance; + int32_t ahead; + int32_t bite; + int16_t angle; + int16_t enemy_facing; +} AI_INFO; + +typedef struct { + bool is_blocked; + char *content_text; + TEXTSTRING *content; +} REQUESTER_ITEM; + +typedef struct { + uint16_t items_used; + uint16_t max_items; + uint16_t requested; + uint16_t vis_lines; + int16_t line_offset; + int16_t line_old_offset; + uint16_t pix_width; + uint16_t line_height; + bool is_blockable; + int16_t x; + int16_t y; + char *heading_text; + TEXTSTRING *heading; + TEXTSTRING *background; + TEXTSTRING *moreup; + TEXTSTRING *moredown; + REQUESTER_ITEM *items; +} REQUEST_INFO; diff --git a/src/tr1/global/vars.c b/src/tr1/global/vars.c index 6adf7b098..5ecb4065c 100644 --- a/src/tr1/global/vars.c +++ b/src/tr1/global/vars.c @@ -15,9 +15,13 @@ float g_FltResZBuf; LARA_INFO g_Lara = {}; ITEM *g_LaraItem = nullptr; GAME_INFO g_GameInfo = { .select_save_slot = -1 }; +int32_t g_SavedGamesCount = 0; +int32_t g_SaveCounter = 0; bool g_LevelComplete = false; int32_t g_OverlayFlag = 0; +INVENTORY_MODE g_InvMode; + #ifndef MESON_BUILD const char *g_TRXVersion = "TR1X (non-Docker build)"; #endif diff --git a/src/tr1/global/vars.h b/src/tr1/global/vars.h index d7e0a1b4c..b0d83e4e5 100644 --- a/src/tr1/global/vars.h +++ b/src/tr1/global/vars.h @@ -23,5 +23,11 @@ extern int32_t g_FPSCounter; extern LARA_INFO g_Lara; extern ITEM *g_LaraItem; extern GAME_INFO g_GameInfo; +extern int32_t g_SavedGamesCount; +extern int32_t g_SaveCounter; extern bool g_LevelComplete; extern int32_t g_OverlayFlag; + +extern REQUEST_INFO g_SavegameRequester; + +extern INVENTORY_MODE g_InvMode; diff --git a/src/tr1/meson.build b/src/tr1/meson.build index a047f9ce8..06aa4e2cb 100644 --- a/src/tr1/meson.build +++ b/src/tr1/meson.build @@ -34,7 +34,6 @@ build_opts = [ # end of common options '-ffile-prefix-map=@0@/='.format(relative_dir), '-DTR_VERSION=1', - '-DDEBUG=' + (get_option('buildtype') == 'debug' ? '1' : '0'), ] + trx.get_variable('defines') add_project_arguments(build_opts, language: 'c') @@ -162,6 +161,7 @@ sources = [ 'game/objects/creatures/bacon_lara.c', 'game/objects/creatures/baldy.c', 'game/objects/creatures/bat.c', + 'game/objects/creatures/bear.c', 'game/objects/creatures/centaur.c', 'game/objects/creatures/cowboy.c', 'game/objects/creatures/crocodile.c', @@ -179,6 +179,7 @@ sources = [ 'game/objects/creatures/statue.c', 'game/objects/creatures/torso.c', 'game/objects/creatures/trex.c', + 'game/objects/creatures/wolf.c', 'game/objects/effects/blood.c', 'game/objects/effects/body_part.c', 'game/objects/effects/bubble.c', @@ -194,9 +195,15 @@ sources = [ 'game/objects/effects/splash.c', 'game/objects/effects/twinkle.c', 'game/objects/general/boat.c', + 'game/objects/general/bridge_common.c', + 'game/objects/general/bridge_flat.c', + 'game/objects/general/bridge_tilt1.c', + 'game/objects/general/bridge_tilt2.c', 'game/objects/general/cabin.c', 'game/objects/general/camera_target.c', 'game/objects/general/cog.c', + 'game/objects/general/door.c', + 'game/objects/general/drawbridge.c', 'game/objects/general/earthquake.c', 'game/objects/general/keyhole.c', 'game/objects/general/moving_bar.c', @@ -208,6 +215,7 @@ sources = [ 'game/objects/general/scion4.c', 'game/objects/general/scion_holder.c', 'game/objects/general/switch.c', + 'game/objects/general/trapdoor.c', 'game/objects/general/waterfall.c', 'game/objects/setup.c', 'game/objects/traps/damocles_sword.c', @@ -237,18 +245,8 @@ sources = [ 'game/option/option_passport.c', 'game/option/option_sound.c', 'game/output.c', - 'game/output/draw_misc.c', - 'game/output/func.c', - 'game/output/meshes/common.c', - 'game/output/meshes/dynamic.c', - 'game/output/meshes/objects.c', - 'game/output/meshes/rooms.c', - 'game/output/shader.c', - 'game/output/sprites.c', - 'game/output/state.c', - 'game/output/textures.c', - 'game/output/vertex_range.c', 'game/overlay.c', + 'game/requester.c', 'game/room.c', 'game/room_draw.c', 'game/savegame/savegame.c', @@ -260,10 +258,13 @@ sources = [ 'game/spawn.c', 'game/stats/common.c', 'game/text.c', - 'game/ui/dialogs/stats.c', + 'game/ui/common.c', + 'game/ui/widgets/stats_dialog.c', + 'game/ui/widgets/paginator.c', 'game/viewport.c', 'global/enum_map.c', 'global/vars.c', + 'specific/s_output.c', 'specific/s_shell.c', resources, ] diff --git a/src/tr1/specific/s_output.c b/src/tr1/specific/s_output.c new file mode 100644 index 000000000..7ae61cd15 --- /dev/null +++ b/src/tr1/specific/s_output.c @@ -0,0 +1,1391 @@ +#include "specific/s_output.h" + +#include "game/output.h" +#include "game/screen.h" +#include "game/shell.h" +#include "game/viewport.h" +#include "global/vars.h" +#include "specific/s_shell.h" + +#include +#include +#include + +#include + +#define CLIP_VERTCOUNT_SCALE 4 +#define MAP_DEPTH(zv) (g_FltResZBuf - g_FltResZ * (1.0 / (double)(zv))) +#define VBUF_VISIBLE(a, b, c) \ + (((a).ys - (b).ys) * ((c).xs - (b).xs) \ + >= ((c).ys - (b).ys) * ((a).xs - (b).xs)) + +#define S_Output_CheckError(result) \ + { \ + if (!result) { \ + Shell_ExitSystem("Fatal 2D renderer error!"); \ + } \ + } + +static int m_TextureMap[GFX_MAX_TEXTURES] = { GFX_NO_TEXTURE }; +static int m_EnvMapTexture = GFX_NO_TEXTURE; + +static GFX_2D_RENDERER *m_Renderer2D = nullptr; +static GFX_3D_RENDERER *m_Renderer3D = nullptr; +static bool m_IsTextureMode = false; +static int32_t m_SelectedTexture = -1; + +static int32_t m_SurfaceWidth = 0; +static int32_t m_SurfaceHeight = 0; +static float m_SurfaceMinX = 0.0f; +static float m_SurfaceMinY = 0.0f; +static float m_SurfaceMaxX = 0.0f; +static float m_SurfaceMaxY = 0.0f; +static GFX_2D_SURFACE *m_PrimarySurface = nullptr; +static GFX_2D_SURFACE *m_PictureSurface = nullptr; +static GFX_2D_SURFACE *m_TextureSurfaces[GFX_MAX_TEXTURES] = { nullptr }; + +static inline float M_GetUV(uint16_t uv); +static void M_ReleaseTextures(void); +static void M_ReleaseSurfaces(void); +static void M_FlipPrimaryBuffer(void); +static void M_ClearSurface(GFX_2D_SURFACE *surface); +static void M_DrawTriangleFan(GFX_3D_VERTEX *vertices, int vertex_count); +static void M_DrawTriangleStrip(GFX_3D_VERTEX *vertices, int vertex_count); +static int32_t M_VisibleZClip( + const PHD_VBUF *vn1, const PHD_VBUF *vn2, const PHD_VBUF *vn3); +static int32_t M_ZedClipper( + int32_t vertex_count, const PHD_VBUF *vns[], GFX_3D_VERTEX *vertices); + +static inline float M_GetUV(const uint16_t uv) +{ + return g_Config.rendering.pretty_pixels + && g_Config.rendering.texture_filter == GFX_TF_NN + ? uv / 65536.0f + : ((uv & 0xFF00) + 127) / 65536.0f; +} + +static void M_ReleaseTextures(void) +{ + if (m_Renderer3D == nullptr) { + return; + } + for (int i = 0; i < GFX_MAX_TEXTURES; i++) { + if (m_TextureMap[i] != GFX_NO_TEXTURE) { + GFX_3D_Renderer_UnregisterTexturePage( + m_Renderer3D, m_TextureMap[i]); + m_TextureMap[i] = GFX_NO_TEXTURE; + } + } + if (m_EnvMapTexture != GFX_NO_TEXTURE) { + GFX_3D_Renderer_UnregisterEnvironmentMap(m_Renderer3D, m_EnvMapTexture); + } +} + +static void M_ReleaseSurfaces(void) +{ + if (m_PrimarySurface) { + M_ClearSurface(m_PrimarySurface); + + GFX_2D_Surface_Free(m_PrimarySurface); + m_PrimarySurface = nullptr; + } + + for (int i = 0; i < GFX_MAX_TEXTURES; i++) { + if (m_TextureSurfaces[i] != nullptr) { + GFX_2D_Surface_Free(m_TextureSurfaces[i]); + m_TextureSurfaces[i] = nullptr; + } + } + + if (m_PictureSurface) { + GFX_2D_Surface_Free(m_PictureSurface); + m_PictureSurface = nullptr; + } +} + +void Output_FillEnvironmentMap(void) +{ + GFX_3D_Renderer_FillEnvironmentMap(m_Renderer3D); +} + +static void M_FlipPrimaryBuffer(void) +{ + GFX_Context_SwapBuffers(); +} + +static void M_ClearSurface(GFX_2D_SURFACE *surface) +{ + GFX_2D_Surface_Clear(surface, 0); +} + +static void M_DrawTriangleFan(GFX_3D_VERTEX *vertices, int vertex_count) +{ + GFX_3D_Renderer_RenderPrimFan(m_Renderer3D, vertices, vertex_count); +} + +static void M_DrawTriangleStrip(GFX_3D_VERTEX *vertices, int vertex_count) +{ + GFX_3D_Renderer_RenderPrimStrip(m_Renderer3D, vertices, vertex_count); +} + +static int32_t M_VisibleZClip( + const PHD_VBUF *const vn1, const PHD_VBUF *const vn2, + const PHD_VBUF *const vn3) +{ + double v1x = vn1->xv; + double v1y = vn1->yv; + double v1z = vn1->zv; + double v2x = vn2->xv; + double v2y = vn2->yv; + double v2z = vn2->zv; + double v3x = vn3->xv; + double v3y = vn3->yv; + double v3z = vn3->zv; + double a = v3y * v1x - v1y * v3x; + double b = v3x * v1z - v1x * v3z; + double c = v3z * v1y - v1z * v3y; + return a * v2z + b * v2y + c * v2x < 0.0; +} + +static int32_t M_ZedClipper( + const int32_t vertex_count, const PHD_VBUF *vns[], + GFX_3D_VERTEX *const vertices) +{ + const float multiplier = g_Config.visuals.brightness / 16.0f; + const float near_z = Output_GetNearZ(); + const float persp_o_near_z = (double)g_PhdPersp / near_z; + + GFX_3D_VERTEX *v = &vertices[0]; + int32_t current = 0; + int32_t prev = vertex_count - 1; + for (int32_t i = 0; i < vertex_count; i++) { + const PHD_VBUF *vn0 = vns[current]; + const PHD_VBUF *vn1 = vns[prev]; + const int32_t diff0 = near_z - vn0->zv; + const int32_t diff1 = near_z - vn1->zv; + if ((diff0 | diff1) >= 0) { + goto loop_end; + } + + if ((diff0 ^ diff1) < 0) { + const double clip = diff0 / (vn1->zv - vn0->zv); + v->x = (vn0->xv + (vn1->xv - vn0->xv) * clip) * persp_o_near_z + + Viewport_GetCenterX(); + v->y = (vn0->yv + (vn1->yv - vn0->yv) * clip) * persp_o_near_z + + Viewport_GetCenterY(); + v->z = MAP_DEPTH(vn0->zv + (vn1->zv - vn0->zv) * clip); + + v->w = 1.0f / near_z; + v->s = M_GetUV(vn0->u) + (M_GetUV(vn1->u) - M_GetUV(vn0->u)) * clip; + v->t = M_GetUV(vn0->v) + (M_GetUV(vn1->v) - M_GetUV(vn0->v)) * clip; + v->tex_coord[2] = vn0->tex_coord[2] + + (vn1->tex_coord[2] - vn0->tex_coord[2]) * clip; + v->tex_coord[3] = vn0->tex_coord[3] + + (vn1->tex_coord[3] - vn0->tex_coord[3]) * clip; + + v->r = v->g = v->b = + (8192.0f - (vn0->g + (vn1->g - vn0->g) * clip)) * multiplier; + Output_ApplyTint(&v->r, &v->g, &v->b); + + v++; + } + + if (diff0 < 0) { + v->x = vn0->xs; + v->y = vn0->ys; + v->z = MAP_DEPTH(vn0->zv); + + v->w = 1.0f / vn0->zv; + v->s = M_GetUV(vn0->u); + v->t = M_GetUV(vn0->v); + v->tex_coord[2] = vn0->tex_coord[2]; + v->tex_coord[3] = vn0->tex_coord[3]; + + v->r = v->g = v->b = (8192.0f - vn0->g) * multiplier; + Output_ApplyTint(&v->r, &v->g, &v->b); + + v++; + } + + loop_end: + prev = current++; + } + + const int32_t count = v - vertices; + return count < 3 ? 0 : count; +} + +void S_Output_EnableTextureMode(void) +{ + if (m_IsTextureMode) { + return; + } + + m_IsTextureMode = true; + GFX_3D_Renderer_SetTexturingEnabled(m_Renderer3D, m_IsTextureMode); +} + +void S_Output_DisableTextureMode(void) +{ + if (!m_IsTextureMode) { + return; + } + + m_IsTextureMode = false; + GFX_3D_Renderer_SetTexturingEnabled(m_Renderer3D, m_IsTextureMode); +} + +void S_Output_SetBlendingMode(const GFX_BLEND_MODE blend_mode) +{ + GFX_3D_Renderer_SetBlendingMode(m_Renderer3D, blend_mode); +} + +void S_Output_EnableDepthWrites(void) +{ + GFX_3D_Renderer_SetDepthWritesEnabled(m_Renderer3D, true); +} + +void S_Output_DisableDepthWrites(void) +{ + GFX_3D_Renderer_SetDepthWritesEnabled(m_Renderer3D, false); +} + +void S_Output_EnableDepthTest(void) +{ + GFX_3D_Renderer_SetDepthTestEnabled(m_Renderer3D, true); +} + +void S_Output_DisableDepthTest(void) +{ + GFX_3D_Renderer_SetDepthTestEnabled(m_Renderer3D, false); +} + +void S_Output_RenderBegin(void) +{ + GFX_Context_Clear(); + S_Output_DrawBackdropSurface(); + GFX_3D_Renderer_RenderBegin(m_Renderer3D); + GFX_3D_Renderer_SetTextureFilter( + m_Renderer3D, g_Config.rendering.texture_filter); + GFX_3D_Renderer_SetBlendingMode(m_Renderer3D, GFX_BLEND_MODE_OFF); +} + +void S_Output_RenderEnd(void) +{ + GFX_3D_Renderer_RenderEnd(m_Renderer3D); +} + +void S_Output_Flush(void) +{ + GFX_3D_Renderer_Flush(m_Renderer3D); +} + +void S_Output_FlipScreen(void) +{ + M_FlipPrimaryBuffer(); + m_SelectedTexture = -1; +} + +void S_Output_ClearDepthBuffer(void) +{ + GFX_3D_Renderer_ClearDepth(m_Renderer3D); +} + +void S_Output_DrawBackdropSurface(void) +{ + if (m_PictureSurface == nullptr) { + return; + } + GFX_2D_Renderer_Render(m_Renderer2D); +} + +void S_Output_DownloadBackdropSurface(const IMAGE *const image) +{ + GFX_2D_Surface_Free(m_PictureSurface); + m_PictureSurface = nullptr; + + if (image == nullptr) { + return; + } + + m_PictureSurface = GFX_2D_Surface_CreateFromImage(image); + GFX_2D_Renderer_Upload( + m_Renderer2D, &m_PictureSurface->desc, m_PictureSurface->buffer); +} + +void S_Output_SelectTexture(const int32_t texture_num) +{ + if (texture_num == m_SelectedTexture) { + return; + } + + if (m_TextureMap[texture_num] == GFX_NO_TEXTURE) { + LOG_ERROR("ERROR: Attempt to select unloaded texture"); + return; + } + + GFX_3D_Renderer_SelectTexture(m_Renderer3D, m_TextureMap[texture_num]); + + m_SelectedTexture = texture_num; +} + +void S_Output_DrawSprite( + int16_t x1, int16_t y1, int16_t x2, int y2, int z, int sprnum, int shade) +{ + int vertex_count = 4; + GFX_3D_VERTEX vertices[vertex_count * CLIP_VERTCOUNT_SCALE]; + + float multiplier = g_Config.visuals.brightness / 16.0f; + + const SPRITE_TEXTURE *const sprite = Output_GetSpriteTexture(sprnum); + float vshade = (8192.0f - shade) * multiplier; + if (vshade >= 256.0f) { + vshade = 255.0f; + } + + const float u0 = (sprite->offset & 0xFF) / 256.0f; + const float v0 = (sprite->offset >> 8) / 256.0f; + const float u1 = (sprite->width >> 8) / 256.0f + u0; + const float v1 = (sprite->height >> 8) / 256.0f + v0; + const float vz = MAP_DEPTH(z); + const float rhw = 1.0f / z; + + vertices[0].x = x1; + vertices[0].y = y1; + vertices[0].z = vz; + vertices[0].s = u0; + vertices[0].t = v0; + vertices[0].tex_coord[2] = 1.0; + vertices[0].tex_coord[3] = 1.0; + vertices[0].w = rhw; + vertices[0].r = vshade; + vertices[0].g = vshade; + vertices[0].b = vshade; + + vertices[1].x = x2; + vertices[1].y = y1; + vertices[1].z = vz; + vertices[1].s = u1; + vertices[1].t = v0; + vertices[1].tex_coord[2] = 1.0; + vertices[1].tex_coord[3] = 1.0; + vertices[1].w = rhw; + vertices[1].r = vshade; + vertices[1].g = vshade; + vertices[1].b = vshade; + + vertices[2].x = x2; + vertices[2].y = y2; + vertices[2].z = vz; + vertices[2].s = u1; + vertices[2].t = v1; + vertices[2].tex_coord[2] = 1.0; + vertices[2].tex_coord[3] = 1.0; + vertices[2].w = rhw; + vertices[2].r = vshade; + vertices[2].g = vshade; + vertices[2].b = vshade; + + vertices[3].x = x1; + vertices[3].y = y2; + vertices[3].z = vz; + vertices[3].s = u0; + vertices[3].t = v1; + vertices[3].tex_coord[2] = 1.0; + vertices[3].tex_coord[3] = 1.0; + vertices[3].w = rhw; + vertices[3].r = vshade; + vertices[3].g = vshade; + vertices[3].b = vshade; + + if (!vertex_count) { + return; + } + + if (m_TextureMap[sprite->tex_page] != GFX_NO_TEXTURE) { + S_Output_EnableTextureMode(); + S_Output_SelectTexture(sprite->tex_page); + M_DrawTriangleFan(vertices, vertex_count); + } else { + S_Output_DisableTextureMode(); + M_DrawTriangleFan(vertices, vertex_count); + } +} + +void S_Output_Draw3DLine( + const PHD_VBUF *const vn0, const PHD_VBUF *const vn1, const RGBA_8888 color) +{ + int32_t vertex_count = 2; + GFX_3D_VERTEX vertices[vertex_count * CLIP_VERTCOUNT_SCALE]; + const PHD_VBUF *const src_vbuf[2] = { vn0, vn1 }; + + for (int32_t i = 0; i < vertex_count; i++) { + vertices[i].x = src_vbuf[i]->xs; + vertices[i].y = src_vbuf[i]->ys; + vertices[i].z = MAP_DEPTH(src_vbuf[i]->zv); + vertices[i].r = color.r; + vertices[i].g = color.g; + vertices[i].b = color.b; + vertices[i].a = color.a; + } + + if ((vn0->clip | vn1->clip) < 0) { + vertex_count = + M_ZedClipper(vertex_count, (const PHD_VBUF **)src_vbuf, vertices); + if (vertex_count == 0) { + return; + } + for (int32_t i = 0; i < vertex_count; i++) { + vertices[i].r = color.r; + vertices[i].g = color.g; + vertices[i].b = color.b; + vertices[i].a = color.a; + } + } + + if (!vertex_count) { + return; + } + + GFX_3D_Renderer_SetPrimType(m_Renderer3D, GFX_3D_PRIM_LINE); + S_Output_DisableTextureMode(); + GFX_3D_Renderer_SetBlendingMode(m_Renderer3D, GFX_BLEND_MODE_NORMAL); + GFX_3D_Renderer_RenderPrimList(m_Renderer3D, vertices, vertex_count); + GFX_3D_Renderer_SetBlendingMode(m_Renderer3D, GFX_BLEND_MODE_OFF); + GFX_3D_Renderer_SetPrimType(m_Renderer3D, GFX_3D_PRIM_TRI); +} + +void S_Output_Draw2DQuad( + int32_t x1, int32_t y1, int32_t x2, int32_t y2, RGBA_8888 tl, RGBA_8888 tr, + RGBA_8888 bl, RGBA_8888 br) +{ + int vertex_count = 4; + GFX_3D_VERTEX vertices[vertex_count]; + + vertices[0].x = x1; + vertices[0].y = y1; + vertices[0].z = 1.0f; + vertices[0].r = tl.r; + vertices[0].g = tl.g; + vertices[0].b = tl.b; + vertices[0].a = tl.a; + + vertices[1].x = x2; + vertices[1].y = y1; + vertices[1].z = 1.0f; + vertices[1].r = tr.r; + vertices[1].g = tr.g; + vertices[1].b = tr.b; + vertices[1].a = tr.a; + + vertices[2].x = x2; + vertices[2].y = y2; + vertices[2].z = 1.0f; + vertices[2].r = br.r; + vertices[2].g = br.g; + vertices[2].b = br.b; + vertices[2].a = br.a; + + vertices[3].x = x1; + vertices[3].y = y2; + vertices[3].z = 1.0f; + vertices[3].r = bl.r; + vertices[3].g = bl.g; + vertices[3].b = bl.b; + vertices[3].a = bl.a; + + S_Output_DisableTextureMode(); + GFX_3D_Renderer_SetBlendingMode(m_Renderer3D, GFX_BLEND_MODE_NORMAL); + M_DrawTriangleFan(vertices, vertex_count); + GFX_3D_Renderer_SetBlendingMode(m_Renderer3D, GFX_BLEND_MODE_OFF); +} + +void S_Output_DrawLightningSegment( + int x1, int y1, int z1, int thickness1, int x2, int y2, int z2, + int thickness2) +{ + int vertex_count = 4; + GFX_3D_VERTEX vertices[vertex_count * CLIP_VERTCOUNT_SCALE]; + + S_Output_DisableTextureMode(); + + GFX_3D_Renderer_SetBlendingMode(m_Renderer3D, GFX_BLEND_MODE_NORMAL); + vertices[0].x = x1; + vertices[0].y = y1; + vertices[0].z = MAP_DEPTH(z1); + vertices[0].g = 0.0f; + vertices[0].r = 0.0f; + vertices[0].b = 255.0f; + vertices[0].a = 128.0f; + + vertices[1].x = thickness1 / 2 + x1; + vertices[1].y = vertices[0].y; + vertices[1].z = vertices[0].z; + vertices[1].b = 255.0f; + vertices[1].g = 255.0f; + vertices[1].r = 255.0f; + vertices[1].a = 128.0f; + + vertices[2].x = thickness2 / 2 + x2; + vertices[2].y = y2; + vertices[2].z = MAP_DEPTH(z2); + vertices[2].b = 255.0f; + vertices[2].g = 255.0f; + vertices[2].r = 255.0f; + vertices[2].a = 128.0f; + + vertices[3].x = x2; + vertices[3].y = vertices[2].y; + vertices[3].z = vertices[2].z; + vertices[3].g = 0.0f; + vertices[3].r = 0.0f; + vertices[3].b = 255.0f; + vertices[3].a = 128.0f; + + M_DrawTriangleFan(vertices, vertex_count); + + vertex_count = 4; + vertices[0].x = thickness1 / 2 + x1; + vertices[0].y = y1; + vertices[0].z = MAP_DEPTH(z1); + vertices[0].b = 255.0f; + vertices[0].g = 255.0f; + vertices[0].r = 255.0f; + vertices[0].a = 128.0f; + + vertices[1].x = thickness1 + x1; + vertices[1].y = vertices[0].y; + vertices[1].z = vertices[0].z; + vertices[1].g = 0.0f; + vertices[1].r = 0.0f; + vertices[1].b = 255.0f; + vertices[1].a = 128.0f; + + vertices[2].x = (thickness2 + x2); + vertices[2].y = y2; + vertices[2].z = MAP_DEPTH(z2); + vertices[2].g = 0.0f; + vertices[2].r = 0.0f; + vertices[2].b = 255.0f; + vertices[2].a = 128.0f; + + vertices[3].x = (thickness2 / 2 + x2); + vertices[3].y = vertices[2].y; + vertices[3].z = vertices[2].z; + vertices[3].b = 255.0f; + vertices[3].g = 255.0f; + vertices[3].r = 255.0f; + vertices[3].a = 128.0f; + + M_DrawTriangleFan(vertices, vertex_count); + GFX_3D_Renderer_SetBlendingMode(m_Renderer3D, GFX_BLEND_MODE_OFF); +} + +void S_Output_DrawShadow(PHD_VBUF *vbufs, int clip, int vertex_count) +{ + // needs to be more than 8 cause clipping might return more polygons. + GFX_3D_VERTEX vertices[vertex_count * CLIP_VERTCOUNT_SCALE]; + + for (int32_t i = 0; i < vertex_count; i++) { + GFX_3D_VERTEX *vertex = &vertices[i]; + PHD_VBUF *vbuf = &vbufs[i]; + vertex->x = vbuf->xs; + vertex->y = vbuf->ys; + vertex->z = MAP_DEPTH(vbuf->zv - (12 << W2V_SHIFT)); + vertex->b = 0.0f; + vertex->g = 0.0f; + vertex->r = 0.0f; + vertex->a = 128.0f; + } + + if (vertex_count == 0) { + return; + } + + S_Output_DisableTextureMode(); + + GFX_3D_Renderer_SetBlendingMode(m_Renderer3D, GFX_BLEND_MODE_NORMAL); + M_DrawTriangleFan(vertices, vertex_count); + GFX_3D_Renderer_SetBlendingMode(m_Renderer3D, GFX_BLEND_MODE_OFF); +} + +void S_Output_ApplyRenderSettings(void) +{ + if (m_Renderer3D == nullptr) { + return; + } + + if (m_PictureSurface != nullptr + && (Screen_GetResWidth() != m_SurfaceWidth + || Screen_GetResHeight() != m_SurfaceHeight)) { + GFX_2D_Surface_Free(m_PictureSurface); + m_PictureSurface = nullptr; + } + + m_SurfaceWidth = Screen_GetResWidth(); + m_SurfaceHeight = Screen_GetResHeight(); + m_SurfaceMinX = 0.0f; + m_SurfaceMinY = 0.0f; + m_SurfaceMaxX = Screen_GetResWidth() - 1.0f; + m_SurfaceMaxY = Screen_GetResHeight() - 1.0f; + + GFX_Context_SetVSync(g_Config.rendering.enable_vsync); + GFX_Context_SetDisplayFilter(g_Config.rendering.fbo_filter); + GFX_Context_SetDisplaySize(m_SurfaceWidth, m_SurfaceHeight); + GFX_Context_SetRenderingMode(g_Config.rendering.render_mode); + GFX_Context_SetWireframeMode(g_Config.rendering.enable_wireframe); + GFX_Context_SetLineWidth(g_Config.rendering.wireframe_width); + GFX_3D_Renderer_SetAnisotropyFilter( + m_Renderer3D, g_Config.rendering.anisotropy_filter); + + if (m_PrimarySurface == nullptr) { + GFX_2D_SURFACE_DESC surface_desc = {}; + m_PrimarySurface = GFX_2D_Surface_Create(&surface_desc); + } + M_ClearSurface(m_PrimarySurface); +} + +void S_Output_SetWindowSize(int width, int height) +{ + GFX_Context_SetWindowSize(width, height); +} + +bool S_Output_Init(void) +{ + for (int i = 0; i < GFX_MAX_TEXTURES; i++) { + m_TextureMap[i] = GFX_NO_TEXTURE; + m_TextureSurfaces[i] = nullptr; + } + + m_Renderer2D = GFX_2D_Renderer_Create(); + m_Renderer3D = GFX_3D_Renderer_Create(); + + S_Output_ApplyRenderSettings(); + GFX_3D_Renderer_SetPrimType(m_Renderer3D, GFX_3D_PRIM_TRI); + GFX_3D_Renderer_SetAlphaThreshold(m_Renderer3D, 0.0); + GFX_3D_Renderer_SetAlphaPointDiscard(m_Renderer3D, true); + + return true; +} + +void S_Output_Shutdown(void) +{ + M_ReleaseTextures(); + M_ReleaseSurfaces(); + + if (m_Renderer2D != nullptr) { + GFX_2D_Renderer_Destroy(m_Renderer2D); + m_Renderer2D = nullptr; + } + if (m_Renderer3D != nullptr) { + GFX_3D_Renderer_Destroy(m_Renderer3D); + m_Renderer3D = nullptr; + } + GFX_Context_Detach(); +} + +void S_Output_DrawFlatTriangle( + PHD_VBUF *vn1, PHD_VBUF *vn2, PHD_VBUF *vn3, RGBA_8888 color) +{ + int vertex_count = 3; + GFX_3D_VERTEX vertices[vertex_count * CLIP_VERTCOUNT_SCALE]; + PHD_VBUF *src_vbuf[3]; + + src_vbuf[0] = vn1; + src_vbuf[1] = vn2; + src_vbuf[2] = vn3; + + if (vn3->clip & vn2->clip & vn1->clip) { + return; + } + + float multiplier = g_Config.visuals.brightness / (16.0f * 255.0f); + for (int32_t i = 0; i < vertex_count; i++) { + vertices[i].x = src_vbuf[i]->xs; + vertices[i].y = src_vbuf[i]->ys; + vertices[i].z = MAP_DEPTH(src_vbuf[i]->zv); + const float light = (8192.0f - src_vbuf[i]->g) * multiplier; + vertices[i].r = color.r * light; + vertices[i].g = color.g * light; + vertices[i].b = color.b * light; + vertices[i].a = color.a; + + Output_ApplyTint(&vertices[i].r, &vertices[i].g, &vertices[i].b); + } + + if ((vn1->clip | vn2->clip | vn3->clip) >= 0) { + if (!VBUF_VISIBLE(*vn1, *vn2, *vn3)) { + return; + } + } else { + if (!M_VisibleZClip(vn1, vn2, vn3)) { + return; + } + + vertex_count = + M_ZedClipper(vertex_count, (const PHD_VBUF **)src_vbuf, vertices); + if (vertex_count == 0) { + return; + } + for (int32_t i = 0; i < vertex_count; i++) { + vertices[i].r *= color.r / 255.0f; + vertices[i].g *= color.g / 255.0f; + vertices[i].b *= color.b / 255.0f; + vertices[i].a = color.a; + } + } + + if (!vertex_count) { + return; + } + + M_DrawTriangleFan(vertices, vertex_count); +} + +void S_Output_DrawEnvMapTriangle( + const PHD_VBUF *const vn1, const PHD_VBUF *const vn2, + const PHD_VBUF *const vn3) +{ + int vertex_count = 3; + GFX_3D_VERTEX vertices[vertex_count * CLIP_VERTCOUNT_SCALE]; + + const float multiplier = g_Config.visuals.brightness / 16.0f; + const PHD_VBUF *const src_vbuf[3] = { vn1, vn2, vn3 }; + + if (vn3->clip & vn2->clip & vn1->clip) { + return; + } + + if (vn1->clip >= 0 && vn2->clip >= 0 && vn3->clip >= 0) { + if (!VBUF_VISIBLE(*vn1, *vn2, *vn3)) { + return; + } + + for (int32_t i = 0; i < vertex_count; i++) { + vertices[i].x = src_vbuf[i]->xs; + vertices[i].y = src_vbuf[i]->ys; + vertices[i].z = MAP_DEPTH(src_vbuf[i]->zv); + + vertices[i].w = 1.0f / src_vbuf[i]->zv; + vertices[i].s = M_GetUV(src_vbuf[i]->u); + vertices[i].t = M_GetUV(src_vbuf[i]->v); + vertices[i].tex_coord[2] = src_vbuf[i]->tex_coord[2]; + vertices[i].tex_coord[3] = src_vbuf[i]->tex_coord[3]; + + vertices[i].r = vertices[i].g = vertices[i].b = + (8192.0f - src_vbuf[i]->g) * multiplier; + + Output_ApplyTint(&vertices[i].r, &vertices[i].g, &vertices[i].b); + } + } else { + if (!M_VisibleZClip(vn1, vn2, vn3)) { + return; + } + + vertex_count = + M_ZedClipper(vertex_count, (const PHD_VBUF **)src_vbuf, vertices); + if (vertex_count == 0) { + return; + } + } + + if (!vertex_count) { + return; + } + + S_Output_EnableTextureMode(); + GFX_3D_Renderer_SelectTexture(m_Renderer3D, m_EnvMapTexture); + GFX_3D_Renderer_SetBlendingMode(m_Renderer3D, GFX_BLEND_MODE_MULTIPLY); + M_DrawTriangleFan(vertices, vertex_count); + GFX_3D_Renderer_SetBlendingMode(m_Renderer3D, GFX_BLEND_MODE_OFF); + m_SelectedTexture = -1; +} + +void S_Output_DrawEnvMapQuad( + const PHD_VBUF *const vn1, const PHD_VBUF *const vn2, + const PHD_VBUF *const vn3, const PHD_VBUF *const vn4) +{ + int vertex_count = 4; + GFX_3D_VERTEX vertices[vertex_count]; + + if (vn4->clip | vn3->clip | vn2->clip | vn1->clip) { + if ((vn4->clip & vn3->clip & vn2->clip & vn1->clip)) { + return; + } + + if (vn1->clip >= 0 && vn2->clip >= 0 && vn3->clip >= 0 + && vn4->clip >= 0) { + if (!VBUF_VISIBLE(*vn1, *vn2, *vn3)) { + return; + } + } else if (!M_VisibleZClip(vn1, vn2, vn3)) { + return; + } + + S_Output_DrawEnvMapTriangle(vn1, vn2, vn3); + S_Output_DrawEnvMapTriangle(vn3, vn4, vn1); + return; + } + + if (!VBUF_VISIBLE(*vn1, *vn2, *vn3)) { + return; + } + + float multiplier = g_Config.visuals.brightness / 16.0f; + + const PHD_VBUF *const src_vbuf[4] = { vn2, vn1, vn3, vn4 }; + + for (int32_t i = 0; i < vertex_count; i++) { + vertices[i].x = src_vbuf[i]->xs; + vertices[i].y = src_vbuf[i]->ys; + vertices[i].z = MAP_DEPTH(src_vbuf[i]->zv); + + vertices[i].w = 1.0f / src_vbuf[i]->zv; + vertices[i].s = M_GetUV(src_vbuf[i]->u); + vertices[i].t = M_GetUV(src_vbuf[i]->v); + vertices[i].tex_coord[2] = src_vbuf[i]->tex_coord[2]; + vertices[i].tex_coord[3] = src_vbuf[i]->tex_coord[3]; + + vertices[i].r = vertices[i].g = vertices[i].b = + (8192.0f - src_vbuf[i]->g) * multiplier; + + Output_ApplyTint(&vertices[i].r, &vertices[i].g, &vertices[i].b); + } + + S_Output_EnableTextureMode(); + GFX_3D_Renderer_SelectTexture(m_Renderer3D, m_EnvMapTexture); + GFX_3D_Renderer_SetBlendingMode(m_Renderer3D, GFX_BLEND_MODE_MULTIPLY); + GFX_3D_Renderer_RenderPrimStrip(m_Renderer3D, vertices, vertex_count); + GFX_3D_Renderer_SetBlendingMode(m_Renderer3D, GFX_BLEND_MODE_OFF); + m_SelectedTexture = -1; +} + +void S_Output_DrawTexturedTriangle( + PHD_VBUF *vn1, PHD_VBUF *vn2, PHD_VBUF *vn3, int16_t tpage, + uint16_t textype) +{ + int vertex_count = 3; + GFX_3D_VERTEX vertices[vertex_count * CLIP_VERTCOUNT_SCALE]; + PHD_VBUF *src_vbuf[3]; + + float multiplier = g_Config.visuals.brightness / 16.0f; + + src_vbuf[0] = vn1; + src_vbuf[1] = vn2; + src_vbuf[2] = vn3; + + if (vn3->clip & vn2->clip & vn1->clip) { + return; + } + + if (src_vbuf[0]->clip >= 0 && src_vbuf[1]->clip >= 0 + && src_vbuf[2]->clip >= 0) { + if (!VBUF_VISIBLE(*src_vbuf[0], *src_vbuf[1], *src_vbuf[2])) { + return; + } + + for (int32_t i = 0; i < vertex_count; i++) { + vertices[i].x = src_vbuf[i]->xs; + vertices[i].y = src_vbuf[i]->ys; + vertices[i].z = MAP_DEPTH(src_vbuf[i]->zv); + + vertices[i].w = 1.0f / src_vbuf[i]->zv; + vertices[i].s = M_GetUV(src_vbuf[i]->u); + vertices[i].t = M_GetUV(src_vbuf[i]->v); + vertices[i].tex_coord[2] = src_vbuf[i]->tex_coord[2]; + vertices[i].tex_coord[3] = src_vbuf[i]->tex_coord[3]; + + vertices[i].r = vertices[i].g = vertices[i].b = + (8192.0f - src_vbuf[i]->g) * multiplier; + + Output_ApplyTint(&vertices[i].r, &vertices[i].g, &vertices[i].b); + } + } else { + if (!M_VisibleZClip(src_vbuf[0], src_vbuf[1], src_vbuf[2])) { + return; + } + + vertex_count = + M_ZedClipper(vertex_count, (const PHD_VBUF **)src_vbuf, vertices); + if (vertex_count == 0) { + return; + } + } + + if (!vertex_count) { + return; + } + + if (m_TextureMap[tpage] != GFX_NO_TEXTURE) { + S_Output_EnableTextureMode(); + S_Output_SelectTexture(tpage); + M_DrawTriangleFan(vertices, vertex_count); + } else { + S_Output_DisableTextureMode(); + M_DrawTriangleFan(vertices, vertex_count); + } +} + +void S_Output_DrawTexturedQuad( + PHD_VBUF *vn1, PHD_VBUF *vn2, PHD_VBUF *vn3, PHD_VBUF *vn4, int16_t tpage, + uint16_t textype) +{ + int vertex_count = 4; + GFX_3D_VERTEX vertices[vertex_count]; + PHD_VBUF *src_vbuf[4] = { vn1, vn2, vn3, vn4 }; + + if (src_vbuf[3]->clip | src_vbuf[2]->clip | src_vbuf[1]->clip + | src_vbuf[0]->clip) { + if ((src_vbuf[3]->clip & src_vbuf[2]->clip & src_vbuf[1]->clip + & src_vbuf[0]->clip)) { + return; + } + + if (src_vbuf[0]->clip >= 0 && src_vbuf[1]->clip >= 0 + && src_vbuf[2]->clip >= 0 && src_vbuf[3]->clip >= 0) { + if (!VBUF_VISIBLE(*src_vbuf[0], *src_vbuf[1], *src_vbuf[2])) { + return; + } + } else if (!M_VisibleZClip(src_vbuf[0], src_vbuf[1], src_vbuf[2])) { + return; + } + + S_Output_DrawTexturedTriangle(vn1, vn2, vn3, tpage, textype); + S_Output_DrawTexturedTriangle(vn3, vn4, vn1, tpage, textype); + return; + } + + if (!VBUF_VISIBLE(*src_vbuf[0], *src_vbuf[1], *src_vbuf[2])) { + return; + } + + float multiplier = g_Config.visuals.brightness / 16.0f; + + for (int32_t i = 0; i < vertex_count; i++) { + vertices[i].x = src_vbuf[i]->xs; + vertices[i].y = src_vbuf[i]->ys; + vertices[i].z = MAP_DEPTH(src_vbuf[i]->zv); + + vertices[i].w = 1.0f / src_vbuf[i]->zv; + vertices[i].s = M_GetUV(src_vbuf[i]->u); + vertices[i].t = M_GetUV(src_vbuf[i]->v); + vertices[i].tex_coord[2] = src_vbuf[i]->tex_coord[2]; + vertices[i].tex_coord[3] = src_vbuf[i]->tex_coord[3]; + + vertices[i].r = vertices[i].g = vertices[i].b = + (8192.0f - src_vbuf[i]->g) * multiplier; + + Output_ApplyTint(&vertices[i].r, &vertices[i].g, &vertices[i].b); + } + + if (m_TextureMap[tpage] != GFX_NO_TEXTURE) { + S_Output_EnableTextureMode(); + S_Output_SelectTexture(tpage); + } else { + S_Output_DisableTextureMode(); + } + + GFX_3D_Renderer_RenderPrimFan(m_Renderer3D, vertices, vertex_count); +} + +void S_Output_DownloadTextures(int32_t pages) +{ + if (pages > GFX_MAX_TEXTURES) { + Shell_ExitSystem("Attempt to download more than texture page limit"); + } + + M_ReleaseTextures(); + + for (int32_t i = 0; i < pages; i++) { + if (m_TextureSurfaces[i] == nullptr) { + const GFX_2D_SURFACE_DESC surface_desc = { + .width = TEXTURE_PAGE_WIDTH, + .height = TEXTURE_PAGE_HEIGHT, + }; + m_TextureSurfaces[i] = GFX_2D_Surface_Create(&surface_desc); + } + GFX_2D_SURFACE *const surface = m_TextureSurfaces[i]; + RGBA_8888 *const output_ptr = (RGBA_8888 *)surface->buffer; + const RGBA_8888 *const input_ptr = Output_GetTexturePage32(i); + memcpy( + output_ptr, input_ptr, + surface->desc.width * surface->desc.height * sizeof(RGBA_8888)); + + m_TextureMap[i] = GFX_3D_Renderer_RegisterTexturePage( + m_Renderer3D, output_ptr, surface->desc.width, + surface->desc.height); + } + + m_SelectedTexture = -1; + + m_EnvMapTexture = GFX_3D_Renderer_RegisterEnvironmentMap(m_Renderer3D); +} + +void S_Output_ScreenBox( + int32_t sx, int32_t sy, int32_t w, int32_t h, RGBA_8888 col_dark, + RGBA_8888 col_light, float thickness) +{ + // this draws the dark then light two tone border + // 2 is the sx+1,sy+1 + // 7 6 + // + // 2 | 4 + // + // 3 | 5 + // + // ~ ~ ~ ~ + // + // 0 1 + // 11 | 9 + // + // 10 | 8 + // this is the vertex structure for the dark part + // while any machine we would be rendering this on will + // handle degenerate triangles, the current render doesn't + // actually reander a strip and instead pulls out each + // triangle into a triangle buffer. So the 4-5-6 triangle is valid + // but will be over drawn by the light box so is not seen. + // thus we form triangles + // 0,1,2 + // 1,2,3 + // 2,3,4 + // 3,4,5 + // 4,5,6 + // 5,6,7 + // 6,7,8 + // 7,8,9 + // 8,9,10 + // 9,10,11 + // the light box is then just a simple 4 sided box + // we share some vertexs so the square is formed as follows + // 13 | 7 + // + // 2 | 4 + // + // ~ ~ ~ ~ + // + // + // 12 | 14 + // + // 11 | 9 + // however as we are not index we have to duplicate the vertex data + // so the above numbers do not match the array below. + // we from the triangles + // 11,12,13 + // 12,13,2 + // 13,2,7 + // 2,7,4 + // 7,4,9 + // 4,9,14 + // 9,14,11 + // 14,11,12 + +#define SB_NUM_VERTS_DARK 12 +#define SB_NUM_VERTS_LIGHT 10 + GFX_3D_VERTEX screen_box_verticies[SB_NUM_VERTS_DARK + SB_NUM_VERTS_LIGHT]; + S_Output_DisableTextureMode(); + + // convert them to floats and apply the (+1) from the original line code + float sxf = (float)sx + thickness; + float syf = (float)sy + thickness; + float hf = (float)h; + float wf = (float)w; + + // Top Left Dark set + screen_box_verticies[0].x = sxf; + screen_box_verticies[0].y = syf + hf - thickness; + + screen_box_verticies[1].x = sxf + thickness; + screen_box_verticies[1].y = screen_box_verticies[0].y; + + screen_box_verticies[2].x = sxf; + screen_box_verticies[2].y = syf; + + screen_box_verticies[3].x = sxf + thickness; + screen_box_verticies[3].y = syf + thickness; + + screen_box_verticies[4].x = sxf + wf - thickness; + screen_box_verticies[4].y = screen_box_verticies[2].y; + + screen_box_verticies[5].x = screen_box_verticies[4].x; + screen_box_verticies[5].y = screen_box_verticies[3].y; + + // Bottom Right Dark set + screen_box_verticies[6].x = sxf + wf + thickness; + screen_box_verticies[6].y = syf - thickness; + + screen_box_verticies[7].x = sxf + wf; + screen_box_verticies[7].y = screen_box_verticies[6].y; + + screen_box_verticies[8].x = screen_box_verticies[6].x; + screen_box_verticies[8].y = syf + hf + thickness; + + screen_box_verticies[9].x = screen_box_verticies[7].x; + screen_box_verticies[9].y = syf + hf; + + screen_box_verticies[10].x = sxf - thickness; + screen_box_verticies[10].y = screen_box_verticies[8].y; + + screen_box_verticies[11].x = screen_box_verticies[10].x; + screen_box_verticies[11].y = screen_box_verticies[9].y; + + // light box + screen_box_verticies[12].x = screen_box_verticies[11].x; + screen_box_verticies[12].y = screen_box_verticies[11].y; + + screen_box_verticies[13].x = screen_box_verticies[12].x + thickness; + screen_box_verticies[13].y = screen_box_verticies[11].y - thickness; + + screen_box_verticies[14].x = sxf - thickness; + screen_box_verticies[14].y = syf - thickness; + + screen_box_verticies[15].x = screen_box_verticies[2].x; + screen_box_verticies[15].y = screen_box_verticies[2].y; + + screen_box_verticies[16].x = screen_box_verticies[7].x; + screen_box_verticies[16].y = screen_box_verticies[7].y; + + screen_box_verticies[17].x = screen_box_verticies[4].x; + screen_box_verticies[17].y = screen_box_verticies[4].y; + + screen_box_verticies[18].x = screen_box_verticies[9].x; + screen_box_verticies[18].y = screen_box_verticies[9].y; + + screen_box_verticies[19].x = screen_box_verticies[9].x - thickness; + screen_box_verticies[19].y = screen_box_verticies[9].y - thickness; + + screen_box_verticies[20].x = screen_box_verticies[12].x; + screen_box_verticies[20].y = screen_box_verticies[12].y; + + screen_box_verticies[21].x = screen_box_verticies[13].x; + screen_box_verticies[21].y = screen_box_verticies[13].y; + + int i = 0; + for (; i < SB_NUM_VERTS_DARK; ++i) { + screen_box_verticies[i].z = 1.0f; // the lines were z 0 but that makes + screen_box_verticies[i].s = 0.0f; // them show over text, so I use Z 1 + screen_box_verticies[i].t = 0.0f; // here to make the line behind text + screen_box_verticies[i].w = 0.0f; // as per dos original + screen_box_verticies[i].r = col_dark.r; + screen_box_verticies[i].g = col_dark.g; + screen_box_verticies[i].b = col_dark.b; + screen_box_verticies[i].a = col_dark.a; + } + for (; i < SB_NUM_VERTS_DARK + SB_NUM_VERTS_LIGHT; ++i) { + screen_box_verticies[i].z = 1.0f; + screen_box_verticies[i].s = 0.0f; + screen_box_verticies[i].t = 0.0f; + screen_box_verticies[i].w = 0.0f; + screen_box_verticies[i].r = col_light.r; + screen_box_verticies[i].g = col_light.g; + screen_box_verticies[i].b = col_light.b; + screen_box_verticies[i].a = col_light.a; + } + + M_DrawTriangleStrip( + screen_box_verticies, SB_NUM_VERTS_DARK + SB_NUM_VERTS_LIGHT); +} + +void S_Output_4ColourTextBox( + int32_t sx, int32_t sy, int32_t w, int32_t h, RGBA_8888 tl, RGBA_8888 tr, + RGBA_8888 bl, RGBA_8888 br, float thickness) +{ + // 0 2 + // * & + // 1 3 + // + // 7 5 + // # @ + // 6 4 + GFX_3D_VERTEX screen_box_verticies[10]; + for (int i = 0; i < 10; ++i) { + screen_box_verticies[i].z = 1.0f; + screen_box_verticies[i].s = 0.0f; + screen_box_verticies[i].t = 0.0f; + screen_box_verticies[i].w = 0.0f; + } + S_Output_DisableTextureMode(); + screen_box_verticies[0].x = sx - thickness; + screen_box_verticies[0].y = sy - thickness; + + screen_box_verticies[1].x = sx + thickness; + screen_box_verticies[1].y = sy + thickness; + + screen_box_verticies[0].r = screen_box_verticies[1].r = tl.r; + screen_box_verticies[0].g = screen_box_verticies[1].g = tl.g; + screen_box_verticies[0].b = screen_box_verticies[1].b = tl.b; + screen_box_verticies[0].a = screen_box_verticies[1].a = tl.a; + + screen_box_verticies[2].x = sx + w + thickness; + screen_box_verticies[2].y = sy - thickness; + + screen_box_verticies[3].x = sx + w - thickness; + screen_box_verticies[3].y = sy + thickness; + + screen_box_verticies[2].r = screen_box_verticies[3].r = tr.r; + screen_box_verticies[2].g = screen_box_verticies[3].g = tr.g; + screen_box_verticies[2].b = screen_box_verticies[3].b = tr.b; + screen_box_verticies[2].a = screen_box_verticies[3].a = tr.a; + + screen_box_verticies[4].x = sx + w + thickness; + screen_box_verticies[4].y = sy + h + thickness; + + screen_box_verticies[5].x = sx + w - thickness; + screen_box_verticies[5].y = sy + h - thickness; + + screen_box_verticies[4].r = screen_box_verticies[5].r = br.r; + screen_box_verticies[4].g = screen_box_verticies[5].g = br.g; + screen_box_verticies[4].b = screen_box_verticies[5].b = br.b; + screen_box_verticies[4].a = screen_box_verticies[5].a = br.a; + + screen_box_verticies[6].x = sx - thickness; + screen_box_verticies[6].y = sy + h + thickness; + + screen_box_verticies[7].x = sx + thickness; + screen_box_verticies[7].y = sy + h - thickness; + + screen_box_verticies[6].r = screen_box_verticies[7].r = bl.r; + screen_box_verticies[6].g = screen_box_verticies[7].g = bl.g; + screen_box_verticies[6].b = screen_box_verticies[7].b = bl.b; + screen_box_verticies[6].a = screen_box_verticies[7].a = bl.a; + + screen_box_verticies[8].x = screen_box_verticies[0].x; + screen_box_verticies[8].y = screen_box_verticies[0].y; + + screen_box_verticies[9].x = screen_box_verticies[1].x; + screen_box_verticies[9].y = screen_box_verticies[1].y; + + screen_box_verticies[8].r = screen_box_verticies[9].r = tl.r; + screen_box_verticies[8].g = screen_box_verticies[9].g = tl.g; + screen_box_verticies[8].b = screen_box_verticies[9].b = tl.b; + screen_box_verticies[8].a = screen_box_verticies[9].a = tl.a; + + M_DrawTriangleStrip(screen_box_verticies, 10); +} + +void S_Output_2ToneColourTextBox( + int32_t sx, int32_t sy, int32_t w, int32_t h, RGBA_8888 edge, + RGBA_8888 centre, float thickness) +{ + // 0 2 4 + // * & + // 1 3 5 + // + // 14 15 7 6 + // + // 13 10 9 + // # @ + // 12 11 8 + + int32_t halfw = w / 2; + int32_t halfh = h / 2; + + GFX_3D_VERTEX screen_box_verticies[18]; + for (int i = 0; i < 18; ++i) { + screen_box_verticies[i].z = 1.0f; + screen_box_verticies[i].s = 0.0f; + screen_box_verticies[i].t = 0.0f; + screen_box_verticies[i].w = 0.0f; + } + S_Output_DisableTextureMode(); + screen_box_verticies[0].x = sx - thickness; + screen_box_verticies[0].y = sy - thickness; + + screen_box_verticies[1].x = sx + thickness; + screen_box_verticies[1].y = sy + thickness; + + screen_box_verticies[0].r = screen_box_verticies[1].r = edge.r; + screen_box_verticies[0].g = screen_box_verticies[1].g = edge.g; + screen_box_verticies[0].b = screen_box_verticies[1].b = edge.b; + screen_box_verticies[0].a = screen_box_verticies[1].a = edge.a; + + screen_box_verticies[2].x = sx + halfw; + screen_box_verticies[2].y = sy - thickness; + + screen_box_verticies[3].x = sx + halfw; + screen_box_verticies[3].y = sy + thickness; + + screen_box_verticies[2].r = screen_box_verticies[3].r = centre.r; + screen_box_verticies[2].g = screen_box_verticies[3].g = centre.g; + screen_box_verticies[2].b = screen_box_verticies[3].b = centre.b; + screen_box_verticies[2].a = screen_box_verticies[3].a = centre.a; + + screen_box_verticies[4].x = sx + w + thickness; + screen_box_verticies[4].y = sy - thickness; + + screen_box_verticies[5].x = sx + w - thickness; + screen_box_verticies[5].y = sy + thickness; + + screen_box_verticies[4].r = screen_box_verticies[5].r = edge.r; + screen_box_verticies[4].g = screen_box_verticies[5].g = edge.g; + screen_box_verticies[4].b = screen_box_verticies[5].b = edge.b; + screen_box_verticies[4].a = screen_box_verticies[5].a = edge.a; + + screen_box_verticies[6].x = sx + w + thickness; + screen_box_verticies[6].y = sy + halfh; + + screen_box_verticies[7].x = sx + w - thickness; + screen_box_verticies[7].y = sy + halfh; + + screen_box_verticies[6].r = screen_box_verticies[7].r = centre.r; + screen_box_verticies[6].g = screen_box_verticies[7].g = centre.g; + screen_box_verticies[6].b = screen_box_verticies[7].b = centre.b; + screen_box_verticies[6].a = screen_box_verticies[7].a = centre.a; + + screen_box_verticies[8].x = sx + w + thickness; + screen_box_verticies[8].y = sy + h + thickness; + + screen_box_verticies[9].x = sx + w - thickness; + screen_box_verticies[9].y = sy + h - thickness; + + screen_box_verticies[8].r = screen_box_verticies[9].r = edge.r; + screen_box_verticies[8].g = screen_box_verticies[9].g = edge.g; + screen_box_verticies[8].b = screen_box_verticies[9].b = edge.b; + screen_box_verticies[8].a = screen_box_verticies[9].a = edge.a; + + screen_box_verticies[10].x = sx + halfw; + screen_box_verticies[10].y = sy + h + thickness; + + screen_box_verticies[11].x = sx + halfw; + screen_box_verticies[11].y = sy + h - thickness; + + screen_box_verticies[10].r = screen_box_verticies[11].r = centre.r; + screen_box_verticies[10].g = screen_box_verticies[11].g = centre.g; + screen_box_verticies[10].b = screen_box_verticies[11].b = centre.b; + screen_box_verticies[10].a = screen_box_verticies[11].a = centre.a; + + screen_box_verticies[12].x = sx - thickness; + screen_box_verticies[12].y = sy + h + thickness; + + screen_box_verticies[13].x = sx + thickness; + screen_box_verticies[13].y = sy + h - thickness; + + screen_box_verticies[12].r = screen_box_verticies[13].r = edge.r; + screen_box_verticies[12].g = screen_box_verticies[13].g = edge.g; + screen_box_verticies[12].b = screen_box_verticies[13].b = edge.b; + screen_box_verticies[12].a = screen_box_verticies[13].a = edge.a; + + screen_box_verticies[14].x = sx - thickness; + screen_box_verticies[14].y = sy + halfh; + + screen_box_verticies[15].x = sx + thickness; + screen_box_verticies[15].y = sy + halfh; + + screen_box_verticies[14].r = screen_box_verticies[15].r = centre.r; + screen_box_verticies[14].g = screen_box_verticies[15].g = centre.g; + screen_box_verticies[14].b = screen_box_verticies[15].b = centre.b; + screen_box_verticies[14].a = screen_box_verticies[15].a = centre.a; + + screen_box_verticies[16].x = screen_box_verticies[0].x; + screen_box_verticies[16].y = screen_box_verticies[0].y; + + screen_box_verticies[17].x = screen_box_verticies[1].x; + screen_box_verticies[17].y = screen_box_verticies[1].y; + + screen_box_verticies[16].r = screen_box_verticies[17].r = edge.r; + screen_box_verticies[16].g = screen_box_verticies[17].g = edge.g; + screen_box_verticies[16].b = screen_box_verticies[17].b = edge.b; + screen_box_verticies[16].a = screen_box_verticies[17].a = edge.a; + + M_DrawTriangleStrip(screen_box_verticies, 18); +} diff --git a/src/tr1/specific/s_output.h b/src/tr1/specific/s_output.h new file mode 100644 index 000000000..bbb9b8dd6 --- /dev/null +++ b/src/tr1/specific/s_output.h @@ -0,0 +1,68 @@ +#pragma once + +#include "global/types.h" + +#include +#include + +#include + +bool S_Output_Init(void); +void S_Output_Shutdown(void); + +void S_Output_EnableTextureMode(void); +void S_Output_DisableTextureMode(void); +void S_Output_SetBlendingMode(GFX_BLEND_MODE blend_mode); +void S_Output_EnableDepthWrites(void); +void S_Output_DisableDepthWrites(void); +void S_Output_EnableDepthTest(void); +void S_Output_DisableDepthTest(void); + +void S_Output_RenderBegin(void); +void S_Output_RenderEnd(void); +void S_Output_Flush(void); +void S_Output_FlipScreen(void); +void S_Output_ClearDepthBuffer(void); + +void S_Output_SetWindowSize(int width, int height); +void S_Output_ApplyRenderSettings(void); + +void S_Output_DownloadTextures(int32_t pages); +void S_Output_SelectTexture(int32_t texture_num); +void S_Output_DownloadBackdropSurface(const IMAGE *image); +void S_Output_DrawBackdropSurface(void); + +void S_Output_DrawFlatTriangle( + PHD_VBUF *vn1, PHD_VBUF *vn2, PHD_VBUF *vn3, RGBA_8888 color); +void S_Output_DrawEnvMapTriangle( + const PHD_VBUF *vn1, const PHD_VBUF *vn2, const PHD_VBUF *vn3); +void S_Output_DrawEnvMapQuad( + const PHD_VBUF *vn1, const PHD_VBUF *vn2, const PHD_VBUF *vn3, + const PHD_VBUF *vn4); +void S_Output_DrawTexturedTriangle( + PHD_VBUF *vn1, PHD_VBUF *vn2, PHD_VBUF *vn3, int16_t tpage, + uint16_t textype); +void S_Output_DrawTexturedQuad( + PHD_VBUF *vn1, PHD_VBUF *vn2, PHD_VBUF *vn3, PHD_VBUF *vn4, int16_t tpage, + uint16_t textype); +void S_Output_Draw3DLine( + const PHD_VBUF *vn1, const PHD_VBUF *vn2, const RGBA_8888 color); +void S_Output_DrawSprite( + int16_t x1, int16_t y1, int16_t x2, int y2, int z, int sprnum, int shade); +void S_Output_Draw2DQuad( + int32_t x1, int32_t y1, int32_t x2, int32_t y2, RGBA_8888 tl, RGBA_8888 tr, + RGBA_8888 bl, RGBA_8888 br); +void S_Output_DrawShadow(PHD_VBUF *vbufs, int clip, int vertex_count); +void S_Output_DrawLightningSegment( + int x1, int y1, int z1, int thickness1, int x2, int y2, int z2, + int thickness2); + +void S_Output_ScreenBox( + int32_t sx, int32_t sy, int32_t w, int32_t h, RGBA_8888 col_dark, + RGBA_8888 col_light, float thickness); +void S_Output_4ColourTextBox( + int32_t sx, int32_t sy, int32_t w, int32_t h, RGBA_8888 tl, RGBA_8888 tr, + RGBA_8888 bl, RGBA_8888 br, float thickness); +void S_Output_2ToneColourTextBox( + int32_t sx, int32_t sy, int32_t w, int32_t h, RGBA_8888 edge, + RGBA_8888 centre, float thickness); diff --git a/src/tr1/specific/s_shell.c b/src/tr1/specific/s_shell.c index 0bf5fd1f2..6557e36bd 100644 --- a/src/tr1/specific/s_shell.c +++ b/src/tr1/specific/s_shell.c @@ -4,7 +4,6 @@ #include "game/fmv.h" #include "game/input.h" #include "game/music.h" -#include "game/option/option_controls.h" #include "game/output.h" #include "game/shell.h" #include "game/sound.h" @@ -12,7 +11,7 @@ #include #include #include -#include +#include #include #include #include @@ -129,6 +128,8 @@ void S_Shell_HandleWindowResize(void) M_SetWindowPos(x, y, false); M_SetWindowSize(width, height, false); + UI_Events_Fire(&(EVENT) { .name = "canvas_resize" }); + // save the updated config, but ensure it was loaded first if (g_Config.loaded) { Config_Write(); @@ -180,7 +181,7 @@ void Shell_ProcessEvents(void) // some keypresses if the player types really fast, so we need to // react sooner. if (!FMV_IsPlaying() && g_Config.gameplay.enable_console - && !Console_IsOpened() && !Option_Controls_IsKeyChangeMode() + && !Console_IsOpened() && Input_IsPressed( INPUT_BACKEND_KEYBOARD, g_Config.input.keyboard_layout, INPUT_ROLE_ENTER_CONSOLE)) { @@ -220,6 +221,12 @@ void Shell_ProcessEvents(void) static void M_SetGLBackend(const GFX_GL_BACKEND backend) { switch (backend) { + case GFX_GL_21: + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 0); + break; + case GFX_GL_33C: SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); @@ -248,6 +255,7 @@ void S_Shell_CreateWindow(void) const GFX_GL_BACKEND backends_to_try[] = { // clang-format off GFX_GL_33C, + GFX_GL_21, GFX_GL_INVALID_BACKEND, // guard // clang-format on }; diff --git a/src/tr2/decomp/decomp.c b/src/tr2/decomp/decomp.c index ef658fcec..2b2a3e2b7 100644 --- a/src/tr2/decomp/decomp.c +++ b/src/tr2/decomp/decomp.c @@ -11,6 +11,7 @@ #include "game/objects/vars.h" #include "game/output.h" #include "game/phase.h" +#include "game/requester.h" #include "game/room.h" #include "game/viewport.h" #include "global/vars.h" @@ -143,13 +144,29 @@ void DecreaseScreenSize(void) } } +void GetValidLevelsList(REQUEST_INFO *const req) +{ + Requester_RemoveAllItems(req); + const GF_LEVEL_TABLE *const level_table = GF_GetLevelTable(GFLT_MAIN); + for (int32_t i = 0; i < level_table->count; i++) { + const GF_LEVEL *const level = &level_table->levels[i]; + if (level->type != GFL_GYM) { + Requester_AddItem(req, level->title, 0, nullptr, 0); + } + } +} + void InitialiseGameFlags(void) { Music_ResetTrackFlags(); + for (GAME_OBJECT_ID obj_id = 0; obj_id < O_NUMBER_OF; obj_id++) { + Object_Get(obj_id)->loaded = 0; + } + Output_SetSunsetTimer(0); g_LevelComplete = false; g_DetonateAllMines = false; - Creature_SetAlliesHostile(false); + g_IsMonkAngry = false; } void GetCarriedItems(void) diff --git a/src/tr2/decomp/decomp.h b/src/tr2/decomp/decomp.h index b5f872622..dad2724a3 100644 --- a/src/tr2/decomp/decomp.h +++ b/src/tr2/decomp/decomp.h @@ -19,6 +19,7 @@ void S_InitialisePolyList(bool clear_back_buffer); void DecreaseScreenSize(void); void IncreaseScreenSize(void); void S_UnloadLevelFile(void); +void GetValidLevelsList(REQUEST_INFO *req); void InitialiseGameFlags(void); void GetCarriedItems(void); int32_t DoShift(ITEM *vehicle, const XYZ_32 *pos, const XYZ_32 *old); diff --git a/src/tr2/decomp/flares.c b/src/tr2/decomp/flares.c index 616f24c70..42cb861c5 100644 --- a/src/tr2/decomp/flares.c +++ b/src/tr2/decomp/flares.c @@ -12,7 +12,6 @@ #include "global/vars.h" #include -#include #include #include #include @@ -83,7 +82,7 @@ int32_t Flare_DoLight(const XYZ_32 *const pos, const int32_t flare_age) const int32_t random = Random_GetDraw(); const XYZ_32 light_pos = { - .x = pos->x + (random & 0xA0), + .x = pos->x + (random & 0xF), .y = pos->y, .z = pos->z, }; @@ -143,22 +142,8 @@ void Flare_DrawInAir(const ITEM *const item) Matrix_TranslateAbs32(item->interp.result.pos); Matrix_Rot16(item->interp.result.rot); const int32_t clip = Output_GetObjectBounds(&frames[0]->bounds); - - const XYZ_32 flare_size = { - .x = frames[0]->bounds.max.x - frames[0]->bounds.min.x, - .y = frames[0]->bounds.max.y - frames[0]->bounds.min.y, - .z = frames[0]->bounds.max.z - frames[0]->bounds.min.z, - }; - const XYZ_32 flare_offset = { - .x = -flare_size.x, - .y = -flare_size.y, - .z = -flare_size.z, - }; - Matrix_TranslateRel32(flare_offset); - if (clip != 0) { Output_CalculateObjectLighting(item, &frames[0]->bounds); - Output_SetDepthBias(-20); Object_DrawMesh(Object_Get(O_FLARE_ITEM)->mesh_idx, clip, false); if (((int32_t)(intptr_t)item->data) & 0x8000) { Matrix_TranslateRel(-6, 6, 80); @@ -167,7 +152,6 @@ void Flare_DrawInAir(const ITEM *const item) Output_CalculateStaticLight(8 * 256); Object_DrawMesh(Object_Get(O_FLARE_FIRE)->mesh_idx, clip, false); } - Output_SetDepthBias(0); } Matrix_Pop(); } @@ -274,7 +258,7 @@ void Flare_Draw(void) frame_num = LF_FL_DRAW; } else if (frame_num == LF_FL_DRAW_GOT_IT) { Flare_DrawMeshes(); - if (!Game_IsBonusFlagSet(GBF_NGPLUS)) { + if (!g_SaveGame.bonus_flag) { Inv_RemoveItem(O_FLARES_ITEM); } } else if (frame_num >= LF_FL_IGNITE && frame_num <= LF_FL_2_HOLD - 2) { diff --git a/src/tr2/game/savegame/savegame_legacy.c b/src/tr2/decomp/savegame.c similarity index 52% rename from src/tr2/game/savegame/savegame_legacy.c rename to src/tr2/decomp/savegame.c index 862c1186e..15919b49b 100644 --- a/src/tr2/game/savegame/savegame_legacy.c +++ b/src/tr2/decomp/savegame.c @@ -1,3 +1,5 @@ +#include "decomp/savegame.h" + #include "game/camera.h" #include "game/game.h" #include "game/game_flow.h" @@ -7,22 +9,21 @@ #include "game/lara/misc.h" #include "game/lot.h" #include "game/objects/general/lift.h" +#include "game/requester.h" #include "game/room.h" -#include "game/savegame.h" #include "game/shell.h" #include "global/const.h" #include "global/vars.h" #include +#include #include -#include +#include #include #include #define SAVE_CREATURE (1 << 7) -#define SAVEGAME_LEGACY_TOTAL_SIZE (1170 + 6272) // header + OG buffer size -#define SAVEGAME_LEGACY_TITLE_SIZE 75 #define SPECIAL_READ_WRITES \ SPECIAL_READ_WRITE(S8, int8_t) \ @@ -32,31 +33,21 @@ SPECIAL_READ_WRITE(U16, uint16_t) \ SPECIAL_READ_WRITE(U32, uint32_t) -#pragma pack(push, 1) -typedef struct { - uint8_t num_pickup[2]; - uint8_t num_puzzle[4]; - uint8_t num_key[4]; - uint16_t reserved; -} SAVEGAME_LEGACY_ITEM_STATS; -#pragma pack(pop) - static int32_t m_BufPos = 0; static char *m_BufPtr = nullptr; +static uint32_t m_ReqFlags1[MAX_REQUESTER_ITEMS]; +static uint32_t m_ReqFlags2[MAX_REQUESTER_ITEMS]; -static bool M_ItemHasSaveFlags(const OBJECT *obj, const ITEM *item); -static bool M_ItemHasSavePosition(const OBJECT *obj, const ITEM *item); - -static void M_Reset(char *buffer); +static void M_Reset(void); static void M_Read(void *ptr, size_t size); #undef SPECIAL_READ_WRITE #define SPECIAL_READ_WRITE(name, type) static type M_Read##name(void); SPECIAL_READ_WRITES static void M_Skip(size_t size); -static void M_ReadResumeInfo(RESUME_INFO *resume); -static void M_ReadResumeInfos(void); -static void M_ReadStats(LEVEL_STATS *const stats); +static void M_ReadStartInfo(MYFILE *fp, START_INFO *start); +static void M_ReadStartInfos(MYFILE *fp); +static void M_ReadStats(MYFILE *fp, LEVEL_STATS *const stats); static void M_ReadItems(void); static void M_ReadLara(LARA_INFO *lara); static void M_ReadLaraArm(LARA_ARM *arm); @@ -67,54 +58,24 @@ static void M_Write(const void *ptr, size_t size); #undef SPECIAL_READ_WRITE #define SPECIAL_READ_WRITE(name, type) static void M_Write##name(type value); SPECIAL_READ_WRITES -static void M_WriteResumeInfo(const RESUME_INFO *resume); -static void M_WriteResumeInfos(void); -static void M_WriteStats(const LEVEL_STATS *stats); +static void M_WriteStartInfo(MYFILE *fp, const START_INFO *start); +static void M_WriteStartInfos(MYFILE *fp); +static void M_WriteStats(MYFILE *fp, const LEVEL_STATS *stats); static void M_WriteItems(void); static void M_WriteLara(const LARA_INFO *lara); static void M_WriteLaraArm(const LARA_ARM *arm); static void M_WriteAmmoInfo(const AMMO_INFO *ammo_info); static void M_WriteFlares(void); -static const char *M_GetSaveFilePattern(void); -static bool M_FillInfo(MYFILE *fp, SAVEGAME_INFO *info); -static void M_SaveToFile(MYFILE *fp, SAVEGAME_INFO *info); -static bool M_LoadFromFile(MYFILE *fp); - -static SAVEGAME_STRATEGY m_Strategy = { - // clang-format off - .allow_load = true, - .allow_save = false, - .format = SAVEGAME_FORMAT_LEGACY, - .get_save_file_pattern_func = M_GetSaveFilePattern, - .fill_info_func = M_FillInfo, - .load_from_file_func = M_LoadFromFile, - .save_to_file_func = M_SaveToFile, - .load_only_resume_info_func = nullptr, - .update_death_counters_func = nullptr, - // clang-format on -}; - -static bool M_ItemHasSaveFlags(const OBJECT *const obj, const ITEM *const item) -{ - return obj->save_flags && item->object_id != O_WATERFALL; -} - -static bool M_ItemHasSavePosition( - const OBJECT *const obj, const ITEM *const item) -{ - return obj->save_position && item->object_id != O_GONDOLA; -} - -static void M_Reset(char *const buffer) +static void M_Reset(void) { m_BufPos = 0; - m_BufPtr = buffer; + m_BufPtr = g_SaveGame.buffer; } static void M_Read(void *const ptr, const size_t size) { - ASSERT(m_BufPos + size <= SAVEGAME_LEGACY_TOTAL_SIZE); + ASSERT(m_BufPos + size <= MAX_SG_BUFFER_SIZE); m_BufPos += size; memcpy(ptr, m_BufPtr, size); m_BufPtr += size; @@ -144,72 +105,74 @@ static void M_Skip(const size_t size) m_BufPtr += size; } -static void M_ReadResumeInfo(RESUME_INFO *const resume) +static void M_ReadStartInfo(MYFILE *const fp, START_INFO *const start) { - resume->pistol_ammo = M_ReadU16(); - resume->magnum_ammo = M_ReadU16(); - resume->uzi_ammo = M_ReadU16(); - resume->shotgun_ammo = M_ReadU16(); - resume->m16_ammo = M_ReadU16(); - resume->grenade_ammo = M_ReadU16(); - resume->harpoon_ammo = M_ReadU16(); - resume->small_medipacks = M_ReadU8(); - resume->large_medipacks = M_ReadU8(); - M_Skip(sizeof(uint8_t)); // legacy reserved value - resume->flares = M_ReadU8(); - resume->gun_status = M_ReadU8(); - resume->equipped_gun_type = M_ReadU8(); + start->pistol_ammo = File_ReadU16(fp); + start->magnum_ammo = File_ReadU16(fp); + start->uzi_ammo = File_ReadU16(fp); + start->shotgun_ammo = File_ReadU16(fp); + start->m16_ammo = File_ReadU16(fp); + start->grenade_ammo = File_ReadU16(fp); + start->harpoon_ammo = File_ReadU16(fp); + start->small_medipacks = File_ReadU8(fp); + start->large_medipacks = File_ReadU8(fp); + start->reserved1 = File_ReadU8(fp); + start->flares = File_ReadU8(fp); + start->gun_status = File_ReadU8(fp); + start->gun_type = File_ReadU8(fp); - const uint16_t flags = M_ReadU16(); + const uint16_t flags = File_ReadU16(fp); // clang-format off - resume->flags.available = (flags & 0x01) ? 1 : 0; - resume->flags.has_pistols = (flags & 0x02) ? 1 : 0; - resume->flags.has_magnums = (flags & 0x04) ? 1 : 0; - resume->flags.has_uzis = (flags & 0x08) ? 1 : 0; - resume->flags.has_shotgun = (flags & 0x10) ? 1 : 0; - resume->flags.has_m16 = (flags & 0x20) ? 1 : 0; - resume->flags.has_grenade = (flags & 0x40) ? 1 : 0; - resume->flags.has_harpoon = (flags & 0x80) ? 1 : 0; + start->available = (flags & 0x01) ? 1 : 0; + start->has_pistols = (flags & 0x02) ? 1 : 0; + start->has_magnums = (flags & 0x04) ? 1 : 0; + start->has_uzis = (flags & 0x08) ? 1 : 0; + start->has_shotgun = (flags & 0x10) ? 1 : 0; + start->has_m16 = (flags & 0x20) ? 1 : 0; + start->has_grenade = (flags & 0x40) ? 1 : 0; + start->has_harpoon = (flags & 0x80) ? 1 : 0; // clang-format on - M_Skip(sizeof(uint16_t)); - M_ReadStats(&resume->stats); + File_ReadU16(fp); + M_ReadStats(fp, &start->stats); } -static void M_ReadResumeInfos(void) +static void M_ReadStartInfos(MYFILE *const fp) { const GF_LEVEL_TABLE *const level_table = GF_GetLevelTable(GFLT_MAIN); for (int32_t i = 0; i < 24; i++) { if (i < level_table->count) { const GF_LEVEL *const level = &level_table->levels[i]; - M_ReadResumeInfo(Savegame_GetCurrentInfo(level)); + M_ReadStartInfo(fp, Savegame_GetCurrentInfo(level)); } else { - RESUME_INFO dummy_resume_info; - M_ReadResumeInfo(&dummy_resume_info); + START_INFO dummy_resume_info; + M_ReadStartInfo(fp, &dummy_resume_info); } } } -static void M_ReadStats(LEVEL_STATS *const stats) +static void M_ReadStats(MYFILE *const fp, LEVEL_STATS *const stats) { - stats->timer = M_ReadU32(); - stats->ammo_used = M_ReadU32(); - stats->ammo_hits = M_ReadU32(); - stats->distance_travelled = M_ReadU32(); - stats->kill_count = M_ReadU16(); - stats->secret_flags = M_ReadU8(); - stats->medipacks_used = M_ReadU8() / 2.0f; + stats->timer = File_ReadU32(fp); + stats->ammo_used = File_ReadU32(fp); + stats->ammo_hits = File_ReadU32(fp); + stats->distance = File_ReadU32(fp); + stats->kills = File_ReadU16(fp); + stats->secret_flags = File_ReadU8(fp); + stats->medipacks = File_ReadU8(fp); } static void M_ReadItems(void) { - Savegame_ProcessItemsBeforeLoad(); - for (int32_t item_num = 0; item_num < Item_GetLevelCount(); item_num++) { ITEM *const item = Item_Get(item_num); const OBJECT *const obj = Object_Get(item->object_id); - if (M_ItemHasSavePosition(obj, item)) { + if (obj->handle_save_func != nullptr) { + obj->handle_save_func(item, SAVEGAME_STAGE_BEFORE_LOAD); + } + + if (obj->save_position) { item->pos.x = M_ReadS32(); item->pos.y = M_ReadS32(); item->pos.z = M_ReadS32(); @@ -223,6 +186,13 @@ static void M_ReadItems(void) if (item->room_num != room_num) { Item_NewRoom(item_num, room_num); } + + if (obj->shadow_size != 0) { + const SECTOR *const sector = Room_GetSector( + item->pos.x, item->pos.y, item->pos.z, &room_num); + item->floor = Room_GetHeight( + sector, item->pos.x, item->pos.y, item->pos.z); + } } if (obj->save_anim) { @@ -237,7 +207,7 @@ static void M_ReadItems(void) item->hit_points = M_ReadS16(); } - if (M_ItemHasSaveFlags(obj, item)) { + if (obj->save_flags) { item->flags = M_ReadU16(); if (obj->intelligent) { @@ -282,6 +252,8 @@ static void M_ReadItems(void) Item_SetPrevActive(item_num); } } + + item->flags &= 0xFF00; } switch (item->object_id) { @@ -302,6 +274,8 @@ static void M_ReadItems(void) obj->handle_save_func(item, SAVEGAME_STAGE_AFTER_LOAD); } } + + MovableBlock_SetupFloor(); } static void M_ReadLara(LARA_INFO *const lara) @@ -422,7 +396,7 @@ static void M_ReadFlares(void) static void M_Write(const void *ptr, const size_t size) { m_BufPos += size; - if (m_BufPos >= SAVEGAME_LEGACY_TOTAL_SIZE) { + if (m_BufPos >= MAX_SG_BUFFER_SIZE) { Shell_ExitSystem("Savegame is too big to fit in buffer"); } @@ -430,73 +404,71 @@ static void M_Write(const void *ptr, const size_t size) m_BufPtr += size; } -static void M_WriteResumeInfo(const RESUME_INFO *const resume) +static void M_WriteStartInfo(MYFILE *const fp, const START_INFO *const start) { - ASSERT(resume != nullptr); - M_WriteU16(resume->pistol_ammo); - M_WriteU16(resume->magnum_ammo); - M_WriteU16(resume->uzi_ammo); - M_WriteU16(resume->shotgun_ammo); - M_WriteU16(resume->m16_ammo); - M_WriteU16(resume->grenade_ammo); - M_WriteU16(resume->harpoon_ammo); - M_WriteU8(resume->small_medipacks); - M_WriteU8(resume->large_medipacks); - M_WriteU8(0); // legacy reserved value - M_WriteU8(resume->flares); - M_WriteU8(resume->gun_status); - M_WriteU8(resume->equipped_gun_type); + ASSERT(start != nullptr); + File_WriteU16(fp, start->pistol_ammo); + File_WriteU16(fp, start->magnum_ammo); + File_WriteU16(fp, start->uzi_ammo); + File_WriteU16(fp, start->shotgun_ammo); + File_WriteU16(fp, start->m16_ammo); + File_WriteU16(fp, start->grenade_ammo); + File_WriteU16(fp, start->harpoon_ammo); + File_WriteU8(fp, start->small_medipacks); + File_WriteU8(fp, start->large_medipacks); + File_WriteU8(fp, start->reserved1); + File_WriteU8(fp, start->flares); + File_WriteU8(fp, start->gun_status); + File_WriteU8(fp, start->gun_type); uint16_t flags = 0; // clang-format off - if (resume->flags.available) { flags |= 0x01; } - if (resume->flags.has_pistols) { flags |= 0x02; } - if (resume->flags.has_magnums) { flags |= 0x04; } - if (resume->flags.has_uzis) { flags |= 0x08; } - if (resume->flags.has_shotgun) { flags |= 0x10; } - if (resume->flags.has_m16) { flags |= 0x20; } - if (resume->flags.has_grenade) { flags |= 0x40; } - if (resume->flags.has_harpoon) { flags |= 0x80; } + if (start->available) { flags |= 0x01; } + if (start->has_pistols) { flags |= 0x02; } + if (start->has_magnums) { flags |= 0x04; } + if (start->has_uzis) { flags |= 0x08; } + if (start->has_shotgun) { flags |= 0x10; } + if (start->has_m16) { flags |= 0x20; } + if (start->has_grenade) { flags |= 0x40; } + if (start->has_harpoon) { flags |= 0x80; } // clang-format on - M_WriteU16(flags); + File_WriteU16(fp, flags); - M_WriteU16(0); - M_WriteStats(&resume->stats); + File_WriteU16(fp, 0); + M_WriteStats(fp, &start->stats); } -static void M_WriteResumeInfos(void) +static void M_WriteStartInfos(MYFILE *const fp) { const GF_LEVEL_TABLE *const level_table = GF_GetLevelTable(GFLT_MAIN); for (int32_t i = 0; i < 24; i++) { if (i < level_table->count) { const GF_LEVEL *const level = &level_table->levels[i]; - M_WriteResumeInfo(Savegame_GetCurrentInfo(level)); + M_WriteStartInfo(fp, Savegame_GetCurrentInfo(level)); } else { - const RESUME_INFO null_resume_info = {}; - M_WriteResumeInfo(&null_resume_info); + const START_INFO null_resume_info = {}; + M_WriteStartInfo(fp, &null_resume_info); } } } -static void M_WriteStats(const LEVEL_STATS *const stats) +static void M_WriteStats(MYFILE *const fp, const LEVEL_STATS *const stats) { - M_WriteU32(stats->timer); - M_WriteU32(stats->ammo_used); - M_WriteU32(stats->ammo_hits); - M_WriteU32(stats->distance_travelled); - M_WriteU16(stats->kill_count); - M_WriteU8(stats->secret_flags & 0xFF); - M_WriteU8(stats->medipacks_used * 2); + File_WriteU32(fp, stats->timer); + File_WriteU32(fp, stats->ammo_used); + File_WriteU32(fp, stats->ammo_hits); + File_WriteU32(fp, stats->distance); + File_WriteU16(fp, stats->kills); + File_WriteU8(fp, stats->secret_flags); + File_WriteU8(fp, stats->medipacks); } static void M_WriteItems(void) { - Savegame_ProcessItemsBeforeSave(); - for (int32_t i = 0; i < Item_GetLevelCount(); i++) { const ITEM *const item = Item_Get(i); const OBJECT *const obj = Object_Get(item->object_id); - if (M_ItemHasSavePosition(obj, item)) { + if (obj->save_position) { M_WriteS32(item->pos.x); M_WriteS32(item->pos.y); M_WriteS32(item->pos.z); @@ -520,7 +492,7 @@ static void M_WriteItems(void) M_WriteS16(item->hit_points); } - if (M_ItemHasSaveFlags(obj, item)) { + if (obj->save_flags) { uint16_t flags = item->flags + item->active + (item->status << 1) + (item->gravity << 3) + (item->collidable << 4); if (obj->intelligent && item->data != nullptr) { @@ -676,92 +648,252 @@ static void M_WriteFlares(void) } } -static const char *M_GetSaveFilePattern(void) +void Savegame_ResetCurrentInfo(const GF_LEVEL *const level) { - return g_GameFlow.savegame_fmt_legacy; + START_INFO *const current = Savegame_GetCurrentInfo(level); + memset(current, 0, sizeof(START_INFO)); } -static bool M_FillInfo(MYFILE *const fp, SAVEGAME_INFO *const savegame_info) +void Savegame_InitCurrentInfo(void) { - char level_title[75]; - File_ReadData(fp, level_title, 75); - savegame_info->level_title = Memory_DupStr(level_title); - savegame_info->counter = File_ReadS32(fp); - - for (int32_t i = 0; i < 24; i++) { - File_Skip(fp, sizeof(uint16_t)); // pistol ammo - File_Skip(fp, sizeof(uint16_t)); // magnum ammo - File_Skip(fp, sizeof(uint16_t)); // uzi ammo - File_Skip(fp, sizeof(uint16_t)); // shotgun ammo - File_Skip(fp, sizeof(uint16_t)); // m16 ammo - File_Skip(fp, sizeof(uint16_t)); // grenade ammo - File_Skip(fp, sizeof(uint16_t)); // harpoon ammo - File_Skip(fp, sizeof(uint8_t)); // small medis - File_Skip(fp, sizeof(uint8_t)); // big medis - File_Skip(fp, sizeof(uint8_t)); // reserved - File_Skip(fp, sizeof(uint8_t)); // flares - File_Skip(fp, sizeof(int8_t)); // gun status - File_Skip(fp, sizeof(int8_t)); // gun type - File_Skip(fp, sizeof(uint16_t)); // flags - File_Skip(fp, sizeof(uint16_t)); // unused - File_Skip(fp, sizeof(uint32_t)); // timer - File_Skip(fp, sizeof(uint32_t)); // ammo used - File_Skip(fp, sizeof(uint32_t)); // hits - File_Skip(fp, sizeof(uint32_t)); // distance - File_Skip(fp, sizeof(uint16_t)); // kills - File_Skip(fp, sizeof(uint8_t)); // secret flags - File_Skip(fp, sizeof(uint8_t)); // medis used + if (g_SaveGame.bonus_flag) { + return; } - File_Skip(fp, sizeof(uint32_t)); // timer - File_Skip(fp, sizeof(uint32_t)); // ammo used - File_Skip(fp, sizeof(uint32_t)); // hits - File_Skip(fp, sizeof(uint32_t)); // distance - File_Skip(fp, sizeof(uint16_t)); // kills - File_Skip(fp, sizeof(uint8_t)); // secret flags - File_Skip(fp, sizeof(uint8_t)); // medis used + const GF_LEVEL_TABLE *const level_table = GF_GetLevelTable(GFLT_MAIN); + for (int32_t i = 0; i < level_table->count; i++) { + const GF_LEVEL *const level = &level_table->levels[i]; + Savegame_ResetCurrentInfo(level); + Savegame_ApplyLogicToCurrentInfo(level); + Savegame_GetCurrentInfo(level)->available = 0; + } - savegame_info->level_num = File_ReadS16(fp); - savegame_info->initial_version = VERSION_LEGACY; - savegame_info->features.restart = false; - savegame_info->features.select_level = false; - - return true; + if (GF_GetGymLevel() != nullptr) { + Savegame_GetCurrentInfo(GF_GetGymLevel())->available = 1; + } + if (GF_GetFirstLevel() != nullptr) { + Savegame_GetCurrentInfo(GF_GetFirstLevel())->available = 1; + } + g_SaveGame.bonus_flag = 0; } -static void M_SaveToFile(MYFILE *const fp, SAVEGAME_INFO *const info) +void Savegame_CarryCurrentInfoToNextLevel( + const GF_LEVEL *const src_level, const GF_LEVEL *const dst_level) { - char *buffer = Memory_Alloc(SAVEGAME_LEGACY_TOTAL_SIZE); - M_Reset(buffer); + LOG_INFO( + "Copying resume info from level #%d to level #%d", src_level->num, + dst_level->num); + START_INFO *const src_resume = Savegame_GetCurrentInfo(src_level); + START_INFO *const dst_resume = Savegame_GetCurrentInfo(dst_level); + memcpy(dst_resume, src_resume, sizeof(START_INFO)); +} - const GF_LEVEL *const current_level = GF_GetCurrentLevel(); - const RESUME_INFO *const current_info = - Savegame_GetCurrentInfo(current_level); +void Savegame_ApplyLogicToCurrentInfo(const GF_LEVEL *const level) +{ + START_INFO *start = Savegame_GetCurrentInfo(level); + if (start == nullptr) { + return; + } - char title[SAVEGAME_LEGACY_TITLE_SIZE]; - snprintf(title, SAVEGAME_LEGACY_TITLE_SIZE, "%s", current_level->title); - M_Write(title, SAVEGAME_LEGACY_TITLE_SIZE); - M_WriteS32(Savegame_GetCounter()); + start->has_pistols = 1; + start->gun_type = LGT_PISTOLS; + start->pistol_ammo = 1000; - M_WriteResumeInfos(); - M_WriteStats(¤t_info->stats); - M_WriteS16(current_level->num); - M_WriteU8(Game_IsBonusFlagSet(GBF_NGPLUS) ? 1 : 0); + if (level == GF_GetGymLevel()) { + start->available = 1; - const SAVEGAME_LEGACY_ITEM_STATS item_stats = { - .num_pickup[0] = Inv_RequestItem(O_PICKUP_ITEM_1), - .num_pickup[1] = Inv_RequestItem(O_PICKUP_ITEM_2), - .num_puzzle[0] = Inv_RequestItem(O_PUZZLE_ITEM_1), - .num_puzzle[1] = Inv_RequestItem(O_PUZZLE_ITEM_2), - .num_puzzle[2] = Inv_RequestItem(O_PUZZLE_ITEM_3), - .num_puzzle[3] = Inv_RequestItem(O_PUZZLE_ITEM_4), - .num_key[0] = Inv_RequestItem(O_KEY_ITEM_1), - .num_key[1] = Inv_RequestItem(O_KEY_ITEM_2), - .num_key[2] = Inv_RequestItem(O_KEY_ITEM_3), - .num_key[3] = Inv_RequestItem(O_KEY_ITEM_4), - .reserved = 0, - }; - M_Write(&item_stats, sizeof(SAVEGAME_LEGACY_ITEM_STATS)); + start->has_pistols = 0; + start->has_shotgun = 0; + start->has_magnums = 0; + start->has_uzis = 0; + start->has_harpoon = 0; + start->has_m16 = 0; + start->has_grenade = 0; + + start->pistol_ammo = 0; + start->shotgun_ammo = 0; + start->magnum_ammo = 0; + start->uzi_ammo = 0; + start->harpoon_ammo = 0; + start->m16_ammo = 0; + start->grenade_ammo = 0; + + start->flares = 0; + start->large_medipacks = 0; + start->small_medipacks = 0; + start->gun_type = LGT_UNARMED; + start->gun_status = LGS_ARMLESS; + } else if (level == GF_GetFirstLevel()) { + start->available = 1; + + start->has_pistols = 1; + start->has_shotgun = 1; + start->has_magnums = 0; + start->has_uzis = 0; + start->has_harpoon = 0; + start->has_m16 = 0; + start->has_grenade = 0; + + start->shotgun_ammo = 2 * SHOTGUN_AMMO_CLIP; + start->magnum_ammo = 0; + start->uzi_ammo = 0; + start->harpoon_ammo = 0; + start->m16_ammo = 0; + start->grenade_ammo = 0; + + start->flares = 2; + start->small_medipacks = 1; + start->large_medipacks = 1; + start->gun_status = LGS_ARMLESS; + } + + if (g_SaveGame.bonus_flag && level != GF_GetGymLevel()) { + start->has_pistols = 1; + start->has_shotgun = 1; + start->has_magnums = 1; + start->has_uzis = 1; + start->has_grenade = 1; + start->has_harpoon = 1; + start->has_m16 = 1; + start->has_grenade = 1; + + start->shotgun_ammo = 10000; + start->magnum_ammo = 10000; + start->uzi_ammo = 10000; + start->harpoon_ammo = 10000; + start->m16_ammo = 10000; + start->grenade_ammo = 10000; + + start->flares = -1; + start->gun_type = LGT_GRENADE; + } + + if (g_GF_RemoveWeapons) { + start->has_pistols = 0; + start->has_magnums = 0; + start->has_uzis = 0; + start->has_shotgun = 0; + start->has_m16 = 0; + start->has_grenade = 0; + start->has_harpoon = 0; + start->gun_type = LGT_UNARMED; + start->gun_status = LGS_ARMLESS; + g_GF_RemoveWeapons = false; + } + + if (g_GF_RemoveAmmo) { + start->m16_ammo = 0; + start->grenade_ammo = 0; + start->harpoon_ammo = 0; + start->shotgun_ammo = 0; + start->uzi_ammo = 0; + start->magnum_ammo = 0; + start->pistol_ammo = 0; + start->flares = 0; + start->large_medipacks = 0; + start->small_medipacks = 0; + g_GF_RemoveAmmo = false; + } +} + +void Savegame_PersistGameToCurrentInfo(const GF_LEVEL *const level) +{ + START_INFO *const start = Savegame_GetCurrentInfo(level); + + start->available = 1; + + if (Inv_RequestItem(O_PISTOL_ITEM)) { + start->has_pistols = 1; + start->pistol_ammo = 1000; + } else { + start->has_pistols = 0; + start->pistol_ammo = 1000; + } + + if (Inv_RequestItem(O_SHOTGUN_ITEM)) { + start->has_shotgun = 1; + start->shotgun_ammo = g_Lara.shotgun_ammo.ammo; + } else { + start->has_shotgun = 0; + start->shotgun_ammo = + Inv_RequestItem(O_SHOTGUN_AMMO_ITEM) * SHOTGUN_AMMO_QTY; + } + + if (Inv_RequestItem(O_MAGNUM_ITEM)) { + start->has_magnums = 1; + start->magnum_ammo = g_Lara.magnum_ammo.ammo; + } else { + start->has_magnums = 0; + start->magnum_ammo = + Inv_RequestItem(O_MAGNUM_AMMO_ITEM) * MAGNUM_AMMO_QTY; + } + + if (Inv_RequestItem(O_UZI_ITEM)) { + start->has_uzis = 1; + start->uzi_ammo = g_Lara.uzi_ammo.ammo; + } else { + start->has_uzis = 0; + start->uzi_ammo = Inv_RequestItem(O_UZI_AMMO_ITEM) * UZI_AMMO_QTY; + } + + if (Inv_RequestItem(O_M16_ITEM)) { + start->has_m16 = 1; + start->m16_ammo = g_Lara.m16_ammo.ammo; + } else { + start->has_m16 = 0; + start->m16_ammo = Inv_RequestItem(O_M16_AMMO_ITEM) * M16_AMMO_QTY; + } + + if (Inv_RequestItem(O_HARPOON_ITEM)) { + start->has_harpoon = 1; + start->harpoon_ammo = g_Lara.harpoon_ammo.ammo; + } else { + start->has_harpoon = 0; + start->harpoon_ammo = + Inv_RequestItem(O_HARPOON_AMMO_ITEM) * HARPOON_AMMO_QTY; + } + + if (Inv_RequestItem(O_GRENADE_ITEM)) { + start->has_grenade = 1; + start->grenade_ammo = g_Lara.grenade_ammo.ammo; + } else { + start->has_grenade = 0; + start->grenade_ammo = + Inv_RequestItem(O_GRENADE_AMMO_ITEM) * GRENADE_AMMO_QTY; + } + + start->flares = Inv_RequestItem(O_FLARE_ITEM); + start->small_medipacks = Inv_RequestItem(O_SMALL_MEDIPACK_ITEM); + start->large_medipacks = Inv_RequestItem(O_LARGE_MEDIPACK_ITEM); + + if (g_Lara.gun_type == LGT_FLARE) { + start->gun_type = g_Lara.last_gun_type; + } else { + start->gun_type = g_Lara.gun_type; + } + start->gun_status = LGS_ARMLESS; +} + +void CreateSaveGameInfo(void) +{ + const GF_LEVEL *const current_level = Game_GetCurrentLevel(); + g_SaveGame.current_level = current_level->num; + Savegame_PersistGameToCurrentInfo(current_level); + + // TODO: refactor me! + g_SaveGame.num_pickup[0] = Inv_RequestItem(O_PICKUP_ITEM_1); + g_SaveGame.num_pickup[1] = Inv_RequestItem(O_PICKUP_ITEM_2); + g_SaveGame.num_puzzle[0] = Inv_RequestItem(O_PUZZLE_ITEM_1); + g_SaveGame.num_puzzle[1] = Inv_RequestItem(O_PUZZLE_ITEM_2); + g_SaveGame.num_puzzle[2] = Inv_RequestItem(O_PUZZLE_ITEM_3); + g_SaveGame.num_puzzle[3] = Inv_RequestItem(O_PUZZLE_ITEM_4); + g_SaveGame.num_key[0] = Inv_RequestItem(O_KEY_ITEM_1); + g_SaveGame.num_key[1] = Inv_RequestItem(O_KEY_ITEM_2); + g_SaveGame.num_key[2] = Inv_RequestItem(O_KEY_ITEM_3); + g_SaveGame.num_key[3] = Inv_RequestItem(O_KEY_ITEM_4); + + M_Reset(); + memset(g_SaveGame.buffer, 0, MAX_SG_BUFFER_SIZE); M_WriteS32(Room_GetFlipStatus()); for (int32_t i = 0; i < MAX_FLIP_MAPS; i++) { @@ -791,54 +923,27 @@ static void M_SaveToFile(MYFILE *const fp, SAVEGAME_INFO *const info) M_WriteS32(Room_GetFlipEffect()); M_WriteS32(Room_GetFlipTimer()); - M_WriteS32(Creature_AreAlliesHostile()); + M_WriteS32(g_IsMonkAngry); M_WriteFlares(); - - File_WriteData(fp, buffer, SAVEGAME_LEGACY_TOTAL_SIZE); - Memory_FreePointer(&buffer); } -static bool M_LoadFromFile(MYFILE *const fp) +void ExtractSaveGameInfo(void) { - char *buffer = Memory_Alloc(File_Size(fp)); - File_Seek(fp, 0, FILE_SEEK_SET); - File_ReadData(fp, buffer, File_Size(fp)); - - M_Reset(buffer); - M_Skip(SAVEGAME_LEGACY_TITLE_SIZE); - M_Skip(sizeof(int32_t)); // save counter - - M_ReadResumeInfos(); - { - LEVEL_STATS current_stats = {}; - M_ReadStats(¤t_stats); - const int16_t current_level = M_ReadS16(); - const GF_LEVEL *const level = GF_GetLevel(GFLT_MAIN, current_level); - RESUME_INFO *const current_info = Savegame_GetCurrentInfo(level); - current_info->stats = current_stats; - } - - const bool is_ng_plus = M_ReadU8() != 0; - if (is_ng_plus) { - Game_SetBonusFlag(GBF_NGPLUS); - } - - SAVEGAME_LEGACY_ITEM_STATS item_stats = {}; - M_Read(&item_stats, sizeof(SAVEGAME_LEGACY_ITEM_STATS)); - const GF_LEVEL *const current_level = Game_GetCurrentLevel(); Lara_InitialiseInventory(current_level); - Inv_AddItemNTimes(O_PICKUP_ITEM_1, item_stats.num_pickup[0]); - Inv_AddItemNTimes(O_PICKUP_ITEM_2, item_stats.num_pickup[1]); - Inv_AddItemNTimes(O_PUZZLE_ITEM_1, item_stats.num_puzzle[0]); - Inv_AddItemNTimes(O_PUZZLE_ITEM_2, item_stats.num_puzzle[1]); - Inv_AddItemNTimes(O_PUZZLE_ITEM_3, item_stats.num_puzzle[2]); - Inv_AddItemNTimes(O_PUZZLE_ITEM_4, item_stats.num_puzzle[3]); - Inv_AddItemNTimes(O_KEY_ITEM_1, item_stats.num_key[0]); - Inv_AddItemNTimes(O_KEY_ITEM_2, item_stats.num_key[1]); - Inv_AddItemNTimes(O_KEY_ITEM_3, item_stats.num_key[2]); - Inv_AddItemNTimes(O_KEY_ITEM_4, item_stats.num_key[3]); + Inv_AddItemNTimes(O_PICKUP_ITEM_1, g_SaveGame.num_pickup[0]); + Inv_AddItemNTimes(O_PICKUP_ITEM_2, g_SaveGame.num_pickup[1]); + Inv_AddItemNTimes(O_PUZZLE_ITEM_1, g_SaveGame.num_puzzle[0]); + Inv_AddItemNTimes(O_PUZZLE_ITEM_2, g_SaveGame.num_puzzle[1]); + Inv_AddItemNTimes(O_PUZZLE_ITEM_3, g_SaveGame.num_puzzle[2]); + Inv_AddItemNTimes(O_PUZZLE_ITEM_4, g_SaveGame.num_puzzle[3]); + Inv_AddItemNTimes(O_KEY_ITEM_1, g_SaveGame.num_key[0]); + Inv_AddItemNTimes(O_KEY_ITEM_2, g_SaveGame.num_key[1]); + Inv_AddItemNTimes(O_KEY_ITEM_3, g_SaveGame.num_key[2]); + Inv_AddItemNTimes(O_KEY_ITEM_4, g_SaveGame.num_key[3]); + + M_Reset(); if (M_ReadS32()) { Room_FlipMap(); @@ -873,14 +978,145 @@ static bool M_LoadFromFile(MYFILE *const fp) weapon_item->room_num = NO_ROOM; } + if (g_Lara.burn) { + g_Lara.burn = 0; + Lara_CatchFire(); + } + Room_SetFlipEffect(M_ReadS32()); Room_SetFlipTimer(M_ReadS32()); - Creature_SetAlliesHostile(M_ReadS32() != 0); + g_IsMonkAngry = M_ReadS32(); M_ReadFlares(); +} - Memory_FreePointer(&buffer); +void GetSavedGamesList(REQUEST_INFO *const req) +{ + Requester_SetSize(req, 10, -32); + if (req->selected >= req->visible_count) { + req->line_offset = req->selected - req->visible_count + 1; + } + memcpy(g_RequesterFlags1, m_ReqFlags1, sizeof(m_ReqFlags1)); + memcpy(g_RequesterFlags2, m_ReqFlags2, sizeof(m_ReqFlags2)); +} + +bool S_FrontEndCheck(void) +{ + Requester_Init(&g_LoadGameRequester); + + g_SavedGames = 0; + for (int32_t i = 0; i < MAX_REQUESTER_ITEMS; i++) { + char file_name[80]; + sprintf(file_name, g_GameFlow.savegame_fmt_legacy, i); + + if (!File_Exists(file_name)) { + Requester_AddItem( + &g_LoadGameRequester, GS(MISC_EMPTY_SLOT), 0, 0, 0); + g_SavedLevels[i] = false; + } else { + MYFILE *const fp = File_Open(file_name, FILE_OPEN_READ); + char level_title[80]; + File_ReadData(fp, level_title, 75); + const int32_t save_num = File_ReadS32(fp); + File_Close(fp); + + char save_num_text[16]; + sprintf(save_num_text, "%d", save_num); + + Requester_AddItem( + &g_LoadGameRequester, level_title, REQ_ALIGN_LEFT, + save_num_text, REQ_ALIGN_RIGHT); + + if (save_num > g_SaveCounter) { + g_SaveCounter = save_num; + g_LoadGameRequester.selected = i; + } + g_SavedLevels[i] = true; + g_SavedGames++; + } + } + + memcpy(m_ReqFlags1, g_RequesterFlags1, sizeof(m_ReqFlags1)); + memcpy(m_ReqFlags2, g_RequesterFlags2, sizeof(m_ReqFlags2)); + g_SaveCounter++; return true; } -REGISTER_SAVEGAME_STRATEGY(m_Strategy) +bool S_SaveGame(const int32_t slot_num) +{ + char file_name[80]; + sprintf(file_name, g_GameFlow.savegame_fmt_legacy, slot_num); + + MYFILE *const fp = File_Open(file_name, FILE_OPEN_WRITE); + if (fp == nullptr) { + return false; + } + + const GF_LEVEL *const current_level = + GF_GetLevel(GFLT_MAIN, g_SaveGame.current_level); + + memset(file_name, 0, 75); + snprintf(file_name, 75, "%s", current_level->title); + File_WriteData(fp, file_name, 75); + File_WriteS32(fp, g_SaveCounter); + M_WriteStartInfos(fp); + M_WriteStats(fp, &g_SaveGame.current_stats); + File_WriteS16(fp, g_SaveGame.current_level); + File_WriteU8(fp, g_SaveGame.bonus_flag); + for (int32_t i = 0; i < 2; i++) { + File_WriteU8(fp, g_SaveGame.num_pickup[i]); + } + for (int32_t i = 0; i < 4; i++) { + File_WriteU8(fp, g_SaveGame.num_puzzle[i]); + } + for (int32_t i = 0; i < 4; i++) { + File_WriteU8(fp, g_SaveGame.num_key[i]); + } + File_WriteS16(fp, 0); + File_WriteData(fp, g_SaveGame.buffer, MAX_SG_BUFFER_SIZE); + File_Close(fp); + + char save_num_text[16]; + sprintf(save_num_text, "%d", g_SaveCounter); + Requester_ChangeItem( + &g_LoadGameRequester, slot_num, file_name, REQ_ALIGN_LEFT, + save_num_text, REQ_ALIGN_RIGHT); + + m_ReqFlags1[slot_num] = g_RequesterFlags1[slot_num]; + m_ReqFlags2[slot_num] = g_RequesterFlags2[slot_num]; + g_SavedLevels[slot_num] = true; + g_SaveCounter++; + g_SavedGames++; + + return true; +} + +bool S_LoadGame(const int32_t slot_num) +{ + char file_name[80]; + sprintf(file_name, g_GameFlow.savegame_fmt_legacy, slot_num); + + MYFILE *const fp = File_Open(file_name, FILE_OPEN_READ); + if (fp == nullptr) { + return false; + } + File_Skip(fp, 75); + File_Skip(fp, 4); + M_ReadStartInfos(fp); + M_ReadStats(fp, &g_SaveGame.current_stats); + g_SaveGame.current_level = File_ReadS16(fp); + g_SaveGame.bonus_flag = File_ReadU8(fp); + for (int32_t i = 0; i < 2; i++) { + g_SaveGame.num_pickup[i] = File_ReadU8(fp); + } + for (int32_t i = 0; i < 4; i++) { + g_SaveGame.num_puzzle[i] = File_ReadU8(fp); + } + for (int32_t i = 0; i < 4; i++) { + g_SaveGame.num_key[i] = File_ReadU8(fp); + } + File_ReadS16(fp); + File_ReadData(fp, g_SaveGame.buffer, MAX_SG_BUFFER_SIZE); + File_Close(fp); + return true; +} diff --git a/src/tr2/decomp/savegame.h b/src/tr2/decomp/savegame.h new file mode 100644 index 000000000..86c173e4c --- /dev/null +++ b/src/tr2/decomp/savegame.h @@ -0,0 +1,27 @@ +#pragma once + +#include "game/game_flow/types.h" +#include "global/types.h" + +#include + +#include + +void Savegame_Init(void); +void Savegame_Shutdown(void); + +void Savegame_InitCurrentInfo(void); + +void Savegame_ResetCurrentInfo(const GF_LEVEL *level); +START_INFO *Savegame_GetCurrentInfo(const GF_LEVEL *level); +void Savegame_CarryCurrentInfoToNextLevel( + const GF_LEVEL *src_level, const GF_LEVEL *dst_level); +void Savegame_ApplyLogicToCurrentInfo(const GF_LEVEL *level); +void Savegame_PersistGameToCurrentInfo(const GF_LEVEL *level); +void CreateSaveGameInfo(void); +void ExtractSaveGameInfo(void); + +void GetSavedGamesList(REQUEST_INFO *req); +bool S_FrontEndCheck(void); +bool S_SaveGame(int32_t slot_num); +bool S_LoadGame(int32_t slot_num); diff --git a/src/tr2/game/camera.c b/src/tr2/game/camera.c index 93ce34c17..d29e32f9f 100644 --- a/src/tr2/game/camera.c +++ b/src/tr2/game/camera.c @@ -877,12 +877,6 @@ void Camera_LoadCutsceneFrame(void) g_Camera.roll = roll; g_Camera.shift = 0; - const int16_t room_num = - Room_FindByPos(g_Camera.pos.x, g_Camera.pos.y, g_Camera.pos.z); - if (room_num != NO_ROOM_NEG) { - g_Camera.pos.room_num = room_num; - } - Viewport_AlterFOV(fov); if (g_Config.audio.enable_lara_mic) { diff --git a/src/tr2/game/creature.c b/src/tr2/game/creature.c index 015943e29..eab8bf411 100644 --- a/src/tr2/game/creature.c +++ b/src/tr2/game/creature.c @@ -1,24 +1,953 @@ #include "game/creature.h" #include "game/box.h" +#include "game/camera.h" +#include "game/effects.h" #include "game/gun/gun_misc.h" +#include "game/items.h" +#include "game/lara/misc.h" #include "game/los.h" +#include "game/lot.h" +#include "game/objects/common.h" +#include "game/objects/vars.h" #include "game/random.h" +#include "game/room.h" #include "game/spawn.h" +#include "global/const.h" +#include "global/vars.h" #include -#include #include #include -#define M_SHOOT_TARGETING_SPEED 300 -#define M_SHOOT_HIT_CHANCE 0x2000 +#define FRONT_ARC DEG_90 +#define ESCAPE_CHANCE 2048 +#define RECOVER_CHANCE 256 +#define MAX_X_ROT (20 * DEG_1) // = 3640 +#define MAX_TILT (3 * DEG_1) // = 546 +#define MAX_HEAD_CHANGE (5 * DEG_1) // = 910 +#define HEAD_ARC 0x3000 // = 12288 +#define FLOAT_SPEED 32 +#define TARGET_TOLERANCE 0x400000 + +#define CREATURE_ATTACK_RANGE SQUARE(WALL_L * 3) // = 0x900000 = 9437184 +#define CREATURE_SHOOT_TARGETING_SPEED 300 +#define CREATURE_SHOOT_RANGE SQUARE(WALL_L * 8) // = 0x4000000 = 67108864 +#define CREATURE_SHOOT_HIT_CHANCE 0x2000 + +void Creature_Initialise(const int16_t item_num) +{ + ITEM *const item = Item_Get(item_num); + item->rot.y += (Random_GetControl() - DEG_90) >> 1; + item->collidable = 1; + item->data = 0; +} + +int32_t Creature_Activate(const int16_t item_num) +{ + ITEM *const item = Item_Get(item_num); + if (item->status != IS_INVISIBLE) { + return true; + } + + if (!LOT_EnableBaddieAI(item_num, false)) { + return false; + } + + item->status = IS_ACTIVE; + return true; +} + +void Creature_AIInfo(ITEM *const item, AI_INFO *const info) +{ + CREATURE *const creature = (CREATURE *)item->data; + if (creature == nullptr) { + return; + } + + switch (item->object_id) { + case O_BANDIT_1: + case O_BANDIT_2: + Creature_GetBaddieTarget(creature->item_num, false); + break; + + case O_MONK_1: + case O_MONK_2: + Creature_GetBaddieTarget(creature->item_num, true); + break; + + default: + creature->enemy = g_LaraItem; + break; + } + + ITEM *enemy = creature->enemy; + if (enemy == nullptr) { + enemy = g_LaraItem; + } + + const int16_t *const zone = Box_GetLotZone(&creature->lot); + + { + const ROOM *const room = Room_Get(item->room_num); + item->box_num = + Room_GetWorldSector(room, item->pos.x, item->pos.z)->box; + info->zone_num = zone[item->box_num]; + } + + { + const ROOM *const room = Room_Get(enemy->room_num); + enemy->box_num = + Room_GetWorldSector(room, enemy->pos.x, enemy->pos.z)->box; + info->enemy_zone_num = zone[enemy->box_num]; + } + + if (((Box_GetBox(enemy->box_num)->overlap_index & creature->lot.block_mask) + != 0) + || (creature->lot.node[item->box_num].search_num + == (creature->lot.search_num | BOX_BLOCKED_SEARCH))) { + info->enemy_zone_num |= BOX_BLOCKED; + } + + const OBJECT *const obj = Object_Get(item->object_id); + const int32_t z = enemy->pos.z + - ((obj->pivot_length * Math_Cos(item->rot.y)) >> W2V_SHIFT) + - item->pos.z; + const int32_t x = enemy->pos.x + - ((obj->pivot_length * Math_Sin(item->rot.y)) >> W2V_SHIFT) + - item->pos.x; + int16_t angle = Math_Atan(z, x); + if (creature->enemy != nullptr) { + info->distance = SQUARE(x) + SQUARE(z); + } else { + info->distance = 0x7FFFFFFF; + } + + info->angle = angle - item->rot.y; + info->enemy_facing = angle - enemy->rot.y + DEG_180; + info->ahead = info->angle > -FRONT_ARC && info->angle < FRONT_ARC; + info->bite = info->ahead && enemy->hit_points > 0 + && ABS(enemy->pos.y - item->pos.y) <= STEP_L; +} + +void Creature_Mood(const ITEM *item, const AI_INFO *info, int32_t violent) +{ + CREATURE *const creature = item->data; + if (creature == nullptr) { + return; + } + + LOT_INFO *const lot = &creature->lot; + const ITEM *enemy = creature->enemy; + if (lot->node[item->box_num].search_num + == (lot->search_num | BOX_BLOCKED_SEARCH)) { + lot->required_box = NO_BOX; + } + if (creature->mood != MOOD_ATTACK && lot->required_box != NO_BOX + && !Box_ValidBox(item, info->zone_num, lot->target_box)) { + if (info->zone_num == info->enemy_zone_num) { + creature->mood = MOOD_BORED; + } + lot->required_box = NO_BOX; + } + + const MOOD_TYPE mood = creature->mood; + if (enemy == nullptr) { + creature->mood = MOOD_BORED; + enemy = g_LaraItem; + } else if (enemy->hit_points <= 0) { + creature->mood = MOOD_BORED; + } else if (violent) { + switch (mood) { + case MOOD_BORED: + case MOOD_STALK: + if (info->zone_num == info->enemy_zone_num) { + creature->mood = MOOD_ATTACK; + } else if (item->hit_status) { + creature->mood = MOOD_ESCAPE; + } + break; + + case MOOD_ATTACK: + if (info->zone_num != info->enemy_zone_num) { + creature->mood = MOOD_BORED; + } + break; + + case MOOD_ESCAPE: + if (info->zone_num == info->enemy_zone_num) { + creature->mood = MOOD_ATTACK; + } + break; + } + } else { + switch (mood) { + case MOOD_BORED: + case MOOD_STALK: + if (item->hit_status + && (Random_GetControl() < ESCAPE_CHANCE + || info->zone_num != info->enemy_zone_num)) { + creature->mood = MOOD_ESCAPE; + } else if (info->zone_num == info->enemy_zone_num) { + if (info->distance < CREATURE_ATTACK_RANGE + || (creature->mood == MOOD_STALK + && lot->required_box == NO_BOX)) { + creature->mood = MOOD_ATTACK; + } else { + creature->mood = MOOD_STALK; + } + } + break; + + case MOOD_ATTACK: + if (item->hit_status + && (Random_GetControl() < ESCAPE_CHANCE + || info->zone_num != info->enemy_zone_num)) { + creature->mood = MOOD_ESCAPE; + } else if (info->zone_num != info->enemy_zone_num) { + creature->mood = MOOD_BORED; + } + break; + + case MOOD_ESCAPE: + if (info->zone_num == info->enemy_zone_num + && Random_GetControl() < RECOVER_CHANCE) { + creature->mood = MOOD_STALK; + } + break; + } + } + + if (mood != creature->mood) { + if (mood == MOOD_ATTACK) { + Box_TargetBox(lot, lot->target_box); + } + lot->required_box = NO_BOX; + } + + switch (creature->mood) { + case MOOD_BORED: { + const int16_t box_num = + lot->node[(lot->zone_count * Random_GetControl()) >> 15].box_num; + if (Box_ValidBox(item, info->zone_num, box_num)) { + if (Box_StalkBox(item, enemy, box_num) && creature->enemy != nullptr + && enemy->hit_points > 0) { + Box_TargetBox(lot, box_num); + creature->mood = MOOD_STALK; + } else if (lot->required_box == NO_BOX) { + Box_TargetBox(lot, box_num); + } + } + break; + } + + case MOOD_ATTACK: + lot->target = enemy->pos; + lot->required_box = enemy->box_num; + if (lot->fly != 0 && g_Lara.water_status == LWS_ABOVE_WATER) { + lot->target.y += Item_GetBestFrame(enemy)->bounds.min.y; + } + break; + + case MOOD_ESCAPE: { + const int16_t box_num = + lot->node[(lot->zone_count * Random_GetControl()) >> 15].box_num; + if (Box_ValidBox(item, info->zone_num, box_num) + && lot->required_box == NO_BOX) { + if (Box_EscapeBox(item, enemy, box_num)) { + Box_TargetBox(lot, box_num); + } else if ( + info->zone_num == info->enemy_zone_num + && Box_StalkBox(item, enemy, box_num)) { + Box_TargetBox(lot, box_num); + creature->mood = MOOD_STALK; + } + } + break; + } + + case MOOD_STALK: { + if (lot->required_box == NO_BOX + || !Box_StalkBox(item, enemy, lot->required_box)) { + const int16_t box_num = + lot->node[(lot->zone_count * Random_GetControl()) >> 15] + .box_num; + if (Box_ValidBox(item, info->zone_num, box_num)) { + if (Box_StalkBox(item, enemy, box_num)) { + Box_TargetBox(lot, box_num); + } else if (lot->required_box == NO_BOX) { + Box_TargetBox(lot, box_num); + if (info->zone_num != info->enemy_zone_num) { + creature->mood = MOOD_BORED; + } + } + } + } + break; + } + } + + if (lot->target_box == NO_BOX) { + Box_TargetBox(lot, item->box_num); + } + Box_CalculateTarget(&creature->target, item, lot); +} + +int32_t Creature_CheckBaddieOverlap(const int16_t item_num) +{ + const ITEM *item = Item_Get(item_num); + + const int32_t x = item->pos.x; + const int32_t y = item->pos.y; + const int32_t z = item->pos.z; + const int32_t radius = SQUARE(Object_Get(item->object_id)->radius); + + int16_t link = Room_Get(item->room_num)->item_num; + while (link != NO_ITEM && link != item_num) { + item = Item_Get(link); + if (item != g_LaraItem && item->status == IS_ACTIVE + && item->speed != 0) { + const int32_t distance = + (SQUARE(item->pos.z - z) + SQUARE(item->pos.y - y) + + SQUARE(item->pos.x - x)); + if (distance < radius) { + return true; + } + } + + link = item->next_item; + } + + return false; +} + +void Creature_Die(const int16_t item_num, const bool explode) +{ + ITEM *const item = Item_Get(item_num); + + if (item->object_id == O_DRAGON_FRONT) { + item->hit_points = 0; + return; + } + + if (item->object_id == O_SKIDOO_DRIVER) { + if (explode) { + Item_Explode(item_num, -1, 0); + } + item->hit_points = DONT_TARGET; + const int16_t vehicle_item_num = (int16_t)(intptr_t)item->data; + ITEM *const vehicle_item = Item_Get(vehicle_item_num); + vehicle_item->hit_points = 0; + return; + } + + item->collidable = 0; + item->hit_points = DONT_TARGET; + if (explode) { + Item_Explode(item_num, -1, 0); + Item_Kill(item_num); + } else { + Item_RemoveActive(item_num); + } + + const OBJECT *const obj = Object_Get(item->object_id); + if (obj->intelligent) { + LOT_DisableBaddieAI(item_num); + } + item->flags |= IF_ONE_SHOT; + + if (item->killed) { + item->next_active = Item_GetPrevActive(); + Item_SetPrevActive(item_num); + } + + if (obj->intelligent) { + int16_t pickup_num = item->carried_item; + while (pickup_num != NO_ITEM) { + ITEM *const pickup = Item_Get(pickup_num); + pickup->pos = item->pos; + Item_NewRoom(pickup_num, item->room_num); + pickup_num = pickup->carried_item; + } + } +} + +int32_t Creature_Animate( + const int16_t item_num, const int16_t angle, const int16_t tilt) +{ + ITEM *const item = Item_Get(item_num); + const CREATURE *const creature = item->data; + const OBJECT *const obj = Object_Get(item->object_id); + if (creature == nullptr) { + return false; + } + + const LOT_INFO *const lot = &creature->lot; + const XYZ_32 old = item->pos; + + const int16_t *const zone = Box_GetLotZone(lot); + + if (!Object_IsType(item->object_id, g_WaterObjects)) { + int16_t room_num = item->room_num; + Room_GetSector(item->pos.x, item->pos.y, item->pos.z, &room_num); + if (room_num != item->room_num) { + Item_NewRoom(item_num, room_num); + } + } + + Item_Animate(item); + if (item->status == IS_DEACTIVATED) { + Creature_Die(item_num, false); + return false; + } + + const BOUNDS_16 *const bounds = Item_GetBoundsAccurate(item); + int32_t y = item->pos.y + bounds->min.y; + + int16_t room_num = item->room_num; + Room_GetSector(old.x, y, old.z, &room_num); + const SECTOR *sector = + Room_GetSector(item->pos.x, y, item->pos.z, &room_num); + int32_t height = Box_GetBox(sector->box)->height; + int16_t next_box = lot->node[sector->box].exit_box; + int32_t next_height = + next_box != NO_BOX ? Box_GetBox(next_box)->height : height; + + const int32_t box_height = Box_GetBox(item->box_num)->height; + if (sector->box == NO_BOX || zone[item->box_num] != zone[sector->box] + || box_height - height > lot->step || box_height - height < lot->drop) { + const int32_t pos_x = item->pos.x >> WALL_SHIFT; + const int32_t shift_x = old.x >> WALL_SHIFT; + const int32_t shift_z = old.z >> WALL_SHIFT; + + if (pos_x < shift_x) { + item->pos.x = old.x & (~(WALL_L - 1)); + } else if (pos_x > shift_x) { + item->pos.x = old.x | (WALL_L - 1); + } + + if (pos_x < shift_z) { + item->pos.z = old.z & (~(WALL_L - 1)); + } else if (pos_x > shift_z) { + item->pos.z = old.z | (WALL_L - 1); + } + + sector = Room_GetSector(item->pos.x, y, item->pos.z, &room_num); + height = Box_GetBox(sector->box)->height; + next_box = lot->node[sector->box].exit_box; + next_height = + next_box != NO_BOX ? Box_GetBox(next_box)->height : height; + } + + const int32_t x = item->pos.x; + const int32_t z = item->pos.z; + const int32_t pos_x = x & (WALL_L - 1); + const int32_t pos_z = z & (WALL_L - 1); + int32_t shift_x = 0; + int32_t shift_z = 0; + const int32_t radius = obj->radius; + + if (pos_z < radius) { + if (Box_BadFloor( + x, y, z - radius, height, next_height, room_num, lot)) { + shift_z = radius - pos_z; + } + + if (pos_x < radius) { + if (Box_BadFloor( + x - radius, y, z, height, next_height, room_num, lot)) { + shift_x = radius - pos_x; + } else if ( + !shift_z + && Box_BadFloor( + x - radius, y, z - radius, height, next_height, room_num, + lot)) { + if (item->rot.y > -DEG_135 && item->rot.y < DEG_45) { + shift_z = radius - pos_z; + } else { + shift_x = radius - pos_x; + } + } + } else if (pos_x > WALL_L - radius) { + if (Box_BadFloor( + x + radius, y, z, height, next_height, room_num, lot)) { + shift_x = WALL_L - radius - pos_x; + } else if ( + !shift_z + && Box_BadFloor( + x + radius, y, z - radius, height, next_height, room_num, + lot)) { + if (item->rot.y > -DEG_45 && item->rot.y < DEG_135) { + shift_z = radius - pos_z; + } else { + shift_x = WALL_L - radius - pos_x; + } + } + } + } else if (pos_z > WALL_L - radius) { + if (Box_BadFloor( + x, y, z + radius, height, next_height, room_num, lot)) { + shift_z = WALL_L - radius - pos_z; + } + + if (pos_x < radius) { + if (Box_BadFloor( + x - radius, y, z, height, next_height, room_num, lot)) { + shift_x = radius - pos_x; + } else if ( + !shift_z + && Box_BadFloor( + x - radius, y, z + radius, height, next_height, room_num, + lot)) { + if (item->rot.y > -DEG_45 && item->rot.y < DEG_135) { + shift_x = radius - pos_x; + } else { + shift_z = WALL_L - radius - pos_z; + } + } + } else if (pos_x > WALL_L - radius) { + if (Box_BadFloor( + x + radius, y, z, height, next_height, room_num, lot)) { + shift_x = WALL_L - radius - pos_x; + } else if ( + !shift_z + && Box_BadFloor( + x + radius, y, z + radius, height, next_height, room_num, + lot)) { + if (item->rot.y > -DEG_135 && item->rot.y < DEG_45) { + shift_x = WALL_L - radius - pos_x; + } else { + shift_z = WALL_L - radius - pos_z; + } + } + } + } else if (pos_x < radius) { + if (Box_BadFloor( + x - radius, y, z, height, next_height, room_num, lot)) { + shift_x = radius - pos_x; + } + } else if (pos_x > WALL_L - radius) { + if (Box_BadFloor( + x + radius, y, z, height, next_height, room_num, lot)) { + shift_x = WALL_L - radius - pos_x; + } + } + + item->pos.x += shift_x; + item->pos.z += shift_z; + + if (shift_x || shift_z) { + sector = Room_GetSector(item->pos.x, y, item->pos.z, &room_num); + item->rot.y += angle; + if (tilt != 0) { + Creature_Tilt(item, tilt * 2); + } + } + + if (Creature_CheckBaddieOverlap(item_num)) { + item->pos = old; + return true; + } + + if (lot->fly) { + int32_t dy = creature->target.y - item->pos.y; + CLAMP(dy, -lot->fly, lot->fly); + + height = Room_GetHeight(sector, item->pos.x, y, item->pos.z); + if (item->pos.y + dy <= height) { + const int32_t ceiling = + Room_GetCeiling(sector, item->pos.x, y, item->pos.z); + const int32_t min_y = + item->object_id == O_SHARK ? 128 : bounds->min.y; + if (item->pos.y + min_y + dy < ceiling) { + if (item->pos.y + min_y < ceiling) { + item->pos.x = old.x; + item->pos.z = old.z; + dy = lot->fly; + } else { + dy = 0; + } + } + } else if (item->pos.y <= height) { + item->pos.y = height; + dy = 0; + } else { + item->pos.x = old.x; + item->pos.z = old.z; + dy = -lot->fly; + } + + item->pos.y += dy; + sector = Room_GetSector(item->pos.x, y, item->pos.z, &room_num); + item->floor = Room_GetHeight(sector, item->pos.x, y, item->pos.z); + + int16_t angle = item->speed != 0 ? Math_Atan(item->speed, -dy) : 0; + CLAMP(angle, -MAX_X_ROT, MAX_X_ROT); + + if (angle < item->rot.x - DEG_1) { + item->rot.x -= DEG_1; + } else if (angle > item->rot.x + DEG_1) { + item->rot.x += DEG_1; + } else { + item->rot.x = angle; + } + } else { + const SECTOR *const sector = + Room_GetSector(item->pos.x, item->pos.y, item->pos.z, &room_num); + item->floor = + Room_GetHeight(sector, item->pos.x, item->pos.y, item->pos.z); + + if (item->pos.y > item->floor) { + item->pos.y = item->floor; + } else if (item->floor - item->pos.y > STEP_L / 4) { + item->pos.y += STEP_L / 4; + } else if (item->pos.y < item->floor) { + item->pos.y = item->floor; + } + item->rot.x = 0; + } + + if (!Object_IsType(item->object_id, g_WaterObjects)) { + Room_GetSector( + item->pos.x, item->pos.y - (STEP_L * 2), item->pos.z, &room_num); + if (Room_Get(room_num)->flags & RF_UNDERWATER) { + item->hit_points = 0; + } + } + + if (item->room_num != room_num) { + Item_NewRoom(item_num, room_num); + } + return true; +} + +int16_t Creature_Turn(ITEM *const item, int16_t max_turn) +{ + const CREATURE *const creature = item->data; + if (creature == nullptr || item->speed == 0 || max_turn == 0) { + return 0; + } + + const int32_t dx = creature->target.x - item->pos.x; + const int32_t dz = creature->target.z - item->pos.z; + + int16_t angle = Math_Atan(dz, dx) - item->rot.y; + if (angle > FRONT_ARC || angle < -FRONT_ARC) { + const int32_t range = (item->speed << 14) / max_turn; + if (SQUARE(dx) + SQUARE(dz) < SQUARE(range)) { + max_turn /= 2; + } + } + + CLAMP(angle, -max_turn, max_turn); + item->rot.y += angle; + return angle; +} + +void Creature_Tilt(ITEM *const item, int16_t angle) +{ + angle = 4 * angle - item->rot.z; + CLAMP(angle, -MAX_TILT, MAX_TILT); + item->rot.z += angle; +} + +void Creature_Head(ITEM *item, int16_t required) +{ + CREATURE *const creature = item->data; + if (creature == nullptr) { + return; + } + + int16_t change = required - creature->head_rotation; + CLAMP(change, -MAX_HEAD_CHANGE, MAX_HEAD_CHANGE); + + creature->head_rotation += change; + CLAMP(creature->head_rotation, -HEAD_ARC, HEAD_ARC); +} + +void Creature_Neck(ITEM *const item, const int16_t required) +{ + CREATURE *const creature = item->data; + if (creature == nullptr) { + return; + } + + int16_t change = required - creature->neck_rotation; + CLAMP(change, -MAX_HEAD_CHANGE, MAX_HEAD_CHANGE); + + creature->neck_rotation += change; + CLAMP(creature->neck_rotation, -HEAD_ARC, HEAD_ARC); +} + +void Creature_Float(const int16_t item_num) +{ + ITEM *const item = Item_Get(item_num); + + item->hit_points = DONT_TARGET; + item->rot.x = 0; + + const int32_t wh = Room_GetWaterHeight( + item->pos.x, item->pos.y, item->pos.z, item->room_num); + if (item->pos.y > wh) { + item->pos.y -= FLOAT_SPEED; + } else if (item->pos.y < wh) { + item->pos.y = wh; + } + + Item_Animate(item); + + int16_t room_num = item->room_num; + const SECTOR *const sector = + Room_GetSector(item->pos.x, item->pos.y, item->pos.z, &room_num); + item->floor = Room_GetHeight(sector, item->pos.x, item->pos.y, item->pos.z); + if (room_num != item->room_num) { + Item_NewRoom(item_num, room_num); + } +} + +void Creature_Underwater(ITEM *const item, const int32_t depth) +{ + const int32_t wh = Room_GetWaterHeight( + item->pos.x, item->pos.y, item->pos.z, item->room_num); + if (item->pos.y >= wh + depth) { + return; + } + + item->pos.y = wh + depth; + if (item->rot.x > 2 * DEG_1) { + item->rot.x -= 2 * DEG_1; + } else { + CLAMPG(item->rot.x, 0); + } +} + +int16_t Creature_Effect( + const ITEM *const item, const BITE *const bite, + int16_t (*const spawn)( + int32_t x, int32_t y, int32_t z, int16_t speed, int16_t y_rot, + int16_t room_num)) +{ + XYZ_32 pos = bite->pos; + Collide_GetJointAbsPosition(item, &pos, bite->mesh_num); + return (*spawn)( + pos.x, pos.y, pos.z, item->speed, item->rot.y, item->room_num); +} + +int32_t Creature_Vault( + const int16_t item_num, const int16_t angle, int32_t vault, + const int32_t shift) +{ + ITEM *const item = Item_Get(item_num); + const int16_t room_num = item->room_num; + const XYZ_32 old = item->pos; + + Creature_Animate(item_num, angle, 0); + + if (item->floor > old.y + STEP_L * 7 / 2) { + vault = -4; + } else if (item->pos.y > old.y - STEP_L * 3 / 2) { + return 0; + } else if (item->pos.y > old.y - STEP_L * 5 / 2) { + vault = 2; + } else if (item->pos.y > old.y - STEP_L * 7 / 2) { + vault = 3; + } else { + vault = 4; + } + + const int32_t old_x_sector = old.x >> WALL_SHIFT; + const int32_t old_z_sector = old.z >> WALL_SHIFT; + const int32_t x_sector = item->pos.x >> WALL_SHIFT; + const int32_t z_sector = item->pos.z >> WALL_SHIFT; + if (old_z_sector == z_sector) { + if (old_x_sector == x_sector) { + return 0; + } + + if (old_x_sector >= x_sector) { + item->rot.y = -DEG_90; + item->pos.x = (old_x_sector << WALL_SHIFT) + shift; + } else { + item->rot.y = DEG_90; + item->pos.x = (x_sector << WALL_SHIFT) - shift; + } + } else if (old_x_sector == x_sector) { + if (old_z_sector >= z_sector) { + item->rot.y = -DEG_180; + item->pos.z = (old_z_sector << WALL_SHIFT) + shift; + } else { + item->rot.y = 0; + item->pos.z = (z_sector << WALL_SHIFT) - shift; + } + } + + item->floor = old.y; + item->pos.y = old.y; + + if (room_num != item->room_num) { + Item_NewRoom(item_num, room_num); + } + return vault; +} + +void Creature_Kill( + ITEM *const item, const int32_t kill_anim, const int32_t kill_state, + const int32_t lara_kill_state) +{ + Item_SwitchToAnim(item, kill_anim, 0); + item->current_anim_state = kill_state; + + Item_SwitchToObjAnim(g_LaraItem, LA_EXTRA_BREATH, 0, O_LARA_EXTRA); + g_LaraItem->current_anim_state = LA_EXTRA_BREATH; + g_LaraItem->goal_anim_state = lara_kill_state; + g_LaraItem->pos = item->pos; + g_LaraItem->rot = item->rot; + g_LaraItem->fall_speed = 0; + g_LaraItem->gravity = 0; + g_LaraItem->speed = 0; + + int16_t room_num = item->room_num; + if (room_num != g_LaraItem->room_num) { + Item_NewRoom(g_Lara.item_num, room_num); + } + + Item_Animate(g_LaraItem); + + g_LaraItem->goal_anim_state = lara_kill_state; + g_LaraItem->current_anim_state = lara_kill_state; + g_Lara.extra_anim = 1; + g_Lara.gun_status = LGS_HANDS_BUSY; + g_Lara.gun_type = LGT_UNARMED; + g_Lara.hit_direction = -1; + g_Lara.air = -1; + + g_Camera.pos.room_num = g_LaraItem->room_num; +} + +void Creature_GetBaddieTarget(const int16_t item_num, const int32_t goody) +{ + ITEM *const item = Item_Get(item_num); + CREATURE *const creature = item->data; + + ITEM *best_item = nullptr; + int32_t best_distance = INT32_MAX; + for (int32_t i = 0; i < NUM_SLOTS; i++) { + const int16_t target_item_num = g_BaddieSlots[i].item_num; + if (target_item_num == NO_ITEM || target_item_num == item_num) { + continue; + } + + ITEM *const target = Item_Get(target_item_num); + const GAME_OBJECT_ID obj_id = target->object_id; + if (goody && obj_id != O_BANDIT_1 && obj_id != O_BANDIT_2) { + continue; + } else if (!goody && obj_id != O_MONK_1 && obj_id != O_MONK_2) { + continue; + } + + const int32_t dx = (target->pos.x - item->pos.x) >> 6; + const int32_t dy = (target->pos.y - item->pos.y) >> 6; + const int32_t dz = (target->pos.z - item->pos.z) >> 6; + const int32_t distance = SQUARE(dx) + SQUARE(dy) + SQUARE(dz); + if (distance < best_distance) { + best_item = target; + best_distance = distance; + } + } + + if (best_item == nullptr) { + if (!goody || g_IsMonkAngry) { + creature->enemy = g_LaraItem; + } else { + creature->enemy = nullptr; + } + return; + } + + if (!goody || g_IsMonkAngry) { + const int32_t dx = (g_LaraItem->pos.x - item->pos.x) >> 6; + const int32_t dy = (g_LaraItem->pos.y - item->pos.y) >> 6; + const int32_t dz = (g_LaraItem->pos.z - item->pos.z) >> 6; + const int32_t distance = SQUARE(dx) + SQUARE(dy) + SQUARE(dz); + if (distance < best_distance) { + best_item = g_LaraItem; + best_distance = distance; + } + } + + const ITEM *const target = creature->enemy; + if (target == nullptr || target->status != IS_ACTIVE) { + creature->enemy = best_item; + } else { + const int32_t dx = (target->pos.x - item->pos.x) >> 6; + const int32_t dy = (target->pos.y - item->pos.y) >> 6; + const int32_t dz = (target->pos.z - item->pos.z) >> 6; + const int32_t distance = SQUARE(dz) + SQUARE(dy) + SQUARE(dx); + if (distance < best_distance + TARGET_TOLERANCE) { + creature->enemy = best_item; + } + } +} + +void Creature_Collision( + const int16_t item_num, ITEM *const lara_item, COLL_INFO *const coll) +{ + ITEM *const item = Item_Get(item_num); + if (!Item_TestBoundsCollide(item, lara_item, coll->radius)) { + return; + } + + if (!Collide_TestCollision(item, lara_item)) { + return; + } + + if (coll->enable_baddie_push && g_Lara.water_status != LWS_UNDERWATER + && g_Lara.water_status != LWS_SURFACE) { + Lara_Push(item, lara_item, coll, coll->enable_hit, false); + } +} + +int32_t Creature_CanTargetEnemy( + const ITEM *const item, const AI_INFO *const info) +{ + const CREATURE *const creature = item->data; + const ITEM *const enemy = creature->enemy; + if (enemy->hit_points <= 0 || !info->ahead + || info->distance >= CREATURE_SHOOT_RANGE) { + return 0; + } + + GAME_VECTOR start; + start.pos.x = item->pos.x; + start.pos.y = item->pos.y - STEP_L * 3; + start.pos.z = item->pos.z; + start.room_num = item->room_num; + + GAME_VECTOR target; + target.pos.x = enemy->pos.x; + target.pos.y = enemy->pos.y - STEP_L * 3; + target.pos.z = enemy->pos.z; + target.room_num = enemy->room_num; + + return LOS_Check(&start, &target); +} + +bool Creature_IsHostile(const ITEM *const item) +{ + return Object_IsType(item->object_id, g_EnemyObjects) + || (g_IsMonkAngry + && (item->object_id == O_MONK_1 || item->object_id == O_MONK_2)); +} + +bool Creature_IsAlly(const ITEM *const item) +{ + return Object_IsType(item->object_id, g_AllyObjects); +} int32_t Creature_ShootAtLara( ITEM *const item, const AI_INFO *const info, const BITE *const gun, const int16_t extra_rotation, const int32_t damage) { - const ITEM *const lara_item = Lara_GetItem(); const CREATURE *const creature = item->data; ITEM *const target_item = creature->enemy; @@ -32,12 +961,12 @@ int32_t Creature_ShootAtLara( int32_t distance = (((target_item->speed * Math_Sin(info->enemy_facing)) >> W2V_SHIFT) * CREATURE_SHOOT_RANGE) - / M_SHOOT_TARGETING_SPEED; + / CREATURE_SHOOT_TARGETING_SPEED; distance = info->distance + SQUARE(distance); if (distance > CREATURE_SHOOT_RANGE) { is_hit = false; } else { - const int32_t chance = M_SHOOT_HIT_CHANCE + const int32_t chance = CREATURE_SHOOT_HIT_CHANCE + (CREATURE_SHOOT_RANGE - info->distance) / (CREATURE_SHOOT_RANGE / 0x5000); is_hit = Random_GetControl() < chance; @@ -46,7 +975,7 @@ int32_t Creature_ShootAtLara( } int16_t effect_num = NO_EFFECT; - if (target_item == lara_item) { + if (target_item == g_LaraItem) { if (is_hit) { effect_num = Creature_Effect(item, gun, Spawn_GunHit); Item_TakeDamage(target_item, damage, true); diff --git a/src/tr2/game/creature.h b/src/tr2/game/creature.h index 6aabac727..2f0548b52 100644 --- a/src/tr2/game/creature.h +++ b/src/tr2/game/creature.h @@ -4,6 +4,33 @@ #include +void Creature_Initialise(int16_t item_num); +int32_t Creature_Activate(int16_t item_num); +void Creature_AIInfo(ITEM *item, AI_INFO *info); +void Creature_Mood(const ITEM *item, const AI_INFO *info, int32_t violent); +int32_t Creature_CheckBaddieOverlap(int16_t item_num); +void Creature_Die(int16_t item_num, bool explode); +int32_t Creature_Animate(int16_t item_num, int16_t angle, int16_t tilt); +int16_t Creature_Turn(ITEM *item, int16_t max_turn); +void Creature_Tilt(ITEM *item, int16_t angle); +void Creature_Head(ITEM *item, int16_t required); +void Creature_Neck(ITEM *item, int16_t required); +void Creature_Float(int16_t item_num); +void Creature_Underwater(ITEM *item, int32_t depth); +int16_t Creature_Effect( + const ITEM *item, const BITE *bite, + int16_t (*spawn)( + int32_t x, int32_t y, int32_t z, int16_t speed, int16_t y_rot, + int16_t room_num)); +int32_t Creature_Vault( + int16_t item_num, int16_t angle, int32_t vault, int32_t shift); +void Creature_Kill( + ITEM *item, int32_t kill_anim, int32_t kill_state, int32_t lara_kill_state); +void Creature_GetBaddieTarget(int16_t item_num, int32_t goody); +void Creature_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); +int32_t Creature_CanTargetEnemy(const ITEM *item, const AI_INFO *info); +bool Creature_IsHostile(const ITEM *item); +bool Creature_IsAlly(const ITEM *item); int32_t Creature_ShootAtLara( ITEM *item, const AI_INFO *info, const BITE *gun, int16_t extra_rotation, int32_t damage); diff --git a/src/tr2/game/demo.c b/src/tr2/game/demo.c index c8d347b57..3a1b47391 100644 --- a/src/tr2/game/demo.c +++ b/src/tr2/game/demo.c @@ -1,5 +1,6 @@ #include "game/demo.h" +#include "decomp/savegame.h" #include "game/camera.h" #include "game/game.h" #include "game/game_flow.h" @@ -13,7 +14,6 @@ #include "game/overlay.h" #include "game/random.h" #include "game/room.h" -#include "game/savegame.h" #include "game/sound.h" #include "game/stats.h" #include "game/text.h" @@ -30,7 +30,7 @@ typedef struct { TEXTSTRING *text; struct { - GAME_BONUS_FLAG bonus_flag; + bool bonus_flag; } old_config; } M_PRIV; @@ -44,13 +44,13 @@ static void M_RestoreConfig(M_PRIV *p); static void M_PrepareConfig(M_PRIV *const p) { - p->old_config.bonus_flag = Game_GetBonusFlag(); - Game_SetBonusFlag(GBF_NONE); + p->old_config.bonus_flag = g_SaveGame.bonus_flag; + g_SaveGame.bonus_flag = false; } static void M_RestoreConfig(M_PRIV *const p) { - Game_SetBonusFlag(p->old_config.bonus_flag); + g_SaveGame.bonus_flag = p->old_config.bonus_flag; } bool Demo_GetInput(void) diff --git a/src/tr2/game/effects.c b/src/tr2/game/effects.c index 32d72428b..b5a275e6d 100644 --- a/src/tr2/game/effects.c +++ b/src/tr2/game/effects.c @@ -112,7 +112,7 @@ int16_t Effect_Create(const int16_t room_num) effect->next_active = m_NextEffectActive; m_NextEffectActive = effect_num; - effect->shade = SHADE_NEUTRAL; + effect->shade = 0x1000; return effect_num; } diff --git a/src/tr2/game/fmv.c b/src/tr2/game/fmv.c index 41001bce8..9906fd79d 100644 --- a/src/tr2/game/fmv.c +++ b/src/tr2/game/fmv.c @@ -123,8 +123,9 @@ static bool M_Play(const char *const file_name) ? 0 : g_Config.audio.sound_volume / (float)Sound_GetMaxVolume()); - const SHELL_SIZE display_size = Shell_GetCurrentDisplaySize(); - Video_SetSurfaceSize(video, display_size.w, display_size.h); + Video_SetSurfaceSize( + video, Shell_GetCurrentDisplayWidth(), + Shell_GetCurrentDisplayHeight()); if (g_Config.rendering.render_mode == RM_SOFTWARE) { Video_SetSurfacePixelFormat(video, AV_PIX_FMT_RGB8); GFX_2D_Renderer_SetPalette(renderer_2d, palette); diff --git a/src/tr2/game/game.c b/src/tr2/game/game.c index 2269cc5c3..ccd9cf175 100644 --- a/src/tr2/game/game.c +++ b/src/tr2/game/game.c @@ -1,6 +1,7 @@ #include "game/game.h" #include "decomp/decomp.h" +#include "decomp/savegame.h" #include "game/camera.h" #include "game/demo.h" #include "game/effects.h" @@ -15,14 +16,12 @@ #include "game/output.h" #include "game/overlay.h" #include "game/room_draw.h" -#include "game/savegame.h" #include "game/shell.h" #include "game/sound.h" #include "game/stats.h" #include "global/vars.h" #include -#include #include bool Game_Start(const GF_LEVEL *const level, const GF_SEQUENCE_CONTEXT seq_ctx) @@ -114,10 +113,10 @@ GF_COMMAND Game_Control(const bool demo_mode) if (g_OverlayStatus > 0) { if (g_GameFlow.load_save_disabled) { g_OverlayStatus = 0; - } else if (g_Input.save) { - g_OverlayStatus = -2; + } else if (g_Input.load) { + g_OverlayStatus = -1; } else { - g_OverlayStatus = g_Input.load ? -1 : 0; + g_OverlayStatus = g_Input.save ? -2 : 0; } } else { GF_COMMAND gf_cmd; @@ -149,7 +148,7 @@ GF_COMMAND Game_Control(const bool demo_mode) Output_AnimateTextures(1 * TICKS_PER_FRAME); g_HealthBarTimer--; - if (!Game_IsInGym() || Gym_IsAssaultTimerActive()) { + if (!Game_IsInGym() || g_IsAssaultTimerActive) { Stats_UpdateTimer(); } diff --git a/src/tr2/game/game_flow/inventory.c b/src/tr2/game/game_flow/inventory.c index b9af92e91..147acb33f 100644 --- a/src/tr2/game/game_flow/inventory.c +++ b/src/tr2/game/game_flow/inventory.c @@ -1,9 +1,9 @@ #include "game/game_flow/inventory.h" +#include "decomp/savegame.h" #include "game/gun/gun.h" #include "game/inventory.h" #include "game/overlay.h" -#include "game/savegame.h" #include "global/vars.h" static int8_t m_SecretInvItems[O_NUMBER_OF] = {}; @@ -14,11 +14,11 @@ static bool m_RemoveFlares = false; static bool m_RemoveMedipacks = false; static void M_ModifyInventory_GunOrAmmo( - RESUME_INFO *resume, GF_INV_TYPE type, LARA_GUN_TYPE gun_type); + START_INFO *start, GF_INV_TYPE type, LARA_GUN_TYPE gun_type); static void M_ModifyInventory_Item(GF_INV_TYPE type, GAME_OBJECT_ID obj_id); static void M_ModifyInventory_GunOrAmmo( - RESUME_INFO *const resume, const GF_INV_TYPE type, + START_INFO *const start, const GF_INV_TYPE type, const LARA_GUN_TYPE gun_type) { const GAME_OBJECT_ID gun_item = Gun_GetGunObject(gun_type); @@ -42,13 +42,13 @@ static void M_ModifyInventory_GunOrAmmo( // clang-format off // TODO: consider moving this to Inv_AddItem switch (gun_type) { - case LGT_PISTOLS: resume->flags.has_pistols = 1; break; - case LGT_MAGNUMS: resume->flags.has_magnums = 1; break; - case LGT_UZIS: resume->flags.has_uzis = 1; break; - case LGT_SHOTGUN: resume->flags.has_shotgun = 1; break; - case LGT_HARPOON: resume->flags.has_harpoon = 1; break; - case LGT_M16: resume->flags.has_m16 = 1; break; - case LGT_GRENADE: resume->flags.has_grenade = 1; break; + case LGT_PISTOLS: start->has_pistols = 1; break; + case LGT_MAGNUMS: start->has_magnums = 1; break; + case LGT_UZIS: start->has_uzis = 1; break; + case LGT_SHOTGUN: start->has_shotgun = 1; break; + case LGT_HARPOON: start->has_harpoon = 1; break; + case LGT_M16: start->has_m16 = 1; break; + case LGT_GRENADE: start->has_grenade = 1; break; default: break; } // clang-format on @@ -137,55 +137,52 @@ void GF_InventoryModifier_Scan(const GF_LEVEL *const level) void GF_InventoryModifier_Apply( const GF_LEVEL *const level, const GF_INV_TYPE type) { - RESUME_INFO *const resume = Savegame_GetCurrentInfo(level); + START_INFO *const start = Savegame_GetCurrentInfo(level); - if (m_RemoveWeapons) { - resume->flags.has_pistols = 0; - resume->flags.has_magnums = 0; - resume->flags.has_uzis = 0; - resume->flags.has_shotgun = 0; - resume->flags.has_m16 = 0; - resume->flags.has_grenade = 0; - resume->flags.has_harpoon = 0; - resume->equipped_gun_type = LGT_UNARMED; - resume->gun_status = LGS_ARMLESS; + if (!start->has_pistols && m_Add2InvItems[O_PISTOL_ITEM]) { + start->has_pistols = 1; + Inv_AddItem(O_PISTOL_ITEM); } - if (!resume->flags.has_pistols && m_Add2InvItems[O_PISTOL_ITEM]) { - resume->flags.has_pistols = 1; - Inv_AddItem(O_PISTOL_ITEM); - if (resume->equipped_gun_type == LGT_UNARMED) { - resume->equipped_gun_type = LGT_PISTOLS; - } + if (m_RemoveWeapons) { + start->has_pistols = 0; + start->has_magnums = 0; + start->has_uzis = 0; + start->has_shotgun = 0; + start->has_m16 = 0; + start->has_grenade = 0; + start->has_harpoon = 0; + start->gun_type = LGT_UNARMED; + start->gun_status = LGS_ARMLESS; } if (m_RemoveAmmo) { - resume->m16_ammo = 0; - resume->grenade_ammo = 0; - resume->harpoon_ammo = 0; - resume->shotgun_ammo = 0; - resume->uzi_ammo = 0; - resume->magnum_ammo = 0; - resume->pistol_ammo = 0; + start->m16_ammo = 0; + start->grenade_ammo = 0; + start->harpoon_ammo = 0; + start->shotgun_ammo = 0; + start->uzi_ammo = 0; + start->magnum_ammo = 0; + start->pistol_ammo = 0; } if (m_RemoveFlares) { - resume->flares = 0; + start->flares = 0; m_RemoveFlares = false; } if (m_RemoveMedipacks) { - resume->large_medipacks = 0; - resume->small_medipacks = 0; + start->large_medipacks = 0; + start->small_medipacks = 0; m_RemoveMedipacks = false; } - M_ModifyInventory_GunOrAmmo(resume, type, LGT_MAGNUMS); - M_ModifyInventory_GunOrAmmo(resume, type, LGT_UZIS); - M_ModifyInventory_GunOrAmmo(resume, type, LGT_SHOTGUN); - M_ModifyInventory_GunOrAmmo(resume, type, LGT_HARPOON); - M_ModifyInventory_GunOrAmmo(resume, type, LGT_M16); - M_ModifyInventory_GunOrAmmo(resume, type, LGT_GRENADE); + M_ModifyInventory_GunOrAmmo(start, type, LGT_MAGNUMS); + M_ModifyInventory_GunOrAmmo(start, type, LGT_UZIS); + M_ModifyInventory_GunOrAmmo(start, type, LGT_SHOTGUN); + M_ModifyInventory_GunOrAmmo(start, type, LGT_HARPOON); + M_ModifyInventory_GunOrAmmo(start, type, LGT_M16); + M_ModifyInventory_GunOrAmmo(start, type, LGT_GRENADE); M_ModifyInventory_Item(type, O_FLARE_ITEM); M_ModifyInventory_Item(type, O_SMALL_MEDIPACK_ITEM); diff --git a/src/tr2/game/game_flow/sequencer_events.c b/src/tr2/game/game_flow/sequencer_events.c index a1a02505a..0c1b3c7ac 100644 --- a/src/tr2/game/game_flow/sequencer_events.c +++ b/src/tr2/game/game_flow/sequencer_events.c @@ -1,4 +1,5 @@ #include "decomp/decomp.h" +#include "decomp/savegame.h" #include "game/camera.h" #include "game/fmv.h" #include "game/game.h" @@ -8,11 +9,9 @@ #include "game/music.h" #include "game/output.h" #include "game/phase.h" -#include "game/savegame.h" #include "game/stats.h" #include "global/vars.h" -#include #include static DECLARE_GF_EVENT_HANDLER(M_HandlePlayLevel); @@ -28,6 +27,7 @@ static DECLARE_GF_EVENT_HANDLER(M_HandleRemoveAmmo); static DECLARE_GF_EVENT_HANDLER(M_HandleRemoveFlares); static DECLARE_GF_EVENT_HANDLER(M_HandleRemoveMedipacks); static DECLARE_GF_EVENT_HANDLER(M_HandleSetStartAnim); +static DECLARE_GF_EVENT_HANDLER(M_HandleSetNumSecrets); static DECLARE_GF_EVENT_HANDLER((*m_EventHandlers[GFS_NUMBER_OF])) = { // clang-format off @@ -44,6 +44,7 @@ static DECLARE_GF_EVENT_HANDLER((*m_EventHandlers[GFS_NUMBER_OF])) = { [GFS_REMOVE_FLARES] = M_HandleRemoveFlares, [GFS_REMOVE_MEDIPACKS] = M_HandleRemoveMedipacks, [GFS_SET_START_ANIM] = M_HandleSetStartAnim, + [GFS_SET_NUM_SECRETS] = M_HandleSetNumSecrets, // clang-format on }; @@ -56,9 +57,6 @@ static DECLARE_GF_EVENT_HANDLER(M_HandlePlayLevel) case GFSC_SAVED: GF_InventoryModifier_Scan(level); - // reset current info to the defaults so that we do not do - // Item_GlobalReplace in the inventory initialization routines too early - Savegame_InitCurrentInfo(); break; case GFSC_SELECT: { @@ -78,14 +76,16 @@ static DECLARE_GF_EVENT_HANDLER(M_HandlePlayLevel) } tmp_level = next_level; } + Stats_Reset(); break; } default: Savegame_ApplyLogicToCurrentInfo(level); - if (level->type == GFL_NORMAL || level->type == GFL_BONUS) { + if (level->type == GFL_NORMAL) { GF_InventoryModifier_Scan(level); GF_InventoryModifier_Apply(level, GF_INV_REGULAR); + Stats_Reset(); } break; } @@ -115,29 +115,17 @@ static DECLARE_GF_EVENT_HANDLER(M_HandlePlayLevel) switch (seq_ctx) { case GFSC_SAVED: - const int16_t slot_num = Savegame_GetBoundSlot(); - if (!Savegame_Load(slot_num)) { - LOG_ERROR("Failed to load save file!"); - return (GF_COMMAND) { .action = GF_EXIT_TO_TITLE }; - } + ExtractSaveGameInfo(); break; default: - if (level->type == GFL_NORMAL || level->type == GFL_BONUS) { - Savegame_SetInitialVersion(SAVEGAME_CURRENT_VERSION); + if (level->type == GFL_NORMAL) { GF_InventoryModifier_Scan(Game_GetCurrentLevel()); GF_InventoryModifier_Apply(Game_GetCurrentLevel(), GF_INV_REGULAR); } break; } - Stats_CalculateStats(); - RESUME_INFO *const resume = Savegame_GetCurrentInfo(level); - if (resume != nullptr) { - const int32_t secret_count = Stats_GetSecrets(); - resume->stats.max_secret_count = secret_count; - } - ASSERT(GF_GetCurrentLevel() == level); if (level->type == GFL_DEMO) { gf_cmd = GF_RunDemo(level->num); @@ -167,23 +155,22 @@ static DECLARE_GF_EVENT_HANDLER(M_HandleLevelComplete) const GF_LEVEL *const next_level = GF_GetLevelAfter(current_level); if (current_level == GF_GetLastLevel()) { - g_Config.profile.new_game_plus_unlock = true; - Config_Write(); + g_SaveGame.bonus_flag = true; + // TODO: refactor me + START_INFO *const start = Savegame_GetCurrentInfo(current_level); + start->stats = g_SaveGame.current_stats; } - RESUME_INFO *const resume = Savegame_GetCurrentInfo(current_level); - resume->flags.available = 0; - const bool bonus_level_unlock = Stats_CheckAllSecretsCollected(GFL_NORMAL); - + START_INFO *const start = Savegame_GetCurrentInfo(current_level); + start->stats = g_SaveGame.current_stats; + start->available = 0; if (next_level != nullptr) { Savegame_PersistGameToCurrentInfo(next_level); + g_SaveGame.current_level = next_level->num; } if (next_level == nullptr) { return (GF_COMMAND) { .action = GF_NOOP }; } - if (next_level->type == GFL_BONUS && !bonus_level_unlock) { - return (GF_COMMAND) { .action = GF_EXIT_TO_TITLE }; - } return (GF_COMMAND) { .action = GF_START_GAME, .param = next_level->num, @@ -263,6 +250,15 @@ static DECLARE_GF_EVENT_HANDLER(M_HandleSetStartAnim) return gf_cmd; } +static DECLARE_GF_EVENT_HANDLER(M_HandleSetNumSecrets) +{ + GF_COMMAND gf_cmd = { .action = GF_NOOP }; + if (seq_ctx != GFSC_STORY) { + g_GF_NumSecrets = (int16_t)(intptr_t)event->data; + } + return gf_cmd; +} + void GF_PreSequenceHook( const GF_SEQUENCE_CONTEXT seq_ctx, void *const seq_ctx_arg) { @@ -271,10 +267,10 @@ void GF_PreSequenceHook( g_GF_LaraStartAnim = 0; g_GF_RemoveAmmo = false; g_GF_RemoveWeapons = false; + g_GF_NumSecrets = 3; + // TODO: reset bonus flag if seq_ctx == GFSC_SAVED once S_LoadGame logic is + // merged with overall save loading logic. Camera_GetCineData()->position.target_angle = DEG_90; - if (seq_ctx == GFSC_SAVED) { - Game_SetBonusFlag(GBF_NONE); - } } GF_SEQUENCE_CONTEXT GF_SwitchSequenceContext( @@ -293,6 +289,12 @@ GF_SEQUENCE_CONTEXT GF_SwitchSequenceContext( } } +bool GF_ShouldSkipSequenceEvent( + const GF_LEVEL *const level, const GF_SEQUENCE_EVENT *const event) +{ + return false; +} + GF_EVENT_QUEUE_TYPE GF_ShouldDeferSequenceEvent( const GF_SEQUENCE_EVENT_TYPE event_type) { diff --git a/src/tr2/game/game_flow/sequencer_misc.c b/src/tr2/game/game_flow/sequencer_misc.c index 13726a531..43812ff69 100644 --- a/src/tr2/game/game_flow/sequencer_misc.c +++ b/src/tr2/game/game_flow/sequencer_misc.c @@ -1,9 +1,9 @@ +#include "decomp/savegame.h" #include "game/game.h" #include "game/game_flow/common.h" #include "game/game_flow/sequencer.h" #include "game/game_flow/vars.h" #include "game/level.h" -#include "game/savegame.h" #include #include @@ -50,8 +50,3 @@ GF_COMMAND GF_DoLevelSequence( current_level++; } } - -bool GF_HasAvailableStory(const int32_t slot_num) -{ - return false; -} diff --git a/src/tr2/game/game_string.def b/src/tr2/game/game_string.def index ee77fca25..58c7c250b 100644 --- a/src/tr2/game/game_string.def +++ b/src/tr2/game/game_string.def @@ -17,7 +17,6 @@ GS_DEFINE(OSD_SAVE_GAME, "Saved game to save slot %d") GS_DEFINE(KEYMAP_USE_FLARE, "Flare") GS_DEFINE(KEYMAP_CAMERA_RESET, "Camera Reset") GS_DEFINE(STATS_TIME_TAKEN, "Time Taken") -GS_DEFINE(STATS_BONUS_STATISTICS, "Bonus Statistics") GS_DEFINE(STATS_SECRETS, "Secrets Found") GS_DEFINE(STATS_AMMO_USED, "Ammo Used") GS_DEFINE(STATS_AMMO_HITS, "Hits") @@ -29,6 +28,7 @@ GS_DEFINE(STATS_ASSAULT_NO_TIMES_SET, "No Times Set") GS_DEFINE(STATS_ASSAULT_FINISH, "Finish") GS_DEFINE(PASSPORT_EXIT_DEMO, "Exit Demo") GS_DEFINE(MISC_NONE, "None") +GS_DEFINE(MISC_EMPTY_SLOT, "- EMPTY SLOT -") GS_DEFINE(OSD_BILINEAR_FILTER_ON, "Bilinear filter: on") GS_DEFINE(OSD_BILINEAR_FILTER_OFF, "Bilinear filter: off") GS_DEFINE(OSD_WIREFRAME_MODE_ON, "Wireframe mode: on") @@ -41,18 +41,3 @@ GS_DEFINE(OSD_HARDWARE_RENDERING, "Hardware rendering") GS_DEFINE(OSD_SOFTWARE_RENDERING, "Software rendering") GS_DEFINE(OSD_PERSPECTIVE_FILTER_ON, "Perspective correction: on") GS_DEFINE(OSD_PERSPECTIVE_FILTER_OFF, "Perspective correction: off") -GS_DEFINE(DETAIL_FOV, "Field of view") -GS_DEFINE(DETAIL_USE_PSX_FOV, "Use PSX FOV") -GS_DEFINE(DETAIL_DEPTH_BUFFER, "Z-Buffer") -GS_DEFINE(DETAIL_LIGHTING_CONTRAST, "Lighting contrast") -GS_DEFINE(DETAIL_LIGHTING_CONTRAST_LOW, "Low") -GS_DEFINE(DETAIL_LIGHTING_CONTRAST_MEDIUM, "Medium") -GS_DEFINE(DETAIL_LIGHTING_CONTRAST_HIGH, "High") -GS_DEFINE(DETAIL_RENDER_MODE_SOFTWARE, "Software") -GS_DEFINE(DETAIL_RENDER_MODE_HARDWARE, "Hardware") -GS_DEFINE(DETAIL_ASPECT_MODE, "Aspect mode") -GS_DEFINE(DETAIL_ASPECT_MODE_4_3, "4:3") -GS_DEFINE(DETAIL_ASPECT_MODE_16_9, "16:9") -GS_DEFINE(DETAIL_ASPECT_MODE_ANY, "Any") -GS_DEFINE(DETAIL_SCALER, "Scaler") -GS_DEFINE(DETAIL_SIZER, "Sizer") diff --git a/src/tr2/game/gun/gun_misc.c b/src/tr2/game/gun/gun_misc.c index 5561e7e1a..327d0764b 100644 --- a/src/tr2/game/gun/gun_misc.c +++ b/src/tr2/game/gun/gun_misc.c @@ -8,12 +8,10 @@ #include "game/random.h" #include "game/room.h" #include "game/spawn.h" -#include "game/stats.h" #include "global/vars.h" #include #include -#include #include #include #include @@ -116,7 +114,7 @@ void Gun_GetNewTarget(const WEAPON_INFO *const winfo) ITEM *best_target = nullptr; const int16_t max_dist = winfo->target_dist; - for (int32_t i = 0; i < LOT_SLOT_COUNT; i++) { + for (int32_t i = 0; i < NUM_SLOTS; i++) { const int16_t item_num = g_BaddieSlots[i].item_num; if (item_num == NO_ITEM || item_num == g_Lara.item_num) { continue; @@ -207,7 +205,7 @@ int32_t Gun_FireWeapon( AMMO_INFO *const ammo = Gun_GetAmmoInfo(weapon_type); ASSERT(ammo != nullptr); - if (ammo == &g_Lara.pistol_ammo || Game_IsBonusFlagSet(GBF_NGPLUS)) { + if (ammo == &g_Lara.pistol_ammo || g_SaveGame.bonus_flag) { ammo->ammo = 1000; } @@ -251,7 +249,7 @@ int32_t Gun_FireWeapon( } } - Stats_AddAmmoUsed(); + g_SaveGame.current_stats.ammo_used++; GAME_VECTOR start; start.pos.x = view_pos.x; @@ -279,7 +277,7 @@ int32_t Gun_FireWeapon( return -1; } } else { - Stats_AddAmmoHits(); + g_SaveGame.current_stats.ammo_hits++; GAME_VECTOR hit_pos; hit_pos.pos.x = view_pos.x + ((best_dist * g_MatrixPtr->_20) >> W2V_SHIFT); @@ -293,9 +291,7 @@ int32_t Gun_FireWeapon( if (item_to_smash != NO_ITEM) { Gun_SmashItem(item_to_smash, weapon_type); } - Gun_HitTarget( - target, &hit_pos, - winfo->damage * (Game_IsBonusFlagSet(GBF_JAPANESE) ? 2 : 1)); + Gun_HitTarget(target, &hit_pos, winfo->damage); return 1; } } @@ -319,7 +315,7 @@ void Gun_HitTarget( { if (item->hit_points > 0 && item->hit_points <= damage && item->object_id != O_DRAGON_FRONT) { - Stats_AddKill(); + g_SaveGame.current_stats.kills++; } Item_TakeDamage(item, damage, true); @@ -329,13 +325,13 @@ void Gun_HitTarget( item->rot.y, item->room_num); } - if (!Creature_AreAlliesHostile() + if (!g_IsMonkAngry && (item->object_id == O_MONK_1 || item->object_id == O_MONK_2)) { CREATURE *const creature = item->data; creature->flags += damage; if ((creature->flags & 0xFFF) > MONK_FRIENDLY_FIRE_THRESHOLD || creature->mood == MOOD_BORED) { - Creature_SetAlliesHostile(true); + g_IsMonkAngry = true; } } } @@ -369,7 +365,7 @@ void Gun_DrawFlash(const LARA_GUN_TYPE weapon_type, const int32_t clip) switch (weapon_type) { case LGT_MAGNUMS: - shade = SHADE_NEUTRAL; + shade = HIGH_LIGHT; y = 215; z = 65; break; @@ -400,7 +396,7 @@ void Gun_DrawFlash(const LARA_GUN_TYPE weapon_type, const int32_t clip) return; default: - shade = SHADE_LOW; + shade = LOW_LIGHT; y = 185; z = 40; break; diff --git a/src/tr2/game/gun/gun_rifle.c b/src/tr2/game/gun/gun_rifle.c index f551e95fc..4f2faa93c 100644 --- a/src/tr2/game/gun/gun_rifle.c +++ b/src/tr2/game/gun/gun_rifle.c @@ -8,11 +8,9 @@ #include "game/random.h" #include "game/room.h" #include "game/sound.h" -#include "game/stats.h" #include "global/vars.h" #include -#include #include #include #include @@ -197,11 +195,11 @@ void Gun_Rifle_FireHarpoon(void) item->status = IS_ACTIVE; g_Lara.harpoon_ammo.ammo--; - if (Game_IsBonusFlagSet(GBF_NGPLUS) + if (g_SaveGame.bonus_flag && (g_Lara.harpoon_ammo.ammo % HARPOON_RECOIL) == 0) { g_Lara.harpoon_ammo.ammo += HARPOON_RECOIL; } - Stats_AddAmmoUsed(); + g_SaveGame.current_stats.ammo_used++; } void Gun_Rifle_FireGrenade(void) @@ -238,10 +236,10 @@ void Gun_Rifle_FireGrenade(void) Item_AddActive(item_num); item->status = IS_ACTIVE; - if (!Game_IsBonusFlagSet(GBF_NGPLUS)) { + if (!g_SaveGame.bonus_flag) { g_Lara.grenade_ammo.ammo--; } - Stats_AddAmmoUsed(); + g_SaveGame.current_stats.ammo_used++; } void Gun_Rifle_Draw(const LARA_GUN_TYPE weapon_type) diff --git a/src/tr2/game/gym.c b/src/tr2/game/gym.c deleted file mode 100644 index 91ea861b8..000000000 --- a/src/tr2/game/gym.c +++ /dev/null @@ -1,8 +0,0 @@ -#include "game/game_flow.h" - -#include - -bool Gym_IsAccessible(void) -{ - return g_GameFlow.gym_enabled && GF_GetGymLevel() != nullptr; -} diff --git a/src/tr2/game/input.h b/src/tr2/game/input.h index 6f2d02c9b..e4e59f254 100644 --- a/src/tr2/game/input.h +++ b/src/tr2/game/input.h @@ -1,3 +1,5 @@ #pragma once #include + +const char *Input_GetRoleName(INPUT_ROLE role); diff --git a/src/tr2/game/inventory.c b/src/tr2/game/inventory.c index 26e705e50..708260adf 100644 --- a/src/tr2/game/inventory.c +++ b/src/tr2/game/inventory.c @@ -3,21 +3,10 @@ #include "game/inventory_ring.h" #include "game/items.h" #include "game/objects/vars.h" -#include "game/stats.h" #include "global/vars.h" -#include -#include #include -static int32_t M_GetFlareQuantity(void); - -static int32_t M_GetFlareQuantity(void) -{ - return Game_IsBonusFlagSet(GBF_JAPANESE) ? FLARE_AMMO_JAPANESE_QTY - : FLARE_AMMO_QTY; -} - bool Inv_AddItem(const GAME_OBJECT_ID obj_id) { const GAME_OBJECT_ID inv_obj_id = Inv_GetItemOption(obj_id); @@ -32,7 +21,7 @@ bool Inv_AddItem(const GAME_OBJECT_ID obj_id) for (int32_t i = 0; i < source->count; i++) { if (source->items[i]->object_id == inv_obj_id) { const int32_t qty = - obj_id == O_FLARES_ITEM ? M_GetFlareQuantity() : 1; + obj_id == O_FLARES_ITEM ? FLARE_AMMO_QTY : 1; source->qtys[i] += qty; CLAMPG(source->qtys[i], MAX_QTY); return true; @@ -193,7 +182,7 @@ bool Inv_AddItem(const GAME_OBJECT_ID obj_id) case O_FLARES_ITEM: case O_FLARES_OPTION: - for (int32_t i = 0; i < M_GetFlareQuantity(); i++) { + for (int32_t i = 0; i < FLARE_AMMO_QTY; i++) { Inv_AddItem(O_FLARE_ITEM); } return true; @@ -223,9 +212,15 @@ bool Inv_AddItem(const GAME_OBJECT_ID obj_id) return true; case O_SECRET_1: + g_SaveGame.current_stats.secret_flags |= 1; + return true; + case O_SECRET_2: + g_SaveGame.current_stats.secret_flags |= 2; + return true; + case O_SECRET_3: - Stats_MarkSecretCollected(obj_id); + g_SaveGame.current_stats.secret_flags |= 4; return true; case O_KEY_ITEM_1: diff --git a/src/tr2/game/inventory_ring/control.c b/src/tr2/game/inventory_ring/control.c index 5f1b04969..f28c126a4 100644 --- a/src/tr2/game/inventory_ring/control.c +++ b/src/tr2/game/inventory_ring/control.c @@ -1,5 +1,6 @@ #include "game/inventory_ring/control.h" +#include "decomp/savegame.h" #include "game/clock.h" #include "game/demo.h" #include "game/game.h" @@ -13,7 +14,6 @@ #include "game/option/option.h" #include "game/output.h" #include "game/overlay.h" -#include "game/savegame.h" #include "game/shell.h" #include "game/sound.h" #include "game/stats.h" @@ -21,8 +21,6 @@ #include "global/vars.h" #include -#include -#include #include #include #include @@ -32,8 +30,8 @@ #include -#define TITLE_RING_OBJECTS 4 -#define OPTION_RING_OBJECTS 4 +#define TITLE_RING_OBJECTS 3 +#define OPTION_RING_OBJECTS 3 #define INV_RING_FADE_TIME_FAST \ (INV_RING_CLOSE_FRAMES / INV_RING_FRAMES / (double)LOGIC_FPS) #define INV_RING_FADE_TIME_TITLE_FINISH 0.25 @@ -54,7 +52,7 @@ static GF_COMMAND M_Control(INV_RING *ring); static void M_ShowAmmoQuantity(const char *const fmt, const int32_t qty) { - if (!Game_IsBonusFlagSet(GBF_NGPLUS)) { + if (!g_SaveGame.bonus_flag) { InvRing_ShowItemQuantity(fmt, qty); } } @@ -193,6 +191,11 @@ static GF_COMMAND M_Finish(INV_RING *const ring, const bool apply_changes) // first passport page: load game. if (apply_changes) { Inv_RemoveAllItems(); + if (!S_LoadGame(g_Inv_ExtraData[1])) { + return (GF_COMMAND) { + .action = GF_EXIT_TO_TITLE, + }; + } } return (GF_COMMAND) { .action = GF_START_SAVED_GAME, @@ -239,7 +242,8 @@ static GF_COMMAND M_Finish(INV_RING *const ring, const bool apply_changes) } else { if (apply_changes) { Music_Unpause(); - Savegame_Save(g_Inv_ExtraData[1]); + CreateSaveGameInfo(); + S_SaveGame(g_Inv_ExtraData[1]); } return (GF_COMMAND) { .action = GF_NOOP }; } @@ -639,10 +643,10 @@ static GF_COMMAND M_Control(INV_RING *const ring) if (g_InputDB.menu_confirm) { g_Inv_Chosen = inv_item->object_id; - if (ring->type == RT_MAIN) { - g_InvRing_Source[RT_MAIN].current = ring->current_object; - } else { + if (ring->type != RT_MAIN) { g_InvRing_Source[RT_OPTION].current = ring->current_object; + } else { + g_InvRing_Source[RT_MAIN].current = ring->current_object; } if (ring->mode == INV_TITLE_MODE && (inv_item->object_id == O_DETAIL_OPTION @@ -783,7 +787,7 @@ INV_RING *InvRing_Open(const INVENTORY_MODE mode) } for (int32_t i = 0; i < 8; i++) { - g_Inv_ExtraData[i] = -1; + g_Inv_ExtraData[i] = 0; } g_InvRing_Source[RT_MAIN].current = 0; @@ -796,15 +800,12 @@ INV_RING *InvRing_Open(const INVENTORY_MODE mode) } g_InvRing_Source[RT_OPTION].current = 0; - if (mode == INV_TITLE_MODE) { - if (!Gym_IsAccessible()) { - Inv_RemoveItem(O_PHOTO_OPTION); - } else if (Gym_IsInventoryOpenEnabled()) { - for (int32_t i = 0; i < g_InvRing_Source[RT_OPTION].count; i++) { - if (g_InvRing_Source[RT_OPTION].items[i]->object_id - == O_PHOTO_OPTION) { - g_InvRing_Source[RT_OPTION].current = i; - } + if (g_GymInvOpenEnabled && mode == INV_TITLE_MODE + && GF_GetGymLevel() != nullptr) { + for (int32_t i = 0; i < g_InvRing_Source[RT_OPTION].count; i++) { + if (g_InvRing_Source[RT_OPTION].items[i]->object_id + == O_PHOTO_OPTION) { + g_InvRing_Source[RT_OPTION].current = i; } } } diff --git a/src/tr2/game/inventory_ring/draw.c b/src/tr2/game/inventory_ring/draw.c index a7f7141a6..609be4fee 100644 --- a/src/tr2/game/inventory_ring/draw.c +++ b/src/tr2/game/inventory_ring/draw.c @@ -1,13 +1,12 @@ #include "game/inventory_ring/draw.h" +#include "decomp/savegame.h" #include "game/console/common.h" -#include "game/game.h" #include "game/input.h" #include "game/inventory_ring/control.h" #include "game/option/option.h" #include "game/output.h" #include "game/overlay.h" -#include "game/savegame.h" #include "global/vars.h" #include @@ -68,9 +67,9 @@ static void M_DrawItem( { if (ring->motion.status != RNG_FADING_OUT && ring->motion.status != RNG_DONE && inv_item == ring->list[ring->current_object] && !ring->rotating) { - Output_SetLightAdder(SHADE_NEUTRAL); + Output_SetLightAdder(HIGH_LIGHT); } else { - Output_SetLightAdder(SHADE_LOW); + Output_SetLightAdder(LOW_LIGHT); } Matrix_TranslateRel(0, inv_item->y_trans, inv_item->z_trans); @@ -91,18 +90,16 @@ static void M_DrawItem( ANIM_FRAME *frame2; const int32_t frac = M_GetFrames(ring, inv_item, &frame1, &frame2, &rate); if (inv_item->object_id == O_COMPASS_OPTION) { - const RESUME_INFO *const current_info = - Savegame_GetCurrentInfo(Game_GetCurrentLevel()); const int32_t total_seconds = - current_info->stats.timer / FRAMES_PER_SECOND; + g_SaveGame.current_stats.timer / FRAMES_PER_SECOND; const int32_t hours = (total_seconds % 43200) * DEG_1 * -360 / 43200; const int32_t minutes = (total_seconds % 3600) * DEG_1 * -360 / 3600; const int32_t seconds = (total_seconds % 60) * DEG_1 * -360 / 60; const int16_t extra_rotation[3] = { hours, minutes, seconds }; - Object_GetBone(obj, 3)->rot.z = true; - Object_GetBone(obj, 4)->rot.z = true; - Object_GetBone(obj, 5)->rot.z = true; + Object_GetBone(obj, 3)->rot_z = true; + Object_GetBone(obj, 4)->rot_z = true; + Object_GetBone(obj, 5)->rot_z = true; Object_DrawInterpolatedObject( obj, inv_item->meshes_drawn, extra_rotation, frame1, frame2, frac, rate); diff --git a/src/tr2/game/inventory_ring/vars.c b/src/tr2/game/inventory_ring/vars.c index 4411b0314..f5143c0e2 100644 --- a/src/tr2/game/inventory_ring/vars.c +++ b/src/tr2/game/inventory_ring/vars.c @@ -2,6 +2,7 @@ #include "global/vars.h" +INVENTORY_MODE g_Inv_Mode = INV_TITLE_MODE; int16_t g_Inv_Chosen = -1; int32_t g_Inv_ExtraData[8]; int32_t g_Inv_NFrames = 2; @@ -43,12 +44,11 @@ INV_RING_SOURCE g_InvRing_Source[RT_NUMBER_OF] = { }, }, [RT_OPTION] = { - .count = 5, + .count = 4, .current = 0, - .qtys = { 1, 1, 1, 1, 1 }, + .qtys = { 1, 1, 1, 1 }, .items = { &g_InvRing_Item_Passport, - &g_InvRing_Item_Graphics, &g_InvRing_Item_Controls, &g_InvRing_Item_Sound, &g_InvRing_Item_Photo, diff --git a/src/tr2/game/inventory_ring/vars.h b/src/tr2/game/inventory_ring/vars.h index a180596e7..bedf75446 100644 --- a/src/tr2/game/inventory_ring/vars.h +++ b/src/tr2/game/inventory_ring/vars.h @@ -4,6 +4,7 @@ #include +extern INVENTORY_MODE g_Inv_Mode; extern int16_t g_Inv_Chosen; extern int32_t g_Inv_ExtraData[8]; diff --git a/src/tr2/game/item_actions.c b/src/tr2/game/item_actions.c index fd57134ca..e52ca6235 100644 --- a/src/tr2/game/item_actions.c +++ b/src/tr2/game/item_actions.c @@ -2,6 +2,7 @@ #include "game/camera.h" #include "game/lara/hair.h" +#include "game/music.h" #include "game/random.h" #include "game/room.h" #include "game/sound.h" @@ -11,7 +12,6 @@ #include "global/vars.h" #include -#include #include #include @@ -300,25 +300,60 @@ void M_ResetHair(ITEM *const item) void M_AssaultStart(ITEM *const item) { - Gym_StartAssault(); + g_SaveGame.current_stats.timer = 0; + g_IsAssaultTimerActive = true; + g_IsAssaultTimerDisplay = true; Room_SetFlipEffect(-1); + Stats_StartTimer(); } void M_AssaultStop(ITEM *const item) { - Gym_StopAssault(); + g_IsAssaultTimerActive = false; + g_IsAssaultTimerDisplay = true; Room_SetFlipEffect(-1); } void M_AssaultReset(ITEM *const item) { - Gym_ResetAssault(); + g_IsAssaultTimerActive = false; + g_IsAssaultTimerDisplay = false; Room_SetFlipEffect(-1); } void M_AssaultFinished(ITEM *const item) { - Gym_FinishAssault(); + if (g_IsAssaultTimerActive) { + Stats_StoreAssaultTime(g_SaveGame.current_stats.timer); + + if ((int32_t)g_AssaultBestTime < 0) { + if (g_SaveGame.current_stats.timer < 100 * FRAMES_PER_SECOND) { + // "Gosh! That was my best time yet!" + Music_Play(MX_GYM_HINT_15, MPM_ALWAYS); + g_AssaultBestTime = g_SaveGame.current_stats.timer; + } else { + // "Congratulations! You did it! But perhaps I could've been + // faster." + Music_Play(MX_GYM_HINT_17, MPM_ALWAYS); + g_AssaultBestTime = 100 * FRAMES_PER_SECOND; + } + } else if (g_SaveGame.current_stats.timer < g_AssaultBestTime) { + // "Gosh! That was my best time yet!" + Music_Play(MX_GYM_HINT_15, MPM_ALWAYS); + g_AssaultBestTime = g_SaveGame.current_stats.timer; + } else if ( + g_SaveGame.current_stats.timer + < g_AssaultBestTime + 5 * FRAMES_PER_SECOND) { + // "Almost. Perhaps another try and I might beat it." + Music_Play(MX_GYM_HINT_16, MPM_ALWAYS); + } else { + // "Great. But nowhere near my best time." + Music_Play(MX_GYM_HINT_14, MPM_ALWAYS); + } + + g_IsAssaultTimerActive = false; + } + Room_SetFlipEffect(-1); } diff --git a/src/tr2/game/items.c b/src/tr2/game/items.c index 09c7b16ca..aa26ba927 100644 --- a/src/tr2/game/items.c +++ b/src/tr2/game/items.c @@ -10,16 +10,48 @@ #include #include -#include #include #include #include #include #include -static BOUNDS_16 m_NullBounds = {}; static BOUNDS_16 m_InterpolatedBounds = {}; +static OBJECT_BOUNDS M_ConvertBounds(const int16_t *bounds_in); + +static OBJECT_BOUNDS M_ConvertBounds(const int16_t *const bounds_in) +{ + // TODO: remove this conversion utility once we gain control over its + // incoming arguments + return (OBJECT_BOUNDS) { + .shift = { + .min = { + .x = bounds_in[0], + .y = bounds_in[2], + .z = bounds_in[4], + }, + .max = { + .x = bounds_in[1], + .y = bounds_in[3], + .z = bounds_in[5], + }, + }, + .rot = { + .min = { + .x = bounds_in[6], + .y = bounds_in[8], + .z = bounds_in[10], + }, + .max = { + .x = bounds_in[7], + .y = bounds_in[9], + .z = bounds_in[11], + }, + }, + }; +} + void Item_Control(void) { int16_t item_num = Item_GetNextActive(); @@ -90,8 +122,7 @@ void Item_Initialise(const int16_t item_num) Room_GetWorldSector(room, item->pos.x, item->pos.z); item->floor = sector->floor.height; - if (Game_IsBonusFlagSet(GBF_NGPLUS) - && GF_GetCurrentLevel()->type != GFL_DEMO) { + if (g_SaveGame.bonus_flag && GF_GetCurrentLevel()->type != GFL_DEMO) { item->hit_points *= 2; } @@ -154,14 +185,152 @@ int16_t Item_GetHeight(const ITEM *const item) return height; } -int32_t Item_GetFrames(const ITEM *item, ANIM_FRAME *frames[], int32_t *rate) +int32_t Item_TestBoundsCollide( + const ITEM *const src_item, const ITEM *const dst_item, + const int32_t radius) { - const ANIM *const anim = Item_GetAnim(item); - if (anim->frame_ptr == nullptr) { - frames[0] = nullptr; - return 0; + const BOUNDS_16 *const src_bounds = &Item_GetBestFrame(src_item)->bounds; + const BOUNDS_16 *const dst_bounds = &Item_GetBestFrame(dst_item)->bounds; + + if (src_item->pos.y + src_bounds->max.y + <= dst_item->pos.y + dst_bounds->min.y + || src_item->pos.y + src_bounds->min.y + >= dst_item->pos.y + dst_bounds->max.y) { + return false; } + const int32_t c = Math_Cos(src_item->rot.y); + const int32_t s = Math_Sin(src_item->rot.y); + const int32_t dx = dst_item->pos.x - src_item->pos.x; + const int32_t dz = dst_item->pos.z - src_item->pos.z; + const int32_t rx = (c * dx - s * dz) >> W2V_SHIFT; + const int32_t rz = (c * dz + s * dx) >> W2V_SHIFT; + + // clang-format off + return ( + rx >= src_bounds->min.x - radius && + rx <= src_bounds->max.x + radius && + rz >= src_bounds->min.z - radius && + rz <= src_bounds->max.z + radius); + // clang-format on +} + +int32_t Item_TestPosition( + const int16_t *const bounds_in, const ITEM *const src_item, + const ITEM *const dst_item) +{ + const OBJECT_BOUNDS bounds = M_ConvertBounds(bounds_in); + + const XYZ_16 rot = { + .x = dst_item->rot.x - src_item->rot.x, + .y = dst_item->rot.y - src_item->rot.y, + .z = dst_item->rot.z - src_item->rot.z, + }; + const XYZ_32 dist = { + .x = dst_item->pos.x - src_item->pos.x, + .y = dst_item->pos.y - src_item->pos.y, + .z = dst_item->pos.z - src_item->pos.z, + }; + + // clang-format off + if (rot.x < bounds.rot.min.x || + rot.x > bounds.rot.max.x || + rot.y < bounds.rot.min.y || + rot.y > bounds.rot.max.y || + rot.z < bounds.rot.min.z || + rot.z > bounds.rot.max.z + ) { + return false; + } + // clang-format on + + Matrix_PushUnit(); + Matrix_Rot16(src_item->rot); + const MATRIX *const m = g_MatrixPtr; + const XYZ_32 shift = { + .x = (dist.x * m->_00 + dist.y * m->_10 + dist.z * m->_20) >> W2V_SHIFT, + .y = (dist.x * m->_01 + dist.y * m->_11 + dist.z * m->_21) >> W2V_SHIFT, + .z = (dist.x * m->_02 + dist.y * m->_12 + dist.z * m->_22) >> W2V_SHIFT, + }; + Matrix_Pop(); + + // clang-format off + return ( + shift.x >= bounds.shift.min.x && + shift.x <= bounds.shift.max.x && + shift.y >= bounds.shift.min.y && + shift.y <= bounds.shift.max.y && + shift.z >= bounds.shift.min.z && + shift.z <= bounds.shift.max.z + ); + // clang-format on +} + +void Item_AlignPosition( + const XYZ_32 *const vec, const ITEM *const src_item, ITEM *const dst_item) +{ + dst_item->rot = src_item->rot; + Matrix_PushUnit(); + Matrix_Rot16(src_item->rot); + const MATRIX *const m = g_MatrixPtr; + const XYZ_32 shift = { + .x = (vec->x * m->_00 + vec->y * m->_01 + vec->z * m->_02) >> W2V_SHIFT, + .y = (vec->x * m->_10 + vec->y * m->_11 + vec->z * m->_12) >> W2V_SHIFT, + .z = (vec->x * m->_20 + vec->y * m->_21 + vec->z * m->_22) >> W2V_SHIFT, + }; + Matrix_Pop(); + + const XYZ_32 new_pos = { + .x = src_item->pos.x + shift.x, + .y = src_item->pos.y + shift.y, + .z = src_item->pos.z + shift.z, + }; + + int16_t room_num = dst_item->room_num; + const SECTOR *const sector = + Room_GetSector(new_pos.x, new_pos.y, new_pos.z, &room_num); + const int32_t height = + Room_GetHeight(sector, new_pos.x, new_pos.y, new_pos.z); + const int32_t ceiling = + Room_GetCeiling(sector, new_pos.x, new_pos.y, new_pos.z); + + if (ABS(height - dst_item->pos.y) > STEP_L + || ABS(ceiling - dst_item->pos.y) < LARA_HEIGHT) { + return; + } + + dst_item->pos.x = new_pos.x; + dst_item->pos.y = new_pos.y; + dst_item->pos.z = new_pos.z; +} + +int32_t Item_IsTriggerActive(ITEM *const item) +{ + const bool ok = !(item->flags & IF_REVERSE); + + if ((item->flags & IF_CODE_BITS) != IF_CODE_BITS) { + return !ok; + } + + if (!item->timer) { + return ok; + } + + if (item->timer == -1) { + return !ok; + } + + item->timer--; + if (item->timer == 0) { + item->timer = -1; + } + + return ok; +} + +int32_t Item_GetFrames(const ITEM *item, ANIM_FRAME *frmptr[], int32_t *rate) +{ + const ANIM *const anim = Item_GetAnim(item); const int32_t cur_frame_num = item->frame_num - anim->frame_base; const int32_t last_frame_num = anim->frame_end - anim->frame_base; const int32_t key_frame_span = anim->interpolation; @@ -180,8 +349,8 @@ int32_t Item_GetFrames(const ITEM *item, ANIM_FRAME *frames[], int32_t *rate) } } - frames[0] = &anim->frame_ptr[first_key_frame_num]; - frames[1] = &anim->frame_ptr[second_key_frame_num]; + frmptr[0] = &anim->frame_ptr[first_key_frame_num]; + frmptr[1] = &anim->frame_ptr[second_key_frame_num]; // OG if (g_Config.rendering.fps == 30) { @@ -212,38 +381,34 @@ int32_t Item_GetFrames(const ITEM *item, ANIM_FRAME *frames[], int32_t *rate) return final * 10; } -const BOUNDS_16 *Item_GetBoundsAccurate(const ITEM *const item) +BOUNDS_16 *Item_GetBoundsAccurate(const ITEM *const item) { int32_t rate; - ANIM_FRAME *frames[2]; - const int32_t frac = Item_GetFrames(item, frames, &rate); - if (frames[0] == nullptr) { - return &m_NullBounds; - } - - if (frac == 0) { - return &frames[0]->bounds; + ANIM_FRAME *frmptr[2]; + const int32_t frac = Item_GetFrames(item, frmptr, &rate); + if (!frac) { + return &frmptr[0]->bounds; } #define CALC(target, b1, b2, prop) \ target->prop = (b1)->prop + ((((b2)->prop - (b1)->prop) * frac) / rate); BOUNDS_16 *const result = &m_InterpolatedBounds; - CALC(result, &frames[0]->bounds, &frames[1]->bounds, min.x); - CALC(result, &frames[0]->bounds, &frames[1]->bounds, max.x); - CALC(result, &frames[0]->bounds, &frames[1]->bounds, min.y); - CALC(result, &frames[0]->bounds, &frames[1]->bounds, max.y); - CALC(result, &frames[0]->bounds, &frames[1]->bounds, min.z); - CALC(result, &frames[0]->bounds, &frames[1]->bounds, max.z); + CALC(result, &frmptr[0]->bounds, &frmptr[1]->bounds, min.x); + CALC(result, &frmptr[0]->bounds, &frmptr[1]->bounds, max.x); + CALC(result, &frmptr[0]->bounds, &frmptr[1]->bounds, min.y); + CALC(result, &frmptr[0]->bounds, &frmptr[1]->bounds, max.y); + CALC(result, &frmptr[0]->bounds, &frmptr[1]->bounds, min.z); + CALC(result, &frmptr[0]->bounds, &frmptr[1]->bounds, max.z); return result; } ANIM_FRAME *Item_GetBestFrame(const ITEM *const item) { - ANIM_FRAME *frames[2]; - int32_t rate = 0; - const int32_t frac = Item_GetFrames(item, frames, &rate); - return frames[(frac > rate / 2) ? 1 : 0]; + ANIM_FRAME *frmptr[2]; + int32_t rate; + const int32_t frac = Item_GetFrames(item, frmptr, &rate); + return frmptr[(frac > rate / 2) ? 1 : 0]; } bool Item_IsNearItem( @@ -325,7 +490,18 @@ int32_t Item_Explode( Matrix_TranslateRel32(bone->pos); Matrix_Rot16(best_frame->mesh_rots[i]); - Object_ApplyExtraRotation(&extra_rotation, bone->rot, false); + + if (extra_rotation != nullptr) { + if (bone->rot_y) { + Matrix_RotY(*extra_rotation++); + } + if (bone->rot_x) { + Matrix_RotX(*extra_rotation++); + } + if (bone->rot_z) { + Matrix_RotZ(*extra_rotation++); + } + } bit <<= 1; if ((mesh_bits & bit) && (item->mesh_bits & bit)) { diff --git a/src/tr2/game/items.h b/src/tr2/game/items.h index c82d6ec0c..f60ebe7a2 100644 --- a/src/tr2/game/items.h +++ b/src/tr2/game/items.h @@ -8,7 +8,15 @@ void Item_Control(void); void Item_ClearKilled(void); void Item_ShiftCol(ITEM *item, COLL_INFO *coll); void Item_UpdateRoom(ITEM *item, int32_t height); +int32_t Item_TestBoundsCollide( + const ITEM *src_item, const ITEM *dst_item, int32_t radius); +int32_t Item_TestPosition( + const int16_t *bounds, const ITEM *src_item, const ITEM *dst_item); +void Item_AlignPosition( + const XYZ_32 *vec, const ITEM *src_item, ITEM *dst_item); +int32_t Item_IsTriggerActive(ITEM *item); int32_t Item_GetFrames(const ITEM *item, ANIM_FRAME *frmptr[], int32_t *rate); +BOUNDS_16 *Item_GetBoundsAccurate(const ITEM *item); bool Item_IsNearItem(const ITEM *item, const XYZ_32 *pos, int32_t distance); bool Item_IsSmashable(const ITEM *item); diff --git a/src/tr2/game/lara/cheat.c b/src/tr2/game/lara/cheat.c index 1f0a9e8a7..9e9b0672f 100644 --- a/src/tr2/game/lara/cheat.c +++ b/src/tr2/game/lara/cheat.c @@ -301,23 +301,13 @@ bool Lara_Cheat_GiveAllItems(void) return true; } -bool Lara_Cheat_Teleport(int32_t x, int32_t y, int32_t z, int16_t room_num) +bool Lara_Cheat_Teleport(int32_t x, int32_t y, int32_t z) { - if (room_num == NO_ROOM) { - room_num = Room_GetIndexFromPos(x, y, z); - } + int16_t room_num = Room_GetIndexFromPos(x, y, z); if (room_num == NO_ROOM) { return false; } - const ROOM *const room = Room_Get(room_num); - if (room->flip_status == RFS_FLIPPED && Room_GetFlipStatus()) { - room_num = Room_GetFlippedBaseRoom(room_num); - if (room_num == NO_ROOM) { - return false; - } - } - const SECTOR *sector = Room_GetSector(x, y, z, &room_num); int16_t height = Room_GetHeight(sector, x, y, z); @@ -339,9 +329,12 @@ bool Lara_Cheat_Teleport(int32_t x, int32_t y, int32_t z, int16_t room_num) .y = y, .z = ROUND_TO_SECTOR(z + dz * unit) + WALL_L / 2, }; + room_num = Room_GetIndexFromPos(point.x, point.y, point.z); + if (room_num == NO_ROOM) { + continue; + } sector = Room_GetSector(point.x, point.y, point.z, &room_num); - height = - Room_GetHeightEx(sector, point.x, point.y, point.z, true); + height = Room_GetHeight(sector, point.x, point.y, point.z); if (height == NO_HEIGHT) { continue; } @@ -368,8 +361,12 @@ bool Lara_Cheat_Teleport(int32_t x, int32_t y, int32_t z, int16_t room_num) } } + room_num = Room_GetIndexFromPos(x, y, z); + if (room_num == NO_ROOM) { + return false; + } sector = Room_GetSector(x, y, z, &room_num); - height = Room_GetHeightEx(sector, x, y, z, true); + height = Room_GetHeight(sector, x, y, z); if (height == NO_HEIGHT) { return false; } diff --git a/src/tr2/game/lara/cheat.h b/src/tr2/game/lara/cheat.h index 09ce86840..a6c051cf7 100644 --- a/src/tr2/game/lara/cheat.h +++ b/src/tr2/game/lara/cheat.h @@ -10,4 +10,5 @@ bool Lara_Cheat_GiveAllKeys(void); bool Lara_Cheat_GiveAllGuns(void); bool Lara_Cheat_GiveAllItems(void); bool Lara_Cheat_OpenNearestDoor(void); +bool Lara_Cheat_Teleport(int32_t x, int32_t y, int32_t z); bool Lara_Cheat_KillEnemy(int16_t item_num); diff --git a/src/tr2/game/lara/control.c b/src/tr2/game/lara/control.c index 726c9dcdc..d43988385 100644 --- a/src/tr2/game/lara/control.c +++ b/src/tr2/game/lara/control.c @@ -1,5 +1,6 @@ #include "game/lara/control.h" +#include "decomp/savegame.h" #include "decomp/skidoo.h" #include "game/camera.h" #include "game/creature.h" @@ -16,13 +17,10 @@ #include "game/lara/state.h" #include "game/music.h" #include "game/room.h" -#include "game/savegame.h" #include "game/sound.h" #include "game/spawn.h" -#include "game/stats.h" #include "global/vars.h" -#include #include #include #include @@ -625,7 +623,7 @@ void Lara_Control(const int16_t item_num) if (item->hit_points <= 0) { item->hit_points = -1; if (Game_IsInGym()) { - Gym_SetInventoryOpenEnabled(true); + g_GymInvOpenEnabled = true; } if (!g_Lara.death_timer) { Music_Stop(); @@ -681,7 +679,11 @@ void Lara_Control(const int16_t item_num) break; } - Stats_AddDistanceTravelled(item->pos, g_Lara.last_pos); + g_SaveGame.current_stats.distance += Math_Sqrt( + SQUARE(item->pos.z - g_Lara.last_pos.z) + + SQUARE(item->pos.y - g_Lara.last_pos.y) + + SQUARE(item->pos.x - g_Lara.last_pos.x)); + g_Lara.last_pos = item->pos; } @@ -737,7 +739,7 @@ void Lara_UseItem(const GAME_OBJECT_ID obj_id) CLAMPG(item->hit_points, LARA_MAX_HITPOINTS); Inv_RemoveItem(O_SMALL_MEDIPACK_ITEM); Sound_Effect(SFX_MENU_MEDI, nullptr, SPM_ALWAYS); - Stats_AddMedipacksUsed(0.5); + g_SaveGame.current_stats.medipacks++; } break; @@ -747,7 +749,7 @@ void Lara_UseItem(const GAME_OBJECT_ID obj_id) item->hit_points = LARA_MAX_HITPOINTS; Inv_RemoveItem(O_LARGE_MEDIPACK_ITEM); Sound_Effect(SFX_MENU_MEDI, nullptr, SPM_ALWAYS); - Stats_AddMedipacksUsed(1); + g_SaveGame.current_stats.medipacks += 2; } break; @@ -814,8 +816,7 @@ void Lara_Initialise(const GF_LEVEL *const level) g_Lara.right_arm.lock = 0; g_Lara.creature = nullptr; - if ((level->type == GFL_NORMAL || level->type == GFL_BONUS) - && g_GF_LaraStartAnim) { + if (level->type == GFL_NORMAL && g_GF_LaraStartAnim) { g_Lara.water_status = LWS_ABOVE_WATER; g_Lara.gun_status = LGS_HANDS_BUSY; Item_SwitchToObjAnim(item, LA_EXTRA_BREATH, 0, O_LARA_EXTRA); @@ -855,72 +856,72 @@ void Lara_InitialiseInventory(const GF_LEVEL *const level) Inv_RemoveAllItems(); Inv_AddItem(O_COMPASS_ITEM); - RESUME_INFO *const resume = Savegame_GetCurrentInfo(level); - if (resume != nullptr) { + START_INFO *const start = Savegame_GetCurrentInfo(level); + if (start != nullptr) { g_Lara.pistol_ammo.ammo = 1000; - if (resume->flags.has_pistols) { + if (start->has_pistols) { Inv_AddItem(O_PISTOL_ITEM); } - if (resume->flags.has_magnums) { + if (start->has_magnums) { Inv_AddItem(O_MAGNUM_ITEM); - g_Lara.magnum_ammo.ammo = resume->magnum_ammo; + g_Lara.magnum_ammo.ammo = start->magnum_ammo; Item_GlobalReplace(O_MAGNUM_ITEM, O_MAGNUM_AMMO_ITEM); } else { - Inv_AddItemNTimes(O_MAGNUM_AMMO_ITEM, resume->magnum_ammo / 40); + Inv_AddItemNTimes(O_MAGNUM_AMMO_ITEM, start->magnum_ammo / 40); g_Lara.magnum_ammo.ammo = 0; } - if (resume->flags.has_uzis) { + if (start->has_uzis) { Inv_AddItem(O_UZI_ITEM); - g_Lara.uzi_ammo.ammo = resume->uzi_ammo; + g_Lara.uzi_ammo.ammo = start->uzi_ammo; Item_GlobalReplace(O_UZI_ITEM, O_UZI_AMMO_ITEM); } else { - Inv_AddItemNTimes(O_UZI_AMMO_ITEM, resume->uzi_ammo / 80); + Inv_AddItemNTimes(O_UZI_AMMO_ITEM, start->uzi_ammo / 80); g_Lara.uzi_ammo.ammo = 0; } - if (resume->flags.has_shotgun) { + if (start->has_shotgun) { Inv_AddItem(O_SHOTGUN_ITEM); - g_Lara.shotgun_ammo.ammo = resume->shotgun_ammo; + g_Lara.shotgun_ammo.ammo = start->shotgun_ammo; Item_GlobalReplace(O_SHOTGUN_ITEM, O_SHOTGUN_AMMO_ITEM); } else { - Inv_AddItemNTimes(O_SHOTGUN_AMMO_ITEM, resume->shotgun_ammo / 12); + Inv_AddItemNTimes(O_SHOTGUN_AMMO_ITEM, start->shotgun_ammo / 12); g_Lara.shotgun_ammo.ammo = 0; } - if (resume->flags.has_m16) { + if (start->has_m16) { Inv_AddItem(O_M16_ITEM); - g_Lara.m16_ammo.ammo = resume->m16_ammo; + g_Lara.m16_ammo.ammo = start->m16_ammo; Item_GlobalReplace(O_M16_ITEM, O_M16_AMMO_ITEM); } else { - Inv_AddItemNTimes(O_M16_AMMO_ITEM, resume->m16_ammo / 40); + Inv_AddItemNTimes(O_M16_AMMO_ITEM, start->m16_ammo / 40); g_Lara.m16_ammo.ammo = 0; } - if (resume->flags.has_grenade) { + if (start->has_grenade) { Inv_AddItem(O_GRENADE_ITEM); - g_Lara.grenade_ammo.ammo = resume->grenade_ammo; + g_Lara.grenade_ammo.ammo = start->grenade_ammo; Item_GlobalReplace(O_GRENADE_ITEM, O_GRENADE_AMMO_ITEM); } else { - Inv_AddItemNTimes(O_GRENADE_AMMO_ITEM, resume->grenade_ammo / 2); + Inv_AddItemNTimes(O_GRENADE_AMMO_ITEM, start->grenade_ammo / 2); g_Lara.grenade_ammo.ammo = 0; } - if (resume->flags.has_harpoon) { + if (start->has_harpoon) { Inv_AddItem(O_HARPOON_ITEM); - g_Lara.harpoon_ammo.ammo = resume->harpoon_ammo; + g_Lara.harpoon_ammo.ammo = start->harpoon_ammo; Item_GlobalReplace(O_HARPOON_ITEM, O_HARPOON_AMMO_ITEM); } else { - Inv_AddItemNTimes(O_HARPOON_AMMO_ITEM, resume->harpoon_ammo / 3); + Inv_AddItemNTimes(O_HARPOON_AMMO_ITEM, start->harpoon_ammo / 3); g_Lara.harpoon_ammo.ammo = 0; } - Inv_AddItemNTimes(O_FLARE_ITEM, resume->flares); - Inv_AddItemNTimes(O_SMALL_MEDIPACK_ITEM, resume->small_medipacks); - Inv_AddItemNTimes(O_LARGE_MEDIPACK_ITEM, resume->large_medipacks); + Inv_AddItemNTimes(O_FLARE_ITEM, start->flares); + Inv_AddItemNTimes(O_SMALL_MEDIPACK_ITEM, start->small_medipacks); + Inv_AddItemNTimes(O_LARGE_MEDIPACK_ITEM, start->large_medipacks); - g_Lara.last_gun_type = resume->equipped_gun_type; + g_Lara.last_gun_type = start->gun_type; } g_Lara.gun_status = LGS_ARMLESS; @@ -937,16 +938,16 @@ void Lara_InitialiseMeshes(const GF_LEVEL *const level) Lara_SwapSingleMesh(i, O_LARA); } - const RESUME_INFO *const resume = Savegame_GetCurrentInfo(level); - if (resume == nullptr) { + const START_INFO *const start = Savegame_GetCurrentInfo(level); + if (start == nullptr) { return; } GAME_OBJECT_ID holster_obj_id = NO_OBJECT; - if (resume->equipped_gun_type != LGT_UNARMED) { - if (resume->equipped_gun_type == LGT_MAGNUMS) { + if (start->gun_type != LGT_UNARMED) { + if (start->gun_type == LGT_MAGNUMS) { holster_obj_id = O_LARA_MAGNUMS; - } else if (resume->equipped_gun_type == LGT_UZIS) { + } else if (start->gun_type == LGT_UZIS) { holster_obj_id = O_LARA_UZIS; } else { holster_obj_id = O_LARA_PISTOLS; @@ -958,11 +959,11 @@ void Lara_InitialiseMeshes(const GF_LEVEL *const level) Lara_SwapSingleMesh(LM_THIGH_R, holster_obj_id); } - if (resume->equipped_gun_type == LGT_FLARE) { + if (start->gun_type == LGT_FLARE) { Lara_SwapSingleMesh(LM_HAND_L, O_LARA_FLARE); } - switch (resume->equipped_gun_type) { + switch (start->gun_type) { case LGT_M16: g_Lara.back_gun = O_LARA_M16; return; @@ -974,18 +975,15 @@ void Lara_InitialiseMeshes(const GF_LEVEL *const level) case LGT_HARPOON: g_Lara.back_gun = O_LARA_HARPOON; return; - - default: - break; } - if (resume->flags.has_shotgun) { + if (start->has_shotgun) { g_Lara.back_gun = O_LARA_SHOTGUN; - } else if (resume->flags.has_m16) { + } else if (start->has_m16) { g_Lara.back_gun = O_LARA_M16; - } else if (resume->flags.has_grenade) { + } else if (start->has_grenade) { g_Lara.back_gun = O_LARA_GRENADE; - } else if (resume->flags.has_harpoon) { + } else if (start->has_harpoon) { g_Lara.back_gun = O_LARA_HARPOON; } } @@ -1005,3 +1003,8 @@ void Lara_GetOffVehicle(void) g_LaraItem->rot.z = 0; } } + +void Lara_TakeDamage(const int16_t damage, const bool hit_status) +{ + Item_TakeDamage(g_LaraItem, damage, hit_status); +} diff --git a/src/tr2/game/lara/control.h b/src/tr2/game/lara/control.h index cd3aaa5b4..ec979f43f 100644 --- a/src/tr2/game/lara/control.h +++ b/src/tr2/game/lara/control.h @@ -25,3 +25,5 @@ void Lara_InitialiseInventory(const GF_LEVEL *level); void Lara_InitialiseMeshes(const GF_LEVEL *level); void Lara_GetOffVehicle(void); + +void Lara_TakeDamage(int16_t damage, bool hit_status); diff --git a/src/tr2/game/lara/draw.c b/src/tr2/game/lara/draw.c index 04fa37486..60cf7ce56 100644 --- a/src/tr2/game/lara/draw.c +++ b/src/tr2/game/lara/draw.c @@ -473,13 +473,6 @@ void Lara_Draw_I( Matrix_Rot16_ID(mesh_rots_1[LM_UARM_R], mesh_rots_2[LM_UARM_R]); Output_DrawObjectMesh_I(g_Lara.mesh_ptrs[LM_UARM_R], clip); -// NOTE: gcc wrongly complains about mesh_rots_1 possibly being NULL. -// While this is not the case, it's curious how the pistols subtract the -// frame_base from g_Lara.*_arm.frame_num to access the mesh_rots, and the -// rifles do not. -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Warray-bounds" - M_DrawBodyPart(LM_LARM_R, bone, mesh_rots_1, mesh_rots_2, clip); M_DrawBodyPart(LM_HAND_R, bone, mesh_rots_1, mesh_rots_2, clip); @@ -493,8 +486,6 @@ void Lara_Draw_I( M_DrawBodyPart(LM_LARM_L, bone, mesh_rots_1, mesh_rots_2, clip); M_DrawBodyPart(LM_HAND_L, bone, mesh_rots_1, mesh_rots_2, clip); -#pragma GCC diagnostic pop - if (g_Lara.right_arm.flash_gun) { *g_MatrixPtr = saved_matrix; Gun_DrawFlash(gun_type, clip); diff --git a/src/tr2/game/lara/misc.c b/src/tr2/game/lara/misc.c index c510e6895..309e2fa17 100644 --- a/src/tr2/game/lara/misc.c +++ b/src/tr2/game/lara/misc.c @@ -912,7 +912,7 @@ void Lara_GetJointAbsPosition_I( if (g_Lara.gun_type == LGT_FLARE) { Matrix_Interpolate(); - Matrix_TranslateRel32(bone[LM_UARM_L - 1].pos); + Matrix_TranslateRel32(bone[LM_UARM_R - 1].pos); if (g_Lara.flare_control_left) { const LARA_ARM *const arm = &g_Lara.left_arm; const ANIM *const anim = Anim_GetAnim(arm->anim_num); @@ -948,6 +948,7 @@ void Lara_GetJointAbsPosition_I( vec->x = item->pos.x + (g_MatrixPtr->_03 >> W2V_SHIFT); vec->y = item->pos.y + (g_MatrixPtr->_13 >> W2V_SHIFT); vec->z = item->pos.z + (g_MatrixPtr->_23 >> W2V_SHIFT); + Matrix_Pop(); } @@ -1025,12 +1026,11 @@ void Lara_BaddieCollision(ITEM *lara_item, COLL_INFO *coll) } void Lara_Push( - const ITEM *const item, COLL_INFO *const coll, const bool hit_on, - const bool big_push) + const ITEM *const item, ITEM *const lara_item, COLL_INFO *const coll, + const bool hit_on, const bool big_push) { - ITEM *const target_item = Lara_GetItem(); - int32_t dx = target_item->pos.x - item->pos.x; - int32_t dz = target_item->pos.z - item->pos.z; + int32_t dx = lara_item->pos.x - item->pos.x; + int32_t dz = lara_item->pos.z - item->pos.z; const int32_t c = Math_Cos(item->rot.y); const int32_t s = Math_Sin(item->rot.y); int32_t rx = (c * dx - s * dz) >> W2V_SHIFT; @@ -1068,8 +1068,8 @@ void Lara_Push( rz = min_z; } - target_item->pos.x = item->pos.x + ((rz * s + rx * c) >> W2V_SHIFT); - target_item->pos.z = item->pos.z + ((rz * c - rx * s) >> W2V_SHIFT); + lara_item->pos.x = item->pos.x + ((rz * s + rx * c) >> W2V_SHIFT); + lara_item->pos.z = item->pos.z + ((rz * c - rx * s) >> W2V_SHIFT); rz = (bounds->max.z + bounds->min.z) / 2; rx = (bounds->max.x + bounds->min.x) / 2; @@ -1077,7 +1077,7 @@ void Lara_Push( dz -= (c * rz - s * rx) >> W2V_SHIFT; if (hit_on && bounds->max.y - bounds->min.y > STEP_L) { - M_TakeHit(target_item, dx, dz); + M_TakeHit(lara_item, dx, dz); } int16_t old_facing = coll->facing; @@ -1085,20 +1085,20 @@ void Lara_Push( coll->bad_neg = -STEPUP_HEIGHT; coll->bad_ceiling = 0; coll->facing = Math_Atan( - target_item->pos.z - coll->old.z, target_item->pos.x - coll->old.x); + lara_item->pos.z - coll->old.z, lara_item->pos.x - coll->old.x); Collide_GetCollisionInfo( - coll, target_item->pos.x, target_item->pos.y, target_item->pos.z, - target_item->room_num, LARA_HEIGHT); + coll, lara_item->pos.x, lara_item->pos.y, lara_item->pos.z, + lara_item->room_num, LARA_HEIGHT); coll->facing = old_facing; if (coll->coll_type != COLL_NONE) { - target_item->pos.x = coll->old.x; - target_item->pos.z = coll->old.z; + lara_item->pos.x = coll->old.x; + lara_item->pos.z = coll->old.z; } else { - coll->old.x = target_item->pos.x; - coll->old.y = target_item->pos.y; - coll->old.z = target_item->pos.z; - Item_UpdateRoom(target_item, -10); + coll->old.x = lara_item->pos.x; + coll->old.y = lara_item->pos.y; + coll->old.z = lara_item->pos.z; + Item_UpdateRoom(lara_item, -WALL_SHIFT); } } diff --git a/src/tr2/game/lara/misc.h b/src/tr2/game/lara/misc.h index 39286a548..36a6379a3 100644 --- a/src/tr2/game/lara/misc.h +++ b/src/tr2/game/lara/misc.h @@ -46,6 +46,9 @@ void Lara_GetJointAbsPosition_I( void Lara_BaddieCollision(ITEM *lara_item, COLL_INFO *coll); void Lara_TakeHit(ITEM *lara_item, const COLL_INFO *coll); +void Lara_Push( + const ITEM *item, ITEM *lara_item, COLL_INFO *coll, bool hit_on, + bool big_push); int32_t Lara_MovePosition(XYZ_32 *vec, ITEM *item, ITEM *lara_item); int32_t Lara_IsNearItem(const XYZ_32 *pos, int32_t distance); @@ -72,6 +75,8 @@ void Lara_SwimCollision(ITEM *item, COLL_INFO *coll); void Lara_WaterCurrent(COLL_INFO *coll); +void Lara_CatchFire(void); + void Lara_TouchLava(ITEM *item); // Returns true if Lara has the M16 equipped and is in either anim state: 0 diff --git a/src/tr2/game/level.c b/src/tr2/game/level.c index 2a128e8fe..1bdfffe7e 100644 --- a/src/tr2/game/level.c +++ b/src/tr2/game/level.c @@ -1,6 +1,7 @@ #include "game/level.h" #include "decomp/decomp.h" +#include "decomp/savegame.h" #include "game/camera.h" #include "game/effects.h" #include "game/game.h" @@ -14,21 +15,17 @@ #include "game/random.h" #include "game/render/common.h" #include "game/room.h" -#include "game/savegame.h" #include "game/shell.h" #include "game/sound.h" -#include "game/stats.h" #include "global/const.h" #include "global/vars.h" #include -#include #include #include #include #include #include -#include #include #include #include @@ -48,90 +45,6 @@ static int32_t M_CompareSampleOffsets(const void *a, const void *b); static void M_LoadFromFile(const GF_LEVEL *level); static void M_InitialiseSoundEffects(const char *const file_name); static void M_CompleteSetup(const GF_LEVEL *level); -static bool M_SkimLevel(VFILE *file); - -static bool M_SkimLevel(VFILE *const file) -{ -#define TRY_OR_FAIL(call) \ - if (!call) { \ - return false; \ - } -#define TRY_OR_FAIL_ARR_S32(size) \ - { \ - int32_t num; \ - TRY_OR_FAIL(VFile_TryReadS32(file, &num)); \ - TRY_OR_FAIL(VFile_TrySkip(file, num *size)); \ - } -#define TRY_OR_FAIL_ARR_U16(size) \ - { \ - uint16_t num; \ - TRY_OR_FAIL(VFile_TryReadU16(file, &num)); \ - TRY_OR_FAIL(VFile_TrySkip(file, num *size)); \ - } - - VFile_SetPos(file, 0); - - int32_t version; - TRY_OR_FAIL(VFile_TryReadS32(file, &version)); - if (version != 45) { - return false; - } - - TRY_OR_FAIL(VFile_TrySkip(file, 1792)); // palettes - TRY_OR_FAIL_ARR_S32(TEXTURE_PAGE_SIZE * 3); // texture pages - TRY_OR_FAIL(VFile_TrySkip(file, 4)); // unused version number - - uint16_t room_count; - TRY_OR_FAIL(VFile_TryReadU16(file, &room_count)); - for (int32_t i = 0; i < room_count; i++) { - TRY_OR_FAIL(VFile_TrySkip(file, 16)); - TRY_OR_FAIL_ARR_S32(2); // meshes - TRY_OR_FAIL_ARR_U16(32); // portals - - int16_t size_z; - int16_t size_x; - TRY_OR_FAIL(VFile_TryReadS16(file, &size_z)); - TRY_OR_FAIL(VFile_TryReadS16(file, &size_x)); - TRY_OR_FAIL(VFile_TrySkip(file, size_z * size_x * 8)); // sectors - - TRY_OR_FAIL(VFile_TrySkip(file, 6)); // lighting - TRY_OR_FAIL_ARR_U16(24); // lights - TRY_OR_FAIL_ARR_U16(20); // static meshes - TRY_OR_FAIL(VFile_TrySkip(file, 4)); - } - - TRY_OR_FAIL_ARR_S32(2); // floor data - TRY_OR_FAIL_ARR_S32(2); // object meshes - TRY_OR_FAIL_ARR_S32(4); // object mesh pointers - TRY_OR_FAIL_ARR_S32(32); // animations - TRY_OR_FAIL_ARR_S32(6); // animation changes - TRY_OR_FAIL_ARR_S32(8); // animation ranges - TRY_OR_FAIL_ARR_S32(2); // animation commands - TRY_OR_FAIL_ARR_S32(4); // animation bones - TRY_OR_FAIL_ARR_S32(2); // animation frames - TRY_OR_FAIL_ARR_S32(18); // objects - TRY_OR_FAIL_ARR_S32(32); // static objects - TRY_OR_FAIL_ARR_S32(20); // object textures - TRY_OR_FAIL_ARR_S32(16); // sprite textures - TRY_OR_FAIL_ARR_S32(8); // sprites sequences - TRY_OR_FAIL_ARR_S32(16); // cameras/sinks - TRY_OR_FAIL_ARR_S32(16); // sound sources - - int32_t box_count; - TRY_OR_FAIL(VFile_TryReadS32(file, &box_count)); - TRY_OR_FAIL(VFile_TrySkip(file, box_count * 8)); - TRY_OR_FAIL_ARR_S32(2); // overlaps - TRY_OR_FAIL(VFile_TrySkip(file, box_count * 20)); // zones - - TRY_OR_FAIL_ARR_S32(2); // animated texture ranges - Level_ReadItems(file); - -#undef TRY_OR_FAIL -#undef TRY_OR_FAIL_ARR_U16 -#undef TRY_OR_FAIL_ARR_S32 - - return true; -} static int32_t M_CompareSampleOffsets(const void *const a, const void *const b) { @@ -143,24 +56,22 @@ static int32_t M_CompareSampleOffsets(const void *const a, const void *const b) static void M_InitialiseSoundEffects(const char *file_name) { BENCHMARK benchmark = Benchmark_Start(); - LEVEL_INFO *info = nullptr; SAMPLE_ENTRY *entries = nullptr; - if (file_name == nullptr) { file_name = g_GameFlow.settings.sfx_path; } const char *full_path = File_GetFullPath(file_name == nullptr ? DEFAULT_SFX_PATH : file_name); LOG_DEBUG("Loading samples from %s", full_path); - MYFILE *const fp = File_Open(full_path, FILE_OPEN_READ); Memory_FreePointer(&full_path); + if (fp == nullptr) { Shell_ExitSystemFmt("Could not open %s file", file_name); goto finish; } - info = Level_GetInfo(); + LEVEL_INFO *const info = Level_GetInfo(); const int32_t sample_count = info->samples.offset_count; entries = Memory_Alloc(sizeof(SAMPLE_ENTRY) * sample_count); for (int32_t i = 0; i < sample_count; i++) { @@ -220,6 +131,7 @@ static void M_LoadFromFile(const GF_LEVEL *const level) BENCHMARK benchmark = Benchmark_Start(); const char *full_path = File_GetFullPath(level->path); + strcpy(g_LevelFileName, full_path); VFILE *const file = VFile_CreateFromPath(full_path); Memory_FreePointer(&full_path); @@ -279,7 +191,7 @@ static void M_CompleteSetup(const GF_LEVEL *const level) Level_LoadTexturePages(); Level_LoadPalettes(); Level_LoadFaces(); - Output_ObserveLevelLoad(); + Output_InitialiseNamedColors(); Render_Reset( RENDER_RESET_PALETTE | RENDER_RESET_TEXTURES | RENDER_RESET_UVS); @@ -296,7 +208,13 @@ bool Level_Load(const GF_LEVEL *const level) Audio_Sample_CloseAll(); Audio_Sample_UnloadAll(); - Object_Reset(); + for (int32_t i = 0; i < O_NUMBER_OF; i++) { + Object_Get(i)->loaded = false; + } + for (int32_t i = 0; i < MAX_STATIC_OBJECTS; i++) { + Object_Get2DStatic(i)->loaded = false; + Object_Get3DStatic(i)->loaded = false; + } Inject_InitLevel(level); @@ -320,7 +238,7 @@ bool Level_Initialise( } if (level->type != GFL_TITLE && level->type != GFL_DEMO) { - Gym_SetInventoryOpenEnabled(false); + g_GymInvOpenEnabled = false; } if (level->type != GFL_TITLE && level->type != GFL_CUTSCENE) { @@ -366,43 +284,21 @@ bool Level_Initialise( level->type == GFL_CUTSCENE ? MPM_ALWAYS : MPM_LOOPED); } - Gym_ResetAssault(); + g_IsAssaultTimerActive = false; + g_IsAssaultTimerDisplay = false; g_Camera.underwater = 0; return true; } void Level_Unload(void) { - Output_ObserveLevelUnload(); - Camera_Reset(); -} + strcpy(g_LevelFileName, ""); + Output_InitialiseTexturePages(0, true); + Output_InitialiseObjectTextures(0); -void Level_Init(void) -{ - BENCHMARK benchmark = Benchmark_Start(); - const GF_LEVEL_TABLE *const level_table = GF_GetLevelTable(GFLT_MAIN); - for (int32_t i = 0; i < level_table->count; i++) { - const GF_LEVEL *const level = GF_GetLevel(GFLT_MAIN, i); - if (level->type != GFL_NORMAL && level->type != GFL_BONUS) { - continue; - } - - VFILE *const file = VFile_CreateFromPath(level->path); - if (file == nullptr) { - continue; - } - - const bool result = M_SkimLevel(file); - VFile_Close(file); - - if (result) { - Stats_CalculateStats(); - STATS_COMMON stats = {}; - stats.max_secret_count = Stats_GetSecrets(); - Savegame_SetDefaultStats(level, stats); - } - GameBuf_Reset(); + if (Output_GetBackgroundType() == BK_OBJECT) { + Output_UnloadBackground(); } - Benchmark_End(&benchmark, nullptr); + Camera_Reset(); } diff --git a/src/tr2/game/level.h b/src/tr2/game/level.h index 8ab62b911..5a6825b92 100644 --- a/src/tr2/game/level.h +++ b/src/tr2/game/level.h @@ -2,9 +2,8 @@ #include "game/game_flow/types.h" -#include +#include -void Level_Init(void); bool Level_Initialise(const GF_LEVEL *level, GF_SEQUENCE_CONTEXT seq_ctx); bool Level_Load(const GF_LEVEL *level); void Level_Unload(void); diff --git a/src/tr2/game/los.c b/src/tr2/game/los.c index caba2eb80..12d38ffa4 100644 --- a/src/tr2/game/los.c +++ b/src/tr2/game/los.c @@ -253,7 +253,7 @@ int32_t LOS_ClipTarget( return 1; } -bool LOS_Check(const GAME_VECTOR *const start, GAME_VECTOR *const target) +int32_t LOS_Check(const GAME_VECTOR *const start, GAME_VECTOR *const target) { int32_t los1; int32_t los2; @@ -270,7 +270,7 @@ bool LOS_Check(const GAME_VECTOR *const start, GAME_VECTOR *const target) } if (!los2) { - return false; + return 0; } if (dx == 0 && dz == 0) { @@ -281,12 +281,12 @@ bool LOS_Check(const GAME_VECTOR *const start, GAME_VECTOR *const target) Room_GetSector(target->x, target->y, target->z, &target->room_num); if (!LOS_ClipTarget(start, target, sector)) { - return false; + return 0; } if (los1 == 1 && los2 == 1) { - return true; + return 1; } - return false; + return 0; } int32_t LOS_CheckSmashable( diff --git a/src/tr2/game/los.h b/src/tr2/game/los.h index 23d48a723..7db997536 100644 --- a/src/tr2/game/los.h +++ b/src/tr2/game/los.h @@ -1,10 +1,10 @@ #pragma once -#include -#include +#include "global/types.h" int32_t LOS_CheckX(const GAME_VECTOR *start, GAME_VECTOR *target); int32_t LOS_CheckZ(const GAME_VECTOR *start, GAME_VECTOR *target); int32_t LOS_ClipTarget( const GAME_VECTOR *start, GAME_VECTOR *target, const SECTOR *sector); +int32_t LOS_Check(const GAME_VECTOR *start, GAME_VECTOR *target); int32_t LOS_CheckSmashable(const GAME_VECTOR *start, const GAME_VECTOR *target); diff --git a/src/tr2/game/lot.c b/src/tr2/game/lot.c index 9a6e16e97..5ff0cc34b 100644 --- a/src/tr2/game/lot.c +++ b/src/tr2/game/lot.c @@ -10,13 +10,12 @@ #include static int32_t m_SlotsUsed = 0; - void LOT_InitialiseArray(void) { g_BaddieSlots = - GameBuf_Alloc(LOT_SLOT_COUNT * sizeof(CREATURE), GBUF_CREATURE_DATA); + GameBuf_Alloc(NUM_SLOTS * sizeof(CREATURE), GBUF_CREATURE_DATA); - for (int32_t i = 0; i < LOT_SLOT_COUNT; i++) { + for (int32_t i = 0; i < NUM_SLOTS; i++) { CREATURE *const creature = &g_BaddieSlots[i]; creature->item_num = NO_ITEM; creature->lot.node = @@ -26,11 +25,6 @@ void LOT_InitialiseArray(void) m_SlotsUsed = 0; } -CREATURE *LOT_GetBaddieSlot(const int32_t i) -{ - return &g_BaddieSlots[i]; -} - void LOT_DisableBaddieAI(const int16_t item_num) { CREATURE *creature; @@ -60,8 +54,8 @@ bool LOT_EnableBaddieAI(const int16_t item_num, const bool always) return true; } - if (m_SlotsUsed < LOT_SLOT_COUNT) { - for (int32_t slot = 0; slot < LOT_SLOT_COUNT; slot++) { + if (m_SlotsUsed < NUM_SLOTS) { + for (int32_t slot = 0; slot < NUM_SLOTS; slot++) { if (g_BaddieSlots[slot].item_num == NO_ITEM) { LOT_InitialiseSlot(item_num, slot); return true; @@ -80,7 +74,7 @@ bool LOT_EnableBaddieAI(const int16_t item_num, const bool always) } int32_t worst_slot = -1; - for (int32_t slot = 0; slot < LOT_SLOT_COUNT; slot++) { + for (int32_t slot = 0; slot < NUM_SLOTS; slot++) { const int32_t item_num = g_BaddieSlots[slot].item_num; const ITEM *const item = Item_Get(item_num); const int32_t dx = (item->pos.x - g_Camera.pos.pos.x) >> 8; diff --git a/src/tr2/game/lot.h b/src/tr2/game/lot.h index 1976b2cf7..6c2575f6c 100644 --- a/src/tr2/game/lot.h +++ b/src/tr2/game/lot.h @@ -2,8 +2,9 @@ #include "global/types.h" -#include - void LOT_InitialiseArray(void); +void LOT_DisableBaddieAI(int16_t item_num); +bool LOT_EnableBaddieAI(int16_t item_num, bool always); void LOT_InitialiseSlot(int16_t item_num, int32_t slot); void LOT_CreateZone(ITEM *item); +void LOT_ClearLOT(LOT_INFO *LOT); diff --git a/src/tr2/game/objects/common.c b/src/tr2/game/objects/common.c index 20ecc6366..8c3688289 100644 --- a/src/tr2/game/objects/common.c +++ b/src/tr2/game/objects/common.c @@ -1,13 +1,13 @@ #include "game/objects/common.h" #include "game/items.h" +#include "game/lara/misc.h" #include "game/output.h" #include "game/room.h" #include "game/viewport.h" #include "global/vars.h" #include -#include #include #include @@ -62,12 +62,8 @@ void Object_DrawUnclippedItem(const ITEM *const item) void Object_DrawSpriteItem(const ITEM *const item) { - SHADE shade = item->shade; - if (shade.value_1 < 0) { - shade.value_1 = SHADE_NEUTRAL; - } Output_CalculateStaticMeshLight( - item->interp.result.pos, shade, Room_Get(item->room_num)); + item->interp.result.pos, item->shade, Room_Get(item->room_num)); const OBJECT *const obj = Object_Get(item->object_id); @@ -76,7 +72,7 @@ void Object_DrawSpriteItem(const ITEM *const item) | SPRITE_SHADE, item->interp.result.pos.x, item->interp.result.pos.y, item->interp.result.pos.z, obj->mesh_idx - item->frame_num, - Output_GetLightAdder() + SHADE_NEUTRAL, 0); + Output_GetLightAdder() + 4096, 0); } void Object_Collision( @@ -93,7 +89,7 @@ void Object_Collision( } if (coll->enable_baddie_push) { - Lara_Push(item, coll, false, true); + Lara_Push(item, lara_item, coll, false, true); } } diff --git a/src/tr2/game/objects/common.h b/src/tr2/game/objects/common.h index 65911e138..15a9e8cc0 100644 --- a/src/tr2/game/objects/common.h +++ b/src/tr2/game/objects/common.h @@ -6,6 +6,7 @@ void Object_DrawDummyItem(const ITEM *item); void Object_DrawAnimatingItem(const ITEM *item); +void Object_DrawUnclippedItem(const ITEM *item); void Object_DrawSpriteItem(const ITEM *item); void Object_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); diff --git a/src/tr2/game/objects/creatures/bandit_1.c b/src/tr2/game/objects/creatures/bandit_1.c index 5ffbd2b9e..9318aefe5 100644 --- a/src/tr2/game/objects/creatures/bandit_1.c +++ b/src/tr2/game/objects/creatures/bandit_1.c @@ -70,8 +70,8 @@ static void M_Setup(OBJECT *const obj) obj->save_flags = 1; obj->save_anim = 1; - Object_GetBone(obj, 6)->rot.y = true; - Object_GetBone(obj, 8)->rot.y = true; + Object_GetBone(obj, 6)->rot_y = true; + Object_GetBone(obj, 8)->rot_y = true; } static void M_Control(const int16_t item_num) diff --git a/src/tr2/game/objects/creatures/bandit_2.c b/src/tr2/game/objects/creatures/bandit_2.c index 998d03a15..db697e98e 100644 --- a/src/tr2/game/objects/creatures/bandit_2.c +++ b/src/tr2/game/objects/creatures/bandit_2.c @@ -71,8 +71,8 @@ static void M_Setup2A(OBJECT *const obj) obj->save_flags = 1; obj->save_anim = 1; - Object_GetBone(obj, 6)->rot.y = true; - Object_GetBone(obj, 8)->rot.y = true; + Object_GetBone(obj, 6)->rot_y = true; + Object_GetBone(obj, 8)->rot_y = true; } static void M_Setup2B(OBJECT *const obj) @@ -100,8 +100,8 @@ static void M_Setup2B(OBJECT *const obj) obj->save_flags = 1; obj->save_anim = 1; - Object_GetBone(obj, 6)->rot.y = true; - Object_GetBone(obj, 8)->rot.y = true; + Object_GetBone(obj, 6)->rot_y = true; + Object_GetBone(obj, 8)->rot_y = true; } static void M_Control(const int16_t item_num) diff --git a/src/tr2/game/objects/creatures/barracuda.c b/src/tr2/game/objects/creatures/barracuda.c index 7899e8629..b49fd9ee8 100644 --- a/src/tr2/game/objects/creatures/barracuda.c +++ b/src/tr2/game/objects/creatures/barracuda.c @@ -61,7 +61,7 @@ static void M_Setup(OBJECT *const obj) obj->save_flags = 1; obj->save_anim = 1; - Object_GetBone(obj, 6)->rot.y = true; + Object_GetBone(obj, 6)->rot_y = true; } static void M_Control(const int16_t item_num) diff --git a/src/tr2/game/objects/creatures/big_spider.c b/src/tr2/game/objects/creatures/big_spider.c index 9309143ca..a96bfc9f5 100644 --- a/src/tr2/game/objects/creatures/big_spider.c +++ b/src/tr2/game/objects/creatures/big_spider.c @@ -1,5 +1,3 @@ -#include "game/objects/creatures/big_spider.h" - #include "game/creature.h" #include "game/lara/control.h" #include "game/objects/common.h" @@ -35,8 +33,29 @@ static const BITE m_SpiderBite = { .mesh_num = 1, }; +static void M_Setup(OBJECT *obj); static void M_Control(int16_t item_num); +static void M_Setup(OBJECT *const obj) +{ + if (!obj->loaded) { + return; + } + + obj->control_func = M_Control; + obj->collision_func = Creature_Collision; + + obj->hit_points = BIG_SPIDER_HITPOINTS; + obj->radius = BIG_SPIDER_RADIUS; + obj->shadow_size = UNIT_SHADOW / 2; + + obj->intelligent = 1; + obj->save_position = 1; + obj->save_hitpoints = 1; + obj->save_flags = 1; + obj->save_anim = 1; +} + static void M_Control(const int16_t item_num) { if (!Creature_Activate(item_num)) { @@ -113,24 +132,4 @@ static void M_Control(const int16_t item_num) Creature_Animate(item_num, angle, tilt); } -void BigSpider_Setup(OBJECT *const obj) -{ - if (!obj->loaded) { - return; - } - - obj->control_func = M_Control; - obj->collision_func = Creature_Collision; - - obj->hit_points = BIG_SPIDER_HITPOINTS; - obj->radius = BIG_SPIDER_RADIUS; - obj->shadow_size = UNIT_SHADOW / 2; - - obj->intelligent = 1; - obj->save_position = 1; - obj->save_hitpoints = 1; - obj->save_flags = 1; - obj->save_anim = 1; -} - -REGISTER_OBJECT(O_BIG_SPIDER, BigSpider_Setup) +REGISTER_OBJECT(O_BIG_SPIDER, M_Setup) diff --git a/src/tr2/game/objects/creatures/big_spider.h b/src/tr2/game/objects/creatures/big_spider.h deleted file mode 100644 index 5f5b7ae25..000000000 --- a/src/tr2/game/objects/creatures/big_spider.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#include - -void BigSpider_Setup(OBJECT *obj); diff --git a/src/tr2/game/objects/creatures/bird_guardian.c b/src/tr2/game/objects/creatures/bird_guardian.c index 1d202c7f1..8db943b54 100644 --- a/src/tr2/game/objects/creatures/bird_guardian.c +++ b/src/tr2/game/objects/creatures/bird_guardian.c @@ -6,7 +6,6 @@ #include "global/const.h" #include "global/vars.h" -#include #include // clang-format off @@ -18,6 +17,7 @@ #define BIRD_GUARDIAN_ATTACK_1_RANGE SQUARE(WALL_L) // = 1048576 #define BIRD_GUARDIAN_ATTACK_2_RANGE SQUARE(WALL_L * 2) // = 4194304 #define BIRD_GUARDIAN_PUNCH_DAMAGE 200 +#define BIRD_GUARDIAN_DEATH_FRAME 158 // clang-format on typedef enum { @@ -65,9 +65,6 @@ static void M_Setup(OBJECT *const obj) obj->hit_points = BIRD_GUARDIAN_HITPOINTS; obj->radius = BIRD_GUARDIAN_RADIUS; - if (g_Config.visuals.fix_texture_issues) { - obj->shadow_size = UNIT_SHADOW / 2; - } obj->intelligent = 1; obj->save_position = 1; @@ -75,7 +72,7 @@ static void M_Setup(OBJECT *const obj) obj->save_flags = 1; obj->save_anim = 1; - Object_GetBone(obj, 14)->rot.y = true; + Object_GetBone(obj, 14)->rot_y = true; } static void M_Control(const int16_t item_num) @@ -190,9 +187,15 @@ static void M_Control(const int16_t item_num) default: break; } - } else if (item->current_anim_state != BIRD_GUARDIAN_STATE_DEATH) { - Item_SwitchToAnim(item, BIRD_GUARDIAN_ANIM_DEATH, 0); - item->current_anim_state = BIRD_GUARDIAN_STATE_DEATH; + } else { + if (item->current_anim_state != BIRD_GUARDIAN_STATE_DEATH) { + Item_SwitchToAnim(item, BIRD_GUARDIAN_ANIM_DEATH, 0); + item->current_anim_state = BIRD_GUARDIAN_STATE_DEATH; + } + + if (Item_TestFrameEqual(item, BIRD_GUARDIAN_DEATH_FRAME)) { + g_LevelComplete = true; + } } Creature_Head(item, head); diff --git a/src/tr2/game/objects/creatures/cultist_1.c b/src/tr2/game/objects/creatures/cultist_1.c index 0907c8bf7..a48de2dc6 100644 --- a/src/tr2/game/objects/creatures/cultist_1.c +++ b/src/tr2/game/objects/creatures/cultist_1.c @@ -72,7 +72,7 @@ static void M_Setup1(OBJECT *const obj) obj->save_flags = 1; obj->save_anim = 1; - Object_GetBone(obj, 0)->rot.y = true; + Object_GetBone(obj, 0)->rot_y = true; } static void M_Setup1A(OBJECT *const obj) @@ -101,7 +101,7 @@ static void M_Setup1A(OBJECT *const obj) obj->save_flags = 1; obj->save_anim = 1; - Object_GetBone(obj, 0)->rot.y = true; + Object_GetBone(obj, 0)->rot_y = true; } static void M_Setup1B(OBJECT *const obj) @@ -130,7 +130,7 @@ static void M_Setup1B(OBJECT *const obj) obj->save_flags = 1; obj->save_anim = 1; - Object_GetBone(obj, 0)->rot.y = true; + Object_GetBone(obj, 0)->rot_y = true; } static void M_Initialise(const int16_t item_num) diff --git a/src/tr2/game/objects/creatures/cultist_2.c b/src/tr2/game/objects/creatures/cultist_2.c index 15d827265..6eedc04a4 100644 --- a/src/tr2/game/objects/creatures/cultist_2.c +++ b/src/tr2/game/objects/creatures/cultist_2.c @@ -68,8 +68,8 @@ static void M_Setup(OBJECT *const obj) obj->save_flags = 1; obj->save_anim = 1; - Object_GetBone(obj, 0)->rot.y = true; - Object_GetBone(obj, 8)->rot.y = true; + Object_GetBone(obj, 0)->rot_y = true; + Object_GetBone(obj, 8)->rot_y = true; } static void M_Control(const int16_t item_num) diff --git a/src/tr2/game/objects/creatures/cultist_3.c b/src/tr2/game/objects/creatures/cultist_3.c index 733f8e2da..ea32c5059 100644 --- a/src/tr2/game/objects/creatures/cultist_3.c +++ b/src/tr2/game/objects/creatures/cultist_3.c @@ -276,10 +276,10 @@ static void M_Control(const int16_t item_num) Creature_Tilt(item, tilt); const OBJECT *const obj = Object_Get(item->object_id); - Object_GetBone(obj, 0)->rot.y = body != 0; - Object_GetBone(obj, 2)->rot.y = left != 0; - Object_GetBone(obj, 6)->rot.y = right != 0; - Object_GetBone(obj, 10)->rot.y = head != 0; + Object_GetBone(obj, 0)->rot_y = body != 0; + Object_GetBone(obj, 2)->rot_y = left != 0; + Object_GetBone(obj, 6)->rot_y = right != 0; + Object_GetBone(obj, 10)->rot_y = head != 0; if (body != 0) { Creature_Head(item, body); diff --git a/src/tr2/game/objects/creatures/diver.c b/src/tr2/game/objects/creatures/diver.c index 6145144a1..6cf5eec1a 100644 --- a/src/tr2/game/objects/creatures/diver.c +++ b/src/tr2/game/objects/creatures/diver.c @@ -115,8 +115,8 @@ static void M_Setup(OBJECT *const obj) obj->save_flags = 1; obj->save_anim = 1; - Object_GetBone(obj, 10)->rot.y = true; - Object_GetBone(obj, 14)->rot.z = true; + Object_GetBone(obj, 10)->rot_y = true; + Object_GetBone(obj, 14)->rot_z = true; } static void M_Control(const int16_t item_num) diff --git a/src/tr2/game/objects/creatures/dog.c b/src/tr2/game/objects/creatures/dog.c index 1b22857e1..9917e9fb6 100644 --- a/src/tr2/game/objects/creatures/dog.c +++ b/src/tr2/game/objects/creatures/dog.c @@ -77,7 +77,7 @@ static void M_Setup(OBJECT *const obj) obj->save_flags = 1; obj->save_anim = 1; - Object_GetBone(obj, 19)->rot.y = true; + Object_GetBone(obj, 19)->rot_y = true; } static void M_Control(const int16_t item_num) diff --git a/src/tr2/game/objects/creatures/dragon.c b/src/tr2/game/objects/creatures/dragon.c index 15bf0d611..bc09a2549 100644 --- a/src/tr2/game/objects/creatures/dragon.c +++ b/src/tr2/game/objects/creatures/dragon.c @@ -2,19 +2,18 @@ #include "game/creature.h" #include "game/input.h" #include "game/lara/control.h" +#include "game/lara/misc.h" #include "game/lot.h" #include "game/objects/common.h" #include "game/output.h" #include "game/random.h" #include "game/sound.h" #include "game/spawn.h" -#include "game/stats.h" #include "global/const.h" #include "global/vars.h" #include #include -#include #include // clang-format off @@ -82,7 +81,7 @@ static void M_MarkDragonDead(const ITEM *const dragon_back_item) const ITEM *const dragon_front_item = Item_Get(dragon_front_item_num); CREATURE *const creature = dragon_front_item->data; creature->flags = -1; - Stats_AddKill(); + g_SaveGame.current_stats.kills++; } static void M_PushLaraAway( @@ -185,7 +184,7 @@ static void M_SetupFront(OBJECT *const obj) obj->save_flags = 1; obj->save_anim = 1; - Object_GetBone(obj, 10)->rot.z = true; + Object_GetBone(obj, 10)->rot_z = true; } static void M_SetupBack(OBJECT *const obj) @@ -229,7 +228,7 @@ static void M_Collision( } if (item->current_anim_state != DRAGON_STATE_DEATH) { - Lara_Push(item, coll, true, false); + Lara_Push(item, lara_item, coll, true, false); return; } @@ -240,7 +239,7 @@ static void M_Collision( const int32_t side_shift = (cy * dz + sy * dx) >> W2V_SHIFT; if (side_shift <= DRAGON_L_COL || side_shift >= DRAGON_R_COL) { - Lara_Push(item, coll, true, false); + Lara_Push(item, lara_item, coll, true, false); return; } diff --git a/src/tr2/game/objects/creatures/monk.c b/src/tr2/game/objects/creatures/monk.c index c74158323..8919ef4e0 100644 --- a/src/tr2/game/objects/creatures/monk.c +++ b/src/tr2/game/objects/creatures/monk.c @@ -52,6 +52,7 @@ static const BITE m_MonkHit = { }; static void M_SetupBase(OBJECT *obj); +static void M_Setup1(OBJECT *obj); static void M_Setup2(OBJECT *obj); static void M_Control(int16_t item_num); @@ -70,7 +71,16 @@ static void M_SetupBase(OBJECT *const obj) obj->save_flags = 1; obj->save_anim = 1; - Object_GetBone(obj, 6)->rot.y = true; + Object_GetBone(obj, 6)->rot_y = true; +} + +static void M_Setup1(OBJECT *const obj) +{ + if (!obj->loaded) { + return; + } + M_SetupBase(obj); + obj->pivot_length = 0; } static void M_Setup2(OBJECT *const obj) @@ -113,8 +123,7 @@ static void M_Control(const int16_t item_num) switch (item->current_anim_state) { case MONK_STATE_WAIT_1: creature->flags &= 0xFFF; - if (!Creature_AreAlliesHostile() && info.ahead != 0 - && g_Lara.target == item) { + if (!g_IsMonkAngry && info.ahead != 0 && g_Lara.target == item) { } else if (creature->mood == MOOD_BORED) { item->goal_anim_state = MONK_STATE_WALK; } else if (creature->mood == MOOD_ESCAPE) { @@ -138,8 +147,7 @@ static void M_Control(const int16_t item_num) case MONK_STATE_WAIT_2: creature->flags &= 0xFFF; - if (!Creature_AreAlliesHostile() && info.ahead != 0 - && g_Lara.target == item) { + if (!g_IsMonkAngry && info.ahead != 0 && g_Lara.target == item) { } else if (creature->mood == MOOD_BORED) { item->goal_anim_state = MONK_STATE_WALK; } else if (creature->mood == MOOD_ESCAPE) { @@ -163,7 +171,7 @@ static void M_Control(const int16_t item_num) case MONK_STATE_WALK: creature->maximum_turn = MONK_WALK_TURN; if (creature->mood == MOOD_BORED) { - if (!Creature_AreAlliesHostile() && info.ahead != 0 + if (!g_IsMonkAngry && info.ahead != 0 && g_Lara.target == item) { if (Random_GetControl() < 0x4000) { item->goal_anim_state = MONK_STATE_WAIT_1; @@ -187,7 +195,7 @@ static void M_Control(const int16_t item_num) case MONK_STATE_RUN: creature->flags &= 0xFFF; creature->maximum_turn = MONK_RUN_TURN; - if (Creature_AreAlliesHostile()) { + if (g_IsMonkAngry) { creature->maximum_turn = MONK_RUN_TURN_FAST; } tilt = angle / 4; @@ -251,25 +259,5 @@ static void M_Control(const int16_t item_num) Creature_Animate(item_num, angle, 0); } -void Monk1_Setup(OBJECT *const obj) -{ - if (!obj->loaded) { - return; - } - M_SetupBase(obj); - obj->pivot_length = 0; -} - -void Monk3_Setup(OBJECT *const obj) -{ - if (!obj->loaded) { - return; - } - M_SetupBase(obj); - obj->pivot_length = 0; - obj->shadow_size = 0; -} - -REGISTER_OBJECT(O_MONK_1, Monk1_Setup) +REGISTER_OBJECT(O_MONK_1, M_Setup1) REGISTER_OBJECT(O_MONK_2, M_Setup2) -REGISTER_OBJECT(O_MONK_3, Monk3_Setup) diff --git a/src/tr2/game/objects/creatures/monk.h b/src/tr2/game/objects/creatures/monk.h deleted file mode 100644 index 8d491dd73..000000000 --- a/src/tr2/game/objects/creatures/monk.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -#include - -void Monk1_Setup(OBJECT *obj); -void Monk3_Setup(OBJECT *obj); diff --git a/src/tr2/game/objects/creatures/mouse.c b/src/tr2/game/objects/creatures/mouse.c index 330968dc7..009f9db05 100644 --- a/src/tr2/game/objects/creatures/mouse.c +++ b/src/tr2/game/objects/creatures/mouse.c @@ -63,7 +63,7 @@ static void M_Setup(OBJECT *const obj) obj->save_flags = 1; obj->save_anim = 1; - Object_GetBone(obj, 3)->rot.y = true; + Object_GetBone(obj, 3)->rot_y = true; } static void M_Control(const int16_t item_num) diff --git a/src/tr2/game/objects/creatures/shark.c b/src/tr2/game/objects/creatures/shark.c index c9ef7c129..530567ef2 100644 --- a/src/tr2/game/objects/creatures/shark.c +++ b/src/tr2/game/objects/creatures/shark.c @@ -70,7 +70,7 @@ static void M_Setup(OBJECT *const obj) obj->save_flags = 1; obj->save_anim = 1; - Object_GetBone(obj, 9)->rot.y = true; + Object_GetBone(obj, 9)->rot_y = true; } static void M_Control(const int16_t item_num) @@ -159,7 +159,7 @@ static void M_Control(const int16_t item_num) } if (lara_alive && g_LaraItem->hit_points <= 0) { - Creature_SpecialKill( + Creature_Kill( item, SHARK_ANIM_KILL, SHARK_STATE_KILL, LA_EXTRA_SHARK_KILL); } else if (item->current_anim_state == SHARK_STATE_KILL) { Item_Animate(item); diff --git a/src/tr2/game/objects/creatures/spider.c b/src/tr2/game/objects/creatures/spider.c index 570f62d8b..b56dab097 100644 --- a/src/tr2/game/objects/creatures/spider.c +++ b/src/tr2/game/objects/creatures/spider.c @@ -1,5 +1,3 @@ -#include "game/objects/creatures/spider.h" - #include "game/creature.h" #include "game/items.h" #include "game/lara/control.h" @@ -68,6 +66,26 @@ static void M_Leap(const int16_t item_num, const int16_t angle) Creature_Animate(item_num, angle, 0); } +static void M_Setup(OBJECT *const obj) +{ + if (!obj->loaded) { + return; + } + + obj->control_func = M_Control; + obj->collision_func = Creature_Collision; + + obj->hit_points = SPIDER_HITPOINTS; + obj->radius = SPIDER_RADIUS; + obj->shadow_size = UNIT_SHADOW / 2; + + obj->intelligent = 1; + obj->save_position = 1; + obj->save_hitpoints = 1; + obj->save_flags = 1; + obj->save_anim = 1; +} + static void M_Control(const int16_t item_num) { if (!Creature_Activate(item_num)) { @@ -154,24 +172,4 @@ static void M_Control(const int16_t item_num) M_Leap(item_num, angle); } -void Spider_Setup(OBJECT *const obj) -{ - if (!obj->loaded) { - return; - } - - obj->control_func = M_Control; - obj->collision_func = Creature_Collision; - - obj->hit_points = SPIDER_HITPOINTS; - obj->radius = SPIDER_RADIUS; - obj->shadow_size = UNIT_SHADOW / 2; - - obj->intelligent = 1; - obj->save_position = 1; - obj->save_hitpoints = 1; - obj->save_flags = 1; - obj->save_anim = 1; -} - -REGISTER_OBJECT(O_SPIDER, Spider_Setup) +REGISTER_OBJECT(O_SPIDER, M_Setup) diff --git a/src/tr2/game/objects/creatures/spider.h b/src/tr2/game/objects/creatures/spider.h deleted file mode 100644 index d2e20b05e..000000000 --- a/src/tr2/game/objects/creatures/spider.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#include - -void Spider_Setup(OBJECT *obj); diff --git a/src/tr2/game/objects/creatures/tiger.c b/src/tr2/game/objects/creatures/tiger.c index 9c2d87102..b10e2a664 100644 --- a/src/tr2/game/objects/creatures/tiger.c +++ b/src/tr2/game/objects/creatures/tiger.c @@ -68,7 +68,7 @@ static void M_Setup(OBJECT *const obj) obj->save_flags = 1; obj->save_anim = 1; - Object_GetBone(obj, 21)->rot.y = true; + Object_GetBone(obj, 21)->rot_y = true; } static void M_Control(const int16_t item_num) diff --git a/src/tr2/game/objects/creatures/trex.c b/src/tr2/game/objects/creatures/trex.c index 294818b9d..ad05df7db 100644 --- a/src/tr2/game/objects/creatures/trex.c +++ b/src/tr2/game/objects/creatures/trex.c @@ -63,8 +63,8 @@ static void M_Setup(OBJECT *const obj) obj->save_flags = 1; obj->save_anim = 1; - Object_GetBone(obj, 10)->rot.y = true; - Object_GetBone(obj, 11)->rot.y = true; + Object_GetBone(obj, 10)->rot_y = true; + Object_GetBone(obj, 11)->rot_y = true; } static void M_Control(const int16_t item_num) @@ -147,7 +147,7 @@ static void M_Control(const int16_t item_num) case TREX_STATE_ATTACK_2: if ((item->touch_bits & TREX_TOUCH_BITS) != 0) { Lara_TakeDamage(TREX_BITE_DAMAGE, true); - Creature_SpecialKill( + Creature_Kill( item, TREX_ANIM_KILL, TREX_STATE_KILL, LA_EXTRA_TREX_KILL); return; } diff --git a/src/tr2/game/objects/creatures/worker_1.c b/src/tr2/game/objects/creatures/worker_1.c index 3055d79a4..c52399904 100644 --- a/src/tr2/game/objects/creatures/worker_1.c +++ b/src/tr2/game/objects/creatures/worker_1.c @@ -65,8 +65,8 @@ static void M_Setup(OBJECT *const obj) obj->save_flags = 1; obj->save_anim = 1; - Object_GetBone(obj, 4)->rot.y = true; - Object_GetBone(obj, 13)->rot.y = true; + Object_GetBone(obj, 4)->rot_y = true; + Object_GetBone(obj, 13)->rot_y = true; } static void M_Control(const int16_t item_num) diff --git a/src/tr2/game/objects/creatures/worker_2.c b/src/tr2/game/objects/creatures/worker_2.c index 9a01abbfa..6224f13b4 100644 --- a/src/tr2/game/objects/creatures/worker_2.c +++ b/src/tr2/game/objects/creatures/worker_2.c @@ -88,8 +88,8 @@ static void M_Setup(OBJECT *const obj) obj->save_flags = 1; obj->save_anim = 1; - Object_GetBone(obj, 4)->rot.y = true; - Object_GetBone(obj, 13)->rot.y = true; + Object_GetBone(obj, 4)->rot_y = true; + Object_GetBone(obj, 13)->rot_y = true; } static void M_Setup5(OBJECT *const obj) @@ -112,8 +112,8 @@ static void M_Setup5(OBJECT *const obj) obj->save_flags = 1; obj->save_anim = 1; - Object_GetBone(obj, 4)->rot.y = true; - Object_GetBone(obj, 13)->rot.y = true; + Object_GetBone(obj, 4)->rot_y = true; + Object_GetBone(obj, 13)->rot_y = true; } static void M_Control(const int16_t item_num) diff --git a/src/tr2/game/objects/creatures/worker_3.c b/src/tr2/game/objects/creatures/worker_3.c index 08aa83e25..a5b0158e1 100644 --- a/src/tr2/game/objects/creatures/worker_3.c +++ b/src/tr2/game/objects/creatures/worker_3.c @@ -83,8 +83,8 @@ static void M_SetupBase(OBJECT *const obj) obj->save_flags = 1; obj->save_anim = 1; - Object_GetBone(obj, 0)->rot.y = true; - Object_GetBone(obj, 4)->rot.y = true; + Object_GetBone(obj, 0)->rot_y = true; + Object_GetBone(obj, 4)->rot_y = true; } static void M_Setup3(OBJECT *const obj) diff --git a/src/tr2/game/objects/creatures/xian_common.c b/src/tr2/game/objects/creatures/xian_common.c index c55296922..969fe5d94 100644 --- a/src/tr2/game/objects/creatures/xian_common.c +++ b/src/tr2/game/objects/creatures/xian_common.c @@ -48,7 +48,6 @@ void XianWarrior_Draw(const ITEM *item) Matrix_Rot16_ID( frames[0]->mesh_rots[mesh_idx], frames[1]->mesh_rots[mesh_idx]); - Object_ApplyExtraRotation(&extra_rotation, obj->base_rot, true); } else { const ANIM_BONE *const bone = Object_GetBone(obj, mesh_idx - 1); if (bone->matrix_pop) { @@ -62,7 +61,17 @@ void XianWarrior_Draw(const ITEM *item) Matrix_Rot16_ID( frames[0]->mesh_rots[mesh_idx], frames[1]->mesh_rots[mesh_idx]); - Object_ApplyExtraRotation(&extra_rotation, bone->rot, true); + if (extra_rotation != nullptr) { + if (bone->rot_y) { + Matrix_RotY_I(*extra_rotation++); + } + if (bone->rot_x) { + Matrix_RotX_I(*extra_rotation++); + } + if (bone->rot_z) { + Matrix_RotZ_I(*extra_rotation++); + } + } } if (item->mesh_bits & (1 << mesh_idx)) { @@ -76,8 +85,6 @@ void XianWarrior_Draw(const ITEM *item) if (mesh_idx == 0) { Matrix_TranslateRel16(frames[0]->offset); Matrix_Rot16(frames[0]->mesh_rots[mesh_idx]); - Object_ApplyExtraRotation( - &extra_rotation, obj->base_rot, false); } else { const ANIM_BONE *const bone = Object_GetBone(obj, mesh_idx - 1); if (bone->matrix_pop) { @@ -89,7 +96,17 @@ void XianWarrior_Draw(const ITEM *item) Matrix_TranslateRel32(bone->pos); Matrix_Rot16(frames[0]->mesh_rots[mesh_idx]); - Object_ApplyExtraRotation(&extra_rotation, bone->rot, false); + if (extra_rotation != nullptr) { + if (bone->rot_y) { + Matrix_RotY(*extra_rotation++); + } + if (bone->rot_x) { + Matrix_RotX(*extra_rotation++); + } + if (bone->rot_z) { + Matrix_RotZ(*extra_rotation++); + } + } } if (item->mesh_bits & (1 << mesh_idx)) { diff --git a/src/tr2/game/objects/creatures/xian_knight.c b/src/tr2/game/objects/creatures/xian_knight.c index 7ba224e1d..c4adc25bd 100644 --- a/src/tr2/game/objects/creatures/xian_knight.c +++ b/src/tr2/game/objects/creatures/xian_knight.c @@ -101,8 +101,8 @@ static void M_Setup(OBJECT *const obj) obj->save_flags = 1; obj->save_anim = 1; - Object_GetBone(obj, 6)->rot.y = true; - Object_GetBone(obj, 16)->rot.y = true; + Object_GetBone(obj, 6)->rot_y = true; + Object_GetBone(obj, 16)->rot_y = true; } static void M_Control(const int16_t item_num) diff --git a/src/tr2/game/objects/creatures/xian_spearman.c b/src/tr2/game/objects/creatures/xian_spearman.c index aef49992e..ca9902dd7 100644 --- a/src/tr2/game/objects/creatures/xian_spearman.c +++ b/src/tr2/game/objects/creatures/xian_spearman.c @@ -127,8 +127,8 @@ static void M_Setup(OBJECT *const obj) obj->save_flags = 1; obj->save_anim = 1; - Object_GetBone(obj, 6)->rot.y = true; - Object_GetBone(obj, 12)->rot.y = true; + Object_GetBone(obj, 6)->rot_y = true; + Object_GetBone(obj, 12)->rot_y = true; } static void M_Initialise(const int16_t item_num) @@ -409,7 +409,7 @@ static void M_Control(const int16_t item_num) } if (lara_alive && g_LaraItem->hit_points <= 0) { - Creature_SpecialKill( + Creature_Kill( item, XIAN_SPEARMAN_ANIM_KILL, XIAN_SPEARMAN_STATE_KILL, LA_EXTRA_YETI_KILL); return; diff --git a/src/tr2/game/objects/creatures/yeti.c b/src/tr2/game/objects/creatures/yeti.c index fa5e67c17..92962c75b 100644 --- a/src/tr2/game/objects/creatures/yeti.c +++ b/src/tr2/game/objects/creatures/yeti.c @@ -91,8 +91,8 @@ static void M_Setup(OBJECT *const obj) obj->save_flags = 1; obj->save_anim = 1; - Object_GetBone(obj, 6)->rot.y = true; - Object_GetBone(obj, 14)->rot.y = true; + Object_GetBone(obj, 6)->rot_y = true; + Object_GetBone(obj, 14)->rot_y = true; } static void M_Control(const int16_t item_num) @@ -284,7 +284,7 @@ static void M_Control(const int16_t item_num) } if (lara_alive && g_LaraItem->hit_points <= 0) { - Creature_SpecialKill( + Creature_Kill( item, YETI_ANIM_KILL, YETI_STATE_KILL, LA_EXTRA_YETI_KILL); return; } diff --git a/src/tr2/game/objects/effects/body_part.c b/src/tr2/game/objects/effects/body_part.c index 85dca0a8f..51d7e2435 100644 --- a/src/tr2/game/objects/effects/body_part.c +++ b/src/tr2/game/objects/effects/body_part.c @@ -62,7 +62,7 @@ static void M_Control(const int16_t effect_num) effect->frame_num = 0; effect->counter = 0; effect->object_id = O_EXPLOSION; - effect->shade = SHADE_NEUTRAL; + effect->shade = HIGH_LIGHT; Sound_Effect(SFX_EXPLOSION_1, &effect->pos, SPM_NORMAL); } else { Effect_Kill(effect_num); @@ -78,7 +78,7 @@ static void M_Control(const int16_t effect_num) effect->frame_num = 0; effect->counter = 0; effect->object_id = O_EXPLOSION; - effect->shade = SHADE_NEUTRAL; + effect->shade = HIGH_LIGHT; Sound_Effect(SFX_EXPLOSION_1, &effect->pos, SPM_NORMAL); g_Lara.hit_effect_count = 5; g_Lara.hit_effect = effect; diff --git a/src/tr2/game/objects/general/bridge_common.c b/src/tr2/game/objects/general/bridge_common.c new file mode 100644 index 000000000..396fef560 --- /dev/null +++ b/src/tr2/game/objects/general/bridge_common.c @@ -0,0 +1,16 @@ +#include "game/objects/general/bridge_common.h" + +int32_t Bridge_GetOffset( + const ITEM *const item, const int32_t x, const int32_t z) +{ + switch (item->rot.y) { + case 0: + return (WALL_L - x) & (WALL_L - 1); + case -DEG_180: + return x & (WALL_L - 1); + case DEG_90: + return z & (WALL_L - 1); + default: + return (WALL_L - z) & (WALL_L - 1); + } +} diff --git a/src/tr2/game/objects/general/bridge_common.h b/src/tr2/game/objects/general/bridge_common.h new file mode 100644 index 000000000..da5090619 --- /dev/null +++ b/src/tr2/game/objects/general/bridge_common.h @@ -0,0 +1,5 @@ +#pragma once + +#include "global/types.h" + +int32_t Bridge_GetOffset(const ITEM *item, int32_t x, int32_t z); diff --git a/src/tr2/game/objects/general/bridge_flat.c b/src/tr2/game/objects/general/bridge_flat.c new file mode 100644 index 000000000..fe26b8543 --- /dev/null +++ b/src/tr2/game/objects/general/bridge_flat.c @@ -0,0 +1,35 @@ +#include "game/objects/general/bridge_common.h" + +static int16_t M_GetFloorHeight( + const ITEM *item, int32_t x, int32_t y, int32_t z, int16_t height); +static int16_t M_GetCeilingHeight( + const ITEM *item, int32_t x, int32_t y, int32_t z, int16_t height); +static void M_Setup(OBJECT *obj); + +static int16_t M_GetFloorHeight( + const ITEM *const item, const int32_t x, const int32_t y, const int32_t z, + const int16_t height) +{ + if (y > item->pos.y) { + return height; + } + return item->pos.y; +} + +static int16_t M_GetCeilingHeight( + const ITEM *const item, const int32_t x, const int32_t y, const int32_t z, + const int16_t height) +{ + if (y <= item->pos.y) { + return height; + } + return item->pos.y + STEP_L; +} + +static void M_Setup(OBJECT *const obj) +{ + obj->floor_height_func = M_GetFloorHeight; + obj->ceiling_height_func = M_GetCeilingHeight; +} + +REGISTER_OBJECT(O_BRIDGE_FLAT, M_Setup) diff --git a/src/tr2/game/objects/general/bridge_tilt_1.c b/src/tr2/game/objects/general/bridge_tilt_1.c new file mode 100644 index 000000000..23ae126a8 --- /dev/null +++ b/src/tr2/game/objects/general/bridge_tilt_1.c @@ -0,0 +1,39 @@ +#include "game/objects/general/bridge_common.h" + +static int16_t M_GetFloorHeight( + const ITEM *item, int32_t x, int32_t y, int32_t z, int16_t height); +static int16_t M_GetCeilingHeight( + const ITEM *item, int32_t x, int32_t y, int32_t z, int16_t height); +static void M_Setup(OBJECT *obj); + +static int16_t M_GetFloorHeight( + const ITEM *const item, const int32_t x, const int32_t y, const int32_t z, + const int16_t height) +{ + const int32_t offset_height = + item->pos.y + (Bridge_GetOffset(item, x, z) / 4); + if (y > offset_height) { + return height; + } + return offset_height; +} + +static int16_t M_GetCeilingHeight( + const ITEM *const item, const int32_t x, const int32_t y, const int32_t z, + const int16_t height) +{ + const int32_t offset_height = + item->pos.y + (Bridge_GetOffset(item, x, z) / 4); + if (y <= offset_height) { + return height; + } + return offset_height + STEP_L; +} + +static void M_Setup(OBJECT *const obj) +{ + obj->floor_height_func = M_GetFloorHeight; + obj->ceiling_height_func = M_GetCeilingHeight; +} + +REGISTER_OBJECT(O_BRIDGE_TILT_1, M_Setup) diff --git a/src/tr2/game/objects/general/bridge_tilt_2.c b/src/tr2/game/objects/general/bridge_tilt_2.c new file mode 100644 index 000000000..c5ba54e07 --- /dev/null +++ b/src/tr2/game/objects/general/bridge_tilt_2.c @@ -0,0 +1,39 @@ +#include "game/objects/general/bridge_common.h" + +static int16_t M_GetFloorHeight( + const ITEM *item, int32_t x, int32_t y, int32_t z, int16_t height); +static int16_t M_GetCeilingHeight( + const ITEM *item, int32_t x, int32_t y, int32_t z, int16_t height); +static void M_Setup(OBJECT *obj); + +static int16_t M_GetFloorHeight( + const ITEM *const item, const int32_t x, const int32_t y, const int32_t z, + const int16_t height) +{ + const int32_t offset_height = + item->pos.y + (Bridge_GetOffset(item, x, z) / 2); + if (y > offset_height) { + return height; + } + return offset_height; +} + +static int16_t M_GetCeilingHeight( + const ITEM *const item, const int32_t x, const int32_t y, const int32_t z, + const int16_t height) +{ + const int32_t offset_height = + item->pos.y + (Bridge_GetOffset(item, x, z) / 2); + if (y <= offset_height) { + return height; + } + return offset_height + STEP_L; +} + +static void M_Setup(OBJECT *const obj) +{ + obj->floor_height_func = M_GetFloorHeight; + obj->ceiling_height_func = M_GetCeilingHeight; +} + +REGISTER_OBJECT(O_BRIDGE_TILT_2, M_Setup) diff --git a/src/tr2/game/objects/general/detonator.c b/src/tr2/game/objects/general/detonator.c index e0b0120a3..50a4980e6 100644 --- a/src/tr2/game/objects/general/detonator.c +++ b/src/tr2/game/objects/general/detonator.c @@ -10,37 +10,33 @@ #include "game/sound.h" #include "global/vars.h" -#include - #define EXPLOSION_START_FRAME 76 #define EXPLOSION_END_FRAME 99 #define EXPLOSION_ACTION_FRAME 80 static XYZ_32 m_DetonatorPosition = { .x = 0, .y = 0, .z = 0 }; -static const OBJECT_BOUNDS m_GongBounds = { - .shift = { - .min = { .x = -WALL_L / 2, .y = -100, .z = -WALL_L / 2 - 300, }, - .max = { .x = +WALL_L, .y = +100, .z = -WALL_L / 2 + 100, }, - }, - .rot = { - .min = { .x = -30 * DEG_1, .y = 0, .z = 0, }, - .max = { .x = +30 * DEG_1, .y = 0, .z = 0, }, - }, +static int16_t m_GongBounds[12] = { + -WALL_L / 2, + +WALL_L, + -100, + +100, + -WALL_L / 2 - 300, + -WALL_L / 2 + 100, + -30 * DEG_1, + +30 * DEG_1, + +0, + +0, + +0, + +0, }; -static const OBJECT_BOUNDS *M_Bounds(void); static void M_CreateGongBonger(ITEM *lara_item); static void M_Setup1(OBJECT *obj); static void M_Setup2(OBJECT *obj); static void M_Control(int16_t item_num); static void M_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); -static const OBJECT_BOUNDS *M_Bounds(void) -{ - return &m_GongBounds; -} - static void M_CreateGongBonger(ITEM *const lara_item) { const int16_t item_gong_bonger_num = Item_Create(); @@ -68,14 +64,12 @@ static void M_CreateGongBonger(ITEM *const lara_item) static void M_Setup1(OBJECT *const obj) { obj->collision_func = M_Collision; - obj->bounds_func = M_Bounds; } static void M_Setup2(OBJECT *const obj) { obj->collision_func = M_Collision; obj->control_func = M_Control; - obj->bounds_func = Pickup_Bounds; obj->save_flags = 1; obj->save_anim = 1; } @@ -108,7 +102,6 @@ static void M_Collision( } ITEM *const item = Item_Get(item_num); - const OBJECT *const obj = Object_Get(item->object_id); const XYZ_16 old_rot = item->rot; const int16_t x = item->rot.x; const int16_t y = item->rot.y; @@ -124,11 +117,16 @@ static void M_Collision( goto normal_collision; } - if (!Lara_TestPosition(item, obj->bounds_func())) { - goto normal_collision; - } - if (item->object_id == O_DETONATOR_1) { - item->rot = old_rot; + if (item->object_id == O_DETONATOR_2) { + if (!Item_TestPosition(g_PickupBounds, item, lara_item)) { + goto normal_collision; + } + } else { + if (!Item_TestPosition(m_GongBounds, item, lara_item)) { + goto normal_collision; + } else { + item->rot = old_rot; + } } if (g_Inv_Chosen == NO_OBJECT) { @@ -140,7 +138,7 @@ static void M_Collision( } Inv_RemoveItem(O_KEY_OPTION_2); - Lara_AlignPosition(item, &m_DetonatorPosition); + Item_AlignPosition(&m_DetonatorPosition, item, lara_item); Item_SwitchToObjAnim(lara_item, LA_EXTRA_BREATH, 0, O_LARA_EXTRA); lara_item->current_anim_state = LA_EXTRA_BREATH; if (item->object_id == O_DETONATOR_2) { diff --git a/src/tr2/game/objects/general/door.c b/src/tr2/game/objects/general/door.c new file mode 100644 index 000000000..b7d6eed03 --- /dev/null +++ b/src/tr2/game/objects/general/door.c @@ -0,0 +1,223 @@ +#include "game/objects/general/door.h" + +#include "game/box.h" +#include "game/items.h" +#include "game/lara/misc.h" +#include "game/objects/common.h" +#include "game/room.h" +#include "global/vars.h" + +#include +#include + +typedef struct { + DOORPOS_DATA d1; + DOORPOS_DATA d1flip; + DOORPOS_DATA d2; + DOORPOS_DATA d2flip; +} DOOR_DATA; + +static SECTOR *M_GetRoomRelSector( + const ROOM *room, const ITEM *item, int32_t sector_dx, int32_t sector_dz); +static void M_InitialisePortal( + const ROOM *room, const ITEM *item, int32_t sector_dx, int32_t sector_dz, + DOORPOS_DATA *door_pos); +static void M_Shut(DOORPOS_DATA *d); +static void M_Open(DOORPOS_DATA *d); +static void M_Setup(OBJECT *obj); +static void M_Initialise(int16_t item_num); +static void M_Control(int16_t item_num); + +static SECTOR *M_GetRoomRelSector( + const ROOM *const room, const ITEM *item, const int32_t sector_dx, + const int32_t sector_dz) +{ + const XZ_32 sector = { + .x = ((item->pos.x - room->pos.x) >> WALL_SHIFT) + sector_dx, + .z = ((item->pos.z - room->pos.z) >> WALL_SHIFT) + sector_dz, + }; + return Room_GetUnitSector(room, sector.x, sector.z); +} + +static void Door_Shut(DOORPOS_DATA *const d) +{ + SECTOR *const sector = d->sector; + if (d->sector == nullptr) { + return; + } + + sector->idx = 0; + sector->box = NO_BOX; + sector->ceiling.height = NO_HEIGHT; + sector->floor.height = NO_HEIGHT; + sector->floor.tilt = 0; + sector->ceiling.tilt = 0; + sector->portal_room.sky = NO_ROOM_NEG; + sector->portal_room.pit = NO_ROOM_NEG; + sector->portal_room.wall = NO_ROOM; + + const int16_t box_num = d->block; + if (box_num != NO_BOX) { + Box_GetBox(box_num)->overlap_index |= BOX_BLOCKED; + } +} + +static void Door_Open(DOORPOS_DATA *const d) +{ + if (d->sector == nullptr) { + return; + } + + *d->sector = d->old_sector; + + const int16_t box_num = d->block; + if (box_num != NO_BOX) { + Box_GetBox(box_num)->overlap_index &= ~BOX_BLOCKED; + } +} + +static void M_InitialisePortal( + const ROOM *const room, const ITEM *const item, const int32_t sector_dx, + const int32_t sector_dz, DOORPOS_DATA *const door_pos) +{ + door_pos->sector = M_GetRoomRelSector(room, item, sector_dx, sector_dz); + + const SECTOR *sector = door_pos->sector; + + const int16_t room_num = door_pos->sector->portal_room.wall; + if (room_num != NO_ROOM) { + sector = + M_GetRoomRelSector(Room_Get(room_num), item, sector_dx, sector_dz); + } + + int16_t box_num = sector->box; + if (!(Box_GetBox(box_num)->overlap_index & BOX_BLOCKABLE)) { + box_num = NO_BOX; + } + door_pos->block = box_num; + door_pos->old_sector = *door_pos->sector; +} + +static void M_Setup(OBJECT *const obj) +{ + obj->initialise_func = M_Initialise; + obj->control_func = M_Control; + obj->draw_func = Object_DrawUnclippedItem; + obj->collision_func = Door_Collision; + obj->save_flags = 1; + obj->save_anim = 1; +} + +static void M_Initialise(const int16_t item_num) +{ + ITEM *const item = Item_Get(item_num); + DOOR_DATA *door = GameBuf_Alloc(sizeof(DOOR_DATA), GBUF_ITEM_DATA); + item->data = door; + + int32_t dx = 0; + int32_t dz = 0; + if (item->rot.y == 0) { + dz = -1; + } else if (item->rot.y == -DEG_180) { + dz = 1; + } else if (item->rot.y == DEG_90) { + dx = -1; + } else { + dx = 1; + } + + int16_t room_num = item->room_num; + const ROOM *room = Room_Get(room_num); + M_InitialisePortal(room, item, dx, dz, &door->d1); + + if (room->flipped_room == NO_ROOM_NEG) { + door->d1flip.sector = nullptr; + } else { + room = Room_Get(room->flipped_room); + M_InitialisePortal(room, item, dx, dz, &door->d1flip); + } + + room_num = door->d1.sector->portal_room.wall; + Door_Shut(&door->d1); + Door_Shut(&door->d1flip); + + if (room_num == NO_ROOM) { + door->d2.sector = nullptr; + door->d2flip.sector = nullptr; + } else { + room = Room_Get(room_num); + M_InitialisePortal(room, item, 0, 0, &door->d2); + if (room->flipped_room == NO_ROOM_NEG) { + door->d2flip.sector = nullptr; + } else { + room = Room_Get(room->flipped_room); + M_InitialisePortal(room, item, 0, 0, &door->d2flip); + } + + Door_Shut(&door->d2); + Door_Shut(&door->d2flip); + + const int16_t prev_room = item->room_num; + Item_NewRoom(item_num, room_num); + item->room_num = prev_room; + } +} + +static void M_Control(const int16_t item_num) +{ + ITEM *const item = Item_Get(item_num); + DOOR_DATA *const data = item->data; + + if (Item_IsTriggerActive(item)) { + if (item->current_anim_state == DOOR_STATE_CLOSED) { + item->goal_anim_state = DOOR_STATE_OPEN; + } else { + Door_Open(&data->d1); + Door_Open(&data->d2); + Door_Open(&data->d1flip); + Door_Open(&data->d2flip); + } + } else { + if (item->current_anim_state == DOOR_STATE_OPEN) { + item->goal_anim_state = DOOR_STATE_CLOSED; + } else { + Door_Shut(&data->d1); + Door_Shut(&data->d2); + Door_Shut(&data->d1flip); + Door_Shut(&data->d2flip); + } + } + + Item_Animate(item); +} + +void Door_Collision( + const int16_t item_num, ITEM *const lara_item, COLL_INFO *const coll) +{ + ITEM *const item = Item_Get(item_num); + + if (!Item_TestBoundsCollide(item, lara_item, coll->radius)) { + return; + } + + if (!Collide_TestCollision(item, lara_item)) { + return; + } + + if (coll->enable_baddie_push) { + Lara_Push( + item, lara_item, coll, + item->current_anim_state != item->goal_anim_state ? coll->enable_hit + : false, + true); + } +} + +REGISTER_OBJECT(O_DOOR_TYPE_1, M_Setup) +REGISTER_OBJECT(O_DOOR_TYPE_2, M_Setup) +REGISTER_OBJECT(O_DOOR_TYPE_3, M_Setup) +REGISTER_OBJECT(O_DOOR_TYPE_4, M_Setup) +REGISTER_OBJECT(O_DOOR_TYPE_5, M_Setup) +REGISTER_OBJECT(O_DOOR_TYPE_6, M_Setup) +REGISTER_OBJECT(O_DOOR_TYPE_7, M_Setup) +REGISTER_OBJECT(O_DOOR_TYPE_8, M_Setup) diff --git a/src/tr2/game/objects/general/door.h b/src/tr2/game/objects/general/door.h new file mode 100644 index 000000000..c702447b8 --- /dev/null +++ b/src/tr2/game/objects/general/door.h @@ -0,0 +1,10 @@ +#pragma once + +#include "global/types.h" + +typedef enum { + DOOR_STATE_CLOSED = 0, + DOOR_STATE_OPEN = 1, +} DOOR_STATE; + +void Door_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); diff --git a/src/tr2/game/objects/general/drawbridge.c b/src/tr2/game/objects/general/drawbridge.c new file mode 100644 index 000000000..d891f8b3d --- /dev/null +++ b/src/tr2/game/objects/general/drawbridge.c @@ -0,0 +1,99 @@ +#include "game/objects/general/door.h" +#include "game/objects/general/general.h" + +typedef enum { + DRAWBRIDGE_STATE_CLOSED = DOOR_STATE_CLOSED, + DRAWBRIDGE_STATE_OPEN = DOOR_STATE_OPEN, +} DRAWBRIDGE_STATE; + +static int16_t M_GetFloorHeight( + const ITEM *item, int32_t x, int32_t y, int32_t z, int16_t height); +static int16_t M_GetCeilingHeight( + const ITEM *item, int32_t x, int32_t y, int32_t z, int16_t height); +static bool M_IsItemOnTop(const ITEM *item, int32_t z, int32_t x); +static void M_Setup(OBJECT *obj); +static void M_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); + +static int16_t M_GetFloorHeight( + const ITEM *const item, const int32_t x, const int32_t y, const int32_t z, + const int16_t height) +{ + if (item->current_anim_state != DRAWBRIDGE_STATE_OPEN) { + return height; + } else if (!M_IsItemOnTop(item, z, x)) { + return height; + } else if (item->pos.y < y) { + return height; + } + return item->pos.y; +} + +static int16_t M_GetCeilingHeight( + const ITEM *const item, const int32_t x, const int32_t y, const int32_t z, + const int16_t height) +{ + if (item->current_anim_state != DRAWBRIDGE_STATE_OPEN) { + return height; + } else if (!M_IsItemOnTop(item, z, x)) { + return height; + } else if (item->pos.y >= y) { + return height; + } + return item->pos.y + STEP_L; +} + +static bool M_IsItemOnTop( + const ITEM *const item, const int32_t z, const int32_t x) +{ + // drawbridge sector + const XZ_32 obj = { + .x = item->pos.x >> WALL_SHIFT, + .z = item->pos.z >> WALL_SHIFT, + }; + + // test sector + const XZ_32 test = { + .x = x >> WALL_SHIFT, + .z = z >> WALL_SHIFT, + }; + + switch (item->rot.y) { + case 0: + return test.x == obj.x && (test.z == obj.z - 1 || test.z == obj.z - 2); + + case -DEG_180: + return test.x == obj.x && (test.z == obj.z + 1 || test.z == obj.z + 2); + + case -DEG_90: + return test.z == obj.z && (test.x == obj.x + 1 || test.x == obj.x + 2); + + case DEG_90: + return test.z == obj.z && (test.x == obj.x - 1 || test.x == obj.x - 2); + } + + return false; +} + +static void M_Setup(OBJECT *const obj) +{ + if (!obj->loaded) { + return; + } + obj->control_func = General_Control; + obj->collision_func = M_Collision; + obj->floor_height_func = M_GetFloorHeight; + obj->ceiling_height_func = M_GetCeilingHeight; + obj->save_flags = 1; + obj->save_anim = 1; +} + +static void M_Collision( + const int16_t item_num, ITEM *const lara_item, COLL_INFO *const coll) +{ + const ITEM *const item = Item_Get(item_num); + if (item->current_anim_state == DRAWBRIDGE_STATE_CLOSED) { + Door_Collision(item_num, lara_item, coll); + } +} + +REGISTER_OBJECT(O_DRAWBRIDGE, M_Setup) diff --git a/src/tr2/game/objects/general/final_level_counter.c b/src/tr2/game/objects/general/final_level_counter.c index 9672bc0b2..0694ceb2f 100644 --- a/src/tr2/game/objects/general/final_level_counter.c +++ b/src/tr2/game/objects/general/final_level_counter.c @@ -1,13 +1,11 @@ #include "decomp/flares.h" #include "game/camera.h" #include "game/creature.h" -#include "game/game.h" #include "game/gun/gun.h" #include "game/items.h" #include "game/los.h" #include "game/lot.h" #include "game/objects/common.h" -#include "game/savegame.h" #include "global/vars.h" #include @@ -78,7 +76,7 @@ static void M_PrepareCutscene(const int16_t item_num) g_Lara.water_status = LWS_ABOVE_WATER; ITEM *const item = Item_Get(item_num); - Creature_SpecialKill(item, 0, 0, LA_EXTRA_FINAL_ANIM); + Creature_Kill(item, 0, 0, LA_EXTRA_FINAL_ANIM); Camera_InvokeCinematic(item, 428, 0); } @@ -92,15 +90,13 @@ static void M_Setup(OBJECT *const obj) static void M_Control(const int16_t item_num) { - const RESUME_INFO *const current_info = - Savegame_GetCurrentInfo(Game_GetCurrentLevel()); - if (current_info->stats.kill_count == g_FinalLevelCount + if (g_SaveGame.current_stats.kills == g_FinalLevelCount && !g_FinalBossActive) { M_ActivateLastBoss(); return; } - if (current_info->stats.kill_count > g_FinalLevelCount) { + if (g_SaveGame.current_stats.kills > g_FinalLevelCount) { g_FinalBossActive++; if (g_FinalBossActive == CUTSCENE_DELAY) { M_PrepareCutscene(item_num); diff --git a/src/tr2/game/objects/general/flare_item.c b/src/tr2/game/objects/general/flare_item.c index fe893694e..0a89767cb 100644 --- a/src/tr2/game/objects/general/flare_item.c +++ b/src/tr2/game/objects/general/flare_item.c @@ -6,7 +6,6 @@ static void M_Setup(OBJECT *obj); static void M_Setup(OBJECT *const obj) { obj->collision_func = Pickup_Collision; - obj->bounds_func = Pickup_Bounds; obj->control_func = Flare_Control; obj->draw_func = Flare_DrawInAir; obj->save_position = 1; diff --git a/src/tr2/game/objects/general/grenade.c b/src/tr2/game/objects/general/grenade.c index 94cdb0bf9..631c51c60 100644 --- a/src/tr2/game/objects/general/grenade.c +++ b/src/tr2/game/objects/general/grenade.c @@ -5,7 +5,6 @@ #include "game/objects/general/window.h" #include "game/room.h" #include "game/sound.h" -#include "game/stats.h" #include "global/vars.h" #include @@ -131,7 +130,7 @@ static void M_Control(const int16_t item_num) } Gun_HitTarget(target_item, nullptr, 30); - Stats_AddAmmoHits(); + g_SaveGame.current_stats.ammo_hits++; if (target_item->hit_points <= 0) { if (target_item->object_id != O_DRAGON_FRONT diff --git a/src/tr2/game/objects/general/harpoon_bolt.c b/src/tr2/game/objects/general/harpoon_bolt.c index 2e0922c0a..1c33e628c 100644 --- a/src/tr2/game/objects/general/harpoon_bolt.c +++ b/src/tr2/game/objects/general/harpoon_bolt.c @@ -3,7 +3,6 @@ #include "game/objects/general/window.h" #include "game/room.h" #include "game/spawn.h" -#include "game/stats.h" #include "global/vars.h" #include @@ -62,9 +61,6 @@ static void M_Control(const int16_t item_num) } const ANIM_FRAME *const frame = Item_GetBestFrame(target_item); - if (frame == nullptr) { - continue; - } const BOUNDS_16 *const bounds = &frame->bounds; const int32_t cdy = item->pos.y - target_item->pos.y; @@ -102,7 +98,7 @@ static void M_Control(const int16_t item_num) 5); Gun_HitTarget( target_item, nullptr, g_Weapons[LGT_HARPOON].damage); - Stats_AddAmmoHits(); + g_SaveGame.current_stats.ammo_hits++; } Item_Kill(item_num); return; diff --git a/src/tr2/game/objects/general/keyhole.c b/src/tr2/game/objects/general/keyhole.c index c9b44a88f..359d23110 100644 --- a/src/tr2/game/objects/general/keyhole.c +++ b/src/tr2/game/objects/general/keyhole.c @@ -17,29 +17,29 @@ static XYZ_32 m_KeyholePosition = { .z = WALL_L / 2 - LARA_RADIUS - 50, }; -static const OBJECT_BOUNDS m_KeyHoleBounds = { - .shift = { - .min = { .x = -200, .y = +0, .z = +WALL_L / 2 - 200, }, - .max = { .x = +200, .y = +0, .z = +WALL_L / 2, }, - }, - .rot = { - .min = { .x = -10 * DEG_1, .y = -30 * DEG_1, .z = -10 * DEG_1, }, - .max = { .x = +10 * DEG_1, .y = +30 * DEG_1, .z = +10 * DEG_1, }, - }, +static int16_t m_KeyholeBounds[12] = { + // clang-format off + -200, + +200, + +0, + +0, + +WALL_L / 2 - 200, + +WALL_L / 2, + -10 * DEG_1, + +10 * DEG_1, + -30 * DEG_1, + +30 * DEG_1, + -10 * DEG_1, + +10 * DEG_1, + // clang-format on }; -static const OBJECT_BOUNDS *M_Bounds(void); static void M_Consume( ITEM *lara_item, ITEM *keyhole_item, GAME_OBJECT_ID key_obj_id); static void M_Refuse(const ITEM *lara_item); static void M_Setup(OBJECT *obj); static void M_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); -static const OBJECT_BOUNDS *M_Bounds(void) -{ - return &m_KeyHoleBounds; -} - static void M_Refuse(const ITEM *const lara_item) { if (lara_item->pos.x == g_InteractPosition.x @@ -57,7 +57,7 @@ static void M_Consume( const GAME_OBJECT_ID key_obj_id) { Inv_RemoveItem(key_obj_id); - Lara_AlignPosition(keyhole_item, &m_KeyholePosition); + Item_AlignPosition(&m_KeyholePosition, keyhole_item, lara_item); lara_item->goal_anim_state = LS_USE_KEY; do { Lara_Animate(lara_item); @@ -71,7 +71,6 @@ static void M_Consume( static void M_Setup(OBJECT *const obj) { obj->collision_func = M_Collision; - obj->bounds_func = M_Bounds; obj->save_flags = 1; } @@ -83,13 +82,12 @@ static void M_Collision( } ITEM *const item = Item_Get(item_num); - const OBJECT *const obj = Object_Get(item->object_id); if ((g_Inv_Chosen == NO_OBJECT && !g_Input.action) || g_Lara.gun_status != LGS_ARMLESS || lara_item->gravity) { return; } - if (!Lara_TestPosition(item, obj->bounds_func())) { + if (!Item_TestPosition(m_KeyholeBounds, item, lara_item)) { return; } diff --git a/src/tr2/game/objects/general/lift.c b/src/tr2/game/objects/general/lift.c index 0177c4244..49bf5b6d2 100644 --- a/src/tr2/game/objects/general/lift.c +++ b/src/tr2/game/objects/general/lift.c @@ -5,7 +5,6 @@ #include "global/vars.h" #include -#include #define LIFT_WAIT_TIME (3 * FRAMES_PER_SECOND) // = 90 #define LIFT_SHIFT 16 @@ -47,38 +46,14 @@ static void M_FloorCeiling( .z = z >> WALL_SHIFT, }; - const DIRECTION direction = Math_GetDirection(item->rot.y); - int32_t dx = 0; - int32_t dz = 0; - switch (direction) { - case DIR_NORTH: - dx = -1; - dz = 1; - break; - case DIR_EAST: - dx = 1; - dz = 1; - break; - case DIR_SOUTH: - dx = 1; - dz = -1; - break; - case DIR_WEST: - dx = -1; - dz = -1; - break; - default: - break; - } - // clang-format off const bool point_in_shaft = - (test_tile.x == lift_tile.x || test_tile.x + dx == lift_tile.x) && - (test_tile.z == lift_tile.z || test_tile.z + dz == lift_tile.z); + (test_tile.x == lift_tile.x || test_tile.x + 1 == lift_tile.x) && + (test_tile.z == lift_tile.z || test_tile.z - 1 == lift_tile.z); const bool lara_in_shaft = - (lara_tile.x == lift_tile.x || lara_tile.x + dx == lift_tile.x) && - (lara_tile.z == lift_tile.z || lara_tile.z + dz == lift_tile.z); + (lara_tile.x == lift_tile.x || lara_tile.x + 1 == lift_tile.x) && + (lara_tile.z == lift_tile.z || lara_tile.z - 1 == lift_tile.z); // clang-format on const int32_t lift_floor = item->pos.y; diff --git a/src/tr2/game/objects/general/movable_block.c b/src/tr2/game/objects/general/movable_block.c index 3dbea88e8..6e00b5234 100644 --- a/src/tr2/game/objects/general/movable_block.c +++ b/src/tr2/game/objects/general/movable_block.c @@ -20,18 +20,21 @@ typedef enum { MOVABLE_BLOCK_STATE_PULL = 3, } MOVABLE_BLOCK_STATE; -static const OBJECT_BOUNDS m_MovableBlockBounds = { - .shift = { - .min = { .x = -300, .y = 0, .z = -WALL_L / 2 - LARA_RADIUS - 80, }, - .max = { .x = +300, .y = 0, .z = -WALL_L / 2, }, - }, - .rot = { - .min = { .x = -10 * DEG_1, .y = -30 * DEG_1, .z = -10 * DEG_1, }, - .max = { .x = +10 * DEG_1, .y = +30 * DEG_1, .z = +10 * DEG_1, }, - }, +static int16_t m_MovableBlockBounds[12] = { + -300, + +300, + +0, + +0, + -WALL_L / 2 - LARA_RADIUS - 80, + -WALL_L / 2, + -10 * DEG_1, + +10 * DEG_1, + -30 * DEG_1, + +30 * DEG_1, + -10 * DEG_1, + +10 * DEG_1, }; -static const OBJECT_BOUNDS *M_Bounds(void); static bool M_TestDestination(const ITEM *item, int32_t block_height); static bool M_TestPush( const ITEM *item, int32_t block_height, DIRECTION quadrant); @@ -44,11 +47,6 @@ static void M_Draw(const ITEM *item); static void M_Control(int16_t item_num); static void M_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); -static const OBJECT_BOUNDS *M_Bounds(void) -{ - return &m_MovableBlockBounds; -} - static bool M_TestDestination( const ITEM *const item, const int32_t block_height) { @@ -201,12 +199,10 @@ static void M_Setup(OBJECT *const obj) obj->handle_save_func = M_HandleSave; obj->control_func = M_Control; obj->collision_func = M_Collision; - obj->bounds_func = M_Bounds; obj->draw_func = M_Draw; obj->save_position = 1; obj->save_flags = 1; obj->save_anim = 1; - obj->base_rot.y = true; } static void M_HandleSave(ITEM *const item, const SAVEGAME_STAGE stage) @@ -218,7 +214,6 @@ static void M_HandleSave(ITEM *const item, const SAVEGAME_STAGE stage) item->status = IS_INACTIVE; } item->priv = item->status == IS_ACTIVE ? (void *)true : (void *)false; - MovableBlock_UpdateRotation(item, item->rot.y); } } @@ -291,22 +286,22 @@ static void M_Collision( switch (quadrant) { case DIR_NORTH: - MovableBlock_UpdateRotation(item, 0); + item->rot.y = 0; break; case DIR_EAST: - MovableBlock_UpdateRotation(item, DEG_90); + item->rot.y = DEG_90; break; case DIR_SOUTH: - MovableBlock_UpdateRotation(item, -DEG_180); + item->rot.y = -DEG_180; break; case DIR_WEST: - MovableBlock_UpdateRotation(item, -DEG_90); + item->rot.y = -DEG_90; break; default: break; } - if (!Lara_TestPosition(item, obj->bounds_func())) { + if (!Item_TestPosition(m_MovableBlockBounds, item, lara_item)) { return; } @@ -349,7 +344,7 @@ static void M_Collision( } else if ( Item_TestAnimEqual(lara_item, LA_PUSHABLE_GRAB) && Item_TestFrameEqual(lara_item, LF_PPREADY)) { - if (!Lara_TestPosition(item, obj->bounds_func())) { + if (!Item_TestPosition(m_MovableBlockBounds, item, lara_item)) { return; } diff --git a/src/tr2/game/objects/general/pickup.c b/src/tr2/game/objects/general/pickup.c index 2f1853f3f..0c891df85 100644 --- a/src/tr2/game/objects/general/pickup.c +++ b/src/tr2/game/objects/general/pickup.c @@ -11,11 +11,9 @@ #include "game/lara/control.h" #include "game/lara/misc.h" #include "game/objects/common.h" -#include "game/objects/vars.h" #include "game/output.h" #include "game/overlay.h" #include "game/room.h" -#include "game/stats.h" #include "global/vars.h" #include @@ -26,29 +24,41 @@ #define LF_PICKUP_FLARE_UW 20 #define LF_PICKUP_UW 18 +int16_t g_PickupBounds[12] = { + // clang-format off + -WALL_L / 4, + +WALL_L / 4, + -100, + +100, + -WALL_L / 4, + +WALL_L / 4, + -10 * DEG_1, + +10 * DEG_1, + +0, + +0, + +0, + +0, + // clang-format on +}; + static XYZ_32 m_PickupPosition = { .x = 0, .y = 0, .z = -100 }; static XYZ_32 m_PickupPositionUW = { .x = 0, .y = -200, .z = -350 }; -static const OBJECT_BOUNDS m_PickUpBounds = { - .shift = { - .min = { .x = -WALL_L / 4, .y = -100, .z = -WALL_L / 4, }, - .max = { .x = +WALL_L / 4, .y = +100, .z = +WALL_L / 4, }, - }, - .rot = { - .min = { .x = -10 * DEG_1, .y = 0, .z = 0, }, - .max = { .x = +10 * DEG_1, .y = 0, .z = 0, }, - }, -}; - -static const OBJECT_BOUNDS m_PickUpBoundsUW = { - .shift = { - .min = { .x = -WALL_L / 2, .y = -WALL_L / 2, .z = -WALL_L / 2, }, - .max = { .x = +WALL_L / 2, .y = +WALL_L / 2, .z = +WALL_L / 2, }, - }, - .rot = { - .min = { .x = -45 * DEG_1, .y = -45 * DEG_1, .z = -45 * DEG_1, }, - .max = { .x = +45 * DEG_1, .y = +45 * DEG_1, .z = +45 * DEG_1, }, - }, +static int16_t m_PickupBoundsUW[12] = { + // clang-format off + -WALL_L / 2, + +WALL_L / 2, + -WALL_L / 2, + +WALL_L / 2, + -WALL_L / 2, + +WALL_L / 2, + -45 * DEG_1, + +45 * DEG_1, + -45 * DEG_1, + +45 * DEG_1, + -45 * DEG_1, + +45 * DEG_1, + // clang-format on }; static void M_DoPickup(int16_t item_num); @@ -70,8 +80,12 @@ static void M_DoPickup(const int16_t item_num) Overlay_AddDisplayPickup(item->object_id); Inv_AddItem(item->object_id); - if (Object_IsType(item->object_id, g_SecretObjects) - && Stats_CheckAllLevelSecretsCollected()) { + if ((item->object_id == O_SECRET_1 || item->object_id == O_SECRET_2 + || item->object_id == O_SECRET_3) + && (g_SaveGame.current_stats.secret_flags & 1) + + ((g_SaveGame.current_stats.secret_flags >> 1) & 1) + + ((g_SaveGame.current_stats.secret_flags >> 2) & 1) + >= 3) { GF_InventoryModifier_Apply(Game_GetCurrentLevel(), GF_INV_SECRET); } @@ -95,14 +109,13 @@ static void M_DoFlarePickup(const int16_t item_num) static void M_DoAboveWater(const int16_t item_num, ITEM *const lara_item) { ITEM *const item = Item_Get(item_num); - const OBJECT *const obj = Object_Get(item->object_id); const XYZ_16 old_rot = item->rot; item->rot.x = 0; item->rot.y = lara_item->rot.y; item->rot.z = 0; - if (!Lara_TestPosition(item, obj->bounds_func())) { + if (!Item_TestPosition(g_PickupBounds, item, lara_item)) { goto cleanup; } @@ -134,7 +147,7 @@ static void M_DoAboveWater(const int16_t item_num, ITEM *const lara_item) lara_item->goal_anim_state = LS_STOP; g_Lara.gun_status = LGS_HANDS_BUSY; } else { - Lara_AlignPosition(item, &m_PickupPosition); + Item_AlignPosition(&m_PickupPosition, item, lara_item); lara_item->goal_anim_state = LS_PICKUP; do { Lara_Animate(lara_item); @@ -152,14 +165,13 @@ cleanup: static void M_DoUnderwater(const int16_t item_num, ITEM *const lara_item) { ITEM *const item = Item_Get(item_num); - const OBJECT *const obj = Object_Get(item->object_id); const XYZ_16 old_rot = item->rot; item->rot.x = -25 * DEG_1; item->rot.y = lara_item->rot.y; item->rot.z = 0; - if (!Lara_TestPosition(item, obj->bounds_func())) { + if (!Item_TestPosition(m_PickupBoundsUW, item, lara_item)) { goto cleanup; } @@ -214,7 +226,6 @@ static void M_Setup(OBJECT *const obj) obj->handle_save_func = M_HandleSave; obj->activate_func = M_Activate; obj->collision_func = Pickup_Collision; - obj->bounds_func = Pickup_Bounds; obj->draw_func = M_Draw; obj->save_position = 1; obj->save_flags = 1; @@ -334,15 +345,6 @@ static void M_Draw(const ITEM *const item) Matrix_Pop(); } -const OBJECT_BOUNDS *Pickup_Bounds(void) -{ - if (g_Lara.water_status == LWS_UNDERWATER) { - return &m_PickUpBoundsUW; - } else { - return &m_PickUpBounds; - } -} - void Pickup_Collision( const int16_t item_num, ITEM *const lara_item, COLL_INFO *const coll) { diff --git a/src/tr2/game/objects/general/pickup.h b/src/tr2/game/objects/general/pickup.h index 1b1a51fff..dfac91fc3 100644 --- a/src/tr2/game/objects/general/pickup.h +++ b/src/tr2/game/objects/general/pickup.h @@ -2,6 +2,7 @@ #include "global/types.h" +extern int16_t g_PickupBounds[]; + void Pickup_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); bool Pickup_Trigger(int16_t item_num); -const OBJECT_BOUNDS *Pickup_Bounds(void); diff --git a/src/tr2/game/objects/general/puzzle_hole.c b/src/tr2/game/objects/general/puzzle_hole.c index f63db4c5c..174755780 100644 --- a/src/tr2/game/objects/general/puzzle_hole.c +++ b/src/tr2/game/objects/general/puzzle_hole.c @@ -17,18 +17,23 @@ static XYZ_32 m_PuzzleHolePosition = { .z = WALL_L / 2 - LARA_RADIUS - 85, }; -static const OBJECT_BOUNDS m_PuzzleHoleBounds = { - .shift = { - .min = { .x = -200, .y = 0, .z = WALL_L / 2 - 200, }, - .max = { .x = +200, .y = 0, .z = WALL_L / 2, }, - }, - .rot = { - .min = { .x = -10 * DEG_1, .y = -30 * DEG_1, .z = -10 * DEG_1, }, - .max = { .x = +10 * DEG_1, .y = +30 * DEG_1, .z = +10 * DEG_1, }, - }, +static int16_t m_PuzzleHoleBounds[12] = { + // clang-format off + -200, + +200, + +0, + +0, + +WALL_L / 2 - 200, + +WALL_L / 2, + -10 * DEG_1, + +10 * DEG_1, + -30 * DEG_1, + +30 * DEG_1, + -10 * DEG_1, + +10 * DEG_1, + // clang-format on }; -static const OBJECT_BOUNDS *M_Bounds(void); static void M_Refuse(const ITEM *lara_item); static void M_Consume( ITEM *lara_item, ITEM *puzzle_hole_item, GAME_OBJECT_ID puzzle_obj_id); @@ -38,11 +43,6 @@ static void M_SetupDone(OBJECT *obj); static void M_HandleSave(ITEM *item, SAVEGAME_STAGE stage); static void M_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); -static const OBJECT_BOUNDS *M_Bounds(void) -{ - return &m_PuzzleHoleBounds; -} - static void M_Refuse(const ITEM *const lara_item) { if (lara_item->pos.x != g_InteractPosition.x @@ -58,7 +58,7 @@ static void M_Consume( const GAME_OBJECT_ID puzzle_obj_id) { Inv_RemoveItem(puzzle_obj_id); - Lara_AlignPosition(puzzle_hole_item, &m_PuzzleHolePosition); + Item_AlignPosition(&m_PuzzleHolePosition, puzzle_hole_item, lara_item); lara_item->goal_anim_state = LS_USE_PUZZLE; do { Lara_Animate(lara_item); @@ -82,7 +82,6 @@ static void M_SetupEmpty(OBJECT *const obj) { obj->collision_func = M_Collision; obj->handle_save_func = M_HandleSave; - obj->bounds_func = M_Bounds; obj->save_flags = 1; } @@ -104,11 +103,10 @@ static void M_Collision( const int16_t item_num, ITEM *const lara_item, COLL_INFO *const coll) { ITEM *const item = Item_Get(item_num); - const OBJECT *const obj = Object_Get(item->object_id); if (lara_item->current_anim_state != LS_STOP) { if (lara_item->current_anim_state != LS_USE_PUZZLE - || !Lara_TestPosition(item, obj->bounds_func()) + || !Item_TestPosition(m_PuzzleHoleBounds, item, lara_item) || !Item_TestFrameEqual(lara_item, LF_USE_PUZZLE)) { return; } @@ -122,7 +120,7 @@ static void M_Collision( return; } - if (!Lara_TestPosition(item, obj->bounds_func())) { + if (!Item_TestPosition(m_PuzzleHoleBounds, item, lara_item)) { return; } diff --git a/src/tr2/game/objects/general/switch.c b/src/tr2/game/objects/general/switch.c index 382425e6f..3af8f70e2 100644 --- a/src/tr2/game/objects/general/switch.c +++ b/src/tr2/game/objects/general/switch.c @@ -17,30 +17,40 @@ static XYZ_32 g_PushSwitchPosition = { .x = 0, .y = 0, .z = 292 }; static XYZ_32 m_AirlockPosition = { .x = 0, .y = 0, .z = 212 }; static XYZ_32 m_SwitchUWPosition = { .x = 0, .y = 0, .z = 108 }; -static const OBJECT_BOUNDS m_SwitchBounds = { - .shift = { - .min = { .x = -220, .y = +0, .z = +WALL_L / 2 - 220, }, - .max = { .x = +220, .y = +0, .z = +WALL_L / 2, }, - }, - .rot = { - .min = { .x = -10 * DEG_1, .y = -30 * DEG_1, .z = -10 * DEG_1, }, - .max = { .x = +10 * DEG_1, .y = +30 * DEG_1, .z = +10 * DEG_1, }, - }, +static int16_t m_SwitchBounds[12] = { + // clang-format off + -220, + +220, + +0, + +0, + +WALL_L / 2 - 220, + +WALL_L / 2, + -10 * DEG_1, + +10 * DEG_1, + -30 * DEG_1, + +30 * DEG_1, + -10 * DEG_1, + +10 * DEG_1, + // clang-format on }; -static const OBJECT_BOUNDS m_SwitchBoundsUW = { - .shift = { - .min = { .x = -WALL_L, .y = -WALL_L, .z = -WALL_L, }, - .max = { .x = +WALL_L, .y = +WALL_L, .z = +WALL_L / 2, }, - }, - .rot = { - .min = { .x = -80 * DEG_1, .y = -80 * DEG_1, .z = -80 * DEG_1, }, - .max = { .x = +80 * DEG_1, .y = +80 * DEG_1, .z = +80 * DEG_1, }, - }, +static int16_t m_SwitchBoundsUW[12] = { + // clang-format off + -WALL_L, + +WALL_L, + -WALL_L, + +WALL_L, + -WALL_L, + +WALL_L / 2, + -80 * DEG_1, + +80 * DEG_1, + -80 * DEG_1, + +80 * DEG_1, + -80 * DEG_1, + +80 * DEG_1, + // clang-format on }; -static const OBJECT_BOUNDS *M_Bounds(void); -static const OBJECT_BOUNDS *M_BoundsUW(void); static void M_AlignLara(ITEM *lara_item, ITEM *switch_item); static void M_SwitchOn(ITEM *switch_item, ITEM *lara_item); static void M_SwitchOff(ITEM *switch_item, ITEM *lara_item); @@ -52,30 +62,19 @@ static void M_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); static void M_CollisionUW(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); static void M_Control(int16_t item_num); -static const OBJECT_BOUNDS *M_Bounds(void) -{ - return &m_SwitchBounds; -} - -static const OBJECT_BOUNDS *M_BoundsUW(void) -{ - return &m_SwitchBoundsUW; -} - static void M_AlignLara(ITEM *const lara_item, ITEM *const switch_item) { - lara_item->rot.y = switch_item->rot.y; switch (switch_item->object_id) { case O_SWITCH_TYPE_AIRLOCK: - Lara_AlignPosition(switch_item, &m_AirlockPosition); + Item_AlignPosition(&m_AirlockPosition, switch_item, lara_item); break; case O_SWITCH_TYPE_SMALL: - Lara_AlignPosition(switch_item, &g_SmallSwitchPosition); + Item_AlignPosition(&g_SmallSwitchPosition, switch_item, lara_item); break; case O_SWITCH_TYPE_BUTTON: - Lara_AlignPosition(switch_item, &g_PushSwitchPosition); + Item_AlignPosition(&g_PushSwitchPosition, switch_item, lara_item); break; } } @@ -140,35 +139,33 @@ static void M_Setup(OBJECT *const obj) { M_SetupBase(obj); obj->collision_func = M_Collision; - obj->bounds_func = M_Bounds; } static void M_SetupPushButton(OBJECT *const obj) { M_Setup(obj); obj->enable_interpolation = false; - obj->bounds_func = M_Bounds; } static void M_SetupUW(OBJECT *const obj) { M_SetupBase(obj); obj->collision_func = M_CollisionUW; - obj->bounds_func = M_BoundsUW; } static void M_Collision( const int16_t item_num, ITEM *const lara_item, COLL_INFO *const coll) { ITEM *const item = Item_Get(item_num); - const OBJECT *const obj = Object_Get(item->object_id); if (!g_Input.action || item->status != IS_INACTIVE || g_Lara.gun_status != LGS_ARMLESS || lara_item->gravity || lara_item->current_anim_state != LS_STOP - || !Lara_TestPosition(item, obj->bounds_func())) { + || !Item_TestPosition(m_SwitchBounds, item, lara_item)) { return; } + lara_item->rot.y = item->rot.y; + if (item->object_id == O_SWITCH_TYPE_AIRLOCK && item->current_anim_state == SWITCH_STATE_ON) { return; @@ -196,7 +193,6 @@ static void M_CollisionUW( const int16_t item_num, ITEM *const lara_item, COLL_INFO *const coll) { ITEM *const item = Item_Get(item_num); - const OBJECT *const obj = Object_Get(item->object_id); if (!g_Input.action || item->status != IS_INACTIVE || g_Lara.water_status != LWS_UNDERWATER @@ -205,7 +201,7 @@ static void M_CollisionUW( return; } - if (!Lara_TestPosition(item, obj->bounds_func())) { + if (!Item_TestPosition(m_SwitchBoundsUW, item, lara_item)) { return; } diff --git a/src/libtrx/game/objects/general/trapdoor.c b/src/tr2/game/objects/general/trapdoor.c similarity index 98% rename from src/libtrx/game/objects/general/trapdoor.c rename to src/tr2/game/objects/general/trapdoor.c index d9b0ee8f0..20082f048 100644 --- a/src/libtrx/game/objects/general/trapdoor.c +++ b/src/tr2/game/objects/general/trapdoor.c @@ -1,5 +1,4 @@ -#include "game/const.h" -#include "game/objects.h" +#include "game/items.h" typedef enum { TRAPDOOR_STATE_CLOSED, diff --git a/src/tr2/game/objects/general/waterfall.c b/src/tr2/game/objects/general/waterfall.c index c84f9f24a..b2446b3c1 100644 --- a/src/tr2/game/objects/general/waterfall.c +++ b/src/tr2/game/objects/general/waterfall.c @@ -17,7 +17,6 @@ static void M_Setup(OBJECT *const obj) { obj->control_func = M_Control; obj->draw_func = Object_DrawDummyItem; - obj->save_flags = 1; } static void M_Control(const int16_t item_num) diff --git a/src/tr2/game/objects/general/zipline.c b/src/tr2/game/objects/general/zipline.c index 0effd9bb6..6dd04d867 100644 --- a/src/tr2/game/objects/general/zipline.c +++ b/src/tr2/game/objects/general/zipline.c @@ -22,35 +22,33 @@ static XYZ_32 m_ZiplineHandlePosition = { .y = 0, .z = WALL_L / 2 - 141, }; - -static const OBJECT_BOUNDS m_ZiplineHandleBounds = { - .shift = { - .min = { .x = -WALL_L / 4, .y = -100, .z = +WALL_L / 4, }, - .max = { .x = +WALL_L / 4, .y = +100, .z = +WALL_L / 2, }, - }, - .rot = { - .min = { .x = +0, .y = -25 * DEG_1, .z = +0, }, - .max = { .x = +0, .y = +25 * DEG_1, .z = +0, }, - }, +static int16_t m_ZiplineHandleBounds[12] = { + // clang-format off + -WALL_L / 4, + +WALL_L / 4, + -100, + +100, + +WALL_L / 4, + +WALL_L / 2, + +0, + +0, + -25 * DEG_1, + +25 * DEG_1, + +0, + +0, + // clang-format on }; -static const OBJECT_BOUNDS *M_Bounds(void); static void M_Setup(OBJECT *obj); static void M_Initialise(int16_t item_num); static void M_Control(int16_t item_num); static void M_Collision(int16_t item_num, ITEM *lara_item, COLL_INFO *coll); -static const OBJECT_BOUNDS *M_Bounds(void) -{ - return &m_ZiplineHandleBounds; -} - static void M_Setup(OBJECT *const obj) { obj->initialise_func = M_Initialise; obj->control_func = M_Control; obj->collision_func = M_Collision; - obj->bounds_func = M_Bounds; obj->save_position = 1; obj->save_flags = 1; obj->save_anim = 1; @@ -151,12 +149,11 @@ static void M_Collision( return; } - const OBJECT *const obj = Object_Get(item->object_id); - if (!Lara_TestPosition(item, obj->bounds_func())) { + if (!Item_TestPosition(m_ZiplineHandleBounds, item, lara_item)) { return; } - Lara_AlignPosition(item, &m_ZiplineHandlePosition); + Item_AlignPosition(&m_ZiplineHandlePosition, item, lara_item); g_Lara.gun_status = LGS_HANDS_BUSY; lara_item->goal_anim_state = LS_ZIPLINE; diff --git a/src/tr2/game/objects/traps/gondola.c b/src/tr2/game/objects/traps/gondola.c index c74c6cb35..c098d3aa4 100644 --- a/src/tr2/game/objects/traps/gondola.c +++ b/src/tr2/game/objects/traps/gondola.c @@ -15,7 +15,6 @@ static void M_Setup(OBJECT *const obj) obj->collision_func = Object_Collision; obj->save_flags = 1; obj->save_anim = 1; - obj->save_position = 1; } static void M_Control(const int16_t item_num) diff --git a/src/tr2/game/objects/traps/mine.c b/src/tr2/game/objects/traps/mine.c index 3487225e4..42b8713a5 100644 --- a/src/tr2/game/objects/traps/mine.c +++ b/src/tr2/game/objects/traps/mine.c @@ -48,12 +48,8 @@ static void M_DetonateAll( g_LaraItem->flags |= IF_ONE_SHOT; } - const OBJECT *const obj = Object_Get(O_BOAT_BITS); - if (obj->loaded) { - boat_item->object_id = O_BOAT_BITS; - boat_item->mesh_bits = (1 << obj->mesh_count) - 1; - Item_Explode(boat_item_num, -1, 0); - } + boat_item->object_id = O_BOAT_BITS; + Item_Explode(boat_item_num, -1, 0); Item_Kill(boat_item_num); boat_item->object_id = O_BOAT; diff --git a/src/tr2/game/objects/traps/rolling_ball.c b/src/tr2/game/objects/traps/rolling_ball.c index 3b7847d17..627798ffb 100644 --- a/src/tr2/game/objects/traps/rolling_ball.c +++ b/src/tr2/game/objects/traps/rolling_ball.c @@ -1,5 +1,6 @@ #include "game/camera.h" #include "game/items.h" +#include "game/lara/misc.h" #include "game/objects/common.h" #include "game/random.h" #include "game/room.h" @@ -9,7 +10,6 @@ #include #include -#include #include #include @@ -167,7 +167,7 @@ static void M_Collision( if (lara_item->gravity) { if (coll->enable_baddie_push) { - Lara_Push(item, coll, coll->enable_hit, true); + Lara_Push(item, lara_item, coll, coll->enable_hit, true); } lara_item->hit_points -= ROLLING_BALL_DAMAGE_AIR; diff --git a/src/tr2/game/objects/traps/spike_wall.c b/src/tr2/game/objects/traps/spike_wall.c index fa6a8ceee..9a3784a4c 100644 --- a/src/tr2/game/objects/traps/spike_wall.c +++ b/src/tr2/game/objects/traps/spike_wall.c @@ -6,30 +6,16 @@ #include "game/spawn.h" #include "global/vars.h" -#include #include #define SPIKE_WALL_DAMAGE 20 #define SPIKE_WALL_SPEED 1 -static void M_Initialise(int16_t item_num); static void M_Move(int16_t item_num); -static void M_Reset(int16_t item_num); static void M_HitLara(ITEM *item); static void M_Setup(OBJECT *obj); static void M_Control(int16_t item_num); -static void M_Initialise(const int16_t item_num) -{ - ITEM *const item = Item_Get(item_num); - - GAME_VECTOR *const data = - GameBuf_Alloc(sizeof(GAME_VECTOR), GBUF_ITEM_DATA); - data->pos = item->pos; - data->room_num = item->room_num; - item->data = data; -} - static void M_Move(const int16_t item_num) { ITEM *const item = Item_Get(item_num); @@ -39,9 +25,9 @@ static void M_Move(const int16_t item_num) item->pos.x + (SPIKE_WALL_SPEED * Math_Sin(item->rot.y) >> WALL_SHIFT); int16_t room_num = item->room_num; - const SECTOR *const sector = Room_GetSector(x, item->pos.y, z, &room_num); + const SECTOR *const Sector = Room_GetSector(x, item->pos.y, z, &room_num); - if (Room_GetHeight(sector, x, item->pos.y, z) != item->pos.y) { + if (Room_GetHeight(Sector, x, item->pos.y, z) != item->pos.y) { item->status = IS_DEACTIVATED; } else { item->pos.z = z; @@ -54,24 +40,6 @@ static void M_Move(const int16_t item_num) Sound_Effect(SFX_DOOR_SLIDE, &item->pos, SPM_NORMAL); } -static void M_Reset(const int16_t item_num) -{ - ITEM *const item = Item_Get(item_num); - item->status = IS_INACTIVE; - - const GAME_VECTOR *const data = item->data; - item->pos = data->pos; - if (item->room_num != data->room_num) { - Item_RemoveDrawn(item_num); - ROOM *const room = Room_Get(data->room_num); - item->next_item = room->item_num; - room->item_num = item_num; - item->room_num = data->room_num; - } - - Item_RemoveActive(item_num); -} - static void M_HitLara(ITEM *const item) { Lara_TakeDamage(SPIKE_WALL_DAMAGE, true); @@ -86,7 +54,6 @@ static void M_HitLara(ITEM *const item) static void M_Setup(OBJECT *const obj) { - obj->initialise_func = M_Initialise; obj->control_func = M_Control; obj->collision_func = Object_Collision; obj->save_position = 1; @@ -97,9 +64,7 @@ static void M_Control(const int16_t item_num) { ITEM *const item = Item_Get(item_num); - if (!Item_IsTriggerActive(item)) { - M_Reset(item_num); - } else if (item->status != IS_DEACTIVATED) { + if (Item_IsTriggerActive(item) && item->status != IS_DEACTIVATED) { M_Move(item_num); } diff --git a/src/tr2/game/objects/traps/springboard.c b/src/tr2/game/objects/traps/springboard.c index f181329bf..568ab69c8 100644 --- a/src/tr2/game/objects/traps/springboard.c +++ b/src/tr2/game/objects/traps/springboard.c @@ -35,29 +35,17 @@ static void M_Control(const int16_t item_num) return; } - const LARA_INFO *const lara = Lara_GetLaraInfo(); - if (lara->skidoo != NO_ITEM) { - ITEM *const skidoo = Item_Get(lara->skidoo); - if (skidoo->object_id != O_SKIDOO_FAST - && skidoo->object_id != O_SKIDOO_ARMED) { - return; - } - - skidoo->fall_speed = -200; - skidoo->pos.y -= STEP_L; - } else { - if (lara_item->current_anim_state == LS_BACK - || lara_item->current_anim_state == LS_FAST_BACK) { - lara_item->speed = -lara_item->speed; - } - - lara_item->fall_speed = -240; - lara_item->gravity = 1; - - Item_SwitchToAnim(lara_item, LA_FALL_START, 0); - lara_item->current_anim_state = LS_JUMP_FORWARD; - lara_item->goal_anim_state = LS_JUMP_FORWARD; + if (lara_item->current_anim_state == LS_BACK + || lara_item->current_anim_state == LS_FAST_BACK) { + lara_item->speed = -lara_item->speed; } + + lara_item->fall_speed = -240; + lara_item->gravity = 1; + + Item_SwitchToAnim(lara_item, LA_FALL_START, 0); + lara_item->current_anim_state = LS_JUMP_FORWARD; + lara_item->goal_anim_state = LS_JUMP_FORWARD; item->goal_anim_state = SPRINGBOARD_STATE_ON; } diff --git a/src/tr2/game/objects/vars.c b/src/tr2/game/objects/vars.c index 6719ddbb2..865daa971 100644 --- a/src/tr2/game/objects/vars.c +++ b/src/tr2/game/objects/vars.c @@ -24,9 +24,7 @@ const GAME_OBJECT_ID g_EnemyObjects[] = { O_WORKER_5, O_JELLY, O_SPIDER, - O_WOLF, O_BIG_SPIDER, - O_BEAR, O_CROW, O_TIGER, O_BARTOLI, @@ -66,7 +64,6 @@ const GAME_OBJECT_ID g_AllyObjects[] = { O_WINSTON, O_MONK_1, O_MONK_2, - O_MONK_3, O_DYING_MONK, NO_OBJECT, // Lara's social skills: still loading... @@ -110,15 +107,6 @@ const GAME_OBJECT_ID g_PickupObjects[] = { // clang-format on }; -const GAME_OBJECT_ID g_SecretObjects[] = { - // clang-format off - O_SECRET_1, - O_SECRET_2, - O_SECRET_3, - NO_OBJECT, - // clang-format on -}; - const GAME_OBJECT_ID g_DoorObjects[] = { // clang-format off O_DOOR_TYPE_1, @@ -242,14 +230,6 @@ const GAME_OBJECT_ID g_InvObjects[] = { // clang-format on }; -const GAME_OBJECT_ID g_WaterSpriteObjects[] = { - // clang-format off - O_WATERFALL, - O_SPLASH, - O_BUBBLE, - // clang-format on -}; - const GAME_OBJECT_PAIR g_ItemToInvObjectMap[] = { // clang-format off { O_COMPASS_ITEM, O_COMPASS_OPTION }, diff --git a/src/tr2/game/objects/vars.h b/src/tr2/game/objects/vars.h index 16d172938..c15fb9625 100644 --- a/src/tr2/game/objects/vars.h +++ b/src/tr2/game/objects/vars.h @@ -4,4 +4,3 @@ extern const GAME_OBJECT_ID g_DoorObjects[]; extern const GAME_OBJECT_ID g_TrapdoorObjects[]; -extern const GAME_OBJECT_ID g_SecretObjects[]; diff --git a/src/tr2/game/objects/vehicles/boat.c b/src/tr2/game/objects/vehicles/boat.c index 7464e5ceb..d3762c7fb 100644 --- a/src/tr2/game/objects/vehicles/boat.c +++ b/src/tr2/game/objects/vehicles/boat.c @@ -46,7 +46,6 @@ #define BOAT_TURN (DEG_1 / 8) // = 22 #define BOAT_MAX_TURN (DEG_1 * 4) // = 728 #define BOAT_SOUND_CEILING (WALL_L * 5) // = 5120 -#define BOAT_SHIFT_Y (-5) typedef enum { BOAT_STATE_GET_ON = 0, @@ -165,7 +164,7 @@ static int32_t M_TestWaterHeight( } } - return height + BOAT_SHIFT_Y; + return height - 5; } static void M_DoWakeEffect(const ITEM *const boat) @@ -619,7 +618,7 @@ static void M_Collision( ITEM *const boat = Item_Get(item_num); lara->pos.x = boat->pos.x; - lara->pos.y = boat->pos.y + BOAT_SHIFT_Y; + lara->pos.y = boat->pos.y - 5; lara->pos.z = boat->pos.z; lara->gravity = 0; lara->rot.x = 0; @@ -657,8 +656,8 @@ static void M_Control(const int16_t item_num) const int32_t hfr = M_TestWaterHeight(boat, BOAT_FRONT, BOAT_SIDE, &fr); int16_t room_num = boat->room_num; - const SECTOR *sector = Room_GetSector( - boat->pos.x, boat->pos.y + BOAT_SHIFT_Y, boat->pos.z, &room_num); + const SECTOR *sector = + Room_GetSector(boat->pos.x, boat->pos.y, boat->pos.z, &room_num); int32_t height = Room_GetHeight(sector, boat->pos.x, boat->pos.y, boat->pos.z); const int32_t ceiling = @@ -696,7 +695,7 @@ static void M_Control(const int16_t item_num) } } - boat->floor = height + BOAT_SHIFT_Y; + boat->floor = height - 5; if (boat_data->water == NO_HEIGHT) { boat_data->water = height; } else { @@ -742,7 +741,7 @@ static void M_Control(const int16_t item_num) Room_TestTriggers(boat); sector = Room_GetSector( - lara->pos.x, lara->pos.y + BOAT_SHIFT_Y, lara->pos.z, &room_num); + lara->pos.x, lara->pos.y - 5, lara->pos.z, &room_num); if (room_num != g_LaraItem->room_num) { Item_NewRoom(g_Lara.item_num, room_num); } @@ -770,7 +769,7 @@ static void M_Control(const int16_t item_num) : boat->speed; boat_data->pitch += ((pitch - boat_data->pitch) >> 2); - if (boat->speed != 0 && water_height + BOAT_SHIFT_Y != boat->pos.y) { + if (boat->speed != 0 && water_height - 5 != boat->pos.y) { Sound_Effect(SFX_BOAT_ENGINE, &boat->pos, SPM_NORMAL); } else if (boat->speed > 20) { Sound_Effect( @@ -785,7 +784,7 @@ static void M_Control(const int16_t item_num) + ((0x10000 - (BOAT_MAX_SPEED - boat_data->pitch) * 100) << 8)); } - if (boat->speed && water_height + BOAT_SHIFT_Y == boat->pos.y) { + if (boat->speed && water_height - 5 == boat->pos.y) { M_DoWakeEffect(boat); } diff --git a/src/tr2/game/objects/vehicles/skidoo_armed.c b/src/tr2/game/objects/vehicles/skidoo_armed.c index 01f88c96b..b3f60109c 100644 --- a/src/tr2/game/objects/vehicles/skidoo_armed.c +++ b/src/tr2/game/objects/vehicles/skidoo_armed.c @@ -1,10 +1,10 @@ #include "game/items.h" #include "game/lara/control.h" +#include "game/lara/misc.h" #include "game/objects/creatures/skidoo_driver.h" #include "global/vars.h" #include -#include #include #define SKIDOO_ARMED_RADIUS (WALL_L / 3) // = 341 @@ -46,7 +46,8 @@ static void M_Collision( if (coll->enable_baddie_push) { Lara_Push( - item, coll, item->speed > 0 ? coll->enable_hit : false, false); + item, lara_item, coll, item->speed > 0 ? coll->enable_hit : false, + false); } if (g_Lara.skidoo == NO_ITEM && item->speed > 0) { diff --git a/src/tr2/game/option/option.c b/src/tr2/game/option/option.c index 0df640c96..6f1237358 100644 --- a/src/tr2/game/option/option.c +++ b/src/tr2/game/option/option.c @@ -73,7 +73,7 @@ void Option_Draw(INVENTORY_ITEM *const item) Option_Passport_Draw(item); break; case O_COMPASS_OPTION: - Option_Compass_Draw(); + Option_Compass_Draw(item); break; case O_DETAIL_OPTION: Option_Detail_Draw(item); diff --git a/src/tr2/game/option/option.h b/src/tr2/game/option/option.h index 5c6fd04fc..c6f81514d 100644 --- a/src/tr2/game/option/option.h +++ b/src/tr2/game/option/option.h @@ -27,5 +27,5 @@ void Option_Controls_ShowControls(void); void Option_Controls_UpdateText(void); void Option_Compass_Control(INVENTORY_ITEM *item, bool is_busy); -void Option_Compass_Draw(void); +void Option_Compass_Draw(INVENTORY_ITEM *item); void Option_Compass_Shutdown(void); diff --git a/src/tr2/game/option/option_compass.c b/src/tr2/game/option/option_compass.c index ed4d36437..77ada6d52 100644 --- a/src/tr2/game/option/option_compass.c +++ b/src/tr2/game/option/option_compass.c @@ -1,76 +1,67 @@ #include "game/game.h" #include "game/input.h" #include "game/option/option.h" -#include "game/savegame.h" +#include "game/requester.h" #include "game/sound.h" +#include "game/stats.h" +#include "game/ui/widgets/stats_dialog.h" #include "global/vars.h" -#include - #include -typedef struct { - bool ui_active; - UI_STATS_DIALOG_STATE ui_state; -} M_PRIV; +static UI_WIDGET *m_Dialog = nullptr; -static M_PRIV m_Priv = {}; +static void M_Init(void); -static void M_Init(M_PRIV *p); -static void M_Shutdown(M_PRIV *p); - -static void M_Init(M_PRIV *const p) +static void M_Init(void) { - p->ui_active = true; - UI_StatsDialog_Init( - &p->ui_state, - (UI_STATS_DIALOG_ARGS) { - .mode = Game_IsInGym() ? UI_STATS_DIALOG_MODE_ASSAULT_COURSE - : UI_STATS_DIALOG_MODE_LEVEL, - .level_num = Game_GetCurrentLevel()->num, - .style = UI_STATS_DIALOG_STYLE_BORDERED, - }); + m_Dialog = UI_StatsDialog_Create((UI_STATS_DIALOG_ARGS) { + .mode = Game_IsInGym() ? UI_STATS_DIALOG_MODE_ASSAULT_COURSE + : UI_STATS_DIALOG_MODE_LEVEL, + .level_num = Game_GetCurrentLevel()->num, + .style = UI_STATS_DIALOG_STYLE_BORDERED, + }); } -static void M_Shutdown(M_PRIV *const p) +static void M_Shutdown(void) { - if (p->ui_active) { - p->ui_active = false; - UI_StatsDialog_Free(&p->ui_state); + if (m_Dialog != nullptr) { + m_Dialog->free(m_Dialog); + m_Dialog = nullptr; } } -void Option_Compass_Control(INVENTORY_ITEM *const inv_item, const bool is_busy) +void Option_Compass_Control(INVENTORY_ITEM *const item, const bool is_busy) { - M_PRIV *const p = &m_Priv; if (is_busy) { return; } - if (!p->ui_active) { - M_Init(p); + char buffer[32]; + const int32_t sec = g_SaveGame.current_stats.timer / FRAMES_PER_SECOND; + sprintf(buffer, "%02d:%02d:%02d", sec / 3600, sec / 60 % 60, sec % 60); + + if (m_Dialog == nullptr) { + M_Init(); } - UI_StatsDialog_Control(&p->ui_state); + m_Dialog->control(m_Dialog); if (g_InputDB.menu_confirm || g_InputDB.menu_back) { - M_Shutdown(p); - inv_item->anim_direction = 1; - inv_item->goal_frame = inv_item->frames_total - 1; + item->anim_direction = 1; + item->goal_frame = item->frames_total - 1; } Sound_Effect(SFX_MENU_STOPWATCH, 0, SPM_ALWAYS); } -void Option_Compass_Draw(void) +void Option_Compass_Draw(INVENTORY_ITEM *const item) { - M_PRIV *const p = &m_Priv; - if (p->ui_active) { - UI_StatsDialog(&p->ui_state); + if (m_Dialog != nullptr) { + m_Dialog->draw(m_Dialog); } } void Option_Compass_Shutdown(void) { - M_PRIV *const p = &m_Priv; - M_Shutdown(p); + M_Shutdown(); } diff --git a/src/tr2/game/option/option_controls.c b/src/tr2/game/option/option_controls.c index 0d1321378..b544d4cb0 100644 --- a/src/tr2/game/option/option_controls.c +++ b/src/tr2/game/option/option_controls.c @@ -1,58 +1,61 @@ #include "game/option/option.h" +#include "game/ui/widgets/controls_dialog.h" #include "global/vars.h" #include -#include +#include -typedef struct { - int32_t listeners[2]; - struct { - bool is_ready; - UI_CONTROLS_STATE state; - } ui; -} M_PRIV; +static UI_WIDGET *m_Dialog; +static UI_CONTROLS_CONTROLLER m_Controller; +static int32_t m_Listener1; +static int32_t m_Listener2; -static M_PRIV m_Priv = {}; - -static void M_Init(M_PRIV *p); -static void M_Shutdown(M_PRIV *p); +static void M_Init(void); +static void M_Shutdown(void); static void M_HandleLayoutChange(const EVENT *event, void *user_data); static void M_HandleKeyChange(const EVENT *event, void *user_data); -static void M_Init(M_PRIV *const p) +static void M_Init(void) { - UI_Controls_Init(&p->ui.state); - p->ui.is_ready = true; - p->listeners[0] = EventManager_Subscribe( - p->ui.state.events, "layout_change", nullptr, M_HandleLayoutChange, p); - p->listeners[1] = EventManager_Subscribe( - p->ui.state.events, "key_change", nullptr, M_HandleKeyChange, p); + UI_ControlsController_Init(&m_Controller); + m_Controller.active_layout = g_Config.input.keyboard_layout; + + m_Dialog = UI_ControlsDialog_Create(&m_Controller); + m_Listener1 = EventManager_Subscribe( + m_Controller.events, "layout_change", nullptr, M_HandleLayoutChange, + nullptr); + m_Listener2 = EventManager_Subscribe( + m_Controller.events, "key_change", nullptr, M_HandleKeyChange, nullptr); } -static void M_Shutdown(M_PRIV *const p) +static void M_Shutdown(void) { - if (p->ui.is_ready) { - EventManager_Unsubscribe(p->ui.state.events, p->listeners[0]); - EventManager_Unsubscribe(p->ui.state.events, p->listeners[1]); - UI_Controls_Free(&p->ui.state); - p->ui.is_ready = false; + if (m_Dialog == nullptr) { + return; } + + m_Dialog->free(m_Dialog); + m_Dialog = nullptr; + + EventManager_Unsubscribe(m_Controller.events, m_Listener1); + EventManager_Unsubscribe(m_Controller.events, m_Listener2); + + UI_ControlsController_Shutdown(&m_Controller); } static void M_HandleLayoutChange(const EVENT *event, void *user_data) { - const M_PRIV *const p = user_data; - switch (p->ui.state.backend) { + switch (m_Controller.backend) { case INPUT_BACKEND_KEYBOARD: - g_Config.input.keyboard_layout = p->ui.state.editor_state.active_layout; + g_Config.input.keyboard_layout = m_Controller.active_layout; break; case INPUT_BACKEND_CONTROLLER: - g_Config.input.controller_layout = - p->ui.state.editor_state.active_layout; + g_Config.input.controller_layout = m_Controller.active_layout; break; default: break; } + Config_Write(); } @@ -63,22 +66,22 @@ static void M_HandleKeyChange(const EVENT *event, void *user_data) void Option_Controls_Shutdown(void) { - M_Shutdown(&m_Priv); + M_Shutdown(); } void Option_Controls_Control(INVENTORY_ITEM *const item, const bool is_busy) { - M_PRIV *const p = &m_Priv; if (is_busy) { return; } - if (!p->ui.is_ready) { - M_Init(p); + if (m_Dialog == nullptr) { + M_Init(); } - if (UI_Controls_Control(&p->ui.state)) { - M_Shutdown(p); + m_Dialog->control(m_Dialog); + if (m_Controller.state == UI_CONTROLS_STATE_EXIT) { + Option_Controls_Shutdown(); } else { g_Input = (INPUT_STATE) {}; g_InputDB = (INPUT_STATE) {}; @@ -87,8 +90,7 @@ void Option_Controls_Control(INVENTORY_ITEM *const item, const bool is_busy) void Option_Controls_Draw(INVENTORY_ITEM *const item) { - M_PRIV *const p = &m_Priv; - if (p->ui.is_ready) { - UI_Controls(&p->ui.state); + if (m_Dialog != nullptr) { + m_Dialog->draw(m_Dialog); } } diff --git a/src/tr2/game/option/option_detail.c b/src/tr2/game/option/option_detail.c index 2f4fb0184..9ac45a404 100644 --- a/src/tr2/game/option/option_detail.c +++ b/src/tr2/game/option/option_detail.c @@ -1,57 +1,16 @@ #include "game/input.h" #include "game/option/option.h" #include "game/text.h" -#include "game/ui/dialogs/graphic_settings.h" #include "global/vars.h" -typedef struct { - struct { - bool is_ready; - UI_GRAPHIC_SETTINGS_STATE state; - } ui; -} M_PRIV; - -static M_PRIV m_Priv = {}; - -static void M_Init(M_PRIV *p); -static void M_Shutdown(M_PRIV *p); - -static void M_Init(M_PRIV *const p) -{ - p->ui.is_ready = true; - UI_GraphicSettings_Init(&p->ui.state); -} - -static void M_Shutdown(M_PRIV *const p) -{ - if (p->ui.is_ready) { - p->ui.is_ready = false; - UI_GraphicSettings_Free(&p->ui.state); - } -} - void Option_Detail_Control(INVENTORY_ITEM *const item, const bool is_busy) { - M_PRIV *const p = &m_Priv; - if (is_busy) { - return; - } - if (!p->ui.is_ready) { - M_Init(p); - } - UI_GraphicSettings_Control(&p->ui.state); } void Option_Detail_Draw(INVENTORY_ITEM *const item) { - M_PRIV *const p = &m_Priv; - if (p->ui.is_ready) { - UI_GraphicSettings(&p->ui.state); - } } void Option_Detail_Shutdown(void) { - M_PRIV *const p = &m_Priv; - M_Shutdown(p); } diff --git a/src/tr2/game/option/option_passport.c b/src/tr2/game/option/option_passport.c index 1da73aebc..6c90706a2 100644 --- a/src/tr2/game/option/option_passport.c +++ b/src/tr2/game/option/option_passport.c @@ -1,19 +1,18 @@ #include "decomp/decomp.h" +#include "decomp/savegame.h" #include "game/game.h" #include "game/game_flow.h" #include "game/game_string.h" #include "game/input.h" -#include "game/inventory.h" #include "game/inventory_ring.h" #include "game/option/option.h" -#include "game/savegame.h" +#include "game/requester.h" #include "game/sound.h" #include "game/text.h" #include "global/vars.h" #include #include -#include typedef enum { M_ROLE_LOAD_GAME, @@ -23,66 +22,30 @@ typedef enum { M_ROLE_EXIT, } M_PAGE_ROLE; -typedef enum { - M_MODE_BROWSE, - M_MODE_PICK_OPTION, -} M_PAGE_MODE; - typedef struct { bool available; M_PAGE_ROLE role; } M_PAGE; static struct { - M_PAGE_MODE mode; int32_t current_page; int32_t active_page; int32_t selection; M_PAGE pages[3]; - bool is_ready; - struct { - bool is_ready; - UI_NEW_GAME_STATE state; - } new_game; - struct { - UI_PLAY_ANY_LEVEL_DIALOG_STATE *state; - } play_any_level; - struct { - UI_SAVE_SLOT_DIALOG_STATE *state; - } save_slot; + bool page_ready; } m_State = { .active_page = -1 }; TEXTSTRING *m_SubtitleText = nullptr; -static void M_ChangePageTextContent(const char *title); static void M_SetPage(int32_t page, M_PAGE_ROLE role, bool available); -static void M_InitRequesters(void); -static void M_FreeRequesters(void); static void M_DeterminePages(void); static void M_RemoveAllText(void); -static void M_InitSaveRequester(M_PAGE_ROLE page_role); -static void M_ShowSaves(INVENTORY_ITEM *inv_item); -static void M_LoadGame(INVENTORY_ITEM *inv_item); -static void M_SaveGame(INVENTORY_ITEM *inv_item); -static void M_NewGame(void); -static void M_PlayAnyLevel(INVENTORY_ITEM *inv_item); -static int32_t M_GetCurrentPage(const INVENTORY_ITEM *inv_item); -static bool M_IsFlipping(const INVENTORY_ITEM *inv_item); static void M_FlipLeft(INVENTORY_ITEM *inv_item); static void M_FlipRight(INVENTORY_ITEM *inv_item); static void M_Close(INVENTORY_ITEM *inv_item); -static void M_ShowPage(INVENTORY_ITEM *inv_item); +static void M_ShowPage(const INVENTORY_ITEM *inv_item); static void M_HandleFlipInputs(void); -static void M_ChangePageTextContent(const char *const title) -{ - if (m_SubtitleText == nullptr) { - m_SubtitleText = Text_Create(0, -16, title); - Text_AlignBottom(m_SubtitleText, true); - Text_CentreH(m_SubtitleText, true); - } -} - static void M_SetPage( const int32_t page, const M_PAGE_ROLE role, const bool available) { @@ -90,21 +53,9 @@ static void M_SetPage( m_State.pages[page].available = available; } -static void M_InitRequesters(void) -{ - UI_NewGame_Init(&m_State.new_game.state); -} - -static void M_FreeRequesters(void) -{ - UI_NewGame_Free(&m_State.new_game.state); - m_State.new_game.is_ready = false; - m_State.is_ready = false; -} - static void M_DeterminePages(void) { - const bool has_saves = Savegame_GetTotalCount() != 0; + const bool has_saves = g_SavedGames != 0; for (int32_t i = 0; i < 3; i++) { m_State.pages[i].available = false; @@ -112,7 +63,6 @@ static void M_DeterminePages(void) switch (g_Inv_Mode) { case INV_TITLE_MODE: - m_State.mode = M_MODE_BROWSE; M_SetPage(0, M_ROLE_LOAD_GAME, has_saves); M_SetPage( 1, @@ -122,20 +72,17 @@ static void M_DeterminePages(void) break; case INV_GAME_MODE: - m_State.mode = M_MODE_BROWSE; M_SetPage(0, M_ROLE_LOAD_GAME, has_saves); M_SetPage(1, M_ROLE_SAVE_GAME, true); M_SetPage(2, M_ROLE_EXIT, true); break; case INV_DEATH_MODE: - m_State.mode = M_MODE_BROWSE; M_SetPage(0, M_ROLE_LOAD_GAME, has_saves); M_SetPage(2, M_ROLE_EXIT, true); break; case INV_LOAD_MODE: - m_State.mode = M_MODE_BROWSE; if (has_saves) { M_SetPage(0, M_ROLE_LOAD_GAME, true); } else { @@ -144,7 +91,6 @@ static void M_DeterminePages(void) break; case INV_SAVE_MODE: - m_State.mode = M_MODE_BROWSE; M_SetPage(1, M_ROLE_SAVE_GAME, true); break; @@ -189,146 +135,16 @@ static void M_DeterminePages(void) static void M_RemoveAllText(void) { + if (g_LoadGameRequester.ready) { + Requester_Shutdown(&g_LoadGameRequester); + } + if (g_SaveGameRequester.ready) { + Requester_Shutdown(&g_SaveGameRequester); + } if (m_SubtitleText != nullptr) { Text_Remove(m_SubtitleText); m_SubtitleText = nullptr; } - if (m_State.save_slot.state != nullptr) { - UI_SaveSlotDialog_Free(m_State.save_slot.state); - m_State.save_slot.state = nullptr; - } - if (m_State.play_any_level.state != nullptr) { - UI_SaveSlotDialog_Free(m_State.save_slot.state); - m_State.play_any_level.state = nullptr; - } - M_FreeRequesters(); -} - -static void M_InitSaveRequester(const M_PAGE_ROLE role) -{ - int32_t save_slot = Savegame_GetMostRecentlyUsedSlot(); - if (save_slot == -1) { - save_slot = Savegame_GetMostRecentlyCreatedSlot(); - } - - const UI_SAVE_SLOT_DIALOG_TYPE dialog_type = role == M_ROLE_LOAD_GAME - ? UI_SAVE_SLOT_DIALOG_LOAD_GAME - : UI_SAVE_SLOT_DIALOG_SAVE_GAME; - m_State.save_slot.state = UI_SaveSlotDialog_Init(dialog_type, save_slot); -} - -static void M_ShowSaves(INVENTORY_ITEM *const inv_item) -{ - if (m_State.save_slot.state == nullptr) { - M_InitSaveRequester(m_State.pages[m_State.active_page].role); - } - const UI_SAVE_SLOT_DIALOG_CHOICE choice = - UI_SaveSlotDialog_Control(m_State.save_slot.state); - switch (choice.action) { - case UI_SAVE_SLOT_DIALOG_NO_CHOICE: - // prevent propagating confirmation input up - if (g_Input.menu_confirm) { - g_Input = (INPUT_STATE) {}; - g_InputDB = (INPUT_STATE) {}; - } - break; - - case UI_SAVE_SLOT_DIALOG_CANCEL: - if (g_Inv_Mode != INV_SAVE_MODE && g_Inv_Mode != INV_LOAD_MODE) { - M_Close(inv_item); - } - break; - - case UI_SAVE_SLOT_DIALOG_DETAILS: - g_Input = (INPUT_STATE) {}; - g_InputDB = (INPUT_STATE) {}; - break; - - case UI_SAVE_SLOT_DIALOG_CONFIRM: - m_State.selection = choice.slot_num; - break; - } -} - -static void M_LoadGame(INVENTORY_ITEM *const inv_item) -{ - M_ChangePageTextContent(GS(PASSPORT_LOAD_GAME)); - M_ShowSaves(inv_item); -} - -static void M_SaveGame(INVENTORY_ITEM *const inv_item) -{ - M_ChangePageTextContent(GS(PASSPORT_SAVE_GAME)); - M_ShowSaves(inv_item); -} - -static void M_NewGame(void) -{ - M_ChangePageTextContent(GS(PASSPORT_NEW_GAME)); - m_State.selection = GF_GetFirstLevel()->num; - if (m_State.mode == M_MODE_BROWSE) { - if (g_InputDB.menu_confirm - && (g_Config.gameplay.enable_game_modes - || g_Config.profile.new_game_plus_unlock)) { - g_Input = (INPUT_STATE) {}; - g_InputDB = (INPUT_STATE) {}; - m_State.mode = M_MODE_PICK_OPTION; - m_State.new_game.is_ready = true; - } - } else if (m_State.mode == M_MODE_PICK_OPTION) { - const int32_t choice = UI_NewGame_Control(&m_State.new_game.state); - if (choice == UI_REQUESTER_NO_CHOICE) { - g_Input = (INPUT_STATE) {}; - g_InputDB = (INPUT_STATE) {}; - } else if (choice == UI_REQUESTER_CANCEL) { - m_State.mode = M_MODE_BROWSE; - m_State.new_game.is_ready = false; - g_Input = (INPUT_STATE) {}; - g_InputDB = (INPUT_STATE) {}; - } else { - switch (choice) { - case 0: - Game_SetBonusFlag(GBF_NONE); - break; - case 1: - Game_SetBonusFlag(GBF_NGPLUS); - break; - case 2: - Game_SetBonusFlag(GBF_JAPANESE); - break; - case 3: - Game_SetBonusFlag(GBF_JAPANESE | GBF_NGPLUS); - break; - default: - Game_SetBonusFlag(GBF_NONE); - break; - } - } - } -} - -static void M_PlayAnyLevel(INVENTORY_ITEM *const inv_item) -{ - M_ChangePageTextContent(GS(PASSPORT_NEW_GAME)); - if (m_State.play_any_level.state == nullptr) { - m_State.play_any_level.state = UI_PlayAnyLevelDialog_Init(); - } - const int32_t choice = - UI_PlayAnyLevelDialog_Control(m_State.play_any_level.state); - if (choice != UI_PLAY_ANY_LEVEL_CHOICE_NO_CHOICE) { - m_State.selection = choice; - } -} - -static int32_t M_GetCurrentPage(const INVENTORY_ITEM *const inv_item) -{ - const int32_t frame = inv_item->goal_frame - inv_item->open_frame; - return frame % 5 == 0 ? frame / 5 : -1; -} - -static bool M_IsFlipping(const INVENTORY_ITEM *const inv_item) -{ - return M_GetCurrentPage(inv_item) == -1; } static void M_FlipLeft(INVENTORY_ITEM *const inv_item) @@ -349,7 +165,6 @@ static void M_FlipRight(INVENTORY_ITEM *const inv_item) static void M_Close(INVENTORY_ITEM *const inv_item) { - m_State.active_page = -1; M_RemoveAllText(); if (m_State.current_page == 2) { inv_item->anim_direction = 1; @@ -360,32 +175,90 @@ static void M_Close(INVENTORY_ITEM *const inv_item) } } -static void M_ShowPage(INVENTORY_ITEM *const inv_item) +static void M_ShowPage(const INVENTORY_ITEM *const inv_item) { switch (m_State.pages[m_State.active_page].role) { case M_ROLE_LOAD_GAME: - M_LoadGame(inv_item); + if (m_SubtitleText == nullptr) { + m_SubtitleText = Text_Create(0, -16, GS(PASSPORT_LOAD_GAME)); + Text_AlignBottom(m_SubtitleText, true); + Text_CentreH(m_SubtitleText, true); + } + + if (!g_LoadGameRequester.ready) { + GetSavedGamesList(&g_LoadGameRequester); + Requester_SetHeading( + &g_LoadGameRequester, GS(PASSPORT_LOAD_GAME), 0, nullptr, 0); + Requester_SetSize(&g_LoadGameRequester, 10, -32); + g_LoadGameRequester.ready = true; + } + m_State.selection = + Requester_Display(&g_LoadGameRequester, false, true) - 1; + if (m_State.selection >= 0 && !g_SavedLevels[m_State.selection]) { + m_State.selection = -1; + g_Input = (INPUT_STATE) {}; + g_InputDB = (INPUT_STATE) {}; + } break; - case M_ROLE_SAVE_GAME: - M_SaveGame(inv_item); + case M_ROLE_SAVE_GAME: { + if (m_SubtitleText == nullptr) { + m_SubtitleText = Text_Create(0, -16, GS(PASSPORT_SAVE_GAME)); + Text_AlignBottom(m_SubtitleText, true); + Text_CentreH(m_SubtitleText, true); + } + + if (!g_LoadGameRequester.ready) { + Requester_SetHeading( + &g_LoadGameRequester, GS(PASSPORT_SAVE_GAME), 0, nullptr, 0); + Requester_SetSize(&g_LoadGameRequester, 10, -32); + g_LoadGameRequester.ready = true; + } + m_State.selection = + Requester_Display(&g_LoadGameRequester, true, true) - 1; break; + } + + case M_ROLE_PLAY_ANY_LEVEL: { + if (!g_SaveGameRequester.ready) { + Requester_Init(&g_SaveGameRequester); + Requester_SetSize(&g_SaveGameRequester, 10, -32); + GetValidLevelsList(&g_SaveGameRequester); + Requester_SetHeading( + &g_SaveGameRequester, GS(PASSPORT_SELECT_LEVEL), 0, nullptr, 0); + g_SaveGameRequester.ready = true; + } + if (m_SubtitleText == nullptr) { + m_SubtitleText = Text_Create(0, -16, GS(PASSPORT_NEW_GAME)); + Text_AlignBottom(m_SubtitleText, true); + Text_CentreH(m_SubtitleText, true); + } + m_State.selection = + Requester_Display(&g_SaveGameRequester, true, true) - 1; + break; + } case M_ROLE_NEW_GAME: - M_NewGame(); - break; - - case M_ROLE_PLAY_ANY_LEVEL: - M_PlayAnyLevel(inv_item); + if (m_SubtitleText == nullptr) { + m_SubtitleText = Text_Create(0, -16, GS(PASSPORT_NEW_GAME)); + Text_AlignBottom(m_SubtitleText, true); + Text_CentreH(m_SubtitleText, true); + } + m_State.selection = GF_GetFirstLevel()->num; break; case M_ROLE_EXIT: - if (g_Inv_Mode == INV_TITLE_MODE) { - M_ChangePageTextContent(GS(PASSPORT_EXIT_GAME)); - } else if (g_GameFlow.is_demo_version) { - M_ChangePageTextContent(GS(PASSPORT_EXIT_DEMO)); - } else { - M_ChangePageTextContent(GS(PASSPORT_EXIT_TO_TITLE)); + if (m_SubtitleText == nullptr) { + if (g_Inv_Mode == INV_TITLE_MODE) { + m_SubtitleText = Text_Create(0, -16, GS(PASSPORT_EXIT_GAME)); + } else if (g_GameFlow.is_demo_version) { + m_SubtitleText = Text_Create(0, -16, GS(PASSPORT_EXIT_DEMO)); + } else { + m_SubtitleText = + Text_Create(0, -16, GS(PASSPORT_EXIT_TO_TITLE)); + } + Text_AlignBottom(m_SubtitleText, true); + Text_CentreH(m_SubtitleText, true); } break; } @@ -413,7 +286,6 @@ static void M_HandleFlipInputs(void) void Option_Passport_Control(INVENTORY_ITEM *const item, const bool is_busy) { if (m_State.active_page == -1) { - M_InitRequesters(); M_DeterminePages(); } @@ -426,32 +298,32 @@ void Option_Passport_Control(INVENTORY_ITEM *const item, const bool is_busy) InvRing_RemoveAllText(); - if (M_IsFlipping(item)) { + const int32_t frame = item->goal_frame - item->open_frame; + const int32_t page = frame % 5 == 0 ? frame / 5 : -1; + const bool is_flipping = page == -1; + if (is_flipping) { return; } - m_State.current_page = M_GetCurrentPage(item); + m_State.current_page = page; if (m_State.current_page < m_State.active_page) { M_FlipRight(item); - g_Input = (INPUT_STATE) {}; - g_InputDB = (INPUT_STATE) {}; } else if (m_State.current_page > m_State.active_page) { M_FlipLeft(item); - g_Input = (INPUT_STATE) {}; - g_InputDB = (INPUT_STATE) {}; } else { - m_State.is_ready = true; M_ShowPage(item); if (g_InputDB.menu_confirm) { g_Inv_ExtraData[0] = m_State.active_page; g_Inv_ExtraData[1] = m_State.selection; + m_State.active_page = -1; M_Close(item); } else if (g_InputDB.menu_back) { - if (g_Inv_Mode == INV_DEATH_MODE) { + if (g_Inv_Mode != INV_DEATH_MODE) { + m_State.active_page = -1; + M_Close(item); + } else { g_Input = (INPUT_STATE) {}; g_InputDB = (INPUT_STATE) {}; - } else { - M_Close(item); } } else { M_HandleFlipInputs(); @@ -461,34 +333,10 @@ void Option_Passport_Control(INVENTORY_ITEM *const item, const bool is_busy) void Option_Passport_Draw(INVENTORY_ITEM *const item) { - switch (m_State.pages[m_State.active_page].role) { - case M_ROLE_NEW_GAME: - if (m_State.new_game.is_ready) { - UI_NewGame(&m_State.new_game.state); - } - break; - - case M_ROLE_PLAY_ANY_LEVEL: - if (m_State.play_any_level.state != nullptr) { - UI_PlayAnyLevelDialog(m_State.play_any_level.state); - } - break; - - case M_ROLE_LOAD_GAME: - case M_ROLE_SAVE_GAME: - if (m_State.is_ready && m_State.save_slot.state != nullptr) { - UI_SaveSlotDialog(m_State.save_slot.state); - } - break; - - default: - break; - } } void Option_Passport_Shutdown(void) { M_RemoveAllText(); - UI_NewGame_Free(&m_State.new_game.state); m_State.active_page = -1; } diff --git a/src/tr2/game/option/option_sound.c b/src/tr2/game/option/option_sound.c index f5dec720f..f6cfab736 100644 --- a/src/tr2/game/option/option_sound.c +++ b/src/tr2/game/option/option_sound.c @@ -1,48 +1,132 @@ +#include "game/game_string.h" +#include "game/input.h" +#include "game/inventory_ring.h" +#include "game/music.h" #include "game/option/option.h" +#include "game/sound.h" +#include "game/text.h" +#include "global/vars.h" #include -#include +#include -typedef struct { - UI_SOUND_SETTINGS_STATE *ui; -} M_PRIV; +#include -static M_PRIV m_Priv = {}; +static TEXTSTRING *m_SoundText[4]; -static void M_Init(M_PRIV *const p) +static void M_InitText(void); +static void M_ShutdownText(void); + +static void M_InitText(void) { - p->ui = UI_SoundSettings_Init(); -} + CLAMPG(g_Config.audio.music_volume, 10); + CLAMPG(g_Config.audio.sound_volume, 10); -static void M_Shutdown(M_PRIV *const p) -{ - if (p->ui != nullptr) { - UI_SoundSettings_Free(p->ui); - p->ui = nullptr; + char text[32]; + sprintf(text, "\\{icon music} %2d", g_Config.audio.music_volume); + m_SoundText[0] = Text_Create(0, 0, text); + Text_AddBackground(m_SoundText[0], 128, 0, 0, 0, TS_REQUESTED); + Text_AddOutline(m_SoundText[0], TS_REQUESTED); + + sprintf(text, "\\{icon sound} %2d", g_Config.audio.sound_volume); + m_SoundText[1] = Text_Create(0, 25, text); + + m_SoundText[2] = Text_Create(0, -32, " "); + Text_AddBackground(m_SoundText[2], 140, 85, 0, 0, TS_BACKGROUND); + Text_AddOutline(m_SoundText[2], TS_BACKGROUND); + + m_SoundText[3] = Text_Create(0, -30, GS(SOUND_SET_VOLUMES)); + Text_AddBackground(m_SoundText[3], 136, 0, 0, 0, TS_HEADING); + Text_AddOutline(m_SoundText[3], TS_HEADING); + + for (int32_t i = 0; i < 4; i++) { + Text_CentreH(m_SoundText[i], true); + Text_CentreV(m_SoundText[i], true); } } -void Option_Sound_Control(INVENTORY_ITEM *const inv_item, const bool is_busy) +static void M_ShutdownText(void) { - M_PRIV *const p = &m_Priv; - if (is_busy) { - return; - } - if (p->ui == nullptr) { - M_Init(p); - } - UI_SoundSettings_Control(p->ui); -} - -void Option_Sound_Draw(INVENTORY_ITEM *const inv_item) -{ - M_PRIV *const p = &m_Priv; - if (p->ui != nullptr) { - UI_SoundSettings(p->ui); + for (int32_t i = 0; i < 4; i++) { + Text_Remove(m_SoundText[i]); + m_SoundText[i] = nullptr; } } void Option_Sound_Shutdown(void) { - M_Shutdown(&m_Priv); + M_ShutdownText(); +} + +void Option_Sound_Control(INVENTORY_ITEM *const item, const bool is_busy) +{ + if (is_busy) { + return; + } + + char text[32]; + + if (m_SoundText[0] == nullptr) { + M_InitText(); + } + + if (g_InputDB.menu_up && g_SoundOptionLine > 0) { + Text_RemoveOutline(m_SoundText[g_SoundOptionLine]); + Text_RemoveBackground(m_SoundText[g_SoundOptionLine]); + g_SoundOptionLine--; + Text_AddBackground( + m_SoundText[g_SoundOptionLine], 128, 0, 0, 0, TS_REQUESTED); + Text_AddOutline(m_SoundText[g_SoundOptionLine], TS_REQUESTED); + } + + if (g_InputDB.menu_down && g_SoundOptionLine < 1) { + Text_RemoveOutline(m_SoundText[g_SoundOptionLine]); + Text_RemoveBackground(m_SoundText[g_SoundOptionLine]); + g_SoundOptionLine++; + Text_AddBackground( + m_SoundText[g_SoundOptionLine], 128, 0, 0, 0, TS_REQUESTED); + Text_AddOutline(m_SoundText[g_SoundOptionLine], TS_REQUESTED); + } + + if (g_SoundOptionLine) { + bool changed = false; + if (g_InputDB.menu_left && g_Config.audio.sound_volume > 0) { + g_Config.audio.sound_volume--; + changed = true; + } else if (g_InputDB.menu_right && g_Config.audio.sound_volume < 10) { + g_Config.audio.sound_volume++; + changed = true; + } + + if (changed) { + sprintf(text, "\\{icon sound} %2d", g_Config.audio.sound_volume); + Text_ChangeText(m_SoundText[1], text); + Sound_SetMasterVolume(g_Config.audio.sound_volume); + Sound_Effect(SFX_MENU_PASSPORT, nullptr, SPM_ALWAYS); + } + } else { + bool changed = false; + if (g_InputDB.menu_left && g_Config.audio.music_volume > 0) { + g_Config.audio.music_volume--; + changed = true; + } else if (g_InputDB.menu_right && g_Config.audio.music_volume < 10) { + g_Config.audio.music_volume++; + changed = true; + } + + if (changed) { + sprintf(text, "\\{icon music} %2d", g_Config.audio.music_volume); + Text_ChangeText(m_SoundText[0], text); + Music_SetVolume(g_Config.audio.music_volume); + Sound_Effect(SFX_MENU_PASSPORT, nullptr, SPM_ALWAYS); + } + } + + if (g_InputDB.menu_confirm || g_InputDB.menu_back) { + Option_Sound_Shutdown(); + } +} + +void Option_Sound_Draw(INVENTORY_ITEM *const item) +{ } diff --git a/src/tr2/game/output.c b/src/tr2/game/output.c index 4f4f586d8..6fc348625 100644 --- a/src/tr2/game/output.c +++ b/src/tr2/game/output.c @@ -2,23 +2,18 @@ #include "game/clock.h" #include "game/inventory_ring.h" -#include "game/level.h" #include "game/random.h" #include "game/render/common.h" #include "game/render/priv.h" +#include "game/scaler.h" #include "game/shell.h" -#include "game/viewport.h" #include "global/vars.h" -#include #include #include #include #include -#include #include -#include -#include #include typedef enum { @@ -57,7 +52,6 @@ static NAMED_COLOR m_NamedColors[COLOR_NUMBER_OF] = { // clang-format on }; -static int32_t m_VBufCapacity = 0; static int32_t m_TickComp = 0; static int32_t m_LsAdder = 0; static int32_t m_LsDivider = 0; @@ -68,20 +62,13 @@ static int16_t m_ShadesTable[32]; static int32_t m_RandomTable[32]; static BACKGROUND_TYPE m_BackgroundType = BK_TRANSPARENT; static XYZ_32 m_LsVectorView = {}; - -static int32_t m_FogEnd = 0; -static RGB_F m_WaterColor = {}; - static bool m_IsWaterEffect = false; static bool m_IsWibbleEffect = false; static bool m_IsShadeEffect = false; static int32_t m_WibbleOffset = 0; - static bool m_IsSunsetEnabled = false; static int32_t m_SunsetTimer = 0; -static int32_t m_DepthBias = 0; - static void M_CalcRoomVertices(const ROOM_MESH *mesh, int32_t far_clip); static void M_CalcRoomVerticesWibble(const ROOM_MESH *mesh); static void M_DrawRoomSprites(const ROOM_MESH *mesh); @@ -184,14 +171,19 @@ static void M_CalcRoomVertices(const ROOM_MESH *const mesh, int32_t far_clip) const double persp = g_FltPersp / zv; const int32_t depth = zv_int >> W2V_SHIFT; vbuf->zv += base_z; - if (zv >= Output_GetFarZ()) { + + if (depth < FOG_END) { + if (depth > FOG_START) { + shade += depth - FOG_START; + } + vbuf->rhw = persp * g_FltRhwOPersp; + } else { + // clip_flags = far_clip; + shade = 0x1FFF; vbuf->zv = g_FltFarZ; + vbuf->rhw = persp * g_FltRhwOPersp; } - shade += Output_CalcFogShade(depth); - - vbuf->rhw = persp * g_FltRhwOPersp; - double xs = xv * persp + g_FltWinCenterX; double ys = yv * persp + g_FltWinCenterY; @@ -212,7 +204,7 @@ static void M_CalcRoomVertices(const ROOM_MESH *const mesh, int32_t far_clip) // clip_flags |= (~((uint8_t)(vbuf->zv / 0x155555.p0))) << 8; } - CLAMP(shade, 0, SHADE_MAX); + CLAMP(shade, 0, 0x1FFF); vbuf->g = shade; vbuf->clip = clip_flags; } @@ -303,11 +295,9 @@ static bool M_CalcObjectVertices( } const double persp = g_FltPersp / zv; - const double persp_biased = - g_FltPersp / (zv + (m_DepthBias << W2V_SHIFT)); vbuf->xs = persp * xv + g_FltWinCenterX; vbuf->ys = persp * yv + g_FltWinCenterY; - vbuf->rhw = persp_biased * g_FltRhwOPersp; + vbuf->rhw = persp * g_FltRhwOPersp; clip_flags = 0x00; if (vbuf->xs < g_FltWinLeft) { @@ -364,7 +354,7 @@ static void M_CalcVerticeLight(const OBJECT_MESH *const mesh) if (mesh->num_lights <= 0) { for (int32_t i = 0; i < -mesh->num_lights; i++) { int16_t shade = m_LsAdder + mesh->lighting.lights[i]; - CLAMP(shade, 0, SHADE_MAX); + CLAMP(shade, 0, 0x1FFF); g_PhdVBuf[i].g = shade; } @@ -373,7 +363,7 @@ static void M_CalcVerticeLight(const OBJECT_MESH *const mesh) if (m_LsDivider == 0) { int16_t shade = m_LsAdder; - CLAMP(shade, 0, SHADE_MAX); + CLAMP(shade, 0, 0x1FFF); for (int32_t i = 0; i < mesh->num_lights; i++) { g_PhdVBuf[i].g = shade; } @@ -406,7 +396,7 @@ static void M_CalcVerticeLight(const OBJECT_MESH *const mesh) const XYZ_16 *const normal = &mesh->lighting.normals[i]; int16_t shade = m_LsAdder + ((xv * normal->x + yv * normal->y + zv * normal->z) >> 16); - CLAMP(shade, 0, SHADE_MAX); + CLAMP(shade, 0, 0x1FFF); g_PhdVBuf[i].g = shade; } } @@ -418,75 +408,6 @@ static void M_CalcSkyboxLight(const OBJECT_MESH *const mesh) } } -static void M_ReserveVertexBuffer(void) -{ - BENCHMARK benchmark = Benchmark_Start(); - int32_t max_vertices = 1500; - for (int32_t i = 0; i < O_NUMBER_OF; i++) { - const OBJECT *const obj = Object_Get(i); - if (!obj->loaded) { - continue; - } - - for (int32_t j = 0; j < obj->mesh_count; j++) { - const OBJECT_MESH *const mesh = Object_GetMesh(obj->mesh_idx + j); - max_vertices = MAX(max_vertices, mesh->num_vertices); - } - } - - for (int32_t i = 0; i < MAX_STATIC_OBJECTS_3D; i++) { - const STATIC_OBJECT_3D *obj = Object_Get3DStatic(i); - if (!obj->loaded) { - continue; - } - - const OBJECT_MESH *const mesh = Object_GetMesh(obj->mesh_idx); - max_vertices = MAX(max_vertices, mesh->num_vertices); - } - - for (int32_t i = 0; i < Room_GetCount(); i++) { - const ROOM *const room = Room_Get(i); - max_vertices = MAX(max_vertices, room->mesh.num_vertices); - } - - Benchmark_End(&benchmark, nullptr); - if (max_vertices >= m_VBufCapacity) { - g_PhdVBuf = Memory_Realloc(g_PhdVBuf, max_vertices * sizeof(PHD_VBUF)); - m_VBufCapacity = max_vertices; - } -} - -void Output_ApplyLevelSettings(void) -{ - Output_SetWaterColor(Level_GetWaterColor()); - Output_SetFogStart(Level_GetFogStart() * WALL_L); - Output_SetFogEnd(Level_GetFogEnd() * WALL_L); - Viewport_Reset(); -} - -void Output_ObserveLevelLoad(void) -{ - M_ReserveVertexBuffer(); - Output_ApplyLevelSettings(); - for (int32_t i = 0; i < COLOR_NUMBER_OF; i++) { - m_NamedColors[i].palette_index = - Output_FindColor8(m_NamedColors[i].rgb); - } -} - -void Output_ObserveLevelUnload(void) -{ - Output_InitialiseTexturePages(0, true); - Output_InitialiseObjectTextures(0); - if (Output_GetBackgroundType() == BK_OBJECT) { - Output_UnloadBackground(); - } -} - -void Output_ObserveRoomFlip(const ROOM *room) -{ -} - void Output_DrawObjectMesh(const OBJECT_MESH *const mesh, const int32_t clip) { g_FltWinLeft = 0.0f; @@ -669,14 +590,14 @@ void Output_DrawSprite( if (flags & SPRF_SHADE) { const int32_t depth = zv >> W2V_SHIFT; - if (depth > Output_GetFogStart()) { - shade += depth - Output_GetFogStart(); - if (shade > SHADE_MAX) { + if (depth > FOG_START) { + shade += depth - FOG_START; + if (shade > 0x1FFF) { return; } } } else { - shade = SHADE_NEUTRAL; + shade = 0x1000; } Render_InsertSprite(zv, x0, y0, x1, y1, sprite_idx, shade); @@ -739,9 +660,14 @@ BACKGROUND_TYPE Output_GetBackgroundType(void) return m_BackgroundType; } -bool Output_LoadBackgroundFromImage(const IMAGE *const image) +bool Output_LoadBackgroundFromFile(const char *const file_name) { + IMAGE *const image = Image_CreateFromFile(file_name); + if (image == nullptr) { + return false; + } Render_LoadBackgroundFromImage(image); + Image_Free(image); m_BackgroundType = BK_IMAGE; return true; } @@ -770,7 +696,6 @@ void Output_UnloadBackground(void) { Render_UnloadBackground(); m_BackgroundType = BK_TRANSPARENT; - Output_ClearLastBackgroundPath(); } void Output_InsertBackPolygon( @@ -805,7 +730,7 @@ void Output_DrawScreenLine( x, y, x + x_len, y + y_len, g_PhdNearZ + 8 * z, color_idx); } -void Output_DrawScreenFrame( +void Output_DrawScreenBox( const int32_t sx, const int32_t sy, const int32_t z, const int32_t width, const int32_t height, const uint8_t color_idx, const void *const gour, const uint16_t flags) @@ -900,7 +825,7 @@ void Output_CalculateWibbleTable(void) for (int32_t i = 0; i < WIBBLE_SIZE; i++) { const int32_t sine = Math_Sin(i * DEG_360 / WIBBLE_SIZE); m_WibbleTable[i] = (sine * MAX_WIBBLE) >> W2V_SHIFT; - m_ShadesTable[i] = (sine * SHADE_CAUSTICS) >> W2V_SHIFT; + m_ShadesTable[i] = (sine * MAX_SHADE) >> W2V_SHIFT; m_RandomTable[i] = (Random_GetDraw() >> 5) - 0x01FF; for (int32_t j = 0; j < WIBBLE_SIZE; j++) { m_RoomLightTables[i].table[j] = (j - (WIBBLE_SIZE / 2)) * i @@ -975,41 +900,6 @@ int32_t Output_GetObjectBounds(const BOUNDS_16 *const bounds) return 1; } -int32_t Output_GetNearZ(void) -{ - return 20 << W2V_SHIFT; -} - -int32_t Output_GetFarZ(void) -{ - return Output_GetFogEnd() << W2V_SHIFT; -} - -void Output_SetWaterColor(const RGB_888 color) -{ - m_WaterColor.r = color.r / 255.0f; - m_WaterColor.g = color.g / 255.0f; - m_WaterColor.b = color.b / 255.0f; -} - -RGB_F Output_GetTint(void) -{ - if (Output_IsShadeEffect()) { - return m_WaterColor; - } - return (RGB_F) { 1.0f, 1.0f, 1.0f }; -} - -int32_t Output_GetFogEnd(void) -{ - return m_FogEnd; -} - -void Output_SetFogEnd(const int32_t dist) -{ - m_FogEnd = dist; -} - void Output_SetupBelowWater(const bool is_underwater) { Render_SetWet(is_underwater); @@ -1084,15 +974,13 @@ void Output_SetLightDivider(const int32_t divider) int32_t Output_CalcFogShade(const int32_t depth) { - const int32_t fog_start = Output_GetFogStart(); - const int32_t fog_end = Output_GetFogEnd(); - if (depth < fog_start) { - return 0; + if (depth > FOG_START) { + return depth - FOG_START; } - if (depth >= fog_end) { - return SHADE_MAX; + if (depth > FOG_END) { + return 0x1FFF; } - return (depth - fog_start) * SHADE_MAX / (fog_end - fog_start); + return 0; } int32_t Output_GetRoomLightShade(const ROOM_LIGHT_MODE mode) @@ -1112,56 +1000,10 @@ void Output_LightRoomVertices(const ROOM *const room) } } -void Output_DrawTextOutline( - const UI_STYLE ui_style, const int32_t x, const int32_t y, - const int32_t width, const int32_t height, const int32_t z, - const TEXT_STYLE text_style) +void Output_InitialiseNamedColors(void) { - const int32_t mesh_idx = Object_Get(O_TEXT_BOX)->mesh_idx; - - const int32_t offset = 4; - const int32_t x0 = x + offset; - const int32_t y0 = y + offset; - const int32_t x1 = x0 + width - offset * 2; - const int32_t y1 = y0 + height - offset * 2; - const int32_t scale_h = TEXT_BASE_SCALE; - const int32_t scale_v = TEXT_BASE_SCALE; - - Output_DrawScreenSprite( - x0, y0, z, scale_h, scale_v, mesh_idx + 0, SHADE_NEUTRAL, 0); - Output_DrawScreenSprite( - x1, y0, z, scale_h, scale_v, mesh_idx + 1, SHADE_NEUTRAL, 0); - Output_DrawScreenSprite( - x1, y1, z, scale_h, scale_v, mesh_idx + 2, SHADE_NEUTRAL, 0); - Output_DrawScreenSprite( - x0, y1, z, scale_h, scale_v, mesh_idx + 3, SHADE_NEUTRAL, 0); - - int32_t w = (width - offset * 2) * TEXT_BASE_SCALE / 8; - int32_t h = (height - offset * 2) * TEXT_BASE_SCALE / 8; - - Output_DrawScreenSprite( - x0, y0, z, w, scale_v, mesh_idx + 4, SHADE_NEUTRAL, 0); - Output_DrawScreenSprite( - x1, y0, z, scale_h, h, mesh_idx + 5, SHADE_NEUTRAL, 0); - Output_DrawScreenSprite( - x0, y1, z, w, scale_v, mesh_idx + 6, SHADE_NEUTRAL, 0); - Output_DrawScreenSprite( - x0, y0, z, scale_h, h, mesh_idx + 7, SHADE_NEUTRAL, 0); -} - -void Output_DrawTextBackground( - const UI_STYLE ui_style, const int32_t x, const int32_t y, const int32_t w, - const int32_t h, const int32_t z, const TEXT_STYLE text_style) -{ - Output_DrawScreenFBox(x, y, z, w, h, 0, nullptr, 0); -} - -int32_t Output_GetDepthBias(void) -{ - return m_DepthBias; -} - -void Output_SetDepthBias(const int32_t bias) -{ - m_DepthBias = bias; + for (int32_t i = 0; i < COLOR_NUMBER_OF; i++) { + m_NamedColors[i].palette_index = + Output_FindColor8(m_NamedColors[i].rgb); + } } diff --git a/src/tr2/game/output.h b/src/tr2/game/output.h index b09802eed..6f32d461d 100644 --- a/src/tr2/game/output.h +++ b/src/tr2/game/output.h @@ -22,8 +22,6 @@ typedef struct { float g; } VERTEX_INFO; -void Output_ApplyLevelSettings(void); - void Output_DrawObjectMesh(const OBJECT_MESH *mesh, int32_t clip); void Output_DrawObjectMesh_I(const OBJECT_MESH *mesh, int32_t clip); void Output_DrawRoom(const ROOM_MESH *mesh, bool is_outside); @@ -62,7 +60,7 @@ void Output_DrawScreenLine( int32_t x, int32_t y, int32_t z, int32_t x_len, int32_t y_len, uint8_t color_idx, const void *gour, uint16_t flags); -void Output_DrawScreenFrame( +void Output_DrawScreenBox( int32_t sx, int32_t sy, int32_t z, int32_t width, int32_t height, uint8_t color_idx, const void *gour, uint16_t flags); @@ -76,6 +74,7 @@ void Output_DrawAirBar(int32_t percent); BACKGROUND_TYPE Output_GetBackgroundType(void); void Output_LoadBackgroundFromObject(void); +void Output_InitialiseNamedColors(void); void Output_InsertShadow( int16_t radius, const BOUNDS_16 *bounds, const ITEM *item); @@ -88,12 +87,3 @@ void Output_SetShadeEffect(bool shade_effect); bool Output_IsShadeEffect(void); void Output_SetSunsetEnabled(bool enabled); void Output_SetSunsetTimer(int32_t timer); - -int32_t Output_GetNearZ(void); -int32_t Output_GetFarZ(void); - -void Output_SetWaterColor(RGB_888 color); -RGB_F Output_GetTint(void); - -int32_t Output_GetDepthBias(void); -void Output_SetDepthBias(int32_t bias); diff --git a/src/tr2/game/overlay.c b/src/tr2/game/overlay.c index 84d697f77..3cd0ea8ca 100644 --- a/src/tr2/game/overlay.c +++ b/src/tr2/game/overlay.c @@ -7,17 +7,14 @@ #include "game/inventory.h" #include "game/music.h" #include "game/objects/common.h" -#include "game/objects/vars.h" #include "game/output.h" -#include "game/savegame.h" +#include "game/scaler.h" #include "game/text.h" #include "game/viewport.h" #include "global/vars.h" #include -#include #include -#include #include #include @@ -158,15 +155,14 @@ static void M_AnimatePickups(const int32_t frames) static void M_DrawAssaultTimer(void) { - if (!Game_IsInGym() || !Gym_IsAssaultTimerDisplay()) { + if (!Game_IsInGym() || !g_IsAssaultTimerDisplay) { return; } char buffer[32]; - const RESUME_INFO *const resume = - Savegame_GetCurrentInfo(Game_GetCurrentLevel()); - const int32_t total_sec = resume->stats.timer / FRAMES_PER_SECOND; - const int32_t frame = resume->stats.timer % FRAMES_PER_SECOND; + const int32_t total_sec = + g_SaveGame.current_stats.timer / FRAMES_PER_SECOND; + const int32_t frame = g_SaveGame.current_stats.timer % FRAMES_PER_SECOND; sprintf( buffer, "%d:%02d.%d", total_sec / 60, total_sec % 60, frame * 10 / FRAMES_PER_SECOND); @@ -211,8 +207,7 @@ static void M_DrawAssaultTimer(void) glyph_info[glyph_type].offset, SCALER_TARGET_ASSAULT_DIGITS); Output_DrawScreenSprite( x, y, 0, scale_h, scale_v, - Object_Get(O_ASSAULT_DIGITS)->mesh_idx + mesh_num, SHADE_NEUTRAL, - 0); + Object_Get(O_ASSAULT_DIGITS)->mesh_idx + mesh_num, 0x1000, 0); x += Scaler_Calc( glyph_info[glyph_type].width, SCALER_TARGET_ASSAULT_DIGITS); } @@ -260,7 +255,7 @@ static void M_DrawAirBar(void) static void M_DrawAmmoInfo(void) { if (g_Lara.gun_status != LGS_READY || g_OverlayStatus <= 0 - || Game_IsBonusFlagSet(GBF_NGPLUS)) { + || g_SaveGame.bonus_flag) { if (m_AmmoTextInfo != nullptr) { Text_Remove(m_AmmoTextInfo); m_AmmoTextInfo = nullptr; @@ -457,7 +452,7 @@ static void M_DrawPickup3D(const DISPLAY_PICKUP *const pickup) Matrix_RotY(pickup->rot_y); Output_SetLightDivider(0x6000); - Output_SetLightAdder(SHADE_LOW); + Output_SetLightAdder(LOW_LIGHT); Output_RotateLight(0, 0); Matrix_Push(); @@ -505,7 +500,7 @@ static void M_DrawPickupSprite(const DISPLAY_PICKUP *const pickup) // TODO: use proper scaling const int32_t scale = 12288 * g_PhdWinWidth / 640; const int16_t sprite_num = pickup->object->mesh_idx; - Output_DrawPickup(x, y, scale, sprite_num, SHADE_NEUTRAL); + Output_DrawPickup(x, y, scale, sprite_num, 4096); } static void M_DrawPickups(void) @@ -527,7 +522,7 @@ static void M_DrawPickups(void) void Overlay_AddDisplayPickup(const GAME_OBJECT_ID obj_id) { - if (Object_IsType(obj_id, g_SecretObjects)) { + if (obj_id == O_SECRET_1 || obj_id == O_SECRET_2 || obj_id == O_SECRET_3) { Music_Play(g_GameFlow.secret_track, MPM_ALWAYS); } diff --git a/src/tr2/game/render/common.c b/src/tr2/game/render/common.c index 82b232443..90a1704b6 100644 --- a/src/tr2/game/render/common.c +++ b/src/tr2/game/render/common.c @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -45,6 +44,7 @@ static RENDERER *M_GetRenderer(void) } else if (g_Config.rendering.render_mode == RM_HARDWARE) { r = &m_Renderer_HW; } + ASSERT(r != nullptr); return r; } @@ -75,6 +75,12 @@ static void M_ResetPolyList(void) static void M_SetGLBackend(const GFX_GL_BACKEND backend) { switch (backend) { + case GFX_GL_21: + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 0); + break; + case GFX_GL_33C: SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); @@ -94,6 +100,7 @@ void Render_Init(void) const GFX_GL_BACKEND backends_to_try[] = { // clang-format off GFX_GL_33C, + GFX_GL_21, GFX_GL_INVALID_BACKEND, // guard // clang-format on }; @@ -123,6 +130,7 @@ void Render_Init(void) void Render_Shutdown(void) { + LOG_DEBUG(""); RENDERER *const r = M_GetRenderer(); if (r != nullptr) { r->Close(r); @@ -194,7 +202,6 @@ void Render_SetupDisplay( void Render_BeginScene(void) { GFX_Context_Clear(); - GFX_Track_Reset(); RENDERER *const r = M_GetRenderer(); r->BeginScene(r); M_ResetPolyList(); diff --git a/src/tr2/game/render/hwr.c b/src/tr2/game/render/hwr.c index adc99c2ae..2f00339fd 100644 --- a/src/tr2/game/render/hwr.c +++ b/src/tr2/game/render/hwr.c @@ -29,6 +29,7 @@ typedef struct { GFX_2D_SURFACE *surface_pic; GFX_2D_SURFACE *surface_tex[GFX_MAX_TEXTURES]; int32_t texture_map[GFX_MAX_TEXTURES]; + int32_t env_map_texture; int32_t current_texture; } M_PRIV; @@ -129,10 +130,13 @@ static void M_ShadeColor( GFX_3D_VERTEX *const target, uint32_t red, uint32_t green, const uint32_t blue, const uint8_t alpha) { - const RGB_F tint = Output_GetTint(); - target->r = red * tint.r; - target->g = green * tint.g; - target->b = blue * tint.b; + if (Output_IsShadeEffect()) { + red /= 2; + green = green * 7 / 8; + } + target->r = red; + target->g = green; + target->b = blue; target->a = alpha; } @@ -146,22 +150,22 @@ static void M_ShadeLightColor( GFX_3D_VERTEX *const target, uint32_t shade, const bool is_textured, uint32_t red, uint32_t green, uint32_t blue, const uint8_t alpha) { - CLAMPG(shade, SHADE_MAX); + CLAMPG(shade, 0x1FFF); if (g_Config.rendering.lighting_contrast == LIGHTING_CONTRAST_MEDIUM) { CLAMPL(shade, 0x800); } if (g_Config.rendering.lighting_contrast != LIGHTING_CONTRAST_LOW && is_textured) { - shade = SHADE_NEUTRAL + shade / 2; + shade = 0x1000 + shade / 2; } if (g_Config.rendering.lighting_contrast == LIGHTING_CONTRAST_LOW && !is_textured) { - CLAMPL(shade, SHADE_NEUTRAL); + CLAMPL(shade, 0x1000); } - if (shade != SHADE_NEUTRAL) { - const int32_t brightness = SHADE_MAX - shade; + if (shade != 0x1000) { + const int32_t brightness = 0x1FFF - shade; red = (red * brightness) >> 12; green = (green * brightness) >> 12; blue = (blue * brightness) >> 12; @@ -183,6 +187,10 @@ static void M_ReleaseTextures(RENDERER *const renderer) priv->texture_map[i] = GFX_NO_TEXTURE; } } + if (priv->env_map_texture != GFX_NO_TEXTURE) { + GFX_3D_Renderer_UnregisterEnvironmentMap( + priv->renderer_3d, priv->env_map_texture); + } } static void M_LoadTexturePages(RENDERER *renderer) @@ -203,6 +211,9 @@ static void M_LoadTexturePages(RENDERER *renderer) priv->renderer_3d, surface->buffer, surface->desc.width, surface->desc.height); } + + priv->env_map_texture = + GFX_3D_Renderer_RegisterEnvironmentMap(priv->renderer_3d); } static void M_SelectTexture(RENDERER *const renderer, const int32_t tex_source) @@ -1236,6 +1247,7 @@ static void M_Init(RENDERER *const renderer) for (int32_t i = 0; i < GFX_MAX_TEXTURES; i++) { priv->texture_map[i] = GFX_NO_TEXTURE; } + priv->env_map_texture = GFX_NO_TEXTURE; renderer->initialized = true; renderer->priv = priv; diff --git a/src/tr2/game/render/swr.c b/src/tr2/game/render/swr.c index e0fefc072..1478bca4f 100644 --- a/src/tr2/game/render/swr.c +++ b/src/tr2/game/render/swr.c @@ -1439,11 +1439,8 @@ static void M_SetWet(RENDERER *const renderer, const bool is_wet) { M_PRIV *const priv = renderer->priv; if (is_wet) { - const RGB_F tint = Output_GetTint(); GFX_2D_Renderer_SetTint( - priv->renderer_2d, - (GFX_COLOR) { - .r = tint.r * 255, .g = tint.g * 255, .b = tint.b * 255 }); + priv->renderer_2d, (GFX_COLOR) { .r = 170, .g = 170, .b = 255 }); } else { GFX_2D_Renderer_SetTint( priv->renderer_2d, (GFX_COLOR) { .r = 255, .g = 255, .b = 255 }); diff --git a/src/tr2/game/requester.c b/src/tr2/game/requester.c new file mode 100644 index 000000000..2cc0d98eb --- /dev/null +++ b/src/tr2/game/requester.c @@ -0,0 +1,461 @@ +#include "game/requester.h" + +#include "decomp/decomp.h" +#include "game/game_string.h" +#include "game/input.h" +#include "game/text.h" +#include "global/vars.h" + +#include + +static void M_ClearTextStrings(REQUEST_INFO *req); + +static void M_ClearTextStrings(REQUEST_INFO *const req) +{ + Text_Remove(req->heading_text1); + req->heading_text1 = nullptr; + + Text_Remove(req->heading_text2); + req->heading_text2 = nullptr; + + Text_Remove(req->background_text); + req->background_text = nullptr; + + Text_Remove(req->moreup_text); + req->moreup_text = nullptr; + + Text_Remove(req->moredown_text); + req->moredown_text = nullptr; + + for (int32_t i = 0; i < MAX_REQUESTER_ITEMS; i++) { + Text_Remove(req->item_texts1[i]); + req->item_texts1[i] = nullptr; + Text_Remove(req->item_texts2[i]); + req->item_texts2[i] = nullptr; + } +} + +void Requester_Init(REQUEST_INFO *const req) +{ + M_ClearTextStrings(req); + + req->background_flags = 1; + req->moreup_flags = 1; + req->moredown_flags = 1; + req->items_count = 0; + + req->heading_text1 = nullptr; + req->heading_text2 = nullptr; + req->heading_flags1 = 0; + req->heading_flags2 = 0; + req->background_text = nullptr; + req->moreup_text = nullptr; + req->moredown_text = nullptr; + + for (int32_t i = 0; i < MAX_REQUESTER_ITEMS; i++) { + req->item_texts1[i] = nullptr; + req->item_texts2[i] = nullptr; + req->item_flags1[i] = 0; + req->item_flags2[i] = 0; + } + + req->pitem_flags1 = g_RequesterFlags1; + req->pitem_flags2 = g_RequesterFlags2; + + req->render_width = g_PhdWinWidth; + req->render_height = g_PhdWinHeight; +} + +void Requester_Shutdown(REQUEST_INFO *const req) +{ + M_ClearTextStrings(req); + req->ready = 0; +} + +int32_t Requester_Display( + REQUEST_INFO *const req, const bool destroy, const bool backgrounds) +{ + int32_t line_qty = req->visible_count; + const int32_t lines_height = line_qty * req->line_height + 10; + const int32_t line_one_off = req->y_pos - lines_height; + + const uint32_t render_width = g_PhdWinWidth; + const uint32_t render_height = g_PhdWinHeight; + if (render_width != req->render_width + || render_height != req->render_height) { + Requester_Shutdown(req); + req->render_width = render_width; + req->render_height = render_height; + } + + req->pitem_flags1 = g_RequesterFlags1; + req->pitem_flags2 = g_RequesterFlags2; + if (req->items_count < req->visible_count) { + line_qty = req->items_count; + } + + /* heading */ + if (req->heading_flags1 & REQ_USE) { + if (req->heading_text1 == nullptr) { + req->heading_text1 = Text_Create( + req->x_pos, line_one_off - req->line_height - 10, + req->heading_string1); + req->heading_text1->pos.z = req->z_pos; + Text_CentreH(req->heading_text1, true); + Text_AlignBottom(req->heading_text1, true); + if (backgrounds) { + Text_AddBackground( + req->heading_text1, req->pix_width - 4, 0, 0, 0, + TS_HEADING); + Text_AddOutline(req->heading_text1, TS_HEADING); + } + } + + if (req->heading_flags1 & REQ_ALIGN_LEFT) { + Requester_Item_LeftAlign(req, req->heading_text1); + } + if (req->heading_flags1 & REQ_ALIGN_RIGHT) { + Requester_Item_RightAlign(req, req->heading_text1); + } + } + + /* heading 2 */ + if (req->heading_flags2 & REQ_USE) { + if (req->heading_text2) { + } else { + req->heading_text2 = Text_Create( + req->x_pos, line_one_off - req->line_height - 10, + req->heading_string2); + req->heading_text2->pos.z = req->z_pos; + Text_CentreH(req->heading_text2, true); + Text_AlignBottom(req->heading_text2, true); + if (backgrounds) { + Text_AddBackground( + req->heading_text2, req->pix_width - 4, 0, 0, 0, + TS_HEADING); + Text_AddOutline(req->heading_text2, TS_HEADING); + } + } + + if (req->heading_flags2 & REQ_ALIGN_LEFT) { + Requester_Item_LeftAlign(req, req->heading_text2); + } + + if (req->heading_flags2 & REQ_ALIGN_RIGHT) { + Requester_Item_RightAlign(req, req->heading_text2); + } + } + + /* background */ + if (backgrounds && !req->background_text + && (req->background_flags & 1) != 0) { + req->background_text = + Text_Create(req->x_pos, line_one_off - req->line_height - 12, " "); + req->background_text->pos.z = req->z_pos; + Text_CentreH(req->background_text, true); + Text_AlignBottom(req->background_text, true); + Text_AddBackground( + req->background_text, req->pix_width, + req->line_height + lines_height + 12, 0, 0, TS_BACKGROUND); + Text_AddOutline(req->background_text, TS_BACKGROUND); + } + + /* arrows */ + if (req->line_offset) { + if (!req->moreup_text && (req->moreup_flags & 1) != 0) { + Text_CentreH(req->moreup_text, true); + Text_AlignBottom(req->moreup_text, true); + } + } else { + Text_Remove(req->moreup_text); + req->moreup_text = nullptr; + } + + if (req->items_count <= req->visible_count + req->line_offset) { + Text_Remove(req->moredown_text); + req->moredown_text = nullptr; + } else if (!req->moredown_text && (req->moredown_flags & 1) != 0) { + Text_CentreH(req->moredown_text, true); + Text_AlignBottom(req->moredown_text, true); + } + + /* item texts */ + for (int32_t i = 0; i < line_qty; i++) { + const int32_t n = i + req->line_offset; + + TEXTSTRING **const text1 = &req->item_texts1[i]; + if (req->pitem_flags1[n] & REQ_USE) { + if (*text1 == nullptr) { + *text1 = Text_Create( + 0, line_one_off + req->line_height * i, + &req->pitem_strings1[n * req->item_string_len]); + (*text1)->pos.z = req->z_pos; + Text_CentreH(*text1, true); + Text_AlignBottom(*text1, true); + } + if (req->no_selector || n != req->selected) { + Text_RemoveBackground(*text1); + Text_RemoveOutline(*text1); + } else { + Text_AddBackground( + *text1, req->pix_width - 12, 0, 0, 0, TS_REQUESTED); + Text_AddOutline(*text1, TS_REQUESTED); + } + + if (req->pitem_flags1[n] & REQ_ALIGN_LEFT) { + Requester_Item_LeftAlign(req, *text1); + } else if (req->pitem_flags1[n] & REQ_ALIGN_RIGHT) { + Requester_Item_RightAlign(req, *text1); + } else { + Requester_Item_CenterAlign(req, *text1); + } + } else { + Text_Remove(*text1); + Text_RemoveBackground(*text1); + Text_RemoveOutline(*text1); + *text1 = nullptr; + } + + TEXTSTRING **const text2 = &req->item_texts2[i]; + if (req->pitem_flags2[n] & REQ_USE) { + if (*text2 == nullptr) { + *text2 = Text_Create( + 0, line_one_off + req->line_height * i, + &req->pitem_strings2[n * req->item_string_len]); + (*text2)->pos.z = req->z_pos; + Text_CentreH(*text2, true); + Text_AlignBottom(*text2, true); + } + if (req->pitem_flags2[n] & REQ_ALIGN_LEFT) { + Requester_Item_LeftAlign(req, *text2); + } else if (req->pitem_flags2[n] & REQ_ALIGN_RIGHT) { + Requester_Item_RightAlign(req, *text2); + } else { + Requester_Item_CenterAlign(req, *text2); + } + } else { + Text_Remove(*text2); + Text_RemoveBackground(*text2); + Text_RemoveOutline(*text2); + *text2 = nullptr; + } + } + + if (req->line_offset != req->line_old_offset) { + for (int32_t i = 0; i < line_qty; i++) { + const int32_t n = req->line_offset + i; + + TEXTSTRING **text1 = &req->item_texts1[i]; + if (*text1 != nullptr && (req->pitem_flags1[n] & REQ_USE)) { + Text_ChangeText( + *text1, &req->pitem_strings1[n * req->item_string_len]); + } + if (req->pitem_flags1[n] & REQ_ALIGN_LEFT) { + Requester_Item_LeftAlign(req, *text1); + } else if (req->pitem_flags1[n] & REQ_ALIGN_RIGHT) { + Requester_Item_RightAlign(req, *text1); + } else { + Requester_Item_CenterAlign(req, *text1); + } + + TEXTSTRING **text2 = &req->item_texts2[i]; + if (*text2 != nullptr && (req->pitem_flags2[n] & REQ_USE)) { + Text_ChangeText( + *text2, &req->pitem_strings2[n * req->item_string_len]); + } + if (req->pitem_flags2[n] & REQ_ALIGN_LEFT) { + Requester_Item_LeftAlign(req, *text2); + } else if (req->pitem_flags2[n] & REQ_ALIGN_RIGHT) { + Requester_Item_RightAlign(req, *text2); + } else { + Requester_Item_CenterAlign(req, *text2); + } + } + } + + if (g_InputDB.menu_down) { + if (req->no_selector) { + req->line_old_offset = req->line_offset; + if (req->visible_count + req->line_offset < req->items_count) { + req->line_offset++; + return 0; + } + } else { + if (req->selected < req->items_count - 1) { + req->selected++; + } + req->line_old_offset = req->line_offset; + if (req->selected > req->line_offset + req->visible_count - 1) { + req->line_offset++; + return 0; + } + } + return 0; + } + + if (g_InputDB.menu_up) { + if (req->no_selector) { + req->line_old_offset = req->line_offset; + if (req->line_offset > 0) { + req->line_offset--; + return 0; + } + } else { + if (req->selected > 0) { + req->selected--; + } + req->line_old_offset = req->line_offset; + if (req->selected < req->line_offset) { + req->line_offset--; + return 0; + } + } + return 0; + } + + if (!g_InputDB.menu_confirm) { + if (g_InputDB.menu_back && destroy) { + Requester_Shutdown(req); + return -1; + } + return 0; + } + + if (destroy) { + Requester_Shutdown(req); + } + return req->selected + 1; +} + +void Requester_RemoveAllItems(REQUEST_INFO *const req) +{ + req->items_count = 0; + req->line_offset = 0; + req->selected = 0; +} + +void Requester_Item_CenterAlign(REQUEST_INFO *const req, TEXTSTRING *const text) +{ + if (text == nullptr) { + return; + } + text->background.offset.x = 0; + text->pos.x = req->x_pos; +} + +void Requester_Item_LeftAlign(REQUEST_INFO *const req, TEXTSTRING *const text) +{ + if (text == nullptr) { + return; + } + const int32_t x = + (req->pix_width + - (Text_GetWidth(text) * TEXT_BASE_SCALE / text->scale.h)) + / 2 + - 8; + text->pos.x = req->x_pos - x; + text->background.offset.x = x; +} + +void Requester_Item_RightAlign(REQUEST_INFO *const req, TEXTSTRING *const text) +{ + if (text == nullptr) { + return; + } + const int32_t x = + (req->pix_width + - (Text_GetWidth(text) * TEXT_BASE_SCALE / text->scale.h)) + / 2 + - 8; + text->pos.x = req->x_pos + x; + text->background.offset.x = -x; +} + +void Requester_SetHeading( + REQUEST_INFO *const req, const char *const text1, const uint32_t flags1, + const char *const text2, const uint32_t flags2) +{ + Text_Remove(req->heading_text1); + req->heading_text1 = nullptr; + + Text_Remove(req->heading_text2); + req->heading_text2 = nullptr; + + if (text1 != nullptr) { + strcpy(req->heading_string1, text1); + req->heading_flags1 = flags1 | REQ_USE; + } else { + strcpy(req->heading_string1, "u"); + req->heading_flags1 = 0; + } + + if (text2 != nullptr) { + strcpy(req->heading_string2, text2); + req->heading_flags2 = flags2 | REQ_USE; + } else { + strcpy(req->heading_string2, "u"); + req->heading_flags2 = 0; + } +} + +void Requester_ChangeItem( + REQUEST_INFO *const req, const int32_t item, const char *const text1, + const uint32_t flags1, const char *const text2, const uint32_t flags2) +{ + Text_Remove(req->item_texts1[item]); + req->item_texts1[item] = nullptr; + + Text_Remove(req->item_texts2[item]); + req->item_texts2[item] = nullptr; + + if (text1 != nullptr) { + strcpy(&req->pitem_strings1[item * req->item_string_len], text1); + req->pitem_flags1[item] = flags1 | REQ_USE; + } else { + req->pitem_flags1[item] = 0; + } + + if (text2 != nullptr) { + strcpy(&req->pitem_strings2[item * req->item_string_len], text2); + req->pitem_flags2[item] = flags2 | REQ_USE; + } else { + req->pitem_flags2[item] = 0; + } +} + +void Requester_AddItem( + REQUEST_INFO *const req, const char *const text1, const uint32_t flags1, + const char *const text2, const uint32_t flags2) +{ + req->pitem_flags1 = g_RequesterFlags1; + req->pitem_flags2 = g_RequesterFlags2; + + if (text1 != nullptr) { + strcpy( + &req->pitem_strings1[req->items_count * req->item_string_len], + text1); + req->pitem_flags1[req->items_count] = flags1 | REQ_USE; + } else { + g_RequesterFlags1[req->items_count] = 0; + } + + if (text2 != nullptr) { + strcpy( + &req->pitem_strings2[req->items_count * req->item_string_len], + text2); + req->pitem_flags2[req->items_count] = flags2 | REQ_USE; + } else { + req->pitem_flags2[req->items_count] = 0; + } + + req->items_count++; +} + +void Requester_SetSize( + REQUEST_INFO *const req, const int32_t max_lines, const int32_t y_pos) +{ + int32_t visible_lines = g_PhdWinHeight / 2 / 18; + CLAMPG(visible_lines, max_lines); + req->y_pos = y_pos; + req->visible_count = visible_lines; +} diff --git a/src/tr2/game/requester.h b/src/tr2/game/requester.h new file mode 100644 index 000000000..a26473d22 --- /dev/null +++ b/src/tr2/game/requester.h @@ -0,0 +1,21 @@ +#pragma once + +#include "global/types.h" + +void Requester_Init(REQUEST_INFO *req); +void Requester_Shutdown(REQUEST_INFO *req); +int32_t Requester_Display(REQUEST_INFO *req, bool destroy, bool backgrounds); +void Requester_RemoveAllItems(REQUEST_INFO *req); +void Requester_Item_CenterAlign(REQUEST_INFO *req, TEXTSTRING *text); +void Requester_Item_LeftAlign(REQUEST_INFO *req, TEXTSTRING *text); +void Requester_Item_RightAlign(REQUEST_INFO *req, TEXTSTRING *text); +void Requester_SetHeading( + REQUEST_INFO *req, const char *text1, uint32_t flags1, const char *text2, + uint32_t flags2); +void Requester_ChangeItem( + REQUEST_INFO *req, int32_t item, const char *text1, uint32_t flags1, + const char *text2, uint32_t flags2); +void Requester_AddItem( + REQUEST_INFO *req, const char *text1, uint32_t flags1, const char *text2, + uint32_t flags2); +void Requester_SetSize(REQUEST_INFO *req, int32_t max_lines, int32_t y_pos); diff --git a/src/tr2/game/room.h b/src/tr2/game/room.h index 9062cf960..09de7087d 100644 --- a/src/tr2/game/room.h +++ b/src/tr2/game/room.h @@ -11,5 +11,7 @@ void Room_GetNewRoom(int32_t x, int32_t y, int32_t z, int16_t room_num); // TODO: poor abstraction void Room_InitCinematic(void); +int32_t Room_GetWaterHeight(int32_t x, int32_t y, int32_t z, int16_t room_num); + void Room_TestTriggers(const ITEM *item); void Room_TestSectorTrigger(const ITEM *item, const SECTOR *sector); diff --git a/src/tr2/game/savegame.h b/src/tr2/game/savegame.h deleted file mode 100644 index 18cbe44f9..000000000 --- a/src/tr2/game/savegame.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -#include diff --git a/src/tr2/game/savegame/common.c b/src/tr2/game/savegame/common.c index 892342892..13621e063 100644 --- a/src/tr2/game/savegame/common.c +++ b/src/tr2/game/savegame/common.c @@ -1,133 +1,66 @@ -#include "game/game.h" +#include "decomp/savegame.h" #include "game/game_flow.h" -#include "game/game_string.h" -#include "game/savegame.h" -#include "global/types_decomp.h" #include "global/vars.h" #include -#include -#include +#include +#include +#include +#include -// TODO: make configurable -#define MAX_SAVE_SLOTS MAX_REQUESTER_ITEMS +void Savegame_Init(void) +{ + g_SaveGame.start = Memory_Alloc( + sizeof(START_INFO) + * (GF_GetLevelTable(GFLT_MAIN)->count + + GF_GetLevelTable(GFLT_DEMOS)->count)); + + const GF_LEVEL_TABLE *const level_table = GF_GetLevelTable(GFLT_DEMOS); + for (int32_t i = 0; i < level_table->count; i++) { + START_INFO *const resume_info = + Savegame_GetCurrentInfo(&level_table->levels[i]); + resume_info->available = 1; + resume_info->has_pistols = 1; + resume_info->pistol_ammo = 1000; + resume_info->gun_status = LGS_ARMLESS; + resume_info->gun_type = LGT_PISTOLS; + } +} + +void Savegame_Shutdown(void) +{ + Memory_FreePointer(&g_SaveGame.start); +} int32_t Savegame_GetSlotCount(void) { return MAX_SAVE_SLOTS; } -void Savegame_HighlightNewestSlot(void) +bool Savegame_IsSlotFree(const int32_t slot_idx) { + return g_SavedLevels[slot_idx] == 0; } -void Savegame_ApplyLogicToCurrentInfo(const GF_LEVEL *const level) +bool Savegame_Save(const int32_t slot_idx) { - RESUME_INFO *resume = Savegame_GetCurrentInfo(level); - if (resume == nullptr) { - return; - } - - resume->flags.has_pistols = 1; - resume->equipped_gun_type = LGT_PISTOLS; - resume->pistol_ammo = 1000; - - if (level == GF_GetGymLevel()) { - resume->flags.available = 1; - - resume->flags.has_pistols = 0; - resume->flags.has_shotgun = 0; - resume->flags.has_magnums = 0; - resume->flags.has_uzis = 0; - resume->flags.has_harpoon = 0; - resume->flags.has_m16 = 0; - resume->flags.has_grenade = 0; - - resume->pistol_ammo = 0; - resume->shotgun_ammo = 0; - resume->magnum_ammo = 0; - resume->uzi_ammo = 0; - resume->harpoon_ammo = 0; - resume->m16_ammo = 0; - resume->grenade_ammo = 0; - - resume->flares = 0; - resume->large_medipacks = 0; - resume->small_medipacks = 0; - resume->equipped_gun_type = LGT_UNARMED; - resume->gun_status = LGS_ARMLESS; - } else if (level == GF_GetFirstLevel()) { - resume->flags.available = 1; - - resume->flags.has_pistols = 1; - resume->flags.has_shotgun = 1; - resume->flags.has_magnums = 0; - resume->flags.has_uzis = 0; - resume->flags.has_harpoon = 0; - resume->flags.has_m16 = 0; - resume->flags.has_grenade = 0; - - resume->shotgun_ammo = 2 * SHOTGUN_AMMO_CLIP; - resume->magnum_ammo = 0; - resume->uzi_ammo = 0; - resume->harpoon_ammo = 0; - resume->m16_ammo = 0; - resume->grenade_ammo = 0; - - resume->flares = 2; - resume->small_medipacks = 1; - resume->large_medipacks = 1; - resume->gun_status = LGS_ARMLESS; - } - - if (Game_IsBonusFlagSet(GBF_NGPLUS) && level != GF_GetGymLevel()) { - resume->flags.has_pistols = 1; - resume->flags.has_shotgun = 1; - resume->flags.has_magnums = 1; - resume->flags.has_uzis = 1; - resume->flags.has_grenade = 1; - resume->flags.has_harpoon = 1; - resume->flags.has_m16 = 1; - resume->flags.has_grenade = 1; - - resume->shotgun_ammo = 10000; - resume->magnum_ammo = 10000; - resume->uzi_ammo = 10000; - resume->harpoon_ammo = 10000; - resume->m16_ammo = 10000; - resume->grenade_ammo = 10000; - - resume->flares = -1; - resume->equipped_gun_type = LGT_GRENADE; - } - - if (g_GF_RemoveWeapons) { - resume->flags.has_pistols = 0; - resume->flags.has_magnums = 0; - resume->flags.has_uzis = 0; - resume->flags.has_shotgun = 0; - resume->flags.has_m16 = 0; - resume->flags.has_grenade = 0; - resume->flags.has_harpoon = 0; - resume->equipped_gun_type = LGT_UNARMED; - resume->gun_status = LGS_ARMLESS; - g_GF_RemoveWeapons = false; - } - - if (g_GF_RemoveAmmo) { - resume->m16_ammo = 0; - resume->grenade_ammo = 0; - resume->harpoon_ammo = 0; - resume->shotgun_ammo = 0; - resume->uzi_ammo = 0; - resume->magnum_ammo = 0; - resume->pistol_ammo = 0; - resume->flares = 0; - resume->large_medipacks = 0; - resume->small_medipacks = 0; - g_GF_RemoveAmmo = false; - } - - const STATS_COMMON default_stats = Savegame_GetDefaultStats(level); - resume->stats.max_secret_count = default_stats.max_secret_count; + CreateSaveGameInfo(); + S_SaveGame(slot_idx); + GetSavedGamesList(&g_LoadGameRequester); + return true; +} + +START_INFO *Savegame_GetCurrentInfo(const GF_LEVEL *const level) +{ + ASSERT(g_SaveGame.start != nullptr); + ASSERT(level != nullptr); + if (GF_GetLevelTableType(level->type) == GFLT_MAIN) { + return &g_SaveGame.start[level->num]; + } else if (level->type == GFL_DEMO) { + return &g_SaveGame.start[GF_GetLevelTable(GFLT_MAIN)->count]; + } + LOG_WARNING( + "Warning: unable to get resume info for level %d (type=%s)", level->num, + ENUM_MAP_TO_STRING(GF_LEVEL_TYPE, level->type)); + return nullptr; } diff --git a/src/tr2/game/savegame/savegame_bson.c b/src/tr2/game/savegame/savegame_bson.c deleted file mode 100644 index 8727d5191..000000000 --- a/src/tr2/game/savegame/savegame_bson.c +++ /dev/null @@ -1,1308 +0,0 @@ -#include "game/game.h" -#include "game/game_flow.h" -#include "game/inventory.h" -#include "game/lara/control.h" -#include "game/music.h" -#include "game/objects/general/lift.h" -#include "game/savegame.h" -#include "global/vars.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#define SAVEGAME_BSON_MAGIC MKTAG('T', '2', 'X', 'B') - -#define DUMP_XYZ(obj, key, value) \ - do { \ - JSON_OBJECT *const sub_obj = JSON_ObjectNew(); \ - JSON_ObjectAppendInt(sub_obj, "x", value.x); \ - JSON_ObjectAppendInt(sub_obj, "y", value.y); \ - JSON_ObjectAppendInt(sub_obj, "z", value.z); \ - JSON_ObjectAppendObject(obj, key, sub_obj); \ - } while (0) - -#define LOAD_XYZ(obj, key, value) \ - do { \ - const JSON_OBJECT *const sub_obj = JSON_ObjectGetObject(obj, key); \ - value.x = JSON_ObjectGetInt(sub_obj, "x", value.x); \ - value.y = JSON_ObjectGetInt(sub_obj, "y", value.y); \ - value.z = JSON_ObjectGetInt(sub_obj, "z", value.z); \ - } while (0) - -static void M_SaveRaw(MYFILE *fp, const JSON_VALUE *root); -static JSON_VALUE *M_ReadRaw(MYFILE *fp, int32_t *version_out); -static JSON_VALUE *M_ParseFromBuffer(const char *buffer, int32_t *version_out); - -static JSON_OBJECT *M_DumpMisc(void); -static JSON_OBJECT *M_DumpMusic(void); -static JSON_ARRAY *M_DumpResumeInfo(void); -static JSON_OBJECT *M_DumpInventory(void); -static JSON_OBJECT *M_DumpFlipmaps(void); -static JSON_ARRAY *M_DumpCameras(void); -static JSON_ARRAY *M_DumpItems(void); -static JSON_ARRAY *M_DumpFlares(void); -static JSON_OBJECT *M_DumpLara(void); -static JSON_OBJECT *M_DumpArm(const LARA_ARM *arm); -static JSON_OBJECT *M_DumpAmmo(const AMMO_INFO *ammo); - -static bool M_LoadMisc(JSON_OBJECT *misc_obj); -static bool M_LoadMusic(JSON_OBJECT *music_obj); -static bool M_LoadResumeInfo(JSON_ARRAY *resume_arr); -static bool M_LoadInventory(JSON_OBJECT *inv_obj); -static bool M_LoadFlipmaps(JSON_OBJECT *flipmap_obj); -static bool M_LoadCameras(JSON_ARRAY *cameras_arr); -static bool M_LoadItems(JSON_ARRAY *items_arr); -static bool M_LoadFlares(JSON_ARRAY *flares_arr); -static bool M_LoadLara(JSON_OBJECT *lara_obj); -static bool M_LoadArm(JSON_OBJECT *arm_obj, LARA_ARM *arm); -static bool M_LoadAmmo(JSON_OBJECT *ammo_obj, AMMO_INFO *ammo); - -static bool M_IsValidItemObject( - GAME_OBJECT_ID saved_obj_id, GAME_OBJECT_ID initial_obj_id); - -static const char *M_GetSaveFilePattern(void); -static bool M_FillInfo(MYFILE *fp, SAVEGAME_INFO *info); -static void M_SaveToFile(MYFILE *fp, SAVEGAME_INFO *info); -static bool M_LoadFromFile(MYFILE *fp); - -static SAVEGAME_STRATEGY m_Strategy = { - // clang-format off - .allow_load = true, - .allow_save = true, - .format = SAVEGAME_FORMAT_BSON, - .get_save_file_pattern_func = M_GetSaveFilePattern, - .fill_info_func = M_FillInfo, - .load_from_file_func = M_LoadFromFile, - .save_to_file_func = M_SaveToFile, - .load_only_resume_info_func = nullptr, - .update_death_counters_func = nullptr, - // clang-format on -}; - -static const char *M_GetSaveFilePattern(void) -{ - return g_GameFlow.savegame_fmt_bson; -} - -static bool M_FillInfo(MYFILE *const fp, SAVEGAME_INFO *const info) -{ - SAVEGAME_BSON_HEADER header; - File_Seek(fp, 0, FILE_SEEK_SET); - File_ReadData(fp, &header, sizeof(SAVEGAME_BSON_HEADER)); - info->initial_version = header.initial_version; - info->features.restart = false; - info->features.select_level = false; - - File_Skip(fp, header.compressed_size); - SAVEGAME_BSON_EXTENDED_HEADER extra_header; - File_ReadData(fp, &extra_header, sizeof(extra_header)); - - info->counter = extra_header.counter; - info->level_num = extra_header.level_num; - if (extra_header.title_size >= (int32_t)File_Size(fp)) { - return false; - } - info->level_title = Memory_Alloc(extra_header.title_size + 1); - File_ReadData(fp, info->level_title, extra_header.title_size); - return true; -} - -static void M_SaveToFile(MYFILE *const fp, SAVEGAME_INFO *const info) -{ - const GF_LEVEL *const current_level = Game_GetCurrentLevel(); - JSON_OBJECT *const root_obj = JSON_ObjectNew(); - - JSON_ObjectAppendObject(root_obj, "misc", M_DumpMisc()); - JSON_ObjectAppendObject(root_obj, "music", M_DumpMusic()); - JSON_ObjectAppendArray(root_obj, "resume_info", M_DumpResumeInfo()); - JSON_ObjectAppendObject(root_obj, "inventory", M_DumpInventory()); - JSON_ObjectAppendObject(root_obj, "flipmap", M_DumpFlipmaps()); - JSON_ObjectAppendArray(root_obj, "cameras", M_DumpCameras()); - JSON_ObjectAppendArray(root_obj, "items", M_DumpItems()); - JSON_ObjectAppendArray(root_obj, "flares", M_DumpFlares()); - JSON_ObjectAppendObject(root_obj, "lara", M_DumpLara()); - - JSON_VALUE *const root = JSON_ValueFromObject(root_obj); - M_SaveRaw(fp, root); - JSON_ValueFree(root); -} - -static void M_SaveRaw(MYFILE *const fp, const JSON_VALUE *const root) -{ - size_t uncompressed_size; - char *uncompressed = BSON_Write(root, &uncompressed_size); - - uLongf compressed_size = compressBound(uncompressed_size); - char *compressed = Memory_Alloc(compressed_size); - const int32_t result = compress( - (Bytef *)compressed, &compressed_size, (const Bytef *)uncompressed, - (uLongf)uncompressed_size); - if (result != Z_OK) { - Shell_ExitSystem("Failed to compress savegame data"); - } - - const GF_LEVEL *const level = Game_GetCurrentLevel(); - const SAVEGAME_BSON_HEADER header = { - .magic = SAVEGAME_BSON_MAGIC, - .initial_version = Savegame_GetInitialVersion(), - .version = SAVEGAME_CURRENT_VERSION, - .compressed_size = compressed_size, - .uncompressed_size = uncompressed_size, - }; - const SAVEGAME_BSON_EXTENDED_HEADER extra_header = { - .flags = Game_GetBonusFlag(), - .counter = Savegame_GetCounter(), - .level_num = level->num, - .title_size = strlen(level->title), - }; - - File_WriteData(fp, &header, sizeof(header)); - File_WriteData(fp, compressed, compressed_size); - File_WriteData(fp, &extra_header, sizeof(extra_header)); - File_WriteData(fp, level->title, strlen(level->title)); - - Memory_FreePointer(&uncompressed); - Memory_FreePointer(&compressed); -} - -static bool M_LoadFromFile(MYFILE *const fp) -{ - bool result = false; - - int32_t version; - JSON_VALUE *const root = M_ReadRaw(fp, &version); - JSON_OBJECT *const root_obj = JSON_ValueAsObject(root); - if (root_obj == nullptr) { - LOG_ERROR("Malformed save: cannot parse BSON data"); - goto cleanup; - } - - if (!M_LoadMisc(JSON_ObjectGetObject(root_obj, "misc"))) { - goto cleanup; - } - - if (!M_LoadMusic(JSON_ObjectGetObject(root_obj, "music"))) { - goto cleanup; - } - - if (!M_LoadResumeInfo(JSON_ObjectGetArray(root_obj, "resume_info"))) { - goto cleanup; - } - - if (!M_LoadInventory(JSON_ObjectGetObject(root_obj, "inventory"))) { - goto cleanup; - } - - if (!M_LoadFlipmaps(JSON_ObjectGetObject(root_obj, "flipmap"))) { - goto cleanup; - } - - if (!M_LoadCameras(JSON_ObjectGetArray(root_obj, "cameras"))) { - goto cleanup; - } - - if (!M_LoadItems(JSON_ObjectGetArray(root_obj, "items"))) { - goto cleanup; - } - - if (!M_LoadFlares(JSON_ObjectGetArray(root_obj, "flares"))) { - goto cleanup; - } - - if (!M_LoadLara(JSON_ObjectGetObject(root_obj, "lara"))) { - goto cleanup; - } - - result = true; - -cleanup: - JSON_ValueFree(root); - return result; -} - -static JSON_VALUE *M_ReadRaw(MYFILE *const fp, int32_t *const version_out) -{ - const size_t buffer_size = File_Size(fp); - char *buffer = Memory_Alloc(buffer_size); - File_Seek(fp, 0, FILE_SEEK_SET); - File_ReadData(fp, buffer, buffer_size); - - JSON_VALUE *const result = M_ParseFromBuffer(buffer, version_out); - Memory_FreePointer(&buffer); - return result; -} - -static JSON_VALUE *M_ParseFromBuffer( - const char *const buffer, int32_t *const version_out) -{ - const SAVEGAME_BSON_HEADER *const header = (SAVEGAME_BSON_HEADER *)buffer; - if (header->magic != SAVEGAME_BSON_MAGIC) { - LOG_ERROR("Invalid savegame magic"); - return nullptr; - } - - if (version_out != nullptr) { - *version_out = header->version; - } - - const char *compressed = buffer + sizeof(SAVEGAME_BSON_HEADER); - char *uncompressed = Memory_Alloc(header->uncompressed_size); - - uLongf uncompressed_size = header->uncompressed_size; - const int32_t error_code = uncompress( - (Bytef *)uncompressed, &uncompressed_size, (const Bytef *)compressed, - (uLongf)header->compressed_size); - if (error_code != Z_OK) { - LOG_ERROR("Failed to decompress the data (error %d)", error_code); - Memory_FreePointer(&uncompressed); - return nullptr; - } - - JSON_VALUE *const root = BSON_Parse(uncompressed, uncompressed_size); - Memory_FreePointer(&uncompressed); - return root; -} - -static JSON_OBJECT *M_DumpMisc(void) -{ - JSON_OBJECT *const misc_obj = JSON_ObjectNew(); - JSON_ObjectAppendString(misc_obj, "game_version", g_TRXVersion); - JSON_ObjectAppendInt(misc_obj, "bonus_flag", Game_GetBonusFlag()); - JSON_ObjectAppendBool( - misc_obj, "are_monks_angry", Creature_AreAlliesHostile()); - return misc_obj; -} - -static bool M_LoadMisc(JSON_OBJECT *const misc_obj) -{ - if (misc_obj == nullptr) { - LOG_ERROR("Malformed save: invalid or missing misc info"); - return false; - } - - const int32_t bonus_flag = JSON_ObjectGetInt(misc_obj, "bonus_flag", 0); - Game_SetBonusFlag(bonus_flag); - const bool hostile = JSON_ObjectGetBool(misc_obj, "are_monks_angry", false); - Creature_SetAlliesHostile(hostile); - return true; -} - -static JSON_OBJECT *M_DumpMusic(void) -{ - JSON_OBJECT *const music_obj = JSON_ObjectNew(); - JSON_ARRAY *const track_arr = JSON_ArrayNew(); - for (int32_t i = 0; i < MAX_MUSIC_TRACKS; i++) { - JSON_ArrayAppendInt(track_arr, Music_GetTrackFlags(i)); - } - JSON_ObjectAppendArray(music_obj, "flags", track_arr); - - const MUSIC_TRACK_ID current_track = Music_GetCurrentPlayingTrack(); - const bool is_ambient = current_track == Music_GetCurrentLoopedTrack(); - JSON_OBJECT *const current_obj = JSON_ObjectNew(); - JSON_ObjectAppendInt(current_obj, "current_track", current_track); - JSON_ObjectAppendDouble(current_obj, "timestamp", Music_GetTimestamp()); - JSON_ObjectAppendBool(current_obj, "is_ambient", is_ambient); - JSON_ObjectAppendObject(music_obj, "current", current_obj); - - return music_obj; -} - -static bool M_LoadMusic(JSON_OBJECT *const music_obj) -{ - if (music_obj == nullptr) { - LOG_ERROR("Malformed save: invalid or missing music info"); - return false; - } - - const JSON_ARRAY *const track_arr = JSON_ObjectGetArray(music_obj, "flags"); - if (track_arr == nullptr) { - LOG_WARNING("Malformed save: invalid or missing music track array"); - return true; - } - - if ((signed)track_arr->length != MAX_MUSIC_TRACKS) { - LOG_WARNING( - "Malformed save: expected %d music track flags, got %d", - MAX_MUSIC_TRACKS, track_arr->length); - return true; - } - - for (int32_t i = 0; i < (signed)track_arr->length; i++) { - Music_SetTrackFlags(i, JSON_ArrayGetInt(track_arr, i, 0)); - } - - const JSON_OBJECT *const current_obj = - JSON_ObjectGetObject(music_obj, "current"); - if (current_obj == nullptr - || g_Config.audio.music_load_condition == MUSIC_LOAD_NEVER) { - return true; - } - - const int16_t current_track = - JSON_ObjectGetInt(current_obj, "current_track", -1); - double timestamp = JSON_ObjectGetDouble(current_obj, "timestamp", -1.0); - if (current_track != MX_INACTIVE) { - const bool is_ambient = - JSON_ObjectGetBool(music_obj, "is_ambient", false); - if (is_ambient) { - if (g_Config.audio.music_load_condition == MUSIC_LOAD_NON_AMBIENT) { - return true; - } - Music_Play(current_track, MPM_LOOPED); - } else { - Music_Play(current_track, MPM_ALWAYS); - } - - if (!Music_SeekTimestamp(timestamp)) { - LOG_WARNING( - "Could not load current track %d at timestamp %" PRId64 ".", - current_track, timestamp); - } - } - - return true; -} - -static JSON_ARRAY *M_DumpResumeInfo(void) -{ - JSON_ARRAY *const resume_arr = JSON_ArrayNew(); - for (int32_t i = 0; i < GF_GetLevelTable(GFLT_MAIN)->count; i++) { - const GF_LEVEL *const level = GF_GetLevel(GFLT_MAIN, i); - const RESUME_INFO *const resume = Savegame_GetCurrentInfo(level); - JSON_OBJECT *const resume_obj = JSON_ObjectNew(); - JSON_ObjectAppendInt(resume_obj, "pistol_ammo", resume->pistol_ammo); - JSON_ObjectAppendInt(resume_obj, "magnum_ammo", resume->magnum_ammo); - JSON_ObjectAppendInt(resume_obj, "uzi_ammo", resume->uzi_ammo); - JSON_ObjectAppendInt(resume_obj, "shotgun_ammo", resume->shotgun_ammo); - JSON_ObjectAppendInt(resume_obj, "m16_ammo", resume->m16_ammo); - JSON_ObjectAppendInt(resume_obj, "grenade_ammo", resume->grenade_ammo); - JSON_ObjectAppendInt(resume_obj, "harpoon_ammo", resume->harpoon_ammo); - JSON_ObjectAppendInt(resume_obj, "num_medis", resume->small_medipacks); - JSON_ObjectAppendInt( - resume_obj, "num_big_medis", resume->large_medipacks); - JSON_ObjectAppendInt(resume_obj, "num_flares", resume->flares); - JSON_ObjectAppendInt(resume_obj, "gun_status", resume->gun_status); - JSON_ObjectAppendInt(resume_obj, "gun_type", resume->equipped_gun_type); - - JSON_ObjectAppendBool(resume_obj, "available", resume->flags.available); - JSON_ObjectAppendBool( - resume_obj, "has_pistols", resume->flags.has_pistols); - JSON_ObjectAppendBool( - resume_obj, "has_magnums", resume->flags.has_magnums); - JSON_ObjectAppendBool(resume_obj, "has_uzis", resume->flags.has_uzis); - JSON_ObjectAppendBool( - resume_obj, "has_shotgun", resume->flags.has_shotgun); - JSON_ObjectAppendBool(resume_obj, "has_m16", resume->flags.has_m16); - JSON_ObjectAppendBool( - resume_obj, "has_grenade", resume->flags.has_grenade); - JSON_ObjectAppendBool( - resume_obj, "has_harpoon", resume->flags.has_harpoon); - - JSON_ObjectAppendInt(resume_obj, "timer", resume->stats.timer); - JSON_ObjectAppendInt(resume_obj, "ammo_hits", resume->stats.ammo_hits); - JSON_ObjectAppendInt(resume_obj, "ammo_used", resume->stats.ammo_used); - JSON_ObjectAppendInt( - resume_obj, "distance_travelled", resume->stats.distance_travelled); - JSON_ObjectAppendInt(resume_obj, "kills", resume->stats.kill_count); - JSON_ObjectAppendInt(resume_obj, "secrets", resume->stats.secret_flags); - JSON_ObjectAppendDouble( - resume_obj, "medipacks_used", resume->stats.medipacks_used); - JSON_ObjectAppendInt( - resume_obj, "max_secrets", resume->stats.max_secret_count); - JSON_ArrayAppendObject(resume_arr, resume_obj); - } - return resume_arr; -} - -static bool M_LoadResumeInfo(JSON_ARRAY *const resume_arr) -{ - if (resume_arr == nullptr) { - LOG_ERROR("Malformed save: invalid or missing resume array"); - return false; - } - const int32_t expected_length = GF_GetLevelTable(GFLT_MAIN)->count; - if ((signed)resume_arr->length != expected_length) { - LOG_ERROR( - "Malformed save: expected %d resume info elements, got %d", - expected_length, resume_arr->length); - return false; - } - - for (int32_t i = 0; i < expected_length; i++) { - JSON_OBJECT *const resume_obj = JSON_ArrayGetObject(resume_arr, i); - if (resume_obj == nullptr) { - LOG_ERROR("Malformed save: invalid resume info"); - return false; - } - - const GF_LEVEL *const level = GF_GetLevel(GFLT_MAIN, i); - RESUME_INFO *const resume = Savegame_GetCurrentInfo(level); - resume->pistol_ammo = JSON_ObjectGetInt(resume_obj, "pistol_ammo", 0); - resume->magnum_ammo = JSON_ObjectGetInt(resume_obj, "magnum_ammo", 0); - resume->uzi_ammo = JSON_ObjectGetInt(resume_obj, "uzi_ammo", 0); - resume->shotgun_ammo = JSON_ObjectGetInt(resume_obj, "shotgun_ammo", 0); - resume->m16_ammo = JSON_ObjectGetInt(resume_obj, "m16_ammo", 0); - resume->grenade_ammo = JSON_ObjectGetInt(resume_obj, "grenade_ammo", 0); - resume->harpoon_ammo = JSON_ObjectGetInt(resume_obj, "harpoon_ammo", 0); - resume->small_medipacks = JSON_ObjectGetInt(resume_obj, "num_medis", 0); - resume->large_medipacks = - JSON_ObjectGetInt(resume_obj, "num_big_medis", 0); - resume->flares = JSON_ObjectGetInt(resume_obj, "num_flares", 0); - resume->gun_status = - JSON_ObjectGetInt(resume_obj, "gun_status", LGS_ARMLESS); - resume->equipped_gun_type = - JSON_ObjectGetInt(resume_obj, "gun_type", LGT_UNARMED); - - resume->flags.available = - JSON_ObjectGetBool(resume_obj, "available", 0); - resume->flags.has_pistols = - JSON_ObjectGetBool(resume_obj, "has_pistols", 0); - resume->flags.has_magnums = - JSON_ObjectGetBool(resume_obj, "has_magnums", 0); - resume->flags.has_uzis = JSON_ObjectGetBool(resume_obj, "has_uzis", 0); - resume->flags.has_shotgun = - JSON_ObjectGetBool(resume_obj, "has_shotgun", 0); - resume->flags.has_m16 = JSON_ObjectGetBool(resume_obj, "has_m16", 0); - resume->flags.has_grenade = - JSON_ObjectGetBool(resume_obj, "has_grenade", 0); - resume->flags.has_harpoon = - JSON_ObjectGetBool(resume_obj, "has_harpoon", 0); - - resume->stats.timer = JSON_ObjectGetInt(resume_obj, "timer", 0); - resume->stats.ammo_hits = JSON_ObjectGetInt(resume_obj, "ammo_hits", 0); - resume->stats.ammo_used = JSON_ObjectGetInt(resume_obj, "ammo_used", 0); - resume->stats.distance_travelled = - JSON_ObjectGetInt(resume_obj, "distance_travelled", 0); - resume->stats.kill_count = JSON_ObjectGetInt(resume_obj, "kills", 0); - resume->stats.secret_flags = - JSON_ObjectGetInt(resume_obj, "secrets", 0); - resume->stats.medipacks_used = - JSON_ObjectGetDouble(resume_obj, "medipacks_used", 0); - resume->stats.max_secret_count = - JSON_ObjectGetInt(resume_obj, "max_secrets", 0); - } - - return true; -} - -static JSON_OBJECT *M_DumpInventory(void) -{ - JSON_OBJECT *const inv_obj = JSON_ObjectNew(); - JSON_ObjectAppendInt(inv_obj, "pickup1", Inv_RequestItem(O_PICKUP_ITEM_1)); - JSON_ObjectAppendInt(inv_obj, "pickup2", Inv_RequestItem(O_PICKUP_ITEM_2)); - JSON_ObjectAppendInt(inv_obj, "puzzle1", Inv_RequestItem(O_PUZZLE_ITEM_1)); - JSON_ObjectAppendInt(inv_obj, "puzzle2", Inv_RequestItem(O_PUZZLE_ITEM_2)); - JSON_ObjectAppendInt(inv_obj, "puzzle3", Inv_RequestItem(O_PUZZLE_ITEM_3)); - JSON_ObjectAppendInt(inv_obj, "puzzle4", Inv_RequestItem(O_PUZZLE_ITEM_4)); - JSON_ObjectAppendInt(inv_obj, "key1", Inv_RequestItem(O_KEY_ITEM_1)); - JSON_ObjectAppendInt(inv_obj, "key2", Inv_RequestItem(O_KEY_ITEM_2)); - JSON_ObjectAppendInt(inv_obj, "key3", Inv_RequestItem(O_KEY_ITEM_3)); - JSON_ObjectAppendInt(inv_obj, "key4", Inv_RequestItem(O_KEY_ITEM_4)); - return inv_obj; -} - -static bool M_LoadInventory(JSON_OBJECT *const inv_obj) -{ - if (inv_obj == nullptr) { - LOG_ERROR("Malformed save: invalid or missing inventory info"); - return false; - } - - const GF_LEVEL *const current_level = Game_GetCurrentLevel(); - Lara_InitialiseInventory(current_level); - Inv_AddItemNTimes( - O_PICKUP_ITEM_1, JSON_ObjectGetInt(inv_obj, "pickup1", 0)); - Inv_AddItemNTimes( - O_PICKUP_ITEM_2, JSON_ObjectGetInt(inv_obj, "pickup2", 0)); - Inv_AddItemNTimes( - O_PUZZLE_ITEM_1, JSON_ObjectGetInt(inv_obj, "puzzle1", 0)); - Inv_AddItemNTimes( - O_PUZZLE_ITEM_2, JSON_ObjectGetInt(inv_obj, "puzzle2", 0)); - Inv_AddItemNTimes( - O_PUZZLE_ITEM_3, JSON_ObjectGetInt(inv_obj, "puzzle3", 0)); - Inv_AddItemNTimes( - O_PUZZLE_ITEM_4, JSON_ObjectGetInt(inv_obj, "puzzle4", 0)); - Inv_AddItemNTimes(O_KEY_ITEM_1, JSON_ObjectGetInt(inv_obj, "key1", 0)); - Inv_AddItemNTimes(O_KEY_ITEM_2, JSON_ObjectGetInt(inv_obj, "key2", 0)); - Inv_AddItemNTimes(O_KEY_ITEM_3, JSON_ObjectGetInt(inv_obj, "key3", 0)); - Inv_AddItemNTimes(O_KEY_ITEM_4, JSON_ObjectGetInt(inv_obj, "key4", 0)); - - return true; -} - -static JSON_OBJECT *M_DumpFlipmaps(void) -{ - JSON_OBJECT *const flipmap_obj = JSON_ObjectNew(); - JSON_ObjectAppendBool(flipmap_obj, "status", Room_GetFlipStatus()); - JSON_ObjectAppendInt(flipmap_obj, "effect", Room_GetFlipEffect()); - JSON_ObjectAppendInt(flipmap_obj, "timer", Room_GetFlipTimer()); - JSON_ARRAY *const flipmap_arr = JSON_ArrayNew(); - for (int32_t i = 0; i < MAX_FLIP_MAPS; i++) { - JSON_ArrayAppendInt(flipmap_arr, Room_GetFlipSlotFlags(i) >> 8); - } - JSON_ObjectAppendArray(flipmap_obj, "table", flipmap_arr); - return flipmap_obj; -} - -static bool M_LoadFlipmaps(JSON_OBJECT *const flipmap_obj) -{ - if (flipmap_obj == nullptr) { - LOG_ERROR("Malformed save: invalid or missing flipmap info"); - return false; - } - - if (JSON_ObjectGetBool(flipmap_obj, "status", false)) { - Room_FlipMap(); - } - - Room_SetFlipEffect(JSON_ObjectGetInt(flipmap_obj, "effect", 0)); - Room_SetFlipTimer(JSON_ObjectGetInt(flipmap_obj, "timer", 0)); - - const JSON_ARRAY *const flipmap_arr = - JSON_ObjectGetArray(flipmap_obj, "table"); - if (flipmap_arr == nullptr) { - LOG_ERROR("Malformed save: invalid or missing flipmap table"); - return false; - } - if ((signed)flipmap_arr->length != MAX_FLIP_MAPS) { - LOG_ERROR( - "Malformed save: expected %d flipmap elements, got %d", - MAX_FLIP_MAPS, flipmap_arr->length); - return false; - } - for (int32_t i = 0; i < (signed)flipmap_arr->length; i++) { - Room_SetFlipSlotFlags(i, JSON_ArrayGetInt(flipmap_arr, i, 0) << 8); - } - - return true; -} - -static JSON_ARRAY *M_DumpCameras(void) -{ - JSON_ARRAY *const cameras_arr = JSON_ArrayNew(); - for (int32_t i = 0; i < Camera_GetFixedObjectCount(); i++) { - const OBJECT_VECTOR *const object = Camera_GetFixedObject(i); - JSON_ArrayAppendInt(cameras_arr, object->flags); - } - return cameras_arr; -} - -static bool M_LoadCameras(JSON_ARRAY *const cameras_arr) -{ - if (cameras_arr == nullptr) { - LOG_ERROR("Malformed save: invalid or missing cameras array"); - return false; - } - - const int32_t num_cameras = Camera_GetFixedObjectCount(); - if ((signed)cameras_arr->length != num_cameras) { - LOG_ERROR( - "Malformed save: expected %d cameras, got %d", num_cameras, - cameras_arr->length); - return false; - } - - for (int32_t i = 0; i < num_cameras; i++) { - OBJECT_VECTOR *const object = Camera_GetFixedObject(i); - object->flags = JSON_ArrayGetInt(cameras_arr, i, 0); - } - - return true; -} - -static JSON_ARRAY *M_DumpItems(void) -{ - Savegame_ProcessItemsBeforeSave(); - - JSON_ARRAY *const items_arr = JSON_ArrayNew(); - for (int32_t i = 0; i < Item_GetLevelCount(); i++) { - JSON_OBJECT *const item_obj = JSON_ObjectNew(); - const ITEM *const item = Item_Get(i); - const OBJECT *const obj = Object_Get(item->object_id); - - JSON_ObjectAppendInt(item_obj, "obj_num", item->object_id); - - if (obj->save_position) { - DUMP_XYZ(item_obj, "pos", item->pos); - DUMP_XYZ(item_obj, "rot", item->rot); - JSON_ObjectAppendInt(item_obj, "room_num", item->room_num); - JSON_ObjectAppendInt(item_obj, "speed", item->speed); - JSON_ObjectAppendInt(item_obj, "fall_speed", item->fall_speed); - } - - if (obj->save_anim) { - JSON_ObjectAppendInt( - item_obj, "current_anim", item->current_anim_state); - JSON_ObjectAppendInt(item_obj, "goal_anim", item->goal_anim_state); - JSON_ObjectAppendInt( - item_obj, "required_anim", item->required_anim_state); - JSON_ObjectAppendInt(item_obj, "anim_num", item->anim_num); - JSON_ObjectAppendInt(item_obj, "frame_num", item->frame_num); - } - - if (obj->save_hitpoints) { - JSON_ObjectAppendInt(item_obj, "hitpoints", item->hit_points); - } - - if (obj->save_flags) { - JSON_ObjectAppendInt(item_obj, "flags", item->flags); - JSON_ObjectAppendInt(item_obj, "status", item->status); - JSON_ObjectAppendBool(item_obj, "active", item->active); - JSON_ObjectAppendBool(item_obj, "gravity", item->gravity); - JSON_ObjectAppendBool(item_obj, "collidable", item->collidable); - JSON_ObjectAppendBool( - item_obj, "intelligent", obj->intelligent && item->data); - JSON_ObjectAppendInt(item_obj, "timer", item->timer); - if (obj->intelligent && item->data != nullptr) { - const CREATURE *const creature = (CREATURE *)item->data; - JSON_ObjectAppendInt( - item_obj, "head_rot", creature->head_rotation); - JSON_ObjectAppendInt( - item_obj, "neck_rot", creature->neck_rotation); - JSON_ObjectAppendInt( - item_obj, "max_turn", creature->maximum_turn); - JSON_ObjectAppendInt( - item_obj, "creature_flags", creature->flags); - JSON_ObjectAppendInt(item_obj, "creature_mood", creature->mood); - } - if (obj->intelligent) { - JSON_ObjectAppendInt( - item_obj, "carried_item", item->carried_item); - } - } - - switch (item->object_id) { - case O_BOAT: { - const BOAT_INFO *const data = (BOAT_INFO *)item->data; - JSON_OBJECT *const data_obj = JSON_ObjectNew(); - JSON_ObjectAppendInt(data_obj, "boat_turn", data->boat_turn); - JSON_ObjectAppendInt( - data_obj, "left_fallspeed", data->left_fallspeed); - JSON_ObjectAppendInt( - data_obj, "right_fallspeed", data->right_fallspeed); - JSON_ObjectAppendInt(data_obj, "tilt_angle", data->tilt_angle); - JSON_ObjectAppendInt( - data_obj, "extra_rotation", data->extra_rotation); - JSON_ObjectAppendInt(data_obj, "water", data->water); - JSON_ObjectAppendInt(data_obj, "pitch", data->pitch); - JSON_ObjectAppendObject(item_obj, "data", data_obj); - break; - } - - case O_SKIDOO_FAST: { - const SKIDOO_INFO *const data = (SKIDOO_INFO *)item->data; - JSON_OBJECT *const data_obj = JSON_ObjectNew(); - JSON_ObjectAppendInt(data_obj, "track_mesh", data->track_mesh); - JSON_ObjectAppendInt(data_obj, "skidoo_turn", data->skidoo_turn); - JSON_ObjectAppendInt( - data_obj, "left_fallspeed", data->left_fallspeed); - JSON_ObjectAppendInt( - data_obj, "right_fallspeed", data->right_fallspeed); - JSON_ObjectAppendInt( - data_obj, "momentum_angle", data->momentum_angle); - JSON_ObjectAppendInt( - data_obj, "extra_rotation", data->extra_rotation); - JSON_ObjectAppendInt(data_obj, "pitch", data->pitch); - JSON_ObjectAppendObject(item_obj, "data", data_obj); - break; - } - - case O_LIFT: { - const LIFT_INFO *const data = (LIFT_INFO *)item->data; - JSON_OBJECT *const data_obj = JSON_ObjectNew(); - JSON_ObjectAppendInt(data_obj, "start_height", data->start_height); - JSON_ObjectAppendInt(data_obj, "wait_time", data->wait_time); - JSON_ObjectAppendObject(item_obj, "data", data_obj); - break; - } - - default: - break; - } - - JSON_ArrayAppendObject(items_arr, item_obj); - } - return items_arr; -} - -static bool M_LoadItems(JSON_ARRAY *const items_arr) -{ - if (items_arr == nullptr) { - LOG_ERROR("Malformed save: invalid or missing items array"); - return false; - } - - const int32_t item_count = Item_GetLevelCount(); - if ((signed)items_arr->length != item_count) { - LOG_ERROR( - "Malformed save: expected %d items, got %d", item_count, - items_arr->length); - return false; - } - - Savegame_ProcessItemsBeforeLoad(); - - for (int32_t i = 0; i < item_count; i++) { - JSON_OBJECT *const item_obj = JSON_ArrayGetObject(items_arr, i); - if (item_obj == nullptr) { - LOG_ERROR("Malformed save: invalid item data"); - return false; - } - - ITEM *const item = Item_Get(i); - const OBJECT *const obj = Object_Get(item->object_id); - - const GAME_OBJECT_ID obj_id = - JSON_ObjectGetInt(item_obj, "obj_num", -1); - if (!M_IsValidItemObject(obj_id, item->object_id)) { - LOG_ERROR( - "Malformed save: expected object %d, got %d", item->object_id, - obj_id); - return false; - } - - if (obj->save_position) { - LOAD_XYZ(item_obj, "pos", item->pos); - LOAD_XYZ(item_obj, "rot", item->rot); - item->speed = JSON_ObjectGetInt(item_obj, "speed", item->speed); - item->fall_speed = - JSON_ObjectGetInt(item_obj, "fall_speed", item->fall_speed); - - int16_t room_num = JSON_ObjectGetInt(item_obj, "room_num", -1); - if (room_num != -1 && item->room_num != room_num) { - Item_NewRoom(i, room_num); - } - } - - if (obj->save_anim) { - item->current_anim_state = JSON_ObjectGetInt( - item_obj, "current_anim", item->current_anim_state); - item->goal_anim_state = - JSON_ObjectGetInt(item_obj, "goal_anim", item->goal_anim_state); - item->required_anim_state = JSON_ObjectGetInt( - item_obj, "required_anim", item->required_anim_state); - item->anim_num = - JSON_ObjectGetInt(item_obj, "anim_num", item->anim_num); - item->frame_num = - JSON_ObjectGetInt(item_obj, "frame_num", item->frame_num); - } - - if (obj->save_hitpoints) { - item->hit_points = - JSON_ObjectGetInt(item_obj, "hitpoints", item->hit_points); - } - - if (obj->save_flags) { - item->flags = JSON_ObjectGetInt(item_obj, "flags", item->flags); - item->timer = JSON_ObjectGetInt(item_obj, "timer", item->timer); - - if ((item->flags & IF_KILLED) != 0) { - Item_Kill(i); - item->status = IS_DEACTIVATED; - } else { - if (JSON_ObjectGetBool(item_obj, "active", item->active) - && !item->active) { - Item_AddActive(i); - } - item->status = - JSON_ObjectGetInt(item_obj, "status", item->status); - item->gravity = - JSON_ObjectGetBool(item_obj, "gravity", item->gravity); - item->collidable = JSON_ObjectGetBool( - item_obj, "collidable", item->collidable); - } - - if (JSON_ObjectGetBool(item_obj, "intelligent", obj->intelligent)) { - LOT_EnableBaddieAI(i, true); - CREATURE *const creature = (CREATURE *)item->data; - if (creature != nullptr) { - creature->head_rotation = JSON_ObjectGetInt( - item_obj, "head_rot", creature->head_rotation); - creature->neck_rotation = JSON_ObjectGetInt( - item_obj, "neck_rot", creature->neck_rotation); - creature->maximum_turn = JSON_ObjectGetInt( - item_obj, "max_turn", creature->maximum_turn); - creature->flags = JSON_ObjectGetInt( - item_obj, "creature_flags", creature->flags); - creature->mood = JSON_ObjectGetInt( - item_obj, "creature_mood", creature->mood); - } - } else if (obj->intelligent) { - item->data = nullptr; - if (item->killed && item->hit_points <= 0 - && !(item->flags & IF_KILLED)) { - item->next_active = Item_GetPrevActive(); - Item_SetPrevActive(i); - } - } - - if (obj->intelligent) { - item->carried_item = - JSON_ObjectGetInt(item_obj, "carried_item", NO_ITEM); - } - - switch (item->object_id) { - case O_BOAT: { - const JSON_OBJECT *const data_obj = - JSON_ObjectGetObject(item_obj, "data"); - if (data_obj == nullptr) { - LOG_ERROR( - "Malformed save: missing boat data for item %d", i); - return false; - } - BOAT_INFO *const data = (BOAT_INFO *)item->data; - data->boat_turn = - JSON_ObjectGetInt(data_obj, "boat_turn", data->boat_turn); - data->left_fallspeed = JSON_ObjectGetInt( - data_obj, "left_fallspeed", data->left_fallspeed); - data->right_fallspeed = JSON_ObjectGetInt( - data_obj, "right_fallspeed", data->right_fallspeed); - data->tilt_angle = - JSON_ObjectGetInt(data_obj, "tilt_angle", data->tilt_angle); - data->extra_rotation = JSON_ObjectGetInt( - data_obj, "extra_rotation", data->extra_rotation); - data->water = JSON_ObjectGetInt(data_obj, "water", data->water); - data->pitch = JSON_ObjectGetInt(data_obj, "pitch", data->pitch); - break; - } - - case O_SKIDOO_FAST: { - const JSON_OBJECT *const data_obj = - JSON_ObjectGetObject(item_obj, "data"); - if (data_obj == nullptr) { - LOG_ERROR( - "Malformed save: missing skidoo data for item %d", i); - return false; - } - SKIDOO_INFO *const data = (SKIDOO_INFO *)item->data; - data->track_mesh = - JSON_ObjectGetInt(data_obj, "track_mesh", data->track_mesh); - data->skidoo_turn = JSON_ObjectGetInt( - data_obj, "skidoo_turn", data->skidoo_turn); - data->left_fallspeed = JSON_ObjectGetInt( - data_obj, "left_fallspeed", data->left_fallspeed); - data->right_fallspeed = JSON_ObjectGetInt( - data_obj, "right_fallspeed", data->right_fallspeed); - data->momentum_angle = JSON_ObjectGetInt( - data_obj, "momentum_angle", data->momentum_angle); - data->extra_rotation = JSON_ObjectGetInt( - data_obj, "extra_rotation", data->extra_rotation); - data->pitch = JSON_ObjectGetInt(data_obj, "pitch", data->pitch); - break; - } - - case O_LIFT: { - const JSON_OBJECT *const data_obj = - JSON_ObjectGetObject(item_obj, "data"); - if (data_obj == nullptr) { - LOG_ERROR( - "Malformed save: missing lift data for item %d", i); - return false; - } - LIFT_INFO *const data = (LIFT_INFO *)item->data; - data->start_height = JSON_ObjectGetInt( - data_obj, "start_height", data->start_height); - data->wait_time = - JSON_ObjectGetInt(data_obj, "wait_time", data->wait_time); - break; - } - - default: - break; - } - - if (obj->handle_save_func != nullptr) { - obj->handle_save_func(item, SAVEGAME_STAGE_AFTER_LOAD); - } - } - } - - return true; -} - -static bool M_IsValidItemObject( - const GAME_OBJECT_ID saved_obj_id, const GAME_OBJECT_ID initial_obj_id) -{ - if (saved_obj_id == initial_obj_id) { - return true; - } - - // clang-format off - switch (saved_obj_id) { - // used keyholes - case O_PUZZLE_DONE_1: return initial_obj_id == O_PUZZLE_HOLE_1; - case O_PUZZLE_DONE_2: return initial_obj_id == O_PUZZLE_HOLE_2; - case O_PUZZLE_DONE_3: return initial_obj_id == O_PUZZLE_HOLE_3; - case O_PUZZLE_DONE_4: return initial_obj_id == O_PUZZLE_HOLE_4; - // pickups - case O_PISTOL_AMMO_ITEM: return initial_obj_id == O_PISTOL_ITEM; - case O_SHOTGUN_AMMO_ITEM: return initial_obj_id == O_SHOTGUN_ITEM; - case O_MAGNUM_AMMO_ITEM: return initial_obj_id == O_MAGNUM_ITEM; - case O_UZI_AMMO_ITEM: return initial_obj_id == O_UZI_ITEM; - case O_HARPOON_AMMO_ITEM: return initial_obj_id == O_HARPOON_ITEM; - case O_M16_AMMO_ITEM: return initial_obj_id == O_M16_ITEM; - case O_GRENADE_AMMO_ITEM: return initial_obj_id == O_GRENADE_ITEM; - // skidoo swaps - case O_SKIDOO_FAST: return initial_obj_id == O_SKIDOO_ARMED; - // default - default: return false; - } - // clang-format on -} - -static JSON_ARRAY *M_DumpFlares(void) -{ - JSON_ARRAY *const flares_arr = JSON_ArrayNew(); - for (int32_t item_num = 0; item_num < Item_GetTotalCount(); item_num++) { - const ITEM *const item = Item_Get(item_num); - if (!item->active || item->object_id != O_FLARE_ITEM) { - continue; - } - - JSON_OBJECT *const flare_obj = JSON_ObjectNew(); - DUMP_XYZ(flare_obj, "pos", item->pos); - DUMP_XYZ(flare_obj, "rot", item->rot); - JSON_ObjectAppendInt(flare_obj, "room_num", item->room_num); - JSON_ObjectAppendInt(flare_obj, "speed", item->speed); - JSON_ObjectAppendInt(flare_obj, "fall_speed", item->fall_speed); - JSON_ObjectAppendInt(flare_obj, "age", (intptr_t)item->data); - JSON_ArrayAppendObject(flares_arr, flare_obj); - } - return flares_arr; -} - -static bool M_LoadFlares(JSON_ARRAY *const flares_arr) -{ - if (flares_arr == nullptr) { - LOG_ERROR("Malformed save: invalid or missing flares array"); - return false; - } - - for (int32_t i = 0; i < (signed)flares_arr->length; i++) { - JSON_OBJECT *const flare_obj = JSON_ArrayGetObject(flares_arr, i); - if (flare_obj == nullptr) { - LOG_ERROR("Malformed save: invalid flare data"); - return false; - } - - const int16_t item_num = Item_Create(); - ITEM *const item = Item_Get(item_num); - item->object_id = O_FLARE_ITEM; - LOAD_XYZ(flare_obj, "pos", item->pos); - LOAD_XYZ(flare_obj, "rot", item->rot); - item->room_num = - JSON_ObjectGetInt(flare_obj, "room_num", item->room_num); - item->speed = JSON_ObjectGetInt(flare_obj, "speed", item->speed); - item->fall_speed = - JSON_ObjectGetInt(flare_obj, "fall_speed", item->fall_speed); - Item_Initialise(item_num); - Item_AddActive(item_num); - const int32_t flare_age = JSON_ObjectGetInt(flare_obj, "age", 0); - item->data = (void *)(intptr_t)flare_age; - } - return true; -} - -static JSON_OBJECT *M_DumpLara(void) -{ - const LARA_INFO *const lara = Lara_GetLaraInfo(); - ASSERT(lara != nullptr); - - JSON_OBJECT *const lara_obj = JSON_ObjectNew(); - JSON_ObjectAppendInt(lara_obj, "item_number", lara->item_num); - JSON_ObjectAppendInt(lara_obj, "gun_status", lara->gun_status); - JSON_ObjectAppendInt(lara_obj, "gun_type", lara->gun_type); - JSON_ObjectAppendInt(lara_obj, "request_gun_type", lara->request_gun_type); - JSON_ObjectAppendInt(lara_obj, "last_gun_type", lara->last_gun_type); - JSON_ObjectAppendInt(lara_obj, "calc_fall_speed", lara->calc_fall_speed); - JSON_ObjectAppendInt(lara_obj, "water_status", lara->water_status); - JSON_ObjectAppendInt(lara_obj, "climb_status", lara->climb_status); - JSON_ObjectAppendInt(lara_obj, "pose_count", lara->pose_count); - JSON_ObjectAppendInt(lara_obj, "hit_frame", lara->hit_frame); - JSON_ObjectAppendInt(lara_obj, "hit_direction", lara->hit_direction); - JSON_ObjectAppendInt(lara_obj, "air", lara->air); - JSON_ObjectAppendInt(lara_obj, "dive_count", lara->dive_count); - JSON_ObjectAppendInt(lara_obj, "death_count", lara->death_timer); - JSON_ObjectAppendInt(lara_obj, "current_active", lara->current_active); - JSON_ObjectAppendInt(lara_obj, "hit_effect_count", lara->hit_effect_count); - JSON_ObjectAppendInt(lara_obj, "flare_age", lara->flare_age); - JSON_ObjectAppendInt(lara_obj, "vehicle_item_number", lara->skidoo); - JSON_ObjectAppendInt(lara_obj, "back_gun_obj_id", lara->back_gun); - JSON_ObjectAppendInt(lara_obj, "flare_frame", lara->flare_frame); - JSON_ObjectAppendInt(lara_obj, "mesh_effects", lara->mesh_effects); - JSON_ObjectAppendInt( - lara_obj, "water_surface_dist", lara->water_surface_dist); - - JSON_ObjectAppendBool( - lara_obj, "flare_control_left", lara->flare_control_left); - JSON_ObjectAppendBool( - lara_obj, "flare_control_right", lara->flare_control_right); - JSON_ObjectAppendBool(lara_obj, "extra_anim", lara->extra_anim); - JSON_ObjectAppendBool(lara_obj, "look", lara->look); - JSON_ObjectAppendBool(lara_obj, "burn", lara->burn); - - JSON_ARRAY *const lara_meshes_arr = JSON_ArrayNew(); - for (int i = 0; i < LM_NUMBER_OF; i++) { - JSON_ArrayAppendInt( - lara_meshes_arr, Object_GetMeshOffset(lara->mesh_ptrs[i])); - } - JSON_ObjectAppendArray(lara_obj, "meshes", lara_meshes_arr); - - JSON_ObjectAppendInt(lara_obj, "target_angle1", lara->target_angles[0]); - JSON_ObjectAppendInt(lara_obj, "target_angle2", lara->target_angles[1]); - JSON_ObjectAppendInt(lara_obj, "turn_rate", lara->turn_rate); - JSON_ObjectAppendInt(lara_obj, "move_angle", lara->move_angle); - DUMP_XYZ(lara_obj, "head_rot", lara->head_rot); - DUMP_XYZ(lara_obj, "torso_rot", lara->torso_rot); - DUMP_XYZ(lara_obj, "last_pos", lara->last_pos); - - JSON_ObjectAppendObject(lara_obj, "left_arm", M_DumpArm(&lara->left_arm)); - JSON_ObjectAppendObject(lara_obj, "right_arm", M_DumpArm(&lara->right_arm)); - JSON_ObjectAppendObject( - lara_obj, "pistols", M_DumpAmmo(&lara->pistol_ammo)); - JSON_ObjectAppendObject( - lara_obj, "magnums", M_DumpAmmo(&lara->magnum_ammo)); - JSON_ObjectAppendObject(lara_obj, "uzis", M_DumpAmmo(&lara->uzi_ammo)); - JSON_ObjectAppendObject( - lara_obj, "shotgun", M_DumpAmmo(&lara->shotgun_ammo)); - JSON_ObjectAppendObject( - lara_obj, "harpoon", M_DumpAmmo(&lara->harpoon_ammo)); - JSON_ObjectAppendObject( - lara_obj, "grenade", M_DumpAmmo(&lara->grenade_ammo)); - JSON_ObjectAppendObject(lara_obj, "m16", M_DumpAmmo(&lara->m16_ammo)); - - if (lara->weapon_item != NO_ITEM) { - JSON_OBJECT *const weapon_obj = JSON_ObjectNew(); - const ITEM *const weapon_item = Item_Get(lara->weapon_item); - JSON_ObjectAppendInt(weapon_obj, "obj_id", weapon_item->object_id); - JSON_ObjectAppendInt(weapon_obj, "anim_num", weapon_item->anim_num); - JSON_ObjectAppendInt(weapon_obj, "frame_num", weapon_item->frame_num); - JSON_ObjectAppendInt( - weapon_obj, "current_anim_state", weapon_item->current_anim_state); - JSON_ObjectAppendInt( - weapon_obj, "goal_anim_state", weapon_item->goal_anim_state); - JSON_ObjectAppendObject(lara_obj, "weapon", weapon_obj); - } - - return lara_obj; -} - -static bool M_LoadLara(JSON_OBJECT *const lara_obj) -{ - if (lara_obj == nullptr) { - LOG_ERROR("Malformed save: invalid or missing Lara info"); - return false; - } - - LARA_INFO *const lara = Lara_GetLaraInfo(); - ASSERT(lara != nullptr); - - lara->item_num = JSON_ObjectGetInt(lara_obj, "item_number", lara->item_num); - lara->gun_status = - JSON_ObjectGetInt(lara_obj, "gun_status", lara->gun_status); - lara->gun_type = JSON_ObjectGetInt(lara_obj, "gun_type", lara->gun_type); - lara->request_gun_type = - JSON_ObjectGetInt(lara_obj, "request_gun_type", lara->request_gun_type); - lara->last_gun_type = - JSON_ObjectGetInt(lara_obj, "last_gun_type", lara->last_gun_type); - lara->calc_fall_speed = - JSON_ObjectGetInt(lara_obj, "calc_fall_speed", lara->calc_fall_speed); - lara->water_status = - JSON_ObjectGetInt(lara_obj, "water_status", lara->water_status); - lara->climb_status = - JSON_ObjectGetInt(lara_obj, "climb_status", lara->climb_status); - lara->pose_count = - JSON_ObjectGetInt(lara_obj, "pose_count", lara->pose_count); - lara->hit_frame = JSON_ObjectGetInt(lara_obj, "hit_frame", lara->hit_frame); - lara->hit_direction = - JSON_ObjectGetInt(lara_obj, "hit_direction", lara->hit_direction); - lara->air = JSON_ObjectGetInt(lara_obj, "air", lara->air); - lara->dive_count = - JSON_ObjectGetInt(lara_obj, "dive_count", lara->dive_count); - lara->death_timer = - JSON_ObjectGetInt(lara_obj, "death_count", lara->death_timer); - lara->current_active = - JSON_ObjectGetInt(lara_obj, "current_active", lara->current_active); - lara->hit_effect_count = - JSON_ObjectGetInt(lara_obj, "hit_effect_count", lara->hit_effect_count); - lara->flare_age = JSON_ObjectGetInt(lara_obj, "flare_age", lara->flare_age); - lara->skidoo = - JSON_ObjectGetInt(lara_obj, "vehicle_item_number", lara->skidoo); - lara->back_gun = - JSON_ObjectGetInt(lara_obj, "back_gun_obj_id", lara->back_gun); - lara->flare_frame = - JSON_ObjectGetInt(lara_obj, "flare_frame", lara->flare_frame); - lara->mesh_effects = - JSON_ObjectGetInt(lara_obj, "mesh_effects", lara->mesh_effects); - lara->water_surface_dist = JSON_ObjectGetInt( - lara_obj, "water_surface_dist", lara->water_surface_dist); - - lara->flare_control_left = JSON_ObjectGetBool( - lara_obj, "flare_control_left", lara->flare_control_left); - lara->flare_control_right = JSON_ObjectGetBool( - lara_obj, "flare_control_right", lara->flare_control_right); - lara->extra_anim = - JSON_ObjectGetBool(lara_obj, "extra_anim", lara->extra_anim); - lara->look = JSON_ObjectGetBool(lara_obj, "look", lara->look); - lara->burn = JSON_ObjectGetBool(lara_obj, "burn", lara->burn); - - const JSON_ARRAY *const lara_meshes_arr = - JSON_ObjectGetArray(lara_obj, "meshes"); - if (!lara_meshes_arr) { - LOG_ERROR("Malformed save: invalid or missing Lara meshes"); - return false; - } - if ((signed)lara_meshes_arr->length != LM_NUMBER_OF) { - LOG_ERROR( - "Malformed save: expected %d Lara meshes, got %d", LM_NUMBER_OF, - lara_meshes_arr->length); - return false; - } - - for (int32_t i = 0; i < (signed)lara_meshes_arr->length; i++) { - int32_t idx = Object_GetMeshOffset(lara->mesh_ptrs[i]); - idx = JSON_ArrayGetInt(lara_meshes_arr, i, idx); - OBJECT_MESH *const mesh = Object_FindMesh(idx); - if (mesh != nullptr) { - lara->mesh_ptrs[i] = mesh; - } - } - - lara->target = nullptr; - lara->target_angles[0] = - JSON_ObjectGetInt(lara_obj, "target_angle1", lara->target_angles[0]); - lara->target_angles[1] = - JSON_ObjectGetInt(lara_obj, "target_angle2", lara->target_angles[1]); - lara->turn_rate = JSON_ObjectGetInt(lara_obj, "turn_rate", lara->turn_rate); - lara->move_angle = - JSON_ObjectGetInt(lara_obj, "move_angle", lara->move_angle); - LOAD_XYZ(lara_obj, "head_rot", lara->head_rot); - LOAD_XYZ(lara_obj, "torso_rot", lara->torso_rot); - LOAD_XYZ(lara_obj, "last_pos", lara->last_pos); - - if (!M_LoadArm( - JSON_ObjectGetObject(lara_obj, "left_arm"), &lara->left_arm)) { - return false; - } - - if (!M_LoadArm( - JSON_ObjectGetObject(lara_obj, "right_arm"), &lara->right_arm)) { - return false; - } - - if (!M_LoadAmmo( - JSON_ObjectGetObject(lara_obj, "pistols"), &lara->pistol_ammo)) { - return false; - } - - if (!M_LoadAmmo( - JSON_ObjectGetObject(lara_obj, "magnums"), &lara->magnum_ammo)) { - return false; - } - - if (!M_LoadAmmo(JSON_ObjectGetObject(lara_obj, "uzis"), &lara->uzi_ammo)) { - return false; - } - - if (!M_LoadAmmo( - JSON_ObjectGetObject(lara_obj, "shotgun"), &lara->shotgun_ammo)) { - return false; - } - - if (!M_LoadAmmo( - JSON_ObjectGetObject(lara_obj, "harpoon"), &lara->harpoon_ammo)) { - return false; - } - - if (!M_LoadAmmo( - JSON_ObjectGetObject(lara_obj, "grenade"), &lara->grenade_ammo)) { - return false; - } - - if (!M_LoadAmmo(JSON_ObjectGetObject(lara_obj, "m16"), &lara->m16_ammo)) { - return false; - } - - const JSON_OBJECT *const weapon_obj = - JSON_ObjectGetObject(lara_obj, "weapon"); - if (weapon_obj != nullptr) { - lara->weapon_item = Item_Create(); - ITEM *const weapon_item = Item_Get(lara->weapon_item); - weapon_item->object_id = - JSON_ObjectGetInt(weapon_obj, "obj_id", weapon_item->object_id); - weapon_item->anim_num = - JSON_ObjectGetInt(weapon_obj, "anim_num", weapon_item->anim_num); - weapon_item->frame_num = - JSON_ObjectGetInt(weapon_obj, "frame_num", weapon_item->frame_num); - weapon_item->current_anim_state = JSON_ObjectGetInt( - weapon_obj, "current_anim_state", weapon_item->current_anim_state); - weapon_item->goal_anim_state = JSON_ObjectGetInt( - weapon_obj, "goal_anim_state", weapon_item->goal_anim_state); - weapon_item->status = IS_ACTIVE; - weapon_item->room_num = NO_ROOM; - } - - return true; -} - -static JSON_OBJECT *M_DumpArm(const LARA_ARM *const arm) -{ - ASSERT(arm != nullptr); - JSON_OBJECT *const arm_obj = JSON_ObjectNew(); - JSON_ObjectAppendInt(arm_obj, "anim_num", arm->anim_num); - JSON_ObjectAppendInt(arm_obj, "frame_num", arm->frame_num); - JSON_ObjectAppendInt(arm_obj, "lock", arm->lock); - JSON_ObjectAppendInt(arm_obj, "flash_gun", arm->flash_gun); - DUMP_XYZ(arm_obj, "rot", arm->rot); - return arm_obj; -} - -static bool M_LoadArm(JSON_OBJECT *const arm_obj, LARA_ARM *const arm) -{ - ASSERT(arm != nullptr); - if (arm_obj == nullptr) { - LOG_ERROR("Malformed save: invalid or missing arm info"); - return false; - } - - arm->frame_num = JSON_ObjectGetInt(arm_obj, "frame_num", arm->frame_num); - arm->lock = JSON_ObjectGetInt(arm_obj, "lock", arm->lock); - arm->flash_gun = JSON_ObjectGetInt(arm_obj, "flash_gun", arm->flash_gun); - LOAD_XYZ(arm_obj, "rot", arm->rot); - - return true; -} - -static JSON_OBJECT *M_DumpAmmo(const AMMO_INFO *const ammo) -{ - ASSERT(ammo != nullptr); - JSON_OBJECT *const ammo_obj = JSON_ObjectNew(); - JSON_ObjectAppendInt(ammo_obj, "ammo", ammo->ammo); - return ammo_obj; -} - -static bool M_LoadAmmo(JSON_OBJECT *const ammo_obj, AMMO_INFO *const ammo) -{ - ASSERT(ammo != nullptr); - if (ammo_obj == nullptr) { - LOG_ERROR("Malformed save: invalid or missing ammo info"); - return false; - } - - ammo->ammo = JSON_ObjectGetInt(ammo_obj, "ammo", ammo->ammo); - return true; -} - -REGISTER_SAVEGAME_STRATEGY(m_Strategy) diff --git a/src/libtrx/game/scaler.c b/src/tr2/game/scaler.c similarity index 66% rename from src/libtrx/game/scaler.c rename to src/tr2/game/scaler.c index 38913d0a5..d8595f125 100644 --- a/src/libtrx/game/scaler.c +++ b/src/tr2/game/scaler.c @@ -1,21 +1,20 @@ #include "game/scaler.h" -#include "config.h" -#include "game/viewport.h" -#include "log.h" -#include "utils.h" +#include "global/vars.h" + +#include +#include +#include static int32_t M_DoCalc( int32_t unit, int32_t base_width, int32_t base_height, double factor) { - const int32_t win_width = Viewport_GetWidth(); - const int32_t win_height = Viewport_GetHeight(); const int32_t sign = unit < 0 ? -1 : 1; - const int32_t scale_x = win_width > base_width - ? ((double)win_width * ABS(unit) * factor) / MAX(1, base_width) + const int32_t scale_x = g_PhdWinWidth > base_width + ? ((double)g_PhdWinWidth * ABS(unit) * factor) / base_width : ABS(unit) * factor; - const int32_t scale_y = win_height > base_height - ? ((double)win_height * ABS(unit) * factor) / MAX(1, base_height) + const int32_t scale_y = g_PhdWinHeight > base_height + ? ((double)g_PhdWinHeight * ABS(unit) * factor) / base_height : ABS(unit) * factor; return MIN(scale_x, scale_y) * sign; } diff --git a/src/libtrx/include/libtrx/game/scaler.h b/src/tr2/game/scaler.h similarity index 100% rename from src/libtrx/include/libtrx/game/scaler.h rename to src/tr2/game/scaler.h diff --git a/src/tr2/game/shell/common.c b/src/tr2/game/shell/common.c index b40b7e758..4ee02ea3b 100644 --- a/src/tr2/game/shell/common.c +++ b/src/tr2/game/shell/common.c @@ -1,6 +1,7 @@ #include "game/shell/common.h" #include "decomp/decomp.h" +#include "decomp/savegame.h" #include "game/clock.h" #include "game/console/common.h" #include "game/demo.h" @@ -9,16 +10,11 @@ #include "game/game_flow.h" #include "game/game_string.h" #include "game/input.h" -#include "game/level.h" #include "game/music.h" -#include "game/objects/creatures/big_spider.h" -#include "game/objects/creatures/monk.h" -#include "game/objects/creatures/spider.h" #include "game/output.h" #include "game/phase.h" #include "game/random.h" #include "game/render/common.h" -#include "game/savegame.h" #include "game/sound.h" #include "game/text.h" #include "game/viewport.h" @@ -29,10 +25,8 @@ #include #include #include -#include -#include #include -#include +#include #include #include @@ -43,7 +37,6 @@ typedef enum { M_MOD_UNKNOWN, M_MOD_OG, - M_MOD_GM, M_MOD_CUSTOM_LEVEL, } M_MOD; @@ -61,10 +54,6 @@ static struct { .game_flow_path = "cfg/TR2X_gameflow.json5", .game_strings_path = "cfg/TR2X_strings.json5", }, - [M_MOD_GM] = { - .game_flow_path = "cfg/TR2X_gameflow_gm.json5", - .game_strings_path = "cfg/TR2X_strings_gm.json5", - }, [M_MOD_CUSTOM_LEVEL] = { .game_flow_path = "cfg/TR2X_gameflow_level.json5", .game_strings_path = "cfg/TR2X_strings_level.json5", @@ -77,13 +66,10 @@ static SHELL_ARGS m_Args = { .save_to_load = -1, }; -static SHELL_SIZE m_ViewportSize = { .w = -1, .h = -1 }; static Uint64 m_UpdateDebounce = 0; -static bool m_IgnoreConfigChanges = false; static void M_SyncToWindow(void); -static void M_SyncFromWindow(bool update_viewport); -static bool M_MustUpdateRendererViewport(void); +static void M_SyncFromWindow(void); static void M_RefreshRendererViewport(void); static void M_HandleFocusGained(void); static void M_HandleFocusLost(void); @@ -99,7 +85,7 @@ static void M_HandleQuit(void); static void M_ConfigureOpenGL(void); static bool M_CreateGameWindow(void); -static void M_ShowHelp(void); +static void M_ParseArgs(SHELL_ARGS *out_args); static void M_LoadConfig(void); static void M_HandleConfigChange(const EVENT *event, void *data); @@ -138,35 +124,9 @@ static void M_SyncToWindow(void) width = 1280; height = 720; } - - // Handle default position - if (x == -1 && y == -1) { - SDL_DisplayMode display_mode; - SDL_GetCurrentDisplayMode(0, &display_mode); - x = (display_mode.w - width) / 2; - y = (display_mode.h - height) / 2; - } else { - // Adjust window position if completely offscreen - bool on_screen = false; - const int32_t num_displays = SDL_GetNumVideoDisplays(); - for (int32_t i = 0; i < num_displays; i++) { - SDL_Rect bounds; - SDL_GetDisplayBounds(i, &bounds); - if (x + width > bounds.x && x < bounds.x + bounds.w - && y + height > bounds.y && y < bounds.y + bounds.h) { - on_screen = true; - break; - } - } - if (!on_screen) { - x = 0; - y = 0; - // Find the first display to reposition the window - SDL_Rect bounds; - SDL_GetDisplayBounds(0, &bounds); - x = bounds.x + (bounds.w - width) / 2; - y = bounds.y + (bounds.h - height) / 2; - } + if (x <= 0 || y <= 0) { + x = (Shell_GetCurrentDisplayWidth() - width) / 2; + y = (Shell_GetCurrentDisplayHeight() - height) / 2; } SDL_SetWindowFullscreen(g_SDLWindow, 0); @@ -176,54 +136,50 @@ static void M_SyncToWindow(void) } } -static void M_SyncFromWindow(const bool update_viewport) +static void M_SyncFromWindow(void) { - // Determine if this call should sync config, i.e., skip immediate - // programmatic events - const Uint32 now = SDL_GetTicks(); - const bool skip_config = (now - m_UpdateDebounce) < 500; + if (SDL_GetTicks() - m_UpdateDebounce < 1000) { + // Setting the size programatically triggers resize events. + // Additionally, SDL_GetWindowSize() is not guaranteed to return the + // same values as passed to SDL_SetWindowSize(). In order to avoid + // infinite loops where the window dimensions are continuously updated, + // resize events are debounced. + return; + } - // Always pull current window state for logging and viewport reset const Uint32 window_flags = SDL_GetWindowFlags(g_SDLWindow); const bool is_maximized = window_flags & SDL_WINDOW_MAXIMIZED; - int32_t x, y; - int32_t width, height; + + int32_t width; + int32_t height; SDL_GetWindowSize(g_SDLWindow, &width, &height); + + int32_t x; + int32_t y; SDL_GetWindowPosition(g_SDLWindow, &x, &y); + LOG_INFO("%dx%d+%d,%d (maximized: %d)", width, height, x, y, is_maximized); - // Update config only when not in debounce window - if (!skip_config) { - g_Config.window.is_maximized = is_maximized; - if (!is_maximized && !g_Config.window.is_fullscreen) { - g_Config.window.x = x; - g_Config.window.y = y; - g_Config.window.width = width; - g_Config.window.height = height; - } - if (g_Config.loaded) { - m_IgnoreConfigChanges = true; - Config_Write(); - m_IgnoreConfigChanges = false; - } + g_Config.window.is_maximized = is_maximized; + if (!is_maximized && !g_Config.window.is_fullscreen) { + g_Config.window.x = x; + g_Config.window.y = y; + g_Config.window.width = width; + g_Config.window.height = height; } - if (update_viewport || M_MustUpdateRendererViewport()) { - // Refresh viewport to reflect the actual window size - M_RefreshRendererViewport(); + // Save the updated config, but ensure it was loaded first + if (g_Config.loaded) { + Config_Write(); } -} -static bool M_MustUpdateRendererViewport(void) -{ - const SHELL_SIZE size = Shell_GetCurrentSize(); - return m_ViewportSize.w != size.w || m_ViewportSize.h != size.h; + M_RefreshRendererViewport(); } static void M_RefreshRendererViewport(void) { Viewport_Reset(); - m_ViewportSize = Shell_GetCurrentSize(); + UI_Events_Fire(&(EVENT) { .name = "canvas_resize" }); } static void M_HandleFocusGained(void) @@ -241,7 +197,7 @@ static void M_HandleWindowShown(void) static void M_HandleWindowRestored(void) { - M_SyncFromWindow(true); + M_SyncFromWindow(); } static void M_HandleWindowMinimized(void) @@ -251,17 +207,17 @@ static void M_HandleWindowMinimized(void) static void M_HandleWindowMaximized(void) { - M_SyncFromWindow(true); + M_SyncFromWindow(); } static void M_HandleWindowMoved(const int32_t x, const int32_t y) { - M_SyncFromWindow(false); + M_SyncFromWindow(); } static void M_HandleWindowResized(int32_t width, int32_t height) { - M_SyncFromWindow(true); + M_SyncFromWindow(); } static void M_HandleKeyDown(const SDL_Event *const event) @@ -271,7 +227,7 @@ static void M_HandleKeyDown(const SDL_Event *const event) // some keypresses if the player types really fast, so we need to // react sooner. if (!FMV_IsPlaying() && g_Config.gameplay.enable_console - && !Console_IsOpened() && !Input_IsInListenMode() + && !Console_IsOpened() && Input_IsPressed( INPUT_BACKEND_KEYBOARD, g_Config.input.keyboard_layout, INPUT_ROLE_ENTER_CONSOLE)) { @@ -342,13 +298,27 @@ static bool M_CreateGameWindow(void) return true; } -static void M_ShowHelp(void) +static void M_ParseArgs(SHELL_ARGS *const out_args) { - puts("Currently available options:"); - puts(""); - puts("-g/--gold: launch The Golden Mask expansion pack."); - puts("-l/--level : launch a specific level file."); - puts("-s/--save : launch from a specific save slot (starts at 1)."); + const char **args = nullptr; + int32_t arg_count = 0; + Shell_GetCommandLine(&arg_count, &args); + + out_args->mod = M_MOD_OG; + + for (int32_t i = 0; i < arg_count; i++) { + if ((!strcmp(args[i], "-l") || !strcmp(args[i], "--level")) + && i + 1 < arg_count) { + out_args->level_to_play = args[i + 1]; + out_args->mod = M_MOD_CUSTOM_LEVEL; + } + if ((!strcmp(args[i], "-s") || !strcmp(args[i], "--save")) + && i + 1 < arg_count) { + if (String_ParseInteger(args[i + 1], &out_args->save_to_load)) { + out_args->save_to_load--; + } + } + } } static void M_LoadConfig(void) @@ -362,10 +332,6 @@ static void M_LoadConfig(void) static void M_HandleConfigChange(const EVENT *const event, void *const data) { - if (m_IgnoreConfigChanges) { - return; - } - const CONFIG *const old = &g_Config; const CONFIG *const new = &g_SavedConfig; @@ -379,9 +345,9 @@ static void M_HandleConfigChange(const EVENT *const event, void *const data) } if (CHANGED(window.is_fullscreen) || CHANGED(window.is_maximized) - || CHANGED(window.width) || CHANGED(window.height) - || CHANGED(rendering.scaler) || CHANGED(rendering.sizer) - || CHANGED(rendering.aspect_mode) || CHANGED(visuals.use_psx_fov)) { + || CHANGED(window.x) || CHANGED(window.y) || CHANGED(window.width) + || CHANGED(window.height) || CHANGED(rendering.scaler) + || CHANGED(rendering.sizer) || CHANGED(rendering.aspect_mode)) { LOG_DEBUG("Change in settings detected"); M_SyncToWindow(); M_RefreshRendererViewport(); @@ -399,66 +365,17 @@ static void M_HandleConfigChange(const EVENT *const event, void *const data) Render_Reset(RENDER_RESET_PARAMS); } - if (CHANGED(visuals.fov) || CHANGED(visuals.use_psx_fov)) { + if (CHANGED(visuals.fov) || CHANGED(visuals.use_pcx_fov)) { if (Viewport_GetFOV(false) == -1) { Viewport_AlterFOV(-1); } } - - if (CHANGED(visuals.fog_start) || CHANGED(visuals.fog_end) - || CHANGED(visuals.water_color.g) || CHANGED(visuals.water_color.b) - || CHANGED(visuals.water_color.r)) { - Output_ApplyLevelSettings(); - } - - if (CHANGED(rendering.aspect_mode)) { - Output_ReloadBackgroundImage(); - } -} - -bool Shell_ParseArgs(const int32_t arg_count, const char **args) -{ - SHELL_ARGS *const out_args = &m_Args; - out_args->mod = M_MOD_OG; - - for (int32_t i = 0; i < arg_count; i++) { - if (!strcmp(args[i], "-h") || !strcmp(args[i], "--help")) { - M_ShowHelp(); - return false; - } - if (!strcmp(args[i], "-g") || !strcmp(args[i], "--gold") - || !strcmp(args[i], "-gold")) { - out_args->mod = M_MOD_GM; - } - if ((!strcmp(args[i], "-l") || !strcmp(args[i], "--level")) - && i + 1 < arg_count) { - out_args->level_to_play = args[i + 1]; - out_args->mod = M_MOD_CUSTOM_LEVEL; - } - if ((!strcmp(args[i], "-s") || !strcmp(args[i], "--save")) - && i + 1 < arg_count) { - if (String_ParseInteger(args[i + 1], &out_args->save_to_load)) { - out_args->save_to_load--; - } - } - } - return true; } // TODO: refactor the hell out of me -int32_t Shell_Main(void) +void Shell_Main(void) { - LOG_INFO("Game directory: %s", File_GetGameDirectory()); - - if (m_Args.mod == M_MOD_GM) { - Object_Get(O_MONK_3)->setup_func = Monk3_Setup; - Object_Get(O_BEAR)->setup_func = Bear_Setup; - Object_Get(O_WOLF)->setup_func = Wolf_Setup; - } else { - Object_Get(O_MONK_1)->setup_func = Monk1_Setup; - Object_Get(O_SPIDER)->setup_func = Spider_Setup; - Object_Get(O_BIG_SPIDER)->setup_func = BigSpider_Setup; - } + M_ParseArgs(&m_Args); GameString_Init(); EnumMap_Init(); @@ -477,7 +394,7 @@ int32_t Shell_Main(void) if (!M_CreateGameWindow()) { Shell_ExitSystem("Failed to create game window"); - return 1; + return; } Random_Seed(); @@ -490,21 +407,14 @@ int32_t Shell_Main(void) GF_Init(); GF_LoadFromFile(m_ModPaths[m_Args.mod].game_flow_path); - - GameStringTable_Init(); - if (m_Args.mod != M_MOD_OG) { - GameStringTable_Load(m_ModPaths[M_MOD_OG].game_strings_path, false); - } - GameStringTable_Load(m_ModPaths[m_Args.mod].game_strings_path, true); + GameStringTable_LoadFromFile(m_ModPaths[m_Args.mod].game_strings_path); GameStringTable_Apply(nullptr); - GameBuf_Init(); - Level_Init(); - Savegame_Init(); Savegame_InitCurrentInfo(); - Savegame_ScanSavedGames(); - Savegame_HighlightNewestSlot(); + S_FrontEndCheck(); + + GameBuf_Init(); if (m_Args.level_to_play != nullptr) { Memory_Free(g_GameFlow.level_tables[GFLT_MAIN].levels[0].path); @@ -541,15 +451,14 @@ int32_t Shell_Main(void) } case GF_START_SAVED_GAME: { - const int16_t slot_num = gf_cmd.param; - const int16_t level_num = Savegame_GetLevelNumber(slot_num); - if (level_num < 0) { - LOG_ERROR("Corrupt save file!"); + if (!S_LoadGame(gf_cmd.param)) { gf_cmd = (GF_COMMAND) { .action = GF_EXIT_TO_TITLE }; } else { - Savegame_BindSlot(slot_num); - const GF_LEVEL *const level = GF_GetLevel(GFLT_MAIN, level_num); - gf_cmd = GF_DoLevelSequence(level, GFSC_SAVED); + const GF_LEVEL *const level = + GF_GetLevel(GFLT_MAIN, g_SaveGame.current_level); + if (level != nullptr) { + gf_cmd = GF_DoLevelSequence(level, GFSC_SAVED); + } } break; } @@ -573,7 +482,7 @@ int32_t Shell_Main(void) if (gf_cmd.action == GF_NOOP || gf_cmd.action == GF_EXIT_TO_TITLE) { Shell_ExitSystem("Title disabled & no replacement"); - return 1; + return; } } else { gf_cmd = GF_RunTitle(); @@ -595,14 +504,12 @@ int32_t Shell_Main(void) if (m_Args.level_to_play != nullptr) { Memory_FreePointer(&g_GameFlow.level_tables[GFLT_MAIN].levels[0].path); } - return 0; } void Shell_Shutdown(void) { - GameStringTable_Shutdown(); GF_Shutdown(); - + GameString_Shutdown(); Console_Shutdown(); Render_Shutdown(); Text_Shutdown(); @@ -610,7 +517,6 @@ void Shell_Shutdown(void) GameBuf_Shutdown(); Config_Shutdown(); EnumMap_Shutdown(); - GameString_Shutdown(); } const char *Shell_GetConfigPath(void) @@ -634,6 +540,12 @@ void Shell_Start(void) M_RefreshRendererViewport(); } +bool Shell_IsFullscreen(void) +{ + const Uint32 flags = SDL_GetWindowFlags(g_SDLWindow); + return (flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0; +} + // TODO: try to call this function in a single place after introducing phases. void Shell_ProcessEvents(void) { diff --git a/src/tr2/game/sound.c b/src/tr2/game/sound.c index 1998b8d7a..16611bf49 100644 --- a/src/tr2/game/sound.c +++ b/src/tr2/game/sound.c @@ -13,6 +13,8 @@ #include +#define MAX_VOLUME 0x8000 + typedef enum { SOUND_MODE_NORMAL = 0, SOUND_MODE_WAIT = 1, @@ -42,9 +44,7 @@ typedef enum { #define SOUND_RADIUS_SQRD SQUARE(SOUND_RADIUS) // = 0x6400000 #define SOUND_MAX_SLOTS 32 -#define SOUND_RANGE_MULT_CONSTANT 4 -// sample volume ranges from 0..32767 -#define SOUND_MAX_VOLUME ((SOUND_RADIUS * SOUND_RANGE_MULT_CONSTANT) - 1) +#define SOUND_MAX_VOLUME (SOUND_RADIUS - 1) #define SOUND_MAX_VOLUME_CHANGE 0x2000 #define SOUND_MAX_PITCH_CHANGE 6000 @@ -69,12 +69,11 @@ static void M_ClearAllSlots(void); static void M_CloseSlot(SOUND_SLOT *const slot); static void M_UpdateSlot(SOUND_SLOT *const slot); -static int32_t M_ConvertVolumeToDecibel(int32_t volume) +static int32_t M_ConvertVolumeToDecibel(const int32_t volume) { - int32_t idx = - volume * (m_MasterVolume / 64.0f) * DECIBEL_LUT_SIZE / SOUND_MAX_VOLUME; - CLAMP(idx, 0, DECIBEL_LUT_SIZE - 1); - return m_DecibelLUT[idx]; + const double adjusted_volume = m_MasterVolume * volume; + const double scaler = 0x1.p-21; // 2.0e-21 + return (adjusted_volume * scaler - 1.0) * 5000.0; } static int32_t M_ConvertPanToDecibel(const uint16_t pan) @@ -282,8 +281,8 @@ bool Sound_Effect( } if (free_slot == -1) { - // No slot found - try to find the most quiet track, and use this one - int32_t min_volume = INT32_MAX; + // No slot found - try to find the most silent track, and use this one + int32_t min_volume = MAX_VOLUME; for (int32_t i = 0; i < SOUND_MAX_SLOTS; i++) { SOUND_SLOT *const slot = &m_SoundSlots[i]; if (slot->effect_num >= 0 && slot->volume < min_volume) { diff --git a/src/tr2/game/sound.h b/src/tr2/game/sound.h index 53b5e88ca..8b0fc32ad 100644 --- a/src/tr2/game/sound.h +++ b/src/tr2/game/sound.h @@ -9,6 +9,9 @@ void Sound_Init(void); void Sound_Shutdown(void); +void Sound_SetMasterVolume(int32_t volume); void Sound_UpdateEffects(void); void Sound_StopEffect(SOUND_EFFECT_ID sample_id); void Sound_EndScene(void); +int32_t Sound_GetMinVolume(void); +int32_t Sound_GetMaxVolume(void); diff --git a/src/tr2/game/spawn.c b/src/tr2/game/spawn.c index e8c161787..ff4531dd9 100644 --- a/src/tr2/game/spawn.c +++ b/src/tr2/game/spawn.c @@ -134,7 +134,7 @@ int16_t Spawn_GunShot( effect->counter = 3; effect->frame_num = 0; effect->object_id = O_GUN_FLASH; - effect->shade = SHADE_NEUTRAL; + effect->shade = HIGH_LIGHT; return effect_num; } diff --git a/src/tr2/game/spawn.h b/src/tr2/game/spawn.h index 04e263187..ea37e7cd9 100644 --- a/src/tr2/game/spawn.h +++ b/src/tr2/game/spawn.h @@ -2,8 +2,6 @@ #include "game/items.h" -#include - int16_t Spawn_FireStream( int32_t x, int32_t y, int32_t z, int16_t speed, int16_t y_rot, int16_t room_num); @@ -30,6 +28,10 @@ int16_t Spawn_Knife( int32_t x, int32_t y, int32_t z, int16_t speed, int16_t y_rot, int16_t room_num); +int16_t Spawn_Blood( + int32_t x, int32_t y, int32_t z, int16_t speed, int16_t y_rot, + int16_t room_num); + void Spawn_BloodBath( int32_t x, int32_t y, int32_t z, int16_t speed, int16_t y_rot, int16_t room_num, int32_t count); diff --git a/src/tr2/game/stats.c b/src/tr2/game/stats.c index 30a6ee15d..9dc320dd4 100644 --- a/src/tr2/game/stats.c +++ b/src/tr2/game/stats.c @@ -1,22 +1,13 @@ #include "game/stats.h" #include "game/clock.h" -#include "game/game.h" #include "game/game_flow.h" -#include "game/objects/vars.h" -#include "game/savegame.h" #include "global/vars.h" #include -#include #define USE_REAL_CLOCK 0 -static int32_t m_CachedItemCount = 0; -static int32_t m_LevelSecrets = 0; - -static bool M_SetSecretFlag(uint16_t *flags, GAME_OBJECT_ID obj_id); - #if USE_REAL_CLOCK static CLOCK_TIMER m_StartCounter = { .type = CLOCK_TYPE_REAL }; static int32_t m_StartTimer = 0; @@ -24,16 +15,13 @@ static int32_t m_StartTimer = 0; void Stats_StartTimer(void) { ClockTimer_Sync(&m_StartCounter); - const RESUME_INFO *const resume = - Savegame_GetCurrentInfo(Game_GetCurrentLevel()); - m_StartTimer = resume->stats.timer; + m_StartTimer = g_SaveGame.current_stats.timer; } void Stats_UpdateTimer(void) { const double elapsed = ClockTimer_PeekElapsed(&m_StartCounter) * LOGIC_FPS; - RESUME_INFO *const resume = Savegame_GetCurrentInfo(Game_GetCurrentLevel()); - resume->stats.timer = m_StartTimer + elapsed; + g_SaveGame.current_stats.timer = m_StartTimer + elapsed; } #else void Stats_StartTimer(void) @@ -42,146 +30,74 @@ void Stats_StartTimer(void) void Stats_UpdateTimer(void) { - const GF_LEVEL *const level = Game_GetCurrentLevel(); - if (level != nullptr) { - RESUME_INFO *const resume = Savegame_GetCurrentInfo(level); - resume->stats.timer++; - } + g_SaveGame.current_stats.timer++; } #endif -static bool M_SetSecretFlag(uint16_t *const flags, const GAME_OBJECT_ID obj_id) -{ - for (int32_t i = 0; i < 2; i++) { - const int32_t flag = 1 << ((obj_id - O_SECRET_1) + i * 3); - if ((*flags & flag) == 0) { - *flags |= flag; - return true; - } - } - - return false; -} - -FINAL_STATS Stats_ComputeFinalStats(const GF_LEVEL_TYPE level_type) +FINAL_STATS Stats_ComputeFinalStats(void) { FINAL_STATS result = {}; const GF_LEVEL_TABLE *const level_table = GF_GetLevelTable(GFLT_MAIN); for (int32_t i = 0; i < level_table->count; i++) { const GF_LEVEL *const level = &level_table->levels[i]; - if (level->type != level_type) { + if (level->type == GFL_GYM) { continue; } + result.timer += g_SaveGame.start[i].stats.timer; + result.ammo_used += g_SaveGame.start[i].stats.ammo_used; + result.ammo_hits += g_SaveGame.start[i].stats.ammo_hits; + result.kills += g_SaveGame.start[i].stats.kills; + result.distance += g_SaveGame.start[i].stats.distance; + result.medipacks += g_SaveGame.start[i].stats.medipacks; - const RESUME_INFO *const info = Savegame_GetCurrentInfo(level); - const LEVEL_STATS *const stats = &info->stats; - result.timer += stats->timer; - result.ammo_used += stats->ammo_used; - result.ammo_hits += stats->ammo_hits; - result.kill_count += stats->kill_count; - result.distance_travelled += stats->distance_travelled; - result.medipacks_used += stats->medipacks_used; - - for (int32_t j = 0; j < stats->max_secret_count; j++) { - if (stats->secret_flags & (1 << j)) { - result.found_secrets++; + // TODO: #170, consult GFE_NUM_SECRETS rather than hardcoding this + if (i < level_table->count - 2) { + for (int32_t j = 0; j < 3; j++) { + if (g_SaveGame.start[i].stats.secret_flags & (1 << j)) { + result.found_secrets++; + } + result.total_secrets++; } - result.total_secrets++; } } return result; } -void Stats_ObserveItemsLoad(void) +void Stats_Reset(void) { - m_CachedItemCount = Item_GetLevelCount(); + g_SaveGame.current_stats.timer = 0; + g_SaveGame.current_stats.kills = 0; + g_SaveGame.current_stats.distance = 0; + g_SaveGame.current_stats.ammo_hits = 0; + g_SaveGame.current_stats.ammo_used = 0; + g_SaveGame.current_stats.medipacks = 0; + g_SaveGame.current_stats.secret_flags = 0; } -void Stats_CalculateStats(void) +int32_t Stats_StoreAssaultTime(uint32_t time) { - m_LevelSecrets = 0; - uint16_t secret_flags = 0; + ASSAULT_STATS *const stats = &g_Assault; - for (int32_t i = 0; i < m_CachedItemCount; i++) { - const ITEM *const item = Item_Get(i); - if (item->object_id < 0 || item->object_id >= O_NUMBER_OF) { - LOG_ERROR("Bad Object number (%d) on Item %d", item->object_id, i); - continue; - } - - if (Object_IsType(item->object_id, g_SecretObjects) - && M_SetSecretFlag(&secret_flags, item->object_id)) { - m_LevelSecrets++; + int32_t insert_idx = -1; + for (int32_t i = 0; i < MAX_ASSAULT_TIMES; i++) { + if (stats->best_time[i] == 0 || time < stats->best_time[i]) { + insert_idx = i; + break; } } -} - -int32_t Stats_GetSecrets(void) -{ - return m_LevelSecrets; -} - -void Stats_MarkSecretCollected(const GAME_OBJECT_ID obj_id) -{ - RESUME_INFO *const resume = Savegame_GetCurrentInfo(Game_GetCurrentLevel()); - M_SetSecretFlag(&resume->stats.secret_flags, obj_id); -} - -bool Stats_CheckAllLevelSecretsCollected(void) -{ - const RESUME_INFO *const resume = - Savegame_GetCurrentInfo(Game_GetCurrentLevel()); - int32_t flags = resume->stats.secret_flags; - int32_t count = 0; - while (flags != 0) { - count += flags & 1; - flags >>= 1; + if (insert_idx == -1) { + return false; } - return count >= resume->stats.max_secret_count; -} + for (int32_t i = MAX_ASSAULT_TIMES - 1; i > insert_idx; i--) { + stats->best_finish[i] = stats->best_finish[i - 1]; + stats->best_time[i] = stats->best_time[i - 1]; + } -bool Stats_CheckAllSecretsCollected(GF_LEVEL_TYPE level_type) -{ - const FINAL_STATS stats = Stats_ComputeFinalStats(level_type); - return stats.found_secrets >= stats.total_secrets; -} - -void Stats_AddKill(void) -{ - RESUME_INFO *const current_info = - Savegame_GetCurrentInfo(Game_GetCurrentLevel()); - current_info->stats.kill_count++; -} - -void Stats_AddAmmoHits(void) -{ - RESUME_INFO *const current_info = - Savegame_GetCurrentInfo(Game_GetCurrentLevel()); - current_info->stats.ammo_hits++; -} - -void Stats_AddAmmoUsed(void) -{ - RESUME_INFO *const current_info = - Savegame_GetCurrentInfo(Game_GetCurrentLevel()); - current_info->stats.ammo_used++; -} - -void Stats_AddMedipacksUsed(const double medipack_value) -{ - RESUME_INFO *const current_info = - Savegame_GetCurrentInfo(Game_GetCurrentLevel()); - current_info->stats.medipacks_used += medipack_value; -} - -void Stats_AddDistanceTravelled(const XYZ_32 pos, const XYZ_32 last_pos) -{ - RESUME_INFO *const current_info = - Savegame_GetCurrentInfo(Game_GetCurrentLevel()); - current_info->stats.distance_travelled += Math_Sqrt( - SQUARE(pos.z - last_pos.z) + SQUARE(pos.y - last_pos.y) - + SQUARE(pos.x - last_pos.x)); + stats->finish_count++; + stats->best_time[insert_idx] = time; + stats->best_finish[insert_idx] = stats->finish_count; + return true; } diff --git a/src/tr2/game/stats.h b/src/tr2/game/stats.h index 735801ac2..34008c39e 100644 --- a/src/tr2/game/stats.h +++ b/src/tr2/game/stats.h @@ -2,19 +2,8 @@ #include "global/types.h" -#include -#include - +void Stats_StartTimer(void); void Stats_UpdateTimer(void); -void Stats_CalculateStats(void); -int32_t Stats_GetSecrets(void); -void Stats_MarkSecretCollected(GAME_OBJECT_ID obj_id); -bool Stats_CheckAllLevelSecretsCollected(void); -FINAL_STATS Stats_ComputeFinalStats(GF_LEVEL_TYPE level_type); -bool Stats_CheckAllSecretsCollected(GF_LEVEL_TYPE level_type); - -void Stats_AddKill(void); -void Stats_AddAmmoHits(void); -void Stats_AddAmmoUsed(void); -void Stats_AddMedipacksUsed(double medipack_value); -void Stats_AddDistanceTravelled(XYZ_32 pos, XYZ_32 last_pos); +void Stats_Reset(void); +FINAL_STATS Stats_ComputeFinalStats(void); +int32_t Stats_StoreAssaultTime(uint32_t time); diff --git a/src/tr2/game/text.c b/src/tr2/game/text.c index 7b7dff238..7d7d57efd 100644 --- a/src/tr2/game/text.c +++ b/src/tr2/game/text.c @@ -3,9 +3,9 @@ #include "decomp/decomp.h" #include "game/clock.h" #include "game/output.h" +#include "game/scaler.h" #include "global/vars.h" -#include #include static int32_t M_Scale(const int32_t value); @@ -15,6 +15,38 @@ static int32_t M_Scale(const int32_t value) return Scaler_Calc(value, SCALER_TARGET_TEXT); } +void Text_DrawBorder( + const int32_t x, const int32_t y, const int32_t z, const int32_t width, + const int32_t height) +{ + const int32_t mesh_idx = Object_Get(O_TEXT_BOX)->mesh_idx; + + const int32_t offset = 4; + const int32_t x0 = x + offset; + const int32_t y0 = y + offset; + const int32_t x1 = x0 + width - offset * 2; + const int32_t y1 = y0 + height - offset * 2; + const int32_t scale_h = TEXT_BASE_SCALE; + const int32_t scale_v = TEXT_BASE_SCALE; + + Output_DrawScreenSprite( + x0, y0, z, scale_h, scale_v, mesh_idx + 0, 0x1000, 0); + Output_DrawScreenSprite( + x1, y0, z, scale_h, scale_v, mesh_idx + 1, 0x1000, 0); + Output_DrawScreenSprite( + x1, y1, z, scale_h, scale_v, mesh_idx + 2, 0x1000, 0); + Output_DrawScreenSprite( + x0, y1, z, scale_h, scale_v, mesh_idx + 3, 0x1000, 0); + + int32_t w = (width - offset * 2) * TEXT_BASE_SCALE / 8; + int32_t h = (height - offset * 2) * TEXT_BASE_SCALE / 8; + + Output_DrawScreenSprite(x0, y0, z, w, scale_v, mesh_idx + 4, 0x1000, 0); + Output_DrawScreenSprite(x1, y0, z, scale_h, h, mesh_idx + 5, 0x1000, 0); + Output_DrawScreenSprite(x0, y1, z, w, scale_v, mesh_idx + 6, 0x1000, 0); + Output_DrawScreenSprite(x0, y0, z, scale_h, h, mesh_idx + 7, 0x1000, 0); +} + void Text_DrawText(TEXTSTRING *const text) { if (text->flags.drawn) { @@ -100,8 +132,7 @@ void Text_DrawText(TEXTSTRING *const text) const float sprite_scale = MIN(sprite_scale_h, sprite_scale_v); Output_DrawScreenSprite( x + M_Scale(10), y, z, M_Scale(glyph->width * sprite_scale), - M_Scale(glyph->width * sprite_scale), sprite_idx, SHADE_NEUTRAL, - 0); + M_Scale(glyph->width * sprite_scale), sprite_idx, 4096, 0); x += glyph->width * scale_h / TEXT_BASE_SCALE; goto loop_end; } @@ -117,7 +148,7 @@ void Text_DrawText(TEXTSTRING *const text) Output_DrawScreenSprite( cx, cy, 0, scale_h, scale_v, - obj->mesh_idx + glyph->combine_with.mesh_idx, SHADE_NEUTRAL, 0); + obj->mesh_idx + glyph->combine_with.mesh_idx, 4096, 0); } if (x >= 0 && x < g_PhdWinWidth && y >= 0 && y < g_PhdWinHeight) { @@ -126,7 +157,7 @@ void Text_DrawText(TEXTSTRING *const text) } Output_DrawScreenSprite( x, y, z, scale_h, scale_v, obj->mesh_idx + glyph->mesh_idx, - SHADE_NEUTRAL, 0); + 4096, 0); } if (glyph->role != GLYPH_COMBINING) { @@ -155,14 +186,13 @@ void Text_DrawText(TEXTSTRING *const text) } if (text->flags.background) { - Output_DrawTextBackground( - UI_STYLE_PC, box_x, box_y, box_w, box_h, - z + text->background.offset.z, TS_REQUESTED); + Output_DrawScreenFBox( + box_x, box_y, z + text->background.offset.z, box_w, box_h, 0, + nullptr, 0); } if (text->flags.outline) { - Output_DrawTextOutline( - UI_STYLE_PC, box_x, box_y, box_w, box_h, z, TS_REQUESTED); + Text_DrawBorder(box_x, box_y, z, box_w, box_h); } } diff --git a/src/tr2/game/text.h b/src/tr2/game/text.h index 47addce84..05b8687d8 100644 --- a/src/tr2/game/text.h +++ b/src/tr2/game/text.h @@ -6,4 +6,6 @@ #define TEXT_HEIGHT 15 +void Text_DrawBorder( + int32_t x, int32_t y, int32_t z, int32_t width, int32_t height); void Text_DrawText(TEXTSTRING *text); diff --git a/src/tr2/game/ui/common.c b/src/tr2/game/ui/common.c new file mode 100644 index 000000000..55b7f2ddf --- /dev/null +++ b/src/tr2/game/ui/common.c @@ -0,0 +1,35 @@ +#include "game/scaler.h" +#include "global/vars.h" + +#include +#include + +#include + +int32_t UI_GetCanvasWidth(void) +{ + return Scaler_CalcInverse(g_PhdWinWidth, SCALER_TARGET_TEXT); +} + +int32_t UI_GetCanvasHeight(void) +{ + return Scaler_CalcInverse(g_PhdWinHeight, SCALER_TARGET_TEXT); +} + +UI_INPUT UI_TranslateInput(uint32_t system_keycode) +{ + // clang-format off + switch (system_keycode) { + case SDLK_UP: return UI_KEY_UP; + case SDLK_DOWN: return UI_KEY_DOWN; + case SDLK_LEFT: return UI_KEY_LEFT; + case SDLK_RIGHT: return UI_KEY_RIGHT; + case SDLK_HOME: return UI_KEY_HOME; + case SDLK_END: return UI_KEY_END; + case SDLK_BACKSPACE: return UI_KEY_BACK; + case SDLK_RETURN: return UI_KEY_RETURN; + case SDLK_ESCAPE: return UI_KEY_ESCAPE; + } + // clang-format on + return -1; +} diff --git a/src/tr2/game/ui/controllers/controls.c b/src/tr2/game/ui/controllers/controls.c new file mode 100644 index 000000000..6012394c4 --- /dev/null +++ b/src/tr2/game/ui/controllers/controls.c @@ -0,0 +1,285 @@ +#include "game/ui/controllers/controls.h" + +#include "game/input.h" +#include "game/shell.h" +#include "global/vars.h" + +#include +#include +#include +#include + +static const INPUT_ROLE m_LeftRoles[] = { + // clang-format off + INPUT_ROLE_UP, + INPUT_ROLE_DOWN, + INPUT_ROLE_LEFT, + INPUT_ROLE_RIGHT, + INPUT_ROLE_STEP_L, + INPUT_ROLE_STEP_R, + INPUT_ROLE_SLOW, + INPUT_ROLE_ENTER_CONSOLE, + INPUT_ROLE_PAUSE, + INPUT_ROLE_TOGGLE_PHOTO_MODE, + INPUT_ROLE_TOGGLE_UI, + // INPUT_ROLE_CAMERA_RESET, // same as look, no need to configure + INPUT_ROLE_CAMERA_UP, + INPUT_ROLE_CAMERA_DOWN, + INPUT_ROLE_CAMERA_LEFT, + INPUT_ROLE_CAMERA_RIGHT, + INPUT_ROLE_CAMERA_FORWARD, + INPUT_ROLE_CAMERA_BACK, + (INPUT_ROLE)-1, + // clang-format on +}; + +static const INPUT_ROLE m_RightRoles_CheatsOff[] = { + // clang-format off + INPUT_ROLE_JUMP, + INPUT_ROLE_ACTION, + INPUT_ROLE_DRAW, + INPUT_ROLE_USE_FLARE, + INPUT_ROLE_LOOK, + INPUT_ROLE_ROLL, + INPUT_ROLE_OPTION, + (INPUT_ROLE)-1, + // clang-format on +}; + +static const INPUT_ROLE m_RightRoles_CheatsOn[] = { + // clang-format off + INPUT_ROLE_JUMP, + INPUT_ROLE_ACTION, + INPUT_ROLE_DRAW, + INPUT_ROLE_USE_FLARE, + INPUT_ROLE_LOOK, + INPUT_ROLE_ROLL, + INPUT_ROLE_OPTION, + INPUT_ROLE_FLY_CHEAT, + INPUT_ROLE_ITEM_CHEAT, + INPUT_ROLE_LEVEL_SKIP_CHEAT, + INPUT_ROLE_TURBO_CHEAT, + (INPUT_ROLE)-1, + // clang-format on +}; + +static INPUT_ROLE M_GetInputRole(int32_t col, int32_t row); +static void M_CycleLayout(UI_CONTROLS_CONTROLLER *controller, int32_t dir); +static bool M_NavigateLayout(UI_CONTROLS_CONTROLLER *controller); +static bool M_NavigateInputs(UI_CONTROLS_CONTROLLER *controller); +static bool M_NavigateInputsDebounce(UI_CONTROLS_CONTROLLER *controller); +static bool M_Listen(UI_CONTROLS_CONTROLLER *controller); +static bool M_ListenDebounce(UI_CONTROLS_CONTROLLER *controller); + +static INPUT_ROLE M_GetInputRole(const int32_t col, const int32_t row) +{ + if (col == 0) { + return m_LeftRoles[row]; + } else if (g_Config.gameplay.enable_cheats) { + return m_RightRoles_CheatsOn[row]; + } else { + return m_RightRoles_CheatsOff[row]; + } +} + +static void M_CycleLayout( + UI_CONTROLS_CONTROLLER *const controller, const int32_t dir) +{ + controller->active_layout += dir; + controller->active_layout += INPUT_LAYOUT_NUMBER_OF; + controller->active_layout %= INPUT_LAYOUT_NUMBER_OF; + + const EVENT event = { + .name = "layout_change", + .sender = nullptr, + .data = nullptr, + }; + EventManager_Fire(controller->events, &event); +} + +static bool M_NavigateBackend(UI_CONTROLS_CONTROLLER *const controller) +{ + if (g_InputDB.menu_down && controller->backend == INPUT_BACKEND_KEYBOARD) { + controller->backend = INPUT_BACKEND_CONTROLLER; + return true; + } + + if (g_InputDB.menu_up && controller->backend == INPUT_BACKEND_CONTROLLER) { + controller->backend = INPUT_BACKEND_KEYBOARD; + return true; + } + + if (g_InputDB.menu_back) { + controller->state = UI_CONTROLS_STATE_EXIT; + return true; + } + + if (g_InputDB.menu_confirm) { + controller->state = UI_CONTROLS_STATE_NAVIGATE_LAYOUT; + return true; + } + + return false; +} + +static bool M_NavigateLayout(UI_CONTROLS_CONTROLLER *const controller) +{ + if (g_InputDB.menu_confirm) { + controller->state = UI_CONTROLS_STATE_EXIT; + } else if (g_InputDB.menu_back) { + controller->state = UI_CONTROLS_STATE_NAVIGATE_BACKEND; + } else if (g_InputDB.menu_left) { + M_CycleLayout(controller, -1); + } else if (g_InputDB.menu_right) { + M_CycleLayout(controller, 1); + } else if (g_InputDB.menu_down && controller->active_layout != 0) { + controller->state = UI_CONTROLS_STATE_NAVIGATE_INPUTS; + controller->active_col = 0; + controller->active_row = 0; + } else if (g_InputDB.menu_up && controller->active_layout != 0) { + controller->state = UI_CONTROLS_STATE_NAVIGATE_INPUTS; + controller->active_col = 1; + controller->active_row = UI_ControlsController_GetInputRoleCount(1) - 1; + } else { + return false; + } + controller->active_role = + M_GetInputRole(controller->active_col, controller->active_row); + return true; +} + +static bool M_NavigateInputs(UI_CONTROLS_CONTROLLER *const controller) +{ + if (g_InputDB.menu_back) { + controller->state = UI_CONTROLS_STATE_NAVIGATE_BACKEND; + } else if (g_InputDB.menu_left || g_InputDB.menu_right) { + controller->active_col ^= 1; + CLAMP( + controller->active_row, 0, + UI_ControlsController_GetInputRoleCount(controller->active_col) + - 1); + } else if (g_InputDB.menu_up) { + controller->active_row--; + if (controller->active_row < 0) { + if (controller->active_col == 0) { + controller->state = UI_CONTROLS_STATE_NAVIGATE_LAYOUT; + } else { + controller->active_col = 0; + controller->active_row = + UI_ControlsController_GetInputRoleCount(0) - 1; + } + } + } else if (g_InputDB.menu_down) { + controller->active_row++; + if (controller->active_row >= UI_ControlsController_GetInputRoleCount( + controller->active_col)) { + if (controller->active_col == 0) { + controller->active_col = 1; + controller->active_row = 0; + } else { + controller->state = UI_CONTROLS_STATE_NAVIGATE_LAYOUT; + } + } + } else if (g_InputDB.menu_confirm) { + controller->state = UI_CONTROLS_STATE_NAVIGATE_INPUTS_DEBOUNCE; + } else { + return false; + } + controller->active_role = + M_GetInputRole(controller->active_col, controller->active_row); + return true; +} + +static bool M_NavigateInputsDebounce(UI_CONTROLS_CONTROLLER *const controller) +{ + Shell_ProcessEvents(); + Input_Update(); + if (g_Input.any) { + return false; + } + Input_EnterListenMode(); + controller->state = UI_CONTROLS_STATE_LISTEN; + return true; +} + +static bool M_Listen(UI_CONTROLS_CONTROLLER *const controller) +{ + if (!Input_ReadAndAssignRole( + controller->backend, controller->active_layout, + controller->active_role)) { + return false; + } + + Input_ExitListenMode(); + + const EVENT event = { + .name = "key_change", + .sender = nullptr, + .data = nullptr, + }; + EventManager_Fire(controller->events, &event); + + controller->state = UI_CONTROLS_STATE_LISTEN_DEBOUNCE; + return true; +} + +static bool M_ListenDebounce(UI_CONTROLS_CONTROLLER *const controller) +{ + if (g_Input.any) { + return false; + } + + controller->state = UI_CONTROLS_STATE_NAVIGATE_INPUTS; + return true; +} + +void UI_ControlsController_Init(UI_CONTROLS_CONTROLLER *const controller) +{ + ASSERT(controller->events == nullptr); + controller->backend = INPUT_BACKEND_KEYBOARD; + controller->state = UI_CONTROLS_STATE_NAVIGATE_BACKEND; + + controller->events = EventManager_Create(); +} + +void UI_ControlsController_Shutdown(UI_CONTROLS_CONTROLLER *const controller) +{ + EventManager_Free(controller->events); + controller->events = nullptr; +} + +bool UI_ControlsController_Control(UI_CONTROLS_CONTROLLER *const controller) +{ + switch (controller->state) { + case UI_CONTROLS_STATE_NAVIGATE_BACKEND: + return M_NavigateBackend(controller); + case UI_CONTROLS_STATE_NAVIGATE_LAYOUT: + return M_NavigateLayout(controller); + case UI_CONTROLS_STATE_NAVIGATE_INPUTS: + return M_NavigateInputs(controller); + case UI_CONTROLS_STATE_NAVIGATE_INPUTS_DEBOUNCE: + return M_NavigateInputsDebounce(controller); + case UI_CONTROLS_STATE_LISTEN: + return M_Listen(controller); + case UI_CONTROLS_STATE_LISTEN_DEBOUNCE: + return M_ListenDebounce(controller); + default: + return false; + } + return false; +} + +INPUT_ROLE UI_ControlsController_GetInputRole( + const int32_t col, const int32_t row) +{ + return M_GetInputRole(col, row); +} + +int32_t UI_ControlsController_GetInputRoleCount(const int32_t col) +{ + int32_t row = 0; + while (M_GetInputRole(col, row) != (INPUT_ROLE)-1) { + row++; + } + return row; +} diff --git a/src/tr2/game/ui/controllers/controls.h b/src/tr2/game/ui/controllers/controls.h new file mode 100644 index 000000000..60d4985d2 --- /dev/null +++ b/src/tr2/game/ui/controllers/controls.h @@ -0,0 +1,33 @@ +#pragma once + +#include "game/input.h" + +#include + +typedef enum { + UI_CONTROLS_STATE_NAVIGATE_BACKEND, + UI_CONTROLS_STATE_NAVIGATE_LAYOUT, + UI_CONTROLS_STATE_NAVIGATE_INPUTS, + UI_CONTROLS_STATE_NAVIGATE_INPUTS_DEBOUNCE, + UI_CONTROLS_STATE_LISTEN, + UI_CONTROLS_STATE_LISTEN_DEBOUNCE, + UI_CONTROLS_STATE_EXIT, +} UI_CONTROLS_STATE; + +typedef struct { + INPUT_BACKEND backend; + UI_CONTROLS_STATE state; + int32_t active_layout; + INPUT_ROLE active_role; + int32_t active_col; + int32_t active_row; + + EVENT_MANAGER *events; +} UI_CONTROLS_CONTROLLER; + +void UI_ControlsController_Init(UI_CONTROLS_CONTROLLER *controller); +void UI_ControlsController_Shutdown(UI_CONTROLS_CONTROLLER *controller); +bool UI_ControlsController_Control(UI_CONTROLS_CONTROLLER *controller); + +INPUT_ROLE UI_ControlsController_GetInputRole(int32_t col, int32_t row); +int32_t UI_ControlsController_GetInputRoleCount(int32_t col); diff --git a/src/tr2/game/ui/dialogs/graphic_settings.c b/src/tr2/game/ui/dialogs/graphic_settings.c deleted file mode 100644 index 87aa8c043..000000000 --- a/src/tr2/game/ui/dialogs/graphic_settings.c +++ /dev/null @@ -1,548 +0,0 @@ -#include "game/ui/dialogs/graphic_settings.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#define M_ARROW_SPACING 2.0f - -typedef struct { - CONFIG_OPTION_TYPE option_type; - GAME_STRING_ID label_id; - void *target; - int32_t min_value; - int32_t max_value; - int32_t delta_slow; - int32_t delta_fast; - const void *misc; -} M_OPTION; - -typedef struct { - int32_t value; - GAME_STRING_ID name; -} M_ENUM_ENTRY; - -typedef struct { - const M_ENUM_ENTRY *entry; - int32_t position; - int32_t count; -} M_ENUM_LOOKUP; - -static const M_ENUM_ENTRY m_TextureFilterOptions[] = { - // clang-format off - { GFX_TF_NN, GS_ID(MISC_OFF), }, - { GFX_TF_BILINEAR, GS_ID(DETAIL_BILINEAR), }, - { -1, nullptr, }, - // clang-format on -}; - -static const M_ENUM_ENTRY m_LightingContrastOptions[] = { - // clang-format off - { LIGHTING_CONTRAST_LOW, GS_ID(DETAIL_LIGHTING_CONTRAST_LOW), }, - { LIGHTING_CONTRAST_MEDIUM, GS_ID(DETAIL_LIGHTING_CONTRAST_MEDIUM), }, - { LIGHTING_CONTRAST_HIGH, GS_ID(DETAIL_LIGHTING_CONTRAST_HIGH), }, - { -1, nullptr, }, - // clang-format on -}; - -static const M_ENUM_ENTRY m_RenderModeOptions[] = { - // clang-format off - { RM_SOFTWARE, GS_ID(DETAIL_RENDER_MODE_SOFTWARE), }, - { RM_HARDWARE, GS_ID(DETAIL_RENDER_MODE_HARDWARE), }, - { -1, nullptr, }, - // clang-format on -}; - -static const M_ENUM_ENTRY m_AspectModeOptions[] = { - // clang-format off - { AM_4_3, GS_ID(DETAIL_ASPECT_MODE_4_3), }, - { AM_16_9, GS_ID(DETAIL_ASPECT_MODE_16_9), }, - { AM_ANY, GS_ID(DETAIL_ASPECT_MODE_ANY), }, - { -1, nullptr, }, - // clang-format on -}; - -static const M_OPTION m_Options[] = { - { - .option_type = COT_INT32, - .label_id = GS_ID(DETAIL_FPS), - .target = &g_Config.rendering.fps, - .min_value = 30, - .max_value = 60, - .delta_slow = 30, - .delta_fast = 30, - }, - - { - .option_type = COT_INT32, - .label_id = GS_ID(DETAIL_FOG_START), - .target = &g_Config.visuals.fog_start, - .min_value = 1, - .max_value = 100, - }, - - { - .option_type = COT_INT32, - .label_id = GS_ID(DETAIL_FOG_END), - .target = &g_Config.visuals.fog_end, - .min_value = 1, - .max_value = 100, - }, - - { - .option_type = COT_RGB888, - .label_id = GS_ID(DETAIL_WATER_COLOR_R), - .target = &g_Config.visuals.water_color, - .min_value = 0, - .max_value = 255, - .delta_slow = 1, - .delta_fast = 10, - .misc = (void *)(intptr_t)0, - }, - - { - .option_type = COT_RGB888, - .label_id = GS_ID(DETAIL_WATER_COLOR_G), - .target = &g_Config.visuals.water_color, - .min_value = 0, - .max_value = 255, - .delta_slow = 1, - .delta_fast = 10, - .misc = (void *)(intptr_t)1, - }, - - { - .option_type = COT_RGB888, - .label_id = GS_ID(DETAIL_WATER_COLOR_B), - .target = &g_Config.visuals.water_color, - .min_value = 0, - .max_value = 255, - .delta_slow = 1, - .delta_fast = 10, - .misc = (void *)(intptr_t)2, - }, - - { - .option_type = COT_INT32, - .label_id = GS_ID(DETAIL_FOV), - .target = &g_Config.visuals.fov, - .min_value = 30, - .max_value = 150, - .delta_slow = 1, - .delta_fast = 10, - }, - - { - .option_type = COT_BOOL, - .label_id = GS_ID(DETAIL_USE_PSX_FOV), - .target = &g_Config.visuals.use_psx_fov, - }, - - { - .option_type = COT_ENUM, - .label_id = GS_ID(DETAIL_TEXTURE_FILTER), - .target = &g_Config.rendering.texture_filter, - .delta_slow = 1, - .delta_fast = 1, - .misc = m_TextureFilterOptions, - }, - - { - .option_type = COT_BOOL, - .label_id = GS_ID(DETAIL_TRAPEZOID_FILTER), - .target = &g_Config.rendering.enable_trapezoid_filter, - }, - - { - .option_type = COT_BOOL, - .label_id = GS_ID(DETAIL_DEPTH_BUFFER), - .target = &g_Config.rendering.enable_zbuffer, - }, - - { - .option_type = COT_ENUM, - .label_id = GS_ID(DETAIL_LIGHTING_CONTRAST), - .target = &g_Config.rendering.lighting_contrast, - .delta_slow = 1, - .delta_fast = 1, - .misc = m_LightingContrastOptions, - }, - - { - .option_type = COT_ENUM, - .label_id = GS_ID(DETAIL_RENDER_MODE), - .target = &g_Config.rendering.render_mode, - .delta_slow = 1, - .delta_fast = 1, - .misc = m_RenderModeOptions, - }, - - { - .option_type = COT_ENUM, - .label_id = GS_ID(DETAIL_ASPECT_MODE), - .target = &g_Config.rendering.aspect_mode, - .delta_slow = 1, - .delta_fast = 1, - .misc = m_AspectModeOptions, - }, - - { - .option_type = COT_DOUBLE, - .label_id = GS_ID(DETAIL_UI_TEXT_SCALE), - .target = &g_Config.ui.text_scale, - .min_value = 50, - .max_value = 200, - .delta_slow = 10, - .delta_fast = 10, - }, - - { - .option_type = COT_DOUBLE, - .label_id = GS_ID(DETAIL_UI_BAR_SCALE), - .target = &g_Config.ui.bar_scale, - .min_value = 50, - .max_value = 200, - .delta_slow = 10, - .delta_fast = 10, - }, - - { - .option_type = COT_BOOL, - .label_id = GS_ID(DETAIL_UI_SCROLL_WRAPAROUND), - .target = &g_Config.ui.enable_wraparound, - }, - - { - .option_type = COT_INT32, - .label_id = GS_ID(DETAIL_SCALER), - .target = &g_Config.rendering.scaler, - .min_value = 1, - .max_value = 4, - .delta_slow = 1, - .delta_fast = 1, - }, - - { - .option_type = COT_FLOAT, - .label_id = GS_ID(DETAIL_SIZER), - .target = &g_Config.rendering.sizer, - .min_value = 40, - .max_value = 200, - .delta_slow = 10, - .delta_fast = 10, - }, - - { - .target = nullptr, - }, -}; - -static int32_t M_GetVisibleRows(void); -static uint8_t *M_GetColorComponent(const M_OPTION *option); -static M_ENUM_LOOKUP M_GetEnumEntry(const M_OPTION *option); -static char *M_FormatRowValue(int32_t row_idx); -static bool M_CanChangeValue(int32_t row_idx, int32_t dir); -static bool M_RequestChangeValue(int32_t row_idx, int32_t dir); -static float M_GetValueWidth(const UI_GRAPHIC_SETTINGS_STATE *s); - -static int32_t M_GetVisibleRows(void) -{ - const int32_t res_h = - Scaler_CalcInverse(Viewport_GetHeight(), SCALER_TARGET_TEXT); - if (res_h <= 240) { - return 5; - } else if (res_h <= 384) { - return 7; - } else if (res_h < 480) { - return 10; - } else { - return 12; - } -} - -static uint8_t *M_GetColorComponent(const M_OPTION *const option) -{ - RGB_888 *const color = option->target; - switch ((int32_t)(intptr_t)option->misc) { - case 0: - return &color->r; - case 1: - return &color->g; - case 2: - return &color->b; - } - ASSERT_FAIL(); - return nullptr; -} - -static M_ENUM_LOOKUP M_GetEnumEntry(const M_OPTION *const option) -{ - M_ENUM_LOOKUP result = { - .entry = nullptr, - .position = -1, - .count = 0, - }; - int32_t current_pos = 0; - const M_ENUM_ENTRY *entry = &((M_ENUM_ENTRY *)option->misc)[0]; - while (entry->value != -1) { - if (entry->value == *(int32_t *)option->target) { - result.entry = entry; - result.position = current_pos; - } - entry++; - current_pos++; - result.count++; - } - return result; -} - -static char *M_FormatRowValue(const int32_t row_idx) -{ - const M_OPTION *const option = &m_Options[row_idx]; - switch (option->option_type) { - case COT_BOOL: - return String_Format( - "%s", *(bool *)option->target ? GS(MISC_ON) : GS(MISC_OFF)); - case COT_INT32: - return String_Format( - GS(DETAIL_INTEGER_FMT), *(int32_t *)option->target); - case COT_DOUBLE: - return String_Format(GS(DETAIL_FLOAT_FMT), *(double *)option->target); - case COT_FLOAT: - return String_Format(GS(DETAIL_FLOAT_FMT), *(float *)option->target); - case COT_RGB888: { - const uint8_t *const component = M_GetColorComponent(option); - return String_Format("%d", *component); - } - case COT_ENUM: { - const M_ENUM_LOOKUP enum_lookup = M_GetEnumEntry(option); - ASSERT(enum_lookup.entry != nullptr); - return (char *)GameString_Get(enum_lookup.entry->name); - } - default: - break; - } - return nullptr; -} - -static bool M_CanChangeValue(const int32_t row_idx, const int32_t dir) -{ - const M_OPTION *const option = &m_Options[row_idx]; - switch (option->option_type) { - case COT_BOOL: - return true; - case COT_INT32: - if (dir < 0) { - return *(int32_t *)option->target > option->min_value; - } else if (dir > 0) { - return *(int32_t *)option->target < option->max_value; - } - break; - case COT_DOUBLE: - if (dir < 0) { - return *(double *)option->target > (double)option->min_value / 100; - } else if (dir > 0) { - return *(double *)option->target < (double)option->max_value / 100; - } - break; - case COT_FLOAT: - if (dir < 0) { - return *(float *)option->target > (float)option->min_value / 100; - } else if (dir > 0) { - return *(float *)option->target < (float)option->max_value / 100; - } - break; - case COT_RGB888: { - const uint8_t *const component = M_GetColorComponent(option); - if (dir < 0) { - return *component > option->min_value; - } else if (dir > 0) { - return *component < option->max_value; - } - break; - } - case COT_ENUM: { - const M_ENUM_LOOKUP enum_lookup = M_GetEnumEntry(option); - ASSERT(enum_lookup.entry != nullptr); - if (dir < 0) { - return enum_lookup.position > 0; - } else if (dir > 0) { - return enum_lookup.position < enum_lookup.count - 1; - } - break; - } - default: - break; - } - return false; -} - -static bool M_RequestChangeValue(const int32_t row_idx, const int32_t dir) -{ - if (!M_CanChangeValue(row_idx, dir)) { - return false; - } - - const M_OPTION *const option = &m_Options[row_idx]; - int32_t delta = g_Input.slow ? option->delta_slow : option->delta_fast; - if (delta == 0) { - delta = 1; - } - delta *= dir; - - switch (option->option_type) { - case COT_BOOL: - *(bool *)option->target = !*(bool *)option->target; - break; - case COT_INT32: - *(int32_t *)option->target += delta; - break; - case COT_DOUBLE: - *(double *)option->target = - (round(*(double *)option->target * 10) + (delta / 10.0f)) / 10.0f; - break; - case COT_FLOAT: - *(float *)option->target = - (round(*(float *)option->target * 10) + (delta / 10.0f)) / 10.0f; - break; - case COT_RGB888: { - uint8_t *const component = M_GetColorComponent(option); - int32_t component_i = *component; - component_i += delta; - CLAMP(component_i, 0, 255); - *component = component_i; - break; - } - case COT_ENUM: { - const M_ENUM_LOOKUP enum_lookup = M_GetEnumEntry(option); - const M_ENUM_ENTRY *const next_entry = - &((M_ENUM_ENTRY *)option->misc)[enum_lookup.position + delta]; - *(int32_t *)option->target = next_entry->value; - break; - } - default: - return false; - } - Config_Write(); - return true; -} - -static float M_GetValueWidth(const UI_GRAPHIC_SETTINGS_STATE *const s) -{ - // Measure the maximum width of the value label to prevent the entire - // dialog from changing its size as the player changes the sound levels. - float result = -1.0f; - for (int32_t i = 0; i < s->req.max_rows; i++) { - const char *const value = M_FormatRowValue(i); - float value_w; - UI_Label_Measure(value, &value_w, nullptr); - result = MAX(result, value_w); - } - float arrow_w; - UI_Label_Measure("\\{button left}", &arrow_w, nullptr); - result += arrow_w; - UI_Label_Measure("\\{button right}", &arrow_w, nullptr); - result += arrow_w; - result += M_ARROW_SPACING * 2; - return result; -} - -void UI_GraphicSettings_Init(UI_GRAPHIC_SETTINGS_STATE *const s) -{ - int32_t row_count = 0; - for (int32_t i = 0; m_Options[i].target != nullptr; i++) { - row_count++; - } - UI_Requester_Init(&s->req, row_count, row_count, true); - s->req.row_pad = 2.0f; - s->req.row_spacing = 0.0f; - s->req.show_arrows = true; -} - -void UI_GraphicSettings_Free(UI_GRAPHIC_SETTINGS_STATE *const s) -{ - UI_Requester_Free(&s->req); -} - -bool UI_GraphicSettings_Control(UI_GRAPHIC_SETTINGS_STATE *const s) -{ - UI_Requester_SetVisibleRows(&s->req, M_GetVisibleRows()); - const int32_t choice = UI_Requester_Control(&s->req); - if (choice == UI_REQUESTER_CANCEL) { - return true; - } - const int32_t sel_row = UI_Requester_GetCurrentRow(&s->req); - if (g_InputDB.menu_left && sel_row >= 0) { - M_RequestChangeValue(sel_row, -1); - } else if (g_InputDB.menu_right && sel_row >= 0) { - M_RequestChangeValue(sel_row, +1); - } - return false; -} - -void UI_GraphicSettings(UI_GRAPHIC_SETTINGS_STATE *const s) -{ - const int32_t sel_row = UI_Requester_GetCurrentRow(&s->req); - UI_BeginModal(0.5f, 0.6f); - UI_BeginRequester(&s->req, GS(DETAIL_TITLE)); - - const float max_value_w = M_GetValueWidth(s) / g_Config.ui.text_scale; - - for (int32_t i = 0; i < s->req.max_rows; i++) { - if (!UI_Requester_IsRowVisible(&s->req, i)) { - UI_BeginResize(-1.0f, 0.0f); - } else { - UI_BeginResize(-1.0f, -1.0f); - } - - UI_BeginRequesterRow(&s->req, i); - UI_BeginStackEx((UI_STACK_SETTINGS) { - .orientation = UI_STACK_HORIZONTAL, - .align = { .h = UI_STACK_H_ALIGN_DISTRIBUTE }, - }); - UI_Label(GameString_Get(m_Options[i].label_id)); - UI_Spacer(20.0f, 0.0f); - - UI_BeginResize(max_value_w, -1.0f); - UI_BeginAnchor(1.0f, 0.5f); - UI_BeginStackEx((UI_STACK_SETTINGS) { - .orientation = UI_STACK_HORIZONTAL, - .align = { .h = UI_STACK_H_ALIGN_DISTRIBUTE }, - .spacing = { .h = M_ARROW_SPACING }, - }); - UI_BeginHide(i != sel_row || !M_CanChangeValue(i, -1)); - UI_Label("\\{button left}"); - UI_EndHide(); - - UI_Label(M_FormatRowValue(i)); - - UI_BeginHide(i != sel_row || !M_CanChangeValue(i, +1)); - UI_Label("\\{button right}"); - UI_EndHide(); - UI_EndStack(); - UI_EndAnchor(); - UI_EndResize(); - - UI_EndStack(); - UI_EndRequesterRow(&s->req, i); - UI_EndResize(); - } - UI_EndRequester(&s->req); - UI_EndModal(); -} diff --git a/src/tr2/game/ui/dialogs/graphic_settings.h b/src/tr2/game/ui/dialogs/graphic_settings.h deleted file mode 100644 index 6f7f053be..000000000 --- a/src/tr2/game/ui/dialogs/graphic_settings.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include -#include - -typedef struct { - UI_REQUESTER_STATE req; - float arrow_spacing; - float value_w; -} UI_GRAPHIC_SETTINGS_STATE; - -void UI_GraphicSettings_Init(UI_GRAPHIC_SETTINGS_STATE *s); -void UI_GraphicSettings_Free(UI_GRAPHIC_SETTINGS_STATE *s); -bool UI_GraphicSettings_Control(UI_GRAPHIC_SETTINGS_STATE *s); - -void UI_GraphicSettings(UI_GRAPHIC_SETTINGS_STATE *s); diff --git a/src/tr2/game/ui/dialogs/stats.c b/src/tr2/game/ui/dialogs/stats.c deleted file mode 100644 index 9a3c275e7..000000000 --- a/src/tr2/game/ui/dialogs/stats.c +++ /dev/null @@ -1,318 +0,0 @@ -#include "game/ui/dialogs/stats.h" - -#include "game/game_flow.h" -#include "game/game_string.h" -#include "game/savegame.h" -#include "game/stats.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -typedef enum { - M_ROW_GENERIC, - M_ROW_TIMER, - M_ROW_LEVEL_SECRETS, - M_ROW_ALL_SECRETS, - M_ROW_KILLS, - M_ROW_AMMO_USED, - M_ROW_AMMO_HITS, - M_ROW_MEDIPACKS, - M_ROW_DISTANCE_TRAVELLED, -} M_ROW_ROLE; - -static void M_FormatTime(char *out, int32_t total_frames); -static void M_FormatSecrets(char *out, const LEVEL_STATS *level_stats); -static void M_FormatDistance(char *const out, int32_t distance); -static void M_Row( - const UI_STATS_DIALOG_STATE *s, const char *key, const char *value); -static void M_RowFromRole( - const UI_STATS_DIALOG_STATE *s, M_ROW_ROLE role, const STATS_COMMON *stats); -static void M_LevelStatsRows(const UI_STATS_DIALOG_STATE *s); -static void M_FinalStatsRows(const UI_STATS_DIALOG_STATE *s); -static void M_AssaultCourseStatsRows(UI_STATS_DIALOG_STATE *s); -static const char *M_GetDialogTitle(const UI_STATS_DIALOG_STATE *s); -static void M_BeginDialog(const UI_STATS_DIALOG_STATE *s); -static void M_EndDialog(const UI_STATS_DIALOG_STATE *s); - -static void M_FormatTime(char *const out, const int32_t total_frames) -{ - const int32_t total_seconds = total_frames / LOGIC_FPS; - const int32_t hours = total_seconds / 3600; - const int32_t minutes = (total_seconds / 60) % 60; - const int32_t seconds = total_seconds % 60; - sprintf(out, "%02d:%02d:%02d", hours, minutes, seconds); -} - -static void M_FormatSecrets( - char *const out, const LEVEL_STATS *const level_stats) -{ - char *ptr = out; - int32_t num_secrets = 0; - for (int32_t i = 1; i >= 0; i--) { - for (int32_t j = 0; j < 3; j++) { - const int32_t flag = 1 << (j + i * 3); - if ((level_stats->secret_flags & flag) != 0) { - sprintf(ptr, "\\{secret %d}", j + 1); - num_secrets++; - } else { - strcpy(ptr, " "); - } - ptr += strlen(ptr); - } - } - - *ptr++ = '\0'; - if (num_secrets == 0) { - strcpy(out, GS(MISC_NONE)); - } -} - -static void M_FormatDistance(char *const out, int32_t distance) -{ - distance /= 445; - if (distance < 1000) { - sprintf(out, "%dm", distance); - } else { - sprintf(out, "%d.%02dkm", distance / 1000, (distance % 1000) / 10); - } -} - -static void M_Row( - const UI_STATS_DIALOG_STATE *const s, const char *const key, - const char *const value) -{ - UI_BeginStackEx((UI_STACK_SETTINGS) { - .orientation = UI_STACK_HORIZONTAL, - .spacing = { .h = TR_VERSION == 1 ? 30.0f : 90.0f }, - .align = { .h = UI_STACK_H_ALIGN_DISTRIBUTE }, - }); - UI_Label(key); - UI_Label(value); - UI_EndStack(); -} - -static void M_RowFromRole( - const UI_STATS_DIALOG_STATE *const s, const M_ROW_ROLE role, - const STATS_COMMON *const stats) -{ - char buf[64]; - - switch (role) { - case M_ROW_TIMER: { - M_FormatTime(buf, stats->timer); - M_Row(s, GS(STATS_TIME_TAKEN), buf); - break; - } - - case M_ROW_LEVEL_SECRETS: { - M_FormatSecrets(buf, (LEVEL_STATS *)stats); - M_Row(s, GS(STATS_SECRETS), buf); - break; - } - - case M_ROW_ALL_SECRETS: - sprintf( - buf, GS(STATS_DETAIL_FMT), ((FINAL_STATS *)stats)->found_secrets, - ((FINAL_STATS *)stats)->total_secrets); - M_Row(s, GS(STATS_SECRETS), buf); - break; - - case M_ROW_KILLS: - sprintf(buf, GS(STATS_BASIC_FMT), stats->kill_count); - M_Row(s, GS(STATS_KILLS), buf); - break; - - case M_ROW_AMMO_USED: - sprintf(buf, "%d", stats->ammo_used); - M_Row(s, GS(STATS_AMMO_USED), buf); - break; - - case M_ROW_AMMO_HITS: - sprintf(buf, "%d", stats->ammo_hits); - M_Row(s, GS(STATS_AMMO_HITS), buf); - break; - - case M_ROW_MEDIPACKS: - sprintf(buf, GS(DETAIL_FLOAT_FMT), stats->medipacks_used); - M_Row(s, GS(STATS_MEDIPACKS_USED), buf); - break; - - case M_ROW_DISTANCE_TRAVELLED: - M_FormatDistance(buf, stats->distance_travelled); - M_Row(s, GS(STATS_DISTANCE_TRAVELLED), buf); - break; - - default: - break; - } -} - -static void M_LevelStatsRows(const UI_STATS_DIALOG_STATE *const s) -{ - const GF_LEVEL *const current_level = - GF_GetLevel(GFLT_MAIN, s->args.level_num); - const RESUME_INFO *const current_info = - Savegame_GetCurrentInfo(current_level); - const STATS_COMMON *const stats = (STATS_COMMON *)¤t_info->stats; - - M_RowFromRole(s, M_ROW_TIMER, stats); - if (stats->max_secret_count != 0) { - M_RowFromRole(s, M_ROW_LEVEL_SECRETS, stats); - } - M_RowFromRole(s, M_ROW_KILLS, stats); - M_RowFromRole(s, M_ROW_AMMO_USED, stats); - M_RowFromRole(s, M_ROW_AMMO_HITS, stats); - M_RowFromRole(s, M_ROW_MEDIPACKS, stats); - M_RowFromRole(s, M_ROW_DISTANCE_TRAVELLED, stats); -} - -static void M_FinalStatsRows(const UI_STATS_DIALOG_STATE *const s) -{ - const GF_LEVEL_TYPE level_type = - GF_GetLevel(GFLT_MAIN, s->args.level_num)->type; - const FINAL_STATS final_stats = Stats_ComputeFinalStats(level_type); - const STATS_COMMON *const stats = (STATS_COMMON *)&final_stats; - M_RowFromRole(s, M_ROW_TIMER, stats); - M_RowFromRole(s, M_ROW_ALL_SECRETS, stats); - M_RowFromRole(s, M_ROW_KILLS, stats); - M_RowFromRole(s, M_ROW_AMMO_USED, stats); - M_RowFromRole(s, M_ROW_AMMO_HITS, stats); - M_RowFromRole(s, M_ROW_MEDIPACKS, stats); - M_RowFromRole(s, M_ROW_DISTANCE_TRAVELLED, stats); -} - -static void M_AssaultCourseStatsRows(UI_STATS_DIALOG_STATE *const s) -{ - const ASSAULT_STATS stats = Gym_GetAssaultStats(); - UI_BeginRequester(&s->assault_req, M_GetDialogTitle(s)); - // ensure minimum dialog width - UI_Spacer(290.0f, 0.0f); - if (stats.entries[0].time == 0) { - UI_BeginAnchor(0.5f, 0.5f); - UI_Label(GS(STATS_ASSAULT_NO_TIMES_SET)); - UI_EndAnchor(); - } else { - const int32_t first = UI_Requester_GetFirstRow(&s->assault_req); - const int32_t last = UI_Requester_GetLastRow(&s->assault_req); - for (int32_t i = first; i < last; i++) { - if (stats.entries[i].time == 0) { - break; - } - - char left_buf[32] = " "; - char right_buf[32] = " "; - sprintf( - left_buf, "%2d: %s %d", i + 1, GS(STATS_ASSAULT_FINISH), - stats.entries[i].attempt_num); - - const int32_t sec = stats.entries[i].time / FRAMES_PER_SECOND; - sprintf( - right_buf, "%02d:%02d.%-2d", sec / 60, sec % 60, - stats.entries[i].time % FRAMES_PER_SECOND - / (FRAMES_PER_SECOND / 10)); - - M_Row(s, left_buf, right_buf); - } - } - UI_EndRequester(&s->assault_req); -} - -static const char *M_GetDialogTitle(const UI_STATS_DIALOG_STATE *const s) -{ - switch (s->args.mode) { - case UI_STATS_DIALOG_MODE_LEVEL: - return GF_GetLevel(GFLT_MAIN, s->args.level_num)->title; - case UI_STATS_DIALOG_MODE_FINAL: { - const GF_LEVEL_TYPE level_type = - GF_GetLevel(GFLT_MAIN, s->args.level_num)->type; - const char *const title = level_type == GFL_BONUS - ? GS(STATS_BONUS_STATISTICS) - : GS(STATS_FINAL_STATISTICS); - return title; - } - case UI_STATS_DIALOG_MODE_ASSAULT_COURSE: - return GS(STATS_ASSAULT_TITLE); - } - return nullptr; -} - -static void M_BeginDialog(const UI_STATS_DIALOG_STATE *const s) -{ - const char *const title = M_GetDialogTitle(s); - if (s->args.style == UI_STATS_DIALOG_STYLE_BARE) { - UI_BeginStackEx((UI_STACK_SETTINGS) { - .orientation = UI_STACK_VERTICAL, - .spacing = { .v = 11.0f }, - .align = { .h = UI_STACK_H_ALIGN_CENTER }, - }); - if (title != nullptr) { - UI_Label(title); - } - } else { - UI_BeginWindow(); - if (title != nullptr) { - UI_WindowTitle(title); - } - UI_BeginWindowBody(); - UI_BeginStackEx((UI_STACK_SETTINGS) { - .orientation = UI_STACK_VERTICAL, - .spacing = { .v = 3.0f }, - .align = { .h = UI_STACK_H_ALIGN_SPAN }, - }); - } - // ensure minimum dialog width - UI_Spacer(290.0f, 0.0f); -} - -static void M_EndDialog(const UI_STATS_DIALOG_STATE *const s) -{ - if (s->args.style == UI_STATS_DIALOG_STYLE_BARE) { - UI_EndStack(); - } else { - UI_EndStack(); - UI_EndWindowBody(); - UI_EndWindow(); - } -} - -void UI_StatsDialog(UI_STATS_DIALOG_STATE *const s) -{ - // TODO: add support for the bare style by merging TR1 and TR2 stats dialog - // implementations. - ASSERT(s->args.style == UI_STATS_DIALOG_STYLE_BORDERED); - - UI_BeginModal(0.5f, 1.0f); - UI_BeginPad(40.f, 40.0f); - - switch (s->args.mode) { - case UI_STATS_DIALOG_MODE_LEVEL: - M_BeginDialog(s); - M_LevelStatsRows(s); - M_EndDialog(s); - break; - case UI_STATS_DIALOG_MODE_FINAL: - M_BeginDialog(s); - M_FinalStatsRows(s); - M_EndDialog(s); - break; - case UI_STATS_DIALOG_MODE_ASSAULT_COURSE: - M_AssaultCourseStatsRows(s); - break; - } - - UI_EndPad(); - UI_EndModal(); -} diff --git a/src/tr2/game/ui/dialogs/stats.h b/src/tr2/game/ui/dialogs/stats.h deleted file mode 100644 index 00c84b406..000000000 --- a/src/tr2/game/ui/dialogs/stats.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -#include diff --git a/src/tr2/game/ui/widgets/controls_backend_selector.c b/src/tr2/game/ui/widgets/controls_backend_selector.c new file mode 100644 index 000000000..7ad4b6e49 --- /dev/null +++ b/src/tr2/game/ui/widgets/controls_backend_selector.c @@ -0,0 +1,111 @@ +#include "game/ui/widgets/controls_backend_selector.h" + +#include "game/game_string.h" + +#include +#include +#include + +typedef struct { + UI_WIDGET_VTABLE vtable; + UI_WIDGET *keyboard_label; + UI_WIDGET *controller_label; + UI_WIDGET *container; + UI_CONTROLS_CONTROLLER *controller; +} UI_CONTROLS_BACKEND_SELECTOR; + +static void M_SyncOutlines(UI_CONTROLS_BACKEND_SELECTOR *self); + +static void M_UpdateText(UI_CONTROLS_BACKEND_SELECTOR *self); +static int32_t M_GetWidth(const UI_CONTROLS_BACKEND_SELECTOR *self); +static int32_t M_GetHeight(const UI_CONTROLS_BACKEND_SELECTOR *self); +static void M_SetPosition( + UI_CONTROLS_BACKEND_SELECTOR *self, int32_t x, int32_t y); +static void M_Control(UI_CONTROLS_BACKEND_SELECTOR *self); +static void M_Draw(UI_CONTROLS_BACKEND_SELECTOR *self); +static void M_Free(UI_CONTROLS_BACKEND_SELECTOR *self); + +static void M_SyncOutlines(UI_CONTROLS_BACKEND_SELECTOR *const self) +{ + UI_Label_RemoveFrame(self->keyboard_label); + UI_Label_RemoveFrame(self->controller_label); + switch (self->controller->backend) { + case INPUT_BACKEND_KEYBOARD: + UI_Label_AddFrame(self->keyboard_label); + break; + case INPUT_BACKEND_CONTROLLER: + UI_Label_AddFrame(self->controller_label); + break; + default: + break; + } +} + +static int32_t M_GetWidth(const UI_CONTROLS_BACKEND_SELECTOR *const self) +{ + return self->container->get_width(self->container); +} + +static int32_t M_GetHeight(const UI_CONTROLS_BACKEND_SELECTOR *const self) +{ + return self->container->get_height(self->container); +} + +static void M_SetPosition( + UI_CONTROLS_BACKEND_SELECTOR *const self, const int32_t x, const int32_t y) +{ + self->container->set_position(self->container, x, y); +} + +static void M_Control(UI_CONTROLS_BACKEND_SELECTOR *const self) +{ + M_SyncOutlines(self); +} + +static void M_Draw(UI_CONTROLS_BACKEND_SELECTOR *const self) +{ + if (self->container->draw != nullptr) { + self->container->draw(self->container); + } +} + +static void M_Free(UI_CONTROLS_BACKEND_SELECTOR *const self) +{ + self->keyboard_label->free(self->keyboard_label); + self->controller_label->free(self->controller_label); + self->container->free(self->container); + Memory_Free(self); +} + +UI_WIDGET *UI_ControlsBackendSelector_Create( + UI_CONTROLS_CONTROLLER *const controller) +{ + UI_CONTROLS_BACKEND_SELECTOR *const self = + Memory_Alloc(sizeof(UI_CONTROLS_BACKEND_SELECTOR)); + self->vtable = (UI_WIDGET_VTABLE) { + .get_width = (UI_WIDGET_GET_WIDTH)M_GetWidth, + .get_height = (UI_WIDGET_GET_HEIGHT)M_GetHeight, + .set_position = (UI_WIDGET_SET_POSITION)M_SetPosition, + .control = (UI_WIDGET_CONTROL)M_Control, + .draw = (UI_WIDGET_DRAW)M_Draw, + .free = (UI_WIDGET_FREE)M_Free, + }; + + self->controller = controller; + + self->keyboard_label = + UI_Label_Create(GS(CONTROL_BACKEND_KEYBOARD), UI_LABEL_AUTO_SIZE, 16); + self->controller_label = + UI_Label_Create(GS(CONTROL_BACKEND_CONTROLLER), UI_LABEL_AUTO_SIZE, 16); + + self->container = UI_Stack_Create( + UI_STACK_LAYOUT_VERTICAL, UI_STACK_AUTO_SIZE, UI_STACK_AUTO_SIZE); + + UI_Stack_AddChild(self->container, self->keyboard_label); + UI_Stack_AddChild(self->container, self->controller_label); + UI_Stack_SetHAlign(self->container, UI_STACK_H_ALIGN_CENTER); + + M_SyncOutlines(self); + + return (UI_WIDGET *)self; +} diff --git a/src/tr2/game/ui/widgets/controls_backend_selector.h b/src/tr2/game/ui/widgets/controls_backend_selector.h new file mode 100644 index 000000000..b2c085757 --- /dev/null +++ b/src/tr2/game/ui/widgets/controls_backend_selector.h @@ -0,0 +1,8 @@ +#pragma once + +#include "game/ui/controllers/controls.h" + +#include + +UI_WIDGET *UI_ControlsBackendSelector_Create( + UI_CONTROLS_CONTROLLER *controller); diff --git a/src/tr2/game/ui/widgets/controls_column.c b/src/tr2/game/ui/widgets/controls_column.c new file mode 100644 index 000000000..162d3b765 --- /dev/null +++ b/src/tr2/game/ui/widgets/controls_column.c @@ -0,0 +1,87 @@ +#include "game/ui/widgets/controls_column.h" + +#include "game/ui/widgets/controls_input_selector.h" + +#include +#include + +typedef struct { + UI_WIDGET_VTABLE vtable; + UI_WIDGET *container; + int32_t selector_count; + UI_WIDGET **selectors; +} UI_CONTROLS_COLUMN; + +static int32_t M_GetWidth(const UI_CONTROLS_COLUMN *self); +static int32_t M_GetHeight(const UI_CONTROLS_COLUMN *self); +static void M_SetPosition(UI_CONTROLS_COLUMN *self, int32_t x, int32_t y); +static void M_Control(UI_CONTROLS_COLUMN *self); +static void M_Draw(UI_CONTROLS_COLUMN *self); +static void M_Free(UI_CONTROLS_COLUMN *self); + +static int32_t M_GetWidth(const UI_CONTROLS_COLUMN *const self) +{ + return self->container->get_width(self->container); +} + +static int32_t M_GetHeight(const UI_CONTROLS_COLUMN *const self) +{ + return self->container->get_height(self->container); +} + +static void M_SetPosition( + UI_CONTROLS_COLUMN *const self, const int32_t x, const int32_t y) +{ + self->container->set_position(self->container, x, y); +} + +static void M_Control(UI_CONTROLS_COLUMN *const self) +{ + if (self->container->control != nullptr) { + self->container->control(self->container); + } +} + +static void M_Draw(UI_CONTROLS_COLUMN *const self) +{ + if (self->container->draw != nullptr) { + self->container->draw(self->container); + } +} + +static void M_Free(UI_CONTROLS_COLUMN *const self) +{ + for (int32_t i = 0; i < self->selector_count; i++) { + self->selectors[i]->free(self->selectors[i]); + } + self->container->free(self->container); + Memory_FreePointer(&self->selectors); + Memory_Free(self); +} + +UI_WIDGET *UI_ControlsColumn_Create( + const int32_t column, UI_CONTROLS_CONTROLLER *const controller) +{ + UI_CONTROLS_COLUMN *const self = Memory_Alloc(sizeof(UI_CONTROLS_COLUMN)); + self->vtable = (UI_WIDGET_VTABLE) { + .get_width = (UI_WIDGET_GET_WIDTH)M_GetWidth, + .get_height = (UI_WIDGET_GET_HEIGHT)M_GetHeight, + .set_position = (UI_WIDGET_SET_POSITION)M_SetPosition, + .control = (UI_WIDGET_CONTROL)M_Control, + .draw = (UI_WIDGET_DRAW)M_Draw, + .free = (UI_WIDGET_FREE)M_Free, + }; + + self->selector_count = UI_ControlsController_GetInputRoleCount(column); + self->container = UI_Stack_Create( + UI_STACK_LAYOUT_VERTICAL, UI_STACK_AUTO_SIZE, UI_STACK_AUTO_SIZE); + self->selectors = Memory_Alloc(sizeof(UI_WIDGET *) * self->selector_count); + + for (int32_t i = 0; i < self->selector_count; i++) { + self->selectors[i] = UI_ControlsInputSelector_Create( + UI_ControlsController_GetInputRole(column, i), controller); + UI_Stack_AddChild(self->container, self->selectors[i]); + } + + return (UI_WIDGET *)self; +} diff --git a/src/tr2/game/ui/widgets/controls_column.h b/src/tr2/game/ui/widgets/controls_column.h new file mode 100644 index 000000000..3589dd486 --- /dev/null +++ b/src/tr2/game/ui/widgets/controls_column.h @@ -0,0 +1,8 @@ +#pragma once + +#include "game/ui/controllers/controls.h" + +#include + +UI_WIDGET *UI_ControlsColumn_Create( + int32_t column, UI_CONTROLS_CONTROLLER *controller); diff --git a/src/tr2/game/ui/widgets/controls_dialog.c b/src/tr2/game/ui/widgets/controls_dialog.c new file mode 100644 index 000000000..b28bf21e1 --- /dev/null +++ b/src/tr2/game/ui/widgets/controls_dialog.c @@ -0,0 +1,122 @@ +#include "game/ui/widgets/controls_dialog.h" + +#include "game/game_string.h" +#include "game/ui/widgets/controls_backend_selector.h" +#include "game/ui/widgets/controls_column.h" +#include "game/ui/widgets/controls_layout_editor.h" + +#include +#include +#include + +typedef struct { + UI_WIDGET_VTABLE vtable; + UI_CONTROLS_CONTROLLER *controller; + UI_WIDGET *window; + UI_WIDGET *backend_selector; + UI_WIDGET *layout_editor; + int32_t listener; +} UI_CONTROLS_DIALOG; + +static void M_DoLayout(UI_CONTROLS_DIALOG *self); +static void M_HandleCanvasResize(const EVENT *event, void *data); +static int32_t M_GetWidth(const UI_CONTROLS_DIALOG *self); +static int32_t M_GetHeight(const UI_CONTROLS_DIALOG *self); +static void M_SetPosition(UI_CONTROLS_DIALOG *self, int32_t x, int32_t y); +static void M_Control(UI_CONTROLS_DIALOG *self); +static void M_Draw(UI_CONTROLS_DIALOG *self); +static void M_Free(UI_CONTROLS_DIALOG *self); + +static void M_DoLayout(UI_CONTROLS_DIALOG *const self) +{ + M_SetPosition( + self, (UI_GetCanvasWidth() - M_GetWidth(self)) / 2, + (UI_GetCanvasHeight() - M_GetHeight(self)) * 2 / 3); +} + +static void M_HandleCanvasResize(const EVENT *event, void *data) +{ + UI_CONTROLS_DIALOG *const self = (UI_CONTROLS_DIALOG *)data; + M_DoLayout(self); +} + +static int32_t M_GetWidth(const UI_CONTROLS_DIALOG *const self) +{ + return self->window->get_width(self->window); +} + +static int32_t M_GetHeight(const UI_CONTROLS_DIALOG *const self) +{ + return self->window->get_height(self->window); +} + +static void M_SetPosition( + UI_CONTROLS_DIALOG *const self, const int32_t x, const int32_t y) +{ + self->window->set_position(self->window, x, y); +} + +static void M_Control(UI_CONTROLS_DIALOG *const self) +{ + // Trigger the UI updates only if anything has changed. + if (UI_ControlsController_Control(self->controller)) { + // Set the root widget - backend selector modal or the inputs modal + if (self->controller->state == UI_CONTROLS_STATE_NAVIGATE_BACKEND + || self->controller->state == UI_CONTROLS_STATE_EXIT) { + UI_Window_SetTitle(self->window, GS(CONTROL_CUSTOMIZE)); + UI_Window_SetRootWidget(self->window, self->backend_selector); + } else { + UI_Window_SetTitle(self->window, nullptr); + UI_Window_SetRootWidget(self->window, self->layout_editor); + } + M_DoLayout(self); + + if (self->window->control != nullptr) { + self->window->control(self->window); + } + } +} + +static void M_Draw(UI_CONTROLS_DIALOG *const self) +{ + if (self->window->draw != nullptr) { + self->window->draw(self->window); + } +} + +static void M_Free(UI_CONTROLS_DIALOG *const self) +{ + self->layout_editor->free(self->layout_editor); + self->backend_selector->free(self->backend_selector); + self->window->free(self->window); + UI_Events_Unsubscribe(self->listener); + Memory_Free(self); +} + +UI_WIDGET *UI_ControlsDialog_Create(UI_CONTROLS_CONTROLLER *const controller) +{ + UI_CONTROLS_DIALOG *const self = Memory_Alloc(sizeof(UI_CONTROLS_DIALOG)); + self->vtable = (UI_WIDGET_VTABLE) { + .get_width = (UI_WIDGET_GET_WIDTH)M_GetWidth, + .get_height = (UI_WIDGET_GET_HEIGHT)M_GetHeight, + .set_position = (UI_WIDGET_SET_POSITION)M_SetPosition, + .control = (UI_WIDGET_CONTROL)M_Control, + .draw = (UI_WIDGET_DRAW)M_Draw, + .free = (UI_WIDGET_FREE)M_Free, + }; + + self->controller = controller; + + self->layout_editor = UI_ControlsLayoutEditor_Create(self->controller); + self->backend_selector = + UI_ControlsBackendSelector_Create(self->controller); + self->window = UI_Window_Create(self->backend_selector, 5, 5, 10, 5); + + UI_Window_SetTitle(self->window, GS(CONTROL_CUSTOMIZE)); + + self->listener = UI_Events_Subscribe( + "canvas_resize", nullptr, M_HandleCanvasResize, self); + + M_DoLayout(self); + return (UI_WIDGET *)self; +} diff --git a/src/tr2/game/ui/widgets/controls_dialog.h b/src/tr2/game/ui/widgets/controls_dialog.h new file mode 100644 index 000000000..8cec14bb8 --- /dev/null +++ b/src/tr2/game/ui/widgets/controls_dialog.h @@ -0,0 +1,7 @@ +#pragma once + +#include "game/ui/controllers/controls.h" + +#include + +UI_WIDGET *UI_ControlsDialog_Create(UI_CONTROLS_CONTROLLER *controller); diff --git a/src/tr2/game/ui/widgets/controls_input_selector.c b/src/tr2/game/ui/widgets/controls_input_selector.c new file mode 100644 index 000000000..edc34c25c --- /dev/null +++ b/src/tr2/game/ui/widgets/controls_input_selector.c @@ -0,0 +1,135 @@ +#include "game/ui/widgets/controls_input_selector.h" + +#include +#include +#include + +#include +#include + +typedef struct { + UI_WIDGET_VTABLE vtable; + INPUT_ROLE input_role; + UI_WIDGET *label; + UI_WIDGET *choice; + UI_WIDGET *container; + UI_CONTROLS_CONTROLLER *controller; +} UI_CONTROLS_INPUT_SELECTOR; + +static void M_UpdateText(UI_CONTROLS_INPUT_SELECTOR *self); +static int32_t M_GetWidth(const UI_CONTROLS_INPUT_SELECTOR *self); +static int32_t M_GetHeight(const UI_CONTROLS_INPUT_SELECTOR *self); +static void M_SetPosition( + UI_CONTROLS_INPUT_SELECTOR *self, int32_t x, int32_t y); +static void M_Control(UI_CONTROLS_INPUT_SELECTOR *self); +static void M_Draw(UI_CONTROLS_INPUT_SELECTOR *self); +static void M_Free(UI_CONTROLS_INPUT_SELECTOR *self); + +static void M_UpdateText(UI_CONTROLS_INPUT_SELECTOR *const self) +{ + const char *const key_name = Input_GetKeyName( + self->controller->backend, self->controller->active_layout, + self->input_role); + UI_Label_ChangeText(self->choice, key_name); + const char *const role_name = Input_GetRoleName(self->input_role); + char role_name_padded[strlen(role_name) + 2]; + sprintf(role_name_padded, "%s ", role_name); + UI_Label_ChangeText(self->label, role_name_padded); +} + +static int32_t M_GetWidth(const UI_CONTROLS_INPUT_SELECTOR *const self) +{ + return self->container->get_width(self->container); +} + +static int32_t M_GetHeight(const UI_CONTROLS_INPUT_SELECTOR *const self) +{ + return self->container->get_height(self->container); +} + +static void M_SetPosition( + UI_CONTROLS_INPUT_SELECTOR *const self, const int32_t x, const int32_t y) +{ + self->container->set_position(self->container, x, y); +} + +static void M_Control(UI_CONTROLS_INPUT_SELECTOR *const self) +{ + if (self->label->control != nullptr) { + self->label->control(self->label); + } + if (self->choice->control != nullptr) { + self->choice->control(self->choice); + } + + // Sync outlines + UI_Label_RemoveFrame(self->label); + UI_Label_RemoveFrame(self->choice); + if (self->controller->active_role == self->input_role) { + if (self->controller->state == UI_CONTROLS_STATE_NAVIGATE_INPUTS + || self->controller->state + == UI_CONTROLS_STATE_NAVIGATE_INPUTS_DEBOUNCE) { + UI_Label_AddFrame(self->label); + } else if (self->controller->state == UI_CONTROLS_STATE_LISTEN) { + UI_Label_AddFrame(self->choice); + } + } + + M_UpdateText(self); + + // Flash conflicts + UI_Label_Flash(self->choice, false, 0); + if (Input_IsKeyConflicted( + self->controller->backend, self->controller->active_layout, + self->input_role)) { + UI_Label_Flash(self->choice, true, 20); + } +} + +static void M_Draw(UI_CONTROLS_INPUT_SELECTOR *const self) +{ + if (self->label->draw != nullptr) { + self->label->draw(self->label); + } + if (self->choice->draw != nullptr) { + self->choice->draw(self->choice); + } +} + +static void M_Free(UI_CONTROLS_INPUT_SELECTOR *const self) +{ + self->label->free(self->label); + self->choice->free(self->choice); + self->container->free(self->container); + Memory_Free(self); +} + +UI_WIDGET *UI_ControlsInputSelector_Create( + const INPUT_ROLE input_role, UI_CONTROLS_CONTROLLER *const controller) +{ + UI_CONTROLS_INPUT_SELECTOR *const self = + Memory_Alloc(sizeof(UI_CONTROLS_INPUT_SELECTOR)); + self->vtable = (UI_WIDGET_VTABLE) { + .get_width = (UI_WIDGET_GET_WIDTH)M_GetWidth, + .get_height = (UI_WIDGET_GET_HEIGHT)M_GetHeight, + .set_position = (UI_WIDGET_SET_POSITION)M_SetPosition, + .control = (UI_WIDGET_CONTROL)M_Control, + .draw = (UI_WIDGET_DRAW)M_Draw, + .free = (UI_WIDGET_FREE)M_Free, + }; + + self->controller = controller; + self->input_role = input_role; + + self->label = UI_Label_Create("", UI_LABEL_AUTO_SIZE, 15); + self->choice = UI_Label_Create("", 70, 15); + self->container = UI_Stack_Create( + UI_STACK_LAYOUT_HORIZONTAL, UI_STACK_AUTO_SIZE, UI_STACK_AUTO_SIZE); + UI_Stack_AddChild(self->container, self->choice); + UI_Stack_AddChild(self->container, self->label); + + // update the text on init + M_UpdateText(self); + + return (UI_WIDGET *)self; +} diff --git a/src/tr2/game/ui/widgets/controls_input_selector.h b/src/tr2/game/ui/widgets/controls_input_selector.h new file mode 100644 index 000000000..6c514c471 --- /dev/null +++ b/src/tr2/game/ui/widgets/controls_input_selector.h @@ -0,0 +1,8 @@ +#pragma once + +#include "game/ui/controllers/controls.h" + +#include + +UI_WIDGET *UI_ControlsInputSelector_Create( + INPUT_ROLE input_role, UI_CONTROLS_CONTROLLER *controller); diff --git a/src/tr2/game/ui/widgets/controls_layout_editor.c b/src/tr2/game/ui/widgets/controls_layout_editor.c new file mode 100644 index 000000000..da5689965 --- /dev/null +++ b/src/tr2/game/ui/widgets/controls_layout_editor.c @@ -0,0 +1,103 @@ +#include "game/ui/widgets/controls_layout_editor.h" + +#include "game/ui/widgets/controls_column.h" +#include "game/ui/widgets/controls_layout_selector.h" + +#include +#include +#include + +typedef struct { + UI_WIDGET_VTABLE vtable; + UI_CONTROLS_CONTROLLER *controller; + UI_WIDGET *layout_selector; + UI_WIDGET *outer_stack; + UI_WIDGET *column_stack; + UI_WIDGET *left_column; + UI_WIDGET *right_column; +} UI_CONTROLS_LAYOUT_EDITOR; + +static void M_DoLayout(UI_CONTROLS_LAYOUT_EDITOR *self); +static int32_t M_GetWidth(const UI_CONTROLS_LAYOUT_EDITOR *self); +static int32_t M_GetHeight(const UI_CONTROLS_LAYOUT_EDITOR *self); +static void M_SetPosition( + UI_CONTROLS_LAYOUT_EDITOR *self, int32_t x, int32_t y); +static void M_Control(UI_CONTROLS_LAYOUT_EDITOR *self); +static void M_Draw(UI_CONTROLS_LAYOUT_EDITOR *self); +static void M_Free(UI_CONTROLS_LAYOUT_EDITOR *self); + +static int32_t M_GetWidth(const UI_CONTROLS_LAYOUT_EDITOR *const self) +{ + return self->outer_stack->get_width(self->outer_stack); +} + +static int32_t M_GetHeight(const UI_CONTROLS_LAYOUT_EDITOR *const self) +{ + return self->outer_stack->get_height(self->outer_stack); +} + +static void M_SetPosition( + UI_CONTROLS_LAYOUT_EDITOR *const self, const int32_t x, const int32_t y) +{ + self->outer_stack->set_position(self->outer_stack, x, y); +} + +static void M_Control(UI_CONTROLS_LAYOUT_EDITOR *const self) +{ + if (self->outer_stack->control != nullptr) { + self->outer_stack->control(self->outer_stack); + } + // Reposition the header. + UI_Stack_DoLayout(self->outer_stack); +} + +static void M_Draw(UI_CONTROLS_LAYOUT_EDITOR *const self) +{ + if (self->outer_stack->draw != nullptr) { + self->outer_stack->draw(self->outer_stack); + } +} + +static void M_Free(UI_CONTROLS_LAYOUT_EDITOR *const self) +{ + self->left_column->free(self->left_column); + self->right_column->free(self->right_column); + self->column_stack->free(self->column_stack); + self->outer_stack->free(self->outer_stack); + self->layout_selector->free(self->layout_selector); + Memory_Free(self); +} + +UI_WIDGET *UI_ControlsLayoutEditor_Create( + UI_CONTROLS_CONTROLLER *const controller) +{ + UI_CONTROLS_LAYOUT_EDITOR *const self = + Memory_Alloc(sizeof(UI_CONTROLS_LAYOUT_EDITOR)); + self->vtable = (UI_WIDGET_VTABLE) { + .get_width = (UI_WIDGET_GET_WIDTH)M_GetWidth, + .get_height = (UI_WIDGET_GET_HEIGHT)M_GetHeight, + .set_position = (UI_WIDGET_SET_POSITION)M_SetPosition, + .control = (UI_WIDGET_CONTROL)M_Control, + .draw = (UI_WIDGET_DRAW)M_Draw, + .free = (UI_WIDGET_FREE)M_Free, + }; + + self->controller = controller; + + self->layout_selector = UI_ControlsLayoutSelector_Create(self->controller); + self->left_column = UI_ControlsColumn_Create(0, self->controller); + self->right_column = UI_ControlsColumn_Create(1, self->controller); + + self->column_stack = UI_Stack_Create( + UI_STACK_LAYOUT_HORIZONTAL, UI_STACK_AUTO_SIZE, UI_STACK_AUTO_SIZE); + UI_Stack_AddChild(self->column_stack, self->left_column); + UI_Stack_AddChild(self->column_stack, self->right_column); + + self->outer_stack = UI_Stack_Create( + UI_STACK_LAYOUT_VERTICAL, UI_STACK_AUTO_SIZE, UI_STACK_AUTO_SIZE); + UI_Stack_SetHAlign(self->outer_stack, UI_STACK_H_ALIGN_CENTER); + UI_Stack_AddChild(self->outer_stack, self->layout_selector); + UI_Stack_AddChild(self->outer_stack, self->column_stack); + + return (UI_WIDGET *)self; +} diff --git a/src/tr2/game/ui/widgets/controls_layout_editor.h b/src/tr2/game/ui/widgets/controls_layout_editor.h new file mode 100644 index 000000000..0e3a08ea1 --- /dev/null +++ b/src/tr2/game/ui/widgets/controls_layout_editor.h @@ -0,0 +1,7 @@ +#pragma once + +#include "game/ui/controllers/controls.h" + +#include + +UI_WIDGET *UI_ControlsLayoutEditor_Create(UI_CONTROLS_CONTROLLER *controller); diff --git a/src/tr2/game/ui/widgets/controls_layout_selector.c b/src/tr2/game/ui/widgets/controls_layout_selector.c new file mode 100644 index 000000000..ba956759d --- /dev/null +++ b/src/tr2/game/ui/widgets/controls_layout_selector.c @@ -0,0 +1,83 @@ +#include "game/ui/widgets/controls_layout_selector.h" + +#include +#include + +typedef struct { + UI_WIDGET_VTABLE vtable; + UI_WIDGET *label; + UI_CONTROLS_CONTROLLER *controller; +} UI_CONTROLS_LAYOUT_SELECTOR; + +static int32_t M_GetWidth(const UI_CONTROLS_LAYOUT_SELECTOR *self); +static int32_t M_GetHeight(const UI_CONTROLS_LAYOUT_SELECTOR *self); +static void M_SetPosition( + UI_CONTROLS_LAYOUT_SELECTOR *self, int32_t x, int32_t y); +static void M_Control(UI_CONTROLS_LAYOUT_SELECTOR *self); +static void M_Draw(UI_CONTROLS_LAYOUT_SELECTOR *self); +static void M_Free(UI_CONTROLS_LAYOUT_SELECTOR *self); + +static int32_t M_GetWidth(const UI_CONTROLS_LAYOUT_SELECTOR *const self) +{ + return self->label->get_width(self->label); +} + +static int32_t M_GetHeight(const UI_CONTROLS_LAYOUT_SELECTOR *const self) +{ + return self->label->get_height(self->label); +} + +static void M_SetPosition( + UI_CONTROLS_LAYOUT_SELECTOR *const self, const int32_t x, const int32_t y) +{ + self->label->set_position(self->label, x, y); +} + +static void M_Control(UI_CONTROLS_LAYOUT_SELECTOR *const self) +{ + if (self->controller->state == UI_CONTROLS_STATE_NAVIGATE_LAYOUT) { + UI_Label_AddFrame(self->label); + UI_Label_ChangeText( + self->label, Input_GetLayoutName(self->controller->active_layout)); + } else { + UI_Label_RemoveFrame(self->label); + } + if (self->label->control != nullptr) { + self->label->control(self->label); + } +} + +static void M_Draw(UI_CONTROLS_LAYOUT_SELECTOR *const self) +{ + if (self->label->draw != nullptr) { + self->label->draw(self->label); + } +} + +static void M_Free(UI_CONTROLS_LAYOUT_SELECTOR *const self) +{ + self->label->free(self->label); + Memory_Free(self); +} + +UI_WIDGET *UI_ControlsLayoutSelector_Create( + UI_CONTROLS_CONTROLLER *const controller) +{ + UI_CONTROLS_LAYOUT_SELECTOR *self = + Memory_Alloc(sizeof(UI_CONTROLS_LAYOUT_SELECTOR)); + self->vtable = (UI_WIDGET_VTABLE) { + .get_width = (UI_WIDGET_GET_WIDTH)M_GetWidth, + .get_height = (UI_WIDGET_GET_HEIGHT)M_GetHeight, + .set_position = (UI_WIDGET_SET_POSITION)M_SetPosition, + .control = (UI_WIDGET_CONTROL)M_Control, + .draw = (UI_WIDGET_DRAW)M_Draw, + .free = (UI_WIDGET_FREE)M_Free, + }; + + self->controller = controller; + self->label = UI_Label_Create( + Input_GetLayoutName(self->controller->active_layout), + UI_LABEL_AUTO_SIZE, 25); + UI_Label_AddFrame(self->label); + return (UI_WIDGET *)self; +} diff --git a/src/tr2/game/ui/widgets/controls_layout_selector.h b/src/tr2/game/ui/widgets/controls_layout_selector.h new file mode 100644 index 000000000..a14cead06 --- /dev/null +++ b/src/tr2/game/ui/widgets/controls_layout_selector.h @@ -0,0 +1,7 @@ +#pragma once + +#include "game/ui/controllers/controls.h" + +#include + +UI_WIDGET *UI_ControlsLayoutSelector_Create(UI_CONTROLS_CONTROLLER *controller); diff --git a/src/tr2/game/ui/widgets/stats_dialog.c b/src/tr2/game/ui/widgets/stats_dialog.c new file mode 100644 index 000000000..ae2743cfa --- /dev/null +++ b/src/tr2/game/ui/widgets/stats_dialog.c @@ -0,0 +1,328 @@ +#include "game/ui/widgets/stats_dialog.h" + +#include "game/game.h" +#include "game/game_flow.h" +#include "game/game_string.h" +#include "game/input.h" +#include "game/stats.h" +#include "global/vars.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#define VISIBLE_ROWS 7 +#define ROW_HEIGHT 18 + +typedef enum { + M_ROW_GENERIC, + M_ROW_TIMER, + M_ROW_LEVEL_SECRETS, + M_ROW_ALL_SECRETS, + M_ROW_KILLS, + M_ROW_AMMO_USED, + M_ROW_AMMO_HITS, + M_ROW_MEDIPACKS, + M_ROW_DISTANCE_TRAVELED, +} M_ROW_ROLE; + +typedef struct { + UI_WIDGET_VTABLE vtable; + UI_STATS_DIALOG_ARGS args; + UI_WIDGET *requester; + int32_t listener; +} UI_STATS_DIALOG; + +static void M_AddRow( + UI_STATS_DIALOG *self, M_ROW_ROLE role, const char *left_text, + const char *right_text); +static void M_AddRowFromRole( + UI_STATS_DIALOG *self, M_ROW_ROLE role, const STATS_COMMON *stats); +static void M_AddLevelStatsRows(UI_STATS_DIALOG *self); +static void M_AddFinalStatsRows(UI_STATS_DIALOG *self); +static void M_AddAssaultCourseStatsRows(UI_STATS_DIALOG *self); +static void M_UpdateTimerRow(UI_STATS_DIALOG *self); +static void M_DoLayout(UI_STATS_DIALOG *self); +static void M_HandleLayoutUpdate(const EVENT *event, void *data); + +static int32_t M_GetWidth(const UI_STATS_DIALOG *self); +static int32_t M_GetHeight(const UI_STATS_DIALOG *self); +static void M_SetPosition(UI_STATS_DIALOG *self, int32_t x, int32_t y); +static void M_Control(UI_STATS_DIALOG *self); +static void M_Draw(UI_STATS_DIALOG *self); +static void M_Free(UI_STATS_DIALOG *self); + +static void M_AddRow( + UI_STATS_DIALOG *const self, const M_ROW_ROLE role, + const char *const left_text, const char *const right_text) +{ + UI_Requester_AddRowLR( + self->requester, left_text, right_text, (void *)(intptr_t)role); +} + +static void M_AddRowFromRole( + UI_STATS_DIALOG *const self, const M_ROW_ROLE role, + const STATS_COMMON *const stats) +{ + char buf[32]; + + switch (role) { + case M_ROW_TIMER: { + const int32_t sec = stats->timer / FRAMES_PER_SECOND; + sprintf( + buf, "%02d:%02d:%02d", (sec / 60) / 60, (sec / 60) % 60, sec % 60); + M_AddRow(self, role, GS(STATS_TIME_TAKEN), buf); + break; + } + + case M_ROW_LEVEL_SECRETS: { + char *ptr = buf; + int32_t num_secrets = 0; + for (int32_t i = 0; i < 3; i++) { + if (((LEVEL_STATS *)stats)->secret_flags & (1 << i)) { + sprintf(ptr, "\\{secret %d}", i + 1); + num_secrets++; + } else { + strcpy(ptr, " "); + } + ptr += strlen(ptr); + } + *ptr++ = '\0'; + if (num_secrets == 0) { + strcpy(buf, GS(MISC_NONE)); + } + M_AddRow(self, role, GS(STATS_SECRETS), buf); + break; + } + + case M_ROW_ALL_SECRETS: + sprintf( + buf, GS(STATS_DETAIL_FMT), ((FINAL_STATS *)stats)->found_secrets, + ((FINAL_STATS *)stats)->total_secrets); + M_AddRow(self, role, GS(STATS_SECRETS), buf); + break; + + case M_ROW_KILLS: + sprintf(buf, GS(STATS_BASIC_FMT), stats->kills); + M_AddRow(self, role, GS(STATS_KILLS), buf); + break; + + case M_ROW_AMMO_USED: + sprintf(buf, "%d", stats->ammo_used); + M_AddRow(self, role, GS(STATS_AMMO_USED), buf); + break; + + case M_ROW_AMMO_HITS: + sprintf(buf, "%d", stats->ammo_hits); + M_AddRow(self, role, GS(STATS_AMMO_HITS), buf); + break; + + case M_ROW_MEDIPACKS: + if ((stats->medipacks & 1) != 0) { + sprintf(buf, "%d.5", stats->medipacks >> 1); + } else { + sprintf(buf, "%d.0", stats->medipacks >> 1); + } + M_AddRow(self, role, GS(STATS_MEDIPACKS_USED), buf); + break; + + case M_ROW_DISTANCE_TRAVELED: + const int32_t distance = stats->distance / 445; + if (distance < 1000) { + sprintf(buf, "%dm", distance); + } else { + sprintf(buf, "%d.%02dkm", distance / 1000, distance % 100); + } + M_AddRow(self, role, GS(STATS_DISTANCE_TRAVELLED), buf); + break; + + default: + break; + } +} + +static void M_AddLevelStatsRows(UI_STATS_DIALOG *const self) +{ + const GF_LEVEL *const current_level = Game_GetCurrentLevel(); + const STATS_COMMON *stats = + current_level != nullptr && self->args.level_num == current_level->num + ? (STATS_COMMON *)&g_SaveGame.current_stats + : (STATS_COMMON *)&g_SaveGame.start[self->args.level_num].stats; + M_AddRowFromRole(self, M_ROW_TIMER, stats); + if (g_GF_NumSecrets != 0) { + M_AddRowFromRole(self, M_ROW_LEVEL_SECRETS, stats); + } + M_AddRowFromRole(self, M_ROW_KILLS, stats); + M_AddRowFromRole(self, M_ROW_AMMO_USED, stats); + M_AddRowFromRole(self, M_ROW_AMMO_HITS, stats); + M_AddRowFromRole(self, M_ROW_MEDIPACKS, stats); + M_AddRowFromRole(self, M_ROW_DISTANCE_TRAVELED, stats); +} + +static void M_AddFinalStatsRows(UI_STATS_DIALOG *const self) +{ + const FINAL_STATS final_stats = Stats_ComputeFinalStats(); + const STATS_COMMON *stats = (STATS_COMMON *)&final_stats; + M_AddRowFromRole(self, M_ROW_TIMER, stats); + M_AddRowFromRole(self, M_ROW_ALL_SECRETS, stats); + M_AddRowFromRole(self, M_ROW_KILLS, stats); + M_AddRowFromRole(self, M_ROW_AMMO_USED, stats); + M_AddRowFromRole(self, M_ROW_AMMO_HITS, stats); + M_AddRowFromRole(self, M_ROW_MEDIPACKS, stats); + M_AddRowFromRole(self, M_ROW_DISTANCE_TRAVELED, stats); +} + +static void M_AddAssaultCourseStatsRows(UI_STATS_DIALOG *const self) +{ + if (!g_Assault.best_time[0]) { + M_AddRow(self, M_ROW_GENERIC, GS(STATS_ASSAULT_NO_TIMES_SET), nullptr); + return; + } + + for (int32_t i = 0; i < 10; i++) { + char left_buf[32] = ""; + char right_buf[32] = ""; + if (g_Assault.best_time[i]) { + sprintf( + left_buf, "%2d: %s %d", i + 1, GS(STATS_ASSAULT_FINISH), + g_Assault.best_finish[i]); + + const int32_t sec = g_Assault.best_time[i] / FRAMES_PER_SECOND; + sprintf( + right_buf, "%02d:%02d.%-2d", sec / 60, sec % 60, + g_Assault.best_time[i] % FRAMES_PER_SECOND + / (FRAMES_PER_SECOND / 10)); + } + + M_AddRow(self, M_ROW_GENERIC, left_buf, right_buf); + } +} + +static void M_UpdateTimerRow(UI_STATS_DIALOG *const self) +{ + if (self->args.mode != UI_STATS_DIALOG_MODE_LEVEL) { + return; + } + + for (int32_t i = 0; i < UI_Requester_GetRowCount(self->requester); i++) { + if ((intptr_t)UI_Requester_GetRowUserData(self->requester, i) + != M_ROW_TIMER) { + continue; + } + char buf[32]; + const int32_t sec = g_SaveGame.current_stats.timer / FRAMES_PER_SECOND; + sprintf( + buf, "%02d:%02d:%02d", (sec / 60) / 60, (sec / 60) % 60, sec % 60); + UI_Requester_ChangeRowLR( + self->requester, i, nullptr, buf, (void *)(intptr_t)M_ROW_TIMER); + return; + } +} + +static void M_DoLayout(UI_STATS_DIALOG *const self) +{ + M_SetPosition( + self, (UI_GetCanvasWidth() - M_GetWidth(self)) / 2, + (UI_GetCanvasHeight() - M_GetHeight(self)) - 50); +} + +static void M_HandleLayoutUpdate(const EVENT *event, void *data) +{ + UI_STATS_DIALOG *const self = (UI_STATS_DIALOG *)data; + M_DoLayout(self); +} + +static int32_t M_GetWidth(const UI_STATS_DIALOG *const self) +{ + return self->requester->get_width(self->requester); +} + +static int32_t M_GetHeight(const UI_STATS_DIALOG *const self) +{ + return self->requester->get_height(self->requester); +} + +static void M_SetPosition( + UI_STATS_DIALOG *const self, const int32_t x, const int32_t y) +{ + self->requester->set_position(self->requester, x, y); +} + +static void M_Control(UI_STATS_DIALOG *const self) +{ + if (self->requester->control != nullptr) { + self->requester->control(self->requester); + } + + M_UpdateTimerRow(self); +} + +static void M_Draw(UI_STATS_DIALOG *const self) +{ + if (self->requester->draw != nullptr) { + self->requester->draw(self->requester); + } +} + +static void M_Free(UI_STATS_DIALOG *const self) +{ + self->requester->free(self->requester); + UI_Events_Unsubscribe(self->listener); + Memory_Free(self); +} + +UI_WIDGET *UI_StatsDialog_Create(UI_STATS_DIALOG_ARGS args) +{ + UI_STATS_DIALOG *const self = Memory_Alloc(sizeof(UI_STATS_DIALOG)); + self->vtable = (UI_WIDGET_VTABLE) { + .get_width = (UI_WIDGET_GET_WIDTH)M_GetWidth, + .get_height = (UI_WIDGET_GET_HEIGHT)M_GetHeight, + .set_position = (UI_WIDGET_SET_POSITION)M_SetPosition, + .control = (UI_WIDGET_CONTROL)M_Control, + .draw = (UI_WIDGET_DRAW)M_Draw, + .free = (UI_WIDGET_FREE)M_Free, + }; + + // TODO: add support for the bare style by merging TR1 and TR2 stats dialog + // implementations. + ASSERT(args.style == UI_STATS_DIALOG_STYLE_BORDERED); + + self->args = args; + self->requester = UI_Requester_Create((UI_REQUESTER_SETTINGS) { + .is_selectable = false, + .visible_rows = VISIBLE_ROWS, + .width = 290, + .row_height = ROW_HEIGHT, + }); + + self->listener = UI_Events_Subscribe( + "layout_update", nullptr, M_HandleLayoutUpdate, self); + + switch (args.mode) { + case UI_STATS_DIALOG_MODE_LEVEL: + UI_Requester_SetTitle( + self->requester, GF_GetLevel(GFLT_MAIN, args.level_num)->title); + M_AddLevelStatsRows(self); + break; + + case UI_STATS_DIALOG_MODE_FINAL: + UI_Requester_SetTitle(self->requester, GS(STATS_FINAL_STATISTICS)); + M_AddFinalStatsRows(self); + break; + + case UI_STATS_DIALOG_MODE_ASSAULT_COURSE: + UI_Requester_SetTitle(self->requester, GS(STATS_ASSAULT_TITLE)); + M_AddAssaultCourseStatsRows(self); + break; + } + + M_DoLayout(self); + + return (UI_WIDGET *)self; +} diff --git a/src/tr2/game/ui/widgets/stats_dialog.h b/src/tr2/game/ui/widgets/stats_dialog.h new file mode 100644 index 000000000..96afd51a2 --- /dev/null +++ b/src/tr2/game/ui/widgets/stats_dialog.h @@ -0,0 +1,3 @@ +#pragma once + +#include diff --git a/src/tr2/game/viewport.c b/src/tr2/game/viewport.c index 424039f73..275f6faed 100644 --- a/src/tr2/game/viewport.c +++ b/src/tr2/game/viewport.c @@ -52,7 +52,7 @@ static void M_AlterFov(VIEWPORT *const vp) const int32_t view_angle = vp->view_angle <= 0 ? g_Config.visuals.fov * DEG_1 : vp->view_angle; const int32_t fov_width = vp->game_vars.win_height * 320 - / (g_Config.visuals.use_psx_fov ? 200 : 240); + / (g_Config.visuals.use_pcx_fov ? 200 : 240); vp->game_vars.persp = fov_width / 2 * Math_Cos(view_angle / 2) / Math_Sin(view_angle / 2); @@ -120,32 +120,36 @@ static void M_ApplyGameVars(const VIEWPORT *const vp) void Viewport_Reset(void) { - const SHELL_SIZE size = Shell_GetCurrentSize(); + int32_t win_width; + int32_t win_height; + if (Shell_IsFullscreen()) { + win_width = Shell_GetCurrentDisplayWidth(); + win_height = Shell_GetCurrentDisplayHeight(); + } else { + SDL_GetWindowSize(g_SDLWindow, &win_width, &win_height); + } VIEWPORT *const vp = &m_Viewport; switch (g_Config.rendering.aspect_mode) { case AM_4_3: - vp->render_ar.w = 4; - vp->render_ar.h = 3; + vp->render_ar = 4.0 / 3.0; break; case AM_16_9: - vp->render_ar.w = 16; - vp->render_ar.h = 9; + vp->render_ar = 16.0 / 9.0; break; case AM_ANY: - vp->render_ar.w = size.w; - vp->render_ar.h = size.h; + vp->render_ar = win_width / (double)win_height; break; } - vp->width = size.w / g_Config.rendering.scaler; - vp->height = size.h / g_Config.rendering.scaler; + vp->width = win_width / g_Config.rendering.scaler; + vp->height = win_height / g_Config.rendering.scaler; if (g_Config.rendering.aspect_mode != AM_ANY) { - vp->width = vp->height * vp->render_ar.w / vp->render_ar.h; + vp->width = vp->height * vp->render_ar; } - vp->near_z = Output_GetNearZ() >> W2V_SHIFT; - vp->far_z = Output_GetFarZ() >> W2V_SHIFT; + vp->near_z = VIEW_NEAR; + vp->far_z = VIEW_FAR; // We do not update vp->view_angle on purpose, as it's managed by the game // rather than the window manager. (Think cutscenes, special cameras, etc.) @@ -167,8 +171,9 @@ void Viewport_Reset(void) M_InitGameVars(&m_Viewport); M_ApplyGameVars(&m_Viewport); - const int32_t win_border = size.h * (1.0 - g_Config.rendering.sizer); - Render_SetupDisplay(win_border, size.w, size.h, vp->width, vp->height); + const int32_t win_border = win_height * (1.0 - g_Config.rendering.sizer); + Render_SetupDisplay( + win_border, win_width, win_height, vp->width, vp->height); } const VIEWPORT *Viewport_Get(void) @@ -198,16 +203,6 @@ void Viewport_AlterFOV(const int16_t view_angle) M_ApplyGameVars(&m_Viewport); } -int32_t Viewport_GetWidth(void) -{ - return g_PhdWinWidth; -} - -int32_t Viewport_GetHeight(void) -{ - return g_PhdWinHeight; -} - int32_t Viewport_GetMaxX(void) { return g_PhdWinMaxX; diff --git a/src/tr2/game/viewport.h b/src/tr2/game/viewport.h index 8ec3d7485..9af7d0105 100644 --- a/src/tr2/game/viewport.h +++ b/src/tr2/game/viewport.h @@ -8,10 +8,7 @@ typedef struct { int32_t near_z; int32_t far_z; int16_t view_angle; - struct { - int32_t w; - int32_t h; - } render_ar; + double render_ar; // TODO: remove most of these variables if possible struct { diff --git a/src/tr2/global/const.h b/src/tr2/global/const.h index bca90631b..e2f048b14 100644 --- a/src/tr2/global/const.h +++ b/src/tr2/global/const.h @@ -12,6 +12,7 @@ #define STEPUP_HEIGHT ((STEP_L * 3) / 2) // = 384 #define SLOPE_DIF 60 #define MAX_WIBBLE 2 +#define MAX_SHADE 0x300 #define LIGHT_MAP_SIZE 32 #define MAX_ROOM_LIGHT_UNIT (0x2000 / (WIBBLE_SIZE / 2)) @@ -27,10 +28,14 @@ #define MAX_PALETTES 16 #define MAX_VERTICES 0x2000 #define MAX_BOUND_ROOMS 128 +#define MAX_EFFECTS 100 #define MAX_LEVELS 24 #define MAX_LEVEL_NAME_SIZE 50 // TODO: get rid of this limit #define MAX_DEMO_FILES MAX_LEVELS #define MAX_REQUESTER_ITEMS 24 +#define MAX_SAVE_SLOTS 16 +#define MAX_ASSAULT_TIMES 10 +#define MAX_SG_BUFFER_SIZE 6272 #define DEATH_WAIT (5 * 2 * FRAMES_PER_SECOND) // = 300 #define DEATH_WAIT_INPUT (2 * FRAMES_PER_SECOND) // = 60 @@ -141,8 +146,53 @@ #define SPRITE_TRANS_QUARTER 0x60000000 #define SPRITE_COLOUR(r, g, b) ((r) | ((g) << 8) | ((b) << 16)) +#define VIEW_NEAR (20 * 1) // = 20 +#define VIEW_FAR (20 * WALL_L) // = 20480 + +#define FOG_START (12 * WALL_L) // = 12288 +#define FOG_END (20 * WALL_L) // = 20480 + +#define NUM_SLOTS 5 #define PITCH_SHIFT 4 #define IDS_DX5_REQUIRED 1 #define MONK_FRIENDLY_FIRE_THRESHOLD 10 + +#define GUN_AMMO_CLIP 16 +#define GUN_AMMO_QTY (GUN_AMMO_CLIP * 2) // = 32 + +#define MAGNUM_AMMO_CLIP 20 +#define MAGNUM_AMMO_QTY (MAGNUM_AMMO_CLIP * 2) // = 40 + +#define UZI_AMMO_CLIP 40 +#define UZI_AMMO_QTY (UZI_AMMO_CLIP * 2) // = 80 + +#define M16_AMMO_CLIP 40 +#define M16_AMMO_QTY M16_AMMO_CLIP // = 40 + +#define SHOTGUN_SHELL_COUNT 2 +#define SHOTGUN_AMMO_CLIP 6 +#define SHOTGUN_AMMO_QTY (SHOTGUN_AMMO_CLIP * SHOTGUN_SHELL_COUNT) // = 12 +#define SHOTGUN_PELLET_SCATTER (DEG_1 * 20) // = 3640 + +#define HARPOON_BOLT_SPEED 150 +#define HARPOON_RECOIL 4 +#define HARPOON_AMMO_CLIP 3 +#define HARPOON_AMMO_QTY HARPOON_AMMO_CLIP // = 3 + +#define GRENADE_SPEED 200 +#define GRENADE_AMMO_CLIP 1 +#define GRENADE_AMMO_QTY (GRENADE_AMMO_CLIP * 2) // = 2 + +#if defined(PSX_VERSION) && defined(JAPAN) + #define FLARE_AMMO_BOX 8 +#else + #define FLARE_AMMO_BOX 6 +#endif +#define FLARE_AMMO_QTY FLARE_AMMO_BOX + +#define LOW_LIGHT 5120 +#define HIGH_LIGHT 4096 + +#define UNIT_SHADOW 256 diff --git a/src/tr2/global/types_decomp.h b/src/tr2/global/types_decomp.h index 59ecb25d0..e0645c33d 100644 --- a/src/tr2/global/types_decomp.h +++ b/src/tr2/global/types_decomp.h @@ -70,6 +70,12 @@ typedef struct { float g; } POINT_INFO; +typedef struct { + uint32_t best_time[10]; + uint32_t best_finish[10]; + uint32_t finish_count; +} ASSAULT_STATS; + typedef struct { void *_0; int32_t _1; @@ -80,6 +86,65 @@ typedef enum { DRAW_COLOR_KEY = 1, } DRAW_TYPE; +typedef struct STATS_COMMON { + uint32_t timer; + uint32_t ammo_used; + uint32_t ammo_hits; + uint32_t distance; + uint16_t kills; + uint8_t medipacks; +} STATS_COMMON; + +typedef struct { + struct STATS_COMMON; + int32_t found_secrets; + int32_t total_secrets; +} FINAL_STATS; + +typedef struct { + struct STATS_COMMON; + uint8_t secret_flags; +} LEVEL_STATS; + +typedef struct { + uint16_t pistol_ammo; + uint16_t magnum_ammo; + uint16_t uzi_ammo; + uint16_t shotgun_ammo; + uint16_t m16_ammo; + uint16_t grenade_ammo; + uint16_t harpoon_ammo; + uint8_t small_medipacks; + uint8_t large_medipacks; + uint8_t reserved1; + uint8_t flares; + uint8_t gun_status; + uint8_t gun_type; + uint16_t available: 1; // 0x01 1 + uint16_t has_pistols: 1; // 0x02 2 + uint16_t has_magnums: 1; // 0x04 4 + uint16_t has_uzis: 1; // 0x08 8 + uint16_t has_shotgun: 1; // 0x10 16 + uint16_t has_m16: 1; // 0x20 32 + uint16_t has_grenade: 1; // 0x40 64 + uint16_t has_harpoon: 1; // 0x80 128 + uint16_t pad : 8; + uint16_t reserved2; + LEVEL_STATS stats; +} START_INFO; + +typedef struct { + START_INFO *start; + LEVEL_STATS current_stats; + int16_t current_level; + bool bonus_flag; + uint8_t num_pickup[2]; + uint8_t num_puzzle[4]; + uint8_t num_key[4]; + uint16_t reserved; + char buffer[MAX_SG_BUFFER_SIZE]; +} SAVEGAME_INFO; + typedef struct { int16_t lock_angles[4]; int16_t left_angles[4]; @@ -94,6 +159,16 @@ typedef struct { int16_t sample_num; } WEAPON_INFO; +typedef struct { + int16_t zone_num; + int16_t enemy_zone_num; + int32_t distance; + int32_t ahead; + int32_t bite; + int16_t angle; + int16_t enemy_facing; +} AI_INFO; + typedef enum { GFE_PICTURE = 0, GFE_LIST_START = 1, @@ -120,6 +195,17 @@ typedef enum { GFE_REMOVE_AMMO = 22, } GF_EVENTS; +typedef struct { + XYZ_32 pos; + int32_t mesh_num; +} BITE; + +typedef struct { + SECTOR *sector; + SECTOR old_sector; + int16_t block; +} DOORPOS_DATA; + typedef enum { TRAP_SET = 0, TRAP_ACTIVATE = 1, @@ -151,6 +237,13 @@ typedef struct { int32_t pitch; } SKIDOO_INFO; +typedef struct { + struct { + XYZ_16 min; + XYZ_16 max; + } shift, rot; +} OBJECT_BOUNDS; + typedef struct { int32_t xv; int32_t yv; @@ -161,6 +254,56 @@ typedef struct { int32_t table[32]; // WIBBLE_SIZE } ROOM_LIGHT_TABLE; +typedef enum { + REQ_CENTER = 0x00, + REQ_USE = 0x01, + REQ_ALIGN_LEFT = 0x02, + REQ_ALIGN_RIGHT = 0x04, + REQ_HEADING = 0x08, + REQ_BEST_TIME = 0x10, + REQ_NORMAL_TIME = 0x20, + REQ_NO_TIME = 0x40, +} REQUESTER_FLAGS; + #pragma pack(pop) +typedef struct { + uint16_t no_selector : 1; + uint16_t ready : 1; // not present in the OG + uint16_t pad : 14; + uint16_t items_count; + uint16_t selected; + uint16_t visible_count; + uint16_t line_offset; + uint16_t line_old_offset; + uint16_t pix_width; + uint16_t line_height; + int16_t x_pos; + int16_t y_pos; + int16_t z_pos; + uint16_t item_string_len; + char *pitem_strings1; + char *pitem_strings2; + uint32_t *pitem_flags1; + uint32_t *pitem_flags2; + uint32_t heading_flags1; + uint32_t heading_flags2; + uint32_t background_flags; + uint32_t moreup_flags; + uint32_t moredown_flags; + uint32_t item_flags1[24]; // MAX_REQUESTER_ITEMS + uint32_t item_flags2[24]; // MAX_REQUESTER_ITEMS + TEXTSTRING *heading_text1; + TEXTSTRING *heading_text2; + TEXTSTRING *background_text; + TEXTSTRING *moreup_text; + TEXTSTRING *moredown_text; + TEXTSTRING *item_texts1[24]; // MAX_REQUESTER_ITEMS + TEXTSTRING *item_texts2[24]; // MAX_REQUESTER_ITEMS + char heading_string1[32]; + char heading_string2[32]; + uint32_t render_width; + uint32_t render_height; +} REQUEST_INFO; + // clang-format on diff --git a/src/tr2/global/vars.c b/src/tr2/global/vars.c index 820214a79..bda567bfd 100644 --- a/src/tr2/global/vars.c +++ b/src/tr2/global/vars.c @@ -11,8 +11,10 @@ const float g_RhwFactor = 0x14000000.p0; SDL_Window *g_SDLWindow = nullptr; uint32_t g_PerspectiveDistance = 0x3000000; +uint32_t g_AssaultBestTime = -1; int32_t g_OverlayStatus = 1; +bool g_GymInvOpenEnabled = true; // TODO: make me configurable int32_t g_MidSort = 0; int32_t g_PhdWinTop; float g_FltWinBottom; @@ -44,19 +46,26 @@ float g_FltPersp; int16_t *g_Info3DPtr = nullptr; int32_t g_PhdWinWidth; int32_t g_PhdViewDistance; -PHD_VBUF *g_PhdVBuf = nullptr; +PHD_VBUF g_PhdVBuf[1500]; float g_FltWinRight; int32_t g_PhdWinRight; int32_t g_SurfaceCount; SORT_ITEM *g_Sort3DPtr = nullptr; +bool g_IsDemoLoaded; +bool g_IsAssaultTimerDisplay; +bool g_IsAssaultTimerActive; +bool g_IsMonkAngry; uint16_t g_SoundOptionLine; +ASSAULT_STATS g_Assault; int32_t g_HealthBarTimer; int32_t g_LevelComplete; +SAVEGAME_INFO g_SaveGame = {}; LARA_INFO g_Lara; ITEM *g_LaraItem = nullptr; CREATURE *g_BaddieSlots = nullptr; bool g_CameraUnderwater; +char g_LevelFileName[256]; WEAPON_INFO g_Weapons[] = { {}, @@ -168,17 +177,104 @@ WEAPON_INFO g_Weapons[] = { }; int16_t g_FinalBossActive; -uint16_t g_FinalLevelCount; +int16_t g_FinalLevelCount; int16_t g_FinalBossCount; int16_t g_FinalBossItem[5]; static char m_LoadGameRequesterStrings1[MAX_LEVELS][50]; static char m_LoadGameRequesterStrings2[MAX_LEVELS][50]; +REQUEST_INFO g_LoadGameRequester = { + .no_selector = 0, + .ready = 0, + .pad = 0, + .items_count = 1, + .selected = 0, + .visible_count = 5, + .line_offset = 0, + .line_old_offset = 0, + .pix_width = 296, + .line_height = 18, + .x_pos = 0, + .y_pos = -32, + .z_pos = 0, + .item_string_len = 50, + .pitem_strings1 = (char *)m_LoadGameRequesterStrings1, + .pitem_strings2 = (char *)m_LoadGameRequesterStrings2, + .pitem_flags1 = nullptr, + .pitem_flags2 = nullptr, + .heading_flags1 = 0, + .heading_flags2 = 0, + .background_flags = 0, + .moreup_flags = 0, + .moredown_flags = 0, + .item_flags1 = {}, + .item_flags2 = {}, + .heading_text1 = nullptr, + .heading_text2 = nullptr, + .background_text = nullptr, + .moreup_text = nullptr, + .moredown_text = nullptr, + .item_texts1 = { nullptr }, + .item_texts2 = { nullptr }, + .heading_string1 = {}, + .heading_string2 = {}, + .render_width = 0, + .render_height = 0, +}; + +REQUEST_INFO g_SaveGameRequester = { + .no_selector = 0, + .ready = 0, + .pad = 0, + .items_count = 1, + .selected = 0, + .visible_count = 5, + .line_offset = 0, + .line_old_offset = 0, + .pix_width = 272, + .line_height = 18, + .x_pos = 0, + .y_pos = -32, + .z_pos = 0, + .item_string_len = 50, + .pitem_strings1 = (char *)g_ValidLevelStrings1, + .pitem_strings2 = (char *)g_ValidLevelStrings2, + .pitem_flags1 = nullptr, + .pitem_flags2 = nullptr, + .heading_flags1 = 0, + .heading_flags2 = 0, + .background_flags = 0, + .moreup_flags = 0, + .moredown_flags = 0, + .item_flags1 = {}, + .item_flags2 = {}, + .heading_text1 = nullptr, + .heading_text2 = nullptr, + .background_text = nullptr, + .moreup_text = nullptr, + .moredown_text = nullptr, + .item_texts1 = { nullptr }, + .item_texts2 = { nullptr }, + .heading_string1 = {}, + .heading_string2 = {}, + .render_width = 0, + .render_height = 0, +}; + bool g_GF_RemoveAmmo = false; bool g_GF_RemoveWeapons = false; +int16_t g_GF_NumSecrets = 3; int32_t g_GF_LaraStartAnim; int32_t g_GF_ScriptVersion; +int32_t g_SavedGames; +char g_ValidLevelStrings1[MAX_LEVELS][50]; +char g_ValidLevelStrings2[MAX_LEVELS][50]; +uint32_t g_RequesterFlags1[MAX_REQUESTER_ITEMS]; +uint32_t g_RequesterFlags2[MAX_REQUESTER_ITEMS]; +int32_t g_SaveCounter; +int16_t g_SavedLevels[MAX_LEVELS] = { -1, 0 }; + XYZ_32 g_InteractPosition = { .x = 0, .y = 0, .z = 0 }; bool g_DetonateAllMines = false; diff --git a/src/tr2/global/vars.h b/src/tr2/global/vars.h index bcfa815fb..8d7b5c582 100644 --- a/src/tr2/global/vars.h +++ b/src/tr2/global/vars.h @@ -13,7 +13,9 @@ extern const float g_RhwFactor; extern SDL_Window *g_SDLWindow; extern uint32_t g_PerspectiveDistance; +extern uint32_t g_AssaultBestTime; extern int32_t g_OverlayStatus; +extern bool g_GymInvOpenEnabled; extern int32_t g_MidSort; extern int32_t g_PhdWinTop; extern float g_FltWinBottom; @@ -45,27 +47,44 @@ extern float g_FltPersp; extern int16_t *g_Info3DPtr; extern int32_t g_PhdWinWidth; extern int32_t g_PhdViewDistance; -extern PHD_VBUF *g_PhdVBuf; +extern PHD_VBUF g_PhdVBuf[]; extern float g_FltWinRight; extern int32_t g_PhdWinRight; extern int32_t g_SurfaceCount; extern SORT_ITEM *g_Sort3DPtr; +extern bool g_IsDemoLoaded; +extern bool g_IsAssaultTimerDisplay; +extern bool g_IsAssaultTimerActive; +extern bool g_IsMonkAngry; extern uint16_t g_SoundOptionLine; +extern ASSAULT_STATS g_Assault; extern int32_t g_HealthBarTimer; extern int32_t g_LevelComplete; +extern SAVEGAME_INFO g_SaveGame; extern LARA_INFO g_Lara; extern ITEM *g_LaraItem; extern CREATURE *g_BaddieSlots; extern bool g_CameraUnderwater; +extern char g_LevelFileName[256]; extern WEAPON_INFO g_Weapons[]; extern int16_t g_FinalBossActive; -extern uint16_t g_FinalLevelCount; +extern int16_t g_FinalLevelCount; extern int16_t g_FinalBossCount; extern int16_t g_FinalBossItem[5]; +extern REQUEST_INFO g_LoadGameRequester; +extern REQUEST_INFO g_SaveGameRequester; extern bool g_GF_RemoveAmmo; extern bool g_GF_RemoveWeapons; +extern int16_t g_GF_NumSecrets; extern int32_t g_GF_LaraStartAnim; +extern int32_t g_SavedGames; +extern char g_ValidLevelStrings1[MAX_LEVELS][50]; +extern char g_ValidLevelStrings2[MAX_LEVELS][50]; +extern uint32_t g_RequesterFlags1[MAX_REQUESTER_ITEMS]; +extern uint32_t g_RequesterFlags2[MAX_REQUESTER_ITEMS]; +extern int32_t g_SaveCounter; +extern int16_t g_SavedLevels[MAX_LEVELS]; extern XYZ_32 g_InteractPosition; extern bool g_DetonateAllMines; diff --git a/src/tr2/meson.build b/src/tr2/meson.build index b3d748095..c7bcfb8b4 100644 --- a/src/tr2/meson.build +++ b/src/tr2/meson.build @@ -34,7 +34,6 @@ build_opts = [ # end of common options '-ffile-prefix-map=@0@/='.format(relative_dir), '-DTR_VERSION=2', - '-DDEBUG=' + (get_option('buildtype') == 'debug' ? '1' : '0'), ] + trx.get_variable('defines') add_project_arguments(build_opts, language: 'c') @@ -90,6 +89,7 @@ sources = [ init, 'decomp/decomp.c', 'decomp/flares.c', + 'decomp/savegame.c', 'decomp/skidoo.c', 'game/camera.c', 'game/clock.c', @@ -110,7 +110,6 @@ sources = [ 'game/gun/gun_misc.c', 'game/gun/gun_pistols.c', 'game/gun/gun_rifle.c', - 'game/gym.c', 'game/input.c', 'game/inventory.c', 'game/inventory_ring/control.c', @@ -189,12 +188,18 @@ sources = [ 'game/objects/general/bell.c', 'game/objects/general/big_bowl.c', 'game/objects/general/bird_tweeter.c', + 'game/objects/general/bridge_common.c', + 'game/objects/general/bridge_flat.c', + 'game/objects/general/bridge_tilt_1.c', + 'game/objects/general/bridge_tilt_2.c', 'game/objects/general/camera_target.c', 'game/objects/general/clock_chimes.c', 'game/objects/general/copter.c', 'game/objects/general/cutscene_player.c', 'game/objects/general/detonator.c', 'game/objects/general/ding_dong.c', + 'game/objects/general/door.c', + 'game/objects/general/drawbridge.c', 'game/objects/general/earthquake.c', 'game/objects/general/final_cutscene.c', 'game/objects/general/final_level_counter.c', @@ -212,6 +217,7 @@ sources = [ 'game/objects/general/puzzle_hole.c', 'game/objects/general/sphere_of_doom.c', 'game/objects/general/switch.c', + 'game/objects/general/trapdoor.c', 'game/objects/general/waterfall.c', 'game/objects/general/window.c', 'game/objects/general/zipline.c', @@ -255,19 +261,26 @@ sources = [ 'game/render/hwr.c', 'game/render/priv.c', 'game/render/swr.c', + 'game/requester.c', 'game/room.c', 'game/room_draw.c', 'game/savegame/common.c', - 'game/savegame/savegame_bson.c', - 'game/savegame/savegame_legacy.c', + 'game/scaler.c', 'game/shell/common.c', 'game/shell/input.c', 'game/sound.c', 'game/spawn.c', 'game/stats.c', 'game/text.c', - 'game/ui/dialogs/graphic_settings.c', - 'game/ui/dialogs/stats.c', + 'game/ui/common.c', + 'game/ui/controllers/controls.c', + 'game/ui/widgets/controls_backend_selector.c', + 'game/ui/widgets/controls_column.c', + 'game/ui/widgets/controls_dialog.c', + 'game/ui/widgets/controls_input_selector.c', + 'game/ui/widgets/controls_layout_editor.c', + 'game/ui/widgets/controls_layout_selector.c', + 'game/ui/widgets/stats_dialog.c', 'game/viewport.c', 'global/enum_map.c', 'global/vars.c', diff --git a/tools/additional_lint b/tools/additional_lint index d57ea9c55..1fe5f1acd 100755 --- a/tools/additional_lint +++ b/tools/additional_lint @@ -24,8 +24,6 @@ def filter_files( files: Iterable[Path], ignored_patterns: list[str] | None, debug: bool ) -> Iterable[Path]: for path in files: - if not path.exists(): - continue if path.is_dir(): continue if is_binary_file(path): diff --git a/tools/config/.gitignore b/tools/config/.gitignore index 641a9bade..7645b76b5 100644 --- a/tools/config/.gitignore +++ b/tools/config/.gitignore @@ -13,4 +13,3 @@ *.vcxproj *.filters *.pubxml -[Oo]ut/ diff --git a/tools/config/TRX_ConfigTool.sln b/tools/config/TRX_ConfigToolLib.sln similarity index 53% rename from tools/config/TRX_ConfigTool.sln rename to tools/config/TRX_ConfigToolLib.sln index c756d9111..efff011e3 100644 --- a/tools/config/TRX_ConfigTool.sln +++ b/tools/config/TRX_ConfigToolLib.sln @@ -5,10 +5,6 @@ VisualStudioVersion = 17.11.35219.272 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TRX_ConfigToolLib", "TRX_ConfigToolLib\TRX_ConfigToolLib.csproj", "{27F08E8C-2910-4682-B8BC-96ED4C1ECE54}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TR1X_ConfigTool", "TR1X_ConfigTool\TR1X_ConfigTool.csproj", "{A19A8ED1-D703-4954-B0F2-8E651453EB3E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TR2X_ConfigTool", "TR2X_ConfigTool\TR2X_ConfigTool.csproj", "{9FAE4BC6-08E0-46AB-9E29-6FED79D60733}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -19,14 +15,6 @@ Global {27F08E8C-2910-4682-B8BC-96ED4C1ECE54}.Debug|Any CPU.Build.0 = Debug|Any CPU {27F08E8C-2910-4682-B8BC-96ED4C1ECE54}.Release|Any CPU.ActiveCfg = Release|Any CPU {27F08E8C-2910-4682-B8BC-96ED4C1ECE54}.Release|Any CPU.Build.0 = Release|Any CPU - {A19A8ED1-D703-4954-B0F2-8E651453EB3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A19A8ED1-D703-4954-B0F2-8E651453EB3E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A19A8ED1-D703-4954-B0F2-8E651453EB3E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A19A8ED1-D703-4954-B0F2-8E651453EB3E}.Release|Any CPU.Build.0 = Release|Any CPU - {9FAE4BC6-08E0-46AB-9E29-6FED79D60733}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9FAE4BC6-08E0-46AB-9E29-6FED79D60733}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9FAE4BC6-08E0-46AB-9E29-6FED79D60733}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9FAE4BC6-08E0-46AB-9E29-6FED79D60733}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/tools/config/TRX_ConfigToolLib/Controls/AboutWindow.xaml b/tools/config/TRX_ConfigToolLib/Controls/AboutWindow.xaml index 73f1b237a..888098dec 100644 --- a/tools/config/TRX_ConfigToolLib/Controls/AboutWindow.xaml +++ b/tools/config/TRX_ConfigToolLib/Controls/AboutWindow.xaml @@ -4,9 +4,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:models="clr-namespace:TRX_ConfigToolLib.Models" mc:Ignorable="d" - d:DataContext="{d:DesignInstance Type=models:AboutWindowViewModel}" Title="{Binding ViewText[window_title_about]}" SizeToContent="WidthAndHeight" WindowStartupLocation="CenterOwner" diff --git a/tools/config/TRX_ConfigToolLib/Controls/CategoryControl.xaml b/tools/config/TRX_ConfigToolLib/Controls/CategoryControl.xaml index f8449e4e4..c5619156b 100644 --- a/tools/config/TRX_ConfigToolLib/Controls/CategoryControl.xaml +++ b/tools/config/TRX_ConfigToolLib/Controls/CategoryControl.xaml @@ -5,9 +5,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:controls="clr-namespace:TRX_ConfigToolLib.Controls" - xmlns:models="clr-namespace:TRX_ConfigToolLib.Models" mc:Ignorable="d" - d:DataContext="{d:DesignInstance Type=models:CategoryViewModel}" d:DesignHeight="450" d:DesignWidth="800"> diff --git a/tools/config/TRX_ConfigToolLib/Controls/PropertyControl.xaml b/tools/config/TRX_ConfigToolLib/Controls/PropertyControl.xaml index dec47a92b..4e6914d52 100644 --- a/tools/config/TRX_ConfigToolLib/Controls/PropertyControl.xaml +++ b/tools/config/TRX_ConfigToolLib/Controls/PropertyControl.xaml @@ -5,11 +5,9 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:controls="clr-namespace:TRX_ConfigToolLib.Controls" - xmlns:models="clr-namespace:TRX_ConfigToolLib.Models.Specification" - xmlns:types="clr-namespace:TRX_ConfigToolLib.Models.Specification.Types" - xmlns:converters="clr-namespace:TRX_ConfigToolLib.Utils.Converters" + xmlns:models="clr-namespace:TRX_ConfigToolLib.Models" + xmlns:utils="clr-namespace:TRX_ConfigToolLib.Utils" mc:Ignorable="d" - d:DataContext="{d:DesignInstance Type=models:BaseProperty}" d:DesignHeight="450" d:DesignWidth="800"> @@ -18,11 +16,11 @@ - - @@ -45,14 +43,14 @@ HorizontalAlignment="Left" Visibility="{Binding IsEnabled, Converter={StaticResource BoolToCollapsedConverter}}"> - + - + - + - - - @@ -129,7 +126,7 @@ Grid.Column="1" BorderBrush="#666" BorderThickness="1" - Background="{Binding SearchFailStatus, Converter={converters:ConditionalMarkupConverter TrueValue='#FFC7CE', FalseValue='White'}}"> + Background="{Binding SearchFailStatus, Converter={utils:ConditionalMarkupConverter TrueValue='#FFC7CE', FalseValue='White'}}"> @@ -204,8 +201,8 @@ Visibility="{Binding IsEditorActive, Converter={StaticResource BoolToHiddenConverter}}"/> diff --git a/tools/config/TRX_ConfigToolLib/Models/BaseLanguageViewModel.cs b/tools/config/TRX_ConfigToolLib/Models/BaseLanguageViewModel.cs index 385e3aa27..cff2ea8d5 100644 --- a/tools/config/TRX_ConfigToolLib/Models/BaseLanguageViewModel.cs +++ b/tools/config/TRX_ConfigToolLib/Models/BaseLanguageViewModel.cs @@ -1,7 +1,4 @@ -using TRX_ConfigToolLib.Models.Lang; -using TRX_ConfigToolLib.Utils; - -namespace TRX_ConfigToolLib.Models; +namespace TRX_ConfigToolLib.Models; public class BaseLanguageViewModel : BaseNotifyPropertyChanged { diff --git a/tools/config/TRX_ConfigToolLib/Models/CategoryViewModel.cs b/tools/config/TRX_ConfigToolLib/Models/CategoryViewModel.cs index 94dac58d1..fc56a2867 100644 --- a/tools/config/TRX_ConfigToolLib/Models/CategoryViewModel.cs +++ b/tools/config/TRX_ConfigToolLib/Models/CategoryViewModel.cs @@ -1,5 +1,4 @@ -using TRX_ConfigToolLib.Models.Specification; -using TRX_ConfigToolLib.Utils; +using TRX_ConfigToolLib.Utils; namespace TRX_ConfigToolLib.Models; diff --git a/tools/config/TRX_ConfigToolLib/Models/Lang/Language.cs b/tools/config/TRX_ConfigToolLib/Models/Lang/Language.cs index 1b8e6139d..2c44763b3 100644 --- a/tools/config/TRX_ConfigToolLib/Models/Lang/Language.cs +++ b/tools/config/TRX_ConfigToolLib/Models/Lang/Language.cs @@ -3,7 +3,7 @@ using Newtonsoft.Json.Linq; using System.Globalization; using TRX_ConfigToolLib.Utils; -namespace TRX_ConfigToolLib.Models.Lang; +namespace TRX_ConfigToolLib.Models; public class Language { diff --git a/tools/config/TRX_ConfigToolLib/Models/Lang/PropertyText.cs b/tools/config/TRX_ConfigToolLib/Models/Lang/PropertyText.cs index 3f300118f..c11a006c2 100644 --- a/tools/config/TRX_ConfigToolLib/Models/Lang/PropertyText.cs +++ b/tools/config/TRX_ConfigToolLib/Models/Lang/PropertyText.cs @@ -1,4 +1,4 @@ -namespace TRX_ConfigToolLib.Models.Lang; +namespace TRX_ConfigToolLib.Models; public class PropertyText { diff --git a/tools/config/TRX_ConfigToolLib/Models/MainWindowViewModel.cs b/tools/config/TRX_ConfigToolLib/Models/MainWindowViewModel.cs index e93e9d8f5..45266f259 100644 --- a/tools/config/TRX_ConfigToolLib/Models/MainWindowViewModel.cs +++ b/tools/config/TRX_ConfigToolLib/Models/MainWindowViewModel.cs @@ -4,7 +4,6 @@ using System.IO; using System.Windows; using System.Windows.Input; using TRX_ConfigToolLib.Controls; -using TRX_ConfigToolLib.Models.Specification; using TRX_ConfigToolLib.Utils; namespace TRX_ConfigToolLib.Models; diff --git a/tools/config/TRX_ConfigToolLib/Models/Specification/BaseProperty.cs b/tools/config/TRX_ConfigToolLib/Models/Specification/BaseProperty.cs index 79f671dd6..2b4b2a2fe 100644 --- a/tools/config/TRX_ConfigToolLib/Models/Specification/BaseProperty.cs +++ b/tools/config/TRX_ConfigToolLib/Models/Specification/BaseProperty.cs @@ -1,7 +1,6 @@ -using TRX_ConfigToolLib.Models.Lang; -using TRX_ConfigToolLib.Utils; +using TRX_ConfigToolLib.Utils; -namespace TRX_ConfigToolLib.Models.Specification; +namespace TRX_ConfigToolLib.Models; public abstract class BaseProperty : BaseNotifyPropertyChanged { diff --git a/tools/config/TRX_ConfigToolLib/Models/Specification/Category.cs b/tools/config/TRX_ConfigToolLib/Models/Specification/Category.cs index 3600a78d6..317cb502e 100644 --- a/tools/config/TRX_ConfigToolLib/Models/Specification/Category.cs +++ b/tools/config/TRX_ConfigToolLib/Models/Specification/Category.cs @@ -1,6 +1,4 @@ -using TRX_ConfigToolLib.Models.Lang; - -namespace TRX_ConfigToolLib.Models.Specification; +namespace TRX_ConfigToolLib.Models; public class Category { diff --git a/tools/config/TRX_ConfigToolLib/Models/Specification/Configuration.cs b/tools/config/TRX_ConfigToolLib/Models/Specification/Configuration.cs index 202b323b8..f9ca4e30a 100644 --- a/tools/config/TRX_ConfigToolLib/Models/Specification/Configuration.cs +++ b/tools/config/TRX_ConfigToolLib/Models/Specification/Configuration.cs @@ -2,9 +2,8 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System.IO; using TRX_ConfigToolLib.Utils; -using TRX_ConfigToolLib.Utils.Json; -namespace TRX_ConfigToolLib.Models.Specification; +namespace TRX_ConfigToolLib.Models; public class Configuration { diff --git a/tools/config/TRX_ConfigToolLib/Models/Specification/DataType.cs b/tools/config/TRX_ConfigToolLib/Models/Specification/DataType.cs index d807329ee..3766f58ee 100644 --- a/tools/config/TRX_ConfigToolLib/Models/Specification/DataType.cs +++ b/tools/config/TRX_ConfigToolLib/Models/Specification/DataType.cs @@ -1,4 +1,4 @@ -namespace TRX_ConfigToolLib.Models.Specification; +namespace TRX_ConfigToolLib.Models; public enum DataType { diff --git a/tools/config/TRX_ConfigToolLib/Models/Specification/EnumOption.cs b/tools/config/TRX_ConfigToolLib/Models/Specification/EnumOption.cs index f80eaad37..5b8ed1107 100644 --- a/tools/config/TRX_ConfigToolLib/Models/Specification/EnumOption.cs +++ b/tools/config/TRX_ConfigToolLib/Models/Specification/EnumOption.cs @@ -1,6 +1,4 @@ -using TRX_ConfigToolLib.Models.Lang; - -namespace TRX_ConfigToolLib.Models.Specification; +namespace TRX_ConfigToolLib.Models; public class EnumOption { diff --git a/tools/config/TRX_ConfigToolLib/Models/Specification/Specification.cs b/tools/config/TRX_ConfigToolLib/Models/Specification/Specification.cs index 2a6ba7af5..43da7c44f 100644 --- a/tools/config/TRX_ConfigToolLib/Models/Specification/Specification.cs +++ b/tools/config/TRX_ConfigToolLib/Models/Specification/Specification.cs @@ -1,8 +1,8 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using TRX_ConfigToolLib.Utils.Json; +using TRX_ConfigToolLib.Utils; -namespace TRX_ConfigToolLib.Models.Specification; +namespace TRX_ConfigToolLib.Models; public class Specification { diff --git a/tools/config/TRX_ConfigToolLib/Models/Specification/Types/BoolProperty.cs b/tools/config/TRX_ConfigToolLib/Models/Specification/Types/BoolProperty.cs index 3b7bc6eda..32ed4b4aa 100644 --- a/tools/config/TRX_ConfigToolLib/Models/Specification/Types/BoolProperty.cs +++ b/tools/config/TRX_ConfigToolLib/Models/Specification/Types/BoolProperty.cs @@ -1,4 +1,4 @@ -namespace TRX_ConfigToolLib.Models.Specification.Types; +namespace TRX_ConfigToolLib.Models; public class BoolProperty : BaseProperty { diff --git a/tools/config/TRX_ConfigToolLib/Models/Specification/Types/EnumProperty.cs b/tools/config/TRX_ConfigToolLib/Models/Specification/Types/EnumProperty.cs index 45b105f03..4da283292 100644 --- a/tools/config/TRX_ConfigToolLib/Models/Specification/Types/EnumProperty.cs +++ b/tools/config/TRX_ConfigToolLib/Models/Specification/Types/EnumProperty.cs @@ -1,4 +1,4 @@ -namespace TRX_ConfigToolLib.Models.Specification.Types; +namespace TRX_ConfigToolLib.Models; public class EnumProperty : BaseProperty { diff --git a/tools/config/TRX_ConfigToolLib/Models/Specification/Types/NumericProperty.cs b/tools/config/TRX_ConfigToolLib/Models/Specification/Types/NumericProperty.cs index 4c4e15344..37422a000 100644 --- a/tools/config/TRX_ConfigToolLib/Models/Specification/Types/NumericProperty.cs +++ b/tools/config/TRX_ConfigToolLib/Models/Specification/Types/NumericProperty.cs @@ -1,4 +1,4 @@ -namespace TRX_ConfigToolLib.Models.Specification.Types; +namespace TRX_ConfigToolLib.Models; public class NumericProperty : BaseProperty { diff --git a/tools/config/TRX_ConfigToolLib/Resources/Lang/en.json b/tools/config/TRX_ConfigToolLib/Resources/Lang/en.json index 5efe27e19..bdfc136a1 100644 --- a/tools/config/TRX_ConfigToolLib/Resources/Lang/en.json +++ b/tools/config/TRX_ConfigToolLib/Resources/Lang/en.json @@ -44,11 +44,6 @@ "jpg": "JPEG", "png": "PNG" }, - "music_load_condition": { - "never": "Never", - "non-ambient": "Non-ambient", - "always": "Always" - }, "underwater_music": { "full": "Full", "quiet": "Quiet", diff --git a/tools/config/TRX_ConfigToolLib/Resources/Lang/it.json b/tools/config/TRX_ConfigToolLib/Resources/Lang/it.json index 7cce646e4..bf5a3dd09 100644 --- a/tools/config/TRX_ConfigToolLib/Resources/Lang/it.json +++ b/tools/config/TRX_ConfigToolLib/Resources/Lang/it.json @@ -38,19 +38,5 @@ "graphics": "Grafica", "sound": "Suono", "ui": "Interfaccia utente" - }, - "Enums": { - "underwater_music": { - "full": "Completa", - "quiet": "Attenuata", - "full_no_ambient": "Completa ma senza suoni ambientali", - "quiet_no_ambient": "Attenuata ma senza suoni ambientali", - "none": "Assente" - }, - "music_load_condition": { - "never": "Mai", - "non-ambient": "Non ambientale", - "always": "Sempre" - } } } diff --git a/tools/config/TRX_ConfigToolLib/TRX_ConfigToolLib.csproj b/tools/config/TRX_ConfigToolLib/TRX_ConfigToolLib.csproj index 42fc35259..97545b5a8 100644 --- a/tools/config/TRX_ConfigToolLib/TRX_ConfigToolLib.csproj +++ b/tools/config/TRX_ConfigToolLib/TRX_ConfigToolLib.csproj @@ -5,7 +5,6 @@ true enable false - true diff --git a/tools/config/TRX_ConfigToolLib/Utils/BaseNotifyPropertyChanged.cs b/tools/config/TRX_ConfigToolLib/Utils/BaseNotifyPropertyChanged.cs index c5eef5ecc..3bdaa5c1f 100644 --- a/tools/config/TRX_ConfigToolLib/Utils/BaseNotifyPropertyChanged.cs +++ b/tools/config/TRX_ConfigToolLib/Utils/BaseNotifyPropertyChanged.cs @@ -1,7 +1,7 @@ using System.ComponentModel; using System.Runtime.CompilerServices; -namespace TRX_ConfigToolLib.Utils; +namespace TRX_ConfigToolLib; public abstract class BaseNotifyPropertyChanged : INotifyPropertyChanged { diff --git a/tools/config/TRX_ConfigToolLib/Utils/Converters/BoolToVisibilityConverter.cs b/tools/config/TRX_ConfigToolLib/Utils/Converters/BoolToVisibilityConverter.cs index a443fba96..2bf988719 100644 --- a/tools/config/TRX_ConfigToolLib/Utils/Converters/BoolToVisibilityConverter.cs +++ b/tools/config/TRX_ConfigToolLib/Utils/Converters/BoolToVisibilityConverter.cs @@ -2,7 +2,7 @@ using System.Windows; using System.Windows.Data; -namespace TRX_ConfigToolLib.Utils.Converters; +namespace TRX_ConfigToolLib.Utils; [ValueConversion(typeof(bool), typeof(Visibility))] public class BoolToVisibilityConverter : IValueConverter diff --git a/tools/config/TRX_ConfigToolLib/Utils/Converters/ConditionalMarkupConverter.cs b/tools/config/TRX_ConfigToolLib/Utils/Converters/ConditionalMarkupConverter.cs index 847a691bf..2ec09526a 100644 --- a/tools/config/TRX_ConfigToolLib/Utils/Converters/ConditionalMarkupConverter.cs +++ b/tools/config/TRX_ConfigToolLib/Utils/Converters/ConditionalMarkupConverter.cs @@ -2,7 +2,7 @@ using System.Globalization; using System.Windows.Data; using System.Windows.Markup; -namespace TRX_ConfigToolLib.Utils.Converters; +namespace TRX_ConfigToolLib.Utils; public class ConditionalMarkupConverter : MarkupExtension, IValueConverter { diff --git a/tools/config/TRX_ConfigToolLib/Utils/Converters/ConditionalViewTextConverter.cs b/tools/config/TRX_ConfigToolLib/Utils/Converters/ConditionalViewTextConverter.cs index da0348b87..f350b5c0f 100644 --- a/tools/config/TRX_ConfigToolLib/Utils/Converters/ConditionalViewTextConverter.cs +++ b/tools/config/TRX_ConfigToolLib/Utils/Converters/ConditionalViewTextConverter.cs @@ -1,7 +1,7 @@ using System.Globalization; -using TRX_ConfigToolLib.Models.Lang; +using TRX_ConfigToolLib.Models; -namespace TRX_ConfigToolLib.Utils.Converters; +namespace TRX_ConfigToolLib.Utils; public class ConditionalViewTextConverter : ConditionalMarkupConverter { diff --git a/tools/config/TRX_ConfigToolLib/Utils/Json/NumericConverter.cs b/tools/config/TRX_ConfigToolLib/Utils/Json/NumericConverter.cs index a386315a7..f0a288592 100644 --- a/tools/config/TRX_ConfigToolLib/Utils/Json/NumericConverter.cs +++ b/tools/config/TRX_ConfigToolLib/Utils/Json/NumericConverter.cs @@ -1,7 +1,7 @@ using Newtonsoft.Json; using System.Globalization; -namespace TRX_ConfigToolLib.Utils.Json; +namespace TRX_ConfigToolLib.Utils; public class NumericConverter : JsonConverter { diff --git a/tools/config/TRX_ConfigToolLib/Utils/Json/PropertyConverter.cs b/tools/config/TRX_ConfigToolLib/Utils/Json/PropertyConverter.cs index 084a6655e..53b87d08c 100644 --- a/tools/config/TRX_ConfigToolLib/Utils/Json/PropertyConverter.cs +++ b/tools/config/TRX_ConfigToolLib/Utils/Json/PropertyConverter.cs @@ -1,9 +1,8 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using TRX_ConfigToolLib.Models.Specification; -using TRX_ConfigToolLib.Models.Specification.Types; +using TRX_ConfigToolLib.Models; -namespace TRX_ConfigToolLib.Utils.Json; +namespace TRX_ConfigToolLib.Utils; public class PropertyConverter : JsonConverter { diff --git a/tools/config/TRX_ConfigToolLib/Utils/Json/PropertyResolver.cs b/tools/config/TRX_ConfigToolLib/Utils/Json/PropertyResolver.cs index cb0f05469..038cacaca 100644 --- a/tools/config/TRX_ConfigToolLib/Utils/Json/PropertyResolver.cs +++ b/tools/config/TRX_ConfigToolLib/Utils/Json/PropertyResolver.cs @@ -1,8 +1,8 @@ using Newtonsoft.Json; using Newtonsoft.Json.Serialization; -using TRX_ConfigToolLib.Models.Specification; +using TRX_ConfigToolLib.Models; -namespace TRX_ConfigToolLib.Utils.Json; +namespace TRX_ConfigToolLib.Utils; public class PropertyResolver : DefaultContractResolver { diff --git a/tools/download_assets b/tools/download_assets deleted file mode 100755 index 6b20c3b9f..000000000 --- a/tools/download_assets +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import shutil -import sys -import tempfile -from pathlib import Path -from urllib.request import Request, urlopen -from zipfile import ZipFile - -from shared.paths import PROJECT_PATHS - - -def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser( - description=( - "Downloads large binary assets into local " - "data/*/ship/ directories." - ) - ) - parser.add_argument( - "game_version", choices=["1", "2", "all"], default="all", nargs="?" - ) - return parser.parse_args() - - -def download_to_file(url: str, path: Path) -> None: - print(f"Downloading {url}...") - req = Request(url, headers={"User-Agent": "download_assets"}) - with urlopen(req) as response: - if getattr(response, "status", None) not in (None, 200): - sys.exit( - f"Error: failed to download {url}. Status: {response.status}" - ) - with path.open("wb") as f: - shutil.copyfileobj(response, f) - - -def extract_zip(zip_path: Path, dest_dir: Path) -> None: - print(f"Extracting {zip_path} to {dest_dir}...") - dest_dir.mkdir(parents=True, exist_ok=True) - with ZipFile(zip_path) as z: - z.extractall(dest_dir) - - -def download_assets(asset_urls: list[str], target_dir: Path) -> None: - with tempfile.TemporaryDirectory() as tmpdir_str: - tmpdir = Path(tmpdir_str) - for url in asset_urls: - filename = Path(url).name - local_zip = tmpdir / filename - download_to_file(url, local_zip) - extract_zip(local_zip, target_dir) - print("Asset download and extraction complete.") - - -def main() -> None: - args = parse_args() - asset_urls_map: dict[int, list[str]] = { - 1: ["https://lostartefacts.dev/aux/tr1x/main.zip"], - 2: [ - "https://lostartefacts.dev/aux/tr2x/main.zip", - "https://lostartefacts.dev/aux/tr2x/trgm.zip", - ], - } - - versions = {"1": [1], "2": [2], "all": [1, 2]}[args.game_version] - for version in versions: - download_assets( - asset_urls_map[version], - target_dir=PROJECT_PATHS[version].shipped_data_dir, - ) - - -if __name__ == "__main__": - main() diff --git a/tools/installer/TR1X_Installer/App.xaml b/tools/installer/TR1X_Installer/App.xaml deleted file mode 100644 index ebf518429..000000000 --- a/tools/installer/TR1X_Installer/App.xaml +++ /dev/null @@ -1,4 +0,0 @@ - diff --git a/tools/installer/TR1X_Installer/App.xaml.cs b/tools/installer/TR1X_Installer/App.xaml.cs deleted file mode 100644 index 05615d2ef..000000000 --- a/tools/installer/TR1X_Installer/App.xaml.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Windows; -using TR1X_Installer.Installers; -using TRX_InstallerLib.Controls; -using TRX_InstallerLib.Installers; - -namespace TR1X_Installer; - -public partial class App : Application -{ - public App() - { - Current.MainWindow = new TRXInstallWindow(new List - { - new SteamInstallSource(), - new GOGInstallSource(), - new TombATIInstallSource(), - new CDRomInstallSource(), - new TR1XInstallSource(), - }); - Current.MainWindow.Show(); - } -} diff --git a/tools/installer/TR1X_Installer/Resources/Lang/en.json b/tools/installer/TR1X_Installer/Resources/Lang/en.json deleted file mode 100644 index 5eec7f72f..000000000 --- a/tools/installer/TR1X_Installer/Resources/Lang/en.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "Controls": { - "window_title_main": "TR1X Installer", - "step_source_content": "TR1X requires original game files to run.\nPlease choose the source location where to install the data files from.\nIf you're upgrading an existing installation, please choose TR1X.", - "step_settings_music_content": "Neither the Steam nor GOG versions of the game ship with the full soundtrack found on the PlayStation or Saturn retail releases. This option lets you download the missing tracks automatically (164 MB). The legality of these files is disputable; the most legal way to import the music to PC is to rip the audio tracks yourself from a physical PlayStation or Saturn disc.", - "step_settings_expansion_heading": "Download Unfinished Business expansion pack", - "step_settings_expansion_content": "The Unfinished Business expansion pack was made freeware. However, the Steam and GOG versions do not ship it. This option lets you download the expansion files automatically (6 MB).", - "step_settings_expansion_music": "Fan-made edition (includes music triggers)", - "step_settings_expansion_vanilla": "Original edition (does not include music triggers)", - "step_settings_saves_content": "Imports existing savegame files. Only TombATI and TR1X savegame format is supported at this time." - } -} diff --git a/tools/installer/TR1X_Installer/Resources/const.json b/tools/installer/TR1X_Installer/Resources/const.json deleted file mode 100644 index 4e22a53f8..000000000 --- a/tools/installer/TR1X_Installer/Resources/const.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "Game": "TR1X", - "GoldGame": "TR1X - UB", - "GoldFileIdentifier": "cat.phd", - "AllowExpansionTypeSelection": true, - "ShortcutTitle": "Tomb Raider I: Community Edition", - "GoldZips": { - "0": "trub-music.zip", - "1": "trub-vanilla.zip" - } -} diff --git a/tools/installer/TR1X_Installer/TR1X_Installer.csproj b/tools/installer/TR1X_Installer/TR1X_Installer.csproj deleted file mode 100644 index cb8b6781e..000000000 --- a/tools/installer/TR1X_Installer/TR1X_Installer.csproj +++ /dev/null @@ -1,56 +0,0 @@ - - - WinExe - net6.0-windows - enable - enable - true - false - - true - TR1X_Installer - True - true - true - false - true - false - false - win-x64 - Resources\icon.ico - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tools/installer/TR2X_Installer/App.xaml b/tools/installer/TR2X_Installer/App.xaml deleted file mode 100644 index e78943c52..000000000 --- a/tools/installer/TR2X_Installer/App.xaml +++ /dev/null @@ -1,4 +0,0 @@ - diff --git a/tools/installer/TR2X_Installer/App.xaml.cs b/tools/installer/TR2X_Installer/App.xaml.cs deleted file mode 100644 index c2df3c146..000000000 --- a/tools/installer/TR2X_Installer/App.xaml.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Windows; -using TR2X_Installer.Installers; -using TRX_InstallerLib.Controls; -using TRX_InstallerLib.Installers; - -namespace TR2X_Installer; - -public partial class App : Application -{ - public App() - { - Current.MainWindow = new TRXInstallWindow(new List - { - new SteamInstallSource(), - new GOGInstallSource(), - new CDRomInstallSource(), - new TR2XInstallSource(), - }); - Current.MainWindow.Show(); - } -} diff --git a/tools/installer/TR2X_Installer/Installers/CDRomInstallSource.cs b/tools/installer/TR2X_Installer/Installers/CDRomInstallSource.cs deleted file mode 100644 index f67e23be2..000000000 --- a/tools/installer/TR2X_Installer/Installers/CDRomInstallSource.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.IO; - -namespace TR2X_Installer.Installers; - -public class CDRomInstallSource : GenericInstallSource -{ - public override IEnumerable DirectoriesToTry - { - get - { - DriveInfo[] allDrives = DriveInfo.GetDrives(); - foreach (var drive in allDrives) - { - if (drive.DriveType == DriveType.CDRom && drive.IsReady) - { - yield return drive.RootDirectory.FullName; - } - } - } - } - - public override bool IsImportingSavesSupported => false; - public override string SourceName => "CDRom"; - - public override bool IsGameFound(string sourceDirectory) - { - return File.Exists(Path.Combine(sourceDirectory, "fmv", "ancient.rpl")) - && File.Exists(Path.Combine(sourceDirectory, "data", "wall.tr2")) - && File.Exists(Path.Combine(sourceDirectory, "data", "main.sfx")); - } -} diff --git a/tools/installer/TR2X_Installer/Installers/GOGInstallSource.cs b/tools/installer/TR2X_Installer/Installers/GOGInstallSource.cs deleted file mode 100644 index 248bb5c34..000000000 --- a/tools/installer/TR2X_Installer/Installers/GOGInstallSource.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Microsoft.Win32; -using System.IO; -using System.Text.RegularExpressions; - -namespace TR2X_Installer.Installers; - -public class GOGInstallSource : GenericInstallSource -{ - public override IEnumerable DirectoriesToTry - { - get - { - yield return @"C:\Program Files (x86)\GOG Galaxy\Games\Tomb Raider 2"; - - using var key = Registry.ClassesRoot.OpenSubKey(@"goggalaxy\shell\open\command"); - if (key is not null) - { - var value = key.GetValue("")?.ToString(); - if (value is not null && new Regex(@"""(?[^""]+)""").Match(value) is { Success: true } match) - { - yield return Path.Combine(Path.GetDirectoryName(match.Groups["path"].Value)!, @"Games\Tomb Raider 2"); - } - } - } - } - - public override bool IsImportingSavesSupported => true; - public override string SourceName => "GOG"; - - public override bool IsGameFound(string sourceDirectory) - { - return File.Exists(Path.Combine(sourceDirectory, "tomb2.exe")) - && File.Exists(Path.Combine(sourceDirectory, "data", "wall.tr2")) - && File.Exists(Path.Combine(sourceDirectory, "data", "main.sfx")); - } -} diff --git a/tools/installer/TR2X_Installer/Installers/GenericInstallSource.cs b/tools/installer/TR2X_Installer/Installers/GenericInstallSource.cs deleted file mode 100644 index b7ed06ad7..000000000 --- a/tools/installer/TR2X_Installer/Installers/GenericInstallSource.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System.IO; -using System.Text.RegularExpressions; -using TRX_InstallerLib.Installers; -using TRX_InstallerLib.Utils; - -namespace TR2X_Installer.Installers; - -public abstract class GenericInstallSource : BaseInstallSource -{ - private static readonly Dictionary> _targetFiles = new() - { - ["data"] = new() { ".tr2", ".sfx", ".pcx" }, - ["fmv"] = new() { ".*" }, - ["music"] = new() { ".flac", ".ogg", ".mp3", ".wav" }, - }; - - public override bool IsDownloadingMusicNeeded(string sourceDirectory) - => true; - - public override bool IsDownloadingExpansionNeeded(string sourceDirectory) - => true; - - public override async Task CopyOriginalGameFiles( - string sourceDirectory, - string targetDirectory, - IProgress progress, - bool importSaves - ) - { - await InstallUtils.CopyDirectoryTree( - sourceDirectory, - targetDirectory, - progress, - file => IsMatch(sourceDirectory, file, importSaves), - path => ConvertTargetPath(path) - ); - - string musicDir = Path.Combine(targetDirectory, "music"); - string audioDir = Path.Combine(sourceDirectory, "audio"); - if ((Directory.Exists(musicDir) && Directory.EnumerateFiles(musicDir).Any()) || !Directory.Exists(audioDir)) - { - return; - } - - await InstallUtils.CopyDirectoryTree( - Path.Combine(sourceDirectory, "audio"), - Path.Combine(targetDirectory, "audio"), - progress, - null, - path => ConvertTargetPath(path) - ); - } - - private static bool IsMatch(string sourceDirectory, string path, bool importSaves) - { - string[] parts = Path.GetRelativePath(sourceDirectory, path).ToLower().Split('\\'); - if (parts.Length == 1 && importSaves && Regex.IsMatch(parts[0], @"savegame.\d+", RegexOptions.IgnoreCase)) - { - return true; - } - - return parts.Length > 0 - && _targetFiles.ContainsKey(parts[0]) - && (_targetFiles[parts[0]].Contains(".*") || _targetFiles[parts[0]].Contains(Path.GetExtension(path).ToLower())); - } -} diff --git a/tools/installer/TR2X_Installer/Installers/SteamInstallSource.cs b/tools/installer/TR2X_Installer/Installers/SteamInstallSource.cs deleted file mode 100644 index 901aaf640..000000000 --- a/tools/installer/TR2X_Installer/Installers/SteamInstallSource.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Microsoft.Win32; -using System.IO; - -namespace TR2X_Installer.Installers; - -public class SteamInstallSource : GOGInstallSource -{ - public override IEnumerable DirectoriesToTry - { - get - { - yield return @"C:\Program Files (x86)\Steam\steamapps\common\Tomb Raider (II)"; - - using var key = Registry.CurrentUser.OpenSubKey(@"Software\Valve\Steam"); - if (key is not null) - { - var value = key.GetValue("SteamPath")?.ToString(); - if (value is not null) - { - yield return Path.Combine(value, @"steamapps\common\Tomb Raider (II)"); - } - } - } - } - - public override string SourceName => "Steam"; -} diff --git a/tools/installer/TR2X_Installer/Installers/TR2XInstallSource.cs b/tools/installer/TR2X_Installer/Installers/TR2XInstallSource.cs deleted file mode 100644 index b18586e1b..000000000 --- a/tools/installer/TR2X_Installer/Installers/TR2XInstallSource.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System.IO; -using System.Text.RegularExpressions; -using TRX_InstallerLib.Installers; -using TRX_InstallerLib.Utils; - -namespace TR2X_Installer.Installers; - -public class TR2XInstallSource : GenericInstallSource -{ - public override IEnumerable DirectoriesToTry - { - get - { - var previousPath = InstallUtils.GetPreviousInstallationPath(); - if (previousPath is not null) - { - yield return previousPath; - } - - foreach (var path in InstallUtils.GetDesktopShortcutDirectories()) - { - yield return path; - } - } - } - - public override string SuggestedInstallationDirectory - { - get - { - return InstallUtils.GetPreviousInstallationPath() ?? base.SuggestedInstallationDirectory; - } - } - - public override bool IsImportingSavesSupported => true; - public override string SourceName => "TR2X"; - - public override async Task CopyOriginalGameFiles( - string sourceDirectory, - string targetDirectory, - IProgress progress, - bool importSaves - ) - { - var filterRegex = new Regex(importSaves ? @"(audio|data|fmv|music|saves)[\\/]|save.*\.\d+" : @"(audio|data|fmv|music)[\\/]", RegexOptions.IgnoreCase); - await InstallUtils.CopyDirectoryTree( - sourceDirectory, - targetDirectory, - progress, - file => filterRegex.IsMatch(file) - ); - } - - public override bool IsDownloadingExpansionNeeded(string sourceDirectory) - { - return !File.Exists(Path.Combine(sourceDirectory, "data", "title_gm.tr2")); - } - - public override bool IsGameFound(string sourceDirectory) - { - return File.Exists(Path.Combine(sourceDirectory, "TR2X.exe")); - } -} diff --git a/tools/installer/TR2X_Installer/Resources/CDRom.png b/tools/installer/TR2X_Installer/Resources/CDRom.png deleted file mode 100644 index 232d2a223..000000000 Binary files a/tools/installer/TR2X_Installer/Resources/CDRom.png and /dev/null differ diff --git a/tools/installer/TR2X_Installer/Resources/GOG.png b/tools/installer/TR2X_Installer/Resources/GOG.png deleted file mode 100644 index 43f2c381b..000000000 Binary files a/tools/installer/TR2X_Installer/Resources/GOG.png and /dev/null differ diff --git a/tools/installer/TR2X_Installer/Resources/Lang/en.json b/tools/installer/TR2X_Installer/Resources/Lang/en.json deleted file mode 100644 index 13f002c67..000000000 --- a/tools/installer/TR2X_Installer/Resources/Lang/en.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Controls": { - "window_title_main": "TR2X Installer", - "step_source_content": "TR2X requires original game files to run.\nPlease choose the source location where to install the data files from.\nIf you're upgrading an existing installation, please choose TR2X.", - "step_settings_music_content": "This option lets you download compatible music files for the game automatically (60 MB). The legality of these files is disputable; the most legal way to import the music to PC is to obtain them from your own source - TR2 supports FLAC, OGG, MP3 and WAV files.", - "step_settings_expansion_heading": "Download The Golden Mask expansion pack", - "step_settings_expansion_content": "The Golden Mask expansion pack was made freeware. However, the Steam and GOG versions do not ship it. This option lets you download the expansion files automatically (15 MB).", - "step_settings_saves_content": "Imports existing savegame files." - } -} diff --git a/tools/installer/TR2X_Installer/Resources/Steam.png b/tools/installer/TR2X_Installer/Resources/Steam.png deleted file mode 100644 index 267e8ad8c..000000000 Binary files a/tools/installer/TR2X_Installer/Resources/Steam.png and /dev/null differ diff --git a/tools/installer/TR2X_Installer/Resources/TR1X.png b/tools/installer/TR2X_Installer/Resources/TR1X.png deleted file mode 100644 index 1cd53660a..000000000 Binary files a/tools/installer/TR2X_Installer/Resources/TR1X.png and /dev/null differ diff --git a/tools/installer/TR2X_Installer/Resources/TR2X.png b/tools/installer/TR2X_Installer/Resources/TR2X.png deleted file mode 100644 index ca583a04f..000000000 Binary files a/tools/installer/TR2X_Installer/Resources/TR2X.png and /dev/null differ diff --git a/tools/installer/TR2X_Installer/Resources/TombATI.png b/tools/installer/TR2X_Installer/Resources/TombATI.png deleted file mode 100644 index de1f30d10..000000000 Binary files a/tools/installer/TR2X_Installer/Resources/TombATI.png and /dev/null differ diff --git a/tools/installer/TR2X_Installer/Resources/const.json b/tools/installer/TR2X_Installer/Resources/const.json deleted file mode 100644 index dbbc93bac..000000000 --- a/tools/installer/TR2X_Installer/Resources/const.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "Game": "TR2X", - "GoldGame": "TR2X - GM", - "GoldFileIdentifier": "title_gm.tr2", - "AllowExpansionTypeSelection": false, - "ShortcutTitle": "Tomb Raider II: Community Edition", - "GoldZips": { - "0": "trgm.zip", - "1": "trgm.zip" - } -} diff --git a/tools/installer/TR2X_Installer/Resources/icon.ico b/tools/installer/TR2X_Installer/Resources/icon.ico deleted file mode 100644 index 2a2bbf280..000000000 Binary files a/tools/installer/TR2X_Installer/Resources/icon.ico and /dev/null differ diff --git a/tools/installer/TR2X_Installer/Resources/release.zip b/tools/installer/TR2X_Installer/Resources/release.zip deleted file mode 100644 index 5284b4be4..000000000 Binary files a/tools/installer/TR2X_Installer/Resources/release.zip and /dev/null differ diff --git a/tools/installer/TR2X_Installer/Resources/side1.jpg b/tools/installer/TR2X_Installer/Resources/side1.jpg deleted file mode 100644 index f41b9c987..000000000 Binary files a/tools/installer/TR2X_Installer/Resources/side1.jpg and /dev/null differ diff --git a/tools/installer/TR2X_Installer/Resources/side2.jpg b/tools/installer/TR2X_Installer/Resources/side2.jpg deleted file mode 100644 index 6784d6eb8..000000000 Binary files a/tools/installer/TR2X_Installer/Resources/side2.jpg and /dev/null differ diff --git a/tools/installer/TR2X_Installer/Resources/side3.jpg b/tools/installer/TR2X_Installer/Resources/side3.jpg deleted file mode 100644 index 19199c560..000000000 Binary files a/tools/installer/TR2X_Installer/Resources/side3.jpg and /dev/null differ diff --git a/tools/installer/TR2X_Installer/Resources/side4.jpg b/tools/installer/TR2X_Installer/Resources/side4.jpg deleted file mode 100644 index d95d6ff58..000000000 Binary files a/tools/installer/TR2X_Installer/Resources/side4.jpg and /dev/null differ diff --git a/tools/installer/TR2X_Installer/TR2X_Installer.csproj b/tools/installer/TR2X_Installer/TR2X_Installer.csproj deleted file mode 100644 index 6e101b3cb..000000000 --- a/tools/installer/TR2X_Installer/TR2X_Installer.csproj +++ /dev/null @@ -1,53 +0,0 @@ - - - WinExe - net6.0-windows - enable - enable - true - false - - true - TR2X_Installer - True - true - true - false - true - false - false - win-x64 - Resources\icon.ico - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tools/installer/TRX_Installer.sln b/tools/installer/TRX_Installer.sln deleted file mode 100644 index dea4833a6..000000000 --- a/tools/installer/TRX_Installer.sln +++ /dev/null @@ -1,37 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.11.35219.272 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TRX_InstallerLib", "TRX_InstallerLib\TRX_InstallerLib.csproj", "{27F08E8C-2910-4682-B8BC-96ED4C1ECE54}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TR1X_Installer", "TR1X_Installer\TR1X_Installer.csproj", "{5B32640D-3997-472F-A1BA-FCE4128E0688}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TR2X_Installer", "TR2X_Installer\TR2X_Installer.csproj", "{DCCEAD2D-BC68-40D7-B1B9-981450416466}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {27F08E8C-2910-4682-B8BC-96ED4C1ECE54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {27F08E8C-2910-4682-B8BC-96ED4C1ECE54}.Debug|Any CPU.Build.0 = Debug|Any CPU - {27F08E8C-2910-4682-B8BC-96ED4C1ECE54}.Release|Any CPU.ActiveCfg = Release|Any CPU - {27F08E8C-2910-4682-B8BC-96ED4C1ECE54}.Release|Any CPU.Build.0 = Release|Any CPU - {5B32640D-3997-472F-A1BA-FCE4128E0688}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5B32640D-3997-472F-A1BA-FCE4128E0688}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5B32640D-3997-472F-A1BA-FCE4128E0688}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5B32640D-3997-472F-A1BA-FCE4128E0688}.Release|Any CPU.Build.0 = Release|Any CPU - {DCCEAD2D-BC68-40D7-B1B9-981450416466}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DCCEAD2D-BC68-40D7-B1B9-981450416466}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DCCEAD2D-BC68-40D7-B1B9-981450416466}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DCCEAD2D-BC68-40D7-B1B9-981450416466}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {BA21B1D5-1CC7-4ED8-8C79-A1A5B0ACC840} - EndGlobalSection -EndGlobal diff --git a/tools/installer/TRX_InstallerLib/Controls/FinishStepControl.xaml b/tools/installer/TRX_InstallerLib/Controls/FinishStepControl.xaml deleted file mode 100644 index 028c6e43f..000000000 --- a/tools/installer/TRX_InstallerLib/Controls/FinishStepControl.xaml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/tools/installer/TRX_InstallerLib/Controls/FinishStepControl.xaml.cs b/tools/installer/TRX_InstallerLib/Controls/FinishStepControl.xaml.cs deleted file mode 100644 index 02be4e3c8..000000000 --- a/tools/installer/TRX_InstallerLib/Controls/FinishStepControl.xaml.cs +++ /dev/null @@ -1,11 +0,0 @@ -using WC = System.Windows.Controls; - -namespace TRX_InstallerLib.Controls; - -public partial class FinishStepControl : WC.UserControl -{ - public FinishStepControl() - { - InitializeComponent(); - } -} diff --git a/tools/installer/TRX_InstallerLib/Controls/InstallSettingsStepControl.xaml.cs b/tools/installer/TRX_InstallerLib/Controls/InstallSettingsStepControl.xaml.cs deleted file mode 100644 index ff8cc15a0..000000000 --- a/tools/installer/TRX_InstallerLib/Controls/InstallSettingsStepControl.xaml.cs +++ /dev/null @@ -1,11 +0,0 @@ -using WC = System.Windows.Controls; - -namespace TRX_InstallerLib.Controls; - -public partial class InstallSettingsStepControl : WC.UserControl -{ - public InstallSettingsStepControl() - { - InitializeComponent(); - } -} diff --git a/tools/installer/TRX_InstallerLib/Controls/InstallSourceControl.xaml.cs b/tools/installer/TRX_InstallerLib/Controls/InstallSourceControl.xaml.cs deleted file mode 100644 index 3af76d30a..000000000 --- a/tools/installer/TRX_InstallerLib/Controls/InstallSourceControl.xaml.cs +++ /dev/null @@ -1,11 +0,0 @@ -using WC = System.Windows.Controls; - -namespace TRX_InstallerLib.Controls; - -public partial class InstallSourceControl : WC.UserControl -{ - public InstallSourceControl() - { - InitializeComponent(); - } -} diff --git a/tools/installer/TRX_InstallerLib/Controls/SourceStepControl.xaml.cs b/tools/installer/TRX_InstallerLib/Controls/SourceStepControl.xaml.cs deleted file mode 100644 index ed2bf125e..000000000 --- a/tools/installer/TRX_InstallerLib/Controls/SourceStepControl.xaml.cs +++ /dev/null @@ -1,11 +0,0 @@ -using WC = System.Windows.Controls; - -namespace TRX_InstallerLib.Controls; - -public partial class SourceStepControl : WC.UserControl -{ - public SourceStepControl() - { - InitializeComponent(); - } -} diff --git a/tools/installer/TRX_InstallerLib/Controls/TRXInstallWindow.xaml.cs b/tools/installer/TRX_InstallerLib/Controls/TRXInstallWindow.xaml.cs deleted file mode 100644 index 3e9bda9d8..000000000 --- a/tools/installer/TRX_InstallerLib/Controls/TRXInstallWindow.xaml.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Windows; -using TRX_InstallerLib.Installers; -using TRX_InstallerLib.Models; - -namespace TRX_InstallerLib.Controls; - -public partial class TRXInstallWindow : Window -{ - public TRXInstallWindow(IEnumerable installSources) - { - InitializeComponent(); - DataContext = new MainWindowViewModel(installSources); - } -} diff --git a/tools/installer/TRX_InstallerLib/Installers/BaseInstallSource.cs b/tools/installer/TRX_InstallerLib/Installers/BaseInstallSource.cs deleted file mode 100644 index fa366643a..000000000 --- a/tools/installer/TRX_InstallerLib/Installers/BaseInstallSource.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System.IO; -using TRX_InstallerLib.Models; -using TRX_InstallerLib.Utils; - -namespace TRX_InstallerLib.Installers; - -public abstract class BaseInstallSource : IInstallSource -{ - public abstract IEnumerable DirectoriesToTry { get; } - - public virtual string ImageSource - { - get => AssemblyUtils.GetEmbeddedResourcePath($"{SourceName}.png"); - } - - public abstract bool IsImportingSavesSupported { get; } - public abstract string SourceName { get; } - - public virtual string SuggestedInstallationDirectory - { - get => Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), - TRXConstants.Instance.Game!); - } - - public abstract Task CopyOriginalGameFiles( - string sourceDirectory, - string targetDirectory, - IProgress progress, - bool importSaves - ); - - public abstract bool IsDownloadingMusicNeeded(string sourceDirectory); - - public abstract bool IsDownloadingExpansionNeeded(string sourceDirectory); - - public abstract bool IsGameFound(string sourceDirectory); - - public static string ConvertTargetPath(string relPath) - { - string ext = Path.GetExtension(relPath).ToLower(); - switch (ext) - { - case ".pcx": - relPath = @$"data\images\og\{Path.GetFileName(relPath)}"; - break; - case ".json5": - case ".exe": - return relPath; - default: - break; - } - - return relPath.ToLower(); - } -} diff --git a/tools/installer/TRX_InstallerLib/Installers/InstallExecutor.cs b/tools/installer/TRX_InstallerLib/Installers/InstallExecutor.cs deleted file mode 100644 index 1cfd9d560..000000000 --- a/tools/installer/TRX_InstallerLib/Installers/InstallExecutor.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System.IO; -using TRX_InstallerLib.Models; -using TRX_InstallerLib.Utils; - -namespace TRX_InstallerLib.Installers; - -public class InstallExecutor -{ - private static readonly string _resourceBaseURL; - - static InstallExecutor() - { - _resourceBaseURL = $"https://lostartefacts.dev/aux/{TRXConstants.Instance.Game!.ToLower()}"; - } - - private readonly InstallSettings _settings; - - public InstallExecutor(InstallSettings settings) - { - _settings = settings; - } - - public IInstallSource? InstallSource - { - get => _settings.InstallSource; - } - - public async Task ExecuteInstall(IProgress progress) - { - if (_settings.SourceDirectory is null) - { - throw new NullReferenceException(); - } - if (_settings.TargetDirectory is null) - { - throw new NullReferenceException(); - } - - await CopyOriginalGameFiles(_settings.SourceDirectory, _settings.TargetDirectory, progress); - await CopyTRXFiles(_settings.TargetDirectory, progress); - if (_settings.DownloadMusic) - { - await DownloadMusicFiles(_settings.TargetDirectory, progress); - } - - if (_settings.DownloadExpansionPack) - { - await DownloadExpansionFiles(_settings.TargetDirectory, _settings.ExpansionPackType, progress); - } - if (_settings.CreateDesktopShortcut) - { - CreateDesktopShortcut(_settings.TargetDirectory); - } - - progress.Report(new InstallProgress { Description = Language.Instance.Controls!["progress_finished"], Finished = true }); - } - - protected async Task CopyOriginalGameFiles(string sourceDirectory, string targetDirectory, IProgress progress) - { - if (_settings.InstallSource is null) - { - throw new NullReferenceException(); - } - await _settings.InstallSource.CopyOriginalGameFiles(sourceDirectory, targetDirectory, progress, _settings.ImportSaves); - } - - protected static async Task CopyTRXFiles(string targetDirectory, IProgress progress) - { - InstallUtils.StoreInstallationPath(targetDirectory); - - progress.Report(new InstallProgress - { - CurrentValue = 0, - MaximumValue = 1, - Description = Language.Instance.Controls!["progress_opening_zip"], - }); - - using var stream = AssemblyUtils.GetResourceStream("Resources.release.zip", false) - ?? throw new ApplicationException(Language.Instance.Controls!["progress_zip_failure"]); - await InstallUtils.ExtractZip(stream, targetDirectory, progress, overwrite: true); - } - - protected static void CreateDesktopShortcut(string targetDirectory) - { - string targetExe = Path.Combine(targetDirectory, TRXConstants.Instance.Exe); - InstallUtils.CreateDesktopShortcut(TRXConstants.Instance.Game!, TRXConstants.Instance.ShortcutTitle!, targetExe); - if (File.Exists(Path.Combine(targetDirectory, "data", TRXConstants.Instance.GoldFileIdentifier!))) - { - InstallUtils.CreateDesktopShortcut(TRXConstants.Instance.GoldGame!, TRXConstants.Instance.ShortcutTitle!, - targetExe, new[] { TRXConstants.Instance.GoldArgs! }); - } - } - - protected static async Task DownloadMusicFiles(string targetDirectory, IProgress progress) - { - await InstallUtils.DownloadZip($"{_resourceBaseURL}/music.zip", targetDirectory, progress); - } - - protected static async Task DownloadExpansionFiles(string targetDirectory, ExpansionPackType type, IProgress progress) - { - string? zipName = null; - TRXConstants.Instance.GoldZips?.TryGetValue(type, out zipName); - if (zipName == null) - { - throw new ApplicationException(string.Format(Language.Instance.Controls!["progress_expansion_undefined"], type)); - } - await InstallUtils.DownloadZip($"{_resourceBaseURL}/{zipName}", targetDirectory, progress); - } -} diff --git a/tools/installer/TRX_InstallerLib/Models/BaseLanguageViewModel.cs b/tools/installer/TRX_InstallerLib/Models/BaseLanguageViewModel.cs deleted file mode 100644 index 436db1559..000000000 --- a/tools/installer/TRX_InstallerLib/Models/BaseLanguageViewModel.cs +++ /dev/null @@ -1,11 +0,0 @@ -using TRX_InstallerLib.Utils; - -namespace TRX_InstallerLib.Models; - -public class BaseLanguageViewModel : BaseNotifyPropertyChanged -{ - public static Dictionary ViewText - { - get => Language.Instance.Controls ?? new(); - } -} diff --git a/tools/installer/TRX_InstallerLib/Models/ExpansionPackType.cs b/tools/installer/TRX_InstallerLib/Models/ExpansionPackType.cs deleted file mode 100644 index 6b072667c..000000000 --- a/tools/installer/TRX_InstallerLib/Models/ExpansionPackType.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace TRX_InstallerLib.Models; - -public enum ExpansionPackType -{ - Music, - Vanilla, -} diff --git a/tools/installer/TRX_InstallerLib/Models/Language.cs b/tools/installer/TRX_InstallerLib/Models/Language.cs deleted file mode 100644 index 6b868f897..000000000 --- a/tools/installer/TRX_InstallerLib/Models/Language.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System.Globalization; -using TRX_InstallerLib.Utils; - -namespace TRX_InstallerLib.Models; - -public class Language -{ - private static readonly string _langPathFormat = "Resources.Lang.{0}.json"; - private static readonly string _defaultCulture = "en-US"; - - public static Language Instance { get; private set; } - - public Dictionary? Controls { get; set; } - - static Language() - { - CultureInfo defaultCulture = CultureInfo.GetCultureInfo(_defaultCulture); - JObject defaultData = ReadLanguage(defaultCulture.TwoLetterISOLanguageName); - - if (CultureInfo.CurrentCulture != defaultCulture) - { - // Merge the main language first if it exists, and then the country specific if that exists. - // e.g. fr.json would load first, then fr-BE.json. - MergeLanguage(defaultData, CultureInfo.CurrentCulture.TwoLetterISOLanguageName); - MergeLanguage(defaultData, CultureInfo.CurrentCulture.Name); - } - - Instance = JsonConvert.DeserializeObject(defaultData.ToString())!; - } - - private static JObject ReadLanguage(string tag) - { - return JsonUtils.LoadEmbeddedResource(string.Format(_langPathFormat, tag)) ?? new(); - } - - private static void MergeLanguage(JObject data, string tag) - { - JObject cultureData = ReadLanguage(tag); - if (cultureData != null) - { - data.Merge(cultureData); - } - } -} diff --git a/tools/installer/TRX_InstallerLib/Models/TRXConstants.cs b/tools/installer/TRX_InstallerLib/Models/TRXConstants.cs deleted file mode 100644 index 985799de0..000000000 --- a/tools/installer/TRX_InstallerLib/Models/TRXConstants.cs +++ /dev/null @@ -1,24 +0,0 @@ -using TRX_InstallerLib.Utils; - -namespace TRX_InstallerLib.Models; - -public class TRXConstants -{ - private static readonly string _constConfigPath = "Resources.const.json"; - - public static TRXConstants Instance { get; private set; } - - static TRXConstants() - { - Instance = JsonUtils.LoadEmbeddedResource(_constConfigPath)?.ToObject() ?? new(); - } - - public string? Game { get; set; } - public string? GoldGame { get; set; } - public string? GoldFileIdentifier { get; set; } - public string Exe => $"{Game}.exe"; - public bool? AllowExpansionTypeSelection { get; set; } - public string? GoldArgs { get; set; } - public string? ShortcutTitle { get; set; } - public Dictionary? GoldZips { get; set; } -} diff --git a/tools/installer/TRX_InstallerLib/Resources/Lang/en.json b/tools/installer/TRX_InstallerLib/Resources/Lang/en.json deleted file mode 100644 index c37913918..000000000 --- a/tools/installer/TRX_InstallerLib/Resources/Lang/en.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "Controls": { - "command_back": "_Back", - "command_next": "_Next", - "command_close": "_Close", - "command_cancel": "_Cancel", - "command_change": "C_hange...", - "command_change_link": "(change)", - "label_found": "Found", - "label_not_found": "Not found", - "label_already_found": "(already found)", - "label_folder_not_selected": "(no folder selected)", - "label_destination_folder": "Destination folder:", - "label_select_folder": "Choose directory", - "step_source_heading": "Step 1: Choose installation source", - "step_source_content": "Placeholder", - "step_settings_heading": "Step 2: Installation options", - "step_settings_music_heading": "Download music tracks", - "step_settings_music_content": "Placeholder", - "step_settings_expansion_heading": "Placeholder", - "step_settings_expansion_content": "Placeholder", - "step_settings_expansion_music": "Placeholder", - "step_settings_expansion_vanilla": "Placeholder", - "step_settings_saves_header": "Import saves", - "step_settings_saves_content": "Placeholder", - "step_settings_shortcut_heading": "Create desktop shortcut", - "step_install_heading": "Step 3: Installing", - "step_finish_heading": "Step 4: Done", - "step_finish_content": "Installation complete. To configure more advanced features, you can edit the JSON files in the cfg/ directory with a text editor.\n\nHappy raiding :)", - "step_finish_open_directory": "Open game directory after closing this window", - "step_finish_open_game": "Launch the game after closing this window", - "progress_scanning": "Scanning directory", - "progress_scanning_source": "Scanning the source directory", - "progress_preparing_extract": "Preparing to extract the ISO", - "progress_copying": "Copying {0}", - "progress_skipped": "Copying {0} - skipped", - "progress_init_download": "Initializing download of {0}", - "progress_expansion_undefined": "No target zip defined for expansion pack type {0}", - "progress_downloading": "Downloading {0}", - "progress_opening_zip": "Opening embedded ZIP", - "progress_zip_failure": "Could not open embedded ZIP.", - "progress_scanning_zip": "Scanning ZIP", - "progress_extracting": "Extracting {0}", - "progress_extracting_skipped": "Extracting {0} - skipped", - "progress_converting_bin": "Converting BIN to ISO", - "progress_converting_bin_failure": "Could not convert BIN to ISO: {0}", - "progress_converting_iso_failure": "Could not open converted ISO: {0}", - "progress_track_write_failure": "Could not write to track file {0}: {1}", - "progress_track_seek_failure": "Could not seek to track location: {0}", - "progress_bin_failure": "Could not open BIN {0}: {1}", - "progress_cue_failure": "Could not read CUE {0}: {1}", - "progress_cue_empty": "Could not parse {0}: no tracks were found", - "progress_finished": "Finished", - "shortcut_signature_failure": "Invalid LNK signature", - "shortcut_target_failure": "Unable to determine link target path" - } -} diff --git a/tools/installer/TRX_InstallerLib/Resources/const.json b/tools/installer/TRX_InstallerLib/Resources/const.json deleted file mode 100644 index afde1f76d..000000000 --- a/tools/installer/TRX_InstallerLib/Resources/const.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "Game": "TRX", - "AllowExpansionTypeSelection": false, - "GoldArgs": "-gold" -} diff --git a/tools/installer/TRX_InstallerLib/TRX_InstallerLib.csproj b/tools/installer/TRX_InstallerLib/TRX_InstallerLib.csproj deleted file mode 100644 index 478161f7d..000000000 --- a/tools/installer/TRX_InstallerLib/TRX_InstallerLib.csproj +++ /dev/null @@ -1,28 +0,0 @@ - - - net6.0-windows - enable - true - true - enable - false - true - - - - - - - - - - - - - - MSBuild:Compile - - - - - diff --git a/tools/installer/TRX_InstallerLib/Utils/AssemblyUtils.cs b/tools/installer/TRX_InstallerLib/Utils/AssemblyUtils.cs deleted file mode 100644 index 0ea0cdb03..000000000 --- a/tools/installer/TRX_InstallerLib/Utils/AssemblyUtils.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.IO; -using System.Reflection; - -namespace TRX_InstallerLib.Utils; - -public static class AssemblyUtils -{ - public static readonly string _resourcePathFormat = "pack://application:,,,/{0};component/Resources/{1}"; - - private static Assembly? GetReferencedAssembly(bool local) - { - return local ? Assembly.GetExecutingAssembly() : Assembly.GetEntryAssembly(); - } - - public static Stream GetResourceStream(string relativePath, bool local) - { - return GetReferencedAssembly(local)?.GetManifestResourceStream(GetAbsolutePath(relativePath, local))!; - } - - public static bool ResourceExists(string relativePath, bool local) - { - return GetReferencedAssembly(local)?.GetManifestResourceNames() - .Contains(GetAbsolutePath(relativePath, local)) ?? false; - } - - public static string GetAbsolutePath(string relativePath, bool local) - { - return $"{GetReferencedAssembly(local)!.GetName().Name}.{relativePath}"; - } - - public static string GetEmbeddedResourcePath(string resource) - { - return string.Format(_resourcePathFormat, Assembly.GetEntryAssembly()!.GetName().Name, resource); - } -} diff --git a/tools/installer/TRX_InstallerLib/Utils/ConditionalViewTextConverter.cs b/tools/installer/TRX_InstallerLib/Utils/ConditionalViewTextConverter.cs deleted file mode 100644 index 0f47d9443..000000000 --- a/tools/installer/TRX_InstallerLib/Utils/ConditionalViewTextConverter.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Globalization; -using TRX_InstallerLib.Models; - -namespace TRX_InstallerLib.Utils; - -public class ConditionalViewTextConverter : ConditionalMarkupConverter -{ - public override object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - string result = base.Convert(value, targetType, parameter, culture).ToString()!; - return result.Length == 0 ? string.Empty : Language.Instance.Controls![result]; - } -} diff --git a/tools/installer/TRX_InstallerLib/Utils/JsonUtils.cs b/tools/installer/TRX_InstallerLib/Utils/JsonUtils.cs deleted file mode 100644 index 8724be984..000000000 --- a/tools/installer/TRX_InstallerLib/Utils/JsonUtils.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Newtonsoft.Json.Linq; -using System.IO; - -namespace TRX_InstallerLib.Utils; - -public static class JsonUtils -{ - public static JObject? LoadEmbeddedResource(string path) - { - // Try to locate the data in this assembly first, then merge it - // with the same in the entry assembly if relevant. - JObject? data = null; - - if (AssemblyUtils.ResourceExists(path, true)) - { - using Stream stream = AssemblyUtils.GetResourceStream(path, true); - using StreamReader reader = new(stream); - data = JObject.Parse(reader.ReadToEnd()); - } - - if (AssemblyUtils.ResourceExists(path, false)) - { - data ??= new(); - using Stream stream = AssemblyUtils.GetResourceStream(path, false); - using StreamReader reader = new(stream); - data.Merge(JObject.Parse(reader.ReadToEnd())); - } - - return data; - } -} diff --git a/tools/installer/TRX_InstallerLib/Utils/ProcessUtils.cs b/tools/installer/TRX_InstallerLib/Utils/ProcessUtils.cs deleted file mode 100644 index 4a6c13100..000000000 --- a/tools/installer/TRX_InstallerLib/Utils/ProcessUtils.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Diagnostics; -using System.IO; - -namespace TRX_InstallerLib.Utils; - -public static class ProcessUtils -{ - public static void Start(string fileName, string? arguments = null) - { - Process.Start(new ProcessStartInfo - { - FileName = fileName, - Arguments = arguments, - UseShellExecute = true, - WorkingDirectory = new Uri(fileName).IsFile - ? Path.GetDirectoryName(fileName) - : null - }); - } -} diff --git a/tools/release b/tools/release index 35a759715..0efc8c3d1 100755 --- a/tools/release +++ b/tools/release @@ -82,7 +82,7 @@ class BranchCommand(BaseCommand): new_tag = f"tr{args.game_version}-{args.version}" self.git.checkout_branch("stable") self.git.merge_reset( - "develop", text=f"tr{args.game_version}: release {args.version}" + "develop", text=f"{args.game_version}: release {args.version}" ) self.git.delete_tag(new_tag) self.git.create_tag(new_tag) diff --git a/tools/shared/docker/__init__.py b/tools/shared/cli/__init__.py similarity index 100% rename from tools/shared/docker/__init__.py rename to tools/shared/cli/__init__.py diff --git a/tools/shared/cli/game_docker_entrypoint.py b/tools/shared/cli/game_docker_entrypoint.py new file mode 100644 index 000000000..76b38c8a1 --- /dev/null +++ b/tools/shared/cli/game_docker_entrypoint.py @@ -0,0 +1,139 @@ +import argparse +import os +from dataclasses import dataclass +from pathlib import Path +from subprocess import check_call, run +from typing import Any + +from shared.packaging import create_zip +from shared.versioning import generate_version + + +@dataclass +class Options: + version: int + platform: str + compile_args: list[str] + release_zip_files: list[tuple[Path, str]] + strip_tool = "strip" + upx_tool = "upx" + target = os.environ.get("TARGET", "debug") + compressable_exes: list[Path] | None = None + + @property + def ship_dir(self) -> Path: + return Path(f"/app/data/tr{self.version}/ship/") + + @property + def build_root(self) -> Path: + return Path(f"/app/build/tr{self.version}/{self.platform}/") + + @property + def build_target(self) -> str: + return f"src/tr{self.version}" + + @property + def release_zip_filename_fmt(self) -> str: + platform = self.platform + if platform == "win": + platform = "windows" + return f"TR{self.version}X-{{version}}-{platform.title()}.zip" + + +def compress_exe(options: Options, path: Path) -> None: + if run([options.upx_tool, "-t", str(path)]).returncode != 0: + check_call([options.strip_tool, str(path)]) + check_call([options.upx_tool, str(path)]) + + +class BaseCommand: + name: str = NotImplemented + help: str = NotImplemented + + def decorate_parser(self, parser: argparse.ArgumentParser) -> None: + pass + + def run(self, args: argparse.Namespace) -> None: + raise NotImplementedError("not implemented") + + +class CompileCommand(BaseCommand): + name = "compile" + + def run(self, args: argparse.Namespace, options: Options) -> None: + pkg_config_path = os.environ.get("PKG_CONFIG_PATH") + + if not (options.build_root / "build.ninja").exists(): + command = [ + "meson", + "setup", + "--buildtype", + options.target, + *options.compile_args, + options.build_root, + options.build_target, + ] + if pkg_config_path: + command.extend(["--pkg-config-path", pkg_config_path]) + check_call(command) + + check_call(["meson", "compile"], cwd=options.build_root) + + if options.target == "release": + for exe_path in options.compressable_exes: + compress_exe(options, exe_path) + + +class PackageCommand(BaseCommand): + name = "package" + + def decorate_parser(self, parser: argparse.ArgumentParser) -> None: + parser.add_argument("-o", "--output", type=Path) + + def run(self, args: argparse.Namespace, options: Options) -> None: + if args.output: + zip_path = args.output + else: + zip_path = Path( + options.release_zip_filename_fmt.format( + version=generate_version(options.version) + ) + ) + + source_files = [ + *[ + (path, path.relative_to(options.ship_dir)) + for path in options.ship_dir.rglob("*") + if path.is_file() + ], + *options.release_zip_files, + ] + + create_zip(zip_path, source_files) + print(f"Created {zip_path}") + + +def parse_args(commands: dict[str, BaseCommand]) -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Docker entrypoint") + subparsers = parser.add_subparsers(dest="action", help="Subcommands") + parser.set_defaults(action="compile", command=commands["compile"]) + + for command in commands.values(): + subparser = subparsers.add_parser(command.name, help=command.help) + command.decorate_parser(subparser) + subparser.set_defaults(command=command) + result = parser.parse_args() + # if not hasattr(result, "command"): + # args.action = "compile" + # args.command = CompileCommand + return result + + +def run_script(**kwargs: Any) -> None: + commands = { + command_cls.name: command_cls() + for command_cls in BaseCommand.__subclasses__() + } + args = parse_args(commands) + options = Options(**kwargs) + args.command.run(args, options) diff --git a/tools/shared/docker/config/entrypoint.sh b/tools/shared/docker/config/entrypoint.sh deleted file mode 100755 index ea7fc3de4..000000000 --- a/tools/shared/docker/config/entrypoint.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash -set -x -set -e - -if [[ -z "$1" || ( "$1" != "1" && "$1" != "2" ) ]]; then - echo "Error: You must supply '1' or '2' as an argument to decide which game to build." - exit 1 -fi -TR_VERSION=$1 - -export DOTNET_CLI_HOME="/tmp/DOTNET_CLI_HOME" -echo $HOME -shopt -s globstar - -cd /app/tools/config/ -rm -rf **/bin **/obj **/out/* -dotnet publish TR${TR_VERSION}X_ConfigTool -c Release -o out diff --git a/tools/shared/docker/game-linux/entrypoint.sh b/tools/shared/docker/game-linux/entrypoint.sh deleted file mode 100755 index 8059a1402..000000000 --- a/tools/shared/docker/game-linux/entrypoint.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env python3 -from shared.docker.game_entrypoint import main - -main(platform="linux") diff --git a/tools/shared/docker/game-win/entrypoint.sh b/tools/shared/docker/game-win/entrypoint.sh deleted file mode 100755 index 4ea78a52a..000000000 --- a/tools/shared/docker/game-win/entrypoint.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env python3 -from shared.docker.game_entrypoint import main - -main(platform="win") diff --git a/tools/shared/docker/game_entrypoint.py b/tools/shared/docker/game_entrypoint.py deleted file mode 100644 index fcf955ba7..000000000 --- a/tools/shared/docker/game_entrypoint.py +++ /dev/null @@ -1,210 +0,0 @@ -import argparse -import os -from dataclasses import dataclass, fields -from pathlib import Path -from subprocess import check_call, run -from typing import Any, Self - -from shared.packaging import create_zip -from shared.versioning import generate_version - - -@dataclass -class BaseOptions: - platform: str - tr_version: int - - @property - def build_root(self) -> Path: - return Path(f"/app/build/tr{self.tr_version}/{self.platform}/") - - @property - def version(self) -> str: - return generate_version(self.tr_version) - - @classmethod - def from_args(cls, args: argparse.Namespace) -> Self: - cls_fields = [field.name for field in fields(cls)] - filtered_args = vars(args) - filtered_args = { - k: v for k, v in filtered_args.items() if k in cls_fields - } - return cls(**filtered_args) - - -@dataclass -class PackageOptions(BaseOptions): - @property - def release_zip_filename(self) -> Path: - platform = self.platform - if platform == "win": - platform = "windows" - return Path( - f"TR{self.tr_version}X-{self.version}-{platform.title()}.zip" - ) - - @property - def ship_dir(self) -> Path: - return Path(f"/app/data/tr{self.tr_version}/ship/") - - @property - def release_zip_files(self) -> list[tuple[Path, str]]: - if self.platform == "linux": - return [ - ( - self.build_root / f"TR{self.tr_version}X", - f"TR{self.tr_version}X", - ) - ] - - elif self.platform == "win": - return [ - ( - self.build_root / f"TR{self.tr_version}X.exe", - f"TR{self.tr_version}X.exe", - ), - ( - Path( - f"/app/tools/config/out/TR{self.tr_version}X_ConfigTool.exe" - ), - f"TR{self.tr_version}X_ConfigTool.exe", - ), - ] - - return [] - - -@dataclass -class BuildOptions(BaseOptions): - target: str - - strip_tool = "strip" - upx_tool = "upx" - - @property - def build_args(self) -> list[str]: - if self.platform == "win": - return [ - "--cross", - "/app/tools/shared/docker/game-win/meson_linux_mingw32.txt", - ] - return [] - - @property - def compressable_exes(self) -> list[Path]: - if self.platform == "linux": - return [self.build_root / f"TR{self.tr_version}X"] - elif self.platform == "win": - return [self.build_root / f"TR{self.tr_version}X.exe"] - return [] - - @property - def build_target(self) -> Path: - return Path(f"src/tr{self.tr_version}") - - -def compress_exe(options: BuildOptions, path: Path) -> None: - if run([options.upx_tool, "-t", str(path)]).returncode != 0: - check_call([options.strip_tool, str(path)]) - check_call([options.upx_tool, str(path)]) - - -class BaseCommand: - name: str = NotImplemented - help: str = NotImplemented - - def decorate_parser(self, parser: argparse.ArgumentParser) -> None: - pass - - def run(self, args: argparse.Namespace) -> None: - raise NotImplementedError("not implemented") - - -class BuildCommand(BaseCommand): - name = "build" - - def decorate_parser(self, parser: argparse.ArgumentParser) -> None: - parser.add_argument("--platform") - parser.add_argument("--tr-version", type=int, required=True) - parser.add_argument( - "--target", - choices=["debug", "release", "debugoptim"], - required=True, - ) - - def run(self, args: argparse.Namespace) -> None: - options = BuildOptions.from_args(args) - pkg_config_path = os.environ.get("PKG_CONFIG_PATH") - - if not (options.build_root / "build.ninja").exists(): - command: list[str | Path] = [ - "meson", - "setup", - "--buildtype", - options.target, - *options.build_args, - options.build_root, - options.build_target, - ] - if pkg_config_path: - command.extend(["--pkg-config-path", pkg_config_path]) - check_call(command) - - check_call(["meson", "compile"], cwd=options.build_root) - - if options.target == "release": - for exe_path in options.compressable_exes: - compress_exe(options, exe_path) - - -class PackageCommand(BaseCommand): - name = "package" - - def decorate_parser(self, parser: argparse.ArgumentParser) -> None: - parser.add_argument("--tr-version", type=int, required=True) - parser.add_argument("-o", "--output", type=Path) - - def run(self, args: argparse.Namespace) -> None: - options = PackageOptions.from_args(args) - if args.output: - zip_path = args.output - else: - zip_path = options.release_zip_filename - - source_files = [ - *[ - (path, str(path.relative_to(options.ship_dir))) - for path in options.ship_dir.rglob("*") - if path.is_file() - ], - *options.release_zip_files, - ] - - create_zip(zip_path, source_files) - print(f"Created {zip_path}") - - -def parse_args( - commands: dict[str, BaseCommand], **kwargs -) -> argparse.Namespace: - parser = argparse.ArgumentParser(description="Docker entrypoint") - subparsers = parser.add_subparsers(dest="action", help="Subcommands") - parser.set_defaults(action="build", command=commands["build"]) - parser.set_defaults(**kwargs) - - for command in commands.values(): - subparser = subparsers.add_parser(command.name, help=command.help) - command.decorate_parser(subparser) - subparser.set_defaults(command=command) - subparser.set_defaults(**kwargs) - result = parser.parse_args() - return result - - -def main(**kwargs: Any) -> None: - commands = { - command_cls.name: command_cls() - for command_cls in BaseCommand.__subclasses__() - } - args = parse_args(commands, **kwargs) - args.command.run(args) diff --git a/tools/shared/docker/installer/entrypoint.sh b/tools/shared/docker/installer/entrypoint.sh deleted file mode 100755 index 5c61ae708..000000000 --- a/tools/shared/docker/installer/entrypoint.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash -set -x -set -e - -if [[ -z "$1" || ( "$1" != "1" && "$1" != "2" ) ]]; then - echo "Error: You must supply '1' or '2' as an argument to decide which game to build." - exit 1 -fi -TR_VERSION=$1 - -cd /app/tools/installer/ - -export DOTNET_CLI_HOME="/tmp/DOTNET_CLI_HOME" - -shopt -s globstar -rm -rf **/bin **/obj **/out/* -dotnet publish TR${TR_VERSION}X_Installer -c Release -o out diff --git a/tools/tr1/config/.gitignore b/tools/tr1/config/.gitignore new file mode 100644 index 000000000..5fbf210e0 --- /dev/null +++ b/tools/tr1/config/.gitignore @@ -0,0 +1,4 @@ +bin/ +obj/ +out/ +*.pubxml diff --git a/tools/tr1/config/TR1X_ConfigTool.sln b/tools/tr1/config/TR1X_ConfigTool.sln new file mode 100644 index 000000000..e54647185 --- /dev/null +++ b/tools/tr1/config/TR1X_ConfigTool.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32407.343 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TR1X_ConfigTool", "TR1X_ConfigTool\TR1X_ConfigTool.csproj", "{DCFCB544-9B88-41D6-A995-EA2D06FE405E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DCFCB544-9B88-41D6-A995-EA2D06FE405E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DCFCB544-9B88-41D6-A995-EA2D06FE405E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DCFCB544-9B88-41D6-A995-EA2D06FE405E}.Debug|x64.ActiveCfg = Debug|x64 + {DCFCB544-9B88-41D6-A995-EA2D06FE405E}.Debug|x64.Build.0 = Debug|x64 + {DCFCB544-9B88-41D6-A995-EA2D06FE405E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DCFCB544-9B88-41D6-A995-EA2D06FE405E}.Release|Any CPU.Build.0 = Release|Any CPU + {DCFCB544-9B88-41D6-A995-EA2D06FE405E}.Release|x64.ActiveCfg = Release|x64 + {DCFCB544-9B88-41D6-A995-EA2D06FE405E}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {49C0A7F4-2D17-424B-BD94-A8FC6E95931D} + EndGlobalSection +EndGlobal diff --git a/tools/config/TR1X_ConfigTool/App.xaml b/tools/tr1/config/TR1X_ConfigTool/App.xaml similarity index 100% rename from tools/config/TR1X_ConfigTool/App.xaml rename to tools/tr1/config/TR1X_ConfigTool/App.xaml diff --git a/tools/config/TR1X_ConfigTool/App.xaml.cs b/tools/tr1/config/TR1X_ConfigTool/App.xaml.cs similarity index 100% rename from tools/config/TR1X_ConfigTool/App.xaml.cs rename to tools/tr1/config/TR1X_ConfigTool/App.xaml.cs diff --git a/tools/config/TR1X_ConfigTool/Resources/Graphics/graphic1.jpg b/tools/tr1/config/TR1X_ConfigTool/Resources/Graphics/graphic1.jpg similarity index 100% rename from tools/config/TR1X_ConfigTool/Resources/Graphics/graphic1.jpg rename to tools/tr1/config/TR1X_ConfigTool/Resources/Graphics/graphic1.jpg diff --git a/tools/config/TR1X_ConfigTool/Resources/Graphics/graphic2.jpg b/tools/tr1/config/TR1X_ConfigTool/Resources/Graphics/graphic2.jpg similarity index 100% rename from tools/config/TR1X_ConfigTool/Resources/Graphics/graphic2.jpg rename to tools/tr1/config/TR1X_ConfigTool/Resources/Graphics/graphic2.jpg diff --git a/tools/config/TR1X_ConfigTool/Resources/Graphics/graphic3.jpg b/tools/tr1/config/TR1X_ConfigTool/Resources/Graphics/graphic3.jpg similarity index 100% rename from tools/config/TR1X_ConfigTool/Resources/Graphics/graphic3.jpg rename to tools/tr1/config/TR1X_ConfigTool/Resources/Graphics/graphic3.jpg diff --git a/tools/config/TR1X_ConfigTool/Resources/Graphics/graphic4.jpg b/tools/tr1/config/TR1X_ConfigTool/Resources/Graphics/graphic4.jpg similarity index 100% rename from tools/config/TR1X_ConfigTool/Resources/Graphics/graphic4.jpg rename to tools/tr1/config/TR1X_ConfigTool/Resources/Graphics/graphic4.jpg diff --git a/tools/config/TR1X_ConfigTool/Resources/Graphics/graphic5.jpg b/tools/tr1/config/TR1X_ConfigTool/Resources/Graphics/graphic5.jpg similarity index 100% rename from tools/config/TR1X_ConfigTool/Resources/Graphics/graphic5.jpg rename to tools/tr1/config/TR1X_ConfigTool/Resources/Graphics/graphic5.jpg diff --git a/tools/config/TR1X_ConfigTool/Resources/Graphics/graphic6.jpg b/tools/tr1/config/TR1X_ConfigTool/Resources/Graphics/graphic6.jpg similarity index 100% rename from tools/config/TR1X_ConfigTool/Resources/Graphics/graphic6.jpg rename to tools/tr1/config/TR1X_ConfigTool/Resources/Graphics/graphic6.jpg diff --git a/tools/config/TR1X_ConfigTool/Resources/Graphics/graphic7.jpg b/tools/tr1/config/TR1X_ConfigTool/Resources/Graphics/graphic7.jpg similarity index 100% rename from tools/config/TR1X_ConfigTool/Resources/Graphics/graphic7.jpg rename to tools/tr1/config/TR1X_ConfigTool/Resources/Graphics/graphic7.jpg diff --git a/tools/config/TR1X_ConfigTool/Resources/Lang/en-GB.json b/tools/tr1/config/TR1X_ConfigTool/Resources/Lang/en-GB.json similarity index 100% rename from tools/config/TR1X_ConfigTool/Resources/Lang/en-GB.json rename to tools/tr1/config/TR1X_ConfigTool/Resources/Lang/en-GB.json diff --git a/tools/config/TR1X_ConfigTool/Resources/Lang/en.json b/tools/tr1/config/TR1X_ConfigTool/Resources/Lang/en.json similarity index 96% rename from tools/config/TR1X_ConfigTool/Resources/Lang/en.json rename to tools/tr1/config/TR1X_ConfigTool/Resources/Lang/en.json index 80daa2023..106359855 100644 --- a/tools/config/TR1X_ConfigTool/Resources/Lang/en.json +++ b/tools/tr1/config/TR1X_ConfigTool/Resources/Lang/en.json @@ -54,10 +54,10 @@ "semi-lock": "Semi lock", "no-lock": "No lock" }, - "stat_mode": { - "minimal": "Minimal", - "detailed": "Detailed", - "full": "Full" + "music_load_condition": { + "never": "Never", + "non-ambient": "Non-ambient", + "always": "Always" } }, "Properties": { @@ -131,7 +131,7 @@ }, "fix_bear_ai": { "Title": "Fix bear AI", - "Description": "Fixes the bear pat attack so it does not miss Lara." + "Description": "Fixes bear pat attack so it does not miss Lara." }, "fix_descending_glitch": { "Title": "Fix breakable floor falls", @@ -147,7 +147,7 @@ }, "fix_texture_issues": { "Title": "Fix texture issues", - "Description": "Fixes original issues with missing or incorrect textures/meshes." + "Description": "Fixes original issues with missing or incorrect textures." }, "fix_animated_sprites": { "Title": "Fix sprite animations", @@ -229,11 +229,11 @@ "Title": "Level statistics in compass", "Description": "Enables showing level statistics when the compass is selected." }, - "stat_detail_mode": { - "Title": "Stat detail mode", - "Description": "Allows various levels of stat detail.\n- Minimal: shows kills, pickups, secrets, and time taken.\n- Detailed: shows all stats from minimal as well as the maximum pickup count and kill count of each level.\n- Full: shows all stats from detailed as well as ammo hits, ammo used, health packs used, and distance travelled." + "enable_detailed_stats": { + "Title": "Show total kills and pickups", + "Description": "Enables showing the maximum pickup count and kill count on each level." }, - "enable_cutscenes": { + "enable_cine": { "Title": "Enable cutscenes", "Description": "Enables cutscenes playing." }, @@ -245,9 +245,9 @@ "Title": "Enable FMVs", "Description": "Enables FMVs playing." }, - "enable_legal": { - "Title": "Enable legal", - "Description": "Enables EIDOS logo, Core Design FMV and bink video codec FMV at the game start." + "enable_eidos_logo": { + "Title": "Enable EIDOS logo", + "Description": "Enables EIDOS logo at the game start." }, "enable_loading_screens": { "Title": "Enable loading screens", diff --git a/tools/config/TR1X_ConfigTool/Resources/Lang/es.json b/tools/tr1/config/TR1X_ConfigTool/Resources/Lang/es.json similarity index 96% rename from tools/config/TR1X_ConfigTool/Resources/Lang/es.json rename to tools/tr1/config/TR1X_ConfigTool/Resources/Lang/es.json index f281ba2b8..001b62154 100644 --- a/tools/config/TR1X_ConfigTool/Resources/Lang/es.json +++ b/tools/tr1/config/TR1X_ConfigTool/Resources/Lang/es.json @@ -69,11 +69,6 @@ "never": "Nunca", "non-ambient": "No ambiental", "always": "Siempre" - }, - "stat_mode": { - "minimal": "Mínimo", - "detailed": "Detallado", - "full": "Completo" } }, "Properties": { @@ -153,7 +148,7 @@ "Title": "Consola", "Description": "Habilita la consola de desarrollo." }, - "enable_cutscenes": { + "enable_cine": { "Title": "Habilitar escenas", "Description": "Habilita la reproducción de escenas." }, @@ -169,9 +164,9 @@ "Title": "Habilitar modo de demostración", "Description": "Habilita las demostraciones que se muestran en el menú principal." }, - "stat_detail_mode": { - "Title": "Modo de estadísticas detalladas", - "Description": "Permite varios niveles de detalle de estadísticas.\n- Mínimo: muestra muertes, recogidas, secretos y tiempo empleado.\n- Detallado: muestra todas las estadísticas de mínimo así como el recuento máximo de recogidas y muertes de cada nivel.\n- Completo: muestra todas las estadísticas de detailed así como impactos de munición, munición usada, paquetes de salud usados y distancia recorrida." + "enable_detailed_stats": { + "Title": "Mostrar total de objetos y muertes", + "Description": "Permite mostrar el recuento máximo de objetos recogidos y el recuento de muertes en cada nivel. Esto incluye objetos que no se pueden obtener." }, "enemy_healthbar_show_mode": { "Title": "Mostrar barra de salud enemiga", @@ -197,9 +192,9 @@ "Title": "Habilitar FMV", "Description": "Habilita la reproducción de vídeos FMV." }, - "enable_legal": { - "Title": "Habilitar el contenido legal", - "Description": "Habilita el logo de EIDOS, el FMV de Core Design y el FMV de códec de video bink al inicio del juego." + "enable_eidos_logo": { + "Title": "Habilitar el logo de EIDOS", + "Description": "Habilita el logo de EIDOS al comienzo del juego." }, "enable_loading_screens": { "Title": "Habilitar pantallas de carga", diff --git a/tools/config/TR1X_ConfigTool/Resources/Lang/fr.json b/tools/tr1/config/TR1X_ConfigTool/Resources/Lang/fr.json similarity index 96% rename from tools/config/TR1X_ConfigTool/Resources/Lang/fr.json rename to tools/tr1/config/TR1X_ConfigTool/Resources/Lang/fr.json index 25a5c3811..a0d3be184 100644 --- a/tools/config/TR1X_ConfigTool/Resources/Lang/fr.json +++ b/tools/tr1/config/TR1X_ConfigTool/Resources/Lang/fr.json @@ -69,11 +69,6 @@ "never": "Jamais", "non-ambient": "Non ambiant", "always": "Toujours" - }, - "stat_mode": { - "minimal": "Minime", - "detailed": "Détaillée", - "full": "Complet" } }, "Properties": { @@ -245,11 +240,11 @@ "Title": "Statistiques de niveau dans la boussole", "Description": "Active l'affichage des statistiques de niveau lorsque la boussole est sélectionnée." }, - "stat_detail_mode": { - "Title": "Mode détail Stat", - "Description": "Permet différents niveaux de détail des statistiques.\n- Minime: indique les tués, les ramassages, les secrets et le temps passé.\n- Détaillée: indique toutes les statistiques de nu ainsi que le nombre maximum de ramassages et de tués pour chaque niveau.\n- Complet: indique toutes les statistiques de détaillé ainsi que les munitions touchées, les munitions utilisées, les trousses de santé utilisées et la distance parcourue." + "enable_detailed_stats": { + "Title": "Afficher le nombre total de victimes et de collectibles", + "Description": "Permet d'afficher le nombre maximum de collectibles ramassés et le nombre de victimes faites à chaque niveau. Cela inclut les objets impossibles à obtenir." }, - "enable_cutscenes": { + "enable_cine": { "Title": "Activer les cutscenes", "Description": "Active les cutscenes en jeu." }, @@ -261,9 +256,9 @@ "Title": "Activer les FMV", "Description": "Active les FMV en jeu." }, - "enable_legal": { - "Title": "Activer le contenu légal", - "Description": "Active le logo EIDOS, la vidéo FMV de Core Design et la vidéo FMV avec le codec Bink au début du jeu." + "enable_eidos_logo": { + "Title": "Activer le logo EIDOS", + "Description": "Active le logo EIDOS au début du jeu." }, "enable_loading_screens": { "Title": "Activer les écrans de chargement", diff --git a/tools/config/TR1X_ConfigTool/Resources/Lang/it.json b/tools/tr1/config/TR1X_ConfigTool/Resources/Lang/it.json similarity index 96% rename from tools/config/TR1X_ConfigTool/Resources/Lang/it.json rename to tools/tr1/config/TR1X_ConfigTool/Resources/Lang/it.json index 37d85b362..9a5250147 100644 --- a/tools/config/TR1X_ConfigTool/Resources/Lang/it.json +++ b/tools/tr1/config/TR1X_ConfigTool/Resources/Lang/it.json @@ -58,10 +58,17 @@ "semi-lock": "Parziale", "no-lock": "Nessuno" }, - "stat_mode": { - "minimal": "Minimo", - "detailed": "Dettagliato", - "full": "Completo" + "underwater_music": { + "full": "Completa", + "quiet": "Attenuata", + "full_no_ambient": "Completa ma senza suoni ambientali", + "quiet_no_ambient": "Attenuata ma senza suoni ambientali", + "none": "Assente" + }, + "music_load_condition": { + "never": "Mai", + "non-ambient": "Non ambientale", + "always": "Sempre" } }, "Properties": { @@ -233,11 +240,11 @@ "Title": "Statistiche del livello nella bussola", "Description": "Abilita la visualizzazione delle statistiche del livello quando è selezionata la bussola." }, - "stat_detail_mode": { - "Title": "Modalità di visualizzazione delle statistiche", - "Description": "Consente di scegliere tra vari livelli di dettaglio per la visualizzazione delle statistiche.\n- Minimo: mostra le uccisioni, gli oggetti raccolti, i segreti e il tempo impiegato per completare il livello.\n- Dettagliato: mostra tutte le statistiche della modalità 'Minimo' e, in aggiunta, il numero massimo di uccisioni e di oggetti recuperabili di ogni livello.\n- Completo: mostra tutte le statistiche della modalità 'Dettagliato' e, in aggiunta, i colpi andati a segno, le munizioni utilizzate, i kit medici adoperati e la distanza percorsa." + "enable_detailed_stats": { + "Title": "Mostra il numero totale di uccisioni e oggetti", + "Description": "Consente di mostrare il numero massimo di uccisioni e oggetti recuperabili di ogni livello." }, - "enable_cutscenes": { + "enable_cine": { "Title": "Abilita le scene di intermezzo", "Description": "Abilita la riproduzione delle scene di intermezzo." }, @@ -249,9 +256,9 @@ "Title": "Abilita gli FMV", "Description": "Abilita la riproduzione dei filmati." }, - "enable_legal": { - "Title": "Abilita il contenuto legale", - "Description": "Abilita il logo EIDOS, il FMV di Core Design e il FMV del codec video Bink all'avvio del gioco." + "enable_eidos_logo": { + "Title": "Abilita il logo EIDOS", + "Description": "Abilita il logo EIDOS all'avvio del gioco." }, "enable_loading_screens": { "Title": "Abilita le schermate di caricamento", diff --git a/tools/config/TR1X_ConfigTool/Resources/TR1X.png b/tools/tr1/config/TR1X_ConfigTool/Resources/TR1X.png similarity index 100% rename from tools/config/TR1X_ConfigTool/Resources/TR1X.png rename to tools/tr1/config/TR1X_ConfigTool/Resources/TR1X.png diff --git a/tools/config/TR1X_ConfigTool/Resources/const.json b/tools/tr1/config/TR1X_ConfigTool/Resources/const.json similarity index 100% rename from tools/config/TR1X_ConfigTool/Resources/const.json rename to tools/tr1/config/TR1X_ConfigTool/Resources/const.json diff --git a/tools/config/TR1X_ConfigTool/Resources/icon.ico b/tools/tr1/config/TR1X_ConfigTool/Resources/icon.ico similarity index 100% rename from tools/config/TR1X_ConfigTool/Resources/icon.ico rename to tools/tr1/config/TR1X_ConfigTool/Resources/icon.ico diff --git a/tools/config/TR1X_ConfigTool/Resources/specification.json b/tools/tr1/config/TR1X_ConfigTool/Resources/specification.json similarity index 98% rename from tools/config/TR1X_ConfigTool/Resources/specification.json rename to tools/tr1/config/TR1X_ConfigTool/Resources/specification.json index a499416cd..afac10419 100644 --- a/tools/config/TR1X_ConfigTool/Resources/specification.json +++ b/tools/tr1/config/TR1X_ConfigTool/Resources/specification.json @@ -62,11 +62,6 @@ "never", "non-ambient", "always" - ], - "stat_mode": [ - "minimal", - "detailed", - "full" ] }, "CategorisedProperties": [ @@ -308,13 +303,12 @@ "DefaultValue": true }, { - "Field": "stat_detail_mode", - "DataType": "Enum", - "EnumKey": "stat_mode", - "DefaultValue": "full" + "Field": "enable_detailed_stats", + "DataType": "Bool", + "DefaultValue": true }, { - "Field": "enable_cutscenes", + "Field": "enable_cine", "DataType": "Bool", "DefaultValue": true }, @@ -329,7 +323,7 @@ "DefaultValue": true }, { - "Field": "enable_legal", + "Field": "enable_eidos_logo", "DataType": "Bool", "DefaultValue": true }, diff --git a/tools/config/TR1X_ConfigTool/TR1X_ConfigTool.csproj b/tools/tr1/config/TR1X_ConfigTool/TR1X_ConfigTool.csproj similarity index 87% rename from tools/config/TR1X_ConfigTool/TR1X_ConfigTool.csproj rename to tools/tr1/config/TR1X_ConfigTool/TR1X_ConfigTool.csproj index 0cf47d537..e6a7a199a 100644 --- a/tools/config/TR1X_ConfigTool/TR1X_ConfigTool.csproj +++ b/tools/tr1/config/TR1X_ConfigTool/TR1X_ConfigTool.csproj @@ -1,16 +1,17 @@ + WinExe net6.0-windows disable - enable true - false + true true TR1X_ConfigTool True app.manifest + true true false @@ -22,8 +23,10 @@ - - + + + + @@ -62,5 +65,9 @@ + + + ..\..\..\config\TRX_ConfigToolLib\bin\Release\publish\TRX_ConfigToolLib.dll + diff --git a/tools/config/TR1X_ConfigTool/app.manifest b/tools/tr1/config/TR1X_ConfigTool/app.manifest similarity index 94% rename from tools/config/TR1X_ConfigTool/app.manifest rename to tools/tr1/config/TR1X_ConfigTool/app.manifest index c9bc2e2ff..ce145c9c0 100644 --- a/tools/config/TR1X_ConfigTool/app.manifest +++ b/tools/tr1/config/TR1X_ConfigTool/app.manifest @@ -10,7 +10,8 @@ version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" - language="*"/> + language="*" + /> diff --git a/tools/shared/docker/config/Dockerfile b/tools/tr1/docker/config/Dockerfile similarity index 56% rename from tools/shared/docker/config/Dockerfile rename to tools/tr1/docker/config/Dockerfile index aa624cdc8..61ca890bb 100644 --- a/tools/shared/docker/config/Dockerfile +++ b/tools/tr1/docker/config/Dockerfile @@ -2,4 +2,4 @@ FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env ENV HOME /app WORKDIR /app -ENTRYPOINT ["/app/tools/shared/docker/config/entrypoint.sh"] +ENTRYPOINT ["/app/tools/tr1/docker/config/entrypoint.sh"] diff --git a/tools/tr1/docker/config/entrypoint.sh b/tools/tr1/docker/config/entrypoint.sh new file mode 100755 index 000000000..42cf78b6f --- /dev/null +++ b/tools/tr1/docker/config/entrypoint.sh @@ -0,0 +1,19 @@ +#!/bin/bash +set -x +set -e + +export DOTNET_CLI_HOME="/tmp/DOTNET_CLI_HOME" +echo $HOME +shopt -s globstar + +# Build the common lib DLL +cd /app/tools/config/ +rm -rf **/bin **/obj +dotnet restore -p:EnableWindowsTargeting=true +dotnet publish -c Release -p:EnableWindowsTargeting=true + +# Build the main executable +cd /app/tools/tr1/config/ +rm -rf **/bin **/obj **/out/* +dotnet restore +dotnet publish -c Release -o out diff --git a/tools/shared/docker/game-linux/Dockerfile b/tools/tr1/docker/game-linux/Dockerfile similarity index 96% rename from tools/shared/docker/game-linux/Dockerfile rename to tools/tr1/docker/game-linux/Dockerfile index ab8e65b40..887ab10f1 100644 --- a/tools/shared/docker/game-linux/Dockerfile +++ b/tools/tr1/docker/game-linux/Dockerfile @@ -1,9 +1,9 @@ -# TRX building toolchain for Linux. +# TR1X building toolchain for Linux. # # This is a multi-stage Docker image. It is designed to keep the final image # size low. Each stage builds an external dependency. The final stage takes the # artifacts (binaries, includes etc.) from previous stages and installs all the -# tools necessary to build TRX. +# tools necessary to build TR1X. FROM ubuntu:latest AS base @@ -121,7 +121,7 @@ RUN sed -i "s/Cflags: .*/\\0 -DGLEW_STATIC /" /ext/lib/pkgconfig/glew.pc -# TRX +# TR1X FROM base # set the build dir - actual files are mounted with a Docker volume @@ -159,4 +159,4 @@ COPY --from=pcre2 /ext/ /ext/ COPY --from=glew /ext/ /ext/ ENV PYTHONPATH=/app/tools/ -ENTRYPOINT ["/app/tools/shared/docker/game-linux/entrypoint.sh"] +ENTRYPOINT ["/app/tools/tr1/docker/game-linux/entrypoint.sh"] diff --git a/tools/tr1/docker/game-linux/entrypoint.sh b/tools/tr1/docker/game-linux/entrypoint.sh new file mode 100755 index 000000000..ca4da0764 --- /dev/null +++ b/tools/tr1/docker/game-linux/entrypoint.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +from pathlib import Path + +from shared.cli.game_docker_entrypoint import run_script + +run_script( + version=1, + platform="linux", + compile_args=[], + release_zip_files=[ + (Path("/app/build/tr1/linux/TR1X"), "TR1X"), + ], + compressable_exes=[ + Path("/app/build/tr1/linux/TR1X"), + ], +) diff --git a/tools/shared/docker/game-win/Dockerfile b/tools/tr1/docker/game-win/Dockerfile similarity index 98% rename from tools/shared/docker/game-win/Dockerfile rename to tools/tr1/docker/game-win/Dockerfile index 65854410a..2d91de504 100644 --- a/tools/shared/docker/game-win/Dockerfile +++ b/tools/tr1/docker/game-win/Dockerfile @@ -183,4 +183,4 @@ ENV PKG_CONFIG_LIBDIR=/ext/lib/ ENV PKG_CONFIG_PATH=/ext/lib/pkgconfig/ ENV C_INCLUDE_PATH=/ext/include/ ENV PYTHONPATH=/app/tools/ -ENTRYPOINT ["/app/tools/shared/docker/game-win/entrypoint.sh"] +ENTRYPOINT ["/app/tools/tr1/docker/game-win/entrypoint.sh"] diff --git a/tools/tr1/docker/game-win/entrypoint.sh b/tools/tr1/docker/game-win/entrypoint.sh new file mode 100755 index 000000000..d5b5ef3f9 --- /dev/null +++ b/tools/tr1/docker/game-win/entrypoint.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +from pathlib import Path + +from shared.cli.game_docker_entrypoint import run_script + +run_script( + version=1, + platform="win", + compile_args=[ + "--cross", + "/app/tools/tr1/docker/game-win/meson_linux_mingw32.txt", + ], + release_zip_files=[ + (Path("/app/build/tr1/win/TR1X.exe"), "TR1X.exe"), + ( + Path("/app/tools/tr1/config/out/TR1X_ConfigTool.exe"), + "TR1X_ConfigTool.exe", + ), + ], + compressable_exes=[ + Path("/app/build/tr1/win/TR1X.exe"), + ], +) diff --git a/tools/shared/docker/game-win/meson_linux_mingw32.txt b/tools/tr1/docker/game-win/meson_linux_mingw32.txt similarity index 100% rename from tools/shared/docker/game-win/meson_linux_mingw32.txt rename to tools/tr1/docker/game-win/meson_linux_mingw32.txt diff --git a/tools/shared/docker/installer/Dockerfile b/tools/tr1/docker/installer/Dockerfile similarity index 55% rename from tools/shared/docker/installer/Dockerfile rename to tools/tr1/docker/installer/Dockerfile index 093e9514b..ea3a8afa4 100644 --- a/tools/shared/docker/installer/Dockerfile +++ b/tools/tr1/docker/installer/Dockerfile @@ -2,4 +2,4 @@ FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env ENV HOME /app WORKDIR /app -ENTRYPOINT ["/app/tools/shared/docker/installer/entrypoint.sh"] +ENTRYPOINT ["/app/tools/tr1/docker/installer/entrypoint.sh"] diff --git a/tools/tr1/docker/installer/entrypoint.sh b/tools/tr1/docker/installer/entrypoint.sh new file mode 100755 index 000000000..fdd1be54f --- /dev/null +++ b/tools/tr1/docker/installer/entrypoint.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -x +set -e + +cd /app/tools/tr1/installer/ + +export DOTNET_CLI_HOME="/tmp/DOTNET_CLI_HOME" + +shopt -s globstar +rm -rf **/bin **/obj **/out/* +dotnet restore +dotnet publish -c Release -o out diff --git a/tools/inspect_save b/tools/tr1/inspect_save similarity index 100% rename from tools/inspect_save rename to tools/tr1/inspect_save diff --git a/tools/tr1/installer/.gitignore b/tools/tr1/installer/.gitignore new file mode 100644 index 000000000..e76c073eb --- /dev/null +++ b/tools/tr1/installer/.gitignore @@ -0,0 +1,3 @@ +bin/ +obj/ +out/ diff --git a/tools/tr1/installer/Installer/App.xaml b/tools/tr1/installer/Installer/App.xaml new file mode 100644 index 000000000..72af0457e --- /dev/null +++ b/tools/tr1/installer/Installer/App.xaml @@ -0,0 +1,8 @@ + + + + + diff --git a/tools/tr1/installer/Installer/App.xaml.cs b/tools/tr1/installer/Installer/App.xaml.cs new file mode 100644 index 000000000..88e5206ff --- /dev/null +++ b/tools/tr1/installer/Installer/App.xaml.cs @@ -0,0 +1,12 @@ +using System.Windows; + +namespace Installer +{ + public partial class App : Application + { + public App() + { + InitializeComponent(); + } + } +} diff --git a/tools/tr1/installer/Installer/Controls/FinishStepControl.xaml b/tools/tr1/installer/Installer/Controls/FinishStepControl.xaml new file mode 100644 index 000000000..ca8905a79 --- /dev/null +++ b/tools/tr1/installer/Installer/Controls/FinishStepControl.xaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + Step 4: Done + + + + Installation complete. To configure more advanced features, you can edit the JSON files in the cfg/ directory with a text editor. + + + + Happy raiding :) + + + + + + diff --git a/tools/tr1/installer/Installer/Controls/FinishStepControl.xaml.cs b/tools/tr1/installer/Installer/Controls/FinishStepControl.xaml.cs new file mode 100644 index 000000000..4e49298de --- /dev/null +++ b/tools/tr1/installer/Installer/Controls/FinishStepControl.xaml.cs @@ -0,0 +1,11 @@ +using System.Windows.Controls; + +namespace Installer.Controls; + +public partial class FinishStepControl : UserControl +{ + public FinishStepControl() + { + InitializeComponent(); + } +} diff --git a/tools/installer/TRX_InstallerLib/Controls/InstallSettingsStepControl.xaml b/tools/tr1/installer/Installer/Controls/InstallSettingsStepControl.xaml similarity index 64% rename from tools/installer/TRX_InstallerLib/Controls/InstallSettingsStepControl.xaml rename to tools/tr1/installer/Installer/Controls/InstallSettingsStepControl.xaml index 1102b4732..55d1bc9af 100644 --- a/tools/installer/TRX_InstallerLib/Controls/InstallSettingsStepControl.xaml +++ b/tools/tr1/installer/Installer/Controls/InstallSettingsStepControl.xaml @@ -1,25 +1,19 @@ - - + d:DesignHeight="450" + d:DesignWidth="800"> - - - - + @@ -30,9 +24,9 @@ - + + Step 2: Installation options + @@ -92,46 +86,54 @@ - - + Download music tracks + - + + Neither the Steam nor GOG versions of the game ship with the + full soundtrack found on the PlayStation or Saturn retail + releases. This option lets you download the missing tracks + automatically (164 MB). The legality of these files is + disputable; the most legal way to import the music to PC is to + rip the audio tracks yourself from a physical PlayStation or + Saturn disc. + - + - - + Download Unfinished Business expansion pack + - + + The Unfinished Business expansion pack was made freeware. However, the Steam and GOG versions do not ship it. This option lets you download the expansion files automatically (6 MB). + - - - - + + - + Import saves - + + Imports existing savegame files. Only TombATI and TR1X savegame format is supported at this time. + - + + Create desktop shortcut + @@ -141,9 +143,9 @@ -