Map Editor feature#526
Conversation
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces a powerful in-game Map Editor, enabling developers to modify terrain textures, height, walkability attributes, and world objects directly within the client. It includes a minimap generator and comprehensive save functionality to ensure changes are persisted to the game's data files, significantly streamlining the map development workflow. Highlights
New Features🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces an in-game Map Editor (MuEditor) for editing terrain textures, height, walkability attributes, and world objects, along with support for capturing minimap screenshots. The review feedback identifies several critical issues, including potential out-of-bounds array access in MapObjectImport.cpp (due to using NumMeshs instead of NumTextures and indexing with MAX_MODELS), undefined behavior when casting un-clamped alpha values in MapEditorSave.cpp, and multiple instances of unsafe wide-string formatting (%ls) with snprintf and ImGui::SetTooltip that could lead to rendering issues or crashes.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
|
|
||
| CopyIfPresent(srcDir + bmdFile, dstDir + ObjectBmdStem(slot) + L".bmd", /*overwrite*/ true); | ||
|
|
||
| for (int k = 0; k < Models[slot].NumMeshs; ++k) |
There was a problem hiding this comment.
The loop iterates up to NumMeshs but accesses Models[slot].Textures[k]. In the MU client, the number of textures loaded for a model is stored in NumTextures, not NumMeshs. If NumMeshs is greater than NumTextures (or greater than the size of the Textures array), this will cause out-of-bounds array access or memory corruption.
| for (int k = 0; k < Models[slot].NumMeshs; ++k) | |
| for (int k = 0; k < Models[slot].NumTextures; ++k) |
| for (int i = 0; i < CELLS; ++i) | ||
| plain[p++] = static_cast<BYTE>(TerrainMappingAlpha[i] * 255.f); |
There was a problem hiding this comment.
If TerrainMappingAlpha[i] is outside the range [0.0f, 1.0f] (or is NaN), multiplying by 255.f and casting to BYTE results in undefined behavior in C++. It is safer to clamp the value to [0.0f, 1.0f] before scaling and casting.
| for (int i = 0; i < CELLS; ++i) | |
| plain[p++] = static_cast<BYTE>(TerrainMappingAlpha[i] * 255.f); | |
| for (int i = 0; i < CELLS; ++i) | |
| { | |
| float alpha = TerrainMappingAlpha[i]; | |
| plain[p++] = alpha < 0.0f ? 0 : (alpha > 1.0f ? 255 : static_cast<BYTE>(alpha * 255.f)); | |
| } |
| // Models[] is over-allocated past MAX_MODELS, so this index is always valid | ||
| // memory and sits outside the world-object range (0..159), so it never shows | ||
| // up as a placeable model on the current map. | ||
| const int scratch = MAX_MODELS; |
There was a problem hiding this comment.
The code uses MAX_MODELS as an index into the Models array (Models[scratch]). If Models is declared as BMD Models[MAX_MODELS], then Models[MAX_MODELS] is out of bounds (valid indices are 0 to MAX_MODELS - 1). Relying on over-allocation of a global array is highly unsafe and can lead to memory corruption or crashes if the array size is strictly enforced or changed. Consider using a dedicated slot or ensuring MAX_MODELS is increased in the global definition.
| for (const auto& m : m_models) | ||
| if (m.type == m_selectedModelType) | ||
| { | ||
| snprintf(selName, sizeof(selName), "%d: %ls", m.type, m.file.c_str()); |
There was a problem hiding this comment.
Using %ls with snprintf to format wide strings (std::wstring) into narrow UTF-8 strings is unsafe and incorrect. ImGui expects UTF-8 narrow strings, but %ls in snprintf converts to the local ANSI code page, which will cause rendering issues (mojibake) for non-ASCII characters. Additionally, %ls behavior in narrow vsnprintf is platform-dependent and can fail or truncate. Use StringUtils::WideToNarrow to convert the wide string to a UTF-8 std::string and format it using %s.
| snprintf(selName, sizeof(selName), "%d: %ls", m.type, m.file.c_str()); | |
| snprintf(selName, sizeof(selName), "%d: %s", m.type, StringUtils::WideToNarrow(m.file).c_str()); |
| if (selected) | ||
| ImGui::PopStyleColor(); | ||
| if (ImGui::IsItemHovered()) | ||
| ImGui::SetTooltip("%d: %ls", m.type, m.file.c_str()); |
There was a problem hiding this comment.
Using %ls with ImGui::SetTooltip to format wide strings (std::wstring) into narrow UTF-8 strings is unsafe and incorrect. ImGui expects UTF-8 narrow strings, but %ls in snprintf converts to the local ANSI code page, which will cause rendering issues (mojibake) for non-ASCII characters. Additionally, %ls behavior in narrow vsnprintf is platform-dependent and can fail or truncate. Use StringUtils::WideToNarrow to convert the wide string to a UTF-8 std::string and format it using %s.
| ImGui::SetTooltip("%d: %ls", m.type, m.file.c_str()); | |
| ImGui::SetTooltip("%d: %s", m.type, StringUtils::WideToNarrow(m.file).c_str()); |
| char buf[128]; | ||
| snprintf(buf, sizeof(buf), "Wrote %ls (next to Main.exe) - run it on the DB, then restart the server.", |
There was a problem hiding this comment.
Using %ls with snprintf to format wide strings (std::wstring) into narrow UTF-8 strings is unsafe and incorrect. ImGui expects UTF-8 narrow strings, but %ls in snprintf converts to the local ANSI code page, which will cause rendering issues (mojibake) for non-ASCII characters. Additionally, %ls behavior in narrow vsnprintf is platform-dependent and can fail or truncate. Use StringUtils::WideToNarrow to convert the wide string to a UTF-8 std::string and format it using %s.
snprintf(buf, sizeof(buf), "Wrote %s (next to Main.exe) - run it on the DB, then restart the server.",
StringUtils::WideToNarrow(path).c_str());| } | ||
|
|
||
| char msg[192]; | ||
| snprintf(msg, sizeof(msg), "[MapEditor] Also saved a copy next to Main.exe: %ls", dst.wstring().c_str()); |
There was a problem hiding this comment.
Using %ls with snprintf to format wide strings (std::wstring) into narrow UTF-8 strings is unsafe and incorrect. ImGui expects UTF-8 narrow strings, but %ls in snprintf converts to the local ANSI code page, which will cause rendering issues (mojibake) for non-ASCII characters. Additionally, %ls behavior in narrow vsnprintf is platform-dependent and can fail or truncate. Use StringUtils::WideToNarrow to convert the wide string to a UTF-8 std::string and format it using %s.
| snprintf(msg, sizeof(msg), "[MapEditor] Also saved a copy next to Main.exe: %ls", dst.wstring().c_str()); | |
| snprintf(msg, sizeof(msg), "[MapEditor] Also saved a copy next to Main.exe: %s", StringUtils::WideToNarrow(dst.wstring()).c_str()); |
Summary
This PR adds a huge "Map Editor" with multiple features:
All of those can be seen working in this video attached in the Discord channel: https://discord.com/channels/669595902750490698/1297814156275093566/1527317348489363628
Checklist
docs/CODING_RULES.md.docs/build/README.md).<-- not sure it's possible in this case hehe.