Skip to content

unify(worldbuilder): Merge Generals and Zero Hour WorldBuilder - #3025

Open
OmarAglan wants to merge 2 commits into
TheSuperHackers:mainfrom
OmarAglan:unify/worldbuilder-merge
Open

unify(worldbuilder): Merge Generals and Zero Hour WorldBuilder#3025
OmarAglan wants to merge 2 commits into
TheSuperHackers:mainfrom
OmarAglan:unify/worldbuilder-merge

Conversation

@OmarAglan

@OmarAglan OmarAglan commented Jul 28, 2026

Copy link
Copy Markdown

Merge with Rebase

This merges the Generals and Zero Hour WorldBuilder implementations so the duplicate editor trees can be moved to Core in the final pull request.

The Zero Hour implementation is used as the shared base. Target-specific conditions preserve the existing behavior of each game:

  • Generals gains layers, the ruler, team object properties, ambient sound editing, and the expanded editor resources.
  • Zero Hour gains the camera-direction controls and raw tile-data access from Generals.
  • Generals continues writing BlendTileData version 7, while Zero Hour continues writing version 8.
  • Zero Hour-only editor behavior is guarded with !RTS_GENERALS.
  • Generals gains the ThingTemplate accessors and ambient-sound map property keys required by the merged editor.
  • Each game keeps its intentional product-specific resources and behavior.

@greptile-apps

greptile-apps Bot commented Jul 28, 2026

Copy link
Copy Markdown

Greptile Summary

This pull aligns the separate Generals and Zero Hour WorldBuilder implementations.

  • Adds layers, ruler tooling, expanded object and team properties, ambient-sound editing, and additional visualization controls to Generals.
  • Aligns WorldBuilder resources, build manifests, script editing, trigger handling, and map serialization while retaining target-specific behavior.
  • Fixes layer-rename synchronization for objects, polygon triggers, and the active-layer name.
  • Removes invalid polygon triggers from layer membership before unlinking them globally.

Confidence Score: 3/5

The PR is not yet safe to merge because script-bundle round trips still lose polygon-trigger layer assignments.

The layer-rename and invalid-trigger cleanup issues are fixed at current HEAD, but ScriptDialog still writes and reads polygon triggers using a format with no layer-name field and imports them without registering layer membership.

Files Needing Attention: Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp

Important Files Changed

Filename Overview
Generals/Code/Tools/WorldBuilder/src/LayersList.cpp Adds polygon-trigger layer membership and active-layer behavior, and now synchronizes persisted assignments when layers are renamed.
Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp Expands script verification and side-bundle handling while retaining the existing version-3 polygon-trigger bundle format.
Generals/Code/Tools/WorldBuilder/src/WaypointOptions.cpp Safely removes invalid triggers from layer membership before unlinking them from the global trigger list.
Generals/Code/Tools/WorldBuilder/src/WaterOptions.cpp Applies the corresponding layer-membership cleanup when invalid water triggers are removed.
Generals/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp Adds trigger-layer serialization support while preserving retail Generals map compatibility through conditional version selection.

Reviews (10): Last reviewed commit: "unify(worldbuilder): Merge WorldBuilder" | Re-trigger Greptile

@OmarAglan
OmarAglan force-pushed the unify/worldbuilder-merge branch from c56bc00 to bc2980a Compare July 28, 2026 11:12
Comment thread Generals/Code/Tools/WorldBuilder/src/LayersList.cpp Outdated
@OmarAglan
OmarAglan force-pushed the unify/worldbuilder-merge branch from bc2980a to 0a40354 Compare July 28, 2026 11:17
Comment thread Generals/Code/Tools/WorldBuilder/src/LayersList.cpp Outdated

@stephanmeesters stephanmeesters left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Incomplete review

Comment thread Generals/Code/Tools/WorldBuilder/include/addplayerdialog.h Outdated
Comment thread Generals/Code/Tools/WorldBuilder/include/CFixTeamOwnerDialog.h Outdated
/***************POLYGON TRIGGERS DATA ***************/
chunkWriter.openDataChunk("PolygonTriggers", K_TRIGGERS_VERSION_3);
// Version 4 preserves polygon trigger layer assignments in script bundles.
chunkWriter.openDataChunk("PolygonTriggers", K_TRIGGERS_VERSION_4);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Safe to do this?

