diff --git a/CMakePresets.json b/CMakePresets.json index feb76247721..987eb72f294 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -1,26 +1,33 @@ { "version": 6, - "cmakeMinimumRequired": { + "cmakeMinimumRequired": + { "major": 3, "minor": 25, "patch": 0 }, - "configurePresets": [ + "configurePresets": + [ { "name": "vc6", "displayName": "Windows 32bit VC6 Release", - "generator": "NMake Makefiles", + "generator": "Ninja", "hidden": false, "binaryDir": "${sourceDir}/build/${presetName}", - "cacheVariables": { + "toolchainFile": "${sourceDir}/cmake/vc6toolchain.cmake", + "cacheVariables": + { + "CMAKE_VERBOSE_MAKEFILE": "TRUE", "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", "CMAKE_MSVC_RUNTIME_LIBRARY": "MultiThreaded$<$:Debug>DLL", - "CMAKE_MSVC_DEBUG_INFORMATION_FORMAT": "$<$:ProgramDatabase>", + "CMAKE_MSVC_DEBUG_INFORMATION_FORMAT": "$<$:Embedded>", "CMAKE_BUILD_TYPE": "Release", "RTS_FLAGS": "/W3" }, - "vendor": { - "jetbrains.com/clion": { + "vendor": + { + "jetbrains.com/clion": + { "toolchain": "Visual Studio 6" } } @@ -30,7 +37,8 @@ "displayName": "Windows 32bit VC6 Profile", "hidden": false, "inherits": "vc6", - "cacheVariables": { + "cacheVariables": + { "RTS_BUILD_OPTION_PROFILE": "ON" } }, @@ -38,7 +46,8 @@ "name": "vc6-internal", "displayName": "Windows 32bit VC6 Internal", "inherits": "vc6", - "cacheVariables": { + "cacheVariables": + { "RTS_BUILD_OPTION_INTERNAL": "ON" } }, @@ -47,7 +56,8 @@ "displayName": "Windows 32bit VC6 Debug", "hidden": false, "inherits": "vc6", - "cacheVariables": { + "cacheVariables": + { "CMAKE_BUILD_TYPE": "Debug", "RTS_BUILD_OPTION_DEBUG": "ON" } @@ -58,7 +68,8 @@ "generator": "Ninja Multi-Config", "hidden": true, "binaryDir": "${sourceDir}/build/${presetName}", - "cacheVariables": { + "cacheVariables": + { "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", "CMAKE_MSVC_DEBUG_INFORMATION_FORMAT": "$<$:Embedded>", "CMAKE_MSVC_RUNTIME_LIBRARY": "MultiThreaded$<$:Debug>DLL" @@ -70,7 +81,8 @@ "generator": "Ninja Multi-Config", "hidden": true, "binaryDir": "${sourceDir}/build/${presetName}", - "cacheVariables": { + "cacheVariables": + { "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", "CMAKE_MSVC_DEBUG_INFORMATION_FORMAT": "$<$:Embedded>", "CMAKE_MSVC_RUNTIME_LIBRARY": "MultiThreaded$<$:Debug>DLL", @@ -82,15 +94,19 @@ "inherits": "default", "hidden": false, "displayName": "Windows 32bit Release", - "architecture": { + "architecture": + { "value": "Win32", "strategy": "external" }, - "cacheVariables": { + "cacheVariables": + { "RTS_FLAGS": "/W3" }, - "vendor": { - "jetbrains.com/clion": { + "vendor": + { + "jetbrains.com/clion": + { "toolchain": "Visual Studio" } } @@ -99,7 +115,8 @@ "name": "win32-profile", "inherits": "win32", "displayName": "Windows 32bit Profile", - "cacheVariables": { + "cacheVariables": + { "RTS_BUILD_OPTION_PROFILE": "ON" } }, @@ -107,7 +124,8 @@ "name": "win32-internal", "inherits": "win32", "displayName": "Windows 32bit Internal", - "cacheVariables": { + "cacheVariables": + { "RTS_BUILD_OPTION_INTERNAL": "ON" } }, @@ -115,7 +133,8 @@ "name": "win32-debug", "inherits": "win32", "displayName": "Windows 32bit Debug", - "cacheVariables": { + "cacheVariables": + { "RTS_BUILD_OPTION_DEBUG": "ON" } }, @@ -124,15 +143,19 @@ "inherits": "default-vcpkg", "hidden": false, "displayName": "Windows 32bit VCPKG Release", - "architecture": { + "architecture": + { "value": "Win32", "strategy": "external" }, - "cacheVariables": { + "cacheVariables": + { "RTS_FLAGS": "/W3" }, - "vendor": { - "jetbrains.com/clion": { + "vendor": + { + "jetbrains.com/clion": + { "toolchain": "Visual Studio" } } @@ -141,7 +164,8 @@ "name": "win32-vcpkg-profile", "inherits": "win32-vcpkg", "displayName": "Windows 32bit VCPKG Profile", - "cacheVariables": { + "cacheVariables": + { "RTS_BUILD_OPTION_PROFILE": "ON" } }, @@ -149,7 +173,8 @@ "name": "win32-vcpkg-internal", "inherits": "win32-vcpkg", "displayName": "Windows 32bit VCPKG Internal", - "cacheVariables": { + "cacheVariables": + { "RTS_BUILD_OPTION_INTERNAL": "ON" } }, @@ -157,7 +182,8 @@ "name": "win32-vcpkg-debug", "inherits": "win32-vcpkg", "displayName": "Windows 32bit VCPKG Debug", - "cacheVariables": { + "cacheVariables": + { "RTS_BUILD_OPTION_DEBUG": "ON" } }, @@ -168,7 +194,8 @@ "displayName": "Unix 32bit VCPKG Release" } ], - "buildPresets": [ + "buildPresets": + [ { "name": "vc6", "configurePreset": "vc6", @@ -257,10 +284,12 @@ "configuration": "Release" } ], - "workflowPresets": [ + "workflowPresets": + [ { "name": "vc6", - "steps": [ + "steps": + [ { "type": "configure", "name": "vc6" @@ -273,7 +302,8 @@ }, { "name": "vc6-debug", - "steps": [ + "steps": + [ { "type": "configure", "name": "vc6-debug" @@ -286,7 +316,8 @@ }, { "name": "vc6-internal", - "steps": [ + "steps": + [ { "type": "configure", "name": "vc6-internal" @@ -299,7 +330,8 @@ }, { "name": "vc6-profile", - "steps": [ + "steps": + [ { "type": "configure", "name": "vc6-profile" @@ -312,7 +344,8 @@ }, { "name": "win32", - "steps": [ + "steps": + [ { "type": "configure", "name": "win32" @@ -325,7 +358,8 @@ }, { "name": "win32-internal", - "steps": [ + "steps": + [ { "type": "configure", "name": "win32-internal" @@ -338,7 +372,8 @@ }, { "name": "win32-profile", - "steps": [ + "steps": + [ { "type": "configure", "name": "win32-profile" @@ -351,7 +386,8 @@ }, { "name": "win32-debug", - "steps": [ + "steps": + [ { "type": "configure", "name": "win32-debug" @@ -364,7 +400,8 @@ }, { "name": "win32-vcpkg", - "steps": [ + "steps": + [ { "type": "configure", "name": "win32-vcpkg" @@ -377,7 +414,8 @@ }, { "name": "win32-vcpkg-internal", - "steps": [ + "steps": + [ { "type": "configure", "name": "win32-vcpkg-internal" @@ -390,7 +428,8 @@ }, { "name": "win32-vcpkg-profile", - "steps": [ + "steps": + [ { "type": "configure", "name": "win32-vcpkg-profile" @@ -403,7 +442,8 @@ }, { "name": "win32-vcpkg-debug", - "steps": [ + "steps": + [ { "type": "configure", "name": "win32-vcpkg-debug" @@ -416,7 +456,8 @@ }, { "name": "unix", - "steps": [ + "steps": + [ { "type": "configure", "name": "unix" diff --git a/Core/GameEngine/CMakeLists.txt b/Core/GameEngine/CMakeLists.txt index 35fc9a97d1e..56b8b90cfac 100644 --- a/Core/GameEngine/CMakeLists.txt +++ b/Core/GameEngine/CMakeLists.txt @@ -95,6 +95,7 @@ set(GAMEENGINE_SRC # Include/Common/RandomValue.h # Include/Common/Recorder.h # Include/Common/Registry.h + Include/Common/ReplaySimulation.h # Include/Common/ResourceGatheringManager.h # Include/Common/Science.h # Include/Common/ScopedMutex.h @@ -129,6 +130,7 @@ set(GAMEENGINE_SRC # Include/Common/UserPreferences.h # Include/Common/version.h # Include/Common/WellKnownKeys.h + Include/Common/WorkerProcess.h Include/Common/Xfer.h Include/Common/XferCRC.h Include/Common/XferDeepCRC.h @@ -607,6 +609,7 @@ set(GAMEENGINE_SRC # Source/Common/PerfTimer.cpp # Source/Common/RandomValue.cpp # Source/Common/Recorder.cpp + Source/Common/ReplaySimulation.cpp # Source/Common/RTS/AcademyStats.cpp # Source/Common/RTS/ActionManager.cpp # Source/Common/RTS/Energy.cpp @@ -676,6 +679,7 @@ set(GAMEENGINE_SRC # Source/Common/Thing/ThingTemplate.cpp # Source/Common/UserPreferences.cpp # Source/Common/version.cpp + Source/Common/WorkerProcess.cpp # Source/GameClient/ClientInstance.cpp # Source/GameClient/Color.cpp # Source/GameClient/Credits.cpp diff --git a/Core/GameEngine/Include/Common/ReplaySimulation.h b/Core/GameEngine/Include/Common/ReplaySimulation.h new file mode 100644 index 00000000000..219f6233709 --- /dev/null +++ b/Core/GameEngine/Include/Common/ReplaySimulation.h @@ -0,0 +1,48 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#pragma once + +class ReplaySimulation +{ +public: + + // TheSuperHackers @feature helmutbuhler 13/04/2025 + // Simulate a list of replays without graphics. + // Returns exit code 1 if mismatch or other error occurred + // Returns exit code 0 if all replays were successfully simulated without mismatches + static int simulateReplays(const std::vector &filenames, int maxProcesses); + + static void stop() { s_isRunning = false; } + + static Bool isRunning() { return s_isRunning; } + static UnsignedInt getCurrentReplayIndex() { return s_replayIndex; } + static UnsignedInt getReplayCount() { return s_replayCount; } + +private: + + static int simulateReplaysInThisProcess(const std::vector &filenames); + static int simulateReplaysInWorkerProcesses(const std::vector &filenames, int maxProcesses); + static std::vector resolveFilenameWildcards(const std::vector &filenames); + +private: + + static Bool s_isRunning; + static UnsignedInt s_replayIndex; + static UnsignedInt s_replayCount; +}; diff --git a/Core/GameEngine/Include/Common/WorkerProcess.h b/Core/GameEngine/Include/Common/WorkerProcess.h new file mode 100644 index 00000000000..0a0b104f64b --- /dev/null +++ b/Core/GameEngine/Include/Common/WorkerProcess.h @@ -0,0 +1,56 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#pragma once + +// Helper class that allows you to start a worker process and retrieve its exit code +// and console output as a string. +// It also makes sure that the started process is killed in case our process exits in any way. +class WorkerProcess +{ +public: + WorkerProcess(); + + bool startProcess(UnicodeString command); + + void update(); + + bool isRunning() const; + + // returns true iff the process exited. + bool isDone() const; + + DWORD getExitCode() const; + AsciiString getStdOutput() const; + + // Terminate Process if it's running + void kill(); + +private: + // returns true if all output has been received + // returns false if the worker is still running + bool fetchStdOutput(); + +private: + HANDLE m_processHandle; + HANDLE m_readHandle; + HANDLE m_jobHandle; + AsciiString m_stdOutput; + DWORD m_exitcode; + bool m_isDone; +}; diff --git a/Core/GameEngine/Source/Common/ReplaySimulation.cpp b/Core/GameEngine/Source/Common/ReplaySimulation.cpp new file mode 100644 index 00000000000..354925c8fcd --- /dev/null +++ b/Core/GameEngine/Source/Common/ReplaySimulation.cpp @@ -0,0 +1,255 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine + +#include "Common/ReplaySimulation.h" + +#include "Common/GameEngine.h" +#include "Common/LocalFileSystem.h" +#include "Common/Recorder.h" +#include "Common/WorkerProcess.h" +#include "GameLogic/GameLogic.h" +#include "GameClient/GameClient.h" + + +Bool ReplaySimulation::s_isRunning = false; +UnsignedInt ReplaySimulation::s_replayIndex = 0; +UnsignedInt ReplaySimulation::s_replayCount = 0; + +namespace +{ +int countProcessesRunning(const std::vector& processes) +{ + int numProcessesRunning = 0; + size_t i = 0; + for (; i < processes.size(); ++i) + { + if (processes[i].isRunning()) + ++numProcessesRunning; + } + return numProcessesRunning; +} +} // namespace + +int ReplaySimulation::simulateReplaysInThisProcess(const std::vector &filenames) +{ + int numErrors = 0; + + if (!TheGlobalData->m_headless) + { + s_isRunning = true; + s_replayIndex = 0; + s_replayCount = static_cast(filenames.size()); + + // If we are not in headless mode, we need to run the replay in the engine. + for (; s_replayIndex < s_replayCount; ++s_replayIndex) + { + TheRecorder->playbackFile(filenames[s_replayIndex]); + TheGameEngine->execute(); + if (TheRecorder->sawCRCMismatch()) + numErrors++; + if (!s_isRunning) + break; + TheGameEngine->setQuitting(FALSE); + } + s_isRunning = false; + s_replayIndex = 0; + s_replayCount = 0; + return numErrors != 0 ? 1 : 0; + } + // Note that we use printf here because this is run from cmd. + DWORD totalStartTimeMillis = GetTickCount(); + for (size_t i = 0; i < filenames.size(); i++) + { + AsciiString filename = filenames[i]; + printf("Simulating Replay \"%s\"\n", filename.str()); + fflush(stdout); + DWORD startTimeMillis = GetTickCount(); + if (TheRecorder->simulateReplay(filename)) + { + UnsignedInt totalTimeSec = TheRecorder->getPlaybackFrameCount() / LOGICFRAMES_PER_SECOND; + while (TheRecorder->isPlaybackInProgress()) + { + TheGameClient->updateHeadless(); + + const int progressFrameInterval = 10*60*LOGICFRAMES_PER_SECOND; + if (TheGameLogic->getFrame() != 0 && TheGameLogic->getFrame() % progressFrameInterval == 0) + { + // Print progress report + UnsignedInt gameTimeSec = TheGameLogic->getFrame() / LOGICFRAMES_PER_SECOND; + UnsignedInt realTimeSec = (GetTickCount()-startTimeMillis) / 1000; + printf("Elapsed Time: %02d:%02d Game Time: %02d:%02d/%02d:%02d\n", + realTimeSec/60, realTimeSec%60, gameTimeSec/60, gameTimeSec%60, totalTimeSec/60, totalTimeSec%60); + fflush(stdout); + } + TheGameLogic->UPDATE(); + if (TheRecorder->sawCRCMismatch()) + { + numErrors++; + break; + } + } + UnsignedInt gameTimeSec = TheGameLogic->getFrame() / LOGICFRAMES_PER_SECOND; + UnsignedInt realTimeSec = (GetTickCount()-startTimeMillis) / 1000; + printf("Elapsed Time: %02d:%02d Game Time: %02d:%02d/%02d:%02d\n", + realTimeSec/60, realTimeSec%60, gameTimeSec/60, gameTimeSec%60, totalTimeSec/60, totalTimeSec%60); + fflush(stdout); + } + else + { + printf("Cannot open replay\n"); + numErrors++; + } + } + if (filenames.size() > 1) + { + printf("Simulation of all replays completed. Errors occurred: %d\n", numErrors); + + UnsignedInt realTime = (GetTickCount()-totalStartTimeMillis) / 1000; + printf("Total Time: %d:%02d:%02d\n", realTime/60/60, realTime/60%60, realTime%60); + fflush(stdout); + } + + return numErrors != 0 ? 1 : 0; +} + +int ReplaySimulation::simulateReplaysInWorkerProcesses(const std::vector &filenames, int maxProcesses) +{ + DWORD totalStartTimeMillis = GetTickCount(); + + WideChar exePath[1024]; + GetModuleFileNameW(NULL, exePath, ARRAY_SIZE(exePath)); + + std::vector processes; + int filenamePositionStarted = 0; + int filenamePositionDone = 0; + int numErrors = 0; + + while (true) + { + int i; + for (i = 0; i < processes.size(); i++) + processes[i].update(); + + // Get result of finished processes and print output in order + while (!processes.empty()) + { + if (!processes[0].isDone()) + break; + AsciiString stdOutput = processes[0].getStdOutput(); + printf("%d/%d %s", filenamePositionDone+1, (int)filenames.size(), stdOutput.str()); + DWORD exitcode = processes[0].getExitCode(); + if (exitcode != 0) + printf("Error!\n"); + fflush(stdout); + numErrors += exitcode == 0 ? 0 : 1; + processes.erase(processes.begin()); + filenamePositionDone++; + } + + int numProcessesRunning = countProcessesRunning(processes); + + // Add new processes when we are below the limit and there are replays left + while (numProcessesRunning < maxProcesses && filenamePositionStarted < filenames.size()) + { + UnicodeString filenameWide; + filenameWide.translate(filenames[filenamePositionStarted]); + UnicodeString command; + command.format(L"\"%s\"%s%s -replay \"%s\"", + exePath, + TheGlobalData->m_windowed ? L" -win" : L"", + TheGlobalData->m_headless ? L" -headless" : L"", + filenameWide.str()); + + processes.push_back(WorkerProcess()); + processes.back().startProcess(command); + + filenamePositionStarted++; + numProcessesRunning++; + } + + if (processes.empty()) + break; + + // Don't waste CPU here, our workers need every bit of CPU time they can get + Sleep(100); + } + + DEBUG_ASSERTCRASH(filenamePositionStarted == filenames.size(), ("inconsistent file position 1")); + DEBUG_ASSERTCRASH(filenamePositionDone == filenames.size(), ("inconsistent file position 2")); + + printf("Simulation of all replays completed. Errors occurred: %d\n", numErrors); + + UnsignedInt realTime = (GetTickCount()-totalStartTimeMillis) / 1000; + printf("Total Wall Time: %d:%02d:%02d\n", realTime/60/60, realTime/60%60, realTime%60); + fflush(stdout); + + return numErrors != 0 ? 1 : 0; +} + +std::vector ReplaySimulation::resolveFilenameWildcards(const std::vector &filenames) +{ + // If some filename contains wildcards, search for actual filenames. + // Note that we cannot do this in parseReplay because we require TheLocalFileSystem initialized. + std::vector filenamesResolved; + for (std::vector::const_iterator filename = filenames.begin(); filename != filenames.end(); ++filename) + { + if (filename->find('*') || filename->find('?')) + { + AsciiString dir1 = TheRecorder->getReplayDir(); + AsciiString dir2 = *filename; + AsciiString wildcard = *filename; + { + int len = dir2.getLength(); + while (len) + { + char c = dir2.getCharAt(len-1); + if (c == '/' || c == '\\') + { + wildcard.set(wildcard.str()+dir2.getLength()); + break; + } + dir2.removeLastChar(); + len--; + } + } + + FilenameList files; + TheLocalFileSystem->getFileListInDirectory(dir2.str(), dir1.str(), wildcard, files, FALSE); + for (FilenameList::iterator it = files.begin(); it != files.end(); ++it) + { + AsciiString file; + file.set(it->str() + dir1.getLength()); + filenamesResolved.push_back(file); + } + } + else + filenamesResolved.push_back(*filename); + } + return filenamesResolved; +} + +int ReplaySimulation::simulateReplays(const std::vector &filenames, int maxProcesses) +{ + std::vector filenamesResolved = resolveFilenameWildcards(filenames); + if (maxProcesses == SIMULATE_REPLAYS_SEQUENTIAL) + return simulateReplaysInThisProcess(filenamesResolved); + else + return simulateReplaysInWorkerProcesses(filenamesResolved, maxProcesses); +} diff --git a/Core/GameEngine/Source/Common/WorkerProcess.cpp b/Core/GameEngine/Source/Common/WorkerProcess.cpp new file mode 100644 index 00000000000..5514c4c2882 --- /dev/null +++ b/Core/GameEngine/Source/Common/WorkerProcess.cpp @@ -0,0 +1,231 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine +#include "Common/WorkerProcess.h" + +// We need Job-related functions, but these aren't defined in the Windows-headers that VC6 uses. +// So we define them here and load them dynamically. +#if defined(_MSC_VER) && _MSC_VER < 1300 +struct JOBOBJECT_BASIC_LIMIT_INFORMATION2 +{ + LARGE_INTEGER PerProcessUserTimeLimit; + LARGE_INTEGER PerJobUserTimeLimit; + DWORD LimitFlags; + SIZE_T MinimumWorkingSetSize; + SIZE_T MaximumWorkingSetSize; + DWORD ActiveProcessLimit; + ULONG_PTR Affinity; + DWORD PriorityClass; + DWORD SchedulingClass; +}; +struct IO_COUNTERS +{ + ULONGLONG ReadOperationCount; + ULONGLONG WriteOperationCount; + ULONGLONG OtherOperationCount; + ULONGLONG ReadTransferCount; + ULONGLONG WriteTransferCount; + ULONGLONG OtherTransferCount; +}; +struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION +{ + JOBOBJECT_BASIC_LIMIT_INFORMATION2 BasicLimitInformation; + IO_COUNTERS IoInfo; + SIZE_T ProcessMemoryLimit; + SIZE_T JobMemoryLimit; + SIZE_T PeakProcessMemoryUsed; + SIZE_T PeakJobMemoryUsed; +}; + +#define JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE 0x00002000 +const int JobObjectExtendedLimitInformation = 9; + +typedef HANDLE (WINAPI *PFN_CreateJobObjectW)(LPSECURITY_ATTRIBUTES, LPCWSTR); +typedef BOOL (WINAPI *PFN_SetInformationJobObject)(HANDLE, JOBOBJECTINFOCLASS, LPVOID, DWORD); +typedef BOOL (WINAPI *PFN_AssignProcessToJobObject)(HANDLE, HANDLE); + +static PFN_CreateJobObjectW CreateJobObjectW = (PFN_CreateJobObjectW)GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "CreateJobObjectW"); +static PFN_SetInformationJobObject SetInformationJobObject = (PFN_SetInformationJobObject)GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "SetInformationJobObject"); +static PFN_AssignProcessToJobObject AssignProcessToJobObject = (PFN_AssignProcessToJobObject)GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "AssignProcessToJobObject"); +#endif + +WorkerProcess::WorkerProcess() +{ + m_processHandle = NULL; + m_readHandle = NULL; + m_jobHandle = NULL; + m_exitcode = 0; + m_isDone = false; +} + +bool WorkerProcess::startProcess(UnicodeString command) +{ + m_stdOutput.clear(); + m_isDone = false; + + // Create pipe for reading console output + SECURITY_ATTRIBUTES saAttr = { sizeof(SECURITY_ATTRIBUTES) }; + saAttr.bInheritHandle = TRUE; + HANDLE writeHandle = NULL; + if (!CreatePipe(&m_readHandle, &writeHandle, &saAttr, 0)) + return false; + SetHandleInformation(m_readHandle, HANDLE_FLAG_INHERIT, 0); + + STARTUPINFOW si = { sizeof(STARTUPINFOW) }; + si.dwFlags = STARTF_FORCEOFFFEEDBACK; // Prevent cursor wait animation + si.dwFlags |= STARTF_USESTDHANDLES; + si.hStdError = writeHandle; + si.hStdOutput = writeHandle; + + PROCESS_INFORMATION pi = { 0 }; + + if (!CreateProcessW(NULL, (LPWSTR)command.str(), + NULL, NULL, /*bInheritHandles=*/TRUE, 0, + NULL, 0, &si, &pi)) + { + CloseHandle(writeHandle); + CloseHandle(m_readHandle); + m_readHandle = NULL; + return false; + } + + CloseHandle(pi.hThread); + CloseHandle(writeHandle); + m_processHandle = pi.hProcess; + + // We want to make sure that when our process is killed, our workers automatically terminate as well. + // In Windows, the way to do this is to attach the worker to a job we own. + m_jobHandle = CreateJobObjectW != NULL ? CreateJobObjectW(NULL, NULL) : NULL; + if (m_jobHandle != NULL) + { + JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobInfo = { 0 }; + jobInfo.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + SetInformationJobObject(m_jobHandle, (JOBOBJECTINFOCLASS)JobObjectExtendedLimitInformation, &jobInfo, sizeof(jobInfo)); + AssignProcessToJobObject(m_jobHandle, m_processHandle); + } + + return true; +} + +bool WorkerProcess::isRunning() const +{ + return m_processHandle != NULL; +} + +bool WorkerProcess::isDone() const +{ + return m_isDone; +} + +DWORD WorkerProcess::getExitCode() const +{ + return m_exitcode; +} + +AsciiString WorkerProcess::getStdOutput() const +{ + return m_stdOutput; +} + +bool WorkerProcess::fetchStdOutput() +{ + while (true) + { + // Call PeekNamedPipe to make sure ReadFile won't block + DWORD bytesAvailable = 0; + DEBUG_ASSERTCRASH(m_readHandle != NULL, ("Is not expected NULL")); + BOOL success = PeekNamedPipe(m_readHandle, NULL, 0, NULL, &bytesAvailable, NULL); + if (!success) + return true; + if (bytesAvailable == 0) + { + // Child process is still running and we have all output so far + return false; + } + + DWORD readBytes = 0; + char buffer[1024]; + success = ReadFile(m_readHandle, buffer, ARRAY_SIZE(buffer)-1, &readBytes, NULL); + if (!success) + return true; + DEBUG_ASSERTCRASH(readBytes != 0, ("expected readBytes to be non null")); + + // Remove \r, otherwise each new line is doubled when we output it again + for (int i = 0; i < readBytes; i++) + if (buffer[i] == '\r') + buffer[i] = ' '; + buffer[readBytes] = 0; + m_stdOutput.concat(buffer); + } +} + +void WorkerProcess::update() +{ + if (!isRunning()) + return; + + if (!fetchStdOutput()) + { + // There is still potential output pending + return; + } + + // Pipe broke, that means the process already exited. But we call this just to make sure + WaitForSingleObject(m_processHandle, INFINITE); + GetExitCodeProcess(m_processHandle, &m_exitcode); + CloseHandle(m_processHandle); + m_processHandle = NULL; + + CloseHandle(m_readHandle); + m_readHandle = NULL; + + CloseHandle(m_jobHandle); + m_jobHandle = NULL; + + m_isDone = true; +} + +void WorkerProcess::kill() +{ + if (!isRunning()) + return; + + if (m_processHandle != NULL) + { + TerminateProcess(m_processHandle, 1); + CloseHandle(m_processHandle); + m_processHandle = NULL; + } + + if (m_readHandle != NULL) + { + CloseHandle(m_readHandle); + m_readHandle = NULL; + } + + if (m_jobHandle != NULL) + { + CloseHandle(m_jobHandle); + m_jobHandle = NULL; + } + + m_stdOutput.clear(); + m_isDone = false; +} + diff --git a/Core/Libraries/Source/WWVegas/WW3D2/rendobj.cpp b/Core/Libraries/Source/WWVegas/WW3D2/rendobj.cpp index 8e42e9f998b..0c9b8faad04 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/rendobj.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/rendobj.cpp @@ -1221,6 +1221,7 @@ PersistClass * RenderObjPersistFactoryClass::Load(ChunkLoadClass & cload) const RenderObjClass * old_obj = NULL; Matrix3D tm(1); char name[64]; + name[0] = '\0'; while (cload.Open_Chunk()) { switch (cload.Cur_Chunk_ID()) { diff --git a/Core/Libraries/Source/WWVegas/WWDownload/FTP.CPP b/Core/Libraries/Source/WWVegas/WWDownload/FTP.CPP index 1a55571639d..e847ce8691a 100644 --- a/Core/Libraries/Source/WWVegas/WWDownload/FTP.CPP +++ b/Core/Libraries/Source/WWVegas/WWDownload/FTP.CPP @@ -659,7 +659,7 @@ HRESULT Cftp::LogoffFromServer( void ) HRESULT Cftp::FindFile( LPCSTR szRemoteFileName, int * piSize ) { char command[ 256 ]; - static char listline[ 256 ]; + static char listline[ 256 ] = {0}; int i, iReply; char ext[ 10 ]; diff --git a/Core/Libraries/Source/WWVegas/WWLib/always.h b/Core/Libraries/Source/WWVegas/WWLib/always.h index 7439bc9e628..255a306647f 100644 --- a/Core/Libraries/Source/WWVegas/WWLib/always.h +++ b/Core/Libraries/Source/WWVegas/WWLib/always.h @@ -46,7 +46,6 @@ // TheSuperHackers @compile feliwir 17/04/2025 include utility macros for cross-platform compatibility #include #include - #include // Disable warning about exception handling not being enabled. It's used as part of STL - in a part of STL we don't use. diff --git a/Core/Libraries/Source/WWVegas/WWLib/refcount.h b/Core/Libraries/Source/WWVegas/WWLib/refcount.h index c7d1a702936..6ee4f8f9b60 100644 --- a/Core/Libraries/Source/WWVegas/WWLib/refcount.h +++ b/Core/Libraries/Source/WWVegas/WWLib/refcount.h @@ -123,6 +123,9 @@ class RefCountClass #endif } + /* + ** The reference counter value cannot be copied. + */ RefCountClass(const RefCountClass & ) : NumRefs(1) #ifndef NDEBUG @@ -135,6 +138,8 @@ class RefCountClass #endif } + RefCountClass& operator=(const RefCountClass&) { return *this; } + /* ** Add_Ref, call this function if you are going to keep a pointer ** to this object. @@ -280,6 +285,12 @@ class RefCountValue WWASSERT(NumRefs == IntegerType(0)); } + /* + ** The reference counter value cannot be copied. + */ + RefCountValue(const RefCountValue&) : NumRefs(1) {} + RefCountValue& operator=(const RefCountValue&) { return *this; } + /* ** Add_Ref, call this function if you are going to keep a pointer to this object. */ diff --git a/Dependencies/Utility/Utility/CppMacros.h b/Dependencies/Utility/Utility/CppMacros.h index 57aaa75abc8..59a5ae09afa 100644 --- a/Dependencies/Utility/Utility/CppMacros.h +++ b/Dependencies/Utility/Utility/CppMacros.h @@ -21,8 +21,12 @@ #if __cplusplus >= 201703L #define NOEXCEPT_17 noexcept +#define REGISTER +#define FALLTHROUGH [[fallthrough]] #else #define NOEXCEPT_17 +#define REGISTER register +#define FALLTHROUGH #endif // noexcept for methods of IUNKNOWN interface @@ -33,15 +37,11 @@ #endif #if __cplusplus >= 201103L - #define CPP_11(code) code -#else - #define CPP_11(code) -#endif - -#if __cplusplus >= 201703L -#define REGISTER + #define CPP_11(code) code + #define CONSTEXPR constexpr #else -#define REGISTER register + #define CPP_11(code) + #define CONSTEXPR #endif #if __cplusplus < 201103L diff --git a/Generals/Code/GameEngine/CMakeLists.txt b/Generals/Code/GameEngine/CMakeLists.txt index f1fe224b5b2..eed55eae5b6 100644 --- a/Generals/Code/GameEngine/CMakeLists.txt +++ b/Generals/Code/GameEngine/CMakeLists.txt @@ -91,6 +91,7 @@ set(GAMEENGINE_SRC Include/Common/RAMFile.h Include/Common/RandomValue.h Include/Common/Recorder.h +# Include/Common/ReplaySimulation.h Include/Common/Registry.h Include/Common/ResourceGatheringManager.h Include/Common/Science.h @@ -123,6 +124,7 @@ set(GAMEENGINE_SRC Include/Common/UserPreferences.h Include/Common/version.h Include/Common/WellKnownKeys.h +# Include/Common/WorkerProcess.h # Include/Common/Xfer.h # Include/Common/XferCRC.h # Include/Common/XferDeepCRC.h @@ -633,6 +635,7 @@ set(GAMEENGINE_SRC Source/Common/Thing/ThingTemplate.cpp Source/Common/UserPreferences.cpp Source/Common/version.cpp +# Source/Common/WorkerProcess.cpp Source/GameClient/ClientInstance.cpp Source/GameClient/Color.cpp Source/GameClient/Credits.cpp diff --git a/Generals/Code/GameEngine/Include/Common/GameEngine.h b/Generals/Code/GameEngine/Include/Common/GameEngine.h index e483db6548d..0b2ab9f6137 100644 --- a/Generals/Code/GameEngine/Include/Common/GameEngine.h +++ b/Generals/Code/GameEngine/Include/Common/GameEngine.h @@ -67,7 +67,6 @@ class GameEngine : public SubsystemInterface virtual ~GameEngine(); virtual void init( void ); ///< Init engine by creating client and logic - virtual void init( int argc, char *argv[] ); ///< Init engine by creating client and logic virtual void reset( void ); ///< reset system to starting state virtual void update( void ); ///< per frame update @@ -115,6 +114,6 @@ extern GameEngine *TheGameEngine; extern GameEngine *CreateGameEngine( void ); /// The entry point for the game system -extern void GameMain( int argc, char *argv[] ); +extern Int GameMain(); #endif // _GAME_ENGINE_H_ diff --git a/Generals/Code/GameEngine/Include/Common/GlobalData.h b/Generals/Code/GameEngine/Include/Common/GlobalData.h index dbf13303402..5cbd6975ef4 100644 --- a/Generals/Code/GameEngine/Include/Common/GlobalData.h +++ b/Generals/Code/GameEngine/Include/Common/GlobalData.h @@ -52,7 +52,8 @@ enum AIDebugOptions CPP_11(: Int); // PUBLIC ///////////////////////////////////////////////////////////////////////////////////////// -const Int MAX_GLOBAL_LIGHTS = 3; +CONSTEXPR const Int MAX_GLOBAL_LIGHTS = 3; +CONSTEXPR const Int SIMULATE_REPLAYS_SEQUENTIAL = -1; //------------------------------------------------------------------------------------------------- class CommandLineData @@ -338,9 +339,12 @@ class GlobalData : public SubsystemInterface Real m_cameraAdjustSpeed; ///< Rate at which we adjust camera height Bool m_enforceMaxCameraHeight; ///< Enfoce max camera height while scrolling? Bool m_buildMapCache; - AsciiString m_initialFile; ///< If this is specified, load a specific map/replay from the command-line + AsciiString m_initialFile; ///< If this is specified, load a specific map from the command-line AsciiString m_pendingFile; ///< If this is specified, use this map at the next game start + std::vector m_simulateReplays; ///< If not empty, simulate this list of replays and exit. + Int m_simulateReplayJobs; ///< Maximum number of processes to use for simulation, or SIMULATE_REPLAYS_SEQUENTIAL for sequential simulation + Int m_maxParticleCount; ///< maximum number of particles that can exist Int m_maxFieldParticleCount; ///< maximum number of field-type particles that can exist (roughly) WeaponBonusSet* m_weaponBonusSet; @@ -560,6 +564,11 @@ class GlobalData : public SubsystemInterface // singleton extern GlobalData* TheWritableGlobalData; +// use TheGlobalData for all read-only accesses +#if __cplusplus >= 201703L +inline const GlobalData* const& TheGlobalData = TheWritableGlobalData; +#else #define TheGlobalData ((const GlobalData*)TheWritableGlobalData) +#endif #endif diff --git a/Generals/Code/GameEngine/Include/Common/Recorder.h b/Generals/Code/GameEngine/Include/Common/Recorder.h index fefd82c89d4..7663b8dc919 100644 --- a/Generals/Code/GameEngine/Include/Common/Recorder.h +++ b/Generals/Code/GameEngine/Include/Common/Recorder.h @@ -48,6 +48,7 @@ class ReplayGameInfo : public GameInfo enum RecorderModeType CPP_11(: Int) { RECORDERMODETYPE_RECORD, RECORDERMODETYPE_PLAYBACK, + RECORDERMODETYPE_SIMULATION_PLAYBACK, // Play back replay without any graphics RECORDERMODETYPE_NONE // this is a valid state to be in on the shell map, or in saved games }; @@ -74,11 +75,13 @@ class RecorderClass : public SubsystemInterface { Bool replayMatchesGameVersion(AsciiString filename); ///< Returns true if the playback is a valid playback file for this version. static Bool replayMatchesGameVersion(const ReplayHeader& header); ///< Returns true if the playback is a valid playback file for this version. AsciiString getCurrentReplayFilename( void ); ///< valid during playback only + UnsignedInt getPlaybackFrameCount() const { return m_playbackFrameCount; } ///< valid during playback only void stopPlayback(); ///< Stops playback. Its fine to call this even if not playing back a file. + Bool simulateReplay(AsciiString filename); #if defined RTS_DEBUG || defined RTS_INTERNAL Bool analyzeReplay( AsciiString filename ); - Bool isAnalysisInProgress( void ); #endif + Bool isPlaybackInProgress() const; public: void handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool fromPlayback); @@ -100,7 +103,7 @@ class RecorderClass : public SubsystemInterface { UnsignedInt iniCRC; time_t startTime; time_t endTime; - UnsignedInt frameDuration; + UnsignedInt frameCount; Bool quitEarly; Bool desyncGame; Bool playerDiscons[MAX_SLOTS]; @@ -110,10 +113,11 @@ class RecorderClass : public SubsystemInterface { Bool readReplayHeader( ReplayHeader& header ); RecorderModeType getMode(); ///< Returns the current operating mode. + Bool isPlaybackMode() const { return m_mode == RECORDERMODETYPE_PLAYBACK || m_mode == RECORDERMODETYPE_SIMULATION_PLAYBACK; } void initControls(); ///< Show or Hide the Replay controls AsciiString getReplayDir(); ///< Returns the directory that holds the replay files. - AsciiString getReplayExtention(); ///< Returns the file extention for replay files. + static AsciiString getReplayExtention(); ///< Returns the file extention for replay files. AsciiString getLastReplayFileName(); ///< Returns the filename used for the default replay. GameInfo *getGameInfo( void ) { return &m_gameInfo; } ///< Returns the slot list for playback game start @@ -124,6 +128,7 @@ class RecorderClass : public SubsystemInterface { void logPlayerDisconnect(UnicodeString player, Int slot); void logCRCMismatch( void ); + Bool sawCRCMismatch() const; void cleanUpReplayFile( void ); ///< after a crash, send replay/debug info to a central repository void stopRecording(); ///< Stop recording and close m_file. @@ -154,6 +159,7 @@ class RecorderClass : public SubsystemInterface { Int m_currentFilePosition; RecorderModeType m_mode; AsciiString m_currentReplayFilename; ///< valid during playback only + UnsignedInt m_playbackFrameCount; ReplayGameInfo m_gameInfo; Bool m_wasDesync; diff --git a/Generals/Code/GameEngine/Include/GameClient/ClientInstance.h b/Generals/Code/GameEngine/Include/GameClient/ClientInstance.h index 74e7add404b..1f1ada1daca 100644 --- a/Generals/Code/GameEngine/Include/GameClient/ClientInstance.h +++ b/Generals/Code/GameEngine/Include/GameClient/ClientInstance.h @@ -30,6 +30,15 @@ class ClientInstance static bool isInitialized(); + static bool isMultiInstance(); + + // Change multi instance on runtime. Must be called before initialize. + static void setMultiInstance(bool v); + + // Skips using the primary instance. Must be called before initialize. + // Useful when the new process is not meant to collide with another normal Generals process. + static void skipPrimaryInstance(); + // Returns the instance index of this game client. Starts at 0. static UnsignedInt getInstanceIndex(); @@ -42,6 +51,7 @@ class ClientInstance private: static HANDLE s_mutexHandle; static UnsignedInt s_instanceIndex; + static Bool s_isMultiInstance; }; } // namespace rts diff --git a/Generals/Code/GameEngine/Include/GameClient/GameClient.h b/Generals/Code/GameEngine/Include/GameClient/GameClient.h index d599305b21c..f0c6a708029 100644 --- a/Generals/Code/GameEngine/Include/GameClient/GameClient.h +++ b/Generals/Code/GameEngine/Include/GameClient/GameClient.h @@ -95,6 +95,8 @@ class GameClient : public SubsystemInterface, virtual void setFrame( UnsignedInt frame ) { m_frame = frame; } ///< Set the GameClient's internal frame number virtual void registerDrawable( Drawable *draw ); ///< Given a drawable, register it with the GameClient and give it a unique ID + void updateHeadless(); + void addDrawableToLookupTable( Drawable *draw ); ///< add drawable ID to hash lookup table void removeDrawableFromLookupTable( Drawable *draw ); ///< remove drawable ID from hash lookup table diff --git a/Generals/Code/GameEngine/Source/Common/CommandLine.cpp b/Generals/Code/GameEngine/Source/Common/CommandLine.cpp index b01b0d10bdb..ec75d89b2fc 100644 --- a/Generals/Code/GameEngine/Source/Common/CommandLine.cpp +++ b/Generals/Code/GameEngine/Source/Common/CommandLine.cpp @@ -29,7 +29,9 @@ #include "Common/CommandLine.h" #include "Common/CRCDebug.h" #include "Common/LocalFileSystem.h" +#include "Common/Recorder.h" #include "Common/version.h" +#include "GameClient/ClientInstance.h" #include "GameClient/TerrainVisual.h" // for TERRAIN_LOD_MIN definition #include "GameClient/GameText.h" #include "GameNetwork/NetworkDefs.h" @@ -405,6 +407,56 @@ Int parseMapName(char *args[], int num) return 1; } +Int parseHeadless(char *args[], int num) +{ + TheWritableGlobalData->m_headless = TRUE; + TheWritableGlobalData->m_playIntro = FALSE; + TheWritableGlobalData->m_afterIntro = TRUE; + TheWritableGlobalData->m_playSizzle = FALSE; + return 1; +} + +Int parseReplay(char *args[], int num) +{ + if (num > 1) + { + AsciiString filename = args[1]; + if (!filename.endsWithNoCase(RecorderClass::getReplayExtention())) + { + printf("Invalid replay name \"%s\"\n", filename.str()); + exit(1); + } + TheWritableGlobalData->m_simulateReplays.push_back(filename); + + TheWritableGlobalData->m_playIntro = FALSE; + TheWritableGlobalData->m_afterIntro = TRUE; + TheWritableGlobalData->m_playSizzle = FALSE; + TheWritableGlobalData->m_shellMapOn = FALSE; + + // Make replay playback possible while other clients (possible retail) are running + rts::ClientInstance::setMultiInstance(TRUE); + rts::ClientInstance::skipPrimaryInstance(); + + return 2; + } + return 1; +} + +Int parseJobs(char *args[], int num) +{ + if (num > 1) + { + TheWritableGlobalData->m_simulateReplayJobs = atoi(args[1]); + if (TheGlobalData->m_simulateReplayJobs < SIMULATE_REPLAYS_SEQUENTIAL || TheGlobalData->m_simulateReplayJobs == 0) + { + printf("Invalid number of jobs: %d\n", TheGlobalData->m_simulateReplayJobs); + exit(1); + } + return 2; + } + return 1; +} + Int parseXRes(char *args[], int num) { if (num > 1) @@ -782,13 +834,6 @@ Int parseQuickStart( char *args[], int num ) return 1; } -Int parseHeadless( char *args[], int num ) -{ - TheWritableGlobalData->m_headless = TRUE; - - return 1; -} - Int parseConstantDebug( char *args[], int num ) { TheWritableGlobalData->m_constantDebugUpdate = TRUE; @@ -1103,8 +1148,20 @@ static CommandLineParam paramsForStartup[] = { "-fullscreen", parseNoWin }, // TheSuperHackers @feature helmutbuhler 11/04/2025 - // This runs the game without a window, graphics, input and audio. Used for testing. + // This runs the game without a window, graphics, input and audio. You can combine this with -replay { "-headless", parseHeadless }, + + // TheSuperHackers @feature helmutbuhler 13/04/2025 + // Play back a replay. Pass the filename including .rep afterwards. + // You can pass this multiple times to play back multiple replays. + // You can also include wildcards. The file must be in the replay folder or in a subfolder. + { "-replay", parseReplay }, + + // TheSuperHackers @feature helmutbuhler 23/05/2025 + // Simulate each replay in a separate process and use 1..N processes at the same time. + // (If you have 4 cores, call it with -jobs 4) + // If you do not call this, all replays will be simulated in sequence in the same process. + { "-jobs", parseJobs }, }; // These Params are parsed during Engine Init before INI data is loaded diff --git a/Generals/Code/GameEngine/Source/Common/Dict.cpp b/Generals/Code/GameEngine/Source/Common/Dict.cpp index 3aaa9e272b2..c4bb7a2266a 100644 --- a/Generals/Code/GameEngine/Source/Common/Dict.cpp +++ b/Generals/Code/GameEngine/Source/Common/Dict.cpp @@ -182,7 +182,7 @@ Dict::DictPair *Dict::ensureUnique(int numPairsNeeded, Bool preserveData, DictPa } } - Int delta; + Int delta = 0; if (pairToTranslate && m_data) delta = pairToTranslate - m_data->peek(); diff --git a/Generals/Code/GameEngine/Source/Common/GameEngine.cpp b/Generals/Code/GameEngine/Source/Common/GameEngine.cpp index f6a97e93b21..4542419d928 100644 --- a/Generals/Code/GameEngine/Source/Common/GameEngine.cpp +++ b/Generals/Code/GameEngine/Source/Common/GameEngine.cpp @@ -250,8 +250,7 @@ void GameEngine::setFramesPerSecondLimit( Int fps ) /** ----------------------------------------------------------------------------------------------- * Initialize the game engine by initializing the GameLogic and GameClient. */ -void GameEngine::init( void ) {} /// @todo: I changed this to take argc & argv so we can parse those after the GDF is loaded. We need to rethink this immediately as it is a nasty hack -void GameEngine::init( int argc, char *argv[] ) +void GameEngine::init() { try { //create an INI object to use for loading stuff @@ -442,7 +441,7 @@ void GameEngine::init( int argc, char *argv[] ) // load the initial shell screen //TheShell->push( AsciiString("Menus/MainMenu.wnd") ); - // This allows us to run a map/reply from the command line + // This allows us to run a map from the command line if (TheGlobalData->m_initialFile.isEmpty() == FALSE) { AsciiString fname = TheGlobalData->m_initialFile; @@ -464,10 +463,6 @@ void GameEngine::init( int argc, char *argv[] ) msg->appendIntegerArgument(0); InitRandom(0); } - else if (fname.endsWithNoCase(".rep")) - { - TheRecorder->playbackFile(fname); - } } // diff --git a/Generals/Code/GameEngine/Source/Common/GameLOD.cpp b/Generals/Code/GameEngine/Source/Common/GameLOD.cpp index 08600c85ce3..0a6568d9f43 100644 --- a/Generals/Code/GameEngine/Source/Common/GameLOD.cpp +++ b/Generals/Code/GameEngine/Source/Common/GameLOD.cpp @@ -253,13 +253,17 @@ BenchProfile *GameLODManager::newBenchProfile(void) LODPresetInfo *GameLODManager::newLODPreset(StaticGameLODLevel index) { - if (m_numLevelPresets[index] < MAX_LOD_PRESETS_PER_LEVEL) - { - m_numLevelPresets[index]++; - return &m_lodPresets[index][m_numLevelPresets[index]-1]; + if (index >= 0 && index < STATIC_GAME_LOD_COUNT) + { + if (m_numLevelPresets[index] < MAX_LOD_PRESETS_PER_LEVEL) + { + m_numLevelPresets[index]++; + return &m_lodPresets[index][m_numLevelPresets[index]-1]; + } + + DEBUG_CRASH(( "GameLODManager::newLODPreset - Too many presets defined for '%s'\n", TheGameLODManager->getStaticGameLODLevelName(index))); } - DEBUG_CRASH(( "GameLODManager::newLODPreset - Too many presets defined for '%s'\n", TheGameLODManager->getStaticGameLODLevelName(index))); return NULL; } @@ -577,9 +581,10 @@ void GameLODManager::applyStaticLODLevel(StaticGameLODLevel level) TheWritableGlobalData->m_enableDynamicLOD = lodInfo->m_enableDynamicLOD; TheWritableGlobalData->m_useFpsLimit = lodInfo->m_useFpsLimit; TheWritableGlobalData->m_useTrees = requestedTrees; - } - if (!m_memPassed || isReallyLowMHz()) { - TheWritableGlobalData->m_shellMapOn = false; + + if (!m_memPassed || isReallyLowMHz()) { + TheWritableGlobalData->m_shellMapOn = false; + } } if (TheTerrainVisual) TheTerrainVisual->setTerrainTracksDetail(); @@ -688,6 +693,9 @@ Int GameLODManager::getRecommendedTextureReduction(void) if (!m_memPassed) //if they have < 256 MB, force them to low res textures. return m_staticGameLODInfo[STATIC_GAME_LOD_LOW].m_textureReduction; + if (m_idealDetailLevel < 0 || m_idealDetailLevel >= STATIC_GAME_LOD_COUNT) + return m_staticGameLODInfo[STATIC_GAME_LOD_LOW].m_textureReduction; + return m_staticGameLODInfo[m_idealDetailLevel].m_textureReduction; } diff --git a/Generals/Code/GameEngine/Source/Common/GameMain.cpp b/Generals/Code/GameEngine/Source/Common/GameMain.cpp index a8cbc291f91..cf5f2e3be84 100644 --- a/Generals/Code/GameEngine/Source/Common/GameMain.cpp +++ b/Generals/Code/GameEngine/Source/Common/GameMain.cpp @@ -29,23 +29,33 @@ #include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine #include "Common/GameEngine.h" +#include "Common/ReplaySimulation.h" /** * This is the entry point for the game system. */ -void GameMain( int argc, char *argv[] ) +Int GameMain() { + int exitcode = 0; // initialize the game engine using factory function TheGameEngine = CreateGameEngine(); - TheGameEngine->init(argc, argv); - - // run it - TheGameEngine->execute(); + TheGameEngine->init(); + + if (!TheGlobalData->m_simulateReplays.empty()) + { + exitcode = ReplaySimulation::simulateReplays(TheGlobalData->m_simulateReplays, TheGlobalData->m_simulateReplayJobs); + } + else + { + // run it + TheGameEngine->execute(); + } // since execute() returned, we are exiting the game delete TheGameEngine; TheGameEngine = NULL; + return exitcode; } diff --git a/Generals/Code/GameEngine/Source/Common/GlobalData.cpp b/Generals/Code/GameEngine/Source/Common/GlobalData.cpp index b7f04ba32c2..5f9e9cd2942 100644 --- a/Generals/Code/GameEngine/Source/Common/GlobalData.cpp +++ b/Generals/Code/GameEngine/Source/Common/GlobalData.cpp @@ -948,6 +948,9 @@ GlobalData::GlobalData() m_initialFile.clear(); m_pendingFile.clear(); + m_simulateReplays.clear(); + m_simulateReplayJobs = SIMULATE_REPLAYS_SEQUENTIAL; + for (i = LEVEL_FIRST; i <= LEVEL_LAST; ++i) m_healthBonus[i] = 1.0f; diff --git a/Generals/Code/GameEngine/Source/Common/INI/INI.cpp b/Generals/Code/GameEngine/Source/Common/INI/INI.cpp index ec573971627..5fa23ac345a 100644 --- a/Generals/Code/GameEngine/Source/Common/INI/INI.cpp +++ b/Generals/Code/GameEngine/Source/Common/INI/INI.cpp @@ -725,6 +725,7 @@ AsciiString INI::getNextQuotedAsciiString() { AsciiString result; char buff[INI_MAX_CHARS_PER_LINE]; + buff[0] = '\0'; const char *token = getNextTokenOrNull(); // if null, just leave an empty string if (token != NULL) diff --git a/Generals/Code/GameEngine/Source/Common/RTS/Money.cpp b/Generals/Code/GameEngine/Source/Common/RTS/Money.cpp index 13d219f0648..035033d723e 100644 --- a/Generals/Code/GameEngine/Source/Common/RTS/Money.cpp +++ b/Generals/Code/GameEngine/Source/Common/RTS/Money.cpp @@ -59,13 +59,14 @@ UnsignedInt Money::withdraw(UnsignedInt amountToWithdraw, Bool playSound) if (amountToWithdraw == 0) return amountToWithdraw; - //@todo: Do we do this frequently enough that it is a performance hit? - AudioEventRTS event = TheAudio->getMiscAudio()->m_moneyWithdrawSound; - event.setPlayerIndex(m_playerIndex); - // Play a sound if (playSound) + { + //@todo: Do we do this frequently enough that it is a performance hit? + AudioEventRTS event = TheAudio->getMiscAudio()->m_moneyWithdrawSound; + event.setPlayerIndex(m_playerIndex); TheAudio->addAudioEvent(&event); + } m_money -= amountToWithdraw; @@ -78,13 +79,14 @@ void Money::deposit(UnsignedInt amountToDeposit, Bool playSound) if (amountToDeposit == 0) return; - //@todo: Do we do this frequently enough that it is a performance hit? - AudioEventRTS event = TheAudio->getMiscAudio()->m_moneyDepositSound; - event.setPlayerIndex(m_playerIndex); - // Play a sound if (playSound) + { + //@todo: Do we do this frequently enough that it is a performance hit? + AudioEventRTS event = TheAudio->getMiscAudio()->m_moneyDepositSound; + event.setPlayerIndex(m_playerIndex); TheAudio->addAudioEvent(&event); + } m_money += amountToDeposit; } diff --git a/Generals/Code/GameEngine/Source/Common/RandomValue.cpp b/Generals/Code/GameEngine/Source/Common/RandomValue.cpp index 194ec0cea78..8b2f5dafcce 100644 --- a/Generals/Code/GameEngine/Source/Common/RandomValue.cpp +++ b/Generals/Code/GameEngine/Source/Common/RandomValue.cpp @@ -383,6 +383,7 @@ Real GameClientRandomVariable::getValue( void ) const if (m_low == m_high) { return m_low; } // else return as though a UNIFORM. + FALLTHROUGH; case UNIFORM: return GameClientRandomValueReal( m_low, m_high ); @@ -427,6 +428,7 @@ Real GameLogicRandomVariable::getValue( void ) const if (m_low == m_high) { return m_low; } // else return as though a UNIFORM. + FALLTHROUGH; case UNIFORM: return GameLogicRandomValueReal( m_low, m_high ); diff --git a/Generals/Code/GameEngine/Source/Common/Recorder.cpp b/Generals/Code/GameEngine/Source/Common/Recorder.cpp index dacd872a046..a1fa13adab6 100644 --- a/Generals/Code/GameEngine/Source/Common/Recorder.cpp +++ b/Generals/Code/GameEngine/Source/Common/Recorder.cpp @@ -67,8 +67,8 @@ typedef int32_t replay_time_t; static time_t startTime; static const UnsignedInt startTimeOffset = 6; static const UnsignedInt endTimeOffset = startTimeOffset + sizeof(replay_time_t); -static const UnsignedInt framesOffset = endTimeOffset + sizeof(replay_time_t); -static const UnsignedInt desyncOffset = framesOffset + sizeof(UnsignedInt); +static const UnsignedInt frameCountOffset = endTimeOffset + sizeof(replay_time_t); +static const UnsignedInt desyncOffset = frameCountOffset + sizeof(UnsignedInt); static const UnsignedInt quitEarlyOffset = desyncOffset + sizeof(Bool); static const UnsignedInt disconOffset = quitEarlyOffset + sizeof(Bool); @@ -232,7 +232,7 @@ void RecorderClass::logGameEnd( void ) time_t t; time(&t); - UnsignedInt duration = TheGameLogic->getFrame(); + UnsignedInt frameCount = TheGameLogic->getFrame(); UnsignedInt fileSize = ftell(m_file); // move to appropriate offset if (!fseek(m_file, endTimeOffset, SEEK_SET)) @@ -242,10 +242,10 @@ void RecorderClass::logGameEnd( void ) fwrite(&tmp, sizeof(replay_time_t), 1, m_file); } // move to appropriate offset - if (!fseek(m_file, framesOffset, SEEK_SET)) + if (!fseek(m_file, frameCountOffset, SEEK_SET)) { - // save off duration - fwrite(&duration, sizeof(UnsignedInt), 1, m_file); + // save off frameCount + fwrite(&frameCount, sizeof(UnsignedInt), 1, m_file); } // move back to end of stream #ifdef DEBUG_CRASHING @@ -272,7 +272,7 @@ void RecorderClass::logGameEnd( void ) if (logFP) { struct tm *t2 = localtime(&t); - duration = t - startTime; + time_t duration = t - startTime; Int minutes = duration/60; Int seconds = duration%60; fprintf(logFP, "Game end at %s(%d:%2.2d elapsed time)\n", asctime(t2), minutes, seconds); @@ -408,6 +408,7 @@ void RecorderClass::init() { m_gameInfo.setSeed(GetGameLogicRandomSeed()); m_wasDesync = FALSE; m_doingAnalysis = FALSE; + m_playbackFrameCount = 0; } /** @@ -430,7 +431,7 @@ void RecorderClass::reset() { void RecorderClass::update() { if (m_mode == RECORDERMODETYPE_RECORD || m_mode == RECORDERMODETYPE_NONE) { updateRecord(); - } else if (m_mode == RECORDERMODETYPE_PLAYBACK) { + } else if (isPlaybackMode()) { updatePlayback(); } } @@ -476,11 +477,11 @@ void RecorderClass::stopPlayback() { m_file = NULL; } m_fileName.clear(); - // Don't clear the game data if the replay is over - let things continue -//#ifdef DEBUG_CRC + if (!m_doingAnalysis) + { TheMessageStream->appendMessage(GameMessage::MSG_CLEAR_GAME_DATA); -//#endif + } } /** @@ -852,7 +853,7 @@ Bool RecorderClass::readReplayHeader(ReplayHeader& header) fread(&tmp, sizeof(replay_time_t), 1, m_file); header.endTime = tmp; - fread(&header.frameDuration, sizeof(UnsignedInt), 1, m_file); + fread(&header.frameCount, sizeof(UnsignedInt), 1, m_file); fread(&header.desyncGame, sizeof(Bool), 1, m_file); fread(&header.quitEarly, sizeof(Bool), 1, m_file); @@ -915,6 +916,14 @@ Bool RecorderClass::readReplayHeader(ReplayHeader& header) return TRUE; } +Bool RecorderClass::simulateReplay(AsciiString filename) +{ + Bool success = playbackFile(filename); + if (success) + m_mode = RECORDERMODETYPE_SIMULATION_PLAYBACK; + return success; +} + #if defined RTS_DEBUG || defined RTS_INTERNAL Bool RecorderClass::analyzeReplay( AsciiString filename ) { @@ -922,15 +931,18 @@ Bool RecorderClass::analyzeReplay( AsciiString filename ) return playbackFile(filename); } -Bool RecorderClass::isAnalysisInProgress( void ) + + +#endif + +Bool RecorderClass::isPlaybackInProgress( void ) const { - return m_mode == RECORDERMODETYPE_PLAYBACK && m_nextFrame != -1; + return isPlaybackMode() && m_nextFrame != -1; } -#endif AsciiString RecorderClass::getCurrentReplayFilename( void ) { - if (m_mode == RECORDERMODETYPE_PLAYBACK) + if (isPlaybackMode()) { return m_currentReplayFilename; } @@ -964,7 +976,7 @@ class CRCInfo UnsignedInt getLocalPlayer(void) { return m_localPlayer; } void setSawCRCMismatch(void) { m_sawCRCMismatch = TRUE; } - Bool sawCRCMismatch(void) { return m_sawCRCMismatch; } + Bool sawCRCMismatch(void) const { return m_sawCRCMismatch; } protected: @@ -1012,6 +1024,11 @@ UnsignedInt CRCInfo::readCRC(void) return val; } +Bool RecorderClass::sawCRCMismatch() const +{ + return m_crcInfo->sawCRCMismatch(); +} + void RecorderClass::handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool fromPlayback) { if (fromPlayback) @@ -1058,6 +1075,9 @@ void RecorderClass::handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool f DEBUG_LOG(("Replay has gone out of sync!\nInGame:%8.8X Replay:%8.8X\nFrame:%d\n", playbackCRC, newCRC, mismatchFrame)); + // Print Mismatch in case we are simulating replays from console. + printf("CRC Mismatch in Frame %d\n", mismatchFrame); + // TheSuperHackers @tweak Pause the game on mismatch. Bool pause = TRUE; Bool pauseMusic = FALSE; @@ -1212,17 +1232,23 @@ Bool RecorderClass::playbackFile(AsciiString filename) // send a message to the logic for a new game if (!m_doingAnalysis) { - GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_NEW_GAME ); + // TheSuperHackers @info helmutbuhler 13/04/2025 + // We send the New Game message here directly to the command list and bypass the TheMessageStream. + // That's ok because Multiplayer is disabled during replay playback and is actually required + // during replay simulation because we don't update TheMessageStream during simulation. + GameMessage *msg = newInstance(GameMessage)(GameMessage::MSG_NEW_GAME); msg->appendIntegerArgument(GAME_REPLAY); msg->appendIntegerArgument(difficulty); msg->appendIntegerArgument(rankPoints); if( maxFPS != 0 ) msg->appendIntegerArgument(maxFPS); + TheCommandList->appendMessage( msg ); //InitGameLogicRandom( m_gameInfo.getSeed()); InitRandom( m_gameInfo.getSeed() ); } m_currentReplayFilename = filename; + m_playbackFrameCount = header.frameCount; return TRUE; } @@ -1660,7 +1686,7 @@ void RecorderClass::initControls() Bool RecorderClass::isMultiplayer( void ) { - if (m_mode == RECORDERMODETYPE_PLAYBACK) + if (isPlaybackMode()) { GameSlot *slot; for (int i=0; ifindUpgrade( *it ); - if( !theTemplate && !it->isEmpty() && !it->isNone()) + if( !theTemplate ) { - DEBUG_CRASH(("An upgrade module references %s, which is not an Upgrade", it->str())); + DEBUG_CRASH(("An upgrade module references '%s', which is not an Upgrade", it->str())); throw INI_INVALID_DATA; } @@ -275,11 +275,12 @@ void UpgradeMuxData::getUpgradeActivationMasks(UpgradeMaskType& activation, Upgr it++) { const UpgradeTemplate* theTemplate = TheUpgradeCenter->findUpgrade( *it ); - if( !theTemplate && !it->isEmpty() && !it->isNone()) + if( !theTemplate ) { - DEBUG_CRASH(("An upgrade module references %s, which is not an Upgrade", it->str())); + DEBUG_CRASH(("An upgrade module references '%s', which is not an Upgrade", it->str())); throw INI_INVALID_DATA; } + m_conflictingMask.set( theTemplate->getUpgradeMask() ); } diff --git a/Generals/Code/GameEngine/Source/GameClient/ClientInstance.cpp b/Generals/Code/GameEngine/Source/GameClient/ClientInstance.cpp index 2ac1ff33988..417bcbd323a 100644 --- a/Generals/Code/GameEngine/Source/GameClient/ClientInstance.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/ClientInstance.cpp @@ -25,6 +25,12 @@ namespace rts HANDLE ClientInstance::s_mutexHandle = NULL; UnsignedInt ClientInstance::s_instanceIndex = 0; +#if defined(RTS_MULTI_INSTANCE) +Bool ClientInstance::s_isMultiInstance = true; +#else +Bool ClientInstance::s_isMultiInstance = false; +#endif + bool ClientInstance::initialize() { if (isInitialized()) @@ -36,39 +42,42 @@ bool ClientInstance::initialize() // WARNING: DO NOT use this number for any other application except Generals. while (true) { -#if defined(RTS_MULTI_INSTANCE) - std::string guidStr = getFirstInstanceName(); - if (s_instanceIndex > 0u) - { - char idStr[33]; - itoa(s_instanceIndex, idStr, 10); - guidStr.push_back('-'); - guidStr.append(idStr); - } - s_mutexHandle = CreateMutex(NULL, FALSE, guidStr.c_str()); - if (GetLastError() == ERROR_ALREADY_EXISTS) + if (isMultiInstance()) { - if (s_mutexHandle != NULL) + std::string guidStr = getFirstInstanceName(); + if (s_instanceIndex > 0u) { - CloseHandle(s_mutexHandle); - s_mutexHandle = NULL; + char idStr[33]; + itoa(s_instanceIndex, idStr, 10); + guidStr.push_back('-'); + guidStr.append(idStr); + } + s_mutexHandle = CreateMutex(NULL, FALSE, guidStr.c_str()); + if (GetLastError() == ERROR_ALREADY_EXISTS) + { + if (s_mutexHandle != NULL) + { + CloseHandle(s_mutexHandle); + s_mutexHandle = NULL; + } + // Try again with a new instance. + ++s_instanceIndex; + continue; } - // Try again with a new instance. - ++s_instanceIndex; - continue; } -#else - s_mutexHandle = CreateMutex(NULL, FALSE, getFirstInstanceName()); - if (GetLastError() == ERROR_ALREADY_EXISTS) + else { - if (s_mutexHandle != NULL) + s_mutexHandle = CreateMutex(NULL, FALSE, getFirstInstanceName()); + if (GetLastError() == ERROR_ALREADY_EXISTS) { - CloseHandle(s_mutexHandle); - s_mutexHandle = NULL; + if (s_mutexHandle != NULL) + { + CloseHandle(s_mutexHandle); + s_mutexHandle = NULL; + } + return false; } - return false; } -#endif break; } @@ -80,6 +89,31 @@ bool ClientInstance::isInitialized() return s_mutexHandle != NULL; } +bool ClientInstance::isMultiInstance() +{ + return s_isMultiInstance; +} + +void ClientInstance::setMultiInstance(bool v) +{ + if (isInitialized()) + { + DEBUG_CRASH(("ClientInstance::setMultiInstance(%d) - cannot set multi instance after initialization", (int)v)); + return; + } + s_isMultiInstance = v; +} + +void ClientInstance::skipPrimaryInstance() +{ + if (isInitialized()) + { + DEBUG_CRASH(("ClientInstance::skipPrimaryInstance() - cannot skip primary instance after initialization")); + return; + } + s_instanceIndex = 1; +} + UnsignedInt ClientInstance::getInstanceIndex() { DEBUG_ASSERTLOG(isInitialized(), ("ClientInstance::isInitialized() failed")); diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarScheme.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarScheme.cpp index 423ea8559a7..0ddac645277 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarScheme.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarScheme.cpp @@ -991,7 +991,7 @@ void ControlBarSchemeManager::preloadAssets( TimeOfDay timeOfDay ) if (CBScheme->m_rightHUDImage) { - TheDisplay->preloadTextureAssets(CBScheme->m_buttonQueueImage->getFilename()); + TheDisplay->preloadTextureAssets(CBScheme->m_rightHUDImage->getFilename()); } for (Int layer = 0; layer < MAX_CONTROL_BAR_SCHEME_IMAGE_LAYERS; ++layer) diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp index 4bd172b22d9..c80a6595162 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp @@ -1578,21 +1578,27 @@ void OptionsMenuInit( WindowLayout *layout, void *userData ) // populate resolution modes GadgetComboBoxReset(comboBoxResolution); Int numResolutions = TheDisplay->getDisplayModeCount(); + UnsignedInt displayWidth = TheDisplay->getWidth(); + UnsignedInt displayHeight = TheDisplay->getHeight(); + for( i = 0; i < numResolutions; ++i ) { Int xres,yres,bitDepth; TheDisplay->getDisplayModeDescription(i,&xres,&yres,&bitDepth); str.format(L"%d x %d",xres,yres); GadgetComboBoxAddEntry( comboBoxResolution, str, color); - if (xres == selectedXRes && yres == selectedYRes) + // TheSuperHackers @bugfix xezon 12/06/2025 Now makes a selection with the active display resolution + // instead of the resolution read from the Option Preferences, because the active display resolution + // is the most relevant to make a selection with and the Option Preferences could be wrong. + if ( xres == displayWidth && yres == displayHeight ) selectedResIndex=i; } - if (selectedResIndex == -1) //check if saved mode no longer available + if (selectedResIndex == -1) { // TheSuperHackers @bugfix xezon 08/06/2025 Now adds the current resolution instead of defaulting to 800 x 600. // This avoids force changing the resolution when the user has set a custom resolution in the Option Preferences. - Int xres = TheDisplay->getWidth(); - Int yres = TheDisplay->getHeight(); + Int xres = displayWidth; + Int yres = displayHeight; str.format(L"%d x %d",xres,yres); GadgetComboBoxAddEntry( comboBoxResolution, str, color ); selectedResIndex = GadgetComboBoxGetLength( comboBoxResolution ) - 1; diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupSaveLoad.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupSaveLoad.cpp index 1f93cecb7a0..ddad0deef71 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupSaveLoad.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupSaveLoad.cpp @@ -772,8 +772,7 @@ WindowMsgHandledType SaveLoadMenuSystem( GameWindow *window, UnsignedInt msg, // save the game AsciiString filename; - if( selectedGameInfo ) - filename = selectedGameInfo->filename; + filename = selectedGameInfo->filename; TheGameState->saveGame( filename, selectedGameInfo->saveGameInfo.description, fileType ); /* diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ReplayMenu.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ReplayMenu.cpp index 93fbffe9039..21bad58720a 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ReplayMenu.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ReplayMenu.cpp @@ -248,7 +248,7 @@ void PopulateReplayFileListbox(GameWindow *listbox) // time_t totalSeconds = header.endTime - header.startTime; // Int mins = totalSeconds/60; // Int secs = totalSeconds%60; -// Real fps = header.frameDuration/totalSeconds; +// Real fps = header.frameCount/totalSeconds; // extraStr.format(L"%d:%d (%g fps) %hs", mins, secs, fps, header.desyncGame?"OOS ":""); // // for (Int i=0; iupdate(); - } while (TheRecorder->isAnalysisInProgress()); + } while (TheRecorder->isPlaybackInProgress()); } } } diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp index 0f9492d7515..e3ee2daf761 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp @@ -64,6 +64,7 @@ #include "Common/PlayerTemplate.h" #include "Common/RandomValue.h" #include "Common/Recorder.h" +#include "Common/ReplaySimulation.h" #include "Common/ScoreKeeper.h" #include "Common/SkirmishBattleHonors.h" #include "Common/ThingFactory.h" @@ -411,6 +412,11 @@ WindowMsgHandledType ScoreScreenInput( GameWindow *window, UnsignedInt msg, } // end MainMenuInput +static Bool showButtonContinue() +{ + return ReplaySimulation::getCurrentReplayIndex() != ReplaySimulation::getReplayCount()-1; +} + /** System Function for the ScoreScreen */ //------------------------------------------------------------------------------------------------- WindowMsgHandledType ScoreScreenSystem( GameWindow *window, UnsignedInt msg, @@ -449,23 +455,36 @@ WindowMsgHandledType ScoreScreenSystem( GameWindow *window, UnsignedInt msg, { TheShell->pop(); TheCampaignManager->setCampaign(AsciiString::TheEmptyString); + + if ( ReplaySimulation::getReplayCount() > 0 ) + { + ReplaySimulation::stop(); + TheGameEngine->setQuitting(TRUE); + } } else if ( controlID == buttonContinueID ) { - if(!buttonIsFinishCampaign) - ReplayWasPressed = TRUE; - if( screenType == SCORESCREEN_SINGLEPLAYER) + if( ReplaySimulation::getReplayCount() > 0 ) { - AsciiString mapName = TheCampaignManager->getCurrentMap(); - - if( mapName.isEmpty() ) - { - ReplayWasPressed = FALSE; - TheShell->pop(); - } - else + TheGameEngine->setQuitting(TRUE); + } + else + { + if(!buttonIsFinishCampaign) + ReplayWasPressed = TRUE; + if( screenType == SCORESCREEN_SINGLEPLAYER) { - CheckForCDAtGameStart( startNextCampaignGame ); + AsciiString mapName = TheCampaignManager->getCurrentMap(); + + if( mapName.isEmpty() ) + { + ReplayWasPressed = FALSE; + TheShell->pop(); + } + else + { + CheckForCDAtGameStart( startNextCampaignGame ); + } } } } @@ -800,7 +819,7 @@ void initReplaySinglePlayer( void ) if (chatBoxBorder) chatBoxBorder->winHide(TRUE); if (buttonContinue) - buttonContinue->winHide(TRUE); + buttonContinue->winHide(!showButtonContinue()); if (buttonBuddies) buttonBuddies->winHide(TRUE); if (listboxChatWindowScoreScreen) @@ -886,7 +905,7 @@ void initReplayMultiPlayer(void) if (chatBoxBorder) chatBoxBorder->winHide(TRUE); if (buttonContinue) - buttonContinue->winHide(TRUE); + buttonContinue->winHide(!showButtonContinue()); if (buttonBuddies) buttonBuddies->winHide(TRUE); // if (buttonRehost) @@ -1159,7 +1178,7 @@ static void updateChallengeMedals(Int& medals) //------------------------------------------------------------------------------------------------- void populatePlayerInfo( Player *player, Int pos) { - if(!player || pos > MAX_SLOTS) + if(!player || pos < 0 || pos >= MAX_SLOTS) return; Color color = player->getPlayerColor(); ScoreKeeper *scoreKpr = player->getScoreKeeper(); @@ -1877,20 +1896,23 @@ void grabSinglePlayerInfo( void ) { Bool isFriend = TRUE; - // set the string we'll be compairing to + // set the string we'll be comparing to switch (j) { case USA_ENEMY: isFriend = FALSE; + FALLTHROUGH; case USA_FRIEND: side.set("America"); break; case CHINA_ENEMY: isFriend = FALSE; + FALLTHROUGH; case CHINA_FRIEND: side.set("China"); break; case GLA_ENEMY: - isFriend = FALSE; + isFriend = FALSE; + FALLTHROUGH; case GLA_FRIEND: side.set("GLA"); break; @@ -2049,7 +2071,7 @@ winName.format("ScoreScreen.wnd:StaticTextScore%d", i); //------------------------------------------------------------------------------------------------- void setObserverWindows( Player *player, Int i ) { - if(i < 0 || i >= MAX_SLOTS) + if(!player || i < 0 || i >= MAX_SLOTS) return; AsciiString winName; GameWindow *win; @@ -2060,16 +2082,10 @@ void setObserverWindows( Player *player, Int i ) winName.format("ScoreScreen.wnd:StaticTextPlayer%d", i); win = TheWindowManager->winGetWindowFromId( parent, TheNameKeyGenerator->nameToKey( winName ) ); DEBUG_ASSERTCRASH(win,("Could not find window %s on the score screen", winName.str())); - if (player) - { - GadgetStaticTextSetText(win, player->getPlayerDisplayName()); - win->winHide(FALSE); - win->winSetEnabledTextColors(color, win->winGetEnabledTextBorderColor()); - } - else - { - win->winHide(TRUE); - } + + GadgetStaticTextSetText(win, player->getPlayerDisplayName()); + win->winHide(FALSE); + win->winSetEnabledTextColors(color, win->winGetEnabledTextBorderColor()); // set the player name winName.format("ScoreScreen.wnd:StaticTextObserver%d", i); diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp index 2a4c2f83ddc..680a9e9abc5 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp @@ -568,14 +568,18 @@ void PopulateLobbyPlayerListbox(void) // restore selection if (indicesToSelect.size()) { - std::set::const_iterator indexIt; - Int *newIndices = NEW Int[indicesToSelect.size()]; - for (i=0, indexIt = indicesToSelect.begin(); indexIt != indicesToSelect.end(); ++i, ++indexIt) + std::set::const_iterator indexIt = indicesToSelect.begin(); + const size_t count = indicesToSelect.size(); + size_t index = 0; + Int *newIndices = NEW Int[count]; + while (index < count) { - newIndices[i] = *indexIt; + newIndices[index] = *indexIt; DEBUG_LOG(("Queueing up index %d to re-select\n", *indexIt)); + ++index; + ++indexIt; } - GadgetListBoxSetSelected(listboxLobbyPlayers, newIndices, indicesToSelect.size()); + GadgetListBoxSetSelected(listboxLobbyPlayers, newIndices, count); delete[] newIndices; } diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp index 420b5b87db8..2bb9320edd7 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp @@ -1621,7 +1621,7 @@ static GameWindow *createGadget( char *type, WinInstanceData *instData, void *data ) { - GameWindow *window; + GameWindow *window = NULL; instData->m_owner = parent; diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GameWindowTransitionsStyles.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GameWindowTransitionsStyles.cpp index c968b80232f..b95f23b4929 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/GameWindowTransitionsStyles.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GameWindowTransitionsStyles.cpp @@ -142,6 +142,8 @@ void FlashTransition::update( Int frame ) } // end if } + FALLTHROUGH; + case FLASHTRANSITION_FADE_IN_2: case FLASHTRANSITION_FADE_IN_3: { @@ -810,8 +812,8 @@ void ScaleUpTransition::update( Int frame ) TheAudio->addAudioEvent( &buttonClick ); } // end if - } + FALLTHROUGH; case SCALEUPTRANSITION_2: case SCALEUPTRANSITION_3: @@ -933,8 +935,8 @@ void ScoreScaleUpTransition::update( Int frame ) TheAudio->addAudioEvent( &buttonClick ); } // end if - } + FALLTHROUGH; case SCORESCALEUPTRANSITION_2: case SCORESCALEUPTRANSITION_3: diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/LoadScreen.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/LoadScreen.cpp index a2de250a1e7..149a8e37a08 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/LoadScreen.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/LoadScreen.cpp @@ -886,6 +886,7 @@ void MultiPlayerLoadScreen::processProgress(Int playerId, Int percentage) if( percentage < 0 || percentage > 100 || playerId >= MAX_SLOTS || playerId < 0 || m_playerLookup[playerId] == -1) { DEBUG_ASSERTCRASH(FALSE, ("Percentage %d was passed in for Player %d\n", percentage, playerId)); + return; } //DEBUG_LOG(("Percentage %d was passed in for Player %d (in loadscreen position %d)\n", percentage, playerId, m_playerLookup[playerId])); if(m_progressBars[m_playerLookup[playerId]]) @@ -1213,6 +1214,7 @@ void GameSpyLoadScreen::processProgress(Int playerId, Int percentage) if( percentage < 0 || percentage > 100 || playerId >= MAX_SLOTS || playerId < 0 || m_playerLookup[playerId] == -1) { DEBUG_ASSERTCRASH(FALSE, ("Percentage %d was passed in for Player %d\n", percentage, playerId)); + return; } //DEBUG_LOG(("Percentage %d was passed in for Player %d (in loadscreen position %d)\n", percentage, playerId, m_playerLookup[playerId])); if(m_progressBars[m_playerLookup[playerId]]) @@ -1365,6 +1367,7 @@ void MapTransferLoadScreen::processProgress(Int playerId, Int percentage, AsciiS if( percentage < 0 || percentage > 100 || playerId >= MAX_SLOTS || playerId < 0 || m_playerLookup[playerId] == -1) { DEBUG_ASSERTCRASH(FALSE, ("Percentage %d was passed in for Player %d\n", percentage, playerId)); + return; } if (m_oldProgress[playerId] == percentage) diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp index 2633a328c8b..164dbc47d9b 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp @@ -408,7 +408,7 @@ void Shell::showShell( Bool runInit ) { DEBUG_LOG(("Shell:showShell() - %s (%s)\n", TheGlobalData->m_initialFile.str(), (top())?top()->getFilename().str():"no top screen")); - if(!TheGlobalData->m_initialFile.isEmpty()) + if(!TheGlobalData->m_initialFile.isEmpty() || !TheGlobalData->m_simulateReplays.empty()) { return; } @@ -465,7 +465,7 @@ void Shell::showShell( Bool runInit ) void Shell::showShellMap(Bool useShellMap ) { // we don't want any of this to show if we're loading straight into a file - if(TheGlobalData->m_initialFile.isNotEmpty() || !TheGameLogic ) + if (TheGlobalData->m_initialFile.isNotEmpty() || !TheGameLogic || !TheGlobalData->m_simulateReplays.empty()) return; if(useShellMap && TheGlobalData->m_shellMapOn) { diff --git a/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp b/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp index 8964e037870..07ac79f01f5 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -517,7 +517,7 @@ void GameClient::update( void ) //Initial Game Codition. We must show the movie first and then we can display the shell if(TheGlobalData->m_afterIntro && !TheDisplay->isMoviePlaying()) { - if( playSizzle && TheGlobalData->m_playSizzle && !TheGlobalData->m_headless )// Remove headless-check with Replay Simulation PR) + if( playSizzle && TheGlobalData->m_playSizzle ) { TheWritableGlobalData->m_allowExitOutOfMovies = TRUE; if(TheGameLODManager && TheGameLODManager->didMemPass()) @@ -591,11 +591,8 @@ void GameClient::update( void ) if(TheGlobalData->m_playIntro || TheGlobalData->m_afterIntro) { // redraw all views, update the GUI - if (!TheGlobalData->m_headless)// Remove headless-check with Replay Simulation PR - { - TheDisplay->DRAW(); - TheDisplay->UPDATE(); - } + TheDisplay->DRAW(); + TheDisplay->UPDATE(); return; } @@ -713,12 +710,10 @@ void GameClient::update( void ) } // update display - if (!TheGlobalData->m_headless)// Remove headless-check with Replay Simulation PR { TheDisplay->UPDATE(); } - if (!TheGlobalData->m_headless)// Remove headless-check with Replay Simulation PR { USE_PERF_TIMER(GameClient_draw) @@ -744,6 +739,18 @@ void GameClient::update( void ) } } // end update +void GameClient::updateHeadless() +{ + // TheSuperHackers @info helmutbuhler 03/05/2025 + // When we play a replay back in headless mode, we want to skip the update of GameClient + // because it's not necessary for CRC checking. + // But we do reset the particles. The problem is that particles can be generated during + // GameLogic and are only cleaned up during rendering. If we don't clean this up here, + // the particles accumulate and slow things down a lot and can even cause a crash on + // longer replays. + TheParticleSystemManager->reset(); +} + /** ----------------------------------------------------------------------------------------------- * Call the given callback function for each object contained within the given region. */ diff --git a/Generals/Code/GameEngine/Source/GameClient/GameText.cpp b/Generals/Code/GameEngine/Source/GameClient/GameText.cpp index 6a8f1f485b3..f720c480dc2 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GameText.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GameText.cpp @@ -630,6 +630,7 @@ void GameTextManager::readToEndOfQuote( File *file, Char *in, Char *out, Char *w } state = 1; + FALLTHROUGH; case 1: if ( ( ch >= 'a' && ch <= 'z') || ( ch >= 'A' && ch <='Z') || (ch >= '0' && ch <= '9') || ch == '_' ) { @@ -638,6 +639,7 @@ void GameTextManager::readToEndOfQuote( File *file, Char *in, Char *out, Char *w break; } state = 2; + FALLTHROUGH; case 2: break; } diff --git a/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp b/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index 834751d52e1..0710af5eab8 100644 --- a/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp @@ -2071,7 +2071,7 @@ GameMessage::Type CommandTranslator::evaluateContextCommand( Drawable *draw, } // end else if #endif // ******************************************************************************************** - else if ( pos && !draw && TheInGameUI->canSelectedObjectsDoAction( InGameUI::ACTIONTYPE_SET_RALLY_POINT, NULL, InGameUI::SELECTION_ALL, FALSE )) + else if ( !draw && TheInGameUI->canSelectedObjectsDoAction( InGameUI::ACTIONTYPE_SET_RALLY_POINT, NULL, InGameUI::SELECTION_ALL, FALSE )) { msgType = GameMessage::MSG_SET_RALLY_POINT; diff --git a/Generals/Code/GameEngine/Source/GameClient/MessageStream/PlaceEventTranslator.cpp b/Generals/Code/GameEngine/Source/GameClient/MessageStream/PlaceEventTranslator.cpp index 7ae460e94b7..4c1192f24f9 100644 --- a/Generals/Code/GameEngine/Source/GameClient/MessageStream/PlaceEventTranslator.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/MessageStream/PlaceEventTranslator.cpp @@ -170,11 +170,7 @@ GameMessageDisposition PlaceEventTranslator::translateGameMessage(const GameMess // translate the screen position of start to world target location TheTacticalView->screenToTerrain( &anchorStart, &world ); - // get the source object ID of the thing that is "building" the object - ObjectID builderID = INVALID_ID; Object *builderObj = TheGameLogic->findObjectByID( TheInGameUI->getPendingPlaceSourceObjectID() ); - if( builderObj ) - builderID = builderObj->getID(); //Kris: September 27, 2002 //Make sure we have enough CASH to build it! It's possible that between the @@ -209,6 +205,8 @@ GameMessageDisposition PlaceEventTranslator::translateGameMessage(const GameMess break; } + DEBUG_ASSERTCRASH(builderObj != NULL, ("builderObj is NULL")); + // check to see if this is a legal location to build something at LegalBuildCode lbc; lbc = TheBuildAssistant->isLocationLegalToBuild( &world, diff --git a/Generals/Code/GameEngine/Source/GameClient/MessageStream/WindowXlat.cpp b/Generals/Code/GameEngine/Source/GameClient/MessageStream/WindowXlat.cpp index 0241f4fa5bf..dfb78fa691f 100644 --- a/Generals/Code/GameEngine/Source/GameClient/MessageStream/WindowXlat.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/MessageStream/WindowXlat.cpp @@ -204,7 +204,7 @@ GameMessageDisposition WindowTranslator::translateGameMessage(const GameMessage //If we release the button outside forceKeepMessage = TRUE; } - //FALL THROUGH INTENTIONALLY! + FALLTHROUGH; //FALL THROUGH INTENTIONALLY! } case GameMessage::MSG_RAW_MOUSE_POSITION: case GameMessage::MSG_RAW_MOUSE_LEFT_BUTTON_DOWN: diff --git a/Generals/Code/GameEngine/Source/GameLogic/AI/AI.cpp b/Generals/Code/GameEngine/Source/GameLogic/AI/AI.cpp index 987fe75ea11..0ec8efe442b 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/AI/AI.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/AI/AI.cpp @@ -427,11 +427,10 @@ void AI::parseAiDataDefinition( INI* ini ) if( ini->getLoadType() == INI_LOAD_CREATE_OVERRIDES ) TheAI->newOverride(); - } // end if - - // parse the ini weapon definition - ini->initFromINI( TheAI->m_aiData, TheAIFieldParseTable ); + // parse the ini weapon definition + ini->initFromINI( TheAI->m_aiData, TheAIFieldParseTable ); + } // end if } diff --git a/Generals/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp b/Generals/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp index d3563f3cfc1..f540d5c0eb7 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp @@ -1972,7 +1972,7 @@ void AIPlayer::repairStructure(ObjectID structure) return; } } - if (m_structuresInQueue==MAX_STRUCTURES_TO_REPAIR) { + if (m_structuresInQueue>=MAX_STRUCTURES_TO_REPAIR) { DEBUG_LOG(("Structure repair queue is full, ignoring repair request. JBA\n")); return; } diff --git a/Generals/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp b/Generals/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp index e2092633986..2841377d9e2 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp @@ -95,6 +95,10 @@ void PolygonTrigger::reallocate(void) { DEBUG_ASSERTCRASH(m_numPoints <= m_sizePoints, ("Invalid m_numPoints.")); if (m_numPoints == m_sizePoints) { + if (m_sizePoints > INT_MAX / 2) { + DEBUG_CRASH(("Too many points to allocate.")); + return; + } // Reallocate. m_sizePoints += m_sizePoints; ICoord3D *newPts = NEW ICoord3D[m_sizePoints]; diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeScaffoldBehavior.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeScaffoldBehavior.cpp index 25b9a985d5a..bc705ce7001 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeScaffoldBehavior.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeScaffoldBehavior.cpp @@ -192,6 +192,10 @@ UpdateSleepTime BridgeScaffoldBehavior::update( void ) end = &m_riseToPos; break; + default: + DEBUG_CRASH(("Unhandled case in BridgeScaffoldBehavior::update()")); + return UPDATE_SLEEP_NONE; + } // end switch // adjust speed so it's slower at the end of motion diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp index 49945919baa..f9da65a67d0 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp @@ -163,7 +163,7 @@ Bool CrateCollide::isValidToExecute( const Object *other ) const return FALSE; // must match our kindof flags (if any) - if (md && !other->isKindOfMulti(md->m_kindof, md->m_kindofnot)) + if ( !other->isKindOfMulti(md->m_kindof, md->m_kindofnot) ) return FALSE; if( other->isEffectivelyDead() ) diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Damage/TransitionDamageFX.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Damage/TransitionDamageFX.cpp index 7092f77281e..0a88f6b44d5 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Damage/TransitionDamageFX.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Damage/TransitionDamageFX.cpp @@ -363,7 +363,7 @@ void TransitionDamageFX::onBodyDamageStateChange( const DamageInfo* damageInfo, } // end if // do any object creation list for our new state - if( modData->m_OCL[ newState ][ i ].ocl ) + if( damageSource && modData->m_OCL[ newState ][ i ].ocl ) { if( lastDamageInfo == NULL || diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp index 9b98775f05f..dbb33dcc1af 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp @@ -2039,7 +2039,6 @@ void PartitionData::updateCellsTouched() Object *obj = getObject(); - DEBUG_ASSERTCRASH(obj != NULL || m_ghostObject != NULL, ("must be attached to an Object here 1")); if (obj) { @@ -2060,6 +2059,11 @@ void PartitionData::updateCellsTouched() majorRadius = m_ghostObject->getGeometryMajorRadius(); minorRadius = m_ghostObject->getGeometryMinorRadius(); } + else + { + DEBUG_CRASH(("must be attached to an Object here")); + return; + } removeAllTouchedCells(); if (isSmall) @@ -2162,14 +2166,21 @@ Int PartitionData::calcMaxCoiForShape(GeometryType geom, Real majorRadius, Real // this actually allocates a few too many, but that's ok. Int cells = ThePartitionManager->worldToCellDist(majorRadius*2) + 1; result = cells * cells; +#if !RETAIL_COMPATIBLE_CRC + break; +#endif } case GEOMETRY_BOX: { Real diagonal = (Real)(sqrtf(majorRadius*majorRadius + minorRadius*minorRadius)); Int cells = ThePartitionManager->worldToCellDist(diagonal*2) + 1; result = cells * cells; + break; } + default: + return 4; }; + static_assert(GEOMETRY_NUM_TYPES == 3, "GEOMETRY_NUM_TYPES has changed"); } if (result < 4) result = 4; diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/HackInternetAIUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/HackInternetAIUpdate.cpp index d69e1f7a228..db4372c1530 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/HackInternetAIUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/HackInternetAIUpdate.cpp @@ -494,28 +494,28 @@ StateReturnType HackInternetState::update() { break; } - //If entry missing, fall through! + FALLTHROUGH; //If entry missing, fall through! case LEVEL_ELITE: amount = ai->getEliteCashAmount(); if( amount ) { break; } - //If entry missing, fall through! + FALLTHROUGH; //If entry missing, fall through! case LEVEL_VETERAN: amount = ai->getVeteranCashAmount(); if( amount ) { break; } - //If entry missing, fall through! + FALLTHROUGH; //If entry missing, fall through! case LEVEL_REGULAR: amount = ai->getRegularCashAmount(); if( amount ) { break; } - //If entry missing, fall through! + FALLTHROUGH; //If entry missing, fall through! default: amount = 1; break; diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp index 12d0b138d19..9f1764699cf 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp @@ -2216,7 +2216,7 @@ void JetAIUpdate::aiDoCommand(const AICommandParms* parms) if (isParkedAt(parms->m_obj)) return; - // else fall thru to the default case! + FALLTHROUGH; // else fall thru to the default case! default: { diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp index 725840f56db..774913b42b2 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp @@ -652,6 +652,8 @@ UpdateSleepTime MissileAIUpdate::update() { break; } + FALLTHROUGH; + case IGNITION: doIgnitionState(); break; diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/BoneFXUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/BoneFXUpdate.cpp index 693e8cf09bd..b2112f89e86 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/BoneFXUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/BoneFXUpdate.cpp @@ -508,9 +508,6 @@ void BoneFXUpdate::resolveBoneLocations() { Drawable *drawable = building->getDrawable(); if (drawable == NULL) { DEBUG_ASSERTCRASH(drawable != NULL, ("There is no drawable?")); - } - - if (d == NULL) { return; } diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp index 33b7fc8aae6..df2df552a75 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp @@ -180,8 +180,8 @@ void EMPUpdate::doDisableAttack( void ) Real curVictimDistSqr; const Coord3D *pos = object->getPosition(); - SimpleObjectIterator *iter; - Object *curVictim; + SimpleObjectIterator *iter = NULL; + Object *curVictim = NULL; if (radius > 0.0f) { diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp index 6ac17ecd7c6..66feeb1ef21 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp @@ -942,8 +942,10 @@ void PhysicsBehavior::addOverlap(Object *obj) void PhysicsBehavior::transferVelocityTo(PhysicsBehavior* that) const { if (that != NULL) + { that->m_vel.add(&m_vel); - that->m_velMag = INVALID_VEL_MAG; + that->m_velMag = INVALID_VEL_MAG; + } } //------------------------------------------------------------------------------------------------- diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp index 31757c6f4a5..d1ed003577a 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp @@ -567,10 +567,16 @@ Real WeaponTemplate::estimateWeaponTemplateDamage( return 0.0f; } + const Real damageAmount = getPrimaryDamage(bonus); + if ( victimObj == NULL ) + { + return damageAmount; + } + DamageType damageType = getDamageType(); DeathType deathType = getDeathType(); - if (victimObj && victimObj->isKindOf(KINDOF_SHRUBBERY)) + if ( victimObj->isKindOf(KINDOF_SHRUBBERY) ) { if (deathType == DEATH_BURNED) { @@ -584,7 +590,7 @@ Real WeaponTemplate::estimateWeaponTemplateDamage( } // this stays, even if ALLOW_SURRENDER is not defed, since flashbangs still use 'em - if (damageType == DAMAGE_SURRENDER || m_allowAttackGarrisonedBldgs) + if ( damageType == DAMAGE_SURRENDER || m_allowAttackGarrisonedBldgs ) { ContainModuleInterface* contain = victimObj->getContain(); if( contain && contain->getContainCount() > 0 && contain->isGarrisonable() && !contain->isImmuneToClearBuildingAttacks() ) @@ -594,42 +600,25 @@ Real WeaponTemplate::estimateWeaponTemplateDamage( } } - if( victimObj ) + if( victimObj->isKindOf(KINDOF_MINE) && damageType == DAMAGE_DISARM ) { - if( victimObj->isKindOf(KINDOF_MINE) && damageType == DAMAGE_DISARM ) - { - // this is just a nonzero value, to ensure we can target mines with disarm weapons, regardless... - return 1.0f; - } - if( damageType == DAMAGE_DEPLOY && !victimObj->isAirborneTarget() ) - { - return 1.0f; - } + // this is just a nonzero value, to ensure we can target mines with disarm weapons, regardless... + return 1.0f; + } + if( damageType == DAMAGE_DEPLOY && !victimObj->isAirborneTarget() ) + { + return 1.0f; } //@todo Kris need to examine the DAMAGE_HACK type for damage estimation purposes. //Likely this damage type will have threat implications that won't properly be dealt with until resolved. -// const Coord3D* sourcePos = sourceObj->getPosition(); - if (victimPos == NULL) - { - victimPos = victimObj->getPosition(); - } - - Real damageAmount = getPrimaryDamage(bonus); - if (victimObj == NULL) - { - return damageAmount; - } - else - { - DamageInfoInput damageInfo; - damageInfo.m_damageType = damageType; - damageInfo.m_deathType = deathType; - damageInfo.m_sourceID = sourceObj->getID(); - damageInfo.m_amount = damageAmount; - return victimObj->estimateDamage(damageInfo); - } + DamageInfoInput damageInfo; + damageInfo.m_damageType = damageType; + damageInfo.m_deathType = deathType; + damageInfo.m_sourceID = sourceObj->getID(); + damageInfo.m_amount = damageAmount; + return victimObj->estimateDamage(damageInfo); } //------------------------------------------------------------------------------------------------- diff --git a/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp b/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp index e07e6bc95b9..ffc82e81363 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp @@ -2551,6 +2551,7 @@ void ScriptActions::doCameoFlash(const AsciiString& name, Int timeInSeconds) if( button == NULL ) { DEBUG_CRASH(( "ScriptActions::doCameoFlash can't find AsciiString cameoflash" )); + return; } Int frames = LOGICFRAMES_PER_SECOND * timeInSeconds; diff --git a/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/Scripts.cpp b/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/Scripts.cpp index 0499d5b508d..b5ca0f05743 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/Scripts.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/Scripts.cpp @@ -1776,7 +1776,7 @@ void Parameter::qualify(const AsciiString& qualifier, if (m_string == THIS_TEAM) { break; } - /// otherwise drop down & qualify. + FALLTHROUGH; /// otherwise drop down & qualify. case SCRIPT: case COUNTER: case FLAG: diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index dcc23721d5b..e90f56366bf 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -287,7 +287,7 @@ void GameLogic::setDefaults( Bool saveGame ) Bool GameLogic::isInSinglePlayerGame( void ) { return (m_gameMode == GAME_SINGLE_PLAYER || - (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_PLAYBACK && TheRecorder->getGameMode() == GAME_SINGLE_PLAYER)); + (TheRecorder && TheRecorder->isPlaybackMode() && TheRecorder->getGameMode() == GAME_SINGLE_PLAYER)); } @@ -794,9 +794,11 @@ static void populateRandomStartPosition( GameInfo *game ) Int i; Int numPlayers = MAX_SLOTS; const MapMetaData *md = TheMapCache->findMap( game->getMap() ); - DEBUG_ASSERTCRASH( md , ("Could not find map %s in the mapcache", game->getMap().str())); if (md) numPlayers = md->m_numPlayers; + else + printf("Could not find map \"%s\"\n", game->getMap().str()); + DEBUG_ASSERTCRASH( md , ("Could not find map %s in the mapcache", game->getMap().str())); // generate a map of start spot distances Real startSpotDistance[MAX_SLOTS][MAX_SLOTS]; @@ -1086,7 +1088,7 @@ void GameLogic::startNewGame( Bool saveGame ) } else { - if (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_PLAYBACK) + if (TheRecorder && TheRecorder->isPlaybackMode()) { TheGameInfo = game = TheRecorder->getGameInfo(); } @@ -1130,7 +1132,7 @@ void GameLogic::startNewGame( Bool saveGame ) //****************************// // Get the m_loadScreen for this kind of game - if(!m_loadScreen) + if(!m_loadScreen && !(TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_SIMULATION_PLAYBACK)) { m_loadScreen = getLoadScreen( saveGame ); if(m_loadScreen && !TheGlobalData->m_headless) @@ -1914,7 +1916,7 @@ void GameLogic::startNewGame( Bool saveGame ) } // if we're in a load game, don't fade yet - if(saveGame == FALSE && TheTransitionHandler != NULL) + if(saveGame == FALSE && TheTransitionHandler != NULL && m_loadScreen) { TheTransitionHandler->setGroup("FadeWholeScreen"); while(!TheTransitionHandler->isFinished()) @@ -3168,20 +3170,21 @@ void GameLogic::update( void ) if (generateForSolo || generateForMP) { m_CRC = getCRC( CRC_RECALC ); - if (isMPGameOrReplay) - { - GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_LOGIC_CRC ); - msg->appendIntegerArgument( m_CRC ); - msg->appendBooleanArgument( (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_PLAYBACK) ); // playback CRC - DEBUG_LOG(("Appended CRC on frame %d: %8.8X\n", m_frame, m_CRC)); - } - else - { - GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_LOGIC_CRC ); - msg->appendIntegerArgument( m_CRC ); - msg->appendBooleanArgument( (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_PLAYBACK) ); // playback CRC - DEBUG_LOG(("Appended Playback CRC on frame %d: %8.8X\n", m_frame, m_CRC)); - } + bool isPlayback = (TheRecorder && TheRecorder->isPlaybackMode()); + + GameMessage *msg = newInstance(GameMessage)(GameMessage::MSG_LOGIC_CRC); + msg->appendIntegerArgument(m_CRC); + msg->appendBooleanArgument(isPlayback); + + // TheSuperHackers @info helmutbuhler 13/04/2025 + // During replay simulation, we bypass TheMessageStream and instead put the CRC message + // directly into TheCommandList because we don't update TheMessageStream during simulation. + GameMessageList *messageList = TheMessageStream; + if (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_SIMULATION_PLAYBACK) + messageList = TheCommandList; + messageList->appendMessage(msg); + + DEBUG_LOG(("Appended %sCRC on frame %d: %8.8X\n", isPlayback ? "Playback " : "", m_frame, m_CRC)); } // collect stats diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index cf835cb3733..eeeefd1b2a0 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -1799,7 +1799,7 @@ void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData ) // -------------------------------------------------------------------------------------------- case GameMessage::MSG_SET_REPLAY_CAMERA: { - if (TheRecorder->getMode() == RECORDERMODETYPE_PLAYBACK && TheGlobalData->m_useCameraInReplay && TheControlBar->getObserverLookAtPlayer() == thisPlayer) + if (TheRecorder->isPlaybackMode() && TheGlobalData->m_useCameraInReplay && TheControlBar->getObserverLookAtPlayer() == thisPlayer) { if (TheTacticalView->isCameraMovementFinished()) { @@ -1936,7 +1936,7 @@ void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData ) //thisPlayer->getPlayerDisplayName().str(), m_frame)); m_cachedCRCs[msg->getPlayerIndex()] = newCRC; // to mask problem: = (oldCRC < newCRC)?newCRC:oldCRC; } - else if (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_PLAYBACK) + else if (TheRecorder && TheRecorder->isPlaybackMode()) { UnsignedInt newCRC = msg->getArgument(0)->integer; //DEBUG_LOG(("Saw CRC of %X from player %d. Our CRC is %X. Arg count is %d\n", @@ -1966,7 +1966,7 @@ void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData ) } // end switch /**/ /// @todo: multiplayer semantics - if (currentlySelectedGroup && TheRecorder->getMode() == RECORDERMODETYPE_PLAYBACK && TheGlobalData->m_useCameraInReplay && TheControlBar->getObserverLookAtPlayer() == thisPlayer /*&& !TheRecorder->isMultiplayer()*/) + if (currentlySelectedGroup && TheRecorder->isPlaybackMode() && TheGlobalData->m_useCameraInReplay && TheControlBar->getObserverLookAtPlayer() == thisPlayer /*&& !TheRecorder->isMultiplayer()*/) { const VecObjectID& selectedObjects = currentlySelectedGroup->getAllIDs(); TheInGameUI->deselectAllDrawables(); diff --git a/Generals/Code/GameEngine/Source/GameNetwork/DisconnectManager.cpp b/Generals/Code/GameEngine/Source/GameNetwork/DisconnectManager.cpp index 6daed7ef83a..5f8bc102726 100644 --- a/Generals/Code/GameEngine/Source/GameNetwork/DisconnectManager.cpp +++ b/Generals/Code/GameEngine/Source/GameNetwork/DisconnectManager.cpp @@ -139,10 +139,13 @@ void DisconnectManager::update(ConnectionManager *conMgr) { //use next ping server static size_t serverIndex = 0; serverIndex++; - if( serverIndex >= TheGameSpyConfig->getPingServers().size() ) + + AsciiStringList pingServers = TheGameSpyConfig->getPingServers(); + + if( serverIndex >= pingServers.size() ) serverIndex = 0; //wrap back to first ping server - std::list::iterator it = TheGameSpyConfig->getPingServers().begin(); + std::list::iterator it = pingServers.begin(); for( size_t i = 0; i < serverIndex; i++ ) it++; diff --git a/Generals/Code/GameEngine/Source/GameNetwork/GUIUtil.cpp b/Generals/Code/GameEngine/Source/GameNetwork/GUIUtil.cpp index f7eb013b315..29ba9158aa6 100644 --- a/Generals/Code/GameEngine/Source/GameNetwork/GUIUtil.cpp +++ b/Generals/Code/GameEngine/Source/GameNetwork/GUIUtil.cpp @@ -339,13 +339,14 @@ void UpdateSlotList( GameInfo *myGame, GameWindow *comboPlayer[], for( int i =0; i < MAX_SLOTS; i++ ) { GameSlot * slot = myGame->getSlot(i); + // if i'm host, enable the controls for AI - if(myGame->amIHost() && slot && slot->isAI()) + if(myGame->amIHost() && slot->isAI()) { EnableAcceptControls(TRUE, myGame, comboPlayer, comboColor, comboPlayerTemplate, comboTeam, buttonAccept, buttonStart, buttonMapStartPosition, i); } - else if (slot && myGame->getLocalSlotNum() == i) + else if (myGame->getLocalSlotNum() == i) { if(slot->isAccepted() && !myGame->amIHost()) { @@ -371,7 +372,7 @@ void UpdateSlotList( GameInfo *myGame, GameWindow *comboPlayer[], EnableAcceptControls(FALSE, myGame, comboPlayer, comboColor, comboPlayerTemplate, comboTeam, buttonAccept, buttonStart, buttonMapStartPosition, i); } - if(slot && slot->isHuman()) + if(slot->isHuman()) { UnicodeString newName = slot->getName(); UnicodeString oldName = GadgetComboBoxGetText(comboPlayer[i]); diff --git a/Generals/Code/GameEngine/Source/GameNetwork/GameInfo.cpp b/Generals/Code/GameEngine/Source/GameNetwork/GameInfo.cpp index 0d564421d8b..903a2957434 100644 --- a/Generals/Code/GameEngine/Source/GameNetwork/GameInfo.cpp +++ b/Generals/Code/GameEngine/Source/GameNetwork/GameInfo.cpp @@ -449,6 +449,7 @@ GameSlot* GameInfo::getSlot( Int slotNum ) if (slotNum < 0 || slotNum >= MAX_SLOTS) return NULL; + DEBUG_ASSERTCRASH( m_slot[slotNum], ("NULL slot pointer") ); return m_slot[slotNum]; } @@ -458,6 +459,7 @@ const GameSlot* GameInfo::getConstSlot( Int slotNum ) const if (slotNum < 0 || slotNum >= MAX_SLOTS) return NULL; + DEBUG_ASSERTCRASH( m_slot[slotNum], ("NULL slot pointer") ); return m_slot[slotNum]; } diff --git a/Generals/Code/GameEngine/Source/GameNetwork/GameSpy/StagingRoomGameInfo.cpp b/Generals/Code/GameEngine/Source/GameNetwork/GameSpy/StagingRoomGameInfo.cpp index 4e8ad212e0c..372c2ac4ff1 100644 --- a/Generals/Code/GameEngine/Source/GameNetwork/GameSpy/StagingRoomGameInfo.cpp +++ b/Generals/Code/GameEngine/Source/GameNetwork/GameSpy/StagingRoomGameInfo.cpp @@ -57,8 +57,8 @@ // GameSpyGameSlot ------------------------------------------- GameSpyGameSlot::GameSpyGameSlot() + : GameSlot() { - GameSlot(); m_gameSpyLogin.clear(); m_gameSpyLocale.clear(); m_profileID = 0; diff --git a/Generals/Code/GameEngine/Source/GameNetwork/IPEnumeration.cpp b/Generals/Code/GameEngine/Source/GameNetwork/IPEnumeration.cpp index e6fab46eff0..72c556e5f15 100644 --- a/Generals/Code/GameEngine/Source/GameNetwork/IPEnumeration.cpp +++ b/Generals/Code/GameEngine/Source/GameNetwork/IPEnumeration.cpp @@ -97,15 +97,16 @@ EnumeratedIP * IPEnumeration::getAddresses( void ) return NULL; } -#if defined(RTS_MULTI_INSTANCE) // TheSuperHackers @feature Add one unique local host IP address for each multi client instance. - const UnsignedInt id = rts::ClientInstance::getInstanceId(); - addNewIP( - 127, - (UnsignedByte)(id >> 16), - (UnsignedByte)(id >> 8), - (UnsignedByte)(id)); -#endif + if (rts::ClientInstance::isMultiInstance()) + { + const UnsignedInt id = rts::ClientInstance::getInstanceId(); + addNewIP( + 127, + (UnsignedByte)(id >> 16), + (UnsignedByte)(id >> 8), + (UnsignedByte)(id)); + } // construct a list of addresses int numAddresses = 0; diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp index c409c8610db..afd5ef7d28d 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp @@ -234,7 +234,12 @@ void W3DTankDraw::updateTreadPositions(Real uvDelta) else if (pTread->m_type == TREAD_RIGHT) //this tread needs to scroll backwards offset_u = pTread->m_materialSettings.customUVOffset.X - uvDelta; - + else + { + DEBUG_CRASH(("Unhandled case in W3DTankDraw::updateTreadPositions")); + offset_u = 0.0f; + } + // ensure coordinates of offset are in [0, 1] range: offset_u = offset_u - WWMath::Floor(offset_u); pTread->m_materialSettings.customUVOffset.Set(offset_u,0); diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp index 6b5df1a2494..95e1fa16e40 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp @@ -414,7 +414,12 @@ void W3DTankTruckDraw::updateTreadPositions(Real uvDelta) else if (pTread->m_type == TREAD_RIGHT) //this tread needs to scroll backwards offset_u = pTread->m_materialSettings.customUVOffset.X - uvDelta; - + else + { + DEBUG_CRASH(("Unhandled case in W3DTankTruckDraw::updateTreadPositions")); + offset_u = 0.0f; + } + // ensure coordinates of offset are in [0, 1] range: offset_u = offset_u - WWMath::Floor(offset_u); pTread->m_materialSettings.customUVOffset.Set(offset_u,0); diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/GUI/W3DGameFont.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/GUI/W3DGameFont.cpp index def7a7bcc67..d45fe882500 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/GUI/W3DGameFont.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/GUI/W3DGameFont.cpp @@ -137,10 +137,11 @@ void W3DFontLibrary::releaseFontData( GameFont *font ) if(((FontCharsClass *)(font->fontData))->AlternateUnicodeFont) ((FontCharsClass *)(font->fontData))->AlternateUnicodeFont->Release_Ref(); ((FontCharsClass *)(font->fontData))->Release_Ref(); + + font->fontData = NULL; } - font->fontData = NULL; - -} // end releaseFont + +} // end releaseFontData // PUBLIC FUNCTIONS /////////////////////////////////////////////////////////// diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp index bbd0b18ae2f..5ac331ddc94 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp @@ -1575,6 +1575,7 @@ Shadow* W3DProjectedShadowManager::addDecal(Shadow::ShadowTypeInfo *shadowInfo) break; case SHADOW_PROJECTION: m_numProjectionShadows++; + break; default: break; } @@ -1711,6 +1712,7 @@ Shadow* W3DProjectedShadowManager::addDecal(RenderObjClass *robj, Shadow::Shadow break; case SHADOW_PROJECTION: m_numProjectionShadows++; + break; default: break; } @@ -1907,6 +1909,7 @@ W3DProjectedShadow* W3DProjectedShadowManager::addShadow(RenderObjClass *robj, S break; case SHADOW_PROJECTION: m_numProjectionShadows++; + break; default: break; } @@ -2037,6 +2040,7 @@ void W3DProjectedShadowManager::removeShadow (W3DProjectedShadow *shadow) break; case SHADOW_PROJECTION: m_numProjectionShadows--; + break; default: break; } @@ -2062,6 +2066,7 @@ void W3DProjectedShadowManager::removeShadow (W3DProjectedShadow *shadow) break; case SHADOW_PROJECTION: m_numProjectionShadows--; + break; default: break; } diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp index 6794944a86d..02177767862 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp @@ -2525,6 +2525,7 @@ void W3DDisplay::drawImage( const Image *image, Int startX, Int startY, m_2DRender->Enable_Additive(false); m_2DRender->Enable_Alpha(false); doAlphaReset = TRUE; + break; default: break; } @@ -2779,75 +2780,77 @@ void W3DDisplay::setShroudLevel( Int x, Int y, CellShroudStatus setting ) //============================================================================= ///Utility function to dump data into a .BMP file static void CreateBMPFile(LPTSTR pszFile, char *image, Int width, Int height) -{ - HANDLE hf; // file handle - BITMAPFILEHEADER hdr; // bitmap file-header - PBITMAPINFOHEADER pbih; // bitmap info-header - LPBYTE lpBits; // memory pointer - DWORD dwTotal; // total count of bytes - DWORD cb; // incremental count of bytes - BYTE *hp; // byte pointer - DWORD dwTmp; - - PBITMAPINFO pbmi; - - pbmi = (PBITMAPINFO) LocalAlloc(LPTR,sizeof(BITMAPINFOHEADER)); - pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); - pbmi->bmiHeader.biWidth = width; - pbmi->bmiHeader.biHeight = height; - pbmi->bmiHeader.biPlanes = 1; - pbmi->bmiHeader.biBitCount = 24; - pbmi->bmiHeader.biCompression = BI_RGB; - pbmi->bmiHeader.biSizeImage = (pbmi->bmiHeader.biWidth + 7) /8 * pbmi->bmiHeader.biHeight * 24; - pbmi->bmiHeader.biClrImportant = 0; - - - pbih = (PBITMAPINFOHEADER) pbmi; - lpBits = (LPBYTE) image; - - // Create the .BMP file. - hf = CreateFile(pszFile, - GENERIC_READ | GENERIC_WRITE, - (DWORD) 0, - NULL, - CREATE_ALWAYS, - FILE_ATTRIBUTE_NORMAL, - (HANDLE) NULL); - if (hf == INVALID_HANDLE_VALUE) - return; - hdr.bfType = 0x4d42; // 0x42 = "B" 0x4d = "M" - // Compute the size of the entire file. - hdr.bfSize = (DWORD) (sizeof(BITMAPFILEHEADER) + - pbih->biSize + pbih->biClrUsed - * sizeof(RGBQUAD) + pbih->biSizeImage); - hdr.bfReserved1 = 0; - hdr.bfReserved2 = 0; - - // Compute the offset to the array of color indices. - hdr.bfOffBits = (DWORD) sizeof(BITMAPFILEHEADER) + - pbih->biSize + pbih->biClrUsed - * sizeof (RGBQUAD); - - // Copy the BITMAPFILEHEADER into the .BMP file. - if (!WriteFile(hf, (LPVOID) &hdr, sizeof(BITMAPFILEHEADER), - (LPDWORD) &dwTmp, NULL)) - return; - - // Copy the BITMAPINFOHEADER and RGBQUAD array into the file. - if (!WriteFile(hf, (LPVOID) pbih, sizeof(BITMAPINFOHEADER) + pbih->biClrUsed * sizeof (RGBQUAD),(LPDWORD) &dwTmp, NULL)) +{ + HANDLE hf; // file handle + BITMAPFILEHEADER hdr; // bitmap file-header + PBITMAPINFOHEADER pbih; // bitmap info-header + LPBYTE lpBits; // memory pointer + DWORD dwTotal; // total count of bytes + DWORD cb; // incremental count of bytes + BYTE *hp; // byte pointer + DWORD dwTmp; + + PBITMAPINFO pbmi; + + pbmi = (PBITMAPINFO) LocalAlloc(LPTR,sizeof(BITMAPINFOHEADER)); + if (pbmi == NULL) return; - // Copy the array of color indices into the .BMP file. - dwTotal = cb = pbih->biSizeImage; - hp = lpBits; - if (!WriteFile(hf, (LPSTR) hp, (int) cb, (LPDWORD) &dwTmp,NULL)) - return; + pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + pbmi->bmiHeader.biWidth = width; + pbmi->bmiHeader.biHeight = height; + pbmi->bmiHeader.biPlanes = 1; + pbmi->bmiHeader.biBitCount = 24; + pbmi->bmiHeader.biCompression = BI_RGB; + pbmi->bmiHeader.biSizeImage = (pbmi->bmiHeader.biWidth + 7) /8 * pbmi->bmiHeader.biHeight * 24; + pbmi->bmiHeader.biClrImportant = 0; + + pbih = (PBITMAPINFOHEADER) pbmi; + lpBits = (LPBYTE) image; + + // Create the .BMP file. + hf = CreateFile(pszFile, + GENERIC_READ | GENERIC_WRITE, + (DWORD) 0, + NULL, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + (HANDLE) NULL); + + if (hf != INVALID_HANDLE_VALUE) + { + hdr.bfType = 0x4d42; // 0x42 = "B" 0x4d = "M" + // Compute the size of the entire file. + hdr.bfSize = (DWORD) (sizeof(BITMAPFILEHEADER) + + pbih->biSize + pbih->biClrUsed + * sizeof(RGBQUAD) + pbih->biSizeImage); + hdr.bfReserved1 = 0; + hdr.bfReserved2 = 0; + + // Compute the offset to the array of color indices. + hdr.bfOffBits = (DWORD) sizeof(BITMAPFILEHEADER) + + pbih->biSize + pbih->biClrUsed + * sizeof (RGBQUAD); + + // Copy the BITMAPFILEHEADER into the .BMP file. + if (WriteFile(hf, (LPVOID) &hdr, sizeof(BITMAPFILEHEADER), + (LPDWORD) &dwTmp, NULL)) + { + // Copy the BITMAPINFOHEADER and RGBQUAD array into the file. + if (WriteFile(hf, (LPVOID) pbih, sizeof(BITMAPINFOHEADER) + pbih->biClrUsed * sizeof (RGBQUAD),(LPDWORD) &dwTmp, NULL)) + { + // Copy the array of color indices into the .BMP file. + dwTotal = cb = pbih->biSizeImage; + hp = lpBits; + WriteFile(hf, (LPSTR) hp, (int) cb, (LPDWORD) &dwTmp, NULL); + } + } - // Close the .BMP file. - if (!CloseHandle(hf)) - return; + // Close the .BMP file. + CloseHandle(hf); + } - // Free memory. + // Free memory. LocalFree( (HLOCAL) pbmi); } diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp index 2f8633aa94e..83a39bce904 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp @@ -136,7 +136,7 @@ RTS3DScene::RTS3DScene() //Allocate memory to hold queue of visible renderobjects that need to be drawn last //because they are forced translucent. m_translucentObjectsCount = 0; - if (TheGlobalData && TheGlobalData->m_maxVisibleTranslucentObjects) + if (TheGlobalData->m_maxVisibleTranslucentObjects > 0) m_translucentObjectsBuffer = NEW RenderObjClass* [TheGlobalData->m_maxVisibleTranslucentObjects]; else m_translucentObjectsBuffer = NULL; @@ -146,18 +146,26 @@ RTS3DScene::RTS3DScene() m_numNonOccluderOrOccludee=0; m_occludedObjectsCount=0; - m_potentialOccluders=NULL; - m_potentialOccludees=NULL; - m_nonOccludersOrOccludees=NULL; + if (TheGlobalData->m_maxVisibleOccluderObjects > 0) + m_potentialOccluders = NEW RenderObjClass* [TheGlobalData->m_maxVisibleOccluderObjects]; + else + m_potentialOccluders = NULL; + + if (TheGlobalData->m_maxVisibleOccludeeObjects > 0) + m_potentialOccludees = NEW RenderObjClass* [TheGlobalData->m_maxVisibleOccludeeObjects]; + else + m_potentialOccludees = NULL; + + if (TheGlobalData->m_maxVisibleNonOccluderOrOccludeeObjects > 0) + m_nonOccludersOrOccludees = NEW RenderObjClass* [TheGlobalData->m_maxVisibleNonOccluderOrOccludeeObjects]; + else + m_nonOccludersOrOccludees = NULL; //Modify the shader to make occlusion transparent ShaderClass shader = PlayerColorShader; shader.Set_Src_Blend_Func(ShaderClass::SRCBLEND_SRC_ALPHA); shader.Set_Dst_Blend_Func(ShaderClass::DSTBLEND_ONE_MINUS_SRC_ALPHA); - m_potentialOccluders = NEW RenderObjClass* [TheGlobalData->m_maxVisibleOccluderObjects]; - m_potentialOccludees = NEW RenderObjClass* [TheGlobalData->m_maxVisibleOccludeeObjects]; - m_nonOccludersOrOccludees = NEW RenderObjClass* [TheGlobalData->m_maxVisibleNonOccluderOrOccludeeObjects]; #ifdef USE_NON_STENCIL_OCCLUSION for (i=0; igetFrame(); + Int currentFrame=TheGameLogic->getFrame(); if (currentFrame <= TheGlobalData->m_defaultOcclusionDelay) currentFrame = TheGlobalData->m_defaultOcclusionDelay+1; //make sure occlusion is enabled when game starts (frame 0). diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp index f562493b036..6fe7a765013 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp @@ -2773,11 +2773,11 @@ HRESULT W3DShaderManager::LoadAndCreateD3DShader(const char* strFilePath, const file->close(); file = NULL; - if (ShaderType == TRUE)//SHADERTYPE_VERTEX) + if (ShaderType) // SHADERTYPE_VERTEX { hr = DX8Wrapper::_Get_D3D_Device8()->CreateVertexShader(pDeclaration, pShader, pHandle, Usage); } - else if (ShaderType == FALSE)//SHADERTYPE_PIXEL) + else // SHADERTYPE_PIXEL { hr = DX8Wrapper::_Get_D3D_Device8()->CreatePixelShader(pShader, pHandle); } diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp index 7167fb16331..857064fe16b 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp @@ -1695,6 +1695,7 @@ void WaterRenderObjClass::Render(RenderInfoClass & rinfo) renderWater(); } //WATER_TYPE_1 + break; default: break; diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/assetmgr.cpp b/Generals/Code/Libraries/Source/WWVegas/WW3D2/assetmgr.cpp index 50740c98052..a3712b62d42 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/assetmgr.cpp +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/assetmgr.cpp @@ -1145,6 +1145,12 @@ TextureClass * WW3DAssetManager::Get_Texture { tex = NEW_REF (TextureClass, (lower_case_name, NULL, mip_level_count, texture_format, allow_compression)); } + else + { + WWASSERT_PRINT(false, ("Unhandled case")); + return NULL; + } + TextureHash.Insert(tex->Get_Texture_Name(),tex); } diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp b/Generals/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp index 61156e1580c..a9a6365b05f 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp @@ -669,7 +669,8 @@ void DazzleRenderObjClass::Init_Type(const DazzleInitClass& i) unsigned new_count=i.type+1; DazzleTypeClass** new_types=W3DNEWARRAY DazzleTypeClass*[new_count]; unsigned a=0; - for (;aHeader.Attributes & W3D_MESH_FLAG_PRELIT_LIGHTMAP_MULTI_PASS) { @@ -352,7 +352,7 @@ WW3DErrorType MeshModelClass::Load_W3D(ChunkLoadClass & cload) Set_Flag (PRELIT_LIGHTMAP_MULTI_PASS, true); break; } - // Else fall thru... + FALLTHROUGH; // Else fall thru... case WW3D::PRELIT_MODE_VERTEX: if (context->Header.Attributes & W3D_MESH_FLAG_PRELIT_VERTEX) { @@ -360,7 +360,7 @@ WW3DErrorType MeshModelClass::Load_W3D(ChunkLoadClass & cload) Set_Flag (PRELIT_VERTEX, true); break; } - // Else fall thru... + FALLTHROUGH; // Else fall thru... default: diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/textureloader.cpp b/Generals/Code/Libraries/Source/WWVegas/WW3D2/textureloader.cpp index e24aef6a2bc..417aecde43f 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/textureloader.cpp +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/textureloader.cpp @@ -199,7 +199,7 @@ IDirect3DTexture8* TextureLoader::Load_Thumbnail(const StringClass& filename,WW3 MIP_LEVELS_ALL); unsigned level=0; - D3DLOCKED_RECT locked_rects[12]; + D3DLOCKED_RECT locked_rects[12]={0}; WWASSERT(d3d_texture->GetLevelCount()<=12); // Lock all surfaces @@ -979,7 +979,7 @@ void TextureLoadTaskClass::Init(TextureBaseClass* tc,bool high_priority) { // Make sure texture has a filename. REF_PTR_SET(Texture,tc); - WWASSERT(Texture->Get_Full_Path() != NULL); + //WWASSERT(Texture->Get_Full_Path() != NULL); Reduction=Texture->Get_Reduction(); HighPriorityRequested=high_priority; diff --git a/Generals/Code/Main/WinMain.cpp b/Generals/Code/Main/WinMain.cpp index 1f959a5bdf9..8a3f00888c3 100644 --- a/Generals/Code/Main/WinMain.cpp +++ b/Generals/Code/Main/WinMain.cpp @@ -740,6 +740,7 @@ static CriticalSection critSec1, critSec2, critSec3, critSec4, critSec5; Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, Int nCmdShow ) { + Int exitcode = 1; try { _set_se_translator( DumpExceptionInfo ); // Hook that allows stack trace. @@ -794,7 +795,7 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, // register windows class and create application window if(!TheGlobalData->m_headless && initializeAppWindows(hInstance, nCmdShow, TheGlobalData->m_windowed) == false) - return 0; + return exitcode; // save our application instance for future use ApplicationHInstance = hInstance; @@ -835,13 +836,13 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, TheVersion = NULL; shutdownMemoryManager(); DEBUG_SHUTDOWN(); - return 0; + return exitcode; } DEBUG_LOG(("Create Generals Mutex okay.\n")); DEBUG_LOG(("CRC message is %d\n", GameMessage::MSG_LOGIC_CRC)); // run the game main loop - GameMain(0, NULL); + exitcode = GameMain(); delete TheVersion; TheVersion = NULL; @@ -870,7 +871,7 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, TheDmaCriticalSection = NULL; TheMemoryPoolCriticalSection = NULL; - return 0; + return exitcode; } // end WinMain diff --git a/Generals/Code/Tools/WorldBuilder/src/WorldBuilderDoc.cpp b/Generals/Code/Tools/WorldBuilder/src/WorldBuilderDoc.cpp index ba03053650c..e3be4773473 100644 --- a/Generals/Code/Tools/WorldBuilder/src/WorldBuilderDoc.cpp +++ b/Generals/Code/Tools/WorldBuilder/src/WorldBuilderDoc.cpp @@ -1210,7 +1210,16 @@ BOOL CWorldBuilderDoc::OnNewDocument() m_waypointTableNeedsUpdate = true; m_curWaypointID = 0; WbApp()->selectPointerTool(); + + // Make sure that all the old units are removed from the list. + // Bug fix by MLL 1/14/03 + TheLayersList->enableUpdates(); + TheLayersList->resetLayers(); + TheLayersList->disableUpdates(); + + // TheSuperHackers @bugfix Caball009 20/06/2025 Must not delete polygon triggers before calling enableUpdates. PolygonTrigger::deleteTriggers(); + TheSidesList->clear(); TheSidesList->validateSides(); diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt index 343a0fe28f4..e2bea3ec545 100644 --- a/GeneralsMD/Code/GameEngine/CMakeLists.txt +++ b/GeneralsMD/Code/GameEngine/CMakeLists.txt @@ -94,6 +94,7 @@ set(GAMEENGINE_SRC Include/Common/RAMFile.h Include/Common/RandomValue.h Include/Common/Recorder.h +# Include/Common/ReplaySimulation.h Include/Common/Registry.h Include/Common/ResourceGatheringManager.h Include/Common/Science.h @@ -129,6 +130,7 @@ set(GAMEENGINE_SRC Include/Common/UserPreferences.h Include/Common/version.h Include/Common/WellKnownKeys.h +# Include/Common/WorkerProcess.h # Include/Common/Xfer.h # Include/Common/XferCRC.h # Include/Common/XferDeepCRC.h @@ -607,6 +609,7 @@ set(GAMEENGINE_SRC Source/Common/PerfTimer.cpp Source/Common/RandomValue.cpp Source/Common/Recorder.cpp +# Source/Common/ReplaySimulation.cpp Source/Common/RTS/AcademyStats.cpp Source/Common/RTS/ActionManager.cpp Source/Common/RTS/Energy.cpp @@ -676,6 +679,7 @@ set(GAMEENGINE_SRC Source/Common/Thing/ThingTemplate.cpp Source/Common/UserPreferences.cpp Source/Common/version.cpp +# Source/Common/WorkerProcess.cpp Source/GameClient/ClientInstance.cpp Source/GameClient/Color.cpp Source/GameClient/Credits.cpp diff --git a/GeneralsMD/Code/GameEngine/Include/Common/GameEngine.h b/GeneralsMD/Code/GameEngine/Include/Common/GameEngine.h index 087e0e8aba7..995817c7a2b 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/GameEngine.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/GameEngine.h @@ -67,7 +67,6 @@ class GameEngine : public SubsystemInterface virtual ~GameEngine(); virtual void init( void ); ///< Init engine by creating client and logic - virtual void init( int argc, char *argv[] ); ///< Init engine by creating client and logic virtual void reset( void ); ///< reset system to starting state virtual void update( void ); ///< per frame update @@ -114,6 +113,6 @@ extern GameEngine *TheGameEngine; extern GameEngine *CreateGameEngine( void ); /// The entry point for the game system -extern void GameMain( int argc, char *argv[] ); +extern Int GameMain(); #endif // _GAME_ENGINE_H_ diff --git a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h index 9dc544d9b2e..c4e33a69620 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h @@ -54,7 +54,8 @@ enum AIDebugOptions CPP_11(: Int); // PUBLIC ///////////////////////////////////////////////////////////////////////////////////////// -const Int MAX_GLOBAL_LIGHTS = 3; +CONSTEXPR const Int MAX_GLOBAL_LIGHTS = 3; +CONSTEXPR const Int SIMULATE_REPLAYS_SEQUENTIAL = -1; //------------------------------------------------------------------------------------------------- class CommandLineData @@ -347,8 +348,11 @@ class GlobalData : public SubsystemInterface Real m_cameraAdjustSpeed; ///< Rate at which we adjust camera height Bool m_enforceMaxCameraHeight; ///< Enfoce max camera height while scrolling? Bool m_buildMapCache; - AsciiString m_initialFile; ///< If this is specified, load a specific map/replay from the command-line + AsciiString m_initialFile; ///< If this is specified, load a specific map from the command-line AsciiString m_pendingFile; ///< If this is specified, use this map at the next game start + + std::vector m_simulateReplays; ///< If not empty, simulate this list of replays and exit. + Int m_simulateReplayJobs; ///< Maximum number of processes to use for simulation, or SIMULATE_REPLAYS_SEQUENTIAL for sequential simulation Int m_maxParticleCount; ///< maximum number of particles that can exist Int m_maxFieldParticleCount; ///< maximum number of field-type particles that can exist (roughly) @@ -573,6 +577,11 @@ class GlobalData : public SubsystemInterface // singleton extern GlobalData* TheWritableGlobalData; +// use TheGlobalData for all read-only accesses +#if __cplusplus >= 201703L +inline const GlobalData* const& TheGlobalData = TheWritableGlobalData; +#else #define TheGlobalData ((const GlobalData*)TheWritableGlobalData) +#endif #endif diff --git a/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h b/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h index b776e6acf4d..a29187c96a9 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h @@ -48,6 +48,7 @@ class ReplayGameInfo : public GameInfo enum RecorderModeType CPP_11(: Int) { RECORDERMODETYPE_RECORD, RECORDERMODETYPE_PLAYBACK, + RECORDERMODETYPE_SIMULATION_PLAYBACK, // Play back replay without any graphics RECORDERMODETYPE_NONE // this is a valid state to be in on the shell map, or in saved games }; @@ -74,11 +75,13 @@ class RecorderClass : public SubsystemInterface { Bool replayMatchesGameVersion(AsciiString filename); ///< Returns true if the playback is a valid playback file for this version. static Bool replayMatchesGameVersion(const ReplayHeader& header); ///< Returns true if the playback is a valid playback file for this version. AsciiString getCurrentReplayFilename( void ); ///< valid during playback only + UnsignedInt getPlaybackFrameCount() const { return m_playbackFrameCount; } ///< valid during playback only void stopPlayback(); ///< Stops playback. Its fine to call this even if not playing back a file. + Bool simulateReplay(AsciiString filename); #if defined RTS_DEBUG || defined RTS_INTERNAL Bool analyzeReplay( AsciiString filename ); - Bool isAnalysisInProgress( void ); #endif + Bool isPlaybackInProgress() const; public: void handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool fromPlayback); @@ -100,7 +103,7 @@ class RecorderClass : public SubsystemInterface { UnsignedInt iniCRC; time_t startTime; time_t endTime; - UnsignedInt frameDuration; + UnsignedInt frameCount; Bool quitEarly; Bool desyncGame; Bool playerDiscons[MAX_SLOTS]; @@ -110,10 +113,11 @@ class RecorderClass : public SubsystemInterface { Bool readReplayHeader( ReplayHeader& header ); RecorderModeType getMode(); ///< Returns the current operating mode. + Bool isPlaybackMode() const { return m_mode == RECORDERMODETYPE_PLAYBACK || m_mode == RECORDERMODETYPE_SIMULATION_PLAYBACK; } void initControls(); ///< Show or Hide the Replay controls AsciiString getReplayDir(); ///< Returns the directory that holds the replay files. - AsciiString getReplayExtention(); ///< Returns the file extention for replay files. + static AsciiString getReplayExtention(); ///< Returns the file extention for replay files. AsciiString getLastReplayFileName(); ///< Returns the filename used for the default replay. GameInfo *getGameInfo( void ) { return &m_gameInfo; } ///< Returns the slot list for playback game start @@ -124,6 +128,7 @@ class RecorderClass : public SubsystemInterface { void logPlayerDisconnect(UnicodeString player, Int slot); void logCRCMismatch( void ); + Bool sawCRCMismatch() const; void cleanUpReplayFile( void ); ///< after a crash, send replay/debug info to a central repository void stopRecording(); ///< Stop recording and close m_file. @@ -154,6 +159,7 @@ class RecorderClass : public SubsystemInterface { Int m_currentFilePosition; RecorderModeType m_mode; AsciiString m_currentReplayFilename; ///< valid during playback only + UnsignedInt m_playbackFrameCount; ReplayGameInfo m_gameInfo; Bool m_wasDesync; diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/ClientInstance.h b/GeneralsMD/Code/GameEngine/Include/GameClient/ClientInstance.h index 74e7add404b..1f1ada1daca 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/ClientInstance.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/ClientInstance.h @@ -30,6 +30,15 @@ class ClientInstance static bool isInitialized(); + static bool isMultiInstance(); + + // Change multi instance on runtime. Must be called before initialize. + static void setMultiInstance(bool v); + + // Skips using the primary instance. Must be called before initialize. + // Useful when the new process is not meant to collide with another normal Generals process. + static void skipPrimaryInstance(); + // Returns the instance index of this game client. Starts at 0. static UnsignedInt getInstanceIndex(); @@ -42,6 +51,7 @@ class ClientInstance private: static HANDLE s_mutexHandle; static UnsignedInt s_instanceIndex; + static Bool s_isMultiInstance; }; } // namespace rts diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h b/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h index 3411ab8defe..a6f60f36bf6 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h @@ -99,6 +99,8 @@ class GameClient : public SubsystemInterface, virtual void setFrame( UnsignedInt frame ) { m_frame = frame; } ///< Set the GameClient's internal frame number virtual void registerDrawable( Drawable *draw ); ///< Given a drawable, register it with the GameClient and give it a unique ID + void updateHeadless(); + void addDrawableToLookupTable( Drawable *draw ); ///< add drawable ID to hash lookup table void removeDrawableFromLookupTable( Drawable *draw ); ///< remove drawable ID from hash lookup table diff --git a/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp b/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp index 1b31d5028a7..dc967b179c4 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp @@ -29,7 +29,9 @@ #include "Common/CommandLine.h" #include "Common/CRCDebug.h" #include "Common/LocalFileSystem.h" +#include "Common/Recorder.h" #include "Common/version.h" +#include "GameClient/ClientInstance.h" #include "GameClient/TerrainVisual.h" // for TERRAIN_LOD_MIN definition #include "GameClient/GameText.h" #include "GameNetwork/NetworkDefs.h" @@ -405,6 +407,56 @@ Int parseMapName(char *args[], int num) return 1; } +Int parseHeadless(char *args[], int num) +{ + TheWritableGlobalData->m_headless = TRUE; + TheWritableGlobalData->m_playIntro = FALSE; + TheWritableGlobalData->m_afterIntro = TRUE; + TheWritableGlobalData->m_playSizzle = FALSE; + return 1; +} + +Int parseReplay(char *args[], int num) +{ + if (num > 1) + { + AsciiString filename = args[1]; + if (!filename.endsWithNoCase(RecorderClass::getReplayExtention())) + { + printf("Invalid replay name \"%s\"\n", filename.str()); + exit(1); + } + TheWritableGlobalData->m_simulateReplays.push_back(filename); + + TheWritableGlobalData->m_playIntro = FALSE; + TheWritableGlobalData->m_afterIntro = TRUE; + TheWritableGlobalData->m_playSizzle = FALSE; + TheWritableGlobalData->m_shellMapOn = FALSE; + + // Make replay playback possible while other clients (possible retail) are running + rts::ClientInstance::setMultiInstance(TRUE); + rts::ClientInstance::skipPrimaryInstance(); + + return 2; + } + return 1; +} + +Int parseJobs(char *args[], int num) +{ + if (num > 1) + { + TheWritableGlobalData->m_simulateReplayJobs = atoi(args[1]); + if (TheGlobalData->m_simulateReplayJobs < SIMULATE_REPLAYS_SEQUENTIAL || TheGlobalData->m_simulateReplayJobs == 0) + { + printf("Invalid number of jobs: %d\n", TheGlobalData->m_simulateReplayJobs); + exit(1); + } + return 2; + } + return 1; +} + Int parseXRes(char *args[], int num) { if (num > 1) @@ -782,13 +834,6 @@ Int parseQuickStart( char *args[], int num ) return 1; } -Int parseHeadless( char *args[], int num ) -{ - TheWritableGlobalData->m_headless = TRUE; - - return 1; -} - Int parseConstantDebug( char *args[], int num ) { TheWritableGlobalData->m_constantDebugUpdate = TRUE; @@ -1103,8 +1148,20 @@ static CommandLineParam paramsForStartup[] = { "-fullscreen", parseNoWin }, // TheSuperHackers @feature helmutbuhler 11/04/2025 - // This runs the game without a window, graphics, input and audio. Used for testing. + // This runs the game without a window, graphics, input and audio. You can combine this with -replay { "-headless", parseHeadless }, + + // TheSuperHackers @feature helmutbuhler 13/04/2025 + // Play back a replay. Pass the filename including .rep afterwards. + // You can pass this multiple times to play back multiple replays. + // You can also include wildcards. The file must be in the replay folder or in a subfolder. + { "-replay", parseReplay }, + + // TheSuperHackers @feature helmutbuhler 23/05/2025 + // Simulate each replay in a separate process and use 1..N processes at the same time. + // (If you have 4 cores, call it with -jobs 4) + // If you do not call this, all replays will be simulated in sequence in the same process. + { "-jobs", parseJobs }, }; // These Params are parsed during Engine Init before INI data is loaded diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Dict.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Dict.cpp index 6fed812fc98..65114fdbf14 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Dict.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Dict.cpp @@ -182,7 +182,7 @@ Dict::DictPair *Dict::ensureUnique(int numPairsNeeded, Bool preserveData, DictPa } } - Int delta; + Int delta = 0; if (pairToTranslate && m_data) delta = pairToTranslate - m_data->peek(); diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp index e4382cf8138..f95a046a494 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp @@ -251,8 +251,7 @@ void GameEngine::setFramesPerSecondLimit( Int fps ) /** ----------------------------------------------------------------------------------------------- * Initialize the game engine by initializing the GameLogic and GameClient. */ -void GameEngine::init( void ) {} /// @todo: I changed this to take argc & argv so we can parse those after the GDF is loaded. We need to rethink this immediately as it is a nasty hack -void GameEngine::init( int argc, char *argv[] ) +void GameEngine::init() { try { //create an INI object to use for loading stuff @@ -613,7 +612,7 @@ void GameEngine::init( int argc, char *argv[] ) // load the initial shell screen //TheShell->push( AsciiString("Menus/MainMenu.wnd") ); - // This allows us to run a map/reply from the command line + // This allows us to run a map from the command line if (TheGlobalData->m_initialFile.isEmpty() == FALSE) { AsciiString fname = TheGlobalData->m_initialFile; @@ -635,10 +634,6 @@ void GameEngine::init( int argc, char *argv[] ) msg->appendIntegerArgument(0); InitRandom(0); } - else if (fname.endsWithNoCase(".rep")) - { - TheRecorder->playbackFile(fname); - } } // diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GameLOD.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GameLOD.cpp index 725c75fc8ef..235c3a14a68 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GameLOD.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GameLOD.cpp @@ -255,13 +255,17 @@ BenchProfile *GameLODManager::newBenchProfile(void) LODPresetInfo *GameLODManager::newLODPreset(StaticGameLODLevel index) { - if (m_numLevelPresets[index] < MAX_LOD_PRESETS_PER_LEVEL) - { - m_numLevelPresets[index]++; - return &m_lodPresets[index][m_numLevelPresets[index]-1]; + if (index >= 0 && index < STATIC_GAME_LOD_COUNT) + { + if (m_numLevelPresets[index] < MAX_LOD_PRESETS_PER_LEVEL) + { + m_numLevelPresets[index]++; + return &m_lodPresets[index][m_numLevelPresets[index]-1]; + } + + DEBUG_CRASH(( "GameLODManager::newLODPreset - Too many presets defined for '%s'\n", TheGameLODManager->getStaticGameLODLevelName(index))); } - DEBUG_CRASH(( "GameLODManager::newLODPreset - Too many presets defined for '%s'\n", TheGameLODManager->getStaticGameLODLevelName(index))); return NULL; } @@ -582,9 +586,10 @@ void GameLODManager::applyStaticLODLevel(StaticGameLODLevel level) TheWritableGlobalData->m_enableDynamicLOD = lodInfo->m_enableDynamicLOD; TheWritableGlobalData->m_useFpsLimit = lodInfo->m_useFpsLimit; TheWritableGlobalData->m_useTrees = requestedTrees; - } - if (!m_memPassed || isReallyLowMHz()) { - TheWritableGlobalData->m_shellMapOn = false; + + if (!m_memPassed || isReallyLowMHz()) { + TheWritableGlobalData->m_shellMapOn = false; + } } if (TheTerrainVisual) TheTerrainVisual->setTerrainTracksDetail(); @@ -693,6 +698,9 @@ Int GameLODManager::getRecommendedTextureReduction(void) if (!m_memPassed) //if they have < 256 MB, force them to low res textures. return m_staticGameLODInfo[STATIC_GAME_LOD_LOW].m_textureReduction; + if (m_idealDetailLevel < 0 || m_idealDetailLevel >= STATIC_GAME_LOD_COUNT) + return m_staticGameLODInfo[STATIC_GAME_LOD_LOW].m_textureReduction; + return m_staticGameLODInfo[m_idealDetailLevel].m_textureReduction; } diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GameMain.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GameMain.cpp index aef3d163e30..7ee88428c06 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GameMain.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GameMain.cpp @@ -29,23 +29,33 @@ #include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine #include "Common/GameEngine.h" +#include "Common/ReplaySimulation.h" /** * This is the entry point for the game system. */ -void GameMain( int argc, char *argv[] ) +Int GameMain() { + int exitcode = 0; // initialize the game engine using factory function TheGameEngine = CreateGameEngine(); - TheGameEngine->init(argc, argv); - - // run it - TheGameEngine->execute(); + TheGameEngine->init(); + + if (!TheGlobalData->m_simulateReplays.empty()) + { + exitcode = ReplaySimulation::simulateReplays(TheGlobalData->m_simulateReplays, TheGlobalData->m_simulateReplayJobs); + } + else + { + // run it + TheGameEngine->execute(); + } // since execute() returned, we are exiting the game delete TheGameEngine; TheGameEngine = NULL; + return exitcode; } diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp index 6f94d7d59a3..11af51a8109 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp @@ -956,6 +956,9 @@ GlobalData::GlobalData() m_buildMapCache = FALSE; m_initialFile.clear(); m_pendingFile.clear(); + + m_simulateReplays.clear(); + m_simulateReplayJobs = SIMULATE_REPLAYS_SEQUENTIAL; for (i = LEVEL_FIRST; i <= LEVEL_LAST; ++i) m_healthBonus[i] = 1.0f; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp b/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp index 1634d019aec..bc9569238f1 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp @@ -717,6 +717,7 @@ AsciiString INI::getNextQuotedAsciiString() { AsciiString result; char buff[INI_MAX_CHARS_PER_LINE]; + buff[0] = '\0'; const char *token = getNextTokenOrNull(); // if null, just leave an empty string if (token != NULL) diff --git a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Money.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Money.cpp index 9c3fabc5813..b95f35a83fe 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Money.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Money.cpp @@ -60,13 +60,14 @@ UnsignedInt Money::withdraw(UnsignedInt amountToWithdraw, Bool playSound) if (amountToWithdraw == 0) return amountToWithdraw; - //@todo: Do we do this frequently enough that it is a performance hit? - AudioEventRTS event = TheAudio->getMiscAudio()->m_moneyWithdrawSound; - event.setPlayerIndex(m_playerIndex); - // Play a sound if (playSound) + { + //@todo: Do we do this frequently enough that it is a performance hit? + AudioEventRTS event = TheAudio->getMiscAudio()->m_moneyWithdrawSound; + event.setPlayerIndex(m_playerIndex); TheAudio->addAudioEvent(&event); + } m_money -= amountToWithdraw; @@ -79,13 +80,14 @@ void Money::deposit(UnsignedInt amountToDeposit, Bool playSound) if (amountToDeposit == 0) return; - //@todo: Do we do this frequently enough that it is a performance hit? - AudioEventRTS event = TheAudio->getMiscAudio()->m_moneyDepositSound; - event.setPlayerIndex(m_playerIndex); - // Play a sound if (playSound) + { + //@todo: Do we do this frequently enough that it is a performance hit? + AudioEventRTS event = TheAudio->getMiscAudio()->m_moneyDepositSound; + event.setPlayerIndex(m_playerIndex); TheAudio->addAudioEvent(&event); + } m_money += amountToDeposit; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/RandomValue.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RandomValue.cpp index 5fa23da03b4..c1b41acd5b9 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/RandomValue.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/RandomValue.cpp @@ -383,6 +383,7 @@ Real GameClientRandomVariable::getValue( void ) const if (m_low == m_high) { return m_low; } // else return as though a UNIFORM. + FALLTHROUGH; case UNIFORM: return GameClientRandomValueReal( m_low, m_high ); @@ -427,6 +428,7 @@ Real GameLogicRandomVariable::getValue( void ) const if (m_low == m_high) { return m_low; } // else return as though a UNIFORM. + FALLTHROUGH; case UNIFORM: return GameLogicRandomValueReal( m_low, m_high ); diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp index a8873aefd58..8261cbbe7f5 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp @@ -67,8 +67,8 @@ typedef int32_t replay_time_t; static time_t startTime; static const UnsignedInt startTimeOffset = 6; static const UnsignedInt endTimeOffset = startTimeOffset + sizeof(replay_time_t); -static const UnsignedInt framesOffset = endTimeOffset + sizeof(replay_time_t); -static const UnsignedInt desyncOffset = framesOffset + sizeof(UnsignedInt); +static const UnsignedInt frameCountOffset = endTimeOffset + sizeof(replay_time_t); +static const UnsignedInt desyncOffset = frameCountOffset + sizeof(UnsignedInt); static const UnsignedInt quitEarlyOffset = desyncOffset + sizeof(Bool); static const UnsignedInt disconOffset = quitEarlyOffset + sizeof(Bool); @@ -232,7 +232,7 @@ void RecorderClass::logGameEnd( void ) time_t t; time(&t); - UnsignedInt duration = TheGameLogic->getFrame(); + UnsignedInt frameCount = TheGameLogic->getFrame(); UnsignedInt fileSize = ftell(m_file); // move to appropriate offset if (!fseek(m_file, endTimeOffset, SEEK_SET)) @@ -242,10 +242,10 @@ void RecorderClass::logGameEnd( void ) fwrite(&tmp, sizeof(replay_time_t), 1, m_file); } // move to appropriate offset - if (!fseek(m_file, framesOffset, SEEK_SET)) + if (!fseek(m_file, frameCountOffset, SEEK_SET)) { - // save off duration - fwrite(&duration, sizeof(UnsignedInt), 1, m_file); + // save off frameCount + fwrite(&frameCount, sizeof(UnsignedInt), 1, m_file); } // move back to end of stream #ifdef DEBUG_CRASHING @@ -272,7 +272,7 @@ void RecorderClass::logGameEnd( void ) if (logFP) { struct tm *t2 = localtime(&t); - duration = t - startTime; + time_t duration = t - startTime; Int minutes = duration/60; Int seconds = duration%60; fprintf(logFP, "Game end at %s(%d:%2.2d elapsed time)\n", asctime(t2), minutes, seconds); @@ -408,6 +408,7 @@ void RecorderClass::init() { m_gameInfo.setSeed(GetGameLogicRandomSeed()); m_wasDesync = FALSE; m_doingAnalysis = FALSE; + m_playbackFrameCount = 0; } /** @@ -430,7 +431,7 @@ void RecorderClass::reset() { void RecorderClass::update() { if (m_mode == RECORDERMODETYPE_RECORD || m_mode == RECORDERMODETYPE_NONE) { updateRecord(); - } else if (m_mode == RECORDERMODETYPE_PLAYBACK) { + } else if (isPlaybackMode()) { updatePlayback(); } } @@ -476,11 +477,11 @@ void RecorderClass::stopPlayback() { m_file = NULL; } m_fileName.clear(); - // Don't clear the game data if the replay is over - let things continue -//#ifdef DEBUG_CRC + if (!m_doingAnalysis) + { TheMessageStream->appendMessage(GameMessage::MSG_CLEAR_GAME_DATA); -//#endif + } } /** @@ -854,7 +855,7 @@ Bool RecorderClass::readReplayHeader(ReplayHeader& header) fread(&tmp, sizeof(replay_time_t), 1, m_file); header.endTime = tmp; - fread(&header.frameDuration, sizeof(UnsignedInt), 1, m_file); + fread(&header.frameCount, sizeof(UnsignedInt), 1, m_file); fread(&header.desyncGame, sizeof(Bool), 1, m_file); fread(&header.quitEarly, sizeof(Bool), 1, m_file); @@ -917,6 +918,14 @@ Bool RecorderClass::readReplayHeader(ReplayHeader& header) return TRUE; } +Bool RecorderClass::simulateReplay(AsciiString filename) +{ + Bool success = playbackFile(filename); + if (success) + m_mode = RECORDERMODETYPE_SIMULATION_PLAYBACK; + return success; +} + #if defined RTS_DEBUG || defined RTS_INTERNAL Bool RecorderClass::analyzeReplay( AsciiString filename ) { @@ -924,15 +933,18 @@ Bool RecorderClass::analyzeReplay( AsciiString filename ) return playbackFile(filename); } -Bool RecorderClass::isAnalysisInProgress( void ) + + +#endif + +Bool RecorderClass::isPlaybackInProgress( void ) const { - return m_mode == RECORDERMODETYPE_PLAYBACK && m_nextFrame != -1; + return isPlaybackMode() && m_nextFrame != -1; } -#endif AsciiString RecorderClass::getCurrentReplayFilename( void ) { - if (m_mode == RECORDERMODETYPE_PLAYBACK) + if (isPlaybackMode()) { return m_currentReplayFilename; } @@ -966,7 +978,7 @@ class CRCInfo UnsignedInt getLocalPlayer(void) { return m_localPlayer; } void setSawCRCMismatch(void) { m_sawCRCMismatch = TRUE; } - Bool sawCRCMismatch(void) { return m_sawCRCMismatch; } + Bool sawCRCMismatch(void) const { return m_sawCRCMismatch; } protected: @@ -1014,6 +1026,11 @@ UnsignedInt CRCInfo::readCRC(void) return val; } +Bool RecorderClass::sawCRCMismatch() const +{ + return m_crcInfo->sawCRCMismatch(); +} + void RecorderClass::handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool fromPlayback) { if (fromPlayback) @@ -1061,6 +1078,9 @@ void RecorderClass::handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool f DEBUG_LOG(("Replay has gone out of sync!\nInGame:%8.8X Replay:%8.8X\nFrame:%d\n", playbackCRC, newCRC, mismatchFrame)); + // Print Mismatch in case we are simulating replays from console. + printf("CRC Mismatch in Frame %d\n", mismatchFrame); + // TheSuperHackers @tweak Pause the game on mismatch. Bool pause = TRUE; Bool pauseMusic = FALSE; @@ -1215,17 +1235,23 @@ Bool RecorderClass::playbackFile(AsciiString filename) // send a message to the logic for a new game if (!m_doingAnalysis) { - GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_NEW_GAME ); + // TheSuperHackers @info helmutbuhler 13/04/2025 + // We send the New Game message here directly to the command list and bypass the TheMessageStream. + // That's ok because Multiplayer is disabled during replay playback and is actually required + // during replay simulation because we don't update TheMessageStream during simulation. + GameMessage *msg = newInstance(GameMessage)(GameMessage::MSG_NEW_GAME); msg->appendIntegerArgument(GAME_REPLAY); msg->appendIntegerArgument(difficulty); msg->appendIntegerArgument(rankPoints); if( maxFPS != 0 ) msg->appendIntegerArgument(maxFPS); + TheCommandList->appendMessage( msg ); //InitGameLogicRandom( m_gameInfo.getSeed()); InitRandom( m_gameInfo.getSeed() ); } m_currentReplayFilename = filename; + m_playbackFrameCount = header.frameCount; return TRUE; } @@ -1663,7 +1689,7 @@ void RecorderClass::initControls() Bool RecorderClass::isMultiplayer( void ) { - if (m_mode == RECORDERMODETYPE_PLAYBACK) + if (isPlaybackMode()) { GameSlot *slot; for (int i=0; igetID(); - xfer->xferUnsignedInt(&id); - if (id!=state->getID()) { - DEBUG_CRASH(("State ID mismatch - %d expected, %d read", state->getID(), id)); - throw SC_INVALID_DATA; + if( state != NULL ) + { + StateID id = state->getID(); + xfer->xferUnsignedInt(&id); + if (id!=state->getID()) { + DEBUG_CRASH(("State ID mismatch - %d expected, %d read", state->getID(), id)); + throw SC_INVALID_DATA; + } } - - if( state == NULL ) + else { - DEBUG_ASSERTCRASH(state != NULL, ("state was NULL on xfer, trying to heal...")); + DEBUG_CRASH(("state was NULL on xfer, trying to heal...")); // Hmm... too late to find out why we are getting NULL in our state, but if we let it go, we will Throw in xferSnapshot. state = internalGetState(m_defaultStateID); + StateID id = state->getID(); + xfer->xferUnsignedInt(&id); } + xfer->xferSnapshot(state); } diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/Debug.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/Debug.cpp index f9090dfa94e..173c496ad4c 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/Debug.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/Debug.cpp @@ -58,6 +58,7 @@ #ifdef DEBUG_THREADSAFE #include "Common/CriticalSection.h" #endif +#include "Common/CommandLine.h" #include "Common/Debug.h" #include "Common/CRCDebug.h" #include "Common/SystemInfo.h" @@ -366,7 +367,8 @@ void DebugInit(int flags) #ifdef DEBUG_LOGGING // TheSuperHackers @info Debug initialization can happen very early. - // Therefore, initialize the client instance now. + // Therefore, parse initial commandline and initialize the client instance now. + CommandLine::parseCommandLineForStartup(); if (!rts::ClientInstance::initialize()) return; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/FileSystem.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/FileSystem.cpp index 4f8a7d63b3f..aed60a6b17c 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/FileSystem.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/FileSystem.cpp @@ -384,7 +384,6 @@ Bool FileSystem::isPathInDirectory(const AsciiString& testPath, const AsciiStrin if (!testPathNormalized.startsWith(basePathNormalized)) #endif { - DEBUG_CRASH(("Normalized file path for '%s': '%s' was outside the expected base path of '%s' (normalized: '%s').\n", testPath.str(), testPathNormalized.str(), basePath.str(), basePathNormalized.str())); return false; } diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp index ffc33ea9abc..fd2de28eb16 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp @@ -936,6 +936,7 @@ AsciiString GameState::portableMapPathToRealMapPath(const AsciiString& in) const if (!FileSystem::isPathInDirectory(prefix, containingBasePath)) { + DEBUG_LOG(("Normalized file path for '%s' was outside the expected base path of '%s'.\n", prefix.str(), containingBasePath.str())); return AsciiString::TheEmptyString; } diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Thing/Module.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Thing/Module.cpp index 9932109bcbd..a5908421bcd 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Thing/Module.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Thing/Module.cpp @@ -257,9 +257,9 @@ void UpgradeMuxData::muxDataProcessUpgradeRemoval(Object* obj) const it++) { const UpgradeTemplate* theTemplate = TheUpgradeCenter->findUpgrade( *it ); - if( !theTemplate && !it->isEmpty() && !it->isNone()) + if( !theTemplate ) { - DEBUG_CRASH(("An upgrade module references %s, which is not an Upgrade", it->str())); + DEBUG_CRASH(("An upgrade module references '%s', which is not an Upgrade", it->str())); throw INI_INVALID_DATA; } @@ -299,9 +299,9 @@ void UpgradeMuxData::getUpgradeActivationMasks(UpgradeMaskType& activation, Upgr it++) { const UpgradeTemplate* theTemplate = TheUpgradeCenter->findUpgrade( *it ); - if( !theTemplate && !it->isEmpty() && !it->isNone()) + if( !theTemplate ) { - DEBUG_CRASH(("An upgrade module references %s, which is not an Upgrade", it->str())); + DEBUG_CRASH(("An upgrade module references '%s', which is not an Upgrade", it->str())); throw INI_INVALID_DATA; } @@ -313,11 +313,12 @@ void UpgradeMuxData::getUpgradeActivationMasks(UpgradeMaskType& activation, Upgr it++) { const UpgradeTemplate* theTemplate = TheUpgradeCenter->findUpgrade( *it ); - if( !theTemplate && !it->isEmpty() && !it->isNone()) + if( !theTemplate ) { - DEBUG_CRASH(("An upgrade module references %s, which is not an Upgrade", it->str())); + DEBUG_CRASH(("An upgrade module references '%s', which is not an Upgrade", it->str())); throw INI_INVALID_DATA; } + m_conflictingMask.set( theTemplate->getUpgradeMask() ); } diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/ClientInstance.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/ClientInstance.cpp index 2ac1ff33988..59edcc3c325 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/ClientInstance.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/ClientInstance.cpp @@ -25,50 +25,59 @@ namespace rts HANDLE ClientInstance::s_mutexHandle = NULL; UnsignedInt ClientInstance::s_instanceIndex = 0; +#if defined(RTS_MULTI_INSTANCE) +Bool ClientInstance::s_isMultiInstance = true; +#else +Bool ClientInstance::s_isMultiInstance = false; +#endif + bool ClientInstance::initialize() { if (isInitialized()) { return true; } - + // Create a mutex with a unique name to Generals in order to determine if our app is already running. // WARNING: DO NOT use this number for any other application except Generals. while (true) { -#if defined(RTS_MULTI_INSTANCE) - std::string guidStr = getFirstInstanceName(); - if (s_instanceIndex > 0u) - { - char idStr[33]; - itoa(s_instanceIndex, idStr, 10); - guidStr.push_back('-'); - guidStr.append(idStr); - } - s_mutexHandle = CreateMutex(NULL, FALSE, guidStr.c_str()); - if (GetLastError() == ERROR_ALREADY_EXISTS) + if (isMultiInstance()) { - if (s_mutexHandle != NULL) + std::string guidStr = getFirstInstanceName(); + if (s_instanceIndex > 0u) + { + char idStr[33]; + itoa(s_instanceIndex, idStr, 10); + guidStr.push_back('-'); + guidStr.append(idStr); + } + s_mutexHandle = CreateMutex(NULL, FALSE, guidStr.c_str()); + if (GetLastError() == ERROR_ALREADY_EXISTS) { - CloseHandle(s_mutexHandle); - s_mutexHandle = NULL; + if (s_mutexHandle != NULL) + { + CloseHandle(s_mutexHandle); + s_mutexHandle = NULL; + } + // Try again with a new instance. + ++s_instanceIndex; + continue; } - // Try again with a new instance. - ++s_instanceIndex; - continue; } -#else - s_mutexHandle = CreateMutex(NULL, FALSE, getFirstInstanceName()); - if (GetLastError() == ERROR_ALREADY_EXISTS) + else { - if (s_mutexHandle != NULL) + s_mutexHandle = CreateMutex(NULL, FALSE, getFirstInstanceName()); + if (GetLastError() == ERROR_ALREADY_EXISTS) { - CloseHandle(s_mutexHandle); - s_mutexHandle = NULL; + if (s_mutexHandle != NULL) + { + CloseHandle(s_mutexHandle); + s_mutexHandle = NULL; + } + return false; } - return false; } -#endif break; } @@ -80,6 +89,31 @@ bool ClientInstance::isInitialized() return s_mutexHandle != NULL; } +bool ClientInstance::isMultiInstance() +{ + return s_isMultiInstance; +} + +void ClientInstance::setMultiInstance(bool v) +{ + if (isInitialized()) + { + DEBUG_CRASH(("ClientInstance::setMultiInstance(%d) - cannot set multi instance after initialization", (int)v)); + return; + } + s_isMultiInstance = v; +} + +void ClientInstance::skipPrimaryInstance() +{ + if (isInitialized()) + { + DEBUG_CRASH(("ClientInstance::skipPrimaryInstance() - cannot skip primary instance after initialization")); + return; + } + s_instanceIndex = 1; +} + UnsignedInt ClientInstance::getInstanceIndex() { DEBUG_ASSERTLOG(isInitialized(), ("ClientInstance::isInitialized() failed")); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarScheme.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarScheme.cpp index 22e49b08fe4..2684160703b 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarScheme.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarScheme.cpp @@ -1004,7 +1004,7 @@ void ControlBarSchemeManager::preloadAssets( TimeOfDay timeOfDay ) if (CBScheme->m_rightHUDImage) { - TheDisplay->preloadTextureAssets(CBScheme->m_buttonQueueImage->getFilename()); + TheDisplay->preloadTextureAssets(CBScheme->m_rightHUDImage->getFilename()); } for (Int layer = 0; layer < MAX_CONTROL_BAR_SCHEME_IMAGE_LAYERS; ++layer) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp index 29d0afddb71..b2c132d0c98 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp @@ -1644,21 +1644,27 @@ void OptionsMenuInit( WindowLayout *layout, void *userData ) // populate resolution modes GadgetComboBoxReset(comboBoxResolution); Int numResolutions = TheDisplay->getDisplayModeCount(); + UnsignedInt displayWidth = TheDisplay->getWidth(); + UnsignedInt displayHeight = TheDisplay->getHeight(); + for( i = 0; i < numResolutions; ++i ) { Int xres,yres,bitDepth; TheDisplay->getDisplayModeDescription(i,&xres,&yres,&bitDepth); str.format(L"%d x %d",xres,yres); GadgetComboBoxAddEntry( comboBoxResolution, str, color); - if (xres == selectedXRes && yres == selectedYRes) + // TheSuperHackers @bugfix xezon 12/06/2025 Now makes a selection with the active display resolution + // instead of the resolution read from the Option Preferences, because the active display resolution + // is the most relevant to make a selection with and the Option Preferences could be wrong. + if ( xres == displayWidth && yres == displayHeight ) selectedResIndex=i; } - if (selectedResIndex == -1) //check if saved mode no longer available + if (selectedResIndex == -1) { // TheSuperHackers @bugfix xezon 08/06/2025 Now adds the current resolution instead of defaulting to 800 x 600. // This avoids force changing the resolution when the user has set a custom resolution in the Option Preferences. - Int xres = TheDisplay->getWidth(); - Int yres = TheDisplay->getHeight(); + Int xres = displayWidth; + Int yres = displayHeight; str.format(L"%d x %d",xres,yres); GadgetComboBoxAddEntry( comboBoxResolution, str, color ); selectedResIndex = GadgetComboBoxGetLength( comboBoxResolution ) - 1; diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupSaveLoad.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupSaveLoad.cpp index 437bc9cdcb7..04703084d59 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupSaveLoad.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupSaveLoad.cpp @@ -791,8 +791,7 @@ WindowMsgHandledType SaveLoadMenuSystem( GameWindow *window, UnsignedInt msg, // save the game AsciiString filename; - if( selectedGameInfo ) - filename = selectedGameInfo->filename; + filename = selectedGameInfo->filename; TheGameState->saveGame( filename, selectedGameInfo->saveGameInfo.description, fileType ); /* diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ReplayMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ReplayMenu.cpp index ab4aa1ad44d..caa355f0c3b 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ReplayMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ReplayMenu.cpp @@ -248,7 +248,7 @@ void PopulateReplayFileListbox(GameWindow *listbox) // time_t totalSeconds = header.endTime - header.startTime; // Int mins = totalSeconds/60; // Int secs = totalSeconds%60; -// Real fps = header.frameDuration/totalSeconds; +// Real fps = header.frameCount/totalSeconds; // extraStr.format(L"%d:%d (%g fps) %hs", mins, secs, fps, header.desyncGame?"OOS ":""); // // for (Int i=0; iupdate(); - } while (TheRecorder->isAnalysisInProgress()); + } while (TheRecorder->isPlaybackInProgress()); } } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp index 786ba75d532..69c2c46fde6 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp @@ -67,6 +67,7 @@ #include "Common/PlayerTemplate.h" #include "Common/RandomValue.h" #include "Common/Recorder.h" +#include "Common/ReplaySimulation.h" #include "Common/ScoreKeeper.h" #include "Common/SkirmishBattleHonors.h" #include "Common/ThingFactory.h" @@ -503,6 +504,11 @@ WindowMsgHandledType ScoreScreenInput( GameWindow *window, UnsignedInt msg, } // end MainMenuInput +static Bool showButtonContinue() +{ + return ReplaySimulation::getCurrentReplayIndex() != ReplaySimulation::getReplayCount()-1; +} + /** System Function for the ScoreScreen */ //------------------------------------------------------------------------------------------------- WindowMsgHandledType ScoreScreenSystem( GameWindow *window, UnsignedInt msg, @@ -541,23 +547,36 @@ WindowMsgHandledType ScoreScreenSystem( GameWindow *window, UnsignedInt msg, { TheShell->pop(); TheCampaignManager->setCampaign(AsciiString::TheEmptyString); + + if ( ReplaySimulation::getReplayCount() > 0 ) + { + ReplaySimulation::stop(); + TheGameEngine->setQuitting(TRUE); + } } else if ( controlID == buttonContinueID ) { - if(!buttonIsFinishCampaign) - ReplayWasPressed = TRUE; - if( screenType == SCORESCREEN_SINGLEPLAYER) + if( ReplaySimulation::getReplayCount() > 0 ) { - AsciiString mapName = TheCampaignManager->getCurrentMap(); - - if( mapName.isEmpty() ) - { - ReplayWasPressed = FALSE; - TheShell->pop(); - } - else + TheGameEngine->setQuitting(TRUE); + } + else + { + if(!buttonIsFinishCampaign) + ReplayWasPressed = TRUE; + if( screenType == SCORESCREEN_SINGLEPLAYER) { - CheckForCDAtGameStart( startNextCampaignGame ); + AsciiString mapName = TheCampaignManager->getCurrentMap(); + + if( mapName.isEmpty() ) + { + ReplayWasPressed = FALSE; + TheShell->pop(); + } + else + { + CheckForCDAtGameStart( startNextCampaignGame ); + } } } } @@ -984,7 +1003,7 @@ void initReplaySinglePlayer( void ) if (chatBoxBorder) chatBoxBorder->winHide(TRUE); if (buttonContinue) - buttonContinue->winHide(TRUE); + buttonContinue->winHide(!showButtonContinue()); if (buttonBuddies) buttonBuddies->winHide(TRUE); if (listboxChatWindowScoreScreen) @@ -1091,7 +1110,7 @@ void initReplayMultiPlayer(void) if (chatBoxBorder) chatBoxBorder->winHide(TRUE); if (buttonContinue) - buttonContinue->winHide(TRUE); + buttonContinue->winHide(!showButtonContinue()); if (buttonBuddies) buttonBuddies->winHide(TRUE); // if (buttonRehost) @@ -1393,7 +1412,7 @@ static inline int CheckForApocalypse( ScoreKeeper *s, const char* szWeapon ) //------------------------------------------------------------------------------------------------- void populatePlayerInfo( Player *player, Int pos) { - if(!player || pos > MAX_SLOTS) + if(!player || pos < 0 || pos >= MAX_SLOTS) return; Color color = player->getPlayerColor(); ScoreKeeper *scoreKpr = player->getScoreKeeper(); @@ -2145,20 +2164,23 @@ void grabSinglePlayerInfo( void ) { Bool isFriend = TRUE; - // set the string we'll be compairing to + // set the string we'll be comparing to switch (j) { case USA_ENEMY: isFriend = FALSE; + FALLTHROUGH; case USA_FRIEND: side.set("USA"); break; case CHINA_ENEMY: isFriend = FALSE; + FALLTHROUGH; case CHINA_FRIEND: side.set("China"); break; case GLA_ENEMY: - isFriend = FALSE; + isFriend = FALSE; + FALLTHROUGH; case GLA_FRIEND: side.set("GLA"); break; @@ -2317,7 +2339,7 @@ winName.format("ScoreScreen.wnd:StaticTextScore%d", i); //------------------------------------------------------------------------------------------------- void setObserverWindows( Player *player, Int i ) { - if(i < 0 || i >= MAX_SLOTS) + if(!player || i < 0 || i >= MAX_SLOTS) return; AsciiString winName; GameWindow *win; @@ -2328,16 +2350,10 @@ void setObserverWindows( Player *player, Int i ) winName.format("ScoreScreen.wnd:StaticTextPlayer%d", i); win = TheWindowManager->winGetWindowFromId( parent, TheNameKeyGenerator->nameToKey( winName ) ); DEBUG_ASSERTCRASH(win,("Could not find window %s on the score screen", winName.str())); - if (player) - { - GadgetStaticTextSetText(win, player->getPlayerDisplayName()); - win->winHide(FALSE); - win->winSetEnabledTextColors(color, win->winGetEnabledTextBorderColor()); - } - else - { - win->winHide(TRUE); - } + + GadgetStaticTextSetText(win, player->getPlayerDisplayName()); + win->winHide(FALSE); + win->winSetEnabledTextColors(color, win->winGetEnabledTextBorderColor()); // set the player name winName.format("ScoreScreen.wnd:StaticTextObserver%d", i); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp index 760fbb362cf..c727c3d6684 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp @@ -585,14 +585,18 @@ void PopulateLobbyPlayerListbox(void) // restore selection if (indicesToSelect.size()) { - std::set::const_iterator indexIt; - Int *newIndices = NEW Int[indicesToSelect.size()]; - for (i=0, indexIt = indicesToSelect.begin(); indexIt != indicesToSelect.end(); ++i, ++indexIt) + std::set::const_iterator indexIt = indicesToSelect.begin(); + const size_t count = indicesToSelect.size(); + size_t index = 0; + Int *newIndices = NEW Int[count]; + while (index < count) { - newIndices[i] = *indexIt; + newIndices[index] = *indexIt; DEBUG_LOG(("Queueing up index %d to re-select\n", *indexIt)); + ++index; + ++indexIt; } - GadgetListBoxSetSelected(listboxLobbyPlayers, newIndices, indicesToSelect.size()); + GadgetListBoxSetSelected(listboxLobbyPlayers, newIndices, count); delete[] newIndices; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp index 9a2176e1ece..4c22f9c081b 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp @@ -1629,7 +1629,7 @@ static GameWindow *createGadget( char *type, WinInstanceData *instData, void *data ) { - GameWindow *window; + GameWindow *window = NULL; instData->m_owner = parent; diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowTransitionsStyles.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowTransitionsStyles.cpp index f9795b2e5e0..20ea8ea96a7 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowTransitionsStyles.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowTransitionsStyles.cpp @@ -142,6 +142,8 @@ void FlashTransition::update( Int frame ) } // end if } + FALLTHROUGH; + case FLASHTRANSITION_FADE_IN_2: case FLASHTRANSITION_FADE_IN_3: { @@ -810,8 +812,8 @@ void ScaleUpTransition::update( Int frame ) TheAudio->addAudioEvent( &buttonClick ); } // end if - } + FALLTHROUGH; case SCALEUPTRANSITION_2: case SCALEUPTRANSITION_3: @@ -933,8 +935,8 @@ void ScoreScaleUpTransition::update( Int frame ) TheAudio->addAudioEvent( &buttonClick ); } // end if - } + FALLTHROUGH; case SCORESCALEUPTRANSITION_2: case SCORESCALEUPTRANSITION_3: diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/LoadScreen.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/LoadScreen.cpp index 3ceaf51bd7b..b3434558440 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/LoadScreen.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/LoadScreen.cpp @@ -1476,6 +1476,7 @@ void MultiPlayerLoadScreen::processProgress(Int playerId, Int percentage) if( percentage < 0 || percentage > 100 || playerId >= MAX_SLOTS || playerId < 0 || m_playerLookup[playerId] == -1) { DEBUG_ASSERTCRASH(FALSE, ("Percentage %d was passed in for Player %d\n", percentage, playerId)); + return; } //DEBUG_LOG(("Percentage %d was passed in for Player %d (in loadscreen position %d)\n", percentage, playerId, m_playerLookup[playerId])); if(m_progressBars[m_playerLookup[playerId]]) @@ -1843,6 +1844,7 @@ void GameSpyLoadScreen::processProgress(Int playerId, Int percentage) if( percentage < 0 || percentage > 100 || playerId >= MAX_SLOTS || playerId < 0 || m_playerLookup[playerId] == -1) { DEBUG_ASSERTCRASH(FALSE, ("Percentage %d was passed in for Player %d\n", percentage, playerId)); + return; } //DEBUG_LOG(("Percentage %d was passed in for Player %d (in loadscreen position %d)\n", percentage, playerId, m_playerLookup[playerId])); if(m_progressBars[m_playerLookup[playerId]]) @@ -1995,6 +1997,7 @@ void MapTransferLoadScreen::processProgress(Int playerId, Int percentage, AsciiS if( percentage < 0 || percentage > 100 || playerId >= MAX_SLOTS || playerId < 0 || m_playerLookup[playerId] == -1) { DEBUG_ASSERTCRASH(FALSE, ("Percentage %d was passed in for Player %d\n", percentage, playerId)); + return; } if (m_oldProgress[playerId] == percentage) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp index ea50ed4e8fd..0b7148098c2 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp @@ -410,7 +410,7 @@ void Shell::showShell( Bool runInit ) { DEBUG_LOG(("Shell:showShell() - %s (%s)\n", TheGlobalData->m_initialFile.str(), (top())?top()->getFilename().str():"no top screen")); - if(!TheGlobalData->m_initialFile.isEmpty()) + if(!TheGlobalData->m_initialFile.isEmpty() || !TheGlobalData->m_simulateReplays.empty()) { return; } @@ -472,7 +472,7 @@ void Shell::showShell( Bool runInit ) void Shell::showShellMap(Bool useShellMap ) { // we don't want any of this to show if we're loading straight into a file - if(TheGlobalData->m_initialFile.isNotEmpty() || !TheGameLogic ) + if (TheGlobalData->m_initialFile.isNotEmpty() || !TheGameLogic || !TheGlobalData->m_simulateReplays.empty()) return; if(useShellMap && TheGlobalData->m_shellMapOn) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp index 8c6e4076c87..c072e3d0d03 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -538,7 +538,7 @@ void GameClient::update( void ) //Initial Game Codition. We must show the movie first and then we can display the shell if(TheGlobalData->m_afterIntro && !TheDisplay->isMoviePlaying()) { - if( playSizzle && TheGlobalData->m_playSizzle && !TheGlobalData->m_headless )// Remove headless-check with Replay Simulation PR + if( playSizzle && TheGlobalData->m_playSizzle ) { TheWritableGlobalData->m_allowExitOutOfMovies = TRUE; if(TheGameLODManager && TheGameLODManager->didMemPass()) @@ -629,11 +629,8 @@ void GameClient::update( void ) if(TheGlobalData->m_playIntro || TheGlobalData->m_afterIntro) { // redraw all views, update the GUI - if (!TheGlobalData->m_headless)// Remove headless-check with Replay Simulation PR - { - TheDisplay->DRAW(); - TheDisplay->UPDATE(); - } + TheDisplay->DRAW(); + TheDisplay->UPDATE(); return; } @@ -751,12 +748,10 @@ void GameClient::update( void ) } // update display - if (!TheGlobalData->m_headless)// Remove headless-check with Replay Simulation PR { TheDisplay->UPDATE(); } - if (!TheGlobalData->m_headless)// Remove headless-check with Replay Simulation PR { USE_PERF_TIMER(GameClient_draw) @@ -782,6 +777,18 @@ void GameClient::update( void ) } } // end update +void GameClient::updateHeadless() +{ + // TheSuperHackers @info helmutbuhler 03/05/2025 + // When we play a replay back in headless mode, we want to skip the update of GameClient + // because it's not necessary for CRC checking. + // But we do reset the particles. The problem is that particles can be generated during + // GameLogic and are only cleaned up during rendering. If we don't clean this up here, + // the particles accumulate and slow things down a lot and can even cause a crash on + // longer replays. + TheParticleSystemManager->reset(); +} + /** ----------------------------------------------------------------------------------------------- * Call the given callback function for each object contained within the given region. */ diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GameText.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GameText.cpp index 9b6e207f9e5..163018fbd2e 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GameText.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GameText.cpp @@ -630,6 +630,7 @@ void GameTextManager::readToEndOfQuote( File *file, Char *in, Char *out, Char *w } state = 1; + FALLTHROUGH; case 1: if ( ( ch >= 'a' && ch <= 'z') || ( ch >= 'A' && ch <='Z') || (ch >= '0' && ch <= '9') || ch == '_' ) { @@ -638,6 +639,7 @@ void GameTextManager::readToEndOfQuote( File *file, Char *in, Char *out, Char *w break; } state = 2; + FALLTHROUGH; case 2: break; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index 69cd51cf2f1..2ed0568bde8 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp @@ -2201,7 +2201,7 @@ GameMessage::Type CommandTranslator::evaluateContextCommand( Drawable *draw, } // end else if #endif // ******************************************************************************************** - else if ( pos && !draw && TheInGameUI->canSelectedObjectsDoAction( InGameUI::ACTIONTYPE_SET_RALLY_POINT, NULL, InGameUI::SELECTION_ALL, FALSE )) + else if ( !draw && TheInGameUI->canSelectedObjectsDoAction( InGameUI::ACTIONTYPE_SET_RALLY_POINT, NULL, InGameUI::SELECTION_ALL, FALSE )) { msgType = GameMessage::MSG_SET_RALLY_POINT; @@ -3704,7 +3704,7 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage break; } - //intentional fall through + FALLTHROUGH; //intentional fall through } case GameMessage::MSG_MOUSE_RIGHT_CLICK: { @@ -3767,7 +3767,7 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage break; } - //intentional fall through + FALLTHROUGH; //intentional fall through } case GameMessage::MSG_MOUSE_LEFT_CLICK: { diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/PlaceEventTranslator.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/PlaceEventTranslator.cpp index 16ba5e79e6f..6c750071fed 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/PlaceEventTranslator.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/PlaceEventTranslator.cpp @@ -181,11 +181,7 @@ GameMessageDisposition PlaceEventTranslator::translateGameMessage(const GameMess // translate the screen position of start to world target location TheTacticalView->screenToTerrain( &anchorStart, &world ); - // get the source object ID of the thing that is "building" the object - ObjectID builderID = INVALID_ID; Object *builderObj = TheGameLogic->findObjectByID( TheInGameUI->getPendingPlaceSourceObjectID() ); - if( builderObj ) - builderID = builderObj->getID(); //Kris: September 27, 2002 //Make sure we have enough CASH to build it! It's possible that between the @@ -220,6 +216,8 @@ GameMessageDisposition PlaceEventTranslator::translateGameMessage(const GameMess break; } + DEBUG_ASSERTCRASH(builderObj != NULL, ("builderObj is NULL")); + // check to see if this is a legal location to build something at LegalBuildCode lbc; lbc = TheBuildAssistant->isLocationLegalToBuild( &world, diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/WindowXlat.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/WindowXlat.cpp index cce35480d41..8a934a8f7ef 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/WindowXlat.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/WindowXlat.cpp @@ -222,7 +222,7 @@ GameMessageDisposition WindowTranslator::translateGameMessage(const GameMessage //If we release the button outside forceKeepMessage = TRUE; } - //FALL THROUGH INTENTIONALLY! + FALLTHROUGH; //FALL THROUGH INTENTIONALLY! } case GameMessage::MSG_RAW_MOUSE_POSITION: case GameMessage::MSG_RAW_MOUSE_LEFT_BUTTON_DOWN: diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AI.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AI.cpp index 2900f19d9f1..d1fa826a4d5 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AI.cpp @@ -430,11 +430,10 @@ void AI::parseAiDataDefinition( INI* ini ) if( ini->getLoadType() == INI_LOAD_CREATE_OVERRIDES ) TheAI->newOverride(); - } // end if - - // parse the ini weapon definition - ini->initFromINI( TheAI->m_aiData, TheAIFieldParseTable ); + // parse the ini weapon definition + ini->initFromINI( TheAI->m_aiData, TheAIFieldParseTable ); + } // end if } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp index 700012ac947..388de4fbde4 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp @@ -2301,7 +2301,7 @@ void AIPlayer::repairStructure(ObjectID structure) return; } } - if (m_structuresInQueue==MAX_STRUCTURES_TO_REPAIR) { + if (m_structuresInQueue>=MAX_STRUCTURES_TO_REPAIR) { DEBUG_LOG(("Structure repair queue is full, ignoring repair request. JBA\n")); return; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp index 5e1dc0fb657..a0878a76b43 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp @@ -97,6 +97,10 @@ void PolygonTrigger::reallocate(void) { DEBUG_ASSERTCRASH(m_numPoints <= m_sizePoints, ("Invalid m_numPoints.")); if (m_numPoints == m_sizePoints) { + if (m_sizePoints > INT_MAX / 2) { + DEBUG_CRASH(("Too many points to allocate.")); + return; + } // Reallocate. m_sizePoints += m_sizePoints; ICoord3D *newPts = NEW ICoord3D[m_sizePoints]; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeScaffoldBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeScaffoldBehavior.cpp index 4ea04722caa..b7c9b6ada10 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeScaffoldBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeScaffoldBehavior.cpp @@ -192,6 +192,10 @@ UpdateSleepTime BridgeScaffoldBehavior::update( void ) end = &m_riseToPos; break; + default: + DEBUG_CRASH(("Unhandled case in BridgeScaffoldBehavior::update()")); + return UPDATE_SLEEP_NONE; + } // end switch // adjust speed so it's slower at the end of motion diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp index 0be6e01aa8e..b822e26a3cf 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp @@ -166,7 +166,7 @@ Bool CrateCollide::isValidToExecute( const Object *other ) const return FALSE; // must match our kindof flags (if any) - if (md && !other->isKindOfMulti(md->m_kindof, md->m_kindofnot)) + if ( !other->isKindOfMulti(md->m_kindof, md->m_kindofnot) ) return FALSE; if( other->isEffectivelyDead() ) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Damage/TransitionDamageFX.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Damage/TransitionDamageFX.cpp index dd4a8fb0a93..9e1a2bb3485 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Damage/TransitionDamageFX.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Damage/TransitionDamageFX.cpp @@ -366,7 +366,7 @@ void TransitionDamageFX::onBodyDamageStateChange( const DamageInfo* damageInfo, } // end if // do any object creation list for our new state - if( modData->m_OCL[ newState ][ i ].ocl ) + if( damageSource && modData->m_OCL[ newState ][ i ].ocl ) { if( lastDamageInfo == NULL || diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp index c7e6d4b5a0c..6a08638e180 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp @@ -2043,7 +2043,6 @@ void PartitionData::updateCellsTouched() Object *obj = getObject(); - DEBUG_ASSERTCRASH(obj != NULL || m_ghostObject != NULL, ("must be attached to an Object here 1")); if (obj) { @@ -2064,6 +2063,11 @@ void PartitionData::updateCellsTouched() majorRadius = m_ghostObject->getGeometryMajorRadius(); minorRadius = m_ghostObject->getGeometryMinorRadius(); } + else + { + DEBUG_CRASH(("must be attached to an Object here")); + return; + } removeAllTouchedCells(); if (isSmall) @@ -2169,14 +2173,21 @@ Int PartitionData::calcMaxCoiForShape(GeometryType geom, Real majorRadius, Real // this actually allocates a few too many, but that's ok. Int cells = ThePartitionManager->worldToCellDist(majorRadius*2) + 1; result = cells * cells; +#if !RETAIL_COMPATIBLE_CRC + break; +#endif } case GEOMETRY_BOX: { Real diagonal = (Real)(sqrtf(majorRadius*majorRadius + minorRadius*minorRadius)); Int cells = ThePartitionManager->worldToCellDist(diagonal*2) + 1; result = cells * cells; + break; } + default: + return 4; }; + static_assert(GEOMETRY_NUM_TYPES == 3, "GEOMETRY_NUM_TYPES has changed"); } if (result < 4) result = 4; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/HackInternetAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/HackInternetAIUpdate.cpp index 0b1f90090a0..4599b4b94c0 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/HackInternetAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/HackInternetAIUpdate.cpp @@ -506,28 +506,28 @@ StateReturnType HackInternetState::update() { break; } - //If entry missing, fall through! + FALLTHROUGH; //If entry missing, fall through! case LEVEL_ELITE: amount = ai->getEliteCashAmount(); if( amount ) { break; } - //If entry missing, fall through! + FALLTHROUGH; //If entry missing, fall through! case LEVEL_VETERAN: amount = ai->getVeteranCashAmount(); if( amount ) { break; } - //If entry missing, fall through! + FALLTHROUGH; //If entry missing, fall through! case LEVEL_REGULAR: amount = ai->getRegularCashAmount(); if( amount ) { break; } - //If entry missing, fall through! + FALLTHROUGH; //If entry missing, fall through! default: amount = 1; break; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp index cca2a49e8d7..b37edd7dc80 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp @@ -2450,7 +2450,7 @@ void JetAIUpdate::aiDoCommand(const AICommandParms* parms) if (isParkedAt(parms->m_obj)) return; - // else fall thru to the default case! + FALLTHROUGH; // else fall thru to the default case! default: { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp index 657593267d9..8975acb9d5a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp @@ -707,6 +707,8 @@ UpdateSleepTime MissileAIUpdate::update() { break; } + FALLTHROUGH; + case IGNITION: doIgnitionState(); break; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/BoneFXUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/BoneFXUpdate.cpp index c7d817b5d75..ad8422c4760 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/BoneFXUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/BoneFXUpdate.cpp @@ -511,9 +511,6 @@ void BoneFXUpdate::resolveBoneLocations() { Drawable *drawable = building->getDrawable(); if (drawable == NULL) { DEBUG_ASSERTCRASH(drawable != NULL, ("There is no drawable?")); - } - - if (d == NULL) { return; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp index 0873b59b777..c155714faec 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp @@ -198,8 +198,8 @@ void EMPUpdate::doDisableAttack( void ) } } - SimpleObjectIterator *iter; - Object *curVictim; + SimpleObjectIterator *iter = NULL; + Object *curVictim = NULL; if (radius > 0.0f) { @@ -499,8 +499,8 @@ void LeafletDropBehavior::doDisableAttack( void ) Real curVictimDistSqr; const Coord3D *pos = object->getPosition(); - SimpleObjectIterator *iter; - Object *curVictim; + SimpleObjectIterator *iter = NULL; + Object *curVictim = NULL; if (radius > 0.0f) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp index dff994e3d9d..91e4a0b7f1c 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp @@ -1067,8 +1067,10 @@ void PhysicsBehavior::addOverlap(Object *obj) void PhysicsBehavior::transferVelocityTo(PhysicsBehavior* that) const { if (that != NULL) + { that->m_vel.add(&m_vel); - that->m_velMag = INVALID_VEL_MAG; + that->m_velMag = INVALID_VEL_MAG; + } } //------------------------------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpecialAbilityUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpecialAbilityUpdate.cpp index f38cf423b8c..b089734ad9d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpecialAbilityUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpecialAbilityUpdate.cpp @@ -280,7 +280,7 @@ UpdateSleepTime SpecialAbilityUpdate::update( void ) // it's been captured by a colleague! we should stop. shouldAbort = TRUE; } - //deliberately falling through... + FALLTHROUGH; //deliberately falling through... } case SPECIAL_BLACKLOTUS_STEAL_CASH_HACK: case SPECIAL_BOOBY_TRAP: @@ -309,7 +309,7 @@ UpdateSleepTime SpecialAbilityUpdate::update( void ) { if ( target->isKindOf( KINDOF_STRUCTURE ) ) shouldAbort = TRUE; - //deliberately falling through + FALLTHROUGH; //deliberately falling through } case SPECIAL_BLACKLOTUS_DISABLE_VEHICLE_HACK: { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp index 2686ad2798a..8342e12133c 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp @@ -580,10 +580,16 @@ Real WeaponTemplate::estimateWeaponTemplateDamage( return 0.0f; } + const Real damageAmount = getPrimaryDamage(bonus); + if ( victimObj == NULL ) + { + return damageAmount; + } + DamageType damageType = getDamageType(); DeathType deathType = getDeathType(); - if (victimObj && victimObj->isKindOf(KINDOF_SHRUBBERY)) + if ( victimObj->isKindOf(KINDOF_SHRUBBERY) ) { if (deathType == DEATH_BURNED) { @@ -610,7 +616,7 @@ Real WeaponTemplate::estimateWeaponTemplateDamage( - if (damageType == DAMAGE_SURRENDER || m_allowAttackGarrisonedBldgs) + if ( damageType == DAMAGE_SURRENDER || m_allowAttackGarrisonedBldgs ) { ContainModuleInterface* contain = victimObj->getContain(); if( contain && contain->getContainCount() > 0 && contain->isGarrisonable() && !contain->isImmuneToClearBuildingAttacks() ) @@ -620,46 +626,29 @@ Real WeaponTemplate::estimateWeaponTemplateDamage( } } - if( victimObj ) + if( damageType == DAMAGE_DISARM ) { - if( damageType == DAMAGE_DISARM ) - { - if( victimObj->isKindOf( KINDOF_MINE ) || victimObj->isKindOf( KINDOF_BOOBY_TRAP ) || victimObj->isKindOf( KINDOF_DEMOTRAP ) ) - { - // this is just a nonzero value, to ensure we can target mines with disarm weapons, regardless... - return 1.0f; - } - return 0.0f; - } - if( damageType == DAMAGE_DEPLOY && !victimObj->isAirborneTarget() ) + if( victimObj->isKindOf( KINDOF_MINE ) || victimObj->isKindOf( KINDOF_BOOBY_TRAP ) || victimObj->isKindOf( KINDOF_DEMOTRAP ) ) { + // this is just a nonzero value, to ensure we can target mines with disarm weapons, regardless... return 1.0f; } + return 0.0f; + } + if( damageType == DAMAGE_DEPLOY && !victimObj->isAirborneTarget() ) + { + return 1.0f; } //@todo Kris need to examine the DAMAGE_HACK type for damage estimation purposes. //Likely this damage type will have threat implications that won't properly be dealt with until resolved. -// const Coord3D* sourcePos = sourceObj->getPosition(); - if (victimPos == NULL) - { - victimPos = victimObj->getPosition(); - } - - Real damageAmount = getPrimaryDamage(bonus); - if (victimObj == NULL) - { - return damageAmount; - } - else - { - DamageInfoInput damageInfo; - damageInfo.m_damageType = damageType; - damageInfo.m_deathType = deathType; - damageInfo.m_sourceID = sourceObj->getID(); - damageInfo.m_amount = damageAmount; - return victimObj->estimateDamage(damageInfo); - } + DamageInfoInput damageInfo; + damageInfo.m_damageType = damageType; + damageInfo.m_deathType = deathType; + damageInfo.m_sourceID = sourceObj->getID(); + damageInfo.m_amount = damageAmount; + return victimObj->estimateDamage(damageInfo); } //------------------------------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp index b91f81e880f..bdff64698ad 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp @@ -2628,6 +2628,7 @@ void ScriptActions::doCameoFlash(const AsciiString& name, Int timeInSeconds) if( button == NULL ) { DEBUG_CRASH(( "ScriptActions::doCameoFlash can't find AsciiString cameoflash" )); + return; } Int frames = LOGICFRAMES_PER_SECOND * timeInSeconds; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/Scripts.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/Scripts.cpp index 90ec1bdcd23..c90e2a4acb7 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/Scripts.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/Scripts.cpp @@ -1837,7 +1837,7 @@ void Parameter::qualify(const AsciiString& qualifier, if (m_string == THIS_TEAM) { break; } - /// otherwise drop down & qualify. + FALLTHROUGH; /// otherwise drop down & qualify. case SCRIPT: case COUNTER: case FLAG: diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index dfc19096551..7c3b1e3dcf8 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -302,7 +302,7 @@ void GameLogic::setDefaults( Bool loadingSaveGame ) Bool GameLogic::isInSinglePlayerGame( void ) { return (m_gameMode == GAME_SINGLE_PLAYER || - (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_PLAYBACK && TheRecorder->getGameMode() == GAME_SINGLE_PLAYER)); + (TheRecorder && TheRecorder->isPlaybackMode() && TheRecorder->getGameMode() == GAME_SINGLE_PLAYER)); } @@ -839,9 +839,11 @@ static void populateRandomStartPosition( GameInfo *game ) Int i; Int numPlayers = MAX_SLOTS; const MapMetaData *md = TheMapCache->findMap( game->getMap() ); - DEBUG_ASSERTCRASH( md , ("Could not find map %s in the mapcache", game->getMap().str())); if (md) numPlayers = md->m_numPlayers; + else + printf("Could not find map \"%s\"\n", game->getMap().str()); + DEBUG_ASSERTCRASH( md , ("Could not find map %s in the mapcache", game->getMap().str())); // generate a map of start spot distances Real startSpotDistance[MAX_SLOTS][MAX_SLOTS]; @@ -1228,7 +1230,7 @@ void GameLogic::startNewGame( Bool loadingSaveGame ) } else { - if (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_PLAYBACK) + if (TheRecorder && TheRecorder->isPlaybackMode()) { TheGameInfo = game = TheRecorder->getGameInfo(); } @@ -1292,7 +1294,7 @@ void GameLogic::startNewGame( Bool loadingSaveGame ) //****************************// // Get the m_loadScreen for this kind of game - if(!m_loadScreen && !TheGlobalData->m_headless) + if(!m_loadScreen && !(TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_SIMULATION_PLAYBACK)) { m_loadScreen = getLoadScreen( loadingSaveGame ); if(m_loadScreen) @@ -2218,7 +2220,7 @@ void GameLogic::startNewGame( Bool loadingSaveGame ) } // if we're in a load game, don't fade yet - if(loadingSaveGame == FALSE && TheTransitionHandler != NULL) + if(loadingSaveGame == FALSE && TheTransitionHandler != NULL && m_loadScreen) { TheTransitionHandler->setGroup("FadeWholeScreen"); while(!TheTransitionHandler->isFinished()) @@ -3706,20 +3708,21 @@ void GameLogic::update( void ) if (generateForSolo || generateForMP) { m_CRC = getCRC( CRC_RECALC ); - if (isMPGameOrReplay) - { - GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_LOGIC_CRC ); - msg->appendIntegerArgument( m_CRC ); - msg->appendBooleanArgument( (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_PLAYBACK) ); // playback CRC - DEBUG_LOG(("Appended CRC on frame %d: %8.8X\n", m_frame, m_CRC)); - } - else - { - GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_LOGIC_CRC ); - msg->appendIntegerArgument( m_CRC ); - msg->appendBooleanArgument( (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_PLAYBACK) ); // playback CRC - DEBUG_LOG(("Appended Playback CRC on frame %d: %8.8X\n", m_frame, m_CRC)); - } + bool isPlayback = (TheRecorder && TheRecorder->isPlaybackMode()); + + GameMessage *msg = newInstance(GameMessage)(GameMessage::MSG_LOGIC_CRC); + msg->appendIntegerArgument(m_CRC); + msg->appendBooleanArgument(isPlayback); + + // TheSuperHackers @info helmutbuhler 13/04/2025 + // During replay simulation, we bypass TheMessageStream and instead put the CRC message + // directly into TheCommandList because we don't update TheMessageStream during simulation. + GameMessageList *messageList = TheMessageStream; + if (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_SIMULATION_PLAYBACK) + messageList = TheCommandList; + messageList->appendMessage(msg); + + DEBUG_LOG(("Appended %sCRC on frame %d: %8.8X\n", isPlayback ? "Playback " : "", m_frame, m_CRC)); } // collect stats diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index 7003423f037..75ad17edb34 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -1827,7 +1827,7 @@ void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData ) // -------------------------------------------------------------------------------------------- case GameMessage::MSG_SET_REPLAY_CAMERA: { - if (TheRecorder->getMode() == RECORDERMODETYPE_PLAYBACK && TheGlobalData->m_useCameraInReplay && TheControlBar->getObserverLookAtPlayer() == thisPlayer) + if (TheRecorder->isPlaybackMode() && TheGlobalData->m_useCameraInReplay && TheControlBar->getObserverLookAtPlayer() == thisPlayer) { if (TheTacticalView->isCameraMovementFinished()) { @@ -1964,7 +1964,7 @@ void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData ) //thisPlayer->getPlayerDisplayName().str(), m_frame)); m_cachedCRCs[msg->getPlayerIndex()] = newCRC; // to mask problem: = (oldCRC < newCRC)?newCRC:oldCRC; } - else if (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_PLAYBACK) + else if (TheRecorder && TheRecorder->isPlaybackMode()) { UnsignedInt newCRC = msg->getArgument(0)->integer; //DEBUG_LOG(("Saw CRC of %X from player %d. Our CRC is %X. Arg count is %d\n", @@ -1994,7 +1994,7 @@ void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData ) } // end switch /**/ /// @todo: multiplayer semantics - if (currentlySelectedGroup && TheRecorder->getMode() == RECORDERMODETYPE_PLAYBACK && TheGlobalData->m_useCameraInReplay && TheControlBar->getObserverLookAtPlayer() == thisPlayer /*&& !TheRecorder->isMultiplayer()*/) + if (currentlySelectedGroup && TheRecorder->isPlaybackMode() && TheGlobalData->m_useCameraInReplay && TheControlBar->getObserverLookAtPlayer() == thisPlayer /*&& !TheRecorder->isMultiplayer()*/) { const VecObjectID& selectedObjects = currentlySelectedGroup->getAllIDs(); TheInGameUI->deselectAllDrawables(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/DisconnectManager.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/DisconnectManager.cpp index bbecd950156..632c92dc3e2 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/DisconnectManager.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/DisconnectManager.cpp @@ -139,10 +139,13 @@ void DisconnectManager::update(ConnectionManager *conMgr) { //use next ping server static size_t serverIndex = 0; serverIndex++; - if( serverIndex >= TheGameSpyConfig->getPingServers().size() ) + + AsciiStringList pingServers = TheGameSpyConfig->getPingServers(); + + if( serverIndex >= pingServers.size() ) serverIndex = 0; //wrap back to first ping server - std::list::iterator it = TheGameSpyConfig->getPingServers().begin(); + std::list::iterator it = pingServers.begin(); for( size_t i = 0; i < serverIndex; i++ ) it++; diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GUIUtil.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GUIUtil.cpp index 6ecd086d4f2..409a8d47ff0 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GUIUtil.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GUIUtil.cpp @@ -393,13 +393,14 @@ void UpdateSlotList( GameInfo *myGame, GameWindow *comboPlayer[], for( int i =0; i < MAX_SLOTS; i++ ) { GameSlot * slot = myGame->getSlot(i); + // if i'm host, enable the controls for AI - if(myGame->amIHost() && slot && slot->isAI()) + if(myGame->amIHost() && slot->isAI()) { EnableAcceptControls(TRUE, myGame, comboPlayer, comboColor, comboPlayerTemplate, comboTeam, buttonAccept, buttonStart, buttonMapStartPosition, i); } - else if (slot && myGame->getLocalSlotNum() == i) + else if (myGame->getLocalSlotNum() == i) { if(slot->isAccepted() && !myGame->amIHost()) { @@ -425,7 +426,7 @@ void UpdateSlotList( GameInfo *myGame, GameWindow *comboPlayer[], EnableAcceptControls(FALSE, myGame, comboPlayer, comboColor, comboPlayerTemplate, comboTeam, buttonAccept, buttonStart, buttonMapStartPosition, i); } - if(slot && slot->isHuman()) + if(slot->isHuman()) { UnicodeString newName = slot->getName(); UnicodeString oldName = GadgetComboBoxGetText(comboPlayer[i]); diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GameInfo.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GameInfo.cpp index 8d6b22755f5..3c86422d6f7 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GameInfo.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GameInfo.cpp @@ -454,6 +454,7 @@ GameSlot* GameInfo::getSlot( Int slotNum ) if (slotNum < 0 || slotNum >= MAX_SLOTS) return NULL; + DEBUG_ASSERTCRASH( m_slot[slotNum], ("NULL slot pointer") ); return m_slot[slotNum]; } @@ -463,6 +464,7 @@ const GameSlot* GameInfo::getConstSlot( Int slotNum ) const if (slotNum < 0 || slotNum >= MAX_SLOTS) return NULL; + DEBUG_ASSERTCRASH( m_slot[slotNum], ("NULL slot pointer") ); return m_slot[slotNum]; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GameSpy/StagingRoomGameInfo.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GameSpy/StagingRoomGameInfo.cpp index 514076fc435..85a01bcc33d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GameSpy/StagingRoomGameInfo.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GameSpy/StagingRoomGameInfo.cpp @@ -57,8 +57,8 @@ // GameSpyGameSlot ------------------------------------------- GameSpyGameSlot::GameSpyGameSlot() + : GameSlot() { - GameSlot(); m_gameSpyLogin.clear(); m_gameSpyLocale.clear(); m_profileID = 0; diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/IPEnumeration.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/IPEnumeration.cpp index f485c603417..0d0a43018fe 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/IPEnumeration.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/IPEnumeration.cpp @@ -97,15 +97,16 @@ EnumeratedIP * IPEnumeration::getAddresses( void ) return NULL; } -#if defined(RTS_MULTI_INSTANCE) // TheSuperHackers @feature Add one unique local host IP address for each multi client instance. - const UnsignedInt id = rts::ClientInstance::getInstanceId(); - addNewIP( - 127, - (UnsignedByte)(id >> 16), - (UnsignedByte)(id >> 8), - (UnsignedByte)(id)); -#endif + if (rts::ClientInstance::isMultiInstance()) + { + const UnsignedInt id = rts::ClientInstance::getInstanceId(); + addNewIP( + 127, + (UnsignedByte)(id >> 16), + (UnsignedByte)(id >> 8), + (UnsignedByte)(id)); + } // construct a list of addresses int numAddresses = 0; diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp index 269a56b0336..28f7c332368 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp @@ -234,7 +234,12 @@ void W3DTankDraw::updateTreadPositions(Real uvDelta) else if (pTread->m_type == TREAD_RIGHT) //this tread needs to scroll backwards offset_u = pTread->m_materialSettings.customUVOffset.X - uvDelta; - + else + { + DEBUG_CRASH(("Unhandled case in W3DTankDraw::updateTreadPositions")); + offset_u = 0.0f; + } + // ensure coordinates of offset are in [0, 1] range: offset_u = offset_u - WWMath::Floor(offset_u); pTread->m_materialSettings.customUVOffset.Set(offset_u,0); diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp index 0a2687eb944..37e4763c692 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp @@ -414,7 +414,12 @@ void W3DTankTruckDraw::updateTreadPositions(Real uvDelta) else if (pTread->m_type == TREAD_RIGHT) //this tread needs to scroll backwards offset_u = pTread->m_materialSettings.customUVOffset.X - uvDelta; - + else + { + DEBUG_CRASH(("Unhandled case in W3DTankTruckDraw::updateTreadPositions")); + offset_u = 0.0f; + } + // ensure coordinates of offset are in [0, 1] range: offset_u = offset_u - WWMath::Floor(offset_u); pTread->m_materialSettings.customUVOffset.Set(offset_u,0); diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/GUI/W3DGameFont.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/GUI/W3DGameFont.cpp index d28f21b2e89..62c95f7e8c4 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/GUI/W3DGameFont.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/GUI/W3DGameFont.cpp @@ -137,10 +137,11 @@ void W3DFontLibrary::releaseFontData( GameFont *font ) if(((FontCharsClass *)(font->fontData))->AlternateUnicodeFont) ((FontCharsClass *)(font->fontData))->AlternateUnicodeFont->Release_Ref(); ((FontCharsClass *)(font->fontData))->Release_Ref(); + + font->fontData = NULL; } - font->fontData = NULL; - -} // end releaseFont + +} // end releaseFontData // PUBLIC FUNCTIONS /////////////////////////////////////////////////////////// diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp index 84b7380769c..2a0cdcad82a 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp @@ -1575,6 +1575,7 @@ Shadow* W3DProjectedShadowManager::addDecal(Shadow::ShadowTypeInfo *shadowInfo) break; case SHADOW_PROJECTION: m_numProjectionShadows++; + break; default: break; } @@ -1711,6 +1712,7 @@ Shadow* W3DProjectedShadowManager::addDecal(RenderObjClass *robj, Shadow::Shadow break; case SHADOW_PROJECTION: m_numProjectionShadows++; + break; default: break; } @@ -1907,6 +1909,7 @@ W3DProjectedShadow* W3DProjectedShadowManager::addShadow(RenderObjClass *robj, S break; case SHADOW_PROJECTION: m_numProjectionShadows++; + break; default: break; } @@ -2037,6 +2040,7 @@ void W3DProjectedShadowManager::removeShadow (W3DProjectedShadow *shadow) break; case SHADOW_PROJECTION: m_numProjectionShadows--; + break; default: break; } @@ -2062,6 +2066,7 @@ void W3DProjectedShadowManager::removeShadow (W3DProjectedShadow *shadow) break; case SHADOW_PROJECTION: m_numProjectionShadows--; + break; default: break; } diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp index 3c8ddf6c904..a55d561a941 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp @@ -2662,6 +2662,7 @@ void W3DDisplay::drawImage( const Image *image, Int startX, Int startY, m_2DRender->Enable_Additive(false); m_2DRender->Enable_Alpha(false); doAlphaReset = TRUE; + break; default: break; } @@ -2919,75 +2920,77 @@ void W3DDisplay::setShroudLevel( Int x, Int y, CellShroudStatus setting ) //============================================================================= ///Utility function to dump data into a .BMP file static void CreateBMPFile(LPTSTR pszFile, char *image, Int width, Int height) -{ - HANDLE hf; // file handle - BITMAPFILEHEADER hdr; // bitmap file-header - PBITMAPINFOHEADER pbih; // bitmap info-header - LPBYTE lpBits; // memory pointer - DWORD dwTotal; // total count of bytes - DWORD cb; // incremental count of bytes - BYTE *hp; // byte pointer - DWORD dwTmp; - - PBITMAPINFO pbmi; - - pbmi = (PBITMAPINFO) LocalAlloc(LPTR,sizeof(BITMAPINFOHEADER)); - pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); - pbmi->bmiHeader.biWidth = width; - pbmi->bmiHeader.biHeight = height; - pbmi->bmiHeader.biPlanes = 1; - pbmi->bmiHeader.biBitCount = 24; - pbmi->bmiHeader.biCompression = BI_RGB; - pbmi->bmiHeader.biSizeImage = (pbmi->bmiHeader.biWidth + 7) /8 * pbmi->bmiHeader.biHeight * 24; - pbmi->bmiHeader.biClrImportant = 0; - - - pbih = (PBITMAPINFOHEADER) pbmi; - lpBits = (LPBYTE) image; - - // Create the .BMP file. - hf = CreateFile(pszFile, - GENERIC_READ | GENERIC_WRITE, - (DWORD) 0, - NULL, - CREATE_ALWAYS, - FILE_ATTRIBUTE_NORMAL, - (HANDLE) NULL); - if (hf == INVALID_HANDLE_VALUE) - return; - hdr.bfType = 0x4d42; // 0x42 = "B" 0x4d = "M" - // Compute the size of the entire file. - hdr.bfSize = (DWORD) (sizeof(BITMAPFILEHEADER) + - pbih->biSize + pbih->biClrUsed - * sizeof(RGBQUAD) + pbih->biSizeImage); - hdr.bfReserved1 = 0; - hdr.bfReserved2 = 0; - - // Compute the offset to the array of color indices. - hdr.bfOffBits = (DWORD) sizeof(BITMAPFILEHEADER) + - pbih->biSize + pbih->biClrUsed - * sizeof (RGBQUAD); - - // Copy the BITMAPFILEHEADER into the .BMP file. - if (!WriteFile(hf, (LPVOID) &hdr, sizeof(BITMAPFILEHEADER), - (LPDWORD) &dwTmp, NULL)) - return; - - // Copy the BITMAPINFOHEADER and RGBQUAD array into the file. - if (!WriteFile(hf, (LPVOID) pbih, sizeof(BITMAPINFOHEADER) + pbih->biClrUsed * sizeof (RGBQUAD),(LPDWORD) &dwTmp, NULL)) +{ + HANDLE hf; // file handle + BITMAPFILEHEADER hdr; // bitmap file-header + PBITMAPINFOHEADER pbih; // bitmap info-header + LPBYTE lpBits; // memory pointer + DWORD dwTotal; // total count of bytes + DWORD cb; // incremental count of bytes + BYTE *hp; // byte pointer + DWORD dwTmp; + + PBITMAPINFO pbmi; + + pbmi = (PBITMAPINFO) LocalAlloc(LPTR,sizeof(BITMAPINFOHEADER)); + if (pbmi == NULL) return; - // Copy the array of color indices into the .BMP file. - dwTotal = cb = pbih->biSizeImage; - hp = lpBits; - if (!WriteFile(hf, (LPSTR) hp, (int) cb, (LPDWORD) &dwTmp,NULL)) - return; + pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + pbmi->bmiHeader.biWidth = width; + pbmi->bmiHeader.biHeight = height; + pbmi->bmiHeader.biPlanes = 1; + pbmi->bmiHeader.biBitCount = 24; + pbmi->bmiHeader.biCompression = BI_RGB; + pbmi->bmiHeader.biSizeImage = (pbmi->bmiHeader.biWidth + 7) /8 * pbmi->bmiHeader.biHeight * 24; + pbmi->bmiHeader.biClrImportant = 0; + + pbih = (PBITMAPINFOHEADER) pbmi; + lpBits = (LPBYTE) image; + + // Create the .BMP file. + hf = CreateFile(pszFile, + GENERIC_READ | GENERIC_WRITE, + (DWORD) 0, + NULL, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + (HANDLE) NULL); + + if (hf != INVALID_HANDLE_VALUE) + { + hdr.bfType = 0x4d42; // 0x42 = "B" 0x4d = "M" + // Compute the size of the entire file. + hdr.bfSize = (DWORD) (sizeof(BITMAPFILEHEADER) + + pbih->biSize + pbih->biClrUsed + * sizeof(RGBQUAD) + pbih->biSizeImage); + hdr.bfReserved1 = 0; + hdr.bfReserved2 = 0; + + // Compute the offset to the array of color indices. + hdr.bfOffBits = (DWORD) sizeof(BITMAPFILEHEADER) + + pbih->biSize + pbih->biClrUsed + * sizeof (RGBQUAD); + + // Copy the BITMAPFILEHEADER into the .BMP file. + if (WriteFile(hf, (LPVOID) &hdr, sizeof(BITMAPFILEHEADER), + (LPDWORD) &dwTmp, NULL)) + { + // Copy the BITMAPINFOHEADER and RGBQUAD array into the file. + if (WriteFile(hf, (LPVOID) pbih, sizeof(BITMAPINFOHEADER) + pbih->biClrUsed * sizeof (RGBQUAD),(LPDWORD) &dwTmp, NULL)) + { + // Copy the array of color indices into the .BMP file. + dwTotal = cb = pbih->biSizeImage; + hp = lpBits; + WriteFile(hf, (LPSTR) hp, (int) cb, (LPDWORD) &dwTmp, NULL); + } + } - // Close the .BMP file. - if (!CloseHandle(hf)) - return; + // Close the .BMP file. + CloseHandle(hf); + } - // Free memory. + // Free memory. LocalFree( (HLOCAL) pbmi); } diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp index 95153b513e0..691d043f45a 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp @@ -152,7 +152,7 @@ RTS3DScene::RTS3DScene() //Allocate memory to hold queue of visible renderobjects that need to be drawn last //because they are forced translucent. m_translucentObjectsCount = 0; - if (TheGlobalData && TheGlobalData->m_maxVisibleTranslucentObjects) + if (TheGlobalData->m_maxVisibleTranslucentObjects > 0) m_translucentObjectsBuffer = NEW RenderObjClass* [TheGlobalData->m_maxVisibleTranslucentObjects]; else m_translucentObjectsBuffer = NULL; @@ -162,19 +162,26 @@ RTS3DScene::RTS3DScene() m_numNonOccluderOrOccludee=0; m_occludedObjectsCount=0; - m_potentialOccluders=NULL; - m_potentialOccludees=NULL; - m_nonOccludersOrOccludees=NULL; + if (TheGlobalData->m_maxVisibleOccluderObjects > 0) + m_potentialOccluders = NEW RenderObjClass* [TheGlobalData->m_maxVisibleOccluderObjects]; + else + m_potentialOccluders = NULL; + + if (TheGlobalData->m_maxVisibleOccludeeObjects > 0) + m_potentialOccludees = NEW RenderObjClass* [TheGlobalData->m_maxVisibleOccludeeObjects]; + else + m_potentialOccludees = NULL; + + if (TheGlobalData->m_maxVisibleNonOccluderOrOccludeeObjects > 0) + m_nonOccludersOrOccludees = NEW RenderObjClass* [TheGlobalData->m_maxVisibleNonOccluderOrOccludeeObjects]; + else + m_nonOccludersOrOccludees = NULL; //Modify the shader to make occlusion transparent ShaderClass shader = PlayerColorShader; shader.Set_Src_Blend_Func(ShaderClass::SRCBLEND_SRC_ALPHA); shader.Set_Dst_Blend_Func(ShaderClass::DSTBLEND_ONE_MINUS_SRC_ALPHA); - m_potentialOccluders = NEW RenderObjClass* [TheGlobalData->m_maxVisibleOccluderObjects]; - m_potentialOccludees = NEW RenderObjClass* [TheGlobalData->m_maxVisibleOccludeeObjects]; - m_nonOccludersOrOccludees = NEW RenderObjClass* [TheGlobalData->m_maxVisibleNonOccluderOrOccludeeObjects]; - #ifdef USE_NON_STENCIL_OCCLUSION for (i=0; igetFrame(); + Int currentFrame=TheGameLogic->getFrame(); if (currentFrame <= TheGlobalData->m_defaultOcclusionDelay) currentFrame = TheGlobalData->m_defaultOcclusionDelay+1; //make sure occlusion is enabled when game starts (frame 0). diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp index 3d746a5abbc..460c28714c2 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp @@ -3043,11 +3043,11 @@ HRESULT W3DShaderManager::LoadAndCreateD3DShader(const char* strFilePath, const file->close(); file = NULL; - if (ShaderType == TRUE)//SHADERTYPE_VERTEX) + if (ShaderType) // SHADERTYPE_VERTEX { hr = DX8Wrapper::_Get_D3D_Device8()->CreateVertexShader(pDeclaration, pShader, pHandle, Usage); } - else if (ShaderType == FALSE)//SHADERTYPE_PIXEL) + else // SHADERTYPE_PIXEL { hr = DX8Wrapper::_Get_D3D_Device8()->CreatePixelShader(pShader, pHandle); } diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp index f880e070de9..60dad81fecf 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp @@ -1718,6 +1718,7 @@ void WaterRenderObjClass::Render(RenderInfoClass & rinfo) renderWater(); } //WATER_TYPE_1 + break; default: break; diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/assetmgr.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/assetmgr.cpp index 4d00d891145..f963b6d4fbd 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/assetmgr.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/assetmgr.cpp @@ -1122,6 +1122,12 @@ TextureClass * WW3DAssetManager::Get_Texture { tex = NEW_REF (VolumeTextureClass, (lower_case_name, NULL, mip_level_count, texture_format, allow_compression, allow_reduction)); } + else + { + WWASSERT_PRINT(false, ("Unhandled case")); + return NULL; + } + TextureHash.Insert(tex->Get_Texture_Name(),tex); } diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp index 7ea6d67194d..d0b2b3d6223 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp @@ -685,7 +685,8 @@ void DazzleRenderObjClass::Init_Type(const DazzleInitClass& i) unsigned new_count=i.type+1; DazzleTypeClass** new_types=W3DNEWARRAY DazzleTypeClass*[new_count]; unsigned a=0; - for (;aHeader.Attributes & W3D_MESH_FLAG_PRELIT_LIGHTMAP_MULTI_PASS) { @@ -352,7 +352,7 @@ WW3DErrorType MeshModelClass::Load_W3D(ChunkLoadClass & cload) Set_Flag (PRELIT_LIGHTMAP_MULTI_PASS, true); break; } - // Else fall thru... + FALLTHROUGH; // Else fall thru... case WW3D::PRELIT_MODE_VERTEX: if (context->Header.Attributes & W3D_MESH_FLAG_PRELIT_VERTEX) { @@ -360,7 +360,7 @@ WW3DErrorType MeshModelClass::Load_W3D(ChunkLoadClass & cload) Set_Flag (PRELIT_VERTEX, true); break; } - // Else fall thru... + FALLTHROUGH; // Else fall thru... default: diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/textureloader.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/textureloader.cpp index 7982141ca39..6e40fb487eb 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/textureloader.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/textureloader.cpp @@ -452,7 +452,7 @@ IDirect3DTexture8* TextureLoader::Load_Thumbnail(const StringClass& filename, co #endif unsigned level=0; - D3DLOCKED_RECT locked_rects[12]; + D3DLOCKED_RECT locked_rects[12]={0}; WWASSERT(sysmem_texture->GetLevelCount()<=12); // Lock all surfaces @@ -895,7 +895,7 @@ void TextureLoader::Process_Foreground_Thumbnail(TextureLoadTaskClass *task) switch (task->Get_State()) { case TextureLoadTaskClass::STATE_NONE: Load_Thumbnail(task->Peek_Texture()); - // NOTE: fall-through is intentional + FALLTHROUGH; // NOTE: fall-through is intentional case TextureLoadTaskClass::STATE_COMPLETE: task->Destroy(); @@ -1260,12 +1260,15 @@ void TextureLoadTaskClass::Finish_Load(void) Apply_Missing_Texture(); break; } + FALLTHROUGH; case STATE_LOAD_BEGUN: Load(); + FALLTHROUGH; case STATE_LOAD_MIPMAP: End_Load(); + FALLTHROUGH; default: break; diff --git a/GeneralsMD/Code/Main/WinMain.cpp b/GeneralsMD/Code/Main/WinMain.cpp index 95196922a87..c2754bc5417 100644 --- a/GeneralsMD/Code/Main/WinMain.cpp +++ b/GeneralsMD/Code/Main/WinMain.cpp @@ -762,6 +762,7 @@ static CriticalSection critSec1, critSec2, critSec3, critSec4, critSec5; Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, Int nCmdShow ) { + Int exitcode = 1; #ifdef RTS_PROFILE Profile::StartRange("init"); @@ -840,7 +841,7 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, // register windows class and create application window if(!TheGlobalData->m_headless && initializeAppWindows(hInstance, nCmdShow, TheGlobalData->m_windowed) == false) - return 0; + return exitcode; // save our application instance for future use ApplicationHInstance = hInstance; @@ -881,14 +882,14 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, TheVersion = NULL; shutdownMemoryManager(); DEBUG_SHUTDOWN(); - return 0; + return exitcode; } DEBUG_LOG(("Create Generals Mutex okay.\n")); DEBUG_LOG(("CRC message is %d\n", GameMessage::MSG_LOGIC_CRC)); // run the game main loop - GameMain(0, NULL); + exitcode = GameMain(); delete TheVersion; TheVersion = NULL; @@ -916,7 +917,7 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, TheDmaCriticalSection = NULL; TheMemoryPoolCriticalSection = NULL; - return 0; + return exitcode; } // end WinMain diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderDoc.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderDoc.cpp index eeb1bbf5f3a..0929a4d6b62 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderDoc.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderDoc.cpp @@ -1271,7 +1271,6 @@ BOOL CWorldBuilderDoc::OnNewDocument() m_waypointTableNeedsUpdate = true; m_curWaypointID = 0; WbApp()->selectPointerTool(); - PolygonTrigger::deleteTriggers(); // Make sure that all the old units are removed from the list. // Bug fix by MLL 1/14/03 @@ -1279,6 +1278,9 @@ BOOL CWorldBuilderDoc::OnNewDocument() TheLayersList->resetLayers(); TheLayersList->disableUpdates(); + // TheSuperHackers @bugfix Caball009 20/06/2025 Must not delete polygon triggers before calling enableUpdates. + PolygonTrigger::deleteTriggers(); + TheSidesList->clear(); TheSidesList->validateSides(); diff --git a/cmake/compilers.cmake b/cmake/compilers.cmake index 7d184f98672..f9df8a2b53c 100644 --- a/cmake/compilers.cmake +++ b/cmake/compilers.cmake @@ -15,21 +15,21 @@ else() set(IS_VS6_BUILD FALSE) endif() -# Make release builds have debug information too. -if(MSVC) - # Create PDB for Release as long as debug info was generated during compile. - string(APPEND CMAKE_EXE_LINKER_FLAGS_RELEASE " /DEBUG /OPT:REF /OPT:ICF") - string(APPEND CMAKE_SHARED_LINKER_FLAGS_RELEASE " /DEBUG /OPT:REF /OPT:ICF") -else() - # We go a bit wild here and assume any other compiler we are going to use supports -g for debug info. - string(APPEND CMAKE_CXX_FLAGS_RELEASE " -g") - string(APPEND CMAKE_C_FLAGS_RELEASE " -g") -endif() +if (NOT IS_VS6_BUILD) + # Make release builds have debug information too. + if(MSVC) + # Create PDB for Release as long as debug info was generated during compile. + string(APPEND CMAKE_EXE_LINKER_FLAGS_RELEASE " /DEBUG /OPT:REF /OPT:ICF") + string(APPEND CMAKE_SHARED_LINKER_FLAGS_RELEASE " /DEBUG /OPT:REF /OPT:ICF") + else() + # We go a bit wild here and assume any other compiler we are going to use supports -g for debug info. + string(APPEND CMAKE_CXX_FLAGS_RELEASE " -g") + string(APPEND CMAKE_C_FLAGS_RELEASE " -g") + endif() -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) # Ensures only ISO features are used + set(CMAKE_CXX_STANDARD_REQUIRED ON) + set(CMAKE_CXX_EXTENSIONS OFF) # Ensures only ISO features are used -if (NOT IS_VS6_BUILD) if (MSVC) # Multithreaded build. add_compile_options(/MP) diff --git a/cmake/vc6toolchain.cmake b/cmake/vc6toolchain.cmake new file mode 100644 index 00000000000..71901edd8b0 --- /dev/null +++ b/cmake/vc6toolchain.cmake @@ -0,0 +1,89 @@ +#Defining convenience vars + +set(VS_X86 "C:/PROGRA~2/MICROS~2") +set(VC6_ROOT "${VS_X86}/VC98") +set(VS_X86_COMMON "${VS_X86}/Common") + +set(VC6_INCLUDES "${VC6_ROOT}/INCLUDE;${VC6_ROOT}/MFC/INCLUDE;${VC6_ROOT}/ATL/INCLUDE") +set(VC6_LIBRARIES "${VC6_ROOT}/LIB;${VC6_ROOT}/MFC/LIB") +set(VC6_PATH "${VC6_ROOT}/BIN;${VS_X86_COMMON}/Tools;${VS_X86_COMMON}/Tools/WinNT;${VS_X86_COMMON}/MSDev98/Bin") +set(VC6_DEV "${VS_X86_COMMON}/MSDev98") + +set(STANDARD_LIBRARIES "kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib") + +#Setting toolchain vars to configure ninja for vc6 + +set(ENV{INCLUDE} "${VC6_INCLUDES};$ENV{INCLUDE}") +set(ENV{LIB} "${VC6_LIBRARIES};$ENV{LIB}") +set(ENV{PATH} "${VC6_PATH};$ENV{PATH}") +set(ENV{MSDEV_DIR} "${VC6_DEV}") + +set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES "$ENV{INCLUDE}") +set(CMAKE_CXX_STANDARD_LIBRARIES "${STANDARD_LIBRARIES}") +set(CMAKE_CXX_STANDARD_LINK_DIRECTORIES "$ENV{LIB}") + +set(CMAKE_C_STANDARD_INCLUDE_DIRECTORIES "$ENV{INCLUDE}") +set(CMAKE_C_STANDARD_LIBRARIES "${STANDARD_LIBRARIES}") +set(CMAKE_C_STANDARD_LINK_DIRECTORIES "$ENV{LIB}") + +# Setting C++ default compiler flags for VC6 + +string(JOIN " " VC6_CXX_FLAGS + "/DWIN32" # Define WIN32 macro + "/D_WINDOWS" # Define _WINDOWS macro + "/Zm800" # Cap precompiled header memory allocation to prevent running out of heap space + "/Gd" # __cdecl calling convention for all functions (C/C++ standard behaviour) + "/GR" # Enable RTTI (Run-Time Type Information, needed for things like dynamic_cast) + "/Op" # Improve floating point consistency (More ISO-compliant and closer to 64bit behaviour) + "/EHs" # Synchronous C++ exception handling (ISO-standard C++ exception handling) +# "/Za" # Disable Microsoft language extensions (enforce standard) +) +set(CMAKE_CXX_FLAGS "${VC6_CXX_FLAGS}" CACHE STRING "" FORCE) + +string(JOIN " " VC6_CXX_FLAGS_DEBUG + "/Od" # Disable optimizations + "/Ob0" # Disable inline expansion + "/EHc-" # Do NOT assume extern "C" functions never throw (less strict, useful for debugging) + "/Oy-" # Disable frame pointer omission (helps debugging) + "/GZ" # Helps Catch Release-Build Errors in Debug Build + "/Yd" # Store complete debug info in .obj (needed for embedded debug info from precompiled header to be accessible) +) +set(CMAKE_CXX_FLAGS_DEBUG "${VC6_CXX_FLAGS_DEBUG}" CACHE STRING "" FORCE) + +string(JOIN " " VC6_CXX_FLAGS_RELEASE + "/O2" # Optimize for speed + "/Ob2" # Inline any suitable function + "/EHc" # Assume extern "C" functions never throw (safe for release) + "/Oy" # Omit frame pointer (smaller/faster code) + "/nologo" # Suppress copyright message +) +set(CMAKE_CXX_FLAGS_RELEASE "${VC6_CXX_FLAGS_RELEASE}" CACHE STRING "" FORCE) + +# Setting C++ default linker flags for VC6 + +string(JOIN " " VC6_EXE_LINKER_FLAGS + "/machine:IX86" # Target x86 architecture + "/LARGEADDRESSAWARE" # Allow >2GB RAM on x64 OS (useful for large address space on 32-bit binaries) +) +set(CMAKE_EXE_LINKER_FLAGS "${VC6_EXE_LINKER_FLAGS}" CACHE STRING "" FORCE) + +string(JOIN " " VC6_EXE_LINKER_FLAGS_RELEASE + "/RELEASE" # Set the checksum in the header and mark as release + "/DEBUG:NONE" # Do not generate debug info + "/OPT:REF,ICF" # Remove unreferenced code/data, COMDAT folding + "/NOLOGO" # Suppress linker startup banner +) +set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${VC6_EXE_LINKER_FLAGS_RELEASE}" CACHE STRING "" FORCE) + +string(JOIN " " VC6_EXE_LINKER_FLAGS_DEBUG + #"/DEBUG" # Generate debug info + #"/PDB:NONE" # Do not generate a PDB file + "/DEBUGTYPE:CV" # Use CodeView format for debug info (VC6 default) + "/OPT:NOREF,NOICF" # Do not remove unreferenced code/data, COMDAT folding + "/PDBTYPE:SEPT" # Separate debug symbols (one .pdb per .obj, easier for incremental linking) + "/INCREMENTAL:YES" # Enable incremental linking + "/VERBOSE" # Enable verbose output from the linker +) +set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${VC6_EXE_LINKER_FLAGS_DEBUG}" CACHE STRING "" FORCE) + +# @todo: Set default flags for C \ No newline at end of file