Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,14 @@ class ExportScriptsOptions : public CDialog
static Bool m_waypoints;
static Bool m_triggers;
static Bool m_allScripts;
static Bool m_sides;

public:
Bool getDoUnits() {return m_units;}
Bool getDoWaypoints() {return m_waypoints;}
Bool getDoTriggers() {return m_triggers;}
Bool getDoAllScripts() {return m_allScripts;}
Bool getDoSides() {return m_sides;}

protected:

Expand Down
19 changes: 11 additions & 8 deletions Generals/Code/Tools/WorldBuilder/res/WorldBuilder.rc
Original file line number Diff line number Diff line change
Expand Up @@ -1374,26 +1374,29 @@ BEGIN
225,117,8
END

IDD_EXPORT_SCRIPTS_OPTIONS DIALOG DISCARDABLE 0, 0, 186, 153
// TheSuperHackers @fix OmarAglan Keep the export buttons inside the expanded dialog.
IDD_EXPORT_SCRIPTS_OPTIONS DIALOG DISCARDABLE 0, 0, 186, 173
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Export Script Options"
FONT 8, "MS Sans Serif"
BEGIN
DEFPUSHBUTTON "OK",IDOK,69,132,50,14
PUSHBUTTON "Cancel",IDCANCEL,129,132,50,14
DEFPUSHBUTTON "OK",IDOK,69,152,50,14
PUSHBUTTON "Cancel",IDCANCEL,129,152,50,14
CONTROL "Include waypoints and waypoint paths.",IDC_WAYPOINTS,
"Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,20,150,8
CONTROL "Include trigger areas.",IDC_TRIGGERS,"Button",
BS_AUTOCHECKBOX | WS_TABSTOP,12,40,156,9
CONTROL "Include units and buildings.",IDC_UNITS,"Button",
BS_AUTOCHECKBOX | WS_TABSTOP,12,61,154,9
GROUPBOX "Include items referenced in the scripts:",IDC_STATIC,7,
7,172,73
7,172,88
CONTROL "Export all scripts.",IDC_ALL_SCRIPTS,"Button",
BS_AUTORADIOBUTTON,15,94,97,8
BS_AUTORADIOBUTTON,15,115,97,8
CONTROL "Export selected scripts.",IDC_SELECTED_SCRIPTS,"Button",
BS_AUTORADIOBUTTON,15,106,99,8
GROUPBOX "Export Mode:",IDC_STATIC,7,83,172,39
BS_AUTORADIOBUTTON,15,127,99,8
GROUPBOX "Export Mode:",IDC_STATIC,7,104,172,39
CONTROL "Include sides.",IDC_SIDES,"Button",BS_AUTOCHECKBOX |
WS_TABSTOP,12,79,154,9
END

IDD_GROVE_OPTIONS DIALOG DISCARDABLE 0, 0, 200, 173
Expand Down Expand Up @@ -2089,7 +2092,7 @@ BEGIN
LEFTMARGIN, 7
RIGHTMARGIN, 179
TOPMARGIN, 7
BOTTOMMARGIN, 146
BOTTOMMARGIN, 166
END

IDD_GROVE_OPTIONS, DIALOG
Expand Down
3 changes: 2 additions & 1 deletion Generals/Code/Tools/WorldBuilder/res/resource.h
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,7 @@
#define IDC_SCRIPT_PREFIX15 1313
#define IDC_SCRIPT_PREFIX16 1314
#define IDC_TeamGeneric_Script1 1315
#define IDC_SIDES 1329
#define ID_BRUSH_TOOL 32771
#define IDM_ShowGrid 32772
#define ID_FEATHERTOOL 32791
Expand Down Expand Up @@ -697,7 +698,7 @@
#define _APS_3D_CONTROLS 1
#define _APS_NEXT_RESOURCE_VALUE 232
#define _APS_NEXT_COMMAND_VALUE 33344
#define _APS_NEXT_CONTROL_VALUE 1326
#define _APS_NEXT_CONTROL_VALUE 1330
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif
11 changes: 11 additions & 0 deletions Generals/Code/Tools/WorldBuilder/src/ExportScriptsOptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Bool ExportScriptsOptions::m_units = true;
Bool ExportScriptsOptions::m_waypoints = true;
Bool ExportScriptsOptions::m_triggers = true;
Bool ExportScriptsOptions::m_allScripts = false;
Bool ExportScriptsOptions::m_sides = true;