@OmarAglan OmarAglan Jul 29, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

well, as the added comments says version 4 is required because this record now contains the layer name and The updated reader only consumes it for version 4 and continues to load versions 1 to 3. note that old builds will not understand newly written version 4 bundles. so it probably safe.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

more explanation: adding layerName changes the serialized record layout, so keeping version 3 would cause the following trigger ID and flags to be read at the wrong offsets. Both writers and readers are updated together, and the reader only consumes layerName for version 4 or newer, so existing version 1–3 bundles still load.

The expected limitation is that older WorldBuilder builds cannot read bundles written in version 4.

this can be another pr that fix this, if needed

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think that the retail version of vanilla Generals will not be able to open the maps saved with the version 4.

Could be discussed first but I suggest to introduce a new macro in GameDefines.h RETAIL_COMPATIBLE_WORLDBUILDER (enabled by default). So for PolygonTriggers it should keep using version 3 for generalsv when saving maps when that macro is enabled.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Another option would be to keep the existing PolygonTriggers chunk at version 3 and store layer assignments in a separate optional chunk instead of changing the original record layout.

For example:

PolygonTriggers (version 3)
    trigger count
    trigger name
    trigger ID
    water and river flags
    points
    ...

PolygonTriggerLayers (version 1)
    assignment count
    trigger name
    layer name
    ...

The new WorldBuilder would write and register parsers for both chunks. It would load the version 3 triggers first, then apply the layer assignments from PolygonTriggerLayers.

Trigger names would probably be the safest key here because WorldBuilder already treats them as unique identifiers, and the script-bundle importer does not preserve the serialized trigger IDs.

The existing DataChunkInput::parse() implementation skips chunks for which no parser is registered, so retail WorldBuilder should read the version 3 PolygonTriggers data and ignore PolygonTriggerLayers. This would need to be confirmed with an actual retail WorldBuilder test.

The same extension chunk could be used for exported script bundles, allowing their main PolygonTriggers chunk to remain at version 3 as well.

it has big tradeoffs like for example, Saving the map again with retail WorldBuilder would remove the unsupported layer-assignment chunk, and Both the map loader and script-bundle importer would need to register the new parser.
we also should consider missing or invalid trigger names would need to fall back safely to the default trigger layer, and we would need to test maps and script bundles in both the new and retail WorldBuilder builds.

this would preserve retail readability without requiring users to disable a compatibility macro, while Zero Hour could retain its existing version 4 format.

But its complex, and im not in favor of it really, a clean change would be a macro as you suggested, we need another opinion,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Why is this logic added to GeneralsMD, while Generals doesn't have a version 4? This doesn't seem right

Comment thread Generals/Code/Tools/WorldBuilder/src/WBHeightMap.cpp
Comment thread Generals/Code/Tools/WorldBuilder/src/WBHeightMap.cpp
@OmarAglan
OmarAglan force-pushed the unify/worldbuilder-merge branch 3 times, most recently from 7f8a34a to 09aa190 Compare July 29, 2026 08:43
@OmarAglan

Copy link
Copy Markdown
Author

While reviewing the code yesterday, I found a few issues that are worth addressing. I don’t think they all need to be fixed in this PR unless they are required for correctness; the remaining items could be handled in one or two focused follow up PRs.

