Skip to content

Map Editor feature#526

Open
valentinoConti wants to merge 4 commits into
sven-n:mainfrom
valentinoConti:feature/map-editor
Open

Map Editor feature#526
valentinoConti wants to merge 4 commits into
sven-n:mainfrom
valentinoConti:feature/map-editor

Conversation

@valentinoConti

@valentinoConti valentinoConti commented Jul 13, 2026

Copy link
Copy Markdown

Summary

This PR adds a huge "Map Editor" with multiple features:

  • Adds a zoom-in / zoom-out feature for the Mu Editor build UI.
  • Adds a map textures editor
  • Adds a map objects editor
  • Adds a map height editor
  • Adds a map attributes editor
  • Adds a minimap generator (or most precisely, a top view camera positioning that lets you see ALL the map at once as some kind of god-mode, with tools to help you add/edit the minimap for a map)

All of those can be seen working in this video attached in the Discord channel: https://discord.com/channels/669595902750490698/1297814156275093566/1527317348489363628

Checklist

  • I have read and followed docs/CODING_RULES.md.
  • I have built the project locally (see docs/build/README.md).
  • Changes are focused on one concern
  • The diff is as small as it reasonably can be. <-- not sure it's possible in this case hehe.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, 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

  • Map Editor UI: Introduced a comprehensive in-game Map Editor with dedicated tabs for managing terrain textures, objects, height, and walkability attributes.
  • UI Scaling: Added global UI scaling functionality to the editor interface, allowing for better accessibility and visibility.
  • Minimap Generation: Implemented a top-down camera mode that allows for full-map rendering to capture high-quality minimap screenshots.
  • Persistence and Encryption: Added robust save logic for terrain mapping, attributes, and objects, including necessary encryption to match the game's native file formats.
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 Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

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.

Suggested change
for (int k = 0; k < Models[slot].NumMeshs; ++k)
for (int k = 0; k < Models[slot].NumTextures; ++k)

Comment on lines +42 to +43
for (int i = 0; i < CELLS; ++i)
plain[p++] = static_cast<BYTE>(TerrainMappingAlpha[i] * 255.f);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

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.

Suggested change
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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

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());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

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.

Suggested change
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());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

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.

Suggested change
ImGui::SetTooltip("%d: %ls", m.type, m.file.c_str());
ImGui::SetTooltip("%d: %s", m.type, StringUtils::WideToNarrow(m.file).c_str());

Comment on lines +738 to +739
char buf[128];
snprintf(buf, sizeof(buf), "Wrote %ls (next to Main.exe) - run it on the DB, then restart the server.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

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());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

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.

Suggested change
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());

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.

1 participant