ExportScriptsOptions::ExportScriptsOptions(CWnd* pParent /*=nullptr*/)
: CDialog(ExportScriptsOptions::IDD, pParent)
Expand Down Expand Up @@ -68,6 +69,9 @@ void ExportScriptsOptions::OnOK()
m_triggers = pButton->GetCheck()==1;
pButton = (CButton*)GetDlgItem(IDC_ALL_SCRIPTS);
m_allScripts = pButton->GetCheck()==1;
pButton = (CButton*)GetDlgItem(IDC_SIDES);
m_sides = pButton->GetCheck()==1;


CDialog::OnOK();
}
Expand All @@ -86,6 +90,13 @@ BOOL ExportScriptsOptions::OnInitDialog()
pButton->SetCheck(m_allScripts?1:0);
pButton = (CButton*)GetDlgItem(IDC_SELECTED_SCRIPTS);
pButton->SetCheck(m_allScripts?0:1);
pButton = (CButton*)GetDlgItem(IDC_SIDES);
#if RTS_GENERALS && RETAIL_COMPATIBLE_DATA
// Retail Generals script files do not store side dictionaries.
m_sides = false;
pButton->EnableWindow(FALSE);
#endif
pButton->SetCheck(m_sides?1:0);

return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
Expand Down
113 changes: 99 additions & 14 deletions Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1100,6 +1100,8 @@ void ScriptDialog::scanForWaypointsAndTeams(Script *pScript, Bool doUnits, Bool
}

#define K_PLAYERS_NAMES_FOR_SCRIPTS_VERSION_1 1
// Added in Zero Hour
#define K_PLAYERS_NAMES_FOR_SCRIPTS_VERSION_2 2

/** Write out selected scripts, and possibly waypoints, trigger areas & teams. */
void ScriptDialog::OnSave()
Expand All @@ -1108,6 +1110,7 @@ void ScriptDialog::OnSave()
Bool doTriggerAreas = true;
Bool doUnits = true;
Bool doAllScripts = true;
Bool doSides = true;
Int i;

ExportScriptsOptions optionsDlg;
Expand All @@ -1118,6 +1121,7 @@ void ScriptDialog::OnSave()
doUnits = optionsDlg.getDoUnits();
doTriggerAreas = optionsDlg.getDoTriggers();
doAllScripts = optionsDlg.getDoAllScripts();
doSides = optionsDlg.getDoSides();

Script *pScript = getCurScript();
ScriptGroup *pGroup = getCurGroup();
Expand Down Expand Up @@ -1211,12 +1215,27 @@ void ScriptDialog::OnSave()
ScriptList::WriteScriptsDataChunk(chunkWriter, scripts, numScriptLists);

