diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index efa2369a..5ee83df6 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -10,7 +10,7 @@ jobs: steps: - name: Create Development release if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/vanilla' }} - uses: "marvinpinto/action-automatic-releases@latest" + uses: "0xDylan/action-auto-releases-n20@v1.1" with: repo_token: "${{ secrets.GITHUB_TOKEN }}" automatic_release_tag: "latest" @@ -100,7 +100,7 @@ jobs: - name: Upload development release if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/vanilla' && matrix.networking == 'net' }} - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: name: Development Build tag_name: "latest" @@ -112,7 +112,7 @@ jobs: - name: Upload tagged release if: ${{ startsWith(github.ref, 'refs/tags/') && matrix.networking == 'net' }} - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: files: | artifact/* diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index f1e706c4..e2b7cd1f 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -30,7 +30,7 @@ jobs: - name: Restore cache id: dep-cache - uses: actions/cache@v3 + uses: actions/cache@v4 env: cache-name: cache-dependencies with: @@ -95,7 +95,7 @@ jobs: - name: Upload development release if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/vanilla' && matrix.networking == 'net' }} - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: name: Development Build tag_name: "latest" @@ -107,7 +107,7 @@ jobs: - name: Upload tagged release if: ${{ startsWith(github.ref, 'refs/tags/') && matrix.networking == 'net' }} - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: files: | artifact/* diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index e91433f5..bc92d6dd 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -126,7 +126,7 @@ jobs: - name: Upload development release if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/vanilla' && matrix.networking == 'net' }} - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: name: Development Build tag_name: "latest" @@ -138,7 +138,7 @@ jobs: - name: Upload tagged release if: ${{ startsWith(github.ref, 'refs/tags/') && matrix.networking == 'net' }} - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: files: | artifact/* diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index fa676ac7..b6cc3c11 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -14,13 +14,16 @@ jobs: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} - - uses: ilammy/msvc-dev-cmd@v1.12.1 + - uses: ilammy/msvc-dev-cmd@v1.13.0 with: arch: x86 - name: Set Git Info id: gitinfo - run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + shell: pwsh + run: | + $gitoutput = git rev-parse --short HEAD + echo "sha_short=${gitoutput}" >> $env:GITHUB_OUTPUT - name: Install Dependencies run: | @@ -54,7 +57,7 @@ jobs: - name: Upload development release if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/vanilla' }} - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: name: Development Build tag_name: "latest" @@ -66,7 +69,7 @@ jobs: - name: Upload tagged release if: ${{ startsWith(github.ref, 'refs/tags/') }} - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: files: | artifact/* @@ -86,13 +89,16 @@ jobs: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} - - uses: ilammy/msvc-dev-cmd@v1.12.1 + - uses: ilammy/msvc-dev-cmd@v1.13.0 with: arch: ${{ matrix.platform }} - name: Set Git Info id: gitinfo - run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + shell: pwsh + run: | + $gitoutput = git rev-parse --short HEAD + echo "sha_short=${gitoutput}" >> $env:GITHUB_OUTPUT - name: Set variables id: vars @@ -152,7 +158,7 @@ jobs: - name: Upload development release if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/vanilla' && matrix.networking == 'net' }} - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: name: Development Build tag_name: "latest" @@ -164,7 +170,7 @@ jobs: - name: Upload tagged release if: ${{ startsWith(github.ref, 'refs/tags/') && matrix.networking == 'net' }} - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: files: | artifact/* diff --git a/CMakeLists.txt b/CMakeLists.txt index a7c8eec9..2a221f93 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ include(FeatureSummary) include(CheckCXXCompilerFlag) if(NOT DEFINED CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo" FORCE) + set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo" FORCE) endif() project(VanillaConquer C CXX) @@ -85,10 +85,34 @@ else() endif() if(WIN32) - add_definitions(-DWIN32 -D_WINDOWS -D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE) + add_definitions(-DWIN32 -D_WINDOWS -D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE -DNOMINMAX) set(COMMON_LIBS winmm) endif() +if(NOT MSVC) + # GCC and Clang don't check new allocations by default while MSVC does. + message(STATUS "Checking whether compiler supports -fcheck-new") + check_cxx_compiler_flag("-Werror -fcheck-new" HAVE_CHECK_NEW) + + if(HAVE_CHECK_NEW) + message(STATUS "yes") + list(APPEND VC_CXX_FLAGS -fcheck-new) + else() + message(STATUS "no") + endif() + + # Some platforms default to an unsigned char, this fixes that. + message(STATUS "Checking whether compiler supports -fsigned-char") + check_cxx_compiler_flag("-Werror -fsigned-char" HAVE_SIGNED_CHAR) + + if(HAVE_SIGNED_CHAR) + message(STATUS "yes") + list(APPEND VC_CXX_FLAGS -fsigned-char) + else() + message(STATUS "no") + endif() +endif() + set(VANILLA_DEFS "") set(VANILLA_LIBS "") diff --git a/CMakePresets.json b/CMakePresets.json index 2290a57d..79aa6238 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -108,9 +108,9 @@ "cacheVariables": { "CMAKE_CXX_FLAGS_DEBUG": "-g3 -Og", "CMAKE_C_FLAGS_DEBUG": "-g3 -Og", - "CMAKE_CXX_FLAGS_RELEASE": "-O3 -g3 -DNDEBUG", - "CMAKE_C_FLAGS_RELEASE": "-O3 -g3 -DNDEBUG", - "VC_CXX_FLAGS": "-w;-Wwrite-strings;-Werror=write-strings;-fcheck-new;-DNOMINMAX", + "CMAKE_CXX_FLAGS_RELEASE": "-O2 -g3 -DNDEBUG", + "CMAKE_C_FLAGS_RELEASE": "-O2 -g3 -DNDEBUG", + "VC_CXX_FLAGS": "-w;-Wwrite-strings;-Werror=write-strings", "MAP_EDITORTD": "ON", "MAP_EDITORRA": "ON", "BUILD_TOOLS": "ON" @@ -174,11 +174,11 @@ "cacheVariables": { "CMAKE_CXX_FLAGS_DEBUG": "-g -Og", "CMAKE_C_FLAGS_DEBUG": "-g -Og", - "CMAKE_CXX_FLAGS_RELEASE": "-O3 -g -DNDEBUG", - "CMAKE_C_FLAGS_RELEASE": "-O3 -g -DNDEBUG", + "CMAKE_CXX_FLAGS_RELEASE": "-O2 -g -DNDEBUG", + "CMAKE_C_FLAGS_RELEASE": "-O2 -g -DNDEBUG", "CMAKE_EXE_LINKER_FLAGS": "-static-libstdc++ -static-libgcc", "CMAKE_SHARESD_LINKER_FLAGS": "-static-libstdc++ -static-libgcc", - "VC_CXX_FLAGS": "-fpermissive;-w;-Wwrite-strings;-Werror=write-strings;-fcheck-new;-fsigned-char;-DNOMINMAX", + "VC_CXX_FLAGS": "-w;-Wwrite-strings;-Werror=write-strings", "MAP_EDITORTD": "ON", "MAP_EDITORRA": "ON", "BUILD_TOOLS": "ON" @@ -199,6 +199,7 @@ "BUILD_VANILLARA": "OFF", "MAP_EDITORTD": "OFF", "MAP_EDITORRA": "OFF", + "VC_CXX_FLAGS": "-fpermissive;-w;-Wwrite-strings;-Werror=write-strings", "BUILD_TOOLS": "OFF" } }, diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 14599075..cbe2a34b 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -112,6 +112,7 @@ set(COMMONR_SRC soundio_null.cpp video_null.cpp wwkeyboard.cpp + wwkeyboard_win32.cpp wwmouse.cpp ) @@ -134,18 +135,18 @@ if(DSOUND) list(APPEND COMMONV_SRC soundio.cpp vqaaudio_dsound.cpp) list(APPEND VANILLA_LIBS dsound) elseif(OPENAL) - list(APPEND COMMONV_SRC soundio_openal.cpp vqaaudio_openal.cpp) + list(APPEND COMMONV_SRC soundio_common.cpp soundio_openal.cpp vqaaudio_openal.cpp) else() list(APPEND COMMONV_SRC soundio_null.cpp vqaaudio_null.cpp) endif() if(DDRAW) - list(APPEND COMMONV_SRC video_ddraw.cpp) + list(APPEND COMMONV_SRC video_ddraw.cpp wwkeyboard_win32.cpp) list(APPEND VANILLA_LIBS ddraw) elseif(SDL2) - list(APPEND COMMONV_SRC video_sdl2.cpp) + list(APPEND COMMONV_SRC video_sdl2.cpp wwkeyboard_sdl2.cpp) elseif(SDL1) - list(APPEND COMMONV_SRC video_sdl1.cpp) + list(APPEND COMMONV_SRC video_sdl1.cpp wwkeyboard_sdl1.cpp) else() list(APPEND COMMONV_SRC video_null.cpp) endif() diff --git a/common/keybuff.cpp b/common/keybuff.cpp index c69e9eef..1f9575d4 100644 --- a/common/keybuff.cpp +++ b/common/keybuff.cpp @@ -9,6 +9,7 @@ // distributed with this program. You should have received a copy of the // GNU General Public License along with permitted additional restrictions // with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection +#include "debugstring.h" #include "graphicsviewport.h" #include "keyframe.h" #include "shape.h" @@ -29,7 +30,10 @@ extern bool OriginalUseBigShapeBuffer; // with the value PredTable[PredFrame] pixels away if PartialCount // is greater than or equal to 256. After every pixel, it is increased by // PartialPred and reset to % 256 after reaching 256 or greater. -static const short PredTable[8] = {1, 3, 2, 5, 2, 3, 4, 1}; +static const short PredNegTable[8] = {-1, -3, -2, -5, -2, -3, -4, -1}; +static short PredNegOffTable[8] = {0, 0, 0, 0, 0, 0, 0, 0}; +static const short PredPosTable[8] = {1, 3, 2, 5, 2, 3, 4, 1}; +static const short* PredTable = PredPosTable; static unsigned PredFrame; static unsigned PartialCount; static unsigned PartialPred; @@ -172,7 +176,7 @@ void BF_Fading(int width, { while (height--) { for (int i = width; i > 0; --i) { - unsigned char sbyte = *src; + unsigned char sbyte = *src++; for (int i = 0; i < count; ++i) { sbyte = fade_tab[sbyte]; @@ -180,6 +184,9 @@ void BF_Fading(int width, *dst++ = sbyte; } + + src += src_pitch; + dst += dst_pitch; } } @@ -298,8 +305,6 @@ void BF_Predator(int width, for (int i = width; i > 0; --i) { PartialCount += PartialPred; - // if ( PartialCount & 0xFF00 ) { - // PartialCount &= 0xFFFF00FF; if (PartialCount >= 256) { PartialCount %= 256; @@ -307,13 +312,12 @@ void BF_Predator(int width, *dst = dst[PredTable[PredFrame]]; } - PredFrame = (PredFrame + 2) % 8; + PredFrame = (PredFrame + 1) % 8; } ++dst; } - src += src_pitch + width; dst += dst_pitch; } } @@ -340,9 +344,11 @@ void BF_Predator_Trans(int width, if (&dst[PredTable[PredFrame]] < PredatorLimit) { sbyte = dst[PredTable[PredFrame]]; + } else { + sbyte = *dst; } - PredFrame = (PredFrame + 2) % 8; + PredFrame = (PredFrame + 1) % 8; } *dst = sbyte; @@ -377,9 +383,11 @@ void BF_Predator_Ghost(int width, if (&dst[PredTable[PredFrame]] < PredatorLimit) { sbyte = dst[PredTable[PredFrame]]; + } else { + sbyte = *dst; } - PredFrame = (PredFrame + 2) % 8; + PredFrame = (PredFrame + 1) % 8; } unsigned char fbyte = ghost_lookup[sbyte]; @@ -418,9 +426,11 @@ void BF_Predator_Ghost_Trans(int width, if (&dst[PredTable[PredFrame]] < PredatorLimit) { sbyte = dst[PredTable[PredFrame]]; + } else { + sbyte = *dst; } - PredFrame = (PredFrame + 2) % 8; + PredFrame = (PredFrame + 1) % 8; } unsigned char fbyte = ghost_lookup[sbyte]; @@ -461,9 +471,11 @@ void BF_Predator_Fading(int width, if (&dst[PredTable[PredFrame]] < PredatorLimit) { sbyte = dst[PredTable[PredFrame]]; + } else { + sbyte = *dst; } - PredFrame = (PredFrame + 2) % 8; + PredFrame = (PredFrame + 1) % 8; } for (int i = 0; i < count; ++i) { @@ -497,9 +509,11 @@ void BF_Predator_Fading_Trans(int width, if (&dst[PredTable[PredFrame]] < PredatorLimit) { sbyte = dst[PredTable[PredFrame]]; + } else { + sbyte = *dst; } - PredFrame = (PredFrame + 2) % 8; + PredFrame = (PredFrame + 1) % 8; } for (int i = 0; i < count; ++i) { @@ -539,9 +553,11 @@ void BF_Predator_Ghost_Fading(int width, if (&dst[PredTable[PredFrame]] < PredatorLimit) { sbyte = dst[PredTable[PredFrame]]; + } else { + sbyte = *dst; } - PredFrame = (PredFrame + 2) % 8; + PredFrame = (PredFrame + 1) % 8; } unsigned char fbyte = ghost_lookup[sbyte]; @@ -585,9 +601,11 @@ void BF_Predator_Ghost_Fading_Trans(int width, if (&dst[PredTable[PredFrame]] < PredatorLimit) { sbyte = dst[PredTable[PredFrame]]; + } else { + sbyte = *dst; } - PredFrame = (PredFrame + 2) % 8; + PredFrame = (PredFrame + 1) % 8; } unsigned char fbyte = ghost_lookup[sbyte]; @@ -858,7 +876,7 @@ void Single_Line_Predator(int width, *dst = dst[PredTable[PredFrame]]; } - PredFrame = (PredFrame + 2) % 8; + PredFrame = (PredFrame + 1) % 8; } ++dst; @@ -883,9 +901,11 @@ void Single_Line_Predator_Trans(int width, if (&dst[PredTable[PredFrame]] < PredatorLimit) { sbyte = dst[PredTable[PredFrame]]; + } else { + sbyte = *dst; } - PredFrame = (PredFrame + 2) % 8; + PredFrame = (PredFrame + 1) % 8; } *dst = sbyte; @@ -912,9 +932,11 @@ void Single_Line_Predator_Ghost(int width, if (&dst[PredTable[PredFrame]] < PredatorLimit) { sbyte = dst[PredTable[PredFrame]]; + } else { + sbyte = *dst; } - PredFrame = (PredFrame + 2) % 8; + PredFrame = (PredFrame + 1) % 8; } unsigned char fbyte = ghost_lookup[sbyte]; @@ -945,9 +967,11 @@ void Single_Line_Predator_Ghost_Trans(int width, if (&dst[PredTable[PredFrame]] < PredatorLimit) { sbyte = dst[PredTable[PredFrame]]; + } else { + sbyte = *dst; } - PredFrame = (PredFrame + 2) % 8; + PredFrame = (PredFrame + 1) % 8; } unsigned char fbyte = ghost_lookup[sbyte]; @@ -980,9 +1004,11 @@ void Single_Line_Predator_Fading(int width, if (&dst[PredTable[PredFrame]] < PredatorLimit) { sbyte = dst[PredTable[PredFrame]]; + } else { + sbyte = *dst; } - PredFrame = (PredFrame + 2) % 8; + PredFrame = (PredFrame + 1) % 8; } for (int i = 0; i < count; ++i) { @@ -1011,9 +1037,11 @@ void Single_Line_Predator_Fading_Trans(int width, if (&dst[PredTable[PredFrame]] < PredatorLimit) { sbyte = dst[PredTable[PredFrame]]; + } else { + sbyte = *dst; } - PredFrame = (PredFrame + 2) % 8; + PredFrame = (PredFrame + 1) % 8; } for (int i = 0; i < count; ++i) { @@ -1045,9 +1073,11 @@ void Single_Line_Predator_Ghost_Fading(int width, if (&dst[PredTable[PredFrame]] < PredatorLimit) { sbyte = dst[PredTable[PredFrame]]; + } else { + sbyte = *dst; } - PredFrame = (PredFrame + 2) % 8; + PredFrame = (PredFrame + 1) % 8; } unsigned char fbyte = ghost_lookup[sbyte]; @@ -1083,9 +1113,11 @@ void Single_Line_Predator_Ghost_Fading_Trans(int width, if (&dst[PredTable[PredFrame]] < PredatorLimit) { sbyte = dst[PredTable[PredFrame]]; + } else { + sbyte = *dst; } - PredFrame = (PredFrame + 2) % 8; + PredFrame = (PredFrame + 1) % 8; } unsigned char fbyte = ghost_lookup[sbyte]; @@ -1302,6 +1334,17 @@ void Buffer_Frame_To_Page(int x, int current_frame = va_arg(ap, unsigned); blit_style |= 8; + if (current_frame < 0) { + + for (int i = 0; i < 8; ++i) { + PredNegOffTable[i] = + PredNegTable[i] + viewport.Get_XAdd() + viewport.Get_Width() + viewport.Get_Pitch(); + } + PredTable = PredNegOffTable; + } else { + PredTable = PredPosTable; + } + PredFrame = ((unsigned)current_frame) % 8; PartialCount = 0; PartialPred = 256; diff --git a/common/packet.cpp b/common/packet.cpp index 010ae72c..da29423f 100644 --- a/common/packet.cpp +++ b/common/packet.cpp @@ -38,7 +38,7 @@ #include "packet.h" #include "endianness.h" -#ifdef NOMINMAX +#if !defined _WIN32 || defined NOMINMAX inline int min(int a, int b) { return a < b ? a : b; diff --git a/common/region.h b/common/region.h index 3085328e..0cb5ab05 100644 --- a/common/region.h +++ b/common/region.h @@ -38,9 +38,8 @@ class RegionClass { public: - RegionClass(void) - { - Threat = 0; + RegionClass(void){ + //Threat = 0; }; ~RegionClass(void){}; int operator!=(RegionClass const& region) @@ -75,6 +74,10 @@ class RegionClass { return Threat; }; + void Init() + { + Threat = 0; + } protected: int Threat; diff --git a/common/soundio_common.cpp b/common/soundio_common.cpp new file mode 100644 index 00000000..4597ca65 --- /dev/null +++ b/common/soundio_common.cpp @@ -0,0 +1,1132 @@ +// TiberianDawn.DLL and RedAlert.dll and corresponding source code 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. + +// TiberianDawn.DLL and RedAlert.dll and corresponding source code is distributed +// in the hope that it will be useful, but with permitted additional restrictions +// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT +// distributed with this program. You should have received a copy of the +// GNU General Public License along with permitted additional restrictions +// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection +#include "audio.h" +#include "auduncmp.h" +#include "endianness.h" +#include "file.h" +#include "memflag.h" +#include "soscomp.h" +#include "sound.h" +#include "soundio_imp.h" +#include +#include + +enum +{ + AUD_CHUNK_MAGIC_ID = 0x0000DEAF, + VOLUME_MIN = 0, + VOLUME_MAX = 255, + PRIORITY_MIN = 0, + PRIORITY_MAX = 255, + MAX_SAMPLE_TRACKERS = 5, // C&C issue where sounds get cut off is because of the small number of trackers. + STREAM_BUFFER_COUNT = 16, + BUFFER_CHUNK_SIZE = 8192, // 256 * 32, + UNCOMP_BUFFER_SIZE = 2098, + BUFFER_TOTAL_BYTES = BUFFER_CHUNK_SIZE * 4, // 32 kb + TIMER_DELAY = 25, + TIMER_RESOLUTION = 1, + TIMER_TARGET_RESOLUTION = 10, // 10-millisecond target resolution + INVALID_AUDIO_HANDLE = -1, + INVALID_FILE_HANDLE = -1, +}; + +/* +** Define the different type of sound compression avaliable to the westwood +** library. +*/ +typedef enum +{ + SCOMP_NONE = 0, // No compression -- raw data. + SCOMP_WESTWOOD = 1, // Special sliding window delta compression. + SCOMP_SOS = 99 // SOS frame compression. +} SCompressType; + +struct SampleTrackerType +{ + /* + ** This flags whether this sample structure is active or not. + */ + bool Active; + + /* + ** This flags whether the sample is loading or has been started. + */ + bool Loading; + + /* + ** If this sample is really to be considered a score rather than + ** a sound effect, then special rules apply. These largely fall into + ** the area of volume control. + */ + bool IsScore; + + /* + ** This is the original sample pointer. It is used to control the sample based on + ** pointer rather than handle. The handle method is necessary when more than one + ** sample could be playing simultaneously. The pointer method is necessary when + ** the dealing with a sample that may have stopped behind the programmer's back and + ** this occurance is not otherwise determinable. It is also used in + ** conjunction with original size to unlock a sample which has been DPMI + ** locked. + */ + const void* Original; + int OriginalSize; + + /* + ** Variable to keep track of the playback rate of this buffer + */ + int Frequency; + + /* + ** Variable to keep track of the sample type ( 8 or 16 bit ) of this buffer + */ + int BitsPerSample; + + /* + ** Variable to keep track of the stereo ability of this buffer + */ + bool Stereo; + + /* + ** Samples maintain a priority which is used to determine + ** which sounds live or die when the maximum number of + ** sounds are being played. + */ + int Priority; + + /* + ** This is the current volume of the sample as it is being played. + */ + int Volume; + int Reducer; // Amount to reduce volume per tick. + + /* + ** This is the compression that the sound data is using. + */ + SCompressType Compression; + + /* + ** This flag indicates whether this sample needs servicing. + ** Servicing entails filling one of the empty low buffers. + */ + short Service; + + /* + ** This flag is true when the sample has stopped playing, + ** BUT there is more data available. The sample must be + ** restarted upon filling the low buffer. + */ + bool Restart; + + /* + ** Streaming control handlers. + */ + bool (*Callback)(short id, short* odd, void** buffer, int* size); + int FilePending; // Number of buffers already filled ahead. + int FilePendingSize; // Number of bytes in last filled buffer. + short Odd; // Block number tracker (0..StreamBufferCount-1). + void* QueueBuffer; // Pointer to continued sample data. + int QueueSize; // Size of queue buffer attached. + + /* + ** The file variables are used when streaming directly off of the + ** hard drive. + */ + int FileHandle; // Streaming file handle (INVALID_FILE_HANDLE = not in use). + void* FileBuffer; + + /* + ** The following structure is used if the sample if compressed using + ** the sos 16 bit compression Codec. + */ + _SOS_COMPRESS_INFO sosinfo; + + /* + ** This flag indicates that there is more source data + ** to copy to the play buffer + ** + */ + bool MoreSource; + + /* + ** This flag indicates that the entire sample fitted inside the + ** direct sound secondary buffer + ** + */ + bool OneShot; + + /* + ** Pointer to the sound data that has not yet been copied + ** to the playback buffers. + */ + void* Source; + + /* + ** This is the number of bytes remaining in the source data as + ** pointed to by the "Source" element. + */ + int Remainder; + + struct SampleTrackerTypeImp* Imp; +}; + +struct LockedDataType +{ + unsigned int DigiHandle; // = -1; + bool ServiceSomething; // = false; + unsigned MagicNumber; // = 0xDEAF; + void* UncompBuffer; // = NULL; + int StreamBufferSize; // = (2*SECONDARY_BUFFER_SIZE)+128; + short StreamBufferCount; // = 32; + SampleTrackerType SampleTracker[MAX_SAMPLE_TRACKERS]; + unsigned SoundVolume; + unsigned ScoreVolume; + int VolumeLock; +}; + +void (*Audio_Focus_Loss_Function)() = nullptr; + +static struct LockedDataType LockedData; +SFX_Type SoundType; +Sample_Type SampleType; +static void* FileStreamBuffer = nullptr; +bool StreamLowImpact = false; +static bool StartingFileStream = false; +static bool volatile AudioDone = false; +extern bool GameInFocus; +static uint8_t ChunkBuffer[BUFFER_CHUNK_SIZE]; + +bool Any_Locked(); // From each games winstub.cpp at the moment. +static int Get_Free_Sample_Handle(int priority); +static void Maintenance_Callback(); +static int Play_Sample_Handle(const void* sample, int priority, int volume, signed short panloc, int id); +static int Sample_Read(int fh, void* buffer, int size); + +static void Init_Locked_Data() +{ + LockedData.DigiHandle = INVALID_AUDIO_HANDLE; + LockedData.ServiceSomething = false; + LockedData.MagicNumber = AUD_CHUNK_MAGIC_ID; + LockedData.UncompBuffer = 0; + LockedData.StreamBufferSize = BUFFER_CHUNK_SIZE + 128; + LockedData.StreamBufferCount = STREAM_BUFFER_COUNT; + LockedData.SoundVolume = VOLUME_MAX; + LockedData.ScoreVolume = VOLUME_MAX; +} + +static int Simple_Copy(void** source, int* ssize, void** alternate, int* altsize, void** dest, int size) +{ + int out = 0; + + if (*ssize == 0) { + *source = *alternate; + *ssize = *altsize; + *alternate = nullptr; + *altsize = 0; + } + + if (*source == nullptr || *ssize == 0) { + return out; + } + + int s = size; + + if (*ssize < size) { + s = *ssize; + } + + memcpy(*dest, *source, s); + *source = static_cast(*source) + s; + *ssize -= s; + *dest = static_cast(*dest) + s; + out = s; + + if ((size - s) == 0) { + return out; + } + + *source = *alternate; + *ssize = *altsize; + *alternate = nullptr; + *altsize = 0; + + out = Simple_Copy(source, ssize, alternate, altsize, dest, (size - s)) + s; + + return out; +} + +static int Sample_Copy(SampleTrackerType* st, + void** source, + int* ssize, + void** alternate, + int* altsize, + void* dest, + int size, + SCompressType scomp, + void* trailer, + int16_t* trailersize) +{ + int datasize = 0; + + // There is no compression or it doesn't match any of the supported compressions so we just copy the data over. + if (scomp == SCOMP_NONE || (scomp != SCOMP_WESTWOOD && scomp != SCOMP_SOS)) { + return Simple_Copy(source, ssize, alternate, altsize, &dest, size); + } + + _SOS_COMPRESS_INFO* s = &st->sosinfo; + + while (size > 0) { + uint16_t fsize; + uint16_t dsize; + unsigned magic; + + void* fptr = &fsize; + void* dptr = &dsize; + void* mptr = &magic; + + // Verify and seek over the chunk header. + if (Simple_Copy(source, ssize, alternate, altsize, &fptr, sizeof(fsize)) < sizeof(fsize)) { + break; + } + + fsize = le16toh(fsize); + + if (Simple_Copy(source, ssize, alternate, altsize, &dptr, sizeof(dsize)) < sizeof(dsize)) { + break; + } + + dsize = le16toh(dsize); + + if (dsize > size) { + break; + } + + if (Simple_Copy(source, ssize, alternate, altsize, &mptr, sizeof(magic)) < sizeof(magic)) { + break; + } + + magic = le32toh(magic); + + if (magic != LockedData.MagicNumber) { + break; + } + + if (fsize == dsize) { + // File size matches size to decompress, so there's nothing to do other than copy the buffer over. + if (Simple_Copy(source, ssize, alternate, altsize, &dest, fsize) < dsize) { + return datasize; + } + } else { + // Else we need to decompress it. + void* uptr = LockedData.UncompBuffer; + + if (Simple_Copy(source, ssize, alternate, altsize, &uptr, fsize) < fsize) { + return datasize; + } + + if (scomp == SCOMP_WESTWOOD) { + Audio_Unzap(LockedData.UncompBuffer, dest, dsize); + } else { + s->lpSource = (char*)LockedData.UncompBuffer; + s->lpDest = (char*)dest; + + sosCODECDecompressData(s, dsize); + } + + dest = reinterpret_cast(dest) + dsize; + } + + datasize += dsize; + size -= dsize; + } + + return datasize; +} + +static int +Stream_Sample_Vol(void* buffer, int size, bool (*callback)(short, short*, void**, int*), int volume, int handle) +{ + if (AudioDone || buffer == nullptr || size == 0 || LockedData.DigiHandle == INVALID_AUDIO_HANDLE) { + return INVALID_AUDIO_HANDLE; + } + + AUDHeaderType header; + memcpy(&header, buffer, sizeof(header)); + int oldsize = header.Size; + header.Size = htole32(size - sizeof(header)); + memcpy(buffer, &header, sizeof(header)); + int playid = Play_Sample_Handle(buffer, PRIORITY_MAX, volume, 0, handle); + header.Size = oldsize; + memcpy(buffer, &header, sizeof(header)); + + if (playid == INVALID_AUDIO_HANDLE) { + return INVALID_AUDIO_HANDLE; + } + + SampleTrackerType* st = &LockedData.SampleTracker[playid]; + st->Callback = callback; + st->Odd = 0; + + return playid; +} + +static bool File_Callback(short id, short* odd, void** buffer, int* size) +{ + if (id == INVALID_AUDIO_HANDLE) { + return false; + } + + SampleTrackerType* st = &LockedData.SampleTracker[id]; + + if (st->FileBuffer == nullptr) { + return false; + } + + if (*buffer == nullptr && st->FilePending) { + *buffer = + static_cast(st->FileBuffer) + LockedData.StreamBufferSize * (*odd % LockedData.StreamBufferCount); + --st->FilePending; + ++*odd; + *size = st->FilePending == 0 ? st->FilePendingSize : LockedData.StreamBufferSize; + } + + Maintenance_Callback(); + + int count = StreamLowImpact ? LockedData.StreamBufferCount / 2 : LockedData.StreamBufferCount - 3; + + if (count > st->FilePending && st->FileHandle != INVALID_FILE_HANDLE) { + if (LockedData.StreamBufferCount - 2 != st->FilePending) { + // Fill empty buffers. + for (int num_empty_buffers = LockedData.StreamBufferCount - 2 - st->FilePending; + num_empty_buffers && st->FileHandle != INVALID_FILE_HANDLE; + --num_empty_buffers) { + // Buffer to fill with data. + void* tofill = + static_cast(st->FileBuffer) + + LockedData.StreamBufferSize * ((st->FilePending + *odd) % LockedData.StreamBufferCount); + + int psize = Read_File(st->FileHandle, tofill, LockedData.StreamBufferSize); + + if (psize != LockedData.StreamBufferSize) { + Close_File(st->FileHandle); + st->FileHandle = INVALID_FILE_HANDLE; + } + + if (psize > 0) { + st->FilePendingSize = psize; + ++st->FilePending; + Maintenance_Callback(); + } + } + } + + if (st->QueueBuffer == nullptr && st->FilePending) { + st->QueueBuffer = static_cast(st->FileBuffer) + + LockedData.StreamBufferSize * (st->Odd % LockedData.StreamBufferCount); + --st->FilePending; + ++st->Odd; + st->QueueSize = st->FilePending > 0 ? LockedData.StreamBufferSize : st->FilePendingSize; + } + + Maintenance_Callback(); + } + + if (st->FilePending) { + return true; + } + + return false; +} + +static void File_Stream_Preload(int index) +{ + SampleTrackerType* st = &LockedData.SampleTracker[index]; + int maxnum = (LockedData.StreamBufferCount / 2) + 4; + int num = st->Loading ? std::min(st->FilePending + 2, maxnum) : maxnum; + + int i = 0; + + for (i = st->FilePending; i < num; ++i) { + int size = Read_File(st->FileHandle, + static_cast(st->FileBuffer) + i * LockedData.StreamBufferSize, + LockedData.StreamBufferSize); + + if (size > 0) { + st->FilePendingSize = size; + ++st->FilePending; + } + + if (size < LockedData.StreamBufferSize) { + break; + } + } + + Maintenance_Callback(); + + if (LockedData.StreamBufferSize > st->FilePendingSize || i == maxnum) { + int old_vol = LockedData.SoundVolume; + + int stream_size = st->FilePending == 1 ? st->FilePendingSize : LockedData.StreamBufferSize; + + LockedData.SoundVolume = LockedData.ScoreVolume; + StartingFileStream = true; + Stream_Sample_Vol(st->FileBuffer, stream_size, File_Callback, st->Volume, index); + StartingFileStream = false; + + LockedData.SoundVolume = old_vol; + + st->Loading = false; + --st->FilePending; + + if (st->FilePending == 0) { + st->Odd = 0; + st->QueueBuffer = 0; + st->QueueSize = 0; + st->FilePendingSize = 0; + st->Callback = nullptr; + Close_File(st->FileHandle); + } else { + st->Odd = 2; + --st->FilePending; + + if (st->FilePendingSize != LockedData.StreamBufferSize) { + Close_File(st->FileHandle); + st->FileHandle = INVALID_FILE_HANDLE; + } + + st->QueueBuffer = static_cast(st->FileBuffer) + LockedData.StreamBufferSize; + st->QueueSize = st->FilePending == 0 ? st->FilePendingSize : LockedData.StreamBufferSize; + } + } +} + +int File_Stream_Sample_Vol(char const* filename, int volume, bool real_time_start) +{ + if (LockedData.DigiHandle == INVALID_AUDIO_HANDLE || filename == nullptr || !Find_File(filename)) { + return INVALID_AUDIO_HANDLE; + } + + if (FileStreamBuffer == nullptr) { + FileStreamBuffer = malloc((unsigned int)(LockedData.StreamBufferSize * LockedData.StreamBufferCount)); + + for (int i = 0; i < MAX_SAMPLE_TRACKERS; ++i) { + LockedData.SampleTracker[i].FileBuffer = FileStreamBuffer; + } + } + + if (FileStreamBuffer == nullptr) { + return INVALID_AUDIO_HANDLE; + } + + int fh = Open_File(filename, 1); + + if (fh == INVALID_FILE_HANDLE) { + return INVALID_AUDIO_HANDLE; + } + + int handle = Get_Free_Sample_Handle(PRIORITY_MAX); + + if (handle < MAX_SAMPLE_TRACKERS) { + SampleTrackerType* st = &LockedData.SampleTracker[handle]; + st->IsScore = true; + st->FilePending = 0; + st->FilePendingSize = 0; + st->Loading = real_time_start; + st->Volume = volume; + st->FileHandle = fh; + File_Stream_Preload(handle); + return handle; + } + + return INVALID_AUDIO_HANDLE; +} + +void Sound_Callback() +{ + if (!AudioDone && LockedData.DigiHandle != INVALID_AUDIO_HANDLE) { + Maintenance_Callback(); + + for (int i = 0; i < MAX_SAMPLE_TRACKERS; ++i) { + SampleTrackerType* st = &LockedData.SampleTracker[i]; + + // Is a load pending? + if (st->Loading) { + File_Stream_Preload(i); + // We are done with this sample. + continue; + } + + // Is this sample inactive? + if (!st->Active) { + // If so, we close the handle. + if (st->FileHandle != INVALID_FILE_HANDLE) { + Close_File(st->FileHandle); + st->FileHandle = INVALID_FILE_HANDLE; + } + // We are done with this sample. + continue; + } + + // Has it been faded Is the volume 0? + if (st->Reducer && !st->Volume) { + // If so stop it. + Stop_Sample(i); + + // We are done with this sample. + continue; + } + + // Process pending files. + if (st->QueueBuffer == nullptr + || st->FileHandle != INVALID_FILE_HANDLE && LockedData.StreamBufferCount - 3 > st->FilePending) { + if (st->Callback != nullptr) { + if (!st->Callback(i, &st->Odd, &st->QueueBuffer, &st->QueueSize)) { + // No files are pending so pending file callback not needed anymore. + st->Callback = nullptr; + } + } + + // We are done with this sample. + continue; + } + } + } +} + +static void Maintenance_Callback() +{ + if (AudioDone) { + return; + } + + SampleTrackerType* st = LockedData.SampleTracker; + + for (int i = 0; i < MAX_SAMPLE_TRACKERS; ++i) { + if (st->Active) { // If this tracker needs processing and isn't already marked as being processed, then process it. + if (st->Service) { + // Do we have more data in this tracker to play? + if (st->MoreSource) { + int processed_buffers; + + // Work out if we have any space to buffer more data right now. + processed_buffers = SoundImp_Get_Sample_Free_Buffer_Count(st->Imp); + + while (processed_buffers > 0 && st->MoreSource) { + int bytes_copied = Sample_Copy(st, + &st->Source, + &st->Remainder, + &st->QueueBuffer, + &st->QueueSize, + ChunkBuffer, + BUFFER_CHUNK_SIZE, + st->Compression, + nullptr, + nullptr); + + if (bytes_copied != BUFFER_CHUNK_SIZE) { + st->MoreSource = false; + } + + if (bytes_copied > 0) { + SoundImp_Buffer_Sample_Data(st->Imp, ChunkBuffer, bytes_copied); + --processed_buffers; + } + } + } else { + if (!SoundImp_Sample_Status(st->Imp)) { + st->Service = 0; + Stop_Sample(i); + } + } + } + + if (!st->QueueBuffer && st->FilePending != 0) { + st->QueueBuffer = static_cast(st->FileBuffer) + + LockedData.StreamBufferSize * (st->Odd % LockedData.StreamBufferCount); + --st->FilePending; + ++st->Odd; + + if (st->FilePending != 0) { + st->QueueSize = LockedData.StreamBufferSize; + } else { + st->QueueSize = st->FilePendingSize; + } + } + } + + ++st; + } + + // Perform any volume modifications that need to be made. + if (LockedData.VolumeLock == 0) { + ++LockedData.VolumeLock; + st = LockedData.SampleTracker; + + for (int i = 0; i < MAX_SAMPLE_TRACKERS; ++i) { + if (st->Active && st->Reducer > 0 && st->Volume > 0) { + if (st->Reducer >= st->Volume) { + st->Volume = VOLUME_MIN; + } else { + st->Volume -= st->Reducer; + } + + if (!st->IsScore) { + SoundImp_Set_Sample_Volume(st->Imp, LockedData.SoundVolume * st->Volume); + } else { + SoundImp_Set_Sample_Volume(st->Imp, LockedData.ScoreVolume * st->Volume); + } + } + + ++st; + } + + --LockedData.VolumeLock; + } +} + +void* Load_Sample(char const* filename) +{ + if (LockedData.DigiHandle == INVALID_AUDIO_HANDLE || filename == nullptr || !Find_File(filename)) { + return nullptr; + } + + void* data = nullptr; + int handle = Open_File(filename, 1); + + if (handle != INVALID_FILE_HANDLE) { + int data_size = File_Size(handle) + sizeof(AUDHeaderType); + data = malloc(data_size); + + if (data != nullptr) { + Sample_Read(handle, data, data_size); + } + + Close_File(handle); + Misc = data_size; + } + + return data; +} + +static int Sample_Read(int fh, void* buffer, int size) +{ + if (buffer == nullptr || fh == INVALID_AUDIO_HANDLE || size <= sizeof(AUDHeaderType)) { + return 0; + } + + AUDHeaderType header; + int actual_bytes_read = Read_File(fh, &header, sizeof(AUDHeaderType)); + int to_read = std::min(size - sizeof(AUDHeaderType), header.Size); + + actual_bytes_read += Read_File(fh, static_cast(buffer) + sizeof(AUDHeaderType), to_read); + + memcpy(buffer, &header, sizeof(AUDHeaderType)); + + return actual_bytes_read; +} + +void Free_Sample(const void* sample) +{ + if (sample != nullptr) { + free((void*)sample); + } +} + +bool Audio_Init(int bits_per_sample, bool stereo, int rate, bool reverse_channels) +{ + Init_Locked_Data(); + + if (!SoundImp_Init(bits_per_sample, stereo, rate, reverse_channels)) { + return false; + } + + LockedData.DigiHandle = 1; + + LockedData.UncompBuffer = malloc(UNCOMP_BUFFER_SIZE); + + if (LockedData.UncompBuffer == nullptr) { + //CCDebugString("Audio_Init - Failed to allocate UncompBuffer."); + return false; + } + + // Create placback buffers for all trackers. + for (int i = 0; i < MAX_SAMPLE_TRACKERS; ++i) { + SampleTrackerType* st = &LockedData.SampleTracker[i]; + + st->BitsPerSample = bits_per_sample; + st->Stereo = stereo; + st->Frequency = rate; + + if (!(st->Imp = SoundImp_Init_Sample(bits_per_sample, stereo, rate))) { + return false; + } + } + + SoundType = SFX_ALFX; + SampleType = SAMPLE_SB; + AudioDone = false; + + return true; +} + +void Sound_End() +{ + if (AudioDone) { + return; + } + + for (int i = 0; i < MAX_SAMPLE_TRACKERS; ++i) { + Stop_Sample(i); + SoundImp_Shutdown_Sample(LockedData.SampleTracker[i].Imp); + } + + if (FileStreamBuffer != nullptr) { + free((void*)FileStreamBuffer); + FileStreamBuffer = nullptr; + } + + SoundImp_Shutdown(); + + if (LockedData.UncompBuffer != nullptr) { + free((void*)LockedData.UncompBuffer); + LockedData.UncompBuffer = nullptr; + } + + AudioDone = true; +} + +void Stop_Sample(int index) +{ + if (LockedData.DigiHandle != INVALID_AUDIO_HANDLE && index < MAX_SAMPLE_TRACKERS && !AudioDone) { + SampleTrackerType* st = &LockedData.SampleTracker[index]; + + if (st->Active || st->Loading) { + st->Active = false; + + if (!st->IsScore) { + st->Original = nullptr; + } + + st->Priority = 0; + + if (!st->Loading) { + SoundImp_Stop_Sample(st->Imp); + } + + st->Loading = false; + + if (st->FileHandle != INVALID_FILE_HANDLE) { + Close_File(st->FileHandle); + st->FileHandle = INVALID_FILE_HANDLE; + } + + st->QueueBuffer = nullptr; + } + } +} + +bool Sample_Status(int index) +{ + if (index < 0) { + return false; + } + + if (AudioDone) { + return false; + } + + if (LockedData.DigiHandle == INVALID_AUDIO_HANDLE || index >= MAX_SAMPLE_TRACKERS) { + return false; + } + + SampleTrackerType* st = &LockedData.SampleTracker[index]; + + if (st->Loading) { + return true; + } + + if (!st->Active) { + return false; + } + + return SoundImp_Sample_Status(st->Imp); +} + +bool Is_Sample_Playing(const void* sample) +{ + if (AudioDone || sample == nullptr) { + return false; + } + + for (int i = 0; i < MAX_SAMPLE_TRACKERS; ++i) { + if (sample == LockedData.SampleTracker[i].Original && Sample_Status(i)) { + return true; + } + } + + return false; +} + +void Stop_Sample_Playing(const void* sample) +{ + if (sample != nullptr) { + for (int i = 0; i < MAX_SAMPLE_TRACKERS; ++i) { + if (LockedData.SampleTracker[i].Original == sample) { + Stop_Sample(i); + break; + } + } + } +} + +int Play_Sample(const void* sample, int priority, int volume, signed short panloc) +{ + return Play_Sample_Handle(sample, priority, volume, panloc, Get_Free_Sample_Handle(priority)); +} + +static int Attempt_To_Play_Buffer(int id) +{ + SampleTrackerType* st = &LockedData.SampleTracker[id]; + + SoundImp_Start_Sample(st->Imp); + + // Playback was started so we set some needed sample tracker values. + st->Active = true; + + return id; +} + +static int Play_Sample_Handle(const void* sample, int priority, int volume, signed short panloc, int id) +{ + if (Any_Locked()) { + return INVALID_AUDIO_HANDLE; + } + + if (!AudioDone) { + if (sample == nullptr || LockedData.DigiHandle == INVALID_AUDIO_HANDLE) { + return INVALID_AUDIO_HANDLE; + } + + if (id == INVALID_AUDIO_HANDLE) { + return INVALID_AUDIO_HANDLE; + } + + SampleTrackerType* st = &LockedData.SampleTracker[id]; + + // Read in the sample's header. + AUDHeaderType raw_header; + memcpy(&raw_header, sample, sizeof(raw_header)); + raw_header.Rate = le16toh(raw_header.Rate); + raw_header.Size = le32toh(raw_header.Size); + raw_header.UncompSize = le32toh(raw_header.UncompSize); + + // We don't support anything lower than 20000 hz. + if (raw_header.Rate < 24000 && raw_header.Rate > 20000) { + raw_header.Rate = 22050; + } + + // Set up basic sample tracker info. + st->Compression = SCompressType(raw_header.Compression); + st->Original = sample; + st->Odd = 0; + st->Reducer = 0; + st->Restart = false; + st->QueueBuffer = nullptr; + st->QueueSize = 0; + st->OriginalSize = raw_header.Size + sizeof(AUDHeaderType); + st->Priority = priority; + st->Service = 0; + st->Remainder = raw_header.Size; + st->Source = Add_Long_To_Pointer(sample, sizeof(AUDHeaderType)); + + // Compression is ADPCM so we need to init it's stream info. + if (st->Compression == SCOMP_SOS) { + st->sosinfo.wChannels = (raw_header.Flags & 1) + 1; + st->sosinfo.wBitSize = raw_header.Flags & 2 ? 16 : 8; + st->sosinfo.dwCompSize = raw_header.Size; + st->sosinfo.dwUnCompSize = raw_header.Size * (st->sosinfo.wBitSize / 4); + sosCODECInitStream(&st->sosinfo); + } + + // If the loaded sample doesn't match the sample tracker we need to adjust the tracker. + int new_bits_per_sample = (raw_header.Flags & 2) ? 16 : 8; + bool new_stereo = (raw_header.Flags & 1) ? true : false; + if (new_bits_per_sample != st->BitsPerSample || new_stereo != st->Stereo || raw_header.Rate != st->Frequency) { + st->Active = false; + st->Service = 0; + st->MoreSource = false; + + st->BitsPerSample = new_bits_per_sample; + st->Stereo = (raw_header.Flags & 1) ? true : false; + st->Frequency = raw_header.Rate; + + SoundImp_Set_Sample_Attributes(st->Imp, st->BitsPerSample, st->Stereo, st->Frequency); + } + + // If the sample is already playing stop it. + if (SoundImp_Sample_Status(st->Imp)) { + st->Active = false; + st->Service = 0; + st->MoreSource = false; + + SoundImp_Stop_Sample(st->Imp); + } + + while (SoundImp_Get_Sample_Free_Buffer_Count(st->Imp)) { + + int bytes_read = Sample_Copy(st, + &st->Source, + &st->Remainder, + &st->QueueBuffer, + &st->QueueSize, + ChunkBuffer, + BUFFER_CHUNK_SIZE, + st->Compression, + nullptr, + nullptr); + + if (bytes_read > 0) { + SoundImp_Buffer_Sample_Data(st->Imp, ChunkBuffer, bytes_read); + } + + if (bytes_read == BUFFER_CHUNK_SIZE) { + st->MoreSource = true; + st->OneShot = false; + } else { + st->MoreSource = false; + st->OneShot = true; + break; + } + } + + st->Service = 1; + + st->Volume = volume; + + SoundImp_Set_Sample_Volume(st->Imp, LockedData.SoundVolume * st->Volume); + + if (!Start_Primary_Sound_Buffer(false)) { + //CCDebugString("Play_Sample_Handle - Can't start primary buffer!"); + return INVALID_AUDIO_HANDLE; + } + + return Attempt_To_Play_Buffer(id); + } + + return INVALID_AUDIO_HANDLE; +} + +int Set_Score_Vol(int volume) +{ + int old = LockedData.ScoreVolume; + LockedData.ScoreVolume = volume; + + for (int i = 0; i < MAX_SAMPLE_TRACKERS; ++i) { + SampleTrackerType* st = &LockedData.SampleTracker[i]; + + if (st->IsScore & st->Active) { + SoundImp_Set_Sample_Volume(st->Imp, LockedData.ScoreVolume * st->Volume); + } + } + + return old; +} + +void Fade_Sample(int index, int ticks) +{ + if (Sample_Status(index)) { + SampleTrackerType* st = &LockedData.SampleTracker[index]; + + if (ticks > 0 && !st->Loading) { + st->Reducer = ((st->Volume / ticks) + 1); + } else { + Stop_Sample(index); + } + } +} + +static int Get_Free_Sample_Handle(int priority) +{ + int index = 0; + + for (index = MAX_SAMPLE_TRACKERS - 1; index >= 0; --index) { + if (!LockedData.SampleTracker[index].Active && !LockedData.SampleTracker[index].Loading) { + if (StartingFileStream || !LockedData.SampleTracker[index].IsScore) { + break; + } + + StartingFileStream = true; + } + } + + if (index < 0) { + for (index = 0; index < MAX_SAMPLE_TRACKERS && LockedData.SampleTracker[index].Priority > priority; ++index) { + ; + } + + if (index == MAX_SAMPLE_TRACKERS) { + return INVALID_AUDIO_HANDLE; + } + + Stop_Sample(index); + } + + if (index == INVALID_AUDIO_HANDLE) { + return INVALID_AUDIO_HANDLE; + } + + if (LockedData.SampleTracker[index].FileHandle != INVALID_FILE_HANDLE) { + Close_File(LockedData.SampleTracker[index].FileHandle); + LockedData.SampleTracker[index].FileHandle = INVALID_FILE_HANDLE; + } + + if (LockedData.SampleTracker[index].Original) { + if (!LockedData.SampleTracker[index].IsScore) { + LockedData.SampleTracker[index].Original = 0; + } + } + + LockedData.SampleTracker[index].IsScore = false; + return index; +} + +int Get_Digi_Handle() +{ + return LockedData.DigiHandle; +} + +void Restore_Sound_Buffers() +{ +} + +bool Set_Primary_Buffer_Format() +{ + return true; +} + +bool Start_Primary_Sound_Buffer(bool forced) +{ + if (!GameInFocus) { + return false; + } + + return SoundImp_ResumeSound(); +} + +void Stop_Primary_Sound_Buffer() +{ + for (int i = 0; i < MAX_SAMPLE_TRACKERS; ++i) { + Stop_Sample(i); + } + + SoundImp_PauseSound(); +} diff --git a/common/soundio_imp.h b/common/soundio_imp.h new file mode 100644 index 00000000..7fec7b76 --- /dev/null +++ b/common/soundio_imp.h @@ -0,0 +1,17 @@ +#include + +struct SampleTrackerTypeImp; + +void SoundImp_Buffer_Sample_Data(SampleTrackerTypeImp* st, const void* data, size_t datalen); +int SoundImp_Get_Sample_Free_Buffer_Count(SampleTrackerTypeImp* st); +bool SoundImp_Init(int bits_per_sample, bool stereo, int rate, bool reverse_channels); +void SoundImp_PauseSound(); +bool SoundImp_ResumeSound(); +SampleTrackerTypeImp* SoundImp_Init_Sample(int bits_per_sample, bool stereo, int rate); +bool SoundImp_Sample_Status(SampleTrackerTypeImp* st); +void SoundImp_Set_Sample_Attributes(SampleTrackerTypeImp* st, int bits_per_sample, bool stereo, int rate); +void SoundImp_Set_Sample_Volume(SampleTrackerTypeImp* st, unsigned int volume); +void SoundImp_Shutdown(); +void SoundImp_Shutdown_Sample(SampleTrackerTypeImp* st); +void SoundImp_Start_Sample(SampleTrackerTypeImp* st); +void SoundImp_Stop_Sample(SampleTrackerTypeImp* st); diff --git a/common/soundio_openal.cpp b/common/soundio_openal.cpp index 669d91be..4f88ce1a 100644 --- a/common/soundio_openal.cpp +++ b/common/soundio_openal.cpp @@ -9,175 +9,18 @@ // distributed with this program. You should have received a copy of the // GNU General Public License along with permitted additional restrictions // with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection -#include "audio.h" -#include "auduncmp.h" -#include "file.h" -#include "memflag.h" -#include "soscomp.h" -#include "sound.h" -#include "endianness.h" +#include "soundio_imp.h" #include #include -#include #include enum { - AUD_CHUNK_MAGIC_ID = 0x0000DEAF, - VOLUME_MIN = 0, - VOLUME_MAX = 255, - PRIORITY_MIN = 0, - PRIORITY_MAX = 255, - MAX_SAMPLE_TRACKERS = 5, // C&C issue where sounds get cut off is because of the small number of trackers. - STREAM_BUFFER_COUNT = 16, - BUFFER_CHUNK_SIZE = 8192, // 256 * 32, - UNCOMP_BUFFER_SIZE = 2098, - BUFFER_TOTAL_BYTES = BUFFER_CHUNK_SIZE * 4, // 32 kb - TIMER_DELAY = 25, - TIMER_RESOLUTION = 1, - TIMER_TARGET_RESOLUTION = 10, // 10-millisecond target resolution - INVALID_AUDIO_HANDLE = -1, - INVALID_FILE_HANDLE = -1, OPENAL_BUFFER_COUNT = 2, }; -/* -** Define the different type of sound compression avaliable to the westwood -** library. -*/ -typedef enum +struct SampleTrackerTypeImp { - SCOMP_NONE = 0, // No compression -- raw data. - SCOMP_WESTWOOD = 1, // Special sliding window delta compression. - SCOMP_SOS = 99 // SOS frame compression. -} SCompressType; - -struct SampleTrackerType -{ - /* - ** This flags whether this sample structure is active or not. - */ - bool Active; - - /* - ** This flags whether the sample is loading or has been started. - */ - bool Loading; - - /* - ** If this sample is really to be considered a score rather than - ** a sound effect, then special rules apply. These largely fall into - ** the area of volume control. - */ - bool IsScore; - - /* - ** This is the original sample pointer. It is used to control the sample based on - ** pointer rather than handle. The handle method is necessary when more than one - ** sample could be playing simultaneously. The pointer method is necessary when - ** the dealing with a sample that may have stopped behind the programmer's back and - ** this occurance is not otherwise determinable. It is also used in - ** conjunction with original size to unlock a sample which has been DPMI - ** locked. - */ - const void* Original; - int OriginalSize; - - /* - ** Variable to keep track of the playback rate of this buffer - */ - //int PlaybackRate; - - /* - ** Variable to keep track of the sample type ( 8 or 16 bit ) of this buffer - */ - //int BitSize; - - /* - ** Variable to keep track of the stereo ability of this buffer - */ - //int Stereo; - - /* - ** Samples maintain a priority which is used to determine - ** which sounds live or die when the maximum number of - ** sounds are being played. - */ - int Priority; - - /* - ** This is the current volume of the sample as it is being played. - */ - int Volume; - int Reducer; // Amount to reduce volume per tick. - - /* - ** This is the compression that the sound data is using. - */ - SCompressType Compression; - - /* - ** This flag indicates whether this sample needs servicing. - ** Servicing entails filling one of the empty low buffers. - */ - short Service; - - /* - ** This flag is true when the sample has stopped playing, - ** BUT there is more data available. The sample must be - ** restarted upon filling the low buffer. - */ - bool Restart; - - /* - ** Streaming control handlers. - */ - bool (*Callback)(short id, short* odd, void** buffer, int* size); - int FilePending; // Number of buffers already filled ahead. - int FilePendingSize; // Number of bytes in last filled buffer. - short Odd; // Block number tracker (0..StreamBufferCount-1). - void* QueueBuffer; // Pointer to continued sample data. - int QueueSize; // Size of queue buffer attached. - - /* - ** The file variables are used when streaming directly off of the - ** hard drive. - */ - int FileHandle; // Streaming file handle (INVALID_FILE_HANDLE = not in use). - void* FileBuffer; - - /* - ** The following structure is used if the sample if compressed using - ** the sos 16 bit compression Codec. - */ - _SOS_COMPRESS_INFO sosinfo; - - /* - ** This flag indicates that there is more source data - ** to copy to the play buffer - ** - */ - bool MoreSource; - - /* - ** This flag indicates that the entire sample fitted inside the - ** direct sound secondary buffer - ** - */ - bool OneShot; - - /* - ** Pointer to the sound data that has not yet been copied - ** to the playback buffers. - */ - void* Source; - - /* - ** This is the number of bytes remaining in the source data as - ** pointed to by the "Source" element. - */ - int Remainder; - // A point in space that is the source of this sound. ALuint OpenALSource; @@ -189,40 +32,11 @@ struct SampleTrackerType // A set of buffers ALuint AudioBuffers[OPENAL_BUFFER_COUNT]; -}; -struct LockedDataType -{ - unsigned int DigiHandle; // = -1; - bool ServiceSomething; // = false; - unsigned MagicNumber; // = 0xDEAF; - void* UncompBuffer; // = NULL; - int StreamBufferSize; // = (2*SECONDARY_BUFFER_SIZE)+128; - short StreamBufferCount; // = 32; - SampleTrackerType SampleTracker[MAX_SAMPLE_TRACKERS]; - unsigned SoundVolume; - unsigned ScoreVolume; - int VolumeLock; + int UnusedBufferCount; }; -void (*Audio_Focus_Loss_Function)() = nullptr; - -static struct LockedDataType LockedData; -SFX_Type SoundType; -Sample_Type SampleType; -static void* FileStreamBuffer = nullptr; -bool StreamLowImpact = false; -static bool StartingFileStream = false; -static bool volatile AudioDone = false; -ALCcontext* OpenALContext = nullptr; -extern bool GameInFocus; -static uint8_t ChunkBuffer[BUFFER_CHUNK_SIZE]; - -bool Any_Locked(); // From each games winstub.cpp at the moment. -static int Get_Free_Sample_Handle(int priority); -static void Maintenance_Callback(); -static int Play_Sample_Handle(const void* sample, int priority, int volume, signed short panloc, int id); -static int Sample_Read(int fh, void* buffer, int size); +static ALCcontext* OpenALContext = nullptr; static ALenum Get_OpenAL_Format(int bits, int channels) { @@ -272,546 +86,38 @@ static const char* Get_OpenAL_Error(ALenum error) return "Unknown OpenAL error."; } -static void Init_Locked_Data() -{ - LockedData.DigiHandle = INVALID_AUDIO_HANDLE; - LockedData.ServiceSomething = false; - LockedData.MagicNumber = AUD_CHUNK_MAGIC_ID; - LockedData.UncompBuffer = 0; - LockedData.StreamBufferSize = BUFFER_CHUNK_SIZE + 128; - LockedData.StreamBufferCount = STREAM_BUFFER_COUNT; - LockedData.SoundVolume = VOLUME_MAX; - LockedData.ScoreVolume = VOLUME_MAX; -} - -static int Simple_Copy(void** source, int* ssize, void** alternate, int* altsize, void** dest, int size) -{ - int out = 0; - - if (*ssize == 0) { - *source = *alternate; - *ssize = *altsize; - *alternate = nullptr; - *altsize = 0; - } - - if (*source == nullptr || *ssize == 0) { - return out; - } - - int s = size; - - if (*ssize < size) { - s = *ssize; - } - - memcpy(*dest, *source, s); - *source = static_cast(*source) + s; - *ssize -= s; - *dest = static_cast(*dest) + s; - out = s; - - if ((size - s) == 0) { - return out; - } - - *source = *alternate; - *ssize = *altsize; - *alternate = nullptr; - *altsize = 0; - - out = Simple_Copy(source, ssize, alternate, altsize, dest, (size - s)) + s; - - return out; -} - -static int Sample_Copy(SampleTrackerType* st, - void** source, - int* ssize, - void** alternate, - int* altsize, - void* dest, - int size, - SCompressType scomp, - void* trailer, - int16_t* trailersize) -{ - int datasize = 0; - - // There is no compression or it doesn't match any of the supported compressions so we just copy the data over. - if (scomp == SCOMP_NONE || (scomp != SCOMP_WESTWOOD && scomp != SCOMP_SOS)) { - return Simple_Copy(source, ssize, alternate, altsize, &dest, size); - } - - _SOS_COMPRESS_INFO* s = &st->sosinfo; - - while (size > 0) { - uint16_t fsize; - uint16_t dsize; - unsigned magic; - - void* fptr = &fsize; - void* dptr = &dsize; - void* mptr = &magic; - - // Verify and seek over the chunk header. - if (Simple_Copy(source, ssize, alternate, altsize, &fptr, sizeof(fsize)) < sizeof(fsize)) { - break; - } - - fsize = le16toh(fsize); - - if (Simple_Copy(source, ssize, alternate, altsize, &dptr, sizeof(dsize)) < sizeof(dsize)) { - break; - } - - dsize = le16toh(dsize); - - if (dsize > size) { - break; - } - - if (Simple_Copy(source, ssize, alternate, altsize, &mptr, sizeof(magic)) < sizeof(magic)) { - break; - } - - magic = le32toh(magic); - - if (magic != LockedData.MagicNumber) { - break; - } - - if (fsize == dsize) { - // File size matches size to decompress, so there's nothing to do other than copy the buffer over. - if (Simple_Copy(source, ssize, alternate, altsize, &dest, fsize) < dsize) { - return datasize; - } - } else { - // Else we need to decompress it. - void* uptr = LockedData.UncompBuffer; - - if (Simple_Copy(source, ssize, alternate, altsize, &uptr, fsize) < fsize) { - return datasize; - } - - if (scomp == SCOMP_WESTWOOD) { - Audio_Unzap(LockedData.UncompBuffer, dest, dsize); - } else { - s->lpSource = (char*)LockedData.UncompBuffer; - s->lpDest = (char*)dest; - - sosCODECDecompressData(s, dsize); - } - - dest = reinterpret_cast(dest) + dsize; - } - - datasize += dsize; - size -= dsize; - } - - return datasize; -} - -static int -Stream_Sample_Vol(void* buffer, int size, bool (*callback)(short, short*, void**, int*), int volume, int handle) -{ - if (AudioDone || buffer == nullptr || size == 0 || LockedData.DigiHandle == INVALID_AUDIO_HANDLE) { - return INVALID_AUDIO_HANDLE; - } - - AUDHeaderType header; - memcpy(&header, buffer, sizeof(header)); - int oldsize = header.Size; - header.Size = htole32(size - sizeof(header)); - memcpy(buffer, &header, sizeof(header)); - int playid = Play_Sample_Handle(buffer, PRIORITY_MAX, volume, 0, handle); - header.Size = oldsize; - memcpy(buffer, &header, sizeof(header)); - - if (playid == INVALID_AUDIO_HANDLE) { - return INVALID_AUDIO_HANDLE; - } - - SampleTrackerType* st = &LockedData.SampleTracker[playid]; - st->Callback = callback; - st->Odd = 0; - - return playid; -} - -static bool File_Callback(short id, short* odd, void** buffer, int* size) -{ - if (id == INVALID_AUDIO_HANDLE) { - return false; - } - - SampleTrackerType* st = &LockedData.SampleTracker[id]; - - if (st->FileBuffer == nullptr) { - return false; - } - - if (*buffer == nullptr && st->FilePending) { - *buffer = - static_cast(st->FileBuffer) + LockedData.StreamBufferSize * (*odd % LockedData.StreamBufferCount); - --st->FilePending; - ++*odd; - *size = st->FilePending == 0 ? st->FilePendingSize : LockedData.StreamBufferSize; - } - - Maintenance_Callback(); - - int count = StreamLowImpact ? LockedData.StreamBufferCount / 2 : LockedData.StreamBufferCount - 3; - - if (count > st->FilePending && st->FileHandle != INVALID_FILE_HANDLE) { - if (LockedData.StreamBufferCount - 2 != st->FilePending) { - // Fill empty buffers. - for (int num_empty_buffers = LockedData.StreamBufferCount - 2 - st->FilePending; - num_empty_buffers && st->FileHandle != INVALID_FILE_HANDLE; - --num_empty_buffers) { - // Buffer to fill with data. - void* tofill = - static_cast(st->FileBuffer) - + LockedData.StreamBufferSize * ((st->FilePending + *odd) % LockedData.StreamBufferCount); - - int psize = Read_File(st->FileHandle, tofill, LockedData.StreamBufferSize); - - if (psize != LockedData.StreamBufferSize) { - Close_File(st->FileHandle); - st->FileHandle = INVALID_FILE_HANDLE; - } - - if (psize > 0) { - st->FilePendingSize = psize; - ++st->FilePending; - Maintenance_Callback(); - } - } - } - - if (st->QueueBuffer == nullptr && st->FilePending) { - st->QueueBuffer = static_cast(st->FileBuffer) - + LockedData.StreamBufferSize * (st->Odd % LockedData.StreamBufferCount); - --st->FilePending; - ++st->Odd; - st->QueueSize = st->FilePending > 0 ? LockedData.StreamBufferSize : st->FilePendingSize; - } - - Maintenance_Callback(); - } - - if (st->FilePending) { - return true; - } - - return false; -} - -static void File_Stream_Preload(int index) -{ - SampleTrackerType* st = &LockedData.SampleTracker[index]; - int maxnum = (LockedData.StreamBufferCount / 2) + 4; - int num = st->Loading ? std::min(st->FilePending + 2, maxnum) : maxnum; - - int i = 0; - - for (i = st->FilePending; i < num; ++i) { - int size = Read_File(st->FileHandle, - static_cast(st->FileBuffer) + i * LockedData.StreamBufferSize, - LockedData.StreamBufferSize); - - if (size > 0) { - st->FilePendingSize = size; - ++st->FilePending; - } - - if (size < LockedData.StreamBufferSize) { - break; - } - } - - Maintenance_Callback(); - - if (LockedData.StreamBufferSize > st->FilePendingSize || i == maxnum) { - int old_vol = LockedData.SoundVolume; - - int stream_size = st->FilePending == 1 ? st->FilePendingSize : LockedData.StreamBufferSize; - - LockedData.SoundVolume = LockedData.ScoreVolume; - StartingFileStream = true; - Stream_Sample_Vol(st->FileBuffer, stream_size, File_Callback, st->Volume, index); - StartingFileStream = false; - - LockedData.SoundVolume = old_vol; - - st->Loading = false; - --st->FilePending; - - if (st->FilePending == 0) { - st->Odd = 0; - st->QueueBuffer = 0; - st->QueueSize = 0; - st->FilePendingSize = 0; - st->Callback = nullptr; - Close_File(st->FileHandle); - } else { - st->Odd = 2; - --st->FilePending; - - if (st->FilePendingSize != LockedData.StreamBufferSize) { - Close_File(st->FileHandle); - st->FileHandle = INVALID_FILE_HANDLE; - } - - st->QueueBuffer = static_cast(st->FileBuffer) + LockedData.StreamBufferSize; - st->QueueSize = st->FilePending == 0 ? st->FilePendingSize : LockedData.StreamBufferSize; - } - } -} - -int File_Stream_Sample_Vol(char const* filename, int volume, bool real_time_start) +void SoundImp_Buffer_Sample_Data(SampleTrackerTypeImp* st, const void* data, size_t datalen) { - if (LockedData.DigiHandle == INVALID_AUDIO_HANDLE || filename == nullptr || !Find_File(filename)) { - return INVALID_AUDIO_HANDLE; - } - - if (FileStreamBuffer == nullptr) { - FileStreamBuffer = malloc((unsigned int)(LockedData.StreamBufferSize * LockedData.StreamBufferCount)); - - for (int i = 0; i < MAX_SAMPLE_TRACKERS; ++i) { - LockedData.SampleTracker[i].FileBuffer = FileStreamBuffer; - } - } - - if (FileStreamBuffer == nullptr) { - return INVALID_AUDIO_HANDLE; - } + ALuint buffer; - int fh = Open_File(filename, 1); - - if (fh == INVALID_FILE_HANDLE) { - return INVALID_AUDIO_HANDLE; - } - - int handle = Get_Free_Sample_Handle(PRIORITY_MAX); - - if (handle < MAX_SAMPLE_TRACKERS) { - SampleTrackerType* st = &LockedData.SampleTracker[handle]; - st->IsScore = true; - st->FilePending = 0; - st->FilePendingSize = 0; - st->Loading = real_time_start; - st->Volume = volume; - st->FileHandle = fh; - File_Stream_Preload(handle); - return handle; + if (1 && st->UnusedBufferCount) { + buffer = st->AudioBuffers[OPENAL_BUFFER_COUNT - st->UnusedBufferCount]; + st->UnusedBufferCount--; + } else { + alSourceUnqueueBuffers(st->OpenALSource, 1, &buffer); } - return INVALID_AUDIO_HANDLE; -} - -void Sound_Callback() -{ - if (!AudioDone && LockedData.DigiHandle != INVALID_AUDIO_HANDLE) { - Maintenance_Callback(); - - for (int i = 0; i < MAX_SAMPLE_TRACKERS; ++i) { - SampleTrackerType* st = &LockedData.SampleTracker[i]; - - // Is a load pending? - if (st->Loading) { - File_Stream_Preload(i); - // We are done with this sample. - continue; - } - - // Is this sample inactive? - if (!st->Active) { - // If so, we close the handle. - if (st->FileHandle != INVALID_FILE_HANDLE) { - Close_File(st->FileHandle); - st->FileHandle = INVALID_FILE_HANDLE; - } - // We are done with this sample. - continue; - } - - // Has it been faded Is the volume 0? - if (st->Reducer && !st->Volume) { - // If so stop it. - Stop_Sample(i); - - // We are done with this sample. - continue; - } - - // Process pending files. - if (st->QueueBuffer == nullptr - || st->FileHandle != INVALID_FILE_HANDLE && LockedData.StreamBufferCount - 3 > st->FilePending) { - if (st->Callback != nullptr) { - if (!st->Callback(i, &st->Odd, &st->QueueBuffer, &st->QueueSize)) { - // No files are pending so pending file callback not needed anymore. - st->Callback = nullptr; - } - } - - // We are done with this sample. - continue; - } - } - } + alBufferData(buffer, st->Format, data, datalen, st->Frequency); + alSourceQueueBuffers(st->OpenALSource, 1, &buffer); } -static void Maintenance_Callback() +int SoundImp_Get_Sample_Free_Buffer_Count(SampleTrackerTypeImp* st) { - if (AudioDone) { - return; - } - - SampleTrackerType* st = LockedData.SampleTracker; - - for (int i = 0; i < MAX_SAMPLE_TRACKERS; ++i) { - if (st->Active) { // If this tracker needs processing and isn't already marked as being processed, then process it. - if (st->Service) { - // Do we have more data in this tracker to play? - if (st->MoreSource) { - ALint processed_buffers; - - // Work out if we have any space to buffer more data right now. - alGetSourcei(st->OpenALSource, AL_BUFFERS_PROCESSED, &processed_buffers); + ALint processed_buffers; + ALint state; - while (processed_buffers > 0 && st->MoreSource) { - int bytes_copied = Sample_Copy(st, - &st->Source, - &st->Remainder, - &st->QueueBuffer, - &st->QueueSize, - ChunkBuffer, - BUFFER_CHUNK_SIZE, - st->Compression, - nullptr, - nullptr); - - if (bytes_copied != BUFFER_CHUNK_SIZE) { - st->MoreSource = false; - } - - if (bytes_copied > 0) { - ALuint buffer; - alSourceUnqueueBuffers(st->OpenALSource, 1, &buffer); - alBufferData(buffer, st->Format, ChunkBuffer, bytes_copied, st->Frequency); - alSourceQueueBuffers(st->OpenALSource, 1, &buffer); - --processed_buffers; - } - } - } else { - ALint source_status; - alGetSourcei(st->OpenALSource, AL_SOURCE_STATE, &source_status); - - if (source_status != AL_PLAYING) { - st->Service = 0; - Stop_Sample(i); - } - } - } - - if (!st->QueueBuffer && st->FilePending != 0) { - st->QueueBuffer = static_cast(st->FileBuffer) - + LockedData.StreamBufferSize * (st->Odd % LockedData.StreamBufferCount); - --st->FilePending; - ++st->Odd; - - if (st->FilePending != 0) { - st->QueueSize = LockedData.StreamBufferSize; - } else { - st->QueueSize = st->FilePendingSize; - } - } - } - - ++st; + alGetSourcei(st->OpenALSource, AL_SOURCE_STATE, &state); + if (state == AL_PLAYING) { + alGetSourcei(st->OpenALSource, AL_BUFFERS_PROCESSED, &processed_buffers); + } else { + processed_buffers = 0; } - // Perform any volume modifications that need to be made. - if (LockedData.VolumeLock == 0) { - ++LockedData.VolumeLock; - st = LockedData.SampleTracker; - - for (int i = 0; i < MAX_SAMPLE_TRACKERS; ++i) { - if (st->Active && st->Reducer > 0 && st->Volume > 0) { - if (st->Reducer >= st->Volume) { - st->Volume = VOLUME_MIN; - } else { - st->Volume -= st->Reducer; - } - - if (!st->IsScore) { - alSourcef(st->OpenALSource, AL_GAIN, ((LockedData.SoundVolume * st->Volume) / 256) / 256.0f); - } else { - alSourcef(st->OpenALSource, AL_GAIN, ((LockedData.ScoreVolume * st->Volume) / 256) / 256.0f); - } - } - - ++st; - } - - --LockedData.VolumeLock; - } + return st->UnusedBufferCount + processed_buffers; } -void* Load_Sample(char const* filename) +bool SoundImp_Init(int bits_per_sample, bool stereo, int rate, bool reverse_channels) { - if (LockedData.DigiHandle == INVALID_AUDIO_HANDLE || filename == nullptr || !Find_File(filename)) { - return nullptr; - } - - void* data = nullptr; - int handle = Open_File(filename, 1); - - if (handle != INVALID_FILE_HANDLE) { - int data_size = File_Size(handle) + sizeof(AUDHeaderType); - data = malloc(data_size); - - if (data != nullptr) { - Sample_Read(handle, data, data_size); - } - - Close_File(handle); - Misc = data_size; - } - - return data; -} - -static int Sample_Read(int fh, void* buffer, int size) -{ - if (buffer == nullptr || fh == INVALID_AUDIO_HANDLE || size <= sizeof(AUDHeaderType)) { - return 0; - } - - AUDHeaderType header; - int actual_bytes_read = Read_File(fh, &header, sizeof(AUDHeaderType)); - int to_read = std::min(size - sizeof(AUDHeaderType), header.Size); - - actual_bytes_read += Read_File(fh, static_cast(buffer) + sizeof(AUDHeaderType), to_read); - - memcpy(buffer, &header, sizeof(AUDHeaderType)); - - return actual_bytes_read; -} - -void Free_Sample(const void* sample) -{ - if (sample != nullptr) { - free((void*)sample); - } -} - -bool Audio_Init(int bits_per_sample, bool stereo, int rate, bool reverse_channels) -{ - Init_Locked_Data(); - ALCenum error; ALCdevice* device = alcOpenDevice(nullptr); if (device == nullptr) { @@ -829,425 +135,113 @@ bool Audio_Init(int bits_per_sample, bool stereo, int rate, bool reverse_channel return false; } - LockedData.DigiHandle = 1; - - LockedData.UncompBuffer = malloc(UNCOMP_BUFFER_SIZE); - - if (LockedData.UncompBuffer == nullptr) { - //CCDebugString("Audio_Init - Failed to allocate UncompBuffer."); - return false; - } - - // Create placback buffers for all trackers. - for (int i = 0; i < MAX_SAMPLE_TRACKERS; ++i) { - SampleTrackerType* st = &LockedData.SampleTracker[i]; - - // Gen buffers on audio start? - // alGenBuffers(OPENAL_BUFFER_COUNT, st->AudioBuffers); - - if ((error = alGetError()) != AL_NO_ERROR) { - //CCDebugString(Get_OpenAL_Error(error)); - return false; - } - - alGenSources(1, &st->OpenALSource); - - if ((error = alGetError()) != AL_NO_ERROR) { - //CCDebugString(Get_OpenAL_Error(error)); - return false; - } - - st->Frequency = rate; - st->Format = Get_OpenAL_Format(bits_per_sample, stereo ? 2 : 1); - } - - SoundType = SFX_ALFX; - SampleType = SAMPLE_SB; - AudioDone = false; - return true; } -void Sound_End() -{ - if (OpenALContext == nullptr) { - for (int i = 0; i < MAX_SAMPLE_TRACKERS; ++i) { - Stop_Sample(i); - alDeleteSources(1, &LockedData.SampleTracker[i].OpenALSource); - } - } - - if (FileStreamBuffer != nullptr) { - free((void*)FileStreamBuffer); - FileStreamBuffer = nullptr; - } - - ALCdevice* device = alcGetContextsDevice(OpenALContext); - - alcMakeContextCurrent(nullptr); - alcDestroyContext(OpenALContext); - alcCloseDevice(device); - - if (LockedData.UncompBuffer != nullptr) { - free((void*)LockedData.UncompBuffer); - LockedData.UncompBuffer = nullptr; - } - - AudioDone = true; -} - -void Stop_Sample(int index) -{ - if (LockedData.DigiHandle != INVALID_AUDIO_HANDLE && index < MAX_SAMPLE_TRACKERS && !AudioDone) { - SampleTrackerType* st = &LockedData.SampleTracker[index]; - - if (st->Active || st->Loading) { - st->Active = false; - - if (!st->IsScore) { - st->Original = nullptr; - } - - st->Priority = 0; - - if (!st->Loading) { - ALint processed_count = -1; - alSourceStop(st->OpenALSource); - alGetSourcei(st->OpenALSource, AL_BUFFERS_PROCESSED, &processed_count); - - while (processed_count-- > 0) { - ALuint tmp; - alSourceUnqueueBuffers(st->OpenALSource, 1, &tmp); - } - - alDeleteBuffers(OPENAL_BUFFER_COUNT, st->AudioBuffers); - } - - st->Loading = false; - - if (st->FileHandle != INVALID_FILE_HANDLE) { - Close_File(st->FileHandle); - st->FileHandle = INVALID_FILE_HANDLE; - } - - st->QueueBuffer = nullptr; - } - } -} - -bool Sample_Status(int index) +void SoundImp_PauseSound() { - if (index < 0) { - return false; - } - - if (AudioDone) { - return false; - } - - if (LockedData.DigiHandle == INVALID_AUDIO_HANDLE || index >= MAX_SAMPLE_TRACKERS) { - return false; - } - - SampleTrackerType* st = &LockedData.SampleTracker[index]; - - if (st->Loading) { - return true; - } - - if (!st->Active) { - return false; + if (OpenALContext != nullptr) { + alcSuspendContext(OpenALContext); } - - ALint val; - alGetSourcei(st->OpenALSource, AL_SOURCE_STATE, &val); - - return val == AL_PLAYING; } -bool Is_Sample_Playing(const void* sample) +bool SoundImp_ResumeSound() { - if (AudioDone || sample == nullptr) { + if (OpenALContext == nullptr) { return false; } - for (int i = 0; i < MAX_SAMPLE_TRACKERS; ++i) { - if (sample == LockedData.SampleTracker[i].Original && Sample_Status(i)) { - return true; - } - } - - return false; -} - -void Stop_Sample_Playing(const void* sample) -{ - if (sample != nullptr) { - for (int i = 0; i < MAX_SAMPLE_TRACKERS; ++i) { - if (LockedData.SampleTracker[i].Original == sample) { - Stop_Sample(i); - break; - } - } - } -} - -int Play_Sample(const void* sample, int priority, int volume, signed short panloc) -{ - return Play_Sample_Handle(sample, priority, volume, panloc, Get_Free_Sample_Handle(priority)); -} - -static int Attempt_To_Play_Buffer(int id) -{ - SampleTrackerType* st = &LockedData.SampleTracker[id]; - - alSourcePlay(st->OpenALSource); - - // Playback was started so we set some needed sample tracker values. - st->Active = true; + alcProcessContext(OpenALContext); - return id; + return true; } -static int Play_Sample_Handle(const void* sample, int priority, int volume, signed short panloc, int id) +SampleTrackerTypeImp* SoundImp_Init_Sample(int bits_per_sample, bool stereo, int rate) { - if (Any_Locked()) { - return INVALID_AUDIO_HANDLE; - } - - if (!AudioDone) { - if (sample == nullptr || LockedData.DigiHandle == INVALID_AUDIO_HANDLE) { - return INVALID_AUDIO_HANDLE; - } - - if (id == INVALID_AUDIO_HANDLE) { - return INVALID_AUDIO_HANDLE; - } - - SampleTrackerType* st = &LockedData.SampleTracker[id]; - - // Read in the sample's header. - AUDHeaderType raw_header; - memcpy(&raw_header, sample, sizeof(raw_header)); - raw_header.Rate = le16toh(raw_header.Rate); - raw_header.Size = le32toh(raw_header.Size); - raw_header.UncompSize = le32toh(raw_header.UncompSize); - - // We don't support anything lower than 20000 hz. - if (raw_header.Rate < 24000 && raw_header.Rate > 20000) { - raw_header.Rate = 22050; - } - - // Set up basic sample tracker info. - st->Compression = SCompressType(raw_header.Compression); - st->Original = sample; - st->Odd = 0; - st->Reducer = 0; - st->Restart = false; - st->QueueBuffer = nullptr; - st->QueueSize = 0; - st->OriginalSize = raw_header.Size + sizeof(AUDHeaderType); - st->Priority = priority; - st->Service = 0; - st->Remainder = raw_header.Size; - st->Source = Add_Long_To_Pointer(sample, sizeof(AUDHeaderType)); - - // Compression is ADPCM so we need to init it's stream info. - if (st->Compression == SCOMP_SOS) { - st->sosinfo.wChannels = (raw_header.Flags & 1) + 1; - st->sosinfo.wBitSize = raw_header.Flags & 2 ? 16 : 8; - st->sosinfo.dwCompSize = raw_header.Size; - st->sosinfo.dwUnCompSize = raw_header.Size * (st->sosinfo.wBitSize / 4); - sosCODECInitStream(&st->sosinfo); - } - - // If the loaded sample doesn't match the sample tracker we need to adjust the tracker. - if (raw_header.Rate != st->Frequency - || Get_OpenAL_Format((raw_header.Flags & 2) ? 16 : 8, (raw_header.Flags & 1) ? 2 : 1) != st->Format) { - st->Active = false; - st->Service = 0; - st->MoreSource = false; - - // Set the new sample info. - st->Frequency = raw_header.Rate; - st->Format = Get_OpenAL_Format((raw_header.Flags & 2) ? 16 : 8, (raw_header.Flags & 1) ? 2 : 1); - } - - ALint source_status; - alGetSourcei(st->OpenALSource, AL_SOURCE_STATE, &source_status); - - // If the sample is already playing stop it. - if (source_status != AL_STOPPED) { - st->Active = false; - st->Service = 0; - st->MoreSource = false; - - ALint processed_count = -1; - alSourceStop(st->OpenALSource); - alGetSourcei(st->OpenALSource, AL_BUFFERS_PROCESSED, &processed_count); - - while (processed_count-- > 0) { - ALuint tmp; - alSourceUnqueueBuffers(st->OpenALSource, 1, &tmp); - } - - alDeleteBuffers(OPENAL_BUFFER_COUNT, st->AudioBuffers); - } - - alGenBuffers(OPENAL_BUFFER_COUNT, st->AudioBuffers); - int buffer_index = 0; - - while (buffer_index < OPENAL_BUFFER_COUNT) { - - int bytes_read = Sample_Copy(st, - &st->Source, - &st->Remainder, - &st->QueueBuffer, - &st->QueueSize, - ChunkBuffer, - BUFFER_CHUNK_SIZE, - st->Compression, - nullptr, - nullptr); + SampleTrackerTypeImp* st; + ALCenum error; - if (bytes_read > 0) { - alBufferData(st->AudioBuffers[buffer_index++], st->Format, ChunkBuffer, bytes_read, st->Frequency); - } + st = (SampleTrackerTypeImp*)malloc(sizeof(*st)); + if (st) { + if ((error = alGetError()) != AL_NO_ERROR) { + //CCDebugString(Get_OpenAL_Error(error)); + } else { + alGenSources(1, &st->OpenALSource); + alGenBuffers(OPENAL_BUFFER_COUNT, st->AudioBuffers); - if (bytes_read == BUFFER_CHUNK_SIZE) { - st->MoreSource = true; - st->OneShot = false; + if ((error = alGetError()) != AL_NO_ERROR) { + //CCDebugString(Get_OpenAL_Error(error)); } else { - st->MoreSource = false; - st->OneShot = true; - break; - } - } - - alSourceQueueBuffers(st->OpenALSource, buffer_index, st->AudioBuffers); - st->Service = 1; + st->Frequency = rate; + st->Format = Get_OpenAL_Format(bits_per_sample, stereo ? 2 : 1); + st->UnusedBufferCount = OPENAL_BUFFER_COUNT; - st->Volume = volume; - - alSourcef(st->OpenALSource, AL_GAIN, ((LockedData.SoundVolume * st->Volume) / 256) / 256.0f); - - if (!Start_Primary_Sound_Buffer(false)) { - //CCDebugString("Play_Sample_Handle - Can't start primary buffer!"); - return INVALID_AUDIO_HANDLE; + return st; + } } - return Attempt_To_Play_Buffer(id); + free(st); } - return INVALID_AUDIO_HANDLE; + return NULL; } -int Set_Score_Vol(int volume) +void SoundImp_Set_Sample_Attributes(SampleTrackerTypeImp* st, int bits_per_sample, bool stereo, int rate) { - int old = LockedData.ScoreVolume; - LockedData.ScoreVolume = volume; + ALenum new_format; - for (int i = 0; i < MAX_SAMPLE_TRACKERS; ++i) { - SampleTrackerType* st = &LockedData.SampleTracker[i]; + new_format = Get_OpenAL_Format(bits_per_sample, stereo ? 2 : 1); - if (st->IsScore & st->Active) { - alSourcef(st->OpenALSource, AL_GAIN, ((LockedData.ScoreVolume * st->Volume) / 256) / 256.0f); - } + if (rate != st->Frequency || new_format != st->Format) { + st->Frequency = rate; + st->Format = new_format; } - - return old; } -void Fade_Sample(int index, int ticks) +void SoundImp_Set_Sample_Volume(SampleTrackerTypeImp* st, unsigned int volume) { - if (Sample_Status(index)) { - SampleTrackerType* st = &LockedData.SampleTracker[index]; - - if (ticks > 0 && !st->Loading) { - st->Reducer = ((st->Volume / ticks) + 1); - } else { - Stop_Sample(index); - } - } + alSourcef(st->OpenALSource, AL_GAIN, volume / 65536.0); } -static int Get_Free_Sample_Handle(int priority) +void SoundImp_Shutdown() { - int index = 0; - - for (index = MAX_SAMPLE_TRACKERS - 1; index >= 0; --index) { - if (!LockedData.SampleTracker[index].Active && !LockedData.SampleTracker[index].Loading) { - if (StartingFileStream || !LockedData.SampleTracker[index].IsScore) { - break; - } - - StartingFileStream = true; - } - } - - if (index < 0) { - for (index = 0; index < MAX_SAMPLE_TRACKERS && LockedData.SampleTracker[index].Priority > priority; ++index) { - ; - } - - if (index == MAX_SAMPLE_TRACKERS) { - return INVALID_AUDIO_HANDLE; - } - - Stop_Sample(index); - } - - if (index == INVALID_AUDIO_HANDLE) { - return INVALID_AUDIO_HANDLE; - } - - if (LockedData.SampleTracker[index].FileHandle != INVALID_FILE_HANDLE) { - Close_File(LockedData.SampleTracker[index].FileHandle); - LockedData.SampleTracker[index].FileHandle = INVALID_FILE_HANDLE; - } - - if (LockedData.SampleTracker[index].Original) { - if (!LockedData.SampleTracker[index].IsScore) { - LockedData.SampleTracker[index].Original = 0; - } - } + ALCdevice* device = alcGetContextsDevice(OpenALContext); - LockedData.SampleTracker[index].IsScore = false; - return index; + alcMakeContextCurrent(nullptr); + alcDestroyContext(OpenALContext); + alcCloseDevice(device); } -int Get_Digi_Handle() +void SoundImp_Shutdown_Sample(SampleTrackerTypeImp* st) { - return LockedData.DigiHandle; + alDeleteBuffers(OPENAL_BUFFER_COUNT, st->AudioBuffers); + alDeleteSources(1, &st->OpenALSource); + free(st); } -void Restore_Sound_Buffers() +void SoundImp_Start_Sample(SampleTrackerTypeImp* st) { + alSourcePlay(st->OpenALSource); } -bool Set_Primary_Buffer_Format() +void SoundImp_Stop_Sample(SampleTrackerTypeImp* st) { - return true; -} + ALint processed_count = -1; + alSourceStop(st->OpenALSource); + alGetSourcei(st->OpenALSource, AL_BUFFERS_PROCESSED, &processed_count); -bool Start_Primary_Sound_Buffer(bool forced) -{ - if (OpenALContext == nullptr || !GameInFocus) { - return false; + while (processed_count-- > 0) { + ALuint tmp; + alSourceUnqueueBuffers(st->OpenALSource, 1, &tmp); } - alcProcessContext(OpenALContext); - - return true; + st->UnusedBufferCount = OPENAL_BUFFER_COUNT; } -void Stop_Primary_Sound_Buffer() +bool SoundImp_Sample_Status(SampleTrackerTypeImp* st) { - for (int i = 0; i < MAX_SAMPLE_TRACKERS; ++i) { - Stop_Sample(i); - } + ALint val; + alGetSourcei(st->OpenALSource, AL_SOURCE_STATE, &val); - if (OpenALContext != nullptr) { - alcSuspendContext(OpenALContext); - } + return val == AL_PLAYING; } diff --git a/common/video_sdl1.cpp b/common/video_sdl1.cpp index f9440cbd..1c190682 100644 --- a/common/video_sdl1.cpp +++ b/common/video_sdl1.cpp @@ -457,7 +457,7 @@ class VideoSurfaceSDL1 : public VideoSurface virtual void FillRect(const Rect& rect, unsigned char color) { - SDL_Rect rectSDL = {rect.X, rect.Y, rect.Width, rect.Height}; + SDL_Rect rectSDL = {rect.X, rect.Y, rect.Width + 1, rect.Height + 1}; SDL_FillRect(surface, &rectSDL, color); } diff --git a/common/video_sdl2.cpp b/common/video_sdl2.cpp index b84f7436..e5ca8aac 100644 --- a/common/video_sdl2.cpp +++ b/common/video_sdl2.cpp @@ -790,7 +790,8 @@ class VideoSurfaceSDL2 : public VideoSurface virtual void FillRect(const Rect& rect, unsigned char color) { - SDL_FillRect(surface, (SDL_Rect*)(&rect), color); + SDL_Rect rectSDL = {rect.X, rect.Y, rect.Width + 1, rect.Height + 1}; + SDL_FillRect(surface, &rectSDL, color); } void RenderSurface() diff --git a/common/wwkeyboard.cpp b/common/wwkeyboard.cpp index 8020d1f2..5e4edb0c 100644 --- a/common/wwkeyboard.cpp +++ b/common/wwkeyboard.cpp @@ -55,12 +55,7 @@ #include "video.h" #include "miscasm.h" #include -#include #include -#ifdef SDL_BUILD -#include -#include "sdl_keymap.h" -#endif #include "settings.h" #define ARRAY_SIZE(x) int(sizeof(x) / sizeof(x[0])) @@ -88,13 +83,13 @@ WWKeyboardClass::WWKeyboardClass(void) , Tail(0) , DownSkip(0) { -#if defined(_WIN32) - memset(KeyState, '\0', sizeof(KeyState)); -#endif - memset(DownState, '\0', sizeof(DownState)); } +WWKeyboardClass::~WWKeyboardClass() +{ +} + /*********************************************************************************************** * WWKeyboardClass::Buff_Get -- Lowlevel function to get a key from key buffer * * * @@ -275,92 +270,6 @@ bool WWKeyboardClass::Put_Mouse_Message(unsigned short vk_key, int x, int y, boo return (false); } -/*********************************************************************************************** - * WWKeyboardClass::To_ASCII -- Convert the key value into an ASCII representation. * - * * - * This routine will convert the key code specified into an ASCII value. This takes into * - * consideration the language and keyboard mapping of the host Windows system. * - * * - * INPUT: key -- The key code to convert into ASCII. * - * * - * OUTPUT: Returns with the key converted into ASCII. If the key has no ASCII equivalent, * - * then '\0' is returned. * - * * - * WARNINGS: none * - * * - * HISTORY: * - * 09/30/1996 JLB : Created. * - *=============================================================================================*/ -KeyASCIIType WWKeyboardClass::To_ASCII(unsigned short key) -{ - /* - ** Released keys never translate into an ASCII value. - */ - if (key & WWKEY_RLS_BIT) { - return KA_NONE; - } - - /* - ** Ask windows to translate the key into an ASCII equivalent. - */ - char buffer[10]; - int result = 1; - int scancode = 0; - -#if defined(SDL_BUILD) - key &= 0xFF; // drop all mods - - if (key > ARRAY_SIZE(sdl_keymap) / 2 - 1) { - return KA_NONE; - } - - if (SDL_GetModState() & KMOD_SHIFT) { - return sdl_keymap[key + ARRAY_SIZE(sdl_keymap) / 2]; - } else { - return sdl_keymap[key]; - } -#elif defined(_WIN32) - /* - ** Set the KeyState buffer to reflect the shift bits stored in the key value. - */ - if (key & WWKEY_SHIFT_BIT) { - KeyState[VK_SHIFT] = 0x80; - } - if (key & WWKEY_CTRL_BIT) { - KeyState[VK_CONTROL] = 0x80; - } - if (key & WWKEY_ALT_BIT) { - KeyState[VK_MENU] = 0x80; - } - - scancode = MapVirtualKeyA(key & 0xFF, 0); - result = ToAscii((UINT)(key & 0xFF), (UINT)scancode, (PBYTE)KeyState, (LPWORD)buffer, (UINT)0); - - /* - ** Restore the KeyState buffer back to pristine condition. - */ - if (key & WWKEY_SHIFT_BIT) { - KeyState[VK_SHIFT] = 0; - } - if (key & WWKEY_CTRL_BIT) { - KeyState[VK_CONTROL] = 0; - } - if (key & WWKEY_ALT_BIT) { - KeyState[VK_MENU] = 0; - } -#endif - - /* - ** If Windows could not perform the translation as expected, then - ** return with a null ASCII value. - */ - if (result != 1) { - return KA_NONE; - } - - return (KeyASCIIType)(buffer[0]); -} - /*********************************************************************************************** * WWKeyboardClass::Down -- Checks to see if the specified key is being held down. * * * @@ -534,326 +443,28 @@ bool WWKeyboardClass::Is_Buffer_Empty(void) const * HISTORY: * * 09/30/1996 JLB : Created. * *=============================================================================================*/ -void Process_Network(); - -void WWKeyboardClass::Fill_Buffer_From_System(void) -{ -#ifdef SDL_BUILD -#ifdef NETWORKING - Process_Network(); -#endif - SDL_Event event; - - while (!Is_Buffer_Full() && SDL_PollEvent(&event)) { - unsigned short key; - switch (event.type) { - case SDL_QUIT: - exit(0); - break; - case SDL_KEYDOWN: -#ifdef SDL2_BUILD - Put_Key_Message(event.key.keysym.scancode, false); -#else - Put_Key_Message(event.key.keysym.sym, false); -#endif - break; - case SDL_KEYUP: -#ifdef SDL2_BUILD - if (event.key.keysym.scancode == SDL_SCANCODE_RETURN && Down(VK_MENU)) { - Toggle_Video_Fullscreen(); - } else { - Put_Key_Message(event.key.keysym.scancode, true); - } -#else - Put_Key_Message(event.key.keysym.sym, true); -#endif - break; - case SDL_MOUSEMOTION: - Move_Video_Mouse(static_cast(event.motion.xrel), static_cast(event.motion.yrel)); - break; - case SDL_MOUSEBUTTONDOWN: - case SDL_MOUSEBUTTONUP: { - int x, y; - - switch (event.button.button) { - case SDL_BUTTON_LEFT: - default: - key = VK_LBUTTON; - break; - case SDL_BUTTON_RIGHT: - key = VK_RBUTTON; - break; - case SDL_BUTTON_MIDDLE: - key = VK_MBUTTON; - break; -#ifdef SDL1_BUILD - case SDL_BUTTON_WHEELUP: - key = VK_MOUSEWHEEL_UP; - break; - case SDL_BUTTON_WHEELDOWN: - key = VK_MOUSEWHEEL_DOWN; - break; -#endif - } - - if (Settings.Mouse.RawInput || Is_Gamepad_Active()) { - Get_Video_Mouse(x, y); - } else { - float scale_x = 1.0f, scale_y = 1.0f; - Get_Video_Scale(scale_x, scale_y); - x = event.button.x / scale_x; - y = event.button.y / scale_y; - } - - Put_Mouse_Message(key, x, y, event.type == SDL_MOUSEBUTTONDOWN ? false : true); - } break; -#ifdef SDL2_BUILD - case SDL_WINDOWEVENT: - switch (event.window.event) { - case SDL_WINDOWEVENT_EXPOSED: - case SDL_WINDOWEVENT_RESTORED: - case SDL_WINDOWEVENT_FOCUS_GAINED: - Focus_Restore(); - break; - case SDL_WINDOWEVENT_HIDDEN: - case SDL_WINDOWEVENT_MINIMIZED: - case SDL_WINDOWEVENT_FOCUS_LOST: - Focus_Loss(); - break; - } - break; - case SDL_MOUSEWHEEL: - if (event.wheel.y > 0) { // scroll up - Put_Key_Message(VK_MOUSEWHEEL_UP, false); - } else if (event.wheel.y < 0) { // scroll down - Put_Key_Message(VK_MOUSEWHEEL_DOWN, false); - } - break; - case SDL_CONTROLLERDEVICEREMOVED: - if (GameController != nullptr) { - const SDL_GameController* removedController = SDL_GameControllerFromInstanceID(event.jdevice.which); - if (removedController == GameController) { - SDL_GameControllerClose(GameController); - GameController = nullptr; - } - } - break; - case SDL_CONTROLLERDEVICEADDED: - if (GameController == nullptr) { - GameController = SDL_GameControllerOpen(event.jdevice.which); - } - break; - case SDL_CONTROLLERAXISMOTION: - Handle_Controller_Axis_Event(event.caxis); - break; - case SDL_CONTROLLERBUTTONDOWN: - case SDL_CONTROLLERBUTTONUP: - Handle_Controller_Button_Event(event.cbutton); - break; -#endif - } - } -#ifdef SDL2_BUILD - if (Is_Gamepad_Active()) { - Process_Controller_Axis_Motion(); - } -#endif -#elif defined(_WIN32) - if (!Is_Buffer_Full()) { - MSG msg; - while (PeekMessageA(&msg, NULL, 0, 0, PM_NOREMOVE)) { - if (!GetMessageA(&msg, NULL, 0, 0)) { - return; - } - TranslateMessage(&msg); - DispatchMessageA(&msg); - } - } -#endif -} - -#ifdef SDL2_BUILD bool WWKeyboardClass::Is_Gamepad_Active() { - return GameController != nullptr; + return false; } void WWKeyboardClass::Open_Controller() { - for (int i = 0; i < SDL_NumJoysticks(); ++i) { - if (SDL_IsGameController(i)) { - GameController = SDL_GameControllerOpen(i); - } - } } void WWKeyboardClass::Close_Controller() { - if (SDL_GameControllerGetAttached(GameController)) { - SDL_GameControllerClose(GameController); - GameController = nullptr; - } -} - -void WWKeyboardClass::Process_Controller_Axis_Motion() -{ - const uint32_t currentTime = SDL_GetTicks(); - const float deltaTime = currentTime - LastControllerTime; - LastControllerTime = currentTime; - - if (ControllerLeftXAxis != 0 || ControllerLeftYAxis != 0) { - const int16_t xSign = (ControllerLeftXAxis > 0) - (ControllerLeftXAxis < 0); - const int16_t ySign = (ControllerLeftYAxis > 0) - (ControllerLeftYAxis < 0); - - float movX = std::pow(std::abs(ControllerLeftXAxis), CONTROLLER_AXIS_SPEEDUP) * xSign * deltaTime - * Settings.Mouse.ControllerPointerSpeed / CONTROLLER_SPEED_MOD * ControllerSpeedBoost; - float movY = std::pow(std::abs(ControllerLeftYAxis), CONTROLLER_AXIS_SPEEDUP) * ySign * deltaTime - * Settings.Mouse.ControllerPointerSpeed / CONTROLLER_SPEED_MOD * ControllerSpeedBoost; - - Move_Video_Mouse(movX, movY); - } -} - -void WWKeyboardClass::Handle_Controller_Axis_Event(const SDL_ControllerAxisEvent& motion) -{ - AnalogScrollActive = false; - ScrollDirType directionX = SDIR_NONE; - ScrollDirType directionY = SDIR_NONE; - - if (motion.axis == SDL_CONTROLLER_AXIS_LEFTX) { - if (std::abs(motion.value) > CONTROLLER_L_DEADZONE) - ControllerLeftXAxis = motion.value; - else - ControllerLeftXAxis = 0; - } else if (motion.axis == SDL_CONTROLLER_AXIS_LEFTY) { - if (std::abs(motion.value) > CONTROLLER_L_DEADZONE) - ControllerLeftYAxis = motion.value; - else - ControllerLeftYAxis = 0; - } else if (motion.axis == SDL_CONTROLLER_AXIS_RIGHTX) { - if (std::abs(motion.value) > CONTROLLER_R_DEADZONE) - ControllerRightXAxis = motion.value; - else - ControllerRightXAxis = 0; - } else if (motion.axis == SDL_CONTROLLER_AXIS_RIGHTY) { - if (std::abs(motion.value) > CONTROLLER_R_DEADZONE) - ControllerRightYAxis = motion.value; - else - ControllerRightYAxis = 0; - } else if (motion.axis == SDL_CONTROLLER_AXIS_TRIGGERRIGHT) { - if (std::abs(motion.value) > CONTROLLER_TRIGGER_R_DEADZONE) - ControllerSpeedBoost = 1 + (static_cast(motion.value) / 32767) * CONTROLLER_TRIGGER_SPEEDUP; - else - ControllerSpeedBoost = 1; - } - - if (ControllerRightXAxis != 0) { - AnalogScrollActive = true; - directionX = ControllerRightXAxis > 0 ? SDIR_E : SDIR_W; - } - if (ControllerRightYAxis != 0) { - AnalogScrollActive = true; - directionY = ControllerRightYAxis > 0 ? SDIR_S : SDIR_N; - } - - if (directionX == SDIR_E && directionY == SDIR_N) { - ScrollDirection = SDIR_NE; - } else if (directionX == SDIR_E && directionY == SDIR_S) { - ScrollDirection = SDIR_SE; - } else if (directionX == SDIR_W && directionY == SDIR_N) { - ScrollDirection = SDIR_NW; - } else if (directionX == SDIR_W && directionY == SDIR_S) { - ScrollDirection = SDIR_SW; - } else if (directionX == SDIR_E) { - ScrollDirection = SDIR_E; - } else if (directionX == SDIR_W) { - ScrollDirection = SDIR_W; - } else if (directionY == SDIR_S) { - ScrollDirection = SDIR_S; - } else if (directionY == SDIR_N) { - ScrollDirection = SDIR_N; - } -} - -void WWKeyboardClass::Handle_Controller_Button_Event(const SDL_ControllerButtonEvent& button) -{ - bool keyboardPress = false; - bool mousePress = false; - unsigned short key; - SDL_Scancode scancode; - - switch (button.button) { - case SDL_CONTROLLER_BUTTON_A: - mousePress = true; - key = VK_LBUTTON; - break; - case SDL_CONTROLLER_BUTTON_B: - mousePress = true; - key = VK_RBUTTON; - break; - case SDL_CONTROLLER_BUTTON_X: - keyboardPress = true; - scancode = SDL_SCANCODE_G; - break; - case SDL_CONTROLLER_BUTTON_Y: - keyboardPress = true; - scancode = SDL_SCANCODE_F; - break; - case SDL_CONTROLLER_BUTTON_BACK: - keyboardPress = true; - scancode = SDL_SCANCODE_ESCAPE; - break; - case SDL_CONTROLLER_BUTTON_START: - keyboardPress = true; - scancode = SDL_SCANCODE_RETURN; - break; - case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: - keyboardPress = true; - scancode = SDL_SCANCODE_LCTRL; - break; - case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: - keyboardPress = true; - scancode = SDL_SCANCODE_LALT; - break; - case SDL_CONTROLLER_BUTTON_DPAD_UP: - keyboardPress = true; - scancode = SDL_SCANCODE_1; - break; - case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: - keyboardPress = true; - scancode = SDL_SCANCODE_2; - break; - case SDL_CONTROLLER_BUTTON_DPAD_DOWN: - keyboardPress = true; - scancode = SDL_SCANCODE_3; - break; - case SDL_CONTROLLER_BUTTON_DPAD_LEFT: - keyboardPress = true; - scancode = SDL_SCANCODE_4; - break; - default: - break; - } - - if (keyboardPress) { - Put_Key_Message(scancode, button.state == SDL_RELEASED); - } else if (mousePress) { - int x, y; - Get_Video_Mouse(x, y); - Put_Mouse_Message(key, x, y, button.state == SDL_RELEASED); - } } bool WWKeyboardClass::Is_Analog_Scroll_Active() { - return AnalogScrollActive; + return false; } unsigned char WWKeyboardClass::Get_Scroll_Direction() { - return ScrollDirection; + return SDIR_NONE; } -#endif /*********************************************************************************************** * WWKeyboardClass::Clear -- Clears the keyboard buffer. * diff --git a/common/wwkeyboard.h b/common/wwkeyboard.h index a18c047a..30d98027 100644 --- a/common/wwkeyboard.h +++ b/common/wwkeyboard.h @@ -860,30 +860,25 @@ typedef enum ScrollDirType : unsigned char class WWKeyboardClass { -public: +protected: /* Define the base constructor and destructors for the class */ WWKeyboardClass(); + virtual ~WWKeyboardClass(); +public: /* Define the functions which work with the Keyboard Class */ KeyNumType Check(void) const; KeyNumType Get(void); bool Put(unsigned short key); void Clear(void); - KeyASCIIType To_ASCII(unsigned short num); + virtual KeyASCIIType To_ASCII(unsigned short num) = 0; bool Down(unsigned short key); -#ifdef SDL2_BUILD - bool Is_Gamepad_Active(); - void Open_Controller(); - void Close_Controller(); - bool Is_Analog_Scroll_Active(); - unsigned char Get_Scroll_Direction(); -#elif defined(SDL1_BUILD) - bool Is_Gamepad_Active() - { - return false; - } -#endif + virtual bool Is_Gamepad_Active(); + virtual void Open_Controller(); + virtual void Close_Controller(); + virtual bool Is_Analog_Scroll_Active(); + virtual unsigned char Get_Scroll_Direction(); #if defined(_WIN32) && !defined(SDL_BUILD) /* Define the main hook for the message processing loop. */ @@ -895,15 +890,12 @@ class WWKeyboardClass int MouseQX; int MouseQY; -private: - /* - ** This is a keyboard state array that is used to aid in translating - ** KN_ keys into KA_ keys. - */ -#if defined(_WIN32) - unsigned char KeyState[256]; -#endif +protected: + bool Is_Buffer_Full(void) const; + bool Put_Key_Message(unsigned short vk_key, bool release = false); + bool Put_Mouse_Message(unsigned short vk_key, int x, int y, bool release = false); +private: /* ** This is the circular keyboard holding buffer. It holds the VK key and ** the current shift state at the time the key was added to the queue. @@ -914,12 +906,9 @@ class WWKeyboardClass unsigned short Fetch_Element(void); unsigned short Peek_Element(void) const; bool Put_Element(unsigned short val); - bool Is_Buffer_Full(void) const; bool Is_Buffer_Empty(void) const; static bool Is_Mouse_Key(unsigned short key); - void Fill_Buffer_From_System(void); - bool Put_Key_Message(unsigned short vk_key, bool release = false); - bool Put_Mouse_Message(unsigned short vk_key, int x, int y, bool release = false); + virtual void Fill_Buffer_From_System(void) = 0; int Available_Buffer_Room(void) const; /* @@ -934,36 +923,8 @@ class WWKeyboardClass */ uint8_t DownState[0x2000]; // (UINT16_MAX / 8) + 1 int DownSkip; - -#ifdef SDL2_BUILD - void Handle_Controller_Axis_Event(const SDL_ControllerAxisEvent& motion); - void Handle_Controller_Button_Event(const SDL_ControllerButtonEvent& button); - void Process_Controller_Axis_Motion(); - - // used to convert user-friendly pointer speed values into more useable ones - static constexpr float CONTROLLER_SPEED_MOD = 2000000.0f; - // bigger value correndsponds to faster pointer movement speed with bigger stick axis values - static constexpr float CONTROLLER_AXIS_SPEEDUP = 1.03f; - // speedup value while the trigger is pressed - static constexpr int CONTROLLER_TRIGGER_SPEEDUP = 2; - - enum - { - CONTROLLER_L_DEADZONE = 4000, - CONTROLLER_R_DEADZONE = 6000, - CONTROLLER_TRIGGER_R_DEADZONE = 3000 - }; - - SDL_GameController* GameController = nullptr; - int16_t ControllerLeftXAxis = 0; - int16_t ControllerLeftYAxis = 0; - int16_t ControllerRightXAxis = 0; - int16_t ControllerRightYAxis = 0; - uint32_t LastControllerTime = 0; - float ControllerSpeedBoost = 1; - bool AnalogScrollActive = false; - ScrollDirType ScrollDirection = SDIR_NONE; -#endif }; +WWKeyboardClass* CreateWWKeyboardClass(void); + #endif diff --git a/common/wwkeyboard_sdl1.cpp b/common/wwkeyboard_sdl1.cpp new file mode 100644 index 00000000..4a6a2b9f --- /dev/null +++ b/common/wwkeyboard_sdl1.cpp @@ -0,0 +1,113 @@ +// +// Copyright 2020 Electronic Arts Inc. +// +// TiberianDawn.DLL and RedAlert.dll and corresponding source code 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. + +// TiberianDawn.DLL and RedAlert.dll and corresponding source code is distributed +// in the hope that it will be useful, but with permitted additional restrictions +// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT +// distributed with this program. You should have received a copy of the +// GNU General Public License along with permitted additional restrictions +// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection + +#include "macros.h" +#include "wwkeyboard_sdl1.h" +#include "video.h" +#include "sdl_keymap.h" +#include "settings.h" +#include + +void Focus_Loss(); +void Focus_Restore(); +void Process_Network(); + +WWKeyboardClassSDL1::~WWKeyboardClassSDL1() +{ +} + +void WWKeyboardClassSDL1::Fill_Buffer_From_System(void) +{ +#ifdef NETWORKING + Process_Network(); +#endif + SDL_Event event; + + while (!Is_Buffer_Full() && SDL_PollEvent(&event)) { + unsigned short key; + switch (event.type) { + case SDL_QUIT: + exit(0); + break; + case SDL_KEYDOWN: + Put_Key_Message(event.key.keysym.sym, false); + break; + case SDL_KEYUP: + Put_Key_Message(event.key.keysym.sym, true); + break; + case SDL_MOUSEMOTION: + Move_Video_Mouse(static_cast(event.motion.xrel), static_cast(event.motion.yrel)); + break; + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: { + int x, y; + + switch (event.button.button) { + case SDL_BUTTON_LEFT: + default: + key = VK_LBUTTON; + break; + case SDL_BUTTON_RIGHT: + key = VK_RBUTTON; + break; + case SDL_BUTTON_MIDDLE: + key = VK_MBUTTON; + break; + case SDL_BUTTON_WHEELUP: + key = VK_MOUSEWHEEL_UP; + break; + case SDL_BUTTON_WHEELDOWN: + key = VK_MOUSEWHEEL_DOWN; + break; + } + + if (Settings.Mouse.RawInput || Is_Gamepad_Active()) { + Get_Video_Mouse(x, y); + } else { + float scale_x = 1.0f, scale_y = 1.0f; + Get_Video_Scale(scale_x, scale_y); + x = event.button.x / scale_x; + y = event.button.y / scale_y; + } + + Put_Mouse_Message(key, x, y, event.type == SDL_MOUSEBUTTONDOWN ? false : true); + } break; + } + } +} + +KeyASCIIType WWKeyboardClassSDL1::To_ASCII(unsigned short key) +{ + if (key & WWKEY_RLS_BIT) { + return KA_NONE; + } + + key &= 0xFF; // drop all mods + + if (key > ARRAY_SIZE(sdl_keymap) / 2 - 1) { + return KA_NONE; + } + + if (SDL_GetModState() & KMOD_SHIFT) { + return sdl_keymap[key + ARRAY_SIZE(sdl_keymap) / 2]; + } else { + return sdl_keymap[key]; + } +} + +WWKeyboardClass* CreateWWKeyboardClass(void) +{ + return new WWKeyboardClassSDL1; +} diff --git a/common/wwkeyboard_sdl1.h b/common/wwkeyboard_sdl1.h new file mode 100644 index 00000000..1757f812 --- /dev/null +++ b/common/wwkeyboard_sdl1.h @@ -0,0 +1,10 @@ +#include "wwkeyboard.h" + +class WWKeyboardClassSDL1 : public WWKeyboardClass +{ +public: + virtual ~WWKeyboardClassSDL1(); + + virtual void Fill_Buffer_From_System(void); + virtual KeyASCIIType To_ASCII(unsigned short key); +}; diff --git a/common/wwkeyboard_sdl2.cpp b/common/wwkeyboard_sdl2.cpp new file mode 100644 index 00000000..30f17451 --- /dev/null +++ b/common/wwkeyboard_sdl2.cpp @@ -0,0 +1,338 @@ +// +// Copyright 2020 Electronic Arts Inc. +// +// TiberianDawn.DLL and RedAlert.dll and corresponding source code 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. + +// TiberianDawn.DLL and RedAlert.dll and corresponding source code is distributed +// in the hope that it will be useful, but with permitted additional restrictions +// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT +// distributed with this program. You should have received a copy of the +// GNU General Public License along with permitted additional restrictions +// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection + +#include "macros.h" +#include "wwkeyboard_sdl2.h" +#include "video.h" +#include "sdl_keymap.h" +#include "settings.h" +#include +#include + +void Focus_Loss(); +void Focus_Restore(); +void Process_Network(); + +WWKeyboardClassSDL2::~WWKeyboardClassSDL2() +{ +} + +void WWKeyboardClassSDL2::Fill_Buffer_From_System(void) +{ +#ifdef NETWORKING + Process_Network(); +#endif + SDL_Event event; + + while (!Is_Buffer_Full() && SDL_PollEvent(&event)) { + unsigned short key; + switch (event.type) { + case SDL_QUIT: + exit(0); + break; + case SDL_KEYDOWN: + Put_Key_Message(event.key.keysym.scancode, false); + break; + case SDL_KEYUP: + if (event.key.keysym.scancode == SDL_SCANCODE_RETURN && Down(VK_MENU)) { + Toggle_Video_Fullscreen(); + } else { + Put_Key_Message(event.key.keysym.scancode, true); + } + break; + case SDL_MOUSEMOTION: + Move_Video_Mouse(static_cast(event.motion.xrel), static_cast(event.motion.yrel)); + break; + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: { + int x, y; + + switch (event.button.button) { + case SDL_BUTTON_LEFT: + default: + key = VK_LBUTTON; + break; + case SDL_BUTTON_RIGHT: + key = VK_RBUTTON; + break; + case SDL_BUTTON_MIDDLE: + key = VK_MBUTTON; + break; + } + + if (Settings.Mouse.RawInput || Is_Gamepad_Active()) { + Get_Video_Mouse(x, y); + } else { + float scale_x = 1.0f, scale_y = 1.0f; + Get_Video_Scale(scale_x, scale_y); + x = event.button.x / scale_x; + y = event.button.y / scale_y; + } + + Put_Mouse_Message(key, x, y, event.type == SDL_MOUSEBUTTONDOWN ? false : true); + } break; + case SDL_WINDOWEVENT: + switch (event.window.event) { + case SDL_WINDOWEVENT_EXPOSED: + case SDL_WINDOWEVENT_RESTORED: + case SDL_WINDOWEVENT_FOCUS_GAINED: + Focus_Restore(); + break; + case SDL_WINDOWEVENT_HIDDEN: + case SDL_WINDOWEVENT_MINIMIZED: + case SDL_WINDOWEVENT_FOCUS_LOST: + Focus_Loss(); + break; + } + break; + case SDL_MOUSEWHEEL: + if (event.wheel.y > 0) { // scroll up + Put_Key_Message(VK_MOUSEWHEEL_UP, false); + } else if (event.wheel.y < 0) { // scroll down + Put_Key_Message(VK_MOUSEWHEEL_DOWN, false); + } + break; + case SDL_CONTROLLERDEVICEREMOVED: + if (GameController != nullptr) { + const SDL_GameController* removedController = SDL_GameControllerFromInstanceID(event.jdevice.which); + if (removedController == GameController) { + SDL_GameControllerClose(GameController); + GameController = nullptr; + } + } + break; + case SDL_CONTROLLERDEVICEADDED: + if (GameController == nullptr) { + GameController = SDL_GameControllerOpen(event.jdevice.which); + } + break; + case SDL_CONTROLLERAXISMOTION: + Handle_Controller_Axis_Event(event.caxis); + break; + case SDL_CONTROLLERBUTTONDOWN: + case SDL_CONTROLLERBUTTONUP: + Handle_Controller_Button_Event(event.cbutton); + break; + } + } + if (Is_Gamepad_Active()) { + Process_Controller_Axis_Motion(); + } +} + +bool WWKeyboardClassSDL2::Is_Gamepad_Active() +{ + return GameController != nullptr; +} + +void WWKeyboardClassSDL2::Open_Controller() +{ + for (int i = 0; i < SDL_NumJoysticks(); ++i) { + if (SDL_IsGameController(i)) { + GameController = SDL_GameControllerOpen(i); + } + } +} + +void WWKeyboardClassSDL2::Close_Controller() +{ + if (SDL_GameControllerGetAttached(GameController)) { + SDL_GameControllerClose(GameController); + GameController = nullptr; + } +} + +void WWKeyboardClassSDL2::Process_Controller_Axis_Motion() +{ + const uint32_t currentTime = SDL_GetTicks(); + const float deltaTime = currentTime - LastControllerTime; + LastControllerTime = currentTime; + + if (ControllerLeftXAxis != 0 || ControllerLeftYAxis != 0) { + const int16_t xSign = (ControllerLeftXAxis > 0) - (ControllerLeftXAxis < 0); + const int16_t ySign = (ControllerLeftYAxis > 0) - (ControllerLeftYAxis < 0); + + float movX = std::pow(std::abs(ControllerLeftXAxis), CONTROLLER_AXIS_SPEEDUP) * xSign * deltaTime + * Settings.Mouse.ControllerPointerSpeed / CONTROLLER_SPEED_MOD * ControllerSpeedBoost; + float movY = std::pow(std::abs(ControllerLeftYAxis), CONTROLLER_AXIS_SPEEDUP) * ySign * deltaTime + * Settings.Mouse.ControllerPointerSpeed / CONTROLLER_SPEED_MOD * ControllerSpeedBoost; + + Move_Video_Mouse(movX, movY); + } +} + +void WWKeyboardClassSDL2::Handle_Controller_Axis_Event(const SDL_ControllerAxisEvent& motion) +{ + AnalogScrollActive = false; + ScrollDirType directionX = SDIR_NONE; + ScrollDirType directionY = SDIR_NONE; + + if (motion.axis == SDL_CONTROLLER_AXIS_LEFTX) { + if (std::abs(motion.value) > CONTROLLER_L_DEADZONE) + ControllerLeftXAxis = motion.value; + else + ControllerLeftXAxis = 0; + } else if (motion.axis == SDL_CONTROLLER_AXIS_LEFTY) { + if (std::abs(motion.value) > CONTROLLER_L_DEADZONE) + ControllerLeftYAxis = motion.value; + else + ControllerLeftYAxis = 0; + } else if (motion.axis == SDL_CONTROLLER_AXIS_RIGHTX) { + if (std::abs(motion.value) > CONTROLLER_R_DEADZONE) + ControllerRightXAxis = motion.value; + else + ControllerRightXAxis = 0; + } else if (motion.axis == SDL_CONTROLLER_AXIS_RIGHTY) { + if (std::abs(motion.value) > CONTROLLER_R_DEADZONE) + ControllerRightYAxis = motion.value; + else + ControllerRightYAxis = 0; + } else if (motion.axis == SDL_CONTROLLER_AXIS_TRIGGERRIGHT) { + if (std::abs(motion.value) > CONTROLLER_TRIGGER_R_DEADZONE) + ControllerSpeedBoost = 1 + (static_cast(motion.value) / 32767) * CONTROLLER_TRIGGER_SPEEDUP; + else + ControllerSpeedBoost = 1; + } + + if (ControllerRightXAxis != 0) { + AnalogScrollActive = true; + directionX = ControllerRightXAxis > 0 ? SDIR_E : SDIR_W; + } + if (ControllerRightYAxis != 0) { + AnalogScrollActive = true; + directionY = ControllerRightYAxis > 0 ? SDIR_S : SDIR_N; + } + + if (directionX == SDIR_E && directionY == SDIR_N) { + ScrollDirection = SDIR_NE; + } else if (directionX == SDIR_E && directionY == SDIR_S) { + ScrollDirection = SDIR_SE; + } else if (directionX == SDIR_W && directionY == SDIR_N) { + ScrollDirection = SDIR_NW; + } else if (directionX == SDIR_W && directionY == SDIR_S) { + ScrollDirection = SDIR_SW; + } else if (directionX == SDIR_E) { + ScrollDirection = SDIR_E; + } else if (directionX == SDIR_W) { + ScrollDirection = SDIR_W; + } else if (directionY == SDIR_S) { + ScrollDirection = SDIR_S; + } else if (directionY == SDIR_N) { + ScrollDirection = SDIR_N; + } +} + +void WWKeyboardClassSDL2::Handle_Controller_Button_Event(const SDL_ControllerButtonEvent& button) +{ + bool keyboardPress = false; + bool mousePress = false; + unsigned short key; + SDL_Scancode scancode; + + switch (button.button) { + case SDL_CONTROLLER_BUTTON_A: + mousePress = true; + key = VK_LBUTTON; + break; + case SDL_CONTROLLER_BUTTON_B: + mousePress = true; + key = VK_RBUTTON; + break; + case SDL_CONTROLLER_BUTTON_X: + keyboardPress = true; + scancode = SDL_SCANCODE_G; + break; + case SDL_CONTROLLER_BUTTON_Y: + keyboardPress = true; + scancode = SDL_SCANCODE_F; + break; + case SDL_CONTROLLER_BUTTON_BACK: + keyboardPress = true; + scancode = SDL_SCANCODE_ESCAPE; + break; + case SDL_CONTROLLER_BUTTON_START: + keyboardPress = true; + scancode = SDL_SCANCODE_RETURN; + break; + case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: + keyboardPress = true; + scancode = SDL_SCANCODE_LCTRL; + break; + case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: + keyboardPress = true; + scancode = SDL_SCANCODE_LALT; + break; + case SDL_CONTROLLER_BUTTON_DPAD_UP: + keyboardPress = true; + scancode = SDL_SCANCODE_1; + break; + case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: + keyboardPress = true; + scancode = SDL_SCANCODE_2; + break; + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: + keyboardPress = true; + scancode = SDL_SCANCODE_3; + break; + case SDL_CONTROLLER_BUTTON_DPAD_LEFT: + keyboardPress = true; + scancode = SDL_SCANCODE_4; + break; + default: + break; + } + + if (keyboardPress) { + Put_Key_Message(scancode, button.state == SDL_RELEASED); + } else if (mousePress) { + int x, y; + Get_Video_Mouse(x, y); + Put_Mouse_Message(key, x, y, button.state == SDL_RELEASED); + } +} + +bool WWKeyboardClassSDL2::Is_Analog_Scroll_Active() +{ + return AnalogScrollActive; +} + +unsigned char WWKeyboardClassSDL2::Get_Scroll_Direction() +{ + return ScrollDirection; +} + +KeyASCIIType WWKeyboardClassSDL2::To_ASCII(unsigned short key) +{ + if (key & WWKEY_RLS_BIT) { + return KA_NONE; + } + + key &= 0xFF; // drop all mods + + if (key > ARRAY_SIZE(sdl_keymap) / 2 - 1) { + return KA_NONE; + } + + if (SDL_GetModState() & KMOD_SHIFT) { + return sdl_keymap[key + ARRAY_SIZE(sdl_keymap) / 2]; + } else { + return sdl_keymap[key]; + } +} + +WWKeyboardClass* CreateWWKeyboardClass(void) +{ + return new WWKeyboardClassSDL2; +} diff --git a/common/wwkeyboard_sdl2.h b/common/wwkeyboard_sdl2.h new file mode 100644 index 00000000..fac58d57 --- /dev/null +++ b/common/wwkeyboard_sdl2.h @@ -0,0 +1,44 @@ +#include "wwkeyboard.h" + +class WWKeyboardClassSDL2 : public WWKeyboardClass +{ +public: + virtual ~WWKeyboardClassSDL2(); + + virtual void Fill_Buffer_From_System(void); + virtual bool Is_Gamepad_Active(); + virtual void Open_Controller(); + virtual void Close_Controller(); + virtual bool Is_Analog_Scroll_Active(); + virtual unsigned char Get_Scroll_Direction(); + virtual KeyASCIIType To_ASCII(unsigned short key); + +private: + void Handle_Controller_Axis_Event(const SDL_ControllerAxisEvent& motion); + void Handle_Controller_Button_Event(const SDL_ControllerButtonEvent& button); + void Process_Controller_Axis_Motion(); + + // used to convert user-friendly pointer speed values into more useable ones + static constexpr float CONTROLLER_SPEED_MOD = 2000000.0f; + // bigger value correndsponds to faster pointer movement speed with bigger stick axis values + static constexpr float CONTROLLER_AXIS_SPEEDUP = 1.03f; + // speedup value while the trigger is pressed + static constexpr int CONTROLLER_TRIGGER_SPEEDUP = 2; + + enum + { + CONTROLLER_L_DEADZONE = 4000, + CONTROLLER_R_DEADZONE = 6000, + CONTROLLER_TRIGGER_R_DEADZONE = 3000 + }; + + SDL_GameController* GameController = nullptr; + int16_t ControllerLeftXAxis = 0; + int16_t ControllerLeftYAxis = 0; + int16_t ControllerRightXAxis = 0; + int16_t ControllerRightYAxis = 0; + uint32_t LastControllerTime = 0; + float ControllerSpeedBoost = 1; + bool AnalogScrollActive = false; + ScrollDirType ScrollDirection = SDIR_NONE; +}; diff --git a/common/wwkeyboard_sdl32.cpp b/common/wwkeyboard_sdl32.cpp new file mode 100644 index 00000000..e6b19db4 --- /dev/null +++ b/common/wwkeyboard_sdl32.cpp @@ -0,0 +1,46 @@ +// +// Copyright 2020 Electronic Arts Inc. +// +// TiberianDawn.DLL and RedAlert.dll and corresponding source code 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. + +// TiberianDawn.DLL and RedAlert.dll and corresponding source code is distributed +// in the hope that it will be useful, but with permitted additional restrictions +// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT +// distributed with this program. You should have received a copy of the +// GNU General Public License along with permitted additional restrictions +// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection + +#include "wwkeyboard_sdl1.h" +#include "video.h" +#include "settings.h" +#include + +void Focus_Loss(); +void Focus_Restore(); +void Process_Network(); + +WWKeyboardClassWin32::~WWKeyboardClassWin32() +{ +} + +void WWKeyboardClassWin32::Fill_Buffer_From_System(void) +{ + if (!Is_Buffer_Full()) { + MSG msg; + while (PeekMessageA(&msg, NULL, 0, 0, PM_NOREMOVE)) { + if (!GetMessageA(&msg, NULL, 0, 0)) { + return; + } + TranslateMessage(&msg); + DispatchMessageA(&msg); + } + } +} + +WWKeyboardClass* CreateWWKeyboardClass(void) +{ + return new WWKeyboardClassWin32; +} diff --git a/common/wwkeyboard_win32.cpp b/common/wwkeyboard_win32.cpp new file mode 100644 index 00000000..7c7e6314 --- /dev/null +++ b/common/wwkeyboard_win32.cpp @@ -0,0 +1,118 @@ +// +// Copyright 2020 Electronic Arts Inc. +// +// TiberianDawn.DLL and RedAlert.dll and corresponding source code 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. + +// TiberianDawn.DLL and RedAlert.dll and corresponding source code is distributed +// in the hope that it will be useful, but with permitted additional restrictions +// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT +// distributed with this program. You should have received a copy of the +// GNU General Public License along with permitted additional restrictions +// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection + +#include "wwkeyboard_win32.h" +#include "video.h" +#include "settings.h" + +void Focus_Loss(); +void Focus_Restore(); +void Process_Network(); + +WWKeyboardClassWin32::~WWKeyboardClassWin32() +{ + memset(KeyState, '\0', sizeof(KeyState)); +} + +void WWKeyboardClassWin32::Fill_Buffer_From_System(void) +{ + if (!Is_Buffer_Full()) { + MSG msg; + while (PeekMessageA(&msg, NULL, 0, 0, PM_NOREMOVE)) { + if (!GetMessageA(&msg, NULL, 0, 0)) { + return; + } + TranslateMessage(&msg); + DispatchMessageA(&msg); + } + } +} + +/*********************************************************************************************** + * WWKeyboardClassWin32::To_ASCII -- Convert the key value into an ASCII representation. * + * * + * This routine will convert the key code specified into an ASCII value. This takes into * + * consideration the language and keyboard mapping of the host Windows system. * + * * + * INPUT: key -- The key code to convert into ASCII. * + * * + * OUTPUT: Returns with the key converted into ASCII. If the key has no ASCII equivalent, * + * then '\0' is returned. * + * * + * WARNINGS: none * + * * + * HISTORY: * + * 09/30/1996 JLB : Created. * + *=============================================================================================*/ +KeyASCIIType WWKeyboardClassWin32::To_ASCII(unsigned short key) +{ + /* + ** Released keys never translate into an ASCII value. + */ + if (key & WWKEY_RLS_BIT) { + return KA_NONE; + } + + /* + ** Ask windows to translate the key into an ASCII equivalent. + */ + char buffer[10]; + int result = 1; + int scancode = 0; + + /* + ** Set the KeyState buffer to reflect the shift bits stored in the key value. + */ + if (key & WWKEY_SHIFT_BIT) { + KeyState[VK_SHIFT] = 0x80; + } + if (key & WWKEY_CTRL_BIT) { + KeyState[VK_CONTROL] = 0x80; + } + if (key & WWKEY_ALT_BIT) { + KeyState[VK_MENU] = 0x80; + } + + scancode = MapVirtualKeyA(key & 0xFF, 0); + result = ToAscii((UINT)(key & 0xFF), (UINT)scancode, (PBYTE)KeyState, (LPWORD)buffer, (UINT)0); + + /* + ** Restore the KeyState buffer back to pristine condition. + */ + if (key & WWKEY_SHIFT_BIT) { + KeyState[VK_SHIFT] = 0; + } + if (key & WWKEY_CTRL_BIT) { + KeyState[VK_CONTROL] = 0; + } + if (key & WWKEY_ALT_BIT) { + KeyState[VK_MENU] = 0; + } + + /* + ** If Windows could not perform the translation as expected, then + ** return with a null ASCII value. + */ + if (result != 1) { + return KA_NONE; + } + + return (KeyASCIIType)(buffer[0]); +} + +WWKeyboardClass* CreateWWKeyboardClass(void) +{ + return new WWKeyboardClassWin32; +} diff --git a/common/wwkeyboard_win32.h b/common/wwkeyboard_win32.h new file mode 100644 index 00000000..eff1c277 --- /dev/null +++ b/common/wwkeyboard_win32.h @@ -0,0 +1,17 @@ +#include "wwkeyboard.h" + +class WWKeyboardClassWin32 : public WWKeyboardClass +{ +public: + virtual ~WWKeyboardClassWin32(); + + virtual void Fill_Buffer_From_System(void); + virtual KeyASCIIType To_ASCII(unsigned short key); + +private: + /* + ** This is a keyboard state array that is used to aid in translating + ** KN_ keys into KA_ keys. + */ + unsigned char KeyState[256]; +}; diff --git a/redalert/aircraft.cpp b/redalert/aircraft.cpp index 36c00990..deb2ec7d 100644 --- a/redalert/aircraft.cpp +++ b/redalert/aircraft.cpp @@ -111,6 +111,13 @@ *=============================================================================================*/ static bool _Counts_As_Civ_Evac(ObjectClass const* candidate) { + /* + ** If evac logic isn't enabled, no candidate can be evacuated. + */ + if (!Scen.EnableEvac) { + return false; + } + /* ** If the candidate pointer is missing, then return with failure code. */ diff --git a/redalert/cell.cpp b/redalert/cell.cpp index 33bf3c3a..32cdd989 100644 --- a/redalert/cell.cpp +++ b/redalert/cell.cpp @@ -3221,6 +3221,13 @@ bool CellClass::Can_Tiberium_Germinate(void) const if (building != NULL && !building->Class->IsInvisible) return (false); + /* + ** Don't allow Tiberium to grow on a cell with a terrain object. + */ + TerrainClass const* terrain = Cell_Terrain(); + if (terrain != NULL && (*terrain != TERRAIN_NONE)) + return (false); + if (!Ground[Land_Type()].Build) return (false); diff --git a/redalert/conquer.cpp b/redalert/conquer.cpp index 37c578b9..bbda852c 100644 --- a/redalert/conquer.cpp +++ b/redalert/conquer.cpp @@ -3139,7 +3139,7 @@ void CC_Draw_Shape(void const* shapefile, predoffset = Frame; - if (x > WindowList[window][WINDOWWIDTH]) { + if (x > WindowList[window][WINDOWWIDTH] >> 1) { predoffset = -predoffset; } diff --git a/redalert/defines.h b/redalert/defines.h index 22b218bf..b39a83b1 100644 --- a/redalert/defines.h +++ b/redalert/defines.h @@ -130,8 +130,13 @@ #endif // Test to see if partial object drawing is any faster. +#ifdef REMASTER_BUILD //#define PARTIAL #define SORTDRAW +#else +#define PARTIAL +//#define SORTDRAW +#endif /********************************************************************** ** If the scenario editor to to be active in this build then uncomment diff --git a/redalert/function.h b/redalert/function.h index 3fcc5803..21ff2e94 100644 --- a/redalert/function.h +++ b/redalert/function.h @@ -133,6 +133,7 @@ bool PlayMpegMovie(const char* name); #endif #include "externs.h" +#include "keyframe.h" extern int Get_CD_Drive(void); extern void Fatal(char const* message, ...); diff --git a/redalert/jshell.h b/redalert/jshell.h index 6a68d792..15ec5526 100644 --- a/redalert/jshell.h +++ b/redalert/jshell.h @@ -80,7 +80,7 @@ template inline T operator~(T t1) return ((T)(~(int)t1)); } -#ifdef NOMINMAX +#if !defined _WIN32 || defined NOMINMAX inline int min(int a, int b) { return a < b ? a : b; diff --git a/redalert/map.cpp b/redalert/map.cpp index f8d14e32..471bef5e 100644 --- a/redalert/map.cpp +++ b/redalert/map.cpp @@ -2552,7 +2552,9 @@ void MapClass::Shroud_The_Map(HouseClass* house) } for (int obj_index = 0; obj_index < DisplayClass::Layer[LAYER_GROUND].Count(); obj_index++) { ObjectClass* layer_object = DisplayClass::Layer[LAYER_GROUND][obj_index]; - if (layer_object && layer_object->Is_Techno() && ((TechnoClass*)layer_object)->House == house) { + if (layer_object && layer_object->Is_Techno() + && ((((TechnoClass*)layer_object)->House == house) + || (Rule.IsAllyReveal && ((TechnoClass*)layer_object)->House->Is_Ally(house)))) { layer_object->Look(); } } diff --git a/redalert/object.cpp b/redalert/object.cpp index 529044c2..f4fd8249 100644 --- a/redalert/object.cpp +++ b/redalert/object.cpp @@ -2380,7 +2380,7 @@ BuildingClass* ObjectTypeClass::Who_Can_Build_Me(bool intheory, bool legal, Hous BuildingClass* building = Buildings.Ptr(index); assert(building != NULL); - if (!building->IsInLimbo && building->House->Class->House == house && building->Class->ToBuild == RTTI + if (!building->IsInLimbo && building->House->Class->House == house && (*building == STRUCT_AIRSTRIP) && building->Mission != MISSION_DECONSTRUCTION && building->MissionQueue != MISSION_DECONSTRUCTION && ((1L << building->ActLike) & Get_Ownable()) && (!legal || building->House->Can_Build(this, building->ActLike))) { diff --git a/redalert/scenario.cpp b/redalert/scenario.cpp index 8e9e181c..3738c61a 100644 --- a/redalert/scenario.cpp +++ b/redalert/scenario.cpp @@ -164,6 +164,7 @@ ScenarioClass::ScenarioClass(void) , IsNoMapSel(false) , IsTruckCrate(false) , IsMoneyTiberium(false) + , EnableEvac(true) , #ifdef FIXIT_VERSION_3 // For endgame auto-sonar pulse. #define AUTOSONAR_PERIOD TICKS_PER_SECOND * 40 @@ -782,6 +783,10 @@ void Clear_Scenario(void) Scen.CarryOverPercent = 0; Scen.TransitTheme = THEME_NONE; Scen.Percent = 0; + /* + ** Default setting for evac depends on session type. + */ + Scen.EnableEvac = Session.Type == GAME_NORMAL ? true : false; memset(Scen.GlobalFlags, 0, sizeof(Scen.GlobalFlags)); @@ -2302,6 +2307,7 @@ bool Read_Scenario_INI(char* fname, bool) Scen.IsTruckCrate = ini.Get_Bool(BASIC, "TruckCrate", Scen.IsTruckCrate); Scen.IsMoneyTiberium = ini.Get_Bool(BASIC, "FillSilos", Scen.IsMoneyTiberium); Scen.Percent = ini.Get_Int(BASIC, "Percent", Scen.Percent); + Scen.EnableEvac = ini.Get_Bool(BASIC, "EnableEvac", Scen.EnableEvac); /* ** Read in the specific information for each of the house types. This creates @@ -2635,9 +2641,15 @@ bool Read_Scenario_INI(char* fname, bool) */ #ifdef FIXIT_CSII // checked - ajw 9/28/98 - Added runtime check. if (Is_Aftermath_Installed()) { +#ifdef REMASTER_BUILD if (Session.Type == GAME_SKIRMISH || Session.Type == GAME_GLYPHX_MULTIPLAYER) { bAftermathMultiplayer = NewUnitsEnabled = OverrideNewUnitsEnabled; } +#else + if (Session.Type == GAME_SKIRMISH) { + bAftermathMultiplayer = NewUnitsEnabled = true; + } +#endif } #endif ScenarioInit--; diff --git a/redalert/scenario.h b/redalert/scenario.h index 893b1f77..67d8279d 100644 --- a/redalert/scenario.h +++ b/redalert/scenario.h @@ -249,7 +249,7 @@ class ScenarioClass unsigned IsToInherit : 1; /* - ** If Tanya or a civilian is to be automatically evacuated when they enter + ** If Tanya is to be automatically evacuated when they enter ** a transport vehicle, then this flag will be true. */ unsigned IsTanyaEvac : 1; @@ -310,6 +310,11 @@ class ScenarioClass */ unsigned IsMoneyTiberium : 1; + /* + ** When set to false, units will not be evacuated no matter what + */ + unsigned EnableEvac : 1; + /* ** This is the fading countdown timer. As this timer counts down, the ** fading to b&w or color will progress. This timer represents a diff --git a/redalert/startup.cpp b/redalert/startup.cpp index a84a5087..0c4320aa 100644 --- a/redalert/startup.cpp +++ b/redalert/startup.cpp @@ -298,7 +298,7 @@ int main(int argc, char* argv[]) CCFileClass cfile(CONFIG_FILE_NAME); - Keyboard = new WWKeyboardClass(); + Keyboard = CreateWWKeyboardClass(); /* ** If there is loads of memory then use uncompressed shapes @@ -503,7 +503,9 @@ int main(int argc, char* argv[]) */ #if defined(_WIN32) && !defined(SDL_BUILD) PostMessage(MainWindow, WM_DESTROY, 0, 0); +#endif +#if !defined(REMASTER_BUILD) && defined(_WIN32) && !defined(SDL_BUILD) /* ** Wait until the message handler has dealt with the message */ @@ -701,12 +703,14 @@ void Emergency_Exit(int code) PostMessage(MainWindow, WM_DESTROY, 0, 0); #endif +#if !defined(REMASTER_BUILD) && defined(_WIN32) && !defined(SDL_BUILD) /* ** Wait until the message handler has dealt with the message */ do { Keyboard->Check(); } while (ReadyToQuit == 3); +#endif exit(code); } diff --git a/redalert/techno.cpp b/redalert/techno.cpp index 0c2b0120..682741b2 100644 --- a/redalert/techno.cpp +++ b/redalert/techno.cpp @@ -162,7 +162,7 @@ const char* NewName[] = { "Fire Ant", "Feuer-Ameise", "Queen Ant", - "Ameisenk”nigin", + "Ameisenk�nigin", "ATS", "Angriffs-U-Boot", "Tesla Tank", @@ -188,7 +188,7 @@ const char* NewName[] = { "Scout Ant", "Fourmi de Reconnaissance", "Warrior Ant", - "Fourmi GuerriŠre", + "Fourmi Guerri�re", "Fire Ant", "Fourmi Lance-Flammes", "Queen Ant", @@ -204,11 +204,11 @@ const char* NewName[] = { "Stavros", "Stavros", "F-A Longbow", - "HAA (H‚licoptŠre d'Assaut Avanc‚)", + "HAA (H�licopt�re d'Assaut Avanc�)", "Civilian Specialist", - "Sp‚cialiste Civil", + "Sp�cialiste Civil", "Alloy Facility", - "Usine M‚tallurgique", + "Usine M�tallurgique", NULL, }; @@ -683,11 +683,15 @@ TechnoClass::TechnoClass(RTTIType rtti, int id, HousesType house) // IsOwnedByPlayer = (PlayerPtr == House); // Added for multiplayer changes. ST - 4/24/2019 10:40AM IsDiscoveredByPlayerMask = 0; +#ifdef REMASTER_BUILD if (Session.Type == GAME_NORMAL) { IsOwnedByPlayer = (PlayerPtr == House); } else { IsOwnedByPlayer = House->IsHuman; } +#else + IsOwnedByPlayer = (PlayerPtr == House); +#endif } /*********************************************************************************************** @@ -775,7 +779,7 @@ bool TechnoClass::Revealed(HouseClass* house) ** An enemy object that is discovered will go into hunt mode if ** its current mission is to ambush. */ - if (!house->IsHuman && Mission == MISSION_AMBUSH) { + if (!House->IsHuman && Mission == MISSION_AMBUSH) { Assign_Mission(MISSION_HUNT); } diff --git a/redalert/winstub.cpp b/redalert/winstub.cpp index ef7a5bb8..11fb773d 100644 --- a/redalert/winstub.cpp +++ b/redalert/winstub.cpp @@ -626,9 +626,11 @@ void Memory_Error_Handler(void) #ifdef _WIN32 PostMessage(MainWindow, WM_DESTROY, 0, 0); #endif +#if !defined(REMASTER_BUILD) && defined(_WIN32) && !defined(SDL_BUILD) do { Keyboard->Check(); } while (ReadyToQuit == 1); +#endif exit(1); } diff --git a/tiberiandawn/base.cpp b/tiberiandawn/base.cpp index f44de572..a2c58850 100644 --- a/tiberiandawn/base.cpp +++ b/tiberiandawn/base.cpp @@ -170,6 +170,18 @@ void BaseClass::Read_INI(CCINIClass& ini) */ node.Coord = atol(strtok(NULL, ",")); +#ifdef MEGAMAPS + if (Map.MapBinaryVersion != MAP_VERSION_MEGA) { + int x = Coord_X(node.Coord); + int y = Coord_Y(node.Coord); + x >>= 8; + y >>= 8; + CELL cell = XY_Cell(x, y); + cell = Confine_Old_Cell(cell); + node.Coord = Cell_Coord(cell); + } +#endif + /* ** Add this node to the Base's list */ diff --git a/tiberiandawn/building.h b/tiberiandawn/building.h index f6a5c427..3cff8a95 100644 --- a/tiberiandawn/building.h +++ b/tiberiandawn/building.h @@ -35,6 +35,7 @@ #ifndef BUILDING_H #define BUILDING_H +#include "ftimer.h" #include "tarcom.h" #include "radio.h" #include "cargo.h" @@ -175,7 +176,9 @@ class BuildingClass : public TechnoClass : Class(0){}; BuildingClass(NoInitClass const& x) : TechnoClass(x) - , Class(this->Class){}; + , Class(this->Class) + , CountDown(x) + , PlacementDelay(x){}; BuildingClass(StructType type, HousesType house); virtual ~BuildingClass(void); virtual RTTIType What_Am_I(void) const diff --git a/tiberiandawn/conquer.cpp b/tiberiandawn/conquer.cpp index 7aa6a2e1..1e9cb172 100644 --- a/tiberiandawn/conquer.cpp +++ b/tiberiandawn/conquer.cpp @@ -2687,7 +2687,7 @@ void CC_Draw_Shape(void const* shapefile, predoffset = Frame; - if (x > WindowList[window][WINDOWWIDTH]) { + if (x > WindowList[window][WINDOWWIDTH] >> 1) { predoffset = -predoffset; } diff --git a/tiberiandawn/credits.h b/tiberiandawn/credits.h index 152fe945..2b867668 100644 --- a/tiberiandawn/credits.h +++ b/tiberiandawn/credits.h @@ -50,6 +50,9 @@ class CreditClass ** Constructors, Destructors, and overloaded operators. */ CreditClass(void); + CreditClass(NoInitClass const&) + { + } /*--------------------------------------------------------------------- ** Member function prototypes. diff --git a/tiberiandawn/door.h b/tiberiandawn/door.h index 9e363d4f..27b1cc62 100644 --- a/tiberiandawn/door.h +++ b/tiberiandawn/door.h @@ -68,7 +68,8 @@ class DoorClass public: DoorClass(void); - DoorClass(NoInitClass const&){}; + DoorClass(NoInitClass const& x) + : Control(x){}; bool Time_To_Redraw(void) { diff --git a/tiberiandawn/function.h b/tiberiandawn/function.h index 05594362..869a025d 100644 --- a/tiberiandawn/function.h +++ b/tiberiandawn/function.h @@ -88,7 +88,7 @@ Map(screen) class heirarchy. ³ InfantryTypeClass AircraftTypeClass */ -#ifdef NOMINMAX +#if !defined _WIN32 || defined NOMINMAX inline int min(int a, int b) { return a < b ? a : b; diff --git a/tiberiandawn/house.cpp b/tiberiandawn/house.cpp index a575920b..7d0bd8a4 100644 --- a/tiberiandawn/house.cpp +++ b/tiberiandawn/house.cpp @@ -367,6 +367,9 @@ HouseClass::HouseClass(HousesType house) , CapturedBuildings() , TotalCrates() { + for (int i = 0; i < MAP_TOTAL_REGIONS; ++i) { + Regions[i].Init(); + } for (HousesType i = HOUSE_FIRST; i < HOUSE_COUNT; i++) { UnitsKilled[i] = 0; diff --git a/tiberiandawn/house.h b/tiberiandawn/house.h index 8e0d3b09..1ffcb36a 100644 --- a/tiberiandawn/house.h +++ b/tiberiandawn/house.h @@ -421,10 +421,35 @@ class HouseClass { } HouseClass(void) - : Class(0){}; + : Class(0) + { + for (int i = 0; i < MAP_TOTAL_REGIONS; ++i) { + Regions[i].Init(); + } + } HouseClass(HousesType house); - HouseClass(NoInitClass const&) - : Class(this->Class){}; + HouseClass(NoInitClass const& noinit) + : Class(this->Class) + , FreeHarvester(noinit) + , IonCannon(noinit) + , AirStrike(noinit) + , NukeStrike(noinit) + , AlertTime(noinit) + , BorrowedTime(noinit) + , BlitzTime(noinit) + , VisibleCredits(noinit) + , ScreenShakeTime(noinit) + , DamageTime(noinit) + , TeamTime(noinit) + , TriggerTime(noinit) + , SpeakAttackDelay(noinit) + , SpeakPowerDelay(noinit) + , SpeakMoneyDelay(noinit) + , SpeakMaxedDelay(noinit) + , Attack(noinit) + , AITimer(noinit) + { + } operator HousesType(void) const; /*--------------------------------------------------------------------- diff --git a/tiberiandawn/init.cpp b/tiberiandawn/init.cpp index ce2a979f..f8c79db2 100644 --- a/tiberiandawn/init.cpp +++ b/tiberiandawn/init.cpp @@ -306,7 +306,7 @@ bool Init_Game(int, char*[]) sprintf(buffer, "Command & Conquer kann Ihren Maustreiber nicht finden.."); #else #ifdef FRENCH - sprintf(buffer, "Command & Conquer ne peut pas d‚tecter votre gestionnaire de souris."); + sprintf(buffer, "Command & Conquer ne peut pas d�tecter votre gestionnaire de souris."); #else sprintf(buffer, "Command & Conquer is unable to detect your mouse driver."); #endif @@ -877,37 +877,43 @@ bool Select_Game(bool fade) case SEL_NEW_SCENARIO: Scen.CarryOverMoney = 0; if (Expansion_Dialog()) { - switch (Fetch_Difficulty()) { - case 0: - Scen.CDifficulty = DIFF_HARD; - Scen.Difficulty = DIFF_EASY; - break; - - case 1: - Scen.CDifficulty = DIFF_HARD; - Scen.Difficulty = DIFF_NORMAL; - break; - - case 2: - Scen.CDifficulty = DIFF_NORMAL; - Scen.Difficulty = DIFF_NORMAL; - break; - - case 3: - Scen.CDifficulty = DIFF_EASY; - Scen.Difficulty = DIFF_NORMAL; - break; + int difficulty = Fetch_Difficulty(); + if (difficulty != -1) { + switch (difficulty) { + case 0: + Scen.CDifficulty = DIFF_HARD; + Scen.Difficulty = DIFF_EASY; + break; + + case 1: + Scen.CDifficulty = DIFF_HARD; + Scen.Difficulty = DIFF_NORMAL; + break; + + case 2: + Scen.CDifficulty = DIFF_NORMAL; + Scen.Difficulty = DIFF_NORMAL; + break; + + case 3: + Scen.CDifficulty = DIFF_EASY; + Scen.Difficulty = DIFF_NORMAL; + break; + + case 4: + Scen.CDifficulty = DIFF_EASY; + Scen.Difficulty = DIFF_HARD; + break; + } - case 4: - Scen.CDifficulty = DIFF_EASY; - Scen.Difficulty = DIFF_HARD; - break; + Theme.Fade_Out(); + // Theme.Queue_Song(THEME_AOI); + GameToPlay = GAME_NORMAL; + process = false; + } else { + display = true; + selection = SEL_NONE; } - - Theme.Fade_Out(); - // Theme.Queue_Song(THEME_AOI); - GameToPlay = GAME_NORMAL; - process = false; } else { display = true; selection = SEL_NONE; @@ -955,12 +961,15 @@ bool Select_Game(bool fade) /* ** SEL_START_NEW_GAME: Play the game */ - case SEL_START_NEW_GAME: - if (Special.IsFromInstall) { - Scen.CDifficulty = DIFF_NORMAL; - Scen.Difficulty = DIFF_NORMAL; - } else { - switch (Fetch_Difficulty()) { + case SEL_START_NEW_GAME: { + int difficulty = 2; + + if (!Special.IsFromInstall) { + difficulty = Fetch_Difficulty(); + } + + if (difficulty != -1) { + switch (difficulty) { case 0: Scen.CDifficulty = DIFF_HARD; Scen.Difficulty = DIFF_EASY; @@ -986,47 +995,51 @@ bool Select_Game(bool fade) Scen.Difficulty = DIFF_HARD; break; } - } - Scen.CarryOverMoney = 0; + Scen.CarryOverMoney = 0; - if (Is_Demo()) { - Hide_Mouse(); - Fade_Palette_To(BlackPalette, FADE_PALETTE_MEDIUM, Call_Back); - Load_Title_Screen("PREPICK.CPS", &HidPage, Palette); - Blit_Hid_Page_To_Seen_Buff(); - Fade_Palette_To(Palette, FADE_PALETTE_MEDIUM, Call_Back); - Keyboard->Clear(); - Keyboard->Get(); - Fade_Palette_To(BlackPalette, FADE_PALETTE_MEDIUM, Call_Back); - Show_Mouse(); - } + if (Is_Demo()) { + Hide_Mouse(); + Fade_Palette_To(BlackPalette, FADE_PALETTE_MEDIUM, Call_Back); + Load_Title_Screen("PREPICK.CPS", &HidPage, Palette); + Blit_Hid_Page_To_Seen_Buff(); + Fade_Palette_To(Palette, FADE_PALETTE_MEDIUM, Call_Back); + Keyboard->Clear(); + Keyboard->Get(); + Fade_Palette_To(BlackPalette, FADE_PALETTE_MEDIUM, Call_Back); + Show_Mouse(); + } - Scen.Scenario = 1; - BuildLevel = 1; + Scen.Scenario = 1; + BuildLevel = 1; - ScenPlayer = SCEN_PLAYER_GDI; - ScenDir = SCEN_DIR_EAST; - Whom = HOUSE_GOOD; + ScenPlayer = SCEN_PLAYER_GDI; + ScenDir = SCEN_DIR_EAST; + Whom = HOUSE_GOOD; - if (!Is_Demo()) { - Theme.Fade_Out(); - Choose_Side(); - } + if (!Is_Demo()) { + Theme.Fade_Out(); + Choose_Side(); + } - /* - ** If user is playing special mode, do NOT change Whom; leave it set to - ** GDI or NOD. Ini.cpp will set the player's ActLike to mirror the - ** Whom value. - */ - if (Special.IsJurassic && AreThingiesEnabled) { - ScenPlayer = SCEN_PLAYER_JP; - ScenDir = SCEN_DIR_EAST; - } + /* + ** If user is playing special mode, do NOT change Whom; leave it set to + ** GDI or NOD. Ini.cpp will set the player's ActLike to mirror the + ** Whom value. + */ + if (Special.IsJurassic && AreThingiesEnabled) { + ScenPlayer = SCEN_PLAYER_JP; + ScenDir = SCEN_DIR_EAST; + } - GameToPlay = GAME_NORMAL; - process = false; + GameToPlay = GAME_NORMAL; + process = false; + } else { + display = true; + selection = SEL_NONE; + } break; + } /* ** Load a saved game. @@ -1652,20 +1665,20 @@ bool Parse_Command_Line(int argc, char* argv[]) " (Syntax: DESTNETxx.xx.xx.xx)\r\n" " -SOCKET = Kennung des Netzwerk-Sockets (0 - 16383)\n" " -STEALTH = Namen im Mehrspieler-Modus verstecken (\"Boss-Modus\")\r\n" - " -MESSAGES = Mitteilungen von auáerhalb des Spiels zulassen\r\n" + " -MESSAGES = Mitteilungen von au�erhalb des Spiels zulassen\r\n" // " -ELITE = Fortgeschrittene KI und Gefechtstechniken.\r\n" "\r\n"); #else #ifdef FRENCH puts("Command & Conquer (c) 1995, Westwood Studios\r\n" - "ParamŠtres:\r\n" - // " -CD = Recherche des fichiers dans le\r\n" - // " r‚pertoire indiqu‚.\r\n" - " -DESTNET = Sp‚cifier le num‚ro de r‚seau du systŠme de destination\r\n" + "Param�tres:\r\n" + // " -CD = Recherche des fichiers dans le\r\n" + // " r�pertoire indiqu�.\r\n" + " -DESTNET = Sp�cifier le num�ro de r�seau du syst�me de destination\r\n" " (Syntaxe: DESTNETxx.xx.xx.xx)\r\n" - " -SOCKET = ID poste r‚seau (0 … 16383)\r\n" + " -SOCKET = ID poste r�seau (0 � 16383)\r\n" " -STEALTH = Cacher les noms en mode multijoueurs (\"Mode Boss\")\r\n" - " -MESSAGES = Autorise les messages ext‚rieurs … ce jeu.\r\n" + " -MESSAGES = Autorise les messages ext�rieurs � ce jeu.\r\n" "\r\n"); #else puts("Command & Conquer (c) 1995, 1996 Westwood Studios\r\n" @@ -2062,9 +2075,9 @@ void Parse_INI_File(void) /* ** These arrays store the coded version of the names Geologic, Period, & Jurassic. ** Decode them by subtracting 83. For you curious types, the names look like: - ** š¸Â¿Âº¼¶ - ** £¸Å¼Â· - ** ?ÈÅ´ÆƼ¶ + ** ��¿º�� + ** ��ż· + ** ?�Ŵ�Ƽ� ** If these INI entries aren't found, the IsJurassic flag does nothing. */ static char coded_section[] = {154, 184, 194, 191, 194, 186, 188, 182, 0}; diff --git a/tiberiandawn/mission.h b/tiberiandawn/mission.h index 47b802cf..64b2df21 100644 --- a/tiberiandawn/mission.h +++ b/tiberiandawn/mission.h @@ -67,6 +67,7 @@ class MissionClass : public ObjectClass MissionClass(void); MissionClass(NoInitClass const& x) : ObjectClass(x) + , Timer(x) { } virtual ~MissionClass(void){}; diff --git a/tiberiandawn/saveload.cpp b/tiberiandawn/saveload.cpp index 0db689f8..2dcd47ef 100644 --- a/tiberiandawn/saveload.cpp +++ b/tiberiandawn/saveload.cpp @@ -673,6 +673,7 @@ bool Save_Misc_Values(FileClass& file) // This is new... file.Write(ActionMovie, sizeof(ActionMovie)); file.Write(&TempleIoned, sizeof(TempleIoned)); + file.Write(&AreThingiesEnabled, sizeof(AreThingiesEnabled)); return (true); } @@ -787,6 +788,10 @@ bool Load_Misc_Values(FileClass& file) file.Read(&TempleIoned, sizeof(TempleIoned)); } + if (file.Seek(0, SEEK_CUR) < file.Size()) { + file.Read(&AreThingiesEnabled, sizeof(AreThingiesEnabled)); + } + return (true); } diff --git a/tiberiandawn/special.cpp b/tiberiandawn/special.cpp index a23683fa..261d1e08 100644 --- a/tiberiandawn/special.cpp +++ b/tiberiandawn/special.cpp @@ -282,7 +282,7 @@ int Fetch_Difficulty(void) int factor = (SeenBuff.Get_Width() == 320) ? 1 : 2; int const w = 250 * factor; - int const h = 80 * factor; + int const h = 70 * factor; int const x = ((320 * factor) / 2) - w / 2; int const y = ((200 * factor) / 2) - h / 2; int const bwidth = 30 * factor; @@ -304,12 +304,14 @@ int Fetch_Difficulty(void) ** Create the OK button. */ TextButtonClass okbutton(1, TXT_OK, TPF_BUTTON, (x + w) - (bwidth + 20 * factor), (y + h) - (18 * factor), bwidth); + TextButtonClass cancelbutton(3, TXT_CANCEL, TPF_BUTTON, x + (20 * factor), (y + h) - (18 * factor), bwidth); GadgetClass* buttonlist = &okbutton; + cancelbutton.Add(*buttonlist); /* ** Create the slider button. */ - SliderClass slider(2, x + 20 * factor, y + h - 29 * factor, w - 40 * factor, 8 * factor, true); + SliderClass slider(2, x + 20 * factor, y + h - 38 * factor, w - 40 * factor, 8 * factor, true); if (Rule.IsFineDifficulty) { slider.Set_Maximum(5); slider.Set_Value(2); @@ -403,6 +405,9 @@ int Fetch_Difficulty(void) process = false; break; + case (3 | BUTTON_FLAG): + return -1; + default: break; } diff --git a/tiberiandawn/startup.cpp b/tiberiandawn/startup.cpp index 6c4e60dd..8536cc91 100644 --- a/tiberiandawn/startup.cpp +++ b/tiberiandawn/startup.cpp @@ -232,7 +232,7 @@ int main(int argc, char** argv) CCFileClass cfile("CONQUER.INI"); - Keyboard = new WWKeyboardClass(); + Keyboard = CreateWWKeyboardClass(); #ifdef JAPANESE //////////////////////////////////////if(!ForceEnglish) KBLanguage = 1; @@ -484,7 +484,9 @@ int main(int argc, char** argv) */ #if defined(_WIN32) && !defined(SDL_BUILD) PostMessage(MainWindow, WM_DESTROY, 0, 0); +#endif +#if !defined(REMASTER_BUILD) && defined(_WIN32) && !defined(SDL_BUILD) /* ** Wait until the message handler has dealt with the message */ @@ -609,4 +611,4 @@ void Read_Setup_Options(RawFileClass* config_file) */ VideoBackBufferAllowed = ini.Get_Bool("Options", "VideoBackBuffer", true); AllowHardwareBlitFills = ini.Get_Bool("Options", "HardwareFills", true); -} \ No newline at end of file +} diff --git a/tiberiandawn/super.h b/tiberiandawn/super.h index 8e30e3e1..52a046f5 100644 --- a/tiberiandawn/super.h +++ b/tiberiandawn/super.h @@ -45,6 +45,10 @@ class SuperClass VoxType ready = VOX_NONE, VoxType impatient = VOX_NONE, VoxType suspend = VOX_NONE); + SuperClass(NoInitClass const& noinit) + : Control(noinit) + { + } bool Suspend(bool on); bool Enable(bool onetime = false, bool player = false, bool quiet = false); diff --git a/tiberiandawn/team.h b/tiberiandawn/team.h index 4435f531..305961b9 100644 --- a/tiberiandawn/team.h +++ b/tiberiandawn/team.h @@ -177,7 +177,8 @@ class TeamClass : public AbstractClass : AbstractClass(x) , SuspendTimer(x) , Class(this->Class) - , House(this->House){}; + , House(this->House) + , TimeOut(x){}; TeamClass(TeamTypeClass const* team, HouseClass* owner); virtual ~TeamClass(void); diff --git a/tiberiandawn/techno.cpp b/tiberiandawn/techno.cpp index dcfe2503..d96fac8e 100644 --- a/tiberiandawn/techno.cpp +++ b/tiberiandawn/techno.cpp @@ -549,7 +549,7 @@ bool TechnoClass::Revealed(HouseClass* house) ** An enemy object that is discovered will go into hunt mode if ** its current mission is to ambush. */ - if (!house->IsHuman && Mission == MISSION_AMBUSH) { + if (!House->IsHuman && Mission == MISSION_AMBUSH) { Assign_Mission(MISSION_HUNT); } @@ -916,11 +916,15 @@ TechnoClass::TechnoClass(HousesType house) // Added for multiplayer changes. ST - 4/24/2019 10:40AM IsDiscoveredByPlayerMask = 0; +#ifdef REMASTER_BUILD if (GameToPlay == GAME_NORMAL) { IsOwnedByPlayer = (house == PlayerPtr->Class->House); } else { IsOwnedByPlayer = House->IsHuman; } +#else + IsOwnedByPlayer = (PlayerPtr == House); +#endif /* ** There is a chance that a vehicle will be a "lemon". diff --git a/tiberiandawn/techno.h b/tiberiandawn/techno.h index 4216ed6c..73872694 100644 --- a/tiberiandawn/techno.h +++ b/tiberiandawn/techno.h @@ -226,7 +226,9 @@ class TechnoClass : public RadioClass, , CargoClass(x) , DoorClass(x) , CrewClass(x) - , House(this->House){}; + , House(this->House) + , CloakingDevice(x) + , PrimaryFacing(x){}; virtual ~TechnoClass(void){}; /* diff --git a/tiberiandawn/turret.h b/tiberiandawn/turret.h index f77a21f2..7f229953 100644 --- a/tiberiandawn/turret.h +++ b/tiberiandawn/turret.h @@ -67,7 +67,9 @@ class TurretClass : public DriveClass TurretClass(UnitType classid, HousesType house); TurretClass(void); TurretClass(NoInitClass const& x) - : DriveClass(x){}; + : DriveClass(x) + , Reload(x) + , SecondaryFacing(x){}; virtual ~TurretClass(void); BulletClass* Fire_At(TARGET target, int which);