From ad34a9aa16573bf97ee7f9b5021a3d3011832cc3 Mon Sep 17 00:00:00 2001 From: Mauller <26652186+Mauller@users.noreply.github.com> Date: Mon, 30 Mar 2026 21:22:18 +0100 Subject: [PATCH 1/4] refactor(team): Flatten condition nesting in Team::updateGenericScripts (#) --- .../GameEngine/Source/Common/RTS/Team.cpp | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Team.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Team.cpp index 03aaead2883..5aa1d01d8d9 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Team.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Team.cpp @@ -2531,26 +2531,27 @@ void Team::updateGenericScripts() { //USE_PERF_TIMER(updateGenericScripts) for (Int i = 0; i < MAX_GENERIC_SCRIPTS; ++i) { - if (m_shouldAttemptGenericScript[i]) { - // Does the condition succeed? If so, run it. If it is a run once script, also mark that we - // shouldn't run it again. - Script *script = m_proto->getGenericScript(i); - if (script) { - if (TheScriptEngine->evaluateConditions(script, this)) { - // It was successful. - if (script->isOneShot()) { - m_shouldAttemptGenericScript[i] = false; - } - TheScriptEngine->friend_executeAction(script->getAction(), this); - AsciiString msg = "Generic script '"; - msg.concat(script->getName()); - msg.concat("' run on team "); - msg.concat(getName()); - TheScriptEngine->AppendDebugMessage(msg, false); - } - } else { + if (!m_shouldAttemptGenericScript[i]) { + continue; + } + + Script *script = m_proto->getGenericScript(i); + if (!script) { + m_shouldAttemptGenericScript[i] = false; + continue; + } + + if (TheScriptEngine->evaluateConditions(script, this)) { + // It was successful. + if (script->isOneShot()) { m_shouldAttemptGenericScript[i] = false; } + TheScriptEngine->friend_executeAction(script->getAction(), this); + AsciiString msg = "Generic script '"; + msg.concat(script->getName()); + msg.concat("' run on team "); + msg.concat(getName()); + TheScriptEngine->AppendDebugMessage(msg, false); } } } From 64d3bfbdc383999f1fa82ac32cd39829c33246cb Mon Sep 17 00:00:00 2001 From: Mauller <26652186+Mauller@users.noreply.github.com> Date: Mon, 30 Mar 2026 21:36:58 +0100 Subject: [PATCH 2/4] feature(Team): Allow team generic scripts to have their active states set within scripting (#) --- Core/GameEngine/Include/Common/GameDefines.h | 5 +++ .../GameEngine/Source/Common/RTS/Team.cpp | 35 +++++++++++++++++++ .../GameLogic/ScriptEngine/ScriptEngine.cpp | 33 ++++++++++++++++- 3 files changed, 72 insertions(+), 1 deletion(-) diff --git a/Core/GameEngine/Include/Common/GameDefines.h b/Core/GameEngine/Include/Common/GameDefines.h index d688cc73ae4..dce094becc5 100644 --- a/Core/GameEngine/Include/Common/GameDefines.h +++ b/Core/GameEngine/Include/Common/GameDefines.h @@ -54,6 +54,11 @@ #define RETAIL_COMPATIBLE_NETWORKING (1) #endif +// Retail compatible scripting disables improved or fixed behaviour when handling scripts +#ifndef RETAIL_COMPATIBLE_SCRIPTING +#define RETAIL_COMPATIBLE_SCRIPTING (1) +#endif + // This is essentially synonymous for RETAIL_COMPATIBLE_CRC. There is a lot wrong with AIGroup, such as use-after-free, double-free, leaks, // but we cannot touch it much without breaking retail compatibility. Do not shy away from using massive hacks when fixing issues with AIGroup, // but put them behind this macro. diff --git a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Team.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Team.cpp index 5aa1d01d8d9..6cd427f0e84 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Team.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Team.cpp @@ -2530,6 +2530,7 @@ Bool Team::hasAnyBuildFacility() const void Team::updateGenericScripts() { //USE_PERF_TIMER(updateGenericScripts) +#if RETAIL_COMPATIBLE_SCRIPTING for (Int i = 0; i < MAX_GENERIC_SCRIPTS; ++i) { if (!m_shouldAttemptGenericScript[i]) { continue; @@ -2554,6 +2555,31 @@ void Team::updateGenericScripts() TheScriptEngine->AppendDebugMessage(msg, false); } } +#else + for (Int i = 0; i < MAX_GENERIC_SCRIPTS; ++i) { + Script* script = m_proto->getGenericScript(i); + if (!script) { + continue; + } + + if (!script->isActive()) { + continue; + } + + if (TheScriptEngine->evaluateConditions(script, this)) { + // It was successful. + if (script->isOneShot()) { + script->setActive(false); + } + TheScriptEngine->friend_executeAction(script->getAction(), this); + AsciiString msg = "Generic script '"; + msg.concat(script->getName()); + msg.concat("' run on team "); + msg.concat(getName()); + TheScriptEngine->AppendDebugMessage(msg, false); + } + } +#endif } // ------------------------------------------------------------------------------------------------ @@ -2678,8 +2704,17 @@ void Team::xfer( Xfer *xfer ) throw SC_INVALID_DATA; } +#if RETAIL_COMPATIBLE_SCRIPTING for (Int i = 0; i < shouldAttemptGenericScriptCount; ++i) xfer->xferBool(&m_shouldAttemptGenericScript[i]); +#else + // TheSuperHackers @info Generic scripts can now be toggled so we need to directly check each scripts active state + for (Int i = 0; i < shouldAttemptGenericScriptCount; ++i) { + Script* script = m_proto->getGenericScript(i); + Bool scriptActive = script ? script->isActive() : false; + xfer->xferBool(&scriptActive); + } +#endif // recruitability set xfer->xferBool( &m_isRecruitablitySet ); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp index 7c9c612245c..72a8a9aa259 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp @@ -6815,6 +6815,23 @@ void ScriptEngine::adjustTimer( ScriptAction *pAction, Bool millisecondTimer, Bo void ScriptEngine::enableScript( ScriptAction *pAction ) { DEBUG_ASSERTCRASH(pAction->getNumParameters() >= 1, ("Not enough parameters.")); +#if !RETAIL_COMPATIBLE_SCRIPTING + // TheSuperHackers @feature Mauller/TanSo 30/03/2026 Allow searching for a team generic script. + // We can now enable and disable team generic scripts through scripting. + // OneShot generic scripts can also be re-enabled to run again. + if (m_callingTeam) { + const AsciiString& scriptName = pAction->getParameter(0)->getString(); + TeamPrototype* teamProto = const_cast(m_callingTeam->getPrototype()); + for (Int i = 0; i < MAX_GENERIC_SCRIPTS; ++i) { + Script* script = teamProto->getGenericScript(i); + if (script && script->getName() == scriptName) { + script->setActive(true); + return; + } + } + } +#endif + ScriptGroup *pGroup = findGroup(pAction->getParameter(0)->getString()); if (pGroup) { pGroup->setActive(true); @@ -6826,11 +6843,25 @@ void ScriptEngine::enableScript( ScriptAction *pAction ) } //------------------------------------------------------------------------------------------------- -/** Enables a script or group. */ +/** Disables a script or group. */ //------------------------------------------------------------------------------------------------- void ScriptEngine::disableScript( ScriptAction *pAction ) { DEBUG_ASSERTCRASH(pAction->getNumParameters() >= 1, ("Not enough parameters.")); +#if !RETAIL_COMPATIBLE_SCRIPTING + if (m_callingTeam) { + const AsciiString& scriptName = pAction->getParameter(0)->getString(); + TeamPrototype* teamProto = const_cast(m_callingTeam->getPrototype()); + for (Int i = 0; i < MAX_GENERIC_SCRIPTS; ++i) { + Script* script = teamProto->getGenericScript(i); + if (script && script->getName() == scriptName) { + script->setActive(false); + return; + } + } + } +#endif + Script *pScript = findScript(pAction->getParameter(0)->getString()); if (pScript) { pScript->setActive(false); From fa4df221006eac36a453070a8f36055247e13759 Mon Sep 17 00:00:00 2001 From: Mauller <26652186+Mauller@users.noreply.github.com> Date: Mon, 30 Mar 2026 21:40:44 +0100 Subject: [PATCH 3/4] feature(Team): Evaluate generic script properties for difficulty and timing --- .../GameEngine/Source/Common/RTS/Team.cpp | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Team.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Team.cpp index 6cd427f0e84..958e4dff79b 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Team.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Team.cpp @@ -2566,6 +2566,27 @@ void Team::updateGenericScripts() continue; } + // TheSuperHackers @feature Mauller/TanSo 30/03/2026 Evaluate all script properties on generic scripts + Player* currentPlayer = getControllingPlayer(); + GameDifficulty difficulty = TheScriptEngine->getGlobalDifficulty(); + if (currentPlayer) { + difficulty = currentPlayer->getPlayerDifficulty(); + } + switch (difficulty) { + case DIFFICULTY_EASY: if (!script->isEasy()) continue; break; + case DIFFICULTY_NORMAL: if (!script->isNormal()) continue; break; + case DIFFICULTY_HARD: if (!script->isHard()) continue; break; + } + + if (TheGameLogic->getFrame() < script->getFrameToEvaluate()) { + continue; + } + + Int delaySeconds = script->getDelayEvalSeconds(); + if (delaySeconds > 0) { + script->setFrameToEvaluate(TheGameLogic->getFrame() + delaySeconds * LOGICFRAMES_PER_SECOND); + } + if (TheScriptEngine->evaluateConditions(script, this)) { // It was successful. if (script->isOneShot()) { From ee8ee5d42f2946cf6c401d7eaf288e96945fdfe9 Mon Sep 17 00:00:00 2001 From: Mauller <26652186+Mauller@users.noreply.github.com> Date: Wed, 1 Apr 2026 23:58:25 +0100 Subject: [PATCH 4/4] feature(Team): Implement generic scripting on each team instance (#) --- .../Code/GameEngine/Include/Common/Team.h | 9 ++++++ .../GameEngine/Source/Common/RTS/Team.cpp | 30 +++++++++++++++++-- .../GameLogic/ScriptEngine/ScriptEngine.cpp | 6 ++-- 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/Common/Team.h b/GeneralsMD/Code/GameEngine/Include/Common/Team.h index a9deba8c0d4..77303f494f6 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/Team.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/Team.h @@ -215,8 +215,13 @@ class Team : public MemoryPoolObject, // Following waypoint paths as a team. const Waypoint *m_currentWaypoint; +#if RETAIL_COMPATIBLE_SCRIPTING // Should check/Execute generic script Bool m_shouldAttemptGenericScript[MAX_GENERIC_SCRIPTS]; +#else + // TheSuperHackers @feature Mauller/TanSo 1/04/2026 Implement generic scripts on each team instance + Script* m_genericScriptsToRun[MAX_GENERIC_SCRIPTS]; +#endif // Recruitablity. Bool m_isRecruitablitySet; ///< If false, recruitability is team proto value. If true, m_isRecruitable. @@ -245,6 +250,10 @@ class Team : public MemoryPoolObject, /// return the prototype used to create this team const TeamPrototype *getPrototype() { return m_proto; } +#if !RETAIL_COMPATIBLE_SCRIPTING + Script* getGenericScript(Int scriptToRetrieve) { return m_genericScriptsToRun[scriptToRetrieve]; } +#endif + void setID( TeamID id ) { m_id = id; } TeamID getID() const { return m_id; } diff --git a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Team.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Team.cpp index 958e4dff79b..9ee57749abe 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Team.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Team.cpp @@ -1323,6 +1323,12 @@ Team::Team(TeamPrototype *proto, TeamID id ) : m_playerRelations = newInstance(PlayerRelationMap); m_teamRelations = newInstance(TeamRelationMap); +#if !RETAIL_COMPATIBLE_SCRIPTING + for (int i = 0; i < MAX_GENERIC_SCRIPTS; ++i) { + m_genericScriptsToRun[i] = nullptr; + } +#endif + if (proto) { proto->prependTo_TeamInstanceList(this); @@ -1332,15 +1338,26 @@ Team::Team(TeamPrototype *proto, TeamID id ) : m_checkEnemySighted = true; // Only keep track of enemy sighted if there is a script that cares. } +#if !RETAIL_COMPATIBLE_SCRIPTING + for (int i = 0; i < MAX_GENERIC_SCRIPTS; ++i) { + Script* script = proto->getGenericScript(i); + if (script) { + m_genericScriptsToRun[i] = proto->getGenericScript(i)->duplicate(); + } + } +#endif + AsciiString teamName = proto->getName(); teamName.concat(" - creating team instance."); TheScriptEngine->AppendDebugMessage(teamName, false); } +#if RETAIL_COMPATIBLE_SCRIPTING for (Int i = 0; i < MAX_GENERIC_SCRIPTS; ++i) { m_shouldAttemptGenericScript[i] = true; } +#endif } @@ -1380,6 +1397,15 @@ Team::~Team() // make sure the xfer list is clear m_xferMemberIDList.clear(); +#if !RETAIL_COMPATIBLE_SCRIPTING + // Clear any scripts that we have ownership of + for (i = 0; i < MAX_GENERIC_SCRIPTS; ++i) + { + deleteInstance(m_genericScriptsToRun[i]); + m_genericScriptsToRun[i] = nullptr; + } +#endif + } // ------------------------------------------------------------------------ @@ -2557,7 +2583,7 @@ void Team::updateGenericScripts() } #else for (Int i = 0; i < MAX_GENERIC_SCRIPTS; ++i) { - Script* script = m_proto->getGenericScript(i); + Script* script = m_genericScriptsToRun[i]; if (!script) { continue; } @@ -2731,7 +2757,7 @@ void Team::xfer( Xfer *xfer ) #else // TheSuperHackers @info Generic scripts can now be toggled so we need to directly check each scripts active state for (Int i = 0; i < shouldAttemptGenericScriptCount; ++i) { - Script* script = m_proto->getGenericScript(i); + Script* script = m_genericScriptsToRun[i]; Bool scriptActive = script ? script->isActive() : false; xfer->xferBool(&scriptActive); } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp index 72a8a9aa259..58377a67c4d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp @@ -6821,9 +6821,8 @@ void ScriptEngine::enableScript( ScriptAction *pAction ) // OneShot generic scripts can also be re-enabled to run again. if (m_callingTeam) { const AsciiString& scriptName = pAction->getParameter(0)->getString(); - TeamPrototype* teamProto = const_cast(m_callingTeam->getPrototype()); for (Int i = 0; i < MAX_GENERIC_SCRIPTS; ++i) { - Script* script = teamProto->getGenericScript(i); + Script* script = m_callingTeam->getGenericScript(i); if (script && script->getName() == scriptName) { script->setActive(true); return; @@ -6851,9 +6850,8 @@ void ScriptEngine::disableScript( ScriptAction *pAction ) #if !RETAIL_COMPATIBLE_SCRIPTING if (m_callingTeam) { const AsciiString& scriptName = pAction->getParameter(0)->getString(); - TeamPrototype* teamProto = const_cast(m_callingTeam->getPrototype()); for (Int i = 0; i < MAX_GENERIC_SCRIPTS; ++i) { - Script* script = teamProto->getGenericScript(i); + Script* script = m_callingTeam->getGenericScript(i); if (script && script->getName() == scriptName) { script->setActive(false); return;