From e90f32eacb30284b7345758520229529f54cdd5b Mon Sep 17 00:00:00 2001 From: OmniBlade Date: Sat, 15 Jun 2024 22:40:39 +0100 Subject: [PATCH 01/21] Remove need to define NOMINMAX on none windows. This is a quick fix, a better solution is to move to using std::min/std::max that will require a larger refactor. --- CMakeLists.txt | 26 +++++++++++++++++++++++++- CMakePresets.json | 5 +++-- common/packet.cpp | 2 +- redalert/jshell.h | 2 +- tiberiandawn/function.h | 2 +- 5 files changed, 31 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a7c8eec9..0c8115a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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..aa58ba54 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -110,7 +110,7 @@ "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", + "VC_CXX_FLAGS": "-w;-Wwrite-strings;-Werror=write-strings", "MAP_EDITORTD": "ON", "MAP_EDITORRA": "ON", "BUILD_TOOLS": "ON" @@ -178,7 +178,7 @@ "CMAKE_C_FLAGS_RELEASE": "-O3 -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/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/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/tiberiandawn/function.h b/tiberiandawn/function.h index 454679a6..c1806dfe 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; From 4d324e147649ee2a543b2ae55fece60f322471fb Mon Sep 17 00:00:00 2001 From: OmniBlade Date: Mon, 17 Jun 2024 21:16:57 +0100 Subject: [PATCH 02/21] Reduce optimisation level. Optimisations of -O3 are causing issues, probably due to undefined behaviour. --- CMakeLists.txt | 2 +- CMakePresets.json | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c8115a5..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) diff --git a/CMakePresets.json b/CMakePresets.json index aa58ba54..79aa6238 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -108,8 +108,8 @@ "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", + "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", @@ -174,8 +174,8 @@ "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": "-w;-Wwrite-strings;-Werror=write-strings", From ee2ad034d6be51cb346121bb6e16d55a71bced71 Mon Sep 17 00:00:00 2001 From: OmniBlade Date: Thu, 13 Jun 2024 23:06:07 +0100 Subject: [PATCH 03/21] Fix under by one error in FillRect on SDL driver. --- common/video_sdl1.cpp | 2 +- common/video_sdl2.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) 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() From 254e7f0decf83e1ca92c60449325cac2d32c830c Mon Sep 17 00:00:00 2001 From: OmniBlade Date: Tue, 18 Jun 2024 10:37:04 +0100 Subject: [PATCH 04/21] [TD] Fixes loading for a number of classes. Also fixes dino missions. --- common/region.h | 9 ++++++--- tiberiandawn/building.h | 5 ++++- tiberiandawn/credits.h | 3 +++ tiberiandawn/door.h | 3 ++- tiberiandawn/house.cpp | 3 +++ tiberiandawn/house.h | 31 ++++++++++++++++++++++++++++--- tiberiandawn/mission.h | 1 + tiberiandawn/saveload.cpp | 5 +++++ tiberiandawn/super.h | 4 ++++ tiberiandawn/team.h | 3 ++- tiberiandawn/techno.h | 4 +++- tiberiandawn/turret.h | 4 +++- 12 files changed, 64 insertions(+), 11 deletions(-) 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/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/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/house.cpp b/tiberiandawn/house.cpp index 1115d860..7e9f27c3 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/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 12c562d8..425890b0 100644 --- a/tiberiandawn/saveload.cpp +++ b/tiberiandawn/saveload.cpp @@ -638,6 +638,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); } @@ -752,6 +753,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/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.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); From 5d87cb14f40b2d21e4c907e6a379ec87ba83a944 Mon Sep 17 00:00:00 2001 From: OmniBlade Date: Fri, 14 Jun 2024 10:31:43 +0100 Subject: [PATCH 05/21] [TD] Implement Cancel button on difficulty select. --- tiberiandawn/init.cpp | 171 +++++++++++++++++++++------------------ tiberiandawn/special.cpp | 9 ++- 2 files changed, 99 insertions(+), 81 deletions(-) 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/special.cpp b/tiberiandawn/special.cpp index d75e44cc..b38943fd 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; } From cc8370e97f0d8e80c647c9e09bd74be2bda00d7f Mon Sep 17 00:00:00 2001 From: Mark Olsen Date: Mon, 6 Mar 2023 10:18:09 +0000 Subject: [PATCH 06/21] Moved platform-specific WWKeyboardClass code into their own classes and files. --- common/CMakeLists.txt | 7 +- common/wwkeyboard.cpp | 403 +----------------------------------- common/wwkeyboard.h | 73 ++----- common/wwkeyboard_sdl1.cpp | 113 ++++++++++ common/wwkeyboard_sdl1.h | 10 + common/wwkeyboard_sdl2.cpp | 338 ++++++++++++++++++++++++++++++ common/wwkeyboard_sdl2.h | 44 ++++ common/wwkeyboard_sdl32.cpp | 46 ++++ common/wwkeyboard_win32.cpp | 118 +++++++++++ common/wwkeyboard_win32.h | 17 ++ redalert/startup.cpp | 2 +- tiberiandawn/startup.cpp | 2 +- 12 files changed, 716 insertions(+), 457 deletions(-) create mode 100644 common/wwkeyboard_sdl1.cpp create mode 100644 common/wwkeyboard_sdl1.h create mode 100644 common/wwkeyboard_sdl2.cpp create mode 100644 common/wwkeyboard_sdl2.h create mode 100644 common/wwkeyboard_sdl32.cpp create mode 100644 common/wwkeyboard_win32.cpp create mode 100644 common/wwkeyboard_win32.h diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 14599075..f609e7b6 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 ) @@ -140,12 +141,12 @@ else() 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/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/startup.cpp b/redalert/startup.cpp index a84a5087..32fdc282 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 diff --git a/tiberiandawn/startup.cpp b/tiberiandawn/startup.cpp index 6c4e60dd..d1a1c97f 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; From 3fe4c5d6a13c68582165d14cdb880c16f7e585f9 Mon Sep 17 00:00:00 2001 From: Mark Olsen Date: Thu, 16 Feb 2023 05:10:52 +0000 Subject: [PATCH 07/21] Split the OpenAL sound code into common and OpenAL-specific parts to make it easier to add support for other audio APIs. --- common/CMakeLists.txt | 2 +- common/soundio_common.cpp | 1132 +++++++++++++++++++++++++++++++++++ common/soundio_imp.h | 17 + common/soundio_openal.cpp | 1166 +++---------------------------------- 4 files changed, 1230 insertions(+), 1087 deletions(-) create mode 100644 common/soundio_common.cpp create mode 100644 common/soundio_imp.h diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index f609e7b6..cbe2a34b 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -135,7 +135,7 @@ 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() 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; } From f2928522d80d93c1c42fb1b2fc8c2f3b8ac003d0 Mon Sep 17 00:00:00 2001 From: Maikel Date: Fri, 15 Jan 2021 02:14:41 +0100 Subject: [PATCH 08/21] for non-remaster builds set SORTDRAW and PARTIAL defines to same value as 3.03 --- redalert/defines.h | 5 +++++ redalert/function.h | 1 + 2 files changed, 6 insertions(+) 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, ...); From 08bd6a5b2cb3aa6335c836af53a9fdee7893a1a6 Mon Sep 17 00:00:00 2001 From: Thorsten Otto Date: Sun, 17 Mar 2024 09:28:13 +0100 Subject: [PATCH 09/21] Ready_To_Quit is only handled in WIN32 message handler --- redalert/startup.cpp | 4 ++++ redalert/winstub.cpp | 2 ++ tiberiandawn/startup.cpp | 4 +++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/redalert/startup.cpp b/redalert/startup.cpp index 32fdc282..0c4320aa 100644 --- a/redalert/startup.cpp +++ b/redalert/startup.cpp @@ -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/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/startup.cpp b/tiberiandawn/startup.cpp index d1a1c97f..8536cc91 100644 --- a/tiberiandawn/startup.cpp +++ b/tiberiandawn/startup.cpp @@ -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 +} From 587fe501bd7ac0dc0b4cf202d83dcd22b5a91d03 Mon Sep 17 00:00:00 2001 From: Maikel Date: Thu, 14 Jan 2021 22:17:04 +0100 Subject: [PATCH 10/21] Prevent evacuate logic by default in MP, add EvacInMP map keyword to enable evac and add DisableEvac keyword to disable evacuate entirely for sp&mp --- redalert/aircraft.cpp | 10 ++++++++++ redalert/scenario.cpp | 6 +++++- redalert/scenario.h | 8 +++++++- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/redalert/aircraft.cpp b/redalert/aircraft.cpp index 36c00990..7fc3450c 100644 --- a/redalert/aircraft.cpp +++ b/redalert/aircraft.cpp @@ -111,6 +111,16 @@ *=============================================================================================*/ static bool _Counts_As_Civ_Evac(ObjectClass const* candidate) { + if (Scen.DisableEvac) { + return false; + } + + // If it's a multiplayer game and the scenario doesn't have + // evacuate in multiplayer keyword defined, don't evacuate + if (Scen.EvacInMP == false && Session.Type != GAME_NORMAL) { + return false; + } + /* ** If the candidate pointer is missing, then return with failure code. */ diff --git a/redalert/scenario.cpp b/redalert/scenario.cpp index 8e9e181c..a2f1684e 100644 --- a/redalert/scenario.cpp +++ b/redalert/scenario.cpp @@ -164,7 +164,9 @@ ScenarioClass::ScenarioClass(void) , IsNoMapSel(false) , IsTruckCrate(false) , IsMoneyTiberium(false) - , + , EvacInMP(false) + , DisableEvac(false) + , #ifdef FIXIT_VERSION_3 // For endgame auto-sonar pulse. #define AUTOSONAR_PERIOD TICKS_PER_SECOND * 40 AutoSonarTimer(AUTOSONAR_PERIOD) @@ -2302,6 +2304,8 @@ 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.EvacInMP = ini.Get_Int(BASIC, "EvacInMP", Scen.EvacInMP); + Scen.DisableEvac = ini.Get_Int(BASIC, "DisableEvac", Scen.DisableEvac); /* ** Read in the specific information for each of the house types. This creates diff --git a/redalert/scenario.h b/redalert/scenario.h index 893b1f77..feb4e6bc 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,12 @@ class ScenarioClass */ unsigned IsMoneyTiberium : 1; + /* With this disabled the units will not be evacuated in single player*/ + bool DisableEvac; + + /* by default evacuate is disabled for multiplayer*/ + bool EvacInMP; + /* ** This is the fading countdown timer. As this timer counts down, the ** fading to b&w or color will progress. This timer represents a From 26b12596d940591b0ebd93f60350cbd5bdb0beda Mon Sep 17 00:00:00 2001 From: Maikel Date: Thu, 14 Jan 2021 23:40:37 +0100 Subject: [PATCH 11/21] Fix reading & clearing of EvacInMP and DisableEvac options --- redalert/scenario.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/redalert/scenario.cpp b/redalert/scenario.cpp index a2f1684e..c0b9d86f 100644 --- a/redalert/scenario.cpp +++ b/redalert/scenario.cpp @@ -784,6 +784,8 @@ void Clear_Scenario(void) Scen.CarryOverPercent = 0; Scen.TransitTheme = THEME_NONE; Scen.Percent = 0; + Scen.EvacInMP = false; + Scen.DisableEvac = false; memset(Scen.GlobalFlags, 0, sizeof(Scen.GlobalFlags)); @@ -2304,8 +2306,8 @@ 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.EvacInMP = ini.Get_Int(BASIC, "EvacInMP", Scen.EvacInMP); - Scen.DisableEvac = ini.Get_Int(BASIC, "DisableEvac", Scen.DisableEvac); + Scen.EvacInMP = ini.Get_Bool(BASIC, "EvacInMP", Scen.EvacInMP); + Scen.DisableEvac = ini.Get_Bool(BASIC, "DisableEvac", Scen.DisableEvac); /* ** Read in the specific information for each of the house types. This creates From 5c42ac9641c2885628c8a9e46b13967bd8f64620 Mon Sep 17 00:00:00 2001 From: OmniBlade Date: Thu, 27 Jan 2022 23:19:00 +0000 Subject: [PATCH 12/21] [RA] Gates evac logic behind EnableEvac ini key. Allows globally enabling or disabling evac logic on per map basis. Default behaviour is enabled in SP disabled in MP if key is absent. --- redalert/aircraft.cpp | 11 ++++------- redalert/scenario.cpp | 14 +++++++------- redalert/scenario.h | 9 ++++----- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/redalert/aircraft.cpp b/redalert/aircraft.cpp index 7fc3450c..deb2ec7d 100644 --- a/redalert/aircraft.cpp +++ b/redalert/aircraft.cpp @@ -111,13 +111,10 @@ *=============================================================================================*/ static bool _Counts_As_Civ_Evac(ObjectClass const* candidate) { - if (Scen.DisableEvac) { - return false; - } - - // If it's a multiplayer game and the scenario doesn't have - // evacuate in multiplayer keyword defined, don't evacuate - if (Scen.EvacInMP == false && Session.Type != GAME_NORMAL) { + /* + ** If evac logic isn't enabled, no candidate can be evacuated. + */ + if (!Scen.EnableEvac) { return false; } diff --git a/redalert/scenario.cpp b/redalert/scenario.cpp index c0b9d86f..03b41f9b 100644 --- a/redalert/scenario.cpp +++ b/redalert/scenario.cpp @@ -164,9 +164,8 @@ ScenarioClass::ScenarioClass(void) , IsNoMapSel(false) , IsTruckCrate(false) , IsMoneyTiberium(false) - , EvacInMP(false) - , DisableEvac(false) - , + , EnableEvac(true) + , #ifdef FIXIT_VERSION_3 // For endgame auto-sonar pulse. #define AUTOSONAR_PERIOD TICKS_PER_SECOND * 40 AutoSonarTimer(AUTOSONAR_PERIOD) @@ -784,8 +783,10 @@ void Clear_Scenario(void) Scen.CarryOverPercent = 0; Scen.TransitTheme = THEME_NONE; Scen.Percent = 0; - Scen.EvacInMP = false; - Scen.DisableEvac = false; + /* + ** Default setting for evac depends on session type. + */ + Scen.EnableEvac = Session.Type == GAME_NORMAL ? true : false; memset(Scen.GlobalFlags, 0, sizeof(Scen.GlobalFlags)); @@ -2306,8 +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.EvacInMP = ini.Get_Bool(BASIC, "EvacInMP", Scen.EvacInMP); - Scen.DisableEvac = ini.Get_Bool(BASIC, "DisableEvac", Scen.DisableEvac); + Scen.EnableEvac = ini.Get_Bool(BASIC, "EnableEvac", Scen.EnableEvac); /* ** Read in the specific information for each of the house types. This creates diff --git a/redalert/scenario.h b/redalert/scenario.h index feb4e6bc..67d8279d 100644 --- a/redalert/scenario.h +++ b/redalert/scenario.h @@ -310,11 +310,10 @@ class ScenarioClass */ unsigned IsMoneyTiberium : 1; - /* With this disabled the units will not be evacuated in single player*/ - bool DisableEvac; - - /* by default evacuate is disabled for multiplayer*/ - bool EvacInMP; + /* + ** 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 From d8a43c9eb852fbd3953a891ef2e4f5cd991ed774 Mon Sep 17 00:00:00 2001 From: OmniBlade Date: Wed, 19 Jun 2024 13:23:48 +0100 Subject: [PATCH 13/21] [RA] Enable AM units in skirmish when expansion detected. --- redalert/scenario.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/redalert/scenario.cpp b/redalert/scenario.cpp index 03b41f9b..3738c61a 100644 --- a/redalert/scenario.cpp +++ b/redalert/scenario.cpp @@ -2641,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--; From 610695f8d79c6b66efb1dd0546931fe4aa54885b Mon Sep 17 00:00:00 2001 From: OmniBlade Date: Wed, 19 Jun 2024 10:13:50 +0100 Subject: [PATCH 14/21] Fix MISSION_AMBUSH to actually work. --- redalert/techno.cpp | 12 ++++++------ tiberiandawn/techno.cpp | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/redalert/techno.cpp b/redalert/techno.cpp index 0c2b0120..b6893011 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, }; @@ -775,7 +775,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/tiberiandawn/techno.cpp b/tiberiandawn/techno.cpp index 59bf7f8c..ca56085e 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); } From 1800e11eba938484db846406291b75398c0eb7e4 Mon Sep 17 00:00:00 2001 From: OmniBlade Date: Wed, 19 Jun 2024 09:35:10 +0100 Subject: [PATCH 15/21] [RA] Fix ore occasionally growing over terrain. Similar fix to https://github.com/ChthonVII/CnC_Remastered_Collection/commit/cbe79fb0dfeacbf2189263215cc3722c347cd899 --- redalert/cell.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/redalert/cell.cpp b/redalert/cell.cpp index e3067afa..3f1c7f1e 100644 --- a/redalert/cell.cpp +++ b/redalert/cell.cpp @@ -3223,6 +3223,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); From 362b2a52adaf36bd8338a93468ddce32c6f013e7 Mon Sep 17 00:00:00 2001 From: ChthonVII Date: Wed, 19 Jun 2024 09:19:31 +0100 Subject: [PATCH 16/21] [RA] Fix helipads counting towards Airstrips. Chthon CFE Note: Bugfix! We need to specifically count ONLY airstrips. Original code also counted helipads Which allowed you to create excess planes if you had 1+ helipads and 1+ planes in the air when the construction finished Petroglyph made it worse by making the blocked production spam retries every frame, guaranteeing you'll activate the bug as soon as your planes take off. --- redalert/object.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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))) { From 59b8f9213ef02515e8fdc3f2506255e94b8a7aff Mon Sep 17 00:00:00 2001 From: OmniBlade Date: Tue, 11 Jun 2024 16:42:20 +0100 Subject: [PATCH 17/21] [TD] Add handling of cell coords to BaseClass::Read_INI. --- tiberiandawn/base.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) 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 */ From 53e5674159b1d728e5c66edf942ecd5d91ff0340 Mon Sep 17 00:00:00 2001 From: OmniBlade Date: Tue, 11 Jun 2024 23:05:43 +0100 Subject: [PATCH 18/21] Make the "predator" effect match original more closely. --- common/keybuff.cpp | 85 ++++++++++++++++++++++++++++++---------- redalert/conquer.cpp | 2 +- tiberiandawn/conquer.cpp | 2 +- 3 files changed, 66 insertions(+), 23 deletions(-) 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/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/tiberiandawn/conquer.cpp b/tiberiandawn/conquer.cpp index 413c3ce5..0220640c 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; } From 2bfee4fd63a10afa422c896d7009d1f86327e0f7 Mon Sep 17 00:00:00 2001 From: ChthonVII Date: Wed, 19 Jun 2024 09:52:18 +0100 Subject: [PATCH 19/21] [RA] Prevent reshroud hiding allies with IsAllyReveal. See also https://github.com/ChthonVII/CnC_Remastered_Collection/commit/ae33eed78322480afe91f1948817fa191f576a72 --- redalert/map.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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(); } } From f4a3cff3b3f56f45bcd7f632167faf1832b36ffc Mon Sep 17 00:00:00 2001 From: OmniBlade Date: Wed, 19 Jun 2024 10:54:26 +0100 Subject: [PATCH 20/21] Fix assignment of IsOwnedByPlayer in standalone. Fixes a number of standalone MP bugs where code assumes IsOwnedByPlayer only refers to the local machine player. --- redalert/techno.cpp | 4 ++++ tiberiandawn/techno.cpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/redalert/techno.cpp b/redalert/techno.cpp index b6893011..682741b2 100644 --- a/redalert/techno.cpp +++ b/redalert/techno.cpp @@ -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 } /*********************************************************************************************** diff --git a/tiberiandawn/techno.cpp b/tiberiandawn/techno.cpp index ca56085e..015154b2 100644 --- a/tiberiandawn/techno.cpp +++ b/tiberiandawn/techno.cpp @@ -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". From 5bff786f9c7d4f425f746bcd203789f0b20b717e Mon Sep 17 00:00:00 2001 From: OmniBlade Date: Thu, 20 Jun 2024 15:38:02 +0100 Subject: [PATCH 21/21] Update github actions to avoid deprecations. --- .github/workflows/linux.yml | 6 +++--- .github/workflows/macos.yml | 6 +++--- .github/workflows/mingw.yml | 4 ++-- .github/workflows/windows.yml | 22 ++++++++++++++-------- 4 files changed, 22 insertions(+), 16 deletions(-) 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/*