Skip to content

feat(weather editor): integrate weather picker and rework feature menu#1817

Closed
SkrubbySkrubInAShrub wants to merge 23 commits into
community-shaders:devfrom
SkrubbySkrubInAShrub:weather-picker-rework
Closed

feat(weather editor): integrate weather picker and rework feature menu#1817
SkrubbySkrubInAShrub wants to merge 23 commits into
community-shaders:devfrom
SkrubbySkrubInAShrub:weather-picker-rework

Conversation

@SkrubbySkrubInAShrub
Copy link
Copy Markdown
Collaborator

@SkrubbySkrubInAShrub SkrubbySkrubInAShrub commented Feb 4, 2026

This is a major PR for after 1.4.8 is released, but I want to see what feedback I can get.

  • Removed the Weather Picker Feature
  • Merged the Weather Picker functionality with the weather editor
  • Added instant weather changes
  • Added weather lock toggle
  • Added Time Scale Slider
  • Added Time Slider
  • Added Time Pause
  • Reworked UI
  • Fixed game freeze that would be caused by waiting while the game is paused, both due to the weather widget and the new pause function.

Summary by CodeRabbit

  • New Features

    • Major Weather Editor upgrade: real-time editing, time controls (pause/scale), detailed weather panels, overlay display, colorized names, flag-based filtering, analytics, and persistent weather details window.
  • Chores

    • Removed legacy WeatherPicker and migrated references to Weather Editor.
    • Renamed feature registry entry from weatherPicker to weatherEditor.
  • Documentation

    • Removed WeatherPicker from README/docs and updated UI text to reference Weather Editor.
  • UI

    • Category icon mapping updated so "Utility" shows the debug-style icon.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 4, 2026

📝 Walkthrough

Walkthrough

Removed the WeatherPicker feature and its artifacts; introduced a new overlay-based WeatherEditor (headers, implementation, settings, UI, time controls, weather loading/filtering, colorized displays) and updated globals, menu wiring, docs, and UI category mapping accordingly.

Changes

Cohort / File(s) Summary
Feature list & globals
src/Feature.cpp, src/Globals.cpp, src/Globals.h
Removed WeatherPicker include/extern/instance and its registration; switched global references to WeatherEditor.
New WeatherEditor
src/Features/WeatherEditor.h, src/Features/WeatherEditor.cpp
Added overlay-based WeatherEditor: new base class, settings struct, extensive public/static UI/rendering/data methods, time controls, weather loading/filtering, color utilities, overlay draw/isVisible, and static state.
WeatherPicker removal
src/Features/WeatherPicker.h, src/Features/WeatherPicker.cpp, features/Weather Picker/Shaders/Features/WeatherPicker.ini
Deleted WeatherPicker header, implementation, and INI — removing its public API, helpers, and static state.
Integration updates
src/Features/WetnessEffects.cpp, src/Menu.cpp
Replaced WeatherPicker references/includes with WeatherEditor; Menu now delegates weather details to WeatherEditor and adds BuildCategoryCounts().
Docs & readme
.claude/CLAUDE.md, package/SKSE/Plugins/CommunityShaders/Overrides/README.md
Renamed public feature label to weatherEditor and removed WeatherPicker from README/docs.
UI category mapping
src/Utils/UI.cpp
Changed DrawCategoryHeader branch from "Debug" → "Utility" for icon mapping.

Sequence Diagram(s)

sequenceDiagram
  participant User as "User / Menu"
  participant Menu as "Menu"
  participant Editor as "WeatherEditor (overlay)"
  participant Game as "Game / RE::Sky / Engine"

  rect rgba(200,200,255,0.5)
  User->>Menu: Open Weather Details
  Menu->>Editor: Invoke RenderWeatherDetailsWindow / show overlay
  end

  rect rgba(200,255,200,0.5)
  Editor->>Editor: LoadAllWeathers / UpdateFilteredWeathers (DataLoaded/Prepass)
  Editor->>Game: Query current sky/weather (RenderWeatherControls)
  Game-->>Editor: Current sky/weather state
  end

  rect rgba(255,200,200,0.5)
  User->>Editor: Interact (time controls, pick weather)
  Editor->>Game: Apply time/weather changes (pause, accelerate, transition)
  Game-->>Editor: Updated state / confirmation
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • davo0411
  • alandtse