This points to the Generals implementation, but the corresponding Zero Hour files should be checked as well.

  1. Preserve the default trigger layer during reload

    In WorldBuilderDoc.cpp:403, triggers belonging to the default layer are serialized with an empty layer name. During reload, addPolygonTriggerToLayersList() tries the previously active layer before falling back to the default trigger layer. If a map is reopened after another layer was selected, default layer triggers can be moved into that active layer.

    The script-bundle import path in ScriptDialog.cpp:1565 has the same ambiguity.

  2. Guard zero-parameter entries before opening the parameter editor

    The new rich edit keyboard path in EditAction.cpp:434 calls EditParameter::edit() without first checking whether the action has any parameters. Several common actions, including victory, defeat, and null operations, have no parameters. Typing while one of these entries is focused can therefore pass a null pointer to the editor and cause a crash.

    The same issue appears in the condition editor at EditCondition.cpp:437.

  3. Apply visibility settings to optimized trees

    In wbview3d.cpp:1017, updateTrees() adds optimized trees without checking FLAG_DONT_RENDER or the Show Models setting. Hiding a layer only updates the optimized tree’s position, while rebuilding the tree list after disabling models adds it again.

    As a result, optimized trees can remain visible after their layer or all models has been hidden.

  4. Keep the default sound index valid in the sorted combo box

    In mapobjectprops.cpp:1733, IDC_SOUND_COMBO uses CBS_SORT. The code stores the index returned when inserting Default, then inserts (None) afterward. Because the combo box sorts its contents, the second insertion can move Default and make the stored index stale.

    On the first UI refresh, this can remove an actual sound instead of Default, leave duplicate default entries, or save the default label as an ambient sound name.

  5. Initialize the ruler length before circle mode uses it

    In RulerTool.cpp:38, m_savedLength is not initialized. Switching directly to circle mode before measuring a line reads that value through getLength() and uses it as the radius, which can produce unpredictable measurements or geometry.

@Skyaero42 Skyaero42 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I stopped reviewing at some point

  • There are quite a few issues with the merge.
  • Particularly, I see code added to GeneralsMD files that I'm unable to find in Generals.
  • The size of this review (> 10k changes) makes it hard to review. I recommend smaller changes.

