Skip to content

feat(data): load XML data palletes, tilesets, brushes, etc.#32

Merged
karolak6612 merged 3 commits into
WIP-Brushesfrom
dev-brushes
Jun 4, 2026
Merged

feat(data): load XML data palletes, tilesets, brushes, etc.#32
karolak6612 merged 3 commits into
WIP-Brushesfrom
dev-brushes

Conversation

@karolak6612

@karolak6612 karolak6612 commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • Added automatic data directory detection for client versions.
    • Implemented version-specific data directory support for resource loading.
  • Bug Fixes

    • Fixed structural code issue in tile definitions.
  • Refactor

    • Improved brush preview card rendering in the tileset grid.
    • Enhanced data directory resolution logic for better resource management.
  • Chores

    • Removed legacy sample data files to streamline the data directory structure.

Refactored client data loading to support version-specific directories and added pulse animations to the UI. Removed obsolete sample data files.
- `ImguiMapEditor/Brushes/BrushRegistry.h` — added `getBorderTemplateCount()`
- `ImguiMapEditor/CMakeLists.txt` — updated copy command to use `../data` instead of `sample_data`
- `ImguiMapEditor/Domain/ClientVersion.cpp` — updated `getItemMetadataPath()` to support versioned data directories
- `ImguiMapEditor/Domain/ClientVersion.h` — added data directory normalization and auto-detection
- `ImguiMapEditor/Services/ClientVersionRegistry.cpp` — added automatic data directory detection during registry loading
- `ImguiMapEditor/Services/Map/MapLoadingService.cpp` — updated resource loading to use version-specific paths
- `ImguiMapEditor/Services/Map/MapLoadingService.h` — updated `tryLoadCreatures` and `tryLoadItems` signatures
- `ImguiMapEditor/UI/Widgets/TilesetGridWidget.cpp` — implemented `renderBrushCard` and added pulse animation for selected items
- `ImguiMapEditor/UI/Widgets/TilesetGridWidget.h` — added `renderBrushCard` method declaration
- `ImguiMapEditor/sample_data/background.jpg` — [deleted]
- `ImguiMapEditor/sample_data/borders/borders.xml` — [deleted]
- `ImguiMapEditor/sample_data/brushes/brushes.xml` — [deleted]
- `ImguiMapEditor/sample_data/creatures/creatures.xml` — [deleted]
- `ImguiMapEditor/sample_data/items/items.xml` — [deleted]
- `ImguiMapEditor/sample_data/materials.xml` — [deleted]
- `ImguiMapEditor/sample_data/palettes.xml` — [deleted]
@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: bcdb4027-f265-465c-880a-c4cf586ed196

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist

Copy link
Copy Markdown

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 more flexible and robust system for loading XML-based game data, such as palettes, tilesets, and brushes. By shifting from a hardcoded global data path to a version-aware approach, the editor can now correctly resolve resources specific to different client versions. Additionally, the UI has been polished to provide better visual cues when interacting with brushes, and redundant sample data files have been removed to streamline the repository.

Highlights

  • Data Directory Management: Implemented robust handling for version-specific data directories, including auto-detection and normalization of paths to ensure correct loading of XML resources like creatures and items.
  • Map Loading Service Improvements: Updated the loading service to prioritize version-specific data folders over global application defaults, enhancing flexibility for different client versions.
  • UI Enhancements: Refactored the brush card rendering in the tileset grid to support better visual feedback, including improved border rendering and fallback text handling.
  • Cleanup: Removed deprecated sample data files that are no longer required for the updated data loading architecture.
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.

@sourcery-ai

sourcery-ai Bot commented Jun 4, 2026

Copy link
Copy Markdown

Reviewer's Guide

Refactors tileset grid brush rendering into a reusable card renderer with improved visuals and pulse handling, introduces version-specific data directory logic for loading XML resources (materials, tilesets, palettes, creatures, items) and item metadata, normalizes client version data directories including auto-detection, wires new callbacks in MainWindow, and updates build/runtime data copying while cleaning up minor APIs and includes.

File-Level Changes

Change Details Files
Unify brush tile rendering into a reusable card renderer with pulse animation and text fallback for missing preview textures.
  • Introduced renderBrushCard helper in TilesetGridWidget with rounded card background, border, and internal padded image rendering using ResolvedBrushPreview.
  • Added text fallback rendering when preview textures are missing or invalid, including simple width-based truncation and centering.
  • Replaced inline brush grid background/selection/pulse drawing code in renderBrushGrid with calls to renderBrushCard, consolidating pulse timing using PULSE_DURATION and shared state.
  • Adjusted brush preview rendering to use getIconSize-derived tileSize and cursorPos from ImGui::GetCursorScreenPos().
ImguiMapEditor/UI/Widgets/TilesetGridWidget.cpp
ImguiMapEditor/UI/Widgets/TilesetGridWidget.h
Load creatures.xml and items.xml from a version-specific data directory in addition to existing search locations.
  • Extended MapLoadingService::tryLoadCreatures and tryLoadItems to accept a version_data_path and attempt loads from version_data_path/creatures/creatures.xml and version_data_path/items/items.xml.
  • Updated loadClientData to compute a version-specific data_dir path from ClientVersion::getDataDirectory, pass it into tryLoadCreatures/tryLoadItems, and log when resources are missing or loaded.
  • Ensured the BrushRegistry dependency for MapLoadingService uses the fully qualified ::MapEditor::Brushes namespace.
ImguiMapEditor/Services/Map/MapLoadingService.cpp
ImguiMapEditor/Services/Map/MapLoadingService.h
Use client version dataDirectory to load materials, tilesets, palettes and improve logging when data folders are missing or misconfigured.
  • Changed tileset/palette loading in MapLoadingService::loadClientData to resolve app_data_path from version_info->getDataDirectory under current_path()/data, handling both plain version folder names and paths that include a data/ prefix.
  • Added existence checks and early warning logs when app_data_path is empty or invalid, and more specific logs around materials.xml, tilesets, and palettes discovery.
  • Removed assumption of a single global data folder in favor of version-scoped directories.
ImguiMapEditor/Services/Map/MapLoadingService.cpp
Update ClientVersion to normalize and auto-detect data directories and to resolve item metadata from the version-specific data directory instead of client_path_.
  • Modified ClientVersion::getItemMetadataPath to prefer a user-provided custom_items_db_path, then attempt to resolve items.srv/items.otb from current_path()/data/[data_directory_] (handling possible embedded data/ prefix), and to avoid falling back to client_path_.
  • Updated ClientVersion::setDataDirectory to normalize inputs by stripping a leading data/ (or data\) component when present.
  • Added ClientVersion::autoDetectDataDirectory, which checks for a directory named by version_ under data/ and stores it when found.