Poem

🐰
I hopped through code and swapped the sky,
Picker shelved, the Editor hops high,
Sliders, colors, overlay light,
I tweak the clouds from dawn to night,
A rabbit’s nudge — the weather's right.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changeset: integrating weather picker functionality into weather editor and reworking the feature menu structure.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Feb 4, 2026

Using provided base ref: 5ed11de
Using base ref: 5ed11de
Base commit date: 2026-02-05T00:48:53-08:00 (Thursday, February 05, 2026 12:48 AM)
No actionable suggestions for changed features.

alandtse and others added 2 commits February 5, 2026 00:06
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@src/Features/WeatherEditor.cpp`:
- Around line 477-503: In WeatherEditor::DisplayPrecipitationInfo, the check for
particleTexture.textureName uses .c_str() which is never null so the "None"
branch never runs; change the condition to test if
particleTexture.textureName.empty() (or !empty()) to detect a missing texture
name, keeping the GET_INSTANCE_MEMBER(particleTexture,
weather->precipitationData) usage and preserving the same log messages and
tooltip behavior.
- Around line 80-124: Replace the hardcoded "Sleep/Wait Menu" string in
WeatherEditor::Prepass (the ui->IsMenuOpen call) with the appropriate named
constant: use RE::SleepWaitMenu::MENU_NAME if that symbol exists; if not, add a
module-level constant (e.g., constexpr auto kSleepWaitMenuName = "Sleep/Wait
Menu") and use that constant instead to match the project's MENU_NAME pattern
and avoid magic strings.

In `@src/Menu.cpp`:
- Around line 1069-1082: The current Menu::DrawWeatherDetailsWindow only checks
WeatherDetailsWindow.Enabled but must also ensure the weatherEditor feature
finished initialization; add a guard before calling RenderWeatherDetailsWindow
to return early if globals::features::weatherEditor is not initialized/loaded
(e.g. check a Loaded/Initialized flag or an IsLoaded()/IsInitialized() method
that is set in DataLoaded()), then proceed to set p_open and call
RenderWeatherDetailsWindow only when the feature is confirmed loaded.
🧹 Nitpick comments (2)
.claude/CLAUDE.md (1)

1-3: PR metadata: tighten conventional-commit scope + add issue refs (optional)

The title is close to conventional commit format, but scopes typically avoid spaces and keep the title ≤50 chars. Consider something like feat(weather-editor): integrate picker into editor. If the pause/wait freeze fix or time-control feature maps to a tracked issue, add a Fixes/Addresses #… reference in the PR description.

As per coding guidelines: “Format: type(scope): description” with a 50-character title limit and “Suggest adding appropriate GitHub keywords (Fixes/Closes/Addresses #…) for bug fixes or features.”

src/Features/WeatherEditor.h (1)

40-57: Add Doxygen comments for the new public helpers.

Public API surface expanded (e.g., RenderWeatherDetailsWindow, DisplayWeatherInfo, RenderCoreWeatherDetails, RenderFeatureWeatherAnalysis, RenderWeatherControls, RenderWeatherInformationDisplay). Please add brief Doxygen comments for these new entry points.

As per coding guidelines: “Include Doxygen comments for all public methods, especially graphics-related functions.”

Comment thread src/Features/WeatherEditor.cpp
Comment thread src/Features/WeatherEditor.cpp
Comment thread src/Menu.cpp
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Feb 4, 2026

✅ A pre-release build is available for this PR:
Download

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/Features/WeatherEditor.cpp`:
- Around line 247-277: DrawTimeControls currently uses local statics
(s_isTimePaused, s_savedTimeScale) causing desync with
EditorWindow::IsTimePaused() used in Prepass; replace the local static state by
centralizing pause state and saved timeScale into a single shared location
(e.g., add or use EditorWindow::IsTimePaused/SetTimePaused and a shared
EditorWindow::savedTimeScale float or a new TimeState struct) so both
DrawTimeControls and Prepass read/write the same flags; update DrawTimeControls
to call the centralized API to query/set pause and saved scale, preserve the
sleep/wait check logic (UI::IsMenuOpen("Sleep/Wait Menu")) and ensure restoring
time uses the shared savedTimeScale rather than a local default.
🧹 Nitpick comments (2)
src/Features/WeatherEditor.cpp (2)