while (count>0) {
count--;
triggerName = file.readAsciiString();
if (info->version >= K_TRIGGERS_VERSION_4) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think @stephanmeesters mentioned it already (but I don't see his comment appear here), this needs to be behind ZH guards or the Generals World Builder will no longer work properly.

#if RTS_ZEROHOUR
  if (info->version >= K_TRIGGERS_VERSION_4) {
    layerName = file.readAsciiString();
  }
#endif

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I kept the reader version gated rather than Zero Hour-only. With the updated writer logic in #3024, Generals writes version 3 by default, but it can still write version 4 when RETAIL_COMPATIBLE_WORLDBUILDER is disabled. The Generals reader therefore still needs to understand version 4. Zero Hour continues writing and reading version 4.

numPoints = file.readInt();
PolygonTrigger *pTrig = newInstance(PolygonTrigger)(numPoints+1);
pTrig->setTriggerName(triggerName);
if (info->version >= K_TRIGGERS_VERSION_4) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Needs to be behind RTS_ZEROHOUR guards

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The same reasoning applies here. Generals can receive version 4 data when compatibility is disabled, so it needs to apply the serialized layer name. The version check ensures that version 1–3 files retain their existing layout and behavior.

@@ -224,7 +233,7 @@ Bool PolygonTrigger::ParsePolygonTriggersDataChunk(DataChunkInput &file, DataChu
*/
void PolygonTrigger::WritePolygonTriggersDataChunk(DataChunkOutput &chunkWriter)
{
chunkWriter.openDataChunk("PolygonTriggers", K_TRIGGERS_VERSION_3);
chunkWriter.openDataChunk("PolygonTriggers", K_TRIGGERS_VERSION_4);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Needs to be behind RTS_ZEROHOUR guards

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Updated in #3024. Both writer implementations now use RETAIL_COMPATIBLE_WORLDBUILDER && RTS_GENERALS: Generals writes version 3 by default, while Zero Hour and non-compatible Generals builds write version 4.

@@ -234,6 +243,7 @@ void PolygonTrigger::WritePolygonTriggersDataChunk(DataChunkOutput &chunkWriter)
chunkWriter.writeInt(count);
for (pTrig=PolygonTrigger::getFirstPolygonTrigger(); pTrig; pTrig = pTrig->getNext()) {
chunkWriter.writeAsciiString(pTrig->getTriggerName());
chunkWriter.writeAsciiString(pTrig->getLayerName());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Needs to be behind RTS_ZEROHOUR guards

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Updated alongside the version selection. layerName is written only when the version 4 branch is selected, using !(RETAIL_COMPATIBLE_WORLDBUILDER && RTS_GENERALS) in both games.

Comment thread Generals/Code/Tools/WorldBuilder/CMakeLists.txt
pNextTrig = pTrig->getNext();
pTrig->setNextPoly(nullptr);
// Register the imported trigger with its serialized WorldBuilder layer.
TheLayersList->addPolygonTriggerToLayersList(pTrig, pTrig->getLayerName());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Why is this added to a GeneralsMD (zerohour) file, while it is a feature not present in Generals?

count--;
Bool isWater = false;
triggerName = file.readAsciiString();
if (info->version >= K_TRIGGERS_VERSION_4) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Why is this added to a GeneralsMD (zerohour) file, while it is a feature not present in Generals?

numPoints = file.readInt();
PolygonTrigger *pTrig = newInstance(PolygonTrigger)(numPoints+1);
pTrig->setTriggerName(triggerName);
if (info->version >= K_TRIGGERS_VERSION_4) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Why is this added to a GeneralsMD (zerohour) file, while it is a feature not present in Generals?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in #3024

Comment thread GeneralsMD/Code/Tools/WorldBuilder/src/RulerOptions.cpp
Comment thread GeneralsMD/Code/Tools/WorldBuilder/src/MainFrm.cpp
@OmarAglan

Copy link
Copy Markdown
Author

I stopped reviewing at some point

  • There are quite a few issues with the merge.
  • Particularly, I see code added to GeneralsMD files that I'm unable to find in Generals.
  • The size of this review (> 10k changes) makes it hard to review. I recommend smaller changes.

Ok i can spilt this pr to small review able pr, the question is how much?
I cant split it to 2 or 3 pr, what do you think?

@OmarAglan
OmarAglan force-pushed the unify/worldbuilder-merge branch 2 times, most recently from c30ce18 to 68d5ff3 Compare July 30, 2026 08:49
@greptile-apps

greptile-apps Bot commented Jul 30, 2026

Copy link
Copy Markdown

Want your agent to iterate on Greptile's feedback? Try greploops.

@OmarAglan
OmarAglan force-pushed the unify/worldbuilder-merge branch 2 times, most recently from 54a07b7 to 854debb Compare July 30, 2026 09:48
@OmarAglan
OmarAglan requested a review from Skyaero42 August 2, 2026 08:43
didMatch = true;
} else {
// Keep layer membership in sync when discarding the invalid trigger.
TheLayersList->removePolygonTriggerFromLayersList(pTrig);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

What is this? This does not come from Generals and is no merge.

flattenHeights();
}
#ifdef USE_FLAT_HEIGHT_MAP
#if USE_FLAT_HEIGHT_MAP

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This is not a merge


initSubsystem(TheScriptEngine, (ScriptEngine*)(new ScriptEngine()));

#if RTS_ZEROHOUR

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Minor: use !RTS_GENERALS (for if there was a game after ZH)

Several times in this change


CString str;
str.Format("Diameter (in feet): %f", length * 2.0f);
str.Format("Diameter (in feet): %f", length * 2.0f);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This is not a merge, or is it?


layerIt->layerName = AsciiString(ptvdi->item.pszText);
if (mCurrentlyEditingLabel.compareNoCase(AsciiString(TheDefaultLayerName.c_str())) == 0) {
AsciiString newLayerName(ptvdi->item.pszText);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This code is not a merge, or is it? I cannot find it in Generals.

break;
case Parameter::ANGLE:
break;
#if RTS_ZEROHOUR

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Why do all of these things need to be ZH specific?

@OmarAglan
OmarAglan force-pushed the unify/worldbuilder-merge branch from 854debb to ee08d1a Compare August 6, 2026 09:45
@OmarAglan

Copy link
Copy Markdown
Author

rebranded this branch on top of the updated #3024 and force-pushed it as a single merge commit, also removed the cleanups and fixes that were not part of the actual WorldBuilder merge, including:

  • the layer rename
  • invalid trigger
  • height map cleanup
  • ruler formatting
  • DrawObject and ScriptDialog

changed the Zero Hour-specific guards to !RTS_GENERALS. The only changes outside the WorldBuilder trees are the Generals ThingTemplate accessors and ambient-sound property keys required by the merged editor code.

reviewing is required.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants