diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 00000000000..69fe82afafb --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,167 @@ +# TheSuperHackers @build JohnsterID 15/09/2025 Add clang-tidy configuration for code quality analysis +--- +# Clang-tidy configuration for GeneralsGameCode project +# This configuration is tailored for a legacy C++98/C++20 hybrid codebase +# with Windows-specific code and COM interfaces + +# Enable specific checks that are appropriate for this codebase +Checks: > + -*, + bugprone-*, + -bugprone-easily-swappable-parameters, + -bugprone-implicit-widening-of-multiplication-result, + -bugprone-narrowing-conversions, + -bugprone-signed-char-misuse, + cert-*, + -cert-dcl21-cpp, + -cert-dcl50-cpp, + -cert-dcl58-cpp, + -cert-env33-c, + -cert-err58-cpp, + clang-analyzer-*, + -clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling, + cppcoreguidelines-*, + -cppcoreguidelines-avoid-c-arrays, + -cppcoreguidelines-avoid-magic-numbers, + -cppcoreguidelines-avoid-non-const-global-variables, + -cppcoreguidelines-init-variables, + -cppcoreguidelines-macro-usage, + -cppcoreguidelines-no-malloc, + -cppcoreguidelines-owning-memory, + -cppcoreguidelines-pro-bounds-array-to-pointer-decay, + -cppcoreguidelines-pro-bounds-constant-array-index, + -cppcoreguidelines-pro-bounds-pointer-arithmetic, + -cppcoreguidelines-pro-type-cstyle-cast, + -cppcoreguidelines-pro-type-reinterpret-cast, + -cppcoreguidelines-pro-type-union-access, + -cppcoreguidelines-pro-type-vararg, + -cppcoreguidelines-special-member-functions, + google-*, + -google-build-using-namespace, + -google-explicit-constructor, + -google-readability-casting, + -google-readability-todo, + -google-runtime-int, + -google-runtime-references, + hicpp-*, + -hicpp-avoid-c-arrays, + -hicpp-explicit-conversions, + -hicpp-no-array-decay, + -hicpp-signed-bitwise, + -hicpp-special-member-functions, + -hicpp-uppercase-literal-suffix, + -hicpp-use-auto, + -hicpp-vararg, + misc-*, + -misc-const-correctness, + -misc-include-cleaner, + -misc-non-private-member-variables-in-classes, + -misc-use-anonymous-namespace, + modernize-*, + -modernize-avoid-c-arrays, + -modernize-concat-nested-namespaces, + -modernize-loop-convert, + -modernize-pass-by-value, + -modernize-raw-string-literal, + -modernize-return-braced-init-list, + -modernize-use-auto, + -modernize-use-default-member-init, + -modernize-use-nodiscard, + -modernize-use-trailing-return-type, + performance-*, + -performance-avoid-endl, + portability-*, + readability-*, + -readability-avoid-const-params-in-decls, + -readability-braces-around-statements, + -readability-convert-member-functions-to-static, + -readability-function-cognitive-complexity, + -readability-identifier-length, + -readability-implicit-bool-conversion, + -readability-isolate-declaration, + -readability-magic-numbers, + -readability-named-parameter, + -readability-redundant-access-specifiers, + -readability-uppercase-literal-suffix + +# Treat warnings as errors for CI/CD +WarningsAsErrors: false + +# Header filter to include project headers +HeaderFilterRegex: '(Core|Generals|GeneralsMD|Dependencies)/.*\.(h|hpp)$' + +# Check options for specific rules +CheckOptions: + # Naming conventions - adapted for the existing codebase style + - key: readability-identifier-naming.ClassCase + value: CamelCase + - key: readability-identifier-naming.StructCase + value: CamelCase + - key: readability-identifier-naming.FunctionCase + value: CamelCase + - key: readability-identifier-naming.MethodCase + value: CamelCase + - key: readability-identifier-naming.VariableCase + value: lower_case + - key: readability-identifier-naming.ParameterCase + value: lower_case + - key: readability-identifier-naming.MemberCase + value: lower_case + - key: readability-identifier-naming.MemberPrefix + value: m_ + - key: readability-identifier-naming.ConstantCase + value: UPPER_CASE + - key: readability-identifier-naming.EnumConstantCase + value: UPPER_CASE + - key: readability-identifier-naming.MacroDefinitionCase + value: UPPER_CASE + + # Performance settings + - key: performance-for-range-copy.WarnOnAllAutoCopies + value: true + - key: performance-unnecessary-value-param.AllowedTypes + value: 'AsciiString;UnicodeString;Utf8String;Utf16String' + + # Modernize settings - be conservative for legacy code + - key: modernize-use-nullptr.NullMacros + value: 'NULL' + - key: modernize-replace-auto-ptr.IncludeStyle + value: llvm + + # Readability settings + - key: readability-function-size.LineThreshold + value: 150 + - key: readability-function-size.StatementThreshold + value: 100 + - key: readability-function-size.BranchThreshold + value: 25 + - key: readability-function-size.ParameterThreshold + value: 8 + - key: readability-function-size.NestingThreshold + value: 6 + + # Bugprone settings + - key: bugprone-argument-comment.StrictMode + value: false + - key: bugprone-suspicious-string-compare.WarnOnImplicitComparison + value: true + - key: bugprone-suspicious-string-compare.WarnOnLogicalNotComparison + value: true + + # Google style settings + - key: google-readability-braces-around-statements.ShortStatementLines + value: 2 + - key: google-readability-function-size.StatementThreshold + value: 100 + + # CERT settings + - key: cert-dcl16-c.NewSuffixes + value: 'L;LL;LU;LLU' + - key: cert-oop54-cpp.WarnOnlyIfThisHasSuspiciousField + value: false + +# Use .clang-format for formatting suggestions +FormatStyle: file + +# Exclude certain directories and files +# Note: This is handled by HeaderFilterRegex above, but can be extended diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000000..b944333a0b6 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +root=true + +[*] +insert_final_newline = true +trim_trailing_whitespace = true + +[{*.h,*.cpp,*.inl}] +indent_style = unset +indent_size = 2 + +[{CMakeLists.txt,*.cmake,*.py}] +indent_style = spaces +indent_size = 4 diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000000..de2ca2326e4 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,133 @@ +# AI Coding Agent Instructions + +## Project Overview + +This is the **GeneralsGameCode** project - a community-driven effort to fix and improve the classic RTS games *Command & Conquer: Generals* and *Zero Hour*. The codebase has been modernized from Visual Studio 6/C++98 to Visual Studio 2022/C++20 while maintaining retail compatibility. + +## Architecture + +### Dual Game Structure +- **Generals/**: Original C&C Generals (v1.08) codebase +- **GeneralsMD/**: Zero Hour expansion (v1.04) codebase - **primary focus** +- **Core/**: Shared game engine and libraries used by both games + +### Key Components +- **Core/GameEngine/**: Base game engine with GameClient/GameLogic separation +- **Core/Libraries/**: Internal libraries including WWVegas graphics framework +- **Core/GameEngineDevice/**: Platform-specific rendering (DirectX 8) +- **Core/Tools/**: Development tools (W3DView, texture compression, etc.) +- **Dependencies/**: External dependencies (MaxSDK for VC6, utilities) + +## Build System + +### CMake Presets (Critical) +- **vc6**: Visual Studio 6 compatible build (retail compatibility required) +- **win32**: Modern Visual Studio 2022 build +- **vc6-debug/vc6-profile**: Debug/profiling variants +- Use `cmake --preset ` followed by `cmake --build build/` + +### Build Commands +```bash +# Configure with specific preset +cmake --preset vc6 + +# Build (from project root) +cmake --build build/vc6 + +# Build with tools and extras +cmake --build build/vc6 --target _tools _extras +``` + +### Retail Compatibility +- VC6 builds are required for replay compatibility testing +- Debug builds break retail compatibility +- Use RTS_BUILD_OPTION_DEBUG=OFF for compatibility testing + +## Development Workflow + +### Code Change Documentation +**Every user-facing change requires TheSuperHackers comment format:** +```cpp +// TheSuperHackers @keyword author DD/MM/YYYY Description +``` + +Common keywords: `@bugfix`, `@feature`, `@performance`, `@refactor`, `@tweak`, `@build` + +### Pull Request Guidelines +- Title format: `type: Description starting with action verb` +- Types: `bugfix:`, `feat:`, `fix:`, `refactor:`, `perf:`, `build:` +- Zero Hour changes take precedence over Generals +- Changes must be identical between both games when applicable + +### Code Style +- Maintain consistency with surrounding legacy code +- Prefer C++98 style unless modern features add significant value +- No big refactors mixed with logical changes +- Use present tense in documentation ("Fixes" not "Fixed") + +## Testing + +### Replay Compatibility Testing +Located in `GeneralsReplays/` - critical for ensuring retail compatibility: +```bash +generalszh.exe -jobs 4 -headless -replay subfolder/*.rep +``` +- Requires VC6 optimized build with RTS_BUILD_OPTION_DEBUG=OFF +- Copies replays to `%USERPROFILE%/Documents/Command and Conquer Generals Zero Hour Data/Replays` +- CI automatically tests GeneralsMD builds against known replays + +### Build Validation +- CI tests multiple presets: vc6, vc6-profile, vc6-debug, win32 variants +- Path-based change detection triggers relevant builds +- Tools and extras are built with `+t+e` flags + +## Common Patterns + +### Memory Management +- Manual memory management (delete/delete[]) - this is legacy C++98 code +- STLPort for VC6 compatibility (see `cmake/stlport.cmake`) + +### Game Engine Separation +- **GameLogic**: Game state, rules, simulation +- **GameClient**: Rendering, UI, platform-specific code +- Clean separation maintained for potential future networking + +### Module Structure +``` +Core/ +├── GameEngine/Include/Common/ # Shared interfaces +├── GameEngine/Include/GameLogic/ # Game simulation +├── GameEngine/Include/GameClient/ # Rendering/UI +├── Libraries/Include/rts/ # RTS-specific utilities +└── Libraries/Source/WWVegas/ # Graphics framework +``` + +## External Dependencies + +### Required for Building +- **VC6 builds**: Requires MSVC 6.0 toolchain (automated in CI via itsmattkc/MSVC600) +- **Modern builds**: Visual Studio 2022, Ninja generator +- **vcpkg** (optional): zlib, ffmpeg for enhanced builds + +### Platform-Specific +- **Windows**: DirectX 8, Miles Sound System, Bink Video +- **Registry detection**: Automatic game install path detection from EA registry keys + +## Tools and Utilities + +### Development Scripts (`scripts/cpp/`) +- `fixInludesCase.sh`: Fix include case sensitivity +- `refactor_*.py`: Code refactoring utilities +- `remove_trailing_whitespace.py`: Code cleanup + +### Build Tools +- W3DView: 3D model viewer +- TextureCompress: Asset optimization +- MapCacheBuilder: Map preprocessing + +## Key Files to Understand +- `CMakePresets.json`: All build configurations +- `cmake/config-build.cmake`: Build options and feature flags +- `Core/GameEngine/Include/`: Core engine interfaces +- `**/Code/Main/WinMain.cpp`: Application entry points +- `GeneralsReplays/`: Compatibility test data diff --git a/.github/workflows/build-toolchain.yml b/.github/workflows/build-toolchain.yml index 8413ff68041..6dcad70e0de 100644 --- a/.github/workflows/build-toolchain.yml +++ b/.github/workflows/build-toolchain.yml @@ -28,9 +28,15 @@ on: jobs: build: - name: Preset ${{ inputs.preset }}${{ inputs.tools && '+t' || '' }}${{ inputs.extras && '+e' || '' }} - runs-on: windows-latest - timeout-minutes: 40 + name: ${{ inputs.preset }}${{ inputs.tools && '+t' || '' }}${{ inputs.extras && '+e' || '' }} + runs-on: windows-2022 + timeout-minutes: 30 + + env: + VCPKG_FILE_CACHE: ${{ github.workspace }}\vcpkg-bincache + VCPKG_BINARY_SOURCES: clear;files,${{ github.workspace }}\vcpkg-bincache,readwrite + VCPKG_FEATURE_FLAGS: manifests,versions,binarycaching + steps: - name: Checkout Code uses: actions/checkout@v4 @@ -41,41 +47,38 @@ jobs: uses: actions/cache@v4 with: path: C:\VC6 - key: vc6-permanent-cache-v1 + key: vc6-permanent-cache-v2 - name: Cache CMake Dependencies id: cache-cmake-deps uses: actions/cache@v4 with: path: build\${{ inputs.preset }}\_deps - key: cmake-deps-${{ inputs.preset }}-${{ hashFiles('cmake/**/*.cmake', '**/CMakeLists.txt') }} - restore-keys: | - cmake-deps-${{ inputs.preset }}- + key: cmake-deps-${{ inputs.preset }}-${{ hashFiles('CMakePresets.json','cmake/**/*.cmake','**/CMakeLists.txt') }} - - name: Download VC6 Portable from Cloudflare R2 + - name: Download VC6 Portable from itsmattkc repo if: ${{ startsWith(inputs.preset, 'vc6') && steps.cache-vc6.outputs.cache-hit != 'true' }} env: - AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} - AWS_ENDPOINT_URL: ${{ secrets.R2_ENDPOINT_URL }} - EXPECTED_HASH: "118D0F1ACBBD70C3F8B081CA4DBAF955FE0C6C359A76636E930AA89FDC551091" + EXPECTED_HASH: "D0EE1F6DCEF7DB3AD703120D9FB4FAD49EBCA28F44372E40550348B1C00CA583" + COMMIT: "001c4bafdcf2ef4b474d693acccd35a91e848f40" shell: pwsh run: | Write-Host "Downloading VC6 Portable Installation" -ForegroundColor Cyan - aws s3 cp s3://github-ci/VS6_VisualStudio6.7z VS6_VisualStudio6.7z --endpoint-url $env:AWS_ENDPOINT_URL + Invoke-WebRequest -Uri https://github.com/itsmattkc/MSVC600/archive/$env:COMMIT.zip -OutFile VS6_VisualStudio6.zip Write-Host "Verifying File Integrity" -ForegroundColor Cyan - $fileHash = (Get-FileHash -Path VS6_VisualStudio6.7z -Algorithm SHA256).Hash + $fileHash = (Get-FileHash -Path VS6_VisualStudio6.zip -Algorithm SHA256).Hash Write-Host "Downloaded file SHA256: $fileHash" Write-Host "Expected file SHA256: $env:EXPECTED_HASH" - if ($hash -ne $env:EXPECTED_HASH) { + if ($fileHash -ne $env:EXPECTED_HASH) { Write-Error "Hash verification failed! File may be corrupted or tampered with." exit 1 } Write-Host "Extracting Archive" -ForegroundColor Cyan - & 7z x VS6_VisualStudio6.7z -oC:\VC6 - Remove-Item VS6_VisualStudio6.7z -Verbose + & Expand-Archive -Path VS6_VisualStudio6.zip -DestinationPath C:\VC6 + Move-Item -Path C:\VC6\MSVC600-$env:COMMIT -Destination C:\VC6\VC6SP6 + Remove-Item VS6_VisualStudio6.zip -Verbose - name: Set Up VC6 Environment if: startsWith(inputs.preset, 'vc6') @@ -102,15 +105,65 @@ jobs: with: arch: x86 + - name: Compute vcpkg cache key parts + if: startsWith(inputs.preset, 'win32') + id: vcpkg_key + shell: pwsh + run: | + $baseline = (Get-Content vcpkg.json | ConvertFrom-Json)."builtin-baseline" + + $msvc = $env:VCToolsVersion + if (-not $msvc) { $msvc = "unknown" } + + # Reduce churn: keep major.minor (e.g. 14.44) + $msvcMajorMinor = ($msvc -split '\.')[0..1] -join '.' + + $triplet = "x86-windows" + if ("${{ inputs.preset }}" -like "x64*") { $triplet = "x64-windows" } + + "baseline=$baseline" >> $env:GITHUB_OUTPUT + "msvc=$msvcMajorMinor" >> $env:GITHUB_OUTPUT + "triplet=$triplet" >> $env:GITHUB_OUTPUT + + Write-Host "vcpkg cache key parts: baseline=$baseline, msvc=$msvcMajorMinor, triplet=$triplet" + + - name: Restore vcpkg binary cache + if: startsWith(inputs.preset, 'win32') + id: vcpkg_cache + uses: actions/cache/restore@v4 + with: + path: ${{ github.workspace }}\vcpkg-bincache + key: vcpkg-bincache-v2-${{ runner.os }}-msvc${{ steps.vcpkg_key.outputs.msvc }}-baseline${{ steps.vcpkg_key.outputs.baseline }}-${{ steps.vcpkg_key.outputs.triplet }} + restore-keys: | + vcpkg-bincache-v2-${{ runner.os }}-msvc${{ steps.vcpkg_key.outputs.msvc }}-baseline${{ steps.vcpkg_key.outputs.baseline }}- + vcpkg-bincache-v2-${{ runner.os }}- + - name: Setup vcpkg uses: lukka/run-vcpkg@v11 + with: + runVcpkgInstall: false + doNotCache: true + + - name: Configure vcpkg to use cached directory + if: startsWith(inputs.preset, 'win32') + shell: pwsh + run: | + $cacheDir = "${{ github.workspace }}\vcpkg-bincache" + New-Item -ItemType Directory -Force -Path $cacheDir | Out-Null + + # lukka/run-vcpkg sets its own temp cache dir; override to force our cached dir + $env:VCPKG_DEFAULT_BINARY_CACHE = $cacheDir + $env:VCPKG_BINARY_SOURCES = "clear;files,$cacheDir,readwrite" + + "VCPKG_DEFAULT_BINARY_CACHE=$cacheDir" >> $env:GITHUB_ENV + "VCPKG_BINARY_SOURCES=$env:VCPKG_BINARY_SOURCES" >> $env:GITHUB_ENV - name: Configure ${{ inputs.game }} with CMake Using ${{ inputs.preset }}${{ inputs.tools && '+t' || '' }}${{ inputs.extras && '+e' || '' }} Preset shell: pwsh run: | $buildFlags = @( - "-DRTS_BUILD_ZEROHOUR=${{ inputs.game == 'GeneralsMD' && 'ON' || 'OFF' }}", - "-DRTS_BUILD_GENERALS=${{ inputs.game == 'Generals' && 'ON' || 'OFF' }}" + "-DRTS_BUILD_ZEROHOUR=${{ inputs.game == 'GeneralsMD' && 'ON' || 'OFF' }}", + "-DRTS_BUILD_GENERALS=${{ inputs.game == 'Generals' && 'ON' || 'OFF' }}" ) $gamePrefix = "${{ inputs.game == 'Generals' && 'GENERALS' || 'ZEROHOUR' }}" @@ -120,7 +173,6 @@ jobs: $buildFlags += "-DRTS_BUILD_${gamePrefix}_EXTRAS=${{ inputs.extras && 'ON' || 'OFF' }}" Write-Host "Build flags: $($buildFlags -join ' | ')" - cmake --preset ${{ inputs.preset }} $buildFlags - name: Build ${{ inputs.game }} with CMake Using ${{ inputs.preset }}${{ inputs.tools && '+t' || '' }}${{ inputs.extras && '+e' || '' }} Preset @@ -128,19 +180,29 @@ jobs: run: | cmake --build --preset ${{ inputs.preset }} + - name: Save vcpkg binary cache + # Only one job should save to avoid "Unable to reserve cache" conflicts. + if: ${{ startsWith(inputs.preset, 'win32') && steps.vcpkg_cache.outputs.cache-hit != 'true' && inputs.game == 'Generals' && inputs.preset == 'win32-vcpkg-debug' }} + uses: actions/cache/save@v4 + with: + path: ${{ github.workspace }}\vcpkg-bincache + key: vcpkg-bincache-v2-${{ runner.os }}-msvc${{ steps.vcpkg_key.outputs.msvc }}-baseline${{ steps.vcpkg_key.outputs.baseline }}-${{ steps.vcpkg_key.outputs.triplet }} + - name: Collect ${{ inputs.game }} ${{ inputs.preset }}${{ inputs.tools && '+t' || '' }}${{ inputs.extras && '+e' || '' }} Artifact shell: pwsh run: | $buildDir = "build\${{ inputs.preset }}" $artifactsDir = New-Item -ItemType Directory -Force -Path "$buildDir\${{ inputs.game }}\artifacts" -Verbose - if ("${{ inputs.preset }}" -like "win32*") { - # For win32 preset, look in config-specific subdirectories + if ("${{ inputs.preset }}" -like "win32*") { $configToUse = if ("${{ inputs.preset }}" -match "debug") { "Debug" } else { "Release" } - $files = Get-ChildItem -Path "$buildDir\Core\$configToUse","$buildDir\${{ inputs.game }}\$configToUse" -File | Where-Object { $_.Extension -in @(".exe", ".dll", ".pdb") } -Verbose - } else { - $files = Get-ChildItem -Path "$buildDir\Core","$buildDir\${{ inputs.game }}" -File | Where-Object { $_.Extension -in @(".exe", ".dll", ".pdb") } -Verbose + $files = Get-ChildItem -Path "$buildDir\Core\$configToUse","$buildDir\${{ inputs.game }}\$configToUse" -File | + Where-Object { $_.Extension -in @(".exe", ".dll", ".pdb") } -Verbose + } else { + $files = Get-ChildItem -Path "$buildDir\Core","$buildDir\${{ inputs.game }}" -File | + Where-Object { $_.Extension -in @(".exe", ".dll", ".pdb") } -Verbose } + $files | Move-Item -Destination $artifactsDir -Verbose -Force - name: Upload ${{ inputs.game }} ${{ inputs.preset }}${{ inputs.tools && '+t' || '' }}${{ inputs.extras && '+e' || '' }} Artifact diff --git a/.github/workflows/check-replays.yml b/.github/workflows/check-replays.yml new file mode 100644 index 00000000000..0f10f67d85b --- /dev/null +++ b/.github/workflows/check-replays.yml @@ -0,0 +1,242 @@ +name: Check Replays + +permissions: + contents: read + pull-requests: write + +on: + workflow_call: + inputs: + game: + required: true + type: string + description: "Game to check (only GeneralsMD for now)" + userdata: + required: true + type: string + description: "Path to folder with replays and maps" + preset: + required: true + type: string + description: "CMake preset" + +jobs: + build: + name: ${{ inputs.preset }} + runs-on: windows-2022 + timeout-minutes: 15 + env: + GAME_PATH: C:\GameData + GENERALS_PATH: C:\GameData\Generals + GENERALSMD_PATH: C:\GameData\GeneralsMD + steps: + - name: Checkout Code + uses: actions/checkout@v4 + with: + submodules: true + + - name: Download Game Artifact + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.game }}-${{ inputs.preset }} + path: build + + - name: Cache Game Data + id: cache-gamedata + uses: actions/cache@v4 + with: + path: ${{ env.GAME_PATH }} + key: gamedata-permanent-cache-v4 + + - name: Download Game Data from Cloudflare R2 + if: ${{ steps.cache-gamedata.outputs.cache-hit != 'true' }} + env: + AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} + AWS_ENDPOINT_URL: ${{ secrets.R2_ENDPOINT_URL }} + EXPECTED_HASH_GENERALS: "37A351AA430199D1F05DEB9E404857DCE7B461A6AC272C5D4A0B5652CDB06372" + EXPECTED_HASH_GENERALSMD: "6837FE1E3009A4C239406C39B1598216C0943EE8ED46BB10626767029AC05E21" + shell: pwsh + run: | + # Download trimmed gamedata of both Generals 1.08 and Generals Zero Hour 1.04. + # This data cannot be used for playing because it's + # missing textures, audio and gui files. But it's enough for replay checking. + # It's also encrypted because it's not allowed to distribute these files. + + if (-not $env:AWS_ACCESS_KEY_ID -or -not $env:AWS_SECRET_ACCESS_KEY -or -not $env:AWS_ENDPOINT_URL) { + $ok1 = [bool]$env:AWS_ACCESS_KEY_ID + $ok2 = [bool]$env:AWS_SECRET_ACCESS_KEY + $ok3 = [bool]$env:AWS_ENDPOINT_URL + Write-Host "One or more required secrets are not set or are empty. R2_ACCESS_KEY_ID: $ok1, R2_SECRET_ACCESS_KEY: $ok2, R2_ENDPOINT_URL: $ok3" + exit 1 + } + + # Download Generals Game Files + # The archive contains these files: + # BINKW32.DLL + # English.big + # INI.big + # Maps.big + # mss32.dll + # W3D.big + # Data\Scripts\MultiplayerScripts.scb + # Data\Scripts\SkirmishScripts.scb + + Write-Host "Downloading Game Data for Generals" -ForegroundColor Cyan + aws s3 cp s3://github-ci/generals108_gamedata_trimmed.7z generals108_gamedata_trimmed.7z --endpoint-url $env:AWS_ENDPOINT_URL + + Write-Host "Verifying File Integrity" -ForegroundColor Cyan + $fileHash = (Get-FileHash -Path generals108_gamedata_trimmed.7z -Algorithm SHA256).Hash + Write-Host "Downloaded file SHA256: $fileHash" + Write-Host "Expected file SHA256: $env:EXPECTED_HASH_GENERALS" + if ($fileHash -ne $env:EXPECTED_HASH_GENERALS) { + Write-Error "Hash verification failed! File may be corrupted or tampered with." + exit 1 + } + + Write-Host "Extracting Archive" -ForegroundColor Cyan + $extractPath = $env:GENERALS_PATH + & 7z x generals108_gamedata_trimmed.7z -o"$extractPath" + Remove-Item generals108_gamedata_trimmed.7z -Verbose + + # Download GeneralsMD (ZH) Game Files + # The archive contains these files: + # BINKW32.DLL + # INIZH.big + # MapsZH.big + # mss32.dll + # W3DZH.big + # Data\Scripts\MultiplayerScripts.scb + # Data\Scripts\Scripts.ini + # Data\Scripts\SkirmishScripts.scb + + Write-Host "Downloading Game Data for GeneralsMD" -ForegroundColor Cyan + aws s3 cp s3://github-ci/zerohour104_gamedata_trimmed.7z zerohour104_gamedata_trimmed.7z --endpoint-url $env:AWS_ENDPOINT_URL + + Write-Host "Verifying File Integrity" -ForegroundColor Cyan + $fileHash = (Get-FileHash -Path zerohour104_gamedata_trimmed.7z -Algorithm SHA256).Hash + Write-Host "Downloaded file SHA256: $fileHash" + Write-Host "Expected file SHA256: $env:EXPECTED_HASH_GENERALSMD" + if ($fileHash -ne $env:EXPECTED_HASH_GENERALSMD) { + Write-Error "Hash verification failed! File may be corrupted or tampered with." + exit 1 + } + + Write-Host "Extracting Archive" -ForegroundColor Cyan + $extractPath = $env:GENERALSMD_PATH + & 7z x zerohour104_gamedata_trimmed.7z -o"$extractPath" + Remove-Item zerohour104_gamedata_trimmed.7z -Verbose + + - name: Set Up Game Data + shell: pwsh + run: | + $source = "$env:GAME_PATH\${{ inputs.game }}" + $destination = "build" + Copy-Item -Path $source\* -Destination $destination -Recurse -Force + + - name: Set Generals InstallPath in Registry + shell: pwsh + run: | + # Zero Hour loads some Generals files and needs this registry key to find the + # Generals data files. + + $regPath = "HKCU:\SOFTWARE\Electronic Arts\EA Games\Generals" + $installPath = "$env:GENERALS_PATH\" + + # Ensure the key exists + if (-not (Test-Path $regPath)) { + New-Item -Path $regPath -Force | Out-Null + } + + # Set the InstallPath value + Set-ItemProperty -Path $regPath -Name InstallPath -Value $installPath -Type String + Write-Host "Registry key set: $regPath -> InstallPath = $installPath" + + - name: Move Replays and Maps to User Dir + shell: pwsh + run: | + # These files are expected in the user dir, so we move them here. + + $source = "${{ inputs.userdata }}\Replays" + $destination = "$env:USERPROFILE\Documents\Command and Conquer Generals Zero Hour Data\Replays" + Write-Host "Move replays to $destination" + New-Item -ItemType Directory -Path $destination -Force | Out-Null + Move-Item -Path "$source\*" -Destination $destination -Force + + $source = "${{ inputs.userdata }}\Maps" + $destination = "$env:USERPROFILE\Documents\Command and Conquer Generals Zero Hour Data\Maps" + Write-Host "Move maps to $destination" + New-Item -ItemType Directory -Path $destination -Force | Out-Null + Move-Item -Path "$source\*" -Destination $destination -Force + + - name: Run Replay Compatibility Tests + shell: pwsh + run: | + $exePath = "build/generalszh.exe" + $arguments = "-jobs 4 -headless -replay *.rep" + $timeoutSeconds = 10*60 + $stdoutPath = "stdout.log" + $stderrPath = "stderr.log" + + if (-not (Test-Path $exePath)) { + Write-Host "ERROR: Executable not found at $exePath" + exit 1 + } + + # Note that the game is a gui application. That means we need to redirect console output to a file + # in order to retrieve it. + # Clean previous logs + Remove-Item $stdoutPath, $stderrPath -ErrorAction SilentlyContinue + + # Start the process + Write-Host "Run $exePath $arguments" + $process = Start-Process -FilePath $exePath ` + -ArgumentList $arguments ` + -RedirectStandardOutput $stdoutPath ` + -RedirectStandardError $stderrPath ` + -PassThru + + # Wait with timeout + $exited = $process.WaitForExit($timeoutSeconds * 1000) + + if (-not $exited) { + Write-Host "ERROR: Process still running after $timeoutSeconds seconds. Killing process..." + Stop-Process -Id $process.Id -Force + } + + # Read output + Write-Host "=== STDOUT ===" + Get-Content $stdoutPath + + if ((Test-Path $stderrPath) -and (Get-Item $stderrPath).Length -gt 0) { + Write-Host "`n=== STDERR ===" + Get-Content $stderrPath + } + + if (-not $exited) { + exit 1 + } + + # Check exit code + $exitCode = $process.ExitCode + + # The above doesn't work on all Windows versions. If not, try this: (see https://stackoverflow.com/a/16018287) + #$process.HasExited | Out-Null # Needs to be called for the command below to work correctly + #$exitCode = $process.GetType().GetField('exitCode', 'NonPublic, Instance').GetValue($process) + #Write-Host "exit code $exitCode" + + if ($exitCode -ne 0) { + Write-Host "ERROR: Process failed with exit code $exitCode" + exit $exitCode + } + + Write-Host "Success!" + + - name: Upload Debug Log + if: always() + uses: actions/upload-artifact@v4 + with: + name: Replay-Debug-Log-${{ inputs.preset }} + path: build/DebugLogFile*.txt + retention-days: 30 + if-no-files-found: ignore diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cd16f177f09..0420bc2e276 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,8 +41,7 @@ jobs: generalsmd: - 'GeneralsMD/**' shared: - - '.github/workflows/build-toolchain.yml' - - '.github/workflows/ci.yml' + - '.github/workflows/**' - 'CMakeLists.txt' - 'CMakePresets.json' - 'cmake/**' @@ -69,19 +68,22 @@ jobs: - preset: "vc6-profile" tools: true extras: true - - preset: "vc6-internal" + - preset: "vc6-debug" tools: true extras: true - - preset: "vc6-debug" + - preset: "win32" tools: true extras: true - - preset: "win32-vcpkg" + - preset: "win32-profile" tools: true extras: true - - preset: "win32-vcpkg-profile" + - preset: "win32-debug" + tools: true + extras: true + - preset: "win32-vcpkg" tools: true extras: true - - preset: "win32-vcpkg-internal" + - preset: "win32-vcpkg-profile" tools: true extras: true - preset: "win32-vcpkg-debug" @@ -96,7 +98,9 @@ jobs: extras: ${{ matrix.extras }} secrets: inherit - build-generalsmd: + # Note build-generalsmd is split into two jobs for vc6 and win32 because replaycheck-generalsmd + # only requires the vc6 build and compiling vc6 is much faster than win32 + build-generalsmd-vc6: name: Build GeneralsMD${{ matrix.preset && '' }} needs: detect-changes if: ${{ github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.generalsmd == 'true' || needs.detect-changes.outputs.shared == 'true' }} @@ -109,22 +113,44 @@ jobs: - preset: "vc6-profile" tools: true extras: true - - preset: "vc6-internal" + - preset: "vc6-debug" tools: true extras: true - - preset: "vc6-debug" + - preset: "vc6-releaselog" tools: true extras: true + fail-fast: false + uses: ./.github/workflows/build-toolchain.yml + with: + game: "GeneralsMD" + preset: ${{ matrix.preset }} + tools: ${{ matrix.tools }} + extras: ${{ matrix.extras }} + secrets: inherit + + build-generalsmd-win32: + name: Build GeneralsMD${{ matrix.preset && '' }} + needs: detect-changes + if: ${{ github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.generalsmd == 'true' || needs.detect-changes.outputs.shared == 'true' }} + strategy: + matrix: + include: - preset: "win32" tools: true extras: true - preset: "win32-profile" tools: true extras: true - - preset: "win32-internal" + - preset: "win32-debug" tools: true extras: true - - preset: "win32-debug" + - preset: "win32-vcpkg" + tools: true + extras: true + - preset: "win32-vcpkg-profile" + tools: true + extras: true + - preset: "win32-vcpkg-debug" tools: true extras: true fail-fast: false @@ -135,3 +161,20 @@ jobs: tools: ${{ matrix.tools }} extras: ${{ matrix.extras }} secrets: inherit + + replaycheck-generalsmd: + name: Replay Check GeneralsMD${{ matrix.preset && '' }} + needs: build-generalsmd-vc6 + if: ${{ github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.generalsmd == 'true' || needs.detect-changes.outputs.shared == 'true' }} + strategy: + matrix: + include: + - preset: "vc6+t+e" + - preset: "vc6-releaselog+t+e" # optimized build with logging and crashing enabled should be compatible, so we test that here. + fail-fast: false + uses: ./.github/workflows/check-replays.yml + with: + game: "GeneralsMD" + userdata: "GeneralsReplays/GeneralsZH/1.04" + preset: ${{ matrix.preset }} + secrets: inherit diff --git a/.github/workflows/weekly-release.yml b/.github/workflows/weekly-release.yml new file mode 100644 index 00000000000..c127d9e5f02 --- /dev/null +++ b/.github/workflows/weekly-release.yml @@ -0,0 +1,210 @@ +name: Weekly Release + +permissions: + contents: write + pull-requests: write + +on: + workflow_dispatch: + inputs: + build_notes: + description: 'Build notes (optional)' + required: false + default: '' + type: string + known_issues: + description: 'Known issues (optional)' + required: false + default: '' + type: string + force_changed: + description: 'Force build' + required: false + default: 'false' + type: choice + options: + - 'false' + - 'true' + pre-release: + description: 'Mark release as pre-release' + required: false + default: 'false' + type: choice + options: + - 'false' + - 'true' + + schedule: + - cron: '0 9 * * 5' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + get-date: + runs-on: ubuntu-latest + outputs: + date: ${{ steps.date.outputs.date }} + steps: + - name: Get current date + id: date + run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT + + detect-scm-changes: + needs: [get-date] + runs-on: ubuntu-latest + outputs: + changed: ${{ steps.check.outputs.changed }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true + - id: check + run: | + if [ "${{ github.event.inputs.force_changed }}" = "true" ]; then + echo "changed=true" >> $GITHUB_OUTPUT + exit 0 + fi + + echo LAST TAG: + git describe --tags --abbrev=0 2>/dev/null || echo "" + + LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") + if [ -z "$LAST_TAG" ]; then + echo "changed=true" >> $GITHUB_OUTPUT + exit 0 + fi + CHANGED=$(git diff --name-only $LAST_TAG..HEAD | grep -v '.github/workflows/' | wc -l) + if [ "$CHANGED" -eq "0" ]; then + echo "changed=false" >> $GITHUB_OUTPUT + else + echo "changed=true" >> $GITHUB_OUTPUT + fi + + build-generals: + needs: [detect-scm-changes, get-date] + if: needs.detect-scm-changes.outputs.changed == 'true' + name: Build Generals${{ matrix.preset && '' }} + strategy: + matrix: + include: + - preset: "vc6-weekly" + tools: true + extras: false + release: true + fail-fast: false + uses: ./.github/workflows/build-toolchain.yml + with: + game: "Generals" + preset: ${{ matrix.preset }} + tools: ${{ matrix.tools }} + extras: ${{ matrix.extras }} + secrets: inherit + + build-generalsmd: + needs: [detect-scm-changes, get-date] + if: needs.detect-scm-changes.outputs.changed == 'true' + name: Build GeneralsMD${{ matrix.preset && '' }} + strategy: + matrix: + include: + - preset: "vc6-weekly" + tools: true + extras: false + release: true + fail-fast: false + uses: ./.github/workflows/build-toolchain.yml + with: + game: "GeneralsMD" + preset: ${{ matrix.preset }} + tools: ${{ matrix.tools }} + extras: ${{ matrix.extras }} + secrets: inherit + + create-release: + name: Create Release + needs: [build-generals, build-generalsmd, get-date] + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true + + - name: Collect commits since last release + id: changelog + run: | + LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") + if [ -z "$LAST_TAG" ]; then + CHANGELOG_COMMITS=$(git log --pretty="format:- %s" --no-merges HEAD | head -n 10 || true) + else + CHANGELOG_COMMITS=$(git log --pretty="format:- %s" --no-merges "$LAST_TAG"..HEAD || true) + fi + if [ -z "$CHANGELOG_COMMITS" ]; then + CHANGELOG_COMMITS="- No relevant changes detected since the last release." + fi + { + echo 'commits<> "$GITHUB_OUTPUT" + + # Generals vc6 + - name: Download Generals VC6 Artifacts + uses: actions/download-artifact@v4 + with: + name: Generals-vc6-weekly+t + path: generals-vc6-artifacts + + - name: Prepare and Zip Generals VC6 + run: | + zip -jr generals-weekly-${{ needs.get-date.outputs.date }}.zip generals-vc6-artifacts/* + + # GeneralsMD vc6 + - name: Download GeneralsMD VC6 Artifacts + uses: actions/download-artifact@v4 + with: + name: GeneralsMD-vc6-weekly+t + path: generalsmd-vc6-artifacts + + - name: Prepare and Zip GeneralsMD VC6 + run: | + zip -jr generalszh-weekly-${{ needs.get-date.outputs.date }}.zip generalsmd-vc6-artifacts/* + + - name: Generate release notes + id: release_body + run: | + BODY="" + if [ "${{ github.event.inputs.build_notes }}" != "" ]; then + BODY="${BODY}### Build notes\n${{ github.event.inputs.build_notes }}\n" + fi + if [ "${{ github.event.inputs.known_issues }}" != "" ]; then + BODY="${BODY}### Known issues\n${{ github.event.inputs.known_issues }}\n" + fi + BODY="${BODY}### Changelog\n${{ steps.changelog.outputs.commits }}" + echo "body<> $GITHUB_OUTPUT + echo -e "$BODY" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: weekly-${{ needs.get-date.outputs.date }} + name: weekly-${{ needs.get-date.outputs.date }} + prerelease: ${{ github.event.inputs.pre-release == 'true' }} + body: ${{ steps.release_body.outputs.body }} + files: | + generals-weekly-${{ needs.get-date.outputs.date }}.zip + generalszh-weekly-${{ needs.get-date.outputs.date }}.zip + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Clean up release folders + if: always() + run: | + rm -rf generals-vc6-artifacts generalsmd-vc6-artifacts + rm -f generals-weekly-${{ needs.get-date.outputs.date }}.zip + rm -f generalszh-weekly-${{ needs.get-date.outputs.date }}.zip diff --git a/.gitignore b/.gitignore index e5e550b48da..1c9c525ae12 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,10 @@ -# Ignore everything starting with dot, except git files. +# Ignore everything starting with dot, except specific files. .* +!.editorconfig !.gitignore !.gitattributes !.github +!.gitmodules *.user *.ncb diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000000..09d4419ccc5 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "GeneralsReplays"] + path = GeneralsReplays + url = https://github.com/TheSuperHackers/GeneralsReplays diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b9bb8bf6b7..4160c918c74 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,7 +43,6 @@ if((WIN32 OR "${CMAKE_SYSTEM}" MATCHES "Windows") AND ${CMAKE_SIZEOF_VOID_P} EQU include(cmake/miles.cmake) include(cmake/bink.cmake) include(cmake/dx8.cmake) - include(cmake/dbghelp.cmake) endif() # Define a dummy stlport target when not on VC6. @@ -57,7 +56,6 @@ include(cmake/config.cmake) include(cmake/gamespy.cmake) include(cmake/lzhl.cmake) -add_subdirectory(Dependencies/Benchmark) if (IS_VS6_BUILD) # The original max sdk does not compile against a modern compiler. # If there is a desire to make this work, then a fixed max sdk needs to be created. diff --git a/CMakePresets.json b/CMakePresets.json index feb76247721..ba1d2d194c2 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -9,13 +9,13 @@ { "name": "vc6", "displayName": "Windows 32bit VC6 Release", - "generator": "NMake Makefiles", + "generator": "Ninja", "hidden": false, "binaryDir": "${sourceDir}/build/${presetName}", "cacheVariables": { "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", "CMAKE_MSVC_RUNTIME_LIBRARY": "MultiThreaded$<$:Debug>DLL", - "CMAKE_MSVC_DEBUG_INFORMATION_FORMAT": "$<$:ProgramDatabase>", + "CMAKE_MSVC_DEBUG_INFORMATION_FORMAT": "", "CMAKE_BUILD_TYPE": "Release", "RTS_FLAGS": "/W3" }, @@ -34,14 +34,6 @@ "RTS_BUILD_OPTION_PROFILE": "ON" } }, - { - "name": "vc6-internal", - "displayName": "Windows 32bit VC6 Internal", - "inherits": "vc6", - "cacheVariables": { - "RTS_BUILD_OPTION_INTERNAL": "ON" - } - }, { "name": "vc6-debug", "displayName": "Windows 32bit VC6 Debug", @@ -52,6 +44,23 @@ "RTS_BUILD_OPTION_DEBUG": "ON" } }, + { + "name": "vc6-releaselog", + "displayName": "Windows 32bit VC6 Release Logging", + "inherits": "vc6", + "cacheVariables": { + "RTS_DEBUG_LOGGING": "ON", + "RTS_DEBUG_CRASHING": "ON" + } + }, + { + "name": "vc6-weekly", + "displayName": "Windows 32bit VC6 Weekly Release", + "inherits": "vc6", + "cacheVariables": { + "RTS_BUILD_OPTION_VC6_FULL_DEBUG": "ON" + } + }, { "name": "default", "displayName": "Default Config (don't use directly!)", @@ -103,14 +112,6 @@ "RTS_BUILD_OPTION_PROFILE": "ON" } }, - { - "name": "win32-internal", - "inherits": "win32", - "displayName": "Windows 32bit Internal", - "cacheVariables": { - "RTS_BUILD_OPTION_INTERNAL": "ON" - } - }, { "name": "win32-debug", "inherits": "win32", @@ -145,14 +146,6 @@ "RTS_BUILD_OPTION_PROFILE": "ON" } }, - { - "name": "win32-vcpkg-internal", - "inherits": "win32-vcpkg", - "displayName": "Windows 32bit VCPKG Internal", - "cacheVariables": { - "RTS_BUILD_OPTION_INTERNAL": "ON" - } - }, { "name": "win32-vcpkg-debug", "inherits": "win32-vcpkg", @@ -175,12 +168,6 @@ "displayName": "Build Windows 32bit VC6 Release", "description": "Build Windows 32bit VC6 Release" }, - { - "name": "vc6-internal", - "configurePreset": "vc6-internal", - "displayName": "Build Windows 32bit VC6 Internal", - "description": "Build Windows 32bit VC6 Internal" - }, { "name": "vc6-profile", "configurePreset": "vc6-profile", @@ -193,6 +180,18 @@ "displayName": "Build Windows 32bit VC6 Debug", "description": "Build Windows 32bit VC6 Debug" }, + { + "name": "vc6-releaselog", + "configurePreset": "vc6-releaselog", + "displayName": "Build Windows 32bit VC6 Release Logging", + "description": "Build Windows 32bit VC6 Release Logging" + }, + { + "name": "vc6-weekly", + "configurePreset": "vc6-weekly", + "displayName": "Build Windows 32bit VC6 Weekly Release", + "description": "Build Windows 32bit VC6 Weekly Release" + }, { "name": "win32", "configurePreset": "win32", @@ -200,13 +199,6 @@ "description": "Build Windows 32bit Release", "configuration": "Release" }, - { - "name": "win32-internal", - "configurePreset": "win32-internal", - "displayName": "Build Windows 32bit Internal", - "description": "Build Windows 32bit Internal", - "configuration": "Release" - }, { "name": "win32-profile", "configurePreset": "win32-profile", @@ -228,13 +220,6 @@ "description": "Build Windows 32bit VCPKG Release", "configuration": "Release" }, - { - "name": "win32-vcpkg-internal", - "configurePreset": "win32-vcpkg-internal", - "displayName": "Build Windows 32bit VCPKG Internal", - "description": "Build Windows 32bit VCPKG Internal", - "configuration": "Release" - }, { "name": "win32-vcpkg-profile", "configurePreset": "win32-vcpkg-profile", @@ -285,54 +270,54 @@ ] }, { - "name": "vc6-internal", + "name": "vc6-profile", "steps": [ { "type": "configure", - "name": "vc6-internal" + "name": "vc6-profile" }, { "type": "build", - "name": "vc6-internal" + "name": "vc6-profile" } ] }, { - "name": "vc6-profile", + "name": "vc6-releaselog", "steps": [ { "type": "configure", - "name": "vc6-profile" + "name": "vc6-releaselog" }, { "type": "build", - "name": "vc6-profile" + "name": "vc6-releaselog" } ] }, { - "name": "win32", + "name": "vc6-weekly", "steps": [ { "type": "configure", - "name": "win32" + "name": "vc6-weekly" }, { "type": "build", - "name": "win32" + "name": "vc6-weekly" } ] }, { - "name": "win32-internal", + "name": "win32", "steps": [ { "type": "configure", - "name": "win32-internal" + "name": "win32" }, { "type": "build", - "name": "win32-internal" + "name": "win32" } ] }, @@ -375,19 +360,6 @@ } ] }, - { - "name": "win32-vcpkg-internal", - "steps": [ - { - "type": "configure", - "name": "win32-vcpkg-internal" - }, - { - "type": "build", - "name": "win32-vcpkg-internal" - } - ] - }, { "name": "win32-vcpkg-profile", "steps": [ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000000..84d5b7d70e4 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,153 @@ +# How to contribute as a developer + +To contribute, fork this repository to create your own copy that you can clone locally and push back to. You can use your fork to create pull requests for your code to be merged into this repository. + +## Code guidelines + +### Scope of code changes + +Code edits only touch the lines of code that serve the intended goal of the change. Big refactors should not be combined with logical changes, because these can become very difficult to review. If a change requires a refactor, create a commit for the refactor before (or after) creating a commit for the change. A Pull Request can contain multiple commits and can be merged with **Rebase and Merge** if these commits are meant to be preserved on the main branch. Otherwise, method of merging will be **Squash and Merge**. + +### Style of code changes + +Code edits should fit the nearby code in ways that the code style reads consistent, unless the original code style is bad. The original game code uses c++98, or a deviation thereof, and is simple to read. Prefer not to use newer language features unless required to implement the desired change. Prefer to use newer language features when they are considerably more robust or make the code easier to understand or maintain. + +### Language style guide + +*Work in progress. Needs a maintainer. Can be built upon existing Code guidelines, such as the "Google C++ Style Guide".* + +### Precedence of code changes + +Changes to Zero Hour take precedence over Generals, if applicable. When the changed code is not shared by both titles, then the change needs to be created for Zero Hour first, and then recreated for Generals. The implementation of a change for both titles needs to be identical or as close as possible. Preferably the Generals replica of a change comes with the same Pull Request. The Generals replica can be created after the Zero Hour code review has finished. + + +## Change documentation + +User facing changes need to be documented in code, Pull Requests and change logs. All documentation ideally is written in the present tense, and not the past. + +Good: + +> Fixes particle effect of USA Missile Defender + +Bad: + +> Fixed particle effect of USA Missile Defender + +When a text refers to a faction unit, structure, upgrade or similar, then the unit should be worded without any abbrevations and should be prefixed with the faction name. Valid faction names are USA, China, GLA, Boss, Civilian. Subfaction names can be appended too, for example GLA Stealth. + +Good: + +> Fixes particle effect of USA Missile Defender + +Bad: + +> Fixes particle effect of MD + + +### Code documentation + +User facing changes need to be accompanied by comment(s) where the change is made. Maintenance related changes, such as compilation fixes, typically do not need commenting, unless the next reader can benefit from a special explanation. The comment can be put at the begin of the changed file, class, function or block. It must be clear from the change description what has changed. + +The expected comment format is + +``` +// TheSuperHackers @keyword author DD/MM/YYYY A meaningful description for this change. +``` + +The `TheSuperHackers` word and `@keyword` are mandatory. `author` and date can be omitted when preferred. + +| Keyword | Use-case | +|------------------|-------------------------------------------------------------| +| @bugfix | Fixes a bug | +| @fix | Fixes something, but is not a user facing bug | +| @build | Addresses a compile warning or error | +| @feature | Adds something new | +| @performance | Improves performance | +| @refactor | Moves or rewrites code, but does not change the behaviour | +| @tweak | Changes values or settings | +| @info | Writes useful information for the next reader | +| @todo | Adds a note for something left to do if really necessary | + +Block comment sample + +``` + // TheSuperHackers @bugfix JAJames 17/03/2025 Fix uninitialized memory access and add more Windows versions. + memset(&os_info,0,sizeof(os_info)); +``` + +Optionally, the pull request number can be appended to the comment. This can only be done after the pull request has been created. + +``` +// TheSuperHackers @bugfix JAJames 17/03/2025 Fix uninitialized memory access and add more Windows versions. (#123) +``` + +### Pull request documentation + +The title of a new Pull Request, and/or commit(s) within, begins with a [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) tag. The tag is followed by a concise and descriptive sentence about the change and/or commit, beginning with an upper case letter and ending without a dot. The sentence ideally begins with a word that describes the action that the change takes, for example `fix *this*`, `change *that*`, `add *those*`, `refactor *thing*`. + +Allowed (extended) commit title types are: +``` +bugfix: +build: +chore: +ci: +docs: +fix: +feat: +perf: +refactor: +style: +test: +tweak: +unify: +``` + +For the optional scope behind the type pick a suitable word that describes the overall area that the change touches. + +Good: +``` +bugfix(system): fix uninitialized memory access in Get_OS_Info +``` + +Bad: +``` +Minimal changes for successful build. +``` + +If the Pull Request is meant to be merged with rebase, then a note for **Merge with Rebase** should be added to the top of the text body, to help identify the correct merge action when it is ready for merge. All commits of the Pull Request need to be properly named and need the number of the Pull Request added as a suffix in parentheses. Example: **(#333)**. All commits need to be able to compile on their own without dependencies in newer commits of the same Pull Request. Prefer to create changes for **Squash and Merge**, as this will simplify things. + +The text body begins with links to related issue report(s) and/or Pull Request(s) if applicable. + +To write a link use the following format: + +``` +* Fixes #222 +* Closes #333 +* Relates to #555 +* Follow up for #666 +``` + +Links are commonly used for + +* closing a related issue report or task when this pull request is merged +* closing another pull request when this pull request is merged + +Some keywords are interpreted by GitHub. Read about it [here](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue). + +The text body continues with a description of the change in appropriate detail. This serves to educate reviewers and visitors to get a good understanding of the change without the need to study and understand the associated changed files. If the change is controversial or affects gameplay in a considerable way, then a rationale text needs to be appended. The rationale explains why the given change makes sense. + + +### Pull request merging rules + +Please be mindful when merging changes. There are pitfalls in regards to the commit title consistency. + +When attempting to **Squash and Merge** a Pull Request that contains a single commit, then GitHub will default generate a commit title from that single commit. Typically this is undesired, when the new commit title is meant to be kept in sync with the Pull Request title rather than the Pull Request commit title. The generated commit title may need to be adjusted before merging the Pull Request. + +When attempting to **Squash and Merge** a Pull Request that contains multiple commits, the GitHub will default generate a commit title from the Pull Request title. Additionally it will generate a commit description from the multiple commits that are part of the Pull Request. The generated commit description generally needs to be cleared before merging the Pull Request to keep the commit title clean. + +When attempting to **Rebase and Merge** a Pull Request, then all commits will transfer with the same names to the main branch. Verify that all commit titles are properly crafted, with tags where applicable, trailing Pull Request numbers in parentheses and no unnecessary commit descriptions (texts below the commit title). + + +### Change log documentation + +*Work in progress.* diff --git a/Core/CMakeLists.txt b/Core/CMakeLists.txt index 30b785f330c..6f6f640b3a4 100644 --- a/Core/CMakeLists.txt +++ b/Core/CMakeLists.txt @@ -1,17 +1,21 @@ -# c stands for core, i stands for Interface +# i stands for Interface +add_library(corei_gameengine_include INTERFACE) add_library(corei_libraries_include INTERFACE) add_library(corei_libraries_source_wwvegas INTERFACE) -add_library(corei_libraries_source_wwvegas_wwdebug INTERFACE) add_library(corei_libraries_source_wwvegas_wwlib INTERFACE) +add_library(corei_main INTERFACE) add_library(corei_always INTERFACE) +target_include_directories(corei_gameengine_include INTERFACE "GameEngine/Include") target_include_directories(corei_libraries_include INTERFACE "Libraries/Include") target_include_directories(corei_libraries_source_wwvegas INTERFACE "Libraries/Source/WWVegas") -target_include_directories(corei_libraries_source_wwvegas_wwdebug INTERFACE "Libraries/Source/WWVegas/WWDebug") target_include_directories(corei_libraries_source_wwvegas_wwlib INTERFACE "Libraries/Source/WWVegas/WWLib") +target_include_directories(corei_main INTERFACE "Main") + target_link_libraries(corei_always INTERFACE core_utility corei_libraries_include + resources ) # Set where the build results will end up @@ -24,6 +28,6 @@ add_subdirectory(Libraries) add_subdirectory(GameEngine) # Platform specific GameEngine code -# add_subdirectory(GameEngineDevice) +add_subdirectory(GameEngineDevice) add_subdirectory(Tools) diff --git a/Core/GameEngine/CMakeLists.txt b/Core/GameEngine/CMakeLists.txt index 35fc9a97d1e..eb7b7541e14 100644 --- a/Core/GameEngine/CMakeLists.txt +++ b/Core/GameEngine/CMakeLists.txt @@ -1,16 +1,17 @@ set(GAMEENGINE_SRC # Include/Common/AcademyStats.h # Include/Common/ActionManager.h -# Include/Common/ArchiveFile.h -# Include/Common/ArchiveFileSystem.h -# Include/Common/AsciiString.h -# Include/Common/AudioAffect.h -# Include/Common/AudioEventInfo.h -# Include/Common/AudioEventRTS.h -# Include/Common/AudioHandleSpecialValues.h -# Include/Common/AudioRandomValue.h -# Include/Common/AudioRequest.h -# Include/Common/AudioSettings.h + Include/Common/AddonCompat.h + Include/Common/ArchiveFile.h + Include/Common/ArchiveFileSystem.h + Include/Common/AsciiString.h + Include/Common/AudioAffect.h + Include/Common/AudioEventInfo.h + Include/Common/AudioEventRTS.h + Include/Common/AudioHandleSpecialValues.h + Include/Common/AudioRandomValue.h + Include/Common/AudioRequest.h + Include/Common/AudioSettings.h # Include/Common/BattleHonors.h # Include/Common/BezFwdIterator.h # Include/Common/BezierSegment.h @@ -21,37 +22,40 @@ set(GAMEENGINE_SRC # Include/Common/CDManager.h # Include/Common/ClientUpdateModule.h # Include/Common/CommandLine.h -# Include/Common/crc.h -# Include/Common/CRCDebug.h + Include/Common/crc.h + Include/Common/CRCDebug.h # Include/Common/CriticalSection.h # Include/Common/CustomMatchPreferences.h # Include/Common/DamageFX.h # Include/Common/DataChunk.h -# Include/Common/Debug.h + Include/Common/Debug.h # Include/Common/Dict.h # Include/Common/Directory.h # Include/Common/DisabledTypes.h # Include/Common/DiscreteCircle.h # Include/Common/DrawModule.h -# Include/Common/DynamicAudioEventInfo.h + Include/Common/DynamicAudioEventInfo.h # Include/Common/encrypt.h # Include/Common/Energy.h # Include/Common/Errors.h -# Include/Common/file.h -# Include/Common/FileSystem.h + Include/Common/file.h + Include/Common/FileSystem.h + Include/Common/FramePacer.h + Include/Common/FrameRateLimit.h # Include/Common/FunctionLexicon.h -# Include/Common/GameAudio.h + Include/Common/GameAudio.h # Include/Common/GameCommon.h Include/Common/GameDefines.h # Include/Common/GameEngine.h # Include/Common/GameLOD.h -# Include/Common/GameMemory.h -# Include/Common/GameMusic.h -# Include/Common/GameSounds.h + Include/Common/GameMemory.h + Include/Common/GameMusic.h + Include/Common/GameSounds.h # Include/Common/GameSpyMiscPreferences.h # Include/Common/GameState.h # Include/Common/GameStateMap.h # Include/Common/GameType.h + Include/Common/GameUtility.h # Include/Common/Geometry.h # Include/Common/GlobalData.h # Include/Common/Handicap.h @@ -63,13 +67,14 @@ set(GAMEENGINE_SRC # Include/Common/Language.h # Include/Common/LatchRestore.h # Include/Common/List.h -# Include/Common/LocalFile.h -# Include/Common/LocalFileSystem.h -# Include/Common/MapObject.h + Include/Common/LocalFile.h + Include/Common/LocalFileSystem.h + Include/Common/MapObject.h # Include/Common/MapReaderWriterInfo.h # Include/Common/MessageStream.h + Include/Common/MiniDumper.h # Include/Common/MiniLog.h -# Include/Common/MiscAudio.h + Include/Common/MiscAudio.h # Include/Common/MissionStats.h # Include/Common/ModelState.h # Include/Common/Module.h @@ -77,7 +82,7 @@ set(GAMEENGINE_SRC # Include/Common/Money.h # Include/Common/MultiplayerSettings.h # Include/Common/NameKeyGenerator.h -# Include/Common/ObjectStatusTypes.h + Include/Common/ObjectStatusTypes.h # Include/Common/OSDisplay.h # Include/Common/Overridable.h # Include/Common/Override.h @@ -90,16 +95,17 @@ set(GAMEENGINE_SRC # Include/Common/ProductionPrerequisite.h # Include/Common/QuickmatchPreferences.h # Include/Common/QuotedPrintable.h -# Include/Common/Radar.h -# Include/Common/RAMFile.h -# Include/Common/RandomValue.h + Include/Common/Radar.h + Include/Common/RAMFile.h + Include/Common/RandomValue.h # Include/Common/Recorder.h # Include/Common/Registry.h + Include/Common/ReplaySimulation.h # Include/Common/ResourceGatheringManager.h # Include/Common/Science.h # Include/Common/ScopedMutex.h # Include/Common/ScoreKeeper.h -# Include/Common/simpleplayer.h + #Include/Common/simpleplayer.h # unused # Include/Common/SkirmishBattleHonors.h # Include/Common/SkirmishPreferences.h # Include/Common/Snapshot.h @@ -111,7 +117,7 @@ set(GAMEENGINE_SRC # Include/Common/StateMachine.h # Include/Common/StatsCollector.h # Include/Common/STLTypedefs.h -# Include/Common/StreamingArchiveFile.h + Include/Common/StreamingArchiveFile.h # Include/Common/SubsystemInterface.h # Include/Common/SystemInfo.h # Include/Common/Team.h @@ -122,13 +128,14 @@ set(GAMEENGINE_SRC # Include/Common/ThingSort.h # Include/Common/ThingTemplate.h # Include/Common/TunnelTracker.h -# Include/Common/UnicodeString.h + Include/Common/UnicodeString.h # Include/Common/UnitTimings.h # Include/Common/Upgrade.h -# Include/Common/urllaunch.h + #Include/Common/urllaunch.h # unused # Include/Common/UserPreferences.h # Include/Common/version.h # Include/Common/WellKnownKeys.h + Include/Common/WorkerProcess.h Include/Common/Xfer.h Include/Common/XferCRC.h Include/Common/XferDeepCRC.h @@ -140,7 +147,7 @@ set(GAMEENGINE_SRC # Include/GameClient/CDCheck.h # Include/GameClient/ChallengeGenerals.h # Include/GameClient/ClientInstance.h -# Include/GameClient/ClientRandomValue.h + Include/GameClient/ClientRandomValue.h # Include/GameClient/Color.h # Include/GameClient/CommandXlat.h # Include/GameClient/ControlBar.h @@ -197,14 +204,14 @@ set(GAMEENGINE_SRC # Include/GameClient/Line2D.h # Include/GameClient/LoadScreen.h # Include/GameClient/LookAtXlat.h -# Include/GameClient/MapUtil.h + Include/GameClient/MapUtil.h # Include/GameClient/MessageBox.h # Include/GameClient/MetaEvent.h # Include/GameClient/Module/AnimatedParticleSysBoneClientUpdate.h # Include/GameClient/Module/BeaconClientUpdate.h # Include/GameClient/Module/SwayClientUpdate.h # Include/GameClient/Mouse.h -# Include/GameClient/ParabolicEase.h + Include/GameClient/ParabolicEase.h # Include/GameClient/ParticleSys.h # Include/GameClient/PlaceEventTranslator.h # Include/GameClient/ProcessAnimateWindow.h @@ -216,16 +223,16 @@ set(GAMEENGINE_SRC # Include/GameClient/Shell.h # Include/GameClient/ShellHooks.h # Include/GameClient/ShellMenuScheme.h -# Include/GameClient/Smudge.h -# Include/GameClient/Snow.h + Include/GameClient/Smudge.h + Include/GameClient/Snow.h # Include/GameClient/Statistics.h -# Include/GameClient/TerrainRoads.h -# Include/GameClient/TerrainVisual.h -# Include/GameClient/VideoPlayer.h -# Include/GameClient/View.h -# Include/GameClient/Water.h + Include/GameClient/TerrainRoads.h + Include/GameClient/TerrainVisual.h + Include/GameClient/VideoPlayer.h + Include/GameClient/View.h + Include/GameClient/Water.h # Include/GameClient/WindowLayout.h -# Include/GameClient/WindowVideoManager.h + Include/GameClient/WindowVideoManager.h # Include/GameClient/WindowXlat.h # Include/GameClient/WinInstanceData.h # Include/GameLogic/AI.h @@ -249,7 +256,7 @@ set(GAMEENGINE_SRC # Include/GameLogic/GhostObject.h # Include/GameLogic/Locomotor.h # Include/GameLogic/LocomotorSet.h -# Include/GameLogic/LogicRandomValue.h + Include/GameLogic/LogicRandomValue.h # Include/GameLogic/Module/ActiveBody.h # Include/GameLogic/Module/ActiveShroudUpgrade.h # Include/GameLogic/Module/AIUpdate.h @@ -496,84 +503,89 @@ set(GAMEENGINE_SRC # Include/GameLogic/WeaponSetFlags.h # Include/GameLogic/WeaponSetType.h # Include/GameLogic/WeaponStatus.h -# Include/GameNetwork/Connection.h -# Include/GameNetwork/ConnectionManager.h -# Include/GameNetwork/DisconnectManager.h -# Include/GameNetwork/DownloadManager.h -# Include/GameNetwork/FileTransfer.h -# Include/GameNetwork/FirewallHelper.h -# Include/GameNetwork/FrameData.h -# Include/GameNetwork/FrameDataManager.h -# Include/GameNetwork/FrameMetrics.h -# Include/GameNetwork/GameInfo.h -# Include/GameNetwork/GameMessageParser.h -# Include/GameNetwork/GameSpy/BuddyDefs.h -# Include/GameNetwork/GameSpy/BuddyThread.h -# Include/GameNetwork/GameSpy/GameResultsThread.h -# Include/GameNetwork/GameSpy/GSConfig.h -# Include/GameNetwork/GameSpy/LadderDefs.h -# Include/GameNetwork/GameSpy/LobbyUtils.h -# Include/GameNetwork/GameSpy/MainMenuUtils.h -# Include/GameNetwork/GameSpy/PeerDefs.h -# Include/GameNetwork/GameSpy/PeerDefsImplementation.h -# Include/GameNetwork/GameSpy/PeerThread.h -# Include/GameNetwork/GameSpy/PersistentStorageDefs.h -# Include/GameNetwork/GameSpy/PersistentStorageThread.h -# Include/GameNetwork/GameSpy/PingThread.h -# Include/GameNetwork/GameSpy/StagingRoomGameInfo.h -# Include/GameNetwork/GameSpy/ThreadUtils.h -# Include/GameNetwork/GameSpyChat.h -# Include/GameNetwork/GameSpyGameInfo.h -# Include/GameNetwork/GameSpyGP.h -# Include/GameNetwork/GameSpyOverlay.h -# Include/GameNetwork/GameSpyThread.h + Include/GameNetwork/Connection.h + Include/GameNetwork/ConnectionManager.h + Include/GameNetwork/DisconnectManager.h + Include/GameNetwork/DownloadManager.h + Include/GameNetwork/FileTransfer.h + Include/GameNetwork/FirewallHelper.h + Include/GameNetwork/FrameData.h + Include/GameNetwork/FrameDataManager.h + Include/GameNetwork/FrameMetrics.h + Include/GameNetwork/GameInfo.h + Include/GameNetwork/GameMessageParser.h + Include/GameNetwork/GameSpy/BuddyDefs.h + Include/GameNetwork/GameSpy/BuddyThread.h + Include/GameNetwork/GameSpy/GameResultsThread.h + Include/GameNetwork/GameSpy/GSConfig.h + Include/GameNetwork/GameSpy/LadderDefs.h + Include/GameNetwork/GameSpy/LobbyUtils.h + Include/GameNetwork/GameSpy/MainMenuUtils.h + Include/GameNetwork/GameSpy/PeerDefs.h + Include/GameNetwork/GameSpy/PeerDefsImplementation.h + Include/GameNetwork/GameSpy/PeerThread.h + Include/GameNetwork/GameSpy/PersistentStorageDefs.h + Include/GameNetwork/GameSpy/PersistentStorageThread.h + Include/GameNetwork/GameSpy/PingThread.h + Include/GameNetwork/GameSpy/StagingRoomGameInfo.h + Include/GameNetwork/GameSpy/ThreadUtils.h +# Include/GameNetwork/GameSpyChat.h # unused +# Include/GameNetwork/GameSpyGameInfo.h # unused +# Include/GameNetwork/GameSpyGP.h # unused + Include/GameNetwork/GameSpyOverlay.h + Include/GameNetwork/GameSpyThread.h # Include/GameNetwork/GUIUtil.h -# Include/GameNetwork/IPEnumeration.h -# Include/GameNetwork/LANAPI.h -# Include/GameNetwork/LANAPICallbacks.h -# Include/GameNetwork/LANGameInfo.h -# Include/GameNetwork/LANPlayer.h -# Include/GameNetwork/NAT.h -# Include/GameNetwork/NetCommandList.h -# Include/GameNetwork/NetCommandMsg.h -# Include/GameNetwork/NetCommandRef.h -# Include/GameNetwork/NetCommandWrapperList.h -# Include/GameNetwork/NetPacket.h -# Include/GameNetwork/NetworkDefs.h -# Include/GameNetwork/NetworkInterface.h -# Include/GameNetwork/networkutil.h -# Include/GameNetwork/RankPointValue.h -# Include/GameNetwork/Transport.h -# Include/GameNetwork/udp.h -# Include/GameNetwork/User.h -# Include/GameNetwork/WOLBrowser/FEBDispatch.h -# Include/GameNetwork/WOLBrowser/WebBrowser.h + Include/GameNetwork/IPEnumeration.h + Include/GameNetwork/LANAPI.h + Include/GameNetwork/LANAPICallbacks.h + Include/GameNetwork/LANGameInfo.h + Include/GameNetwork/LANPlayer.h + Include/GameNetwork/NAT.h + Include/GameNetwork/NetCommandList.h + Include/GameNetwork/NetCommandMsg.h + Include/GameNetwork/NetCommandRef.h + Include/GameNetwork/NetCommandWrapperList.h + Include/GameNetwork/NetPacket.h + Include/GameNetwork/NetPacketStructs.h + Include/GameNetwork/NetworkDefs.h + Include/GameNetwork/NetworkInterface.h + Include/GameNetwork/networkutil.h + Include/GameNetwork/RankPointValue.h + Include/GameNetwork/Transport.h + Include/GameNetwork/udp.h + Include/GameNetwork/User.h + Include/GameNetwork/WOLBrowser/FEBDispatch.h + Include/GameNetwork/WOLBrowser/WebBrowser.h # Include/Precompiled/PreRTS.h -# Source/Common/Audio/AudioEventRTS.cpp -# Source/Common/Audio/AudioRequest.cpp -# Source/Common/Audio/DynamicAudioEventInfo.cpp -# Source/Common/Audio/GameAudio.cpp -# Source/Common/Audio/GameMusic.cpp -# Source/Common/Audio/GameSounds.cpp + Source/Common/AddonCompat.cpp + Source/Common/Audio/AudioEventRTS.cpp + Source/Common/Audio/AudioRequest.cpp + Source/Common/Audio/DynamicAudioEventInfo.cpp + Source/Common/Audio/GameAudio.cpp + Source/Common/Audio/GameMusic.cpp + Source/Common/Audio/GameSounds.cpp #Source/Common/Audio/simpleplayer.cpp # unused #Source/Common/Audio/urllaunch.cpp # unused # Source/Common/Bezier/BezFwdIterator.cpp # Source/Common/Bezier/BezierSegment.cpp # Source/Common/BitFlags.cpp # Source/Common/CommandLine.cpp -# Source/Common/crc.cpp -# Source/Common/CRCDebug.cpp + Source/Common/crc.cpp + Source/Common/CRCDebug.cpp # Source/Common/DamageFX.cpp # Source/Common/Dict.cpp # Source/Common/DiscreteCircle.cpp + Source/Common/FramePacer.cpp + Source/Common/FrameRateLimit.cpp # Source/Common/GameEngine.cpp # Source/Common/GameLOD.cpp # Source/Common/GameMain.cpp + Source/Common/GameUtility.cpp # Source/Common/GlobalData.cpp # Source/Common/INI/INI.cpp # Source/Common/INI/INIAiData.cpp # Source/Common/INI/INIAnimation.cpp -# Source/Common/INI/INIAudioEventInfo.cpp + Source/Common/INI/INIAudioEventInfo.cpp # Source/Common/INI/INICommandButton.cpp # Source/Common/INI/INICommandSet.cpp # Source/Common/INI/INIControlBarScheme.cpp @@ -584,7 +596,7 @@ set(GAMEENGINE_SRC # Source/Common/INI/INIMapCache.cpp # Source/Common/INI/INIMapData.cpp # Source/Common/INI/INIMappedImage.cpp -# Source/Common/INI/INIMiscAudio.cpp + Source/Common/INI/INIMiscAudio.cpp # Source/Common/INI/INIModel.cpp # Source/Common/INI/INIMultiplayer.cpp # Source/Common/INI/INIObject.cpp @@ -594,7 +606,7 @@ set(GAMEENGINE_SRC # Source/Common/INI/INITerrainBridge.cpp # Source/Common/INI/INITerrainRoad.cpp # Source/Common/INI/INIUpgrade.cpp -# Source/Common/INI/INIVideo.cpp + Source/Common/INI/INIVideo.cpp # Source/Common/INI/INIWater.cpp # Source/Common/INI/INIWeapon.cpp # Source/Common/INI/INIWebpageURL.cpp @@ -605,8 +617,9 @@ set(GAMEENGINE_SRC # Source/Common/NameKeyGenerator.cpp # Source/Common/PartitionSolver.cpp # Source/Common/PerfTimer.cpp -# Source/Common/RandomValue.cpp + Source/Common/RandomValue.cpp # Source/Common/Recorder.cpp + Source/Common/ReplaySimulation.cpp # Source/Common/RTS/AcademyStats.cpp # Source/Common/RTS/ActionManager.cpp # Source/Common/RTS/Energy.cpp @@ -626,42 +639,43 @@ set(GAMEENGINE_SRC # Source/Common/SkirmishBattleHonors.cpp # Source/Common/StateMachine.cpp # Source/Common/StatsCollector.cpp -# Source/Common/System/ArchiveFile.cpp -# Source/Common/System/ArchiveFileSystem.cpp -# Source/Common/System/AsciiString.cpp + Source/Common/System/ArchiveFile.cpp + Source/Common/System/ArchiveFileSystem.cpp + Source/Common/System/AsciiString.cpp # Source/Common/System/BuildAssistant.cpp # Source/Common/System/CDManager.cpp # Source/Common/System/CriticalSection.cpp # Source/Common/System/DataChunk.cpp -# Source/Common/System/Debug.cpp + Source/Common/System/Debug.cpp # Source/Common/System/Directory.cpp # Source/Common/System/DisabledTypes.cpp # Source/Common/System/encrypt.cpp -# Source/Common/System/File.cpp -# Source/Common/System/FileSystem.cpp + Source/Common/System/File.cpp + Source/Common/System/FileSystem.cpp # Source/Common/System/FunctionLexicon.cpp # Source/Common/System/GameCommon.cpp #Source/Common/System/GameMemory.cpp # is conditionally appended + #Source/Common/System/GameMemoryInit.cpp # is conditionally appended # Source/Common/System/GameType.cpp # Source/Common/System/Geometry.cpp # Source/Common/System/KindOf.cpp # Source/Common/System/List.cpp -# Source/Common/System/LocalFile.cpp -# Source/Common/System/LocalFileSystem.cpp - #Source/Common/System/MemoryInit.cpp # is conditionally appended -# Source/Common/System/ObjectStatusTypes.cpp + Source/Common/System/LocalFile.cpp + Source/Common/System/LocalFileSystem.cpp + Source/Common/System/MiniDumper.cpp + Source/Common/System/ObjectStatusTypes.cpp # Source/Common/System/QuotedPrintable.cpp -# Source/Common/System/Radar.cpp -# Source/Common/System/RAMFile.cpp + Source/Common/System/Radar.cpp + Source/Common/System/RAMFile.cpp # Source/Common/System/registry.cpp # Source/Common/System/SaveGame/GameState.cpp # Source/Common/System/SaveGame/GameStateMap.cpp # Source/Common/System/Snapshot.cpp # Source/Common/System/StackDump.cpp -# Source/Common/System/StreamingArchiveFile.cpp + Source/Common/System/StreamingArchiveFile.cpp # Source/Common/System/SubsystemInterface.cpp # Source/Common/System/Trig.cpp -# Source/Common/System/UnicodeString.cpp + Source/Common/System/UnicodeString.cpp # Source/Common/System/Upgrade.cpp Source/Common/System/Xfer.cpp Source/Common/System/XferCRC.cpp @@ -676,6 +690,7 @@ set(GAMEENGINE_SRC # Source/Common/Thing/ThingTemplate.cpp # Source/Common/UserPreferences.cpp # Source/Common/version.cpp + Source/Common/WorkerProcess.cpp # Source/GameClient/ClientInstance.cpp # Source/GameClient/Color.cpp # Source/GameClient/Credits.cpp @@ -786,14 +801,14 @@ set(GAMEENGINE_SRC # Source/GameClient/GUI/Shell/Shell.cpp # Source/GameClient/GUI/Shell/ShellMenuScheme.cpp # Source/GameClient/GUI/WindowLayout.cpp -# Source/GameClient/GUI/WindowVideoManager.cpp + Source/GameClient/GUI/WindowVideoManager.cpp # Source/GameClient/GUI/WinInstanceData.cpp # Source/GameClient/InGameUI.cpp # Source/GameClient/Input/Keyboard.cpp # Source/GameClient/Input/Mouse.cpp # Source/GameClient/LanguageFilter.cpp # Source/GameClient/Line2D.cpp -# Source/GameClient/MapUtil.cpp + Source/GameClient/MapUtil.cpp # Source/GameClient/MessageStream/CommandXlat.cpp # Source/GameClient/MessageStream/GUICommandTranslator.cpp # Source/GameClient/MessageStream/HintSpy.cpp @@ -803,25 +818,25 @@ set(GAMEENGINE_SRC # Source/GameClient/MessageStream/PlaceEventTranslator.cpp # Source/GameClient/MessageStream/SelectionXlat.cpp # Source/GameClient/MessageStream/WindowXlat.cpp -# Source/GameClient/ParabolicEase.cpp + Source/GameClient/ParabolicEase.cpp # Source/GameClient/RadiusDecal.cpp # Source/GameClient/SelectionInfo.cpp -# Source/GameClient/Snow.cpp + Source/GameClient/Snow.cpp # Source/GameClient/Statistics.cpp # Source/GameClient/System/Anim2D.cpp # Source/GameClient/System/CampaignManager.cpp -# "Source/GameClient/System/Debug Displayers/AudioDebugDisplay.cpp" + Source/GameClient/System/Debug/AudioDebugDisplay.cpp # Source/GameClient/System/DebugDisplay.cpp # Source/GameClient/System/Image.cpp # Source/GameClient/System/ParticleSys.cpp # Source/GameClient/System/RayEffect.cpp -# Source/GameClient/System/Smudge.cpp -# Source/GameClient/Terrain/TerrainRoads.cpp -# Source/GameClient/Terrain/TerrainVisual.cpp -# Source/GameClient/VideoPlayer.cpp -# Source/GameClient/VideoStream.cpp -# Source/GameClient/View.cpp -# Source/GameClient/Water.cpp + Source/GameClient/System/Smudge.cpp + Source/GameClient/Terrain/TerrainRoads.cpp + Source/GameClient/Terrain/TerrainVisual.cpp + Source/GameClient/VideoPlayer.cpp + Source/GameClient/VideoStream.cpp + Source/GameClient/View.cpp + Source/GameClient/Water.cpp # Source/GameLogic/AI/AI.cpp # Source/GameLogic/AI/AIDock.cpp # Source/GameLogic/AI/AIGroup.cpp @@ -1081,67 +1096,71 @@ set(GAMEENGINE_SRC # Source/GameLogic/System/GameLogic.cpp # Source/GameLogic/System/GameLogicDispatch.cpp # Source/GameLogic/System/RankInfo.cpp -# Source/GameNetwork/Connection.cpp -# Source/GameNetwork/ConnectionManager.cpp -# Source/GameNetwork/DisconnectManager.cpp -# Source/GameNetwork/DownloadManager.cpp -# Source/GameNetwork/FileTransfer.cpp -# Source/GameNetwork/FirewallHelper.cpp -# Source/GameNetwork/FrameData.cpp -# Source/GameNetwork/FrameDataManager.cpp -# Source/GameNetwork/FrameMetrics.cpp -# Source/GameNetwork/GameInfo.cpp -# Source/GameNetwork/GameMessageParser.cpp + Source/GameNetwork/Connection.cpp + Source/GameNetwork/ConnectionManager.cpp + Source/GameNetwork/DisconnectManager.cpp + Source/GameNetwork/DownloadManager.cpp + Source/GameNetwork/FileTransfer.cpp + Source/GameNetwork/FirewallHelper.cpp + Source/GameNetwork/FrameData.cpp + Source/GameNetwork/FrameDataManager.cpp + Source/GameNetwork/FrameMetrics.cpp + Source/GameNetwork/GameInfo.cpp + Source/GameNetwork/GameMessageParser.cpp #Source/GameNetwork/GameSpyChat.cpp # unused #Source/GameNetwork/GameSpyGameInfo.cpp # unused #Source/GameNetwork/GameSpyGP.cpp # unused -# Source/GameNetwork/GameSpy/Chat.cpp -# Source/GameNetwork/GameSpy/GSConfig.cpp -# Source/GameNetwork/GameSpy/LadderDefs.cpp -# Source/GameNetwork/GameSpy/LobbyUtils.cpp -# Source/GameNetwork/GameSpy/MainMenuUtils.cpp -# Source/GameNetwork/GameSpy/PeerDefs.cpp -# Source/GameNetwork/GameSpy/StagingRoomGameInfo.cpp -# Source/GameNetwork/GameSpy/Thread/BuddyThread.cpp -# Source/GameNetwork/GameSpy/Thread/GameResultsThread.cpp -# Source/GameNetwork/GameSpy/Thread/PeerThread.cpp -# Source/GameNetwork/GameSpy/Thread/PersistentStorageThread.cpp -# Source/GameNetwork/GameSpy/Thread/PingThread.cpp -# Source/GameNetwork/GameSpy/Thread/ThreadUtils.cpp -# Source/GameNetwork/GameSpyOverlay.cpp + Source/GameNetwork/GameSpy/Chat.cpp + Source/GameNetwork/GameSpy/GSConfig.cpp + Source/GameNetwork/GameSpy/LadderDefs.cpp + Source/GameNetwork/GameSpy/LobbyUtils.cpp + Source/GameNetwork/GameSpy/MainMenuUtils.cpp + Source/GameNetwork/GameSpy/PeerDefs.cpp + Source/GameNetwork/GameSpy/StagingRoomGameInfo.cpp + Source/GameNetwork/GameSpy/Thread/BuddyThread.cpp + Source/GameNetwork/GameSpy/Thread/GameResultsThread.cpp + Source/GameNetwork/GameSpy/Thread/PeerThread.cpp + Source/GameNetwork/GameSpy/Thread/PersistentStorageThread.cpp + Source/GameNetwork/GameSpy/Thread/PingThread.cpp + Source/GameNetwork/GameSpy/Thread/ThreadUtils.cpp + Source/GameNetwork/GameSpyOverlay.cpp # Source/GameNetwork/GUIUtil.cpp -# Source/GameNetwork/IPEnumeration.cpp -# Source/GameNetwork/LANAPI.cpp -# Source/GameNetwork/LANAPICallbacks.cpp -# Source/GameNetwork/LANAPIhandlers.cpp -# Source/GameNetwork/LANGameInfo.cpp -# Source/GameNetwork/NAT.cpp -# Source/GameNetwork/NetCommandList.cpp -# Source/GameNetwork/NetCommandMsg.cpp -# Source/GameNetwork/NetCommandRef.cpp -# Source/GameNetwork/NetCommandWrapperList.cpp -# Source/GameNetwork/NetMessageStream.cpp -# Source/GameNetwork/NetPacket.cpp -# Source/GameNetwork/Network.cpp -# Source/GameNetwork/NetworkUtil.cpp -# Source/GameNetwork/Transport.cpp -# Source/GameNetwork/udp.cpp -# Source/GameNetwork/User.cpp -# Source/GameNetwork/WOLBrowser/WebBrowser.cpp + Source/GameNetwork/IPEnumeration.cpp + Source/GameNetwork/LANAPI.cpp + Source/GameNetwork/LANAPICallbacks.cpp + Source/GameNetwork/LANAPIhandlers.cpp + Source/GameNetwork/LANGameInfo.cpp + Source/GameNetwork/NAT.cpp + Source/GameNetwork/NetCommandList.cpp + Source/GameNetwork/NetCommandMsg.cpp + Source/GameNetwork/NetCommandRef.cpp + Source/GameNetwork/NetCommandWrapperList.cpp + Source/GameNetwork/NetMessageStream.cpp + Source/GameNetwork/NetPacket.cpp + Source/GameNetwork/Network.cpp + Source/GameNetwork/NetworkUtil.cpp + Source/GameNetwork/Transport.cpp + Source/GameNetwork/udp.cpp + Source/GameNetwork/User.cpp + Source/GameNetwork/WOLBrowser/WebBrowser.cpp # Source/Precompiled/PreRTS.cpp ) if(RTS_GAMEMEMORY_ENABLE) # Uses the original Game Memory implementation. list(APPEND GAMEENGINE_SRC -# Source/Common/System/GameMemory.cpp -# Source/Common/System/MemoryInit.cpp + Source/Common/System/GameMemory.cpp + Source/Common/System/GameMemoryInit.cpp + Source/Common/System/GameMemoryInitDMA_Generals.inl + Source/Common/System/GameMemoryInitDMA_GeneralsMD.inl + Source/Common/System/GameMemoryInitPools_Generals.inl + Source/Common/System/GameMemoryInitPools_GeneralsMD.inl ) else() # Uses the null implementation when disabled. list(APPEND GAMEENGINE_SRC -# Source/Common/System/GameMemoryNull.cpp -# Include/Common/GameMemoryNull.h + Source/Common/System/GameMemoryNull.cpp + Include/Common/GameMemoryNull.h ) endif() diff --git a/Core/GameEngine/Include/Common/AddonCompat.h b/Core/GameEngine/Include/Common/AddonCompat.h new file mode 100644 index 00000000000..7b8bc2c9e97 --- /dev/null +++ b/Core/GameEngine/Include/Common/AddonCompat.h @@ -0,0 +1,25 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#pragma once + +namespace addon +{ +extern Bool HasFullviewportDat(); + +} // namespace addon diff --git a/Core/GameEngine/Include/Common/ArchiveFile.h b/Core/GameEngine/Include/Common/ArchiveFile.h new file mode 100644 index 00000000000..7c3d1c7a8fb --- /dev/null +++ b/Core/GameEngine/Include/Common/ArchiveFile.h @@ -0,0 +1,72 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +/////// ArchiveFile.h //////////////////// +// Bryan Cleveland, August 2002 +////////////////////////////////////////// + +#pragma once + +#include "Lib/BaseType.h" +#include "Common/AsciiString.h" +#include "Common/ArchiveFileSystem.h" + +class File; + +/** + * An archive file is itself a collection of sub files. Each file inside the archive file + * has a unique name by which it can be accessed. The ArchiveFile object class is the + * runtime interface to the mix file and the sub files. Each file inside the mix + * file can be accessed by the openFile(). + * + * ArchiveFile interfaces can be created by the TheArchiveFileSystem object. + */ +//=============================== + +class ArchiveFile +{ +public: + ArchiveFile(); + virtual ~ArchiveFile(); + + virtual Bool getFileInfo( const AsciiString& filename, FileInfo *fileInfo) const = 0; ///< fill in the fileInfo struct with info about the file requested. + virtual File* openFile( const Char *filename, Int access = 0) = 0; ///< Open the specified file within the archive file + virtual void closeAllFiles( void ) = 0; ///< Close all file opened in this archive file + virtual AsciiString getName( void ) = 0; ///< Returns the name of the archive file + virtual AsciiString getPath( void ) = 0; ///< Returns full path and name of archive file + virtual void setSearchPriority( Int new_priority ) = 0; ///< Set this archive file's search priority + virtual void close( void ) = 0; ///< Close this archive file + void attachFile(File *file); + + void getFileListInDirectory(const AsciiString& currentDirectory, const AsciiString& originalDirectory, const AsciiString& searchName, FilenameList &filenameList, Bool searchSubdirectories) const; + void getFileListInDirectory(const DetailedArchivedDirectoryInfo *dirInfo, const AsciiString& currentDirectory, const AsciiString& searchName, FilenameList &filenameList, Bool searchSubdirectories) const; + + void addFile(const AsciiString& path, const ArchivedFileInfo *fileInfo); ///< add this file to our directory tree. + +protected: + const ArchivedFileInfo * getArchivedFileInfo(const AsciiString& filename) const; ///< return the ArchivedFileInfo from the directory tree. + + File *m_file; ///< file pointer to the archive file on disk. Kept open so we don't have to continuously open and close the file all the time. + DetailedArchivedDirectoryInfo m_rootDirectory; +}; diff --git a/Core/GameEngine/Include/Common/ArchiveFileSystem.h b/Core/GameEngine/Include/Common/ArchiveFileSystem.h new file mode 100644 index 00000000000..a482e593b99 --- /dev/null +++ b/Core/GameEngine/Include/Common/ArchiveFileSystem.h @@ -0,0 +1,179 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +//---------------------------------------------------------------------------- +// +// Westwood Studios Pacific. +// +// Confidential Information +// Copyright (C) 2001 - All Rights Reserved +// +//---------------------------------------------------------------------------- +// +// Project: Generals +// +// Module: Archive files +// +// File name: Common/ArchiveFileSystem.h +// +// Created: 11/26/01 TR +// +//---------------------------------------------------------------------------- + +#pragma once + +#define MUSIC_BIG "Music.big" + +//---------------------------------------------------------------------------- +// Includes +//---------------------------------------------------------------------------- + +#include "Common/SubsystemInterface.h" +#include "Common/AsciiString.h" +#include "Common/FileSystem.h" // for typedefs, etc. +#include "Common/STLTypedefs.h" + +//---------------------------------------------------------------------------- +// Forward References +//---------------------------------------------------------------------------- + +class File; +class ArchiveFile; + +//---------------------------------------------------------------------------- +// Type Defines +//---------------------------------------------------------------------------- + + +//=============================== +// ArchiveFileSystem +//=============================== +/** + * Creates and manages ArchiveFile interfaces. ArchiveFiles can be accessed + * by calling the openArchiveFile() member. ArchiveFiles can be accessed by + * name or by File interface. + * + * openFile() member searches all Archive files for the specified sub file. + */ +//=============================== +class ArchivedDirectoryInfo; +class DetailedArchivedDirectoryInfo; +class ArchivedFileInfo; + +typedef std::map DetailedArchivedDirectoryInfoMap; // Archived directory name to detailed archived directory info +typedef std::map ArchivedDirectoryInfoMap; // Archived directory name to archived directory info +typedef std::map ArchivedFileInfoMap; // Archived file name to archived file info +typedef std::map ArchiveFileMap; // Archive file name to archive data +typedef std::multimap ArchivedFileLocationMap; // Archived file name to archive data + +class ArchivedDirectoryInfo +{ +public: + AsciiString m_path; // The full path to this directory + AsciiString m_directoryName; // The current directory + ArchivedDirectoryInfoMap m_directories; // Contained leaf directories + ArchivedFileLocationMap m_files; // Contained files +}; + +class DetailedArchivedDirectoryInfo +{ +public: + AsciiString m_directoryName; + DetailedArchivedDirectoryInfoMap m_directories; + ArchivedFileInfoMap m_files; +}; + +class ArchivedFileInfo +{ +public: + AsciiString m_filename; + AsciiString m_archiveFilename; + UnsignedInt m_offset; + UnsignedInt m_size; + + ArchivedFileInfo() + : m_offset(0) + , m_size(0) + { + } +}; + + +class ArchiveFileSystem : public SubsystemInterface +{ +public: + ArchiveFileSystem(); + virtual ~ArchiveFileSystem(); + + virtual void init( void ) = 0; + virtual void update( void ) = 0; + virtual void reset( void ) = 0; + virtual void postProcessLoad( void ) = 0; + + // ArchiveFile operations + virtual ArchiveFile* openArchiveFile( const Char *filename ) = 0; ///< Create new or return existing Archive file from file name + virtual void closeArchiveFile( const Char *filename ) = 0; ///< Close the one specified big file. + virtual void closeAllArchiveFiles( void ) = 0; ///< Close all Archive files currently open + + // File operations + virtual File* openFile( const Char *filename, Int access = 0, FileInstance instance = 0); ///< Search Archive files for specified file name and open it if found + virtual void closeAllFiles( void ) = 0; ///< Close all files associated with Archive files + virtual Bool doesFileExist(const Char *filename, FileInstance instance = 0) const; ///< return true if that file exists in an archive file somewhere. + + void getFileListInDirectory(const AsciiString& currentDirectory, const AsciiString& originalDirectory, const AsciiString& searchName, FilenameList &filenameList, Bool searchSubdirectories) const; ///< search the given directory for files matching the searchName (egs. *.ini, *.rep). Possibly search subdirectories. Scans each Archive file. + Bool getFileInfo(const AsciiString& filename, FileInfo *fileInfo, FileInstance instance = 0) const; ///< see FileSystem.h + + virtual Bool loadBigFilesFromDirectory(AsciiString dir, AsciiString fileMask, Bool overwrite = FALSE) = 0; + + // Unprotected this for copy-protection routines + ArchiveFile* getArchiveFile(const AsciiString& filename, FileInstance instance = 0) const; + + void loadMods( void ); + + ArchivedDirectoryInfo* friend_getArchivedDirectoryInfo(const Char* directory); + +protected: + struct ArchivedDirectoryInfoResult + { + ArchivedDirectoryInfoResult() : dirInfo(NULL) {} + Bool valid() const { return dirInfo != NULL; } + + ArchivedDirectoryInfo* dirInfo; + AsciiString lastToken; ///< Synonymous for file name if the search directory was a file path + }; + + ArchivedDirectoryInfoResult getArchivedDirectoryInfo(const Char* directory); + + virtual void loadIntoDirectoryTree(ArchiveFile *archiveFile, Bool overwrite = FALSE); ///< load the archive file's header information and apply it to the global archive directory tree. + + ArchiveFileMap m_archiveFileMap; + ArchivedDirectoryInfo m_rootDirectory; +}; + + +extern ArchiveFileSystem *TheArchiveFileSystem; + +//---------------------------------------------------------------------------- +// Inlining +//---------------------------------------------------------------------------- diff --git a/Core/GameEngine/Include/Common/AsciiString.h b/Core/GameEngine/Include/Common/AsciiString.h new file mode 100644 index 00000000000..5af9cbe130e --- /dev/null +++ b/Core/GameEngine/Include/Common/AsciiString.h @@ -0,0 +1,596 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: AsciiString.h +//----------------------------------------------------------------------------- +// +// Westwood Studios Pacific. +// +// Confidential Information +// Copyright (C) 2001 - All Rights Reserved +// +//----------------------------------------------------------------------------- +// +// Project: RTS3 +// +// File name: AsciiString.h +// +// Created: Steven Johnson, October 2001 +// +// Desc: General-purpose string classes +// +//----------------------------------------------------------------------------- +/////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include "Lib/BaseType.h" +#include "Common/Debug.h" +#include "Common/Errors.h" + +class UnicodeString; + +// ----------------------------------------------------- +/** + AsciiString is the fundamental single-byte string type used in the Generals + code base, and should be preferred over all other string constructions + (e.g., array of char, STL string<>, WWVegas StringClass, etc.) + + Of course, other string setups may be used when necessary or appropriate! + + AsciiString is modeled after the MFC CString class, with some minor + syntactic differences to keep in line with our coding conventions. + + Basically, AsciiString allows you to treat a string as an intrinsic + type, rather analogous to 'int' -- when passed by value, a new string + is created, and modifying the new string doesn't modify the original. + This is done fairly efficiently, so that no new memory allocation is done + unless the string is actually modified. + + Naturally, AsciiString handles all memory issues, so there's no need + to do anything to free memory... just allow the AsciiString's + destructor to run. + + AsciiStrings are suitable for use as automatic, member, or static variables. +*/ + +class AsciiString +{ +private: + + // Note, this is a Plain Old Data Structure... don't + // add a ctor/dtor, 'cuz they won't ever be called. + struct AsciiStringData + { +#if defined(RTS_DEBUG) + const char* m_debugptr; // just makes it easier to read in the debugger +#endif + unsigned short m_refCount; // reference count + unsigned short m_numCharsAllocated; // length of data allocated + // char m_stringdata[]; + + char* peek() { return (char*)(this+1); } + }; + + #ifdef RTS_DEBUG + void validate() const; + #else + void validate() const { } + #endif + +protected: + AsciiStringData* m_data; // pointer to ref counted string data + + char* peek() const; + void releaseBuffer(); + void ensureUniqueBufferOfSize(int numCharsNeeded, Bool preserveData, const char* strToCpy, const char* strToCat); + +public: + + typedef Char value_type; + typedef value_type* pointer; + typedef const value_type* const_pointer; + + enum + { + MAX_FORMAT_BUF_LEN = 2048, ///< max total len of string created by format/format_va + MAX_LEN = 32767 ///< max total len of any AsciiString, in chars + }; + + + /** + This is a convenient global used to indicate the empty + string, so we don't need to construct temporaries + for such a common thing. + */ + static const AsciiString TheEmptyString; + + /** + Default constructor -- construct a new, empty AsciiString. + */ + AsciiString(); + /** + Copy constructor -- make this AsciiString identical to the + other AsciiString. (This is actually quite efficient, because + they will simply share the same string and increment the + refcount.) + */ + AsciiString(const AsciiString& stringSrc); + /** + Constructor -- from a literal string. Constructs an AsciiString + with the given string. Note that a copy of the string is made; + the input ptr is not saved. + Note that this is no longer explicit, as the conversion is almost + always wanted, anyhow. + */ + AsciiString(const char* s); + + /** + Constructs an AsciiString with the given string and length. + The length must not be larger than the actual string length. + */ + AsciiString(const char* s, int len); + + /** + Destructor. Not too exciting... clean up the works and such. + */ + ~AsciiString(); + + /** + Return the length, in characters, of the string up to the first zero or null terminator. + */ + int getLength() const; + + /** + Return the number of bytes used by the string up to the first zero or null terminator. + */ + int getByteCount() const; + /** + Return true iff the length of the string is zero. Equivalent + to (getLength() == 0) but slightly more efficient. + */ + Bool isEmpty() const; + /** + Make the string empty. Equivalent to (str = "") but slightly more efficient. + */ + void clear(); + + /** + Return the character and the given (zero-based) index into the string. + No range checking is done (except in debug mode). + */ + char getCharAt(int index) const; + /** + Return a pointer to the (null-terminated) string. Note that this is + a const pointer: do NOT change this! It is imperative that it be + impossible (or at least, really difficuly) for someone to change our + private data, since it might be shared amongst other AsciiStrings. + */ + const char* str() const; + + /** + Makes sure there is room for a string of len+1 characters, and + returns a pointer to the string buffer. This ensures that the + string buffer is NOT shared. This is intended for the file reader, + that is reading new strings in from a file. jba. + */ + char* getBufferForRead(Int len); + + /** + Replace the contents of self with the given string. + (This is actually quite efficient, because + they will simply share the same string and increment the + refcount.) + */ + void set(const AsciiString& stringSrc); + + /** + Replace the contents of self with the given string. + Note that a copy of the string is made; the input ptr is not saved. + */ + void set(const char* s); + + /** + Replace the contents of self with the given string and length. + Note that a copy of the string is made; the input ptr is not saved. + The length must not be larger than the actual string length. + */ + void set(const char* s, int len); + + /** + replace contents of self with the given string. Note the + nomenclature is translate rather than set; this is because + not all single-byte strings translate one-for-one into + UnicodeStrings, so some data manipulation may be necessary, + and the resulting strings may not be equivalent. + */ + void translate(const UnicodeString& stringSrc); + + /** + Concatenate the given string onto self. + */ + void concat(const AsciiString& stringSrc); + /** + Concatenate the given string onto self. + */ + void concat(const char* s); + /** + Concatenate the given character onto self. + */ + void concat(const char c); + + /** + Remove leading and trailing whitespace from the string. + */ + void trim( void ); + + /** + Remove trailing whitespace from the string. + */ + void trimEnd(void); + + /** + Remove all consecutive occurances of c from the end of the string. + */ + void trimEnd(const char c); + + /** + Make the string lowercase + */ + void toLower( void ); + + /** + Remove the final character in the string. If the string is empty, + do nothing. (This is a rather dorky method, but used a lot in + text editing, thus its presence here.) + */ + void removeLastChar(); + + /** + Remove the final charCount characters in the string. If the string is empty, + do nothing. + */ + void truncateBy(const Int charCount); + + /** + Truncate the string to a length of maxLength characters, not including null termination, + by removing from the end. If the string is empty or shorter than maxLength, do nothing. + */ + void truncateTo(const Int maxLength); + + /** + Analogous to sprintf() -- this formats a string according to the + given sprintf-style format string (and the variable argument list) + and stores the result in self. + */ + void format(AsciiString format, ...); + void format(const char* format, ...); + /** + Identical to format(), but takes a va_list rather than + a variable argument list. (i.e., analogous to vsprintf.) + */ + void format_va(const AsciiString& format, va_list args); + void format_va(const char* format, va_list args); + + /** + Conceptually identical to strcmp(). + */ + int compare(const AsciiString& stringSrc) const; + /** + Conceptually identical to strcmp(). + */ + int compare(const char* s) const; + /** + Conceptually identical to _stricmp(). + */ + int compareNoCase(const AsciiString& stringSrc) const; + /** + Conceptually identical to _stricmp(). + */ + int compareNoCase(const char* s) const; + + /** + Conceptually identical to strchr(). + */ + const char* find(char c) const; + + /** + Conceptually identical to strrchr(). + */ + const char* reverseFind(char c) const; + + /** + return true iff self starts with the given string. + */ + Bool startsWith(const char* p) const; + Bool startsWith(const AsciiString& stringSrc) const { return startsWith(stringSrc.str()); } + + /** + return true iff self starts with the given string. (case insensitive) + */ + Bool startsWithNoCase(const char* p) const; + Bool startsWithNoCase(const AsciiString& stringSrc) const { return startsWithNoCase(stringSrc.str()); } + + /** + return true iff self ends with the given string. + */ + Bool endsWith(const char* p) const; + Bool endsWith(const AsciiString& stringSrc) const { return endsWith(stringSrc.str()); } + + /** + return true iff self ends with the given string. (case insensitive) + */ + Bool endsWithNoCase(const char* p) const; + Bool endsWithNoCase(const AsciiString& stringSrc) const { return endsWithNoCase(stringSrc.str()); } + + /** + conceptually similar to strtok(): + + extract the next seps-delimited token from the front + of 'this' and copy it into 'token', returning true if a nonempty + token was found. (note that this modifies 'this' as well, stripping + the token off!) + */ + Bool nextToken(AsciiString* token, const char* seps = NULL); + + /** + return true iff the string is "NONE" (case-insensitive). + Hey, hokey, but we use it a ton. + */ + Bool isNone() const; + + Bool isNotEmpty() const { return !isEmpty(); } + Bool isNotNone() const { return !isNone(); } + +// +// You might think it would be a good idea to overload the * operator +// to allow for an implicit conversion to an char*. This is +// (in theory) a good idea, but in practice, there's lots of code +// that assumes it should check text fields for null, which +// is meaningless for us, since we never return a null ptr. +// +// operator const char*() const { return str(); } +// + + AsciiString& operator=(const AsciiString& stringSrc); ///< the same as set() + AsciiString& operator=(const char* s); ///< the same as set() + + void debugIgnoreLeaks(); + +}; + +// ----------------------------------------------------- +inline char* AsciiString::peek() const +{ + DEBUG_ASSERTCRASH(m_data, ("null string ptr")); + validate(); + return m_data->peek(); +} + +// ----------------------------------------------------- +inline AsciiString::AsciiString() : m_data(0) +{ + validate(); +} + +// ----------------------------------------------------- +inline AsciiString::~AsciiString() +{ + validate(); + releaseBuffer(); +} + +// ----------------------------------------------------- +inline int AsciiString::getLength() const +{ + validate(); + return m_data ? strlen(peek()) : 0; +} + +// ----------------------------------------------------- +inline int AsciiString::getByteCount() const +{ + validate(); + return m_data ? getLength() : 0; +} + +// ----------------------------------------------------- +inline Bool AsciiString::isEmpty() const +{ + validate(); + return m_data == NULL || peek()[0] == 0; +} + +// ----------------------------------------------------- +inline void AsciiString::clear() +{ + validate(); + releaseBuffer(); + validate(); +} + +// ----------------------------------------------------- +inline char AsciiString::getCharAt(int index) const +{ + DEBUG_ASSERTCRASH(index >= 0 && index < getLength(), ("bad index in getCharAt")); + validate(); + return m_data ? peek()[index] : 0; +} + +// ----------------------------------------------------- +inline const char* AsciiString::str() const +{ + validate(); + static const char TheNullChr = 0; + return m_data ? peek() : &TheNullChr; +} + +// ----------------------------------------------------- +inline AsciiString& AsciiString::operator=(const AsciiString& stringSrc) +{ + validate(); + set(stringSrc); + validate(); + return *this; +} + +// ----------------------------------------------------- +inline AsciiString& AsciiString::operator=(const char* s) +{ + validate(); + set(s); + validate(); + return *this; +} + +// ----------------------------------------------------- +inline void AsciiString::concat(const AsciiString& stringSrc) +{ + validate(); + concat(stringSrc.str()); + validate(); +} + +// ----------------------------------------------------- +inline void AsciiString::concat(const char c) +{ + validate(); + /// this can probably be made more efficient, if necessary + char tmp[2] = { c, 0 }; + concat(tmp); + validate(); +} + +// ----------------------------------------------------- +inline int AsciiString::compare(const AsciiString& stringSrc) const +{ + validate(); + return strcmp(this->str(), stringSrc.str()); +} + +// ----------------------------------------------------- +inline int AsciiString::compare(const char* s) const +{ + validate(); + return strcmp(this->str(), s); +} + +// ----------------------------------------------------- +inline int AsciiString::compareNoCase(const AsciiString& stringSrc) const +{ + validate(); + return _stricmp(this->str(), stringSrc.str()); +} + +// ----------------------------------------------------- +inline int AsciiString::compareNoCase(const char* s) const +{ + validate(); + return _stricmp(this->str(), s); +} + +// ----------------------------------------------------- +inline const char* AsciiString::find(char c) const +{ + return strchr(this->str(), c); +} + +// ----------------------------------------------------- +inline const char* AsciiString::reverseFind(char c) const +{ + return strrchr(this->str(), c); +} + +// ----------------------------------------------------- +inline Bool operator==(const AsciiString& s1, const AsciiString& s2) +{ + return strcmp(s1.str(), s2.str()) == 0; +} + +// ----------------------------------------------------- +inline Bool operator!=(const AsciiString& s1, const AsciiString& s2) +{ + return strcmp(s1.str(), s2.str()) != 0; +} + +// ----------------------------------------------------- +inline Bool operator<(const AsciiString& s1, const AsciiString& s2) +{ + return strcmp(s1.str(), s2.str()) < 0; +} + +// ----------------------------------------------------- +inline Bool operator<=(const AsciiString& s1, const AsciiString& s2) +{ + return strcmp(s1.str(), s2.str()) <= 0; +} + +// ----------------------------------------------------- +inline Bool operator>(const AsciiString& s1, const AsciiString& s2) +{ + return strcmp(s1.str(), s2.str()) > 0; +} + +// ----------------------------------------------------- +inline Bool operator>=(const AsciiString& s1, const AsciiString& s2) +{ + return strcmp(s1.str(), s2.str()) >= 0; +} + +// ----------------------------------------------------- +inline Bool operator==(const AsciiString& s1, const char* s2) +{ + return strcmp(s1.str(), s2) == 0; +} + +// ----------------------------------------------------- +inline Bool operator!=(const AsciiString& s1, const char* s2) +{ + return strcmp(s1.str(), s2) != 0; +} + +// ----------------------------------------------------- +inline Bool operator<(const AsciiString& s1, const char* s2) +{ + return strcmp(s1.str(), s2) < 0; +} + +// ----------------------------------------------------- +inline Bool operator<=(const AsciiString& s1, const char* s2) +{ + return strcmp(s1.str(), s2) <= 0; +} + +// ----------------------------------------------------- +inline Bool operator>(const AsciiString& s1, const char* s2) +{ + return strcmp(s1.str(), s2) > 0; +} + +// ----------------------------------------------------- +inline Bool operator>=(const AsciiString& s1, const char* s2) +{ + return strcmp(s1.str(), s2) >= 0; +} diff --git a/Core/GameEngine/Include/Common/AudioAffect.h b/Core/GameEngine/Include/Common/AudioAffect.h new file mode 100644 index 00000000000..f58cb9e18b2 --- /dev/null +++ b/Core/GameEngine/Include/Common/AudioAffect.h @@ -0,0 +1,44 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// EA Pacific +// John McDonald, Jr +// Do not distribute + +#pragma once + +#include + +// if it is set by the options panel, use the system setting parameter. Otherwise, this will be +// appended to whatever the current system volume is. +enum AudioAffect CPP_11(: Int) +{ + AudioAffect_Music = 0x01, + AudioAffect_Sound = 0x02, + AudioAffect_Sound3D = 0x04, + AudioAffect_Speech = 0x08, + AudioAffect_All = (AudioAffect_Music | AudioAffect_Sound | AudioAffect_Sound3D | AudioAffect_Speech), + + AudioAffect_SystemSetting = 0x10, +}; diff --git a/Core/GameEngine/Include/Common/AudioEventInfo.h b/Core/GameEngine/Include/Common/AudioEventInfo.h new file mode 100644 index 00000000000..ff412fcb6f4 --- /dev/null +++ b/Core/GameEngine/Include/Common/AudioEventInfo.h @@ -0,0 +1,135 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: AudioEventInfo.h ///////////////////////////////////////////////////////////////////////// +// AudioEventInfo structure +// Author: John K. McDonald, March 2002 + +#pragma once + +#include "Common/AsciiString.h" +#include "Common/GameMemory.h" +#include "Common/STLTypedefs.h" + +// DEFINES +#define NO_INTENSIVE_AUDIO_DEBUG + +// FORWARD DECLARATIONS /////////////////////////////////////////////////////////////////////////// +struct FieldParse; + +// USEFUL DECLARATIONS //////////////////////////////////////////////////////////////////////////// +enum AudioType CPP_11(: Int) +{ + AT_Music, + AT_Streaming, + AT_SoundEffect +}; + +extern const char* const theAudioPriorityNames[]; +enum AudioPriority CPP_11(: Int) +{ + AP_LOWEST, + AP_LOW, + AP_NORMAL, + AP_HIGH, + AP_CRITICAL, + + AP_COUNT +}; + +extern const char *const theSoundTypeNames[]; +enum SoundType CPP_11(: Int) +{ + ST_UI = 0x0001, + ST_WORLD = 0x0002, + ST_SHROUDED = 0x0004, + ST_GLOBAL = 0x0008, + ST_VOICE = 0x0010, + ST_PLAYER = 0x0020, + ST_ALLIES = 0x0040, + ST_ENEMIES = 0x0080, + ST_EVERYONE = 0x0100, +}; + +extern const char *const theAudioControlNames[]; +enum AudioControl CPP_11(: Int) +{ + AC_LOOP = 0x0001, + AC_RANDOM = 0x0002, + AC_ALL = 0x0004, + AC_POSTDELAY = 0x0008, + AC_INTERRUPT = 0x0010, +}; + +class DynamicAudioEventInfo; + +struct AudioEventInfo : public MemoryPoolObject +{ + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( AudioEventInfo, "AudioEventInfo" ) + +public: + AsciiString m_audioName; // This name matches the name of the AudioEventRTS + AsciiString m_filename; // For music tracks, this is the filename of the track + + Real m_volume; // Desired volume of this audio + Real m_volumeShift; // Desired volume shift of the audio + Real m_minVolume; // Clamped minimum value, useful when muting sound effects + Real m_pitchShiftMin; // minimum pitch shift value + Real m_pitchShiftMax; // maximum pitch shift value + Int m_delayMin; // minimum delay before we'll fire up another one of these + Int m_delayMax; // maximum delay before we'll fire up another one of these + Int m_limit; // Limit to the number of these sounds that can be fired up simultaneously + Int m_loopCount; // number of times to loop this sound + + AudioPriority m_priority; // Priority of this sound + UnsignedInt m_type; // Type of sound + UnsignedInt m_control; // control of sound + + std::vector m_soundsMorning; // Sounds to play in the wee hours of the morning + std::vector m_sounds; // Default sounds to play + std::vector m_soundsNight; // Sounds to play at night + std::vector m_soundsEvening; // Sounds to play in the evening + + std::vector m_attackSounds; + std::vector m_decaySounds; + + Real m_lowPassFreq; // When performing low pass filters, what is the maximum frequency heard, expressed as a percentage? + Real m_minDistance; // less than this distance and the sound behaves as though it is at minDistance + Real m_maxDistance; // greater than this distance and the sound behaves as though it is muted + + AudioType m_soundType; // This should be either Music, Streaming or SoundEffect + + + // DynamicAudioEventInfo interfacing functions + virtual Bool isLevelSpecific() const { return false; } ///< If true, this sound is only defined on the current level and can be deleted when that level ends + virtual DynamicAudioEventInfo * getDynamicAudioEventInfo() { return NULL; } ///< If this object is REALLY a DynamicAudioEventInfo, return a pointer to the derived class + virtual const DynamicAudioEventInfo * getDynamicAudioEventInfo() const { return NULL; } ///< If this object is REALLY a DynamicAudioEventInfo, return a pointer to the derived class + + /// Is this a permenant sound? That is, if I start this sound up, will it ever end + /// "on its own" or only if I explicitly kill it? + Bool isPermanentSound() const { return BitIsSet( m_control, AC_LOOP ) && (m_loopCount == 0 ); } + + static const FieldParse m_audioEventInfo[]; ///< the parse table for INI definition + const FieldParse *getFieldParse( void ) const { return m_audioEventInfo; } +}; diff --git a/Core/GameEngine/Include/Common/AudioEventRTS.h b/Core/GameEngine/Include/Common/AudioEventRTS.h new file mode 100644 index 00000000000..5a6f0f4510b --- /dev/null +++ b/Core/GameEngine/Include/Common/AudioEventRTS.h @@ -0,0 +1,212 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: AudioEventRTS.h /////////////////////////////////////////////////////////////////////////////// +// AudioEventRTS structure +// Author: John K. McDonald, March 2002 + +#pragma once + +#include "Common/AsciiString.h" +#include "Common/GameAudio.h" +#include "Common/GameMemory.h" +#include "Common/GameType.h" + +// forward declarations /////////////////////////////////////////////////////////////////////////// +struct AudioEventInfo; + +enum OwnerType CPP_11(: Int) +{ + OT_Positional, + OT_Drawable, + OT_Object, + OT_Dead, + OT_INVALID +}; + +enum PortionToPlay CPP_11(: Int) +{ + PP_Attack, + PP_Sound, + PP_Decay, + PP_Done +}; + +enum AudioPriority CPP_11(: Int); + +// This is called AudioEventRTS because AudioEvent is a typedef in ww3d +// You might want this to be memory pooled (I personally do), but it can't +// because we allocate them on the stack frequently. +class AudioEventRTS +{ +public: + AudioEventRTS( ); + AudioEventRTS( const AsciiString& eventName ); + AudioEventRTS( const AsciiString& eventName, ObjectID ownerID ); + AudioEventRTS( const AsciiString& eventName, DrawableID drawableID ); // Pass 0 for unused if attaching to drawable + AudioEventRTS( const AsciiString& eventName, const Coord3D *positionOfAudio ); + + virtual ~AudioEventRTS( ); + + AudioEventRTS( const AudioEventRTS& right ); + AudioEventRTS& operator=( const AudioEventRTS& right ); + + void setEventName( AsciiString name ); + const AsciiString& getEventName( void ) const { return m_eventName; } + + // generateFilename is separate from generatePlayInfo because generatePlayInfo should only be called once + // per triggered event. generateFilename will be called once per loop, or once to get each filename if 'all' is + // specified. + void generateFilename( void ); + AsciiString getFilename( void ); + + // The attack and decay sounds are generated in generatePlayInfo, because they will never be played more + // than once during a given sound event. + void generatePlayInfo( void ); + Real getPitchShift( void ) const; + Real getVolumeShift( void ) const; + AsciiString getAttackFilename( void ) const; + AsciiString getDecayFilename( void ) const; + Real getDelay( void ) const; + + void decrementDelay( Real timeToDecrement ); + + PortionToPlay getNextPlayPortion( void ) const; + void advanceNextPlayPortion( void ); + void setNextPlayPortion( PortionToPlay ptp ); + + void decreaseLoopCount( void ); + Bool hasMoreLoops( void ) const; + + void setAudioEventInfo( const AudioEventInfo *eventInfo ) const; + const AudioEventInfo *getAudioEventInfo( void ) const; + + void setPlayingHandle( AudioHandle handle ); // for ID of this audio piece. + AudioHandle getPlayingHandle( void ); // for ID of this audio piece + + void setPosition( const Coord3D *pos ); + const Coord3D* getPosition( void ); + + void setObjectID( ObjectID objID ); + ObjectID getObjectID( void ); + + Bool isDead() const { return m_ownerType == OT_Dead; } + OwnerType getOwnerType() const { return m_ownerType; } + + void setDrawableID( DrawableID drawID ); + DrawableID getDrawableID( void ); + + void setTimeOfDay( TimeOfDay tod ); + TimeOfDay getTimeOfDay( void ) const; + + void setHandleToKill( AudioHandle handleToKill ); + AudioHandle getHandleToKill( void ) const; + + void setShouldFade( Bool shouldFade ); + Bool getShouldFade( void ) const; + + void setIsLogicalAudio( Bool isLogicalAudio ); + Bool getIsLogicalAudio( void ) const; + + Bool isPositionalAudio( void ) const; + Bool isCurrentlyPlaying( void ) const; + + AudioPriority getAudioPriority( void ) const; + void setAudioPriority( AudioPriority newPriority ); + + Real getVolume( void ) const; + void setVolume( Real vol ); + + Int getPlayerIndex( void ) const; + void setPlayerIndex( Int playerNdx ); + + Int getPlayingAudioIndex( void ) { return m_playingAudioIndex; }; + void setPlayingAudioIndex( Int pai ) { m_playingAudioIndex = pai; }; + + Bool getUninterruptable( ) const { return m_uninterruptable; } + void setUninterruptable( Bool uninterruptable ) { m_uninterruptable = uninterruptable; } + + + // This will retrieve the appropriate position based on type. + const Coord3D *getCurrentPosition( void ); + + // This will return the directory leading up to the appropriate type, including the trailing '\\' + // If localized is true, we'll append a language specifc directory to the end of the path. + AsciiString generateFilenamePrefix( AudioType audioTypeToPlay, Bool localized ); + AsciiString generateFilenameExtension( AudioType audioTypeToPlay ); +protected: + void adjustForLocalization( AsciiString &strToAdjust ); + +protected: + AsciiString m_filenameToLoad; + mutable const AudioEventInfo *m_eventInfo; // Mutable so that it can be modified even on const objects + AudioHandle m_playingHandle; + + AudioHandle m_killThisHandle; ///< Sometimes sounds will canabilize other sounds in order to take their handle away. + ///< This is one of those instances. + + AsciiString m_eventName; ///< This should correspond with an entry in Dialog.ini, Speech.ini, or Audio.ini + AsciiString m_attackName; ///< This is the filename that should be used during the attack. + AsciiString m_decayName; ///< This is the filename that should be used during the decay. + + AudioPriority m_priority; ///< This should be the priority as given by the event info, or the overrided priority. + Real m_volume; ///< This is the override for the volume. It will either be the normal + TimeOfDay m_timeOfDay; ///< This should be the current Time Of Day. + + Coord3D m_positionOfAudio; ///< Position of the sound if no further positional updates are necessary + union // These are now unioned. + { + ObjectID m_objectID; ///< ObjectID of the object that this sound is tied to. Position can be automatically updated from this. + DrawableID m_drawableID; ///< DrawableID of the drawable that owns this sound + }; + OwnerType m_ownerType; + + Bool m_shouldFade; ///< This should fade in or out (if it is starting or stopping) + Bool m_isLogicalAudio; ///< Should probably only be true for scripted sounds + Bool m_uninterruptable; + + // Playing attributes + Real m_pitchShift; ///< Pitch shift that should occur on this piece of audio + Real m_volumeShift; ///< Volume shift that should occur on this piece of audio + Real m_delay; ///< Amount to delay before playing this sound + Int m_loopCount; ///< The current loop count value. Only valid if this is a looping type event or the override has been set. + Int m_playingAudioIndex; ///< The sound index we are currently playing. In the case of non-random, we increment this to move to the next sound + Int m_allCount; ///< If this sound is an ALL type, then this is how many sounds we have played so far. + + Int m_playerIndex; ///< The index of the player who owns this sound. Used for sounds that should have an owner, but don't have an object, etc. + + PortionToPlay m_portionToPlayNext; ///< Which portion (attack, sound, decay) should be played next? +}; + +class DynamicAudioEventRTS : public MemoryPoolObject +{ + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(DynamicAudioEventRTS, "DynamicAudioEventRTS" ) +public: + + DynamicAudioEventRTS() { } + DynamicAudioEventRTS(const AudioEventRTS& a) : m_event(a) { } + + AudioEventRTS m_event; +}; +EMPTY_DTOR(DynamicAudioEventRTS) diff --git a/Core/GameEngine/Include/Common/AudioHandleSpecialValues.h b/Core/GameEngine/Include/Common/AudioHandleSpecialValues.h new file mode 100644 index 00000000000..102f9ed860b --- /dev/null +++ b/Core/GameEngine/Include/Common/AudioHandleSpecialValues.h @@ -0,0 +1,40 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// EA Pacific +// John McDonald, Jr +// Do not distribute + +#pragma once + +enum AudioHandleSpecialValues CPP_11(: Int) +{ + AHSV_Error = 0x00, + AHSV_NoSound, + AHSV_Muted, + AHSV_NotForLocal, + AHSV_StopTheMusic, + AHSV_StopTheMusicFade, + AHSV_FirstHandle +}; diff --git a/Core/GameEngine/Include/Common/AudioRandomValue.h b/Core/GameEngine/Include/Common/AudioRandomValue.h new file mode 100644 index 00000000000..4efc9c26b3d --- /dev/null +++ b/Core/GameEngine/Include/Common/AudioRandomValue.h @@ -0,0 +1,42 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// AudioRandomValue.h +// Random number generation system +// Author: Michael S. Booth, January 1998 +// Split out into separate Logic/Client/Audio headers by MDC Sept 2002 + +#pragma once + +#include "Lib/BaseType.h" + +// do NOT use these functions directly, rather use the macros below +extern Int GetGameAudioRandomValue( int lo, int hi, const char *file, int line ); +extern Real GetGameAudioRandomValueReal( Real lo, Real hi, const char *file, int line ); + +// use these macros to access the random value functions +#define GameAudioRandomValue( lo, hi ) GetGameAudioRandomValue( lo, hi, __FILE__, __LINE__ ) +#define GameAudioRandomValueReal( lo, hi ) GetGameAudioRandomValueReal( lo, hi, __FILE__, __LINE__ ) + +//-------------------------------------------------------------------------------------------------------------- diff --git a/Core/GameEngine/Include/Common/AudioRequest.h b/Core/GameEngine/Include/Common/AudioRequest.h new file mode 100644 index 00000000000..a0321fdb481 --- /dev/null +++ b/Core/GameEngine/Include/Common/AudioRequest.h @@ -0,0 +1,56 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// EA Pacific +// John McDonald, Jr +// Do not distribute + +#pragma once + +#include "Common/GameAudio.h" +#include "Common/GameMemory.h" + +class AudioEventRTS; + +enum RequestType CPP_11(: Int) +{ + AR_Play, + AR_Pause, + AR_Stop +}; + +struct AudioRequest : public MemoryPoolObject +{ + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( AudioRequest, "AudioRequest" ) + +public: + RequestType m_request; + union + { + AudioEventRTS *m_pendingEvent; + AudioHandle m_handleToInteractOn; + }; + Bool m_usePendingEvent; + Bool m_requiresCheckForSample; +}; diff --git a/Core/GameEngine/Include/Common/AudioSettings.h b/Core/GameEngine/Include/Common/AudioSettings.h new file mode 100644 index 00000000000..93a485e848a --- /dev/null +++ b/Core/GameEngine/Include/Common/AudioSettings.h @@ -0,0 +1,102 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// EA Pacific +// John McDonald, Jr +// Do not distribute + +#pragma once + +#include "Common/AsciiString.h" + +enum { MAX_HW_PROVIDERS = 4 }; + +// TheSuperHackers @tweak xezon 23/07/2025 Adds setting to modify the volume of money deposit and withdraw sounds + +struct AudioSettings +{ + AudioSettings() +#if RTS_GENERALS + : m_defaultMoneyTransactionVolume(1.0f) +#elif RTS_ZEROHOUR + : m_defaultMoneyTransactionVolume(0.0f) // Uses zero volume by default because originally the money sounds did not work in Zero Hour +#endif + { + } + + AsciiString m_audioRoot; + AsciiString m_soundsFolder; + AsciiString m_musicFolder; + AsciiString m_streamingFolder; + AsciiString m_soundsExtension; + Bool m_useDigital; + Bool m_useMidi; + Int m_outputRate; + Int m_outputBits; + Int m_outputChannels; + Int m_sampleCount2D; + Int m_sampleCount3D; + Int m_streamCount; + Int m_globalMinRange; + Int m_globalMaxRange; + Int m_drawableAmbientFrames; + Int m_fadeAudioFrames; + UnsignedInt m_maxCacheSize; + + Real m_minVolume; // At volumes less than this, the sample will be culled. + + AsciiString m_preferred3DProvider[MAX_HW_PROVIDERS + 1]; + + //Defaults actually don't ever get changed! + Real m_relative2DVolume; //2D volume compared to 3D + Real m_defaultSoundVolume; + Real m_default3DSoundVolume; + Real m_defaultSpeechVolume; + Real m_defaultMusicVolume; + Real m_defaultMoneyTransactionVolume; + UnsignedInt m_defaultSpeakerType2D; + UnsignedInt m_defaultSpeakerType3D; + + //If you want to change a value, store it somewhere else (like here) + Real m_preferredSoundVolume; + Real m_preferred3DSoundVolume; + Real m_preferredSpeechVolume; + Real m_preferredMusicVolume; + Real m_preferredMoneyTransactionVolume; + + //The desired altitude of the microphone to improve panning relative to terrain. + Real m_microphoneDesiredHeightAboveTerrain; + + //When tracing a line between the ground look-at-point and the camera, we want + //to ensure a maximum percentage, so the microphone never goes behind the camera. + Real m_microphoneMaxPercentageBetweenGroundAndCamera; + + //Handles changing sound volume whenever the camera is close to the microphone. + Real m_zoomMinDistance; //If we're closer than the minimum distance, then apply the full bonus no matter how close. + Real m_zoomMaxDistance; //The maximum distance from microphone we need to be before benefiting from any bonus. + + //NOTE: The higher this value is, the lower normal sounds will be! If you specify a sound volume value of 25%, then sounds will play + //between 75% and 100%, not 100% to 125%! + Real m_zoomSoundVolumePercentageAmount; //The amount of sound volume dedicated to zooming. +}; diff --git a/Core/GameEngine/Include/Common/CRCDebug.h b/Core/GameEngine/Include/Common/CRCDebug.h new file mode 100644 index 00000000000..1fbeffebe61 --- /dev/null +++ b/Core/GameEngine/Include/Common/CRCDebug.h @@ -0,0 +1,128 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// CRCDebug.h /////////////////////////////////////////////////////////////// +// Macros/functions/etc to help logging values for tracking down sync errors +// Author: Matthew D. Campbell, June 2002 + +#pragma once + +#include "Common/Debug.h" + +#ifndef NO_DEBUG_CRC + #ifdef DEBUG_LOGGING + #define DEBUG_CRC + #endif +#endif + +#ifdef DEBUG_CRC + +#include "Common/AsciiString.h" +#include "GameLogic/GameLogic.h" +#include "Lib/BaseType.h" +#include "WWMath/vector3.h" +#include "WWMath/matrix3d.h" + + #define AS_INT(x) (*(Int *)(&x)) + #define DUMPVEL DUMPCOORD3DNAMED(&m_vel, "m_vel") + #define DUMPACCEL DUMPCOORD3DNAMED(&m_accel, "m_accel") + #define DUMPVECTOR3(x) DUMPVECTOR3NAMED(x, #x) + #define DUMPVECTOR3NAMED(x, y) dumpVector3(x, y, __FILE__, __LINE__) + #define DUMPCOORD3D(x) DUMPCOORD3DNAMED(x, #x) + #define DUMPCOORD3DNAMED(x, y) dumpCoord3D(x, y, __FILE__, __LINE__) + #define DUMPMATRIX3D(x) DUMPMATRIX3DNAMED(x, #x) + #define DUMPMATRIX3DNAMED(x, y) dumpMatrix3D(x, y, __FILE__, __LINE__) + #define DUMPREAL(x) DUMPREALNAMED(x, #x) + #define DUMPREALNAMED(x, y) dumpReal(x, y, __FILE__, __LINE__) + + extern Int TheCRCFirstFrameToLog; + extern UnsignedInt TheCRCLastFrameToLog; + + void dumpVector3(const Vector3 *v, AsciiString name, AsciiString fname, Int line); + void dumpCoord3D(const Coord3D *c, AsciiString name, AsciiString fname, Int line); + void dumpMatrix3D(const Matrix3D *m, AsciiString name, AsciiString fname, Int line); + void dumpReal(Real r, AsciiString name, AsciiString fname, Int line); + + void outputCRCDebugLines( void ); + void CRCDebugStartNewGame( void ); + void outputCRCDumpLines( void ); + + void addCRCDebugLine(const char *fmt, ...); + void addCRCDebugLineNoCounter(const char *fmt, ...); + void addCRCDumpLine(const char *fmt, ...); + void addCRCGenLine(const char *fmt, ...); + #define CRCDEBUG_LOG(x) addCRCDebugLine x + #define CRCDUMP_LOG(x) addCRCDumpLine x + #define CRCGEN_LOG(x) addCRCGenLine x + + class CRCVerification + { + public: + CRCVerification(); + ~CRCVerification(); + protected: + UnsignedInt m_startCRC; + }; + #define VERIFY_CRC CRCVerification crcVerification; + + extern Int lastCRCDebugFrame; + extern Int lastCRCDebugIndex; + + extern Bool g_verifyClientCRC; + extern Bool g_clientDeepCRC; + + extern Bool g_crcModuleDataFromClient; + extern Bool g_crcModuleDataFromLogic; + + extern Bool g_keepCRCSaves; + extern Bool g_saveDebugCRCPerFrame; + extern AsciiString g_saveDebugCRCPerFrameDir; + + extern Bool g_logObjectCRCs; + +#else // DEBUG_CRC + + #define DUMPVEL {} + #define DUMPACCEL {} + #define DUMPVECTOR3(x) {} + #define DUMPVECTOR3NAMED(x, y) {} + #define DUMPCOORD3D(x) {} + #define DUMPCOORD3DNAMED(x, y) {} + #define DUMPMATRIX3D(x) {} + #define DUMPMATRIX3DNAMED(x, y) {} + + #define DUMPREAL(x) {} + #define DUMPREALNAMED(x, y) {} + + #define CRCDEBUG_LOG(x) {} + #define CRCDUMP_LOG(x) {} + #define CRCGEN_LOG(x) {} + + #define VERIFY_CRC {} + +#endif + +extern Int NET_CRC_INTERVAL; +extern Int REPLAY_CRC_INTERVAL; +extern Bool TheDebugIgnoreSyncErrors; diff --git a/Core/GameEngine/Include/Common/Debug.h b/Core/GameEngine/Include/Common/Debug.h new file mode 100644 index 00000000000..673e7d58e7b --- /dev/null +++ b/Core/GameEngine/Include/Common/Debug.h @@ -0,0 +1,261 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: Debug.h +//----------------------------------------------------------------------------- +// +// Westwood Studios Pacific. +// +// Confidential Information +// Copyright (C) 2001 - All Rights Reserved +// +//----------------------------------------------------------------------------- +// +// Project: RTS3 +// +// File name: Debug.h +// +// Created: Steven Johnson, August 2001 +// +// Desc: Debug Utilities +// +//----------------------------------------------------------------------------- +/////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include + +class AsciiString; + +#define NO_RELEASE_DEBUG_LOGGING + +#ifdef RELEASE_DEBUG_LOGGING ///< Creates a DebugLogFile.txt (No I or D) with all the debug log goodness. Good for startup problems. + #define ALLOW_DEBUG_UTILS 1 + #define DEBUG_LOGGING 1 + #define DISABLE_DEBUG_CRASHING 1 + #define DISABLE_DEBUG_STACKTRACE 1 + #define DISABLE_DEBUG_PROFILE 1 +#endif + +// These are stolen from the WW3D Debug file. REALLY useful. :-) +#define STRING_IT(a) #a +#define TOKEN_IT(a) STRING_IT(,##a) +#define MESSAGE(a) message (__FILE__ "(" TOKEN_IT(__LINE__) ") : " a) + +// by default, turn on ALLOW_DEBUG_UTILS if RTS_DEBUG is turned on. +#if defined(RTS_DEBUG) && !defined(ALLOW_DEBUG_UTILS) && !defined(DISABLE_ALLOW_DEBUG_UTILS) + #define ALLOW_DEBUG_UTILS 1 +#elif defined(DEBUG_LOGGING) || defined(DEBUG_CRASHING) || defined(DEBUG_STACKTRACE) || defined(DEBUG_PROFILE) + // TheSuperHackers @tweak also turn on when any of the above options is already set. + #define ALLOW_DEBUG_UTILS 1 +#endif + +// these are predicated on ALLOW_DEBUG_UTILS, not RTS_DEBUG, and allow you to selectively disable +// bits of the debug stuff for special builds. +#if defined(ALLOW_DEBUG_UTILS) && !defined(DEBUG_LOGGING) && !defined(DISABLE_DEBUG_LOGGING) + #define DEBUG_LOGGING 1 +#endif +#if defined(ALLOW_DEBUG_UTILS) && !defined(DEBUG_CRASHING) && !defined(DISABLE_DEBUG_CRASHING) + #define DEBUG_CRASHING 1 +#endif +#if defined(ALLOW_DEBUG_UTILS) && !defined(DEBUG_STACKTRACE) && !defined(DISABLE_DEBUG_STACKTRACE) + #define DEBUG_STACKTRACE 1 + #ifndef DEBUG_LOGGING + #define DEBUG_LOGGING 1 // TheSuperHackers @build Stack trace requires logging. + #endif +#endif +#if defined(ALLOW_DEBUG_UTILS) && !defined(DEBUG_PROFILE) && !defined(DISABLE_DEBUG_PROFILE) + #define DEBUG_PROFILE 1 +#endif + +#ifdef __cplusplus + #define DEBUG_EXTERN_C extern "C" +#else + #define DEBUG_EXTERN_C extern +#endif + + +// SYSTEM INCLUDES //////////////////////////////////////////////////////////// + +// USER INCLUDES ////////////////////////////////////////////////////////////// + +// FORWARD REFERENCES ///////////////////////////////////////////////////////// + +// TYPE DEFINES /////////////////////////////////////////////////////////////// + +// INLINING /////////////////////////////////////////////////////////////////// + +// EXTERNALS ////////////////////////////////////////////////////////////////// + +/// @todo: the standard line-to-string trick isn't working correctly in vc6; figure out why +#define DEBUG_STRING_IT(b) #b +#define DEBUG_TOKEN_IT(a) DEBUG_STRING_IT(a) +#define DEBUG_FILENLINE __FILE__ ":" DEBUG_TOKEN_IT(__LINE__) + +#ifdef ALLOW_DEBUG_UTILS + + enum + { + DEBUG_FLAG_LOG_TO_FILE = 0x01, + DEBUG_FLAG_LOG_TO_CONSOLE = 0x02, + DEBUG_FLAG_PREPEND_TIME = 0x04, + DEBUG_FLAGS_DEFAULT = (DEBUG_FLAG_LOG_TO_FILE | DEBUG_FLAG_LOG_TO_CONSOLE), + }; + + DEBUG_EXTERN_C void DebugInit(int flags); + DEBUG_EXTERN_C void DebugShutdown(); + + DEBUG_EXTERN_C int DebugGetFlags(); + DEBUG_EXTERN_C void DebugSetFlags(int flags); + + #define DEBUG_INIT(f) do { DebugInit(f); } while (0) + #define DEBUG_SHUTDOWN() do { DebugShutdown(); } while (0) + +#else + + #define DEBUG_INIT(f) ((void)0) + #define DEBUG_SHUTDOWN() ((void)0) + +#endif + +#ifdef DEBUG_LOGGING + + DEBUG_EXTERN_C void DebugLog(const char *format, ...); + DEBUG_EXTERN_C void DebugLogRaw(const char *format, ...); + DEBUG_EXTERN_C const char* DebugGetLogFileName(); + DEBUG_EXTERN_C const char* DebugGetLogFileNamePrev(); + + // This defines a bitmask of log types that we care about, to allow some flexability + // in what gets logged. This should be extended to asserts, too, but the assert box + // is waiting to be rewritten. -MDC 3/19/2003 + extern unsigned int DebugLevelMask; + enum + { + DEBUG_LEVEL_NET = 0, // in-game network + DEBUG_LEVEL_MAX + }; + extern const char *TheDebugLevels[DEBUG_LEVEL_MAX]; + + #define DEBUG_LOG(m) do { { DebugLog m ; } } while (0) // Log message with trailing new line character (LF) + #define DEBUG_LOG_RAW(m) do { { DebugLogRaw m ; } } while (0) // Log message without trailing new line character (LF) + #define DEBUG_LOG_LEVEL(l, m) do { if (l & DebugLevelMask) { DebugLog m ; } } while (0) + #define DEBUG_LOG_LEVEL_RAW(l, m) do { if (l & DebugLevelMask) { DebugLogRaw m ; } } while (0) + #define DEBUG_ASSERTLOG(c, m) do { { if (!(c)) DebugLog m ; } } while (0) + +#else + + #define DEBUG_LOG(m) ((void)0) + #define DEBUG_LOG_RAW(m) ((void)0) + #define DEBUG_LOG_LEVEL(l, m) ((void)0) + #define DEBUG_LOG_LEVEL_RAW(l, m) ((void)0) + #define DEBUG_ASSERTLOG(c, m) ((void)0) + +#endif + +#ifdef DEBUG_CRASHING + + DEBUG_EXTERN_C void DebugCrash(const char *format, ...); + + /* + Yeah, it's a sleazy global, since we can't reasonably add + any args to DebugCrash due to the varargs nature of it. + We'll just let it slide in this case... + */ + DEBUG_EXTERN_C char* TheCurrentIgnoreCrashPtr; + + #define DEBUG_CRASH(m) \ + do { \ + { \ + static char ignoreCrash = 0; \ + if (!ignoreCrash) { \ + TheCurrentIgnoreCrashPtr = &ignoreCrash; \ + DebugCrash m ; \ + TheCurrentIgnoreCrashPtr = nullptr; \ + } \ + } \ + } while (0) + + #define DEBUG_ASSERTCRASH(c, m) do { { if (!(c)) DEBUG_CRASH(m); } } while (0) + + //Note: RELEASE_CRASH(m) is now always defined. + //#define RELEASE_CRASH(m) DEBUG_CRASH((m)) + +#else + + #define DEBUG_CRASH(m) ((void)0) + #define DEBUG_ASSERTCRASH(c, m) ((void)0) + +// DEBUG_EXTERN_C void ReleaseCrash(const char* reason); + +// #define RELEASE_CRASH(m) do { ReleaseCrash(m); } while (0) + +#endif + +DEBUG_EXTERN_C void ReleaseCrash(const char* reason); +DEBUG_EXTERN_C void ReleaseCrashLocalized(const AsciiString& p, const AsciiString& m); + +#define RELEASE_CRASH(m) do { ReleaseCrash(m); } while (0) +#define RELEASE_CRASHLOCALIZED(p, m) do { ReleaseCrashLocalized(p, m); } while (0) + + +#ifdef DEBUG_PROFILE + +class SimpleProfiler +{ +private: + __int64 m_freq; + __int64 m_startThisSession; + __int64 m_totalThisSession; + __int64 m_totalAllSessions; + int m_numSessions; + +public: + + SimpleProfiler(); + void start(); + void stop(); + void stopAndLog(const char *msg, int howOftenToLog, int howOftenToResetAvg); + double getTime(); // of most recent session, in milliseconds + int getNumSessions(); + double getTotalTime(); // total over all sessions, in milliseconds + double getAverageTime(); // averaged over all sessions, in milliseconds + +}; + +#define BEGIN_PROFILE(uniqueid) \ + static SimpleProfiler prof_##uniqueid; \ + prof_##uniqueid.start(); + +#define END_PROFILE(uniqueid, msg, howoftentolog, howoftentoreset) \ + prof_##uniqueid.stopAndLog(msg, howoftentolog, howoftentoreset); + +#else + +#define BEGIN_PROFILE(uniqueid) +#define END_PROFILE(uniqueid, msg, howoftentolog, howoftentoreset) + +#endif + +// MACROS ////////////////////////////////////////////////////////////////// diff --git a/GeneralsMD/Code/GameEngine/Include/Common/DynamicAudioEventInfo.h b/Core/GameEngine/Include/Common/DynamicAudioEventInfo.h similarity index 97% rename from GeneralsMD/Code/GameEngine/Include/Common/DynamicAudioEventInfo.h rename to Core/GameEngine/Include/Common/DynamicAudioEventInfo.h index c13dfbd5912..145f895cc64 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/DynamicAudioEventInfo.h +++ b/Core/GameEngine/Include/Common/DynamicAudioEventInfo.h @@ -28,10 +28,6 @@ #pragma once - -#ifndef DYNAMICAUDIOEVENTINFO_H_INCLUDED -#define DYNAMICAUDIOEVENTINFO_H_INCLUDED - #include "Common/AudioEventInfo.h" #include "Common/BitFlags.h" @@ -155,8 +151,3 @@ inline Bool DynamicAudioEventInfo::wasPriorityOverriden() const { return m_overriddenFields.test( OVERRIDE_PRIORITY ); } - - - -#endif // DYNAMICAUDIOEVENTINFO_H_INCLUDED - diff --git a/Core/GameEngine/Include/Common/FileSystem.h b/Core/GameEngine/Include/Common/FileSystem.h new file mode 100644 index 00000000000..51bf9153caa --- /dev/null +++ b/Core/GameEngine/Include/Common/FileSystem.h @@ -0,0 +1,189 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +//----------------------------------------------------------------------------= +// +// Westwood Studios Pacific. +// +// Confidential Information +// Copyright(C) 2001 - All Rights Reserved +// +//---------------------------------------------------------------------------- +// +// Project: GameEngine +// +// Module: IO +// +// File name: FileSystem.h +// +// Created: +// +//---------------------------------------------------------------------------- + +#pragma once + +//---------------------------------------------------------------------------- +// Includes +//---------------------------------------------------------------------------- + +#include "Common/file.h" +#include "Common/STLTypedefs.h" +#include "Common/SubsystemInterface.h" + +#include + +#include "mutex.h" + +//---------------------------------------------------------------------------- +// Forward References +//---------------------------------------------------------------------------- + +//---------------------------------------------------------------------------- +// Type Defines +//---------------------------------------------------------------------------- + +typedef std::set > FilenameList; +typedef FilenameList::iterator FilenameListIter; +typedef UnsignedByte FileInstance; + +//---------------------------------------------------------------------------- +// Type Defines +//---------------------------------------------------------------------------- +//#define W3D_DIR_PATH "../FinalArt/W3D/" ///< .w3d files live here +//#define TGA_DIR_PATH "../FinalArt/Textures/" ///< .tga texture files live here +//#define TERRAIN_TGA_DIR_PATH "../FinalArt/Terrain/" ///< terrain .tga texture files live here +#define W3D_DIR_PATH "Art/W3D/" ///< .w3d files live here +#define TGA_DIR_PATH "Art/Textures/" ///< .tga texture files live here +#define TERRAIN_TGA_DIR_PATH "Art/Terrain/" ///< terrain .tga texture files live here +#define MAP_PREVIEW_DIR_PATH "%sMapPreviews/" ///< We need a common place we can copy the map previews to at runtime. +#define USER_W3D_DIR_PATH "%sW3D/" ///< .w3d files live here +#define USER_TGA_DIR_PATH "%sTextures/" ///< User .tga texture files live here + +// the following defines are only to be used while maintaining legacy compatibility +// with old files until they are completely gone and in the regular art set +#ifdef MAINTAIN_LEGACY_FILES +#define LEGACY_W3D_DIR_PATH "../LegacyArt/W3D/" ///< .w3d files live here +#define LEGACY_TGA_DIR_PATH "../LegacyArt/Textures/" ///< .tga texture files live here +#endif // MAINTAIN_LEGACY_FILES + +// LOAD_TEST_ASSETS automatically loads w3d assets from the TEST_W3D_DIR_PATH +// without having to add an INI entry. +#if defined(RTS_DEBUG) +#define LOAD_TEST_ASSETS 1 +#endif + +#ifdef LOAD_TEST_ASSETS + #define ROAD_DIRECTORY "../TestArt/TestRoad/" + #define TEST_STRING "***TESTING" + // the following directories will be used to look for test art + #define LOOK_FOR_TEST_ART + #define TEST_W3D_DIR_PATH "../TestArt/" ///< .w3d files live here + #define TEST_TGA_DIR_PATH "../TestArt/" ///< .tga texture files live here +#endif + +#ifndef ENABLE_FILESYSTEM_LOGGING +#define ENABLE_FILESYSTEM_LOGGING (0) +#endif + + +struct FileInfo { + + Int64 size() const { return (Int64)sizeHigh << 32 | sizeLow; } + Int64 timestamp() const { return (Int64)timestampHigh << 32 | timestampLow; } + + Int sizeHigh; + Int sizeLow; + Int timestampHigh; + Int timestampLow; +}; + +//=============================== +// FileSystem +//=============================== +/** + * FileSystem is an interface class for creating specific FileSystem objects. + * + * A FileSystem object's implementation decides what derivative of File object needs to be + * created when FileSystem::Open() gets called. + */ +// TheSuperHackers @feature xezon 23/08/2025 Implements file instance access. +// Can be used to access different versions of files in different archives under the same name. +// Instance 0 refers to the top file that shadows all other files under the same name. +// +// TheSuperHackers @bugfix xezon 26/10/2025 Adds a mutex to the file exist map to try prevent +// application hangs during level load after the file exist map was corrupted because of writes +// from multiple threads. +//=============================== +class FileSystem : public SubsystemInterface +{ + FileSystem(const FileSystem&); + FileSystem& operator=(const FileSystem&); + +public: + FileSystem(); + virtual ~FileSystem(); + + void init(); + void reset(); + void update(); + + File* openFile( const Char *filename, Int access = File::NONE, size_t bufferSize = File::BUFFERSIZE, FileInstance instance = 0 ); ///< opens a File interface to the specified file + Bool doesFileExist(const Char *filename, FileInstance instance = 0) const; ///< returns TRUE if the file exists. filename should have no directory. + void getFileListInDirectory(const AsciiString& directory, const AsciiString& searchName, FilenameList &filenameList, Bool searchSubdirectories) const; ///< search the given directory for files matching the searchName (egs. *.ini, *.rep). Possibly search subdirectories. + Bool getFileInfo(const AsciiString& filename, FileInfo *fileInfo, FileInstance instance = 0) const; ///< fills in the FileInfo struct for the file given. returns TRUE if successful. + + Bool createDirectory(AsciiString directory); ///< create a directory of the given name. + + Bool areMusicFilesOnCD(); + void loadMusicFilesFromCD(); + void unloadMusicFilesFromCD(); + + static AsciiString normalizePath(const AsciiString& path); ///< normalizes a file path. The path can refer to a directory. File path must be absolute, but does not need to exist. Returns an empty string on failure. + static Bool isPathInDirectory(const AsciiString& testPath, const AsciiString& basePath); ///< determines if a file path is within a base path. Both paths must be absolute, but do not need to exist. + +protected: +#if ENABLE_FILESYSTEM_EXISTENCE_CACHE + struct FileExistData + { + FileExistData() : instanceExists(0), instanceDoesNotExist(~FileInstance(0)) {} + FileInstance instanceExists; + FileInstance instanceDoesNotExist; + }; + typedef std::hash_map< + rts::string_key, FileExistData, + rts::string_key_hash, + rts::string_key_equal > FileExistMap; + + mutable FileExistMap m_fileExist; + mutable FastCriticalSectionClass m_fileExistMutex; +#endif +}; + +extern FileSystem* TheFileSystem; + + + +//---------------------------------------------------------------------------- +// Inlining +//---------------------------------------------------------------------------- diff --git a/Core/GameEngine/Include/Common/FramePacer.h b/Core/GameEngine/Include/Common/FramePacer.h new file mode 100644 index 00000000000..5bba1a704e8 --- /dev/null +++ b/Core/GameEngine/Include/Common/FramePacer.h @@ -0,0 +1,84 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#pragma once + +#include "Common/FrameRateLimit.h" + + +// TheSuperHackers @todo Use unsigned integers for fps values +// TheSuperHackers @todo Consolidate the GlobalData::m_useFpsLimit and FramePacer::m_enableFpsLimit +// TheSuperHackers @todo Implement new fast forward in here +class FramePacer +{ +public: + + typedef UnsignedInt LogicTimeQueryFlags; + enum LogicTimeQueryFlags_ CPP_11(: LogicTimeQueryFlags) + { + IgnoreFrozenTime = 1<<0, ///< Ignore frozen time for the query + IgnoreHaltedGame = 1<<1, ///< Ignore halted game for the query + }; + + FramePacer(); + ~FramePacer(); + + void update(); ///< Signal that the app/render update is done and wait for the fps limit if applicable. + + void setFramesPerSecondLimit( Int fps ); ///< Set the update fps limit. + Int getFramesPerSecondLimit() const; ///< Get the update fps limit. + void enableFramesPerSecondLimit( Bool enable ); ///< Enable or disable the update fps limit. + Bool isFramesPerSecondLimitEnabled() const; ///< Returns whether the fps limit is enabled here. + Bool isActualFramesPerSecondLimitEnabled() const; ///< Returns whether the fps limit is actually enabled when considering all game settings and setups. + Int getActualFramesPerSecondLimit() const; // Get the actual update fps limit. + + Real getUpdateTime() const; ///< Get the last update delta time in seconds. + Real getUpdateFps() const; ///< Get the last update fps. + Real getBaseOverUpdateFpsRatio(Real minUpdateFps = 5.0f); ///< Get the last engine base over update fps ratio. Used to scale user inputs to a frame rate independent speed. + + void setTimeFrozen(Bool frozen); ///< Set time frozen. Allows scripted camera movement. + void setGameHalted(Bool halted); ///< Set game halted. Does not allow scripted camera movement. + Bool isTimeFrozen() const; + Bool isGameHalted() const; + + void setLogicTimeScaleFps( Int fps ); ///< Set the logic time scale fps and therefore scale the simulation time. Is capped by the max render fps and does not apply to network matches. + Int getLogicTimeScaleFps() const; ///< Get the raw logic time scale fps value. + void enableLogicTimeScale( Bool enable ); ///< Enable or disable the logic time scale setup. If disabled, the simulation time scale is bound to the render frame time or network update time. + Bool isLogicTimeScaleEnabled() const; ///< Check whether the logic time scale setup is enabled. + Int getActualLogicTimeScaleFps(LogicTimeQueryFlags flags = 0) const; ///< Get the real logic time scale fps, depending on the max render fps, network state and enabled state. + Real getActualLogicTimeScaleRatio(LogicTimeQueryFlags flags = 0) const; ///< Get the real logic time scale ratio, depending on the max render fps, network state and enabled state. + Real getActualLogicTimeScaleOverFpsRatio(LogicTimeQueryFlags flags = 0) const; ///< Get the real logic time scale over render fps ratio, used to scale down steps in render updates to match logic updates. + Real getLogicTimeStepSeconds(LogicTimeQueryFlags flags = 0) const; ///< Get the logic time step in seconds + Real getLogicTimeStepMilliseconds(LogicTimeQueryFlags flags = 0) const; ///< Get the logic time step in milliseconds + +protected: + + FrameRateLimit m_frameRateLimit; + + Int m_maxFPS; ///< Maximum frames per second for rendering + Int m_logicTimeScaleFPS; ///< Maximum frames per second for logic time scale + + Real m_updateTime; ///< Last update delta time in seconds + + Bool m_enableFpsLimit; + Bool m_enableLogicTimeScale; + Bool m_isTimeFrozen; + Bool m_isGameHalted; +}; + +extern FramePacer* TheFramePacer; diff --git a/Core/GameEngine/Include/Common/FrameRateLimit.h b/Core/GameEngine/Include/Common/FrameRateLimit.h new file mode 100644 index 00000000000..5bb2b5fd3a7 --- /dev/null +++ b/Core/GameEngine/Include/Common/FrameRateLimit.h @@ -0,0 +1,78 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#pragma once + +#include "Common/GameCommon.h" + + +class FrameRateLimit +{ +public: + FrameRateLimit(); + + Real wait(UnsignedInt maxFps); + +private: + Int64 m_freq; + Int64 m_start; +}; + + +enum FpsValueChange +{ + FpsValueChange_Increase, + FpsValueChange_Decrease, +}; + + +class RenderFpsPreset +{ +public: + enum CPP_11(: UnsignedInt) + { + UncappedFpsValue = 1000000, + }; + + static UnsignedInt getNextFpsValue(UnsignedInt value); + static UnsignedInt getPrevFpsValue(UnsignedInt value); + static UnsignedInt changeFpsValue(UnsignedInt value, FpsValueChange change); + +private: + static const UnsignedInt s_fpsValues[]; +}; + + +class LogicTimeScaleFpsPreset +{ +public: + enum CPP_11(: UnsignedInt) + { +#if RTS_DEBUG + MinFpsValue = 5, +#else + MinFpsValue = LOGICFRAMES_PER_SECOND, +#endif + StepFpsValue = 5, + }; + + static UnsignedInt getNextFpsValue(UnsignedInt value); + static UnsignedInt getPrevFpsValue(UnsignedInt value); + static UnsignedInt changeFpsValue(UnsignedInt value, FpsValueChange change); +}; + diff --git a/Core/GameEngine/Include/Common/GameAudio.h b/Core/GameEngine/Include/Common/GameAudio.h new file mode 100644 index 00000000000..de19bb7fcc2 --- /dev/null +++ b/Core/GameEngine/Include/Common/GameAudio.h @@ -0,0 +1,430 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +//---------------------------------------------------------------------------- +// +// Westwood Studios Pacific. +// +// Confidential Information +// Copyright (C) 2001 - All Rights Reserved +// +//---------------------------------------------------------------------------- +// +// Project: RTS 3 +// +// File name: Common/GameAudio.h +// +// Created: 5/01/01 +// +//---------------------------------------------------------------------------- + +#pragma once + +// Includes +#include "Lib/BaseType.h" +#include "Common/STLTypedefs.h" +#include "Common/SubsystemInterface.h" + + +// Forward Declarations + +class AsciiString; +class AudioEventRTS; +class DebugDisplayInterface; +class Drawable; +class MusicManager; +class Object; +class SoundManager; + + +enum AudioAffect CPP_11(: Int); +enum AudioType CPP_11(: Int); + +struct AudioEventInfo; +struct AudioRequest; +struct AudioSettings; +struct MiscAudio; + +typedef std::hash_map, rts::equal_to > AudioEventInfoHash; +typedef AudioEventInfoHash::iterator AudioEventInfoHashIt; +typedef UnsignedInt AudioHandle; + + +// Defines +enum +{ + PROVIDER_ERROR = 0xFFFFFFFF +}; + +// Class AudioManager +/** + The life of audio. + + When audio is requested to play, it is done so in the following manner: + 1) An AudioEventRTS is created on the stack. + 2) Its guts are copied from elsewhere (for instance, a ThingTemplate, or MiscAudio). + 3) It is added to TheAudio via TheAudio->addAudioEvent(...) + + The return value from addAudioEvent can be saved in case the sound needs to loop and/or be + terminated at some point. + + To reomve a playing sound, the call TheAudio->removeAudioEvent(...) is used. This will search + the list of currently playing audio for the specified handle, and kill the attached sound. It + will play a decay sound, if one is specified. + + The important functions of TheAudio, are therefore + GameAudio::addAudioEvent() + GameAudio::removeAudioEvent() + All other functions exist to support these two basic requirements. + + In addition to the fundamental requirements, the audio has a fairly complicated sound management + scheme. If all units were always allowed to sound off, the sound engine would be overwhelmed and + would sound awful. Therefore, when an audio event is requested, it goes through a series of + checks to determine if it is near enough to the camera, if it should be heard based on shroud, + local player affiliation, etc. (The entire list of checks is contained in shouldPlayLocally()). + + In addition, the world and unit audio are never allowed to exceed their footprint, as specified + in the audio settings INI file. In order to accommodate this, the audio uses an audio cache. The + audio cache will attempt to load a sample, assuming there is enough room. If there is not enough + room, then it goes through and finds any samples that are lower priority, and kills them until + enough room is present for the sample. If it cannot free enough room, nothing happens to the + cache. + + Although the audio is multithreaded, most of the operations are performed such that the worst + case scenario for thread miscommunication is that the main thread misses an event for one frame. + One specific case of this is the status of playing audio. Because audio is playing + asynchronously, it can complete at any time. When most audio completes, it sets a flag on the + event noting that it has completed. During the next update (from the main thread), anything with + that flag set is moved to the stopped list, and then is cleaned up. (Basically, the audio uses + a push model for its multithreadedness, which doesn't require thread safety such as mutexes or + semaphores). + + All in all, the best way to learn how the audio works is to track the lifetime of an event + through the system. This will give a better understanding than all the documentation I could + write. + + -jkmcd + -December 2002 +*/ + +class AudioManager : public SubsystemInterface +{ + public: + AudioManager(); + virtual ~AudioManager(); +#if defined(RTS_DEBUG) + virtual void audioDebugDisplay(DebugDisplayInterface *dd, void *userData, FILE *fp = NULL ) = 0; +#endif + + // From SubsystemInterface + virtual void init(); + virtual void postProcessLoad(); + virtual void reset(); + virtual void update(); + + // device dependent stop, pause and resume + virtual void stopAudio( AudioAffect which ) = 0; + virtual void pauseAudio( AudioAffect which ) = 0; + virtual void resumeAudio( AudioAffect which ) = 0; + virtual void pauseAmbient( Bool shouldPause ) = 0; + + // for focus issues + virtual void loseFocus( void ); + virtual void regainFocus( void ); + + // control for AudioEventsRTS + virtual AudioHandle addAudioEvent( const AudioEventRTS *eventToAdd ); ///< Add an audio event (event must be declared in an INI file) + virtual void removeAudioEvent( AudioHandle audioEvent ); ///< Remove an audio event, stop for instance. + virtual void killAudioEventImmediately( AudioHandle audioEvent ) = 0; + + virtual Bool isValidAudioEvent( const AudioEventRTS *eventToCheck ) const; ///< validate that this piece of audio exists + virtual Bool isValidAudioEvent( AudioEventRTS *eventToCheck ) const; ///< validate that this piece of audio exists + + // add tracks during INIification + void addTrackName( const AsciiString& trackName ); + AsciiString nextTrackName(const AsciiString& currentTrack ); + AsciiString prevTrackName(const AsciiString& currentTrack ); + + // changing music tracks + virtual void nextMusicTrack( void ) = 0; + virtual void prevMusicTrack( void ) = 0; + virtual Bool isMusicPlaying( void ) const = 0; + virtual Bool hasMusicTrackCompleted( const AsciiString& trackName, Int numberOfTimes ) const = 0; + virtual AsciiString getMusicTrackName( void ) const = 0; + + virtual void setAudioEventEnabled( AsciiString eventToAffect, Bool enable ); + virtual void setAudioEventVolumeOverride( AsciiString eventToAffect, Real newVolume ); + virtual void removeAudioEvent( AsciiString eventToRemove ); + virtual void removeDisabledEvents(); + + // Really meant for internal purposes only, but cannot be protected. + virtual void getInfoForAudioEvent( const AudioEventRTS *eventToFindAndFill ) const; // Note: m_eventInfo is Mutable, and so this function will overwrite it if found + + ///< Return whether the current audio is playing or not. + ///< NOTE NOTE NOTE !!DO NOT USE THIS IN FOR GAMELOGIC PURPOSES!! NOTE NOTE NOTE + virtual Bool isCurrentlyPlaying( AudioHandle handle ); + + // Device Dependent open and close functions + virtual void openDevice( void ) = 0; + virtual void closeDevice( void ) = 0; + virtual void *getDevice( void ) = 0; + + // Device Dependent notification functions + virtual void notifyOfAudioCompletion( UnsignedInt audioCompleted, UnsignedInt flags ) = 0; + + // Device Dependent enumerate providers functions. It is okay for there to be only 1 provider (Miles provides a maximum of 64. + virtual UnsignedInt getProviderCount( void ) const = 0; + virtual AsciiString getProviderName( UnsignedInt providerNum ) const = 0; + virtual UnsignedInt getProviderIndex( AsciiString providerName ) const = 0; + virtual void selectProvider( UnsignedInt providerNdx ) = 0; + virtual void unselectProvider( void ) = 0; + virtual UnsignedInt getSelectedProvider( void ) const = 0; + virtual void setSpeakerType( UnsignedInt speakerType ) = 0; + virtual UnsignedInt getSpeakerType( void ) = 0; + + virtual UnsignedInt translateSpeakerTypeToUnsignedInt( const AsciiString& speakerType ); + virtual AsciiString translateUnsignedIntToSpeakerType( UnsignedInt speakerType ); + + // Device Dependent calls to get the number of channels for each type of audio (2-D, 3-D, Streams) + virtual UnsignedInt getNum2DSamples( void ) const = 0; + virtual UnsignedInt getNum3DSamples( void ) const = 0; + virtual UnsignedInt getNumStreams( void ) const = 0; + + // Device Dependent calls to determine sound prioritization info + virtual Bool doesViolateLimit( AudioEventRTS *event ) const = 0; + virtual Bool isPlayingLowerPriority( AudioEventRTS *event ) const = 0; + virtual Bool isPlayingAlready( AudioEventRTS *event ) const = 0; + virtual Bool isObjectPlayingVoice( UnsignedInt objID ) const = 0; + + virtual void adjustVolumeOfPlayingAudio(AsciiString eventName, Real newVolume) = 0; + virtual void removePlayingAudio( AsciiString eventName ) = 0; + virtual void removeAllDisabledAudio() = 0; + + // Is the audio device on? We can skip a lot of audio processing if not. + virtual Bool isOn( AudioAffect whichToGet ) const; + virtual void setOn( Bool turnOn, AudioAffect whichToAffect ); + + // Set and get the device Volume + virtual void setVolume( Real volume, AudioAffect whichToAffect ); + virtual Real getVolume( AudioAffect whichToGet ); + + // To get a more 3-D feeling from the universe, we adjust the volume of the 3-D samples based + // on zoom. + virtual void set3DVolumeAdjustment( Real volumeAdjustment ); + + virtual Bool has3DSensitiveStreamsPlaying( void ) const = 0; + + virtual void *getHandleForBink( void ) = 0; + virtual void releaseHandleForBink( void ) = 0; + + // this function will play an audio event rts by loading it into memory. It should not be used + // by anything except for the load screens. + virtual void friend_forcePlayAudioEventRTS(const AudioEventRTS* eventToPlay) = 0; + + // Update Listener position information + virtual void setListenerPosition( const Coord3D *newListenerPos, const Coord3D *newListenerOrientation ); + virtual const Coord3D *getListenerPosition( void ) const; + + virtual AudioRequest *allocateAudioRequest( Bool useAudioEvent ); + virtual void releaseAudioRequest( AudioRequest *requestToRelease ); + virtual void appendAudioRequest( AudioRequest *m_request ); + virtual void processRequestList( void ); + + virtual AudioEventInfo *newAudioEventInfo( AsciiString newEventName ); + virtual void addAudioEventInfo( AudioEventInfo * newEventInfo ); + virtual AudioEventInfo *findAudioEventInfo( AsciiString eventName ) const; + + const AudioSettings *getAudioSettings( void ) const; + const MiscAudio *getMiscAudio( void ) const; + + // This function should only be called by AudioManager, MusicManager and SoundManager + virtual void releaseAudioEventRTS( AudioEventRTS *&eventToRelease ); + + // For INI + AudioSettings *friend_getAudioSettings( void ); + MiscAudio *friend_getMiscAudio( void ); + const FieldParse *getFieldParseTable( void ) const; + + const AudioEventRTS *getValidSilentAudioEvent() const { return m_silentAudioEvent; } + + virtual void setHardwareAccelerated(Bool accel) { m_hardwareAccel = accel; } + virtual Bool getHardwareAccelerated() { return m_hardwareAccel; } + + virtual void setSpeakerSurround(Bool surround) { m_surroundSpeakers = surround; } + virtual Bool getSpeakerSurround() { return m_surroundSpeakers; } + + virtual void refreshCachedVariables(); + + virtual void setPreferredProvider(AsciiString providerNdx) = 0; + virtual void setPreferredSpeaker(AsciiString speakerType) = 0; + + // For Scripting + virtual Real getAudioLengthMS( const AudioEventRTS *event ); + virtual Real getFileLengthMS( AsciiString strToLoad ) const = 0; + + // For the file cache to know when to remove files. + virtual void closeAnySamplesUsingFile( const void *fileToClose ) = 0; + + virtual Bool isMusicAlreadyLoaded(void) const; + virtual Bool isMusicPlayingFromCD(void) const { return m_musicPlayingFromCD; } + + Bool getDisallowSpeech( void ) const { return m_disallowSpeech; } + void setDisallowSpeech( Bool disallowSpeech ) { m_disallowSpeech = disallowSpeech; } + + // For Worldbuilder, to build lists from which to select + virtual void findAllAudioEventsOfType( AudioType audioType, std::vector& allEvents ); + virtual const AudioEventInfoHash & getAllAudioEvents() const { return m_allAudioEventInfo; } + + Real getZoomVolume() const { return m_zoomVolume; } + protected: + + // Is the currently selected provider actually HW accelerated? + virtual Bool isCurrentProviderHardwareAccelerated(); + + // Is the currently selected speaker type Surround sound? + virtual Bool isCurrentSpeakerTypeSurroundSound(); + + // Should this piece of audio play on the local machine? + virtual Bool shouldPlayLocally(const AudioEventRTS *audioEvent); + + // Set the Listening position for the device + virtual void setDeviceListenerPosition( void ) = 0; + + // For tracking purposes + virtual AudioHandle allocateNewHandle( void ); + + // Remove all AudioEventInfo's with the m_isLevelSpecific flag + virtual void removeLevelSpecificAudioEventInfos( void ); + + void removeAllAudioRequests( void ); + + protected: + AudioSettings *m_audioSettings; + MiscAudio *m_miscAudio; + MusicManager *m_music; + SoundManager *m_sound; + Coord3D m_listenerPosition; + Coord3D m_listenerOrientation; + std::list m_audioRequests; + std::vector m_musicTracks; + + AudioEventInfoHash m_allAudioEventInfo; + AudioHandle theAudioHandlePool; + std::list > m_adjustedVolumes; + + Real m_musicVolume; + Real m_soundVolume; + Real m_sound3DVolume; + Real m_speechVolume; + + Real m_scriptMusicVolume; + Real m_scriptSoundVolume; + Real m_scriptSound3DVolume; + Real m_scriptSpeechVolume; + + Real m_systemMusicVolume; + Real m_systemSoundVolume; + Real m_systemSound3DVolume; + Real m_systemSpeechVolume; + Real m_zoomVolume; + + + AudioEventRTS *m_silentAudioEvent; + + enum + { + VOLUME_TYPE_MUSIC, + VOLUME_TYPE_SOUND, + VOLUME_TYPE_SOUND3D, + VOLUME_TYPE_SPEECH, + NUM_VOLUME_TYPES + }; + Real *m_savedValues; + + // Group of 8 + Bool m_speechOn : 1; + Bool m_soundOn : 1; + Bool m_sound3DOn : 1; + Bool m_musicOn : 1; + Bool m_volumeHasChanged : 1; + Bool m_hardwareAccel : 1; + Bool m_surroundSpeakers : 1; + Bool m_musicPlayingFromCD : 1; + + // Next 8 + Bool m_disallowSpeech : 1; +}; + +// TheSuperHackers @feature helmutbuhler 17/05/2025 +// AudioManager that does nothing. Used for Headless Mode. +class AudioManagerDummy : public AudioManager +{ +#if defined(RTS_DEBUG) + virtual void audioDebugDisplay(DebugDisplayInterface* dd, void* userData, FILE* fp) {} +#endif + virtual void stopAudio(AudioAffect which) {} + virtual void pauseAudio(AudioAffect which) {} + virtual void resumeAudio(AudioAffect which) {} + virtual void pauseAmbient(Bool shouldPause) {} + virtual void killAudioEventImmediately(AudioHandle audioEvent) {} + virtual void nextMusicTrack() {} + virtual void prevMusicTrack() {} + virtual Bool isMusicPlaying() const { return false; } + virtual Bool hasMusicTrackCompleted(const AsciiString& trackName, Int numberOfTimes) const { return false; } + virtual AsciiString getMusicTrackName() const { return ""; } + virtual void openDevice() {} + virtual void closeDevice() {} + virtual void* getDevice() { return NULL; } + virtual void notifyOfAudioCompletion(UnsignedInt audioCompleted, UnsignedInt flags) {} + virtual UnsignedInt getProviderCount(void) const { return 0; }; + virtual AsciiString getProviderName(UnsignedInt providerNum) const { return ""; } + virtual UnsignedInt getProviderIndex(AsciiString providerName) const { return 0; } + virtual void selectProvider(UnsignedInt providerNdx) {} + virtual void unselectProvider(void) {} + virtual UnsignedInt getSelectedProvider(void) const { return 0; } + virtual void setSpeakerType(UnsignedInt speakerType) {} + virtual UnsignedInt getSpeakerType(void) { return 0; } + virtual UnsignedInt getNum2DSamples(void) const { return 0; } + virtual UnsignedInt getNum3DSamples(void) const { return 0; } + virtual UnsignedInt getNumStreams(void) const { return 0; } + virtual Bool doesViolateLimit(AudioEventRTS* event) const { return false; } + virtual Bool isPlayingLowerPriority(AudioEventRTS* event) const { return false; } + virtual Bool isPlayingAlready(AudioEventRTS* event) const { return false; } + virtual Bool isObjectPlayingVoice(UnsignedInt objID) const { return false; } + virtual void adjustVolumeOfPlayingAudio(AsciiString eventName, Real newVolume) {} + virtual void removePlayingAudio(AsciiString eventName) {} + virtual void removeAllDisabledAudio() {} + virtual Bool has3DSensitiveStreamsPlaying(void) const { return false; } + virtual void* getHandleForBink(void) { return NULL; } + virtual void releaseHandleForBink(void) {} + virtual void friend_forcePlayAudioEventRTS(const AudioEventRTS* eventToPlay) {} + virtual void setPreferredProvider(AsciiString providerNdx) {} + virtual void setPreferredSpeaker(AsciiString speakerType) {} + virtual Real getFileLengthMS(AsciiString strToLoad) const { return -1; } + virtual void closeAnySamplesUsingFile(const void* fileToClose) {} + virtual void setDeviceListenerPosition(void) {} +}; + + +extern AudioManager *TheAudio; diff --git a/Core/GameEngine/Include/Common/GameDefines.h b/Core/GameEngine/Include/Common/GameDefines.h index 0cc57e7b299..667ea8aa9ff 100644 --- a/Core/GameEngine/Include/Common/GameDefines.h +++ b/Core/GameEngine/Include/Common/GameDefines.h @@ -18,9 +18,15 @@ #pragma once +#include "WWDefines.h" + // Note: Retail compatibility must not be broken before this project officially does. // Use RETAIL_COMPATIBLE_CRC and RETAIL_COMPATIBLE_XFER_SAVE to guard breaking changes. +#ifndef PRESERVE_RETAIL_BEHAVIOR +#define PRESERVE_RETAIL_BEHAVIOR (1) // Retain behavior present in retail Generals 1.08 and Zero Hour 1.04 +#endif + #ifndef RETAIL_COMPATIBLE_CRC #define RETAIL_COMPATIBLE_CRC (1) // Game is expected to be CRC compatible with retail Generals 1.08, Zero Hour 1.04 #endif @@ -28,3 +34,73 @@ #ifndef RETAIL_COMPATIBLE_XFER_SAVE #define RETAIL_COMPATIBLE_XFER_SAVE (1) // Game is expected to be Xfer Save compatible with retail Generals 1.08, Zero Hour 1.04 #endif + +// This is here to easily toggle between the retail compatible with fixed pathfinding fallback and pure fixed pathfinding mode +#if RETAIL_COMPATIBLE_CRC +#define RETAIL_COMPATIBLE_PATHFINDING (1) +#else +#define RETAIL_COMPATIBLE_PATHFINDING (0) +#endif + +// This is essentially synonymous for RETAIL_COMPATIBLE_CRC. There is a lot wrong with AIGroup, such as use-after-free, double-free, leaks, +// but we cannot touch it much without breaking retail compatibility. Do not shy away from using massive hacks when fixing issues with AIGroup, +// but put them behind this macro. + +#ifndef RETAIL_COMPATIBLE_AIGROUP +#define RETAIL_COMPATIBLE_AIGROUP (1) // AIGroup logic is expected to be CRC compatible with retail Generals 1.08, Zero Hour 1.04 +#endif + +#ifndef ENABLE_GAMETEXT_SUBSTITUTES +#define ENABLE_GAMETEXT_SUBSTITUTES (1) // The code can provide substitute texts when labels and strings are missing in the STR or CSF translation file +#endif + +// Previously the configurable shroud sat behind #if defined(RTS_DEBUG) +// Enable the configurable shroud to properly draw the terrain in World Builder without RTS_DEBUG compiled in. +// Disable the configurable shroud to make shroud hacking a bit less accessible in Release game builds. +#ifndef ENABLE_CONFIGURABLE_SHROUD +#define ENABLE_CONFIGURABLE_SHROUD (1) // When enabled, the GlobalData contains a field to turn on/off the shroud, otherwise shroud is always enabled +#endif + +// Enable buffered IO in File System. Was disabled in retail game. +// Buffered IO generally is much faster than unbuffered for small reads and writes. +#ifndef USE_BUFFERED_IO +#define USE_BUFFERED_IO (1) +#endif + +// Enable cache for local file existence. Reduces amount of disk accesses for better performance, +// but decreases file existence correctness and runtime stability, if a cached file is deleted on runtime. +#ifndef ENABLE_FILESYSTEM_EXISTENCE_CACHE +#define ENABLE_FILESYSTEM_EXISTENCE_CACHE (1) +#endif + +// Enable prioritization of textures by size. This will improve the texture quality of 481 textures in Zero Hour +// by using the larger resolution textures from Generals. Content wise these textures are identical. +#ifndef PRIORITIZE_TEXTURES_BY_SIZE +#define PRIORITIZE_TEXTURES_BY_SIZE (1) +#endif + +// Enable obsolete code. This mainly refers to code that existed in Generals but was removed in GeneralsMD. +// Disable and remove this when Generals and GeneralsMD are merged. +#if RTS_GENERALS +#ifndef USE_OBSOLETE_GENERALS_CODE +#define USE_OBSOLETE_GENERALS_CODE (1) +#endif +#endif + +// Overwrite window settings until wnd data files are adapted or fixed. +#ifndef ENABLE_GUI_HACKS +#define ENABLE_GUI_HACKS (1) +#endif + +// Tell our computer identity in the LAN lobby. Disable for privacy. +// Was enabled in the retail game and exposed the computer login and host names. +#ifdef RTS_DEBUG +#ifndef TELL_COMPUTER_IDENTITY_IN_LAN_LOBBY +#define TELL_COMPUTER_IDENTITY_IN_LAN_LOBBY (1) +#endif +#endif + +#define MIN_DISPLAY_BIT_DEPTH 16 +#define DEFAULT_DISPLAY_BIT_DEPTH 32 +#define DEFAULT_DISPLAY_WIDTH 800 // The standard resolution this game was designed for +#define DEFAULT_DISPLAY_HEIGHT 600 // The standard resolution this game was designed for diff --git a/Core/GameEngine/Include/Common/GameMemory.h b/Core/GameEngine/Include/Common/GameMemory.h new file mode 100644 index 00000000000..1ea96161cea --- /dev/null +++ b/Core/GameEngine/Include/Common/GameMemory.h @@ -0,0 +1,912 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: Memory.h +//----------------------------------------------------------------------------- +// +// Westwood Studios Pacific. +// +// Confidential Information +// Copyright (C); 2001 - All Rights Reserved +// +//----------------------------------------------------------------------------- +// +// Project: RTS3 +// +// File name: Memory.h +// +// Created: Steven Johnson, August 2001 +// +// Desc: Memory manager +// +//----------------------------------------------------------------------------- +/////////////////////////////////////////////////////////////////////////////// + +#pragma once + +// Turn off memory pool checkpointing for now. +#ifndef DISABLE_MEMORYPOOL_CHECKPOINTING + #define DISABLE_MEMORYPOOL_CHECKPOINTING 1 +#endif + +#if defined(RTS_DEBUG) && !defined(MEMORYPOOL_DEBUG_CUSTOM_NEW) && !defined(DISABLE_MEMORYPOOL_DEBUG_CUSTOM_NEW) + #define MEMORYPOOL_DEBUG_CUSTOM_NEW +#endif + +//#if defined(RTS_DEBUG) && !defined(MEMORYPOOL_DEBUG) && !defined(DISABLE_MEMORYPOOL_DEBUG) +#if defined(RTS_DEBUG) && !defined(MEMORYPOOL_DEBUG) && !defined(DISABLE_MEMORYPOOL_DEBUG) + #define MEMORYPOOL_DEBUG +#endif + +// SYSTEM INCLUDES //////////////////////////////////////////////////////////// + +#include +#include +#ifdef MEMORYPOOL_OVERRIDE_MALLOC + #include +#endif + +// USER INCLUDES ////////////////////////////////////////////////////////////// + +#include "Lib/BaseType.h" +#include "Common/Debug.h" +#include "Common/Errors.h" + +// MACROS ////////////////////////////////////////////////////////////////// + +#ifdef MEMORYPOOL_DEBUG + + // by default, enable free-block-retention for checkpointing in debug mode + #if !defined(DISABLE_MEMORYPOOL_CHECKPOINTING) || DISABLE_MEMORYPOOL_CHECKPOINTING == 0 + #define MEMORYPOOL_CHECKPOINTING + #endif + + // by default, enable bounding walls in debug mode (unless we have specifically disabled them) + #ifndef DISABLE_MEMORYPOOL_BOUNDINGWALL + #define MEMORYPOOL_BOUNDINGWALL + #endif + + #if !defined(MEMORYPOOL_STACKTRACE) && !defined(DISABLE_MEMORYPOOL_STACKTRACE) + #define MEMORYPOOL_STACKTRACE + #endif + + // flags for the memory-report options. + enum + { + +#ifdef MEMORYPOOL_CHECKPOINTING + // ------------------------------------------------------ + // you usually won't use the _REPORT bits directly; see below for more convenient combinations. + + // you must set at least one of the 'allocate' bits. + _REPORT_CP_ALLOCATED_BEFORE = 0x0001, + _REPORT_CP_ALLOCATED_BETWEEN = 0x0002, + _REPORT_CP_ALLOCATED_DONTCARE = (_REPORT_CP_ALLOCATED_BEFORE|_REPORT_CP_ALLOCATED_BETWEEN), + + // you must set at least one of the 'freed' bits. + _REPORT_CP_FREED_BEFORE = 0x0010, + _REPORT_CP_FREED_BETWEEN = 0x0020, + _REPORT_CP_FREED_NEVER = 0x0040, // ie, still in existence + _REPORT_CP_FREED_DONTCARE = (_REPORT_CP_FREED_BEFORE|_REPORT_CP_FREED_BETWEEN|_REPORT_CP_FREED_NEVER), + // ------------------------------------------------------ +#endif // MEMORYPOOL_CHECKPOINTING + +#ifdef MEMORYPOOL_CHECKPOINTING + /** display the stacktrace for allocation location for all blocks found. + this bit may be mixed-n-matched with any other flag. + */ + REPORT_CP_STACKTRACE = 0x0100, +#endif + + /** display stats for each pool, in addition to each block. + (this is useful for finding suitable allocation counts for the pools.) + this bit may be mixed-n-matched with any other flag. + */ + REPORT_POOLINFO = 0x0200, + + /** report on the overall memory situation (including all pools and dma's). + this bit may be mixed-n-matched with any other flag. + */ + REPORT_FACTORYINFO = 0x0400, + + /** report on pools that have overflowed their initial allocation. + this bit may be mixed-n-matched with any other flag. + */ + REPORT_POOL_OVERFLOW = 0x0800, + + /** simple-n-cheap leak checking */ + REPORT_SIMPLE_LEAKS = 0x1000, + +#ifdef MEMORYPOOL_CHECKPOINTING + /** report on blocks that were allocated between the checkpoints. + (don't care if they were freed or not.) + */ + REPORT_CP_ALLOCATES = (_REPORT_CP_ALLOCATED_BETWEEN | _REPORT_CP_FREED_DONTCARE), + + /** report on blocks that were freed between the checkpoints. + (don't care when they were allocated.) + */ + REPORT_CP_FREES = (_REPORT_CP_ALLOCATED_DONTCARE | _REPORT_CP_FREED_BETWEEN), + + /** report on blocks that were allocated between the checkpoints, and still exist + (note that this reports *potential* leaks -- some such blocks may be desired) + */ + REPORT_CP_LEAKS = (_REPORT_CP_ALLOCATED_BETWEEN | _REPORT_CP_FREED_NEVER), + + /** report on blocks that existed before checkpoint #1 and still exist now. + */ + REPORT_CP_LONGTERM = (_REPORT_CP_ALLOCATED_BEFORE | _REPORT_CP_FREED_NEVER), + + /** report on blocks that were allocated-and-freed between the checkpoints. + */ + REPORT_CP_TRANSIENT = (_REPORT_CP_ALLOCATED_BETWEEN | _REPORT_CP_FREED_BETWEEN), + + /** report on all blocks that currently exist + */ + REPORT_CP_EXISTING = (_REPORT_CP_ALLOCATED_BEFORE | _REPORT_CP_ALLOCATED_BETWEEN | _REPORT_CP_FREED_NEVER), + + /** report on all blocks that have ever existed (!) (or at least, since the last call + to debugResetCheckpoints) + */ + REPORT_CP_ALL = (_REPORT_CP_ALLOCATED_DONTCARE | _REPORT_CP_FREED_DONTCARE) +#endif // MEMORYPOOL_CHECKPOINTING + + }; + +#endif // MEMORYPOOL_DEBUG + +// TheSuperHackers @build xezon 30/03/2025 Define DISABLE_GAMEMEMORY to use a null implementations for Game Memory. +// Useful for address sanitizer checks and other investigations. +// Is included below the macros so that memory pool debug code can still be used. +#ifdef DISABLE_GAMEMEMORY +#include "GameMemoryNull.h" +#else + +#ifdef MEMORYPOOL_DEBUG + + #define DECLARE_LITERALSTRING_ARG1 const char * debugLiteralTagString + #define PASS_LITERALSTRING_ARG1 debugLiteralTagString + #define DECLARE_LITERALSTRING_ARG2 , const char * debugLiteralTagString + #define PASS_LITERALSTRING_ARG2 , debugLiteralTagString + + #define MP_LOC_SUFFIX /*" [" DEBUG_FILENLINE "]"*/ + + #define allocateBlock(ARGLITERAL) allocateBlockImplementation(ARGLITERAL MP_LOC_SUFFIX) + #define allocateBlockDoNotZero(ARGLITERAL) allocateBlockDoNotZeroImplementation(ARGLITERAL MP_LOC_SUFFIX) + #define allocateBytes(ARGCOUNT,ARGLITERAL) allocateBytesImplementation(ARGCOUNT, ARGLITERAL MP_LOC_SUFFIX) + #define allocateBytesDoNotZero(ARGCOUNT,ARGLITERAL) allocateBytesDoNotZeroImplementation(ARGCOUNT, ARGLITERAL MP_LOC_SUFFIX) + #define newInstanceDesc(ARGCLASS,ARGLITERAL) new(ARGCLASS::ARGCLASS##_GLUE_NOT_IMPLEMENTED, ARGLITERAL MP_LOC_SUFFIX) ARGCLASS + #define newInstance(ARGCLASS) new(ARGCLASS::ARGCLASS##_GLUE_NOT_IMPLEMENTED, __FILE__) ARGCLASS + +#else + + #define DECLARE_LITERALSTRING_ARG1 + #define PASS_LITERALSTRING_ARG1 + #define DECLARE_LITERALSTRING_ARG2 + #define PASS_LITERALSTRING_ARG2 + + #define allocateBlock(ARGLITERAL) allocateBlockImplementation() + #define allocateBlockDoNotZero(ARGLITERAL) allocateBlockDoNotZeroImplementation() + #define allocateBytes(ARGCOUNT,ARGLITERAL) allocateBytesImplementation(ARGCOUNT) + #define allocateBytesDoNotZero(ARGCOUNT,ARGLITERAL) allocateBytesDoNotZeroImplementation(ARGCOUNT) + #define newInstanceDesc(ARGCLASS,ARGLITERAL) new(ARGCLASS::ARGCLASS##_GLUE_NOT_IMPLEMENTED) ARGCLASS + #define newInstance(ARGCLASS) new(ARGCLASS::ARGCLASS##_GLUE_NOT_IMPLEMENTED) ARGCLASS + +#endif + +// FORWARD REFERENCES ///////////////////////////////////////////////////////// + +class MemoryPoolSingleBlock; +class MemoryPoolBlob; +class MemoryPool; +class MemoryPoolFactory; +class DynamicMemoryAllocator; +class BlockCheckpointInfo; + +// TYPE DEFINES /////////////////////////////////////////////////////////////// + +// ---------------------------------------------------------------------------- +/** + This class is purely a convenience used to pass optional arguments to initMemoryManager(), + and by extension, to createDynamicMemoryAllocator(). You can specify how many sub-pools you + want, what size each is, what the allocation counts are to be, etc. Most apps will + construct an array of these to pass to initMemoryManager() and never use it elsewhere. +*/ +struct PoolInitRec +{ + const char *poolName; ///< name of the pool; by convention, "dmaPool_XXX" where XXX is allocationSize + Int allocationSize; ///< size, in bytes, of the pool. + Int initialAllocationCount; ///< initial number of blocks to allocate. + Int overflowAllocationCount; ///< when the pool runs out of space, allocate more blocks in this increment +}; + +enum +{ + MAX_DYNAMICMEMORYALLOCATOR_SUBPOOLS = 8 ///< The max number of subpools allowed in a DynamicMemoryAllocator +}; + +#ifdef MEMORYPOOL_CHECKPOINTING +// ---------------------------------------------------------------------------- +/** + This class exists purely for coding convenience, and should never be used by external code. + It simply allows MemoryPool and DynamicMemoryAllocator to share checkpoint-related + code in a seamless way. +*/ +class Checkpointable +{ +private: + BlockCheckpointInfo *m_firstCheckpointInfo; ///< head of the linked list of checkpoint infos for this pool/dma + Bool m_cpiEverFailed; ///< flag to detect if we ran out of memory accumulating checkpoint info. + +protected: + + Checkpointable(); + ~Checkpointable(); + + /// create a new checkpoint info and add it to the list. + BlockCheckpointInfo *debugAddCheckpointInfo( + const char *debugLiteralTagString, + Int allocCheckpoint, + Int blockSize + ); + +public: + /// dump a checkpoint report to logfile + void debugCheckpointReport(Int flags, Int startCheckpoint, Int endCheckpoint, const char *poolName); + /// reset all the checkpoints for this pool/dma + void debugResetCheckpoints(); +}; +#endif + +// ---------------------------------------------------------------------------- +/** + A MemoryPool provides a way to efficiently allocate objects of the same (or similar) + size. We allocate large a large chunk of memory (a "blob") and subdivide it into + even-size chunks, doling these out as needed. If the first blob gets full, we allocate + additional blobs as necessary. A given pool can allocate blocks of only one size; + if you need a different size, you should use a different pool. +*/ +class MemoryPool +#ifdef MEMORYPOOL_CHECKPOINTING + : public Checkpointable +#endif +{ +private: + + MemoryPoolFactory *m_factory; ///< the factory that created us + MemoryPool *m_nextPoolInFactory; ///< linked list node, managed by factory + const char *m_poolName; ///< name of this pool. (literal string; must not be freed) + Int m_allocationSize; ///< size of the blocks allocated by this pool, in bytes + Int m_initialAllocationCount; ///< number of blocks to be allocated in initial blob + Int m_overflowAllocationCount; ///< number of blocks to be allocated in any subsequent blob(s) + Int m_usedBlocksInPool; ///< total number of blocks in use in the pool. + Int m_totalBlocksInPool; ///< total number of blocks in all blobs of this pool (used or not). + Int m_peakUsedBlocksInPool; ///< high-water mark of m_usedBlocksInPool + MemoryPoolBlob *m_firstBlob; ///< head of linked list: first blob for this pool. + MemoryPoolBlob *m_lastBlob; ///< tail of linked list: last blob for this pool. (needed for efficiency) + MemoryPoolBlob *m_firstBlobWithFreeBlocks; ///< first blob in this pool that has at least one unallocated block. + +private: + /// create a new blob with the given number of blocks. + MemoryPoolBlob* createBlob(Int allocationCount); + + /// destroy a blob. + Int freeBlob(MemoryPoolBlob *blob); + +public: + + // 'public' funcs that are really only for use by MemoryPoolFactory + MemoryPool *getNextPoolInList(); ///< return next pool in linked list + void addToList(MemoryPool **pHead); ///< add this pool to head of the linked list + void removeFromList(MemoryPool **pHead); ///< remove this pool from the linked list + #ifdef MEMORYPOOL_DEBUG + static void debugPoolInfoReport( MemoryPool *pool, FILE *fp = NULL ); ///< dump a report about this pool to the logfile + const char *debugGetBlockTagString(void *pBlock); ///< return the tagstring for the given block (assumed to belong to this pool) + void debugMemoryVerifyPool(); ///< perform internal consistency check on this pool. + Int debugPoolReportLeaks( const char* owner ); + #endif + #ifdef MEMORYPOOL_CHECKPOINTING + void debugResetCheckpoints(); ///< throw away all checkpoint information for this pool. + #endif + +public: + + MemoryPool(); + + /// initialize the given memory pool. + void init(MemoryPoolFactory *factory, const char *poolName, Int allocationSize, Int initialAllocationCount, Int overflowAllocationCount); + + ~MemoryPool(); + + /// allocate a block from this pool. (don't call directly; use allocateBlock() macro) + void *allocateBlockImplementation(DECLARE_LITERALSTRING_ARG1); + + /// same as allocateBlockImplementation, but memory returned is not zeroed + void *allocateBlockDoNotZeroImplementation(DECLARE_LITERALSTRING_ARG1); + + /// free the block. it is OK to pass null. + void freeBlock(void *pMem); + + /// return the factory that created (and thus owns) this pool. + MemoryPoolFactory *getOwningFactory(); + + /// return the name of this pool. the result is a literal string and must not be freed. + const char *getPoolName(); + + /// return the block allocation size of this pool. + Int getAllocationSize(); + + /// return the number of free (available) blocks in this pool. + Int getFreeBlockCount(); + + /// return the number of blocks in use in this pool. + Int getUsedBlockCount(); + + /// return the total number of blocks in this pool. [ == getFreeBlockCount() + getUsedBlockCount() ] + Int getTotalBlockCount(); + + /// return the high-water mark for getUsedBlockCount() + Int getPeakBlockCount(); + + /// return the initial allocation count for this pool + Int getInitialBlockCount(); + + Int countBlobsInPool(); + + /// if this pool has any empty blobs, return them to the system. + Int releaseEmpties(); + + /// destroy all blocks and blobs in this pool. + void reset(); + + #ifdef MEMORYPOOL_DEBUG + /// return true iff this block was allocated by this pool. + Bool debugIsBlockInPool(void *pBlock); + #endif +}; + +// ---------------------------------------------------------------------------- +/** + The DynamicMemoryAllocator class is used to handle unpredictably-sized + allocation requests. It basically allocates a number of (private) MemoryPools, + then routes request to the smallest-size pool that will satisfy the request. + (Requests too large for any of the pool are routed to the system memory allocator.) + You should normally use this in place of malloc/free or (global) new/delete. +*/ +class DynamicMemoryAllocator +#ifdef MEMORYPOOL_CHECKPOINTING + : public Checkpointable +#endif +{ +private: + MemoryPoolFactory *m_factory; ///< the factory that created us + DynamicMemoryAllocator *m_nextDmaInFactory; ///< linked list node, managed by factory + Int m_numPools; ///< number of subpools (up to MAX_DYNAMICMEMORYALLOCATOR_SUBPOOLS) + Int m_usedBlocksInDma; ///< total number of blocks allocated, from subpools and "raw" + MemoryPool *m_pools[MAX_DYNAMICMEMORYALLOCATOR_SUBPOOLS]; ///< the subpools + MemoryPoolSingleBlock *m_rawBlocks; ///< linked list of "raw" blocks allocated directly from system + + /// return the best pool for the given allocSize, or null if none are suitable + MemoryPool *findPoolForSize(Int allocSize); + +public: + + // 'public' funcs that are really only for use by MemoryPoolFactory + + DynamicMemoryAllocator *getNextDmaInList(); ///< return next dma in linked list + void addToList(DynamicMemoryAllocator **pHead); ///< add this dma to the list + void removeFromList(DynamicMemoryAllocator **pHead); ///< remove this dma from the list + #ifdef MEMORYPOOL_DEBUG + Int debugCalcRawBlockBytes(Int *numBlocks); ///< calculate the number of bytes in "raw" (non-subpool) blocks + void debugMemoryVerifyDma(); ///< perform internal consistency check + const char *debugGetBlockTagString(void *pBlock); ///< return the tagstring for the given block (assumed to belong to this dma) + void debugDmaInfoReport( FILE *fp = NULL ); ///< dump a report about this pool to the logfile + Int debugDmaReportLeaks(); + #endif + #ifdef MEMORYPOOL_CHECKPOINTING + void debugResetCheckpoints(); ///< toss all checkpoint information + #endif + +public: + + DynamicMemoryAllocator(); + + /// initialize the dma. pass 0/null for numSubPool/parms to get some reasonable default subpools. + void init(MemoryPoolFactory *factory, Int numSubPools, const PoolInitRec pParms[]); + + ~DynamicMemoryAllocator(); + + /// allocate bytes from this pool. (don't call directly; use allocateBytes() macro) + void *allocateBytesImplementation(Int numBytes DECLARE_LITERALSTRING_ARG2); + + /// like allocateBytesImplementation, but zeroes the memory before returning + void *allocateBytesDoNotZeroImplementation(Int numBytes DECLARE_LITERALSTRING_ARG2); + +#ifdef MEMORYPOOL_DEBUG + void debugIgnoreLeaksForThisBlock(void* pBlockPtr); +#endif + + /// free the bytes. (assumes allocated by this dma.) + void freeBytes(void* pMem); + + /** + return the actual number of bytes that would be allocated + if you tried to allocate the given size. (It will generally be slightly + larger than you request.) This lets you use extra space if you're gonna get it anyway... + The idea is that you will call this before doing a memory allocation, to see if + you got any extra "bonus" space. + */ + Int getActualAllocationSize(Int numBytes); + + /// destroy all allocations performed by this DMA. + void reset(); + + Int getDmaMemoryPoolCount() const { return m_numPools; } + MemoryPool* getNthDmaMemoryPool(Int i) const { return m_pools[i]; } + + #ifdef MEMORYPOOL_DEBUG + + /// return true iff this block was allocated by this dma + Bool debugIsBlockInDma(void *pBlock); + + /// return true iff the pool is a subpool of this dma + Bool debugIsPoolInDma(MemoryPool *pool); + + #endif // MEMORYPOOL_DEBUG +}; + +// ---------------------------------------------------------------------------- +#ifdef MEMORYPOOL_DEBUG +enum { MAX_SPECIAL_USED = 256 }; +#endif + +// ---------------------------------------------------------------------------- +/** + The class that manages all the MemoryPools and DynamicMemoryAllocators. + Usually you will create exactly one of these (TheMemoryPoolFactory) + and use it for everything. +*/ +class MemoryPoolFactory +{ +private: + MemoryPool *m_firstPoolInFactory; ///< linked list of pools + DynamicMemoryAllocator *m_firstDmaInFactory; ///< linked list of dmas +#ifdef MEMORYPOOL_CHECKPOINTING + Int m_curCheckpoint; ///< most recent checkpoint value +#endif +#ifdef MEMORYPOOL_DEBUG + Int m_usedBytes; ///< total bytes in use + Int m_physBytes; ///< total bytes allocated to all pools (includes unused blocks) + Int m_peakUsedBytes; ///< high-water mark of m_usedBytes + Int m_peakPhysBytes; ///< high-water mark of m_physBytes + Int m_usedBytesSpecial[MAX_SPECIAL_USED]; + Int m_usedBytesSpecialPeak[MAX_SPECIAL_USED]; + Int m_physBytesSpecial[MAX_SPECIAL_USED]; + Int m_physBytesSpecialPeak[MAX_SPECIAL_USED]; +#endif + +public: + + // 'public' funcs that are really only for use by MemoryPool and friends + #ifdef MEMORYPOOL_DEBUG + /// adjust the usedBytes and physBytes variables by the given amoun ts. + void adjustTotals(const char* tagString, Int usedDelta, Int physDelta); + #endif + #ifdef MEMORYPOOL_CHECKPOINTING + /// return the current checkpoint value. + Int getCurCheckpoint() { return m_curCheckpoint; } + #endif + +public: + + MemoryPoolFactory(); + void init(); + ~MemoryPoolFactory(); + + /// create a new memory pool with the given settings. if a pool with the given name already exists, return it. + MemoryPool *createMemoryPool(const PoolInitRec *parms); + + /// overloaded version of createMemoryPool with explicit parms. + MemoryPool *createMemoryPool(const char *poolName, Int allocationSize, Int initialAllocationCount, Int overflowAllocationCount); + + /// return the pool with the given name. if no such pool exists, return null. + MemoryPool *findMemoryPool(const char *poolName); + + /// destroy the given pool. + void destroyMemoryPool(MemoryPool *pMemoryPool); + + /// create a DynamicMemoryAllocator with subpools with the given parms. + DynamicMemoryAllocator *createDynamicMemoryAllocator(Int numSubPools, const PoolInitRec pParms[]); + + /// destroy the given DynamicMemoryAllocator. + void destroyDynamicMemoryAllocator(DynamicMemoryAllocator *dma); + + /// destroy the contents of all pools and dmas. (the pools and dma's are not destroyed, just reset) + void reset(); + + void memoryPoolUsageReport( const char* filename, FILE *appendToFileInstead = NULL ); + + #ifdef MEMORYPOOL_DEBUG + + /// perform internal consistency checking + void debugMemoryVerify(); + + /// return true iff the block was allocated by any pool or dma owned by this factory. + Bool debugIsBlockInAnyPool(void *pBlock); + + /// return the tag string for the block. + const char *debugGetBlockTagString(void *pBlock); + + /// dump a report with the given options to the logfile. + void debugMemoryReport(Int flags, Int startCheckpoint, Int endCheckpoint, FILE *fp = NULL ); + + void debugSetInitFillerIndex(Int index); + + #endif + #ifdef MEMORYPOOL_CHECKPOINTING + + /// set a new checkpoint. + Int debugSetCheckpoint(); + + /// reset all checkpoint information. + void debugResetCheckpoints(); + + #endif +}; + +// how many bytes are we allowed to 'waste' per pool allocation before the debug code starts yelling at us... +#define MEMORY_POOL_OBJECT_ALLOCATION_SLOP 16 + +// ---------------------------------------------------------------------------- +#define GCMP_FIND(ARGCLASS, ARGPOOLNAME) \ +private: \ + static MemoryPool *getClassMemoryPool() \ + { \ + /* \ + Note that this static variable will be initialized exactly once: the first time \ + control flows over this section of code. This allows us to neatly resolve the \ + order-of-execution problem for static variables, ensuring this is not executed \ + prior to the initialization of TheMemoryPoolFactory. \ + */ \ + DEBUG_ASSERTCRASH(TheMemoryPoolFactory, ("TheMemoryPoolFactory is NULL")); \ + static MemoryPool *The##ARGCLASS##Pool = TheMemoryPoolFactory->findMemoryPool(ARGPOOLNAME); \ + DEBUG_ASSERTCRASH(The##ARGCLASS##Pool, ("Pool \"%s\" not found (did you set it up in initMemoryPools?)", ARGPOOLNAME)); \ + DEBUG_ASSERTCRASH(The##ARGCLASS##Pool->getAllocationSize() >= sizeof(ARGCLASS), ("Pool \"%s\" is too small for this class (currently %d, need %d)", ARGPOOLNAME, The##ARGCLASS##Pool->getAllocationSize(), sizeof(ARGCLASS))); \ + DEBUG_ASSERTCRASH(The##ARGCLASS##Pool->getAllocationSize() <= sizeof(ARGCLASS)+MEMORY_POOL_OBJECT_ALLOCATION_SLOP, ("Pool \"%s\" is too large for this class (currently %d, need %d)", ARGPOOLNAME, The##ARGCLASS##Pool->getAllocationSize(), sizeof(ARGCLASS))); \ + return The##ARGCLASS##Pool; \ + } + +// ---------------------------------------------------------------------------- +#define GCMP_CREATE(ARGCLASS, ARGPOOLNAME, ARGINITIAL, ARGOVERFLOW) \ +private: \ + static MemoryPool *getClassMemoryPool() \ + { \ + /* \ + Note that this static variable will be initialized exactly once: the first time \ + control flows over this section of code. This allows us to neatly resolve the \ + order-of-execution problem for static variables, ensuring this is not executed \ + prior to the initialization of TheMemoryPoolFactory. \ + */ \ + DEBUG_ASSERTCRASH(TheMemoryPoolFactory, ("TheMemoryPoolFactory is NULL")); \ + static MemoryPool *The##ARGCLASS##Pool = TheMemoryPoolFactory->createMemoryPool(ARGPOOLNAME, sizeof(ARGCLASS), ARGINITIAL, ARGOVERFLOW); \ + DEBUG_ASSERTCRASH(The##ARGCLASS##Pool, ("Pool \"%s\" not found (did you set it up in initMemoryPools?)", ARGPOOLNAME)); \ + DEBUG_ASSERTCRASH(The##ARGCLASS##Pool->getAllocationSize() >= sizeof(ARGCLASS), ("Pool \"%s\" is too small for this class (currently %d, need %d)", ARGPOOLNAME, The##ARGCLASS##Pool->getAllocationSize(), sizeof(ARGCLASS))); \ + DEBUG_ASSERTCRASH(The##ARGCLASS##Pool->getAllocationSize() <= sizeof(ARGCLASS)+MEMORY_POOL_OBJECT_ALLOCATION_SLOP, ("Pool \"%s\" is too large for this class (currently %d, need %d)", ARGPOOLNAME, The##ARGCLASS##Pool->getAllocationSize(), sizeof(ARGCLASS))); \ + return The##ARGCLASS##Pool; \ + } + +// ---------------------------------------------------------------------------- +#define MEMORY_POOL_GLUE_WITHOUT_GCMP(ARGCLASS) \ +protected: \ + virtual ~ARGCLASS(); \ +public: \ + enum ARGCLASS##MagicEnum { ARGCLASS##_GLUE_NOT_IMPLEMENTED = 0 }; \ +public: \ + inline void *operator new(size_t s, ARGCLASS##MagicEnum e DECLARE_LITERALSTRING_ARG2) \ + { \ + DEBUG_ASSERTCRASH(s == sizeof(ARGCLASS), ("The wrong operator new is being called; ensure all objects in the hierarchy have MemoryPoolGlue set up correctly")); \ + return ARGCLASS::getClassMemoryPool()->allocateBlockImplementation(PASS_LITERALSTRING_ARG1); \ + } \ +public: \ + /* \ + Note that this delete operator can't be called directly; it is called \ + only if the analogous new operator is called, AND the constructor \ + throws an exception... \ + */ \ + inline void operator delete(void *p, ARGCLASS##MagicEnum e DECLARE_LITERALSTRING_ARG2) \ + { \ + ARGCLASS::getClassMemoryPool()->freeBlock(p); \ + } \ +protected: \ + /* \ + Make normal new and delete protected, so they can't be called by the outside world. \ + Note that delete is funny, in that it can still be called by the class itself; \ + this is safe but not recommended, for consistency purposes. More problematically, \ + it can be called by another class that has declared itself 'friend' to us. \ + In theory, this shouldn't work, since it may not use the right operator-delete, \ + and thus the wrong memory pool; in practice, it seems the right delete IS called \ + in MSVC -- it seems to make operator delete virtual if the destructor is also virtual. \ + At any rate, this is undocumented behavior as far as I can tell, so we put a big old \ + crash into operator delete telling people to do the right thing and call deleteInstance \ + instead -- it'd be nice if we could catch this at compile time, but catching it at \ + runtime seems to be the best we can do... \ + */ \ + inline void *operator new(size_t s) \ + { \ + DEBUG_CRASH(("This operator new should normally never be called... please use new(char*) instead.")); \ + DEBUG_ASSERTCRASH(s == sizeof(ARGCLASS), ("The wrong operator new is being called; ensure all objects in the hierarchy have MemoryPoolGlue set up correctly")); \ + throw ERROR_BUG; \ + return 0; \ + } \ + inline void operator delete(void *p) \ + { \ + DEBUG_CRASH(("Please call deleteInstance instead of delete.")); \ + ARGCLASS::getClassMemoryPool()->freeBlock(p); \ + } \ +private: \ + virtual MemoryPool *getObjectMemoryPool() \ + { \ + return ARGCLASS::getClassMemoryPool(); \ + } \ +public: /* include this line at the end to reset visibility to 'public' */ + +// ---------------------------------------------------------------------------- +#define MEMORY_POOL_GLUE(ARGCLASS, ARGPOOLNAME) \ + MEMORY_POOL_GLUE_WITHOUT_GCMP(ARGCLASS) \ + GCMP_FIND(ARGCLASS, ARGPOOLNAME) + +// ---------------------------------------------------------------------------- +#define MEMORY_POOL_GLUE_WITH_EXPLICIT_CREATE(ARGCLASS, ARGPOOLNAME, ARGINITIAL, ARGOVERFLOW) \ + MEMORY_POOL_GLUE_WITHOUT_GCMP(ARGCLASS) \ + GCMP_CREATE(ARGCLASS, ARGPOOLNAME, ARGINITIAL, ARGOVERFLOW) + +// ---------------------------------------------------------------------------- +#define MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(ARGCLASS, ARGPOOLNAME) \ + MEMORY_POOL_GLUE_WITHOUT_GCMP(ARGCLASS) \ + GCMP_CREATE(ARGCLASS, ARGPOOLNAME, -1, -1) + +// ---------------------------------------------------------------------------- +// this is the version for an Abstract Base Class, which will never be instantiated... +#define MEMORY_POOL_GLUE_ABC(ARGCLASS) \ +protected: \ + virtual ~ARGCLASS(); \ +public: \ + enum ARGCLASS##MagicEnum { ARGCLASS##_GLUE_NOT_IMPLEMENTED = 0 }; \ +protected: \ + inline void *operator new(size_t s, ARGCLASS##MagicEnum e DECLARE_LITERALSTRING_ARG2) \ + { \ + DEBUG_CRASH(("this should be impossible to call (abstract base class)")); \ + DEBUG_ASSERTCRASH(s == sizeof(ARGCLASS), ("The wrong operator new is being called; ensure all objects in the hierarchy have MemoryPoolGlue set up correctly")); \ + throw ERROR_BUG; \ + return 0; \ + } \ +protected: \ + inline void operator delete(void *p, ARGCLASS##MagicEnum e DECLARE_LITERALSTRING_ARG2) \ + { \ + DEBUG_CRASH(("this should be impossible to call (abstract base class)")); \ + } \ +protected: \ + inline void *operator new(size_t s) \ + { \ + DEBUG_CRASH(("this should be impossible to call (abstract base class)")); \ + DEBUG_ASSERTCRASH(s == sizeof(ARGCLASS), ("The wrong operator new is being called; ensure all objects in the hierarchy have MemoryPoolGlue set up correctly")); \ + throw ERROR_BUG; \ + return 0; \ + } \ + inline void operator delete(void *p) \ + { \ + DEBUG_CRASH(("this should be impossible to call (abstract base class)")); \ + } \ +private: \ + virtual MemoryPool *getObjectMemoryPool() \ + { \ + throw ERROR_BUG; \ + return 0; \ + } \ +public: /* include this line at the end to reset visibility to 'public' */ + + +// ---------------------------------------------------------------------------- +/** + This class is provided as a simple and safe way to integrate C++ object allocation + into MemoryPool usage. To use it, you must have your class inherit from + MemoryPoolObject, then put the macro MEMORY_POOL_GLUE(MyClassName, "MyPoolName") + at the start of your class definition. (This does not create the pool itself -- you + must create that manually using MemoryPoolFactory::createMemoryPool) +*/ +class MemoryPoolObject +{ +protected: + + /** ensure that all destructors are virtual */ + virtual ~MemoryPoolObject() { } + +protected: + void *operator new(size_t s) { DEBUG_CRASH(("This should be impossible")); return 0; } + void operator delete(void *p) { DEBUG_CRASH(("This should be impossible")); } + +protected: + + virtual MemoryPool *getObjectMemoryPool() = 0; + +public: + + static void deleteInstanceInternal(MemoryPoolObject* mpo) + { + if (mpo) + { + MemoryPool *pool = mpo->getObjectMemoryPool(); // save this, since the dtor will nuke our vtbl + mpo->~MemoryPoolObject(); // it's virtual, so the right one will be called. + pool->freeBlock((void *)mpo); + } + } +}; + +inline void deleteInstance(MemoryPoolObject* mpo) +{ + MemoryPoolObject::deleteInstanceInternal(mpo); +} + + +// INLINING /////////////////////////////////////////////////////////////////// + +// ---------------------------------------------------------------------------- +inline MemoryPoolFactory *MemoryPool::getOwningFactory() { return m_factory; } +inline MemoryPool *MemoryPool::getNextPoolInList() { return m_nextPoolInFactory; } +inline const char *MemoryPool::getPoolName() { return m_poolName; } +inline Int MemoryPool::getAllocationSize() { return m_allocationSize; } +inline Int MemoryPool::getFreeBlockCount() { return getTotalBlockCount() - getUsedBlockCount(); } +inline Int MemoryPool::getUsedBlockCount() { return m_usedBlocksInPool; } +inline Int MemoryPool::getTotalBlockCount() { return m_totalBlocksInPool; } +inline Int MemoryPool::getPeakBlockCount() { return m_peakUsedBlocksInPool; } +inline Int MemoryPool::getInitialBlockCount() { return m_initialAllocationCount; } + +// ---------------------------------------------------------------------------- +inline DynamicMemoryAllocator *DynamicMemoryAllocator::getNextDmaInList() { return m_nextDmaInFactory; } + +// EXTERNALS ////////////////////////////////////////////////////////////////// + +/** + Initialize the memory manager. Construct a new MemoryPoolFactory and + DynamicMemoryAllocator and store 'em in the singletons of the relevant + names. +*/ +extern void initMemoryManager(); + +/** + return true if initMemoryManager() has been called. + return false if only preMainInitMemoryManager() has been called. +*/ +extern Bool isMemoryManagerOfficiallyInited(); + +/** + similar to initMemoryManager, but this should be used if the memory manager must be initialized + prior to main() (e.g., from a static constructor). If preMainInitMemoryManager() is called prior + to initMemoryManager(), then subsequent calls to either are quietly ignored, AS IS any subsequent + call to shutdownMemoryManager() [since there's no safe way to ensure that shutdownMemoryManager + will execute after all static destructors]. + + (Note: this function is actually not externally visible, but is documented here for clarity.) +*/ +/* extern void preMainInitMemoryManager(); */ + +/** + Shut down the memory manager. Throw away TheMemoryPoolFactory and + TheDynamicMemoryAllocator. +*/ +extern void shutdownMemoryManager(); + +extern MemoryPoolFactory *TheMemoryPoolFactory; +extern DynamicMemoryAllocator *TheDynamicMemoryAllocator; + +/** + This function is declared in this header, but is not defined anywhere -- you must provide + it in your code. It is called by initMemoryManager() or preMainInitMemoryManager() in order + to get the specifics of the subpool for the dynamic memory allocator. (If you just want + some defaults, set both return arguments to zero.) The reason for this odd setup is that + we may need to init the memory manager prior to main() [due to static C++ ctors] and + this allows us a way to get the necessary parameters. +*/ +extern void userMemoryManagerGetDmaParms(Int *numSubPools, const PoolInitRec **pParms); + +/** + This function is declared in this header, but is not defined anywhere -- you must provide + it in your code. It is called by initMemoryManager() or preMainInitMemoryManager() in order + to initialize the pools to be used. (You can define an empty function if you like.) +*/ +extern void userMemoryManagerInitPools(); + +/** + This function is declared in this header, but is not defined anywhere -- you must provide + it in your code. It is called by createMemoryPool to adjust the allocation size(s) for a + given pool. Note that the counts are in-out parms! +*/ +extern void userMemoryAdjustPoolSize(const char *poolName, Int& initialAllocationCount, Int& overflowAllocationCount); + +#ifdef __cplusplus + +#ifndef _OPERATOR_NEW_DEFINED_ + + #define _OPERATOR_NEW_DEFINED_ + + extern void * __cdecl operator new (size_t size); + extern void __cdecl operator delete (void *p); + + extern void * __cdecl operator new[] (size_t size); + extern void __cdecl operator delete[] (void *p); + + // additional overloads to account for VC/MFC funky versions + extern void* __cdecl operator new(size_t nSize, const char *, int); + extern void __cdecl operator delete(void *, const char *, int); + + extern void* __cdecl operator new[](size_t nSize, const char *, int); + extern void __cdecl operator delete[](void *, const char *, int); + +#if defined(_MSC_VER) && _MSC_VER < 1300 + // additional overloads for 'placement new' + //inline void* __cdecl operator new (size_t s, void *p) { return p; } + //inline void __cdecl operator delete (void *, void *p) { } + inline void* __cdecl operator new[] (size_t s, void *p) { return p; } + inline void __cdecl operator delete[] (void *, void *p) { } +#endif + +#endif + +#ifdef MEMORYPOOL_DEBUG_CUSTOM_NEW + #define MSGNEW(MSG) new(MSG, 0) + #define NEW new(__FILE__, __LINE__) +#else + #define MSGNEW(MSG) new + #define NEW new +#endif + +#endif + +class STLSpecialAlloc +{ +public: + static void* allocate(size_t __n); + static void deallocate(void* __p, size_t); +}; + +#endif // DISABLE_GAMEMEMORY + + +/** + A simple utility class to ensure exception safety; this holds a MemoryPoolObject + and deletes it in its destructor. Especially useful for iterators! +*/ +class MemoryPoolObjectHolder +{ +private: + MemoryPoolObject *m_mpo; +public: + MemoryPoolObjectHolder(MemoryPoolObject *mpo = NULL) : m_mpo(mpo) { } + void hold(MemoryPoolObject *mpo) { DEBUG_ASSERTCRASH(!m_mpo, ("already holding")); m_mpo = mpo; } + void release() { m_mpo = NULL; } + ~MemoryPoolObjectHolder() { deleteInstance(m_mpo); } +}; + + +#define EMPTY_DTOR(CLASS) inline CLASS::~CLASS() { } diff --git a/Core/GameEngine/Include/Common/GameMemoryNull.h b/Core/GameEngine/Include/Common/GameMemoryNull.h new file mode 100644 index 00000000000..5babbf4553c --- /dev/null +++ b/Core/GameEngine/Include/Common/GameMemoryNull.h @@ -0,0 +1,171 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#pragma once + +#define allocateBytes(ARGCOUNT,ARGLITERAL) allocateBytesImplementation(ARGCOUNT) +#define allocateBytesDoNotZero(ARGCOUNT,ARGLITERAL) allocateBytesDoNotZeroImplementation(ARGCOUNT) +#define newInstanceDesc(ARGCLASS,ARGLITERAL) new ARGCLASS +#define newInstance(ARGCLASS) new ARGCLASS +#define MSGNEW(MSG) new +#define NEW new + + +/** + The DynamicMemoryAllocator class is used to handle unpredictably-sized + allocation requests. +*/ +class DynamicMemoryAllocator +{ +public: + + /// allocate bytes from this pool. (don't call directly; use allocateBytes() macro) + void *allocateBytesImplementation(Int numBytes); + + /// like allocateBytesImplementation, but zeroes the memory before returning + void *allocateBytesDoNotZeroImplementation(Int numBytes); + +#ifdef MEMORYPOOL_DEBUG + void debugIgnoreLeaksForThisBlock(void* pBlockPtr); +#endif + + /// free the bytes. (assumes allocated by this dma.) + void freeBytes(void* pMem); + + /** + return the actual number of bytes that would be allocated + if you tried to allocate the given size. + */ + Int getActualAllocationSize(Int numBytes); +}; + + +/** + The class that manages all the MemoryPools and DynamicMemoryAllocators. + Usually you will create exactly one of these (TheMemoryPoolFactory) + and use it for everything. +*/ +class MemoryPoolFactory +{ +public: + + void memoryPoolUsageReport( const char* filename, FILE *appendToFileInstead = NULL ); + +#ifdef MEMORYPOOL_DEBUG + + void debugMemoryReport(Int flags, Int startCheckpoint, Int endCheckpoint, FILE *fp = NULL ); + void debugSetInitFillerIndex(Int index); + +#endif +}; + + +#define MEMORY_POOL_GLUE_WITHOUT_GCMP(ARGCLASS) \ +protected: \ + virtual ~ARGCLASS(); \ +public: /* include this line at the end to reset visibility to 'public' */ + + +#define MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(ARGCLASS, ARGPOOLNAME) \ + MEMORY_POOL_GLUE_WITHOUT_GCMP(ARGCLASS) + + +// this is the version for an Abstract Base Class, which will never be instantiated... +#define MEMORY_POOL_GLUE_ABC(ARGCLASS) \ +protected: \ + virtual ~ARGCLASS(); \ +public: /* include this line at the end to reset visibility to 'public' */ + + +/** + This class is provided as a simple and safe way to integrate C++ object allocation + into MemoryPool usage. To use it, you must have your class inherit from + MemoryPoolObject, then put the macro MEMORY_POOL_GLUE(MyClassName, "MyPoolName") + at the start of your class definition. (This does not create the pool itself -- you + must create that manually using MemoryPoolFactory::createMemoryPool) +*/ +class MemoryPoolObject +{ +protected: + + /** ensure that all destructors are virtual */ + virtual ~MemoryPoolObject() { } + +public: + + static void deleteInstanceInternal(MemoryPoolObject* mpo) + { + delete mpo; + } +}; + +inline void deleteInstance(MemoryPoolObject* mpo) +{ + MemoryPoolObject::deleteInstanceInternal(mpo); +} + + +/** + Initialize the memory manager. Construct a new MemoryPoolFactory and + DynamicMemoryAllocator and store 'em in the singletons of the relevant + names. +*/ +extern void initMemoryManager(); + +/** + return true if initMemoryManager() has been called. + return false if only preMainInitMemoryManager() has been called. +*/ +extern Bool isMemoryManagerOfficiallyInited(); + +/** + Shut down the memory manager. Throw away TheMemoryPoolFactory and + TheDynamicMemoryAllocator. +*/ +extern void shutdownMemoryManager(); + +extern MemoryPoolFactory *TheMemoryPoolFactory; +extern DynamicMemoryAllocator *TheDynamicMemoryAllocator; + + +// TheSuperHackers @info +// The new operator overloads will zero all memory after allocation. +// This replicates the behavior of the original Game Memory implementation and is necessary to avoid crashing the game, +// where data is not properly zero initialized. Disable these operators when fixing those issues. +#ifndef DISABLE_GAMEMEMORY_NEW_OPERATORS + +extern void * __cdecl operator new(size_t size); +extern void __cdecl operator delete(void *p); + +extern void * __cdecl operator new[](size_t size); +extern void __cdecl operator delete[](void *p); + +// additional overloads to account for VC/MFC funky versions +extern void* __cdecl operator new(size_t size, const char *, int); +extern void __cdecl operator delete(void *p, const char *, int); + +extern void* __cdecl operator new[](size_t size, const char *, int); +extern void __cdecl operator delete[](void *p, const char *, int); + +#endif + +#if defined(_MSC_VER) && _MSC_VER < 1300 +// additional overloads for 'placement new' +inline void* __cdecl operator new[](size_t s, void* p) { return p; } +inline void __cdecl operator delete[](void*, void* p) {} +#endif diff --git a/Core/GameEngine/Include/Common/GameMusic.h b/Core/GameEngine/Include/Common/GameMusic.h new file mode 100644 index 00000000000..864c52f82a5 --- /dev/null +++ b/Core/GameEngine/Include/Common/GameMusic.h @@ -0,0 +1,112 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +//---------------------------------------------------------------------------- +// +// Westwood Studios Pacific. +// +// Confidential Information +// Copyright (C) 2001 - All Rights Reserved +// +//---------------------------------------------------------------------------- +// +// Project: RTS 3 +// +// File name: Common/GameMusic.h +// +// Created: 5/01/01 +// +//---------------------------------------------------------------------------- + +#pragma once + +//---------------------------------------------------------------------------- +// Includes +//---------------------------------------------------------------------------- + +#include "Common/GameAudio.h" +#include "Common/GameMemory.h" + + +//---------------------------------------------------------------------------- +// Forward References +//---------------------------------------------------------------------------- + +class AudioEventRTS; +struct FieldParse; + +//---------------------------------------------------------------------------- +// Type Defines +//---------------------------------------------------------------------------- + + +//=============================== +// MusicTrack +//=============================== + +//------------------------------------------------------------------------------------------------- +/** The MusicTrack struct holds all information about a music track. + * Place data in TrackInfo that is useful to the game code in determining + * what tracks to play. */ +//------------------------------------------------------------------------------------------------- + +class MusicTrack : public MemoryPoolObject +{ + + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( MusicTrack, "MusicTrack" ) + +public: + + MusicTrack(); + // virtual destructor prototype defined by memory pool object + + const FieldParse *getFieldParse( void ) const { return m_musicTrackFieldParseTable; } + + Int index; ///< Track index + AsciiString name; ///< Logical name of track + AsciiString filename; ///< Filename with extension of music track + Real volume; ///< Mixing level for this track + Bool ambient; ///< Game info about this track(public) + + MusicTrack *next; + MusicTrack *prev; + + static const FieldParse m_musicTrackFieldParseTable[]; ///< the parse table for INI definition + +}; + +class MusicManager +{ + public: + MusicManager(); + virtual ~MusicManager(); + + void playTrack( AudioEventRTS *eventToUse ); + void stopTrack( AudioHandle eventToRemove ); + + virtual void addAudioEvent(AudioEventRTS *eventToAdd); // pre-copied + virtual void removeAudioEvent( AudioHandle eventToRemove ); + + void setVolume( Real m_volume ); +}; diff --git a/Core/GameEngine/Include/Common/GameSounds.h b/Core/GameEngine/Include/Common/GameSounds.h new file mode 100644 index 00000000000..4f3a5f955ea --- /dev/null +++ b/Core/GameEngine/Include/Common/GameSounds.h @@ -0,0 +1,98 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +//---------------------------------------------------------------------------- +// +// Westwood Studios Pacific. +// +// Confidential Information +// Copyright (C) 2001 - All Rights Reserved +// +//---------------------------------------------------------------------------- +// +// Project: RTS 3 +// +// File name: Common/GameSounds.h +// +// Created: 5/02/01 +// +//---------------------------------------------------------------------------- + +#pragma once + +#include "Common/SubsystemInterface.h" +#include "Common/GameAudio.h" +#include "Common/GameType.h" + +// Forward declarations +class AudioEventRTS; + +class SoundManager : public SubsystemInterface +{ + public: + SoundManager(); + virtual ~SoundManager(); + + virtual void init( void ); ///< Initializes the sounds system + virtual void postProcessLoad(); + virtual void update( void ); ///< Services sounds tasks. Called by AudioInterface + virtual void reset( void ); ///< Reset the sounds system + + virtual void loseFocus( void ); ///< Called when application loses focus + virtual void regainFocus( void ); ///< Called when application regains focus + + virtual void setListenerPosition( const Coord3D *position ); ///< Set the listener position for map3DSound() calculations + virtual void setViewRadius( Real viewRadius );///< Sets the radius of the view from the center of the screen in world coordinate units + virtual void setCameraAudibleDistance( Real audibleDistance ); + virtual Real getCameraAudibleDistance( void ); + + virtual void addAudioEvent(AudioEventRTS *&eventToAdd); // pre-copied + + virtual void notifyOf2DSampleStart( void ); + virtual void notifyOf3DSampleStart( void ); + + virtual void notifyOf2DSampleCompletion( void ); + virtual void notifyOf3DSampleCompletion( void ); + + virtual Int getAvailableSamples( void ); + virtual Int getAvailable3DSamples( void ); + + // empty string means that this sound wasn't found or some error occurred. CHECK FOR EMPTY STRING. + virtual AsciiString getFilenameForPlayFromAudioEvent( const AudioEventRTS *eventToGetFrom ); + + // called by this class and MilesAudioManager to determine if a sound can still be played + virtual Bool canPlayNow( AudioEventRTS *event ); + + protected: + virtual Bool violatesVoice( AudioEventRTS *event ); + virtual Bool isInterrupting( AudioEventRTS *event ); + + + protected: + UnsignedInt m_num2DSamples; + UnsignedInt m_num3DSamples; + + UnsignedInt m_numPlaying2DSamples; + UnsignedInt m_numPlaying3DSamples; +}; diff --git a/Core/GameEngine/Include/Common/GameUtility.h b/Core/GameEngine/Include/Common/GameUtility.h new file mode 100644 index 00000000000..86790ff4847 --- /dev/null +++ b/Core/GameEngine/Include/Common/GameUtility.h @@ -0,0 +1,39 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#pragma once + +#include "Lib/BaseType.h" + +// For miscellaneous game utility functions. + +class Player; +typedef Int PlayerIndex; + +namespace rts +{ + +bool localPlayerHasRadar(); +Player* getObservedOrLocalPlayer(); ///< Get the current observed or local player. Is never null. +Player* getObservedOrLocalPlayer_Safe(); ///< Get the current observed or local player. Is never null, except when the application does not have players. +PlayerIndex getObservedOrLocalPlayerIndex_Safe(); ///< Get the current observed or local player index. Returns 0 when the application does not have players. + +void changeLocalPlayer(Player* player); //< Change local player during game. Must not pass null. +void changeObservedPlayer(Player* player); ///< Change observed player during game. Can pass null: is identical to passing the "ReplayObserver" player. + +} // namespace rts diff --git a/Core/GameEngine/Include/Common/LocalFile.h b/Core/GameEngine/Include/Common/LocalFile.h new file mode 100644 index 00000000000..db9568ee0e7 --- /dev/null +++ b/Core/GameEngine/Include/Common/LocalFile.h @@ -0,0 +1,131 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +//----------------------------------------------------------------------------= +// +// Westwood Studios Pacific. +// +// Confidential Information +// Copyright(C) 2001 - All Rights Reserved +// +//---------------------------------------------------------------------------- +// +// Project: WSYS Library +// +// Module: IO +// +// File name: LocalFile.h +// +// Created: 4/23/01 +// +//---------------------------------------------------------------------------- + +#pragma once + +//---------------------------------------------------------------------------- +// Includes +//---------------------------------------------------------------------------- + +#include "Common/file.h" + +#if USE_BUFFERED_IO +#include "Utility/stdio_adapter.h" +#endif + +//---------------------------------------------------------------------------- +// Forward References +//---------------------------------------------------------------------------- + + +//---------------------------------------------------------------------------- +// Type Defines +//---------------------------------------------------------------------------- + +//=============================== +// LocalFile +//=============================== +/** + * File abstraction for standard C file operators: open, close, lseek, read, write + */ +//=============================== + +class LocalFile : public File +{ + MEMORY_POOL_GLUE_ABC(LocalFile) + private: + +#if USE_BUFFERED_IO + // srj sez: this was purely an experiment in optimization. + // at the present time, it doesn't appear to be a good one. + // TheSuperHackers @info It is a good optimization and will be + // significantly faster than unbuffered IO with small reads and writes. + FILE* m_file; +#else + int m_handle; ///< Local C file handle +#endif + + public: + + LocalFile(); + //virtual ~LocalFile(); + + + virtual Bool open( const Char *filename, Int access = NONE, size_t bufferSize = BUFFERSIZE ); ///< Open a file for access + virtual void close( void ); ///< Close the file + virtual Int read( void *buffer, Int bytes ); ///< Read the specified number of bytes in to buffer: See File::read + virtual Int readChar(); ///< Read a character from the file + virtual Int readWideChar(); ///< Read a wide character from the file + virtual Int write( const void *buffer, Int bytes ); ///< Write the specified number of bytes from the buffer: See File::write + virtual Int writeFormat( const Char* format, ... ); ///< Write an unterminated formatted string to the file + virtual Int writeFormat( const WideChar* format, ... ); ///< Write an unterminated formatted string to the file + virtual Int writeChar( const Char* character ); ///< Write a character to the file + virtual Int writeChar( const WideChar* character ); ///< Write a wide character to the file + virtual Int seek( Int new_pos, seekMode mode = CURRENT ); ///< Set file position: See File::seek + virtual Bool flush(); ///< flush data to disk + virtual void nextLine(Char *buf = NULL, Int bufSize = 0); ///< moves file position to after the next new-line + virtual Bool scanInt(Int &newInt); ///< return what gets read in as an integer at the current file position. + virtual Bool scanReal(Real &newReal); ///< return what gets read in as a float at the current file position. + virtual Bool scanString(AsciiString &newString); ///< return what gets read in as a string at the current file position. + /** + Allocate a buffer large enough to hold entire file, read + the entire file into the buffer, then close the file. + the buffer is owned by the caller, who is responsible + for freeing is (via delete[]). This is a Good Thing to + use because it minimizes memory copies for BIG files. + */ + virtual char* readEntireAndClose(); + virtual File* convertToRAMFile(); + + protected: + + void closeWithoutDelete(); + void closeFile(); +}; + + + + +//---------------------------------------------------------------------------- +// Inlining +//---------------------------------------------------------------------------- diff --git a/Core/GameEngine/Include/Common/LocalFileSystem.h b/Core/GameEngine/Include/Common/LocalFileSystem.h new file mode 100644 index 00000000000..08f00af5d27 --- /dev/null +++ b/Core/GameEngine/Include/Common/LocalFileSystem.h @@ -0,0 +1,53 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +/////// LocalFileSystem.h //////////////////////////////// +// Bryan Cleveland, August 2002 +////////////////////////////////////////////////////////// + +#pragma once + +#include "Common/SubsystemInterface.h" +#include "FileSystem.h" // for typedefs, etc. + +class LocalFileSystem : public SubsystemInterface +{ +public: + virtual ~LocalFileSystem() {} + + virtual void init() = 0; + virtual void reset() = 0; + virtual void update() = 0; + + virtual File * openFile(const Char *filename, Int access = File::NONE, size_t bufferSize = File::BUFFERSIZE) = 0; + virtual Bool doesFileExist(const Char *filename) const = 0; + virtual void getFileListInDirectory(const AsciiString& currentDirectory, const AsciiString& originalDirectory, const AsciiString& searchName, FilenameList &filenameList, Bool searchSubdirectories) const = 0; ///< search the given directory for files matching the searchName (egs. *.ini, *.rep). Possibly search subdirectories. + virtual Bool getFileInfo(const AsciiString& filename, FileInfo *fileInfo) const = 0; ///< see FileSystem.h + virtual Bool createDirectory(AsciiString directory) = 0; ///< see FileSystem.h + virtual AsciiString normalizePath(const AsciiString& filePath) const = 0; ///< see FileSystem.h + +protected: +}; + +extern LocalFileSystem *TheLocalFileSystem; diff --git a/Core/GameEngine/Include/Common/MapObject.h b/Core/GameEngine/Include/Common/MapObject.h new file mode 100644 index 00000000000..1438662eccc --- /dev/null +++ b/Core/GameEngine/Include/Common/MapObject.h @@ -0,0 +1,182 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + + +// MapObject.h +// Class to encapsulate height map. +// Author: John Ahlquist, April 2001 + +#pragma once + +#include "Common/Dict.h" +#include "Common/GameMemory.h" +#include "GameClient/TerrainRoads.h" + + + +class WorldHeightMapInterfaceClass +{ +public: + + virtual Int getBorderSize() = 0; + virtual Real getSeismicZVelocity(Int xIndex, Int yIndex) const = 0; + virtual void setSeismicZVelocity(Int xIndex, Int yIndex, Real value) = 0; + virtual Real getBilinearSampleSeismicZVelocity( Int x, Int y) = 0; + +}; + +/** MapObject class +Not ref counted. Do not store pointers to this class. */ +class WorldHeightMap; +class RenderObjClass; +class ThingTemplate; +class Shadow; +enum WaypointID CPP_11(: Int); + +#define MAP_XY_FACTOR (10.0f) //How wide and tall each height map square is in world space. +#define MAP_HEIGHT_SCALE (MAP_XY_FACTOR/16.0f) //divide all map heights by 8. + +// m_flags bit values. +enum { + FLAG_DRAWS_IN_MIRROR = 0x00000001, ///< If set, draws in water mirror. + FLAG_ROAD_POINT1 = 0x00000002, ///< If set, is the first point in a road segment. + FLAG_ROAD_POINT2 = 0x00000004, ///< If set, is the second point in a road segment. + FLAG_ROAD_FLAGS = (FLAG_ROAD_POINT1|FLAG_ROAD_POINT2), ///< If nonzero, object is a road piece. + FLAG_ROAD_CORNER_ANGLED = 0x00000008, ///< If set, the road corner is angled rather than curved. + FLAG_BRIDGE_POINT1 = 0x00000010, ///< If set, is the first point in a bridge. + FLAG_BRIDGE_POINT2 = 0x00000020, ///< If set, is the second point in a bridge. + FLAG_BRIDGE_FLAGS = (FLAG_BRIDGE_POINT1|FLAG_BRIDGE_POINT2), ///< If nonzero, object is a bridge piece. + FLAG_ROAD_CORNER_TIGHT = 0x00000040, + FLAG_ROAD_JOIN = 0x00000080, ///< If set, this road end does a generic alpha join. + FLAG_DONT_RENDER = 0x00000100 ///< If set, do not render this object. Only WB pays attention to this. (Right now, anyways) +}; + +class MapObject : public MemoryPoolObject +{ + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(MapObject, "MapObject") + +// friend doesn't play well with MPO -- srj +// friend class WorldHeightMap; +// friend class WorldHeightMapEdit; +// friend class AddObjectUndoable; +// friend class DeleteInfo; + + enum + { + MO_SELECTED = 0x01, + MO_LIGHT = 0x02, + MO_WAYPOINT = 0x04, + MO_SCORCH = 0x08 + }; + + // This data is currently written out into the map data file. + Coord3D m_location; ///< Location of the center of the object. + AsciiString m_objectName; ///< The object name. + const ThingTemplate* m_thingTemplate; ///< thing template for map object + Real m_angle; ///< positive x is 0 degrees, angle is counterclockwise in degrees. + MapObject* m_nextMapObject; ///< linked list. + Int m_flags; ///< Bit flags. + Dict m_properties; ///< general property sheet. + // This data is runtime data that is used by the worldbuider editor, but + // not saved in the map file. + Int m_color; ///< Display color. + RenderObjClass* m_renderObj; ///< object that renders in the 3d scene. + Shadow* m_shadowObj; ///< object that renders shadow in the 3d scene. + RenderObjClass* m_bridgeTowers[ BRIDGE_MAX_TOWERS ]; ///< for bridge towers + Int m_runtimeFlags; + +public: + static MapObject *TheMapObjectListPtr; + static Dict TheWorldDict; + +public: + MapObject(Coord3D loc, AsciiString name, Real angle, Int flags, const Dict* props, + const ThingTemplate *thingTemplate ); + //~MapObject(void); ///< Note that deleting the head of a list deletes all linked objects in the list. + +public: + + Dict *getProperties() { return &m_properties; } ///< return the object's property sheet. + + void setNextMap(MapObject *nextMap) {m_nextMapObject = nextMap;} ///< Link the next map object. + const Coord3D *getLocation(void) const {return &m_location;} ///< Get the center point. + Real getAngle(void) const {return m_angle;} ///< Get the angle. + Int getColor(void) const {return m_color;} ///< Gets whatever ui color we set. + void setColor(Int color) {m_color=color;} ///< Sets the ui color. + AsciiString getName(void) const {return m_objectName;} ///< Gets the object name + void setName(AsciiString name); ///< Sets the object name + void setThingTemplate( const ThingTemplate* thing ); ///< set template + const ThingTemplate *getThingTemplate( void ) const; + MapObject *getNext(void) const {return m_nextMapObject;} ///< Next map object in the list. Not a copy, don't delete it. + MapObject *duplicate(void); ///< Allocates a copy. Caller is responsible for delete-ing this when done with it. + + void setAngle(Real angle) {m_angle = normalizeAngle(angle);} + void setLocation(Coord3D *pLoc) {m_location = *pLoc;} + void setFlag(Int flag) {m_flags |= flag;} + void clearFlag(Int flag) {m_flags &= (~flag);} + Bool getFlag(Int flag) const {return (m_flags&flag)?true:false;} + Int getFlags(void) const {return (m_flags);} + + Bool isSelected(void) const {return (m_runtimeFlags & MO_SELECTED) != 0;} + void setSelected(Bool sel) { if (sel) m_runtimeFlags |= MO_SELECTED; else m_runtimeFlags &= ~MO_SELECTED; } + + Bool isLight(void) const {return (m_runtimeFlags & MO_LIGHT) != 0;} + Bool isWaypoint(void) const {return (m_runtimeFlags & MO_WAYPOINT) != 0;} + Bool isScorch(void) const {return (m_runtimeFlags & MO_SCORCH) != 0;} + + void setIsLight() {m_runtimeFlags |= MO_LIGHT;} + void setIsWaypoint() { m_runtimeFlags |= MO_WAYPOINT; } + void setIsScorch() { m_runtimeFlags |= MO_SCORCH; } + + void setRenderObj(RenderObjClass *pObj); + RenderObjClass *getRenderObj(void) const {return m_renderObj;} + void setShadowObj(Shadow *pObj) {m_shadowObj=pObj;} + Shadow *getShadowObj(void) const {return m_shadowObj;} + + RenderObjClass* getBridgeRenderObject( BridgeTowerType type ); + void setBridgeRenderObject( BridgeTowerType type, RenderObjClass* renderObj ); + + WaypointID getWaypointID(); + AsciiString getWaypointName(); + void setWaypointID(Int i); + void setWaypointName(AsciiString n); + + // calling validate will call verifyValidTeam and verifyValidUniqueID. + void validate(void); + + // verifyValidTeam will either place the map object on an approrpriate team, or leave the + // current team (if it is valid) + void verifyValidTeam(void); + + // verifyValidUniqueID will ensure that this unit isn't sharing a number with another unit. + void verifyValidUniqueID(void); + + // The fast version doesn't attempt to verify uniqueness. It goes + static void fastAssignAllUniqueIDs(void); + + + static MapObject *getFirstMapObject(void) { return TheMapObjectListPtr; } + static Dict* getWorldDict() { return &TheWorldDict; } + static Int countMapObjectsWithOwner(const AsciiString& n); +}; diff --git a/Core/GameEngine/Include/Common/MiniDumper.h b/Core/GameEngine/Include/Common/MiniDumper.h new file mode 100644 index 00000000000..44c03e96ef1 --- /dev/null +++ b/Core/GameEngine/Include/Common/MiniDumper.h @@ -0,0 +1,97 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#pragma once + +#ifdef RTS_ENABLE_CRASHDUMP +#include "DbgHelpLoader.h" + +enum DumpType CPP_11(: Char) +{ + // Smallest dump type with call stacks and some supporting variables + DumpType_Minimal = 'M', + // Largest dump size including complete memory contents of the process + DumpType_Full = 'F', +}; + +class MiniDumper +{ + enum MiniDumperExitCode CPP_11(: Int) + { + MiniDumperExitCode_Success = 0x0, + MiniDumperExitCode_FailureWait = 0x37DA1040, + MiniDumperExitCode_FailureParam = 0x4EA527BB, + MiniDumperExitCode_ForcedTerminate = 0x158B1154, + }; + +public: + MiniDumper(); + Bool IsInitialized() const; + void TriggerMiniDump(DumpType dumpType); + void TriggerMiniDumpForException(_EXCEPTION_POINTERS* e_info, DumpType dumpType); + static void initMiniDumper(const AsciiString& userDirPath); + static void shutdownMiniDumper(); + static LONG WINAPI DumpingExceptionFilter(_EXCEPTION_POINTERS* e_info); + +private: + void Initialize(const AsciiString& userDirPath); + void ShutDown(); + void CreateMiniDump(DumpType dumpType); + void CleanupResources(); + Bool IsDumpThreadStillRunning() const; + void ShutdownDumpThread(); + + // Thread procs + static DWORD WINAPI MiniDumpThreadProc(LPVOID lpParam); + DWORD ThreadProcInternal(); + + // Dump file directory bookkeeping + Bool InitializeDumpDirectory(const AsciiString& userDirPath); + static void KeepNewestFiles(const std::string& directory, const DumpType dumpType, const Int keepCount); + + // Struct to hold file information + struct FileInfo + { + std::string name; + FILETIME lastWriteTime; + }; + + static bool CompareByLastWriteTime(const FileInfo& a, const FileInfo& b); + +private: + Bool m_miniDumpInitialized; + Bool m_loadedDbgHelp; + DumpType m_requestedDumpType; + + // Path buffers + Char m_dumpDir[MAX_PATH]; + Char m_dumpFile[MAX_PATH]; + WideChar m_executablePath[MAX_PATH]; + + // Event handles + HANDLE m_dumpRequested; + HANDLE m_dumpComplete; + HANDLE m_quitting; + + // Thread handles + HANDLE m_dumpThread; + DWORD m_dumpThreadId; +}; + +extern MiniDumper* TheMiniDumper; +#endif diff --git a/Core/GameEngine/Include/Common/MiscAudio.h b/Core/GameEngine/Include/Common/MiscAudio.h new file mode 100644 index 00000000000..fa7b47f3c16 --- /dev/null +++ b/Core/GameEngine/Include/Common/MiscAudio.h @@ -0,0 +1,72 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +///MiscAudio.h///////////////////////////////////////////////////////////////////////////////////// +// This file is for miscellaneous sound hooks that don't have another happy home. + +#pragma once + +#include "Common/AudioEventRTS.h" + +struct MiscAudio +{ + static const FieldParse m_fieldParseTable[]; + + AudioEventRTS m_radarUnitUnderAttackSound; ///< radar sounds to play when unit under attack + AudioEventRTS m_radarHarvesterUnderAttackSound; ///< radar sounds to play when harvester under attack + AudioEventRTS m_radarStructureUnderAttackSound; ///< radar sounds to play when structure under attack + AudioEventRTS m_radarUnderAttackSound; ///< radar sounds to play when ? under attack + AudioEventRTS m_radarInfiltrationSound; ///< radar sounds to play when something is infiltrated + AudioEventRTS m_radarOnlineSound; ///< radar sounds to play when radar goes online + AudioEventRTS m_radarOfflineSound; ///< radar sounds to play when radar goes offline + AudioEventRTS m_defectorTimerTickSound; ///< snd to play during transient invulnerability while defecting // lorenzen + AudioEventRTS m_defectorTimerDingSound; ///< snd to play when you become vulnerable again // lorenzen + AudioEventRTS m_lockonTickSound; ///< snd to play during stealth-fighter-lockon period + AudioEventRTS m_allCheerSound; ///< snd to play when user presses 'cheer' key + AudioEventRTS m_battleCrySound; ///< snd to play when user presses 'battlecry' key + AudioEventRTS m_guiClickSound; ///< snd to play when user presses button in GUI + AudioEventRTS m_noCanDoSound; ///< Global "No Can Do" sound + AudioEventRTS m_stealthDiscoveredSound; ///< I have just discovered an enemy stealth unit + AudioEventRTS m_stealthNeutralizedSound; ///< One of my stealthed units has just been discovered by the enemy + AudioEventRTS m_moneyDepositSound; ///< Money was deposited in my bank + AudioEventRTS m_moneyWithdrawSound; ///< Money was withdrawn from my bank + AudioEventRTS m_buildingDisabled; ///< Building has lost power, been hit with an EMP, or disable hacked. + AudioEventRTS m_buildingReenabled; ///< Building has recovered from being disabled. + AudioEventRTS m_vehicleDisabled; ///< Vehicle has been disabled via EMP or hacker attack. + AudioEventRTS m_vehicleReenabled; ///< Vehicle has recovered from being disabled. + AudioEventRTS m_splatterVehiclePilotsBrain; ///< Pilot has been sniped by Jarmen Kell. + AudioEventRTS m_terroristInCarMoveVoice; ///< Terrorist issues a move order while in a car. + AudioEventRTS m_terroristInCarAttackVoice; ///< Terrorist issues attack order while in a car. + AudioEventRTS m_terroristInCarSelectVoice; ///< Terrorist is selected while in a car. + AudioEventRTS m_crateHeal; ///< When heal crate is picked up. + AudioEventRTS m_crateShroud; ///< When shroud crate is picked up. + AudioEventRTS m_crateSalvage; ///< When salvage crate is picked up. + AudioEventRTS m_crateFreeUnit; ///< When free unit crate is picked up. + AudioEventRTS m_crateMoney; ///< When money crate is picked up. + AudioEventRTS m_unitPromoted; ///< Unit is promoted. + AudioEventRTS m_repairSparks; ///< Battle drone repairs unit. + AudioEventRTS m_sabotageShutDownBuilding; ///< When Saboteur hits a building + AudioEventRTS m_sabotageResetTimerBuilding; ///< When Saboteur hits a building + AudioEventRTS m_aircraftWheelScreech; ///< When a jet lands on a runway. +}; diff --git a/Core/GameEngine/Include/Common/ObjectStatusTypes.h b/Core/GameEngine/Include/Common/ObjectStatusTypes.h new file mode 100644 index 00000000000..affa254c9e0 --- /dev/null +++ b/Core/GameEngine/Include/Common/ObjectStatusTypes.h @@ -0,0 +1,143 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: ObjectStatusTypes.h ///////////////////////////////////////////////////////////////////////// +// Author: Kris, May 2003 +// Desc: Object status types that are stackable using the BitSet system. Used to be ObjectStatusBits +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "Lib/BaseType.h" +#include "Common/BitFlags.h" +#include "Common/BitFlagsIO.h" + +//------------------------------------------------------------------------------------------------- +/** Object status types */ +//------------------------------------------------------------------------------------------------- +enum ObjectStatusTypes CPP_11(: Int) +{ + //These are saved. Do not insert or remove any! + + OBJECT_STATUS_NONE, ///< no status bit + OBJECT_STATUS_DESTROYED, ///< has been destroyed, pending delete + OBJECT_STATUS_CAN_ATTACK, ///< used by garrissoned buildings, is OR'ed with KINDOF_CAN_ATTACK in isAbleToAttack() + OBJECT_STATUS_UNDER_CONSTRUCTION, ///< object is being constructed and is not yet complete + OBJECT_STATUS_UNSELECTABLE, ///< This is a negative condition since these statuses are overrides. ie their presence forces the condition, but their absence means nothing + OBJECT_STATUS_NO_COLLISIONS, ///< object should be ignored for object-object collisions (but not object-ground); used for thing like collapsing parachutes that are intangible + OBJECT_STATUS_NO_ATTACK, ///< Absolute override to being able to attack + OBJECT_STATUS_AIRBORNE_TARGET, ///< InTheAir as far as AntiAir weapons are concerned only. + OBJECT_STATUS_PARACHUTING, ///< object is on a parachute + OBJECT_STATUS_REPULSOR, ///< object repulses "KINDOF_CAN_BE_REPULSED" objects. + OBJECT_STATUS_HIJACKED, ///< unit is in the possesion of an enemy criminal, call the authorities + OBJECT_STATUS_AFLAME, ///< This object is on fire. + OBJECT_STATUS_BURNED, ///< This object has already burned as much as it can. + OBJECT_STATUS_WET, ///< object has been soaked with water + OBJECT_STATUS_IS_FIRING_WEAPON, ///< Object is firing a weapon, now. Not true for special attacks. --Lorenzen + OBJECT_STATUS_BRAKING, ///< Object is braking, and subverts the physics. + OBJECT_STATUS_STEALTHED, ///< Object is currently "stealthed" + OBJECT_STATUS_DETECTED, ///< Object is in range of a stealth-detector unit (meaningless if STEALTHED not set) + OBJECT_STATUS_CAN_STEALTH, ///< Object has ability to stealth allowing the stealth update module to run. + OBJECT_STATUS_SOLD, ///< Object is being sold + OBJECT_STATUS_UNDERGOING_REPAIR, ///< Object is awaiting/undergoing a repair order that has been issued + OBJECT_STATUS_RECONSTRUCTING, ///< Reconstructing + OBJECT_STATUS_MASKED, ///< Masked objects are not selectable and targetable by players or AI + OBJECT_STATUS_IS_ATTACKING, ///< Object is in the general Attack state (incl. aim, approach, etc.). Note that IS_FIRING_WEAPON and IS_AIMING_WEAPON is a subset of this! + OBJECT_STATUS_IS_USING_ABILITY, ///< Object is in the process of preparing or firing a special ability. + OBJECT_STATUS_IS_AIMING_WEAPON, ///< Object is aiming a weapon, now. Not true for special attacks. + OBJECT_STATUS_NO_ATTACK_FROM_AI, ///< attacking this object may not be done from commandSource == CMD_FROM_AI + OBJECT_STATUS_IGNORING_STEALTH, ///< temporarily ignoring all stealth bits. (used only for some special-case mine clearing stuff.) + OBJECT_STATUS_IS_CARBOMB, ///< Object is now a carbomb. + + // TheSuperHackers @info New statuses added in Zero Hour + // Note: Loading old save games that do not track these flags in objects will not recover them. Expect logic bugs. + OBJECT_STATUS_DECK_HEIGHT_OFFSET, ///< Object factors deck height on top of ground altitude. + OBJECT_STATUS_RIDER1, + OBJECT_STATUS_RIDER2, + OBJECT_STATUS_RIDER3, + OBJECT_STATUS_RIDER4, + OBJECT_STATUS_RIDER5, + OBJECT_STATUS_RIDER6, + OBJECT_STATUS_RIDER7, + OBJECT_STATUS_RIDER8, + OBJECT_STATUS_FAERIE_FIRE, ///< Anyone shooting at you shoots faster than normal + OBJECT_STATUS_MISSILE_KILLING_SELF, ///< Object (likely a missile or bomb) is *BUSTING* its way through the *BUNKER*, building or ground, awaiting death at the bottom. + OBJECT_STATUS_REASSIGN_PARKING, ///< Jet is trying to get a better parking assignment. + OBJECT_STATUS_BOOBY_TRAPPED, ///< We need to know we have a booby trap on us so we can detonate it from many different code segments + OBJECT_STATUS_IMMOBILE, ///< Do not move! + OBJECT_STATUS_DISGUISED, ///< Object is disguised (a type of stealth) + OBJECT_STATUS_DEPLOYED, ///< Object is deployed. + // add more status types here and don't forget to add to the string table ObjectStatusMaskType::s_bitNameList[] + + OBJECT_STATUS_COUNT + +}; + +typedef BitFlags ObjectStatusMaskType; + +#define MAKE_OBJECT_STATUS_MASK(k) ObjectStatusMaskType(ObjectStatusMaskType::kInit, (k)) +#define MAKE_OBJECT_STATUS_MASK2(k,a) ObjectStatusMaskType(ObjectStatusMaskType::kInit, (k), (a)) +#define MAKE_OBJECT_STATUS_MASK3(k,a,b) ObjectStatusMaskType(ObjectStatusMaskType::kInit, (k), (a), (b)) +#define MAKE_OBJECT_STATUS_MASK4(k,a,b,c) ObjectStatusMaskType(ObjectStatusMaskType::kInit, (k), (a), (b), (c)) +#define MAKE_OBJECT_STATUS_MASK5(k,a,b,c,d) ObjectStatusMaskType(ObjectStatusMaskType::kInit, (k), (a), (b), (c), (d)) + +inline Bool TEST_OBJECT_STATUS_MASK( const ObjectStatusMaskType& m, ObjectStatusTypes t ) +{ + return m.test( t ); +} + +inline Bool TEST_OBJECT_STATUS_MASK_ANY( const ObjectStatusMaskType& m, const ObjectStatusMaskType& mask ) +{ + return m.anyIntersectionWith( mask ); +} + +inline Bool TEST_OBJECT_STATUS_MASK_MULTI( const ObjectStatusMaskType& m, const ObjectStatusMaskType& mustBeSet, const ObjectStatusMaskType& mustBeClear ) +{ + return m.testSetAndClear( mustBeSet, mustBeClear ); +} + +inline Bool OBJECT_STATUS_MASK_ANY_SET( const ObjectStatusMaskType& m) +{ + return m.any(); +} + +inline void CLEAR_OBJECT_STATUS_MASK( ObjectStatusMaskType& m ) +{ + m.clear(); +} + +inline void SET_ALL_OBJECT_STATUS_MASK_BITS( ObjectStatusMaskType& m ) +{ + m.clear( ); + m.flip( ); +} + +inline void FLIP_OBJECT_STATUS_MASK( ObjectStatusMaskType& m ) +{ + m.flip(); +} + +// defined in Common/System/ObjectStatusTypes.cpp +extern ObjectStatusMaskType OBJECT_STATUS_MASK_NONE; // inits to all zeroes diff --git a/Core/GameEngine/Include/Common/RAMFile.h b/Core/GameEngine/Include/Common/RAMFile.h new file mode 100644 index 00000000000..084aa8d857e --- /dev/null +++ b/Core/GameEngine/Include/Common/RAMFile.h @@ -0,0 +1,127 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +//----------------------------------------------------------------------------= +// +// Westwood Studios Pacific. +// +// Confidential Information +// Copyright(C) 2001 - All Rights Reserved +// +//---------------------------------------------------------------------------- +// +// Project: WSYS Library +// +// Module: IO +// +// File name: wsys/RAMFile.h +// +// Created: 11/08/01 +// +//---------------------------------------------------------------------------- + +#pragma once + +//---------------------------------------------------------------------------- +// Includes +//---------------------------------------------------------------------------- + +#include "Common/file.h" + +//---------------------------------------------------------------------------- +// Forward References +//---------------------------------------------------------------------------- + + + +//---------------------------------------------------------------------------- +// Type Defines +//---------------------------------------------------------------------------- + +//=============================== +// RAMFile +//=============================== +/** + * File abstraction for standard C file operators: open, close, lseek, read, write + */ +//=============================== + +class RAMFile : public File +{ + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(RAMFile, "RAMFile") + protected: + + Char *m_data; ///< File data in memory + Int m_pos; ///< current read position + Int m_size; ///< size of file in memory + + public: + + RAMFile(); + //virtual ~RAMFile(); + + + virtual Bool open( const Char *filename, Int access = NONE, size_t bufferSize = 0 ); ///< Open a file for access + virtual void close( void ); ///< Close the file + virtual Int read( void *buffer, Int bytes ); ///< Read the specified number of bytes in to buffer: See File::read + virtual Int readChar(); ///< Read a character from the file + virtual Int readWideChar(); ///< Read a wide character from the file + virtual Int write( const void *buffer, Int bytes ); ///< Write the specified number of bytes from the buffer: See File::write + virtual Int writeFormat( const Char* format, ... ); ///< Write the formatted string to the file + virtual Int writeFormat( const WideChar* format, ... ); ///< Write the formatted string to the file + virtual Int writeChar( const Char* character ); ///< Write a character to the file + virtual Int writeChar( const WideChar* character ); ///< Write a wide character to the file + virtual Int seek( Int new_pos, seekMode mode = CURRENT ); ///< Set file position: See File::seek + virtual Bool flush(); ///< flush data to disk + virtual void nextLine(Char *buf = NULL, Int bufSize = 0); ///< moves current position to after the next new-line + + virtual Bool scanInt(Int &newInt); ///< return what gets read as an integer from the current memory position. + virtual Bool scanReal(Real &newReal); ///< return what gets read as a float from the current memory position. + virtual Bool scanString(AsciiString &newString); ///< return what gets read as a string from the current memory position. + + virtual Bool open( File *file ); ///< Open file for fast RAM access + virtual Bool openFromArchive(File *archiveFile, const AsciiString& filename, Int offset, Int size); ///< copy file data from the given file at the given offset for the given size. + virtual Bool copyDataToFile(File *localFile); ///< write the contents of the RAM file to the given local file. This could be REALLY slow. + + /** + Allocate a buffer large enough to hold entire file, read + the entire file into the buffer, then close the file. + the buffer is owned by the caller, who is responsible + for freeing is (via delete[]). This is a Good Thing to + use because it minimizes memory copies for BIG files. + */ + virtual char* readEntireAndClose(); + virtual File* convertToRAMFile(); + + protected: + + void closeFile(); +}; + + + + +//---------------------------------------------------------------------------- +// Inlining +//---------------------------------------------------------------------------- diff --git a/Core/GameEngine/Include/Common/Radar.h b/Core/GameEngine/Include/Common/Radar.h new file mode 100644 index 00000000000..431cfbf1668 --- /dev/null +++ b/Core/GameEngine/Include/Common/Radar.h @@ -0,0 +1,312 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: Radar.h ////////////////////////////////////////////////////////////////////////////////// +// Author: Colin Day, January 2002 +// Desc: Logical radar implementation +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "Lib/BaseType.h" +#include "Common/SubsystemInterface.h" +#include "Common/GameMemory.h" +#include "GameClient/Display.h" // for ShroudLevel +#include "GameClient/Color.h" + +// FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// +class GameWindow; +class Object; +class Player; +class TerrainLogic; + +// GLOBAL ///////////////////////////////////////////////////////////////////////////////////////// +// +// the following is used for the resolution of the radar "cells" ... this is how accurate +// the radar is and also reflects directly the size of the image we build ... which with +// WW3D must be a square power of two as well +// +enum +{ + RADAR_CELL_WIDTH = 128, // radar created at this horz resolution + RADAR_CELL_HEIGHT = 128 // radar created at this vert resolution +}; + +//------------------------------------------------------------------------------------------------- +/** These event types determine the colors radar events happen in to make it easier for us + * to play events with a consistent color scheme */ +//------------------------------------------------------------------------------------------------- +enum RadarEventType CPP_11(: Int) +{ + RADAR_EVENT_INVALID = 0, + RADAR_EVENT_CONSTRUCTION, + RADAR_EVENT_UPGRADE, + RADAR_EVENT_UNDER_ATTACK, + RADAR_EVENT_INFORMATION, + RADAR_EVENT_BEACON_PULSE, + RADAR_EVENT_INFILTRATION, //for defection, hijacking, hacking, carbombing, and other sneaks + RADAR_EVENT_BATTLE_PLAN, + RADAR_EVENT_STEALTH_DISCOVERED, // we discovered a stealth unit + RADAR_EVENT_STEALTH_NEUTRALIZED, // our stealth unit has been revealed + RADAR_EVENT_FAKE, //Internally creates a radar event, but doesn't notify the player (unit lost + //for example, so we can use the spacebar to jump to the event). + + RADAR_EVENT_NUM_EVENTS + +}; + +// PROTOTYPES ///////////////////////////////////////////////////////////////////////////////////// + +//------------------------------------------------------------------------------------------------- +/** Radar objects are objects that are on the radar, go figure :) */ +//------------------------------------------------------------------------------------------------- +class RadarObject : public MemoryPoolObject, + public Snapshot +{ + + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( RadarObject, "RadarObject" ) + +public: + + RadarObject( void ); + // destructor prototype defined by memory pool glue + + // color management + void setColor( Color c ) { m_color = c; } + Color getColor( void ) const { return m_color; } + + void friend_setObject( Object *obj ) { m_object = obj; } + Object *friend_getObject( void ) { return m_object; } + const Object *friend_getObject( void ) const { return m_object; } + + void friend_setNext( RadarObject *next ) { m_next = next; } + RadarObject *friend_getNext( void ) { return m_next; } + const RadarObject *friend_getNext( void ) const { return m_next; } + + Bool isTemporarilyHidden() const; + static Bool isTemporarilyHidden(const Object* obj); + +protected: + + // snapshot methods + virtual void crc( Xfer *xfer ); + virtual void xfer( Xfer *xfer ); + virtual void loadPostProcess( void ); + + Object *m_object; ///< the object + RadarObject *m_next; ///< next radar object + Color m_color; ///< color to draw for this object on the radar + +}; + +//------------------------------------------------------------------------------------------------- +/** Radar priorities. Keep this in sync with the priority names list below */ +//------------------------------------------------------------------------------------------------- +enum RadarPriorityType CPP_11(: Int) +{ + RADAR_PRIORITY_INVALID, // a priority that has not been set (in general it won't show up on the radar) + RADAR_PRIORITY_NOT_ON_RADAR, // object specifically forbidden from being on the radar + RADAR_PRIORITY_STRUCTURE, // structure level drawing priority + RADAR_PRIORITY_UNIT, // unit level drawing priority + RADAR_PRIORITY_LOCAL_UNIT_ONLY, // unit priority, but only on the radar if controlled by the local player + + RADAR_PRIORITY_NUM_PRIORITIES +}; +#ifdef DEFINE_RADAR_PRIORITY_NAMES +static const char *const RadarPriorityNames[] = +{ + "INVALID", // a priority that has not been set (in general it won't show up on the radar) + "NOT_ON_RADAR", // object specifically forbidden from being on the radar + "STRUCTURE", // structure level drawing priority + "UNIT", // unit level drawing priority + "LOCAL_UNIT_ONLY", // unit priority, but only on the radar if controlled by the local player + + NULL +}; +static_assert(ARRAY_SIZE(RadarPriorityNames) == RADAR_PRIORITY_NUM_PRIORITIES + 1, "Incorrect array size"); +#endif // DEFINE_RADAR_PRIOTITY_NAMES + +//------------------------------------------------------------------------------------------------- +/** Interface for the radar */ +//------------------------------------------------------------------------------------------------- +class Radar : public Snapshot, + public SubsystemInterface +{ + +public: + + Radar( void ); + virtual ~Radar( void ); + + virtual void init( void ) { } ///< subsystem initialization + virtual void reset( void ); ///< subsystem reset + virtual void update( void ); ///< subsystem per frame update + + // is the game window parameter the radar window + Bool isRadarWindow( GameWindow *window ) { return (m_radarWindow == window) && (m_radarWindow != NULL); } + + Bool radarToWorld( const ICoord2D *radar, Coord3D *world ); ///< radar point to world point on terrain + Bool radarToWorld2D( const ICoord2D *radar, Coord3D *world ); ///< radar point to world point (x,y only!) + Bool worldToRadar( const Coord3D *world, ICoord2D *radar ); ///< translate world point to radar (x,y) + Bool localPixelToRadar( const ICoord2D *pixel, ICoord2D *radar ); ///< translate pixel (with UL of radar being (0,0)) to logical radar coordinates + Bool screenPixelToWorld( const ICoord2D *pixel, Coord3D *world ); ///< translate pixel (with UL of the screen being (0,0)) to world position in the world + Object *objectUnderRadarPixel( const ICoord2D *pixel ); ///< return the object (if any) represented by the pixel coordinates passed in + void findDrawPositions( Int startX, Int startY, Int width, Int height, + ICoord2D *ul, ICoord2D *lr ); ///< make translation for screen area of radar square to scaled aspect ratio preserving points inside the radar area + + // priority inquiry + static Bool isPriorityVisible( RadarPriorityType priority ); ///< is the priority passed in a "visible" one on the radar + + // radar events + void createEvent( const Coord3D *world, RadarEventType type, Real secondsToLive = 4.0f ); ///< create radar event at location in world + void createPlayerEvent( Player *player, const Coord3D *world, RadarEventType type, Real secondsToLive = 4.0f ); ///< create radar event using player colors + + Bool getLastEventLoc( Coord3D *eventPos ); ///< get last event loc (if any) + void tryUnderAttackEvent( const Object *obj ); ///< try to make an "under attack" event if it's the proper time + void tryInfiltrationEvent( const Object *obj ); ///< try to make an "infiltration" event if it's the proper time + Bool tryEvent( RadarEventType event, const Coord3D *pos ); ///< try to make a "stealth" event + + // adding and removing objects from the radar + virtual Bool addObject( Object *obj ); ///< add object to radar + virtual Bool removeObject( Object *obj ); ///< remove object from radar + + // radar options + void hide( Int playerIndex, Bool hide ) { m_radarHidden[playerIndex] = hide; } ///< hide/show the radar + Bool isRadarHidden( Int playerIndex ) { return m_radarHidden[playerIndex]; } ///< is radar hidden + // other radar option methods here like the ability to show a certain + // team, show buildings, show units at all, etc + + // forcing the radar on/off regardless of player situation + void forceOn( Int playerIndex, Bool force ) { m_radarForceOn[playerIndex] = force; } ///< force the radar to be on + Bool isRadarForced( Int playerIndex ) { return m_radarForceOn[playerIndex]; } ///< is radar forced on? + + /// refresh the water values for the radar + virtual void refreshTerrain( TerrainLogic *terrain ); + + /// refresh the radar when the state of world objects changes drastically + virtual void refreshObjects() {}; + + /// queue a refresh of the terrain at the next available time + virtual void queueTerrainRefresh( void ); + + virtual void newMap( TerrainLogic *terrain ); ///< reset radar for new map + + virtual void draw( Int pixelX, Int pixelY, Int width, Int height ) = 0; ///< draw the radar + + /// empty the entire shroud + virtual void clearShroud() = 0; + + /// set the shroud level at shroud cell x,y + virtual void setShroudLevel( Int x, Int y, CellShroudStatus setting ) = 0; + +protected: + + // snapshot methods + virtual void crc( Xfer *xfer ); + virtual void xfer( Xfer *xfer ); + virtual void loadPostProcess( void ); + + /// internal method for creating a radar event with specific colors + void internalCreateEvent( const Coord3D *world, RadarEventType type, Real secondsToLive, + const RGBAColorInt *color1, const RGBAColorInt *color2 ); + + void deleteList( RadarObject **list ); + void deleteListResources( void ); ///< delete list radar resources used + Bool deleteFromList( Object *obj, RadarObject **list ); ///< try to remove object from specific list + + Real getTerrainAverageZ() const { return m_terrainAverageZ; } + Real getWaterAverageZ() const { return m_waterAverageZ; } + + void clearAllEvents( void ); ///< remove all radar events in progress + + // search the object list for an object that maps to the given logical radar coordinates + Object *searchListForRadarLocationMatch( RadarObject *listHead, ICoord2D *radarMatch ); + + void linkRadarObject( RadarObject *newObj, RadarObject **list ); + void assignObjectColorToRadarObject( RadarObject *radarObj, Object *obj ); + + Bool m_radarHidden[MAX_PLAYER_COUNT]; ///< true when radar is not visible + Bool m_radarForceOn[MAX_PLAYER_COUNT]; ///< true when radar is forced to be on + + RadarObject *m_objectList; ///< list of objects in the radar + RadarObject *m_localObjectList; /** list of objects for the local player, sorted + * in exactly the same priority as the regular + * object list for all other objects */ + + // TheSuperHackers @bugfix xezon 22/11/2025 Now stores local heroes in a separate list, + // because they are treated with special icons but should otherwise work like all other + // radar objects. In retail version, the cached hero object data was able to dangle + // for a few frames and cause undefined behavior. + RadarObject *m_localHeroObjectList; ///< list of hero objects for the local player + + Real m_terrainAverageZ; ///< average Z for terrain samples + Real m_waterAverageZ; ///< average Z for water samples + + // + // when dealing with world sampling we will sample at these intervals so that + // the whole map can be accounted for within our RADAR_CELL_WIDTH and + // RADAR_CELL_HEIGHT resolutions + // + Real m_xSample; + Real m_ySample; + + enum { MAX_RADAR_EVENTS = 64 }; + struct RadarEvent + { + RadarEventType type; ///< type of this radar event + Bool active; ///< TRUE when event is "active", otherwise it's just historical information in the event array to look through + UnsignedInt createFrame; ///< frame event was created on + UnsignedInt dieFrame; ///< frame the event will go away on + UnsignedInt fadeFrame; ///< start fading out on this frame + RGBAColorInt color1; ///< color 1 for drawing + RGBAColorInt color2; ///< color 2 for drawing + Coord3D worldLoc; ///< location of event in the world + ICoord2D radarLoc; ///< 2D radar location of the event + Bool soundPlayed; ///< TRUE when we have played the radar sound for this + }; + RadarEvent m_event[ MAX_RADAR_EVENTS ];///< our radar events + Int m_nextFreeRadarEvent; ///< index into m_event for where to store the next event + Int m_lastRadarEvent; ///< index of the most recent radar event + + GameWindow *m_radarWindow; ///< window we display the radar in + + Region3D m_mapExtent; ///< extents of the current map + + UnsignedInt m_queueTerrainRefreshFrame; ///< frame we requested the last terrain refresh on + +}; + +// EXTERNALS ////////////////////////////////////////////////////////////////////////////////////// +extern Radar *TheRadar; ///< the radar singleton extern + +// TheSuperHackers @feature helmutbuhler 10/04/2025 +// Radar that does nothing. Used for Headless Mode. +class RadarDummy : public Radar +{ +public: + virtual void draw(Int pixelX, Int pixelY, Int width, Int height) { } + virtual void clearShroud() { } + virtual void setShroudLevel(Int x, Int y, CellShroudStatus setting) { } +}; diff --git a/Core/GameEngine/Include/Common/RandomValue.h b/Core/GameEngine/Include/Common/RandomValue.h new file mode 100644 index 00000000000..011477a3372 --- /dev/null +++ b/Core/GameEngine/Include/Common/RandomValue.h @@ -0,0 +1,39 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// RandomValue.h +// Random number generation system +// Author: Michael S. Booth, January 1998 + +#pragma once + +#include "Lib/BaseType.h" + +extern void InitRandom( void ); +extern void InitRandom( UnsignedInt seed ); +extern void InitGameLogicRandom( UnsignedInt seed ); ///< Set the GameLogic seed to a known value at game start +extern UnsignedInt GetGameLogicRandomSeed( void ); ///< Get the seed (used for replays) +extern UnsignedInt GetGameLogicRandomSeedCRC( void );///< Get the seed (used for CRCs) + +//-------------------------------------------------------------------------------------------------------------- diff --git a/Core/GameEngine/Include/Common/ReplaySimulation.h b/Core/GameEngine/Include/Common/ReplaySimulation.h new file mode 100644 index 00000000000..219f6233709 --- /dev/null +++ b/Core/GameEngine/Include/Common/ReplaySimulation.h @@ -0,0 +1,48 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#pragma once + +class ReplaySimulation +{ +public: + + // TheSuperHackers @feature helmutbuhler 13/04/2025 + // Simulate a list of replays without graphics. + // Returns exit code 1 if mismatch or other error occurred + // Returns exit code 0 if all replays were successfully simulated without mismatches + static int simulateReplays(const std::vector &filenames, int maxProcesses); + + static void stop() { s_isRunning = false; } + + static Bool isRunning() { return s_isRunning; } + static UnsignedInt getCurrentReplayIndex() { return s_replayIndex; } + static UnsignedInt getReplayCount() { return s_replayCount; } + +private: + + static int simulateReplaysInThisProcess(const std::vector &filenames); + static int simulateReplaysInWorkerProcesses(const std::vector &filenames, int maxProcesses); + static std::vector resolveFilenameWildcards(const std::vector &filenames); + +private: + + static Bool s_isRunning; + static UnsignedInt s_replayIndex; + static UnsignedInt s_replayCount; +}; diff --git a/Core/GameEngine/Include/Common/StreamingArchiveFile.h b/Core/GameEngine/Include/Common/StreamingArchiveFile.h new file mode 100644 index 00000000000..bf481b19784 --- /dev/null +++ b/Core/GameEngine/Include/Common/StreamingArchiveFile.h @@ -0,0 +1,111 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +//----------------------------------------------------------------------------= +// +// Westwood Studios Pacific. +// +// Confidential Information +// Copyright(C) 2001 - All Rights Reserved +// +//---------------------------------------------------------------------------- +// +// Project: RTS 3 +// +// Module: IO +// +// File name: Common/StreamingArchiveFile.h +// +// Created: 11/08/01 +// +//---------------------------------------------------------------------------- + +#pragma once + +//---------------------------------------------------------------------------- +// Includes +//---------------------------------------------------------------------------- + +#include "Common/RAMFile.h" + +//---------------------------------------------------------------------------- +// Forward References +//---------------------------------------------------------------------------- + + + +//---------------------------------------------------------------------------- +// Type Defines +//---------------------------------------------------------------------------- + +//=============================== +// StreamingArchiveFile +//=============================== +/** + * File abstraction for standard C file operators: open, close, lseek, read, write + */ +//=============================== + +class StreamingArchiveFile : public RAMFile +{ + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(StreamingArchiveFile, "StreamingArchiveFile") + protected: + + File *m_file; ///< The archive file that I came from + Int m_startingPos; ///< My starting position in the archive + Int m_size; ///< My length + Int m_curPos; ///< My current position. + + public: + + StreamingArchiveFile(); + //virtual ~StreamingArchiveFile(); + + + virtual Bool open( const Char *filename, Int access = NONE, size_t bufferSize = BUFFERSIZE ); ///< Open a file for access + virtual void close( void ); ///< Close the file + virtual Int read( void *buffer, Int bytes ); ///< Read the specified number of bytes in to buffer: See File::read + virtual Int write( const void *buffer, Int bytes ); ///< Write the specified number of bytes from the buffer: See File::write + virtual Int seek( Int new_pos, seekMode mode = CURRENT ); ///< Set file position: See File::seek + + // Ini's should not be parsed with streaming files, that's just dumb. + virtual void nextLine(Char *buf = NULL, Int bufSize = 0) { DEBUG_CRASH(("Should not call nextLine on a streaming file.")); } + virtual Bool scanInt(Int &newInt) { DEBUG_CRASH(("Should not call scanInt on a streaming file.")); return FALSE; } + virtual Bool scanReal(Real &newReal) { DEBUG_CRASH(("Should not call scanReal on a streaming file.")); return FALSE; } + virtual Bool scanString(AsciiString &newString) { DEBUG_CRASH(("Should not call scanString on a streaming file.")); return FALSE; } + + virtual Bool open( File *file ); ///< Open file for fast RAM access + virtual Bool openFromArchive(File *archiveFile, const AsciiString& filename, Int offset, Int size); ///< copy file data from the given file at the given offset for the given size. + virtual Bool copyDataToFile(File *localFile) { DEBUG_CRASH(("Are you sure you meant to copyDataToFile on a streaming file?")); return FALSE; } + + virtual char* readEntireAndClose() { DEBUG_CRASH(("Are you sure you meant to readEntireAndClose on a streaming file?")); return NULL; } + virtual File* convertToRAMFile() { DEBUG_CRASH(("Are you sure you meant to readEntireAndClose on a streaming file?")); return this; } +}; + + + + +//---------------------------------------------------------------------------- +// Inlining +//---------------------------------------------------------------------------- diff --git a/Core/GameEngine/Include/Common/UnicodeString.h b/Core/GameEngine/Include/Common/UnicodeString.h new file mode 100644 index 00000000000..ff66edfe478 --- /dev/null +++ b/Core/GameEngine/Include/Common/UnicodeString.h @@ -0,0 +1,522 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: UnicodeString.h +//----------------------------------------------------------------------------- +// +// Westwood Studios Pacific. +// +// Confidential Information +// Copyright (C) 2001 - All Rights Reserved +// +//----------------------------------------------------------------------------- +// +// Project: RTS3 +// +// File name: UnicodeString.h +// +// Created: Steven Johnson, October 2001 +// +// Desc: General-purpose string classes +// +//----------------------------------------------------------------------------- +/////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include "Lib/BaseType.h" +#include "Common/Debug.h" +#include "Common/Errors.h" + +class AsciiString; + +// ----------------------------------------------------- +/** + UnicodeString is the fundamental double-byte string type used in the Generals + code base, and should be preferred over all other string constructions + (e.g., array of WideChar, STL string<>, WWVegas StringClass, etc.) + + Of course, other string setups may be used when necessary or appropriate! + + UnicodeString is modeled after the MFC CString class, with some minor + syntactic differences to keep in line with our coding conventions. + + Basically, UnicodeString allows you to treat a string as an intrinsic + type, rather analogous to 'int' -- when passed by value, a new string + is created, and modifying the new string doesn't modify the original. + This is done fairly efficiently, so that no new memory allocation is done + unless the string is actually modified. + + Naturally, UnicodeString handles all memory issues, so there's no need + to do anything to free memory... just allow the UnicodeString's + destructor to run. + + UnicodeStrings are suitable for use as automatic, member, or static variables. +*/ + +class UnicodeString +{ +private: + + // Note, this is a Plain Old Data Structure... don't + // add a ctor/dtor, 'cuz they won't ever be called. + struct UnicodeStringData + { +#if defined(RTS_DEBUG) + const WideChar* m_debugptr; // just makes it easier to read in the debugger +#endif + unsigned short m_refCount; // reference count + unsigned short m_numCharsAllocated; // length of data allocated + // WideChar m_stringdata[]; + + WideChar* peek() { return (WideChar*)(this+1); } + }; + + #ifdef RTS_DEBUG + void validate() const; + #else + void validate() const { } + #endif + +protected: + UnicodeStringData* m_data; // pointer to ref counted string data + + WideChar* peek() const; + void releaseBuffer(); + void ensureUniqueBufferOfSize(int numCharsNeeded, Bool preserveData, const WideChar* strToCpy, const WideChar* strToCat); + +public: + + typedef WideChar value_type; + typedef value_type* pointer; + typedef const value_type* const_pointer; + + enum + { + MAX_FORMAT_BUF_LEN = 2048, ///< max total len of string created by format/format_va + MAX_LEN = 32767 ///< max total len of any UnicodeString, in chars + }; + + + /** + This is a convenient global used to indicate the empty + string, so we don't need to construct temporaries + for such a common thing. + */ + static const UnicodeString TheEmptyString; + + /** + Default constructor -- construct a new, empty UnicodeString. + */ + UnicodeString(); + /** + Copy constructor -- make this UnicodeString identical to the + other UnicodeString. (This is actually quite efficient, because + they will simply share the same string and increment the + refcount.) + */ + UnicodeString(const UnicodeString& stringSrc); + /** + Constructor -- from a literal string. Constructs an UnicodeString + with the given string. Note that a copy of the string is made; + the input ptr is not saved. + Note that this is no longer explicit, as the conversion is almost + always wanted, anyhow. + */ + UnicodeString(const WideChar* s); + + /** + Constructs an UnicodeString with the given string and length. + The length must not be larger than the actual string length. + */ + UnicodeString(const WideChar* s, int len); + + /** + Destructor. Not too exciting... clean up the works and such. + */ + ~UnicodeString(); + + /** + Return the length, in characters, of the string up to the first zero or null terminator. + */ + int getLength() const; + + /** + Return the number of bytes used by the string up to the first zero or null terminator. + */ + int getByteCount() const; + /** + Return true iff the length of the string is zero. Equivalent + to (getLength() == 0) but slightly more efficient. + */ + Bool isEmpty() const; + /** + Make the string empty. Equivalent to (str = "") but slightly more efficient. + */ + void clear(); + + /** + Return the character and the given (zero-based) index into the string. + No range checking is done (except in debug mode). + */ + WideChar getCharAt(int index) const; + /** + Return a pointer to the (null-terminated) string. Note that this is + a const pointer: do NOT change this! It is imperative that it be + impossible (or at least, really difficuly) for someone to change our + private data, since it might be shared amongst other UnicodeStrings. + */ + const WideChar* str() const; + + /** + Makes sure there is room for a string of len+1 characters, and + returns a pointer to the string buffer. This ensures that the + string buffer is NOT shared. This is intended for the file reader, + that is reading new strings in from a file. jba. + */ + WideChar* getBufferForRead(Int len); + + /** + Replace the contents of self with the given string. + (This is actually quite efficient, because + they will simply share the same string and increment the + refcount.) + */ + void set(const UnicodeString& stringSrc); + + /** + Replace the contents of self with the given string. + Note that a copy of the string is made; the input ptr is not saved. + */ + void set(const WideChar* s); + + /** + Replace the contents of self with the given string and length. + Note that a copy of the string is made; the input ptr is not saved. + The length must not be larger than the actual string length. + */ + void set(const WideChar* s, int len); + + /** + replace contents of self with the given string. Note the + nomenclature is translate rather than set; this is because + not all single-byte strings translate one-for-one into + UnicodeStrings, so some data manipulation may be necessary, + and the resulting strings may not be equivalent. + */ + void translate(const AsciiString& stringSrc); + + /** + Concatenate the given string onto self. + */ + void concat(const UnicodeString& stringSrc); + /** + Concatenate the given string onto self. + */ + void concat(const WideChar* s); + /** + Concatenate the given character onto self. + */ + void concat(const WideChar c); + + /** + Remove leading and trailing whitespace from the string. + */ + void trim( void ); + + /** + Remove trailing whitespace from the string. + */ + void trimEnd(void); + + /** + Remove all consecutive occurances of c from the end of the string. + */ + void trimEnd(const WideChar c); + + /** + Remove the final character in the string. If the string is empty, + do nothing. (This is a rather dorky method, but used a lot in + text editing, thus its presence here.) + */ + void removeLastChar(); + + /** + Remove the final charCount characters in the string. If the string is empty, + do nothing. + */ + void truncateBy(const Int charCount); + + /** + Truncate the string to a length of maxLength characters, not including null termination, + by removing from the end. If the string is empty or shorter than maxLength, do nothing. + */ + void truncateTo(const Int maxLength); + + /** + Analogous to sprintf() -- this formats a string according to the + given sprintf-style format string (and the variable argument list) + and stores the result in self. + */ + void format(UnicodeString format, ...); + void format(const WideChar* format, ...); + /** + Identical to format(), but takes a va_list rather than + a variable argument list. (i.e., analogous to vsprintf.) + */ + void format_va(const UnicodeString& format, va_list args); + void format_va(const WideChar* format, va_list args); + + /** + Conceptually identical to wsccmp(). + */ + int compare(const UnicodeString& stringSrc) const; + /** + Conceptually identical to wsccmp(). + */ + int compare(const WideChar* s) const; + /** + Conceptually identical to _wcsicmp(). + */ + int compareNoCase(const UnicodeString& stringSrc) const; + /** + Conceptually identical to _wcsicmp(). + */ + int compareNoCase(const WideChar* s) const; + + /** + return true iff self starts with the given string. + */ + Bool startsWith(const WideChar* p) const; + Bool startsWith(const UnicodeString& stringSrc) const { return startsWith(stringSrc.str()); } + + /** + return true iff self starts with the given string. (case insensitive) + */ + Bool startsWithNoCase(const WideChar* p) const; + Bool startsWithNoCase(const UnicodeString& stringSrc) const { return startsWithNoCase(stringSrc.str()); } + + /** + return true iff self ends with the given string. + */ + Bool endsWith(const WideChar* p) const; + Bool endsWith(const UnicodeString& stringSrc) const { return endsWith(stringSrc.str()); } + + /** + return true iff self ends with the given string. (case insensitive) + */ + Bool endsWithNoCase(const WideChar* p) const; + Bool endsWithNoCase(const UnicodeString& stringSrc) const { return endsWithNoCase(stringSrc.str()); } + + /** + conceptually similar to strtok(): + + extract the next whitespace-delimited token from the front + of 'this' and copy it into 'token', returning true if a nonempty + token was found. (note that this modifies 'this' as well, stripping + the token off!) + */ + Bool nextToken(UnicodeString* token, UnicodeString delimiters = UnicodeString::TheEmptyString); + +// +// You might think it would be a good idea to overload the * operator +// to allow for an implicit conversion to an WideChar*. This is +// in theory a good idea, but in practice, there's lots of code +// that assumes it should check text fields for null, which +// is meaningless for us, since we never return a null ptr. +// +// operator const WideChar*() const { return str(); } +// + + UnicodeString& operator=(const UnicodeString& stringSrc); ///< the same as set() + UnicodeString& operator=(const WideChar* s); ///< the same as set() +}; + + +// ----------------------------------------------------- +inline WideChar* UnicodeString::peek() const +{ + DEBUG_ASSERTCRASH(m_data, ("null string ptr")); + validate(); + return m_data->peek(); +} + +// ----------------------------------------------------- +inline UnicodeString::UnicodeString() : m_data(0) +{ + validate(); +} + +// ----------------------------------------------------- +inline UnicodeString::~UnicodeString() +{ + validate(); + releaseBuffer(); +} + +// ----------------------------------------------------- +inline int UnicodeString::getLength() const +{ + validate(); + return m_data ? wcslen(peek()) : 0; +} + +// ----------------------------------------------------- +inline int UnicodeString::getByteCount() const +{ + validate(); + return m_data ? getLength() * sizeof(WideChar) : 0; +} + +// ----------------------------------------------------- +inline Bool UnicodeString::isEmpty() const +{ + validate(); + return m_data == NULL || peek()[0] == 0; +} + +// ----------------------------------------------------- +inline void UnicodeString::clear() +{ + validate(); + releaseBuffer(); + validate(); +} + +// ----------------------------------------------------- +inline WideChar UnicodeString::getCharAt(int index) const +{ + DEBUG_ASSERTCRASH(index >= 0 && index < getLength(), ("bad index in getCharAt")); + validate(); + return m_data ? peek()[index] : 0; +} + +// ----------------------------------------------------- +inline const WideChar* UnicodeString::str() const +{ + validate(); + static const WideChar TheNullChr = 0; + return m_data ? peek() : &TheNullChr; +} + +// ----------------------------------------------------- +inline UnicodeString& UnicodeString::operator=(const UnicodeString& stringSrc) +{ + validate(); + set(stringSrc); + validate(); + return *this; +} + +// ----------------------------------------------------- +inline UnicodeString& UnicodeString::operator=(const WideChar* s) +{ + validate(); + set(s); + validate(); + return *this; +} + +// ----------------------------------------------------- +inline void UnicodeString::concat(const UnicodeString& stringSrc) +{ + validate(); + concat(stringSrc.str()); + validate(); +} + +// ----------------------------------------------------- +inline void UnicodeString::concat(const WideChar c) +{ + validate(); + /// this can probably be made more efficient, if necessary + WideChar tmp[2] = { c, 0 }; + concat(tmp); + validate(); +} + +// ----------------------------------------------------- +inline int UnicodeString::compare(const UnicodeString& stringSrc) const +{ + validate(); + return wcscmp(this->str(), stringSrc.str()); +} + +// ----------------------------------------------------- +inline int UnicodeString::compare(const WideChar* s) const +{ + validate(); + return wcscmp(this->str(), s); +} + +// ----------------------------------------------------- +inline int UnicodeString::compareNoCase(const UnicodeString& stringSrc) const +{ + validate(); + return _wcsicmp(this->str(), stringSrc.str()); +} + +// ----------------------------------------------------- +inline int UnicodeString::compareNoCase(const WideChar* s) const +{ + validate(); + return _wcsicmp(this->str(), s); +} + +// ----------------------------------------------------- +inline Bool operator==(const UnicodeString& s1, const UnicodeString& s2) +{ + return wcscmp(s1.str(), s2.str()) == 0; +} + +// ----------------------------------------------------- +inline Bool operator!=(const UnicodeString& s1, const UnicodeString& s2) +{ + return wcscmp(s1.str(), s2.str()) != 0; +} + +// ----------------------------------------------------- +inline Bool operator<(const UnicodeString& s1, const UnicodeString& s2) +{ + return wcscmp(s1.str(), s2.str()) < 0; +} + +// ----------------------------------------------------- +inline Bool operator<=(const UnicodeString& s1, const UnicodeString& s2) +{ + return wcscmp(s1.str(), s2.str()) <= 0; +} + +// ----------------------------------------------------- +inline Bool operator>(const UnicodeString& s1, const UnicodeString& s2) +{ + return wcscmp(s1.str(), s2.str()) > 0; +} + +// ----------------------------------------------------- +inline Bool operator>=(const UnicodeString& s1, const UnicodeString& s2) +{ + return wcscmp(s1.str(), s2.str()) >= 0; +} diff --git a/Core/GameEngine/Include/Common/WorkerProcess.h b/Core/GameEngine/Include/Common/WorkerProcess.h new file mode 100644 index 00000000000..664d4a7f65d --- /dev/null +++ b/Core/GameEngine/Include/Common/WorkerProcess.h @@ -0,0 +1,56 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#pragma once + +// Helper class that allows you to start a worker process and retrieve its exit code +// and console output as a string. +// It also makes sure that the started process is killed in case our process exits in any way. +class WorkerProcess +{ +public: + WorkerProcess(); + + bool startProcess(UnicodeString command); + + void update(); + + bool isRunning() const; + + // returns true iff the process exited. + bool isDone() const; + + DWORD getExitCode() const; + AsciiString getStdOutput() const; + + // Terminate Process if it's running + void kill(); + +private: + // returns true if all output has been received + // returns false if the worker is still running + bool fetchStdOutput(); + +private: + HANDLE m_processHandle; + HANDLE m_readHandle; + HANDLE m_jobHandle; + AsciiString m_stdOutput; + DWORD m_exitcode; + bool m_isDone; +}; diff --git a/Core/GameEngine/Include/Common/Xfer.h b/Core/GameEngine/Include/Common/Xfer.h index f8f64d95ea8..f9c82288007 100644 --- a/Core/GameEngine/Include/Common/Xfer.h +++ b/Core/GameEngine/Include/Common/Xfer.h @@ -39,9 +39,6 @@ #pragma once -#ifndef __XFER_H_ -#define __XFER_H_ - // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// #include "Common/Science.h" #include "Common/Upgrade.h" @@ -68,7 +65,7 @@ enum XferMode CPP_11(: Int) XFER_LOAD, XFER_CRC, - NUM_XFER_TYPES // please keep this last + NUM_XFER_TYPES }; //------------------------------------------------------------------------------------------------- @@ -76,7 +73,7 @@ enum XferMode CPP_11(: Int) enum XferStatus CPP_11(: Int) { XFER_STATUS_INVALID = 0, - + XFER_OK, ///< all is green and good XFER_EOF, ///< end of file encountered XFER_FILE_NOT_FOUND, ///< requested file does not exist @@ -93,10 +90,10 @@ enum XferStatus CPP_11(: Int) XFER_INVALID_PARAMETERS, ///< invalid parameters XFER_LIST_NOT_EMPTY, ///< trying to xfer into a list that should be empty, but isn't XFER_UNKNOWN_STRING, ///< unrecognized string value - + XFER_ERROR_UNKNOWN, ///< unknown error (isn't that useful!) - NUM_XFER_STATUS // please keep this last + NUM_XFER_STATUS }; // ------------------------------------------------------------------------------------------------ @@ -106,7 +103,7 @@ enum XferOptions CPP_11(: UnsignedInt) XO_NONE = 0x00000000, XO_NO_POST_PROCESSING = 0x00000001, - XO_ALL = 0xFFFFFFFF // keep this last please + XO_ALL = 0xFFFFFFFF }; /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -189,8 +186,5 @@ class Xfer UnsignedInt m_options; ///< xfer options XferMode m_xferMode; ///< the current xfer mode AsciiString m_identifier; ///< the string identifier - -}; - -#endif // __XFER_H_ +}; diff --git a/Core/GameEngine/Include/Common/XferCRC.h b/Core/GameEngine/Include/Common/XferCRC.h index 5c07d5bc4a2..b57769e86d2 100644 --- a/Core/GameEngine/Include/Common/XferCRC.h +++ b/Core/GameEngine/Include/Common/XferCRC.h @@ -29,9 +29,6 @@ #pragma once -#ifndef __XFERCRC_H_ -#define __XFERCRC_H_ - // USER INCLUDES ////////////////////////////////////////////////////////////////////////////////// #include "Common/Xfer.h" @@ -64,11 +61,8 @@ class XferCRC : public Xfer virtual void xferImplementation( void *data, Int dataSize ); - void addCRC( UnsignedInt val ); ///< CRC a 4-byte block + inline void addCRC( UnsignedInt val ); ///< CRC a 4-byte block UnsignedInt m_crc; }; - -#endif // __XFERDISKWRITE_H_ - diff --git a/Core/GameEngine/Include/Common/XferDeepCRC.h b/Core/GameEngine/Include/Common/XferDeepCRC.h index 514ea80c1db..7125d41fe04 100644 --- a/Core/GameEngine/Include/Common/XferDeepCRC.h +++ b/Core/GameEngine/Include/Common/XferDeepCRC.h @@ -29,9 +29,6 @@ #pragma once -#ifndef __XFERDEEPCRC_H_ -#define __XFERDEEPCRC_H_ - // USER INCLUDES ////////////////////////////////////////////////////////////////////////////////// #include "Common/Xfer.h" #include "Common/XferCRC.h" @@ -64,6 +61,3 @@ class XferDeepCRC : public XferCRC FILE * m_fileFP; ///< pointer to file }; - -#endif // __XFERDEEPCRC_H_ - diff --git a/Core/GameEngine/Include/Common/XferLoad.h b/Core/GameEngine/Include/Common/XferLoad.h index 0d68e29271c..4df7a5428d6 100644 --- a/Core/GameEngine/Include/Common/XferLoad.h +++ b/Core/GameEngine/Include/Common/XferLoad.h @@ -29,11 +29,7 @@ #pragma once -#ifndef __XFER_LOAD_H_ -#define __XFER_LOAD_H_ - // USER INCLUDES ////////////////////////////////////////////////////////////////////////////////// -#include #include "Common/Xfer.h" // FOWARD REFERNCES /////////////////////////////////////////////////////////////////////////////// @@ -68,6 +64,3 @@ class XferLoad : public Xfer FILE * m_fileFP; ///< pointer to file }; - -#endif // __XFER_LOAD_H_ - diff --git a/Core/GameEngine/Include/Common/XferSave.h b/Core/GameEngine/Include/Common/XferSave.h index 58c3a315f48..f836910beec 100644 --- a/Core/GameEngine/Include/Common/XferSave.h +++ b/Core/GameEngine/Include/Common/XferSave.h @@ -29,9 +29,6 @@ #pragma once -#ifndef __XFER_SAVE_H_ -#define __XFER_SAVE_H_ - // USER INCLUDES ////////////////////////////////////////////////////////////////////////////////// #include "Common/Xfer.h" @@ -73,6 +70,3 @@ class XferSave : public Xfer XferBlockData *m_blockStack; ///< stack of block data }; - -#endif // __XFER_SAVE_H_ - diff --git a/Core/GameEngine/Include/Common/crc.h b/Core/GameEngine/Include/Common/crc.h new file mode 100644 index 00000000000..0cc7ae74add --- /dev/null +++ b/Core/GameEngine/Include/Common/crc.h @@ -0,0 +1,138 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// CRC.h /////////////////////////////////////////////////////////////// +// A class encapsulating CRC calculation +// Author: Matthew D. Campbell, October 2001 + +#pragma once + +#include "Lib/BaseType.h" + +#ifdef RTS_DEBUG + +//#include "winsock2.h" // for htonl + +class CRC +{ +public: + CRC() { crc = 0; } + + void computeCRC( const void *buf, Int len ); ///< Compute the CRC for a buffer, added into current CRC + void clear( void ) { crc = 0; } ///< Clears the CRC to 0 +// UnsignedInt get( void ) { return htonl(crc); } ///< Get the combined CRC + UnsignedInt get( void ); + +#if (defined(_MSC_VER) && _MSC_VER < 1300) && RETAIL_COMPATIBLE_CRC + void set( UnsignedInt v ) + { + crc = v; + } +#endif + +private: + void addCRC( UnsignedByte val ); ///< CRC a 4-byte block + + UnsignedInt crc; +}; + +#else + +// optimized inline only version +class CRC +{ +public: + CRC(void) { crc=0; } + + /// Compute the CRC for a buffer, added into current CRC + __forceinline void computeCRC( const void *buf, Int len ) + { + if (!buf||len<1) + return; + +#if !(defined(_MSC_VER) && _MSC_VER < 1300) + // C++ version left in for reference purposes + for (UnsignedByte *uintPtr=(UnsignedByte *)buf;len>0;len--,uintPtr++) + { + int hibit; + if (crc & 0x80000000) + { + hibit = 1; + } + else + { + hibit = 0; + } + + crc <<= 1; + crc += *uintPtr; + crc += hibit; + } +#else + // ASM version, verified by comparing resulting data with C++ version data + unsigned *crcPtr=&crc; + _asm + { + mov esi,[buf] + mov ecx,[len] + dec ecx + mov edi,[crcPtr] + mov ebx,dword ptr [edi] + xor eax,eax + lp: + mov al,byte ptr [esi] + shl ebx,1 + inc esi + adc ebx,eax + dec ecx + jns lp + mov dword ptr [edi],ebx + }; +#endif + } + + /// Clears the CRC to 0 + void clear( void ) + { + crc = 0; + } + + ///< Get the combined CRC + UnsignedInt get( void ) const + { + return crc; + } + +#if (defined(_MSC_VER) && _MSC_VER < 1300) && RETAIL_COMPATIBLE_CRC + void set( UnsignedInt v ) + { + crc = v; + } +#endif + +private: + UnsignedInt crc; +}; + +#endif diff --git a/Core/GameEngine/Include/Common/file.h b/Core/GameEngine/Include/Common/file.h new file mode 100644 index 00000000000..80e66445914 --- /dev/null +++ b/Core/GameEngine/Include/Common/file.h @@ -0,0 +1,224 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +//----------------------------------------------------------------------------= +// +// Westwood Studios Pacific. +// +// Confidential Information +// Copyright(C) 2001 - All Rights Reserved +// +//---------------------------------------------------------------------------- +// +// Project: WSYS Library +// +// Module: IO +// +// File name: wsys/File.h +// +// Created: 4/23/01 +// +//---------------------------------------------------------------------------- + +#pragma once + +//---------------------------------------------------------------------------- +// Includes +//---------------------------------------------------------------------------- + +#include "Lib/BaseType.h" +#include "Common/AsciiString.h" +#include "Common/GameMemory.h" + +//---------------------------------------------------------------------------- +// Forward References +//---------------------------------------------------------------------------- + + + +//---------------------------------------------------------------------------- +// Type Defines +//---------------------------------------------------------------------------- + +//=============================== +// File +//=============================== +/** + * File is an interface class for basic file operations. + * + * All code should use the File class and not its derivatives, unless + * absolutely necessary. Also FS::Open should be used to create File objects and open files. + * + * TheSuperHackers @feature Adds LINEBUF and FULLBUF modes and buffer size argument for file open. + */ +//=============================== + +class File : public MemoryPoolObject +{ + MEMORY_POOL_GLUE_ABC(File) +// friend doesn't play well with MPO (srj) +// friend class FileSystem; + + public: + + enum access + { + NONE = 0x00000000, ///< Access file. Reading by default + + READ = 0x00000001, ///< Access file for reading + WRITE = 0x00000002, ///< Access file for writing + READWRITE = (READ | WRITE), + + APPEND = 0x00000004, ///< Seek to end of file on open + CREATE = 0x00000008, ///< Create file if it does not exist + TRUNCATE = 0x00000010, ///< Delete all data in file when opened + + // NOTE: accesses file as binary data if neither TEXT and BINARY are set + TEXT = 0x00000020, ///< Access file as text data + BINARY = 0x00000040, ///< Access file as binary data + + ONLYNEW = 0x00000080, ///< Only create file if it does not exist + + // NOTE: STREAMING is Mutually exclusive with WRITE + STREAMING = 0x00000100, ///< Do not read this file into a ram file, read it as requested. + + // NOTE: accesses file with full buffering if neither LINEBUF and FULLBUF are set + LINEBUF = 0x00000200, ///< Access file with line buffering + FULLBUF = 0x00000400, ///< Access file with full buffering + }; + + enum seekMode + { + START, ///< Seek position is relative to start of file + CURRENT, ///< Seek position is relative to current file position + END ///< Seek position is relative from the end of the file + }; + + enum + { + BUFFERSIZE = BUFSIZ, + }; + + protected: + + AsciiString m_nameStr; ///< Stores file name + Int m_access; ///< How the file was opened + Bool m_open; ///< Has the file been opened + Bool m_deleteOnClose; ///< delete File object on close() + + + File(); ///< This class can only used as a base class + //virtual ~File(); + + void closeWithoutDelete(); + + public: + + + Bool eof(); + virtual Bool open( const Char *filename, Int access = NONE, size_t bufferSize = BUFFERSIZE ); ///< Open a file for access + virtual void close( void ); ///< Close the file !!! File object no longer valid after this call !!! + + virtual Int read( void *buffer, Int bytes ) = 0 ; /**< Read the specified number of bytes from the file in to the + * memory pointed at by buffer. Returns the number of bytes read. + * Returns -1 if an error occurred. + */ + virtual Int readChar() = 0 ; /**< Read a character from the file + * Returns the character converted to an integer. + * Returns EOF if an error occurred. + */ + virtual Int readWideChar() = 0 ; /**< Read a wide character from the file + * Returns the wide character converted to an integer. + * Returns wide EOF if an error occurred. + */ + virtual Int write( const void *buffer, Int bytes ) = 0 ; /**< Write the specified number of bytes from the + * memory pointed at by buffer to the file. Returns the number of bytes written. + * Returns -1 if an error occurred. + */ + virtual Int writeFormat( const Char* format, ... ) = 0 ; /**< Write an unterminated formatted string to the file + * Returns the number of bytes written. + * Returns -1 if an error occurred. + */ + virtual Int writeFormat( const WideChar* format, ... ) = 0 ; /**< Write an unterminated formatted wide character string to the file + * Returns the number of bytes written. + * Returns -1 if an error occurred. + */ + virtual Int writeChar( const Char* character ) = 0 ; /**< Write a character to the file + * Returns a copy of the character written. + * Returns EOF if an error occurred. + */ + virtual Int writeChar( const WideChar* character ) = 0 ; /**< Write a wide character to the file + * Returns a copy of the wide character written. + * Returns wide EOF if an error occurred. + */ + virtual Int seek( Int bytes, seekMode mode = CURRENT ) = 0; /**< Sets the file position of the next read/write operation. Returns the new file + * position as the number of bytes from the start of the file. + * Returns -1 if an error occurred. + * + * seekMode determines how the seek is done: + * + * START : means seek to the specified number of bytes from the start of the file + * CURRENT: means seek the specified the number of bytes from the current file position + * END: means seek the specified number of bytes back from the end of the file + */ + virtual Bool flush() = 0; ///< flush data to disk + virtual void nextLine(Char *buf = NULL, Int bufSize = 0) = 0; ///< reads until it reaches a new-line character + + virtual Bool scanInt(Int &newInt) = 0; ///< read an integer from the current file position. + virtual Bool scanReal(Real &newReal) = 0; ///< read a real number from the current file position. + virtual Bool scanString(AsciiString &newString) = 0; ///< read a string from the current file position. + + virtual Bool print ( const Char *format, ...); ///< Prints formated string to text file + virtual Int size( void ); ///< Returns the size of the file + virtual Int position( void ); ///< Returns the current read/write position + + + void setName( const char *name ); ///< Set the name of the file + const char* getName( void ) const; ///< Returns a pointer to the name of the file + Int getAccess( void ) const; ///< Returns file's access flags + + void deleteOnClose ( void ); ///< Causes the File object to delete itself when it closes + + /** + Allocate a buffer large enough to hold entire file, read + the entire file into the buffer, then close the file. + the buffer is owned by the caller, who is responsible + for freeing is (via delete[]). This is a Good Thing to + use because it minimizes memory copies for BIG files. + */ + virtual char* readEntireAndClose() = 0; + virtual File* convertToRAMFile() = 0; +}; + + + + +//---------------------------------------------------------------------------- +// Inlining +//---------------------------------------------------------------------------- + +inline const char* File::getName( void ) const { return m_nameStr.str(); } +inline void File::setName( const char *name ) { m_nameStr.set(name); } +inline Int File::getAccess( void ) const { return m_access; } +inline void File::deleteOnClose( void ) { m_deleteOnClose = TRUE; } diff --git a/Core/GameEngine/Include/Common/simpleplayer.h b/Core/GameEngine/Include/Common/simpleplayer.h new file mode 100644 index 00000000000..ff93823701d --- /dev/null +++ b/Core/GameEngine/Include/Common/simpleplayer.h @@ -0,0 +1,119 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#pragma once + +#include "wmsdk.h" + +// +// we preserve a list of "ready-to-free" list of wave headers for the +// caller to unprepare and free +// +typedef struct WAVEHDR_LIST { + LPWAVEHDR pwh; + struct WAVEHDR_LIST *next; +} WAVEHDR_LIST; + +#define SIMPLE_PLAYER_OPEN_EVENT _T( "45ab58e0-382e-4d1c-ac50-88a5f9601851" ) +#define SIMPLE_PLAYER_CLOSE_EVENT _T( "276095fa-a8e0-48e6-ac61-8b0002345607" ) +#define WMAPLAY_EVENT _T( "9e828a72-64f3-48f0-9de8-13dafd0cbd3a" ) +/////////////////////////////////////////////////////////////////////////////// +class CSimplePlayer : public IWMReaderCallback +{ +public: + CSimplePlayer( HRESULT* phr ); + ~CSimplePlayer(); + + virtual HRESULT Play( LPCWSTR pszUrl, DWORD dwSecDuration, HANDLE hCompletionEvent, HRESULT *phrCompletion ); + +// +// IUnknown Implemenation +// +public: + virtual HRESULT STDMETHODCALLTYPE QueryInterface( + REFIID riid, + void **ppvObject ); + + virtual ULONG STDMETHODCALLTYPE AddRef(); + virtual ULONG STDMETHODCALLTYPE Release(); + +// +// IWMReaderCallback Implemenation +// +public: + virtual HRESULT STDMETHODCALLTYPE OnSample( + /* [in] */ DWORD dwOutputNum, + /* [in] */ QWORD cnsSampleTime, + /* [in] */ QWORD cnsSampleDuration, + /* [in] */ DWORD dwFlags, + /* [in] */ INSSBuffer __RPC_FAR *pSample, + /* [in] */ void __RPC_FAR *pvContext); + + virtual HRESULT STDMETHODCALLTYPE OnStatus( + /* [in] */ WMT_STATUS Status, + /* [in] */ HRESULT hr, + /* [in] */ WMT_ATTR_DATATYPE dwType, + /* [in] */ BYTE __RPC_FAR *pValue, + /* [in] */ void __RPC_FAR *pvContext); + +// +// Helper Methods +// +protected: + + HRESULT Close(); + + void OnWaveOutMsg( UINT uMsg, DWORD dwParam1, DWORD dwParam2 ); + + static void CALLBACK WaveProc( + HWAVEOUT hwo, + UINT uMsg, + DWORD dwInstance, + DWORD dwParam1, + DWORD dwParam2 ); + + HRESULT AddWaveHeader( LPWAVEHDR pwh ); + void RemoveWaveHeaders( void ); + + CRITICAL_SECTION m_CriSec; + WAVEHDR_LIST *m_whdrHead; + + LONG m_cRef; + LONG m_cBuffersOutstanding; + BOOL m_fEof; + HANDLE m_hCompletionEvent; + + IWMReader *m_pReader; + IWMHeaderInfo *m_pHeader; + HWAVEOUT m_hwo; + + HRESULT *m_phrCompletion; + + HRESULT m_hrOpen; + HANDLE m_hOpenEvent; + HANDLE m_hCloseEvent; + + union + { + WAVEFORMATEX m_wfx; + BYTE m_WfxBuf[1024]; + }; + + LPWSTR m_pszUrl; + +}; diff --git a/Core/GameEngine/Include/Common/urllaunch.h b/Core/GameEngine/Include/Common/urllaunch.h new file mode 100644 index 00000000000..ca23690143d --- /dev/null +++ b/Core/GameEngine/Include/Common/urllaunch.h @@ -0,0 +1,23 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#pragma once + +HRESULT MakeEscapedURL( LPWSTR pszInURL, LPWSTR *ppszOutURL ); + +HRESULT LaunchURL( LPCWSTR pszURL ); diff --git a/Core/GameEngine/Include/GameClient/ClientRandomValue.h b/Core/GameEngine/Include/GameClient/ClientRandomValue.h new file mode 100644 index 00000000000..3222cf4e71f --- /dev/null +++ b/Core/GameEngine/Include/GameClient/ClientRandomValue.h @@ -0,0 +1,91 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// ClientRandomValue.h +// Random number generation system +// Author: Michael S. Booth, January 1998 +// Split out into separate Logic/Client/Audio headers by MDC Sept 2002 + +#pragma once + +#include "Lib/BaseType.h" + +// do NOT use these functions directly, rather use the macros below +extern Int GetGameClientRandomValue( int lo, int hi, const char *file, int line ); +extern Real GetGameClientRandomValueReal( Real lo, Real hi, const char *file, int line ); + +// use these macros to access the random value functions +#define GameClientRandomValue( lo, hi ) GetGameClientRandomValue( lo, hi, __FILE__, __LINE__ ) +#define GameClientRandomValueReal( lo, hi ) GetGameClientRandomValueReal( lo, hi, __FILE__, __LINE__ ) + +//-------------------------------------------------------------------------------------------------------------- +class CColorAlphaDialog; +class DebugWindowDialog; + +/** + * A GameClientRandomVariable represents a distribution of random values + * from which discrete values can be retrieved. + */ +class GameClientRandomVariable +{ +public: + // NOTE: This class cannot have a constructor or destructor due to its use within unions + + /** + * CONSTANT represents a single, constant, value. + * UNIFORM represents a uniform distribution of random values. + * GAUSSIAN represents a normally distributed set of random values. + * TRIANGULAR represents a distribution of random values in the shape + * of a triangle, with the peak probability midway between low and high. + * LOW_BIAS represents a distribution of random values with + * maximum probability at low, and zero probability at high. + * HIGH_BIAS represents a distribution of random values with + * zero probability at low, and maximum probability at high. + */ + enum DistributionType + { + CONSTANT, UNIFORM, GAUSSIAN, TRIANGULAR, LOW_BIAS, HIGH_BIAS, + DISTRIBUTION_COUNT + }; + + static const char *const DistributionTypeNames[]; + + /// define the range of random values, and the distribution of values + void setRange( Real low, Real high, DistributionType type = UNIFORM ); + + Real getValue( void ) const; ///< return a value from the random distribution + Real getMinimumValue( void ) const { return m_low; } + Real getMaximumValue( void ) const { return m_high; } + DistributionType getDistributionType( void ) const { return m_type; } +protected: + DistributionType m_type; ///< the kind of random distribution + Real m_low, m_high; ///< the range of random values + + // These two friends are for particle editing. + friend CColorAlphaDialog; + friend DebugWindowDialog; + +}; + +//-------------------------------------------------------------------------------------------------------------- diff --git a/Core/GameEngine/Include/GameClient/MapUtil.h b/Core/GameEngine/Include/GameClient/MapUtil.h new file mode 100644 index 00000000000..408333c4794 --- /dev/null +++ b/Core/GameEngine/Include/GameClient/MapUtil.h @@ -0,0 +1,150 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: MapUtil.h ///////////////////////////////////////////////////////// +// Author: Matt Campbell, December 2001 +// Description: Map utility/convenience routines +//////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "Common/AsciiString.h" +#include "Common/UnicodeString.h" + +#include "Common/STLTypedefs.h" + +class GameWindow; +class AsciiString; +struct Coord3D; +struct FileInfo; +class Image; +class DataChunkInput; +struct DataChunkInfo; +// This matches the windows timestamp. +enum { SUPPLY_TECH_SIZE = 15}; +typedef std::list ICoord2DList; + +class TechAndSupplyImages +{ +public: + ICoord2DList m_techPosList; + ICoord2DList m_supplyPosList; +}; + +struct WinTimeStamp +{ + UnsignedInt m_lowTimeStamp; + UnsignedInt m_highTimeStamp; +}; + + +class WaypointMap : public std::map +{ +public: + void update( void ); ///< returns the number of multiplayer start spots found + Int m_numStartSpots; +}; + +typedef std::list Coord3DList; + +class MapMetaData +{ +public: + UnicodeString m_displayName; + AsciiString m_nameLookupTag; + Region3D m_extent; + Int m_numPlayers; + + Bool m_isMultiplayer; + Bool m_isOfficial; + Bool m_doesExist; ///< Flag to indicate whether the map physically exists. Should be true. + UnsignedInt m_filesize; + UnsignedInt m_CRC; + + WinTimeStamp m_timestamp; + + WaypointMap m_waypoints; + Coord3DList m_supplyPositions; + Coord3DList m_techPositions; + AsciiString m_fileName; +}; + +// TheSuperHackers @performance xezon 02/11/2025 Simplifies and improves the implementation of MapCache +// to prevent expensive reoccurring redundant map cache reads. + +class MapCache : public std::map +{ + typedef std::set MapNameSet; + +public: + MapCache() + : m_doCreateStandardMapCacheINI(TRUE) + , m_doLoadStandardMapCacheINI(TRUE) + , m_doLoadUserMapCacheINI(TRUE) + {} + + void updateCache( void ); + + AsciiString getMapDir() const; + AsciiString getUserMapDir() const; + AsciiString getMapExtension() const; + + const MapMetaData *findMap(AsciiString mapName); + + // allow us to create a set of shippable maps to be in mapcache.ini. For use with -buildMapCache. + void addShippingMap(AsciiString mapName) { mapName.toLower(); m_allowedMaps.insert(mapName); } + +private: + void prepareUnseenMaps(const AsciiString &mapDir); + Bool clearUnseenMaps(const AsciiString &mapDir); + void loadMapsFromMapCacheINI(const AsciiString &mapDir); + Bool loadMapsFromDisk(const AsciiString &mapDir, Bool isOfficial, Bool filterByAllowedMaps = FALSE); // returns true if we needed to (re)parse a map + Bool addMap(const AsciiString &mapDir, const AsciiString &fname, const AsciiString &lowerFname, FileInfo &fileInfo, Bool isOfficial); ///< returns true if it had to (re)parse the map + void writeCacheINI(const AsciiString &mapDir); + + static const char *const m_mapCacheName; + + MapNameSet m_allowedMaps; + Bool m_doCreateStandardMapCacheINI; + Bool m_doLoadStandardMapCacheINI; + Bool m_doLoadUserMapCacheINI; +}; + +extern MapCache *TheMapCache; +extern TechAndSupplyImages TheSupplyAndTechImageLocations; + +// TheSuperHackers @refactor xezon 28/11/2025 Refactors the map list population implementation +// by breaking it into smaller pieces to make it more maintainable. + +Int populateMapListbox( GameWindow *listbox, Bool useSystemMaps, Bool isMultiplayer, AsciiString mapToSelect = AsciiString::TheEmptyString ); /// Read a list of maps from the run directory and fill in the listbox. Return the selected index +Int populateMapListboxNoReset( GameWindow *listbox, Bool useSystemMaps, Bool isMultiplayer, AsciiString mapToSelect = AsciiString::TheEmptyString ); /// Read a list of maps from the run directory and fill in the listbox. Return the selected index +Bool isValidMap( AsciiString mapName, Bool isMultiplayer ); /// Validate a map +Image *getMapPreviewImage( AsciiString mapName ); +AsciiString getDefaultMap( Bool isMultiplayer ); /// Find a valid map +AsciiString getDefaultOfficialMap(); +Bool isOfficialMap( AsciiString mapName ); +Bool parseMapPreviewChunk(DataChunkInput &file, DataChunkInfo *info, void *userData); +void findDrawPositions( Int startX, Int startY, Int width, Int height, Region3D extent, + ICoord2D *ul, ICoord2D *lr ); +Bool WouldMapTransfer( const AsciiString& mapName ); diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/ParabolicEase.h b/Core/GameEngine/Include/GameClient/ParabolicEase.h similarity index 97% rename from GeneralsMD/Code/GameEngine/Include/GameClient/ParabolicEase.h rename to Core/GameEngine/Include/GameClient/ParabolicEase.h index ef13c49a50e..3334def572b 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/ParabolicEase.h +++ b/Core/GameEngine/Include/GameClient/ParabolicEase.h @@ -23,9 +23,9 @@ // Ease in and out based on a parabolic function. // Author: Robert Minsk May 12, 2003 // ============================================================================ + #pragma once -#ifndef _PARABOLICEASE_H -#define _PARABOLICEASE_H + // ============================================================================ #include "Lib/BaseType.h" // ============================================================================ @@ -89,4 +89,3 @@ class ParabolicEase }; // ============================================================================ -#endif // _PARABOLICEASE_H diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/Smudge.h b/Core/GameEngine/Include/GameClient/Smudge.h similarity index 89% rename from GeneralsMD/Code/GameEngine/Include/GameClient/Smudge.h rename to Core/GameEngine/Include/GameClient/Smudge.h index e523c4ea0d8..657736d1f2f 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/Smudge.h +++ b/Core/GameEngine/Include/GameClient/Smudge.h @@ -20,9 +20,6 @@ #pragma once -#ifndef _SMUDGE_H_ -#define _SMUDGE_H_ - #include "WW3D2/dllist.h" #include "WWMath/vector2.h" #include "WWMath/vector3.h" @@ -81,9 +78,9 @@ class SmudgeManager SmudgeSet *addSmudgeSet(void); void removeSmudgeSet(SmudgeSet &mySmudge); - inline Int getSmudgeCountLastFrame(void) {return m_smudgeCountLastFrame;} ///. +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: TerrainRoads.h /////////////////////////////////////////////////////////////////////////// +// Author: Colin Day, December 2001 +// Desc: Terrain road descriptions +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "Common/GameMemory.h" +#include "Common/SubsystemInterface.h" + +#include "GameLogic/Module/BodyModule.h" + + +// FORWARD DECLARATIONS /////////////////////////////////////////////////////////////////////////// +struct FieldParse; +class AsciiString; + +// ------------------------------------------------------------------------------------------------ +/** Bridges have 4 towers around it that the player can attack or use to repair the bridge */ +// ------------------------------------------------------------------------------------------------ +enum BridgeTowerType CPP_11(: Int) +{ + BRIDGE_TOWER_FROM_LEFT = 0, + BRIDGE_TOWER_FROM_RIGHT, + BRIDGE_TOWER_TO_LEFT, + BRIDGE_TOWER_TO_RIGHT, + + BRIDGE_MAX_TOWERS +}; + +// ------------------------------------------------------------------------------------------------ +enum { MAX_BRIDGE_BODY_FX = 3 }; + +//------------------------------------------------------------------------------------------------- +/** Terrain road description, good for roads and bridges */ +//------------------------------------------------------------------------------------------------- +class TerrainRoadType : public MemoryPoolObject +{ + + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( TerrainRoadType, "TerrainRoadType" ) + +public: + + TerrainRoadType( void ); + // destructor prototypes defined by memory pool object + + AsciiString getName( void ) { return m_name; } + AsciiString getTexture( void ) { return m_texture; } + Bool isBridge( void ) { return m_isBridge; } + UnsignedInt getID( void ) { return m_id; } + + Real getRoadWidth( void ) { return m_roadWidth; } + Real getRoadWidthInTexture( void ) { return m_roadWidthInTexture; } + + Real getBridgeScale( void ) { return m_bridgeScale; } + AsciiString getScaffoldObjectName( void ) { return m_scaffoldObjectName; } + AsciiString getScaffoldSupportObjectName( void ) { return m_scaffoldSupportObjectName; } + RGBColor getRadarColor( void ) { return m_radarColor; } + AsciiString getBridgeModel( void ) { return m_bridgeModelName; } + AsciiString getBridgeModelNameDamaged( void ) { return m_bridgeModelNameDamaged; } + AsciiString getBridgeModelNameReallyDamaged( void ) { return m_bridgeModelNameReallyDamaged; } + AsciiString getBridgeModelNameBroken( void ) { return m_bridgeModelNameBroken; } + AsciiString getTextureDamaged( void ) { return m_textureDamaged; } + AsciiString getTextureReallyDamaged( void ) { return m_textureReallyDamaged; } + AsciiString getTextureBroken( void ) { return m_textureBroken; } + AsciiString getTowerObjectName( BridgeTowerType tower ) { return m_towerObjectName[ tower ]; } + AsciiString getDamageToSoundString( BodyDamageType state ) { return m_damageToSoundString[ state ]; } + AsciiString getDamageToOCLString( BodyDamageType state, Int index ) { return m_damageToOCLString[ state ][ index ]; } + AsciiString getDamageToFXString( BodyDamageType state, Int index ) { return m_damageToFXString[ state ][ index ]; } + AsciiString getRepairedToSoundString( BodyDamageType state ) { return m_repairedToSoundString[ state ]; } + AsciiString getRepairedToOCLString( BodyDamageType state, Int index ) { return m_repairedToOCLString[ state ][ index ]; } + AsciiString getRepairedToFXString( BodyDamageType state, Int index ) { return m_repairedToFXString[ state ][ index ]; } + Real getTransitionEffectsHeight( void ) { return m_transitionEffectsHeight; } + Int getNumFXPerType( void ) { return m_numFXPerType; } + + // friend access methods to be used by the road collection only! + void friend_setName( AsciiString name ) { m_name = name; } + void friend_setTexture( AsciiString texture ) { m_texture = texture; } + void friend_setBridge( Bool isBridge ) { m_isBridge = isBridge; } + void friend_setID( UnsignedInt id ) { m_id = id; } + void friend_setNext( TerrainRoadType *next ) { m_next = next; } + TerrainRoadType *friend_getNext( void ) { return m_next; } + void friend_setRoadWidth( Real width ) { m_roadWidth = width; } + void friend_setRoadWidthInTexture( Real width ) { m_roadWidthInTexture = width; } + void friend_setBridgeScale( Real scale ) { m_bridgeScale = scale; } + void friend_setScaffoldObjectName( AsciiString name ) { m_scaffoldObjectName = name; } + void friend_setScaffoldSupportObjectName( AsciiString name ) { m_scaffoldSupportObjectName = name; } + void friend_setBridgeModelName( AsciiString name ) { m_bridgeModelName = name; } + void friend_setBridgeModelNameDamaged( AsciiString name ) { m_bridgeModelNameDamaged = name; } + void friend_setBridgeModelNameReallyDamaged( AsciiString name ) { m_bridgeModelNameReallyDamaged = name; } + void friend_setBridgeModelNameBroken( AsciiString name ) { m_bridgeModelNameBroken = name; } + void friend_setTextureDamaged( AsciiString texture ) { m_textureDamaged = texture; } + void friend_setTextureReallyDamaged( AsciiString texture ) { m_textureReallyDamaged = texture; } + void friend_setTextureBroken( AsciiString texture ) { m_textureBroken = texture; } + void friend_setTowerObjectName( BridgeTowerType tower, AsciiString name ) { m_towerObjectName[ tower ] = name; } + void friend_setDamageToSoundString( BodyDamageType state, AsciiString s ) { m_damageToSoundString[ state ] = s; } + void friend_setDamageToOCLString( BodyDamageType state, Int index, AsciiString s ) { m_damageToOCLString[ state ][ index ] = s; } + void friend_setDamageToFXString( BodyDamageType state, Int index, AsciiString s ) { m_damageToFXString[ state ][ index ] = s; } + void friend_setRepairedToSoundString( BodyDamageType state, AsciiString s ) { m_repairedToSoundString[ state ] = s; } + void friend_setRepairedToOCLString( BodyDamageType state, Int index, AsciiString s ) { m_repairedToOCLString[ state ][ index ] = s; } + void friend_setRepairedToFXString( BodyDamageType state, Int index, AsciiString s ) { m_repairedToFXString[ state ][ index ] = s; } + void friend_setTransitionEffectsHeight( Real height ) { m_transitionEffectsHeight = height; } + void friend_setNumFXPerType( Int num ) { m_numFXPerType = num; } + + /// get the parsing table for INI + const FieldParse *getRoadFieldParse( void ) { return m_terrainRoadFieldParseTable; } + const FieldParse *getBridgeFieldParse( void ) { return m_terrainBridgeFieldParseTable; } + +protected: + + AsciiString m_name; ///< entry name + Bool m_isBridge; ///< true if entry is for a bridge + UnsignedInt m_id; ///< unique id + TerrainRoadType *m_next; ///< next in road list + + // for parsing from INI + static const FieldParse m_terrainRoadFieldParseTable[]; ///< the parse table for INI definition + static const FieldParse m_terrainBridgeFieldParseTable[]; ///< the parse table for INI definition + static void parseTransitionToOCL( INI *ini, void *instance, void *store, const void *userData ); + static void parseTransitionToFX( INI *ini, void *instance, void *store, const void *userData ); + + // + // *note* I would union the road and bridge data, but unions can't have a copy + // constructor such as the AsciiString does + // + + // road data + Real m_roadWidth; ///< width of road + Real m_roadWidthInTexture; ///< width of road in the texture + + // bridge data + Real m_bridgeScale; ///< scale for bridge + + AsciiString m_scaffoldObjectName; ///< scaffold object name + AsciiString m_scaffoldSupportObjectName; ///< scaffold support object name + + RGBColor m_radarColor; ///< color for this bridge on the radar + + AsciiString m_bridgeModelName; ///< model name for bridge + AsciiString m_texture; ///< texture filename + + AsciiString m_bridgeModelNameDamaged; ///< model name for bridge + AsciiString m_textureDamaged; ///< model name for bridge + + AsciiString m_bridgeModelNameReallyDamaged; ///< model name for bridge + AsciiString m_textureReallyDamaged; ///< model name for bridge + + AsciiString m_bridgeModelNameBroken; ///< model name for bridge + AsciiString m_textureBroken; ///< model name for bridge + + AsciiString m_towerObjectName[ BRIDGE_MAX_TOWERS ]; ///< object names for the targetable towers on the bridge + + // + // the following strings are for repair/damage transition events, what sounds to + // play and a collection of OCL and FX lists to play over the bridge area + // + AsciiString m_damageToSoundString[ BODYDAMAGETYPE_COUNT ]; + AsciiString m_damageToOCLString[ BODYDAMAGETYPE_COUNT ][ MAX_BRIDGE_BODY_FX ]; + AsciiString m_damageToFXString[ BODYDAMAGETYPE_COUNT ][ MAX_BRIDGE_BODY_FX ]; + AsciiString m_repairedToSoundString[ BODYDAMAGETYPE_COUNT ]; + AsciiString m_repairedToOCLString[ BODYDAMAGETYPE_COUNT ][ MAX_BRIDGE_BODY_FX ]; + AsciiString m_repairedToFXString[ BODYDAMAGETYPE_COUNT ][ MAX_BRIDGE_BODY_FX ]; + Real m_transitionEffectsHeight; + Int m_numFXPerType; ///< for *each* fx/ocl we will make this many of them on the bridge area + +}; + +//------------------------------------------------------------------------------------------------- +/** Collection of all roads and bridges */ +//------------------------------------------------------------------------------------------------- +class TerrainRoadCollection : public SubsystemInterface +{ + +public: + + TerrainRoadCollection( void ); + ~TerrainRoadCollection( void ); + + void init() { } + void reset() { } + void update() { } + + TerrainRoadType *findRoad( AsciiString name ); ///< find road with matching name + TerrainRoadType *newRoad( AsciiString name ); ///< allocate new road, assing name, and link to list + TerrainRoadType *firstRoad( void ) { return m_roadList; } ///< return first road + TerrainRoadType *nextRoad( TerrainRoadType *road ); ///< get next road + + TerrainRoadType *findBridge( AsciiString name ); ///< find bridge with matching name + TerrainRoadType *newBridge( AsciiString name ); ///< allocate new bridge, assign name, and link + TerrainRoadType *firstBridge( void ) { return m_bridgeList; } ///< return first bridge + TerrainRoadType *nextBridge( TerrainRoadType *bridge ); ///< get next bridge + + TerrainRoadType *findRoadOrBridge( AsciiString name ); ///< search roads and bridges + +protected: + + TerrainRoadType *m_roadList; ///< list of available roads + TerrainRoadType *m_bridgeList; ///< list of available bridges + static UnsignedInt m_idCounter; ///< unique id counter when allocating roads/bridges + +}; + +// EXTERNAL //////////////////////////////////////////////////////////////////////////////////////// +extern TerrainRoadCollection *TheTerrainRoads; diff --git a/Core/GameEngine/Include/GameClient/TerrainVisual.h b/Core/GameEngine/Include/GameClient/TerrainVisual.h new file mode 100644 index 00000000000..8463ff83ab1 --- /dev/null +++ b/Core/GameEngine/Include/GameClient/TerrainVisual.h @@ -0,0 +1,310 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: TerrainVisual.h ////////////////////////////////////////////////////////////////////////// +// Interface for visual representation of terrain on the client +// Author: Colin Day, April 2001 +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "Common/Terrain.h" +#include "Common/Snapshot.h" +#include "Common/MapObject.h" + +// FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// +class TerrainType; +class WaterHandle; +class Matrix3D; +class Object; +class Drawable; +class GeometryInfo; + + +class WorldHeightMap; +struct SeismicSimulationNode; +class SeismicSimulationFilterBase; + + +#define DEFAULT_SEISMIC_SIMULATION_MAGNITUDE (20.0f) +struct SeismicSimulationNode; // just a forward declaration folks, no cause for alarm +class SeismicSimulationFilterBase +{ +public: + enum SeismicSimStatusCode CPP_11(: Int) + { + SEISMIC_STATUS_INVALID, + SEISMIC_STATUS_ACTIVE, + SEISMIC_STATUS_ZERO_ENERGY, + }; + + virtual SeismicSimStatusCode filterCallback( WorldHeightMapInterfaceClass *heightMap, const SeismicSimulationNode *node ) = 0; + virtual Real applyGravityCallback( Real velocityIn ) = 0; +}; + +struct SeismicSimulationNode +{ + SeismicSimulationNode() + { + m_center.x = 0; + m_center.y = 0; + m_radius = 0; + m_region.lo.x = 0; + m_region.lo.y = 0; + m_region.hi.x = 0; + m_region.hi.y = 0; + m_clean = FALSE; + callbackFilter = NULL; + m_life = 0; + m_magnitude = DEFAULT_SEISMIC_SIMULATION_MAGNITUDE; + + } + SeismicSimulationNode( const SeismicSimulationNode &ssn ) + { + m_center.x = ssn.m_center.x; + m_center.y = ssn.m_center.y; + m_radius = ssn.m_radius; + m_region.lo.x = ssn.m_region.lo.x; + m_region.lo.y = ssn.m_region.lo.y; + m_region.hi.x = ssn.m_region.hi.x; + m_region.hi.y = ssn.m_region.hi.y; + m_clean = ssn.m_clean; + callbackFilter= ssn.callbackFilter; + m_life = ssn.m_life; + m_magnitude = ssn.m_magnitude; + + } + SeismicSimulationNode( const Coord3D* ctr, Real rad, Real mag, SeismicSimulationFilterBase *cbf = NULL ) + { + m_center.x = REAL_TO_INT_FLOOR(ctr->x/MAP_XY_FACTOR); + m_center.y = REAL_TO_INT_FLOOR(ctr->y/MAP_XY_FACTOR); + m_radius = (rad-1)/MAP_XY_FACTOR; + UnsignedInt regionSize = rad/MAP_XY_FACTOR; + m_region.lo.x = m_center.x - regionSize; + m_region.lo.y = m_center.y - regionSize; + m_region.hi.x = m_center.x + regionSize; + m_region.hi.y = m_center.y + regionSize; + m_clean = false; + callbackFilter= cbf; + m_life = 0; + m_magnitude = mag; + + } + + SeismicSimulationFilterBase::SeismicSimStatusCode handleFilterCallback( WorldHeightMapInterfaceClass *heightMap ) + { + if ( callbackFilter == NULL ) + return SeismicSimulationFilterBase::SEISMIC_STATUS_INVALID; + + ++m_life; + + return callbackFilter->filterCallback( heightMap, this ); + } + + Real applyGravity( Real velocityIn ) + { + DEBUG_ASSERTCRASH( callbackFilter, ("SeismicSimulationNode::applyGravity() has no callback filter!") ); + + if ( callbackFilter == NULL ) + return velocityIn;//oops, we have no callback! + + return callbackFilter->applyGravityCallback( velocityIn ); + + } + + IRegion2D m_region; + ICoord2D m_center; + Bool m_clean; + Real m_magnitude; + UnsignedInt m_radius; + UnsignedInt m_life; + + SeismicSimulationFilterBase *callbackFilter; + +}; +typedef std::list SeismicSimulationList; +typedef SeismicSimulationList::iterator SeismicSimulationListIt; + +class DomeStyleSeismicFilter : public SeismicSimulationFilterBase +{ + virtual SeismicSimStatusCode filterCallback( WorldHeightMapInterfaceClass *heightMap, const SeismicSimulationNode *node ); + virtual Real applyGravityCallback( Real velocityIn ); +}; + + +//------------------------------------------------------------------------------------------------- +/** LOD values for terrain, keep this in sync with TerrainLODNames[] */ +//------------------------------------------------------------------------------------------------- +typedef enum _TerrainLOD CPP_11(: Int) +{ + TERRAIN_LOD_INVALID, + TERRAIN_LOD_MIN, // note that this is less than max + TERRAIN_LOD_STRETCH_NO_CLOUDS, + TERRAIN_LOD_HALF_CLOUDS, + TERRAIN_LOD_NO_CLOUDS, + TERRAIN_LOD_STRETCH_CLOUDS, + TERRAIN_LOD_NO_WATER, + TERRAIN_LOD_MAX, // note that this is larger than min + TERRAIN_LOD_AUTOMATIC, + TERRAIN_LOD_DISABLE, + + TERRAIN_LOD_NUM_TYPES + +} TerrainLOD; +#ifdef DEFINE_TERRAIN_LOD_NAMES +static const char *const TerrainLODNames[] = +{ + "NONE", + "MIN", + "STRETCH_NO_CLOUDS", + "HALF_CLOUDS", + "NO_CLOUDS", + "STRETCH_CLOUDS", + "NO_WATER", + "MAX", + "AUTOMATIC", + "DISABLE", + + NULL +}; +static_assert(ARRAY_SIZE(TerrainLODNames) == TERRAIN_LOD_NUM_TYPES + 1, "Incorrect array size"); +#endif // end DEFINE_TERRAIN_LOD_NAMES + +//------------------------------------------------------------------------------------------------- +/** Device independent implementation for visual terrain */ +//------------------------------------------------------------------------------------------------- +class TerrainVisual : public Snapshot, + public SubsystemInterface +{ + +public: + + enum {NumSkyboxTextures = 5}; + + TerrainVisual(); + virtual ~TerrainVisual(); + + virtual void init( void ); + virtual void reset( void ); + virtual void update( void ); + + virtual Bool load( AsciiString filename ); + + /// get color of texture on the terrain at location specified + virtual void getTerrainColorAt( Real x, Real y, RGBColor *pColor ) = 0; + + /// get the terrain tile type at the world location in the (x,y) plane ignoring Z + virtual TerrainType *getTerrainTile( Real x, Real y ) = 0; + + /** intersect the ray with the terrain, if a hit occurs TRUE is returned + and the result point on the terrain is returned in "result" */ + virtual Bool intersectTerrain( Coord3D *rayStart, + Coord3D *rayEnd, + Coord3D *result ) { return FALSE; } + + // + // water methods + // + virtual void enableWaterGrid( Bool enable ) = 0; + /// set min/max height values allowed in water grid pointed to by waterTable + virtual void setWaterGridHeightClamps( const WaterHandle *waterTable, Real minZ, Real maxZ ) = 0; + /// adjust fallof parameters for grid change method + virtual void setWaterAttenuationFactors( const WaterHandle *waterTable, Real a, Real b, Real c, Real range ) = 0; + /// set the water table position and orientation in world space + virtual void setWaterTransform( const WaterHandle *waterTable, Real angle, Real x, Real y, Real z ) = 0; + virtual void setWaterTransform( const Matrix3D *transform ) = 0; + /// get water transform parameters + virtual void getWaterTransform( const WaterHandle *waterTable, Matrix3D *transform ) = 0; + /// water grid resolution spacing + virtual void setWaterGridResolution( const WaterHandle *waterTable, Real gridCellsX, Real gridCellsY, Real cellSize ) = 0; + virtual void getWaterGridResolution( const WaterHandle *waterTable, Real *gridCellsX, Real *gridCellsY, Real *cellSize ) = 0; + /// adjust the water grid in world coords by the delta + virtual void changeWaterHeight( Real x, Real y, Real delta ) = 0; + /// adjust the velocity at a water grid point corresponding to the world x,y + virtual void addWaterVelocity( Real worldX, Real worldY, Real velocity, Real preferredHeight ) = 0; + /// get height of water grid at specified position + virtual Bool getWaterGridHeight( Real worldX, Real worldY, Real *height) = 0; + + /// set detail of terrain tracks. + virtual void setTerrainTracksDetail(void)=0; + virtual void setShoreLineDetail(void)=0; + + /// Add a bib for an object at location. + virtual void addFactionBib(Object *factionBuilding, Bool highlight, Real extra = 0)=0; + /// Remove a bib. + virtual void removeFactionBib(Object *factionBuilding)=0; + + /// Add a bib for a drawable at location. + virtual void addFactionBibDrawable(Drawable *factionBuilding, Bool highlight, Real extra = 0)=0; + /// Remove a bib. + virtual void removeFactionBibDrawable(Drawable *factionBuilding)=0; + + virtual void removeAllBibs(void)=0; + virtual void removeBibHighlighting(void)=0; + + virtual void removeTreesAndPropsForConstruction( + const Coord3D* pos, + const GeometryInfo& geom, + Real angle + ) = 0; + + virtual void addProp(const ThingTemplate *tt, const Coord3D *pos, Real angle) = 0; + + // + // Modify height. + // + virtual void setRawMapHeight(const ICoord2D *gridPos, Int height)=0; + virtual Int getRawMapHeight(const ICoord2D *gridPos)=0; + + + //////////////////////////////////////////////////// + //////////////////////////////////////////////////// + //////////////////////////////////////////////////// +#ifdef DO_SEISMIC_SIMULATIONS + virtual void updateSeismicSimulations( void ) = 0; /// walk the SeismicSimulationList and, well, do it. + virtual void addSeismicSimulation( const SeismicSimulationNode& sim ) = 0; +#endif + virtual WorldHeightMap* getLogicHeightMap( void ) {return NULL;}; + virtual WorldHeightMap* getClientHeightMap( void ) {return NULL;}; + //////////////////////////////////////////////////// + //////////////////////////////////////////////////// + //////////////////////////////////////////////////// + + + /// Replace the skybox texture + virtual void replaceSkyboxTextures(const AsciiString *oldTexName[NumSkyboxTextures], const AsciiString *newTexName[NumSkyboxTextures])=0; + +protected: + + // snapshot methods + virtual void crc( Xfer *xfer ); + virtual void xfer( Xfer *xfer ); + virtual void loadPostProcess( void ); + + AsciiString m_filenameString; ///< file with terrain data + +}; + +// EXTERNALS ////////////////////////////////////////////////////////////////////////////////////// +extern TerrainVisual *TheTerrainVisual; ///< singleton extern diff --git a/Core/GameEngine/Include/GameClient/VideoPlayer.h b/Core/GameEngine/Include/GameClient/VideoPlayer.h new file mode 100644 index 00000000000..ce127e89036 --- /dev/null +++ b/Core/GameEngine/Include/GameClient/VideoPlayer.h @@ -0,0 +1,311 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +//---------------------------------------------------------------------------- +// +// Westwood Studios Pacific. +// +// Confidential Information +// Copyright (C) 2001 - All Rights Reserved +// +//---------------------------------------------------------------------------- +// +// Project: Generals +// +// File name: GameClient/VideoPlayer.h +// +// Created: 10/22/01 +// +//---------------------------------------------------------------------------- + +#pragma once + +//---------------------------------------------------------------------------- +// Includes +//---------------------------------------------------------------------------- + +#include +#include "WWMath/rect.h" +#include "Common/SubsystemInterface.h" +#include "Common/AsciiString.h" +#include "Common/INI.h" +#include "Common/STLTypedefs.h" + +//---------------------------------------------------------------------------- +// Forward References +//---------------------------------------------------------------------------- + +struct Video; +class VideoPlayer; + +//---------------------------------------------------------------------------- +// Type Defines +//---------------------------------------------------------------------------- +typedef std::vector