390-405: Sanitize persisted window position values before use.
If config values are non‑finite or out-of-range, reset to defaults to avoid invalid coordinates and UI instability.

As per coding guidelines: **/*.{cpp,h,hpp}: Validate user input and prevent DirectX crashes from malformed configurations.


681-845: Optional: cache weather label strings to avoid per‑frame allocations.
weatherLabels is rebuilt every frame; consider rebuilding only when s_filteredWeathers changes.

Comment thread src/Features/WeatherEditor.cpp
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/Features/WeatherEditor.cpp`:
- Around line 780-786: The lock path can call editorWindow->LockWeather with a
null sky->currentWeather; update the ImGui button handler so it checks that sky
and sky->currentWeather are non-null before calling
editorWindow->LockWeather(sky->currentWeather), and if currentWeather is null
simply skip the LockWeather call (or show/disable the lock action) to avoid
breaking the Prepass() lock enforcement logic; locate this change around the
ImGui::Button(lockLabel) block that calls editorWindow->UnlockWeather() /
LockWeather(...) and add the null guard for sky->currentWeather.
- Around line 1194-1205: The DrawOverlay auto-enable logic forces
WeatherDetailsWindow.Enabled = true every frame when
WeatherDetailsWindow.ShowInOverlay and overlayVisible are true, so closing the
window is immediately undone; change this to only auto-enable once on the
transition when the overlay first becomes visible (track previous overlayVisible
state in WeatherEditor, e.g., a member like prevOverlayVisible) and call
WeatherDetailsWindow.Enabled = true only when overlayVisible is true and
prevOverlayVisible was false, otherwise do not overwrite Enabled; continue to
pass &WeatherDetailsWindow.Enabled to RenderWeatherDetailsWindow so the user can
close it and update prevOverlayVisible each frame.
🧹 Nitpick comments (1)
src/Features/WeatherEditor.cpp (1)

1083-1090: Redundant condition in flag check.

The condition flagNames.size() == 1 || (flagNames.size() == 1 && flagNames[0] == "None") has a redundant second clause. If flagNames.size() == 1 is true, the entire condition is already true.

Did you intend to check for a single non-"None" flag? If so:

♻️ Suggested simplification
-	if (flagNames.empty() || flagNames.size() == 1 || (flagNames.size() == 1 && flagNames[0] == "None")) {
+	if (flagNames.empty() || flagNames.size() == 1) {

Or if the intent was to handle "None" specifically:

-	if (flagNames.empty() || flagNames.size() == 1 || (flagNames.size() == 1 && flagNames[0] == "None")) {
+	if (flagNames.empty() || (flagNames.size() == 1 && flagNames[0] != "None")) {

Comment thread src/Features/WeatherEditor.cpp
Comment thread src/Features/WeatherEditor.cpp
Automated formatting by clang-format, prettier, and other hooks.
See https://pre-commit.ci for details.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/Features/WeatherEditor.cpp`:
- Around line 242-322: The time-scale slider writes a min 0.1 value back into
calendar->timeScale->value while the game is paused (timeScale==0), causing
unintentional resumes; update DrawTimeControls so the SliderFloat for
s_timeScaleSlider is either disabled when the editorWindow reports
IsTimePaused() (or calendar->timeScale->value==0) or, if you must show a slider,
only display s_timeScaleSlider but guard the assignment so that when
calendar->timeScale->value==0 you do not write s_timeScaleSlider back into
calendar->timeScale->value; instead use editorWindow->savedTimeScale to show a
nonzero slider position and restore calendar->timeScale->value from
savedTimeScale only when ResumeTime()/PauseTime() logic actually unpauses.
🧹 Nitpick comments (1)
src/Features/WeatherEditor.cpp (1)

681-845: Consider caching weather labels to avoid per-frame allocations.

Rebuilding weatherLabels every frame can be costly with large mod lists. Cache and rebuild only when s_filteredWeathers changes to keep the UI responsive.
As per coding guidelines: Always consider GPU workload and user experience when implementing graphics features.

♻️ Suggested refactor
-    // Weather Selection - now with colored text
-    std::vector<std::string> weatherLabels;
-    weatherLabels.reserve(s_filteredWeathers.size());
-    for (const auto& weather : s_filteredWeathers) {
-        weatherLabels.push_back(Util::FormatWeather(weather));
-    }
+    // Weather Selection - now with colored text
+    static std::vector<std::string> weatherLabels;
+    static uint32_t s_labelsFilter = 0;
+    if (s_labelsFilter != s_weatherFlagFilter || weatherLabels.size() != s_filteredWeathers.size()) {
+        weatherLabels.clear();
+        weatherLabels.reserve(s_filteredWeathers.size());
+        for (const auto& weather : s_filteredWeathers) {
+            weatherLabels.push_back(Util::FormatWeather(weather));
+        }
+        s_labelsFilter = s_weatherFlagFilter;
+    }

Comment on lines +242 to +322
void WeatherEditor::DrawTimeControls()
{
ImGui::Spacing();
Util::DrawSectionHeader("Time Controls");
ImGui::Spacing();

auto calendar = RE::Calendar::GetSingleton();
if (calendar && calendar->gameHour && calendar->timeScale) {
static constexpr float s_vanillaTimeScale = 20.0f;
auto editorWindow = EditorWindow::GetSingleton();
bool isTimePaused = editorWindow->IsTimePaused();

// Check if sleep/wait menu is open - don't sync pause state during wait
auto ui = RE::UI::GetSingleton();
bool sleepWaitMenuOpen = ui && ui->IsMenuOpen(RE::SleepWaitMenu::MENU_NAME);

// Check if time is actually paused (timeScale is 0), but ignore during sleep/wait
if (!sleepWaitMenuOpen) {
if (calendar->timeScale->value == 0.0f && !isTimePaused) {
// External pause: keep a sane restore value for the shared state.
editorWindow->savedTimeScale = s_vanillaTimeScale;
} else if (calendar->timeScale->value > 0.0f && isTimePaused) {
// External resume: clear the shared pause state.
editorWindow->timePaused = false;
}
isTimePaused = editorWindow->IsTimePaused();
}

if (ImGui::Button(isTimePaused ? "Resume Time" : "Pause Time", ImVec2(120, 0))) {
if (isTimePaused) {
editorWindow->ResumeTime();
} else {
editorWindow->PauseTime();
}
isTimePaused = editorWindow->IsTimePaused();
}
if (auto _tt = Util::HoverTooltipWrapper()) {
ImGui::Text("Pause or resume game time progression");
}

ImGui::SameLine();

ImGui::SliderFloat("Game Time", &calendar->gameHour->value, 0.0f, 23.99f, "%.2f");
if (auto _tt = Util::HoverTooltipWrapper()) {
ImGui::Text("Adjust the current game time");
}

// Timescale multiplier (exponential scale, vanilla at midpoint)
static float s_timeScaleSlider = s_vanillaTimeScale;

// Sync slider with actual value (handles external changes like reset button)
if (std::abs(calendar->timeScale->value - s_timeScaleSlider) > 0.01f) {
s_timeScaleSlider = calendar->timeScale->value;
}

if (ImGui::Button("Reset Speed", ImVec2(120, 0))) {
calendar->timeScale->value = s_vanillaTimeScale;
s_timeScaleSlider = s_vanillaTimeScale;
editorWindow->savedTimeScale = s_vanillaTimeScale;
}
if (auto _tt = Util::HoverTooltipWrapper()) {
ImGui::Text("Reset time speed to vanilla (%.1fx)", s_vanillaTimeScale);
}

ImGui::SameLine();

if (ImGui::SliderFloat("##TimeScale", &s_timeScaleSlider, 0.1f, 4000.0f,
s_timeScaleSlider == s_vanillaTimeScale ? "Vanilla Speed" : "", ImGuiSliderFlags_Logarithmic)) {
calendar->timeScale->value = s_timeScaleSlider;
}

ImGui::SameLine();

ImGui::Text("%.1fx", calendar->timeScale->value);
if (auto _tt = Util::HoverTooltipWrapper()) {
ImGui::Text("Adjust how fast time passes (vanilla: %.1fx)", s_vanillaTimeScale);
}
}

ImGui::Spacing();
}
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.

⚠️ Potential issue | 🟠 Major

Pause can be unintentionally cleared by the time‑scale slider.

When timeScale is 0 (paused), the logarithmic slider’s min of 0.1 can clamp the value and then write 0.1 back, effectively resuming time or jittering the slider. Consider disabling the slider when paused or syncing it to the saved (non‑zero) scale without writing to the engine.

💡 Suggested fix
-        // Sync slider with actual value (handles external changes like reset button)
-        if (std::abs(calendar->timeScale->value - s_timeScaleSlider) > 0.01f) {
-            s_timeScaleSlider = calendar->timeScale->value;
-        }
+        // Sync slider with actual value (handles external changes like reset button)
+        if (isTimePaused) {
+            s_timeScaleSlider = std::max(editorWindow->savedTimeScale, 0.1f);
+        } else if (std::abs(calendar->timeScale->value - s_timeScaleSlider) > 0.01f) {
+            s_timeScaleSlider = calendar->timeScale->value;
+        }

-        if (ImGui::SliderFloat("##TimeScale", &s_timeScaleSlider, 0.1f, 4000.0f,
-                s_timeScaleSlider == s_vanillaTimeScale ? "Vanilla Speed" : "", ImGuiSliderFlags_Logarithmic)) {
-            calendar->timeScale->value = s_timeScaleSlider;
-        }
+        ImGui::BeginDisabled(isTimePaused);
+        if (ImGui::SliderFloat("##TimeScale", &s_timeScaleSlider, 0.1f, 4000.0f,
+                s_timeScaleSlider == s_vanillaTimeScale ? "Vanilla Speed" : "", ImGuiSliderFlags_Logarithmic)) {
+            calendar->timeScale->value = s_timeScaleSlider;
+        }
+        ImGui::EndDisabled();
🤖 Prompt for AI Agents
In `@src/Features/WeatherEditor.cpp` around lines 242 - 322, The time-scale slider
writes a min 0.1 value back into calendar->timeScale->value while the game is
paused (timeScale==0), causing unintentional resumes; update DrawTimeControls so
the SliderFloat for s_timeScaleSlider is either disabled when the editorWindow
reports IsTimePaused() (or calendar->timeScale->value==0) or, if you must show a
slider, only display s_timeScaleSlider but guard the assignment so that when
calendar->timeScale->value==0 you do not write s_timeScaleSlider back into
calendar->timeScale->value; instead use editorWindow->savedTimeScale to show a
nonzero slider position and restore calendar->timeScale->value from
savedTimeScale only when ResumeTime()/PauseTime() logic actually unpauses.

@SkrubbySkrubInAShrub SkrubbySkrubInAShrub marked this pull request as draft February 5, 2026 14:18
@SkrubbySkrubInAShrub
Copy link
Copy Markdown
Collaborator Author

SkrubbySkrubInAShrub commented Feb 6, 2026

I want to do this properly so I'm going to split this PR up.

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.

2 participants