Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Core/GameEngine/Include/Common/GameDefines.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
9 changes: 9 additions & 0 deletions GeneralsMD/Code/GameEngine/Include/Common/Team.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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; }

Expand Down
119 changes: 101 additions & 18 deletions GeneralsMD/Code/GameEngine/Source/Common/RTS/Team.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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


}
Expand Down Expand Up @@ -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

}

// ------------------------------------------------------------------------
Expand Down Expand Up @@ -2530,29 +2556,77 @@ 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]) {
// 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);
}
}
#else
for (Int i = 0; i < MAX_GENERIC_SCRIPTS; ++i) {
Script* script = m_genericScriptsToRun[i];
if (!script) {
continue;
}

if (!script->isActive()) {
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()) {
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
}

// ------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -2677,8 +2751,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_genericScriptsToRun[i];
Bool scriptActive = script ? script->isActive() : false;
xfer->xferBool(&scriptActive);
}
#endif

// recruitability set
xfer->xferBool( &m_isRecruitablitySet );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6815,6 +6815,22 @@ 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();
for (Int i = 0; i < MAX_GENERIC_SCRIPTS; ++i) {
Script* script = m_callingTeam->getGenericScript(i);
if (script && script->getName() == scriptName) {
script->setActive(true);
return;
}
}
}
#endif

ScriptGroup *pGroup = findGroup(pAction->getParameter(0)->getString());
if (pGroup) {
pGroup->setActive(true);
Expand All @@ -6826,11 +6842,24 @@ 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();
for (Int i = 0; i < MAX_GENERIC_SCRIPTS; ++i) {
Script* script = m_callingTeam->getGenericScript(i);
if (script && script->getName() == scriptName) {
script->setActive(false);
return;
}
}
}
#endif

Script *pScript = findScript(pAction->getParameter(0)->getString());
if (pScript) {
pScript->setActive(false);
Expand Down
Loading