/***************Players DATA ***************/
chunkWriter.openDataChunk("ScriptsPlayers", K_PLAYERS_NAMES_FOR_SCRIPTS_VERSION_1);
if (doAllScripts) {
#if RTS_GENERALS && RETAIL_COMPATIBLE_DATA
const DataChunkVersionType playersVersion = K_PLAYERS_NAMES_FOR_SCRIPTS_VERSION_1;
doSides = false;
#else
const DataChunkVersionType playersVersion = K_PLAYERS_NAMES_FOR_SCRIPTS_VERSION_2;
#endif
chunkWriter.openDataChunk("ScriptsPlayers", playersVersion);
if (playersVersion >= K_PLAYERS_NAMES_FOR_SCRIPTS_VERSION_2) {
chunkWriter.writeInt(doSides);
}
if (doAllScripts || doSides) {
chunkWriter.writeInt(m_sides.getNumSides());
for (i=0; i<m_sides.getNumSides(); i++) {
AsciiString name = m_sides.getSideInfo(i)->getDict()->getAsciiString(TheKey_playerName);
chunkWriter.writeAsciiString(name);

if (doSides) {
// The user has requested that the sides get exported.
chunkWriter.writeDict(*m_sides.getSideInfo(i)->getDict());
}

}
} else {
chunkWriter.writeInt(1);
Expand Down Expand Up @@ -1374,8 +1393,16 @@ void ScriptDialog::OnLoad()
file.registerParser( "WaypointsList", AsciiString::TheEmptyString, ParseWaypointDataChunk );
file.registerParser( "ScriptTeams", AsciiString::TheEmptyString, ParseTeamsDataChunk );
file.registerParser( "ScriptsPlayers", AsciiString::TheEmptyString, ParsePlayersDataChunk );
if (!file.parse(this)) {
throw(ERROR_CORRUPT_FILE_FORMAT);
// TheSuperHackers @bugfix OmarAglan Restore sides and teams when script parsing fails.
SidesList sidesBeforeImport;
sidesBeforeImport = m_sides;
try {
if (!file.parse(this)) {
throw(ERROR_CORRUPT_FILE_FORMAT);
}
} catch(...) {
m_sides = sidesBeforeImport;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High src/ScriptDialog.cpp:1404

A parse failure leaves imported waypoint links in the active document, so a file with valid waypoints followed by invalid player/script data produces a partial import that can be saved. ParseWaypointDataChunk calls pDoc->addWaypointLink(...) during file.parse(this), but this catch block restores only m_sides; roll back those document-side changes as well, or make the import transactional.

🤖 Copy this AI Prompt to have your agent fix this:
In file @Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp around line 1404:

A parse failure leaves imported waypoint links in the active document, so a file with valid waypoints followed by invalid player/script data produces a partial import that can be saved. `ParseWaypointDataChunk` calls `pDoc->addWaypointLink(...)` during `file.parse(this)`, but this catch block restores only `m_sides`; roll back those document-side changes as well, or make the import transactional.

throw;
Comment on lines +1403 to +1405

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Failed imports leave map changes

If a bundle contains waypoint links and then fails a player-count or dictionary-name check, parsing has already added those links to the document. This catch restores the sides but leaves links to waypoints that were never imported. Parsed script lists can also remain pending for the next import. The same rollback behavior occurs in the Zero Hour editor.

Knowledge Base Used: Game development tools

}
pDoc->setNextWaypointID(m_maxWaypoint);

Expand All @@ -1385,6 +1412,11 @@ void ScriptDialog::OnLoad()
REF_PTR_RELEASE(pUndo); // belongs to pDoc now.
m_sides = *TheSidesList;

// TheSuperHackers @bugfix OmarAglan Add imported players at their map indices after parsing succeeds.
for (Int sideIndex = sidesBeforeImport.getNumSides(); sideIndex < m_sides.getNumSides(); sideIndex++) {
addPlayer(sideIndex);
}

if (m_firstReadObject) {
AddObjectUndoable *pUndo = new AddObjectUndoable(pDoc, m_firstReadObject);
pDoc->AddAndDoUndoable(pUndo);
Expand All @@ -1409,9 +1441,10 @@ void ScriptDialog::OnLoad()
curSide = m_curSelection.m_playerIndex;
} else {
Int j;
// TheSuperHackers @bugfix OmarAglan Match each imported player to the current map side by name.
for (j=0; j<m_sides.getNumSides(); j++) {
AsciiString name = m_sides.getSideInfo(i)->getDict()->getAsciiString(TheKey_playerName);
if (name == m_readPlayerNames[j]) {
AsciiString name = m_sides.getSideInfo(j)->getDict()->getAsciiString(TheKey_playerName);
if (name == m_readPlayerNames[i]) {
curSide = j;
break;
}
Expand Down Expand Up @@ -1455,15 +1488,23 @@ void ScriptDialog::OnLoad()
scripts[i]->discard(); /* Frees the script list, but none of it's children, as they have been
copied into the current scripts. */
scripts[i] = nullptr;
reloadPlayer(curSide, pSL);
//reloadPlayer(curSide, pSL);
} else {
deleteInstance(scripts[i]);
scripts[i] = nullptr;
}
}

for (i = 0; i < m_sides.getNumSides(); i++) {
// Make sure that the dialog tree is updated.
ScriptList *pSL = m_sides.getSideInfo(i)->getScriptList();
reloadPlayer(i, pSL);
updateIcons(TVI_ROOT);
}


} catch(...) {
DEBUG_CRASH(("threw exception in ScriptDialog::OnLoad"));
::AfxMessageBox("Unable to import scripts. The file contains invalid data or exceeds the player limit.", MB_OK);
}
}

Expand Down Expand Up @@ -1607,15 +1648,19 @@ Bool ScriptDialog::ParseTeamsDataChunk(DataChunkInput &file, DataChunkInfo *info
TeamsInfo ti;
ti.init(&teamDict);
CFixTeamOwnerDialog fix(&ti, &pThis->m_sides);
bool nameSet = false;
if (fix.DoModal() == IDOK) {
if (fix.pickedValidTeam()) {
teamDict.setAsciiString(TheKey_teamOwner, fix.getSelectedOwner());
nameSet = true;
}
}

AsciiString neutralPlayerName; // neutral player name is empty string
// player doesn't exist, so add it to the neutral player.
teamDict.setAsciiString(TheKey_teamOwner, neutralPlayerName);
if (nameSet == false) {
AsciiString neutralPlayerName; // neutral player name is empty string
// player doesn't exist, so add it to the neutral player.
teamDict.setAsciiString(TheKey_teamOwner, neutralPlayerName);
}
pThis->m_sides.addTeam(&teamDict);
}
}
Expand All @@ -1632,14 +1677,54 @@ Bool ScriptDialog::ParseTeamsDataChunk(DataChunkInput &file, DataChunkInfo *info
Bool ScriptDialog::ParsePlayersDataChunk(DataChunkInput &file, DataChunkInfo *info, void *userData)
{
ScriptDialog *pThis = (ScriptDialog *)userData;
Int readDicts = 0;
if (info->version >= K_PLAYERS_NAMES_FOR_SCRIPTS_VERSION_2) {
readDicts = file.readInt();
}
Int numNames = file.readInt();
// TheSuperHackers @bugfix OmarAglan Reject player counts that cannot fit in the import array.
if (numNames < 0 || numNames > MAX_PLAYER_COUNT) {
return false;
}
Int i;
for (i=0; i<numNames; i++) {
if (i>=MAX_PLAYER_COUNT) break;
pThis->m_readPlayerNames[i] = file.readAsciiString();
if (readDicts) {
Dict sideDict = file.readDict();
// TheSuperHackers @bugfix OmarAglan Validate the dictionary identity before adding a player.
Bool hasPlayerName;
AsciiString playerName = sideDict.getAsciiString(TheKey_playerName, &hasPlayerName);
if (!hasPlayerName || playerName != pThis->m_readPlayerNames[i]) {
return false;
}
bool nameFound = false;
for (Int j=0; j < pThis->m_sides.getNumSides(); j++) {
AsciiString name = pThis->m_sides.getSideInfo(j)->getDict()->getAsciiString(TheKey_playerName);

if (name == pThis->m_readPlayerNames[i]) {
// The side already exists so don't add it or overwrite the old data.
nameFound = true;
break;
}
}
if (nameFound == false) {
// TheSuperHackers @bugfix OmarAglan Check capacity before adding a side and using its script list.
if (pThis->m_sides.getNumSides() >= MAX_PLAYER_COUNT) {
return false;
}
// This side doesn't currently exist, so add it.
pThis->m_sides.addSide(&sideDict);
SidesInfo* sides = pThis->m_sides.findSideInfo(playerName);
if (sides == nullptr) {
return false;
}
ScriptList* pList = newInstance(ScriptList);
// A script list must be created.
sides->setScriptList(pList);
}
}
}
DEBUG_ASSERTCRASH(file.atEndOfChunk(), ("Unexpected data left over."));
return true;
return file.atEndOfChunk();
}

/**
Expand Down
7 changes: 4 additions & 3 deletions GeneralsMD/Code/Tools/WorldBuilder/res/WorldBuilder.rc
Original file line number Diff line number Diff line change
Expand Up @@ -1322,7 +1322,8 @@ BEGIN
225,117,8
END

IDD_EXPORT_SCRIPTS_OPTIONS DIALOG DISCARDABLE 0, 0, 186, 153
// TheSuperHackers @fix OmarAglan Keep the export buttons inside the expanded dialog.
IDD_EXPORT_SCRIPTS_OPTIONS DIALOG DISCARDABLE 0, 0, 186, 173
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Export Script Options"
FONT 8, "MS Sans Serif"
Expand All @@ -1342,7 +1343,7 @@ BEGIN
CONTROL "Export selected scripts.",IDC_SELECTED_SCRIPTS,"Button",
BS_AUTORADIOBUTTON,15,127,99,8
GROUPBOX "Export Mode:",IDC_STATIC,7,104,172,39
CONTROL "Include sides.",IDC_SIDES,"Button",BS_AUTOCHECKBOX |
CONTROL "Include sides.",IDC_SIDES,"Button",BS_AUTOCHECKBOX |
WS_TABSTOP,12,79,154,9
END

Expand Down Expand Up @@ -2208,7 +2209,7 @@ BEGIN
LEFTMARGIN, 7
RIGHTMARGIN, 179
TOPMARGIN, 7
BOTTOMMARGIN, 146
BOTTOMMARGIN, 166
END

IDD_GROVE_OPTIONS, DIALOG
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ BOOL ExportScriptsOptions::OnInitDialog()
pButton = (CButton*)GetDlgItem(IDC_SELECTED_SCRIPTS);
pButton->SetCheck(m_allScripts?0:1);
pButton = (CButton*)GetDlgItem(IDC_SIDES);
#if RTS_GENERALS && RETAIL_COMPATIBLE_DATA
// Retail Generals script files do not store side dictionaries.
m_sides = false;
pButton->EnableWindow(FALSE);
#endif
pButton->SetCheck(m_sides?1:0);

return TRUE; // return TRUE unless you set the focus to a control
Expand Down
Loading
Loading