ImguiMapEditor/Domain/ClientVersion.cpp
ImguiMapEditor/Domain/ClientVersion.h
Auto-detect data directories for saved client versions on startup and log detections.
  • Extended ClientVersionRegistry::loadDefaults to iterate loaded ClientVersion instances, call autoDetectDataDirectory for those without a data directory, and log any successfully detected directories.
ImguiMapEditor/Services/ClientVersionRegistry.cpp
Wire new UI and callbacks in MainWindow to support new map/open SEC flows.
  • Included NewMapPanel header in MainWindow and added std::function members for new_map_callback_ and open_sec_callback_, parameterized with NewMapPanel::State and a path/client version pair respectively.
ImguiMapEditor/Presentation/MainWindow.h
Expose additional brush/border metadata, fix minor syntax issues, and update includes.
  • Exposed BrushRegistry::getBorderTemplateCount to query the number of border templates.
  • Fixed a stray closing brace in Tile.cpp and removed a dangling sample_data directory by deleting several XML files from the sample_data tree.
  • Added StringCopy helper includes to EditTownsDialog and PreferencesDialog to support string buffer operations.
  • Adjusted CMakeLists to remove an extra closing parenthesis and to copy the full ../data tree into the runtime data folder instead of sample_data, updating comments accordingly.
ImguiMapEditor/Brushes/BrushRegistry.h
ImguiMapEditor/Domain/Tile.cpp
ImguiMapEditor/UI/Dialogs/EditTownsDialog.cpp
ImguiMapEditor/UI/PreferencesDialog.cpp
ImguiMapEditor/CMakeLists.txt
ImguiMapEditor/sample_data/borders/borders.xml
ImguiMapEditor/sample_data/brushes/brushes.xml
ImguiMapEditor/sample_data/creatures/creatures.xml
ImguiMapEditor/sample_data/items/items.xml
ImguiMapEditor/sample_data/materials.xml
ImguiMapEditor/sample_data/palettes.xml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@github-actions github-actions Bot added Type: Enhancement New feature or request. Area: Data Changes to data files (data/ — materials, clients.xml, menubar.xml). labels Jun 4, 2026

@sourcery-ai sourcery-ai Bot 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.

Hey - I've found 1 issue, and left some high level feedback:

  • The logic for resolving data_dir (handling data/ prefixes, building current_path()/data/..., existence checks) is now duplicated across ClientVersion::setDataDirectory, getItemMetadataPath, and MapLoadingService::loadClientData; consider extracting a shared helper to centralize this behavior and avoid future divergence.
  • In MapLoadingService::loadClientData, if a data_dir is configured but the corresponding directory does not exist, the code now skips materials/tilesets/palettes loading entirely; if that is not intentional, you may want to retain a fallback to the previous default (current_path()/data) to avoid regressions when the configuration is wrong or missing.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The logic for resolving `data_dir` (handling `data/` prefixes, building `current_path()/data/...`, existence checks) is now duplicated across `ClientVersion::setDataDirectory`, `getItemMetadataPath`, and `MapLoadingService::loadClientData`; consider extracting a shared helper to centralize this behavior and avoid future divergence.
- In `MapLoadingService::loadClientData`, if a `data_dir` is configured but the corresponding directory does not exist, the code now skips materials/tilesets/palettes loading entirely; if that is not intentional, you may want to retain a fallback to the previous default (`current_path()/data`) to avoid regressions when the configuration is wrong or missing.

## Individual Comments

### Comment 1
<location path="ImguiMapEditor/Services/Map/MapLoadingService.cpp" line_range="571-580" />
<code_context>

-  if (!tryLoadCreatures(map_dir, client_path)) {
+  // Compute version data path for creatures.xml and items.xml lookup
+  std::string data_dir_for_resources = version_info->getDataDirectory();
+  std::filesystem::path version_data_path;
+  if (!data_dir_for_resources.empty()) {
+    auto candidate = std::filesystem::current_path() / "data" / data_dir_for_resources;
+    if (std::filesystem::exists(candidate)) {
+      version_data_path = candidate;
+    }
+  }
+
+  if (!tryLoadCreatures(map_dir, client_path, version_data_path)) {
     spdlog::warn("No creature data loaded. Spawns may look incorrect.");
   }

-  if (!tryLoadItems(map_dir, client_path)) {
+  if (!tryLoadItems(map_dir, client_path, version_data_path)) {
     spdlog::warn("No items.xml loaded. Item names may be missing.");
   }
</code_context>
<issue_to_address>
**issue (bug_risk):** Data directory handling for creatures/items is inconsistent with tileset loading and may fail when `dataDirectory` contains a `data/` prefix.

Here `version_data_path` is built as `current_path() / "data" / data_dir_for_resources` without the normalization you use later for `app_data_path` (stripping a leading `data/`). If `dataDirectory` is `"data/1098"`, creatures/items are looked up under `.../data/data/1098` while tilesets/palettes use `.../data/1098`, so creature/item loading can silently fail for older configs or user-provided values. Please apply the same normalization logic here (or via a shared helper) so both paths are resolved consistently.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread ImguiMapEditor/Services/Map/MapLoadingService.cpp Outdated

@github-actions github-actions Bot 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.

📋 Review Summary

This PR introduces a significant improvement in how client data (palettes, tilesets, brushes, etc.) are managed by moving them from sample_data to a more structured data directory and adding auto-detection for version-specific data.

🔍 General Feedback

The transition to a more flexible data loading system is a great step. However, there are some concerns regarding the reliability of path resolution using current_path() and a potential bug in the fallback logic when a specific client's data directory is missing but a generic data/ directory exists.

Key Points:

  • 🔴 Path Resolution: Reliance on std::filesystem::current_path() to find application assets can lead to issues if the working directory is not the application root.
  • 🔴 Fallback Bug: In MapLoadingService::loadClientData, an empty metadata path can cause the fallback logic to incorrectly identify the data/ directory as a valid metadata file.
  • 🟡 Path Normalization: The normalization of data_directory_ is somewhat fragile and could be more robust.
  • 🟢 Decoupling: The addition of callbacks in MainWindow for map operations is a positive change for decoupling the UI from the application logic.

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request refactors client version and map loading services to support version-specific data directories for items, creatures, tilesets, and palettes. It also introduces automatic data directory detection and refactors the TilesetGridWidget to use a card-based rendering system with fallback text labels and pulse animations. The review feedback highlights several key improvement opportunities, including resolving path resolution inconsistencies in MapLoadingService, optimizing repeated ImGui::CalcTextSize calls in TilesetGridWidget to prevent performance bottlenecks, and ensuring the pulseBrush_ pointer is safely cleared when pulse animations expire to avoid stale pointer bugs.

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.

Comment on lines +570 to +578
// Compute version data path for creatures.xml and items.xml lookup
std::string data_dir_for_resources = version_info->getDataDirectory();
std::filesystem::path version_data_path;
if (!data_dir_for_resources.empty()) {
auto candidate = std::filesystem::current_path() / "data" / data_dir_for_resources;
if (std::filesystem::exists(candidate)) {
version_data_path = candidate;
}
}

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

The path resolution logic for version_data_path here does not handle the data/ prefix stripping fallback, unlike the logic used for app_data_path later in the function. If the configured data directory contains a data/ prefix, creatures.xml and items.xml will fail to load from the version data folder, while tilesets and palettes will succeed.

We should resolve this path robustly once at the beginning of the function and reuse it to eliminate duplicate code and prevent this bug.

Suggested change
// Compute version data path for creatures.xml and items.xml lookup
std::string data_dir_for_resources = version_info->getDataDirectory();
std::filesystem::path version_data_path;
if (!data_dir_for_resources.empty()) {
auto candidate = std::filesystem::current_path() / "data" / data_dir_for_resources;
if (std::filesystem::exists(candidate)) {
version_data_path = candidate;
}
}
// Compute version data path for creatures.xml and items.xml lookup
std::string data_dir = version_info->getDataDirectory();
std::filesystem::path version_data_path;
if (!data_dir.empty()) {
auto candidate = std::filesystem::current_path() / "data" / data_dir;
if (std::filesystem::exists(candidate)) {
version_data_path = candidate;
} else {
// Try stripping "data/" prefix if present
std::filesystem::path dir_path(data_dir);
if (dir_path.has_parent_path() && dir_path.parent_path().filename() == "data") {
auto stripped = std::filesystem::current_path() / "data" / dir_path.filename();
if (std::filesystem::exists(stripped)) {
version_data_path = stripped;
spdlog::warn("Data directory '{}' had 'data/' prefix, stripped to '{}'",
data_dir, dir_path.filename().string());
}
}
}
}

Comment on lines +136 to +142
if (textSize.x > MAX_TEXT_WIDTH) {
while (label.size() > 1 && ImGui::CalcTextSize((label + "...").c_str()).x > MAX_TEXT_WIDTH) {
label.pop_back();
}
label += "...";
textSize = ImGui::CalcTextSize(label.c_str());
}

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

Calling ImGui::CalcTextSize repeatedly in a linear loop inside the render function (which runs every frame) is highly inefficient and can cause a severe performance bottleneck when rendering many cards with fallback labels.

We can optimize this by estimating the truncation length first based on the average character width, which reduces the number of layout calculations to almost always 1 or 2 iterations.

    if (textSize.x > MAX_TEXT_WIDTH && !label.empty()) {
      float avgCharWidth = textSize.x / label.size();
      size_t estimatedLength = static_cast<size_t>(MAX_TEXT_WIDTH / avgCharWidth);
      if (estimatedLength > 3) {
        label = label.substr(0, estimatedLength - 3);
      } else {
        label = label.substr(0, 1);
      }
      while (label.size() > 1 && ImGui::CalcTextSize((label + "...").c_str()).x > MAX_TEXT_WIDTH) {
        label.pop_back();
      }
      label += "...";
      textSize = ImGui::CalcTextSize(label.c_str());
    }

Comment on lines +608 to +630
// Use version-specific data directory for tilesets, palettes, brushes, etc.
// dataDirectory should be just the folder name (e.g., "1098"), not a path.
// Handle both "1098" and "data/1098" gracefully.
std::string data_dir = version_info->getDataDirectory();
std::filesystem::path app_data_path;

bool tilesets_loaded = tileset_service_.loadMaterials(app_data_path);
if (!tilesets_loaded) {
spdlog::warn("materials.xml could not be loaded. Falling back to direct tileset/palette loading.");
tilesets_loaded = tileset_service_.loadTilesets(app_data_path);
if (!tilesets_loaded) {
spdlog::warn("No tilesets found. The palette will be empty.");
if (!data_dir.empty()) {
auto candidate = std::filesystem::current_path() / "data" / data_dir;
if (std::filesystem::exists(candidate)) {
app_data_path = candidate;
} else {
// Maybe data_dir already contains "data/" prefix — try stripping it
std::filesystem::path dir_path(data_dir);
if (dir_path.has_parent_path() && dir_path.parent_path().filename() == "data") {
auto stripped = std::filesystem::current_path() / "data" / dir_path.filename();
if (std::filesystem::exists(stripped)) {
app_data_path = stripped;
spdlog::warn("Data directory '{}' had 'data/' prefix, stripped to '{}'",
data_dir, dir_path.filename().string());
}
}
}
if (!tileset_service_.loadPalettes(app_data_path)) {
spdlog::warn("No palettes loaded. Ribbon palette buttons will be empty.");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Now that version_data_path is resolved robustly at the top of the function, we can completely eliminate this duplicate path resolution block and simply reuse version_data_path as app_data_path.

Suggested change
// Use version-specific data directory for tilesets, palettes, brushes, etc.
// dataDirectory should be just the folder name (e.g., "1098"), not a path.
// Handle both "1098" and "data/1098" gracefully.
std::string data_dir = version_info->getDataDirectory();
std::filesystem::path app_data_path;
bool tilesets_loaded = tileset_service_.loadMaterials(app_data_path);
if (!tilesets_loaded) {
spdlog::warn("materials.xml could not be loaded. Falling back to direct tileset/palette loading.");
tilesets_loaded = tileset_service_.loadTilesets(app_data_path);
if (!tilesets_loaded) {
spdlog::warn("No tilesets found. The palette will be empty.");
if (!data_dir.empty()) {
auto candidate = std::filesystem::current_path() / "data" / data_dir;
if (std::filesystem::exists(candidate)) {
app_data_path = candidate;
} else {
// Maybe data_dir already contains "data/" prefix — try stripping it
std::filesystem::path dir_path(data_dir);
if (dir_path.has_parent_path() && dir_path.parent_path().filename() == "data") {
auto stripped = std::filesystem::current_path() / "data" / dir_path.filename();
if (std::filesystem::exists(stripped)) {
app_data_path = stripped;
spdlog::warn("Data directory '{}' had 'data/' prefix, stripped to '{}'",
data_dir, dir_path.filename().string());
}
}
}
if (!tileset_service_.loadPalettes(app_data_path)) {
spdlog::warn("No palettes loaded. Ribbon palette buttons will be empty.");
}
// Use version-specific data directory for tilesets, palettes, brushes, etc.
const std::filesystem::path& app_data_path = version_data_path;

Comment on lines +394 to +398
if (pulseElapsed >= PULSE_DURATION) {
pulseBrushName_.clear();
pulseStartTime_ = -1.0f;
isPulsing = false;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

When the pulse duration expires, pulseBrush_ is not cleared (unlike in the original implementation). This leaves a dangling/stale pointer to the brush in the member variable, which can cause state bugs or crashes if the brush is reloaded or destroyed.

        if (pulseElapsed >= PULSE_DURATION) {
          pulseBrush_ = nullptr;
          pulseBrushName_.clear();
          pulseStartTime_ = -1.0f;
          isPulsing = false;
        }

Comment on lines 558 to 562
if (pulseElapsed >= PULSE_DURATION) {
pulseBrushName_.clear();
pulseStartTime_ = -1.0f;
isPulsing = false;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

When the pulse duration expires, pulseBrush_ is not cleared here either. We should reset it to nullptr to avoid holding a stale pointer.

        if (pulseElapsed >= PULSE_DURATION) {
          pulseBrush_ = nullptr;
          pulseBrushName_.clear();
          pulseStartTime_ = -1.0f;
          isPulsing = false;
        }

@github-actions github-actions Bot 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.

📋 Review Summary (Continued)

Adding missing comments from the previous review session.

Key Points:

  • 🔴 Bug in MapLoadingService: Fixed a potential issue where a directory could be loaded as a metadata file.
  • 🔴 Architecture: Further emphasized the need to avoid current_path().
  • 🟡 Normalization: Suggested improvements for data directory normalization.

@github-actions github-actions Bot 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.

📋 Review Summary (Final)

I have reviewed the changes in PR #32. Here is a consolidated list of the remaining points that could not be added as inline comments:

🔴 Critical Issues

  1. Fallback Bug in MapLoadingService::loadClientData (Line 490):
    If getItemMetadataPath() returns an empty path (e.g., when the specific data directory is missing), the fallback logic uses the data/ directory itself as the file path. Since the directory exists, it passes validation but will fail during loading.
    Suggestion: Fall back to a default filename like items.otb or items.srv before constructing the fallback path.

  2. Unreliable Path Resolution:
    The extensive use of std::filesystem::current_path() to locate application assets (in ClientVersion.cpp, ClientVersion.h, etc.) is fragile. If the application is launched from a different working directory, these paths will fail.
    Suggestion: Use paths relative to the executable or a centralized path service.

🟡 Minor Issues

  1. Fragile Normalization in setDataDirectory (ClientVersion.h):
    The current logic only handles a single level of the "data/" prefix and is case-sensitive.
  2. Redundant Validation in ClientVersion::validateFiles:
    The fallback check in validateFiles ignores the data_directory_ configuration, which is inconsistent with how the primary path is resolved.

🟢 Positive Changes

  • UI Decoupling: The use of callbacks for map operations in MainWindow is a good architectural improvement.
  • Auto-detection: The added logic for auto-detecting data directories based on client version simplifies configuration for the user.
  • Namespace fixes: Fixed potential namespace issues in MapLoadingService.cpp.
  • Cleanup: Fixed a premature namespace closure in Tile.cpp.

Overall, the PR is a great improvement but needs a few fixes for robustness.

@github-actions github-actions Bot added Type: Bug Inconsistencies or issues which will cause a problem for users. Area: Brushes Changes related to the brush system (ground, wall, doodad, etc.). Area: UI Changes to the wxWidgets GUI (palettes, dialogs, menus, toolbar). labels Jun 4, 2026

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ImguiMapEditor/CMakeLists.txt`:
- Around line 539-542: The CMake step that runs COMMAND ${CMAKE_COMMAND} -E
copy_directory from "${CMAKE_CURRENT_SOURCE_DIR}/../data" to
"$<TARGET_FILE_DIR:${PROJECT_NAME}>/data" assumes a repo-level data/ dir that
may be missing; update ImguiMapEditor/CMakeLists to guard this by checking if
the source exists (using EXISTS on "${CMAKE_CURRENT_SOURCE_DIR}/../data") and
either emit a clear message(FATAL_ERROR "...data directory not found...") or run
a fallback target that creates/downloads/populates the data directory before
attempting the copy; reference the existing COMMAND, CMAKE_CURRENT_SOURCE_DIR,
and PROJECT_NAME symbols so the guard is colocated with the copy_directory
invocation.

In `@ImguiMapEditor/Domain/ClientVersion.cpp`:
- Around line 48-49: The current unresolved-lookup return of {} drops the
metadata filename needed by downstream fallback, so instead of returning an
empty path in the failure branch, return a path constructed from
metadata_path.filename() (e.g., std::filesystem::path{metadata_path.filename()}
or equivalent) so client_path_ filename context is preserved; update the return
site in ClientVersion.cpp (the branch that currently does "return {}") to return
that filename-only path while leaving parent components empty so downstream code
can build ".../data/<filename>" properly.

In `@ImguiMapEditor/Domain/ClientVersion.h`:
- Around line 127-132: autoDetectDataDirectory currently uses
std::filesystem::current_path() which is unreliable; change it to resolve a
stable base path (either accept a base_path parameter or compute the executable
directory) before appending "data"/std::to_string(version_). Specifically update
the autoDetectDataDirectory function (and callers) to take a
std::filesystem::path base_path or use a platform-safe method to get the
executable path (e.g., read the running binary path via platform APIs
/proc/self/exe on Linux or GetModuleFileName on Windows) then test
std::filesystem::exists(base_path / "data" / std::to_string(version_)) and set
data_directory_ accordingly instead of using current_path(); reference the
symbols autoDetectDataDirectory, data_directory_, and version_ when making the
change.
- Around line 116-124: Update the inline comment for setDataDirectory in
ClientVersion.h to accurately describe its normalization: it reduces paths of
the form .../data/<version> (including absolute paths) to just the final segment
(assigning data_directory_), not only stripping a literal leading "data/"
prefix; mention that autoDetectDataDirectory() uses
std::filesystem::current_path() so detection and MapLoadingService loading
depend on the process working directory. Reference setDataDirectory,
autoDetectDataDirectory, data_directory_, and MapLoadingService in the comment
to make the behavior and dependency clear.

In `@ImguiMapEditor/Services/Map/MapLoadingService.cpp`:
- Around line 570-578: The lookup for creatures.xml/items.xml uses
data_dir_for_resources directly so it fails when getDataDirectory() returns a
path already prefixed with "data/" (or "data\\"); normalize that value before
building version_data_path. In the block that computes version_data_path,
canonicalize or strip a leading "data" component from data_dir_for_resources
(e.g., remove a leading "data/" or "data\\", or parse it as a
std::filesystem::path and use its filename or relative tail) so the candidate
path resolution (current_path() / "data" / data_dir_for_resources) matches the
same normalization used by the tileset/material resolution; update the code that
references data_dir_for_resources and version_data_path accordingly (symbols:
getDataDirectory, data_dir_for_resources, version_data_path).

In `@ImguiMapEditor/UI/Widgets/TilesetGridWidget.cpp`:
- Around line 386-399: Extract the duplicated pulse animation logic into a
private helper on TilesetGridWidget: add a declaration std::pair<bool,float>
computePulseState(const Brushes::IBrush* brush); to the class header, implement
computePulseState to use pulseBrushName_, pulseStartTime_, PULSE_DURATION and
ImGui::GetTime() to compute and return {isPulsing, pulseElapsed} while resetting
pulseBrushName_ and pulseStartTime_ when the duration elapses, and replace the
two identical blocks (the ones using isPulsing, pulseElapsed and
brush->getName() == pulseBrushName_) with calls to computePulseState(brush) to
unpack the returned pair.
- Around line 16-18: Remove the anonymous-namespace PULSE_DURATION constant and
replace its uses with the class-scoped constant
TilesetGridWidget::PULSE_DURATION; locate any references in
TilesetGridWidget.cpp that currently resolve to the local anonymous constant
(mentions in the review correspond to usages around the pulse/timing logic) and
change them to use TilesetGridWidget::PULSE_DURATION (or bare PULSE_DURATION if
inside TilesetGridWidget methods), ensuring only the single static constexpr
float TilesetGridWidget::PULSE_DURATION (the declaration in the header) remains.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e0a11d05-a22f-40ba-b27b-a277e0cc0c8f

📥 Commits

Reviewing files that changed from the base of the PR and between 04c3396 and fbda817.

⛔ Files ignored due to path filters (1)
  • ImguiMapEditor/sample_data/background.jpg is excluded by !**/*.jpg
📒 Files selected for processing (20)
  • ImguiMapEditor/Brushes/BrushRegistry.h
  • ImguiMapEditor/CMakeLists.txt
  • ImguiMapEditor/Domain/ClientVersion.cpp
  • ImguiMapEditor/Domain/ClientVersion.h
  • ImguiMapEditor/Domain/Tile.cpp
  • ImguiMapEditor/Presentation/MainWindow.h
  • ImguiMapEditor/Services/ClientVersionRegistry.cpp
  • ImguiMapEditor/Services/Map/MapLoadingService.cpp
  • ImguiMapEditor/Services/Map/MapLoadingService.h
  • ImguiMapEditor/UI/Dialogs/EditTownsDialog.cpp
  • ImguiMapEditor/UI/Dialogs/Properties/MapPropertiesDialog.cpp
  • ImguiMapEditor/UI/PreferencesDialog.cpp
  • ImguiMapEditor/UI/Widgets/TilesetGridWidget.cpp
  • ImguiMapEditor/UI/Widgets/TilesetGridWidget.h
  • ImguiMapEditor/sample_data/borders/borders.xml
  • ImguiMapEditor/sample_data/brushes/brushes.xml
  • ImguiMapEditor/sample_data/creatures/creatures.xml
  • ImguiMapEditor/sample_data/items/items.xml
  • ImguiMapEditor/sample_data/materials.xml
  • ImguiMapEditor/sample_data/palettes.xml
💤 Files with no reviewable changes (6)
  • ImguiMapEditor/sample_data/materials.xml
  • ImguiMapEditor/sample_data/palettes.xml
  • ImguiMapEditor/sample_data/borders/borders.xml
  • ImguiMapEditor/UI/Dialogs/Properties/MapPropertiesDialog.cpp
  • ImguiMapEditor/Domain/Tile.cpp
  • ImguiMapEditor/sample_data/creatures/creatures.xml

Comment thread ImguiMapEditor/CMakeLists.txt Outdated
Comment on lines 539 to 542
# Copy entire data directory tree (includes version folders, tilesets/, creatures/, items/, etc.)
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${CMAKE_CURRENT_SOURCE_DIR}/sample_data"
"${CMAKE_CURRENT_SOURCE_DIR}/../data"
"$<TARGET_FILE_DIR:${PROJECT_NAME}>/data"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify ../data exists relative to ImguiMapEditor/ and contains version subdirectories

if [ -d "data" ]; then
  echo "✓ data/ directory exists"
  echo "Contents:"
  fd -d 2 -t d . data/
else
  echo "✗ data/ directory not found relative to repository root"
  exit 1
fi

Repository: Open-Tibia-Tools/tibia-imgui-map-editor

Length of output: 139


Guard the CMake copy_directory source or ensure data/ is present

  • The path ${CMAKE_CURRENT_SOURCE_DIR}/../data (used in ImguiMapEditor/CMakeLists.txt:539-542) resolves to ${repo_root}/data, but this checkout has no data/ directory.
  • Add an EXISTS check (and a clear FATAL_ERROR if missing) or make the build generate/download the required data resources.
# Copy entire data directory tree (includes version folders, tilesets/, creatures/, items/, etc.)
COMMAND ${CMAKE_COMMAND} -E copy_directory
    "${CMAKE_CURRENT_SOURCE_DIR}/../data"
    "$<TARGET_FILE_DIR:${PROJECT_NAME}>/data"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ImguiMapEditor/CMakeLists.txt` around lines 539 - 542, The CMake step that
runs COMMAND ${CMAKE_COMMAND} -E copy_directory from
"${CMAKE_CURRENT_SOURCE_DIR}/../data" to
"$<TARGET_FILE_DIR:${PROJECT_NAME}>/data" assumes a repo-level data/ dir that
may be missing; update ImguiMapEditor/CMakeLists to guard this by checking if
the source exists (using EXISTS on "${CMAKE_CURRENT_SOURCE_DIR}/../data") and
either emit a clear message(FATAL_ERROR "...data directory not found...") or run
a fallback target that creates/downloads/populates the data directory before
attempting the copy; reference the existing COMMAND, CMAKE_CURRENT_SOURCE_DIR,
and PROJECT_NAME symbols so the guard is colocated with the copy_directory
invocation.

Comment on lines +48 to +49
// 3. No fallback — client_path_ is not used for items.otb
return {};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve metadata filename on unresolved lookup.

Returning an empty path here drops filename context needed by downstream fallback code (e.g., checks that build .../data/<filename> from metadata_path.filename()). This can mask missing metadata and weaken validation behavior.

Suggested fix
-    // 3. No fallback — client_path_ is not used for items.otb
-    return {};
+    // 3. Preserve filename fallback for downstream resolution (./data/<filename>)
+    switch (data_source_) {
+    case ItemDataSource::SRV:
+        return std::filesystem::path("items.srv");
+    case ItemDataSource::OTB:
+        return std::filesystem::path("items.otb");
+    case ItemDataSource::DAT:
+    default:
+        return {};
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ImguiMapEditor/Domain/ClientVersion.cpp` around lines 48 - 49, The current
unresolved-lookup return of {} drops the metadata filename needed by downstream
fallback, so instead of returning an empty path in the failure branch, return a
path constructed from metadata_path.filename() (e.g.,
std::filesystem::path{metadata_path.filename()} or equivalent) so client_path_
filename context is preserved; update the return site in ClientVersion.cpp (the
branch that currently does "return {}") to return that filename-only path while
leaving parent components empty so downstream code can build
".../data/<filename>" properly.

Comment on lines +116 to +124
void setDataDirectory(const std::string &dir) {
// Normalize: strip "data/" or "data\\" prefix if user typed it
std::filesystem::path p(dir);
if (p.has_parent_path() && p.parent_path().filename() == "data") {
data_directory_ = p.filename().string();
} else {
data_directory_ = dir;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check how setDataDirectory is called to understand expected input patterns

rg -nP 'setDataDirectory\(' -A2 -B2

Repository: Open-Tibia-Tools/tibia-imgui-map-editor

Length of output: 4373


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the relevant implementation area in ClientVersion.h
echo "---- ClientVersion.h (around setDataDirectory) ----"
sed -n '90,170p' ImguiMapEditor/Domain/ClientVersion.h

# Find all mentions of autoDetectDataDirectory
echo "---- autoDetectDataDirectory references ----"
rg -n "autoDetectDataDirectory" ImguiMapEditor/Domain/ClientVersion.h ImguiMapEditor -S --hidden

# Show where getDataDirectory / dataDirectory property is used (brief)
echo "---- getDataDirectory usages ----"
rg -n "getDataDirectory\(" ImguiMapEditor -S --hidden

echo "---- dataDirectory property persistence ----"
sed -n '1,220p' ImguiMapEditor/Services/ClientVersionPersistence.cpp

Repository: Open-Tibia-Tools/tibia-imgui-map-editor

Length of output: 12395


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the UI/editor code that feeds into setDataDirectory(...)
echo "---- ClientPropertyEditor.cpp (setDataDirectory call sites) ----"
sed -n '200,520p' ImguiMapEditor/UI/Dialogs/ClientConfiguration/ClientPropertyEditor.cpp

# Find where eb.dataDir is declared/filled (best-effort search)
echo "---- Search for 'dataDir' in ClientPropertyEditor.cpp ----"
rg -n "dataDir" ImguiMapEditor/UI/Dialogs/ClientConfiguration/ClientPropertyEditor.cpp

# Inspect UI controller where data_dir_buf_ is set from cv.getDataDirectory()
echo "---- ClientConfigurationController.cpp (data_dir_buf_ usage) ----"
sed -n '300,430p' ImguiMapEditor/Presentation/Dialogs/ClientConfigurationController.cpp

# Inspect StartupController client_info.data_directory usage
echo "---- StartupController.cpp (client_info.data_directory) ----"
sed -n '260,470p' ImguiMapEditor/Controllers/StartupController.cpp

# Inspect MapLoadingService usage to understand expected format
echo "---- MapLoadingService.cpp (data_dir_for_resources/data_dir) ----"
sed -n '520,680p' ImguiMapEditor/Services/Map/MapLoadingService.cpp

Repository: Open-Tibia-Tools/tibia-imgui-map-editor

Length of output: 37469


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '1,120p' ImguiMapEditor/Services/ClientVersionRegistry.cpp

Repository: Open-Tibia-Tools/tibia-imgui-map-editor

Length of output: 3729


Fix setDataDirectory comment to match its actual normalization behavior

ImguiMapEditor/Domain/ClientVersion.h (setDataDirectory, ~116-124) strips to the last path segment whenever the immediate parent directory is literally named data—so it’s not “prefix-only” (it also handles absolute selections like .../data/<version>), which aligns with how the UI/help text feeds the value and how MapLoadingService expects dataDirectory (folder name like "<version>"). Update the inline comment to describe this .../data/<version> behavior; the proposed “prefix-only” restriction would break valid absolute-folder inputs.

Also, autoDetectDataDirectory() depends on std::filesystem::current_path() for locating data/<version>, so detection (and similarly MapLoadingService loading) will be sensitive to the app’s working directory.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ImguiMapEditor/Domain/ClientVersion.h` around lines 116 - 124, Update the
inline comment for setDataDirectory in ClientVersion.h to accurately describe
its normalization: it reduces paths of the form .../data/<version> (including
absolute paths) to just the final segment (assigning data_directory_), not only
stripping a literal leading "data/" prefix; mention that
autoDetectDataDirectory() uses std::filesystem::current_path() so detection and
MapLoadingService loading depend on the process working directory. Reference
setDataDirectory, autoDetectDataDirectory, data_directory_, and
MapLoadingService in the comment to make the behavior and dependency clear.

Comment on lines +127 to +132
void autoDetectDataDirectory() {
auto candidate = std::filesystem::current_path() / "data" / std::to_string(version_);
if (std::filesystem::exists(candidate)) {
data_directory_ = std::to_string(version_);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

std::filesystem::current_path() is unreliable for directory detection.

current_path() depends on the process's working directory at launch, which varies by how the executable is invoked (CLI, double-click, debugger, etc.). If launched from a different directory, auto-detection will fail silently or detect the wrong path.

🔧 Use executable-relative or application base path instead
 void autoDetectDataDirectory() {
-  auto candidate = std::filesystem::current_path() / "data" / std::to_string(version_);
+  // Assuming a getApplicationBasePath() or similar exists that returns executable directory
+  auto candidate = getApplicationBasePath() / "data" / std::to_string(version_);
   if (std::filesystem::exists(candidate)) {
     data_directory_ = std::to_string(version_);
   }
 }

Alternatively, if the application already stores a base path elsewhere (e.g., in a configuration service), pass it as a parameter rather than relying on current_path().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ImguiMapEditor/Domain/ClientVersion.h` around lines 127 - 132,
autoDetectDataDirectory currently uses std::filesystem::current_path() which is
unreliable; change it to resolve a stable base path (either accept a base_path
parameter or compute the executable directory) before appending
"data"/std::to_string(version_). Specifically update the autoDetectDataDirectory
function (and callers) to take a std::filesystem::path base_path or use a
platform-safe method to get the executable path (e.g., read the running binary
path via platform APIs /proc/self/exe on Linux or GetModuleFileName on Windows)
then test std::filesystem::exists(base_path / "data" / std::to_string(version_))
and set data_directory_ accordingly instead of using current_path(); reference
the symbols autoDetectDataDirectory, data_directory_, and version_ when making
the change.

Comment on lines +570 to +578
// Compute version data path for creatures.xml and items.xml lookup
std::string data_dir_for_resources = version_info->getDataDirectory();
std::filesystem::path version_data_path;
if (!data_dir_for_resources.empty()) {
auto candidate = std::filesystem::current_path() / "data" / data_dir_for_resources;
if (std::filesystem::exists(candidate)) {
version_data_path = candidate;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Normalize data_dir_for_resources consistently before resolving version resource files.

This lookup path doesn’t handle "data/<version>"-prefixed directory values, while the later tileset/material resolution does. That inconsistency can skip versioned creatures.xml/items.xml even when the directory is valid.

Suggested fix
   std::string data_dir_for_resources = version_info->getDataDirectory();
   std::filesystem::path version_data_path;
   if (!data_dir_for_resources.empty()) {
     auto candidate = std::filesystem::current_path() / "data" / data_dir_for_resources;
     if (std::filesystem::exists(candidate)) {
       version_data_path = candidate;
+    } else {
+      std::filesystem::path dir_path(data_dir_for_resources);
+      if (dir_path.has_parent_path() && dir_path.parent_path().filename() == "data") {
+        auto stripped = std::filesystem::current_path() / "data" / dir_path.filename();
+        if (std::filesystem::exists(stripped)) {
+          version_data_path = stripped;
+        }
+      }
     }
   }
🧰 Tools
🪛 Clang (14.0.6)

[note] 573-573: +1, including nesting penalty of 0, nesting level increased to 1

(clang)


[note] 575-575: +2, including nesting penalty of 1, nesting level increased to 2

(clang)

🪛 Cppcheck (2.20.0)

[style] 573-573: The function 'hasChanges' is never used.

(unusedFunction)


[style] 578-578: The function 'clearChanges' is never used.

(unusedFunction)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ImguiMapEditor/Services/Map/MapLoadingService.cpp` around lines 570 - 578,
The lookup for creatures.xml/items.xml uses data_dir_for_resources directly so
it fails when getDataDirectory() returns a path already prefixed with "data/"
(or "data\\"); normalize that value before building version_data_path. In the
block that computes version_data_path, canonicalize or strip a leading "data"
component from data_dir_for_resources (e.g., remove a leading "data/" or
"data\\", or parse it as a std::filesystem::path and use its filename or
relative tail) so the candidate path resolution (current_path() / "data" /
data_dir_for_resources) matches the same normalization used by the
tileset/material resolution; update the code that references
data_dir_for_resources and version_data_path accordingly (symbols:
getDataDirectory, data_dir_for_resources, version_data_path).

Comment on lines +16 to +18
namespace {
constexpr float PULSE_DURATION = 1.5f;
} // namespace

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Critical: Duplicate PULSE_DURATION constant with conflicting value.

The anonymous namespace defines PULSE_DURATION = 1.5f, but the class header declares static constexpr float PULSE_DURATION = 2.0f at line 271. This creates two constants with different values, and the code currently uses the 1.5f version. Remove the anonymous namespace constant and use the class member instead.

🔧 Proposed fix
-namespace {
-constexpr float PULSE_DURATION = 1.5f;
-} // namespace
-
 `#include` "../../Brushes/BrushController.h"

Then update lines 150, 394, and 558 to use TilesetGridWidget::PULSE_DURATION or just PULSE_DURATION if within class scope.

🧰 Tools
🪛 Clang (14.0.6)

[warning] 17-17: floating point literal has suffix 'f', which is not uppercase

(readability-uppercase-literal-suffix)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ImguiMapEditor/UI/Widgets/TilesetGridWidget.cpp` around lines 16 - 18, Remove
the anonymous-namespace PULSE_DURATION constant and replace its uses with the
class-scoped constant TilesetGridWidget::PULSE_DURATION; locate any references
in TilesetGridWidget.cpp that currently resolve to the local anonymous constant
(mentions in the review correspond to usages around the pulse/timing logic) and
change them to use TilesetGridWidget::PULSE_DURATION (or bare PULSE_DURATION if
inside TilesetGridWidget methods), ensuring only the single static constexpr
float TilesetGridWidget::PULSE_DURATION (the declaration in the header) remains.

Comment on lines 386 to 399
bool isPulsing = isSelected && !pulseBrushName_.empty() &&
brush->getName() == pulseBrushName_;
float pulseElapsed = 0.0f;
if (isPulsing) {
float currentTime = ImGui::GetTime();
if (pulseStartTime_ < 0)
pulseStartTime_ = currentTime;
pulseElapsed = currentTime - pulseStartTime_;
if (pulseElapsed >= PULSE_DURATION) {
pulseBrushName_.clear();
pulseStartTime_ = -1.0f;
isPulsing = false;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Extract duplicated pulse animation logic into a helper method.

The pulse animation computation at lines 386-399 and 550-563 is identical. Extract this into a private helper method to eliminate duplication and improve maintainability.

♻️ Proposed refactoring

Add this private helper method declaration to the header:

std::pair<bool, float> computePulseState(const Brushes::IBrush *brush);

Add this implementation:

std::pair<bool, float> TilesetGridWidget::computePulseState(const Brushes::IBrush *brush) {
  bool isPulsing = !pulseBrushName_.empty() && brush && brush->getName() == pulseBrushName_;
  float pulseElapsed = 0.0f;
  
  if (isPulsing) {
    float currentTime = ImGui::GetTime();
    if (pulseStartTime_ < 0) {
      pulseStartTime_ = currentTime;
    }
    pulseElapsed = currentTime - pulseStartTime_;
    if (pulseElapsed >= PULSE_DURATION) {
      pulseBrushName_.clear();
      pulseStartTime_ = -1.0f;
      isPulsing = false;
    }
  }
  
  return {isPulsing, pulseElapsed};
}

Then replace both occurrences:

-      bool isPulsing = isSelected && !pulseBrushName_.empty() &&
-                       brush->getName() == pulseBrushName_;
-      float pulseElapsed = 0.0f;
-      if (isPulsing) {
-        float currentTime = ImGui::GetTime();
-        if (pulseStartTime_ < 0)
-          pulseStartTime_ = currentTime;
-        pulseElapsed = currentTime - pulseStartTime_;
-        if (pulseElapsed >= PULSE_DURATION) {
-          pulseBrushName_.clear();
-          pulseStartTime_ = -1.0f;
-          isPulsing = false;
-        }
-      }
+      auto [isPulsing, pulseElapsed] = computePulseState(brush);
+      isPulsing = isPulsing && isSelected;

Also applies to: 550-563

🧰 Tools
🪛 Clang (14.0.6)

[note] 386-386: +1

(clang)


[note] 389-389: +3, including nesting penalty of 2, nesting level increased to 3

(clang)


[note] 391-391: +4, including nesting penalty of 3, nesting level increased to 4

(clang)


[note] 394-394: +4, including nesting penalty of 3, nesting level increased to 4

(clang)


[warning] 388-388: floating point literal has suffix 'f', which is not uppercase

(readability-uppercase-literal-suffix)


[warning] 390-390: variable 'currentTime' is not initialized

(cppcoreguidelines-init-variables)


[warning] 391-391: statement should be inside braces

(readability-braces-around-statements)


[warning] 396-396: floating point literal has suffix 'f', which is not uppercase

(readability-uppercase-literal-suffix)

🪛 Cppcheck (2.20.0)

[style] 390-390: The function 'getSpriteCount' is never used.

(unusedFunction)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ImguiMapEditor/UI/Widgets/TilesetGridWidget.cpp` around lines 386 - 399,
Extract the duplicated pulse animation logic into a private helper on
TilesetGridWidget: add a declaration std::pair<bool,float>
computePulseState(const Brushes::IBrush* brush); to the class header, implement
computePulseState to use pulseBrushName_, pulseStartTime_, PULSE_DURATION and
ImGui::GetTime() to compute and return {isPulsing, pulseElapsed} while resetting
pulseBrushName_ and pulseStartTime_ when the duration elapses, and replace the
two identical blocks (the ones using isPulsing, pulseElapsed and
brush->getName() == pulseBrushName_) with calls to computePulseState(brush) to
unpack the returned pair.

Consolidated version-specific data path resolution logic into a central `resolveDataPath` helper and refactored TilesetGridWidget pulse animation logic for improved maintainability. Updated build system to conditionally handle data directory copying to prevent missing directory errors.
- `ImguiMapEditor/CMakeLists.txt` — replaced absolute directory copy with a conditional `EXISTS` check for the data directory
- `ImguiMapEditor/Domain/ClientVersion.cpp` — delegated path resolution to `resolveDataPath`
- `ImguiMapEditor/Domain/ClientVersion.h` — added `resolveDataPath` helper method to encapsulate path resolution logic
- `ImguiMapEditor/Services/Map/MapLoadingService.cpp` — utilized `resolveDataPath` to simplify resource loading paths
- `ImguiMapEditor/UI/Widgets/TilesetGridWidget.cpp` — extracted pulse animation state to `computePulseState`, optimized label truncation
- `ImguiMapEditor/UI/Widgets/TilesetGridWidget.h` — declared `computePulseState` helper
@github-actions github-actions Bot added the Area: Build Changes to CMake, vcpkg, CI/CD, or build configuration. label Jun 4, 2026

@github-actions github-actions Bot 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.

📋 Review Summary

This PR introduces significant improvements to data directory management and version-specific asset loading. By resolving data paths relative to the application's data folder and supporting version-specific subdirectories, it lays the groundwork for better multi-client support.

However, there are some critical issues that need to be addressed:

  1. Incomplete Modal Dialog Implementation: The PR adds callbacks to MainWindow and includes NewMapPanel.h, but the methods showNewMapDialog() and showOpenSecDialog() are not defined in the .cpp file, which will cause linker errors. Furthermore, the NewMapDialog component is not instantiated or rendered within MainWindow.
  2. Path Resolution Robustness: Several locations still rely on std::filesystem::current_path(), which can be unreliable depending on how the application is launched.
  3. Redundant Normalization: The normalization logic in ClientVersion is spread across both the setter and the resolver, which could be consolidated.

🔍 General Feedback

  • Correctness: Stray characters in Tile.cpp and CMakeLists.txt were correctly identified and removed.
  • Maintainability: The refactoring of TilesetGridWidget's brush card rendering and pulse state computation improves code readability.
  • Architecture: Moving towards version-specific data directories is a great architectural decision that solves many asset conflict issues.

Please address the missing definitions in MainWindow.cpp to ensure the project remains buildable.

// Resolve the version-specific data directory to an absolute path.
// Returns empty path if data_directory_ is not set or doesn't exist.
// Handles both "1098" and "data/1098" formats.
std::filesystem::path resolveDataPath() const {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Relying on std::filesystem::current_path() for data folder resolution can be brittle if the application is launched from a different directory. It is generally safer to use the executable's directory or a configured base path.

std::filesystem::path dir_path(data_directory_);
if (dir_path.has_parent_path() && dir_path.parent_path().filename() == "data") {
auto stripped = std::filesystem::current_path() / "data" / dir_path.filename();
if (std::filesystem::exists(stripped)) return stripped;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 While this check handles the case where data_directory_ was manually set to a path containing "data/", the setDataDirectory() method already performs this normalization. It might be cleaner to ensure data_directory_ is always normalized upon assignment to avoid redundant checks during path resolution.

auto otb = data_dir / "items.otb";
if (std::filesystem::exists(otb)) return otb;
break;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟢 Good change. Removing the fallback to client_path_ for items.otb ensures that only the intended editor-specific metadata is used, preventing potential issues with incompatible OTB files found in client directories.

if (!tilesets_loaded) {
spdlog::warn("materials.xml could not be loaded. Falling back to direct tileset/palette loading.");
tilesets_loaded = tileset_service_.loadTilesets(app_data_path);
// Load tilesets, palettes, brushes from version data directory

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟢 Using version_data_path here correctly points the editor to the version-specific assets, which is a major improvement for multi-version support.

auto stripped = std::filesystem::current_path() / "data" / dir_path.filename();
if (std::filesystem::exists(stripped)) return stripped;
}
return {};

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 secondary stripping logic seems redundant since setDataDirectory() already handles this normalization. Ensuring data_directory_ is always normalized upon assignment would allow simplifying this method to just checking the current_path() / "data" / data_directory_ candidate.

UI::CreaturePropertiesDialog creature_properties_dialog_;

// Editor-state modal dialog callbacks
std::function<void(const UI::NewMapPanel::State&)> new_map_callback_;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 These callbacks are added to support the new modal dialogs, but the corresponding methods showNewMapDialog() and showOpenSecDialog() are declared in the header without definitions in MainWindow.cpp. This will lead to linker errors.

Additionally, MainWindow lacks a NewMapDialog member and the necessary render() call in renderEditor(), which are required for these dialogs to function.

@karolak6612 karolak6612 merged commit 8f672f7 into WIP-Brushes Jun 4, 2026
4 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: Brushes Changes related to the brush system (ground, wall, doodad, etc.). Area: Build Changes to CMake, vcpkg, CI/CD, or build configuration. Area: Data Changes to data files (data/ — materials, clients.xml, menubar.xml). Area: UI Changes to the wxWidgets GUI (palettes, dialogs, menus, toolbar). Type: Bug Inconsistencies or issues which will cause a problem for users. Type: Enhancement New feature or request.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant